Compare commits

...

33 Commits
0.2.3 ... 0.3.0

Author SHA1 Message Date
Ashwin Bhat
576ff0043a bump version to 0.3.0 2024-12-05 08:01:57 -08:00
Ashwin Bhat
18dc4d0a99 Merge pull request #100 from evalstate/fix/tool-timeout
Allow setting the timeout with the "timeout" URL parameter
2024-12-05 07:59:55 -08:00
=
f04b161411 Allow setting timeout via "timeout" URL parameter 2024-12-05 08:11:35 +00:00
Ashwin Bhat
bd6a63603a Merge pull request #102 from modelcontextprotocol/ashwin/sdk
update sdk to 1.0.3
2024-12-04 14:20:09 -08:00
Ashwin Bhat
b845444fab update sdk to 1.0.3 2024-12-04 09:56:04 -08:00
Justin Spahr-Summers
ace94c4d37 Merge pull request #95 from modelcontextprotocol/ashwin-ant-patch-1
link to MCP docs site in readme
2024-12-02 06:56:07 -06:00
Ashwin Bhat
50640bc9cc Merge pull request #98 from heuperman/add-button-to-clear-items
Add button to clear loaded items
2024-12-01 11:08:32 -05:00
Kees Heuperman
cc17ba8d56 feat: Add button to clear loaded items
Add a button to the ListPane component that clears loaded items. This
will allow the user to clear and reload resources, resource templates,
prompts or tools when they expect the available items to have changed.
2024-12-01 09:50:53 +01:00
Ashwin Bhat
764f02310d link to MCP docs site in readme 2024-11-29 16:26:56 -05:00
Ashwin Bhat
945299181d bump version to 0.2.7 2024-11-29 08:44:12 -05:00
David Soria Parra
79344bd495 Merge pull request #91 from modelcontextprotocol/ashwin/spawnfix
fix arg passing
2024-11-29 11:34:25 +00:00
Ashwin Bhat
295ccac27e fix arg passing 2024-11-27 19:17:47 -05:00
Ashwin Bhat
f3f424f21e bump version 2024-11-27 17:29:02 -05:00
Ashwin Bhat
6b6eeb8dcd bump version to 0.2.5 2024-11-27 17:24:19 -05:00
Ashwin Bhat
3110cf9343 Merge pull request #88 from modelcontextprotocol/ani/fix-npx
Enable using 'npx' as your command on Windows
2024-11-27 16:03:37 -05:00
Ani Betts
2c04fa31e8 Merge branch 'main' into ani/fix-npx 2024-11-27 21:57:44 +01:00
Ashwin Bhat
e700bc713a Merge pull request #87 from modelcontextprotocol/ashwin/versiondisplay
display inspector version in UI
2024-11-27 13:06:52 -05:00
Ashwin Bhat
bea86af65b Merge pull request #89 from evalstate/main
Dark Mode and Word Wrap for Resource Viewer
2024-11-27 13:06:06 -05:00
evalstate
68a6130b17 fix dark mode styling and add word wrap for resource viewer. 2024-11-27 17:56:22 +00:00
Ani Betts
853a3b4faf Enable using 'npx' as your command on Windows 2024-11-27 17:04:52 +01:00
Ashwin Bhat
6f62066d34 display inspector version in UI 2024-11-27 10:52:38 -05:00
ashwin-ant
c770d217e7 Merge pull request #86 from modelcontextprotocol/ani/debuggability
Make debugging Inspector easier for users
2024-11-27 10:04:00 -05:00
Ani Betts
98470a12f9 Make stdout/error echo for client and server 2024-11-27 15:57:02 +01:00
Ani Betts
a00564fafa Disable minification on production build, we don't need it here and it makes debugging annoying 2024-11-27 15:55:11 +01:00
Ashwin Bhat
62546dec58 bump version to 0.2.4 2024-11-27 09:33:15 -05:00
ashwin-ant
886ac5fc7b Merge pull request #81 from modelcontextprotocol/ashwin/serverport
Respect custom server port
2024-11-27 08:59:59 -05:00
ashwin-ant
722df4d798 Merge pull request #82 from jacksteamdev/fix-1
Add Runtime Type Validation for Tool Results
2024-11-26 15:32:58 -05:00
Jack Steam
407e304585 Merge branch 'main' into fix-1 2024-11-26 13:31:12 -07:00
Jack Steam
60578314aa Update client/src/components/ToolsTab.tsx
Co-authored-by: ashwin-ant <ashwin@anthropic.com>
2024-11-26 13:30:43 -07:00
Jack Steam
fbac5b78bc feat: add data validation message 2024-11-26 12:47:39 -06:00
Ashwin Bhat
f876b1ec0d consolidate server URL configuration 2024-11-26 13:40:28 -05:00
Jack Steam
aecfa21d47 fix: add static type validation 2024-11-26 11:14:55 -07:00
Ashwin Bhat
a3d542c0a3 make server port configurable via URL query param 2024-11-26 13:12:45 -05:00
15 changed files with 137 additions and 50 deletions

View File

@@ -26,6 +26,8 @@ The inspector runs both a client UI (default port 5173) and an MCP proxy server
CLIENT_PORT=8080 SERVER_PORT=9000 npx @modelcontextprotocol/inspector build/index.js
```
For more details on ways to use the inspector, see the [Inspector section of the MCP docs site](https://modelcontextprotocol.io/docs/tools/inspector).
### From this repository
If you're working on the inspector itself:

View File

@@ -49,20 +49,26 @@ async function main() {
[
inspectorServerPath,
...(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], {
env: { ...process.env, PORT: CLIENT_PORT },
signal: abort.signal,
echoOutput: true,
});
// 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} 🚀`,
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} 🚀`,
);
try {

View File

@@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector-client",
"version": "0.2.3",
"version": "0.3.0",
"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": "^1.0.1",
"@modelcontextprotocol/sdk": "^1.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.2",

View File

@@ -57,6 +57,11 @@ import ToolsTab from "./components/ToolsTab";
const DEFAULT_REQUEST_TIMEOUT_MSEC = 10000;
const params = new URLSearchParams(window.location.search);
const PROXY_PORT = params.get("proxyPort") ?? "3000";
const REQUEST_TIMEOUT = parseInt(params.get("timeout") ?? "") || DEFAULT_REQUEST_TIMEOUT_MSEC;
const PROXY_SERVER_URL = `http://localhost:${PROXY_PORT}`;
const App = () => {
const [connectionStatus, setConnectionStatus] = useState<
"disconnected" | "connected" | "error"
@@ -82,7 +87,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 }[]
@@ -191,7 +197,7 @@ const App = () => {
}, [args]);
useEffect(() => {
fetch("http://localhost:3000/config")
fetch(`${PROXY_SERVER_URL}/config`)
.then((response) => response.json())
.then((data) => {
setEnv(data.defaultEnvironment);
@@ -238,7 +244,7 @@ const App = () => {
const abortController = new AbortController();
const timeoutId = setTimeout(() => {
abortController.abort("Request timed out");
}, DEFAULT_REQUEST_TIMEOUT_MSEC);
}, REQUEST_TIMEOUT);
let response;
try {
@@ -404,7 +410,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") {
@@ -412,7 +418,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);
@@ -469,8 +475,8 @@ const App = () => {
setCommand={setCommand}
args={args}
setArgs={setArgs}
url={url}
setUrl={setUrl}
sseUrl={sseUrl}
setSseUrl={setSseUrl}
env={env}
setEnv={setEnv}
onConnect={connectMcpServer}
@@ -520,10 +526,18 @@ const App = () => {
clearError("resources");
listResources();
}}
clearResources={() => {
setResources([]);
setNextResourceCursor(undefined);
}}
listResourceTemplates={() => {
clearError("resources");
listResourceTemplates();
}}
clearResourceTemplates={() => {
setResourceTemplates([]);
setNextResourceTemplateCursor(undefined);
}}
readResource={(uri) => {
clearError("resources");
readResource(uri);
@@ -544,6 +558,10 @@ const App = () => {
clearError("prompts");
listPrompts();
}}
clearPrompts={() => {
setPrompts([]);
setNextPromptCursor(undefined);
}}
getPrompt={(name, args) => {
clearError("prompts");
getPrompt(name, args);
@@ -563,6 +581,10 @@ const App = () => {
clearError("tools");
listTools();
}}
clearTools={() => {
setTools([]);
setNextToolCursor(undefined);
}}
callTool={(name, params) => {
clearError("tools");
callTool(name, params);

View File

@@ -3,6 +3,7 @@ import { Button } from "./ui/button";
type ListPaneProps<T> = {
items: T[];
listItems: () => void;
clearItems: () => void;
setSelectedItem: (item: T) => void;
renderItem: (item: T) => React.ReactNode;
title: string;
@@ -13,6 +14,7 @@ type ListPaneProps<T> = {
const ListPane = <T extends object>({
items,
listItems,
clearItems,
setSelectedItem,
renderItem,
title,
@@ -32,6 +34,14 @@ const ListPane = <T extends object>({
>
{buttonText}
</Button>
<Button
variant="outline"
className="w-full mb-4"
onClick={clearItems}
disabled={items.length === 0}
>
Clear
</Button>
<div className="space-y-2 overflow-y-auto max-h-96">
{items.map((item, index) => (
<div

View File

@@ -22,6 +22,7 @@ export type Prompt = {
const PromptsTab = ({
prompts,
listPrompts,
clearPrompts,
getPrompt,
selectedPrompt,
setSelectedPrompt,
@@ -31,6 +32,7 @@ const PromptsTab = ({
}: {
prompts: Prompt[];
listPrompts: () => void;
clearPrompts: () => void;
getPrompt: (name: string, args: Record<string, string>) => void;
selectedPrompt: Prompt | null;
setSelectedPrompt: (prompt: Prompt) => void;
@@ -55,6 +57,7 @@ const PromptsTab = ({
<ListPane
items={prompts}
listItems={listPrompts}
clearItems={clearPrompts}
setSelectedItem={(prompt) => {
setSelectedPrompt(prompt);
setPromptArgs({});

View File

@@ -16,7 +16,9 @@ const ResourcesTab = ({
resources,
resourceTemplates,
listResources,
clearResources,
listResourceTemplates,
clearResourceTemplates,
readResource,
selectedResource,
setSelectedResource,
@@ -28,7 +30,9 @@ const ResourcesTab = ({
resources: Resource[];
resourceTemplates: ResourceTemplate[];
listResources: () => void;
clearResources: () => void;
listResourceTemplates: () => void;
clearResourceTemplates: () => void;
readResource: (uri: string) => void;
selectedResource: Resource | null;
setSelectedResource: (resource: Resource | null) => void;
@@ -68,6 +72,7 @@ const ResourcesTab = ({
<ListPane
items={resources}
listItems={listResources}
clearItems={clearResources}
setSelectedItem={(resource) => {
setSelectedResource(resource);
readResource(resource.uri);
@@ -90,6 +95,7 @@ const ResourcesTab = ({
<ListPane
items={resourceTemplates}
listItems={listResourceTemplates}
clearItems={clearResourceTemplates}
setSelectedItem={(template) => {
setSelectedTemplate(template);
setSelectedResource(null);

View File

@@ -1,5 +1,4 @@
import { useState } from "react";
import { Play, ChevronDown, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@@ -13,6 +12,7 @@ import {
import { StdErrNotification } from "@/lib/notificationTypes";
import useTheme from "../lib/useTheme";
import { version } from "../../../package.json";
interface SidebarProps {
connectionStatus: "disconnected" | "connected" | "error";
@@ -22,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;
@@ -38,8 +38,8 @@ const Sidebar = ({
setCommand,
args,
setArgs,
url,
setUrl,
sseUrl,
setSseUrl,
env,
setEnv,
onConnect,
@@ -52,7 +52,9 @@ const Sidebar = ({
<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">
<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>
@@ -75,6 +77,7 @@ const Sidebar = ({
</SelectContent>
</Select>
</div>
{transportType === "stdio" ? (
<>
<div className="space-y-2">
@@ -99,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>
)}

View File

@@ -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";
@@ -18,6 +18,7 @@ import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js"
const ToolsTab = ({
tools,
listTools,
clearTools,
callTool,
selectedTool,
setSelectedTool,
@@ -27,6 +28,7 @@ const ToolsTab = ({
}: {
tools: Tool[];
listTools: () => void;
clearTools: () => void;
callTool: (name: string, params: Record<string, unknown>) => void;
selectedTool: Tool | null;
setSelectedTool: (tool: Tool) => void;
@@ -40,7 +42,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 +85,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>
)}
@@ -88,6 +110,7 @@ const ToolsTab = ({
<ListPane
items={tools}
listItems={listTools}
clearItems={clearTools}
setSelectedItem={setSelectedTool}
renderItem={(tool) => (
<>

View File

@@ -23,7 +23,8 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true
},
"include": ["src"]
}

View File

@@ -10,4 +10,12 @@ export default defineConfig({
"@": path.resolve(__dirname, "./src"),
},
},
build: {
minify: false,
rollupOptions: {
output: {
manualChunks: undefined
}
}
}
});

31
package-lock.json generated
View File

@@ -1,22 +1,22 @@
{
"name": "@modelcontextprotocol/inspector",
"version": "0.2.3",
"version": "0.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@modelcontextprotocol/inspector",
"version": "0.2.3",
"version": "0.3.0",
"license": "MIT",
"workspaces": [
"client",
"server"
],
"dependencies": {
"@modelcontextprotocol/inspector-client": "0.2.3",
"@modelcontextprotocol/inspector-server": "0.2.3",
"@modelcontextprotocol/inspector-client": "0.3.0",
"@modelcontextprotocol/inspector-server": "0.3.0",
"concurrently": "^9.0.1",
"spawn-rx": "^5.0.4",
"spawn-rx": "^5.1.0",
"ts-node": "^10.9.2"
},
"bin": {
@@ -29,10 +29,10 @@
},
"client": {
"name": "@modelcontextprotocol/inspector-client",
"version": "0.2.3",
"version": "0.3.0",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.1",
"@modelcontextprotocol/sdk": "^1.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-select": "^2.1.2",
@@ -1203,10 +1203,9 @@
"link": true
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.1.tgz",
"integrity": "sha512-slLdFaxQJ9AlRg+hw28iiTtGvShAOgOKXcD0F91nUcRYiOMuS9ZBYjcdNZRXW9G5JQ511GRTdUy1zQVZDpJ+4w==",
"license": "MIT",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.3.tgz",
"integrity": "sha512-2as3cX/VJ0YBHGmdv3GFyTpoM8q2gqE98zh3Vf1NwnsSY0h3mvoO07MUzfygCKkWsFjcZm4otIiqD6Xh7kiSBQ==",
"dependencies": {
"content-type": "^1.0.5",
"raw-body": "^3.0.0",
@@ -5737,9 +5736,9 @@
}
},
"node_modules/spawn-rx": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.0.4.tgz",
"integrity": "sha512-Do11ahkHLlqN9G/J6fs10gdx25BU33NrpkyN3/DFXIIUVojBiJysl12nC0iGUkE+msJAPflzyfpLWWHGHw/6Xg==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.1.0.tgz",
"integrity": "sha512-b4HX44hI0usMiHu6LNaZUVg0BGqHuBcl+81iEhZwhvKHz1efTqD/CHBcUbm/uIe5TARy9pJolxU2NMfh6GuQBA==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.7",
@@ -6926,10 +6925,10 @@
},
"server": {
"name": "@modelcontextprotocol/inspector-server",
"version": "0.2.3",
"version": "0.3.0",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.1",
"@modelcontextprotocol/sdk": "^1.0.3",
"cors": "^2.8.5",
"eventsource": "^2.0.2",
"express": "^4.21.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector",
"version": "0.2.3",
"version": "0.3.0",
"description": "Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
@@ -33,10 +33,10 @@
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
},
"dependencies": {
"@modelcontextprotocol/inspector-client": "0.2.3",
"@modelcontextprotocol/inspector-server": "0.2.3",
"@modelcontextprotocol/inspector-client": "0.3.0",
"@modelcontextprotocol/inspector-server": "0.3.0",
"concurrently": "^9.0.1",
"spawn-rx": "^5.0.4",
"spawn-rx": "^5.1.0",
"ts-node": "^10.9.2"
},
"devDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@modelcontextprotocol/inspector-server",
"version": "0.2.3",
"version": "0.3.0",
"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": "^1.0.1",
"@modelcontextprotocol/sdk": "^1.0.3",
"cors": "^2.8.5",
"eventsource": "^2.0.2",
"express": "^4.21.0",

View File

@@ -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,14 +38,17 @@ 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",