MetaMCP MCP Aggregator on Ubuntu 24.04 on Azure User Guide
Overview
MetaMCP is an open-source aggregator, orchestrator, middleware and gateway for the Model Context Protocol (MCP) — the standard that lets AI assistants and agents call external tools. It groups many separate MCP servers into namespaces and exposes each namespace as one endpoint — reachable over the Streamable HTTP and SSE transports and as an OpenAPI REST surface — so a client connects once and reaches every underlying tool. A management web interface registers the downstream servers, composes them into namespaces, applies per-namespace tool middleware, and issues API keys that gate access to the aggregated endpoints. Instead of wiring every assistant to every server by hand, your agents reach your whole MCP tool estate through a single controlled, authenticated front door.
The cloudimg image runs MetaMCP 2.4.22 and PostgreSQL as a Docker Compose stack managed by systemd, fronts it with an nginx TLS reverse proxy, and generates unique secrets on the first boot of every VM. Because an aggregator with nothing behind it cannot be demonstrated, the image registers one real reference MCP server — the official time server — into a default namespace exposed by a public endpoint at first boot, so a working federated tool call is available the moment the VM is up. Backed by 24/7 cloudimg support.
What is included:
- MetaMCP 2.4.22 (a Next.js + Node backend) running under Docker Compose, published to loopback
127.0.0.1:12008 - PostgreSQL 16 in the same Compose stack on a private container network with no host port
- nginx TLS reverse proxy on
:443(a per-VM self-signed certificate;:80redirects to:443) - A bundled reference MCP time server (
uv tool run mcp-server-time), registered and aggregated at first boot into thedefaultnamespace behind thepublicendpoint - Per-VM administrator password, session authentication secret, database password and API key generated at first boot; the admin credential and API key are written to
/root/metamcp-credentials.txt - Public self-registration disabled after the single administrator is seeded
metamcp.service(the Compose stack) andnginx.serviceas systemd units, enabled and active- 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet + subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting point; scale up for larger tool estates or more concurrent clients. NSG inbound: allow 22/tcp from your management network and 443/tcp from the clients and agents that need the aggregator. MetaMCP is a credential-aggregation point (it holds access to every server it federates and issues API keys), so keep it on a private network or restrict 443/tcp to trusted sources.
Step 1 — Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for MetaMCP by cloudimg, and select Create. On Basics pick your subscription, resource group, region, and size (Standard_B2s or larger); under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTPS (443). Then Review + create → Create.
Step 2 — Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name metamcp \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name metamcp --port 443 --priority 1010
Step 3 — Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 — Confirm the services are running
The Compose stack (the MetaMCP app and PostgreSQL) and nginx come up automatically on first boot.
systemctl is-active docker metamcp nginx
ss -tln | grep -E ':443 |:80 |:12008 '
curl -sk -o /dev/null -w 'MetaMCP health: HTTP %{http_code}\n' https://127.0.0.1/health
The MetaMCP app (12008) is bound to loopback and PostgreSQL is only on the private Compose network — neither is reachable directly from the network; only nginx on :443 is exposed.

Step 5 — Retrieve the per-VM administrator credentials
Every VM generates its own administrator password, session authentication secret, database password and API key on first boot — there is no shared default login. The administrator credential and API key are written to a root-only file:
sudo cat /root/metamcp-credentials.txt
The file is mode 0600 root:root. Note the admin email, password and the METAMCP_API_KEY, then keep them somewhere safe.
Step 6 — Sign in to the management UI
Browse to https://<vm-public-ip>/. The image ships a self-signed TLS certificate, so your browser will warn on first visit (replace it with your own certificate — see Step 11). MetaMCP requires authentication, so you are presented with a sign-in page; enter the administrator email and password from Step 5. Public self-registration is disabled, so the sign-up form is not offered.

After signing in you land on the MCP Servers page. On a fresh VM the bundled localtime server is already registered (type STDIO, status No Error) — this is the reference server the image aggregates at first boot, and it is the proof that the aggregator works end to end.

Step 7 — The default namespace, aggregating the bundled server
Open MetaMCP Namespaces and open the default namespace. Its connection status is Connected, and MetaMCP has automatically discovered the tools the localtime server exposes (get_current_time and convert_time) and mapped them into the namespace. Any client that reaches the namespace's endpoint can now call those tools.

Step 8 — The public endpoint
Open MetaMCP Endpoints. The public endpoint maps to the default namespace and exposes it over three transports — SSE, Streamable HTTP and an OpenAPI REST surface — each secured with the API key. Point your MCP client at the Streamable HTTP or SSE URL, or drive the tools over plain REST via the OpenAPI surface.

Step 9 — Call a federated tool through the aggregated endpoint
The aggregated endpoint is authenticated with this VM's own API key. An unauthenticated request is refused; with the key, the OpenAPI surface lists the federated tools, and a POST invokes one through the aggregator:
KEY=$(sudo grep '^METAMCP_API_KEY=' /root/metamcp-credentials.txt | cut -d= -f2-)
curl -sk -o /dev/null -w 'no API key: HTTP %{http_code}\n' https://127.0.0.1/metamcp/public/api/openapi.json
curl -sk -H "X-API-Key: $KEY" https://127.0.0.1/metamcp/public/api/openapi.json \
| python3 -c 'import sys,json; print("federated tools:", [p.strip("/") for p in json.load(sys.stdin).get("paths",{})])'
Now invoke the bundled tool. The request goes to MetaMCP, which proxies it to the localtime server and returns the result — a real federated round-trip:
KEY=$(sudo grep '^METAMCP_API_KEY=' /root/metamcp-credentials.txt | cut -d= -f2-)
curl -sk -X POST https://127.0.0.1/metamcp/public/api/localtime__get_current_time \
-H "X-API-Key: $KEY" -H 'Content-Type: application/json' -d '{"timezone":"Europe/Dublin"}'
The response contains the current time for the requested timezone, returned by the bundled server via the aggregated endpoint.

Step 10 — Secure by default
There is no baked login: the administrator password, API key and session authentication secret are generated per VM at first boot, the credential file is 0600 root:root, public self-registration is disabled, and PostgreSQL is on the private Compose network only.
stat -c 'creds file: %n perms: %a owner: %U:%G' /root/metamcp-credentials.txt
curl -sk https://127.0.0.1/trpc/frontend.config.getSignupDisabled \
| python3 -c 'import sys,json; print("public signup disabled:", json.load(sys.stdin)["result"]["data"])'
ss -tln | grep -q ':5432 ' && echo 'PostgreSQL EXPOSED (unexpected)' || echo 'PostgreSQL not exposed on the host (good)'

Step 11 — Add your own MCP server, namespace and endpoint
Use the management UI to build out your tool estate. On MCP Servers, add a server — a STDIO command (for example npx -y @modelcontextprotocol/server-filesystem /data) or the SSE / Streamable HTTP URL of a remote MCP server. On MetaMCP Namespaces, create a namespace and attach the servers you want grouped together, optionally applying tool middleware. On MetaMCP Endpoints, expose the namespace as a new endpoint and choose its authentication. Finally, on API Keys, mint keys for the clients that will connect. Downstream STDIO servers are launched on the VM and may fetch their package from the internet on first use, so ensure the VM has outbound access.
Step 12 — Replace the self-signed certificate
The image ships a per-VM self-signed certificate so the UI is available over HTTPS immediately. For any real use, install a certificate for your VM's DNS name (for example from your internal CA or Let's Encrypt), and set APP_URL to that name so the browser origin is trusted:
sudo cp your-fullchain.pem /etc/ssl/metamcp/cert.pem
sudo cp your-private-key.pem /etc/ssl/metamcp/key.pem
sudo chmod 600 /etc/ssl/metamcp/key.pem
sudo sed -i 's#^APP_URL=.*#APP_URL=https://mcp.example.com#; s#^NEXT_PUBLIC_APP_URL=.*#NEXT_PUBLIC_APP_URL=https://mcp.example.com#' /opt/metamcp/.env
cd /opt/metamcp && sudo docker compose up -d && sudo systemctl reload nginx
Step 13 — Version, backup and maintenance
Confirm the running version:
grep -oE 'metamcp:[0-9.]+' /opt/metamcp/docker-compose.yml | head -1
MetaMCP's state (registered servers, namespaces, endpoints, users and API keys) lives in the PostgreSQL database inside the Compose stack. Back it up with pg_dump (the container reads its own credentials from the environment):
cd /opt/metamcp
sudo docker compose exec -T metamcp-pg sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' \
| sudo tee /var/backups/metamcp-$(date +%F).sql >/dev/null
The image keeps unattended security upgrades enabled, so the OS receives patches automatically. Configuration lives in /opt/metamcp/.env; apply changes with cd /opt/metamcp && sudo docker compose up -d.
Support
Every cloudimg deployment includes 24/7 support. If you have any questions about this image, contact us through cloudimg.co.uk.