Merge pull request #271 from pulkitsharma07/progress_flow_support
feat: Progress Support for Long Running Tool Calls ⏳
This commit is contained in:
14
README.md
14
README.md
@@ -48,12 +48,16 @@ The MCP Inspector includes a proxy server that can run and communicate with loca
|
|||||||
|
|
||||||
### Configuration
|
### 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 |
|
| Setting | Description | Default |
|
||||||
| -------------------------- | ----------------------------------------------------------------------------------------- | ------------- |
|
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ------- |
|
||||||
| MCP_SERVER_REQUEST_TIMEOUT | Maximum time in milliseconds to wait for a response from the MCP server before timing out | 10000 |
|
| `MCP_SERVER_REQUEST_TIMEOUT` | Timeout for requests to the MCP server (ms) | 10000 |
|
||||||
| MCP_PROXY_FULL_ADDRESS | The full URL of the MCP Inspector proxy server (e.g. `http://10.2.1.14:2277`) | `null` |
|
| `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 this repository
|
||||||
|
|
||||||
|
|||||||
@@ -45,10 +45,7 @@ import Sidebar from "./components/Sidebar";
|
|||||||
import ToolsTab from "./components/ToolsTab";
|
import ToolsTab from "./components/ToolsTab";
|
||||||
import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants";
|
import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants";
|
||||||
import { InspectorConfig } from "./lib/configurationTypes";
|
import { InspectorConfig } from "./lib/configurationTypes";
|
||||||
import {
|
import { getMCPProxyAddress } from "./utils/configUtils";
|
||||||
getMCPProxyAddress,
|
|
||||||
getMCPServerRequestTimeout,
|
|
||||||
} from "./utils/configUtils";
|
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
@@ -98,10 +95,21 @@ const App = () => {
|
|||||||
const [config, setConfig] = useState<InspectorConfig>(() => {
|
const [config, setConfig] = useState<InspectorConfig>(() => {
|
||||||
const savedConfig = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY);
|
const savedConfig = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY);
|
||||||
if (savedConfig) {
|
if (savedConfig) {
|
||||||
return {
|
// merge default config with saved config
|
||||||
|
const mergedConfig = {
|
||||||
...DEFAULT_INSPECTOR_CONFIG,
|
...DEFAULT_INSPECTOR_CONFIG,
|
||||||
...JSON.parse(savedConfig),
|
...JSON.parse(savedConfig),
|
||||||
} as InspectorConfig;
|
} 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,
|
||||||
|
label: DEFAULT_INSPECTOR_CONFIG[key as keyof InspectorConfig].label,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return mergedConfig;
|
||||||
}
|
}
|
||||||
return DEFAULT_INSPECTOR_CONFIG;
|
return DEFAULT_INSPECTOR_CONFIG;
|
||||||
});
|
});
|
||||||
@@ -148,7 +156,7 @@ const App = () => {
|
|||||||
serverCapabilities,
|
serverCapabilities,
|
||||||
mcpClient,
|
mcpClient,
|
||||||
requestHistory,
|
requestHistory,
|
||||||
makeRequest: makeConnectionRequest,
|
makeRequest,
|
||||||
sendNotification,
|
sendNotification,
|
||||||
handleCompletion,
|
handleCompletion,
|
||||||
completionsSupported,
|
completionsSupported,
|
||||||
@@ -161,8 +169,7 @@ const App = () => {
|
|||||||
sseUrl,
|
sseUrl,
|
||||||
env,
|
env,
|
||||||
bearerToken,
|
bearerToken,
|
||||||
proxyServerUrl: getMCPProxyAddress(config),
|
config,
|
||||||
requestTimeout: getMCPServerRequestTimeout(config),
|
|
||||||
onNotification: (notification) => {
|
onNotification: (notification) => {
|
||||||
setNotifications((prev) => [...prev, notification as ServerNotification]);
|
setNotifications((prev) => [...prev, notification as ServerNotification]);
|
||||||
},
|
},
|
||||||
@@ -279,13 +286,13 @@ const App = () => {
|
|||||||
setErrors((prev) => ({ ...prev, [tabKey]: null }));
|
setErrors((prev) => ({ ...prev, [tabKey]: null }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeRequest = async <T extends z.ZodType>(
|
const sendMCPRequest = async <T extends z.ZodType>(
|
||||||
request: ClientRequest,
|
request: ClientRequest,
|
||||||
schema: T,
|
schema: T,
|
||||||
tabKey?: keyof typeof errors,
|
tabKey?: keyof typeof errors,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const response = await makeConnectionRequest(request, schema);
|
const response = await makeRequest(request, schema);
|
||||||
if (tabKey !== undefined) {
|
if (tabKey !== undefined) {
|
||||||
clearError(tabKey);
|
clearError(tabKey);
|
||||||
}
|
}
|
||||||
@@ -303,7 +310,7 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listResources = async () => {
|
const listResources = async () => {
|
||||||
const response = await makeRequest(
|
const response = await sendMCPRequest(
|
||||||
{
|
{
|
||||||
method: "resources/list" as const,
|
method: "resources/list" as const,
|
||||||
params: nextResourceCursor ? { cursor: nextResourceCursor } : {},
|
params: nextResourceCursor ? { cursor: nextResourceCursor } : {},
|
||||||
@@ -316,7 +323,7 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listResourceTemplates = async () => {
|
const listResourceTemplates = async () => {
|
||||||
const response = await makeRequest(
|
const response = await sendMCPRequest(
|
||||||
{
|
{
|
||||||
method: "resources/templates/list" as const,
|
method: "resources/templates/list" as const,
|
||||||
params: nextResourceTemplateCursor
|
params: nextResourceTemplateCursor
|
||||||
@@ -333,7 +340,7 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const readResource = async (uri: string) => {
|
const readResource = async (uri: string) => {
|
||||||
const response = await makeRequest(
|
const response = await sendMCPRequest(
|
||||||
{
|
{
|
||||||
method: "resources/read" as const,
|
method: "resources/read" as const,
|
||||||
params: { uri },
|
params: { uri },
|
||||||
@@ -346,7 +353,7 @@ const App = () => {
|
|||||||
|
|
||||||
const subscribeToResource = async (uri: string) => {
|
const subscribeToResource = async (uri: string) => {
|
||||||
if (!resourceSubscriptions.has(uri)) {
|
if (!resourceSubscriptions.has(uri)) {
|
||||||
await makeRequest(
|
await sendMCPRequest(
|
||||||
{
|
{
|
||||||
method: "resources/subscribe" as const,
|
method: "resources/subscribe" as const,
|
||||||
params: { uri },
|
params: { uri },
|
||||||
@@ -362,7 +369,7 @@ const App = () => {
|
|||||||
|
|
||||||
const unsubscribeFromResource = async (uri: string) => {
|
const unsubscribeFromResource = async (uri: string) => {
|
||||||
if (resourceSubscriptions.has(uri)) {
|
if (resourceSubscriptions.has(uri)) {
|
||||||
await makeRequest(
|
await sendMCPRequest(
|
||||||
{
|
{
|
||||||
method: "resources/unsubscribe" as const,
|
method: "resources/unsubscribe" as const,
|
||||||
params: { uri },
|
params: { uri },
|
||||||
@@ -377,7 +384,7 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listPrompts = async () => {
|
const listPrompts = async () => {
|
||||||
const response = await makeRequest(
|
const response = await sendMCPRequest(
|
||||||
{
|
{
|
||||||
method: "prompts/list" as const,
|
method: "prompts/list" as const,
|
||||||
params: nextPromptCursor ? { cursor: nextPromptCursor } : {},
|
params: nextPromptCursor ? { cursor: nextPromptCursor } : {},
|
||||||
@@ -390,7 +397,7 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getPrompt = async (name: string, args: Record<string, string> = {}) => {
|
const getPrompt = async (name: string, args: Record<string, string> = {}) => {
|
||||||
const response = await makeRequest(
|
const response = await sendMCPRequest(
|
||||||
{
|
{
|
||||||
method: "prompts/get" as const,
|
method: "prompts/get" as const,
|
||||||
params: { name, arguments: args },
|
params: { name, arguments: args },
|
||||||
@@ -402,7 +409,7 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listTools = async () => {
|
const listTools = async () => {
|
||||||
const response = await makeRequest(
|
const response = await sendMCPRequest(
|
||||||
{
|
{
|
||||||
method: "tools/list" as const,
|
method: "tools/list" as const,
|
||||||
params: nextToolCursor ? { cursor: nextToolCursor } : {},
|
params: nextToolCursor ? { cursor: nextToolCursor } : {},
|
||||||
@@ -415,21 +422,34 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const callTool = async (name: string, params: Record<string, unknown>) => {
|
const callTool = async (name: string, params: Record<string, unknown>) => {
|
||||||
const response = await makeRequest(
|
try {
|
||||||
{
|
const response = await sendMCPRequest(
|
||||||
method: "tools/call" as const,
|
{
|
||||||
params: {
|
method: "tools/call" as const,
|
||||||
name,
|
params: {
|
||||||
arguments: params,
|
name,
|
||||||
_meta: {
|
arguments: params,
|
||||||
progressToken: progressTokenRef.current++,
|
_meta: {
|
||||||
|
progressToken: progressTokenRef.current++,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
CompatibilityCallToolResultSchema,
|
||||||
CompatibilityCallToolResultSchema,
|
"tools",
|
||||||
"tools",
|
);
|
||||||
);
|
setToolResult(response);
|
||||||
setToolResult(response);
|
} catch (e) {
|
||||||
|
const toolResult: CompatibilityCallToolResult = {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: (e as Error).message ?? String(e),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
setToolResult(toolResult);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRootsChange = async () => {
|
const handleRootsChange = async () => {
|
||||||
@@ -437,7 +457,7 @@ const App = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sendLogLevelRequest = async (level: LoggingLevel) => {
|
const sendLogLevelRequest = async (level: LoggingLevel) => {
|
||||||
await makeRequest(
|
await sendMCPRequest(
|
||||||
{
|
{
|
||||||
method: "logging/setLevel" as const,
|
method: "logging/setLevel" as const,
|
||||||
params: { level },
|
params: { level },
|
||||||
@@ -637,9 +657,10 @@ const App = () => {
|
|||||||
setTools([]);
|
setTools([]);
|
||||||
setNextToolCursor(undefined);
|
setNextToolCursor(undefined);
|
||||||
}}
|
}}
|
||||||
callTool={(name, params) => {
|
callTool={async (name, params) => {
|
||||||
clearError("tools");
|
clearError("tools");
|
||||||
callTool(name, params);
|
setToolResult(null);
|
||||||
|
await callTool(name, params);
|
||||||
}}
|
}}
|
||||||
selectedTool={selectedTool}
|
selectedTool={selectedTool}
|
||||||
setSelectedTool={(tool) => {
|
setSelectedTool={(tool) => {
|
||||||
@@ -654,7 +675,7 @@ const App = () => {
|
|||||||
<ConsoleTab />
|
<ConsoleTab />
|
||||||
<PingTab
|
<PingTab
|
||||||
onPingClick={() => {
|
onPingClick={() => {
|
||||||
void makeRequest(
|
void sendMCPRequest(
|
||||||
{
|
{
|
||||||
method: "ping" as const,
|
method: "ping" as const,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -325,8 +325,8 @@ const Sidebar = ({
|
|||||||
return (
|
return (
|
||||||
<div key={key} className="space-y-2">
|
<div key={key} className="space-y-2">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<label className="text-sm font-medium text-green-600">
|
<label className="text-sm font-medium text-green-600 break-all">
|
||||||
{configKey}
|
{configItem.label}
|
||||||
</label>
|
</label>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
ListToolsResult,
|
ListToolsResult,
|
||||||
Tool,
|
Tool,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { Send } from "lucide-react";
|
import { Loader2, Send } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ListPane from "./ListPane";
|
import ListPane from "./ListPane";
|
||||||
import JsonView from "./JsonView";
|
import JsonView from "./JsonView";
|
||||||
@@ -31,7 +31,7 @@ const ToolsTab = ({
|
|||||||
tools: Tool[];
|
tools: Tool[];
|
||||||
listTools: () => void;
|
listTools: () => void;
|
||||||
clearTools: () => void;
|
clearTools: () => void;
|
||||||
callTool: (name: string, params: Record<string, unknown>) => void;
|
callTool: (name: string, params: Record<string, unknown>) => Promise<void>;
|
||||||
selectedTool: Tool | null;
|
selectedTool: Tool | null;
|
||||||
setSelectedTool: (tool: Tool | null) => void;
|
setSelectedTool: (tool: Tool | null) => void;
|
||||||
toolResult: CompatibilityCallToolResult | null;
|
toolResult: CompatibilityCallToolResult | null;
|
||||||
@@ -39,6 +39,8 @@ const ToolsTab = ({
|
|||||||
error: string | null;
|
error: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const [params, setParams] = useState<Record<string, unknown>>({});
|
const [params, setParams] = useState<Record<string, unknown>>({});
|
||||||
|
const [isToolRunning, setIsToolRunning] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setParams({});
|
setParams({});
|
||||||
}, [selectedTool]);
|
}, [selectedTool]);
|
||||||
@@ -235,9 +237,28 @@ const ToolsTab = ({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
<Button onClick={() => callTool(selectedTool.name, params)}>
|
<Button
|
||||||
<Send className="w-4 h-4 mr-2" />
|
onClick={async () => {
|
||||||
Run Tool
|
try {
|
||||||
|
setIsToolRunning(true);
|
||||||
|
await callTool(selectedTool.name, params);
|
||||||
|
} finally {
|
||||||
|
setIsToolRunning(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={isToolRunning}
|
||||||
|
>
|
||||||
|
{isToolRunning ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||||
|
Running...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Send className="w-4 h-4 mr-2" />
|
||||||
|
Run Tool
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{toolResult && renderToolResult()}
|
{toolResult && renderToolResult()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -343,6 +343,7 @@ describe("Sidebar Environment Variables", () => {
|
|||||||
expect(setConfig).toHaveBeenCalledWith(
|
expect(setConfig).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||||
|
label: "Request Timeout",
|
||||||
description: "Timeout for requests to the MCP server (ms)",
|
description: "Timeout for requests to the MCP server (ms)",
|
||||||
value: 5000,
|
value: 5000,
|
||||||
},
|
},
|
||||||
@@ -350,6 +351,56 @@ 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: {
|
||||||
|
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",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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: {
|
||||||
|
label: "Maximum 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", () => {
|
it("should handle invalid timeout values entered by user", () => {
|
||||||
const setConfig = jest.fn();
|
const setConfig = jest.fn();
|
||||||
renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig });
|
renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig });
|
||||||
@@ -364,6 +415,7 @@ describe("Sidebar Environment Variables", () => {
|
|||||||
expect(setConfig).toHaveBeenCalledWith(
|
expect(setConfig).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||||
|
label: "Request Timeout",
|
||||||
description: "Timeout for requests to the MCP server (ms)",
|
description: "Timeout for requests to the MCP server (ms)",
|
||||||
value: 0,
|
value: 0,
|
||||||
},
|
},
|
||||||
@@ -409,6 +461,7 @@ describe("Sidebar Environment Variables", () => {
|
|||||||
expect(setConfig).toHaveBeenLastCalledWith(
|
expect(setConfig).toHaveBeenLastCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||||
|
label: "Request Timeout",
|
||||||
description: "Timeout for requests to the MCP server (ms)",
|
description: "Timeout for requests to the MCP server (ms)",
|
||||||
value: 3000,
|
value: 3000,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 { describe, it, expect, jest } from "@jest/globals";
|
||||||
import "@testing-library/jest-dom";
|
import "@testing-library/jest-dom";
|
||||||
import ToolsTab from "../ToolsTab";
|
import ToolsTab from "../ToolsTab";
|
||||||
@@ -43,7 +43,7 @@ describe("ToolsTab", () => {
|
|||||||
tools: mockTools,
|
tools: mockTools,
|
||||||
listTools: jest.fn(),
|
listTools: jest.fn(),
|
||||||
clearTools: jest.fn(),
|
clearTools: jest.fn(),
|
||||||
callTool: jest.fn(),
|
callTool: jest.fn(async () => {}),
|
||||||
selectedTool: null,
|
selectedTool: null,
|
||||||
setSelectedTool: jest.fn(),
|
setSelectedTool: jest.fn(),
|
||||||
toolResult: null,
|
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({
|
const { rerender } = renderToolsTab({
|
||||||
selectedTool: mockTools[0],
|
selectedTool: mockTools[0],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Enter a value in the first tool's input
|
// Enter a value in the first tool's input
|
||||||
const input = screen.getByRole("spinbutton") as HTMLInputElement;
|
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");
|
expect(input.value).toBe("42");
|
||||||
|
|
||||||
// Switch to second tool
|
// Switch to second tool
|
||||||
@@ -80,7 +82,8 @@ describe("ToolsTab", () => {
|
|||||||
const newInput = screen.getByRole("spinbutton") as HTMLInputElement;
|
const newInput = screen.getByRole("spinbutton") as HTMLInputElement;
|
||||||
expect(newInput.value).toBe("");
|
expect(newInput.value).toBe("");
|
||||||
});
|
});
|
||||||
it("should handle integer type inputs", () => {
|
|
||||||
|
it("should handle integer type inputs", async () => {
|
||||||
renderToolsTab({
|
renderToolsTab({
|
||||||
selectedTool: mockTools[1], // Use the tool with integer type
|
selectedTool: mockTools[1], // Use the tool with integer type
|
||||||
});
|
});
|
||||||
@@ -93,10 +96,49 @@ describe("ToolsTab", () => {
|
|||||||
expect(input.value).toBe("42");
|
expect(input.value).toBe("42");
|
||||||
|
|
||||||
const submitButton = screen.getByRole("button", { name: /run tool/i });
|
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, {
|
expect(defaultProps.callTool).toHaveBeenCalledWith(mockTools[1].name, {
|
||||||
count: 42,
|
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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export type ConfigItem = {
|
export type ConfigItem = {
|
||||||
|
label: string;
|
||||||
description: string;
|
description: string;
|
||||||
value: string | number | boolean;
|
value: string | number | boolean;
|
||||||
};
|
};
|
||||||
@@ -15,5 +16,21 @@ export type InspectorConfig = {
|
|||||||
* Maximum time in milliseconds to wait for a response from the MCP server before timing out.
|
* Maximum time in milliseconds to wait for a response from the MCP server before timing out.
|
||||||
*/
|
*/
|
||||||
MCP_SERVER_REQUEST_TIMEOUT: ConfigItem;
|
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_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_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
|
||||||
|
*/
|
||||||
MCP_PROXY_FULL_ADDRESS: ConfigItem;
|
MCP_PROXY_FULL_ADDRESS: ConfigItem;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,10 +22,23 @@ export const DEFAULT_MCP_PROXY_LISTEN_PORT = "6277";
|
|||||||
**/
|
**/
|
||||||
export const DEFAULT_INSPECTOR_CONFIG: InspectorConfig = {
|
export const DEFAULT_INSPECTOR_CONFIG: InspectorConfig = {
|
||||||
MCP_SERVER_REQUEST_TIMEOUT: {
|
MCP_SERVER_REQUEST_TIMEOUT: {
|
||||||
|
label: "Request Timeout",
|
||||||
description: "Timeout for requests to the MCP server (ms)",
|
description: "Timeout for requests to the MCP server (ms)",
|
||||||
value: 10000,
|
value: 10000,
|
||||||
},
|
},
|
||||||
|
MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS: {
|
||||||
|
label: "Reset Timeout on Progress",
|
||||||
|
description: "Reset timeout on progress notifications",
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
MCP_REQUEST_MAX_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: {
|
MCP_PROXY_FULL_ADDRESS: {
|
||||||
|
label: "Inspector Proxy Address",
|
||||||
description:
|
description:
|
||||||
"Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577",
|
"Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577",
|
||||||
value: "",
|
value: "",
|
||||||
|
|||||||
164
client/src/lib/hooks/__tests__/useConnection.test.tsx
Normal file
164
client/src/lib/hooks/__tests__/useConnection.test.tsx
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
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_REQUEST_MAX_TOTAL_TIMEOUT.value,
|
||||||
|
resetTimeoutOnProgress:
|
||||||
|
DEFAULT_INSPECTOR_CONFIG.MCP_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");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
ClientRequest,
|
ClientRequest,
|
||||||
CreateMessageRequestSchema,
|
CreateMessageRequestSchema,
|
||||||
ListRootsRequestSchema,
|
ListRootsRequestSchema,
|
||||||
ProgressNotificationSchema,
|
|
||||||
ResourceUpdatedNotificationSchema,
|
ResourceUpdatedNotificationSchema,
|
||||||
LoggingMessageNotificationSchema,
|
LoggingMessageNotificationSchema,
|
||||||
Request,
|
Request,
|
||||||
@@ -23,7 +22,9 @@ import {
|
|||||||
ResourceListChangedNotificationSchema,
|
ResourceListChangedNotificationSchema,
|
||||||
ToolListChangedNotificationSchema,
|
ToolListChangedNotificationSchema,
|
||||||
PromptListChangedNotificationSchema,
|
PromptListChangedNotificationSchema,
|
||||||
|
Progress,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -32,6 +33,13 @@ import { Notification, StdErrNotificationSchema } from "../notificationTypes";
|
|||||||
import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
|
import { auth } from "@modelcontextprotocol/sdk/client/auth.js";
|
||||||
import { authProvider } from "../auth";
|
import { authProvider } from "../auth";
|
||||||
import packageJson from "../../../package.json";
|
import packageJson from "../../../package.json";
|
||||||
|
import {
|
||||||
|
getMCPProxyAddress,
|
||||||
|
getMCPServerRequestMaxTotalTimeout,
|
||||||
|
resetRequestTimeoutOnProgress,
|
||||||
|
} from "@/utils/configUtils";
|
||||||
|
import { getMCPServerRequestTimeout } from "@/utils/configUtils";
|
||||||
|
import { InspectorConfig } from "../configurationTypes";
|
||||||
|
|
||||||
interface UseConnectionOptions {
|
interface UseConnectionOptions {
|
||||||
transportType: "stdio" | "sse";
|
transportType: "stdio" | "sse";
|
||||||
@@ -39,9 +47,8 @@ interface UseConnectionOptions {
|
|||||||
args: string;
|
args: string;
|
||||||
sseUrl: string;
|
sseUrl: string;
|
||||||
env: Record<string, string>;
|
env: Record<string, string>;
|
||||||
proxyServerUrl: string;
|
|
||||||
bearerToken?: string;
|
bearerToken?: string;
|
||||||
requestTimeout?: number;
|
config: InspectorConfig;
|
||||||
onNotification?: (notification: Notification) => void;
|
onNotification?: (notification: Notification) => void;
|
||||||
onStdErrNotification?: (notification: Notification) => void;
|
onStdErrNotification?: (notification: Notification) => void;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -50,21 +57,14 @@ interface UseConnectionOptions {
|
|||||||
getRoots?: () => any[];
|
getRoots?: () => any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RequestOptions {
|
|
||||||
signal?: AbortSignal;
|
|
||||||
timeout?: number;
|
|
||||||
suppressToast?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useConnection({
|
export function useConnection({
|
||||||
transportType,
|
transportType,
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
sseUrl,
|
sseUrl,
|
||||||
env,
|
env,
|
||||||
proxyServerUrl,
|
|
||||||
bearerToken,
|
bearerToken,
|
||||||
requestTimeout,
|
config,
|
||||||
onNotification,
|
onNotification,
|
||||||
onStdErrNotification,
|
onStdErrNotification,
|
||||||
onPendingRequest,
|
onPendingRequest,
|
||||||
@@ -94,31 +94,50 @@ export function useConnection({
|
|||||||
const makeRequest = async <T extends z.ZodType>(
|
const makeRequest = async <T extends z.ZodType>(
|
||||||
request: ClientRequest,
|
request: ClientRequest,
|
||||||
schema: T,
|
schema: T,
|
||||||
options?: RequestOptions,
|
options?: RequestOptions & { suppressToast?: boolean },
|
||||||
): Promise<z.output<T>> => {
|
): Promise<z.output<T>> => {
|
||||||
if (!mcpClient) {
|
if (!mcpClient) {
|
||||||
throw new Error("MCP client not connected");
|
throw new Error("MCP client not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const timeoutId = setTimeout(() => {
|
|
||||||
abortController.abort("Request timed out");
|
// prepare MCP Client request options
|
||||||
}, options?.timeout ?? requestTimeout);
|
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;
|
let response;
|
||||||
try {
|
try {
|
||||||
response = await mcpClient.request(request, schema, {
|
response = await mcpClient.request(request, schema, mcpRequestOptions);
|
||||||
signal: options?.signal ?? abortController.signal,
|
|
||||||
});
|
|
||||||
pushHistory(request, response);
|
pushHistory(request, response);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
error instanceof Error ? error.message : String(error);
|
error instanceof Error ? error.message : String(error);
|
||||||
pushHistory(request, { error: errorMessage });
|
pushHistory(request, { error: errorMessage });
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@@ -211,7 +230,7 @@ export function useConnection({
|
|||||||
|
|
||||||
const checkProxyHealth = async () => {
|
const checkProxyHealth = async () => {
|
||||||
try {
|
try {
|
||||||
const proxyHealthUrl = new URL(`${proxyServerUrl}/health`);
|
const proxyHealthUrl = new URL(`${getMCPProxyAddress(config)}/health`);
|
||||||
const proxyHealthResponse = await fetch(proxyHealthUrl);
|
const proxyHealthResponse = await fetch(proxyHealthUrl);
|
||||||
const proxyHealth = await proxyHealthResponse.json();
|
const proxyHealth = await proxyHealthResponse.json();
|
||||||
if (proxyHealth?.status !== "ok") {
|
if (proxyHealth?.status !== "ok") {
|
||||||
@@ -256,7 +275,7 @@ export function useConnection({
|
|||||||
setConnectionStatus("error-connecting-to-proxy");
|
setConnectionStatus("error-connecting-to-proxy");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const mcpProxyServerUrl = new URL(`${proxyServerUrl}/sse`);
|
const mcpProxyServerUrl = new URL(`${getMCPProxyAddress(config)}/sse`);
|
||||||
mcpProxyServerUrl.searchParams.append("transportType", transportType);
|
mcpProxyServerUrl.searchParams.append("transportType", transportType);
|
||||||
if (transportType === "stdio") {
|
if (transportType === "stdio") {
|
||||||
mcpProxyServerUrl.searchParams.append("command", command);
|
mcpProxyServerUrl.searchParams.append("command", command);
|
||||||
@@ -289,7 +308,6 @@ export function useConnection({
|
|||||||
if (onNotification) {
|
if (onNotification) {
|
||||||
[
|
[
|
||||||
CancelledNotificationSchema,
|
CancelledNotificationSchema,
|
||||||
ProgressNotificationSchema,
|
|
||||||
LoggingMessageNotificationSchema,
|
LoggingMessageNotificationSchema,
|
||||||
ResourceUpdatedNotificationSchema,
|
ResourceUpdatedNotificationSchema,
|
||||||
ResourceListChangedNotificationSchema,
|
ResourceListChangedNotificationSchema,
|
||||||
|
|||||||
@@ -12,3 +12,15 @@ export const getMCPProxyAddress = (config: InspectorConfig): string => {
|
|||||||
export const getMCPServerRequestTimeout = (config: InspectorConfig): number => {
|
export const getMCPServerRequestTimeout = (config: InspectorConfig): number => {
|
||||||
return config.MCP_SERVER_REQUEST_TIMEOUT.value as number;
|
return config.MCP_SERVER_REQUEST_TIMEOUT.value as number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const resetRequestTimeoutOnProgress = (
|
||||||
|
config: InspectorConfig,
|
||||||
|
): boolean => {
|
||||||
|
return config.MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS.value as boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMCPServerRequestMaxTotalTimeout = (
|
||||||
|
config: InspectorConfig,
|
||||||
|
): number => {
|
||||||
|
return config.MCP_REQUEST_MAX_TOTAL_TIMEOUT.value as number;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user