Workspace Operator
Deep automation for Google Workspace through the Model Context Protocol
Drive · Docs · Sheets · Slides · Gmail · Calendar · Apps Script — one local MCP server with 67 tools, staged writes and server-side tool profiles.
Workspace Operator (formerly yet-another-google-mcp) runs on your machine, talks to Google over your own OAuth client, and gives an AI agent the operations that turn "read my inbox" into "do the work and let me undo it": staged Apps Script updates, file transfer in both directions, presentation assembly, sheet formatting, threaded replies and attachments.
67 tools · 3 resources · 3 resource templates · 8 prompts, every tool annotated
with readOnlyHint / destructiveHint.
Nothing here is tied to a specific account, spreadsheet or document: every tool takes identifiers and ranges, and all examples use synthetic data.
How it compares
| Google Workspace MCP (remote) | Workspace Operator | |
|---|---|---|
| Transport | Streamable HTTP, Google-hosted | stdio, local |
| Availability | Developer Preview | public, MIT |
| Data path | Google MCP endpoint | your machine, your OAuth client |
| Apps Script | — | create · read · stage · execute · cancel · restore · run function |
| Local files | — | upload, download, export to pdf/docx/xlsx/pptx |
| Slides | — | create, add slides, fill text, insert images |
| Sheets formatting | — | bold, colours, number formats, widths, freeze |
| Gmail threads | single messages | threads, attachments, in-thread replies |
| Server-side tool profiles | IAM / admin allow-list | read-only / read-write / full + allow-deny |
Official servers are the right default for "ask my mail a question". Workspace Operator is for "change the workspace and be able to roll it back".
What you get
| Group | Tools | Highlights |
|---|---|---|
| 🔎 Cross-search | 1 | One query over Drive, Gmail and Calendar, merged by date |
| 📁 Drive | 8 | Search, move, copy, share, permissions, revoke public access |
| 💾 Drive files | 3 | Upload/download/export, conversion to native Google formats |
| 📊 Sheets | 12 | Read/write, formatting, filter views, named ranges, CSV |
| 📄 Docs | 5 | Read (tables included), append, templates, PDF export |
| 🖼 Slides | 5 | Create decks, add slides, replace text, insert images |
| ⚙️ Apps Script | 7 | Staged updates with backups, rollback, run functions |
| 📧 Gmail | 14 | Threads, attachments, replies, drafts, labels, batch delete |
| 📅 Calendar | 7 | Events, free slots, meetings, update/delete, invite responses |
| 🧩 Composite | 5 | report from sheet, digest, table diff, template clone, table → deck |
Plus 3 resources, 3 resource templates and 8 prompts — see the tool catalog.
Quick start
1 — Install
pip install workspace-operator-mcp
From source (development):
git clone https://github.com/Artoxem/workspace-operator.git
cd workspace-operator
python -m venv .venv && .venv\Scripts\activate # Windows
pip install -r requirements.txt
Python 3.10 or newer. The package installs the workspace-operator command; the
python -m workspace_operator form works from a source checkout too.
2 — Google OAuth
- Create an OAuth 2.0 Client ID (type: Desktop app) in Google Cloud Console.
- Enable these APIs: Drive, Docs, Sheets, Slides, Gmail, Calendar, Apps Script.
- Download the client JSON — the server will read it on first run.
Step by step: docs/AUTH_SETUP.md · docs/ru/AUTH_SETUP.md Upgrading? Slides needs the
.../auth/presentationsscope — re-run the OAuth flow once.
3 — Configure
cp config.example.yaml config.yaml
client_secrets_file: "C:\\Users\\you\\.google\\oauth.keys.json"
token_file: "C:\\Users\\you\\.google\\token.json"
backup_dir: "C:\\Users\\you\\.google\\backups"
mcp_auth_token: "MySecretToken_2026!"
tool_profile: "read-write" # full | read-write | read-only
mcp_auth_token is a local activation key, not network security: the server refuses
to handle tools until it is set. Every key can be overridden by an environment
variable — see docs/SECURITY.md.
4 — Connect a client
Google Antigravity — App, IDE and CLI share one config
~/.gemini/config/mcp_config.json (global) or <workspace>/.agents/mcp_config.json:
{
"mcpServers": {
"workspace-operator": {
"command": "python",
"args": ["D:\\DEV\\workspace-operator\\server.py"],
"env": {
"MCP_CONFIG_FILE": "D:\\DEV\\workspace-operator\\config.yaml",
"MCP_AUTH_TOKEN": "MySecretToken_2026!",
"MCP_TOOL_PROFILE": "read-write"
},
"timeoutSeconds": 300,
"tools": { "workspace_search": { "eager": true } }
}
}
}
Ready-made file: antigravity/mcp_config.example.json · walkthrough: antigravity/README.md · Agent Skill: skills/google-workspace/SKILL.md
Claude Desktop, Cursor, VS Code, Windsurf
{
"mcpServers": {
"workspace-operator": {
"command": "python",
"args": ["D:\\DEV\\workspace-operator\\server.py"],
"env": {
"MCP_CONFIG_FILE": "D:\\DEV\\workspace-operator\\config.yaml",
"MCP_AUTH_TOKEN": "MySecretToken_2026!"
}
}
}
}
Per-client paths: docs/IDE_SETUP.md · docs/ru/IDE_SETUP.md.
Tool profiles
A profile is a server-side allow-list: tools outside it are neither listed nor callable. It complements OAuth scopes (what Google allows) and client permissions (what the agent may do without asking).
| Profile | Tools | Use it for |
|---|---|---|
full |
67 | your own machine, full trust |
read-write |
59 | daily work — destructive tools hidden |
read-only |
21 | exploration, demos, shared machines, untrusted agents |
tool_profile: "read-write"
# Optional, applied on top. Deny wins over allow.
allowed_tools: ["workspace_search", "find_files", "read_sheet", "read_email"]
denied_tools: ["send_email", "share_file"]
Environment overrides: MCP_TOOL_PROFILE, MCP_ALLOWED_TOOLS, MCP_DENIED_TOOLS.
A blocked call returns a clear explanation instead of failing silently.
Operations
python -m workspace_operator doctor # config, token, scopes, API reachability
python -m workspace_operator tools # catalog (--profile read-only, --json)
python -m workspace_operator serve # stdio server (same as python server.py)
| Feature | How to use it | What it does |
|---|---|---|
| Dry run | MCP_DRY_RUN=1 or dry_run: true |
Every non-read-only tool returns a preview instead of executing — safe demos, safe exploration |
| Audit log | MCP_AUDIT_LOG=~/.google/audit.jsonl |
One JSON line per call: tool, profile, status, duration, argument names (never values) |
| Retries | MCP_RETRY_ATTEMPTS=3 |
Transient Google API errors (429/5xx) are retried with exponential backoff |
| Doctor | python -m workspace_operator doctor |
Checks secrets, token, granted vs configured scopes, and Drive/Gmail/Calendar reachability |
In dry-run the read-only tools still work, so an agent can explore data while every write is turned into an explicit preview.
HTTP transport (localhost first)
python -m workspace_operator serve --http --port 8765
- binds
127.0.0.1by default — not reachable from the network; - every request needs
Authorization: Bearer <mcp_auth_token>; extra per-client tokens can be listed underhttp_tokensinconfig.yaml, and the audit log records the token name asclient; - DNS-rebinding protection is on: only expected
Hostheaders pass, browser origins are denied unless explicitly allowed; GET /healthzis unauthenticated and returns only version and transport info;- binding a non-loopback address requires both
--allow-networkand--insecure, so network exposure is always deliberate.
Docker
docker compose up --build
The compose file publishes 127.0.0.1:8765 only and mounts your OAuth files
read-only.
Tool catalog
🔎 Cross-search (1)
| Tool | Type | Description |
|---|---|---|
workspace_search |
read | One query across Drive, Gmail and Calendar, date-sorted, partial failures reported |
📁 Drive (8)
| Tool | Type | Description |
|---|---|---|
find_files |
read | Search by name or Drive query |
drive_search_advanced |
read | Full Drive query syntax with a limit |
drive_list_permissions |
read | List permissions of a file |
create_folder |
write | Create a folder |
move_file |
write | Move a file into a folder |
drive_copy_file |
write | Copy a file |
share_file |
write | Share with user/group/domain/anyone |
drive_revoke_public |
destructive | Revoke public access (confirm=true) |
💾 Drive files (3)
| Tool | Type | Description |
|---|---|---|
drive_upload |
write | Upload a local file; convert_to turns docx/xlsx/pptx into native Google files |
drive_download |
read | Download a binary file to disk |
drive_export |
read | Export a Doc/Sheet/Slide to pdf, docx, xlsx, pptx, csv… |
📊 Sheets (12)
| Tool | Type | Description |
|---|---|---|
read_sheet |
read | Read a cell range |
get_spreadsheet_meta |
read | Sheet names and IDs |
sheet_export_csv |
read | Export a range as CSV |
append_row |
write | Append a row |
update_sheet |
write | Write rows to a range |
create_spreadsheet |
write | Create a spreadsheet |
add_sheet |
write | Add a tab |
sheet_create_filter_view |
write | Create a filter view |
sheet_create_named_range |
write | Create a named range |
sheet_find_replace |
write | Find & replace (dry_run=true by default) |
sheet_format |
write | Bold, colours, number formats, widths, row heights, frozen panes |
clear_range |
destructive | Clear a range (auto dry-run for large ranges) |
📄 Docs (5)
| Tool | Type | Description |
|---|---|---|
read_doc |
read | Read text and tables |
doc_export_pdf |
read | Export as binary PDF |
create_doc |
write | Create a document |
append_to_doc |
write | Append text |
doc_fill_template |
destructive | Fill placeholders (confirm=true) |
🖼 Slides (5)
| Tool | Type | Description |
|---|---|---|
slides_create |
write | Create a presentation |
slides_get |
read | Slide count, IDs and text per slide |
slides_add_slide |
write | Add a slide with a predefined layout |
slides_replace_text |
destructive | Replace placeholders (confirm=true) |
slides_insert_image |
write | Insert a public image URL onto a slide |
⚙️ Apps Script (7)
| Tool | Type | Description |
|---|---|---|
get_script_content |
read | Read project files |
create_script_project |
write | Create a project |
prepare_script_update |
write | Stage an update, returns operation_id |
execute_operation |
write | Apply a staged update (auto-backup first) |
cancel_operation |
write | Cancel a staged update |
restore_script_backup |
write | Restore from a backup |
script_run_function |
destructive | Run a function from an API-executable deployment |
📧 Gmail (14)
| Tool | Type | Description |
|---|---|---|
get_gmail_profile |
read | Authenticated address |
list_emails |
read | List with date, sender and subject |
read_email |
read | Headers, labels, attachments and decoded body |
gmail_search_and_summarize |
read | Search with a compact summary |
gmail_get_thread |
read | Whole conversation |
gmail_get_attachment |
read | Save an attachment to disk |
create_draft |
write | Create a draft |
send_email |
write | Send or draft (draft_mode=true by default) |
send_draft |
write | Send an existing draft |
gmail_reply |
write | Reply in-thread (draft_mode=true by default) |
gmail_label_apply |
write | Apply a label (dry_run=true by default) |
gmail_archive |
write | Archive (confirm=true) |
delete_email |
destructive | Delete (confirm=true) |
batch_delete_emails |
destructive | Delete many (dry_run=true by default) |
🧩 Composite (5)
| Tool | Type | Description |
|---|---|---|
report_from_sheet |
write | Read a range → column statistics → Doc with native tables or plain text (output=text) |
digest |
write | Last N hours across Gmail, Calendar and Drive → text or Doc |
compare_tables |
write | Diff two ranges by a key column; output=doc builds native tables |
clone_and_fill |
write | Copy any Drive file and replace placeholders in the copy |
sheet_to_slides |
write | Read a range → deck: title slide + one slide per row group |
📅 Calendar (7)
| Tool | Type | Description |
|---|---|---|
list_events |
read | Upcoming events with times and locations |
calendar_find_free_slots |
read | Free slots in a window |
create_event |
write | Create an event |
calendar_create_meeting |
write | Meeting with attendees (confirm=true) |
calendar_update_event |
write | Change any field; only what you pass is modified |
calendar_delete_event |
destructive | Delete an event (confirm=true) |
calendar_respond_to_invite |
write | Accept / decline / tentative (confirm=true) |
Resources
| URI | Attaches |
|---|---|
gdrive://recent |
20 most recently modified Drive files |
gmail://inbox |
Recent unread inbox messages |
gcalendar://upcoming |
Events for the next 7 days |
gdrive://file/{file_id} |
Metadata for one file |
gsheets://{spreadsheet_id}/{range} |
A sheet range as a table |
gdocs://{document_id} |
Full document text, tables included |
Prompts
| Prompt | What it does |
|---|---|
summarize_inbox |
Fetch recent mail → ready for a summary |
analyze_spreadsheet |
Read a range → ready for analysis |
plan_week |
Load calendar events → ready for planning |
search_drive |
Search files → ready for organisation advice |
weekly_review |
Last N days of mail, files and agenda → weekly summary |
inbox_triage |
Gmail slice → urgent / waiting / informational / archive |
sheet_brief |
Range → statistics plus a briefing question |
compare_brief |
Two ranges → reconciliation report |
Safety by default
| Guard | Applies to |
|---|---|
confirm=true required |
delete_email, gmail_archive, calendar_create_meeting, drive_revoke_public, doc_fill_template, slides_replace_text |
dry_run=true by default |
batch_delete_emails, sheet_find_replace, gmail_label_apply |
| Draft mode by default | send_email, gmail_reply |
| Public sharing blocked | share_file refuses type=anyone without allow_public=true |
| No silent overwrite | drive_download, drive_export, gmail_get_attachment |
| Staged Apps Script | prepare_script_update → execute_operation (backup first) |
| Tool annotations | every tool carries readOnlyHint / destructiveHint |
| Server-side profiles | read-only / read-write / full + allow/deny lists |
⚠️ An agent can reach everything your OAuth token can. Use read-only scopes and the
read-onlyprofile for sensitive accounts — docs/SECURITY.md.
How it works
server.py MCP server: 67 tool definitions, resources, prompts, profiles
workspace_operator/
├─ profiles.py read-only / read-write / full + allow-deny lists
├─ config.py config file + environment overrides
├─ auth.py OAuth 2.0 credentials and token cache
├─ text.py entity/whitespace normalisation, timestamps, error hints
├─ operations.py staged-operation store for Apps Script
├─ security.py token and email checks
├─ runtime.py retries, audit log, dry-run helpers
├─ cli.py doctor / tools / serve
└─ handlers/
├─ search.py workspace_search — Drive + Gmail + Calendar
├─ files.py drive_upload / drive_download / drive_export
├─ formatting.py sheet_format request builders
├─ gmail_threads.py threads, attachments, in-thread replies
├─ slides.py presentations
├─ calendar_writes.py update / delete / invite responses
├─ report.py report_from_sheet: stats + document
├─ digest.py digest across mail, calendar and drive
├─ compare.py table diff by key column
├─ template.py clone a file and fill placeholders
├─ deck.py table → presentation
├─ drive.py sheets.py docs.py
├─ gmail.py calendar.py apps_script.py
tests/ unit, live and round-trip checks
antigravity/ ready-to-paste mcp_config.json
skills/google-workspace/ Agent Skill: when to use which tool
Design rules:
- every handler returns a string — exceptions never reach the transport;
- writes are staged, confirmed or drafted, never silent;
- Apps Script is a two-phase commit with an automatic backup;
- server-side profiles keep the exposed surface small.
Testing
python -m unittest discover -s tests -v # 240 unit tests, no network
python tests/live_smoke.py # 51 checks against a real account
python tests/pptx_roundtrip.py deck.pptx # 5 checks: pptx → Slides → replace
The live suite is self-cleaning: it uploads and trashes a temp file, creates and
trashes a presentation and a spreadsheet, drafts and deletes a reply, downloads an
attachment and removes it. tests/test_docs.py keeps this README, the docs and the
code in sync (tool counts, profile numbers, links, examples).
Documentation
🇬🇧 English
| Document | Description |
|---|---|
| AUTH_SETUP.md | Google OAuth setup, step by step |
| TOOLS_REFERENCE.md | Every tool, resource and prompt |
| SECURITY.md | Scopes, profiles, safety model |
| IDE_SETUP.md | Antigravity, Cursor, Windsurf, VS Code |
| LEARN.md | How this server was built |
| docs/ | Release notes and specs for every version |
🇷🇺 На русском
| Документ | Описание |
|---|---|
| AUTH_SETUP.md | Пошаговая настройка OAuth |
| TOOLS_REFERENCE.md | Справочник инструментов |
| SECURITY.md | Скоупы, профили, модель безопасности |
| IDE_SETUP.md | Antigravity, Cursor, Windsurf, VS Code |
Roadmap
- Cross-product search, tool profiles
- Files, Slides, Apps Script execution
- Sheet formatting
- Gmail threads, attachments, replies
- Calendar update/delete/respond
- Composite tools:
report_from_sheet,digest,compare_tables,clone_and_fill - Optional HTTP transport (Streamable HTTP, stateless, localhost-first)
Responsibility
You are responsible for what the agent does with your account. Read
docs/SECURITY.md, start with read-only, keep backups on.
MIT licensed · built on the MCP Python SDK
Metadata
Release files for workspace-operator-mcp 1.12.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| workspace_operator_mcp-1.12.1.tar.gz | 105.6 kB | With provenance | Details for workspace_operator_mcp-1.12.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| workspace_operator_mcp-1.12.1-py3-none-any.whl | Python 3 | none | any | Details for workspace_operator_mcp-1.12.1-py3-none-any.whl |
Total release size: 202.7 kB
Release files / workspace_operator_mcp-1.12.1.tar.gz
| Download URL | workspace_operator_mcp-1.12.1.tar.gz |
|---|---|
| Size | 105.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aed164f03a5818ffc6ca66f60ec3b1934028bc965df613808dfe25ceeae87157
|
|
BLAKE2b-256 checksum How to use checksums |
395a33e5d9bd2664f52c4124842f2d082009993b5786d41a95c1a4078fb71193
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logRelease files / workspace_operator_mcp-1.12.1-py3-none-any.whl
| Download URL | workspace_operator_mcp-1.12.1-py3-none-any.whl |
|---|---|
| Size | 97.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
204b0b77f6d59ad8aed2311360f8c7d5a1283ce50748946b5671cbba77747e54
|
|
BLAKE2b-256 checksum How to use checksums |
ab53bb4f2db3cfb039b4b7edf1dcf2792802babed7187e6426153688486a8fc8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logSecurity for workspace-operator-mcp 1.12.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for workspace_operator_mcp-1.12.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 6, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.