agentmentoragentmentor

第 04 节:给 server 加上下文和提示模板

本节 objectives:

  • 能解释为什么 resource 不等于只读 tool。
  • 能设计一个静态 resource 和一个 prompt。
  • 能判断哪些内容不应该塞进 tool handler。

先修:已写出一个最小 tool server | 上一节 << 03 | 下一节 05 >>

你的 server 不该只有按钮

如果 server 只有 tools,host 看到的是一排按钮:调用这个、调用那个。可是很多工作流不只是动作,还需要材料和固定问法。

官方 TypeScript SDK 文档把 resources 描述为 server 暴露的只读数据,例如文件、数据库 schema、配置;prompts 则是可复用模板,适合用户显式选择一种交互模式。1 这两个东西能让 server 更像一个"知识和动作包",而不是一堆函数。

讲解

Resource 的判断句:

如果它的核心是"让 host 读取一份上下文",优先考虑 resource。

Prompt 的判断句:

如果它的核心是"按固定结构发起一次对话/分析",优先考虑 prompt。

在 notes server 里,project://note-policy 可以是 resource。它告诉 host "学习日志的格式规范是什么"。而 review-week 可以是 prompt。它把"请按主题、阻塞点、下周动作复盘"做成模板。

不要把这两个都塞进 summarize_note handler。handler 越大,越难调试;primitive 越混,host 越难正确呈现。

跟我做一遍(worked example)

在上一节的 server.mjs 里,先加一个静态 resource:

js
server.registerResource(  "note_policy",  "notes://policy",  {    title: "Note Policy",    description: "The expected format for learning notes.",    mimeType: "text/plain",  },  async (uri) => ({    contents: [      {        uri: uri.href,        text: [          "Each note should include:",          "- topic",          "- what changed in your understanding",          "- one remaining question",        ].join("\n"),      },    ],  }));

再加一个 prompt:

js
server.registerPrompt(  "review_week",  {    title: "Review Week",    description: "Create a weekly learning review from notes.",    argsSchema: {      topic: z.string().min(1),    },  },  ({ topic }) => ({    messages: [      {        role: "user",        content: {          type: "text",          text: [            `Review my learning notes about ${topic}.`,            "Organize the answer into:",            "1. what became clearer",            "2. what is still confusing",            "3. one next practice task",          ].join("\n"),        },      },    ],  }));

这两个注册项没有让模型"自动更聪明"。它们只是把可读材料和可复用交互入口用协议暴露出来。

换你补全(faded example)

你有一个项目规范文件,URI 想设计成 project://review-rules。请补全 resource:

js
server.registerResource(  "review_rules",  "project://review-rules",  {    title: "Review Rules",    description: ____________________,    mimeType: ____________________,  },  async (uri) => ({    contents: [      {        uri: uri.href,        text: ____________________,      },    ],  }));

参考答案:

js
server.registerResource(  "review_rules",  "project://review-rules",  {    title: "Review Rules",    description: "Team rules for reviewing pull requests.",    mimeType: "text/plain",  },  async (uri) => ({    contents: [      {        uri: uri.href,        text: "Check behavior first, then tests, then naming. Do not request style-only churn.",      },    ],  }));

关键留空是 descriptionmimeTypetext:host 要靠它们判断这份 resource 是什么、怎么展示、能不能作为上下文使用。

小结 + 通向下一节

Tool 做动作,resource 给上下文,prompt 给交互模板。server 的表达力来自三者组合,不是来自把一个 handler 写得很肥。

下一节看 transport 和 lifecycle。你会知道为什么本地教程常用 stdio,远程部署又会谈 Streamable HTTP。

Footnotes

  1. MCP TypeScript SDK — https://github.com/modelcontextprotocol/typescript-sdk

练习

Level 1: 给你的 notes server 增加一个 resource,暴露一段固定文本。用你真实项目里的规则、术语表或 README 摘要都可以。

提示 1

先写一段你平时会复制给 agent 的提示词。

提示 2

找出每次会变的词,把它们做成 args。

提示 3

固定结构留在 prompt 模板里。

自评