#大模型#AI Agent#MCP

MCP Sampling Model Routing: Preference Mapping, Tool Loops, and Two-Stage Consent Receipts

`modelPreferences` does not specify a model, and Sampling results should not reach the Server without review. This article freezes the routing policy, actual model, tool loop, and two-stage request/result consent into a single delivery receipt.

Key Points

  • Topic Focus: 大模型、AI Agent、MCP
  • Suitable for: indie hackers, full-stack engineers, AI application developers
  • Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Author @admin·Published July 26, 2026·Updated July 26, 2026

A Server specified a model family in modelPreferences.hints, but the Client had integrated only another provider, so the router silently mapped the request to a more expensive model. The user approved the initial prompt but never saw the context automatically added by the Client or the tool calls initiated by the model. As soon as the final result returned, the Server treated it as an approved fact. The protocol permits abstract selection, but a production system must still explain exactly what was sent, which model was selected, how much it cost, and who approved delivery.

LLM Gateway Routing Policy handles provider selection, while Human Approval Contracts for Agents handles general tool authorization. This article brings them together around MCP sampling/createMessage: the Client retains control over model access, selection, and permissions, while the Server may only express preferences and cannot treat a hint as a hard-coded model ID.

Turn Routing Decisions into a Reproducible Contract

A Server may request Sampling only after the Client declares sampling during initialization. Requests involving tools also require the sampling.tools sub-capability. The thisServer and allServers values of includeContext are soft-deprecated and should not be used unless sampling.context has been negotiated. The Client first normalizes messages, systemPrompt, maxTokens, tools, toolChoice, and modelPreferences to generate a samplingRequest.v1 digest. samplingRoutePolicy.v1 then maps hints together with costPriority, speedPriority, and intelligencePriority to the set of models available at that time.

The routing receipt records the request digest, Server and user principals, session capability receipt, candidate-set snapshot, policy version, actual provider/model, pricing-table generation, data region, budget ceiling, context sources, and downgrade reason. A hint only affects ranking. Even an exact name match must pass tenant authorization, modality support, tool support, and cost gates. The capacity principles from Capability-Based Routing and Overload Control for Multi-Agent Systems can be reused, but Sampling must also return the actual model to the Server.

text
RECEIVED -> VALIDATED -> REQUEST_REVIEW
REQUEST_REVIEW -> REQUEST_APPROVAL_UNKNOWN | DECLINED | ROUTED -> MODEL_RUNNING
MODEL_RUNNING -> TOOL_USE_REVIEW -> RESPONSE_APPROVAL_UNKNOWN | TOOL_USE_DELIVERED
MODEL_RUNNING -> FINAL_REVIEW -> RESPONSE_APPROVAL_UNKNOWN | FINAL_DELIVERED | REJECTED
TOOL_USE_DELIVERED -> ROUND_CLOSED -> NEXT_REQUEST_RECEIVED

Both approval stages use the stable approvalId = (sessionGeneration, samplingRequestId, phase, revision). The ledger binds the complete prompt shown to the user, tool schemas, proposed model tier or response summary, cost ceiling, and expiration time to a PENDING -> APPROVED | DECLINED receipt. If the UI ACK is lost after the decision is persisted, the local state is marked UNKNOWN and queried using the same approval ID. The system must not generate a new ID and repeat the prompt, nor may it treat "the user may have clicked" as approval. If the user modifies the request digest, the revision must increase. Routing and model invocation are allowed only when a request-approval receipt exists.

When the model returns stopReason: toolUse, the Client reviews every unique tool use ID and delivers the CreateMessageResult to the Server only after a response-approval receipt exists. That round ends at this point; the Client does not execute tools on behalf of the Server. After executing the tools, the Server initiates a new sampling/createMessage request and includes the history and tool results in the next round. The new request has an independent JSON-RPC ID, digest, and approval, and is linked only through the conversation root and the previous delivery receipt.

For each new request, the Client validates its history as follows: every preceding assistant tool use must be matched one-to-one by ID in the immediately following user message containing only tool results. Tool results cannot be mixed with text or other content. The toolUse response currently awaiting delivery does not yet have results and must not be rejected as "unbalanced" for that reason. Each round is constrained by maximum iteration, total token, total cost, and wall-clock budgets. Tool execution also requires separate Server-side approval and cannot inherit the decision to "allow Sampling." Binding Approval Replay to Execution prevents old consent from being applied to a new prompt.

The sole delivery point for each request is deliverApprovedSamplingResult. The transaction matches the request digest, routing receipt, actual model, content validation, and the current round's APPROVED response receipt, then writes a delivery receipt and a unique response outbox entry. Nothing is delivered while approval is UNKNOWN. The toolUse branch validates the current IDs against the tool set before delivery and does not fabricate results that do not yet exist. Full pairing checks run only for the final branch and for incoming history in subsequent requests. If the user rejects the result, the Client returns an explicit error without exposing the model's original output to the Server. Schema Defenses for Agent Tool Outputs continues validating the content.

Fault injection covers unmatched hints, loss of a request-approval decision or UI ACK, prompt modification after approval, loss of a response-approval ACK, duplicate tool use IDs, loss of a toolUse delivery ACK, Server replay of the next round, history missing a tool result, out-of-order parallel results, session revocation, and model-name spoofing. Assertions require at most one decision per approval ID with no repeated prompts; model invocation and delivery must both remain at 0 until UNKNOWN is resolved; a valid current toolUse must not be blocked because it has no result yet; unbalanced history in a new request must be rejected; and an out-of-bounds loop must stop without exceeding the cost ceiling.

Release gates require 100% selection consistency across 10,000 routing replays. The rates of rejecting unnegotiated tool requests, advancing while approval is UNKNOWN, missing tool-use/result mismatches, and exceeding cost limits must all be 0. Every model invocation must be linked to request approval, every delivery to response approval, and the duplicate user-prompt rate must be 0. The human queue's p95 wait must remain below the business SLO. Rollout begins with shadow routing and an approval ledger, followed by 1% exposure for internal Servers. Rollback freezes the new policy while continuing to validate rounds that already have receipts. Approval cannot be transferred to a different model, prompt, or tool set.

Related

Related Content