can1357/oh-my-pi · error · SyntaxError

Malformed JSON-RPC response

Error message

Malformed JSON-RPC response

What it means

The HTTP response to a JSON-RPC POST was 2xx but its parsed JSON body is not a valid JSON-RPC 2.0 response — missing jsonrpc:"2.0" and neither result nor error. The transport throws SyntaxError("Malformed JSON-RPC response"), later normalized into a decode-stage MCPTransportError.

Source

Thrown at packages/coding-agent/src/mcp/transports/http.ts:503

					message: `HTTP ${response.status}: ${text}${suffix}`,
					retryable: response.status === 404 || response.status === 502 || response.status === 503,
					code: response.status,
					traceId,
				});
			}

			const contentType = response.headers.get("Content-Type") ?? "";

			// Handle SSE response
			if (contentType.includes("text/event-stream")) {
				return this.#parseSSEResponse<T>(response, id, options);
			}

			stage = "decode";
			// Handle JSON response
			const result: unknown = await response.json();
			if (!isRecord(result) || result.jsonrpc !== "2.0" || (!("result" in result) && !("error" in result))) {
				throw new SyntaxError("Malformed JSON-RPC response");
			}
			if (result.error !== undefined) {
				if (
					!isRecord(result.error) ||
					typeof result.error.code !== "number" ||
					typeof result.error.message !== "string"
				) {
					throw new SyntaxError("Malformed JSON-RPC error response");
				}
				throw createMCPJsonRpcError(
					"http",
					{ code: result.error.code, message: result.error.message, data: result.error.data },
					traceId,
				);
			}

			return result.result as T;
		} catch (error) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Log the raw response body to see what the server actually returned
  2. Verify the URL is the MCP JSON-RPC endpoint, not a REST or health-check route
  3. Upgrade/align the MCP server so responses follow JSON-RPC 2.0 ({jsonrpc:"2.0", id, result|error})
  4. Check for proxies/middleware that wrap or replace response bodies

Example fix

// before
// server returns {"ok":true,"data":{...}} → Malformed JSON-RPC response
// after
// server returns {"jsonrpc":"2.0","id":1,"result":{...}} — fix server or point client at the correct /mcp endpoint
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate a sample response shape during integration:
const body = await (await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "ping" }) })).json();
if (body?.jsonrpc !== "2.0") throw new Error("endpoint does not speak JSON-RPC 2.0");

Type guard

function isJsonRpcResponse(v: unknown): v is { jsonrpc: "2.0"; id: unknown; result?: unknown; error?: unknown } {
  return typeof v === "object" && v !== null && (v as any).jsonrpc === "2.0" && ("result" in v || "error" in v);
}

Try / catch

try {
  await transport.request(method, params);
} catch (err) {
  if (err instanceof SyntaxError && err.message === "Malformed JSON-RPC response") {
    log.error("non-JSON-RPC endpoint or rewriting proxy", { url });
  } else throw err;
}

Prevention

When it happens

Trigger: Response Content-Type was application/json but the body is arbitrary JSON: a plain error object, an HTML/error page served with a JSON content type, a proxy's JSON envelope, or a non-JSON-RPC API at the configured URL.

Common situations: Pointing the client at a REST endpoint instead of the MCP JSON-RPC endpoint; an API gateway rewriting error bodies; server bug returning unwrapped results; wrong protocol version speaking a different envelope.

Understand the failure class

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/ac75dc0e788125c2. Report an issue: GitHub.