From 645f2e942e0da41c59605e7e093352c69340cd45 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Thu, 7 Nov 2024 14:02:53 +0000 Subject: [PATCH] Add support for listing and filling resource templates --- client/src/App.tsx | 27 +++ client/src/components/ResourcesTab.tsx | 226 ++++++++++++++++++------- 2 files changed, 194 insertions(+), 59 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 055552a..01dc987 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -9,10 +9,12 @@ import { GetPromptResultSchema, ListPromptsResultSchema, ListResourcesResultSchema, + ListResourceTemplatesResultSchema, ListToolsResultSchema, ProgressNotificationSchema, ReadResourceResultSchema, Resource, + ResourceTemplate, ServerNotification, Tool, } from "@modelcontextprotocol/sdk/types.js"; @@ -56,6 +58,9 @@ const App = () => { "disconnected" | "connected" | "error" >("disconnected"); const [resources, setResources] = useState([]); + const [resourceTemplates, setResourceTemplates] = useState< + ResourceTemplate[] + >([]); const [resourceContent, setResourceContent] = useState(""); const [prompts, setPrompts] = useState([]); const [promptContent, setPromptContent] = useState(""); @@ -116,6 +121,9 @@ const App = () => { const [nextResourceCursor, setNextResourceCursor] = useState< string | undefined >(); + const [nextResourceTemplateCursor, setNextResourceTemplateCursor] = useState< + string | undefined + >(); const [nextPromptCursor, setNextPromptCursor] = useState< string | undefined >(); @@ -167,6 +175,22 @@ const App = () => { setNextResourceCursor(response.nextCursor); }; + const listResourceTemplates = async () => { + const response = await makeRequest( + { + method: "resources/templates/list" as const, + params: nextResourceTemplateCursor + ? { cursor: nextResourceTemplateCursor } + : {}, + }, + ListResourceTemplatesResultSchema, + ); + setResourceTemplates( + resourceTemplates.concat(response.resourceTemplates ?? []), + ); + setNextResourceTemplateCursor(response.nextCursor); + }; + const readResource = async (uri: string) => { const response = await makeRequest( { @@ -368,12 +392,15 @@ const App = () => {
void; + listResourceTemplates: () => void; readResource: (uri: string) => void; selectedResource: Resource | null; - setSelectedResource: (resource: Resource) => void; + setSelectedResource: (resource: Resource | null) => void; resourceContent: string; nextCursor: ListResourcesResult["nextCursor"]; + nextTemplateCursor: ListResourceTemplatesResult["nextCursor"]; error: string | null; -}) => ( - - { - setSelectedResource(resource); - readResource(resource.uri); - }} - renderItem={(resource) => ( -
- - - {resource.name} - - -
- )} - title="Resources" - buttonText={nextCursor ? "List More Resources" : "List Resources"} - isButtonDisabled={!nextCursor && resources.length > 0} - /> +}) => { + const [selectedTemplate, setSelectedTemplate] = + useState(null); + const [templateValues, setTemplateValues] = useState>( + {}, + ); -
-
-

- {selectedResource ? selectedResource.name : "Select a resource"} -

- {selectedResource && ( - - )} + {selectedResource + ? selectedResource.name + : selectedTemplate + ? selectedTemplate.name + : "Select a resource or template"} + + {selectedResource && ( + + )} +
+
+ {error ? ( + + + Error + {error} + + ) : selectedResource ? ( +
+              {resourceContent}
+            
+ ) : selectedTemplate ? ( +
+

+ {selectedTemplate.description} +

+ {selectedTemplate.uriTemplate + .match(/{([^}]+)}/g) + ?.map((param) => { + const key = param.slice(1, -1); + return ( +
+ + + setTemplateValues({ + ...templateValues, + [key]: e.target.value, + }) + } + className="mt-1" + /> +
+ ); + })} + +
+ ) : ( + + + Select a resource or template from the list to view its contents + + + )} +
-
- {error ? ( - - - Error - {error} - - ) : selectedResource ? ( -
-            {resourceContent}
-          
- ) : ( - - - Select a resource from the list to view its contents - - - )} -
-
- -); + + ); +}; export default ResourcesTab;