Skip to content

Support raw MCP content blocks from execute - #149

Merged
kentcdodds merged 1 commit into
mainfrom
cursor/execute-tool-raw-content-c992
Apr 11, 2026
Merged

kentcdodds merged 1 commit into
mainfrom
cursor/execute-tool-raw-content-c992

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Apr 11, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • add an extractRawContent helper to detect __mcpContent sentinel results from execute sandbox code
  • pass raw MCP content blocks through directly in the execute tool success path instead of always serializing to a text block
  • keep structured execute results backward compatible by returning null for structuredContent.result when raw MCP content is emitted

Testing

  • npm exec -- vitest run --project node-unit packages/worker/src/mcp/executor.node.test.ts packages/worker/src/mcp/tools/execute.node.test.ts
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added native support for preserving raw content blocks from tool executions, enabling improved handling of complex result types.
  • Refactor

    • Enhanced execution result processing to intelligently handle both formatted and raw content responses, with updated response structure to reflect content availability.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented Apr 11, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces a new extractRawContent helper function that extracts a __mcpContent property from execution results and integrates it into the MCP execute tool to prefer raw MCP content blocks in responses when available.

Changes

Cohort / File(s) Summary
New Content Extraction Helper
packages/worker/src/mcp/executor.ts
Added exported extractRawContent() function that inspects values for a __mcpContent property and returns it as Array<ContentBlock> when present, otherwise null.
Execute Tool Integration
packages/worker/src/mcp/tools/execute.ts
Updated to extract raw content from execution results and prefer it in the response payload; sets structuredContent.result to null when raw content is present.
Test Coverage
packages/worker/src/mcp/executor.node.test.ts, packages/worker/src/mcp/tools/execute.node.test.ts
Added tests validating extractRawContent() behavior with sentinel and non-sentinel inputs; expanded execute tool tests to verify content handling for both raw MCP content and normal result objects, including structured content field population.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • Expose conversationId in MCP tool content #106: Modifies MCP execute tool content handling, including how tool results' content arrays are composed (prepending conversational metadata) and the overall handler logic for response construction.

Poem

🐰 A rabbit hops through the code so neat,
Raw content flows, no more defeat!
With helpers small and tests so keen,
The cleanest MCP response you've seen! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Support raw MCP content blocks from execute' clearly and specifically summarizes the main change: adding support for raw MCP content blocks in the execute tool.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/execute-tool-raw-content-c992

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kentcdodds
kentcdodds marked this pull request as ready for review April 11, 2026 02:12
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-149.kentcdodds.workers.dev

Worker: kody-pr-149
D1: kody-pr-149-db
KV: kody-pr-149-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/worker/src/mcp/executor.ts`:
- Around line 216-224: extractRawContent currently only checks that
value.__mcpContent is an array but doesn't validate each ContentBlock, letting
malformed blocks propagate to consumers like prependToolMetadataContent; add a
validation step in extractRawContent that iterates the array and rejects the
whole payload (return null) if any block fails validation. Implement/invoke a
helper (e.g., validateContentBlock(block): boolean) that checks block.type and
required fields per type (for example: for 'text' ensure a non-empty text
string; for 'image' ensure data and mimeType are present and of correct types;
for other types validate their mandatory keys) and only return the cast
Array<ContentBlock> when every block passes validation. Ensure the helper is
used by extractRawContent before returning the array.

In `@packages/worker/src/mcp/tools/execute.ts`:
- Around line 193-201: The code is returning raw MCP content from
extractRawContent without enforcing a size limit, bypassing the truncation used
by formatExecutionOutput; update the block that builds the returned content
(where prependToolMetadataContent is called with resolvedConversationId and
rawContent from extractRawContent(result.result)) to detect oversized rawContent
(or individual items) and, when exceeding safe limits, replace/truncate it by
calling formatExecutionOutput(result) or otherwise trim the payload to the
bounded fallback used elsewhere so the response cannot exceed limits; ensure the
same guard is applied to the analogous path around line 206.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2033dac-2bde-4bb7-81f2-f9bd875e1086

📥 Commits

Reviewing files that changed from the base of the PR and between 33472e4 and 0edccd1.

📒 Files selected for processing (4)
  • packages/worker/src/mcp/executor.node.test.ts
  • packages/worker/src/mcp/executor.ts
  • packages/worker/src/mcp/tools/execute.node.test.ts
  • packages/worker/src/mcp/tools/execute.ts

Comment on lines +216 to +224
export function extractRawContent(value: unknown): Array<ContentBlock> | null {
if (
typeof value === 'object' &&
value !== null &&
'__mcpContent' in value &&
Array.isArray((value as { __mcpContent: unknown }).__mcpContent)
) {
return (value as { __mcpContent: Array<ContentBlock> }).__mcpContent
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat packages/worker/src/mcp/executor.ts | head -n 230

Repository: kentcdodds/kody

Length of output: 6376


🏁 Script executed:

rg "ContentBlock" packages/worker/src/mcp/ -A 2 -B 2

Repository: kentcdodds/kody

Length of output: 5852


🏁 Script executed:

rg "extractRawContent" packages/worker/src/mcp/ -A 3 -B 1

Repository: kentcdodds/kody

Length of output: 2780


🏁 Script executed:

cat packages/worker/src/mcp/executor.node.test.ts | grep -A 20 "extractRawContent returns MCP"

Repository: kentcdodds/kody

Length of output: 512


🏁 Script executed:

cat packages/worker/src/mcp/tools/tool-response-content.ts | head -n 50

Repository: kentcdodds/kody

Length of output: 749


🏁 Script executed:

rg "type ContentBlock|interface ContentBlock" --type ts

Repository: kentcdodds/kody

Length of output: 725


Validate structure of MCP content blocks before returning from extractRawContent.

The function only verifies that __mcpContent is an array but does not validate individual block structure. Blocks missing required fields (e.g., text in text blocks, or data/mimeType in image blocks) will pass through unchecked and cause errors downstream when used by prependToolMetadataContent or other handlers.

Add a validation helper:

Proposed fix
+function isContentBlockArray(value: unknown): value is Array<ContentBlock> {
+	return (
+		Array.isArray(value) &&
+		value.every((block) => {
+			if (typeof block !== 'object' || block === null) return false
+			const candidate = block as Record<string, unknown>
+			if (candidate.type === 'text') return typeof candidate.text === 'string'
+			if (candidate.type === 'image') {
+				return (
+					typeof candidate.data === 'string' &&
+					typeof candidate.mimeType === 'string'
+				)
+			}
+			return false
+		})
+	)
+}
+
 export function extractRawContent(value: unknown): Array<ContentBlock> | null {
 	if (
 		typeof value === 'object' &&
 		value !== null &&
-		'__mcpContent' in value &&
-		Array.isArray((value as { __mcpContent: unknown }).__mcpContent)
+		Object.hasOwn(value, '__mcpContent') &&
+		isContentBlockArray((value as { __mcpContent: unknown }).__mcpContent)
 	) {
 		return (value as { __mcpContent: Array<ContentBlock> }).__mcpContent
 	}
 	return null
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/executor.ts` around lines 216 - 224,
extractRawContent currently only checks that value.__mcpContent is an array but
doesn't validate each ContentBlock, letting malformed blocks propagate to
consumers like prependToolMetadataContent; add a validation step in
extractRawContent that iterates the array and rejects the whole payload (return
null) if any block fails validation. Implement/invoke a helper (e.g.,
validateContentBlock(block): boolean) that checks block.type and required fields
per type (for example: for 'text' ensure a non-empty text string; for 'image'
ensure data and mimeType are present and of correct types; for other types
validate their mandatory keys) and only return the cast Array<ContentBlock> when
every block passes validation. Ensure the helper is used by extractRawContent
before returning the array.

Comment on lines +193 to +201
const rawContent = extractRawContent(result.result)
return {
content: prependToolMetadataContent(resolvedConversationId, [
{
type: 'text',
text: formatExecutionOutput(result),
},
...(rawContent ?? [
{
type: 'text',
text: formatExecutionOutput(result),
},
]),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add a size guard for raw MCP content responses.

Line 196 now bypasses the truncation path used by formatExecutionOutput, so very large __mcpContent payloads can exceed response limits and fail tool responses.

Suggested bounded fallback
 			const rawContent = extractRawContent(result.result)
+			const MAX_RAW_CONTENT_BYTES = 200_000
+			const rawContentJson =
+				rawContent === null ? null : JSON.stringify(rawContent)
+			const safeRawContent =
+				rawContentJson && rawContentJson.length <= MAX_RAW_CONTENT_BYTES
+					? rawContent
+					: null
 			return {
 				content: prependToolMetadataContent(resolvedConversationId, [
-					...(rawContent ?? [
+					...(safeRawContent ?? [
 						{
 							type: 'text',
 							text: formatExecutionOutput(result),
 						},
 					]),
 					...formatSurfacedMemoriesMarkdown(surfacedMemories),
 				]),
 				structuredContent: {
 					conversationId: resolvedConversationId,
-					result: rawContent ? null : result.result,
+					result: safeRawContent ? null : result.result,
 					logs: result.logs ?? [],
 					...buildMemoryStructuredContent(surfacedMemories),
 				},
 				isError: false,
 			}

Also applies to: 206-206

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/worker/src/mcp/tools/execute.ts` around lines 193 - 201, The code is
returning raw MCP content from extractRawContent without enforcing a size limit,
bypassing the truncation used by formatExecutionOutput; update the block that
builds the returned content (where prependToolMetadataContent is called with
resolvedConversationId and rawContent from extractRawContent(result.result)) to
detect oversized rawContent (or individual items) and, when exceeding safe
limits, replace/truncate it by calling formatExecutionOutput(result) or
otherwise trim the payload to the bounded fallback used elsewhere so the
response cannot exceed limits; ensure the same guard is applied to the analogous
path around line 206.

@kentcdodds
kentcdodds merged commit 565a35f into main Apr 11, 2026
16 checks passed
@kentcdodds
kentcdodds deleted the cursor/execute-tool-raw-content-c992 branch April 11, 2026 03:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants