Checks
Strands Version
1.30.0
Python Version
3.11.5
Operating System
macOS Tahoe 26.1
Installation Method
pip
Steps to Reproduce
Summary
Two related issues prevent _meta (per MCP spec) from reaching MCP servers when using Strands:
MCPClient never passes meta to ClientSession.call_tool() — there is no way for users to attach custom _meta to tool calls.
mcp_instrumentation.py corrupts _meta — it uses model_dump() instead of model_dump(by_alias=True), sending "meta" instead of "_meta" on the wire.
Steps to Reproduce
Part 1: _meta not forwarded
MCPClient._create_call_tool_coroutine calls:
# strands/tools/mcp/mcp_client.py
return await cast(ClientSession, self._background_thread_session).call_tool(
name, arguments, read_timeout_seconds
)
But ClientSession.call_tool supports a meta kwarg:
# mcp/client/session.py
async def call_tool(
self,
name: str,
arguments: dict[str, Any] | None = None,
read_timeout_seconds: timedelta | None = None,
progress_callback: ProgressFnT | None = None,
*,
meta: dict[str, Any] | None = None, # <-- never passed by Strands
) -> types.CallToolResult:
The meta kwarg is never forwarded, so custom _meta never reaches the server.
Part 2: Instrumentation corrupts _meta
In strands/tools/mcp/mcp_instrumentation.py, the patch_mcp_client function does:
params_dict = request.root.params.model_dump() # BUG: missing by_alias=True
meta = params_dict.setdefault("_meta", {}) # creates NEW key since "meta" != "_meta"
The MCP Pydantic model defines:
class CallToolRequestParams:
meta: Meta | None = Field(alias="_meta") # Python name is "meta", wire name is "_meta"
So model_dump() returns {"meta": {...}} (Python field name), not {"_meta": {...}} (wire format). Then setdefault("_meta", {}) finds no "_meta" key and creates a new empty one, resulting in:
{
"meta": {"progressToken": null, "com.example/request_id": "abc-123"},
"_meta": {}
}
Proof
from mcp.types import CallToolRequestParams
params = CallToolRequestParams(
name="echo",
arguments={"message": "hello"},
_meta={"com.example/request_id": "abc-123"},
)
# What strands instrumentation does:
print(params.model_dump())
# {'meta': {'progressToken': None, 'com.example/request_id': 'abc-123'}, ...}
# ^ Python field name "meta", NOT "_meta"
# What it should do:
print(params.model_dump(by_alias=True))
# {'_meta': {'progressToken': None, 'com.example/request_id': 'abc-123'}, ...}
# ^ Correct wire format "_meta"
Expected Behavior
Expected Behavior
MCPClient should expose a way to pass custom _meta through to ClientSession.call_tool(meta=...).
mcp_instrumentation.py should use model_dump(by_alias=True) so _meta is serialized correctly.
Actual Behavior
-
_meta is always None on the MCP server — When using Strands MCPClient to call MCP tools, the server
receives _meta: None regardless of any custom metadata the user intends to send. There is no API
surface on MCPClient to pass _meta through to ClientSession.call_tool().
-
When OpenTelemetry instrumentation is active, _meta is corrupted on the wire — The outbound JSON-RPC
payload contains both "meta" (with the original data) and "_meta" (empty {}), instead of a single
"_meta" with the correct values:
What gets sent on the wire:
"params": {
"meta": {"progressToken": null, "com.example/request_id": "abc-123"},
"_meta": {}
}
What should be sent:
"params": {
"_meta": {"progressToken": null, "com.example/request_id": "abc-123"}
}
Additional Context
No response
Possible Solution
Suggested Fix
Part 1 — Add meta parameter to call_tool_sync/call_tool_async and forward it:
# In _create_call_tool_coroutine
async def _call_tool_direct() -> MCPCallToolResult:
return await cast(ClientSession, self._background_thread_session).call_tool(
name, arguments, read_timeout_seconds, meta=meta # forward meta
)
Part 2 — One-line fix in mcp_instrumentation.py:
# Before (buggy)
params_dict = request.root.params.model_dump()
# After (fixed)
params_dict = request.root.params.model_dump(by_alias=True)
Related Issues
No response
Checks
Strands Version
1.30.0
Python Version
3.11.5
Operating System
macOS Tahoe 26.1
Installation Method
pip
Steps to Reproduce
Summary
Two related issues prevent
_meta(per MCP spec) from reaching MCP servers when using Strands:MCPClientnever passesmetatoClientSession.call_tool()— there is no way for users to attach custom_metato tool calls.mcp_instrumentation.pycorrupts_meta— it usesmodel_dump()instead ofmodel_dump(by_alias=True), sending"meta"instead of"_meta"on the wire.Steps to Reproduce
Part 1:
_metanot forwardedMCPClient._create_call_tool_coroutinecalls:But
ClientSession.call_toolsupports ametakwarg:The
metakwarg is never forwarded, so custom_metanever reaches the server.Part 2: Instrumentation corrupts
_metaIn
strands/tools/mcp/mcp_instrumentation.py, thepatch_mcp_clientfunction does:The MCP Pydantic model defines:
So
model_dump()returns{"meta": {...}}(Python field name), not{"_meta": {...}}(wire format). Thensetdefault("_meta", {})finds no"_meta"key and creates a new empty one, resulting in:{ "meta": {"progressToken": null, "com.example/request_id": "abc-123"}, "_meta": {} }Proof
Expected Behavior
Expected Behavior
MCPClientshould expose a way to pass custom_metathrough toClientSession.call_tool(meta=...).mcp_instrumentation.pyshould usemodel_dump(by_alias=True)so_metais serialized correctly.Actual Behavior
_meta is always None on the MCP server — When using Strands MCPClient to call MCP tools, the server
receives _meta: None regardless of any custom metadata the user intends to send. There is no API
surface on MCPClient to pass _meta through to ClientSession.call_tool().
When OpenTelemetry instrumentation is active, _meta is corrupted on the wire — The outbound JSON-RPC
payload contains both "meta" (with the original data) and "_meta" (empty {}), instead of a single
"_meta" with the correct values:
What gets sent on the wire:
"params": {
"meta": {"progressToken": null, "com.example/request_id": "abc-123"},
"_meta": {}
}
What should be sent:
"params": {
"_meta": {"progressToken": null, "com.example/request_id": "abc-123"}
}
Additional Context
No response
Possible Solution
Suggested Fix
Part 1 — Add
metaparameter tocall_tool_sync/call_tool_asyncand forward it:Part 2 — One-line fix in
mcp_instrumentation.py:Related Issues
No response