mcp 实验室
语音+MCP日历演示。
此项目是一个小型Express应用程序,它:
- 托管MCP服务器(流式HTTP传输),该服务器公开由CalDAV服务器支持的日历工具
- 托管一个HTTP“MCP客户端”端点,该端点向OpenAI兼容的聊天完成API发送提示,并让模型调用这些MCP工具
- 提供简单的浏览器UI(在
public/)记录音频,转录它(Whisper API),并将结果提示发送到MCP客户端端点
需求
- Node.js 18+(建议使用Node 20+)
- CalDAV服务器+凭据(或接受默认值,但工具没有用)
- 与OpenAI兼容的API终结点,用于:
- 聊天完成(/v1/chat/completions) - 音频转录(/v1/audio/transcriptions)
在这个实验室里, OPENAI_PROXY_URL 通常指向代理服务(不是直接指向OpenAI)。代理负责向传出请求添加任何所需的身份验证(例如API密钥)。
因为身份验证是由代理处理的,所以服务器使用直接的HTTP请求(通过 fetchData)而不是官方的OpenAI SDK。
快速开始
- 安装
npm install- 创建
.env
cp .env-sample .env然后编辑 .env 并设置 OPENAI_PROXY_URL 到您的代理/基本URL(例如。 http://localhost:1234 或您部署的代理)。
- 运行开发服务器
npm run dev- 打开演示UI
- 访问 http://localhost:3000/ - 点击“开始录制”,说出命令,然后点击“停止录制” - 点击“发送”
环境变量
创建一个 .env 项目根目录中的文件。
服务器
PORT(可选,默认值:3000)NODE_ENV(可选,例如。development)
MCP客户端+支持OpenAI的API
MCP_SERVER_URL(必填)
- 例子: http://localhost:3000/api/v1/mcp - 这可以指向同一个服务器实例。
OPENAI_PROXY_URL(必填)
- OpenAI兼容代理的基本URL(应用程序调用 /v1/chat/completions 和 /v1/audio/transcriptions 下面) - 例子: http://localhost:1234
OPENAI_MODEL(可选,默认值:gpt-4o)
音频转录(耳语)
OPENAI_TRANSCRIPTION_MODEL(可选,默认值:whisper-1)
CalDAV(日历备份存储)
CALDAV_SERVER_URL(可选,默认值:http://localhost:5232/)CALDAV_USERNAME(可选,默认值:username)CALDAV_PASSWORD(可选,默认值:password)
Radical(开发CalDAV服务器)
对于本地开发,您可以在不进行身份验证的情况下运行最小的Radicale CalDAV服务器。
macOS/Linux
- 安装Radical
python3 -m pip install --user radicale- 创建本地配置+数据文件夹
mkdir -p radicale-data/collections
cat > radicale.config <<'EOF'
[server]
hosts = 127.0.0.1:5232
[auth]
type = none
[storage]
filesystem_folder = ./radicale-data/collections
EOF- 运行 激进
radicale --config ./radicale.configWindows(PowerShell)
- 安装Radical
py -m pip install --user radicale- 创建本地配置+数据文件夹
New-Item -ItemType Directory -Force -Path .\radicale-data\collections | Out-Null
@'
[server]
hosts = 127.0.0.1:5232
[auth]
type = none
[storage]
filesystem_folder = ./radicale-data/collections
'@ | Set-Content -Encoding UTF8 .\radicale.config- 运行 激进
py -m radicale --config .\radicale.config- 将此应用程序指向Radical
在 .env:
CALDAV_SERVER_URL=http://localhost:5232/CALDAV_USERNAME=anything(忽略时auth=none)CALDAV_PASSWORD=anything(忽略时auth=none)
脚本
npm run dev–使用nodemon+ts节点运行服务器npm run build–将TypeScript编译为dist/npm start–运行编译后的服务器(dist/index.js)
API
基础路径为 /api/v1.
健康
GET /api/v1/
返回一个简单的JSON消息。
MCP服务器
POST /api/v1/mcp
实现MCP流式HTTP传输。
MCP客户端
POST /api/v1/client
接受以下任一选项:
- JSON(文本提示)
curl -sS \
-H 'Content-Type: application/json' \
-d '{"prompt":"List my events","timezone":"Europe/Helsinki"}' \
http://localhost:3000/api/v1/clientmultipart/form-data(音频上传)
字段名必须为 audio.
curl -sS \
-F 'audio=@command.webm' \
-F 'timezone=Europe/Helsinki' \
http://localhost:3000/api/v1/client答复:
{
"answer": "...",
"toolCalls": 2
}MCP工具
MCP服务器当前公开:
listEvents–列出主要CalDAV日历中的事件getEventsInTimeSlot–检查某个时间段的可用性(相对日期输入)createEvent–创建事件(相对日期输入+标题+可选描述/位置)
MCP客户端指示模型使用工具处理所有用户请求,并应用一些工作流规则(例如,当用户询问“是否有空”时,在创建事件之前检查可用性)。
故障排除
- 麦克风权限:浏览器将在第一次提示麦克风访问。如果录制失败,请检查站点权限。
- CalDAV认证:如果日历工具总是返回空结果,请验证
CALDAV_*值,并且您的用户至少有一个日历。 - 代理URL:
OPENAI_PROXY_URL必须是基本URL;应用程序调用/v1/chat/completions和/v1/audio/transcriptions在它下面。 - 上传清理:上传的音频在转录后尽最大努力删除;如果满足以下条件,请检查文件系统权限
uploads/出乎意料地增长。
______________________________________________________________________
API文件: POST /api/v1/client
通过MCP客户端运行用户提示。服务器将调用OpenAI兼容的Chat Completions API,模型可以调用MCP日历工具(通过MCP服务器URL)。
请求
支持的内容类型:
application/json(文本提示)
prompt(string,必填)-用户命令/问题timezone(字符串,可选)-IANA时区名称(默认为服务器的默认时区)。请参阅: tz数据库时区列表
JSON请求体示例:
{
"prompt": "List my events",
"timezone": "Europe/Helsinki"
}例子:
curl -sS \
-H 'Content-Type: application/json' \
-d '{"prompt":"List my events","timezone":"Europe/Helsinki"}' \
http://localhost:3000/api/v1/client选项2: multipart/form-data (音频)
audio(文件,必填)–要转录的音频文件(浏览器演示发送audio/webm)timezone(字符串,可选)–IANA时区名称
在此模式下,服务器首先转录音频,并使用转录文本作为提示。
例子:
curl -sS \
-F 'audio=@command.webm' \
-F 'timezone=Europe/Helsinki' \
http://localhost:3000/api/v1/client响应
成功时(HTTP 200):
{
"answer": "...",
"toolCalls": 2
}answer(string)-最终辅助输出toolCalls(number)–运行期间进行的工具调用总数
错误
400–请求正文无效(例如,缺失promptJSON格式,无效timezone)500–转录失败、OpenAI/MCP错误或意外的服务器错误
错误以JSON格式返回:
{
"message": "..."
}#� �m�c�p�-�l�a�b�s�-�2�6� � �
