Tool Protocols
Agents are only as useful as the tools they can reach. The ecosystem converged on a layered stack: MCP for tools, LSP for code, A2A for other agents. Three protocols, three reasons. Tools are heterogeneous and remote — stateless wire format. Code is structured and stateful — project-graph-aware server. Other agents are autonomous — discovery and task lifecycle. One protocol cannot do all three; the runtime should speak all three.
MCP Won
Model Context Protocol is the de facto standard for connecting agents to tools.
- 97M monthly SDK downloads as of March 2026 (official TypeScript and Python SDKs), up from ~2M at launch in November 2024.
- Donated to the Agentic AI Foundation under the Linux Foundation on December 9, 2025 — a directed fund co-founded by Anthropic, Block, and OpenAI, with supporting members Google, Microsoft, AWS, Cloudflare, and Bloomberg.
- Current spec revision: 2025-11-25. Wire format is JSON-RPC 2.0 with stateful capability negotiation between hosts, clients, and servers.
- Adopted natively by Claude Code, OpenAI (Codex CLI and Responses API), Gemini CLI, and Google ADK. Available via adapter in LangGraph (
langchain-mcp-adapters) and CrewAI.
The Three-Layer Protocol Stack
| Layer | Protocol | Scope | Status |
|---|---|---|---|
| 1 | MCP | Agent-to-Tool | Stable. Spec rev 2025-11-25. AAIF / Linux Foundation stewardship (Dec 2025). |
| 2 | A2A | Agent-to-Agent | v1.2. Linux Foundation stewardship since June 2025. 150+ orgs. |
| 3 | WebMCP | Agent-to-Web | W3C Community Group Draft Report — not a W3C Standard. Microsoft + Google editors. |
MCP Architecture
MCP uses a client–server architecture with a JSON-RPC 2.0 wire format. Clients live in the agent runtime; servers are standalone processes (stdio) or remote services (Streamable HTTP).
sequenceDiagram
participant A as Agent (MCP Client)
participant S as MCP Server (e.g. GitHub)
A->>S: initialize (capabilities negotiation)
S-->>A: serverInfo, capabilities
A->>S: tools/list
S-->>A: [{name: "create_issue", inputSchema: {...}}, ...]
A->>S: tools/call {name: "create_issue", arguments: {repo: "org/app", title: "Fix login bug"}}
S-->>A: {content: [{type: "text", text: "Created issue #42"}]}
Transports: the current spec defines stdio (local processes) and Streamable HTTP (remote servers). The older HTTP+SSE transport is deprecated as of spec revision 2025-03-26; major vendors have SSE sunset deadlines arriving mid-2026. New remote servers should target Streamable HTTP.
Configuration: .mcp.json
Projects declare MCP server dependencies in .mcp.json at the repository root, checked into version control so every developer and agent gets the same tool configuration.
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"POSTGRES_CONNECTION_STRING": "${DATABASE_URL}"
}
}
}
}
Credentials MUST use ${ENV_VAR} references — never inline secrets. This file is committed to git; inline secrets end up in git history.
Tool Discovery
When a client connects, it calls tools/list. The server responds with a JSON Schema per tool — the protocol is self-describing, so no hardcoded tool knowledge is required.
{
"tools": [{
"name": "create_issue",
"description": "Create a new GitHub issue in a repository.",
"inputSchema": {
"type": "object",
"properties": {
"repo": { "type": "string", "description": "owner/repo" },
"title": { "type": "string" },
"body": { "type": "string" }
},
"required": ["repo", "title"]
}
}]
}
Current MCP Server Ecosystem
The reference server set was trimmed in May 2025. Active reference servers in modelcontextprotocol/servers are now: Filesystem, Git, Fetch, Memory, Sequential Thinking, Time, and Everything. The earlier reference implementations for GitHub, Slack, Postgres, Puppeteer, Redis, Sentry, and SQLite were moved to modelcontextprotocol/servers-archived and are no longer canonical. For production, use vendor-official servers: GitHub’s own MCP server, Microsoft’s playwright-mcp, Sentry’s own server, and so on.
Streaming as a Structural Constraint
Streaming is not a UX feature layered on top of MCP — it is a structural constraint that changes error handling, cancellation, tool execution, rendering, and backpressure throughout the stack. Streamable HTTP supports multi-message streaming within a single response; the server can push notifications and requests back to the client mid-call. Every layer from API client to terminal renderer must handle partial, in-flight state.
| Problem | Non-streaming | Streaming |
|---|---|---|
| Error handling | Check response status | Stream can break mid-token, mid-tool-call, or mid-markdown block |
| Cancellation | Don’t send the request | User hits Ctrl+C during a file write — abort cleanly, discard partial output, leave the conversation in a valid state |
| Tool calls | Parse complete JSON | Tool call JSON arrives incrementally — buffer, detect boundaries, dispatch |
| Rendering | Render complete markdown | Render partial markdown that may have unclosed blocks, incomplete tables, or half-written code fences |
| Backpressure | N/A | Model produces tokens faster than the terminal renders — buffer without unbounded memory growth |
Two design notes carry the weight. Define a StreamEvent enum every layer speaks: TokenDelta, ToolCallStart, ToolCallDelta, ToolCallEnd, Error, Done. Make cancellation a first-class event — every async operation must respond to a cancellation signal within one tick.
Tool Failure
Tools fail. bash returns exit code 1. File writes fail because the path doesn’t exist. MCP servers crash mid-call. Treat failures as structured data the model reasons about, not exceptions that crash the loop.
| Failure type | Example | Correct handling |
|---|---|---|
| Expected error | bash exits with code 1 | Return stderr as tool result. Model adapts. |
| Transient failure | Network timeout on web_fetch | Retry with backoff. Include attempt count in result. |
| Permanent failure | MCP server process died | Mark server unavailable. Remove its tools from the active registry. Inform the model. |
| Partial result | Streaming tool output cut short | Return what was received with a truncation marker. |
| Permission denial | User rejected the tool call | Return denial as a structured result. Model must not retry the same call. |
Every tool result is an envelope: { status, content, metadata } where status is ok | error | denied | timeout and metadata carries duration_ms, exit_code, and similar. Track consecutive failures per tool — three in a row means the model is stuck in a retry loop, and the harness should surface this to the user rather than burn tokens.
LSP — The Missing Protocol Layer
Agents that work with code lean overwhelmingly on text tools: grep, glob, regex. These treat code as strings. Code is not strings — it is a structured artifact with types, references, scopes, and semantic relationships text search cannot reliably capture. The result is false positives in reference searches, missing type information on hover, and renames that quietly break string literals. LSP is the mature, decade-old protocol that fixes this — every major language has one, every major IDE uses one, and the MCP spec itself credits LSP as design inspiration.
Text Tools vs. LSP
The difference is not incremental — it is categorical.
| Task | Text tools (grep, glob) | LSP |
|---|---|---|
Find all callers of processPayment() | grep -r "processPayment" — finds comments, strings, imports, dead code, and the definition itself. Manual filtering required. | textDocument/references — returns only call sites. No false positives. |
| Check if a refactor broke the types | Run the full compiler. Slow, noisy, reports errors in unrelated files. | textDocument/diagnostic — incremental. Reports only new errors in changed files. |
Rename userId to accountId everywhere | Find-and-replace. Breaks userIdValidator, getUserId(), string literals, and comments. | textDocument/rename — renames only the semantic symbol. Strings, comments, substrings untouched. |
| Understand an unfamiliar function | Read the file. Hope the docstring is accurate. | textDocument/hover — returns type signature, documentation, and source location. Always current. |
What LSP Provides
| LSP Method | What It Returns | Agent Use Case |
|---|---|---|
textDocument/references | All locations referencing a symbol | ”Find every caller” — zero false positives |
textDocument/definition | The definition site of a symbol | ”Where is this defined?” — instant |
textDocument/hover | Type information and documentation | ”What does this return?” — compiler-accurate |
textDocument/diagnostic | Errors, warnings, hints | ”Is this code valid?” — incremental, language-aware |
textDocument/rename | All locations for a safe rename | ”Rename everywhere” — semantic, won’t break strings |
textDocument/completion | Valid completions at a cursor | ”What methods exist here?” — type-aware |
textDocument/signatureHelp | Parameter names and types | ”What arguments does this take?” |
workspace/symbol | All matching symbols across workspace | ”Find all *Controller” — structured, not regex |
Lazy Lifecycle
LSP servers are stateful, memory-intensive processes that hold a project graph and consume hundreds of megabytes on large codebases. Do not start them at session start — most conversations never need semantic code intelligence. Spawn on the first LSP tool call, accept the 2–10 second startup penalty, and serve subsequent calls fast. Kill servers when the session ends, after a configurable idle timeout (typically 5 minutes), or on crash (do not auto-restart — inform the agent that LSP is unavailable).
Server Selection
| File Extension | Language Server |
|---|---|
.ts, .tsx, .js, .jsx | typescript-language-server |
.rs | rust-analyzer |
.py | pyright or python-lsp-server |
.go | gopls |
.java | jdtls (Eclipse) |
.c, .cpp, .h | clangd |
When a file type has no configured server, LSP tools should fail gracefully and the agent falls back to text-based tools without error.
MCP and LSP Are Complementary, Not Competing
MCP is stateless tool calls to any service. LSP is stateful, code-specific, project-graph-aware intelligence. Both belong in the runtime: MCP for breadth, LSP for depth on code. Wrapping LSP behind an MCP server is technically possible but architecturally awkward — LSP’s open-document model maps poorly to MCP’s stateless call shape. Expose LSP capabilities directly as tools, managed by a dedicated LSP client in the harness. Claude Code shipped native LSP in v2.0.74 (December 2025) with plugins covering 20+ languages; it remains the only CLI agent that ships LSP as a first-party tool.
A2A
Agent-to-Agent addresses multi-agent coordination: discovery, authentication, and delegation between agents. Each A2A agent publishes an Agent Card at /.well-known/agent-card.json (per RFC 8615) advertising name, version, service endpoint, supported modalities, auth schemes (Basic, Bearer, OAuth 2.0 / OIDC), and skills. Originally Google-led, contributed to the Linux Foundation in June 2025; now under the AAIF umbrella alongside MCP, currently at v1.2 with signed Agent Cards and adoption across 150+ organizations (Microsoft, AWS, Salesforce, SAP, ServiceNow).
A2A defines a stateful task lifecycle: submitted, working, input-required, auth-required, completed, failed, canceled, rejected. MCP and A2A are complementary — MCP connects agents to tools, A2A connects agents to agents.
Security
- Never hardcode secrets. Use
${ENV_VAR}references in.mcp.json. The file is committed; inline secrets end up in git history. - Scope MCP permissions tightly. Filesystem servers to specific directories; database connections read-only where possible; tokens fine-grained with only the required scopes.
- Enforce tool-level allow/deny lists. MCP-compatible agents support per-tool permissions (e.g.,
mcp__github__create_issueallowed,mcp__postgres__execute_ddldenied). Use glob patterns for broader rules.
See Credentials for the full credential lifecycle — OAuth with PKCE, token storage, proactive refresh before 80% of TTL, and revocation.