Files
claude-plugins-official/plugins/mcp-server-dev/skills/build-mcp-server/references/server-capabilities.md
Den Delimarsky 48a018f27a fix(plugin): mcp-server-dev — correct APIs against spec, add missing primitives
Corrects fabricated/deprecated APIs: ext-apps App class model (not embedded
resources), real MCPB v0.4 manifest (no permissions block exists), registerTool
(not server.tool), @anthropic-ai/mcpb package name, CIMD preferred over DCR.

Adds missing spec coverage: resources, prompts, elicitation (with capability
check + fallback), sampling, roots, tool annotations, structured output,
instructions field, progress/cancellation.
2026-03-18 22:53:38 +00:00

4.6 KiB

Server capabilities — the rest of the spec

Features beyond the three core primitives. Most are optional, a few are near-free wins.


instructions — system prompt injection

One line of config, lands directly in Claude's system prompt. Use it for tool-use hints that don't fit in individual tool descriptions.

const server = new McpServer(
  { name: "my-server", version: "1.0.0" },
  { instructions: "Always call search_items before get_item — IDs aren't guessable." },
);
mcp = FastMCP("my-server", instructions="Always call search_items before get_item — IDs aren't guessable.")

This is the highest-leverage one-liner in the spec. If Claude keeps misusing your tools, put the fix here.


Sampling — delegate LLM calls to the host

If your tool logic needs LLM inference (summarize, classify, generate), don't ship your own model client. Ask the host to do it.

// Inside a tool handler
const result = await extra.sendRequest({
  method: "sampling/createMessage",
  params: {
    messages: [{ role: "user", content: { type: "text", text: `Summarize: ${doc}` } }],
    maxTokens: 500,
  },
}, CreateMessageResultSchema);
# fastmcp
response = await ctx.sample("Summarize this document", context=doc)

Requires client support — check clientCapabilities.sampling first. Model preference hints are substring-matched ("claude-3-5" matches any Claude 3.5 variant).


Roots — query workspace boundaries

Instead of hardcoding a root directory, ask the host which directories the user approved.

const caps = server.getClientCapabilities();
if (caps?.roots) {
  const { roots } = await server.server.listRoots();
  // roots: [{ uri: "file:///home/user/project", name: "My Project" }]
}
roots = await ctx.list_roots()

Particularly relevant for MCPB local servers — see build-mcpb/references/local-security.md.


Logging — structured, level-aware

Better than stderr for remote servers. Client can filter by level.

// In a tool handler
await extra.sendNotification({
  method: "notifications/message",
  params: { level: "info", logger: "my-tool", data: { msg: "Processing", count: 42 } },
});
await ctx.info("Processing", count=42)   # also: ctx.debug, ctx.warning, ctx.error

Levels follow syslog: debug, info, notice, warning, error, critical, alert, emergency. Client sets minimum via logging/setLevel.


Progress — for long-running tools

Client sends a progressToken in request _meta. Server emits progress notifications against it.

async (args, extra) => {
  const token = extra._meta?.progressToken;
  for (let i = 0; i < 100; i++) {
    if (token !== undefined) {
      await extra.sendNotification({
        method: "notifications/progress",
        params: { progressToken: token, progress: i, total: 100, message: `Step ${i}` },
      });
    }
    await doStep(i);
  }
  return { content: [{ type: "text", text: "Done" }] };
}
async def long_task(ctx: Context) -> str:
    for i in range(100):
        await ctx.report_progress(progress=i, total=100, message=f"Step {i}")
        await do_step(i)
    return "Done"

Cancellation — honor the abort signal

Long tools should check the SDK-provided AbortSignal:

async (args, extra) => {
  for (const item of items) {
    if (extra.signal.aborted) throw new Error("Cancelled");
    await process(item);
  }
}

fastmcp handles this via asyncio cancellation — no explicit check needed if your handler is properly async.


Completion — autocomplete for prompt args

If you've registered prompts or resource templates with arguments, you can offer autocomplete:

server.registerPrompt("query", {
  argsSchema: {
    table: completable(z.string(), async (partial) => tables.filter(t => t.startsWith(partial))),
  },
}, ...);

Low priority unless your prompts have many valid values.


Which capabilities need client support?

Feature Server declares Client must support Fallback if not
instructions implicit — (always works)
Logging logging: {} stderr
Progress sends progressToken silently skip
Sampling sampling: {} bring your own LLM
Elicitation elicitation: {} return text, ask Claude to relay
Roots roots: {} config env var

Check client caps via server.getClientCapabilities() (TS) or ctx.session.client_params.capabilities (fastmcp) before using the bottom three.