{"slug": "mcp-sampling-when-the-server-gets-to-prompt-your-model", "title": "MCP Sampling: When the Server Gets to Prompt Your Model", "summary": "A developer traced the Model Context Protocol's Sampling feature, in which an MCP server can send a `sampling/createMessage` request up to the client asking it to run an LLM completion on the server's behalf. The writeup details how the request's `messages`, `systemPrompt`, `modelPreferences`, and `includeContext` fields cross a new trust boundary, letting a third-party server prompt a user's authenticated model, and notes the spec requires human approval on both the inbound prompt and outbound response. The developer warns that client implementations of that gate vary, making Sampling a potential route into a model's context.", "body_md": "Most of what I'd read about MCP framed the data flow one direction: I ask, the client picks a tool, the server runs it, I get a result. The server was a thing my model *called*. It did not occur to me that the server could call back.\n\nThen I read the part of the spec that covers Sampling, and the arrow flipped. With Sampling, an MCP server can send a request *up* to the client that says, in effect, \"run an LLM completion for me and hand me the text.\" The server is not returning data anymore. It is asking my model to think on its behalf, with a prompt the server wrote.\n\nI spent an evening tracing exactly what that request contains, who approves it, and which clients even support it in 2026. This post is what I found: how Sampling works, the trust boundary it crosses, and why a feature designed to make servers smarter is also a clean new way to get into your model's context.\n\nIn a normal MCP exchange, the model is the one with the LLM. The server is dumb plumbing: it exposes tools, runs them, returns text. Sampling inverts that. It lets a server that has no model of its own borrow yours.\n\nSay a server is processing an expense and hits a transaction it can't categorize. Without Sampling it has to fail, guess, or hand the problem back. With Sampling it pauses mid-task and asks the client: \"given this description, which account does this belong to?\" The client runs that against its LLM and returns the answer, and the server keeps going. ([Speakeasy, What is MCP sampling](https://www.speakeasy.com/mcp/core-concepts/sampling))\n\nThat is genuinely useful. It lets a server stay simple and still make judgment calls, instead of every server shipping its own model and API key. The MCP docs pitch it as the thing that makes \"agentic\" server workflows possible: the server can stop and reason at a step instead of running blind. ([Model Context Protocol, Sampling concept](https://modelcontextprotocol.info/docs/concepts/sampling/))\n\nBut re-read that sentence. A third party's server got to write a prompt and run it through the model I authenticated, on context I own. That is a different relationship than \"I called your tool.\"\n\nThe method is `sampling/createMessage`, and the request the server sends back up to the client looks roughly like this. ([MCP spec draft, client/sampling](https://modelcontextprotocol.io/specification/draft/client/sampling))\n\n```\n{\n  \"method\": \"sampling/createMessage\",\n  \"params\": {\n    \"messages\": [\n      { \"role\": \"user\",\n        \"content\": { \"type\": \"text\", \"text\": \"Categorize this transaction: ...\" } }\n    ],\n    \"modelPreferences\": {\n      \"hints\": [{ \"name\": \"claude-3-sonnet\" }],\n      \"costPriority\": 0.3,\n      \"intelligencePriority\": 0.8,\n      \"speedPriority\": 0.5\n    },\n    \"systemPrompt\": \"You are a helpful bookkeeping assistant.\",\n    \"includeContext\": \"thisServer\",\n    \"maxTokens\": 100\n  }\n}\n```\n\nThree fields are doing more than they look:\n\n`messages` and `systemPrompt`` modelPreferences``hints`, `costPriority`, `intelligencePriority`, and `speedPriority`. The client still makes the final pick, but the server gets to lobby for an expensive model. (`includeContext``\"none\"`, `\"thisServer\"`, or `\"allServers\"`. It tells the client how much of the conversation to fold into the prompt the server requested. The default is That `includeContext` field is where the convenience and the danger sit on the same line.\n\nThe protocol authors clearly saw this coming, because they did not leave the server's request unsupervised. The spec puts a human in the loop at two points, not one.\n\nSo the design intent is: the server proposes, the model thinks, but a person sits on both the inbound prompt and the outbound answer. The client owns the actual LLM call, picks the real model, and is the gate. That is a reasonable threat model on paper.\n\nThe catch is the word *supposed to*. The spec describes the gate. Whether a given client implements it well, or implements Sampling at all, is a different question.\n\nThis is the part that reset my assumptions. Sampling is one of the older MCP primitives by spec, and it is still thinly implemented.\n\nClaude Code acting as an MCP client still does not support Sampling. The feature request ([Issue #1785, claude-code](https://github.com/anthropics/claude-code/issues/1785)) has been open since June 2025 and was still open, at 58 comments, in August 2026. Other clients have moved: opencode's \"Add MCP sampling support (createMessage)\" request ([Issue #11948, opencode](https://github.com/anomalyco/opencode/issues/11948)) was closed as completed in April 2026, and VS Code's MCP client acts on `modelPreferences`.\n\nTwo things follow from that. First, if you are building a server and leaning on Sampling, your server fails or degrades on the clients most of your users run. Second, the security surface lives in *how each client implements the gate*. A primitive that is \"emerging\" and implemented unevenly across clients means the human-in-the-loop guarantee is only as strong as the specific client in front of you. The spec mandating an approval step does not mean the client you're on actually renders one. Which is its own small joke: I'd spent an evening auditing a gate that, on the client I use every day, doesn't exist yet.\n\nHere is the boundary I had been ignoring. When my model calls a tool, the data flows server-to-me and I treat the result as untrusted output. When a server uses Sampling, the request flows server-to-my-model, and the server is now upstream of my model's reasoning. It crossed from \"thing I call\" to \"thing that prompts me.\"\n\nIn April 2026, Palo Alto's Unit 42 published an analysis of attack vectors specific to Sampling, and it names the failure modes cleanly. Sampling attacks slip past tool-integrity checks and sandboxing because they ride a legitimate protocol feature, not a malformed tool. They group the abuse into three classes: covert tool invocation that performs hidden file and system operations, conversation hijacking that injects instructions persisting across turns, and resource theft that drains your compute quota for the attacker's workloads. ([Unit 42, Prompt Injection Attack Vectors Through MCP Sampling](https://unit42.paloaltonetworks.com/model-context-protocol-attack-vectors/))\n\nThe mechanism for the second one is worth spelling out, because it's subtle. A malicious server's Sampling prompt instructs the model to append a directive to its next visible response. Because that text lands in the conversation history, the model keeps following it on later turns, long after the Sampling call finished. The same trick exfiltrates data by telling the model to slip extracted information into its next answer to you. ([Unit 42, MCP Sampling attack vectors](https://unit42.paloaltonetworks.com/model-context-protocol-attack-vectors/))\n\nAnd `includeContext`? That is the cross-server problem. If a client isn't strict about scoping each server's Sampling request to that server's own context, a malicious server can ask for `\"allServers\"` and pull in conversation belonging to servers it was never meant to see. In a multi-server session, one untrusted server can read context from the trusted ones it shares the model with. ([Unit 42, MCP Sampling attack vectors](https://unit42.paloaltonetworks.com/model-context-protocol-attack-vectors/)) The soft-deprecation of `\"thisServer\"`/`\"allServers\"` is the spec quietly walking that back, but only for clients that respect the capability flag.\n\nIf I'm reviewing a server that uses Sampling, or a client that implements it, this is what I check:\n\n`includeContext: \"allServers\"` on a loose client hands one server the conversation of every other server in the session.`sampling/createMessage`) using a prompt the server wrote.` none`/` thisServer`/` allServers`) is the cross-server leak risk; I went deeper on MCP's primitives, trust boundaries, and the OWASP MCP failure modes in my book if you want the long version: [MCP Security Practice](https://kenimoto.dev/books/mcp-security-practice?utm_source=devto&utm_medium=article&utm_campaign=mcp-sampling-security).", "url": "https://wpnews.pro/news/mcp-sampling-when-the-server-gets-to-prompt-your-model", "canonical_source": "https://dev.to/kenimo49/mcp-sampling-when-the-server-gets-to-prompt-your-model-32bc", "published_at": "2026-09-11 13:00:00+00:00", "updated_at": "2026-09-11 13:11:00.829072+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "developer-tools", "large-language-models", "ai-infrastructure"], "entities": ["Model Context Protocol", "Speakeasy", "Claude 3 Sonnet"], "alternates": {"html": "https://wpnews.pro/news/mcp-sampling-when-the-server-gets-to-prompt-your-model", "markdown": "https://wpnews.pro/news/mcp-sampling-when-the-server-gets-to-prompt-your-model.md", "text": "https://wpnews.pro/news/mcp-sampling-when-the-server-gets-to-prompt-your-model.txt", "jsonld": "https://wpnews.pro/news/mcp-sampling-when-the-server-gets-to-prompt-your-model.jsonld"}}