You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Is your feature request related to a problem? Please describe.
I would like to be able to pass metadata via the "tools/call" invocation in the call_tool method. This would afford the Caller to use both the pre-defined client_id value anticipated by the Context.client_id attribute, as well as any other Caller specific values.
Describe the solution you'd like
Here is how I have modified my local copy to allow this feature functionality
@000-000-000-000-000 - thank you for the link. My read on that issue is the discussion about the meta parameter in the context of the protocol specification. My issue is about the implementation of the specification in the python SDK "call_tool" method that would expose that feature of the specification.
but the _meta doesnt exist as a parameter for the call_tool method. so to get around this i just created a new class like:
frommcpimportClientSession, StdioServerParameters, typesclassCustomClientSession(ClientSession):
asyncdefcall_tool(
self,
name: str,
arguments: dict[str, Any] |None=None,
read_timeout_seconds: Any=None,
progress_callback: Any=None,
_meta: Dict[str, Any] |None=None,
) ->types.CallToolResult:
"""Send a tools/call request with optional progress callback and meta support."""# Properly create the Meta object if _meta is providedmeta_obj=types.RequestParams.Meta(**_meta) if_metaelseNone# Pass meta as the 'meta' argument (not '_meta')params=types.CallToolRequestParams(
name=name, arguments=arguments, _meta=meta_obj
)
# Build the request objectrequest_obj=types.ClientRequest(
types.CallToolRequest(
method="tools/call",
params=params,
)
)
# Print the outgoing JSON-RPC request for debuggingprint("Outgoing JSON-RPC request:")
print(json.dumps(request_obj.model_dump(mode="json", by_alias=True), indent=2))
returnawaitself.send_request(
request_obj,
types.CallToolResult,
request_read_timeout_seconds=read_timeout_seconds,
progress_callback=progress_callback,
)
asyncdefmain():
# Define server parametersserver_params=StdioServerParameters(
command="python", # The command to run your serverargs=["server.py"], # Arguments to the command
)
# Connect to the serverasyncwithstdio_client(server_params) as (read_stream, write_stream):
# Use our custom session classasyncwithCustomClientSession(read_stream, write_stream) assession:
# Initialize the connectionawaitsession.initialize()
# List available toolstools_result=awaitsession.list_tools()
print("Available tools:")
fortoolintools_result.tools:
print(f" - {tool.name}: {tool.description}")
result=awaitsession.call_tool(
"echo",
arguments={"name": "Hello, World!"},
_meta={"user_id": "1234567890"},
)
print(f"Stdio Echo: {result.content[0].text}")
if__name__=="__main__":
asyncio.run(main())
and in my server, used it like so:
defextract_meta_from_context(ctx: Context) ->dict:
""" Extract meta information from the MCP context. Args: ctx: The MCP context object Returns: dict: Dictionary containing custom meta data (excluding progressToken) """ifnothasattr(ctx, "request_context") orctx.request_contextisNone:
return {}
ifnothasattr(ctx.request_context, "meta") orctx.request_context.metaisNone:
return {}
# Convert meta object to dictionarymeta_dict= (
ctx.request_context.meta.model_dump()
ifhasattr(ctx.request_context.meta, "model_dump")
else {}
)
returnmeta_dict# Run the serverif__name__=="__main__":
# parser for the type of message transport channel to run the serverparser=argparse.ArgumentParser()
parser.add_argument(
"--transport",
type=str,
default="stdio",
choices=["stdio", "streamable-http", "websockets"],
)
parser.add_argument(
"--host",
type=str,
default="127.0.0.1",
help="Host to bind to (only used for SSE transport)",
)
parser.add_argument(
"--port",
type=int,
default=8050,
help="Port to bind to (only used for SSE transport)",
)
args=parser.parse_args()
# Create an MCP server with host and port from argumentsmcp=FastMCP(name="DemoServer", host=args.host, port=args.port)
# Simple tool@mcp.tool()defecho(name: str, ctx: Context) ->str:
"""Echos the input back to the client Args: name: The input to echo back """# Use stderr for debugging output (won't interfere with stdio communication)print(f"Context: {ctx}", file=sys.stderr)
print(f"Context type: {type(ctx)}", file=sys.stderr)
print(f"Context content: {ctx.request_context.meta}", file=sys.stderr)
# Use the helper function to get meta informationmeta=extract_meta_from_context(ctx)
# Return the echo with meta informationreturnf"{name}! ({meta})"# run the serverifargs.transport=="stdio":
mcp.run()
elifargs.transport=="streamable-http":
mcp.run(transport="streamable-http")
elifargs.transport=="websockets":
raiseNotImplementedError("Websockets are not supported yet")
else:
raiseValueError(f"Invalid transport: {args.transport}")
When i run the client.py i get the following output:
[06/22/25 09:35:45] INFO Processing request of type ListToolsRequest server.py:523
Available tools:
- echo: Echos the input back to the client
Args:
name: The input to echo back
Outgoing JSON-RPC request:
{
"method": "tools/call",
"params": {
"_meta": {
"progressToken": null,
"user_id": "1234567890"
},
"name": "echo",
"arguments": {
"name": "Hello, World!"
}
}
}
INFO Processing request of type CallToolRequest server.py:523
Context:
Context type: <class 'mcp.server.fastmcp.server.Context'>
Context content: progressToken=None user_id='1234567890'
Stdio Echo: Hello, World!! ({'progressToken': None, 'user_id': '1234567890'})
As you may notice, the _meta is making it to the server and we are able to get it in the server.
My only concern is that this is not supported by the tool_call method of the ClientSession, hence the CustomClientSession. its an easy fix. maybe the team will get to it eventually.
Activity
shiwenbin1617 commentedon May 6, 2025
mark
000-000-000-000-000 commentedon May 7, 2025
curious, is this not already addressed? For some reason this: modelcontextprotocol/modelcontextprotocol#414 made me think it was.
jeremyschulman commentedon May 7, 2025
@000-000-000-000-000 - thank you for the link. My read on that issue is the discussion about the meta parameter in the context of the protocol specification. My issue is about the implementation of the specification in the python SDK "call_tool" method that would expose that feature of the specification.
pyrotank41 commentedon Jun 22, 2025
I noticed this too @jeremyschulman , following is how I went about it:
Following is how i was calling it:
but the _meta doesnt exist as a parameter for the call_tool method. so to get around this i just created a new class like:
and in my server, used it like so:
When i run the client.py i get the following output:
As you may notice, the _meta is making it to the server and we are able to get it in the server.
My only concern is that this is not supported by the tool_call method of the ClientSession, hence the CustomClientSession. its an easy fix. maybe the team will get to it eventually.
thanks
pyrotank41 commentedon Jun 22, 2025
@jeremyschulman, i created a PR for this as well #1003
felixweinberger commentedon Oct 6, 2025
See closure in #1003, closing this associated issue.