Files
reverse-engineering-anything/src/serverIdentity.ts
T
gensokyo-devandgensokyo-dev 065fbec4b3 fix(session): select the registered entrypoint as the running runtime does
Node resolves its script entrypoint lexically, so a registered spelling that
crosses a symlink before `..` names the running file lexically, while Bun
follows native traversal (#8844). Select the expected server path with the
shared runtime-aware entrypoint normalizer before comparing it with the
running entrypoint, and fall back to file identity only when they differ.

Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-11-14 14:06:23 +01:00

148 lines
5.2 KiB
TypeScript

import { statSync } from "node:fs";
import { dirname, basename, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
javaScriptRuntime,
type JavaScriptRuntime,
} from "./domain/runtimeVersion.js";
import { normalizeEntrypointPath } from "./process/CurrentEntrypointPath.js";
import 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, "..");
};
const compareServerPaths = (
expected: string,
observed: string,
runtime: JavaScriptRuntime["id"],
): "aligned" | "mismatch" | "unknown" => {
// Select the registered script the way this runtime does: Node resolves it
// lexically, while Bun follows native traversal through a symlink before `..`.
const script = normalizeEntrypointPath(expected, runtime) ?? expected;
if (script === observed) return "aligned";
if (process.platform === "win32") return "mismatch";
try {
const selected = statSync(script, { bigint: true });
const running = statSync(observed, { bigint: true });
return selected.dev === running.dev && selected.ino === running.ino
? "aligned"
: "mismatch";
} catch {
return "unknown";
}
};
/** 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>;
/** The runtime that selected `commandPath`; defaults to the current one. */
readonly runtime?: JavaScriptRuntime["id"];
}) => {
const catalog = input.catalog;
const serverPath = input.commandPath || undefined;
const pathComparison =
input.expected?.server_path === undefined || serverPath === undefined
? "unknown"
: compareServerPaths(
input.expected.server_path,
serverPath,
input.runtime ?? javaScriptRuntime(process.versions).id,
);
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 (pathComparison === "mismatch") 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 || pathComparison !== "unknown";
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."
: input.expected?.server_path !== undefined &&
pathComparison === "unknown"
? "The expected or running server entrypoint could not be inspected, so registration identity cannot be compared."
: "Supply expected package, catalog, or registration identity to compare the live server.",
},
} as const;
};