diff --git a/AGENTS.md b/AGENTS.md index 27d28f72d..3b8fef4a6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,35 +1,13 @@ # MCP Inspector Development Guide -> **Note:** Inspector V2 is under development to address architectural and UX improvements. During this time, V1 contributions should focus on **bug fixes and MCP spec compliance**. See [CONTRIBUTING.md](CONTRIBUTING.md) for more details. - -## Build Commands - -- Build all: `npm run build` -- Build client: `npm run build-client` -- Build server: `npm run build-server` -- Development mode: `npm run dev` (use `npm run dev:windows` on Windows) -- Format code: `npm run prettier-fix` -- Client lint: `cd client && npm run lint` - -## Code Style Guidelines - -- Use TypeScript with proper type annotations -- Follow React functional component patterns with hooks -- Use ES modules (import/export) not CommonJS -- Use Prettier for formatting (auto-formatted on commit) -- Follow existing naming conventions: - - camelCase for variables and functions - - PascalCase for component names and types - - kebab-case for file names -- Use async/await for asynchronous operations -- Implement proper error handling with try/catch blocks -- Use Tailwind CSS for styling in the client -- Keep components small and focused on a single responsibility - -## Project Organization - -The project is organized as a monorepo with workspaces: - -- `client/`: React frontend with Vite, TypeScript and Tailwind -- `server/`: Express backend with TypeScript -- `cli/`: Command-line interface for testing and invoking MCP server methods directly +> # ⛔ v1 is deprecated — do not open pull requests against this branch +> +> **This branch (`v1/main`) accepts security fixes only, and only by way of +> issue creation.** +> +> **Agents must NEVER attempt to create a pull request against `v1/main`.** File +> an issue describing the security problem instead. A human maintainer decides +> whether it warrants a v1 release and implements it. +> +> Everything else — features, bug fixes, refactors, docs — belongs in **v2**. +> Do not port it here, and do not "helpfully" fix it here in passing. diff --git a/README.md b/README.md index 36e9f3dee..d185cf3d5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,17 @@ The MCP inspector is a developer tool for testing and debugging MCP servers. -![MCP Inspector Screenshot](https://raw.githubusercontent.com/modelcontextprotocol/inspector/main/mcp-inspector.png) +> # ⚠️ Deprecated +> +> **`@modelcontextprotocol/inspector` v1 is deprecated.** +> +> ``` +> npx @modelcontextprotocol/inspector@latest +> ``` +> +> v1 receives **security fixes only**. All new development happens in v2. + +![MCP Inspector Screenshot](https://raw.githubusercontent.com/modelcontextprotocol/inspector/v1/main/mcp-inspector.png) ## Architecture Overview diff --git a/cli/package.json b/cli/package.json index 81f0ac68f..a59bd941c 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-cli", - "version": "1.0.0", + "version": "1.0.1", "description": "CLI for the Model Context Protocol inspector", "license": "SEE LICENSE IN LICENSE", "author": "Model Context Protocol a Series of LF Projects, LLC.", diff --git a/cli/src/cli.ts b/cli/src/cli.ts index f4187e02d..f625ed010 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -9,6 +9,20 @@ import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); +/** + * v1 deprecation notice. Written to stderr, never stdout: `--cli` mode emits + * JSON on stdout and callers pipe it, so a banner there would corrupt output. + */ +const DEPRECATION_NOTICE = [ + "⚠️ @modelcontextprotocol/inspector v1 is deprecated.", + " Upgrade: npx @modelcontextprotocol/inspector@latest", + " v1 receives security fixes only.", +].join("\n"); + +function printDeprecationNotice(): void { + console.error(`\n${DEPRECATION_NOTICE}\n`); +} + type Args = { command: string; args: string[]; @@ -378,6 +392,8 @@ async function main(): Promise { handleError(error); }); + printDeprecationNotice(); + try { const args = parseArgs(); diff --git a/client/README.md b/client/README.md index 780c92d8b..2516eb395 100644 --- a/client/README.md +++ b/client/README.md @@ -1,3 +1,13 @@ +> # ⚠️ Deprecated +> +> **`@modelcontextprotocol/inspector` v1 is deprecated.** +> +> ``` +> npx @modelcontextprotocol/inspector@latest +> ``` +> +> v1 receives **security fixes only**. All new development happens in v2. + # React + TypeScript + Vite This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. diff --git a/client/bin/start.js b/client/bin/start.js index 7e7e013af..15ebd358a 100755 --- a/client/bin/start.js +++ b/client/bin/start.js @@ -167,6 +167,11 @@ async function startDevClient(clientOptions) { // Give vite time to start before opening or logging the URL setTimeout(() => { + console.log( + `\n⚠️ @modelcontextprotocol/inspector v1 is deprecated.\n` + + ` Upgrade: npx @modelcontextprotocol/inspector@latest\n` + + ` v1 receives security fixes only.`, + ); console.log(`\n🚀 MCP Inspector is up and running at:\n ${url}\n`); if (process.env.MCP_AUTO_OPEN_ENABLED !== "false") { console.log("🌐 Opening browser..."); diff --git a/client/package.json b/client/package.json index fc0d9194d..a4af09c6b 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "1.0.0", + "version": "1.0.1", "description": "Client-side application for the Model Context Protocol inspector", "license": "SEE LICENSE IN LICENSE", "author": "Model Context Protocol a Series of LF Projects, LLC.", diff --git a/client/src/App.tsx b/client/src/App.tsx index 7cf6d751a..ffc95a30d 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -69,6 +69,7 @@ import { z } from "zod"; import "./App.css"; import AuthDebugger from "./components/AuthDebugger"; import ConsoleTab from "./components/ConsoleTab"; +import DeprecationBanner from "./components/DeprecationBanner"; import HistoryAndNotifications from "./components/HistoryAndNotifications"; import PingTab from "./components/PingTab"; import PromptsTab, { Prompt } from "./components/PromptsTab"; @@ -1367,429 +1368,434 @@ const App = () => { } return ( -
-
- +
+ +
-
-
-
- {mcpClient ? ( - { - setActiveTab(value); - window.location.hash = value; - }} - > - - - - Resources - - - - Prompts - - - - Tools - - - - Tasks - - - - Apps - - - - Ping - - - - Sampling - {pendingSampleRequests.length > 0 && ( - - {pendingSampleRequests.length} - - )} - - - - Elicitations - {pendingElicitationRequests.length > 0 && ( - - {pendingElicitationRequests.length} - - )} - - - - Roots - - - - Auth - - - - Metadata - - - -
- {!serverCapabilities?.resources && - !serverCapabilities?.prompts && - !serverCapabilities?.tools ? ( - <> -
-

- The connected server does not support any MCP - capabilities -

-
- { - void sendMCPRequest( - { - method: "ping" as const, - }, - EmptyResultSchema, - ); - }} - /> - - ) : ( - <> - { - clearError("resources"); - listResources(); - }} - clearResources={() => { - setResources([]); - setNextResourceCursor(undefined); - }} - listResourceTemplates={() => { - clearError("resources"); - listResourceTemplates(); - }} - clearResourceTemplates={() => { - setResourceTemplates([]); - setNextResourceTemplateCursor(undefined); - }} - readResource={(uri, options) => { - clearError("resources"); - readResource(uri, options); - }} - selectedResource={selectedResource} - setSelectedResource={(resource) => { - clearError("resources"); - setSelectedResource(resource); - }} - resourceSubscriptionsSupported={ - serverCapabilities?.resources?.subscribe || false - } - resourceSubscriptions={resourceSubscriptions} - subscribeToResource={(uri) => { - clearError("resources"); - subscribeToResource(uri); - }} - unsubscribeFromResource={(uri) => { - clearError("resources"); - unsubscribeFromResource(uri); - }} - handleCompletion={handleCompletion} - completionsSupported={completionsSupported} - resourceContent={resourceContent} - nextCursor={nextResourceCursor} - nextTemplateCursor={nextResourceTemplateCursor} - error={errors.resources} - /> - { - clearError("prompts"); - listPrompts(); - }} - clearPrompts={() => { - setPrompts([]); - setNextPromptCursor(undefined); - }} - getPrompt={(name, args) => { - clearError("prompts"); - getPrompt(name, args); - }} - selectedPrompt={selectedPrompt} - setSelectedPrompt={(prompt) => { - clearError("prompts"); - setSelectedPrompt(prompt); - setPromptContent(""); - }} - handleCompletion={handleCompletion} - completionsSupported={completionsSupported} - promptContent={promptContent} - nextCursor={nextPromptCursor} - error={errors.prompts} - /> - { - clearError("tools"); - listTools(); - }} - clearTools={() => { - setTools([]); - setNextToolCursor(undefined); - cacheToolOutputSchemas([]); - }} - callTool={async ( - name: string, - params: Record, - metadata?: Record, - runAsTask?: boolean, - ) => { - clearError("tools"); - setToolResult(null); - const result = await callTool( - name, - params, - metadata, - runAsTask, - ); - const calledTool = tools.find( - (tool) => tool.name === name, - ); - if (calledTool && hasAppResourceUri(calledTool)) { - setPrefilledAppsToolCall({ - id: ++prefilledAppsToolCallIdRef.current, - toolName: name, - params: cloneToolParams(params), - result, - }); - } else { - setPrefilledAppsToolCall(null); + className="bg-card border-r border-border flex flex-col h-full relative" + > + +
+
+
+
+ {mcpClient ? ( + { + setActiveTab(value); + window.location.hash = value; + }} + > + + + + Resources + + + + Prompts + + + + Tools + + + + Tasks + + + + Apps + + + + Ping + + + + Sampling + {pendingSampleRequests.length > 0 && ( + + {pendingSampleRequests.length} + + )} + + + + Elicitations + {pendingElicitationRequests.length > 0 && ( + + {pendingElicitationRequests.length} + + )} + + + + Roots + + + + Auth + + + + Metadata + + + +
+ {!serverCapabilities?.resources && + !serverCapabilities?.prompts && + !serverCapabilities?.tools ? ( + <> +
+

+ The connected server does not support any MCP + capabilities +

+
+ { + void sendMCPRequest( + { + method: "ping" as const, + }, + EmptyResultSchema, + ); + }} + /> + + ) : ( + <> + { + clearError("resources"); + listResources(); + }} + clearResources={() => { + setResources([]); + setNextResourceCursor(undefined); + }} + listResourceTemplates={() => { + clearError("resources"); + listResourceTemplates(); + }} + clearResourceTemplates={() => { + setResourceTemplates([]); + setNextResourceTemplateCursor(undefined); + }} + readResource={(uri, options) => { + clearError("resources"); + readResource(uri, options); + }} + selectedResource={selectedResource} + setSelectedResource={(resource) => { + clearError("resources"); + setSelectedResource(resource); + }} + resourceSubscriptionsSupported={ + serverCapabilities?.resources?.subscribe || false } - return result; - }} - selectedTool={selectedTool} - setSelectedTool={(tool) => { - clearError("tools"); - setSelectedTool(tool); - setToolResult(null); - }} - toolResult={toolResult} - isPollingTask={isPollingTask} - nextCursor={nextToolCursor} - error={errors.tools} - resourceContent={resourceContentMap} - resourceError={resourceErrorMap} - onReadResource={(uri: string) => { - clearError("resources"); - readResource(uri); - }} - /> - { - clearError("tasks"); - listTasks(); - }} - clearTasks={() => { - setTasks([]); - setNextTaskCursor(undefined); - }} - cancelTask={cancelTask} - selectedTask={selectedTask} - setSelectedTask={(task) => { - clearError("tasks"); - setSelectedTask(task); - }} - error={errors.tasks} - nextCursor={nextTaskCursor} - /> - { - clearError("tools"); - listTools(); - }} - callTool={async ( - name: string, - params: Record, - metadata?: Record, - runAsTask?: boolean, - ) => { - clearError("tools"); - setToolResult(null); - return callTool(name, params, metadata, runAsTask); - }} - prefilledToolCall={prefilledAppsToolCall} - onPrefilledToolCallConsumed={(callId) => { - setPrefilledAppsToolCall((prev) => - prev?.id === callId ? null : prev, - ); - }} - error={errors.tools} - mcpClient={mcpClient} - onNotification={(notification) => { - setNotifications((prev) => [...prev, notification]); - }} - /> - - { - void sendMCPRequest( - { - method: "ping" as const, - }, - EmptyResultSchema, - ); - }} - /> - - - - - - - )} -
-
- ) : isAuthDebuggerVisible ? ( - (window.location.hash = value)} - > - - - ) : ( -
-

- Connect to an MCP server to start inspecting -

-
-

- Need to configure authentication? + resourceSubscriptions={resourceSubscriptions} + subscribeToResource={(uri) => { + clearError("resources"); + subscribeToResource(uri); + }} + unsubscribeFromResource={(uri) => { + clearError("resources"); + unsubscribeFromResource(uri); + }} + handleCompletion={handleCompletion} + completionsSupported={completionsSupported} + resourceContent={resourceContent} + nextCursor={nextResourceCursor} + nextTemplateCursor={nextResourceTemplateCursor} + error={errors.resources} + /> + { + clearError("prompts"); + listPrompts(); + }} + clearPrompts={() => { + setPrompts([]); + setNextPromptCursor(undefined); + }} + getPrompt={(name, args) => { + clearError("prompts"); + getPrompt(name, args); + }} + selectedPrompt={selectedPrompt} + setSelectedPrompt={(prompt) => { + clearError("prompts"); + setSelectedPrompt(prompt); + setPromptContent(""); + }} + handleCompletion={handleCompletion} + completionsSupported={completionsSupported} + promptContent={promptContent} + nextCursor={nextPromptCursor} + error={errors.prompts} + /> + { + clearError("tools"); + listTools(); + }} + clearTools={() => { + setTools([]); + setNextToolCursor(undefined); + cacheToolOutputSchemas([]); + }} + callTool={async ( + name: string, + params: Record, + metadata?: Record, + runAsTask?: boolean, + ) => { + clearError("tools"); + setToolResult(null); + const result = await callTool( + name, + params, + metadata, + runAsTask, + ); + const calledTool = tools.find( + (tool) => tool.name === name, + ); + if (calledTool && hasAppResourceUri(calledTool)) { + setPrefilledAppsToolCall({ + id: ++prefilledAppsToolCallIdRef.current, + toolName: name, + params: cloneToolParams(params), + result, + }); + } else { + setPrefilledAppsToolCall(null); + } + return result; + }} + selectedTool={selectedTool} + setSelectedTool={(tool) => { + clearError("tools"); + setSelectedTool(tool); + setToolResult(null); + }} + toolResult={toolResult} + isPollingTask={isPollingTask} + nextCursor={nextToolCursor} + error={errors.tools} + resourceContent={resourceContentMap} + resourceError={resourceErrorMap} + onReadResource={(uri: string) => { + clearError("resources"); + readResource(uri); + }} + /> + { + clearError("tasks"); + listTasks(); + }} + clearTasks={() => { + setTasks([]); + setNextTaskCursor(undefined); + }} + cancelTask={cancelTask} + selectedTask={selectedTask} + setSelectedTask={(task) => { + clearError("tasks"); + setSelectedTask(task); + }} + error={errors.tasks} + nextCursor={nextTaskCursor} + /> + { + clearError("tools"); + listTools(); + }} + callTool={async ( + name: string, + params: Record, + metadata?: Record, + runAsTask?: boolean, + ) => { + clearError("tools"); + setToolResult(null); + return callTool(name, params, metadata, runAsTask); + }} + prefilledToolCall={prefilledAppsToolCall} + onPrefilledToolCallConsumed={(callId) => { + setPrefilledAppsToolCall((prev) => + prev?.id === callId ? null : prev, + ); + }} + error={errors.tools} + mcpClient={mcpClient} + onNotification={(notification) => { + setNotifications((prev) => [...prev, notification]); + }} + /> + + { + void sendMCPRequest( + { + method: "ping" as const, + }, + EmptyResultSchema, + ); + }} + /> + + + + + + + )} +

+ + ) : isAuthDebuggerVisible ? ( + (window.location.hash = value)} + > + + + ) : ( +
+

+ Connect to an MCP server to start inspecting

- +
+

+ Need to configure authentication? +

+ +
-
- )} -
-
+ )} +
-
-
-
- +
+
+
+
+ +
diff --git a/client/src/components/DeprecationBanner.tsx b/client/src/components/DeprecationBanner.tsx new file mode 100644 index 000000000..c322b19df --- /dev/null +++ b/client/src/components/DeprecationBanner.tsx @@ -0,0 +1,62 @@ +import { useState } from "react"; +import { X } from "lucide-react"; + +const DISMISSED_KEY = "mcp-inspector-v1-deprecation-dismissed"; + +/** + * v1 deprecation notice shown at the top of the app. + * + * Dismissal is persisted in localStorage so it stays hidden across reloads. + * A storage failure (Safari private mode, disabled storage) must not break the + * app, so both reads and writes are guarded — the banner simply reappears. + */ +const DeprecationBanner = () => { + const [dismissed, setDismissed] = useState(() => { + try { + return localStorage.getItem(DISMISSED_KEY) === "true"; + } catch { + return false; + } + }); + + if (dismissed) { + return null; + } + + const handleDismiss = () => { + try { + localStorage.setItem(DISMISSED_KEY, "true"); + } catch { + // Ignore: dismissal just won't persist across reloads. + } + setDismissed(true); + }; + + return ( +
+

+ + ⚠️ MCP Inspector v1 is deprecated. + {" "} + Upgrade with{" "} + + npx @modelcontextprotocol/inspector@latest + + . v1 receives security fixes only. +

+ +
+ ); +}; + +export default DeprecationBanner; diff --git a/mcp-inspector.png b/mcp-inspector.png index 4217cf14d..1be86e61e 100644 Binary files a/mcp-inspector.png and b/mcp-inspector.png differ diff --git a/package-lock.json b/package-lock.json index d70bf35ae..4aa46af7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/inspector", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "1.0.0", + "version": "1.0.1", "license": "SEE LICENSE IN LICENSE", "workspaces": [ "client", @@ -14,9 +14,9 @@ "cli" ], "dependencies": { - "@modelcontextprotocol/inspector-cli": "^1.0.0", - "@modelcontextprotocol/inspector-client": "^1.0.0", - "@modelcontextprotocol/inspector-server": "^1.0.0", + "@modelcontextprotocol/inspector-cli": "^1.0.1", + "@modelcontextprotocol/inspector-client": "^1.0.1", + "@modelcontextprotocol/inspector-server": "^1.0.1", "@modelcontextprotocol/sdk": "^1.25.2", "concurrently": "^9.2.0", "node-fetch": "^3.3.2", @@ -48,7 +48,7 @@ }, "cli": { "name": "@modelcontextprotocol/inspector-cli", - "version": "1.0.0", + "version": "1.0.1", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@modelcontextprotocol/sdk": "^1.25.2", @@ -74,7 +74,7 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "1.0.0", + "version": "1.0.1", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@mcp-ui/client": "^6.0.0", @@ -13254,7 +13254,7 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "1.0.0", + "version": "1.0.1", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@modelcontextprotocol/sdk": "^1.25.2", diff --git a/package.json b/package.json index 42d3fb904..722f4e3dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "1.0.0", + "version": "1.0.1", "description": "Model Context Protocol inspector", "license": "SEE LICENSE IN LICENSE", "author": "Model Context Protocol a Series of LF Projects, LLC.", @@ -52,9 +52,9 @@ "check-version": "node scripts/check-version-consistency.js" }, "dependencies": { - "@modelcontextprotocol/inspector-cli": "^1.0.0", - "@modelcontextprotocol/inspector-client": "^1.0.0", - "@modelcontextprotocol/inspector-server": "^1.0.0", + "@modelcontextprotocol/inspector-cli": "^1.0.1", + "@modelcontextprotocol/inspector-client": "^1.0.1", + "@modelcontextprotocol/inspector-server": "^1.0.1", "@modelcontextprotocol/sdk": "^1.25.2", "concurrently": "^9.2.0", "node-fetch": "^3.3.2", diff --git a/server/package.json b/server/package.json index 8137a69f7..08dc4baf9 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "1.0.0", + "version": "1.0.1", "description": "Server-side application for the Model Context Protocol inspector", "license": "SEE LICENSE IN LICENSE", "author": "Model Context Protocol a Series of LF Projects, LLC.", diff --git a/server/src/index.ts b/server/src/index.ts index 4c86bd791..7d9d36aee 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1088,6 +1088,11 @@ const HOST = process.env.HOST || "localhost"; const server = app.listen(PORT, HOST); server.on("listening", () => { + console.log( + `\n⚠️ @modelcontextprotocol/inspector v1 is deprecated.\n` + + ` Upgrade: npx @modelcontextprotocol/inspector@latest\n` + + ` v1 receives security fixes only.\n`, + ); console.log(`⚙️ Proxy server listening on ${HOST}:${PORT}`); if (!authDisabled) { console.log(