MCP Servers in Depth: The Model Context Protocol for Tools, Data, and Agents

Before every chat app reinvented its own plugin format, teams hit the same wall: an LLM in the IDE needed databases, browsers, Git, and cloud APIs—but each integration was bespoke, brittle, and impossible to reuse. The Model Context Protocol (MCP) is an open standard that treats those integrations like microservices: a host (Cursor, Claude Desktop, an SDK agent) speaks to MCP servers over a small JSON-RPC protocol, discovering tools, resources, and prompts with explicit schemas. This guide explains the architecture, message lifecycle, transports, security, and how to build and operate MCP servers in production-minded environments.

In short

MCP separates the model host from capability providers. Servers expose typed tools and readable resources; clients negotiate capabilities once, then call tools through JSON-RPC. Success means treating MCP like any other service boundary: least privilege, schema discipline, observability, and clear trust boundaries—not piping root credentials into a demo server.

What problem MCP solves

Large language models are general reasoners; they are not, by themselves, connected to your systems. Product teams historically built N custom bridges for M hosts: a VS Code extension that shells out to kubectl, a Slack bot with its own Jira client, a notebook that pastes CSV exports into context. Each bridge duplicated auth, error handling, and schema design. MCP collapses that pattern into N servers × 1 protocol that any compliant host can reuse.

Think of MCP as USB-C for AI tools: a standard plug shape (messages and capability types), not a mandate about what you plug in. A filesystem server, a Postgres server, and a browser automation server all look the same to the host—they advertise capabilities; the model chooses among them under your policies.

Core vocabulary: host, client, and server

  • Host — The application the human uses: Cursor, Claude Desktop, an internal agent built on the Cursor SDK or another runtime. The host owns the UI, model API keys, and policy (which tools may run, approval flows).
  • Client — Code inside the host that maintains one JSON-RPC session per MCP server. A host with five configured servers runs five clients (often five child processes or HTTP connections).
  • Server — A program that implements MCP and exposes capabilities: list files, query a database, fetch docs from Confluence, drive a headless browser. Servers are small, focused, and replaceable.

The model never talks to the server socket directly. The flow is: user → host → model proposes tool use → host/client executes MCP call → result returned to model → user sees outcome. That indirection is what makes auditing, consent prompts, and rate limits possible.

Architecture at a glance

┌─────────────────────────────────────────────────────────────┐
│  MCP Host (e.g. Cursor IDE, Claude Desktop, SDK agent)      │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐          │
│  │ MCP Client  │  │ MCP Client  │  │ MCP Client  │  ...     │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘          │
└─────────┼────────────────┼────────────────┼────────────────┘
          │ stdio / HTTP   │                │
          ▼                ▼                ▼
   ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
   │ MCP Server   │ │ MCP Server   │ │ MCP Server   │
   │ (filesystem) │ │ (postgres)   │ │ (browser)    │
   └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
          │                │                │
          ▼                ▼                ▼
      Local FS          Database         Chromium

Protocol foundation: JSON-RPC 2.0

MCP messages ride on JSON-RPC 2.0: requests with method and params, responses with result or error, and optional notifications (no response expected). The spec defines a versioned capability handshake so hosts and servers agree on supported features before heavy traffic.

Typical lifecycle:

  1. Initialize — Client sends protocol version and client info; server returns its version and capabilities (tools? resources? prompts? logging?).
  2. Initialized notification — Client acknowledges; session is ready.
  3. Discoverytools/list, resources/list, prompts/list (as supported).
  4. Executiontools/call with a tool name and JSON arguments matching the advertised input schema.
  5. Shutdown — Graceful close of stdio pipe or HTTP stream.

Schemas matter: each tool publishes an input JSON Schema so hosts can validate arguments before execution and surface structured errors when the model hallucinates parameter names.

The three capability primitives

1. Tools (model-initiated actions)

Tools are functions the model may invoke: run a SQL query, create a Git branch, post to Slack, restart a deployment. Each tool has a name, description (shown to the model), and input schema. Tool results are structured content—often text, sometimes images or embedded resources.

Tools are the highest-risk surface: they mutate state. Hosts should implement human-in-the-loop approval for destructive operations, per-tool allowlists, and environment separation (read-only prod vs write sandbox).

2. Resources (context the model can read)

Resources address data by URI-like identifiers (file:///README.md, postgres://schema/users). The host or model can read resource contents into context—configuration files, ticket descriptions, API specs—without treating every read as a bespoke tool call. Resources support listing, subscriptions (change notifications), and MIME types so hosts know how to render them.

Use resources when data is reference material; use tools when the operation has side effects or complex inputs.

3. Prompts (reusable prompt templates)

Prompts are parameterized templates packaged by the server: “summarize this repo,” “generate migration from schema diff.” They standardize expert prompts and can ship with domain-specific servers (e.g. a Kubernetes server that knows how to phrase kubectl output requests).

Transports: how bytes move

Transport How it works Best for
stdio Host spawns server as child process; JSON-RPC lines on stdin/stdout Local dev, Cursor/Claude Desktop, CLI agents; simplest auth boundary (OS user)
Streamable HTTP HTTP POST + optional SSE for server→client streaming Remote shared servers, team-hosted capability gateways, SDK cloud agents

stdio servers are configured with a command and args—e.g. npx -y @modelcontextprotocol/server-filesystem /path. The host inherits the user’s environment variables (which is why secrets in .env are dangerous unless scoped). HTTP servers add TLS, API keys, and network policies—closer to how you’d expose any internal microservice.

What a tool call looks like (conceptually)

After discovery, the model emits a structured intent; the host validates and forwards:

  1. Model: “Call query_database with {\"sql\": \"SELECT count(*) FROM orders\"}.”
  2. Host: checks tool is enabled, arguments match schema, optional user approval.
  3. Client → Server: JSON-RPC tools/call.
  4. Server: executes against Postgres, returns rows as text/JSON.
  5. Host: attaches result to conversation; model continues reasoning.

Failures should be actionable: schema validation errors, timeout messages, and policy denials belong in the tool result so the model can retry with corrected input—not opaque 500 pages dumped into context.

Configuring MCP servers in Cursor

Cursor reads MCP config from project or user settings (e.g. .cursor/mcp.json or the MCP panel). A minimal stdio server entry resembles:

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/root"]
    }
  }
}

Design choices when adding servers:

  • Scope paths narrowly — filesystem servers should not mount / or your home directory unless you accept exfiltration risk.
  • Prefer read-only credentials for analytics DBs; separate write tools behind approval.
  • Document each server in README so teammates know which processes spawn and which env vars are required.
  • Version-pin npx packages or use locally installed binaries for reproducibility.

Building an MCP server

Official SDKs exist for TypeScript and Python (@modelcontextprotocol/sdk, mcp on PyPI). A minimal server:

  1. Creates a Server instance with name and version.
  2. Registers handlers for list_tools and call_tool (and optionally resources/prompts).
  3. Connects a StdioServerTransport (or HTTP transport) and runs the event loop.

Example shape (Python, abbreviated):

from mcp.server import Server
from mcp.server.stdio import stdio_server

app = Server("demo-weather")

@app.list_tools()
async def list_tools():
    return [{
        "name": "get_forecast",
        "description": "City weather summary",
        "inputSchema": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"]
        }
    }]

@app.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "get_forecast":
        city = arguments["city"]
        # call external API with timeouts and structured errors
        return [{"type": "text", "text": f"Forecast for {city}: ..."}]
    raise ValueError(f"Unknown tool: {name}")

async def main():
    async with stdio_server() as (read, write):
        await app.run(read, write, app.create_initialization_options())

Production servers add: structured logging, request timeouts, circuit breakers on downstream APIs, input sanitization, metrics (latency, error rate per tool), and graceful degradation when dependencies are down.

Security: treat MCP as remote code execution

Any tool the model can call is effectively RCE with natural-language triggers. Assume prompts will be attacked.

  • Prompt injection — Malicious content in a file or webpage instructs the model to exfiltrate secrets via a tool. Mitigate with scoped filesystem roots, secret scanners, and disallowing arbitrary outbound network from sensitive servers.
  • Over-privileged tokens — A GitHub MCP server with org-admin PAT is one bad prompt away from disaster. Use fine-scoped tokens, short TTL, and GitHub Apps where possible.
  • Supply chain — Pin server packages; audit what npx -y pulls on every launch.
  • Data residency — Tool results land in model context and often vendor logs. Do not pipe PHI/PCI through third-party models without review.
  • Multi-tenant HTTP servers — Authenticate clients, isolate tenants, rate-limit tool calls.

Platform engineers should map MCP servers into the same risk tier as CI jobs with secrets: identity, network policy, audit logs, and break-glass procedures.

MCP vs native function calling vs ad-hoc plugins

Approach Strength Limitation
OpenAI-style functions / tools API First-class in a single vendor API; low latency Host-specific; no standard for sharing across IDEs and agents
Custom plugins per product Tailored UX M×N integrations; brittle maintenance
MCP Portable servers; discovery; resources + prompts; growing ecosystem Extra process/HTTP hop; host must implement policy layer

Many products will support both: vendor-native tools for hosted-only features, MCP for everything you want portable across Cursor, Claude, and internal agents. For LLM fundamentals and tool loops inside applications, see LLMs in depth and RAG in depth.

Ecosystem: what ships today

Reference and community servers cover common needs (exact names change—check the official MCP servers repository):

  • Filesystem — read/write within a configured root
  • Git — status, diff, commit operations
  • Postgres / SQLite — schema-aware querying
  • Fetch / Brave search — web retrieval with policy hooks
  • Puppeteer / browser — automated browsing (high risk; sandbox hard)
  • Slack, Google Drive, Sentry — SaaS integrations via vendor templates

Enterprises increasingly wrap internal platforms (Kubernetes, Terraform Cloud, internal feature flags) as MCP servers so the same capability surfaces in IDE assistants and on-call bots—provided SRE and security sign off on scopes.

Operating MCP in platform teams

  • Golden paths — Curate an approved server list; avoid “every engineer runs random GitHub gists.”
  • Observability — Log tool name, latency, success/failure (not always full arguments if sensitive).
  • Cost — Tools that dump huge JSON into context burn tokens; paginate and summarize server-side.
  • Testing — Contract-test tool schemas; simulate hostile inputs (injection strings, path traversal).
  • CI/CD agents — The Cursor SDK and similar runtimes can attach MCP in pipelines; same security rules as production shells.

If you operate Kubernetes and Git-backed delivery, MCP complements rather than replaces them: GitOps declares cluster state; MCP helps engineers interrogate that state during incidents. See GitOps principles and Kubernetes troubleshooting playbook.

Troubleshooting checklist

  • Server never appears in host — wrong command path, missing node/python, or JSON syntax error in config.
  • Initialize fails — protocol version mismatch; upgrade host or server SDK.
  • Tool not found — server restarted without re-listing; host cache stale; typo in tool name.
  • Silent hang — blocking stdio without newline framing; deadlocks when server logs to stdout (use stderr for logs).
  • Permission denied — OS permissions on paths; cloud IAM on HTTP servers.

Learning path

  1. Read the official MCP specification (architecture + lifecycle).
  2. Run a reference filesystem server locally in Cursor; inspect tool list.
  3. Build a tiny custom server wrapping one internal read-only API.
  4. Add schema validation, timeouts, and structured errors.
  5. Pair with How to become an AI developer for agent architecture and eval discipline.

Further reading

  • modelcontextprotocol.io — specification, SDK docs, and server registry
  • Anthropic — MCP announcement and design rationale
  • Cursor documentation — MCP configuration, approval modes, and SDK agent integration
  • OWASP — LLM Top 10 (prompt injection, excessive agency) for threat modeling

Blog index · LLMs in depth · How to become an AI developer · Git & GitHub in depth

Back to blog list