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 [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 />

View File

@@ -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}

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">