anthropics / anthropics/claude-agent-sdk-typescript
Large MCP tool result offload: structuredContent produces unnavigable single-line file vs content array's pretty-printed output
- Vorherrschende Sprache
- Shell
- Sterne
- 1.8k
- Forks
- 226
- PR-Merge-Kennzahlen
- Keine gemergten PRs in 30 T.
Beschreibung
## Summary
When the SDK offloads a large MCP tool result to a temp file, the serialization path differs between `content` and `structuredContent` in a way that makes `structuredContent`-based results **completely unnavigable** — worse than the `content` array approach, and the opposite of what the MCP spec intends.
## Background
We hit this while investigating why a production agent couldn't navigate a large tool result (~135K chars of structured extraction data). The agent was receiving an offloaded file but couldn't use `Read` with offset/limit or `Grep` effectively.
## Root Cause
The SDK's large-result offload path serializes the tool result differently depending on which field is present:
**`content` array path:**
```
JSON.stringify(result.content, null, 2)
```
Produces a pretty-printed JSON array. Each text block's `"text"` field value is a JSON string, so multi-line text is still collapsed to a single escaped string — but the outer structure has many lines.
**`structuredContent` path:**
```
JSON.stringify(result.structuredContent)
```
No indent argument. Produces a single minified line regardless of the object's complexity.
The `structuredContent` path should use `JSON.stringify(result.structuredContent, null, 2)` to match the intent of the field and produce a navigable file.
## Empirical Validation
We wrote a probe against SDK v0.2.41 that runs a real `query()` with a fake MCP tool returning 150 location objects (~135K chars / ~3,469 lines of pretty-printed JSON). We tested all three result shapes and inspected the resulting offloaded files:
| Return format | File lines | Max line length | Agent-navigable? |
|---|---|---|---|
| `content: [{type: 'text', text: prettyJSON}]` | 5 | 149,500 chars | ❌ |
| `structuredContent: parsedObject` | 1 | 88,566 chars | ❌ (worst) |
| `toolResult: prettyJSON` *(pre-standard, removed)* | 3,468 | 95 chars | ✅ |
**`content` array (5 lines):** The SDK wraps the array with `JSON.stringify(array, null, 2)`. The outer structure is pretty-printed, but the `"text"` field value is a JSON string — its embedded newlines are escaped as `\n`, collapsing the entire payload to a single 149K-char line:
```
L1 (1 char): [
L2 (3 chars): {
L3 (19 chars): "type": "text",
L4 (149,500 chars): "text": "{\n \"document_id\": \"...\",\n \"extractions\": [\n ..."
L5 (3 chars): }
L6 (1 char): ]
```
**`structuredContent` (1 line):** The entire object is minified to a single line. Even larger tools are worse with this format than with the `content` array.
**`toolResult` string (3,468 lines, max 95 chars):** Because `toolResult` is a raw string, the SDK writes it directly to the file without re-encoding. The pretty-printed JSON lands intact and is fully navigable with `Read(offset, limit)` and `Grep`.
## Discovery of the `toolResult` behavior
During this investigation we discovered that the SDK still handles a `toolResult` string field on the result object — routing it through a code path that writes the string directly to the temp file. This produced the only navigable output in our tests.
We understand `toolResult` was a pre-standard attribute that was removed before the MCP spec launched, so we're not proposing it as a solution. We're reporting it here because its behavior (raw string → direct file write → navigable output) clearly demonstrates what the `structuredContent` path should achieve once the missing `null, 2` indent is added.
## Expected Behavior
When a tool returns `structuredContent`, the offloaded file should be pretty-printed:
```
JSON.stringify(result.structuredContent, null, 2)
```
This would produce a file with many short lines (one per JSON key/value), making it navigable with `Read(offset, limit)` and `Grep` — which is exactly the point of offloading to a file in the first place.
## Workaround (Standards-Compliant)
While waiting for a fix, we discovered that returning one `content` text block **per line** of the pretty-printed JSON produces a navigable file. The SDK serializes the array with `JSON.stringify(content, null, 2)` — with many short blocks, each block becomes ~4 short file lines:
```typescript
content: JSON.stringify(data, null, 2).split('\n').map(line => ({ type: 'text', text: line }))
```
Result: 13,877 lines, max 113 chars — fully navigable. Not elegant, but MCP-compliant and effective.
## Environment
- SDK version: `@anthropic-ai/claude-agent-sdk` v0.2.41
- MCP spec reference: 2025-11-25 §tools (structured content)
- Probe payload: 150 location objects, ~135K chars / ~3,469 lines pretty-printed
Beitragsleitfaden
Für dieses Repository ist kein Beitragsleitfaden indexiert
Bewertung
Dieses Issue wurde noch nicht bewertet.