Add edge routing design doc and deploy-agent script; ignore built binaries

Previously untracked working-tree files. mcp-cli and mcp-master are local
build outputs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-20 15:04:15 -07:00
co-authored by Claude Fable 5.1
parent 90d47427ff
commit d5d3004518
3 changed files with 283 additions and 0 deletions
+239
View File
@@ -0,0 +1,239 @@
# 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:<assigned-port>`. Internal access.
2. **Edge** (svc mc-proxy): `kls.metacircular.net`
`<rift-tailscale-ip>:<assigned-port>`. 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:<port>
│ cert: /srv/mc-proxy/certs/kls.pem (internal SAN)
└── call svc agent: RegisterEdgeRoute
├── hostname: kls.metacircular.net
├── backend: <rift-tailscale-ip>:<port>
├── 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: <rift-tailscale-ip>:<port>
```
### 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.