add ability to connect manually

This commit is contained in:
Ashwin Bhat
2024-10-08 08:20:18 -07:00
parent 38e1c8b2b9
commit 1095e48c44
2 changed files with 119 additions and 65 deletions

View File

@@ -6,8 +6,11 @@ import {
Files, Files,
MessageSquare, MessageSquare,
Hammer, Hammer,
Play,
} from "lucide-react"; } from "lucide-react";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import ConsoleTab from "./components/ConsoleTab"; import ConsoleTab from "./components/ConsoleTab";
import Sidebar from "./components/Sidebar"; import Sidebar from "./components/Sidebar";
@@ -29,6 +32,13 @@ const App = () => {
const [tools, setTools] = useState<ToolType[]>([]); const [tools, setTools] = useState<ToolType[]>([]);
const [toolResult, setToolResult] = useState<string>(""); const [toolResult, setToolResult] = useState<string>("");
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [command, setCommand] = useState<string>(
"/Users/ashwin/.nvm/versions/node/v18.20.4/bin/node",
);
const [args, setArgs] = useState<string>(
"/Users/ashwin/code/example-servers/build/everything/index.js",
);
const [mcpConnected, setMcpConnected] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
const ws = new WebSocket("ws://localhost:3000"); const ws = new WebSocket("ws://localhost:3000");
@@ -62,6 +72,8 @@ const App = () => {
setError(null); setError(null);
} else if (message.type === "error") { } else if (message.type === "error") {
setError(message.message); setError(message.message);
} else if (message.type === "connected") {
setMcpConnected(true);
} }
}; };
@@ -71,6 +83,7 @@ const App = () => {
ws.onclose = () => { ws.onclose = () => {
setConnectionStatus("disconnected"); setConnectionStatus("disconnected");
setMcpConnected(false);
}; };
return () => ws.close(); return () => ws.close();
@@ -113,73 +126,105 @@ const App = () => {
sendWebSocketMessage({ type: "callTool", name, params }); sendWebSocketMessage({ type: "callTool", name, params });
}; };
const connectMcpServer = () => {
const argsArray = args.split(" ").filter((arg) => arg.trim() !== "");
sendWebSocketMessage({ type: "connect", command, args: argsArray });
};
return ( return (
<div className="flex h-screen bg-gray-100"> <div className="flex h-screen bg-gray-100">
<Sidebar connectionStatus={connectionStatus} /> <Sidebar connectionStatus={connectionStatus} />
<div className="flex-1 flex flex-col overflow-hidden"> <div className="flex-1 flex flex-col overflow-hidden">
<h1 className="text-2xl font-bold p-4">MCP Inspector</h1> <h1 className="text-2xl font-bold p-4">MCP Inspector</h1>
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<Tabs defaultValue="requests" className="w-full p-4"> <div className="p-4 bg-white shadow-md m-4 rounded-md">
<TabsList className="mb-4"> <h2 className="text-lg font-semibold mb-2">Connect MCP Server</h2>
<TabsTrigger value="resources"> <div className="flex space-x-2 mb-2">
<Files className="w-4 h-4 mr-2" /> <Input
Resources placeholder="Command"
</TabsTrigger> value={command}
<TabsTrigger value="prompts"> onChange={(e) => setCommand(e.target.value)}
<MessageSquare className="w-4 h-4 mr-2" />
Prompts
</TabsTrigger>
<TabsTrigger value="requests" disabled>
<Send className="w-4 h-4 mr-2" />
Requests
</TabsTrigger>
<TabsTrigger value="notifications" disabled>
<Bell className="w-4 h-4 mr-2" />
Notifications
</TabsTrigger>
<TabsTrigger value="tools" disabled>
<Hammer className="w-4 h-4 mr-2" />
Tools
</TabsTrigger>
<TabsTrigger value="console" disabled>
<Terminal className="w-4 h-4 mr-2" />
Console
</TabsTrigger>
</TabsList>
<div className="w-full">
<RequestsTab />
<ResourcesTab
resources={resources}
listResources={listResources}
readResource={readResource}
selectedResource={selectedResource}
setSelectedResource={setSelectedResource}
resourceContent={resourceContent}
error={error}
/> />
<NotificationsTab /> <Input
<PromptsTab placeholder="Arguments (space-separated)"
prompts={prompts} value={args}
listPrompts={listPrompts} onChange={(e) => setArgs(e.target.value)}
getPrompt={getPrompt}
selectedPrompt={selectedPrompt}
setSelectedPrompt={setSelectedPrompt}
promptContent={promptContent}
error={error}
/> />
<ToolsTab <Button onClick={connectMcpServer}>
tools={tools} <Play className="w-4 h-4 mr-2" />
listTools={listTools} Connect
callTool={callTool} </Button>
selectedTool={selectedTool}
setSelectedTool={setSelectedTool}
toolResult={toolResult}
error={error}
/>
<ConsoleTab />
</div> </div>
</Tabs> </div>
{mcpConnected ? (
<Tabs defaultValue="resources" className="w-full p-4">
<TabsList className="mb-4 p-0">
<TabsTrigger value="resources">
<Files className="w-4 h-4 mr-2" />
Resources
</TabsTrigger>
<TabsTrigger value="prompts">
<MessageSquare className="w-4 h-4 mr-2" />
Prompts
</TabsTrigger>
<TabsTrigger value="requests" disabled>
<Send className="w-4 h-4 mr-2" />
Requests
</TabsTrigger>
<TabsTrigger value="notifications" disabled>
<Bell className="w-4 h-4 mr-2" />
Notifications
</TabsTrigger>
<TabsTrigger value="tools" disabled>
<Hammer className="w-4 h-4 mr-2" />
Tools
</TabsTrigger>
<TabsTrigger value="console" disabled>
<Terminal className="w-4 h-4 mr-2" />
Console
</TabsTrigger>
</TabsList>
<div className="w-full">
<ResourcesTab
resources={resources}
listResources={listResources}
readResource={readResource}
selectedResource={selectedResource}
setSelectedResource={setSelectedResource}
resourceContent={resourceContent}
error={error}
/>
<NotificationsTab />
<PromptsTab
prompts={prompts}
listPrompts={listPrompts}
getPrompt={getPrompt}
selectedPrompt={selectedPrompt}
setSelectedPrompt={setSelectedPrompt}
promptContent={promptContent}
error={error}
/>
<RequestsTab />
<ToolsTab
tools={tools}
listTools={listTools}
callTool={callTool}
selectedTool={selectedTool}
setSelectedTool={setSelectedTool}
toolResult={toolResult}
error={error}
/>
<ConsoleTab />
</div>
</Tabs>
) : (
<div className="flex items-center justify-center h-full">
<p className="text-lg text-gray-500">
Connect to an MCP server to start inspecting
</p>
</div>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -7,18 +7,25 @@ const app = express();
const server = http.createServer(app); const server = http.createServer(app);
const wss = new WebSocketServer({ server }); const wss = new WebSocketServer({ server });
const mcpClient = new McpClient("MyApp", "1.0.0"); let mcpClient: McpClient | null = null;
await mcpClient.connectStdio(
"/Users/ashwin/.nvm/versions/node/v18.20.4/bin/node",
["/Users/ashwin/code/example-servers/build/everything/index.js"],
);
wss.on("connection", (ws: WebSocket) => { wss.on("connection", (ws: WebSocket) => {
ws.on("message", async (message: string) => { ws.on("message", async (message: string) => {
try { try {
const command = JSON.parse(message); const command = JSON.parse(message);
if (command.type === "listResources") { if (command.type === "connect" && command.command && command.args) {
mcpClient = new McpClient("MyApp", "1.0.0");
await mcpClient.connectStdio(command.command, command.args);
ws.send(JSON.stringify({ type: "connected" }));
} else if (!mcpClient) {
ws.send(
JSON.stringify({
type: "error",
message: "Not connected to MCP server",
}),
);
} else if (command.type === "listResources") {
const resources = await mcpClient.listResources(); const resources = await mcpClient.listResources();
ws.send(JSON.stringify({ type: "resources", data: resources })); ws.send(JSON.stringify({ type: "resources", data: resources }));
} else if (command.type === "readResource" && command.uri) { } else if (command.type === "readResource" && command.uri) {
@@ -58,6 +65,8 @@ server.listen(PORT, () => {
// Close the client when the server is shutting down // Close the client when the server is shutting down
process.on("SIGINT", async () => { process.on("SIGINT", async () => {
await mcpClient.close(); if (mcpClient) {
await mcpClient.close();
}
process.exit(); process.exit();
}); });