koala73/worldmonitor · error
tools/call error
Error message
tools/call error: ${callResp.error.message} What it means
After a successful initialize, mcp-proxy sends JSON-RPC `tools/call` (id 2) with the requested toolName and arguments (defaulting to {}). This error means the server accepted the session but rejected the call itself: unknown tool, arguments failing the tool's input schema, or the tool executed and reported its own failure through the RPC error channel.
Solutions
- List the server's tools (tools/list through the proxy, or `worldmonitor tools`) and copy the exact name
- Validate toolArgs against the tool's inputSchema — fill every required field with the right primitive type
- Decode the embedded message: -32602 invalid params, -32601 unknown tool/method, other codes are tool-specific failures
- If the tool itself failed, correct the inputs it names (bad country code, unknown id) and retry
Example fix
// before
const callResp = await session.send(2, 'tools/call', { name: toolName, arguments: toolArgs || {} });
if (callResp.error) throw new Error(`tools/call error: ${callResp.error.message}`);
// after — list tools first and fail with the available names
const listResp = await session.send(2, 'tools/list', {});
const tools = (listResp.result && listResp.result.tools) || [];
if (!tools.some((t) => t.name === toolName)) {
throw new Error(`Unknown tool: ${toolName}. Available: ${tools.map((t) => t.name).join(', ')}`);
} Defensive patterns
Strategy: validation
Validate before calling
// Fetch tools/list once, then validate name + required args before calling
const { result } = await mcpCallToolSse(serverUrl, '__list__', undefined, headers); // or a tools/list helper
const tool = tools.find((t) => t.name === toolName);
if (!tool) throw new UsageError(`Unknown tool ${toolName}`);
const required = tool.inputSchema?.required ?? [];
const missing = required.filter((k) => args?.[k] === undefined);
if (missing.length) throw new UsageError(`Missing args: ${missing.join(', ')}`); Type guard
function isToolDescriptor(v: unknown): v is { name: string; inputSchema?: { required?: string[] } } {
return typeof v === 'object' && v !== null && typeof (v as any).name === 'string';
} Try / catch
try {
await mcpCallToolSse(serverUrl, toolName, args, headers);
} catch (err) {
if (err instanceof Error && err.message.startsWith('tools/call error:')) {
// server-side rejection: unknown tool or invalid args — fix inputs, do not blind-retry
return { retryable: false, reason: err.message };
}
throw err;
} Prevention
- Cache tools/list per server and drive argument forms from the tool's inputSchema
- Validate required fields and primitive types client-side before sending tools/call
- Treat -32602/-32601 style rejections as permanent (fix the request), not transient
- Integration-test tool names after every target-server upgrade
When it happens
Trigger: toolName does not exist on the server (typo, renamed tool, different server version); toolArgs missing required fields or with wrong types per the tool's inputSchema; calling a tool with {} when it requires parameters; the tool ran and failed server-side, returning its error via the RPC layer.
Common situations: Frontend hardcodes a tool name that changed after a server update; callers assume the proxy validates arguments; passing nested objects as strings; one-shot SSE sessions used for tools that need prior session state.
Related errors
- get-food-stocks validation: country_code is required and…
- tools/call error: MCP server rejected request
- Airspace data unavailable: both civilian and military…
- app_destroyed
- body-too-large
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-08-21).
Data as JSON: /api/errors/6dc83c7a825ed6a3.
Report an issue: GitHub.
Appendix: source
Thrown at api/mcp-proxy.ts:620
const { response: callResp } = await postJson(sessionUrl, {
jsonrpc: '2.0', id: 3, method: 'tools/call',
params: { name: toolName, arguments: toolArgs || {} },
}, headers, sessionId);
if (!callResp.ok) throw new McpProxyUpstreamError(`tools/call failed: HTTP ${callResp.status}`);
const callData = await parseJsonRpcResponse(callResp);
if (callData.error) throw new McpProxyUpstreamError('tools/call error: MCP server rejected request');
return callData.result;
}
// --- SSE transport (HTTP+SSE, older MCP spec) ---
// Servers whose URL path ends with /sse use this protocol:
// 1. Client GETs the SSE URL — server opens a stream and emits an `endpoint` event
// containing the URL where the client should POST JSON-RPC messages.
// 2. Client POSTs JSON-RPC to that endpoint URL.
// 3. Server sends responses on the same SSE stream as `data:` lines.
function isSseTransport(url) {
const p = url.pathname;
return p === '/sse' || p.endsWith('/sse');
}
function makeDeferred() {
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
return { promise, resolve, reject };
}
class SseSession {
constructor(sseUrl, headers) {
this._sseUrl = sseUrl;
this._originHost = new URL(sseUrl).host;
this._originProtocol = new URL(sseUrl).protocol;
this._headers = headers;
this._endpointUrl = null;
this._endpointDeferred = makeDeferred();
this._pending = new Map(); // rpc id -> deferredView on GitHub (pinned to e586b8b4b8)