Compare commits
28 Commits
davidsp/em
...
0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fdb2903a4 | ||
|
|
b9d6695fbf | ||
|
|
3789ef984a | ||
|
|
e1aa419332 | ||
|
|
94e4e92618 | ||
|
|
148e84388a | ||
|
|
6147640656 | ||
|
|
1ddd2d7aaa | ||
|
|
94ddfed1ac | ||
|
|
448910d986 | ||
|
|
3a9b08bd37 | ||
|
|
bce3a7b8d6 | ||
|
|
196f2f801d | ||
|
|
a0d8ec1e7e | ||
|
|
6759c461f9 | ||
|
|
ea4484cc04 | ||
|
|
171b59bec6 | ||
|
|
2867173e7b | ||
|
|
57f0c49154 | ||
|
|
5337baa116 | ||
|
|
afefcb3fa5 | ||
|
|
76e2cf6fdc | ||
|
|
193032533b | ||
|
|
f3406ca43d | ||
|
|
645f2e942e | ||
|
|
d80214d0a2 | ||
|
|
97b67ca1ae | ||
|
|
216c8a15d5 |
14
.github/workflows/main.yml
vendored
14
.github/workflows/main.yml
vendored
@@ -17,20 +17,6 @@ jobs:
|
||||
node-version: 18
|
||||
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
|
||||
# - run: npm ci
|
||||
- run: npm install --no-package-lock
|
||||
|
||||
3
.npmrc
3
.npmrc
@@ -1 +1,2 @@
|
||||
registry = "https://registry.npmjs.org/"
|
||||
registry="https://registry.npmjs.org/"
|
||||
@modelcontextprotocol:registry="https://registry.npmjs.org/"
|
||||
|
||||
7
LICENSE
Normal file
7
LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
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.
|
||||
36
bin/cli.js
Executable file
36
bin/cli.js
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/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);
|
||||
});
|
||||
18
client/bin/cli.js
Executable file
18
client/bin/cli.js
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/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}`);
|
||||
});
|
||||
@@ -17,4 +17,4 @@
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
{
|
||||
"name": "client",
|
||||
"name": "@modelcontextprotocol/inspector-client",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"description": "Client-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",
|
||||
"bin": {
|
||||
"mcp-inspector-client": "./bin/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
@@ -21,6 +33,7 @@
|
||||
"lucide-react": "^0.447.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"serve-handler": "^6.1.6",
|
||||
"tailwind-merge": "^2.5.3",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.23.8"
|
||||
@@ -30,6 +43,7 @@
|
||||
"@types/node": "^22.7.5",
|
||||
"@types/react": "^18.3.10",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.11.1",
|
||||
|
||||
@@ -3,4 +3,4 @@ export default {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
CompatibilityCallToolResultSchema,
|
||||
ClientRequest,
|
||||
CreateMessageRequestSchema,
|
||||
CreateMessageResult,
|
||||
@@ -9,12 +9,18 @@ import {
|
||||
GetPromptResultSchema,
|
||||
ListPromptsResultSchema,
|
||||
ListResourcesResultSchema,
|
||||
ListResourceTemplatesResultSchema,
|
||||
ListRootsRequestSchema,
|
||||
ListToolsResultSchema,
|
||||
ProgressNotificationSchema,
|
||||
ReadResourceResultSchema,
|
||||
Resource,
|
||||
ResourceTemplate,
|
||||
Root,
|
||||
ServerNotification,
|
||||
Tool,
|
||||
CompatibilityCallToolResult,
|
||||
ClientNotification,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -37,9 +43,12 @@ import {
|
||||
Play,
|
||||
Send,
|
||||
Terminal,
|
||||
FolderTree,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
} from "lucide-react";
|
||||
|
||||
import { AnyZodObject } from "zod";
|
||||
import { ZodType } from "zod";
|
||||
import "./App.css";
|
||||
import ConsoleTab from "./components/ConsoleTab";
|
||||
import HistoryAndNotifications from "./components/History";
|
||||
@@ -47,6 +56,7 @@ import PingTab from "./components/PingTab";
|
||||
import PromptsTab, { Prompt } from "./components/PromptsTab";
|
||||
import RequestsTab from "./components/RequestsTabs";
|
||||
import ResourcesTab from "./components/ResourcesTab";
|
||||
import RootsTab from "./components/RootsTab";
|
||||
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
|
||||
import Sidebar from "./components/Sidebar";
|
||||
import ToolsTab from "./components/ToolsTab";
|
||||
@@ -56,11 +66,15 @@ const App = () => {
|
||||
"disconnected" | "connected" | "error"
|
||||
>("disconnected");
|
||||
const [resources, setResources] = useState<Resource[]>([]);
|
||||
const [resourceTemplates, setResourceTemplates] = useState<
|
||||
ResourceTemplate[]
|
||||
>([]);
|
||||
const [resourceContent, setResourceContent] = useState<string>("");
|
||||
const [prompts, setPrompts] = useState<Prompt[]>([]);
|
||||
const [promptContent, setPromptContent] = useState<string>("");
|
||||
const [tools, setTools] = useState<Tool[]>([]);
|
||||
const [toolResult, setToolResult] = useState<string>("");
|
||||
const [toolResult, setToolResult] =
|
||||
useState<CompatibilityCallToolResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [command, setCommand] = useState<string>(() => {
|
||||
return (
|
||||
@@ -77,10 +91,13 @@ const App = () => {
|
||||
const [url, setUrl] = useState<string>("http://localhost:3001/sse");
|
||||
const [transportType, setTransportType] = useState<"stdio" | "sse">("stdio");
|
||||
const [requestHistory, setRequestHistory] = useState<
|
||||
{ request: string; response: string }[]
|
||||
{ request: string; response?: string }[]
|
||||
>([]);
|
||||
const [mcpClient, setMcpClient] = useState<Client | null>(null);
|
||||
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<
|
||||
Array<
|
||||
@@ -91,6 +108,7 @@ const App = () => {
|
||||
>
|
||||
>([]);
|
||||
const nextRequestId = useRef(0);
|
||||
const rootsRef = useRef<Root[]>([]);
|
||||
|
||||
const handleApproveSampling = (id: number, result: CreateMessageResult) => {
|
||||
setPendingSampleRequests((prev) => {
|
||||
@@ -116,6 +134,9 @@ const App = () => {
|
||||
const [nextResourceCursor, setNextResourceCursor] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [nextResourceTemplateCursor, setNextResourceTemplateCursor] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
const [nextPromptCursor, setNextPromptCursor] = useState<
|
||||
string | undefined
|
||||
>();
|
||||
@@ -130,14 +151,30 @@ const App = () => {
|
||||
localStorage.setItem("lastArgs", args);
|
||||
}, [args]);
|
||||
|
||||
const pushHistory = (request: object, response: object) => {
|
||||
useEffect(() => {
|
||||
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) => [
|
||||
...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 AnyZodObject>(
|
||||
const makeRequest = async <T extends ZodType<object>>(
|
||||
request: ClientRequest,
|
||||
schema: T,
|
||||
) => {
|
||||
@@ -155,6 +192,20 @@ 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 response = await makeRequest(
|
||||
{
|
||||
@@ -167,6 +218,22 @@ const App = () => {
|
||||
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 response = await makeRequest(
|
||||
{
|
||||
@@ -225,9 +292,13 @@ const App = () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
CallToolResultSchema,
|
||||
CompatibilityCallToolResultSchema,
|
||||
);
|
||||
setToolResult(JSON.stringify(response.toolResult, null, 2));
|
||||
setToolResult(response);
|
||||
};
|
||||
|
||||
const handleRootsChange = async () => {
|
||||
await sendNotification({ method: "notifications/roots/list_changed" });
|
||||
};
|
||||
|
||||
const connectMcpServer = async () => {
|
||||
@@ -243,6 +314,7 @@ const App = () => {
|
||||
if (transportType === "stdio") {
|
||||
backendUrl.searchParams.append("command", command);
|
||||
backendUrl.searchParams.append("args", args);
|
||||
backendUrl.searchParams.append("env", JSON.stringify(env));
|
||||
} else {
|
||||
backendUrl.searchParams.append("url", url);
|
||||
}
|
||||
@@ -269,6 +341,10 @@ const App = () => {
|
||||
});
|
||||
});
|
||||
|
||||
client.setRequestHandler(ListRootsRequestSchema, async () => {
|
||||
return { roots: rootsRef.current };
|
||||
});
|
||||
|
||||
setMcpClient(client);
|
||||
setConnectionStatus("connected");
|
||||
} catch (e) {
|
||||
@@ -326,6 +402,66 @@ const App = () => {
|
||||
Connect
|
||||
</Button>
|
||||
</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>
|
||||
{mcpClient ? (
|
||||
<Tabs defaultValue="resources" className="w-full p-4">
|
||||
@@ -363,17 +499,24 @@ const App = () => {
|
||||
</span>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="roots">
|
||||
<FolderTree className="w-4 h-4 mr-2" />
|
||||
Roots
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<div className="w-full">
|
||||
<ResourcesTab
|
||||
resources={resources}
|
||||
resourceTemplates={resourceTemplates}
|
||||
listResources={listResources}
|
||||
listResourceTemplates={listResourceTemplates}
|
||||
readResource={readResource}
|
||||
selectedResource={selectedResource}
|
||||
setSelectedResource={setSelectedResource}
|
||||
resourceContent={resourceContent}
|
||||
nextCursor={nextResourceCursor}
|
||||
nextTemplateCursor={nextResourceTemplateCursor}
|
||||
error={error}
|
||||
/>
|
||||
<PromptsTab
|
||||
@@ -394,7 +537,7 @@ const App = () => {
|
||||
selectedTool={selectedTool}
|
||||
setSelectedTool={(tool) => {
|
||||
setSelectedTool(tool);
|
||||
setToolResult("");
|
||||
setToolResult(null);
|
||||
}}
|
||||
toolResult={toolResult}
|
||||
nextCursor={nextToolCursor}
|
||||
@@ -416,6 +559,11 @@ const App = () => {
|
||||
onApprove={handleApproveSampling}
|
||||
onReject={handleRejectSampling}
|
||||
/>
|
||||
<RootsTab
|
||||
roots={roots}
|
||||
setRoots={setRoots}
|
||||
onRootsChange={handleRootsChange}
|
||||
/>
|
||||
</div>
|
||||
</Tabs>
|
||||
) : (
|
||||
|
||||
@@ -6,7 +6,7 @@ const HistoryAndNotifications = ({
|
||||
requestHistory,
|
||||
serverNotifications,
|
||||
}: {
|
||||
requestHistory: Array<{ request: string; response: string | null }>;
|
||||
requestHistory: Array<{ request: string; response?: string }>;
|
||||
serverNotifications: ServerNotification[];
|
||||
}) => {
|
||||
const [expandedRequests, setExpandedRequests] = useState<{
|
||||
|
||||
@@ -1,88 +1,199 @@
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { TabsContent } from "@/components/ui/tabs";
|
||||
import { ListResourcesResult, Resource } from "@modelcontextprotocol/sdk/types.js";
|
||||
import {
|
||||
ListResourcesResult,
|
||||
Resource,
|
||||
ResourceTemplate,
|
||||
ListResourceTemplatesResult,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { AlertCircle, ChevronRight, FileText, RefreshCw } from "lucide-react";
|
||||
import ListPane from "./ListPane";
|
||||
import { useState } from "react";
|
||||
|
||||
const ResourcesTab = ({
|
||||
resources,
|
||||
resourceTemplates,
|
||||
listResources,
|
||||
listResourceTemplates,
|
||||
readResource,
|
||||
selectedResource,
|
||||
setSelectedResource,
|
||||
resourceContent,
|
||||
nextCursor,
|
||||
nextTemplateCursor,
|
||||
error,
|
||||
}: {
|
||||
resources: Resource[];
|
||||
resourceTemplates: ResourceTemplate[];
|
||||
listResources: () => void;
|
||||
listResourceTemplates: () => void;
|
||||
readResource: (uri: string) => void;
|
||||
selectedResource: Resource | null;
|
||||
setSelectedResource: (resource: Resource) => void;
|
||||
setSelectedResource: (resource: Resource | null) => void;
|
||||
resourceContent: string;
|
||||
nextCursor: ListResourcesResult["nextCursor"];
|
||||
nextTemplateCursor: ListResourceTemplatesResult["nextCursor"];
|
||||
error: string | null;
|
||||
}) => (
|
||||
<TabsContent value="resources" className="grid grid-cols-2 gap-4">
|
||||
<ListPane
|
||||
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 [selectedTemplate, setSelectedTemplate] =
|
||||
useState<ResourceTemplate | null>(null);
|
||||
const [templateValues, setTemplateValues] = useState<Record<string, string>>(
|
||||
{},
|
||||
);
|
||||
|
||||
<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}>
|
||||
{selectedResource ? selectedResource.name : "Select a resource"}
|
||||
</h3>
|
||||
{selectedResource && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => readResource(selectedResource.uri)}
|
||||
const fillTemplate = (
|
||||
template: string,
|
||||
values: Record<string, string>,
|
||||
): string => {
|
||||
return template.replace(
|
||||
/{([^}]+)}/g,
|
||||
(_, key) => values[key] || `{${key}}`,
|
||||
);
|
||||
};
|
||||
|
||||
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}
|
||||
>
|
||||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
Refresh
|
||||
</Button>
|
||||
)}
|
||||
{selectedResource
|
||||
? selectedResource.name
|
||||
: selectedTemplate
|
||||
? 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 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>
|
||||
);
|
||||
</TabsContent>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResourcesTab;
|
||||
|
||||
77
client/src/components/RootsTab.tsx
Normal file
77
client/src/components/RootsTab.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
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;
|
||||
@@ -3,11 +3,13 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { TabsContent } from "@/components/ui/tabs";
|
||||
import { ListToolsResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { CallToolResult, ListToolsResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { AlertCircle, Send } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import ListPane from "./ListPane";
|
||||
|
||||
import { CompatibilityCallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
const ToolsTab = ({
|
||||
tools,
|
||||
listTools,
|
||||
@@ -23,12 +25,58 @@ const ToolsTab = ({
|
||||
callTool: (name: string, params: Record<string, unknown>) => void;
|
||||
selectedTool: Tool | null;
|
||||
setSelectedTool: (tool: Tool) => void;
|
||||
toolResult: string;
|
||||
toolResult: CompatibilityCallToolResult | null;
|
||||
nextCursor: ListToolsResult["nextCursor"];
|
||||
error: string | null;
|
||||
}) => {
|
||||
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 (
|
||||
<TabsContent value="tools" className="grid grid-cols-2 gap-4">
|
||||
<ListPane
|
||||
@@ -100,14 +148,7 @@ const ToolsTab = ({
|
||||
<Send className="w-4 h-4 mr-2" />
|
||||
Run Tool
|
||||
</Button>
|
||||
{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>
|
||||
</>
|
||||
)}
|
||||
{toolResult && renderToolResult()}
|
||||
</div>
|
||||
) : (
|
||||
<Alert>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
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",
|
||||
@@ -16,8 +16,8 @@ const alertVariants = cva(
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
const Alert = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
@@ -29,8 +29,8 @@ const Alert = React.forwardRef<
|
||||
className={cn(alertVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Alert.displayName = "Alert"
|
||||
));
|
||||
Alert.displayName = "Alert";
|
||||
|
||||
const AlertTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
@@ -41,8 +41,8 @@ const AlertTitle = React.forwardRef<
|
||||
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertTitle.displayName = "AlertTitle"
|
||||
));
|
||||
AlertTitle.displayName = "AlertTitle";
|
||||
|
||||
const AlertDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
@@ -53,7 +53,7 @@ const AlertDescription = React.forwardRef<
|
||||
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDescription.displayName = "AlertDescription"
|
||||
));
|
||||
AlertDescription.displayName = "AlertDescription";
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription }
|
||||
export { Alert, AlertTitle, AlertDescription };
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
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",
|
||||
@@ -31,27 +31,27 @@ const buttonVariants = cva(
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
);
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants }
|
||||
export { Button, buttonVariants };
|
||||
|
||||
@@ -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
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
@@ -12,14 +12,14 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
type={type}
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input }
|
||||
export { Input };
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
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<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
@@ -18,7 +18,7 @@ const Label = React.forwardRef<
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
));
|
||||
Label.displayName = LabelPrimitive.Root.displayName;
|
||||
|
||||
export { Label }
|
||||
export { Label };
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
import {
|
||||
CaretSortIcon,
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
} from "@radix-ui/react-icons";
|
||||
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<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
@@ -23,7 +23,7 @@ const SelectTrigger = React.forwardRef<
|
||||
ref={ref}
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -32,8 +32,8 @@ const SelectTrigger = React.forwardRef<
|
||||
<CaretSortIcon className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
));
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
@@ -43,14 +43,14 @@ const SelectScrollUpButton = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
));
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
@@ -60,15 +60,15 @@ const SelectScrollDownButton = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
));
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName
|
||||
SelectPrimitive.ScrollDownButton.displayName;
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
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",
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
@@ -91,7 +91,7 @@ const SelectContent = React.forwardRef<
|
||||
className={cn(
|
||||
"p-1",
|
||||
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}
|
||||
@@ -99,8 +99,8 @@ const SelectContent = React.forwardRef<
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
));
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
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)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
));
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
@@ -122,7 +122,7 @@ const SelectItem = React.forwardRef<
|
||||
ref={ref}
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -133,8 +133,8 @@ const SelectItem = React.forwardRef<
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
));
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
@@ -145,8 +145,8 @@ const SelectSeparator = React.forwardRef<
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
));
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||
|
||||
export {
|
||||
Select,
|
||||
@@ -159,4 +159,4 @@ export {
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
import * as React from "react";
|
||||
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<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
@@ -13,12 +13,12 @@ const TabsList = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
));
|
||||
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
@@ -28,12 +28,12 @@ const TabsTrigger = React.forwardRef<
|
||||
ref={ref}
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
));
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
@@ -43,11 +43,11 @@ const TabsContent = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"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}
|
||||
/>
|
||||
))
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
));
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||
|
||||
@@ -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
|
||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
@@ -11,14 +11,14 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
<textarea
|
||||
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",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Textarea.displayName = "Textarea"
|
||||
);
|
||||
},
|
||||
);
|
||||
Textarea.displayName = "Textarea";
|
||||
|
||||
export { Textarea }
|
||||
export { Textarea };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
|
||||
"target": "ES2020",
|
||||
|
||||
167
package-lock.json
generated
167
package-lock.json
generated
@@ -1,23 +1,32 @@
|
||||
{
|
||||
"name": "mcp-inspector",
|
||||
"version": "0.0.1",
|
||||
"name": "@modelcontextprotocol/inspector",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mcp-inspector",
|
||||
"version": "0.0.1",
|
||||
"name": "@modelcontextprotocol/inspector",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"client",
|
||||
"server"
|
||||
],
|
||||
"dependencies": {
|
||||
"concurrently": "^9.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"mcp-inspector": "bin/cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.0.1",
|
||||
"@types/node": "^22.7.5",
|
||||
"prettier": "3.3.3"
|
||||
}
|
||||
},
|
||||
"client": {
|
||||
"version": "0.0.0",
|
||||
"name": "@modelcontextprotocol/inspector-client",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "*",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
@@ -30,15 +39,20 @@
|
||||
"lucide-react": "^0.447.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"serve-handler": "^6.1.6",
|
||||
"tailwind-merge": "^2.5.3",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"bin": {
|
||||
"mcp-inspector-client": "bin/cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.11.1",
|
||||
"@types/node": "^22.7.5",
|
||||
"@types/react": "^18.3.10",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"@vitejs/plugin-react": "^4.3.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.11.1",
|
||||
@@ -840,10 +854,18 @@
|
||||
"@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": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://artifactory.infra.ant.dev:443/artifactory/api/npm/npm-internal/@modelcontextprotocol/sdk/-/@modelcontextprotocol/sdk-0.1.0.tgz",
|
||||
"integrity": "sha512-46/FTHNZWUWbdFspKsCIixhCKwi9Hub5+HWNXC1DRIL2TSV8cdx5sTsg+Jy6I4Uc8/rd7tLZcVQ6IasIY1g4zg==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.4.0.tgz",
|
||||
"integrity": "sha512-79gx8xh4o9YzdbtqMukOe5WKzvEZpvBA1x8PAgJWL7J5k06+vJx8NK2kWzOazPgqnfDego7cNEO8tjai/nOPAA==",
|
||||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
"raw-body": "^3.0.0",
|
||||
@@ -1733,6 +1755,16 @@
|
||||
"@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": {
|
||||
"version": "1.15.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
|
||||
@@ -2245,7 +2277,6 @@
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
@@ -2369,7 +2400,6 @@
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
@@ -2427,15 +2457,10 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/client": {
|
||||
"resolved": "client",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
@@ -2486,14 +2511,12 @@
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concurrently": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz",
|
||||
"integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
@@ -2519,7 +2542,6 @@
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
@@ -2797,7 +2819,6 @@
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -3340,7 +3361,6 @@
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
@@ -3479,7 +3499,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -3843,7 +3862,6 @@
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
@@ -3884,10 +3902,6 @@
|
||||
"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": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@@ -3974,7 +3988,6 @@
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
@@ -4199,6 +4212,11 @@
|
||||
"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": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -4635,12 +4653,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
|
||||
@@ -4689,7 +4701,6 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -4805,19 +4816,11 @@
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
|
||||
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"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": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
@@ -4911,6 +4914,69 @@
|
||||
"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": {
|
||||
"version": "1.16.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
|
||||
@@ -4974,7 +5040,6 @@
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
|
||||
"integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -5121,7 +5186,6 @@
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
@@ -5273,7 +5337,6 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"tree-kill": "cli.js"
|
||||
@@ -5658,7 +5721,6 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
@@ -5715,7 +5777,6 @@
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -5744,7 +5805,6 @@
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
@@ -5763,7 +5823,6 @@
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -5792,8 +5851,9 @@
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"name": "mcp-inspector",
|
||||
"version": "0.0.1",
|
||||
"name": "@modelcontextprotocol/inspector-server",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "*",
|
||||
"cors": "^2.8.5",
|
||||
@@ -5802,6 +5862,9 @@
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"bin": {
|
||||
"mcp-inspector-server": "build/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/eventsource": "^1.1.15",
|
||||
|
||||
28
package.json
28
package.json
@@ -1,8 +1,22 @@
|
||||
{
|
||||
"name": "mcp-inspector",
|
||||
"name": "@modelcontextprotocol/inspector",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"description": "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",
|
||||
"bin": {
|
||||
"mcp-inspector": "./bin/cli.js"
|
||||
},
|
||||
"files": [
|
||||
"bin",
|
||||
"client/bin",
|
||||
"client/dist",
|
||||
"server/build"
|
||||
],
|
||||
"workspaces": [
|
||||
"client",
|
||||
"server"
|
||||
@@ -14,11 +28,15 @@
|
||||
"build": "npm run build-server && npm run build-client",
|
||||
"start-server": "cd server && npm run start",
|
||||
"start-client": "cd client && npm run preview",
|
||||
"start": "concurrently \"npm run start-server\" \"npm run start-client\"",
|
||||
"start": "./bin/cli.js",
|
||||
"prepare": "npm run build",
|
||||
"prettier-fix": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"concurrently": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.0.1",
|
||||
"prettier": "3.3.3"
|
||||
"prettier": "3.3.3",
|
||||
"@types/node": "^22.7.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
{
|
||||
"name": "mcp-inspector",
|
||||
"version": "0.0.1",
|
||||
"main": "build/index.js",
|
||||
"name": "@modelcontextprotocol/inspector-server",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"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",
|
||||
"bin": {
|
||||
"mcp-inspector-server": "build/index.js"
|
||||
},
|
||||
"files": [
|
||||
"build"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node build/index.js",
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import cors from "cors";
|
||||
import EventSource from "eventsource";
|
||||
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
import {
|
||||
StdioClientTransport,
|
||||
getDefaultEnvironment,
|
||||
} from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import express from "express";
|
||||
import mcpProxy from "./mcpProxy.js";
|
||||
@@ -24,8 +29,11 @@ const createTransport = async (query: express.Request["query"]) => {
|
||||
if (transportType === "stdio") {
|
||||
const command = query.command as string;
|
||||
const args = (query.args as string).split(/\s+/);
|
||||
console.log(`Stdio transport: command=${command}, args=${args}`);
|
||||
const transport = new StdioClientTransport({ command, args });
|
||||
const env = query.env ? JSON.parse(query.env as string) : undefined;
|
||||
console.log(
|
||||
`Stdio transport: command=${command}, args=${args}, env=${JSON.stringify(env)}`,
|
||||
);
|
||||
const transport = new StdioClientTransport({ command, args, env });
|
||||
await transport.start();
|
||||
console.log("Spawned stdio transport");
|
||||
return transport;
|
||||
@@ -79,6 +87,10 @@ app.post("/message", async (req, res) => {
|
||||
await transport.handlePostMessage(req, res);
|
||||
});
|
||||
|
||||
app.get("/default-environment", (req, res) => {
|
||||
res.json(getDefaultEnvironment());
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
|
||||
Reference in New Issue
Block a user