逐日AI

面试题库

共 328 题,当前筛选 6 题。

7 天 MCP:把工具接进任何 Agent

D1 为什么需要一个协议:host / client / server 三角、JSON-RPC 消息与三种原语

  • 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

    分析过程 · 先想清楚再作答

    1. 这题的区分度在「知不知道这一版改了什么」。凭旧记忆答握手、会话标识、断流续传的人会当场暴露,因为这三样在这一版全被删了。
    2. 先说改了什么:没有 initialize 与 notifications/initialized,每条请求在 _meta 里自带协议版本与客户端能力;新增 server/discover 供客户端一次性取回版本、能力与身份,服务端必须实现它。
    3. 拆代价的角度是「省了什么、贵了什么」。贵的是报文:每条请求都要重复带版本与能力块。省的是三件事——任意副本都能处理请求所以扩容不用粘性路由、一条连接可以穿插无关请求、进程重启后在途请求重发即可。
    4. 结论:这是一次拿带宽换可伸缩性的交易,对本机 stdio 几乎无感,对多副本的远程部署收益很大。
    5. 状态怎么办:显式句柄。创建工具返回一个服务端铸造的 id,后续调用把它当普通参数传回来;服务端把状态按这个 key 存在自己的库里,并在工具描述里写清有效期。
    6. 可预期的追问:句柄安全吗?必须补一句——句柄是名字不是凭证,服务端每次都要重新校验调用者身份,句柄要用安全随机数生成、绑定到已认证的主体、并设过期时间。

    How to reason about it · think before answering

    1. 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.
    2. 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.
    3. 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.
    4. Conclusion: it trades bandwidth for scalability — near-invisible on local stdio, valuable for multi-replica remote deployments.
    5. 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.
    6. 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 调试

  • 一个 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

    分析过程 · 先想清楚再作答

    1. 这题在验有没有真跑过。没实际接过的人会答「进程没起来」「路径不对」这类泛泛的,真踩过的人第一句就会说标准输出被污染。
    2. 拆法:先复述 stdio 的硬规矩——消息是一行一条换行分隔的 JSON、内部不许有裸换行,服务端不得往标准输出写任何不是 MCP 消息的东西,日志一律走标准错误。规矩一说完,翻车原因就自明了。
    3. 现场特征值得单独说,因为它是这题的区分点:客户端只会报一句 JSON 解析失败,指不到你哪一行 console.log;而且污染源常常不是你自己的代码,而是某个第三方库在启动时打的横幅或弃用警告。
    4. 结论分两层。代码上:封一个只写标准错误的日志函数并全局禁用直接打印,接第三方库之前先确认它不往标准输出写东西,把 JSON 序列化后确保不含裸换行。流程上:加一个自测入口,用内存传输在同一个进程里把客户端和服务端接起来跑断言,有明确退出码,进持续集成——这样污染一出现就会在合并前被拦下。
    5. 再补一条相关的:优雅停机。规范说客户端关掉输入流、服务端读到文件结束就应尽快退出,这是主要且唯一可移植的停机信号;不处理它就会留下孤儿进程,本机开发时表现为端口和文件锁莫名被占。
    6. 可预期的追问:既然这么脆,为什么还用 stdio?因为它零配置、零网络攻击面、进程隔离天生就有,本机场景收益远大于代价;要给团队共享或多副本才需要换成远程传输。

    How to reason about it · think before answering

    1. 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.
    2. 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.
    3. 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.
    4. 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.
    5. 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.
    6. 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 模板、变更通知、进度与日志、分页,以及客户端能力

  • 订阅流和请求内的进度通知在 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

    分析过程 · 先想清楚再作答

    1. 这题的区分度在版本认知。还在讲 resources/subscribe 和一条独立 GET 长连接的人会当场暴露——这两样在这一版被合并替换成了 subscriptions/listen。
    2. 先把事实摆清:subscriptions/listen 本身是一条普通请求,只是它的响应是一条一直开着的通知流;客户端在 notifications 过滤器里显式勾选 toolsListChanged、promptsListChanged、resourcesListChanged、resourceSubscriptions,服务端不得推送没勾选的类型;第一条消息必须是 acknowledged,之后每条通知在 _meta 里带 subscriptionId。
    3. 拆法:问两类通知的生命周期一样吗。进度和日志属于某一次具体请求,请求结束它们就该停;列表变更、资源更新属于整个连接期,跟任何单次请求都无关。生命周期不同的东西混在一条流里,取消语义就说不清——HTTP 上关闭响应流就是取消该请求,你不会希望取消一次工具调用顺带把订阅也掐了。
    4. 第二个理由是无状态与可路由。请求内通知天然跟着那条请求的响应流走,任意副本都能处理;订阅是唯一一条长活连接,把它单独隔出来,剩下的请求才能真正做到无粘性路由。
    5. 结论:订阅流回答「世界变了吗」,跨请求、长期存在;响应流回答「我这一单做到哪了」,随请求生随请求死。规范明确写了进度与日志通知不在订阅流上出现。
    6. 可预期的追问:日志通知现在怎么开?logging/setLevel 已删除,改为每请求在 _meta 的 logLevel 里指定,且服务端不得对没带这个字段的请求发日志通知;而且 Logging 连同 Roots、Sampling 一起已被标记弃用,建议迁到 stderr 或 OpenTelemetry。

    How to reason about it · think before answering

    1. 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.
    2. 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.
    3. 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.
    4. 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.
    5. 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.
    6. 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 授权、容器部署

  • 为什么 MCP 服务端绝对不能把客户端给的访问令牌直接转发给下游 API?Why must an MCP server never forward the client's access token straight to a downstream API?
    国内高频海外高频深入#oauth#security

    分析过程 · 先想清楚再作答

    1. 这题在考安全边界的直觉。答「不安全」「会泄露」是空话;规范给这个反模式起了名字叫令牌转发(token passthrough),并明令禁止,能说出它坏在哪三处才算过关。
    2. 先把前提说清:MCP 服务端在授权体系里是 OAuth 2.1 的资源服务器,它必须校验收到的令牌受众就是自己(客户端靠 RFC 8707 的 resource 参数让授权服务器把受众写进令牌),并且必须只接受对自己资源有效的令牌,不得接受或转接其它令牌。
    3. 拆危害的角度是「谁的假设被打破了」。第一,绕过安全控制:限流、请求校验、流量监控往往挂在「这个令牌是发给我的」这个前提上,客户端拿着别处的令牌直连或经服务端转发,这些控制全空转。第二,审计链断裂:服务端分不清是哪个客户端在调(上游令牌对它可能是不透明的),下游日志里的身份又不是真正在转发的那个服务端,出事之后没人能还原现场;持有失窃令牌的人还能把服务端当成数据外泄的代理。第三,信任边界被打穿:下游是按「只有上游那个服务能拿到这个令牌」授信的,一旦某个服务被攻破,同一个令牌就能横着走。
    4. 结论:服务端要访问下游,就得自己作为 OAuth 客户端去拿一份属于自己的凭证,和客户端给自己的令牌完全隔离。
    5. 正确做法要一起说:需要代表用户访问第三方时走 URL 模式的补充输入,让用户在浏览器里直接和第三方完成授权,服务端把第三方令牌存在自己这边并绑定到已认证的用户身份。规范要求第三方凭证不得经由 MCP 客户端传输。
    6. 可预期的追问:那和混淆代理是什么关系?令牌转发是受众校验失败的下游后果,混淆代理是代理型服务端用静态 client id 加上跳过按客户端的同意确认造成的授权码劫持——两者都源于「服务端替别人做决定却没确认这个别人是谁」。这一条第 6 天会展开。

    How to reason about it · think before answering

    1. 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.
    2. 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.
    3. 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.
    4. 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.
    5. 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.
    6. 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 的工具定义翻译成模型 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

    分析过程 · 先想清楚再作答

    1. 这题在考「你知不知道这一步是有损的」。答「字段名对一下就行」的人多半没写过客户端,因为 MCP 的工具定义里有好几样东西在模型 API 那边根本没有对应位置。
    2. 先列清单再讲后果。丢的主要是三样:annotations(readOnlyHint、destructiveHint、idempotentHint 这类行为提示)、outputSchema(结构化返回的形状)、title(给人看的名字)。另外还有一样常被忽略的是分页——只取 tools/list 第一页等于把后面的工具整批丢掉。
    3. 逐条讲后果。annotations 丢了,模型不知道哪个工具是破坏性的,客户端也就没法自动决定要不要弹确认框——所以确认逻辑必须由客户端按注解自己做,不能指望模型自觉。outputSchema 丢了,下游只能靠解析自然语言拿数据,而且做代码模式(让模型写代码调工具)时生成不出准确的返回类型。title 丢了,界面上只能显示一串带前缀的机器名。
    4. 这里必须主动补一句最重要的:**注解本身是不可信输入**。规范要求客户端把工具注解当成不可信的,除非来自可信服务端。readOnlyHint 为真不是「这个工具安全」的证明,它只是服务端的自我声明。所以注解可以用来决定 UI 上要不要多问一句,但不能拿它当权限判据。
    5. 结论:翻译这一步的正确心态是「知道自己丢了什么,并在客户端补回来」。补法是——确认与拦截由客户端按注解做、结构化返回自己校验、界面用 title 而给模型用 description、分页翻到 nextCursor 消失为止。
    6. 可预期的追问:description 要不要改写?可以适度加工,比如在前面缀一句来源说明帮助模型在重名时选对,但不要重写语义——描述是服务端作者调过的,也是他们唯一能影响模型选择的地方。再追问可能是「outputSchema 缺失怎么办」,官方建议先用泛型接住往下游传,真需要类型时用一个小模型做一次抽取并校验,别在循环里做。

    How to reason about it · think before answering

    1. 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.
    2. 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.
    3. 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.
    4. 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.
    5. 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.
    6. 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 场景里具体是怎么发生的?规范要求怎么防?How does the confused deputy attack play out in an MCP setting, and what does the spec require to prevent it?
    国内高频海外高频深入#oauth#confused-deputy

    分析过程 · 先想清楚再作答

    1. 这题在筛 OAuth 的实战经验。能背出「混淆代理就是代理被骗着用自己的权限做事」只算入门,面试官要的是这条链在 MCP 里的具体形状。
    2. 先把角色摆清:出事的是**代理型服务端**——它对 MCP 客户端是服务端,对第三方 API 是一个 OAuth 客户端。它自己不是被攻破的那个,它是被利用的那个。
    3. 然后列四个必须同时成立的条件,少一个就打不成:代理对第三方用**静态 client id**(所有用户共用一个);代理允许 MCP 客户端**动态注册**,各自拿到自己的 client id;第三方授权服务器在用户首次同意后**设了同意 cookie**;代理在转给第三方之前**没有做按客户端的同意确认**。
    4. 再串攻击链:攻击者先向代理动态注册一个客户端,redirect_uri 填自己的地址;把构造好的授权链接发给用户;用户浏览器带着上次留下的同意 cookie 去第三方,第三方认出静态 client id 加 cookie,**跳过同意页**直接发授权码;授权码回到代理,代理换成 MCP 授权码,按注册时那个恶意 redirect_uri 回跳,码落到攻击者手里;攻击者拿它换令牌,冒充用户访问。**整条链上用户什么都没同意过**——那个 cookie 是他上次正常授权时留下的。
    5. 防法要按规范的措辞答:代理型服务端**必须**实现按客户端的同意,而且这次同意必须发生在**转给第三方之前**。配套四条:同意记录按「用户加 client id」存,不是只记「这个用户同意过」;redirect_uri 精确字符串匹配、不做通配、改了就要重新注册;state 用安全随机数、单次使用、短过期,并且**同意通过之后才落 cookie 或会话**(提前落等于同意页形同虚设);同意页要有 CSRF 防护并禁止被 iframe 内嵌。
    6. 可预期的追问一:这和令牌转发什么关系?令牌转发是受众校验失败的下游后果,混淆代理是同意确认缺失造成的授权码劫持,根子都是「服务端替别人做了决定却没确认这个别人是谁」。追问二:我怎么知道自己要不要管这一节?判据一句话——我的服务端有没有替用户去第三方要过授权。没有就整节不适用,有就是必须做。

    How to reason about it · think before answering

    1. 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.
    2. 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.
    3. 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.
    4. 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.
    5. 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.
    6. 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