From claude-code-meta
This skill should be used when the user asks to "design an MCP server", "audit an MCP server", "review MCP tools", "add MCP tool", "improve tool descriptions", "design tool surface", "add submit_feedback tool", "review tool schema", mentions "MCP transport", "tool annotations", "mcp stdio", "MCP Resources", "MCP Prompts", or is designing, reviewing, or auditing any MCP server. Covers design philosophy, tool naming, parameter schemas, agent UX, feedback channel, transport, security, and client compatibility. NOT for hands-on implementation from scratch (use mcp-builder for that).
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-code-meta:mcp-server-designThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
SDK setup, scaffolding, language idioms → [`mcp-builder`](https://github.com/anthropics/skills/tree/main/skills/mcp-builder).
examples/long-running-tasks-wire-shape.mdexamples/structured-output-search-orders.mdreferences/agent-ux.mdreferences/audit-checklist.mdreferences/clients.mdreferences/daemon-architecture.mdreferences/design-philosophy.mdreferences/feedback-tool.mdreferences/gateway-aggregation.mdreferences/observability.mdreferences/security-threats.mdreferences/tool-design.mdSDK setup, scaffolding, language idioms → mcp-builder.
This skill intentionally mixes universal MCP guidance with narrower production recipes. Treat the tags below as binding when applying the references:
[STACK:label] where label names the specific stack (e.g. [STACK:Python], [STACK:stateful-backends])When in doubt, enforce UNIVERSAL rules first. Do not treat OPINIONATED or STACK-SPECIFIC recipes as protocol requirements.
| Term | Design meaning |
|---|---|
stdio / Streamable HTTP | The two MCP transports. Pick by who-launches-whom — see §Transport. |
| Resource | A primitive selected and injected by the client, not invoked by the model. Use when context is stable and URI-addressable. → tool-design.md §Picking a Primitive |
| Prompt | A primitive the user invokes by name (slash command in Claude Code). Use for reusable workflows the user explicitly triggers. → tool-design.md §Picking a Primitive |
outputSchema + structuredContent | Server contract: declaring outputSchema makes structuredContent MUST on every successful call. Declare for any tool returning machine-parseable data; always pair with a compact text preview. Owner for nullable / null-arm / additionalProperties rules: tool-design.md §Schema Compatibility. |
isError | The right channel for business / validation errors (agent self-corrects). Protocol exceptions are for malformed requests, not domain failures. |
| Tasks (SEP-1686) | Spec primitive for long-running ops. Status (verified 2026-05-22): no tracked client confirmed negotiating it — matrix shows ⚠️ not declared / unverified across both Claude clients (clients.md). Working default is the roll-your-own async handle (submit tool returns {id, status: "working"}; separate polling tool returns terminal state). Switch to the spec primitive when the matrix flips. Wire shape: examples/long-running-tasks-wire-shape.md. Decision tree: tool-design.md §Long-Running Operations. |
_meta | Spec-defined open-ended object on requests / results / tool definitions. Vendor-specific knobs ride here when the spec hasn't standardised them yet — e.g. Claude Code's anthropic/maxResultSizeChars is a namespaced field on the tool definition itself, not inside _meta (verify exact placement against the Claude Code MCP docs for your version). When in doubt: tool-level knobs go on the tool definition; per-call hints go in request _meta; per-result hints in result _meta. |
server.instructions | Server-declared system prompt — a config surface for shaping agent behaviour without adding tools. Keep it tight; budget rules in agent-ux.md. SDK wiring (verified): FastMCP instructions= constructor arg; TypeScript SDK Server instructions field at construction. Other SDKs (Go, Rust, low-level Python mcp) expose the same instructions field on the server constructor — check the SDK's Server/McpServer reference. Caveat: MCP gateways/aggregators may replace or drop upstream server instructions; load-bearing rules must also be recoverable from tool descriptions, schemas, errors, or results. |
Tool annotations | Posture hints (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) — advisory, not security. title is top-level on the Tool object per spec, not inside annotations. Asymmetric default: destructiveHint defaults to true (opt-out) but is only meaningful when readOnlyHint: false — on read-only tools it's ignored. Forgetting to set it to false on an additive write (e.g. submit_feedback) silently marks the tool destructive. → tool-design.md §Annotations. |
posture (primary / secondary) | Project-level classification used by this skill (not a protocol field — secondary tools still appear in tools/list and count for the client). Primary = user-facing capability; secondary = plumbing (health, polling/status pairs, submit_feedback). Drives the ≤10 design signal, not a runtime filter. |
resource_link | Tool result type that returns a URI pointer instead of inlining content. Design use: large payloads, already-addressable resources. Not guaranteed to appear in resources/list. |
icons | Optional icon array on Tool/Resource/Prompt (SEP-973, spec 2025-11-25). Pure presentation — no client tracked in clients.md is known to render them today. Design implication only when targeting clients that confirm rendering — don't invest in icon assets ahead of that confirmation. |
References verified as of 2026-05-24; per-file recheck dates inside clients.md (the freshness-sensitive file).
Designing a new server. Minimum viable spine (read these): design-philosophy → tool-design → clients. Add when the server takes that shape: agent-ux once you start writing descriptions / server.instructions; feedback-tool only if you're adopting that pattern (when-not-to-use); security-threats before exposing on a network; observability before first production deploy. Stack-conditional: daemon-architecture (stateful backends), gateway-aggregation (multi-server-behind-one-edge).
Auditing an existing server: start at audit-checklist.md and jump to the linked ref on each ❌ finding.
| Reference | Scope | Content |
|---|---|---|
| design-philosophy.md | UNIVERSAL | "Not an API wrapper" principles, antipatterns, Bad vs Good comparisons |
| tool-design.md | UNIVERSAL | Naming, classification, annotations, outputSchema, parameters, pagination, long-running ops |
| agent-ux.md | UNIVERSAL + OPINIONATED | System prompt, dark-room testing, Action: error hints |
| feedback-tool.md | OPINIONATED | submit_feedback interface, CLI contract, data model, when-not-to-use |
| security-threats.md | UNIVERSAL | Prompt injection, authn/authz, sessions, DoS, secrets, supply chain |
| observability.md | UNIVERSAL + OPINIONATED | Per-call logging schema, storage patterns, privacy rules, report templates |
| clients.md | EMPIRICAL | Claude Desktop, Claude Code capabilities + timeouts + cross-client matrix — verified 2026-04-28 / 2026-05-22 |
| audit-checklist.md | MIXED | 16-section, ~80-item checklist; items tagged; HIGH/MEDIUM/LOW output |
| daemon-architecture.md | STACK:stateful-backends | Daemon + on-demand split, Unix socket, crash isolation, plus the stderr-rule inversion under this pattern. Skip on stateless servers — the UNIVERSAL stderr rule is in §Transport below. |
| gateway-aggregation.md | STACK:remote-multi-server | Docker MCP Gateway, shared OAuth edge, tool-surface curation |
→ Full philosophy, antipatterns, concrete Bad vs Good comparisons: references/design-philosophy.md
Choose the right primitive first:
Tool rules:
snake_case, verb_noun — list_dialogs, get_entity_info, submit_feedbacktitle: include on every tool [OPINIONATED] — spec marks it optional; in practice clients display it as user-facing prose. Top-level on the Tool object (not inside annotations). 1–3 words, product language, sentence case ("Search messages", not search_messages)icons: optional {src, mimeType, sizes?[]} array on Tool/Resource/Prompt (SEP-973, 2025-11-25)primary (user-facing) or secondary/helper (plumbing)readOnlyHint, destructiveHint, idempotentHint, openWorldHintoutputSchema on structured tools — when declared, MUST return structuredContent on every call (see glossary + tool-design.md)isError: true for business errors (validation, API failures) — never raise protocol exceptions for domain errorsAction: hintdict / object without properties. Typed nested models with fully-declared properties at ≤1 level are fine; ≥2 levels hallucinate regardless of typing. → tool-design.md §Argument Flatteningreferences/tool-design.md §Classification). Secondary tools that don't count against the budget: diagnostics (health, version); the roll-your-own polling tool paired with its submit tool (tool-design.md §Long-Running Operations); and the submit_feedback channel. Secondary is a design-time classification, not a runtime filter — these tools still appear in tools/list.tools capability whenever the server exposes tools. Minimum is "tools": {}; upgrade to "tools": {"listChanged": true} only when your tool set mutates after init (auth gating, feature flags, multi-tenant). Declaring listChanged: true on a static surface misleads defenders into watching for events that never fire; delivery across clients is uneven — see clients.md cross-client matrix and tool-design.md §Dynamic Tool Sets.→ Full conventions: references/tool-design.md
A useful pattern for self-owned production servers with a maintainer who reads the queue — not an MCP protocol requirement. Skip this entirely for adversarial environments, deployments without an active reviewer, or short-lived/demo servers — see feedback-tool.md §When NOT to use.
If you adopt the pattern:
feedback list / feedback status / feedback delete→ Full interface spec including severity, missing_capability, workaround_used, and the complete parameter contract: references/feedback-tool.md
elicitation (mid-call structured user input, supported by Claude Code, not by Claude Desktop — verify your client in clients.md matrix) is the right channel for optional parameters that need clarification — use it instead of stuffing every conditional into the tool description. Cross-client safe path: design the tool to work without elicitation; treat elicitation as a UX upgrade when the negotiated capability is present. → clients.md §Claude Code Design Implicationsserver.instructions): keep minimal — grow only when you see agents making wrong decisions without the directive. ALL-CAPS named workflow patterns, built dynamically at startup. Canonical ~100-word example covering all four content types: agent-ux.md §System Prompt as Configuration Surfacesubmit_feedback deployed.Action: hint for every recoverable error — agents act on error text directly→ Full patterns: references/agent-ux.md
[STACK:stateful-backends]Skip this section unless your backend is stateful or requires shared infrastructure across tool calls.
→ references/daemon-architecture.md — daemon/MCP split, Unix socket rules, crash isolation, when NOT to use this pattern.
Decision tree. Disambiguate first: is the client launching the server (subprocess), or connecting to a long-lived endpoint? First matching branch wins, then keep walking for the auth layer.
.mcp.json with "command"; any CLI host)? → stdio. Same-machine, single-consumer, no port allocation, no Origin/DNS-rebinding surface.security-threats.md §3.Worked pairings:
| Deployment shape | Transport | Auth |
|---|---|---|
| Claude Desktop / local Claude Code launches your server as a subprocess | stdio | none (process boundary) |
| Docker MCP gateway behind shared OAuth edge | streamable-http on 0.0.0.0:<port> inside the docker network | OAuth 2.1 terminated at the gateway, not per backend |
| Personal / single-user server behind Tailscale, VPN, or private LAN | streamable-http (TLS if crossing untrusted hops) | single bearer token tied to the principal (do not skip — §0 applies even on private networks) |
| Remote SaaS server for external users (incl. remote Claude Code) | streamable-http + TLS | OAuth 2.1 per-principal; narrow scopes; audience-bound tokens |
Streamable HTTP = MCP's current HTTP transport (introduced in spec 2025-03-26, current in 2025-11-25): one endpoint accepting POST + GET, server picks application/json or text/event-stream per response. Replaces the HTTP+SSE transport (introduced in 2024-11-05, deprecated in 2025-03-26). Design-binding rules (the rest of the protocol shape is in the spec):
Host and Origin (403 if invalid). Host is the load-bearing DNS-rebinding defence; Origin is defence in depth. SDK defaults vary — recent FastMCP enables protection only when bound to loopback, and mutating host post-construction silently bypasses it; TS SDK and others differ. Allow-list must match what your client actually sends, on the host you actually bind to. Full SDK footgun list + probes: security-threats.md §0 — HTTP Origin + Host validation.0.0.0.0) by default — relax only for the docker-network + auth-gateway pairing in the table above. Unix domain socket is the strongest mitigation (browsers cannot reach it).[STACK:remote-multi-server] Put auth/proxy/ingress in front of a curated gateway, not in every backend serverstdio: stdout is JSON-RPC only. Any other byte on stdout corrupts the transport silently. This is the UNIVERSAL rule — applies to every stdio MCP server, every language, every SDK. Diagnostic / human-readable logging goes to stderr; structured event logs (e.g. JSONL usage logs) go to a file the server process owns — see observability.md §Where to store. Probe: your_server </dev/null >/tmp/out 2>/dev/null & pid=$!; sleep 1; kill $pid; wc -c </tmp/out must print 0. The one exception is the daemon + on-demand pattern, where the MCP-server child is silent on both streams and logs travel to the daemon over the Unix socket — see daemon-architecture.md §Stderr Rule.→ Gateway aggregation: references/gateway-aggregation.md → Security per transport: references/security-threats.md → Client capabilities and limitations (Claude Desktop, Claude Code) + cross-client matrix: references/clients.md
127.0.0.1 or Unix socket; never expose without auth on public interfaceHost and Origin headers; SDK defaults vary and may silently bypass on 0.0.0.0 bindings. Canonical rule + footguns + probes: security-threats.md §0→ Threat reference (data injection, authn/authz, sessions, DoS, secrets, supply chain, release stability): references/security-threats.md
Per-call logs drive dead-tool / hot-tool / error-rate decisions. Minimum fields: ts, tool_name, status, duration_ms. Never log raw args or responses (secrets, PII, prompt-injected content). → references/observability.md for schema, storage patterns, privacy rules, report templates.
→ references/audit-checklist.md — 16-section, ~80-item checklist,
* marks high-priority items, produces HIGH / MEDIUM / LOW findings summary.
Precondition. §1's 80/20 / dead-tool usage-data items require ≥30 days of production tool-call logs (audit-checklist.md §1). For a new or pre-production server, mark only those usage-data items N/A; the rest of §1 (tool count vs. ≤10 signal, 1:1 endpoint wrappers, outcome orientation, one-job scoping) reads on design alone — run it now and queue the usage-data rerun once traffic exists.
Before shipping or handing off:
title set on every tool [OPINIONATED] — top-level on the Tool object, 1–3 words, sentence case, user-facing. Skip when: no client in your target matrix renders title distinctly from name.server.instructions reviewed [OPINIONATED] — empty / near-empty is worse than absent; either grow it to a real configuration surface or omit entirely. Budget + canonical shape: agent-ux.md §System Prompt as Configuration Surface.[OPINIONATED] — see tool-design.md §Tool Classification. Skip when: surface intentionally domain-broad with prefix namespacing across many tools — namespacing carries the load instead.submit_feedback present [OPINIONATED] — write-only, fire-and-forget; system prompt includes feedback directive. Skip when: no maintainer reads the queue, deployment is short-lived/demo, or environment is adversarial.outputSchema declared tools always return structuredContent (MUST)isError: true with actionable diagnostics — no protocol exceptionsstdio, stdout is JSON-RPC only; diagnostic logs go to stderr, structured event logs to a file the server ownsHost and Origin allow-lists are configured for the host you actually bind to (loopback-default SDK protection silently bypasses on 0.0.0.0) — see security-threats.md §0dict / object without properties in parameter schemas; typed nested models OK at ≤1 level[OPINIONATED] — ts, tool_name, status, duration_ms minimum. Skip when: pre-production / dev server with no real traffic — treat as debt to clear before first production deploy.[UNIVERSAL] — applies whether or not the usage log above exists; raw values may carry secrets, PII, or prompt-injected content.structuredContent schema-stable. MCP Apps (SEP-1865) is rolling out a ui:// rendering extension; stable schemas keep its later adoption non-breaking.sampling/createMessage works in spec but neither tracked client declares the capability. The 2025-11-25 spec keeps sampling and adds SEP-1577 (sampling with tools / server-side agent loops); only includeContext is soft-deprecated. But neither Claude Desktop nor Claude Code declares the sampling capability (clients.md) — so designing around sampling today produces dead code on those clients. Acceptable to include a sampling code path behind a capability check; not acceptable to make a primary tool's behaviour depend on it.taskSupport: "optional" is a future-leaning hedge. taskSupport (per-tool field declared via the Tasks SEP) is a no-op on clients that don't negotiate the tasks capability — and no client in clients.md is confirmed to do so today (2026-05-22). For Claude Desktop's defensive ≤20s heuristic (one observation: socket closed at 26s; see clients.md §Timeouts — not a documented ceiling), only the roll-your-own handle protects you; declaring taskSupport: "optional" alongside is harmless and switches on automatically when clients catch up. Reserve taskSupport: "required" until the matrix flips. Wire shape: examples/long-running-tasks-wire-shape.md.npx claudepluginhub j2h4u/oh-my-claude-plugins --plugin claude-code-metaCreates, edits, and verifies skills using a test-driven development approach with pressure scenarios and subagents.