MCP入门梳理

前置知识-stdio(传输层)

什么是stdio

stdio 是 "标准输入输出"(Standard Input/Output)的缩写,是计算机程序中用于进程间通信的基本机制。它通过三个主要的流(standard streams)来处理数据:

  • stdin:标准输入,用于接收外部数据输入。
  • stdout:标准输出,用于输出数据给外部。
  • stderr:标准错误输出,用于输出错误信息。

可以把 stdio 想象成一个管道,程序的一部分通过管道将数据发送出去,另一部分通过管道接收数据。这种设计使得 stdio 成为最基本的通信形式,尤其在 命令行工具脚本程序 中得到了广泛应用。

为什么 MCP 会选 stdio 作为核心通信方式之一

  • 本地工具场景非常匹配 :MCP 通常用于 工具链与模型的协作,这种场景下,stdio 提供了一种最原始、最直接的通信方式。MCP 主要通过本地工具或进程来执行任务,不需要外部的网络支持或服务暴露。通过 stdio ,MCP 可以在不同的进程之间传递数据,保证了 本地化、低延迟 的需求。
  • 无需网络,启动成本极低 :与传统的网络通信协议相比,stdio 的一个巨大优势在于,它不需要依赖网络连接。无论是 端口配置 还是 网络访问权限,都不再是使用 stdio 的障碍。在 MCP 中,任何一个本地进程都可以通过标准输入输出直接与另一个进程进行通信。
  • 天然的安全边界 :在 MCP 的架构中,stdio 的通信只发生在本地进程间,不涉及网络层面的暴露。这意味着它天然具备了较高的安全性。每个进程的标准输入输出只能在该进程的上下文中访问,不会无意中暴露给外部网络或其他程序。
  • 非常容易跨语言 :不同于某些专用协议,stdio 是操作系统层级的标准,它被绝大多数编程语言所支持。无论是用 Node.js、Python,还是 Go,所有这些语言都可以通过统一的方式进行输入输出。这为 MCP 提供了极大的灵活性,使得它可以轻松与多种工具和模型集成,跨语言的操作非常简单。

stdio使用演示

client.js :通过 stdio 向子进程发送消息并接收响应

js 复制代码
const { spawn } = require('child_process');
const serverProcess = spawn('node', ['server.js']); // 启动 server.js 子进程
const messages = ['你好吗?', '你吃饭了吗?', '天气真好!'];
messages.forEach((message, index) => {
  setTimeout(() => {
    const msg = message + '\n'; // 加上换行符可以让服务端更容易区分每一条输入
    console.log('我:' + msg);
    serverProcess.stdin.write(msg);
  }, index * 1000); // 每秒发送一条消息
});
let count = 0;
serverProcess.stdout.on('data', (data) => {
  console.log('AI:' + data.toString());
  count++;
  if (count === messages.length) {
    serverProcess.stdin.end();
  }
});

server.js :从 stdin 读取输入并通过 stdout 返回结果

js 复制代码
process.stdin.setEncoding('utf-8'); // 设置utf-8 编码,输入的数据将被转换为字符串
process.stdin.on('data', (data) => {
  data = data
    .replace(/[??]/g, '')
    .replace(/我/g, '你')
    .replace(/你/g, '我')
    .replace(/吗/g, '');
  process.stdout.write(`${data}\n`);
});
process.stdin.on('end', () => {
  process.exit(0); // 正常退出进程
});

前置知识-JSON-RPC(协议层)

什么是 JSON-RPC

JSON-RPC 是一种轻量级的远程过程调用(RPC)协议,基于 JSON 格式实现跨进程 / 跨网络的通信,核心目标是让不同系统能够通过简单的文本格式调用彼此的方法,无需依赖复杂的传输层协议。通俗的讲,JSON-RPC 做的事情只有一件:把一次"函数调用",用一段 JSON 表达出来

  • 它的核心特点:

    • 无状态:每次请求独立,服务器不保存客户端上下文;
    • 语言无关:JSON 是通用格式,几乎所有编程语言都支持解析;
    • 简洁:协议规则极少,请求 / 响应格式固定且易理解。
  • 核心结构由「请求对象」和「响应对象」组成:

    • 请求:包含要调用的方法名、参数、唯一标识(ID);
    • 响应:包含调用结果(成功 / 失败)、对应请求的 ID(用于关联)。

MCP 为什么选择 JSON-RPC 2.0

JSON-RPC 2.0 是 1.0 的官方标准化升级(1.0 无正式规范,仅为社区约定),主要解决了 1.0 的模糊性、扩展性问题。MCP 并没有重新设计一套通信协议,而是选择了 JSON-RPC 2.0 作为协议层,主要因为:

首先,JSON-RPC 2.0 是一个成熟且稳定的规范。它的结构固定、语义清晰,不需要在"如何定义消息格式"这件事上再花额外成本。

其次,JSON-RPC 的核心抽象与 MCP 的设计高度契合:

  • method 对应工具或能力的调用
  • params 对应输入参数
  • resulterror 对应执行结果

这种一一对应关系,使得 MCP 在表达工具调用时几乎不需要做额外包装。

最后,JSON-RPC 与传输层完全解耦。 在 MCP 中,JSON-RPC 消息既可以通过 stdio 传输,也可以通过 HTTP 传输,而协议本身不需要做任何修改。可以简单理解为:

stdio 和 http 负责"怎么传",

JSON-RPC 负责"传什么"。

JSON-RPC 2.0的基本结构

介绍几种常用的rpc调用,字段介绍及其他用法可参照英文文档,也可以参照中文文档

  • 参数是数组的rpc调用
json 复制代码
--> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
<-- {"jsonrpc": "2.0", "result": 19, "id": 1}
  • 参数是键值对的rpc调用
json 复制代码
--> {"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}
<-- {"jsonrpc": "2.0", "result": 19, "id": 3}
  • 通知(没有 id 字段)
json 复制代码
--> {"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}

--> {"jsonrpc": "2.0", "method": "foobar"}
  • 失败响应的rpc调用
json 复制代码
--> {"jsonrpc": "2.0", "method": "foobar", "id": "1"}
<-- {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "1"}

JSON-RPC 2.0的使用演示

server.js :是一个基于 JSON-RPC 2.0 的本地服务端进程,核心职责是:读取 stdin 结构化输入 → 根据 method 执行能力 → 返回 stdout 结构化结果

js 复制代码
const fs = require('fs');

const utils = {
  sum({ a, b }) {
    return a + b;
  },
  createFile({ filename, content }) {
    try {
      fs.writeFileSync(filename, content);
      return true;
    } catch {
      return false;
    }
  }
};

process.stdin.on('data', (data) => {
  const req = JSON.parse(data);
  const funcName = req.method;
  const params = req.params;
  const result = utils[funcName](params);
  const res = {
    jsonrpc: '2.0',
    id: req.id,
    result
  };
  process.stdout.write(JSON.stringify(res) + '\n');
});

jsonrpc.txt:提供两个模拟客户端发送的 JSON-RPC 请求消息,第一个是无副作用的,单纯的函数调用,第二个是有副作用的,触发系统能力的调用。

txt 复制代码
{"jsonrpc":"2.0","id":1,"method":"sum","params":{"a":1,"b":2}}

{"jsonrpc":"2.0","id":2,"method":"createFile","params":{"filename":"./test.txt","content":"Hello, World!"}}

基于 JSON-RPC 的 MCP 实现

server.js :是一个基于 JSON-RPC 2.0 实现的 MCP 服务端示例,通过 initializetools/listtools/call 等方法对外声明能力并提供工具调用能力。utils里的MCP字段配置可以参考MCP官方的Schema Reference

js 复制代码
const fs = require('fs');

const tools = {
  sum({ a, b }) {
    return {
      content: [
        {
          type: 'text',
          text: `两数求和结果是:${a + b}`
        }
      ]
    };
  },
  createFile({ filename, content }) {
    try {
      fs.writeFileSync(filename, content);
      return {
        content: [
          {
            type: 'text',
            text: `文件创建成功!`
          }
        ]
      };
    } catch (err) {
      return {
        content: [
          {
            type: 'text',
            text: err.message || '文件创建失败!'
          }
        ]
      };
    }
  }
};

const utils = {
  initialize() {
    return {
      // 服务器功能
      capabilities: {
        // 支持工具的服务器必须声明工具能力
        tools: {
          listChanged: true // 该服务器是否支持工具列表变更的通知
        }
      },
      protocolVersion: '2025-11-25', // 协议版本
      // 服务器信息
      serverInfo: {
        name: 'Demo Server', // 名称
        version: '1.0.0' // 版本
      }
    };
  },
  'tools/list'() {
    return {
      tools: [
        {
          name: 'sum',
          title: '两数求和',
          description: '得到两个数的和',
          inputSchema: {
            type: 'object',
            properties: {
              a: {
                type: 'number',
                description: '第一个数'
              },
              b: {
                type: 'number',
                description: '第二个数'
              }
            },
            required: ['a', 'b']
          }
        },
        {
          name: 'createFile',
          title: '创建文件',
          description: '在指定目录下创建一个文件',
          inputSchema: {
            type: 'object',
            properties: {
              filename: {
                type: 'string',
                description: '文件名'
              },
              content: {
                type: 'string',
                description: '文件内容'
              }
            },
            required: ['filename', 'content']
          }
        }
      ]
    };
  }
};

process.stdin.on('data', (data) => {
  const req = JSON.parse(data);
  let result;
  if (req.method === 'tools/call') {
    result = tools[req.params.name](req.params.arguments);
  } else if (req.method in utils) {
    result = utils[req.method](req.params);
  } else {
    return;
  }

  const res = {
    jsonrpc: '2.0',
    result,
    id: req.id
  };
  process.stdout.write(JSON.stringify(res) + '\n');
});

jsonrpc.txt:展示了客户端通过 JSON-RPC 2.0 协议与 MCP 服务端交互的完整流程,包括初始化、查询工具列表以及调用具体工具的示例请求。

json 复制代码
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientInfo":{"name":"Demo Client","version":"1.0.0"},"protocolVersion":"2025-11-25"}}

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

{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"sum","arguments":{"a":1,"b":2}}}

{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"createFile","arguments":{"filename":"./test.txt","content":"Hello World!"}}}

使用 TypeScript SDK 构建 MCP 应用

StdioServerTransport

stdio.js :实现了一个基于 StdioServerTransport 的 MCP Server,通过标准输入输出与客户端通信,并对外注册了 sumcreateFile 两个工具,分别用于数值计算和文件写入。

js 复制代码
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import fs from 'fs';

async function main() {
  const server = new McpServer({
    name: 'demo-server',
    version: '1.0.0'
  });

  server.registerTool(
    'sum',
    {
      title: '两数求和',
      description: '将两个数字相加',
      inputSchema: { a: z.number(), b: z.number() },
      outputSchema: { result: z.number() }
    },
    async ({ a, b }) => {
      const output = { result: a + b };
      return {
        content: [{ type: 'text', text: JSON.stringify(output) }],
        structuredContent: output
      };
    }
  );

  server.registerTool(
    'createFile',
    {
      title: '创建文件',
      description: '将内容添加到文件中',
      inputSchema: { filename: z.string(), content: z.string() }
    },
    async ({ filename, content }) => {
      try {
        fs.writeFileSync(filename, content);
        return {
          content: [
            {
              type: 'text',
              text: '文件创建成功!'
            }
          ]
        };
      } catch (err) {
        return {
          content: [
            {
              type: 'text',
              text: err.message || '文件创建失败!'
            }
          ]
        };
      }
    }
  );

  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch(console.error);
使用rpc调用调试
  • 在命令行窗口运行node ./src/stdio.js启动服务
  • 如下jsonrpc.txt 的内容,整行复制jsonrpc协议请求到命令行窗口,调试MCP工具函数
json 复制代码
{"jsonrpc":"2.0","id":2,"method":"tools/list"}

{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"sum","arguments":{"a":1,"b":2}}}

{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"createFile","arguments":{"filename":"./test.txt","content":"Hello World!"}}}
使用@modelcontextprotocol/inspector调试
  • 在命令行窗口运行npx @modelcontextprotocol/inspector,浏览器会自动弹出调试窗口
  • 在浏览器的调试窗口左侧填写对应的字段,Arguments 里填写对应服务的文件相对地址或者绝对地址,然后点击Connect 建立连接,会触发initialize
  • 最后就可以在右侧的如下图两个红框内输入,调试MCP工具函数
使用LLM客户端调试(以Cursor举例,其他的配置差不多)
  • 点击对话窗口的右上角的三个点,选择Agent Settings
  • 在Tools & MCP一栏,点击Add Custom MCP ,这时会打开mcp.json文件
  • mcp.json文件内容如下,添加自己的mcp服务my-mcp-server,注意args里的服务文件地址需要填写绝对路径地址
json 复制代码
{
  "mcpServers": {
    "my-mcp-server": {
      "transport": "stdio",
      "command": "node",
      "args": ["xxx/xxx/src/stdio.js"]
    }
  }
}
  • 返回对话窗口,输入@MCP xxx就能在对话框直接调用mcp工具函数,如果不@MCP有时也会触发mcp工具函数,只不过是大模型自己判断调用的

StreamableHTTPServerTransport

streamableHttp.js :实现了一个基于 StreamableHTTPServerTransport 的 MCP Server,通过 Express 提供 /mcp HTTP 接口,使 MCP 客户端可以以流式 HTTP 的方式调用 sumcreateFile 两个工具。

js 复制代码
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import express from 'express';
import { z } from 'zod';
import fs from 'fs';

const server = new McpServer({
  name: 'demo-server',
  version: '1.0.0'
});

server.registerTool(
  'sum',
  {
    title: '两数求和',
    description: '将两个数字相加',
    inputSchema: { a: z.number(), b: z.number() },
    outputSchema: { result: z.number() }
  },
  async ({ a, b }) => {
    const output = { result: a + b };
    return {
      content: [{ type: 'text', text: JSON.stringify(output) }],
      structuredContent: output
    };
  }
);

server.registerTool(
  'createFile',
  {
    title: '创建文件',
    description: '将内容添加到文件中',
    inputSchema: { filename: z.string(), content: z.string() }
  },
  async ({ filename, content }) => {
    try {
      fs.writeFileSync(filename, content);
      return {
        content: [
          {
            type: 'text',
            text: '文件创建成功!'
          }
        ]
      };
    } catch (err) {
      return {
        content: [
          {
            type: 'text',
            text: err.message || '文件创建失败!'
          }
        ]
      };
    }
  }
);

const app = express();
app.use(express.json());

app.post('/mcp', async (req, res) => {
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
    enableJsonResponse: true
  });

  res.on('close', () => {
    transport.close();
  });

  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

const port = parseInt(process.env.PORT || '3000');
app
  .listen(port, () => {
    console.log(`Demo MCP Server running on http://localhost:${port}/mcp`);
  })
  .on('error', (error) => {
    console.error('Server error:', error);
    process.exit(1);
  });
使用@modelcontextprotocol/inspector调试
  • 需要先使用node ./src/streamableHttp.js命令启动服务
  • 再开一个命令行窗口运行npx @modelcontextprotocol/inspector命令启动MCP检查器
  • 打开的浏览器窗口里参数如下,点击Connect 就可以在右侧调试工具函数了
使用LLM客户端调试(以Cursor举例,其他的配置差不多)
  • 需要先使用node ./src/streamableHttp.js命令启动服务
  • 修改mcp.json文件里的配置如下,然后就可以在对话窗口调试了
json 复制代码
{
  "mcpServers": {
    "my-mcp-server": {
      "transport": "http",
      "url": "http://localhost:3000/mcp"
    }
  }
}
相关推荐
小林攻城狮2 小时前
一个基于 canvas 的 pdf 图片分页切割方法
前端·javascript
老华带你飞2 小时前
学生宿舍管理|基于java + vue学生宿舍管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
一天前2 小时前
一个功能强大的 React Native 拖拽排序组件库,支持单列和多列布局,提供流畅的拖拽体验和自动滚动功能
前端
OpenTiny社区2 小时前
2025年OpenTiny年度人气贡献者评选正式开始
前端·javascript·vue.js
JosieBook2 小时前
【Vue】04 Vue技术——Vue 模板语法详解:插值与指令
前端·javascript·vue.js
汤姆Tom2 小时前
硬核指南:Volta —— 重新定义 JavaScript 工具链管理
前端·敏捷开发·命令行
Goodbaibaibai2 小时前
Element自定义主题色
前端·css·css3
灰海2 小时前
为什么给<a>标签设置了download属性, 浏览器没有下载而是打开新标签!!
前端·vue·html·下载·download
1024肥宅2 小时前
面试和算法:常见面试题实现与深度解析
前端·javascript·面试