router: multi-domain MCP gateway (per-domain + merged) #116

Closed
opened 2026-06-14 12:31:54 +00:00 by mahmoud · 1 comment
Owner

Goal

Make the router's MCP gateway multi-domain. Today the router collapses every service to a single (first) domain; multi-domain services (e.g. hero_proc with jobs/logs/secrets/system) only expose their first domain over MCP.

Model (confirmed): services serve one rpc.sock, domains are path segments — GET /api/domains.json, GET /api/{domain}/openrpc.json, POST /api/{domain}/rpc, GET /api/{domain}/events. The router's discovery/proxy already follow this; the gap is the MCP layer + spec cache + UI.

Gaps (verified on development)

  • scanner.rs:476-480 caches only domains.first()'s spec → other domains invisible.
  • ServiceEntry has a single openrpc_json + domains: Vec<String> (names only) — no per-domain specs.
  • MCP route is only POST /mcp/{id} (routes.rs:1514); tools/list uses the one entry.openrpc_json.
  • tools/call isn't domain-aware; UI/explorer/markdown render one spec.

Plan / tasks

  • T1 — per-domain spec cache: store a spec per domain on ServiceEntry; scanner fetches every domain (not just the first).
  • T2 — per-domain + merged MCP: add POST /mcp/{id}/{domain} (per-domain) + keep POST /mcp/{id} as a merged MCP (domain-qualified tool names).
  • T3 — domain-aware tools/call: resolve (domain, method) and forward to /api/{domain}/rpc on the service rpc.sock.
  • T4 — UI/explorer/markdown: list per-domain + merged MCP endpoints; render all domains.
  • T5 — tests: per-domain + merged MCP cases against the two-domain demo server (hero_lib/examples/hero_lifecycle/hero_demo_server); build/verify from development.

Notes

  • Branch development_router_multidomain_mcp off development; PR → development.
  • Out of scope here: the router's own control-plane server stays the hand-rolled dispatcher (chicken-and-egg, later); access.rs proc-secrets wire fix tracked separately.
## Goal Make the router's **MCP gateway multi-domain**. Today the router collapses every service to a single (first) domain; multi-domain services (e.g. `hero_proc` with `jobs/logs/secrets/system`) only expose their first domain over MCP. Model (confirmed): services serve **one `rpc.sock`**, domains are path segments — `GET /api/domains.json`, `GET /api/{domain}/openrpc.json`, `POST /api/{domain}/rpc`, `GET /api/{domain}/events`. The router's discovery/proxy already follow this; the gap is the **MCP layer + spec cache + UI**. ## Gaps (verified on `development`) - `scanner.rs:476-480` caches only `domains.first()`'s spec → other domains invisible. - `ServiceEntry` has a single `openrpc_json` + `domains: Vec<String>` (names only) — no per-domain specs. - MCP route is only `POST /mcp/{id}` (`routes.rs:1514`); `tools/list` uses the one `entry.openrpc_json`. - `tools/call` isn't domain-aware; UI/explorer/markdown render one spec. ## Plan / tasks - [ ] **T1 — per-domain spec cache**: store a spec per domain on `ServiceEntry`; scanner fetches every domain (not just the first). - [ ] **T2 — per-domain + merged MCP**: add `POST /mcp/{id}/{domain}` (per-domain) + keep `POST /mcp/{id}` as a merged MCP (domain-qualified tool names). - [ ] **T3 — domain-aware `tools/call`**: resolve `(domain, method)` and forward to `/api/{domain}/rpc` on the service `rpc.sock`. - [ ] **T4 — UI/explorer/markdown**: list per-domain + merged MCP endpoints; render all domains. - [ ] **T5 — tests**: per-domain + merged MCP cases against the two-domain demo server (`hero_lib/examples/hero_lifecycle/hero_demo_server`); build/verify from `development`. ## Notes - Branch `development_router_multidomain_mcp` off `development`; PR → `development`. - Out of scope here: the router's own control-plane server stays the hand-rolled dispatcher (chicken-and-egg, later); `access.rs` proc-secrets wire fix tracked separately.
mahmoud self-assigned this 2026-06-14 12:40:12 +00:00
mahmoud added this to the ACTIVE project 2026-06-14 12:40:16 +00:00
mahmoud added this to the now milestone 2026-06-14 12:40:19 +00:00
Author
Owner

Covered by PR #118 (router: multi-domain MCP gateway (per-domain + merged), merged into development @ e7c9e49). All tasks delivered against the confirmed single-rpc.sock / domains-as-path-segments model:

  • T1 — per-domain spec cache acca99cServiceEntry.domain_specs: BTreeMap<String,String> + spec_for_domain() / domain_spec_pairs() (cache.rs:220,251,260); scanner fetches every domain's /api/{domain}/openrpc.json, not just the first (scanner.rs:455-480).
  • T2 — per-domain + merged MCP 51eef73POST /mcp/{id} (merged, domain-qualified tool names via DOMAIN_SEP = "__") and POST /mcp/{id}/{domain} (per-domain, plain names) (routes.rs:1525,1533; mcp.rs:34,91,103).
  • T3 — domain-aware tools/call 51eef73proxy_to_service() resolves (domain, method) and forwards to /api/{domain}/rpc on the single rpc.sock (mcp.rs:791,809).
  • T4 — UI/explorer/markdown f48fb3e/mcp overview lists merged + per-domain endpoints (mcp.html:165-170); the OpenRPC explorer renders all domains via a selector hitting /api/{domain}/openrpc.json + /api/{domain}/rpc (service_openrpc.html:35,55-67).
  • T5 — tests 1aef9bf — per-domain + merged cases against the two-domain demo server (hero_router_test/src/functional/mcp.rs:63-113), live-verified (merged alpha__alpha_ping / per-domain alpha_ping; tools/call lands on the right domain).

Closing as done. (Minor cosmetic follow-up, not blocking: a stale doc comment at routes.rs:466 still references the old rpc_<domain>.sock model — the code itself uses the correct single-socket paths.)

Covered by **PR #118** (`router: multi-domain MCP gateway (per-domain + merged)`, merged into `development` @ `e7c9e49`). All tasks delivered against the confirmed single-`rpc.sock` / domains-as-path-segments model: - **T1 — per-domain spec cache** ✅ `acca99c` — `ServiceEntry.domain_specs: BTreeMap<String,String>` + `spec_for_domain()` / `domain_spec_pairs()` (`cache.rs:220,251,260`); scanner fetches **every** domain's `/api/{domain}/openrpc.json`, not just the first (`scanner.rs:455-480`). - **T2 — per-domain + merged MCP** ✅ `51eef73` — `POST /mcp/{id}` (merged, domain-qualified tool names via `DOMAIN_SEP = "__"`) **and** `POST /mcp/{id}/{domain}` (per-domain, plain names) (`routes.rs:1525,1533`; `mcp.rs:34,91,103`). - **T3 — domain-aware `tools/call`** ✅ `51eef73` — `proxy_to_service()` resolves `(domain, method)` and forwards to `/api/{domain}/rpc` on the single `rpc.sock` (`mcp.rs:791,809`). - **T4 — UI/explorer/markdown** ✅ `f48fb3e` — `/mcp` overview lists merged + per-domain endpoints (`mcp.html:165-170`); the OpenRPC explorer renders all domains via a selector hitting `/api/{domain}/openrpc.json` + `/api/{domain}/rpc` (`service_openrpc.html:35,55-67`). - **T5 — tests** ✅ `1aef9bf` — per-domain + merged cases against the two-domain demo server (`hero_router_test/src/functional/mcp.rs:63-113`), live-verified (merged `alpha__alpha_ping` / per-domain `alpha_ping`; `tools/call` lands on the right domain). Closing as done. (Minor cosmetic follow-up, not blocking: a stale doc comment at `routes.rs:466` still references the old `rpc_<domain>.sock` model — the code itself uses the correct single-socket paths.)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_router#116
No description provided.