AgentVault MCP Support (Proxy Pattern - v1.0.0)¶
Status: Defined & Implemented via Proxy Pattern
1. Introduction¶
The AgentVault Agent-to-Agent (A2A) protocol defines the core mechanisms for secure communication, task management, and event streaming between agents. However, many complex agent interactions require agents to execute external Tools (e.g., filesystem access, code execution, database queries, specialized APIs).
The Model Context Protocol (MCP) is a conceptual protocol designed for agent-tool interaction, typically using JSON-RPC 2.0 over HTTP(S). While direct client-library support for invoking MCP tools may evolve, AgentVault v1.0.0 provides a robust and validated solution for integrating MCP tools via the A2A Proxy Pattern.
Goal: To enable standardized, reusable, secure, and controlled access to external capabilities within AgentVault workflows without requiring the orchestrator or core agents to directly implement the MCP protocol.
2. The A2A Proxy Pattern¶
The recommended and validated pattern in AgentVault v1.0.0 for integrating MCP tools involves a dedicated MCP Tool Proxy Agent:
- MCP Tool Proxy Agent: An A2A-compliant agent (built using the
agentvault-server-sdk
) acts as a secure bridge between the A2A domain and the MCP domain. - MCP Tool Server: A separate service (potentially non-A2A compliant itself) that exposes specific tools (e.g.,
filesystem.readFile
,code.runPython
) via an MCP-compatible JSON-RPC 2.0 HTTP endpoint (typically/rpc
). - Orchestrator/Client: An A2A client (like the
agentvault-cli
, a LangGraph orchestrator, or a custom application using theagentvault
library) that needs to invoke an MCP tool.
Workflow:
- A2A Request to Proxy: The Orchestrator sends a standard A2A
tasks/send
request to the Proxy Agent's/a2a
endpoint. The crucial information about the desired MCP call is embedded within aDataPart
of the A2AMessage
. - Proxy Authentication & Routing: The Proxy Agent authenticates the Orchestrator using standard A2A mechanisms (API Key, OAuth2 via
KeyManager
). It then parses theDataPart
to identify the target MCP server and tool. - MCP Request from Proxy: The Proxy Agent looks up the target MCP Tool Server's URL (e.g., from its configuration) and sends a standard MCP JSON-RPC 2.0 request (e.g.,
{"method": "filesystem.readFile", "params": {...}}
) via HTTP POST to the Tool Server's/rpc
endpoint. - MCP Tool Execution: The MCP Tool Server executes the requested tool.
- MCP Response to Proxy: The Tool Server sends a JSON-RPC 2.0 response back to the Proxy Agent (containing either a
result
or anerror
, potentially using theisError: true
convention within theresult
for tool-level errors). - A2A Response from Proxy: The Proxy Agent translates the MCP response into a standard A2A format (e.g., placing the MCP
result
or error details within aDataPart
of an assistantMessage
orArtifact
) and sends it back to the Orchestrator via the A2A task's SSE stream or final result.
Diagram:
(Diagram showing Orchestrator communicating via A2A (HTTPS+Auth) to the MCP Tool Proxy Agent. The Proxy Agent then communicates via MCP (HTTPS/JSON-RPC) to an MCP Tool Server, receiving a response, and relaying the outcome back to the Orchestrator via A2A.)
3. A2A Message Structure (Client -> Proxy)¶
Instead of using message.metadata["mcp_context"]
, the client sends instructions to the proxy embedded within a DataPart
inside the standard A2A Message.parts
list.
Required DataPart.content
structure:
{
"target_mcp_server_id": "filesystem", // Logical ID mapped to URL in Proxy config
"tool_name": "filesystem.readFile", // The MCP method to call
"arguments": { // Parameters for the MCP method
"path": "/data/input.txt"
}
}
Example A2A Message sent by Orchestrator to Proxy Agent:
// This is the JSON-RPC 'params.message' field in the tasks/send request
{
"role": "user", // Or 'system' / 'assistant' depending on orchestrator
"parts": [
{
"type": "data",
"mediaType": "application/json", // Recommended
"content": {
"target_mcp_server_id": "code",
"tool_name": "code.runPython",
"arguments": {
"code": "print('Hello from proxied MCP call!')"
}
}
}
// Optionally include a TextPart for human context if needed
// { "type": "text", "content": "Execute the code specified in the data part."}
],
"metadata": null // metadata["mcp_context"] is NOT used for this pattern
}
4. Client-Side Usage (agentvault
Library)¶
To invoke an MCP tool via the proxy pattern using the client library:
- Load the Proxy Agent's
AgentCard
. - Instantiate
KeyManager
for the Proxy Agent's authentication. - Construct the A2A
Message
with the correctDataPart
payload (as shown above). - Use
AgentVaultClient.initiate_task
(orsend_message
) targeting the Proxy Agent. - Process the A2A events/response received from the Proxy Agent, which will contain the translated result or error from the underlying MCP call.
import asyncio
from agentvault import (
AgentVaultClient, KeyManager, Message, DataPart,
agent_card_utils, models as av_models, exceptions as av_exceptions
)
async def call_mcp_via_proxy(
proxy_agent_ref: str, # ID, URL, or file path for the *Proxy* Agent
target_mcp_server_id: str,
tool_name: str,
arguments: dict
):
key_manager = KeyManager(use_keyring=True) # Assuming proxy might need auth
proxy_card = None
task_id = None
try:
# Load the PROXY agent's card
proxy_card = await agent_card_utils.fetch_agent_card_from_url(proxy_agent_ref) # Example load
if not proxy_card: raise ValueError("Proxy agent card not found")
# Prepare the DataPart payload for the proxy
proxy_instruction_payload = {
"target_mcp_server_id": target_mcp_server_id,
"tool_name": tool_name,
"arguments": arguments
}
initial_message = Message(
role="user", # Or appropriate role
parts=[DataPart(content=proxy_instruction_payload)]
)
async with AgentVaultClient() as client:
print(f"Sending tool request to Proxy Agent: {proxy_card.human_readable_id}")
task_id = await client.initiate_task(
agent_card=proxy_card,
initial_message=initial_message,
key_manager=key_manager
)
print(f"Proxy Task initiated: {task_id}")
# Stream events FROM THE PROXY
async for event in client.receive_messages(proxy_card, task_id, key_manager):
if isinstance(event, av_models.TaskStatusUpdateEvent):
print(f" Proxy Task Status: {event.state}")
if event.state.is_terminal(): break # Proxy task finished
elif isinstance(event, av_models.TaskMessageEvent):
# The proxy wraps the MCP result/error in a message part
print(f" Proxy Response Message:")
for part in event.message.parts:
if isinstance(part, DataPart):
print(f" Data Result/Error from MCP: {part.content}")
# TODO: Parse part.content (which should match McpToolExecOutput)
# to check success/error and extract MCP result
else:
print(f" Other Part: {part}")
# Handle other events...
except av_exceptions.AgentVaultError as e:
print(f"AgentVault Error: {e}")
except Exception as e:
print(f"Unexpected Error: {e}")
# Example Call
# asyncio.run(call_mcp_via_proxy(
# proxy_agent_ref="http://localhost:8059/agent-card.json",
# target_mcp_server_id="filesystem",
# tool_name="filesystem.readFile",
# arguments={"path": "/data/test_script.py"}
# ))
5. Server-Side Usage (Implementing the Proxy Agent)¶
The MCP Tool Proxy Agent itself (built with agentvault-server-sdk
):
- Receives the A2A
Message
in itshandle_task_send
method. - Extracts the
target_mcp_server_id
,tool_name
, andarguments
from the incomingMessage.parts
(specifically looking for theDataPart
). - Looks up the target MCP server's base URL using the
target_mcp_server_id
(e.g., from an environment variable mapMCP_SERVER_MAP
). - Uses an HTTP client (like
httpx
) to make the JSON-RPC 2.0 POST request to the target MCP server's/rpc
endpoint. - Parses the JSON-RPC response from the MCP server.
- Uses its
TaskStore
(notify_message_event
ornotify_artifact_event
) to send the outcome back to the original A2A caller, wrapping the MCP result/error within aDataPart
.
(See the mcp-tool-proxy-agent/src/mcp_tool_proxy_agent/agent.py
file in the POC for a concrete implementation.)
6. Underlying MCP Protocol Details (Proxy <-> Tool Server)¶
The communication between the Proxy Agent and the MCP Tool Server uses JSON-RPC 2.0 over HTTPS POST, typically to an /rpc
endpoint on the tool server.
- Request: Standard JSON-RPC request where
method
is the tool name (e.g.,filesystem.readFile
) andparams
contains the arguments object. - Response (Success): JSON-RPC response where the
result
field contains the tool's output, often structured with acontent
array (e.g.,{"content": [{"type": "text", "text": "file data"}]}
). - Response (Tool Error): JSON-RPC response where the
result
field contains{"isError": true, "content": [{"type": "text", "text": "Error message"}]}
. - Response (Protocol Error): Standard JSON-RPC
error
object (e.g., for method not found, invalid params).
7. Future Considerations¶
While the A2A Proxy Pattern is robust and recommended for v1.0.0, future work could include:
- Adding direct MCP client support to the
agentvault
library for simpler scenarios where a proxy isn't desired (though this requires careful security consideration). - Further standardization of MCP tool schemas and discovery mechanisms.
- Refining the
mcp_utils
in the core library to align with official MCP specifications as they mature.