fix(jeb): confine an invalid REA_JEB_MCP_URL to the JEB tools (#2145)

JebProvider throws when REA_JEB_MCP_URL fails endpoint validation, and the
composition built it eagerly for the CLI and every MCP connection. A
malformed value, or http://localhost:8425/mcp, therefore crashed every CLI
command with a stack trace and failed every MCP connection with an
internal server error. The composition now parses the endpoint first and,
when it is unusable, returns a JEB port that reports not_configured
availability and a ConfigurationError naming REA_JEB_MCP_URL.

Fixes #2143

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822 2026-10-12 06:49:15 -07:00 committed by GitHub
parent 58fb372071
commit df2c08b546
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 3 deletions

View File

@ -1,13 +1,48 @@
import type { JebAnalysisPort } from "../application/jeb/JebAnalysisPort.js";
import { ConfigurationError } from "../domain/configurationErrors.js";
import { err } from "../domain/result.js";
import type { JebMcpConnectionFactory } from "../jeb/JebMcpConnection.js";
import { parseJebMcpEndpoint } from "../jeb/JebMcpEndpoint.js";
import { JebProvider } from "../jeb/JebProvider.js";
/** Construct one JEB provider bound to the caller-selected MCP endpoint. */
/**
* Construct one JEB provider bound to the caller-selected MCP endpoint. An
* unusable endpoint makes only JEB unavailable; every other CLI command and
* MCP tool is composed alongside it.
*/
export const createJebAnalysisProvider = (
environment: Readonly<Record<string, string | undefined>> = process.env,
connectionFactory?: JebMcpConnectionFactory,
): JebAnalysisPort =>
new JebProvider({
): JebAnalysisPort => {
const endpoint = parseJebMcpEndpoint(environment.REA_JEB_MCP_URL);
if (!endpoint.ok) {
const constraint =
typeof endpoint.error.cause === "string"
? endpoint.error.cause
: endpoint.error.message;
return unconfiguredJebProvider(
new ConfigurationError("Invalid REA environment configuration", {
cause: endpoint.error,
settings: [{ setting: "REA_JEB_MCP_URL", constraint }],
}),
);
}
return new JebProvider({
environment,
...(connectionFactory === undefined ? {} : { connectionFactory }),
});
};
const unconfiguredJebProvider = (
error: ConfigurationError,
): JebAnalysisPort => ({
inspectAvailability: () =>
Promise.resolve({
status: "unavailable",
code: "not_configured",
reason: `REA_JEB_MCP_URL is invalid: ${error.settings[0]?.constraint ?? error.message}`,
diagnostics: { setting: "REA_JEB_MCP_URL" },
}),
close: () => Promise.resolve(),
execute: () => Promise.resolve(err(error)),
});

View File

@ -30,3 +30,14 @@ it("validates decompile item input before contacting the engine", async () => {
],
});
});
it("reports an invalid endpoint without breaking other commands", async () => {
const environment = { REA_JEB_MCP_URL: "http://localhost:8425/mcp" };
const jeb = await serveCli(["inspect-jeb-client", "--json"], environment);
expect(JSON.parse(jeb.stdout)).toMatchObject({
code: "configuration_invalid",
details: { settings: [{ setting: "REA_JEB_MCP_URL" }] },
});
const capabilities = await serveCli(["capabilities", "--json"], environment);
expect(capabilities.exitCode).toBe(0);
});

View File

@ -141,3 +141,45 @@ it("preserves JEB provider failure reasons through the MCP boundary", async () =
expect(text).toContain("ECONNREFUSED");
expect(text).toContain("REA_JEB_MCP_URL");
});
it("keeps the MCP server usable when REA_JEB_MCP_URL is invalid", async () => {
const session = createTestBinarySession(() => {
throw new Error("deep provider must not start");
});
const server = createServer(
{ kind: "session", session },
{ environment: { REA_JEB_MCP_URL: "http://localhost:8425/mcp" } },
);
const client = new Client({ name: "jeb-invalid-endpoint", version: "1" });
onTestFinished(async () => {
await client.close();
await server.close();
await session.close();
});
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
await client.connect(clientTransport);
expect((await client.listTools()).tools.length).toBeGreaterThan(0);
const failed = await client.callTool({
name: "inspect_jeb_client",
arguments: {},
});
expect(failed.isError).toBe(true);
const text = (failed.content ?? [])
.map((block) => (block.type === "text" ? block.text : ""))
.join("");
expect(JSON.parse(text)).toMatchObject({
error: {
code: "configuration_invalid",
details: {
settings: [
{
setting: "REA_JEB_MCP_URL",
constraint: expect.stringContaining("loopback"),
},
],
},
},
});
});