Merge branch 'main' into main
This commit is contained in:
@@ -7,7 +7,7 @@ Thanks for your interest in contributing! This guide explains how to get involve
|
||||
1. Fork the repository and clone it locally
|
||||
2. Install dependencies with `npm install`
|
||||
3. Run `npm run dev` to start both client and server in development mode
|
||||
4. Use the web UI at http://localhost:5173 to interact with the inspector
|
||||
4. Use the web UI at http://127.0.0.1:5173 to interact with the inspector
|
||||
|
||||
## Development Process & Pull Requests
|
||||
|
||||
|
||||
@@ -42,6 +42,10 @@ For more details on ways to use the inspector, see the [Inspector section of the
|
||||
|
||||
The inspector supports bearer token authentication for SSE connections. Enter your token in the UI when connecting to an MCP server, and it will be sent in the Authorization header.
|
||||
|
||||
### Security Considerations
|
||||
|
||||
The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server.
|
||||
|
||||
### From this repository
|
||||
|
||||
If you're working on the inspector itself:
|
||||
|
||||
@@ -102,7 +102,7 @@ async function main() {
|
||||
await Promise.any([server, client, delay(2 * 1000)]);
|
||||
const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`;
|
||||
console.log(
|
||||
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} 🚀`,
|
||||
`\n🔍 MCP Inspector is up and running at http://127.0.0.1:${CLIENT_PORT}${portParam} 🚀`,
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
@@ -3,16 +3,12 @@ module.exports = {
|
||||
testEnvironment: "jsdom",
|
||||
moduleNameMapper: {
|
||||
"^@/(.*)$": "<rootDir>/src/$1",
|
||||
"^../components/DynamicJsonForm$":
|
||||
"<rootDir>/src/utils/__mocks__/DynamicJsonForm.ts",
|
||||
"^../../components/DynamicJsonForm$":
|
||||
"<rootDir>/src/utils/__mocks__/DynamicJsonForm.ts",
|
||||
"\\.css$": "<rootDir>/src/__mocks__/styleMock.js",
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
"ts-jest",
|
||||
{
|
||||
useESM: true,
|
||||
jsx: "react-jsx",
|
||||
tsconfig: "tsconfig.jest.json",
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@modelcontextprotocol/inspector-client",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"description": "Client-side application for the Model Context Protocol inspector",
|
||||
"license": "MIT",
|
||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||
@@ -24,8 +24,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.6.1",
|
||||
"@radix-ui/react-dialog": "^1.1.3",
|
||||
"@radix-ui/react-checkbox": "^1.1.4",
|
||||
"@radix-ui/react-dialog": "^1.1.3",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-popover": "^1.1.3",
|
||||
@@ -38,7 +38,7 @@
|
||||
"cmdk": "^1.0.4",
|
||||
"lucide-react": "^0.447.0",
|
||||
"pkce-challenge": "^4.1.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
@@ -50,6 +50,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.7.5",
|
||||
"@types/react": "^18.3.10",
|
||||
|
||||
@@ -55,16 +55,6 @@ const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
|
||||
|
||||
const App = () => {
|
||||
// Handle OAuth callback route
|
||||
if (window.location.pathname === "/oauth/callback") {
|
||||
const OAuthCallback = React.lazy(
|
||||
() => import("./components/OAuthCallback"),
|
||||
);
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<OAuthCallback />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
const [resources, setResources] = useState<Resource[]>([]);
|
||||
const [resourceTemplates, setResourceTemplates] = useState<
|
||||
ResourceTemplate[]
|
||||
@@ -122,22 +112,6 @@ const App = () => {
|
||||
const nextRequestId = useRef(0);
|
||||
const rootsRef = useRef<Root[]>([]);
|
||||
|
||||
const handleApproveSampling = (id: number, result: CreateMessageResult) => {
|
||||
setPendingSampleRequests((prev) => {
|
||||
const request = prev.find((r) => r.id === id);
|
||||
request?.resolve(result);
|
||||
return prev.filter((r) => r.id !== id);
|
||||
});
|
||||
};
|
||||
|
||||
const handleRejectSampling = (id: number) => {
|
||||
setPendingSampleRequests((prev) => {
|
||||
const request = prev.find((r) => r.id === id);
|
||||
request?.reject(new Error("Sampling request rejected"));
|
||||
return prev.filter((r) => r.id !== id);
|
||||
});
|
||||
};
|
||||
|
||||
const [selectedResource, setSelectedResource] = useState<Resource | null>(
|
||||
null,
|
||||
);
|
||||
@@ -237,7 +211,7 @@ const App = () => {
|
||||
// Connect to the server
|
||||
connectMcpServer();
|
||||
}
|
||||
}, []);
|
||||
}, [connectMcpServer]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch(`${PROXY_SERVER_URL}/config`)
|
||||
@@ -266,6 +240,22 @@ const App = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleApproveSampling = (id: number, result: CreateMessageResult) => {
|
||||
setPendingSampleRequests((prev) => {
|
||||
const request = prev.find((r) => r.id === id);
|
||||
request?.resolve(result);
|
||||
return prev.filter((r) => r.id !== id);
|
||||
});
|
||||
};
|
||||
|
||||
const handleRejectSampling = (id: number) => {
|
||||
setPendingSampleRequests((prev) => {
|
||||
const request = prev.find((r) => r.id === id);
|
||||
request?.reject(new Error("Sampling request rejected"));
|
||||
return prev.filter((r) => r.id !== id);
|
||||
});
|
||||
};
|
||||
|
||||
const clearError = (tabKey: keyof typeof errors) => {
|
||||
setErrors((prev) => ({ ...prev, [tabKey]: null }));
|
||||
};
|
||||
@@ -438,6 +428,17 @@ const App = () => {
|
||||
setLogLevel(level);
|
||||
};
|
||||
|
||||
if (window.location.pathname === "/oauth/callback") {
|
||||
const OAuthCallback = React.lazy(
|
||||
() => import("./components/OAuthCallback"),
|
||||
);
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<OAuthCallback />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-background">
|
||||
<Sidebar
|
||||
|
||||
1
client/src/__mocks__/styleMock.js
Normal file
1
client/src/__mocks__/styleMock.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {};
|
||||
@@ -108,6 +108,21 @@ const DynamicJsonForm = ({
|
||||
}
|
||||
};
|
||||
|
||||
const formatJson = () => {
|
||||
try {
|
||||
const jsonStr = rawJsonValue.trim();
|
||||
if (!jsonStr) {
|
||||
return;
|
||||
}
|
||||
const formatted = JSON.stringify(JSON.parse(jsonStr), null, 2);
|
||||
setRawJsonValue(formatted);
|
||||
debouncedUpdateParent(formatted);
|
||||
setJsonError(undefined);
|
||||
} catch (err) {
|
||||
setJsonError(err instanceof Error ? err.message : "Invalid JSON");
|
||||
}
|
||||
};
|
||||
|
||||
const renderFormFields = (
|
||||
propSchema: JsonSchemaType,
|
||||
currentValue: JsonValue,
|
||||
@@ -353,7 +368,12 @@ const DynamicJsonForm = ({
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-end">
|
||||
<div className="flex justify-end space-x-2">
|
||||
{isJsonMode && (
|
||||
<Button variant="outline" size="sm" onClick={formatJson}>
|
||||
Format JSON
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="outline" size="sm" onClick={handleSwitchToFormMode}>
|
||||
{isJsonMode ? "Switch to Form" : "Switch to JSON"}
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { Copy } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import JsonView from "./JsonView";
|
||||
|
||||
const HistoryAndNotifications = ({
|
||||
requestHistory,
|
||||
@@ -74,9 +75,9 @@ const HistoryAndNotifications = ({
|
||||
<Copy size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
|
||||
{JSON.stringify(JSON.parse(request.request), null, 2)}
|
||||
</pre>
|
||||
<div className="bg-background p-2 rounded">
|
||||
<JsonView data={request.request} />
|
||||
</div>
|
||||
</div>
|
||||
{request.response && (
|
||||
<div className="mt-2">
|
||||
@@ -91,13 +92,9 @@ const HistoryAndNotifications = ({
|
||||
<Copy size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
|
||||
{JSON.stringify(
|
||||
JSON.parse(request.response),
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
</pre>
|
||||
<div className="bg-background p-2 rounded">
|
||||
<JsonView data={request.response} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@@ -146,9 +143,11 @@ const HistoryAndNotifications = ({
|
||||
<Copy size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<pre className="whitespace-pre-wrap break-words bg-background p-2 rounded">
|
||||
{JSON.stringify(notification, null, 2)}
|
||||
</pre>
|
||||
<div className="bg-background p-2 rounded">
|
||||
<JsonView
|
||||
data={JSON.stringify(notification, null, 2)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
|
||||
@@ -3,7 +3,6 @@ import Editor from "react-simple-code-editor";
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/components/prism-json";
|
||||
import "prismjs/themes/prism.css";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface JsonEditorProps {
|
||||
value: string;
|
||||
@@ -16,49 +15,25 @@ const JsonEditor = ({
|
||||
onChange,
|
||||
error: externalError,
|
||||
}: JsonEditorProps) => {
|
||||
const [editorContent, setEditorContent] = useState(value);
|
||||
const [editorContent, setEditorContent] = useState(value || "");
|
||||
const [internalError, setInternalError] = useState<string | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setEditorContent(value);
|
||||
setEditorContent(value || "");
|
||||
}, [value]);
|
||||
|
||||
const formatJson = (json: string): string => {
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(json), null, 2);
|
||||
} catch {
|
||||
return json;
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditorChange = (newContent: string) => {
|
||||
setEditorContent(newContent);
|
||||
setInternalError(undefined);
|
||||
onChange(newContent);
|
||||
};
|
||||
|
||||
const handleFormatJson = () => {
|
||||
try {
|
||||
const formatted = formatJson(editorContent);
|
||||
setEditorContent(formatted);
|
||||
onChange(formatted);
|
||||
setInternalError(undefined);
|
||||
} catch (err) {
|
||||
setInternalError(err instanceof Error ? err.message : "Invalid JSON");
|
||||
}
|
||||
};
|
||||
|
||||
const displayError = internalError || externalError;
|
||||
|
||||
return (
|
||||
<div className="relative space-y-2">
|
||||
<div className="flex justify-end">
|
||||
<Button variant="outline" size="sm" onClick={handleFormatJson}>
|
||||
Format JSON
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div
|
||||
className={`border rounded-md ${
|
||||
displayError
|
||||
|
||||
228
client/src/components/JsonView.tsx
Normal file
228
client/src/components/JsonView.tsx
Normal file
@@ -0,0 +1,228 @@
|
||||
import { useState, memo } from "react";
|
||||
import { JsonValue } from "./DynamicJsonForm";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface JsonViewProps {
|
||||
data: unknown;
|
||||
name?: string;
|
||||
initialExpandDepth?: number;
|
||||
}
|
||||
|
||||
function tryParseJson(str: string): { success: boolean; data: JsonValue } {
|
||||
const trimmed = str.trim();
|
||||
if (
|
||||
!(trimmed.startsWith("{") && trimmed.endsWith("}")) &&
|
||||
!(trimmed.startsWith("[") && trimmed.endsWith("]"))
|
||||
) {
|
||||
return { success: false, data: str };
|
||||
}
|
||||
try {
|
||||
return { success: true, data: JSON.parse(str) };
|
||||
} catch {
|
||||
return { success: false, data: str };
|
||||
}
|
||||
}
|
||||
|
||||
const JsonView = memo(
|
||||
({ data, name, initialExpandDepth = 3 }: JsonViewProps) => {
|
||||
const normalizedData =
|
||||
typeof data === "string"
|
||||
? tryParseJson(data).success
|
||||
? tryParseJson(data).data
|
||||
: data
|
||||
: data;
|
||||
|
||||
return (
|
||||
<div className="font-mono text-sm transition-all duration-300 ">
|
||||
<JsonNode
|
||||
data={normalizedData as JsonValue}
|
||||
name={name}
|
||||
depth={0}
|
||||
initialExpandDepth={initialExpandDepth}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
JsonView.displayName = "JsonView";
|
||||
|
||||
interface JsonNodeProps {
|
||||
data: JsonValue;
|
||||
name?: string;
|
||||
depth: number;
|
||||
initialExpandDepth: number;
|
||||
}
|
||||
|
||||
const JsonNode = memo(
|
||||
({ data, name, depth = 0, initialExpandDepth }: JsonNodeProps) => {
|
||||
const [isExpanded, setIsExpanded] = useState(depth < initialExpandDepth);
|
||||
|
||||
const getDataType = (value: JsonValue): string => {
|
||||
if (Array.isArray(value)) return "array";
|
||||
if (value === null) return "null";
|
||||
return typeof value;
|
||||
};
|
||||
|
||||
const dataType = getDataType(data);
|
||||
|
||||
const typeStyleMap: Record<string, string> = {
|
||||
number: "text-blue-600",
|
||||
boolean: "text-amber-600",
|
||||
null: "text-purple-600",
|
||||
undefined: "text-gray-600",
|
||||
string: "text-green-600 break-all whitespace-pre-wrap",
|
||||
default: "text-gray-700",
|
||||
};
|
||||
|
||||
const renderCollapsible = (isArray: boolean) => {
|
||||
const items = isArray
|
||||
? (data as JsonValue[])
|
||||
: Object.entries(data as Record<string, JsonValue>);
|
||||
const itemCount = items.length;
|
||||
const isEmpty = itemCount === 0;
|
||||
|
||||
const symbolMap = {
|
||||
open: isArray ? "[" : "{",
|
||||
close: isArray ? "]" : "}",
|
||||
collapsed: isArray ? "[ ... ]" : "{ ... }",
|
||||
empty: isArray ? "[]" : "{}",
|
||||
};
|
||||
|
||||
if (isEmpty) {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
{name && (
|
||||
<span className="mr-1 text-gray-600 dark:text-gray-400">
|
||||
{name}:
|
||||
</span>
|
||||
)}
|
||||
<span className="text-gray-500">{symbolMap.empty}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div
|
||||
className="flex items-center mr-1 rounded cursor-pointer group hover:bg-gray-800/10 dark:hover:bg-gray-800/20"
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
>
|
||||
{name && (
|
||||
<span className="mr-1 text-gray-600 dark:text-gray-400 dark:group-hover:text-gray-100 group-hover:text-gray-400">
|
||||
{name}:
|
||||
</span>
|
||||
)}
|
||||
{isExpanded ? (
|
||||
<span className="text-gray-600 dark:text-gray-400 dark:group-hover:text-gray-100 group-hover:text-gray-400">
|
||||
{symbolMap.open}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-gray-600 dark:group-hover:text-gray-100 group-hover:text-gray-400">
|
||||
{symbolMap.collapsed}
|
||||
</span>
|
||||
<span className="ml-1 text-gray-700 dark:group-hover:text-gray-100 group-hover:text-gray-400">
|
||||
{itemCount} {itemCount === 1 ? "item" : "items"}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{isExpanded && (
|
||||
<>
|
||||
<div className="pl-2 ml-4 border-l border-gray-200 dark:border-gray-800">
|
||||
{isArray
|
||||
? (items as JsonValue[]).map((item, index) => (
|
||||
<div key={index} className="my-1">
|
||||
<JsonNode
|
||||
data={item}
|
||||
name={`${index}`}
|
||||
depth={depth + 1}
|
||||
initialExpandDepth={initialExpandDepth}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
: (items as [string, JsonValue][]).map(([key, value]) => (
|
||||
<div key={key} className="my-1">
|
||||
<JsonNode
|
||||
data={value}
|
||||
name={key}
|
||||
depth={depth + 1}
|
||||
initialExpandDepth={initialExpandDepth}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="text-gray-600 dark:text-gray-400">
|
||||
{symbolMap.close}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderString = (value: string) => {
|
||||
const maxLength = 100;
|
||||
const isTooLong = value.length > maxLength;
|
||||
|
||||
if (!isTooLong) {
|
||||
return (
|
||||
<div className="flex mr-1 rounded hover:bg-gray-800/20">
|
||||
{name && (
|
||||
<span className="mr-1 text-gray-600 dark:text-gray-400">
|
||||
{name}:
|
||||
</span>
|
||||
)}
|
||||
<pre className={typeStyleMap.string}>"{value}"</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex mr-1 rounded group hover:bg-gray-800/20">
|
||||
{name && (
|
||||
<span className="mr-1 text-gray-600 dark:text-gray-400 dark:group-hover:text-gray-100 group-hover:text-gray-400">
|
||||
{name}:
|
||||
</span>
|
||||
)}
|
||||
<pre
|
||||
className={clsx(
|
||||
typeStyleMap.string,
|
||||
"cursor-pointer group-hover:text-green-500",
|
||||
)}
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
title={isExpanded ? "Click to collapse" : "Click to expand"}
|
||||
>
|
||||
{isExpanded ? `"${value}"` : `"${value.slice(0, maxLength)}..."`}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
switch (dataType) {
|
||||
case "object":
|
||||
case "array":
|
||||
return renderCollapsible(dataType === "array");
|
||||
case "string":
|
||||
return renderString(data as string);
|
||||
default:
|
||||
return (
|
||||
<div className="flex items-center mr-1 rounded hover:bg-gray-800/20">
|
||||
{name && (
|
||||
<span className="mr-1 text-gray-600 dark:text-gray-400">
|
||||
{name}:
|
||||
</span>
|
||||
)}
|
||||
<span className={typeStyleMap[dataType] || typeStyleMap.default}>
|
||||
{data === null ? "null" : String(data)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
JsonNode.displayName = "JsonNode";
|
||||
|
||||
export default JsonView;
|
||||
@@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { TabsContent } from "@/components/ui/tabs";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
|
||||
import {
|
||||
ListPromptsResult,
|
||||
PromptReference,
|
||||
@@ -13,6 +13,7 @@ import { AlertCircle } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import ListPane from "./ListPane";
|
||||
import { useCompletionState } from "@/lib/hooks/useCompletionState";
|
||||
import JsonView from "./JsonView";
|
||||
|
||||
export type Prompt = {
|
||||
name: string;
|
||||
@@ -151,11 +152,9 @@ const PromptsTab = ({
|
||||
Get Prompt
|
||||
</Button>
|
||||
{promptContent && (
|
||||
<Textarea
|
||||
value={promptContent}
|
||||
readOnly
|
||||
className="h-64 font-mono"
|
||||
/>
|
||||
<div className="p-4 border rounded">
|
||||
<JsonView data={promptContent} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -15,6 +15,7 @@ import { AlertCircle, ChevronRight, FileText, RefreshCw } from "lucide-react";
|
||||
import ListPane from "./ListPane";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCompletionState } from "@/lib/hooks/useCompletionState";
|
||||
import JsonView from "./JsonView";
|
||||
|
||||
const ResourcesTab = ({
|
||||
resources,
|
||||
@@ -214,9 +215,9 @@ const ResourcesTab = ({
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
) : selectedResource ? (
|
||||
<pre className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 whitespace-pre-wrap break-words text-gray-900 dark:text-gray-100">
|
||||
{resourceContent}
|
||||
</pre>
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-4 rounded text-sm overflow-auto max-h-96 text-gray-900 dark:text-gray-100">
|
||||
<JsonView data={resourceContent} />
|
||||
</div>
|
||||
) : selectedTemplate ? (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-600">
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
CreateMessageRequest,
|
||||
CreateMessageResult,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import JsonView from "./JsonView";
|
||||
|
||||
export type PendingRequest = {
|
||||
id: number;
|
||||
@@ -43,9 +44,9 @@ const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => {
|
||||
<h3 className="text-lg font-semibold">Recent Requests</h3>
|
||||
{pendingRequests.map((request) => (
|
||||
<div key={request.id} className="p-4 border rounded-lg space-y-4">
|
||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-2 rounded">
|
||||
{JSON.stringify(request.request, null, 2)}
|
||||
</pre>
|
||||
<div className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-2 rounded">
|
||||
<JsonView data={JSON.stringify(request.request)} />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button onClick={() => handleApprove(request.id)}>Approve</Button>
|
||||
<Button variant="outline" onClick={() => onReject(request.id)}>
|
||||
|
||||
@@ -457,36 +457,37 @@ const Sidebar = ({
|
||||
</Select>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button variant="ghost" title="Inspector Documentation" asChild>
|
||||
<a
|
||||
href="https://modelcontextprotocol.io/docs/tools/inspector"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Button variant="ghost" title="Inspector Documentation">
|
||||
<CircleHelp className="w-4 h-4 text-gray-800" />
|
||||
</Button>
|
||||
<CircleHelp className="w-4 h-4 text-foreground" />
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="ghost" title="Debugging Guide" asChild>
|
||||
<a
|
||||
href="https://modelcontextprotocol.io/docs/tools/debugging"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Button variant="ghost" title="Debugging Guide">
|
||||
<Bug className="w-4 h-4 text-gray-800" />
|
||||
</Button>
|
||||
<Bug className="w-4 h-4 text-foreground" />
|
||||
</a>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
title="Report bugs or contribute on GitHub"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href="https://github.com/modelcontextprotocol/inspector"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
title="Report bugs or contribute on GitHub"
|
||||
>
|
||||
<Github className="w-4 h-4 text-gray-800" />
|
||||
</Button>
|
||||
<Github className="w-4 h-4 text-foreground" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { AlertCircle, Send } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import ListPane from "./ListPane";
|
||||
import { escapeUnicode } from "@/utils/escapeUnicode";
|
||||
import JsonView from "./JsonView";
|
||||
|
||||
const ToolsTab = ({
|
||||
tools,
|
||||
@@ -53,17 +53,14 @@ const ToolsTab = ({
|
||||
return (
|
||||
<>
|
||||
<h4 className="font-semibold mb-2">Invalid Tool Result:</h4>
|
||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64">
|
||||
{escapeUnicode(toolResult)}
|
||||
</pre>
|
||||
<div className="p-4 border rounded">
|
||||
<JsonView data={toolResult} />
|
||||
</div>
|
||||
<h4 className="font-semibold mb-2">Errors:</h4>
|
||||
{parsedResult.error.errors.map((error, idx) => (
|
||||
<pre
|
||||
key={idx}
|
||||
className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64"
|
||||
>
|
||||
{escapeUnicode(error)}
|
||||
</pre>
|
||||
<div key={idx} className="p-4 border rounded">
|
||||
<JsonView data={error} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
@@ -79,9 +76,9 @@ const ToolsTab = ({
|
||||
{structuredResult.content.map((item, index) => (
|
||||
<div key={index} className="mb-2">
|
||||
{item.type === "text" && (
|
||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64">
|
||||
{item.text}
|
||||
</pre>
|
||||
<div className="p-4 border rounded">
|
||||
<JsonView data={item.text} />
|
||||
</div>
|
||||
)}
|
||||
{item.type === "image" && (
|
||||
<img
|
||||
@@ -100,9 +97,9 @@ const ToolsTab = ({
|
||||
<p>Your browser does not support audio playback</p>
|
||||
</audio>
|
||||
) : (
|
||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 whitespace-pre-wrap break-words p-4 rounded text-sm overflow-auto max-h-64">
|
||||
{escapeUnicode(item.resource)}
|
||||
</pre>
|
||||
<div className="p-4 border rounded">
|
||||
<JsonView data={item.resource} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
@@ -112,9 +109,9 @@ const ToolsTab = ({
|
||||
return (
|
||||
<>
|
||||
<h4 className="font-semibold mb-2">Tool Result (Legacy):</h4>
|
||||
<pre className="bg-gray-50 dark:bg-gray-800 dark:text-gray-100 p-4 rounded text-sm overflow-auto max-h-64">
|
||||
{escapeUnicode(toolResult.toolResult)}
|
||||
</pre>
|
||||
<div className="p-4 border rounded">
|
||||
<JsonView data={toolResult.toolResult} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
95
client/src/components/__tests__/DynamicJsonForm.test.tsx
Normal file
95
client/src/components/__tests__/DynamicJsonForm.test.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { describe, it, expect, jest } from "@jest/globals";
|
||||
import DynamicJsonForm from "../DynamicJsonForm";
|
||||
import type { JsonSchemaType } from "../DynamicJsonForm";
|
||||
|
||||
describe("DynamicJsonForm String Fields", () => {
|
||||
const renderForm = (props = {}) => {
|
||||
const defaultProps = {
|
||||
schema: {
|
||||
type: "string" as const,
|
||||
description: "Test string field",
|
||||
} satisfies JsonSchemaType,
|
||||
value: undefined,
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
return render(<DynamicJsonForm {...defaultProps} {...props} />);
|
||||
};
|
||||
|
||||
describe("Type Validation", () => {
|
||||
it("should handle numeric input as string type", () => {
|
||||
const onChange = jest.fn();
|
||||
renderForm({ onChange });
|
||||
|
||||
const input = screen.getByRole("textbox");
|
||||
fireEvent.change(input, { target: { value: "123321" } });
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith("123321");
|
||||
// Verify the value is a string, not a number
|
||||
expect(typeof onChange.mock.calls[0][0]).toBe("string");
|
||||
});
|
||||
|
||||
it("should render as text input, not number input", () => {
|
||||
renderForm();
|
||||
const input = screen.getByRole("textbox");
|
||||
expect(input).toHaveProperty("type", "text");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("DynamicJsonForm Integer Fields", () => {
|
||||
const renderForm = (props = {}) => {
|
||||
const defaultProps = {
|
||||
schema: {
|
||||
type: "integer" as const,
|
||||
description: "Test integer field",
|
||||
} satisfies JsonSchemaType,
|
||||
value: undefined,
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
return render(<DynamicJsonForm {...defaultProps} {...props} />);
|
||||
};
|
||||
|
||||
describe("Basic Operations", () => {
|
||||
it("should render number input with step=1", () => {
|
||||
renderForm();
|
||||
const input = screen.getByRole("spinbutton");
|
||||
expect(input).toHaveProperty("type", "number");
|
||||
expect(input).toHaveProperty("step", "1");
|
||||
});
|
||||
|
||||
it("should pass integer values to onChange", () => {
|
||||
const onChange = jest.fn();
|
||||
renderForm({ onChange });
|
||||
|
||||
const input = screen.getByRole("spinbutton");
|
||||
fireEvent.change(input, { target: { value: "42" } });
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(42);
|
||||
// Verify the value is a number, not a string
|
||||
expect(typeof onChange.mock.calls[0][0]).toBe("number");
|
||||
});
|
||||
|
||||
it("should not pass string values to onChange", () => {
|
||||
const onChange = jest.fn();
|
||||
renderForm({ onChange });
|
||||
|
||||
const input = screen.getByRole("spinbutton");
|
||||
fireEvent.change(input, { target: { value: "abc" } });
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
it("should handle non-numeric input by not calling onChange", () => {
|
||||
const onChange = jest.fn();
|
||||
renderForm({ onChange });
|
||||
|
||||
const input = screen.getByRole("spinbutton");
|
||||
fireEvent.change(input, { target: { value: "abc" } });
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
307
client/src/components/__tests__/Sidebar.test.tsx
Normal file
307
client/src/components/__tests__/Sidebar.test.tsx
Normal file
@@ -0,0 +1,307 @@
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { describe, it, beforeEach, jest } from "@jest/globals";
|
||||
import Sidebar from "../Sidebar";
|
||||
|
||||
// Mock theme hook
|
||||
jest.mock("../../lib/useTheme", () => ({
|
||||
__esModule: true,
|
||||
default: () => ["light", jest.fn()],
|
||||
}));
|
||||
|
||||
describe("Sidebar Environment Variables", () => {
|
||||
const defaultProps = {
|
||||
connectionStatus: "disconnected" as const,
|
||||
transportType: "stdio" as const,
|
||||
setTransportType: jest.fn(),
|
||||
command: "",
|
||||
setCommand: jest.fn(),
|
||||
args: "",
|
||||
setArgs: jest.fn(),
|
||||
sseUrl: "",
|
||||
setSseUrl: jest.fn(),
|
||||
env: {},
|
||||
setEnv: jest.fn(),
|
||||
bearerToken: "",
|
||||
setBearerToken: jest.fn(),
|
||||
onConnect: jest.fn(),
|
||||
stdErrNotifications: [],
|
||||
logLevel: "info" as const,
|
||||
sendLogLevelRequest: jest.fn(),
|
||||
loggingSupported: true,
|
||||
};
|
||||
|
||||
const renderSidebar = (props = {}) => {
|
||||
return render(<Sidebar {...defaultProps} {...props} />);
|
||||
};
|
||||
|
||||
const openEnvVarsSection = () => {
|
||||
const button = screen.getByText("Environment Variables");
|
||||
fireEvent.click(button);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("Basic Operations", () => {
|
||||
it("should add a new environment variable", () => {
|
||||
const setEnv = jest.fn();
|
||||
renderSidebar({ env: {}, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const addButton = screen.getByText("Add Environment Variable");
|
||||
fireEvent.click(addButton);
|
||||
|
||||
expect(setEnv).toHaveBeenCalledWith({ "": "" });
|
||||
});
|
||||
|
||||
it("should remove an environment variable", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = { TEST_KEY: "test_value" };
|
||||
renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const removeButton = screen.getByRole("button", { name: "×" });
|
||||
fireEvent.click(removeButton);
|
||||
|
||||
expect(setEnv).toHaveBeenCalledWith({});
|
||||
});
|
||||
|
||||
it("should update environment variable value", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = { TEST_KEY: "test_value" };
|
||||
renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const valueInput = screen.getByDisplayValue("test_value");
|
||||
fireEvent.change(valueInput, { target: { value: "new_value" } });
|
||||
|
||||
expect(setEnv).toHaveBeenCalledWith({ TEST_KEY: "new_value" });
|
||||
});
|
||||
|
||||
it("should toggle value visibility", () => {
|
||||
const initialEnv = { TEST_KEY: "test_value" };
|
||||
renderSidebar({ env: initialEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const valueInput = screen.getByDisplayValue("test_value");
|
||||
expect(valueInput).toHaveProperty("type", "password");
|
||||
|
||||
const toggleButton = screen.getByRole("button", { name: /show value/i });
|
||||
fireEvent.click(toggleButton);
|
||||
|
||||
expect(valueInput).toHaveProperty("type", "text");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Key Editing", () => {
|
||||
it("should maintain order when editing first key", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = {
|
||||
FIRST_KEY: "first_value",
|
||||
SECOND_KEY: "second_value",
|
||||
THIRD_KEY: "third_value",
|
||||
};
|
||||
renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const firstKeyInput = screen.getByDisplayValue("FIRST_KEY");
|
||||
fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } });
|
||||
|
||||
expect(setEnv).toHaveBeenCalledWith({
|
||||
NEW_FIRST_KEY: "first_value",
|
||||
SECOND_KEY: "second_value",
|
||||
THIRD_KEY: "third_value",
|
||||
});
|
||||
});
|
||||
|
||||
it("should maintain order when editing middle key", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = {
|
||||
FIRST_KEY: "first_value",
|
||||
SECOND_KEY: "second_value",
|
||||
THIRD_KEY: "third_value",
|
||||
};
|
||||
renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const middleKeyInput = screen.getByDisplayValue("SECOND_KEY");
|
||||
fireEvent.change(middleKeyInput, { target: { value: "NEW_SECOND_KEY" } });
|
||||
|
||||
expect(setEnv).toHaveBeenCalledWith({
|
||||
FIRST_KEY: "first_value",
|
||||
NEW_SECOND_KEY: "second_value",
|
||||
THIRD_KEY: "third_value",
|
||||
});
|
||||
});
|
||||
|
||||
it("should maintain order when editing last key", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = {
|
||||
FIRST_KEY: "first_value",
|
||||
SECOND_KEY: "second_value",
|
||||
THIRD_KEY: "third_value",
|
||||
};
|
||||
renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const lastKeyInput = screen.getByDisplayValue("THIRD_KEY");
|
||||
fireEvent.change(lastKeyInput, { target: { value: "NEW_THIRD_KEY" } });
|
||||
|
||||
expect(setEnv).toHaveBeenCalledWith({
|
||||
FIRST_KEY: "first_value",
|
||||
SECOND_KEY: "second_value",
|
||||
NEW_THIRD_KEY: "third_value",
|
||||
});
|
||||
});
|
||||
|
||||
it("should maintain order during key editing", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = {
|
||||
KEY1: "value1",
|
||||
KEY2: "value2",
|
||||
};
|
||||
renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
// Type "NEW_" one character at a time
|
||||
const key1Input = screen.getByDisplayValue("KEY1");
|
||||
"NEW_".split("").forEach((char) => {
|
||||
fireEvent.change(key1Input, {
|
||||
target: { value: char + "KEY1".slice(1) },
|
||||
});
|
||||
});
|
||||
|
||||
// Verify the last setEnv call maintains the order
|
||||
const lastCall = setEnv.mock.calls[
|
||||
setEnv.mock.calls.length - 1
|
||||
][0] as Record<string, string>;
|
||||
const entries = Object.entries(lastCall);
|
||||
|
||||
// The values should stay with their original keys
|
||||
expect(entries[0][1]).toBe("value1"); // First entry should still have value1
|
||||
expect(entries[1][1]).toBe("value2"); // Second entry should still have value2
|
||||
});
|
||||
});
|
||||
|
||||
describe("Multiple Operations", () => {
|
||||
it("should maintain state after multiple key edits", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = {
|
||||
FIRST_KEY: "first_value",
|
||||
SECOND_KEY: "second_value",
|
||||
};
|
||||
const { rerender } = renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
// First key edit
|
||||
const firstKeyInput = screen.getByDisplayValue("FIRST_KEY");
|
||||
fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } });
|
||||
|
||||
// Get the updated env from the first setEnv call
|
||||
const updatedEnv = setEnv.mock.calls[0][0] as Record<string, string>;
|
||||
|
||||
// Rerender with the updated env
|
||||
rerender(<Sidebar {...defaultProps} env={updatedEnv} setEnv={setEnv} />);
|
||||
|
||||
// Second key edit
|
||||
const secondKeyInput = screen.getByDisplayValue("SECOND_KEY");
|
||||
fireEvent.change(secondKeyInput, { target: { value: "NEW_SECOND_KEY" } });
|
||||
|
||||
// Verify the final state matches what we expect
|
||||
expect(setEnv).toHaveBeenLastCalledWith({
|
||||
NEW_FIRST_KEY: "first_value",
|
||||
NEW_SECOND_KEY: "second_value",
|
||||
});
|
||||
});
|
||||
|
||||
it("should maintain visibility state after key edit", () => {
|
||||
const initialEnv = { TEST_KEY: "test_value" };
|
||||
const { rerender } = renderSidebar({ env: initialEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
// Show the value
|
||||
const toggleButton = screen.getByRole("button", { name: /show value/i });
|
||||
fireEvent.click(toggleButton);
|
||||
|
||||
const valueInput = screen.getByDisplayValue("test_value");
|
||||
expect(valueInput).toHaveProperty("type", "text");
|
||||
|
||||
// Edit the key
|
||||
const keyInput = screen.getByDisplayValue("TEST_KEY");
|
||||
fireEvent.change(keyInput, { target: { value: "NEW_KEY" } });
|
||||
|
||||
// Rerender with updated env
|
||||
rerender(<Sidebar {...defaultProps} env={{ NEW_KEY: "test_value" }} />);
|
||||
|
||||
// Value should still be visible
|
||||
const updatedValueInput = screen.getByDisplayValue("test_value");
|
||||
expect(updatedValueInput).toHaveProperty("type", "text");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
it("should handle empty key", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = { TEST_KEY: "test_value" };
|
||||
renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const keyInput = screen.getByDisplayValue("TEST_KEY");
|
||||
fireEvent.change(keyInput, { target: { value: "" } });
|
||||
|
||||
expect(setEnv).toHaveBeenCalledWith({ "": "test_value" });
|
||||
});
|
||||
|
||||
it("should handle special characters in key", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = { TEST_KEY: "test_value" };
|
||||
renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const keyInput = screen.getByDisplayValue("TEST_KEY");
|
||||
fireEvent.change(keyInput, { target: { value: "TEST-KEY@123" } });
|
||||
|
||||
expect(setEnv).toHaveBeenCalledWith({ "TEST-KEY@123": "test_value" });
|
||||
});
|
||||
|
||||
it("should handle unicode characters", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = { TEST_KEY: "test_value" };
|
||||
renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const keyInput = screen.getByDisplayValue("TEST_KEY");
|
||||
fireEvent.change(keyInput, { target: { value: "TEST_🔑" } });
|
||||
|
||||
expect(setEnv).toHaveBeenCalledWith({ "TEST_🔑": "test_value" });
|
||||
});
|
||||
|
||||
it("should handle very long key names", () => {
|
||||
const setEnv = jest.fn();
|
||||
const initialEnv = { TEST_KEY: "test_value" };
|
||||
renderSidebar({ env: initialEnv, setEnv });
|
||||
|
||||
openEnvVarsSection();
|
||||
|
||||
const keyInput = screen.getByDisplayValue("TEST_KEY");
|
||||
const longKey = "A".repeat(100);
|
||||
fireEvent.change(keyInput, { target: { value: longKey } });
|
||||
|
||||
expect(setEnv).toHaveBeenCalledWith({ [longKey]: "test_value" });
|
||||
});
|
||||
});
|
||||
});
|
||||
72
client/src/components/__tests__/ToolsTab.test.tsx
Normal file
72
client/src/components/__tests__/ToolsTab.test.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { describe, it, expect, jest } from "@jest/globals";
|
||||
import ToolsTab from "../ToolsTab";
|
||||
import { Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { Tabs } from "@/components/ui/tabs";
|
||||
|
||||
describe("ToolsTab", () => {
|
||||
const mockTools: Tool[] = [
|
||||
{
|
||||
name: "tool1",
|
||||
description: "First tool",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
num: { type: "number" as const },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "tool2",
|
||||
description: "Second tool",
|
||||
inputSchema: {
|
||||
type: "object" as const,
|
||||
properties: {
|
||||
num: { type: "number" as const },
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const defaultProps = {
|
||||
tools: mockTools,
|
||||
listTools: jest.fn(),
|
||||
clearTools: jest.fn(),
|
||||
callTool: jest.fn(),
|
||||
selectedTool: null,
|
||||
setSelectedTool: jest.fn(),
|
||||
toolResult: null,
|
||||
nextCursor: "",
|
||||
error: null,
|
||||
};
|
||||
|
||||
const renderToolsTab = (props = {}) => {
|
||||
return render(
|
||||
<Tabs defaultValue="tools">
|
||||
<ToolsTab {...defaultProps} {...props} />
|
||||
</Tabs>,
|
||||
);
|
||||
};
|
||||
|
||||
it("should reset input values when switching tools", () => {
|
||||
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" } });
|
||||
expect(input.value).toBe("42");
|
||||
|
||||
// Switch to second tool
|
||||
rerender(
|
||||
<Tabs defaultValue="tools">
|
||||
<ToolsTab {...defaultProps} selectedTool={mockTools[1]} />
|
||||
</Tabs>,
|
||||
);
|
||||
|
||||
// Verify input is reset
|
||||
const newInput = screen.getByRole("spinbutton") as HTMLInputElement;
|
||||
expect(newInput.value).toBe("");
|
||||
});
|
||||
});
|
||||
@@ -19,6 +19,10 @@ import {
|
||||
McpError,
|
||||
CompleteResultSchema,
|
||||
ErrorCode,
|
||||
CancelledNotificationSchema,
|
||||
ResourceListChangedNotificationSchema,
|
||||
ToolListChangedNotificationSchema,
|
||||
PromptListChangedNotificationSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
@@ -248,20 +252,24 @@ export function useConnection({
|
||||
});
|
||||
|
||||
if (onNotification) {
|
||||
client.setNotificationHandler(
|
||||
[
|
||||
CancelledNotificationSchema,
|
||||
ProgressNotificationSchema,
|
||||
onNotification,
|
||||
);
|
||||
|
||||
client.setNotificationHandler(
|
||||
ResourceUpdatedNotificationSchema,
|
||||
onNotification,
|
||||
);
|
||||
|
||||
client.setNotificationHandler(
|
||||
LoggingMessageNotificationSchema,
|
||||
onNotification,
|
||||
);
|
||||
ResourceUpdatedNotificationSchema,
|
||||
ResourceListChangedNotificationSchema,
|
||||
ToolListChangedNotificationSchema,
|
||||
PromptListChangedNotificationSchema,
|
||||
].forEach((notificationSchema) => {
|
||||
client.setNotificationHandler(notificationSchema, onNotification);
|
||||
});
|
||||
|
||||
client.fallbackNotificationHandler = (
|
||||
notification: Notification,
|
||||
): Promise<void> => {
|
||||
onNotification(notification);
|
||||
return Promise.resolve();
|
||||
};
|
||||
}
|
||||
|
||||
if (onStdErrNotification) {
|
||||
|
||||
@@ -14,7 +14,9 @@ export const StdErrNotificationSchema = BaseNotificationSchema.extend({
|
||||
|
||||
export const NotificationSchema = ClientNotificationSchema.or(
|
||||
StdErrNotificationSchema,
|
||||
).or(ServerNotificationSchema);
|
||||
)
|
||||
.or(ServerNotificationSchema)
|
||||
.or(BaseNotificationSchema);
|
||||
|
||||
export type StdErrNotification = z.infer<typeof StdErrNotificationSchema>;
|
||||
export type Notification = z.infer<typeof NotificationSchema>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
type Theme = "light" | "dark" | "system";
|
||||
|
||||
@@ -36,16 +36,14 @@ const useTheme = (): [Theme, (mode: Theme) => void] => {
|
||||
};
|
||||
}, [theme]);
|
||||
|
||||
return [
|
||||
theme,
|
||||
useCallback((newTheme: Theme) => {
|
||||
const setThemeWithSideEffect = useCallback((newTheme: Theme) => {
|
||||
setTheme(newTheme);
|
||||
localStorage.setItem("theme", newTheme);
|
||||
if (newTheme !== "system") {
|
||||
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
||||
}
|
||||
}, []),
|
||||
];
|
||||
}, []);
|
||||
return useMemo(() => [theme, setThemeWithSideEffect], [theme]);
|
||||
};
|
||||
|
||||
export default useTheme;
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"types": ["jest", "@testing-library/jest-dom", "node"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
292
package-lock.json
generated
292
package-lock.json
generated
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"name": "@modelcontextprotocol/inspector",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@modelcontextprotocol/inspector",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"client",
|
||||
"server"
|
||||
],
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/inspector-client": "^0.6.0",
|
||||
"@modelcontextprotocol/inspector-server": "^0.6.0",
|
||||
"@modelcontextprotocol/inspector-client": "^0.7.0",
|
||||
"@modelcontextprotocol/inspector-server": "^0.7.0",
|
||||
"concurrently": "^9.0.1",
|
||||
"shell-quote": "^1.8.2",
|
||||
"spawn-rx": "^5.1.2",
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"client": {
|
||||
"name": "@modelcontextprotocol/inspector-client",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.6.1",
|
||||
@@ -50,7 +50,7 @@
|
||||
"cmdk": "^1.0.4",
|
||||
"lucide-react": "^0.447.0",
|
||||
"pkce-challenge": "^4.1.0",
|
||||
"prismjs": "^1.29.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
@@ -65,6 +65,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.7.5",
|
||||
"@types/react": "^18.3.10",
|
||||
@@ -87,6 +89,13 @@
|
||||
"vite": "^5.4.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz",
|
||||
"integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||
@@ -576,6 +585,19 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz",
|
||||
"integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
|
||||
@@ -3786,6 +3808,148 @@
|
||||
"@sinonjs/commons": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
|
||||
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/aria-query": "^5.0.1",
|
||||
"aria-query": "5.3.0",
|
||||
"chalk": "^4.1.0",
|
||||
"dom-accessibility-api": "^0.5.9",
|
||||
"lz-string": "^1.5.0",
|
||||
"pretty-format": "^27.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/pretty-format": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom/node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom": {
|
||||
"version": "6.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz",
|
||||
"integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": "^4.4.0",
|
||||
"aria-query": "^5.0.0",
|
||||
"chalk": "^3.0.0",
|
||||
"css.escape": "^1.5.1",
|
||||
"dom-accessibility-api": "^0.6.3",
|
||||
"lodash": "^4.17.21",
|
||||
"redent": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14",
|
||||
"npm": ">=6",
|
||||
"yarn": ">=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom/node_modules/chalk": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
|
||||
"integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@testing-library/jest-dom/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/react": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.2.0.tgz",
|
||||
"integrity": "sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@testing-library/dom": "^10.0.0",
|
||||
"@types/react": "^18.0.0 || ^19.0.0",
|
||||
"@types/react-dom": "^18.0.0 || ^19.0.0",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
@@ -3820,6 +3984,14 @@
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/aria-query": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
|
||||
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
@@ -4585,6 +4757,16 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/aria-query": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
|
||||
"integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"dequal": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
@@ -5331,6 +5513,13 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css.escape": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||
"integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@@ -5467,6 +5656,16 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/dequal": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
|
||||
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
@@ -5524,6 +5723,14 @@
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dom-accessibility-api": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/domexception": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
|
||||
@@ -6796,6 +7003,16 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/indent-string": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
||||
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
@@ -7984,6 +8201,17 @@
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/lz-string": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
|
||||
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"lz-string": "bin/bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
|
||||
@@ -8137,6 +8365,16 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/min-indent": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@@ -8842,9 +9080,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
"version": "1.29.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
|
||||
"integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
||||
"integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -9151,6 +9389,27 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
|
||||
"integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"indent-string": "^4.0.0",
|
||||
"strip-indent": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -9837,6 +10096,19 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-indent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"min-indent": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||
@@ -11299,7 +11571,7 @@
|
||||
},
|
||||
"server": {
|
||||
"name": "@modelcontextprotocol/inspector-server",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.6.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@modelcontextprotocol/inspector",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.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.6.0",
|
||||
"@modelcontextprotocol/inspector-server": "^0.6.0",
|
||||
"@modelcontextprotocol/inspector-client": "^0.7.0",
|
||||
"@modelcontextprotocol/inspector-server": "^0.7.0",
|
||||
"concurrently": "^9.0.1",
|
||||
"shell-quote": "^1.8.2",
|
||||
"spawn-rx": "^5.1.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@modelcontextprotocol/inspector-server",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"description": "Server-side application for the Model Context Protocol inspector",
|
||||
"license": "MIT",
|
||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||
|
||||
Reference in New Issue
Block a user