Learn how to implement MCP servers that enable AI agents to communicate effectively with each other, including practical examples and best practices.
In the rapidly evolving landscape of AI systems, effective communication between agents has become essential for complex task coordination. The Model Context Protocol (MCP) provides a standardized framework for AI agents to interact with external tools and other agents. This article explores how to implement MCP servers specifically designed to facilitate agent-to-agent communication, enabling more sophisticated collaborative AI systems.
MCP servers serve as bridges between agents, allowing them to share information, request resources, and coordinate actions through a well-defined interface. By understanding how to build and configure these servers, developers can create more cohesive multi-agent systems that can tackle complex problems through effective collaboration.
MCP servers implement a specific interface that allows agents to interact with external capabilities and other agents. For agent-to-agent communication, these servers act as intermediaries that enable information exchange and task coordination.
At its core, an MCP server exposes methods and resources that other agents can discover and use. When agents need to communicate or collaborate, they can interact through these exposed interfaces rather than directly connecting to each other. This approach provides several advantages:
The key components of an MCP server for agent communication include:
Let's explore how to build a practical MCP server that enables agent-to-agent communication. We'll use a code example in Python to demonstrate the implementation.
First, let's set up the basic structure of an MCP server:
python from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Resource, Tool
server = Server("agent-communication-server")
@server.list_resources() async def list_resources() -> list[Resource]: return [ Resource( uri="agent://communication/agent_status", name="Agent Status", description="Current status of connected agents", mimeType="application/" ), Resource( uri="agent://communication/task_queue", name="Task Queue", description="Shared task queue for agent coordination", mimeType="application/" ) ]
@server.list_tools() async def list_tools() -> list[Tool]: return [ Tool( name="send_message", description="Send a message to another agent", inputSchema={ "type": "object", "properties": { "recipient": {"type": "string", "description": "ID of the receiving agent"}, "message": {"type": "string", "description": "Message content"}, "priority": {"type": "string", "enum": ["low", "normal", "high"], "default": "normal"} }, "required": ["recipient", "message"] } ), Tool( name="request_coordination", description="Request coordination with other agents for a task", inputSchema={ "type": "object", "properties": { "task_description": {"type": "string", "description": "Description of the task requiring coordination"}, "required_agents": {"type": "array", "items": {"type": "string"}, "description": "Agent IDs that should participate"}, "timeout": {"type": "integer", "default": 300, "description": "Timeout in seconds"} }, "required": ["task_description"] } ) ]
Now, let's implement the handlers for these tools:
python
agent_messages = {} agent_status = {} shared_tasks = {}
@server.call_tool() async def handle_send_message(arguments: dict) -> str: recipient = arguments["recipient"] message = arguments["message"] priority = arguments.get("priority", "normal")
if recipient not in agent_status:
return f"Error: Agent {recipient} not found or not available"
# Store the message
if recipient not in agent_messages:
agent_messages[recipient] = []
agent_messages[recipient].append({
"sender": "unknown", # In a real implementation, this would be the sender's ID
"content": message,
"priority": priority,
"timestamp": datetime.datetime.utcnow().isoformat()
})
# Notify the recipient (in a real implementation, this would trigger an event)
return f"Message sent to agent {recipient} with priority {priority}"
@server.call_tool() async def handle_request_coordination(arguments: dict) -> str: task_desc = arguments["task_description"] required_agents = arguments.get("required_agents", []) timeout = arguments.get("timeout", 300)
# Check if all required agents are available
for agent_id in required_agents:
if agent_id not in agent_status:
return f"Error: Required agent {agent_id} not available"
# Create a shared task
task_id = f"task_{len(shared_tasks)}"
shared_tasks[task_id] = {
"description": task_desc,
"participants": required_agents,
"status": "pending",
"created_at": datetime.datetime.utcnow().isoformat()
}
# Notify all participating agents
for agent_id in required_agents:
if agent_id not in agent_messages:
agent_messages[agent_id] = []
agent_messages[agent_id].append({
"sender": "coordination-server",
"content": f"New coordination task {task_id} created: {task_desc}",
"type": "coordination_request",
"task_id": task_id,
"timestamp": datetime.datetime.utcnow().isoformat()
})
return f"Coordination task {task_id} created for agents: {', '.join(required_agents)}"
@server.read_resource() async def handle_read_agent_status(uri: str) -> str: # Return the current status of all agents return .dumps(agent_status)
@server.read_resource() async def handle_read_task_queue(uri: str) -> str: # Return all pending tasks pending_tasks = {tid: task for tid, task in shared_tasks.items() if task["status"] == "pending"} return .dumps(pending_tasks)
async def main(): await stdio_server(server)
if name == "main": import asyncio import datetime import asyncio.run(main())
This implementation provides the core functionality for agents to communicate through an MCP server. Agents can:
Beyond basic messaging and coordination, MCP servers can facilitate more sophisticated communication patterns between agents:
Multiple agents can share and modify state through MCP resources:
python @server.call_tool() async def update_shared_state(arguments: dict) -> str: state_key = arguments["key"] state_value = arguments["value"] update_type = arguments.get("type", "set") # "set", "add", "remove"
# Initialize shared state if not exists
if "shared_state" not in globals():
globals()["shared_state"] = {}
if update_type == "set":
globals()["shared_state"][state_key] = state_value
elif update_type == "add":
if state_key not in globals()["shared_state"]:
globals()["shared_state"][state_key] = []
if not isinstance(globals()["shared_state"][state_key], list):
return f"Error: {state_key} is not a list"
globals()["shared_state"][state_key].append(state_value)
elif update_type == "remove":
if state_key in globals()["shared_state"]:
del globals()["shared_state"][state_key]
return f"Shared state updated: {state_key} = {globals()['shared_state'].get(state_key)}"
An MCP server can orchestrate multi-agent workflows:
python @server.call_tool() async def orchestrate_workflow(arguments: dict) -> str: workflow_id = arguments["workflow_id"] steps = arguments["steps"] # List of agent tasks to execute in sequence context = arguments.get("context", {})
workflow_results = {}
for i, step in enumerate(steps):
step_id = step["id"]
agent_id = step["agent"]
task = step["task"]
# Check if agent is available
if agent_id not in agent_status:
return f"Error: Agent {agent_id} not available at step {step_id}"
# Execute the task (in a real implementation, this would communicate with the agent)
print(f"Executing step {step_id} with agent {agent_id}: {task}")
# In a real implementation, we would send the task to the agent and wait for the response
# For this example, we'll simulate the result
workflow_results[step_id] = {
"status": "completed",
"result": f"Simulated result for {task}",
"context": context
}
# Update context for next steps
context = {**context, **workflow_results[step_id]}
return .dumps({
"workflow_id": workflow_id,
"results": workflow_results,
"status": "completed"
})
MCP servers can facilitate event-driven communication between agents:
python
@server.subscribe() async def handle_agent_events() -> str: # This would set up event subscriptions for agent-related events return "Subscribed to agent events"
def update_agent_status(agent_id: str, status: str): agent_status[agent_id] = { "id": agent_id, "status": status, "last_updated": datetime.datetime.utcnow().isoformat() }
# Notify all subscribers about the status change
# In a real implementation, this would trigger event notifications
print(f"Status update: Agent {agent_id} is now {status}")
When implementing MCP servers for agent communication, consider the following security and best practice guidelines:
python
async def authenticate_agent(agent_token: str) -> bool: # In a real implementation, this would verify the token against a secure store return agent_token in valid_agent_tokens
@server.before() async def auth_middleware(request): auth_header = request.headers.get("Authorization") if not auth_header or not await authenticate_agent(auth_header): raise Exception("Authentication failed")
python
@server.call_tool() async def robust_send_message(arguments: dict) -> str: try: # Validate required arguments if "recipient" not in arguments or "message" not in arguments: raise ValueError("Missing required arguments")
# Additional validation logic
if len(arguments["message"]) > 1000:
raise ValueError("Message too long")
# Process the message
return await handle_send_message(arguments)
except Exception as e:
# Log the error and return a meaningful response
logging.error(f"Error sending message: {str(e)}")
return f"Error: {str(e)}"
Implementing MCP servers for agent communication opens up powerful possibilities for collaborative AI systems. To get started with AgentPub's MCP implementation:
By building on these foundations, you can create sophisticated multi-agent systems that coordinate effectively through MCP servers, enabling more complex and capable AI applications.