MCP Automation Service
Allows an LLM to search, read, and send emails via the Gmail API.
Allows an LLM to manage calendar events and schedules.
Allows an LLM to manage files and folders on Google Drive.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@MCP Automation Servicesummarise last week's invoice emails"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MCP Automation Service
The Google Workspace brain behind SABAH.OS. It holds each employee's corporate Google credential, reads their entire Workspace on demand, and drives long LLM ⇄ tool conversations over the Model Context Protocol (MCP).
This is an internal, corporate deployment: every user is an employee signing in with a company Google account, so a single consent screen requests the full Workspace scope set — Gmail, Drive, Calendar, Docs, Sheets, Slides, Contacts, Tasks, Forms, Chat and Meet — and the platform can then read everything that employee owns.
In one sentence: an employee clicks Continue with Google in SABAH.OS once, and from then on can ask "summarise every email and document about the Q3 budget" — asynchronously, auditably, and with tokens encrypted at rest.
Table of Contents
Related MCP server: Google Workspace MCP Server
How it fits with SABAH.OS
Three services, one flow:
Service | Owns | Talks to |
SABAH.OS frontend (Next.js) | The UI, including Continue with Google and the Integrations page | Django |
SABAH.OS backend (Django) | User identity, registration, permissions, the Google OAuth exchange | This service, over |
MCP Automation Service (FastAPI, this repo) | Google credentials, Workspace data access, the LLM ⇄ MCP loop | Google APIs, Google MCP servers, OpenAI |
Django is the authority on who may sign in. This service is the authority on what their Google account can do. Neither trusts the browser.
sequenceDiagram
participant U as Employee
participant F as Next.js
participant D as Django
participant M as MCP service
participant G as Google
U->>F: Continue with Google
F->>D: GET /api/v1/auth/google/url/
D-->>U: redirect to Google consent
U->>G: approve (full Workspace scopes)
G->>D: callback with code
D->>G: exchange code → access + refresh token
D->>D: is this a registered SABAH.OS user?
Note over D: unknown email → not_registered, no account is created
D->>M: POST /internal/google/credentials (tokens)
D-->>F: redirect with one-time code
F->>D: POST /api/v1/auth/google/exchange/ → JWT pair
U->>F: "summarise Q3 budget emails"
F->>D: POST /api/v1/integrations/mcp/run/
D->>M: POST /internal/automation/run
M->>G: LLM drives MCP tools across Gmail/Drive/Calendar…Sign-in never provisions. An employee who has not registered in SABAH.OS is
turned away with not_registered and sent to the registration page — role,
department and permissions are assigned deliberately, not inferred from a Google
account. Once registered, Google sign-in skips the OTP step: Google has already
verified the identity, so a second factor by email adds friction, not security.
Architecture
Four containerised services. The API never blocks on an LLM call — it validates, persists a job, and hands off to a Celery worker, which drives the LLM ⇄ MCP loop and writes results back to PostgreSQL.
flowchart LR
U[SABAH.OS<br/>Django + Next.js]
subgraph Docker Compose
API[FastAPI API<br/>REST + internal API + MCP/SSE server]
W[Celery Worker<br/>LLM ⇄ MCP loop]
R[(Redis<br/>broker)]
P[(PostgreSQL<br/>credentials + audit log)]
end
LLM[OpenAI<br/>Responses API]
G[Google Workspace<br/>REST APIs + MCP servers]
U -- X-Internal-Key --> API
API -- enqueue job --> R
API -- persist --> P
R -- deliver task --> W
W -- prompt + MCP tools --> LLM
LLM -- calls tools --> G
API -- direct reads --> G
W -- write result --> PTwo directions of MCP
MCP client (consume): the LLM connects out to Google's MCP servers to use Workspace services as tools.
MCP server (expose): this service exposes its own data and the signed-in employee's whole Workspace at
/mcp/, so other AI agents can query it —google_workspace_snapshotandgoogle_readare MCP tools, not just REST routes.
Three ways to reach Google data
Path | Route | Use when |
Direct read |
| You know exactly what you want. No LLM cost, millisecond latency. |
Passthrough |
| Google shipped an endpoint this service doesn't wrap yet. Host-allowlisted. |
LLM automation |
| The question needs reasoning across services. Optional — see below. |
The LLM is optional.
OPENAI_API_KEYis the only reason this service talks to a model provider, and it powers exactly one endpoint. Leave it empty andPOST /automation/runreturns503; direct reads, passthrough, OAuth and the MCP server this app exposes are all unaffected. Theopenaipackage is likewise not inrequirements.txt— install it only when enabling the feature.Worth knowing before you enable it: the loop uses OpenAI's remote MCP tools, so the employee's live Google access token is sent to OpenAI and Workspace content is read through their infrastructure.
Request Lifecycle
Accept —
POST /automation/runvalidates the JWT, resolves the target MCP servers (or all enabled Google servers), writes anAutomationRunwithstatus=pending, and returns202 Acceptedimmediately.Enqueue — the API dispatches a Celery task carrying only the
run_id.Authorise — the worker loads the
GoogleCredential, decrypts it, and refreshes the access token if it expires within 5 minutes.Orchestrate — the worker calls the OpenAI Responses API with the MCP servers as
tools. OpenAI's infrastructure connects to them and invokes the tools.Persist — output and every tool call are written to the run row.
Poll — the client reads
GET /automation/run/{id}, scoped to its own user.
Why This Design
Decision | Rationale |
Django decides identity, this service decides capability | One system owns the employee record; one owns the Google grant. Neither has to re-implement the other. |
Authorization-code flow, not one-tap ID tokens | Only the code flow returns a refresh token. Without one, Workspace access would die an hour after sign-in. |
Full Workspace scope in one consent | Internal deployment. Incremental per-service consent would mean an employee hitting a Google screen every time a new feature touches a new service. |
Corporate domain gate in both services | Django checks it, this service checks it again. A misconfiguration on one side cannot open the platform to arbitrary Google accounts. |
Async work in Celery, not request handlers | An LLM ⇄ tool loop can take minutes; running it inline would exhaust web workers and time out clients. |
Pass | SQLAlchemy objects aren't JSON-serialisable and go stale across process boundaries. |
Tokens encrypted with Fernet | A database leak must not hand anyone a working corporate refresh token. |
Permanent failures don't retry | A revoked Google grant fails identically on every attempt; retrying it three times just delays the error the user needs to see. |
MCP tools scoped by a context variable | MCP has no per-call identity. The JWT subject validated at the SSE handshake is what every tool filters on — a tool argument never decides whose data is read. |
JWT validated before the SSE handshake | The MCP stream is a FastAPI route (not |
| Long tasks are re-queued (not lost) if a worker crashes. |
Technology Stack
Layer | Choice |
Web framework | FastAPI + Uvicorn (async ASGI) |
LLM orchestration | OpenAI Responses API (remote MCP tools) — optional, off by default |
Protocol | Model Context Protocol ( |
Background jobs | Celery + Redis |
Database | PostgreSQL + SQLAlchemy 2.0 (async) + Alembic |
Auth | Google OAuth2 ( |
Encryption | Fernet ( |
Config | pydantic-settings (12-factor |
Packaging | Docker (multi-stage) + Docker Compose |
Data Model
erDiagram
GoogleCredential {
int id PK
string user_id UK "Google subject id"
string sabah_user_id "SABAH.OS user"
string google_account_email
string domain
text access_token "Fernet-encrypted"
text refresh_token "Fernet-encrypted"
datetime token_expiry
json scopes
json services
bool revoked
}
MCPServer {
int id PK
string name UK
string provider "google | custom"
string service "gmail | drive | …"
enum transport "http | stdio"
text url
string auth_type
bool enabled
}
AutomationRun {
int id PK
string user_id
string sabah_user_id
int mcp_server_id FK "null when spanning many"
json server_names
string tool_name
json input_payload
json output_payload
enum status "pending | success | error"
datetime started_at
datetime finished_at
}
MCPServer ||--o{ AutomationRun : "executes"GoogleCredential— one row per Google account, encrypted, auto-refreshed, linked to a SABAH.OS user.MCPServer— registry of MCP servers. Google's are seeded automatically on first boot; disabling or repointing one survives restarts.AutomationRun— append-only audit log of every AI action.
Security Model
Corporate gate —
ALLOWED_EMAIL_DOMAINSis enforced at OAuth callback and at credential push. Production refuses to boot with an empty list.Encryption at rest — access and refresh tokens are Fernet-encrypted.
Production config validation — the app will not start in production with a placeholder
SECRET_KEY, a missingFERNET_KEY/INTERNAL_API_KEY, a plaintextPUBLIC_BASE_URL, or"*"inALLOWED_ORIGINS.Signed, single-purpose OAuth state — the
stateis a short-lived signed token carrying the redirect target; a tampered or expired one is rejected.Internal API —
/internal/*needsX-Internal-Key(constant-time compare), is excluded from the OpenAPI schema, and should also be IP-restricted at nginx.Host allowlist for passthrough — the generic Google proxy can only reach
*.googleapis.comhosts on an explicit list; a corporate token can never be sent elsewhere.User isolation — run endpoints enforce ownership, on the internal API too.
No docs in production —
/docs,/redocand/openapi.jsonare disabled whenENVIRONMENT=production.Prompt-injection awareness — MCP tools fetch external content; write-capable scopes should be granted deliberately.
Getting Started
Prerequisites
Docker + Docker Compose v2
A Google Cloud project in the company Workspace organisation
An OpenAI API key
1. Google Cloud setup
In Google Cloud Console, create a project inside the company organisation.
Enable the APIs you intend to read: Gmail, Drive, Calendar, Docs, Sheets, Slides, People, Tasks, Forms, Chat, Meet.
Configure the OAuth consent screen as Internal — this is what limits sign-in to the company Workspace and removes Google's verification requirement for sensitive scopes.
Create an OAuth 2.0 Client ID (Web application). Add both redirect URIs:
https://mcp.your-domain.com/auth/google/callback(this service)https://api.your-domain.com/api/v1/auth/google/callback/(SABAH.OS Django)
2. Configure environment
cp .env.example .envGenerate the secrets:
openssl rand -hex 32 # SECRET_KEY
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" # FERNET_KEY
python -c "from app.core.config import generate_internal_key as g; print(g())" # INTERNAL_API_KEYThen set GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and ALLOWED_EMAIL_DOMAINS.
OPENAI_API_KEY stays empty unless you want POST /automation/run — enabling it
also needs pip install "openai>=1.68.0,<2.0.0".
INTERNAL_API_KEYhere must equalMCP_INTERNAL_API_KEYin the SABAH.OS Django.env.
3. Launch
docker compose up --build # postgres, redis, api, workerMigrations run automatically (RUN_MIGRATIONS=true on the api container), and the
Google MCP server registry is seeded on first boot.
4. Connect a Google account
Open http://localhost:8000/auth/google/login, approve, and the browser lands on
FRONTEND_REDIRECT_URL with a JWT. In normal operation SABAH.OS drives this — you
only need it to test the service standalone.
5. Read data
curl http://localhost:8000/google/snapshot -H "Authorization: Bearer <jwt>"
curl -X POST http://localhost:8000/automation/run \
-H "Authorization: Bearer <jwt>" -H "Content-Type: application/json" \
-d '{"instructions": "Summarise last week'\''s invoice emails"}'
# → 202 { "run_id": 1, "status": "pending" }Deploying on the company server
git clone <this repo> /opt/mcp && cd /opt/mcp
cp .env.example .env # fill in real secrets, ENVIRONMENT=production
docker compose -f docker-compose.prod.yml up -d --build
docker compose -f docker-compose.prod.yml logs -f apiThen put nginx in front:
sudo cp deploy/nginx.conf /etc/nginx/sites-available/mcp
sudo ln -s /etc/nginx/sites-available/mcp /etc/nginx/sites-enabled/mcp
sudo certbot --nginx -d mcp.your-domain.com
sudo nginx -t && sudo systemctl reload nginxWhat the production stack changes:
Postgres and Redis are not published to the host — compose network only.
The API binds to
127.0.0.1:8000; nginx terminates TLS./internal/is IP-restricted in nginx in addition to theX-Internal-Keycheck.Log rotation (10 MB × 3) and
restart: alwayson every service.One uvicorn worker per api container.
Scaling: MCP SSE sessions live in the API process's memory. To run more than one
API container, add each to the ip_hash upstream in deploy/nginx.conf so a client
always reaches the node holding its session.
Health: GET /health is liveness, GET /ready also checks the database.
Backups: the only irreplaceable state is Postgres (pgdata). A lost FERNET_KEY
makes every stored credential unreadable — back it up separately from the database,
or every employee must reconnect.
API Reference
Interactive docs (non-production only): http://localhost:8000/docs
Public / user-facing (JWT)
Method | Path | Description |
GET |
| Liveness / readiness |
GET |
| Consent URL |
GET |
| Begin OAuth (redirect) |
GET |
| OAuth callback → redirects to the frontend |
GET |
| Is this user's Google account connected? |
POST |
| Revoke at Google and locally |
GET |
| Services this deployment requests |
GET |
| Every readable resource name |
POST |
| Read one named resource |
GET |
| Everything at once, bounded |
GET |
| Google profile |
GET |
| Mail search |
GET |
| File search |
GET |
| Events |
GET |
| Contacts or company directory |
GET |
| Task lists / tasks |
POST |
| Authorised call to any allowlisted Google host |
GET |
| Registered MCP servers |
POST |
| Queue an LLM ⇄ MCP run ( |
GET |
| Poll a run |
GET |
| List your runs |
GET |
| MCP server stream (SSE) |
POST |
| MCP message relay |
Internal (SABAH.OS Django only — X-Internal-Key)
Method | Path | Description |
POST |
| Push a user's Google tokens |
GET |
| Connection status |
DELETE |
| Disconnect |
GET |
| Consent URL bound to a SABAH.OS user |
GET |
| Service catalogue |
POST |
| Read a resource for a user |
POST |
| Queue a run for a user |
GET |
| List a user's runs |
GET |
| Poll a run |
GET |
| Dependency check |
Resource names accepted by /google/read and /internal/google/read:
profile, snapshot, gmail.messages, gmail.labels, gmail.send, drive.files,
drive.file, drive.export, calendar.list, calendar.events, docs.document,
sheets.values, sheets.metadata, slides.presentation, contacts.list,
contacts.directory, tasks.lists, tasks.items, forms.responses, chat.spaces,
chat.messages, meet.records.
Exposed MCP tools: get_automation_runs, get_mcp_servers, get_run_detail,
google_workspace_snapshot, google_read.
Testing
python3.12 -m venv .venv && .venv/bin/pip install -r requirements.txt aiosqlite
.venv/bin/python -m pytest tests/ -vCoverage focuses on the highest-risk logic:
test_security.py— Fernet round-trips, tamper detection, JWT creation/expiry/ signature validation, config-time key validation.test_tool_logging.py— run creation, pending→success/error transitions,finished_atstamping, cross-user access isolation.test_corporate_google.py— the scope catalogue, the corporate domain gate, production config refusal, internal-API authentication, encrypted credential upsert (including Google omitting the refresh token on re-consent), MCP server seeding, and resource dispatch.
Project Structure
.
├── docker-compose.yml # dev: api · worker · postgres · redis
├── docker-compose.prod.yml # company server: loopback-bound, log-rotated
├── Dockerfile # multi-stage build (shared by api & worker)
├── docker/entrypoint.sh # wait for DB → migrate (api only) → exec
├── deploy/nginx.conf # TLS, SSE tuning, /internal IP restriction
├── alembic/ # database migrations
└── app/
├── main.py # FastAPI app, routers, lifespan, health/ready
├── api/
│ ├── auth.py # Google OAuth → JWT, corporate gate
│ ├── google.py # direct Workspace data access
│ ├── internal.py # service-to-service API for Django
│ └── automation.py # run endpoints (enqueue + poll)
├── core/
│ ├── config.py # pydantic-settings + production validation
│ ├── db.py # async SQLAlchemy engine/session
│ ├── google_scopes.py # service catalogue, scopes, host allowlist
│ ├── google_client.py # refresh, authorised requests, revocation
│ └── security.py # Fernet, JWT, OAuth state, internal key
├── services/
│ └── google_data.py # per-service readers + resource dispatch
├── mcp/
│ ├── server.py # MCP server (tools + SSE transport)
│ └── client.py # server registry + tool config builder
├── models/ # GoogleCredential · MCPServer · AutomationRun
└── workers/
├── celery_app.py # Celery configuration
└── tasks.py # the LLM ⇄ MCP orchestration loopThis server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityDmaintenanceA Model Context Protocol server that enables AI assistants to interact with Gmail services, supporting email operations, draft management, and calendar functionality through Google API integration.Last updated2021MIT
- AlicenseAqualityDmaintenanceA Model Context Protocol server that provides tools for interacting with Gmail and Calendar APIs, enabling programmatic management of emails and calendar events.Last updated827MIT
- AlicenseAqualityDmaintenanceA Model Context Protocol server that enables AI agents to interact with Google Workspace services including Drive, Docs, and Sheets through natural language commands.Last updated8MIT
- AlicenseBqualityDmaintenanceA Model Context Protocol (MCP) server that connects AI agents to Google Workspace (Gmail, Calendar, Drive, Docs, Sheets, and Slides).Last updated302MIT
Related MCP Connectors
Hosted Google Calendar MCP server for AI agents. No self-hosting or Google Cloud setup.
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
A Model Context Protocol server for Wix AI tools
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/shukurlukerem/MCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server