发布于: -/最后更新: -/7 分钟/#FancyHelper#MCP#服务器开发#JSON-RPC

MCP 服务器开发指南

本文面向 FancyHelper 开发者,详细说明了 MCP 协议规范。MCP 基于 JSON-RPC 2.0,支持 HTTP 和 SSE 两种传输方式。服务器需按顺序实现 initialize、notifications/initialized、tools/list 和 tools/call 等方法,HTTP 模式下还需处理 ping。文档还规定了消息格式、错误码及约束,并提供了 Node.js 和 Python 的实现示例。

概述

本文档面向 MCP 服务器的开发者,说明 FancyHelper 使用的 MCP 协议规范和服务器需要实现的方法。

MCP 基于 JSON-RPC 2.0 协议。FancyHelper 作为客户端,连接到开发者编写的 MCP 服务器,调用服务器暴露的工具。


传输方式

FancyHelper 支持两种传输方式,MCP 服务器可以选择实现其中一种或同时支持两种:

HTTP 传输

客户端直接向配置的 URL 发送 HTTP POST 请求,请求头包含:

纯文本
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer <api_key>    (如果配置了 api_key)

服务器只需实现一个 POST 端点,接收 JSON-RPC 请求并返回 JSON 响应。这是最简单的实现方式。

客户端配置:transport: "auto"transport: "http"

SSE 传输

客户端分两步连接:

  1. 向配置的 URL 发起 GET 请求,Accept: text/event-stream,建立 SSE 连接

  2. 服务器通过 SSE 事件发送消息端点 URL

  3. 客户端后续 JSON-RPC 请求全部发送到消息端点

SSE 事件格式:

纯文本
event: endpoint
data: http://<host>:<port>/messages

其中 data 为字符串格式的消息端点 URL。如果 data 为 JSON 对象,则读取 uri 字段。

客户端配置:transport: "sse"


协议流程

连接建立后,客户端按以下顺序依次调用:

纯文本
initialize    → 协议握手,协商版本和能力
notifications/initialized → 通知握手完成(无响应)
tools/list    → 获取可用工具列表
tools/call    → 调用具体工具(可多次调用)

HTTP 模式下,客户端还会每 30 秒发送一次 ping 保持连接。


JSON-RPC 消息格式

请求

JSON
{
  "jsonrpc": "2.0",
  "id": "<string>",
  "method": "<string>",
  "params": { ... }
}

字段

类型

说明

jsonrpc

string

固定为 "2.0"

id

string

请求 ID,响应中需原样返回

method

string

方法名

params

object

方法参数

成功响应

JSON
{
  "jsonrpc": "2.0",
  "id": "<原请求 ID>",
  "result": { ... }
}

错误响应

JSON
{
  "jsonrpc": "2.0",
  "id": "<原请求 ID 或 null>",
  "error": {
    "code": -32601,
    "message": "<错误描述>"
  }
}

通知(Notification)

通知的请求中没有 id 字段,服务器无需返回任何数据,HTTP 状态码返回 202 Accepted 即可。

JSON
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized",
  "params": {}
}

方法参考

initialize

握手请求,客户端连接后首先发送此方法。服务器需返回协议版本和能力声明。

请求参数:

字段

类型

说明

protocolVersion

string

客户端支持的协议版本

clientInfo

object

客户端信息,包含 nameversion

capabilities

object

客户端能力声明

响应字段:

字段

类型

说明

protocolVersion

string

服务器使用的协议版本

capabilities

object

服务器能力声明

serverInfo

object

服务器信息,包含 nameversion

请求示例:

JSON
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "clientInfo": { "name": "FancyHelper", "version": "1.0.0" },
    "capabilities": {}
  }
}

响应示例:

JSON
{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": { "listChanged": true }
    },
    "serverInfo": {
      "name": "MyMCPServer",
      "version": "1.0.0"
    }
  }
}

notifications/initialized

客户端在 initialize 成功后发送此通知。服务器收到后即可进入正常工作状态。

此方法为 Notification,无需返回数据。

JSON
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized",
  "params": {}
}

tools/list

客户端请求服务器返回可用的工具列表。

请求参数:

响应字段:

字段

类型

说明

tools

array

工具列表

tools[].name

string

工具名称,同一服务器内唯一

tools[].description

string

工具描述,帮助 AI 理解工具用途

tools[].inputSchema

object

参数 Schema,JSON Schema 格式

请求示例:

JSON
{
  "jsonrpc": "2.0",
  "id": "2",
  "method": "tools/list",
  "params": {}
}

响应示例:

JSON
{
  "jsonrpc": "2.0",
  "id": "2",
  "result": {
    "tools": [
      {
        "name": "get_weather",
        "description": "获取指定城市的天气信息",
        "inputSchema": {
          "type": "object",
          "properties": {
            "city": {
              "type": "string",
              "description": "城市名称"
            }
          },
          "required": ["city"]
        }
      }
    ]
  }
}

tools/call

客户端请求执行指定工具。

请求参数:

字段

类型

说明

name

string

工具名称

arguments

object

工具参数,对应 inputSchema 中定义的字段

响应字段:

字段

类型

说明

content

array

执行结果内容列表

content[].type

string

内容类型,目前仅处理 "text"

content[].text

string

文本内容

isError

boolean

是否执行出错

请求示例:

JSON
{
  "jsonrpc": "2.0",
  "id": "3",
  "method": "tools/call",
  "params": {
    "name": "get_weather",
    "arguments": { "city": "Beijing" }
  }
}

成功响应示例:

JSON
{
  "jsonrpc": "2.0",
  "id": "3",
  "result": {
    "content": [
      { "type": "text", "text": "北京当前天气:晴,28°C" }
    ],
    "isError": false
  }
}

执行失败响应示例:

JSON
{
  "jsonrpc": "2.0",
  "id": "3",
  "result": {
    "content": [
      { "type": "text", "text": "查询失败:API 服务不可用" }
    ],
    "isError": true
  }
}

ping(仅 HTTP 模式)

客户端在 HTTP 模式下每 30 秒发送一次心跳检测。

JSON
{
  "jsonrpc": "2.0",
  "id": "4",
  "method": "ping",
  "params": {}
}

响应:

JSON
{
  "jsonrpc": "2.0",
  "id": "4",
  "result": {}
}

错误码

JSON-RPC 标准错误码:

错误码

含义

-32700

JSON 解析错误

-32600

无效请求

-32601

方法不存在

-32602

无效参数

-32603

内部错误


约束与注意事项

  1. initialize 必须在任何其他请求之前处理

  2. 通知类消息(方法名以 notifications/ 开头)的请求缺少 id 字段,不应返回响应体

  3. content 数组中 type"text" 以外的内容项,FancyHelper 当前不会处理,会跳过

  4. content[].textnull 时,FancyHelper 会显示为 (空结果)

  5. 工具名和服务器的 name 字段只能包含字母、数字、下划线和连字符

  6. HTTP 模式的服务器需要在一处 URL 同时处理 initializetools/listtools/callping 所有方法


实现示例

Node.js

JavaScript
const http = require('http');

const TOOLS = [
  {
    name: 'greet',
    description: '向指定用户打招呼',
    inputSchema: {
      type: 'object',
      properties: {
        name: { type: 'string', description: '用户名' }
      },
      required: ['name']
    }
  }
];

function handleRequest(body) {
  const req = JSON.parse(body);
  const { id, method, params } = req;

  switch (method) {
    case 'initialize':
      return JSON.stringify({
        jsonrpc: '2.0', id,
        result: {
          protocolVersion: '2024-11-05',
          capabilities: { tools: { listChanged: true } },
          serverInfo: { name: 'MyServer', version: '1.0.0' }
        }
      });

    case 'notifications/initialized':
      return null;

    case 'tools/list':
      return JSON.stringify({
        jsonrpc: '2.0', id,
        result: { tools: TOOLS }
      });

    case 'tools/call':
      if (params.name === 'greet') {
        return JSON.stringify({
          jsonrpc: '2.0', id,
          result: {
            content: [{ type: 'text', text: `Hello, ${params.arguments.name}!` }],
            isError: false
          }
        });
      }
      return JSON.stringify({
        jsonrpc: '2.0', id,
        error: { code: -32601, message: 'Unknown tool' }
      });

    case 'ping':
      return JSON.stringify({ jsonrpc: '2.0', id, result: {} });

    default:
      return JSON.stringify({
        jsonrpc: '2.0', id,
        error: { code: -32601, message: 'Unknown method' }
      });
  }
}

const server = http.createServer((req, res) => {
  if (req.method === 'POST') {
    let body = '';
    req.on('data', c => body += c);
    req.on('end', () => {
      const response = handleRequest(body);
      if (response === null) {
        res.writeHead(202);
        res.end();
      } else {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(response);
      }
    });
  }
});

server.listen(3000);

运行后配置 FancyHelper:

YAML
mcp:
  client:
    enabled: true
    servers:
      - name: "my-server"
        url: "http://localhost:3000/mcp"

Python

python
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

TOOLS = [{
    "name": "get_time",
    "description": "获取当前服务器时间",
    "inputSchema": {"type": "object", "properties": {}}
}]

class MCPHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        length = int(self.headers['Content-Length'])
        body = self.rfile.read(length).decode()
        req = json.loads(body)
        id = req.get('id')
        method = req.get('method')
        params = req.get('params', {})

        if method == 'initialize':
            response = {
                "jsonrpc": "2.0", "id": id,
                "result": {
                    "protocolVersion": "2024-11-05",
                    "capabilities": {"tools": {"listChanged": True}},
                    "serverInfo": {"name": "PyServer", "version": "1.0.0"}
                }
            }
        elif method == 'notifications/initialized':
            self.send_response(202)
            self.end_headers()
            return
        elif method == 'tools/list':
            response = {"jsonrpc": "2.0", "id": id, "result": {"tools": TOOLS}}
        elif method == 'tools/call':
            response = {
                "jsonrpc": "2.0", "id": id,
                "result": {
                    "content": [{"type": "text", "text": "Hello from Python!"}],
                    "isError": False
                }
            }
        elif method == 'ping':
            response = {"jsonrpc": "2.0", "id": id, "result": {}}
        else:
            response = {"jsonrpc": "2.0", "id": id, "error": {"code": -32601, "message": "unknown method"}}

        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps(response).encode())

HTTPServer(('', 3000), MCPHandler).serve_forever()

调试

使用 curl 可直接测试 MCP 服务器的响应:

Bash
# 测试 initialize
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"test","version":"1.0"},"capabilities":{}}}'

# 测试 tools/list
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"2","method":"tools/list","params":{}}'

# 测试 tools/call
curl -X POST http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"greet","arguments":{"name":"test"}}}'

正文结束