fix(identity)!: preserve invocation uncertainty and advertised catalogs

Capture the current entrypoint once without inventing a path. Preserve unknown registration ownership through setup and doctor, refuse unsafe registration plans, and retain exact pinned-launcher readiness checks. Hash the same full or compact schema projection advertised by each MCP server and use that identity in doctor comparisons.

BREAKING CHANGE: server.command_path may be null when the invocation path is unavailable. Registration and setup statuses, and doctor classifications, may report unknown.

Reviewed-PR: #3230, #3015
Co-authored-by: qorvr <330186599+qorvr@users.noreply.github.com>
Co-authored-by: Hemang Varshney <218273197+hemv-857@users.noreply.github.com>
This commit is contained in:
morluto
2026-10-14 16:00:18 +08:00
parent dd1c879580
commit 187f113f79
32 changed files with 755 additions and 180 deletions
+48 -4
View File
@@ -1,4 +1,4 @@
import { basename, isAbsolute, resolve } from "node:path";
import { isAbsolute, resolve } from "node:path";
import { valid } from "semver";
import { PRODUCT_IDENTITY } from "../identity.js";
@@ -112,13 +112,56 @@ const isOwnedPackageReference = (packageReference: string): boolean => {
};
/** Check that one parsed command points to REA's MCP entry point. */
export type ClientRegistrationCommandIdentity = "owned" | "foreign" | "unknown";
/** Classify only what the command and current executable identity establish. */
export const classifyClientRegistrationCommand = (
command: readonly string[],
currentCommandPath: string | undefined,
): ClientRegistrationCommandIdentity => {
const packageReference = clientRegistrationPackageReference(command);
if (packageReference !== undefined)
return isOwnedClientRegistrationCommand(command, currentCommandPath)
? "owned"
: "foreign";
const currentPathAvailable =
currentCommandPath !== undefined && currentCommandPath.length > 0;
const nodeScript = command.length === 3 && command[2] === "mcp";
if (nodeScript) {
if (
!isAbsolute(command[0] ?? "") ||
!isAbsolute(command[1] ?? "") ||
!currentPathAvailable
)
return "unknown";
return isOwnedClientRegistrationCommand(command, currentCommandPath)
? "owned"
: "foreign";
}
if (command.length === 2 && command[1] === "mcp") {
const executable = command[0] ?? "";
if (executable === PRODUCT_IDENTITY.cliBinary) return "owned";
const relativePathLike =
!isAbsolute(executable) && /[./\\]/u.test(executable);
if (relativePathLike || (isAbsolute(executable) && !currentPathAvailable))
return "unknown";
}
if (isOwnedClientRegistrationCommand(command, currentCommandPath))
return "owned";
return "foreign";
};
export const isOwnedClientRegistrationCommand = (
command: readonly string[],
currentCommandPath: string = resolve(process.argv[1] ?? "unknown"),
currentCommandPath: string | undefined,
): boolean => {
if (
currentCommandPath !== undefined &&
currentCommandPath.length > 0 &&
command.length === 3 &&
command[2] === "mcp" &&
isAbsolute(command[0] ?? "") &&
isAbsolute(command[1] ?? "") &&
resolve(command[0] ?? "") === resolve(process.execPath) &&
resolve(command[1] ?? "") === currentCommandPath
)
@@ -129,8 +172,9 @@ export const isOwnedClientRegistrationCommand = (
if (
executable === PRODUCT_IDENTITY.cliBinary ||
(isAbsolute(executable) &&
basename(executable) === PRODUCT_IDENTITY.cliBinary) ||
resolve(executable) === currentCommandPath
currentCommandPath !== undefined &&
currentCommandPath.length > 0 &&
resolve(executable) === currentCommandPath)
)
return true;
}
+71 -42
View File
@@ -7,16 +7,17 @@ import {
type ClientConfigurationDocument,
} from "./ClientConfigurationDocument.js";
import { access } from "node:fs/promises";
import { resolve } from "node:path";
import { isAbsolute, resolve } from "node:path";
import { z } from "zod";
import { valid } from "semver";
import { PRODUCT_IDENTITY } from "../identity.js";
import { CURRENT_ENTRYPOINT_PATH } from "../process/CurrentEntrypointPath.js";
import { MCP_STARTUP_POLICY } from "../mcpStartupPolicy.js";
import {
clientRegistrationPackageReference,
isOwnedClientRegistrationCommand,
classifyClientRegistrationCommand,
packageRunnerRegistrationCommand,
} from "./ClientRegistrationIdentity.js";
import {
@@ -50,6 +51,11 @@ export type ClientRegistrationStatus = ClientRegistrationStatusBase &
readonly state: "stale";
readonly remediation: string;
}
| {
readonly command: RegistrationCommand;
readonly state: "unknown";
readonly remediation: string;
}
| {
readonly command: readonly [];
readonly state: "missing" | "invalid" | "manual";
@@ -93,7 +99,7 @@ export interface ClientRegistrationInspection {
/** Project diagnostics from the same inspection used to select maintenance. */
export const readClientRegistrationStatuses = async (
home: string,
currentCommandPath: string = resolve(process.argv[1] ?? "unknown"),
currentCommandPath: string | undefined = CURRENT_ENTRYPOINT_PATH,
options: ClientRegistrationReadOptions = {},
): Promise<readonly ClientRegistrationStatus[]> =>
(
@@ -103,7 +109,7 @@ export const readClientRegistrationStatuses = async (
/** Parse effective registrations and their client-specific activation once. */
export const readClientRegistrationInspections = async (
home: string,
currentCommandPath: string = resolve(process.argv[1] ?? "unknown"),
currentCommandPath: string | undefined = CURRENT_ENTRYPOINT_PATH,
options: ClientRegistrationReadOptions = {},
): Promise<readonly ClientRegistrationInspection[]> => {
const inspections: ClientRegistrationInspection[] = [];
@@ -219,26 +225,32 @@ export const readClientRegistrationInspections = async (
const packageVersionDrift =
packageVersion !== undefined &&
packageVersion !== PRODUCT_IDENTITY.packageVersion;
const alignment = registrationAligned(
registration,
effectiveClient,
currentCommandPath,
options.platform ?? process.platform,
);
record(
configuredStatus(
effectiveClient,
command,
activation === "enabled" &&
!packageVersionDrift &&
registrationAligned(
registration,
effectiveClient,
currentCommandPath,
options.platform ?? process.platform,
) &&
(parsed.dialect !== "dsh" || dshDocument(parsed.document).ready) &&
policyBlock === undefined
? "aligned"
: "stale",
alignment === "unknown"
? "unknown"
: activation === "enabled" &&
!packageVersionDrift &&
alignment === "aligned" &&
(parsed.dialect !== "dsh" ||
dshDocument(parsed.document).ready) &&
policyBlock === undefined
? "aligned"
: "stale",
policyBlock ??
(packageVersionDrift
? packageVersionRemediation(packageReference ?? "", client)
: undefined),
: alignment === "unknown"
? unknownEntryPointRemediation()
: undefined),
packageReference,
),
activation,
@@ -275,25 +287,29 @@ const registrationActivation = (
const registrationAligned = (
registration: z.output<typeof registrationSchema>,
client: SetupClient,
currentCommandPath: string,
currentCommandPath: string | undefined,
platform: NodeJS.Platform,
): boolean => {
): "aligned" | "stale" | "unknown" => {
const command = [registration.command, ...registration.args];
if (!isOwnedClientRegistrationCommand(command, currentCommandPath))
return false;
const identity = classifyClientRegistrationCommand(
command,
currentCommandPath,
);
if (identity === "unknown") return "unknown";
if (identity === "foreign") return "stale";
if (
(client.name === "codex" || client.name === "grok_build") &&
registration.startup_timeout_sec !==
MCP_STARTUP_POLICY.codexStartupTimeoutSeconds
)
return false;
return "stale";
if (
client.name === "kimi" &&
(registration.startupTimeoutMs !==
MCP_STARTUP_POLICY.kimiStartupTimeoutMs ||
registration.toolTimeoutMs !== MCP_STARTUP_POLICY.kimiToolTimeoutMs)
)
return false;
return "stale";
if (
(client.format === "kimi" ||
client.format === "omp" ||
@@ -301,45 +317,55 @@ const registrationAligned = (
registration.type !== undefined &&
registration.type !== "stdio"
)
return false;
if (client.format === "vscode" && registration.type !== "stdio") return false;
return "stale";
if (client.format === "vscode" && registration.type !== "stdio")
return "stale";
// A Harness registration only connects as the rea stdio server; another
// server name or transport leaves the session without REA tools.
if (
client.format === "dsh" &&
(registration.serverName !== "rea" || registration.transport !== "stdio")
)
return false;
return "stale";
if (
client.format === "copilot_cli" &&
(registration.type !== "stdio" ||
JSON.stringify(registration.tools) !== JSON.stringify(["*"]))
)
return false;
return "stale";
if (client.format === "commandcode" && registration.transport !== "stdio")
return false;
if (
command.length === 3 &&
command[2] === "mcp" &&
resolve(command[0] ?? "") === resolve(process.execPath) &&
resolve(command[1] ?? "") === currentCommandPath
)
return true;
return "stale";
if (
(["npx", "bunx"] as const).some((runner) => {
const runnerCommand = packageRunnerRegistrationCommand(runner, platform);
const expected = packageRunnerRegistrationCommand(runner, platform);
return (
command.length === runnerCommand.length &&
command.every((argument, index) => argument === runnerCommand[index])
command.length === expected.length &&
command.every((argument, index) => argument === expected[index])
);
})
)
return true;
return (
return "aligned";
if (
currentCommandPath !== undefined &&
currentCommandPath.length > 0 &&
command.length === 3 &&
command[2] === "mcp" &&
isAbsolute(command[0] ?? "") &&
isAbsolute(command[1] ?? "") &&
resolve(command[0] ?? "") === resolve(process.execPath) &&
resolve(command[1] ?? "") === currentCommandPath
)
return "aligned";
if (
currentCommandPath !== undefined &&
currentCommandPath.length > 0 &&
command.length === 2 &&
command[1] === "mcp" &&
isAbsolute(command[0] ?? "") &&
resolve(command[0] ?? "") === currentCommandPath
);
)
return "aligned";
return "stale";
};
const parseRegistration = (
@@ -400,10 +426,13 @@ const remediation = (client: SetupClient): string => {
return `Quit ${name} fully, run rea setup to refresh this registration, then start ${name} again. A running ${name} can rewrite its configuration from memory and drop the registration.`;
};
const unknownEntryPointRemediation = (): string =>
"The existing registration was retained because its executable identity cannot be verified. Run the installed REA CLI or a supported package runner, then rerun rea doctor; no registration was changed.";
const configuredStatus = (
client: SetupClient,
command: RegistrationCommand,
state: "aligned" | "stale",
state: "aligned" | "stale" | "unknown",
policyRemediation?: string,
packageReference?: string,
): ClientRegistrationStatus =>
+2
View File
@@ -7,6 +7,7 @@ import type { DirectAnalysisDependencies } from "./DirectAnalysisDependencies.js
import { providerCapabilitySet } from "../contracts/toolOutputSchemaPrimitives.js";
import { providerCapabilitySets } from "./binary/BinarySessionStatus.js";
import { sqliteDatabaseCapability } from "./sqlite/SqliteDatabaseCapability.js";
import { CURRENT_ENTRYPOINT_PATH } from "../process/CurrentEntrypointPath.js";
/** Read target-free provider status and operation availability. */
export const runSessionStatus = async (
@@ -31,6 +32,7 @@ export const runSessionStatus = async (
],
server_identity: createServerIdentity({
startedAt: new Date().toISOString(),
commandPath: CURRENT_ENTRYPOINT_PATH,
}),
};
} finally {
+6 -2
View File
@@ -330,7 +330,7 @@ describe("doctor scoped readiness", () => {
client: "cursor",
config_path: "/home/user/.cursor/mcp.json",
command: ["/old/rea", "mcp"],
state: "stale",
state: "unknown",
remediation: "Run rea setup to refresh this registration.",
},
];
@@ -349,7 +349,11 @@ describe("doctor scoped readiness", () => {
expect.objectContaining({ name: "registration:codex", ok: true }),
);
expect(scoped.informational_checks).toContainEqual(
expect.objectContaining({ name: "registration:cursor", ok: false }),
expect.objectContaining({
name: "registration:cursor",
ok: false,
classification: "unknown",
}),
);
const staleSelected = await runDoctor(
+2 -1
View File
@@ -53,6 +53,7 @@ export interface DoctorCheck {
| "unsupported_host"
| "unsupported_target"
| "missing_analysis_engine"
| "unknown"
| "config_drift";
readonly detail?: string;
readonly details?: Readonly<Record<string, JsonValue>>;
@@ -385,7 +386,7 @@ const registrationCheck = (
): DoctorCheck => ({
name: `registration:${registration.client}`,
ok: false,
classification: "config_drift",
classification: registration.state === "unknown" ? "unknown" : "config_drift",
detail: registration.config_path,
remediation: registration.remediation,
});
+1 -1
View File
@@ -174,7 +174,7 @@ const registrationCheck = (
): DoctorCheck => ({
name: `registration:${status.client}`,
ok: false,
classification: "config_drift",
classification: status.state === "unknown" ? "unknown" : "config_drift",
detail: status.config_path,
remediation: status.remediation,
});
+8 -7
View File
@@ -1,9 +1,10 @@
import { describe, expect, it } from "vitest";
import { join, resolve } from "node:path";
import { join } from "node:path";
import { FakeSetupHost, options } from "./Setup.fixture.js";
import { runSetup } from "./Setup.js";
import { setupRegistrationCommand } from "./SetupHost.js";
import { CURRENT_ENTRYPOINT_PATH } from "../process/CurrentEntrypointPath.js";
import { discoverSetupState } from "./SetupPlan.js";
import type { MsixPackageView } from "./MsixPackageStorage.js";
import type { SetupClient } from "./SupportedClients.js";
@@ -333,13 +334,13 @@ describe("setup workflow action selection", () => {
expect(result.status).toBe("ready");
expect(host.configuredCommands).toEqual([
setupRegistrationCommand("win32", undefined),
]);
expect(setupRegistrationCommand("win32", undefined)).toEqual([
process.execPath,
resolve(process.argv[1] ?? "rea"),
"mcp",
setupRegistrationCommand("win32", undefined, CURRENT_ENTRYPOINT_PATH),
]);
expect(
setupRegistrationCommand("win32", undefined, CURRENT_ENTRYPOINT_PATH),
).toEqual([process.execPath, CURRENT_ENTRYPOINT_PATH, "mcp"]);
expect(setupRegistrationCommand("win32", undefined, undefined)).toEqual([]);
expect(setupRegistrationCommand("win32", "npx", undefined)).not.toEqual([]);
});
it.each([
+7 -2
View File
@@ -16,6 +16,7 @@ import { PRODUCT_IDENTITY, SDK_IDENTITY } from "../identity.js";
import { CATALOG_IDENTITY } from "../catalogIdentity.js";
import type { ClientRegistrationStatus } from "./ClientRegistrationStatus.js";
import { setupRegistrationCommand } from "./SetupHost.js";
import { CURRENT_ENTRYPOINT_PATH } from "../process/CurrentEntrypointPath.js";
/** Recording setup host for service-level planning and recovery tests. */
export class FakeSetupHost implements SetupHost {
@@ -24,7 +25,7 @@ export class FakeSetupHost implements SetupHost {
skillDestinations(clientIds: readonly string[]) {
return skillDestinations(this.homeDirectory, clientIds);
}
readonly registrationCommand: readonly string[];
registrationCommand: readonly string[];
registrationCommandBlocker?: string;
nodeVersion = "24.18.0";
version: string | undefined = "14.5";
@@ -57,7 +58,11 @@ export class FakeSetupHost implements SetupHost {
constructor(platform: NodeJS.Platform = "darwin") {
this.platform = platform;
this.registrationCommand = setupRegistrationCommand(platform, undefined);
this.registrationCommand = setupRegistrationCommand(
platform,
undefined,
CURRENT_ENTRYPOINT_PATH,
);
}
macosVersion = (): Promise<string | undefined> =>
+49
View File
@@ -136,6 +136,55 @@ describe("skill plan destinations", () => {
});
describe("setup workflow", () => {
it("blocks client registration when no executable entrypoint is available", async () => {
const host = new FakeSetupHost("win32");
host.registrationCommand = [];
host.registrationCommandBlocker =
"REA could not determine the current executable entrypoint.";
host.clients = [{ name: "codex", configPath: "C:\\config\\codex.toml" }];
const result = await runSetup(
{ ...options(true), clientIds: ["codex"], installSkill: false },
host,
);
expect(result.status).toBe("needs_human");
expect(result.plannedActions).toEqual([]);
expect(result.remediation).toBe(host.registrationCommandBlocker);
expect(host.configurations).toBe(0);
});
it("keeps a relative Node registration out of setup defaults when cwd happens to match", async () => {
const host = new FakeSetupHost();
host.clients = [{ name: "cursor", configPath: "/cursor.json" }];
host.productRegistrations = [
{
client: "cursor",
config_path: "/cursor.json",
command: [process.execPath, "./rea.mjs", "mcp"],
state: "stale",
remediation: "Run rea setup to refresh this registration.",
},
];
const result = await runSetup(
{ ...options(false), clientIds: [], installSkill: false },
host,
);
expect(result.clientStates).toContainEqual(
expect.objectContaining({
client: expect.objectContaining({ name: "cursor" }),
status: "unknown",
configured: false,
}),
);
expect(result.plannedActions).toEqual([]);
expect(host.configurations).toBe(0);
});
});
describe("setup plan application", () => {
it("returns a complete plan without mutation", async () => {
const host = new FakeSetupHost();
host.clients = [{ name: "cursor", configPath: "/cursor.json" }];
+19 -16
View File
@@ -56,21 +56,21 @@ import type {
SetupProviderEnvironment,
} from "./SetupTypes.js";
import type { DoctorScope } from "./Doctor.js";
import { CURRENT_ENTRYPOINT_PATH } from "../process/CurrentEntrypointPath.js";
/** Resolve the executable and arguments used in a managed MCP registration. */
export const setupRegistrationCommand = (
platform: NodeJS.Platform,
packageRunner: PackageRunner | undefined,
): readonly string[] =>
packageRunner !== undefined
? packageRunnerRegistrationCommand(packageRunner, platform)
: platform === "win32"
? [
process.execPath,
resolve(process.argv[1] ?? PRODUCT_IDENTITY.cliBinary),
"mcp",
]
: [resolve(process.argv[1] ?? PRODUCT_IDENTITY.cliBinary), "mcp"];
entrypointPath: string | undefined,
): readonly string[] => {
if (packageRunner !== undefined)
return packageRunnerRegistrationCommand(packageRunner, platform);
if (entrypointPath === undefined || entrypointPath.length === 0) return [];
return platform === "win32"
? [process.execPath, entrypointPath, "mcp"]
: [entrypointPath, "mcp"];
};
/**
* The launcher setup registers, and why it must not be registered when it
@@ -84,14 +84,17 @@ const setupRegistration = (
const registrationCommand = setupRegistrationCommand(
platform,
invokingPackageRunner(environment, platform),
CURRENT_ENTRYPOINT_PATH,
);
const registrationCommandBlocker =
platform === "win32"
? packagePrivateLauncherBlocker(
registrationCommand,
msixPackagesDirectory(homeDirectory, environment),
)
: undefined;
registrationCommand.length === 0
? "REA could not determine the current executable entrypoint. Run setup through the installed REA CLI or a supported package runner before configuring clients."
: platform === "win32"
? packagePrivateLauncherBlocker(
registrationCommand,
msixPackagesDirectory(homeDirectory, environment),
)
: undefined;
return registrationCommandBlocker === undefined
? { registrationCommand }
: { registrationCommand, registrationCommandBlocker };
+32 -9
View File
@@ -1,6 +1,6 @@
import { join } from "node:path";
import { isOwnedClientRegistrationCommand } from "./ClientRegistrationIdentity.js";
import { classifyClientRegistrationCommand } from "./ClientRegistrationIdentity.js";
import {
linuxHopperInstallDisclosure,
type LinuxPackageFamily,
@@ -22,6 +22,7 @@ import {
import { redirectedWriteBlocker } from "./MsixPackageStorage.js";
import type { DoctorReport } from "./Doctor.js";
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
import { CURRENT_ENTRYPOINT_PATH } from "../process/CurrentEntrypointPath.js";
/** Read-only setup facts shared by client selection and preflight. */
export interface SetupDiscovery {
@@ -78,20 +79,29 @@ export const discoverSetupState = async (input: {
);
const clientStates = clients.map((client): SetupClientState => {
const registration = registrationByClient.get(client.name);
const commandIdentity =
registration === undefined
? undefined
: classifyClientRegistrationCommand(
registration.command,
CURRENT_ENTRYPOINT_PATH,
);
const ownedRegistration =
(registration?.state === "aligned" || registration?.state === "stale") &&
isOwnedClientRegistrationCommand(registration.command);
commandIdentity === "owned";
const configured = ownedRegistration;
const status: SetupClientState["status"] =
registration?.state === "invalid"
? "invalid"
: ownedRegistration && registration.state === "aligned"
? "configured"
: ownedRegistration && registration.state === "stale"
? "needs_configuration"
: detectedIds.has(client.name)
: registration?.state === "unknown" || commandIdentity === "unknown"
? "unknown"
: ownedRegistration && registration.state === "aligned"
? "configured"
: ownedRegistration && registration.state === "stale"
? "needs_configuration"
: "missing";
: detectedIds.has(client.name)
? "needs_configuration"
: "missing";
return {
client,
detected: detectedIds.has(client.name),
@@ -103,7 +113,10 @@ export const discoverSetupState = async (input: {
.filter(
(registration) =>
(registration.state === "aligned" || registration.state === "stale") &&
isOwnedClientRegistrationCommand(registration.command),
classifyClientRegistrationCommand(
registration.command,
CURRENT_ENTRYPOINT_PATH,
) === "owned",
)
.map(({ client }) => client);
return {
@@ -150,6 +163,16 @@ export const planSetupActions = async (input: {
const client = byId.get(id);
return client === undefined ? [] : [client];
});
if (
selectedClients.length > 0 &&
input.host.registrationCommandBlocker !== undefined
)
return {
plannedActions: [],
selectedClients,
clientsToConfigure: [],
blocker: input.host.registrationCommandBlocker,
};
const inspections = await Promise.all(
selectedClients.map(async (client) => ({
client,
+7 -2
View File
@@ -67,7 +67,7 @@ export interface SetupHost {
readonly homeDirectory: string;
skillDestinations(clientIds: readonly string[]): readonly SkillDestination[];
readonly registrationCommand: readonly string[];
/** Why no client may be registered with `registrationCommand` from this host. */
/** Why client registration cannot proceed from this host. */
readonly registrationCommandBlocker?: string;
readonly nodeVersion: string;
macosVersion(): Promise<string | undefined>;
@@ -157,7 +157,12 @@ export interface SetupClientState {
readonly client: SetupClient;
readonly detected: boolean;
readonly configured: boolean;
readonly status: "configured" | "missing" | "needs_configuration" | "invalid";
readonly status:
| "configured"
| "missing"
| "needs_configuration"
| "invalid"
| "unknown";
}
/** One concrete setup mutation disclosed before approval. */
+6 -2
View File
@@ -23,6 +23,7 @@ import {
skillDestinations,
} from "./SetupSkill.js";
import { isOwnedClientRegistrationCommand } from "./ClientRegistrationIdentity.js";
import { CURRENT_ENTRYPOINT_PATH } from "../process/CurrentEntrypointPath.js";
import { dshDocument } from "./DshCordisPatch.js";
import { resolveClientConfigTransactionPath } from "./ClientConfigPath.js";
import {
@@ -562,12 +563,15 @@ const isOwnedRegistration = (value: unknown, client: SetupClient): boolean => {
.safeParse(value);
if (!parsed.success) return false;
const command = parsed.data.command;
return isOwnedClientRegistrationCommand(command);
return isOwnedClientRegistrationCommand(command, CURRENT_ENTRYPOINT_PATH);
}
const parsed = registrationSchema.safeParse(value);
if (!parsed.success) return false;
const { command, args } = parsed.data;
return isOwnedClientRegistrationCommand([command, ...args]);
return isOwnedClientRegistrationCommand(
[command, ...args],
CURRENT_ENTRYPOINT_PATH,
);
};
const purgeManagedData = async (
+38 -14
View File
@@ -10,6 +10,10 @@ import type { ToolContract } from "./contracts/toolContractTypes.js";
import { CLI_COMMAND_NAMES } from "./cliCommandNames.js";
import { compareUnicodeCodePoints } from "./domain/unicodeCodePointOrder.js";
import { advertisedInputJsonSchema } from "./contracts/toolSchemaMetadata.js";
import {
presentAdvertisedInputSchema,
type CompactInputSchemaPresentation,
} from "./contracts/compactInputSchema.js";
const sortByName = (
contracts: readonly ToolContract[],
@@ -26,7 +30,10 @@ const sortedToolContracts = sortByName(TOOL_CONTRACTS);
* that predates the untrusted-content framing must not digest equal to one
* that emits `_meta.provenance`, or a stale process would report aligned.
*/
const createToolCatalog = (contracts: readonly ToolContract[]) =>
const createToolCatalog = (
contracts: readonly ToolContract[],
compactPresentation: CompactInputSchemaPresentation | undefined,
) =>
contracts.map((contract) => ({
name: contract.name,
title: contract.title,
@@ -35,7 +42,10 @@ const createToolCatalog = (contracts: readonly ToolContract[]) =>
effects: { ...contract.effects },
returns_target_content: contract.returnsTargetContent,
annotations: contract.annotations,
input_schema: advertisedInputJsonSchema(contract),
input_schema: presentAdvertisedInputSchema(
advertisedInputJsonSchema(contract),
compactPresentation,
),
output_schema: {
type: "object",
...z.toJSONSchema(contract.outputSchema, {
@@ -73,8 +83,9 @@ const digest = (value: unknown): string => {
export const createCatalogDigests = (
contracts: readonly ToolContract[] = TOOL_CONTRACTS,
promptContracts: readonly PromptContract[] = PROMPT_CONTRACTS,
compactPresentation: CompactInputSchemaPresentation | undefined = undefined,
) => {
const tools = createToolCatalog(sortByName(contracts));
const tools = createToolCatalog(sortByName(contracts), compactPresentation);
const prompts = createPromptCatalog(promptContracts);
return {
tools_sha256: digest(tools),
@@ -87,16 +98,29 @@ export const createCatalogDigests = (
} as const;
};
let catalogDigests: ReturnType<typeof createCatalogDigests> | undefined;
export const createCatalogIdentity = (
compactPresentation: CompactInputSchemaPresentation | undefined = undefined,
) => {
const selectedPresentation =
compactPresentation === undefined
? undefined
: { budgetBytes: compactPresentation.budgetBytes };
let catalogDigests: ReturnType<typeof createCatalogDigests> | undefined;
return {
counts: {
cli_commands: CLI_COMMAND_NAMES.length,
mcp_tools: sortedToolContracts.length,
mcp_prompts: promptCatalog.length,
},
get digests() {
return (catalogDigests ??= createCatalogDigests(
TOOL_CONTRACTS,
PROMPT_CONTRACTS,
selectedPresentation,
));
},
} as const;
};
/** 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;
export const CATALOG_IDENTITY = createCatalogIdentity();
+8
View File
@@ -12,6 +12,14 @@ export const COMPACT_INPUT_SCHEMA_BUDGET_BYTES = 13 * 1024;
export const mcpInputSchemaProfileSchema = z.enum(["full", "compact"]);
/** Select the exact compact presentation budget shared by registration and identity. */
export const compactPresentationForMcpInputSchemaProfile = (
profile: "full" | "compact" | undefined,
): { readonly budgetBytes: number } | undefined =>
profile === "compact"
? { budgetBytes: COMPACT_INPUT_SCHEMA_BUDGET_BYTES }
: undefined;
const profileConstraint =
'REA_MCP_INPUT_SCHEMA_PROFILE must be "full" (default) or "compact". The compact profile advertises input schemas without nested annotation prose for providers that cap each tool schema\'s serialized size.';
+15
View File
@@ -26,6 +26,11 @@ const jsonValueTypes = [
export type AdvertisedInputSchemaPresentation = "inline" | "reduced";
/** Budget selected by the caller for compact MCP input-schema presentation. */
export interface CompactInputSchemaPresentation {
readonly budgetBytes: number;
}
export interface CompactedInputSchema {
readonly schema: Record<string, unknown>;
readonly presentation: AdvertisedInputSchemaPresentation;
@@ -335,3 +340,13 @@ export const compactAdvertisedInputSchema = (
presentation: "reduced",
};
};
/** Return the JSON schema clients receive for the selected input presentation. */
export const presentAdvertisedInputSchema = (
schema: Record<string, unknown>,
compactPresentation: CompactInputSchemaPresentation | undefined,
): Record<string, unknown> =>
compactPresentation === undefined
? schema
: compactAdvertisedInputSchema(schema, compactPresentation.budgetBytes)
.schema;
+1 -1
View File
@@ -505,7 +505,7 @@ export const sessionProvider = z
name: z.string(),
version: z.string(),
started_at: z.string(),
command_path: z.string(),
command_path: z.string().nullable(),
}),
sdk: z.object({
server: z.string(),
+2 -2
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env node
import { snapshotEnvironment } from "./process/snapshotEnvironment.js";
import { CURRENT_ENTRYPOINT_PATH } from "./process/CurrentEntrypointPath.js";
import { pathToFileURL } from "node:url";
import { realpathSync } from "node:fs";
@@ -122,7 +123,6 @@ const isDirectEntrypoint = (
}
};
const entryPath = process.argv[1];
if (isDirectEntrypoint(entryPath, import.meta.url)) {
if (isDirectEntrypoint(CURRENT_ENTRYPOINT_PATH, import.meta.url)) {
void runEntrypoint();
}
+23 -4
View File
@@ -5,7 +5,11 @@ import { performance } from "node:perf_hooks";
import { resolve } from "node:path";
import { StringDecoder } from "node:string_decoder";
import { CATALOG_IDENTITY } from "./catalogIdentity.js";
import { createCatalogIdentity } from "./catalogIdentity.js";
import {
compactPresentationForMcpInputSchemaProfile,
parseMcpInputSchemaProfile,
} from "./config/mcpInputSchemaProfile.js";
import { PROMPT_CONTRACTS } from "./contracts/promptContracts.js";
import { TOOL_CONTRACTS } from "./contracts/toolContracts.js";
import { err, ok } from "./domain/result.js";
@@ -75,6 +79,7 @@ interface McpDoctorCheck {
/** Diagnose the actual production stdio adapter with one startup-bounded SDK session. */
export const runProductionMcpDoctor = async (options: McpDoctorOptions) => {
const catalog = catalogIdentityForEnvironment(options.environment);
const deadline =
performance.now() +
(options.deadlineMs ?? MCP_STARTUP_POLICY.doctorDeadlineMs);
@@ -104,6 +109,7 @@ export const runProductionMcpDoctor = async (options: McpDoctorOptions) => {
client,
deadline,
controller.signal,
catalog,
);
} catch (cause: unknown) {
result = {
@@ -189,6 +195,7 @@ const inspectProductionMcpSession = async (
client: Client,
deadline: number,
signal: AbortSignal,
catalog: ReturnType<typeof createCatalogIdentity>,
) => {
const request = requestOptions(deadline, signal);
const [tools, prompts] = await Promise.all([
@@ -238,8 +245,7 @@ const inspectProductionMcpSession = async (
name: "server-identity",
ok:
observedIdentity.packageVersion === PRODUCT_IDENTITY.packageVersion &&
observedIdentity.catalogDigest ===
CATALOG_IDENTITY.digests.combined_sha256,
observedIdentity.catalogDigest === catalog.digests.combined_sha256,
detail: `package=${observedIdentity.packageVersion ?? "missing"}; catalog=${observedIdentity.catalogDigest ?? "missing"}`,
},
{
@@ -262,7 +268,7 @@ const inspectProductionMcpSession = async (
identity: {
expected_package_version: PRODUCT_IDENTITY.packageVersion,
observed_package_version: observedIdentity.packageVersion,
expected_catalog_digest: CATALOG_IDENTITY.digests.combined_sha256,
expected_catalog_digest: catalog.digests.combined_sha256,
observed_catalog_digest: observedIdentity.catalogDigest,
},
request_flow: {
@@ -483,6 +489,19 @@ const targetFreeEnvironment = (
return result;
};
const catalogIdentityForEnvironment = (
environment: Readonly<Record<string, string | undefined>>,
): ReturnType<typeof createCatalogIdentity> => {
const parsed = parseMcpInputSchemaProfile(
environment.REA_MCP_INPUT_SCHEMA_PROFILE,
);
return createCatalogIdentity(
compactPresentationForMcpInputSchemaProfile(
parsed.ok ? parsed.value : undefined,
),
);
};
const captureStderr = () => {
const retained = Buffer.allocUnsafe(MCP_STARTUP_POLICY.doctorStderrBytes);
let retainedBytes = 0;
+11
View File
@@ -0,0 +1,11 @@
import { resolve } from "node:path";
/** Absolute argv entrypoint, or undefined when the process did not supply one. */
export const normalizeEntrypointPath = (
entrypoint: string | undefined,
): string | undefined =>
entrypoint === undefined || entrypoint.length === 0
? undefined
: resolve(entrypoint);
export const CURRENT_ENTRYPOINT_PATH = normalizeEntrypointPath(process.argv[1]);
+21 -9
View File
@@ -21,7 +21,11 @@ import {
} from "@modelcontextprotocol/server";
import { analysisErrorProjectionSchema } from "../contracts/errorSchemas.js";
import { compactAdvertisedInputSchema } from "../contracts/compactInputSchema.js";
import {
presentAdvertisedInputSchema,
type CompactInputSchemaPresentation,
} from "../contracts/compactInputSchema.js";
import { CATALOG_IDENTITY, createCatalogIdentity } from "../catalogIdentity.js";
import { transformAdvertisedInputJsonSchema } from "../contracts/toolSchemaMetadata.js";
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
import { AnalysisResourceConstraintError } from "../domain/analysisErrorCore.js";
@@ -36,10 +40,7 @@ import {
TARGET_CONTENT_RESULT_META,
} from "./toolResultEncoding.js";
/** Compact advertised input schemas within this per-schema byte budget. */
export interface CompactInputSchemaPresentation {
readonly budgetBytes: number;
}
export type { CompactInputSchemaPresentation } from "../contracts/compactInputSchema.js";
const currentRequestSignal = new AsyncLocalStorage<AbortSignal>();
@@ -103,6 +104,11 @@ export const forgetPendingMcpRequest = (
/** Bind oversized MCP error recovery to this server's Evidence ledger. */
export class EvidenceMcpServer extends McpServer {
readonly catalogIdentity: ReturnType<typeof createCatalogIdentity>;
private readonly compactPresentation:
| CompactInputSchemaPresentation
| undefined;
constructor(
info: Implementation,
options: McpServerOptions,
@@ -110,12 +116,18 @@ export class EvidenceMcpServer extends McpServer {
| EvidenceWriter["recordEvidence"]
| undefined,
readonly delivery: ToolResultDelivery,
private readonly compactPresentation:
| CompactInputSchemaPresentation
| undefined = undefined,
compactPresentation: CompactInputSchemaPresentation | undefined = undefined,
private readonly omitOutputSchemas = false,
) {
super(info, options);
this.compactPresentation =
compactPresentation === undefined
? undefined
: { budgetBytes: compactPresentation.budgetBytes };
this.catalogIdentity =
this.compactPresentation === undefined
? CATALOG_IDENTITY
: createCatalogIdentity(this.compactPresentation);
}
/**
@@ -152,7 +164,7 @@ export class EvidenceMcpServer extends McpServer {
typeof transformAdvertisedInputJsonSchema
>[0],
(projected) =>
compactAdvertisedInputSchema(projected, budget).schema,
presentAdvertisedInputSchema(projected, { budgetBytes: budget }),
)
: undefined;
const registeredConfig =
+2 -4
View File
@@ -43,7 +43,7 @@ import {
import { parseMcpResponseBudget } from "../config/mcpResponseBudget.js";
import { parseMcpOutputSchemaProfile } from "../config/mcpOutputSchemaProfile.js";
import {
COMPACT_INPUT_SCHEMA_BUDGET_BYTES,
compactPresentationForMcpInputSchemaProfile,
parseMcpInputSchemaProfile,
} from "../config/mcpInputSchemaProfile.js";
import type { CompactInputSchemaPresentation } from "./EvidenceMcpServer.js";
@@ -612,9 +612,7 @@ const selectCompactInputSchemaPresentation = (
environment.REA_MCP_INPUT_SCHEMA_PROFILE,
);
if (!configured.ok) throw configured.error;
return configured.value === "compact"
? { budgetBytes: COMPACT_INPUT_SCHEMA_BUDGET_BYTES }
: undefined;
return compactPresentationForMcpInputSchemaProfile(configured.value);
};
const registerConfiguredAnalysisTools = (
+3
View File
@@ -18,6 +18,7 @@ import { mcpClientMetadata } from "./mcpClientMetadata.js";
import type { AvailabilityPolicy } from "../application/CapabilityInventory.js";
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
import { err } from "../domain/result.js";
import { CURRENT_ENTRYPOINT_PATH } from "../process/CurrentEntrypointPath.js";
/** Inputs required to register the binary session status tool. */
export interface SessionStatusToolOptions {
@@ -75,6 +76,8 @@ export const registerSessionStatusTool = (
);
const serverIdentity = createServerIdentity({
startedAt,
commandPath: CURRENT_ENTRYPOINT_PATH,
catalog: server.catalogIdentity,
expected: {
...(input.expected_package_version === undefined
? {}
+21 -9
View File
@@ -1,7 +1,10 @@
import { dirname, basename, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { CATALOG_IDENTITY } from "./catalogIdentity.js";
import {
CATALOG_IDENTITY,
type createCatalogIdentity,
} from "./catalogIdentity.js";
import { PRODUCT_IDENTITY, SDK_IDENTITY } from "./identity.js";
/** Optional observations supplied by a client comparing its expected server. */
@@ -27,11 +30,14 @@ const packageRoot = (): string => {
/** Build one runtime identity without claiming alignment from absent observations. */
export const createServerIdentity = (input: {
readonly startedAt: string;
readonly commandPath: string | undefined;
readonly expected?: ExpectedServerIdentity;
readonly client?: ConnectedClientIdentity;
readonly protocolVersion?: string | undefined;
readonly catalog?: ReturnType<typeof createCatalogIdentity>;
}) => {
const serverPath = resolve(process.argv[1] ?? "unknown");
const catalog = input.catalog ?? CATALOG_IDENTITY;
const serverPath = input.commandPath || undefined;
const reasons: string[] = [];
if (
input.expected?.package_version !== undefined &&
@@ -40,17 +46,20 @@ export const createServerIdentity = (input: {
reasons.push("package_version_mismatch");
if (
input.expected?.catalog_digest !== undefined &&
input.expected.catalog_digest !== CATALOG_IDENTITY.digests.combined_sha256
input.expected.catalog_digest !== catalog.digests.combined_sha256
)
reasons.push("catalog_digest_mismatch");
if (
input.expected?.server_path !== undefined &&
serverPath !== undefined &&
resolve(input.expected.server_path) !== serverPath
)
reasons.push("registration_path_mismatch");
const observedExpectation =
input.expected !== undefined &&
Object.values(input.expected).some((value) => value !== undefined);
const pathComparable =
input.expected?.server_path === undefined || serverPath !== undefined;
return {
package: {
name: PRODUCT_IDENTITY.packageName,
@@ -62,7 +71,7 @@ export const createServerIdentity = (input: {
name: PRODUCT_IDENTITY.mcpServerKey,
version: PRODUCT_IDENTITY.packageVersion,
started_at: input.startedAt,
command_path: serverPath,
command_path: serverPath ?? null,
},
sdk: SDK_IDENTITY,
negotiated_protocol_version: input.protocolVersion ?? null,
@@ -75,8 +84,8 @@ export const createServerIdentity = (input: {
expected_version: PRODUCT_IDENTITY.skillVersion,
},
catalog: {
counts: CATALOG_IDENTITY.counts,
digests: CATALOG_IDENTITY.digests,
counts: catalog.counts,
digests: catalog.digests,
},
protocol_features: {
progress: true,
@@ -87,16 +96,19 @@ export const createServerIdentity = (input: {
state:
reasons.length > 0
? "mcp_server_restart_required"
: observedExpectation
: observedExpectation && pathComparable
? "aligned"
: "unknown",
reasons,
remediation:
reasons.length > 0
? "Restart the registered MCP server/client, then compare identity again."
: observedExpectation
: observedExpectation && pathComparable
? null
: "Supply expected package, catalog, or registration identity to compare the live server.",
: input.expected?.server_path !== undefined &&
serverPath === undefined
? "The server entrypoint path is unavailable, so registration identity cannot be compared."
: "Supply expected package, catalog, or registration identity to compare the live server.",
},
} as const;
};
@@ -1,5 +1,5 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { join, relative, resolve } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { parse as parseJsonc, type ParseError } from "jsonc-parser";
@@ -9,7 +9,14 @@ import { configureClientConfiguration } from "../../../src/application/SetupClie
import { systemUninstallHost } from "../../../src/application/Uninstall.js";
import { supportedClients } from "../../../src/application/SupportedClients.js";
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
import { isOwnedClientRegistrationCommand } from "../../../src/application/ClientRegistrationIdentity.js";
import {
classifyClientRegistrationCommand,
isOwnedClientRegistrationCommand,
} from "../../../src/application/ClientRegistrationIdentity.js";
import {
CURRENT_ENTRYPOINT_PATH,
normalizeEntrypointPath,
} from "../../../src/process/CurrentEntrypointPath.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import {
clearClientLocationEnvironment,
@@ -37,6 +44,39 @@ const getClients = (home: string) =>
);
describe("additional client configuration dialects", () => {
it("does not uninstall a Node registration whose relative script only matches this process cwd", async () => {
if (CURRENT_ENTRYPOINT_PATH === undefined)
throw new Error("test runner entrypoint is unavailable");
const home = await createTestTempDirectory("rea-relative-uninstall-");
const cursor = supportedClients(home).find(({ name }) => name === "cursor");
if (cursor === undefined) throw new Error("missing Cursor client");
await mkdir(cursor.markerPath ?? home, { recursive: true });
const relativeScript = relative(process.cwd(), CURRENT_ENTRYPOINT_PATH);
const registration = {
command: process.execPath,
args: [relativeScript, "mcp"],
};
await writeFile(
cursor.configPath,
JSON.stringify({ mcpServers: { rea: registration } }),
);
expect(
await readClientRegistrationStatuses(home, CURRENT_ENTRYPOINT_PATH),
).toContainEqual(
expect.objectContaining({
client: "cursor",
command: [process.execPath, relativeScript, "mcp"],
state: "unknown",
}),
);
await systemUninstallHost(home).removeClient(cursor);
const after = parseJsonc(await readFile(cursor.configPath, "utf8")) as {
readonly mcpServers: Readonly<Record<string, unknown>>;
};
expect(after.mcpServers.rea).toEqual(registration);
});
it("treats an empty XDG config home as unset for OpenCode setup", async () => {
const home = await createTestTempDirectory("rea-opencode-empty-xdg-");
const client = supportedClients(home, "linux", {
@@ -130,7 +170,9 @@ describe("additional client configuration dialects", () => {
],
).toBeUndefined();
});
});
describe("OpenCode JSONC registration edits", () => {
it("preserves OpenCode JSONC comments and sibling registrations during update and removal", async () => {
const home = await createTestTempDirectory("rea-opencode-jsonc-");
const client = getClients(home).find(({ name }) => name === "opencode");
@@ -489,28 +531,43 @@ describe("platform-aware client config paths", () => {
]),
);
});
});
describe("client registration command ownership", () => {
it("recognizes prior exact REA releases while rejecting foreign package commands", () => {
expect(isOwnedClientRegistrationCommand(["rea", "mcp"], undefined)).toBe(
true,
);
const cwdMatchingEntry = resolve("./rea");
expect(
isOwnedClientRegistrationCommand([
"npx",
"-y",
`${PRODUCT_IDENTITY.packageName}@3.2.0`,
"mcp",
]),
).toBe(true);
classifyClientRegistrationCommand(["./rea", "mcp"], cwdMatchingEntry),
).toBe("unknown");
expect(
isOwnedClientRegistrationCommand(["npx", "-y", "another-package", "mcp"]),
isOwnedClientRegistrationCommand(["./rea", "mcp"], cwdMatchingEntry),
).toBe(false);
expect(
isOwnedClientRegistrationCommand([
"bunx",
`${PRODUCT_IDENTITY.packageName}@3.2.0`,
"mcp",
]),
isOwnedClientRegistrationCommand(
["npx", "-y", `${PRODUCT_IDENTITY.packageName}@3.2.0`, "mcp"],
undefined,
),
).toBe(true);
expect(
isOwnedClientRegistrationCommand(["bunx", "another-package", "mcp"]),
isOwnedClientRegistrationCommand(
["npx", "-y", "another-package", "mcp"],
undefined,
),
).toBe(false);
expect(
isOwnedClientRegistrationCommand(
["bunx", `${PRODUCT_IDENTITY.packageName}@3.2.0`, "mcp"],
undefined,
),
).toBe(true);
expect(
isOwnedClientRegistrationCommand(
["bunx", "another-package", "mcp"],
undefined,
),
).toBe(false);
expect(
isOwnedClientRegistrationCommand(
@@ -518,6 +575,12 @@ describe("platform-aware client config paths", () => {
"/packaged/rea.mjs",
),
).toBe(true);
expect(
isOwnedClientRegistrationCommand(
[process.execPath, resolve("unknown"), "mcp"],
normalizeEntrypointPath(""),
),
).toBe(false);
expect(
isOwnedClientRegistrationCommand(
["node", "/packaged/other-agent.mjs", "mcp"],
@@ -1,5 +1,5 @@
import { mkdir, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { join, relative, resolve } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -112,7 +112,9 @@ describe("client registration status", () => {
]);
},
);
});
describe("client registration launcher identity", () => {
it("reports an unversioned npx registration as stale", async () => {
const home = await createTestTempDirectory("rea-registrations-");
await mkdir(join(home, ".codex"));
@@ -132,6 +134,96 @@ describe("client registration status", () => {
]);
});
it("reports an unpinned bunx registration as stale rather than aligned", async () => {
const home = await createTestTempDirectory("rea-bunx-unpinned-");
await writeFile(
join(home, ".claude.json"),
JSON.stringify({
mcpServers: {
rea: { command: "bunx", args: ["rea-agents", "mcp"] },
},
}),
);
const [status] = await readClientRegistrationStatuses(home);
expect(status).toMatchObject({
client: "claude_code",
command: ["bunx", "rea-agents", "mcp"],
state: "stale",
});
});
it("preserves direct command identity as unknown when the current entrypoint is absent", async () => {
const home = await createTestTempDirectory("rea-registration-unknown-");
await mkdir(join(home, ".cursor"));
const command = ["/opt/rea/bin/rea", "mcp"];
await writeFile(
join(home, ".cursor/mcp.json"),
JSON.stringify({
mcpServers: { rea: { command: command[0], args: [command[1]] } },
}),
);
const [status] = await readClientRegistrationStatuses(home, "");
expect(status).toMatchObject({
client: "cursor",
command,
state: "unknown",
remediation: expect.stringContaining("registration was retained"),
});
});
it("does not use the REA process cwd to resolve a client's relative launcher path", async () => {
const home = await createTestTempDirectory("rea-registration-relative-");
await mkdir(join(home, ".cursor"));
const currentEntry = resolve("./rea.mjs");
const configPath = join(home, ".cursor/mcp.json");
const registration = (
scriptPath: string,
executable = process.execPath,
) => ({
mcpServers: {
rea: { command: executable, args: [scriptPath, "mcp"] },
},
});
await writeFile(configPath, JSON.stringify(registration("./rea.mjs")));
const [relativeStatus] = await readClientRegistrationStatuses(
home,
currentEntry,
);
expect(relativeStatus).toMatchObject({
command: [process.execPath, "./rea.mjs", "mcp"],
state: "unknown",
});
await writeFile(configPath, JSON.stringify(registration(currentEntry)));
const [absoluteStatus] = await readClientRegistrationStatuses(
home,
currentEntry,
);
expect(absoluteStatus).toMatchObject({
command: [process.execPath, currentEntry, "mcp"],
state: "aligned",
});
const relativeInterpreter = relative(process.cwd(), process.execPath);
await writeFile(
configPath,
JSON.stringify(registration(currentEntry, relativeInterpreter)),
);
const [relativeInterpreterStatus] = await readClientRegistrationStatuses(
home,
currentEntry,
);
expect(relativeInterpreterStatus).toMatchObject({
command: [relativeInterpreter, currentEntry, "mcp"],
state: "unknown",
});
});
it("reports the exact requested package version when it differs from this CLI", async () => {
const home = await createTestTempDirectory(
"rea-registration-version-drift-",
@@ -178,7 +178,7 @@ describe("Kimi Code package launchers", () => {
"keeps the %s launcher aligned on %s",
async (runner, platform) => {
const { home, client } = await kimiClient();
const launcher = setupRegistrationCommand(platform, runner);
const launcher = setupRegistrationCommand(platform, runner, undefined);
expect(
(await configureClientConfiguration(client, {}, launcher)).status,
).toBe("configured");
@@ -56,7 +56,7 @@ it.each([
expect.objectContaining({ client: "claude_code", state: "stale" }),
);
const client = { name: "claude_code", configPath, format: "json" } as const;
const command = setupRegistrationCommand("win32", "npx");
const command = setupRegistrationCommand("win32", "npx", undefined);
expect(
await configureClientConfiguration(client, {}, command),
).toMatchObject({ status: "configured" });
@@ -93,7 +93,7 @@ it.each(["npx", "bunx"] as const)(
'\uFEFF# Keep this explanation.\nnotify = ["a", "b"]\nliteral = \'C:\\demo\\path\'\ndisabled_mcp_servers = ["rea"]\n\n[mcp_servers.other]\ncommand = "another-server"\n';
await writeFile(configPath, original);
const client = { name: "codex", configPath, format: "toml" } as const;
const command = setupRegistrationCommand("win32", runner);
const command = setupRegistrationCommand("win32", runner, undefined);
expect(
await configureClientConfiguration(client, {}, command),
).toMatchObject({ status: "configured" });
@@ -160,7 +160,7 @@ it.skipIf(process.platform !== "win32").each([
configPath,
format: "json",
} as const;
const command = setupRegistrationCommand("win32", runner);
const command = setupRegistrationCommand("win32", runner, undefined);
// The default writer and npm-run setup must produce the same native launcher.
expect(
await (runner === "npx"
@@ -4,6 +4,10 @@ import { Ajv2020 } from "ajv/dist/2020.js";
import { describe, expect, it } from "vitest";
import { TOOL_CONTRACTS } from "../../../src/contracts/toolContracts.js";
import { PROMPT_CONTRACTS } from "../../../src/contracts/promptContracts.js";
import { advertisedInputJsonSchema } from "../../../src/contracts/toolSchemaMetadata.js";
import { presentAdvertisedInputSchema } from "../../../src/contracts/compactInputSchema.js";
import { createCatalogDigests } from "../../../src/catalogIdentity.js";
import {
schemaArrays,
schemaChildren,
@@ -109,6 +113,68 @@ const fullServer = (): EvidenceMcpServer =>
);
describe("MCP compact input schema profile", () => {
it("matches profile digests to tools/list and excludes stripped annotations", async () => {
const compactPresentation = {
budgetBytes: COMPACT_INPUT_SCHEMA_BUDGET_BYTES,
};
const full = fullServer();
const compact = compactServer();
registerCatalog(full);
registerCatalog(compact);
const [fullTools, compactTools] = await Promise.all([
listToolsThroughTransport(full),
listToolsThroughTransport(compact),
]);
const toolsByName = [
new Map(fullTools.map((tool) => [tool.name, tool])),
new Map(compactTools.map((tool) => [tool.name, tool])),
];
for (const [index, presentation] of [
undefined,
compactPresentation,
].entries()) {
for (const contract of TOOL_CONTRACTS) {
expect(toolsByName[index]?.get(contract.name)?.inputSchema).toEqual(
presentAdvertisedInputSchema(
advertisedInputJsonSchema(contract),
presentation,
),
);
}
}
const fullIdentity = createCatalogDigests();
const compactIdentity = createCatalogDigests(
TOOL_CONTRACTS,
PROMPT_CONTRACTS,
compactPresentation,
);
expect(full.catalogIdentity.digests).toEqual(fullIdentity);
expect(compact.catalogIdentity.digests).toEqual(compactIdentity);
expect(compactIdentity.tools_sha256).not.toBe(fullIdentity.tools_sha256);
expect(
createCatalogDigests(TOOL_CONTRACTS, PROMPT_CONTRACTS, {
budgetBytes: 1,
}).tools_sha256,
).not.toBe(compactIdentity.tools_sha256);
const withoutExamples = TOOL_CONTRACTS.map((contract) =>
contract.examples.length === 0 ? contract : { ...contract, examples: [] },
);
expect(createCatalogDigests(withoutExamples).tools_sha256).not.toBe(
fullIdentity.tools_sha256,
);
expect(
createCatalogDigests(
withoutExamples,
PROMPT_CONTRACTS,
compactPresentation,
).tools_sha256,
).toBe(compactIdentity.tools_sha256);
});
});
describe("MCP compact schema constraints", () => {
it("advertises every tool input schema within the provider budget", async () => {
const server = compactServer();
registerCatalog(server);
+30 -26
View File
@@ -40,34 +40,38 @@ describe("production MCP doctor", () => {
});
});
it("connects to the production stdio child and verifies the canonical catalog", async () => {
const result = await runProductionMcpDoctor({
command: process.execPath,
args: [resolve("scripts/rea.mjs"), "mcp"],
cwd: process.cwd(),
environment: process.env,
});
it.each(["full", "compact"] as const)(
"connects to the production stdio child and verifies the %s catalog",
async (profile) => {
const result = await runProductionMcpDoctor({
command: process.execPath,
args: [resolve("scripts/rea.mjs"), "mcp"],
cwd: process.cwd(),
environment: { ...process.env, REA_MCP_INPUT_SCHEMA_PROFILE: profile },
});
expect(result).toMatchObject({
healthy: true,
adapter: "production-stdio",
inventory: {
tools: {
expected: expect.any(Number),
observed: expect.any(Number),
missing: [],
unexpected: [],
expect(result).toMatchObject({
healthy: true,
adapter: "production-stdio",
inventory: {
tools: {
expected: expect.any(Number),
observed: expect.any(Number),
missing: [],
unexpected: [],
},
},
},
request_flow: { tool: "binary_session", ok: true },
});
expect(result.inventory?.tools.expected).toBe(
result.inventory?.tools.observed,
);
expect(result.inventory?.tools.expected).toBe(
CATALOG_IDENTITY.counts.mcp_tools,
);
}, 30_000);
request_flow: { tool: "binary_session", ok: true },
});
expect(result.inventory?.tools.expected).toBe(
result.inventory?.tools.observed,
);
expect(result.inventory?.tools.expected).toBe(
CATALOG_IDENTITY.counts.mcp_tools,
);
},
30_000,
);
});
describe("production MCP doctor probe validation", () => {
+65 -1
View File
@@ -25,6 +25,7 @@ import {
import { PROMPT_CONTRACTS } from "../../../src/contracts/promptContracts.js";
import { createServer } from "../../../src/server/createServer.js";
import { createServerIdentity } from "../../../src/serverIdentity.js";
import { normalizeEntrypointPath } from "../../../src/process/CurrentEntrypointPath.js";
import { observed } from "../../fixtures/analysisExecution.js";
import { buildCapabilityInventory } from "../../../src/application/CapabilityInventory.js";
import type {
@@ -156,19 +157,23 @@ describe("server and catalog identity", () => {
it("reports unknown without a live comparison and distinguishes aligned from stale", () => {
const unknown = createServerIdentity({
startedAt: "2026-07-13T00:00:00.000Z",
commandPath: normalizeEntrypointPath(""),
});
expect(unknown.alignment.state).toBe("unknown");
expect(unknown.server.command_path).toBeNull();
const aligned = createServerIdentity({
startedAt: "2026-07-13T00:00:00.000Z",
commandPath: "/packaged/rea.mjs",
expected: {
package_version: PRODUCT_IDENTITY.packageVersion,
catalog_digest: CATALOG_IDENTITY.digests.combined_sha256,
server_path: process.argv[1] ?? "unknown",
server_path: "/packaged/rea.mjs",
},
});
expect(aligned.alignment).toMatchObject({ state: "aligned", reasons: [] });
const stale = createServerIdentity({
startedAt: "2026-07-13T00:00:00.000Z",
commandPath: "/packaged/rea.mjs",
expected: {
package_version: "1.2.0",
catalog_digest: "0".repeat(64),
@@ -180,6 +185,23 @@ describe("server and catalog identity", () => {
});
});
it("keeps registration alignment unknown when the server entrypoint is absent", () => {
const identity = createServerIdentity({
startedAt: "2026-07-13T00:00:00.000Z",
commandPath: undefined,
expected: { server_path: "/registered/rea.mjs" },
});
expect(identity.server.command_path).toBeNull();
expect(identity.alignment).toMatchObject({
state: "unknown",
reasons: [],
remediation: expect.stringContaining("entrypoint path is unavailable"),
});
});
});
describe("provider capability inventory", () => {
it("reports composed, host, and target-specific availability truthfully", () => {
const policy = {
processCaptureEnabled: true,
@@ -305,6 +327,48 @@ describe("live server identity over MCP", () => {
]);
}
}, 10_000);
it("compares the caller's digest against the selected compact tools/list catalog", async () => {
const session = createTestBinarySession(availabilityProvider());
const server = createServer(
{ kind: "session", session },
{ environment: { REA_MCP_INPUT_SCHEMA_PROFILE: "compact" } },
);
const client = new Client({ name: "compact-identity-test", version: "1" });
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
try {
await server.connect(serverTransport);
await client.connect(clientTransport);
const listed = await client.listTools();
expect(listed.tools).toHaveLength(TOOL_CONTRACTS.length);
const response = await client.callTool({
name: "binary_session",
arguments: {
expected_catalog_digest:
server.catalogIdentity.digests.combined_sha256,
},
});
const status = z
.object({ result: sessionProvider })
.parse(response.structuredContent).result;
expect(status.server_identity.catalog).toEqual(server.catalogIdentity);
expect(status.server_identity.alignment).toMatchObject({
state: "aligned",
reasons: [],
});
expect(server.catalogIdentity.digests.tools_sha256).not.toBe(
CATALOG_IDENTITY.digests.tools_sha256,
);
} finally {
await Promise.allSettled([
client.close(),
server.close(),
session.close(),
]);
}
}, 10_000);
});
describe("active-target availability over MCP", () => {
@@ -1,6 +1,10 @@
import { describe, expect, it } from "vitest";
import { parseMcpInputSchemaProfile } from "../../../src/config/mcpInputSchemaProfile.js";
import {
compactPresentationForMcpInputSchemaProfile,
COMPACT_INPUT_SCHEMA_BUDGET_BYTES,
parseMcpInputSchemaProfile,
} from "../../../src/config/mcpInputSchemaProfile.js";
describe("MCP input schema profile configuration", () => {
it("accepts the full and compact presentations", () => {
@@ -26,4 +30,14 @@ describe("MCP input schema profile configuration", () => {
expect(parsed.error.message).toContain("REA_MCP_INPUT_SCHEMA_PROFILE");
},
);
it("selects the same result-affecting budget for the compact profile", () => {
expect(compactPresentationForMcpInputSchemaProfile(undefined)).toBe(
undefined,
);
expect(compactPresentationForMcpInputSchemaProfile("full")).toBe(undefined);
expect(compactPresentationForMcpInputSchemaProfile("compact")).toEqual({
budgetBytes: COMPACT_INPUT_SCHEMA_BUDGET_BYTES,
});
});
});