{"record":{"id":"c298a932b7ad8803","repo":"koala73/worldmonitor","slug":"tools-call-error-mcp-server-rejected-request","errorCode":null,"errorMessage":"tools/call error: MCP server rejected request","messagePattern":"tools/call error: MCP server rejected request","errorType":"exception","errorClass":"McpProxyUpstreamError","httpStatus":null,"severity":"error","filePath":"api/mcp-proxy.ts","lineNumber":607,"sourceCode":"  return listData.result?.tools || [];\n}\n\nasync function mcpCallTool(serverUrl, toolName, toolArgs, customHeaders) {\n  const { response: initResp, url: sessionUrl, headers } = await postJson(\n    serverUrl, buildInitPayload(), buildHeaders(customHeaders), null,\n  );\n  if (!initResp.ok) throw new McpProxyUpstreamError(`Initialize failed: HTTP ${initResp.status}`);\n  const sessionId = initResp.headers.get('Mcp-Session-Id') || initResp.headers.get('mcp-session-id');\n  const initData = await parseJsonRpcResponse(initResp);\n  if (initData.error) throw new McpProxyUpstreamError('Initialize error: MCP server rejected request');\n  await sendInitialized(sessionUrl, headers, sessionId);\n  const { response: callResp } = await postJson(sessionUrl, {\n    jsonrpc: '2.0', id: 3, method: 'tools/call',\n    params: { name: toolName, arguments: toolArgs || {} },\n  }, headers, sessionId);\n  if (!callResp.ok) throw new McpProxyUpstreamError(`tools/call failed: HTTP ${callResp.status}`);\n  const callData = await parseJsonRpcResponse(callResp);\n  if (callData.error) throw new McpProxyUpstreamError('tools/call error: MCP server rejected request');\n  return callData.result;\n}\n\n// --- SSE transport (HTTP+SSE, older MCP spec) ---\n// Servers whose URL path ends with /sse use this protocol:\n//   1. Client GETs the SSE URL — server opens a stream and emits an `endpoint` event\n//      containing the URL where the client should POST JSON-RPC messages.\n//   2. Client POSTs JSON-RPC to that endpoint URL.\n//   3. Server sends responses on the same SSE stream as `data:` lines.\n\nfunction isSseTransport(url) {\n  const p = url.pathname;\n  return p === '/sse' || p.endsWith('/sse');\n}\n\nfunction makeDeferred() {\n  let resolve, reject;\n  const promise = new Promise((res, rej) => { resolve = res; reject = rej; });","sourceCodeStart":589,"sourceCodeEnd":625,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/api/mcp-proxy.ts#L589-L625","documentation":"The MCP session was initialized successfully, but the tools/call JSON-RPC request returned HTTP 2xx with a JSON-RPC error object instead of a result. mcpCallTool throws McpProxyUpstreamError('tools/call error: MCP server rejected request') to signal the server refused the specific tool invocation.","triggerScenarios":"Thrown from mcpCallTool (called by the result handler) when callData = await parseJsonRpcResponse(callResp) contains { error: ... } for the tools/call request (id: 3) — the session and handshake succeeded but the call itself was rejected.","commonSituations":"Calling a tool name that does not exist on the server (JSON-RPC 'unknown tool' error); passing arguments that fail the tool's input schema validation; the session's Mcp-Session-Id expiring before the call (idle timeout or non-sticky load balancing); the supplied credentials lack permission to execute that tool; sending toolArgs in the wrong shape (not an object).","solutions":["Read the JSON-RPC error code/message from the response — it distinguishes unknown tool, invalid arguments, and permission failures.","Validate toolArgs against the tool's input schema (fetch it via tools/list) before calling, and ensure it is a plain object.","Confirm the tool name matches exactly what tools/list returned (case-sensitive, including namespace prefixes).","Keep the session alive: make the tools/call soon after initialize and use sticky sessions so Mcp-Session-Id stays valid.","Check the credentials/headers passed to the proxy grant execute rights for the requested tool."],"exampleFix":"// before: arguments don't match the tool's input schema\nmcpCallTool(url, 'search', { q: 'world' }, headers);\n// after: match the declared schema (parameter is 'query', string, required)\nmcpCallTool(url, 'search', { query: 'world' }, headers);","handlingStrategy":"validation","validationCode":"// before calling, confirm the tool exists and arguments match its schema\nconst tools = await mcpProxy.tools(serverUrl, headers);\nconst tool = tools.find(t => t.name === toolName);\nif (!tool) throw new Error('Unknown tool: ' + toolName);\nconst required = tool.inputSchema?.required || [];\nfor (const key of required) {\n  if (!(key in (toolArgs || {}))) throw new Error('Missing required argument: ' + key);\n}","typeGuard":"function isJsonRpcError(msg) {\n  return typeof msg === 'object' && msg !== null && 'error' in msg && msg.error !== undefined;\n}","tryCatchPattern":"try {\n  const result = await mcpProxy.result(serverUrl, toolName, toolArgs, headers);\n  return result;\n} catch (error) {\n  if (error instanceof McpProxyUpstreamError && error.message.startsWith('tools/call error')) {\n    // JSON-RPC rejection: unknown tool, schema mismatch, or permission — retry once with a fresh session\n    return retryToolCallWithFreshSession(serverUrl, toolName, toolArgs, headers);\n  }\n  throw error;\n}","preventionTips":["Fetch tools/list first and validate toolArgs against the tool's inputSchema before invoking.","Match tool names exactly, including any namespace prefix, as returned by tools/list.","Issue tools/call promptly after initialize and use sticky sessions so Mcp-Session-Id does not expire.","Confirm the credentials for the server grant execute permission on the specific tool."],"tags":["mcp","json-rpc","tool-call","validation","upstream"],"backgroundTag":"upstream-api-error","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}