一、MCP SDK概述
MCP(Model Context Protocol)是Anthropic推出的开放协议,旨在标准化AI模型与外部工具/数据源的交互方式。MCP服务器是实现了该协议的独立服务,可以被任何MCP客户端(如Claude Desktop、Claude Code)动态发现和调用。MCP官方提供了两套主流SDK,分别面向TypeScript/Node.js和Python生态系统。
TypeScript SDK
TypeScript SDK (@modelcontextprotocol/sdk) 提供了完整的类型定义和服务器/客户端类,适合在Node.js运行时中构建MCP服务器。它天然支持异步流式处理,完美适配现代JS开发范式。安装方式如下:
npm install @modelcontextprotocol/sdk
# 或
yarn add @modelcontextprotocol/sdk
# 或
pnpm add @modelcontextprotocol/sdk
Python SDK
Python SDK (mcp) 提供了同样完整的MCP协议实现,支持asyncio异步编程模型,适合AI/ML开发者快速搭建MCP服务。安装方式如下:
pip install mcp
# 或使用 uv(更快的包管理器)
uv add mcp
核心类 (TypeScript)
Server、Client、McpServer、McpClient。Server用于提供服务,Client用于消费服务。StdioServerTransport、SSEServerTransport用于传输层配置。
核心类 (Python)
Server、Session。提供装饰器风格注册工具和资源,支持async/await异步编程,与流行的Python框架深度集成。
核心类型定义
Tool、Resource、Prompt、CallToolResult、ReadResourceResult、GetPromptResult。这些类型定义了MCP三种原语的标准接口格式。
注意: MCP SDK遵循JSON-RPC 2.0协议规范。所有服务器与客户端之间的通信均通过格式化的JSON消息进行。理解JSON-RPC的消息结构(jsonrpc、method、params、id字段)对调试和开发MCP服务器非常有帮助。
二、项目初始化
创建一个新的MCP服务器项目需要从项目初始化、依赖安装到基本结构搭建。以下是TypeScript和Python两种方案的完整初始化步骤。
TypeScript 项目初始化
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node ts-node
# 创建 tsconfig.json
npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext
--outDir ./dist --rootDir ./src --declaration true
推荐的项目目录结构如下:
my-mcp-server/
├── src/
│ ├── index.ts # 入口文件,创建服务器实例
│ ├── tools/ # 工具实现
│ │ ├── weather.ts
│ │ └── calculator.ts
│ ├── resources/ # 资源实现
│ │ └── files.ts
│ └── prompts/ # 提示模板
│ └── templates.ts
├── package.json
├── tsconfig.json
└── README.md
Python 项目初始化
mkdir my-mcp-server
cd my-mcp-server
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install mcp httpx
# 或使用 pyproject.toml
# [project]
# name = "my-mcp-server"
# version = "0.1.0"
# dependencies = ["mcp", "httpx"]
推荐的Python项目目录结构:
my-mcp-server/
├── src/
│ ├── __init__.py
│ ├── server.py # 入口文件
│ ├── tools/ # 工具模块
│ │ ├── __init__.py
│ │ ├── weather.py
│ │ └── search.py
│ └── resources/ # 资源模块
│ ├── __init__.py
│ └── docs.py
├── pyproject.toml
└── README.md
点击复制
MCP Server 的核心设计原则:每个服务器可以独立提供 Tools(工具)、Resources(资源)和 Prompts(提示模板)三种原语中的任意组合。客户端通过 capabilities 协商来发现服务器支持哪些功能。开发者可以根据需要只实现其中一种或全部三种。
三、实现一个简单的Tool(工具)
Tool(工具)是MCP服务器中最常用的原语,它允许AI模型执行外部操作——查询数据库、调用API、进行计算等。每个Tool需要定义名称、描述和输入参数模式(inputSchema),并通过处理函数返回结果。
TypeScript 实现
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("城市名称"),
days: z.number().optional().describe("预报天数"),
},
async ({ city, days = 1 }) => {
// 这里可以调用真实天气API
const result = `查询城市:${city},未来${days}天天气:晴,温度15-25℃`;
return { content: [{ type: "text", text: result }] };
}
);
// 启动服务器
const transport = new StdioServerTransport();
await server.connect(transport);
Python 实现
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import TextContent
# 创建服务器实例
server = Server("weather-server")
@server.tool("get-weather")
async def get_weather(city: str, days: int = 1) -> list[TextContent]:
"""查询指定城市的天气信息"""
# 这里可以调用真实天气API
result = f"查询城市:{city},未来{days}天天气:晴,温度15-25℃"
return [TextContent(type="text", text=result)]
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream, server.create_initialization_options())
Tool 设计要点
设计Tool时需注意以下几点:第一,name使用小写字母和连字符的kebab-case命名,便于客户端调用;第二,description清晰描述工具用途,帮助AI模型理解何时使用;第三,inputSchema参数应尽可能精确,使用必选参数而非全可选,降低模型误调用概率;第四,返回结果使用标准化的content数组格式,支持text、image、resource等多种类型。
最佳实践: 为每个Tool编写详细的description。AI模型(如Claude)通过description来判断何时调用哪个Tool。一个模糊的description会导致模型在错误的场景下调用工具,或在正确的场景下忽略调用。好的description应当描述"什么情况下使用此工具"以及"返回什么类型的数据"。
四、实现Resources和Prompts
除Tools之外,MCP协议还定义了Resources(资源)和Prompts(提示模板)两种原语。Resources允许AI模型读取结构化的外部数据,如文件内容、数据库记录;Prompts则为常见任务提供可复用的提示模板。
实现 Resources
Resources采用类似文件系统的URI方案进行标识。每个Resource通过唯一的URI进行访问,支持静态资源和模板资源两种模式。静态资源对应固定URI(如file:///docs/readme),模板资源使用URI Template语法支持参数化访问。
// TypeScript 实现 Resources
// 静态资源
server.resource(
"config",
"config://app",
async (uri) => ({
contents: [{
uri: uri.href,
text: JSON.stringify({ version: "1.0", debug: false })
}]
})
);
// 模板资源 - 支持参数化
server.resource(
"user-profile",
new ResourceTemplate("users://{userId}/profile", { list: undefined }),
async (uri, { userId }) => ({
contents: [{
uri: uri.href,
text: `用户 ${userId} 的配置信息...`
}]
})
);
# Python 实现 Resources
@server.resource("config://app")
async def get_config(uri: str) -> str:
"""返回应用配置信息"""
return json.dumps({"version": "1.0", "debug": False})
@server.resource("users://{userId}/profile")
async def get_user_profile(uri: str, userId: str) -> str:
"""返回指定用户的配置信息"""
return f"用户 {userId} 的配置信息..."
实现 Prompts
Prompts是可复用的提示模板,支持参数化占位符。当客户端需要执行某种固定模式的任务时,可以通过Prompts获取预定义的提示内容,填充参数后发送给AI模型。
// TypeScript 实现 Prompts
server.prompt(
"code-review",
{
language: z.string().describe("编程语言"),
code: z.string().describe("代码内容"),
},
({ language, code }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `请审查以下${language}代码,指出潜在的问题和改进建议:\n\n${code}`
}
}]
})
);
server.prompt(
"translate",
{ text: z.string(), targetLang: z.string() },
({ text, targetLang }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `请将以下内容翻译成${targetLang}:\n\n${text}`
}
}]
})
);
# Python 实现 Prompts
@server.prompt("code-review")
async def code_review(language: str, code: str) -> list[Message]:
return [
Message(
role="user",
content=TextContent(
type="text",
text=f"请审查以下{language}代码,指出潜在的问题和改进建议:\n\n{code}"
)
)
]
重要: 三种原语的选择策略:如果你需要模型执行操作(写数据库、发邮件、算数学),使用Tool;如果你需要模型读取数据(查文档、看文件、获取配置),使用Resource;如果你需要模型按照固定格式处理重复任务,使用Prompt。一个服务器可以同时提供三种原语,客户端在connect时自动发现所有能力。
五、配置传输层
传输层决定了MCP服务器如何与客户端进行通信。MCP协议定义了两种标准传输方式:stdio(标准输入输出)和SSE(Server-Sent Events),各自适用于不同的部署场景。
stdio 传输
stdio传输是最简单的传输方式,服务器通过标准输入接收JSON-RPC请求,通过标准输出发送响应。客户端(如Claude Desktop)以子进程方式启动服务器,通过管道进行双向通信。
// TypeScript - stdio 传输
import { StdioServerTransport } from
"@modelcontextprotocol/sdk/server/stdio.js";
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Server running on stdio");
# Python - stdio 传输
from mcp.server.stdio import stdio_server
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream,
server.create_initialization_options())
使用stdio传输的场景:本地开发工具、Claude Desktop插件、与CI/CD流水线集成。stdio的优势在于零配置、低延迟、无需网络端口占用。但缺点是无法远程访问,服务器生命周期与客户端绑定。
SSE 传输
SSE传输使MCP服务器作为HTTP服务运行,支持远程访问和多个客户端连接。服务器通过HTTP POST接收客户端请求,通过SSE连接向客户端推送事件。
// TypeScript - SSE 传输(使用 Express)
import express from "express";
import {
SSEServerTransport
} from "@modelcontextprotocol/sdk/server/sse.js";
const app = express();
let transport = null;
app.get("/sse", (req, res) => {
transport = new SSEServerTransport("/messages", res);
server.connect(transport);
});
app.post("/messages", (req, res) => {
transport.handlePostMessage(req, res);
});
app.listen(3000, () => {
console.error("MCP Server running on http://localhost:3000/sse");
});
# Python - SSE 传输(使用 Starlette)
from starlette.applications import Starlette
from starlette.routing import Route
from mcp.server.sse import SseServerTransport
sse = SseServerTransport("/messages")
async def handle_sse(request):
async with sse.connect_sse(
request.scope, request.receive, request._send
) as (read_stream, write_stream):
await server.run(read_stream, write_stream,
server.create_initialization_options())
async def handle_messages(request):
await sse.handle_post_message(request.scope, request.receive)
routes = [
Route("/sse", endpoint=handle_sse),
Route("/messages", endpoint=handle_messages, methods=["POST"]),
]
app = Starlette(routes=routes)
使用SSE传输的场景:云端部署的MCP服务器、需要多客户端共享的服务、与现有Web服务集成的场景。SSE支持跨网络访问,服务器可独立运行,客户端无需本地安装。缺点是增加了网络延迟,需要处理认证和安全性。
点击复制
配置Claude Desktop连接MCP服务器(claude_desktop_config.json):
{
"mcpServers": {
"my-weather-server": {
"command": "node",
"args": ["path/to/dist/index.js"]
},
"my-python-server": {
"command": "uv",
"args": ["run", "path/to/server.py"]
}
}
}
对于SSE远程服务器,使用"url"字段替代"command"/"args"。
六、测试与调试
MCP服务器的测试和调试需要特殊工具和方法,因为其通信基于JSON-RPC协议而不是传统的REST API。MCP官方提供了Inspector工具,同时也推荐编写自动化测试来确保服务器稳定性。
MCP Inspector 交互式调试
MCP Inspector是官方提供的Web-based调试工具,可以连接到任何MCP服务器并交互式测试其Tools、Resources和Prompts。它提供了可视化的请求-响应面板,方便开发者观察原始JSON-RPC消息。
# 全局安装 Inspector(方式一:npx)
npx @modelcontextprotocol/inspector node dist/index.js
# 方式二:直接连接已运行的SSE服务器
npx @modelcontextprotocol/inspector --connect http://localhost:3000/sse
# 安装后打开浏览器访问 http://localhost:5173
Inspector提供的主要功能包括:查看服务器信息和capabilities声明、测试每个Tool的调用并查看原始JSON响应、浏览Resources列表并读取指定资源内容、测试Prompts模板并查看生成的消息内容、检查服务器日志输出、检查连接状态和错误信息。
编写自动化测试
推荐使用SDK提供的测试工具编写自动化测试,验证每个Tool和Resource的行为是否符合预期。
// TypeScript 自动化测试示例
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { InMemoryTransport } from
"@modelcontextprotocol/sdk/inMemory.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
async function testWeatherTool() {
const server = new McpServer({ name: "test", version: "1.0" });
server.tool("get-weather", { city: z.string() },
async ({ city }) => {
return { content: [{ type: "text", text: `天气:${city} 晴` }] };
}
);
// 使用内存传输进行测试
const [clientTransport, serverTransport] =
InMemoryTransport.createPair();
const client = new Client({ name: "test-client", version: "1.0" });
await Promise.all([
server.connect(serverTransport),
client.connect(clientTransport),
]);
const result = await client.request(
{ method: "tools/call", params: {
name: "get-weather", arguments: { city: "北京" }
}},
CallToolResultSchema
);
console.assert(result.content[0].text === "天气:北京 晴");
console.log("测试通过!");
}
# Python 自动化测试示例
import pytest
from mcp import ClientSession, StdioServerParameters
@pytest.mark.asyncio
async def test_get_weather():
async with ClientSession(
StdioServerParameters(
command="python",
args=["src/server.py"]
)
) as session:
await session.initialize()
result = await session.call_tool(
"get-weather",
{"city": "北京"}
)
assert "北京" in result.content[0].text
print("测试通过!")
常见错误排查
开发MCP服务器时常见的错误及解决方法:
1. 传输层未正确连接:检查transport是否正确创建并connect;stdio模式确保子进程路径正确;SSE模式检查端口是否被占用。
2. Tool参数校验失败:检查inputSchema定义是否与处理函数的参数签名一致;使用zod(TS)或Pydantic(Python)进行严格校验。
3. 返回格式错误:检查返回的content数组是否符合CallToolResult格式;确保JSON-RPC消息的id字段正确匹配请求。
4. 服务器崩溃/意外退出:检查未捕获的异常;在关键处添加try/catch;使用console.error(TS)或logging(Python)记录日志到stderr。
调试技巧: stdio传输模式下,所有console.log输出会被误认为JSON-RPC响应发送给客户端,导致协议解析错误。应始终使用console.error(输出到stderr)打印调试日志,因为stderr不会被MCP传输层捕获。在开发过程中,Inspector的"Server Logs"面板可以实时查看stderr输出。
七、发布MCP服务器
完成开发后,可以将MCP服务器发布到公共包管理器供他人使用。根据SDK的选择,分别发布到npm(TypeScript)或PyPI(Python)。良好的文档和配置示例对于用户采用至关重要。
发布到 npm
# 构建 TypeScript 项目
npm run build # tsc 编译
# 确保 package.json 包含正确配置
# {
# "name": "@your-org/my-mcp-server",
# "type": "module",
# "main": "./dist/index.js",
# "bin": {
# "my-mcp-server": "./dist/index.js"
# },
# "files": ["dist/"]
# }
# 登录并发布
npm login
npm publish --access public
发布到 PyPI
# 使用 pyproject.toml 配置
# [build-system]
# requires = ["setuptools>=68.0", "wheel"]
# build-backend = "setuptools.build_meta"
#
# [project]
# name = "my-mcp-server"
# version = "0.1.0"
# description = "My custom MCP server"
# requires-python = ">=3.10"
# dependencies = ["mcp", "httpx"]
# 构建并发布
python -m build
twine upload dist/*
# 或使用 uv
uv build
uv publish
编写用户配置文档
发布后,用户需要将你的MCP服务器配置到他们的客户端中。在README中提供清晰的配置示例至关重要,包括Claude Desktop的claude_desktop_config.json配置示例、Claude Code的CLAUUDE.md或settings.json配置方式以及使用SSE传输时的远程连接URL。
点击复制
完整的README文档应包含:
1. 项目简介:说明MCP服务器的功能和适用场景
2. 快速开始:从安装到运行的最简步骤
3. 支持的Tools列表:每个Tool的名称、参数说明和返回值
4. 支持的Resources列表:每个Resource的URI模式和说明
5. 支持的Prompts列表:每个Prompt的参数和示例
6. 配置示例:Claude Desktop和Claude Code的配置JSON
7. 开发指南:本地开发、测试和构建命令
8. 许可证和贡献指南:MIT或Apache-2.0许可证,PR和Issue流程
一个好的README是项目被广泛采用的关键。
在GitHub上开源
将MCP服务器开源到GitHub可以吸引社区贡献,形成良性发展循环。推荐在仓库中配置好GitHub Actions CI/CD流水线,自动运行测试、构建和发布。在Description中添加"MCP Server"标签,便于其他开发者搜索发现。可以在MCP官方Awesome列表(github.com/modelcontextprotocol/awesome-mcp-servers)中提交你的服务器,获得更广泛的曝光。
安全提示: MCP服务器具有执行代码和访问数据的权限,在发布前务必进行安全审查。特别注意:不要硬编码API密钥或敏感凭证,应通过环境变量注入;实现用户认证机制(对于SSE远程服务器);对用户输入进行验证和清理,防止注入攻击;在文档中明确告知用户服务器需要哪些权限和访问哪些资源。