Repository navigation
Description
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:
-
Inconsistent Client Behavior (#1411):
- Cursor recently changed to prefering
contentfor the model input per @msfeldstein - VS Code prefers
structuredContentovercontentfor model context. Both are shown in Chat UI. - Most other clients (CC/Windsurf) ignore
structuredContententirely - Some clients forward both fields verbatim, causing redundant context (Citation? Mentioned in Discord discussion)
- Cursor recently changed to prefering
-
Server Implementation Issues:
- Servers returning different semantic information in each field (violating backwards compatibility intent)
- Unclear guidance leading to JSON stringification in
contentinstructuredContentresulting in at best indentical output, at worst semanitcally duplicated output instructuredContent
-
Misaligned Use Cases:
- Original design intent (PR RFC: add
Tool.outputSchemaandCallToolResult.structuredContent#371): Strict schema validation for programmatic/codeful tool use - Current perception:
structuredContentis "better" and should always be preferred - Lack of clarity on which modality is appropriate for different client architectures
- Original design intent (PR RFC: add
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
contentandstructuredContentmutually exclusive
PR #559 relaxed the requirements for backwards compatibility:
- Made
contentnon-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):
structuredContentis 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,
contentis often preferred (lower token cost, better readability) - Current spec language inadvertently leads to treating
structuredContentas 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 present2. 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
contentis present instructuredContentas a substring, rather than the stringified JSON we have today, we will have semantic duplication instructuredContent. By the letter of the proposed spec this is not an issue as codeful use ofstructuredContentcan ignore the embeddedcontentfield. In practice with clients currently preferringstructuredContentas 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:
- Breaking changes to field optionality - Both fields remain as currently specified
- Changes to validation semantics - SHOULD/MUST requirements remain unchanged
- New protocol fields or capabilities - Pure clarification only
- Deprecation of either field - Both remain first-class citizens for their use cases
Rationale
Why These Changes
-
Aligns with Original Design Intent: PR RFC: add
Tool.outputSchemaandCallToolResult.structuredContent#371 was explicitly designed for programmatic tool use, not as a universal replacement forcontent -
Addresses Real-World Pain Points:
- Cursor has already reverted to preferring
contentbased on the Discord discussion - Developers are confused by Inspector warnings that contradict best practices
- Server authors need clear guidance on semantic equivalence requirement
- Cursor has already reverted to preferring
-
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
-
Maintains Backwards Compatibility:
- All existing valid implementations remain valid
- Changes are purely additive clarifications
- No protocol changes required
Alternative Approaches Considered
-
Making
structuredContenta side-channel for metadata- OpenAI Apps SDK implements this pattern as of 10/7 and also leverages
_metafor 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])
- OpenAI Apps SDK implements this pattern as of 10/7 and also leverages
-
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
-
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:
- No Protocol Changes: No changes to message structure, field types, or optionality
- No Breaking Semantics: MUST/SHOULD requirements remain as specified in PR make CallToolResult.content non-optional and relax output schema validation requirement #559
- Additive Guidance Only: All existing valid implementations remain valid
- Clarification of Intent: Documents the original design intent from PR RFC: add
Tool.outputSchemaandCallToolResult.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
structuredContentwithoutputSchema: ✅ 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
- Original PR RFC: add
Tool.outputSchemaandCallToolResult.structuredContent#371: RFC: addTool.outputSchemaandCallToolResult.structuredContent#371 - Relaxation PR make CallToolResult.content non-optional and relax output schema validation requirement #559: make CallToolResult.content non-optional and relax output schema validation requirement #559
- Issue Clarify client handling of structuredContent vs content fields #1411: Clarify client handling of structuredContent vs content fields #1411
- Discord Discussion
- Anthropic Blog: https://www.anthropic.com/engineering/writing-tools-for-agents
- Cloudflare Code Mode: https://blog.cloudflare.com/code-mode/
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
[-]SEP-####: Clarify `structuredContent` vs `content` Usage Guidance[/-][+]SEP-1624: Clarify `structuredContent` vs `content` Usage Guidance[/+]domdomegg commentedon Oct 8, 2025
A couple thoughts:
structuedContentstructuredContentshould includecontent- agree this leads to duplication, especially if all the info is already captured in the responsecontent==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.structuredContentso the server doesn't need to sendcontentas well, but tbh I think more features/combinations in MCP is probably the wrong direction.msfeldstein commentedon Oct 8, 2025
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?
msfeldstein commentedon Oct 8, 2025
Why?
krubenok commentedon Oct 8, 2025
Thanks both for the input! I think we're caught between a few points here, trying to identify which compromises make sense.
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
contentis a JSON.stringify'ed substring ofstructuredContent. 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.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.
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:
structuredContentis optimized for codeful use and output validatation,contentis for direct model use.contentshould be able to usestructuredContentinstead 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.
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.
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.domdomegg commentedon Oct 16, 2025
@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.
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.
@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.
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).
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!
olaservo commentedon Feb 3, 2026
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.
krubenok commentedon Feb 3, 2026
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.
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.
krubenok commentedon Feb 3, 2026
Opened PR #2200 with the SEP and spec clarifications. SEP is now . PR: #2200
dend commentedon Mar 4, 2026
Closing as SEP is tracked in PR.
Add client-aware tool result mode (content vs structuredContent)
feat(MCP): Break down tool result size by content type