From 747c0154c51dfc42f001c3f14cdd9484dcdd8d29 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 8 Mar 2025 11:05:13 -0500 Subject: [PATCH 1/8] WIP: Subscribe to resources * In App.tsx - added subscribeToResource() - takes a uri - sends a `resources/subscribe` message with the uri - added unsubscribeFromResource() - takes a uri - sends a `resources/unsubscribe` message with the uri - in ResourcesTab element, - pass subscribeToResource and subscribeToResource invokers to component * In notificationTypes.ts - add ServerNotificationSchema to NotificationSchema to permit server update messages. * In ResourcesTab.tsx - deconstruct subscribeToResource and unsubscribeFromResource and add prop types - Add Subscribe and Unsubscribe buttons to selected resource panel, left of the refresh button. They call the sub and unsub functions that came in on props, passing the selected resource URI. - [WIP]: Will show the appropriate button in a follow up commit. * In useConnection.ts - import ResourceUpdatedNotificationSchema - in the connect function, - set onNotification as the handler for ResourceUpdatedNotificationSchema --- client/src/App.tsx | 33 +++++++++++++++++++++++ client/src/components/ResourcesTab.tsx | 36 ++++++++++++++++++++------ client/src/lib/hooks/useConnection.ts | 6 +++++ client/src/lib/notificationTypes.ts | 7 ++--- 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index a9adea5..f5b1f79 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -308,6 +308,31 @@ const App = () => { setResourceContent(JSON.stringify(response, null, 2)); }; + const subscribeToResource = async (uri: string) => { + + await makeRequest( + { + method: "resources/subscribe" as const, + params: { uri }, + }, + z.object({}), + "resources", + ); + }; + + const unsubscribeFromResource = async (uri: string) => { + + await makeRequest( + { + method: "resources/unsubscribe" as const, + params: { uri }, + }, + z.object({}), + "resources", + ); + }; + + const listPrompts = async () => { const response = await makeRequest( { @@ -485,6 +510,14 @@ const App = () => { clearError("resources"); setSelectedResource(resource); }} + subscribeToResource={(uri) => { + clearError("resources"); + subscribeToResource(uri); + }} + unsubscribeFromResource={(uri) => { + clearError("resources"); + unsubscribeFromResource(uri); + }} handleCompletion={handleCompletion} completionsSupported={completionsSupported} resourceContent={resourceContent} diff --git a/client/src/components/ResourcesTab.tsx b/client/src/components/ResourcesTab.tsx index 93127a9..9d94296 100644 --- a/client/src/components/ResourcesTab.tsx +++ b/client/src/components/ResourcesTab.tsx @@ -26,6 +26,8 @@ const ResourcesTab = ({ readResource, selectedResource, setSelectedResource, + subscribeToResource, + unsubscribeFromResource, handleCompletion, completionsSupported, resourceContent, @@ -52,6 +54,8 @@ const ResourcesTab = ({ nextCursor: ListResourcesResult["nextCursor"]; nextTemplateCursor: ListResourceTemplatesResult["nextCursor"]; error: string | null; + subscribeToResource: (uri: string) => void; + unsubscribeFromResource: (uri: string) => void; }) => { const [selectedTemplate, setSelectedTemplate] = useState(null); @@ -164,14 +168,30 @@ const ResourcesTab = ({ : "Select a resource or template"} {selectedResource && ( - + <> + + + + )}
diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 75b5467..9e7bb11 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -9,6 +9,7 @@ import { CreateMessageRequestSchema, ListRootsRequestSchema, ProgressNotificationSchema, + ResourceUpdatedNotificationSchema, Request, Result, ServerCapabilities, @@ -247,6 +248,11 @@ export function useConnection({ ProgressNotificationSchema, onNotification, ); + + client.setNotificationHandler( + ResourceUpdatedNotificationSchema, + onNotification, + ); } if (onStdErrNotification) { diff --git a/client/src/lib/notificationTypes.ts b/client/src/lib/notificationTypes.ts index 7aa6518..abd714b 100644 --- a/client/src/lib/notificationTypes.ts +++ b/client/src/lib/notificationTypes.ts @@ -1,6 +1,7 @@ import { NotificationSchema as BaseNotificationSchema, ClientNotificationSchema, + ServerNotificationSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; @@ -11,9 +12,9 @@ export const StdErrNotificationSchema = BaseNotificationSchema.extend({ }), }); -export const NotificationSchema = ClientNotificationSchema.or( - StdErrNotificationSchema, -); +export const NotificationSchema = ClientNotificationSchema + .or(StdErrNotificationSchema) + .or(ServerNotificationSchema); export type StdErrNotification = z.infer; export type Notification = z.infer; From a669272fda9f5b2af99bd9a0f2241e779fc947e1 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 8 Mar 2025 13:40:37 -0500 Subject: [PATCH 2/8] Track subscribed resources and show the appropriate subscribe or unsubscribe button on selected resource panel. If the server does not support resource subscriptions, do not show any subscription buttons. * In App.tsx - useState for resourceSubscriptions, setResourceSubscriptions a Set of type string. - in subscribeToResource() - only make the request to subscribe if the uri is not in the resourceSubscriptions set - in unsubscribeFromResource() - only make the request to unsubscribe if the uri is in the resourceSubscriptions set - in ResourceTab element, - pass a boolean resourceSubscriptionsSupported as serverCapabilities.resources.subscribe - pass resourceSubscriptions as a prop * In ResourcesTab.tsx - deconstruct resourceSubscriptions and resourceSubscriptionsSupported from props and add prop type - in selected resource panel - don't show subscribe or unsubscribe buttons unless resourceSubscriptionsSupported is true - only show subscribe button if selected resource uri is not in resourceSubscriptions set - only show unsubscribe button if selected resource uri is in resourceSubscriptions set - wrap buttons in a flex div that is - justified right - has a minimal gap between - 2/5 wide (just big enough to contain two buttons and leave the h3 text 3/5 of the row to render and not overflow. --- client/src/App.tsx | 47 +++++++++++++++++--------- client/src/components/ResourcesTab.tsx | 13 +++++-- package-lock.json | 4 +-- package.json | 4 +-- 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index f5b1f79..382ae03 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -128,6 +128,8 @@ const App = () => { const [selectedResource, setSelectedResource] = useState( null, ); + const [resourceSubscriptions, setResourceSubscriptions] = useState>(new Set()); + const [selectedPrompt, setSelectedPrompt] = useState(null); const [selectedTool, setSelectedTool] = useState(null); const [nextResourceCursor, setNextResourceCursor] = useState< @@ -310,26 +312,37 @@ const App = () => { const subscribeToResource = async (uri: string) => { - await makeRequest( - { - method: "resources/subscribe" as const, - params: { uri }, - }, - z.object({}), - "resources", - ); + if (!resourceSubscriptions.has(uri)) { + await makeRequest( + { + method: "resources/subscribe" as const, + params: { uri }, + }, + z.object({}), + "resources", + ); + const clone = new Set(resourceSubscriptions); + clone.add(uri); + setResourceSubscriptions(clone); + } + }; const unsubscribeFromResource = async (uri: string) => { - await makeRequest( - { - method: "resources/unsubscribe" as const, - params: { uri }, - }, - z.object({}), - "resources", - ); + if (resourceSubscriptions.has(uri)) { + await makeRequest( + { + method: "resources/unsubscribe" as const, + params: { uri }, + }, + z.object({}), + "resources", + ); + const clone = new Set(resourceSubscriptions); + clone.delete(uri); + setResourceSubscriptions(clone); + } }; @@ -510,6 +523,8 @@ const App = () => { clearError("resources"); setSelectedResource(resource); }} + resourceSubscriptionsSupported={serverCapabilities?.resources?.subscribe || false} + resourceSubscriptions={resourceSubscriptions} subscribeToResource={(uri) => { clearError("resources"); subscribeToResource(uri); diff --git a/client/src/components/ResourcesTab.tsx b/client/src/components/ResourcesTab.tsx index 9d94296..317ec85 100644 --- a/client/src/components/ResourcesTab.tsx +++ b/client/src/components/ResourcesTab.tsx @@ -26,6 +26,8 @@ const ResourcesTab = ({ readResource, selectedResource, setSelectedResource, + resourceSubscriptionsSupported, + resourceSubscriptions, subscribeToResource, unsubscribeFromResource, handleCompletion, @@ -54,6 +56,8 @@ const ResourcesTab = ({ nextCursor: ListResourcesResult["nextCursor"]; nextTemplateCursor: ListResourceTemplatesResult["nextCursor"]; error: string | null; + resourceSubscriptionsSupported: boolean; + resourceSubscriptions: Set; subscribeToResource: (uri: string) => void; unsubscribeFromResource: (uri: string) => void; }) => { @@ -168,14 +172,16 @@ const ResourcesTab = ({ : "Select a resource or template"} {selectedResource && ( - <> - + } + { resourceSubscriptionsSupported && resourceSubscriptions.has(selectedResource.uri) && + } - +
)}
diff --git a/package-lock.json b/package-lock.json index 550bb75..ed9bc96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,8 @@ "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "0.4.1", - "@modelcontextprotocol/inspector-server": "0.4.1", + "@modelcontextprotocol/inspector-client": "^0.5.1", + "@modelcontextprotocol/inspector-server": "^0.5.1", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", diff --git a/package.json b/package.json index 3de7ce4..b84fbd6 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { - "@modelcontextprotocol/inspector-client": "0.4.1", - "@modelcontextprotocol/inspector-server": "0.4.1", + "@modelcontextprotocol/inspector-client": "^0.5.1", + "@modelcontextprotocol/inspector-server": "^0.5.1", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", From 952bee2605e4acd25f2d5fc5c2263f81edfd2c20 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 8 Mar 2025 15:08:12 -0500 Subject: [PATCH 3/8] Fix prettier complaints --- client/src/App.tsx | 12 ++++---- client/src/components/ResourcesTab.tsx | 39 +++++++++++++++----------- client/src/lib/notificationTypes.ts | 6 ++-- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 382ae03..e902da9 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -128,7 +128,9 @@ const App = () => { const [selectedResource, setSelectedResource] = useState( null, ); - const [resourceSubscriptions, setResourceSubscriptions] = useState>(new Set()); + const [resourceSubscriptions, setResourceSubscriptions] = useState< + Set + >(new Set()); const [selectedPrompt, setSelectedPrompt] = useState(null); const [selectedTool, setSelectedTool] = useState(null); @@ -311,7 +313,6 @@ const App = () => { }; const subscribeToResource = async (uri: string) => { - if (!resourceSubscriptions.has(uri)) { await makeRequest( { @@ -325,11 +326,9 @@ const App = () => { clone.add(uri); setResourceSubscriptions(clone); } - }; const unsubscribeFromResource = async (uri: string) => { - if (resourceSubscriptions.has(uri)) { await makeRequest( { @@ -345,7 +344,6 @@ const App = () => { } }; - const listPrompts = async () => { const response = await makeRequest( { @@ -523,7 +521,9 @@ const App = () => { clearError("resources"); setSelectedResource(resource); }} - resourceSubscriptionsSupported={serverCapabilities?.resources?.subscribe || false} + resourceSubscriptionsSupported={ + serverCapabilities?.resources?.subscribe || false + } resourceSubscriptions={resourceSubscriptions} subscribeToResource={(uri) => { clearError("resources"); diff --git a/client/src/components/ResourcesTab.tsx b/client/src/components/ResourcesTab.tsx index 317ec85..f000840 100644 --- a/client/src/components/ResourcesTab.tsx +++ b/client/src/components/ResourcesTab.tsx @@ -173,23 +173,28 @@ const ResourcesTab = ({ {selectedResource && (
- { resourceSubscriptionsSupported && !resourceSubscriptions.has(selectedResource.uri) && - } - { resourceSubscriptionsSupported && resourceSubscriptions.has(selectedResource.uri) && - - } + {resourceSubscriptionsSupported && + !resourceSubscriptions.has(selectedResource.uri) && ( + + )} + {resourceSubscriptionsSupported && + resourceSubscriptions.has(selectedResource.uri) && ( + + )}
) : ( -
- - setSseUrl(e.target.value)} - className="font-mono" - /> -
+ <> +
+ + setSseUrl(e.target.value)} + className="font-mono" + /> +
+
+ + {showBearerToken && ( +
+ + setBearerToken(e.target.value)} + className="font-mono" + type="password" + /> +
+ )} +
+ )} {transportType === "stdio" && (
diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 75b5467..468e9ad 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -37,6 +37,7 @@ interface UseConnectionOptions { sseUrl: string; env: Record; proxyServerUrl: string; + bearerToken?: string; requestTimeout?: number; onNotification?: (notification: Notification) => void; onStdErrNotification?: (notification: Notification) => void; @@ -57,6 +58,7 @@ export function useConnection({ sseUrl, env, proxyServerUrl, + bearerToken, requestTimeout = DEFAULT_REQUEST_TIMEOUT_MSEC, onNotification, onStdErrNotification, @@ -228,9 +230,11 @@ export function useConnection({ // Inject auth manually instead of using SSEClientTransport, because we're // proxying through the inspector server first. const headers: HeadersInit = {}; - const tokens = await authProvider.tokens(); - if (tokens) { - headers["Authorization"] = `Bearer ${tokens.access_token}`; + + // Use manually provided bearer token if available, otherwise use OAuth tokens + const token = bearerToken || (await authProvider.tokens())?.access_token; + if (token) { + headers["Authorization"] = `Bearer ${token}`; } const clientTransport = new SSEClientTransport(backendUrl, { From f56961ac62eca78840d28991ce7d0a9a21a7bf53 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Tue, 11 Mar 2025 10:55:14 +0000 Subject: [PATCH 5/8] Bump version --- client/package.json | 4 ++-- package.json | 8 ++++---- server/package.json | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/package.json b/client/package.json index 1b7d4cb..c3a700d 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.5.1", + "version": "0.6.0", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -64,4 +64,4 @@ "typescript-eslint": "^8.7.0", "vite": "^5.4.8" } -} +} \ No newline at end of file diff --git a/package.json b/package.json index b84fbd6..5407d97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.5.1", + "version": "0.6.0", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -34,8 +34,8 @@ "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.5.1", - "@modelcontextprotocol/inspector-server": "^0.5.1", + "@modelcontextprotocol/inspector-client": "^0.6.0", + "@modelcontextprotocol/inspector-server": "^0.6.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -46,4 +46,4 @@ "@types/shell-quote": "^1.7.5", "prettier": "3.3.3" } -} +} \ No newline at end of file diff --git a/server/package.json b/server/package.json index e64557f..1e22e92 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.5.1", + "version": "0.6.0", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -33,4 +33,4 @@ "ws": "^8.18.0", "zod": "^3.23.8" } -} +} \ No newline at end of file From 0281e5f8219d6f6f73db0509d1a30d85df4b49f8 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Tue, 11 Mar 2025 10:56:53 +0000 Subject: [PATCH 6/8] Fix formatting --- client/package.json | 2 +- package.json | 2 +- server/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/package.json b/client/package.json index c3a700d..b1bd337 100644 --- a/client/package.json +++ b/client/package.json @@ -64,4 +64,4 @@ "typescript-eslint": "^8.7.0", "vite": "^5.4.8" } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 5407d97..3f40d2e 100644 --- a/package.json +++ b/package.json @@ -46,4 +46,4 @@ "@types/shell-quote": "^1.7.5", "prettier": "3.3.3" } -} \ No newline at end of file +} diff --git a/server/package.json b/server/package.json index 1e22e92..5d8839f 100644 --- a/server/package.json +++ b/server/package.json @@ -33,4 +33,4 @@ "ws": "^8.18.0", "zod": "^3.23.8" } -} \ No newline at end of file +} From 397a0f651f93e8d4b4f7cdc22e58cc59ca8ef1ec Mon Sep 17 00:00:00 2001 From: Abdelkader Boudih Date: Tue, 11 Mar 2025 13:33:45 +0000 Subject: [PATCH 7/8] feat: Fetch version from package.json in useConnection hook --- client/src/lib/hooks/useConnection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index 4abd5c6..ea9e05a 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -26,6 +26,7 @@ import { SESSION_KEYS } from "../constants"; import { Notification, StdErrNotificationSchema } from "../notificationTypes"; import { auth } from "@modelcontextprotocol/sdk/client/auth.js"; import { authProvider } from "../auth"; +import packageJson from "../../../package.json"; const params = new URLSearchParams(window.location.search); const DEFAULT_REQUEST_TIMEOUT_MSEC = @@ -205,7 +206,7 @@ export function useConnection({ const client = new Client( { name: "mcp-inspector", - version: "0.0.1", + version: packageJson.version, }, { capabilities: { From e5ee00bf89ee44a57f78c1332d02fec4950f8f66 Mon Sep 17 00:00:00 2001 From: leoshimo <56844000+leoshimo@users.noreply.github.com> Date: Tue, 11 Mar 2025 12:59:28 -0700 Subject: [PATCH 8/8] fix: add dark mode support to SamplingTab JSON display (#181) --- client/src/components/SamplingTab.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/SamplingTab.tsx b/client/src/components/SamplingTab.tsx index d851880..5c45400 100644 --- a/client/src/components/SamplingTab.tsx +++ b/client/src/components/SamplingTab.tsx @@ -43,7 +43,7 @@ const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => {

Recent Requests

{pendingRequests.map((request) => (
-
+            
               {JSON.stringify(request.request, null, 2)}