MCP: The Universal Integration Layer for Agentic AI
Why MCP Exists: Escaping the N×M Integration Trap
Before a common protocol, an organization with N AI applications and M enterprise systems could face roughly N×M integration paths. A copilot might need one custom connector for GitHub, another for SharePoint, another for a CRM, and another for a historian; a second copilot would repeat much of that work with different tool schemas, authentication assumptions, and error handling. Function calling improved how a model requests an operation, but it did not standardize how a host discovers capabilities, reads context, manages sessions, or connects to diverse backends. MCP moves the integration boundary to a reusable server: an MCP host can connect through an MCP client to many servers, while each server can serve many compatible hosts.
The protocol was created in response to a practical gap in agentic AI: models can reason over structured descriptions, but enterprise value depends on safely reaching systems outside the model’s context window. MCP separates the model from the integration implementation. A server may wrap a REST API, execute a parameterized database query, retrieve a document, expose a repository operation, or translate a historian query into a small, model-friendly result. The model sees declared capabilities and structured results; it does not need to know whether the underlying implementation uses SQL, GraphQL, OAuth, an SDK, or an industrial gateway.
The protocol is maintained as an open specification and ecosystem rather than as a single vendor’s product. Teams should consult the current MCP specification and the official MCP repositories when pinning SDK versions, because protocol features and SDK ergonomics continue to evolve. Vendor neutrality also means resisting a common mistake: exposing every internal API directly to an agent. A well-designed MCP server is a policy and translation boundary, not merely a thin HTTP proxy.
Architecture: Hosts, Clients, Servers, and Primitives
An MCP host is the AI application that owns the user experience, model interaction, approval policy, and often the conversation state. An IDE assistant, enterprise chatbot, workflow engine, or autonomous agent can be a host. Inside it, an MCP client maintains a protocol connection to one MCP server, performs initialization and capability negotiation, lists available primitives, sends requests, and receives notifications. One host can create multiple clients, isolating a GitHub server from a database server and applying different trust policies to each.
An MCP server is a focused capability provider. It can be local or remote, and it commonly contains an adapter, validation layer, authorization checks, rate limits, audit logging, and a result normalizer. MCP’s principal primitives are tools, resources, and prompts. Tools are model-invocable actions such as search_issues, get_temperature, or create_purchase_request. Resources represent addressable context—documents, schemas, reports, repository files, or time-series snapshots—usually identified by URIs. Prompts are reusable interaction templates that help a host expose domain workflows without hard-coding every instruction in the application.
The flow below illustrates the separation of responsibilities. The host decides whether a proposed action needs user approval; the client handles MCP protocol mechanics; the server enforces backend-specific policy; and the enterprise system remains the system of record.
+----------------------+ MCP Client +----------------------+
| MCP Host | JSON-RPC over | MCP Server |
| model, UI, policy, | STDIO or HTTP | tools, resources, |
| approvals, sessions | <--------------------> | prompts, adapters |
+----------+-----------+ +----------+-----------+
| |
| model tool selection | validated calls
v v
+-----------+ +-------------------------+
| AI model | | GitHub, SQL, SharePoint |
+-----------+ | REST, PI, files, SaaS |
+-------------------------+
Capabilities are negotiated during initialization. A client and server exchange protocol-version information and capability declarations, such as whether the server supports tools, resources, prompts, subscriptions, or logging. The client must not assume that a feature exists merely because an SDK contains a method for it. Capability-aware behavior enables graceful degradation: a host can fall back from resource subscriptions to polling, or omit prompt discovery when a server does not advertise prompts.
Transports and JSON-RPC Mechanics
MCP uses a JSON-RPC communication model: requests have an identifier, a method, and parameters; responses return either a result or a structured error; notifications have no identifier and therefore require no response. Methods include initialization, capability discovery, listing primitives, calling tools, reading resources, and receiving server notifications. JSON-RPC is deliberately transport-neutral, which lets the same semantic operations travel over different connection mechanisms.
STDIO is well suited to local integrations. The host launches a server process and exchanges newline-delimited JSON-RPC messages through standard input and output. It is simple to sandbox, avoids opening a network listener, and works naturally for desktop applications and developer tools. The server must write protocol messages only to stdout; diagnostic logs belong on stderr. Process lifecycle, environment variables, executable permissions, and secret inheritance become important operational concerns.
HTTP-based transport is suitable for centralized or remotely deployed servers. Depending on the MCP implementation and current specification version, streamable HTTP and related session behavior may be used to deliver request/response traffic and server-initiated notifications. Reverse proxies, load balancers, TLS termination, connection affinity, request limits, and authentication must be designed explicitly. A remote server should never be treated as trusted simply because it speaks valid JSON-RPC: the host still needs endpoint allowlists, certificate validation, timeouts, and authorization.
A representative exchange looks like this:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{"tools":{}},"clientInfo":{"name":"operations-copilot","version":"1.0.0"}}}
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-06-18","capabilities":{"tools":{"listChanged":true},"resources":{}},"serverInfo":{"name":"plant-data","version":"1.2.0"}}}
{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"get_temperature","arguments":{"asset":"Furnace-04","minutes":30}}}
Production clients should correlate IDs safely, reject malformed responses, cap message size, handle cancellation, distinguish retryable from permanent errors, and avoid blindly replaying non-idempotent tools. Results should be bounded and typed where possible. A tool that returns a million log lines has technically succeeded but has failed as an agent interface; pagination, summaries, filters, and resource references are better designs.
Building a Small MCP Server
The following Python example uses the commonly adopted high-level server pattern from the MCP Python ecosystem. It exposes one read-only tool and one resource. In production, replace the in-memory data with a repository or service adapter, validate asset identifiers against an allowlist, enforce the caller’s identity, and add structured audit events.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("plant-data")
@mcp.tool()
def get_temperature(asset: str, minutes: int = 30) -> dict:
"""Return a bounded temperature summary for an approved asset."""
if asset not in {"Furnace-04", "Boiler-02"}:
raise ValueError("asset is not approved")
if not 1 <= minutes <= 1440:
raise ValueError("minutes must be between 1 and 1440")
# Replace this deterministic sample with a parameterized PI/REST query.
return {"asset": asset, "minutes": minutes,
"unit": "degC", "min": 418.2, "max": 431.7, "average": 425.4}
@mcp.resource("plant://assets/{asset}/schema")
def asset_schema(asset: str) -> str:
if asset not in {"Furnace-04", "Boiler-02"}:
raise ValueError("asset is not approved")
return '{"asset":"' + asset + '","measurements":["temperature","pressure"]}'
if __name__ == "__main__":
mcp.run(transport="stdio")
The tool description and argument schema become part of discovery, so names and descriptions should be precise rather than marketing-oriented. A database server should expose narrow operations such as find_customer_orders, not arbitrary SQL execution, unless a separate read-only query service, query parser, row limit, timeout, and data-loss policy are in place. For GitHub, expose scoped operations such as searching issues or reading a pull request; for SharePoint, expose approved site and document retrieval with tenant-aware authorization; for AVEVA PI, translate asset and time-window requests into safe AF or PI Web API queries and return units, quality flags, timestamps, and aggregation semantics.
Connecting Agents and Invoking Tools
A host generally discovers tools, converts their schemas into the model provider’s tool format, lets the model select a tool, asks the user for approval when policy requires it, and sends the selected arguments through MCP. The model never directly executes the tool. This distinction matters because the host can redact arguments, apply a risk score, require dual approval for write operations, and present the exact proposed action to a person.
A minimal TypeScript client illustrates the orchestration boundary. Exact import paths and transport constructors can vary by pinned SDK release, so treat this as a production-shaped example rather than a promise that every future SDK version has identical names.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const client = new Client({ name: "ops-agent", version: "1.0.0" },
{ capabilities: {} });
const transport = new StdioClientTransport({
command: "python",
args: ["./plant_server.py"]
});
await client.connect(transport);
const { tools } = await client.listTools();
const selected = tools.find(t => t.name === "get_temperature");
if (!selected) throw new Error("Required capability is unavailable");
// In a real host, insert model selection and human approval here.
const result = await client.callTool({
name: "get_temperature",
arguments: { asset: "Furnace-04", minutes: 30 }
});
console.log(JSON.stringify(result));
await transport.close();
The equivalent C# shape is useful in .NET enterprise applications. SDK namespaces and transport APIs should be pinned to a tested release, but the lifecycle remains the same: create a client, connect, inspect capabilities, call a named tool, and close the session.
using ModelContextProtocol.Client;
var client = await McpClientFactory.CreateAsync(
new StdioClientTransport(new StdioClientTransportOptions {
Name = "plant-data",
Command = "python",
Arguments = new[] { "./plant_server.py" }
}));
var tools = await client.ListToolsAsync();
var tool = tools.FirstOrDefault(t => t.Name == "get_temperature")
?? throw new InvalidOperationException("Tool unavailable");
var result = await client.CallToolAsync(
tool.Name,
new Dictionary<string, object?> {
["asset"] = "Furnace-04",
["minutes"] = 30
});
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(result));
await client.DisposeAsync();
For HTTP deployments, replace the STDIO transport with the SDK’s HTTP transport and supply TLS-validated endpoint configuration plus an authentication handler. Do not place bearer tokens in tool arguments or prompts. Credentials belong in the transport or server-side secret manager, and a server should exchange the caller’s identity for a narrowly scoped backend token rather than using one powerful shared account.
MCP Compared with Adjacent Integration Patterns
A traditional REST or GraphQL API is an application-facing contract for deterministic clients. MCP can sit in front of such APIs and make selected operations discoverable to agents, but it does not replace resource modeling, versioning, pagination, idempotency, or API management. A plugin system is usually tied to a particular host’s extension model; MCP is a protocol-level boundary intended to let different hosts consume the same server. Plugins may still be useful for UI integration, while MCP supplies the capability channel.
Function calling describes how a model emits structured intent for a function. MCP describes how a host discovers and invokes capabilities across a session, including resources, prompts, negotiation, transport, and server notifications. RAG retrieves passages for grounding but does not inherently define actions, identity, approvals, or write semantics. MCP resources can support retrieval and can be combined with a vector index, but MCP is broader than RAG and does not dictate embedding models or chunking.
The Language Server Protocol (LSP) is the closest architectural cousin. LSP standardized editor-to-language-service features such as diagnostics, completion, hover information, and navigation, removing editor-language N×M duplication. MCP applies a similar interoperability idea to model hosts and external capabilities. The domains differ: LSP is optimized for programming-language tooling and editor state, whereas MCP covers agent tools, contextual resources, prompts, and enterprise workflows. Both demonstrate the power of a stable protocol boundary, but neither eliminates the need for domain-specific semantics.
Enterprise Integration, Deployment, and Scale
In a small desktop deployment, one MCP server per integration can run locally over STDIO. In an enterprise, servers can be grouped by trust domain: a read-only knowledge server, a software-engineering server, an operational-technology server, and a controlled transaction server. Kubernetes, container isolation, private networking, a service mesh, and an API gateway can provide repeatable deployment, while a serverless design may suit short-lived read operations. Long-running jobs should return a job identifier or resource URI rather than blocking a model request indefinitely.
Scalability is mostly ordinary distributed-systems engineering. Use connection pooling for databases, bounded concurrency, circuit breakers for SaaS dependencies, caching for stable resources, pagination for searches, and backpressure for subscriptions. Stateless HTTP servers simplify horizontal scaling; if sessions or streams require affinity, use a deliberate session store and understand the failure mode. Tool results should include correlation IDs, timestamps, source systems, units, freshness, and quality indicators—especially for operational data such as AVEVA PI measurements.
Observability must cover both protocol and business behavior. Emit metrics for initialization failures, tool latency, error classes, payload sizes, authorization denials, approval outcomes, and downstream dependency health. Trace the chain from user request to model decision to MCP call to backend query, but redact secrets, personal data, credentials, and sensitive prompt content. Keep an immutable audit record for consequential actions: principal, server, tool, arguments after redaction, approval evidence, result status, and backend transaction ID.
Governance begins with an inventory of servers and tools. Classify tools as read, reversible write, irreversible write, or privileged administration; assign owners; publish data-retention rules; scan server packages and containers; rotate credentials; and require change review for schemas and descriptions. A tool description is part of the model’s decision surface, so prompt injection can occur through hostile documents, resource content, or misleading metadata. Treat all external content as untrusted, separate instructions from data, constrain tool arguments with schemas and business rules, and never let retrieved text silently override system policy.
Security: Authentication Is Only the First Gate
For local STDIO, process isolation, filesystem permissions, executable allowlists, environment hygiene, and OS-level sandboxing are central. For HTTP, use modern TLS, authenticated clients, short-lived tokens, audience and issuer validation, replay protection where appropriate, and strict origin and network controls. Authentication answers who is connecting; authorization must answer which user may invoke which tool against which tenant, asset, site, repository, document, or time range.
High-risk tools need explicit consent and ideally a two-step design: first produce a preview or dry-run, then commit only after confirmation. Prevent SSRF in URL-fetching tools, injection in SQL and shell adapters, path traversal in file resources, oversized responses, denial-of-service through expensive queries, and accidental cross-tenant caching. A server should fail closed when identity, asset scope, or data quality is ambiguous. For industrial systems, place MCP outside the control network where possible, expose read-only historian replicas or gateways, and never allow a language model to bypass established safety interlocks.
The Road Ahead for Agentic AI
MCP’s lasting value is less about any single tool call than about giving agentic systems a common nervous system for interoperability. Enterprise copilots can combine governed knowledge resources with approved actions; multi-agent systems can delegate specialized work to servers without embedding every connector in every agent; and workflow platforms can add model reasoning while retaining conventional authorization and transaction controls. As schemas, subscriptions, identity patterns, and conformance testing mature, organizations can build capability catalogs instead of isolated demonstrations.
The universal layer will succeed only if it remains modest about what a protocol can solve. MCP can standardize discovery and message exchange, but semantic quality, least privilege, reliability, provenance, and human accountability remain application responsibilities. Teams that treat MCP servers as well-governed products—small interfaces, strong contracts, observable behavior, and explicit risk boundaries—can turn the N×M problem into a more manageable ecosystem of N hosts and M reusable, policy-aware capability providers.
