mcpField
F
FastMCP

FastMCP

本地服务
github.com/punkpeye开发工具3.3k1 天前更新

关于 FastMCP

FastMCP 是一个基于 TypeScript 的框架,用于构建可处理客户端会话的 MCP 服务。它基于官方 SDK 封装,面向希望快速开发 MCP 服务而不处理底层细节的开发者。该框架仅支持 2025-11-25 及更早的握手式协议版本,不支持 2026-07-28 规范。

主要功能

  1. 01Tool、Resource、Prompt 的简洁定义方式
  2. 02OpenAPI 到 MCP 的转换
  3. 03HTTP Streaming 与 SSE 兼容传输
  4. 04Edge Runtime 支持,可部署到 Cloudflare Workers 等环境
  5. 05无状态模式,适用于 Serverless 部署
  6. 06内存传输,便于无端口的单元测试

适用场景

  1. 01快速构建 TypeScript 编写的 MCP 服务
  2. 02将 OpenAPI 接口转换为 MCP 工具
  3. 03部署到 Cloudflare Workers 等边缘运行时

MCP 相关

FastMCP 用于构建 MCP 服务,向 MCP 客户端暴露 Tool、Resource 和 Prompt。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"fastmcp-punkpeye": {
"command": "npx",
"args": [
"-y",
"fastmcp"
]
}
}
}
启动命令npx -y fastmcp

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npmfastmcp

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

FastMCP

一个用于构建 MCP 服务的 TypeScript 框架,可处理客户端会话。

重要

FastMCP 实现的是基于握手的旧版 MCP 修订版(2025-11-25 及更早版本)。它不支持当前规范 2026-07-28,该规范将协议改为无状态——不再需要 initialize 握手,也没有 Mcp-Session-Id。如需面向当前规范的框架,请使用 ViteMCP。

功能

何时选择 FastMCP,而不是官方 SDK?

FastMCP 基于官方 SDK 构建。

官方 SDK 提供了构建 MCP 的基础模块,但许多实现细节仍需由你处理:

FastMCP 通过提供一个有明确主张的框架来消除这些复杂性,该框架:

  • 自动处理所有样板代码
  • 为常见任务提供简单直观的 API
  • 内置最佳实践和错误处理
  • 让你专注于 MCP 的核心功能

**何时选择 FastMCP:**你希望快速构建 MCP 服务,而不必处理底层实现细节。

**何时使用官方 SDK:**你需要最大程度的控制,或有特定的架构要求。在这种情况下,我们建议参考 FastMCP 的实现,以避免常见问题。

安装

npm install fastmcp

快速开始

说明

实际应用中有许多使用 FastMCP 的示例。请参阅展示查看示例。

import { FastMCP } from "fastmcp";
import { z } from "zod"; // Or any validation library that supports 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

# Test the addition server example using CLI:
npx fastmcp dev src/examples/addition.ts
# Test the addition server example using MCP Inspector:
npx fastmcp inspect src/examples/addition.ts

如果你正在寻找一个用于构建自己的 MCP 服务的样板代码仓库,请查看 fastmcp-boilerplate。

远程服务选项

FastMCP 支持多种远程通信传输方式,因此可以通过网络访问托管在远程机器上的 MCP。

HTTP 流式传输

HTTP 流式传输在支持该方式的环境中,提供了比 SSE 更高效的替代方案,对于较大的有效负载,性能可能更好。

你可以运行支持 HTTP 流式传输的服务:

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
  },
});

这将启动服务,并在 http://localhost:8080/mcp 上监听 HTTP 流式连接。

**注意:**你也可以使用 httpStream.endpoint 选项自定义端点路径(默认值为 /mcp)。

**注意:**若要在签发者路径下提供 HTTP 流式传输和内置 OAuth 路由,请设置 httpStream.basePath(例如 /issuer1)。这会根据 RFC 8414,在 /.well-known/oauth-authorization-server/issuer1 提供授权服务器元数据。

**注意:**这也会在 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);
HTTPS 支持

FastMCP 支持通过提供 SSL 证书选项来建立安全连接:

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8443,
    sslCert: "./path/to/cert.pem",
    sslKey: "./path/to/key.pem",
    sslCa: "./path/to/ca.pem", // Optional: for client certificate authentication
  },
});

这将以 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"

用于生产环境时,请从 Let's Encrypt 等受信任的 CA 获取证书。

请参阅 https-server 示例,了解完整演示。

CORS 配置

FastMCP 默认启用 CORS,并使用一组标准的允许标头。你可以通过传入 cors 选项自定义 CORS 行为:

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
    cors: {
      origin: "http://localhost:3000",
      allowedHeaders: [
        "Content-Type",
        "Authorization",
        "Accept",
        "Mcp-Session-Id",
        "Mcp-Protocol-Version",
        "Last-Event-Id",
        "X-Custom-Header",
      ],
      credentials: true,
    },
  },
});

cors 选项接受:

  • true(默认)- 使用默认设置启用 CORS
  • false - 完全禁用 CORS
  • 包含以下字段的对象:
    • origin - 字符串、字符串数组,或函数 (origin: string) => boolean
    • allowedHeaders - 字符串或字符串数组
    • methods - 允许的 HTTP 方法数组
    • exposedHeaders - 要公开的标头数组
    • credentials - 是否允许凭据的布尔值
    • maxAge - 预检请求缓存时长,单位为秒

为方便使用,CorsOptions 类型已从 fastmcp 导出。

自定义 HTTP 路由

FastMCP 允许你在 MCP 端点旁添加自定义 HTTP 路由,从而在同一服务进程中构建包含 REST API、Webhook、管理界面等功能的综合 HTTP 服务。

const app = server.getApp();

// Add REST API endpoints with Hono's native API
app.get("/api/users", async (c) => {
  return c.json({ users: [] });
});

// Handle path parameters
app.get("/api/users/:id", async (c) => {
  return c.json({
    userId: c.req.param("id"),
    query: c.req.query(), // Access query parameters
  });
});

// Handle POST requests with body parsing
app.post("/api/users", async (c) => {
  const body = await c.req.json();
  return c.json({ created: body }, 201);
});

// Serve HTML content
app.get("/admin", async (c) => {
  return c.html("<html><body><h1>Admin Panel</h1></body></html>");
});

// Handle webhooks
app.post("/webhook/github", async (c) => {
  const payload = await c.req.json();
  const event = c.req.header("x-github-event");

  // Process webhook...
  return c.json({ received: true });
});

自定义路由使用 server.getApp() 返回的底层 Hono 应用,并支持:

  • Hono 的 HTTP 方法:get、post、put、delete、patch、options 等
  • 路径参数(:param)和通配符(*)
  • 查询字符串解析
  • 来自 c.req 的 JSON、文本、表单及其他请求体辅助方法
  • 自定义状态码和标头
  • 通过 Hono 使用中间件和路由组

路由按注册顺序进行匹配,因此你可以先定义特定路由,再定义兜底模式。

公开和受保护的路由

自定义 Hono 路由默认是公开的,除非你添加自己的路由中间件或身份验证检查。对于受保护的自定义路由,请将身份验证逻辑放入可复用的辅助函数中,并在 FastMCP 的 authenticate 选项和 Hono 路由处理程序中调用它:

import type { IncomingMessage } from "node:http";
import type { Context } from "hono";
import { FastMCP } from "fastmcp";

async function authenticateRequest(request: IncomingMessage) {
  const apiKey = request.headers["x-api-key"];
  return apiKey === "123" ? { userId: "123" } : undefined;
}

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  authenticate: authenticateRequest,
});

const app = server.getApp();

async function requireAuth(c: Context) {
  const auth = await authenticateRequest(c.env.incoming);

  if (!auth) {
    return c.json({ error: "Authentication required" }, 401);
  }

  return auth;
}

// Public route - no authentication required
app.get("/.well-known/openid-configuration", async (c) => {
  return c.json({
    issuer: "https://example.com",
    authorization_endpoint: "https://example.com/auth",
    token_endpoint: "https://example.com/token",
  });
});

// Private route - requires authentication
app.get("/api/users", async (c) => {
  const auth = await requireAuth(c);
  if (auth instanceof Response) {
    return auth;
  }

  return c.json({ users: [] });
});

// Public static files
app.get("/public/*", async (c) => {
  return c.text(`File: ${c.req.path}`);
});

公开路由非常适合用于:

  • OAuth 发现端点(.well-known/*)
  • 健康检查和状态页面
  • 静态资源和文档
  • 来自外部服务的 Webhook 端点
  • 不需要用户身份验证的公开 API

请参阅 custom-routes 示例,了解完整演示。

边缘运行时支持

FastMCP 支持 Cloudflare Workers 等边缘运行时,使 MCP 服务能够以极低延迟部署到全球各地的边缘节点。

在 FastMCP 和 EdgeFastMCP 之间进行选择
用例类导入
Node.js、Express、BunFastMCPimport { FastMCP } from "fastmcp"
Cloudflare Workers、Deno DeployEdgeFastMCPimport { EdgeFastMCP } from "fastmcp/edge"
功能FastMCPEdgeFastMCP
运行时Node.jsEdge(V8 isolates)
启动方法server.start({ port })export default server
传输方式stdio、httpStream、SSE仅 HTTP Streamable
会话有状态或无状态仅无状态
文件系统支持不支持
OAuth/身份验证内置 authenticate 选项使用 Hono 中间件(计划内置)
自定义路由server.getApp()server.getApp()

注意: 计划在未来版本中为 EdgeFastMCP 添加内置身份验证。FastMCP 和 EdgeFastMCP 内部都使用 Hono,因此不存在技术障碍——EdgeFastMCP 只是在 FastMCP 添加 OAuth 之前编写的。欢迎提交 PR,添加一个接受 Web Request 而非 Node.js http.IncomingMessage 的 authenticate 选项。

同时,请使用 Hono 中间件:

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();
});
Cloudflare Workers

要将 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",
});

// Add tools, resources, prompts as usual
server.addTool({
  name: "greet",
  description: "Greet someone",
  parameters: z.object({
    name: z.string(),
  }),
  execute: async ({ name }) => {
    return `Hello, ${name}! Served from the edge.`;
  },
});

// Export the server as the default (required for Cloudflare Workers)
export default server;
Edge 运行时差异

在 Edge 运行时中运行时:

  • 默认无状态:每个请求都会独立处理
  • 无法访问文件系统:使用 fetch API 获取外部数据
  • V8 Isolates:冷启动快,资源使用高效
  • 全球部署:自动分发到各个 Edge 位置
Edge 上的自定义路由

你可以访问底层 Hono 应用,以添加自定义 HTTP 路由:

const app = server.getApp();

// Add a landing page
app.get("/", (c) => c.html("<h1>Welcome to my MCP server</h1>"));

// Add REST API endpoints
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 参数或环境变量启用无状态模式:

# Via CLI argument
npx tsx src/server.ts --transport http-stream --port 8080 --stateless true

# Via environment variable
FASTMCP_TRANSPORT=httpStream FASTMCP_STATELESS=true npx tsx src/server.ts

/ready 健康检查端点会指示服务器是否以无状态模式运行:

{
  "mode": "stateless",
  "ready": 1,
  "status": "ready",
  "total": 1
}

核心概念

工具

MCP 中的工具允许服务公开可由客户端调用并供大模型用于执行操作的函数。

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 需要 peer dependency @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);
  },
});

纯 JSON Schema 示例:

如果你已有 JSON Schema(来自 OpenAPI 文档、配置文件或 其他服务),jsonSchemaAdapter 可以将其封装起来,直接使用, 无需额外的架构库。

它需要 peer dependency ajv 来执行验证;如果你的架构使用了 email 或 uri 等 format 关键字,还需要 ajv-formats。 这两个库会在首次调用工具时导入,因此不使用它们的服务无需承担任何开销。

npm install ajv ajv-formats
import { jsonSchemaAdapter } from "fastmcp";

server.addTool({
  name: "fetch-json-schema",
  description: "Fetch the content of a url (using plain JSON Schema)",
  parameters: jsonSchemaAdapter({
    type: "object",
    properties: {
      url: { type: "string", format: "uri" },
    },
    required: ["url"],
  }),
  execute: async (args) => {
    const { url } = args as { url: string };
    return await fetchWebpageContent(url);
  },
});

outputSchema 也适用。请注意,FastMCP 会为输入架构声明 additionalProperties: false,无论你的架构如何设置;Zod 和 Valibot 架构也会受到同样处理。例外情况是仅由附加属性构成的对象:字典(没有 properties 且有 additionalProperties 架构,例如 z.record())会保留其值架构;自由格式参数({ type: "object" } 或 additionalProperties: true) 则保持开放。输出架构会保留你声明的 additionalProperties 规则。

与上述架构库不同,纯 JSON Schema 不包含 TypeScript 类型,因此 execute 接收的参数类型为 unknown。请自行进行类型转换或收窄。

无参数工具

创建无需参数的工具时,有两种选择:

  1. 完全省略 parameters 属性:

    server.addTool({
      name: "sayHello",
      description: "Say hello",
      // No parameters property
      execute: async () => {
        return "Hello, world!";
      },
    });
    
  2. 显式定义空参数:

    import { z } from "zod";
    
    server.addTool({
      name: "sayHello",
      description: "Say hello",
      parameters: z.object({}), // Empty object
      execute: async () => {
        return "Hello, world!";
      },
    });
    

说明

这两种方式都与所有 MCP 客户端完全兼容,包括 Cursor。FastMCP 会在这两种情况下自动生成合适的架构。

结构化工具输出

工具可以声明 outputSchema 并返回结构化数据。FastMCP 会将该值作为 MCP structuredContent 暴露,同时也会返回 JSON 文本回退内容,以便仅渲染文本内容的客户端使用。

server.addTool({
  name: "get-weather",
  description: "Get weather for a city",
  parameters: z.object({
    city: z.string(),
  }),
  outputSchema: z.object({
    temperature: z.number(),
    humidity: z.number(),
  }),
  execute: async ({ city }) => {
    const weather = await getWeather(city);

    return {
      temperature: weather.temperature,
      humidity: weather.humidity,
    };
  },
});

你也可以同时返回明确的文本内容和结构化内容:

server.addTool({
  name: "get-weather",
  description: "Get weather for a city",
  parameters: z.object({
    city: z.string(),
  }),
  outputSchema: z.object({
    temperature: z.number(),
    humidity: z.number(),
  }),
  execute: async ({ city }) => {
    const weather = await getWeather(city);

    return {
      content: [
        {
          type: "text",
          text: `${city}: ${weather.temperature}F`,
        },
      ],
      structuredContent: {
        temperature: weather.temperature,
        humidity: weather.humidity,
      },
    };
  },
});

提供 outputSchema 时,FastMCP 会在发送工具结果前验证 structuredContent。如果结构化输出无效,会作为工具错误返回给客户端,而不会在不提示的情况下违反已声明的架构。

工具授权

你可以通过在工具定义中添加可选的 canAccess 函数,控制已通过身份验证的用户可以使用哪些工具。此函数接收身份验证上下文,如果用户获准访问该工具,则应返回 true。

server.addTool({
  name: "admin-tool",
  description: "An admin-only tool",
  canAccess: (auth) => auth?.role === "admin",
  execute: async () => "Welcome, admin!",
});

没有 authenticate 函数的服务无需检查身份验证,因此每个会话都能看到所有工具。如果定义了该函数,但会话最终仍未通过身份验证——在 stdio 模式下,服务器会在 authenticate 抛出异常或不返回任何内容时继续运行——那么该会话看不到任何带有 canAccess 的工具。

返回字符串

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",
    });

    // or...
    // return imageContent({
    //   path: "/path/to/image.png",
    // });

    // or...
    // return imageContent({
    //   buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
    // });

    // or...
    // return {
    //   content: [
    //     await imageContent(...)
    //   ],
    // };
  },
});

imageContent 函数接受以下选项:

  • url:图像的 URL。
  • timeoutMs:从 URL 下载图像的可选超时时间,单位为毫秒(默认 30 秒)。
  • 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",
        },
      ],
    };
  },
});
可配置的 Ping 行为

FastMCP 内置了可配置的 ping 机制,用于维护连接健康状态。可以通过服务选项自定义 ping 行为:

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  ping: {
    // Explicitly enable or disable pings (defaults vary by transport)
    enabled: true,
    // Configure ping interval in milliseconds (default: 5000ms)
    intervalMs: 10000,
    // Set log level for ping-related messages (default: 'debug')
    logLevel: "debug",
  },
});

默认情况下,ping 行为会针对不同传输类型进行优化:

  • 为 SSE 和 HTTP 流式连接启用(这些连接可从保活机制中受益)
  • 为 stdio 连接禁用(通常不需要 ping)

这种可配置方式有助于减少日志冗余,并针对不同使用场景优化性能。

保持长时间工具调用的连接畅通(streamKeepalive)

Ping 通过独立的服务端到客户端流传输,因此无法保持单个工具调用的连接畅通;在 stateless 模式下,该流根本不存在。如果某个工具运行数分钟而没有产生输出,其响应连接就会一直处于静默状态,而服务前方的空闲连接超时机制(AWS ALB 的默认值为 60 秒)会在结果写入前关闭该连接。

这两种会话模式都会遇到这个问题。有状态部署会通过 ping 保持独立流畅通,但承载工具调用的连接仍会因闲置而关闭;无状态模式则从一开始就没有独立流。

streamKeepalive 会定期向正在执行的工具调用自己的响应流写入内容,也就是原本会被关闭的连接。此功能需显式启用:

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  streamKeepalive: {
    // Opt in; disabled by default
    enabled: true,
    // Keep comfortably below the shortest idle timeout on the path
    intervalMs: 20000,
  },
});

每条保活消息都是一条 notifications/message(级别可通过 logLevel 配置,默认为 debug),并带有日志记录器 fastmcp-keepalive,且与正在处理的工具调用相关联。工具开始执行时会启动保活;请求不再等待工具时(无论成功、出错、超时还是客户端取消)便会停止,因此空闲连接会保持静默。由于这些消息与请求相关联,此功能在有状态和 stateless 模式下均可使用,且除标准日志通知外不需要客户端提供支持。

需要注意的限制:

  • 使用 httpStream.enableJsonResponse 时,此功能无效。该选项会缓存单个 JSON 响应,而不是进行流式传输,因此没有可写入的开放流。
  • 此功能仅适用于工具调用。其他长时间运行的请求(resources/read、prompts/get)在完成前仍不会写入任何内容。
  • stdio 没有需要保活的代理,因此在此类连接上启用此功能只会增加通知流量。
取消长时间运行的工具调用(context.signal)

每次调用 execute 都会收到一个 AbortSignal,当其结果无法再送达任何接收方时,该信号就会触发。将它传递给实际执行工作的部分,以便工作能随调用一起停止,而不是在调用结束后仍继续运行:

server.addTool({
  name: "fetch_report",
  parameters: z.object({ url: z.string() }),
  timeoutMs: 30000,
  execute: async (args, { signal }) => {
    const response = await fetch(args.url, { signal });
    return await response.text();
  },
});

以下三种事件都会触发该信号:

  • 客户端取消了调用——客户端发送了 notifications/cancelled。当调用方中止自己的请求时,MCP SDK 就会发出此通知。
  • 会话结束——传输连接已关闭,或会话被显式关闭。此时不会涉及取消通知。
  • 经过了 timeoutMs 指定的时间——signal.reason 与调用方收到的 UserError 相同,因此工具可以区分超时和取消。

调用正常完成后,信号不会被触发,因此可以安全地将清理操作绑定到该信号。

需要注意的限制:

  • FastMCP 不会替你终止任何操作。 FastMCP 会停止等待工具,但 execute 返回的 promise 仍会继续运行,直到结束。忽略该信号的工具仍会运行至完成,只是届时已无处报告结果。
  • 无法检测在请求过程中断开的 HTTP 客户端。 MCP SDK 只会在收到显式的 notifications/cancelled 时中止请求自身的信号,而 StreamableHTTPServerTransport 不会将被放弃的响应流视为会话关闭。调用方若挂断连接而未终止会话(DELETE),工具会继续运行,直到完成或超时。对于任何开销较大的操作,都应设置 timeoutMs,而不要依赖断连检测。
  • load 不会收到该信号。 资源、资源模板和提示词收到的是不包含 signal 的精简上下文。
健康检查端点

使用 httpStream 传输运行 FastMCP 时,可以选择公开一个简单的 HTTP 端点,该端点会返回纯文本响应,可用于负载均衡器或容器编排的存活检查。

通过服务器选项中的 health 键启用或自定义端点:

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  health: {
    // Enable / disable (default: true)
    enabled: true,
    // Body returned by the endpoint (default: 'ok')
    message: "healthy",
    // Path that should respond (default: '/health')
    path: "/healthz",
    // HTTP status code to return (default: 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 传输启动服务器时,此端点会被忽略。

Roots 管理

FastMCP 支持 Roots——这项功能允许客户端提供一组类似文件系统的根位置,并对其进行列出和动态更新。可在服务器选项中配置或禁用 Roots 功能:

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  roots: {
    // Set to false to explicitly disable roots support
    enabled: false,
    // By default, roots support is enabled (true)
  },
});

这带来以下好处:

  • 与可能不支持 Roots 的不同客户端更好地兼容
  • 连接到未实现 roots 能力的客户端时,减少错误日志
  • 更明确地控制 MCP 服务能力
  • 在 roots 功能不可用时平稳降级

你可以在服务器中监听根位置的更改:

server.on("connect", (event) => {
  const session = event.session;

  // Access the current roots
  console.log("Initial roots:", session.roots);

  // Listen for changes to the roots
  session.on("rootsChanged", (event) => {
    console.log("Roots changed:", event.roots);
  });
});

当客户端不支持 roots,或明确禁用了 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",
    });

    // or...
    // return audioContent({
    //   path: "/path/to/audio.mp3",
    // });

    // or...
    // return audioContent({
    //   buffer: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=", "base64"),
    // });

    // or...
    // return {
    //   content: [
    //     await audioContent(...)
    //   ],
    // };
  },
});

audioContent 函数接受以下选项:

  • url:音频的 URL。
  • timeoutMs:下载 URL 的可选超时时间,单位为毫秒(默认为 30 秒)。
  • 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",
        },
      ],
    };
  },

  // or...
  // 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(),
});

有关使用 Winston、Pino 和基于文件的日志记录示例,请参阅 src/examples/custom-logger.ts。

日志记录

工具可以使用上下文对象中的 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)

客户端可以通过 logging/setLevel 请求提高所需的最低级别,FastMCP 随后会丢弃严重程度较低的消息,而不会发送。因此,在通过 logging/setLevel 设置为 error 后,log.debug 和 log.info 的消息将无人收到。在客户端发出请求之前,所有级别的消息都会发送。当前生效的级别可通过 session.loggingLevel 读取。

每条消息都会通过记录该消息的请求流发送,因此日志记录在 stateless 模式下也能正常工作。启用 httpStream.enableJsonResponse 时,每个请求只会收到一个 JSON 正文,此时请求运行期间记录的消息会被丢弃。

错误

应向用户显示的错误应以 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";
  },
});

reportProgress 接受一个可选的人类可读 message,以及数值字段;客户端可以将其显示在进度指示器旁边:

await reportProgress({
  progress: 40,
  total: 100,
  message: "Downloading chunk 4 of 10…",
});

只有客户端在工具调用中提供 progressToken 时,才会发送进度通知;否则,reportProgress 不会执行任何操作。由于 notifications/progress 是 MCP 规范的一部分(message 字段自修订版 2025-03-26 起纳入规范),因此这是在长时间运行的工具调用期间发送增量更新的可移植方式——下文的流式输出介绍了两者的区别。

流式输出

FastMCP 可以在工具仍在执行时流式传输部分结果,从而实现响应迅速的界面并提供实时反馈。这在以下情况中特别有用:

  • 增量生成内容的长时间运行操作
  • 逐步生成文本、图像或其他媒体
  • 用户能够从即时部分结果中受益的操作

重要

streamContent 是 FastMCP 扩展,不属于 MCP 规范。它会发送 notifications/tool/streamContent 通知,而 MCP 规范并未定义该通知——截至修订版 2025-11-25,尚无流式传输工具输出的标准机制(SEP-2998 是一项正在进行中的提案,旨在新增此机制)。

客户端会静默丢弃没有注册处理器的通知,且不会报错。只有当客户端为该方法注册处理器(或设置 fallbackNotificationHandler)时,才能看到流式内容,并且目前已知没有客户端会将其渲染为工具输出——例如,MCP Inspector 会通过回退处理器在通知面板中记录该内容,但工具结果本身仍只显示 execute 返回的内容。因此,流式传输主要适用于你也能控制客户端的场景——请参阅下文的使用流式内容。如果你需要能在任何客户端上使用的增量更新,请改用带有 message 的 reportProgress。

要从工具进行流式传输,请使用 streamContent 方法:

server.addTool({
  name: "generateText",
  description: "Generate text incrementally",
  parameters: z.object({
    prompt: z.string(),
  }),
  annotations: {
    streamingHint: true, // Advisory only; see below
    readOnlyHint: true,
  },
  execute: async (args, { streamContent }) => {
    // Send initial content immediately
    await streamContent({ type: "text", text: "Starting generation...\n" });

    // Simulate incremental content generation
    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)); // Simulate delay
    }

    // Always return a final result. Returning nothing sends an empty tool
    // result, so clients that ignore the streamed notifications see no output
    // at all.
    return "The quick brown fox jumps over the lazy dog.";
  },
});

警告

从 execute 返回 undefined 会生成 content 为空的工具结果。如果你流式传输了所有内容却不返回任何内容,那么工具调用最终会得到一个空结果,且不会提示有内容丢失——即使是在会记录通知的客户端上也是如此。请同时返回完整结果,并仅将流式内容用作渐进式渲染增强功能。

streamingHint 注解是建议性元数据。它会原样转发给 tools/list 中的客户端,但不会启用或限制 streamContent,FastMCP 本身也不会读取它。目前已知没有客户端会对此作出响应,不过 SEP-2998 提议标准化相同名称的注解。

使用流式内容

只有当客户端为该方法注册处理器(或设置 fallbackNotificationHandler)时,才能看到这些通知:

import { z } from "zod";

const StreamContentNotificationSchema = z.object({
  method: z.literal("notifications/tool/streamContent"),
  params: z.object({
    content: z.array(z.any()),
    toolName: z.string(),
  }),
});

client.setNotificationHandler(
  StreamContentNotificationSchema,
  (notification) => {
    const { content, toolName } = notification.params;
    // Render the partial content however you like.
  },
);

请注意,通知只携带 toolName,不携带请求令牌或进度令牌,因此无法区分同一会话中对同一工具的并发调用。

流式传输适用于所有内容类型(文本、图像、音频),并且可以与进度报告结合使用:

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++) {
      // Standard progress notification: reaches every spec-compliant client
      await reportProgress({
        progress: i,
        total,
        message: `Processed ${i} of ${total} items`,
      });

      // Richer partial content: only reaches clients that opt in
      if (i % 10 === 0) {
        await streamContent({
          type: "text",
          text: `Processed ${i} of ${total} items...\n`,
        });
      }

      await new Promise((resolve) => setTimeout(resolve, 50));
    }

    return "Processing complete!";
  },
});
询问补充信息

工具可在执行过程中通过 询问补充信息 请求用户提供更多信息,方法是在上下文对象中使用 elicit 方法。客户端必须声明与之匹配的 elicitation 能力模式:表单请求使用 elicitation: { form: {} }(默认值),URL 请求使用 elicitation: { url: {} },也可以两者都声明。

server.addTool({
  name: "delete-file",
  description: "Delete a file",
  parameters: z.object({
    path: z.string(),
  }),
  execute: async (args, { elicit }) => {
    const response = await elicit({
      message: `Are you sure you want to delete ${args.path}?`,
      requestedSchema: {
        type: "object",
        properties: {
          confirmed: {
            type: "boolean",
          },
        },
        required: ["confirmed"],
      },
    });

    if (response.action !== "accept" || !response.content?.confirmed) {
      return "Deletion cancelled.";
    }

    // ...

    return `Deleted ${args.path}`;
  },
});

响应中的 action 为 "accept"、"decline" 或 "cancel";接受请求时,content 包含与 requestedSchema 匹配的用户回答。也可以在工具之外使用 session.requestElicitation 询问补充信息。

工具注解

从 MCP Specification (2025-03-26) 起,工具可以通过添加有关工具行为的元数据来包含注解,从而提供更丰富的上下文和控制:

server.addTool({
  name: "fetch-content",
  description: "Fetch content from a URL",
  parameters: z.object({
    url: z.string(),
  }),
  annotations: {
    title: "Web Content Fetcher", // Human-readable title for UI display
    readOnlyHint: true, // Tool doesn't modify its environment
    openWorldHint: true, // Tool interacts with external entities
  },
  execute: async (args) => {
    return await fetchWebpageContent(args.url);
  },
});

可用的注解如下:

注解类型默认值描述
titlestring-易于用户理解的工具标题,适用于在 UI 中显示
readOnlyHintbooleanfalse如果为 true,表示该工具不会修改其环境
destructiveHintbooleantrue如果为 true,该工具可能会执行破坏性更新(仅在 readOnlyHint 为 false 时有意义)
idempotentHintbooleanfalse如果为 true,使用相同参数重复调用该工具不会产生额外影响(仅在 readOnlyHint 为 false 时有意义)
openWorldHintbooleantrue如果为 true,该工具可能会与“开放世界”中的外部实体交互

这些注解有助于客户端和大模型更好地理解如何使用工具,以及调用工具时会发生什么。

资源

资源代表 MCP 服务希望提供给客户端的任何类型的数据,包括:

  • 文件内容
  • 屏幕截图和图片
  • 日志文件
  • 以及更多内容

每个资源都有唯一的 URI,可包含文本或二进制数据。

server.addResource({
  uri: "file:///logs/app.log",
  name: "Application Logs",
  mimeType: "text/plain",
  async load() {
    return {
      text: await readLogFile(),
    };
  },
});

说明

load 可以返回多个资源。例如,读取某个目录时,可以返回该目录中的文件列表。

async load() {
  return [
    {
      text: "First file content",
    },
    {
      text: "Second file content",
    },
  ];
}

你也可以在 load 中返回二进制内容:

async load() {
  return {
    blob: 'base64-encoded-data'
  };
}

load 还会将 auth(你的 authenticate 函数返回的值,如果有)和 context 对象分别作为第二个和第三个参数接收。context 中包含 tool.execute 可用的 client、log、session 和 sessionId 字段(请参阅会话 ID 和请求 ID 跟踪);其中不包含 reportProgress 和 streamContent,因为它们与工具调用的进度令牌绑定:

server.addResource({
  uri: "file:///logs/app.log",
  name: "Application Logs",
  mimeType: "text/plain",
  async load(auth, context) {
    context.log.info("loading application logs", { requestedBy: auth?.userId });

    return {
      text: await readLogFile(),
    };
  },
});
订阅资源更新

客户端可以通过 MCP resources/subscribe 方法订阅资源,以便在其内容发生变化时收到通知。FastMCP 会自动为任何公开资源的服务声明 subscribe 能力,跟踪每个客户端的订阅,并允许你使用 sendResourceUpdated 发出更新通知:

server.addResource({
  uri: "file:///logs/app.log",
  name: "Application Logs",
  mimeType: "text/plain",
  async load() {
    return { text: await readLogFile() };
  },
});

// Whenever the underlying data changes, notify subscribed clients:
await server.sendResourceUpdated("file:///logs/app.log");

sendResourceUpdated 只会通知订阅了给定 URI 的客户端,因此数据发生变化时可以随时调用。FastMCP 还会为工具、资源和提示词声明 listChanged 能力,并在运行时添加或移除工具、资源、资源模板或提示词时,自动发送 notifications/tools/list_changed / notifications/resources/list_changed / notifications/prompts/list_changed。

无法向会话展示它未协商过的类型的原语。MCP 会在 initialize 期间确定能力,并在会话存续期间保持不变。因此,客户端连接时服务还没有任何资源,之后新增的资源对该客户端不可见——FastMCP 会记录一条指出具体能力的警告,并保持该会话不变。请在 start() 之前注册每种你打算添加的类型中的至少一个,或者让客户端重新连接;之后连接的会话则可以看到所有内容。

资源模板

你还可以定义资源模板:

server.addResourceTemplate({
  uriTemplate: "file:///logs/{name}.log",
  name: "Application Logs",
  mimeType: "text/plain",
  arguments: [
    {
      name: "name",
      description: "Name of the log",
      required: true,
    },
  ],
  async load({ name }) {
    return {
      text: `Example log content for ${name}`,
    };
  },
});

与普通资源一样,load 的第二和第三个参数也是 auth 和 context(请参阅资源)。

{name} 表达式会在单个路径段内匹配,符合 RFC 6570 的规定,因此 file:///logs/a/b.log 与上述模板不匹配。对于跨多个路径段的值,例如 file:///{+path},请使用 {+name}。值会进行百分号解码(file:///logs/..%2Fsecret.log 会得到 name: "../secret"),因此在将其用作路径前,请先进行验证。

资源模板参数自动补全

为资源模板参数提供 complete 函数,以启用自动补全:

server.addResourceTemplate({
  uriTemplate: "file:///logs/{name}.log",
  name: "Application Logs",
  mimeType: "text/plain",
  arguments: [
    {
      name: "name",
      description: "Name of the log",
      required: true,
      complete: async (value) => {
        if (value === "Example") {
          return {
            values: ["Example Log"],
          };
        }

        return {
          values: [],
        };
      },
    },
  ],
  async load({ name }) {
    return {
      text: `Example log content for ${name}`,
    };
  },
});
嵌入式资源

FastMCP 提供了便捷的 embedded() 方法,可简化在工具响应中包含资源的操作。此功能可减少重复代码,并让你更轻松地从工具中引用资源。

基本用法
server.addTool({
  name: "get_user_data",
  description: "Retrieve user information",
  parameters: z.object({
    userId: z.string(),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "resource",
          resource: await server.embedded(`user://profile/${args.userId}`),
        },
      ],
    };
  },
});
使用资源模板

embedded() 方法可与资源模板无缝配合:

// Define a resource template
server.addResourceTemplate({
  uriTemplate: "docs://project/{section}",
  name: "Project Documentation",
  mimeType: "text/markdown",
  arguments: [
    {
      name: "section",
      required: true,
    },
  ],
  async load(args) {
    const docs = {
      "getting-started": "# Getting Started\n\nWelcome to our project!",
      "api-reference": "# API Reference\n\nAuthentication is required.",
    };
    return {
      text: docs[args.section] || "Documentation not found",
    };
  },
});

// Use embedded resources in a tool
server.addTool({
  name: "get_documentation",
  description: "Retrieve project documentation",
  parameters: z.object({
    section: z.enum(["getting-started", "api-reference"]),
  }),
  execute: async (args) => {
    return {
      content: [
        {
          type: "resource",
          resource: await server.embedded(`docs://project/${args.section}`),
        },
      ],
    };
  },
});
使用直接定义的资源

它也适用于直接定义的资源:

// Define a direct resource
server.addResource({
  uri: "system://status",
  name: "System Status",
  mimeType: "text/plain",
  async load() {
    return {
      text: "System operational",
    };
  },
});

// Use in a tool
server.addTool({
  name: "get_system_status",
  description: "Get current system status",
  parameters: z.object({}),
  execute: async () => {
    return {
      content: [
        {
          type: "resource",
          resource: await server.embedded("system://status"),
        },
      ],
    };
  },
});
提示词

提示词使服务能够定义可复用的提示词模板和工作流,客户端可以轻松地将其呈现给用户和大模型。它们为标准化和共享常见的大模型交互提供了强大方式。

server.addPrompt({
  name: "git-commit",
  description: "Generate a Git commit message",
  arguments: [
    {
      name: "changes",
      description: "Git diff or description of changes",
      required: true,
    },
  ],
  load: async (args) => {
    return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
  },
});

与资源一样,load 的第二和第三个参数也是 auth 和 context(请参阅资源):

server.addPrompt({
  name: "git-commit",
  description: "Generate a Git commit message",
  arguments: [
    {
      name: "changes",
      description: "Git diff or description of changes",
      required: true,
    },
  ],
  load: async (args, auth, context) => {
    context.log.debug("generating git commit prompt", { user: auth?.userId });

    return `Generate a concise but descriptive commit message for these changes:\n\n${args.changes}`;
  },
});
提示词参数自动补全

提示词可以为其参数提供自动补全:

server.addPrompt({
  name: "countryPoem",
  description: "Writes a poem about a country",
  load: async ({ name }) => {
    return `Hello, ${name}!`;
  },
  arguments: [
    {
      name: "name",
      description: "Name of the country",
      required: true,
      complete: async (value) => {
        if (value === "Germ") {
          return {
            values: ["Germany"],
          };
        }

        return {
          values: [],
        };
      },
    },
  ],
});
使用 enum 自动补全提示词参数

如果为参数提供 enum 数组,服务将自动为该参数提供补全选项。

server.addPrompt({
  name: "countryPoem",
  description: "Writes a poem about a country",
  load: async ({ name }) => {
    return `Hello, ${name}!`;
  },
  arguments: [
    {
      name: "name",
      description: "Name of the country",
      required: true,
      enum: ["Germany", "France", "Italy"],
    },
  ],
});
OpenAPI

fromOpenAPI()(来自 fastmcp/openapi)可将 OpenAPI 3.x 文档转换为 FastMCP 服务,每个操作对应一个工具,并在转换过程中处理外部 $ref(多文件规范)、相对 servers[0].url 解析以及参数扁平化冲突:

import { fromOpenAPI } from "fastmcp/openapi";

const server = await fromOpenAPI({
  spec: "https://petstore3.swagger.io/api/v3/openapi.json",
  include: (operation) => operation.tags.includes("pet"),
});

await server.start({ transportType: "stdio" });

完整的选项参考、身份验证和已知限制,请参阅 OpenAPI to MCP。

身份验证

FastMCP 支持使用预配置提供程序进行 OAuth 2.1 身份验证,让你只需进行少量设置即可保护服务。

使用预配置提供商进行 OAuth

使用提供商配置 auth 选项以启用 OAuth 身份验证:

import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";

const server = new FastMCP({
  auth: new GoogleProvider({
    baseUrl: "https://your-server.com",
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  }),
  name: "My Server",
  version: "1.0.0",
});

server.addTool({
  canAccess: requireAuth,
  description: "Get user profile",
  execute: async (_args, { session }) => {
    const { accessToken } = getAuthSession(session);
    const response = await fetch(
      "https://www.googleapis.com/oauth2/v2/userinfo",
      {
        headers: { Authorization: `Bearer ${accessToken}` },
      },
    );
    return JSON.stringify(await response.json());
  },
  name: "get-profile",
});

可用提供商:

提供商导入使用场景
GoogleProviderfastmcpGoogle OAuth
GitHubProviderfastmcpGitHub OAuth
AzureProviderfastmcpAzure/Entra ID
OAuthProviderfastmcp任意 OAuth 2.0 提供商

通用 OAuth 提供商(适用于 SAP、Auth0、Okta 等):

import { FastMCP, OAuthProvider } from "fastmcp";

const server = new FastMCP({
  auth: new OAuthProvider({
    authorizationEndpoint: process.env.OAUTH_AUTH_ENDPOINT!,
    baseUrl: "https://your-server.com",
    clientId: process.env.OAUTH_CLIENT_ID!,
    clientSecret: process.env.OAUTH_CLIENT_SECRET!,
    scopes: ["openid", "profile"],
    tokenEndpoint: process.env.OAUTH_TOKEN_ENDPOINT!,
  }),
  name: "My Server",
  version: "1.0.0",
});
工具授权

使用内置辅助函数配置 canAccess 属性,以控制工具访问权限:

import {
  requireAuth,
  requireScopes,
  requireRole,
  requireAll,
  requireAny,
  getAuthSession,
} from "fastmcp";

// Require any authenticated user
server.addTool({
  canAccess: requireAuth,
  name: "user-tool",
  // ...
});

// Require specific OAuth scopes
server.addTool({
  canAccess: requireScopes("read:user", "write:data"),
  name: "scoped-tool",
  // ...
});

// Require specific role
server.addTool({
  canAccess: requireRole("admin"),
  name: "admin-tool",
  // ...
});

// Combine with AND logic
server.addTool({
  canAccess: requireAll(requireAuth, requireRole("admin")),
  name: "admin-only",
  // ...
});

// Combine with OR logic
server.addTool({
  canAccess: requireAny(requireRole("admin"), requireRole("moderator")),
  name: "staff-tool",
  // ...
});

自定义授权:

如需自定义逻辑,可直接传入函数:

server.addTool({
  name: "custom-auth-tool",
  canAccess: (auth) =>
    auth?.role === "admin" && auth?.department === "engineering",
  execute: async () => "Access granted!",
});

提取会话数据:

在工具的执行函数中,使用 getAuthSession 类型安全地访问 OAuth 会话:

import { getAuthSession, GoogleSession } from "fastmcp";

server.addTool({
  canAccess: requireAuth,
  name: "get-profile",
  execute: async (_args, { session }) => {
    // Type-safe destructuring (throws if not authenticated)
    const { accessToken } = getAuthSession(session);

    // Or with provider-specific typing:
    // const { accessToken } = getAuthSession<GoogleSession>(session);

    const response = await fetch("https://api.example.com/user", {
      headers: { Authorization: `Bearer ${accessToken}` },
    });
    return JSON.stringify(await response.json());
  },
});

注意: 你也可以直接访问 session.accessToken,但必须处理 session 未定义的情况。如果会话未经身份验证,getAuthSession 辅助函数会抛出明确的错误;因此在与 canAccess: requireAuth 搭配使用时更安全。

自定义身份验证

对于非 OAuth 场景(API 密钥、自定义令牌),请使用 authenticate 选项:

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  authenticate: (request) => {
    const apiKey = request.headers["x-api-key"];

    if (apiKey !== "123") {
      throw new Response(null, {
        status: 401,
        statusText: "Unauthorized",
      });
    }

    return { id: 1, role: "user" };
  },
});

server.addTool({
  name: "sayHello",
  execute: async (args, { session }) => {
    return `Hello, ${session.id}!`;
  },
});
OAuth 代理

auth 选项使用 FastMCP 内置的 OAuth 代理,在 MCP 客户端与上游 OAuth 提供商之间充当安全中介。该代理负责处理完整的 OAuth 2.1 授权流程,包括动态客户端注册(DCR)、PKCE、授权同意管理,以及默认启用加密和令牌交换模式的令牌管理。

主要功能:

  • 🔐 默认安全:自动加密(AES-256-GCM)并采用令牌交换模式
  • 🚀 零配置:自动生成密钥并处理 OAuth 流程
  • 🔌 预配置提供商:内置支持 Google、GitHub 和 Azure
  • 🎯 符合 RFC 规范:实现 DCR(RFC 7591)、PKCE 和 OAuth 2.1
  • 🔑 可选 JWKS:支持 RS256/ES256 令牌验证(通过可选的 jose 依赖项)

快速入门:

import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";

const server = new FastMCP({
  auth: new GoogleProvider({
    baseUrl: "https://your-server.com",
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  }),
  name: "My Server",
  version: "1.0.0",
});

server.addTool({
  canAccess: requireAuth,
  name: "protected-tool",
  execute: async (_args, { session }) => {
    const { accessToken } = getAuthSession(session);
    // Use accessToken to call upstream APIs
    return "Authenticated!";
  },
});

高级配置:

如需更好地控制 OAuth 行为,可直接使用 oauth 选项:

import { FastMCP } from "fastmcp";
import { GoogleProvider } from "fastmcp/auth";

const authProvider = new GoogleProvider({
  baseUrl: "https://your-server.com",
  clientId: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  scopes: ["openid", "profile", "email"],
});

const server = new FastMCP({
  name: "My Server",
  oauth: {
    authorizationServer: authProvider
      .getProxy()
      .getAuthorizationServerMetadata(),
    enabled: true,
    proxy: authProvider.getProxy(),
  },
  version: "1.0.0",
});

文档:

OAuth 发现端点

FastMCP 还支持 OAuth 发现端点,可直接与 OAuth 提供方集成,并支持 MCP Specification 2025-03-26 和 MCP Specification 2025-06-18。这会提供符合 RFC 8414(OAuth 2.0 授权服务器元数据)和 RFC 9470(OAuth 2.0 受保护资源元数据)的标准发现端点:

import { FastMCP } from "fastmcp";
import buildGetJwks from "get-jwks";
import fastJwt, { type DecodedJwt } from "fast-jwt";

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  oauth: {
    enabled: true,
    authorizationServer: {
      issuer: "https://auth.example.com",
      authorizationEndpoint: "https://auth.example.com/oauth/authorize",
      tokenEndpoint: "https://auth.example.com/oauth/token",
      jwksUri: "https://auth.example.com/.well-known/jwks.json",
      responseTypesSupported: ["code"],
    },
    protectedResource: {
      resource: "mcp://my-server",
      authorizationServers: ["https://auth.example.com"],
    },
  },
  authenticate: async (request) => {
    const authHeader = request.headers.authorization;

    if (!authHeader?.startsWith("Bearer ")) {
      throw new Response(null, {
        status: 401,
        statusText: "Missing or invalid authorization header",
      });
    }

    const token = authHeader.slice(7); // Remove 'Bearer ' prefix

    // Validate OAuth JWT access token using OpenID Connect discovery
    try {
      // Create JWKS client for token verification
      const getJwks = buildGetJwks();

      // Create JWT verifier
      const verify = fastJwt.createVerifier({
        async key({ header }: DecodedJwt) {
          const publicKey = await getJwks.getPublicKey({
            kid: header.kid,
            alg: header.alg,
            domain: "https://auth.example.com",
          });
          return publicKey;
        },
        algorithms: ["RS256"],
      });

      // Verify the JWT token
      const payload = await verify(token);

      return {
        userId: payload.sub,
        scope: payload.scope,
        email: payload.email,
        // Include other claims as needed
      };
    } catch (error) {
      throw new Response(null, {
        status: 401,
        statusText: "Invalid OAuth token",
      });
    }
  },
});

如果 MCP 服务发布在颁发者路径下,请同时配置 HTTP 流的基础路径:

server.start({
  transportType: "httpStream",
  httpStream: {
    basePath: "/issuer1",
    endpoint: "/mcp",
    port: 8080,
  },
});

使用此配置时,FastMCP 会在 /.well-known/oauth-authorization-server/issuer1 提供颁发者路径下的授权服务器元数据,同时在 /.well-known/oauth-protected-resource/issuer1/mcp 为 MCP 端点提供受保护资源元数据。

此配置会自动公开 OAuth 发现端点:

  • /.well-known/oauth-authorization-server - 授权服务器元数据(RFC 8414)
  • /.well-known/oauth-authorization-server<basePath> - 设置 httpStream.basePath 时的授权服务器元数据(RFC 8414 第 3 节)
  • /.well-known/oauth-protected-resource - 受保护资源元数据(RFC 9728)
  • /.well-known/oauth-protected-resource<endpoint> - 子路径上的受保护资源元数据(MCP 2025-11-25)

发现机制(MCP Specification 2025-11-25):

客户端按以下顺序查找受保护资源元数据:

  1. WWW-Authenticate header - 首选方法(由 mcp-proxy 自动处理)
  2. 子路径 well-known - /.well-known/oauth-protected-resource<endpoint>(例如 /.well-known/oauth-protected-resource/mcp)
  3. 根路径 well-known - /.well-known/oauth-protected-resource(回退方式)

子路径和根路径端点均返回相同的元数据,以确保兼容所有 MCP 客户端实现。

验证 JWT 令牌时,可以使用 get-jwks 和 fast-jwt 等库来处理 OAuth JWT 令牌。

通过上下文传递标头

如果通过 HTTP 暴露 MCP 服务,您可能希望允许客户端通过标头提供敏感密钥,并将这些密钥传递给工具所交互的 API,以便每个客户端都能提供自己的 API 密钥。您可以在 authenticate 部分捕获 HTTP 标头,并将其存储在会话中,供工具稍后引用。

import { FastMCP } from "fastmcp";
import { IncomingHttpHeaders } from "http";

// Define the session data type
interface SessionData {
  headers: IncomingHttpHeaders;
  [key: string]: unknown; // Add index signature to satisfy Record<string, unknown>
}

// Create a server instance
const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  authenticate: async (request: any): Promise<SessionData> => {
    // Authentication logic
    return {
      headers: request.headers,
    };
  },
});

// Tool to display HTTP headers
server.addTool({
  name: "headerTool",
  description: "Reads HTTP headers from the request",
  execute: async (args: any, context: any) => {
    const session = context.session as SessionData;
    const headers = session?.headers ?? {};

    const getHeaderString = (header: string | string[] | undefined) =>
      Array.isArray(header) ? header.join(", ") : (header ?? "N/A");

    const userAgent = getHeaderString(headers["user-agent"]);
    const authorization = getHeaderString(headers["authorization"]);
    return `User-Agent: ${userAgent}\nAuthorization: ${authorization}\nAll Headers: ${JSON.stringify(headers, null, 2)}`;
  },
});

// Start the server
server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
  },
});

连接到此服务的客户端可能如下所示:

import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

const transport = new StreamableHTTPClientTransport(
  new URL(`http://localhost:8080/mcp`),
  {
    requestInit: {
      headers: {
        Authorization: "Test 123",
      },
    },
  },
);

const client = new Client({
  name: "example-client",
  version: "1.0.0",
});

(async () => {
  await client.connect(transport);

  // Call a tool
  const result = await client.callTool({
    name: "headerTool",
    arguments: {
      arg1: "value",
    },
  });

  console.log("Tool result:", result);
})().catch(console.error);

客户端运行后,控制台中可能会显示如下内容:

Tool result: {
  content: [
    {
      type: 'text',
      text: 'User-Agent: node\n' +
        'Authorization: Test 123\n' +
        'All Headers: {\n' +
        '  "host": "localhost:8080",\n' +
        '  "connection": "keep-alive",\n' +
        '  "authorization": "Test 123",\n' +
        '  "content-type": "application/json",\n' +
        '  "accept": "application/json, text/event-stream",\n' +
        '  "accept-language": "*",\n' +
        '  "sec-fetch-mode": "cors",\n' +
        '  "user-agent": "node",\n' +
        '  "accept-encoding": "gzip, deflate",\n' +
        '  "content-length": "163"\n' +
        '}'
    }
  ]
}
会话 ID 和请求 ID 跟踪

FastMCP 会通过 context 参数,自动向工具处理程序公开会话 ID 和请求 ID。这有助于按会话管理状态并跟踪请求。

会话 ID(context.sessionId):

  • 仅适用于基于 HTTP 的传输方式(HTTP Stream、SSE)
  • 从 Mcp-Session-Id 标头中提取
  • 同一客户端发出的多个请求中保持不变
  • 适用于维护每个会话的状态、计数器或特定用户数据

请求 ID(context.requestId):

  • 所有传输方式均适用,前提是客户端提供了请求 ID
  • 每个单独的请求都有唯一的 ID
  • 适用于请求跟踪和调试
import { FastMCP } from "fastmcp";
import { z } from "zod";

const server = new FastMCP({
  name: "Session Counter Server",
  version: "1.0.0",
});

// Per-session counter storage
const sessionCounters = new Map<string, number>();

server.addTool({
  name: "increment_counter",
  description: "Increment a per-session counter",
  parameters: z.object({}),
  execute: async (args, context) => {
    if (!context.sessionId) {
      return "Session ID not available (requires HTTP transport)";
    }

    const counter = sessionCounters.get(context.sessionId) || 0;
    const newCounter = counter + 1;
    sessionCounters.set(context.sessionId, newCounter);

    return `Counter for session ${context.sessionId}: ${newCounter}`;
  },
});

server.addTool({
  name: "show_ids",
  description: "Display session and request IDs",
  parameters: z.object({}),
  execute: async (args, context) => {
    return `Session ID: ${context.sessionId || "N/A"}
Request ID: ${context.requestId || "N/A"}`;
  },
});

server.start({
  transportType: "httpStream",
  httpStream: {
    port: 8080,
  },
});

使用场景:

  • 按会话管理状态:维护每个客户端会话独有的计数器、缓存或临时数据
  • 用户身份验证和授权:跨请求跟踪已通过身份验证的用户
  • 会话专属资源管理:按会话分配和管理资源
  • 多租户实现:按会话隔离数据和操作
  • 请求跟踪:跟踪单个请求,以便调试和监控

示例:

请参阅 src/examples/session-id-counter.ts,其中提供了基于会话的计数器管理完整示例。

注意:

  • 会话 ID 由 MCP 传输层自动生成
  • 在无状态模式下,会话 ID 不会在请求之间保留
  • 对于 stdio 传输方式,sessionId 将为 undefined,因为此方式没有 HTTP 会话的概念
提供说明

你可以使用 instructions 选项向服务提供说明:

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  instructions:
    'Instructions describing how to use the server and its features.\n\nThis can be used by clients to improve the LLM\'s understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt.',
});
服务图标和元数据

你可以为服务提供图标及相关元数据。支持图标的客户端可以在其界面中显示这些图标:

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  websiteUrl: "https://example.com",
  icons: [
    {
      src: "https://example.com/icon.png",
      mimeType: "image/png",
      sizes: ["48x48"],
    },
  ],
});

可选的 title 也会传递给 MCP initialize(serverInfo),供偏好使用显示名称而非服务 name 的客户端使用。

会话

session 对象是 FastMCPSession 的实例,用于描述活跃的客户端会话。

server.sessions;

我们会为每个客户端连接分配一个新的服务实例,以实现客户端与服务之间的一对一通信。

类型化服务事件

你可以使用 on 方法监听服务发出的事件:

server.on("connect", (event) => {
  console.log("Client connected:", event.session);
});

server.on("disconnect", (event) => {
  console.log("Client disconnected:", event.session);
});

FastMCPSession

FastMCPSession 表示一个客户端会话,并提供与客户端交互的方法。

有关如何获取 FastMCPSession 实例的示例,请参阅会话。

requestElicitation

requestElicitation 会创建一个 elicitation 请求,通过客户端向用户收集更多信息,并返回响应。客户端必须声明匹配的 elicitation 能力模式:表单请求(默认)使用 elicitation: { form: {} },URL 请求使用 elicitation: { url: {} },也可以同时声明两者。

await session.requestElicitation({
  message: "What is your name?",
  requestedSchema: {
    type: "object",
    properties: {
      name: { type: "string" },
    },
    required: ["name"],
  },
});

在工具内部,建议使用上下文对象中的 elicit 方法。

requestSampling

requestSampling 会创建一个 sampling 请求,并返回响应。

await session.requestSampling({
  messages: [
    {
      role: "user",
      content: {
        type: "text",
        text: "What files are in the current directory?",
      },
    },
  ],
  systemPrompt: "You are a helpful file system assistant.",
  includeContext: "thisServer",
  maxTokens: 100,
});
选项

requestSampling 接受一个可选的第二个参数,用于设置请求选项:

await session.requestSampling(
  {
    messages: [
      {
        role: "user",
        content: {
          type: "text",
          text: "What files are in the current directory?",
        },
      },
    ],
    systemPrompt: "You are a helpful file system assistant.",
    includeContext: "thisServer",
    maxTokens: 100,
  },
  {
    // Progress callback - called when progress notifications are received
    onprogress: (progress) => {
      console.log(`Progress: ${progress.progress}/${progress.total}`);
    },

    // Abort signal for cancelling the request
    signal: abortController.signal,

    // Request timeout in milliseconds (default: DEFAULT_REQUEST_TIMEOUT_MSEC)
    timeout: 30000,

    // Whether progress notifications reset the timeout (default: false)
    resetTimeoutOnProgress: true,

    // Maximum total timeout regardless of progress (no default)
    maxTotalTimeout: 60000,
  },
);

选项:

  • onprogress?: (progress: Progress) => void - 远程端发送进度通知时调用的回调
  • signal?: AbortSignal - 用于取消请求的中止信号
  • timeout?: number - 请求超时时间,单位为毫秒
  • resetTimeoutOnProgress?: boolean - 进度通知是否会重置超时时间
  • maxTotalTimeout?: number - 无论是否收到进度通知,允许的最长总超时时间
clientCapabilities

clientCapabilities 属性包含客户端能力。

session.clientCapabilities;
loggingLevel

loggingLevel 属性描述客户端设置的日志级别。在客户端发送 logging/setLevel 之前,它的值为 info;严重程度低于客户端所设级别的消息会被丢弃,而不会发送。

session.loggingLevel;
roots

roots 属性包含客户端设置的根目录。

session.roots;
server

server 属性包含与会话关联的 MCP 服务实例。

session.server;
类型化会话事件

你可以使用 on 方法监听会话发出的事件:

session.on("rootsChanged", (event) => {
  console.log("Roots changed:", event.roots);
});

session.on("error", (event) => {
  console.error("Error:", event.error);
});

运行你的服务

使用内存传输进行单元测试

server.connect(transport) 会将服务连接到你自行构造的传输,而不是让 start() 创建传输。配合 SDK 的 InMemoryTransport,你可以在进程内驱动服务,无需绑定端口,也无需启动子进程。测试 stdio 服务时,这通常正是你需要的方式:

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";

async function createTestClient(server: FastMCP) {
  const [clientTransport, serverTransport] =
    InMemoryTransport.createLinkedPair();

  const client = new Client({ name: "test-client", version: "0.0.0" });

  const [session] = await Promise.all([
    server.connect(serverTransport),
    client.connect(clientTransport),
  ]);

  return { client, session };
}

test("adds two numbers", async () => {
  const { client } = await createTestClient(server);

  expect(
    await client.callTool({ arguments: { a: 2, b: 3 }, name: "add" }),
  ).toEqual({
    content: [{ text: "5", type: "text" }],
  });

  await client.close();
});

会话根据实例上注册的工具、资源和提示词构建,与 start() 构建会话的方式完全相同。因此,你的测试会使用与实际服务相同的连接配置,包括 canAccess 过滤以及 connect/disconnect 事件。

将会话身份验证信息作为第二个参数传入,效果等同于你的 authenticate 函数所返回的内容:

await server.connect(serverTransport, { id: 7, role: "admin" });

connect 会返回 FastMCPSession,因此你可以断言 session.clientCapabilities、session.roots 等属性。传输的生命周期由你管理:stop() 不会关闭传递给 connect 的传输,因此测试结束时请关闭客户端(如果需要触发 disconnect,也请关闭会话)。

使用 mcp-cli 测试

测试和调试服务最快的方式是使用 fastmcp dev:

npx fastmcp dev server.js
npx fastmcp dev server.ts

这会使用 mcp-cli 在终端中运行你的服务,以便测试和调试 MCP 服务。

如需以非交互方式调用工具(例如,在脚本或自动化测试中),请传入 --tool 和可选的 JSON --args:

npx fastmcp dev server.ts --tool add --args '{"a":1,"b":2}'

此命令会将工具结果以 JSON 格式输出后退出,而不是打开交互式检查器。在此模式下,--watch 不起作用,因为服务只会为单次调用启动。

使用 MCP Inspector 检查

另一种方式是使用官方 MCP Inspector,通过 Web UI 检查你的服务:

npx fastmcp inspect server.ts

常见问题

如何与 Claude Desktop 配合使用?

请按照指南 https://modelcontextprotocol.io/quickstart/user 操作,并添加以下配置:

{
  "mcpServers": {
    "my-mcp-server": {
      "command": "npx",
      "args": ["tsx", "/PATH/TO/YOUR_PROJECT/src/index.ts"],
      "env": {
        "YOUR_ENV_VAR": "value"
      }
    }
  }
}
如何在代理后运行 FastMCP?

请参阅此问题,了解使用 FastMCP、express 和 http-proxy-middleware 的示例。

展示

说明

如果你使用 FastMCP 开发了 MCP 服务,请提交 PR,让它在此处展示!

说明

如果你正在寻找用于构建自己的 MCP 服务的脚手架仓库,请查看 fastmcp-boilerplate。

致谢

本项目使用 BrowserStack 进行测试。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。