anthropics / anthropics/claude-agent-sdk-python
Feature Request: Expose MCP `_meta` field in ToolResultBlock for handler-only data
- Lingua principale
- Python
- Stelle
- 8.1k
- Fork
- 1.3k
- Merge medio
- 2g 31m
- PR unite (30g)
- 1
Descrizione
## The Ask
Provide a return path from MCP tools to event handlers that **does not enter model context**.
MCP's `_meta` field is designed for exactly this. Exposing it in `ToolResultBlock` would solve it.
## Problem
MCP tools often need to return two types of data:
| Data Type | Destination | Example |
|-----------|-------------|---------|
| **For model** | Model context | "Found 3 matching records" |
| **For UI/handlers** | Event handlers only | Record IDs, metadata, pagination cursors |
Currently, **everything in the tool return enters model context**. There's no way to say "this part is for handlers, not the model."
The only options today:
| Approach | Reaches Handlers | Enters Model Context |
|----------|------------------|----------------------|
| Put in `content` | ✓ | ✓ (unwanted) |
| Put in `_meta` | ✗ (stripped by SDK) | ✗ |
| Build custom callback | ✓ | ✗ |
To avoid polluting model context, we're forced to build out-of-band callback infrastructure for each tool that needs UI-only data.
## What We Have To Do Today
```python
# 1. Define a notifier class
class DataNotifier:
def __init__(self):
self._callback = None
def set_callback(self, cb):
self._callback = cb
def notify(self, data):
if self._callback:
self._callback(data)
# 2. Create module-level singleton
_notifier = DataNotifier()
# 3. Tool fires callback BEFORE return (before SDK processes)
async def my_tool(args):
result = do_work(args)
_notifier.notify(result["ui_data"]) # Side-channel to handler
return {"content": [{"type": "text", "text": "Done"}]}
# 4. Wire callback during app initialization
notifier.set_callback(handler.on_ui_data)
# 5. Handler receives via callback
def on_ui_data(self, data):
self.panel.update(data)
```
This pattern must be repeated for every tool that needs handler-only data.
## Why Not Just Use `content`?
We can put UI data in `content` and parse it in handlers. But:
1. **Token waste** - Model receives data it doesn't need
2. **Context pollution** - Model sees IDs, cursors, metadata meant for UI
3. **Semantic confusion** - Model may try to interpret UI-specific data structures
MCP designed `_meta` specifically for "hidden data for client applications only, not visible to the model."
## Proposed Solution
Expose `_meta` in `ToolResultBlock`:
```python
@dataclass
class ToolResultBlock:
tool_use_id: str
content: str | List[Dict[str, Any]] | None = None
is_error: bool | None = None
meta: Dict[str, Any] | None = None # NEW
```
Tools return:
```python
return {
"content": [{"type": "text", "text": "Recalled 3 observations"}],
"_meta": {"observation_ids": ["obs_1", "obs_2", "obs_3"]}
}
```
Handlers receive:
```python
def on_tool_complete(self, block: ToolResultBlock):
if block.meta:
self.highlight_items(block.meta["observation_ids"])
```
- **Model sees:** `"Recalled 3 observations"`
- **Handler gets:** `{"observation_ids": ["obs_1", "obs_2", "obs_3"]}`
## Use Cases
| Use Case | Model Sees | Handler Gets (via `_meta`) |
|----------|------------|----------------------------|
| Knowledge retrieval | "Found 3 relevant items" | Item IDs for UI highlighting |
| Search | "Results for 'query'" | Facets, pagination cursor, result IDs |
| File operations | "Uploaded file.txt" | Progress %, bytes transferred |
| Database query | Query results | Execution plan, timing stats |
| API calls | Response data | Rate limits, request ID, headers |
## Related
[anthropics/claude-code#9767](https://github.com/anthropics/claude-code/issues/9767) addresses MCP events/notifications not reaching the LLM for multi-turn state. This request is complementary - we need a channel that reaches event handlers but **bypasses** model context, for UI-only data that shouldn't consume tokens.
## Benefit
- Eliminates custom callback machinery for every UI-data use case
- Aligns SDK with MCP specification intent for `_meta`
- Natural pattern for synchronous tool calls
- Backwards compatible (new optional field)
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Valutazione
Questa issue non è ancora stata valutata.