Skip to content
August 28, 20266 min readBy Dzaki Amri Zaidaan

Model Context Protocol (MCP): Building Interoperable AI Agent Tool Ecosystems

Model Context Protocol (MCP) is emerging as the USB-C standard for AI agents, enabling seamless integration with local filesystems, databases, and APIs. This article dives into MCP's architecture, provides a production-grade TypeScript example, and analyzes performance trade-offs for building scalable agent ecosystems.

#AI & ML#Architecture
robot and human hands reaching toward ai text

The Problem & Industry Shift

AI agents are only as powerful as the tools they can access. Historically, connecting an LLM to a filesystem, a PostgreSQL database, or GitHub required bespoke integrations: each tool had its own API, authentication, and data format. This led to fragile, one-off code that didn't scale across models or applications. The industry needed a standardized way to expose tools to AI models—something like a USB-C for AI.

Enter the Model Context Protocol (MCP), an open standard introduced by Anthropic in late 2024 [1]. MCP provides a universal, client-server architecture that decouples AI applications from the underlying tools. Instead of writing custom glue code for every tool, developers can implement an MCP server once and connect it to any MCP-compatible client (e.g., Claude Desktop, VS Code, or custom agents). This shift is analogous to how USB-C standardized device connectivity, and it's rapidly gaining adoption across the industry.

Architecture & Core Mechanics

MCP follows a client-server model with three primary components:

  • MCP Host: The AI application (e.g., an agent runtime) that initiates requests.
  • MCP Client: A lightweight connector within the host that communicates with servers.
  • MCP Server: A process that exposes tools, resources, and prompts via a standardized JSON-RPC 2.0 interface.

Communication can occur over stdio (for local processes) or HTTP/SSE (for remote servers). The protocol defines primitives for:

  • Tools: Functions the model can invoke (e.g., query_database).
  • Resources: Data that can be read (e.g., file contents).
  • Prompts: Reusable prompt templates.

Below is a simplified data flow diagram:

+----------------+    JSON-RPC    +----------------+    Native API    +----------------+
|                | <------------> |                | <--------------> |                |
|  MCP Host      |                |  MCP Server    |                  |  Tool (e.g.,   |
|  (Agent)       |                |  (Tool Adapter)|                  |  PostgreSQL)   |
|                |                |                |                  |                |
+----------------+                +----------------+                  +----------------+

Production Code Example

Let's build a minimal MCP server in TypeScript that exposes a tool to query a PostgreSQL database. We'll use the official @modelcontextprotocol/sdk [2].

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { Pool } from "pg";

// 1. Initialize the MCP server with metadata
const server = new McpServer({
  name: "postgres-tool",
  version: "1.0.0",
});

// 2. Set up a PostgreSQL connection pool (reuse connections for performance)
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 10, // limit concurrent connections to avoid exhausting the DB
});

// 3. Register a tool named "query_postgres"
server.tool(
  "query_postgres",
  { sql: z.string().describe("SQL query to execute") },
  async ({ sql }) => {
    try {
      // 4. Execute the query with a timeout to prevent long-running queries
      const result = await Promise.race([
        pool.query(sql),
        new Promise((_, reject) => setTimeout(() => reject(new Error("Query timeout")), 5000)),
      ]);
      return {
        content: [{ type: "text", text: JSON.stringify(result.rows) }],
      };
    } catch (error) {
      return {
        isError: true,
        content: [{ type: "text", text: `Query failed: ${(error as Error).message}` }],
      };
    }
  }
);

// 5. Start the server on stdio (for local integration)
const transport = new StdioServerTransport();
await server.connect(transport);

Critical engineering decisions:

  • Connection pooling prevents the server from opening a new DB connection per request, which would be slow and resource-intensive.
  • Timeout enforcement protects the agent from hanging on a slow query.
  • Error handling returns structured errors to the model, allowing it to self-correct.

Performance, Cost & Trade-offs

MCP introduces a layer of abstraction that adds latency. In a local stdio setup, the overhead is minimal (~1-2 ms per call). For remote servers over HTTP/SSE, network round-trips can add 20-50 ms, which is acceptable for most agent workflows but critical for low-latency applications.

Security considerations:

  • Tool authorization: MCP servers must validate that the AI model is allowed to invoke a tool. The protocol itself doesn't enforce permissions; the server must implement its own checks.
  • Data exposure: Exposing a database via MCP means the model can run arbitrary SQL. Use read-only roles, query timeouts, and allowlists to mitigate risks.
  • Supply chain: Only use MCP servers from trusted sources, as they execute with the host's privileges.

Cost implications:

  • MCP reduces integration development time, but each tool call consumes tokens (the tool definition and response are sent to the model). Keep tool descriptions concise to minimize token usage.
  • For high-frequency calls, consider caching tool responses or batching operations.

Actionable Checklist / Summary

When adopting MCP in production, follow these steps:

  1. Start with stdio for local tools to minimize latency and simplify debugging.
  2. Implement authentication and authorization inside your MCP server—never rely on the client alone.
  3. Define clear tool schemas using JSON Schema to reduce model errors.
  4. Add observability: log all tool invocations and responses for auditing and debugging.
  5. Test with multiple models to ensure tool descriptions are unambiguous.
  6. Monitor resource usage (CPU, memory, connections) to prevent server overload.
  7. Keep MCP SDKs updated to benefit from security patches and new features.

References