{"id":50684950,"url":"https://github.com/smslavin/mcp-aggregator","last_synced_at":"2026-06-08T22:02:10.370Z","repository":{"id":359130497,"uuid":"1244642878","full_name":"smslavin/mcp-aggregator","owner":"smslavin","description":"MCP aggregator — unified tool namespace across multiple backend MCP servers","archived":false,"fork":false,"pushed_at":"2026-05-20T15:17:43.000Z","size":19,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-20T18:35:17.505Z","etag":null,"topics":["fastmcp","industrial-automation","mcp","model-context-protocol","mqtt","ollama","opcua","ot-security","python","scada"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smslavin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-20T13:07:49.000Z","updated_at":"2026-05-20T18:30:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/smslavin/mcp-aggregator","commit_stats":null,"previous_names":["smslavin/mcp-aggregator"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/smslavin/mcp-aggregator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smslavin%2Fmcp-aggregator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smslavin%2Fmcp-aggregator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smslavin%2Fmcp-aggregator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smslavin%2Fmcp-aggregator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smslavin","download_url":"https://codeload.github.com/smslavin/mcp-aggregator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smslavin%2Fmcp-aggregator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34082130,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fastmcp","industrial-automation","mcp","model-context-protocol","mqtt","ollama","opcua","ot-security","python","scada"],"created_at":"2026-06-08T22:02:09.003Z","updated_at":"2026-06-08T22:02:10.361Z","avatar_url":"https://github.com/smslavin.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mcp-aggregator\n\nSingle endpoint that aggregates multiple backend MCP servers into one unified tool namespace.\n\n```\nAI Client (Claude Desktop / Chat UI)\n         │ SSE :8100\n         ▼\n    mcp-aggregator          ← this server\n    ├── startup: discover tools from all backends\n    ├── runtime: route + proxy tool calls\n    └── cross-cutting: logging, error isolation\n         │                    │\n    SSE :8002            SSE :8003\n    opcua-mcp            mock-backend (or any FastMCP server)\n```\n\n## Tool namespacing\n\nTools are prefixed `{backend_name}__{tool_name}`. With backends named `opcua` and\n`plant`, the aggregated namespace looks like:\n\n```\nopcua__connect_server\nopcua__browse_nodes\nopcua__read_node\n...\nplant__get_plant_status\nplant__list_sensors\nplant__acknowledge_alarm\n...\n```\n\nThe prefix is stripped before forwarding to the backend, so backend servers receive\nthe original tool name unchanged.\n\nIn environments with multiple data sources — OPC-UA, MQTT, SCADA configuration — namespacing \nprevents tool name collisions and makes the audit log immediately readable. \nEvery tool call identifies both the domain and the operation.\n\n## How it works\n\n**Startup:** for each backend in `backends.json`, connects to the backend and calls\n`tools/list`. Every discovered tool is registered with a `{backend}__{tool}` prefix so\nthe routing is explicit and collision-free. Streamable HTTP backends also start a\npersistent pooled session at this point.\n\n**Runtime:** each tool call is routed to the originating backend. Streamable HTTP\nbackends use the persistent pooled session; SSE backends open a fresh connection per\ncall. The aggregator adds no parsing or transformation — it is a transparent proxy.\n\n## Setup\n\n**Windows (PowerShell)**\n```powershell\npython -m venv .venv\n.venv\\Scripts\\pip install -r requirements.txt\n```\n\n**macOS / Linux**\n```bash\npython3 -m venv .venv\n.venv/bin/pip install -r requirements.txt\n```\n\n## Running\n\nStart all backends first, then the aggregator.\n\n**Windows (PowerShell)**\n```powershell\n# Terminal 1 — mock plant backend (demo, no AVEVA required)\n.venv\\Scripts\\python mock_backend.py\n\n# Terminal 2 — opcua-mcp (optional, needs OPC-UA simulator or server)\ncd ..\\opcua-mcp \u0026\u0026 .venv\\Scripts\\python server.py\n\n# Terminal 3 — aggregator\n.venv\\Scripts\\python server.py\n```\n\n**macOS / Linux**\n```bash\n# Terminal 1 — mock plant backend (demo, no AVEVA required)\n.venv/bin/python mock_backend.py\n\n# Terminal 2 — opcua-mcp (optional, needs OPC-UA simulator or server)\ncd ../opcua-mcp \u0026\u0026 .venv/bin/python server.py\n\n# Terminal 3 — aggregator\n.venv/bin/python server.py\n```\n\nOr use the launcher scripts:\n\n```powershell\n# Windows\n.\\start_demo.ps1\n```\n```bash\n# macOS / Linux (make executable once, then run)\nchmod +x start_demo.sh\n./start_demo.sh\n```\n\nThe aggregator runs on port 8100 by default. Set `AGGREGATOR_PORT` in `.env` to change.\n\n## backends.json\n\nEdit to add or remove backends. The aggregator reads this at startup. Set the\n`BACKENDS_FILE` environment variable to point at a different file (relative to the\nscript directory, or absolute path) — useful for keeping separate demo and production\nconfigs without modifying `backends.json`.\n\n```json\n[\n  { \"name\": \"opcua\", \"url\": \"http://localhost:8002/sse\" },\n  { \"name\": \"plant\", \"url\": \"http://localhost:8003/sse\" }\n]\n```\n\nIf a backend is unreachable at startup, its tools are silently skipped and a warning\nis logged. The aggregator still starts with whatever tools it could discover.\n\n### Tool filtering\n\nUse `include_tools` or `exclude_tools` to control which tools are exposed from a\nbackend. Filtering happens at discovery time — filtered tools never appear in the\naggregated namespace. The two fields are mutually exclusive; using both on the same\nbackend is an error.\n\n```json\n[\n  {\n    \"name\": \"influxdb\",\n    \"url\": \"http://localhost:8003/sse\",\n    \"include_tools\": [\"query\", \"list_measurements\"]\n  },\n  {\n    \"name\": \"opcua\",\n    \"url\": \"http://localhost:8002/sse\",\n    \"exclude_tools\": [\"dangerous_write_tool\"]\n  }\n]\n```\n\n### Default arguments\n\nUse `default_args` to inject argument defaults into every tool call forwarded to a\nbackend. Caller-supplied arguments always take precedence over defaults. This is useful\nfor scoping a backend to a specific context — for example, pointing two MQTT backends\nat the same server but restricting each to a different topic namespace:\n\n```json\n[\n  {\n    \"name\": \"mqtt_rawwater\",\n    \"url\": \"http://localhost:8001/sse\",\n    \"include_tools\": [\"read_topic_value\", \"scan_topics\"],\n    \"default_args\": { \"topic_filter\": \"Plant/WTP/Pump/RawWater_*\" }\n  },\n  {\n    \"name\": \"mqtt_treated\",\n    \"url\": \"http://localhost:8001/sse\",\n    \"include_tools\": [\"read_topic_value\", \"scan_topics\"],\n    \"default_args\": { \"topic_filter\": \"Plant/WTP/Pump/Treated_*\" }\n  }\n]\n```\n\n### Streamable HTTP backends\n\nBackends that serve the newer Streamable HTTP transport can be declared with\n`\"transport\": \"streamable_http\"`. The default is `\"sse\"` if the field is absent.\n\n```json\n[\n  { \"name\": \"opcua\", \"url\": \"http://localhost:8002/sse\" },\n  { \"name\": \"analytics\", \"url\": \"http://localhost:8004/mcp\", \"transport\": \"streamable_http\" }\n]\n```\n\nStreamable HTTP backends benefit from connection pooling — the aggregator keeps one\npersistent `ClientSession` open per backend and routes all calls through it. SSE\nbackends still open a fresh connection per call.\n\n## Management API\n\nThe aggregator exposes a runtime management API for adding and removing backends\nwithout restarting. All endpoints are unauthenticated.\n\n\u003e **OT environment note:** In a live operational technology environment, expose the\n\u003e management API only on a trusted network interface or behind a reverse proxy with\n\u003e access controls. Unauthenticated `POST /backends` allows any caller to register\n\u003e an arbitrary backend server.\n\n| Method | Path | Description |\n|---|---|---|\n| `GET` | `/backends` | List active backends, tool counts, pool status |\n| `POST` | `/backends` | Add a backend (same JSON shape as a backends.json entry) |\n| `DELETE` | `/backends/{name}` | Remove a backend and deregister its tools |\n| `POST` | `/backends/reload` | Re-read backends.json, add new entries, remove gone ones |\n\n**Note:** MCP clients (including Claude Desktop) call `tools/list` once at connect\ntime and cache the result. Adding or removing a backend takes effect for new client\nconnections but won't be visible to already-connected clients until they reconnect.\n\n### Example: add a backend at runtime\n\n```bash\ncurl -X POST http://localhost:8100/backends \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"historian\", \"url\": \"http://192.168.1.50:8005/sse\"}'\n```\n\n### Example: reload from backends.json\n\n```bash\ncurl -X POST http://localhost:8100/backends/reload\n```\n\n## Running as a Windows service\n\nThe aggregator can run as an auto-start Windows service via [NSSM](https://nssm.cc/download).\nStart the three backend MCP servers before starting the aggregator — tool discovery runs at\nstartup and backends that are unreachable at that point will not have their tools registered.\n\n```powershell\n# Install (run as Administrator)\n.\\install_service.ps1\n\n# Remove\n.\\uninstall_service.ps1\n```\n\nEdit the `$BackendsFile` variable at the top of `install_service.ps1` to point at your\nbackends config. The service is installed as `AVEVA Demo McpAggregator` on port 8100.\n\n## Claude Desktop config\n\nClaude Desktop requires HTTPS for URL-based remote connectors and rejects plain HTTP\nentries at config validation. The workaround is [`mcp-remote`](https://www.npmjs.com/package/mcp-remote),\na lightweight npm bridge that runs as a local stdio subprocess and connects to the\naggregator over HTTP internally.\n\n**Prerequisite:** Node.js on the client machine (`winget install OpenJS.NodeJS` or\n[nodejs.org](https://nodejs.org)).\n\nAdd to `%APPDATA%\\Claude\\claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"scada\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"mcp-remote\", \"http://\u003caggregator-host\u003e:8100/mcp\", \"--allow-http\"]\n    }\n  }\n}\n```\n\nReplace `\u003caggregator-host\u003e` with the IP or hostname of the machine running the aggregator\n(e.g. `192.168.80.134`). Use `localhost` if Claude Desktop and the aggregator are on the\nsame machine.\n\nThe `--allow-http` flag is required because `mcp-remote` also enforces HTTPS by default\nfor non-localhost URLs.\n\nThe aggregator serves two transports on the same port:\n\n| Path | Transport | Use for |\n|---|---|---|\n| `/mcp` | Streamable HTTP | Claude Desktop (via mcp-remote), modern MCP clients |\n| `/sse` | Legacy SSE | Older clients, custom chat UIs |","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmslavin%2Fmcp-aggregator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmslavin%2Fmcp-aggregator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmslavin%2Fmcp-aggregator/lists"}