| 29 | } |
| 30 | |
| 31 | export async function executeTool( |
| 32 | toolName: string, |
| 33 | args: Record<string, any> |
| 34 | ): Promise<QueryResult> { |
| 35 | const response = await fetch('/mcp', { |
| 36 | method: 'POST', |
| 37 | headers: { |
| 38 | 'Content-Type': 'application/json', |
| 39 | 'Accept': 'application/json, text/event-stream', |
| 40 | }, |
| 41 | body: JSON.stringify({ |
| 42 | jsonrpc: '2.0', |
| 43 | id: crypto.randomUUID(), |
| 44 | method: 'tools/call', |
| 45 | params: { |
| 46 | name: toolName, |
| 47 | arguments: args, |
| 48 | }, |
| 49 | }), |
| 50 | }); |
| 51 | |
| 52 | if (!response.ok) { |
| 53 | throw new ApiError(`HTTP error: ${response.status}`, response.status); |
| 54 | } |
| 55 | |
| 56 | const mcpResponse: McpResponse = await response.json(); |
| 57 | |
| 58 | if (mcpResponse.error) { |
| 59 | throw new ApiError(mcpResponse.error.message, mcpResponse.error.code); |
| 60 | } |
| 61 | |
| 62 | if (!mcpResponse.result?.content?.[0]?.text) { |
| 63 | throw new ApiError('Invalid response format', 500); |
| 64 | } |
| 65 | |
| 66 | const toolResult: ToolResultData = JSON.parse(mcpResponse.result.content[0].text); |
| 67 | |
| 68 | if (!toolResult.success || toolResult.error) { |
| 69 | throw new ApiError(toolResult.error || 'Tool execution failed', 500); |
| 70 | } |
| 71 | |
| 72 | if (!toolResult.data || !toolResult.data.rows) { |
| 73 | return { columns: [], rows: [], rowCount: 0 }; |
| 74 | } |
| 75 | |
| 76 | const rows = toolResult.data.rows; |
| 77 | if (rows.length === 0) { |
| 78 | // For INSERT/UPDATE/DELETE, rows is empty but count reflects affected rows |
| 79 | return { columns: [], rows: [], rowCount: toolResult.data.count }; |
| 80 | } |
| 81 | |
| 82 | const columns = Object.keys(rows[0]); |
| 83 | const rowArrays = rows.map((row) => columns.map((col) => row[col])); |
| 84 | |
| 85 | return { |
| 86 | columns, |
| 87 | rows: rowArrays, |
| 88 | rowCount: toolResult.data.count, |