feat(javascript): return a retained summary view on request (#1568)

analyze_javascript_application returns the complete application graph
inline, which repeats tens of kilobytes even for a few source lines and
exceeds the default 10 MiB MCP frame for a 300-function source file.

The new optional detail input keeps complete delivery by default. With
detail "summary" the server records the complete analysis Evidence in
the session and returns its inspect_analysis_view summary, whose
parent_evidence_id lets callers page modules, select items, or pass the
retained reference to application workflows. A server without Evidence
retention refuses summary detail with capability_unavailable rather
than returning a summary whose analysis cannot be reached.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-10 07:37:37 -07:00
1 parent 649661dc8b
commit c19e15a2b3
9 files changed
+279 -7

No files matched your search

@@ -0,0 +1,125 @@
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, onTestFinished } from "vitest";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { createServer } from "../../../src/server/createServer.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const contract = toolContract("analyze_javascript_application");
const connect = async (retainsEvidence: boolean) => {
const session = createTestBinarySession(() => {
throw new Error("JavaScript analysis must not start a deep provider");
});
const server = createServer(
retainsEvidence
? { kind: "session", session }
: {
kind: "fixed",
analysis: {
execute: () => {
throw new Error("JavaScript analysis must not use a target");
},
},
},
);
const client = new Client({ name: "analysis-summary-detail", 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);
return client;
};
const application = async () => {
const root = await createTestTempDirectory("rea-summary-detail-");
await writeFile(
join(root, "main.js"),
'import { helper } from "./util.js";\nexport const greet = (name) => helper(name);\n',
);
await writeFile(
join(root, "util.js"),
"export const helper = (value) => value.toUpperCase();\n",
);
return { input_path: root, format: "directory" };
};
const textBytes = (content: unknown): number => {
const [first] = Array.isArray(content) ? content : [];
if (first?.type !== "text") throw new Error("Missing MCP text result");
return Buffer.byteLength(String(first.text));
};
it("returns the summary of complete Evidence retained for later views", async () => {
const client = await connect(true);
const input = await application();
const complete = await client.callTool({
name: contract.name,
arguments: input,
});
expect(complete.isError).not.toBe(true);
const completeEvidence = contract.outputSchema.parse(
complete.structuredContent,
);
expect(completeEvidence.operation).toBe("analyze_javascript_application");
const summary = await client.callTool({
name: contract.name,
arguments: { ...input, detail: "summary" },
});
expect(summary.isError).not.toBe(true);
const summaryEvidence = contract.outputSchema.parse(
summary.structuredContent,
);
expect(summaryEvidence).toMatchObject({
operation: "inspect_analysis_view",
evidence_links: [completeEvidence.evidence_id],
normalized_result: {
kind: "summary",
parent_evidence_id: completeEvidence.evidence_id,
parent_operation: "analyze_javascript_application",
},
});
expect(textBytes(summary.content)).toBeLessThan(textBytes(complete.content));
const modules = await client.callTool({
name: "inspect_analysis_view",
arguments: {
source: {
kind: "retained-evidence",
evidence_id: completeEvidence.evidence_id,
},
view: { kind: "page", collection: "modules", offset: 0, limit: 8 },
},
});
expect(modules.isError).not.toBe(true);
expect(modules.structuredContent).toMatchObject({
normalized_result: {
kind: "page",
parent_evidence_id: completeEvidence.evidence_id,
},
});
});
it("refuses summary detail when the server cannot retain the complete Evidence", async () => {
const client = await connect(false);
const failure = parseMcpToolError(
await client.callTool({
name: contract.name,
arguments: { ...(await application()), detail: "summary" },
}),
);
expect(failure.error).toMatchObject({
code: "capability_unavailable",
message: expect.stringContaining("detail complete"),
});
});