# Edge Routing Design **Status: SUPERSEDED by ARCHITECTURE_V2.md (2026-04-01).** This draft used agent-to-agent communication, a single shared cert with both SANs, private key transmission over gRPC, and an `edge` field on routes. All of these were replaced in the v2 architecture by master- mediated coordination, two separate certs, edge-local cert provisioning, and `public = true` on routes. Retained for historical context only. --- Draft design for publicly accessible services via edge nodes. --- ## Problem Services run on internal nodes (rift, hyperborea) behind mc-proxy. To make a service publicly accessible, an edge node (svc) needs an mc-proxy route pointing back to the internal node. Today this is manual: configure svc's mc-proxy, provision a cert, set up DNS. MCP should automate everything except DNS. ## Model A route in the service definition gains an `edge` field naming the public-facing node: ```toml [[components.routes]] port = 443 mode = "l7" hostname = "kls.metacircular.net" edge = "svc" ``` When `edge` is set, MCP registers the route on **two** mc-proxy instances: 1. **Local** (rift mc-proxy): `kls.metacircular.net` → `127.0.0.1:`. Internal access. 2. **Edge** (svc mc-proxy): `kls.metacircular.net` → `:`. Public access. ## Agent-to-Agent Coordination mc-proxy is a local concern — each node's MCP agent is the sole authority for its mc-proxy. Cross-node route registration goes through agent-to-agent gRPC, not direct mc-proxy access. ### Deploy Flow ``` mcp deploy kls │ ▼ rift agent (node where container runs) ├── pull image, start container ├── allocate host port ├── provision TLS cert (both SANs, see below) ├── register route on rift mc-proxy (local) │ hostname: kls.metacircular.net │ backend: 127.0.0.1: │ cert: /srv/mc-proxy/certs/kls.pem (internal SAN) │ └── call svc agent: RegisterEdgeRoute ├── hostname: kls.metacircular.net ├── backend: : ├── mode: l7 └── cert: pushed with request (public SAN) │ ▼ svc agent ├── write cert to /srv/mc-proxy/certs/kls.pem └── register route on svc mc-proxy (local) hostname: kls.metacircular.net backend: : ``` ### Undeploy Flow Reverse of deploy. Rift agent calls svc agent to remove the edge route, then tears down locally. ## TLS Certificates There are two hostnames involved: | Hostname | Where used | Purpose | |----------|-----------|---------| | `kls.svc.mcp.metacircular.net` | rift mc-proxy | Internal platform access | | `kls.metacircular.net` | svc mc-proxy | Public internet access | ### Options **Option A: Two separate certs.** Rift's agent provisions one cert with SAN `kls.svc.mcp.metacircular.net` for the internal route. For the edge route, the rift agent (or svc agent) provisions a second cert with SAN `kls.metacircular.net` and pushes it to svc. **Option B: Single cert with both SANs.** The agent requests one cert from Metacrypt with both `kls.svc.mcp.metacircular.net` and `kls.metacircular.net` as SANs. This cert is used on both rift and svc mc-proxy instances. Option B is simpler (one cert to manage, one renewal) but means internal certs carry public hostnames. Option A is cleaner separation but doubles the cert management. ### Recommendation Option B (single cert, both SANs) for simplicity. The Metacrypt CA is internal anyway — both names are under our control. The cert issuance request already accepts a list of `dns_names`, so this is a natural fit: ```json { "dns_names": [ "kls.svc.mcp.metacircular.net", "kls.metacircular.net" ] } ``` The agent pushes the same cert to both rift and svc mc-proxy cert dirs. ### Trust Consideration The Metacrypt CA cert is not publicly trusted. External clients hitting `kls.metacircular.net` will see an untrusted cert. This is acceptable for now (platform services are operator-facing, `-k` or CA trust is expected). Future option: Let's Encrypt / ACME on svc mc-proxy for publicly trusted certs. ## New RPC: RegisterEdgeRoute ```protobuf rpc RegisterEdgeRoute(RegisterEdgeRouteRequest) returns (RegisterEdgeRouteResponse); message RegisterEdgeRouteRequest { string hostname = 1; string backend = 2; // e.g. "100.95.252.120:13881" string mode = 3; // "l4" or "l7" bytes cert_pem = 4; // TLS cert chain bytes key_pem = 5; // TLS private key string service = 6; // service name (for cert file naming) } message RegisterEdgeRouteResponse { bool success = 1; string error = 2; } ``` A corresponding `RemoveEdgeRoute` RPC for undeploy: ```protobuf rpc RemoveEdgeRoute(RemoveEdgeRouteRequest) returns (RemoveEdgeRouteResponse); message RemoveEdgeRouteRequest { string hostname = 1; string service = 2; int32 port = 3; // mc-proxy listener port } message RemoveEdgeRouteResponse { bool success = 1; string error = 2; } ``` ## Agent Config Changes The agent needs to know how to reach other agents for edge route registration. This could be: 1. **Explicit config** — agent config lists peer agents: ```toml [[peers]] name = "svc" address = "100.x.x.x:9444" ``` 2. **Discovered from node registry** — the agent already receives node info during sync. If the CLI pushes node addresses to agents, the agent can dial peers without extra config. Option 2 is preferable (less config duplication) but requires the agent to have MCIAS credentials for authenticating to the peer agent. ## Service Definition Changes Proto: add `edge` field to `RouteSpec`: ```protobuf message RouteSpec { string name = 1; int32 port = 2; string mode = 3; string hostname = 4; string edge = 5; // edge node name (e.g. "svc") } ``` Servicedef: add `Edge` field to `RouteDef`: ```go type RouteDef struct { Name string `toml:"name,omitempty"` Port int `toml:"port"` Mode string `toml:"mode,omitempty"` Hostname string `toml:"hostname,omitempty"` Edge string `toml:"edge,omitempty"` } ``` ## Prerequisites 1. MCP agent provisioned on svc. 2. mc-proxy running on svc with gRPC admin socket (currently config-only on svc, per svc-deployment-plan.md). 3. Agent-to-agent authentication (MCIAS service credentials). ## Open Questions - Should the internal route (`kls.svc.mcp.metacircular.net`) also be registered on rift mc-proxy when an edge route exists? Or does the edge route replace the internal one? Probably both — internal for platform use, edge for public. - Port for the public mc-proxy listener: should it always be 443 for L7, or should the route's `port` field apply? For public services, 443 is the only sensible choice. - Should the edge route backend use TLS to the internal node (`backend_tls = true`)? Yes — traffic crosses Tailscale but defense-in-depth is good practice.