Merge pull request #19 from modelcontextprotocol/ashwin/pagination
add pagination handling for lists
This commit is contained in:
@@ -74,6 +74,13 @@ const App = () => {
|
||||
);
|
||||
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
|
||||
const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
|
||||
const [nextResourceCursor, setNextResourceCursor] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [nextPromptCursor, setNextPromptCursor] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [nextToolCursor, setNextToolCursor] = useState<string | undefined>();
|
||||
const progressTokenRef = useRef(0);
|
||||
|
||||
const pushHistory = (request: object, response: object) => {
|
||||
@@ -105,12 +112,12 @@ const App = () => {
|
||||
const response = await makeRequest(
|
||||
{
|
||||
method: "resources/list" as const,
|
||||
params: nextResourceCursor ? { cursor: nextResourceCursor } : {},
|
||||
},
|
||||
ListResourcesResultSchema,
|
||||
);
|
||||
if (response.resources) {
|
||||
setResources(response.resources);
|
||||
}
|
||||
setResources(resources.concat(response.resources ?? []));
|
||||
setNextResourceCursor(response.nextCursor);
|
||||
};
|
||||
|
||||
const readResource = async (uri: URL) => {
|
||||
@@ -128,10 +135,12 @@ const App = () => {
|
||||
const response = await makeRequest(
|
||||
{
|
||||
method: "prompts/list" as const,
|
||||
params: nextPromptCursor ? { cursor: nextPromptCursor } : {},
|
||||
},
|
||||
ListPromptsResultSchema,
|
||||
);
|
||||
setPrompts(response.prompts);
|
||||
setNextPromptCursor(response.nextCursor);
|
||||
};
|
||||
|
||||
const getPrompt = async (name: string, args: Record<string, string> = {}) => {
|
||||
@@ -149,10 +158,12 @@ const App = () => {
|
||||
const response = await makeRequest(
|
||||
{
|
||||
method: "tools/list" as const,
|
||||
params: nextToolCursor ? { cursor: nextToolCursor } : {},
|
||||
},
|
||||
ListToolsResultSchema,
|
||||
);
|
||||
setTools(response.tools);
|
||||
setNextToolCursor(response.nextCursor);
|
||||
};
|
||||
|
||||
const callTool = async (name: string, params: Record<string, unknown>) => {
|
||||
@@ -293,6 +304,7 @@ const App = () => {
|
||||
selectedResource={selectedResource}
|
||||
setSelectedResource={setSelectedResource}
|
||||
resourceContent={resourceContent}
|
||||
nextCursor={nextResourceCursor}
|
||||
error={error}
|
||||
/>
|
||||
<PromptsTab
|
||||
@@ -302,6 +314,7 @@ const App = () => {
|
||||
selectedPrompt={selectedPrompt}
|
||||
setSelectedPrompt={setSelectedPrompt}
|
||||
promptContent={promptContent}
|
||||
nextCursor={nextPromptCursor}
|
||||
error={error}
|
||||
/>
|
||||
<RequestsTab />
|
||||
@@ -315,6 +328,7 @@ const App = () => {
|
||||
setToolResult("");
|
||||
}}
|
||||
toolResult={toolResult}
|
||||
nextCursor={nextToolCursor}
|
||||
error={error}
|
||||
/>
|
||||
<ConsoleTab />
|
||||
|
||||
@@ -7,6 +7,7 @@ type ListPaneProps<T> = {
|
||||
renderItem: (item: T) => React.ReactNode;
|
||||
title: string;
|
||||
buttonText: string;
|
||||
isButtonDisabled?: boolean;
|
||||
};
|
||||
|
||||
const ListPane = <T extends object>({
|
||||
@@ -16,16 +17,22 @@ const ListPane = <T extends object>({
|
||||
renderItem,
|
||||
title,
|
||||
buttonText,
|
||||
isButtonDisabled,
|
||||
}: ListPaneProps<T>) => (
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="p-4 border-b border-gray-200">
|
||||
<h3 className="font-semibold">{title}</h3>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Button variant="outline" className="w-full mb-4" onClick={listItems}>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full mb-4"
|
||||
onClick={listItems}
|
||||
disabled={isButtonDisabled}
|
||||
>
|
||||
{buttonText}
|
||||
</Button>
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-2 overflow-y-auto max-h-96">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { useState } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import ListPane from "./ListPane";
|
||||
import { ListPromptsResult } from "mcp-typescript/types.js";
|
||||
|
||||
export type Prompt = {
|
||||
name: string;
|
||||
@@ -25,6 +26,7 @@ const PromptsTab = ({
|
||||
selectedPrompt,
|
||||
setSelectedPrompt,
|
||||
promptContent,
|
||||
nextCursor,
|
||||
error,
|
||||
}: {
|
||||
prompts: Prompt[];
|
||||
@@ -33,6 +35,7 @@ const PromptsTab = ({
|
||||
selectedPrompt: Prompt | null;
|
||||
setSelectedPrompt: (prompt: Prompt) => void;
|
||||
promptContent: string;
|
||||
nextCursor: ListPromptsResult["nextCursor"];
|
||||
error: string | null;
|
||||
}) => {
|
||||
const [promptArgs, setPromptArgs] = useState<Record<string, string>>({});
|
||||
@@ -63,7 +66,8 @@ const PromptsTab = ({
|
||||
</>
|
||||
)}
|
||||
title="Prompts"
|
||||
buttonText="List Prompts"
|
||||
buttonText={nextCursor ? "List More Prompts" : "List Prompts"}
|
||||
isButtonDisabled={!nextCursor && prompts.length > 0}
|
||||
/>
|
||||
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FileText, ChevronRight, AlertCircle, RefreshCw } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { TabsContent } from "@/components/ui/tabs";
|
||||
import { Resource } from "mcp-typescript/types.js";
|
||||
import { ListResourcesResult, Resource } from "mcp-typescript/types.js";
|
||||
import ListPane from "./ListPane";
|
||||
|
||||
const ResourcesTab = ({
|
||||
@@ -12,6 +12,7 @@ const ResourcesTab = ({
|
||||
selectedResource,
|
||||
setSelectedResource,
|
||||
resourceContent,
|
||||
nextCursor,
|
||||
error,
|
||||
}: {
|
||||
resources: Resource[];
|
||||
@@ -20,6 +21,7 @@ const ResourcesTab = ({
|
||||
selectedResource: Resource | null;
|
||||
setSelectedResource: (resource: Resource) => void;
|
||||
resourceContent: string;
|
||||
nextCursor: ListResourcesResult["nextCursor"];
|
||||
error: string | null;
|
||||
}) => (
|
||||
<TabsContent value="resources" className="grid grid-cols-2 gap-4">
|
||||
@@ -40,7 +42,8 @@ const ResourcesTab = ({
|
||||
</div>
|
||||
)}
|
||||
title="Resources"
|
||||
buttonText="List Resources"
|
||||
buttonText={nextCursor ? "List More Resources" : "List Resources"}
|
||||
isButtonDisabled={!nextCursor && resources.length > 0}
|
||||
/>
|
||||
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Send, AlertCircle } from "lucide-react";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Tool } from "mcp-typescript/types.js";
|
||||
import { ListToolsResult, Tool } from "mcp-typescript/types.js";
|
||||
import { useState } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import ListPane from "./ListPane";
|
||||
@@ -15,6 +15,7 @@ const ToolsTab = ({
|
||||
selectedTool,
|
||||
setSelectedTool,
|
||||
toolResult,
|
||||
nextCursor,
|
||||
error,
|
||||
}: {
|
||||
tools: Tool[];
|
||||
@@ -23,6 +24,7 @@ const ToolsTab = ({
|
||||
selectedTool: Tool | null;
|
||||
setSelectedTool: (tool: Tool) => void;
|
||||
toolResult: string;
|
||||
nextCursor: ListToolsResult["nextCursor"];
|
||||
error: string | null;
|
||||
}) => {
|
||||
const [params, setParams] = useState<Record<string, unknown>>({});
|
||||
@@ -42,7 +44,8 @@ const ToolsTab = ({
|
||||
</>
|
||||
)}
|
||||
title="Tools"
|
||||
buttonText="List Tools"
|
||||
buttonText={nextCursor ? "List More Tools" : "List Tools"}
|
||||
isButtonDisabled={!nextCursor && tools.length > 0}
|
||||
/>
|
||||
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
|
||||
Reference in New Issue
Block a user