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 [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 />
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user