Files
reverse-engineering-anything/src/server/registerSessionStatusTool.ts
T
morluto 150e996a86 refactor(providers)!: await owned readiness observations
Carry asynchronous readiness through provider selection, session status, CLI and MCP. Join observations during cancellation and shutdown, keep status readable during finalization, and retain mixed provider errors and exact cleanup dependencies through the shared inspection failure owner.

Validation: 255 focused tests across 18 files; 21 registry tests rerun with primary restoration assertion; check:fast; docs:check and 5 README conformance tests. Registered MCP and SDK schema checks use controlled providers; no real Ghidra/JVM verification.

This is a prerequisite for #4561. The synchronous Ghidra Java producer and its doctor/client ownership migration remain unfinished.

BREAKING CHANGE: binary_session candidate availability can be unknown during owner finalization. Consumers must handle this state; the internal session status API is now asynchronous.

Refs: #4561
2026-10-17 09:57:43 +08:00

159 lines
5.8 KiB
TypeScript

import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import type { BinarySessionPort } from "../application/binary/BinarySessionPort.js";
import type { ProviderAvailability } from "../application/AnalysisProvider.js";
import type { JebAnalysisAvailability } from "../application/jeb/JebAnalysisPort.js";
import { AnalysisError } from "../domain/analysisErrorBase.js";
import {
buildCapabilityInventory,
compactToolAvailability,
} from "../application/CapabilityInventory.js";
import { toolContract } from "../contracts/toolContracts.js";
import type {
ClientFeatureAvailability,
ToolAvailability,
} from "../contracts/toolOutputSchemaPrimitives.js";
import { jsonObjectSchema, type JsonValue } from "../domain/jsonValue.js";
import { createServerIdentity } from "../serverIdentity.js";
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";
/** Provider-owned live probes used by session discovery. */
export interface SessionAvailabilityProbes {
readonly androidAnalysisAvailability: (
signal: AbortSignal,
) => Promise<ProviderAvailability>;
readonly adbAnalysisAvailability?: (
signal: AbortSignal,
) => Promise<ProviderAvailability>;
readonly apktoolAnalysisAvailability?: (
signal: AbortSignal,
) => Promise<ProviderAvailability>;
readonly jebAnalysisAvailability?: (
signal: AbortSignal,
) => Promise<JebAnalysisAvailability>;
}
/** Inputs required to register the binary session status tool. */
export interface SessionStatusToolOptions extends SessionAvailabilityProbes {
readonly server: EvidenceMcpServer;
readonly session: BinarySessionPort;
readonly contract: ReturnType<typeof toolContract<"binary_session">>;
readonly startedAt: string;
readonly availabilityPolicy: () => AvailabilityPolicy;
}
/** Register the read-only provider and target status operation. */
export const registerSessionStatusTool = (
options: SessionStatusToolOptions,
): void => {
const {
server,
session,
contract,
startedAt,
availabilityPolicy,
androidAnalysisAvailability,
adbAnalysisAvailability,
apktoolAnalysisAvailability,
jebAnalysisAvailability,
} = options;
server.registerTool(
contract.name,
toolRegistrationOptions(contract),
async (input, context) => {
const { client, clientFeatures, protocolVersion } = mcpClientMetadata(
context,
server.server,
);
let androidAvailability: ProviderAvailability;
let adbAvailability: ProviderAvailability | undefined;
let apktoolAvailability: ProviderAvailability | undefined;
let jebAvailability: JebAnalysisAvailability | undefined;
let status: JsonValue;
try {
const [android, adb, apktool, jeb] = await Promise.all([
androidAnalysisAvailability(context.mcpReq.signal),
adbAnalysisAvailability?.(context.mcpReq.signal) ?? undefined,
apktoolAnalysisAvailability?.(context.mcpReq.signal) ?? undefined,
jebAnalysisAvailability?.(context.mcpReq.signal) ?? undefined,
] as const);
androidAvailability = android;
adbAvailability = adb;
apktoolAvailability = apktool;
jebAvailability = jeb;
status = await session.status({ signal: context.mcpReq.signal });
} catch (cause) {
if (!(cause instanceof AnalysisError)) throw cause;
return server.delivery.toCallToolResult(err(cause), contract);
}
const statusObject = jsonObjectSchema.parse(status);
const toolAvailability = buildCapabilityInventory(
status,
{
...availabilityPolicy(),
androidAnalysisAvailability: androidAvailability,
...(adbAvailability === undefined
? {}
: { adbAnalysisAvailability: adbAvailability }),
...(apktoolAvailability === undefined
? {}
: { apktoolAnalysisAvailability: apktoolAvailability }),
...(jebAvailability === undefined
? {}
: { jebAnalysisAvailability: jebAvailability }),
},
clientFeatures,
);
const serverIdentity = createServerIdentity({
startedAt,
commandPath: CURRENT_ENTRYPOINT_PATH,
catalog: server.catalogIdentity,
expected: {
...(input.expected_package_version === undefined
? {}
: { package_version: input.expected_package_version }),
...(input.expected_catalog_digest === undefined
? {}
: { catalog_digest: input.expected_catalog_digest }),
...(input.expected_server_path === undefined
? {}
: { server_path: input.expected_server_path }),
},
...(client === undefined ? {} : { client }),
...(protocolVersion === undefined ? {} : { protocolVersion }),
});
return server.delivery.toCallToolResult(
{
ok: true,
value: projectSessionStatus({
status: statusObject,
toolAvailability,
serverIdentity,
clientFeatures,
}),
},
contract,
);
},
);
};
const projectSessionStatus = (options: {
readonly status: Readonly<
Record<string, import("../domain/jsonValue.js").JsonValue>
>;
readonly toolAvailability: readonly ToolAvailability[];
readonly serverIdentity: ReturnType<typeof createServerIdentity>;
readonly clientFeatures: ClientFeatureAvailability;
}) =>
jsonObjectSchema.parse({
...options.status,
tool_availability: compactToolAvailability(options.toolAvailability),
client_features: options.clientFeatures,
server_identity: options.serverIdentity,
});