跳到主要内容

MCP高级特性与最佳实践

在掌握 MCP 基础(MCP开发与应用)之后,本文聚焦协议规范中真实存在的高级能力——资源订阅、Roots、Sampling、Elicitation——以及可观测性与工程化最佳实践。所有代码均基于唯一官方 SDK @modelcontextprotocol/sdk,可直接作为面试回答。

目录

⚠️ 先澄清:哪些是真能力,哪些是虚构

很多旧教程里出现过下面这些「方法」,它们并不在 MCP 规范或官方 SDK 中,属于虚构,请勿使用:

虚构 API(❌ 不存在)应使用的真实能力(✅)
enableDynamicDiscovery / discoveryInterval规范的 listChanged 能力 + notifications/tools/list_changed 等列表变更通知
batchReadResources / batchCallTools没有批量原语;并发多次 resources/read / tools/call,编排放在 Agent 层
setAuthorizationPolicy远程用 OAuth 2.1;权限在 Server 实现内/宿主侧落地
registerToolChain / callToolChainMCP 保持原子工具,链路编排交给 LangGraph / OpenAI Agents SDK

下面介绍的都是 spec 中真实存在、且常被面试问到的高级能力。

🔔 资源订阅与变更通知

MCP 资源支持订阅 + 主动通知,让宿主无需轮询即可感知变化。能力协商时 Server 在 capabilities.resources 中声明 subscribe: true / listChanged: true

  • 内容更新:Client 发 resources/subscribe(带 uri),Server 在内容变化时推送 notifications/resources/updated
  • 列表变化:Server 推送 notifications/resources/list_changed,提示客户端重新 resources/list
// 服务端:资源变化时主动通知订阅者(官方 SDK)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

const server = new McpServer(
{ name: "docs-server", version: "1.0.0" },
{ capabilities: { resources: { subscribe: true, listChanged: true } } }
);

// 当底层文档发生变化时,通知客户端某个资源已更新
async function onDocChanged(uri) {
await server.server.sendResourceUpdated({ uri });
}

// 当资源列表本身发生增减时
async function onResourceListChanged() {
await server.server.sendResourceListChanged();
}
// 客户端:订阅并监听更新通知
import { ResourceUpdatedNotificationSchema } from "@modelcontextprotocol/sdk/types.js";

await client.subscribeResource({ uri: "file:///app/config.json" });

client.setNotificationHandler(ResourceUpdatedNotificationSchema, async (n) => {
const fresh = await client.readResource({ uri: n.params.uri });
console.log("资源已更新,重新读取:", fresh);
});

📁 Roots:宿主向 Server 声明可访问根

Roots 是 Client(宿主)能力:宿主向 Server 声明一组「根」URI(通常是文件系统目录或项目工作区),告诉 Server「你应当在这些边界内工作」。这是 MCP 的安全/作用域边界机制——比如把文件型 Server 限制在当前工作区目录内。

  • Client 在 initialize 时声明 capabilities.roots(可含 listChanged)。
  • Server 可调用 roots/list 获取根列表;根变化时 Client 推送 notifications/roots/list_changed
// 客户端:声明支持 roots,并提供当前工作区根
import { Client } from "@modelcontextprotocol/sdk/client/index.js";

const client = new Client(
{ name: "my-ide", version: "1.0.0" },
{ capabilities: { roots: { listChanged: true } } }
);

client.setRequestHandler(/* ListRootsRequestSchema */ ListRootsSchema, async () => ({
roots: [{ uri: "file:///Users/me/project", name: "当前项目" }],
}));

🤖 Sampling:Server 反向请求 LLM

SamplingServer 反向向宿主请求一次 LLM 补全sampling/createMessage)。这样 Server 内部需要「智能」时,无需自己持有模型 API Key,而是借用宿主的模型——并且宿主可对每次采样做人工审批与限额,是重要的安全/成本控制点。

// 服务端工具内部:请求宿主的 LLM 完成一次推理
server.tool("summarize", "对长文本做摘要", { text: z.string() }, async ({ text }) => {
const completion = await server.server.createMessage({
messages: [
{ role: "user", content: { type: "text", text: `用三句话总结:\n${text}` } },
],
maxTokens: 200,
});
return { content: [{ type: "text", text: completion.content.text }] };
});

Server 不应假定 sampling 一定可用:宿主可能未实现,或要求用户逐次批准。面试要点:Sampling 是「Server→Host→LLM」的反向链路,主权与审批在宿主

💬 Elicitation:Server 向用户补充信息

Elicitation(较新版本规范引入)允许 Server 在执行过程中向用户索取结构化输入elicitation/create),例如缺少必填参数、需要二次确认时,由宿主弹出表单收集,再继续执行。它配合 JSON Schema 描述需要的字段。

// 服务端:执行中向用户索取缺失信息
const result = await server.server.elicitInput({
message: "请提供要部署的目标环境",
requestedSchema: {
type: "object",
properties: { env: { type: "string", enum: ["staging", "production"] } },
required: ["env"],
},
});
// result.action: "accept" | "decline" | "cancel"

📊 可观测性:用 OpenTelemetry 包装 tools/call

MCP 规范本身不提供 metrics 配置项(旧教程里的 metrics: { endpoint } 是虚构)。生产中的做法是在 Server 侧用 OpenTelemetry 给每次 tools/call 打 span,记录工具名、入参摘要、耗时、成败、错误码,并接入 Jaeger / Tempo / Datadog 等后端。

import { trace, SpanStatusCode } from "@opentelemetry/api";

const tracer = trace.getTracer("mcp-server");

function instrumentedTool(name, description, schema, handler) {
server.tool(name, description, schema, async (args, extra) => {
return tracer.startActiveSpan(`tool.${name}`, async (span) => {
span.setAttribute("mcp.tool.name", name);
try {
const res = await handler(args, extra);
span.setStatus({ code: SpanStatusCode.OK });
return res;
} catch (err) {
span.recordException(err);
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
throw err;
} finally {
span.end();
}
});
});
}

配合宿主侧的「任务级 trace id」,即可端到端回放「模型决策 → 工具调用 → 数据源访问」的全链路(详见 AI应用架构设计 的可观测性章节)。

🔐 安全与权限最佳实践

  • 细粒度权限在 Server 内落地:用真实的鉴权中间件/数据库角色控制每个工具能访问的数据,而不是依赖虚构的 setAuthorizationPolicy
  • 远程鉴权用 OAuth 2.1:Streamable HTTP Server 作为 OAuth 资源服务器,校验访问令牌、限定 scope、使用 PKCE。
  • 传输安全:远程一律 HTTPS(wss/https),校验 Origin、绑定会话 ID,防止 DNS 重绑定攻击。
  • 高危工具 HITL:删除/转账/写生产等操作交由宿主人工确认;Sampling 与 Elicitation 也应受用户审批。
  • 供应链审计:第三方 Server = 第三方代码,安装前审查来源与依赖、锁定版本,纳入发布清单。

🧩 工具链编排归 Agent 层,MCP 保持原子工具

一个常见误区是想让 MCP「内置工具链/流水线」。正确分层是

  • MCP Server:只暴露原子、单一职责的工具(fetch_datarun_querysend_email…),无状态、可组合。
  • Agent 层:由 LangGraph(状态图)、OpenAI Agents SDK(handoff/guardrails)、Vercel AI SDK 等负责把多个工具编排成多步工作流、做条件分支、重试与补偿。

这样 MCP Server 可被任意宿主复用,编排逻辑也不被锁死在协议层,符合「关注点分离」。

📝 总结

MCP 的高级能力是资源订阅、Roots、Sampling、Elicitation,外加用 OpenTelemetry 自建可观测性;权限靠 Server 实现与 OAuth 2.1,编排交给 Agent 层。务必避开 enableDynamicDiscoverybatchCallToolssetAuthorizationPolicyregisterToolChain 这类虚构 API——它们既不在规范里,也不在官方 SDK 中。坚持「Server 提供原子能力、宿主掌握主权与审批、Agent 负责编排」的分层,是构建安全可扩展 MCP 系统的关键。