A monorepo of MCP servers for Gmail (read, search, triage, draft, send), Google Sheets (read, find, update with two-phase confirmation), and WhatsApp (send messages and approved templates via Meta Cloud API).
Three distinct MCP servers (Gmail, Google Sheets, WhatsApp) with generally strong naming conventions (verb-driven: mail_search, sheet_update, whatsapp_send_template) and comprehensive parameter descriptions. All 20 tools have clear, substantive descriptions (100-500+ chars). Input schemas are visible and include type definitions. However, output schemas are not formally documented in the source code, and several tools lack error handling guidance or recovery paths. Tool descriptions are excellent for domain context but could be more concise for LLM parsing. Security patterns are solid (credentials injected, no secrets in params), and composition is clean (single responsibility per tool). STDIO transport is a hard cap at 50 for protocol readiness but does not affect definition quality scoring here.
Archive one message or thread (remove from INBOX, land in All Mail). Archiving is reversible: label it INBOX again to unarchive.
Create a DRAFT in the authenticated mailbox (same composition as mail_send, attachments included; nothing goes out — it lands in Drafts for a human to review/send in Gmail). Still OUTWARD-FACING content: confirm recipient(s) + subject + full body with the user before calling. Returns the draft id + underlying message id.
List labels, or add/remove labels on a message or thread. With no arguments: lists all labels (id + name). With message_id OR thread_id (exactly one) + add/remove: label names are resolved case-insensitively; names being ADDED are auto-created if missing (taxonomy: partners/<slug>, soporte, plataformas, facturación). Labeling never deletes mail.
Read a full email thread by thread id (from mail_search): every message with From/To/Cc/Date/Subject headers plus a best-effort text body (text/plain preferred, stripped text/html as fallback). Message ids in the output feed mail_send (reply_to_message_id), mail_label, and mail_archive.
Search messages in the authenticated mailbox with Gmail query syntax (from:, to:, subject:, label:, is:unread, newer_than:7d, …). Returns one block per message: message id + thread id + date + from + subject + snippet. Use the thread id with mail_read_thread, the message id with mail_send (reply_to_message_id) / mail_label / mail_archive.
Output schemas not formally documented in source code. While tool descriptions reference what they return (e.g., 'Returns message id + thread id + date + from + subject + snippet' for mail_search), there is no explicit JSON Schema definition visible for response structures. LLMs must infer output shape from descriptions alone.
Error handling and recovery guidance is minimal. Tools like mail_send (IRREVERSIBLE) and whatsapp_send_template (IRREVERSIBLE) lack explicit error classification (retryable vs. user-fixable vs. fatal) or recovery hints in descriptions. E.g., whatsapp_send_message mentions error 131047 for out-of-window sends but provides no structured recovery path.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | B | 71 | 2026-07-28+ | v2 |
Send an email from the authenticated mailbox. OUTWARD-FACING — real people receive it immediately: ALWAYS confirm recipient(s) + subject + FULL body (and any attachments) with the user before calling. v1 has no auto-sends. Plain text by default; html=true sends multipart/alternative; attachments (local file paths) ride multipart/mixed. To reply in a thread pass reply_to_message_id (In-Reply-To/References are set from the original and the thread is inherited). Returns the sent message id + thread id.
Verify the mail credential: returns the authenticated mailbox (emailAddress — the account that granted the token), messagesTotal/threadsTotal, and where that credential was resolved from (the environment, a .env, or a credential file path). Use it to confirm WHICH mailbox this session is on before sending anything. Never prints any credential field.
Append rows to a table (detect the first empty row and insert there). TWO-PHASE: without confirm_token, shows a before/after preview and returns a token. With the token (and same arguments), actually appends. The token binds to the TABLE'S CURRENT HEIGHT, so concurrent appends are detected and the token is refused with a fresh preview.
Update multiple ranges in one call (same TWO-PHASE logic as sheet_update). Efficient for coordinated multi-cell or multi-tab edits.
Search for text in a sheet (all cells, all tabs unless restricted). Returns matches as A1 references + row + col + value. Use it to locate rows before reading or updating.
Import an .xlsx file into a new Google Sheet. The .xlsx must be a file you can open in Google Drive (shared with the authenticated account). Returns the new spreadsheet id and url.
The map of a spreadsheet: title, url, and every tab with its row/column counts and frozen-header count. Returns NO cell data, so it costs the same on a 40-row file and a 400,000-row file. ALWAYS call this first on an unfamiliar spreadsheet — it tells you which tab and which ranges to aim at, so you never read more than you need.
Find spreadsheets by name among the files the signed-in account can already open (including ones shared with it). Returns ids, names and last-modified — no cell data, so it is cheap regardless of how big the files are. Use it when the human names a file instead of pasting a link.
Read one or more A1 ranges. Hard-capped at 50000 cells per call — a range over the cap is refused, not truncated.
Update one range (A1 notation). TWO-PHASE: without confirm_token, shows a before/after preview and returns a token. With the token (and same arguments), actually writes. This is the load-bearing rule: a human sees the change and approves before it lands. The token is a fingerprint of (file, range, current state, new values) — if the sheet moved between preview and confirmation, the token no longer matches and a fresh preview is shown.
Verify the credential: returns the Google account that granted the token — the account whose access this connector has, and the identity that will appear in a spreadsheet's edit history. Never prints any credential field. Use this before any write session so the human knows who is about to edit.
List the message templates on the WhatsApp Business Account, with their language, status (APPROVED/PENDING/REJECTED), category, and how many {{n}} parameters each body takes. Call this before whatsapp_send_template — template names are case-sensitive and only APPROVED templates can be sent. Requires WHATSAPP_WABA_ID.
Send a free-form text message. IMPORTANT: this only works inside an open 24-hour customer service window — i.e. within 24h of the recipient last messaging this number. Outside it, Meta rejects the send with error 131047 and you must use whatsapp_send_template instead. This server has no inbound receiver, so whether the window is open cannot be known in advance — attempting the send and reading the error is the intended flow. Free-form messages inside the window are free. Sends a real message to a real person — confirm recipient and content with the user before calling.
Send a pre-approved template message. This is the ONLY message type WhatsApp accepts outside the 24-hour customer service window, so it is the only reliable way to start a conversation with someone who has not messaged first. The template must already be APPROVED (see whatsapp_list_templates). Sends a real, billed message to a real person — confirm the recipient and content with the user before calling.
Verify the WhatsApp credentials: returns the business phone number, verified name, quality rating, and throughput for the configured phone number id. Never prints the token. A falling quality_rating is the early warning before Meta throttles or bans the number.
Confirmation patterns are manual and text-based. sheet_update and sheet_append use a two-phase preview+confirm_token pattern (good), but mail_send and whatsapp_send_template rely on prose instructions ('ALWAYS confirm recipient(s) + subject + FULL body with the user before calling'). LLMs cannot reliably follow implicit confirmation requirements, these should be enforced structurally or through tool annotations.
Ambiguous/overloaded parameters in mail_label and mail_archive. Both tools accept 'message_id OR thread_id (exactly one)' but the JSON Schema shows both as optional (not explicitly mutually exclusive in schema syntax). Descriptions state mutual exclusivity, but LLMs should not rely on prose, schema should encode this as oneOf or an enum discriminator.
No rate limiting or per-call metadata (logLevel, request timeout, cancellation hints). Tools do not declare which ones are slow (e.g., sheet_read on large ranges) or retry-safe. No _meta field in request/response for per-request log level or cancellation tokens as per current MCP spec.
Tool annotations (readOnlyHint, destructiveHint, idempotentHint) are absent from the schema. Mail and sheet operations manually categorize risk (READ_ONLY, WRITE, REVERSIBLE, IRREVERSIBLE) in comments, but MCP 2026-07-28 spec expects formal tool annotations for LLM-facing semantics.