rea-agents 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -19
- package/bridge/ghidra/ReaGhidraBridge.java +524 -4
- package/bridge/hopper_bridge.py +313 -28
- package/dist/application/AnalysisClientCleanup.js +14 -0
- package/dist/application/AnalysisContextQueries.js +130 -0
- package/dist/application/AnalysisSnapshotCache.js +4 -2
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
- package/dist/application/ArtifactInventory/scanCanonical.js +1 -1
- package/dist/application/BinarySession.js +20 -8
- package/dist/application/BinarySessionComposition.js +21 -0
- package/dist/application/BinarySessionRecords.js +79 -9
- package/dist/application/BinarySessionStatus.js +31 -1
- package/dist/application/BrowserEvidence.js +14 -0
- package/dist/application/BrowserScenarioCapturePort.js +1 -0
- package/dist/application/BrowserScenarioCaptureService.js +53 -0
- package/dist/application/BrowserScenarioEvidence.js +40 -0
- package/dist/application/CallPathTracing.js +160 -0
- package/dist/application/CapabilityInventory.js +84 -14
- package/dist/application/ClientRegistrationStatus.js +4 -1
- package/dist/application/CompositeProvider.js +10 -1
- package/dist/application/DirectAnalysis.js +13 -4
- package/dist/application/EnhancedLiteralTracing.js +118 -0
- package/dist/application/EnhancedTools.js +71 -127
- package/dist/application/EvidenceLedger.js +45 -16
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
- package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
- package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
- package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
- package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
- package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
- package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
- package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
- package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
- package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
- package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
- package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
- package/dist/application/LazyAnalysisProvider.js +77 -0
- package/dist/application/LoopbackReplay.js +1 -1
- package/dist/application/NativeApiInspection.js +80 -0
- package/dist/application/ProcessReactiveEffects.js +6 -0
- package/dist/application/ProcessReactiveHarness.js +16 -1
- package/dist/application/ProjectPermissionStore.js +1 -0
- package/dist/application/ReconstructionObligationAuthority.js +72 -0
- package/dist/application/ReconstructionObligationCandidates.js +265 -0
- package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
- package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
- package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
- package/dist/application/ReconstructionObligationLedgerService.js +72 -0
- package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
- package/dist/application/ReconstructionObligationPolicies.js +132 -0
- package/dist/application/ReconstructionReadinessService.js +48 -0
- package/dist/application/SessionProviderRouter.js +6 -6
- package/dist/application/SetupClientConfiguration.js +4 -2
- package/dist/application/runtime.js +26 -9
- package/dist/artifacts/ArtifactProvider.js +87 -22
- package/dist/browser/BrowserScenarioSecrets.js +53 -0
- package/dist/browser/BrowserScenarioSessionPort.js +1 -0
- package/dist/browser/CdpBrowserProvider.js +2 -2
- package/dist/browser/CdpCaptureDocuments.js +57 -0
- package/dist/browser/CdpCaptureStorage.js +71 -29
- package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
- package/dist/browser/CdpEndpoint.js +8 -6
- package/dist/browser/CdpPageCapture.js +2 -0
- package/dist/browser/JavaScriptRuntimeScope.js +38 -0
- package/dist/browser/PlaywrightBrowserScenarioProvider.js +259 -0
- package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
- package/dist/browser/PlaywrightScenarioActions.js +62 -0
- package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
- package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
- package/dist/browser/PlaywrightScenarioEvents.js +301 -0
- package/dist/browser/PlaywrightScenarioSession.js +300 -0
- package/dist/browser/V8InspectorCaptureProjection.js +110 -0
- package/dist/browser/V8InspectorEndpoint.js +72 -0
- package/dist/browser/V8InspectorProvider.js +267 -0
- package/dist/catalogIdentity.js +11 -3
- package/dist/cli/artifactCommands.js +44 -3
- package/dist/cli/coreAnalysisCommands.js +78 -0
- package/dist/cli.js +4 -0
- package/dist/cliApplicationCommands.js +30 -2
- package/dist/cliBrowserAdvancedCommands.js +17 -5
- package/dist/cliBrowserCommands.js +1 -0
- package/dist/cliBrowserScenarioCommands.js +43 -0
- package/dist/cliCommandNames.js +9 -0
- package/dist/cliJavaScriptRuntimeCommands.js +164 -0
- package/dist/cliObservationOptions.js +4 -0
- package/dist/cliPolicyCommands.js +1 -0
- package/dist/config/environment.js +12 -0
- package/dist/config/parseConfig.js +88 -17
- package/dist/contracts/analysisSearchContract.js +14 -0
- package/dist/contracts/applicationToolContracts.js +86 -2
- package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
- package/dist/contracts/artifactToolContracts.js +29 -2
- package/dist/contracts/browserProviderToolContracts.js +7 -0
- package/dist/contracts/browserScenarioToolContracts.js +81 -0
- package/dist/contracts/browserToolContracts.js +88 -98
- package/dist/contracts/enhancedInputs.js +35 -6
- package/dist/contracts/functionInstructionContract.js +8 -0
- package/dist/contracts/functionWorkflowToolContracts.js +30 -0
- package/dist/contracts/hopperMemoryContracts.js +22 -0
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
- package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
- package/dist/contracts/promptContracts.js +14 -15
- package/dist/contracts/reconstructionReadinessExample.js +436 -0
- package/dist/contracts/toolContractExamples.js +8 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +34 -25
- package/dist/contracts/toolEffects.js +25 -0
- package/dist/contracts/toolOutputSchemaGroups.js +120 -30
- package/dist/contracts/toolOutputSchemaPrimitives.js +37 -0
- package/dist/domain/analysisErrorPresentation.js +8 -2
- package/dist/domain/analysisErrorProjection.js +9 -0
- package/dist/domain/artifactInspection.js +347 -0
- package/dist/domain/browserCaptureComparison.js +82 -0
- package/dist/domain/browserObservation.js +8 -1
- package/dist/domain/browserObservationErrors.js +1 -0
- package/dist/domain/browserObservationSchemas.js +26 -0
- package/dist/domain/browserScenario.js +165 -0
- package/dist/domain/browserScenarioCapture.js +57 -0
- package/dist/domain/browserScenarioCaptureValues.js +204 -0
- package/dist/domain/browserScenarioDiff.js +324 -0
- package/dist/domain/browserScenarioDiffValues.js +157 -0
- package/dist/domain/browserScenarioNormalization.js +45 -0
- package/dist/domain/browserScenarioValues.js +372 -0
- package/dist/domain/bytecodeProvider.js +179 -0
- package/dist/domain/conformancePackage.js +169 -0
- package/dist/domain/conformanceReplay.js +104 -0
- package/dist/domain/conformanceTrustGate.js +193 -0
- package/dist/domain/customProtocolCapture.js +137 -0
- package/dist/domain/eventProcessTree.js +214 -0
- package/dist/domain/evidenceBundle.js +47 -0
- package/dist/domain/hopperStartupFailure.js +1 -1
- package/dist/domain/hopperValues.js +14 -1
- package/dist/domain/inputIssueProjection.js +7 -1
- package/dist/domain/javascriptRuntimeObservation.js +170 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
- package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
- package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
- package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
- package/dist/domain/javascriptSemanticAnalysis.js +13 -16
- package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
- package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
- package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
- package/dist/domain/javascriptSemanticDataEffects.js +284 -0
- package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
- package/dist/domain/javascriptSemanticFingerprints.js +174 -0
- package/dist/domain/javascriptSemanticIr.js +21 -1
- package/dist/domain/javascriptSemanticObjects.js +115 -0
- package/dist/domain/javascriptSemanticPromises.js +290 -0
- package/dist/domain/javascriptSemanticResources.js +136 -0
- package/dist/domain/javascriptSourceParser.js +15 -0
- package/dist/domain/javascriptStaticAnalysis.js +12 -15
- package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
- package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
- package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
- package/dist/domain/mobileApplicationInvestigation.js +159 -0
- package/dist/domain/nativeApiBoundary.js +130 -0
- package/dist/domain/objcSwiftMetadata.js +150 -0
- package/dist/domain/packageAnalysis.js +123 -0
- package/dist/domain/peInspection.js +196 -0
- package/dist/domain/processCapture.js +8 -2
- package/dist/domain/processReactiveScenario.js +1 -0
- package/dist/domain/processReactiveTransition.js +9 -4
- package/dist/domain/protocolCapture.js +243 -0
- package/dist/domain/providerCleanupError.js +11 -0
- package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
- package/dist/domain/reconstructionReadiness.js +121 -0
- package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
- package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
- package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
- package/dist/domain/reconstructionReadinessSchemas.js +240 -0
- package/dist/domain/sourceToBundleComparison.js +335 -0
- package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
- package/dist/domain/sourceToBundleSignals.js +202 -0
- package/dist/domain/webCaptureDiff.js +141 -27
- package/dist/domain/webScreenshot.js +1 -1
- package/dist/dotnet/ManagedStaticProvider.js +34 -5
- package/dist/generatedMcpToolCatalog.js +37 -0
- package/dist/generatedPackageMetadata.js +4 -4
- package/dist/ghidra/GhidraDefaults.js +1 -1
- package/dist/ghidra/GhidraFunctionValues.js +40 -2
- package/dist/ghidra/GhidraProviderCapabilities.js +47 -38
- package/dist/hopper/HopperCleanup.js +158 -0
- package/dist/hopper/HopperClient.js +93 -99
- package/dist/hopper/HopperProvider.js +61 -30
- package/dist/hopper/HopperRequestQueue.js +215 -0
- package/dist/hopper/HopperResponseStream.js +46 -0
- package/dist/hopper/protocol.js +63 -20
- package/dist/main/transport.js +24 -27
- package/dist/main.js +14 -6
- package/dist/mcpDoctor.js +27 -4
- package/dist/mcpStartupPolicy.js +14 -0
- package/dist/process/ProcessCommandIdentity.js +19 -0
- package/dist/process/ProcessOwnership.js +11 -8
- package/dist/server/DynamicToolAvailability.js +70 -0
- package/dist/server/LazyToolCatalog.js +134 -0
- package/dist/server/createServer.js +92 -154
- package/dist/server/hydrateServerTools.js +191 -0
- package/dist/server/lifecycleProgress.js +10 -0
- package/dist/server/permissionFailure.js +12 -0
- package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
- package/dist/server/registerApplicationTools/helpers.js +11 -7
- package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
- package/dist/server/registerApplicationTools/readiness.js +55 -0
- package/dist/server/registerApplicationTools.js +6 -0
- package/dist/server/registerArtifactTools.js +9 -1
- package/dist/server/registerBrowserScenarioTool.js +25 -0
- package/dist/server/registerBrowserTools.js +2 -2
- package/dist/server/registerCloseLifecycleTool.js +57 -0
- package/dist/server/registerEnhancedTools.js +28 -7
- package/dist/server/registerEvidenceResources.js +54 -101
- package/dist/server/registerEvidenceTools.js +9 -2
- package/dist/server/registerInvestigationTools.js +1 -2
- package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
- package/dist/server/registerReconstructionObligationLedgerResource.js +32 -0
- package/dist/server/registerReconstructionReadinessResource.js +32 -0
- package/dist/server/registerSessionRecordTools.js +82 -12
- package/dist/server/registerSessionStatusTool.js +11 -8
- package/dist/server/registerSessionTools.js +32 -58
- package/dist/server/sessionAvailabilityPolicy.js +10 -8
- package/dist/server/toolRegistrationOptions.js +21 -6
- package/dist/server/toolResult.js +12 -0
- package/package.json +28 -11
- package/scripts/package-runner-bootstrap.mjs +72 -0
- package/scripts/rea.mjs +11 -1
- package/skills/reverse-engineer-anything/SKILL.md +10 -3
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { parseBridgeMessageLine, } from "./protocol.js";
|
|
2
|
+
const MAX_LINE_BYTES = 10 * 1024 * 1024;
|
|
3
|
+
/** Incrementally validates bounded Hopper NDJSON response frames. */
|
|
4
|
+
export class HopperResponseStream {
|
|
5
|
+
options;
|
|
6
|
+
#buffer = "";
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
push(chunk) {
|
|
11
|
+
this.#buffer += chunk;
|
|
12
|
+
let newline = this.#buffer.indexOf("\n");
|
|
13
|
+
while (newline >= 0) {
|
|
14
|
+
const line = this.#buffer.slice(0, newline).trim();
|
|
15
|
+
this.#buffer = this.#buffer.slice(newline + 1);
|
|
16
|
+
if (Buffer.byteLength(line) > MAX_LINE_BYTES) {
|
|
17
|
+
this.options.abort("Hopper response exceeded the maximum line size");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (line.length > 0 && !this.#acceptLine(line))
|
|
21
|
+
return;
|
|
22
|
+
newline = this.#buffer.indexOf("\n");
|
|
23
|
+
}
|
|
24
|
+
if (Buffer.byteLength(this.#buffer) > MAX_LINE_BYTES) {
|
|
25
|
+
this.options.abort("Hopper response exceeded the maximum line size");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
reset() {
|
|
29
|
+
this.#buffer = "";
|
|
30
|
+
}
|
|
31
|
+
#acceptLine(line) {
|
|
32
|
+
const parsed = parseBridgeMessageLine(line);
|
|
33
|
+
if (!parsed.ok) {
|
|
34
|
+
this.options.abort(parsed.error.message, parsed.error);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (this.options.accept(parsed.value))
|
|
38
|
+
return true;
|
|
39
|
+
if (parsed.value.id >= this.options.nextRequestId() ||
|
|
40
|
+
this.options.hasQueued(parsed.value.id)) {
|
|
41
|
+
this.options.abort("Hopper returned an unknown response id");
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/hopper/protocol.js
CHANGED
|
@@ -2,29 +2,72 @@ import { z } from "zod";
|
|
|
2
2
|
import { HopperProtocolError, HopperRemoteError } from "../domain/errors.js";
|
|
3
3
|
import { jsonValueSchema } from "../domain/jsonValue.js";
|
|
4
4
|
import { err, ok } from "../domain/result.js";
|
|
5
|
+
const remoteErrorSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
code: z.number().int(),
|
|
8
|
+
message: z.string().max(512),
|
|
9
|
+
type: z
|
|
10
|
+
.enum([
|
|
11
|
+
"remote",
|
|
12
|
+
"authorization",
|
|
13
|
+
"invalid_request",
|
|
14
|
+
"capability_unavailable",
|
|
15
|
+
"bridge_exception",
|
|
16
|
+
])
|
|
17
|
+
.default("remote"),
|
|
18
|
+
})
|
|
19
|
+
.strict();
|
|
5
20
|
const responseSchema = z.union([
|
|
6
|
-
z
|
|
21
|
+
z
|
|
22
|
+
.object({
|
|
7
23
|
id: z.number().int().nonnegative(),
|
|
8
24
|
result: jsonValueSchema,
|
|
9
|
-
})
|
|
10
|
-
|
|
25
|
+
})
|
|
26
|
+
.strict(),
|
|
27
|
+
z
|
|
28
|
+
.object({
|
|
11
29
|
id: z.number().int().nonnegative(),
|
|
12
|
-
error:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
type: z
|
|
16
|
-
.enum([
|
|
17
|
-
"remote",
|
|
18
|
-
"authorization",
|
|
19
|
-
"invalid_request",
|
|
20
|
-
"bridge_exception",
|
|
21
|
-
])
|
|
22
|
-
.default("remote"),
|
|
23
|
-
}),
|
|
24
|
-
}),
|
|
30
|
+
error: remoteErrorSchema,
|
|
31
|
+
})
|
|
32
|
+
.strict(),
|
|
25
33
|
]);
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
const eventSchema = z
|
|
35
|
+
.object({
|
|
36
|
+
id: z.number().int().nonnegative(),
|
|
37
|
+
event: z.discriminatedUnion("type", [
|
|
38
|
+
z
|
|
39
|
+
.object({
|
|
40
|
+
type: z.literal("progress"),
|
|
41
|
+
phase: z.string().min(1).max(64),
|
|
42
|
+
completed: z.number().nonnegative().finite(),
|
|
43
|
+
total: z.number().nonnegative().finite().nullable(),
|
|
44
|
+
message: z.string().min(1).max(512),
|
|
45
|
+
terminal: z.boolean().optional(),
|
|
46
|
+
})
|
|
47
|
+
.strict(),
|
|
48
|
+
z
|
|
49
|
+
.object({
|
|
50
|
+
type: z.literal("diagnostic"),
|
|
51
|
+
error: remoteErrorSchema,
|
|
52
|
+
})
|
|
53
|
+
.strict(),
|
|
54
|
+
]),
|
|
55
|
+
})
|
|
56
|
+
.strict()
|
|
57
|
+
.superRefine((message, context) => {
|
|
58
|
+
if (message.event.type === "progress" &&
|
|
59
|
+
message.event.total !== null &&
|
|
60
|
+
message.event.completed > message.event.total) {
|
|
61
|
+
context.addIssue({
|
|
62
|
+
code: "custom",
|
|
63
|
+
message: "Progress total cannot be less than completed",
|
|
64
|
+
path: ["event", "total"],
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const messageSchema = z.union([responseSchema, eventSchema]);
|
|
69
|
+
/** Parse one complete Hopper NDJSON response or event line. */
|
|
70
|
+
export const parseBridgeMessageLine = (line) => {
|
|
28
71
|
let decoded;
|
|
29
72
|
try {
|
|
30
73
|
decoded = JSON.parse(line);
|
|
@@ -32,10 +75,10 @@ export const parseResponseLine = (line) => {
|
|
|
32
75
|
catch (cause) {
|
|
33
76
|
return err(new HopperProtocolError("Hopper returned malformed JSON", { cause }));
|
|
34
77
|
}
|
|
35
|
-
const parsed =
|
|
78
|
+
const parsed = messageSchema.safeParse(decoded);
|
|
36
79
|
return parsed.success
|
|
37
80
|
? ok(parsed.data)
|
|
38
|
-
: err(new HopperProtocolError("Hopper returned a
|
|
81
|
+
: err(new HopperProtocolError("Hopper returned a message outside the bridge contract", { cause: parsed.error }));
|
|
39
82
|
};
|
|
40
83
|
/** Project a parsed response into its result or expected remote failure. */
|
|
41
84
|
export const responseResult = (response) => "error" in response
|
package/dist/main/transport.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createServer } from "../server/createServer.js";
|
|
1
|
+
import { createServer, } from "../server/createServer.js";
|
|
2
2
|
import { MCP_CONNECTION_LOST, MCP_CONNECTION_START_FAILED, } from "./messages.js";
|
|
3
3
|
export const startMcpTransport = async (dependencies, session, serverContext) => {
|
|
4
4
|
const liveServers = new Set();
|
|
@@ -13,36 +13,12 @@ export const startMcpTransport = async (dependencies, session, serverContext) =>
|
|
|
13
13
|
investigationInputRoots: serverContext.runtimeState.investigationRoots,
|
|
14
14
|
analysisSnapshotFilePolicy: serverContext.runtimeState.snapshotPolicy,
|
|
15
15
|
permissionAuthority: serverContext.permissionAuthority,
|
|
16
|
-
|
|
17
|
-
electronObservation: serverContext.electronObservation,
|
|
16
|
+
loadOptionalProviders: serverContext.loadOptionalProviders,
|
|
18
17
|
artifactIntegrityContinueEnabled: () => serverContext.runtimeState.currentConfig
|
|
19
18
|
.artifactIntegrityContinueEnabled,
|
|
20
19
|
javascriptReplayPolicy: serverContext.runtimeState.javascriptReplayPolicy,
|
|
21
20
|
managedRuntimePolicy: serverContext.runtimeState.managedRuntimePolicy,
|
|
22
|
-
availabilityPolicy: () => (
|
|
23
|
-
processCaptureEnabled: serverContext.runtimeState.currentConfig.processExecutionPolicy
|
|
24
|
-
.enabled,
|
|
25
|
-
evidenceFileRoots: serverContext.runtimeState.currentConfig.evidenceFilePolicy
|
|
26
|
-
.roots.length,
|
|
27
|
-
investigationInputRoots: serverContext.runtimeState.currentConfig.investigationInputRoots
|
|
28
|
-
.length,
|
|
29
|
-
browserObservationEnabled: serverContext.runtimeState.currentConfig
|
|
30
|
-
.browserObservationEnabled &&
|
|
31
|
-
serverContext.runtimeState.currentConfig.browserCdpEndpoints
|
|
32
|
-
.length > 0 &&
|
|
33
|
-
serverContext.runtimeState.currentConfig.browserAllowedOrigins
|
|
34
|
-
.length > 0,
|
|
35
|
-
electronObservationEnabled: serverContext.runtimeState.currentConfig
|
|
36
|
-
.electronObservationEnabled &&
|
|
37
|
-
serverContext.runtimeState.currentConfig.electronCdpEndpoints
|
|
38
|
-
.length > 0 &&
|
|
39
|
-
serverContext.runtimeState.currentConfig.electronFileRoots
|
|
40
|
-
.length > 0,
|
|
41
|
-
javascriptReplayEnabled: serverContext.runtimeState.currentConfig.javascriptReplayPolicy
|
|
42
|
-
.enabled,
|
|
43
|
-
managedRuntimeEnabled: serverContext.runtimeState.currentConfig.managedRuntimePolicy
|
|
44
|
-
.enabled,
|
|
45
|
-
}),
|
|
21
|
+
availabilityPolicy: () => runtimeAvailability(serverContext.runtimeState),
|
|
46
22
|
});
|
|
47
23
|
liveServers.add(server);
|
|
48
24
|
return server;
|
|
@@ -61,3 +37,24 @@ export const startMcpTransport = async (dependencies, session, serverContext) =>
|
|
|
61
37
|
}
|
|
62
38
|
return { ok: true, handle, liveServers };
|
|
63
39
|
};
|
|
40
|
+
const runtimeAvailability = (state) => ({
|
|
41
|
+
processCaptureEnabled: state.currentConfig.processExecutionPolicy.enabled,
|
|
42
|
+
evidenceFileRoots: state.currentConfig.evidenceFilePolicy.roots.length,
|
|
43
|
+
investigationInputRoots: state.currentConfig.investigationInputRoots.length,
|
|
44
|
+
browserObservationEnabled: state.currentConfig.browserObservationEnabled &&
|
|
45
|
+
state.currentConfig.browserCdpEndpoints.length > 0 &&
|
|
46
|
+
state.currentConfig.browserAllowedOrigins.length > 0,
|
|
47
|
+
browserScenarioEnabled: state.currentConfig.browserScenarioPolicy.enabled &&
|
|
48
|
+
state.currentConfig.browserScenarioPolicy.allowedOrigins.length > 0 &&
|
|
49
|
+
(state.currentConfig.browserScenarioPolicy.executableRoots.length > 0 ||
|
|
50
|
+
state.currentConfig.browserScenarioPolicy.cdpEndpoints.length > 0),
|
|
51
|
+
electronObservationEnabled: state.currentConfig.electronObservationEnabled &&
|
|
52
|
+
state.currentConfig.electronCdpEndpoints.length > 0 &&
|
|
53
|
+
state.currentConfig.electronFileRoots.length > 0,
|
|
54
|
+
v8InspectorObservationEnabled: state.currentConfig.v8InspectorObservationEnabled &&
|
|
55
|
+
state.currentConfig.v8InspectorEndpoints.length > 0 &&
|
|
56
|
+
(state.currentConfig.v8InspectorFileRoots.length > 0 ||
|
|
57
|
+
state.currentConfig.v8InspectorAllowedOrigins.length > 0),
|
|
58
|
+
javascriptReplayEnabled: state.currentConfig.javascriptReplayPolicy.enabled,
|
|
59
|
+
managedRuntimeEnabled: state.currentConfig.managedRuntimePolicy.enabled,
|
|
60
|
+
});
|
package/dist/main.js
CHANGED
|
@@ -7,8 +7,6 @@ import { createBinarySession } from "./application/runtime.js";
|
|
|
7
7
|
import { createLogger } from "./logger.js";
|
|
8
8
|
import { projectAnalysisError } from "./domain/errors.js";
|
|
9
9
|
import { loadConfiguredPermissionAuthority } from "./application/PermissionConfiguration.js";
|
|
10
|
-
import { CdpBrowserProvider } from "./browser/CdpBrowserProvider.js";
|
|
11
|
-
import { CdpElectronProvider } from "./browser/CdpElectronProvider.js";
|
|
12
10
|
import { SERVER_START_FAILED } from "./main/messages.js";
|
|
13
11
|
import { createRuntimeState } from "./main/state.js";
|
|
14
12
|
import { openInitialTarget } from "./main/startup.js";
|
|
@@ -58,8 +56,6 @@ export const run = async (dependencies = runtimeDependencies()) => {
|
|
|
58
56
|
return 1;
|
|
59
57
|
}
|
|
60
58
|
const session = createBinarySession(config.value, logger);
|
|
61
|
-
const browserObservation = new CdpBrowserProvider();
|
|
62
|
-
const electronObservation = new CdpElectronProvider();
|
|
63
59
|
const opened = await openInitialTarget(session, config.value, serverLogger, dependencies.writeStderr);
|
|
64
60
|
if (!opened.ok)
|
|
65
61
|
return opened.exitCode;
|
|
@@ -67,8 +63,20 @@ export const run = async (dependencies = runtimeDependencies()) => {
|
|
|
67
63
|
const transport = await startMcpTransport(dependencies, session, {
|
|
68
64
|
logger,
|
|
69
65
|
serverLogger,
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
loadOptionalProviders: async () => {
|
|
67
|
+
const [{ CdpBrowserProvider }, { PlaywrightBrowserScenarioProvider }, { CdpElectronProvider }, { V8InspectorProvider },] = await Promise.all([
|
|
68
|
+
import("./browser/CdpBrowserProvider.js"),
|
|
69
|
+
import("./browser/PlaywrightBrowserScenarioProvider.js"),
|
|
70
|
+
import("./browser/CdpElectronProvider.js"),
|
|
71
|
+
import("./browser/V8InspectorProvider.js"),
|
|
72
|
+
]);
|
|
73
|
+
return {
|
|
74
|
+
browserObservation: new CdpBrowserProvider(),
|
|
75
|
+
browserScenarioCapture: new PlaywrightBrowserScenarioProvider(),
|
|
76
|
+
electronObservation: new CdpElectronProvider(),
|
|
77
|
+
javascriptRuntimeObservation: new V8InspectorProvider(),
|
|
78
|
+
};
|
|
79
|
+
},
|
|
72
80
|
permissionAuthority: permissionAuthority.value,
|
|
73
81
|
runtimeState,
|
|
74
82
|
});
|
package/dist/mcpDoctor.js
CHANGED
|
@@ -5,12 +5,12 @@ import { resolve } from "node:path";
|
|
|
5
5
|
import { CATALOG_IDENTITY, MCP_RESOURCE_CATALOG, MCP_RESOURCE_TEMPLATE_CATALOG, } from "./catalogIdentity.js";
|
|
6
6
|
import { PROMPT_CONTRACTS } from "./contracts/promptContracts.js";
|
|
7
7
|
import { PRODUCT_IDENTITY } from "./identity.js";
|
|
8
|
-
|
|
8
|
+
import { MCP_STARTUP_POLICY } from "./mcpStartupPolicy.js";
|
|
9
9
|
const STDERR_LIMIT_BYTES = 64 * 1_024;
|
|
10
10
|
const OUTPUT_FORMATS = ["toon", "json", "yaml", "md", "jsonl"];
|
|
11
11
|
/** Diagnose the actual production stdio adapter with one bounded SDK session. */
|
|
12
12
|
export const runProductionMcpDoctor = async (options) => {
|
|
13
|
-
const deadline = Date.now() + (options.deadlineMs ??
|
|
13
|
+
const deadline = Date.now() + (options.deadlineMs ?? MCP_STARTUP_POLICY.doctorDeadlineMs);
|
|
14
14
|
const controller = new AbortController();
|
|
15
15
|
const timer = setTimeout(() => controller.abort(new Error("Production MCP doctor deadline expired")), Math.max(1, deadline - Date.now()));
|
|
16
16
|
const stderr = boundedStderr();
|
|
@@ -61,10 +61,11 @@ const inspectProductionMcpSession = async (client, deadline, signal) => {
|
|
|
61
61
|
client.listResources(undefined, request),
|
|
62
62
|
client.listResourceTemplates(undefined, request),
|
|
63
63
|
client.readResource({ uri: "rea://server/identity" }, request),
|
|
64
|
-
client.callTool({ name: "binary_session", arguments: {} }, request),
|
|
64
|
+
client.callTool({ name: "binary_session", arguments: { detail: "full" } }, request),
|
|
65
65
|
]);
|
|
66
|
+
const expectedToolNames = availableToolNames(requestFlow.structuredContent) ?? [];
|
|
66
67
|
const inventory = {
|
|
67
|
-
tools: compareInventory(
|
|
68
|
+
tools: compareInventory(expectedToolNames, tools.tools.map(({ name }) => name)),
|
|
68
69
|
prompts: compareInventory(PROMPT_CONTRACTS.map(({ name }) => name), prompts.prompts.map(({ name }) => name)),
|
|
69
70
|
resources: compareInventory(MCP_RESOURCE_CATALOG.map(({ name }) => name), resources.resources.map(({ name }) => name)),
|
|
70
71
|
resource_templates: compareInventory(MCP_RESOURCE_TEMPLATE_CATALOG.map(({ name }) => name), templates.resourceTemplates.map(({ name }) => name)),
|
|
@@ -116,6 +117,28 @@ const inspectProductionMcpSession = async (client, deadline, signal) => {
|
|
|
116
117
|
checks,
|
|
117
118
|
};
|
|
118
119
|
};
|
|
120
|
+
const availableToolNames = (structuredContent) => {
|
|
121
|
+
if (typeof structuredContent !== "object" || structuredContent === null)
|
|
122
|
+
return undefined;
|
|
123
|
+
const result = Reflect.get(structuredContent, "result");
|
|
124
|
+
if (typeof result !== "object" || result === null)
|
|
125
|
+
return undefined;
|
|
126
|
+
const availability = Reflect.get(result, "tool_availability");
|
|
127
|
+
if (!Array.isArray(availability))
|
|
128
|
+
return undefined;
|
|
129
|
+
const names = [];
|
|
130
|
+
for (const item of availability) {
|
|
131
|
+
if (typeof item !== "object" || item === null)
|
|
132
|
+
return undefined;
|
|
133
|
+
const name = Reflect.get(item, "name");
|
|
134
|
+
const available = Reflect.get(item, "available");
|
|
135
|
+
if (typeof name !== "string" || typeof available !== "boolean")
|
|
136
|
+
return undefined;
|
|
137
|
+
if (available)
|
|
138
|
+
names.push(name);
|
|
139
|
+
}
|
|
140
|
+
return names;
|
|
141
|
+
};
|
|
119
142
|
/**
|
|
120
143
|
* Dispatcher entry for `rea mcp doctor`, kept separate from Incur MCP setup.
|
|
121
144
|
* @public
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported MCP startup deadlines and regression budgets.
|
|
3
|
+
*
|
|
4
|
+
* The Codex timeout is caller-facing configuration. The smaller phase budgets
|
|
5
|
+
* preserve operational headroom, while the doctor deadline covers additional
|
|
6
|
+
* inventory requests after initialization.
|
|
7
|
+
*/
|
|
8
|
+
export const MCP_STARTUP_POLICY = {
|
|
9
|
+
codexStartupTimeoutSeconds: 30,
|
|
10
|
+
initializeBudgetMs: 15_000,
|
|
11
|
+
toolsListBudgetMs: 5_000,
|
|
12
|
+
firstCatalogBudgetMs: 20_000,
|
|
13
|
+
doctorDeadlineMs: 30_000,
|
|
14
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
/** Match process-table command text while handling macOS executable-name normalization. */
|
|
3
|
+
export const matchesOwnedProcessCommand = (actual, expected, platform = process.platform) => {
|
|
4
|
+
const actualCommand = actual.trim();
|
|
5
|
+
const expectedCommand = expected.trim();
|
|
6
|
+
if (actualCommand === expectedCommand ||
|
|
7
|
+
actualCommand.startsWith(`${expectedCommand} `))
|
|
8
|
+
return true;
|
|
9
|
+
if (platform !== "darwin")
|
|
10
|
+
return false;
|
|
11
|
+
const actualExecutable = actualCommand.split(/\s+/u, 1)[0];
|
|
12
|
+
const expectedExecutable = expectedCommand.split(/\s+/u, 1)[0];
|
|
13
|
+
const normalizedActualExecutable = actualExecutable?.startsWith("(") === true && actualExecutable.endsWith(")")
|
|
14
|
+
? actualExecutable.slice(1, -1)
|
|
15
|
+
: actualExecutable;
|
|
16
|
+
return (normalizedActualExecutable !== undefined &&
|
|
17
|
+
expectedExecutable !== undefined &&
|
|
18
|
+
basename(normalizedActualExecutable) === basename(expectedExecutable));
|
|
19
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
|
+
import { matchesOwnedProcessCommand } from "./ProcessCommandIdentity.js";
|
|
4
5
|
const execFileAsync = promisify(execFile);
|
|
5
6
|
/**
|
|
6
7
|
* Terminate one Windows process tree through the platform utility.
|
|
@@ -168,8 +169,8 @@ const launcherIdentityFailure = (launcher, ownership) => {
|
|
|
168
169
|
launcher.parentPid !== ownership.expectedParentPid)
|
|
169
170
|
return "owned launcher parent identity did not match";
|
|
170
171
|
if (ownership.expectedCommand !== undefined &&
|
|
171
|
-
!
|
|
172
|
-
return
|
|
172
|
+
!matchesOwnedProcessCommand(launcher.command, ownership.expectedCommand))
|
|
173
|
+
return `owned launcher command identity did not match (observed=${launcher.command}; expected=${ownership.expectedCommand})`;
|
|
173
174
|
return null;
|
|
174
175
|
};
|
|
175
176
|
const revalidateOwnedProcessGroup = async (processGroupId, ownership, host) => {
|
|
@@ -218,6 +219,12 @@ const processOwnershipFailures = async (members, runId, host) => {
|
|
|
218
219
|
failures.push({ pid: member.pid, reason: "run-token-mismatch" });
|
|
219
220
|
}
|
|
220
221
|
catch {
|
|
222
|
+
try {
|
|
223
|
+
const live = liveProcesses(await host.listProcesses());
|
|
224
|
+
if (!live.some(({ pid }) => pid === member.pid))
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
catch { }
|
|
221
228
|
failures.push({ pid: member.pid, reason: "environment-unreadable" });
|
|
222
229
|
}
|
|
223
230
|
}
|
|
@@ -280,8 +287,8 @@ export const observeOwnedProcessLineage = async (ownership, host = systemHost) =
|
|
|
280
287
|
launcher.parentPid !== ownership.expectedParentPid)
|
|
281
288
|
return unavailableLineage(ownership, "owned launcher parent identity did not match");
|
|
282
289
|
if (ownership.expectedCommand !== undefined &&
|
|
283
|
-
!
|
|
284
|
-
return unavailableLineage(ownership,
|
|
290
|
+
!matchesOwnedProcessCommand(launcher.command, ownership.expectedCommand))
|
|
291
|
+
return unavailableLineage(ownership, `owned launcher command identity did not match (observed=${launcher.command}; expected=${ownership.expectedCommand})`);
|
|
285
292
|
const descendants = descendantsOf(launcher.pid, processes);
|
|
286
293
|
for (const member of [launcher, ...descendants]) {
|
|
287
294
|
try {
|
|
@@ -340,7 +347,3 @@ const unavailableLineage = (ownership, reason) => ({
|
|
|
340
347
|
reason,
|
|
341
348
|
});
|
|
342
349
|
const liveProcesses = (members) => members.filter(({ state }) => !state.startsWith("Z"));
|
|
343
|
-
const commandMatches = (actual, expected) => {
|
|
344
|
-
return (actual.trim() === expected.trim() ||
|
|
345
|
-
actual.trim().startsWith(`${expected.trim()} `));
|
|
346
|
-
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { buildCapabilityInventory, } from "../application/CapabilityInventory.js";
|
|
2
|
+
/**
|
|
3
|
+
* Track public SDK tool handles and keep their visible state synchronized with
|
|
4
|
+
* the same availability projection returned by `binary_session`.
|
|
5
|
+
*/
|
|
6
|
+
export const installDynamicToolAvailability = (server, session, policy) => {
|
|
7
|
+
const tools = new Map();
|
|
8
|
+
const originalRegisterTool = server.registerTool.bind(server);
|
|
9
|
+
const originalSendToolListChanged = server.sendToolListChanged.bind(server);
|
|
10
|
+
let synchronizing = false;
|
|
11
|
+
const controller = {
|
|
12
|
+
synchronize: () => {
|
|
13
|
+
if (synchronizing)
|
|
14
|
+
return false;
|
|
15
|
+
synchronizing = true;
|
|
16
|
+
try {
|
|
17
|
+
const availability = new Map(buildCapabilityInventory(session.status(), policy(), clientFeatures(server)).map((item) => [item.name, item.available]));
|
|
18
|
+
let changed = false;
|
|
19
|
+
for (const [name, tool] of tools) {
|
|
20
|
+
const enabled = availability.get(name) === true;
|
|
21
|
+
if (tool.enabled === enabled)
|
|
22
|
+
continue;
|
|
23
|
+
changed = true;
|
|
24
|
+
if (enabled)
|
|
25
|
+
tool.enable();
|
|
26
|
+
else
|
|
27
|
+
tool.disable();
|
|
28
|
+
}
|
|
29
|
+
return changed;
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
synchronizing = false;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
const wrappedRegisterTool = (...args) => {
|
|
37
|
+
const name = args[0];
|
|
38
|
+
if (typeof name !== "string")
|
|
39
|
+
throw new TypeError("MCP tool registration requires a string name");
|
|
40
|
+
const registered = Reflect.apply(originalRegisterTool, server, args);
|
|
41
|
+
if (!isRegisteredTool(registered))
|
|
42
|
+
throw new TypeError(`MCP SDK did not return a tool handle for ${name}`);
|
|
43
|
+
tools.set(name, registered);
|
|
44
|
+
return registered;
|
|
45
|
+
};
|
|
46
|
+
if (!Reflect.set(server, "registerTool", wrappedRegisterTool))
|
|
47
|
+
throw new TypeError("Unable to install dynamic MCP tool registration");
|
|
48
|
+
if (!Reflect.set(server, "sendToolListChanged", () => {
|
|
49
|
+
if (controller.synchronize())
|
|
50
|
+
originalSendToolListChanged();
|
|
51
|
+
}))
|
|
52
|
+
throw new TypeError("Unable to install dynamic MCP tool notifications");
|
|
53
|
+
return controller;
|
|
54
|
+
};
|
|
55
|
+
const clientFeatures = (server) => {
|
|
56
|
+
const capabilities = server.server.getClientCapabilities();
|
|
57
|
+
return {
|
|
58
|
+
elicitation_form: capabilities?.elicitation?.form !== undefined,
|
|
59
|
+
elicitation_url: capabilities?.elicitation?.url !== undefined,
|
|
60
|
+
roots: capabilities?.roots !== undefined,
|
|
61
|
+
sampling: capabilities?.sampling !== undefined,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
const isRegisteredTool = (value) => {
|
|
65
|
+
if (typeof value !== "object" || value === null)
|
|
66
|
+
return false;
|
|
67
|
+
return (typeof Reflect.get(value, "enabled") === "boolean" &&
|
|
68
|
+
typeof Reflect.get(value, "enable") === "function" &&
|
|
69
|
+
typeof Reflect.get(value, "disable") === "function");
|
|
70
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { fromJsonSchema, isCallToolResult, isInputRequiredResult, } from "@modelcontextprotocol/server";
|
|
2
|
+
import { GENERATED_MCP_TOOL_CATALOG } from "../generatedMcpToolCatalog.js";
|
|
3
|
+
/**
|
|
4
|
+
* Advertise the generated MCP catalog immediately and hydrate its callbacks
|
|
5
|
+
* through the existing registrars on first execution.
|
|
6
|
+
*/
|
|
7
|
+
export class LazyToolCatalog {
|
|
8
|
+
#server;
|
|
9
|
+
#hydrateRegistrations;
|
|
10
|
+
#includeSessionTools;
|
|
11
|
+
#tools = new Map();
|
|
12
|
+
#hydrations = new Map();
|
|
13
|
+
#hydrationQueue = Promise.resolve();
|
|
14
|
+
#closed = false;
|
|
15
|
+
constructor(server, hydrateRegistrations, includeSessionTools = true) {
|
|
16
|
+
this.#server = server;
|
|
17
|
+
this.#hydrateRegistrations = hydrateRegistrations;
|
|
18
|
+
this.#includeSessionTools = includeSessionTools;
|
|
19
|
+
}
|
|
20
|
+
/** Register every generated tool with a lazy callback. */
|
|
21
|
+
register() {
|
|
22
|
+
for (const contract of GENERATED_MCP_TOOL_CATALOG) {
|
|
23
|
+
if (contract.requiresSession && !this.#includeSessionTools)
|
|
24
|
+
continue;
|
|
25
|
+
const inputSchema = advertisedSchema(contract.inputSchema);
|
|
26
|
+
const outputSchema = advertisedSchema(contract.outputSchema);
|
|
27
|
+
let registered;
|
|
28
|
+
const callback = async (input, context) => {
|
|
29
|
+
await this.#hydrate(contract.kind);
|
|
30
|
+
const handler = registered?.handler;
|
|
31
|
+
if (handler === undefined || handler === callback)
|
|
32
|
+
throw new Error(`MCP tool ${contract.name} was not hydrated`);
|
|
33
|
+
const result = await Reflect.apply(handler, undefined, [
|
|
34
|
+
input,
|
|
35
|
+
context,
|
|
36
|
+
]);
|
|
37
|
+
if (isCallToolResult(result) || isInputRequiredResult(result))
|
|
38
|
+
return result;
|
|
39
|
+
throw new TypeError(`MCP tool ${contract.name} returned an invalid result`);
|
|
40
|
+
};
|
|
41
|
+
registered = this.#server.registerTool(contract.name, {
|
|
42
|
+
title: contract.title,
|
|
43
|
+
description: contract.description,
|
|
44
|
+
inputSchema,
|
|
45
|
+
outputSchema,
|
|
46
|
+
annotations: contract.annotations,
|
|
47
|
+
}, callback);
|
|
48
|
+
this.#tools.set(contract.name, registered);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Wait for in-flight hydration and reject future first-use loading. */
|
|
52
|
+
async close() {
|
|
53
|
+
this.#closed = true;
|
|
54
|
+
await Promise.allSettled(this.#hydrations.values());
|
|
55
|
+
}
|
|
56
|
+
async #hydrate(kind) {
|
|
57
|
+
if (this.#closed)
|
|
58
|
+
throw new Error("MCP tool catalog is closed");
|
|
59
|
+
const existing = this.#hydrations.get(kind);
|
|
60
|
+
if (existing !== undefined) {
|
|
61
|
+
await existing;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const hydration = this.#hydrationQueue
|
|
65
|
+
.catch(() => undefined)
|
|
66
|
+
.then(() => this.#runHydration(kind));
|
|
67
|
+
this.#hydrationQueue = hydration;
|
|
68
|
+
this.#hydrations.set(kind, hydration);
|
|
69
|
+
try {
|
|
70
|
+
await hydration;
|
|
71
|
+
}
|
|
72
|
+
catch (cause) {
|
|
73
|
+
if (this.#hydrations.get(kind) === hydration)
|
|
74
|
+
this.#hydrations.delete(kind);
|
|
75
|
+
throw cause;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async #runHydration(kind) {
|
|
79
|
+
if (this.#closed)
|
|
80
|
+
throw new Error("MCP tool catalog is closed");
|
|
81
|
+
const expected = GENERATED_MCP_TOOL_CATALOG.filter((contract) => contract.kind === kind &&
|
|
82
|
+
(this.#includeSessionTools || !contract.requiresSession)).map((contract) => contract.name);
|
|
83
|
+
const expectedNames = new Set(expected);
|
|
84
|
+
const hydrated = new Set();
|
|
85
|
+
const originalRegisterTool = this.#server.registerTool;
|
|
86
|
+
const hydrationRegisterTool = (...args) => {
|
|
87
|
+
const [name, config, callback] = args;
|
|
88
|
+
if (typeof name !== "string" ||
|
|
89
|
+
!isRecord(config) ||
|
|
90
|
+
typeof callback !== "function")
|
|
91
|
+
throw new TypeError("Invalid lazy MCP tool registration");
|
|
92
|
+
const registered = this.#tools.get(name);
|
|
93
|
+
if (registered === undefined || !expectedNames.has(name))
|
|
94
|
+
throw new TypeError(`MCP tool ${name} is not in the ${kind} catalog family`);
|
|
95
|
+
if (hydrated.has(name))
|
|
96
|
+
throw new TypeError(`MCP tool ${name} was hydrated more than once`);
|
|
97
|
+
hydrated.add(name);
|
|
98
|
+
Reflect.apply(registered.update, registered, [
|
|
99
|
+
{
|
|
100
|
+
title: config.title,
|
|
101
|
+
description: config.description,
|
|
102
|
+
paramsSchema: config.inputSchema,
|
|
103
|
+
outputSchema: config.outputSchema,
|
|
104
|
+
annotations: config.annotations,
|
|
105
|
+
callback,
|
|
106
|
+
},
|
|
107
|
+
]);
|
|
108
|
+
return registered;
|
|
109
|
+
};
|
|
110
|
+
if (!Reflect.set(this.#server, "registerTool", hydrationRegisterTool))
|
|
111
|
+
throw new TypeError("Unable to install lazy MCP tool hydration");
|
|
112
|
+
try {
|
|
113
|
+
await this.#hydrateRegistrations(kind);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
Reflect.set(this.#server, "registerTool", originalRegisterTool);
|
|
117
|
+
}
|
|
118
|
+
const missing = expected.filter((name) => !hydrated.has(name));
|
|
119
|
+
if (missing.length > 0)
|
|
120
|
+
throw new TypeError(`Generated MCP tools were not hydrated: ${missing.join(", ")}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const advertisedSchema = (jsonSchema) => fromJsonSchema(jsonSchema, DEFERRED_VALIDATION_PROVIDER);
|
|
124
|
+
class DeferredValidationProvider {
|
|
125
|
+
getValidator() {
|
|
126
|
+
return (input) => ({
|
|
127
|
+
valid: true,
|
|
128
|
+
data: input,
|
|
129
|
+
errorMessage: undefined,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const DEFERRED_VALIDATION_PROVIDER = new DeferredValidationProvider();
|
|
134
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|