Compare commits

...

10 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
Justin Spahr-Summers
752631f5a1 Merge pull request #41 from modelcontextprotocol/justin/npmrc
Add npmrc to always point to npm for public packages
2024-10-31 22:46:26 +00:00
Justin Spahr-Summers
2acc6290c8 Add .npmrc 2024-10-31 21:53:01 +00:00
Justin Spahr-Summers
5427a6ec7c Merge pull request #37 from modelcontextprotocol/justin/sdk-install-instructions
Instructions for installing the SDK dependency manually
2024-10-29 19:57:02 +00:00
Justin Spahr-Summers
daa329b3bd README instructions for preinstalling the SDK 2024-10-29 14:22:19 +00:00
Justin Spahr-Summers
a5606f7ac7 Merge pull request #28 from modelcontextprotocol/justin/sampling
Add tab and approval flow for server -> client sampling
2024-10-28 15:24:05 +00:00
Justin Spahr-Summers
7926eea39c Fix import 2024-10-28 13:01:31 +00:00
Justin Spahr-Summers
a798c5528e Merge branch 'main' into justin/sampling 2024-10-28 12:52:18 +00:00
Justin Spahr-Summers
0d66867701 Add notification badge 2024-10-25 14:48:15 +01:00
Justin Spahr-Summers
5da417470f Add tab and approval flow for server -> client sampling 2024-10-25 14:47:08 +01:00
4 changed files with 141 additions and 17 deletions

1
.npmrc Normal file
View File

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

View File

@@ -2,9 +2,16 @@
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
```

View File

@@ -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 { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import {
CallToolResultSchema,
ClientRequest,
CreateMessageRequestSchema,
CreateMessageResult,
EmptyResultSchema,
GetPromptResultSchema,
ListPromptsResultSchema,
@@ -24,16 +16,28 @@ import {
ServerNotification,
Tool,
} 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 {
Bell,
Files,
Hammer,
Hash,
MessageSquare,
Play,
Send,
Terminal,
} from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { AnyZodObject } from "zod";
import "./App.css";
@@ -43,6 +47,7 @@ import PingTab from "./components/PingTab";
import PromptsTab, { Prompt } from "./components/PromptsTab";
import RequestsTab from "./components/RequestsTabs";
import ResourcesTab from "./components/ResourcesTab";
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
import Sidebar from "./components/Sidebar";
import ToolsTab from "./components/ToolsTab";
@@ -64,10 +69,7 @@ const App = () => {
);
});
const [args, setArgs] = useState<string>(() => {
return (
localStorage.getItem("lastArgs") ||
"/Users/ashwin/code/mcp/example-servers/build/everything/stdio.js"
);
return localStorage.getItem("lastArgs") || "";
});
const [url, setUrl] = useState<string>("http://localhost:3001/sse");
const [transportType, setTransportType] = useState<"stdio" | "sse">("stdio");
@@ -77,6 +79,32 @@ const App = () => {
const [mcpClient, setMcpClient] = useState<Client | null>(null);
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>(
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);
setConnectionStatus("connected");
} catch (e) {
@@ -314,6 +351,15 @@ const App = () => {
<Bell className="w-4 h-4 mr-2" />
Ping
</TabsTrigger>
<TabsTrigger value="sampling" className="relative">
<Hash className="w-4 h-4 mr-2" />
Sampling
{pendingSampleRequests.length > 0 && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
{pendingSampleRequests.length}
</span>
)}
</TabsTrigger>
</TabsList>
<div className="w-full">
@@ -362,6 +408,11 @@ const App = () => {
);
}}
/>
<SamplingTab
pendingRequests={pendingSampleRequests}
onApprove={handleApproveSampling}
onReject={handleRejectSampling}
/>
</div>
</Tabs>
) : (

View 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;