Skip to main content

Network isolation

Most MCP servers require network access to function properly, for example, to access APIs, download data, or communicate with other services. However, malicious or misconfigured servers can also exfiltrate sensitive data or download unwanted content.

When you run an MCP server in ToolHive, you can optionally enable network isolation. This feature restricts the MCP server's network access to only the resources you specify.

Enable network isolation

To enforce network access rules, use the --isolate-network flag when running the MCP server. Network rules can come from:

  • The MCP server's default registry permissions
  • A custom permission profile you create
thv run --isolate-network [--permission-profile </path/to/custom-profile.json>] <SERVER>
tip

You can combine file system and network permissions in the same permission profile. For details on creating profiles that include both types of permissions, see custom permissions.

When you enable network isolation, ToolHive creates a secure network architecture around your MCP server that includes several components working together to control network access.

Network architecture components

Along with the main MCP server container, ToolHive launches additional containers to manage network traffic:

  • An egress proxy container that filters outgoing network traffic
  • A DNS container that provides controlled domain name resolution
  • An ingress proxy container that handles incoming requests (only for MCP servers using SSE or Streamable HTTP transport; stdio MCP servers don't need this since they don't expose ports)

Network topology

ToolHive creates two separate networks in the container runtime:

  • A shared external network (toolhive-external) that connects to your host's network
  • An internal network (toolhive-<SERVER_NAME>-internal) for each MCP server that isolates it from external access

The MCP server container connects only to the internal network, while the proxy and DNS containers connect to both networks. This design ensures that all network traffic flows through controlled points, allowing ToolHive to enforce the access rules you specify in your permission profile.

The following diagrams show how network traffic flows through the isolation architecture for different transport types:

For MCP servers using stdio transport, the ToolHive proxy process communicates directly with the MCP server container through standard input and output. All outbound network requests from the MCP server flow through the egress proxy and DNS containers:

Important

Network isolation supports HTTP and HTTPS protocols. If your MCP server needs to use other protocols (like direct TCP connections for database access), run it without the --isolate-network flag and rely on the container's built-in isolation instead.

Example: Enable network isolation using registry defaults

Many MCP servers in the ToolHive registry have default permission profiles that allow access to the specific resources they need. For example, the atlassian MCP server has a default profile that allows access to Atlassian services.

First, check the registry to see the default permissions for the atlassian MCP server:

thv registry info atlassian

Look for the Permissions section in the output:

Permissions:
Network:
Allow Host: .atlassian.net, .atlassian.com
Allow Port: 443

To run the atlassian MCP server with these default permissions and enable network isolation, use the following command:

thv run --isolate-network atlassian

Example: Customize network access

The GitHub MCP server in the registry has a default profile that allows access to github.com, but you might need to customize it for a self-hosted GitHub Enterprise instance:

github-enterprise-profile.json
{
"network": {
"outbound": {
"insecure_allow_all": false,
"allow_host": ["github.example.com"],
"allow_port": [443]
}
}
}

Run the GitHub MCP server with this profile:

thv run --isolate-network --permission-profile ./github-enterprise-profile.json --secret github,target=GITHUB_PERSONAL_ACCESS_TOKEN github

Example: Combined network and file system permissions

Some MCP servers need both network restrictions and file system access. For example, the aws-diagram MCP server generates diagrams locally but doesn't need network access:

aws-diagram-profile.json
{
"write": ["/tmp/generated-diagrams"],
"network": {
"outbound": {
"insecure_allow_all": false,
"allow_host": [],
"allow_port": []
}
}
}

This profile:

  • Blocks all network access (equivalent to the none built-in profile)
  • Allows the server to write generated diagrams to /tmp/generated-diagrams

Run the server with this combined profile:

thv run --isolate-network --permission-profile ./aws-diagram-profile.json aws-diagram

Alternative: Using built-in profiles with volume mounts

You can achieve the same result without creating a custom profile file by using the built-in none profile and the --volume flag:

thv run --isolate-network --permission-profile none --volume /home/user/aws-diagrams:/tmp/generated-diagrams aws-diagram

This approach is more flexible since you can easily change the host directory without editing a profile file.

Accessing other workloads on the same container network

ToolHive allows you to configure both outbound and inbound network access for MCP servers. This is commonly needed when your MCP server needs to communicate with databases, APIs, or other services that are running on your local host during development, or when other containers need to communicate with your MCP server.

Outbound access: MCP server to other workloads

To allow an MCP server to access other workloads on the same network, you need to configure outbound network isolation to include the appropriate hostnames and ports.

For example, in Docker environments, you can add host.docker.internal to access services on the host. host.docker.internal is a special hostname provided by Docker that resolves to the host machine's IP address from within containers.

Create a permission profile that allows this hostname and the required port:

internal-access-profile.json
{
"network": {
"outbound": {
"insecure_allow_all": false,
"allow_host": ["host.docker.internal"],
"allow_port": [3000]
}
}
}

Run the MCP server with this profile:

thv run --isolate-network --permission-profile ./internal-access-profile.json <SERVER>

Inbound access: Other containers to MCP server

By default, the ingress proxy only allows traffic from the container's own hostname, localhost, and 127.0.0.1. If you need to allow other containers or workloads to communicate with your MCP server, configure the network.inbound.allow_host setting in your permission profile.

This is useful when:

  • Other containers need to call your MCP server's API
  • You're running multiple services that need to communicate with each other
  • You need to allow traffic from specific internal hostnames or domains

Create a permission profile that allows specific inbound hostnames:

inbound-access-profile.json
{
"network": {
"inbound": {
"allow_host": ["host.docker.internal", "localhost"]
}
}
}

Run the MCP server with this profile:

thv run --isolate-network --permission-profile ./inbound-access-profile.json <SERVER>
info

If no network.inbound configuration is specified, the ingress proxy uses the default behavior of allowing traffic only from the container's own hostname, localhost, and 127.0.0.1.

Next steps

Troubleshooting

Network connectivity issues

If your MCP server can't reach an external service when network isolation is enabled, the egress proxy is the source of truth for what's allowed and what gets denied.

  1. Confirm your profile lists the host and port the server is trying to reach. Network isolation only supports HTTP and HTTPS, so direct TCP/UDP connections (databases, custom protocols) won't work behind the egress proxy.

  2. Check the egress proxy logs for the blocked request. ToolHive launches the egress container as <SERVER_NAME>-egress:

    docker logs <SERVER_NAME>-egress

    Look for denied or 403-style entries that name the host and port the server attempted to reach.

  3. To prove it's a permissions issue and not a different failure, run the server temporarily with the built-in network profile, which allows all outbound HTTP/HTTPS:

    thv run --isolate-network --permission-profile network <SERVER>

    If the server works with this profile, copy the host and port from the denied entries in step 2 into your custom profile's network.outbound.allow_host and allow_port lists.