MCP 全解:从理解到深度开发从零开发一款 MCP Server
进阶:使用 SSE 协议
stdio 模式在日志、文件依赖、产物依赖三方面的实践痛点,以及为什么改用 SSE Transport——附基于 Express 的完整实现与 Cursor 配置。
上一节的示例实现了一个最基础的、基于 stdio 的 MCP 服务,优点是简单,但实践中一般并不会这么做,因为效率实在太低了。几个问题:
- 日志缺失:使用 stdio 模式时,所有通过
console接口输出到命令行的日志都会被 MCP Client 认为是 Server 的返回内容,可能影响 LLM 的执行效果;另外,当 Cursor 直接调用 Command 时会吞掉所有 console 日志,即使命令出错了也看不到日志输出,你根本不知道是什么问题。 - 文件依赖:MCP Server 在执行时无法预设 Current Working Directory,这意味着启动时你无法预设目标文件所在的相对目录,因此上述 Command 形态下必须设定启动命令为 MCP Server 代码所在的绝对目录,这在协同开发中会增加沟通成本。
- 产物依赖:实测,使用 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);关键点:
- 依然需要创建
McpServer实例,但这次不用StdioServerTransport,改为SSEServerTransport实例; - 需要通过
express或其他 http 框架,挂载/sse与/messages两类接口,其中/sse用于初始化通讯连接,/messages用于实现与 LLM 的交互; - 特别注意,通过
/sse初始化的Transport对象必须带到/messages中复用,例如上述代码中就将transport设置为全局对象了。
2. 在 Cursor 中配置
🖼️ 配图待补:NIhzbofLToFaoTxOpbEclWF0n8d
注意几个点:
- Type 为 sse;
- URL 里,记得加上
/sse路径。
之后,即使出现问题,也能在 Server 的日志中定位问题:
🖼️ 配图待补:IASJbOuqOoDCu8xbAO2cizZInnb

