# 简介 Source: https://picomcp.github.io/picomcp/zh/docs/ > picomcp 是什么、不是什么,以及解释它的那十行代码。 picomcp 是一个用于构建 [Model Context Protocol](https://modelcontextprotocol.io) 服务器的 TypeScript 框架。MCP 服务器为 AI 应用(Claude、Cursor、VS Code 或你自己的智能体)提供可以调用的**工具**、可以读取的**资源**,以及可以提供给用户的**提示词**。 ```ts import { pico } from "picomcp"; import { z } from "zod"; pico({ name: "weather" }) .tool("forecast", { description: "Get the forecast for a city.", input: z.object({ city: z.string() }), run: ({ city }) => getForecast(city), }) .serve(); ``` 这就是一个完整的服务器。用 `node server.ts` 运行它,然后连接到任意 MCP 客户端。 ## 为什么选择 picomcp - **小巧。** gzip 后约 4 kB,零依赖。整个库可以装进你的脑子里,一次代码评审就能看完。 - **类型来自你的 schema。** 传入 Zod、Valibot 或 ArkType 的 schema。处理函数拿到带类型的参数,客户端拿到 JSON Schema,无效调用会得到清晰易读的错误。 - **随处运行。** Node 22+、Bun、Deno、Cloudflare Workers,以及任何提供 `fetch` 的运行时。核心不含任何 Node 内置模块。 - **两种传输方式,同一个服务器。** 桌面客户端用 stdio,远程服务器用 Streamable HTTP。改一个选项即可切换。 - **无需客户端即可测试。** `server.call("forecast", { city: "Lyon" })` 在进程内直接运行工具,类型完整。 - **经官方 SDK 验证。** 测试套件通过 `@modelcontextprotocol/sdk` 在两种传输方式上驱动 picomcp。 ## picomcp 不是什么 picomcp 只实现 MCP 的服务器端。它不包含客户端、智能体循环或 LLM SDK。它专注于协议中无状态、请求/响应的那一半,这正是大多数服务器需要的。由服务器发起的功能(如 sampling 和 elicitation)目前尚不支持,详细列表见[协议覆盖范围](https://picomcp.github.io/picomcp/zh/docs/protocol)。 ## 下一步 - [快速上手](https://picomcp.github.io/picomcp/zh/docs/quickstart):五分钟内构建并连接一个服务器。 - [工具](https://picomcp.github.io/picomcp/zh/docs/tools):工具能做的一切。 - [写给 AI 智能体](https://picomcp.github.io/picomcp/zh/docs/agents):用一个网址把文档交给你的编程智能体。 --- # 快速上手 Source: https://picomcp.github.io/picomcp/zh/docs/quickstart > 五分钟内创建、测试并连接你的第一个 MCP 服务器。 你需要 Node 22.18 或更高版本(可直接运行 TypeScript 文件)、Bun 或 Deno。 ## 创建项目 ```sh npx picomcp init my-server cd my-server npm install ``` 这会生成一个可以直接运行的服务器: | 文件 | 用途 | | --- | --- | | `server.ts` | 服务器定义:工具、资源、提示词 | | `main.ts` | 通过 stdio 启动服务器 | | `server.test.ts` | 在进程内调用工具的测试 | 想从零开始?安装这两个包,自己编写 `server.ts`: ```sh npm install picomcp zod ``` ## 编写一个工具 打开 `server.ts`: ```ts import { pico } from "picomcp"; import { z } from "zod"; export const server = pico({ name: "my-server", version: "0.1.0" }) .tool("greet", { description: "Greets someone by name.", input: z.object({ name: z.string().describe("Who to greet") }), run: ({ name }) => `Hello, ${name}!`, }); ``` - 模型会读取 `description` 来决定何时调用这个工具。请写给模型看。 - `input` 可以是任意 [Standard Schema](https://picomcp.github.io/picomcp/zh/docs/schemas)。在 `run` 中,`name` 的类型是 `string`。 - 这里 `run` 返回一个字符串。它也可以返回对象、内容块,或者抛出错误,详见[工具](https://picomcp.github.io/picomcp/zh/docs/tools)。 ## 测试 ```ts import assert from "node:assert/strict"; import { test } from "node:test"; import { server } from "./server.ts"; test("greet says hello", async () => { const result = await server.call("greet", { name: "Ada" }); assert.deepEqual(result.content, [{ type: "text", text: "Hello, Ada!" }]); }); ``` ```sh npm test ``` `server.call` 会经过类型检查:工具名拼错或缺少参数都无法通过编译。 ## 连接 在 Claude Code 中: ```sh claude mcp add my-server -- node /absolute/path/to/my-server/main.ts ``` 然后让 Claude 跟某人打个招呼。Claude Desktop、Cursor、VS Code 等客户端的配置见[连接客户端](https://picomcp.github.io/picomcp/zh/docs/clients)。 ## 调试 MCP Inspector 可以手动调用工具,并查看原始消息: ```sh npx @modelcontextprotocol/inspector node main.ts ``` --- # 连接客户端 Source: https://picomcp.github.io/picomcp/zh/docs/clients > Claude Code、Claude Desktop、Cursor、VS Code 和 MCP Inspector 的配置方法。 所有 MCP 客户端启动本地服务器的方式都一样:运行一条命令,并通过 stdio 与它通信。远程服务器则通过 URL,使用 Streamable HTTP 访问。下面所有配置都请使用绝对路径。 ## Claude Code ```sh # local, over stdio claude mcp add weather -- node /absolute/path/main.ts # remote, over HTTP claude mcp add --transport http weather https://example.com/mcp ``` 加上 `--scope project`,服务器会写入 `.mcp.json`,方便与团队共享。 ## Claude Desktop 编辑 `claude_desktop_config.json`(设置 → 开发者 → 编辑配置),然后重启应用: ```json { "mcpServers": { "weather": { "command": "node", "args": ["/absolute/path/main.ts"] } } } ``` ## Cursor 在项目中创建 `.cursor/mcp.json`,或创建 `~/.cursor/mcp.json` 让所有项目都能使用: ```json { "mcpServers": { "weather": { "command": "node", "args": ["/absolute/path/main.ts"] }, "remote-weather": { "url": "https://example.com/mcp" } } } ``` ## VS Code 创建 `.vscode/mcp.json`: ```json { "servers": { "weather": { "type": "stdio", "command": "node", "args": ["/absolute/path/main.ts"] }, "remote-weather": { "type": "http", "url": "https://example.com/mcp" } } } ``` ## MCP Inspector ```sh npx @modelcontextprotocol/inspector node /absolute/path/main.ts ``` 要调试 HTTP 服务器,先用 `serve({ http: 3000 })` 启动它,打开 Inspector,选择 **Streamable HTTP**,然后输入 `http://127.0.0.1:3000/mcp`。Inspector 运行在 localhost 上,picomcp 默认允许它访问。 ## Bun 和 Deno 把 `node` 换成你的运行时: ```json { "command": "bun", "args": ["/absolute/path/main.ts"] } ``` ```json { "command": "deno", "args": ["run", "--allow-net", "/absolute/path/main.ts"] } ``` ## 故障排查 **客户端看不到任何工具。** 自己运行配置里的那条命令。如果它退出或报错,客户端看到的也是同样的结果。 **客户端日志里出现“Unexpected token”或 JSON 错误。** 有东西写入了 stdout。通过 stdio 提供服务时,picomcp 会把 `console.log` 重定向到 stderr,但其他库可能会直接写入 `process.stdout`。 **Node 无法运行 `.ts` 文件。** 升级到 Node 22.18 或更高版本,或者改用 Bun 运行服务器。 --- # 工具 Source: https://picomcp.github.io/picomcp/zh/docs/tools > 定义工具、校验输入、返回文本、对象或媒体、报告错误以及处理取消。 工具是模型可以调用的函数。用 `server.tool(name, config)` 注册一个工具: ```ts server.tool("search_issues", { title: "Search issues", description: "Search open GitHub issues in a repository. Use it before filing a new issue.", input: z.object({ repo: z.string().describe("owner/name"), query: z.string(), limit: z.number().int().max(50).default(10), }), annotations: { readOnlyHint: true, openWorldHint: true }, run: async ({ repo, query, limit }, { signal }) => { const res = await fetch(`https://api.github.com/search/issues?q=${encodeURIComponent(`${query} repo:${repo}`)}&per_page=${limit}`, { signal }); const { items } = await res.json(); return items.map((i: { title: string; html_url: string }) => `${i.title} — ${i.html_url}`).join("\n"); }, }); ``` ## 配置项 | 选项 | 类型 | 说明 | | --- | --- | --- | | `description` | `string` | 工具做什么、什么时候用。模型会读取这段文字。 | | `title` | `string` | 客户端显示的名称。 | | `input` | Standard Schema | 参数的 schema。没有参数的工具可以省略。 | | `output` | Standard Schema | 结构化结果的 schema。见[结构化输出](#结构化输出)。 | | `annotations` | `ToolAnnotations` | 给客户端的提示:`readOnlyHint`、`destructiveHint`、`idempotentHint`、`openWorldHint`。 | | `run` | `(input, ctx) => value` | 处理函数,可以是异步的。 | 工具名长度为 1 到 128 个字符,只能包含字母、数字、`_`、`.` 和 `-`。注册无效或重复的名称会立即抛出错误,因此问题在服务器启动时就会暴露,而不是等到模型调用工具时。 ## 输入校验 picomcp 在调用 `run` 之前,会用你的 schema 校验参数。默认值和转换都会生效,所以上面例子里的 `limit` 在 `run` 中始终是数字。 校验失败时,工具不会被调用。客户端会收到一个指明出错字段的工具错误,模型可以据此修正调用并重试: ```text Invalid arguments for tool "search_issues": limit: Too big: expected number to be <=50 ``` ## 返回值 按最自然的方式返回即可。picomcp 会把它转换成 MCP 结果: | `run` 返回 | 客户端收到 | | --- | --- | | `string` | 一个文本块 | | 对象、数组或数字 | 以文本形式发送的 JSON。对象还会作为 `structuredContent` 发送。 | | 一个内容块,或内容块数组 | 原样发送这些内容块 | | `{ content: [...] }` | 原样发送结果,完全由你掌控 | | `undefined` | 空结果 | 用辅助函数构建内容块: ```ts import { audio, image, link, text } from "picomcp"; run: async () => [ text("Here is the chart:"), image(await renderPng(), "image/png"), // Uint8Array or base64 string link("file:///reports/q3.csv", "q3.csv", { mimeType: "text/csv" }), ]; ``` ## 结构化输出 当结果要交给客户端或代码处理,而不只是给模型看时,请声明 `output` schema: ```ts server.tool("get_price", { input: z.object({ sku: z.string() }), output: z.object({ sku: z.string(), cents: z.number().int(), currency: z.literal("EUR") }), run: async ({ sku }) => ({ sku, cents: await priceOf(sku), currency: "EUR" }), }); ``` 这样 picomcp 会: 1. 在 `tools/list` 中把该 schema 声明为 `outputSchema`; 2. 在类型层面检查 `run` 的返回值; 3. 在运行时校验返回值,并作为 `structuredContent` 发送,同时附带一份 JSON 文本,供旧版客户端使用。 如果返回值不符合 schema,客户端会收到工具错误,而不是格式错误的数据。 ## 错误 在 `run` 中抛出错误。错误信息会变成模型可以读取并作出反应的工具错误(`isError: true`): ```ts run: async ({ id }) => { const user = await db.users.find(id); if (!user) throw new Error(`No user with id ${id}. Call list_users to see valid ids.`); return user; }; ``` 错误信息要写给模型看:说明哪里出了问题,以及下一步该怎么做。 工具错误是正常对话的一部分,协议错误则不是。只有当请求本身有问题时才使用 `McpError`,例如用户没有访问权限: ```ts import { McpError } from "picomcp"; throw new McpError(-32001, "Not authorised for this workspace"); ``` ## 取消 `run` 的第二个参数带有一个 `AbortSignal`。当客户端发送 `notifications/cancelled` 时它会被触发,通常是因为用户点了停止。把它传给 `fetch` 以及其他支持它的 API: ```ts run: async ({ url }, { signal }) => { const res = await fetch(url, { signal }); return res.text(); }; ``` 请求被取消后,picomcp 不会再为它发送响应,这是协议的要求。 ## 元数据 如果客户端随请求发送了 `_meta` 对象,`ctx.meta` 中就会包含它。客户端用它来做链路追踪和进度令牌。 ## 写出好工具 - **一个工具只做一件事。** `create_invoice` 和 `send_invoice` 比 `invoice(action)` 更好。 - **描述每个参数。** 每个字段上的 `.describe()` 都会进入模型看到的 JSON Schema。 - **少返回一些。** 模型的每个 token 都有成本。只返回有用的字段,而不是整个 API 响应。 - **如实标注。** `readOnlyHint: true` 会让客户端跳过确认提示。不要把它用在会写入数据的工具上。 --- # 资源 Source: https://picomcp.github.io/picomcp/zh/docs/resources > 提供客户端可以读取的文件、记录和实时数据,并支持带类型的 URI 模板。 资源是客户端可以读取并附加到对话中的数据:文件、数据库记录、API 响应。工具由模型调用,资源通常由用户或应用来选择。 ```ts server.resource("config://app", { description: "The app configuration", read: () => loadConfig(), }); ``` ## URI 模板 在 URI 中放入 `{variables}`,就可以提供一整类资源。变量的类型直接从字符串推导: ```ts server.resource("github://{owner}/{repo}/readme", { mimeType: "text/markdown", read: async ({ owner, repo }) => { // ^? { owner: string; repo: string } const res = await fetch(`https://raw.githubusercontent.com/${owner}/${repo}/HEAD/README.md`); return res.text(); }, }); ``` | 语法 | 匹配 | 示例 | | --- | --- | --- | | `{name}` | 一个路径段,不含 `/`、`?` 或 `#` | `users://{id}` 匹配 `users://42` | | `{+name}` | 任意内容,包括 `/` | `files://{+path}` 匹配 `files://src/index.ts` | 变量值会先做 URL 解码,再传给 `read`。静态资源出现在 `resources/list` 中,模板出现在 `resources/templates/list` 中。 ## 返回值 | `read` 返回 | 发送的内容 | | --- | --- | | `string` | 文本,类型为 `mimeType` 或 `text/plain` | | `Uint8Array` | base64 二进制数据,类型为 `mimeType` 或 `application/octet-stream` | | `{ uri, text }` 或 `{ uri, blob }` 的数组 | 原样发送这些内容 | | 其他任何值 | 它的 JSON,类型为 `application/json` | `read` 的第二个参数是上下文,并附带所请求的 `uri`: ```ts read: (params, { uri, signal }) => fetch(toHttp(uri), { signal }).then((r) => r.text()); ``` ## 选项 | 选项 | 说明 | | --- | --- | | `name` | 显示给用户的标识,默认为 URI。 | | `title` | 显示名称。 | | `description` | 资源包含什么内容。 | | `mimeType` | `read` 返回内容的类型。 | | `annotations` | `audience`、`priority` 和 `lastModified` 提示。 | ## 未找到 当没有任何资源匹配某个 URI 时,picomcp 会返回协议错误 `-32002 Resource not found`。如果某个 URI 匹配了模板,但实际并不存在,请抛出同样错误码的 `McpError`: ```ts import { McpError } from "picomcp"; read: async ({ id }) => { const user = await db.users.find(id); if (!user) throw new McpError(-32002, `Resource not found: users://${id}`); return user; }; ``` ## 在工具中链接资源 工具可以把模型引向某个资源,而不是直接把内容塞进去。这样大块数据在真正需要之前,都不会占用上下文: ```ts import { link } from "picomcp"; run: async () => link("reports://2026-q3", "Q3 report", { mimeType: "application/pdf" }); ``` --- # 提示词 Source: https://picomcp.github.io/picomcp/zh/docs/prompts > 提供可复用的提示词模板,用户可在客户端中选择,参数经过校验。 提示词是用户在客户端中选择的模板,通常以斜杠命令的形式出现。它们把几个参数变成一条或多条消息。 ```ts server.prompt("review", { title: "Code review", description: "Review a diff for bugs and style issues.", args: z.object({ diff: z.string().describe("The diff to review"), focus: z.string().optional().describe("What to pay attention to"), }), run: ({ diff, focus }) => `Review this diff${focus ? `, focusing on ${focus}` : ""}:\n\n${diff}`, }); ``` 客户端会把 `diff` 显示为必填、`focus` 显示为可选,并附上它们的说明,这些都从 schema 推导而来。 ## 参数都是字符串 MCP 以字符串形式发送提示词参数。请使用字符串 schema,或进行类型转换: ```ts args: z.object({ count: z.coerce.number().int().min(1) }); ``` 无效参数会在调用 `run` 之前被拒绝,并返回 `Invalid params` 错误。 ## 返回值 | `run` 返回 | 发送的消息 | | --- | --- | | `string` | 一条用户消息 | | `PromptMessage[]` | 这些消息 | | `{ description, messages }` | 完整结果 | 多轮模板或需要包含媒体时,请返回数组: ```ts import { image, text } from "picomcp"; run: async ({ id }) => [ { role: "user", content: text("What is wrong with this screenshot?") }, { role: "user", content: image(await screenshot(id), "image/png") }, ]; ``` --- # Schema Source: https://picomcp.github.io/picomcp/zh/docs/schemas > 使用 Zod、Valibot、ArkType 或任何 Standard Schema 库,以及 JSON Schema 是如何生成的。 picomcp 不自带 schema 库。它接受任何实现了 [Standard Schema](https://standardschema.dev) 的库,并通过 [Standard JSON Schema](https://standardschema.dev/json-schema) 生成 MCP 客户端需要的 JSON Schema。你可以继续用自己熟悉的库,picomcp 也能保持小巧。 ## Zod 4 直接可用: ```ts import { z } from "zod"; server.tool("forecast", { input: z.object({ city: z.string().describe("City name") }), run: ({ city }) => getForecast(city), }); ``` ## ArkType 直接可用: ```ts import { type } from "arktype"; server.tool("forecast", { input: type({ city: "string" }), run: ({ city }) => getForecast(city), }); ``` ## Valibot Valibot 把 JSON Schema 支持放在单独的包里。用 `toStandardJsonSchema` 包装 schema: ```ts import * as v from "valibot"; import { toStandardJsonSchema } from "@valibot/to-json-schema"; server.tool("forecast", { input: toStandardJsonSchema(v.object({ city: v.string() })), run: ({ city }) => getForecast(city), }); ``` ## 其他任何库 如果某个库通过 Standard Schema 做校验,但无法生成 JSON Schema(比如 Zod 3),请用 `withJsonSchema` 自己附上一份: ```ts import { withJsonSchema } from "picomcp"; server.tool("forecast", { input: withJsonSchema(schema, { type: "object", properties: { city: { type: "string" } }, required: ["city"], }), run: ({ city }) => getForecast(city), }); ``` 否则,注册工具时会抛出错误,并明确告诉你这一点,问题永远不会传到客户端。 ## 类型如何流转 - `run` 拿到的是 schema 的**输出**类型,即应用默认值和转换之后的类型。 - `server.call()` 接受 schema 的**输入**类型,所以在测试中,带默认值的字段是可选的。 - 声明了 `output` schema 后,`run` 必须返回该 schema 的输入类型。 ## 让模型读懂 schema 的建议 - 描述每个字段。这些描述就是模型读到的文档。 - 值的范围已知时,用枚举代替自由文本。 - 可选行为用默认值表达,这样模型可以省略这些参数。 - 让对象保持扁平。嵌套很深的参数,模型更难填写正确。 --- # 传输方式 Source: https://picomcp.github.io/picomcp/zh/docs/transports > 本地客户端用 stdio,远程客户端用 Streamable HTTP,二者都基于同一个 fetch 处理函数。 传输方式负责在客户端和你的服务器之间传递 JSON-RPC 消息。picomcp 支持 MCP 定义的两种传输方式,切换时你的工具无需改动。 ## stdio ```ts await server.serve(); ``` 客户端把你的服务器作为子进程启动,通过 stdin 和 stdout 交换以换行分隔的 JSON。桌面客户端和编程智能体就是这样使用本地服务器的。 stdout 专属于协议。通过 stdio 提供服务时,picomcp 会把 `console.log`、`console.info` 和 `console.debug` 重定向到 stderr,所以一行误打的日志不会破坏连接。客户端通常会在日志中显示 stderr。 ## Streamable HTTP ```ts const running = await server.serve({ http: 3000 }); console.error(`Listening on ${running.url}`); // http://127.0.0.1:3000/mcp ``` `serve({ http })` 会在当前运行的 Bun、Deno 或 Node 上启动一个服务器。选项: | 选项 | 默认值 | 说明 | | --- | --- | --- | | `port` | `3000` | 监听端口。`0` 表示自动选择空闲端口。 | | `hostname` | `"127.0.0.1"` | 用 `"0.0.0.0"` 接受来自其他机器的连接。 | | `path` | `"/mcp"` | 端点路径。其他路径返回 404。 | 它返回 `{ url, close() }`。 ## fetch 处理函数 `server.fetch` 是一个符合 Web 标准的 `(Request) => Promise` 函数。可以把它交给任何支持 `fetch` 的运行时或框架: ```ts Bun.serve({ port: 3000, fetch: server.fetch }); ``` ```ts Deno.serve({ port: 3000 }, server.fetch); ``` ```ts export default { fetch: server.fetch }; // Cloudflare Workers ``` `server.fetch` 不检查路径,需要你自己把请求路由给它。更多平台见[部署](https://picomcp.github.io/picomcp/zh/docs/deploy)。 ## picomcp 如何使用 HTTP picomcp 以无状态 JSON 模式运行 Streamable HTTP: - 带有 JSON-RPC 消息或批量消息的 `POST` → 返回 `200` 和 JSON 响应,通知则返回 `202`。 - `GET` 和 `DELETE` → 返回 `405`,告诉客户端不提供服务器到客户端的流。 - `OPTIONS` → 为允许的来源处理 CORS 预检。 - 不支持的 `MCP-Protocol-Version` 请求头 → 返回 `400`。 不需要保存会话,所以任何实例都能响应任何请求。这让 picomcp 无需额外工作就能适配 serverless 平台、负载均衡和自动扩缩容。 ## 来源检查与 DNS 重绑定 浏览器会发送 `Origin` 请求头。恶意网页可能借此访问你机器上的服务器,所以 picomcp 会检查它: - 没有 `Origin` 的请求(CLI、服务器、桌面应用)始终允许。 - 默认只允许 `localhost`、`127.0.0.1` 和 `[::1]` 来源,端口不限。 - 其他来源返回 `403`。 用 `origins` 选项允许更多来源: ```ts pico({ name: "notes", origins: ["https://claude.ai", "https://app.example.com"] }); pico({ name: "public", origins: "*" }); // any origin: only for public, unauthenticated servers ``` ## 自定义传输方式 `server.handle(message)` 处理一条 JSON-RPC 消息或一批消息,并返回响应;对于通知,它返回 `undefined`。用它可以把 picomcp 接入 WebSocket、消息队列或其他任何通道: ```ts socket.on("message", async (data) => { const response = await server.handle(JSON.parse(data)); if (response) socket.send(JSON.stringify(response)); }); ``` --- # 测试 Source: https://picomcp.github.io/picomcp/zh/docs/testing > 在进程内带完整类型地测试工具,再端到端地测试协议。 ## 在进程内调用工具 `server.call(name, input)` 会像客户端一样运行工具,但不需要传输层,也不需要子进程。校验、默认值、输出检查和错误处理全部照常生效。 ```ts import { describe, expect, it } from "vitest"; import { server } from "./server.ts"; describe("forecast", () => { it("returns the forecast", async () => { const result = await server.call("forecast", { city: "Lyon" }); expect(result.isError).toBeUndefined(); expect(result.structuredContent).toMatchObject({ city: "Lyon" }); }); it("explains invalid input to the model", async () => { const result = await server.call("forecast", { city: "Lyon", days: 30 }); expect(result.isError).toBe(true); }); }); ``` 它适用于任何测试运行器:Vitest、`node:test`、`bun test`、`deno test`。 ## 经过类型检查的调用 当工具以链式方式注册时,`call` 知道它们的名称和输入: ```ts const server = pico({ name: "weather" }).tool("forecast", { input: z.object({ city: z.string(), days: z.number().default(3) }), run: ({ city, days }) => `${city}: ${days} days`, }); server.call("forecast", { city: "Lyon" }); // ✓ days is optional: it has a default server.call("forecast", {}); // ✗ city is missing server.call("forcast", { city: "Lyon" }); // ✗ no tool named forcast ``` 如果你在不同的语句中注册工具,`server` 的类型就无法跟踪它们,`call` 会接受任何名称。请使用链式调用,或者重新赋值:`const app = server.tool(...)`。 ## 测试协议 `server.handle` 接受原始 JSON-RPC,适合用来检查列表和 schema: ```ts const res = await server.handle({ jsonrpc: "2.0", id: 1, method: "tools/list" }); expect(res).toMatchSnapshot(); ``` 给 `tools/list` 做快照,是发现 schema 改动影响了模型所见内容的一种低成本方法。 ## 用真实客户端测试 做端到端测试时,可以用官方 SDK 的客户端通过 HTTP 驱动服务器: ```ts import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const running = await server.serve({ http: { port: 0 } }); const client = new Client({ name: "test", version: "1.0.0" }); await client.connect(new StreamableHTTPClientTransport(new URL(running.url!))); const { tools } = await client.listTools(); await client.close(); await running.close(); ``` picomcp 自己的测试套件正是这样做的,覆盖 stdio 和 HTTP 两种方式。 --- # 部署 Source: https://picomcp.github.io/picomcp/zh/docs/deploy > 在 Cloudflare Workers、Deno Deploy、Bun、Node、Hono 或 Next.js 上运行远程 MCP 服务器,并加上身份验证。 远程服务器通过 URL,使用 Streamable HTTP 访问。picomcp 是无状态的,因此无需额外设置就能运行在 serverless 平台上。 ## Cloudflare Workers ```ts import { server } from "./server.ts"; export default { fetch(request: Request) { if (new URL(request.url).pathname !== "/mcp") return new Response("Not found", { status: 404 }); return server.fetch(request); }, }; ``` ```sh npx wrangler deploy src/worker.ts ``` ## Deno Deploy ```ts import { server } from "./server.ts"; Deno.serve((request) => server.fetch(request)); ``` ## Bun ```ts Bun.serve({ port: Number(process.env.PORT ?? 3000), routes: { "/mcp": server.fetch }, }); ``` ## Node ```ts await server.serve({ http: { port: Number(process.env.PORT ?? 3000), hostname: "0.0.0.0" } }); ``` ## Hono ```ts import { Hono } from "hono"; const app = new Hono(); app.all("/mcp", (c) => server.fetch(c.req.raw)); export default app; ``` ## Next.js ```ts // app/mcp/route.ts import { server } from "@/lib/mcp"; export const POST = server.fetch; export const GET = server.fetch; export const OPTIONS = server.fetch; ``` ## 身份验证 picomcp 本身不处理身份验证:请包装 `server.fetch`,在请求到达工具之前先做检查。 ```ts export default { async fetch(request: Request, env: { TOKEN: string }) { if (request.headers.get("authorization") !== `Bearer ${env.TOKEN}`) { return new Response("Unauthorized", { status: 401, headers: { "www-authenticate": "Bearer" } }); } return server.fetch(request); }, }; ``` 客户端会发送其配置中的请求头,例如: ```sh claude mcp add --transport http notes https://notes.example.com/mcp --header "Authorization: Bearer $TOKEN" ``` 如需 OAuth,请把身份提供方的中间件放在 `server.fetch` 之前,并遵循 [MCP 授权规范](https://modelcontextprotocol.io/specification/latest/basic/authorization)。 ## 检查清单 - 通过 HTTPS 提供服务。 - 除非服务器是公开且只读的,否则要求身份验证。 - 如果有预期的浏览器来源,用 `origins` 设置它们。 - 把密钥放在环境变量中,绝不要写进工具描述或工具结果。 - 把工具参数当作不可信输入:模型可能受到它所读取内容的影响。 --- # API 参考 Source: https://picomcp.github.io/picomcp/zh/docs/api > picomcp 的全部导出及其签名。 ```ts import { pico, McpError, PROTOCOL_VERSIONS, text, image, audio, link, withJsonSchema } from "picomcp"; import type { Server, ToolConfig, ResourceConfig, PromptConfig, Context, CallToolResult } from "picomcp"; ``` ## pico(info) 创建一个服务器。 ```ts function pico(info: ServerInfo): Server; interface ServerInfo { name: string; // machine-friendly name, e.g. "weather" version?: string; // defaults to "0.0.0" title?: string; // display name instructions?: string; // how the model should use the server as a whole origins?: string[] | "*"; // allowed browser origins for HTTP; defaults to localhost } ``` ## server.tool(name, config) 注册一个工具并返回服务器。见[工具](https://picomcp.github.io/picomcp/zh/docs/tools)。 ```ts interface ToolConfig { title?: string; description?: string; input?: I; // Standard Schema output?: O; // Standard Schema; enables structuredContent annotations?: ToolAnnotations; run(input: Infer, ctx: Context): Return | Promise; } interface ToolAnnotations { title?: string; readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean; } ``` 名称无效或重复,或者 schema 不支持 JSON Schema 时,会抛出 `TypeError`。 ## server.resource(uri, config) 注册一个资源;当 `uri` 包含 `{variables}` 时,注册的是资源模板。返回服务器。见[资源](https://picomcp.github.io/picomcp/zh/docs/resources)。 ```ts interface ResourceConfig { name?: string; title?: string; description?: string; mimeType?: string; annotations?: Annotations; read(params: UriParams, ctx: Context & { uri: string }): ResourceReturn | Promise; } ``` ## server.prompt(name, config) 注册一个提示词并返回服务器。见[提示词](https://picomcp.github.io/picomcp/zh/docs/prompts)。 ```ts interface PromptConfig { title?: string; description?: string; args?: A; // Standard Schema of an object with string fields run(args: Infer, ctx: Context): string | PromptMessage[] | { description?: string; messages: PromptMessage[] }; } ``` ## server.call(name, input?) 在进程内运行一个工具,并返回它的 `CallToolResult`。如果工具以链式方式注册,调用会经过类型检查。工具名未知时,Promise 会被拒绝。见[测试](https://picomcp.github.io/picomcp/zh/docs/testing)。 ```ts call(name: string, input?: unknown): Promise; interface CallToolResult { content: ContentBlock[]; structuredContent?: Record; isError?: boolean; } ``` ## server.serve(options?) 通过 stdio 启动服务器;设置了 `http` 时,则通过 Streamable HTTP 启动。见[传输方式](https://picomcp.github.io/picomcp/zh/docs/transports)。 ```ts serve(options?: { http?: number | { port?: number; hostname?: string; path?: string } }): Promise<{ url?: string; close(): Promise }>; ``` ## server.fetch(request) 符合 Web 标准的 Streamable HTTP 处理函数。它已绑定到服务器,因此可以作为值直接传递。 ```ts fetch(request: Request): Promise; ``` ## server.handle(message) 处理一条 JSON-RPC 消息或一批消息。返回响应;对于通知,返回 `undefined`。 ```ts handle(message: unknown): Promise; ``` ## Context 每个处理函数的第二个参数。 ```ts interface Context { signal: AbortSignal; // aborted when the client cancels meta: Record | undefined; // the request's _meta } ``` ## 内容辅助函数 ```ts text(text: string, annotations?: Annotations): ContentBlock; image(data: string | Uint8Array, mimeType: string): ContentBlock; audio(data: string | Uint8Array, mimeType: string): ContentBlock; link(uri: string, name: string, extra?: { title?: string; description?: string; mimeType?: string }): ContentBlock; ``` `data` 可以是 base64 字符串或原始字节。在任何运行时上,字节都会自动编码。 ## withJsonSchema(schema, jsonSchema, output?) 为无法生成 JSON Schema 的 Standard Schema 附加一份 JSON Schema。见[Schema](https://picomcp.github.io/picomcp/zh/docs/schemas)。 ```ts withJsonSchema(schema: S, jsonSchema: JsonSchema, output?: JsonSchema): StandardSchemaV1, Infer>; ``` ## McpError 在处理函数中抛出它,就会返回 JSON-RPC 错误,而不是结果。 ```ts new McpError(code: number, message: string, data?: unknown); ``` | 错误码 | 含义 | | --- | --- | | `-32602` | 参数无效 | | `-32002` | 资源未找到 | | `-32000` 至 `-32099` | 可自由用于你自己的服务器错误 | ## PROTOCOL_VERSIONS picomcp 支持的 MCP 协议版本,从新到旧:`["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"]`。 ## 类型辅助 | 类型 | 说明 | | --- | --- | | `Infer` | schema 的输出类型 | | `InferInput` | schema 的输入类型 | | `UriParams<"a://{x}/{+y}">` | `{ x: string; y: string }` | | `StandardSchemaV1` | 内联的 Standard Schema 接口 | --- # 协议覆盖范围 Source: https://picomcp.github.io/picomcp/zh/docs/protocol > picomcp 具体实现了 MCP 规范的哪些部分,哪些尚未实现。 picomcp 以 MCP `2025-11-25` 为目标,并可协商降级到 `2025-06-18`、`2025-03-26` 和 `2024-11-05`。所有标记为支持的功能都有测试覆盖,包括与官方 TypeScript SDK 客户端的端到端测试。 ## 已支持 | 领域 | 功能 | | --- | --- | | 生命周期 | 带版本协商的 `initialize`、`notifications/initialized`、`ping` | | 工具 | `tools/list`、`tools/call`、输入校验、`outputSchema` 和 `structuredContent`、标注、标题 | | 内容 | 文本、图片、音频、资源链接、内嵌资源 | | 资源 | `resources/list`、`resources/templates/list`、`resources/read`、文本和二进制内容 | | 提示词 | `prompts/list`、`prompts/get`、从 schema 推导的参数 | | 实用功能 | 基于 `AbortSignal` 的 `notifications/cancelled` | | JSON-RPC | 批量消息、错误码、忽略客户端发来的响应 | | stdio | 以换行分隔的 JSON,stdout 不受误打日志影响 | | Streamable HTTP | 无状态 JSON 响应,通知返回 `202`,`GET` 返回 `405`,CORS,检查 `Origin` 和 `MCP-Protocol-Version` | ## 尚未支持 | 功能 | 原因 | | --- | --- | | Sampling、elicitation、roots | 需要服务器向客户端发送请求。picomcp 的无状态设计目前还没有为此保持一个开放通道。 | | 日志、进度和 `list_changed` 通知 | 同样的原因:都是服务器发给客户端的消息。 | | 资源订阅 | 同样的原因。 | | 自动补全 | 已列入计划。 | | 分页游标 | 列表会一次性完整返回,适合条目在几百个以内的服务器。 | | 会话(`Mcp-Session-Id`)和 SSE 流 | 无状态模式下不需要。 | | Tasks | 在规范中仍是实验性功能。 | 需要其中某项功能?请[提交 issue](https://github.com/picomcp/picomcp/issues)并描述你的使用场景。只要能在不让 picomcp 变臃肿的前提下加入,新功能就会到来。 --- # 写给 AI 智能体 Source: https://picomcp.github.io/picomcp/zh/docs/agents > 机器可读的文档、llms.txt、Markdown 页面,以及可以交给编程智能体的规则。 编程智能体会编写大量 MCP 服务器。这份文档既是写给你的,也同样是写给它们的。 ## 机器可读的文档 | URL | 内容 | | --- | --- | | [`/llms.txt`](https://picomcp.github.io/picomcp/llms.txt) | 所有页面的索引,每页附一行摘要,采用 [llms.txt](https://llmstxt.org) 格式 | | [`/llms-full.txt`](https://picomcp.github.io/picomcp/llms-full.txt) | 完整文档,合并为一个 Markdown 文件 | | `/docs/.md` | 任意页面的 Markdown 版本。在网址后加上 `.md`,例如 [`/docs/tools.md`](https://picomcp.github.io/picomcp/zh/docs/tools.md) | 每个页面还有一个**复制为 Markdown**按钮,方便粘贴到对话中。 这些文件是英文的:英文是参考语言,也是模型遵循得最好的语言。每种译文也有自己的 `llms-full.txt`,例如 `/fr/llms-full.txt`。 ## 让智能体读这份文档 把下面的内容粘贴到智能体的指令中,例如项目根目录下的 `AGENTS.md` 或 `CLAUDE.md`: ```md ## MCP servers This project builds MCP servers with picomcp. - Read https://picomcp.github.io/picomcp/llms-full.txt before writing picomcp code. - Define servers with `pico({ name })` and chain `.tool()`, `.resource()`, `.prompt()`. - Use Zod 4 schemas with `.describe()` on every field. - Write tool descriptions and error messages for the model: say what the tool does, when to use it, and what to do after an error. - Never write to stdout in a stdio server. Use `console.error` for logs. - Test tools with `server.call(name, input)`. Do not spawn a client in unit tests. ``` ## 给智能体一个文档服务器 文档也以 MCP 服务器的形式提供:[`picomcp-docs`](https://www.npmjs.com/package/picomcp-docs),用 picomcp 构建。它为智能体提供三个工具 `list_docs`、`search_docs` 和 `read_doc`,并把每个页面作为资源提供。 在 Claude Code 中: ```sh claude mcp add picomcp-docs -- npx -y picomcp-docs ``` 在 Cursor、Windsurf、Claude Desktop 和大多数其他客户端中,把它加入 MCP 配置文件(Cursor 为 `.cursor/mcp.json`): ```json { "mcpServers": { "picomcp-docs": { "command": "npx", "args": ["-y", "picomcp-docs"] } } } ``` 源码位于 [`packages/docs`](https://github.com/picomcp/picomcp/tree/main/packages/docs),大约 100 行。 ## 智能体常犯的错误 以下是模型编写 MCP 服务器时最常犯的错误,以及它们在 picomcp 中的正确做法: - **stdio 模式下,stdout 就是协议。** 一个 `console.log` 就会把它弄坏。通过 stdio 提供服务时,picomcp 会把控制台输出重定向到 stderr,但 `process.stdout.write` 依然不安全。 - **工具错误是结果,不是异常。** 在 `run` 中抛出错误,会得到带有你的错误信息的 `isError: true`。模型能看到它并重试。不要为了返回 `"Error: ..."` 字符串而捕获错误。 - **提示词参数都是字符串。** 数值类型的提示词参数请用 `z.coerce.number()`。 - **JSON Schema 来自 schema 库。** Zod 4 和 ArkType 无需额外处理。Valibot 需要 `toStandardJsonSchema`。其他库需要 `withJsonSchema`。 - **`server.fetch` 不检查路径。** 请自己把 `/mcp` 路由给它,或者使用只响应 `/mcp` 的 `serve({ http })`。 - **带类型的 `server.call` 需要链式注册。** `pico().tool().tool()` 会保留类型,分开写的语句会丢失类型。 ## 借助智能体参与贡献 仓库里有一份 [`AGENTS.md`](https://github.com/picomcp/picomcp/blob/main/AGENTS.md),写明了开发 picomcp 本身的规则:体积预算、测试命令和设计约束。