Model Context Protocol (MCP)
Executive Summary
Model Context Protocol (MCP) is an open protocol, introduced by Anthropic in November 2024, that standardizes how AI applications expose tools, resources, and prompts to language models. MCP solves the M×N integration problem: without a standard protocol, every AI application must build custom integrations with every data source and tool system — a combinatorial complexity that does not scale. With MCP, each data source or tool system exposes a standard MCP server; any MCP-compatible AI application connects to it once. This chapter covers MCP's architecture, server design patterns, client integration, and enterprise deployment considerations. AI architects evaluating integration strategies and engineers building MCP servers for enterprise tool ecosystems should read this chapter.
Note: MCP was introduced in November 2024 and continues to evolve. Verify current SDK versions and protocol specifications at modelcontextprotocol.io before implementing.
Learning Objectives
- Explain MCP's role in solving the M×N integration problem
- Describe MCP's three primitive types: Tools, Resources, and Prompts
- Implement an MCP server exposing clinical data tools using the Python SDK
- Configure an MCP client connection in a Claude-powered application
- Identify the security and authorization considerations for MCP in enterprise environments
Business Problem
A Reference Healthcare Organization's AI platform integrates with eight backend systems: Epic EHR via FHIR R4, a formulary database, a clinical guidelines library, a laboratory results system, a radiology reporting system, a scheduling system, a billing system, and a compliance audit log. Without a standardized protocol, each integration requires custom code: custom authentication handling, custom serialization, custom error handling, and custom schema definition — multiplied by the number of AI applications consuming these integrations.
MCP replaces this multiplication with addition: each backend system exposes one MCP server; each AI application connects to the servers it needs through one standard client interface. When a new backend system is added, it requires only one new MCP server. When a new AI application is added, it connects to existing servers immediately.
Why This Technology Exists
The pre-MCP landscape for AI tool integration mirrors the pre-REST landscape for web APIs: every system had its own integration pattern, every consumer needed system-specific knowledge, and integration complexity scaled with the number of systems and consumers.
REST (and later OpenAPI) standardized HTTP-based API design, dramatically reducing integration overhead. MCP applies the same standardization logic to the tool-and-context interface between AI applications and the systems they access.
MCP builds on the Language Server Protocol (LSP) design precedent from developer tooling, where a standard protocol between editors and language servers allowed any editor to support any language without bilateral integration work.
Conceptual Explanation
MCP Primitives
MCP defines three primitives:
Tools: Functions the AI model can call with arguments to perform actions or retrieve data. Tools are the MCP equivalent of LLM function/tool calls — they have a name, description, and input schema. Tool execution has side effects and produces results. Example: get<em>patient</em>summary(patient<em>id), check</em>drug<em>interaction(drug</em>a, drug_b).
Resources: Read-only data sources the AI model can reference. Resources are identified by URIs and provide content (text, binary) that the model can include in its context. Example: clinical://guidelines/sepsis-management, patient://P-12345/current-medications.
Prompts: Reusable prompt templates the server exposes. Prompts can be parameterized and composed. Example: a discharge summary prompt template that the server provides with configurable patient context fields.
MCP Transport
MCP supports two transports:
- stdio: Server runs as a subprocess; client communicates via stdin/stdout. Simple; appropriate for local tool access.
- HTTP with SSE (Server-Sent Events): Server runs as a service; client connects via HTTP. Required for multi-user, remotely hosted MCP servers.
Connection Lifecycle
Client Server
│ │
│── initialize ──────────────> │ Exchange capabilities
│<─ initialized ───────────── │
│ │
│── tools/list ──────────────> │ Discover available tools
│<─ {tools: [...]} ────────── │
│ │
│── tools/call ──────────────> │ Execute tool
│ {name, arguments} │
│<─ {content: [...]} ──────── │ Return resultCore Architecture
Implementation Patterns
Pattern 1: MCP Server for Clinical Data Access
"""
MCP server exposing clinical data tools for HMS integration.
Educational Example — Illustrative MCP server implementation.
Not intended for clinical decision making.
Prerequisites:
pip install mcp # Verify current package name at modelcontextprotocol.io
"""
from __future__ import annotations
import json
from typing import Any
try:
import mcp.server.stdio
import mcp.types as types
from mcp.server import NotificationOptions, Server
from mcp.server.models import InitializationOptions
MCP_AVAILABLE = True
except ImportError:
MCP_AVAILABLE = False
print("MCP SDK not installed. See modelcontextprotocol.io for installation instructions.")
# ── Stub backend clients ──────────────────────────────────────────────────────
class FHIRClient:
"""Stub FHIR R4 client — production uses real FHIR R4 endpoint."""
def get_patient(self, patient_id: str) -> dict:
return {
"resourceType": "Patient",
"id": patient_id,
"name": [{"family": "[SURNAME]", "given": ["[GIVEN]"]}],
"gender": "unknown",
"birthDate": "1970-01-01",
}
def get_conditions(self, patient_id: str) -> list[dict]:
return [
{"code": "G47.33", "display": "Obstructive sleep apnea (adult)"},
{"code": "E11.9", "display": "Type 2 diabetes mellitus without complications"},
]
def get_medications(self, patient_id: str) -> list[dict]:
return [
{"medication": "Metformin 500 mg", "status": "active"},
{"medication": "Lisinopril 10 mg", "status": "active"},
]
class GuidelinesSearchClient:
"""Stub guidelines search client — production queries vector store."""
def search(self, query: str, limit: int = 3) -> list[dict]:
return [
{
"id": "guideline-001",
"title": "Synthetic Guideline (Educational Example Only)",
"content": "This is placeholder guideline content for illustration purposes.",
"relevance_score": 0.85,
}
]
if MCP_AVAILABLE:
# ── Server setup ──────────────────────────────────────────────────────────
fhir_client = FHIRClient()
guidelines_client = GuidelinesSearchClient()
server = Server("hms-clinical-mcp-server")
# ── Tool definitions ──────────────────────────────────────────────────────
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""Expose available tools to MCP clients."""
return [
types.Tool(
name="get_patient_clinical_summary",
description=(
"Retrieve a patient's clinical summary from the EHR system. "
"Returns demographics, active diagnoses, and current medications. "
"Use when clinical context about a specific patient is required."
),
inputSchema={
"type": "object",
"properties": {
"patient_id": {
"type": "string",
"description": "Patient identifier (EHR system ID)",
}
},
"required": ["patient_id"],
},
),
types.Tool(
name="search_clinical_guidelines",
description=(
"Search the institutional clinical guidelines library using natural language. "
"Returns relevant guideline excerpts with source citations. "
"Use when clinical evidence or criteria are needed for a specific condition or procedure."
),
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Natural language search query",
},
"max_results": {
"type": "integer",
"description": "Maximum results to return (1–10, default 3)",
"default": 3,
"minimum": 1,
"maximum": 10,
},
},
"required": ["query"],
},
),
]
# ── Tool handlers ─────────────────────────────────────────────────────────
@server.call_tool()
async def handle_call_tool(
name: str, arguments: dict[str, Any] | None
) -> list[types.TextContent]:
"""Route tool calls to backend implementations."""
args = arguments or {}
if name == "get_patient_clinical_summary":
patient_id = args.get("patient_id")
if not patient_id:
return [types.TextContent(
type="text",
text=json.dumps({"error": "patient_id is required"})
)]
patient = fhir_client.get_patient(patient_id)
conditions = fhir_client.get_conditions(patient_id)
medications = fhir_client.get_medications(patient_id)
summary = {
"patient_id": patient_id,
"demographics": {
"gender": patient.get("gender"),
"birth_date": patient.get("birthDate"),
},
"active_diagnoses": [
{"code": c["code"], "display": c["display"]}
for c in conditions
],
"current_medications": [
{"medication": m["medication"], "status": m["status"]}
for m in medications
],
"disclaimer": "Educational Example — Not for clinical use",
}
return [types.TextContent(type="text", text=json.dumps(summary, indent=2))]
elif name == "search_clinical_guidelines":
query = args.get("query", "")
max_results = min(args.get("max_results", 3), 10)
results = guidelines_client.search(query, limit=max_results)
response = {
"query": query,
"result_count": len(results),
"results": results,
"disclaimer": "Educational Example — Verify guideline accuracy before clinical use",
}
return [types.TextContent(type="text", text=json.dumps(response, indent=2))]
else:
return [types.TextContent(
type="text",
text=json.dumps({"error": f"Unknown tool: {name}"})
)]
# ── Resource definitions ──────────────────────────────────────────────────
@server.list_resources()
async def handle_list_resources() -> list[types.Resource]:
"""Expose static resources available to MCP clients."""
return [
types.Resource(
uri="clinical://guidelines/index",
name="Clinical Guidelines Index",
description="Index of available clinical guideline categories in the institutional library",
mimeType="application/json",
),
]
@server.read_resource()
async def handle_read_resource(uri: str) -> str:
"""Return resource content by URI."""
if uri == "clinical://guidelines/index":
index = {
"categories": [
"Cardiology", "Endocrinology", "Neurology",
"Oncology", "Pulmonology", "Infectious Disease"
],
"last_updated": "2026-01-15",
"disclaimer": "Educational Example",
}
return json.dumps(index, indent=2)
raise ValueError(f"Unknown resource URI: {uri}")
# ── Server entry point ────────────────────────────────────────────────────
async def run_server():
"""Run the MCP server over stdio transport."""
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="hms-clinical-mcp-server",
server_version="1.0.0",
capabilities=server.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)Pattern 2: Connecting to MCP Servers from a Claude Application
"""
MCP client integration for a Claude-based application.
Educational Example — Illustrative client configuration.
Verify current MCP client API at modelcontextprotocol.io/docs
"""
from __future__ import annotations
import asyncio
import json
from typing import Any
try:
from anthropic import Anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
MCP_CLIENT_AVAILABLE = True
except ImportError:
MCP_CLIENT_AVAILABLE = False
async def run_agent_with_mcp_server(
user_query: str,
server_script_path: str,
system_prompt: str = "",
) -> str:
"""
Run a Claude agent that uses tools from a local MCP server.
The agent:
1. Connects to the MCP server via stdio
2. Discovers available tools
3. Runs the agent loop, routing tool calls to the MCP server
"""
if not MCP_CLIENT_AVAILABLE:
return "MCP client SDK not available — install required packages"
anthropic_client = Anthropic()
server_params = StdioServerParameters(
command="python",
args=[server_script_path],
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# Discover tools from the MCP server
tools_response = await session.list_tools()
# Convert MCP tool definitions to Anthropic API tool format
anthropic_tools = [
{
"name": tool.name,
"description": tool.description,
"input_schema": tool.inputSchema,
}
for tool in tools_response.tools
]
messages = [{"role": "user", "content": user_query}]
# Agent loop
for _ in range(10): # max_iterations circuit breaker
response = anthropic_client.messages.create(
model="claude-opus-4-8", # Verify current model ID at docs.anthropic.com
max_tokens=4096,
system=system_prompt,
tools=anthropic_tools,
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
# Extract final text response
for block in response.content:
if hasattr(block, "text"):
return block.text
return "No text response produced"
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type != "tool_use":
continue
# Route tool call through MCP session
mcp_result = await session.call_tool(
name=block.name,
arguments=block.input,
)
# Convert MCP result to Anthropic tool result format
result_content = ""
if mcp_result.content:
result_content = mcp_result.content[0].text if hasattr(mcp_result.content[0], "text") else str(mcp_result.content[0])
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result_content,
})
messages.append({"role": "user", "content": tool_results})
return "Max iterations reached without final response"
def run_mcp_agent_example():
"""Example entry point — illustrative use."""
system = (
"You are a clinical data assistant for a Reference Healthcare Organization. "
"Use the available tools to retrieve patient information and clinical guidelines "
"when needed to answer questions. "
"Educational Example — Not for clinical decision making."
)
result = asyncio.run(run_agent_with_mcp_server(
user_query="Retrieve the clinical summary for patient P-DEMO-001",
server_script_path="examples/mcp/01-mcp-server-example.py",
system_prompt=system,
))
print(result)Enterprise Considerations
Authorization model. MCP servers accept tool calls from any authenticated client. In enterprise deployments, the server must enforce authorization at the tool level: which clients (AI applications) are permitted to call which tools, and for which data scope. A clinical data MCP server used by both a general-purpose assistant and a specialized prior authorization agent should not grant both the same tool access.
Multi-tenant server design. A single MCP server can serve multiple AI applications. Design the server to accept a client identity token (from the MCP connection or request metadata) and enforce per-client authorization policies. Do not rely on the AI application to self-limit its tool use.
Stateless vs. stateful servers. MCP servers should be stateless where possible — each tool call is independent and does not depend on prior session state. Stateful servers (maintaining session context across calls) are harder to scale horizontally and introduce failure modes when sessions are lost.
Registry and discovery. In an enterprise with many MCP servers, establish a central MCP server registry. AI applications query the registry to discover which servers are available and what capabilities they expose. This prevents hardcoded server lists in application configuration.
Security Considerations
Tool description injection. The tool description is rendered in the LLM's context. A malicious or compromised MCP server could craft tool descriptions designed to manipulate the LLM's behavior. Validate and review tool descriptions from third-party MCP servers before deploying them in production contexts.
Data exfiltration via tool results. Tool results are returned to the LLM, which includes them in its response. A compromised MCP server could return data from systems it should not have access to, and the LLM may include that data in its output to the user. Apply defense-in-depth: MCP server access controls, network segmentation, and output monitoring.
Server authentication. HTTP+SSE MCP servers must enforce client authentication. Use mutual TLS or token-based authentication. Do not expose MCP servers on public networks without authentication.
Healthcare Example
Educational Example — Illustrative MCP Architecture. Not intended for clinical decision making.
A Reference Healthcare Organization's MCP architecture deploys five MCP servers:
| MCP Server | Backend System | Transport | Authorization |
|---|---|---|---|
| EHR Integration Server | Epic FHIR R4 | HTTP+SSE | OAuth 2.0 / SMART on FHIR |
| Clinical Guidelines Server | Internal vector store | HTTP+SSE | API key per application |
| Formulary Server | Drug database | HTTP+SSE | Service account token |
| Lab Results Server | Laboratory IS | HTTP+SSE | OAuth 2.0 |
| Compliance Audit Server | Audit log (write-only) | HTTP+SSE | Service account token |
Each AI application declares which MCP servers it needs. The organization's central gateway enforces that AI applications only connect to authorized servers. The EHR server enforces SMART on FHIR scopes so the prior authorization agent can only access patient data for the encounter it is processing.
Common Mistakes
No tool authorization at the server. Expecting the AI application to not call tools it should not use is not a security boundary. The MCP server must enforce authorization independent of the client's intentions.
Including PHI in tool descriptions. Tool descriptions are static metadata visible to all clients. Never include patient-specific data, example PHI, or data that varies by request in tool descriptions.
Synchronous blocking in async server. MCP servers are typically async. Calling synchronous blocking I/O (database queries, HTTP requests without async) in an async MCP handler blocks the event loop and degrades throughput under concurrent requests.
No error schema. Tool handlers that return unstructured error messages make it difficult for the LLM to reason about recoverable versus unrecoverable errors. Define a consistent error response schema (with error_code, message, recoverable fields) and apply it in every tool handler.
Best Practices
- Design MCP tool descriptions as if writing for an intelligent engineer unfamiliar with your system — precision and completeness matter for LLM comprehension
- Enforce tool authorization at the server level, not the client level
- Make MCP servers stateless where possible for horizontal scalability
- Define consistent error response schemas across all tools on a server
- Establish a central MCP server registry for enterprise discoverability
- Validate third-party MCP server tool descriptions before production deployment
- Use HTTP+SSE transport for multi-user, remotely hosted MCP servers; stdio only for local single-process tools
Alternatives
| Approach | When Appropriate | Trade-off vs. MCP |
|---|---|---|
| Custom tool SDK | Maximum control; unique requirements | No standardization benefit; N×M complexity |
| OpenAPI-based tool calling | When backend systems already have OpenAPI specs | Not standardized at the AI layer; no Resources/Prompts primitives |
| LangChain tools | Within LangChain ecosystems | Framework-specific; not cross-framework |
| Native Anthropic tool calling | Single-application deployments | No server abstraction; tight coupling |
Interview Questions
Q1: What problem does MCP solve, and why does it matter at enterprise scale?
Category: Architecture Difficulty: Mid-level Role: AI Architect / ML Engineer
Answer Framework:
MCP solves the M×N integration problem. Without a standard protocol, M AI applications integrating with N tool/data systems requires M×N integrations, each with custom authentication, serialization, and error handling. At enterprise scale — say, 10 AI applications and 20 data systems — that is 200 integration pairs.
With MCP, each data system exposes one standard MCP server (N servers); each AI application connects to the servers it needs through one standard client interface (M clients). Adding a new data system requires building one MCP server, not one integration per AI application. Adding a new AI application requires only configuring which existing servers it connects to.
Beyond the integration math, MCP provides a uniform tool discovery mechanism (list_tools), a standard authorization point (the server), and a consistent error contract — all of which reduce the operational overhead of maintaining AI integrations in production.
Key Points to Hit:
- M×N reduction to M+N is the core value proposition
- Standard discovery (list_tools) enables dynamic tool discovery without hardcoded schemas
- Authorization is enforced at the server, making it a proper security boundary
- The protocol builds on LSP's precedent in developer tooling
Q2: What are the three MCP primitive types, and what is each used for?
Category: Architecture Difficulty: Junior Role: ML Engineer
Answer Framework:
Tools are callable functions that perform actions or retrieve data and can have side effects. They have a name, description, and input schema. The LLM calls tools by name with arguments; the server executes them and returns results. Tools are the primary way agents interact with external systems.
Resources are read-only data sources identified by URIs. They provide content (text, binary) that the model can include in its context without executing a function call. Resources are appropriate for static or infrequently-updated content: configuration, reference documents, templates. A clinical guidelines index or a drug formulary catalog is a good resource use case.
Prompts are reusable, parameterized prompt templates that the server exposes. They allow server-side prompt management: the server defines standard prompts (e.g., "discharge summary generation") with required fields, and clients instantiate them with specific values. This keeps prompt logic close to the data it operates on.
Key Takeaways
- MCP solves the M×N integration problem: N backend systems expose MCP servers; M AI applications connect via standard client interfaces
- Three MCP primitives: Tools (callable functions with side effects), Resources (read-only data by URI), Prompts (reusable parameterized templates)
- Two transports: stdio (local subprocess) and HTTP+SSE (remote, multi-user)
- Tool authorization must be enforced at the MCP server level — the client is not a security boundary
- MCP tool descriptions are rendered in LLM context — treat them with the same rigor as system prompts
- Design MCP servers to be stateless for horizontal scalability
Glossary
| Term | Definition |
|---|---|
| MCP (Model Context Protocol) | Open protocol standardizing how AI applications expose tools, resources, and prompts to language models |
| MCP Server | A process that implements the MCP protocol and exposes tools, resources, or prompts for AI client consumption |
| MCP Client | The AI application component that connects to MCP servers, discovers capabilities, and routes tool calls |
| Tool (MCP) | A callable function exposed by an MCP server, with a name, description, and input schema |
| Resource (MCP) | A read-only data source exposed by an MCP server, identified by a URI |
| Prompt (MCP) | A reusable parameterized prompt template exposed by an MCP server |
| stdio transport | MCP communication via stdin/stdout of a subprocess — simple; local only |
| HTTP+SSE transport | MCP communication via HTTP with Server-Sent Events — required for remote, multi-user servers |
| M×N problem | The integration complexity where M consumers and N providers require M×N bilateral integrations |
Further Reading
In This Repository:
- Tool Design Patterns — Tool schema design principles applicable to MCP tool definitions
- Multi-Agent Systems — MCP as an integration layer for multi-agent architectures
- Agentic Security — MCP server security, tool description injection, authorization
External References:
- modelcontextprotocol.io — official MCP specification and SDK documentation
- Anthropic MCP announcement (November 2024) — background and design rationale
Previous: Agent Observability | Next: Agentic Security