Model Context Protocol (MCP)
This document is an independent paraphrased tutorial describing public concepts of the Model Context Protocol (MCP). For definitive specifications and evolving details, always consult the official MCP repositories and documentation.
1. Executive Summary
The Model Context Protocol (MCP) defines a standardized, transport‑agnostic contract that lets AI clients (editors, IDEs, orchestrators, agent frameworks) discover and invoke servers that expose structured context (resources), callable tools, and streaming events. MCP aims to eradicate bespoke plugin APIs, enabling portable tool ecosystems and reproducible model contexts across environments (local dev, cloud, CI, notebooks).
2. Why MCP? (Motivation)
| Problem | Limitation Without MCP | MCP Remedy |
|---|---|---|
| Fragmented Tool Integrations | Each app invents a plugin API. | Unified capability negotiation + tool schema. |
| Opaque Context Injection | Ad‑hoc prompt stuffing without provenance. | Explicit resource listing/versioning + structured retrieval. |
| Model / Runtime Divergence | Tooling tied to a single vendor. | Open, transport‑agnostic protocol surface. |
| Lack of Streaming Semantics | Polling / brittle websockets. | Built‑in event & token streaming channels. |
| Poor Reproducibility | Hidden side effects. | Declarative capabilities & deterministic resource handles. |
3. High-Level Architecture
+-----------+ Transport (JSON-RPC / stdio / WebSocket) +-------------+
| Client | <-----------------------------------------------> | MCP Server|
| (IDE/LLM) | Init -> Capability Exchange -> Resource/Tool Calls | (Adapter) |
+-----------+ +-------------+
| |
| Tool Invoke / Resource Fetch | Backend Systems
v v
Model / Agent Stack <-- internal orchestration --> Databases / APIs / Repos / Vector Stores
Roles:
- Client: Initiates session, queries capabilities, presents UI or feeds model.
- Server: Declares resources, tools, events; executes actions deterministically.
- Transport: Usually JSON-RPC 2.0 framing (often over stdio or WebSocket); not mandated by semantics.
4. Core Protocol Primitives
| Primitive | Purpose | Notes |
|---|---|---|
| Initialization | Negotiate protocol + feature versions. | Prevents silent divergence. |
| Capabilities | Server declares optional features (tools, resources, streaming). | Enables graceful fallback. |
| Resources | Enumerated contextual objects (files, embeddings, configs). | Addressed by stable uri or handle. |
| Tools | Structured callable functions with JSON schemas for params & result. | Model / agent can plan reliably. |
| Events / Streaming | Asynchronous push: status, progress, token streams. | Avoids blocking synchronous calls. |
| Errors | Standardized error codes & messages. | Encourages predictable recovery. |
5. Capabilities & Negotiation
Handshake typically includes:
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "1.0",
"client": { "name": "example-ide", "version": "0.4.2" },
"wanted": { "tools": true, "resources": true, "streaming": true }
},
"id": 1
}
Server responds with accepted + advertised capabilities (example structure). The client must gracefully handle omitted/false features.
6. Resource Model
Characteristics:
- Each resource described with metadata:
uri,kind,title,mimeType, optionalversion,hash/etag. - Retrieval via request—may return content inline or a reference (e.g., pre-signed URL).
- Pagination or chunking for large resources.
- Determinism: Clients can verify integrity (hash) to include stable context in prompts.
Example resource listing response (illustrative):
{
"jsonrpc": "2.0", "id": 2,
"result": {
"resources": [
{"uri": "repo://main/src/models/model.py", "kind": "code", "mimeType": "text/x-python", "version": "acbd18", "hash": "sha256:..."},
{"uri": "vector://products/index", "kind": "embedding-index", "mimeType": "application/vnd.vector+json"}
]
}
}
7. Tools (Callable Functions)
A tool definition surfaces:
- Name (stable identifier)
- Description (model-readable & human)
- JSON Schema for input parameters
- JSON Schema or shape for result
- Side-effect classification (pure vs mutating) & safety notes
- Optional cost hints / latency class
Example tool declaration fragment:
{
"name": "searchProducts",
"description": "Semantic product search returning top matches.",
"inputSchema": {
"type": "object",
"required": ["query"],
"properties": { "query": {"type": "string", "minLength": 2}, "limit": {"type": "integer", "default": 5, "minimum": 1, "maximum": 20} }
},
"outputSchema": {
"type": "object",
"properties": { "results": { "type": "array", "items": {"type": "object", "properties": {"id": {"type": "string"}, "score": {"type": "number"}}}} }
},
"sideEffects": "read-only"
}
Invocation request (illustrative):
{
"jsonrpc": "2.0",
"method": "tool/execute",
"params": { "name": "searchProducts", "arguments": { "query": "wireless earbuds", "limit": 3 }},
"id": 42
}
8. Streaming & Events
Use cases:
- Long‑running tool progress
- Incremental retrieval (token / chunk streaming)
- Observability (log channels)
Pattern (pseudo): initial request acknowledges, subsequent notification messages deliver event objects with sequence numbers. Client reassembles or displays progressively. Ensure idempotency or ordering guarantees via monotonic seq.
9. Error Handling
| Error Class | Example Code | Client Strategy |
|---|---|---|
| Validation | INVALID_PARAMS | Reconstruct request with corrected shape. |
| Capability Missing | UNSUPPORTED_FEATURE | Fallback or disable UI affordance. |
| Transient Backend | RETRYABLE | Exponential backoff with jitter. |
| Rate / Budget | RATE_LIMIT | Surface to user; delay further calls. |
| Internal | INTERNAL_ERROR | Capture diagnostic metadata; show minimal message. |
Include stable code, human message, and optional structured data for machine recovery.
10. Versioning & Compatibility
Strategies:
- Protocol semantic version (e.g.,
1.0,1.1) ininitialize. - Capability flags for additive features (avoid breaking shape changes mid‑major).
- Deprecation metadata: server may include
deprecatedSince+removalTargetin tool descriptors. - Use contract tests in CI: golden JSON requests/responses to prevent accidental drift.
11. Security & Isolation
| Domain | Control |
|---|---|
| Transport | Use local stdio for colocated processes; TLS for remote sockets. |
| AuthN | Out‑of‑band (e.g., bearer token env var) or mutual trust if local user session. |
| AuthZ | Server internally scopes which tools/resources a client identity can see. |
| Input Validation | Strict JSON Schema validation before execution. |
| Sandboxing | Run high‑risk tools in subprocess / container with resource limits. |
| Auditing | Log tool invocations, arguments hash (avoid secrets in plaintext logs). |
Never assume the client is benign—treat all inputs as untrusted.
12. Implementing a Minimal Node.js MCP Server
Illustrative simplified server (conceptual only):
#!/usr/bin/env node
import { stdin as input, stdout as output } from 'node:process';
interface JsonRpc { jsonrpc: '2.0'; id?: number | string; method?: string; params?: any; result?: any; error?: any; }
function send(msg: any) {
output.write(JSON.stringify(msg) + '\n');
}
send({ jsonrpc: '2.0', method: 'log', params: { level: 'info', message: 'MCP server starting' }});
process.stdin.setEncoding('utf8');
input.on('data', (chunk) => {
for (const line of chunk.trim().split(/\n+/)) {
if (!line) continue;
let req: JsonRpc;
try { req = JSON.parse(line); } catch (e) { continue; }
if (req.method === 'initialize') {
send({ jsonrpc: '2.0', id: req.id, result: { protocolVersion: '1.0', capabilities: { tools: true, resources: true }}});
} else if (req.method === 'resource/list') {
send({ jsonrpc: '2.0', id: req.id, result: { resources: [{ uri: 'memo://welcome', kind: 'text', mimeType: 'text/plain', version: '1', hash: 'sha256:demo' }] }});
} else if (req.method === 'tool/list') {
send({ jsonrpc: '2.0', id: req.id, result: { tools: [{ name: 'echo', description: 'Echo text', inputSchema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] }, outputSchema: { type: 'object', properties: { text: { type: 'string' }}} }] }});
} else if (req.method === 'tool/execute') {
const text = req.params?.arguments?.text ?? '';
send({ jsonrpc: '2.0', id: req.id, result: { text } });
} else {
send({ jsonrpc: '2.0', id: req.id, error: { code: 'METHOD_NOT_FOUND', message: 'Unknown method' }});
}
}
});
Run:
chmod +x ./mcp-echo-server.ts
# (transpile or run with ts-node) then connect your client via stdio
13. Python Sketch (Server Skeleton)
#!/usr/bin/env python3
import sys, json
def send(msg):
sys.stdout.write(json.dumps(msg) + '\n')
sys.stdout.flush()
send({"jsonrpc":"2.0","method":"log","params":{"level":"info","message":"MCP py server starting"}})
for line in sys.stdin:
line=line.strip()
if not line:
continue
try:
req=json.loads(line)
except Exception:
continue
mid=req.get('id')
method=req.get('method')
if method=='initialize':
send({"jsonrpc":"2.0","id":mid,"result":{"protocolVersion":"1.0","capabilities":{"tools":True,"resources":True}}})
elif method=='resource/list':
send({"jsonrpc":"2.0","id":mid,"result":{"resources":[{"uri":"memo://py","kind":"text","mimeType":"text/plain","version":"1"}]}})
elif method=='tool/list':
send({"jsonrpc":"2.0","id":mid,"result":{"tools":[{"name":"echo","description":"Echo text","inputSchema":{"type":"object","properties":{"text":{"type":"string"}},"required":["text"]},"outputSchema":{"type":"object","properties":{"text":{"type":"string"}}}}]}})
elif method=='tool/execute':
args=req.get('params',{}).get('arguments',{})
send({"jsonrpc":"2.0","id":mid,"result":{"text":args.get('text','')}})
else:
send({"jsonrpc":"2.0","id":mid,"error":{"code":"METHOD_NOT_FOUND","message":"Unknown method"}})
14. Client Considerations
| Concern | Guidance |
|---|---|
| Backpressure | Buffer bounded; request concurrency gating. |
| Tool Discovery | Cache descriptors; refresh on tool/updated events if supported. |
| Prompt Assembly | Insert resource excerpts with provenance (URI + version/hash). |
| Retry Logic | Idempotent GET‑like calls safe; avoid re‑invoking mutating tools blindly. |
| Secret Handling | Client should redact or avoid transmitting secrets unless required. |
15. Observability & Telemetry
- Structured invocation logs:
timestamp,tool, latency ms, outcome. - Metrics: success rate, p95 tool latency, resource fetch size distribution.
- Optional tracing: correlate request IDs across server backends.
- Anomaly detection: spikes in attempts for
METHOD_NOT_FOUNDmay indicate version skew.
16. Advanced Extensions
| Feature | Idea |
|---|---|
| Partial Tool Plans | Server suggests intermediate sub‑calls for complex workflows. |
| Cost Metadata | Tools expose estimated token or compute cost → model planning. |
| Adaptive Streaming | Client negotiates chunk size or token cadence. |
| Multi-Modal Resources | mimeType variants (image embeddings, audio transcripts). |
| Sandboxed WASM Tools | Distribute portable tool payloads executed client‑side. |
17. Best Practices
- Separate pure vs mutating tools to inform safe auto‑invocation.
- Keep tool surface minimal; avoid overlapping semantics.
- Provide JSON Schema enums & constraints to reduce model ambiguity.
- Version bump only when contract changes; additive fields gated by capability flags.
- Log hashes rather than raw sensitive content for debug traceability.
18. Common Pitfalls & Troubleshooting
| Symptom | Likely Cause | Resolution |
|---|---|---|
| Client ignores tool | Capability not advertised | Ensure initialize response sets tools: true. |
Repeated METHOD_NOT_FOUND | Method name mismatch | Align to canonical tool/execute etc. |
| Resource hash mismatch | Server content mutated mid-session | Add version pin & stable snapshot semantics. |
| Latency spikes | Overloaded backend or synchronous heavy tool | Introduce async worker / queue. |
| Broken streaming order | Missing sequence monotonicity | Add seq field & reorder or reject out‑of‑order. |
19. FAQ
| Question | Answer |
|---|---|
| Is MCP tied to a specific model vendor? | No; it's model/runtime agnostic. |
| Do I need WebSockets? | Not necessarily; stdio or pipes suffice locally. |
| How do I secure remote servers? | TLS + client auth (mTLS or token) + strict tool allowlists. |
| Can tools call other tools? | Yes, but avoid recursive loops; maintain trace context. |
| How big can resources be? | Chunk or page large datasets; advertise size hints. |
20. Reference Implementation Checklist
| Area | Minimal | Production |
|---|---|---|
| Transport | stdio JSON-RPC | TLS WebSocket + auth + backpressure |
| Validation | Basic try/except | JSON Schema + contract tests |
| Logging | Console lines | Structured logs + metrics + tracing |
| Tool Registry | Hardcoded list | Dynamic plugin discovery & hot reload |
| Security | Local trust | AuthZ per tool + sandboxing + rate limits |
| Observability | Manual timing | p95 metrics, alerts, anomaly detection |
21. Integration Opportunities
- IDE & Editor Extensions (context panel + tool palette)
- CI Agents (deterministic environment diff resource tool)
- Retrieval Augmented Generation pipelines (resource enumerator + embedding index tool)
- Governance: audit export tool enumerating all context passed to models
- Multi‑tenant platform: per‑tenant server processes with isolated tool registries
22. Migration & Evolution Strategy
- Start with read‑only resource exposure.
- Introduce low‑risk tools (search/list) with schemas.
- Add streaming for long operations (embedding build, index refresh).
- Implement mutating tools guarded by policies & review.
- Automate contract tests in CI.
- Monitor metrics -> refine capacity planning.
23. Attribution
Model Context Protocol™ concepts are attributed to their respective open initiative & contributors. This document is an educational paraphrased guide.
End of guide.