Skip to content

fix(client): getAgentInfo() retries on StreamableHTTP session errors (FastMCP compatibility)#1232

Open
bokelley wants to merge 5 commits intomainfrom
claude/issue-1231-fastmcp-streamablehttp-session
Open

fix(client): getAgentInfo() retries on StreamableHTTP session errors (FastMCP compatibility)#1232
bokelley wants to merge 5 commits intomainfrom
claude/issue-1231-fastmcp-streamablehttp-session

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 1, 2026

Closes #1231

Summary

AdCPClient.getAgentInfo() used connectMCP() with no post-connect retry logic. FastMCP and other stateful StreamableHTTP servers can return HTTP 400 "Missing session ID" on listTools() after a successful initialize handshake (e.g., when a load balancer routes the two requests to different process instances that don't share session state). The error propagated bare with no recovery path.

The standard callMCPTool path already handles this case via withCachedConnection's evict-and-retry loop. This PR brings getAgentInfo() to parity.

Changes:

  • SingleAgentClient.tsgetAgentInfo() MCP path: catch StreamableHTTPError from listTools(), re-connectMCP() with a fresh session, retry once. 401/403 fast-fail before reconnect (auth failures are not recoverable by reconnecting — matches withCachedConnection guard at mcp.ts:209). sessionErr is chained as retryErr.cause on double-failure, matching withCachedConnection diagnostic parity.
  • SingleAgentClient.ts — propagates customHeaders and debugLogs into connectOptions (previously silently dropped).
  • mcp.tsconnectMCP() now calls trackStreamableHTTPUrl() on success so subsequent callMCPTool calls to the same URL skip the SSE fallback probe.
  • mcp.ts — re-exports StreamableHTTPError for use by consumers.
  • adcp.ts — adds mcp_transport?: 'streamable_http' | 'sse' to AgentConfig. Field is defined for forward compatibility and aligns with the mcp_transport registry field from adcp#3066 (Option B). Active wiring for the callMCPTool path is a follow-up.

What was tested

  • node --test test/unit/mcp-get-agent-info-session.test.js10/10 pass (new tests cover: success no-retry, 400 retry, any-code retry, 401 fast-fail, 403 fast-fail, non-StreamableHTTPError no-retry, double-failure with cause chain, debug log, connectOptions forwarding)
  • npx tsc --project tsconfig.lib.json --noEmit — pre-existing TS2688/TS5107 env errors only; no new type errors from these changes
  • Existing MCP unit tests: all previously-passing tests still pass

Nits noted (not fixed)

  • test/unit/mcp-get-agent-info-session.test.js uses a local StreamableHTTPError stub rather than importing from the real SDK. Tests validate the retry logic correctly; a structural mismatch with the SDK class would require an integration test to catch and is out of scope here.
  • mcp_transport wiring in callMCPTool path deferred (noted in JSDoc @remarks).

Pre-PR review

  • code-reviewer: approved — no resource leaks, Awaited<ReturnType> pattern correct, 401/403 guard resolves previous blocker, cause-chaining added, nits noted above
  • ad-tech-protocol-expert: approved — 401/403 guard spec-compliant (retrying session errors is correct per MCP §6.1; auth errors should fast-fail); cause-chaining correctly positioned; 429/503 handling belongs at a higher call site

Triage-managed PR. This bot does not currently iterate on
review comments or PR conversation threads (only on the source
issue). To unblock:

  • Push fixup commits directly: gh pr checkout <num>
    fix → push.
  • Or re-trigger: comment /triage execute on the source
    issue.

See adcp#3121
for context.

Session: https://claude.ai/code/session_017GhLX3ZU8z7dFWarSxp1Nw


Generated by Claude Code

claude added 3 commits May 1, 2026 19:43
FastMCP returns 400 "Missing session ID" when listTools() is called
after connect() if the session wasn't properly established. Add a single
retry-with-fresh-connection in getAgentInfo() matching the session-retry
logic already in withCachedConnection for the standard callMCPTool path.

Also propagates customHeaders and debugLogs in connectOptions, calls
trackStreamableHTTPUrl on connectMCP success, and adds mcp_transport
field to AgentConfig for future caller-side transport hints.

https://claude.ai/code/session_017GhLX3ZU8z7dFWarSxp1Nw
Per pre-PR review: StreamableHTTPError 401/403 should not trigger the
session-reconnect retry (auth failures are not recoverable by reconnecting).
Also chains the original sessionErr as retryErr.cause for diagnostic parity
with withCachedConnection. Adds 3 new tests covering these paths (10 total).

Documents mcp_transport field as not yet active for callMCPTool path.

https://claude.ai/code/session_017GhLX3ZU8z7dFWarSxp1Nw
Pre-PR review noted the asymmetry with is401Error() used elsewhere.
Add comment to prevent a future refactor from accidentally breaking
the 403 case.

https://claude.ai/code/session_017GhLX3ZU8z7dFWarSxp1Nw
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

Source issue #1231 closed as not-a-bug. @bokelley tested FastMCP 3.2.4 in four configurations; vanilla StreamableHTTP, stateless_http=True, and json_response=True all work correctly — @modelcontextprotocol/sdk@1.29 handles session-init transparently. The adcp.bidmachine.io failure was a registration mismatch (host-root URI with no MCP endpoint), not a session-handshake gap.

What that means for this PR:

Flagging so reviewers have context. @bokelley owns the call on whether to close, rebase onto #1233's scope, or drop the retry and ship only the ancillary fixes.


Triaged by Claude Code. Session: https://claude.ai/code/session_014GbxY2Aep3uSVvnkVvLT74


Generated by Claude Code

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

Triage note (issue #1234): While you're in SingleAgentClient.ts, consider folding in the hint-text improvement from #1234 before merge. The fix is a one-liner at the discoverMCPEndpoint throw (~line 735): when all candidates 404 with no 401 anywhere, append:

Hint: this usually means agent_uri does not include the MCP endpoint path. The SDK only probes /, /mcp, and /mcp/ automatically. If your server exposes MCP at a different path (e.g. /api/mcp, /adcp/mcp), register that exact path as agent_uri.

No behavior change. Easy to inline here since you're already in the file — and the two changes are contextually related (both address MCP discovery failure UX). Skipping if this PR is too close to ready or you'd rather ship it separately.


Generated by Claude Code

When all MCP endpoint candidates 404 with no 401, the error now appends
an actionable hint pointing operators to register the exact MCP path
as `agent_uri` rather than the host root.

Also fixes pre-existing Prettier formatting in mcp-get-agent-info-session.test.js.

Refs #1232
Closes #1234

https://claude.ai/code/session_01QbdnKNvfQsVAHw7WQnyeUi
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

Done — folded in the discoverMCPEndpoint hint text from #1234 in commit 73d5f47.

  • Added the path-hint message to the all-404/no-401 throw in SingleAgentClient.ts:735
  • Updated the changeset to include Closes #1234
  • Fixed the pre-existing Prettier formatting issue in mcp-get-agent-info-session.test.js (was blocking ci:quick on the branch)

No behavior change; purely UX improvement on discovery failure.


Generated by Claude Code


Generated by Claude Code

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

Noted — the discoverMCPEndpoint hint from #1234 and the Prettier fix are folded in. Changeset now covers both #1231 and #1234. Ready to promote from draft whenever you're satisfied with the diff.


_Generated by Claude Code


Generated by Claude Code

@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

Diff reviewed — promoting from draft now.

The discoverMCPEndpoint hint, Prettier fix, and changeset update are all correctly incorporated. Retry logic, connectOptions forwarding, trackStreamableHTTPUrl() wiring, and mcp_transport field look good. Tests cover all branches including the 401/403 fast-fail paths.

One nit for reviewers: the mcp_transport @remarks says "Currently only affects the session-retry guard in getAgentInfo()" but the field has zero active wiring anywhere yet — the retry fires on all StreamableHTTPError codes independently. The description is aspirational rather than accurate. Not a blocker; an editorial touch before merge is fine.

Also note for reviewers: Closes #1231 in the PR body references an issue that was closed as not-a-bug — the close keyword will be a no-op on merge since the issue is already closed. The changes are still independently correct (the defensive retry is real for other load-balanced stateful StreamableHTTP servers; the ancillary fixes were always valid).


Generated by Claude Code


Generated by Claude Code

@bokelley bokelley marked this pull request as ready for review May 1, 2026 21:10
The previous wording said "Currently only affects the session-retry
guard in getAgentInfo()" but the field has no active wiring in either
path; the retry fires on all StreamableHTTPError codes regardless.
Replace with accurate forward-compatibility statement.

https://claude.ai/code/session_015Qp6t45J2JZ4ZtGqwGWh7r
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 1, 2026

Thanks for the thorough review, @bokelley.

Applied the editorial fix in 71585b0: the @remarks now reads "Field is declared for forward compatibility; no active wiring yet. Both the callMCPTool and getAgentInfo() paths will respect this hint in a follow-up." — removes the aspirational claim about getAgentInfo() wiring.

The Closes #1231 note is acknowledged; since the issue is already closed the keyword is a no-op on merge, no action needed there.


Generated by Claude Code


Generated by Claude Code

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AdCPClient.getAgentInfo() fails against FastMCP StreamableHTTP agents (no session init)

2 participants