11import { Client } from "@modelcontextprotocol/sdk/client/index.js" ;
22import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js" ;
33import {
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" ;
1925import { 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" ;
4352import "./App.css" ;
4453import ConsoleTab from "./components/ConsoleTab" ;
4554import HistoryAndNotifications from "./components/History" ;
4655import PingTab from "./components/PingTab" ;
4756import PromptsTab , { Prompt } from "./components/PromptsTab" ;
4857import RequestsTab from "./components/RequestsTabs" ;
4958import ResourcesTab from "./components/ResourcesTab" ;
59+ import RootsTab from "./components/RootsTab" ;
5060import SamplingTab , { PendingRequest } from "./components/SamplingTab" ;
5161import Sidebar from "./components/Sidebar" ;
5262import 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 ) : (
0 commit comments