一、前言
其实前文已经讲过了MCP Server
的开发以及相关的调试。但是只是一个基础的MCP Tools
,说穿了就是运行在Client
的一段脚本。本次的主要内容是开发一个远程HTTP服务,这样我们可以将各类Tools
都集成到远程服务上,既能动态的实现MCP
功能的增减,也能提供更多更强大的功能,比如远程处理各种文件、爬取各种资源等等。
本文所用到的完整代码见文章结尾
二、准备
node 22.14.0
@modelcontextprotocol/sdk 1.21.1
Cursor (Windsurf 暂时不支持这种远程服务形式,因此以Cursor作为Client进行实验)
三、实现
我们可以先想一下如果你想实现这样一个远程服务,该如何实现?我的大概思路如下
1. 首先它得是个可运行的服务
2. 它需要提供可以被AI识别的Tools
3. Client 知道有这个服务并能访问
4. Client访问后能感知到并能够调用这些功能
看起来其实很简单,前后就这几步,那么我们就按照步骤来实现
3.1、 可运行的服务
作为前端,首选的,快捷的,比较快捷的方式肯定是启一个node
服务啦
import express from 'express';
// 创建并配置 HTTP 服务
const app = express();
app.use(express.json());
const PORT = 3020;
app.listen(PORT, () =>
console.error(`MCP server listening on http://localhost:${PORT}/mcp`)
);
3.2、可以被AI识别的Tools
回想前文,我们其实已经实现过MCP Tools
,这里无需再做赘述传送门,我们以一个自定义加减映射来做Demo
import express from 'express';
import { randomUUID } from 'node:crypto';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { z } from 'zod';
// 创建 MCP Server 并在实例上注册工具
function getServer(): McpServer {
// 首先,创建 McpServer 实例
const server = new McpServer({
serverId: randomUUID(),
name: 'Local Calculator MCP',
version: '1.0.0',
});
// 然后,在 server 实例上调用 registerTool 方法来定义和注册工具
server.registerTool('add',
{
description: '计算两个数字的LS映射和',
// 使用 zod 定义输入参数的结构和类型
inputSchema: {
a: z.number().describe('第一个数字'),
b: z.number().describe('第二个数字'),
},
},
// 第三个参数是工具的实际处理函数
// 第三个参数是工具的实际处理函数
async (params) => {
const sum = params.a + params.b * 33;
console.log(`Executing 'add' tool: ${params.a} + ${params.b} = ${sum}`);
// 修复返回值:结果必须包装在一个带有 `content` 数组的对象中
return {
content: [
{
type: 'text',
text: JSON.stringify({ result: sum })
}
]
};
}
);
// 如果有更多工具,可以在这里继续调用 server.registerTool(...)
return server;
}
...
其实这两步感觉可以不用写,对大家来说太基础了。 接下来的步骤才是重头戏
3.3、给Client 配置访问
根据 MCP官方给的demo
,需要提供一个POST
方法给Client
访问
所以我们代码里首先需要增加一个POST
方法就行,如下
...
app.post('/mcp', async (req, res) => {
console.log('post MCP')
console.log('Received a POST request to /mcp, responding with health check OK.');
res.status(200).json({ status: 'ok', message: 'MCP server is alive and listening for POST requests.' });
});
...
注意 因为 Windsurf
现在还只支持 stdio
和sse
两种形式,作为例子
我们在mcp.json
中做如下修改
{
"mcpServers": {
"test_mcp_server": {
"type": "streamable-http",
"url": "http://localhost:3020/mcp",
"note": "For Streamable HTTP connections, add this URL directly in your MCP Client"
}
}
}
注意 我们的服务要先启动 也就是运行
npm run build
node build/index.js // 这个看你build后文件叫啥就是啥
当我们在mcp.json
配置好后,看到终端有打印这个 说明Cursor
已经访问了我们的代码
(其实我们也可以通过MCP Inspector来调试,具体见下文)
3.4、Client访问后能感知到并能够调用这些功能
这里我们要用到一个方法 StreamableHTTPServerTransport
StreamableHTTPServerTransport 在 MCP SDK 中的作用一般是作为一个“流式 HTTP 服务端传输层”,用于处理和管理通过 HTTP 协议与 MCP server 进行的流式数据通信。
具体来说,它的主要功能包括:
- 作为 MCP server 的传输层,负责接收和发送 HTTP 请求和响应,支持流式数据处理(如大文件、长连接、实时数据等场景)。
- 封装了 HTTP 协议的细节,让上层 server(如 getServer() 返回的对象)能够专注于业务逻辑,而不用关心底层 HTTP 连接的管理。
- 支持 session(会话)管理,比如通过 sessionIdGenerator 可以自定义会话 ID 的生成方式。
- 通常配合 server.connect(transport) 以及 transport.handleRequest(req, res, req.body) 一起使用,实现 HTTP 请求的接入、处理和响应。
简单理解:
StreamableHTTPServerTransport 是 MCP server 和 HTTP 客户端之间的“桥梁”,负责把 HTTP 请求转化为 MCP server 能理解的格式,并将 server 的响应通过 HTTP 返回给客户端,且支持流式、长连接等高级用法。
修改POST
方法
app.post('/mcp', async (req, res) => {
console.log('post MCP')
console.log('Received a POST request to /mcp, responding with health check OK.');
const server = getServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
await server.connect(transport);
res.on('close', () => {
transport.close();
server.close();
});
await transport.handleRequest(req, res, req.body);
});
这样 当Client
访问到/mcp
接口的时候,就会获取到 Tools
信息,然后进行调用。
其实这里我们可以使用MCP Inspector进行调试,看一下在我们配置之后,是经历了哪些流程
PS 关于调试基本内容 我们这里不做赘述,有兴趣可以看我前面的这篇文章
3.5、 MCP Inspector 查看调用流程
1、启动你的服务
node build/index.js
2、启动 MCP Inspector
npx @modelcontextprotocol/inspector
3、打开 Inspector
的页面,并做如下配置后点击Connect
4、 这时候我们就可以看到连接情况以及对应的History
和Tools
我们可以看到它首先初始化了一次请求 initialize
initialize
的请求和返回如下
5、 我们再点击Tools
就会再发起请求 获取到服务所提供的Tools
6、 所以我总结了一下,我们在Client
配置完我们的服务之后,一个大概的流程就是 Client
首先发送初始化请求,确认服务器状态,然后获取到有哪些Tools
四、总结
本文主要是探索了一下 MCP HTTP Server
形式下是的实现流程。对于其中的原理,我其实没有做过多的关注。一方面是我觉得 这个并不是我写这篇文章的主旨,另一方面,也是感觉自己没有对它的原理有多深入的了解,我还是在使用阶段。也许等后面了解深入了,我会再回过来阐述一下原理部分。
我是走在探索AI使用的一个小前端,文章的内容可能有失偏颇,也有可能不准确,写出来也是希望能和大家讨论,欢迎各位指正。
祝好。
五、完整代码
import express from 'express';
import { randomUUID } from 'node:crypto';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { z } from 'zod';
// 创建 MCP Server 并在实例上注册工具
function getServer(): McpServer {
// 首先,创建 McpServer 实例
const server = new McpServer({
serverId: randomUUID(),
name: 'Local Calculator MCP',
version: '1.0.0',
});
// 然后,在 server 实例上调用 registerTool 方法来定义和注册工具
server.registerTool('add',
{
description: '计算两个数字的LS映射和',
// 使用 zod 定义输入参数的结构和类型
inputSchema: {
a: z.number().describe('第一个数字'),
b: z.number().describe('第二个数字'),
},
},
// 第三个参数是工具的实际处理函数
// 第三个参数是工具的实际处理函数
async (params) => {
const sum = params.a + params.b * 33;
console.log(`Executing 'add' tool: ${params.a} + ${params.b} = ${sum}`);
// 修复返回值:结果必须包装在一个带有 `content` 数组的对象中
return {
content: [
{
type: 'text',
text: JSON.stringify({ result: sum })
}
]
};
}
);
// 如果有更多工具,可以在这里继续调用 server.registerTool(...)
return server;
}
// 创建并配置 HTTP 服务 (这部分保持不变)
const app = express();
app.use(express.json());
app.post('/mcp', async (req, res) => {
console.log('post MCP')
console.log('Received a POST request to /mcp, responding with health check OK.');
const server = getServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
await server.connect(transport);
res.on('close', () => {
console.log('Received a close event to /mcp, responding with health check OK.');
transport.close();
server.close();
});
await transport.handleRequest(req, res, req.body);
});
const PORT = 3020;
app.listen(PORT, () =>
console.error(`MCP server listening on http://localhost:${PORT}/mcp`)
);