Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3110cf9343 | ||
|
|
2c04fa31e8 | ||
|
|
e700bc713a | ||
|
|
bea86af65b | ||
|
|
68a6130b17 | ||
|
|
853a3b4faf | ||
|
|
6f62066d34 | ||
|
|
c770d217e7 | ||
|
|
98470a12f9 | ||
|
|
a00564fafa | ||
|
|
62546dec58 | ||
|
|
886ac5fc7b | ||
|
|
722df4d798 | ||
|
|
407e304585 | ||
|
|
60578314aa | ||
|
|
3c4cb17d09 | ||
|
|
fbac5b78bc | ||
|
|
f876b1ec0d | ||
|
|
aecfa21d47 | ||
|
|
a3d542c0a3 | ||
|
|
2b79b6ffd4 | ||
|
|
1f28b4474c | ||
|
|
d69d67cb64 | ||
|
|
7792070d81 | ||
|
|
34a2843756 | ||
|
|
2a34770959 | ||
|
|
6b674b0827 | ||
|
|
ca8db1f417 | ||
|
|
eb4456d1e3 | ||
|
|
780b92274d | ||
|
|
b825784b8f | ||
|
|
52c7e98055 | ||
|
|
4862aa7c1d | ||
|
|
561ea91504 | ||
|
|
7c2be8d139 | ||
|
|
97d469911e | ||
|
|
11b891c6ca |
35
README.md
35
README.md
@@ -2,18 +2,47 @@
|
||||
|
||||
The MCP inspector is a developer tool for testing and debugging MCP servers.
|
||||
|
||||
It can be run easily from `npx`. For example, in a folder where there's a built JavaScript server at `build/index.js`:
|
||||

|
||||
|
||||
```
|
||||
## Running the Inspector
|
||||
|
||||
### From an MCP server repository
|
||||
|
||||
To inspect an MCP server implementation, there's no need to clone this repo. Instead, use `npx`. For example, if your server is built at `build/index.js`:
|
||||
|
||||
```bash
|
||||
npx @modelcontextprotocol/inspector build/index.js
|
||||
```
|
||||
|
||||
You can also pass arguments along to the server:
|
||||
You can also pass arguments along which will get passed as arguments to your MCP server:
|
||||
|
||||
```
|
||||
npx @modelcontextprotocol/inspector build/index.js arg1 arg2 ...
|
||||
```
|
||||
|
||||
The inspector runs both a client UI (default port 5173) and an MCP proxy server (default port 3000). Open the client UI in your browser to use the inspector. You can customize the ports if needed:
|
||||
|
||||
```bash
|
||||
CLIENT_PORT=8080 SERVER_PORT=9000 npx @modelcontextprotocol/inspector build/index.js
|
||||
```
|
||||
|
||||
### From this repository
|
||||
|
||||
If you're working on the inspector itself:
|
||||
|
||||
Development mode:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Production mode:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
119
bin/cli.js
119
bin/cli.js
@@ -1,65 +1,88 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { join, dirname } from "path";
|
||||
import { resolve, dirname } from "path";
|
||||
import { spawnPromise } from "spawn-rx";
|
||||
import { fileURLToPath } from "url";
|
||||
import concurrently from "concurrently";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Get command line arguments
|
||||
const [, , command, ...mcpServerArgs] = process.argv;
|
||||
|
||||
const inspectorServerPath = join(__dirname, "../server/build/index.js");
|
||||
|
||||
// Path to the client entry point
|
||||
const inspectorClientPath = join(__dirname, "../client/bin/cli.js");
|
||||
|
||||
console.log("Starting MCP inspector...");
|
||||
|
||||
function escapeArg(arg) {
|
||||
if (arg.includes(" ") || arg.includes("'") || arg.includes('"')) {
|
||||
return `\\"${arg.replace(/"/g, '\\\\\\"')}\\"`;
|
||||
}
|
||||
return arg;
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const serverCommand = [
|
||||
`node`,
|
||||
inspectorServerPath,
|
||||
command ? `--env ${escapeArg(command)}` : "",
|
||||
mcpServerArgs.length
|
||||
? `--args="${mcpServerArgs.map(escapeArg).join(" ")}"`
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
async function main() {
|
||||
// Get command line arguments
|
||||
const [, , command, ...mcpServerArgs] = process.argv;
|
||||
|
||||
const CLIENT_PORT = process.env.CLIENT_PORT ?? "";
|
||||
const SERVER_PORT = process.env.SERVER_PORT ?? "";
|
||||
const inspectorServerPath = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"server",
|
||||
"build",
|
||||
"index.js",
|
||||
);
|
||||
|
||||
const { result } = concurrently(
|
||||
// Path to the client entry point
|
||||
const inspectorClientPath = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"client",
|
||||
"bin",
|
||||
"cli.js",
|
||||
);
|
||||
|
||||
const CLIENT_PORT = process.env.CLIENT_PORT ?? "5173";
|
||||
const SERVER_PORT = process.env.SERVER_PORT ?? "3000";
|
||||
|
||||
console.log("Starting MCP inspector...");
|
||||
|
||||
const abort = new AbortController();
|
||||
|
||||
let cancelled = false;
|
||||
process.on("SIGINT", () => {
|
||||
cancelled = true;
|
||||
abort.abort();
|
||||
});
|
||||
|
||||
const server = spawnPromise(
|
||||
"node",
|
||||
[
|
||||
{
|
||||
command: `PORT=${SERVER_PORT} ${serverCommand}`,
|
||||
name: "server",
|
||||
},
|
||||
{
|
||||
command: `PORT=${CLIENT_PORT} node ${inspectorClientPath}`,
|
||||
name: "client",
|
||||
},
|
||||
inspectorServerPath,
|
||||
...(command ? [`--env`, command] : []),
|
||||
...(mcpServerArgs ? ["--args", mcpServerArgs.join(" ")] : []),
|
||||
],
|
||||
{
|
||||
prefix: "name",
|
||||
killOthers: ["failure", "success"],
|
||||
restartTries: 3,
|
||||
env: { ...process.env, PORT: SERVER_PORT },
|
||||
signal: abort.signal,
|
||||
echoOutput: true,
|
||||
},
|
||||
);
|
||||
);
|
||||
|
||||
console.log(
|
||||
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT || 5173} 🚀`,
|
||||
);
|
||||
const client = spawnPromise("node", [inspectorClientPath], {
|
||||
env: { ...process.env, PORT: CLIENT_PORT },
|
||||
signal: abort.signal,
|
||||
echoOutput: true,
|
||||
});
|
||||
|
||||
result.catch((err) => {
|
||||
console.error("An error occurred:", err);
|
||||
// Make sure our server/client didn't immediately fail
|
||||
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} 🚀`,
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.any([server, client]);
|
||||
} catch (e) {
|
||||
if (!cancelled || process.env.DEBUG) throw e;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
main()
|
||||
.then((_) => process.exit(0))
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@modelcontextprotocol/inspector-client",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.4",
|
||||
"description": "Client-side application for the Model Context Protocol inspector",
|
||||
"license": "MIT",
|
||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||
@@ -21,7 +21,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "0.7.0",
|
||||
"@modelcontextprotocol/sdk": "^1.0.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-select": "^2.1.2",
|
||||
|
||||
@@ -29,7 +29,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Notification,
|
||||
StdErrNotification,
|
||||
StdErrNotificationSchema
|
||||
StdErrNotificationSchema,
|
||||
} from "./lib/notificationTypes";
|
||||
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
@@ -54,10 +54,13 @@ import RootsTab from "./components/RootsTab";
|
||||
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
|
||||
import Sidebar from "./components/Sidebar";
|
||||
import ToolsTab from "./components/ToolsTab";
|
||||
import useDarkModeSync from "./lib/useDarkModeSync";
|
||||
|
||||
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 [connectionStatus, setConnectionStatus] = useState<
|
||||
"disconnected" | "connected" | "error"
|
||||
@@ -83,7 +86,8 @@ const App = () => {
|
||||
const [args, setArgs] = useState<string>(() => {
|
||||
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 [requestHistory, setRequestHistory] = useState<
|
||||
{ request: string; response?: string }[]
|
||||
@@ -144,8 +148,6 @@ const App = () => {
|
||||
const dragStartY = useRef<number>(0);
|
||||
const dragStartHeight = useRef<number>(0);
|
||||
|
||||
useDarkModeSync();
|
||||
|
||||
const handleDragStart = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
setIsDragging(true);
|
||||
@@ -194,7 +196,7 @@ const App = () => {
|
||||
}, [args]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("http://localhost:3000/config")
|
||||
fetch(`${PROXY_SERVER_URL}/config`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setEnv(data.defaultEnvironment);
|
||||
@@ -407,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);
|
||||
if (transportType === "stdio") {
|
||||
@@ -415,7 +417,7 @@ const App = () => {
|
||||
backendUrl.searchParams.append("args", args);
|
||||
backendUrl.searchParams.append("env", JSON.stringify(env));
|
||||
} else {
|
||||
backendUrl.searchParams.append("url", url);
|
||||
backendUrl.searchParams.append("url", sseUrl);
|
||||
}
|
||||
|
||||
const clientTransport = new SSEClientTransport(backendUrl);
|
||||
@@ -472,8 +474,8 @@ const App = () => {
|
||||
setCommand={setCommand}
|
||||
args={args}
|
||||
setArgs={setArgs}
|
||||
url={url}
|
||||
setUrl={setUrl}
|
||||
sseUrl={sseUrl}
|
||||
setSseUrl={setSseUrl}
|
||||
env={env}
|
||||
setEnv={setEnv}
|
||||
onConnect={connectMcpServer}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { Play, ChevronDown, ChevronRight, Settings } from "lucide-react";
|
||||
import { Play, ChevronDown, ChevronRight } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
@@ -12,6 +11,9 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { StdErrNotification } from "@/lib/notificationTypes";
|
||||
|
||||
import useTheme from "../lib/useTheme";
|
||||
import { version } from "../../../package.json";
|
||||
|
||||
interface SidebarProps {
|
||||
connectionStatus: "disconnected" | "connected" | "error";
|
||||
transportType: "stdio" | "sse";
|
||||
@@ -20,8 +22,8 @@ interface SidebarProps {
|
||||
setCommand: (command: string) => void;
|
||||
args: string;
|
||||
setArgs: (args: string) => void;
|
||||
url: string;
|
||||
setUrl: (url: string) => void;
|
||||
sseUrl: string;
|
||||
setSseUrl: (url: string) => void;
|
||||
env: Record<string, string>;
|
||||
setEnv: (env: Record<string, string>) => void;
|
||||
onConnect: () => void;
|
||||
@@ -36,20 +38,24 @@ const Sidebar = ({
|
||||
setCommand,
|
||||
args,
|
||||
setArgs,
|
||||
url,
|
||||
setUrl,
|
||||
sseUrl,
|
||||
setSseUrl,
|
||||
env,
|
||||
setEnv,
|
||||
onConnect,
|
||||
stdErrNotifications,
|
||||
}: SidebarProps) => {
|
||||
const [theme, setTheme] = useTheme();
|
||||
const [showEnvVars, setShowEnvVars] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
|
||||
<div className="flex items-center p-4 border-b border-gray-200">
|
||||
<Settings className="w-6 h-6 text-gray-500" />
|
||||
<h1 className="ml-2 text-lg font-semibold">MCP Inspector</h1>
|
||||
<div className="flex items-center justify-between p-4 border-b border-gray-200">
|
||||
<div className="flex items-center">
|
||||
<h1 className="ml-2 text-lg font-semibold">
|
||||
MCP Inspector v{version}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 flex-1 overflow-auto">
|
||||
@@ -71,6 +77,7 @@ const Sidebar = ({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{transportType === "stdio" ? (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
@@ -95,8 +102,8 @@ const Sidebar = ({
|
||||
<label className="text-sm font-medium">URL</label>
|
||||
<Input
|
||||
placeholder="URL"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
value={sseUrl}
|
||||
onChange={(e) => setSseUrl(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -212,6 +219,25 @@ const Sidebar = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 border-t">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Select
|
||||
value={theme}
|
||||
onValueChange={(value: string) =>
|
||||
setTheme(value as "system" | "light" | "dark")
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-[120px]" id="theme-select">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="system">System</SelectItem>
|
||||
<SelectItem value="light">Light</SelectItem>
|
||||
<SelectItem value="dark">Dark</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,9 +5,9 @@ import { Label } from "@/components/ui/label";
|
||||
import { TabsContent } from "@/components/ui/tabs";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
CallToolResult,
|
||||
ListToolsResult,
|
||||
Tool,
|
||||
CallToolResultSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { AlertCircle, Send } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
@@ -40,7 +40,27 @@ const ToolsTab = ({
|
||||
if (!toolResult) return null;
|
||||
|
||||
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;
|
||||
|
||||
return (
|
||||
@@ -63,7 +83,7 @@ const ToolsTab = ({
|
||||
/>
|
||||
)}
|
||||
{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)}
|
||||
</pre>
|
||||
)}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
// Listen for changes to the user's preferred color scheme
|
||||
const useDarkModeSync = () => {
|
||||
useEffect(() => {
|
||||
const darkModeMediaQuery = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
);
|
||||
const handleDarkModeChange = (e: MediaQueryListEvent) => {
|
||||
if (e.matches) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
};
|
||||
|
||||
if (darkModeMediaQuery.matches) {
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
|
||||
darkModeMediaQuery.addEventListener("change", handleDarkModeChange);
|
||||
|
||||
return () => {
|
||||
darkModeMediaQuery.removeEventListener("change", handleDarkModeChange);
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
|
||||
export default useDarkModeSync;
|
||||
51
client/src/lib/useTheme.ts
Normal file
51
client/src/lib/useTheme.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
type Theme = "light" | "dark" | "system";
|
||||
|
||||
const useTheme = (): [Theme, (mode: Theme) => void] => {
|
||||
const [theme, setTheme] = useState<Theme>(() => {
|
||||
const savedTheme = localStorage.getItem("theme") as Theme;
|
||||
return savedTheme || "system";
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const darkModeMediaQuery = window.matchMedia(
|
||||
"(prefers-color-scheme: dark)",
|
||||
);
|
||||
const handleDarkModeChange = (e: MediaQueryListEvent) => {
|
||||
if (theme === "system") {
|
||||
updateDocumentTheme(e.matches ? "dark" : "light");
|
||||
}
|
||||
};
|
||||
|
||||
const updateDocumentTheme = (newTheme: "light" | "dark") => {
|
||||
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
||||
};
|
||||
|
||||
// Set initial theme based on current mode
|
||||
if (theme === "system") {
|
||||
updateDocumentTheme(darkModeMediaQuery.matches ? "dark" : "light");
|
||||
} else {
|
||||
updateDocumentTheme(theme);
|
||||
}
|
||||
|
||||
darkModeMediaQuery.addEventListener("change", handleDarkModeChange);
|
||||
|
||||
return () => {
|
||||
darkModeMediaQuery.removeEventListener("change", handleDarkModeChange);
|
||||
};
|
||||
}, [theme]);
|
||||
|
||||
return [
|
||||
theme,
|
||||
useCallback((newTheme: Theme) => {
|
||||
setTheme(newTheme);
|
||||
localStorage.setItem("theme", newTheme);
|
||||
if (newTheme !== "system") {
|
||||
document.documentElement.classList.toggle("dark", newTheme === "dark");
|
||||
}
|
||||
}, []),
|
||||
];
|
||||
};
|
||||
|
||||
export default useTheme;
|
||||
@@ -1,4 +1,5 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
import animate from "tailwindcss-animate";
|
||||
export default {
|
||||
darkMode: ["class"],
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
@@ -53,5 +54,5 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
plugins: [animate],
|
||||
};
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -10,4 +10,12 @@ export default defineConfig({
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
minify: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BIN
mcp-inspector.png
Normal file
BIN
mcp-inspector.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 294 KiB |
2425
package-lock.json
generated
2425
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@modelcontextprotocol/inspector",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.4",
|
||||
"description": "Model Context Protocol inspector",
|
||||
"license": "MIT",
|
||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||
@@ -27,18 +27,20 @@
|
||||
"build": "npm run build-server && npm run build-client",
|
||||
"start-server": "cd server && npm run start",
|
||||
"start-client": "cd client && npm run preview",
|
||||
"start": "./bin/cli.js",
|
||||
"start": "node ./bin/cli.js",
|
||||
"prepare": "npm run build",
|
||||
"prettier-fix": "prettier --write .",
|
||||
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/inspector-client": "0.2.1",
|
||||
"@modelcontextprotocol/inspector-server": "0.2.1",
|
||||
"concurrently": "^9.0.1"
|
||||
"@modelcontextprotocol/inspector-client": "0.2.4",
|
||||
"@modelcontextprotocol/inspector-server": "0.2.4",
|
||||
"concurrently": "^9.0.1",
|
||||
"spawn-rx": "^5.1.0",
|
||||
"ts-node": "^10.9.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "3.3.3",
|
||||
"@types/node": "^22.7.5"
|
||||
"@types/node": "^22.7.5",
|
||||
"prettier": "3.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@modelcontextprotocol/inspector-server",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.4",
|
||||
"description": "Server-side application for the Model Context Protocol inspector",
|
||||
"license": "MIT",
|
||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||
@@ -27,7 +27,7 @@
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "0.7.0",
|
||||
"@modelcontextprotocol/sdk": "^1.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"eventsource": "^2.0.2",
|
||||
"express": "^4.21.0",
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import express from "express";
|
||||
import mcpProxy from "./mcpProxy.js";
|
||||
import { findActualExecutable } from "spawn-rx";
|
||||
|
||||
// Polyfill EventSource for an SSE client in Node.js
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -37,25 +38,33 @@ const createTransport = async (query: express.Request["query"]) => {
|
||||
|
||||
if (transportType === "stdio") {
|
||||
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 { cmd, args } = findActualExecutable(command, origArgs);
|
||||
|
||||
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({
|
||||
command,
|
||||
command: cmd,
|
||||
args,
|
||||
env,
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
await transport.start();
|
||||
|
||||
console.log("Spawned stdio transport");
|
||||
return transport;
|
||||
} else if (transportType === "sse") {
|
||||
const url = query.url as string;
|
||||
console.log(`SSE transport: url=${url}`);
|
||||
|
||||
const transport = new SSEClientTransport(new URL(url));
|
||||
await transport.start();
|
||||
|
||||
console.log("Connected to SSE transport");
|
||||
return transport;
|
||||
} else {
|
||||
@@ -99,6 +108,7 @@ app.get("/sse", async (req, res) => {
|
||||
console.error(error);
|
||||
},
|
||||
});
|
||||
|
||||
console.log("Set up MCP proxy");
|
||||
} catch (error) {
|
||||
console.error("Error in /sse route:", error);
|
||||
@@ -126,6 +136,7 @@ app.post("/message", async (req, res) => {
|
||||
app.get("/config", (req, res) => {
|
||||
try {
|
||||
const defaultEnvironment = getDefaultEnvironment();
|
||||
|
||||
res.json({
|
||||
defaultEnvironment,
|
||||
defaultCommand: values.env,
|
||||
@@ -138,4 +149,4 @@ app.get("/config", (req, res) => {
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.listen(PORT, () => { });
|
||||
app.listen(PORT, () => {});
|
||||
|
||||
Reference in New Issue
Block a user