MCP Is the USB Port for AI Agents — Here's What That Means in Production
Model Context Protocol became the default AI tool interop standard in 2025. Every serious agent stack uses it now. Here's what it actually is, what it solves, and how we wire it into production LangGraph systems.
The problem wasn't the agents themselves. By 2024, everyone had built a few. The problem was the spaghetti. Every proof-of-concept, every internal agent, every client demo was a bespoke integration nightmare. You’d build an agent that could query your internal CRM, great. Then your marketing team wanted the same capability in their Claude Desktop setup. Your ops team wanted it integrated into a LangGraph workflow. And suddenly, you were rewriting the CRM integration three times, each with a slightly different API wrapper, schema definition, and invocation pattern.
This wasn't just inefficient; it was how AI projects died. We saw it constantly at Verel Systems: companies accumulating AI debt, tangled prompt chains, unmonitored agents, and demo-quality RAG that never made it past pilot purgatory. Before MCP, integrating tools meant rebuilding them for every framework. OpenAI had its function calling schemas. LangChain had its Tool objects. AutoGen had its own function_specs. None of it was portable. You couldn't just "plug in" a tool. You had to re-engineer the connection every single time. This churn wasted millions in engineering hours and pushed deployment timelines out by months.
MCP: The Universal Socket for AI Tools
Model Context Protocol (MCP) changed that. Developed by Anthropic and rapidly adopted, MCP is now the default interoperability layer for AI agents and external services. Think of it as the USB port for AI. Before USB, every peripheral – keyboard, mouse, printer – had its own proprietary port and driver. USB standardized the physical connection and the communication protocol, making devices plug-and-play. MCP does the same for AI tools.
At its core, MCP is a client-server protocol. It defines how an AI agent (the client) discovers, invokes, and receives results from external tools (exposed by an MCP server).
- ▸Tool Discovery: An MCP server exposes a manifest of the tools it offers, including their schemas (input parameters, expected output) and descriptions. This manifest is typically a JSON document adhering to the MCP specification.
- ▸Tool Invocation: The AI agent, having discovered a tool, constructs a request based on the tool's schema and sends it to the MCP server.
- ▸Result Streaming: The MCP server executes the tool's logic (e.g., calling an external API, querying a database) and streams the results back to the agent. This streaming capability is crucial for long-running tasks or when dealing with large data payloads, preventing agents from blocking.
Any model, any agent framework, can now use any MCP server. It decouples the tool's implementation from the agent's execution environment. This isn't just a convenience; it's a fundamental shift that allows us to move AI projects from experimental spaghetti to robust production systems.
The 2026 MCP Landscape: It Won
The adoption curve for MCP was steep and decisive. By early 2026, it became the de facto standard.
- ▸LangGraph: Every agent built with LangGraph can now be exposed natively as an MCP endpoint. This means a complex multi-agent system, previously a black box, can become a discoverable, invocable tool for other agents or even human users via MCP-aware clients. We routinely use this to expose sophisticated analytical agents to less technical stakeholders.
- ▸n8n: The popular automation platform n8n quickly integrated MCP community nodes, allowing users to easily connect their workflows to any MCP server or expose n8n workflows as MCP tools. This democratized access to agent capabilities for business users.
- ▸Claude Desktop: Anthropic's Claude Desktop client uses MCP for its internal tool integrations. This was a significant driver of adoption. If you want Claude to interact with your internal systems, you expose them via MCP. Simple.
- ▸Cursor: The AI-native code editor, Cursor, uses MCP to integrate external code context and execution environments. This allows developers to extend Cursor's capabilities with custom tools that operate on their specific codebase or infrastructure.
When you see this level of native support across major platforms, it’s not a "might be worth considering" situation. MCP is the right choice for any new external tool integration in an agentic system. It eliminates redundant integration work and builds a foundation for scalable, interoperable AI deployments. We now tell clients who are still building bespoke integrations that they are actively accumulating AI debt, and it will cost them.
Where We Use It at Verel
At Verel, MCP is a foundational component of our production AI architectures. It's how we rescue failed POCs and turn them into shippable systems.
- ▸Exposing Client-Specific Tools as MCP Servers: This is our bread and butter. Many clients have critical internal APIs, legacy systems, or proprietary databases that agents need to interact with. Instead of building custom wrappers for each agent framework, we build a single MCP server. This server acts as a standardized gateway. For instance, we recently built an MCP server for a financial client that exposed 12 tools for their internal trading platform API, including
get_portfolio_balance,execute_trade, andget_market_data. This single server now serves agents built in LangGraph, agents running on Claude Desktop, and even custom internal Python scripts. This approach dramatically reduces integration time – we’ve seen a 40% reduction in time-to-production for new agent capabilities using this method. - ▸Connecting LangGraph Agents to Claude Desktop for Client Demos: We often demonstrate complex agentic workflows to clients. By exposing our LangGraph agents as MCP endpoints, we can easily connect them to Claude Desktop. This allows clients to interact with sophisticated multi-agent systems using a familiar, intuitive interface, fostering trust and accelerating adoption. The agent running in Claude Desktop can then invoke a Verel-built LangGraph agent that might, for example, orchestrate a data pull from a legacy system, perform an analysis, and then return a summarized report directly into the Claude conversation.
- ▸Building Reusable Tool Servers: We've developed a library of common MCP servers for standard services like Salesforce, HubSpot, Jira, and various cloud provider APIs (AWS, Azure, GCP). These aren't just generic wrappers; they're production-hardened servers with proper authentication, error handling, and rate limiting. This allows us to rapidly deploy agents for new clients, reusing validated components rather than reinventing the wheel. This approach directly combats the "spaghetti to production" problem, ensuring that the underlying infrastructure is robust from day one.
A Real Implementation: CRM Integration with MCP
Let's walk through a concrete example: exposing a CRM API via an MCP server and then connecting it to both a LangGraph agent and Claude Desktop. We'll use a simplified CRM API for demonstration.
First, the MCP server in Python. We'll assume a basic crm_api_client.py exists, handling actual API calls.
# crm_api_client.py (simplified for example)
class CRMApiClient:
def get_contact_details(self, contact_id: str):
# In a real system, this would make an API call to Salesforce/HubSpot
print(f"CRM API: Fetching details for contact_id: {contact_id}")
if contact_id == "contact-123":
return {"id": "contact-123", "name": "Alice Smith", "email": "alice@example.com", "company": "Acme Corp"}
return None
def create_lead(self, name: str, email: str, company: str):
print(f"CRM API: Creating lead for {name} from {company}")
# Simulate lead creation
new_lead_id = f"lead-{len(name) + len(email) + len(company)}"
return {"id": new_lead_id, "status": "new", "name": name, "email": email, "company": company}
# mcp_crm_server.py
import uvicorn
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import Dict, Any, List
from crm_api_client import CRMApiClient
app = FastAPI(title="CRM MCP Server", description="Exposes CRM functionalities via MCP")
crm_client = CRMApiClient()
# Define Pydantic models for tool inputs
class GetContactDetailsInput(BaseModel):
contact_id: str = Field(..., description="The unique identifier for the contact.")
class CreateLeadInput(BaseModel):
name: str = Field(..., description="The name of the lead.")
email: str = Field(..., description="The email address of the lead.")
company: str = Field(..., description="The company the lead belongs to.")
# MCP Tool Manifest
MCP_TOOLS_MANIFEST = {
"tools": [
{
"name": "get_contact_details",
"description": "Retrieves detailed information about a contact from the CRM.",
"input_schema": GetContactDetailsInput.model_json_schema(),
"output_schema": {
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"email": {"type": "string"},
"company": {"type": "string"}
}
}
},
{
"name": "create_lead",
"description": "Creates a new lead entry in the CRM.",
"input_schema": CreateLeadInput.model_json_schema(),
"output_schema": {
"type": "object",
"properties": {
"id": {"type": "string"},
"status": {"type": "string"},
"name": {"type": "string"},
"email": {"type": "string"},
"company": {"type": "string"}
}
}
}
]
}
@app.get("/mcp/v1/tools")
async def get_tools_manifest() -> Dict[str, List[Dict[str, Any]]]:
"""Returns the MCP tool manifest."""
return MCP_TOOLS_MANIFEST
@app.post("/mcp/v1/tools/get_contact_details")
async def invoke_get_contact_details(input_data: GetContactDetailsInput) -> Dict[str, Any]:
"""Invokes the get_contact_details tool."""
try:
details = crm_client.get_contact_details(input_data.contact_id)
if details:
return details
raise HTTPException(status_code=404, detail="Contact not found")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
@app.post("/mcp/v1/tools/create_lead")
async def invoke_create_lead(input_data: CreateLeadInput) -> Dict[str, Any]:
"""Invokes the create_lead tool."""
try:
lead = crm_client.create_lead(input_data.name, input_data.email, input_data.company)
return lead
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
This server, when run (python mcp_crm_server.py), exposes two CRM tools via the /mcp/v1/tools endpoint for discovery and /mcp/v1/tools/{tool_name} for invocation.
Connecting to a LangGraph Agent
LangGraph (and its underlying LangChain framework) has native MCP client support. You simply point it to the MCP server's base URL.
# langgraph_agent.py
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langchain_community.tools.mcp import MCPTool # Assuming this MCP client exists
from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage
from langchain_core.pydantic_v1 import BaseModel, Field
from typing import List, Literal
class AgentState(BaseModel):
messages: List[BaseMessage] = Field(default_factory=list)
# Add other state variables as needed
# Define the tools from our MCP server
# In a real scenario, you'd use an MCPClient to dynamically load tools
# For demonstration, let's assume we load them or define them directly
mcp_base_url = "http://localhost:8000/mcp/v1/tools"
# In a real setup, you'd fetch the manifest and create tools dynamically
# For simplicity, let's create them directly using the MCPTool helper
get_contact_details_tool = MCPTool(
name="get_contact_details",
description="Retrieves detailed information about a contact from the CRM.",
args_schema=GetContactDetailsInput, # Pydantic model from server
mcp_base_url=mcp_base_url
)
create_lead_tool = MCPTool(
name="create_lead",
description="Creates a new lead entry in the CRM.",
args_schema=CreateLeadInput, # Pydantic model from server
mcp_base_url=mcp_base_url
)
tools = [get_contact_details_tool, create_lead_tool]
# Define your LLM (e.g., Anthropic Claude, OpenAI GPT)
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(model="claude-3-opus-20240229", temperature=0) # Or gpt-4-turbo
# Define the agent node
def call_llm(state: AgentState):
messages = state["messages"]
response = llm.invoke(messages, tools=tools) # Pass tools to LLM
return {"messages": messages + [response]}
# Build the graph
graph_builder = StateGraph(AgentState)
graph_builder.add_node("llm_node", call_llm)
graph_builder.add_node("tool_node", ToolNode(tools)) # ToolNode handles tool invocation
graph_builder.add_edge(START, "llm_node")
def tool_router(state: AgentState):
last_message = state["messages"][-1]
if last_message.tool_calls:
return "tool_node"
return END # Or another agent node
graph_builder.add_conditional_edges(
"llm_node",
tool_router
)
graph_builder.add_edge("tool_node", "llm_node") # After tool execution, go back to LLM
graph = graph_builder.compile()
# Example Usage
if __name__ == "__main__":
initial_state = {"messages": [HumanMessage(content="What are the details for contact-123?")]}
for s in graph.stream(initial_state):
print(s)
print("---")
print("\n--- Creating a lead ---")
initial_state_2 = {"messages": [HumanMessage(content="Create a new lead named Bob Johnson, email bob@example.com, company Example Corp.")]}
for s in graph.stream(initial_state_2):
print(s)
print("---")
The LangGraph agent dynamically uses the MCP tools. The agent doesn't need to know the CRM API's specifics; it just interacts with the MCP server via the MCPTool wrapper.
Connecting to Claude Desktop
For Claude Desktop, the process is even simpler. You typically provide a configuration file (e.g., mcp_config.json or through a GUI) that points to your MCP server.
// mcp_config.json (or configured via Claude Desktop UI)
{
"mcp_servers": [
{
"name": "Verel CRM Tools",
"url": "http://localhost:8000/mcp/v1/tools",
"description": "Access to Verel's CRM API via MCP."
}
]
}
Once configured, Claude Desktop will automatically discover the get_contact_details and create_lead tools. A user can then simply type in Claude Desktop: "Hey Claude, get me the details for contact-123" or "Create a lead for John Doe at Acme Inc., email john@acme.com." Claude will recognize the intent, call the MCP server, and present the results. The same underlying MCP server serves both the programmatic LangGraph agent and the interactive Claude Desktop client, demonstrating the power of the "USB" analogy.
MCP vs. Native Function Calling vs. tool_use API
This is a common point of confusion. When should you use which?
- ▸MCP (Model Context Protocol): Use MCP when cross-framework portability is paramount. If you need a tool to be usable by LangGraph, Claude Desktop, AutoGen, and custom Python scripts, an MCP server is the right choice. It's designed for external tool orchestration, complex long-running tasks, and enterprise-grade service exposure. The latency overhead can be around 50-100ms per tool call due to the HTTP round trip, but this is often negligible for most business processes compared to the flexibility gained.
- ▸Native Function Calling (e.g., OpenAI's
functionsAPI): When you are tightly coupled to a single model provider (e.g., only using OpenAI's models) and the tool is primarily for direct model-tool interaction within that ecosystem, native function calling can offer slightly lower latency and a more integrated developer experience. You define the tool schema directly in the model's API call. We use this when a tool is very specific to an OpenAI-only agent and doesn't need to be exposed elsewhere. - ▸
tool_useAPI (e.g., Anthropic'stoolsparameter): Similar to native function calling, this is Anthropic's specific mechanism for models to interact with tools. If your primary LLM is Claude and the tool will only be used by Claude, this offers the most direct integration.
The decision is clear: If a tool needs to be shared across different LLMs or agent frameworks, build it as an MCP server. If it's exclusively for one model's direct use, use that model's native function calling. Don't rebuild a tool as an MCP server if its only consumer is a single model via its native function calling – that's unnecessary abstraction.
Production Considerations
Moving from a demo to a production-grade MCP deployment requires addressing several key engineering concerns that the core specification doesn't entirely cover. This is where Verel's expertise in shipping real systems comes in.
- ▸Authentication and Authorization: MCP servers exposing sensitive internal APIs must implement robust security. We integrate MCP servers with existing enterprise identity providers using OAuth 2.0 or API keys. The MCP client (the agent framework) needs a mechanism to pass these credentials to the server. For example, a LangGraph agent might retrieve an OAuth token from a secure vault and include it as a header in the MCP invocation request. This ensures only authorized agents or users can invoke specific tools.
- ▸Tool Versioning: As your internal APIs evolve, so will your MCP tool schemas. We enforce semantic versioning for our MCP tool manifests (e.g.,
/mcp/v1/tools,/mcp/v2/tools). This allows agents to explicitly request a specific version of a tool, preventing breaking changes from disrupting production workflows. A new version might introduce new parameters or change output formats, and older agents can continue using the/v1endpoint until they are updated. - ▸Error Handling and Observability: Production MCP servers need comprehensive error handling, including standardized error responses (e.g., HTTP status codes, detailed error messages) that clients can interpret. Beyond that, robust logging (structured logs to Splunk or Datadog), metrics (latency, error rates per tool), and tracing (OpenTelemetry integration) are non-negotiable. You need to know when a tool call fails, why it failed, and how long it took. Without this, you're flying blind, and your agents will quickly become unreliable.
- ▸Rate Limiting and Circuit Breakers: MCP servers act as a gateway to your backend services. Agents can be chatty, and an uncontrolled agent might accidentally flood a downstream API. Implement rate limiting at the MCP server level to protect your backend systems. Additionally, circuit breakers can prevent cascading failures by temporarily disabling access to a tool if the underlying service is experiencing issues, allowing it to recover.
- ▸Scalability: MCP servers should be stateless where possible and designed for horizontal scaling. Deploy them behind load balancers, and ensure the underlying tool implementations are also scalable. You don't want your MCP server to become a bottleneck.
These aren't optional features; they are the bedrock of any production system. Ignoring them leads directly to the kind of "AI debt" and abandoned pilots we see too often. MCP provides the standard, but good engineering provides the resilience.
MCP has fundamentally changed how we build and deploy AI agents at Verel Systems. It moves us past the era of bespoke integrations and into a future where AI tools are as plug-and-play as a USB device. If you're building any agentic system today that needs to interact with external services, adopting MCP isn't just a good idea – it's a requirement for moving from pilot purgatory to actual production. Implement it with production-grade rigor.
