Connecting External Tools (MCP)
So far your agents have been limited to what they can do with LLM calls alone. MCP (Model Context Protocol) extends your agent's capabilities by connecting it to external tools and data sources — file systems, APIs, databases, and more with AgentVisor™.
What You'll Learn
- Configuring MCP servers in
agentvisor.yaml - Loading MCP tools in your agent code
- Adding policies to control MCP tool access
- Testing tool integration end-to-end
Prerequisites
This chapter continues in the chatbot-agent project from the previous chapters, which now has the fetch_url tool and its policy from Adding Policies. Ensure Temporal and Ollama are still running — they were started with docker compose up -d inside chatbot-agent back in Your First Agent and run detached, so they're still up unless you've since run docker compose down there. If you need to restart them, cd chatbot-agent and run docker compose up -d.
The MCP filesystem server needs Node.js — but only if you run with --sandbox=none (see the sandbox mode note in Configuring an MCP Server below). With the default Docker/gVisor sandbox, AgentVisor provides Node.js inside its own mcp-tools sandbox, so no host-side install is required.
agentvisor template create langgraph/tutorial-agent scaffolds the project as it stands at the end of this chapter, MCP server and policies included.
How MCP Works in AgentVisor
Unlike traditional MCP integrations where agents connect directly to MCP servers, AgentVisor places MCP servers on the host side. This provides critical security benefits:
- Credential isolation: API keys and tokens never enter the agent sandbox
- Policy enforcement: Every tool call is checked against your policies
- Automatic discovery: Agents discover tools without hardcoded configuration
Agent (sandbox) Host Runtime
┌──────────────┐ ┌─────────────────────────┐
│ get_mcp_tools│────gRPC────▶│ Policy check ──▶ MCP │
│ call_tool │◀────────────│ servers │
└──────────────┘ └─────────────────────────┘
The agent calls get_mcp_tools() and receives tools it's authorized to use. When calling a tool, the request flows through the host runtime where policies are enforced before reaching the MCP server.
Configuring an MCP Server
MCP servers are declared in the runtime config, not in agent code. Add an mcp: section to the agentvisor.yaml already in chatbot-agent (it holds the proxy.ssrf_allowed_cidrs entries that let the agent reach Ollama on loopback — leave those in place):
agentvisor.yaml
proxy:
ssrf_allowed_cidrs:
- "127.0.0.1/32"
- "::1/128"
mcp:
servers:
- name: filesystem
transport: stdio
command: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp/mcp-data"]
This configuration tells AgentVisor to launch the MCP filesystem server as a sandboxed subprocess. With --sandbox=gvisor or --sandbox=docker the server runs in an isolated mcp-tools container (node/npx pre-installed), so no local Node.js installation is required on the host.
--sandbox=gvisor/--sandbox=docker(default): The server runs inside AgentVisor'smcp-toolssandbox, which providesnpx/uvx— no host-side Node.js needed.- Air-gapped / offline hosts: Use
mcp.prepullinmav-agent-config.yamlto pre-install packages into the sandbox image so no outbound network access is needed at runtime. See Pre-installing Packages (prepull). --sandbox=none(development only): The server runs as a host subprocess without isolation —npxmust be available on$PATH.- Centrally-hosted MCP servers: Use
streamable_httpto connect to an already-running server instead of spawning a local process:mcp:servers:- name: filesystemtransport: streamable_httpurl: "http://mcp-filesystem-server:8080"
See MCP Gateway Guide: Sandboxed Execution of stdio Servers and Configuration Reference: MCP for all transport options.
Prepare some test data:
mkdir -p /tmp/mcp-data
echo 'Hello from MCP!' > /tmp/mcp-data/readme.txt
echo '{"version": "1.0"}' > /tmp/mcp-data/config.json
Using MCP Tools in Agent Code
Your agent needs two small changes in agent.py. Import the helper:
from agentvisor.langchain import get_mcp_tools
and replace the tools = [fetch_url] line with:
# Load MCP tools from the host's configuration, next to the local fetch tool
tools = [fetch_url, *get_mcp_tools()]
Nothing else changes: chat already binds tools to the model and the tools node already runs whichever tool the model picks. get_mcp_tools():
- Queries the host for available MCP servers
- Retrieves tool definitions from each server
- Converts them to LangChain
StructuredToolinstances
No tool definitions in your agent code — MCP tools are discovered at startup from the host's configuration, alongside fetch_url, which is defined locally. If no MCP servers are configured, get_mcp_tools() returns an empty list.
langgraph.json and requirements.txt are unchanged.
Adding MCP Policies
MCP tool access follows the same policy model as HTTP requests. Tools are identified by MRN (Manetu Resource Name):
mrn:agentvisor:mcp:<server>/<tool>
Examples:
mrn:agentvisor:mcp:filesystem/read_file
mrn:agentvisor:mcp:filesystem/list_directory
policies/domain.yml
Your policies/domain.yml already routes Ollama and httpbin.org, and denies anything else by default — so right now every MCP call is denied too. Add an MCP resource group under resource-groups: (above the default group):
- mrn: "mrn:agentvisor:resourcegroup:mcp"
name: mcp
description: "MCP tool access"
policy: *policy-allow
and route MCP resources to it under resources::
- name: mcp-tools
description: "All MCP tools and resources"
selector:
- "mrn:agentvisor:mcp:.*"
group: "mrn:agentvisor:resourcegroup:mcp"
*policy-allow is the same allow-authenticated policy the other AgentVisor resource groups use.
The key MCP policy elements:
- Resource selector
mrn:agentvisor:mcp:.*matches all MCP tools and resources - Operations like
agentvisor:mcp:tool:listandagentvisor:mcp:tool:callare checked per request
For production, restrict access per server or tool:
resources:
# Allow read operations only
- name: mcp-readonly
selector:
- "mrn:agentvisor:mcp:filesystem/read_file"
- "mrn:agentvisor:mcp:filesystem/list_directory"
group: "mrn:agentvisor:resourcegroup:mcp"
# Block write operations
- name: mcp-blocked
selector:
- "mrn:agentvisor:mcp:filesystem/write_file"
- "mrn:agentvisor:mcp:filesystem/delete_file"
group: "mrn:agentvisor:resourcegroup:default"
Testing Your MCP Integration
Start the agent:
agentvisor serve .
--sandbox=none runs the guest without container isolation and requires a local Python environment with the AgentVisor SDK installed, plus Node.js on $PATH for the filesystem server — see Local Development Setup. The default sandbox mode used above needs neither.
In another terminal, test MCP tool usage:
# Create a thread
THREAD=$(curl -sX POST http://localhost:8090/threads | jq -r '.thread_id')
# Ask the agent to list files
curl -sX POST "http://localhost:8090/threads/$THREAD/runs?wait=60s" \
-H "Content-Type: application/json" \
-d '{"input": {"messages": [{"role": "user", "content": "List the files in the data directory"}]}}' \
| jq '.output.messages[-1].content'
# Ask the agent to read a file
curl -sX POST "http://localhost:8090/threads/$THREAD/runs?wait=60s" \
-H "Content-Type: application/json" \
-d '{"input": {"messages": [{"role": "user", "content": "Read the readme.txt file"}]}}' \
| jq '.output.messages[-1].content'
You should see the agent use the filesystem tools to list and read files. Because a small local model chooses its own tool calls, confirm which tools actually ran. A run's output carries the thread's whole message history, so ask once more and list the tool messages:
curl -sX POST "http://localhost:8090/threads/$THREAD/runs?wait=60s" \
-H "Content-Type: application/json" \
-d '{"input": {"messages": [{"role": "user", "content": "What tools do you have?"}]}}' \
| jq '[.output.messages[] | select(.type == "tool") | .name]'
You should see names such as filesystem__list_directory and filesystem__read_file. The fetch_url tool from the previous chapter is still available alongside them — try fetch https://httpbin.org/get in the same thread.
MCP tools are prefixed with the server name to avoid conflicts: filesystem__read_file, filesystem__list_directory. The LLM sees these as distinct tools and calls them by their full names.
Next Steps
You've learned how to extend your agent with external tools via MCP. Continue exploring:
- MCP Integration — Architecture, security model, credential injection
- MCP SDK Reference — Complete API documentation
- MCP Agent Example — Full working example with ReAct pattern
- Deploying Your Agent — Build images and deploy to production