Google workspace mcp
Skill dipseth/google_workspace_fastmcp2/.claude/skills/google-workspace-mcp
Guide for using the Google Workspace MCP server — DSL notation, code mode, macros, and tools across 11+ Google servicesFrom its SKILL.md
npx -y skills add dipseth/google_workspace_fastmcp2 --skill google-workspace-mcpAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
22.6 KB, as published. Nobody here has run it
Google Workspace MCP Server
FastMCP-based Google Workspace server with DSL-driven composition for emails and chat cards, sandboxed code execution, Jinja2 macros, privacy middleware, and Qdrant-backed response history. Not all tools are active at any given time — use manage_tools(action="list") to see what's currently enabled.
Core Wrapped Tools
compose_dynamic_email — MJML Email Composer
Composes responsive HTML emails via DSL notation, then sends or saves as draft.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
email_description | str | required | DSL structure ONLY + optional subject. Text after the DSL becomes the email subject (e.g., ε[ħ, τ] Welcome aboard) |
email_params | dict/str | None | REQUIRED for content — block text/titles/URLs go here, keyed by symbol. email_description is structure ONLY. Also accepts subject and preheader keys. Supports _shared/_items merging |
to | str/list | "myself" | Recipients: "myself", comma-separated string, or list of emails |
user_google_email | str | None | Auto-injected from OAuth session. Pass only to override (e.g., for secondary accounts) |
action | "send"/"draft" | "draft" | Draft is safe default; "send" delivers immediately |
cc / bcc | str | None | Optional CC/BCC recipients |
reply_to_message_id | str | None | Thread the composed email as a reply to this Gmail message. Recipients derive from the original; the reply subject overrides the DSL subject |
draft_id | str | None | Update an existing draft in place with the freshly rendered email instead of creating a new one (action='draft' only) |
Email DSL Symbols:
| Symbol | Block |
|---|---|
ε | EmailSpec |
ħ | HeroBlock |
τ | TextBlock |
Ƀ | ButtonBlock |
ɨ | ImageBlock |
¢ | ColumnsBlock |
© | Column |
Ħ | HeaderBlock |
ƒ | FooterBlock |
đ | DividerBlock |
ş | SpacerBlock |
ą | AccordionBlock |
ȼ | CarouselBlock |
ʂ | SocialBlock |
ƭ | TableBlock |
DSL Syntax:
ε[ħ, τ]— EmailSpec with HeroBlock and TextBlockε[Ħ, ħ, τ×3, Ƀ]— Header, hero, 3 text blocks, buttonε[ħ, ¢[©×2], Ƀ]— Hero, 2-column layout, buttonε[Ħ, τ, đ, ƒ]— Header, text, divider, footer
Params Structure (_shared/_items merging):
{
"subject": "Weekly Report",
"τ": {
"_shared": {"padding": "10px 25px"},
"_items": [
{"text": "Hello {{user://current/email.email}}"},
{"text": "Here is your report."}
]
},
"Ƀ": {
"_items": [
{"text": "View Report", "url": "https://example.com/report"}
]
}
}
Example — Simple email:
email_description: ε[Ħ, ħ, τ, Ƀ]
email_params: {
"subject": "Meeting Reminder",
"Ħ": {"_items": [{"title": "Acme Corp"}]},
"ħ": {"_items": [{"title": "Team Standup", "subtitle": "Tomorrow at 10am"}]},
"τ": {"_items": [{"text": "Don't forget the standup meeting."}]},
"Ƀ": {"_items": [{"text": "Add to Calendar", "url": "https://calendar.google.com"}]}
}
Draft → patch → send workflow (iterative composition):
Email composition is rarely one-shot. Treat a draft as a canvas you refine over several turns — never accumulate duplicate drafts:
- Create —
compose_dynamic_email(..., action="draft"). The response'sdraftIdis the stable handle for every later edit (the draft's message id changes on each update; the draft id does not). Keep it. - Patch bit by bit — change only what you need in
email_params(or the DSL), then re-call withdraft_id=<draftId>. The same draft is re-rendered in place. Repeat freely: tweak one block's text, swap a color theme, add a section — one call per iteration. - Recover a lost draft id —
search_gmail_messages(query="in:draft")returnsdraft_idon every draft result (including drafts created in the Gmail UI), andget_gmail_message_contentincludes it when the message is a draft. No draft is unreachable. - Thread as a reply — add
reply_to_message_id=<message id>:action="draft"creates a threaded reply draft (combine withdraft_idto re-render it),action="send"sends the reply immediately. The reply subject comes from the original message; the DSL subject is ignored. - Send — after review, send from the Gmail UI (safest, sends the draft
itself), or re-call with
action="send"(renders and sends a new message; the draft stays behind — delete it afterwards).
The plain-HTML tools follow the same pattern: draft_gmail_message and
draft_gmail_reply accept the same draft_id for in-place updates, and
draft_gmail_reply accepts email_spec for MJML reply drafts.
Example — patch one block of an existing draft (code mode):
drafts = await call_tool("search_gmail_messages", {"query": "in:draft", "page_size": 5})
target = drafts["messages"][0] # newest draft; has draft_id
params = from_json(saved_params) # the params used to create it
params["τ"]["_items"][0]["text"] = "Updated opening paragraph."
result = await call_tool("compose_dynamic_email", {
"email_description": "ε[ħ, τx2, Ƀ]",
"email_params": params,
"action": "draft",
"draft_id": target["draft_id"],
})
return result["draftId"] # same id — edited in place
Macro-powered iteration: for an email you patch repeatedly (or a layout you
reuse), store it once as a dual-mode Jinja2 macro via create_template_macro
(persist_to_file=True survives restarts). Each iteration then re-invokes the
macro with only the changed data instead of resending the whole params blob —
the Template Middleware renders {{ ... }} in tool parameters before the
tool runs:
await call_tool("compose_dynamic_email", {
"email_description": "{{ team_update(email_symbols, mode='dsl') }}",
"email_params": "{{ team_update(email_symbols, mode='params', headline='Week 3 recap') }}",
"action": "draft",
"draft_id": known_draft_id,
})
Macros can also pull live data via resource URIs (e.g. service://gmail/labels,
user://current/profile) as macro arguments — see the Jinja2 Macro System
section below.
For detailed component field reference: Use skill://mjml-email/ resources (15 component docs, symbols, containment rules).
send_dynamic_card — Google Chat Card Builder
Sends a card to Google Chat using DSL notation for structure control.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
card_description | str | required | DSL structure string — see Card DSL Symbols table. Supports both Structure DSL (§[δx2]) and Content DSL (δ 'text' success bold) |
card_params | dict/str | None | Widget content keyed by symbol. Supports _shared/_items merging. Takes priority over Content DSL in card_description |
space_id | str | None | Chat space ID. Required only for API mode (no webhook) |
user_google_email | str | None | Auto-injected from OAuth session. Pass only to override (e.g., for secondary accounts) |
webhook_url | str | None | Webhook URL. Defaults to MCP_CHAT_WEBHOOK env var |
thread_key | str | None | Thread key for replies |
Card DSL Symbols (primary building blocks):
| Symbol | Component |
|---|---|
§ | Section |
δ | DecoratedText |
Đ | Divider |
ᵬ | Button |
Ƀ | ButtonList |
ℊ | Grid |
ǵ | GridItem |
◦ | Carousel |
▼ | CarouselCard |
ȼ | ChipList |
ℂ | Chip |
ʈ | TextParagraph |
τ | TextInput |
ǐ | Image |
ɨ | Icon |
▲ | SelectionInput |
◙ | DateTimePicker |
◇ | CardHeader |
¢ | Columns |
ç | Column |
DSL Syntax:
§[δ]— Section with one DecoratedText§[δ×3, Ƀ[ᵬ×2]]— Section with 3 DecoratedTexts and 2 Buttons§[ℊ[ǵ×4]]— Section with a 4-item Grid§[δ, ǐ, Ƀ[ᵬ]]— Section with text, image, and button
Context Consumption Pattern:
Widget content comes from card_params via symbol keys, NOT from inline text in card_description. The builder creates structure from DSL; content comes from context consumption.
{
"δ": {
"_shared": {"top_label": "Status"},
"_items": [
{"text": "Build passed", "icon": "STAR"},
{"text": "Tests: 142/142"}
]
},
"ᵬ": {
"_items": [
{"text": "View Build", "url": "https://ci.example.com/build/123"}
]
}
}
Session-scope enable required: This tool often needs session-scope enablement:
manage_tools(action="enable", tool_names="send_dynamic_card", scope="session")
After session enable, must /mcp reconnect for Claude Code to discover the tool.
For detailed component field reference: Use skill://gchat-cards/ resources (symbols, DSL syntax, 100+ component docs).
Qdrant Search Tools
| Tool | Purpose |
|---|---|
qdrant_search | Search tool response history with semantic or hybrid search |
search_tool_history | Find past tool outputs by tool name, date range, or keywords |
get_tool_analytics | Usage statistics across tools |
get_response_details | Get full details of a specific stored response |
fetch | Fetch a specific Qdrant point by ID |
cleanup_qdrant_data | Clean up old or orphaned Qdrant data |
Collections:
mcp_tool_responses— Stores all tool invocation results with embeddingsmcp_gchat_cards_v8— Card templates and feedback patterns
Code Mode (Execute Tool)
The execute tool runs sandboxed Python with call_tool() available for chaining MCP tool calls.
Key rules:
- Use
await call_tool(tool_name, params)to call any MCP tool - Use
returnto produce output importis NOT available — use built-in helpers instead- Prefer returning the final answer from a single block
- Execution is time-limited (default 90s,
CODE_MODE_MAX_DURATION_SECSenv var) — split long chains of heavy tool calls (e.g. several full email renders) into separate execute blocks rather than one mega-block
Built-in Helpers:
| Category | Functions |
|---|---|
| Datetime | now(tz_offset=-6), today(tz_offset=-6), days_ago(n), hours_ago(n), format_date(iso, fmt), parse_date(iso), timestamp() |
| JSON | to_json(obj, indent), from_json(s) |
| URL | url_encode(s), url_decode(s), url_join(base, *parts), query_string(params) |
| Regex | re_find(pattern, text), re_match(pattern, text), re_sub(pattern, repl, text) |
| Text | truncate(text, n=80), join(items, sep), html_escape(s), dedent(text), wrap_text(text, width), pad_left(s, width), pad_right(s, width) |
| Math | sqrt(n), ceil(n), floor(n), round_(n, digits), abs_(), min_(), max_(), sum_() |
| Collections | sorted_(items, key, reverse), unique(items), flatten(lists), counter(items), chunk(items, size), zip_(*iterables), dict_get(d, 'a.b.c', default) |
| Hashing | md5(s), sha256(s) |
| Async | gather(*coros), sleep(seconds) |
Code Mode Discovery Tools:
| Tool | Purpose |
|---|---|
tags | List all tool tags for filtering |
search | BM25 keyword search across tool names/descriptions |
get_schema | Get full JSON schema for a tool before calling it |
semantic_search | Full Qdrant vector search — semantic, DSL filters (filter_dsl), recommendation (positive/negative_point_ids), advanced queries (query_dsl, prefetch_dsl), and dry_run validation |
fetch_document | Peek at a stored response by point ID (truncated preview) |
tool_activity | Dashboard of tool usage — counts, error rates, sample IDs |
execute | Run sandboxed Python code |
Example — Chain tool calls:
# Fetch labels, then compose a report email
labels = await call_tool("list_gmail_labels", {"user_google_email": "[email protected]"})
label_count = len(labels.get("labels", []))
await call_tool("compose_dynamic_email", {
"email_description": "ε[Ħ, τ, đ, ƒ]",
"email_params": to_json({
"subject": f"Label Report - {today()}",
"Ħ": {"_items": [{"title": "Gmail Labels"}]},
"τ": {"_items": [{"text": f"You have {label_count} labels."}]},
"ƒ": {"_items": [{"text": f"Generated {now()}"}]}
}),
"action": "draft"
})
return f"Draft created with {label_count} labels"
Jinja2 Macro System
Macros enable reusable, data-driven templates for emails and cards.
Location: middleware/templates/*.j2 (tracked) and middleware/templates/dynamic/*.j2 (gitignored, experimental)
Dual-Mode Pattern
Macros support two modes:
mode='dsl'— Returns DSL structure string (used asemail_descriptionorcard_description)mode='params'— Returns JSON params (used asemail_paramsorcard_params)
{% macro email_resource_report(label_data, profile_data, session_data, symbols, mode='dsl') %}
{%- if mode == 'dsl' -%}
{{ s.EmailSpec }}[{{ s.HeaderBlock }}, {{ s.HeroBlock }}, ...]
{%- elif mode == 'params' -%}
{{ result | tojson }}
{%- endif -%}
{% endmacro %}
Resource URI Templating
Inside macros, service:// URIs are preprocessed into Jinja2 variables:
service://gmail/labelsbecomesservice_gmail_labels(SimpleNamespace with dot access)user://current/emailbecomesuser_current_email- Access via:
{{ service_gmail_labels.labels[0].name }}
create_template_macro Tool
Create macros at runtime:
| Parameter | Type | Description |
|---|---|---|
macro_name | str | Valid Python identifier (1-100 chars) |
macro_content | str | Full {% macro %}...{% endmacro %} Jinja2 block (10-50K chars) |
description | str | Optional description |
usage_example | str | Optional usage example |
persist_to_file | bool | Save to templates/dynamic/ for persistence across restarts |
Re-creating an existing name overwrites it. remove_template_macro(macro_name)
deletes a dynamic macro (and its persisted file); file-based macros shipped with
the server are protected. Listing stays resource-based: template://macros.
Macro invocations inside tool parameters are detected and rendered by the
Template Middleware, including calls with full dict/list literals in their
arguments — e.g. {{ my_macro(items=[{"k": "v"}], mode='params') }}.
email_symbols Global
The email_symbols object is available as a Jinja2 global, providing symbol-to-class mappings for constructing DSL strings dynamically in macros.
Resource URIs
User & Auth
| URI | Description |
|---|---|
user://current/email | Authenticated user's email |
user://current/profile | User profile with auth status |
user://profile/{email} | Profile for specific email |
auth://session/current | Current session info |
auth://sessions/list | All active sessions |
auth://credentials/{email}/status | Credential status for email |
Service Data
| URI | Description |
|---|---|
service://{service}/lists | Available list types for a service |
service://{service}/{list_type} | All items for list type |
service://{service}/{list_type}/{item_id} | Specific item details |
Supported services: gmail, drive, calendar, docs, sheets, chat, forms, slides, photos
Templates & Macros
| URI | Description |
|---|---|
template://macros | List all available macros |
template://macros/{macro_name} | Specific macro usage example |
Qdrant
| URI | Description |
|---|---|
qdrant://collections/list | All Qdrant collections |
qdrant://collection/{name}/info | Collection details |
qdrant://collection/{name}/responses/recent | Recent responses |
qdrant://search/{query} | Search across collections |
qdrant://collection/{name}/{point_id} | Specific point |
qdrant://status | Overall Qdrant status |
Other
| URI | Description |
|---|---|
recent://{service} | Recent items for service |
chat://digest | Chat digest (supports ?hours=N&limit=N) |
gmail://messages/recent | Recent Gmail messages |
tools://list/all | All available tools |
Skill Resources
| Resource | Content | When to Use |
|---|---|---|
skill://gchat-cards/ | 100+ component docs, symbols, containment rules, DSL syntax | Detailed card component field reference |
skill://mjml-email/ | Email block symbols, DSL syntax, per-component docs | Detailed email block field reference |
Tool Management
manage_tools
| Parameter | Type | Default | Description |
|---|---|---|---|
action | str | required | list, enable, disable, disable_all_except, enable_all |
tool_names | str/list | None | Tool name(s) — string, list, comma-separated, or JSON array |
scope | str | "global" | global (all clients) or session (current client only) |
service_filter | str | None | Filter by service: gmail, chat, drive, etc. |
Protected tools (cannot be disabled): manage_tools, qdrant_search, health_check, start_google_auth, check_drive_auth, and Code Mode meta-tools (tags, search, get_schema, semantic_search, fetch_document, tool_activity, execute).
All Services Quick Reference
Use manage_tools(action="list") or manage_tools(action="list", service_filter="gmail") to see currently active tools.
| Service | Key Tools |
|---|---|
| Drive | upload_to_drive, search_drive_files, list_drive_items, create_drive_file, share_drive_files, manage_drive_files, get_drive_file_content, make_drive_files_public |
| Gmail | compose_dynamic_email, search_gmail_messages, send_gmail_message, draft_gmail_message, reply_to_gmail_message, list_gmail_labels, manage_gmail_label, list_gmail_filters, create_gmail_filter, manage_gmail_allow_list |
| Chat | send_dynamic_card, list_spaces, list_messages, send_message, search_messages |
| Chat Cards | send_simple_card, send_rich_card, send_enhanced_card, send_interactive_card, send_smart_card, preview_card_from_description, validate_card, find_card_templates, save_card_template |
| Calendar | list_calendars, list_events, create_event, modify_event, delete_event, get_event, create_calendar, bulk_calendar_operations, move_events_between_calendars |
| Docs | search_docs, get_doc_content, list_docs_in_folder, create_doc |
| Sheets | list_spreadsheets, get_spreadsheet_info, read_sheet_values, modify_sheet_values, batch_modify_sheet_values, create_spreadsheet, create_sheet, format_sheet_range, batch_update_sheet |
| Slides | create_presentation, get_presentation_info, add_slide, update_slide_content, export_and_download_presentation |
| Forms | create_form, add_questions_to_form, get_form, set_form_publish_state, publish_form_publicly, get_form_response, list_form_responses, update_form_questions |
| Photos | list_photos_albums, search_photos, upload_photos, upload_folder_photos, photos_smart_search, photos_batch_details, create_photos_album, get_photo_details |
| People | list_people_contact_labels, get_people_contact_group_members, manage_people_contact_labels |
| Qdrant | qdrant_search, search_tool_history, get_tool_analytics, fetch, get_response_details, cleanup_qdrant_data |
| Email Templates | create_email_template, list_email_templates, preview_email_template, intelligent_email_composer, send_smart_email |
| Module Wrappers | list_wrapped_modules, wrap_module, search_module, list_module_components, get_module_component |
| Server | health_check, manage_credentials, manage_tools, create_template_macro |
| Code Mode | tags, search, get_schema, semantic_search, fetch_document, tool_activity, execute |
Sheets Power Tools
Beyond simple reads/writes, three tools cover advanced spreadsheet work:
batch_update_sheet— rawspreadsheets.batchUpdatepassthrough (up to 100 requests, atomic: one bad request rolls back the whole batch). This is the route to everything the dedicated tools don't cover: charts (addChart— all 9 chart families: basic BAR/LINE/AREA/COLUMN/SCATTER/COMBO/STEPPED_AREA, pie, bubble, candlestick, histogram, org, scorecard, treemap, waterfall), data-validation dropdowns (setDataValidationwithONE_OF_LIST+showCustomUi), protected/banded/named ranges,unmergeCells, delete/duplicate/rename sheets,sortRange,findReplace,autoResizeDimensions, row/column grouping, filter views, and slicers. Request objects follow the Sheets API batchUpdate reference verbatim; replies return created IDs (e.g. chart IDs). Gotcha: BAR chart series must targetBOTTOM_AXIS; other basic charts useLEFT_AXIS.batch_modify_sheet_values— write many ranges and/or clear ranges in single API calls; prefer it over loopingmodify_sheet_valueswhenever touching more than ~2 ranges.get_spreadsheet_infowithfields— read back formatting, conditional-format rules, charts, merges, and validation via a Google API field mask (raw response inraw), e.g.sheets(properties,charts(chartId,spec(title))). Scope grid-data masks tightly.
Cell values are real scalars: numbers/booleans round-trip properly (RAW input writes real
numbers; UNFORMATTED_VALUE reads them back typed). modify_sheet_values also supports
append=True to add rows after existing data.
Cross-Cutting Features
Privacy Middleware
- Encrypts PII in tool responses using per-session Fernet keys
- LLM sees masked tokens (
[MASKED:token_1]), wire carries ciphertext - Modes:
auto(heuristic PII detection) orstrict(encrypt all strings) - Round-trip:
[PRIVATE:token_N]in tool arguments resolves back to plaintext
Authentication
- Call
start_google_authwith email to begin OAuth flow - Complete authentication in browser
- Call
check_drive_authto verify credentials - Session-level account linking tracks authed emails across session lifetime
Response History (Qdrant)
- All tool responses are automatically stored with embeddings
- Searchable via
qdrant_search(semantic),search_tool_history(filtered), orget_tool_analytics(stats) - Collections:
mcp_tool_responses(all responses),mcp_gchat_cards_v8(card templates + feedback)