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 " ;
3
3
import {
4
- ClientNotification ,
5
4
ClientRequest ,
6
5
CompatibilityCallToolResult ,
7
6
CompatibilityCallToolResultSchema ,
8
- CreateMessageRequestSchema ,
9
7
CreateMessageResult ,
10
8
EmptyResultSchema ,
11
9
GetPromptResultSchema ,
12
10
ListPromptsResultSchema ,
13
11
ListResourcesResultSchema ,
14
12
ListResourceTemplatesResultSchema ,
15
- ListRootsRequestSchema ,
16
- ListToolsResultSchema ,
17
- ProgressNotificationSchema ,
18
13
ReadResourceResultSchema ,
19
- Request ,
14
+ ListToolsResultSchema ,
20
15
Resource ,
21
16
ResourceTemplate ,
22
17
Root ,
23
18
ServerNotification ,
24
- Tool ,
25
- ServerCapabilitiesSchema ,
26
- Result ,
19
+ Tool
27
20
} from "@modelcontextprotocol/sdk/types.js" ;
28
- import { useCallback , useEffect , useRef , useState } from "react" ;
21
+ import { useEffect , useRef , useState } from "react" ;
29
22
30
- import {
31
- Notification ,
32
- StdErrNotification ,
33
- StdErrNotificationSchema ,
34
- } from "./lib/notificationTypes" ;
23
+ import { StdErrNotification } from "./lib/notificationTypes" ;
35
24
36
25
import { Tabs , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
37
26
import {
@@ -43,8 +32,7 @@ import {
43
32
MessageSquare ,
44
33
} from "lucide-react" ;
45
34
46
- import { toast } from "react-toastify" ;
47
- import { z , type ZodType } from "zod" ;
35
+ import { z } from "zod" ;
48
36
import "./App.css" ;
49
37
import ConsoleTab from "./components/ConsoleTab" ;
50
38
import HistoryAndNotifications from "./components/History" ;
@@ -56,21 +44,11 @@ import SamplingTab, { PendingRequest } from "./components/SamplingTab";
56
44
import Sidebar from "./components/Sidebar" ;
57
45
import ToolsTab from "./components/ToolsTab" ;
58
46
59
- type ServerCapabilities = z . infer < typeof ServerCapabilitiesSchema > ;
60
-
61
- const DEFAULT_REQUEST_TIMEOUT_MSEC = 10000 ;
62
-
63
47
const params = new URLSearchParams ( window . location . search ) ;
64
48
const PROXY_PORT = params . get ( "proxyPort" ) ?? "3000" ;
65
- const REQUEST_TIMEOUT =
66
- parseInt ( params . get ( "timeout" ) ?? "" ) || DEFAULT_REQUEST_TIMEOUT_MSEC ;
67
49
const PROXY_SERVER_URL = `http://localhost:${ PROXY_PORT } ` ;
68
50
69
51
const App = ( ) => {
70
- const [ connectionStatus , setConnectionStatus ] = useState <
71
- "disconnected" | "connected" | "error"
72
- > ( "disconnected" ) ;
73
- const [ serverCapabilities , setServerCapabilities ] = useState < ServerCapabilities | null > ( null ) ;
74
52
const [ resources , setResources ] = useState < Resource [ ] > ( [ ] ) ;
75
53
const [ resourceTemplates , setResourceTemplates ] = useState <
76
54
ResourceTemplate [ ]
@@ -95,10 +73,6 @@ const App = () => {
95
73
96
74
const [ sseUrl , setSseUrl ] = useState < string > ( "http://localhost:3001/sse" ) ;
97
75
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 ) ;
102
76
const [ notifications , setNotifications ] = useState < ServerNotification [ ] > ( [ ] ) ;
103
77
const [ stdErrNotifications , setStdErrNotifications ] = useState <
104
78
StdErrNotification [ ]
@@ -149,49 +123,64 @@ const App = () => {
149
123
> ( ) ;
150
124
const [ nextToolCursor , setNextToolCursor ] = useState < string | undefined > ( ) ;
151
125
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 ) ;
156
126
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 ] ) ;
163
149
} ,
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 ] ) ;
176
152
} ,
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
+ } ) ;
184
161
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 ;
193
182
}
194
- } , [ isDragging , handleDragMove , handleDragEnd ] ) ;
183
+ } ;
195
184
196
185
useEffect ( ( ) => {
197
186
localStorage . setItem ( "lastCommand" , command ) ;
@@ -228,83 +217,10 @@ const App = () => {
228
217
}
229
218
} , [ ] ) ;
230
219
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
-
241
220
const clearError = ( tabKey : keyof typeof errors ) => {
242
221
setErrors ( ( prev ) => ( { ...prev , [ tabKey ] : null } ) ) ;
243
222
} ;
244
223
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
-
308
224
const listResources = async ( ) => {
309
225
const response = await makeRequest (
310
226
{
@@ -407,82 +323,6 @@ const App = () => {
407
323
await sendNotification ( { method : "notifications/roots/list_changed" } ) ;
408
324
} ;
409
325
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
-
486
326
return (
487
327
< div className = "flex h-screen bg-background" >
488
328
< Sidebar
0 commit comments