Skip to main content

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.

Starting here instead?

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:

  1. Credential isolation: API keys and tokens never enter the agent sandbox
  2. Policy enforcement: Every tool call is checked against your policies
  3. 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 mode and packages
  • --sandbox=gvisor / --sandbox=docker (default): The server runs inside AgentVisor's mcp-tools sandbox, which provides npx/uvx — no host-side Node.js needed.
  • Air-gapped / offline hosts: Use mcp.prepull in mav-agent-config.yaml to 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 — npx must be available on $PATH.
  • Centrally-hosted MCP servers: Use streamable_http to connect to an already-running server instead of spawning a local process:
    mcp:
    servers:
    - name: filesystem
    transport: streamable_http
    url: "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():

  1. Queries the host for available MCP servers
  2. Retrieves tool definitions from each server
  3. Converts them to LangChain StructuredTool instances

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:list and agentvisor:mcp:tool:call are checked per request
Fine-Grained Access

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 .
Running with --sandbox=none instead

--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.

Tool Names

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: