mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 13:26:10 +00:00
fix(mcp): isolate optional observation adapter failures (#838)
This commit is contained in:
1 parent
78f4848d70
commit
be3cfbffdf
31 files changed
+741
-102
No files matched your search
@@ -8,6 +8,7 @@ import type { BrowserScenarioCapturePort } from "../application/BrowserScenarioC
|
||||
import type { ElectronActiveObservationPort } from "../application/ElectronActiveObservationPort.js";
|
||||
import type { ElectronObservationPort } from "../application/ElectronObservationPort.js";
|
||||
import type { JavaScriptRuntimeObservationPort } from "../application/JavaScriptRuntimeObservationPort.js";
|
||||
import type { OptionalProviderLoadFailures } from "../application/OptionalObservationProviders.js";
|
||||
import { PRODUCT_IDENTITY } from "../identity.js";
|
||||
import { silentLogger, type Logger } from "../logger.js";
|
||||
import { registerApplicationTools } from "./registerApplicationTools.js";
|
||||
@@ -51,6 +52,7 @@ export interface CreateServerOptions {
|
||||
readonly electronActiveObservation?: ElectronActiveObservationPort;
|
||||
readonly javascriptRuntimeObservation?: JavaScriptRuntimeObservationPort;
|
||||
readonly availabilityPolicy?: () => SessionAvailability;
|
||||
readonly optionalProviderLoadFailures?: OptionalProviderLoadFailures;
|
||||
}
|
||||
|
||||
const installSessionToolAvailability = (
|
||||
@@ -60,6 +62,7 @@ const installSessionToolAvailability = (
|
||||
) => {
|
||||
if (session === undefined) return undefined;
|
||||
const policy = sessionAvailabilityPolicy(options.availabilityPolicy, {
|
||||
optionalProviderLoadFailures: options.optionalProviderLoadFailures,
|
||||
optionalFeatures: {
|
||||
firmwareInspectionEnabled:
|
||||
options.firmwareAnalysis !== undefined ||
|
||||
@@ -259,19 +262,27 @@ const registerObservationTools = ({
|
||||
registerBrowserTools(server, {
|
||||
...common,
|
||||
browser: options.browserObservation,
|
||||
loadFailure: options.optionalProviderLoadFailures?.browserObservation,
|
||||
});
|
||||
registerBrowserScenarioTool(server, {
|
||||
...common,
|
||||
provider: options.browserScenarioCapture,
|
||||
loadFailure: options.optionalProviderLoadFailures?.browserScenarioCapture,
|
||||
});
|
||||
registerElectronTools(server, {
|
||||
...common,
|
||||
electron: options.electronObservation,
|
||||
electronActive: options.electronActiveObservation,
|
||||
observationLoadFailure:
|
||||
options.optionalProviderLoadFailures?.electronObservation,
|
||||
activeLoadFailure:
|
||||
options.optionalProviderLoadFailures?.electronActiveObservation,
|
||||
});
|
||||
registerJavaScriptRuntimeObservationTools(server, {
|
||||
...common,
|
||||
runtime: options.javascriptRuntimeObservation,
|
||||
loadFailure:
|
||||
options.optionalProviderLoadFailures?.javascriptRuntimeObservation,
|
||||
});
|
||||
registerApplicationTools(server, {
|
||||
evidenceById,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
optionalProviderUnavailable,
|
||||
type OptionalProviderLoadFailure,
|
||||
} from "../application/OptionalObservationProviders.js";
|
||||
import { err } from "../domain/result.js";
|
||||
import type { McpServer } from "@modelcontextprotocol/server";
|
||||
|
||||
import type { BinarySessionPort } from "../application/BinarySession.js";
|
||||
@@ -12,6 +17,7 @@ import { toCallToolResult } from "./toolResult.js";
|
||||
|
||||
interface BrowserScenarioToolRegistration {
|
||||
readonly logger: Logger;
|
||||
readonly loadFailure?: OptionalProviderLoadFailure | undefined;
|
||||
readonly provider: BrowserScenarioCapturePort | undefined;
|
||||
readonly recordEvidence: BinarySessionPort["recordEvidence"] | undefined;
|
||||
}
|
||||
@@ -27,6 +33,11 @@ export const registerBrowserScenarioTool = (
|
||||
toolRegistrationOptions(contract),
|
||||
async (input, context) => {
|
||||
const scenario = browserScenarioSchema.parse(input);
|
||||
if (options.loadFailure !== undefined)
|
||||
return toCallToolResult(
|
||||
err(optionalProviderUnavailable(options.loadFailure, contract.name)),
|
||||
contract,
|
||||
);
|
||||
const result = await logToolExecution(options.logger, contract.name, () =>
|
||||
captureBrowserScenario(options.provider, scenario, {
|
||||
signal: context.mcpReq.signal,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
optionalProviderUnavailable,
|
||||
type OptionalProviderLoadFailure,
|
||||
} from "../application/OptionalObservationProviders.js";
|
||||
import { err } from "../domain/result.js";
|
||||
import type { McpServer, ServerContext } from "@modelcontextprotocol/server";
|
||||
|
||||
import type { BinarySessionPort } from "../application/BinarySession.js";
|
||||
@@ -28,6 +33,7 @@ import { toCallToolResult } from "./toolResult.js";
|
||||
|
||||
interface BrowserToolRegistration {
|
||||
readonly logger: Logger;
|
||||
readonly loadFailure?: OptionalProviderLoadFailure | undefined;
|
||||
readonly browser: BrowserObservationPort | undefined;
|
||||
readonly recordEvidence: BinarySessionPort["recordEvidence"] | undefined;
|
||||
}
|
||||
@@ -182,6 +188,11 @@ const runBrowserTool = async <Input>(
|
||||
) => Promise<Result<Evidence, AnalysisError>>,
|
||||
) => {
|
||||
const { input, context } = request;
|
||||
if (options.loadFailure !== undefined)
|
||||
return toCallToolResult(
|
||||
err(optionalProviderUnavailable(options.loadFailure, contract.name)),
|
||||
contract,
|
||||
);
|
||||
const result = await logToolExecution(options.logger, contract.name, () =>
|
||||
execute(input, {
|
||||
signal: context.mcpReq.signal,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
optionalProviderUnavailable,
|
||||
type OptionalProviderLoadFailure,
|
||||
} from "../application/OptionalObservationProviders.js";
|
||||
import { err } from "../domain/result.js";
|
||||
import type { McpServer, ServerContext } from "@modelcontextprotocol/server";
|
||||
|
||||
import type { BinarySessionPort } from "../application/BinarySession.js";
|
||||
@@ -25,6 +30,8 @@ import { toCallToolResult } from "./toolResult.js";
|
||||
|
||||
interface ElectronToolRegistration {
|
||||
readonly logger: Logger;
|
||||
readonly observationLoadFailure?: OptionalProviderLoadFailure | undefined;
|
||||
readonly activeLoadFailure?: OptionalProviderLoadFailure | undefined;
|
||||
readonly electron: ElectronObservationPort | undefined;
|
||||
readonly electronActive: ElectronActiveObservationPort | undefined;
|
||||
readonly recordEvidence: BinarySessionPort["recordEvidence"] | undefined;
|
||||
@@ -134,6 +141,18 @@ const runElectronTool = async <Input>(
|
||||
) => Promise<Result<Evidence, AnalysisError>>,
|
||||
) => {
|
||||
const { input, context } = request;
|
||||
const failure =
|
||||
contract.name === "capture_electron_scenario"
|
||||
? options.activeLoadFailure
|
||||
: contract.name === "list_electron_targets" ||
|
||||
contract.name === "inspect_electron_page"
|
||||
? options.observationLoadFailure
|
||||
: undefined;
|
||||
if (failure !== undefined)
|
||||
return toCallToolResult(
|
||||
err(optionalProviderUnavailable(failure, contract.name)),
|
||||
contract,
|
||||
);
|
||||
const result = await logToolExecution(options.logger, contract.name, () =>
|
||||
execute(input, {
|
||||
signal: context.mcpReq.signal,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
optionalProviderUnavailable,
|
||||
type OptionalProviderLoadFailure,
|
||||
} from "../application/OptionalObservationProviders.js";
|
||||
import { err } from "../domain/result.js";
|
||||
import type { McpServer, ServerContext } from "@modelcontextprotocol/server";
|
||||
|
||||
import type { BinarySessionPort } from "../application/BinarySession.js";
|
||||
@@ -19,6 +24,7 @@ import { toCallToolResult } from "./toolResult.js";
|
||||
|
||||
interface RuntimeToolRegistration {
|
||||
readonly logger: Logger;
|
||||
readonly loadFailure?: OptionalProviderLoadFailure | undefined;
|
||||
readonly runtime: JavaScriptRuntimeObservationPort | undefined;
|
||||
readonly recordEvidence: BinarySessionPort["recordEvidence"] | undefined;
|
||||
}
|
||||
@@ -68,6 +74,11 @@ const runRuntimeTool = async <Input>(
|
||||
) => Promise<Result<Evidence, AnalysisError>>,
|
||||
) => {
|
||||
const { input, context } = request;
|
||||
if (options.loadFailure !== undefined)
|
||||
return toCallToolResult(
|
||||
err(optionalProviderUnavailable(options.loadFailure, contract.name)),
|
||||
contract,
|
||||
);
|
||||
const result = await logToolExecution(options.logger, contract.name, () =>
|
||||
execute(input, context.mcpReq.signal),
|
||||
);
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { AvailabilityPolicy } from "../application/CapabilityInventory.js";
|
||||
import type { OptionalProviderLoadFailures } from "../application/OptionalObservationProviders.js";
|
||||
import { platform } from "node:process";
|
||||
|
||||
export type SessionAvailability = AvailabilityPolicy;
|
||||
|
||||
export interface SessionAvailabilityDefaults {
|
||||
readonly optionalProviderLoadFailures?:
|
||||
| OptionalProviderLoadFailures
|
||||
| undefined;
|
||||
readonly optionalFeatures?: Pick<
|
||||
SessionAvailability,
|
||||
| "browserObservationEnabled"
|
||||
@@ -21,24 +25,34 @@ export interface SessionAvailabilityDefaults {
|
||||
export const sessionAvailabilityPolicy = (
|
||||
configured: (() => SessionAvailability) | undefined,
|
||||
defaults: SessionAvailabilityDefaults,
|
||||
): (() => SessionAvailability) =>
|
||||
configured ??
|
||||
(() => ({
|
||||
processCaptureEnabled: platform !== "win32",
|
||||
firmwareInspectionEnabled:
|
||||
defaults.optionalFeatures?.firmwareInspectionEnabled ?? false,
|
||||
firmwareExtractionEnabled:
|
||||
defaults.optionalFeatures?.firmwareExtractionEnabled ?? false,
|
||||
androidAnalysisEnabled:
|
||||
defaults.optionalFeatures?.androidAnalysisEnabled ?? false,
|
||||
browserObservationEnabled:
|
||||
defaults.optionalFeatures?.browserObservationEnabled ?? false,
|
||||
browserScenarioEnabled:
|
||||
defaults.optionalFeatures?.browserScenarioEnabled ?? false,
|
||||
electronObservationEnabled:
|
||||
defaults.optionalFeatures?.electronObservationEnabled ?? false,
|
||||
electronAutomationEnabled:
|
||||
defaults.optionalFeatures?.electronAutomationEnabled ?? false,
|
||||
v8InspectorObservationEnabled:
|
||||
defaults.optionalFeatures?.v8InspectorObservationEnabled ?? false,
|
||||
}));
|
||||
): (() => SessionAvailability) => {
|
||||
const policy =
|
||||
configured ??
|
||||
(() => ({
|
||||
processCaptureEnabled: platform !== "win32",
|
||||
firmwareInspectionEnabled:
|
||||
defaults.optionalFeatures?.firmwareInspectionEnabled ?? false,
|
||||
firmwareExtractionEnabled:
|
||||
defaults.optionalFeatures?.firmwareExtractionEnabled ?? false,
|
||||
androidAnalysisEnabled:
|
||||
defaults.optionalFeatures?.androidAnalysisEnabled ?? false,
|
||||
browserObservationEnabled:
|
||||
defaults.optionalFeatures?.browserObservationEnabled ?? false,
|
||||
browserScenarioEnabled:
|
||||
defaults.optionalFeatures?.browserScenarioEnabled ?? false,
|
||||
electronObservationEnabled:
|
||||
defaults.optionalFeatures?.electronObservationEnabled ?? false,
|
||||
electronAutomationEnabled:
|
||||
defaults.optionalFeatures?.electronAutomationEnabled ?? false,
|
||||
v8InspectorObservationEnabled:
|
||||
defaults.optionalFeatures?.v8InspectorObservationEnabled ?? false,
|
||||
}));
|
||||
return () => ({
|
||||
...policy(),
|
||||
...(defaults.optionalProviderLoadFailures === undefined
|
||||
? {}
|
||||
: {
|
||||
optionalProviderLoadFailures: defaults.optionalProviderLoadFailures,
|
||||
}),
|
||||
});
|
||||
};
|
||||
Reference in new issue
Block a user