

























@@ -24,6 +24,8 @@ import { extractPayloadText } from "./test-helpers.agent-results.js";
2424const CLI_CRON_MCP_PROBE_MAX_ATTEMPTS = 10;
2525const CLI_CRON_MCP_PROBE_VERIFY_POLLS = 20;
2626const CLI_CRON_MCP_PROBE_VERIFY_POLL_MS = 2_000;
27+const CLI_CRON_MCP_LOOPBACK_REQUEST_TIMEOUT_MS = 30_000;
28+const CLI_CRON_MCP_LOOPBACK_MAX_BODY_BYTES = 1_048_576;
27292830function shouldLogCliCronProbe(): boolean {
2931return (
@@ -111,6 +113,17 @@ type LoopbackToolListEntry = {
111113inputSchema?: unknown;
112114};
113115116+function parsePositiveInt(value: string | undefined, fallback: number, name: string): number {
117+if (!value?.trim()) {
118+return fallback;
119+}
120+const parsed = Number.parseInt(value, 10);
121+if (!Number.isFinite(parsed) || parsed <= 0) {
122+throw new Error(`invalid ${name}: ${value}`);
123+}
124+return parsed;
125+}
126+114127function asLoopbackSchemaRecord(schema: unknown): Record<string, unknown> | null {
115128return schema && typeof schema === "object" && !Array.isArray(schema)
116129 ? (schema as Record<string, unknown>)
@@ -170,6 +183,7 @@ async function callLoopbackJsonRpc(params: {
170183messageProvider?: string;
171184accountId?: string;
172185body: Record<string, unknown>;
186+env?: NodeJS.ProcessEnv;
173187}): Promise<LoopbackJsonRpcResponse> {
174188const runtime = getActiveMcpLoopbackRuntime();
175189if (!runtime) {
@@ -186,12 +200,34 @@ async function callLoopbackJsonRpc(params: {
186200if (params.accountId) {
187201headers["x-openclaw-account-id"] = params.accountId;
188202}
189-const response = await fetch(`http://127.0.0.1:${runtime.port}/mcp`, {
190-method: "POST",
191- headers,
192-body: JSON.stringify(params.body),
193-});
194-const text = await response.text();
203+const timeoutMs = parsePositiveInt(
204+params.env?.OPENCLAW_MCP_LOOPBACK_PROBE_TIMEOUT_MS,
205+CLI_CRON_MCP_LOOPBACK_REQUEST_TIMEOUT_MS,
206+"OPENCLAW_MCP_LOOPBACK_PROBE_TIMEOUT_MS",
207+);
208+const maxBodyBytes = parsePositiveInt(
209+params.env?.OPENCLAW_MCP_LOOPBACK_PROBE_MAX_BODY_BYTES,
210+CLI_CRON_MCP_LOOPBACK_MAX_BODY_BYTES,
211+"OPENCLAW_MCP_LOOPBACK_PROBE_MAX_BODY_BYTES",
212+);
213+const controller = new AbortController();
214+const timer = setTimeout(() => controller.abort(), timeoutMs);
215+let response: Response | undefined;
216+let text = "";
217+try {
218+response = await fetch(`http://127.0.0.1:${runtime.port}/mcp`, {
219+method: "POST",
220+ headers,
221+body: JSON.stringify(params.body),
222+signal: controller.signal,
223+});
224+text = await readBoundedResponseText(response, maxBodyBytes);
225+} finally {
226+clearTimeout(timer);
227+}
228+if (!response) {
229+throw new Error("mcp loopback did not return a response");
230+}
195231if (!response.ok) {
196232throw new Error(`mcp loopback http ${response.status}: ${text}`);
197233}
@@ -205,6 +241,28 @@ async function callLoopbackJsonRpc(params: {
205241return parsed;
206242}
207243244+async function readBoundedResponseText(response: Response, byteLimit: number): Promise<string> {
245+const reader = response.body?.getReader();
246+if (!reader) {
247+return "";
248+}
249+const chunks: Buffer[] = [];
250+let totalBytes = 0;
251+for (;;) {
252+const { done, value } = await reader.read();
253+if (done) {
254+break;
255+}
256+totalBytes += value.byteLength;
257+if (totalBytes > byteLimit) {
258+await reader.cancel();
259+throw new Error(`mcp loopback response body exceeded ${byteLimit} bytes`);
260+}
261+chunks.push(Buffer.from(value));
262+}
263+return Buffer.concat(chunks, totalBytes).toString("utf8");
264+}
265+208266export async function verifyCliCronMcpLoopbackPreflight(params: {
209267sessionKey: string;
210268port: number;
@@ -224,6 +282,7 @@ export async function verifyCliCronMcpLoopbackPreflight(params: {
224282sessionKey: params.sessionKey,
225283messageProvider: params.messageProvider,
226284accountId: params.accountId,
285+env: params.env,
227286body: {
228287jsonrpc: "2.0",
229288id: "init",
@@ -235,12 +294,14 @@ export async function verifyCliCronMcpLoopbackPreflight(params: {
235294sessionKey: params.sessionKey,
236295messageProvider: params.messageProvider,
237296accountId: params.accountId,
297+env: params.env,
238298body: { jsonrpc: "2.0", method: "notifications/initialized" },
239299});
240300const toolsList = await callLoopbackJsonRpc({
241301sessionKey: params.sessionKey,
242302messageProvider: params.messageProvider,
243303accountId: params.accountId,
304+env: params.env,
244305body: { jsonrpc: "2.0", id: "tools-list", method: "tools/list" },
245306});
246307const tools = Array.isArray((toolsList.result as { tools?: unknown[] } | undefined)?.tools)
@@ -265,6 +326,7 @@ export async function verifyCliCronMcpLoopbackPreflight(params: {
265326sessionKey: params.sessionKey,
266327messageProvider: params.messageProvider,
267328accountId: params.accountId,
329+env: params.env,
268330body: {
269331jsonrpc: "2.0",
270332id: "cron-add",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。