mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
* feat(kuna): verify release 1.990 * feat(kuna): preserve stored pointer references * fix(kuna): group image-wide reference coverage gaps * fix(evidence): retain producer-required xref observations * fix(mcp): clarify xref address page scope * fix(kuna): preserve reference target aliases * feat: page recorded evidence arrays * fix(mcp): guide large evidence views to files * fix: preserve source context in recorded views * fix: keep recorded rows accessible with large context * fix: disclose alias scope in recorded references * test(kuna): identify pointer fixture version * fix(view): keep deep recorded-array pages inside Evidence depth Parent parameters are valid on the Evidence root and one level too deep under a page result. Omit those parameters, and refuse a row that still cannot be nested, instead of throwing from Evidence parsing. * test(view): narrow recorded-array depth pages before reading parameters * fix(view): validate recorded page rows once --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1140 lines
40 KiB
TypeScript
1140 lines
40 KiB
TypeScript
import type { AnalysisSettings } from "../application/AnalysisSettings.js";
|
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
import { DEFAULT_MCP_MAX_INPUT_ELEMENTS } from "../config/mcpInputBudget.js";
|
|
|
|
import {
|
|
specTypeSchemas,
|
|
isCallToolResult,
|
|
isJSONRPCErrorResponse,
|
|
isJSONRPCRequest,
|
|
isJSONRPCNotification,
|
|
isJSONRPCResultResponse,
|
|
type CallToolResult,
|
|
type Implementation,
|
|
type Icon,
|
|
type JSONRPCMessage,
|
|
type McpServerOptions,
|
|
type RegisteredTool,
|
|
type RegisteredPrompt,
|
|
type PromptCallback,
|
|
type RequestId,
|
|
type ScopeChallengeHandler,
|
|
type StandardSchemaWithJSON,
|
|
type ToolAnnotations,
|
|
type ToolCallback,
|
|
type Transport,
|
|
type TransportSendOptions,
|
|
} from "@modelcontextprotocol/server";
|
|
|
|
import { analysisErrorProjectionSchema } from "../contracts/errorSchemas.js";
|
|
import {
|
|
presentAdvertisedInputSchema,
|
|
type CompactInputSchemaPresentation,
|
|
} from "../contracts/compactInputSchema.js";
|
|
import { presentAdvertisedOutputSchema } from "../contracts/compactOutputSchema.js";
|
|
import {
|
|
compactPresentationForMcpOutputSchemaProfile,
|
|
type McpOutputSchemaProfile,
|
|
} from "../config/mcpOutputSchemaProfile.js";
|
|
import type { createCatalogIdentity } from "../catalogIdentity.js";
|
|
import { transformAdvertisedInputJsonSchema } from "../contracts/toolSchemaMetadata.js";
|
|
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
|
|
import {
|
|
AnalysisInputError,
|
|
AnalysisResourceConstraintError,
|
|
type AnalysisInputIssue,
|
|
} from "../domain/analysisErrorCore.js";
|
|
import {
|
|
projectAnalysisError,
|
|
projectAnalysisErrorWithoutPartialObservation,
|
|
} from "../domain/analysisErrorProjection.js";
|
|
import { projectInputIssueGroupsWithinBudget } from "../domain/inputIssueProjection.js";
|
|
import type { z } from "zod";
|
|
import { createEvidence } from "../domain/evidence.js";
|
|
import { jsonObjectSchema, type JsonValue } from "../domain/jsonValue.js";
|
|
import {
|
|
mcpResponseOverflowProjection,
|
|
type ToolResultDelivery,
|
|
} from "./ToolResultDelivery.js";
|
|
import { toolReturnsTargetContent } from "../contracts/toolEffects.js";
|
|
import {
|
|
carriesTargetContentMarker,
|
|
encodeToolResult,
|
|
measureMcpResultWithinBudget,
|
|
TARGET_CONTENT_RESULT_META,
|
|
} from "./toolResultEncoding.js";
|
|
import { RequestTrackingMcpServer } from "./RequestTrackingMcpServer.js";
|
|
import { DISCOVER_TOOLS } from "../catalogDiscovery.js";
|
|
import { ProgressiveToolCatalog } from "./ProgressiveToolCatalog.js";
|
|
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
|
import { canReferenceInlineEvidence } from "./evidenceInputRegistration.js";
|
|
import {
|
|
currentMcpToolRequest,
|
|
McpToolRequest,
|
|
runMcpToolRequest,
|
|
} from "./McpToolRequest.js";
|
|
|
|
const currentRequestSignal = new AsyncLocalStorage<AbortSignal>();
|
|
|
|
/** Capture structured issues from the canonical validation the SDK already performs. */
|
|
const captureInputValidation = <Schema extends StandardSchemaWithJSON>(
|
|
tool: string,
|
|
schema: Schema,
|
|
): Schema => {
|
|
const standard = schema["~standard"];
|
|
if (standard.vendor !== "zod") return schema;
|
|
return {
|
|
...schema,
|
|
"~standard": {
|
|
...standard,
|
|
validate: (value: unknown) => {
|
|
const context = currentMcpToolRequest();
|
|
const capture = (
|
|
result: Awaited<ReturnType<typeof standard.validate>>,
|
|
) => {
|
|
if (context?.tool === tool && result.issues !== undefined)
|
|
context.validation = {
|
|
issues: result.issues as readonly z.core.$ZodIssue[],
|
|
input: value,
|
|
};
|
|
return result;
|
|
};
|
|
const result = standard.validate(value);
|
|
return result instanceof Promise
|
|
? result.then(capture)
|
|
: capture(result);
|
|
},
|
|
},
|
|
} as Schema;
|
|
};
|
|
|
|
/** Signal for the completion request currently being dispatched by the SDK. */
|
|
export const currentMcpRequestSignal = (): AbortSignal | undefined =>
|
|
currentRequestSignal.getStore();
|
|
|
|
/** A Zod schema whose standard interface exposes a JSON Schema projection. */
|
|
const hasAdvertisedInputJsonSchema = (value: unknown): boolean => {
|
|
if (typeof value !== "object" || value === null) return false;
|
|
const standard = Reflect.get(value, "~standard");
|
|
if (typeof standard !== "object" || standard === null) return false;
|
|
const jsonSchema = Reflect.get(standard, "jsonSchema");
|
|
return (
|
|
typeof jsonSchema === "object" &&
|
|
jsonSchema !== null &&
|
|
Reflect.get(jsonSchema, "input") !== undefined
|
|
);
|
|
};
|
|
|
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
|
|
/** Recognize the SDK's argument diagnostic after its wire size is checked. */
|
|
const sdkInputValidationDiagnostic = (
|
|
text: string | undefined,
|
|
tool: string | undefined,
|
|
): string | undefined => {
|
|
if (text === undefined || tool === undefined) return undefined;
|
|
const prefix = `Input validation error: Invalid arguments for tool ${tool}`;
|
|
if (!text.startsWith(prefix) || text[prefix.length] !== ":") return undefined;
|
|
const diagnostic = text.slice(prefix.length + 1).trimStart();
|
|
return diagnostic.length === 0 ? undefined : diagnostic;
|
|
};
|
|
|
|
/** Place an SDK diagnostic at a field only when that same issue produced it. */
|
|
const partitionSdkDiagnostic = (
|
|
validation: McpToolRequest["validation"],
|
|
groups: readonly (readonly AnalysisInputIssue[])[] | undefined,
|
|
diagnostic: string,
|
|
):
|
|
| { issues: readonly AnalysisInputIssue[]; remaining: string | undefined }
|
|
| undefined => {
|
|
if (
|
|
validation === undefined ||
|
|
groups === undefined ||
|
|
validation.issues.length !== groups.length
|
|
)
|
|
return undefined;
|
|
let offset = 0;
|
|
const consume = (text: string): boolean => {
|
|
if (!diagnostic.startsWith(text, offset)) return false;
|
|
offset += text.length;
|
|
return true;
|
|
};
|
|
const mapped: AnalysisInputIssue[] = [];
|
|
const remaining: string[] = [];
|
|
for (let index = 0; index < validation.issues.length; index += 1) {
|
|
const raw = validation.issues[index];
|
|
const group = groups[index];
|
|
if (raw === undefined || group === undefined) return undefined;
|
|
if (index > 0 && !consume(", ")) return undefined;
|
|
const start = offset;
|
|
for (let part = 0; part < raw.path.length; part += 1) {
|
|
if (part > 0 && !consume(".")) return undefined;
|
|
if (!consume(String(raw.path[part]))) return undefined;
|
|
}
|
|
if (raw.path.length > 0 && !consume(": ")) return undefined;
|
|
if (!consume(raw.message)) return undefined;
|
|
const issue = group.length === 1 ? group[0] : undefined;
|
|
const sameField =
|
|
issue !== undefined &&
|
|
raw.path.length === issue.path.length &&
|
|
raw.path.every((part, i) => part === issue.path[i]);
|
|
const sameMessage =
|
|
issue?.message !== undefined && issue.message === raw.message;
|
|
const direct =
|
|
raw.code !== "invalid_union" &&
|
|
raw.code !== "invalid_key" &&
|
|
raw.code !== "unrecognized_keys";
|
|
if (
|
|
sameField &&
|
|
issue !== undefined &&
|
|
(sameMessage || (direct && issue.message === undefined))
|
|
) {
|
|
mapped.push(
|
|
issue.message === undefined
|
|
? { ...issue, message: raw.message }
|
|
: issue,
|
|
);
|
|
} else {
|
|
for (const issue of group) mapped.push(issue);
|
|
const explainedByIssues =
|
|
raw.code === "unrecognized_keys" &&
|
|
group.length > 0 &&
|
|
group.every((item) => item.message !== undefined);
|
|
if (!explainedByIssues) remaining.push(diagnostic.slice(start, offset));
|
|
}
|
|
}
|
|
if (offset !== diagnostic.length) return undefined;
|
|
return {
|
|
issues: mapped,
|
|
remaining: remaining.length === 0 ? undefined : remaining.join(", "),
|
|
};
|
|
};
|
|
|
|
/** Replace the tool payload after the SDK codec while retaining its envelope. */
|
|
const replaceToolResultPayload = (
|
|
original: CallToolResult,
|
|
replacement: CallToolResult,
|
|
): CallToolResult => {
|
|
const {
|
|
content: _content,
|
|
structuredContent: _structuredContent,
|
|
isError: _isError,
|
|
_meta,
|
|
...envelope
|
|
} = original;
|
|
return {
|
|
...envelope,
|
|
...replacement,
|
|
...(_meta === undefined && replacement._meta === undefined
|
|
? {}
|
|
: { _meta: { ..._meta, ...replacement._meta } }),
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Rewrite only client-facing output schemas; SDK registrations stay canonical.
|
|
* `compact` bounds each advertised schema, while `off` omits it entirely.
|
|
* The SDK memoises each tool's converted schema, so the caller passes a cache
|
|
* keyed by that object; repeated listings reuse the same projection.
|
|
*/
|
|
const presentOutputSchemasForToolsList = (
|
|
message: JSONRPCMessage,
|
|
requestId: RequestId,
|
|
profile: McpOutputSchemaProfile,
|
|
compactedSchemas: WeakMap<Record<string, unknown>, Record<string, unknown>>,
|
|
): JSONRPCMessage => {
|
|
if (
|
|
profile === "full" ||
|
|
!isJSONRPCResultResponse(message) ||
|
|
message.id !== requestId ||
|
|
!isRecord(message.result) ||
|
|
!Array.isArray(message.result.tools)
|
|
)
|
|
return message;
|
|
const compactPresentation =
|
|
compactPresentationForMcpOutputSchemaProfile(profile);
|
|
return {
|
|
...message,
|
|
result: {
|
|
...message.result,
|
|
tools: message.result.tools.map((tool: unknown) => {
|
|
if (!isRecord(tool)) return tool;
|
|
if (compactPresentation === undefined) {
|
|
const { outputSchema: _outputSchema, ...advertisedTool } = tool;
|
|
return advertisedTool;
|
|
}
|
|
if (!isRecord(tool.outputSchema)) return tool;
|
|
let outputSchema = compactedSchemas.get(tool.outputSchema);
|
|
if (outputSchema === undefined) {
|
|
outputSchema = presentAdvertisedOutputSchema(
|
|
tool.outputSchema,
|
|
compactPresentation,
|
|
);
|
|
compactedSchemas.set(tool.outputSchema, outputSchema);
|
|
}
|
|
return { ...tool, outputSchema };
|
|
}),
|
|
},
|
|
} as JSONRPCMessage;
|
|
};
|
|
|
|
/** Release every transport-owned reference for a cancelled/completed request. */
|
|
export const forgetPendingMcpRequest = (
|
|
pendingTools: Map<RequestId, string>,
|
|
pendingToolLists: Set<RequestId>,
|
|
pendingCompletions: Map<RequestId, AbortController>,
|
|
id: RequestId,
|
|
): void => {
|
|
pendingTools.delete(id);
|
|
pendingToolLists.delete(id);
|
|
pendingCompletions.get(id)?.abort();
|
|
pendingCompletions.delete(id);
|
|
};
|
|
import type { PromptCompletionSource } from "./promptCompletion.js";
|
|
|
|
/** Standard-schema registration boundary shared with deferred SDK bindings. */
|
|
export interface StandardToolRegistration {
|
|
readonly title?: string;
|
|
readonly description?: string;
|
|
readonly inputSchema?: StandardSchemaWithJSON;
|
|
readonly outputSchema?: StandardSchemaWithJSON;
|
|
readonly annotations?: ToolAnnotations;
|
|
readonly icons?: Icon[];
|
|
readonly scopeChallenge?: ScopeChallengeHandler;
|
|
readonly _meta?: Record<string, unknown>;
|
|
}
|
|
|
|
/** Bind oversized MCP error recovery to this server's Evidence ledger. */
|
|
export class EvidenceMcpServerCore extends RequestTrackingMcpServer {
|
|
private readonly registrations = new Map<string, RegisteredTool>();
|
|
private progressiveCatalog: ProgressiveToolCatalog | undefined;
|
|
private catalogChangeDepth = 0;
|
|
private catalogChanged = false;
|
|
analysisSettings: AnalysisSettings | undefined;
|
|
/** Transport-owned Evidence uses the same lifetime as application writes. */
|
|
withAdmittedAnalysis: WithAdmittedAnalysis | undefined;
|
|
private identity: ReturnType<typeof createCatalogIdentity> | undefined;
|
|
|
|
/** Composition supplies identity without importing the canonical schema graph. */
|
|
get catalogIdentity(): ReturnType<typeof createCatalogIdentity> {
|
|
if (this.identity === undefined)
|
|
throw new Error("MCP catalog identity has not been configured");
|
|
return this.progressiveCatalog?.identity ?? this.identity;
|
|
}
|
|
|
|
/** Install discovery after canonical registrations, before connecting. */
|
|
enableProgressiveCatalog(): void {
|
|
if (this.progressiveCatalog !== undefined) return;
|
|
const catalog = new ProgressiveToolCatalog(
|
|
this.registrations,
|
|
this.catalogIdentity,
|
|
(change) => {
|
|
this.catalogChangeDepth++;
|
|
try {
|
|
change();
|
|
} finally {
|
|
this.catalogChangeDepth--;
|
|
if (this.catalogChangeDepth === 0 && this.catalogChanged) {
|
|
this.catalogChanged = false;
|
|
super.sendToolListChanged();
|
|
}
|
|
}
|
|
},
|
|
() => this.delivery,
|
|
);
|
|
this.progressiveCatalog = catalog;
|
|
const { name, ...config } = DISCOVER_TOOLS;
|
|
// Transport discovery has no target or analysis lifecycle. Domain tools
|
|
// continue through registerTool and their original named handlers.
|
|
super.registerTool(name, config, (input, context) => {
|
|
context.mcpReq.signal.throwIfAborted();
|
|
return catalog.discover(input);
|
|
});
|
|
}
|
|
|
|
override sendToolListChanged(): void {
|
|
if (this.catalogChangeDepth > 0) this.catalogChanged = true;
|
|
else super.sendToolListChanged();
|
|
}
|
|
|
|
/** Attach the process-owned session only after lazy runtime construction. */
|
|
attachRuntime(
|
|
recordEvidence: EvidenceWriter["recordEvidence"] | undefined,
|
|
delivery: ToolResultDelivery,
|
|
identity: ReturnType<typeof createCatalogIdentity>,
|
|
): void {
|
|
if (
|
|
this.identity !== undefined &&
|
|
this.identity.digests.combined_sha256 !== identity.digests.combined_sha256
|
|
)
|
|
throw new Error("MCP catalog identity does not match runtime");
|
|
this.recordEvidence = recordEvidence;
|
|
this.delivery = delivery;
|
|
this.identity = identity;
|
|
}
|
|
|
|
protected promptCompletion: PromptCompletionSource | undefined;
|
|
bindPromptCompletion(
|
|
completion: PromptCompletionSource,
|
|
): PromptCompletionSource {
|
|
this.promptCompletion = completion;
|
|
return completion;
|
|
}
|
|
|
|
registerCanonicalPrompt<Args extends StandardSchemaWithJSON>(
|
|
name: string,
|
|
config: { title?: string; description?: string; argsSchema: Args },
|
|
callback: PromptCallback<Args>,
|
|
): RegisteredPrompt {
|
|
return super.registerPrompt(name, config, callback);
|
|
}
|
|
private readonly compactPresentation:
|
|
| CompactInputSchemaPresentation
|
|
| undefined;
|
|
private readonly maximumInputElements: number;
|
|
private readonly evidenceReferenceAlternatives = new Set<string>();
|
|
|
|
protected registerEvidenceReferenceAlternative(name: string): void {
|
|
this.evidenceReferenceAlternatives.add(name);
|
|
}
|
|
|
|
constructor(
|
|
info: Implementation,
|
|
options: McpServerOptions,
|
|
private recordEvidence: EvidenceWriter["recordEvidence"] | undefined,
|
|
public delivery: ToolResultDelivery,
|
|
compactPresentation: CompactInputSchemaPresentation | undefined = undefined,
|
|
private readonly outputSchemaProfile: McpOutputSchemaProfile = "full",
|
|
identity?: ReturnType<typeof createCatalogIdentity>,
|
|
readonly advertisedToolFamilies:
|
|
| ReadonlySet<string>
|
|
| undefined = undefined,
|
|
) {
|
|
const maximumInputElements =
|
|
options.maxToolInputElements ?? DEFAULT_MCP_MAX_INPUT_ELEMENTS;
|
|
super(info, { ...options, maxToolInputElements: maximumInputElements });
|
|
this.maximumInputElements = maximumInputElements;
|
|
this.compactPresentation =
|
|
compactPresentation === undefined
|
|
? undefined
|
|
: { budgetBytes: compactPresentation.budgetBytes };
|
|
this.identity = identity;
|
|
}
|
|
|
|
/**
|
|
* Compact advertised input schemas and bound or omit output-schema
|
|
* advertisements. Canonical parsers and contracts remain registered; both
|
|
* output profiles validate successful structured results locally.
|
|
* The signatures mirror the SDK's registrations so call sites keep their
|
|
* inferred handler argument types.
|
|
*/
|
|
override registerTool<
|
|
OutputArgs extends StandardSchemaWithJSON,
|
|
InputArgs extends StandardSchemaWithJSON | undefined = undefined,
|
|
>(
|
|
name: string,
|
|
config: {
|
|
title?: string;
|
|
description?: string;
|
|
inputSchema?: InputArgs;
|
|
outputSchema?: OutputArgs;
|
|
annotations?: ToolAnnotations;
|
|
icons?: Icon[];
|
|
scopeChallenge?: ScopeChallengeHandler;
|
|
_meta?: Record<string, unknown>;
|
|
},
|
|
callback: ToolCallback<InputArgs>,
|
|
): RegisteredTool {
|
|
if (canReferenceInlineEvidence(config.inputSchema))
|
|
this.registerEvidenceReferenceAlternative(name);
|
|
const budget = this.compactPresentation?.budgetBytes;
|
|
const inputSchema =
|
|
budget !== undefined &&
|
|
config !== undefined &&
|
|
hasAdvertisedInputJsonSchema(config.inputSchema)
|
|
? transformAdvertisedInputJsonSchema(
|
|
config.inputSchema as Parameters<
|
|
typeof transformAdvertisedInputJsonSchema
|
|
>[0],
|
|
(projected) =>
|
|
presentAdvertisedInputSchema(projected, { budgetBytes: budget }),
|
|
)
|
|
: undefined;
|
|
const presentedSchema = inputSchema ?? config.inputSchema;
|
|
const registeredConfig =
|
|
presentedSchema === undefined
|
|
? config
|
|
: {
|
|
...config,
|
|
inputSchema: captureInputValidation(
|
|
name,
|
|
presentedSchema as StandardSchemaWithJSON,
|
|
),
|
|
};
|
|
const scoped = async (input: never, context: never) => {
|
|
const request = currentMcpToolRequest();
|
|
request?.beginHandler();
|
|
try {
|
|
return await (this.analysisSettings === undefined
|
|
? callback(input, context)
|
|
: this.analysisSettings.run(() => callback(input, context)));
|
|
} finally {
|
|
request?.finishHandler();
|
|
}
|
|
};
|
|
const registered = this.bindTool(
|
|
name,
|
|
registeredConfig as never,
|
|
scoped as never,
|
|
);
|
|
if (!this.advertisesTool(name)) registered.disable();
|
|
return registered;
|
|
}
|
|
|
|
/** Whether this process advertises one named operation. */
|
|
advertisesTool(_name: string): boolean {
|
|
return true;
|
|
}
|
|
|
|
/** Provider probes consult the registration scope before doing work. */
|
|
advertisesFamily(family: string): boolean {
|
|
return (
|
|
this.advertisedToolFamilies === undefined ||
|
|
this.advertisedToolFamilies.has(family)
|
|
);
|
|
}
|
|
|
|
protected bindTool(
|
|
name: string,
|
|
config: StandardToolRegistration,
|
|
callback: ToolCallback<StandardSchemaWithJSON | undefined>,
|
|
): RegisteredTool {
|
|
const registered = super.registerTool(
|
|
name,
|
|
config as never,
|
|
callback as never,
|
|
);
|
|
this.registrations.set(name, registered);
|
|
return registered;
|
|
}
|
|
|
|
/** Preserve the SDK transport lifecycle while retaining oversized errors. */
|
|
override connect(transport: Transport): Promise<void> {
|
|
// HTTP reuses the analysis server after draining the previous transport.
|
|
// Reset visibility while disconnected, before the next initialize/list.
|
|
if (!this.isConnected()) this.progressiveCatalog?.reset();
|
|
return super.connect(
|
|
new ErrorEvidenceTransport(
|
|
transport,
|
|
() => this.recordEvidence,
|
|
() => this.delivery,
|
|
() => this.withAdmittedAnalysis,
|
|
this.outputSchemaProfile,
|
|
this.maximumInputElements,
|
|
this.evidenceReferenceAlternatives,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ErrorEvidenceTransport implements Transport {
|
|
onclose: Transport["onclose"];
|
|
onerror: Transport["onerror"];
|
|
onmessage: Transport["onmessage"];
|
|
readonly hasPerRequestStream?: boolean;
|
|
private readonly pendingTools = new Map<RequestId, string>();
|
|
private readonly pendingValidation = new Map<RequestId, McpToolRequest>();
|
|
private readonly pendingToolLists = new Set<RequestId>();
|
|
private readonly pendingCompletions = new Map<RequestId, AbortController>();
|
|
|
|
private get recordEvidence(): EvidenceWriter["recordEvidence"] | undefined {
|
|
return this.currentEvidenceWriter();
|
|
}
|
|
|
|
private get delivery(): ToolResultDelivery {
|
|
return this.currentDelivery();
|
|
}
|
|
|
|
/** Bounded projections already computed for this server's budget. */
|
|
private readonly compactedOutputSchemas = new WeakMap<
|
|
Record<string, unknown>,
|
|
Record<string, unknown>
|
|
>();
|
|
|
|
constructor(
|
|
private readonly transport: Transport,
|
|
private readonly currentEvidenceWriter: () =>
|
|
| EvidenceWriter["recordEvidence"]
|
|
| undefined,
|
|
private readonly currentDelivery: () => ToolResultDelivery,
|
|
private readonly currentAdmission: () => WithAdmittedAnalysis | undefined,
|
|
private readonly outputSchemaProfile: McpOutputSchemaProfile,
|
|
private readonly maximumInputElements: number,
|
|
private readonly evidenceReferenceAlternatives: ReadonlySet<string>,
|
|
) {
|
|
if (transport.hasPerRequestStream !== undefined)
|
|
this.hasPerRequestStream = transport.hasPerRequestStream;
|
|
transport.onmessage = (message, extra) => {
|
|
// The pinned SDK classifies generic method schema failures as internal
|
|
// errors. Validate these wire requests with its canonical schemas before
|
|
// dispatch, leaving callback failures and negotiated-era checks to it.
|
|
if (isJSONRPCRequest(message)) {
|
|
const schema = discoveryRequestSchema(message.method);
|
|
const result = schema?.["~standard"].validate(message);
|
|
if (result?.issues !== undefined) {
|
|
void this.send({
|
|
jsonrpc: "2.0",
|
|
id: message.id,
|
|
error: {
|
|
code: -32602,
|
|
message: `Invalid ${message.method} request: ${JSON.stringify(result.issues)}`,
|
|
},
|
|
}).catch((cause: unknown) =>
|
|
this.onerror?.(
|
|
cause instanceof Error ? cause : new Error(String(cause)),
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
let validation: McpToolRequest | undefined;
|
|
if (
|
|
isJSONRPCRequest(message) &&
|
|
message.method === "tools/call" &&
|
|
typeof message.params?.["name"] === "string"
|
|
) {
|
|
this.pendingTools.set(message.id, message.params["name"]);
|
|
validation = new McpToolRequest(message.id, message.params["name"]);
|
|
this.pendingValidation.set(message.id, validation);
|
|
}
|
|
if (isJSONRPCRequest(message) && message.method === "tools/list")
|
|
this.pendingToolLists.add(message.id);
|
|
if (isJSONRPCRequest(message) && message.method === "completion/complete")
|
|
this.pendingCompletions.set(message.id, new AbortController());
|
|
// The SDK suppresses replies after cancellation, so send() cannot release
|
|
// this bookkeeping. Keep transport authentication outside retained data.
|
|
if (
|
|
isJSONRPCNotification(message) &&
|
|
message.method === "notifications/cancelled"
|
|
) {
|
|
const id = message.params?.["requestId"];
|
|
if (typeof id === "string" || typeof id === "number") {
|
|
this.pendingValidation.get(id)?.cancel(message.params?.["reason"]);
|
|
this.pendingValidation.delete(id);
|
|
forgetPendingMcpRequest(
|
|
this.pendingTools,
|
|
this.pendingToolLists,
|
|
this.pendingCompletions,
|
|
id,
|
|
);
|
|
}
|
|
}
|
|
const completion =
|
|
isJSONRPCRequest(message) && message.method === "completion/complete"
|
|
? this.pendingCompletions.get(message.id)
|
|
: undefined;
|
|
if (validation !== undefined)
|
|
runMcpToolRequest(validation, () => this.onmessage?.(message, extra));
|
|
else if (completion === undefined) this.onmessage?.(message, extra);
|
|
else
|
|
currentRequestSignal.run(completion.signal, () =>
|
|
this.onmessage?.(message, extra),
|
|
);
|
|
};
|
|
transport.onerror = (error) => this.onerror?.(error);
|
|
transport.onclose = () => {
|
|
this.pendingTools.clear();
|
|
for (const validation of this.pendingValidation.values())
|
|
validation.cancel();
|
|
this.pendingValidation.clear();
|
|
this.pendingToolLists.clear();
|
|
for (const completion of this.pendingCompletions.values())
|
|
completion.abort();
|
|
this.pendingCompletions.clear();
|
|
this.onclose?.();
|
|
};
|
|
}
|
|
|
|
get sessionId(): string | undefined {
|
|
return this.transport.sessionId;
|
|
}
|
|
set sessionId(value: string | undefined) {
|
|
this.transport.sessionId = value;
|
|
}
|
|
|
|
start(): Promise<void> {
|
|
return this.transport.start();
|
|
}
|
|
|
|
async close(): Promise<void> {
|
|
try {
|
|
await this.transport.close();
|
|
} finally {
|
|
this.pendingTools.clear();
|
|
for (const validation of this.pendingValidation.values())
|
|
validation.cancel();
|
|
this.pendingValidation.clear();
|
|
this.pendingToolLists.clear();
|
|
for (const completion of this.pendingCompletions.values())
|
|
completion.abort();
|
|
this.pendingCompletions.clear();
|
|
}
|
|
}
|
|
|
|
setProtocolVersion(version: string): void {
|
|
this.transport.setProtocolVersion?.(version);
|
|
}
|
|
|
|
setSupportedProtocolVersions(versions: string[]): void {
|
|
this.transport.setSupportedProtocolVersions?.(versions);
|
|
}
|
|
|
|
async send(
|
|
message: JSONRPCMessage,
|
|
options?: TransportSendOptions,
|
|
): Promise<void> {
|
|
const id =
|
|
"id" in message &&
|
|
(typeof message.id === "string" || typeof message.id === "number")
|
|
? message.id
|
|
: undefined;
|
|
const tool = id === undefined ? undefined : this.pendingTools.get(id);
|
|
const scopedRequest = currentMcpToolRequest();
|
|
const request =
|
|
id === undefined ||
|
|
(!isJSONRPCResultResponse(message) && !isJSONRPCErrorResponse(message))
|
|
? undefined
|
|
: (this.pendingValidation.get(id) ??
|
|
(scopedRequest?.id === id ? scopedRequest : undefined));
|
|
const rewritesOutputSchemas =
|
|
this.outputSchemaProfile !== "full" &&
|
|
id !== undefined &&
|
|
this.pendingToolLists.has(id);
|
|
try {
|
|
const publish = async (): Promise<void> => {
|
|
const recovered = this.recoverProtocolError(message, tool);
|
|
const advertised = rewritesOutputSchemas
|
|
? presentOutputSchemasForToolsList(
|
|
recovered,
|
|
id,
|
|
this.outputSchemaProfile,
|
|
this.compactedOutputSchemas,
|
|
)
|
|
: recovered;
|
|
await this.publish(advertised, tool, options, request);
|
|
};
|
|
const admission = this.currentAdmission();
|
|
if (
|
|
admission !== undefined &&
|
|
request?.holdsAdmission !== true &&
|
|
isJSONRPCResultResponse(message) &&
|
|
isCallToolResult(message.result) &&
|
|
message.result.isError === true
|
|
) {
|
|
// SDK failures bypass application handlers. Admit both retention and
|
|
// publication so reset cannot clear a newly offered reference between
|
|
// those two steps, and shutdown/cancellation cannot create a late write.
|
|
const admitted = await admission(
|
|
tool ?? "mcp_tool_error",
|
|
request?.cancellation.signal,
|
|
publish,
|
|
);
|
|
if (!admitted.ok)
|
|
await this.publish(
|
|
{
|
|
...message,
|
|
result: replaceToolResultPayload(
|
|
message.result,
|
|
this.delivery.toErrorToolResult(
|
|
admitted.error,
|
|
tool !== undefined && toolReturnsTargetContent(tool),
|
|
),
|
|
),
|
|
},
|
|
tool,
|
|
options,
|
|
request,
|
|
);
|
|
} else await publish();
|
|
} finally {
|
|
request?.finishPublication();
|
|
if (!isJSONRPCRequest(message) && id !== undefined) {
|
|
this.pendingValidation.delete(id);
|
|
forgetPendingMcpRequest(
|
|
this.pendingTools,
|
|
this.pendingToolLists,
|
|
this.pendingCompletions,
|
|
id,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Keep the SDK wire envelope while removing private diagnostic carriers. */
|
|
private async publish(
|
|
message: JSONRPCMessage,
|
|
tool: string | undefined,
|
|
options: TransportSendOptions | undefined,
|
|
request: McpToolRequest | undefined,
|
|
): Promise<void> {
|
|
request?.beginPublication();
|
|
if (
|
|
!isJSONRPCResultResponse(message) ||
|
|
!isCallToolResult(message.result) ||
|
|
message.result.isError !== true
|
|
) {
|
|
await this.transport.send(message, options);
|
|
return;
|
|
}
|
|
// Oversized recovery has already consumed this private structured carrier.
|
|
const { structuredContent: _diagnostic, ...result } = message.result;
|
|
// SDK-built errors bypass ToolResultDelivery's target-content marker.
|
|
const marked =
|
|
tool !== undefined &&
|
|
toolReturnsTargetContent(tool) &&
|
|
!carriesTargetContentMarker(result)
|
|
? {
|
|
...result,
|
|
_meta: { ...result._meta, ...TARGET_CONTENT_RESULT_META },
|
|
}
|
|
: result;
|
|
await this.transport.send({ ...message, result: marked }, options);
|
|
}
|
|
|
|
private recoverError(
|
|
message: JSONRPCMessage,
|
|
tool: string | undefined,
|
|
): JSONRPCMessage {
|
|
if (
|
|
!isJSONRPCResultResponse(message) ||
|
|
!isCallToolResult(message.result) ||
|
|
message.result.isError !== true
|
|
)
|
|
return message;
|
|
const budget = this.delivery.resultBudgetBytes;
|
|
// Count the provenance marker exactly as the projected error did, and keep
|
|
// it on the replacement: the retained original may hold target content.
|
|
const targetContent =
|
|
carriesTargetContentMarker(message.result) ||
|
|
(tool !== undefined && toolReturnsTargetContent(tool));
|
|
// SDK validation failures have no structured carrier. Size their existing
|
|
// text-only result without serializing the complete JSON-RPC message.
|
|
const isStructured = message.result.structuredContent !== undefined;
|
|
const structured = isStructured
|
|
? jsonObjectSchema.parse(message.result.structuredContent)
|
|
: undefined;
|
|
const sdkResultWithMarker =
|
|
!isStructured &&
|
|
targetContent &&
|
|
!carriesTargetContentMarker(message.result)
|
|
? {
|
|
...message.result,
|
|
_meta: {
|
|
...message.result._meta,
|
|
...TARGET_CONTENT_RESULT_META,
|
|
},
|
|
}
|
|
: message.result;
|
|
const sdkDiagnostic = !isStructured
|
|
? message.result.content.find((item) => item.type === "text")?.text
|
|
: undefined;
|
|
let encoded =
|
|
structured !== undefined
|
|
? encodeToolResult(structured, budget, "error-text", targetContent)
|
|
: measureMcpResultWithinBudget(sdkResultWithMarker, budget);
|
|
const normalized = encoded.ok
|
|
? this.normalizeSdkInputError(
|
|
message,
|
|
tool,
|
|
targetContent,
|
|
sdkDiagnostic,
|
|
this.pendingValidation.get(message.id)?.validation,
|
|
)
|
|
: undefined;
|
|
if (
|
|
normalized !== undefined &&
|
|
isJSONRPCResultResponse(normalized) &&
|
|
isCallToolResult(normalized.result)
|
|
) {
|
|
// The structured diagnostic is a private carrier removed before sending.
|
|
// Measure the actual wire result, including its preserved SDK metadata.
|
|
const { structuredContent, ...wireResult } = normalized.result;
|
|
encoded =
|
|
normalized.result.content.length === 0
|
|
? encodeToolResult(
|
|
jsonObjectSchema.parse(structuredContent),
|
|
budget,
|
|
"error-text",
|
|
targetContent,
|
|
)
|
|
: measureMcpResultWithinBudget(wireResult, budget);
|
|
if (encoded.ok) return normalized;
|
|
// JSON escaping and recovery fields can exceed the budget even when
|
|
// the original SDK result fits. Retain that original before replacing it.
|
|
}
|
|
if (encoded.ok) return message;
|
|
const operation = tool ?? "mcp_tool_error";
|
|
const originalResult = jsonObjectSchema.parse(message.result);
|
|
const retainedResult = structured ?? originalResult;
|
|
const originalError = isStructured
|
|
? analysisErrorProjectionSchema.safeParse(structured?.["error"])
|
|
: undefined;
|
|
// Retain the complete original response, including SDK validation text.
|
|
// The SDK error contains its rendered issue, not a separate argument bag;
|
|
// transport authentication and caller arguments are never added here.
|
|
const evidence = createEvidence(
|
|
undefined,
|
|
{ id: "rea-mcp", name: "REA MCP delivery", version: "1" },
|
|
{
|
|
operation: "mcp_tool_error",
|
|
parameters: { tool_name: operation },
|
|
result:
|
|
sdkDiagnostic === undefined
|
|
? retainedResult
|
|
: {
|
|
boundary: "sdk-input-validation",
|
|
diagnostic_preview: sdkDiagnostic.slice(0, 256),
|
|
diagnostic_truncated: sdkDiagnostic.length > 256,
|
|
},
|
|
rawResult: originalResult,
|
|
},
|
|
);
|
|
const recorded = this.recordEvidence?.(evidence);
|
|
const retained = recorded !== undefined && recorded.ok;
|
|
const projected = mcpResponseOverflowProjection({
|
|
completed: false,
|
|
constraint: encoded.constraint,
|
|
effectiveResponseBytes: this.delivery.maximumResponseBytes,
|
|
nextStep: retained
|
|
? "Export the retained original error through export_evidence_bundle to a caller-selected path. The connection remains usable."
|
|
: encoded.constraint === "string-length"
|
|
? "The original error could not be retained by this server. Use complete CLI JSON output when the analysis completed. Repeating the unchanged MCP call fails the same way."
|
|
: "The original error could not be retained by this server. Restart REA with a larger REA_MCP_MAX_RESPONSE_BYTES and a matching client receive buffer, then retry.",
|
|
});
|
|
const replacement = this.transportConstraintResult(
|
|
operation,
|
|
projected.reason,
|
|
projected.remediationAction,
|
|
{
|
|
boundary: "mcp-response",
|
|
...(originalError?.success
|
|
? { original_error_code: originalError.data.code }
|
|
: {}),
|
|
result_budget_bytes: budget,
|
|
response_bytes_at_least: encoded.bytesAtLeast,
|
|
constraint: encoded.constraint,
|
|
...(sdkDiagnostic === undefined
|
|
? {}
|
|
: {
|
|
diagnostic_preview: sdkDiagnostic.slice(0, 256),
|
|
diagnostic_truncated: sdkDiagnostic.length > 256,
|
|
}),
|
|
...(retained
|
|
? {
|
|
evidence_reference: {
|
|
kind: "retained-evidence",
|
|
evidence_id: evidence.evidence_id,
|
|
},
|
|
}
|
|
: { retention: recorded === undefined ? "unavailable" : "failed" }),
|
|
...(recorded !== undefined && !recorded.ok
|
|
? {
|
|
retention_error: projectAnalysisErrorWithoutPartialObservation(
|
|
recorded.error,
|
|
),
|
|
}
|
|
: {}),
|
|
},
|
|
targetContent,
|
|
);
|
|
return {
|
|
...message,
|
|
result: replaceToolResultPayload(message.result, replacement),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Encode a transport-constraint error inside the MCP text budget.
|
|
* The complete SDK diagnostic stays in retained Evidence. Its character
|
|
* preview can still expand past the budget when quotes and backslashes are
|
|
* escaped into that text, so shorten only the preview and keep the reference.
|
|
*/
|
|
private transportConstraintResult(
|
|
operation: string,
|
|
reason: string,
|
|
remediationAction: string,
|
|
limits: Readonly<Record<string, JsonValue>>,
|
|
targetContent: boolean,
|
|
): CallToolResult {
|
|
const deliver = (
|
|
reportedLimits: Readonly<Record<string, JsonValue>>,
|
|
): CallToolResult =>
|
|
this.delivery.toErrorToolResult(
|
|
new AnalysisResourceConstraintError(
|
|
operation,
|
|
"transport",
|
|
reason,
|
|
reportedLimits,
|
|
{ remediationAction },
|
|
),
|
|
targetContent,
|
|
);
|
|
const delivered = deliver(limits);
|
|
const preview = limits.diagnostic_preview;
|
|
if (delivered.content.length > 0 || typeof preview !== "string")
|
|
return delivered;
|
|
let low = 0;
|
|
let high = preview.length - 1;
|
|
let fitted: CallToolResult | undefined;
|
|
let fittedLength = -1;
|
|
while (low <= high) {
|
|
const length = Math.floor((low + high) / 2);
|
|
const candidate = deliver({
|
|
...limits,
|
|
diagnostic_preview: preview.slice(0, length),
|
|
diagnostic_truncated: true,
|
|
});
|
|
if (candidate.content.length > 0) {
|
|
fitted = candidate;
|
|
fittedLength = length;
|
|
low = length + 1;
|
|
} else high = length - 1;
|
|
}
|
|
if (fitted !== undefined && fittedLength > 0) return fitted;
|
|
const { diagnostic_preview: _preview, ...withoutPreview } = limits;
|
|
const omitted = deliver({
|
|
...withoutPreview,
|
|
diagnostic_truncated: preview.length > 0,
|
|
});
|
|
return omitted.content.length > 0 || fitted === undefined
|
|
? omitted
|
|
: fitted;
|
|
}
|
|
|
|
private normalizeSdkInputError(
|
|
message: JSONRPCMessage,
|
|
tool: string | undefined,
|
|
targetContent: boolean,
|
|
text: string | undefined,
|
|
validation: McpToolRequest["validation"],
|
|
): JSONRPCMessage | undefined {
|
|
if (
|
|
tool === undefined ||
|
|
text === undefined ||
|
|
!isJSONRPCResultResponse(message) ||
|
|
!isCallToolResult(message.result)
|
|
)
|
|
return undefined;
|
|
// The SDK owns counting and refusal before Standard Schema validation.
|
|
// Recognize its exact wire refusal using our configured fact, without
|
|
// reparsing the arguments or manufacturing a complete element count.
|
|
if (
|
|
text ===
|
|
`Invalid arguments for tool ${tool}: arguments contain more than the maximum of ${String(this.maximumInputElements)} elements`
|
|
) {
|
|
const error = new AnalysisResourceConstraintError(
|
|
tool,
|
|
"transport",
|
|
"The MCP tool arguments exceed the configured input element allowance; schema validation and tool execution did not start.",
|
|
{ max_input_elements: this.maximumInputElements },
|
|
{
|
|
remediationAction:
|
|
tool === "inspect_analysis_view"
|
|
? 'If using inline Evidence, save the Evidence record as a local JSON file and use source {"kind":"evidence-file","path":"..."}; if it is retained, use its retained-evidence ID. Otherwise reduce the combined array elements and object members, or restart REA with a larger REA_MCP_MAX_INPUT_ELEMENTS allowance.'
|
|
: this.evidenceReferenceAlternatives.has(tool)
|
|
? 'If the source Evidence is retained, use {"kind":"retained-evidence","evidence_id":"ev_..."} for declared Evidence fields; use a summary\'s parent_evidence_id for its complete source. Otherwise import the originating Evidence bundle when available. Reduce the arguments, or restart REA with a larger REA_MCP_MAX_INPUT_ELEMENTS allowance.'
|
|
: "Reduce the combined array elements and object members in the arguments, or restart REA with a larger REA_MCP_MAX_INPUT_ELEMENTS allowance for this workload.",
|
|
},
|
|
);
|
|
const result = this.delivery.toErrorToolResult(error, targetContent);
|
|
return {
|
|
...message,
|
|
result: replaceToolResultPayload(message.result, result),
|
|
};
|
|
}
|
|
const diagnostic = sdkInputValidationDiagnostic(text, tool);
|
|
if (diagnostic === undefined) return undefined;
|
|
const groups =
|
|
validation === undefined
|
|
? undefined
|
|
: projectInputIssueGroupsWithinBudget(
|
|
validation.issues,
|
|
validation.input,
|
|
this.delivery.resultBudgetBytes,
|
|
tool,
|
|
);
|
|
const projectionLimited = validation !== undefined && groups === undefined;
|
|
const summary = `The arguments for MCP tool ${tool} do not match its advertised input schema.`;
|
|
const partitioned = partitionSdkDiagnostic(validation, groups, diagnostic);
|
|
const issues = partitioned?.issues ?? groups?.flat();
|
|
const error = new AnalysisInputError(
|
|
tool,
|
|
{
|
|
userMessage:
|
|
issues === undefined
|
|
? projectionLimited
|
|
? `${summary} Field-level issue projection exceeded its response-derived allowance.`
|
|
: summary
|
|
: partitioned !== undefined
|
|
? partitioned.remaining === undefined
|
|
? summary
|
|
: `${summary} ${partitioned.remaining}`
|
|
: `${summary} ${diagnostic}`,
|
|
},
|
|
issues ?? [
|
|
{
|
|
path: [],
|
|
reason: "invalid_value",
|
|
message: diagnostic,
|
|
...(projectionLimited
|
|
? { observed: { field_details: "omitted" } }
|
|
: {}),
|
|
},
|
|
],
|
|
);
|
|
const result = this.delivery.toErrorToolResult(error, targetContent);
|
|
return {
|
|
...message,
|
|
result: replaceToolResultPayload(message.result, result),
|
|
};
|
|
}
|
|
|
|
private recoverProtocolError(
|
|
message: JSONRPCMessage,
|
|
tool: string | undefined,
|
|
): JSONRPCMessage {
|
|
const recovered = this.recoverError(message, tool);
|
|
if (
|
|
!isJSONRPCErrorResponse(recovered) ||
|
|
tool === undefined ||
|
|
recovered.error.code !== -32602 ||
|
|
recovered.error.message !== `Tool ${tool} not found`
|
|
)
|
|
return recovered;
|
|
return {
|
|
...recovered,
|
|
error: {
|
|
...recovered.error,
|
|
message:
|
|
"The requested MCP tool is not advertised by this REA server. Call tools/list, select an advertised tool, and retry; do not repeat the unknown tool name.",
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
const discoveryRequestSchema = (method: string) => {
|
|
switch (method) {
|
|
case "prompts/get":
|
|
return specTypeSchemas.GetPromptRequest;
|
|
case "prompts/list":
|
|
return specTypeSchemas.ListPromptsRequest;
|
|
case "completion/complete":
|
|
return specTypeSchemas.CompleteRequest;
|
|
case "tools/list":
|
|
return specTypeSchemas.ListToolsRequest;
|
|
default:
|
|
return undefined;
|
|
}
|
|
};
|