1
1
import { Client } from "@modelcontextprotocol/sdk/client/index.js" ;
2
2
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js" ;
3
3
import {
4
- CallToolResultSchema ,
4
+ CompatibilityCallToolResultSchema ,
5
5
ClientRequest ,
6
6
CreateMessageRequestSchema ,
7
7
CreateMessageResult ,
8
8
EmptyResultSchema ,
9
9
GetPromptResultSchema ,
10
10
ListPromptsResultSchema ,
11
11
ListResourcesResultSchema ,
12
+ ListResourceTemplatesResultSchema ,
13
+ ListRootsRequestSchema ,
12
14
ListToolsResultSchema ,
13
15
ProgressNotificationSchema ,
14
16
ReadResourceResultSchema ,
15
17
Resource ,
18
+ ResourceTemplate ,
19
+ Root ,
16
20
ServerNotification ,
17
21
Tool ,
22
+ CompatibilityCallToolResult ,
23
+ ClientNotification ,
18
24
} from "@modelcontextprotocol/sdk/types.js" ;
19
25
import { useEffect , useRef , useState } from "react" ;
20
26
@@ -37,16 +43,20 @@ import {
37
43
Play ,
38
44
Send ,
39
45
Terminal ,
46
+ FolderTree ,
47
+ ChevronDown ,
48
+ ChevronRight ,
40
49
} from "lucide-react" ;
41
50
42
- import { AnyZodObject } from "zod" ;
51
+ import { ZodType } from "zod" ;
43
52
import "./App.css" ;
44
53
import ConsoleTab from "./components/ConsoleTab" ;
45
54
import HistoryAndNotifications from "./components/History" ;
46
55
import PingTab from "./components/PingTab" ;
47
56
import PromptsTab , { Prompt } from "./components/PromptsTab" ;
48
57
import RequestsTab from "./components/RequestsTabs" ;
49
58
import ResourcesTab from "./components/ResourcesTab" ;
59
+ import RootsTab from "./components/RootsTab" ;
50
60
import SamplingTab , { PendingRequest } from "./components/SamplingTab" ;
51
61
import Sidebar from "./components/Sidebar" ;
52
62
import ToolsTab from "./components/ToolsTab" ;
@@ -56,11 +66,15 @@ const App = () => {
56
66
"disconnected" | "connected" | "error"
57
67
> ( "disconnected" ) ;
58
68
const [ resources , setResources ] = useState < Resource [ ] > ( [ ] ) ;
69
+ const [ resourceTemplates , setResourceTemplates ] = useState <
70
+ ResourceTemplate [ ]
71
+ > ( [ ] ) ;
59
72
const [ resourceContent , setResourceContent ] = useState < string > ( "" ) ;
60
73
const [ prompts , setPrompts ] = useState < Prompt [ ] > ( [ ] ) ;
61
74
const [ promptContent , setPromptContent ] = useState < string > ( "" ) ;
62
75
const [ tools , setTools ] = useState < Tool [ ] > ( [ ] ) ;
63
- const [ toolResult , setToolResult ] = useState < string > ( "" ) ;
76
+ const [ toolResult , setToolResult ] =
77
+ useState < CompatibilityCallToolResult | null > ( null ) ;
64
78
const [ error , setError ] = useState < string | null > ( null ) ;
65
79
const [ command , setCommand ] = useState < string > ( ( ) => {
66
80
return (
@@ -77,10 +91,13 @@ const App = () => {
77
91
const [ url , setUrl ] = useState < string > ( "http://localhost:3001/sse" ) ;
78
92
const [ transportType , setTransportType ] = useState < "stdio" | "sse" > ( "stdio" ) ;
79
93
const [ requestHistory , setRequestHistory ] = useState <
80
- { request : string ; response : string } [ ]
94
+ { request : string ; response ? : string } [ ]
81
95
> ( [ ] ) ;
82
96
const [ mcpClient , setMcpClient ] = useState < Client | null > ( null ) ;
83
97
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 ) ;
84
101
85
102
const [ pendingSampleRequests , setPendingSampleRequests ] = useState <
86
103
Array <
@@ -116,6 +133,9 @@ const App = () => {
116
133
const [ nextResourceCursor , setNextResourceCursor ] = useState <
117
134
string | undefined
118
135
> ( ) ;
136
+ const [ nextResourceTemplateCursor , setNextResourceTemplateCursor ] = useState <
137
+ string | undefined
138
+ > ( ) ;
119
139
const [ nextPromptCursor , setNextPromptCursor ] = useState <
120
140
string | undefined
121
141
> ( ) ;
@@ -130,14 +150,26 @@ const App = () => {
130
150
localStorage . setItem ( "lastArgs" , args ) ;
131
151
} , [ args ] ) ;
132
152
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 ) => {
134
163
setRequestHistory ( ( prev ) => [
135
164
...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
+ } ,
137
169
] ) ;
138
170
} ;
139
171
140
- const makeRequest = async < T extends AnyZodObject > (
172
+ const makeRequest = async < T extends ZodType < object > > (
141
173
request : ClientRequest ,
142
174
schema : T ,
143
175
) => {
@@ -155,6 +187,20 @@ const App = () => {
155
187
}
156
188
} ;
157
189
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
+
158
204
const listResources = async ( ) => {
159
205
const response = await makeRequest (
160
206
{
@@ -167,6 +213,22 @@ const App = () => {
167
213
setNextResourceCursor ( response . nextCursor ) ;
168
214
} ;
169
215
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
+
170
232
const readResource = async ( uri : string ) => {
171
233
const response = await makeRequest (
172
234
{
@@ -225,9 +287,13 @@ const App = () => {
225
287
} ,
226
288
} ,
227
289
} ,
228
- CallToolResultSchema ,
290
+ CompatibilityCallToolResultSchema ,
229
291
) ;
230
- setToolResult ( JSON . stringify ( response . toolResult , null , 2 ) ) ;
292
+ setToolResult ( response ) ;
293
+ } ;
294
+
295
+ const handleRootsChange = async ( ) => {
296
+ sendNotification ( { method : "notifications/roots/list_changed" } ) ;
231
297
} ;
232
298
233
299
const connectMcpServer = async ( ) => {
@@ -243,6 +309,7 @@ const App = () => {
243
309
if ( transportType === "stdio" ) {
244
310
backendUrl . searchParams . append ( "command" , command ) ;
245
311
backendUrl . searchParams . append ( "args" , args ) ;
312
+ backendUrl . searchParams . append ( "env" , JSON . stringify ( env ) ) ;
246
313
} else {
247
314
backendUrl . searchParams . append ( "url" , url ) ;
248
315
}
@@ -269,6 +336,10 @@ const App = () => {
269
336
} ) ;
270
337
} ) ;
271
338
339
+ client . setRequestHandler ( ListRootsRequestSchema , async ( ) => {
340
+ return { roots } ;
341
+ } ) ;
342
+
272
343
setMcpClient ( client ) ;
273
344
setConnectionStatus ( "connected" ) ;
274
345
} catch ( e ) {
@@ -326,6 +397,66 @@ const App = () => {
326
397
Connect
327
398
</ Button >
328
399
</ 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
+ ) }
329
460
</ div >
330
461
{ mcpClient ? (
331
462
< Tabs defaultValue = "resources" className = "w-full p-4" >
@@ -363,17 +494,24 @@ const App = () => {
363
494
</ span >
364
495
) }
365
496
</ TabsTrigger >
497
+ < TabsTrigger value = "roots" >
498
+ < FolderTree className = "w-4 h-4 mr-2" />
499
+ Roots
500
+ </ TabsTrigger >
366
501
</ TabsList >
367
502
368
503
< div className = "w-full" >
369
504
< ResourcesTab
370
505
resources = { resources }
506
+ resourceTemplates = { resourceTemplates }
371
507
listResources = { listResources }
508
+ listResourceTemplates = { listResourceTemplates }
372
509
readResource = { readResource }
373
510
selectedResource = { selectedResource }
374
511
setSelectedResource = { setSelectedResource }
375
512
resourceContent = { resourceContent }
376
513
nextCursor = { nextResourceCursor }
514
+ nextTemplateCursor = { nextResourceTemplateCursor }
377
515
error = { error }
378
516
/>
379
517
< PromptsTab
@@ -394,7 +532,7 @@ const App = () => {
394
532
selectedTool = { selectedTool }
395
533
setSelectedTool = { ( tool ) => {
396
534
setSelectedTool ( tool ) ;
397
- setToolResult ( "" ) ;
535
+ setToolResult ( null ) ;
398
536
} }
399
537
toolResult = { toolResult }
400
538
nextCursor = { nextToolCursor }
@@ -416,6 +554,11 @@ const App = () => {
416
554
onApprove = { handleApproveSampling }
417
555
onReject = { handleRejectSampling }
418
556
/>
557
+ < RootsTab
558
+ roots = { roots }
559
+ setRoots = { setRoots }
560
+ onRootsChange = { handleRootsChange }
561
+ />
419
562
</ div >
420
563
</ Tabs >
421
564
) : (
0 commit comments