Skip to content

fix(tools): include response in structuredContent for MCP clients#133

Open
USEONGEE wants to merge 1 commit into
tuannvm:mainfrom
USEONGEE:fix/structured-content-response
Open

fix(tools): include response in structuredContent for MCP clients#133
USEONGEE wants to merge 1 commit into
tuannvm:mainfrom
USEONGEE:fix/structured-content-response

Conversation

@USEONGEE
Copy link
Copy Markdown

@USEONGEE USEONGEE commented Mar 16, 2026

Summary

  • When STRUCTURED_CONTENT_ENABLED=true, structuredContent only contains metadata (threadId, model, sessionId, callbackUri) but not the actual response text
  • MCP clients that rely on structuredContent to extract tool results cannot access the codex response
  • This adds the response field to both the codex tool's outputSchema and the structuredContent payload

Changes

  • src/tools/definitions.ts: Add response property to codex tool's outputSchema
  • src/tools/handlers.ts: Include response in metadata object used for structuredContent
  • src/__tests__/mcp-stdio.test.ts: Update test expectations and types for response in structuredContent

Test plan

  • npm run build passes
  • npm run lint passes
  • npm run format:check passes
  • npm test — all 71 tests pass
  • Verify with an MCP client that structuredContent.response is populated when STRUCTURED_CONTENT_ENABLED=1

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 16, 2026

Walkthrough

Adds a new top-level response: string property to the Codex tool's outputSchema and includes that response value in the handler's metadata; test expectations were updated to assert the new field is present in structuredContent.

Changes

Cohort / File(s) Summary
Tool schema
src/tools/definitions.ts
Added response: string to the Codex tool outputSchema.properties.
Handler metadata
src/tools/handlers.ts
Populates response into the metadata emitted by the Codex tool handler (alongside threadId, model, sessionId, callbackUri).
Tests updated
src/__tests__/mcp-stdio.test.ts
Updated expected tool output shape to include the new top-level response field under structuredContent/response.

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

🐰 I hop with bytes and rhymes so spry,
A response now bounces in the sky.
From defs to handler, tests agree,
My tiny field hops merrily! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding a response field to structuredContent for MCP clients, which matches the PR's core objective and all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.

Change the reviews.profile setting to assertive to make CodeRabbit's nitpick more issues in your PRs.

When STRUCTURED_CONTENT_ENABLED is true, structuredContent only contains
metadata (threadId, model, sessionId) but not the actual response text.
MCP clients that rely on structuredContent to extract tool results cannot
access the response.

Add the response field to both the codex tool's outputSchema and the
structuredContent payload so MCP clients can reliably retrieve it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: useongee <118250509+USEONGEE@users.noreply.github.com>
@USEONGEE USEONGEE force-pushed the fix/structured-content-response branch from 30126a0 to 80a805d Compare March 16, 2026 12:07
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/__tests__/mcp-stdio.test.ts (1)

149-157: ⚠️ Potential issue | 🟡 Minor

Add an assertion for structuredContent.response to validate the PR objective.

Line 149 updates typing, but the test still doesn’t assert that structuredContent.response is actually populated at runtime.

✅ Suggested assertion addition
     expect(callParse.success).toBe(true);
     expect(callResponse.content[0]._meta?.threadId).toBe('th_stub_123');
     expect(callResponse.structuredContent?.threadId).toBe('th_stub_123');
+    expect(callResponse.structuredContent?.response).toBeDefined();
+    expect(callResponse.structuredContent?.response).toBe(
+      callResponse.content[0].text
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/mcp-stdio.test.ts` around lines 149 - 157, Add an assertion to
verify that structuredContent.response is populated at runtime: after parsing
with CallToolResultSchema and the existing threadId assertions, assert that
callResponse.structuredContent?.response (or the equivalent property on the
callResponse object) equals the expected response string; locate where
CallToolResultSchema, callResponse, and structuredContent are used in this test
and add the assertion there.
🧹 Nitpick comments (2)
src/tools/definitions.ts (1)

58-64: Consider making response required in the codex output schema.

You now always populate response in handler output, but the schema still treats it as optional. Making it required gives MCP clients a stronger contract.

💡 Suggested schema tightening
     outputSchema: {
       type: 'object',
       properties: {
         response: { type: 'string' },
         threadId: { type: 'string' },
       },
+      required: ['response'],
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tools/definitions.ts` around lines 58 - 64, The outputSchema currently
defines properties response and threadId but leaves response optional; update
the schema in outputSchema so that response is a required property (e.g., add a
required: ['response'] entry alongside properties) so callers have a stronger
contract; ensure this change is made near the outputSchema object (the
properties.response and properties.threadId definitions) in
src/tools/definitions.ts and run/type-check any schema consumers to confirm no
breaking uses.
src/tools/handlers.ts (1)

205-224: Avoid placing full response in content[0]._meta; keep it in structuredContent only.

Current code duplicates the full response text into both channels. Keeping _meta focused on compatibility metadata (e.g., threadId) avoids payload bloat while still exposing response in structuredContent.

♻️ Suggested split of meta payloads
-      const metadata: Record<string, unknown> = {
-        response,
-        ...(threadId && { threadId }),
-        ...(selectedModel && { model: selectedModel }),
-        ...(activeSessionId && { sessionId: activeSessionId }),
-        ...(effectiveCallbackUri && { callbackUri: effectiveCallbackUri }),
-      };
+      const contentMeta: Record<string, unknown> = {
+        ...(threadId && { threadId }),
+        ...(selectedModel && { model: selectedModel }),
+        ...(activeSessionId && { sessionId: activeSessionId }),
+        ...(effectiveCallbackUri && { callbackUri: effectiveCallbackUri }),
+      };
+      const structuredPayload: Record<string, unknown> = {
+        response,
+        ...contentMeta,
+      };

       return {
         content: [
           {
             type: 'text',
             text: response,
-            _meta: metadata,
+            _meta: contentMeta,
           },
         ],
-        structuredContent:
-          isStructuredContentEnabled() && Object.keys(metadata).length > 0
-            ? metadata
-            : undefined,
+        structuredContent: isStructuredContentEnabled()
+          ? structuredPayload
+          : undefined,
       };

Based on learnings: "When Codex emits threadId, it must be returned in content[0]._meta for Claude Code compatibility and structuredContent for other MCP clients when STRUCTURED_CONTENT_ENABLED env var is set".

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

In `@src/tools/handlers.ts` around lines 205 - 224, The content payload currently
injects the full response into content[0]._meta via the metadata object,
duplicating the response; instead, build _meta to include only compatibility
fields (e.g., threadId and any minimal required identifiers) and omit the full
response string, while keeping the full response only in structuredContent when
isStructuredContentEnabled() is true; update the code that constructs
metadata/metadataForStructuredContent so that content[0]._meta contains threadId
(and other minimal compatibility keys if necessary) and structuredContent
contains the response plus selectedModel, sessionId, callbackUri, etc.,
referencing the existing variables metadata, response, content[0]._meta,
structuredContent, isStructuredContentEnabled(), selectedModel, activeSessionId,
effectiveCallbackUri, and threadId.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/__tests__/mcp-stdio.test.ts`:
- Around line 149-157: Add an assertion to verify that
structuredContent.response is populated at runtime: after parsing with
CallToolResultSchema and the existing threadId assertions, assert that
callResponse.structuredContent?.response (or the equivalent property on the
callResponse object) equals the expected response string; locate where
CallToolResultSchema, callResponse, and structuredContent are used in this test
and add the assertion there.

---

Nitpick comments:
In `@src/tools/definitions.ts`:
- Around line 58-64: The outputSchema currently defines properties response and
threadId but leaves response optional; update the schema in outputSchema so that
response is a required property (e.g., add a required: ['response'] entry
alongside properties) so callers have a stronger contract; ensure this change is
made near the outputSchema object (the properties.response and
properties.threadId definitions) in src/tools/definitions.ts and run/type-check
any schema consumers to confirm no breaking uses.

In `@src/tools/handlers.ts`:
- Around line 205-224: The content payload currently injects the full response
into content[0]._meta via the metadata object, duplicating the response;
instead, build _meta to include only compatibility fields (e.g., threadId and
any minimal required identifiers) and omit the full response string, while
keeping the full response only in structuredContent when
isStructuredContentEnabled() is true; update the code that constructs
metadata/metadataForStructuredContent so that content[0]._meta contains threadId
(and other minimal compatibility keys if necessary) and structuredContent
contains the response plus selectedModel, sessionId, callbackUri, etc.,
referencing the existing variables metadata, response, content[0]._meta,
structuredContent, isStructuredContentEnabled(), selectedModel, activeSessionId,
effectiveCallbackUri, and threadId.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e3bd963d-f56e-4b23-9f07-f10814cac4f6

📥 Commits

Reviewing files that changed from the base of the PR and between 00b6bf6 and 30126a0.

📒 Files selected for processing (3)
  • src/__tests__/mcp-stdio.test.ts
  • src/tools/definitions.ts
  • src/tools/handlers.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/__tests__/mcp-stdio.test.ts (1)

149-157: ⚠️ Potential issue | 🟡 Minor

Missing assertion for structuredContent.response.

The type annotation at line 149 now includes response?: string, but there's no assertion to verify that structuredContent.response is actually populated with the expected value. Since the stub outputs "ok", consider adding an assertion to validate the core feature this PR introduces.

Proposed fix to add missing assertion
     expect(callResponse.content[0]._meta?.threadId).toBe('th_stub_123');
+    expect(callResponse.structuredContent?.response).toBe('ok');
     expect(callResponse.structuredContent?.threadId).toBe('th_stub_123');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/__tests__/mcp-stdio.test.ts` around lines 149 - 157, Add an assertion
that verifies the stubbed response populates structuredContent.response: after
parsing with CallToolResultSchema and the existing threadId checks, assert that
callResponse.structuredContent?.response equals the expected stub value (e.g.,
"ok") so the new optional response field is validated; reference
CallToolResultSchema and the callResponse object to locate where to insert the
expect check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/__tests__/mcp-stdio.test.ts`:
- Around line 149-157: Add an assertion that verifies the stubbed response
populates structuredContent.response: after parsing with CallToolResultSchema
and the existing threadId checks, assert that
callResponse.structuredContent?.response equals the expected stub value (e.g.,
"ok") so the new optional response field is validated; reference
CallToolResultSchema and the callResponse object to locate where to insert the
expect check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fe43a562-6cbf-4134-9bad-f1909e0fad1e

📥 Commits

Reviewing files that changed from the base of the PR and between 30126a0 and 80a805d.

📒 Files selected for processing (3)
  • src/__tests__/mcp-stdio.test.ts
  • src/tools/definitions.ts
  • src/tools/handlers.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tools/definitions.ts
  • src/tools/handlers.ts

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.

1 participant