From b3b04c6939d084599b390555473cedb82f7fecd5 Mon Sep 17 00:00:00 2001 From: Cliff Hall Date: Sun, 19 Jul 2026 01:06:31 -0400 Subject: [PATCH] Thread cacheMode through managed list refresh via SDK cache-aware verbs (#1721) (#1723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Thread cacheMode through managed list refresh via SDK cache-aware verbs (#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) Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Single-page list pagination: sidebar toggle + server setting (#1721) 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) Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Add pr-screenshots proving single-page pagination (#1721) Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Address @claude review: template load, double-click guard, auth recovery (#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) Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Review round 2: revert optimistic toggle when persisting the setting 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) Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Fix single-page Refresh not clearing list-changed indicator + UI polish (#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) Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Rename the singlePageLists setting to paginatedLists for clarity (#1721) `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) Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * Format App.test.tsx (prettier) after the paginatedLists rename (#1721) Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) --- README.md | 2 + clients/web/src/App.test.tsx | 197 +++++++++++- clients/web/src/App.tsx | 304 +++++++++++++++++- .../ListPaginationControls.stories.tsx | 47 +++ .../ListPaginationControls.test.tsx | 125 +++++++ .../ListPaginationControls.tsx | 86 +++++ .../PromptControls/PromptControls.stories.tsx | 2 + .../PromptControls/PromptControls.test.tsx | 2 + .../groups/PromptControls/PromptControls.tsx | 8 + .../ResourceControls.stories.tsx | 2 + .../ResourceControls.test.tsx | 2 + .../ResourceControls/ResourceControls.tsx | 8 + .../ServerSettingsForm.stories.tsx | 7 + .../ServerSettingsForm.test.tsx | 1 + .../ServerSettingsForm/ServerSettingsForm.tsx | 8 + .../ServerSettingsModal.tsx | 5 + .../ToolControls/ToolControls.stories.tsx | 2 + .../groups/ToolControls/ToolControls.test.tsx | 2 + .../groups/ToolControls/ToolControls.tsx | 8 + .../PromptsScreen/PromptsScreen.stories.tsx | 2 + .../PromptsScreen/PromptsScreen.test.tsx | 2 + .../screens/PromptsScreen/PromptsScreen.tsx | 9 +- .../ResourcesScreen.stories.tsx | 2 + .../ResourcesScreen/ResourcesScreen.test.tsx | 2 + .../ResourcesScreen/ResourcesScreen.tsx | 9 +- .../ToolsScreen/ToolsScreen.stories.tsx | 2 + .../screens/ToolsScreen/ToolsScreen.test.tsx | 2 + .../screens/ToolsScreen/ToolsScreen.tsx | 9 +- .../InspectorView/InspectorView.stories.tsx | 4 + .../InspectorView/InspectorView.test.tsx | 4 + .../views/InspectorView/InspectorView.tsx | 13 + .../web/src/hooks/usePaginatedList.test.tsx | 98 ++++++ clients/web/src/hooks/usePaginatedList.ts | 91 ++++++ .../web/src/test/core/mcp/serverList.test.ts | 35 ++ .../mcp/state/managedPromptsState.test.ts | 59 ++-- .../managedResourceTemplatesState.test.ts | 76 +++-- .../mcp/state/managedResourcesState.test.ts | 59 ++-- .../core/mcp/state/managedToolsState.test.ts | 133 ++++++-- .../core/mcp/state/pagedPromptsState.test.ts | 90 ++++++ .../mcp/state/pagedResourcesState.test.ts | 93 ++++++ .../core/mcp/state/pagedToolsState.test.ts | 118 +++++++ .../core/react/useManagedPrompts.test.tsx | 18 ++ .../core/react/useManagedResources.test.tsx | 18 ++ .../test/core/react/useManagedTools.test.tsx | 20 ++ .../test/core/react/usePagedPrompts.test.tsx | 14 + .../core/react/usePagedResources.test.tsx | 17 + .../test/core/react/usePagedTools.test.tsx | 22 ++ .../src/test/core/react/useServers.test.tsx | 2 + clients/web/src/test/fixtures/pagination.ts | 14 + .../integration/mcp/inspectorClient.test.ts | 70 ++++ .../mcp/remote/server-extra-coverage.test.ts | 9 + core/mcp/__tests__/fakeInspectorClient.ts | 53 +++ core/mcp/inspectorClient.ts | 130 ++++++++ core/mcp/inspectorClientProtocol.ts | 24 ++ core/mcp/node/servers.ts | 1 + core/mcp/remote/node/server.ts | 12 + core/mcp/serverList.ts | 9 + core/mcp/state/fetchRequestLogState.ts | 4 +- core/mcp/state/managedListState.ts | 114 ++++--- core/mcp/state/managedPromptsState.ts | 8 +- .../state/managedResourceTemplatesState.ts | 16 +- core/mcp/state/managedResourcesState.ts | 8 +- core/mcp/state/managedToolsState.ts | 8 +- core/mcp/state/messageLogState.ts | 4 +- core/mcp/state/pagedPromptsState.ts | 67 +++- core/mcp/state/pagedResourcesState.ts | 72 ++++- core/mcp/state/pagedToolsState.ts | 85 ++++- core/mcp/types.ts | 14 + core/react/useManagedPrompts.ts | 13 +- core/react/useManagedResourceTemplates.ts | 8 +- core/react/useManagedResources.ts | 13 +- core/react/useManagedTools.ts | 18 +- core/react/usePagedPrompts.ts | 35 +- core/react/usePagedResources.ts | 38 ++- core/react/usePagedTools.ts | 36 ++- pr-screenshots/01-tools-all-pages-default.png | Bin 0 -> 42545 bytes pr-screenshots/02-tools-single-page-first.png | Bin 0 -> 40812 bytes .../03-tools-single-page-load-more.png | Bin 0 -> 44709 bytes pr-screenshots/04-server-setting-checkbox.png | Bin 0 -> 150875 bytes pr-screenshots/README.md | 11 + test-servers/configs/pagination-http.json | 18 ++ 81 files changed, 2500 insertions(+), 253 deletions(-) create mode 100644 clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.stories.tsx create mode 100644 clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.test.tsx create mode 100644 clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.tsx create mode 100644 clients/web/src/hooks/usePaginatedList.test.tsx create mode 100644 clients/web/src/hooks/usePaginatedList.ts create mode 100644 clients/web/src/test/fixtures/pagination.ts create mode 100644 pr-screenshots/01-tools-all-pages-default.png create mode 100644 pr-screenshots/02-tools-single-page-first.png create mode 100644 pr-screenshots/03-tools-single-page-load-more.png create mode 100644 pr-screenshots/04-server-setting-checkbox.png create mode 100644 pr-screenshots/README.md create mode 100644 test-servers/configs/pagination-http.json diff --git a/README.md b/README.md index 6239b18a..845f4d7d 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,8 @@ A streamable-HTTP server can also serve the **modern (2026-07-28) protocol era** `test-servers/configs/modern-network-http.json` is the **Network-tab showcase** for the standardized HTTP headers and new error taxonomy (SEP-2243 / SEP-2575). It serves a `get_weather` tool whose `city` argument carries an `x-mcp-header: "City"` annotation (so a modern client mirrors it to `Mcp-Param-City`), plus four `trigger_*` tools that the modern leg's spec-error injector (`transport.modern.injectSpecErrors: true`) answers with a real HTTP status + JSON-RPC error body: `trigger_header_mismatch` → `400 / -32020`, `trigger_missing_capability` → `400 / -32021`, `trigger_unsupported_version` → `400 / -32022` (with `data.supported`), `trigger_method_not_found` → `404 / -32601`. Connect to it with **Protocol Era = Modern** and open the Network tab to see the mirrored `Mcp-*` headers highlighted, sentinel values decoded, and each error rendered distinctly. Note: `Mcp-Param-*` mirroring is **skipped by the SDK in the browser** (`detectProbeEnvironment() !== "browser"`), so calling `get_weather` from the **web** client omits `Mcp-Param-City` and the strict server answers `-32020` — the same tool is callable from the Node CLI/TUI, where mirroring is active. +`test-servers/configs/pagination-http.json` is the **page-by-page fetch showcase** (#1721). It serves 12 tools, 12 resources, and 12 prompts (presets `numbered_tools` / `numbered_resources` / `numbered_prompts`, `count: 12`) with `maxPageSize` of 4 for each, so every list paginates into three pages. Turn on **"Fetch Lists One Page at a Time"** (Server Settings — the `paginatedLists` setting, or the **Paginated** switch in a list sidebar) and the Tools/Resources/Prompts lists load page 1 only (4 items) with a **Load next page** control and an *N pages loaded* status; each click fetches the next 4 and appends them, and Refresh resets to page 1. With the switch off (the default), the same lists auto-aggregate all three pages on connect. + ## Building ```bash diff --git a/clients/web/src/App.test.tsx b/clients/web/src/App.test.tsx index b44a34de..fc7fc9b1 100644 --- a/clients/web/src/App.test.tsx +++ b/clients/web/src/App.test.tsx @@ -187,12 +187,23 @@ const SERVER_A = { connection: { status: "disconnected" }, }; +// Stable spy so tests can assert the sidebar paginated toggle persisted the +// `paginatedLists` setting (#1721). `vi.hoisted` so it exists when the hoisted +// `vi.mock` factory closes over it. +const { updateServerSettingsSpy } = vi.hoisted(() => ({ + updateServerSettingsSpy: vi.fn(() => Promise.resolve()), +})); +// Stable spy for the tools list-changed acknowledgement, so a test can assert +// the paginated Refresh clears the indicator (#1721). +const { clearToolsListChangedSpy } = vi.hoisted(() => ({ + clearToolsListChangedSpy: vi.fn(), +})); vi.mock("@inspector/core/react/useServers.js", () => ({ useServers: vi.fn(() => ({ servers: [SERVER_A], addServer: vi.fn(), updateServer: vi.fn(), - updateServerSettings: vi.fn(), + updateServerSettings: updateServerSettingsSpy, removeServer: vi.fn(), })), })); @@ -211,13 +222,22 @@ vi.mock("@inspector/core/react/useManagedTools.js", () => ({ useManagedTools: vi.fn(() => ({ tools: [{ name: "get_acts", inputSchema: { type: "object" } }], refresh: vi.fn(), + clearListChanged: clearToolsListChangedSpy, })), })); vi.mock("@inspector/core/react/useManagedPrompts.js", () => ({ - useManagedPrompts: vi.fn(() => ({ prompts: [], refresh: vi.fn() })), + useManagedPrompts: vi.fn(() => ({ + prompts: [], + refresh: vi.fn(), + clearListChanged: vi.fn(), + })), })); vi.mock("@inspector/core/react/useManagedResources.js", () => ({ - useManagedResources: vi.fn(() => ({ resources: [], refresh: vi.fn() })), + useManagedResources: vi.fn(() => ({ + resources: [], + refresh: vi.fn(), + clearListChanged: vi.fn(), + })), })); vi.mock("@inspector/core/react/useManagedResourceTemplates.js", () => ({ useManagedResourceTemplates: vi.fn(() => ({ @@ -225,6 +245,58 @@ vi.mock("@inspector/core/react/useManagedResourceTemplates.js", () => ({ refresh: vi.fn(), })), })); +// Paged (paginated) hooks + state managers (#1721). Mirrors the managed +// mocks: the hooks return an empty accumulated list and a resolving loadPage so +// usePaginatedList runs without a real transport; the state classes are no-op +// constructors App still instantiates/destroys per connect. +vi.mock("@inspector/core/react/usePagedTools.js", () => ({ + usePagedTools: vi.fn(() => ({ + tools: [], + nextCursor: undefined, + pageCount: 0, + loadPage: vi.fn(() => + Promise.resolve({ tools: [], nextCursor: undefined }), + ), + clear: vi.fn(), + })), +})); +vi.mock("@inspector/core/react/usePagedPrompts.js", () => ({ + usePagedPrompts: vi.fn(() => ({ + prompts: [], + nextCursor: undefined, + pageCount: 0, + loadPage: vi.fn(() => + Promise.resolve({ prompts: [], nextCursor: undefined }), + ), + clear: vi.fn(), + })), +})); +vi.mock("@inspector/core/react/usePagedResources.js", () => ({ + usePagedResources: vi.fn(() => ({ + resources: [], + nextCursor: undefined, + pageCount: 0, + loadPage: vi.fn(() => + Promise.resolve({ resources: [], nextCursor: undefined }), + ), + clear: vi.fn(), + })), +})); +vi.mock("@inspector/core/mcp/state/pagedToolsState.js", () => ({ + PagedToolsState: vi.fn(function () { + return { destroy: vi.fn() }; + }), +})); +vi.mock("@inspector/core/mcp/state/pagedPromptsState.js", () => ({ + PagedPromptsState: vi.fn(function () { + return { destroy: vi.fn() }; + }), +})); +vi.mock("@inspector/core/mcp/state/pagedResourcesState.js", () => ({ + PagedResourcesState: vi.fn(function () { + return { destroy: vi.fn() }; + }), +})); vi.mock("@inspector/core/react/useManagedRequestorTasks.js", () => ({ useManagedRequestorTasks: vi.fn(() => ({ tasks: [], @@ -313,6 +385,14 @@ vi.mock("./components/views/InspectorView/InspectorView", () => ({ onReplayProtocol: (id: string) => void; onTogglePinProtocol: (id: string) => void; pinnedProtocolIds?: Set; + onRefreshTools: () => void; + toolsPagination: { + paginated: boolean; + canLoadMore: boolean; + loadedPages: number; + onPaginatedChange: (v: boolean) => void; + onLoadMore: () => void; + }; }) => (
@@ -428,6 +508,22 @@ vi.mock("./components/views/InspectorView/InspectorView", () => ({ replay-history + + {String(props.toolsPagination.paginated)} + + + {props.toolsPagination.loadedPages} + + + + +
), })); @@ -1889,5 +1985,100 @@ describe("App OAuth resume lifecycle", () => { INSPECTOR_SERVERS_TAB, ), ); + + await user.click(screen.getByText("connect")); + await waitFor(() => + expect(screen.getByTestId("active-tab")).toHaveTextContent( + INSPECTOR_SERVERS_TAB, + ), + ); + }); +}); + +describe("App paginated list pagination toggle (#1721)", () => { + beforeEach(() => { + clientInstances.length = 0; + updateServerSettingsSpy.mockClear(); + }); + + it("persists and live-pushes paginatedLists when the sidebar toggle flips", async () => { + const user = userEvent.setup(); + renderWithMantine(); + await user.click(screen.getByText("connect")); + await waitFor(() => expect(clientInstances).toHaveLength(1)); + + expect(screen.getByTestId("tools-paginated")).toHaveTextContent("false"); + + await user.click(screen.getByText("paginated-on")); + + // Optimistic UI flip is immediate. + await waitFor(() => + expect(screen.getByTestId("tools-paginated")).toHaveTextContent("true"), + ); + // Persisted to the server settings (survives reconnects). + expect(updateServerSettingsSpy).toHaveBeenCalledWith( + "A", + expect.objectContaining({ paginatedLists: true }), + ); + // Live-pushed to the client so the managed state's gating reads it now. + const client = clientInstances[0] as unknown as { + setServerSettings: ReturnType; + }; + expect(client.setServerSettings).toHaveBeenCalledWith( + expect.objectContaining({ paginatedLists: true }), + ); + + // Toggling back off persists false. + await user.click(screen.getByText("paginated-off")); + await waitFor(() => + expect(updateServerSettingsSpy).toHaveBeenCalledWith( + "A", + expect.objectContaining({ paginatedLists: false }), + ), + ); + }); + + it("routes Refresh and Load-next-page in paginated mode and clears the indicator", async () => { + const user = userEvent.setup(); + clearToolsListChangedSpy.mockClear(); + renderWithMantine(); + await user.click(screen.getByText("connect")); + await waitFor(() => expect(clientInstances).toHaveLength(1)); + + await user.click(screen.getByText("paginated-on")); + await waitFor(() => + expect(screen.getByTestId("tools-paginated")).toHaveTextContent("true"), + ); + // Exercise the mode-aware Refresh (paginated → reload page 1) and the + // Load-next-page control; both should run without error. + await user.click(screen.getByText("refresh-tools")); + await user.click(screen.getByText("load-more-tools")); + expect(screen.getByTestId("tools-paginated")).toHaveTextContent("true"); + // The paginated Refresh must acknowledge the managed list-changed + // indicator (the paged reload bypasses the managed hook's refresh) (#1721). + expect(clearToolsListChangedSpy).toHaveBeenCalled(); + }); + + it("reverts the optimistic toggle when persisting the setting fails (#1721)", async () => { + const user = userEvent.setup(); + updateServerSettingsSpy.mockRejectedValueOnce(new Error("disk full")); + renderWithMantine(); + await user.click(screen.getByText("connect")); + await waitFor(() => expect(clientInstances).toHaveLength(1)); + + await user.click(screen.getByText("paginated-on")); + // The optimistic flip is rolled back once the persist rejects, so the UI + // reflects the (unchanged) persisted value rather than the failed edit. + await waitFor(() => + expect(screen.getByTestId("tools-paginated")).toHaveTextContent("false"), + ); + // The live client setting was rolled back too (last call reverts it). + const client = clientInstances[0] as unknown as { + setServerSettings: ReturnType; + }; + const lastPush = client.setServerSettings.mock.calls.at(-1)?.[0] as { + paginatedLists?: boolean; + }; + expect(lastPush?.paginatedLists).toBeFalsy(); }); }); diff --git a/clients/web/src/App.tsx b/clients/web/src/App.tsx index 0a11e7d0..d55945a8 100644 --- a/clients/web/src/App.tsx +++ b/clients/web/src/App.tsx @@ -52,6 +52,9 @@ import { import { ManagedToolsState } from "@inspector/core/mcp/state/managedToolsState.js"; import { ManagedPromptsState } from "@inspector/core/mcp/state/managedPromptsState.js"; import { ManagedResourcesState } from "@inspector/core/mcp/state/managedResourcesState.js"; +import { PagedToolsState } from "@inspector/core/mcp/state/pagedToolsState.js"; +import { PagedPromptsState } from "@inspector/core/mcp/state/pagedPromptsState.js"; +import { PagedResourcesState } from "@inspector/core/mcp/state/pagedResourcesState.js"; import { ManagedResourceTemplatesState } from "@inspector/core/mcp/state/managedResourceTemplatesState.js"; import { ManagedRequestorTasksState } from "@inspector/core/mcp/state/managedRequestorTasksState.js"; import { ResourceSubscriptionsState } from "@inspector/core/mcp/state/resourceSubscriptionsState.js"; @@ -91,6 +94,11 @@ import { getWebRemoteOAuthStorage } from "./lib/remoteOAuthStorage"; import { useManagedTools } from "@inspector/core/react/useManagedTools.js"; import { useManagedPrompts } from "@inspector/core/react/useManagedPrompts.js"; import { useManagedResources } from "@inspector/core/react/useManagedResources.js"; +import { usePagedTools } from "@inspector/core/react/usePagedTools.js"; +import { usePagedPrompts } from "@inspector/core/react/usePagedPrompts.js"; +import { usePagedResources } from "@inspector/core/react/usePagedResources.js"; +import { usePaginatedList } from "./hooks/usePaginatedList"; +import type { ListPaginationControlsProps } from "./components/elements/ListPaginationControls/ListPaginationControls"; import { useManagedResourceTemplates } from "@inspector/core/react/useManagedResourceTemplates.js"; import { useManagedRequestorTasks } from "@inspector/core/react/useManagedRequestorTasks.js"; import { useResourceSubscriptions } from "@inspector/core/react/useResourceSubscriptions.js"; @@ -361,6 +369,7 @@ const EMPTY_SETTINGS: InspectorServerSettings = { requestTimeout: 0, taskTtl: DEFAULT_TASK_TTL_MS, autoRefreshOnListChanged: false, + paginatedLists: false, maxFetchRequests: DEFAULT_MAX_FETCH_REQUESTS, roots: [], }; @@ -745,6 +754,14 @@ function App() { useState(null); const [managedResourcesState, setManagedResourcesState] = useState(null); + // Paged (paginated) counterparts, used when the `paginatedLists` setting + // is on (#1721). Created/destroyed alongside the managed states. + const [pagedToolsState, setPagedToolsState] = + useState(null); + const [pagedPromptsState, setPagedPromptsState] = + useState(null); + const [pagedResourcesState, setPagedResourcesState] = + useState(null); const [managedResourceTemplatesState, setManagedResourceTemplatesState] = useState(null); const [managedRequestorTasksState, setManagedRequestorTasksState] = @@ -912,22 +929,93 @@ function App() { lastError, } = useInspectorClient(inspectorClient); const { - tools, + tools: managedTools, listChanged: toolsListChanged, refresh: refreshTools, + clearListChanged: clearToolsListChanged, } = useManagedTools(inspectorClient, managedToolsState); const { - prompts, + prompts: managedPrompts, listChanged: promptsListChanged, refresh: refreshPrompts, + clearListChanged: clearPromptsListChanged, } = useManagedPrompts(inspectorClient, managedPromptsState); const { - resources, + resources: managedResources, listChanged: resourcesListChanged, refresh: refreshResources, + clearListChanged: clearResourcesListChanged, } = useManagedResources(inspectorClient, managedResourcesState); const { resourceTemplates, refresh: refreshResourceTemplates } = useManagedResourceTemplates(inspectorClient, managedResourceTemplatesState); + // Paged (paginated) list sources. When `paginatedLists` is on the managed + // states skip their all-page walk and these drive the sidebar instead (#1721). + const { + tools: pagedTools, + nextCursor: pagedToolsCursor, + pageCount: pagedToolsPageCount, + loadPage: loadToolsPage, + } = usePagedTools(inspectorClient, pagedToolsState); + const { + prompts: pagedPrompts, + nextCursor: pagedPromptsCursor, + pageCount: pagedPromptsPageCount, + loadPage: loadPromptsPage, + } = usePagedPrompts(inspectorClient, pagedPromptsState); + const { + resources: pagedResources, + nextCursor: pagedResourcesCursor, + pageCount: pagedResourcesPageCount, + loadPage: loadResourcesPage, + } = usePagedResources(inspectorClient, pagedResourcesState); + // The active server's persisted paginated setting drives the display mode. + // The sidebar toggle edits it (optimistically, below) and persists it. + const persistedPaginatedLists = + servers.find((s) => s.id === activeServerId)?.settings?.paginatedLists ?? + false; + const [paginatedListsOverride, setPaginatedListsOverride] = useState< + boolean | null + >(null); + // Drop the optimistic override once the persisted value catches up (or the + // active server changes), so the persisted setting is the resting source. + useEffect(() => { + setPaginatedListsOverride(null); + }, [persistedPaginatedLists, activeServerId]); + const paginatedLists = paginatedListsOverride ?? persistedPaginatedLists; + const connected = connectionStatus === "connected"; + const toolsPagination = usePaginatedList({ + connected, + paginated: paginatedLists, + managedItems: managedTools, + managedRefresh: refreshTools, + pagedItems: pagedTools, + pagedNextCursor: pagedToolsCursor, + pagedPageCount: pagedToolsPageCount, + loadPage: loadToolsPage, + }); + const promptsPagination = usePaginatedList({ + connected, + paginated: paginatedLists, + managedItems: managedPrompts, + managedRefresh: refreshPrompts, + pagedItems: pagedPrompts, + pagedNextCursor: pagedPromptsCursor, + pagedPageCount: pagedPromptsPageCount, + loadPage: loadPromptsPage, + }); + const resourcesPagination = usePaginatedList({ + connected, + paginated: paginatedLists, + managedItems: managedResources, + managedRefresh: refreshResources, + pagedItems: pagedResources, + pagedNextCursor: pagedResourcesCursor, + pagedPageCount: pagedResourcesPageCount, + loadPage: loadResourcesPage, + }); + const tools = toolsPagination.items; + const prompts = promptsPagination.items; + const resources = resourcesPagination.items; const { tasks, refresh: refreshTasks, @@ -2067,6 +2155,9 @@ function App() { managedToolsState?.destroy(); managedPromptsState?.destroy(); managedResourcesState?.destroy(); + pagedToolsState?.destroy(); + pagedPromptsState?.destroy(); + pagedResourcesState?.destroy(); managedResourceTemplatesState?.destroy(); managedRequestorTasksState?.destroy(); resourceSubscriptionsState?.destroy(); @@ -2166,6 +2257,9 @@ function App() { setInspectorClient(client); setManagedToolsState(new ManagedToolsState(client)); + setPagedToolsState(new PagedToolsState(client)); + setPagedPromptsState(new PagedPromptsState(client)); + setPagedResourcesState(new PagedResourcesState(client)); setManagedPromptsState(new ManagedPromptsState(client)); const nextResourcesState = new ManagedResourcesState(client); setManagedResourcesState(nextResourcesState); @@ -2204,6 +2298,9 @@ function App() { managedToolsState, managedPromptsState, managedResourcesState, + pagedToolsState, + pagedPromptsState, + pagedResourcesState, managedResourceTemplatesState, managedRequestorTasksState, resourceSubscriptionsState, @@ -3146,18 +3243,189 @@ function App() { [inspectorClient, runWithCommandAuthRecovery], ); + // Refresh acts per pagination mode: in paginated mode reload page 1 (the + // paged state); in all-pages mode re-fetch the whole aggregate with auth + // recovery (the pre-existing path). See usePaginatedList / #1721. const onRefreshTools = useCallback(() => { - void runWithCommandAuthRecovery(() => refreshTools(), "ambient"); - }, [refreshTools, runWithCommandAuthRecovery]); + if (paginatedLists) { + // Paginated refresh reloads page 1 of the paged store, bypassing the + // managed hook's refresh — so acknowledge the list-changed indicator here + // (the managed state lit it on `list_changed`; nothing else clears it in + // paginated mode) (#1721). + clearToolsListChanged(); + void runWithCommandAuthRecovery( + () => toolsPagination.onRefresh(), + "ambient", + ); + } else { + void runWithCommandAuthRecovery(() => refreshTools(), "ambient"); + } + }, [ + paginatedLists, + toolsPagination, + refreshTools, + clearToolsListChanged, + runWithCommandAuthRecovery, + ]); const onRefreshPrompts = useCallback(() => { - void runWithCommandAuthRecovery(() => refreshPrompts(), "ambient"); - }, [refreshPrompts, runWithCommandAuthRecovery]); + if (paginatedLists) { + clearPromptsListChanged(); + void runWithCommandAuthRecovery( + () => promptsPagination.onRefresh(), + "ambient", + ); + } else { + void runWithCommandAuthRecovery(() => refreshPrompts(), "ambient"); + } + }, [ + paginatedLists, + promptsPagination, + refreshPrompts, + clearPromptsListChanged, + runWithCommandAuthRecovery, + ]); const onRefreshResources = useCallback(() => { - void runWithCommandAuthRecovery(async () => { - await refreshResources(); - await refreshResourceTemplates(); - }, "ambient"); - }, [refreshResources, refreshResourceTemplates, runWithCommandAuthRecovery]); + if (paginatedLists) { + clearResourcesListChanged(); + void runWithCommandAuthRecovery( + () => resourcesPagination.onRefresh(), + "ambient", + ); + // Resource templates always use the managed (aggregate) path. + void runWithCommandAuthRecovery( + () => refreshResourceTemplates(), + "ambient", + ); + } else { + void runWithCommandAuthRecovery(async () => { + await refreshResources(); + await refreshResourceTemplates(); + }, "ambient"); + } + }, [ + paginatedLists, + resourcesPagination, + refreshResources, + refreshResourceTemplates, + clearResourcesListChanged, + runWithCommandAuthRecovery, + ]); + // The per-list sidebar toggle edits the server-wide `paginatedLists` setting: + // optimistic override for an instant flip, live push so the managed state's + // gating reads it now, and a persisted PUT so it survives reconnects (#1721). + const onTogglePaginatedLists = useCallback( + (value: boolean) => { + setPaginatedListsOverride(value); + const current = servers.find((s) => s.id === activeServerId); + if (!current || activeServerId === undefined) return; + const prevSettings = current.settings ?? EMPTY_SETTINGS; + const next: InspectorServerSettings = { + ...prevSettings, + paginatedLists: value, + }; + inspectorClient?.setServerSettings(next); + // Drive the load that the mode change implies (data-loading stays out of + // React effects; the paged stores own only the connect-time load). To + // paginated: pull page 1 into each paged store. To all-pages: refetch + // each managed aggregate that was gated off. Only when connected. + if (connected) { + // Wrap in ambient auth recovery so a mid-session 401 triggers re-auth + // rather than surfacing raw, matching the all-pages refresh path. + if (value) { + void runWithCommandAuthRecovery( + () => loadToolsPage(undefined), + "ambient", + ); + void runWithCommandAuthRecovery( + () => loadPromptsPage(undefined), + "ambient", + ); + void runWithCommandAuthRecovery( + () => loadResourcesPage(undefined), + "ambient", + ); + } else { + void runWithCommandAuthRecovery(() => refreshTools(), "ambient"); + void runWithCommandAuthRecovery(() => refreshPrompts(), "ambient"); + void runWithCommandAuthRecovery(() => refreshResources(), "ambient"); + } + } + void updateServerSettings(activeServerId, next).catch((err: unknown) => { + // Persist failed: revert the optimistic override (the effect only + // clears it when the persisted value changes, which won't happen here) + // and roll the live client setting back, so the UI and client reflect + // the value that's actually on disk rather than the failed edit (#1721). + setPaginatedListsOverride(null); + inspectorClient?.setServerSettings(prevSettings); + notifications.show({ + title: "Failed to save pagination setting", + message: err instanceof Error ? err.message : String(err), + color: "red", + }); + }); + }, + [ + servers, + activeServerId, + inspectorClient, + updateServerSettings, + connected, + loadToolsPage, + loadPromptsPage, + loadResourcesPage, + refreshTools, + refreshPrompts, + refreshResources, + runWithCommandAuthRecovery, + ], + ); + // Wrap Load-next-page in ambient auth recovery too, so a paginated + // paginated fetch that hits a 401 recovers like the all-pages path (#1721). + const onLoadMoreTools = useCallback( + () => + void runWithCommandAuthRecovery( + () => toolsPagination.onLoadMore(), + "ambient", + ), + [toolsPagination, runWithCommandAuthRecovery], + ); + const onLoadMorePrompts = useCallback( + () => + void runWithCommandAuthRecovery( + () => promptsPagination.onLoadMore(), + "ambient", + ), + [promptsPagination, runWithCommandAuthRecovery], + ); + const onLoadMoreResources = useCallback( + () => + void runWithCommandAuthRecovery( + () => resourcesPagination.onLoadMore(), + "ambient", + ), + [resourcesPagination, runWithCommandAuthRecovery], + ); + const toolsPaginationControls: ListPaginationControlsProps = { + paginated: toolsPagination.paginated, + onPaginatedChange: onTogglePaginatedLists, + canLoadMore: toolsPagination.canLoadMore, + loadedPages: toolsPagination.loadedPages, + onLoadMore: onLoadMoreTools, + }; + const promptsPaginationControls: ListPaginationControlsProps = { + paginated: promptsPagination.paginated, + onPaginatedChange: onTogglePaginatedLists, + canLoadMore: promptsPagination.canLoadMore, + loadedPages: promptsPagination.loadedPages, + onLoadMore: onLoadMorePrompts, + }; + const resourcesPaginationControls: ListPaginationControlsProps = { + paginated: resourcesPagination.paginated, + onPaginatedChange: onTogglePaginatedLists, + canLoadMore: resourcesPagination.canLoadMore, + loadedPages: resourcesPagination.loadedPages, + onLoadMore: onLoadMoreResources, + }; const onRefreshTasks = useCallback(() => { void runWithCommandAuthRecovery(() => refreshTasks(), "ambient")?.catch( (err: unknown) => { @@ -3339,6 +3607,9 @@ function App() { managedToolsState?.destroy(); managedPromptsState?.destroy(); managedResourcesState?.destroy(); + pagedToolsState?.destroy(); + pagedPromptsState?.destroy(); + pagedResourcesState?.destroy(); managedResourceTemplatesState?.destroy(); managedRequestorTasksState?.destroy(); resourceSubscriptionsState?.destroy(); @@ -3349,6 +3620,9 @@ function App() { setManagedToolsState(null); setManagedPromptsState(null); setManagedResourcesState(null); + setPagedToolsState(null); + setPagedPromptsState(null); + setPagedResourcesState(null); setManagedResourceTemplatesState(null); setManagedRequestorTasksState(null); setResourceSubscriptionsState(null); @@ -3366,6 +3640,9 @@ function App() { managedToolsState, managedPromptsState, managedResourcesState, + pagedToolsState, + pagedPromptsState, + pagedResourcesState, managedResourceTemplatesState, managedRequestorTasksState, resourceSubscriptionsState, @@ -3962,6 +4239,9 @@ function App() { onClearToolResult={onClearToolResult} onReadResourceContents={onReadResourceContents} onRefreshTools={onRefreshTools} + toolsPagination={toolsPaginationControls} + promptsPagination={promptsPaginationControls} + resourcesPagination={resourcesPaginationControls} onPromptsUiChange={setPromptsUi} onGetPrompt={(name, args) => { void onGetPrompt(name, args); diff --git a/clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.stories.tsx b/clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.stories.tsx new file mode 100644 index 00000000..1b23f841 --- /dev/null +++ b/clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.stories.tsx @@ -0,0 +1,47 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { fn } from "storybook/test"; +import { ListPaginationControls } from "./ListPaginationControls"; + +const meta: Meta = { + title: "Elements/ListPaginationControls", + component: ListPaginationControls, + args: { + onPaginatedChange: fn(), + onLoadMore: fn(), + }, +}; + +export default meta; +type Story = StoryObj; + +export const AllPages: Story = { + args: { + paginated: false, + canLoadMore: false, + loadedPages: 0, + }, +}; + +export const SinglePageWithMore: Story = { + args: { + paginated: true, + canLoadMore: true, + loadedPages: 2, + }, +}; + +export const SinglePageAtEnd: Story = { + args: { + paginated: true, + canLoadMore: false, + loadedPages: 3, + }, +}; + +export const SinglePageFirstPage: Story = { + args: { + paginated: true, + canLoadMore: true, + loadedPages: 1, + }, +}; diff --git a/clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.test.tsx b/clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.test.tsx new file mode 100644 index 00000000..ee24ac8d --- /dev/null +++ b/clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.test.tsx @@ -0,0 +1,125 @@ +import { describe, it, expect, vi } from "vitest"; +import { renderWithMantine, screen } from "../../../test/renderWithMantine"; +import userEvent from "@testing-library/user-event"; +import { ListPaginationControls } from "./ListPaginationControls"; + +const baseProps = { + paginated: false, + onPaginatedChange: () => {}, + canLoadMore: false, + loadedPages: 0, + onLoadMore: () => {}, +}; + +describe("ListPaginationControls", () => { + it("renders only the 'Paginated' switch in all-pages mode (no load-more)", () => { + renderWithMantine(); + const toggle = screen.getByRole("switch"); + expect(toggle).not.toBeChecked(); + expect(screen.getByText("Paginated")).toBeInTheDocument(); + expect( + screen.queryByRole("button", { name: "Load next page" }), + ).not.toBeInTheDocument(); + }); + + it("toggles paginated mode via the switch", async () => { + const user = userEvent.setup(); + const onPaginatedChange = vi.fn(); + renderWithMantine( + , + ); + await user.click(screen.getByRole("switch")); + expect(onPaginatedChange).toHaveBeenCalledWith(true); + }); + + it("shows the load-more button and page count in paginated mode", () => { + renderWithMantine( + , + ); + expect(screen.getByRole("switch")).toBeChecked(); + expect( + screen.getByRole("button", { name: "Load next page" }), + ).toBeEnabled(); + expect(screen.getByText("2 pages loaded")).toBeInTheDocument(); + }); + + it("uses the singular label for one page (while more remain)", () => { + renderWithMantine( + , + ); + expect(screen.getByText("1 page loaded")).toBeInTheDocument(); + }); + + it("hides the whole control when the list is a single page", () => { + // Paginated mode, page 1 loaded, no next cursor → nothing to paginate. + renderWithMantine( + , + ); + expect(screen.queryByRole("switch")).not.toBeInTheDocument(); + expect( + screen.queryByRole("button", { name: "Load next page" }), + ).not.toBeInTheDocument(); + }); + + it("still shows the switch while a paginated load is pending (0 pages)", () => { + // loadedPages 0 (page 1 not yet loaded) must not trip the paginated hide. + renderWithMantine( + , + ); + expect(screen.getByRole("switch")).toBeInTheDocument(); + }); + + it("marks the end when there are multiple pages and no more to load", () => { + renderWithMantine( + , + ); + expect( + screen.getByRole("button", { name: "Load next page" }), + ).toBeDisabled(); + expect(screen.getByText("3 pages loaded · end")).toBeInTheDocument(); + }); + + it("invokes onLoadMore when the button is clicked", async () => { + const user = userEvent.setup(); + const onLoadMore = vi.fn(); + renderWithMantine( + , + ); + await user.click(screen.getByRole("button", { name: "Load next page" })); + expect(onLoadMore).toHaveBeenCalledTimes(1); + }); +}); diff --git a/clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.tsx b/clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.tsx new file mode 100644 index 00000000..3ef19211 --- /dev/null +++ b/clients/web/src/components/elements/ListPaginationControls/ListPaginationControls.tsx @@ -0,0 +1,86 @@ +import { Button, Group, Stack, Switch, Text } from "@mantine/core"; + +export interface ListPaginationControlsProps { + /** + * True when the list is fetched one page at a time (backed by the server's + * `paginatedLists` setting). False = auto-aggregate every page on load. + */ + paginated: boolean; + /** Toggle paginated mode. Wired to write the `paginatedLists` setting. */ + onPaginatedChange: (paginated: boolean) => void; + /** Paginated mode only: the server returned a `nextCursor` to load. */ + canLoadMore: boolean; + /** Paginated mode only: number of pages loaded so far (status label). */ + loadedPages: number; + /** Paginated mode only: fetch the next page. */ + onLoadMore: () => void; +} + +// Switch fully left, "Load next page" fully right; the page-count sits on its +// own line under the row. +const ControlsRow = Group.withProps({ + gap: "sm", + align: "center", + justify: "space-between", + wrap: "nowrap", +}); + +const ModeSwitch = Switch.withProps({ + size: "sm", + "aria-label": "Fetch lists one page at a time", +}); + +const LoadMoreButton = Button.withProps({ + size: "compact-sm", + variant: "light", +}); + +const StatusText = Text.withProps({ + size: "xs", + ta: "center", + c: "var(--inspector-text-secondary)", +}); + +/** + * Sidebar control for a paginated list (Tools/Resources/Prompts). A "Paginated" + * switch toggles between auto-aggregating every page and fetching one page at a + * time; in paginated mode a "Load next page" button (to the right of the + * switch) surfaces the server's `nextCursor` and a status shows how many pages + * are loaded. Hidden entirely once the list is known to be a single page, since + * there's nothing to paginate. + */ +export function ListPaginationControls({ + paginated, + onPaginatedChange, + canLoadMore, + loadedPages, + onLoadMore, +}: ListPaginationControlsProps) { + // The list turned out to be a single page (loaded page 1, no `nextCursor`): + // pagination is moot, so hide the whole control rather than show a useless + // toggle + disabled button (#1721). + if (paginated && !canLoadMore && loadedPages === 1) return null; + + return ( + + + onPaginatedChange(e.currentTarget.checked)} + /> + {paginated ? ( + + Load next page + + ) : null} + + {paginated ? ( + + {loadedPages} {loadedPages === 1 ? "page" : "pages"} loaded + {canLoadMore ? "" : " · end"} + + ) : null} + + ); +} diff --git a/clients/web/src/components/groups/PromptControls/PromptControls.stories.tsx b/clients/web/src/components/groups/PromptControls/PromptControls.stories.tsx index 89ec8d3c..9d0d3a38 100644 --- a/clients/web/src/components/groups/PromptControls/PromptControls.stories.tsx +++ b/clients/web/src/components/groups/PromptControls/PromptControls.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import { noopPagination } from "../../../test/fixtures/pagination"; import type { Prompt } from "@modelcontextprotocol/client"; import { fn } from "storybook/test"; import { PromptControls } from "./PromptControls"; @@ -7,6 +8,7 @@ const meta: Meta = { title: "Groups/PromptControls", component: PromptControls, args: { + pagination: noopPagination, onRefreshList: fn(), onSelectPrompt: fn(), onSearchChange: fn(), diff --git a/clients/web/src/components/groups/PromptControls/PromptControls.test.tsx b/clients/web/src/components/groups/PromptControls/PromptControls.test.tsx index b01ccd62..570fe040 100644 --- a/clients/web/src/components/groups/PromptControls/PromptControls.test.tsx +++ b/clients/web/src/components/groups/PromptControls/PromptControls.test.tsx @@ -4,6 +4,7 @@ import userEvent from "@testing-library/user-event"; import type { Prompt } from "@modelcontextprotocol/client"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; import { PromptControls, type PromptControlsProps } from "./PromptControls"; +import { noopPagination } from "../../../test/fixtures/pagination"; const samplePrompts: Prompt[] = [ { @@ -25,6 +26,7 @@ const baseProps = { onRefreshList: vi.fn(), onSelectPrompt: vi.fn(), onSearchChange: vi.fn(), + pagination: noopPagination, }; // The search box is controlled: typing fires onSearchChange but does not diff --git a/clients/web/src/components/groups/PromptControls/PromptControls.tsx b/clients/web/src/components/groups/PromptControls/PromptControls.tsx index 5a8f847f..3f0544f8 100644 --- a/clients/web/src/components/groups/PromptControls/PromptControls.tsx +++ b/clients/web/src/components/groups/PromptControls/PromptControls.tsx @@ -2,6 +2,10 @@ import { Group, ScrollArea, Stack, TextInput, Title } from "@mantine/core"; import { ClearButton } from "../../elements/ClearButton/ClearButton"; import type { Prompt } from "@modelcontextprotocol/client"; import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator"; +import { + ListPaginationControls, + type ListPaginationControlsProps, +} from "../../elements/ListPaginationControls/ListPaginationControls"; import { PromptListItem } from "../PromptListItem/PromptListItem"; import { useScrollMemory } from "../../../hooks/useScrollMemory"; @@ -13,6 +17,8 @@ export interface PromptControlsProps { searchText?: string; listChanged: boolean; onRefreshList: () => void; + /** Pagination controls (#1721). */ + pagination: ListPaginationControlsProps; onSearchChange: (value: string) => void; onSelectPrompt: (name: string) => void; } @@ -23,6 +29,7 @@ export function PromptControls({ searchText = "", listChanged, onRefreshList, + pagination, onSearchChange, onSelectPrompt, }: PromptControlsProps) { @@ -53,6 +60,7 @@ export function PromptControls({ searchText ? onSearchChange("")} /> : null } /> + {filteredPrompts.map((prompt) => ( diff --git a/clients/web/src/components/groups/ResourceControls/ResourceControls.stories.tsx b/clients/web/src/components/groups/ResourceControls/ResourceControls.stories.tsx index f15d2364..958ec6aa 100644 --- a/clients/web/src/components/groups/ResourceControls/ResourceControls.stories.tsx +++ b/clients/web/src/components/groups/ResourceControls/ResourceControls.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; +import { noopPagination } from "../../../test/fixtures/pagination"; import type { Resource, ResourceTemplateType as ResourceTemplate, @@ -26,6 +27,7 @@ const meta: Meta = { title: "Groups/ResourceControls", component: ResourceControls, args: { + pagination: noopPagination, onRefreshList: fn(), onSelectUri: fn(), onSelectTemplate: fn(), diff --git a/clients/web/src/components/groups/ResourceControls/ResourceControls.test.tsx b/clients/web/src/components/groups/ResourceControls/ResourceControls.test.tsx index 0ee67c46..df72686b 100644 --- a/clients/web/src/components/groups/ResourceControls/ResourceControls.test.tsx +++ b/clients/web/src/components/groups/ResourceControls/ResourceControls.test.tsx @@ -11,6 +11,7 @@ import { ResourceControls, type ResourceControlsProps, } from "./ResourceControls"; +import { noopPagination } from "../../../test/fixtures/pagination"; const sampleResources: Resource[] = [ { name: "config.json", uri: "file:///config.json" }, @@ -41,6 +42,7 @@ const baseProps = { onUnsubscribeResource: vi.fn(), compact: false, onCompactChange: vi.fn(), + pagination: noopPagination, }; // ResourceControls is controlled: search text + accordion open-sections live in diff --git a/clients/web/src/components/groups/ResourceControls/ResourceControls.tsx b/clients/web/src/components/groups/ResourceControls/ResourceControls.tsx index 31239774..fea2e518 100644 --- a/clients/web/src/components/groups/ResourceControls/ResourceControls.tsx +++ b/clients/web/src/components/groups/ResourceControls/ResourceControls.tsx @@ -7,6 +7,10 @@ import type { } from "@modelcontextprotocol/client"; import type { InspectorResourceSubscription } from "../../../../../../core/mcp/types.js"; import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator"; +import { + ListPaginationControls, + type ListPaginationControlsProps, +} from "../../elements/ListPaginationControls/ListPaginationControls"; import { ListToggle } from "../../elements/ListToggle/ListToggle"; import { ResourceListItem } from "../ResourceListItem/ResourceListItem"; import { ResourceSubscribedItem } from "../ResourceSubscribedItem/ResourceSubscribedItem"; @@ -32,6 +36,8 @@ export interface ResourceControlsProps { openSections?: string[]; listChanged: boolean; onRefreshList: () => void; + /** Pagination controls for the Resources list (#1721). */ + pagination: ListPaginationControlsProps; onSearchChange: (value: string) => void; onOpenSectionsChange: (value: string[]) => void; onSelectUri: (uri: string) => void; @@ -71,6 +77,7 @@ export function ResourceControls({ openSections: controlledOpenSections, listChanged, onRefreshList, + pagination, onSearchChange, onOpenSectionsChange, onSelectUri, @@ -186,6 +193,7 @@ export function ResourceControls({ /> + { + args.onPaginatedListsChange(value); + updateArgs({ + settings: { ...args.settings, paginatedLists: value }, + }); + }} onMaxFetchRequestsChange={(value) => { args.onMaxFetchRequestsChange(value); updateArgs({ @@ -178,6 +184,7 @@ const meta: Meta = { onMetadataChange: fn(), onTimeoutChange: fn(), onAutoRefreshChange: fn(), + onPaginatedListsChange: fn(), onMaxFetchRequestsChange: fn(), onProtocolEraChange: fn(), onOAuthChange: fn(), diff --git a/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.test.tsx b/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.test.tsx index 553efc3a..7d76ec0d 100644 --- a/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.test.tsx +++ b/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.test.tsx @@ -65,6 +65,7 @@ const baseHandlers = { onMetadataChange: vi.fn(), onTimeoutChange: vi.fn(), onAutoRefreshChange: vi.fn(), + onPaginatedListsChange: vi.fn(), onMaxFetchRequestsChange: vi.fn(), onProtocolEraChange: vi.fn(), onOAuthChange: vi.fn(), diff --git a/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.tsx b/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.tsx index 98fa9e83..c7fc6e29 100644 --- a/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.tsx +++ b/clients/web/src/components/groups/ServerSettingsForm/ServerSettingsForm.tsx @@ -58,6 +58,7 @@ export interface ServerSettingsFormProps { value: number, ) => void; onAutoRefreshChange: (value: boolean) => void; + onPaginatedListsChange: (value: boolean) => void; onMaxFetchRequestsChange: (value: number) => void; onProtocolEraChange: (value: ServerProtocolEra) => void; onOAuthChange: (oauth: OAuthSettings) => void; @@ -239,6 +240,7 @@ export function ServerSettingsForm({ onMetadataChange, onTimeoutChange, onAutoRefreshChange, + onPaginatedListsChange, onMaxFetchRequestsChange, onProtocolEraChange, onOAuthChange, @@ -314,6 +316,12 @@ export function ServerSettingsForm({ checked={settings.autoRefreshOnListChanged ?? false} onChange={(e) => onAutoRefreshChange(e.currentTarget.checked)} /> + onPaginatedListsChange(e.currentTarget.checked)} + /> = { title: "Groups/ToolControls", component: ToolControls, args: { + pagination: noopPagination, onRefreshList: fn(), onSelectTool: fn(), onSearchChange: fn(), diff --git a/clients/web/src/components/groups/ToolControls/ToolControls.test.tsx b/clients/web/src/components/groups/ToolControls/ToolControls.test.tsx index a5c98ee9..1e0290aa 100644 --- a/clients/web/src/components/groups/ToolControls/ToolControls.test.tsx +++ b/clients/web/src/components/groups/ToolControls/ToolControls.test.tsx @@ -4,6 +4,7 @@ import userEvent from "@testing-library/user-event"; import type { Tool } from "@modelcontextprotocol/client"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; import { ToolControls, type ToolControlsProps } from "./ToolControls"; +import { noopPagination } from "../../../test/fixtures/pagination"; const sampleTools: Tool[] = [ { name: "list_files", title: "List Files", inputSchema: { type: "object" } }, @@ -22,6 +23,7 @@ const baseProps = { onRefreshList: vi.fn(), onSelectTool: vi.fn(), onSearchChange: vi.fn(), + pagination: noopPagination, }; // The search box is controlled: typing fires onSearchChange but does not diff --git a/clients/web/src/components/groups/ToolControls/ToolControls.tsx b/clients/web/src/components/groups/ToolControls/ToolControls.tsx index 1065fd7c..dd7ca5c8 100644 --- a/clients/web/src/components/groups/ToolControls/ToolControls.tsx +++ b/clients/web/src/components/groups/ToolControls/ToolControls.tsx @@ -2,6 +2,10 @@ import { Group, ScrollArea, Stack, TextInput, Title } from "@mantine/core"; import { ClearButton } from "../../elements/ClearButton/ClearButton"; import type { Tool } from "@modelcontextprotocol/client"; import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator"; +import { + ListPaginationControls, + type ListPaginationControlsProps, +} from "../../elements/ListPaginationControls/ListPaginationControls"; import { ToolListItem } from "../ToolListItem/ToolListItem"; import { useScrollMemory } from "../../../hooks/useScrollMemory"; @@ -13,6 +17,8 @@ export interface ToolControlsProps { searchText?: string; listChanged: boolean; onRefreshList: () => void; + /** Pagination controls (#1721). */ + pagination: ListPaginationControlsProps; onSearchChange: (value: string) => void; onSelectTool: (name: string) => void; } @@ -23,6 +29,7 @@ export function ToolControls({ searchText = "", listChanged, onRefreshList, + pagination, onSearchChange, onSelectTool, }: ToolControlsProps) { @@ -61,6 +68,7 @@ export function ToolControls({ searchText ? onSearchChange("")} /> : null } /> + {filteredTools.map((tool) => ( diff --git a/clients/web/src/components/screens/PromptsScreen/PromptsScreen.stories.tsx b/clients/web/src/components/screens/PromptsScreen/PromptsScreen.stories.tsx index 0558b563..8728cc73 100644 --- a/clients/web/src/components/screens/PromptsScreen/PromptsScreen.stories.tsx +++ b/clients/web/src/components/screens/PromptsScreen/PromptsScreen.stories.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { noopPagination } from "../../../test/fixtures/pagination"; import type { ComponentProps } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; import type { Prompt } from "@modelcontextprotocol/client"; @@ -21,6 +22,7 @@ const meta: Meta = { component: PromptsScreen, parameters: { layout: "fullscreen" }, args: { + pagination: noopPagination, ui: EMPTY_PROMPTS_UI, onUiChange: fn(), onRefreshList: fn(), diff --git a/clients/web/src/components/screens/PromptsScreen/PromptsScreen.test.tsx b/clients/web/src/components/screens/PromptsScreen/PromptsScreen.test.tsx index 1fc96691..bbb96910 100644 --- a/clients/web/src/components/screens/PromptsScreen/PromptsScreen.test.tsx +++ b/clients/web/src/components/screens/PromptsScreen/PromptsScreen.test.tsx @@ -3,6 +3,7 @@ import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import type { Prompt } from "@modelcontextprotocol/client"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; +import { noopPagination } from "../../../test/fixtures/pagination"; import { PromptsScreen, type PromptsScreenProps, @@ -34,6 +35,7 @@ const baseProps = { ui: EMPTY_PROMPTS_UI, onUiChange: vi.fn(), onRefreshList: vi.fn(), + pagination: noopPagination, onGetPrompt: vi.fn(), }; diff --git a/clients/web/src/components/screens/PromptsScreen/PromptsScreen.tsx b/clients/web/src/components/screens/PromptsScreen/PromptsScreen.tsx index a81f2199..94e2453c 100644 --- a/clients/web/src/components/screens/PromptsScreen/PromptsScreen.tsx +++ b/clients/web/src/components/screens/PromptsScreen/PromptsScreen.tsx @@ -10,6 +10,7 @@ import { } from "@mantine/core"; import type { GetPromptResult, Prompt } from "@modelcontextprotocol/client"; import { PromptControls } from "../../groups/PromptControls/PromptControls"; +import type { ListPaginationControlsProps } from "../../elements/ListPaginationControls/ListPaginationControls"; import { PromptArgumentsForm } from "../../groups/PromptArgumentsForm/PromptArgumentsForm"; import { PromptMessagesDisplay } from "../../groups/PromptMessagesDisplay/PromptMessagesDisplay"; @@ -32,6 +33,8 @@ export interface PromptsScreenProps { completionsSupported?: boolean; onUiChange: (next: PromptsUiState) => void; onRefreshList: () => void; + /** Pagination controls rendered in the sidebar (#1721). */ + pagination: ListPaginationControlsProps; onGetPrompt: (name: string, args: Record) => void; onCopyMessages?: () => void; onCompleteArgument?: ( @@ -62,7 +65,9 @@ const ScreenLayout = Flex.withProps({ }); const Sidebar = Stack.withProps({ - w: 340, + // Widened from 340 to comfortably fit the pagination controls + // (Load-next-page button + status) without cramping list entries (#1721). + w: 360, flex: "0 0 auto", }); @@ -119,6 +124,7 @@ export function PromptsScreen({ completionsSupported, onUiChange, onRefreshList, + pagination, onGetPrompt, onCopyMessages, onCompleteArgument, @@ -255,6 +261,7 @@ export function PromptsScreen({ searchText={search} listChanged={listChanged} onRefreshList={onRefreshList} + pagination={pagination} onSearchChange={(value) => onUiChange({ ...ui, search: value })} onSelectPrompt={handleSelectPrompt} /> diff --git a/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.stories.tsx b/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.stories.tsx index 3dd72ad6..aee4edbf 100644 --- a/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.stories.tsx +++ b/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.stories.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { noopPagination } from "../../../test/fixtures/pagination"; import type { ComponentProps } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; import type { @@ -26,6 +27,7 @@ const meta: Meta = { component: ResourcesScreen, parameters: { layout: "fullscreen" }, args: { + pagination: noopPagination, ui: EMPTY_RESOURCES_UI, onUiChange: fn(), onRefreshList: fn(), diff --git a/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.test.tsx b/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.test.tsx index 40d886f3..47c3f7e4 100644 --- a/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.test.tsx +++ b/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.test.tsx @@ -7,6 +7,7 @@ import type { ReadResourceResult, } from "@modelcontextprotocol/client"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; +import { noopPagination } from "../../../test/fixtures/pagination"; import { ResourcesScreen, type ResourcesScreenProps, @@ -31,6 +32,7 @@ const baseProps = { ui: EMPTY_RESOURCES_UI, onUiChange: vi.fn(), onRefreshList: vi.fn(), + pagination: noopPagination, onReadResource: vi.fn(), onSubscribeResource: vi.fn(), onUnsubscribeResource: vi.fn(), diff --git a/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.tsx b/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.tsx index dd9d9a72..95ddd220 100644 --- a/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.tsx +++ b/clients/web/src/components/screens/ResourcesScreen/ResourcesScreen.tsx @@ -15,6 +15,7 @@ import type { } from "@modelcontextprotocol/client"; import type { InspectorResourceSubscription } from "../../../../../../core/mcp/types.js"; import { ResourceControls } from "../../groups/ResourceControls/ResourceControls"; +import type { ListPaginationControlsProps } from "../../elements/ListPaginationControls/ListPaginationControls"; import { ResourcePreviewPanel } from "../../groups/ResourcePreviewPanel/ResourcePreviewPanel"; import { ResourceTemplatePanel } from "../../groups/ResourceTemplatePanel/ResourceTemplatePanel"; @@ -44,6 +45,8 @@ export interface ResourcesScreenProps { subscriptionsSupported?: boolean; onUiChange: (next: ResourcesUiState) => void; onRefreshList: () => void; + /** Pagination controls rendered in the sidebar (#1721). */ + pagination: ListPaginationControlsProps; onReadResource: (uri: string) => void; onSubscribeResource: (uri: string) => void; onUnsubscribeResource: (uri: string) => void; @@ -80,7 +83,9 @@ const ScreenLayout = Flex.withProps({ }); const Sidebar = Stack.withProps({ - w: 340, + // Widened from 340 to comfortably fit the pagination controls + // (Load-next-page button + status) without cramping list entries (#1721). + w: 360, flex: "0 0 auto", }); @@ -139,6 +144,7 @@ export function ResourcesScreen({ subscriptionsSupported = true, onUiChange, onRefreshList, + pagination, onReadResource, onSubscribeResource, onUnsubscribeResource, @@ -291,6 +297,7 @@ export function ResourcesScreen({ openSections={openSections} listChanged={listChanged} onRefreshList={onRefreshList} + pagination={pagination} onSearchChange={(value) => onUiChange({ ...ui, search: value })} onOpenSectionsChange={(value) => onUiChange({ ...ui, openSections: value }) diff --git a/clients/web/src/components/screens/ToolsScreen/ToolsScreen.stories.tsx b/clients/web/src/components/screens/ToolsScreen/ToolsScreen.stories.tsx index ae6be575..11848bf8 100644 --- a/clients/web/src/components/screens/ToolsScreen/ToolsScreen.stories.tsx +++ b/clients/web/src/components/screens/ToolsScreen/ToolsScreen.stories.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { noopPagination } from "../../../test/fixtures/pagination"; import type { ComponentProps } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; import type { ElicitRequest, Tool } from "@modelcontextprotocol/client"; @@ -27,6 +28,7 @@ const meta: Meta = { component: ToolsScreen, parameters: { layout: "fullscreen" }, args: { + pagination: noopPagination, listChanged: false, serverSupportsTaskToolCalls: false, ui: EMPTY_TOOLS_UI, diff --git a/clients/web/src/components/screens/ToolsScreen/ToolsScreen.test.tsx b/clients/web/src/components/screens/ToolsScreen/ToolsScreen.test.tsx index 66f579d3..6643d949 100644 --- a/clients/web/src/components/screens/ToolsScreen/ToolsScreen.test.tsx +++ b/clients/web/src/components/screens/ToolsScreen/ToolsScreen.test.tsx @@ -3,6 +3,7 @@ import { describe, it, expect, vi } from "vitest"; import userEvent from "@testing-library/user-event"; import type { Tool } from "@modelcontextprotocol/client"; import { renderWithMantine, screen } from "../../../test/renderWithMantine"; +import { noopPagination } from "../../../test/fixtures/pagination"; import { ToolsScreen, type ToolsScreenProps, @@ -34,6 +35,7 @@ const baseProps = { ui: EMPTY_TOOLS_UI, onUiChange: vi.fn(), onRefreshList: vi.fn(), + pagination: noopPagination, onCallTool: vi.fn(), }; diff --git a/clients/web/src/components/screens/ToolsScreen/ToolsScreen.tsx b/clients/web/src/components/screens/ToolsScreen/ToolsScreen.tsx index 787c12c5..ae50d6e9 100644 --- a/clients/web/src/components/screens/ToolsScreen/ToolsScreen.tsx +++ b/clients/web/src/components/screens/ToolsScreen/ToolsScreen.tsx @@ -5,6 +5,7 @@ import type { Tool, } from "@modelcontextprotocol/client"; import { ToolControls } from "../../groups/ToolControls/ToolControls"; +import type { ListPaginationControlsProps } from "../../elements/ListPaginationControls/ListPaginationControls"; import { ToolDetailPanel, type ToolProgress, @@ -44,6 +45,8 @@ export interface ToolsScreenProps { serverSupportsTaskToolCalls: boolean; onUiChange: (next: ToolsUiState) => void; onRefreshList: () => void; + /** Pagination controls rendered in the sidebar (#1721). */ + pagination: ListPaginationControlsProps; onCallTool: ( name: string, args: Record, @@ -76,7 +79,9 @@ const ScreenLayout = Flex.withProps({ }); const Sidebar = Stack.withProps({ - w: 340, + // Widened from 340 to comfortably fit the pagination controls + // (Load-next-page button + status) without cramping list entries (#1721). + w: 360, flex: "0 0 auto", }); @@ -130,6 +135,7 @@ export function ToolsScreen({ serverSupportsTaskToolCalls, onUiChange, onRefreshList, + pagination, onCallTool, onCancelCall, onClearResult, @@ -151,6 +157,7 @@ export function ToolsScreen({ searchText={search} listChanged={listChanged} onRefreshList={onRefreshList} + pagination={pagination} onSearchChange={(value) => onUiChange({ ...ui, search: value })} onSelectTool={(name) => { // Seed the form with the tool's schema defaults so default-only diff --git a/clients/web/src/components/views/InspectorView/InspectorView.stories.tsx b/clients/web/src/components/views/InspectorView/InspectorView.stories.tsx index 3af88729..7dc70052 100644 --- a/clients/web/src/components/views/InspectorView/InspectorView.stories.tsx +++ b/clients/web/src/components/views/InspectorView/InspectorView.stories.tsx @@ -18,6 +18,7 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { useState } from "react"; import { expect, fn } from "storybook/test"; import { InspectorView } from "./InspectorView"; +import { noopPagination } from "../../../test/fixtures/pagination"; import { EMPTY_TOOLS_UI, EMPTY_APPS_UI, @@ -385,6 +386,9 @@ const meta: Meta = { onToolsUiChange: fn(), onCallTool: fn(), onRefreshTools: fn(), + toolsPagination: noopPagination, + promptsPagination: noopPagination, + resourcesPagination: noopPagination, onPromptsUiChange: fn(), onGetPrompt: fn(), onRefreshPrompts: fn(), diff --git a/clients/web/src/components/views/InspectorView/InspectorView.test.tsx b/clients/web/src/components/views/InspectorView/InspectorView.test.tsx index eb770ee8..86907e2d 100644 --- a/clients/web/src/components/views/InspectorView/InspectorView.test.tsx +++ b/clients/web/src/components/views/InspectorView/InspectorView.test.tsx @@ -19,6 +19,7 @@ import { fireEvent, } from "../../../test/renderWithMantine"; import { InspectorView, type InspectorViewProps } from "./InspectorView"; +import { noopPagination } from "../../../test/fixtures/pagination"; // happy-dom's viewport is 1024px, narrower than the app's 1280px floor, so any // viewport media query would read "narrow". Force every `useMediaQuery` "wide" @@ -115,6 +116,9 @@ function makeProps( onToolsUiChange: vi.fn(), onCallTool: vi.fn(), onRefreshTools: vi.fn(), + toolsPagination: noopPagination, + promptsPagination: noopPagination, + resourcesPagination: noopPagination, onPromptsUiChange: vi.fn(), onGetPrompt: vi.fn(), onRefreshPrompts: vi.fn(), diff --git a/clients/web/src/components/views/InspectorView/InspectorView.tsx b/clients/web/src/components/views/InspectorView/InspectorView.tsx index af9e31e5..829ffdae 100644 --- a/clients/web/src/components/views/InspectorView/InspectorView.tsx +++ b/clients/web/src/components/views/InspectorView/InspectorView.tsx @@ -40,6 +40,7 @@ import { isAppTool } from "@inspector/core/mcp/apps.js"; import { ViewHeader } from "../../groups/ViewHeader/ViewHeader"; import { VersionBadge } from "../../elements/VersionBadge/VersionBadge"; import { CopyrightBadge } from "../../elements/CopyrightBadge/CopyrightBadge"; +import type { ListPaginationControlsProps } from "../../elements/ListPaginationControls/ListPaginationControls"; import { ServerListScreen } from "../../screens/ServerListScreen/ServerListScreen"; import { ToolsScreen, @@ -491,6 +492,12 @@ export interface InspectorViewProps { onCancelToolCall?: () => void; onClearToolResult?: () => void; onRefreshTools: () => void; + /** Pagination controls for the Tools list (#1721). */ + toolsPagination: ListPaginationControlsProps; + /** Pagination controls for the Prompts list (#1721). */ + promptsPagination: ListPaginationControlsProps; + /** Pagination controls for the Resources list (#1721). */ + resourcesPagination: ListPaginationControlsProps; /** * Read-on-demand handler for `resource_link` blocks in a tool result. * Returns the linked resource's contents so the result panel can inline them. @@ -625,6 +632,9 @@ export function InspectorView({ onClearToolResult, onReadResourceContents, onRefreshTools, + toolsPagination, + promptsPagination, + resourcesPagination, onPromptsUiChange, onGetPrompt, onCopyPromptMessages, @@ -1285,6 +1295,7 @@ export function InspectorView({ serverSupportsTaskToolCalls={serverSupportsTaskToolCalls} onUiChange={onToolsUiChange} onRefreshList={onRefreshTools} + pagination={toolsPagination} onCallTool={onCallTool} onCancelCall={onCancelToolCall} onClearResult={onClearToolResult} @@ -1317,6 +1328,7 @@ export function InspectorView({ completionsSupported={completionsSupported} onUiChange={onPromptsUiChange} onRefreshList={onRefreshPrompts} + pagination={promptsPagination} onGetPrompt={onGetPrompt} onCopyMessages={onCopyPromptMessages} onCompleteArgument={onCompleteArgument} @@ -1334,6 +1346,7 @@ export function InspectorView({ subscriptionsSupported={subscriptionsSupported} onUiChange={onResourcesUiChange} onRefreshList={onRefreshResources} + pagination={resourcesPagination} onReadResource={onReadResource} onSubscribeResource={onSubscribeResource} onUnsubscribeResource={onUnsubscribeResource} diff --git a/clients/web/src/hooks/usePaginatedList.test.tsx b/clients/web/src/hooks/usePaginatedList.test.tsx new file mode 100644 index 00000000..558716e8 --- /dev/null +++ b/clients/web/src/hooks/usePaginatedList.test.tsx @@ -0,0 +1,98 @@ +import { describe, it, expect, vi } from "vitest"; +import { renderHook } from "@testing-library/react"; +import { usePaginatedList } from "./usePaginatedList"; + +interface Params { + connected: boolean; + paginated: boolean; + managedItems: string[]; + managedRefresh: () => Promise; + pagedItems: string[]; + pagedNextCursor?: string; + pagedPageCount: number; + loadPage: (cursor?: string) => Promise; +} + +function makeParams(over: Partial = {}): Params { + return { + connected: true, + paginated: false, + managedItems: ["m1", "m2"], + managedRefresh: vi.fn(async () => []), + pagedItems: ["p1"], + pagedNextCursor: undefined, + pagedPageCount: 0, + loadPage: vi.fn(async () => ({})), + ...over, + }; +} + +describe("usePaginatedList", () => { + it("shows the managed list and no paging in all-pages mode", () => { + const params = makeParams(); + const { result } = renderHook(() => usePaginatedList(params)); + expect(result.current.items).toEqual(["m1", "m2"]); + expect(result.current.paginated).toBe(false); + expect(result.current.canLoadMore).toBe(false); + }); + + it("shows the paged list and derives paging in paginated mode", () => { + const params = makeParams({ + paginated: true, + pagedNextCursor: "c1", + pagedPageCount: 2, + }); + const { result } = renderHook(() => usePaginatedList(params)); + expect(result.current.items).toEqual(["p1"]); + expect(result.current.canLoadMore).toBe(true); + expect(result.current.loadedPages).toBe(2); + }); + + it("masks paging progress while disconnected", () => { + const params = makeParams({ + connected: false, + paginated: true, + pagedNextCursor: "c1", + pagedPageCount: 2, + }); + const { result } = renderHook(() => usePaginatedList(params)); + expect(result.current.canLoadMore).toBe(false); + expect(result.current.loadedPages).toBe(0); + }); + + it("onLoadMore fetches the next page from the current cursor", () => { + const loadPage = vi.fn(async () => ({})); + const params = makeParams({ + paginated: true, + pagedNextCursor: "c1", + loadPage, + }); + const { result } = renderHook(() => usePaginatedList(params)); + void result.current.onLoadMore(); + expect(loadPage).toHaveBeenCalledWith("c1"); + }); + + it("onLoadMore is a no-op with no next cursor", () => { + const loadPage = vi.fn(async () => ({})); + const params = makeParams({ paginated: true, loadPage }); + const { result } = renderHook(() => usePaginatedList(params)); + void result.current.onLoadMore(); + expect(loadPage).not.toHaveBeenCalled(); + }); + + it("onRefresh reloads page 1 in paginated mode", () => { + const loadPage = vi.fn(async () => ({})); + const params = makeParams({ paginated: true, loadPage }); + const { result } = renderHook(() => usePaginatedList(params)); + void result.current.onRefresh(); + expect(loadPage).toHaveBeenCalledWith(undefined); + }); + + it("onRefresh re-fetches the aggregate in all-pages mode", () => { + const managedRefresh = vi.fn(async () => []); + const params = makeParams({ managedRefresh }); + const { result } = renderHook(() => usePaginatedList(params)); + void result.current.onRefresh(); + expect(managedRefresh).toHaveBeenCalledTimes(1); + }); +}); diff --git a/clients/web/src/hooks/usePaginatedList.ts b/clients/web/src/hooks/usePaginatedList.ts new file mode 100644 index 00000000..7729e592 --- /dev/null +++ b/clients/web/src/hooks/usePaginatedList.ts @@ -0,0 +1,91 @@ +import { useCallback } from "react"; + +/** + * The list + pagination controls a screen renders, produced by + * {@link usePaginatedList}. `items` is already the correct source for the + * active mode (the aggregate list in all-pages mode, the accumulated paged + * list in paginated mode). + */ +export interface PaginatedListModel { + items: T[]; + /** True when fetching one page at a time (the `paginatedLists` setting). */ + paginated: boolean; + /** Paginated mode: the server returned a `nextCursor` still to load. */ + canLoadMore: boolean; + /** Paginated mode: pages loaded so far. */ + loadedPages: number; + /** + * Paginated mode: fetch the next page (no-op otherwise). Returns the load + * promise so the caller can wrap it in auth recovery. + */ + onLoadMore: () => Promise; + /** + * Refresh the list: reload page 1 in paginated mode, or re-fetch the whole + * aggregate in all-pages mode. This is what the list-changed indicator's + * Refresh button calls. Returns the underlying promise so the caller can wrap + * it in auth recovery. + */ + onRefresh: () => Promise; +} + +export interface UsePaginatedListParams { + /** Whether the client is connected (masks the paged progress when not). */ + connected: boolean; + /** The `paginatedLists` server setting (the active mode). */ + paginated: boolean; + /** The auto-aggregated list (all-pages mode display source). */ + managedItems: T[]; + /** Re-fetch the whole aggregate (all-pages mode Refresh). */ + managedRefresh: () => Promise; + /** The accumulated paged list (paginated mode display source). */ + pagedItems: T[]; + /** The paged store's current `nextCursor` (undefined = at the end). */ + pagedNextCursor?: string; + /** The paged store's page count (page 1 = 1). */ + pagedPageCount: number; + /** Fetch one page; `undefined` cursor = page 1 (replaces the paged list). */ + loadPage: (cursor?: string) => Promise; +} + +/** + * Select a list's display source and pagination controls between the managed + * (auto-aggregate-all-pages) and paged (one-page-at-a-time) state stores, + * driven by the `paginatedLists` server setting (#1721). + * + * Loading is owned by the state stores, not this hook: the paged store + * auto-loads page 1 on connect in paginated mode (and the managed store skips + * its all-page walk there), so this hook is pure — it derives the display list, + * the load-more affordance, and a mode-aware Refresh from store state. Mode + * *changes* (which trigger a load) are driven from the sidebar toggle handler + * in App, keeping data-loading out of React effects. + */ +export function usePaginatedList({ + connected, + paginated, + managedItems, + managedRefresh, + pagedItems, + pagedNextCursor, + pagedPageCount, + loadPage, +}: UsePaginatedListParams): PaginatedListModel { + const onLoadMore = useCallback((): Promise => { + if (pagedNextCursor === undefined) return Promise.resolve(); + return loadPage(pagedNextCursor); + }, [pagedNextCursor, loadPage]); + + const onRefresh = useCallback((): Promise => { + return paginated ? loadPage(undefined) : managedRefresh(); + }, [paginated, loadPage, managedRefresh]); + + return { + items: paginated ? pagedItems : managedItems, + paginated, + // Masked by `connected`: while disconnected there is no page to load and no + // meaningful page count (the store resets on disconnect). + canLoadMore: connected && paginated && pagedNextCursor !== undefined, + loadedPages: connected ? pagedPageCount : 0, + onLoadMore, + onRefresh, + }; +} diff --git a/clients/web/src/test/core/mcp/serverList.test.ts b/clients/web/src/test/core/mcp/serverList.test.ts index 8e9cea02..6b6b6be7 100644 --- a/clients/web/src/test/core/mcp/serverList.test.ts +++ b/clients/web/src/test/core/mcp/serverList.test.ts @@ -251,6 +251,39 @@ describe("serverEntriesToMcpConfig", () => { expect(round).toEqual(original); }); + it("round-trips paginatedLists: lifts true to settings and back to disk", () => { + const original: MCPConfig = { + mcpServers: { + delta: { + type: "streamable-http", + url: "https://x.test/mcp", + paginatedLists: true, + }, + }, + }; + const [entry] = mcpConfigToServerEntries(original); + expect(entry?.settings?.paginatedLists).toBe(true); + const round = serverEntriesToMcpConfig(mcpConfigToServerEntries(original)); + expect(round).toEqual(original); + }); + + it("omits paginatedLists from disk when false (the default)", () => { + const original: MCPConfig = { + mcpServers: { + epsilon: { + type: "streamable-http", + url: "https://x.test/mcp", + connectionTimeout: 5000, + }, + }, + }; + const [entry] = mcpConfigToServerEntries(original); + expect(entry?.settings?.paginatedLists).toBe(false); + const round = serverEntriesToMcpConfig(mcpConfigToServerEntries(original)); + expect("paginatedLists" in (round.mcpServers.epsilon ?? {})).toBe(false); + expect(round).toEqual(original); + }); + it("round-trips maxFetchRequests: lifts a non-default value to settings and back to disk", () => { const original: MCPConfig = { mcpServers: { @@ -397,6 +430,8 @@ describe("serverEntriesToMcpConfig", () => { taskTtl: 60000, // Absent autoRefreshOnListChanged on disk → false in memory (for the form) autoRefreshOnListChanged: false, + // Absent paginatedLists on disk → false in memory (for the form) + paginatedLists: false, // Absent maxFetchRequests on disk → product default in memory (for the form) maxFetchRequests: 1000, // Absent roots on disk → empty list in memory (for the form) diff --git a/clients/web/src/test/core/mcp/state/managedPromptsState.test.ts b/clients/web/src/test/core/mcp/state/managedPromptsState.test.ts index 57306820..17c1113f 100644 --- a/clients/web/src/test/core/mcp/state/managedPromptsState.test.ts +++ b/clients/web/src/test/core/mcp/state/managedPromptsState.test.ts @@ -35,7 +35,7 @@ describe("ManagedPromptsState", () => { beforeEach(() => { // Default to a server that advertises `prompts` so the existing flow tests - // exercise the live `listPrompts` path; capability-absent tests below + // exercise the live `listAllPrompts` path; capability-absent tests below // override this. client = new FakeInspectorClient({ capabilities: { prompts: {} } }); state = new ManagedPromptsState(client, 0); @@ -51,13 +51,13 @@ describe("ManagedPromptsState", () => { expect(a).not.toBe(b); }); - it("refresh returns early and does not call listPrompts when disconnected", async () => { + it("refresh returns early and does not call listAllPrompts when disconnected", async () => { const result = await state.refresh(); expect(result).toEqual([]); - expect(client.listPrompts).not.toHaveBeenCalled(); + expect(client.listAllPrompts).not.toHaveBeenCalled(); }); - it("refresh skips listPrompts when the server doesn't advertise prompts capability", async () => { + it("refresh skips listAllPrompts when the server doesn't advertise prompts capability", async () => { // Regression (#1350): a prompts-less server replied to prompts/list with // -32601 "Method not found", surfacing in the console on every connect. const promptless = new FakeInspectorClient({ @@ -68,10 +68,10 @@ describe("ManagedPromptsState", () => { const result = await promptlessState.refresh(); expect(result).toEqual([]); - expect(promptless.listPrompts).not.toHaveBeenCalled(); + expect(promptless.listAllPrompts).not.toHaveBeenCalled(); }); - it("connect against a prompts-less server doesn't fire listPrompts", async () => { + it("connect against a prompts-less server doesn't fire listAllPrompts", async () => { // The connect event runs refresh; the capability gate must also catch it // there, not only the publicly-callable refresh(). const promptless = new FakeInspectorClient({ capabilities: { tools: {} } }); @@ -81,11 +81,11 @@ describe("ManagedPromptsState", () => { const changePromise = waitForPromptsChange(promptlessState); promptless.dispatchTypedEvent("connect"); await changePromise; - expect(promptless.listPrompts).not.toHaveBeenCalled(); + expect(promptless.listAllPrompts).not.toHaveBeenCalled(); expect(promptlessState.getPrompts()).toEqual([]); }); - it("refresh fetches a single page and dispatches promptsChange", async () => { + it("refresh fetches the full list and dispatches promptsChange", async () => { client.setStatus("connected"); client.queuePromptPages({ prompts: [prompt("a"), prompt("b")] }); @@ -97,7 +97,9 @@ describe("ManagedPromptsState", () => { expect(state.getPrompts().map((p) => p.name)).toEqual(["a", "b"]); }); - it("refresh accumulates across multiple paginated pages", async () => { + it("refresh delegates all-page aggregation to listAllPrompts (one call)", async () => { + // The SDK's high-level verb walks every page; the managed state makes a + // single `listAllPrompts` call rather than looping single pages itself. client.setStatus("connected"); client.queuePromptPages( { prompts: [prompt("a")], nextCursor: "c1" }, @@ -107,15 +109,18 @@ describe("ManagedPromptsState", () => { const result = await state.refresh(); expect(result.map((p) => p.name)).toEqual(["a", "b", "c"]); - expect(client.listPrompts).toHaveBeenCalledTimes(3); + expect(client.listAllPrompts).toHaveBeenCalledTimes(1); }); - it("refresh passes setMetadata-supplied metadata to listPrompts", async () => { + it("refresh passes setMetadata-supplied metadata to listAllPrompts", async () => { client.setStatus("connected"); state.setMetadata({ k: "v" }); client.queuePromptPages({ prompts: [prompt("a")] }); await state.refresh(); - expect(client.listPrompts).toHaveBeenCalledWith(undefined, { k: "v" }); + expect(client.listAllPrompts).toHaveBeenCalledWith({ + cacheMode: undefined, + metadata: { k: "v" }, + }); }); it("refresh argument overrides setMetadata", async () => { @@ -123,8 +128,9 @@ describe("ManagedPromptsState", () => { state.setMetadata({ k: "default" }); client.queuePromptPages({ prompts: [prompt("a")] }); await state.refresh({ k: "override" }); - expect(client.listPrompts).toHaveBeenCalledWith(undefined, { - k: "override", + expect(client.listAllPrompts).toHaveBeenCalledWith({ + cacheMode: undefined, + metadata: { k: "override" }, }); }); @@ -144,11 +150,11 @@ describe("ManagedPromptsState", () => { const changed = waitForListChanged(state); client.dispatchTypedEvent("promptsListChanged"); expect(await changed).toBe(true); - expect(client.listPrompts).not.toHaveBeenCalled(); // no automatic fetch + expect(client.listAllPrompts).not.toHaveBeenCalled(); // no automatic fetch expect(state.getPrompts()).toEqual([]); // displayed list untouched }); - it("promptsListChanged auto-refreshes when the server opts in", async () => { + it("promptsListChanged auto-refreshes (cacheMode:refresh) when the server opts in", async () => { const autoClient = new FakeInspectorClient({ capabilities: { prompts: {} }, serverSettings: AUTO_REFRESH_SETTINGS, @@ -159,7 +165,11 @@ describe("ManagedPromptsState", () => { const changed = waitForPromptsChange(autoState); autoClient.dispatchTypedEvent("promptsListChanged"); expect((await changed).map((p) => p.name)).toEqual(["a"]); - expect(autoClient.listPrompts).toHaveBeenCalled(); + // A list_changed means the prior list is stale → bypass the cache. + expect(autoClient.listAllPrompts).toHaveBeenCalledWith({ + cacheMode: "refresh", + metadata: undefined, + }); }); it("statusChange to disconnected clears prompts and dispatches promptsChange", async () => { @@ -196,13 +206,16 @@ describe("ManagedPromptsState", () => { expect(state.getPrompts().map((p) => p.name)).toEqual(["a"]); }); - it("throws when pagination exceeds 100 pages", async () => { + it("refresh forwards an explicit cacheMode to listAllPrompts", async () => { + // A user-initiated refresh (via the hook) passes cacheMode:"refresh" to + // force a cache-bypassing round trip on modern servers (#1721). client.setStatus("connected"); - client.listPrompts.mockImplementation(async () => ({ - prompts: [prompt("a")], - nextCursor: "always", - })); - await expect(state.refresh()).rejects.toThrow(/Maximum pagination limit/); + client.queuePromptPages({ prompts: [prompt("a")] }); + await state.refresh(undefined, "refresh"); + expect(client.listAllPrompts).toHaveBeenCalledWith({ + cacheMode: "refresh", + metadata: undefined, + }); }); it("destroy unsubscribes from client events and clears state", async () => { diff --git a/clients/web/src/test/core/mcp/state/managedResourceTemplatesState.test.ts b/clients/web/src/test/core/mcp/state/managedResourceTemplatesState.test.ts index 8aaae543..c4850760 100644 --- a/clients/web/src/test/core/mcp/state/managedResourceTemplatesState.test.ts +++ b/clients/web/src/test/core/mcp/state/managedResourceTemplatesState.test.ts @@ -33,7 +33,7 @@ describe("ManagedResourceTemplatesState", () => { beforeEach(() => { // Default to a server that advertises `resources` so the existing flow - // tests exercise the live `listResourceTemplates` path; capability-absent + // tests exercise the live `listAllResourceTemplates` path; capability-absent // tests below override this. (Templates are gated on the `resources` // capability — the spec defines no separate `resourceTemplates` one.) client = new FakeInspectorClient({ capabilities: { resources: {} } }); @@ -50,13 +50,13 @@ describe("ManagedResourceTemplatesState", () => { expect(a).not.toBe(b); }); - it("refresh returns early and does not call listResourceTemplates when disconnected", async () => { + it("refresh returns early and does not call listAllResourceTemplates when disconnected", async () => { const result = await state.refresh(); expect(result).toEqual([]); - expect(client.listResourceTemplates).not.toHaveBeenCalled(); + expect(client.listAllResourceTemplates).not.toHaveBeenCalled(); }); - it("refresh skips listResourceTemplates when the server doesn't advertise resources capability", async () => { + it("refresh skips listAllResourceTemplates when the server doesn't advertise resources capability", async () => { // Regression (#1350): templates are part of the resources surface, so a // resources-less server replied to resources/templates/list with -32601 // "Method not found", surfacing in the console on every connect. @@ -71,10 +71,10 @@ describe("ManagedResourceTemplatesState", () => { const result = await resourcelessState.refresh(); expect(result).toEqual([]); - expect(resourceless.listResourceTemplates).not.toHaveBeenCalled(); + expect(resourceless.listAllResourceTemplates).not.toHaveBeenCalled(); }); - it("connect against a resources-less server doesn't fire listResourceTemplates", async () => { + it("connect against a resources-less server doesn't fire listAllResourceTemplates", async () => { // The connect event runs refresh; the capability gate must also catch it // there, not only the publicly-callable refresh(). const resourceless = new FakeInspectorClient({ @@ -89,11 +89,11 @@ describe("ManagedResourceTemplatesState", () => { const changePromise = waitForChange(resourcelessState); resourceless.dispatchTypedEvent("connect"); await changePromise; - expect(resourceless.listResourceTemplates).not.toHaveBeenCalled(); + expect(resourceless.listAllResourceTemplates).not.toHaveBeenCalled(); expect(resourcelessState.getResourceTemplates()).toEqual([]); }); - it("refresh fetches a single page and dispatches resourceTemplatesChange", async () => { + it("refresh fetches the full list and dispatches resourceTemplatesChange", async () => { client.setStatus("connected"); client.queueResourceTemplatePages({ resourceTemplates: [template("a"), template("b")], @@ -107,7 +107,9 @@ describe("ManagedResourceTemplatesState", () => { expect(state.getResourceTemplates().map((t) => t.name)).toEqual(["a", "b"]); }); - it("refresh accumulates across multiple paginated pages", async () => { + it("refresh delegates all-page aggregation to listAllResourceTemplates (one call)", async () => { + // The SDK's high-level verb walks every page; the managed state makes a + // single `listAllResourceTemplates` call rather than looping single pages itself. client.setStatus("connected"); client.queueResourceTemplatePages( { resourceTemplates: [template("a")], nextCursor: "c1" }, @@ -117,7 +119,7 @@ describe("ManagedResourceTemplatesState", () => { const result = await state.refresh(); expect(result.map((t) => t.name)).toEqual(["a", "b", "c"]); - expect(client.listResourceTemplates).toHaveBeenCalledTimes(3); + expect(client.listAllResourceTemplates).toHaveBeenCalledTimes(1); }); it("refresh passes setMetadata-supplied metadata", async () => { @@ -125,8 +127,9 @@ describe("ManagedResourceTemplatesState", () => { state.setMetadata({ k: "v" }); client.queueResourceTemplatePages({ resourceTemplates: [template("a")] }); await state.refresh(); - expect(client.listResourceTemplates).toHaveBeenCalledWith(undefined, { - k: "v", + expect(client.listAllResourceTemplates).toHaveBeenCalledWith({ + cacheMode: undefined, + metadata: { k: "v" }, }); }); @@ -135,8 +138,9 @@ describe("ManagedResourceTemplatesState", () => { state.setMetadata({ k: "default" }); client.queueResourceTemplatePages({ resourceTemplates: [template("a")] }); await state.refresh({ k: "override" }); - expect(client.listResourceTemplates).toHaveBeenCalledWith(undefined, { - k: "override", + expect(client.listAllResourceTemplates).toHaveBeenCalledWith({ + cacheMode: undefined, + metadata: { k: "override" }, }); }); @@ -149,6 +153,25 @@ describe("ManagedResourceTemplatesState", () => { expect(next.map((t) => t.name)).toEqual(["a"]); }); + it("STILL loads on connect in paginated mode (no paged counterpart, #1721)", async () => { + // Unlike tools/prompts/resources, resource templates have no paged + // counterpart, so they must aggregate on connect even when + // `paginatedLists` is on — otherwise the list would be empty until a + // manual refresh (deferWhenPaginated: false). + const spClient = new FakeInspectorClient({ + capabilities: { resources: {} }, + serverSettings: { ...AUTO_REFRESH_SETTINGS, paginatedLists: true }, + }); + spClient.setStatus("connected"); + const spState = new ManagedResourceTemplatesState(spClient, 0); + spClient.queueResourceTemplatePages({ resourceTemplates: [template("a")] }); + const changePromise = waitForChange(spState); + spClient.dispatchTypedEvent("connect"); + expect((await changePromise).map((t) => t.name)).toEqual(["a"]); + expect(spClient.listAllResourceTemplates).toHaveBeenCalled(); + spState.destroy(); + }); + it("resourceTemplatesListChanged does NOT auto-refresh by default (refreshed via the Resources Refresh)", async () => { client.setStatus("connected"); client.queueResourceTemplatePages({ @@ -158,11 +181,11 @@ describe("ManagedResourceTemplatesState", () => { // Yield so a stray refresh would have landed. await Promise.resolve(); await Promise.resolve(); - expect(client.listResourceTemplates).not.toHaveBeenCalled(); + expect(client.listAllResourceTemplates).not.toHaveBeenCalled(); expect(state.getResourceTemplates()).toEqual([]); }); - it("resourceTemplatesListChanged auto-refreshes when the server opts in", async () => { + it("resourceTemplatesListChanged auto-refreshes (cacheMode:refresh) when the server opts in", async () => { const autoClient = new FakeInspectorClient({ capabilities: { resources: {} }, serverSettings: AUTO_REFRESH_SETTINGS, @@ -175,7 +198,11 @@ describe("ManagedResourceTemplatesState", () => { const changed = waitForChange(autoState); autoClient.dispatchTypedEvent("resourceTemplatesListChanged"); expect((await changed).map((t) => t.name)).toEqual(["a"]); - expect(autoClient.listResourceTemplates).toHaveBeenCalled(); + // A list_changed means the prior list is stale → bypass the cache. + expect(autoClient.listAllResourceTemplates).toHaveBeenCalledWith({ + cacheMode: "refresh", + metadata: undefined, + }); }); it("statusChange to disconnected clears templates and dispatches change", async () => { @@ -212,13 +239,16 @@ describe("ManagedResourceTemplatesState", () => { expect(state.getResourceTemplates().map((t) => t.name)).toEqual(["a"]); }); - it("throws when pagination exceeds 100 pages", async () => { + it("refresh forwards an explicit cacheMode to listAllResourceTemplates", async () => { + // A user-initiated refresh (via the hook) passes cacheMode:"refresh" to + // force a cache-bypassing round trip on modern servers (#1721). client.setStatus("connected"); - client.listResourceTemplates.mockImplementation(async () => ({ - resourceTemplates: [template("a")], - nextCursor: "always", - })); - await expect(state.refresh()).rejects.toThrow(/Maximum pagination limit/); + client.queueResourceTemplatePages({ resourceTemplates: [template("a")] }); + await state.refresh(undefined, "refresh"); + expect(client.listAllResourceTemplates).toHaveBeenCalledWith({ + cacheMode: "refresh", + metadata: undefined, + }); }); it("destroy unsubscribes from client events and clears state", async () => { diff --git a/clients/web/src/test/core/mcp/state/managedResourcesState.test.ts b/clients/web/src/test/core/mcp/state/managedResourcesState.test.ts index d0258617..3e82467c 100644 --- a/clients/web/src/test/core/mcp/state/managedResourcesState.test.ts +++ b/clients/web/src/test/core/mcp/state/managedResourcesState.test.ts @@ -37,7 +37,7 @@ describe("ManagedResourcesState", () => { beforeEach(() => { // Default to a server that advertises `resources` so the existing flow - // tests exercise the live `listResources` path; capability-absent tests + // tests exercise the live `listAllResources` path; capability-absent tests // below override this. client = new FakeInspectorClient({ capabilities: { resources: {} } }); state = new ManagedResourcesState(client, 0); @@ -53,13 +53,13 @@ describe("ManagedResourcesState", () => { expect(a).not.toBe(b); }); - it("refresh returns early and does not call listResources when disconnected", async () => { + it("refresh returns early and does not call listAllResources when disconnected", async () => { const result = await state.refresh(); expect(result).toEqual([]); - expect(client.listResources).not.toHaveBeenCalled(); + expect(client.listAllResources).not.toHaveBeenCalled(); }); - it("refresh skips listResources when the server doesn't advertise resources capability", async () => { + it("refresh skips listAllResources when the server doesn't advertise resources capability", async () => { // Regression (#1350): a resources-less server replied to resources/list // with -32601 "Method not found", surfacing in the console on every // connect. @@ -71,10 +71,10 @@ describe("ManagedResourcesState", () => { const result = await resourcelessState.refresh(); expect(result).toEqual([]); - expect(resourceless.listResources).not.toHaveBeenCalled(); + expect(resourceless.listAllResources).not.toHaveBeenCalled(); }); - it("connect against a resources-less server doesn't fire listResources", async () => { + it("connect against a resources-less server doesn't fire listAllResources", async () => { // The connect event runs refresh; the capability gate must also catch it // there, not only the publicly-callable refresh(). const resourceless = new FakeInspectorClient({ @@ -86,11 +86,11 @@ describe("ManagedResourcesState", () => { const changePromise = waitForResourcesChange(resourcelessState); resourceless.dispatchTypedEvent("connect"); await changePromise; - expect(resourceless.listResources).not.toHaveBeenCalled(); + expect(resourceless.listAllResources).not.toHaveBeenCalled(); expect(resourcelessState.getResources()).toEqual([]); }); - it("refresh fetches a single page and dispatches resourcesChange", async () => { + it("refresh fetches the full list and dispatches resourcesChange", async () => { client.setStatus("connected"); client.queueResourcePages({ resources: [resource("a://1"), resource("a://2")], @@ -104,7 +104,9 @@ describe("ManagedResourcesState", () => { expect(state.getResources().map((r) => r.uri)).toEqual(["a://1", "a://2"]); }); - it("refresh accumulates across multiple paginated pages", async () => { + it("refresh delegates all-page aggregation to listAllResources (one call)", async () => { + // The SDK's high-level verb walks every page; the managed state makes a + // single `listAllResources` call rather than looping single pages itself. client.setStatus("connected"); client.queueResourcePages( { resources: [resource("a://1")], nextCursor: "c1" }, @@ -114,15 +116,18 @@ describe("ManagedResourcesState", () => { const result = await state.refresh(); expect(result.map((r) => r.uri)).toEqual(["a://1", "a://2", "a://3"]); - expect(client.listResources).toHaveBeenCalledTimes(3); + expect(client.listAllResources).toHaveBeenCalledTimes(1); }); - it("refresh passes setMetadata-supplied metadata to listResources", async () => { + it("refresh passes setMetadata-supplied metadata to listAllResources", async () => { client.setStatus("connected"); state.setMetadata({ k: "v" }); client.queueResourcePages({ resources: [resource("a://1")] }); await state.refresh(); - expect(client.listResources).toHaveBeenCalledWith(undefined, { k: "v" }); + expect(client.listAllResources).toHaveBeenCalledWith({ + cacheMode: undefined, + metadata: { k: "v" }, + }); }); it("refresh argument overrides setMetadata", async () => { @@ -130,8 +135,9 @@ describe("ManagedResourcesState", () => { state.setMetadata({ k: "default" }); client.queueResourcePages({ resources: [resource("a://1")] }); await state.refresh({ k: "override" }); - expect(client.listResources).toHaveBeenCalledWith(undefined, { - k: "override", + expect(client.listAllResources).toHaveBeenCalledWith({ + cacheMode: undefined, + metadata: { k: "override" }, }); }); @@ -151,11 +157,11 @@ describe("ManagedResourcesState", () => { const changed = waitForListChanged(state); client.dispatchTypedEvent("resourcesListChanged"); expect(await changed).toBe(true); - expect(client.listResources).not.toHaveBeenCalled(); // no automatic fetch + expect(client.listAllResources).not.toHaveBeenCalled(); // no automatic fetch expect(state.getResources()).toEqual([]); // displayed list untouched }); - it("resourcesListChanged auto-refreshes when the server opts in", async () => { + it("resourcesListChanged auto-refreshes (cacheMode:refresh) when the server opts in", async () => { const autoClient = new FakeInspectorClient({ capabilities: { resources: {} }, serverSettings: AUTO_REFRESH_SETTINGS, @@ -166,7 +172,11 @@ describe("ManagedResourcesState", () => { const changed = waitForResourcesChange(autoState); autoClient.dispatchTypedEvent("resourcesListChanged"); expect((await changed).map((r) => r.uri)).toEqual(["a://1"]); - expect(autoClient.listResources).toHaveBeenCalled(); + // A list_changed means the prior list is stale → bypass the cache. + expect(autoClient.listAllResources).toHaveBeenCalledWith({ + cacheMode: "refresh", + metadata: undefined, + }); }); it("statusChange to disconnected clears resources and dispatches resourcesChange", async () => { @@ -203,13 +213,16 @@ describe("ManagedResourcesState", () => { expect(state.getResources().map((r) => r.uri)).toEqual(["a://1"]); }); - it("throws when pagination exceeds 100 pages", async () => { + it("refresh forwards an explicit cacheMode to listAllResources", async () => { + // A user-initiated refresh (via the hook) passes cacheMode:"refresh" to + // force a cache-bypassing round trip on modern servers (#1721). client.setStatus("connected"); - client.listResources.mockImplementation(async () => ({ - resources: [resource("a://1")], - nextCursor: "always", - })); - await expect(state.refresh()).rejects.toThrow(/Maximum pagination limit/); + client.queueResourcePages({ resources: [resource("a://1")] }); + await state.refresh(undefined, "refresh"); + expect(client.listAllResources).toHaveBeenCalledWith({ + cacheMode: "refresh", + metadata: undefined, + }); }); it("destroy unsubscribes from client events and clears state", async () => { diff --git a/clients/web/src/test/core/mcp/state/managedToolsState.test.ts b/clients/web/src/test/core/mcp/state/managedToolsState.test.ts index 1ff01acf..449c1b24 100644 --- a/clients/web/src/test/core/mcp/state/managedToolsState.test.ts +++ b/clients/web/src/test/core/mcp/state/managedToolsState.test.ts @@ -21,6 +21,13 @@ const AUTO_REFRESH_SETTINGS: InspectorServerSettings = { roots: [], }; +// Paginated mode on, plus auto-refresh on — paginated must win so the +// aggregate walk never runs (#1721). +const PAGINATED_SETTINGS: InspectorServerSettings = { + ...AUTO_REFRESH_SETTINGS, + paginatedLists: true, +}; + function waitForToolsChange(state: ManagedToolsState): Promise { return waitForChangeEvent(state, "toolsChange"); } @@ -35,7 +42,7 @@ describe("ManagedToolsState", () => { beforeEach(() => { // Default to a server that advertises `tools` so the existing flow tests - // exercise the live `listTools` path; capability-absent tests below + // exercise the live `listAllTools` path; capability-absent tests below // override this. client = new FakeInspectorClient({ capabilities: { tools: {} } }); state = new ManagedToolsState(client, 0); @@ -51,13 +58,13 @@ describe("ManagedToolsState", () => { expect(a).not.toBe(b); }); - it("refresh returns early and does not call listTools when disconnected", async () => { + it("refresh returns early and does not call listAllTools when disconnected", async () => { const result = await state.refresh(); expect(result).toEqual([]); - expect(client.listTools).not.toHaveBeenCalled(); + expect(client.listAllTools).not.toHaveBeenCalled(); }); - it("refresh skips listTools when the server doesn't advertise tools capability", async () => { + it("refresh skips listAllTools when the server doesn't advertise tools capability", async () => { // Regression (#1350): a tools-less server replied to tools/list with // -32601 "Method not found", surfacing in the console on every connect. const toolless = new FakeInspectorClient({ @@ -68,10 +75,10 @@ describe("ManagedToolsState", () => { const result = await toollessState.refresh(); expect(result).toEqual([]); - expect(toolless.listTools).not.toHaveBeenCalled(); + expect(toolless.listAllTools).not.toHaveBeenCalled(); }); - it("connect against a tools-less server doesn't fire listTools", async () => { + it("connect against a tools-less server doesn't fire listAllTools", async () => { // The connect event runs refresh; the capability gate must also catch it // there, not only the publicly-callable refresh(). const toolless = new FakeInspectorClient({ capabilities: { prompts: {} } }); @@ -81,11 +88,11 @@ describe("ManagedToolsState", () => { const changePromise = waitForToolsChange(toollessState); toolless.dispatchTypedEvent("connect"); await changePromise; - expect(toolless.listTools).not.toHaveBeenCalled(); + expect(toolless.listAllTools).not.toHaveBeenCalled(); expect(toollessState.getTools()).toEqual([]); }); - it("refresh fetches a single page and dispatches toolsChange", async () => { + it("refresh fetches the full list and dispatches toolsChange", async () => { client.setStatus("connected"); client.queueToolPages({ tools: [tool("a"), tool("b")] }); @@ -97,7 +104,9 @@ describe("ManagedToolsState", () => { expect(state.getTools().map((t) => t.name)).toEqual(["a", "b"]); }); - it("refresh accumulates across multiple paginated pages", async () => { + it("refresh delegates all-page aggregation to listAllTools (one call)", async () => { + // The SDK's high-level verb walks every page; the managed state makes a + // single `listAllTools` call rather than looping single pages itself. client.setStatus("connected"); client.queueToolPages( { tools: [tool("a")], nextCursor: "c1" }, @@ -107,15 +116,18 @@ describe("ManagedToolsState", () => { const result = await state.refresh(); expect(result.map((t) => t.name)).toEqual(["a", "b", "c"]); - expect(client.listTools).toHaveBeenCalledTimes(3); + expect(client.listAllTools).toHaveBeenCalledTimes(1); }); - it("refresh passes setMetadata-supplied metadata to listTools", async () => { + it("refresh passes setMetadata-supplied metadata to listAllTools", async () => { client.setStatus("connected"); state.setMetadata({ k: "v" }); client.queueToolPages({ tools: [tool("a")] }); await state.refresh(); - expect(client.listTools).toHaveBeenCalledWith(undefined, { k: "v" }); + expect(client.listAllTools).toHaveBeenCalledWith({ + cacheMode: undefined, + metadata: { k: "v" }, + }); }); it("refresh argument overrides setMetadata", async () => { @@ -123,7 +135,55 @@ describe("ManagedToolsState", () => { state.setMetadata({ k: "default" }); client.queueToolPages({ tools: [tool("a")] }); await state.refresh({ k: "override" }); - expect(client.listTools).toHaveBeenCalledWith(undefined, { k: "override" }); + expect(client.listAllTools).toHaveBeenCalledWith({ + cacheMode: undefined, + metadata: { k: "override" }, + }); + }); + + it("refresh forwards an explicit cacheMode to listAllTools", async () => { + // A user-initiated refresh (via the hook) passes cacheMode:"refresh" to + // force a cache-bypassing round trip on modern servers (#1721). + client.setStatus("connected"); + client.queueToolPages({ tools: [tool("a")] }); + await state.refresh(undefined, "refresh"); + expect(client.listAllTools).toHaveBeenCalledWith({ + cacheMode: "refresh", + metadata: undefined, + }); + }); + + it("connect does NOT fetch in paginated mode (#1721)", async () => { + // The aggregate list isn't the display source in paginated mode, so the + // connect-time all-page walk is skipped (the defensive point of the setting). + const spClient = new FakeInspectorClient({ + capabilities: { tools: {} }, + serverSettings: PAGINATED_SETTINGS, + }); + spClient.setStatus("connected"); + const spState = new ManagedToolsState(spClient, 0); + spClient.queueToolPages({ tools: [tool("a")] }); + spClient.dispatchTypedEvent("connect"); + await new Promise((r) => setTimeout(r, 0)); + expect(spClient.listAllTools).not.toHaveBeenCalled(); + expect(spState.getTools()).toEqual([]); + spState.destroy(); + }); + + it("list_changed only lights the indicator in paginated mode, never aggregates (#1721)", async () => { + // Paginated wins over autoRefreshOnListChanged: the indicator lights but + // the aggregate walk never runs. + const spClient = new FakeInspectorClient({ + capabilities: { tools: {} }, + serverSettings: PAGINATED_SETTINGS, + }); + spClient.setStatus("connected"); + const spState = new ManagedToolsState(spClient, 0); + const changed = waitForListChanged(spState); + spClient.dispatchTypedEvent("toolsListChanged"); + expect(await changed).toBe(true); + expect(spClient.listAllTools).not.toHaveBeenCalled(); + spState.destroy(); }); it("connect event triggers a refresh", async () => { @@ -142,7 +202,7 @@ describe("ManagedToolsState", () => { const changed = waitForListChanged(state); client.dispatchTypedEvent("toolsListChanged"); expect(await changed).toBe(true); - expect(client.listTools).not.toHaveBeenCalled(); // no automatic fetch + expect(client.listAllTools).not.toHaveBeenCalled(); // no automatic fetch expect(state.getTools()).toEqual([]); // displayed list untouched }); @@ -160,7 +220,7 @@ describe("ManagedToolsState", () => { await new Promise((r) => setTimeout(r, 0)); expect(fired).toBe(1); // one debounced flip expect(state.getListChanged()).toBe(true); - expect(client.listTools).not.toHaveBeenCalled(); + expect(client.listAllTools).not.toHaveBeenCalled(); }); it("coalesces an auto-refresh that fires while an earlier one is still fetching (#1444)", async () => { @@ -173,7 +233,7 @@ describe("ManagedToolsState", () => { autoClient.setStatus("connected"); const autoState = new ManagedToolsState(autoClient, 0); let release: (value: { tools: Tool[] }) => void = () => {}; - autoClient.listTools.mockImplementationOnce( + autoClient.listAllTools.mockImplementationOnce( () => new Promise<{ tools: Tool[] }>((resolve) => { release = resolve; @@ -183,20 +243,20 @@ describe("ManagedToolsState", () => { autoClient.dispatchTypedEvent("toolsListChanged"); await new Promise((r) => setTimeout(r, 0)); - expect(autoClient.listTools).toHaveBeenCalledTimes(1); + expect(autoClient.listAllTools).toHaveBeenCalledTimes(1); autoClient.dispatchTypedEvent("toolsListChanged"); await new Promise((r) => setTimeout(r, 0)); - expect(autoClient.listTools).toHaveBeenCalledTimes(1); // coalesced + expect(autoClient.listAllTools).toHaveBeenCalledTimes(1); // coalesced release({ tools: [tool("a")] }); await new Promise((r) => setTimeout(r, 0)); - expect(autoClient.listTools).toHaveBeenCalledTimes(2); + expect(autoClient.listAllTools).toHaveBeenCalledTimes(2); // The (coalesced) auto-refresh applied the new list. expect(autoState.getTools().map((t) => t.name)).toEqual(["a"]); }); - it("toolsListChanged auto-refreshes when the server opts in", async () => { + it("toolsListChanged auto-refreshes (cacheMode:refresh) when the server opts in", async () => { const autoClient = new FakeInspectorClient({ capabilities: { tools: {} }, serverSettings: AUTO_REFRESH_SETTINGS, @@ -207,7 +267,11 @@ describe("ManagedToolsState", () => { const changed = waitForToolsChange(autoState); autoClient.dispatchTypedEvent("toolsListChanged"); expect((await changed).map((t) => t.name)).toEqual(["a"]); - expect(autoClient.listTools).toHaveBeenCalled(); + // A list_changed means the prior list is stale → bypass the cache. + expect(autoClient.listAllTools).toHaveBeenCalledWith({ + cacheMode: "refresh", + metadata: undefined, + }); }); it("honors a live setServerSettings toggle without a reconnect (#1444)", async () => { @@ -256,14 +320,27 @@ describe("ManagedToolsState", () => { expect(state.getTools().map((t) => t.name)).toEqual(["a"]); }); - it("throws when pagination exceeds 100 pages", async () => { + it("clears a pending list_changed debounce timer when the connection goes terminal", async () => { + // A non-zero debounce leaves the timer pending; a terminal statusChange must + // clear it so no stale indicator/fetch fires after disconnect. client.setStatus("connected"); - // Always returns a non-terminal cursor; refresh should bail out at the cap. - client.listTools.mockImplementation(async () => ({ - tools: [tool("a")], - nextCursor: "always", - })); - await expect(state.refresh()).rejects.toThrow(/Maximum pagination limit/); + const debounced = new ManagedToolsState(client, 1000); + client.dispatchTypedEvent("toolsListChanged"); // schedules the debounce timer + client.setStatus("disconnected"); // terminal → clears the pending timer + await new Promise((r) => setTimeout(r, 5)); + expect(debounced.getListChanged()).toBe(false); + debounced.destroy(); + }); + + it("clears a pending list_changed debounce timer on destroy", async () => { + client.setStatus("connected"); + const debounced = new ManagedToolsState(client, 1000); + client.dispatchTypedEvent("toolsListChanged"); // schedules the debounce timer + debounced.destroy(); // unsubscribe clears the pending timer + await new Promise((r) => setTimeout(r, 5)); + // No indicator lit and the list stays empty — the timer never fired. + expect(debounced.getListChanged()).toBe(false); + expect(debounced.getTools()).toEqual([]); }); it("destroy unsubscribes from client events and clears state", async () => { diff --git a/clients/web/src/test/core/mcp/state/pagedPromptsState.test.ts b/clients/web/src/test/core/mcp/state/pagedPromptsState.test.ts index 9df6895c..40584cf9 100644 --- a/clients/web/src/test/core/mcp/state/pagedPromptsState.test.ts +++ b/clients/web/src/test/core/mcp/state/pagedPromptsState.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, beforeEach } from "vitest"; import type { Prompt } from "@modelcontextprotocol/client"; +import type { InspectorServerSettings } from "@inspector/core/mcp/types.js"; import { PagedPromptsState } from "@inspector/core/mcp/state/pagedPromptsState"; import { FakeInspectorClient } from "@inspector/core/mcp/__tests__/fakeInspectorClient"; @@ -7,6 +8,18 @@ function prompt(name: string): Prompt { return { name }; } +const PAGINATED_SETTINGS: InspectorServerSettings = { + headers: [], + env: [], + metadata: [], + connectionTimeout: 0, + requestTimeout: 0, + taskTtl: 60000, + maxFetchRequests: 1000, + roots: [], + paginatedLists: true, +}; + function waitForChange(state: PagedPromptsState): Promise { return new Promise((resolve) => { state.addEventListener("promptsChange", (e) => resolve(e.detail), { @@ -111,4 +124,81 @@ describe("PagedPromptsState", () => { state.destroy(); expect(() => state.destroy()).not.toThrow(); }); + + it("ignores a concurrent loadPage (double-click guard, #1721)", async () => { + client.setStatus("connected"); + client.queuePromptPages( + { prompts: [prompt("a")], nextCursor: "c1" }, + { prompts: [prompt("b")], nextCursor: "c2" }, + ); + await state.loadPage(); + const [r1, r2] = await Promise.all([ + state.loadPage("c1"), + state.loadPage("c1"), + ]); + expect(r1.prompts.map((p) => p.name)).toEqual(["b"]); + expect(r2.prompts).toEqual([]); + expect(r2.nextCursor).toBe("c1"); + expect(state.getPrompts().map((p) => p.name)).toEqual(["a", "b"]); + expect(state.getPagination().pageCount).toBe(2); + }); + + describe("pagination progress + connect auto-load (#1721)", () => { + it("tracks nextCursor/page count and dispatches paginationChange", async () => { + client.setStatus("connected"); + client.queuePromptPages( + { prompts: [prompt("a")], nextCursor: "c1" }, + { prompts: [prompt("b")] }, + ); + const onLoad = new Promise<{ nextCursor?: string; pageCount: number }>( + (resolve) => { + state.addEventListener("paginationChange", (e) => resolve(e.detail), { + once: true, + }); + }, + ); + await state.loadPage(); + expect(await onLoad).toEqual({ nextCursor: "c1", pageCount: 1 }); + await state.loadPage("c1"); + expect(state.getPagination()).toEqual({ + nextCursor: undefined, + pageCount: 2, + }); + }); + + it("resets pagination on disconnect and clear", async () => { + client.setStatus("connected"); + client.queuePromptPages({ prompts: [prompt("a")], nextCursor: "c1" }); + await state.loadPage(); + state.clear(); + expect(state.getPagination()).toEqual({ + nextCursor: undefined, + pageCount: 0, + }); + await state.loadPage(); + client.setStatus("disconnected"); + expect(state.getPagination()).toEqual({ + nextCursor: undefined, + pageCount: 0, + }); + }); + + it("loads page 1 on connect in paginated mode, not otherwise", async () => { + const spClient = new FakeInspectorClient({ + serverSettings: PAGINATED_SETTINGS, + }); + spClient.setStatus("connected"); + const spState = new PagedPromptsState(spClient); + spClient.queuePromptPages({ prompts: [prompt("a")] }); + const changed = waitForChange(spState); + spClient.dispatchTypedEvent("connect"); + expect((await changed).map((p) => p.name)).toEqual(["a"]); + spState.destroy(); + + client.setStatus("connected"); + client.dispatchTypedEvent("connect"); + await Promise.resolve(); + expect(client.listPrompts).not.toHaveBeenCalled(); + }); + }); }); diff --git a/clients/web/src/test/core/mcp/state/pagedResourcesState.test.ts b/clients/web/src/test/core/mcp/state/pagedResourcesState.test.ts index 5a8dbec4..c60d75a3 100644 --- a/clients/web/src/test/core/mcp/state/pagedResourcesState.test.ts +++ b/clients/web/src/test/core/mcp/state/pagedResourcesState.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, beforeEach } from "vitest"; import type { Resource } from "@modelcontextprotocol/client"; +import type { InspectorServerSettings } from "@inspector/core/mcp/types.js"; import { PagedResourcesState } from "@inspector/core/mcp/state/pagedResourcesState"; import { FakeInspectorClient } from "@inspector/core/mcp/__tests__/fakeInspectorClient"; @@ -7,6 +8,18 @@ function resource(uri: string): Resource { return { uri, name: uri }; } +const PAGINATED_SETTINGS: InspectorServerSettings = { + headers: [], + env: [], + metadata: [], + connectionTimeout: 0, + requestTimeout: 0, + taskTtl: 60000, + maxFetchRequests: 1000, + roots: [], + paginatedLists: true, +}; + function waitForChange(state: PagedResourcesState): Promise { return new Promise((resolve) => { state.addEventListener("resourcesChange", (e) => resolve(e.detail), { @@ -113,4 +126,84 @@ describe("PagedResourcesState", () => { state.destroy(); expect(() => state.destroy()).not.toThrow(); }); + + it("ignores a concurrent loadPage (double-click guard, #1721)", async () => { + client.setStatus("connected"); + client.queueResourcePages( + { resources: [resource("a://1")], nextCursor: "c1" }, + { resources: [resource("a://2")], nextCursor: "c2" }, + ); + await state.loadPage(); + const [r1, r2] = await Promise.all([ + state.loadPage("c1"), + state.loadPage("c1"), + ]); + expect(r1.resources.map((r) => r.uri)).toEqual(["a://2"]); + expect(r2.resources).toEqual([]); + expect(r2.nextCursor).toBe("c1"); + expect(state.getResources().map((r) => r.uri)).toEqual(["a://1", "a://2"]); + expect(state.getPagination().pageCount).toBe(2); + }); + + describe("pagination progress + connect auto-load (#1721)", () => { + it("tracks nextCursor/page count and dispatches paginationChange", async () => { + client.setStatus("connected"); + client.queueResourcePages( + { resources: [resource("a://1")], nextCursor: "c1" }, + { resources: [resource("a://2")] }, + ); + const onLoad = new Promise<{ nextCursor?: string; pageCount: number }>( + (resolve) => { + state.addEventListener("paginationChange", (e) => resolve(e.detail), { + once: true, + }); + }, + ); + await state.loadPage(); + expect(await onLoad).toEqual({ nextCursor: "c1", pageCount: 1 }); + await state.loadPage("c1"); + expect(state.getPagination()).toEqual({ + nextCursor: undefined, + pageCount: 2, + }); + }); + + it("resets pagination on disconnect and clear", async () => { + client.setStatus("connected"); + client.queueResourcePages({ + resources: [resource("a://1")], + nextCursor: "c1", + }); + await state.loadPage(); + state.clear(); + expect(state.getPagination()).toEqual({ + nextCursor: undefined, + pageCount: 0, + }); + await state.loadPage(); + client.setStatus("disconnected"); + expect(state.getPagination()).toEqual({ + nextCursor: undefined, + pageCount: 0, + }); + }); + + it("loads page 1 on connect in paginated mode, not otherwise", async () => { + const spClient = new FakeInspectorClient({ + serverSettings: PAGINATED_SETTINGS, + }); + spClient.setStatus("connected"); + const spState = new PagedResourcesState(spClient); + spClient.queueResourcePages({ resources: [resource("a://1")] }); + const changed = waitForChange(spState); + spClient.dispatchTypedEvent("connect"); + expect((await changed).map((r) => r.uri)).toEqual(["a://1"]); + spState.destroy(); + + client.setStatus("connected"); + client.dispatchTypedEvent("connect"); + await Promise.resolve(); + expect(client.listResources).not.toHaveBeenCalled(); + }); + }); }); diff --git a/clients/web/src/test/core/mcp/state/pagedToolsState.test.ts b/clients/web/src/test/core/mcp/state/pagedToolsState.test.ts index 23c31af7..fb1509d5 100644 --- a/clients/web/src/test/core/mcp/state/pagedToolsState.test.ts +++ b/clients/web/src/test/core/mcp/state/pagedToolsState.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect, beforeEach } from "vitest"; import type { Tool } from "@modelcontextprotocol/client"; +import type { InspectorServerSettings } from "@inspector/core/mcp/types.js"; import { PagedToolsState } from "@inspector/core/mcp/state/pagedToolsState"; import { FakeInspectorClient } from "@inspector/core/mcp/__tests__/fakeInspectorClient"; @@ -7,6 +8,18 @@ function tool(name: string): Tool { return { name, inputSchema: { type: "object" } }; } +const PAGINATED_SETTINGS: InspectorServerSettings = { + headers: [], + env: [], + metadata: [], + connectionTimeout: 0, + requestTimeout: 0, + taskTtl: 60000, + maxFetchRequests: 1000, + roots: [], + paginatedLists: true, +}; + function waitForChange(state: PagedToolsState): Promise { return new Promise((resolve) => { state.addEventListener("toolsChange", (e) => resolve(e.detail), { @@ -120,4 +133,109 @@ describe("PagedToolsState", () => { state.destroy(); expect(() => state.destroy()).not.toThrow(); }); + + describe("pagination progress (#1721)", () => { + it("tracks nextCursor and page count across loads", async () => { + client.setStatus("connected"); + client.queueToolPages( + { tools: [tool("a")], nextCursor: "c1" }, + { tools: [tool("b")] }, + ); + expect(state.getPagination()).toEqual({ + nextCursor: undefined, + pageCount: 0, + }); + await state.loadPage(); + expect(state.getPagination()).toEqual({ nextCursor: "c1", pageCount: 1 }); + await state.loadPage("c1"); + expect(state.getPagination()).toEqual({ + nextCursor: undefined, + pageCount: 2, + }); + }); + + it("dispatches paginationChange on load and reset", async () => { + client.setStatus("connected"); + client.queueToolPages({ tools: [tool("a")], nextCursor: "c1" }); + const onLoad = new Promise<{ nextCursor?: string; pageCount: number }>( + (resolve) => { + state.addEventListener("paginationChange", (e) => resolve(e.detail), { + once: true, + }); + }, + ); + await state.loadPage(); + expect(await onLoad).toEqual({ nextCursor: "c1", pageCount: 1 }); + + const onClear = new Promise<{ nextCursor?: string; pageCount: number }>( + (resolve) => { + state.addEventListener("paginationChange", (e) => resolve(e.detail), { + once: true, + }); + }, + ); + state.clear(); + expect(await onClear).toEqual({ nextCursor: undefined, pageCount: 0 }); + }); + + it("resets pagination when the connection goes terminal", async () => { + client.setStatus("connected"); + client.queueToolPages({ tools: [tool("a")], nextCursor: "c1" }); + await state.loadPage(); + client.setStatus("disconnected"); + expect(state.getPagination()).toEqual({ + nextCursor: undefined, + pageCount: 0, + }); + }); + }); + + it("ignores a concurrent loadPage (double-click guard, #1721)", async () => { + // Two overlapping loadPage(cursor) calls must not both append the same + // page. The second (while the first is in flight) is a no-op that returns + // the current cursor. + client.setStatus("connected"); + client.queueToolPages( + { tools: [tool("a")], nextCursor: "c1" }, + { tools: [tool("b")], nextCursor: "c2" }, + ); + await state.loadPage(); // page 1 → cursor c1 + const first = state.loadPage("c1"); + const second = state.loadPage("c1"); // concurrent — should be dropped + const [r1, r2] = await Promise.all([first, second]); + expect(r1.tools.map((t) => t.name)).toEqual(["b"]); + expect(r2.tools).toEqual([]); // guarded no-op + expect(r2.nextCursor).toBe("c1"); // preserves the in-flight cursor + // Page appended exactly once. + expect(state.getTools().map((t) => t.name)).toEqual(["a", "b"]); + expect(state.getPagination().pageCount).toBe(2); + }); + + describe("connect auto-load (#1721)", () => { + it("loads page 1 on connect in paginated mode", async () => { + const spClient = new FakeInspectorClient({ + serverSettings: PAGINATED_SETTINGS, + }); + spClient.setStatus("connected"); + const spState = new PagedToolsState(spClient); + spClient.queueToolPages({ tools: [tool("a")], nextCursor: "c1" }); + const changed = waitForChange(spState); + spClient.dispatchTypedEvent("connect"); + expect((await changed).map((t) => t.name)).toEqual(["a"]); + expect(spState.getPagination()).toEqual({ + nextCursor: "c1", + pageCount: 1, + }); + spState.destroy(); + }); + + it("does NOT load on connect when paginated mode is off", async () => { + client.setStatus("connected"); + client.queueToolPages({ tools: [tool("a")] }); + client.dispatchTypedEvent("connect"); + await Promise.resolve(); + expect(client.listTools).not.toHaveBeenCalled(); + expect(state.getTools()).toEqual([]); + }); + }); }); diff --git a/clients/web/src/test/core/react/useManagedPrompts.test.tsx b/clients/web/src/test/core/react/useManagedPrompts.test.tsx index ff40e29b..6d3e8e6c 100644 --- a/clients/web/src/test/core/react/useManagedPrompts.test.tsx +++ b/clients/web/src/test/core/react/useManagedPrompts.test.tsx @@ -138,5 +138,23 @@ describe("useManagedPrompts", () => { const { result } = renderHook(() => useManagedPrompts(client, null)); expect(result.current.listChanged).toBe(false); }); + + it("clearListChanged() acknowledges the indicator without fetching (#1721)", async () => { + const { result } = renderHook(() => useManagedPrompts(client, state)); + act(() => { + client.dispatchTypedEvent("promptsListChanged"); + }); + await waitFor(() => expect(result.current.listChanged).toBe(true)); + act(() => { + result.current.clearListChanged(); + }); + await waitFor(() => expect(result.current.listChanged).toBe(false)); + expect(client.listAllPrompts).not.toHaveBeenCalled(); + }); + + it("clearListChanged() is a no-op when state is null", () => { + const { result } = renderHook(() => useManagedPrompts(client, null)); + expect(() => result.current.clearListChanged()).not.toThrow(); + }); }); }); diff --git a/clients/web/src/test/core/react/useManagedResources.test.tsx b/clients/web/src/test/core/react/useManagedResources.test.tsx index ee94a5a6..f2f8791d 100644 --- a/clients/web/src/test/core/react/useManagedResources.test.tsx +++ b/clients/web/src/test/core/react/useManagedResources.test.tsx @@ -143,5 +143,23 @@ describe("useManagedResources", () => { const { result } = renderHook(() => useManagedResources(client, null)); expect(result.current.listChanged).toBe(false); }); + + it("clearListChanged() acknowledges the indicator without fetching (#1721)", async () => { + const { result } = renderHook(() => useManagedResources(client, state)); + act(() => { + client.dispatchTypedEvent("resourcesListChanged"); + }); + await waitFor(() => expect(result.current.listChanged).toBe(true)); + act(() => { + result.current.clearListChanged(); + }); + await waitFor(() => expect(result.current.listChanged).toBe(false)); + expect(client.listAllResources).not.toHaveBeenCalled(); + }); + + it("clearListChanged() is a no-op when state is null", () => { + const { result } = renderHook(() => useManagedResources(client, null)); + expect(() => result.current.clearListChanged()).not.toThrow(); + }); }); }); diff --git a/clients/web/src/test/core/react/useManagedTools.test.tsx b/clients/web/src/test/core/react/useManagedTools.test.tsx index a4fa7d25..8f90eae3 100644 --- a/clients/web/src/test/core/react/useManagedTools.test.tsx +++ b/clients/web/src/test/core/react/useManagedTools.test.tsx @@ -138,6 +138,26 @@ describe("useManagedTools", () => { expect(result.current.listChanged).toBe(false); }); + it("clearListChanged() acknowledges the indicator without fetching (#1721)", async () => { + const { result } = renderHook(() => useManagedTools(client, state)); + act(() => { + client.dispatchTypedEvent("toolsListChanged"); + }); + await waitFor(() => expect(result.current.listChanged).toBe(true)); + + act(() => { + result.current.clearListChanged(); + }); + await waitFor(() => expect(result.current.listChanged).toBe(false)); + // No list fetch happened — only the flag was cleared. + expect(client.listAllTools).not.toHaveBeenCalled(); + }); + + it("clearListChanged() is a no-op when state is null", () => { + const { result } = renderHook(() => useManagedTools(client, null)); + expect(() => result.current.clearListChanged()).not.toThrow(); + }); + it("keeps a change that fires during the awaited refresh (race regression)", async () => { const { result } = renderHook(() => useManagedTools(client, state)); diff --git a/clients/web/src/test/core/react/usePagedPrompts.test.tsx b/clients/web/src/test/core/react/usePagedPrompts.test.tsx index 63be072b..1a3bca4a 100644 --- a/clients/web/src/test/core/react/usePagedPrompts.test.tsx +++ b/clients/web/src/test/core/react/usePagedPrompts.test.tsx @@ -65,6 +65,20 @@ describe("usePagedPrompts", () => { }); }); + it("exposes nextCursor and pageCount from paginationChange", async () => { + const { result } = renderHook(() => usePagedPrompts(client, state)); + expect(result.current.nextCursor).toBeUndefined(); + expect(result.current.pageCount).toBe(0); + client.queuePromptPages({ prompts: [prompt("a")], nextCursor: "c1" }); + await act(async () => { + await result.current.loadPage(); + }); + await waitFor(() => { + expect(result.current.nextCursor).toBe("c1"); + expect(result.current.pageCount).toBe(1); + }); + }); + it("clear() proxies to the state", async () => { client.queuePromptPages({ prompts: [prompt("a")] }); await state.loadPage(); diff --git a/clients/web/src/test/core/react/usePagedResources.test.tsx b/clients/web/src/test/core/react/usePagedResources.test.tsx index f8542437..b5b6be44 100644 --- a/clients/web/src/test/core/react/usePagedResources.test.tsx +++ b/clients/web/src/test/core/react/usePagedResources.test.tsx @@ -68,6 +68,23 @@ describe("usePagedResources", () => { }); }); + it("exposes nextCursor and pageCount from paginationChange", async () => { + const { result } = renderHook(() => usePagedResources(client, state)); + expect(result.current.nextCursor).toBeUndefined(); + expect(result.current.pageCount).toBe(0); + client.queueResourcePages({ + resources: [resource("a://1")], + nextCursor: "c1", + }); + await act(async () => { + await result.current.loadPage(); + }); + await waitFor(() => { + expect(result.current.nextCursor).toBe("c1"); + expect(result.current.pageCount).toBe(1); + }); + }); + it("clear() proxies to the state", async () => { client.queueResourcePages({ resources: [resource("a://1")] }); await state.loadPage(); diff --git a/clients/web/src/test/core/react/usePagedTools.test.tsx b/clients/web/src/test/core/react/usePagedTools.test.tsx index acdfa4db..c5bc5f85 100644 --- a/clients/web/src/test/core/react/usePagedTools.test.tsx +++ b/clients/web/src/test/core/react/usePagedTools.test.tsx @@ -66,6 +66,28 @@ describe("usePagedTools", () => { }); }); + it("exposes nextCursor and pageCount from paginationChange", async () => { + const { result } = renderHook(() => usePagedTools(client, state)); + expect(result.current.nextCursor).toBeUndefined(); + expect(result.current.pageCount).toBe(0); + client.queueToolPages({ tools: [tool("a")], nextCursor: "c1" }); + await act(async () => { + await result.current.loadPage(); + }); + await waitFor(() => { + expect(result.current.nextCursor).toBe("c1"); + expect(result.current.pageCount).toBe(1); + }); + }); + + it("seeds pagination from the state's initial snapshot", async () => { + client.queueToolPages({ tools: [tool("a")], nextCursor: "c1" }); + await state.loadPage(); + const { result } = renderHook(() => usePagedTools(client, state)); + expect(result.current.nextCursor).toBe("c1"); + expect(result.current.pageCount).toBe(1); + }); + it("clear() proxies to the state", async () => { client.queueToolPages({ tools: [tool("a")] }); await state.loadPage(); diff --git a/clients/web/src/test/core/react/useServers.test.tsx b/clients/web/src/test/core/react/useServers.test.tsx index 8e8a410d..0ee49df7 100644 --- a/clients/web/src/test/core/react/useServers.test.tsx +++ b/clients/web/src/test/core/react/useServers.test.tsx @@ -431,6 +431,7 @@ describe("useServers", () => { taskTtl: 30000, maxFetchRequests: 1000, autoRefreshOnListChanged: false, + paginatedLists: false, roots: [], }); }); @@ -518,6 +519,7 @@ describe("useServers", () => { maxFetchRequests: 1000, // Absent autoRefreshOnListChanged on disk reads back as false. autoRefreshOnListChanged: false, + paginatedLists: false, // Absent roots on disk reads back as an empty list for the form. roots: [], }); diff --git a/clients/web/src/test/fixtures/pagination.ts b/clients/web/src/test/fixtures/pagination.ts new file mode 100644 index 00000000..401cb97d --- /dev/null +++ b/clients/web/src/test/fixtures/pagination.ts @@ -0,0 +1,14 @@ +import type { ListPaginationControlsProps } from "../../components/elements/ListPaginationControls/ListPaginationControls"; + +/** + * A no-op, all-pages-mode pagination model for tests and stories that don't + * exercise paginated-mode fetching but must satisfy the required `pagination` + * prop threaded through the list screens/controls (#1721). + */ +export const noopPagination: ListPaginationControlsProps = { + paginated: false, + onPaginatedChange: () => {}, + canLoadMore: false, + loadedPages: 0, + onLoadMore: () => {}, +}; diff --git a/clients/web/src/test/integration/mcp/inspectorClient.test.ts b/clients/web/src/test/integration/mcp/inspectorClient.test.ts index 571bbadd..14bcf9a5 100644 --- a/clients/web/src/test/integration/mcp/inspectorClient.test.ts +++ b/clients/web/src/test/integration/mcp/inspectorClient.test.ts @@ -1147,6 +1147,70 @@ describe("InspectorClient", () => { expect(page4.nextCursor).toBeUndefined(); expect(page4.tools[0]?.name).toBe("tool_10"); }); + + it("listAllTools aggregates every page via the SDK's cache-aware verb (#1721)", async () => { + // Disconnect and create a new server with pagination + await client!.disconnect(); + if (server) { + await server.stop(); + } + + // 10 tools, page size 3 → 4 wire pages the SDK walks internally. + server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: createNumberedTools(10), + maxPageSize: { tools: 3 }, + }); + await server.start(); + + client = new InspectorClient( + { type: "streamable-http", url: server.url }, + { + environment: { transport: createTransportNode }, + clientIdentity: { name: "test", version: "1.0.0" }, + }, + ); + await client.connect(); + + // One call returns the fully-aggregated list (no per-page cursor + // surfaced), unlike the single-page listTools() above. `cacheMode: + // "refresh"` forces a wire fetch — a no-op here (no ttlMs hints) but the + // path the managed refresh uses. + const all = await client.listAllTools({ cacheMode: "refresh" }); + expect(all.tools.map((t) => t.name)).toEqual( + Array.from({ length: 10 }, (_, i) => `tool_${i + 1}`), + ); + }); + + it("listAllResources / listAllPrompts / listAllResourceTemplates aggregate too (#1721)", async () => { + await client!.disconnect(); + if (server) { + await server.stop(); + } + server = createTestServerHttp({ + serverInfo: createTestServerInfo(), + tools: createNumberedTools(2), + }); + await server.start(); + client = new InspectorClient( + { type: "streamable-http", url: server.url }, + { + environment: { transport: createTransportNode }, + clientIdentity: { name: "test", version: "1.0.0" }, + }, + ); + await client.connect(); + + // Exercises the remaining three aggregate methods (empty is fine — the + // point is the SDK verb runs and returns a well-formed shape). Default + // cacheMode ('use') for these. + const resources = await client.listAllResources(); + const prompts = await client.listAllPrompts({ metadata: { k: "v" } }); + const templates = await client.listAllResourceTemplates(); + expect(Array.isArray(resources.resources)).toBe(true); + expect(Array.isArray(prompts.prompts)).toBe(true); + expect(Array.isArray(templates.resourceTemplates)).toBe(true); + }); }); describe("Default metadata (server-wide _meta)", () => { @@ -5541,6 +5605,12 @@ describe("InspectorClient", () => { await expectThrow(() => c.listResourceTemplates()); await expectThrow(() => c.listPrompts()); + // aggregate (all-page, cache-aware) listing (#1721) + await expectThrow(() => c.listAllTools()); + await expectThrow(() => c.listAllResources()); + await expectThrow(() => c.listAllResourceTemplates()); + await expectThrow(() => c.listAllPrompts()); + // single-item fetch await expectThrow(() => c.callTool({ name: "x" } as unknown as Tool, {}), diff --git a/clients/web/src/test/integration/mcp/remote/server-extra-coverage.test.ts b/clients/web/src/test/integration/mcp/remote/server-extra-coverage.test.ts index bcffb3e8..afb19929 100644 --- a/clients/web/src/test/integration/mcp/remote/server-extra-coverage.test.ts +++ b/clients/web/src/test/integration/mcp/remote/server-extra-coverage.test.ts @@ -444,6 +444,14 @@ describe("server.ts supplemental coverage", () => { expect((await res.json()).error).toMatch(/autoRefreshOnListChanged/); }); + it("rejects a non-boolean paginatedLists", async () => { + const res = await postSettings({ + ...base, + paginatedLists: "yes", + }); + expect((await res.json()).error).toMatch(/paginatedLists/); + }); + it("rejects a negative maxFetchRequests", async () => { const res = await postSettings({ ...base, maxFetchRequests: -2 }); expect((await res.json()).error).toMatch(/maxFetchRequests/); @@ -476,6 +484,7 @@ describe("server.ts supplemental coverage", () => { cwd: "/tmp", taskTtl: 1000, autoRefreshOnListChanged: true, + paginatedLists: true, maxFetchRequests: 5, protocolEra: "modern", oauthClientId: "cid", diff --git a/core/mcp/__tests__/fakeInspectorClient.ts b/core/mcp/__tests__/fakeInspectorClient.ts index b4d2149f..3934b5f0 100644 --- a/core/mcp/__tests__/fakeInspectorClient.ts +++ b/core/mcp/__tests__/fakeInspectorClient.ts @@ -7,6 +7,7 @@ import { vi } from "vitest"; import type { + CacheMode, ClientCapabilities, DiscoverResult, Implementation, @@ -40,6 +41,24 @@ type ListResult = { [K in TKey]: TItem[]; } & { nextCursor?: string }; +/** + * Drain the entire queue of pre-canned pages for a list key and return the + * flattened items — the aggregate `listAll*` mocks consume every queued page in + * one call, mirroring the SDK's all-page walk (the single-page `list*` mocks + * shift one page per call instead). + */ +function drainPages( + pages: Array>, + key: TKey, +): TItem[] { + const items: TItem[] = []; + while (pages.length > 0) { + const page = pages.shift(); + if (page) items.push(...page[key]); + } + return items; +} + export interface FakeInspectorClientOptions { status?: ConnectionStatus; capabilities?: ServerCapabilities; @@ -93,6 +112,40 @@ export class FakeInspectorClient async () => this.taskPages.shift() ?? { tasks: [] }, ); + // Aggregate variants used by the managed state stores on refresh: drain ALL + // queued pages (mimicking the SDK's all-page walk) and return the flattened + // list. The `options` (incl. `cacheMode`) is recorded by the `vi.fn` so tests + // can assert a user/auto refresh forwarded `cacheMode: "refresh"`. + listAllTools = vi.fn( + async (_options?: { + cacheMode?: CacheMode; + metadata?: Record; + }) => ({ tools: drainPages(this.toolPages, "tools") }), + ); + listAllPrompts = vi.fn( + async (_options?: { + cacheMode?: CacheMode; + metadata?: Record; + }) => ({ prompts: drainPages(this.promptPages, "prompts") }), + ); + listAllResources = vi.fn( + async (_options?: { + cacheMode?: CacheMode; + metadata?: Record; + }) => ({ resources: drainPages(this.resourcePages, "resources") }), + ); + listAllResourceTemplates = vi.fn( + async (_options?: { + cacheMode?: CacheMode; + metadata?: Record; + }) => ({ + resourceTemplates: drainPages( + this.resourceTemplatePages, + "resourceTemplates", + ), + }), + ); + callTool = vi.fn( async ( tool: Tool, diff --git a/core/mcp/inspectorClient.ts b/core/mcp/inspectorClient.ts index 2b48cc06..932d73b1 100644 --- a/core/mcp/inspectorClient.ts +++ b/core/mcp/inspectorClient.ts @@ -84,6 +84,8 @@ import type { import type { Transport } from "@modelcontextprotocol/client"; import type { RequestOptions, + CacheableRequestOptions, + CacheMode, ProgressCallback, VersionNegotiationOptions, ProtocolEra, @@ -676,6 +678,38 @@ export class InspectorClient extends InspectorClientEventTarget { return opts; } + /** + * {@link getRequestOptions} plus the per-call `cacheMode` for the SDK's + * cacheable verbs (the high-level `client.listTools()` / `listPrompts()` / + * `listResources()` / `listResourceTemplates()` used by the `listAll*` + * aggregate methods below). `cacheMode` is only honored by those wrappers — + * the single-page `client.request` path ignores it — so it lives here rather + * than in `getRequestOptions`. Omitted when unset so the SDK default + * (`'use'`) applies. + */ + private getCacheableRequestOptions( + cacheMode?: CacheMode, + ): CacheableRequestOptions { + const opts: CacheableRequestOptions = this.getRequestOptions(); + if (cacheMode !== undefined) { + opts.cacheMode = cacheMode; + } + return opts; + } + + /** + * Build the `params` for the aggregate `listAll*` verbs: merge call metadata + * with `defaultMetadata` and wrap as `{ _meta }`, or `undefined` when empty + * (so the SDK skips an empty `_meta`). Shared by all four `listAll*` methods + * so the merge/omit branch is defined once. + */ + private aggregateListParams( + metadata?: Record, + ): { _meta: Record } | undefined { + const effectiveMeta = this.mergeMeta(metadata); + return effectiveMeta ? { _meta: effectiveMeta } : undefined; + } + private isHttpOAuthConfig(): boolean { const serverType = getServerTypeFromConfig(this.transportConfig); return ( @@ -2098,6 +2132,34 @@ export class InspectorClient extends InspectorClientEventTarget { return { tools, nextCursor: response.nextCursor }; } + /** + * Aggregate ALL pages of `tools/list` via the SDK's high-level + * `client.listTools()` — the cache-aware verb. Unlike the single-page + * {@link listTools} (raw `client.request`, for pagination debugging), this is + * the path the managed tool list uses on refresh: the SDK walks every page, + * applies the SEP-2243 `x-mcp-header` exclusion, and consults/writes its + * response cache. `cacheMode` selects the disposition (`'use'` serves a + * still-fresh cached list without a round trip; `'refresh'` always fetches + * and re-stores; `'bypass'` fetches without touching the cache) — only + * meaningful on modern servers that send `ttlMs` hints; a no-op on legacy + * (nothing is cached, so every call hits the wire regardless). + */ + async listAllTools(options?: { + cacheMode?: CacheMode; + metadata?: Record; + }): Promise<{ tools: Tool[] }> { + if (!this.client) { + throw new Error("Client is not connected"); + } + const response = await this.invokeMcpClient(() => + this.client!.listTools( + this.aggregateListParams(options?.metadata), + this.getCacheableRequestOptions(options?.cacheMode), + ), + ); + return { tools: [...response.tools] }; + } + /** * Call a tool. Caller must provide the Tool (e.g. from a state manager). * @param tool The tool to call (use tool.name for the request) @@ -2886,6 +2948,28 @@ export class InspectorClient extends InspectorClientEventTarget { }; } + /** + * Aggregate ALL pages of `resources/list` via the SDK's high-level + * cache-aware `client.listResources()`. See {@link listAllTools} for the + * `cacheMode` semantics; this is the path the managed resource list uses on + * refresh. + */ + async listAllResources(options?: { + cacheMode?: CacheMode; + metadata?: Record; + }): Promise<{ resources: Resource[] }> { + if (!this.client) { + throw new Error("Client is not connected"); + } + const response = await this.invokeMcpClient(() => + this.client!.listResources( + this.aggregateListParams(options?.metadata), + this.getCacheableRequestOptions(options?.cacheMode), + ), + ); + return { resources: [...response.resources] }; + } + /** * Read a resource by URI * @param uri Resource URI @@ -3020,6 +3104,30 @@ export class InspectorClient extends InspectorClientEventTarget { }; } + /** + * Aggregate ALL pages of `resources/templates/list` via the SDK's high-level + * cache-aware `client.listResourceTemplates()`. See {@link listAllTools} for + * the `cacheMode` semantics; this is the path the managed resource-template + * list uses on refresh. + */ + async listAllResourceTemplates(options?: { + cacheMode?: CacheMode; + metadata?: Record; + }): Promise<{ resourceTemplates: ResourceTemplate[] }> { + if (!this.client) { + throw new Error("Client is not connected"); + } + const response = await this.invokeMcpClient( + () => + this.client!.listResourceTemplates( + this.aggregateListParams(options?.metadata), + this.getCacheableRequestOptions(options?.cacheMode), + ), + { method: "resources/templates/list" }, + ); + return { resourceTemplates: [...response.resourceTemplates] }; + } + /** * List available prompts with pagination support * @param cursor Optional cursor for pagination @@ -3051,6 +3159,28 @@ export class InspectorClient extends InspectorClientEventTarget { }; } + /** + * Aggregate ALL pages of `prompts/list` via the SDK's high-level + * cache-aware `client.listPrompts()`. See {@link listAllTools} for the + * `cacheMode` semantics; this is the path the managed prompt list uses on + * refresh. + */ + async listAllPrompts(options?: { + cacheMode?: CacheMode; + metadata?: Record; + }): Promise<{ prompts: Prompt[] }> { + if (!this.client) { + throw new Error("Client is not connected"); + } + const response = await this.invokeMcpClient(() => + this.client!.listPrompts( + this.aggregateListParams(options?.metadata), + this.getCacheableRequestOptions(options?.cacheMode), + ), + ); + return { prompts: [...response.prompts] }; + } + /** * Get a prompt by name * @param name Prompt name diff --git a/core/mcp/inspectorClientProtocol.ts b/core/mcp/inspectorClientProtocol.ts index 739b0b9b..98ea0ba0 100644 --- a/core/mcp/inspectorClientProtocol.ts +++ b/core/mcp/inspectorClientProtocol.ts @@ -21,6 +21,7 @@ import type { ToolCallInvocation, } from "./types.js"; import type { + CacheMode, ClientCapabilities, DiscoverResult, Implementation, @@ -89,6 +90,29 @@ export interface InspectorClientProtocol extends InspectorClientEventTarget { cursor?: string, ): Promise<{ tasks: Task[]; nextCursor?: string }>; + // Aggregate (all-page) list methods used by the managed state stores on + // refresh. Unlike the single-page methods above, these route through the + // SDK's cache-aware high-level verbs so `cacheMode` ('use' | 'refresh' | + // 'bypass') is honored — a manual refresh forces a cache-bypassing round trip + // on modern servers that send `ttlMs` hints. Options are optional; omitting + // `cacheMode` uses the SDK default ('use'). + listAllTools(options?: { + cacheMode?: CacheMode; + metadata?: Record; + }): Promise<{ tools: Tool[] }>; + listAllPrompts(options?: { + cacheMode?: CacheMode; + metadata?: Record; + }): Promise<{ prompts: Prompt[] }>; + listAllResources(options?: { + cacheMode?: CacheMode; + metadata?: Record; + }): Promise<{ resources: Resource[] }>; + listAllResourceTemplates(options?: { + cacheMode?: CacheMode; + metadata?: Record; + }): Promise<{ resourceTemplates: ResourceTemplate[] }>; + // Invocation methods used by paged result panels callTool( tool: Tool, diff --git a/core/mcp/node/servers.ts b/core/mcp/node/servers.ts index 8bda9cc2..532f2976 100644 --- a/core/mcp/node/servers.ts +++ b/core/mcp/node/servers.ts @@ -56,6 +56,7 @@ export function headersToServerSettings( taskTtl: DEFAULT_TASK_TTL_MS, maxFetchRequests: DEFAULT_MAX_FETCH_REQUESTS, autoRefreshOnListChanged: false, + paginatedLists: false, roots: [], }; } diff --git a/core/mcp/remote/node/server.ts b/core/mcp/remote/node/server.ts index c9d20382..ea631084 100644 --- a/core/mcp/remote/node/server.ts +++ b/core/mcp/remote/node/server.ts @@ -1393,6 +1393,16 @@ export function createRemoteApp( error: "settings.autoRefreshOnListChanged must be a boolean", }; } + // Optional on the wire; boolean when present, else defaults to false below. + if ( + obj.paginatedLists !== undefined && + typeof obj.paginatedLists !== "boolean" + ) { + return { + ok: false, + error: "settings.paginatedLists must be a boolean", + }; + } // maxFetchRequests is optional on the wire (older clients won't send it); // when present it must be a non-negative number (0 = unlimited), otherwise // it defaults below. @@ -1477,6 +1487,8 @@ export function createRemoteApp( // Absent → false, matching the read side. The omit-on-false logic lives // in inspectorSettingsToStoredFields, so a false value writes nothing. autoRefreshOnListChanged: obj.autoRefreshOnListChanged === true, + // Absent → false, matching the read side (omit-on-false on the write side). + paginatedLists: obj.paginatedLists === true, // Absent → product default, matching the read side. The default is the // omit-sentinel in inspectorSettingsToStoredFields, so a client that // didn't send one writes no spurious maxFetchRequests to disk. diff --git a/core/mcp/serverList.ts b/core/mcp/serverList.ts index 7e70c63f..278c555d 100644 --- a/core/mcp/serverList.ts +++ b/core/mcp/serverList.ts @@ -120,6 +120,7 @@ type StoredInspectorFields = Pick< | "requestTimeout" | "taskTtl" | "autoRefreshOnListChanged" + | "paginatedLists" | "maxFetchRequests" | "oauth" | "roots" @@ -186,6 +187,7 @@ export function storedFieldsToInspectorSettings( stored.requestTimeout !== undefined || stored.taskTtl !== undefined || stored.autoRefreshOnListChanged !== undefined || + stored.paginatedLists !== undefined || stored.maxFetchRequests !== undefined || stored.oauth !== undefined || stored.roots !== undefined || @@ -208,6 +210,7 @@ export function storedFieldsToInspectorSettings( // default so the form shows it and "Run as task" has a value to send. taskTtl: stored.taskTtl ?? DEFAULT_TASK_TTL_MS, autoRefreshOnListChanged: stored.autoRefreshOnListChanged ?? false, + paginatedLists: stored.paginatedLists ?? false, // Concrete default like taskTtl (not a 0-sentinel): the form needs a value // to render and the log state needs one to size its buffer. An absent // on-disk field reads back as the default, which the write side then omits. @@ -293,6 +296,11 @@ export function inspectorSettingsToStoredFields( out.autoRefreshOnListChanged = true; } + // Persist only when enabled — absent reads back as false (above). + if (settings.paginatedLists) { + out.paginatedLists = true; + } + // Persist only when it differs from the default era. Absent reads back as // DEFAULT_PROTOCOL_ERA, so writing the default would inject the field into // hand-edited files that never had it and break byte-stable round-trips. @@ -359,6 +367,7 @@ const INSPECTOR_FIELD_KEY_MAP = { requestTimeout: true, taskTtl: true, autoRefreshOnListChanged: true, + paginatedLists: true, maxFetchRequests: true, oauth: true, roots: true, diff --git a/core/mcp/state/fetchRequestLogState.ts b/core/mcp/state/fetchRequestLogState.ts index 62e9298e..080fd39b 100644 --- a/core/mcp/state/fetchRequestLogState.ts +++ b/core/mcp/state/fetchRequestLogState.ts @@ -239,9 +239,7 @@ export class FetchRequestLogState extends TypedEventTarget 0 - ? merged.slice(-this.maxFetchRequests) - : merged; + this.maxFetchRequests > 0 ? merged.slice(-this.maxFetchRequests) : merged; this.dispatchTypedEvent("fetchRequestsChange", this.getFetchRequests()); } diff --git a/core/mcp/state/managedListState.ts b/core/mcp/state/managedListState.ts index 0cf67137..4f6160e1 100644 --- a/core/mcp/state/managedListState.ts +++ b/core/mcp/state/managedListState.ts @@ -13,12 +13,13 @@ import type { InspectorClientProtocol } from "../inspectorClientProtocol.js"; import type { InspectorClientEventMap } from "../inspectorClientEventTarget.js"; -import type { ServerCapabilities } from "@modelcontextprotocol/client"; +import type { + CacheMode, + ServerCapabilities, +} from "@modelcontextprotocol/client"; import { isTerminalStatus } from "../types.js"; import { TypedEventTarget } from "../typedEventTarget.js"; -const MAX_PAGES = 100; - /** * Default delay (ms) for debouncing `list_changed` notifications. Servers * (e.g. the everything server) can emit a rapid burst; debouncing collapses the @@ -33,12 +34,6 @@ export interface ManagedListEventMap { listChangedChange: boolean; } -/** A single page of a list result, normalized across the SDK list methods. */ -export interface ListPage { - items: T[]; - nextCursor?: string; -} - export interface ManagedListConfig { /** The `*Change` event this manager dispatches (e.g. "toolsChange"). */ changeEvent: keyof M; @@ -46,14 +41,16 @@ export interface ManagedListConfig { listChangedEvent: keyof InspectorClientEventMap; /** Server capability gating the list call (empty list when absent). */ capabilityKey: keyof ServerCapabilities; - /** Human label used in the pagination-cap error message. */ - itemLabel: string; - /** Fetch a single page from the client. */ - fetchPage: ( + /** + * Fetch the complete (all-page) list from the client. Delegates page walking + * to the SDK's cache-aware high-level verb (via the client's `listAll*` + * methods), which also lets `cacheMode` select the cache disposition. + */ + fetchAll: ( client: InspectorClientProtocol, - cursor: string | undefined, + cacheMode: CacheMode | undefined, metadata: Record | undefined, - ) => Promise>; + ) => Promise; /** * Whether this list drives a list-changed indicator. When true, a * `list_changed` in non-auto-refresh mode lights the indicator blindly (no @@ -62,6 +59,15 @@ export interface ManagedListConfig { * pulled via the screen's Refresh instead. */ supportsIndicator: boolean; + /** + * Whether this list has a paged (paginated) counterpart that drives the + * display when `paginatedLists` is on. Only such lists defer their + * connect-time / `list_changed` aggregate walk in paginated mode (tools, + * prompts, resources). Lists with no paged counterpart (resource templates) + * set this `false` so they still aggregate on connect regardless of the + * setting — otherwise they'd never load in paginated mode (#1721). + */ + deferWhenPaginated: boolean; /** Debounce delay (ms) for `list_changed` bursts. */ debounceMs: number; } @@ -94,6 +100,18 @@ export abstract class ManagedListState< this.client = client; this.config = config; const onConnect = (): void => { + // In paginated mode the aggregate list is not the display source (the + // paged state drives the sidebar), so skip the connect-time all-page + // walk — the whole point of the setting is to avoid pulling every page + // for servers with very large lists (#1721). Switching back to + // all-pages mode triggers a refresh from the UI. Only lists with a paged + // counterpart defer here; resource templates (none) still aggregate. + if ( + this.config.deferWhenPaginated && + this.client?.getServerSettings()?.paginatedLists + ) { + return; + } void this.refresh(); }; const onListChanged = (): void => { @@ -155,10 +173,22 @@ export abstract class ManagedListState< try { do { this.runQueued = false; - // Read the setting at fire time so a `setServerSettings` toggle that + // Read the settings at fire time so a `setServerSettings` toggle that // lands mid-burst is honored on the settled action. - if (this.client?.getServerSettings()?.autoRefreshOnListChanged) { - await this.refresh(); + const settings = this.client?.getServerSettings(); + // In paginated mode the aggregate list is not the display source for + // lists with a paged counterpart, so never auto-aggregate on + // `list_changed` there — only light the indicator so the user can pull + // page 1 fresh via Refresh (#1721). This wins over + // `autoRefreshOnListChanged`, which would otherwise pull every page. + // Lists with no paged counterpart (resource templates) still aggregate. + const skipAggregate = + this.config.deferWhenPaginated && settings?.paginatedLists; + if (!skipAggregate && settings?.autoRefreshOnListChanged) { + // A `list_changed` means the prior list is stale, so bypass any + // cached entry (`cacheMode: "refresh"`) and re-store the fresh + // aggregate. + await this.refresh(undefined, "refresh"); } else if (this.config.supportsIndicator) { this.setListChanged(true); } @@ -210,8 +240,17 @@ export abstract class ManagedListState< this._metadata = metadata; } - async refresh(metadata?: Record): Promise { - const next = await this.fetchAll(metadata); + /** + * Refresh the full list. `cacheMode` selects the SDK cache disposition for + * this fetch: `undefined` (the connect-time load) uses the default `'use'`; + * a user-initiated or auto refresh passes `'refresh'` to force a + * cache-bypassing round trip and re-store the fresh aggregate. + */ + async refresh( + metadata?: Record, + cacheMode?: CacheMode, + ): Promise { + const next = await this.fetchItems(metadata, cacheMode); // `null` means not connected — leave the current list untouched. if (next === null) return this.getItems(); this.applyItems(next); @@ -219,38 +258,21 @@ export abstract class ManagedListState< } /** - * Fetch all pages, then `applyItems` commits them (see `refresh`). Returns - * `null` when not connected, or - * `[]` when the server doesn't advertise the gating capability (calling the - * list method there returns -32601 "Method not found", which would spam the - * console; empty list is the right semantics). + * Fetch the complete list (all pages, via the SDK's cache-aware verb), then + * `applyItems` commits it (see `refresh`). Returns `null` when not connected, + * or `[]` when the server doesn't advertise the gating capability (calling + * the list method there returns -32601 "Method not found", which would spam + * the console; empty list is the right semantics). */ - private async fetchAll( - metadata?: Record, + private async fetchItems( + metadata: Record | undefined, + cacheMode: CacheMode | undefined, ): Promise { const client = this.client; if (!client || client.getStatus() !== "connected") return null; if (!client.getCapabilities()?.[this.config.capabilityKey]) return []; const effectiveMetadata = metadata ?? this._metadata; - let items: T[] = []; - let cursor: string | undefined; - let pageCount = 0; - do { - const page = await this.config.fetchPage( - client, - cursor, - effectiveMetadata, - ); - items = cursor ? [...items, ...page.items] : page.items; - cursor = page.nextCursor; - pageCount++; - if (pageCount >= MAX_PAGES) { - throw new Error( - `Maximum pagination limit (${MAX_PAGES} pages) reached while listing ${this.config.itemLabel}`, - ); - } - } while (cursor); - return items; + return this.config.fetchAll(client, cacheMode, effectiveMetadata); } /** Commit a fetched list as the current one and notify subscribers. */ diff --git a/core/mcp/state/managedPromptsState.ts b/core/mcp/state/managedPromptsState.ts index a986a195..a12fcd4a 100644 --- a/core/mcp/state/managedPromptsState.ts +++ b/core/mcp/state/managedPromptsState.ts @@ -33,12 +33,12 @@ export class ManagedPromptsState extends ManagedListState< changeEvent: "promptsChange", listChangedEvent: "promptsListChanged", capabilityKey: "prompts", - itemLabel: "prompts", + deferWhenPaginated: true, supportsIndicator: true, debounceMs, - fetchPage: async (c, cursor, metadata) => { - const result = await c.listPrompts(cursor, metadata); - return { items: result.prompts, nextCursor: result.nextCursor }; + fetchAll: async (c, cacheMode, metadata) => { + const result = await c.listAllPrompts({ cacheMode, metadata }); + return result.prompts; }, }); } diff --git a/core/mcp/state/managedResourceTemplatesState.ts b/core/mcp/state/managedResourceTemplatesState.ts index b7d0e003..aceedd4f 100644 --- a/core/mcp/state/managedResourceTemplatesState.ts +++ b/core/mcp/state/managedResourceTemplatesState.ts @@ -39,15 +39,17 @@ export class ManagedResourceTemplatesState extends ManagedListState< listChangedEvent: "resourceTemplatesListChanged", // Templates are gated on the broader `resources` capability. capabilityKey: "resources", - itemLabel: "resource templates", + // No paged counterpart (templates aren't paginated in the UI), so they + // must still aggregate on connect even in paginated mode (#1721). + deferWhenPaginated: false, supportsIndicator: false, debounceMs, - fetchPage: async (c, cursor, metadata) => { - const result = await c.listResourceTemplates(cursor, metadata); - return { - items: result.resourceTemplates, - nextCursor: result.nextCursor, - }; + fetchAll: async (c, cacheMode, metadata) => { + const result = await c.listAllResourceTemplates({ + cacheMode, + metadata, + }); + return result.resourceTemplates; }, }); } diff --git a/core/mcp/state/managedResourcesState.ts b/core/mcp/state/managedResourcesState.ts index f8865e81..f00076e4 100644 --- a/core/mcp/state/managedResourcesState.ts +++ b/core/mcp/state/managedResourcesState.ts @@ -33,12 +33,12 @@ export class ManagedResourcesState extends ManagedListState< changeEvent: "resourcesChange", listChangedEvent: "resourcesListChanged", capabilityKey: "resources", - itemLabel: "resources", + deferWhenPaginated: true, supportsIndicator: true, debounceMs, - fetchPage: async (c, cursor, metadata) => { - const result = await c.listResources(cursor, metadata); - return { items: result.resources, nextCursor: result.nextCursor }; + fetchAll: async (c, cacheMode, metadata) => { + const result = await c.listAllResources({ cacheMode, metadata }); + return result.resources; }, }); } diff --git a/core/mcp/state/managedToolsState.ts b/core/mcp/state/managedToolsState.ts index a9917622..03a87d59 100644 --- a/core/mcp/state/managedToolsState.ts +++ b/core/mcp/state/managedToolsState.ts @@ -33,12 +33,12 @@ export class ManagedToolsState extends ManagedListState< changeEvent: "toolsChange", listChangedEvent: "toolsListChanged", capabilityKey: "tools", - itemLabel: "tools", + deferWhenPaginated: true, supportsIndicator: true, debounceMs, - fetchPage: async (c, cursor, metadata) => { - const result = await c.listTools(cursor, metadata); - return { items: result.tools, nextCursor: result.nextCursor }; + fetchAll: async (c, cacheMode, metadata) => { + const result = await c.listAllTools({ cacheMode, metadata }); + return result.tools; }, }); } diff --git a/core/mcp/state/messageLogState.ts b/core/mcp/state/messageLogState.ts index 2cc6de7b..9355741e 100644 --- a/core/mcp/state/messageLogState.ts +++ b/core/mcp/state/messageLogState.ts @@ -130,9 +130,7 @@ export class MessageLogState extends TypedEventTarget { */ clearMessages(predicate?: (entry: MessageEntry) => boolean): void { const before = this.messages.length; - this.messages = predicate - ? this.messages.filter((m) => !predicate(m)) - : []; + this.messages = predicate ? this.messages.filter((m) => !predicate(m)) : []; // Drop pending-request bookkeeping for any entry that was just removed. // Without this, a later response matching an evicted request id would // mutate a Map-only entry that is no longer reachable from `messages`, diff --git a/core/mcp/state/pagedPromptsState.ts b/core/mcp/state/pagedPromptsState.ts index fd437e2b..ed649711 100644 --- a/core/mcp/state/pagedPromptsState.ts +++ b/core/mcp/state/pagedPromptsState.ts @@ -1,10 +1,12 @@ /** - * PagedPromptsState: holds an aggregated list of prompts loaded via loadPage(cursor). - * Does not load on connect; caller drives loading. Clears on disconnect. + * PagedPromptsState: holds the prompts accumulated so far, loaded one page at a + * time via loadPage(cursor). Backs paginated mode (`paginatedLists`, #1721): + * auto-loads page 1 on connect when the setting is on, and tracks the server's + * `nextCursor` + a running page count as observable state. Clears on disconnect. * * Intentionally does NOT subscribe to `promptsListChanged`: cursors are tied * to the server's prior list, so a list change mid-pagination would invalidate - * them. The caller decides how to react. + * them. The caller pulls page 1 again via Refresh instead. * * Ported from v1.5/main. v2 substitutes `InspectorClientProtocol` for the * concrete `InspectorClient` since the runtime class is not yet ported. @@ -14,9 +16,11 @@ import type { InspectorClientProtocol } from "../inspectorClientProtocol.js"; import type { Prompt } from "@modelcontextprotocol/client"; import { isTerminalStatus } from "../types.js"; import { TypedEventTarget } from "../typedEventTarget.js"; +import type { PagePaginationState } from "./pagedToolsState.js"; export interface PagedPromptsStateEventMap { promptsChange: Prompt[]; + paginationChange: PagePaginationState; } export interface LoadPageResult { @@ -26,21 +30,32 @@ export interface LoadPageResult { export class PagedPromptsState extends TypedEventTarget { private prompts: Prompt[] = []; + private nextCursor: string | undefined = undefined; + private pageCount = 0; + // Double-click guard: a load in flight makes the next `loadPage` a no-op so + // the same page can't be appended twice (#1721). + private loading = false; private client: InspectorClientProtocol | null = null; private unsubscribe: (() => void) | null = null; constructor(client: InspectorClientProtocol) { super(); this.client = client; - const onStatusChange = (): void => { - if (isTerminalStatus(this.client?.getStatus())) { - this.prompts = []; - this.dispatchTypedEvent("promptsChange", []); + const onConnect = (): void => { + if (this.client?.getServerSettings()?.paginatedLists) { + void this.loadPage(undefined); } }; + const onStatusChange = (): void => { + if (isTerminalStatus(this.client?.getStatus())) { + this.reset(); + } + }; + this.client.addEventListener("connect", onConnect); this.client.addEventListener("statusChange", onStatusChange); this.unsubscribe = () => { if (this.client) { + this.client.removeEventListener("connect", onConnect); this.client.removeEventListener("statusChange", onStatusChange); } this.client = null; @@ -51,9 +66,20 @@ export class PagedPromptsState extends TypedEventTarget { private resources: Resource[] = []; + private nextCursor: string | undefined = undefined; + private pageCount = 0; + // Double-click guard: a load in flight makes the next `loadPage` a no-op so + // the same page can't be appended twice (#1721). + private loading = false; private client: InspectorClientProtocol | null = null; private unsubscribe: (() => void) | null = null; constructor(client: InspectorClientProtocol) { super(); this.client = client; - const onStatusChange = (): void => { - if (isTerminalStatus(this.client?.getStatus())) { - this.resources = []; - this.dispatchTypedEvent("resourcesChange", []); + const onConnect = (): void => { + if (this.client?.getServerSettings()?.paginatedLists) { + void this.loadPage(undefined); } }; + const onStatusChange = (): void => { + if (isTerminalStatus(this.client?.getStatus())) { + this.reset(); + } + }; + this.client.addEventListener("connect", onConnect); this.client.addEventListener("statusChange", onStatusChange); this.unsubscribe = () => { if (this.client) { + this.client.removeEventListener("connect", onConnect); this.client.removeEventListener("statusChange", onStatusChange); } this.client = null; @@ -51,9 +67,20 @@ export class PagedResourcesState extends TypedEventTarget { private tools: Tool[] = []; + private nextCursor: string | undefined = undefined; + private pageCount = 0; + // Guards against a concurrent load (e.g. a fast double-click on "Load next + // page"): both calls would otherwise read the same cursor and append the + // same page twice. A load in flight makes the next `loadPage` a no-op (#1721). + private loading = false; private client: InspectorClientProtocol | null = null; private unsubscribe: (() => void) | null = null; constructor(client: InspectorClientProtocol) { super(); this.client = client; - const onStatusChange = (): void => { - if (isTerminalStatus(this.client?.getStatus())) { - this.tools = []; - this.dispatchTypedEvent("toolsChange", []); + const onConnect = (): void => { + // Auto-load page 1 only in paginated mode — otherwise the managed + // (aggregate) state is the display source and this stays idle (#1721). + if (this.client?.getServerSettings()?.paginatedLists) { + void this.loadPage(undefined); } }; + const onStatusChange = (): void => { + if (isTerminalStatus(this.client?.getStatus())) { + this.reset(); + } + }; + this.client.addEventListener("connect", onConnect); this.client.addEventListener("statusChange", onStatusChange); this.unsubscribe = () => { if (this.client) { + this.client.removeEventListener("connect", onConnect); this.client.removeEventListener("statusChange", onStatusChange); } this.client = null; @@ -52,9 +78,21 @@ export class PagedToolsState extends TypedEventTarget { return [...this.tools]; } + getPagination(): PagePaginationState { + return { nextCursor: this.nextCursor, pageCount: this.pageCount }; + } + + /** Clear the accumulated list and pagination progress. */ clear(): void { + this.reset(); + } + + private reset(): void { this.tools = []; + this.nextCursor = undefined; + this.pageCount = 0; this.dispatchTypedEvent("toolsChange", this.tools); + this.dispatchTypedEvent("paginationChange", this.getPagination()); } async loadPage(cursor?: string): Promise { @@ -62,18 +100,35 @@ export class PagedToolsState extends TypedEventTarget { if (!c || c.getStatus() !== "connected") { return { tools: [], nextCursor: undefined }; } - const result = await c.listTools(cursor, undefined); - this.tools = - cursor === undefined - ? [...result.tools] - : [...this.tools, ...result.tools]; - this.dispatchTypedEvent("toolsChange", this.tools); - return { tools: result.tools, nextCursor: result.nextCursor }; + // Drop a concurrent load (double-click guard) — the in-flight one owns the + // current cursor; return it so callers don't misread this as "the end". + if (this.loading) { + return { tools: [], nextCursor: this.nextCursor }; + } + this.loading = true; + try { + const result = await c.listTools(cursor, undefined); + // An undefined cursor is page 1 — replace the list and reset the count; + // a cursor appends the next page. + this.tools = + cursor === undefined + ? [...result.tools] + : [...this.tools, ...result.tools]; + this.pageCount = cursor === undefined ? 1 : this.pageCount + 1; + this.nextCursor = result.nextCursor; + this.dispatchTypedEvent("toolsChange", this.tools); + this.dispatchTypedEvent("paginationChange", this.getPagination()); + return { tools: result.tools, nextCursor: result.nextCursor }; + } finally { + this.loading = false; + } } destroy(): void { this.unsubscribe?.(); this.unsubscribe = null; this.tools = []; + this.nextCursor = undefined; + this.pageCount = 0; } } diff --git a/core/mcp/types.ts b/core/mcp/types.ts index 444f4f34..cefc10bd 100644 --- a/core/mcp/types.ts +++ b/core/mcp/types.ts @@ -123,6 +123,14 @@ export type StoredMCPServer = MCPServerConfig & { * false (the default). (#1402) */ autoRefreshOnListChanged?: boolean; + /** + * When true, the tools/resources/prompts lists are fetched one page at a time + * (a manual "Load next page" control surfaces the server's `nextCursor`) + * instead of auto-aggregating every page on load. A defensive default for + * servers with very large lists. Inspector-specific. Omitted on disk when + * false (the default). (#1721) + */ + paginatedLists?: boolean; /** * Maximum number of HTTP fetch requests retained in the Network log for this * server (oldest rotate out past the cap). Inspector-specific. Omitted on @@ -545,6 +553,12 @@ export interface InspectorServerSettings { * and the user pulls the new list via Refresh. (#1402) */ autoRefreshOnListChanged?: boolean; + /** + * When true, the tools/resources/prompts lists fetch one page at a time (a + * manual "Load next page" control) instead of auto-aggregating all pages. + * Default false. Server-wide; the per-list sidebar toggle edits this. (#1721) + */ + paginatedLists?: boolean; /** * Maximum number of HTTP fetch requests retained in the Network log for this * server. When exceeded, the oldest entries rotate out (and any deferred diff --git a/core/react/useManagedPrompts.ts b/core/react/useManagedPrompts.ts index cacb8bd8..db9b94e1 100644 --- a/core/react/useManagedPrompts.ts +++ b/core/react/useManagedPrompts.ts @@ -12,6 +12,8 @@ export interface UseManagedPromptsResult { /** True when a `prompts/list_changed` arrived since the last user refresh. */ listChanged: boolean; refresh: () => Promise; + /** Acknowledge the list-changed indicator without fetching (#1721). */ + clearListChanged: () => void; } /** @@ -71,10 +73,17 @@ export function useManagedPrompts( // afterward would wipe that genuinely-new signal and the user would miss // it. Clearing up front acknowledges only the change in hand. managedPromptsState.clearListChanged(); - const next = await managedPromptsState.refresh(); + // A user-initiated refresh forces a cache-bypassing round trip + // (`cacheMode: "refresh"`) so a modern server's `ttlMs`-cached list can't + // return stale — and re-stores the fresh aggregate. + const next = await managedPromptsState.refresh(undefined, "refresh"); setPrompts(next); return next; }, [client, managedPromptsState]); - return { prompts, listChanged, refresh }; + const clearListChanged = useCallback(() => { + managedPromptsState?.clearListChanged(); + }, [managedPromptsState]); + + return { prompts, listChanged, refresh, clearListChanged }; } diff --git a/core/react/useManagedResourceTemplates.ts b/core/react/useManagedResourceTemplates.ts index 1f09d09a..3851769e 100644 --- a/core/react/useManagedResourceTemplates.ts +++ b/core/react/useManagedResourceTemplates.ts @@ -52,7 +52,13 @@ export function useManagedResourceTemplates( const refresh = useCallback(async (): Promise => { if (!managedResourceTemplatesState || !client) return []; - const next = await managedResourceTemplatesState.refresh(); + // A user-initiated refresh forces a cache-bypassing round trip + // (`cacheMode: "refresh"`) so a modern server's `ttlMs`-cached list can't + // return stale — and re-stores the fresh aggregate. + const next = await managedResourceTemplatesState.refresh( + undefined, + "refresh", + ); setResourceTemplates(next); return next; }, [client, managedResourceTemplatesState]); diff --git a/core/react/useManagedResources.ts b/core/react/useManagedResources.ts index 9e8d2e70..8fe03172 100644 --- a/core/react/useManagedResources.ts +++ b/core/react/useManagedResources.ts @@ -14,6 +14,8 @@ export interface UseManagedResourcesResult { */ listChanged: boolean; refresh: () => Promise; + /** Acknowledge the list-changed indicator without fetching (#1721). */ + clearListChanged: () => void; } /** @@ -82,10 +84,17 @@ export function useManagedResources( // clearing afterward would wipe that genuinely-new signal and the user // would miss it. Clearing up front acknowledges only the change in hand. managedResourcesState.clearListChanged(); - const next = await managedResourcesState.refresh(); + // A user-initiated refresh forces a cache-bypassing round trip + // (`cacheMode: "refresh"`) so a modern server's `ttlMs`-cached list can't + // return stale — and re-stores the fresh aggregate. + const next = await managedResourcesState.refresh(undefined, "refresh"); setResources(next); return next; }, [client, managedResourcesState]); - return { resources, listChanged, refresh }; + const clearListChanged = useCallback(() => { + managedResourcesState?.clearListChanged(); + }, [managedResourcesState]); + + return { resources, listChanged, refresh, clearListChanged }; } diff --git a/core/react/useManagedTools.ts b/core/react/useManagedTools.ts index 85eb5987..fa71452d 100644 --- a/core/react/useManagedTools.ts +++ b/core/react/useManagedTools.ts @@ -10,6 +10,13 @@ export interface UseManagedToolsResult { /** True when a `tools/list_changed` arrived since the last user refresh. */ listChanged: boolean; refresh: () => Promise; + /** + * Acknowledge the list-changed indicator without fetching the aggregate. + * Used by the paginated Refresh path, which reloads page 1 of the paged + * store (bypassing this hook's `refresh`) but must still clear the indicator + * the managed state lit on `list_changed` (#1721). + */ + clearListChanged: () => void; } /** @@ -66,10 +73,17 @@ export function useManagedTools( // afterward would wipe that genuinely-new signal and the user would miss // it. Clearing up front acknowledges only the change in hand. managedToolsState.clearListChanged(); - const next = await managedToolsState.refresh(); + // A user-initiated refresh forces a cache-bypassing round trip + // (`cacheMode: "refresh"`) so a modern server's `ttlMs`-cached list can't + // return stale — and re-stores the fresh aggregate. + const next = await managedToolsState.refresh(undefined, "refresh"); setTools(next); return next; }, [client, managedToolsState]); - return { tools, listChanged, refresh }; + const clearListChanged = useCallback(() => { + managedToolsState?.clearListChanged(); + }, [managedToolsState]); + + return { tools, listChanged, refresh, clearListChanged }; } diff --git a/core/react/usePagedPrompts.ts b/core/react/usePagedPrompts.ts index f8463170..94650c42 100644 --- a/core/react/usePagedPrompts.ts +++ b/core/react/usePagedPrompts.ts @@ -10,6 +10,10 @@ import type { TypedEventGeneric } from "../mcp/typedEventTarget.js"; export interface UsePagedPromptsResult { prompts: Prompt[]; + /** The server's `nextCursor` from the last page (undefined = at the end). */ + nextCursor?: string; + /** Pages loaded since the last reset (page 1 = 1). */ + pageCount: number; loadPage: ( cursor?: string, metadata?: Record, @@ -18,7 +22,9 @@ export interface UsePagedPromptsResult { } /** - * React hook that subscribes to PagedPromptsState and returns prompts + loadPage. + * React hook that subscribes to PagedPromptsState and returns prompts + + * pagination progress + loadPage. The state store owns loading; this mirrors + * its observable state. */ export function usePagedPrompts( client: InspectorClientProtocol | null, @@ -27,21 +33,42 @@ export function usePagedPrompts( const [prompts, setPrompts] = useState( pagedPromptsState?.getPrompts() ?? [], ); + const [nextCursor, setNextCursor] = useState( + pagedPromptsState?.getPagination().nextCursor, + ); + const [pageCount, setPageCount] = useState( + pagedPromptsState?.getPagination().pageCount ?? 0, + ); useEffect(() => { if (!pagedPromptsState) { setPrompts([]); + setNextCursor(undefined); + setPageCount(0); return; } setPrompts(pagedPromptsState.getPrompts()); + setNextCursor(pagedPromptsState.getPagination().nextCursor); + setPageCount(pagedPromptsState.getPagination().pageCount); const onPromptsChange = ( event: TypedEventGeneric, ) => { setPrompts(event.detail); }; + const onPaginationChange = ( + event: TypedEventGeneric, + ) => { + setNextCursor(event.detail.nextCursor); + setPageCount(event.detail.pageCount); + }; pagedPromptsState.addEventListener("promptsChange", onPromptsChange); + pagedPromptsState.addEventListener("paginationChange", onPaginationChange); return () => { pagedPromptsState.removeEventListener("promptsChange", onPromptsChange); + pagedPromptsState.removeEventListener( + "paginationChange", + onPaginationChange, + ); }; }, [pagedPromptsState]); @@ -53,9 +80,7 @@ export function usePagedPrompts( if (!pagedPromptsState || !client) { return { prompts: [], nextCursor: undefined }; } - const result = await pagedPromptsState.loadPage(cursor, metadata); - setPrompts(pagedPromptsState.getPrompts()); - return result; + return pagedPromptsState.loadPage(cursor, metadata); }, [client, pagedPromptsState], ); @@ -64,5 +89,5 @@ export function usePagedPrompts( pagedPromptsState?.clear(); }, [pagedPromptsState]); - return { prompts, loadPage, clear }; + return { prompts, nextCursor, pageCount, loadPage, clear }; } diff --git a/core/react/usePagedResources.ts b/core/react/usePagedResources.ts index a52096cd..25336a5d 100644 --- a/core/react/usePagedResources.ts +++ b/core/react/usePagedResources.ts @@ -10,6 +10,10 @@ import type { TypedEventGeneric } from "../mcp/typedEventTarget.js"; export interface UsePagedResourcesResult { resources: Resource[]; + /** The server's `nextCursor` from the last page (undefined = at the end). */ + nextCursor?: string; + /** Pages loaded since the last reset (page 1 = 1). */ + pageCount: number; loadPage: ( cursor?: string, metadata?: Record, @@ -18,7 +22,9 @@ export interface UsePagedResourcesResult { } /** - * React hook that subscribes to PagedResourcesState and returns resources + loadPage. + * React hook that subscribes to PagedResourcesState and returns resources + + * pagination progress + loadPage. The state store owns loading; this mirrors + * its observable state. */ export function usePagedResources( client: InspectorClientProtocol | null, @@ -27,24 +33,48 @@ export function usePagedResources( const [resources, setResources] = useState( pagedResourcesState?.getResources() ?? [], ); + const [nextCursor, setNextCursor] = useState( + pagedResourcesState?.getPagination().nextCursor, + ); + const [pageCount, setPageCount] = useState( + pagedResourcesState?.getPagination().pageCount ?? 0, + ); useEffect(() => { if (!pagedResourcesState) { setResources([]); + setNextCursor(undefined); + setPageCount(0); return; } setResources(pagedResourcesState.getResources()); + setNextCursor(pagedResourcesState.getPagination().nextCursor); + setPageCount(pagedResourcesState.getPagination().pageCount); const onResourcesChange = ( event: TypedEventGeneric, ) => { setResources(event.detail); }; + const onPaginationChange = ( + event: TypedEventGeneric, + ) => { + setNextCursor(event.detail.nextCursor); + setPageCount(event.detail.pageCount); + }; pagedResourcesState.addEventListener("resourcesChange", onResourcesChange); + pagedResourcesState.addEventListener( + "paginationChange", + onPaginationChange, + ); return () => { pagedResourcesState.removeEventListener( "resourcesChange", onResourcesChange, ); + pagedResourcesState.removeEventListener( + "paginationChange", + onPaginationChange, + ); }; }, [pagedResourcesState]); @@ -56,9 +86,7 @@ export function usePagedResources( if (!pagedResourcesState || !client) { return { resources: [], nextCursor: undefined }; } - const result = await pagedResourcesState.loadPage(cursor, metadata); - setResources(pagedResourcesState.getResources()); - return result; + return pagedResourcesState.loadPage(cursor, metadata); }, [client, pagedResourcesState], ); @@ -67,5 +95,5 @@ export function usePagedResources( pagedResourcesState?.clear(); }, [pagedResourcesState]); - return { resources, loadPage, clear }; + return { resources, nextCursor, pageCount, loadPage, clear }; } diff --git a/core/react/usePagedTools.ts b/core/react/usePagedTools.ts index 0f9254aa..026b5831 100644 --- a/core/react/usePagedTools.ts +++ b/core/react/usePagedTools.ts @@ -10,33 +10,61 @@ import type { TypedEventGeneric } from "../mcp/typedEventTarget.js"; export interface UsePagedToolsResult { tools: Tool[]; + /** The server's `nextCursor` from the last page (undefined = at the end). */ + nextCursor?: string; + /** Pages loaded since the last reset (page 1 = 1). */ + pageCount: number; loadPage: (cursor?: string) => Promise; clear: () => void; } /** - * React hook that subscribes to PagedToolsState and returns tools + loadPage. + * React hook that subscribes to PagedToolsState and returns tools + pagination + * progress + loadPage. The state store owns loading (incl. the connect-time + * page-1 load in paginated mode); this hook just mirrors its observable + * state. */ export function usePagedTools( client: InspectorClientProtocol | null, pagedToolsState: PagedToolsState | null, ): UsePagedToolsResult { const [tools, setTools] = useState(pagedToolsState?.getTools() ?? []); + const [nextCursor, setNextCursor] = useState( + pagedToolsState?.getPagination().nextCursor, + ); + const [pageCount, setPageCount] = useState( + pagedToolsState?.getPagination().pageCount ?? 0, + ); useEffect(() => { if (!pagedToolsState) { setTools([]); + setNextCursor(undefined); + setPageCount(0); return; } setTools(pagedToolsState.getTools()); + setNextCursor(pagedToolsState.getPagination().nextCursor); + setPageCount(pagedToolsState.getPagination().pageCount); const onToolsChange = ( event: TypedEventGeneric, ) => { setTools(event.detail); }; + const onPaginationChange = ( + event: TypedEventGeneric, + ) => { + setNextCursor(event.detail.nextCursor); + setPageCount(event.detail.pageCount); + }; pagedToolsState.addEventListener("toolsChange", onToolsChange); + pagedToolsState.addEventListener("paginationChange", onPaginationChange); return () => { pagedToolsState.removeEventListener("toolsChange", onToolsChange); + pagedToolsState.removeEventListener( + "paginationChange", + onPaginationChange, + ); }; }, [pagedToolsState]); @@ -45,9 +73,7 @@ export function usePagedTools( if (!pagedToolsState || !client) { return { tools: [], nextCursor: undefined }; } - const result = await pagedToolsState.loadPage(cursor); - setTools(pagedToolsState.getTools()); - return result; + return pagedToolsState.loadPage(cursor); }, [client, pagedToolsState], ); @@ -56,5 +82,5 @@ export function usePagedTools( pagedToolsState?.clear(); }, [pagedToolsState]); - return { tools, loadPage, clear }; + return { tools, nextCursor, pageCount, loadPage, clear }; } diff --git a/pr-screenshots/01-tools-all-pages-default.png b/pr-screenshots/01-tools-all-pages-default.png new file mode 100644 index 0000000000000000000000000000000000000000..6b9ed126a139df12049e96279c6773f85a57a382 GIT binary patch literal 42545 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%0A#zL>O9@T~%s!tTAPTOrl zLO}LA@hJ5yo8ibW^{hz6t0}4s%1#Or#>)1=7fG6cs=#N21FICA=ivap6SIOePf6KfT7a7m z`58*`KdQyALQWNyLMH^;Uya8vc&_5&;+j2&z7rW4f8cn7g5;BY>Z$5#YkgchG7!U$ zZjQqe>!4y0YMV7B3Ee$oIRB@D^NaC`T>R@TN~17@WL~n5B&bt>lJLj7Ps9g_uRe>h z6(2kwF0LxZI11_bhhVcv5$WPm4NJ12p4E=RNB2NFAcB$eT=IQ@1WClO_lh?nK{lnk>6^VsR3ex<83-})h=>Cs|*#GJm zjnjhK8Uv5ztBjDX?Sa!Z>|asLIPbv-IVGiHnYEdSh=&v-B=WfQ>Yt>ZLON;#_In&0 z9Du)+RaCGaDttYCo~SkWZvm&O(~vPtb5}kHmhc&r_3*^xBqwBeC_}K5PrXA0Y2H^C zP7x?JHnx_&{?YeKgbTw+hsF>_l%>l1UpW4Y6ks^uF$6sB5^(~?14E=Ry4TKZw$SW) z_UzfFrlzB#qa3YfIo{8qcVuOjhsva-q*Nhk*^KME?gE<0UlFiMsU2e;xK03x0(% z%DmC5UPUI;G^(Wv*0y$b7=&DSckToR1u>dyoO3iSG!9zA+QPG0le zM~V?54a%{mp1Gsul)wGL|BnJx|K#1dlnh@#KWu8n2Xxhy7~Hl3_s*Z-`jI6W;d?mL zB#ex*#V{<+M4I&S^SK`<1||8Y~R8qQhxL5Pk~N?!~Jb$`VR>S3BLmLncuvOMcYq_y(gXmIyX-~ z@HbCL`D=*pzZ#DI|0`zvcilvG+`;)2LLE6)PqS-76RlA0U!GD8BC@`;`X znH>tJ{Ne{*C3Rc{{M8nq{*;c@l?iCKH{fA!IwNuVeFIle;csMQu(v5N&C63$cd)RqaB<=N@N9cR ze@{=YcBeKJ1Kf^ok^#ryMny^4S7uYNAp-vO0Yg+7rO_wr=1hAsiKj=PZ%7EG3eY5# zH!fYeG^<)HR@9$6&=@7I9!Nf7ox(*x1}=j?EXzMOv+5i_u`D4foKdy>Mx zp@TDnH`iWGTbprbvMesMzCRI=3w_{bL-n=_C~pwTjxH~QA3dqOy!<$yLFuaVgWoTq z<6XXXO`5W*s*108uH)nm-@X0vuX);kfLn--hItGPUMH$MEQ>!_)GGf|7BxV^b^O3B zS=Q47NEXJoZLt$Ku22-4&Kg$&B+kyy-&&i}L+%Ei*g=9I=eVJ3Wng2)EpFovoLm{=r( zE(F#yke;3{Mc8z4^(C*MqZ2^&aO3=i3(y&7_|pStZUwLfREAmD+4uMNq3=)$?Ww+^ zjHLFr(aA}yn>VBKf1EBskyJ?8p(0B`L7{z|uh$Q>2{kyhq|UHG!jdGd%xzJP@wRFvYXySuknMk}G26C%7H znK;eEhsv2RpYrqbgIX8}1u&lZ_~8R8mp2CI>RG@S`8wTVq+IaR00b#dMi}m%_ghr^ ztYU#aHD{aNaCup1sFI9~4A}Pq+5*I+|N3=1oL$9gGF5XKs2w_>JL;nO+=m>F521{V z&uXj=nFaj>px1wS2>krQf})}U>9BjKeS+RvQ>aP50$u~tz+f53pX5Vc=j#x(i%e&w zKwTe8_G=Nl0$JjcnV%n+{^ROgJv@jP{~EmhwwaHrmf}+QrUwMbf{fp(Zyc(el@{P! zyhX@m%R)z&1=uSV_vF65NWn&FYikRX@$=_3kkP|Y0kb0`kKo>Ob92Ec!s8xeT{CED z9%VlLcoDix8Kq)pXsbV)#z1`wDDwiAdDq)yTt_?O@DvEyOy5BHw}a@{g%Y_3XOt$( zB=~n+7q`%g3jxc2Hda*>- zXl-kZfS;cq%$|luiYHiq%6;f2Pvt%&GGi>rXl+^I7$9v#LmpGGJ{xm}uHXSfpou{O zW@qcBBLwElFS6BLrMNIKykXdq4uRo59hO2~?P+NleRrtxZ!QlH*IbT>h&cT}Xv8aZ ze{cbbXD_qVG5%3lVpOHJa{Kl1(7&&g^8XMjYJ&gEIfwss6#pkemGuh^g$_N`>x2uC zlxzj>y4SB>`6uHr>Gr_kLjG>`&CQ71H&-rk%k+n;#0=mU!4E#lpF2AUEI`QVy@O-JkxBVG9eBzD3>5Z;#%|EXR8~|#6`oW) z0(E4~%kLjNJUnJ*W+3dtNhlXDT)=@6*(AtQ+U%Cgw#$$9=D~zn9STJm<$HfHyovuX z!yBgzNqaG{q(exDW7s1h#E>kofFk18WyS?QrdJfw)=owpqkzVXhldwyGg*fP+;H4u z-5!ZzPy@{a0|?QSz~R8ist68H`1AQ(VS%L|2d1CI4Pyu46djMLhf zX$49kFF$_>o(9wpprfDEwbBiX>H+ezk0+*;wKe=@3)p(!FXzvnAE-+Wef)N8auTHj zf*1}#IvMsC&~+GESjdAhkTOrUw95X~vZ}|wFsvL3!_o_jh{-2QH&pF->4}&k`N4l+ zvOb6;HZvpqM%EAok7L~U9ODO0d_xi)Q9^zHI4O69 zzlx6Klv=+0Md7WxTmBS&Sn#A>Wzo<0%dq_aLh;G0`M(fJYbAYk1l9l)0x%=xW>lc^ zL4pVY3n>MKUx7>pC584CN>wdmL#Z7@pa2I#$XFhOw*!S)OBuj?BCKag{#?WmWH~r& z8@|wCaP5ph((F%JQ(H?xPM+AJ1XXjADCf~h;33-E+MtZ}M>q!pxu(9}4{T1;Ul~Kp zBte*ipB4}V!~$NwuwGIJB&mNlqgw@@9^|w9kpBXG@eANfCxJBJA7c-`!WaNPJ_iQ}}eXkjj7=Rk#?+P;k~qAAlUB1@Igo zdxiJxc6+9{xS?`sijBbx0{lI2U=}d4S05Qr+iLO}r~Iu!aBwgE=rvW||kHts&`z2HqF|H2vN_WbMe(_N#zJ3Sy98b(z)6%jslEE1ef*2aEq%|Mztd4^}7b^QOGdtxS-_-(F z^>=F&1ks?Sux#}wuJ1d@lzxBsaG`73ezA{n7B1(P?AhYOztO;dd3G8mIXO8&$I(Fq z(lIz25N000{5XD^3^>DCfK&=;Z&&$DziM}Vnv;X0gI(i{OjT53&XDH-)y5s^B`wrq zmf<1s0*56zMtbePWLTf`TqTSg3u%LDEy^L(U;LKm34+x)_ba)g&s(RUEZ~Dm2TB2| zeqLN${PbydoH>s-8S+b*^X^^-Y^SH7bxHGojz;OfDX>1^1C10&Vf=+q$Fag*`zs>+ zhXpXG`Knq2j{vMXr}4z6KagDDc|1_eg03-`)g)euk<}W@x*ifHC|2FX#bpPPF&sHb z0KQV7UV%jZ^zlCm089fFF+paqili%owN_^f`h-n5AT$-A1RTKuCvp$vzV-V9hXJzt zyY*OgeSM0>D7RJ6Q;kA}Dz+NJ^XV#&fAw!Nz|L8edU)C4SjVy#WD2ghpw|6UtYA`$ zufF4W@%lD2;IxerVxPDF69)x3$ghzLr60p-C4xc+S*C)bRD#(?8I&q^4%L^oM8 z8)vjAiJbbINni!AfHm3Q&@*H^Vqd%Su%@Qwlb09DX7lCCm*8oIs#vwPw|DwafJh5f zGTYaHb0mK)I7(nMT4qf_*cwPMn-5M|jg8b`&&9+9@5|vZT{wyEZD(>PDDBl9sg?3U zv_(0IINl4IxQ8NOrKEgCb|K_?e)@!xu>ZLe#QL;K{WZZCr3PdzZQ-B*fh-uxMJNHn z-ODE!KqLkpzqW8B1fIWS9{!`U+2Zsx*&x)WHsjb{fD`1Oo9%x}JP6T$`u6P`s4_*u z**c&5L}QRX_#^yMH|<5a5ctAbW<&cM;Kskz^O_zX6ej|Nb~1lF`+u@bR+d@u*Q#VD z(@h|q_0uJRI1OwPY**k`56$1>m}dfR1$|zS=`QCnpldv(2;yWwTx}zxVPlCW!lsM#z;c3aFUWrlD_+$XK~rj0$csBcrtq0;ar!D@gTzd_-J60 z^YI^CKp39o3~8@$>0S=5|GBBhzh1vXMT{hIR`(60|!|BL?tC8R>0&Gz*9abvR&sj z-ZhSEy*hj5Wxw=U?%Sca^mySg)euGUpA?$%-Qe%<(%!v${m~lx^!*Ui z8Wkn#Umw$-MMc#cHYbGa?^`FE#gm0Aa9aJDcRjZa;Xh}x$VO2Ac_n@#cKZ==(hu~B z^MB(9k2S!i?KY{%C#HIcOMrXs!@zv4&?7!u4J;osqZNNsAub<;YUq#8|U8>=EXsC56lo$s?6bPVMXip<-Ub{TpQ|Us6HYYf4v7IaI7VUH<%r{CfF?I z$p=u%4q1+SE-Wmpm_m0LOdpH8JvPf_a1k7EULS~f$!HiT{Gc)b8n+3MQ=};utzpS4 z-#odwH@H_=;Lx6kkPtR_9&l9V7BoB5*VGiQ)ltc(Dr7-J6{>j$_ALS?T?H63FjYSA z^tihI7B1{LGdj!2J19=*jVZyob?rotlVnr6UTB4*4!(tHH=bFEemdb~;tUXceH1|&e|NZU2RyDV_}3NT<_A|gC6-S-A}9;T_}SO=?0(M1B6 z{7tjpV|iCY_U&q0fTUFjtIbhFiF^wh1yDFsP2spaP>f3569$Ib?H%=&(UwQN=X}5{3Dzzvr~q`G8+RfQaulsh4Zqi_$yf4$6kVhq=?~XP8Uwnme_fQkEPpH7CTGV8YIxbYdK)cor?Lhz$R-IswIDl~mvKvBH9ZjhXEDdPV>i_}rvwHXq z90H48ji8CguA?mkQUR!M-YL?%re$~?(&Uwq?`mIRR6qf64_Ygrssd0Pw!o$0`E-bQ zQ2UMp_7Or2F7&AK^YewqK_n*{B=5_9Rpqu)Sc%U5KnZ|##W)-wNetio6;JotSpjWr z?G(ARiYJ!3Fzyfsp&~ck7>Y74<+9t8NmDc{7&I?|a8r36rT>Dj<@N(u0X}~^2K^Nzlq4ig0ni>Ml8@5FIYm{Td?MVukAefl*weLvKxRMSSb`n%WbZ-G z_d!SsqT+IGBtf?mry9|32P7kZ(dW4p$YNvUId(bJ+Hm(pi>tZp7heS zI!L}2K;($hF2ip?uNkT_4Q%fnh+`^jLzwjLwRwJi2IW7>KIPF#=+=8tYH0-RprBVS z1(#o^i8#nTV8lRl>AokD;av z{e`8_AP4VycM{K@yXbgc9okX>o4D5{!g5ff1Hf6a0v$F4^j`4gWxG?M(44sh3FdRz z6O>~sqgZOHArqn56oH&xYK*xGC23};EI@#tD7ppv8l)ybMC*ZeJIj^%Um~#v=ji?O zIBI9LyVJvUgP9G605>S+>lT4nUf^5&E{hF+`_Ij~4PUo|Fj%Hcm72EAv?+RXq%m*b zz6E_IRo^WhH&BiTu>pjJV~Lmz%FCr-X>>!eCmJmUPA`JA%}>05O2+iVMW>T14buxn zMz7_ddVT?PG0{JTY9VSD*1d2Z54WR4;^aLPYePLyA?3LKe-Y>nu=(Z_qDVr<%>7%> z`O)$Fwr`$kOqhVxN4{q0HYYN*j&me2BqHCoYVPvO+m7DuD^WKCe44?&yeC9&X!{_W za{FD3b)NRMJJjxHn4azwUM9RJiL(n2?p<2%lpQ=cV+@wIc4;25_02G+o8S1IFx*y* zZgM@2re8ztxk_n`N3iE&8Tqv?GB9*aOeJx?^Sn%WbM)hVTjgh=RnuVmtLv1%Vq+hC zI@^fu(7y2T3zaVrOAEp2mlYNFJacuKDHpEqf_tEoh($g3rfm~og|3n5!w94+CC$iX zW0UT5#T-(9!rk5Z`|m0UpIjld>P(T{FKSX)M_mK5F!IpmC_b^}B#-&ZA@hS1mF=9V z3-}+I*aXp=Y#pHDF;J=>4QMA&k0Y-wO>>GY~b0x{8&7Iv} zG~27KmP?kQU^QMw)E;8K7J+lrMiwd7*j#T=G%uDAX{;OL{Gzp4eL^_Hfzf2^f&)j+M4~sT29kbn z5sv1?(o-%Y)oP6btN_B!4U9lv^siqW8N$8p>XrgH?|{?U#%!gg}_9L=#b zYD?G)`aMJod{2^`#Od7yKq87p)YzjUL2uaP`dqH0E1)A^lqYZl2n;BdsxrmCv!a|{jGsI0HMnZ+*`P7Ba5L& zzO-7loHOsq$|D#Pme9?_SVt2P_?gY|rE?7?w7dO?ea4z(T)ix^NF3I+s_x3B_QRQF z=9#dfVXlLn9EYM*i`H7MaQZ{962gZ&OC{>{lE}lxECIq0Tk9&uf(Aq;`E{9;kaB_k zu7t>eH0{ofNZW0VdijIhHYAtjawDJnyeE0PCZ!qQ&(h7I4^HmFOFe2$l>((}Lzf6? zmGVoC#xGVA?)R5%>)w|AgqXVSl{u=_d^pudb!^*uK?})k-AXm(#C-EEdS(>I6pODv!xFqE?(8w5ovENh1< zKA0n9PGhShh~?uJ?n_5U9ZSugmS(pRW{2;}Uu;@@%I_&QyU>j8uD(O6P;rt6i} zc)c2HWRckT`(5mW2Sv?<<~mu@JI6yi-apHZ)e2(D#dCP>-CFB-?ZxrndZdxi_PjxP z$uw(%{vIhj2Ff;uO2kl^hCBE65%1XS?sPc8VQR90JoUlw#w=0^L5H0YJWRkox{@Al zP-3$3et+?>HbI)#b+tfgiJD2&Jv@|?d#w|J-%_qR$Tm7_hrxxT$m4$_;3M^7?q<{z z9OL(I&JEt`NGu-|YYcUKA8w(|c2s$6yS2OVYPvh_3rI=7Ygo^HQB^ihX!2RH zWMW%37L{*1tzxPAu3Lbf;&O;(pZWG+c^DlPE0cOk6bW5ve>(eQh6-+5BfTEPp0BSQ zf1|`pE1m5V7WPssG+gB-v|U?ILNMv{?d1BEIPr!a@2c0$9}_5*?U@sbg!#)N^;v#b zL=K@x*%mG(yWa|mTfAzqDKq!y(Z%9NlGlf96LS< zT8$CiYLT1}5SS%zOifM=+bzcspwG7qc3bh;y*g9w~YlT_q-})75ZU)d)eL_4uXV$NaDHW`yn+s=WB_%&gBh&qlU3R3M}9*gk2r)ftwKEHpF8%JnHswD19CRSH!E!y(VH(v3{ zsYxfXrQrhk`9Ny7G@F^lS0?-2ItJyBv`sJnjy`}q#7Qs*RojxWzUL>-Qv7C0 zR(G>f%xwCr0MEY7vO{h?jc7*6TD0)-T0zR*c2clp%!GfG!%~?;4aBeQftXD~o3Ox; zz1O>VOs{(U*RdZeB6Y$|gaBM4(u+uROIjB9^9?L)w<}{J1$W#Pq%HTvv#Mx{7@&*U zi3`I4Q&O#NC8K*tG4stTmrdRkc5Q2G#Vjri&KIT(i>1iPjA$K(e^PWPD!>{c*76Fox-FXJIya#fNk@+gaaQcm6)se@P-aw z%UjxXK$7{#Fe{q~RwEW|+NnSCyzcqH@$LPlA zHyvq8)7kD#N_p0e#JtXoI&CwbKRRIbi60F5urm(R6>$(%U%pTyu8P*Wn8-UttFW{8 zCF>)hT|9)Y_*h#+4#&<6L(a6ViN-NklrP?1Lm~#L$W>2^qv6U?!vzwDxtm_~<@9@L zKm5A~Ew#vLGYDIlA5atwZ@yI6fqlA#dYtSiL9X$h{5JCUj)QGia>|L`#1fuL{lE1H?G%w2NF0)--N zGs__H1d-u9zV(&I8*_yA4hM}6iv{yEjl$_RSw&?G<`tRKh}q8Z4tqNryWP%iqPVMv zVJuIlvbiI>i^lC^jt`rh_cCJzBJXl7x@XWV*me0JId@j0nqAbBXL@cFHhf{-Ur-Gi zw4NR*TJJ)LOB!$Tj*nFdS6Uwq<9COf_3dFUgc?k_y!qNeQQ5lpIe)-n8%?cRNj{Ti zm!D|p>f!Ka3&KSda;m(HlG*edFT-07UG$eT5eWkIGq!t_ANc_!xX|FQ1C&Q4Vax&`5)N;}02M<|Kf9%WC zMkm-PcJrOUc0TpmG&5zCABkm{h>3bB=33Sc z>mO>#l@+Tki!jB<#bcImQByNAvWA6(D85>tH3>jFb0*UWsBu6Em(3LKlJ~*r1Ov&U z6=}pHWBL9`ViuE)E!eL---Kt<%9s!h%JOU`f24f1s4 z4mT}6=yq!s7&;csJe`ub59IaUc7iEJCG4dWh6`-11e*43MgsivDTTy5t+@P)Wz$P= zYGR+(az0;B!qdaz(Hj<)B@XEJ^#1;K)`wiov>6jlE2HFgHH_)SOZiR6A%|@_YEsdo zhmW1H@Yc6%wg>F>Sa$A3MA%kHE_;ODaQiCgm%zcblmDpy##N4koy9?_^QZH2 z)Kg7|`9cA8MBy(shBtRLO%?CfG^7SRe=No|I@2OQYPhDlsOYj=FsyT^hmWv3TuilQ z???`Y^XN){dS>&S#nT>K5+GzUn+uv&SqYLDBTXKVDj~6SsEFNeE@Gi9#hgiP_r7Mb zv#>s^MwQ#A7lf8(<6^f(&Q|dGl9P~)cm90y)mgitWm>{VE15L-S^(t6^Mah6LRcM4XDF@^<*^zf%WkV!GzF7IbWgiII*hQ%Mc)^R z?ssVO^2FU@oAys!$+UZwK@)#0>mcrdX4A)q9TxsZgl5Y>2@yDeg9`oI?#%iXs_;41 zs#2@zvBUEOuYIRaGzT|H$@bt`(V@&CN!nR^y<)w&ujKj75~gR^^H-B))AlM~B{-Q~$ZZnYN>$J>DVU4eL}5FOL_))Z(^#8DB6Pbw^wty|yjVtFz!*oaNPI`wVc+TGw#qs#U+j01xu1-w(UBOj&{M~~;gB@1s*664ZBw#L zc6oCISS((1={#-g#M~G{_UVM_+gPflenH55e$VA?zeOg)ameVocTMM< z<&f&uvUC1q_zp6cV~6mWS6vhMmAStftm)&c#krN+Sohqimw#Z9FDjX)xYxwaI9PL@ zS-HJH@?&bt>rsbnx}u)ArR#j%B;2&zSu_EZ@@$9xuPePIQ=8r2cE?Y?qdMBy>RCOP zjvNOZb{OA&wsq>N1F=CFyFJow+r$0})+!a{CH%r>Y=r&cMkcm5YqW6mWGh)j$#PUw znRS@;+N!|hk)iM?Xdj%AKQr}sO1FDjrYo7gY&Ra<*fAq7>&14OX-H-BqcE#svEV(v#HU^?sq4;5XKy;b&c)%={`^_M(M`YS z+VNF)e98$Q)QW@ir5DUBdbscoFgzK`6&-}XOH4i1=_99RGz$t3#UDOgS#fC2Pva>I ziI5YbLv)Ezk^29*aADY~+j$f+RT@P3SobBf%gZ|@`YNHAfir}nbp#OC8<*htBik4q>)ac1kNs+kdocpG>O(UahRln6~cdGwF8Er9}kw#scMm_dJ zWlScWq!dK5R6?B}fMIgHnS#j-wXUqL%{}n(ZZFx!;SL zc8|y9)3epOT&ZJh%=_U^*W^kin*q{p@y8tI;`$7dxqX9H(>Z+0|F##`P~N(!Wr!+EamJ3r6G9ghh}KxJ zA#~2ex2!&NMI=sOoH=trU+~#enFKX)e2y6dhqBr9rD{{O_ON{yKmB5j7938RD-`^n zB>QuR7;VK4qoy@5(2H5sgUJXieqf!uC8c+;Tc~At6h%)z_7>SDoH-l{FPk z5^hJf#u%vDA7!BTkQpg#H`_IpPkQY~+G?sVDl%^OYkAknQo4A=EJs=ou+EG^8PWMx znSEKYJi`7dOAF_SGds`IAxH>9Ti?QtI!V|?x4}E(kvT?m@3O3{_fU|_TYH3BXRF6XS-VuG0ym8et@WQ^({}*`W1tXl*9h*z4icy zvb`~hi=;#8)4d4;0K3{@Bc-NZe1{HpwY`0*duA8|8Fs_<1_N9iUry? z``pv)d+J>6rm7h!2jVJ}{KM87EryWuLjv>aUFc5M83YL=h&BMrYGdyE2*Te!t;}Vr z@F?7f4xV`Xj8BUoI&ht4M>u;Daw&{u>?1y0xk9agzV%_|z+#364(-#4l^93XTLYQr zIx-3>!Ka9K*jY~{yV+eo9*02-d`WDeDM&HvmBvo{wE)XV_Y53M-eXNsC|K}S>^aXY zj#ud9MOPrR-!rQc7zg8C+^}2m(Q0BvI`rVC0C}p+!#J|XerwZSCW2B=l~P)cDESJG z)S71g=-ZiWLhNX%xD_8v^H0gMZ)^4P4M)colMR9j`weAwUN{RZC2o|~HC7as_oAGd+^uo*)Yd=DaA5CG5THg@M zh09C0l}1M^k74^wB=r@@=Y8RWG}o&^M=2UoDjImR43~C;hVAzAa2S(a_NZS-fOeSF z=*3gZ;Z1DJj)tBM?N@`v9}e<%tL*RIZy@VnsokYjEqzSQnfB8G@y0LlRmD9g*V`#L z7w^z0-U>ucVFU#_k~BBg({5FWF66!hE>fGMi*B_Gj)!#>yH=L_a&+uQo+BFvB}ZTc z$4xF)=|?F+%-R{fSTchl7fjjY$^_Sxsa;(>w&b07b_}H-?$1XIOsT$2)hTy@o5EM zGFhxOI6cznF?X_=^|sLzj4Ba2+A+$i@)BCOj!JnNJ&!F%B0_Jy=3TjIozb+F=YGV9 zHNPGmN^vmi7$7?E+x^^O)B3OVG;A3&I>PDCzv!;|YSdog`AypNwhO)u&>eX?okEU` zBtb|H83~kTC-Y|QyBLN2GK}lBiIS*YRPSI?oQw1Re$HDxi{Zse?;MMvq*-7e@V*~= zxDXODt;WiY{)?7G#+O4!MQ7d(%zIZ)O zi?P}B!<##p+d&cn4T(eVnInPHy*+lcoVnih3n+8lCks`}VX&K?dH3q?FSnE(gE;B< lm^?4S1U`8Y6?xB{!SbkmcvG9E68>^VkWcJcI?u}w{~sX~vu6MR literal 0 HcmV?d00001 diff --git a/pr-screenshots/02-tools-single-page-first.png b/pr-screenshots/02-tools-single-page-first.png new file mode 100644 index 0000000000000000000000000000000000000000..5232b0fda7bbb687c01b8d3827d370b4ff8ba07f GIT binary patch literal 40812 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 ixw@QAqt2Rm!lVrO;9D=*7K0j* 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%0DIbtW3d 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 Sb%^^CQ6Z^kU!J~v`~LttF0|eN literal 0 HcmV?d00001 diff --git a/pr-screenshots/03-tools-single-page-load-more.png b/pr-screenshots/03-tools-single-page-load-more.png new file mode 100644 index 0000000000000000000000000000000000000000..1e8e3bc1ea98fda9c38da608b93191a293506a5f GIT binary patch literal 44709 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Ënciqpirjm# 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 Eg2$@{7XSbN literal 0 HcmV?d00001 diff --git a/pr-screenshots/README.md b/pr-screenshots/README.md new file mode 100644 index 00000000..4c18af57 --- /dev/null +++ b/pr-screenshots/README.md @@ -0,0 +1,11 @@ +# Single-page pagination — proof of functionality (#1721) + +Captured against `test-servers/configs/pagination-http.json` (12 tools / +resources / prompts, `maxPageSize` 4 → 3 pages each), driven through the web app. + +| # | Screenshot | What it shows | +| - | ---------- | ------------- | +| 01 | `01-tools-all-pages-default.png` | Default **all-pages** mode: the **Paginated** switch is off and the sidebar shows the full aggregated list (all 12 tools). | +| 02 | `02-tools-single-page-first.png` | **Paginated** on: only page 1 (4 tools). The row is *Paginated (left) … Load next page (right)* with *1 page loaded* on its own line below. | +| 03 | `03-tools-single-page-load-more.png` | After clicking **Load next page**: the next 4 tools are appended (8 total) and the status reads *2 pages loaded*. | +| 04 | `04-server-setting-checkbox.png` | Server Settings → **Fetch Lists One Page at a Time** is checked — the sidebar toggle persisted the server-wide setting. | diff --git a/test-servers/configs/pagination-http.json b/test-servers/configs/pagination-http.json new file mode 100644 index 00000000..a1807fb5 --- /dev/null +++ b/test-servers/configs/pagination-http.json @@ -0,0 +1,18 @@ +{ + "serverInfo": { + "name": "pagination-showcase", + "version": "1.0.0" + }, + "tools": [{ "preset": "numbered_tools", "params": { "count": 12 } }], + "resources": [{ "preset": "numbered_resources", "params": { "count": 12 } }], + "prompts": [{ "preset": "numbered_prompts", "params": { "count": 12 } }], + "maxPageSize": { + "tools": 4, + "resources": 4, + "prompts": 4 + }, + "transport": { + "type": "streamable-http", + "port": 6600 + } +}