Thread cacheMode through managed list refresh via SDK cache-aware verbs (#1721)#1723
Conversation
…bs (#1721) Manual/auto refresh of tools/resources/prompts now forces a cache-bypassing round trip on modern servers, delivering the raw-request cacheMode control deferred from #1628. The InspectorClient list/read path uses the SDK's single-page `client.request` verbs (for pagination debugging), which never consult the SDK response cache — so `cacheMode` was inert there. This adds a separate aggregate path that routes through the SDK's cache-aware high-level verbs, where `cacheMode` is honored: - InspectorClient: new `listAllTools/Prompts/Resources/ResourceTemplates( { cacheMode, metadata })` that call `client.listTools()` etc. (all-page aggregate + SEP-2243 x-mcp-header exclusion + cache consult/write). A shared `getCacheableRequestOptions` / `aggregateListParams` back all four. - InspectorClientProtocol: the four `listAll*` methods added. - ManagedListState: delegates page walking to the SDK verb (config `fetchPage` loop → `fetchAll`); `refresh(metadata?, cacheMode?)` threads cacheMode. A `list_changed` auto-refresh bypasses the cache (`cacheMode: "refresh"`). - Managed hooks: user-initiated `refresh()` passes `cacheMode: "refresh"`, so the existing per-tab Refresh buttons become genuinely cache-bypassing — no App wiring change. Connect-time load stays default (`'use'`). The single-page `listTools(cursor)` primitive is unchanged; the paged hooks still surface per-page `nextCursor`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
Delivers the deferred single-page fetch UI: Tools/Resources/Prompts can be
loaded one page at a time instead of auto-aggregating every page — a defensive
option for servers with very large lists.
- New `singlePageLists` server setting (persisted, server-wide), plumbed through
ServerConfig/InspectorServerSettings, serverList convert/default, the node +
remote settings paths, and the Server Settings form ("Fetch Lists One Page at
a Time").
- New `ListPaginationControls` element (a "Single page" switch + "Load next
page" button + "N pages loaded" status) rendered in each list sidebar; the
toggle edits the setting live (optimistic) and persists it.
- The paged state stores (PagedTools/Prompts/ResourcesState) now own loading:
they auto-load page 1 on connect in single-page mode and track nextCursor +
page count as observable state; the managed stores skip their all-page walk
in single-page mode (so nothing is over-fetched) while still lighting the
list-changed indicator. `usePaginatedList` selects the display source and
pagination controls per mode — pure, no data-loading in React effects.
- Refresh is mode-aware: reload page 1 in single-page mode, refetch the whole
aggregate otherwise. Sidebars widened 340→360 so the controls don't crowd
list entries.
- Test server: `test-servers/configs/pagination-http.json` (12 tools/resources/
prompts, page size 4 → 3 pages each) exercises the flow end to end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
@claude review |
|
Claude finished @cliffhall's task in 2m 34s —— View job Code review — PR #1723 (cacheMode + single-page pagination)
Nice work — the architecture is clean: the aggregate/cache-aware path ( 🐛 Resource templates never load on connect in single-page mode
// core/mcp/state/managedListState.ts:90-98
const onConnect = (): void => {
if (this.client?.getServerSettings()?.singlePageLists) return;
void this.refresh();
};For tools/prompts/resources that's correct — the paged store drives the display instead. But resource templates have no paged counterpart: Result: connect to a server with Suggested fix: make the connect-time skip apply only to lists that actually have a paged counterpart (e.g. a Minor observations
Tests / coverageTest coverage is solid and broad — paged-state tests assert page-count/cursor tracking and reset-on-disconnect, |
…ery (#1721) - Fix: resource templates never loaded on connect in single-page mode. The connect-time all-page skip now applies only to lists with a paged counterpart (new `deferInSinglePage` flag on ManagedListConfig; false for resource templates), so templates still aggregate on connect regardless of the setting. - Guard `PagedTools/Prompts/ResourcesState.loadPage` against a concurrent load (fast double-click on "Load next page") so the same page can't be appended twice; the in-flight load owns the cursor and the second call is a no-op. - Thread ambient auth recovery through the single-page refresh, Load-next-page, and mode-switch loads (they previously bypassed it) — usePaginatedList's onRefresh/onLoadMore now return their promises so App can wrap them. - Drop the misleading "aggregated list" wording from the paged-store docs. Tests added for each; npm run coverage green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
Thanks for the thorough review — all four points addressed in b111649. 🐛 Resource templates never load on connect in single-page mode — fixedGreat catch. Added a 1.
|
|
@claude review |
|
Claude finished @cliffhall's task in 1m 52s —— View job Re-review — PR #1723 (cacheMode + single-page pagination)
All four points from the previous review are addressed correctly and cleanly, with tests. Verified in the code: ✅ 1. Resource templates now load on connect in single-page mode
✅ 2.
|
…fails (#1721) Non-blocking review follow-up. If updateServerSettings rejects, the optimistic `singlePageListsOverride` is now cleared and the live client setting rolled back (the effect only clears the override when the persisted value changes, which doesn't happen on failure), so the UI and client reflect what's on disk rather than the failed edit. Added an App test for the revert path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
Thanks — both non-blocking observations handled in c23209f. 1. Toggle-persist failure left the optimistic override un-reset — fixed
2. Three parallel
|
|
@claude review |
|
Claude finished @cliffhall's task in 2m 52s —— View job Re-review (round 3) — PR #1723 (cacheMode + single-page pagination)
The round-2/round-3 fixes all landed cleanly and the architecture continues to hold up: the cache-aware aggregate path ( 🐛 Single-page Refresh never clears the list-changed indicatorIn single-page mode, a // core/mcp/state/managedListState.ts:179-186
// … only light the indicator so the user can pull page 1 fresh via Refresh (#1721).
const skipAggregate = this.config.deferInSinglePage && settings?.singlePageLists;
if (!skipAggregate && settings?.autoRefreshOnListChanged) {
await this.refresh(undefined, "refresh");
} else if (this.config.supportsIndicator) {
this.setListChanged(true); // ← tools/prompts/resources all light here in single-page mode
}All three lists have The indicator is meant to be acknowledged by Refresh. In all-pages mode that works: // App.tsx onRefreshTools (single-page branch)
void runWithCommandAuthRecovery(() => toolsPagination.onRefresh(), "ambient");
// usePaginatedList.onRefresh → loadPage(undefined) — reloads page 1 of the PAGED store only
The exact "light the indicator so the user can pull page 1 fresh via Refresh" flow the comment describes is broken — the indicator is stuck on until the user switches back to all-pages mode and refreshes, or reconnects. Affects tools, prompts, and resources. Suggested fix: clear the managed indicator in the single-page Refresh path. E.g. expose Minor observations (non-blocking)
Nice work overall — the |
…sh (#1721) Round-3 review fix (blocking): in single-page mode a `list_changed` lights the list-changed indicator, but the single-page Refresh reloads page 1 of the paged store and bypassed `useManaged*.refresh()`, so the indicator was never cleared. Expose `clearListChanged` from useManagedTools/Prompts/Resources and call it in the single-page branch of onRefreshTools/Prompts/Resources. Tests added. UI polish (user feedback on the sidebar control): - Rename the switch label "Single page" → "Paginated". - Put "Load next page" on the same row as the switch, space-between (switch fully left, button fully right). - Move the "N pages loaded" status to its own line below the row, centered. - Hide the whole control once the list is known to be a single page (single-page mode, page 1 loaded, no nextCursor) — nothing to paginate. pr-screenshots refreshed to the new layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
Thanks — the blocking bug is fixed and both non-blocking notes addressed in 7574b9f. 🐛 Single-page Refresh never clears the list-changed indicator — fixedExactly right, and a real break of the documented acknowledge flow. 1.
|
|
@claude review |
|
Claude finished @cliffhall's task in 2m 9s —— View job Re-review (round 4) — PR #1723 (cacheMode + single-page pagination)
The round-3 blocking fix and the UI polish both landed cleanly. Verified in the code, no blocking issues this pass. ✅ Single-page Refresh now clears the list-changed indicator
✅ UI polish
Spot-checked the rest of the surface again and it continues to hold: the Minor observations (non-blocking)
Nice work — clean fix, well-tested, and the architecture has held up across all four passes. No blocking issues from me. |
|
Thanks for the four passes 🙏 — both remaining observations are intentional, so no code change:
Appreciate the thorough review — no outstanding items on my end. |
`singlePageLists` read backwards from the "Paginated" switch label (it sounds like paginated=false). Rename the server setting to `paginatedLists` with the same value meaning — true = paginated/one-page-at-a-time, false = aggregate-all — and follow through on the related identifiers for consistency: - Setting field `paginatedLists` across ServerConfig / InspectorServerSettings, serverList convert/default, node + remote settings paths, and the form/modal callbacks (onPaginatedListsChange). - `usePaginatedList` / `ListPaginationControls` prop `singlePage` → `paginated`, `onSinglePageChange` → `onPaginatedChange`. - `ManagedListConfig.deferInSinglePage` → `deferWhenPaginated`. - App state (paginatedListsOverride, onTogglePaginatedLists, …) and all tests/fixtures + mode-referring comments. "single-page" wording that denotes the SDK's one-page `client.request` verb (inspectorClient) is intentionally left as-is. Purely a rename — no behavior change; unit + integration + cli + tui + coverage all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
Closes #1721
Split from #1628 (SDK V2 + New Spec — card 5). This delivers the deferred raw-request
cacheModecontrol and the one-page-at-a-time fetch UI.Two capabilities in this PR
1. Real
cacheModeon refresh (the acceptance)InspectorClient's list/read path uses the SDK's single-page
client.requestverbs (deliberate — it surfaces the server'snextCursorfor pagination debugging). The SDK response cache is only consulted/written by its high-level wrappers, so threadingcacheModethroughgetRequestOptions()(as the issue proposed) would have been inert. Instead this adds an aggregate path through the SDK's cache-aware verbs:InspectorClient.listAllTools/Prompts/Resources/ResourceTemplates({ cacheMode, metadata })→client.listTools()etc. (all-page aggregate + SEP-2243 x-mcp-header exclusion + cache consult/write).ManagedListStatedelegates page-walking to those verbs;refresh(metadata?, cacheMode?)threadscacheMode. A user-initiated refresh (and alist_changedauto-refresh) usescacheMode: "refresh", so the existing per-tab Refresh becomes genuinely cache-bypassing on modern servers. Legacy is byte-identical (nothing is cached).2. Page-by-page list fetching — NOT deferred, delivered here
The optional one-page-at-a-time fetch UI (originally deferred) is included, per follow-up direction. The sidebar control is a switch labeled Paginated:
Paginatedon → the list fetches one page at a time; a Load next page button appears and each click appends the next page.Paginatedoff (default) → the list auto-aggregates every page on load (all items at once), exactly as before this PR.(Under the hood the switch drives the
paginatedListsserver setting —true= paginated/one-page-at-a-time,false= aggregate-all.)paginatedListsserver setting (persisted, server-wide) — a defensive default for servers with very large lists. Plumbed throughServerConfig/InspectorServerSettings,serverListconvert/default, the node + remote settings paths, and the Server Settings form ("Fetch Lists One Page at a Time").ListPaginationControlselement in each list sidebar (Tools/Resources/Prompts): the Paginated switch (fully left) and Load next page button (fully right) on one row, with a centered N pages loaded status below. The control hides entirely once a list is known to be a single page (nothing to paginate). The sidebar switch edits the setting live (optimistic) and persists it.Paginatedis on they auto-load page 1 on connect and tracknextCursor+ page count as observable state; the managed (aggregate) stores skip their all-page walk in that mode (nothing is over-fetched) while still lighting the list-changed indicator.usePaginatedListselects the display source per mode — pure, no data-loading in React effects.Paginatedon → reload page 1 and clear the indicator; off → refetch the whole aggregate). Sidebars widened 340→360 so the controls don't crowd list entries.Test server
test-servers/configs/pagination-http.json— 12 tools / resources / prompts,maxPageSize4 → three pages each — exercises the flow end to end (documented in the README).Screenshots
Captured against that test server (also committed under
pr-screenshots/).Paginatedoff (default) — the full aggregated list loads at oncePaginatedon — page 1 only (4 tools), with "Load next page" and a centered "1 page loaded"After "Load next page": next page appended (8 tools), "2 pages loaded"

Server Settings: "Fetch Lists One Page at a Time" — the sidebar

Paginatedswitch persisted itNotes / deferred
readResourcecacheModeis intentionally not included: its InspectorClient path is the manual MRTRinput_requireddriver (History: drive MRTR manually to keep pending-request UX on modern connections #1704), which the SDK's high-levelreadResource()would bypass. Outside this issue's acceptance (list verbs only).Acceptance
nextCursor.npm run cigreen (validate + coverage ≥90/file + smoke + Storybook).🤖 Generated with Claude Code
https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5