Files
oh-my-pi/packages/coding-agent/src/mcp/json-rpc.ts
T
can1357 1f7c4f6f90 refactor(stream-parsing): restructured stream utilities to use Web standard APIs and generic SSE parsing
- Refactored SSE stream parsing to use generic `readSseJson` utility instead of domain-specific handlers across multiple packages.
- Migrated from Node.js Buffer API to Web standard APIs (Uint8Array, TextDecoder, DataView) for cross-platform compatibility.
- Simplified stream transformation pipeline by consolidating multiple stream operations into unified `createTextLineSplitter` utility.
- Refactored `ptree.ts` to remove complex stream pumping infrastructure and simplify stderr handling with direct async iteration.
- Rewrote stream utilities to operate at binary level with byte-level parsing for improved efficiency and reduced string allocations.
- Updated TypeScript configuration to include DOM.AsyncIterable type definitions for async iterable DOM API support.
2026-02-05 10:59:47 +01:00

85 lines
2.1 KiB
TypeScript

/**
* MCP JSON-RPC 2.0 over HTTPS.
*
* Lightweight utilities for calling MCP servers directly via HTTP
* without maintaining persistent connections.
*/
import { logger } from "@oh-my-pi/pi-utils";
/** Parse SSE response format (lines starting with "data: ") */
export function parseSSE(text: string): unknown {
const lines = text.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6).trim();
if (data === "[DONE]") continue;
const result = JSON.parse(data) as unknown;
if (result) return result;
}
}
// Fallback: try parsing entire response as JSON
try {
return JSON.parse(text);
} catch {
return null;
}
}
/** JSON-RPC 2.0 response structure */
export interface JsonRpcResponse<T = unknown> {
jsonrpc: "2.0";
id: string | number;
result?: T;
error?: {
code: number;
message: string;
data?: unknown;
};
}
/**
* Call an MCP server with JSON-RPC 2.0 over HTTPS.
*
* @param url - Full MCP server URL (including any query parameters)
* @param method - JSON-RPC method name (e.g., "tools/list", "tools/call")
* @param params - Method parameters
* @returns Parsed JSON-RPC response
*/
export async function callMCP<T = unknown>(
url: string,
method: string,
params?: Record<string, unknown>,
): Promise<JsonRpcResponse<T>> {
const body = {
jsonrpc: "2.0",
id: Math.random().toString(36).slice(2),
method,
params: params ?? {},
};
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json, text/event-stream",
},
body: JSON.stringify(body),
});
if (!response.ok) {
const errorMsg = `MCP request failed: ${response.status} ${response.statusText}`;
logger.error(errorMsg, { url, method, params });
throw new Error(errorMsg);
}
const text = await response.text();
const result = parseSSE(text) as JsonRpcResponse<T> | null;
if (!result) {
logger.error("Failed to parse MCP response", { url, method, responseText: text.slice(0, 500) });
throw new Error("Failed to parse MCP response");
}
return result;
}