fix: harden third-party tool result serialization - #2892
Conversation
OrKoN
left a comment
There was a problem hiding this comment.
Please check the following:
-
Remove the
if (!result)guard, the| undefinedreturn annotation on the evaluate callback insrc/McpPage.ts, and the matchingthrows a descriptive error ... when evaluate resolves to undefinedtest intests/McpPage.test.ts. Puppeteer only turnsevaluateintoundefinedfor "Object reference chain is too long" or "Object couldn't be returned by value" (seerewriteErrorincdp/ExecutionContext.ts), and the new flat{result: string, stashed: number}return value can't trigger either one, so this branch can't run, its error message is misleading, and the test covers a case that can't happen (AGENTS.md: "Only test real scenarios"). -
In
executeThirdPartyDeveloperTool(src/McpPage.ts), don't let the cleanuppptrPage.evaluateinfinallyreplace the original error, and skip it whenresult.stashed === 0. IfevaluateHandlefails because the page navigated or closed, the cleanup call fails too and its error hides the real cause; the page now only setsstashedElementswhen it's non-empty, so the extra CDP round trip on every call with no stashed elements isn't needed. -
When the tool returns
undefined, add an explicit response line (e.g.Tool returned no result.) insrc/McpPage.tsinstead of adding nothing. Right now the agent gets an empty tool response with no sign that the call succeeded, which is easy to read as a failure.
Summary
Hardens
McpPage.executeThirdPartyDeveloperToolagainst CDPreturnByValueserialization failures and edge-case values returned by third-party developer tools.Previously, if a tool returned values that CDP
Runtime.evaluate(returnByValue: true) could not serialize (such asSymbol,BigInt, circular arrays, deeply nested objects, orObject.create(null)),pptrPage.evaluateeither threw or resolved toundefined, resulting inTypeError: Cannot read properties of undefined (reading 'stashed')and leavingwindow.__dtmcp.stashedElementsuncleaned.Changes
pptrPage.evaluateandJSON.parseit in Node so CDP only transfers a flat{result?: string, stashed: number}object by value.processToolResulthandling:Setwithtry ... finallycleanup across both arrays and plain objects, fixing circular array recursion while preserving shared (non-circular DAG) references.Object.create(null)(proto === null) as a plain object and fall back to<Object instance>for non-plain objects with missing or anonymous constructors.symbolandbigintvalues to string representations.pptrPage.evaluateresolves toundefined.window.__dtmcp.stashedElementsis only populated after serialization succeeds and is always cleared in afinallyblock.undefinedto response lines when a tool returnsundefined.