5. Orchestrate from an LLM (MCP)
Connect over MCP, discover the agent with agent_list, and invoke it with agent_send — the MCP→A2A bridge in action.
In Step 2 you built and registered
research-agent-01, and in Steps 3 and
4 you discovered and invoked it as an A2A
caller. Now you'll put an LLM in the driver's seat: an MCP client connects to the
same KubeMQ server, discovers your agent, and calls it — all through the Model Context
Protocol, with no new glue code on your side. This is the MCP→A2A bridge: the LLM
reaches the exact agent you built in Step 2.
This step assumes KubeMQ is running on port 9090 and research-agent-01 is registered
and listening (Steps 1 and 2). The MCP connector is enabled by default — POST /mcp
is already live on the shared HTTP server. For the Python path, install the MCP SDK with
pip install mcp.
Connect and list the tools
An MCP client opens a session against POST http://localhost:9090/mcp, negotiates the
protocol version (2025-11-25), then calls tools/list. KubeMQ exposes 15 tools —
11 core messaging tools plus 4 agent-bridge tools (agent_list, agent_info,
agent_send, agent_query) that appear when the A2A agent registry is available. The
agent-bridge tools are what let the LLM reach your agent.
The MCP session version 2025-11-25 is a different version from the A2A Agent Card's
protocolVersions: ["1.0"] you registered in
Step 2 — one is the MCP handshake,
the other is the agent's A2A card version. They are not interchangeable.
First open a session with initialize, then list the tools with the returned session ID:
# 1. Initialize a session — note the protocol version is the MCP one.
curl -X POST http://localhost:9090/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {"name": "tutorial", "version": "1.0.0"}
}
}'
# 2. List the tools (substitute the session ID from the response above).
curl -X POST http://localhost:9090/mcp \
-H "Content-Type: application/json" \
-H "MCP-Session-Id: <session-id>" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}'The mcp SDK runs the initialize handshake and tracks the session ID for you over the
Streamable HTTP transport — you only call tools/list:
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
KUBEMQ_MCP_URL = "http://localhost:9090"
async def main() -> None:
async with streamablehttp_client(f"{KUBEMQ_MCP_URL}/mcp") as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
names = [t.name for t in tools.tools]
print(f"{len(names)} tools available") # 15: 11 core + 4 agent-bridge
print("agent-bridge tools:", [n for n in names if n.startswith("agent_")])
if __name__ == "__main__":
asyncio.run(main())The tool list includes the four agent-bridge tools alongside the 11 core messaging tools:
["agent_list", "agent_info", "agent_send", "agent_query"]Discover the agent with agent_list
agent_list reads the same A2A registry you queried in
Step 3 — but now through an MCP tool call.
Pass skill_tags to filter by capability, exactly as before, and the LLM gets back
research-agent-01 without ever hard-coding its ID.
curl -X POST http://localhost:9090/mcp \
-H "Content-Type: application/json" \
-H "MCP-Session-Id: <session-id>" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "agent_list",
"arguments": {"skill_tags": ["research"]}
}
}'result = await session.call_tool("agent_list", {"skill_tags": ["research"]})
print(f"IsError: {result.isError}")
for content in result.content:
print(f"Result: {content.text}")The result text is a JSON array of agent summaries — your agent is in it:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{"type": "text", "text": "[{\"agent_id\":\"research-agent-01\",\"name\":\"Research Agent\",\"skills\":[{\"id\":\"research\",\"name\":\"Research\",\"tags\":[\"research\",\"summarize\"]}]}]"}],
"isError": false
}
}Invoke the agent with agent_send
agent_send wraps your message in an A2A message/send envelope and forwards it through
the per-agent Agent Bridge — the same path the direct A2A
call took in Step 3, just initiated from MCP.
By default the call is blocking: it waits up to timeout_seconds for the agent's
reply (default 60, max 300). Pass blocking: false for fire-and-forget, or
context_id to thread the message into an existing conversation.
curl -X POST http://localhost:9090/mcp \
-H "Content-Type: application/json" \
-H "MCP-Session-Id: <session-id>" \
-d '{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "agent_send",
"arguments": {
"agent_id": "research-agent-01",
"message": "Summarize the history of message queues",
"timeout_seconds": 60
}
}
}'result = await session.call_tool("agent_send", {
"agent_id": "research-agent-01",
"message": "Summarize the history of message queues",
"timeout_seconds": 60,
})
print(f"IsError: {result.isError}")
for content in result.content:
print(f"Result: {content.text}")The agent's reply comes back in the content text — the same result your Step 2 agent
returns from message/send, now delivered to the LLM:
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [{"type": "text", "text": "{\"summary\":\"Research summary for: Summarize the history of message queues\",\"sources\":3}"}],
"isError": false
}
}An unknown agent_id returns isError: true with Agent '<id>' not found — a tool-level
error in the content block, not a JSON-RPC protocol error. The bridge also adds a
gateway timeout buffer on top of your timeout_seconds. See
Agent-bridge tools for agent_info, agent_query,
and the full argument and error reference.
One fabric, no glue code
Step back and notice what just happened. The LLM discovered your agent by capability
and invoked it — and you wrote no integration code to make that possible. The same
research-agent-01 you built in Step 2 (a plain-HTTP service, no KubeMQ SDK) is now
reachable by an A2A caller and by any MCP-speaking LLM, over the same message broker.
That's the Aiway thesis: one fabric, two doors — register once, reach it from both.
To wire this into a real LLM host such as Claude Desktop — a claude_desktop_config.json
entry that points at http://localhost:9090/mcp — see
MCP Getting started. The host then sees all 15 tools
(including the four agent-bridge tools) and can call your agent from a conversation.
Where to go next
You've built the full fabric end-to-end: started KubeMQ, registered a zero-SDK agent, discovered and invoked it, streamed live task events, and orchestrated it from an LLM over MCP. From here, go deeper:
Aiway overview
The big picture: an AI Agents Fabric, the two doors in, and one fabric across three planes.
Use cases
Where Aiway is uniquely strong — zero-SDK onboarding, LLM orchestration, streaming, and capability routing.
A2A reference
The full agent gateway API: registration, discovery, message/send, and message/stream.
MCP endpoints
The MCP HTTP surface: initialize, tools/list, tools/call, and session handling.
Was this page helpful?