Skip to main content
Glama
micahyee415
by micahyee415

google-workspace-admin-mcp

A create-only Google Workspace admin MCP server for employee onboarding — availability checks, account creation, and credentials delivery behind Google OAuth, a per-user allowlist, and keyless domain-wide delegation.

Overview

google-workspace-admin-mcp exposes a deliberately narrow slice of the Google Workspace Admin SDK as Model Context Protocol (MCP) tools, so an AI onboarding agent (e.g. Claude) can provision new-hire accounts without ever holding broad admin rights. The surface is create-only by design — there are no delete, suspend, or update tools — and temporary passwords are generated server-side and never appear in tool results or logs.

It is built with the official @modelcontextprotocol/sdk, runs as a stateless HTTP service on Cloud Run, restricts access to a configurable Google Workspace domain plus a per-user allowlist, and reaches the Admin SDK through keyless domain-wide delegation (no service-account key ever touches disk).

Related MCP server: VaultAssist

MCP Tools

Tool

Description

check_email_availability

Verifies a proposed address is free in both Google Workspace and the full Slack member history (including deactivated members). Fails closed: no definitive Slack answer, no availability verdict.

create_user

Creates the Workspace account in the configured organizational unit with a server-side temporary password. Refuses any address that has ever been used, per the never-reuse rule. Capped at 3 creations per day.

send_credentials_email

Resets the account to a fresh temporary password and emails the credentials to the hire's personal address ahead of day one.

The never-reuse rule

Google Workspace frees a deleted address after ~20 days, but Slack retains deactivated members forever — which makes Slack the durable source of truth for "has this address ever been used?". src/slackCheck.ts calls users.lookupByEmail with a bot token (users:read.email scope):

  • ok: true with user.deleted: true → the address was used before (burned)

  • users_not_found → genuinely free

Fail closed: a missing token, missing scope, rate limit, or any HTTP error throws — availability is never vouched for without a successful Slack check.

Architecture

MCP client (e.g. Claude)
        │  HTTPS + Bearer token (Google OAuth)
        ▼
  Cloud Run service  (google-workspace-admin-mcp)
  ┌─────────────────────────────────────────────┐
  │  Express HTTP server                         │
  │  ├── /health              — health check     │
  │  ├── /.well-known/...     — OAuth discovery  │
  │  ├── /register            — dynamic client   │
  │  │                          registration     │
  │  └── /mcp                 — MCP endpoint     │
  │       │                                      │
  │       ├── verifyGoogleToken()                │
  │       │     • @ALLOWED_DOMAIN enforcement    │
  │       │     • ALLOWED_USERS allowlist        │
  │       │                                      │
  │       ├── Zod input validation (schemas.ts)  │
  │       ├── Guards: target OU, daily create cap│
  │       └── Audit log on every tool call       │
  └─────────────────────────────────────────────┘
        │ keyless DWD (IAM Credentials signJwt)
        ▼
  Google Admin SDK + Gmail API        Slack Web API
  (create user, send credentials)     (never-reuse check)

Key design decisions:

  • Create-only surface — the server cannot delete, suspend, or modify existing accounts. The blast radius of a compromised client is limited to creating (capped, audited) accounts.

  • Keyless domain-wide delegation — the Cloud Run service account self-signs the delegation JWT via the IAM Credentials signJwt API. No service-account key exists on disk or in the container image. The connecting user's OAuth token only proves identity; it never touches Workspace APIs.

  • Server-side temporary passwords — generated in src/tempPassword.ts and delivered only via the credentials email. They never appear in MCP responses, logs, or error messages.

  • Fail-closed availability — the Slack never-reuse check throws on any non-definitive answer, so an outage can delay onboarding but never hand out a burned address.

  • Guard rails — new users land in a configurable organizational unit, creations are capped per day, and Zod schemas validate every tool input at the MCP boundary.

  • Rate limiting — 60 requests/min per user on the MCP endpoint; 512 KB JSON body limit.

  • Audit logging — every tool call is logged with user, action, and outcome (Cloud Logging on Cloud Run).

Tech Stack

Layer

Technology

Runtime

Node.js 22 (ESM)

Language

TypeScript 5

MCP SDK

@modelcontextprotocol/sdk

HTTP server

Express 5

Google APIs

googleapis + google-auth-library (keyless DWD)

Validation

Zod 4

Tests

node:test (unit tests for every module)

Container

Docker (multi-stage)

CI

GitHub Actions (CodeQL) + Google Cloud Build (npm audit gate)

Hosting

Google Cloud Run

Getting Started

Prerequisites

  • Node.js 22+

  • A Google Workspace domain with super-admin access (to configure domain-wide delegation)

  • A GCP project with Cloud Run enabled and a Google OAuth 2.0 Web Application client

  • A Slack app bot token with the users:read.email scope (for the never-reuse check)

Install

git clone https://github.com/micahyee415/google-workspace-admin-mcp
cd google-workspace-admin-mcp
npm install

Configuration

Copy .env.example to .env and fill in the values:

cp .env.example .env

Variable

Required

Description

ALLOWED_DOMAIN

Yes

Only verified Google accounts at this domain may connect (default: example.com)

ALLOWED_USERS

No

Per-user allowlist on top of the domain check (comma-separated emails). Unset = domain check only.

DWD_SERVICE_ACCOUNT

Yes

Service account that self-signs the delegation JWT (keyless, via signJwt)

GWS_ADMIN_SUBJECT

Yes

Workspace admin user the DWD service account impersonates

GOOGLE_CLIENT_ID

Yes

GCP OAuth 2.0 client ID (per-user sign-in)

GOOGLE_CLIENT_SECRET

Yes

GCP OAuth 2.0 client secret

SLACK_BOT_TOKEN

Yes

Slack bot token for the never-reuse check (from Secret Manager on Cloud Run)

TARGET_OU

No

Organizational unit new users are created into (default: /Employees)

COMPANY_NAME

No

Display name used in the credentials email (default: Example Corp)

CREDENTIALS_FROM

No

From: address for the credentials email (default: it@<ALLOWED_DOMAIN>)

PORT

No

HTTP port (default: 8080; Cloud Run sets this automatically)

Domain-wide delegation setup: in the Google Admin console, grant the DWD service account's client ID the scopes https://www.googleapis.com/auth/admin.directory.user and https://www.googleapis.com/auth/gmail.send. In GCP, grant the Cloud Run runtime service account roles/iam.serviceAccountTokenCreator on the DWD service account.

Send-as note: if CREDENTIALS_FROM is a group address (e.g. it@your-domain.com) rather than the impersonated admin's mailbox, configure a Gmail send-as alias for that address on the impersonated admin account first — Gmail silently rewrites the From: header otherwise.

Run locally

npm run build
npm start

Run the test suite:

npm test

Deploy to Cloud Run

gcloud builds submit --config cloudbuild.yaml --project your-gcp-project

Cloud Build will:

  1. Run npm audit at high severity — fails the build if high/critical CVEs are found.

  2. Build and push the Docker image.

  3. Deploy to Cloud Run via service.yaml (environment variables and the SLACK_BOT_TOKEN Secret Manager reference are declared there).

  4. Restore the public-invoker IAM binding — gcloud run services replace resets IAM on deploy (authentication is enforced in-app via Google OAuth).

Connecting an MCP Client

Point your MCP client at the Cloud Run service URL:

https://your-service.example.com/mcp

The server implements RFC 8414 OAuth authorization server metadata and RFC 7591 dynamic client registration, so MCP clients that support these standards can auto-discover OAuth settings.

When prompted, authenticate with a Google account in the ALLOWED_DOMAIN domain. If ALLOWED_USERS is set, your email must also be on the allowlist.

Security

  • Authentication — every request requires a valid Google OAuth bearer token; only verified @ALLOWED_DOMAIN accounts (and, if set, ALLOWED_USERS members) are accepted, with audience validation against the configured OAuth client.

  • Least privilege — create-only tool surface; delegation scopes limited to admin.directory.user + gmail.send; no service-account keys anywhere.

  • Secret hygiene — temporary passwords are never returned, logged, or embedded in errors; the Slack token lives in Secret Manager, not in the image or env files.

  • Guard rails — configurable target OU, daily creation cap, Zod validation at the MCP boundary, fail-closed availability checks, CRLF header-injection guard on personal emails.

  • CORS — only https://claude.ai and https://api.claude.ai are accepted as origins.

  • Dynamic client registration caveat/register (RFC 7591) returns the configured OAuth client credentials so MCP clients can complete the Google OAuth flow; Google's registered redirect-URI allowlist is the effective security boundary. Register only trusted callback URIs on the OAuth client, and treat the client as effectively public.

  • Rate limiting — 60 requests/min per user on the MCP endpoint.

  • Security headersX-Content-Type-Options: nosniff, X-Frame-Options: DENY, Strict-Transport-Security on all responses.

  • Audit logging — every tool call is logged with the calling user, action, and result.

  • Dependency scanningnpm audit --audit-level=high gates every Cloud Build deploy; Dependabot and CodeQL run on GitHub.

License

MIT

Related MCP Connectors

Related MCP Servers