Self-host Google Workspace MCP
Run an independent Google Workspace MCP server for RetinueOS without the Developer Preview Program.
This guide configures an independently maintained Google Workspace Model Context Protocol (MCP) server for RetinueOS. The server uses generally available Google APIs, so it doesn't require Google's Workspace Developer Preview Program.
Understand the deployment boundary
Run the Workspace MCP container on the same host as RetinueOS, but expose it through a public HTTPS hostname. RetinueOS rejects private and loopback MCP URLs to prevent server-side request forgery (SSRF).
Use these controls:
- Bind the container port to host loopback
- Terminate Transport Layer Security (TLS) at a reverse proxy
- Require a random bearer token on
/mcp - Leave the Google OAuth callback reachable without the bearer token
- Store Google credentials and downloaded attachments in Docker volumes
- Pin the third-party image to a reviewed version or digest
This guide uses taylorwilsdon/google_workspace_mcp. Review its source, releases, and security policy before upgrading.
Configure Google Cloud
Create a Google Cloud project before starting the container:
- Enable each Google API you plan to expose, such as Gmail, Calendar, Drive, Docs, Sheets, Slides, Forms, Tasks, or People.
- Configure the OAuth consent screen and add your account as a test user when the app is in testing mode.
- Create an OAuth 2.0 client with the Web application type.
- Add
https://workspace-mcp.example.com/oauth2callbackas an authorized redirect URI. - Save the client ID and client secret outside the repository.
Replace workspace-mcp.example.com with your MCP hostname. The redirect URI must match the configured public URL exactly.
Configure the container
Create an unversioned environment file for the optional service:
GOOGLE_CLIENT_ID=your_google_oauth_client_id_here
GOOGLE_CLIENT_SECRET=your_google_oauth_client_secret_here
GOOGLE_USER_EMAIL=you@example.com
WORKSPACE_MCP_PUBLIC_URL=https://workspace-mcp.example.comCreate an unversioned Compose override beside your deployment configuration. This example grants Gmail draft access and write access to the other listed services. Reduce the permission levels to match your use case.
services:
workspace-mcp:
image: ghcr.io/taylorwilsdon/google_workspace_mcp:1.24.1@sha256:bce81e3ba82be53a1363b3f06f831678156f5fe69225d2fe3dd952db560cdf15
restart: unless-stopped
ports:
- "127.0.0.1:8001:8000"
environment:
GOOGLE_OAUTH_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOOGLE_OAUTH_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
USER_GOOGLE_EMAIL: ${GOOGLE_USER_EMAIL}
WORKSPACE_EXTERNAL_URL: ${WORKSPACE_MCP_PUBLIC_URL}
GOOGLE_OAUTH_REDIRECT_URI: ${WORKSPACE_MCP_PUBLIC_URL}/oauth2callback
WORKSPACE_MCP_CREDENTIALS_DIR: /credentials
WORKSPACE_ATTACHMENT_DIR: /attachments
WORKSPACE_MCP_HOST: 0.0.0.0
WORKSPACE_MCP_PORT: 8000
WORKSPACE_MCP_TOOL_TIER: complete
WORKSPACE_MCP_PERMISSIONS: >-
gmail:drafts calendar:full drive:full docs:full sheets:full
slides:full forms:full tasks:manage contacts:full
volumes:
- workspace_mcp_credentials:/credentials
- workspace_mcp_attachments:/attachments
security_opt:
- no-new-privileges:true
volumes:
workspace_mcp_credentials:
workspace_mcp_attachments:The image runs as UID and GID 1000. Initialize the new volumes before the first start so the container can write credentials and attachments:
docker compose \
-f docker-compose.yml \
-f docker-compose.workspace-mcp.yml \
--env-file .env.workspace-mcp \
run --rm --user 0:0 --entrypoint sh workspace-mcp \
-c 'chown 1000:1000 /credentials /attachments && chmod 700 /credentials && chmod 750 /attachments'Start the service with the same environment file that supplies the variables:
docker compose \
-f docker-compose.yml \
-f docker-compose.workspace-mcp.yml \
--env-file .env.workspace-mcp \
up -d workspace-mcpProtect the MCP endpoint with Caddy
Generate a bearer token and store it in /etc/caddy/workspace-mcp.env. Make the file readable by root and the Caddy service group, but not by other users. Don't commit this file.
WORKSPACE_MCP_BEARER_TOKEN=your_random_bearer_token_hereCreate /etc/systemd/system/caddy.service.d/workspace-mcp.conf so the Caddy service loads the file:
[Service]
EnvironmentFile=/etc/caddy/workspace-mcp.envReference the variable from the Caddyfile:
workspace-mcp.example.com {
@unauthorized {
path /mcp /mcp/*
not header Authorization "Bearer {$WORKSPACE_MCP_BEARER_TOKEN}"
}
respond @unauthorized "Unauthorized" 401
reverse_proxy localhost:8001
}The matcher protects MCP requests while allowing /oauth2callback. Bind port 8001 to loopback so clients can't bypass Caddy.
Reload the systemd configuration and Caddy after creating the environment file and drop-in:
sudo systemctl daemon-reload
sudo systemctl reload caddyConnect the server to RetinueOS
Add the server from Connections in RetinueOS:
- Set the URL to
https://workspace-mcp.example.com/mcp. - Select static bearer authentication.
- Enter the same bearer token configured for Caddy.
- Discover the tool catalog.
- Review and approve each tool's risk class.
- Assign approved tools to the personas that need them.
Keep start_google_auth available for initial OAuth authorization. Call it for the service you want to authorize, complete consent in the browser, then retry the original tool call. Repeat this flow if another service requests additional consent.
Verify the deployment
Check each security and functionality boundary:
curl --include https://workspace-mcp.example.com/health
curl --include https://workspace-mcp.example.com/mcp
curl --include \
-H "Authorization: Bearer your_random_bearer_token_here" \
https://workspace-mcp.example.com/mcpThe health request must succeed. The unauthenticated MCP request must return 401. An authenticated GET /mcp may return another 4xx status, such as 405 or 406, because MCP operations use POST with specific content negotiation. That response confirms the request passed Caddy's bearer check.
Complete Google OAuth, rediscover the catalog, and test one read-only tool from each enabled service. Test write tools only after confirming their RetinueOS risk classes and approval behavior.
Account for current limitations
The upstream server doesn't expose Google Keep. Google's Keep API targets managed Workspace domains and uses administrator-approved access patterns, so personal Keep accounts don't have equivalent support.
OpenAI strict schemas can't represent arbitrary object keys. RetinueOS converts those objects to closed schemas. Basic tools work, but advanced attachment objects, custom Calendar reminders, and raw Slides batch requests may require a typed compatibility adapter.
The server stores OAuth credentials as files in the credentials volume. Protect host access, back up the volume, and revoke the Google grant if the host or volume is compromised.