(name: string, args: Record<string, unknown>)
| 127 | } |
| 128 | |
| 129 | async callTool(name: string, args: Record<string, unknown>): Promise<string> { |
| 130 | await this.initialize(); |
| 131 | |
| 132 | const res = await fetch(`${getBaseUrl()}/mcp`, { |
| 133 | method: "POST", |
| 134 | headers: this.buildHeaders(), |
| 135 | body: JSON.stringify({ |
| 136 | jsonrpc: "2.0", |
| 137 | id: nanoid(), |
| 138 | method: "tools/call", |
| 139 | params: { name, arguments: args }, |
| 140 | } satisfies McpRequest), |
| 141 | }); |
| 142 | |
| 143 | // Session expired — reinitialize once and retry |
| 144 | if (res.status === 400 || res.status === 404) { |
| 145 | this.sessionId = null; |
| 146 | this.initPromise = null; |
| 147 | await this.initialize(); |
| 148 | return this.callTool(name, args); |
| 149 | } |
| 150 | |
| 151 | if (!res.ok) { |
| 152 | throw new ApiError(res.status, `MCP tool "${name}" failed`); |
| 153 | } |
| 154 | |
| 155 | const contentType = res.headers.get("content-type") ?? ""; |
| 156 | if (contentType.includes("text/event-stream")) { |
| 157 | return this.parseSseResponse(res); |
| 158 | } |
| 159 | |
| 160 | const json = (await res.json()) as McpResponse<McpToolResult>; |
| 161 | if (json.error) { |
| 162 | throw new ApiError(500, json.error.message); |
| 163 | } |
| 164 | const toolResult = json.result; |
| 165 | if (toolResult?.isError) { |
| 166 | throw new ApiError(500, toolResult.content[0]?.text ?? "Tool error"); |
| 167 | } |
| 168 | return toolResult?.content?.[0]?.text ?? ""; |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | const mcpClient = new McpClient(); |
no test coverage detected