Skip to content

SEP-1624: Clarify structuredContent vs content Usage Guidance #1624

Closed

Description

@krubenok

SEP-1624: Clarify structuredContent vs content Usage Guidance

Status: proposal
Type: Informational
Created: 2025-10-07
Related Issues: #1411
Related PRs: #371, #559

Abstract

This SEP proposes clarifying the specification around structuredContent and content fields in CallToolResult to address inconsistent client and server implementations. The proposal focuses on non-breaking clarifications to the existing spec language to better communicate the intended design and usage patterns for both fields.

Motivation

Current State of Confusion

Since the introduction of structuredContent in PR #371, there has been significant confusion about when and how clients and servers should use these fields:

  1. Inconsistent Client Behavior (#1411):

    • Cursor recently changed to prefering content for the model input per @msfeldstein
    • VS Code prefers structuredContent over content for model context. Both are shown in Chat UI.
    • Most other clients (CC/Windsurf) ignore structuredContent entirely
    • Some clients forward both fields verbatim, causing redundant context (Citation? Mentioned in Discord discussion)
  2. Server Implementation Issues:

    • Servers returning different semantic information in each field (violating backwards compatibility intent)
    • Unclear guidance leading to JSON stringification in content in structuredContent resulting in at best indentical output, at worst semanitcally duplicated output in structuredContent
  3. Misaligned Use Cases:

Historical Context

PR #371 introduced structuredContent with these design goals:

  • Enable strict validation of structured tool results for programmatic tool use
  • Support use cases like agent code generation, tool composition, and typed orchestration
  • Originally made content and structuredContent mutually exclusive

PR #559 relaxed the requirements for backwards compatibility:

  • Made content non-optional again (additive change only)
  • Changed validation requirement from MUST to SHOULD
  • Introduced the backwards compatibility guidance: "For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block."

Discord Discussion (Aug-Sep 2025):

  • structuredContent is primarily valuable at for "Code Mode" / programmatic MCP usage (per Cloudflare Code Mode blog, @domdomegg prototypes, etc...)
  • For conversational/agent use with language models directly, content is often preferred (lower token cost, better readability)
  • Current spec language inadvertently leads to treating structuredContent as superior in all contexts

Specification

Proposed Changes

1. Clarify Client Guidance in /specification/draft/server/tools.mdx

Current Language (lines 298-305):

#### Structured Content

**Structured** content is returned as a JSON object in the `structuredContent` field of a result.

For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block.

Proposed Change:

#### Structured Content

**Structured** content is returned as a JSON object in the `structuredContent` field of a result.

The `structuredContent` and `content` fields serve different use cases:

- **`content`**: Model-oriented output optimized for readability and token efficiency. Preferred for conversational agents and direct model prompting. Does not require strict schematisation. A plain text or markdown representation of the output for example.
- **`structuredContent`**: Machine-oriented output for programmatic tool use, code generation, type-safe orchestration, and strict schema validation.

**Server Requirements**:

- When both fields are present, they MUST be semantically equivalent (same information, different presentation)
  - In some cases, an explicit field in `structuredContent` can be semantically present in `content` without specific mention. For example if expected fields are returned and no error is mentioned, a `structuredContent` response that contains a `"status": "success",` object is evident without the status being verbatim mentioned as successful in `content`.
- For backwards compatibility, a tool that returns structured content SHOULD also return a response in `content`.

**Client Guidance**:

- Clients SHOULD choose the appropriate field to expose to the model based on their use case:
  - **Conversational/Agent UX**: Prefer `content` for lower token cost and readability (by both humans and models).
  - **Programmatic/Code Mode**: Prefer `structuredContent` for type safety and schema validation
- Clients SHOULD NOT forward both fields verbatim to models as semantically distinct inputs.
- Clients that use `content` MUST still perform `outputSchema` validation against `structuredContent` when present

2. Update Tool Output Schema Documentation

Add Section After Output Schema Example (around line 345):

### Designing Tools with Complementary Output Formats

Tool developers should design outputs where `content` and `structuredContent` serve complementary roles, enabling both conversational and programmatic use cases:

- **`content`** provides a human/model-oriented representation:

  - Natural language content, context, and explanations
  - Optimized for readability and token efficiency
  - Includes suggestions, interpretations, or additional guidance
  - Suitable for direct consumption by conversational agents or end-users

- **`structuredContent`** provides a machine-oriented representation:
  - Strict schema validation via `outputSchema`
  - Type safety for programmatic tool composition
  - Consistent structure for code generation and scripting
  - Enables reliable data extraction and transformation

**Design patterns for complementary outputs:**

- Define an `outputSchema` that captures all data fields, ensuring that the `text` field from `content` is present as a substring of `structuredContent`.
- Provide semantically equivalent `content` with enhanced readability
- Format `content` with context that helps models use the information effectively
- Example: A weather tool returns structured temperature/humidity data in `structuredContent`, plus a natural language summary in `content`, i.e:

  structuredContent (machine-oriented):

  ```json
  {
    "temperature": 72,
    "units": "F",
    "condition": "sunny",
    "city": "Seattle",
    "summary": "It's 72 degrees F and sunny in Seattle"
  }
  ```

content (model / human-oriented):

```json
{
  "type": "text",
  "text": "It's 72 degrees F and sunny in Seattle"
}
```

Open Question: would it be beneficial to provide some examples here or cite resources? Writing Effective Tools for Agents for example.

3. Clarify Schema Validation Requirements

Update outputSchema description in schema.ts (draft/schema.ts around line 975):

Current:

/**
 * An optional JSON Schema object defining the structure of the tool's output returned in
 * the structuredContent field of a CallToolResult.
 */
outputSchema?: {
  type: "object";
  properties?: { [key: string]: object };
  required?: string[];
};

Proposed:

/**
 * An optional JSON Schema object defining the structure of the tool's output returned in
 * the structuredContent field of a CallToolResult.
 *
 * When outputSchema is provided:
 * - Servers MUST populate structuredContent with data conforming to this schema
 * - Clients SHOULD validate structuredContent against this schema
 * - Servers SHOULD also provide a human-readable representation in content
 *
 * Use outputSchema for programmatic tool use, code generation, and type-safe integrations.
 * For conversational agents, consider whether unstructured content provides better model performance.
 */
outputSchema?: {
  type: "object";
  properties?: { [key: string]: object };
  required?: string[];
};

Open Question: if we continue to expect that content is present in structuredContent as a substring, rather than the stringified JSON we have today, we will have semantic duplication in structuredContent. By the letter of the proposed spec this is not an issue as codeful use of structuredContent can ignore the embedded content field. In practice with clients currently preferring structuredContent as model input, this duplication could lead to increased token usage and duplicate information being provided.

Non-Changes (Explicitly Out of Scope)

This SEP intentionally does NOT propose:

  1. Breaking changes to field optionality - Both fields remain as currently specified
  2. Changes to validation semantics - SHOULD/MUST requirements remain unchanged
  3. New protocol fields or capabilities - Pure clarification only
  4. Deprecation of either field - Both remain first-class citizens for their use cases

Rationale

Why These Changes

  1. Aligns with Original Design Intent: PR RFC: add Tool.outputSchema and CallToolResult.structuredContent #371 was explicitly designed for programmatic tool use, not as a universal replacement for content

  2. Addresses Real-World Pain Points:

    • Cursor has already reverted to preferring content based on the Discord discussion
    • Developers are confused by Inspector warnings that contradict best practices
    • Server authors need clear guidance on semantic equivalence requirement
  3. Prevents Future Issues:

    • Clear use-case guidance prevents clients from implementing misguided behavior
    • Semantic equivalence requirement prevents the current situation where different clients get different outputs
  4. Maintains Backwards Compatibility:

    • All existing valid implementations remain valid
    • Changes are purely additive clarifications
    • No protocol changes required

Alternative Approaches Considered

  1. Making structuredContent a side-channel for metadata

    • OpenAI Apps SDK implements this pattern as of 10/7 and also leverages _meta for additional data.
    • Would require breaking changes
    • Better addressed in future SEP after more real-world usage with more explicit side channels or audience hints (i.e. [model|host|user])
  2. Deprecating one field in favor of the other

    • Premature - both serve legitimate use cases though implementation of the fields is perhaps sub-optimal.
    • Would break existing implementations
  3. Adding capability negotiation for structured content

    • Overly complex for current problem
    • Can be added later if needed

Backward Compatibility

This SEP introduces no backward incompatibilities.

All proposed changes are purely clarifications of existing behavior:

  1. No Protocol Changes: No changes to message structure, field types, or optionality
  2. No Breaking Semantics: MUST/SHOULD requirements remain as specified in PR make CallToolResult.content non-optional and relax output schema validation requirement #559
  3. Additive Guidance Only: All existing valid implementations remain valid
  4. Clarification of Intent: Documents the original design intent from PR RFC: add Tool.outputSchema and CallToolResult.structuredContent #371

Impact on Existing Implementations

Servers:

  • Servers already returning both fields with semantic equivalence: ✅ No change needed
  • Servers returning only content: ✅ Still valid
  • Servers returning only structuredContent with outputSchema: ✅ Still valid
  • Servers returning different semantics in each field: ⚠️ Should align to guidance (was always problematic)

Clients:

  • Clients preferring content: ✅ Now explicitly supported
  • Clients preferring structuredContent: ✅ Still valid for their use case
  • Clients validating against outputSchema: ✅ No change needed
  • Clients forwarding both fields to models: ⚠️ Should align to guidance (was always problematic)

Security Implications

This SEP has no direct security implications as it introduces no protocol changes. Existing security considerations from the original specification for structuredContent remain applicable.

  • Clients should consider tool annotations for trust decisions
  • Validation of untrusted server data remains critical
  • Schema validation is a defense mechanism, not a security boundary

Reference Implementation

Proposed throughout the issue and purely documentation-driven. Related, though different changes would be beneficial in the SDKs as mentioned in #1411.

References

Appendix: Example Tool Resposes

Below are valid, semantically equivalent responses for a hypothetical get_email tool that retrieves an email for both conten and structuredContent

Note: I am taking the liberty of putting new lines in in place of \n in the outputs of both structuredContent and content to better communicate the differences. In a real implementation, these would be \n within the strings.

structuredContent

284 tokens of output excluding the semantically duplicative content object embedded inside.

{
  "status": "success",
  "response_format": "detailed",
  "mailItem": {
    "subject": "Project Update – Q3 Metrics Review",
    "bodyText":
    "Hi Kyle Rubenok,

    Summary of the requested project artifacts.
    Key Points:
    - Revenue +14% QoQ
    - Churn down 0.6 pts
    - Infra cost stable while traffic increased

    Next Actions:
    1. Prepare draft reply summarizing highlights.
    2. Schedule follow‑up review.

    Thanks,
    Automated System",
    "conversationId": "CONV-EXAMPLE-123456",
    "receivedTime": "2025-10-07T19:45:23-07:00",
    "from": {
      "name": "Kyle Rubenok",
      "email": "[email protected]"
    },
    "to": [
      {
        "name": "Recipient",
        "email": "[email protected]"
      }
    ],
    "cc": [
      {
        "name": "Review Bot",
        "email": "[email protected]"
      }
    ]
  },
  "suggestions": [
    "Use email_draft_message to start a reply when you have enough context.",
    "Call email_get_conversations_in_folder to gather thread context before acting."
  ],
  "content":"Subject: 'Project Update – Q3 Metrics Review' from Kyle Rubenok (received 2025-10-07 19:45 UTC). conversationId 'CONV-EXAMPLE-123456'\n  To: Recipient <[email protected]>, CC: Review Bot <[email protected]>\n  Status: unread (labels: inbox, unread).\n  Attachments: metrics-summary.xlsx (47 KB).\n\n  -- Message body --\n\n  Hi Kyle Rubenok,\n\n    Summary of the requested project artifacts.\n    Key Points:\n    - Revenue +14% QoQ\n    - Churn down 0.6 pts\n    - Infra cost stable while traffic increased\n\n    Next Actions:\n    1. Prepare draft reply summarizing highlights.\n    2. Schedule follow‑up review.\n\n    Thanks,\n    Automated System\n\n    -- End Message --\n\n    Potential actions:\n    - Use email_draft_message to start a reply when you have enough context.\n    - Call email_get_conversations_in_folder to gather thread context before acting."}

content

189 tokens of output

{
  "type": "text",
  "text": "
  Subject: 'Project Update – Q3 Metrics Review' from Kyle Rubenok (received 2025-10-07 19:45 UTC). conversationId 'CONV-EXAMPLE-123456'
  To: Recipient <[email protected]>, CC: Review Bot <[email protected]>

  -- Message body --

  Hi Kyle Rubenok,

    Summary of the requested project artifacts.
    Key Points:
    - Revenue +14% QoQ
    - Churn down 0.6 pts
    - Infra cost stable while traffic increased

    Next Actions:
    1. Prepare draft reply summarizing highlights.
    2. Schedule follow‑up review.

    Thanks,
    Automated System

    -- End Message --

    Potential actions:
    - Use email_draft_message to start a reply when you have enough context.
    - Call email_get_conversations_in_folder to gather thread context before acting."
}

Activity

  1. changed the title [-]SEP-####: Clarify `structuredContent` vs `content` Usage Guidance[/-] [+]SEP-1624: Clarify `structuredContent` vs `content` Usage Guidance[/+] on Oct 8, 2025
  2. domdomegg commented on Oct 8, 2025

    @domdomegg
    Member

    A couple thoughts:

    • I'm supportive of helping to clarify this! I think as programmatic MCP (aka MCP as code, programmatic function calling, programmatic tool calling, Code Mode) gains more traction it'll be valuable to encourage adoption of structuedContent
    • In general, I don't think structuredContent should include content - agree this leads to duplication, especially if all the info is already captured in the response
    • I actually think having content == JSON.stringify(structuredContent) is pretty reasonable most of the time. It's often not actually that many more tokens, and on presentation I think this should be down to the model (if needed for the user) rather than the server tool response. Especially as we move to a world with agents doing longer tasks, I think individual tool results will be less relevant to humans and programmatic MCP will be even more important, so leaning into structuredContent seems reasonable.
      • And yes there's duplication here, but tbh the cost is likely so low that it doesn't matter. If we do want to fix this we could add a capability to clients that indicates they definitely support structuredContent so the server doesn't need to send content as well, but tbh I think more features/combinations in MCP is probably the wrong direction.
  3. msfeldstein commented on Oct 8, 2025

    @msfeldstein

    Honestly it feels like trying to do MCP as Code is really stretching the boundaries of what this protocol is for and confusing the story. Its a neat idea and as a tech geek i love it, but it feels like now its a protocol for 2 pretty different things smushed together into one

    If anything maybe we make it an extension on top of the protocol, so its very clear to clients that they do not need to care about structuredContent at all if they're not an MCP as Code thing?

  4. msfeldstein commented on Oct 8, 2025

    @msfeldstein
    • Clients that use content MUST still perform outputSchema validation against structuredContent when present```

    Why?

  5. krubenok commented on Oct 8, 2025

    @krubenok
    ContributorAuthor

    Thanks both for the input! I think we're caught between a few points here, trying to identify which compromises make sense.

    In general, I don't think structuredContent should include content - agree this leads to duplication, especially if all the info is already captured in the response

    Yes, in general I agree, but it seemed like a simply way to not require any changes of the existing validation expectations driven by the Inspector for example expecting that content is a JSON.stringify'ed substring of structuredContent. If we're willing to "break" that (which I don't think is actually a breaking change?) then I would agree we should ideally not duplicate it.

    I actually think having content == JSON.stringify(structuredContent) is pretty reasonable most of the time. It's often not actually that many more tokens, and on presentation I think this should be down to the model (if needed for the user) rather than the server tool response. Especially as we move to a world with agents doing longer tasks, I think individual tool results will be less relevant to humans and programmatic MCP will be even more important, so leaning into structuredContent seems reasonable.

    This is where I'm not sure I follow. Based on the thread on Discord and this article, and my own instinct/experience, it seems optimal model-oriented output is either mutually exclusive or at least unlikely to be stringified JSON in most cases. Michael's examples and some relevant discussion. Note I think "human" is a bit of a misnomer here as I think it's actually optimized for being used directly by the models, but is coincidentally closer to what a human would prefer too.

    Image Image Image

    It seems to me that tools can fairly easily expose both fields for codeful and model use cases without harming the other. Ack I think a more ideal solution to this would be some of the discussion around audience annotations, but my gut sense is that we don't need to wait for a breaking change like that to improve things in the meantime. This would effectively be saying:

    • structuredContent is optimized for codeful use and output validatation, content is for direct model use.
    • For backwards compatibility or as a fall back clients should prefer one over the other informed by their use case, but since they are intended to be semantically equivalent, a client that prefers content should be able to use structuredContent instead despite some of the compromises.

    I think the main "cost" to going this direction would be extra data over the wire of providing similar data in two fields without having a client declare if it actually wants/needs both.

    If anything maybe we make it an extension on top of the protocol, so its very clear to clients that they do not need to care about structuredContent at all if they're not an MCP as Code thing?

    Bold is my impression as well, but I'll leave it to @domdomegg and co if we want to make such a pointy statement (the not-bold part) on that.

    Clients that use content MUST still perform outputSchema validation against structuredContent when present```
    Why?

    Very open to changing this - I intended this to be the lowest change possible interpretation of this clarification to carry forward some of advantages to outputSchema validation for untrusted servers even when clients prefer content. We could just as easily axe this and say that only the codeful MCP-for-model-driven-code things need to care about schema validation.

  6. domdomegg commented on Oct 16, 2025

    @domdomegg
    Member

    msfeldstein: Honestly it feels like trying to do MCP as Code is really stretching the boundaries of what this protocol is for and confusing the story. Its a neat idea and as a tech geek i love it, but it feels like now its a protocol for 2 pretty different things smushed together into one

    @msfeldstein I see it as still largely the same thing: a way to connect AI models to the world and allow them to read in data and take actions. Ultimately tool calls are some function running remotely, and it seems natural to strongly type these. And then once you have strongly typed functions you can start to compose them safely.

    spec: Clients that use content MUST still perform outputSchema validation against structuredContent when present

    msfeldstein: Why?

    This also doesn't make sense to me, and I'd be happy removing it after someone checks the SEP that introduced it + the author of it to see if there was specific reasoning there.

    domdomegg: I actually think having content == JSON.stringify(structuredContent) is pretty reasonable most of the time.

    krubenok: This is where I'm not sure I follow. Based on the thread on Discord and this article, and my own instinct/experience, it seems optimal model-oriented output is either mutually exclusive or at least unlikely to be stringified JSON in most cases. Michael's examples and some relevant discussion. Note I think "human" is a bit of a misnomer here as I think it's actually optimized for being used directly by the models, but is coincidentally closer to what a human would prefer too.

    @krubenok My guess is that the Discord example is probably strictly worse for the model - it's more tokens, and has ~no more information.

    On David's Discord comment, I think if you design your structuredOutput well the information content should be equal (as he says) to unstructured data. And in practice most systems you talk to will be somewhat deterministic / structured - and even those without structure can be squashed into (a sad) object like {"content":"<text>"}. Of course David may be able to clarify his comment more - I am guessing.

    On the article, it says: Even your tool response structure—for example XML, JSON, or Markdown—can have an impact on evaluation performance: there is no one-size-fits-all solution. This is because LLMs are trained on next-token prediction and tend to perform better with formats that match their training data. The optimal response structure will vary widely by task and agent. We encourage you to select the best response structure based on your own evaluation.

    This doesn't imply markdown is always the right format. I think it's good practice to try different things and see which works best. But smarter models (including most current frontier models) don't seem to be affected that much by tool response format. Older less powerful models (e.g. Haiku 3.5) did struggle with different formats but we're seeing this less with smarter models.

    I do think having some external benchmarks on this across a range of models would be very valuable! Empirics are almost always better than theory. I don't have time to do this atm, but if someone built an external benchmark for this I could almost certainly run it on Anthropic models.

    krubenok: It seems to me that tools can fairly easily expose both fields for codeful and model use cases without harming the other. Ack I think a more ideal solution to this would be some of the discussion around audience annotations, but my gut sense is that we don't need to wait for a breaking change like that to improve things in the meantime. This would effectively be saying:

    • structuredContent is optimized for codeful use and output validatation, content is for direct model use.
    • For backwards compatibility or as a fall back clients should prefer one over the other informed by their use case, but since they are intended to be semantically equivalent, a client that prefers content should be able to use structuredContent instead despite some of the compromises.
      I think the main "cost" to going this direction would be extra data over the wire of providing similar data in two fields without having a client declare if it actually wants/needs both.

    I think this is reasonable! And is what we see from many servers and clients in practice.

    I think the cost of duplication is quite low in practice (esp. when compared to compute costs of inference).

    msfeldstein: If anything maybe we make it an extension on top of the protocol, so its very clear to clients that they do not need to care about structuredContent at all if they're not an MCP as Code thing?

    krubenok: Bold is my impression as well, but I'll leave it to @domdomegg and co if we want to make such a pointy statement (the not-bold part) on that.

    We have seen some servers in the wild that only provide structuredContent, which I think is technically allowed by the spec (and is supported in SDKs). So clients probably do need to be aware of it.

    We could probably put something in the SDKs to make this a lot easier on simpler clients though!

  7. self-assigned this
    on Feb 3, 2026
  8. olaservo commented on Feb 3, 2026

    @olaservo
    Member

    Hi @krubenok , if you'd still like to propose this change, could you create a PR following the new format for SEPs?

    For anyone weighing in on this: I'm curious if there has been any new empirical data or research shared on these approaches since October, related to whether models make better decisions when given both options vs just one format. (Specifically: have there been any new controlled experiments presenting identical tool result information in structured versus unstructured formats, measuring the impact on LLM reasoning accuracy, task completion rates, or hallucination frequency?)

    This clarification seems broadly supported. I think there is more urgency now that MCP Apps appear to be following the convention of content -> model and structuredContent -> app outside of the spec itself. However I would highly prefer there being some evidence to strengthen the case.

  9. krubenok commented on Feb 3, 2026

    @krubenok
    ContributorAuthor

    Yes happy to! And oops I totally didn't notice that this has gotten some attention lately. Makes sense with MCP Apps - working on MCP UI/OAP AppsSDK/MCP Apps is part of what motivated my thinking here.

    For anyone weighing in on this: I'm curious if there has been any new empirical data or research shared on these approaches since October, related to whether models make better decisions when given both options vs just one format. (Specifically: have there been any new controlled experiments presenting identical tool result information in structured versus unstructured formats, measuring the impact on LLM reasoning accuracy, task completion rates, or hallucination frequency?)

    I can't share specifics unfortunately (though I could probably replicate it on something not from my day job) but in general - yes there are positive impacts, in my experience it's been less around hallucination reduction or accuracy (in a direct sense), and more that unstructured content can allow for more relevant and information-dense tokens to be returned for agents.

    On smaller tool calls responses moving to a semantically equivalent but unstructured responses has had negligible effects on response quality. But as the responses got larger or more tool responses were in the context window that token savings to avoid or at least defer compaction which had a much larger impact. Perhaps @domdomegg could share if there was any underlying eval work that is publish-able behind this post? https://www.anthropic.com/engineering/writing-tools-for-agents

    I'd expect it would be more rigorous than what I have on hand to share.

  10. krubenok commented on Feb 3, 2026

    @krubenok
    ContributorAuthor

    Opened PR #2200 with the SEP and spec clarifications. SEP is now . PR: #2200

  11. dend commented on Mar 4, 2026

    @dend
    Contributor

    Closing as SEP is tracked in PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions