基于 MCP 官方提供的typescript-sdk,可以开发自己的 McpServer。
但是尽管使用 sdk,依然存在一些模板代码,以 SSE 类型的 Server 举例:
typescript
const server = new McpServer({
name: "some-server",
version: "1.0.0",
});
const app = express();
const port = process.env.PORT || 3000;
const transports: { [sessionId: string]: SSEServerTransport } = {};
app.get("/sse", (req, res) => {
res.setHeader("Content-Type", "text/event-stream");
const transport = new SSEServerTransport("/messages", res);
const sessionId = transport.sessionId;
transports[sessionId] = transport;
res.on("close", () => {
delete transports[sessionId];
});
server.connect(transport);
});
app.post("/messages", async (req, res) => {
const sessionId = req.query.sessionId as string;
if (!sessionId) {
return res.status(400).send("Missing sessionId query parameter");
}
const transport = transports[sessionId];
if (!transport) {
return res
.status(404)
.send(`No active transport found for sessionId: ${sessionId}`);
}
try {
await transport.handlePostMessage(req, res);
} catch (error) {
if (!res.headersSent) {
res.status(500).send("Error processing message");
}
}
});
app.listen(port);
每开发一个 MCP Server,就需要写上面的这些模板代码,比如
- MCP Server 实例化
- 连接 Transport
- 生命周期管理
- sse 和 message 接口处理函数
这些都属于杂事,我们开发 MCP Server 通常希望只聚焦于具体 tool 的开发。
所以我开发了 nest-mcp-sse,它是一个用于在 NestJS 工程下快速开发 SSE 传输类型 MCP Server 的动态模块,使用此模块,你可以只聚焦于 MCP tool 的开发。
没有过度封装🥳,只封装了上文所说的那些模板代码,没有对官方sdk的原生 MCP Server 实例的方法做任何封装,尽可能不增加额外学习成本。
功能特点
- 快速在 NestJS 应用中创建 MCP 服务器
- 支持多个 MCP Server 实例管理
- 自动处理 SSE 连接生命周期
安装
bash
pnpm install nest-mcp-sse @modelcontextprotocol/sdk
用法
1. 导入模块
在你的模块中导入 McpModule
typescript
import { Module } from "@nestjs/common";
import { McpModule } from "nest-mcp-sse";
@Module({
imports: [
McpModule.register({
controllerBaseUrl: "api/mcp",
mcpServerConfigs: [
{
serverId: "my-mcp-server",
serverInfo: {
name: "my-mcp-server",
version: "1.0.0",
},
},
],
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
另外,你也可以通过forRoot
和forFeature
导入模块,区别如下:
- forRoot:通常在根模块注册
- forFeature:和 forRoot 注册的模块共享 Service 实例
- register:用一次注册一次,不和任何模块共享
2. 操作 McpServer
创建 MCP Server 之后,你可以通过getServer
方法获取到官方sdk的原生 McpServer 实例,这个实例没有任何封装,完全是原生的。
比如,你可以注册工具
ts
import { Controller } from "@nestjs/common";
import { McpServerService } from "nest-mcp-sse";
import { z } from "zod";
@Controller()
export class AppController {
constructor(private readonly mcpServerService: McpServerService) {
this.mcpServerService
.getServer("server-id")
?.tool(
"get-current-time",
{ format: z.enum(["iso", "locale"]).describe("时间格式: iso, locale") },
async ({ format }: { format: "iso" | "locale" }) => {
const now = new Date();
let timeValue: string | number;
switch (format) {
case "iso":
timeValue = now.toISOString();
break;
case "locale":
timeValue = now.toLocaleString();
break;
}
return {
content: [{ type: "text", text: String(timeValue) }],
};
}
);
}
}
3. 手动注册服务器实例
上面的例子直接在McpModule.register
中传入了mcpServerConfigs
,这会在模块初始化时就创建 MCP Server。
当然这不是必需的,你可以不传入mcpServerConfigs
,在 Controller 或 Service 中手动注册 MCP Server:
typescript
import { Controller } from "@nestjs/common";
import { McpServerService } from "nest-mcp-sse";
import { z } from "zod";
@Controller()
export class AppController {
constructor(private readonly mcpServerService: McpServerService) {
// 手动注册一个MCP Server实例
this.mcpServerService.registerServer({
serverId: "another-server",
serverInfo: {
name: "another-mcp-server",
version: "1.0.0",
},
});
}
}
服务器访问端点
注册模块后,将自动创建以下端点:
- SSE 连接端点 :
/{controllerBaseUrl}/{serverId}/sse
- 消息处理端点 :
/{controllerBaseUrl}/{serverId}/messages
例如,如果你使用 controllerBaseUrl: 'api/mcp'
和 serverId: 'my-mcp-server'
,则访问 URL 为:
http://localhost:3000/api/mcp/my-mcp-server/sse
- 用于 SSE 连接http://localhost:3000/api/mcp/my-mcp-server/messages
- 用于消息处理
链接
- github: github.com/HxinY499/ne...
- npm: www.npmjs.com/package/nes...