mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
108 lines
4.0 KiB
JavaScript
108 lines
4.0 KiB
JavaScript
import { mkdir } from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
|
|
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
|
|
import { MANAGED_WORKFLOW_TOOL_CONTRACTS } from "../dist/contracts/managed/managedWorkflowToolContracts.js";
|
|
import { toolRegistrationOptions } from "../dist/server/toolRegistrationOptions.js";
|
|
import { ensureGeneratedFile } from "./lib/generated-file.mjs";
|
|
|
|
const arguments_ = new Set(process.argv.slice(2));
|
|
for (const argument of arguments_)
|
|
if (argument !== "--check")
|
|
throw new Error(`Unknown MCP tool catalog option: ${argument}`);
|
|
|
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
// This generated metadata supports schema conformance tests;
|
|
// production MCP tools are registered from their canonical contracts directly.
|
|
// Keep the test metadata portable and regenerate it with `npm run mcp-catalog:generate`.
|
|
const sessionToolNames = new Set([
|
|
...TOOL_CONTRACTS.filter(({ kind }) => kind === "session").map(
|
|
({ name }) => name,
|
|
),
|
|
...MANAGED_WORKFLOW_TOOL_CONTRACTS.map(({ name }) => name),
|
|
]);
|
|
const advertisedTools = await sdkToolCatalog();
|
|
const ajv = new Ajv2020({ strict: false, validateFormats: false });
|
|
for (const tool of advertisedTools.values()) {
|
|
for (const kind of ["inputSchema", "outputSchema"]) {
|
|
const schema = tool[kind];
|
|
if (schema !== undefined && !ajv.validateSchema(schema))
|
|
throw new Error(
|
|
`Invalid JSON Schema for ${tool.name}.${kind}: ${ajv.errorsText(ajv.errors)}`,
|
|
);
|
|
}
|
|
}
|
|
const catalog = TOOL_CONTRACTS.map((contract) => {
|
|
const advertised = advertisedTools.get(contract.name);
|
|
if (advertised === undefined)
|
|
throw new Error(`MCP SDK omitted registered tool ${contract.name}`);
|
|
return {
|
|
name: contract.name,
|
|
analysisOperation: [
|
|
"official-proxy",
|
|
"enhanced",
|
|
"native-provider",
|
|
"artifact-provider",
|
|
"managed-provider",
|
|
].includes(contract.kind)
|
|
? contract.name
|
|
: null,
|
|
title: advertised.title,
|
|
description: advertised.description,
|
|
kind: contract.kind,
|
|
requiresSession: sessionToolNames.has(contract.name),
|
|
inputSchema: advertised.inputSchema,
|
|
outputSchema: advertised.outputSchema,
|
|
annotations: advertised.annotations,
|
|
effects: contract.effects,
|
|
};
|
|
});
|
|
|
|
async function sdkToolCatalog() {
|
|
const server = new McpServer({ name: "rea-catalog-generator", version: "0" });
|
|
for (const contract of TOOL_CONTRACTS)
|
|
server.registerTool(
|
|
contract.name,
|
|
toolRegistrationOptions(contract),
|
|
async () => ({
|
|
content: [{ type: "text", text: "Catalog-only handler" }],
|
|
isError: true,
|
|
}),
|
|
);
|
|
const client = new Client({ name: "rea-catalog-generator", version: "0" });
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
try {
|
|
await server.connect(serverTransport);
|
|
await client.connect(clientTransport);
|
|
const { tools } = await client.listTools();
|
|
return new Map(tools.map((tool) => [tool.name, tool]));
|
|
} finally {
|
|
await Promise.allSettled([client.close(), server.close()]);
|
|
}
|
|
}
|
|
const canonicalizeJson = (value) => {
|
|
if (Array.isArray(value)) return value.map(canonicalizeJson);
|
|
if (value === null || typeof value !== "object") return value;
|
|
return Object.fromEntries(
|
|
Object.entries(value)
|
|
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
.map(([key, entry]) => [key, canonicalizeJson(entry)]),
|
|
);
|
|
};
|
|
|
|
const payloadJson = JSON.stringify(canonicalizeJson({ catalog }), null, 2);
|
|
const outputPath = join(root, ".cache/mcp-tool-catalog.json");
|
|
if (!arguments_.has("--check"))
|
|
await mkdir(dirname(outputPath), { recursive: true });
|
|
await ensureGeneratedFile({
|
|
path: outputPath,
|
|
source: `${payloadJson}\n`,
|
|
check: arguments_.has("--check"),
|
|
generateCommand: "npm run mcp-catalog:generate",
|
|
});
|