Compare commits
10 Commits
0.0.1
...
davidsp/em
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b3f967867 | ||
|
|
752631f5a1 | ||
|
|
2acc6290c8 | ||
|
|
5427a6ec7c | ||
|
|
daa329b3bd | ||
|
|
a5606f7ac7 | ||
|
|
7926eea39c | ||
|
|
a798c5528e | ||
|
|
0d66867701 | ||
|
|
5da417470f |
11
README.md
11
README.md
@@ -2,9 +2,16 @@
|
|||||||
|
|
||||||
The MCP inspector is a developer tool for testing and debugging MCP servers.
|
The MCP inspector is a developer tool for testing and debugging MCP servers.
|
||||||
|
|
||||||
Setup:
|
## Getting started
|
||||||
|
|
||||||
```bash
|
This repository depends on the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk/). Until these repositories are made public and published to npm, the SDK has to be preinstalled manually:
|
||||||
|
|
||||||
|
1. Download the [latest release of the SDK](https://github.com/modelcontextprotocol/typescript-sdk/releases) (the file named something like `modelcontextprotocol-sdk-0.1.0.tgz`). You don't need to extract it.
|
||||||
|
2. From within your checkout of _this_ repository, run `npm install --save path/to/sdk.tgz`. This will overwrite the expected location for the SDK to allow you to proceed.
|
||||||
|
|
||||||
|
Then, you should be able to install the rest of the dependencies normally:
|
||||||
|
|
||||||
|
```sh
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
||||||
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 {
|
||||||
CallToolResultSchema,
|
CallToolResultSchema,
|
||||||
ClientRequest,
|
ClientRequest,
|
||||||
|
CreateMessageRequestSchema,
|
||||||
|
CreateMessageResult,
|
||||||
EmptyResultSchema,
|
EmptyResultSchema,
|
||||||
GetPromptResultSchema,
|
GetPromptResultSchema,
|
||||||
ListPromptsResultSchema,
|
ListPromptsResultSchema,
|
||||||
@@ -24,16 +16,28 @@ import {
|
|||||||
ServerNotification,
|
ServerNotification,
|
||||||
Tool,
|
Tool,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import {
|
import {
|
||||||
Bell,
|
Bell,
|
||||||
Files,
|
Files,
|
||||||
Hammer,
|
Hammer,
|
||||||
|
Hash,
|
||||||
MessageSquare,
|
MessageSquare,
|
||||||
Play,
|
Play,
|
||||||
Send,
|
Send,
|
||||||
Terminal,
|
Terminal,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
|
|
||||||
import { AnyZodObject } from "zod";
|
import { AnyZodObject } from "zod";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
@@ -43,6 +47,7 @@ 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 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";
|
||||||
|
|
||||||
@@ -64,10 +69,7 @@ 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");
|
||||||
@@ -77,6 +79,32 @@ const App = () => {
|
|||||||
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 [pendingSampleRequests, setPendingSampleRequests] = useState<
|
||||||
|
Array<
|
||||||
|
PendingRequest & {
|
||||||
|
resolve: (result: CreateMessageResult) => void;
|
||||||
|
reject: (error: Error) => void;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>([]);
|
||||||
|
const nextRequestId = useRef(0);
|
||||||
|
|
||||||
|
const handleApproveSampling = (id: number, result: CreateMessageResult) => {
|
||||||
|
setPendingSampleRequests((prev) => {
|
||||||
|
const request = prev.find((r) => r.id === id);
|
||||||
|
request?.resolve(result);
|
||||||
|
return prev.filter((r) => r.id !== id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRejectSampling = (id: number) => {
|
||||||
|
setPendingSampleRequests((prev) => {
|
||||||
|
const request = prev.find((r) => r.id === id);
|
||||||
|
request?.reject(new Error("Sampling request rejected"));
|
||||||
|
return prev.filter((r) => r.id !== id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const [selectedResource, setSelectedResource] = useState<Resource | null>(
|
const [selectedResource, setSelectedResource] = useState<Resource | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
@@ -229,6 +257,15 @@ const App = () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
client.setRequestHandler(CreateMessageRequestSchema, (request) => {
|
||||||
|
return new Promise<CreateMessageResult>((resolve, reject) => {
|
||||||
|
setPendingSampleRequests((prev) => [
|
||||||
|
...prev,
|
||||||
|
{ id: nextRequestId.current++, request, resolve, reject },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
setMcpClient(client);
|
setMcpClient(client);
|
||||||
setConnectionStatus("connected");
|
setConnectionStatus("connected");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -314,6 +351,15 @@ const App = () => {
|
|||||||
<Bell className="w-4 h-4 mr-2" />
|
<Bell className="w-4 h-4 mr-2" />
|
||||||
Ping
|
Ping
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="sampling" className="relative">
|
||||||
|
<Hash className="w-4 h-4 mr-2" />
|
||||||
|
Sampling
|
||||||
|
{pendingSampleRequests.length > 0 && (
|
||||||
|
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
|
||||||
|
{pendingSampleRequests.length}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -362,6 +408,11 @@ const App = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<SamplingTab
|
||||||
|
pendingRequests={pendingSampleRequests}
|
||||||
|
onApprove={handleApproveSampling}
|
||||||
|
onReject={handleRejectSampling}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
65
client/src/components/SamplingTab.tsx
Normal file
65
client/src/components/SamplingTab.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { TabsContent } from "@/components/ui/tabs";
|
||||||
|
import {
|
||||||
|
CreateMessageRequest,
|
||||||
|
CreateMessageResult,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
|
export type PendingRequest = {
|
||||||
|
id: number;
|
||||||
|
request: CreateMessageRequest;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
pendingRequests: PendingRequest[];
|
||||||
|
onApprove: (id: number, result: CreateMessageResult) => void;
|
||||||
|
onReject: (id: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SamplingTab = ({ pendingRequests, onApprove, onReject }: Props) => {
|
||||||
|
const handleApprove = (id: number) => {
|
||||||
|
// For now, just return a stub response
|
||||||
|
onApprove(id, {
|
||||||
|
model: "stub-model",
|
||||||
|
stopReason: "endTurn",
|
||||||
|
role: "assistant",
|
||||||
|
content: {
|
||||||
|
type: "text",
|
||||||
|
text: "This is a stub response.",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TabsContent value="sampling" className="h-96">
|
||||||
|
<Alert>
|
||||||
|
<AlertDescription>
|
||||||
|
When the server requests LLM sampling, requests will appear here for
|
||||||
|
approval.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
<div className="mt-4 space-y-4">
|
||||||
|
<h3 className="text-lg font-semibold">Recent Requests</h3>
|
||||||
|
{pendingRequests.map((request) => (
|
||||||
|
<div key={request.id} className="p-4 border rounded-lg space-y-4">
|
||||||
|
<pre className="bg-gray-50 p-2 rounded">
|
||||||
|
{JSON.stringify(request.request, null, 2)}
|
||||||
|
</pre>
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
<Button onClick={() => handleApprove(request.id)}>Approve</Button>
|
||||||
|
<Button variant="outline" onClick={() => onReject(request.id)}>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{pendingRequests.length === 0 && (
|
||||||
|
<p className="text-gray-500">No pending requests</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SamplingTab;
|
||||||
Reference in New Issue
Block a user