Skip to content

Commit f9ddbbe

Browse files
committed
Add bundle MCP timeline spans
1 parent dbabfc5 commit f9ddbbe

2 files changed

Lines changed: 125 additions & 25 deletions

File tree

src/agents/pi-bundle-mcp-runtime.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
import { mkdtemp, readFile } from "node:fs/promises";
2+
import { tmpdir } from "node:os";
3+
import { join } from "node:path";
14
import { afterEach, describe, expect, it, vi } from "vitest";
5+
import { writeBundleProbeMcpServer } from "./bundle-mcp-shared.test-harness.js";
26
import { createBundleMcpJsonSchemaValidator } from "./pi-bundle-mcp-runtime.js";
37
import { cleanupBundleMcpHarness } from "./pi-bundle-mcp-test-harness.js";
48
import {
59
__testing,
10+
createSessionMcpRuntime,
611
getOrCreateSessionMcpRuntime,
712
materializeBundleMcpToolsForRun,
813
retireSessionMcpRuntime,
@@ -141,6 +146,64 @@ describe("session MCP runtime", () => {
141146
]);
142147
});
143148

149+
it("emits diagnostics timeline spans for bundle MCP server catalog discovery", async () => {
150+
const dir = await mkdtemp(join(tmpdir(), "openclaw-bundle-mcp-timeline-"));
151+
const serverScriptPath = join(dir, "bundle-probe-server.mjs");
152+
const timelinePath = join(dir, "timeline.jsonl");
153+
await writeBundleProbeMcpServer(serverScriptPath);
154+
const previousDiagnostics = process.env.OPENCLAW_DIAGNOSTICS;
155+
const previousTimelinePath = process.env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH;
156+
process.env.OPENCLAW_DIAGNOSTICS = "timeline";
157+
process.env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH = timelinePath;
158+
try {
159+
const runtime = createSessionMcpRuntime({
160+
sessionId: "session-timeline",
161+
workspaceDir: dir,
162+
cfg: {
163+
mcp: {
164+
servers: {
165+
bundleProbe: {
166+
command: "node",
167+
args: [serverScriptPath],
168+
},
169+
},
170+
},
171+
},
172+
});
173+
const materialized = await materializeBundleMcpToolsForRun({ runtime });
174+
await materialized.dispose();
175+
await runtime.dispose();
176+
177+
const events = (await readFile(timelinePath, "utf-8"))
178+
.trim()
179+
.split("\n")
180+
.filter(Boolean)
181+
.map((line) => JSON.parse(line) as { name: string; type: string; durationMs?: number });
182+
expect(events).toEqual(
183+
expect.arrayContaining([
184+
expect.objectContaining({ name: "bundle-mcp.server", type: "span.end" }),
185+
expect.objectContaining({ name: "bundle-mcp.connect", type: "span.end" }),
186+
expect.objectContaining({ name: "bundle-mcp.tools-list", type: "span.end" }),
187+
]),
188+
);
189+
const toolsListSpan = events.find(
190+
(event) => event.name === "bundle-mcp.tools-list" && event.type === "span.end",
191+
);
192+
expect(toolsListSpan?.durationMs).toEqual(expect.any(Number));
193+
} finally {
194+
if (previousDiagnostics === undefined) {
195+
delete process.env.OPENCLAW_DIAGNOSTICS;
196+
} else {
197+
process.env.OPENCLAW_DIAGNOSTICS = previousDiagnostics;
198+
}
199+
if (previousTimelinePath === undefined) {
200+
delete process.env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH;
201+
} else {
202+
process.env.OPENCLAW_DIAGNOSTICS_TIMELINE_PATH = previousTimelinePath;
203+
}
204+
}
205+
});
206+
144207
it("holds a runtime lease until the materialized tool runtime is disposed", async () => {
145208
let activeLeases = 0;
146209
const runtime = {

src/agents/pi-bundle-mcp-runtime.ts

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
} from "@modelcontextprotocol/sdk/validation/types.js";
1313
import type { ErrorObject, ValidateFunction } from "ajv";
1414
import type { OpenClawConfig } from "../config/types.openclaw.js";
15+
import { measureDiagnosticsTimelineSpan } from "../infra/diagnostics-timeline.js";
1516
import { logWarn } from "../logger.js";
1617
import { resolveGlobalSingleton } from "../shared/global-singleton.js";
1718
import { redactSensitiveUrlLikeString } from "../shared/net/redact-sensitive-url.js";
@@ -257,31 +258,67 @@ export function createSessionMcpRuntime(params: {
257258
sessions.set(serverName, session);
258259

259260
try {
260-
failIfDisposed();
261-
await connectWithTimeout(client, resolved.transport, resolved.connectionTimeoutMs);
262-
failIfDisposed();
263-
const listedTools = await listAllTools(client);
264-
failIfDisposed();
265-
servers[serverName] = {
266-
serverName,
267-
launchSummary: resolved.description,
268-
toolCount: listedTools.length,
269-
};
270-
for (const tool of listedTools) {
271-
const toolName = tool.name.trim();
272-
if (!toolName) {
273-
continue;
274-
}
275-
tools.push({
276-
serverName,
277-
safeServerName,
278-
toolName,
279-
title: tool.title,
280-
description: normalizeOptionalString(tool.description),
281-
inputSchema: tool.inputSchema,
282-
fallbackDescription: `Provided by bundle MCP server "${serverName}" (${resolved.description}).`,
283-
});
284-
}
261+
await measureDiagnosticsTimelineSpan(
262+
"bundle-mcp.server",
263+
async () => {
264+
failIfDisposed();
265+
await measureDiagnosticsTimelineSpan(
266+
"bundle-mcp.connect",
267+
() =>
268+
connectWithTimeout(client, resolved.transport, resolved.connectionTimeoutMs),
269+
{
270+
config: params.cfg,
271+
attributes: {
272+
serverName,
273+
safeServerName,
274+
transportType: resolved.transportType,
275+
},
276+
},
277+
);
278+
failIfDisposed();
279+
const listedTools = await measureDiagnosticsTimelineSpan(
280+
"bundle-mcp.tools-list",
281+
() => listAllTools(client),
282+
{
283+
config: params.cfg,
284+
attributes: {
285+
serverName,
286+
safeServerName,
287+
transportType: resolved.transportType,
288+
},
289+
},
290+
);
291+
failIfDisposed();
292+
servers[serverName] = {
293+
serverName,
294+
launchSummary: resolved.description,
295+
toolCount: listedTools.length,
296+
};
297+
for (const tool of listedTools) {
298+
const toolName = tool.name.trim();
299+
if (!toolName) {
300+
continue;
301+
}
302+
tools.push({
303+
serverName,
304+
safeServerName,
305+
toolName,
306+
title: tool.title,
307+
description: normalizeOptionalString(tool.description),
308+
inputSchema: tool.inputSchema,
309+
fallbackDescription: `Provided by bundle MCP server "${serverName}" (${resolved.description}).`,
310+
});
311+
}
312+
},
313+
{
314+
config: params.cfg,
315+
attributes: {
316+
serverName,
317+
safeServerName,
318+
transportType: resolved.transportType,
319+
},
320+
},
321+
);
285322
} catch (error) {
286323
if (!disposed) {
287324
logWarn(

0 commit comments

Comments
 (0)