Files
rea/src/serverIdentity.ts
T
187f113f79 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>
2026-10-14 16:10:46 +08:00

115 lines
3.8 KiB
TypeScript

import { dirname, basename, resolve } from "node:path";
import { fileURLToPath } from "node:url";
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. */
export interface ExpectedServerIdentity {
readonly package_version?: string;
readonly catalog_digest?: string;
readonly server_path?: string;
}
export interface ConnectedClientIdentity {
readonly name: string;
readonly version: string;
}
const packageRoot = (): string => {
const moduleDirectory = dirname(fileURLToPath(import.meta.url));
return basename(moduleDirectory) === "src" &&
basename(dirname(moduleDirectory)) === "dist"
? resolve(moduleDirectory, "../..")
: resolve(moduleDirectory, "..");
};
/** 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 catalog = input.catalog ?? CATALOG_IDENTITY;
const serverPath = input.commandPath || undefined;
const reasons: string[] = [];
if (
input.expected?.package_version !== undefined &&
input.expected.package_version !== PRODUCT_IDENTITY.packageVersion
)
reasons.push("package_version_mismatch");
if (
input.expected?.catalog_digest !== undefined &&
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,
version: PRODUCT_IDENTITY.packageVersion,
root_path: packageRoot(),
build_commit: process.env.REA_BUILD_COMMIT ?? null,
},
server: {
name: PRODUCT_IDENTITY.mcpServerKey,
version: PRODUCT_IDENTITY.packageVersion,
started_at: input.startedAt,
command_path: serverPath ?? null,
},
sdk: SDK_IDENTITY,
negotiated_protocol_version: input.protocolVersion ?? null,
client:
input.client === undefined
? null
: { name: input.client.name, version: input.client.version },
skill: {
name: PRODUCT_IDENTITY.skillName,
expected_version: PRODUCT_IDENTITY.skillVersion,
},
catalog: {
counts: catalog.counts,
digests: catalog.digests,
},
protocol_features: {
progress: true,
cancellation: true,
elicitation: false,
},
alignment: {
state:
reasons.length > 0
? "mcp_server_restart_required"
: observedExpectation && pathComparable
? "aligned"
: "unknown",
reasons,
remediation:
reasons.length > 0
? "Restart the registered MCP server/client, then compare identity again."
: observedExpectation && pathComparable
? null
: 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;
};