* fix(browser): report cancellation after cleanup and source-map abort An interrupt during CDP cleanup could still publish the captured page, and a source-map download abort was wrapped as a provider failure. Both now return AnalysisCancelledError for the requested operation. * fix(setup): keep Devin configuration JSON-formatted The empty DSH_HOME fix also changed Devin's declared format to "devin", which is not a configuration format and stops the typecheck. Devin still writes mcp_config.json. * test(browser): keep cleanup cancellation coverage in its own suite The added interrupt cases made the existing lifecycle suite exceed the function-length lint limit. * test(ghidra): drop the duplicated path join import The inventory MCP test imported join twice, which fails lint on current main. * test(cli): type the retained skill directory pairs noUncheckedIndexedAccess treats the unlabeled pair list as an open array, so the directory passed to join was string or undefined. * fix(browser): preserve source-map cancellation results * style(test): format the partial Frida load cancellation fixture Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: REA Maintenance <rea-maintenance@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
3.6 KiB
TypeScript
95 lines
3.6 KiB
TypeScript
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import type { CallToolResult } from "@modelcontextprotocol/server";
|
|
import { STDIO_DEFAULT_MAX_BUFFER_SIZE } from "@modelcontextprotocol/server";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { TOOL_CONTRACTS } from "../../../src/contracts/toolContracts.js";
|
|
import { COMPACT_INPUT_SCHEMA_BUDGET_BYTES } from "../../../src/config/mcpInputSchemaProfile.js";
|
|
import { EvidenceMcpServer } from "../../../src/server/EvidenceMcpServer.js";
|
|
import { ToolResultDelivery } from "../../../src/server/toolResult.js";
|
|
import { toolRegistrationOptions } from "../../../src/server/toolRegistrationOptions.js";
|
|
|
|
const createServer = (profile: "full" | "compact"): EvidenceMcpServer =>
|
|
new EvidenceMcpServer(
|
|
{ name: "tool-input-admission-test", version: "0" },
|
|
{ capabilities: {} },
|
|
undefined,
|
|
new ToolResultDelivery(STDIO_DEFAULT_MAX_BUFFER_SIZE),
|
|
profile === "compact"
|
|
? { budgetBytes: COMPACT_INPUT_SCHEMA_BUDGET_BYTES }
|
|
: undefined,
|
|
);
|
|
|
|
// These tests cover the registered SDK boundary with deterministic sentinel
|
|
// handlers. They establish canonical rejection before dispatch; they do not
|
|
// exercise providers, targets, or the shape of SDK-generated error text.
|
|
describe("catalog-wide MCP input admission", () => {
|
|
it.each(["full", "compact"] as const)(
|
|
"rejects unknown root keys before dispatch with the %s advertised profile",
|
|
async (profile) => {
|
|
const server = createServer(profile);
|
|
const dispatches = new Map<string, number>();
|
|
for (const contract of TOOL_CONTRACTS) {
|
|
server.registerTool(
|
|
contract.name,
|
|
toolRegistrationOptions(contract),
|
|
async (): Promise<CallToolResult> => {
|
|
dispatches.set(
|
|
contract.name,
|
|
(dispatches.get(contract.name) ?? 0) + 1,
|
|
);
|
|
return {
|
|
content: [{ type: "text", text: "sentinel handler reached" }],
|
|
isError: true,
|
|
};
|
|
},
|
|
);
|
|
}
|
|
|
|
const client = new Client({
|
|
name: "tool-input-admission-test",
|
|
version: "0",
|
|
});
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
try {
|
|
await server.connect(serverTransport);
|
|
await client.connect(clientTransport);
|
|
|
|
expect(TOOL_CONTRACTS.length).toBeGreaterThan(0);
|
|
for (const contract of TOOL_CONTRACTS) {
|
|
const input = {
|
|
...contract.examples[0]?.input,
|
|
__rea_unknown_admission_probe__: true,
|
|
};
|
|
expect(contract.inputSchema.safeParse(input).success).toBe(false);
|
|
|
|
const result = await client.callTool({
|
|
name: contract.name,
|
|
arguments: input,
|
|
});
|
|
expect(result.isError, contract.name).toBe(true);
|
|
expect(dispatches.has(contract.name), contract.name).toBe(false);
|
|
}
|
|
|
|
const firstWithExample = TOOL_CONTRACTS.find(
|
|
(contract) => contract.examples[0] !== undefined,
|
|
);
|
|
const liveExample = firstWithExample?.examples[0];
|
|
if (firstWithExample === undefined || liveExample === undefined)
|
|
throw new Error(
|
|
"No canonical example is available for the liveness check",
|
|
);
|
|
const live = await client.callTool({
|
|
name: firstWithExample.name,
|
|
arguments: liveExample.input,
|
|
});
|
|
expect(live.isError).toBe(true);
|
|
expect(dispatches.get(firstWithExample.name)).toBe(1);
|
|
} finally {
|
|
await Promise.allSettled([client.close(), server.close()]);
|
|
}
|
|
},
|
|
);
|
|
});
|