MCP Server
md
文档:
1. 中文:https://mcpcn.com
2. 官网:https://modelcontextprotocol.io
3. vscode(github copilot):https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp/use-the-github-mcp-server?tool=vscode
4. 高德地图:https://lbs.amap.com/api/mcp-server/gettingstarted#s0
5. VScode 插件:https://code.visualstudio.com/mcp
6. MCP魔塔:https://www.modelscope.cn/mcp/servers/@modelscope/modelscope-mcp-server
7. 建议学习:https://mp.weixin.qq.com/s/ohHHXl4VNIkbPpFPAnqQhQ
MCP Server 概念
在编写代码之前,让我们用简单的语言回顾一下 MCP 的主要组成部分。
服务器 (Server)
一个 McpServer 是核心对象,负责管理:
能力 (Capabilities): 声明是否支持工具、资源和提示。
注册表 (Registry): 保存已注册的工具、资源和提示。
协议合规性: 处理传入的 JSON-RPC 消息 (initialize, callTool, readResource 等)。
在 Node.js (TypeScript/JavaScript) 中,你可以这样创建它:
点击展开代码
js
const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
capabilities: {
tools: { listChanged: true },
resources: { listChanged: true },
prompts: { listChanged: true }
}
});
工具 (Tool)
工具是 AI 可以调用来执行某些操作(计算或产生副作用)的函数。 注册工具时,你需要提供:
名称 (字符串), 例如:"calculate-bmi".
其参数的 Zod 模式(以便 MCP 可以自动验证)。
一个异步处理函数,接收解析后的参数并返回结果或错误。 示例:
点击展开代码
js
server.registerTool({
name: 'calculate-bmi',
schema: z.object({
weight: z.number().positive(),
height: z.number().positive()
}),
handler: async (params) => {
const { weight, height } = params;
return weight / (height * height);
}
});
注册后,AI客户端可以进行JSON- RPC调用
点击展开代码
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "callTool",
"params": {
"toolName": "calculate-bmi",
"arguments": {
"weight": 70,
"height": 1.75
}
}
}
资源 (Resources)
资源向 AI 暴露数据。它们类似于 "GET" 端点:
点击展开代码
js
server.resource(
'user-profile',
new ResourceTemplate('users://{userId}/profile', { list: undefined }),
async (uri, { userId }) => {
// 例如,从你的数据库获取
return {
contents: [{
uri: uri.href,
text: `Profile data for user ${userId}`
}]
};
}
);
那么客户端则进行如下调用:
点击展开代码
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "readResource",
"params": {
"resourceName": "user-profile",
"uri": "users://123/profile"
}
}
提示 (Prompts)
提示是可重用的消息模板。它们帮助为 LLM 格式化请求:
点击展开代码
js
server.prompt(
'review-code',
{ code: z.string() },
({ code }) => ({
messages: [{
role: 'user',
content: {
type: 'text',
text: `Please review this code:\n\n${code}`
}
}]
})
);
然后,客户端可以进行如下调用:
点击展开代码
json
{
"jsonrpc": "2.0",
"id": 5,
"method": "mcp/getPrompt",
"params": { "name": "review-code", "arguments": { "code": "const a = 1;" } }
}
demo如下
点击展开代码
js
/**
* server.js
*
* Express MCP Server (Streamable HTTP, Stateful)
*
* - 声明 MCP 能力(tools/resources/prompts)
* - 资源:config://app(静态)、users://{userId}/profile(动态)
* - 工具:calculate-bmi
* - 提示:review-code
* - 会话内持久化 McpServer 实例
*/
const express = require('express');
const { randomUUID } = require('crypto');
const { McpServer, ResourceTemplate } = require('@modelcontextprotocol/sdk/server/mcp.js');
const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
const { isInitializeRequest } = require('@modelcontextprotocol/sdk/types.js');
const { z } = require('zod');
const app = express();
// 便于 Express 解析所有 JSON 请求体。
app.use(express.json());
// 会话存储:我们维护一个内存中的对象 sessions,以 sessionId 为键,存储 { server, transport }。
// 这确保了每个客户端保持相同的 McpServer 实例,因此工具在多次调用之间保持注册状态
const sessions = {};
/*
初始化流程createMcpServer():
我们传递 capabilities: { tools, resources, prompts } 并将 listChanged 设为 true,以便在初始化握手期间,服务器明确通告可用的工具/资源/提示。
注册两个资源 (config://app 和 users://{userId}/profile)、一个工具 (calculate-bmi) 和一个提示 (review-code)。
*/
function createMcpServer() {
const server = new McpServer({
name: 'example-server',
version: '1.0.0',
capabilities: {
tools: { listChanged: true },
resources: { listChanged: true },
prompts: { listChanged: true }
}
});
// 静态资源 config://app
server.resource(
'config',
'config://app',
async (uri) => {
return {
contents: [
{ uri: uri.href, text: 'App configuration here' }
]
};
}
);
// 动态资源 users://{userId}/profile
server.resource(
'user-profile',
new ResourceTemplate('users://{userId}/profile', { list: undefined }),
async (uri, { userId }) => {
return {
contents: [
{
uri: uri.href,
text: `Profile data for user ${userId}`
}
]
};
}
);
// 工具 calculate-bmi
server.tool(
'calculate-bmi',
{ weightKg: z.number(), heightM: z.number() },
async ({ weightKg, heightM }) => {
const bmi = weightKg / (heightM * heightM);
return {
content: [
{ type: 'text', text: String(bmi) }
]
};
}
);
// 提示 review-code
server.prompt(
'review-code',
{ code: z.string() },
({ code }) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please review this code:\n\n${code}`
}
}
]
};
}
);
return server;
}
// POST /mcp:初始化或复用会话
app.post('/mcp', async (req, res) => {
const sessionIdHeader = req.headers['mcp-session-id'];
let sessionEntry = null;
// 情况1:复用已存在的会话
if (sessionIdHeader && sessions[sessionIdHeader]) {
sessionEntry = sessions[sessionIdHeader];
// 情况2:无会话但这是初始化请求 → 建立新会话
} else if (!sessionIdHeader && isInitializeRequest(req.body)) {
const newSessionId = randomUUID();
// StreamableHTTPServerTransport 是一种支持流式数据传输的 HTTP 服务器传输层实现,其核心作用是在 HTTP 协议基础上
// ,为服务器与客户端(尤其是 LLM 模型或需要实时交互的客户端)提供持续、高效的流式数据交换能力。
// 1. 支持流式响应
// 2. 维持长链接
// 3. 兼容HTTP协议
// 4. 标准化的数据传输格式
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => newSessionId,
onsessioninitialized: (sid) => {
sessions[sid] = { server, transport };
}
});
transport.onclose = () => {
if (transport.sessionId && sessions[transport.sessionId]) {
delete sessions[transport.sessionId];
}
};
// 调用 createMcpServer() 来注册工具/资源/提示。
const server = createMcpServer();
// 在发送任何响应之前调用 await server.connect(transport)。
// server.connect 通常用于建立或管理服务器与客户端之间的连接
await server.connect(transport);
sessions[newSessionId] = { server, transport };
sessionEntry = sessions[newSessionId];
} else {
res.status(400).json({
jsonrpc: '2.0',
error: { code: -32000, message: 'Bad Request: No valid session ID provided' },
id: null
});
return;
}
// 将请求转交给本会话的 transport
await sessionEntry.transport.handleRequest(req, res, req.body);
});
// GET/DELETE /mcp:SSE 下行与关闭会话
async function handleSessionRequest(req, res) {
const sessionIdHeader = req.headers['mcp-session-id'];
if (!sessionIdHeader || !sessions[sessionIdHeader]) {
res.status(400).send('Invalid or missing session ID');
return;
}
const { transport } = sessions[sessionIdHeader];
await transport.handleRequest(req, res);
}
app.get('/mcp', handleSessionRequest);
app.delete('/mcp', handleSessionRequest);
// 启动
const PORT = 7171;
app.listen(PORT, () => {
console.log(`MCP Server listening on port ${PORT}`);
});
/* 测试:初始化 */
/*
curl -i -X POST http://localhost:7171/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer my-secret-token" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": { "interactive": true },
"clientInfo": { "name": "example-client", "version": "1.0.0" }
}
}'
*/
/* 测试:调用 calculate-bmi 工具 */
/*
curl -X POST http://localhost:7171/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: 68be30c9-73cd-42fd-9260-e4591c0735a2【需与返回的sessionId一致】" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "mcp/callTool",
"params": {
"name": "calculate-bmi",
"arguments": { "weightKg": 70, "heightM": 1.75 }
}
}'
*/