Compare commits

..

1 Commits

Author SHA1 Message Date
David Soria Parra
0b3f967867 refactor: remove hardcoded example server path
Remove hardcoded path to example server from default args state, replacing with empty string. This makes the initial state more generic and allows users to specify their own server path without preset assumptions.
2024-11-04 21:29:17 +00:00
26 changed files with 262 additions and 806 deletions

View File

@@ -17,6 +17,20 @@ jobs:
node-version: 18 node-version: 18
cache: npm cache: npm
- uses: actions/checkout@v4
with:
repository: modelcontextprotocol/typescript-sdk
ssh-key: ${{ secrets.TYPESCRIPT_SDK_KEY }}
path: packages/@modelcontextprotocol/sdk
- run: npm ci
working-directory: packages/@modelcontextprotocol/sdk
- run: npm pack
working-directory: packages/@modelcontextprotocol/sdk
- run: npm install --save packages/@modelcontextprotocol/sdk/modelcontextprotocol-sdk-*.tgz
# Working around https://github.com/npm/cli/issues/4828 # Working around https://github.com/npm/cli/issues/4828
# - run: npm ci # - run: npm ci
- run: npm install --no-package-lock - run: npm install --no-package-lock

3
.npmrc
View File

@@ -1,2 +1 @@
registry="https://registry.npmjs.org/" registry = "https://registry.npmjs.org/"
@modelcontextprotocol:registry="https://registry.npmjs.org/"

View File

@@ -1,7 +0,0 @@
Copyright (c) 2024 Anthropic, PBC.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,36 +0,0 @@
#!/usr/bin/env node
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import concurrently from "concurrently";
const __dirname = dirname(fileURLToPath(import.meta.url));
// Paths to the server and client entry points
const serverPath = join(__dirname, "../server/build/index.js");
const clientPath = join(__dirname, "../client/bin/cli.js");
console.log("Starting MCP inspector...");
const { result } = concurrently(
[
{
command: `node ${serverPath}`,
name: "server",
},
{
command: `node ${clientPath}`,
name: "client",
},
],
{
prefix: "name",
killOthers: ["failure", "success"],
restartTries: 3,
},
);
result.catch((err) => {
console.error("An error occurred:", err);
process.exit(1);
});

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env node
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import handler from "serve-handler";
import http from "http";
const __dirname = dirname(fileURLToPath(import.meta.url));
const distPath = join(__dirname, "../dist");
const server = http.createServer((request, response) => {
return handler(request, response, { public: distPath });
});
const port = process.env.PORT || 5173;
server.listen(port, () => {
console.log(`MCP inspector client running at http://localhost:${port}`);
});

View File

@@ -1,20 +1,8 @@
{ {
"name": "@modelcontextprotocol/inspector-client", "name": "client",
"version": "0.1.0",
"private": true, "private": true,
"description": "Client-side application for the Model Context Protocol inspector", "version": "0.0.0",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
"homepage": "https://modelcontextprotocol.github.io",
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
"type": "module", "type": "module",
"bin": {
"mcp-inspector-client": "./bin/cli.js"
},
"files": [
"bin",
"dist"
],
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc -b && vite build", "build": "tsc -b && vite build",
@@ -33,7 +21,6 @@
"lucide-react": "^0.447.0", "lucide-react": "^0.447.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"serve-handler": "^6.1.6",
"tailwind-merge": "^2.5.3", "tailwind-merge": "^2.5.3",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8" "zod": "^3.23.8"
@@ -43,7 +30,6 @@
"@types/node": "^22.7.5", "@types/node": "^22.7.5",
"@types/react": "^18.3.10", "@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/serve-handler": "^6.1.4",
"@vitejs/plugin-react": "^4.3.2", "@vitejs/plugin-react": "^4.3.2",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.11.1", "eslint": "^9.11.1",

View File

@@ -3,4 +3,4 @@ export default {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
}; }

View File

@@ -1,7 +1,7 @@
import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { import {
CompatibilityCallToolResultSchema, CallToolResultSchema,
ClientRequest, ClientRequest,
CreateMessageRequestSchema, CreateMessageRequestSchema,
CreateMessageResult, CreateMessageResult,
@@ -9,18 +9,12 @@ import {
GetPromptResultSchema, GetPromptResultSchema,
ListPromptsResultSchema, ListPromptsResultSchema,
ListResourcesResultSchema, ListResourcesResultSchema,
ListResourceTemplatesResultSchema,
ListRootsRequestSchema,
ListToolsResultSchema, ListToolsResultSchema,
ProgressNotificationSchema, ProgressNotificationSchema,
ReadResourceResultSchema, ReadResourceResultSchema,
Resource, Resource,
ResourceTemplate,
Root,
ServerNotification, ServerNotification,
Tool, Tool,
CompatibilityCallToolResult,
ClientNotification,
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
@@ -43,12 +37,9 @@ import {
Play, Play,
Send, Send,
Terminal, Terminal,
FolderTree,
ChevronDown,
ChevronRight,
} from "lucide-react"; } from "lucide-react";
import { ZodType } from "zod"; import { AnyZodObject } from "zod";
import "./App.css"; import "./App.css";
import ConsoleTab from "./components/ConsoleTab"; import ConsoleTab from "./components/ConsoleTab";
import HistoryAndNotifications from "./components/History"; import HistoryAndNotifications from "./components/History";
@@ -56,7 +47,6 @@ import PingTab from "./components/PingTab";
import PromptsTab, { Prompt } from "./components/PromptsTab"; import PromptsTab, { Prompt } from "./components/PromptsTab";
import RequestsTab from "./components/RequestsTabs"; import RequestsTab from "./components/RequestsTabs";
import ResourcesTab from "./components/ResourcesTab"; import ResourcesTab from "./components/ResourcesTab";
import RootsTab from "./components/RootsTab";
import SamplingTab, { PendingRequest } from "./components/SamplingTab"; import SamplingTab, { PendingRequest } from "./components/SamplingTab";
import Sidebar from "./components/Sidebar"; import Sidebar from "./components/Sidebar";
import ToolsTab from "./components/ToolsTab"; import ToolsTab from "./components/ToolsTab";
@@ -66,15 +56,11 @@ const App = () => {
"disconnected" | "connected" | "error" "disconnected" | "connected" | "error"
>("disconnected"); >("disconnected");
const [resources, setResources] = useState<Resource[]>([]); const [resources, setResources] = useState<Resource[]>([]);
const [resourceTemplates, setResourceTemplates] = useState<
ResourceTemplate[]
>([]);
const [resourceContent, setResourceContent] = useState<string>(""); const [resourceContent, setResourceContent] = useState<string>("");
const [prompts, setPrompts] = useState<Prompt[]>([]); const [prompts, setPrompts] = useState<Prompt[]>([]);
const [promptContent, setPromptContent] = useState<string>(""); const [promptContent, setPromptContent] = useState<string>("");
const [tools, setTools] = useState<Tool[]>([]); const [tools, setTools] = useState<Tool[]>([]);
const [toolResult, setToolResult] = const [toolResult, setToolResult] = useState<string>("");
useState<CompatibilityCallToolResult | null>(null);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [command, setCommand] = useState<string>(() => { const [command, setCommand] = useState<string>(() => {
return ( return (
@@ -83,21 +69,15 @@ const App = () => {
); );
}); });
const [args, setArgs] = useState<string>(() => { const [args, setArgs] = useState<string>(() => {
return ( return localStorage.getItem("lastArgs") || "";
localStorage.getItem("lastArgs") ||
"/Users/ashwin/code/mcp/example-servers/build/everything/stdio.js"
);
}); });
const [url, setUrl] = useState<string>("http://localhost:3001/sse"); const [url, setUrl] = useState<string>("http://localhost:3001/sse");
const [transportType, setTransportType] = useState<"stdio" | "sse">("stdio"); const [transportType, setTransportType] = useState<"stdio" | "sse">("stdio");
const [requestHistory, setRequestHistory] = useState< const [requestHistory, setRequestHistory] = useState<
{ request: string; response?: string }[] { request: string; response: string }[]
>([]); >([]);
const [mcpClient, setMcpClient] = useState<Client | null>(null); const [mcpClient, setMcpClient] = useState<Client | null>(null);
const [notifications, setNotifications] = useState<ServerNotification[]>([]); const [notifications, setNotifications] = useState<ServerNotification[]>([]);
const [roots, setRoots] = useState<Root[]>([]);
const [env, setEnv] = useState<Record<string, string>>({});
const [showEnvVars, setShowEnvVars] = useState(false);
const [pendingSampleRequests, setPendingSampleRequests] = useState< const [pendingSampleRequests, setPendingSampleRequests] = useState<
Array< Array<
@@ -108,7 +88,6 @@ const App = () => {
> >
>([]); >([]);
const nextRequestId = useRef(0); const nextRequestId = useRef(0);
const rootsRef = useRef<Root[]>([]);
const handleApproveSampling = (id: number, result: CreateMessageResult) => { const handleApproveSampling = (id: number, result: CreateMessageResult) => {
setPendingSampleRequests((prev) => { setPendingSampleRequests((prev) => {
@@ -134,9 +113,6 @@ const App = () => {
const [nextResourceCursor, setNextResourceCursor] = useState< const [nextResourceCursor, setNextResourceCursor] = useState<
string | undefined string | undefined
>(); >();
const [nextResourceTemplateCursor, setNextResourceTemplateCursor] = useState<
string | undefined
>();
const [nextPromptCursor, setNextPromptCursor] = useState< const [nextPromptCursor, setNextPromptCursor] = useState<
string | undefined string | undefined
>(); >();
@@ -151,30 +127,14 @@ const App = () => {
localStorage.setItem("lastArgs", args); localStorage.setItem("lastArgs", args);
}, [args]); }, [args]);
useEffect(() => { const pushHistory = (request: object, response: object) => {
fetch("http://localhost:3000/default-environment")
.then((response) => response.json())
.then((data) => setEnv(data))
.catch((error) =>
console.error("Error fetching default environment:", error),
);
}, []);
useEffect(() => {
rootsRef.current = roots;
}, [roots]);
const pushHistory = (request: object, response?: object) => {
setRequestHistory((prev) => [ setRequestHistory((prev) => [
...prev, ...prev,
{ { request: JSON.stringify(request), response: JSON.stringify(response) },
request: JSON.stringify(request),
response: response !== undefined ? JSON.stringify(response) : undefined,
},
]); ]);
}; };
const makeRequest = async <T extends ZodType<object>>( const makeRequest = async <T extends AnyZodObject>(
request: ClientRequest, request: ClientRequest,
schema: T, schema: T,
) => { ) => {
@@ -192,20 +152,6 @@ const App = () => {
} }
}; };
const sendNotification = async (notification: ClientNotification) => {
if (!mcpClient) {
throw new Error("MCP client not connected");
}
try {
await mcpClient.notification(notification);
pushHistory(notification);
} catch (e: unknown) {
setError((e as Error).message);
throw e;
}
};
const listResources = async () => { const listResources = async () => {
const response = await makeRequest( const response = await makeRequest(
{ {
@@ -218,22 +164,6 @@ const App = () => {
setNextResourceCursor(response.nextCursor); setNextResourceCursor(response.nextCursor);
}; };
const listResourceTemplates = async () => {
const response = await makeRequest(
{
method: "resources/templates/list" as const,
params: nextResourceTemplateCursor
? { cursor: nextResourceTemplateCursor }
: {},
},
ListResourceTemplatesResultSchema,
);
setResourceTemplates(
resourceTemplates.concat(response.resourceTemplates ?? []),
);
setNextResourceTemplateCursor(response.nextCursor);
};
const readResource = async (uri: string) => { const readResource = async (uri: string) => {
const response = await makeRequest( const response = await makeRequest(
{ {
@@ -292,13 +222,9 @@ const App = () => {
}, },
}, },
}, },
CompatibilityCallToolResultSchema, CallToolResultSchema,
); );
setToolResult(response); setToolResult(JSON.stringify(response.toolResult, null, 2));
};
const handleRootsChange = async () => {
await sendNotification({ method: "notifications/roots/list_changed" });
}; };
const connectMcpServer = async () => { const connectMcpServer = async () => {
@@ -314,7 +240,6 @@ const App = () => {
if (transportType === "stdio") { if (transportType === "stdio") {
backendUrl.searchParams.append("command", command); backendUrl.searchParams.append("command", command);
backendUrl.searchParams.append("args", args); backendUrl.searchParams.append("args", args);
backendUrl.searchParams.append("env", JSON.stringify(env));
} else { } else {
backendUrl.searchParams.append("url", url); backendUrl.searchParams.append("url", url);
} }
@@ -341,10 +266,6 @@ const App = () => {
}); });
}); });
client.setRequestHandler(ListRootsRequestSchema, async () => {
return { roots: rootsRef.current };
});
setMcpClient(client); setMcpClient(client);
setConnectionStatus("connected"); setConnectionStatus("connected");
} catch (e) { } catch (e) {
@@ -402,66 +323,6 @@ const App = () => {
Connect Connect
</Button> </Button>
</div> </div>
{transportType === "stdio" && (
<div className="mt-4">
<Button
variant="outline"
onClick={() => setShowEnvVars(!showEnvVars)}
className="flex items-center"
>
{showEnvVars ? (
<ChevronDown className="w-4 h-4 mr-2" />
) : (
<ChevronRight className="w-4 h-4 mr-2" />
)}
Environment Variables
</Button>
{showEnvVars && (
<div className="mt-2">
{Object.entries(env).map(([key, value]) => (
<div key={key} className="flex space-x-2 mb-2">
<Input
placeholder="Key"
value={key}
onChange={(e) =>
setEnv((prev) => ({
...prev,
[e.target.value]: value,
}))
}
/>
<Input
placeholder="Value"
value={value}
onChange={(e) =>
setEnv((prev) => ({
...prev,
[key]: e.target.value,
}))
}
/>
<Button
onClick={() =>
setEnv((prev) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { [key]: _, ...rest } = prev;
return rest;
})
}
>
Remove
</Button>
</div>
))}
<Button
onClick={() => setEnv((prev) => ({ ...prev, "": "" }))}
>
Add Environment Variable
</Button>
</div>
)}
</div>
)}
</div> </div>
{mcpClient ? ( {mcpClient ? (
<Tabs defaultValue="resources" className="w-full p-4"> <Tabs defaultValue="resources" className="w-full p-4">
@@ -499,24 +360,17 @@ const App = () => {
</span> </span>
)} )}
</TabsTrigger> </TabsTrigger>
<TabsTrigger value="roots">
<FolderTree className="w-4 h-4 mr-2" />
Roots
</TabsTrigger>
</TabsList> </TabsList>
<div className="w-full"> <div className="w-full">
<ResourcesTab <ResourcesTab
resources={resources} resources={resources}
resourceTemplates={resourceTemplates}
listResources={listResources} listResources={listResources}
listResourceTemplates={listResourceTemplates}
readResource={readResource} readResource={readResource}
selectedResource={selectedResource} selectedResource={selectedResource}
setSelectedResource={setSelectedResource} setSelectedResource={setSelectedResource}
resourceContent={resourceContent} resourceContent={resourceContent}
nextCursor={nextResourceCursor} nextCursor={nextResourceCursor}
nextTemplateCursor={nextResourceTemplateCursor}
error={error} error={error}
/> />
<PromptsTab <PromptsTab
@@ -537,7 +391,7 @@ const App = () => {
selectedTool={selectedTool} selectedTool={selectedTool}
setSelectedTool={(tool) => { setSelectedTool={(tool) => {
setSelectedTool(tool); setSelectedTool(tool);
setToolResult(null); setToolResult("");
}} }}
toolResult={toolResult} toolResult={toolResult}
nextCursor={nextToolCursor} nextCursor={nextToolCursor}
@@ -559,11 +413,6 @@ const App = () => {
onApprove={handleApproveSampling} onApprove={handleApproveSampling}
onReject={handleRejectSampling} onReject={handleRejectSampling}
/> />
<RootsTab
roots={roots}
setRoots={setRoots}
onRootsChange={handleRootsChange}
/>
</div> </div>
</Tabs> </Tabs>
) : ( ) : (

View File

@@ -6,7 +6,7 @@ const HistoryAndNotifications = ({
requestHistory, requestHistory,
serverNotifications, serverNotifications,
}: { }: {
requestHistory: Array<{ request: string; response?: string }>; requestHistory: Array<{ request: string; response: string | null }>;
serverNotifications: ServerNotification[]; serverNotifications: ServerNotification[];
}) => { }) => {
const [expandedRequests, setExpandedRequests] = useState<{ const [expandedRequests, setExpandedRequests] = useState<{

View File

@@ -1,199 +1,88 @@
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import { import { ListResourcesResult, Resource } from "@modelcontextprotocol/sdk/types.js";
ListResourcesResult,
Resource,
ResourceTemplate,
ListResourceTemplatesResult,
} from "@modelcontextprotocol/sdk/types.js";
import { AlertCircle, ChevronRight, FileText, RefreshCw } from "lucide-react"; import { AlertCircle, ChevronRight, FileText, RefreshCw } from "lucide-react";
import ListPane from "./ListPane"; import ListPane from "./ListPane";
import { useState } from "react";
const ResourcesTab = ({ const ResourcesTab = ({
resources, resources,
resourceTemplates,
listResources, listResources,
listResourceTemplates,
readResource, readResource,
selectedResource, selectedResource,
setSelectedResource, setSelectedResource,
resourceContent, resourceContent,
nextCursor, nextCursor,
nextTemplateCursor,
error, error,
}: { }: {
resources: Resource[]; resources: Resource[];
resourceTemplates: ResourceTemplate[];
listResources: () => void; listResources: () => void;
listResourceTemplates: () => void;
readResource: (uri: string) => void; readResource: (uri: string) => void;
selectedResource: Resource | null; selectedResource: Resource | null;
setSelectedResource: (resource: Resource | null) => void; setSelectedResource: (resource: Resource) => void;
resourceContent: string; resourceContent: string;
nextCursor: ListResourcesResult["nextCursor"]; nextCursor: ListResourcesResult["nextCursor"];
nextTemplateCursor: ListResourceTemplatesResult["nextCursor"];
error: string | null; error: string | null;
}) => { }) => (
const [selectedTemplate, setSelectedTemplate] = <TabsContent value="resources" className="grid grid-cols-2 gap-4">
useState<ResourceTemplate | null>(null); <ListPane
const [templateValues, setTemplateValues] = useState<Record<string, string>>( items={resources}
{}, listItems={listResources}
); setSelectedItem={(resource) => {
setSelectedResource(resource);
readResource(resource.uri);
}}
renderItem={(resource) => (
<div className="flex items-center w-full">
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
<span className="flex-1 truncate" title={resource.uri.toString()}>
{resource.name}
</span>
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
</div>
)}
title="Resources"
buttonText={nextCursor ? "List More Resources" : "List Resources"}
isButtonDisabled={!nextCursor && resources.length > 0}
/>
const fillTemplate = ( <div className="bg-white rounded-lg shadow">
template: string, <div className="p-4 border-b border-gray-200 flex justify-between items-center">
values: Record<string, string>, <h3 className="font-semibold truncate" title={selectedResource?.name}>
): string => { {selectedResource ? selectedResource.name : "Select a resource"}
return template.replace( </h3>
/{([^}]+)}/g, {selectedResource && (
(_, key) => values[key] || `{${key}}`, <Button
); variant="outline"
}; size="sm"
onClick={() => readResource(selectedResource.uri)}
const handleReadTemplateResource = () => {
if (selectedTemplate) {
const uri = fillTemplate(selectedTemplate.uriTemplate, templateValues);
readResource(uri);
setSelectedTemplate(null);
// We don't have the full Resource object here, so we create a partial one
setSelectedResource({ uri, name: uri } as Resource);
}
};
return (
<TabsContent value="resources" className="grid grid-cols-3 gap-4">
<ListPane
items={resources}
listItems={listResources}
setSelectedItem={(resource) => {
setSelectedResource(resource);
readResource(resource.uri);
setSelectedTemplate(null);
}}
renderItem={(resource) => (
<div className="flex items-center w-full">
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
<span className="flex-1 truncate" title={resource.uri.toString()}>
{resource.name}
</span>
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
</div>
)}
title="Resources"
buttonText={nextCursor ? "List More Resources" : "List Resources"}
isButtonDisabled={!nextCursor && resources.length > 0}
/>
<ListPane
items={resourceTemplates}
listItems={listResourceTemplates}
setSelectedItem={(template) => {
setSelectedTemplate(template);
setSelectedResource(null);
setTemplateValues({});
}}
renderItem={(template) => (
<div className="flex items-center w-full">
<FileText className="w-4 h-4 mr-2 flex-shrink-0 text-gray-500" />
<span className="flex-1 truncate" title={template.uriTemplate}>
{template.name}
</span>
<ChevronRight className="w-4 h-4 flex-shrink-0 text-gray-400" />
</div>
)}
title="Resource Templates"
buttonText={
nextTemplateCursor ? "List More Templates" : "List Templates"
}
isButtonDisabled={!nextTemplateCursor && resourceTemplates.length > 0}
/>
<div className="bg-white rounded-lg shadow">
<div className="p-4 border-b border-gray-200 flex justify-between items-center">
<h3
className="font-semibold truncate"
title={selectedResource?.name || selectedTemplate?.name}
> >
{selectedResource <RefreshCw className="w-4 h-4 mr-2" />
? selectedResource.name Refresh
: selectedTemplate </Button>
? selectedTemplate.name )}
: "Select a resource or template"}
</h3>
{selectedResource && (
<Button
variant="outline"
size="sm"
onClick={() => readResource(selectedResource.uri)}
>
<RefreshCw className="w-4 h-4 mr-2" />
Refresh
</Button>
)}
</div>
<div className="p-4">
{error ? (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
) : selectedResource ? (
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-96 whitespace-pre-wrap break-words">
{resourceContent}
</pre>
) : selectedTemplate ? (
<div className="space-y-4">
<p className="text-sm text-gray-600">
{selectedTemplate.description}
</p>
{selectedTemplate.uriTemplate
.match(/{([^}]+)}/g)
?.map((param) => {
const key = param.slice(1, -1);
return (
<div key={key}>
<label
htmlFor={key}
className="block text-sm font-medium text-gray-700"
>
{key}
</label>
<Input
id={key}
value={templateValues[key] || ""}
onChange={(e) =>
setTemplateValues({
...templateValues,
[key]: e.target.value,
})
}
className="mt-1"
/>
</div>
);
})}
<Button
onClick={handleReadTemplateResource}
disabled={Object.keys(templateValues).length === 0}
>
Read Resource
</Button>
</div>
) : (
<Alert>
<AlertDescription>
Select a resource or template from the list to view its contents
</AlertDescription>
</Alert>
)}
</div>
</div> </div>
</TabsContent> <div className="p-4">
); {error ? (
}; <Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
) : selectedResource ? (
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-96 whitespace-pre-wrap break-words">
{resourceContent}
</pre>
) : (
<Alert>
<AlertDescription>
Select a resource from the list to view its contents
</AlertDescription>
</Alert>
)}
</div>
</div>
</TabsContent>
);
export default ResourcesTab; export default ResourcesTab;

View File

@@ -1,77 +0,0 @@
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { TabsContent } from "@/components/ui/tabs";
import { Root } from "@modelcontextprotocol/sdk/types.js";
import { Plus, Minus, Save } from "lucide-react";
const RootsTab = ({
roots,
setRoots,
onRootsChange,
}: {
roots: Root[];
setRoots: React.Dispatch<React.SetStateAction<Root[]>>;
onRootsChange: () => void;
}) => {
const addRoot = () => {
setRoots((currentRoots) => [...currentRoots, { uri: "file://", name: "" }]);
};
const removeRoot = (index: number) => {
setRoots((currentRoots) => currentRoots.filter((_, i) => i !== index));
};
const updateRoot = (index: number, field: keyof Root, value: string) => {
setRoots((currentRoots) =>
currentRoots.map((root, i) =>
i === index ? { ...root, [field]: value } : root,
),
);
};
const handleSave = () => {
onRootsChange();
};
return (
<TabsContent value="roots" className="space-y-4">
<Alert>
<AlertDescription>
Configure the root directories that the server can access
</AlertDescription>
</Alert>
{roots.map((root, index) => (
<div key={index} className="flex gap-2 items-center">
<Input
placeholder="file:// URI"
value={root.uri}
onChange={(e) => updateRoot(index, "uri", e.target.value)}
className="flex-1"
/>
<Button
variant="destructive"
size="sm"
onClick={() => removeRoot(index)}
>
<Minus className="h-4 w-4" />
</Button>
</div>
))}
<div className="flex gap-2">
<Button variant="outline" onClick={addRoot}>
<Plus className="h-4 w-4 mr-2" />
Add Root
</Button>
<Button onClick={handleSave}>
<Save className="h-4 w-4 mr-2" />
Save Changes
</Button>
</div>
</TabsContent>
);
};
export default RootsTab;

View File

@@ -3,13 +3,11 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { TabsContent } from "@/components/ui/tabs"; import { TabsContent } from "@/components/ui/tabs";
import { CallToolResult, ListToolsResult, Tool } from "@modelcontextprotocol/sdk/types.js"; import { ListToolsResult, Tool } from "@modelcontextprotocol/sdk/types.js";
import { AlertCircle, Send } from "lucide-react"; import { AlertCircle, Send } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
import ListPane from "./ListPane"; import ListPane from "./ListPane";
import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js";
const ToolsTab = ({ const ToolsTab = ({
tools, tools,
listTools, listTools,
@@ -25,58 +23,12 @@ const ToolsTab = ({
callTool: (name: string, params: Record<string, unknown>) => void; callTool: (name: string, params: Record<string, unknown>) => void;
selectedTool: Tool | null; selectedTool: Tool | null;
setSelectedTool: (tool: Tool) => void; setSelectedTool: (tool: Tool) => void;
toolResult: CompatibilityCallToolResult | null; toolResult: string;
nextCursor: ListToolsResult["nextCursor"]; nextCursor: ListToolsResult["nextCursor"];
error: string | null; error: string | null;
}) => { }) => {
const [params, setParams] = useState<Record<string, unknown>>({}); const [params, setParams] = useState<Record<string, unknown>>({});
const renderToolResult = () => {
if (!toolResult) return null;
if ("content" in toolResult) {
const structuredResult = toolResult as CallToolResult;
return (
<>
<h4 className="font-semibold mb-2">
Tool Result: {structuredResult.isError ? "Error" : "Success"}
</h4>
{structuredResult.content.map((item, index) => (
<div key={index} className="mb-2">
{item.type === "text" && (
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-64">
{item.text}
</pre>
)}
{item.type === "image" && (
<img
src={`data:${item.mimeType};base64,${item.data}`}
alt="Tool result image"
className="max-w-full h-auto"
/>
)}
{item.type === "resource" && (
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-64">
{JSON.stringify(item.resource, null, 2)}
</pre>
)}
</div>
))}
</>
);
} else if ("toolResult" in toolResult) {
return (
<>
<h4 className="font-semibold mb-2">Tool Result (Legacy):</h4>
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-64">
{JSON.stringify(toolResult.toolResult, null, 2)}
</pre>
</>
);
}
};
return ( return (
<TabsContent value="tools" className="grid grid-cols-2 gap-4"> <TabsContent value="tools" className="grid grid-cols-2 gap-4">
<ListPane <ListPane
@@ -148,7 +100,14 @@ const ToolsTab = ({
<Send className="w-4 h-4 mr-2" /> <Send className="w-4 h-4 mr-2" />
Run Tool Run Tool
</Button> </Button>
{toolResult && renderToolResult()} {toolResult && (
<>
<h4 className="font-semibold mb-2">Tool Result:</h4>
<pre className="bg-gray-50 p-4 rounded text-sm overflow-auto max-h-64">
{toolResult}
</pre>
</>
)}
</div> </div>
) : ( ) : (
<Alert> <Alert>

View File

@@ -1,7 +1,7 @@
import * as React from "react"; import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils"
const alertVariants = cva( const alertVariants = cva(
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7", "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
@@ -16,8 +16,8 @@ const alertVariants = cva(
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
}, }
); )
const Alert = React.forwardRef< const Alert = React.forwardRef<
HTMLDivElement, HTMLDivElement,
@@ -29,8 +29,8 @@ const Alert = React.forwardRef<
className={cn(alertVariants({ variant }), className)} className={cn(alertVariants({ variant }), className)}
{...props} {...props}
/> />
)); ))
Alert.displayName = "Alert"; Alert.displayName = "Alert"
const AlertTitle = React.forwardRef< const AlertTitle = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@@ -41,8 +41,8 @@ const AlertTitle = React.forwardRef<
className={cn("mb-1 font-medium leading-none tracking-tight", className)} className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props} {...props}
/> />
)); ))
AlertTitle.displayName = "AlertTitle"; AlertTitle.displayName = "AlertTitle"
const AlertDescription = React.forwardRef< const AlertDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
@@ -53,7 +53,7 @@ const AlertDescription = React.forwardRef<
className={cn("text-sm [&_p]:leading-relaxed", className)} className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props} {...props}
/> />
)); ))
AlertDescription.displayName = "AlertDescription"; AlertDescription.displayName = "AlertDescription"
export { Alert, AlertTitle, AlertDescription }; export { Alert, AlertTitle, AlertDescription }

View File

@@ -1,8 +1,8 @@
import * as React from "react"; import * as React from "react"
import { Slot } from "@radix-ui/react-slot"; import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils"
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
@@ -31,27 +31,27 @@ const buttonVariants = cva(
variant: "default", variant: "default",
size: "default", size: "default",
}, },
}, }
); )
export interface ButtonProps export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>, extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> { VariantProps<typeof buttonVariants> {
asChild?: boolean; asChild?: boolean
} }
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => { ({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"; const Comp = asChild ? Slot : "button"
return ( return (
<Comp <Comp
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
ref={ref} ref={ref}
{...props} {...props}
/> />
); )
}, }
); )
Button.displayName = "Button"; Button.displayName = "Button"
export { Button, buttonVariants }; export { Button, buttonVariants }

View File

@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react"
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils"
export interface InputProps export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {} extends React.InputHTMLAttributes<HTMLInputElement> {}
@@ -12,14 +12,14 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
type={type} type={type}
className={cn( className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className, className
)} )}
ref={ref} ref={ref}
{...props} {...props}
/> />
); )
}, }
); )
Input.displayName = "Input"; Input.displayName = "Input"
export { Input }; export { Input }

View File

@@ -1,12 +1,12 @@
import * as React from "react"; import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"; import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils"
const labelVariants = cva( const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
); )
const Label = React.forwardRef< const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
@@ -18,7 +18,7 @@ const Label = React.forwardRef<
className={cn(labelVariants(), className)} className={cn(labelVariants(), className)}
{...props} {...props}
/> />
)); ))
Label.displayName = LabelPrimitive.Root.displayName; Label.displayName = LabelPrimitive.Root.displayName
export { Label }; export { Label }

View File

@@ -1,19 +1,19 @@
import * as React from "react"; import * as React from "react"
import { import {
CaretSortIcon, CaretSortIcon,
CheckIcon, CheckIcon,
ChevronDownIcon, ChevronDownIcon,
ChevronUpIcon, ChevronUpIcon,
} from "@radix-ui/react-icons"; } from "@radix-ui/react-icons"
import * as SelectPrimitive from "@radix-ui/react-select"; import * as SelectPrimitive from "@radix-ui/react-select"
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils"
const Select = SelectPrimitive.Root; const Select = SelectPrimitive.Root
const SelectGroup = SelectPrimitive.Group; const SelectGroup = SelectPrimitive.Group
const SelectValue = SelectPrimitive.Value; const SelectValue = SelectPrimitive.Value
const SelectTrigger = React.forwardRef< const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>, React.ElementRef<typeof SelectPrimitive.Trigger>,
@@ -23,7 +23,7 @@ const SelectTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className, className
)} )}
{...props} {...props}
> >
@@ -32,8 +32,8 @@ const SelectTrigger = React.forwardRef<
<CaretSortIcon className="h-4 w-4 opacity-50" /> <CaretSortIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon> </SelectPrimitive.Icon>
</SelectPrimitive.Trigger> </SelectPrimitive.Trigger>
)); ))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef< const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>, React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
@@ -43,14 +43,14 @@ const SelectScrollUpButton = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", "flex cursor-default items-center justify-center py-1",
className, className
)} )}
{...props} {...props}
> >
<ChevronUpIcon /> <ChevronUpIcon />
</SelectPrimitive.ScrollUpButton> </SelectPrimitive.ScrollUpButton>
)); ))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef< const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>, React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
@@ -60,15 +60,15 @@ const SelectScrollDownButton = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default items-center justify-center py-1", "flex cursor-default items-center justify-center py-1",
className, className
)} )}
{...props} {...props}
> >
<ChevronDownIcon /> <ChevronDownIcon />
</SelectPrimitive.ScrollDownButton> </SelectPrimitive.ScrollDownButton>
)); ))
SelectScrollDownButton.displayName = SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName; SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef< const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>, React.ElementRef<typeof SelectPrimitive.Content>,
@@ -81,7 +81,7 @@ const SelectContent = React.forwardRef<
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" && position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className, className
)} )}
position={position} position={position}
{...props} {...props}
@@ -91,7 +91,7 @@ const SelectContent = React.forwardRef<
className={cn( className={cn(
"p-1", "p-1",
position === "popper" && position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]", "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)} )}
> >
{children} {children}
@@ -99,8 +99,8 @@ const SelectContent = React.forwardRef<
<SelectScrollDownButton /> <SelectScrollDownButton />
</SelectPrimitive.Content> </SelectPrimitive.Content>
</SelectPrimitive.Portal> </SelectPrimitive.Portal>
)); ))
SelectContent.displayName = SelectPrimitive.Content.displayName; SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef< const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>, React.ElementRef<typeof SelectPrimitive.Label>,
@@ -111,8 +111,8 @@ const SelectLabel = React.forwardRef<
className={cn("px-2 py-1.5 text-sm font-semibold", className)} className={cn("px-2 py-1.5 text-sm font-semibold", className)}
{...props} {...props}
/> />
)); ))
SelectLabel.displayName = SelectPrimitive.Label.displayName; SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef< const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>, React.ElementRef<typeof SelectPrimitive.Item>,
@@ -122,7 +122,7 @@ const SelectItem = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className, className
)} )}
{...props} {...props}
> >
@@ -133,8 +133,8 @@ const SelectItem = React.forwardRef<
</span> </span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item> </SelectPrimitive.Item>
)); ))
SelectItem.displayName = SelectPrimitive.Item.displayName; SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef< const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>, React.ElementRef<typeof SelectPrimitive.Separator>,
@@ -145,8 +145,8 @@ const SelectSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props} {...props}
/> />
)); ))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName; SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export { export {
Select, Select,
@@ -159,4 +159,4 @@ export {
SelectSeparator, SelectSeparator,
SelectScrollUpButton, SelectScrollUpButton,
SelectScrollDownButton, SelectScrollDownButton,
}; }

View File

@@ -1,9 +1,9 @@
import * as React from "react"; import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"; import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root; const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef< const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>, React.ElementRef<typeof TabsPrimitive.List>,
@@ -13,12 +13,12 @@ const TabsList = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className, className
)} )}
{...props} {...props}
/> />
)); ))
TabsList.displayName = TabsPrimitive.List.displayName; TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef< const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>, React.ElementRef<typeof TabsPrimitive.Trigger>,
@@ -28,12 +28,12 @@ const TabsTrigger = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow", "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className, className
)} )}
{...props} {...props}
/> />
)); ))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef< const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>, React.ElementRef<typeof TabsPrimitive.Content>,
@@ -43,11 +43,11 @@ const TabsContent = React.forwardRef<
ref={ref} ref={ref}
className={cn( className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className, className
)} )}
{...props} {...props}
/> />
)); ))
TabsContent.displayName = TabsPrimitive.Content.displayName; TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }; export { Tabs, TabsList, TabsTrigger, TabsContent }

View File

@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react"
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils"
export interface TextareaProps export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {} extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
@@ -11,14 +11,14 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
<textarea <textarea
className={cn( className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className, className
)} )}
ref={ref} ref={ref}
{...props} {...props}
/> />
); )
}, }
); )
Textarea.displayName = "Textarea"; Textarea.displayName = "Textarea"
export { Textarea }; export { Textarea }

View File

@@ -1,6 +1,6 @@
import { clsx, type ClassValue } from "clsx"; import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs))
} }

View File

@@ -2,7 +2,9 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": [
"./src/*"
]
}, },
"target": "ES2020", "target": "ES2020",

167
package-lock.json generated
View File

@@ -1,32 +1,23 @@
{ {
"name": "@modelcontextprotocol/inspector", "name": "mcp-inspector",
"version": "0.1.0", "version": "0.0.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@modelcontextprotocol/inspector", "name": "mcp-inspector",
"version": "0.1.0", "version": "0.0.1",
"license": "MIT",
"workspaces": [ "workspaces": [
"client", "client",
"server" "server"
], ],
"dependencies": {
"concurrently": "^9.0.1"
},
"bin": {
"mcp-inspector": "bin/cli.js"
},
"devDependencies": { "devDependencies": {
"@types/node": "^22.7.5", "concurrently": "^9.0.1",
"prettier": "3.3.3" "prettier": "3.3.3"
} }
}, },
"client": { "client": {
"name": "@modelcontextprotocol/inspector-client", "version": "0.0.0",
"version": "0.1.0",
"license": "MIT",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "*", "@modelcontextprotocol/sdk": "*",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
@@ -39,20 +30,15 @@
"lucide-react": "^0.447.0", "lucide-react": "^0.447.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"serve-handler": "^6.1.6",
"tailwind-merge": "^2.5.3", "tailwind-merge": "^2.5.3",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"bin": {
"mcp-inspector-client": "bin/cli.js"
},
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.11.1", "@eslint/js": "^9.11.1",
"@types/node": "^22.7.5", "@types/node": "^22.7.5",
"@types/react": "^18.3.10", "@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@types/serve-handler": "^6.1.4",
"@vitejs/plugin-react": "^4.3.2", "@vitejs/plugin-react": "^4.3.2",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.11.1", "eslint": "^9.11.1",
@@ -854,18 +840,10 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@modelcontextprotocol/inspector-client": {
"resolved": "client",
"link": true
},
"node_modules/@modelcontextprotocol/inspector-server": {
"resolved": "server",
"link": true
},
"node_modules/@modelcontextprotocol/sdk": { "node_modules/@modelcontextprotocol/sdk": {
"version": "0.4.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.4.0.tgz", "resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-internal/@modelcontextprotocol/sdk/-/@modelcontextprotocol/sdk-0.1.0.tgz",
"integrity": "sha512-79gx8xh4o9YzdbtqMukOe5WKzvEZpvBA1x8PAgJWL7J5k06+vJx8NK2kWzOazPgqnfDego7cNEO8tjai/nOPAA==", "integrity": "sha512-46/FTHNZWUWbdFspKsCIixhCKwi9Hub5+HWNXC1DRIL2TSV8cdx5sTsg+Jy6I4Uc8/rd7tLZcVQ6IasIY1g4zg==",
"dependencies": { "dependencies": {
"content-type": "^1.0.5", "content-type": "^1.0.5",
"raw-body": "^3.0.0", "raw-body": "^3.0.0",
@@ -1755,16 +1733,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/serve-handler": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@types/serve-handler/-/serve-handler-6.1.4.tgz",
"integrity": "sha512-aXy58tNie0NkuSCY291xUxl0X+kGYy986l4kqW6Gi4kEXgr6Tx0fpSH7YwUSa5usPpG3s9DBeIR6hHcDtL2IvQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/serve-static": { "node_modules/@types/serve-static": {
"version": "1.15.7", "version": "1.15.7",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
@@ -2277,6 +2245,7 @@
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
@@ -2400,6 +2369,7 @@
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
@@ -2457,10 +2427,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/client": {
"resolved": "client",
"link": true
},
"node_modules/cliui": { "node_modules/cliui": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"string-width": "^4.2.0", "string-width": "^4.2.0",
@@ -2511,12 +2486,14 @@
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/concurrently": { "node_modules/concurrently": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz",
"integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==", "integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "^4.1.2",
@@ -2542,6 +2519,7 @@
"version": "8.1.1", "version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
@@ -2819,6 +2797,7 @@
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -3361,6 +3340,7 @@
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": "6.* || 8.* || >= 10.*" "node": "6.* || 8.* || >= 10.*"
@@ -3499,6 +3479,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -3862,6 +3843,7 @@
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash.merge": { "node_modules/lodash.merge": {
@@ -3902,6 +3884,10 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
} }
}, },
"node_modules/mcp-inspector": {
"resolved": "server",
"link": true
},
"node_modules/media-typer": { "node_modules/media-typer": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -3988,6 +3974,7 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
@@ -4212,11 +4199,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/path-is-inside": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
"integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="
},
"node_modules/path-key": { "node_modules/path-key": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -4653,6 +4635,12 @@
} }
} }
}, },
"node_modules/react-remove-scroll/node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"license": "0BSD"
},
"node_modules/react-style-singleton": { "node_modules/react-style-singleton": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
@@ -4701,6 +4689,7 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -4816,11 +4805,19 @@
"version": "7.8.1", "version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/rxjs/node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"dev": true,
"license": "0BSD"
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -4914,69 +4911,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/serve-handler": {
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz",
"integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==",
"license": "MIT",
"dependencies": {
"bytes": "3.0.0",
"content-disposition": "0.5.2",
"mime-types": "2.1.18",
"minimatch": "3.1.2",
"path-is-inside": "1.0.2",
"path-to-regexp": "3.3.0",
"range-parser": "1.2.0"
}
},
"node_modules/serve-handler/node_modules/bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/serve-handler/node_modules/content-disposition": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
"integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/serve-handler/node_modules/mime-db": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/serve-handler/node_modules/mime-types": {
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
"dependencies": {
"mime-db": "~1.33.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/serve-handler/node_modules/path-to-regexp": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
"integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw=="
},
"node_modules/serve-handler/node_modules/range-parser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
"integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "1.16.2", "version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
@@ -5040,6 +4974,7 @@
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
"integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@@ -5186,6 +5121,7 @@
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-flag": "^4.0.0" "has-flag": "^4.0.0"
@@ -5337,6 +5273,7 @@
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"tree-kill": "cli.js" "tree-kill": "cli.js"
@@ -5721,6 +5658,7 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-styles": "^4.0.0", "ansi-styles": "^4.0.0",
@@ -5777,6 +5715,7 @@
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@@ -5805,6 +5744,7 @@
"version": "17.7.2", "version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cliui": "^8.0.1", "cliui": "^8.0.1",
@@ -5823,6 +5763,7 @@
"version": "21.1.1", "version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"license": "ISC", "license": "ISC",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -5851,9 +5792,8 @@
} }
}, },
"server": { "server": {
"name": "@modelcontextprotocol/inspector-server", "name": "mcp-inspector",
"version": "0.1.0", "version": "0.0.1",
"license": "MIT",
"dependencies": { "dependencies": {
"@modelcontextprotocol/sdk": "*", "@modelcontextprotocol/sdk": "*",
"cors": "^2.8.5", "cors": "^2.8.5",
@@ -5862,9 +5802,6 @@
"ws": "^8.18.0", "ws": "^8.18.0",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"bin": {
"mcp-inspector-server": "build/index.js"
},
"devDependencies": { "devDependencies": {
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/eventsource": "^1.1.15", "@types/eventsource": "^1.1.15",

View File

@@ -1,22 +1,8 @@
{ {
"name": "@modelcontextprotocol/inspector", "name": "mcp-inspector",
"version": "0.1.0",
"private": true, "private": true,
"description": "Model Context Protocol inspector", "version": "0.0.1",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
"homepage": "https://modelcontextprotocol.github.io",
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
"type": "module", "type": "module",
"bin": {
"mcp-inspector": "./bin/cli.js"
},
"files": [
"bin",
"client/bin",
"client/dist",
"server/build"
],
"workspaces": [ "workspaces": [
"client", "client",
"server" "server"
@@ -28,15 +14,11 @@
"build": "npm run build-server && npm run build-client", "build": "npm run build-server && npm run build-client",
"start-server": "cd server && npm run start", "start-server": "cd server && npm run start",
"start-client": "cd client && npm run preview", "start-client": "cd client && npm run preview",
"start": "./bin/cli.js", "start": "concurrently \"npm run start-server\" \"npm run start-client\"",
"prepare": "npm run build",
"prettier-fix": "prettier --write ." "prettier-fix": "prettier --write ."
}, },
"dependencies": {
"concurrently": "^9.0.1"
},
"devDependencies": { "devDependencies": {
"prettier": "3.3.3", "concurrently": "^9.0.1",
"@types/node": "^22.7.5" "prettier": "3.3.3"
} }
} }

View File

@@ -1,19 +1,8 @@
{ {
"name": "@modelcontextprotocol/inspector-server", "name": "mcp-inspector",
"version": "0.1.0", "version": "0.0.1",
"private": true, "main": "build/index.js",
"description": "Server-side application for the Model Context Protocol inspector",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
"homepage": "https://modelcontextprotocol.github.io",
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
"type": "module", "type": "module",
"bin": {
"mcp-inspector-server": "build/index.js"
},
"files": [
"build"
],
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"start": "node build/index.js", "start": "node build/index.js",

View File

@@ -1,13 +1,8 @@
#!/usr/bin/env node
import cors from "cors"; import cors from "cors";
import EventSource from "eventsource"; import EventSource from "eventsource";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
StdioClientTransport,
getDefaultEnvironment,
} from "@modelcontextprotocol/sdk/client/stdio.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express"; import express from "express";
import mcpProxy from "./mcpProxy.js"; import mcpProxy from "./mcpProxy.js";
@@ -29,11 +24,8 @@ const createTransport = async (query: express.Request["query"]) => {
if (transportType === "stdio") { if (transportType === "stdio") {
const command = query.command as string; const command = query.command as string;
const args = (query.args as string).split(/\s+/); const args = (query.args as string).split(/\s+/);
const env = query.env ? JSON.parse(query.env as string) : undefined; console.log(`Stdio transport: command=${command}, args=${args}`);
console.log( const transport = new StdioClientTransport({ command, args });
`Stdio transport: command=${command}, args=${args}, env=${JSON.stringify(env)}`,
);
const transport = new StdioClientTransport({ command, args, env });
await transport.start(); await transport.start();
console.log("Spawned stdio transport"); console.log("Spawned stdio transport");
return transport; return transport;
@@ -87,10 +79,6 @@ app.post("/message", async (req, res) => {
await transport.handlePostMessage(req, res); await transport.handlePostMessage(req, res);
}); });
app.get("/default-environment", (req, res) => {
res.json(getDefaultEnvironment());
});
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`); console.log(`Server is running on port ${PORT}`);