Merge pull request #442 from morluto/fix/mcp-cold-start

perf(mcp): reduce cold-start work
This commit is contained in:
morluto 2026-07-26 23:31:04 +08:00 committed by GitHub
commit 8ec9bb1bfe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1437 additions and 298 deletions

View File

@ -55,6 +55,8 @@
"deps:check": "node scripts/check-dependency-install.mjs",
"metadata:generate": "node scripts/generate-package-metadata.mjs",
"metadata:check": "node scripts/generate-package-metadata.mjs --check",
"mcp-catalog:generate": "npm run build:cached && node scripts/generate-mcp-tool-catalog.mjs",
"mcp-catalog:check": "node scripts/generate-mcp-tool-catalog.mjs --check",
"prebuild": "npm run deps:check && npm run metadata:check",
"build": "tsc -p tsconfig.build.json",
"precheck": "npm run deps:check",
@ -124,9 +126,9 @@
"evidence:check": "npm run build:cached && node scripts/generate-completion-ledger.mjs --check",
"docs:api": "typedoc",
"docs:api:cached": "turbo run docs:api",
"docs:generate": "npm run metadata:generate && npm run build:cached && node scripts/generate-skill-metadata.mjs && node scripts/generate-product-catalog.mjs && node scripts/generate-completion-ledger.mjs && npm run docs:api:cached && node scripts/generate-error-schema.mjs",
"docs:generate": "npm run metadata:generate && npm run build:cached && node scripts/generate-mcp-tool-catalog.mjs && node scripts/generate-skill-metadata.mjs && node scripts/generate-product-catalog.mjs && node scripts/generate-completion-ledger.mjs && npm run docs:api:cached && node scripts/generate-error-schema.mjs",
"docs:check": "turbo run docs:check:built",
"docs:check:built": "node scripts/generate-package-metadata.mjs --check && node scripts/generate-skill-metadata.mjs --check && node scripts/generate-product-catalog.mjs --check && node scripts/generate-completion-ledger.mjs --check && node scripts/check-doc-facts.mjs && node scripts/generate-error-schema.mjs --check",
"docs:check:built": "node scripts/generate-package-metadata.mjs --check && node scripts/generate-mcp-tool-catalog.mjs --check && node scripts/generate-skill-metadata.mjs --check && node scripts/generate-product-catalog.mjs --check && node scripts/generate-completion-ledger.mjs --check && node scripts/check-doc-facts.mjs && node scripts/generate-error-schema.mjs --check",
"docs:error-schema": "npm run build:cached && node scripts/generate-error-schema.mjs",
"prepack": "node scripts/prepack.mjs",
"prepare": "node scripts/prepare.mjs"

View File

@ -0,0 +1,153 @@
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { format } from "prettier";
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
import { MANAGED_WORKFLOW_TOOL_CONTRACTS } from "../dist/contracts/managedWorkflowToolContracts.js";
import { ArtifactProvider } from "../dist/artifacts/ArtifactProvider.js";
import { ManagedStaticProvider } from "../dist/dotnet/ManagedStaticProvider.js";
import { NativeMacOSProvider } from "../dist/native/NativeMacOSProvider.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)), "..");
const sessionToolNames = new Set([
...TOOL_CONTRACTS.filter(({ kind }) => kind === "session").map(
({ name }) => name,
),
...MANAGED_WORKFLOW_TOOL_CONTRACTS.map(({ name }) => name),
]);
const catalog = TOOL_CONTRACTS.map((contract) => {
const options = toolRegistrationOptions(contract);
return {
name: contract.name,
analysisOperation: [
"official-proxy",
"enhanced",
"native-provider",
"artifact-provider",
"managed-provider",
].includes(contract.kind)
? contract.name
: null,
title: options.title,
description: options.description,
kind: contract.kind,
requiresSession: sessionToolNames.has(contract.name),
inputSchema: options.inputSchema["~standard"].jsonSchema.input({
target: "draft-2020-12",
}),
outputSchema: options.outputSchema["~standard"].jsonSchema.output({
target: "draft-2020-12",
}),
annotations: options.annotations,
effects: contract.effects,
};
});
const auxiliaryProviders = [
new ArtifactProvider(false, false),
new NativeMacOSProvider(),
new ManagedStaticProvider(),
].map((provider) => ({
identity: provider.identity(),
capabilities: provider.capabilities(),
}));
const payloadJson = JSON.stringify({ catalog, auxiliaryProviders });
const source = await format(
`import type { ToolAnnotations } from "@modelcontextprotocol/server";
import type {
CapabilityDescriptor,
ProviderIdentity,
} from "./application/AnalysisProvider.js";
import type { ToolKind } from "./contracts/toolContractTypes.js";
import type { ToolEffects } from "./contracts/toolEffects.js";
interface GeneratedTool {
readonly name: string;
readonly analysisOperation: CapabilityDescriptor["operation"] | null;
readonly title: string;
readonly description: string;
readonly kind: ToolKind;
readonly requiresSession: boolean;
readonly inputSchema: Readonly<Record<string, unknown>>;
readonly outputSchema: Readonly<Record<string, unknown>>;
readonly annotations: ToolAnnotations;
readonly effects: ToolEffects;
}
interface GeneratedProvider {
readonly identity: ProviderIdentity;
readonly capabilities: readonly CapabilityDescriptor[];
}
interface GeneratedPayload {
readonly catalog: readonly GeneratedTool[];
readonly auxiliaryProviders: readonly GeneratedProvider[];
}
const GENERATED_PAYLOAD_JSON = ${JSON.stringify(payloadJson)};
const generatedPayload: unknown = JSON.parse(GENERATED_PAYLOAD_JSON);
if (!isGeneratedPayload(generatedPayload))
throw new TypeError("Generated MCP catalog payload is invalid");
/** Generated from TOOL_CONTRACTS; do not edit. */
export const GENERATED_MCP_TOOL_CATALOG = generatedPayload.catalog;
/** Generated lightweight metadata for lazily loaded auxiliary providers. */
export const GENERATED_AUXILIARY_PROVIDERS = generatedPayload.auxiliaryProviders;
function isGeneratedPayload(value: unknown): value is GeneratedPayload {
return (
isRecord(value) &&
Array.isArray(value.catalog) &&
value.catalog.every(isGeneratedTool) &&
Array.isArray(value.auxiliaryProviders) &&
value.auxiliaryProviders.every(isGeneratedProvider)
);
}
function isGeneratedTool(value: unknown): value is GeneratedTool {
return (
isRecord(value) &&
typeof value.name === "string" &&
(typeof value.analysisOperation === "string" ||
value.analysisOperation === null) &&
typeof value.title === "string" &&
typeof value.description === "string" &&
typeof value.kind === "string" &&
typeof value.requiresSession === "boolean" &&
isRecord(value.inputSchema) &&
isRecord(value.outputSchema) &&
isRecord(value.annotations) &&
isRecord(value.effects)
);
}
function isGeneratedProvider(value: unknown): value is GeneratedProvider {
return (
isRecord(value) &&
isRecord(value.identity) &&
Array.isArray(value.capabilities)
);
}
function isRecord(
value: unknown,
): value is Readonly<Record<string, unknown>> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
`,
{ parser: "typescript" },
);
await ensureGeneratedFile({
path: join(root, "src/generatedMcpToolCatalog.ts"),
source,
check: arguments_.has("--check"),
generateCommand: "npm run mcp-catalog:generate",
});

View File

@ -0,0 +1,62 @@
interface McpStartupPolicy {
readonly initializeBudgetMs: number;
readonly toolsListBudgetMs: number;
readonly firstCatalogBudgetMs: number;
readonly doctorDeadlineMs: number;
}
interface McpProcessOptions {
readonly command: string;
readonly args: readonly string[];
readonly environment: Readonly<Record<string, string | undefined>>;
readonly policy: McpStartupPolicy;
}
interface McpModuleProfileOptions extends McpProcessOptions {
readonly packageName: string;
}
interface RssMeasurement {
readonly supported: boolean;
readonly method: string | null;
readonly peak_bytes: number | null;
}
/** Measure the first usable MCP catalog through a child-process boundary. */
export function measureMcpStartup(options: McpProcessOptions): Promise<{
readonly process_started_to_initialized_ms: number;
readonly initialized_to_tools_list_ms: number;
readonly process_started_to_tools_list_ms: number;
readonly listed_tool_count: number;
readonly rss: RssMeasurement;
}>;
/** Profile ESM file loading in a separate diagnostic MCP child. */
export function profileMcpModuleLoading(
options: McpModuleProfileOptions,
): Promise<{
readonly supported: true;
readonly method: "node-debug-esm";
readonly unique_file_modules: number;
readonly unique_package_modules: number;
readonly unique_dependency_modules: number;
readonly debug_lines: number;
readonly truncated: boolean;
}>;
/** Parse Linux procfs VmRSS text into bytes. */
export function parseLinuxVmRssBytes(status: string): number | null;
/** Collect unique file-backed ESM module URLs from Node diagnostics. */
export function createEsmModuleProfileCollector(packageName?: string): {
append(chunk: string): void;
result(): {
readonly supported: true;
readonly method: "node-debug-esm";
readonly unique_file_modules: number;
readonly unique_package_modules: number;
readonly unique_dependency_modules: number;
readonly debug_lines: number;
readonly truncated: boolean;
};
};

View File

@ -0,0 +1,217 @@
import { readFileSync } from "node:fs";
import { performance } from "node:perf_hooks";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
const RSS_SAMPLE_INTERVAL_MS = 10;
const MAXIMUM_PROFILE_BUFFER_BYTES = 2 * 1_024 * 1_024;
const MAXIMUM_PROFILE_MODULES = 10_000;
/**
* Measure the first usable MCP catalog through the installed process boundary.
*/
export async function measureMcpStartup({
command,
args,
environment,
policy,
}) {
const transport = new StdioClientTransport({
command,
args,
env: environment,
stderr: "pipe",
});
let stderr = "";
transport.stderr?.on("data", (chunk) => {
stderr = boundedAppend(stderr, chunk.toString());
});
const rss = createLinuxRssSampler(() => transport.pid);
const client = new Client({
name: "package-startup-probe",
version: "1.0.0",
});
const startedAt = performance.now();
rss.start();
try {
await client.connect(transport, { timeout: policy.initializeBudgetMs });
const initializedAt = performance.now();
const tools = await client.listTools(undefined, {
timeout: policy.toolsListBudgetMs,
});
const listedAt = performance.now();
rss.stop();
const measurement = {
process_started_to_initialized_ms: elapsed(startedAt, initializedAt),
initialized_to_tools_list_ms: elapsed(initializedAt, listedAt),
process_started_to_tools_list_ms: elapsed(startedAt, listedAt),
listed_tool_count: tools.tools.length,
rss: rss.result(),
};
assertStartupBudgets(measurement, policy);
return measurement;
} catch (cause) {
throw new Error(`packaged MCP startup probe failed: ${stderr}`, { cause });
} finally {
rss.stop();
await Promise.allSettled([client.close(), transport.close()]);
}
}
/**
* Profile ESM file loading in a separate diagnostic child.
*/
export async function profileMcpModuleLoading({
command,
args,
environment,
policy,
packageName,
}) {
const profile = createEsmModuleProfileCollector(packageName);
const transport = new StdioClientTransport({
command,
args,
env: { ...environment, NODE_DEBUG: "esm" },
stderr: "pipe",
});
transport.stderr?.on("data", (chunk) => profile.append(chunk.toString()));
const client = new Client({
name: "package-module-profile",
version: "1.0.0",
});
try {
await client.connect(transport, {
timeout: policy.doctorDeadlineMs,
});
await client.listTools(undefined, {
timeout: policy.toolsListBudgetMs,
});
return profile.result();
} finally {
await Promise.allSettled([client.close(), transport.close()]);
}
}
/**
* Parse Linux procfs VmRSS text into bytes.
*/
export const parseLinuxVmRssBytes = (status) => {
const match = /^VmRSS:\s+(\d+)\s+kB$/mu.exec(status);
return match === null ? null : Number(match[1]) * 1_024;
};
/**
* Collect unique file-backed ESM module URLs from Node's diagnostic stream.
*/
export const createEsmModuleProfileCollector = (packageName = "rea-agents") => {
const modules = new Set();
let buffer = "";
let debugLines = 0;
let truncated = false;
const consume = (line) => {
if (line.startsWith("ESM ")) debugLines += 1;
for (const match of line.matchAll(/file:\/\/\/[^\s'",}\])]+/gu)) {
if (modules.size >= MAXIMUM_PROFILE_MODULES) {
truncated = true;
break;
}
modules.add(match[0]);
}
};
return {
append(chunk) {
buffer += chunk;
if (Buffer.byteLength(buffer) > MAXIMUM_PROFILE_BUFFER_BYTES) {
buffer = buffer.slice(-MAXIMUM_PROFILE_BUFFER_BYTES);
truncated = true;
}
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) consume(line);
},
result() {
if (buffer.length > 0) consume(buffer);
const values = [...modules];
const packageSegment = `/node_modules/${packageName}/`;
return {
supported: true,
method: "node-debug-esm",
unique_file_modules: values.length,
unique_package_modules: values.filter((value) =>
value.includes(packageSegment),
).length,
unique_dependency_modules: values.filter(
(value) =>
value.includes("/node_modules/") && !value.includes(packageSegment),
).length,
debug_lines: debugLines,
truncated,
};
},
};
};
const createLinuxRssSampler = (pid) => {
if (process.platform !== "linux")
return {
start() {},
stop() {},
result: () => ({
supported: false,
method: null,
peak_bytes: null,
}),
};
let peakBytes = null;
let timer;
const sample = () => {
const currentPid = pid();
if (currentPid === null) return;
try {
const current = parseLinuxVmRssBytes(
readFileSync(`/proc/${String(currentPid)}/status`, "utf8"),
);
if (current !== null && (peakBytes === null || current > peakBytes))
peakBytes = current;
} catch {
// The short-lived child may exit between resolving its PID and reading.
}
};
return {
start() {
sample();
timer = setInterval(sample, RSS_SAMPLE_INTERVAL_MS);
timer.unref();
},
stop() {
if (timer !== undefined) clearInterval(timer);
sample();
},
result: () => ({
supported: true,
method: "linux-proc-status-vmrss-sampled",
peak_bytes: peakBytes,
}),
};
};
const assertStartupBudgets = (measurement, policy) => {
if (measurement.process_started_to_initialized_ms > policy.initializeBudgetMs)
throw new Error("packaged MCP exceeded the initialize regression budget");
if (measurement.initialized_to_tools_list_ms > policy.toolsListBudgetMs)
throw new Error("packaged MCP exceeded the tools/list regression budget");
if (
measurement.process_started_to_tools_list_ms > policy.firstCatalogBudgetMs
)
throw new Error(
"packaged MCP exceeded the first-catalog regression budget",
);
};
const elapsed = (startedAt, completedAt) =>
Math.round((completedAt - startedAt) * 100) / 100;
const boundedAppend = (current, chunk) =>
`${current}${chunk}`.slice(-64 * 1_024);

View File

@ -8,6 +8,7 @@ import { fileURLToPath } from "node:url";
import { createPackage } from "@electron/asar";
import { evaluateCodexEvents } from "../dist/evaluation/CodexAgentEval.js";
import { MCP_STARTUP_POLICY } from "../dist/mcpStartupPolicy.js";
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@ -346,7 +347,7 @@ async function runCodex(prompt, investigationRoot) {
"-c",
`mcp_servers.rea.args=${JSON.stringify([join(repositoryRoot, "scripts/rea.mjs"), "mcp"])}`,
"-c",
"mcp_servers.rea.startup_timeout_sec=30",
`mcp_servers.rea.startup_timeout_sec=${String(MCP_STARTUP_POLICY.codexStartupTimeoutSeconds)}`,
"-c",
mcpEnvironmentOverride,
...(optionalModel === undefined ? [] : ["--model", optionalModel]),

View File

@ -5,6 +5,7 @@ import { promisify } from "node:util";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import { MCP_STARTUP_POLICY } from "../dist/mcpStartupPolicy.js";
import * as prompts from "./verify-package-prompts.mjs";
import {
json,
@ -180,7 +181,7 @@ export async function verifyPackageMcp({
(
await execute(cli, ["mcp", "doctor", "--json"], {
env: environment,
timeout: 30_000,
timeout: MCP_STARTUP_POLICY.doctorDeadlineMs,
})
).stdout,
);

View File

@ -4,6 +4,7 @@ import { join } from "node:path";
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
import { PRODUCT_IDENTITY } from "../dist/identity.js";
import { MCP_STARTUP_POLICY } from "../dist/mcpStartupPolicy.js";
import * as prompts from "./verify-package-prompts.mjs";
import { verifyPackagedInvestigation } from "./verify-package-investigation.mjs";
import { verifyPackageArtifactAndElectron } from "./verify-package-artifact.mjs";
@ -19,6 +20,10 @@ import { verifyPackageSetup } from "./verify-package-setup.mjs";
import { verifyManaged } from "./verify-package-managed.mjs";
import { verifyUnknownProvider } from "./verify-package-unknown-provider.mjs";
import { completeVerifierRun, createVerifierRun } from "./lib/verifier-run.mjs";
import {
measureMcpStartup,
profileMcpModuleLoading,
} from "./lib/mcp-startup-probe.mjs";
const root = process.cwd();
const verifierRun = createVerifierRun();
@ -26,6 +31,8 @@ const workspace = await mkdtemp(join(tmpdir(), "rea-package-"));
const evidenceRoot = join(workspace, "evidence");
const referenceRoot = join(workspace, "reference-source");
let tarball;
let mcpStartup;
let mcpModuleLoading;
try {
({ tarball } = await verifyPackagePack({ root, workspace }));
@ -42,6 +49,25 @@ try {
workspace,
environment: environmentData.environment,
});
mcpStartup = await measureMcpStartup({
command: cli,
args: ["mcp"],
environment: {
...environmentData.environment,
REA_INVESTIGATION_INPUT_ROOTS_JSON: JSON.stringify([]),
},
policy: MCP_STARTUP_POLICY,
});
mcpModuleLoading = await profileMcpModuleLoading({
command: cli,
args: ["mcp"],
environment: {
...environmentData.environment,
REA_INVESTIGATION_INPUT_ROOTS_JSON: JSON.stringify([]),
},
policy: MCP_STARTUP_POLICY,
packageName: PRODUCT_IDENTITY.packageName,
});
const { supportedSetupHost } = await verifyPackageDiscovery({
cli,
environment: environmentData.environment,
@ -102,7 +128,7 @@ try {
investigationReplay,
});
process.stdout.write(
`${JSON.stringify({ verifier_run: await completeVerifierRun(verifierRun), cli: true, analysisCli: true, artifactCli: true, managedCli: true, managedReconstructionCli: true, managedNativeVerificationCli: true, managedRuntimePlanCli: true, managedApplicationGraphCli: true, evidenceCli: true, incurMcpCommand: PRODUCT_IDENTITY.mcpCommand, lifecycleScriptsRequired: false, doctor: "platform-appropriate", setup: supportedSetupHost ? "planned-then-idempotent" : "unsupported-host-rejected", setupPlanReadOnly: supportedSetupHost, existingHopperPreserved: supportedSetupHost, clients: supportedSetupHost ? 3 : 0, backupReadback: supportedSetupHost, failureRecovery: supportedSetupHost, configSymlinkLifecycle: supportedSetupHost, skill: supportedSetupHost, skillReferences: supportedSetupHost, mcpTools: TOOL_CONTRACTS.length, mcpPrompts: prompts.names.length, promptCompletion: true, promptCompletionLifecycle: true, evidenceMcp: true, targetFree: true, targetLifecycle: true, boundedRegexBridge: true })}\n`,
`${JSON.stringify({ verifier_run: await completeVerifierRun(verifierRun), cli: true, analysisCli: true, artifactCli: true, managedCli: true, managedReconstructionCli: true, managedNativeVerificationCli: true, managedRuntimePlanCli: true, managedApplicationGraphCli: true, evidenceCli: true, incurMcpCommand: PRODUCT_IDENTITY.mcpCommand, lifecycleScriptsRequired: false, doctor: "platform-appropriate", setup: supportedSetupHost ? "planned-then-idempotent" : "unsupported-host-rejected", setupPlanReadOnly: supportedSetupHost, existingHopperPreserved: supportedSetupHost, clients: supportedSetupHost ? 3 : 0, backupReadback: supportedSetupHost, failureRecovery: supportedSetupHost, configSymlinkLifecycle: supportedSetupHost, skill: supportedSetupHost, skillReferences: supportedSetupHost, mcpTools: TOOL_CONTRACTS.length, mcpPrompts: prompts.names.length, promptCompletion: true, promptCompletionLifecycle: true, evidenceMcp: true, targetFree: true, targetLifecycle: true, boundedRegexBridge: true, mcpStartup, mcpModuleLoading })}\n`,
);
} finally {
if (tarball) await rm(join(root, tarball), { force: true });

View File

@ -26,7 +26,7 @@ import type {
AnalysisOperation,
CapabilityDescriptor,
} from "./AnalysisProvider.js";
import { OFFICIAL_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
const STATEFUL_OPERATIONS: ReadonlySet<AnalysisOperation> = new Set([
"health",
@ -47,13 +47,15 @@ const CURSOR_DEFAULT_OPERATIONS: ReadonlySet<AnalysisOperation> = new Set([
"xrefs",
]);
const DOCUMENT_SCOPED_OPERATIONS: ReadonlySet<AnalysisOperation> = new Set(
OFFICIAL_TOOL_CONTRACTS.map(({ name }) => name).filter(
(name) =>
name !== "current_document" &&
name !== "list_documents" &&
name !== "set_current_document",
),
const DOCUMENT_SCOPED_OPERATIONS: ReadonlySet<string> = new Set(
GENERATED_MCP_TOOL_CATALOG.filter(({ kind }) => kind === "official-proxy")
.map(({ name }) => name)
.filter(
(name) =>
name !== "current_document" &&
name !== "list_documents" &&
name !== "set_current_document",
),
);
/** Whether an operation is immutable and independent of provider UI state. */

View File

@ -24,8 +24,7 @@ import type {
ProviderIdentity,
} from "./AnalysisProvider.js";
import type { AnalysisOperation } from "./AnalysisProvider.js";
import { enhancedToolNameSchema } from "../contracts/enhancedInputs.js";
import { OFFICIAL_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
import type { BinarySessionPort } from "./BinarySessionPort.js";
import {
SessionProviderRouter,
@ -41,7 +40,14 @@ import { prepareSessionExecution } from "./BinarySessionExecution.js";
import { closeAnalysisClient } from "./AnalysisClientCleanup.js";
export type { BinarySessionPort } from "./BinarySessionPort.js";
const OFFICIAL_OPERATIONS: ReadonlySet<string> = new Set(
OFFICIAL_TOOL_CONTRACTS.map(({ name }) => name),
GENERATED_MCP_TOOL_CATALOG.filter(
({ kind }) => kind === "official-proxy",
).map(({ name }) => name),
);
const ENHANCED_OPERATIONS: ReadonlySet<string> = new Set(
GENERATED_MCP_TOOL_CATALOG.filter(({ kind }) => kind === "enhanced").map(
({ name }) => name,
),
);
/**
@ -100,7 +106,7 @@ export class BinarySession
if (operation !== undefined) {
const exact = route.capabilities?.get(operation)?.provider;
if (exact !== undefined) selected = exact;
else if (enhancedToolNameSchema.safeParse(operation).success) {
else if (ENHANCED_OPERATIONS.has(operation)) {
const providers = new Map<string, ProviderIdentity>();
for (const descriptor of route.capabilities?.values() ?? [])
if (

View File

@ -1,8 +1,9 @@
import { z } from "zod";
import { TOOL_CONTRACTS } from "../contracts/toolContracts.js";
import type { ClientFeatureAvailability } from "../contracts/toolOutputSchemaPrimitives.js";
import type { ToolKind } from "../contracts/toolContractTypes.js";
import type { JsonValue } from "../domain/jsonValue.js";
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
const statusSchema = z.object({
open: z.boolean(),
@ -28,7 +29,6 @@ type ToolAvailabilityReason =
| "unsupported_host"
| "policy_disabled";
type ToolKind = (typeof TOOL_CONTRACTS)[number]["kind"];
type ProviderDescriptor = {
readonly available: boolean;
readonly reason: string | null;
@ -128,7 +128,7 @@ export const buildCapabilityInventory = (
},
]),
);
return TOOL_CONTRACTS.map((contract) => {
return GENERATED_MCP_TOOL_CATALOG.map((contract) => {
const availability = availabilityFor({
name: contract.name,
kind: contract.kind,

View File

@ -5,6 +5,7 @@ import { parse as parseToml } from "smol-toml";
import { z } from "zod";
import { PRODUCT_IDENTITY } from "../identity.js";
import { MCP_STARTUP_POLICY } from "../mcpStartupPolicy.js";
import { supportedClients } from "./SupportedClients.js";
export interface ClientRegistrationStatus {
@ -80,7 +81,11 @@ const registrationAligned = (
currentCommandPath: string,
): boolean => {
const command = [registration.command, ...registration.args];
if (client === "codex" && registration.startup_timeout_sec !== 30)
if (
client === "codex" &&
registration.startup_timeout_sec !==
MCP_STARTUP_POLICY.codexStartupTimeoutSeconds
)
return false;
if (
command.length === 4 &&

View File

@ -0,0 +1,113 @@
import type { AnalysisProfileCommitment } from "../domain/analysisProfile.js";
import type { BinaryTarget } from "../domain/binaryTarget.js";
import type {
AnalysisClient,
AnalysisClientContext,
AnalysisProvider,
CapabilityDescriptor,
ProviderIdentity,
ProviderRequestActivitySnapshot,
ProviderRuntimeLineageSnapshot,
} from "./AnalysisProvider.js";
type LoadAnalysisProvider = () => Promise<AnalysisProvider>;
/**
* Preserve synchronous provider discovery while loading implementation modules
* only when a target-bound client first executes.
*/
export class LazyAnalysisProvider implements AnalysisProvider {
readonly #providerIdentity: ProviderIdentity;
readonly #capabilityDescriptors: readonly CapabilityDescriptor[];
readonly #loadProvider: LoadAnalysisProvider;
#provider: Promise<AnalysisProvider> | undefined;
constructor(options: {
readonly identity: ProviderIdentity;
readonly capabilities: readonly CapabilityDescriptor[];
readonly load: LoadAnalysisProvider;
}) {
this.#providerIdentity = options.identity;
this.#capabilityDescriptors = options.capabilities;
this.#loadProvider = options.load;
}
/** Return generated provider identity without loading its implementation. */
identity(): ProviderIdentity {
return this.#providerIdentity;
}
/** Return generated capability metadata without loading its implementation. */
capabilities(): readonly CapabilityDescriptor[] {
return this.#capabilityDescriptors;
}
/** Create a client whose implementation is loaded on first execution. */
createClient(
target: BinaryTarget,
profile?: AnalysisProfileCommitment,
context?: AnalysisClientContext,
): AnalysisClient {
return new LazyAnalysisClient(async () => {
const provider = await this.#load();
return provider.createClient(target, profile, context);
});
}
async #load(): Promise<AnalysisProvider> {
this.#provider ??= this.#loadProvider().catch((cause: unknown) => {
this.#provider = undefined;
throw cause;
});
return this.#provider;
}
}
class LazyAnalysisClient implements AnalysisClient {
readonly #loadClient: () => Promise<AnalysisClient>;
#client: AnalysisClient | undefined;
#loading: Promise<AnalysisClient> | undefined;
#closed = false;
constructor(loadClient: () => Promise<AnalysisClient>) {
this.#loadClient = loadClient;
}
execute: AnalysisClient["execute"] = async (
operation,
parameters,
options,
) => {
const client = await this.#load();
return client.execute(operation, parameters, options);
};
runtimeLineageSnapshots(): readonly ProviderRuntimeLineageSnapshot[] {
return this.#client?.runtimeLineageSnapshots?.() ?? [];
}
requestActivitySnapshots(): readonly ProviderRequestActivitySnapshot[] {
return this.#client?.requestActivitySnapshots?.() ?? [];
}
async close(): Promise<void> {
this.#closed = true;
if (this.#loading === undefined) return;
const client = await this.#loading;
await client.close();
}
async #load(): Promise<AnalysisClient> {
if (this.#closed) throw new Error("Lazy analysis client is closed");
this.#loading ??= this.#loadClient()
.then((client) => {
this.#client = client;
return client;
})
.catch((cause: unknown) => {
this.#loading = undefined;
throw cause;
});
return this.#loading;
}
}

View File

@ -6,6 +6,7 @@ import writeFileAtomic from "write-file-atomic";
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
import { PRODUCT_IDENTITY } from "../identity.js";
import { MCP_STARTUP_POLICY } from "../mcpStartupPolicy.js";
import { resolveClientConfigTransactionPath } from "./ClientConfigPath.js";
import type {
ClientConfigurationInspection,
@ -21,8 +22,6 @@ const defaultCommand = (): readonly string[] => [
"mcp",
];
const CODEX_STARTUP_TIMEOUT_SECONDS = 30;
/** Back up, atomically update, and semantically read back one JSON MCP configuration. */
export const configureJsonClient = async (
client: SetupClient,
@ -299,7 +298,9 @@ const clientConfigurationDesired = (
command: command[0] ?? PRODUCT_IDENTITY.cliBinary,
args: command.slice(1),
...(client.name === "codex"
? { startup_timeout_sec: CODEX_STARTUP_TIMEOUT_SECONDS }
? {
startup_timeout_sec: MCP_STARTUP_POLICY.codexStartupTimeoutSeconds,
}
: {}),
...(Object.keys(environment).length === 0 ? {} : { env: environment }),
};

View File

@ -2,11 +2,10 @@ import type { AppConfig } from "../config.js";
import { BinarySession } from "./BinarySession.js";
import { HopperProvider } from "../hopper/HopperProvider.js";
import { GhidraProvider } from "../ghidra/GhidraProvider.js";
import { NativeMacOSProvider } from "../native/NativeMacOSProvider.js";
import { ArtifactProvider } from "../artifacts/ArtifactProvider.js";
import { ManagedStaticProvider } from "../dotnet/ManagedStaticProvider.js";
import { silentLogger, type Logger } from "../logger.js";
import { GENERATED_AUXILIARY_PROVIDERS } from "../generatedMcpToolCatalog.js";
import { AnalysisProviderRegistry } from "./AnalysisProviderRegistry.js";
import { LazyAnalysisProvider } from "./LazyAnalysisProvider.js";
import { SessionProviderRouter } from "./SessionProviderRouter.js";
/**
@ -20,16 +19,46 @@ export const createBinarySession = (
): BinarySession => {
const hopper = new HopperProvider(config, logger);
const ghidra = new GhidraProvider(config, logger);
const auxiliary = new Map(
GENERATED_AUXILIARY_PROVIDERS.map((provider) => [
provider.identity.id,
provider,
]),
);
const lazyProvider = (
id: string,
load: ConstructorParameters<typeof LazyAnalysisProvider>[0]["load"],
) => {
const generated = auxiliary.get(id);
if (generated === undefined)
throw new TypeError(`Missing generated provider metadata for ${id}`);
return new LazyAnalysisProvider({ ...generated, load });
};
return new BinarySession(
SessionProviderRouter.selectable(
new AnalysisProviderRegistry([hopper, ghidra], config.analysisProvider),
[
new ArtifactProvider(
config.artifactNativeMountEnabled,
config.artifactIntegrityContinueEnabled,
),
new NativeMacOSProvider(),
new ManagedStaticProvider(),
lazyProvider("rea-artifact-graph", async () => {
const { ArtifactProvider } = await import(
"../artifacts/ArtifactProvider.js"
);
return new ArtifactProvider(
config.artifactNativeMountEnabled,
config.artifactIntegrityContinueEnabled,
);
}),
lazyProvider("native-macos", async () => {
const { NativeMacOSProvider } = await import(
"../native/NativeMacOSProvider.js"
);
return new NativeMacOSProvider();
}),
lazyProvider("rea-dotnet-static", async () => {
const { ManagedStaticProvider } = await import(
"../dotnet/ManagedStaticProvider.js"
);
return new ManagedStaticProvider();
}),
],
),
);

View File

@ -1,4 +1,4 @@
import { TOOL_CONTRACTS } from "./toolContracts.js";
import type { TOOL_CONTRACTS } from "./toolContracts.js";
type ToolName = (typeof TOOL_CONTRACTS)[number]["name"];

View File

@ -32,7 +32,7 @@ export const TOOL_KINDS = [
] as const;
/** Adapter family responsible for implementing a public MCP tool. */
type ToolKind = (typeof TOOL_KINDS)[number];
export type ToolKind = (typeof TOOL_KINDS)[number];
/** Single source of truth for a public MCP tool. */
export interface ToolContract<Name extends string = string> {

File diff suppressed because one or more lines are too long

View File

@ -2,10 +2,7 @@ import type {
CapabilityDescriptor,
ProviderIdentity,
} from "../application/AnalysisProvider.js";
import {
ENHANCED_TOOL_CONTRACTS,
OFFICIAL_TOOL_CONTRACTS,
} from "../contracts/toolContracts.js";
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
import {
GHIDRA_DECOMPILE_REQUEST_TIMEOUT_MS,
GHIDRA_MAX_LINE_BYTES,
@ -22,10 +19,9 @@ export const GHIDRA_PROVIDER_IDENTITY: ProviderIdentity = Object.freeze({
});
const providerContractByName = new Map(
[...OFFICIAL_TOOL_CONTRACTS, ...ENHANCED_TOOL_CONTRACTS].map((contract) => [
contract.name,
contract,
]),
GENERATED_MCP_TOOL_CATALOG.filter(
({ kind }) => kind === "official-proxy" || kind === "enhanced",
).map((contract) => [contract.name, contract]),
);
/** Provider-neutral read-only contracts implemented by the Ghidra adapter. */
@ -165,10 +161,13 @@ export const limitationsFor = (operation: string): readonly string[] => {
/** Provider-neutral capabilities advertised by every non-Windows Ghidra session. */
export const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
GHIDRA_PROVIDER_TOOL_CONTRACTS.map((contract) =>
Object.freeze({
GHIDRA_PROVIDER_TOOL_CONTRACTS.map((contract) => {
const operation = contract.analysisOperation;
if (operation === null)
throw new TypeError(`Missing analysis operation for ${contract.name}`);
return Object.freeze({
provider: GHIDRA_PROVIDER_IDENTITY,
operation: contract.name,
operation,
inputContractVersion: 1,
outputContractVersion: 1,
available: true,
@ -198,8 +197,8 @@ export const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
: GHIDRA_REQUEST_TIMEOUT_MS,
}),
limitations: Object.freeze(limitationsFor(contract.name)),
}),
),
});
}),
);
/** Capabilities advertised for the experimental Windows x64 P0 boundary. */

View File

@ -18,10 +18,7 @@ import type { BinaryTarget } from "../domain/binaryTarget.js";
import type { Logger } from "../logger.js";
import { AnalysisCapabilityUnavailableError } from "../domain/errors.js";
import { err } from "../domain/result.js";
import {
ENHANCED_TOOL_CONTRACTS,
OFFICIAL_TOOL_CONTRACTS,
} from "../contracts/toolContracts.js";
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
import { HopperApplicationLauncher } from "./BridgeLauncher.js";
import {
hopperLoaderArgsForTarget,
@ -47,22 +44,29 @@ const MUTATING_OPERATIONS = new Set([
/** Tool contracts implemented directly by the Hopper adapter. */
export const HOPPER_PROVIDER_TOOL_CONTRACTS = Object.freeze([
...OFFICIAL_TOOL_CONTRACTS,
...ENHANCED_TOOL_CONTRACTS.filter(
(contract) => contract.name === "analyze_function",
...GENERATED_MCP_TOOL_CATALOG.filter(({ kind }) => kind === "official-proxy"),
...GENERATED_MCP_TOOL_CATALOG.filter(
({ name }) => name === "analyze_function",
),
]);
const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
HOPPER_PROVIDER_TOOL_CONTRACTS.map((contract) =>
Object.freeze({
HOPPER_PROVIDER_TOOL_CONTRACTS.map((contract) => {
const operation = contract.analysisOperation;
if (operation === null)
throw new TypeError(`Missing analysis operation for ${contract.name}`);
return Object.freeze({
provider: IDENTITY,
operation: contract.name,
operation,
inputContractVersion: 1,
outputContractVersion: 1,
available: true,
reason: null,
pagination: "offset" in contract.inputSchema.shape ? "offset" : "none",
pagination:
isRecord(contract.inputSchema.properties) &&
"offset" in contract.inputSchema.properties
? "offset"
: "none",
exhaustive: false,
effects: Object.freeze({
mutatesArtifact: MUTATING_OPERATIONS.has(contract.name),
@ -81,10 +85,14 @@ const CAPABILITIES: readonly CapabilityDescriptor[] = Object.freeze(
limitations: Object.freeze([
"Results depend on Hopper's completed static analysis.",
]),
}),
),
});
}),
);
function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
/** Concrete analysis provider backed by REA's private Hopper bridge. */
export class HopperProvider implements AnalysisProviderCandidate {
constructor(

View File

@ -10,10 +10,6 @@ import { createBinarySession } from "./application/runtime.js";
import { createLogger } from "./logger.js";
import { projectAnalysisError } from "./domain/errors.js";
import { loadConfiguredPermissionAuthority } from "./application/PermissionConfiguration.js";
import { CdpBrowserProvider } from "./browser/CdpBrowserProvider.js";
import { CdpElectronProvider } from "./browser/CdpElectronProvider.js";
import { V8InspectorProvider } from "./browser/V8InspectorProvider.js";
import { PlaywrightBrowserScenarioProvider } from "./browser/PlaywrightBrowserScenarioProvider.js";
import type { RuntimeDependencies } from "./main/types.js";
import { SERVER_START_FAILED } from "./main/messages.js";
import { createRuntimeState } from "./main/state.js";
@ -73,10 +69,6 @@ export const run = async (
}
const session = createBinarySession(config.value, logger);
const browserObservation = new CdpBrowserProvider();
const browserScenarioCapture = new PlaywrightBrowserScenarioProvider();
const electronObservation = new CdpElectronProvider();
const javascriptRuntimeObservation = new V8InspectorProvider();
const opened = await openInitialTarget(
session,
config.value,
@ -88,10 +80,25 @@ export const run = async (
const transport = await startMcpTransport(dependencies, session, {
logger,
serverLogger,
browserObservation,
browserScenarioCapture,
electronObservation,
javascriptRuntimeObservation,
loadOptionalProviders: async () => {
const [
{ CdpBrowserProvider },
{ PlaywrightBrowserScenarioProvider },
{ CdpElectronProvider },
{ V8InspectorProvider },
] = await Promise.all([
import("./browser/CdpBrowserProvider.js"),
import("./browser/PlaywrightBrowserScenarioProvider.js"),
import("./browser/CdpElectronProvider.js"),
import("./browser/V8InspectorProvider.js"),
]);
return {
browserObservation: new CdpBrowserProvider(),
browserScenarioCapture: new PlaywrightBrowserScenarioProvider(),
electronObservation: new CdpElectronProvider(),
javascriptRuntimeObservation: new V8InspectorProvider(),
};
},
permissionAuthority: permissionAuthority.value,
runtimeState,
});

View File

@ -1,13 +1,12 @@
import type { StdioServerHandle } from "@modelcontextprotocol/server/stdio";
import type { BinarySession } from "../application/BinarySession.js";
import type { CdpBrowserProvider } from "../browser/CdpBrowserProvider.js";
import type { CdpElectronProvider } from "../browser/CdpElectronProvider.js";
import type { V8InspectorProvider } from "../browser/V8InspectorProvider.js";
import type { PlaywrightBrowserScenarioProvider } from "../browser/PlaywrightBrowserScenarioProvider.js";
import type { PermissionAuthority } from "../application/PermissionAuthority.js";
import type { Logger } from "../logger.js";
import { createServer } from "../server/createServer.js";
import {
createServer,
type CreateServerOptions,
} from "../server/createServer.js";
import type { RuntimeDependencies } from "./types.js";
import type { RuntimeState } from "./state.js";
import {
@ -20,10 +19,9 @@ type McpServerInstance = ReturnType<typeof createServer>;
interface ServerContext {
readonly logger: Logger;
readonly serverLogger: Logger;
readonly browserObservation: CdpBrowserProvider;
readonly browserScenarioCapture: PlaywrightBrowserScenarioProvider;
readonly electronObservation: CdpElectronProvider;
readonly javascriptRuntimeObservation: V8InspectorProvider;
readonly loadOptionalProviders: NonNullable<
CreateServerOptions["loadOptionalProviders"]
>;
readonly permissionAuthority: PermissionAuthority;
readonly runtimeState: RuntimeState;
}
@ -58,11 +56,7 @@ export const startMcpTransport = async (
analysisSnapshotFilePolicy:
serverContext.runtimeState.snapshotPolicy,
permissionAuthority: serverContext.permissionAuthority,
browserObservation: serverContext.browserObservation,
browserScenarioCapture: serverContext.browserScenarioCapture,
electronObservation: serverContext.electronObservation,
javascriptRuntimeObservation:
serverContext.javascriptRuntimeObservation,
loadOptionalProviders: serverContext.loadOptionalProviders,
artifactIntegrityContinueEnabled: () =>
serverContext.runtimeState.currentConfig
.artifactIntegrityContinueEnabled,

View File

@ -10,8 +10,8 @@ import {
} from "./catalogIdentity.js";
import { PROMPT_CONTRACTS } from "./contracts/promptContracts.js";
import { PRODUCT_IDENTITY } from "./identity.js";
import { MCP_STARTUP_POLICY } from "./mcpStartupPolicy.js";
const DEFAULT_DEADLINE_MS = 20_000;
const STDERR_LIMIT_BYTES = 64 * 1_024;
const OUTPUT_FORMATS = ["toon", "json", "yaml", "md", "jsonl"] as const;
type OutputFormat = (typeof OUTPUT_FORMATS)[number];
@ -40,7 +40,8 @@ interface McpDoctorCheck {
/** Diagnose the actual production stdio adapter with one bounded SDK session. */
export const runProductionMcpDoctor = async (options: McpDoctorOptions) => {
const deadline = Date.now() + (options.deadlineMs ?? DEFAULT_DEADLINE_MS);
const deadline =
Date.now() + (options.deadlineMs ?? MCP_STARTUP_POLICY.doctorDeadlineMs);
const controller = new AbortController();
const timer = setTimeout(
() => controller.abort(new Error("Production MCP doctor deadline expired")),

14
src/mcpStartupPolicy.ts Normal file
View File

@ -0,0 +1,14 @@
/**
* Supported MCP startup deadlines and regression budgets.
*
* The Codex timeout is caller-facing configuration. The smaller phase budgets
* preserve operational headroom, while the doctor deadline covers additional
* inventory requests after initialization.
*/
export const MCP_STARTUP_POLICY = {
codexStartupTimeoutSeconds: 30,
initializeBudgetMs: 15_000,
toolsListBudgetMs: 5_000,
firstCatalogBudgetMs: 20_000,
doctorDeadlineMs: 30_000,
} as const;

View File

@ -0,0 +1,181 @@
import type {
AnyToolHandler,
JsonSchemaValidator,
jsonSchemaValidator,
McpServer,
RegisteredTool,
StandardSchemaWithJSON,
} from "@modelcontextprotocol/server";
import {
fromJsonSchema,
isCallToolResult,
isInputRequiredResult,
} from "@modelcontextprotocol/server";
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
import type { ToolKind } from "../contracts/toolContractTypes.js";
type HydrateToolRegistrations = (kind: ToolKind) => Promise<void>;
/**
* Advertise the generated MCP catalog immediately and hydrate its callbacks
* through the existing registrars on first execution.
*/
export class LazyToolCatalog {
readonly #server: McpServer;
readonly #hydrateRegistrations: HydrateToolRegistrations;
readonly #includeSessionTools: boolean;
readonly #tools = new Map<string, RegisteredTool>();
readonly #hydrations = new Map<ToolKind, Promise<void>>();
#hydrationQueue: Promise<void> = Promise.resolve();
#closed = false;
constructor(
server: McpServer,
hydrateRegistrations: HydrateToolRegistrations,
includeSessionTools = true,
) {
this.#server = server;
this.#hydrateRegistrations = hydrateRegistrations;
this.#includeSessionTools = includeSessionTools;
}
/** Register every generated tool with a lazy callback. */
register(): void {
for (const contract of GENERATED_MCP_TOOL_CATALOG) {
if (contract.requiresSession && !this.#includeSessionTools) continue;
const inputSchema = advertisedSchema(contract.inputSchema);
const outputSchema = advertisedSchema(contract.outputSchema);
let registered: RegisteredTool | undefined;
const callback: AnyToolHandler<StandardSchemaWithJSON> = async (
input,
context,
) => {
await this.#hydrate(contract.kind);
const handler = registered?.handler;
if (handler === undefined || handler === callback)
throw new Error(`MCP tool ${contract.name} was not hydrated`);
const result: unknown = await Reflect.apply(handler, undefined, [
input,
context,
]);
if (isCallToolResult(result) || isInputRequiredResult(result))
return result;
throw new TypeError(
`MCP tool ${contract.name} returned an invalid result`,
);
};
registered = this.#server.registerTool(
contract.name,
{
title: contract.title,
description: contract.description,
inputSchema,
outputSchema,
annotations: contract.annotations,
},
callback,
);
this.#tools.set(contract.name, registered);
}
}
/** Wait for in-flight hydration and reject future first-use loading. */
async close(): Promise<void> {
this.#closed = true;
await Promise.allSettled(this.#hydrations.values());
}
async #hydrate(kind: ToolKind): Promise<void> {
if (this.#closed) throw new Error("MCP tool catalog is closed");
const existing = this.#hydrations.get(kind);
if (existing !== undefined) {
await existing;
return;
}
const hydration = this.#hydrationQueue
.catch(() => undefined)
.then(() => this.#runHydration(kind));
this.#hydrationQueue = hydration;
this.#hydrations.set(kind, hydration);
try {
await hydration;
} catch (cause: unknown) {
if (this.#hydrations.get(kind) === hydration)
this.#hydrations.delete(kind);
throw cause;
}
}
async #runHydration(kind: ToolKind): Promise<void> {
if (this.#closed) throw new Error("MCP tool catalog is closed");
const expected = GENERATED_MCP_TOOL_CATALOG.filter(
(contract) =>
contract.kind === kind &&
(this.#includeSessionTools || !contract.requiresSession),
).map((contract) => contract.name);
const expectedNames = new Set(expected);
const hydrated = new Set<string>();
const originalRegisterTool = this.#server.registerTool;
const hydrationRegisterTool = (...args: unknown[]): unknown => {
const [name, config, callback] = args;
if (
typeof name !== "string" ||
!isRecord(config) ||
typeof callback !== "function"
)
throw new TypeError("Invalid lazy MCP tool registration");
const registered = this.#tools.get(name);
if (registered === undefined || !expectedNames.has(name))
throw new TypeError(
`MCP tool ${name} is not in the ${kind} catalog family`,
);
if (hydrated.has(name))
throw new TypeError(`MCP tool ${name} was hydrated more than once`);
hydrated.add(name);
Reflect.apply(registered.update, registered, [
{
title: config.title,
description: config.description,
paramsSchema: config.inputSchema,
outputSchema: config.outputSchema,
annotations: config.annotations,
callback,
},
]);
return registered;
};
if (!Reflect.set(this.#server, "registerTool", hydrationRegisterTool))
throw new TypeError("Unable to install lazy MCP tool hydration");
try {
await this.#hydrateRegistrations(kind);
} finally {
Reflect.set(this.#server, "registerTool", originalRegisterTool);
}
const missing = expected.filter((name) => !hydrated.has(name));
if (missing.length > 0)
throw new TypeError(
`Generated MCP tools were not hydrated: ${missing.join(", ")}`,
);
}
}
const advertisedSchema = (
jsonSchema: Readonly<Record<string, unknown>>,
): StandardSchemaWithJSON =>
fromJsonSchema(jsonSchema, DEFERRED_VALIDATION_PROVIDER);
class DeferredValidationProvider implements jsonSchemaValidator {
getValidator<T>(): JsonSchemaValidator<T> {
return (input: unknown) => ({
valid: true,
data: input as T,
errorMessage: undefined,
});
}
}
const DEFERRED_VALIDATION_PROVIDER = new DeferredValidationProvider();
const isRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>
typeof value === "object" && value !== null && !Array.isArray(value);

View File

@ -10,29 +10,16 @@ import {
import type { AnalysisOperationPort } from "../application/AnalysisProvider.js";
import type { BinarySessionPort } from "../application/BinarySession.js";
import { PRODUCT_IDENTITY } from "../identity.js";
import { registerEnhancedTools } from "./registerEnhancedTools.js";
import { registerOfficialTools } from "./registerOfficialTools.js";
import { registerSessionTools } from "./registerSessionTools.js";
import { registerNativeTools } from "./registerNativeTools.js";
import { registerArtifactTools } from "./registerArtifactTools.js";
import { registerManagedTools } from "./registerManagedTools.js";
import { registerManagedWorkflowTools } from "./registerManagedWorkflowTools.js";
import { silentLogger, type Logger } from "../logger.js";
import type { ProcessExecutionPolicy } from "../domain/processCapture.js";
import type { EvidenceFilePolicy } from "../domain/evidenceBundle.js";
import { registerGuidedPrompts } from "./registerPrompts.js";
import { registerEvidenceResources } from "./registerEvidenceResources.js";
import { createServerIdentity } from "../serverIdentity.js";
import type { PermissionAuthority } from "../application/PermissionAuthority.js";
import type { BrowserObservationPort } from "../application/BrowserObservationPort.js";
import type { BrowserScenarioCapturePort } from "../application/BrowserScenarioCapturePort.js";
import { registerBrowserTools } from "./registerBrowserTools.js";
import { registerBrowserScenarioTool } from "./registerBrowserScenarioTool.js";
import type { ElectronObservationPort } from "../application/ElectronObservationPort.js";
import { registerElectronTools } from "./registerElectronTools.js";
import type { JavaScriptRuntimeObservationPort } from "../application/JavaScriptRuntimeObservationPort.js";
import { registerJavaScriptRuntimeObservationTools } from "./registerJavaScriptRuntimeObservationTools.js";
import { registerApplicationTools } from "./registerApplicationTools.js";
import type { SessionAvailability } from "./sessionAvailabilityPolicy.js";
import { sessionAvailabilityPolicy } from "./sessionAvailabilityPolicy.js";
import {
@ -52,12 +39,11 @@ import type {
JavaScriptReplayRunner,
} from "../application/JavaScriptReplayPlanning.js";
import type { ManagedRuntimePolicy } from "../application/ManagedRuntimeCorrelationService.js";
import { LinuxJavaScriptReplayRunner } from "../replay/LinuxJavaScriptReplayRunner.js";
import { SystemJavaScriptReplayHost } from "../replay/SystemJavaScriptReplayHost.js";
import {
PROCESS_CAPTURE_ELICITATION_POLICY,
type ProcessCaptureElicitationState,
} from "./ProcessCaptureElicitation.js";
import { LazyToolCatalog } from "./LazyToolCatalog.js";
export interface CreateServerOptions {
readonly logger?: Logger;
@ -76,6 +62,12 @@ export interface CreateServerOptions {
readonly javascriptReplayRunner?: JavaScriptReplayRunner;
readonly managedRuntimePolicy?: ManagedRuntimePolicy;
readonly availabilityPolicy?: () => SessionAvailability;
readonly loadOptionalProviders?: () => Promise<{
readonly browserObservation: BrowserObservationPort;
readonly browserScenarioCapture: BrowserScenarioCapturePort;
readonly electronObservation: ElectronObservationPort;
readonly javascriptRuntimeObservation: JavaScriptRuntimeObservationPort;
}>;
}
const installSessionToolAvailability = (
@ -155,51 +147,88 @@ export const createServer = (
const toolLogger = logger.child({ layer: "server" });
const { activeTarget, recordEvidence, recordEvidenceWithUnknown } =
createSessionRecorders(server, session);
const toolContext = {
server,
analysis,
session,
options,
logger: toolLogger,
permissionAuthority,
activeTarget,
recordEvidence,
recordEvidenceWithUnknown,
const processCaptureElicitation = {
stateCodec: processCaptureStateCodec,
supported: (context: {
readonly mcpReq: {
readonly envelope?: Readonly<Record<string, unknown>>;
};
}) => {
const envelope = context.mcpReq.envelope;
const version = envelope?.[PROTOCOL_VERSION_META_KEY];
const capabilities = envelope?.[CLIENT_CAPABILITIES_META_KEY];
return (
typeof version === "string" &&
PROCESS_CAPTURE_ELICITATION_POLICY.protocolVersions.some(
(supported) => supported === version,
) &&
isRecord(capabilities) &&
isRecord(capabilities.elicitation) &&
capabilities.elicitation.form !== undefined
);
},
now: Date.now,
consumedNonces: new Map<string, number>(),
};
registerBinaryAnalysisTools(toolContext);
registerObservationTools(toolContext);
let optionalProviders:
| ReturnType<NonNullable<CreateServerOptions["loadOptionalProviders"]>>
| undefined;
const loadOptionalProviders = () => {
optionalProviders ??= options
.loadOptionalProviders?.()
.catch((cause: unknown) => {
optionalProviders = undefined;
throw cause;
});
return optionalProviders;
};
const lazyTools = new LazyToolCatalog(
server,
async (kind) => {
const needsObservationProviders =
kind === "browser-provider" ||
kind === "electron-provider" ||
kind === "runtime-provider";
const [{ hydrateServerToolFamily }, loadedOptionalProviders] =
await Promise.all([
import("./hydrateServerTools.js"),
needsObservationProviders ? loadOptionalProviders() : undefined,
]);
const hydratedOptions =
loadedOptionalProviders === undefined
? options
: { ...options, ...loadedOptionalProviders };
await hydrateServerToolFamily({
kind,
server,
analysis,
session,
options: hydratedOptions,
context: {
logger: toolLogger,
permissionAuthority,
activeTarget,
recordEvidence,
recordEvidenceWithUnknown,
availabilityPolicy: dynamicTools?.policy,
startedAt,
processCaptureElicitation,
},
});
},
session !== undefined,
);
lazyTools.register();
registerGuidedPrompts(server, analysis, session);
if (session !== undefined) {
registerEvidenceResources(server, session);
registerSessionTools(server, session, toolLogger, {
...options,
...(dynamicTools === undefined
? {}
: { availabilityPolicy: dynamicTools.policy }),
...(permissionAuthority === undefined ? {} : { permissionAuthority }),
startedAt,
processCaptureElicitation: {
stateCodec: processCaptureStateCodec,
supported: (context) => {
const envelope = context.mcpReq.envelope;
const version = envelope?.[PROTOCOL_VERSION_META_KEY];
const capabilities = envelope?.[CLIENT_CAPABILITIES_META_KEY];
return (
typeof version === "string" &&
PROCESS_CAPTURE_ELICITATION_POLICY.protocolVersions.some(
(supported) => supported === version,
) &&
isRecord(capabilities) &&
isRecord(capabilities.elicitation) &&
capabilities.elicitation.form !== undefined
);
},
now: Date.now,
consumedNonces: new Map(),
},
});
}
dynamicTools?.controller.synchronize();
const close = server.close.bind(server);
server.close = async () => {
await lazyTools.close();
await close();
};
return server;
};
@ -243,7 +272,8 @@ const registerServerIdentityResource = (
description: "Live package, SDK, protocol, and catalog identity.",
mimeType: "application/json",
},
(uri) => {
async (uri) => {
const { createServerIdentity } = await import("../serverIdentity.js");
const client = server.server.getClientVersion();
const protocolVersion = server.server.getNegotiatedProtocolVersion();
return {
@ -267,154 +297,5 @@ const registerServerIdentityResource = (
);
};
interface ServerToolContext extends ReturnType<typeof createSessionRecorders> {
readonly server: McpServer;
readonly analysis: AnalysisOperationPort;
readonly session: BinarySessionPort | undefined;
readonly options: CreateServerOptions;
readonly logger: Logger;
readonly permissionAuthority: PermissionAuthority | undefined;
}
const registerBinaryAnalysisTools = ({
server,
analysis,
session,
options,
logger,
permissionAuthority,
activeTarget,
recordEvidence,
recordEvidenceWithUnknown,
}: ServerToolContext): void => {
const recordUnknown =
session === undefined
? undefined
: (input: Parameters<typeof session.recordUnknown>[0]) =>
session.recordUnknown(input);
registerOfficialTools(server, analysis, {
logger,
activeTarget,
recordEvidence,
recordUnknown,
});
registerEnhancedTools(server, analysis, {
logger,
activeTarget,
analysisProfile:
session === undefined ? undefined : () => session.analysisProfile(),
recordEvidence,
recordUnknown,
});
registerNativeTools(server, analysis, {
logger,
activeTarget,
recordEvidence,
});
registerArtifactTools(server, analysis, {
logger,
activeTarget,
recordEvidence,
...(permissionAuthority === undefined ? {} : { permissionAuthority }),
});
registerManagedTools(server, analysis, {
logger,
activeTarget,
recordEvidence,
session,
});
if (session !== undefined)
registerManagedWorkflowTools(server, {
logger,
recordEvidence,
recordEvidenceWithUnknown,
session,
runtime: {
policy: options.managedRuntimePolicy ?? {
enabled: false,
roots: [],
executablePath: "/usr/bin/dotnet",
},
authority: permissionAuthority,
},
});
};
const registerObservationTools = ({
server,
session,
options,
logger,
permissionAuthority,
recordEvidence,
recordEvidenceWithUnknown,
}: ServerToolContext): void => {
registerBrowserTools(server, {
logger,
browser: options.browserObservation,
permissionAuthority,
recordEvidence,
});
registerBrowserScenarioTool(server, {
logger,
provider: options.browserScenarioCapture,
permissionAuthority,
recordEvidence,
});
registerElectronTools(server, {
logger,
electron: options.electronObservation,
permissionAuthority,
recordEvidence,
});
registerJavaScriptRuntimeObservationTools(server, {
logger,
runtime: options.javascriptRuntimeObservation,
permissionAuthority,
recordEvidence,
});
registerApplicationTools(server, {
logger,
recordEvidence,
recordEvidenceWithUnknown,
evidenceLookup:
session === undefined
? undefined
: (evidenceId) => session.evidenceById(evidenceId),
evidenceFilePolicy: options.evidenceFilePolicy ?? {
roots: [],
maxBytes: 1,
maxDepth: 1,
maxStringLength: 1,
maxNodes: 1,
},
permissionAuthority,
retainCoverageWorkspace:
session === undefined
? undefined
: (workspace) => {
const retained =
session.retainReconstructionCoverageWorkspace(workspace);
if (retained === "added") server.sendResourceListChanged();
return retained;
},
replay: {
policy: options.javascriptReplayPolicy ?? {
enabled: false,
roots: [],
nodePath: process.execPath,
bubblewrapPath: "/usr/bin/bwrap",
systemdRunPath: "/usr/bin/systemd-run",
systemctlPath: "/usr/bin/systemctl",
shellPath: "/usr/bin/bash",
},
host: options.javascriptReplayHost ?? new SystemJavaScriptReplayHost(),
runner:
options.javascriptReplayRunner ?? new LinuxJavaScriptReplayRunner(),
authority: permissionAuthority,
},
});
};
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);

View File

@ -0,0 +1,300 @@
import type { McpServer } from "@modelcontextprotocol/server";
import type { AnalysisOperationPort } from "../application/AnalysisProvider.js";
import type { BinarySessionPort } from "../application/BinarySession.js";
import type { PermissionAuthority } from "../application/PermissionAuthority.js";
import type { BinaryTarget } from "../domain/binaryTarget.js";
import type { Logger } from "../logger.js";
import type { ToolKind } from "../contracts/toolContractTypes.js";
import type { CreateServerOptions } from "./createServer.js";
import type { ProcessCaptureElicitation } from "./ProcessCaptureElicitation.js";
import type { SessionAvailability } from "./sessionAvailabilityPolicy.js";
interface HydratedToolContext {
readonly logger: Logger;
readonly permissionAuthority: PermissionAuthority | undefined;
readonly activeTarget: (() => BinaryTarget | undefined) | undefined;
readonly recordEvidence: BinarySessionPort["recordEvidence"] | undefined;
readonly recordEvidenceWithUnknown:
| BinarySessionPort["recordEvidenceWithUnknown"]
| undefined;
readonly availabilityPolicy: (() => SessionAvailability) | undefined;
readonly startedAt: string;
readonly processCaptureElicitation: ProcessCaptureElicitation;
}
export interface HydrateServerToolFamilyInput {
readonly kind: ToolKind;
readonly server: McpServer;
readonly analysis: AnalysisOperationPort;
readonly session: BinarySessionPort | undefined;
readonly options: CreateServerOptions;
readonly context: HydratedToolContext;
}
/**
* Install production callbacks on generated lazy tool registrations.
*/
export const hydrateServerToolFamily = async ({
kind,
server,
analysis,
session,
options,
context,
}: HydrateServerToolFamilyInput): Promise<void> => {
const toolContext = {
server,
analysis,
session,
options,
logger: context.logger,
permissionAuthority: context.permissionAuthority,
activeTarget: context.activeTarget,
recordEvidence: context.recordEvidence,
recordEvidenceWithUnknown: context.recordEvidenceWithUnknown,
};
switch (kind) {
case "official-proxy": {
const { registerOfficialTools } = await import(
"./registerOfficialTools.js"
);
registerOfficialTools(server, analysis, analysisOptions(toolContext));
return;
}
case "enhanced": {
const { registerEnhancedTools } = await import(
"./registerEnhancedTools.js"
);
registerEnhancedTools(server, analysis, enhancedOptions(toolContext));
return;
}
case "native-provider": {
const { registerNativeTools } = await import("./registerNativeTools.js");
registerNativeTools(server, analysis, evidenceOptions(toolContext));
return;
}
case "artifact-provider": {
const { registerArtifactTools } = await import(
"./registerArtifactTools.js"
);
registerArtifactTools(server, analysis, {
...evidenceOptions(toolContext),
...(context.permissionAuthority === undefined
? {}
: { permissionAuthority: context.permissionAuthority }),
});
return;
}
case "managed-provider": {
const { registerManagedTools } = await import(
"./registerManagedTools.js"
);
registerManagedTools(server, analysis, {
...evidenceOptions(toolContext),
session,
});
return;
}
case "application":
await registerApplicationFamily(toolContext);
return;
case "browser-provider":
case "electron-provider":
case "runtime-provider": {
await hydrateObservationFamily(kind, toolContext);
return;
}
case "session": {
if (session === undefined)
throw new TypeError("Session tools require a binary session");
const { registerSessionTools } = await import(
"./registerSessionTools.js"
);
registerSessionTools(server, session, context.logger, {
...options,
...(context.availabilityPolicy === undefined
? {}
: { availabilityPolicy: context.availabilityPolicy }),
...(context.permissionAuthority === undefined
? {}
: { permissionAuthority: context.permissionAuthority }),
startedAt: context.startedAt,
processCaptureElicitation: context.processCaptureElicitation,
});
}
}
};
const hydrateObservationFamily = async (
kind: "browser-provider" | "electron-provider" | "runtime-provider",
context: ServerToolContext,
): Promise<void> => {
const common = {
logger: context.logger,
permissionAuthority: context.permissionAuthority,
recordEvidence: context.recordEvidence,
};
if (kind === "browser-provider") {
const [{ registerBrowserTools }, { registerBrowserScenarioTool }] =
await Promise.all([
import("./registerBrowserTools.js"),
import("./registerBrowserScenarioTool.js"),
]);
registerBrowserTools(context.server, {
...common,
browser: context.options.browserObservation,
});
registerBrowserScenarioTool(context.server, {
...common,
provider: context.options.browserScenarioCapture,
});
return;
}
if (kind === "electron-provider") {
const { registerElectronTools } = await import(
"./registerElectronTools.js"
);
registerElectronTools(context.server, {
...common,
electron: context.options.electronObservation,
});
return;
}
const { registerJavaScriptRuntimeObservationTools } = await import(
"./registerJavaScriptRuntimeObservationTools.js"
);
registerJavaScriptRuntimeObservationTools(context.server, {
...common,
runtime: context.options.javascriptRuntimeObservation,
});
};
interface ServerToolContext {
readonly server: McpServer;
readonly analysis: AnalysisOperationPort;
readonly session: BinarySessionPort | undefined;
readonly options: CreateServerOptions;
readonly logger: Logger;
readonly permissionAuthority: PermissionAuthority | undefined;
readonly activeTarget: (() => BinaryTarget | undefined) | undefined;
readonly recordEvidence: BinarySessionPort["recordEvidence"] | undefined;
readonly recordEvidenceWithUnknown:
| BinarySessionPort["recordEvidenceWithUnknown"]
| undefined;
}
const analysisOptions = ({
logger,
activeTarget,
recordEvidence,
session,
}: ServerToolContext) => {
const recordUnknown =
session === undefined
? undefined
: (input: Parameters<typeof session.recordUnknown>[0]) =>
session.recordUnknown(input);
return {
logger,
activeTarget,
recordEvidence,
recordUnknown,
};
};
const enhancedOptions = (context: ServerToolContext) => ({
...analysisOptions(context),
analysisProfile:
context.session === undefined
? undefined
: () => context.session?.analysisProfile(),
});
const evidenceOptions = ({
logger,
activeTarget,
recordEvidence,
}: ServerToolContext) => ({
logger,
activeTarget,
recordEvidence,
});
const registerApplicationFamily = async ({
server,
session,
options,
logger,
permissionAuthority,
recordEvidence,
recordEvidenceWithUnknown,
}: ServerToolContext): Promise<void> => {
const [
{ registerApplicationTools },
{ registerManagedWorkflowTools },
{ LinuxJavaScriptReplayRunner },
{ SystemJavaScriptReplayHost },
] = await Promise.all([
import("./registerApplicationTools.js"),
import("./registerManagedWorkflowTools.js"),
import("../replay/LinuxJavaScriptReplayRunner.js"),
import("../replay/SystemJavaScriptReplayHost.js"),
]);
if (session !== undefined)
registerManagedWorkflowTools(server, {
logger,
recordEvidence,
recordEvidenceWithUnknown,
session,
runtime: {
policy: options.managedRuntimePolicy ?? {
enabled: false,
roots: [],
executablePath: "/usr/bin/dotnet",
},
authority: permissionAuthority,
},
});
registerApplicationTools(server, {
logger,
recordEvidence,
recordEvidenceWithUnknown,
evidenceLookup:
session === undefined
? undefined
: (evidenceId) => session.evidenceById(evidenceId),
evidenceFilePolicy: options.evidenceFilePolicy ?? {
roots: [],
maxBytes: 1,
maxDepth: 1,
maxStringLength: 1,
maxNodes: 1,
},
permissionAuthority,
retainCoverageWorkspace:
session === undefined
? undefined
: (workspace) => {
const retained =
session.retainReconstructionCoverageWorkspace(workspace);
if (retained === "added") server.sendResourceListChanged();
return retained;
},
replay: {
policy: options.javascriptReplayPolicy ?? {
enabled: false,
roots: [],
nodePath: process.execPath,
bubblewrapPath: "/usr/bin/bwrap",
systemdRunPath: "/usr/bin/systemd-run",
systemctlPath: "/usr/bin/systemctl",
shellPath: "/usr/bin/bash",
},
host: options.javascriptReplayHost ?? new SystemJavaScriptReplayHost(),
runner:
options.javascriptReplayRunner ?? new LinuxJavaScriptReplayRunner(),
authority: permissionAuthority,
},
});
};

View File

@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import { MCP_STARTUP_POLICY } from "../src/mcpStartupPolicy.js";
import {
createEsmModuleProfileCollector,
parseLinuxVmRssBytes,
} from "../scripts/lib/mcp-startup-probe.mjs";
describe("MCP startup policy", () => {
it("preserves headroom beneath the configured Codex timeout", () => {
const codexTimeoutMs =
MCP_STARTUP_POLICY.codexStartupTimeoutSeconds * 1_000;
expect(MCP_STARTUP_POLICY.initializeBudgetMs).toBeLessThan(codexTimeoutMs);
expect(MCP_STARTUP_POLICY.firstCatalogBudgetMs).toBeLessThan(
codexTimeoutMs,
);
expect(MCP_STARTUP_POLICY.firstCatalogBudgetMs).toBe(
MCP_STARTUP_POLICY.initializeBudgetMs +
MCP_STARTUP_POLICY.toolsListBudgetMs,
);
expect(MCP_STARTUP_POLICY.doctorDeadlineMs).toBeGreaterThanOrEqual(
codexTimeoutMs,
);
});
it("parses Linux resident memory without accepting other fields", () => {
expect(
parseLinuxVmRssBytes("Name:\tnode\nVmRSS:\t 12345 kB\nThreads:\t7\n"),
).toBe(12_641_280);
expect(parseLinuxVmRssBytes("VmSize:\t12345 kB\n")).toBeNull();
});
it("profiles unique file-backed ESM modules without retaining paths", () => {
const collector = createEsmModuleProfileCollector();
collector.append(
"ESM 1: Storing file:///opt/rea/dist/main.js\n" +
"ESM 1: Translating StandardModule file:///opt/rea/dist/main.js\n" +
"ESM 1: Storing file:///opt/rea/node_modules/zod/index.js",
);
expect(collector.result()).toEqual({
supported: true,
method: "node-debug-esm",
unique_file_modules: 2,
unique_package_modules: 0,
unique_dependency_modules: 1,
debug_lines: 3,
truncated: false,
});
});
});

View File

@ -105,7 +105,7 @@ describe("process-capture MCP elicitation", () => {
await Promise.allSettled([client.close(), server.close()]);
await rm(root, { recursive: true, force: true });
}
});
}, 20_000);
it.each(["tampered", "expired"] as const)(
"rejects %s signed state through the modern MCP client and server",

View File

@ -87,7 +87,9 @@ describe("target-free MCP lifecycle", () => {
expect(closed).toEqual([canonicalTargetPath]);
expect(
z
.object({ result: z.object({ path: z.string(), sha256: z.string() }) })
.object({
result: z.object({ path: z.string(), sha256: z.string() }),
})
.parse(
structured(
await mcp.callTool({
@ -100,7 +102,7 @@ describe("target-free MCP lifecycle", () => {
path: canonicalTargetPath,
sha256: z.object({ sha256: z.string() }).parse(second).sha256,
});
});
}, 10_000);
it("reports no-target, opens, analyzes, switches, reports status, and closes", async () => {
directory = await createTestTempDirectory("rea-mcp-session-");

View File

@ -6,9 +6,9 @@ import { defineConfig } from "vitest/config";
export const TEST_FILES = ["tests/**/*.test.ts"];
const IS_CONTINUOUS_INTEGRATION = process.env.CI === "true";
// Subprocess-heavy parallel tests exceed their existing deadlines above two workers.
// Lazy MCP integration tests are memory-bound while Vite transforms tool families.
const MAX_TEST_WORKERS = 2;
const MAX_UNIT_TEST_WORKERS = 4;
const MAX_UNIT_TEST_WORKERS = 1;
const PROCESS_CAPTURE_TEST = "tests/processCapture.test.ts";
const CANONICAL_TEMPORARY_DIRECTORY = realpathSync(tmpdir());
export const SERIAL_INTEGRATION_TESTS = [