mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 21:34:06 +00:00
fix(agents): surface MCP structured content in tool results
Surface inbound bundle-MCP structuredContent as the model-visible result when present so agents can read Codex MCP threadId values and continue with codex-reply. Preserve non-structured content behavior, preserve the empty-result fallback, and keep details.structuredContent for internal consumers. Also remove an unused secrets path helper that was breaking the latest prod-type gate on main. Fixes #87511. Verification: - node scripts/run-vitest.mjs src/agents/agent-bundle-mcp-tools.materialize.test.ts - pnpm exec oxfmt --check src/secrets/path-utils.ts src/agents/agent-bundle-mcp-materialize.ts src/agents/agent-bundle-mcp-tools.materialize.test.ts - pnpm tsgo:prod - local check-guards shard commands - live Codex MCP smoke with codex__codex and codex__codex-reply same-thread continuation - autoreview clean - CI run 26587222874 green Co-authored-by: Pluviobyte <Pluviobyte@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,30 +27,33 @@ function toAgentToolResult(params: {
|
||||
const content = Array.isArray(params.result.content)
|
||||
? (params.result.content as AgentToolResult<unknown>["content"])
|
||||
: [];
|
||||
const normalizedContent: AgentToolResult<unknown>["content"] =
|
||||
content.length > 0
|
||||
const structuredContentBlock =
|
||||
params.result.structuredContent !== undefined
|
||||
? ({
|
||||
type: "text",
|
||||
text: `structuredContent:\n${JSON.stringify(params.result.structuredContent, null, 2)}`,
|
||||
} as const)
|
||||
: null;
|
||||
// Structured MCP results are the canonical model payload here; replacing
|
||||
// mirrored content avoids duplicating large tool output in the prompt.
|
||||
const normalizedContent: AgentToolResult<unknown>["content"] = structuredContentBlock
|
||||
? [structuredContentBlock]
|
||||
: content.length > 0
|
||||
? content
|
||||
: params.result.structuredContent !== undefined
|
||||
? [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(params.result.structuredContent, null, 2),
|
||||
},
|
||||
]
|
||||
: ([
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
status: params.result.isError === true ? "error" : "ok",
|
||||
server: params.serverName,
|
||||
tool: params.toolName,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
] as AgentToolResult<unknown>["content"]);
|
||||
: ([
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
status: params.result.isError === true ? "error" : "ok",
|
||||
server: params.serverName,
|
||||
tool: params.toolName,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
] as AgentToolResult<unknown>["content"]);
|
||||
const details: Record<string, unknown> = {
|
||||
mcpServer: params.serverName,
|
||||
mcpTool: params.toolName,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { validateToolArguments } from "openclaw/plugin-sdk/llm";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getPluginToolMeta } from "../plugins/tools.js";
|
||||
@@ -18,6 +19,7 @@ function makeToolRuntime(
|
||||
params: {
|
||||
tools?: McpCatalogTool[];
|
||||
serverName?: string;
|
||||
result?: CallToolResult;
|
||||
resultText?: string;
|
||||
} = {},
|
||||
): SessionMcpRuntime {
|
||||
@@ -63,10 +65,11 @@ function makeToolRuntime(
|
||||
},
|
||||
tools,
|
||||
}),
|
||||
callTool: async () => ({
|
||||
content: [{ type: "text", text: params.resultText ?? "FROM-BUNDLE" }],
|
||||
isError: false,
|
||||
}),
|
||||
callTool: async () =>
|
||||
params.result ?? {
|
||||
content: [{ type: "text", text: params.resultText ?? "FROM-BUNDLE" }],
|
||||
isError: false,
|
||||
},
|
||||
dispose: async () => {},
|
||||
};
|
||||
}
|
||||
@@ -87,6 +90,44 @@ describe("createBundleMcpToolRuntime", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps structuredContent visible when MCP tools also return text content", async () => {
|
||||
const runtime = await materializeBundleMcpToolsForRun({
|
||||
runtime: makeToolRuntime({
|
||||
result: {
|
||||
content: [{ type: "text", text: "pong" }],
|
||||
structuredContent: {
|
||||
threadId: "019e6cdb-8e7f-7cb2-891f-9edb689f6fc7",
|
||||
content: "pong",
|
||||
},
|
||||
isError: false,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await runtime.tools[0].execute("call-bundle-probe", {}, undefined, undefined);
|
||||
|
||||
expectTextContentBlock(
|
||||
result.content[0],
|
||||
`structuredContent:\n${JSON.stringify(
|
||||
{
|
||||
threadId: "019e6cdb-8e7f-7cb2-891f-9edb689f6fc7",
|
||||
content: "pong",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}`,
|
||||
);
|
||||
expect(result.content).toHaveLength(1);
|
||||
expect(result.details).toEqual({
|
||||
mcpServer: "bundleProbe",
|
||||
mcpTool: "bundle_probe",
|
||||
structuredContent: {
|
||||
threadId: "019e6cdb-8e7f-7cb2-891f-9edb689f6fc7",
|
||||
content: "pong",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("disambiguates bundle MCP tools that collide with existing tool names", async () => {
|
||||
const runtime = await materializeBundleMcpToolsForRun({
|
||||
runtime: makeToolRuntime(),
|
||||
|
||||
Reference in New Issue
Block a user