Files
rea/src/catalogIdentity.ts
YsopwnableandKaoru0822 6395e8d300 perf(mcp)!: compact repeated session discovery facts (#1988)
Group repeated session tool availability and provider capability facts, preserve complete named contracts and ordered limitations, and remove duplicate catalog descriptors. Update consumers and migration guidance.

Validated by all 31 CI jobs, focused source checks, and actual Claude Code/Linux Hopper/native Windows Ghidra small-target workflows on the current integration tree.

Refs #723.

BREAKING CHANGE: binary_session no longer includes server_identity.catalog.tools; tool_availability and provider capabilities use grouped facts documented in docs/mcp-contracts.md.

Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
2026-10-12 07:40:19 +08:00

78 lines
2.2 KiB
TypeScript

import { createHash } from "node:crypto";
import canonicalize from "canonicalize";
import { z } from "zod";
import { PROMPT_CONTRACTS } from "./contracts/promptContracts.js";
import { TOOL_CONTRACTS } from "./contracts/toolContracts.js";
import { CLI_COMMAND_NAMES } from "./cliCommandNames.js";
import { compareUnicodeCodePoints } from "./domain/unicodeCodePointOrder.js";
const sortedToolContracts = [...TOOL_CONTRACTS].sort((left, right) =>
compareUnicodeCodePoints(left.name, right.name),
);
const createToolCatalog = () =>
sortedToolContracts.map((contract) => ({
name: contract.name,
title: contract.title,
surface: contract.kind,
description: contract.description,
effects: { ...contract.effects },
annotations: contract.annotations,
input_schema: {
type: "object",
...z.toJSONSchema(contract.inputSchema, {
unrepresentable: "any",
}),
},
output_schema: {
type: "object",
...z.toJSONSchema(contract.outputSchema, {
unrepresentable: "any",
}),
},
}));
const promptCatalog = PROMPT_CONTRACTS.map((contract) => ({
name: contract.name,
title: contract.title,
description: contract.description,
arguments: contract.arguments,
steps: contract.steps,
})).sort((left, right) => compareUnicodeCodePoints(left.name, right.name));
const digest = (value: unknown): string => {
const encoded = canonicalize(value);
if (encoded === undefined)
throw new TypeError("Catalog is not canonical JSON");
return createHash("sha256").update(encoded).digest("hex");
};
const createCatalogDigests = () => {
const tools = createToolCatalog();
return {
tools_sha256: digest(tools),
prompts_sha256: digest(promptCatalog),
combined_sha256: digest({
cli: CLI_COMMAND_NAMES,
tools,
prompts: promptCatalog,
}),
} as const;
};
let catalogDigests: ReturnType<typeof createCatalogDigests> | undefined;
/** Stable, schema-sensitive identity; schema digests are computed on first use. */
export const CATALOG_IDENTITY = {
counts: {
cli_commands: CLI_COMMAND_NAMES.length,
mcp_tools: sortedToolContracts.length,
mcp_prompts: promptCatalog.length,
},
get digests() {
return (catalogDigests ??= createCatalogDigests());
},
} as const;