Bound JSON-RPC batch fan-out and stop retrying permanent Summarizer failures - #321
Open
kbennett2000 wants to merge 1 commit into
Open
Bound JSON-RPC batch fan-out and stop retrying permanent Summarizer failures#321kbennett2000 wants to merge 1 commit into
kbennett2000 wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bound JSON-RPC batch fan-out and stop retrying permanent Summarizer failures
Closes #277.
Today a single accepted POST to
/mcpcan dispatch an unbounded number of tool calls, and eachbrave_summarizercall can issue up to 20 outbound Brave Search API requests. The two compound: one HTTP request can consume a large share of a monthly quota.This bounds the fan-out at both ends. It does not add a rate limiter — that needs a decision about scope and durability that belongs to the maintainers (see "Deliberately out of scope").
Changes
src/protocols/batch.ts(new) — cap JSON-RPC batch length. Middleware in the same shape ascreateDnsRebindingGuard, applied after the JSON parser and before the MCP SDK. Arrays longer thanHTTP_LIMITS.maxBatchSizeget a 400 with a JSON-RPC error and a null id, per the Streamable HTTP spec. Non-array bodies — the common single-message case — pass through untouched.src/protocols/http.ts— state the body limit explicitly.express.json()was inheriting body-parser's 100kb default. It now declaresHTTP_LIMITS.maxBodySize(64kb) so the bound is a decision rather than a side effect. This is defense in depth; the batch cap is what actually bounds fan-out.src/BraveAPI/index.ts—BraveApiErrorcarries the HTTP status.issueRequestthrew a bareErrorwith the status interpolated into the message, so callers had no way to tell a 401 from a 503 without parsing strings.BraveApiError extends Errorwith the same message, so existingcatchblocks are unaffected.src/tools/summarizer/index.ts— retry only what retrying can fix. The poll loop now stops immediately on deterministic failures (401, 403, 422) and keeps retrying only 429 and 5xx. An invalid key costs one request instead of twenty. Errors with no status (network faults, parse failures) are still treated as transient. The loop also honors theAbortSignalthe MCP SDK already passes to tool handlers, so a cancelled request stops polling.One thing #277 didn't catch
The report describes the loop as polling "up to 20 times at 50ms intervals." That was only true on the error path. The
awaitonpollIntervalsat inside thecatchblock, so a well-formed response that wasn't yetstatus: 'complete're-polled with no delay at all — all remaining attempts fired back to back as fast as the network allowed. The delay now sits between attempts regardless of outcome. There's a regression test for it.Effect
Tests
src/protocols/batch.test.tsandsrc/tools/summarizer/index.test.ts, in the existingnode:teststyle. Includes the 931-message batch from the report, asserted to stay under the body limit so it proves the batch cap fires on its own rather than the parser catching it.64/64 pass;
tsc --noEmitandprettier --checkclean.Deliberately out of scope
checkRateLimit()is still commented out inissueRequest. As written it's process-global with no durable monthly accounting, so restoring it as-is would be misleading rather than protective. A real limiter needs a decision on scope (per key? per session?) and whether state must survive restarts and multiple replicas. Happy to follow up if there's a direction you'd like. This also overlaps Enforce rate limit locally (and support automatic retries) #238.Choosing the constants
Both live in
src/constants.tsasHTTP_LIMITS.maxBatchSize: 10is the value proposed in #277;maxBodySize: '64kb'is well above any legitimate request these tools take while staying under the inherited default. Both are one-line changes if you'd rather be more permissive — the security property holds at any bounded value.