Space out JLCPCB website searches, and report and back off from its refusals (#110) - #116
Merged
Merged
Conversation
…efusals (#110) JLCPCB's website endpoint answered 403 to a quick search during #107's MCP check, and EasyEDA / LCSC dropped out of the results with no visible reason. - EasyEDA / LCSC gets its own token bucket (ProviderSearchOptions.ProviderRateLimits): one request at once, then one every 3 s. Measured with six live requests: six in 10 s, three of them within 1.4 s, were all answered, while #107's refusal came 0.5 s after the previous request, so the threshold is not a simple per-second rate. The bucket stays below what was measured to work: at most two requests within any second and five within any 10 s. Other providers keep the defaults. - A 403 or 429 from the endpoint throws ProviderRateLimitedException, "JLCPCB is rate-limiting; try again shortly". It is still a failure and never cached. - ProviderRateLimiter backs off after a refusal: no request for 30 s, doubled per further refusal up to 2 min, or longer if Retry-After says so (still capped). A request that waited through another's refusal does not go out. - The aggregator's stream now carries each provider's outcome, its parts or ProviderRateLimited, so the refusal reaches the Part Explorer's JLCPCB notice and status line, and the MCP output's "Not answered" line. An MCP call that found nothing while a provider was left out is isError. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DHQsqcKVEiAKvCS98Hn4Yh
Owner
Author
|
Reviewed and merging.
|
This was referenced Sep 21, 2026
Merged
danielmeza
added a commit
that referenced
this pull request
Sep 22, 2026
Brings the branch up to master 8e4ed2e, KiCadSharp 0.4.0 and SExpressions 0.2.0 included. One textual conflict, and four places where master's code needed the Avalonia 12 port applied to it. Views/MainWindow.axaml.cs, the conflict. Master's download handler is kept whole: - refusing never calls Continue, and a refused id is cancelled on its next update (#97, #106); - only a download the handler accepted becomes an import, and only at the path it accepted; - the folder comes from IConfigService, and nothing reads Avalonia state on CEF's thread (#108, #111). This branch had its own answer to the last one, handing the handler the view model; master's replaces it. On top of master's handler go this branch's browser changes: CefGlue's AvaloniaCefBrowser used directly, the handlers set in the constructor instead of through WebViewControl's GlobalWebViewInitialized, WebviewUrl followed through NavigateTo, and SameBrowserPopupHandler. NavigateTo is also how Find on Ultra Librarian (#100) reaches the browser. CefGlue.Common is still 120.6099.211, where OnBeforeDownload returns void, so the override is unchanged. ReactiveUI.Avalonia 11.4.13 -> 12.0.3. The previous master merge kept 11.4.13, but the CefGlue.Avalonia built from OutSystems/CefGlue#249 depends on ReactiveUI.Avalonia >= 12.0.3, and restore failed with NU1605. That was the red CI build on the last push. 12.0.3 is still ReactiveUI 23.2.28, the ReactiveUI master has, so #116's search pipeline builds unchanged here. ReactiveUI 24 is the next commit. Settings (#96, #103, #115): the text boxes master added use Watermark, which Avalonia 12 makes obsolete; they use PlaceholderText, like the others. The #if DEBUG `using Avalonia;` that #90 added in AboutWindow and SettingsWindow goes with the AttachDevTools calls this branch had already removed. Avalonia 12's developer tools attach once, in App. Program.cs (#95, #104) is merged as master has it: HarfBuzzPreload, SetLogDirectory and the MCP logging allowlist are unchanged. Only the UseReactiveUI comment changes, because the CefGlue build no longer brings in the old Avalonia.ReactiveUI. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DHQsqcKVEiAKvCS98Hn4Yh
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.
Fixes #110
EasyEDA / LCSC searches of JLCPCB's website endpoint now go through a slower rate-limit bucket of their own. A 403 or 429 from the endpoint is now reported as "JLCPCB is rate-limiting; try again shortly" in the Part Explorer and in the MCP output, instead of the provider silently dropping out. After a refusal, the app backs off for a short, bounded time. It is still a failure, never "no results", and it is never cached.
What I measured
I sent seven live requests in total: six raw probes, plus one search through the real app at the end.
The probe used the app's exact request (POST, JSON body, the honest
kicad-ultra/1.0User-Agent):Akamai-GRN,Akamai-Cache-StatusandServer-Timing: ak_pheaders).Retry-Afterand noX-RateLimit-*.What #107's log shows. Its 403 came to a request sent 0.5 s after the previous one:
NE555at 16:24:36.307, thenNE555DRat 16:24:36.812. Five earlier runs that day sent the same pattern (14:39 to 15:03), and all of them were answered.So the threshold is not a simple per-second rate, and I could not reproduce the refusal. It may depend on longer-window volume from this IP, since several agents searched from this machine that day, or on how Akamai classifies the client. I did not try to provoke it: that takes the kind of volume this unofficial endpoint must not be sent, and a block would also hit the user's own browsing of jlcpcb.com from the same IP.
The numbers follow from that:
Changes
ProviderSearchOptions.ProviderRateLimitsgives EasyEDA / LCSC its ownProviderRateLimit(BurstLimit: 1, ReplenishmentPeriod: 3 s, QueueLimit: 4).ProviderRateLimiterbuilds each partition fromGetRateLimit(providerId).JlcpcbWebsiteSearchClientthrows the newProviderRateLimitedExceptionfor 403 or 429. Its message is the user-facing sentence, and it carries the status and anyRetry-After.Retry-Afteris read as a delay, or as a date measured against the response's ownDateheader, so the local clock does not matter.EasyEdaProviderlogs the status and rethrows, following the narrow-catch rule.ProviderRateLimiter:BackOff,GetBackoffandAnswered.Retry-Afteris honoured, up to the same cap.IPartAggregatorService.StreamAllProvidersAsyncnow yields aProviderSearchOutcomeper provider:ProviderResults(Parts)orProviderRateLimited(ProviderId, ProviderName, Reason, RetryAt).SearchAllProvidersAsyncreturnsAggregatedSearchResult(Parts, RateLimited).ProviderLeftOutupdate in the same ordered stream as the results, soSwitch()still drops a superseded search's updates;- EasyEDA / LCSC: JLCPCB is rate-limiting; try again shortly. This server will not ask it again for 30 s; search again after that.;search_componentsorget_component_detailscall that found nothing while a provider was left out returnsisError: true.Decisions to review
isError: trueonly when nothing was found. With some results and a provider left out, the call succeeds, and the "Not answered" line says what is missing.--mcpserver each keep their own bucket and back-off, as they already did for the cache.Verification
Build gates, on the branch rebased onto
14e51ff:dotnet build -c Releaseand-c Debug: 0 warnings, 0 errors;dotnet format --severity warn --verify-no-changes: clean;SampleConsole --test-parser: exit 0.Offline harness. A scratch console references the UI project, and nothing of it is committed. It installed a stub as the handler of
EasyEdaProvider's own staticHttpClient, so the provider, the website client and the whole pipeline ran unchanged. Every otherHttpClientwent to the discard port. All 29 checks pass:Program.BuildMcpServiceProvider's container with the real options, for seven distinct MCP-style searches back to back:ProviderRateLimitedwith the exact reason and a 30 sRetryAt;Retry-After: 90gives 90 s, andRetry-After: 3600is capped at 2 min;Retry-Afterdate is measured against the response'sDateheader;McpServer's own tool handler:search_componentsandget_component_detailsboth returnisError=truewith the "Not answered" line and "for 30 s". A cached search has no such line.The real binaries, with the endpoint stubbed. A
DOTNET_STARTUP_HOOKSassembly swaps in the stub beforeMainruns, and nothing reaches JLCPCB. Each run used a tempHOMEand XDG dirs, and a dead D-Bus.--mcpover stdio: I pipedinitialize,notifications/initialized, thenNE555(200),LM358(403) andget_component_details TL072.NE555at 17:01:09.844 andLM358at 17:01:12.908, 3.06 s apart.TL072sent no request, because of the back-off.isError: truewith the line above.:110), driven with XTest:ne555showed the stub row;lm358got a 403. The notice switched to "JLCPCB is rate-limiting; try again shortly", with "The search for 'lm358' has no EasyEDA / LCSC results because JLCPCB turned a request down as too frequent, not because nothing matched. The app leaves JLCPCB alone until 4:57:03 PM…". The status line read "Found 0 results across providers for 'lm358'. Left out: EasyEDA / LCSC (JLCPCB is rate-limiting; try again shortly).";tl072, during the back-off, showed the same notice for 'tl072', with no request;ne555came from the cache and restored the normal notice;Live, once: the real
--mcpbinary, onesearch_components NE555against the real endpoint.C695838 NE555DR UMW 194291 0.0616 USDandC7593 NE555DR Texas Instruments 164286 0.1205 USD.What was not verified
Overlap
14e51ff(docs: CLAUDE.md after the second agent round (#92–#111) #113 docs, Octopart: keep the results when Nexar refuses a field to the token's plan (#109) #114 Octopart) without conflicts. Octopart: keep the results when Nexar refuses a field to the token's plan (#109) #114 touches onlyOctopartProvider.MainWindow.axaml: one tooltip binding and a comment on the JLCPCB notice.MainWindow.axaml.csis untouched (Browser downloads ignore the download folder set in Settings #108).🤖 Generated with Claude Code
https://claude.ai/code/session_01DHQsqcKVEiAKvCS98Hn4Yh