1- import { Client } from "@modelcontextprotocol/sdk/client/index.js " ;
2- import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js " ;
1+ import { useDraggablePane } from "./lib/hooks/useDraggablePane " ;
2+ import { useConnection } from "./lib/hooks/useConnection " ;
33import {
4- ClientNotification ,
54 ClientRequest ,
65 CompatibilityCallToolResult ,
76 CompatibilityCallToolResultSchema ,
8- CreateMessageRequestSchema ,
97 CreateMessageResult ,
108 EmptyResultSchema ,
119 GetPromptResultSchema ,
1210 ListPromptsResultSchema ,
1311 ListResourcesResultSchema ,
1412 ListResourceTemplatesResultSchema ,
15- ListRootsRequestSchema ,
16- ListToolsResultSchema ,
17- ProgressNotificationSchema ,
1813 ReadResourceResultSchema ,
19- Request ,
14+ ListToolsResultSchema ,
2015 Resource ,
2116 ResourceTemplate ,
2217 Root ,
2318 ServerNotification ,
24- Tool ,
25- ServerCapabilitiesSchema ,
26- Result ,
19+ Tool
2720} from "@modelcontextprotocol/sdk/types.js" ;
28- import { useCallback , useEffect , useRef , useState } from "react" ;
21+ import { useEffect , useRef , useState } from "react" ;
2922
30- import {
31- Notification ,
32- StdErrNotification ,
33- StdErrNotificationSchema ,
34- } from "./lib/notificationTypes" ;
23+ import { StdErrNotification } from "./lib/notificationTypes" ;
3524
3625import { Tabs , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
3726import {
@@ -43,8 +32,7 @@ import {
4332 MessageSquare ,
4433} from "lucide-react" ;
4534
46- import { toast } from "react-toastify" ;
47- import { z , type ZodType } from "zod" ;
35+ import { z } from "zod" ;
4836import "./App.css" ;
4937import ConsoleTab from "./components/ConsoleTab" ;
5038import HistoryAndNotifications from "./components/History" ;
@@ -56,21 +44,11 @@ import SamplingTab, { PendingRequest } from "./components/SamplingTab";
5644import Sidebar from "./components/Sidebar" ;
5745import ToolsTab from "./components/ToolsTab" ;
5846
59- type ServerCapabilities = z . infer < typeof ServerCapabilitiesSchema > ;
60-
61- const DEFAULT_REQUEST_TIMEOUT_MSEC = 10000 ;
62-
6347const params = new URLSearchParams ( window . location . search ) ;
6448const PROXY_PORT = params . get ( "proxyPort" ) ?? "3000" ;
65- const REQUEST_TIMEOUT =
66- parseInt ( params . get ( "timeout" ) ?? "" ) || DEFAULT_REQUEST_TIMEOUT_MSEC ;
6749const PROXY_SERVER_URL = `http://localhost:${ PROXY_PORT } ` ;
6850
6951const App = ( ) => {
70- const [ connectionStatus , setConnectionStatus ] = useState <
71- "disconnected" | "connected" | "error"
72- > ( "disconnected" ) ;
73- const [ serverCapabilities , setServerCapabilities ] = useState < ServerCapabilities | null > ( null ) ;
7452 const [ resources , setResources ] = useState < Resource [ ] > ( [ ] ) ;
7553 const [ resourceTemplates , setResourceTemplates ] = useState <
7654 ResourceTemplate [ ]
@@ -95,10 +73,6 @@ const App = () => {
9573
9674 const [ sseUrl , setSseUrl ] = useState < string > ( "http://localhost:3001/sse" ) ;
9775 const [ transportType , setTransportType ] = useState < "stdio" | "sse" > ( "stdio" ) ;
98- const [ requestHistory , setRequestHistory ] = useState <
99- { request : string ; response ?: string } [ ]
100- > ( [ ] ) ;
101- const [ mcpClient , setMcpClient ] = useState < Client | null > ( null ) ;
10276 const [ notifications , setNotifications ] = useState < ServerNotification [ ] > ( [ ] ) ;
10377 const [ stdErrNotifications , setStdErrNotifications ] = useState <
10478 StdErrNotification [ ]
@@ -149,49 +123,64 @@ const App = () => {
149123 > ( ) ;
150124 const [ nextToolCursor , setNextToolCursor ] = useState < string | undefined > ( ) ;
151125 const progressTokenRef = useRef ( 0 ) ;
152- const [ historyPaneHeight , setHistoryPaneHeight ] = useState < number > ( 300 ) ;
153- const [ isDragging , setIsDragging ] = useState ( false ) ;
154- const dragStartY = useRef < number > ( 0 ) ;
155- const dragStartHeight = useRef < number > ( 0 ) ;
156126
157- const handleDragStart = useCallback (
158- ( e : React . MouseEvent ) => {
159- setIsDragging ( true ) ;
160- dragStartY . current = e . clientY ;
161- dragStartHeight . current = historyPaneHeight ;
162- document . body . style . userSelect = "none" ;
127+ const {
128+ height : historyPaneHeight ,
129+ handleDragStart
130+ } = useDraggablePane ( 300 ) ;
131+
132+ const {
133+ connectionStatus,
134+ serverCapabilities,
135+ mcpClient,
136+ requestHistory,
137+ makeRequest : makeConnectionRequest ,
138+ sendNotification,
139+ connect : connectMcpServer
140+ } = useConnection ( {
141+ transportType,
142+ command,
143+ args,
144+ sseUrl,
145+ env,
146+ proxyServerUrl : PROXY_SERVER_URL ,
147+ onNotification : ( notification ) => {
148+ setNotifications ( prev => [ ...prev , notification as ServerNotification ] ) ;
163149 } ,
164- [ historyPaneHeight ] ,
165- ) ;
166-
167- const handleDragMove = useCallback (
168- ( e : MouseEvent ) => {
169- if ( ! isDragging ) return ;
170- const deltaY = dragStartY . current - e . clientY ;
171- const newHeight = Math . max (
172- 100 ,
173- Math . min ( 800 , dragStartHeight . current + deltaY ) ,
174- ) ;
175- setHistoryPaneHeight ( newHeight ) ;
150+ onStdErrNotification : ( notification ) => {
151+ setStdErrNotifications ( prev => [ ...prev , notification as StdErrNotification ] ) ;
176152 } ,
177- [ isDragging ] ,
178- ) ;
179-
180- const handleDragEnd = useCallback ( ( ) => {
181- setIsDragging ( false ) ;
182- document . body . style . userSelect = "" ;
183- } , [ ] ) ;
153+ onPendingRequest : ( request , resolve , reject ) => {
154+ setPendingSampleRequests ( prev => [
155+ ...prev ,
156+ { id : nextRequestId . current ++ , request, resolve, reject }
157+ ] ) ;
158+ } ,
159+ getRoots : ( ) => rootsRef . current
160+ } ) ;
184161
185- useEffect ( ( ) => {
186- if ( isDragging ) {
187- window . addEventListener ( "mousemove" , handleDragMove ) ;
188- window . addEventListener ( "mouseup" , handleDragEnd ) ;
189- return ( ) => {
190- window . removeEventListener ( "mousemove" , handleDragMove ) ;
191- window . removeEventListener ( "mouseup" , handleDragEnd ) ;
192- } ;
162+ const makeRequest = async < T extends z . ZodType > (
163+ request : ClientRequest ,
164+ schema : T ,
165+ tabKey ?: keyof typeof errors ,
166+ ) => {
167+ try {
168+ const response = await makeConnectionRequest ( request , schema ) ;
169+ if ( tabKey !== undefined ) {
170+ clearError ( tabKey ) ;
171+ }
172+ return response ;
173+ } catch ( e ) {
174+ const errorString = ( e as Error ) . message ?? String ( e ) ;
175+ if ( tabKey !== undefined ) {
176+ setErrors ( ( prev ) => ( {
177+ ...prev ,
178+ [ tabKey ] : errorString ,
179+ } ) ) ;
180+ }
181+ throw e ;
193182 }
194- } , [ isDragging , handleDragMove , handleDragEnd ] ) ;
183+ } ;
195184
196185 useEffect ( ( ) => {
197186 localStorage . setItem ( "lastCommand" , command ) ;
@@ -228,83 +217,10 @@ const App = () => {
228217 }
229218 } , [ ] ) ;
230219
231- const pushHistory = ( request : object , response ?: object ) => {
232- setRequestHistory ( ( prev ) => [
233- ...prev ,
234- {
235- request : JSON . stringify ( request ) ,
236- response : response !== undefined ? JSON . stringify ( response ) : undefined ,
237- } ,
238- ] ) ;
239- } ;
240-
241220 const clearError = ( tabKey : keyof typeof errors ) => {
242221 setErrors ( ( prev ) => ( { ...prev , [ tabKey ] : null } ) ) ;
243222 } ;
244223
245- const makeRequest = async < T extends ZodType < object > > (
246- request : ClientRequest ,
247- schema : T ,
248- tabKey ?: keyof typeof errors ,
249- ) => {
250- if ( ! mcpClient ) {
251- throw new Error ( "MCP client not connected" ) ;
252- }
253-
254- try {
255- const abortController = new AbortController ( ) ;
256- const timeoutId = setTimeout ( ( ) => {
257- abortController . abort ( "Request timed out" ) ;
258- } , REQUEST_TIMEOUT ) ;
259-
260- let response ;
261- try {
262- response = await mcpClient . request ( request , schema , {
263- signal : abortController . signal ,
264- } ) ;
265- pushHistory ( request , response ) ;
266- } catch ( error ) {
267- const errorMessage = error instanceof Error ? error . message : String ( error ) ;
268- pushHistory ( request , { error : errorMessage } ) ;
269- throw error ;
270- } finally {
271- clearTimeout ( timeoutId ) ;
272- }
273-
274- if ( tabKey !== undefined ) {
275- clearError ( tabKey ) ;
276- }
277-
278- return response ;
279- } catch ( e : unknown ) {
280- const errorString = ( e as Error ) . message ?? String ( e ) ;
281- if ( tabKey === undefined ) {
282- toast . error ( errorString ) ;
283- } else {
284- setErrors ( ( prev ) => ( {
285- ...prev ,
286- [ tabKey ] : errorString ,
287- } ) ) ;
288- }
289-
290- throw e ;
291- }
292- } ;
293-
294- const sendNotification = async ( notification : ClientNotification ) => {
295- if ( ! mcpClient ) {
296- throw new Error ( "MCP client not connected" ) ;
297- }
298-
299- try {
300- await mcpClient . notification ( notification ) ;
301- pushHistory ( notification ) ;
302- } catch ( e : unknown ) {
303- toast . error ( ( e as Error ) . message ?? String ( e ) ) ;
304- throw e ;
305- }
306- } ;
307-
308224 const listResources = async ( ) => {
309225 const response = await makeRequest (
310226 {
@@ -407,82 +323,6 @@ const App = () => {
407323 await sendNotification ( { method : "notifications/roots/list_changed" } ) ;
408324 } ;
409325
410- const connectMcpServer = async ( ) => {
411- try {
412- const client = new Client < Request , Notification , Result > (
413- {
414- name : "mcp-inspector" ,
415- version : "0.0.1" ,
416- } ,
417- {
418- capabilities : {
419- // Support all client capabilities since we're an inspector tool
420- sampling : { } ,
421- roots : {
422- listChanged : true ,
423- } ,
424- } ,
425- } ,
426- ) ;
427-
428- const backendUrl = new URL ( `${ PROXY_SERVER_URL } /sse` ) ;
429-
430- backendUrl . searchParams . append ( "transportType" , transportType ) ;
431- if ( transportType === "stdio" ) {
432- backendUrl . searchParams . append ( "command" , command ) ;
433- backendUrl . searchParams . append ( "args" , args ) ;
434- backendUrl . searchParams . append ( "env" , JSON . stringify ( env ) ) ;
435- } else {
436- backendUrl . searchParams . append ( "url" , sseUrl ) ;
437- }
438-
439- const clientTransport = new SSEClientTransport ( backendUrl ) ;
440- client . setNotificationHandler (
441- ProgressNotificationSchema ,
442- ( notification ) => {
443- setNotifications ( ( prevNotifications ) => [
444- ...prevNotifications ,
445- notification ,
446- ] ) ;
447- } ,
448- ) ;
449-
450- client . setNotificationHandler (
451- StdErrNotificationSchema ,
452- ( notification ) => {
453- setStdErrNotifications ( ( prevErrorNotifications ) => [
454- ...prevErrorNotifications ,
455- notification ,
456- ] ) ;
457- } ,
458- ) ;
459-
460- await client . connect ( clientTransport ) ;
461-
462- const capabilities = client . getServerCapabilities ( ) ;
463- setServerCapabilities ( capabilities ?? null ) ;
464-
465- client . setRequestHandler ( CreateMessageRequestSchema , ( request ) => {
466- return new Promise < CreateMessageResult > ( ( resolve , reject ) => {
467- setPendingSampleRequests ( ( prev ) => [
468- ...prev ,
469- { id : nextRequestId . current ++ , request, resolve, reject } ,
470- ] ) ;
471- } ) ;
472- } ) ;
473-
474- client . setRequestHandler ( ListRootsRequestSchema , async ( ) => {
475- return { roots : rootsRef . current } ;
476- } ) ;
477-
478- setMcpClient ( client ) ;
479- setConnectionStatus ( "connected" ) ;
480- } catch ( e ) {
481- console . error ( e ) ;
482- setConnectionStatus ( "error" ) ;
483- }
484- } ;
485-
486326 return (
487327 < div className = "flex h-screen bg-background" >
488328 < Sidebar
0 commit comments