Multi-Agent MCP Server
by jamalla
README.md
# Multi-Agent MCP Demo
A working demonstration of **one MCP server exposing many tools**, with **multiple LangGraph agents each bound to a filtered subset** of those tools, and a **supervisor** that routes each user question to the right agent, all deployed to the cloud with a browser chat UI.
> **The core idea:** a single MCP server hands over its *entire* tool catalog to any client. Filtering, deciding *which* agent sees *which* tools, happens on the client side, in one line:
> ```python
> agent_tools = [t for t in all_tools if t.name.startswith(prefix)]
> ```
## π Live URLs
| Service | URL |
|---|---|
| π¬ **Chat UI (agents)** | https://multi-agent-mcp-agents.onrender.com |
| π οΈ **MCP server** | https://multi-agent-mcp.onrender.com/mcp |
| β€οΈ MCP health check | https://multi-agent-mcp.onrender.com/health |
| π¦ Source | https://github.com/jamalla/multi-agent-mcp |
> β³ **Cold start:** both services run on Render's free tier and sleep after ~15 min idle. The first request after a nap can take 30 to 50s to wake the container, then the second is fast. The chat UI shows a "may take ~40s" hint while waiting.
## Architecture
```
Browser (chat UI)
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββ
β FastAPI agent service (Render service #2) β
β βββββββββββββββββββββββββββββββββββββββββββ β
β β LangGraph Supervisor β β
β β (LLM router β picks the right agent) β β
β ββββββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββ β
β ββββββββΌββββββ ββββΌββββββββ ββΌββββββββββ β
β β Agent 1 β β Agent 2 β β Agent 3 β β
β β weather_* β β country_* β βworldcup_*β β
β β (2 tools) β β (5 tools) β β(5 tools) β β
β ββββββββ¬ββββββ ββββ¬ββββββββ ββ¬ββββββββββ β
ββββββββββββββββΌβββββββββββΌβββββββββββΌββββββββββββ
ββββββββββββΌβββββββββββ
filtered subsets of one catalog
β (streamable-HTTP / MCP)
ββββββββββββββΌβββββββββββββ
β MCP Server β (Render service #1)
β 12 tools, unfiltered β
ββββββ¬βββββββββββ¬ββββββββββ¬β
β β β
ββββββββββΌββ ββββββββΌβββββ ββββΌβββββββββββββββ
βOpen-Meteoβ βCountriesNowβ βfootball-data.orgβ
β(weather) β β (country) β β (World Cup) β
ββββββββββββ ββββββββββββββ βββββββββββββββββββ
```
**Two clean separations:**
- The **supervisor** decides *who* handles a query (routing).
- The **prefix filter** decides *what* each agent can do (tool scoping).
## The tools (12 total)
The naming convention (`weather_` / `country_` / `worldcup_` prefixes) is what makes per-agent filtering a one-liner.
| Prefix | Tool | Source API |
|---|---|---|
| `weather_` | `weather_geocode` | Open-Meteo (geocoding) |
| `weather_` | `weather_current` | Open-Meteo (forecast) |
| `country_` | `country_capital` | CountriesNow |
| `country_` | `country_currency` | CountriesNow |
| `country_` | `country_population` | CountriesNow |
| `country_` | `country_dial_code` | CountriesNow |
| `country_` | `country_flag` | CountriesNow |
| `worldcup_` | `worldcup_matches_upcoming` | football-data.org |
| `worldcup_` | `worldcup_match_results` | football-data.org |
| `worldcup_` | `worldcup_group_standings` | football-data.org |
| `worldcup_` | `worldcup_teams` | football-data.org |
| `worldcup_` | `worldcup_team_form` | football-data.org |
Open-Meteo and CountriesNow are free and need **no key**. football-data.org needs a free API key (`FOOTBALL_API_KEY`). "Predictions" are the World Cup agent reasoning over standings and recent form it fetches with these tools, not a separate prediction API.
## Observability: see the route & tool steps
Every answer returns a structured trace, rendered under each message in the UI (expandable):
```
π€οΈ routed to Agent 1 (weather) βΈ Show reasoning (4 steps)
π§ weather_geocode({"city":"Tokyo"})
π₯ weather_geocode β {"name":"Tokyo","country":"Japan","latitude":35.6895,...}
π§ weather_current({"latitude":35.6895,"longitude":139.69171})
π₯ weather_current β {"temperature_2m":27.0,"wind_speed_10m":4.5,...}
```
The `/ask` endpoint returns:
```json
{
"answer": "β¦",
"route": { "destination": "weather", "agent": "Agent 1 (weather)" },
"steps": [ { "kind": "tool_call", "tool": "...", "args": {...} },
{ "kind": "tool_result", "tool": "...", "output": "..." } ]
}
```
For deeper tracing (timings, tokens, nested spans), set `LANGCHAIN_TRACING_V2=true` and `LANGCHAIN_API_KEY` to enable **LangSmith**, no code changes required.
## Tech stack
- **MCP server:** [FastMCP](https://gofastmcp.com) over streamable-HTTP
- **Agents / routing:** LangGraph (`create_react_agent`) + LangChain
- **MCP β LangGraph bridge:** `langchain-mcp-adapters`
- **LLM:** OpenAI `gpt-4o-mini` (routing + agents)
- **API / UI:** FastAPI (serves both `/ask` and the chat page)
- **Hosting:** Render (two Docker web services, free tier)
## Project structure
```
multi-agent-mcp/
βββ mcp_server/
β βββ server.py # FastMCP server: 7 tools + /health, reads $PORT
βββ agents/
β βββ agent_config.py # MCP client + prefix map (reads MCP_URL from env)
β βββ graph.py # build_agents(): filter tools β create_react_agent
β βββ supervisor.py # LLM router + trace extraction
β βββ api.py # FastAPI: /ask + chat UI
βββ Dockerfile.server # image for the MCP server
βββ Dockerfile.agents # image for the FastAPI agent service
βββ docker-compose.yml # local parity for the MCP server
βββ render.yaml # Render blueprint (MCP server)
βββ requirements.txt
βββ .env # OPENAI_API_KEY (gitignored, never committed)
```
## Run locally
```bash
# 1. Install
python -m venv .venv
.venv\Scripts\activate # Windows (macOS/Linux: source .venv/bin/activate)
pip install -r requirements.txt fastapi uvicorn
# 2. Configure
# .env β OPENAI_API_KEY=sk-...
# 3a. Start the MCP server (terminal 1)
python -m mcp_server.server # serves http://localhost:8000/mcp
# 3b. Start the agent API + chat UI (terminal 2)
# defaults MCP_URL to http://localhost:8000/mcp
uvicorn agents.api:app --reload --port 8080 # open http://localhost:8080
```
Point the agents at a **remote** MCP server without any code change:
```bash
export MCP_URL="https://multi-agent-mcp.onrender.com/mcp"
uvicorn agents.api:app --port 8080
```
## Deploy (Render)
Two Docker web services from this repo.
**Service 1: MCP server**
- Dockerfile: `Dockerfile.server`
- Health check path: `/health`
- Env vars:
- `FOOTBALL_API_KEY` = your football-data.org key (needed by the World Cup tools)
**Service 2: Agent API + UI**
- Dockerfile: `Dockerfile.agents`
- Env vars:
- `OPENAI_API_KEY` = your OpenAI key
- `MCP_URL` = `https://multi-agent-mcp.onrender.com/mcp`
Both read `$PORT` (injected by Render) and bind `0.0.0.0`, so no port config is needed. `render.yaml` describes the MCP server as a blueprint.
## Author
**Jamalla Zawia** - jamala.zawia@gmail.com
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues