Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 49 additions & 2 deletions actions/setup/js/mount_mcp_as_cli.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,53 @@ function httpPostJSON(urlStr, headers, body, timeoutMs = DEFAULT_HTTP_TIMEOUT_MS
});
}

/**
* Parse an MCP response body that may be JSON or Server-Sent Events (SSE).
*
* Some MCP gateway responses are streamed as SSE and contain lines like:
* data: {"jsonrpc":"2.0","id":3,"result":{...}}
*
* @param {unknown} body - Parsed response body from httpPostJSON
* @returns {unknown}
*/
function parseMCPResponseBody(body) {
if (body && typeof body === "object" && !Array.isArray(body)) {
return body;
}
if (typeof body !== "string") {
return null;
}

const trimmed = body.trim();
if (!trimmed) {
return null;
}

try {
return JSON.parse(trimmed);
} catch {
// Fall through to SSE parsing.
}

/** @type {unknown} */
let lastDataMessage = null;
for (const line of trimmed.split(/\r?\n/)) {
if (!line.startsWith("data:")) {
continue;
}
const payload = line.slice(5).trim();
if (!payload || payload === "[DONE]") {
continue;
}
try {
lastDataMessage = JSON.parse(payload);
} catch {
// Ignore non-JSON SSE data lines.
}
}
return lastDataMessage;
}

/**
* Query the tools list from an MCP server via JSON-RPC.
* Follows the standard MCP handshake: initialize → notifications/initialized → tools/list.
Expand Down Expand Up @@ -196,7 +243,7 @@ async function fetchMCPTools(serverUrl, apiKey, core) {
// Step 3: tools/list – get the available tool definitions
try {
const listResp = await httpPostJSON(serverUrl, { ...authHeaders, ...sessionHeader }, { jsonrpc: "2.0", id: 2, method: "tools/list" }, DEFAULT_HTTP_TIMEOUT_MS);
const respBody = listResp.body;
const respBody = parseMCPResponseBody(listResp.body);
if (respBody && typeof respBody === "object" && "result" in respBody && respBody.result && typeof respBody.result === "object") {
const result = respBody.result;
if ("tools" in result && Array.isArray(result.tools)) {
Expand Down Expand Up @@ -396,4 +443,4 @@ async function main() {
core.setOutput("mounted-servers", mountedServers.join(","));
}

module.exports = { main, fetchMCPTools, generateCLIWrapperScript, isValidServerName, shellEscapeDoubleQuoted };
module.exports = { main, fetchMCPTools, generateCLIWrapperScript, isValidServerName, shellEscapeDoubleQuoted, parseMCPResponseBody };
41 changes: 41 additions & 0 deletions actions/setup/js/mount_mcp_as_cli.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// @ts-check
import { describe, expect, it } from "vitest";

import { parseMCPResponseBody } from "./mount_mcp_as_cli.cjs";
Comment on lines +2 to +4
Copy link

Copilot AI Apr 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most tests in actions/setup/js start with // @ts-check (e.g., actions/setup/js/call_workflow.test.cjs:1) to enable JS type-checking under @ts-check. Consider adding the same header here for consistency and to catch type issues early.

See below for a potential fix:

@ -1,3 +1,4 @@
// @ts-check

Copilot uses AI. Check for mistakes.

describe("mount_mcp_as_cli.cjs", () => {
it("parses JSON object responses unchanged", () => {
const body = { jsonrpc: "2.0", result: { tools: [{ name: "logs" }] } };
expect(parseMCPResponseBody(body)).toEqual(body);
});

it("parses raw JSON string responses", () => {
const body = '{"jsonrpc":"2.0","result":{"tools":[{"name":"logs"}]}}';
expect(parseMCPResponseBody(body)).toEqual({
jsonrpc: "2.0",
result: { tools: [{ name: "logs" }] },
});
});

it("parses SSE data lines and returns the JSON payload", () => {
const sseToolListPayload = 'data: {"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"logs","inputSchema":{"properties":{"count":{"type":"integer"}}}}]}}';
const body = ["event: message", sseToolListPayload, ""].join("\n");

expect(parseMCPResponseBody(body)).toEqual({
jsonrpc: "2.0",
id: 2,
result: {
tools: [
{
name: "logs",
inputSchema: {
properties: {
count: { type: "integer" },
},
},
},
],
},
});
});
});
Loading