---
title: "MCP Servers"
slug: "mcp-servers"
category: "ai-agents"
tags: ["ai-agents", "mcp", "tools", "integrations"]
status: "stable"
last_updated: 2026-05-29
summary: "When to ship a Model Context Protocol server, how to scope it, and how to design tools the agent can use reliably."
related: ["[[ai-agents/claude-code]]", "[[ai-agents/multi-agent]]", "[[ai-agents/mcp-streamable-http]]", "[[prompt-engineering/prompt-design]]", "[[ai-agents/prompt-injection-defense]]"]
---

## 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 [[ai-agents/system-prompts|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.

```json
{
  "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.

```text
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 [[ai-agents/claude-code]] for permission patterns at the client side.

## Choose stdio for local, Streamable HTTP for remote

Two transports cover almost every case.

- stdio: server is a local subprocess. Good for filesystem access, local databases, dev tools.
- Streamable HTTP: server is a remote service. Good for SaaS integrations, multi-user deployments. The older HTTP+SSE transport is deprecated; see [[ai-agents/mcp-streamable-http]].

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.

```json
{"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.

## Related

- [[ai-agents/claude-code]]
- [[ai-agents/multi-agent]]
- [[prompt-engineering/prompt-design]]
