Skip to main content
Glama
rce

lab-mcp

by rce

lab-mcp

Remote MCP servers for Claude Cowork, hosted on the homelab.

Cowork only attaches remote MCP servers, and it reaches them from Anthropic's cloud over public HTTPS, whichever client you use. So every server here is a small web service on salvage: an app entry in the homelab repo's provision.yml, which turns into a quadlet behind Caddy, image pulled from GHCR. Each server is also its own OAuth 2.1 authorization server for Claude, delegating the actual login to Google and refusing every Google account but the listed ones.

Layout

packages/mcp_common   settings, Google login with the email allowlist, serving
servers/hello         the dummy server: `ping` and `whoami`
tests                 pytest; run with `uv run pytest`
Containerfile         one image for all servers; CMD picks the server

One uv workspace. Add a server as servers/<name> with a console script, register it in the root pyproject.toml, and give it an app entry in homelab.

Related MCP server: Remote MCP Server

Configuration

Everything is an environment variable, and anything missing stops startup with a message naming what to set. On the homelab the quadlet sets the first two and injects the rest as podman secrets.

Variable

Meaning

BASE_URL

Public URL, e.g. https://hello-mcp.rce.fi. Also the OAuth issuer, so it must match what Caddy serves.

PORT

Listen port, default 8080.

MCP_GOOGLE_CLIENT_ID

Google OAuth client id.

MCP_GOOGLE_CLIENT_SECRET

Its secret.

MCP_JWT_SIGNING_KEY

High-entropy string. Signs the tokens handed to Claude and encrypts stored client registrations.

MCP_ALLOWED_EMAILS

Comma-separated Google accounts allowed in.

FASTMCP_HOME

Where FastMCP persists Claude's registrations and Google sessions. A volume in the container.

MCP_LISTEN_HOST

Default 0.0.0.0.

Claude is pointed at <BASE_URL>/mcp. /healthz answers ok without a token.

Google OAuth client

One client serves every server in this repo. In Google Cloud Console:

  1. Create a project, configure the consent screen as External and publish it. Only openid and email are requested, which needs no verification and, unlike Testing status, does not expire refresh tokens after seven days.

  2. Create an OAuth client of type Web application. Add one redirect URI per server: https://hello-mcp.rce.fi/auth/callback, and http://localhost:8080/auth/callback for local runs.

Login-only scopes are allowed under Advanced Protection and by Workspace admins. Gmail and Drive scopes are not, unless the admin trusts the app; that is a separate decision for the servers that need them.

Deploying

Secrets first, once, from the homelab checkout. Each prompts for the value:

./set-secret.sh hello-mcp MCP_GOOGLE_CLIENT_ID
./set-secret.sh hello-mcp MCP_GOOGLE_CLIENT_SECRET
./set-secret.sh hello-mcp MCP_ALLOWED_EMAILS
head -c 48 /dev/urandom | base64 -w0 | ./set-secret.sh hello-mcp MCP_JWT_SIGNING_KEY

Then ./run-playbook.sh provision.yml. After that, pushing to main here runs the tests, pushes ghcr.io/rce/lab-mcp:main, and podman auto-update on salvage picks it up within five minutes.

In Claude: Settings, Connectors, Add custom connector, URL https://hello-mcp.rce.fi/mcp. It discovers the OAuth endpoints, registers itself, sends you through Google, and whoami should answer with your account.

Running locally

export MCP_GOOGLE_CLIENT_ID=...apps.googleusercontent.com
export MCP_ALLOWED_EMAILS=you@example.com
printf '%s' 'GOCSPX-...' > .local/google_client_secret
./scripts/run-hello.sh

Cowork cannot reach localhost, so this is for the OAuth endpoints and for an MCP client run by hand, e.g. the MCP Inspector.

Related MCP Connectors

Related MCP Servers