Skip to content

Commit 171b59b

Browse files
Merge pull request modelcontextprotocol#44 from modelcontextprotocol/justin/update-sdk
Update SDK; add UI for roots, resource templates, and env vars
2 parents 97b67ca + 2867173 commit 171b59b

File tree

7 files changed

+464
-98
lines changed

7 files changed

+464
-98
lines changed

client/src/App.tsx

Lines changed: 153 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
11
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
22
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
33
import {
4-
CallToolResultSchema,
4+
CompatibilityCallToolResultSchema,
55
ClientRequest,
66
CreateMessageRequestSchema,
77
CreateMessageResult,
88
EmptyResultSchema,
99
GetPromptResultSchema,
1010
ListPromptsResultSchema,
1111
ListResourcesResultSchema,
12+
ListResourceTemplatesResultSchema,
13+
ListRootsRequestSchema,
1214
ListToolsResultSchema,
1315
ProgressNotificationSchema,
1416
ReadResourceResultSchema,
1517
Resource,
18+
ResourceTemplate,
19+
Root,
1620
ServerNotification,
1721
Tool,
22+
CompatibilityCallToolResult,
23+
ClientNotification,
1824
} from "@modelcontextprotocol/sdk/types.js";
1925
import { useEffect, useRef, useState } from "react";
2026

@@ -37,16 +43,20 @@ import {
3743
Play,
3844
Send,
3945
Terminal,
46+
FolderTree,
47+
ChevronDown,
48+
ChevronRight,
4049
} from "lucide-react";
4150

42-
import { AnyZodObject } from "zod";
51+
import { ZodType } from "zod";
4352
import "./App.css";
4453
import ConsoleTab from "./components/ConsoleTab";
4554
import HistoryAndNotifications from "./components/History";
4655
import PingTab from "./components/PingTab";
4756
import PromptsTab, { Prompt } from "./components/PromptsTab";
4857
import RequestsTab from "./components/RequestsTabs";
4958
import ResourcesTab from "./components/ResourcesTab";
59+
import RootsTab from "./components/RootsTab";
5060
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
5161
import Sidebar from "./components/Sidebar";
5262
import ToolsTab from "./components/ToolsTab";
@@ -56,11 +66,15 @@ const App = () => {
5666
"disconnected" | "connected" | "error"
5767
>("disconnected");
5868
const [resources, setResources] = useState<Resource[]>([]);
69+
const [resourceTemplates, setResourceTemplates] = useState<
70+
ResourceTemplate[]
71+
>([]);
5972
const [resourceContent, setResourceContent] = useState<string>("");
6073
const [prompts, setPrompts] = useState<Prompt[]>([]);
6174
const [promptContent, setPromptContent] = useState<string>("");
6275
const [tools, setTools] = useState<Tool[]>([]);
63-
const [toolResult, setToolResult] = useState<string>("");
76+
const [toolResult, setToolResult] =
77+
useState<CompatibilityCallToolResult | null>(null);
6478
const [error, setError] = useState<string | null>(null);
6579
const [command, setCommand] = useState<string>(() => {
6680
return (
@@ -77,10 +91,13 @@ const App = () => {
7791
const [url, setUrl] = useState<string>("http://localhost:3001/sse");
7892
const [transportType, setTransportType] = useState<"stdio" | "sse">("stdio");
7993
const [requestHistory, setRequestHistory] = useState<
80-
{ request: string; response: string }[]
94+
{ request: string; response?: string }[]
8195
>([]);
8296
const [mcpClient, setMcpClient] = useState<Client | null>(null);
8397
const [notifications, setNotifications] = useState<ServerNotification[]>([]);
98+
const [roots, setRoots] = useState<Root[]>([]);
99+
const [env, setEnv] = useState<Record<string, string>>({});
100+
const [showEnvVars, setShowEnvVars] = useState(false);
84101

85102
const [pendingSampleRequests, setPendingSampleRequests] = useState<
86103
Array<
@@ -116,6 +133,9 @@ const App = () => {
116133
const [nextResourceCursor, setNextResourceCursor] = useState<
117134
string | undefined
118135
>();
136+
const [nextResourceTemplateCursor, setNextResourceTemplateCursor] = useState<
137+
string | undefined
138+
>();
119139
const [nextPromptCursor, setNextPromptCursor] = useState<
120140
string | undefined
121141
>();
@@ -130,14 +150,26 @@ const App = () => {
130150
localStorage.setItem("lastArgs", args);
131151
}, [args]);
132152

133-
const pushHistory = (request: object, response: object) => {
153+
useEffect(() => {
154+
fetch("http://localhost:3000/default-environment")
155+
.then((response) => response.json())
156+
.then((data) => setEnv(data))
157+
.catch((error) =>
158+
console.error("Error fetching default environment:", error),
159+
);
160+
}, []);
161+
162+
const pushHistory = (request: object, response?: object) => {
134163
setRequestHistory((prev) => [
135164
...prev,
136-
{ request: JSON.stringify(request), response: JSON.stringify(response) },
165+
{
166+
request: JSON.stringify(request),
167+
response: response !== undefined ? JSON.stringify(response) : undefined,
168+
},
137169
]);
138170
};
139171

140-
const makeRequest = async <T extends AnyZodObject>(
172+
const makeRequest = async <T extends ZodType<object>>(
141173
request: ClientRequest,
142174
schema: T,
143175
) => {
@@ -155,6 +187,20 @@ const App = () => {
155187
}
156188
};
157189

190+
const sendNotification = async (notification: ClientNotification) => {
191+
if (!mcpClient) {
192+
throw new Error("MCP client not connected");
193+
}
194+
195+
try {
196+
await mcpClient.notification(notification);
197+
pushHistory(notification);
198+
} catch (e: unknown) {
199+
setError((e as Error).message);
200+
throw e;
201+
}
202+
};
203+
158204
const listResources = async () => {
159205
const response = await makeRequest(
160206
{
@@ -167,6 +213,22 @@ const App = () => {
167213
setNextResourceCursor(response.nextCursor);
168214
};
169215

216+
const listResourceTemplates = async () => {
217+
const response = await makeRequest(
218+
{
219+
method: "resources/templates/list" as const,
220+
params: nextResourceTemplateCursor
221+
? { cursor: nextResourceTemplateCursor }
222+
: {},
223+
},
224+
ListResourceTemplatesResultSchema,
225+
);
226+
setResourceTemplates(
227+
resourceTemplates.concat(response.resourceTemplates ?? []),
228+
);
229+
setNextResourceTemplateCursor(response.nextCursor);
230+
};
231+
170232
const readResource = async (uri: string) => {
171233
const response = await makeRequest(
172234
{
@@ -225,9 +287,13 @@ const App = () => {
225287
},
226288
},
227289
},
228-
CallToolResultSchema,
290+
CompatibilityCallToolResultSchema,
229291
);
230-
setToolResult(JSON.stringify(response.toolResult, null, 2));
292+
setToolResult(response);
293+
};
294+
295+
const handleRootsChange = async () => {
296+
sendNotification({ method: "notifications/roots/list_changed" });
231297
};
232298

233299
const connectMcpServer = async () => {
@@ -243,6 +309,7 @@ const App = () => {
243309
if (transportType === "stdio") {
244310
backendUrl.searchParams.append("command", command);
245311
backendUrl.searchParams.append("args", args);
312+
backendUrl.searchParams.append("env", JSON.stringify(env));
246313
} else {
247314
backendUrl.searchParams.append("url", url);
248315
}
@@ -269,6 +336,10 @@ const App = () => {
269336
});
270337
});
271338

339+
client.setRequestHandler(ListRootsRequestSchema, async () => {
340+
return { roots };
341+
});
342+
272343
setMcpClient(client);
273344
setConnectionStatus("connected");
274345
} catch (e) {
@@ -326,6 +397,66 @@ const App = () => {
326397
Connect
327398
</Button>
328399
</div>
400+
{transportType === "stdio" && (
401+
<div className="mt-4">
402+
<Button
403+
variant="outline"
404+
onClick={() => setShowEnvVars(!showEnvVars)}
405+
className="flex items-center"
406+
>
407+
{showEnvVars ? (
408+
<ChevronDown className="w-4 h-4 mr-2" />
409+
) : (
410+
<ChevronRight className="w-4 h-4 mr-2" />
411+
)}
412+
Environment Variables
413+
</Button>
414+
{showEnvVars && (
415+
<div className="mt-2">
416+
{Object.entries(env).map(([key, value]) => (
417+
<div key={key} className="flex space-x-2 mb-2">
418+
<Input
419+
placeholder="Key"
420+
value={key}
421+
onChange={(e) =>
422+
setEnv((prev) => ({
423+
...prev,
424+
[e.target.value]: value,
425+
}))
426+
}
427+
/>
428+
<Input
429+
placeholder="Value"
430+
value={value}
431+
onChange={(e) =>
432+
setEnv((prev) => ({
433+
...prev,
434+
[key]: e.target.value,
435+
}))
436+
}
437+
/>
438+
<Button
439+
onClick={() =>
440+
setEnv((prev) => {
441+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
442+
const { [key]: _, ...rest } = prev;
443+
return rest;
444+
})
445+
}
446+
>
447+
Remove
448+
</Button>
449+
</div>
450+
))}
451+
<Button
452+
onClick={() => setEnv((prev) => ({ ...prev, "": "" }))}
453+
>
454+
Add Environment Variable
455+
</Button>
456+
</div>
457+
)}
458+
</div>
459+
)}
329460
</div>
330461
{mcpClient ? (
331462
<Tabs defaultValue="resources" className="w-full p-4">
@@ -363,17 +494,24 @@ const App = () => {
363494
</span>
364495
)}
365496
</TabsTrigger>
497+
<TabsTrigger value="roots">
498+
<FolderTree className="w-4 h-4 mr-2" />
499+
Roots
500+
</TabsTrigger>
366501
</TabsList>
367502

368503
<div className="w-full">
369504
<ResourcesTab
370505
resources={resources}
506+
resourceTemplates={resourceTemplates}
371507
listResources={listResources}
508+
listResourceTemplates={listResourceTemplates}
372509
readResource={readResource}
373510
selectedResource={selectedResource}
374511
setSelectedResource={setSelectedResource}
375512
resourceContent={resourceContent}
376513
nextCursor={nextResourceCursor}
514+
nextTemplateCursor={nextResourceTemplateCursor}
377515
error={error}
378516
/>
379517
<PromptsTab
@@ -394,7 +532,7 @@ const App = () => {
394532
selectedTool={selectedTool}
395533
setSelectedTool={(tool) => {
396534
setSelectedTool(tool);
397-
setToolResult("");
535+
setToolResult(null);
398536
}}
399537
toolResult={toolResult}
400538
nextCursor={nextToolCursor}
@@ -416,6 +554,11 @@ const App = () => {
416554
onApprove={handleApproveSampling}
417555
onReject={handleRejectSampling}
418556
/>
557+
<RootsTab
558+
roots={roots}
559+
setRoots={setRoots}
560+
onRootsChange={handleRootsChange}
561+
/>
419562
</div>
420563
</Tabs>
421564
) : (

client/src/components/History.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const HistoryAndNotifications = ({
66
requestHistory,
77
serverNotifications,
88
}: {
9-
requestHistory: Array<{ request: string; response: string | null }>;
9+
requestHistory: Array<{ request: string; response?: string }>;
1010
serverNotifications: ServerNotification[];
1111
}) => {
1212
const [expandedRequests, setExpandedRequests] = useState<{

0 commit comments

Comments
 (0)