Skip to content

fix(client): report rate limiting during MCP endpoint discovery - #3443

Merged
bokelley merged 1 commit into
mainfrom
fix/mcp-discovery-rate-limit
Oct 25, 2026
Merged

bokelley merged 1 commit into
mainfrom
fix/mcp-discovery-rate-limit

Conversation

@bokelley

Copy link
Copy Markdown
Contributor

Closes #3442

Problem

When every MCP discovery probe got HTTP 429, discoverMCPEndpointWithConnectionPolicy kept probing the other candidate URLs, then threw Failed to discover MCP endpoint … None responded to MCP protocol with the #1234 hint that agent_uri lacks the MCP path. The path was fine; the seller was rate-limiting the caller.

Change

  • Discovery loop (SingleAgentClient.ts): a probe returning 429, or 503 with Retry-After, stops discovery at that URL and throws AgentRateLimitedError. No further candidates are probed and the wrong-path hint is not added. The 401 path, the generic failure (including plain 503 without Retry-After), and success paths are unchanged.
  • AgentRateLimitedError (src/lib/errors, exported from @adcp/sdk): code: 'RATE_LIMITED' (the AdCP transient code), with status, agentUrl (credentials, query, and fragment stripped), retryAfter and retryAfterMs. src/lib/errors had no client-side rate-limit class. RateLimitedError in server/decisioning is the server-side throw helper and does not fit here. retryAfter is kept and echoed only when it is valid delta-seconds or an IMF-fixdate, so seller-controlled header text does not reach the message.
  • Retry-After plumbing (mcp-modern.ts): the MCP SDK's SdkHttpError carries the status but not the response headers. createNegotiatedClient records the last 429/503 response's Retry-After in its transport fetch and attaches it as a non-enumerable retryAfter on the connect error it throws. Nothing else in the fetch chain changes.

Scope: the A2A agent-card loop (fetchA2ACanonicalUrl) is a different shape. It fetches cards through the A2A client and surfaces that client's error, not a wrong-path hint, so it is unchanged here.

Not addressed: the issue's secondary note about per-client session volume in SDK 17 (two initialize sessions per new client and call) is separate and left for follow-up.

Tests

test/lib/discover-mcp-endpoint-rate-limit.test.js (real HTTP stub server):

  • 429 + Retry-After: 30: AgentRateLimitedError with status 429, URL, retryAfter 30 and retryAfterMs 30000. The message names the rate limit and HTTP 429 and has no "None responded" or wrong-path hint. Only the first candidate path was requested.
  • 429 without Retry-After: still a rate-limit error, and probing still stops.
  • 503 + Retry-After: 5: rate-limit error, and probing stops.
  • Control: a server that returns 404 everywhere still gets the existing "None responded" error with the hint, and the trailing-slash candidate is still probed.
  • Constructor: a malformed Retry-After is not echoed, URL credentials and query are stripped, and an HTTP-date is parsed.

Ran npm run build:lib, npm run typecheck, npm run lint, npm run lint:boundaries, prettier --check, npm run ci:docs-check, and TEST_CONCURRENCY=2 npm run test:file on the new test plus 16 related discovery/auth/MCP-negotiation suites (228 tests, all passing). The pre-push hook passed normally; --no-verify was not used.

🤖 Generated with Claude Code

When every MCP discovery probe got HTTP 429, discovery reported "None
responded to MCP protocol" with the wrong-path hint. Stop at the first 429
(or 503 with Retry-After) and throw AgentRateLimitedError with the status,
URL and Retry-After instead of probing the remaining candidates.

Closes #3442

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ladon verdict: Approve

Approve — clean fix for adcp-client#3442 (MCP endpoint discovery now fails closed on 429/503 with AgentRateLimitedError).

No blocking or medium findings. Both connect paths route through createNegotiatedClient so Retry-After plumbing is consistent; probeModernMCPConnection re-throws 429/503 while preserving the v1 fallback only for status-less failures; tests cover 429±Retry-After, 503+Retry-After, 404 control, and constructor sanitization (credentials/query stripped, seller header text never reaching the message). No new unbounded I/O; existing timeout/abort preserved. New code: 'RATE_LIMITED' aligns with existing transient retry policy.

high_risk is true (src/lib/protocols/mcp-modern.ts modified matching src/lib/protocols/**), but row 5 does not fire — no medium findings. No deletions. gated_paths is false.

Medium findings
None.

Non-blocking nit:

  • .changeset/mcp-discovery-rate-limit.md:2 — Changeset typed patch for a new public export (additive, semver-minor); not a wire-break, so low severity only.

Falls through to row 9.

Copy link
Copy Markdown
Contributor Author

On the Ladon nit (patch for the new AgentRateLimitedError export): keeping patch on purpose. The maintainer scoped this as an @adcp/sdk patch, the export is additive, and the repo is in changesets pre mode (beta tag), so either type ships as the next 17.0.0-beta.N. The change does not touch the wire.

@bokelley
bokelley merged commit b27fd8d into main Oct 25, 2026
45 checks passed
@bokelley
bokelley deleted the fix/mcp-discovery-rate-limit branch October 25, 2026 22:38
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.

MCP endpoint discovery reports HTTP 429 as 'None responded to MCP protocol' with a wrong-path hint

1 participant