Overview

The Model Context Protocol (MCP) is the standard interface between an agent and a long-lived integration. An MCP server exposes tools, resources, and prompts that any compatible client (Claude Code, Claude Desktop, IDE plugins) can call. Ship an MCP server when the integration is durable; use a one-off function call when it is throwaway.

Ship an MCP server for integrations the agent revisits

A new MCP server earns its keep when the agent will call it across sessions, not when one prompt needs one API call.

  • Good candidates: GitHub, Notion, internal ticket system, company-specific search index, deployment console.
  • Bad candidates: a single curl to a public API that the user can paste into the prompt; a script that runs once and is deleted.

The break-even point is roughly the third reuse. If you would copy-paste the same tool-use schema into three prompts, write the server.

Scope one server per domain, not per tool

Group related tools under a single server. A “github” server with list_issues, create_pr, read_file is better than three single-tool servers.

  • One server = one domain = one auth boundary. Auth tokens, rate limits, and logging belong at the server level.
  • Tools within a server share types: an Issue schema is defined once and reused by list_issues and comment_on_issue.

Splitting per tool inflates the server count, multiplies auth setup, and breaks shared types. Splitting per domain keeps the surface small.

Design tool schemas the model can actually use

Tool descriptions are part of the system prompt; the model reads them before deciding to call. Write them as if a developer with no context were reading them.

  • Names: verbs, not nouns. search_issues, not issues_search.
  • Types: narrow. Enums where possible. state: "open" | "closed", not state: string.
  • Descriptions: state what the tool does, what it returns, and one example invocation.
  • Required fields: only what the tool cannot infer. Sane defaults reduce model error.
{
  "name": "search_issues",
  "description": "Search GitHub issues by query string. Returns up to 30 issues sorted by relevance. Example: search_issues({ \"q\": \"is:open label:bug repo:org/repo\" }).",
  "input_schema": {
    "type": "object",
    "required": ["q"],
    "properties": {
      "q": { "type": "string" },
      "limit": { "type": "integer", "minimum": 1, "maximum": 100, "default": 30 }
    }
  }
}

The model’s success rate on a tool tracks the quality of the schema more than the cleverness of the prompt.

Expose read-only data as resources, not tools

Tools are actions; resources are addressable data. A file, a table, a doc page is a resource. Resources are referenced by URI, fetched on demand, and cached by the client.

github://org/repo/issues/401
notion://workspace/page/abc123

Resources keep the tool list short. The agent reads by URI instead of calling get_thing(id=...) for every read.

Authenticate, redact, and rate-limit at the server boundary

MCP servers run with credentials. Treat them like API gateways.

  • Auth: OAuth, token, or mTLS at the transport. Never embed secrets in tool schemas.
  • Redaction: strip API keys, emails, and tokens from outputs and logs before they reach the model.
  • Rate limits: per-tool and per-session. A buggy agent loop will hammer a tool 100 times in a minute.
  • Allowlists: explicit permission per tool. create_pr requires an opt-in flag; reads can default to on.

See claude-code for permission patterns at the client side.

Choose stdio for local, HTTP/SSE for remote

Two transports cover almost every case.

  • stdio: server is a local subprocess. Good for filesystem access, local databases, dev tools.
  • HTTP with SSE (or streaming HTTP): server is a remote service. Good for SaaS integrations, multi-user deployments.

Run local servers as stdio unless you need to share them across machines. The setup is simpler and there is no port management.

Log every tool call as structured JSON

Every call gets a log line: tool, args, result_summary, duration_ms, error. Logs feed debugging, eval datasets, and audit trails.

{"ts": "2026-05-14T10:21:00Z", "tool": "search_issues", "args": {"q": "..."}, "duration_ms": 412, "result_count": 17}

When the agent does something weird, the log is the first place to look.