Files
reverse-engineering-anything/src/server/createServer.ts
T
morluto 6259ac41a4 fix(mcp): guide target recovery for scoped tools (#8659)
* fix(mcp): guide target recovery for scoped tools

* test(mcp): cover unscoped target recovery

* fix(mcp): warn before scoped session restart

* refactor(mcp): own scoped target recovery guidance

* fix(mcp): include unknown history in restart guidance
2026-11-03 09:49:49 +08:00

870 lines
27 KiB
TypeScript

import {
createCapabilityServices,
type CapabilityServiceOptions,
} from "../composition/capabilityServices.js";
import { registerToolFamilies } from "../contracts/toolFamilyIds.js";
import { SCOPED_NO_TARGET_RECOVERY } from "./recoveryInstructions.js";
import { AnalysisSettings } from "../application/AnalysisSettings.js";
import { parseMcpInputBudget } from "../config/mcpInputBudget.js";
import { registerAnalysisSettingsTools } from "./registerAnalysisSettingsTools.js";
import { registerDevicetreeTool } from "./registerDevicetreeTool.js";
import { registerGpuCodeObjectsTools } from "./registerGpuCodeObjectsTools.js";
import { serverInstructions } from "./serverInstructions.js";
import { registerCaptureTools } from "./registerCaptureTools.js";
import { registerSignalTools } from "./registerSignalTools.js";
import { registerWasmTools } from "./registerWasmTools.js";
import { registerApkSignatureTools } from "./registerApkSignatureTools.js";
import { retainedDebuggerDiagnostics } from "../process/ownedDebuggerCleanupError.js";
import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
import { captureProviderEnvironment } from "./providerEnvironment.js";
import { createByteEntropyService } from "../composition/binaryDiagnostics.js";
import { registerElfNotesTool } from "./registerElfNotesTool.js";
import { registerAppleIpsTool } from "./registerAppleIpsTool.js";
import { registerKernelModuleTool } from "./registerKernelModuleTool.js";
import { registerPeLoadConfigTool } from "./registerPeLoadConfigTool.js";
import { registerPeCertificatesTool } from "./registerPeCertificatesTool.js";
import { registerPeImportsTool } from "./registerPeImportsTool.js";
import { registerPeResourcesTool } from "./registerPeResourcesTool.js";
import { registerNativeExportsTool } from "./registerNativeExportsTool.js";
import { registerEvmTools } from "./registerEvmTools.js";
import { registerSqliteTools } from "./registerSqliteTools.js";
import { registerRecordedCrashTools } from "./registerRecordedCrashTools.js";
import { registerDesktopApplicationTool } from "./registerDesktopApplicationTool.js";
import { registerModelArtifactTools } from "./registerModelArtifactTools.js";
import { registerGoTools } from "./registerGoTools.js";
import { registerIl2cppTools } from "./registerIl2cppTools.js";
import { registerRustTools } from "./registerRustTools.js";
import { registerPythonPycTool } from "./registerPythonPycTool.js";
import { registerErlangTools } from "./registerErlangTools.js";
import { registerAnalysisViewTool } from "./registerAnalysisViewTool.js";
import { registerBinaryDiagnosticsTools } from "./registerBinaryDiagnosticsTools.js";
import { registerFridaTools } from "./registerFridaTools.js";
import { EvidenceMcpServer } from "./EvidenceMcpServer.js";
import { catalogIdentityForToolFamilies } from "../catalogIdentity.js";
import { parseMcpToolFamilies } from "../config/mcpToolFamilies.js";
import { createToolResultDelivery } from "./toolResult.js";
import type { ToolResultDelivery } from "./ToolResultDelivery.js";
import { parseMcpResponseBudget } from "../config/mcpResponseBudget.js";
import {
parseMcpOutputSchemaProfile,
type McpOutputSchemaProfile,
} from "../config/mcpOutputSchemaProfile.js";
import {
compactPresentationForMcpInputSchemaProfile,
parseMcpInputSchemaProfile,
} from "../config/mcpInputSchemaProfile.js";
import type { CompactInputSchemaPresentation } from "../contracts/compactInputSchema.js";
import type { BinarySessionPort } from "../application/binary/BinarySessionPort.js";
import { PRODUCT_IDENTITY } from "../identity.js";
import { silentLogger } from "../logger.js";
import type { Logger } from "pino";
import { registerApplicationTools } from "./registerApplicationTools.js";
import { registerArtifactTools } from "./registerArtifactTools.js";
import { registerNativeTools } from "./registerNativeTools.js";
import { registerBrowserScenarioTool } from "./registerBrowserScenarioTool.js";
import { registerBrowserTools } from "./registerBrowserTools.js";
import { registerWebScriptTool } from "./registerWebScriptTool.js";
import { registerWebModuleTool } from "./registerWebModuleTool.js";
import { registerWebSourceLocationTool } from "./registerWebSourceLocationTool.js";
import { registerWebRuntimeTools } from "./registerWebRuntimeTools.js";
import { registerWebNetworkCaptureTool } from "./registerWebNetworkCaptureTool.js";
import { registerJavaScriptRecoveryTool } from "./registerJavaScriptRecoveryTool.js";
import { JavaScriptRecoveryService } from "../application/javascript/JavaScriptRecoveryService.js";
import { registerElectronTools } from "./registerElectronTools.js";
import { registerEnhancedTools } from "./registerEnhancedTools.js";
import { registerJavaScriptRuntimeObservationTools } from "./registerJavaScriptRuntimeObservationTools.js";
import { registerManagedTools } from "./registerManagedTools.js";
import { registerFirmwareTools } from "./registerFirmwareTools.js";
import { registerAndroidTools } from "./registerAndroidTools.js";
import { registerApktoolTools } from "./registerApktoolTools.js";
import { registerPcapTools } from "./registerPcapTools.js";
import { registerFlutterTools } from "./registerFlutterTools.js";
import { registerJebTools } from "./registerJebTools.js";
import { registerAdbTools } from "./registerAdbTools.js";
import { AndroidAnalysisService } from "../application/android/AndroidAnalysisService.js";
import { JebAnalysisService } from "../application/jeb/JebAnalysisService.js";
import { PcapAnalysisService } from "../application/pcap/PcapAnalysisService.js";
import { ApktoolResourceAnalysisService } from "../application/apktool/ApktoolResourceAnalysisService.js";
import { FlutterBuildAnalysisService } from "../application/flutter/FlutterBuildAnalysisService.js";
import { AdbDeviceAnalysisService } from "../application/adb/AdbDeviceAnalysisService.js";
import { registerManagedWorkflowTools } from "./registerManagedWorkflowTools.js";
import { registerOfficialTools } from "./registerOfficialTools.js";
import { registerGuidedPrompts } from "./registerPrompts.js";
import { registerSessionTools } from "./registerSessionTools.js";
import type { AvailabilityPolicy } from "../application/CapabilityInventory.js";
import { sessionAvailabilityPolicy } from "./sessionAvailabilityPolicy.js";
import {
withAdmittedAnalysis,
type ServerAnalysisSource,
} from "./analysisAdmission.js";
import { registerReverseEngineeringTools } from "./registerReverseEngineeringTools.js";
import { registerCutterTools } from "./registerCutterTools.js";
import { registerGdbTools } from "./registerGdbTools.js";
import { registerRizinDebugTools } from "./registerRizinDebugTools.js";
export interface CreateServerOptions extends CapabilityServiceOptions {
readonly analysisSettings?: AnalysisSettings;
/** Preserve the connection's creation time when materializing its runtime. */
readonly startedAt?: string;
/** Existing SDK server whose deferred registrations are being materialized. */
readonly mcpServer?: EvidenceMcpServer;
readonly environment?: Readonly<NodeJS.ProcessEnv>;
readonly delivery?: ToolResultDelivery;
readonly providerEnvironment?: Readonly<NodeJS.ProcessEnv>;
readonly logger?: Logger;
readonly availabilityPolicy?: () => AvailabilityPolicy;
}
const createMcpServer = (
session: BinarySessionPort | undefined,
delivery: ToolResultDelivery,
compactPresentation: CompactInputSchemaPresentation | undefined,
outputSchemaProfile: McpOutputSchemaProfile,
advertisedToolFamilies: ReadonlySet<string> | undefined,
inputElementBudget: string | undefined,
): EvidenceMcpServer => {
const configured = parseMcpInputBudget(inputElementBudget);
if (!configured.ok) throw configured.error;
return new EvidenceMcpServer(
{
name: PRODUCT_IDENTITY.mcpServerKey,
version: PRODUCT_IDENTITY.packageVersion,
},
{
capabilities: {},
maxToolInputElements: configured.value,
instructions: serverInstructions(
session !== undefined,
advertisedToolFamilies,
),
},
session === undefined
? undefined
: (evidence) => session.recordEvidence(evidence),
delivery,
compactPresentation,
outputSchemaProfile,
catalogIdentityForToolFamilies(
compactPresentation,
outputSchemaProfile,
advertisedToolFamilies,
),
advertisedToolFamilies,
);
};
/**
* Construct one MCP server without acquiring subprocess resources.
* Supplying a session adds target lifecycle tools; omitting it retains the
* fixed-target seam used by focused tests and embedders.
*/
export const createServer = (
source: ServerAnalysisSource,
options: CreateServerOptions = {},
): EvidenceMcpServer => {
const session = source.kind === "session" ? source.session : undefined;
const analysis = source.kind === "session" ? source.session : source.analysis;
const environment = snapshotEnvironment(options.environment ?? process.env);
const analysisSettings =
options.analysisSettings ?? new AnalysisSettings(environment);
const compactInputPresentation =
selectCompactInputSchemaPresentation(environment);
const selectedOutputSchemaProfile = outputSchemaProfile(environment);
const advertisedToolFamilies = selectAdvertisedToolFamilies(environment);
const providerEnvironment = captureProviderEnvironment(
options.providerEnvironment,
environment,
);
const baseDelivery = selectToolResultDelivery(environment, options.delivery);
const delivery =
session !== undefined &&
advertisedToolFamilies !== undefined &&
!advertisedToolFamilies.has("session")
? baseDelivery.withNoTargetRecovery(SCOPED_NO_TARGET_RECOVERY)
: baseDelivery;
const capabilities = createCapabilityServices({
environment,
providerEnvironment,
settings: analysisSettings.read,
options,
platform: process.platform,
arch: process.arch,
});
const {
evmInterface,
sqliteDatabase,
binaryLayout,
recordedCrash,
webSourceLocation,
webModuleTrace,
desktopApplication,
goBinary,
rustBinary,
erlangBeam,
modelArtifact,
gpuCodeObjects,
il2cppMetadata,
peResources,
nativeExports,
devicetree,
elfNotes,
appleIps,
kernelModule,
peLoadConfig,
peCertificates,
peImports,
electronFuses,
pythonPyc,
webNetworkCapture,
javascriptRecovery,
artifactResources,
reverseEngineering,
firmwareAnalysis,
gdbSessions,
rizinDebugSessions,
android,
pcap,
flutter,
fridaInstrumentation,
wasmArtifact,
apkSignature,
packetCapture,
signalRecording,
webRuntime,
apktool,
jeb,
adbDevice,
cutter,
} = capabilities.services;
const startedAt = options.startedAt ?? new Date().toISOString();
const logger = options.logger ?? silentLogger;
const server =
options.mcpServer ??
createMcpServer(
session,
delivery,
compactInputPresentation,
selectedOutputSchemaProfile,
advertisedToolFamilies,
environment.REA_MCP_MAX_INPUT_ELEMENTS,
);
server.analysisSettings = analysisSettings;
if (options.mcpServer !== undefined) {
server.attachRuntime(
session === undefined
? undefined
: (evidence) => session.recordEvidence(evidence),
delivery,
catalogIdentityForToolFamilies(
compactInputPresentation,
selectedOutputSchemaProfile,
advertisedToolFamilies,
),
);
}
const closeProviders = capabilities.close;
const availability =
session === undefined
? undefined
: (() => {
const basePolicy = sessionAvailabilityPolicy(
options.availabilityPolicy,
{
...capabilities.availability,
optionalProviderLoadFailures:
options.optionalProviderLoadFailures,
},
);
return {
policy: (): AvailabilityPolicy => {
const value = basePolicy();
const failures = capabilities.observeProviderLoadFailures();
return Object.keys(failures).length === 0
? value
: {
...value,
optionalProviderLoadFailures: {
...value.optionalProviderLoadFailures,
...failures,
},
};
},
};
})();
const toolLogger = logger.child({ layer: "server" });
const {
evidenceById,
activeTarget,
recordEvidence,
recordEvidenceWithUnknown,
} = createSessionRecorders(server, session);
const analysisAdmission = withAdmittedAnalysis(source);
server.withAdmittedAnalysis = analysisAdmission;
const recordSessionEvidence =
session === undefined
? undefined
: (
evidence: Parameters<
NonNullable<typeof session>["recordEvidence"]
>[0],
) => session.recordEvidence(evidence);
const previousOnclose = server.server.onclose;
server.server.onclose = () => {
previousOnclose?.();
void closeProviders().catch((cause: unknown) => {
logger.error(
{
error: cause instanceof Error ? cause.message : String(cause),
...retainedDebuggerDiagnostics(cause),
},
"Provider cleanup failed during server shutdown",
);
});
};
const closeServer = server.close.bind(server);
server.close = async () => {
const results = await Promise.allSettled([closeServer(), closeProviders()]);
const failures = results.flatMap((result) =>
result.status === "rejected" ? [result.reason as unknown] : [],
);
if (failures.length === 1) throw failures[0];
if (failures.length > 1)
throw new AggregateError(failures, "REA server cleanup failed");
};
const recordUnknown =
session === undefined
? undefined
: (input: Parameters<NonNullable<typeof session>["recordUnknown"]>[0]) =>
session.recordUnknown(input);
const analysisOptions = {
logger: toolLogger,
activeTarget,
recordEvidence,
recordUnknown,
evidenceById,
withAdmittedAnalysis: analysisAdmission,
};
const evidenceOptions = {
logger: toolLogger,
activeTarget,
recordEvidence,
withAdmittedAnalysis: analysisAdmission,
};
const common = {
logger: toolLogger,
recordEvidence,
withAdmittedAnalysis: analysisAdmission,
};
registerToolFamilies({
direct: () => registerOfficialTools(server, analysisOptions),
enhanced: () =>
registerEnhancedTools(server, {
...analysisOptions,
analysisProfile:
session === undefined
? undefined
: (operation) =>
session.analysisProfile() ?? session.analysisProfile(operation),
allowsSnapshotReplay:
session === undefined
? undefined
: (operation) => session.allowsSnapshotReplay(operation),
recordWorkflowSnapshot:
session === undefined
? undefined
: (input) => session.recordWorkflowSnapshot(input),
}),
native: () => {
registerNativeTools(server, { ...evidenceOptions, evidenceById });
registerReverseEngineeringTools(
server,
reverseEngineering,
toolLogger,
delivery,
recordSessionEvidence,
analysisAdmission,
session === undefined
? undefined
: () => session.invalidateSnapshotForExternalMutation(),
session === undefined
? undefined
: (path, signal, operation, rebindFailure) =>
session.withAdmittedExternalMutation(
path,
signal,
operation,
rebindFailure,
),
evidenceById,
);
registerCutterTools(
server,
cutter,
toolLogger,
delivery,
recordSessionEvidence,
session,
);
registerGdbTools(
server,
gdbSessions,
toolLogger,
delivery,
recordSessionEvidence,
session,
evidenceById,
);
registerRizinDebugTools(
server,
rizinDebugSessions,
toolLogger,
delivery,
recordSessionEvidence,
session,
);
registerBinaryDiagnosticsTools(
server,
binaryLayout,
createByteEntropyService(artifactResources),
toolLogger,
recordEvidence,
evidenceById,
analysisAdmission,
);
registerPeResourcesTool(
server,
peResources,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerNativeExportsTool(
server,
nativeExports,
toolLogger,
recordEvidence,
evidenceById,
analysisAdmission,
);
registerDevicetreeTool(
server,
devicetree,
toolLogger,
recordEvidence,
evidenceById,
analysisAdmission,
);
registerElfNotesTool(
server,
elfNotes,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerAppleIpsTool(
server,
appleIps,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerKernelModuleTool(
server,
kernelModule,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerPeLoadConfigTool(
server,
peLoadConfig,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerPeCertificatesTool(
server,
peCertificates,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerPeImportsTool(
server,
peImports,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerRecordedCrashTools(
server,
recordedCrash,
toolLogger,
recordEvidence,
analysisAdmission,
);
},
artifact: () => {
registerArtifactTools(server, { ...evidenceOptions, evidenceById });
registerDesktopApplicationTool(
server,
desktopApplication,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerModelArtifactTools(
server,
modelArtifact,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerGpuCodeObjectsTools(
server,
gpuCodeObjects,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerEvmTools(
server,
evmInterface,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerGoTools(
server,
goBinary,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerIl2cppTools(
server,
il2cppMetadata,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerRustTools(
server,
rustBinary,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerPythonPycTool(
server,
pythonPyc,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerErlangTools(
server,
erlangBeam,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerWasmTools(
server,
wasmArtifact,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerApkSignatureTools(
server,
apkSignature,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
registerSqliteTools(
server,
sqliteDatabase,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerCaptureTools(
server,
packetCapture,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerSignalTools(
server,
signalRecording,
toolLogger,
recordEvidence,
analysisAdmission,
);
},
managed: () => {
registerManagedTools(server, { ...evidenceOptions, evidenceById });
if (session !== undefined)
registerManagedWorkflowTools(server, {
logger: toolLogger,
recordEvidence,
recordEvidenceWithUnknown,
session,
withAdmittedAnalysis: analysisAdmission,
});
},
firmware: () =>
registerFirmwareTools(
server,
firmwareAnalysis,
toolLogger,
recordEvidence,
analysisAdmission,
),
android: () =>
registerAndroidTools(
server,
new AndroidAnalysisService(android, recordEvidence),
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
),
pcap: () =>
registerPcapTools(
server,
new PcapAnalysisService(pcap),
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
),
flutter: () =>
registerFlutterTools(
server,
new FlutterBuildAnalysisService(flutter),
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
),
frida: () =>
registerFridaTools(
server,
fridaInstrumentation,
toolLogger,
delivery,
recordSessionEvidence,
evidenceById,
),
apktool: () =>
registerApktoolTools(
server,
new ApktoolResourceAnalysisService(apktool),
toolLogger,
recordEvidence,
analysisAdmission,
),
jeb: () =>
registerJebTools(
server,
new JebAnalysisService(jeb),
toolLogger,
recordEvidence,
analysisAdmission,
),
adb: () =>
registerAdbTools(
server,
new AdbDeviceAnalysisService(adbDevice),
toolLogger,
recordEvidence,
analysisAdmission,
),
browser: () => {
registerBrowserTools(server, {
...common,
evidenceById,
browser: options.browserObservation,
loadFailure: options.optionalProviderLoadFailures?.browserObservation,
});
registerBrowserScenarioTool(server, {
...common,
evidenceById,
provider: options.browserScenarioCapture,
loadFailure:
options.optionalProviderLoadFailures?.browserScenarioCapture,
});
registerWebRuntimeTools(
server,
webRuntime,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerWebNetworkCaptureTool(
server,
webNetworkCapture,
toolLogger,
recordEvidence,
analysisAdmission,
evidenceById,
);
},
electron: () =>
registerElectronTools(server, {
...common,
artifactResources,
environment,
evidenceById,
electron: options.electronObservation,
electronActive: options.electronActiveObservation,
electronFuses,
observationLoadFailure:
options.optionalProviderLoadFailures?.electronObservation,
activeLoadFailure:
options.optionalProviderLoadFailures?.electronActiveObservation,
}),
"javascript-runtime": () =>
registerJavaScriptRuntimeObservationTools(server, {
...common,
runtime: options.javascriptRuntimeObservation,
loadFailure:
options.optionalProviderLoadFailures?.javascriptRuntimeObservation,
}),
application: () => {
registerAnalysisSettingsTools(server, analysisSettings);
registerAnalysisViewTool(
server,
toolLogger,
evidenceById,
recordEvidence,
analysisAdmission,
artifactResources,
);
registerWebScriptTool(server, { ...common, artifactResources });
registerWebModuleTool(
server,
webModuleTrace,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerWebSourceLocationTool(
server,
webSourceLocation,
toolLogger,
recordEvidence,
analysisAdmission,
);
registerJavaScriptRecoveryTool(
server,
new JavaScriptRecoveryService(javascriptRecovery),
toolLogger,
recordEvidence,
analysisAdmission,
);
registerApplicationTools(server, {
artifactResources,
evidenceById,
logger: toolLogger,
recordEvidence,
recordEvidenceWithUnknown,
withAdmittedAnalysis: analysisAdmission,
});
},
session: () => {
if (session === undefined) return;
registerSessionTools(server, session, toolLogger, {
environment,
artifactResources,
...(availability === undefined
? {}
: { availabilityPolicy: availability.policy }),
...capabilities.probes,
startedAt,
});
},
});
registerGuidedPrompts(server, analysis, session);
return server;
};
const selectToolResultDelivery = (
environment: Readonly<NodeJS.ProcessEnv>,
selected: ToolResultDelivery | undefined,
): ToolResultDelivery => {
if (selected !== undefined) return selected;
const configured = parseMcpResponseBudget(
environment.REA_MCP_MAX_RESPONSE_BYTES,
);
if (!configured.ok) throw configured.error;
return createToolResultDelivery(configured.value);
};
const outputSchemaProfile = (
environment: Readonly<NodeJS.ProcessEnv>,
): McpOutputSchemaProfile => {
const configured = parseMcpOutputSchemaProfile(
environment.REA_MCP_OUTPUT_SCHEMA_PROFILE,
);
if (!configured.ok) throw configured.error;
return configured.value ?? "full";
};
const selectCompactInputSchemaPresentation = (
environment: Readonly<NodeJS.ProcessEnv>,
): CompactInputSchemaPresentation | undefined => {
const configured = parseMcpInputSchemaProfile(
environment.REA_MCP_INPUT_SCHEMA_PROFILE,
);
if (!configured.ok) throw configured.error;
return compactPresentationForMcpInputSchemaProfile(configured.value);
};
const createSessionRecorders = (
server: EvidenceMcpServer,
session: BinarySessionPort | undefined,
) => ({
evidenceById:
session === undefined
? undefined
: session.evidenceForAnalysis.bind(session),
activeTarget:
session === undefined ? undefined : () => session.activeTarget(),
recordEvidence:
session === undefined
? undefined
: (evidence: Parameters<typeof session.recordEvidence>[0]) =>
session.recordEvidence(evidence),
recordEvidenceWithUnknown:
session === undefined
? undefined
: (
evidence: Parameters<typeof session.recordEvidenceWithUnknown>[0],
input: Parameters<typeof session.recordEvidenceWithUnknown>[1],
) => session.recordEvidenceWithUnknown(evidence, input),
});
const selectAdvertisedToolFamilies = (
environment: Readonly<NodeJS.ProcessEnv>,
): ReadonlySet<string> | undefined => {
const configured = parseMcpToolFamilies(environment.REA_MCP_TOOL_FAMILIES);
if (!configured.ok) throw configured.error;
return configured.value === undefined ? undefined : new Set(configured.value);
};