关于我
MCP 全解:从理解到深度开发从零开发一款 MCP Server

进阶:使用 SSE 协议

stdio 模式在日志、文件依赖、产物依赖三方面的实践痛点,以及为什么改用 SSE Transport——附基于 Express 的完整实现与 Cursor 配置。

上一节的示例实现了一个最基础的、基于 stdio 的 MCP 服务,优点是简单,但实践中一般并不会这么做,因为效率实在太低了。几个问题:

  1. 日志缺失:使用 stdio 模式时,所有通过 console 接口输出到命令行的日志都会被 MCP Client 认为是 Server 的返回内容,可能影响 LLM 的执行效果;另外,当 Cursor 直接调用 Command 时会吞掉所有 console 日志,即使命令出错了也看不到日志输出,你根本不知道是什么问题。
  2. 文件依赖:MCP Server 在执行时无法预设 Current Working Directory,这意味着启动时你无法预设目标文件所在的相对目录,因此上述 Command 形态下必须设定启动命令为 MCP Server 代码所在的绝对目录,这在协同开发中会增加沟通成本。
  3. 产物依赖:实测,使用 Command 形态时只能配置单个执行命令,不支持 cd xxx && npx tsx xx/index.ts 等复合命令,因此实践中源码修改后必须重新生成可直接在 node 上执行的产物形态之后才能正常运行,这在实战中非常浪费时间。

因此,更推荐使用 MCP 的另一种通讯方案:SSE Transport,实现也很简单。

1. 实现

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { z } from 'zod';
import express from 'express';
import morgan from 'morgan';

const main = async () => {
  // Create an MCP server
  const server = new McpServer({
    name: 'Demo',
    version: '1.0.0',
  });

  // Add an addition tool
  server.tool('add', { a: z.number(), b: z.number() }, async ({ a, b }) => ({
    content: [{ type: 'text', text: String(a + b) }],
  }));

  // Create Express application
  const app = express();
  app.use(morgan('tiny'));

  // Store transports for each connection
  let transport: SSEServerTransport;

  // SSE endpoint
  app.get('/sse', async (req, res) => {
    console.log('New connection');
    transport = new SSEServerTransport('/messages', res);
    await server.connect(transport);
  });

  // Message processing endpoint
  app.post('/messages', async (req, res) => {
    console.log('New message: ' + req.query.sessionId);
    await transport.handlePostMessage(req, res);
  });

  // Start the server
  const port = process.env.PORT || 3003;
  app.listen(port, () => {
    console.log(`Server started on port ${port}`);
  });
};

main().catch(console.error);

关键点:

  1. 依然需要创建 McpServer 实例,但这次不用 StdioServerTransport,改为 SSEServerTransport 实例;
  2. 需要通过 express 或其他 http 框架,挂载 /sse 与 /messages 两类接口,其中 /sse 用于初始化通讯连接,/messages 用于实现与 LLM 的交互;
  3. 特别注意,通过 /sse 初始化的 Transport 对象必须带到 /messages 中复用,例如上述代码中就将 transport 设置为全局对象了。

2. 在 Cursor 中配置

🖼️ 配图待补:NIhzbofLToFaoTxOpbEclWF0n8d

注意几个点:

  1. Type 为 sse;
  2. URL 里,记得加上 /sse 路径。

之后,即使出现问题,也能在 Server 的日志中定位问题:

🖼️ 配图待补:IASJbOuqOoDCu8xbAO2cizZInnb

On this page