Add an option to add extra metadata that is pushed and passed to Guardrails during an MCP session (#47)

* use select() before readline

* support for setting static metadata for MCP sessions

* nest extra mcp metadata in metadata object

* unify session metadata

* extra metadata tests

* use empty object as parameters, if None

* list_tools as tool call

* offset indices in tests

* test: adjust addresses

* mcp: make error reporting configurable

* line logging

* log version

* verbose logging + loud exception failure

* add server and client name to policy get

* append trace even if not pushing

* port tools/list message support to SSE

* use python -m build

* adjust guardrail failure address

* support for blocking tools/list in SSE

* use error-based failure response format by default

* tools/list test

* don't list_tools in stdio connect

* flaky test: handle second possible result in anthropic streaming case

---------

Co-authored-by: knielsen404 <kristian@invariantlabs.ai>
This commit is contained in:
Luca Beurer-Kellnerandknielsen404 authored and GitHub committed 2025-05-19 13:44:37 +02:00
1 parent 4dbb400620
commit e18c6b5bdb
14 files changed
+1007 -104

No files matched your search

@@ -97,7 +97,13 @@ async def run(
"PUSH-INVARIANT-EXPLORER": str(push_to_explorer),
},
)
return await client.process_query(tool_name, tool_args)
# list tools
listed_tools = await client.session.list_tools()
# call tool
if tool_name == "tools/list":
return listed_tools
else:
return await client.process_query(tool_name, tool_args)
finally:
# Sleep for a while to allow the server to process the background tasks
# like pushing traces to the explorer
@@ -23,6 +23,7 @@ class MCPClient:
project_name: str,
server_script_path: str,
push_to_explorer: bool,
metadata_keys: Optional[dict[str, str]] = None,
):
"""
Connect to an MCP server.
@@ -42,6 +43,12 @@ class MCPClient:
"--project-name",
project_name,
]
# add metadata cli args
if metadata_keys is not None:
for key, value in metadata_keys.items():
args.append("--metadata-" + key + "=" + value)
if push_to_explorer:
args.append("--push-explorer")
args.extend(
@@ -54,6 +61,7 @@ class MCPClient:
os.path.basename(server_script_path),
]
)
server_params = StdioServerParameters(
command="uvx",
args=args,
@@ -73,6 +81,7 @@ class MCPClient:
)
)
# initialize the session
await self.session.initialize()
async def call_tool(
@@ -101,6 +110,7 @@ async def run(
push_to_explorer: bool,
tool_name: str,
tool_args: dict[str, Any],
metadata_keys: Optional[dict[str, str]] = None,
) -> types.CallToolResult:
"""
Main function to setup the MCP client and server.
@@ -123,7 +133,13 @@ async def run(
project_name,
server_script_path,
push_to_explorer,
metadata_keys=metadata_keys
)
return await client.call_tool(tool_name, tool_args)
listed_tools = await client.session.list_tools()
if tool_name == "tools/list":
# list tools
return listed_tools
else:
return await client.call_tool(tool_name, tool_args)
finally:
await client.cleanup()