跳到主要内容

第一个真正的服务器 — 三件套、Zod 与 Inspector

这一章讲三件事: 一台 MCP 服务器到底能往外卖哪几样东西(答案:只有三种); 用官方 SDK 从空目录到一台能跑的服务器要走哪几步; 以及怎么不写客户端就把服务器测一遍。 从这一章开始,我们离开徒手协议,进入 SDK 的「自动挡」—— 但第 02 章那套握手和消息,在底下一步没少地跑着。

1. 顶层全景:声明三样东西,接上一根管子

用 SDK 写服务器的全部骨架,五行就能说完:

const server = new McpServer({ name: "Demo", version: "1.0.0" });
server.tool( "multiply", {…参数规则…}, async (参数) => {…} );
server.resource( "get_greeting", greeting://{name}, async (uri) => {…} );
server.prompt( "review-code", { code: … }, ({code}) => {…} );
await server.connect(new StdioServerTransport()); // 接上 STDIO,开工

图说:三行声明 = 三样能力;connect 之后,
第 02 章那套握手和 JSON-RPC 收发全由 SDK 包办。

这一章的主走查是 multiply(两数相乘)这个工具的一生: 从你在代码里写下它的那一刻,到别人用 Inspector 工具敲 first=2, second=4、 看到答案 8 的那一刻,中间每一步我们都停下来看。

2. 三件套:服务器只能暴露这三样

先立住全章最重要的分类。一台 MCP 服务器能拿出来的东西,不多不少就三种1:

种类是什么谁来决定用它本书例子
tools(工具)会做事的函数:查数据、调外部服务、改状态通常由模型判断「此刻该调哪个」multiply 两数相乘
resources(资源)可读的静态数据:配置、文件、目录内容通常由应用或用户挑出来,喂给模型当上下文greeting://{name}
prompts(提示词)预制的提示词模板,带填空位通常由用户显式触发review-code 代码评审模板

(提示词我们在第 01 章见过——发给大语言模型的那段自然语言指令; 这里的 prompts 是服务器预写好的模板,客户端拿走填上料就能直接用。)

为什么要分成三种?我们协议书架的规范拆解给了一个很漂亮的记法: 模型、应用、用户,三方各管一件——模型决定调哪个工具,应用挑选哪些资源进上下文, 用户触发哪个模板2。服务器作者的唯一职责是:把这三样做得「对客户端那边的模型有用」3

3. 资源再讲一层:固定名与模板

三样里资源最容易被低估,多讲一层。

资源最常见的用法,书里概括为「一个简化版的 RAG」—— RAG 的做法分两半:「检索增强」——模型回答前, 先去把相关资料找出来贴在问题旁边;然后「生成」——让它照着答(它的全称就叫检索增强生成); 资源干的就是「把资料递过去」这一半4。书里的场景是:用户问「我想买台新笔记本」, 客户端先读一下资源,发现「哦,商品在哪张表里」,再让模型决定调哪个工具、带什么参数4

定义资源有两种写法5:

东西只有一份,用固定名。 比如全应用只有一份配置:

server.resource("config", "config://app", async (uri) => ({
contents: [{ uri: uri.href, text: "App configuration here" }]
}));

先交代一个到处都会碰到的词:字符串——一串字符拼成的文本,config://app 就是一个字符串。

config://app 是这个资源的 URI(统一资源标识符——给资源起名字的字符串, 和网址是亲戚,但不一定真的能在网上打开),客户端以后就照这个名字来取。

东西有一族,用模板。 比如设置分用户设置、日历设置……名字取不完, 就写成 settings://{type} 这样的资源模板(ResourceTemplate)—— {type} 是个填空位,客户端填 settings://calendar 就能取日历那份5:

server.resource("settings",
new ResourceTemplate("settings://{type}", { list: undefined }),
async (uri, { type }) => ({
contents: [{ uri: uri.href, text: `Settings from file ${type}` }]
}));

记住「模板」这两个字——第 5 节测试时,它会变成一个大坑。

4. 工具:输入规则写给机器看,也写给模型看

工具有的只是「一个函数」,但客户端(和它背后的模型)从没见过你的代码, 它怎么知道 multiply 要传两个数字?

答案是声明输入的规则。SDK 的写法是用 Zod——一个 TypeScript 的校验库, 你用「z.number() 是两个数字」这种链式写法声明数据该长什么样, 它既能帮你检查数据,又能把规则导出成机器可读的格式6:

server.tool("multiply",
{ first: z.number(), second: z.number() }, // 输入规则
async ({ first, second }) => ({
content: [{ type: "text", text: String(first * second) }]
})
);

客户端做 tools/list 时,拿到的就是这份规则翻译成的 JSON Schema—— 一种用 JSON 描述「一份数据该有哪些字段、各是什么类型、哪些必填」的通用格式, 下面这个就是 multiply 在 Inspector 里被列出来的真实样子7:

{ "name": "multiply",
"inputSchema": { "type": "object",
"properties": { "first": {"type":"integer"}, "second": {"type":"integer"} },
"required": ["first", "second"] },
"outputSchema": { "type": "object", "properties": { "result": {"type":"integer"} } } }

这一步是三件套的隐形地基:模型「会用你的工具」,靠的不是聪明,是这份 schema。 它读 inputSchema 才知道该凑出什么样的参数——第 06 章写客户端时,我们会亲眼看到 这份 JSON 被原样塞给大语言模型。

5. 主走查:multiply 的一生

现在把前几节拼起来,跟着 multiply 走全程。

第 ① 步,建工程。 npm init -y 起项目,装三样依赖: @modelcontextprotocol/sdk(官方 SDK,书里用 ^1.8.0 版)、zodtypescript; package.json 里配好 "build": "tsc"(把 TypeScript 编译成 JavaScript)和 "start": "node ./build/index.js"(跑编译产物)8

第 ② 步,写服务器。 就是第 1 节那张骨架:创建 McpServer 实例、 声明 multiply 工具、get_greeting 资源模板、review-code 提示词, 最后 server.connect(new StdioServerTransport())9connect 一调用,第 02 章那台「按行读 stdin、等握手」的机器就开始转了—— 只是这次一行协议代码都不用你写。

第 ③ 步,用 Inspector 连上。 Inspector 是官方的调试工具, 一边以客户端身份连你的服务器,一边给你开一个网页界面点点看; 它也能用 --cli 参数跑纯命令行模式,方便写进自动化脚本10。启动只要一句:

npx @modelcontextprotocol/inspector node ./build/index.js

第 ④ 步,列出工具。 命令行模式发 tools/list,回来的就是第 4 节那份 带 inputSchema 的清单——这证明服务器声明成功,规则也翻译成功7

第 ⑤ 步,调用工具。tools/call,带上工具名和参数11:

npx @modelcontextprotocol/inspector --cli … --method tools/call \
--tool-name multiply --tool-arg first=2 --tool-arg second=4

服务器按第 02 章的老规矩回应:content 数组里一个文本块,写着 "8"; 另外还带一个 structuredContent(结构化内容——同一结果的机器友好版, 方便客户端程序直接取值,省得再做解析——把文本按格式拆开、读出里面数据的那道工序)11:

{ "content": [{ "type": "text", "text": "8" }],
"structuredContent": { "result": "8" }, "isError": false }

第 ⑥ 步,读模板资源。 先列一下资源:resources/list 回来是空的—— 这就是第 3 节埋的那个坑:模板资源不在普通资源列表里,要用 resources/templates/list 才看得到。然后填上名字读一次: resources/read --uri greeting://chris,回来 Hello, chris!12

第 ⑦ 步,取提示词。 prompts/get --prompt-name review_code --prompt-args code="print('Hello World')",回来的不是「评审结果」, 而是一条填好料的提示词消息(Please review this code:\n\nprint('Hello World'))—— 它要被交给客户端那边的模型去执行,服务器只负责「预制」,不负责「评审」13

走完这七步,三件事各摸过一遍,这台服务器就算「通了」。

6. 作者的判断与证据

有证据的:

  • 三件套的分类、multiply/get_greeting/review-code 三个例子、 Inspector 的全部命令与输出,书里有完整代码和运行结果157111213;
  • 「STDIO 是最常见的传输、用于跑在本机的服务器」是书中的明确说法14

作者的判断:

  • 「应该把服务器当成『给客户端的模型赋能』的东西来设计」——这是作者的设计哲学, 不是协议要求3;
  • 测试手段上,书里推荐 Inspector 优先,其次才是写自动化测试—— 而且给出的自动化测试示例是 Python 的(pytest + FastMCP), 在一本 TypeScript 书里显得突兀。这不是笔误一两次,本书是从 Python 材料改编而来的, 后面章节还有多处残留,我们会在总纲里集中说明。

7. 边界与局限

  • 这一章的服务器只有「声明」,没有持久状态——订单、购物车全在内存里,重启就没; 书的作业(电商 STDIO 服务器,11 个工具 + 1 个资源)也一样,明示「状态放内存数据结构即可」15;
  • 资源是静态的——书里说得很直白:资源是「服务器能访问并分享的任何静态内容」5。 要实时数据、要改状态,那是工具的活;
  • 工具的输出校验(outputSchema)书里只提了一句「SDK 有默认输出 schema,也可以自定义」6, 没有展开;生产上给工具输出立 schema 是第 11 章会呼应的好习惯;
  • 本章没讲错误该怎么报——工具内部出错时,客户端看到的 isError 字段, 书里只在输出示例里出现,没有专门讲。

8. 可带走的

  1. 服务器三件套:tools 做事、resources 供数据、prompts 供模板;模型、应用、用户各管一件2;
  2. 工具三行出活:server.tool(名字, Zod 规则, 处理函数);
  3. Zod 声明一次,得到两样东西:运行时的输入校验 + 给客户端看的 inputSchema;
  4. 模板资源用 greeting://{name} 这种 URI 模板;它不出现在 resources/list, 要查 resources/templates/list——新手最容易在这里怀疑自己写错了;
  5. Inspector 是出厂自带的「万用客户端」:网页界面手动点,--cli 进流水线;
  6. structuredContent 是给程序吃的结果副本,content 里的文本是给人(和模型)看的;
  7. prompts 的执行者是客户端的模型,服务器只是预制模板——别把服务器当模型用。

9. 原文地图

主题原书章原文位置
三件套总述Building and Testing Serverstext/05-fm-building-and-testing-servers.txt:126(搜「core concepts」)
资源与简化 RAG同上text/05-fm-building-and-testing-servers.txt:148(搜「retrieval-augmented generation」)
固定名与模板资源同上text/05-fm-building-and-testing-servers.txt:192(搜「config://app」)· :211(搜「settings://{type}」)
工具与 Zod同上text/05-fm-building-and-testing-servers.txt:240(搜「multiply」)
提示词同上text/05-fm-building-and-testing-servers.txt:284(搜「product-description」)
运行时列表同上text/05-fm-building-and-testing-servers.txt:310(搜「officially supported runtimes」)
工程与依赖同上text/05-fm-building-and-testing-servers.txt:644(搜「@modelcontextprotocol/sdk」)
服务器完整代码同上text/05-fm-building-and-testing-servers.txt:894(搜「McpServer」)
Inspector 两种模式同上text/05-fm-building-and-testing-servers.txt:350(搜「CLI tool」)
tools/list 输出同上text/05-fm-building-and-testing-servers.txt:1032(搜「inputSchema」)
tools/call 输出同上text/05-fm-building-and-testing-servers.txt:1106(搜「structuredContent」)
模板资源列表的坑同上text/05-fm-building-and-testing-servers.txt:1130(搜「difference between a resource and a templated resource」)
resources/read 输出同上text/05-fm-building-and-testing-servers.txt:1150(搜「greeting://chris」)
prompts/get 输出同上text/05-fm-building-and-testing-servers.txt:856(搜「Please review this code」)
电商作业同上text/05-fm-building-and-testing-servers.txt:1231(搜「an e-commerce STDIO server」)

Footnotes

  1. 出处:「Building and Testing Servers」第 126-128 段(text/05-fm-building-and-testing-servers.txt:126,搜「core concepts」)。 2

  2. 补充(不在书里,依据我们的 protocol 书架):规范拆解把三件套记作「模型/应用/用户各控一件」。 依据: shelf=ai-protocol-reference/mcp-spec#03-server-primitives.md 事实=该章讲 Tools/Resources/Prompts 三件套及各自的控制方。 2

  3. 出处:「Building and Testing Servers」第 174-176 段(text/05-fm-building-and-testing-servers.txt:174,搜「empower a client's LLM」)。 2

  4. 出处:「Building and Testing Servers」第 144-172 段(text/05-fm-building-and-testing-servers.txt:148,搜「retrieval-augmented generation」;:168,搜「we call resources first to learn what table to query」)。 2

  5. 出处:「Building and Testing Servers」第 180-223 段(text/05-fm-building-and-testing-servers.txt:180,搜「Resources are static」;:211,搜「settings://{type}」)。 2 3 4

  6. 出处:「Building and Testing Servers」第 239-269 段(text/05-fm-building-and-testing-servers.txt:253,搜「Zod」;:269,搜「output schema」)。 2

  7. 出处:「Building and Testing Servers」第 1027-1065 段(text/05-fm-building-and-testing-servers.txt:1032,搜「inputSchema」)。 2 3

  8. 出处:「Building and Testing Servers」第 631-652 段(text/05-fm-building-and-testing-servers.txt:644,搜「@modelcontextprotocol/sdk」)。

  9. 出处:「Building and Testing Servers」第 894-943 段(text/05-fm-building-and-testing-servers.txt:901,搜「McpServer」)。

  10. 出处:「Building and Testing Servers」第 348-358 段(text/05-fm-building-and-testing-servers.txt:350,搜「CLI tool」)与第 997-1015 段(text/05-fm-building-and-testing-servers.txt:1003,搜「mcp dev」)。

  11. 出处:「Building and Testing Servers」第 1095-1110 段(text/05-fm-building-and-testing-servers.txt:1095,搜「--tool-name multiply」)。 2 3

  12. 出处:「Building and Testing Servers」第 1128-1164 段(text/05-fm-building-and-testing-servers.txt:1130,搜「difference between a resource and a templated resource」;:1150,搜「greeting://chris」)。 2

  13. 出处:「Building and Testing Servers」第 1197-1213 段(text/05-fm-building-and-testing-servers.txt:1197,搜「--prompt-args」)。 2

  14. 出处:「Building and Testing Servers」第 25 段(text/05-fm-building-and-testing-servers.txt:25,搜「most common way to communicate」)。

  15. 出处:「Building and Testing Servers」第 1231-1359 段(text/05-fm-building-and-testing-servers.txt:1359,搜「keep state within memory data structures」)。