Skip to content

fix: prevent addMcpServer from reporting restored OAuth as ready#1869

Open
mattzcarey wants to merge 5 commits into
mainfrom
fix/addmcpserver-false-ready-on-restored-auth
Open

fix: prevent addMcpServer from reporting restored OAuth as ready#1869
mattzcarey wants to merge 5 commits into
mainfrom
fix/addmcpserver-false-ready-on-restored-auth

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #1855

Problem

After a Durable Object wake, an HTTP MCP connection restored during OAuth is still AUTHENTICATING, but its provider no longer has the live in-memory authUrl. addMcpServer() required that live URL to report authenticating, so it returned ready while getMcpServers() reported authenticating for the same server.

Fix

For an existing AUTHENTICATING connection, addMcpServer() now:

  1. returns a non-empty live provider URL when present;
  2. otherwise returns the persisted URL when it is a valid absolute http: or https: URL;
  3. otherwise reconnects the existing connection and row, without re-registering it:
    • AUTHENTICATING with a URL returns authenticating and persists/broadcasts that URL;
    • CONNECTED runs discovery and returns ready only after discovery succeeds;
    • FAILED throws the normal HTTP connection error;
    • an incomplete AUTHENTICATING result without a URL throws an explicit OAuth configuration error.

Other established/non-failed connection states retain the existing ready behavior, and existing failed connections retain their existing error behavior.

Scope

  • Persisted authorization URLs are syntax/scheme checked only. This PR does not call checkState() or claim that a stored URL is fresh or callback-redeemable.
  • This PR does not add general HTTP server-id reuse. Reconnect is limited to the existing restored connection and its existing id.
  • The changeset releases only agents.

TDD evidence

The restored-state regression failed against the origin/main implementation with { state: "ready", authUrl: undefined } instead of the persisted authenticating result. Subsequent vertical slices were also observed red before their minimal outcome mapping was added: no-URL reconnect returned ready; connected, failed, and incomplete reconnect outcomes were unmapped; and a javascript: persisted URL was returned instead of reconnecting.

The focused OAuth file is now green: 29/29 tests. Coverage includes live-URL precedence, restored persisted HTTP(S) URLs, URL persistence/broadcast with the same id, valid stored-token discovery, connection failure, incomplete OAuth output, and malformed/relative/javascript:/non-HTTP persisted URLs.

Verification

  • pnpm install --frozen-lockfile — passed
  • pnpm --filter agents exec vitest --project workers --run src/tests/mcp/oauth2-mcp-client.test.ts — 29/29 passed
  • pnpm --filter agents test:workers — 75 files, 1,515 tests passed
  • pnpm --filter agents build — passed
  • pnpm run typecheck — 115/115 projects passed
  • pnpm run check — passed
  • pnpm run build — 24/24 projects passed
  • CI=true pnpm exec nx affected -t test — passed for 13 projects and 8 dependent tasks on rerun; Nx identified the first-run @cloudflare/think:test time-bound sweep failure as flaky, its exact test passed in isolation, and the full required command then passed

The branch includes current origin/main and was pushed without force-pushing.

…ctions

A connection restored after a Durable Object wake keeps its persisted
AUTHENTICATING state, but the OAuth provider's in-memory authUrl is only
assigned during a live redirectToAuthorization() and is never rehydrated
from storage. addMcpServer's existing-connection early return required
that in-memory URL to report authenticating, so a restored connection
awaiting consent fell through to READY — disagreeing with getMcpServers()
for the same server in the same tick, and wedging callers that drive the
OAuth prompt off the return value.

addMcpServer now falls back to the persisted auth_url for authenticating
connections. If no auth URL exists in memory or storage, it re-runs the
connect flow to mint a fresh one instead of reporting READY. The HTTP
path also reuses an existing server's id (as the RPC path already did)
so re-adding a known server never orphans its storage row.

Fixes #1855
…links

Second-round hardening on top of the persisted-auth_url fallback:

- The persisted auth_url is only served while its OAuth state is still
  redeemable, validated through the provider's own checkState. The state
  nonce and code verifier expire (10 minutes for the built-in provider),
  so a URL restored after a long hibernation can be a dead link; serving
  it would wedge the flow at the callback with 'State expired'. Expired
  or unverifiable-invalid URLs now fall through to the reconnect path,
  which mints and re-persists a fresh sign-in link. The check fails open:
  URLs without a state parameter and providers without checkState are
  served as before.

- Pins the HTTP-path id-reuse behavior with a dedicated test: re-adding a
  known (name, url) whose in-memory connection is gone reuses the stored
  row's id instead of minting a fresh one and orphaning the row.

Fixes #1855
@changeset-bot

changeset-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9f1d0ef

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agents Patch
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@devin-ai-integration devin-ai-integration 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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 3, 2026

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@1869

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@1869

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@1869

create-think

npm i https://pkg.pr.new/create-think@1869

hono-agents

npm i https://pkg.pr.new/hono-agents@1869

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@1869

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@1869

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@1869

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@1869

commit: 9f1d0ef

Restored AUTHENTICATING connections lose the provider's in-memory authUrl, so addMcpServer previously fell through to READY even while storage still reported OAuth in progress. Prefer live then persisted HTTP(S) authorization URLs, and reconnect the existing connection when neither is servable, mapping authentication, discovery, and failure outcomes without re-registering it.
@mattzcarey mattzcarey changed the title fix: addMcpServer returns READY for a restored connection stuck AUTHENTICATING fix: prevent addMcpServer from reporting restored OAuth as ready Jul 6, 2026
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.

addMcpServer returns READY for a restored connection stuck AUTHENTICATING (in-memory authUrl never rehydrated on wake)

1 participant