Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d74acf526 |
||
|
|
3188ee3a20 |
15 changed files with 557 additions and 465 deletions
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
|
|
@ -2,7 +2,7 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
- v1/main
|
||||
pull_request:
|
||||
release:
|
||||
types: [published]
|
||||
|
|
|
|||
44
AGENTS.md
44
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.
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -2,7 +2,17 @@
|
|||
|
||||
The MCP inspector is a developer tool for testing and debugging MCP servers.
|
||||
|
||||

|
||||
> # ⚠️ Deprecated
|
||||
>
|
||||
> **`@modelcontextprotocol/inspector` v1 is deprecated.**
|
||||
>
|
||||
> ```
|
||||
> npx @modelcontextprotocol/inspector@latest
|
||||
> ```
|
||||
>
|
||||
> v1 receives **security fixes only**. All new development happens in v2.
|
||||
|
||||

|
||||
|
||||
## Architecture Overview
|
||||
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
|||
handleError(error);
|
||||
});
|
||||
|
||||
printDeprecationNotice();
|
||||
|
||||
try {
|
||||
const args = parseArgs();
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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...");
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex h-screen bg-background">
|
||||
<div
|
||||
style={{
|
||||
width: sidebarWidth,
|
||||
minWidth: 200,
|
||||
maxWidth: 600,
|
||||
transition: isSidebarDragging ? "none" : "width 0.15s",
|
||||
}}
|
||||
className="bg-card border-r border-border flex flex-col h-full relative"
|
||||
>
|
||||
<Sidebar
|
||||
connectionStatus={connectionStatus}
|
||||
transportType={transportType}
|
||||
setTransportType={setTransportType}
|
||||
command={command}
|
||||
setCommand={setCommand}
|
||||
args={args}
|
||||
setArgs={setArgs}
|
||||
sseUrl={sseUrl}
|
||||
setSseUrl={setSseUrl}
|
||||
env={env}
|
||||
setEnv={setEnv}
|
||||
config={config}
|
||||
setConfig={setConfig}
|
||||
customHeaders={customHeaders}
|
||||
setCustomHeaders={setCustomHeaders}
|
||||
oauthClientId={oauthClientId}
|
||||
setOauthClientId={setOauthClientId}
|
||||
oauthClientSecret={oauthClientSecret}
|
||||
setOauthClientSecret={setOauthClientSecret}
|
||||
oauthScope={oauthScope}
|
||||
setOauthScope={setOauthScope}
|
||||
onConnect={connectMcpServer}
|
||||
onDisconnect={disconnectMcpServer}
|
||||
logLevel={logLevel}
|
||||
sendLogLevelRequest={sendLogLevelRequest}
|
||||
loggingSupported={!!serverCapabilities?.logging || false}
|
||||
connectionType={connectionType}
|
||||
setConnectionType={setConnectionType}
|
||||
serverImplementation={serverImplementation}
|
||||
/>
|
||||
<div className="flex flex-col h-screen bg-background">
|
||||
<DeprecationBanner />
|
||||
<div className="flex flex-1 min-h-0">
|
||||
<div
|
||||
onMouseDown={handleSidebarDragStart}
|
||||
style={{
|
||||
cursor: "col-resize",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
width: 6,
|
||||
height: "100%",
|
||||
zIndex: 10,
|
||||
background: isSidebarDragging ? "rgba(0,0,0,0.08)" : "transparent",
|
||||
}}
|
||||
aria-label="Resize sidebar"
|
||||
data-testid="sidebar-drag-handle"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<div className="flex-1 overflow-auto">
|
||||
{mcpClient ? (
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
className="w-full p-4"
|
||||
onValueChange={(value) => {
|
||||
setActiveTab(value);
|
||||
window.location.hash = value;
|
||||
}}
|
||||
>
|
||||
<TabsList className="mb-4 py-0">
|
||||
<TabsTrigger
|
||||
value="resources"
|
||||
disabled={!serverCapabilities?.resources}
|
||||
>
|
||||
<Files className="w-4 h-4 mr-2" />
|
||||
Resources
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="prompts"
|
||||
disabled={!serverCapabilities?.prompts}
|
||||
>
|
||||
<MessageSquare className="w-4 h-4 mr-2" />
|
||||
Prompts
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="tools"
|
||||
disabled={!serverCapabilities?.tools}
|
||||
>
|
||||
<Hammer className="w-4 h-4 mr-2" />
|
||||
Tools
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="tasks"
|
||||
disabled={!serverCapabilities?.tasks}
|
||||
>
|
||||
<ListTodo className="w-4 h-4 mr-2" />
|
||||
Tasks
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="apps">
|
||||
<AppWindow className="w-4 h-4 mr-2" />
|
||||
Apps
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="ping">
|
||||
<Bell className="w-4 h-4 mr-2" />
|
||||
Ping
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="sampling" className="relative">
|
||||
<Hash className="w-4 h-4 mr-2" />
|
||||
Sampling
|
||||
{pendingSampleRequests.length > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
|
||||
{pendingSampleRequests.length}
|
||||
</span>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="elicitations" className="relative">
|
||||
<MessageSquare className="w-4 h-4 mr-2" />
|
||||
Elicitations
|
||||
{pendingElicitationRequests.length > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
|
||||
{pendingElicitationRequests.length}
|
||||
</span>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="roots">
|
||||
<FolderTree className="w-4 h-4 mr-2" />
|
||||
Roots
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="auth">
|
||||
<Key className="w-4 h-4 mr-2" />
|
||||
Auth
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="metadata">
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
Metadata
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="w-full">
|
||||
{!serverCapabilities?.resources &&
|
||||
!serverCapabilities?.prompts &&
|
||||
!serverCapabilities?.tools ? (
|
||||
<>
|
||||
<div className="flex items-center justify-center p-4">
|
||||
<p className="text-lg text-gray-500 dark:text-gray-400">
|
||||
The connected server does not support any MCP
|
||||
capabilities
|
||||
</p>
|
||||
</div>
|
||||
<PingTab
|
||||
onPingClick={() => {
|
||||
void sendMCPRequest(
|
||||
{
|
||||
method: "ping" as const,
|
||||
},
|
||||
EmptyResultSchema,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ResourcesTab
|
||||
resources={resources}
|
||||
resourceTemplates={resourceTemplates}
|
||||
listResources={() => {
|
||||
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}
|
||||
/>
|
||||
<PromptsTab
|
||||
prompts={prompts}
|
||||
listPrompts={() => {
|
||||
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}
|
||||
/>
|
||||
<ToolsTab
|
||||
serverSupportsTaskRequests={
|
||||
!!serverCapabilities?.tasks?.requests?.tools?.call
|
||||
}
|
||||
tools={tools}
|
||||
listTools={() => {
|
||||
clearError("tools");
|
||||
listTools();
|
||||
}}
|
||||
clearTools={() => {
|
||||
setTools([]);
|
||||
setNextToolCursor(undefined);
|
||||
cacheToolOutputSchemas([]);
|
||||
}}
|
||||
callTool={async (
|
||||
name: string,
|
||||
params: Record<string, unknown>,
|
||||
metadata?: Record<string, unknown>,
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
<TasksTab
|
||||
tasks={tasks}
|
||||
listTasks={() => {
|
||||
clearError("tasks");
|
||||
listTasks();
|
||||
}}
|
||||
clearTasks={() => {
|
||||
setTasks([]);
|
||||
setNextTaskCursor(undefined);
|
||||
}}
|
||||
cancelTask={cancelTask}
|
||||
selectedTask={selectedTask}
|
||||
setSelectedTask={(task) => {
|
||||
clearError("tasks");
|
||||
setSelectedTask(task);
|
||||
}}
|
||||
error={errors.tasks}
|
||||
nextCursor={nextTaskCursor}
|
||||
/>
|
||||
<AppsTab
|
||||
sandboxPath={`${getMCPProxyAddress(config)}/sandbox`}
|
||||
tools={tools}
|
||||
listTools={() => {
|
||||
clearError("tools");
|
||||
listTools();
|
||||
}}
|
||||
callTool={async (
|
||||
name: string,
|
||||
params: Record<string, unknown>,
|
||||
metadata?: Record<string, unknown>,
|
||||
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]);
|
||||
}}
|
||||
/>
|
||||
<ConsoleTab />
|
||||
<PingTab
|
||||
onPingClick={() => {
|
||||
void sendMCPRequest(
|
||||
{
|
||||
method: "ping" as const,
|
||||
},
|
||||
EmptyResultSchema,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<SamplingTab
|
||||
pendingRequests={pendingSampleRequests}
|
||||
onApprove={handleApproveSampling}
|
||||
onReject={handleRejectSampling}
|
||||
/>
|
||||
<ElicitationTab
|
||||
pendingRequests={pendingElicitationRequests}
|
||||
onResolve={handleResolveElicitation}
|
||||
/>
|
||||
<RootsTab
|
||||
roots={roots}
|
||||
setRoots={setRoots}
|
||||
onRootsChange={handleRootsChange}
|
||||
/>
|
||||
<AuthDebuggerWrapper />
|
||||
<MetadataTab
|
||||
metadata={metadata}
|
||||
onMetadataChange={handleMetadataChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Tabs>
|
||||
) : isAuthDebuggerVisible ? (
|
||||
<Tabs
|
||||
defaultValue={"auth"}
|
||||
className="w-full p-4"
|
||||
onValueChange={(value) => (window.location.hash = value)}
|
||||
>
|
||||
<AuthDebuggerWrapper />
|
||||
</Tabs>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-full gap-4">
|
||||
<p className="text-lg text-gray-500 dark:text-gray-400">
|
||||
Connect to an MCP server to start inspecting
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Need to configure authentication?
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setIsAuthDebuggerVisible(true)}
|
||||
>
|
||||
Open Auth Settings
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="relative border-t border-border"
|
||||
style={{
|
||||
height: `${historyPaneHeight}px`,
|
||||
width: sidebarWidth,
|
||||
minWidth: 200,
|
||||
maxWidth: 600,
|
||||
transition: isSidebarDragging ? "none" : "width 0.15s",
|
||||
}}
|
||||
className="bg-card border-r border-border flex flex-col h-full relative"
|
||||
>
|
||||
<Sidebar
|
||||
connectionStatus={connectionStatus}
|
||||
transportType={transportType}
|
||||
setTransportType={setTransportType}
|
||||
command={command}
|
||||
setCommand={setCommand}
|
||||
args={args}
|
||||
setArgs={setArgs}
|
||||
sseUrl={sseUrl}
|
||||
setSseUrl={setSseUrl}
|
||||
env={env}
|
||||
setEnv={setEnv}
|
||||
config={config}
|
||||
setConfig={setConfig}
|
||||
customHeaders={customHeaders}
|
||||
setCustomHeaders={setCustomHeaders}
|
||||
oauthClientId={oauthClientId}
|
||||
setOauthClientId={setOauthClientId}
|
||||
oauthClientSecret={oauthClientSecret}
|
||||
setOauthClientSecret={setOauthClientSecret}
|
||||
oauthScope={oauthScope}
|
||||
setOauthScope={setOauthScope}
|
||||
onConnect={connectMcpServer}
|
||||
onDisconnect={disconnectMcpServer}
|
||||
logLevel={logLevel}
|
||||
sendLogLevelRequest={sendLogLevelRequest}
|
||||
loggingSupported={!!serverCapabilities?.logging || false}
|
||||
connectionType={connectionType}
|
||||
setConnectionType={setConnectionType}
|
||||
serverImplementation={serverImplementation}
|
||||
/>
|
||||
<div
|
||||
className="absolute w-full h-4 -top-2 cursor-row-resize flex items-center justify-center hover:bg-accent/50 dark:hover:bg-input/40"
|
||||
onMouseDown={handleDragStart}
|
||||
>
|
||||
<div className="w-8 h-1 rounded-full bg-border" />
|
||||
onMouseDown={handleSidebarDragStart}
|
||||
style={{
|
||||
cursor: "col-resize",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
width: 6,
|
||||
height: "100%",
|
||||
zIndex: 10,
|
||||
background: isSidebarDragging
|
||||
? "rgba(0,0,0,0.08)"
|
||||
: "transparent",
|
||||
}}
|
||||
aria-label="Resize sidebar"
|
||||
data-testid="sidebar-drag-handle"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<div className="flex-1 overflow-auto">
|
||||
{mcpClient ? (
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
className="w-full p-4"
|
||||
onValueChange={(value) => {
|
||||
setActiveTab(value);
|
||||
window.location.hash = value;
|
||||
}}
|
||||
>
|
||||
<TabsList className="mb-4 py-0">
|
||||
<TabsTrigger
|
||||
value="resources"
|
||||
disabled={!serverCapabilities?.resources}
|
||||
>
|
||||
<Files className="w-4 h-4 mr-2" />
|
||||
Resources
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="prompts"
|
||||
disabled={!serverCapabilities?.prompts}
|
||||
>
|
||||
<MessageSquare className="w-4 h-4 mr-2" />
|
||||
Prompts
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="tools"
|
||||
disabled={!serverCapabilities?.tools}
|
||||
>
|
||||
<Hammer className="w-4 h-4 mr-2" />
|
||||
Tools
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="tasks"
|
||||
disabled={!serverCapabilities?.tasks}
|
||||
>
|
||||
<ListTodo className="w-4 h-4 mr-2" />
|
||||
Tasks
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="apps">
|
||||
<AppWindow className="w-4 h-4 mr-2" />
|
||||
Apps
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="ping">
|
||||
<Bell className="w-4 h-4 mr-2" />
|
||||
Ping
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="sampling" className="relative">
|
||||
<Hash className="w-4 h-4 mr-2" />
|
||||
Sampling
|
||||
{pendingSampleRequests.length > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
|
||||
{pendingSampleRequests.length}
|
||||
</span>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="elicitations" className="relative">
|
||||
<MessageSquare className="w-4 h-4 mr-2" />
|
||||
Elicitations
|
||||
{pendingElicitationRequests.length > 0 && (
|
||||
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
|
||||
{pendingElicitationRequests.length}
|
||||
</span>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="roots">
|
||||
<FolderTree className="w-4 h-4 mr-2" />
|
||||
Roots
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="auth">
|
||||
<Key className="w-4 h-4 mr-2" />
|
||||
Auth
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="metadata">
|
||||
<Settings className="w-4 h-4 mr-2" />
|
||||
Metadata
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="w-full">
|
||||
{!serverCapabilities?.resources &&
|
||||
!serverCapabilities?.prompts &&
|
||||
!serverCapabilities?.tools ? (
|
||||
<>
|
||||
<div className="flex items-center justify-center p-4">
|
||||
<p className="text-lg text-gray-500 dark:text-gray-400">
|
||||
The connected server does not support any MCP
|
||||
capabilities
|
||||
</p>
|
||||
</div>
|
||||
<PingTab
|
||||
onPingClick={() => {
|
||||
void sendMCPRequest(
|
||||
{
|
||||
method: "ping" as const,
|
||||
},
|
||||
EmptyResultSchema,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ResourcesTab
|
||||
resources={resources}
|
||||
resourceTemplates={resourceTemplates}
|
||||
listResources={() => {
|
||||
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}
|
||||
/>
|
||||
<PromptsTab
|
||||
prompts={prompts}
|
||||
listPrompts={() => {
|
||||
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}
|
||||
/>
|
||||
<ToolsTab
|
||||
serverSupportsTaskRequests={
|
||||
!!serverCapabilities?.tasks?.requests?.tools?.call
|
||||
}
|
||||
tools={tools}
|
||||
listTools={() => {
|
||||
clearError("tools");
|
||||
listTools();
|
||||
}}
|
||||
clearTools={() => {
|
||||
setTools([]);
|
||||
setNextToolCursor(undefined);
|
||||
cacheToolOutputSchemas([]);
|
||||
}}
|
||||
callTool={async (
|
||||
name: string,
|
||||
params: Record<string, unknown>,
|
||||
metadata?: Record<string, unknown>,
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
<TasksTab
|
||||
tasks={tasks}
|
||||
listTasks={() => {
|
||||
clearError("tasks");
|
||||
listTasks();
|
||||
}}
|
||||
clearTasks={() => {
|
||||
setTasks([]);
|
||||
setNextTaskCursor(undefined);
|
||||
}}
|
||||
cancelTask={cancelTask}
|
||||
selectedTask={selectedTask}
|
||||
setSelectedTask={(task) => {
|
||||
clearError("tasks");
|
||||
setSelectedTask(task);
|
||||
}}
|
||||
error={errors.tasks}
|
||||
nextCursor={nextTaskCursor}
|
||||
/>
|
||||
<AppsTab
|
||||
sandboxPath={`${getMCPProxyAddress(config)}/sandbox`}
|
||||
tools={tools}
|
||||
listTools={() => {
|
||||
clearError("tools");
|
||||
listTools();
|
||||
}}
|
||||
callTool={async (
|
||||
name: string,
|
||||
params: Record<string, unknown>,
|
||||
metadata?: Record<string, unknown>,
|
||||
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]);
|
||||
}}
|
||||
/>
|
||||
<ConsoleTab />
|
||||
<PingTab
|
||||
onPingClick={() => {
|
||||
void sendMCPRequest(
|
||||
{
|
||||
method: "ping" as const,
|
||||
},
|
||||
EmptyResultSchema,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<SamplingTab
|
||||
pendingRequests={pendingSampleRequests}
|
||||
onApprove={handleApproveSampling}
|
||||
onReject={handleRejectSampling}
|
||||
/>
|
||||
<ElicitationTab
|
||||
pendingRequests={pendingElicitationRequests}
|
||||
onResolve={handleResolveElicitation}
|
||||
/>
|
||||
<RootsTab
|
||||
roots={roots}
|
||||
setRoots={setRoots}
|
||||
onRootsChange={handleRootsChange}
|
||||
/>
|
||||
<AuthDebuggerWrapper />
|
||||
<MetadataTab
|
||||
metadata={metadata}
|
||||
onMetadataChange={handleMetadataChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Tabs>
|
||||
) : isAuthDebuggerVisible ? (
|
||||
<Tabs
|
||||
defaultValue={"auth"}
|
||||
className="w-full p-4"
|
||||
onValueChange={(value) => (window.location.hash = value)}
|
||||
>
|
||||
<AuthDebuggerWrapper />
|
||||
</Tabs>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-full gap-4">
|
||||
<p className="text-lg text-gray-500 dark:text-gray-400">
|
||||
Connect to an MCP server to start inspecting
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Need to configure authentication?
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setIsAuthDebuggerVisible(true)}
|
||||
>
|
||||
Open Auth Settings
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-full overflow-auto">
|
||||
<HistoryAndNotifications
|
||||
requestHistory={requestHistory}
|
||||
serverNotifications={notifications}
|
||||
onClearHistory={clearRequestHistory}
|
||||
onClearNotifications={handleClearNotifications}
|
||||
/>
|
||||
<div
|
||||
className="relative border-t border-border"
|
||||
style={{
|
||||
height: `${historyPaneHeight}px`,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute w-full h-4 -top-2 cursor-row-resize flex items-center justify-center hover:bg-accent/50 dark:hover:bg-input/40"
|
||||
onMouseDown={handleDragStart}
|
||||
>
|
||||
<div className="w-8 h-1 rounded-full bg-border" />
|
||||
</div>
|
||||
<div className="h-full overflow-auto">
|
||||
<HistoryAndNotifications
|
||||
requestHistory={requestHistory}
|
||||
serverNotifications={notifications}
|
||||
onClearHistory={clearRequestHistory}
|
||||
onClearNotifications={handleClearNotifications}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
62
client/src/components/DeprecationBanner.tsx
Normal file
62
client/src/components/DeprecationBanner.tsx
Normal file
|
|
@ -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 (
|
||||
<div
|
||||
role="status"
|
||||
className="flex items-center justify-between gap-4 px-4 py-2 bg-yellow-100 text-yellow-900 border-b border-yellow-300 dark:bg-yellow-900/40 dark:text-yellow-100 dark:border-yellow-700"
|
||||
>
|
||||
<p className="text-sm">
|
||||
<span className="font-semibold">
|
||||
⚠️ MCP Inspector v1 is deprecated.
|
||||
</span>{" "}
|
||||
Upgrade with{" "}
|
||||
<code className="px-1 py-0.5 rounded bg-yellow-200 dark:bg-yellow-800">
|
||||
npx @modelcontextprotocol/inspector@latest
|
||||
</code>
|
||||
. v1 receives security fixes only.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDismiss}
|
||||
aria-label="Dismiss deprecation notice"
|
||||
className="shrink-0 p-1 rounded hover:bg-yellow-200 dark:hover:bg-yellow-800"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeprecationBanner;
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 418 KiB After Width: | Height: | Size: 142 KiB |
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue