Cookbook

Recipe 7: Self-host the MCP server for an internal AI agent

Run webfetch-server behind your VPN, expose it over HTTP to a team of agents, BYOK for provider auth.

You have an internal AI agent (an LLM + some tools) running in your infrastructure. You want it to fetch license-safe images, but you can't send any traffic to a third-party cloud (compliance, PII in queries, etc.). Self-host the MCP server on your own box.

#Minimal deployment — Docker Compose

# docker-compose.yml
services:
  webfetch:
    image: ghcr.io/ashlrai/webfetch:latest
    command: ["server", "--host", "0.0.0.0", "--no-open"]
    ports:
      - "127.0.0.1:7600:7600"
    environment:
      WEBFETCH_CACHE_DIR: "/var/cache/webfetch"
      WEBFETCH_ENABLE_BROWSER: "0"
      UNSPLASH_ACCESS_KEY: "${UNSPLASH_ACCESS_KEY}"
      PEXELS_API_KEY: "${PEXELS_API_KEY}"
      SPOTIFY_CLIENT_ID: "${SPOTIFY_CLIENT_ID}"
      SPOTIFY_CLIENT_SECRET: "${SPOTIFY_CLIENT_SECRET}"
    volumes:
      - webfetch-cache:/var/cache/webfetch
      - webfetch-home:/root/.webfetch
    restart: unless-stopped

volumes:
  webfetch-cache:
  webfetch-home:

Start:

docker compose up -d
TOKEN="$(docker compose exec -T webfetch cat /root/.webfetch/server.token)"
curl -sS http://127.0.0.1:7600/providers -H "authorization: Bearer $TOKEN"

#Connecting internal agents

If your agent can call HTTP tools directly, point it at the webfetch REST endpoint:

curl -sS -X POST http://webfetch.internal:7600/search \
  -H "authorization: Bearer INSERT_TOKEN_HERE" \
  -H "content-type: application/json" \
  -d '{"query":"architecture diagram","licensePolicy":"safe-only"}'

For MCP-native IDEs, run getwebfetch-mcp as a local stdio server in the IDE and give that process the same provider keys. The current MCP package does not proxy to a remote HTTP server.

npx -y getwebfetch-mcp

#Locking down the network

  • The server only needs outbound HTTPS to the configured providers (whitelist in your egress firewall: en.wikipedia.org, api.openverse.org, api.unsplash.com, api.pexels.com, etc.).
  • All inbound is scoped to your VPN.
  • Disable the browser layer unless you have a separate sandbox: WEBFETCH_ENABLE_BROWSER=0.

#Team pattern: one box, many agents

Deploy behind an ALB / Caddy / Nginx with per-team tokens:

webfetch.internal {
  @team-alpha header Authorization "Bearer alpha-token"
  @team-beta  header Authorization "Bearer beta-token"
  handle @team-alpha { reverse_proxy webfetch-alpha:7600 }
  handle @team-beta  { reverse_proxy webfetch-beta:7600 }
}

Separate deployments per team gives you separate caches and separate provider key sets. Cheaper than real multi-tenancy and keeps blast radius small.

#Observability

  • GET /health for liveness probes.
  • Every request emits a structured JSON log line ({timestamp, endpoint, duration_ms, providers_called, candidates, errors}).
  • Ship logs to your SIEM; alert on error_rate > 5% for 5+ minutes.

For full operational details, see Self-hosting.