Merge pull request #19 from modelcontextprotocol/ashwin/pagination

add pagination handling for lists
This commit is contained in:
ashwin-ant
2024-10-18 08:29:49 -07:00
committed by GitHub
5 changed files with 41 additions and 10 deletions

View File

@@ -74,6 +74,13 @@ const App = () => {
); );
const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null); const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
const [selectedTool, setSelectedTool] = useState<Tool | 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 progressTokenRef = useRef(0);
const pushHistory = (request: object, response: object) => { const pushHistory = (request: object, response: object) => {
@@ -105,12 +112,12 @@ const App = () => {
const response = await makeRequest( const response = await makeRequest(
{ {
method: "resources/list" as const, method: "resources/list" as const,
params: nextResourceCursor ? { cursor: nextResourceCursor } : {},
}, },
ListResourcesResultSchema, ListResourcesResultSchema,
); );
if (response.resources) { setResources(resources.concat(response.resources ?? []));
setResources(response.resources); setNextResourceCursor(response.nextCursor);
}
}; };
const readResource = async (uri: URL) => { const readResource = async (uri: URL) => {
@@ -128,10 +135,12 @@ const App = () => {
const response = await makeRequest( const response = await makeRequest(
{ {
method: "prompts/list" as const, method: "prompts/list" as const,
params: nextPromptCursor ? { cursor: nextPromptCursor } : {},
}, },
ListPromptsResultSchema, ListPromptsResultSchema,
); );
setPrompts(response.prompts); setPrompts(response.prompts);
setNextPromptCursor(response.nextCursor);
}; };
const getPrompt = async (name: string, args: Record<string, string> = {}) => { const getPrompt = async (name: string, args: Record<string, string> = {}) => {
@@ -149,10 +158,12 @@ const App = () => {
const response = await makeRequest( const response = await makeRequest(
{ {
method: "tools/list" as const, method: "tools/list" as const,
params: nextToolCursor ? { cursor: nextToolCursor } : {},
}, },
ListToolsResultSchema, ListToolsResultSchema,
); );
setTools(response.tools); setTools(response.tools);
setNextToolCursor(response.nextCursor);
}; };
const callTool = async (name: string, params: Record<string, unknown>) => { const callTool = async (name: string, params: Record<string, unknown>) => {
@@ -293,6 +304,7 @@ const App = () => {
selectedResource={selectedResource} selectedResource={selectedResource}
setSelectedResource={setSelectedResource} setSelectedResource={setSelectedResource}
resourceContent={resourceContent} resourceContent={resourceContent}
nextCursor={nextResourceCursor}
error={error} error={error}
/> />
<PromptsTab <PromptsTab
@@ -302,6 +314,7 @@ const App = () => {
selectedPrompt={selectedPrompt} selectedPrompt={selectedPrompt}
setSelectedPrompt={setSelectedPrompt} setSelectedPrompt={setSelectedPrompt}
promptContent={promptContent} promptContent={promptContent}
nextCursor={nextPromptCursor}
error={error} error={error}
/> />
<RequestsTab /> <RequestsTab />
@@ -315,6 +328,7 @@ const App = () => {
setToolResult(""); setToolResult("");
}} }}
toolResult={toolResult} toolResult={toolResult}
nextCursor={nextToolCursor}
error={error} error={error}
/> />
<ConsoleTab /> <ConsoleTab />

View File

@@ -7,6 +7,7 @@ type ListPaneProps<T> = {
renderItem: (item: T) => React.ReactNode; renderItem: (item: T) => React.ReactNode;
title: string; title: string;
buttonText: string; buttonText: string;
isButtonDisabled?: boolean;
}; };
const ListPane = <T extends object>({ const ListPane = <T extends object>({
@@ -16,16 +17,22 @@ const ListPane = <T extends object>({
renderItem, renderItem,
title, title,
buttonText, buttonText,
isButtonDisabled,
}: ListPaneProps<T>) => ( }: ListPaneProps<T>) => (
<div className="bg-white rounded-lg shadow"> <div className="bg-white rounded-lg shadow">
<div className="p-4 border-b border-gray-200"> <div className="p-4 border-b border-gray-200">
<h3 className="font-semibold">{title}</h3> <h3 className="font-semibold">{title}</h3>
</div> </div>
<div className="p-4"> <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} {buttonText}
</Button> </Button>
<div className="space-y-2"> <div className="space-y-2 overflow-y-auto max-h-96">
{items.map((item, index) => ( {items.map((item, index) => (
<div <div
key={index} key={index}

View File

@@ -7,6 +7,7 @@ import { Textarea } from "@/components/ui/textarea";
import { useState } from "react"; import { useState } from "react";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import ListPane from "./ListPane"; import ListPane from "./ListPane";
import { ListPromptsResult } from "mcp-typescript/types.js";
export type Prompt = { export type Prompt = {
name: string; name: string;
@@ -25,6 +26,7 @@ const PromptsTab = ({
selectedPrompt, selectedPrompt,
setSelectedPrompt, setSelectedPrompt,
promptContent, promptContent,
nextCursor,
error, error,
}: { }: {
prompts: Prompt[]; prompts: Prompt[];
@@ -33,6 +35,7 @@ const PromptsTab = ({
selectedPrompt: Prompt | null; selectedPrompt: Prompt | null;
setSelectedPrompt: (prompt: Prompt) => void; setSelectedPrompt: (prompt: Prompt) => void;
promptContent: string; promptContent: string;
nextCursor: ListPromptsResult["nextCursor"];
error: string | null; error: string | null;
}) => { }) => {
const [promptArgs, setPromptArgs] = useState<Record<string, string>>({}); const [promptArgs, setPromptArgs] = useState<Record<string, string>>({});
@@ -63,7 +66,8 @@ const PromptsTab = ({
</> </>
)} )}
title="Prompts" title="Prompts"
buttonText="List Prompts" buttonText={nextCursor ? "List More Prompts" : "List Prompts"}
isButtonDisabled={!nextCursor && prompts.length > 0}
/> />
<div className="bg-white rounded-lg shadow"> <div className="bg-white rounded-lg shadow">

View File

@@ -2,7 +2,7 @@ import { FileText, ChevronRight, AlertCircle, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { TabsContent } from "@/components/ui/tabs"; 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"; import ListPane from "./ListPane";
const ResourcesTab = ({ const ResourcesTab = ({
@@ -12,6 +12,7 @@ const ResourcesTab = ({
selectedResource, selectedResource,
setSelectedResource, setSelectedResource,
resourceContent, resourceContent,
nextCursor,
error, error,
}: { }: {
resources: Resource[]; resources: Resource[];
@@ -20,6 +21,7 @@ const ResourcesTab = ({
selectedResource: Resource | null; selectedResource: Resource | null;
setSelectedResource: (resource: Resource) => void; setSelectedResource: (resource: Resource) => void;
resourceContent: string; resourceContent: string;
nextCursor: ListResourcesResult["nextCursor"];
error: string | null; error: string | null;
}) => ( }) => (
<TabsContent value="resources" className="grid grid-cols-2 gap-4"> <TabsContent value="resources" className="grid grid-cols-2 gap-4">
@@ -40,7 +42,8 @@ const ResourcesTab = ({
</div> </div>
)} )}
title="Resources" title="Resources"
buttonText="List Resources" buttonText={nextCursor ? "List More Resources" : "List Resources"}
isButtonDisabled={!nextCursor && resources.length > 0}
/> />
<div className="bg-white rounded-lg shadow"> <div className="bg-white rounded-lg shadow">

View File

@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Send, AlertCircle } from "lucide-react"; import { Send, AlertCircle } from "lucide-react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; 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 { useState } from "react";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import ListPane from "./ListPane"; import ListPane from "./ListPane";
@@ -15,6 +15,7 @@ const ToolsTab = ({
selectedTool, selectedTool,
setSelectedTool, setSelectedTool,
toolResult, toolResult,
nextCursor,
error, error,
}: { }: {
tools: Tool[]; tools: Tool[];
@@ -23,6 +24,7 @@ const ToolsTab = ({
selectedTool: Tool | null; selectedTool: Tool | null;
setSelectedTool: (tool: Tool) => void; setSelectedTool: (tool: Tool) => void;
toolResult: string; toolResult: string;
nextCursor: ListToolsResult["nextCursor"];
error: string | null; error: string | null;
}) => { }) => {
const [params, setParams] = useState<Record<string, unknown>>({}); const [params, setParams] = useState<Record<string, unknown>>({});
@@ -42,7 +44,8 @@ const ToolsTab = ({
</> </>
)} )}
title="Tools" title="Tools"
buttonText="List Tools" buttonText={nextCursor ? "List More Tools" : "List Tools"}
isButtonDisabled={!nextCursor && tools.length > 0}
/> />
<div className="bg-white rounded-lg shadow"> <div className="bg-white rounded-lg shadow">