Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3f424f21e | ||
|
|
6b6eeb8dcd | ||
|
|
3110cf9343 | ||
|
|
2c04fa31e8 | ||
|
|
e700bc713a | ||
|
|
bea86af65b | ||
|
|
68a6130b17 | ||
|
|
853a3b4faf | ||
|
|
6f62066d34 | ||
|
|
c770d217e7 | ||
|
|
98470a12f9 | ||
|
|
a00564fafa | ||
|
|
62546dec58 | ||
|
|
886ac5fc7b | ||
|
|
722df4d798 | ||
|
|
407e304585 | ||
|
|
60578314aa | ||
|
|
fbac5b78bc | ||
|
|
f876b1ec0d | ||
|
|
aecfa21d47 | ||
|
|
a3d542c0a3 |
10
bin/cli.js
10
bin/cli.js
@@ -51,18 +51,24 @@ async function main() {
|
|||||||
...(command ? [`--env`, command] : []),
|
...(command ? [`--env`, command] : []),
|
||||||
...(mcpServerArgs ? ["--args", mcpServerArgs.join(" ")] : []),
|
...(mcpServerArgs ? ["--args", mcpServerArgs.join(" ")] : []),
|
||||||
],
|
],
|
||||||
{ env: { ...process.env, PORT: SERVER_PORT }, signal: abort.signal },
|
{
|
||||||
|
env: { ...process.env, PORT: SERVER_PORT },
|
||||||
|
signal: abort.signal,
|
||||||
|
echoOutput: true,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const client = spawnPromise("node", [inspectorClientPath], {
|
const client = spawnPromise("node", [inspectorClientPath], {
|
||||||
env: { ...process.env, PORT: CLIENT_PORT },
|
env: { ...process.env, PORT: CLIENT_PORT },
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
|
echoOutput: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Make sure our server/client didn't immediately fail
|
// Make sure our server/client didn't immediately fail
|
||||||
await Promise.any([server, client, delay(2 * 1000)]);
|
await Promise.any([server, client, delay(2 * 1000)]);
|
||||||
|
const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`;
|
||||||
console.log(
|
console.log(
|
||||||
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT} 🚀`,
|
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} 🚀`,
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-client",
|
"name": "@modelcontextprotocol/inspector-client",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"description": "Client-side application for the Model Context Protocol inspector",
|
"description": "Client-side application for the Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ import ToolsTab from "./components/ToolsTab";
|
|||||||
|
|
||||||
const DEFAULT_REQUEST_TIMEOUT_MSEC = 10000;
|
const DEFAULT_REQUEST_TIMEOUT_MSEC = 10000;
|
||||||
|
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const PROXY_PORT = params.get("proxyPort") ?? "3000";
|
||||||
|
const PROXY_SERVER_URL = `http://localhost:${PROXY_PORT}`;
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [connectionStatus, setConnectionStatus] = useState<
|
const [connectionStatus, setConnectionStatus] = useState<
|
||||||
"disconnected" | "connected" | "error"
|
"disconnected" | "connected" | "error"
|
||||||
@@ -82,7 +86,8 @@ const App = () => {
|
|||||||
const [args, setArgs] = useState<string>(() => {
|
const [args, setArgs] = useState<string>(() => {
|
||||||
return localStorage.getItem("lastArgs") || "";
|
return localStorage.getItem("lastArgs") || "";
|
||||||
});
|
});
|
||||||
const [url, setUrl] = useState<string>("http://localhost:3001/sse");
|
|
||||||
|
const [sseUrl, setSseUrl] = useState<string>("http://localhost:3001/sse");
|
||||||
const [transportType, setTransportType] = useState<"stdio" | "sse">("stdio");
|
const [transportType, setTransportType] = useState<"stdio" | "sse">("stdio");
|
||||||
const [requestHistory, setRequestHistory] = useState<
|
const [requestHistory, setRequestHistory] = useState<
|
||||||
{ request: string; response?: string }[]
|
{ request: string; response?: string }[]
|
||||||
@@ -191,7 +196,7 @@ const App = () => {
|
|||||||
}, [args]);
|
}, [args]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("http://localhost:3000/config")
|
fetch(`${PROXY_SERVER_URL}/config`)
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setEnv(data.defaultEnvironment);
|
setEnv(data.defaultEnvironment);
|
||||||
@@ -404,7 +409,7 @@ const App = () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const backendUrl = new URL("http://localhost:3000/sse");
|
const backendUrl = new URL(`${PROXY_SERVER_URL}/sse`);
|
||||||
|
|
||||||
backendUrl.searchParams.append("transportType", transportType);
|
backendUrl.searchParams.append("transportType", transportType);
|
||||||
if (transportType === "stdio") {
|
if (transportType === "stdio") {
|
||||||
@@ -412,7 +417,7 @@ const App = () => {
|
|||||||
backendUrl.searchParams.append("args", args);
|
backendUrl.searchParams.append("args", args);
|
||||||
backendUrl.searchParams.append("env", JSON.stringify(env));
|
backendUrl.searchParams.append("env", JSON.stringify(env));
|
||||||
} else {
|
} else {
|
||||||
backendUrl.searchParams.append("url", url);
|
backendUrl.searchParams.append("url", sseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientTransport = new SSEClientTransport(backendUrl);
|
const clientTransport = new SSEClientTransport(backendUrl);
|
||||||
@@ -469,8 +474,8 @@ const App = () => {
|
|||||||
setCommand={setCommand}
|
setCommand={setCommand}
|
||||||
args={args}
|
args={args}
|
||||||
setArgs={setArgs}
|
setArgs={setArgs}
|
||||||
url={url}
|
sseUrl={sseUrl}
|
||||||
setUrl={setUrl}
|
setSseUrl={setSseUrl}
|
||||||
env={env}
|
env={env}
|
||||||
setEnv={setEnv}
|
setEnv={setEnv}
|
||||||
onConnect={connectMcpServer}
|
onConnect={connectMcpServer}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { Play, ChevronDown, ChevronRight } from "lucide-react";
|
import { Play, ChevronDown, ChevronRight } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
import { StdErrNotification } from "@/lib/notificationTypes";
|
import { StdErrNotification } from "@/lib/notificationTypes";
|
||||||
|
|
||||||
import useTheme from "../lib/useTheme";
|
import useTheme from "../lib/useTheme";
|
||||||
|
import { version } from "../../../package.json";
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
connectionStatus: "disconnected" | "connected" | "error";
|
connectionStatus: "disconnected" | "connected" | "error";
|
||||||
@@ -22,8 +22,8 @@ interface SidebarProps {
|
|||||||
setCommand: (command: string) => void;
|
setCommand: (command: string) => void;
|
||||||
args: string;
|
args: string;
|
||||||
setArgs: (args: string) => void;
|
setArgs: (args: string) => void;
|
||||||
url: string;
|
sseUrl: string;
|
||||||
setUrl: (url: string) => void;
|
setSseUrl: (url: string) => void;
|
||||||
env: Record<string, string>;
|
env: Record<string, string>;
|
||||||
setEnv: (env: Record<string, string>) => void;
|
setEnv: (env: Record<string, string>) => void;
|
||||||
onConnect: () => void;
|
onConnect: () => void;
|
||||||
@@ -38,8 +38,8 @@ const Sidebar = ({
|
|||||||
setCommand,
|
setCommand,
|
||||||
args,
|
args,
|
||||||
setArgs,
|
setArgs,
|
||||||
url,
|
sseUrl,
|
||||||
setUrl,
|
setSseUrl,
|
||||||
env,
|
env,
|
||||||
setEnv,
|
setEnv,
|
||||||
onConnect,
|
onConnect,
|
||||||
@@ -52,7 +52,9 @@ const Sidebar = ({
|
|||||||
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
|
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
|
||||||
<div className="flex items-center justify-between p-4 border-b border-gray-200">
|
<div className="flex items-center justify-between p-4 border-b border-gray-200">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<h1 className="ml-2 text-lg font-semibold">MCP Inspector</h1>
|
<h1 className="ml-2 text-lg font-semibold">
|
||||||
|
MCP Inspector v{version}
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -75,6 +77,7 @@ const Sidebar = ({
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{transportType === "stdio" ? (
|
{transportType === "stdio" ? (
|
||||||
<>
|
<>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -99,8 +102,8 @@ const Sidebar = ({
|
|||||||
<label className="text-sm font-medium">URL</label>
|
<label className="text-sm font-medium">URL</label>
|
||||||
<Input
|
<Input
|
||||||
placeholder="URL"
|
placeholder="URL"
|
||||||
value={url}
|
value={sseUrl}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={(e) => setSseUrl(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { TabsContent } from "@/components/ui/tabs";
|
import { TabsContent } from "@/components/ui/tabs";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import {
|
import {
|
||||||
CallToolResult,
|
|
||||||
ListToolsResult,
|
ListToolsResult,
|
||||||
Tool,
|
Tool,
|
||||||
|
CallToolResultSchema,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { AlertCircle, Send } from "lucide-react";
|
import { AlertCircle, Send } from "lucide-react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@@ -40,7 +40,27 @@ const ToolsTab = ({
|
|||||||
if (!toolResult) return null;
|
if (!toolResult) return null;
|
||||||
|
|
||||||
if ("content" in toolResult) {
|
if ("content" in toolResult) {
|
||||||
const structuredResult = toolResult as CallToolResult;
|
const parsedResult = CallToolResultSchema.safeParse(toolResult);
|
||||||
|
if (!parsedResult.success) {
|
||||||
|
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">
|
||||||
|
{JSON.stringify(toolResult, null, 2)}
|
||||||
|
</pre>
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
{JSON.stringify(error, null, 2)}
|
||||||
|
</pre>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const structuredResult = parsedResult.data;
|
||||||
const isError = structuredResult.isError ?? false;
|
const isError = structuredResult.isError ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -63,7 +83,7 @@ const ToolsTab = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.type === "resource" && (
|
{item.type === "resource" && (
|
||||||
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-64">
|
<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">
|
||||||
{JSON.stringify(item.resource, null, 2)}
|
{JSON.stringify(item.resource, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,12 @@ export default defineConfig({
|
|||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
minify: false,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
20
package-lock.json
generated
20
package-lock.json
generated
@@ -1,22 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector",
|
"name": "@modelcontextprotocol/inspector",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@modelcontextprotocol/inspector",
|
"name": "@modelcontextprotocol/inspector",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"client",
|
"client",
|
||||||
"server"
|
"server"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/inspector-client": "0.2.3",
|
"@modelcontextprotocol/inspector-client": "0.2.6",
|
||||||
"@modelcontextprotocol/inspector-server": "0.2.3",
|
"@modelcontextprotocol/inspector-server": "0.2.6",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"spawn-rx": "^5.0.4",
|
"spawn-rx": "^5.1.0",
|
||||||
"ts-node": "^10.9.2"
|
"ts-node": "^10.9.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"name": "@modelcontextprotocol/inspector-client",
|
"name": "@modelcontextprotocol/inspector-client",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.0.1",
|
"@modelcontextprotocol/sdk": "^1.0.1",
|
||||||
@@ -5737,9 +5737,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/spawn-rx": {
|
"node_modules/spawn-rx": {
|
||||||
"version": "5.0.4",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.1.0.tgz",
|
||||||
"integrity": "sha512-Do11ahkHLlqN9G/J6fs10gdx25BU33NrpkyN3/DFXIIUVojBiJysl12nC0iGUkE+msJAPflzyfpLWWHGHw/6Xg==",
|
"integrity": "sha512-b4HX44hI0usMiHu6LNaZUVg0BGqHuBcl+81iEhZwhvKHz1efTqD/CHBcUbm/uIe5TARy9pJolxU2NMfh6GuQBA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.3.7",
|
"debug": "^4.3.7",
|
||||||
@@ -6926,7 +6926,7 @@
|
|||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"name": "@modelcontextprotocol/inspector-server",
|
"name": "@modelcontextprotocol/inspector-server",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.0.1",
|
"@modelcontextprotocol/sdk": "^1.0.1",
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector",
|
"name": "@modelcontextprotocol/inspector",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"description": "Model Context Protocol inspector",
|
"description": "Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
@@ -33,14 +33,14 @@
|
|||||||
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/inspector-client": "0.2.3",
|
"@modelcontextprotocol/inspector-client": "0.2.6",
|
||||||
"@modelcontextprotocol/inspector-server": "0.2.3",
|
"@modelcontextprotocol/inspector-server": "0.2.6",
|
||||||
"concurrently": "^9.0.1",
|
"concurrently": "^9.0.1",
|
||||||
"spawn-rx": "^5.0.4",
|
"spawn-rx": "^5.1.0",
|
||||||
"ts-node": "^10.9.2"
|
"ts-node": "^10.9.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.7.5",
|
"@types/node": "^22.7.5",
|
||||||
"prettier": "3.3.3"
|
"prettier": "3.3.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-server",
|
"name": "@modelcontextprotocol/inspector-server",
|
||||||
"version": "0.2.3",
|
"version": "0.2.6",
|
||||||
"description": "Server-side application for the Model Context Protocol inspector",
|
"description": "Server-side application for the Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import mcpProxy from "./mcpProxy.js";
|
import mcpProxy from "./mcpProxy.js";
|
||||||
|
import { findActualExecutable } from "spawn-rx";
|
||||||
|
|
||||||
// Polyfill EventSource for an SSE client in Node.js
|
// Polyfill EventSource for an SSE client in Node.js
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -37,14 +38,17 @@ const createTransport = async (query: express.Request["query"]) => {
|
|||||||
|
|
||||||
if (transportType === "stdio") {
|
if (transportType === "stdio") {
|
||||||
const command = query.command as string;
|
const command = query.command as string;
|
||||||
const args = (query.args as string).split(/\s+/);
|
const origArgs = (query.args as string).split(/\s+/);
|
||||||
const env = query.env ? JSON.parse(query.env as string) : undefined;
|
const env = query.env ? JSON.parse(query.env as string) : undefined;
|
||||||
|
|
||||||
|
const { cmd, args } = findActualExecutable(command, origArgs);
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Stdio transport: command=${command}, args=${args}, env=${JSON.stringify(env)}`,
|
`Stdio transport: command=${cmd}, args=${args}, env=${JSON.stringify(env)}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const transport = new StdioClientTransport({
|
const transport = new StdioClientTransport({
|
||||||
command,
|
command: cmd,
|
||||||
args,
|
args,
|
||||||
env,
|
env,
|
||||||
stderr: "pipe",
|
stderr: "pipe",
|
||||||
|
|||||||
Reference in New Issue
Block a user