Skip to content

OAuth route lifecycle: restart-free credential pickup (proposed series) - #77

Draft
timcharper wants to merge 4 commits into
tbxark:masterfrom
timcharper:oauth-lifecycle
Draft

OAuth route lifecycle: restart-free credential pickup (proposed series)#77
timcharper wants to merge 4 commits into
tbxark:masterfrom
timcharper:oauth-lifecycle

Conversation

@timcharper

@timcharper timcharper commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Draft PR covering the full proposed series described in docs/OAUTH_LIFECYCLE.md: closing the gap where a downstream server that isn't OAuth-authorized yet at startup never gets its route mounted, requiring a full daemon restart after running -authorize.

This is a stack of 4 commits, each independently buildable/testable, intended to land as separate PRs one at a time rather than as one big change:

  1. docs: add OAuth/route lifecycle design notes, add AGENTS.md — the design doc this series implements, plus a repo AGENTS.md.
  2. refactor: extract per-server connect/mount into connectAndMount — no behavior change, groundwork for calling connect logic more than once.
  3. feat: always mount a server's route, fail fast instead of 404 — every configured server's route is mounted at startup; a server with no valid credential yet responds 503 (with the exact -authorize command to run) instead of a bare 404.
  4. feat: lazy connect-on-request for not-yet-ready routes, no restart — a not-yet-ready route retries the connection lazily on the next request (coalesced per-server via singleflight, so concurrent requests share one attempt), so running -authorize is enough — no restart, no re-mount.

I opened this as a draft to establish direction and get early feedback on the overall approach before splitting it up. #78 is the first commit of this series (the design doc) submitted on its own as a real, mergeable PR.

Design rationale, rejected alternatives (fsnotify, in-memory token cache, crash-on-change, config hot-reload), and a documented known limitation (concurrent refresh-token use isn't serialized by mcp-go, accepted as-is) are all written up in the doc itself.

🤖 Generated with Claude Code

timcharper and others added 4 commits August 11, 2026 20:19
Documents current mount-at-startup-only behavior, the resulting
restart-to-reauthorize gap, and the target design for closing it
(always mount, fail fast locally with no credential, single-flight
connect, disk as sole source of truth) as a stack of small
incremental changes rather than one large rewrite.

Also adds AGENTS.md (none existed) indexing the docs and pointing
at build/format/test commands.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
No behavior change. Pulls the goroutine body of the startup connect
loop (Start/Initialize, tool wiring, route mounting) out into a
standalone function taking an already-built Client/Server pair, so
it can be re-run later for a single server without repeating the
whole loop or restarting the daemon.

Groundwork for docs/OAUTH_LIFECYCLE.md step 3/4.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A server that fails to connect at startup (most commonly: no valid
OAuth token yet) now still gets its HTTP route mounted. Requests to
it get a clear 503 with the failure reason - for OAuth servers, the
exact `-authorize` command to run - instead of a bare 404 that reads
as "this route doesn't exist."

The failure is captured once at connect time and served as a fixed
response; there's no per-request network attempt or retry yet (that's
the next step). Update USAGE.md's OAuth section to match: restart is
still required to pick up a fresh `-authorize`, but the interim
failure mode is now a clear error rather than a 404.

Implements design points 1-2 / delivery step 3 in
docs/OAUTH_LIFECYCLE.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds serverRoute (route.go): the handler mounted for a server that
failed to connect at startup. Each request against it first does a
purely local, no-network check (checkServerAuth, the same one
-auth-status already performs) - if there's plausibly no usable
credential yet, it fails immediately with no connection attempt, so
retrying on every request to a still-unauthorized server costs
nothing. If a credential now looks present, it attempts a real
connect, with concurrent requests coalesced through a per-server
singleflight.Group into one attempt instead of a stampede. On
success it becomes a plain pass-through to the now-working handler,
and this machinery is never consulted again for that server.

This is the headline fix: running `-authorize` now takes effect on
the very next request, no daemon restart, no re-mount (mounting
still happens exactly once, at startup - only the internal
ready/not-ready state changes afterward).

Failed retry attempts close their half-built client so a persistently
broken server doesn't leak a connection per request; shutdown now
also closes any serverRoute that did connect, alongside the existing
clients map. Extracted wrapHandler (middleware wrapping) out of
connectAndMount so both the startup path and the retry path share it.

Updates USAGE.md's OAuth section: restart is no longer needed after
-authorize.

Implements design point 3a / delivery step 4, and notes point 2's
liveness-vs-credential distinction (no backoff yet, deliberately) in
docs/OAUTH_LIFECYCLE.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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