MCP Servers Can Borrow Your LLM

Sampling is the half of MCP nobody uses: your server asks the client's model for help — no API key, no billing, no lock-in.

Share

MCP just published a new roadmap, and it's sitting on HN's front page right now. Good moment to fix a blind spot I keep running into: most MCP server authors build against half the protocol.

The spec has an arrow pointing the other way. Your server can request an LLM completion from the client. It's called sampling, it's been in the spec from early on, and almost nobody uses it.

Why this matters

The usual MCP server is dumb plumbing: fetch, grep, query, return. The moment you want a judgment call — rank these hits, summarize this log, classify this error — you reach for an OpenAI key. Now your server handles secrets, billing, and rate limits, and distribution gets worse. Nobody wants to paste an API key into your tool.

Sampling inverts the dependency. The client already has a model, a key, and a billing relationship. Your server just asks for help.

How it works

MCP is bidirectional JSON-RPC. The client calls your tools, but the server can also send requests upstream. Sampling is a sampling/createMessage request: you send messages, a required maxTokens, and optionally a system prompt plus modelPreferences hints.

The client asks the user for approval, runs its own model, and returns the completion. You never see a key. You can't force a model — hints only — which is a feature: the user's choice and their budget stay theirs. The same reverse channel powers elicitation, where the server asks the user for structured input mid-call.

Where this helps

  • Noise reduction. A code-search tool gets 500 grep hits, asks the model to keep the 10 that match intent, returns those. The user's context window thanks you.
  • Log triage. Compress 40KB of logs into 20 lines inside the tool instead of streaming raw output upstream.
  • Recursive agents. A deterministic pipeline needs one judgment call between steps — pass/fail on a diff, classify an error — without shipping a key.
  • Zero-key distribution. Ship one binary. No signup, no metering, no secret storage.

Watch out

Client support is uneven. Claude Desktop and Claude Code implement sampling; plenty of MCP clients ignore it entirely. Check clientCapabilities.sampling on connect and keep a fallback path.

Approval friction is real. Many clients prompt per request, so a sampling loop will annoy users in seconds. Batch your asks.

Treat sampled output as untrusted. The model isn't yours, and if your tool ingests web content, a prompt injection can round-trip through the user's session. Validate before acting on it.

Try it yourself

A server with one tool that delegates ranking to the client's model:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const mcp = new McpServer({ name: "sampler", version: "0.1.0" });

mcp.tool("rank", { hits: z.array(z.string()) }, async ({ hits }) => {
  const res = await mcp.server.createMessage({
    messages: [{
      role: "user",
      content: { type: "text",
        text: `Keep only the 10 most relevant results:\n${hits.join("\n")}` },
    }],
    maxTokens: 500,
    modelPreferences: { hints: [{ name: "claude-sonnet" }] },
  });
  return { content: [{ type: "text", text: res.content.text }] };
});

await mcp.connect(new StdioServerTransport());

Run it with npx @modelcontextprotocol/inspector node server.js — the inspector implements sampling, so you can test without Claude Desktop.

TL;DR

  • What: MCP servers can call sampling/createMessage to get completions from the client's own LLM.
  • Why: intelligent tools with no API keys, no billing, and the model choice stays with the user.
  • Today: add one createMessage call to a tool with noisy output and run it in the inspector.