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
A production-grade AI automation backend that lets a Large Language Model safely operate real-world tools — Gmail, Google Drive, and Calendar — through the Model Context Protocol (MCP).
The service acts as a secure broker between an LLM and external systems: it stores per-user OAuth credentials (encrypted at rest), exposes its own database as an MCP server, and runs long LLM↔tool conversations in background workers so the API stays responsive.
In one sentence: a user says "summarise last week's invoice emails", and the system orchestrates OpenAI + the Gmail MCP server to do it — asynchronously, auditably, and securely.
Table of Contents
Architecture
The system is built as four independent, 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[Client / User]
subgraph Docker Compose
API[FastAPI API<br/>REST + MCP/SSE server]
W[Celery Worker<br/>LLM ⇄ MCP loop]
R[(Redis<br/>broker + cache)]
P[(PostgreSQL<br/>state + audit log)]
end
LLM[OpenAI<br/>Responses API]
G[Google Workspace<br/>MCP servers<br/>Gmail · Drive · Calendar]
U -- 1. POST /automation/run + JWT --> API
API -- 2. enqueue job --> R
API -- persist AutomationRun --> P
R -- 3. deliver task --> W
W -- 4. prompt + MCP tools --> LLM
LLM -- 5. calls tools --> G
G -- tool results --> LLM
LLM -- 6. final answer --> W
W -- 7. write result --> P
U -- 8. GET /automation/run/{id} --> API
API -- read status --> PComponents
Service | Responsibility | Why it exists |
| REST endpoints, Google OAuth, JWT auth, MCP server over SSE | The only public surface; stays fast by never running LLM work inline |
| Runs the LLM↔MCP conversation, refreshes tokens, writes results | LLM loops are slow (30–120 s) and stateful — they belong off the request path |
| Stores users' encrypted credentials, the MCP server registry, and a full execution audit log | Durable state and traceability of every AI action |
| Message broker between API and worker | Decouples request acceptance from execution |
Two directions of MCP
This project demonstrates both roles MCP can play:
MCP client (consume): the LLM connects out to Google's MCP servers to use Gmail/Drive/Calendar as tools.
MCP server (expose): the service exposes its own data (
/mcp/) so other AI agents can query automation runs and the server registry.
Request Lifecycle
A single automation run flows through the system as follows:
Accept —
POST /automation/runvalidates the JWT and the target MCP server, writes anAutomationRunrow withstatus=pending, and returns202 Acceptedwith arun_idimmediately.Enqueue — the API dispatches a Celery task carrying only the
run_id(never ORM objects), and returns control to the client.Authorise — the worker loads the user's
GoogleCredential, decrypts the token, and refreshes it against Google if it expires within 5 minutes.Orchestrate — the worker calls the OpenAI Responses API, passing the MCP servers as
tools. OpenAI's infrastructure connects to the MCP servers, invokes tools, and returns a final answer.Persist — the worker records the output (and any tool calls) on the
AutomationRunrow, settingstatus=success/errorandfinished_at.Poll — the client retrieves the result via
GET /automation/run/{id}, scoped to its own user.
Why This Design
Decision | Rationale |
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. The API returns in milliseconds. |
Pass | SQLAlchemy objects aren't JSON-serialisable and become stale across process boundaries. The worker re-fetches with its own session. |
Tokens encrypted with Fernet | A database leak must not expose usable Google credentials. Plaintext tokens are the single highest-risk mistake in this class of system. |
JWT validated before the SSE handshake | The MCP stream is registered as a FastAPI route (not |
OpenAI Responses API with remote MCP tools | OpenAI's infrastructure executes the MCP tool calls, so no bespoke MCP client protocol code is needed in the worker. |
| Prevents lazy-load failures when attributes are accessed after |
| Long tasks are re-queued (not lost) if a worker crashes, and fairly distributed across workers. |
Technology Stack
Layer | Choice |
Web framework | FastAPI + Uvicorn (async ASGI) |
LLM orchestration | OpenAI Responses API (remote MCP tools) |
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
string google_account_email
text access_token "Fernet-encrypted"
text refresh_token "Fernet-encrypted"
datetime token_expiry
json scopes
}
MCPServer {
int id PK
string name UK
enum transport "http | stdio"
text url
string auth_type
bool enabled
json config
}
AutomationRun {
int id PK
string user_id
int mcp_server_id FK
string tool_name
json input_payload
json output_payload
enum status "pending | success | error"
text error_message
datetime started_at
datetime finished_at
}
MCPServer ||--o{ AutomationRun : "executes"GoogleCredential— per-user OAuth tokens, stored encrypted, refreshed automatically.MCPServer— registry of MCP servers the system can connect to.AutomationRun— an append-only audit log of every AI action: what was asked, what ran, what came back.
Security Model
Encryption at rest — access and refresh tokens are Fernet-encrypted; the database never holds plaintext credentials.
Authenticated MCP — every MCP/SSE connection requires a valid Bearer JWT, validated before the stream opens.
User isolation — run-status endpoints enforce ownership; a user cannot read another user's runs.
Least-privilege OAuth — only the Google scopes actually needed are requested.
Secret hygiene — all secrets load from
.env(git-ignored); production deployments should use a secrets manager (Vault, AWS Secrets Manager) forFERNET_KEYandSECRET_KEY.Prompt-injection awareness — because MCP tools can fetch external content, write-capable scopes should be granted deliberately.
Getting Started
Prerequisites
Docker + Docker Compose v2
A Google Cloud project with OAuth credentials
An OpenAI API key
1. Google Cloud setup
In Google Cloud Console, create a project.
Enable the Gmail API, Google Drive API, and Google Calendar API.
Configure the OAuth consent screen (External) with scopes:
gmail.readonly,gmail.send,drive.file,calendar.events.Create an OAuth 2.0 Client ID (Web application) with redirect URI
http://localhost:8000/auth/google/callback.
2. Configure environment
cp .env.example .envGenerate the keys and fill in your credentials:
# JWT signing key
openssl rand -hex 32
# Fernet encryption key
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"Then set GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and OPENAI_API_KEY in .env.
3. Launch
docker compose up --build # starts postgres, redis, api, worker
docker compose exec api alembic upgrade head4. Connect a Google account
Open http://localhost:8000/auth/google/login, grant access, and receive a JWT:
{ "access_token": "eyJ...", "token_type": "bearer" }5. Register an MCP server
INSERT INTO mcp_servers (name, transport, url, auth_type, enabled, config)
VALUES ('gmail', 'http', 'https://gmail.googleapis.com/mcp', 'oauth', true, '{}');6. Trigger an automation run
curl -X POST http://localhost:8000/automation/run \
-H "Authorization: Bearer <your-jwt>" \
-H "Content-Type: application/json" \
-d '{
"mcp_server_id": 1,
"tool_name": "search_emails",
"instructions": "Find all emails from last week about invoices and summarise them"
}'
# → 202 { "run_id": 1, "status": "pending" }
curl http://localhost:8000/automation/run/1 -H "Authorization: Bearer <your-jwt>"API Reference
Interactive docs (Swagger UI): http://localhost:8000/docs
Method | Path | Auth | Description |
GET |
| — | Liveness check |
GET |
| — | Begin Google OAuth flow |
GET |
| — | OAuth callback → issues JWT |
POST |
| JWT | Enqueue an automation run |
GET |
| JWT | Poll a run's status/result |
GET |
| JWT | List the user's recent runs |
GET |
| JWT | MCP server stream (SSE) |
POST |
| — | MCP message relay |
Exposed MCP tools: get_automation_runs, get_mcp_servers, get_run_detail
Testing
pip install -r requirements.txt aiosqlite
pytest tests/ -vCoverage focuses on the highest-risk logic:
test_security.py— Fernet encrypt/decrypt round-trips, tamper detection, JWT creation/expiry/signature validation, and config-time key validation.test_tool_logging.py— run creation, pending→success/error transitions,finished_atstamping, and cross-user access isolation.
Project Structure
.
├── docker-compose.yml # api · worker · postgres · redis
├── Dockerfile # multi-stage build (shared by api & worker)
├── alembic/ # database migrations
└── app/
├── main.py # FastAPI app + MCP route mounting + lifespan
├── api/
│ ├── auth.py # Google OAuth → JWT
│ └── automation.py # run endpoints (enqueue + poll)
├── core/
│ ├── config.py # pydantic-settings
│ ├── db.py # async SQLAlchemy engine/session
│ └── security.py # Fernet + JWT
├── mcp/
│ ├── server.py # MCP server (tools + SSE transport)
│ └── client.py # token refresh + MCP tool config builder
├── models/ # GoogleCredential · MCPServer · AutomationRun
└── workers/
├── celery_app.py # Celery configuration
└── tasks.py # the LLM ⇄ MCP orchestration loopNote on scaling: MCP sessions are persisted in RAM per API instance. In a multi-instance deployment, enable session affinity (sticky sessions) at the load balancer so a client always reaches the same
apinode.
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
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