Skip to content

docs: fix the control plane, node and router API references - #788

Open
kaisoz wants to merge 19 commits into
google:mainfrom
kaisoz:kaisoz/fix-docs-http-api
Open

kaisoz wants to merge 19 commits into
google:mainfrom
kaisoz:kaisoz/fix-docs-http-api

Conversation

@kaisoz

@kaisoz kaisoz commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Why

The control plane and node API references are what client authors code against. Nineteen statements there disagree with the handlers: methods, encodings, status codes, which routes accept which credentials, and what some routes return.

What changed

  • Control plane reference.
    • /oauth/authorize takes POST, and /oauth/token also serves RFC 8693.
    • Four mesh routes carry the challenge in headers.
    • Approve and reject answer plain text, with 409 and 410.
    • /admin/revoke takes TokenRevokeRequest in either encoding.
    • Non-admin bootstrap tokens are capped.
    • /user/revoke names the node in ?id=.
    • The page lists the routes that send CORS headers.
  • Node API reference.
    • The node accepts Authorization on every route but the proxy path.
    • /readyz and several other routes answer 503 until the node holds a router connection, and the page names the routes that answer while disconnected.
    • /oauth/revoke accepts a verifying Biscuit without a token.
    • / is not an alias for /mcp, and a GET on /mcp answers 405.
    • /mesh/identity returns no roles or labels.
    • /debug/connect-peer is POST.
    • get_mesh_info also returns peer_id.
    • The node serves the Envoy v2 ext_authz path too.
    • A broken in-page link is fixed.
  • Router reference. A member holds no DHT or gossipsub stream on its router.

Scope

  • One commit per corrected claim. Docs, plus two Go comments, a router help string and the chaos agent's comment and README. No behavior change.
  • The boundaries concept repeated the /mesh/identity claim and is fixed in the same commit.

Verification

  • Each claim was checked against the handlers in internal/controlplane, internal/node and internal/router, and the MCP library's stateless handler. An independent review confirmed the new text, and its corrections are in.
  • make lint passes. The site builds with Hugo 0.147.5 and has no broken link.
  • The router reference row next to the one the metrics fix changes will need a rebase of whichever of the two PRs merges second.

kaisoz added 19 commits October 20, 2026 07:43
The control plane reference listed GET /oauth/authorize and described
/oauth/token as the authorization code endpoint only.
HandleOAuthAuthorize serves GET and POST
(internal/controlplane/sts.go:1347-1352), and HandleOAuthToken
dispatches grant_type token-exchange as well as authorization_code
(internal/controlplane/sts.go:1539-1563). For a JWT subject_token the
exchange mints a Delegated Session Biscuit
(internal/controlplane/sts.go:1747). For a Biscuit subject_token it
returns that Biscuit, attenuated when the request carries task
parameters (internal/controlplane/sts.go:1719-1726,
api/tar.go:635-637).
The control plane reference said every enrollment, refresh and STS
request carries challenge_unix_ms and challenge_signature in the body.
GET /revocations, GET /policies, GET /egress and POST /nodes/catalog
read them from the X-Mesh-Challenge-Ts and X-Mesh-Challenge-Sig headers
and answer 401 without them. The helper is admittedNodeWithChallenge
(internal/controlplane/server.go:1875-1889, 2502-2506), called from
internal/controlplane/server.go:1703 and 1989,
internal/controlplane/sts.go:719 and
internal/controlplane/catalog.go:121.
The admin table implied approve and reject answer protojson and listed
no error codes. Both write 200 with a plain-text body
(internal/controlplane/server.go:2941-2942, 3062-3063). A request
already resolved answers 409 (internal/controlplane/server.go:2924,
2933, 3052). Approve answers 410 for a token revoked, expired or spent
since the request was queued, and 403 for a banned token owner
(internal/controlplane/server.go:2959-2973).

The HTTP API and Admin intros said every request and response is a
protojson or protobuf message. The intros now name the exceptions.
/healthz and /readyz write a JSON status
(internal/controlplane/server.go:783-813). Approve, reject and
/user/revoke write plain text. POST and PUT /policies also take binary
protobuf and answer in the encoding of the request
(internal/controlplane/server.go:1732-1790).
The admin table showed /admin/revoke with an ad-hoc JSON body. The
handler decodes api.TokenRevokeRequest, as protojson when the request is
application/json and as binary protobuf otherwise, and answers
api.TokenRevokeResponse in the same encoding
(internal/controlplane/server.go:3167-3179, 3215-3221).
The user table said /user/bootstrap-tokens takes the same body as the
admin route. A caller who is not an administrator is refused with 403
for a role other than mesh:role:node or for autonomous_recovery, and
with 400 for ttl_hours over 168 or max_usages over 10
(internal/controlplane/server.go:3454-3455, 3480-3486, 3502-3508).
The user table said /user/revoke bans one of the caller's own nodes and
named no parameter. The peer ID comes from the id query parameter
(internal/controlplane/server.go:3583), an administrator may ban any
node (internal/controlplane/server.go:3608-3609), and the handler
answers 200 with a plain-text body
(internal/controlplane/server.go:3623-3624).
The control plane reference did not say which routes send CORS headers.
meshSurface sends Access-Control-Allow-Origin: * and answers OPTIONS
with 204 (internal/controlplane/server.go:443-456). RegisterRoutes wraps
the unauthenticated routes except /healthz, /readyz, /metrics and
/oauth/authorize, and the mesh routes except /routers/lease and
/nodes/catalog (internal/controlplane/server.go:389-413). Routes under
/admin/ and /user/ get no CORS headers. POST and PUT /policies get
them, because one wrapped handler serves every method of /policies
(internal/controlplane/server.go:400).
The node API reference said Authorization carries the token only on
/mcp, /v1/*, /egress/* and /oauth/*. Every route reads it except the
proxy path, /mesh/{peer-id}/{type}/{name}. withAuth has the fallback on
for /metrics, /mesh/identity, /mesh/peer/ and /debug/
(internal/node/sidecar.go:68, 83-94). withCallerOrTokenAuth has it on
for /mesh/service/discover, /v1/*, /egress/, /mcp and CONNECT
(internal/node/sidecar.go:76, 108-123,
internal/node/egress_tunnel.go:308). authenticateSidecarCaller reads it
for /oauth/* (internal/node/sts.go:685). Only the proxy passes the
fallback off (internal/node/sidecar.go:99).
The node API reference said /readyz answers 200 while the process is up.
handleReadyzFor answers 503 until the node holds an authenticated
connection to a router (internal/node/sidecar.go:386-396,
internal/node/node.go:892-900).
The node API reference said every route except /debug/* and
/.well-known/* answers 503 until the node is connected to the mesh. Only
the handlers behind withMeshConnection do: /mesh/service/discover,
/mesh/peer/, the proxy path, /v1/* and /mcp
(internal/node/sidecar.go:76, 88, 99, 108-110, 123). /metrics,
/mesh/identity, /egress/, /oauth/*, the Envoy routes and CONNECT answer
while the node is disconnected (internal/node/sidecar.go:57-68, 83,
117, internal/node/egress_tunnel.go:306-320).
The node API reference listed /oauth/revoke as needing the token. The
handler revokes a Biscuit that verifies under a trusted control plane
key without authenticating the caller (internal/node/sts.go:744-755).

The comment on handleNodeOAuthRevoke said the handler caches the root
Block 0 revocation ID. RevokeBiscuitToken caches the ID of the last
block, so the parent token stays valid (internal/node/node.go:310).
The node API reference said / is an alias for /mcp. The MCP handler is
mounted at / (internal/node/sidecar.go:123), but its inner mux registers
only /mcp (internal/node/mcp.go:187), so a request to / answers 404.
The node API reference and the boundaries page
(site/content/docs/concepts/boundaries.md) said /mesh/identity returns
the credential, the key it verifies under, the peer ID, roles, labels
and expiry, and that the peer evidence route returns the same.
IdentityEvidenceResponse carries peer_id, biscuit, biscuit_expire_time,
control_plane_url, trusted_control_plane_keys and check_time
(internal/node/identity_evidence.go:249-256,
api/agentmesh.proto:823-830) as protojson
(internal/node/identity_evidence.go:159). Only PeerEvidenceResponse
carries verifying_key, roles, labels, expire_time and revocation_ids
(api/agentmesh.proto:832-841).
The node route table listed /debug/* as GET. connect-peer is registered
as POST (internal/node/debug_handlers.go:74), as the Debug section
already says.
The node API reference and a comment in cmd/chaos-agent/agent.py said
the older SSE transport is refused with 400. The MCP server runs
stateless (internal/node/mcp.go:159), and the stateless handler of the
library go-sdk v1.8.0 answers every method but POST with 405
(~/go/pkg/mod/github.com/modelcontextprotocol/go-sdk@v1.8.0/mcp/streamable.go:381-384).

cmd/chaos-agent/README.md said the agent connects via SSE. The agent
opens a streamable_http_client (cmd/chaos-agent/agent.py:97).
The node API reference listed four fields for get_mesh_info. The tool
returns meshInfo whole, whose first field is peer_id
(internal/node/mcp_handlers.go:122,
internal/node/debug_handlers.go:132-136).
The node API reference named only the v3 gRPC path,
envoy.service.auth.v3.Authorization/Check. The gateway also mounts
/envoy.service.auth.v2.Authorization/Check and rewrites it to the v3
path before dispatch (internal/envoy/server.go:51, 113, 120-128).
The router metrics table and the help text of
agentmesh_router_streams_refused_total said a refused stream on
/mesh/kad/1.0.0 or /meshsub/* is a member kept out of discovery. A
member builds no DHT and is a floodsub leaf of the gossip
(internal/node/node.go:709-712). A member reaches the directory on
/mesh/directory/1.0.0 (api/directory_keys.go:31), and after a refusal
it asks the next router it holds
(internal/node/directory_client.go:210-224). Peer routers place records
on that protocol too (internal/router/directory.go:425). Only peer
routers speak /mesh/kad/1.0.0, and the control plane joins the gossip
as a gossipsub peer (internal/controlplane/mesh.go:165).
The node route table linked #token-endpoint-oauthtoken. Hugo renders
the heading as #token-endpoint-oauthtoken-and-revocation-oauthrevoke,
and the site build reports the link as broken.
@kaisoz
kaisoz requested a review from aojea as a code owner October 20, 2026 07:52

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates documentation, code comments, and metric help text across the repository to align with the current implementation of the Agent Mesh APIs. Key updates include documenting the Streamable HTTP transport for MCP (replacing SSE), detailing OAuth 2.1 token exchange and revocation behaviors, specifying CORS and challenge headers, and clarifying response structures for identity evidence, admin, and user endpoints. No code changes were made to Go logic, and there are no review comments to address; therefore, I have no feedback to provide.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant