The integration problem
Without MCP, each model provider needs its own tool contract. Every connector multiplies implementation and maintenance cost.
This creates two costs: duplicated code and inconsistent behavior under failure. Retry semantics differ. Validation quality differs. Audit detail differs.
Architecture debt warning
MCP adoption without contract ownership is just new plumbing on old governance debt.
What top sources cover vs miss
| Source | Strong coverage | Missing piece |
|---|---|---|
| MCP Architecture Docs | Strong conceptual model for hosts, clients, and servers. | No production decision framework for choosing MCP vs simpler patterns. |
| MCP Server Concepts | Clear explanation of tools, resources, prompts, and capability boundaries. | No operational ownership map for auth, retries, and safety responsibilities. |
| Build an MCP Server Tutorial | Great first implementation walkthrough and working local setup path. | No migration blueprint for teams already running provider-specific tool calling. |
MCP architecture in practice
| Layer | Primary responsibility | Common mistake |
|---|---|---|
| Host | Owns user session, model runtime, and client lifecycle. | Treating host as a thin UI and pushing operational logic elsewhere. |
| MCP Client | Maintains stateful protocol session and transport behavior. | Ignoring backpressure, timeout, and reconnect semantics. |
| MCP Server | Exposes tools/resources/prompts with stable contract surface. | Mixing tool contract changes with backend experiments without versioning. |
| Policy/Governance Layer | Evaluates tool calls before execution and enforces approvals. | Implementing governance in prompt text instead of architecture. |
JSON-RPC wire flow
Message flow quality is where integration bugs surface first. Start with predictable initialize and capability negotiation behavior.
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"clientInfo": { "name": "my-host", "version": "1.2.0" },
"capabilities": {
"tools": {},
"resources": {},
"prompts": {}
}
}
}{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "query_database",
"arguments": {
"query": "SELECT count(*) AS active_users FROM users WHERE active = true",
"limit": 1
}
}
}import { Server } from "@modelcontextprotocol/sdk/server";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio";
const server = new Server({
name: "analytics-server",
version: "1.0.0",
});
server.setRequestHandler("tools/list", async () => ({
tools: [
{
name: "query_database",
description: "Execute read-only SQL against analytics DB",
inputSchema: {
type: "object",
properties: {
query: { type: "string" },
limit: { type: "number", default: 100 },
},
required: ["query"],
},
},
],
}));
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
if (name !== "query_database") {
throw new Error("Unknown tool");
}
const rows = await analyticsDb.query(args.query, args.limit);
return { content: [{ type: "text", text: JSON.stringify(rows) }] };
});
await server.connect(new StdioServerTransport());MCP vs alternatives
| Dimension | MCP | Provider Function Calling | Direct API SDK |
|---|---|---|---|
| Provider lock-in | Low | High | Medium |
| Tool discovery | Dynamic capability listing | Static schema in app code | Manual docs and custom adapters |
| Migration cost | Medium upfront, lower long-term | Low upfront, higher at scale | Variable and often repetitive |
| Cross-model reuse | High | Low | High but non-standard for LLM tools |
| Governance insertion point | Natural pre-dispatch boundary | Custom per provider | Gateway-centric, not model-aware |
Migration plan
Do not migrate everything in one cut. Phase by risk and ownership.
| Phase | Primary task | Exit criteria |
|---|---|---|
| Phase 1: Inventory | List existing function calls, owners, auth scopes, and failure modes. | Every call path has an owner and risk classification. |
| Phase 2: Wrap | Expose high-value read-only calls via MCP servers first. | Read traffic runs through MCP with no regression in latency SLO. |
| Phase 3: Govern | Insert policy checks, approvals, and output safety before write rollout. | No unapproved high-risk write can execute. |
| Phase 4: Cutover | Decommission duplicated provider-specific function schemas. | Single MCP contract is source of truth per tool domain. |
Limitations and tradeoffs
Higher upfront architecture effort
MCP standardization pays off later, but onboarding is not free.
Contract governance becomes mandatory
Tool schemas now behave like public APIs and need version discipline.
Operational maturity requirement
You need observability and policy controls or failures stay invisible for too long.