# Phase 7: end-to-end findings This document covers the validation that closes phase 7 (issue `-q6n`). The work splits naturally into two halves: 1. **Local end-to-end** — automated, runs against a fake Forgejo and a real forgejo-mcp child spawned through the live broker binary. Performed in this repo as `TestE2E_FullOAuthAndMCPFlow` under `cmd/broker/e2e_test.go`. 2. **Public Claude.ai validation** — manual, requires a real Forgejo instance and a publicly-reachable broker. Documented below as a step-by-step recipe so the operator can repeat it cleanly. The local automated half was the gating prerequisite: without it, every manual step would hide both setup mistakes and broker bugs. With it green, the manual run becomes mostly a configuration exercise. ## What the wiring shipped `cmd/broker/main.go` now composes every component built across phases 2–5 into a single binary: ``` http.ServeMux ├── /healthz ← internal/httpserver ├── /oauth/* ← internal/oauth.Server.Handler() ├── /.well-known/* ← internal/oauth.Server.Handler() └── /mcp ← internal/oauth.Authenticator.RequireBearer (gates internal/session.Registry.Handler()) ``` The `SpawnFunc` passed to the session registry composes `internal/supervisor` + `internal/bridge`: each new MCP session forks a `forgejo-mcp --transport stdio` subprocess with the user's upstream Forgejo token in env, wires its stdio through a bridge, and returns the bridge's `HandleSSE` as the per-session HTTP handler. The reaper is wired with a `RefreshForgejo` callback that calls the upstream Forgejo OAuth client and persists rotated tokens back to the `access_tokens` row. When a token comes within the rotation lead time of expiry, the rotator atomically swaps the session's child for one spawned with the fresh token; the sid is preserved and the client re-issues `initialize` on its next request. ## Local end-to-end test ```bash go test -v -run TestE2E_FullOAuthAndMCPFlow ./cmd/broker/ ``` The test: 1. Builds the broker binary in TestMain. 2. Builds `forgejo-mcp` from the sibling repo at `../../../forgejo-mcp` (skipped if not present; alternatively set `FORGEJO_MCP_BIN`). 3. Stands up a fake Forgejo on `httptest.Server` covering `/api/v1/version`, `/api/v1/user`, `/login/oauth/access_token`, and `/login/oauth/userinfo`. 4. Runs the broker with all CLI flags wired against the fake. 5. Walks through register → authorize → callback → token → /mcp initialize → tools/list. 6. Verifies that discovery (`/.well-known/oauth-authorization-server`) advertises the configured issuer. It catches at least three classes of regression: - **Wiring**: any component not plumbed correctly into main.go fails a step rather than reporting "not implemented". - **Subprocess-context**: an early version of the wiring used the per-request HTTP context for `supervisor.Start`. Because `exec.CommandContext` ties process lifetime to the ctx, this killed the forgejo-mcp child as soon as the `initialize` request returned; the subsequent `tools/list` came back as 502. Fixed by introducing a long-lived `childCtx` in `run()`. - **Session lifecycle**: 410 / 403 / 503 paths are exercised in unit tests but the happy-path 200 with `Mcp-Session-Id` minted on initialize, then reused on follow-up RPCs, is exercised here. ## Public Claude.ai validation (manual) This is the part the operator runs once. It needs: - a publicly-reachable hostname (`mcp.example.com`) with a real TLS cert (Caddy's automatic Let's Encrypt is sufficient — see `deploy-caddy.md`); - a real Forgejo instance you control; - a Claude.ai workspace where you can register a custom MCP connector. ### Setup 1. Provision the broker container per `deploy-podman.md`. Confirm `https://mcp.example.com/healthz` returns `{"status":"ok"}`. 2. Confirm discovery surface: ```bash curl -fsS https://mcp.example.com/.well-known/oauth-authorization-server | jq .issuer # → "https://mcp.example.com" curl -fsS https://mcp.example.com/.well-known/oauth-protected-resource | jq .resource # → "https://mcp.example.com/mcp" ``` 3. In Claude.ai, add a custom MCP connector pointing at `https://mcp.example.com/mcp` (or whatever the Claude.ai UI asks for — at the time of writing, Claude.ai detects the OAuth requirement from the protected-resource discovery doc and walks the user through the OAuth flow automatically). ### Walkthrough checklist Run through these in order. Each is a check the operator marks done. - [ ] **OAuth completes.** Claude.ai redirects to the broker's `/oauth/authorize`; the broker bounces to the Forgejo consent screen; Forgejo redirects back to `/oauth/callback`; Claude.ai shows the connector as authenticated. Expect the round trip to take a few seconds. - [ ] **Tool discovery.** Claude.ai's UI should list every tool the bundled `forgejo-mcp` exports — check at least `get_forgejo_mcp_server_version`, `list_my_repos`, `list_repo_issues` appear. - [ ] **Tool invocation.** Ask Claude something that exercises a tool (e.g. "list issues open on ``"). Verify the tool runs and the response cites real data from your Forgejo instance. - [ ] **Session reuse.** Run two queries in a row. The broker should send one initialize per session, but successive calls should reuse the same forgejo-mcp child — visible in the broker's logs as no new "session reaped" / spawn entries between calls. - [ ] **Idle timeout.** Leave the conversation idle for 16 minutes (idle timeout default is 15m + 30s reaper tick). Check broker logs for `"session reaped"` matching your sid. The next query should trigger a fresh spawn. - [ ] **Forgejo token refresh during active session.** Lower `--session-idle-timeout` to something tolerable (say 1 h) and use a Forgejo token TTL shorter than that. Run the connector continuously until the rotator fires (`"session rotated"` in logs). Confirm the next query still works — Claude.ai re-issues `initialize` on the swapped child without user intervention. - [ ] **Revocation.** Hit `POST /oauth/revoke` with the access token. The next /mcp request from Claude.ai should fail with 401; Claude.ai should automatically re-authenticate. ### Deliberately deferred Items not checked in this validation, tracked in the backlog: - **Rate limits** on `/oauth/register` and `/oauth/token` (`-ttl`). - **`/proc//environ` token exposure** via the Forgejo token in child env (`-1n2`). - **At-rest encryption of Forgejo tokens** (`-sd4`). - **Prometheus `/metrics`** for production observability (`-7fn`). - **Forgejo Actions CI** (`-0sq`). All of these are P3 hardenings, not blockers for the Claude.ai integration itself. ## Closing notes Every phase-1-through-6 component is now exercised end-to-end against a real `forgejo-mcp` child. The only wiring that can't be tested without a real Claude.ai workspace is the actual MCP-discovery → OAuth-handoff sequence in Claude.ai's client UI; that's left as a manual one-time operator task using the checklist above.