From a8ffc704f05cd1f8e0050cebd6f83a337fbddffc Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Fri, 4 Apr 2025 01:44:30 +0530 Subject: [PATCH 1/6] add support for progress flow --- client/src/App.tsx | 36 ++-- client/src/lib/configurationTypes.ts | 16 ++ client/src/lib/constants.ts | 8 + .../hooks/__tests__/useConnection.test.tsx | 165 ++++++++++++++++++ client/src/lib/hooks/useConnection.ts | 40 +++-- client/src/utils/configUtils.ts | 12 ++ 6 files changed, 238 insertions(+), 39 deletions(-) create mode 100644 client/src/lib/hooks/__tests__/useConnection.test.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index 75645446..61dcad7e 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -45,10 +45,7 @@ import Sidebar from "./components/Sidebar"; import ToolsTab from "./components/ToolsTab"; import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants"; import { InspectorConfig } from "./lib/configurationTypes"; -import { - getMCPProxyAddress, - getMCPServerRequestTimeout, -} from "./utils/configUtils"; +import { getMCPProxyAddress } from "./utils/configUtils"; import { useToast } from "@/hooks/use-toast"; const params = new URLSearchParams(window.location.search); @@ -148,7 +145,7 @@ const App = () => { serverCapabilities, mcpClient, requestHistory, - makeRequest: makeConnectionRequest, + makeRequest, sendNotification, handleCompletion, completionsSupported, @@ -161,8 +158,7 @@ const App = () => { sseUrl, env, bearerToken, - proxyServerUrl: getMCPProxyAddress(config), - requestTimeout: getMCPServerRequestTimeout(config), + config, onNotification: (notification) => { setNotifications((prev) => [...prev, notification as ServerNotification]); }, @@ -279,13 +275,13 @@ const App = () => { setErrors((prev) => ({ ...prev, [tabKey]: null })); }; - const makeRequest = async ( + const makeConnectionRequest = async ( request: ClientRequest, schema: T, tabKey?: keyof typeof errors, ) => { try { - const response = await makeConnectionRequest(request, schema); + const response = await makeRequest(request, schema); if (tabKey !== undefined) { clearError(tabKey); } @@ -303,7 +299,7 @@ const App = () => { }; const listResources = async () => { - const response = await makeRequest( + const response = await makeConnectionRequest( { method: "resources/list" as const, params: nextResourceCursor ? { cursor: nextResourceCursor } : {}, @@ -316,7 +312,7 @@ const App = () => { }; const listResourceTemplates = async () => { - const response = await makeRequest( + const response = await makeConnectionRequest( { method: "resources/templates/list" as const, params: nextResourceTemplateCursor @@ -333,7 +329,7 @@ const App = () => { }; const readResource = async (uri: string) => { - const response = await makeRequest( + const response = await makeConnectionRequest( { method: "resources/read" as const, params: { uri }, @@ -346,7 +342,7 @@ const App = () => { const subscribeToResource = async (uri: string) => { if (!resourceSubscriptions.has(uri)) { - await makeRequest( + await makeConnectionRequest( { method: "resources/subscribe" as const, params: { uri }, @@ -362,7 +358,7 @@ const App = () => { const unsubscribeFromResource = async (uri: string) => { if (resourceSubscriptions.has(uri)) { - await makeRequest( + await makeConnectionRequest( { method: "resources/unsubscribe" as const, params: { uri }, @@ -377,7 +373,7 @@ const App = () => { }; const listPrompts = async () => { - const response = await makeRequest( + const response = await makeConnectionRequest( { method: "prompts/list" as const, params: nextPromptCursor ? { cursor: nextPromptCursor } : {}, @@ -390,7 +386,7 @@ const App = () => { }; const getPrompt = async (name: string, args: Record = {}) => { - const response = await makeRequest( + const response = await makeConnectionRequest( { method: "prompts/get" as const, params: { name, arguments: args }, @@ -402,7 +398,7 @@ const App = () => { }; const listTools = async () => { - const response = await makeRequest( + const response = await makeConnectionRequest( { method: "tools/list" as const, params: nextToolCursor ? { cursor: nextToolCursor } : {}, @@ -415,7 +411,7 @@ const App = () => { }; const callTool = async (name: string, params: Record) => { - const response = await makeRequest( + const response = await makeConnectionRequest( { method: "tools/call" as const, params: { @@ -437,7 +433,7 @@ const App = () => { }; const sendLogLevelRequest = async (level: LoggingLevel) => { - await makeRequest( + await makeConnectionRequest( { method: "logging/setLevel" as const, params: { level }, @@ -654,7 +650,7 @@ const App = () => { { - void makeRequest( + void makeConnectionRequest( { method: "ping" as const, }, diff --git a/client/src/lib/configurationTypes.ts b/client/src/lib/configurationTypes.ts index df9eb29b..d0c12638 100644 --- a/client/src/lib/configurationTypes.ts +++ b/client/src/lib/configurationTypes.ts @@ -15,5 +15,21 @@ export type InspectorConfig = { * Maximum time in milliseconds to wait for a response from the MCP server before timing out. */ MCP_SERVER_REQUEST_TIMEOUT: ConfigItem; + + /** + * Whether to reset the timeout on progress notifications. Useful for long-running operations that send periodic progress updates. + * Refer: https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/utilities/progress/#progress-flow + */ + MCP_SERVER_REQUEST_TIMEOUT_RESET_ON_PROGRESS: ConfigItem; + + /** + * Maximum total time in milliseconds to wait for a response from the MCP server before timing out. Used in conjunction with MCP_SERVER_REQUEST_TIMEOUT_RESET_ON_PROGRESS. + * Refer: https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/utilities/progress/#progress-flow + */ + MCP_SERVER_REQUEST_TIMEOUT_MAX_TOTAL_TIMEOUT: ConfigItem; + + /** + * The full address of the MCP Proxy Server, in case it is running on a non-default address. Example: http://10.1.1.22:5577 + */ MCP_PROXY_FULL_ADDRESS: ConfigItem; }; diff --git a/client/src/lib/constants.ts b/client/src/lib/constants.ts index c370b343..9caf4bc6 100644 --- a/client/src/lib/constants.ts +++ b/client/src/lib/constants.ts @@ -25,6 +25,14 @@ export const DEFAULT_INSPECTOR_CONFIG: InspectorConfig = { description: "Timeout for requests to the MCP server (ms)", value: 10000, }, + MCP_SERVER_REQUEST_TIMEOUT_RESET_ON_PROGRESS: { + description: "Reset timeout on progress notifications", + value: true, + }, + MCP_SERVER_REQUEST_TIMEOUT_MAX_TOTAL_TIMEOUT: { + description: "Maximum total timeout for requests sent to the MCP server (ms)", + value: 60000, + }, MCP_PROXY_FULL_ADDRESS: { description: "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577", diff --git a/client/src/lib/hooks/__tests__/useConnection.test.tsx b/client/src/lib/hooks/__tests__/useConnection.test.tsx new file mode 100644 index 00000000..7a96802b --- /dev/null +++ b/client/src/lib/hooks/__tests__/useConnection.test.tsx @@ -0,0 +1,165 @@ +import { renderHook, act } from "@testing-library/react"; +import { useConnection } from "../useConnection"; +import { z } from "zod"; +import { ClientRequest } from "@modelcontextprotocol/sdk/types.js"; +import { DEFAULT_INSPECTOR_CONFIG } from "../../constants"; + +// Mock fetch +global.fetch = jest.fn().mockResolvedValue({ + json: () => Promise.resolve({ status: "ok" }), +}); + +// Mock the SDK dependencies +const mockRequest = jest.fn().mockResolvedValue({ test: "response" }); +const mockClient = { + request: mockRequest, + notification: jest.fn(), + connect: jest.fn().mockResolvedValue(undefined), + close: jest.fn(), + getServerCapabilities: jest.fn(), + setNotificationHandler: jest.fn(), + setRequestHandler: jest.fn(), +}; + +jest.mock("@modelcontextprotocol/sdk/client/index.js", () => ({ + Client: jest.fn().mockImplementation(() => mockClient), +})); + +jest.mock("@modelcontextprotocol/sdk/client/sse.js", () => ({ + SSEClientTransport: jest.fn(), + SseError: jest.fn(), +})); + +jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({ + auth: jest.fn().mockResolvedValue("AUTHORIZED"), +})); + +// Mock the toast hook +jest.mock("@/hooks/use-toast", () => ({ + useToast: () => ({ + toast: jest.fn(), + }), +})); + +// Mock the auth provider +jest.mock("../../auth", () => ({ + authProvider: { + tokens: jest.fn().mockResolvedValue({ access_token: "mock-token" }), + }, +})); + +describe("useConnection", () => { + const defaultProps = { + transportType: "sse" as const, + command: "", + args: "", + sseUrl: "http://localhost:8080", + env: {}, + config: DEFAULT_INSPECTOR_CONFIG, + }; + + describe("Request Configuration", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test("uses the default config values in makeRequest", async () => { + const { result } = renderHook(() => useConnection(defaultProps)); + + // Connect the client + await act(async () => { + await result.current.connect(); + }); + + // Wait for state update + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + + const mockRequest: ClientRequest = { + method: "ping", + params: {}, + }; + + const mockSchema = z.object({ + test: z.string(), + }); + + await act(async () => { + await result.current.makeRequest(mockRequest, mockSchema); + }); + + expect(mockClient.request).toHaveBeenCalledWith( + mockRequest, + mockSchema, + expect.objectContaining({ + timeout: DEFAULT_INSPECTOR_CONFIG.MCP_SERVER_REQUEST_TIMEOUT.value, + maxTotalTimeout: + DEFAULT_INSPECTOR_CONFIG + .MCP_SERVER_REQUEST_TIMEOUT_MAX_TOTAL_TIMEOUT.value, + resetTimeoutOnProgress: + DEFAULT_INSPECTOR_CONFIG + .MCP_SERVER_REQUEST_TIMEOUT_RESET_ON_PROGRESS.value, + }), + ); + }); + + test("overrides the default config values when passed in options in makeRequest", async () => { + const { result } = renderHook(() => useConnection(defaultProps)); + + // Connect the client + await act(async () => { + await result.current.connect(); + }); + + // Wait for state update + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + + const mockRequest: ClientRequest = { + method: "ping", + params: {}, + }; + + const mockSchema = z.object({ + test: z.string(), + }); + + await act(async () => { + await result.current.makeRequest(mockRequest, mockSchema, { + timeout: 1000, + maxTotalTimeout: 2000, + resetTimeoutOnProgress: false, + }); + }); + + expect(mockClient.request).toHaveBeenCalledWith( + mockRequest, + mockSchema, + expect.objectContaining({ + timeout: 1000, + maxTotalTimeout: 2000, + resetTimeoutOnProgress: false, + }), + ); + }); + }); + + test("throws error when mcpClient is not connected", async () => { + const { result } = renderHook(() => useConnection(defaultProps)); + + const mockRequest: ClientRequest = { + method: "ping", + params: {}, + }; + + const mockSchema = z.object({ + test: z.string(), + }); + + await expect( + result.current.makeRequest(mockRequest, mockSchema), + ).rejects.toThrow("MCP client not connected"); + }); +}); diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index bff01cec..d67c623b 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -24,6 +24,7 @@ import { ToolListChangedNotificationSchema, PromptListChangedNotificationSchema, } from "@modelcontextprotocol/sdk/types.js"; +import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js"; import { useState } from "react"; import { useToast } from "@/hooks/use-toast"; import { z } from "zod"; @@ -32,6 +33,13 @@ import { Notification, StdErrNotificationSchema } from "../notificationTypes"; import { auth } from "@modelcontextprotocol/sdk/client/auth.js"; import { authProvider } from "../auth"; import packageJson from "../../../package.json"; +import { + getMCPProxyAddress, + getMCPServerRequestMaxTotalTimeout, + resetRequestTimeoutOnProgress, +} from "@/utils/configUtils"; +import { getMCPServerRequestTimeout } from "@/utils/configUtils"; +import { InspectorConfig } from "../configurationTypes"; interface UseConnectionOptions { transportType: "stdio" | "sse"; @@ -39,9 +47,8 @@ interface UseConnectionOptions { args: string; sseUrl: string; env: Record; - proxyServerUrl: string; bearerToken?: string; - requestTimeout?: number; + config: InspectorConfig; onNotification?: (notification: Notification) => void; onStdErrNotification?: (notification: Notification) => void; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -50,21 +57,14 @@ interface UseConnectionOptions { getRoots?: () => any[]; } -interface RequestOptions { - signal?: AbortSignal; - timeout?: number; - suppressToast?: boolean; -} - export function useConnection({ transportType, command, args, sseUrl, env, - proxyServerUrl, bearerToken, - requestTimeout, + config, onNotification, onStdErrNotification, onPendingRequest, @@ -94,7 +94,7 @@ export function useConnection({ const makeRequest = async ( request: ClientRequest, schema: T, - options?: RequestOptions, + options?: RequestOptions & { suppressToast?: boolean }, ): Promise> => { if (!mcpClient) { throw new Error("MCP client not connected"); @@ -102,23 +102,25 @@ export function useConnection({ try { const abortController = new AbortController(); - const timeoutId = setTimeout(() => { - abortController.abort("Request timed out"); - }, options?.timeout ?? requestTimeout); - let response; try { response = await mcpClient.request(request, schema, { signal: options?.signal ?? abortController.signal, + resetTimeoutOnProgress: + options?.resetTimeoutOnProgress ?? + resetRequestTimeoutOnProgress(config), + timeout: options?.timeout ?? getMCPServerRequestTimeout(config), + maxTotalTimeout: + options?.maxTotalTimeout ?? + getMCPServerRequestMaxTotalTimeout(config), }); + pushHistory(request, response); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); pushHistory(request, { error: errorMessage }); throw error; - } finally { - clearTimeout(timeoutId); } return response; @@ -211,7 +213,7 @@ export function useConnection({ const checkProxyHealth = async () => { try { - const proxyHealthUrl = new URL(`${proxyServerUrl}/health`); + const proxyHealthUrl = new URL(`${getMCPProxyAddress(config)}/health`); const proxyHealthResponse = await fetch(proxyHealthUrl); const proxyHealth = await proxyHealthResponse.json(); if (proxyHealth?.status !== "ok") { @@ -256,7 +258,7 @@ export function useConnection({ setConnectionStatus("error-connecting-to-proxy"); return; } - const mcpProxyServerUrl = new URL(`${proxyServerUrl}/sse`); + const mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/sse`); mcpProxyServerUrl.searchParams.append("transportType", transportType); if (transportType === "stdio") { mcpProxyServerUrl.searchParams.append("command", command); diff --git a/client/src/utils/configUtils.ts b/client/src/utils/configUtils.ts index a6f2dd21..3295f7de 100644 --- a/client/src/utils/configUtils.ts +++ b/client/src/utils/configUtils.ts @@ -12,3 +12,15 @@ export const getMCPProxyAddress = (config: InspectorConfig): string => { export const getMCPServerRequestTimeout = (config: InspectorConfig): number => { return config.MCP_SERVER_REQUEST_TIMEOUT.value as number; }; + +export const resetRequestTimeoutOnProgress = ( + config: InspectorConfig, +): boolean => { + return config.MCP_SERVER_REQUEST_TIMEOUT_RESET_ON_PROGRESS.value as boolean; +}; + +export const getMCPServerRequestMaxTotalTimeout = ( + config: InspectorConfig, +): number => { + return config.MCP_SERVER_REQUEST_TIMEOUT_MAX_TOTAL_TIMEOUT.value as number; +}; From e35343537cced3f92ac012202d434af135231e2e Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Sat, 5 Apr 2025 01:46:57 +0530 Subject: [PATCH 2/6] Add proper support for progress flow during tool calling --- client/src/App.tsx | 44 +++++++++------ client/src/components/Sidebar.tsx | 2 +- client/src/components/ToolsTab.tsx | 31 +++++++++-- .../src/components/__tests__/Sidebar.test.tsx | 48 +++++++++++++++++ .../components/__tests__/ToolsTab.test.tsx | 54 ++++++++++++++++--- client/src/lib/configurationTypes.ts | 4 +- client/src/lib/constants.ts | 7 +-- .../hooks/__tests__/useConnection.test.tsx | 7 ++- client/src/lib/hooks/useConnection.ts | 42 ++++++++++----- client/src/utils/configUtils.ts | 4 +- package-lock.json | 22 ++++---- 11 files changed, 204 insertions(+), 61 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 61dcad7e..dd7894f6 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -411,21 +411,34 @@ const App = () => { }; const callTool = async (name: string, params: Record) => { - const response = await makeConnectionRequest( - { - method: "tools/call" as const, - params: { - name, - arguments: params, - _meta: { - progressToken: progressTokenRef.current++, + try { + const response = await makeConnectionRequest( + { + method: "tools/call" as const, + params: { + name, + arguments: params, + _meta: { + progressToken: progressTokenRef.current++, + }, }, }, - }, - CompatibilityCallToolResultSchema, - "tools", - ); - setToolResult(response); + CompatibilityCallToolResultSchema, + "tools", + ); + setToolResult(response); + } catch (e) { + const toolResult: CompatibilityCallToolResult = { + content: [ + { + type: "text", + text: (e as Error).message ?? String(e), + }, + ], + isError: true, + }; + setToolResult(toolResult); + } }; const handleRootsChange = async () => { @@ -633,9 +646,10 @@ const App = () => { setTools([]); setNextToolCursor(undefined); }} - callTool={(name, params) => { + callTool={async (name, params) => { clearError("tools"); - callTool(name, params); + setToolResult(null); + await callTool(name, params); }} selectedTool={selectedTool} setSelectedTool={(tool) => { diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 19f8020b..039453e9 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -325,7 +325,7 @@ const Sidebar = ({ return (
-
diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx index e527f6d4..4d0e3f19 100644 --- a/client/src/components/__tests__/Sidebar.test.tsx +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -350,6 +350,54 @@ describe("Sidebar Environment Variables", () => { ); }); + it("should update MCP server proxy address", () => { + const setConfig = jest.fn(); + renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig }); + + openConfigSection(); + + const proxyAddressInput = screen.getByTestId( + "MCP_PROXY_FULL_ADDRESS-input", + ); + fireEvent.change(proxyAddressInput, { + target: { value: "http://localhost:8080" }, + }); + + expect(setConfig).toHaveBeenCalledWith( + expect.objectContaining({ + MCP_PROXY_FULL_ADDRESS: { + description: + "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577", + value: "http://localhost:8080", + }, + }), + ); + }); + + it("should update max total timeout", () => { + const setConfig = jest.fn(); + renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig }); + + openConfigSection(); + + const maxTotalTimeoutInput = screen.getByTestId( + "MCP_REQUEST_MAX_TOTAL_TIMEOUT-input", + ); + fireEvent.change(maxTotalTimeoutInput, { + target: { value: "10000" }, + }); + + expect(setConfig).toHaveBeenCalledWith( + expect.objectContaining({ + MCP_REQUEST_MAX_TOTAL_TIMEOUT: { + description: + "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)", + value: 10000, + }, + }), + ); + }); + it("should handle invalid timeout values entered by user", () => { const setConfig = jest.fn(); renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig }); diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 349977ae..c46a32fc 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from "@testing-library/react"; +import { render, screen, fireEvent, act } from "@testing-library/react"; import { describe, it, expect, jest } from "@jest/globals"; import "@testing-library/jest-dom"; import ToolsTab from "../ToolsTab"; @@ -43,7 +43,7 @@ describe("ToolsTab", () => { tools: mockTools, listTools: jest.fn(), clearTools: jest.fn(), - callTool: jest.fn(), + callTool: jest.fn(async () => {}), selectedTool: null, setSelectedTool: jest.fn(), toolResult: null, @@ -59,14 +59,16 @@ describe("ToolsTab", () => { ); }; - it("should reset input values when switching tools", () => { + it("should reset input values when switching tools", async () => { const { rerender } = renderToolsTab({ selectedTool: mockTools[0], }); // Enter a value in the first tool's input const input = screen.getByRole("spinbutton") as HTMLInputElement; - fireEvent.change(input, { target: { value: "42" } }); + await act(async () => { + fireEvent.change(input, { target: { value: "42" } }); + }); expect(input.value).toBe("42"); // Switch to second tool @@ -80,7 +82,8 @@ describe("ToolsTab", () => { const newInput = screen.getByRole("spinbutton") as HTMLInputElement; expect(newInput.value).toBe(""); }); - it("should handle integer type inputs", () => { + + it("should handle integer type inputs", async () => { renderToolsTab({ selectedTool: mockTools[1], // Use the tool with integer type }); @@ -93,10 +96,49 @@ describe("ToolsTab", () => { expect(input.value).toBe("42"); const submitButton = screen.getByRole("button", { name: /run tool/i }); - fireEvent.click(submitButton); + await act(async () => { + fireEvent.click(submitButton); + }); expect(defaultProps.callTool).toHaveBeenCalledWith(mockTools[1].name, { count: 42, }); }); + + it("should disable button and change text while tool is running", async () => { + // Create a promise that we can resolve later + let resolvePromise: ((value: unknown) => void) | undefined; + const mockPromise = new Promise((resolve) => { + resolvePromise = resolve; + }); + + // Mock callTool to return our promise + const mockCallTool = jest.fn().mockReturnValue(mockPromise); + + renderToolsTab({ + selectedTool: mockTools[0], + callTool: mockCallTool, + }); + + const submitButton = screen.getByRole("button", { name: /run tool/i }); + expect(submitButton.getAttribute("disabled")).toBeNull(); + + // Click the button and verify immediate state changes + await act(async () => { + fireEvent.click(submitButton); + }); + + // Verify button is disabled and text changed + expect(submitButton.getAttribute("disabled")).not.toBeNull(); + expect(submitButton.textContent).toBe("Running..."); + + // Resolve the promise to simulate tool completion + await act(async () => { + if (resolvePromise) { + await resolvePromise({}); + } + }); + + expect(submitButton.getAttribute("disabled")).toBeNull(); + }); }); diff --git a/client/src/lib/configurationTypes.ts b/client/src/lib/configurationTypes.ts index d0c12638..b4dd86e4 100644 --- a/client/src/lib/configurationTypes.ts +++ b/client/src/lib/configurationTypes.ts @@ -20,13 +20,13 @@ export type InspectorConfig = { * Whether to reset the timeout on progress notifications. Useful for long-running operations that send periodic progress updates. * Refer: https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/utilities/progress/#progress-flow */ - MCP_SERVER_REQUEST_TIMEOUT_RESET_ON_PROGRESS: ConfigItem; + MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS: ConfigItem; /** * Maximum total time in milliseconds to wait for a response from the MCP server before timing out. Used in conjunction with MCP_SERVER_REQUEST_TIMEOUT_RESET_ON_PROGRESS. * Refer: https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/utilities/progress/#progress-flow */ - MCP_SERVER_REQUEST_TIMEOUT_MAX_TOTAL_TIMEOUT: ConfigItem; + MCP_REQUEST_MAX_TOTAL_TIMEOUT: ConfigItem; /** * The full address of the MCP Proxy Server, in case it is running on a non-default address. Example: http://10.1.1.22:5577 diff --git a/client/src/lib/constants.ts b/client/src/lib/constants.ts index 9caf4bc6..f5948274 100644 --- a/client/src/lib/constants.ts +++ b/client/src/lib/constants.ts @@ -25,12 +25,13 @@ export const DEFAULT_INSPECTOR_CONFIG: InspectorConfig = { description: "Timeout for requests to the MCP server (ms)", value: 10000, }, - MCP_SERVER_REQUEST_TIMEOUT_RESET_ON_PROGRESS: { + MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS: { description: "Reset timeout on progress notifications", value: true, }, - MCP_SERVER_REQUEST_TIMEOUT_MAX_TOTAL_TIMEOUT: { - description: "Maximum total timeout for requests sent to the MCP server (ms)", + MCP_REQUEST_MAX_TOTAL_TIMEOUT: { + description: + "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)", value: 60000, }, MCP_PROXY_FULL_ADDRESS: { diff --git a/client/src/lib/hooks/__tests__/useConnection.test.tsx b/client/src/lib/hooks/__tests__/useConnection.test.tsx index 7a96802b..1671b700 100644 --- a/client/src/lib/hooks/__tests__/useConnection.test.tsx +++ b/client/src/lib/hooks/__tests__/useConnection.test.tsx @@ -95,11 +95,10 @@ describe("useConnection", () => { expect.objectContaining({ timeout: DEFAULT_INSPECTOR_CONFIG.MCP_SERVER_REQUEST_TIMEOUT.value, maxTotalTimeout: - DEFAULT_INSPECTOR_CONFIG - .MCP_SERVER_REQUEST_TIMEOUT_MAX_TOTAL_TIMEOUT.value, + DEFAULT_INSPECTOR_CONFIG.MCP_REQUEST_MAX_TOTAL_TIMEOUT.value, resetTimeoutOnProgress: - DEFAULT_INSPECTOR_CONFIG - .MCP_SERVER_REQUEST_TIMEOUT_RESET_ON_PROGRESS.value, + DEFAULT_INSPECTOR_CONFIG.MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS + .value, }), ); }); diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts index d67c623b..ac4605c7 100644 --- a/client/src/lib/hooks/useConnection.ts +++ b/client/src/lib/hooks/useConnection.ts @@ -8,7 +8,6 @@ import { ClientRequest, CreateMessageRequestSchema, ListRootsRequestSchema, - ProgressNotificationSchema, ResourceUpdatedNotificationSchema, LoggingMessageNotificationSchema, Request, @@ -23,6 +22,7 @@ import { ResourceListChangedNotificationSchema, ToolListChangedNotificationSchema, PromptListChangedNotificationSchema, + Progress, } from "@modelcontextprotocol/sdk/types.js"; import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js"; import { useState } from "react"; @@ -99,21 +99,38 @@ export function useConnection({ if (!mcpClient) { throw new Error("MCP client not connected"); } - try { const abortController = new AbortController(); + + // prepare MCP Client request options + const mcpRequestOptions: RequestOptions = { + signal: options?.signal ?? abortController.signal, + resetTimeoutOnProgress: + options?.resetTimeoutOnProgress ?? + resetRequestTimeoutOnProgress(config), + timeout: options?.timeout ?? getMCPServerRequestTimeout(config), + maxTotalTimeout: + options?.maxTotalTimeout ?? + getMCPServerRequestMaxTotalTimeout(config), + }; + + // If progress notifications are enabled, add an onprogress hook to the MCP Client request options + // This is required by SDK to reset the timeout on progress notifications + if (mcpRequestOptions.resetTimeoutOnProgress) { + mcpRequestOptions.onprogress = (params: Progress) => { + // Add progress notification to `Server Notification` window in the UI + if (onNotification) { + onNotification({ + method: "notification/progress", + params, + }); + } + }; + } + let response; try { - response = await mcpClient.request(request, schema, { - signal: options?.signal ?? abortController.signal, - resetTimeoutOnProgress: - options?.resetTimeoutOnProgress ?? - resetRequestTimeoutOnProgress(config), - timeout: options?.timeout ?? getMCPServerRequestTimeout(config), - maxTotalTimeout: - options?.maxTotalTimeout ?? - getMCPServerRequestMaxTotalTimeout(config), - }); + response = await mcpClient.request(request, schema, mcpRequestOptions); pushHistory(request, response); } catch (error) { @@ -291,7 +308,6 @@ export function useConnection({ if (onNotification) { [ CancelledNotificationSchema, - ProgressNotificationSchema, LoggingMessageNotificationSchema, ResourceUpdatedNotificationSchema, ResourceListChangedNotificationSchema, diff --git a/client/src/utils/configUtils.ts b/client/src/utils/configUtils.ts index 577e86a3..86e16438 100644 --- a/client/src/utils/configUtils.ts +++ b/client/src/utils/configUtils.ts @@ -16,11 +16,11 @@ export const getMCPServerRequestTimeout = (config: InspectorConfig): number => { export const resetRequestTimeoutOnProgress = ( config: InspectorConfig, ): boolean => { - return config.MCP_SERVER_REQUEST_TIMEOUT_RESET_ON_PROGRESS.value as boolean; + return config.MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS.value as boolean; }; export const getMCPServerRequestMaxTotalTimeout = ( config: InspectorConfig, ): number => { - return config.MCP_SERVER_REQUEST_TIMEOUT_MAX_TOTAL_TIMEOUT.value as number; + return config.MCP_REQUEST_MAX_TOTAL_TIMEOUT.value as number; }; diff --git a/package-lock.json b/package-lock.json index e626474a..1f0b1a07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.8.0", + "version": "0.8.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.8.0", + "version": "0.8.1", "license": "MIT", "workspaces": [ "client", "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.8.0", - "@modelcontextprotocol/inspector-server": "^0.8.0", + "@modelcontextprotocol/inspector-client": "^0.8.1", + "@modelcontextprotocol/inspector-server": "^0.8.1", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -32,10 +32,10 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.8.0", + "version": "0.8.1", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.6.1", + "@modelcontextprotocol/sdk": "^1.8.0", "@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", @@ -1329,11 +1329,13 @@ "link": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.6.1", - "license": "MIT", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz", + "integrity": "sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ==", "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", + "cross-spawn": "^7.0.3", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", @@ -9483,10 +9485,10 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.8.0", + "version": "0.8.1", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.6.1", + "@modelcontextprotocol/sdk": "^1.8.0", "cors": "^2.8.5", "express": "^4.21.0", "ws": "^8.18.0", From 6be05c5278ae830bcee8915fa5d02a03cff0218d Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Sat, 5 Apr 2025 13:06:25 +0530 Subject: [PATCH 3/6] update docs --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c43f9432..a11e13a6 100644 --- a/README.md +++ b/README.md @@ -48,12 +48,16 @@ The MCP Inspector includes a proxy server that can run and communicate with loca ### Configuration -The MCP Inspector supports the following configuration settings. To change them click on the `Configuration` button in the MCP Inspector UI : +The MCP Inspector supports the following configuration settings. To change them, click on the `Configuration` button in the MCP Inspector UI: -| Name | Purpose | Default Value | -| -------------------------- | ----------------------------------------------------------------------------------------- | ------------- | -| MCP_SERVER_REQUEST_TIMEOUT | Maximum time in milliseconds to wait for a response from the MCP server before timing out | 10000 | -| MCP_PROXY_FULL_ADDRESS | The full URL of the MCP Inspector proxy server (e.g. `http://10.2.1.14:2277`) | `null` | +| Setting | Description | Default | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------- | +| `MCP_SERVER_REQUEST_TIMEOUT` | Timeout for requests to the MCP server (ms) | 10000 | +| `MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS` | Reset timeout on progress notifications | true | +| `MCP_REQUEST_MAX_TOTAL_TIMEOUT` | Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications) | 60000 | +| `MCP_PROXY_FULL_ADDRESS` | Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577 | "" | + +These settings can be adjusted in real-time through the UI and will persist across sessions. ### From this repository From f71613227f29703bcfe97fc645ed72e385bce9c2 Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Wed, 9 Apr 2025 00:40:42 +0530 Subject: [PATCH 4/6] rename --- client/src/App.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index dd7894f6..68d67a93 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -275,7 +275,7 @@ const App = () => { setErrors((prev) => ({ ...prev, [tabKey]: null })); }; - const makeConnectionRequest = async ( + const sendMCPRequest = async ( request: ClientRequest, schema: T, tabKey?: keyof typeof errors, @@ -299,7 +299,7 @@ const App = () => { }; const listResources = async () => { - const response = await makeConnectionRequest( + const response = await sendMCPRequest( { method: "resources/list" as const, params: nextResourceCursor ? { cursor: nextResourceCursor } : {}, @@ -312,7 +312,7 @@ const App = () => { }; const listResourceTemplates = async () => { - const response = await makeConnectionRequest( + const response = await sendMCPRequest( { method: "resources/templates/list" as const, params: nextResourceTemplateCursor @@ -329,7 +329,7 @@ const App = () => { }; const readResource = async (uri: string) => { - const response = await makeConnectionRequest( + const response = await sendMCPRequest( { method: "resources/read" as const, params: { uri }, @@ -342,7 +342,7 @@ const App = () => { const subscribeToResource = async (uri: string) => { if (!resourceSubscriptions.has(uri)) { - await makeConnectionRequest( + await sendMCPRequest( { method: "resources/subscribe" as const, params: { uri }, @@ -358,7 +358,7 @@ const App = () => { const unsubscribeFromResource = async (uri: string) => { if (resourceSubscriptions.has(uri)) { - await makeConnectionRequest( + await sendMCPRequest( { method: "resources/unsubscribe" as const, params: { uri }, @@ -373,7 +373,7 @@ const App = () => { }; const listPrompts = async () => { - const response = await makeConnectionRequest( + const response = await sendMCPRequest( { method: "prompts/list" as const, params: nextPromptCursor ? { cursor: nextPromptCursor } : {}, @@ -386,7 +386,7 @@ const App = () => { }; const getPrompt = async (name: string, args: Record = {}) => { - const response = await makeConnectionRequest( + const response = await sendMCPRequest( { method: "prompts/get" as const, params: { name, arguments: args }, @@ -398,7 +398,7 @@ const App = () => { }; const listTools = async () => { - const response = await makeConnectionRequest( + const response = await sendMCPRequest( { method: "tools/list" as const, params: nextToolCursor ? { cursor: nextToolCursor } : {}, @@ -412,7 +412,7 @@ const App = () => { const callTool = async (name: string, params: Record) => { try { - const response = await makeConnectionRequest( + const response = await sendMCPRequest( { method: "tools/call" as const, params: { @@ -446,7 +446,7 @@ const App = () => { }; const sendLogLevelRequest = async (level: LoggingLevel) => { - await makeConnectionRequest( + await sendMCPRequest( { method: "logging/setLevel" as const, params: { level }, @@ -664,7 +664,7 @@ const App = () => { { - void makeConnectionRequest( + void sendMCPRequest( { method: "ping" as const, }, From 48917ca4e5f3aefd4207f49fb82afa94c91c5c92 Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Wed, 9 Apr 2025 01:12:02 +0530 Subject: [PATCH 5/6] don't show full config keys --- client/src/App.tsx | 14 +++++++++++++- client/src/components/Sidebar.tsx | 2 +- client/src/components/__tests__/Sidebar.test.tsx | 12 +++++------- client/src/lib/constants.ts | 10 ++++------ 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 68d67a93..e2aab1b5 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -95,10 +95,22 @@ const App = () => { const [config, setConfig] = useState(() => { const savedConfig = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY); if (savedConfig) { - return { + // merge default config with saved config + const mergedConfig = { ...DEFAULT_INSPECTOR_CONFIG, ...JSON.parse(savedConfig), } as InspectorConfig; + + // update description of keys to match the new description (in case of any updates to the default config description) + Object.entries(mergedConfig).forEach(([key, value]) => { + mergedConfig[key as keyof InspectorConfig] = { + ...value, + description: + DEFAULT_INSPECTOR_CONFIG[key as keyof InspectorConfig].description, + }; + }); + + return mergedConfig; } return DEFAULT_INSPECTOR_CONFIG; }); diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 039453e9..2ef54b10 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -326,7 +326,7 @@ const Sidebar = ({
diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx index 4d0e3f19..f373a416 100644 --- a/client/src/components/__tests__/Sidebar.test.tsx +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -343,7 +343,7 @@ describe("Sidebar Environment Variables", () => { expect(setConfig).toHaveBeenCalledWith( expect.objectContaining({ MCP_SERVER_REQUEST_TIMEOUT: { - description: "Timeout for requests to the MCP server (ms)", + description: "Request Timeout", value: 5000, }, }), @@ -366,8 +366,7 @@ describe("Sidebar Environment Variables", () => { expect(setConfig).toHaveBeenCalledWith( expect.objectContaining({ MCP_PROXY_FULL_ADDRESS: { - description: - "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577", + description: "Inspector Proxy Address", value: "http://localhost:8080", }, }), @@ -390,8 +389,7 @@ describe("Sidebar Environment Variables", () => { expect(setConfig).toHaveBeenCalledWith( expect.objectContaining({ MCP_REQUEST_MAX_TOTAL_TIMEOUT: { - description: - "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)", + description: "Maximum Total Timeout", value: 10000, }, }), @@ -412,7 +410,7 @@ describe("Sidebar Environment Variables", () => { expect(setConfig).toHaveBeenCalledWith( expect.objectContaining({ MCP_SERVER_REQUEST_TIMEOUT: { - description: "Timeout for requests to the MCP server (ms)", + description: "Request Timeout", value: 0, }, }), @@ -457,7 +455,7 @@ describe("Sidebar Environment Variables", () => { expect(setConfig).toHaveBeenLastCalledWith( expect.objectContaining({ MCP_SERVER_REQUEST_TIMEOUT: { - description: "Timeout for requests to the MCP server (ms)", + description: "Request Timeout", value: 3000, }, }), diff --git a/client/src/lib/constants.ts b/client/src/lib/constants.ts index f5948274..1a772f53 100644 --- a/client/src/lib/constants.ts +++ b/client/src/lib/constants.ts @@ -22,21 +22,19 @@ export const DEFAULT_MCP_PROXY_LISTEN_PORT = "6277"; **/ export const DEFAULT_INSPECTOR_CONFIG: InspectorConfig = { MCP_SERVER_REQUEST_TIMEOUT: { - description: "Timeout for requests to the MCP server (ms)", + description: "Request Timeout", value: 10000, }, MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS: { - description: "Reset timeout on progress notifications", + description: "Reset Timeout on Progress", value: true, }, MCP_REQUEST_MAX_TOTAL_TIMEOUT: { - description: - "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)", + description: "Maximum Total Timeout", value: 60000, }, MCP_PROXY_FULL_ADDRESS: { - description: - "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577", + description: "Inspector Proxy Address", value: "", }, } as const; From 9c6663c4c26dddf708bb01ab2f983ec7af08d3db Mon Sep 17 00:00:00 2001 From: Pulkit Sharma Date: Wed, 9 Apr 2025 01:24:23 +0530 Subject: [PATCH 6/6] add label to configItem, use that in UI --- client/src/App.tsx | 3 +-- client/src/components/Sidebar.tsx | 2 +- .../src/components/__tests__/Sidebar.test.tsx | 17 ++++++++++++----- client/src/lib/configurationTypes.ts | 1 + client/src/lib/constants.ts | 14 ++++++++++---- 5 files changed, 25 insertions(+), 12 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index e2aab1b5..4f99ffd4 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -105,8 +105,7 @@ const App = () => { Object.entries(mergedConfig).forEach(([key, value]) => { mergedConfig[key as keyof InspectorConfig] = { ...value, - description: - DEFAULT_INSPECTOR_CONFIG[key as keyof InspectorConfig].description, + label: DEFAULT_INSPECTOR_CONFIG[key as keyof InspectorConfig].label, }; }); diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index 2ef54b10..4633d642 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -326,7 +326,7 @@ const Sidebar = ({
diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx index f373a416..8e72b008 100644 --- a/client/src/components/__tests__/Sidebar.test.tsx +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -343,7 +343,8 @@ describe("Sidebar Environment Variables", () => { expect(setConfig).toHaveBeenCalledWith( expect.objectContaining({ MCP_SERVER_REQUEST_TIMEOUT: { - description: "Request Timeout", + label: "Request Timeout", + description: "Timeout for requests to the MCP server (ms)", value: 5000, }, }), @@ -366,7 +367,9 @@ describe("Sidebar Environment Variables", () => { expect(setConfig).toHaveBeenCalledWith( expect.objectContaining({ MCP_PROXY_FULL_ADDRESS: { - description: "Inspector Proxy Address", + label: "Inspector Proxy Address", + description: + "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577", value: "http://localhost:8080", }, }), @@ -389,7 +392,9 @@ describe("Sidebar Environment Variables", () => { expect(setConfig).toHaveBeenCalledWith( expect.objectContaining({ MCP_REQUEST_MAX_TOTAL_TIMEOUT: { - description: "Maximum Total Timeout", + label: "Maximum Total Timeout", + description: + "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)", value: 10000, }, }), @@ -410,7 +415,8 @@ describe("Sidebar Environment Variables", () => { expect(setConfig).toHaveBeenCalledWith( expect.objectContaining({ MCP_SERVER_REQUEST_TIMEOUT: { - description: "Request Timeout", + label: "Request Timeout", + description: "Timeout for requests to the MCP server (ms)", value: 0, }, }), @@ -455,7 +461,8 @@ describe("Sidebar Environment Variables", () => { expect(setConfig).toHaveBeenLastCalledWith( expect.objectContaining({ MCP_SERVER_REQUEST_TIMEOUT: { - description: "Request Timeout", + label: "Request Timeout", + description: "Timeout for requests to the MCP server (ms)", value: 3000, }, }), diff --git a/client/src/lib/configurationTypes.ts b/client/src/lib/configurationTypes.ts index b4dd86e4..7c74bb71 100644 --- a/client/src/lib/configurationTypes.ts +++ b/client/src/lib/configurationTypes.ts @@ -1,4 +1,5 @@ export type ConfigItem = { + label: string; description: string; value: string | number | boolean; }; diff --git a/client/src/lib/constants.ts b/client/src/lib/constants.ts index 1a772f53..e7fa14c9 100644 --- a/client/src/lib/constants.ts +++ b/client/src/lib/constants.ts @@ -22,19 +22,25 @@ export const DEFAULT_MCP_PROXY_LISTEN_PORT = "6277"; **/ export const DEFAULT_INSPECTOR_CONFIG: InspectorConfig = { MCP_SERVER_REQUEST_TIMEOUT: { - description: "Request Timeout", + label: "Request Timeout", + description: "Timeout for requests to the MCP server (ms)", value: 10000, }, MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS: { - description: "Reset Timeout on Progress", + label: "Reset Timeout on Progress", + description: "Reset timeout on progress notifications", value: true, }, MCP_REQUEST_MAX_TOTAL_TIMEOUT: { - description: "Maximum Total Timeout", + label: "Maximum Total Timeout", + description: + "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)", value: 60000, }, MCP_PROXY_FULL_ADDRESS: { - description: "Inspector Proxy Address", + label: "Inspector Proxy Address", + description: + "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577", value: "", }, } as const;