一个用于构建能够处理客户端会话的 MCP 服务器的 TypeScript 框架。
[!NOTE]
如需 Python 实现,请参阅 FastMCP。
FastMCP 构建在官方 SDK 之上。
官方 SDK 提供了构建 MCP 的基础模块,但许多实现细节需要您自行处理:
FastMCP 通过提供一个有主见的框架来消除这种复杂性,该框架能够:
何时选择 FastMCP: 您希望快速构建 MCP 服务器,而无需处理底层的实现细节。
何时使用官方 SDK: 您需要最大程度的控制权,或者有特定的架构需求。在这种情况下,我们建议参考 FastMCP 的实现以避免常见陷阱。
npm install fastmcp
[!NOTE]
社区中有许多使用 FastMCP 的真实案例。请参阅 案例展示。
import { FastMCP } from "fastmcp";
import { z } from "zod"; // 或者任何支持 Standard Schema 的校验库
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
});
server.addTool({
name: "add",
description: "Add two numbers",
parameters: z.object({
a: z.number(),
b: z.number(),
}),
execute: async (args) => {
return String(args.a + args.b);
},
});
server.start({
transportType: "stdio",
});
仅此而已! 您已经拥有了一个可工作的 MCP 服务器。
你可以在终端中测试服务器:
git clone https://github.com/punkpeye/fastmcp.git
cd fastmcp
pnpm install
pnpm build
# 使用 CLI 测试加法服务器示例:
npx fastmcp dev src/examples/addition.ts
# 使用 MCP Inspector 测试加法服务器示例:
npx fastmcp inspect src/examples/addition.ts
如果您正在寻找构建自己 MCP 服务器的模板仓库,请查看 fastmcp-boilerplate。
FastMCP 支持多种传输选项来实现远程通信,允许托管在远程机器上的 MCP 通过网络访问。
HTTP 流式传输 在支持它的环境中提供了一种比 SSE 更高效的替代方案,对于较大的负载具有潜在更好的性能。
您可以使用 HTTP 流式传输支持来运行服务器:
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
},
});
这将启动服务器,并在 http://localhost:8080/mcp 监听 HTTP 流式传输连接。
注意: 您还可以使用
httpStream.endpoint选项自定义端点路径(默认为/mcp)。注意: 这也会在
http://localhost:8080/sse启动一个 SSE 服务器。
您可以使用适当的客户端传输连接到这些服务器。
对于 HTTP 流式传输连接:
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const client = new Client(
{
name: "example-client",
version: "1.0.0",
},
{
capabilities: {},
},
);
const transport = new StreamableHTTPClientTransport(
new URL(`http://localhost:8080/mcp`),
);
await client.connect(transport);
对于 SSE 连接:
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
const client = new Client(
{
name: "example-client",
version: "1.0.0",
},
{
capabilities: {},
},
);
const transport = new SSEClientTransport(new URL(`http://localhost:8080/sse`));
await client.connect(transport);
FastMCP 通过提供 SSL 证书选项支持 HTTPS 安全连接:
server.start({
transportType: "httpStream",
httpStream: {
port: 8443,
sslCert: "./path/to/cert.pem",
sslKey: "./path/to/key.pem",
sslCa: "./path/to/ca.pem", // 可选:用于客户端证书认证
},
});
这将使用 HTTPS 在 https://localhost:8443/mcp 启动服务器。
SSL 选项:
sslCert - SSL 证书文件路径sslKey - SSL 私钥文件路径sslCa - (可选)用于双向 TLS 认证的 CA 证书路径用于测试,您可以生成自签名证书:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"
用于生产环境,请从受信任的 CA(如 Let's Encrypt)获取证书。
请参阅 https-server 示例 以获取完整演示。
FastMCP 允许您在 MCP 端点旁边添加自定义 HTTP 路由,使您能够在同一服务器进程中构建包括 REST API、Webhook、管理界面等在内的全面 HTTP 服务。
// 添加 REST API 端点
server.addRoute("GET", "/api/users", async (req, res) => {
res.json({ users: [] });
});
// 处理路径参数
server.addRoute("GET", "/api/users/:id", async (req, res) => {
res.json({
userId: req.params.id,
query: req.query, // 访问查询参数
});
});
// 处理带有 body 解析的 POST 请求
server.addRoute("POST", "/api/users", async (req, res) => {
const body = await req.json();
res.status(201).json({ created: body });
});
// 提供 HTML 内容
server.addRoute("GET", "/admin", async (req, res) => {
res.send("<html><body><h1>Admin Panel</h1></body></html>");
});
// 处理 Webhook
server.addRoute("POST", "/webhook/github", async (req, res) => {
const payload = await req.json();
const event = req.headers["x-github-event"];
// 处理 webhook...
res.json({ received: true });
});
自定义路由支持:
:param)和通配符(*)authenticate 函数进行身份验证路由按照注册的顺序进行匹配,允许您在通配模式之前定义特定路由。
默认情况下,自定义路由需要身份验证(如果已配置)。您可以通过添加 { public: true } 选项使路由公开:
// 公共路由 - 无需身份验证
server.addRoute(
"GET",
"/.well-known/openid-configuration",
async (req, res) => {
res.json({
issuer: "https://example.com",
authorization_endpoint: "https://example.com/auth",
token_endpoint: "https://example.com/token",
});
},
{ public: true },
);
// 私有路由 - 需要身份验证
server.addRoute("GET", "/api/users", async (req, res) => {
// req.auth 包含经过身份验证的用户数据
res.json({ users: [] });
});
// 公共静态文件
server.addRoute(
"GET",
"/public/*",
async (req, res) => {
// 提供静态文件,无需身份验证
res.send(`File: ${req.url}`);
},
{ public: true },
);
公共路由非常适合:
.well-known/*)请参阅 custom-routes 示例 以获取完整演示。
FastMCP 支持 Cloudflare Workers 等边缘运行时,使 MCP 服务器能够部署到边缘,全球延迟最低。
| 使用场景 | 类 | 导入 |
|---|---|---|
| Node.js、Express、Bun | FastMCP |
import { FastMCP } from "fastmcp" |
| Cloudflare Workers、Deno Deploy | EdgeFastMCP |
import { EdgeFastMCP } from "fastmcp/edge" |
| 特性 | FastMCP | EdgeFastMCP |
|---|---|---|
| 运行时 | Node.js | 边缘(V8 沙箱) |
| 启动方式 | server.start({ port }) |
export default server |
| 传输协议 | stdio、httpStream、SSE | 仅 HTTP Streamable |
| 会话管理 | 有状态或无状态 | 仅无状态 |
| 文件系统 | 是 | 否 |
| OAuth/身份验证 | 内置 authenticate 选项 |
使用 Hono 中间件(内置计划中) |
| 自定义路由 | server.getApp() |
server.getApp() |
注意: EdgeFastMCP 的内置身份验证功能计划在未来版本中发布。FastMCP 和 EdgeFastMCP 内部都使用 Hono,因此没有技术障碍——EdgeFastMCP 只是在 OAuth 被添加到 FastMCP 之前编写的。欢迎提交 PR 添加一个接受 Web
Request而非 Node.jshttp.IncomingMessage的authenticate选项。在此期间,请使用 Hono 中间件:
ts const app = server.getApp(); app.use("/api/*", async (c, next) => { if (c.req.header("authorization") !== "Bearer secret") { return c.json({ error: "Unauthorized" }, 401); } await next(); });
要将 FastMCP 部署到 Cloudflare Workers,请使用 /edge 子路径中的 EdgeFastMCP 类:
import { EdgeFastMCP } from "fastmcp/edge";
import { z } from "zod";
const server = new EdgeFastMCP({
name: "My Edge Server",
version: "1.0.0",
description: "MCP server running on Cloudflare Workers",
});
// 像往常一样添加工具、资源、提示
server.addTool({
name: "greet",
description: "Greet someone",
parameters: z.object({
name: z.string(),
}),
execute: async ({ name }) => {
return `Hello, ${name}! Served from the edge.`;
},
});
// 将服务器导出为默认(Cloudflare Workers 要求)
export default server;
在边缘运行时上运行时:
您可以访问底层的 Hono 应用来添加自定义 HTTP 路由:
const app = server.getApp();
// 添加一个登陆页面
app.get("/", (c) => c.html("<h1>Welcome to my MCP server</h1>"));
// 添加 REST API 端点
app.get("/api/status", (c) => c.json({ status: "ok" }));
配置您的 wrangler.toml:
name = "my-mcp-server"
main = "src/index.ts"
compatibility_date = "2024-01-01"
使用以下命令部署:
wrangler deploy
请参阅 edge-cloudflare-worker 示例 以获取完整演示。
FastMCP 支持 HTTP 流式传输的无状态操作,其中每个请求独立处理,不维持持久会话。这对于无服务器环境、负载均衡部署或不需要会话状态的场景非常理想。
在无状态模式下:
您可以通过添加 stateless: true 选项来启用无状态模式:
server.start({
transportType: "httpStream",
httpStream: {
port: 8080,
stateless: true,
},
});
注意: 无状态模式仅适用于 HTTP 流式传输。依赖持久会话的功能(如特定于会话的状态)在无状态模式下不可用。
您还可以使用 CLI 参数或环境变量启用无状态模式:
# 通过 CLI 参数
npx fastmcp dev src/server.ts --transport http-stream --port 8080 --stateless true
# 通过环境变量
FASTMCP_STATELESS=true npx fastmcp dev src/server.ts
/ready 健康检查端点将指示服务器是否以无状态模式运行:
{
"mode": "stateless",
"ready": 1,
"status": "ready",
"total": 1
}
MCP 中的工具允许服务器公开可执行的函数,这些函数可以被客户端调用,并由 LLM 用来执行操作。
FastMCP 使用 Standard Schema 规范来定义工具参数。这允许您使用偏好的模式验证库(如 Zod、ArkType 或 Valibot),只要它们实现了该规范。
Zod 示例:
import { z } from "zod";
server.addTool({
name: "fetch-zod",
description: "Fetch the content of a url (using Zod)",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return await fetchWebpageContent(args.url);
},
});
ArkType 示例:
import { type } from "arktype";
server.addTool({
name: "fetch-arktype",
description: "Fetch the content of a url (using ArkType)",
parameters: type({
url: "string",
}),
execute: async (args) => {
return await fetchWebpageContent(args.url);
},
});
Valibot 示例:
Valibot 需要对等依赖 @valibot/to-json-schema。
import * as v from "valibot";
server.addTool({
name: "fetch-valibot",
description: "Fetch the content of a url (using Valibot)",
parameters: v.object({
url: v.string(),
}),
execute: async (args) => {
return await fetchWebpageContent(args.url);
},
});
创建不需要参数的工具时,您有两个选择:
typescript
server.addTool({
name: "sayHello",
description: "Say hello",
// 没有 parameters 属性
execute: async () => {
return "Hello, world!";
},
});
```typescript
import { z } from "zod";
server.addTool({
name: "sayHello",
description: "Say hello",
parameters: z.object({}), // 空对象
execute: async () => {
return "Hello, world!";
},
});
```
[!NOTE]
两种方式都与所有 MCP 客户端(包括 Cursor)完全兼容。FastMCP 在两种情况下都会自动生成正确的模式。
您可以通过向工具定义添加一个可选的 canAccess 函数来控制哪些工具对已认证用户可用。该函数接收身份验证上下文,如果允许用户访问该工具,则应返回 true。
server.addTool({
name: "admin-tool",
description: "An admin-only tool",
canAccess: (auth) => auth?.role === "admin",
execute: async () => "Welcome, admin!",
});
execute 可以返回一个字符串:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return "Hello, world!";
},
});
后者等价于:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "text",
text: "Hello, world!",
},
],
};
},
});
如果您想返回消息列表,可以返回一个带有 content 属性的对象:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{ type: "text", text: "First message" },
{ type: "text", text: "Second message" },
],
};
},
});
使用 imageContent 创建图像的内容对象:
import { imageContent } from "fastmcp";
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return imageContent({
url: "https://example.com/image.png",
});
// 或者...
// return imageContent({
// path: "/path/to/image.png",
// });
// 或者...
// return imageContent({
// buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
// });
// 或者...
// return {
// content: [
// await imageContent(...)
// ],
// };
},
});
imageContent 函数接受以下选项:
url:图像的 URL。path:图像文件的路径。buffer:作为缓冲区的图像数据。只能指定 url、path 或 buffer 中的一个。
上述示例等价于:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "image",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
mimeType: "image/png",
},
],
};
},
});
FastMCP 包含一个可配置的心跳机制来维护连接健康。可以通过服务器选项自定义心跳行为:
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
ping: {
// 显式启用或禁用 ping(默认值因传输方式而异)
enabled: true,
// 配置心跳间隔(毫秒)(默认:5000ms)
intervalMs: 10000,
// 设置与心跳相关的消息日志级别(默认:'debug')
logLevel: "debug",
},
});
默认情况下,心跳行为针对每种传输类型进行了优化:
stdio 连接禁用(通常不需要心跳)这种可配置的方法有助于减少日志冗余,并为不同的使用场景优化性能。
当您使用 httpStream 传输运行 FastMCP 时,可以选择暴露一个简单的 HTTP 端点,返回纯文本响应,用于负载均衡器或容器编排的存活检查。
通过服务器选项中的 health 键启用(或自定义)端点:
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
health: {
// 启用/禁用(默认:true)
enabled: true,
// 端点返回的正文(默认:'ok')
message: "healthy",
// 应响应的路径(默认:'/health')
path: "/healthz",
// 要返回的 HTTP 状态码(默认:200)
status: 200,
},
});
await server.start({
transportType: "httpStream",
httpStream: { port: 8080 },
});
现在,对 http://localhost:8080/healthz 的请求将返回:
HTTP/1.1 200 OK
content-type: text/plain
healthy
当服务器以 stdio 传输启动时,该端点将被忽略。
FastMCP 支持根目录——该功能允许客户端提供一组类似文件系统的根目录位置,这些位置可以被列出和动态更新。根目录功能可以在服务器选项中配置或禁用:
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
roots: {
// 设置为 false 以显式禁用根目录支持
enabled: false,
// 默认情况下,根目录支持是启用的(true)
},
});
这提供了以下好处:
您可以在服务器中监听根目录更改:
server.on("connect", (event) => {
const session = event.session;
// 访问当前根目录
console.log("Initial roots:", session.roots);
// 监听根目录的更改
session.on("rootsChanged", (event) => {
console.log("Roots changed:", event.roots);
});
});
当客户端不支持根目录或根目录功能被显式禁用时,这些操作将优雅地处理情况,不会抛出错误。
使用 audioContent 创建音频的内容对象:
import { audioContent } from "fastmcp";
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return audioContent({
url: "https://example.com/audio.mp3",
});
// 或者...
// return audioContent({
// path: "/path/to/audio.mp3",
// });
// 或者...
// return audioContent({
// buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
// });
// 或者...
// return {
// content: [
// await audioContent(...)
// ],
// };
},
});
audioContent 函数接受以下选项:
url:音频的 URL。path:音频文件的路径。buffer:作为缓冲区的音频数据。只能指定 url、path 或 buffer 中的一个。
上述示例等价于:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "audio",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
mimeType: "audio/mpeg",
},
],
};
},
});
您可以以这种方式组合各种类型并将它们发送回 AI:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
return {
content: [
{
type: "text",
text: "Hello, world!",
},
{
type: "image",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
mimeType: "image/png",
},
{
type: "audio",
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
mimeType: "audio/mpeg",
},
],
};
},
// 或者...
// execute: async (args) => {
// const imgContent = await imageContent({
// url: "https://example.com/image.png",
// });
// const audContent = await audioContent({
// url: "https://example.com/audio.mp3",
// });
// return {
// content: [
// {
// type: "text",
// text: "Hello, world!",
// },
// imgContent,
// audContent,
// ],
// };
// },
});
FastMCP 允许您提供自定义的日志记录器实现,以控制服务器如何记录消息。这对于与现有日志基础设施集成或自定义日志格式非常有用。
import { FastMCP, Logger } from "fastmcp";
class CustomLogger implements Logger {
debug(...args: unknown[]): void {
console.log("[DEBUG]", new Date().toISOString(), ...args);
}
error(...args: unknown[]): void {
console.error("[ERROR]", new Date().toISOString(), ...args);
}
info(...args: unknown[]): void {
console.info("[INFO]", new Date().toISOString(), ...args);
}
log(...args: unknown[]): void {
console.log("[LOG]", new Date().toISOString(), ...args);
}
warn(...args: unknown[]): void {
console.warn("[WARN]", new Date().toISOString(), ...args);
}
}
const server = new FastMCP({
name: "My Server",
version: "1.0.0",
logger: new CustomLogger(),
});
请参阅 src/examples/custom-logger.ts 获取使用 Winston、Pino 和基于文件的日志记录示例。
工具可以使用上下文对象中的 log 对象向客户端记录消息:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args, { log }) => {
log.info("Downloading file...", {
url,
});
// ...
log.info("Downloaded file");
return "done";
},
});
log 对象具有以下方法:
debug(message: string, data?: SerializableValue)error(message: string, data?: SerializableValue)info(message: string, data?: SerializableValue)warn(message: string, data?: SerializableValue)应该向用户显示的错误应作为 UserError 实例抛出:
import { UserError } from "fastmcp";
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args) => {
if (args.url.startsWith("https://example.com")) {
throw new UserError("This URL is not allowed");
}
return "done";
},
});
工具可以通过调用上下文对象中的 reportProgress 来报告进度:
server.addTool({
name: "download",
description: "Download a file",
parameters: z.object({
url: z.string(),
}),
execute: async (args, { reportProgress }) => {
await reportProgress({
progress: 0,
total: 100,
});
// ...
await reportProgress({
progress: 100,
total: 100,
});
return "done";
},
});
FastMCP 支持在工具执行期间流式传输部分结果,从而实现响应式用户界面和实时反馈。这对于以下场景特别有用:
要为工具启用流式传输,请添加 streamingHint 注释并使用 streamContent 方法:
server.addTool({
name: "generateText",
description: "Generate text incrementally",
parameters: z.object({
prompt: z.string(),
}),
annotations: {
streamingHint: true, // 表示此工具使用流式传输
readOnlyHint: true,
},
execute: async (args, { streamContent }) => {
// 立即发送初始内容
await streamContent({ type: "text", text: "Starting generation...\n" });
// 模拟增量内容生成
const words = "The quick brown fox jumps over the lazy dog.".split(" ");
for (const word of words) {
await streamContent({ type: "text", text: word + " " });
await new Promise((resolve) => setTimeout(resolve, 300)); // 模拟延迟
}
// 使用 streamContent 时,您可以:
// 1. 返回 void(如果所有内容都已流式传输)
// 2. 返回最终结果(将附加到流式传输的内容后)
// 选项 1:所有内容都已流式传输,因此返回 void
return;
// 选项 2:返回将附加的最终内容
// return "Generation complete!";
},
});
流式传输适用于所有内容类型(文本、图像、音频),并且可以与进度报告结合使用:
```js
server.addTool({
name: "processData",
description: "Process data with streaming updates",
parameters: z.object({
datasetSize: z.number(),
}),
annotations: {
streamingHint: true,
},
execute: async (args, { streamContent, reportProgress }) => {
const total = args.datasetSize;
for (let i = 0; i < total; i++)