MCP 工具开发实战:从零给 AI 接入任意 API 的完整指南

什么是 MCP?为什么它能让 AI 接入任意 API
MCP(Model Context Protocol)是 Anthropic 于 2024 年底开源的一套标准化协议,专门用于解决一个老大难问题:如何让 AI 助手安全、可复用地调用外部工具与 API?
在 MCP 出现之前,每个 AI 应用都要自己实现工具调用逻辑,格式五花八门,维护成本极高。MCP 相当于给 AI 工具调用定义了一套"USB 接口标准"——只要遵循协议,任何工具都能即插即用。
目前已有大量开源社区 MCP Server 覆盖常见场景:浏览器自动化、数据库查询、GitHub 操作、Slack 消息、Google Drive 等。本文将带你从零实现一个能查询天气的 MCP Server,并与 Claude Desktop 集成。
环境准备与项目初始化
本教程使用 TypeScript + Node.js 实现 MCP Server。首先确认环境:
node -v # 需要 v18+ npm -v # 需要 v9+
创建项目并安装依赖:
mkdir my-weather-mcp && cd my-weather-mcp npm init -y npm install @modelcontextprotocol/sdk zod npm install -D typescript @types/node ts-node npx tsc --init
修改 tsconfig.json,确保以下配置:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
}
}实现核心:编写 MCP Server
创建 src/index.ts,这是整个 Server 的入口:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "weather-server",
version: "1.0.0",
});
// 定义工具:查询天气
server.tool(
"get_weather",
"查询指定城市的当前天气",
{
city: z.string().describe("城市名称,例如:Beijing、Shanghai"),
units: z.enum(["metric", "imperial"]).default("metric").describe("温度单位:metric=摄氏度,imperial=华氏度"),
},
async ({ city, units }) => {
// 调用 Open-Meteo 免费 API(无需 API Key)
const geocodeRes = await fetch(
`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=zh&format=json`
);
const geocodeData = await geocodeRes.json() as any;
if (!geocodeData.results?.length) {
return { content: [{ type: "text", text: `找不到城市:${city}` }] };
}
const { latitude, longitude, name, country } = geocodeData.results[0];
const tempUnit = units === "metric" ? "celsius" : "fahrenheit";
const weatherRes = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code&temperature_unit=${tempUnit}`
);
const weatherData = await weatherRes.json() as any;
const current = weatherData.current;
const tempSymbol = units === "metric" ? "°C" : "°F";
return {
content: [{
type: "text",
text: `${name}, ${country} 当前天气:\n温度:${current.temperature_2m}${tempSymbol}\n湿度:${current.relative_humidity_2m}%\n风速:${current.wind_speed_10m} km/h`
}]
};
}
);
// 定义资源:暴露支持的城市列表
server.resource(
"supported-cities",
"weather://supported-cities",
async () => ({
contents: [{
uri: "weather://supported-cities",
text: "支持全球任意城市,使用英文或拼音输入城市名即可。常用:Beijing, Shanghai, Shenzhen, Guangzhou, Chengdu"
}]
})
);
// 启动 Server(Stdio 模式,供 Claude Desktop 调用)
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Weather MCP Server 已启动");
}
main().catch(console.error);注意几个关键点:
McpServer是 SDK 提供的高层封装,负责处理所有 MCP 协议细节server.tool()第三个参数用zod定义参数 schema,AI 会根据这个生成调用参数StdioServerTransport表示通过标准输入输出通信,是 Claude Desktop 默认的连接方式我们使用了 Open-Meteo 的完全免费 API,无需注册,无需 API Key,适合学习使用
编译与本地测试
编译 TypeScript:
npx tsc
用 MCP Inspector 快速测试(官方调试工具):
npx @modelcontextprotocol/inspector node dist/index.js
浏览器打开 http://localhost:5173,在 Inspector 界面中点击 "Connect" 连接到你的 Server,然后找到 get_weather 工具,输入 {"city": "Shanghai"} 测试:
# 预期输出(成功): Shanghai, China 当前天气: 温度:22.5°C 湿度:68% 风速:12.3 km/h
如果看到这样的输出,说明 MCP Server 工作正常!
接入 Claude Desktop
找到 Claude Desktop 的配置文件并编辑:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/绝对路径/my-weather-mcp/dist/index.js"],
"env": {}
}
}
}重启 Claude Desktop,在对话框左下角会看到一个锤子图标(工具列表),点击可见 get_weather 工具已加载。现在对 Claude 说"帮我查一下深圳今天的天气",它会自动调用你的 MCP Server!
进阶:添加 Prompt 模板和错误处理
MCP 还支持 server.prompt(),预定义对话模板:
server.prompt(
"weather-report",
"生成一份完整的天气播报",
{
city: z.string(),
date: z.string().optional().describe("日期,默认今天"),
},
({ city, date }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `请用专业播音员的口吻,为 ${city} 生成 ${date ?? "今天"} 的天气播报,包括穿衣建议和出行提示。`
}
}]
})
);生产环境中,还需要做好错误处理和日志记录:
// 所有对外请求都包裹 try/catch
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}: ${res.statusText}`);
// ...
} catch (err) {
return {
content: [{ type: "text", text: `查询失败:${(err as Error).message}` }],
isError: true // 标记为错误响应,让 AI 知道需要重试或提示用户
};
}常见踩坑与解决方案
Claude Desktop 看不到工具:检查配置文件路径是否用了绝对路径,相对路径无效;确认 JSON 格式正确,可用
node -e "require('./config.json')"验证Server 启动就崩溃:记住不要在 Server 中用
console.log,Stdio 模式下标准输出是 MCP 协议通道,调试信息必须用console.error工具参数识别不准:优化
zod中的.describe()文字,AI 靠这些描述理解参数含义;参数描述越详细,AI 传值越准确异步工具超时:Claude Desktop 默认超时较短,耗时操作建议加进度提示,或将大任务拆分为多个小工具串联调用
部署到服务器后无法连接:Stdio 模式只支持本地进程通信;需要远程调用则切换为
SSEServerTransport(HTTP + Server-Sent Events 模式)
发布评论
热门评论区: