想自己写 MCP 的话,可以先看这个小项目
我用一个小功能来演示怎么开发 MCP。
在 AI 工具里说「在地图里搜索西湖景区」,这个服务会打开 macOS 自带的「地图」,并发起搜索。
功能就这一个,下面看它是怎么工作的。
模型和 MCP 的关系
模型负责听懂你的话,再决定调哪个工具。工具登记在 MCP 服务里,模型只根据名称、说明和参数来选。MCP 是两者之间的协议:AI 工具启动服务,把这次选择发过去;服务执行完,把结果交还模型。
MCP 是怎么工作的
MCP 服务内部就做这几件事。它和 AI 工具用标准输入、标准输出通信,这叫 stdio。标准输出只放协议消息,日志写到标准错误。服务先登记工具,写上名称、说明和参数。调用到达后,按参数执行,再把结果送回去。
这个仓库叫 local-maps-mcp。要看的就是三个文件。
package.json是项目说明书。bin和npm start都指向入口src/index.js。src/index.js用官方 SDK@modelcontextprotocol/server创建服务,用 Zod 描述参数,登记唯一的工具search_map。参数query是必填字符串,说明里写着:用户要求在地图里搜索、查找或打开某个地点时使用。src/maps.js只做地图这一段:检查参数、拼出链接、调用系统的open。协议消息它不碰。
过程分析
- 平时由 AI 工具启动 MCP 服务里的
src/index.js。 - 调用到达后,
index.js不做地图的事,只把query交给maps.js里的openMapSearch()。 maps.js把地点拼成一条 Apple 地图链接,搜索词放在q参数里,例如https://maps.apple.com/?q=西湖景区。再用系统命令open打开「地图」,不需要申请地图开放平台的 API Key
/usr/bin/open -b com.apple.Maps <地图链接>
-b com.apple.Maps 指定由系统「地图」打开这个链接。open 返回,表示请求已经交给系统。
怎么配置 MCP 服务
AI 工具要有一份配置,写明服务名、用来启动的 Node.js,以及入口 src/index.js。
因为我使用的 AI 工具是 Cursor,所以这里以 Cursor 为例。
Cursor 会合并项目级和用户级配置,同名时以项目级为准。
项目级放在 .cursor/mcp.json,打开这个项目才生效。
用户级放在 ~/.cursor/mcp.json,这台电脑上的项目都能用。
写进用户级时,保留文件里原有的其他服务。
{
"mcpServers": {
"local-maps-mcp": {
"command": "node",
"args": ["${workspaceFolder}/src/index.js"]
}
}
}
在 Agent 对话里输入:
在地图里搜索西湖景区
模型会调用 search_map,参数是 { "query": "西湖景区" }。系统地图来到前台并接收这次搜索,对话里出现:
已向系统地图发送搜索请求:西湖景区
怎么发布 MCP 服务
希望别人也能用,就发到 npm。登录和发布都指定官方源。登录时 npm 会要动态验证码,发布时可能再要一次。
// 在命令行里进入到项目目录
npm login --registry https://registry.npmjs.org
npm publish --registry https://registry.npmjs.org
看到 + local-maps-mcp@1.0.0,就是发布成功。
别人装的是发布后的包,入口不再是仓库里的源文件。配置可以写成:
{
"mcpServers": {
"local-maps-mcp": {
"command": "npx",
"args": ["-y", "local-maps-mcp"]
}
}
}
也可以全局安装,再把启动命令写成 local-maps-mcp:
npm install -g local-maps-mcp --registry https://registry.npmjs.org
command -v local-maps-mcp
command -v 打印出可执行文件路径,就表示当前终端找得到它。
下面是完整代码,这个服务的仓库: