Skip to main content
Glama
aaronsb

Google Workspace MCP Server

by aaronsb

manage_email

Search, read, send, forward, and manage Gmail emails using Gmail search syntax. Organize messages with labels, filters, and bulk actions.

Instructions

Search, read, send, forward, or manage emails in a Google Workspace account. Supports Gmail search syntax.

Input Schema

NameRequiredDescriptionDefault
ccNoCC email(s), comma-separated. On `replyAll` these are ADDITIONAL — they do not replace the recipients already on the thread.
toNoRecipient email(s), comma-separated
bccNoBCC email(s), comma-separated
bodyNoEmail body text. On `forward`, an optional note prepended ABOVE the forwarded message.
fromNoSender email or RFC 2822 mailbox for a verified Gmail Send As alias
htmlNoTreat body as HTML content (default: plain text)
nameNoLabel name, e.g. 'REC/Receipt'. Nested labels use a slash; create the parent first if Gmail shows nesting.
draftNoSave as draft instead of sending (default: false, forced true when attachments present)
emailYesAccount email address
queryNoGmail search query (e.g. "from:alice subject:meeting has:attachment")
draftIdNoDraft id as returned by listDrafts (an r… id, not the message id)
idsOnlyNoOn `search`: return only message ids, up to 500 per page, without fetching each message's headers. For counting a large result or collecting ids to pass to bulk_operations batch mode (e.g. modify).
matchToNoFilter criterion: recipient, e.g. a list or alias address
subjectNoEmail subject line
filenameNoAttachment filename, exactly as it appears in the `read` response (e.g. 'invoice_template.md')
filterIdNoFilter id, as listed by `filters`. On previewFilter, the filter a change would replace; leave it out to preview a new filter.
threadIdNoThread ID to retrieve
forwardToNoFilter action: forward each match to this address. It must already be a verified forwarding address in Gmail settings.
matchFromNoFilter criterion: sender, e.g. billing@vendor.com or @vendor.com
matchSizeNoFilter criterion: size in bytes; requires matchSizeComparison
messageIdNoEmail message ID
operationYessearch: find emails by query. Says when more results exist than the page shows; continue with pageToken, or pass idsOnly to collect up to 500 ids per page for bulk_operations. | read: get full email by ID. Pass bodyFormat: 'html' when the plain-text part is a stub (marketing email, invitations, booking confirmations) and the dates/codes you need only exist in the HTML — returned HTML is sanitized and wrapped in a Spotlighting block (ADR-305). | archive: save an email's headers and body to a markdown file in the workspace — a LOCAL archive, not Gmail's 'remove from inbox' (attachments are listed, not downloaded) | send: compose and send a new email (creates draft when attachments present, 35MB attachment limit via upload endpoint) | reply: reply to a message (thread-aware, creates draft when attachments present) | replyAll: reply-all to a message (thread-aware, includes all recipients, creates draft when attachments present) | forward: forward a message to new recipients (includes original attachments by default) | triage: unread inbox summary (sender, subject, date) | trash: move a message to trash | untrash: restore a message from trash | listDrafts: list saved drafts (unsent messages) with each draft's DRAFT id (the r… id), recipient, subject and date. Use that draft id with deleteDraft — a message id from search in:drafts will not work for deletion. | deleteDraft: permanently delete a draft (unsent message) by its draft id (the r… id returned by listDrafts). Irreversible — the draft is discarded, not moved to trash. | getAttachment: download an email attachment to workspace directory (use read first to see attachment list) | viewAttachment: view an image attachment inline without saving to workspace (png, jpg, gif, webp). Use for quick preview — use getAttachment to save. | modify: add or remove labels on a message (e.g. archive, mark read/unread) | previewModifyMatching: evaluate a label change over everything a query matches, without making it: the exact count (up to 10,000), a sample, and what the change does to each message. Holds the matched set and returns the previewToken modifyMatching needs. No ids are returned beyond the sample. | modifyMatching: apply a previewed label change (archive, mark read, label, trash) to exactly the messages previewModifyMatching counted, and report how many changed. Requires its previewToken from an earlier call, within 30 minutes; each preview applies once. | labels: list all labels in the mailbox | createLabel: create a label. A slash in the name makes it nested (e.g. 'REC/Receipt' under 'REC'). Fails if the name already exists. Use `labels` first to check, then pass the returned label ID to `modify`. | filters: list the account's filters: id, criteria and what each does. Gmail allows 1,000. | getFilter: one filter's criteria and actions in words, plus the parameters that recreate or edit it | previewFilter: evaluate a filter without creating it: how many messages it matches today and a sample, what each action does, and any duplicate or overlapping filter. Returns the previewToken createFilter needs; with filterId, previews replacing that filter and returns the token updateFilter needs. Criteria are the match* params; actions are addLabelIds, removeLabelIds and forwardTo. A good criterion is the query of a search that already found the mail. | createFilter: create a filter. Requires the previewToken from previewFilter called with the same parameters, in an earlier call, within 30 minutes. Acts on mail arriving from then on. | updateFilter: change a filter's criteria or actions. Gmail has no in-place update, so this creates the replacement and then deletes the original; the filter gets a new id. Requires the previewToken from previewFilter called with filterId and the same parameters. | deleteFilter: delete a filter. Returns what it was and the parameters to recreate it. | threads: list email threads by query | getThread: get all messages in a thread
pageTokenNoContinue a list from where the previous page stopped: pass the pageToken its response gave. Absent from a response means that response was the last page.
bodyFormatNoHow to render the message body. 'plain' (default) returns text/plain or a crude HTML-strip fallback. 'html' returns the text/html part sanitized — useful when the plain-text part is empty/stub.
matchQueryNoFilter criterion: any Gmail search query the message must match, same syntax as search
maxResultsNoMax results per page (default 10, max 50). On `search` with idsOnly, default and max 500.
outputPathNoLocal path to save the markdown file (workspace-relative; defaults to a slug from the message date and subject)
addLabelIdsNoComma-separated label IDs to add (e.g. STARRED, IMPORTANT, Label_123). On modify, to this message; on the filter operations, to each matching message as it arrives; on the *ModifyMatching operations, to every message the preview counted.
attachmentsNoWorkspace filenames to attach, comma-separated (files must exist in workspace via manage_workspace). Creates draft when present.
matchSubjectNoFilter criterion: words in the subject
previewTokenNoThe token the preview returned for this exact change: previewFilter for createFilter and updateFilter, previewModifyMatching for modifyMatching. Required: the write runs only after its preview has been seen.
removeLabelIdsNoComma-separated label IDs to remove (e.g. UNREAD to mark read, INBOX to archive). On modify, from this message; on the filter operations, from each matching message as it arrives; on the *ModifyMatching operations, from every message the preview counted.
matchExcludeChatsNoFilter criterion: leave chat messages out
matchNegatedQueryNoFilter criterion: a Gmail search query the message must NOT match
matchHasAttachmentNoFilter criterion: only messages with an attachment
labelListVisibilityNoWhether the label appears in the label list: labelShow (default), labelShowIfUnread, or labelHide
matchSizeComparisonNoFilter criterion: whether matches are larger or smaller than matchSize
messageListVisibilityNoWhether the label appears on messages in the message list: show (default) or hide

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed19 schema fields changedv4.8.0
    • changedInput schema / properties / addLabelIds / description
      Previous value: -"Comma-separated label IDs to add (e.g. STARRED, IMPORTANT, Label_123)"New value: +"Comma-separated label IDs to add (e.g. STARRED, IMPORTANT, Label_123). On modify, to this message; on the filter operations, to each matching message as it arrives; on the *ModifyMatching operations, to every message the preview counted."
    • addedInput schema / properties / filterId
      Added value: +{
      +  "description": "Filter id, as listed by `filters`. On previewFilter, the filter a change would replace; leave it out to preview a new filter.",
      +  "type": "string"
      +}
    • addedInput schema / properties / forwardTo
      Added value: +{
      +  "description": "Filter action: forward each match to this address. It must already be a verified forwarding address in Gmail settings.",
      +  "type": "string"
      +}
    • addedInput schema / properties / idsOnly
      Added value: +{
      +  "description": "On `search`: return only message ids, up to 500 per page, without fetching each message's headers. For counting a large result or collecting ids to pass to bulk_operations batch mode (e.g. modify).",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / matchExcludeChats
      Added value: +{
      +  "description": "Filter criterion: leave chat messages out",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / matchFrom
      Added value: +{
      +  "description": "Filter criterion: sender, e.g. billing@vendor.com or @vendor.com",
      +  "type": "string"
      +}
    • addedInput schema / properties / matchHasAttachment
      Added value: +{
      +  "description": "Filter criterion: only messages with an attachment",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / matchNegatedQuery
      Added value: +{
      +  "description": "Filter criterion: a Gmail search query the message must NOT match",
      +  "type": "string"
      +}
    • addedInput schema / properties / matchQuery
      Added value: +{
      +  "description": "Filter criterion: any Gmail search query the message must match, same syntax as search",
      +  "type": "string"
      +}
    • addedInput schema / properties / matchSize
      Added value: +{
      +  "description": "Filter criterion: size in bytes; requires matchSizeComparison",
      +  "type": "number"
      +}
    • addedInput schema / properties / matchSizeComparison
      Added value: +{
      +  "description": "Filter criterion: whether matches are larger or smaller than matchSize",
      +  "enum": [
      +    "larger",
      +    "smaller"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / matchSubject
      Added value: +{
      +  "description": "Filter criterion: words in the subject",
      +  "type": "string"
      +}
    • addedInput schema / properties / matchTo
      Added value: +{
      +  "description": "Filter criterion: recipient, e.g. a list or alias address",
      +  "type": "string"
      +}
    • changedInput schema / properties / maxResults / description
      Previous value: -"Max results (default: 10, max: 50)"New value: +"Max results per page (default 10, max 50). On `search` with idsOnly, default and max 500."
    • changedInput schema / properties / operation / description
      Previous value: -"search: find emails by query | read: get full email by ID. Pass bodyFormat: 'html' when the plain-text part is a stub (marketing email, invitations, booking confirmations) and the dates/codes you need only exist in the HTML — returned HTML is sanitized and wrapped in a Spotlighting block (ADR-305). | archive: save an email's headers and body to a markdown file in the workspace — a LOCAL archive, not Gmail's 'remove from inbox' (attachments are listed, not downloaded) | send: compose and send a new email (creates draft when attachments present, 35MB attachment limit via upload endpoint) | reply: reply to a message (thread-aware, creates draft when attachments present) | replyAll: reply-all to a message (thread-aware, includes all recipients, creates draft when attachments present) | forward: forward a message to new recipients (includes original attachments by default) | triage: unread inbox summary (sender, subject, date) | trash: move a message to trash | untrash: restore a message from trash | listDrafts: list saved drafts (unsent messages) with each draft's DRAFT id (the r… id), recipient, subject and date. Use that draft id with deleteDraft — a message id from search in:drafts will not work for deletion. | deleteDraft: permanently delete a draft (unsent message) by its draft id (the r… id returned by listDrafts). Irreversible — the draft is discarded, not moved to trash. | getAttachment: download an email attachment to workspace directory (use read first to see attachment list) | viewAttachment: view an image attachment inline without saving to workspace (png, jpg, gif, webp). Use for quick preview — use getAttachment to save. | modify: add or remove labels on a message (e.g. archive, mark read/unread) | labels: list all labels in the mailbox | createLabel: create a label. A slash in the name makes it nested (e.g. 'REC/Receipt' under 'REC'). Fails if the name already exists. Use `labels` first to check, then pass the returned label ID to `modify`. | threads: list email threads by query | getThread: get all messages in a thread"New value: +"search: find emails by query. Says when more results exist than the page shows; continue with pageToken, or pass idsOnly to collect up to 500 ids per page for bulk_operations. | read: get full email by ID. Pass bodyFormat: 'html' when the plain-text part is a stub (marketing email, invitations, booking confirmations) and the dates/codes you need only exist in the HTML — returned HTML is sanitized and wrapped in a Spotlighting block (ADR-305). | archive: save an email's headers and body to a markdown file in the workspace — a LOCAL archive, not Gmail's 'remove from inbox' (attachments are listed, not downloaded) | send: compose and send a new email (creates draft when attachments present, 35MB attachment limit via upload endpoint) | reply: reply to a message (thread-aware, creates draft when attachments present) | replyAll: reply-all to a message (thread-aware, includes all recipients, creates draft when attachments present) | forward: forward a message to new recipients (includes original attachments by default) | triage: unread inbox summary (sender, subject, date) | trash: move a message to trash | untrash: restore a message from trash | listDrafts: list saved drafts (unsent messages) with each draft's DRAFT id (the r… id), recipient, subject and date. Use that draft id with deleteDraft — a message id from search in:drafts will not work for deletion. | deleteDraft: permanently delete a draft (unsent message) by its draft id (the r… id returned by listDrafts). Irreversible — the draft is discarded, not moved to trash. | getAttachment: download an email attachment to workspace directory (use read first to see attachment list) | viewAttachment: view an image attachment inline without saving to workspace (png, jpg, gif, webp). Use for quick preview — use getAttachment to save. | modify: add or remove labels on a message (e.g. archive, mark read/unread) | previewModifyMatching: evaluate a label change over everything a query matches, without making it: the exact count (up to 10,000), a sample, and what the change does to each message. Holds the matched set and returns the previewToken modifyMatching needs. No ids are returned beyond the sample. | modifyMatching: apply a previewed label change (archive, mark read, label, trash) to exactly the messages previewModifyMatching counted, and report how many changed. Requires its previewToken from an earlier call, within 30 minutes; each preview applies once. | labels: list all labels in the mailbox | createLabel: create a label. A slash in the name makes it nested (e.g. 'REC/Receipt' under 'REC'). Fails if the name already exists. Use `labels` first to check, then pass the returned label ID to `modify`. | filters: list the account's filters: id, criteria and what each does. Gmail allows 1,000. | getFilter: one filter's criteria and actions in words, plus the parameters that recreate or edit it | previewFilter: evaluate a filter without creating it: how many messages it matches today and a sample, what each action does, and any duplicate or overlapping filter. Returns the previewToken createFilter needs; with filterId, previews replacing that filter and returns the token updateFilter needs. Criteria are the match* params; actions are addLabelIds, removeLabelIds and forwardTo. A good criterion is the query of a search that already found the mail. | createFilter: create a filter. Requires the previewToken from previewFilter called with the same parameters, in an earlier call, within 30 minutes. Acts on mail arriving from then on. | updateFilter: change a filter's criteria or actions. Gmail has no in-place update, so this creates the replacement and then deletes the original; the filter gets a new id. Requires the previewToken from previewFilter called with filterId and the same parameters. | deleteFilter: delete a filter. Returns what it was and the parameters to recreate it. | threads: list email threads by query | getThread: get all messages in a thread"
    • changedInput schema / properties / operation / enum
      Previous value: -[
      -  "search",
      -  "read",
      -  "archive",
      -  "send",
      -  "reply",
      -  "replyAll",
      -  "forward",
      -  "triage",
      -  "trash",
      -  "untrash",
      -  "listDrafts",
      -  "deleteDraft",
      -  "getAttachment",
      -  "viewAttachment",
      -  "modify",
      -  "labels",
      -  "createLabel",
      -  "threads",
      -  "getThread"
      -]New value: +[
      +  "search",
      +  "read",
      +  "archive",
      +  "send",
      +  "reply",
      +  "replyAll",
      +  "forward",
      +  "triage",
      +  "trash",
      +  "untrash",
      +  "listDrafts",
      +  "deleteDraft",
      +  "getAttachment",
      +  "viewAttachment",
      +  "modify",
      +  "previewModifyMatching",
      +  "modifyMatching",
      +  "labels",
      +  "createLabel",
      +  "filters",
      +  "getFilter",
      +  "previewFilter",
      +  "createFilter",
      +  "updateFilter",
      +  "deleteFilter",
      +  "threads",
      +  "getThread"
      +]
    • addedInput schema / properties / pageToken
      Added value: +{
      +  "description": "Continue a list from where the previous page stopped: pass the pageToken its response gave. Absent from a response means that response was the last page.",
      +  "type": "string"
      +}
    • addedInput schema / properties / previewToken
      Added value: +{
      +  "description": "The token the preview returned for this exact change: previewFilter for createFilter and updateFilter, previewModifyMatching for modifyMatching. Required: the write runs only after its preview has been seen.",
      +  "type": "string"
      +}
    • changedInput schema / properties / removeLabelIds / description
      Previous value: -"Comma-separated label IDs to remove (e.g. UNREAD, INBOX to archive)"New value: +"Comma-separated label IDs to remove (e.g. UNREAD to mark read, INBOX to archive). On modify, from this message; on the filter operations, from each matching message as it arrives; on the *ModifyMatching operations, from every message the preview counted."
  2. Changed6 schema fields changedv4.6.0
    • addedInput schema / properties / draftId
      Added value: +{
      +  "description": "Draft id as returned by listDrafts (an r… id, not the message id)",
      +  "type": "string"
      +}
    • addedInput schema / properties / labelListVisibility
      Added value: +{
      +  "description": "Whether the label appears in the label list: labelShow (default), labelShowIfUnread, or labelHide",
      +  "enum": [
      +    "labelShow",
      +    "labelShowIfUnread",
      +    "labelHide"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / messageListVisibility
      Added value: +{
      +  "description": "Whether the label appears on messages in the message list: show (default) or hide",
      +  "enum": [
      +    "show",
      +    "hide"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / name
      Added value: +{
      +  "description": "Label name, e.g. 'REC/Receipt'. Nested labels use a slash; create the parent first if Gmail shows nesting.",
      +  "type": "string"
      +}
    • changedInput schema / properties / operation / description
      Previous value: -"search: find emails by query | read: get full email by ID. Pass bodyFormat: 'html' when the plain-text part is a stub (marketing email, invitations, booking confirmations) and the dates/codes you need only exist in the HTML — returned HTML is sanitized and wrapped in a Spotlighting block (ADR-305). | archive: save an email's headers and body to a markdown file in the workspace — a LOCAL archive, not Gmail's 'remove from inbox' (attachments are listed, not downloaded) | send: compose and send a new email (creates draft when attachments present, 35MB attachment limit via upload endpoint) | reply: reply to a message (thread-aware, creates draft when attachments present) | replyAll: reply-all to a message (thread-aware, includes all recipients, creates draft when attachments present) | forward: forward a message to new recipients (includes original attachments by default) | triage: unread inbox summary (sender, subject, date) | trash: move a message to trash | untrash: restore a message from trash | getAttachment: download an email attachment to workspace directory (use read first to see attachment list) | viewAttachment: view an image attachment inline without saving to workspace (png, jpg, gif, webp). Use for quick preview — use getAttachment to save. | modify: add or remove labels on a message (e.g. archive, mark read/unread) | labels: list all labels in the mailbox | threads: list email threads by query | getThread: get all messages in a thread"New value: +"search: find emails by query | read: get full email by ID. Pass bodyFormat: 'html' when the plain-text part is a stub (marketing email, invitations, booking confirmations) and the dates/codes you need only exist in the HTML — returned HTML is sanitized and wrapped in a Spotlighting block (ADR-305). | archive: save an email's headers and body to a markdown file in the workspace — a LOCAL archive, not Gmail's 'remove from inbox' (attachments are listed, not downloaded) | send: compose and send a new email (creates draft when attachments present, 35MB attachment limit via upload endpoint) | reply: reply to a message (thread-aware, creates draft when attachments present) | replyAll: reply-all to a message (thread-aware, includes all recipients, creates draft when attachments present) | forward: forward a message to new recipients (includes original attachments by default) | triage: unread inbox summary (sender, subject, date) | trash: move a message to trash | untrash: restore a message from trash | listDrafts: list saved drafts (unsent messages) with each draft's DRAFT id (the r… id), recipient, subject and date. Use that draft id with deleteDraft — a message id from search in:drafts will not work for deletion. | deleteDraft: permanently delete a draft (unsent message) by its draft id (the r… id returned by listDrafts). Irreversible — the draft is discarded, not moved to trash. | getAttachment: download an email attachment to workspace directory (use read first to see attachment list) | viewAttachment: view an image attachment inline without saving to workspace (png, jpg, gif, webp). Use for quick preview — use getAttachment to save. | modify: add or remove labels on a message (e.g. archive, mark read/unread) | labels: list all labels in the mailbox | createLabel: create a label. A slash in the name makes it nested (e.g. 'REC/Receipt' under 'REC'). Fails if the name already exists. Use `labels` first to check, then pass the returned label ID to `modify`. | threads: list email threads by query | getThread: get all messages in a thread"
    • changedInput schema / properties / operation / enum
      Previous value: -[
      -  "search",
      -  "read",
      -  "archive",
      -  "send",
      -  "reply",
      -  "replyAll",
      -  "forward",
      -  "triage",
      -  "trash",
      -  "untrash",
      -  "getAttachment",
      -  "viewAttachment",
      -  "modify",
      -  "labels",
      -  "threads",
      -  "getThread"
      -]New value: +[
      +  "search",
      +  "read",
      +  "archive",
      +  "send",
      +  "reply",
      +  "replyAll",
      +  "forward",
      +  "triage",
      +  "trash",
      +  "untrash",
      +  "listDrafts",
      +  "deleteDraft",
      +  "getAttachment",
      +  "viewAttachment",
      +  "modify",
      +  "labels",
      +  "createLabel",
      +  "threads",
      +  "getThread"
      +]
  3. First observedv4.5.0

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden, and the two sentences disclose nothing: no mention that deleteDraft/deleteFilter are irreversible, no preview-token gating, no draft-creation-on-attachment behavior, no permission or alias-verification requirements. The valuable behavioral detail lives only inside the operation enum's schema description, which does not compensate at the tool-description level.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with no filler and the verb list front-loaded, so there is no waste. However, for a 27-operation, 38-parameter tool the size is disproportionately small and there is no structure signaling that an operation parameter drives everything, leaving the summary under-specified rather than genuinely concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The schema is exceptionally rich (100% param coverage, a long per-operation enum description) and there is no output schema to explain, which lowers the bar considerably. Still, the description never states the single most important fact — that this is an operation-dispatch tool requiring 'operation' and 'email' — and offers no cross-tool routing, so it is only minimally complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and each parameter carries an unusually rich description (including per-operation semantics for addLabelIds, removeLabelIds, draft, pageToken, previewToken). The description adds essentially nothing beyond 'Supports Gmail search syntax', which the query parameter already documents, so the baseline 3 for fully covered schemas is correct.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific resource (emails in a Google Workspace account) and enumerates the core verbs (search, read, send, forward, manage), and 'Supports Gmail search syntax' pins the domain. It is clearly distinguishable from manage_calendar/manage_drive by resource, though it never acknowledges the operation-dispatch model or differentiates itself from bulk_operations for bulk mail changes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no when-to-use guidance: nothing says when to choose this tool over bulk_operations for bulk label changes, or when queue_operations is preferable for large campaigns. The Gmail-syntax note is a hint about one operation, not a routing rule, and no exclusions or prerequisites are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.