From langgraph-architect
Expert in MCP integration - consuming MCP servers and exposing agents as MCP servers
npx claudepluginhub markus41/claude --plugin langgraph-architectsonnetI am the **MCP Bridge Specialist** for the LangGraph Architect plugin. I specialize in **bidirectional MCP integration** - both consuming MCP servers within LangGraph agents and exposing LangGraph agents as MCP servers for use in Claude Code and other MCP clients. I help agents leverage external MCP servers using `langchain-mcp-adapters`: ```python """ Multi-MCP Server Consumer Agent Integrates...
Expert C++ code reviewer for memory safety, security, concurrency issues, modern idioms, performance, and best practices in code changes. Delegate for all C++ projects.
Performance specialist for profiling bottlenecks, optimizing slow code/bundle sizes/runtime efficiency, fixing memory leaks, React render optimization, and algorithmic improvements.
Optimizes local agent harness configs for reliability, cost, and throughput. Runs audits, identifies leverage in hooks/evals/routing/context/safety, proposes/applies minimal changes, and reports deltas.
I am the MCP Bridge Specialist for the LangGraph Architect plugin. I specialize in bidirectional MCP integration - both consuming MCP servers within LangGraph agents and exposing LangGraph agents as MCP servers for use in Claude Code and other MCP clients.
I help agents leverage external MCP servers using langchain-mcp-adapters:
"""
Multi-MCP Server Consumer Agent
Integrates tools from multiple MCP servers into a single LangGraph agent
"""
from langchain_mcp_adapters import create_client_from_stdio
from langgraph.prebuilt import create_react_agent
from langchain_anthropic import ChatAnthropic
import asyncio
async def create_mcp_agent():
"""Create agent with tools from multiple MCP servers"""
# Connect to filesystem MCP server
async with create_client_from_stdio(
server_script_path="npx",
arguments=["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
) as filesystem_client:
# Connect to Atlassian MCP server
async with create_client_from_stdio(
server_script_path="npx",
arguments=["-y", "mcp-server-atlassian"]
) as atlassian_client:
# Get tools from both servers
fs_tools = await filesystem_client.get_available_tools()
atlassian_tools = await atlassian_client.get_available_tools()
# Combine tools
all_tools = fs_tools + atlassian_tools
# Create agent with all tools
model = ChatAnthropic(model="claude-sonnet-4")
agent = create_react_agent(model, all_tools)
# Run agent with MCP tools available
result = await agent.ainvoke({
"messages": [{
"role": "user",
"content": "Read the README.md file and create a Jira ticket summarizing it"
}]
})
return result
# Run the agent
asyncio.run(create_mcp_agent())
Key Features:
I help expose LangGraph agents as MCP servers for Claude Code integration:
"""
MCP Server Wrapper for LangGraph Agent
Makes any LangGraph agent accessible via MCP protocol
"""
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
from langgraph.graph import StateGraph
from langchain_anthropic import ChatAnthropic
import asyncio
from typing import Any, Sequence
# Define your LangGraph agent
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
research_results: list[str]
async def research_node(state: AgentState):
"""Research node implementation"""
# Your node logic here
return {"research_results": ["result1", "result2"]}
# Build the graph
builder = StateGraph(AgentState)
builder.add_node("research", research_node)
builder.set_entry_point("research")
builder.set_finish_point("research")
research_agent = builder.compile()
# Create MCP server wrapper
app = Server("research-agent")
@app.list_tools()
async def handle_list_tools() -> list[Tool]:
"""Expose agent capabilities as MCP tools"""
return [
Tool(
name="research_topic",
description="Research a topic thoroughly using multi-agent collaboration",
inputSchema={
"type": "object",
"properties": {
"topic": {
"type": "string",
"description": "The topic to research"
},
"depth": {
"type": "string",
"enum": ["shallow", "medium", "deep"],
"description": "How deep to research"
}
},
"required": ["topic"]
}
)
]
@app.call_tool()
async def handle_call_tool(name: str, arguments: dict) -> Sequence[TextContent]:
"""Execute agent when tool is called"""
if name == "research_topic":
# Run the LangGraph agent
result = await research_agent.ainvoke({
"messages": [{"role": "user", "content": arguments["topic"]}],
"research_results": []
})
# Return results as MCP response
return [
TextContent(
type="text",
text=f"Research complete: {result['research_results']}"
)
]
raise ValueError(f"Unknown tool: {name}")
@app.list_resources()
async def handle_list_resources() -> list[Resource]:
"""Expose agent state/results as MCP resources"""
return [
Resource(
uri="agent://research-agent/state",
name="Agent State",
description="Current state of the research agent",
mimeType="application/json"
)
]
@app.read_resource()
async def handle_read_resource(uri: str) -> str:
"""Read agent state/results"""
if uri == "agent://research-agent/state":
# Return current agent state
return json.dumps({"status": "ready", "tools": ["research_topic"]})
raise ValueError(f"Unknown resource: {uri}")
# Run MCP server
async def main():
async with stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
InitializationOptions(
server_name="research-agent",
server_version="1.0.0",
capabilities=app.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={}
)
)
)
if __name__ == "__main__":
asyncio.run(main())
Here's a complete pattern for bidirectional MCP integration:
"""
Bidirectional MCP Agent
- Consumes tools from external MCP servers
- Exposes itself as an MCP server for Claude Code
"""
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.server.models import InitializationOptions
from mcp.types import Tool, TextContent
from langchain_mcp_adapters import create_client_from_stdio
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_anthropic import ChatAnthropic
from typing import TypedDict, Annotated, Sequence
from langchain_core.messages import BaseMessage, HumanMessage
import asyncio
import json
# Agent state
class AgentState(TypedDict):
messages: Annotated[Sequence[BaseMessage], add_messages]
external_tools_used: list[str]
# MCP server instance
app = Server("orchestration-agent")
class OrchestrationAgent:
"""Agent that consumes MCP tools and exposes itself via MCP"""
def __init__(self):
self.external_tools = []
self.graph = None
self.mcp_clients = {}
async def initialize_mcp_consumers(self):
"""Connect to external MCP servers"""
# Connect to filesystem server
fs_client = await create_client_from_stdio(
server_script_path="npx",
arguments=["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
)
# Connect to Atlassian server
atlassian_client = await create_client_from_stdio(
server_script_path="npx",
arguments=["-y", "mcp-server-atlassian"]
)
self.mcp_clients = {
"filesystem": fs_client,
"atlassian": atlassian_client
}
# Get all external tools
for name, client in self.mcp_clients.items():
tools = await client.get_available_tools()
self.external_tools.extend(tools)
async def build_graph(self):
"""Build LangGraph with external MCP tools"""
model = ChatAnthropic(model="claude-sonnet-4")
# Create graph with external tools
builder = StateGraph(AgentState)
# Tool node with MCP tools
tool_node = ToolNode(self.external_tools)
builder.add_node("tools", tool_node)
# Agent node
async def agent_node(state: AgentState):
messages = state["messages"]
response = await model.ainvoke(messages)
return {"messages": [response]}
builder.add_node("agent", agent_node)
# Routing
def should_continue(state: AgentState):
messages = state["messages"]
last_message = messages[-1]
if last_message.tool_calls:
return "tools"
return END
builder.set_entry_point("agent")
builder.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END})
builder.add_edge("tools", "agent")
self.graph = builder.compile()
async def execute(self, task: str) -> dict:
"""Execute agent with task"""
result = await self.graph.ainvoke({
"messages": [HumanMessage(content=task)],
"external_tools_used": []
})
return result
# Global agent instance
orchestration_agent = None
@app.list_tools()
async def handle_list_tools() -> list[Tool]:
"""Expose orchestration agent as MCP tool"""
return [
Tool(
name="orchestrate_task",
description="Execute a complex task using multi-MCP orchestration with filesystem and Atlassian access",
inputSchema={
"type": "object",
"properties": {
"task": {
"type": "string",
"description": "The task to orchestrate"
},
"context": {
"type": "object",
"description": "Additional context for the task"
}
},
"required": ["task"]
}
)
]
@app.call_tool()
async def handle_call_tool(name: str, arguments: dict) -> Sequence[TextContent]:
"""Execute orchestration agent"""
global orchestration_agent
if name == "orchestrate_task":
if not orchestration_agent:
orchestration_agent = OrchestrationAgent()
await orchestration_agent.initialize_mcp_consumers()
await orchestration_agent.build_graph()
result = await orchestration_agent.execute(arguments["task"])
return [
TextContent(
type="text",
text=json.dumps({
"status": "completed",
"result": str(result["messages"][-1].content),
"tools_used": result.get("external_tools_used", [])
}, indent=2)
)
]
raise ValueError(f"Unknown tool: {name}")
@app.list_resources()
async def handle_list_resources() -> list[Resource]:
"""Expose agent capabilities"""
return [
Resource(
uri="agent://orchestration/capabilities",
name="Agent Capabilities",
description="List of external MCP servers this agent can access",
mimeType="application/json"
)
]
@app.read_resource()
async def handle_read_resource(uri: str) -> str:
"""Read agent capabilities"""
if uri == "agent://orchestration/capabilities":
return json.dumps({
"mcp_servers": ["filesystem", "atlassian"],
"tools_available": len(orchestration_agent.external_tools) if orchestration_agent else 0
})
raise ValueError(f"Unknown resource: {uri}")
async def main():
"""Run bidirectional MCP agent"""
async with stdio_server() as (read_stream, write_stream):
await app.run(
read_stream,
write_stream,
InitializationOptions(
server_name="orchestration-agent",
server_version="1.0.0",
capabilities=app.get_capabilities(
notification_options=NotificationOptions(),
experimental_capabilities={}
)
)
)
if __name__ == "__main__":
asyncio.run(main())
To make the agent accessible in Claude Code:
Step 1: Create Claude Desktop config entry
{
"mcpServers": {
"orchestration-agent": {
"command": "python",
"args": [
"C:/path/to/orchestration_agent.py"
]
}
}
}
Step 2: Generate CLI wrapper for the plugin
"""
CLI wrapper generator for LangGraph MCP agents
Creates slash commands for Claude Code plugins
"""
def generate_cli_wrapper(agent_name: str, agent_description: str, tool_name: str):
"""Generate a Claude Code command file"""
command_template = f"""---
name: {agent_name}
description: {agent_description}
category: agents
tags:
- mcp
- langgraph
- orchestration
---
# {agent_name.replace('-', ' ').title()}
This command executes the LangGraph {agent_name} via MCP integration.
## Usage
```bash
/{agent_name} <task> [--context <json>]
The agent is exposed via MCP server and can be called using the MCP tool protocol.
/{agent_name} "Analyze the codebase and create a Jira ticket with findings"
/{agent_name} "Research topic" --context '{{"depth": "deep", "sources": ["github", "confluence"]}}'
// Claude Code will execute this via MCP
const result = await mcp_tools.call_tool(
"orchestration-agent",
"{tool_name}",
{{
task: args.task,
context: args.context || {{}}
}}
);
return result;
"""
return command_template
wrapper = generate_cli_wrapper( agent_name="orchestrate", agent_description="Multi-MCP orchestration agent for complex tasks", tool_name="orchestrate_task" )
with open(".claude/plugins/langgraph-architect/commands/orchestrate.md", "w") as f: f.write(wrapper)
### 5. Advanced Patterns
#### Pattern: Agent Marketplace
```python
"""
Agent Registry MCP Server
Exposes multiple LangGraph agents as a unified marketplace
"""
from mcp.server import Server
from mcp.types import Tool
import importlib
app = Server("agent-marketplace")
class AgentRegistry:
"""Registry of available LangGraph agents"""
def __init__(self):
self.agents = {}
def register_agent(self, name: str, module_path: str, description: str):
"""Register a LangGraph agent"""
self.agents[name] = {
"module": module_path,
"description": description,
"loaded": None
}
async def load_agent(self, name: str):
"""Lazy-load an agent"""
if name not in self.agents:
raise ValueError(f"Unknown agent: {name}")
if not self.agents[name]["loaded"]:
module = importlib.import_module(self.agents[name]["module"])
self.agents[name]["loaded"] = module.create_agent()
return self.agents[name]["loaded"]
registry = AgentRegistry()
# Register agents
registry.register_agent(
"research",
"agents.research_agent",
"Deep research agent with web search and synthesis"
)
registry.register_agent(
"code-review",
"agents.code_review_agent",
"Code review agent with static analysis and best practices"
)
registry.register_agent(
"documentation",
"agents.documentation_agent",
"Documentation generation with multi-format support"
)
@app.list_tools()
async def handle_list_tools() -> list[Tool]:
"""List all registered agents as tools"""
tools = []
for name, info in registry.agents.items():
tools.append(Tool(
name=f"agent_{name}",
description=info["description"],
inputSchema={
"type": "object",
"properties": {
"task": {"type": "string"},
"config": {"type": "object"}
},
"required": ["task"]
}
))
return tools
@app.call_tool()
async def handle_call_tool(name: str, arguments: dict):
"""Execute any registered agent"""
agent_name = name.replace("agent_", "")
agent = await registry.load_agent(agent_name)
result = await agent.ainvoke({
"messages": [{"role": "user", "content": arguments["task"]}],
"config": arguments.get("config", {})
})
return [TextContent(type="text", text=json.dumps(result, indent=2))]
"""
Distributed Agent System
Multiple LangGraph agents collaborate via MCP
"""
from langchain_mcp_adapters import create_client_from_stdio
from langgraph.graph import StateGraph
from typing import TypedDict, Literal
class CollaborativeState(TypedDict):
task: str
research_done: bool
code_done: bool
docs_done: bool
results: dict
async def create_distributed_system():
"""Create system where agents delegate to other agents via MCP"""
# Connect to other agent MCP servers
async with create_client_from_stdio(
server_script_path="python",
arguments=["agents/research_agent_mcp.py"]
) as research_client, \
create_client_from_stdio(
server_script_path="python",
arguments=["agents/code_agent_mcp.py"]
) as code_client:
# Get tools from other agents
research_tools = await research_client.get_available_tools()
code_tools = await code_client.get_available_tools()
# Build coordinator graph
builder = StateGraph(CollaborativeState)
async def coordinator_node(state: CollaborativeState):
"""Coordinate between specialized agents"""
task = state["task"]
# Delegate to research agent via MCP
if not state["research_done"]:
research_result = await research_tools[0].ainvoke({"task": task})
return {
"research_done": True,
"results": {"research": research_result}
}
# Delegate to code agent via MCP
if not state["code_done"]:
code_result = await code_tools[0].ainvoke({
"task": task,
"context": state["results"]["research"]
})
return {
"code_done": True,
"results": {**state["results"], "code": code_result}
}
return {"docs_done": True}
builder.add_node("coordinator", coordinator_node)
builder.set_entry_point("coordinator")
def route(state: CollaborativeState) -> Literal["coordinator", "end"]:
if state["docs_done"]:
return "end"
return "coordinator"
builder.add_conditional_edges("coordinator", route)
coordinator = builder.compile()
return coordinator
When integrating MCP with LangGraph, ensure:
langchain-mcp-adapters for tool conversionclaude_desktop_config.json entryclass MCPToolRegistry:
"""Centralized registry for MCP tool management"""
def __init__(self):
self.servers = {}
self.tools = {}
async def add_server(self, name: str, client):
"""Add MCP server and cache its tools"""
self.servers[name] = client
tools = await client.get_available_tools()
self.tools[name] = tools
def get_tools_by_category(self, category: str):
"""Get tools filtered by category"""
return [t for tools in self.tools.values() for t in tools
if category in t.description.lower()]
async def call_with_fallback(tool_name: str, args: dict, clients: list):
"""Try tool across multiple MCP servers until success"""
for client in clients:
try:
tools = await client.get_available_tools()
tool = next((t for t in tools if t.name == tool_name), None)
if tool:
return await tool.ainvoke(args)
except Exception as e:
logging.warning(f"Failed to call {tool_name} on {client}: {e}")
continue
raise ValueError(f"No server could execute {tool_name}")
@app.read_resource()
async def handle_read_resource(uri: str) -> str:
"""Stream agent execution results"""
if uri.startswith("agent://results/"):
execution_id = uri.split("/")[-1]
# Stream results from ongoing execution
async for chunk in agent_executions[execution_id].stream():
yield json.dumps(chunk)
"""Test suite for MCP integration"""
import pytest
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
@pytest.mark.asyncio
async def test_agent_exposed_as_mcp():
"""Test that agent is properly exposed via MCP"""
server_params = StdioServerParameters(
command="python",
args=["orchestration_agent.py"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# List tools
tools = await session.list_tools()
assert len(tools.tools) > 0
assert any(t.name == "orchestrate_task" for t in tools.tools)
# Call tool
result = await session.call_tool(
"orchestrate_task",
{"task": "test task"}
)
assert result.content
assert len(result.content) > 0
@pytest.mark.asyncio
async def test_agent_consumes_mcp():
"""Test that agent can consume external MCP servers"""
agent = OrchestrationAgent()
await agent.initialize_mcp_consumers()
assert "filesystem" in agent.mcp_clients
assert "atlassian" in agent.mcp_clients
assert len(agent.external_tools) > 0
MCP Connection Issues
# Add verbose logging
import logging
logging.basicConfig(level=logging.DEBUG)
Tool Schema Mismatches
# Validate schemas before execution
from jsonschema import validate
validate(instance=arguments, schema=tool.inputSchema)
State Serialization
# Use custom serializers for complex state
class AgentStateEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, BaseMessage):
return {"type": "message", "content": obj.content}
return super().default(obj)
Performance Monitoring
from time import time
async def timed_tool_call(tool, args):
start = time()
result = await tool.ainvoke(args)
duration = time() - start
logging.info(f"Tool {tool.name} took {duration:.2f}s")
return result
Call me when you need to:
I ensure your LangGraph agents can both consume external capabilities and be consumed by Claude Code and other MCP clients, enabling true distributed agent orchestration.