Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions apps/mesh/src/ai-providers/coding-agents/claude-code/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createClaudeCode } from "ai-sdk-provider-claude-code";
import type { McpServerConfig } from "@anthropic-ai/claude-agent-sdk";
import type { ToolApprovalLevel } from "@/api/routes/decopilot/helpers";

/**
Expand All @@ -9,14 +10,7 @@ import type { ToolApprovalLevel } from "@/api/routes/decopilot/helpers";
export function createClaudeCodeModel(
modelId: string,
options?: {
mcpServers?: Record<
string,
{
type: "sse" | "http";
url: string;
headers?: Record<string, string>;
}
>;
mcpServers?: Record<string, McpServerConfig>;
toolApprovalLevel?: ToolApprovalLevel;
resume?: string;
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import { createSdkMcpServer, tool } from "ai-sdk-provider-claude-code";
import { z } from "zod";
import type { VirtualClient } from "./sandbox";
import { normalizePromptContent } from "./prompts";
import {
MAX_RESULT_TOKENS,
createOutputPreview,
estimateJsonTokens,
} from "./read-tool-output";

export function createBuiltinMcpServer(
passthroughClient: VirtualClient,
toolOutputMap: Map<string, string>,
) {
const readPrompt = tool(
"read_prompt",
"Read a prompt by name from <available-prompts>. " +
"Returns the prompt messages with action-oriented guide content. " +
"Use this to load step-by-step instructions for common tasks.",
{
name: z
.string()
.min(1)
.describe("The name of the prompt from <available-prompts>."),
arguments: z
.record(z.string(), z.string())
.optional()
.describe(
"Optional arguments for the prompt, as key-value string pairs.",
),
},
async ({ name, arguments: args }) => {
try {
const result = await passthroughClient.getPrompt({
name,
arguments: args,
});
const messages = result.messages;

if (!messages || messages.length === 0) {
return {
content: [
{ type: "text" as const, text: "Prompt returned no content." },
],
};
}

const parts = messages.map((m) => ({
role: m.role,
content: normalizePromptContent(m.content),
}));

const serialized = JSON.stringify(parts, null, 2);
const tokens = estimateJsonTokens(serialized);

if (tokens > MAX_RESULT_TOKENS) {
const toolCallId = `prompt_${Date.now()}`;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Tool output IDs use Date.now(), which can collide under concurrency and overwrite stored outputs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/decopilot/built-in-tools/built-in-mcp-server.ts, line 57:

<comment>Tool output IDs use `Date.now()`, which can collide under concurrency and overwrite stored outputs.</comment>

<file context>
@@ -0,0 +1,246 @@
+        const tokens = estimateJsonTokens(serialized);
+
+        if (tokens > MAX_RESULT_TOKENS) {
+          const toolCallId = `prompt_${Date.now()}`;
+          toolOutputMap.set(toolCallId, serialized);
+          const preview = createOutputPreview(serialized);
</file context>
Fix with Cubic

toolOutputMap.set(toolCallId, serialized);
const preview = createOutputPreview(serialized);
return {
content: [
{
type: "text" as const,
text: `Prompt content too large (${tokens} tokens). Use read_tool_output with tool_call_id "${toolCallId}" to extract specific data.\n\nPreview:\n${preview}`,
},
],
};
}

return { content: [{ type: "text" as const, text: serialized }] };
} catch (error) {
return {
content: [
{
type: "text" as const,
text: `Error reading prompt: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
},
);

const readResource = tool(
"read_resource",
"Read a resource by its URI. Returns the content of the resource. " +
"Resource URIs (docs://...) are provided in prompt content.",
{
uri: z
.string()
.min(1)
.describe("The URI of the resource to read (e.g. docs://store.md)."),
},
async ({ uri }) => {
try {
const result = await passthroughClient.readResource({ uri });
const contents = result.contents;

if (!contents || contents.length === 0) {
return {
content: [
{ type: "text" as const, text: "Resource returned no content." },
],
};
}

const parts = contents.map((c) => {
if ("text" in c && c.text !== undefined) {
return { uri: c.uri, mimeType: c.mimeType, text: c.text };
}
if ("blob" in c && c.blob !== undefined) {
return {
uri: c.uri,
mimeType: c.mimeType,
blob: `[binary data, ${c.blob.length} bytes base64]`,
};
}
return { uri: c.uri, mimeType: c.mimeType };
});

const serialized = JSON.stringify(parts, null, 2);
const tokens = estimateJsonTokens(serialized);

if (tokens > MAX_RESULT_TOKENS) {
const toolCallId = `resource_${Date.now()}`;
toolOutputMap.set(toolCallId, serialized);
const preview = createOutputPreview(serialized);
return {
content: [
{
type: "text" as const,
text: `Resource content too large (${tokens} tokens). Use read_tool_output with tool_call_id "${toolCallId}" to extract specific data.\n\nPreview:\n${preview}`,
},
],
};
}

return { content: [{ type: "text" as const, text: serialized }] };
} catch (error) {
return {
content: [
{
type: "text" as const,
text: `Error reading resource: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
},
);

const readToolOutput = tool(
"read_tool_output",
"Filter a tool output that was too large to display inline. " +
"Returns all lines matching the given regular expression pattern (grep-like). " +
"You may call this tool multiple times with different patterns to extract different pieces of information.",
{
tool_call_id: z
.string()
.describe("The tool call ID from the truncated output."),
pattern: z
.string()
.min(1)
.describe(
"Regular expression pattern to filter tool output lines. Returns all matching lines.",
),
},
async ({ tool_call_id, pattern }) => {
try {
if (!toolOutputMap.has(tool_call_id)) {
return {
content: [
{
type: "text" as const,
text: `Tool output not found for tool call id: ${tool_call_id}. Available ids: ${[...toolOutputMap.keys()].join(", ") || "(none)"}`,
},
],
};
}
const input = toolOutputMap.get(tool_call_id)!;

let regex: RegExp;
try {
regex = new RegExp(pattern);
} catch {
return {
content: [
{
type: "text" as const,
text: `Invalid regex pattern: ${pattern}`,
},
],
isError: true,
};
}

const lines = input.split("\n");
const matching = lines.filter((line) => regex.test(line));
const resultText = matching.join("\n");

const tokenCount = estimateJsonTokens(resultText);
if (tokenCount > MAX_RESULT_TOKENS) {
const preview = createOutputPreview(resultText);
return {
content: [
{
type: "text" as const,
text: `Output is still too long (${tokenCount} tokens), use a more specific pattern to reduce output.\n\nPreview:\n${preview}`,
},
],
};
}

return {
content: [
{
type: "text" as const,
text: JSON.stringify({
result: resultText,
matchCount: matching.length,
totalLines: lines.length,
}),
},
],
};
} catch (error) {
return {
content: [
{
type: "text" as const,
text: `Error filtering tool output: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
},
);

return createSdkMcpServer({
name: "builtins",
tools: [readPrompt, readResource, readToolOutput],
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
* Replaces image/audio blobs and embedded resources with placeholders
* to avoid dumping base64 into the model context.
*/
function normalizePromptContent(
export function normalizePromptContent(
content:
| { type: string; [key: string]: unknown }
| { type: string; [key: string]: unknown }[]
Expand Down
41 changes: 41 additions & 0 deletions apps/mesh/src/api/routes/decopilot/stream-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
import { getInternalUrl } from "@/core/server-constants";
import { traced, tracer } from "@/observability";
import { getPodId } from "@/core/pod-identity";
import { createBuiltinMcpServer } from "./built-in-tools/built-in-mcp-server";

/**
* Creates a language model from the provider, enabling reasoning when the
Expand Down Expand Up @@ -364,10 +365,13 @@ async function streamCoreInner(

const toolOutputMap = new Map<string, string>();
const organization = ctx.organization!;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let capturedWriter: { write: (part: any) => void } | null = null;

const uiStream = createUIMessageStream({
originalMessages: allMessages,
execute: async ({ writer }) => {
capturedWriter = writer;
const passthroughClient = await createVirtualClientFrom(
virtualMcp,
ctx,
Expand Down Expand Up @@ -574,6 +578,10 @@ async function streamCoreInner(
});

const mcpUrl = `${getInternalUrl()}/mcp/virtual-mcp/${input.agent.id}`;
const builtinServer = createBuiltinMcpServer(
passthroughClient,
toolOutputMap,
);
languageModel = createClaudeCodeModel(
resolveClaudeCodeModelId(input.models.thinking.id),
{
Expand All @@ -586,6 +594,7 @@ async function streamCoreInner(
"x-org-id": input.organizationId,
},
},
builtins: builtinServer,
},
toolApprovalLevel: input.toolApprovalLevel,
resume: resumeSessionId,
Expand Down Expand Up @@ -937,6 +946,38 @@ async function streamCoreInner(
});
},
onStepFinish: ({ responseMessage }) => {
// Emit data-connection-auth for newly created connections that need auth
if (capturedWriter && responseMessage?.parts) {
for (const part of responseMessage.parts) {
if (
"toolName" in part &&
part.toolName === "CONNECTION_INSTALL" &&
"result" in part &&
part.result &&
typeof part.result === "object"
) {
const result = part.result as {
needs_auth?: boolean;
connection_id?: string;
title?: string;
icon?: string | null;
connection_url?: string | null;
};
if (result.needs_auth && result.connection_id) {
capturedWriter.write({
type: "data-connection-auth",
data: {
connectionId: result.connection_id,
title: result.title ?? "Connection",
icon: result.icon ?? null,
connectionUrl: result.connection_url ?? null,
},
});
}
}
}
}

const transitions = runRegistry.dispatch({
type: "STEP_DONE",
taskId: mem.thread.id,
Expand Down
Loading
Loading