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
# 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:
- Token waste - Model receives data it doesn't need
- Context pollution - Model sees IDs, cursors, metadata meant for UI
- 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:
@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:
return {
"content": [{"type": "text", "text": "Recalled 3 observations"}],
"_meta": {"observation_ids": ["obs_1", "obs_2", "obs_3"]}
}
Handlers receive:
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 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)
The Ask
Provide a return path from MCP tools to event handlers that does not enter model context.
MCP's
_metafield is designed for exactly this. Exposing it inToolResultBlockwould solve it.Problem
MCP tools often need to return two types of data:
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:
content_metaTo 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
This pattern must be repeated for every tool that needs handler-only data.
Why Not Just Use
content?We can put UI data in
contentand parse it in handlers. But:MCP designed
_metaspecifically for "hidden data for client applications only, not visible to the model."Proposed Solution
Expose
_metainToolResultBlock:Tools return:
Handlers receive:
"Recalled 3 observations"{"observation_ids": ["obs_1", "obs_2", "obs_3"]}Use Cases
_meta)Related
anthropics/claude-code#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
_meta