From 3b5f21bef7f6200dac12f58b9a1e425d5131b9d5 Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Tue, 10 Jun 2025 17:46:14 +1000 Subject: [PATCH] First cut of tool use --- .../inspector/src/components/McpServers.tsx | 203 ++++++++++++++++-- 1 file changed, 185 insertions(+), 18 deletions(-) diff --git a/examples/inspector/src/components/McpServers.tsx b/examples/inspector/src/components/McpServers.tsx index fc3c646..25a486d 100644 --- a/examples/inspector/src/components/McpServers.tsx +++ b/examples/inspector/src/components/McpServers.tsx @@ -1,5 +1,6 @@ import { useState, useRef, useEffect } from 'react' import { useMcp, type Tool } from 'use-mcp/react' +import { Info, X } from 'lucide-react' // MCP Connection wrapper that only renders when active @@ -56,7 +57,10 @@ export function McpServers({ Promise.resolve(undefined), clearStorage: () => {}, }) + const [toolForms, setToolForms] = useState>>({}) + const [executionLog, setExecutionLog] = useState('') const logRef = useRef(null) + const executionLogRef = useRef(null) // Extract connection properties const { state, tools, error, log, authUrl, disconnect, authenticate } = @@ -174,6 +178,110 @@ export function McpServers({ } }, [tools]) + // Initialize form data when tools change + useEffect(() => { + const newForms: Record> = {} + tools.forEach((tool: Tool) => { + if (tool.inputSchema && tool.inputSchema.properties) { + const formData: Record = {} + Object.entries(tool.inputSchema.properties).forEach(([key, schema]: [string, any]) => { + // Set default values based on type + if (schema.type === 'number' || schema.type === 'integer') { + formData[key] = schema.default || 0 + } else if (schema.type === 'boolean') { + formData[key] = schema.default || false + } else { + formData[key] = schema.default || '' + } + }) + newForms[tool.name] = formData + } + }) + setToolForms(newForms) + }, [tools]) + + // Handle form input changes + const handleFormChange = (toolName: string, fieldName: string, value: any) => { + setToolForms(prev => ({ + ...prev, + [toolName]: { + ...prev[toolName], + [fieldName]: value + } + })) + } + + // Handle tool execution + const handleRunTool = async (tool: Tool) => { + const args = toolForms[tool.name] || {} + const argsStr = JSON.stringify(args) + + // Add execution start message + const startMessage = `Calling ${tool.name}(${argsStr})\n` + setExecutionLog(prev => prev + startMessage) + + try { + const result = await connectionData.callTool(tool.name, args) + const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2) + setExecutionLog(prev => prev + `${resultStr}\n\n`) + } catch (error) { + setExecutionLog(prev => prev + `Error: ${error}\n\n`) + } + + // Auto-scroll to bottom + setTimeout(() => { + if (executionLogRef.current) { + executionLogRef.current.scrollTop = executionLogRef.current.scrollHeight + } + }, 0) + } + + // Clear execution log + const clearExecutionLog = () => { + setExecutionLog('') + } + + // Render form field based on schema + const renderFormField = (toolName: string, fieldName: string, schema: any, isRequired: boolean) => { + const value = toolForms[toolName]?.[fieldName] || '' + + if (schema.type === 'number' || schema.type === 'integer') { + return ( + handleFormChange(toolName, fieldName, + schema.type === 'integer' ? parseInt(e.target.value) || 0 : parseFloat(e.target.value) || 0 + )} + /> + ) + } else if (schema.type === 'boolean') { + return ( + handleFormChange(toolName, fieldName, e.target.checked)} + /> + ) + } else { + // String or other text input + return ( + handleFormChange(toolName, fieldName, e.target.value)} + /> + ) + } + } + return (
@@ -238,39 +346,98 @@ export function McpServers({
)} - {/* Tools display when connected */} - {state === 'ready' && tools.length > 0 && ( -
-

- Available Tools ({tools.length}) -

-
+ {/* Available Tools section - always present */} +
+

+ Available Tools ({tools.length}) +

+ + {tools.length === 0 ? ( +
+ No tools available. Connect to an MCP server to see available tools. +
+ ) : ( +
{tools.map((tool: Tool, index: number) => (
-
+
{tool.name}
{tool.description && ( -

+

{tool.description}

)} - {tool.inputSchema && ( -
-
Parameters:
-
-                        {JSON.stringify(tool.inputSchema, null, 2)}
-                      
+ + {/* Form for tool parameters */} + {tool.inputSchema && tool.inputSchema.properties && ( +
+ {Object.entries(tool.inputSchema.properties).map(([fieldName, schema]: [string, any]) => { + const isRequired = tool.inputSchema.required?.includes(fieldName) || false + return ( +
+
+ + {schema.description && ( +
+ +
+ {schema.description} +
+
+ )} +
+
+ {renderFormField(tool.name, fieldName, schema, isRequired)} +
+
+ ) + })}
)} + + {/* Run button */} +
))} + + {/* Execution log */} + {tools.length > 0 && ( +
+
+

Execution Log

+ +
+