面试题库
共 328 题,当前筛选 21 题。
7 天 MCP:把工具接进任何 Agent
D1 为什么需要一个协议:host / client / server 三角、JSON-RPC 消息与三种原语
MCP 和模型自带的函数调用到底差在哪?什么情况下你不该用 MCP?How is MCP actually different from a model's built-in function calling, and when should you not use MCP?
国内高频海外高频基础#mcp-basics#architecture分析过程 · 先想清楚再作答
- 这题在筛「有没有真正接过工具」。把 MCP 说成「函数调用的升级版」就露馅了,因为两者根本不在同一层,答对的人第一句就会先把层次拆开。
- 拆法:问自己「这一步是模型 API 的事,还是工具从哪来的事」。函数调用是模型 API 的能力——你把工具定义放进请求,模型回一个要调谁;MCP 管的是那份定义和执行体住在哪个进程里、用什么语言交换。
- 接着点出两者是叠加而非替代:MCP 客户端拿到 tools/list 之后,还要把它翻译成模型 API 的工具参数,最终仍然走函数调用那条路。
- 结论:MCP 解决的是 M 个应用乘 N 个工具的重复接线,把乘法变成加法;它换来的代价是多一层进程、一层序列化、一层要排查的地方。
- 不该用的三种情况:工具只有自己这一个程序用;调用极频繁且对延迟敏感(远程一次往返几十到几百毫秒,一轮连调五次用户就有感);这件事根本不需要模型决定,产品逻辑本来就是确定的。
- 可预期的追问:那本机 stdio 的开销很小,是不是就可以随便用?答案是开销不只在传输,还在多一个要部署、要监控、要授权的进程上。
How to reason about it · think before answering
- The screen is whether you have actually wired tools yourself. Calling MCP an upgraded function call fails, because the two sit at different layers.
- Separate the layers first: function calling is a model API feature — you pass tool definitions in the request and the model replies with which one to invoke. MCP governs where that definition and its executor live and how they are exchanged.
- They compose rather than compete: an MCP client still translates tools/list output into the model API's tool parameters, so the final hop is ordinary function calling.
- Conclusion: MCP turns an M-applications-by-N-tools wiring problem into M plus N, at the cost of an extra process, an extra serialization boundary, and an extra place to debug.
- Skip MCP when the tool has exactly one consumer, when calls are hot and latency-sensitive (a remote round trip is tens to hundreds of milliseconds, five per turn is noticeable), or when the decision does not need a model at all.
- Likely follow-up: local stdio is cheap, so why not use it everywhere? Because the cost is not only transport — it is one more process to deploy, monitor, and authorize.
答题要点
- 函数调用是模型 API 的能力,MCP 是工具定义与执行体的分发协议,两者叠加而不是替代
- MCP 的价值是把 M 乘 N 的适配器数量变成 M 加 N,代价是多一层进程与序列化
- 单一消费者、延迟敏感的热路径、以及本来就确定的产品流程,这三种情况不该用 MCP
- 判据是「这个能力要不要给第二个程序用」,只要答案是要,协议的成本就摊得开
Key points
- Function calling is a model API capability; MCP is a distribution protocol for tool definitions and executors — they stack, not compete
- MCP converts M-by-N adapters into M plus N, paying with an extra process and serialization hop
- Skip it for single-consumer tools, latency-sensitive hot paths, and flows that are deterministic by design
- The test is whether a second program will ever need this capability; if yes, the protocol cost amortizes
MCP 规范为什么规定一个客户端只连一个服务端?多路复用不是更省资源吗?Why does the MCP spec require one client per server instead of multiplexing many servers over one connection?
国内高频海外高频进阶#architecture#security分析过程 · 先想清楚再作答
- 这题看着在问性能,其实在问安全边界。只从连接数和资源占用切入的回答会被判为没读过设计原则那一节。
- 拆法:先问「共享一条通道之后,谁能看见谁」。规范写死了两条原则——服务端不应该读到整段对话,也不应该看得见别的服务端;一对一是实现这两条最直接的手段。
- 举一个具体后果:接一个第三方天气服务端时,一对一隔离让它只能看到你传的城市名;共享通道则可能让它读到你和内部数据库服务端之间的往来,那就是一次数据泄露。
- 结论:完整对话历史留在宿主,服务端只拿到这次真正需要的参数;宿主是唯一的安全边界执行者,也是唯一做跨服务端编排的地方。
- 代价要主动说:接 N 个服务端就有 N 条连接、N 套生命周期要管,客户端实现的复杂度大头正是在这里,而不是在发报文上。
- 可预期的追问:那多个服务端的工具重名怎么办?答案是聚合与消歧是宿主侧的职责,规范建议加服务端标识前缀,并且明确说不要依赖服务端自报的名字,因为它不保证唯一也未经验证。
How to reason about it · think before answering
- It reads like a performance question but is really about security boundaries. Answering only in terms of connection count signals you never read the design principles.
- Ask who can see whom once a channel is shared. The spec fixes two principles: servers should not read the whole conversation, and should not see into other servers. One-to-one is the most direct way to enforce both.
- Concrete consequence: with isolation, a third-party weather server sees only the city you passed. On a shared channel it could observe traffic between you and an internal database server — a data leak.
- Conclusion: full history stays with the host, each server receives only the arguments this call needs, and the host is the single place where boundaries are enforced and cross-server orchestration happens.
- State the cost yourself: N servers means N connections and N lifecycles, and that is where most client complexity lives, not in sending messages.
- Likely follow-up: how do you handle tool name collisions across servers? Aggregation and disambiguation belong to the host; the spec suggests prefixing with a server identifier and explicitly warns against relying on the server's self-reported name, which is neither unique nor verified.
答题要点
- 一对一是安全设计而非性能设计:服务端读不到整段对话,也看不见别的服务端
- 完整历史留在宿主,服务端只收到本次调用真正需要的参数
- 跨服务端的聚合、消歧、授权都由宿主统一做,边界只有一处需要加固
- 代价是连接与生命周期管理,这是客户端实现复杂度的主要来源
Key points
- One-to-one is a security decision, not a performance one: servers cannot read the conversation or see peers
- Full history stays in the host; a server receives only the arguments for the current call
- Aggregation, disambiguation, and authorization all happen in the host, so there is a single boundary to harden
- The cost is connection and lifecycle management, which dominates client implementation complexity
2026-07-28 这一版把 MCP 改成了无状态协议,删掉了 initialize 握手。这么改的代价是什么?服务端还想保存状态该怎么办?The 2026-07-28 revision made MCP stateless and removed the initialize handshake. What does that cost, and how should a server that still needs state handle it?
国内高频海外高频深入#protocol-versions#statelessness分析过程 · 先想清楚再作答
- 这题的区分度在「知不知道这一版改了什么」。凭旧记忆答握手、会话标识、断流续传的人会当场暴露,因为这三样在这一版全被删了。
- 先说改了什么:没有 initialize 与 notifications/initialized,每条请求在 _meta 里自带协议版本与客户端能力;新增 server/discover 供客户端一次性取回版本、能力与身份,服务端必须实现它。
- 拆代价的角度是「省了什么、贵了什么」。贵的是报文:每条请求都要重复带版本与能力块。省的是三件事——任意副本都能处理请求所以扩容不用粘性路由、一条连接可以穿插无关请求、进程重启后在途请求重发即可。
- 结论:这是一次拿带宽换可伸缩性的交易,对本机 stdio 几乎无感,对多副本的远程部署收益很大。
- 状态怎么办:显式句柄。创建工具返回一个服务端铸造的 id,后续调用把它当普通参数传回来;服务端把状态按这个 key 存在自己的库里,并在工具描述里写清有效期。
- 可预期的追问:句柄安全吗?必须补一句——句柄是名字不是凭证,服务端每次都要重新校验调用者身份,句柄要用安全随机数生成、绑定到已认证的主体、并设过期时间。
How to reason about it · think before answering
- The discriminator is whether you know what this revision changed. Anyone answering from memory about handshakes, session IDs, or stream resumption exposes themselves — all three were removed.
- State the change first: no initialize and no notifications/initialized; every request carries its protocol version and client capabilities in _meta, and a new server/discover method, which servers MUST implement, returns versions, capabilities, and identity in one call.
- Weigh it as saved versus paid. You pay in payload size, repeating the version and capability block on every request. You save three things: any replica can serve any request so scaling needs no sticky routing, unrelated requests can interleave on one connection, and after a restart in-flight requests simply get resent.
- Conclusion: it trades bandwidth for scalability — near-invisible on local stdio, valuable for multi-replica remote deployments.
- For state, use explicit handles: a creation tool returns a server-minted id, and later calls pass it back as an ordinary argument while the server keys its own storage on it and documents the lifetime in the tool description.
- Likely follow-up: is a handle safe? Say it unprompted — a handle is a name, not a credential. Re-authorize the caller on every call, generate handles with a secure random source, bind them to the authenticated principal, and expire them.
答题要点
- 这一版删掉了 initialize 握手、协议级会话、GET 长连接与断流续传,改为每条请求自带版本与能力
- 新增 server/discover,服务端必须实现,客户端可在任何请求前一次性取回版本、能力与身份
- 代价是报文变胖,收益是无粘性路由的横向扩容、连接上可穿插无关请求、重启后重发即可
- 跨调用状态改用服务端铸造的显式句柄,作为普通工具参数传递,并且句柄不等于身份认证
Key points
- The revision removed the initialize handshake, protocol-level sessions, the GET stream, and stream resumption; each request now carries version and capabilities
- It added server/discover, which servers must implement, letting clients fetch versions, capabilities, and identity up front
- The cost is larger payloads; the payoff is sticky-free horizontal scaling, interleaved unrelated requests, and cheap retry after restarts
- Cross-call state moves to server-minted explicit handles passed as ordinary tool arguments, and a handle is never authentication
D2 写第一个 MCP server:stdio 传输、官方 SDK、参数 schema、工具注解与 Inspector 调试
工具的 description 到底写给谁看?写得太泛,在生产里会造成什么具体后果?Who is a tool's description actually written for, and what concretely goes wrong in production when it is too vague?
国内高频海外高频基础#tool-design#prompt-surface分析过程 · 先想清楚再作答
- 这题在筛「有没有真的排查过模型不调工具」。答成「写清楚一点,方便别人理解」就落到文档思维了;面试官想听的是描述是模型唯一的判断依据这件事。
- 拆法:先问自己「模型做这个决定时手上有什么」。它看不到你的 wiki、代码注释、需求文档,只有工具名加这一句描述加参数 schema。所以描述不是文档,是决策依据。
- 把「太泛」拆成两个方向的后果:一是**漏调**,模型不知道这个工具能解决当前问题,任务默默做不成,而且不会报错;二是**误调**,描述边界不清,模型在不该调的时候调它——如果这个工具有副作用,那就是一次真实的线上事故。
- 结论:一句合格的描述要回答三件事——做什么、参数长什么样(给例子)、什么情况下才该用。第三条最常被漏掉,也最要命,因为它才是防误调的那道闸。
- 补一条工程视角:描述是对外契约,改它等于改行为。同一段描述在不同模型上表现还不一样,所以描述要进版本管理、要有评估集,不能靠上线后人肉观察。
- 可预期的追问:那把描述写得越长越好吗?不是。描述会占上下文预算,工具一多就挤掉真正的对话内容;正确做法是短而准,把细节放进每个参数各自的 description 里。
How to reason about it · think before answering
- The screen is whether you have ever debugged a tool the model refuses to call. Answering 'write it clearly so colleagues understand' reveals doc-thinking; the point is that the description is the model's only evidence.
- Ask what the model has when it makes the decision: the tool name, this one description, and the parameter schema. It cannot see your wiki, comments, or spec. The description is a decision input, not documentation.
- Split vagueness into two failure directions. Under-calling: the model never realizes the tool solves the current problem, so the task silently fails with no error. Over-calling: fuzzy boundaries make the model invoke it when it should not, which is a real incident if the tool has side effects.
- Conclusion: a usable description answers three things — what it does, what the parameters look like with an example, and when it should be used. The third is the one people omit, and it is the gate that prevents over-calling.
- Add the engineering view: a description is an external contract, so changing it changes behavior, and the same wording performs differently across models. It belongs in version control with an eval set, not in post-launch eyeballing.
- Likely follow-up: is longer always better? No. Descriptions consume context budget and crowd out the actual conversation once you have many tools. Keep the summary short and push detail into each parameter's own description.
答题要点
- 描述是给模型看的,是它决定调不调这个工具的唯一依据,不是给同事看的文档
- 写得太泛有两类后果:漏调导致任务静默失败,误调则可能触发有副作用的操作
- 合格描述回答三件事:做什么、参数长什么样并给例子、什么情况下才该用
- 描述是对外契约,要进版本管理并配评估集;细节放进每个参数的 description,总描述保持短而准
Key points
- The description is read by the model and is its only basis for deciding whether to call the tool
- Vagueness causes silent under-calling or dangerous over-calling of side-effecting tools
- A good description states what it does, what the parameters look like with an example, and when it applies
- Treat it as an external contract with version control and evals; push detail into per-parameter descriptions to save context
什么时候该返回 JSON-RPC 的 error,什么时候该返回 isError 为真的工具结果?给我一个判据。When should a tool return a JSON-RPC error versus a result with isError set to true? Give me a decision rule.
国内高频海外高频进阶#error-handling#tool-design分析过程 · 先想清楚再作答
- 这题几乎是 MCP 服务端的入门分水岭。能背出「两类错误」只算及格,区分度在于能不能给出一条可执行的判据,以及知不知道错误文案是写给谁的。
- 拆法:问「谁能修好这个错」。请求本身不合法——工具名不存在、参数不满足调用工具的 schema、服务端内部异常——模型再怎么改参数都没用,这类走 JSON-RPC 的 error,典型是 -32602。工具跑了但业务没成——下游 API 失败、日期格式不对、金额越界——模型换个参数就可能成功,这类走 result 里的 isError。
- 判据一句话:**模型换个参数有没有可能成功?有就用 isError,没有就用 error。** 注意 isError 仍然是一个成功的 JSON-RPC 响应,resultType 照样是 complete。
- 结论要带上文案要求:规范说客户端应当把执行错误交给模型自我纠正,所以文案是写给模型看的,要列出可选值、正确格式、边界条件。写「参数错误」等于让模型瞎猜。
- 生产视角的坑:最危险的不是分错类,而是**两类都不返回**——不做校验,让非法输入算出 NaN 或空结果静默返回。模型会把错误答案当正确答案用下去,且不留痕迹。靠输出 schema 校验去兜底也不算处理,因为模型拿到的是一段 schema 堆栈。
- 可预期的追问:客户端要不要把协议错误也喂给模型?规范说可以,但基本没用,因为模型改不了;更该做的是记日志报警,那是你的 bug 不是模型的。
How to reason about it · think before answering
- This is close to a pass/fail line for MCP server work. Reciting 'two kinds of errors' is baseline; the discriminator is producing an actionable rule and knowing who the error text is written for.
- Ask who can fix it. If the request itself is invalid — unknown tool, arguments failing the call-tool schema, an internal server fault — no amount of parameter tweaking helps, so return a JSON-RPC error, typically -32602. If the tool ran but the business case failed — downstream API error, bad date format, amount out of range — a different argument might work, so return isError in the result.
- The rule in one line: could the model succeed by changing an argument? If yes use isError, if no use error. Note that isError is still a successful JSON-RPC response with resultType complete.
- Carry the text requirement into the conclusion: the spec says clients should hand execution errors to the model for self-correction, so the message is written for the model. List allowed values, the correct format, the boundary. 'Invalid parameter' just makes it guess.
- The production trap is not misclassifying but returning neither — skipping validation so an illegal input yields NaN or an empty result that is silently returned. The model then uses a wrong answer with no trace. Leaning on output-schema validation is not handling it either, since the model receives a schema stack trace.
- Likely follow-up: should clients feed protocol errors to the model too? The spec permits it but it rarely helps, because the model cannot fix them. Log and alert instead — that one is your bug.
答题要点
- 协议错误走 JSON-RPC 的 error:未知工具、请求不满足 schema、服务端内部错,模型改参数也无济于事
- 执行错误走结果里的 isError 为真:下游失败、业务校验不过,它仍是成功的 JSON-RPC 响应
- 判据是模型换个参数有没有可能成功,有就 isError,没有就 error
- 执行错误的文案写给模型看,要列出可选值与正确格式;最危险的是两类都不返回、静默给出错误结果
Key points
- Protocol errors use the JSON-RPC error field: unknown tool, schema-invalid request, internal fault — unfixable by the model
- Execution errors use isError true in the result and remain a successful JSON-RPC response
- The rule: if a different argument could succeed, use isError; otherwise use error
- Write execution-error text for the model with allowed values and formats; the worst case is neither, silently returning a wrong result
一个 stdio 的 MCP 服务端最常见的翻车原因是什么?你会在代码和流程上分别怎么堵住它?What is the most common way a stdio MCP server breaks, and how do you prevent it in code and in process?
国内高频海外高频深入#stdio-transport#debugging分析过程 · 先想清楚再作答
- 这题在验有没有真跑过。没实际接过的人会答「进程没起来」「路径不对」这类泛泛的,真踩过的人第一句就会说标准输出被污染。
- 拆法:先复述 stdio 的硬规矩——消息是一行一条换行分隔的 JSON、内部不许有裸换行,服务端不得往标准输出写任何不是 MCP 消息的东西,日志一律走标准错误。规矩一说完,翻车原因就自明了。
- 现场特征值得单独说,因为它是这题的区分点:客户端只会报一句 JSON 解析失败,指不到你哪一行 console.log;而且污染源常常不是你自己的代码,而是某个第三方库在启动时打的横幅或弃用警告。
- 结论分两层。代码上:封一个只写标准错误的日志函数并全局禁用直接打印,接第三方库之前先确认它不往标准输出写东西,把 JSON 序列化后确保不含裸换行。流程上:加一个自测入口,用内存传输在同一个进程里把客户端和服务端接起来跑断言,有明确退出码,进持续集成——这样污染一出现就会在合并前被拦下。
- 再补一条相关的:优雅停机。规范说客户端关掉输入流、服务端读到文件结束就应尽快退出,这是主要且唯一可移植的停机信号;不处理它就会留下孤儿进程,本机开发时表现为端口和文件锁莫名被占。
- 可预期的追问:既然这么脆,为什么还用 stdio?因为它零配置、零网络攻击面、进程隔离天生就有,本机场景收益远大于代价;要给团队共享或多副本才需要换成远程传输。
How to reason about it · think before answering
- This checks whether you have actually run one. People who have not will say 'the process did not start' or 'wrong path'; anyone who has been bitten leads with stdout contamination.
- Restate the hard rules first: messages are newline-delimited JSON, one per line, with no embedded newlines, and the server must not write anything to stdout that is not an MCP message. Logging goes to stderr. Once the rules are stated the failure mode is obvious.
- Call out the symptom, because that is the discriminator: the client only reports a JSON parse failure and cannot point at your console.log, and the polluter is often a third-party library printing a banner or deprecation warning at import time rather than your own code.
- Conclusion in two layers. In code: wrap a stderr-only logger, ban direct printing, vet third-party libraries for stdout writes, and ensure serialized JSON carries no raw newlines. In process: add a self-test entry point that links a client and server over an in-memory transport in one process, asserts, and exits with a real status code, then run it in CI so contamination is caught before merge.
- Add the adjacent one: graceful shutdown. The spec makes closing stdin and exiting on EOF the primary and only portable shutdown signal; ignoring it leaves orphan processes that show up locally as mysteriously held ports and file locks.
- Likely follow-up: if it is this fragile, why use stdio? Zero configuration, zero network attack surface, and process isolation for free — the tradeoff is clearly worth it locally. You switch transports when you need team sharing or multiple replicas.
答题要点
- 最常见的是标准输出被污染:stdio 规定 stdout 只能有 MCP 消息,一行 console.log 就让客户端解析失败
- 现场只报 JSON 解析失败,指不到具体行,污染源常常是第三方库启动时打的横幅或警告
- 代码上封一个只写标准错误的日志函数并禁用直接打印,接库之前先验它不写 stdout
- 流程上加一个用内存传输的自测入口,有明确退出码并进持续集成;同时处理 stdin 关闭时的优雅退出
Key points
- Stdout contamination: stdio reserves stdout for MCP messages, so a single console.log breaks the client's parser
- The symptom is only a JSON parse failure with no line number, and the culprit is often a third-party library's startup banner
- In code, use a stderr-only logger, ban direct printing, and vet dependencies for stdout writes
- In process, add an in-memory-transport self-test with a real exit code in CI, and exit promptly on stdin EOF to avoid orphan processes
D3 resources 与 prompts:URI 模板、变更通知、进度与日志、分页,以及客户端能力
同一份数据,做成 MCP 资源和做成工具有什么区别?你按什么标准选?For the same data, what is the difference between exposing it as an MCP resource versus a tool, and how do you choose?
国内高频海外高频基础#primitives#server-design分析过程 · 先想清楚再作答
- 这题在筛「有没有真的设计过服务端」。答成「资源是只读的、工具会改数据」只能算及格,因为只读的检索照样该做成工具,区分度全在这一步。
- 拆法:不要问「它是什么」,问「这一次由谁决定用不用它」。规范把资源定成应用驱动——由宿主应用或用户挑;工具是模型控制——模型看着描述自己调。控制方定了,出错时该找谁负责也就定了。
- 再补一条更实用的判据:能不能被枚举。资源要出现在一张可翻页的清单里让人挑,所以「搜索代码」这种输入空间无限的能力,哪怕完全只读也必须做成工具。
- 结论:只读、可枚举、希望用户在界面上挑的做成资源;有副作用、或需要模型自己判断时机、或无法枚举的做成工具。
- 生产视角要主动加一句成本:工具定义不管用不用,每轮都要塞进请求;资源不被选中就一个 token 都不占。三千篇文档做成三千个工具会直接撑爆上下文,做成资源则按需付费。
- 可预期的追问:那提示模板算第几种?答案是第三种,由用户显式选中,典型形态是斜杠命令——三种原语的差别只在控制方,不在能力。
How to reason about it · think before answering
- This screens for real server design experience. Saying resources are read-only and tools mutate scores a pass at best, because read-only search still belongs in a tool.
- Reframe it: do not ask what the data is, ask who decides to use it this time. The spec makes resources application-driven, picked by the host or the user, while tools are model-controlled. Fixing the controller also fixes who is accountable when it goes wrong.
- Add the practical test: enumerability. A resource has to appear in a paginated list a human can pick from, so a code search with an unbounded input space must be a tool even though it never writes anything.
- Conclusion: read-only, enumerable, user-selectable becomes a resource; side-effecting, model-timed, or non-enumerable becomes a tool.
- Bring up cost unprompted: tool definitions ship on every turn whether used or not, while an unselected resource costs zero tokens. Three thousand documents as three thousand tools blows up the context window; as resources they are pay-per-use.
- Likely follow-up: where do prompts fit? They are the third primitive, user-selected and usually surfaced as slash commands — the three differ only by who controls them.
答题要点
- 资源是应用驱动的,由宿主或用户挑;工具是模型控制的,由模型看描述自己调
- 能不能枚举是最实用的分界线:搜索这类输入空间无限的能力即使只读也做成工具
- 成本上工具定义每轮都占上下文,资源不被选中就不花钱,大规模知识库必须走资源
- 控制方决定了出错时找谁负责:模型选错是描述问题,用户选错是命名问题,应用塞错是产品问题
Key points
- Resources are application-driven and picked by host or user; tools are model-controlled and chosen from their descriptions
- Enumerability is the practical dividing line: unbounded-input capabilities like search stay tools even when read-only
- Cost-wise tool definitions occupy context every turn while unselected resources cost nothing, so large corpora must be resources
- The controller determines accountability: bad tool choice means bad descriptions, bad prompt choice means bad naming, bad resource injection is a product problem
MCP 的分页游标为什么必须是不透明的?如果客户端去解析它,会出什么问题?Why must MCP pagination cursors be opaque, and what breaks if a client parses them?
国内高频海外高频进阶#pagination#api-design分析过程 · 先想清楚再作答
- 这题表面考规范条文,实际考「有没有做过带分页的对外接口」。只背出「规范说不透明」拿不到分,要能说出解析之后具体哪一步会崩。
- 拆法:先问游标里到底装的是什么。服务端可以装偏移量、主键、时间戳、甚至一段加密状态,而且**换实现时它随时会变**。客户端一旦按某种格式解析,服务端从偏移量换成主键那天,所有客户端一起挂——这是把服务端的内部实现变成了公开契约。
- 第二个坑是伪造。客户端自己造一个 offset:9999 递给服务端,等于绕过了服务端对翻页范围的控制;如果游标里编了权限或过滤条件,伪造它就是一次越权。
- 第三个坑最阴:把空字符串当成结束。规范写死了只有 nextCursor **缺失**才代表没有下一页,空串是完全合法的游标。判错的表现是最后一页数据被静默丢掉,而且不报错,测试也很难发现。
- 结论:客户端对游标只允许做一个判断——nextCursor 在不在。页大小同理不得假设固定值,服务端随时可以改。非法游标服务端应当回 -32602,而不是静默返回第一页,否则客户端会陷进死循环。
- 可预期的追问:那服务端这边有什么坑?偏移量式游标要求列表顺序稳定,中途插入一条会让后面全部错位,所以要么先排序、要么把游标编成上一条的主键。
How to reason about it · think before answering
- It looks like a spec-recitation question but really tests whether you have shipped a paginated public API. Quoting the rule earns nothing; naming the concrete failure does.
- Start from what a cursor holds. A server may encode an offset, a primary key, a timestamp, or encrypted state, and it may change that at any time. A client that parses one format breaks everywhere the day the server switches, because parsing turned an internal detail into a public contract.
- Second failure is forgery. A client that fabricates offset:9999 bypasses the server's control over paging range, and if the cursor encodes filters or permissions, forging it is a privilege escalation.
- Third and nastiest: treating an empty string as the end. The spec is explicit that only a missing nextCursor ends the sequence; an empty string is a valid cursor. Getting this wrong silently drops the last page with no error, which tests rarely catch.
- Conclusion: a client may make exactly one judgment about a cursor — whether nextCursor is present. Page size likewise must not be assumed fixed. Servers should reject invalid cursors with -32602 rather than silently returning page one, which would loop the client forever.
- Likely follow-up: what bites the server side? Offset cursors require a stable ordering, since an insertion shifts everything after it, so either sort first or encode the last item's key instead.
答题要点
- 游标内容是服务端的内部实现,解析它等于把实现细节变成公开契约,服务端换实现时客户端全挂
- 伪造游标可以绕过服务端对翻页范围的控制,游标里若编了过滤或权限条件就是越权
- 只有 nextCursor 缺失才代表结束,空字符串是合法游标,判错会静默丢掉最后一页
- 页大小由服务端决定不得假设固定,非法游标服务端应回 -32602 而不是静默回第一页
Key points
- Cursor contents are server internals; parsing them turns an implementation detail into a public contract that breaks on any change
- Forged cursors bypass server-side paging control, and become privilege escalation if the cursor encodes filters or permissions
- Only a missing nextCursor ends the sequence — an empty string is valid, and getting it wrong silently drops the last page
- Page size is server-decided and must not be assumed fixed; invalid cursors should return -32602 rather than silently resetting
订阅流和请求内的进度通知在 HTTP 上都走 SSE,为什么 2026-07-28 规范要把它们分成两个通道?On HTTP both subscription streams and in-request progress notifications ride SSE, so why does the 2026-07-28 spec split them into two channels?
国内高频海外高频深入#subscriptions#notifications分析过程 · 先想清楚再作答
- 这题的区分度在版本认知。还在讲 resources/subscribe 和一条独立 GET 长连接的人会当场暴露——这两样在这一版被合并替换成了 subscriptions/listen。
- 先把事实摆清:subscriptions/listen 本身是一条普通请求,只是它的响应是一条一直开着的通知流;客户端在 notifications 过滤器里显式勾选 toolsListChanged、promptsListChanged、resourcesListChanged、resourceSubscriptions,服务端不得推送没勾选的类型;第一条消息必须是 acknowledged,之后每条通知在 _meta 里带 subscriptionId。
- 拆法:问两类通知的生命周期一样吗。进度和日志属于某一次具体请求,请求结束它们就该停;列表变更、资源更新属于整个连接期,跟任何单次请求都无关。生命周期不同的东西混在一条流里,取消语义就说不清——HTTP 上关闭响应流就是取消该请求,你不会希望取消一次工具调用顺带把订阅也掐了。
- 第二个理由是无状态与可路由。请求内通知天然跟着那条请求的响应流走,任意副本都能处理;订阅是唯一一条长活连接,把它单独隔出来,剩下的请求才能真正做到无粘性路由。
- 结论:订阅流回答「世界变了吗」,跨请求、长期存在;响应流回答「我这一单做到哪了」,随请求生随请求死。规范明确写了进度与日志通知不在订阅流上出现。
- 可预期的追问:日志通知现在怎么开?logging/setLevel 已删除,改为每请求在 _meta 的 logLevel 里指定,且服务端不得对没带这个字段的请求发日志通知;而且 Logging 连同 Roots、Sampling 一起已被标记弃用,建议迁到 stderr 或 OpenTelemetry。
How to reason about it · think before answering
- The discriminator is version awareness. Anyone still describing resources/subscribe and a standalone GET stream exposes themselves — both were replaced by subscriptions/listen in this revision.
- Get the facts straight first: subscriptions/listen is an ordinary request whose response is a stream that stays open. The client explicitly opts into toolsListChanged, promptsListChanged, resourcesListChanged, and resourceSubscriptions; the server must not push unselected types; the first message must be the acknowledgment, and every later notification carries subscriptionId in _meta.
- Then compare lifetimes. Progress and log notifications belong to one specific request and should stop when it ends. List changes and resource updates span the whole connection and relate to no single request. Mixing different lifetimes into one stream wrecks cancellation semantics, because closing a response stream on HTTP is the cancel signal — you do not want cancelling a tool call to kill your subscriptions.
- The second reason is statelessness and routability. In-request notifications naturally ride their own response stream so any replica can serve them; isolating the one genuinely long-lived connection is what lets every other request avoid sticky routing.
- Conclusion: the subscription stream answers has the world changed, spanning requests; the response stream answers how far along is my request, living and dying with it. The spec states outright that progress and message notifications never appear on the listen stream.
- Likely follow-up: how do you enable log notifications now? logging/setLevel was removed in favour of a per-request logLevel in _meta, and servers must not emit message notifications for requests that omit it. Logging is also deprecated alongside Roots and Sampling, with stderr or OpenTelemetry as the suggested migration.
答题要点
- 这一版用 subscriptions/listen 取代了 resources/subscribe 与独立的 GET 长连接,客户端显式勾选通知类型
- 两类通知生命周期不同:进度日志随请求生灭,列表与资源变更跨请求长期存在
- 混在一条流里会让取消语义失效,HTTP 上关闭响应流即取消该请求,不该顺带掐掉订阅
- 隔离出唯一的长活连接,其余请求才能无粘性路由,这是无状态设计能横向扩容的前提
Key points
- This revision replaced resources/subscribe and the standalone GET stream with subscriptions/listen, where clients explicitly opt into notification types
- The two kinds have different lifetimes: progress and logs live and die with a request, list and resource changes span the connection
- Merging them breaks cancellation, since closing a response stream on HTTP cancels that request and must not kill subscriptions
- Isolating the single long-lived stream is what lets every other request route without stickiness, enabling horizontal scaling
D4 远程 MCP:Streamable HTTP 绑定、无状态模型与请求元数据、OAuth 2.1 授权、容器部署
2026-07-28 去掉了协议级会话。那一个需要跨调用保存状态的远程服务端——比如购物车、数据库事务——应该怎么设计?The 2026-07-28 revision removed protocol-level sessions. How should a remote server that needs cross-call state — a shopping cart, a database transaction — be designed?
国内高频海外高频进阶#statelessness#api-design分析过程 · 先想清楚再作答
- 这题在筛「有没有把无状态当成设计约束」。答「用 Mcp-Session-Id 头」的当场出局,那个头这一版已经删了;答「存在服务端内存里按连接查」的同样出局,因为客户端根本不保证复用连接。
- 先给结构:状态必须由客户端携带,服务端只认请求里带来的东西。落地成两种形态——一是服务端铸造的显式句柄,创建工具返回一个 id,后续调用把它当普通工具参数传回来;二是签过名的不透明状态串,比如多轮请求里的 requestState,服务端把上下文签进去,重试时原样收回。
- 两者的差别在于「谁存数据」:句柄背后的购物车内容还是存在服务端的库里,句柄只是主键;requestState 是把上下文本身编码进字符串,服务端零存储。前者适合长期存在的业务对象,后者适合一次交互内的续接。
- 结论:不管哪种,服务端内存里都不为某个客户端留东西,所以任何副本都能处理任何请求,扩容不需要粘性路由——这正是这次改动想换来的东西。
- 安全是必须主动补的一句:句柄是名字不是凭证。要用安全随机数生成、绑定到已认证的主体(按 user_id 加 handle 做键)、设过期时间,并且每次调用重新校验调用者身份。规范明确写了服务端不得把持有句柄当成身份认证。requestState 同理,它经客户端转手,是攻击者可控输入,必须 HMAC 或 AEAD 验签,并把主体、原请求标识、短过期签进去。
- 可预期的追问:多副本时 requestState 怎么办?答案是所有副本共享签名密钥即可,这仍然是无状态的——状态在客户端手里,副本只负责验签。追问二可能是「怎么保证一次性」,答案是签名只能缩小重放窗口,真要单次消费得自己在服务端加一层消费记录。
How to reason about it · think before answering
- The screen is whether you treat statelessness as a design constraint. Answering 'use Mcp-Session-Id' fails immediately — that header was removed. So does 'keep it in server memory keyed by connection', since clients are not required to reuse connections.
- Give the structure first: state must travel with the client, and the server trusts only what arrives in the request. Two concrete shapes — a server-minted explicit handle returned by a creation tool and passed back as an ordinary tool argument, or a signed opaque blob like the requestState used by multi round-trip requests.
- The difference is who stores the data. A handle is just a primary key into server-side storage; a requestState encodes the context itself, so the server stores nothing. Handles suit long-lived business objects, requestState suits continuing a single interaction.
- Conclusion: either way the server keeps nothing per client in memory, so any replica can serve any request and scaling needs no sticky routing — which is exactly what the change was buying.
- Volunteer the security half: a handle is a name, not a credential. Generate it from a secure random source, bind it server-side to the authenticated principal (key storage as user id plus handle), expire it, and re-authorize on every call — the spec says possession of a handle must not be treated as authentication. requestState passes through the client, so it is attacker-controlled input and must be integrity-protected with HMAC or AEAD, carrying the principal, an originating-request identifier, and a short expiry.
- Likely follow-up: what about requestState across replicas? Share the signing key; it is still stateless because the state lives with the client and replicas only verify. A second follow-up is single use — signing bounds the replay window but does not guarantee one-time consumption, which needs a server-side redemption record.
答题要点
- 状态必须由客户端携带:服务端铸造显式句柄,作为普通工具参数在后续调用里传回
- 一次交互内的续接可以用签名的不透明状态串,服务端零存储,多副本共享签名密钥即可
- 句柄不是凭证:安全随机生成、绑定已认证主体、设过期,每次调用重新鉴权
- 收益是任何副本能处理任何请求,扩容不需要粘性路由,重启后重发即可
Key points
- State travels with the client: the server mints an explicit handle that later calls pass back as an ordinary tool argument
- Within one interaction, a signed opaque blob works with zero server storage; replicas just share the signing key
- A handle is not a credential: securely random, bound to the authenticated principal, expiring, re-authorized on every call
- The payoff is that any replica serves any request, so scaling needs no sticky routing and retries are cheap
Streamable HTTP 要求 Mcp-Method 头必须和请求体里的 method 一致。为什么要抄一遍?不校验会有什么风险?Streamable HTTP requires the Mcp-Method header to match the method in the request body. Why mirror it at all, and what breaks if the server does not validate the match?
国内高频海外高频进阶#transport#security分析过程 · 先想清楚再作答
- 这题的题眼在后半句。只答「方便网关路由」是答了一半,面试官等的是「不一致会怎样」——能不能自己举出攻击场景,是区分「读过规范」和「理解规范」的地方。
- 先说为什么镜像:中间层不该为了做决策去解析请求体。负载均衡想按方法分流、限流器想给 tools/call 单独设阈值、可观测探针想打标签,只看头就够了,不用把几十 KB 的 body 反序列化一遍。同理还有 Mcp-Name(取自 params.name 或 params.uri)和 MCP-Protocol-Version。
- 再推风险:既然中间层按头决策、服务端按体执行,两个事实来源就分叉了。举个具体的:网关配了「tools/list 免鉴权、tools/call 要鉴权」,攻击者把头写成 tools/list、体写成 tools/call,鉴权就被绕过去了。同样的套路可以绕限流、绕审计、绕按参数值做的地域隔离。
- 结论:所以规范规定处理请求体的服务端必须校验头体一致,不一致必须回 400 加 -32020(HeaderMismatch)。这不是格式洁癖,是把「两个事实来源」重新合并成一个。
- 实现上有个坑值得主动说:头值只能是可见 ASCII,非 ASCII 的工具名或资源 URI 要用 =?base64?...?= 哨兵格式编码,服务端必须先解码再比对,否则自己的校验会把正常请求判成不一致。整数值应当按数值比较而不是按字符串比较。
- 可预期的追问:中间层自己要不要校验?规范建议按头做策略的中间层先确认 MCP-Protocol-Version 指向的是一个要求头体校验的版本,版本更老或头缺失时应当直接拒绝,而不是信任未经校验的头值。
How to reason about it · think before answering
- The real question is the second half. 'It helps gateways route' is half an answer; the interviewer is waiting for a concrete attack, which separates having read the spec from having understood it.
- Why mirror: intermediaries should not parse the body to make decisions. A load balancer routing by method, a rate limiter capping tools/call, an observability probe tagging spans — all can read a header instead of deserializing tens of kilobytes. The same applies to Mcp-Name (from params.name or params.uri) and MCP-Protocol-Version.
- Then derive the risk: if intermediaries decide on the header and the server executes on the body, there are two sources of truth. Concretely, a gateway configured as 'tools/list is unauthenticated, tools/call is authenticated' is bypassed by sending the header as tools/list and the body as tools/call. The same trick evades rate limits, audit tagging, and per-parameter regional isolation.
- Conclusion: the spec therefore requires any server that processes the body to validate the match and reject with 400 plus -32020 (HeaderMismatch). It is not pedantry — it collapses two sources of truth back into one.
- Volunteer the implementation trap: header values are visible ASCII only, so non-ASCII tool names or resource URIs use the =?base64?...?= sentinel, and the server must decode before comparing or its own check will reject valid requests. Integer values should be compared numerically, not as strings.
- Likely follow-up: should intermediaries validate too? The spec advises that any intermediary enforcing policy from mirrored headers first confirm MCP-Protocol-Version names a revision that mandates header-body validation, and otherwise reject rather than trust unvalidated headers.
答题要点
- 镜像是为了让网关、限流器、探针不用解析请求体就能路由和打标签
- 不校验就有两个事实来源:头写 tools/list、体写 tools/call 可以绕过按方法配置的鉴权与限流
- 规范要求处理请求体的服务端必须校验一致性,不一致回 400 与 -32020
- 非 ASCII 值用 base64 哨兵格式,服务端必须先解码再比对;整数按数值比较
Key points
- Mirroring lets gateways, rate limiters, and probes route and tag without parsing the body
- Skipping validation creates two sources of truth: header tools/list with body tools/call bypasses per-method auth and limits
- The spec requires any body-processing server to validate the match and return 400 with -32020 on mismatch
- Non-ASCII values use the base64 sentinel, so decode before comparing; compare integers numerically
为什么 MCP 服务端绝对不能把客户端给的访问令牌直接转发给下游 API?Why must an MCP server never forward the client's access token straight to a downstream API?
国内高频海外高频深入#oauth#security分析过程 · 先想清楚再作答
- 这题在考安全边界的直觉。答「不安全」「会泄露」是空话;规范给这个反模式起了名字叫令牌转发(token passthrough),并明令禁止,能说出它坏在哪三处才算过关。
- 先把前提说清:MCP 服务端在授权体系里是 OAuth 2.1 的资源服务器,它必须校验收到的令牌受众就是自己(客户端靠 RFC 8707 的 resource 参数让授权服务器把受众写进令牌),并且必须只接受对自己资源有效的令牌,不得接受或转接其它令牌。
- 拆危害的角度是「谁的假设被打破了」。第一,绕过安全控制:限流、请求校验、流量监控往往挂在「这个令牌是发给我的」这个前提上,客户端拿着别处的令牌直连或经服务端转发,这些控制全空转。第二,审计链断裂:服务端分不清是哪个客户端在调(上游令牌对它可能是不透明的),下游日志里的身份又不是真正在转发的那个服务端,出事之后没人能还原现场;持有失窃令牌的人还能把服务端当成数据外泄的代理。第三,信任边界被打穿:下游是按「只有上游那个服务能拿到这个令牌」授信的,一旦某个服务被攻破,同一个令牌就能横着走。
- 结论:服务端要访问下游,就得自己作为 OAuth 客户端去拿一份属于自己的凭证,和客户端给自己的令牌完全隔离。
- 正确做法要一起说:需要代表用户访问第三方时走 URL 模式的补充输入,让用户在浏览器里直接和第三方完成授权,服务端把第三方令牌存在自己这边并绑定到已认证的用户身份。规范要求第三方凭证不得经由 MCP 客户端传输。
- 可预期的追问:那和混淆代理是什么关系?令牌转发是受众校验失败的下游后果,混淆代理是代理型服务端用静态 client id 加上跳过按客户端的同意确认造成的授权码劫持——两者都源于「服务端替别人做决定却没确认这个别人是谁」。这一条第 6 天会展开。
How to reason about it · think before answering
- This probes your instinct for trust boundaries. 'It is insecure' is empty; the spec names this anti-pattern token passthrough and forbids it, so you need the three concrete failure modes.
- Set up the premise: in the authorization model an MCP server is an OAuth 2.1 resource server. It must validate that tokens were issued with itself as the audience — clients make that possible via the RFC 8707 resource parameter — and must accept only tokens valid for its own resources, accepting or transiting nothing else.
- Derive the harm by asking whose assumption breaks. First, security controls are circumvented: rate limiting, request validation, and traffic monitoring hang off 'this token was issued to me', and a token minted elsewhere makes them no-ops. Second, the audit trail breaks: the server cannot distinguish clients when the upstream token is opaque to it, downstream logs show an identity that is not the forwarding server, and a thief of a stolen token can use the server as an exfiltration proxy. Third, the trust boundary is punctured: downstream grants trust on the assumption that only the upstream service holds the token, so one compromise travels sideways.
- Conclusion: to call downstream, the server must obtain its own credential as an OAuth client, fully isolated from the token the client presented to it.
- Give the correct pattern too: for third-party access on the user's behalf, use URL-mode elicitation so the user authorizes the third party directly in a browser, and the server stores those tokens bound to the authenticated user identity. The spec requires third-party credentials never to transit the MCP client.
- Likely follow-up: how does this relate to the confused deputy? Token passthrough is the downstream consequence of failed audience validation, while the confused deputy is authorization-code hijacking caused by a proxy server combining a static client id with skipped per-client consent. Both come from a server acting for someone without confirming who that someone is.
答题要点
- MCP 服务端是 OAuth 2.1 资源服务器,必须校验令牌受众是自己,不得接受或转接其它令牌
- 转发会绕过挂在受众上的限流、请求校验与流量监控
- 审计链断裂:服务端分不清调用方,下游看到的身份也不是真正的转发者,还可能被当成外泄代理
- 正确做法是服务端自己作为 OAuth 客户端取下游凭证,第三方凭证绝不经由 MCP 客户端
Key points
- An MCP server is an OAuth 2.1 resource server: it must validate that it is the token audience and must not accept or transit other tokens
- Forwarding bypasses rate limiting, request validation, and monitoring that assume audience-bound tokens
- The audit trail breaks: the server cannot identify callers, downstream sees the wrong identity, and the server can become an exfiltration proxy
- The correct pattern is for the server to obtain its own downstream credential as an OAuth client, with third-party credentials never transiting the MCP client
D5 写一个 MCP client:在自己的 Agent 循环里发现并调用工具、多 server 聚合与命名冲突
你的客户端同时连了五个 MCP 服务端,其中两个都有一个叫 search 的工具。合并成一张工具表给模型时,重名该怎么处理?为什么不能直接拿服务端名做前缀?Your client is connected to five MCP servers and two of them expose a tool called search. How do you merge them into one tool list for the model, and why can't you just prefix with the server's name?
国内高频海外高频进阶#client#tool-naming分析过程 · 先想清楚再作答
- 这题在筛「有没有真的聚合过多个服务端」。只答「加个前缀」能拿一半分,题眼在后半句——为什么不能用服务端自报的那个名字。
- 先把前提摆正:规范只保证工具名在**单个服务端内**唯一,并且明确说聚合多个服务端的客户端或代理可能遇到重名,应当实现一套消歧策略。也就是说重名不是异常情况,是设计上就允许的,消歧责任在客户端这一层,服务端管不着。
- 再答后半句:服务端在 serverInfo 里自报的 name **不保证跨服务端唯一**,规范明说不应当拿它来消歧。它由服务端自己填,两个不相干的服务端都叫 github 完全合法;更糟的是它是不可信输入,一个恶意服务端可以故意把自己报成别人的名字,让模型把请求发到错的地方。所以前缀必须来自客户端自己的配置——用户在配置文件里给每个服务端起的本地别名,别名重复时在启动阶段直接报错,因为那是配置错误。
- 接着说名字怎么拼。MCP 允许字母数字下划线连字符和点、长度建议 128 以内;模型 API 那边通常更严,比如只允许字母数字下划线连字符、最长 64。取交集,超长就截断并缀一段短哈希——要主动说出为什么加哈希:截断本身会制造新的重名,哈希是把唯一性补回来的。
- 结论也是最容易被追问的一条:客户端必须留一张反查表,从带前缀的名字映射回「哪个服务端 + 原来的工具名」。调用时发给服务端的必须是**原名**,服务端根本不认识带前缀的那个。绝不能靠切字符串反推,因为工具原名里本来就允许有下划线,截断过的名字更是拆不回来。
- 可预期的追问一:光靠名字前缀够不够?答不够,模型选工具看的是描述,所以还应当把来源写进描述里。追问二:工具列表变了怎么办?服务端支持 listChanged 时会发通知,客户端收到就重新拉取并重建反查表;同时注意频繁增删工具会打掉提示缓存,因为工具表在缓存前缀里。
How to reason about it · think before answering
- The screen is whether you have actually aggregated multiple servers. 'Add a prefix' is half the answer; the real question is the second half, why the server's own name will not do.
- Set the premise straight: the spec guarantees tool-name uniqueness only within a single server, and explicitly says clients or proxies that aggregate multiple servers may hit collisions and should implement a disambiguation strategy. Collisions are permitted by design, and disambiguation is the client's job.
- Now the second half: the name a server reports in serverInfo is not guaranteed to be unique across servers, and the spec says it should not be relied upon for disambiguation. The server fills it in itself, two unrelated servers may both call themselves github, and worse, it is untrusted input, so a malicious server can impersonate another. The prefix must come from the client's own configuration, a local alias the user assigns per server, with duplicate aliases rejected at startup as a configuration error.
- Then the naming mechanics. MCP allows letters, digits, underscore, hyphen and dot with a suggested 128-character limit; model APIs are usually stricter, often letters, digits, underscore and hyphen with a 64-character cap. Take the intersection, and on overflow truncate plus append a short hash — say why: truncation itself creates new collisions, and the hash restores uniqueness.
- The conclusion, and the most likely follow-up: keep a reverse map from the prefixed name back to server plus original tool name. The call sent to the server must carry the original name, since the server has never heard of the prefixed one. Never recover it by string splitting, because original names may legitimately contain underscores and truncated names cannot be split back at all.
- Likely follow-ups: is the prefix enough? No, the model chooses by description, so put the source in the description too. And what about list changes? Servers declaring listChanged send a notification, on which the client refetches and rebuilds the map, keeping in mind that churning the tool list invalidates prompt caching because the tool array sits in the cached prefix.
答题要点
- 唯一性只在单个服务端内成立,聚合时重名是设计允许的,消歧责任在客户端
- 前缀必须来自客户端配置的本地别名,服务端自报的 name 不保证唯一且是不可信输入
- 名字取 MCP 与模型 API 的字符集与长度交集,超长截断并缀短哈希补回唯一性
- 留一张反查表,调用时发原名;不能靠切字符串反推,工具原名里本来就有下划线
Key points
- Uniqueness holds only within one server; collisions are expected on aggregation and the client owns disambiguation
- The prefix must come from a client-configured local alias, since the server-reported name is neither unique nor trustworthy
- Build names from the intersection of MCP and model-API charset and length limits; truncate plus a short hash on overflow
- Keep a reverse map and send the original name on calls; never split the prefixed string, as original names contain underscores
线上一个 MCP 服务端超时了。你的 Agent 循环应该怎么反应?One of your MCP servers times out in production. How should your agent loop react?
国内高频海外高频进阶#client#reliability分析过程 · 先想清楚再作答
- 这题看的是工程直觉:能不能把「一个依赖挂了」和「这一轮对话失败」分开。答「重试三次」是把问题往后推了一步,面试官会立刻追问重试期间用户在等什么。
- 先分阶段。超时发生在两个完全不同的时刻:发现阶段(server/discover 或 tools/list)和调用阶段(tools/call)。两个阶段的正确反应不一样,混着答就会露怯。
- 发现阶段:逐个服务端 try/catch,失败的记进一张掉线表并继续下一个。整张工具表少几个工具,但循环照常起得来。记的必须是原因而不是一个布尔值,因为事后你要能回答少了什么、为什么少。
- 调用阶段:把失败翻译成一条 isError 为真的工具结果喂回模型,不要抛。理由是 MCP 本来就用 isError 表达「工具执行失败但协议是成功的」,模型看得见这句话就有机会换个工具或换个参数;抛出去只会把整轮对话打断,而且用户什么解释都得不到。
- 接着补三件配套的事。一是**每条请求都必须有超时**,stdio 上服务端不回你就永远不回;二是**幂等性决定能不能重试**,工具注解里的 idempotentHint 是提示不是保证,写操作的重试要靠客户端自己的去重键;三是**掉线要让用户看得见**,把掉线的服务端标在界面上或写进系统提示,否则模型会表现得像那个能力从来不存在,一本正经地说查不到。
- 结论:一个服务端超时,最坏的后果应该是少几个工具加一条明确的说明,而不是这一轮对话失败。
- 可预期的追问:要不要熔断?连续失败到阈值就把这个服务端标记为不可用一段时间,避免每一轮都白等一次超时;恢复用探活或下一次会话重连。再追问会问到超时值怎么定——按工具而不是按服务端定,一个跑三十秒的分析工具和一个查缓存的工具不该共用一个阈值。
How to reason about it · think before answering
- This probes engineering instinct: can you separate 'one dependency is down' from 'this turn fails'. Answering 'retry three times' just moves the problem, and the interviewer will ask what the user is staring at meanwhile.
- Split by phase first. A timeout happens at two very different moments: discovery (server/discover or tools/list) and invocation (tools/call). The correct reaction differs, and blurring them shows you have not built this.
- Discovery: wrap each server in its own try/catch, record the failure with its reason in a down list, and continue to the next server. The tool table loses a few entries but the loop still starts. Record the reason, not a boolean, because afterwards you must be able to say what is missing and why.
- Invocation: translate the failure into a tool result with isError true and feed it back to the model rather than throwing. MCP already uses isError for 'the tool failed but the protocol succeeded', so the model can switch tools or arguments; throwing kills the turn and leaves the user with no explanation.
- Then three supporting points. Every request needs a timeout, because on stdio a silent server is silent forever. Idempotency decides whether a retry is safe, and the idempotentHint annotation is a hint, not a guarantee, so writes need a client-side dedup key. And outages must be visible, surfaced in the UI or in the system prompt, or the model will behave as if the capability never existed and confidently report nothing found.
- Conclusion: the worst outcome of one server timing out should be a few missing tools plus an explicit note, never a failed turn.
- Likely follow-ups: should you add a circuit breaker? Yes, after consecutive failures mark the server unusable for a while so you stop paying a timeout every turn, with recovery by health check or reconnect on the next session. And how do you set the timeout? Per tool rather than per server, since a thirty-second analysis tool and a cache lookup should not share a threshold.
答题要点
- 分阶段:发现阶段逐个服务端 try/catch 记进掉线表并继续,调用阶段一律不抛
- 调用失败翻译成 isError 为真的工具结果喂回模型,让它换工具或换参数
- 每条请求必须设超时;能不能重试取决于幂等性,注解只是提示不是保证
- 掉线必须对用户和模型可见,否则会变成静默降级,模型会假装那个能力不存在
Key points
- Split by phase: per-server try/catch during discovery with a recorded reason, and never throw during invocation
- Translate call failures into isError tool results so the model can switch tools or arguments
- Every request needs a timeout; retry safety depends on idempotency, and the annotation is a hint, not a guarantee
- Outages must be visible to user and model, otherwise silent degradation makes the model deny the capability ever existed
把 MCP 的工具定义翻译成模型 API 的工具参数时,最容易丢掉的是什么?丢了会怎样?When translating an MCP tool definition into a model API's tool parameters, what is most easily lost, and what goes wrong when it is?
国内高频海外高频深入#client#tool-schema分析过程 · 先想清楚再作答
- 这题在考「你知不知道这一步是有损的」。答「字段名对一下就行」的人多半没写过客户端,因为 MCP 的工具定义里有好几样东西在模型 API 那边根本没有对应位置。
- 先列清单再讲后果。丢的主要是三样:annotations(readOnlyHint、destructiveHint、idempotentHint 这类行为提示)、outputSchema(结构化返回的形状)、title(给人看的名字)。另外还有一样常被忽略的是分页——只取 tools/list 第一页等于把后面的工具整批丢掉。
- 逐条讲后果。annotations 丢了,模型不知道哪个工具是破坏性的,客户端也就没法自动决定要不要弹确认框——所以确认逻辑必须由客户端按注解自己做,不能指望模型自觉。outputSchema 丢了,下游只能靠解析自然语言拿数据,而且做代码模式(让模型写代码调工具)时生成不出准确的返回类型。title 丢了,界面上只能显示一串带前缀的机器名。
- 这里必须主动补一句最重要的:**注解本身是不可信输入**。规范要求客户端把工具注解当成不可信的,除非来自可信服务端。readOnlyHint 为真不是「这个工具安全」的证明,它只是服务端的自我声明。所以注解可以用来决定 UI 上要不要多问一句,但不能拿它当权限判据。
- 结论:翻译这一步的正确心态是「知道自己丢了什么,并在客户端补回来」。补法是——确认与拦截由客户端按注解做、结构化返回自己校验、界面用 title 而给模型用 description、分页翻到 nextCursor 消失为止。
- 可预期的追问:description 要不要改写?可以适度加工,比如在前面缀一句来源说明帮助模型在重名时选对,但不要重写语义——描述是服务端作者调过的,也是他们唯一能影响模型选择的地方。再追问可能是「outputSchema 缺失怎么办」,官方建议先用泛型接住往下游传,真需要类型时用一个小模型做一次抽取并校验,别在循环里做。
How to reason about it · think before answering
- This checks whether you know the step is lossy. Anyone answering 'just map the field names' has probably not written a client, because several parts of an MCP tool definition have no home on the model-API side.
- List first, then consequences. Three things go missing: annotations (readOnlyHint, destructiveHint, idempotentHint), outputSchema, and title. A fourth, often overlooked, is pagination — taking only the first page of tools/list silently drops whole batches of tools.
- Consequences one by one. Without annotations the model cannot tell which tool is destructive and the client has nothing to base a confirmation prompt on, so confirmation logic must live in the client and read the annotations directly. Without outputSchema, downstream code parses natural language, and code-mode generation cannot produce accurate return types. Without title, the UI can only show a prefixed machine name.
- Volunteer the most important caveat: annotations are untrusted input. The spec requires clients to treat tool annotations as untrusted unless they come from trusted servers. readOnlyHint being true is not proof of safety, only the server's own claim, so annotations may drive whether you ask the user, never whether the caller is authorized.
- Conclusion: the right posture is to know exactly what you dropped and compensate in the client — confirmation driven by annotations, structured results validated by you, title for the UI and description for the model, and pagination followed until nextCursor disappears.
- Likely follow-ups: may you rewrite the description? Light augmentation is fine, such as prefixing the source to help disambiguate collisions, but do not rewrite the meaning, since the description is what the server author tuned and their only lever on model choice. And what if outputSchema is absent? The official guidance is to accept a generic type and move on, or extract a typed result with a fast model outside loops and validate it.
答题要点
- 丢的是 annotations、outputSchema、title,外加只取第一页时整批丢掉的工具
- annotations 丢了就没法决定要不要弹确认框,确认逻辑必须由客户端按注解自己做
- 注解是不可信输入,只能驱动 UI 提示,不能当权限判据
- outputSchema 丢了下游只能解析自然语言;description 可以缀来源但不要重写语义
Key points
- Annotations, outputSchema and title are lost, plus every tool past the first page if pagination is ignored
- Without annotations there is nothing to drive a confirmation prompt, so that logic must live in the client
- Annotations are untrusted input: they may drive UI prompts, never authorization decisions
- Losing outputSchema forces downstream natural-language parsing; you may prefix the description but must not rewrite its meaning
D6 安全与治理:工具描述里的提示注入、混淆代理、最小权限、审计日志与工具白名单
为什么说 MCP 工具的描述是不可信输入?作为客户端作者,你会做哪些防护?Why is an MCP tool's description untrusted input, and what protections would you build as a client author?
国内高频海外高频进阶#prompt-injection#client分析过程 · 先想清楚再作答
- 这题在筛「有没有把模型上下文当成一条数据入口来看」。答「加个过滤器拦关键词」会被追问到崩,因为基于文本的过滤挡不住改写。
- 先讲清为什么不可信。工具描述由服务端作者写,会原封不动进入给模型的工具表,和你自己写的系统提示处在同一个信任层级——没有引号、没有边界、没有来源标注。它的前置条件低到离谱:攻击者不需要凭证、不需要中间人、不需要用户点任何东西,只要能影响一段会被读进上下文的文本。三条现实路径是发一个服务端等人装、拿下已被信任服务端的发布权限在小版本里改一个字段、或者服务端本身干净但描述里嵌了从数据库读出来的内容。第二条最难防,因为用户只在安装时审过一遍,清单变更通知只说变了、不说哪句话变了。
- 顺手把注解也归进来:规范要求客户端必须把工具注解当成不可信输入,除非来自可信服务端。readOnlyHint 为真不是安全证明,只是服务端的自我声明。
- 然后是防护,关键是**给出顺序**:先挡后果,再挡入口。因为所有基于文本的防御都是概率性的,没有一条能保证挡住,而后果那一层是确定性的。
- 挡后果的三条:破坏性工具执行前一律向人确认,且确认框展示**实际参数**(规范建议把工具输入展示给用户,正是为了挡住工具名人畜无害但参数在外发数据这一类);界面上必须显示每一次工具调用,否则注入里那句「不要告诉用户」是真的会生效的;判据用本地策略为主、注解为辅——注解只能用来多拦一个,不能用来放行。
- 挡入口的三条:把描述当外部数据渲染,加来源标注与边界标记,并把边界符本身转义掉;工具返回同样处理,还要加长度上限;服务端清单变更时把描述的 diff 展示给用户复核,而不是只提示「工具列表变了」。
- 可预期的追问一:那能不能干脆让模型别听描述里的指令?只能降低概率,不能保证,所以它不能是唯一防线。追问二:工具返回算不算同一类问题?算,而且更严重,因为它每次都不一样、量更大;多服务端场景里官方还专门说过,一个服务端的结果对另一个服务端来说是不可信输入。
How to reason about it · think before answering
- The screen is whether you treat the model's context as a data ingress. Answering 'filter for keywords' collapses under follow-up, because text filters do not survive paraphrase.
- Establish why it is untrusted. The description is written by the server author and lands verbatim in the tool list handed to the model, at the same trust level as your own system prompt, with no quoting, boundary, or provenance. The precondition is absurdly low: no credentials, no man in the middle, no user click, just the ability to influence text that will be read into context. Three real paths are publishing a server and waiting for installs, taking over an already-trusted server's release rights and changing one field in a patch, or a clean server whose descriptions embed database content. The second is hardest to defend, since users audit only at install time and list-changed notifications say that something changed, not which sentence.
- Fold annotations in: the spec requires clients to treat tool annotations as untrusted unless they come from trusted servers. readOnlyHint being true is not proof of safety, only the server's own claim.
- Then the defenses, and the ordering is the point: block consequences first, entry second, because every text-based defense is probabilistic while the consequence layer is deterministic.
- Consequences: require human confirmation before destructive tools and show the actual arguments (the spec recommends showing tool inputs to the user precisely to catch an innocuous-looking tool exfiltrating via its arguments); render every tool call in the UI, or the injected 'do not tell the user' genuinely works; and decide what is destructive from local policy first, using annotations only to catch extra cases, never to waive one.
- Entry: render descriptions as external data with provenance and boundary markers, escaping the markers themselves; apply the same treatment plus a length cap to tool results; and on list changes show the user a diff of the descriptions rather than a bare 'the tool list changed'.
- Likely follow-ups: can you just instruct the model to ignore instructions in descriptions? That lowers the probability but cannot guarantee, so it must not be the only line. And do tool results count? Yes, and worse, because they change every call and are larger; official guidance also notes that one server's results are untrusted input to another.
答题要点
- 描述由服务端作者写、原样进上下文,和系统提示同一个信任层级,前置条件低到不需要任何凭证
- 注解同样不可信:规范要求客户端把注解当不可信输入,readOnlyHint 不是安全证明
- 防护顺序是先挡后果再挡入口:破坏性操作人工确认(展示实际参数)、界面显示每次调用
- 入口侧给描述与返回加来源标注与边界标记并转义边界符;清单变更时展示描述的 diff
Key points
- Descriptions are author-written, land verbatim in context at system-prompt trust level, and need no credentials to exploit
- Annotations are equally untrusted: the spec says treat them as such, and readOnlyHint proves nothing
- Order matters: block consequences first with human confirmation showing actual arguments, plus visible tool calls
- At the entry, wrap descriptions and results with provenance and escaped boundary markers, and diff descriptions on list changes
混淆代理攻击在 MCP 场景里具体是怎么发生的?规范要求怎么防?How does the confused deputy attack play out in an MCP setting, and what does the spec require to prevent it?
国内高频海外高频深入#oauth#confused-deputy分析过程 · 先想清楚再作答
- 这题在筛 OAuth 的实战经验。能背出「混淆代理就是代理被骗着用自己的权限做事」只算入门,面试官要的是这条链在 MCP 里的具体形状。
- 先把角色摆清:出事的是**代理型服务端**——它对 MCP 客户端是服务端,对第三方 API 是一个 OAuth 客户端。它自己不是被攻破的那个,它是被利用的那个。
- 然后列四个必须同时成立的条件,少一个就打不成:代理对第三方用**静态 client id**(所有用户共用一个);代理允许 MCP 客户端**动态注册**,各自拿到自己的 client id;第三方授权服务器在用户首次同意后**设了同意 cookie**;代理在转给第三方之前**没有做按客户端的同意确认**。
- 再串攻击链:攻击者先向代理动态注册一个客户端,redirect_uri 填自己的地址;把构造好的授权链接发给用户;用户浏览器带着上次留下的同意 cookie 去第三方,第三方认出静态 client id 加 cookie,**跳过同意页**直接发授权码;授权码回到代理,代理换成 MCP 授权码,按注册时那个恶意 redirect_uri 回跳,码落到攻击者手里;攻击者拿它换令牌,冒充用户访问。**整条链上用户什么都没同意过**——那个 cookie 是他上次正常授权时留下的。
- 防法要按规范的措辞答:代理型服务端**必须**实现按客户端的同意,而且这次同意必须发生在**转给第三方之前**。配套四条:同意记录按「用户加 client id」存,不是只记「这个用户同意过」;redirect_uri 精确字符串匹配、不做通配、改了就要重新注册;state 用安全随机数、单次使用、短过期,并且**同意通过之后才落 cookie 或会话**(提前落等于同意页形同虚设);同意页要有 CSRF 防护并禁止被 iframe 内嵌。
- 可预期的追问一:这和令牌转发什么关系?令牌转发是受众校验失败的下游后果,混淆代理是同意确认缺失造成的授权码劫持,根子都是「服务端替别人做了决定却没确认这个别人是谁」。追问二:我怎么知道自己要不要管这一节?判据一句话——我的服务端有没有替用户去第三方要过授权。没有就整节不适用,有就是必须做。
How to reason about it · think before answering
- This screens for hands-on OAuth. Reciting 'a deputy tricked into using its own authority' is entry level; the interviewer wants the concrete chain as it appears in MCP.
- Fix the roles first: the vulnerable party is a proxy server, which is a server to the MCP client and an OAuth client to the third-party API. It is not compromised, it is used.
- List the four conditions that must all hold: the proxy uses a static client id with the third party; the proxy lets MCP clients register dynamically, each with its own client id; the third-party authorization server sets a consent cookie after the first approval; and the proxy performs no per-client consent before forwarding.
- Then the chain: the attacker dynamically registers a client with their own redirect_uri, sends the user a crafted authorization link, the browser carries the old consent cookie to the third party, which recognizes the static client id plus cookie and skips the consent screen, the code returns to the proxy, the proxy mints an MCP authorization code and redirects to the attacker's registered URI, and the attacker exchanges it for tokens. The user consented to nothing in this flow; the cookie came from a legitimate earlier one.
- Answer the mitigation in the spec's own terms: proxy servers MUST implement per-client consent, and that consent must happen before forwarding to the third party. Four supporting requirements: store consent keyed by user plus client_id rather than 'this user consented'; match redirect_uri by exact string with no wildcards and require re-registration on change; make state cryptographically random, single use, short lived, and set its cookie or session only after consent is approved, since setting it earlier renders the consent screen ineffective; and protect the consent page with CSRF defenses and frame-ancestors or X-Frame-Options.
- Likely follow-ups: how does this relate to token passthrough? Passthrough is the downstream consequence of failed audience validation, while the confused deputy is code hijacking from missing consent; both stem from a server deciding on someone's behalf without confirming who that someone is. And how do I know whether this applies? One test: has my server ever obtained third-party authorization on a user's behalf.
答题要点
- 受害者是代理型服务端:对客户端是服务端,对第三方是一个 OAuth 客户端
- 四个条件同时成立才打得成:静态 client id、允许动态注册、第三方有同意 cookie、缺少按客户端的同意
- 攻击链的关键一步是第三方认出 cookie 跳过同意页,授权码按恶意 redirect_uri 落到攻击者手里
- 必须在转给第三方之前做按客户端的同意;redirect_uri 精确匹配;state 单次短过期且同意后才落
Key points
- The victim is a proxy server: a server to the MCP client, an OAuth client to the third party
- Four conditions must coincide: static client id, dynamic registration, a third-party consent cookie, and no per-client consent
- The pivot is the third party skipping consent on the cookie, sending the code to the attacker's redirect_uri
- Per-client consent must precede forwarding; redirect_uri matched exactly; state single use, short lived, and stored only after approval
2026-07-28 之后协议是无状态的,服务端要保存状态就得铸一个句柄让客户端带回来。这会带来什么新的攻击面?怎么防?Since the protocol is stateless, a server that needs state mints a handle for the client to carry back. What attack surface does that create, and how do you close it?
国内高频海外高频进阶#statelessness#security分析过程 · 先想清楚再作答
- 这题在考「换了机制之后有没有重新想过威胁模型」。上一版的会话劫持大家都熟,这一版会话没了,很多人就默认问题跟着消失了——其实只是换了个名字叫状态句柄劫持。
- 先描述攻击,四步很短:服务端为已认证用户铸一个句柄并放在工具结果里返回;攻击者拿到或猜到这个句柄;攻击者把它当成普通工具参数发过来;服务端没检查这个句柄属不属于调用者,于是操作了原用户的状态。
- 拆「拿到或猜到」这一层很关键,因为它决定了防线该架在哪。猜到,说明句柄可预测(自增 id、时间戳、短随机数);拿到,路径就多了——它出现在工具结果里,而工具结果会进模型上下文、会进日志、可能被另一个服务端看到,也可能被一次提示注入骗着吐出来。所以「句柄不会泄漏」这个假设不能要。
- 防线按规范分三层答。硬性的:实现了授权的服务端**必须**校验所有入站请求,并且**绝不能**把持有句柄当成身份认证——这是整题的题眼,句柄是名字不是凭证。应当层:用安全随机数生成,避免可预测或连续的标识,并设过期。最管用的一层也是应当:**在服务端把句柄绑定到已认证的主体**,比如存储的键做成「用户 id 加句柄」,用户 id 从校验过的令牌里取而不是客户端传,别的主体拿着同一个句柄来就查不到。这样即使猜中也冒充不了别人。
- 然后主动把 requestState 归到同一类:它是多轮请求里由服务端签发、经客户端转手带回的不透明状态,规范要求把它当成攻击者可控输入,用 HMAC 或 AEAD 做完整性保护、验签用定长比较,并把认证主体、原请求标识、短过期一起签进去,分别挡跨用户、跨请求和超时三种重放。
- 结论一句话:无状态没有消灭状态,只是把状态挪到了客户端手里,于是「谁能出示它」和「谁有权用它」必须被分开对待。
- 可预期的追问一:签名能不能保证一次性?不能,签名只缩小重放窗口,真要单次消费得在服务端加一层消费记录。追问二:多副本部署怎么办?句柄背后的数据本来就在共享存储里,requestState 只需要各副本共享签名密钥——这仍然是无状态的,因为服务端内存里没有为某个客户端留东西。
How to reason about it · think before answering
- This checks whether you re-derived the threat model after the mechanism changed. Everyone knows session hijacking from the previous revision; sessions are gone now, so many assume the problem left with them. It only got renamed to state handle hijacking.
- Describe the attack in four steps: the server mints a handle for an authenticated user and returns it in a tool result; the attacker obtains or guesses it; the attacker sends it back as an ordinary tool argument; the server never checks whether the handle belongs to the caller and operates on the original user's state.
- Unpack 'obtains or guesses', because it decides where the defense goes. Guessing means the handle is predictable, such as a sequential id, a timestamp, or too little entropy. Obtaining has many paths: the handle appears in a tool result, so it enters the model context, the logs, possibly another server's view, and it can be coaxed out by a prompt injection. The assumption that handles stay secret is not available to you.
- Answer the defenses in the spec's tiers. Mandatory: servers implementing authorization MUST verify all inbound requests and MUST NOT treat possession of a handle as authentication. That is the crux, a handle is a name, not a credential. Recommended: generate handles from a secure random source, avoid predictable or sequential identifiers, and expire them. The most effective recommendation is binding: key server-side storage as user id plus handle, with the user id derived from the verified token rather than supplied by the client, and reject a handle presented by any other principal, so guessing it still buys nothing.
- Volunteer that requestState belongs to the same family: a server-signed opaque blob carried back through the client in multi round-trip requests, which the spec requires you to treat as attacker-controlled input, protect with HMAC or AEAD, verify with a constant-time comparison, and bind to the authenticated principal, an originating-request identifier, and a short expiry, covering cross-user, cross-request, and timeout replay.
- One-line conclusion: statelessness did not remove state, it moved it into the client's hands, so 'who can present it' and 'who is allowed to use it' must be judged separately.
- Likely follow-ups: does signing guarantee single use? No, it only bounds the replay window; true one-time consumption needs a server-side redemption record. And what about replicas? The data behind a handle already lives in shared storage, and requestState only needs a shared signing key, which is still stateless because nothing per client sits in a replica's memory.
答题要点
- 新攻击面叫状态句柄劫持:拿到或猜到句柄的人可以操作别人的状态
- 句柄会出现在工具结果、上下文与日志里,不能假设它不泄漏
- 硬性要求:必须校验所有入站请求,绝不能把持有句柄当成身份认证
- 做法:安全随机、设过期、按「主体加句柄」在服务端绑定;requestState 同理,验签并签进主体与短过期
Key points
- The new surface is state handle hijacking: anyone who obtains or guesses a handle can act on another user's state
- Handles surface in tool results, model context and logs, so secrecy is not a safe assumption
- Mandatory: verify every inbound request and never treat possession of a handle as authentication
- Use secure randomness, expiry, and server-side binding keyed by principal plus handle; requestState needs signing bound to principal and a short expiry
D7 生产化与复盘:给工具写评估、版本化、发布到 npm 与 registry、可观测与综合项目
怎么评估一个 MCP 工具做得好不好?你会设计哪几类测试用例?How do you evaluate whether an MCP tool is any good, and what categories of test cases would you design?
国内高频海外高频进阶#eval#tooling分析过程 · 先想清楚再作答
- 这题在筛「有没有真的上线过工具」。答「写单元测试」是答错了赛道——单元测试测的是给定参数输出对不对,而 MCP 工具最先出问题的地方是模型压根没选它,参数根本到不了你的函数。
- 先把要评估的对象说清:评估评的是**选中率**,也就是给一句用户的话和一张工具表,模型会不会选中该选的那个。工具本身的正确性归单元测试,两层不要混。
- 然后给三类用例,这是本题的正面回答。正例:意图明确时选得中,比如「帮我搜一下发布流程文档」。边界:意图靠语义而不是关键词,比如「release-process 这篇讲了什么」——没有任何动词提示,只有一个像 slug 的词,最容易被误判成搜索。诱导误选(负例):不该调的时候一个都不调,比如「谢谢,不用查文档了」「文档这个词英文怎么说」「你都能干什么」。占比我一般给四三三。
- 第三类是分水岭,要主动强调:只有正例的评估集会给你一个 100% 的假象,它测不出过度触发,而线上大多数投诉恰恰是过度触发——用户随口一句否定,助手转头就去干了,还带副作用。
- 接着讲断言,这里有个最常见的写法错误:expected 为 null 的用例被当成「随便都行」,于是负例永远绿。正确的断言两个方向都判:期待某个工具时选中它才算过,期待不调用时什么都不选才算过。我会额外拿一个「总是选同一个工具」的假选择器再跑一遍,要求负例全部失败——这验的不是选择器,是我的断言真的在起作用。
- 最后是工程约束:评估集要能一条命令跑完,一分钟以内,用便宜的小模型跑。跑得慢的评估集等于没有,因为改描述的人不会等。结果也不要只看总分,按三类分开看才有行动价值:正例掉了说明描述写糊了,边界掉了说明缺了区分相似工具的那句话,负例掉了说明描述写得太热情。
- 可预期的追问:什么时候跑?描述、schema、工具增删这三类改动都必须跑,把它挂进 CI。再追问会问到「模型换了怎么办」,答案是评估集是跨模型的资产,换模型时先跑一遍拿到基线,这也是它值得投入的原因之一。
How to reason about it · think before answering
- The screen is whether you have shipped tools. 'Write unit tests' answers the wrong question: unit tests check that given arguments produce the right output, while the first thing to break on an MCP tool is the model not selecting it at all, so the arguments never reach your function.
- Define the target: an eval measures selection accuracy, meaning given a user utterance and a tool list, does the model pick the right tool. Correctness of the tool itself belongs to unit tests, and the two layers should not be blurred.
- Then the three categories, which is the direct answer. Happy path: unambiguous intent, such as 'find me the release process doc'. Edge: intent carried by semantics rather than keywords, such as 'what does release-process say', which has no verb cue and only a slug-shaped token, and is most often misread as a search. Traps: cases where nothing should be called, such as 'thanks, no need to look it up', 'how do you say document in English', and 'what can you do'. I usually weight them four, three, three.
- Stress that the third category is the dividing line: an eval set of only happy paths reports a comfortable hundred percent while measuring nothing about over-triggering, which is what most production complaints actually are, and over-triggering has side effects.
- Then the assertion, where the classic bug lives: treating an expected value of null as 'anything goes', which makes negatives permanently green. The correct assertion judges both directions. I also rerun the whole set with a deliberately wrong selector that always picks the same tool and require every negative to fail, which validates the assertion rather than the selector.
- Finally the engineering constraints: one command, under a minute, on a cheap small model, because a slow eval is no eval, since whoever edits a description will not wait. And report per category rather than one number: happy-path drops mean a vague description, edge drops mean the sentence distinguishing two similar tools is missing, trap drops mean the description over-claims.
- Likely follow-ups: when do you run it? On any change to descriptions, schemas, or the tool set, wired into CI. And what if the model changes? The eval set is a cross-model asset, so you rebaseline on a model switch, which is part of why it pays for itself.
答题要点
- 评估评的是选中率,不是工具正确性;后者归单元测试,两层不能混
- 三类用例缺一不可:正例、边界、诱导误选,建议四三三
- 断言两个方向都判:期待 null 时必须什么都不选;再用故意选错的选择器验证断言本身
- 一条命令一分钟内跑完,按类别分开看分数,描述与 schema 改动必须触发
Key points
- Evals measure selection accuracy, not tool correctness; the latter is unit-tested and the layers must not blur
- Three categories are mandatory: happy path, edge, and traps, weighted roughly four three three
- Assert both directions: an expected null must mean nothing was called, and validate the assertion with a deliberately wrong selector
- One command, under a minute, scored per category, and triggered by any description or schema change
给一个 MCP 服务端做版本管理时,什么样的改动算破坏性变更?为什么说改一句工具描述比改一个字段名更危险?When versioning an MCP server, what counts as a breaking change, and why is editing a tool description more dangerous than renaming a field?
国内高频海外高频进阶#versioning#tooling分析过程 · 先想清楚再作答
- 这题的前半句是常识题,后半句才是筛子。能把「描述也是接口」说明白的人,基本都真的运维过工具。
- 先答常规的四类,官方在讲扩展演进时给过定义,直接可用:删除或重命名字段、改字段类型、改变现有行为的语义、新增必填字段。这四类的共同点是会让已有实现直接失败或者行为不正确。
- 然后补 MCP 特有的第五类:**改工具描述**。理由是描述是模型选工具的唯一依据,改一句描述就是一次行为变更。举个具体的:某个服务端把描述从「在内部文档库里按关键词搜索」精简成「搜索文档」,代码一行没动,两周后用户反馈助手变笨了——模型不再选它了。
- 接着讲为什么它**更**危险,这是题眼:改字段名会让调用方立刻报错,错误是响亮的,五分钟内就有人来找你;改描述不报任何错,单元测试全绿、服务端零错误、日志干净,它只会让选中率悄悄掉几个点,最后以「最近变笨了」这种没法定位的形式浮上来。响亮的错误比安静的退化好处理得多,所以描述改动反而更需要闸门。
- 闸门是什么要说出来:一份三类齐全的评估集,描述改了必须跑一遍,选中率掉了就别合。这也是评估集要能一条命令快速跑完的原因。
- 顺带把兼容技巧补上:加字段要加成可选的,因为老客户端不会传新参数;要改语义就换个工具名而不是原地改,旧的标弃用、描述里写明替代品、留一段时间再删,因为你不知道多少人的提示词里写死了那个名字。
- 可预期的追问一:协议自己怎么做版本?MCP 用 YYYY-MM-DD,标的是最后一次破坏性变更的日期,向后兼容的改动不递增版本;弃用的特性至少保留十二个月才可能移除。追问二:发到注册表之后怎么改?改不了——版本号唯一且发布后元数据不可变,打错字只能往上加一个版本,而且范围形式的版本号会被直接拒收。
How to reason about it · think before answering
- The first half is common knowledge; the second half is the filter. Anyone who can explain that the description is part of the interface has actually operated tools in production.
- Give the four conventional categories, which the official guidance on extension evolution defines directly: removing or renaming fields, changing field types, altering the semantics of existing behavior, and adding new required fields. All four make existing implementations fail or behave incorrectly.
- Then add the MCP-specific fifth: editing a tool description. The description is the model's only basis for selecting a tool, so changing a sentence is a behavior change. Concretely, a server shortened 'search the internal doc library by keyword' to 'search documents', shipped no code changes, and two weeks later users reported the assistant had gotten dumber, because the model stopped choosing it.
- Now the crux, why it is more dangerous. Renaming a field makes callers fail loudly and someone finds you within five minutes. Editing a description raises nothing: unit tests pass, the server reports zero errors, logs are clean, and selection accuracy quietly drops a few points, surfacing weeks later as an undiagnosable 'it got worse'. Loud failures are far easier than silent degradation, so description changes need the stronger gate.
- Name the gate: an eval set with all three case categories, run on every description change, blocking the merge when selection accuracy drops. That is also why the eval must run fast from one command.
- Add the compatibility techniques: new fields must be optional, since old clients will not send them; to change semantics, introduce a new tool name rather than mutating in place, mark the old one deprecated with the replacement named in its description, and remove it only after a grace period, because you cannot know how many prompts hardcode that name.
- Likely follow-ups: how does the protocol version itself? MCP uses YYYY-MM-DD marking the last breaking change, backwards-compatible updates do not bump it, and deprecated features stay for at least twelve months before removal. And can you fix metadata after publishing to the registry? No: versions are unique and immutable once published, a typo costs a new version, and range-looking version strings are rejected outright.
答题要点
- 常规四类:删除或重命名字段、改字段类型、改变现有行为语义、新增必填字段
- 第五类是 MCP 特有的:改工具描述,因为描述是模型选工具的唯一依据
- 它更危险是因为不报错:测试全绿、日志干净,只有选中率悄悄下滑,几周后才浮上来
- 闸门是评估集;加字段要可选,改语义要换新工具名并给旧的一段弃用期
Key points
- The four usual categories: removing or renaming fields, changing types, altering semantics, adding required fields
- The MCP-specific fifth is editing a tool description, since the description is the model's only selection signal
- It is more dangerous because nothing fails: tests pass and logs are clean while selection accuracy silently drops
- The gate is the eval set; new fields must be optional, and semantic changes need a new tool name plus a deprecation window
线上有人反馈某个 MCP 工具「总是调不对」。你按什么顺序排查?A user reports that one of your MCP tools is 'always getting it wrong' in production. In what order do you investigate?
国内高频海外高频进阶#observability#debugging分析过程 · 先想清楚再作答
- 这题考的是排查的**顺序**,不是知识点的多少。上来就贴日志和堆栈的人会被追问「你怎么知道问题在服务端」。
- 第零步是把「调不对」翻译成三种互斥的现象,这一步不做后面全是猜:一是**没被调**(模型压根没选这个工具);二是**调了但参数错**;三是**调了参数也对,但结果不对**。问一句「那次它是没动,还是动了但做错了」,或者直接去日志里看有没有这条调用记录,就能分开。
- 对应三条不同的路。没被调,问题在**描述**:去跑评估集,看正例还是边界掉了;正例掉说明描述写糊,边界掉说明缺了区分相似工具的那句话。参数错,问题在 **schema**:看字段名是不是有歧义、描述里有没有写清格式、必填项是不是标对了;这类问题的信号是错误率里工具执行错误持续偏高——那通常不是模型笨,是 schema 没说清。结果不对才是代码问题,这时候才轮到单元测试和日志。
- 指标层面的顺序也说一下:**先看错误率,再看选中率**。错误率正常但用户说不好用,八成是选不中;错误率飙了才去看代码和上游。耗时看 P95 不看平均值,远程服务端上一个工具从 200 毫秒退化到 8 秒,平均值可能只动一点点。
- 还有两条容易被忽略但很常见的原因,要主动提。一是**聚合冲突**:客户端连了多个服务端,两个工具重名,模型选中的是另一个服务端的那个——这时候「你的工具」根本没被调,查你的服务端永远查不出来。二是**版本或缓存**:列表结果带 ttlMs 缓存提示,客户端可能拿着旧的工具清单;工具清单变了要靠 listChanged 通知才会重新拉。
- 结论:这条链上有四个环节——描述、schema、聚合与缓存、实现。**按模型看得见的顺序从前往后查**,因为越靠前的环节越不产生错误日志,也就越容易被跳过。
- 可预期的追问:怎么留证据?每次调用记一条结构化日志,字段里要有工具名、参数摘要与字段名、是否 isError、耗时、以及这次调用有没有经过人工确认;最后那一栏是事后区分「用户授意」和「模型自作主张」的唯一依据。
How to reason about it · think before answering
- This tests ordering, not breadth. Anyone who opens with logs and stack traces gets asked how they know the problem is server-side at all.
- Step zero is translating 'getting it wrong' into three mutually exclusive symptoms, without which everything after is guesswork: it was never called, it was called with wrong arguments, or it was called correctly and returned the wrong thing. Asking whether it did nothing or did the wrong thing, or simply checking whether a call was logged, separates them.
- Each symptom has its own path. Never called means the description is at fault: run the eval set and see whether happy paths or edges dropped, since happy-path drops mean a vague description and edge drops mean the sentence distinguishing similar tools is missing. Wrong arguments means the schema is at fault: ambiguous field names, unstated formats, wrong required markers. The signal is a persistently high tool-execution error rate, which usually means the schema is unclear rather than the model being dumb. Only a wrong result is a code problem, and only then do unit tests and logs matter.
- State the metric ordering too: error rate first, selection accuracy second. A normal error rate with unhappy users almost always means the tool is not being chosen; a spiking error rate sends you to the code and upstream. Watch P95, not the mean, because a remote tool degrading from 200 milliseconds to 8 seconds barely moves an average.
- Volunteer two commonly missed causes. Aggregation collisions: the client is connected to several servers, two tools share a name, and the model picked the other one, so your server was never called and investigating it will never find anything. And version or caching: list results carry ttlMs cache hints, so the client may hold a stale tool list, and refresh depends on a listChanged notification.
- Conclusion: the chain has four links, description, schema, aggregation and caching, and implementation. Walk it in the order the model sees it, because the earliest links produce no error logs and are therefore the ones people skip.
- Likely follow-up: how do you keep evidence? Emit one structured log per call with tool name, an argument digest plus field names, the isError flag, duration, and whether a human confirmed the call, that last column being the only way to distinguish user intent from the model acting on its own.
答题要点
- 先把「调不对」分成没被调、参数错、结果错三种互斥现象,再决定查哪里
- 没被调查描述并跑评估集;参数错查 schema;结果错才轮到代码与日志
- 指标顺序是先错误率再选中率;耗时看 P95 不看平均值
- 别漏掉聚合重名(选中的是别的服务端的同名工具)和工具清单缓存这两类原因
Key points
- First split 'getting it wrong' into never called, wrong arguments, or wrong result; the split decides where to look
- Never called points at the description and the eval set; wrong arguments at the schema; only a wrong result at the code
- Check error rate before selection accuracy, and read P95 rather than the mean
- Do not miss aggregation collisions, where another server's same-named tool was chosen, or a stale cached tool list