Skip to main content
Glama

bulk_modify

DestructiveIdempotent

Bulk-apply label changes to messages matching a Gmail query. Supports add/remove labels, dry-run rehearsal, and verification for safe mass updates.

Instructions

Bulk-apply label changes to every message matching a Gmail query, batched at 1000 messages per API request. Labels may be given by name or by id: an unknown name in add is created automatically (use '/' for nested labels), an unknown name in remove is ignored. Returns matched/submitted counts, matched and submitted thread IDs (both lists capped at 500 — matchedThreadCount/submittedThreadCount hold the true totals), and per-chunk failures (partial success is reported, not hidden). IMPORTANT: submittedMessages is how many ids were handed to the API, NOT how many messages changed — messages.batchModify answers 204 with no body and ignores ids it does not recognise without a word, so an accepted request is not a performed one. Set verify:true to read the labels back afterwards and get verified {applied, notApplied[], unverifiable[]} — the only field here that reports an observed outcome. It costs one extra read per affected thread, so it is off by default; use it when a wrong 'done' would be acted on (trashing, or anything the user is told completed). If more messages match than maxMessages, only the first maxMessages are processed and 'capped' is true — raise maxMessages or re-run to finish the rest. NOTE: the query hits Gmail's search index as-is, WITHOUT the live re-verification search performs. The staleness that makes search re-verify was measured on threads.list (132 threads returned, 114 carrying no unread message at all); the same query through the message index this tool uses returned 19 hits, none stale — same mailbox, same minute. So the known drift does not reach this path, but that is one measurement, not a guarantee: unverifiedPredicates in the result names the conditions taken on the index's word, and when the outcome must be read-state-precise, resolve the set with search (which verifies against live labels) and act on those thread ids instead. Set crossCheck:true to ask Gmail the same question a second way before writing: each derived predicate is re-run as a label filter (labelIds) instead of a query operator, and any message the two routes disagree about is left untouched and listed in crossChecked.dropped. It costs one extra list per predicate — flat, not per message — so unlike verify it stays cheap on a large sweep. Read it as a contradiction detector: a disagreement is real, agreement proves nothing, because both routes read the same index. unverifiedPredicates therefore stays as it is even when this runs. A capped match set is not cross-checked at all (crossChecked.capped), since a message missing from a page is not a message missing the label. Set dryRun:true to rehearse: the same query resolution, matched counts/threads and the labels that would be created — and no message or label is touched. A dry run reads the SAME unverified index, so it confirms the size of the set, never its correctness. USE WHEN: mass operations — 'archive all newsletters older than 30 days' (query + remove INBOX), bulk labeling, bulk mark-read; dryRun first when the query is broad or the user should see the set before it changes. DO NOT USE: for a single thread (use modify_labels or the dedicated tools), or with neither add nor remove. SIDE EFFECTS: modifies up to maxMessages messages in one call (none with dryRun); label changes are reversible by the inverse call.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
addNo
queryYes
dryRunNo
removeNo
verifyNo
crossCheckNo
maxMessagesNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
cappedYes
dryRunYes
failedYes
verifiedNo
crossCheckedNo
labelsToCreateNo
matchedThreadsYes
matchedMessagesYes
submittedThreadsYes
submittedMessagesYes
matchedThreadCountYes
submittedThreadCountYes
unverifiedPredicatesYes

Schema Changelog

Changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. Changed2 schema fields changedv0.18.0
    • addedInput schema / properties / crossCheck
      Added value: +{
      +  "default": false,
      +  "type": "boolean"
      +}
    • addedOutput schema / properties / crossChecked
      Added value: +{
      +  "additionalProperties": false,
      +  "properties": {
      +    "capped": {
      +      "type": "boolean"
      +    },
      +    "dropped": {
      +      "items": {
      +        "additionalProperties": false,
      +        "properties": {
      +          "id": {
      +            "type": "string"
      +          },
      +          "predicate": {
      +            "type": "string"
      +          },
      +          "threadId": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "id",
      +          "threadId",
      +          "predicate"
      +        ],
      +        "type": "object"
      +      },
      +      "type": "array"
      +    },
      +    "droppedMessages": {
      +      "type": "number"
      +    },
      +    "predicates": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    "skipped": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    }
      +  },
      +  "required": [
      +    "predicates",
      +    "droppedMessages",
      +    "dropped",
      +    "capped"
      +  ],
      +  "type": "object"
      +}
  2. Changed9 schema fields changedv0.15.1
    • addedInput schema / properties / verify
      Added value: +{
      +  "default": false,
      +  "type": "boolean"
      +}
    • removedOutput schema / properties / modifiedMessages
      Removed value: -{
      -  "type": "number"
      -}
    • removedOutput schema / properties / modifiedThreadCount
      Removed value: -{
      -  "type": "number"
      -}
    • removedOutput schema / properties / modifiedThreads
      Removed value: -{
      -  "items": {
      -    "type": "string"
      -  },
      -  "type": "array"
      -}
    • addedOutput schema / properties / submittedMessages
      Added value: +{
      +  "type": "number"
      +}
    • addedOutput schema / properties / submittedThreadCount
      Added value: +{
      +  "type": "number"
      +}
    • addedOutput schema / properties / submittedThreads
      Added value: +{
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / verified
      Added value: +{
      +  "additionalProperties": false,
      +  "properties": {
      +    "applied": {
      +      "type": "number"
      +    },
      +    "notApplied": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    },
      +    "unverifiable": {
      +      "items": {
      +        "type": "string"
      +      },
      +      "type": "array"
      +    }
      +  },
      +  "required": [
      +    "applied",
      +    "notApplied",
      +    "unverifiable"
      +  ],
      +  "type": "object"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "dryRun",
      -  "matchedMessages",
      -  "matchedThreadCount",
      -  "matchedThreads",
      -  "modifiedMessages",
      -  "modifiedThreadCount",
      -  "modifiedThreads",
      -  "capped",
      -  "unverifiedPredicates",
      -  "failed"
      -]New value: +[
      +  "dryRun",
      +  "matchedMessages",
      +  "matchedThreadCount",
      +  "matchedThreads",
      +  "submittedMessages",
      +  "submittedThreadCount",
      +  "submittedThreads",
      +  "capped",
      +  "unverifiedPredicates",
      +  "failed"
      +]
  3. Changed7 schema fields changedv0.11.0
    • addedInput schema / properties / dryRun
      Added value: +{
      +  "default": false,
      +  "type": "boolean"
      +}
    • addedOutput schema / properties / dryRun
      Added value: +{
      +  "type": "boolean"
      +}
    • addedOutput schema / properties / labelsToCreate
      Added value: +{
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / matchedThreadCount
      Added value: +{
      +  "type": "number"
      +}
    • addedOutput schema / properties / matchedThreads
      Added value: +{
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • addedOutput schema / properties / unverifiedPredicates
      Added value: +{
      +  "items": {
      +    "type": "string"
      +  },
      +  "type": "array"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "matchedMessages",
      -  "modifiedMessages",
      -  "modifiedThreadCount",
      -  "modifiedThreads",
      -  "capped",
      -  "failed"
      -]New value: +[
      +  "dryRun",
      +  "matchedMessages",
      +  "matchedThreadCount",
      +  "matchedThreads",
      +  "modifiedMessages",
      +  "modifiedThreadCount",
      +  "modifiedThreads",
      +  "capped",
      +  "unverifiedPredicates",
      +  "failed"
      +]
  4. Changed1 schema field changedv0.6.0
    • addedInput schema / properties / query / minLength
      Added value: +1
  5. Changed2 schema fields changedv0.1.9
    • addedOutput schema / properties / capped
      Added value: +{
      +  "type": "boolean"
      +}
    • changedOutput schema / required
      Previous value: -[
      -  "matchedMessages",
      -  "modifiedMessages",
      -  "modifiedThreadCount",
      -  "modifiedThreads",
      -  "failed"
      -]New value: +[
      +  "matchedMessages",
      +  "modifiedMessages",
      +  "modifiedThreadCount",
      +  "modifiedThreads",
      +  "capped",
      +  "failed"
      +]
  6. First observedv0.1.8

TDQS

A5/5.0
Behavior5/5

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

The description goes far beyond the annotations by disclosing partial success reporting, the 204-with-no-body caveat of messages.batchModify, the difference between submitted and applied changes, verification costs, index staleness measurements, and the exact meaning of verified and crossChecked results. The destructiveHint and idempotentHint annotations are consistent with the described reversible but mutating behavior.

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

Conciseness5/5

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

Though long, the description is information-dense with no filler; every sentence addresses a real operational concern for a high-risk bulk mutation tool. It is structured with clear sections (IMPORTANT, USE WHEN, DO NOT USE, SIDE EFFECTS) that make the content scannable, and the core purpose is front-loaded.

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

Completeness5/5

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

Given 7 parameters, bulk effects, subtle Gmail API behavior, and a rich output schema, the description is exceptionally complete: it covers result fields, capping behavior, failure reporting, verification options, costs, index staleness, alternatives, and side effects. An agent has enough context to decide whether to call it, set flags appropriately, and interpret results correctly.

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

Parameters5/5

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

Schema description coverage is 0%, so the description fully compensates by explaining every parameter: query, add/remove label creation and unknown-name handling, maxMessages capping, dryRun rehearsal behavior, verify's read-back semantics, and crossCheck's second query route. It adds meaning far beyond the bare schema types and defaults.

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

Purpose5/5

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

The description opens with a specific verb and resource: 'Bulk-apply label changes to every message matching a Gmail query, batched at 1000 messages per API request.' It clearly differentiates from sibling tools in DO NOT USE, naming modify_labels and dedicated single-thread tools, so an agent can select it correctly.

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

Usage Guidelines5/5

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

The USE WHEN section explicitly lists the intended scenarios ('mass operations — archive all newsletters older than 30 days, bulk labeling, bulk mark-read') and advises dryRun first for broad queries. The DO NOT USE section gives concrete exclusions and alternatives, including not using it for a single thread and requiring at least one of add or remove.

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

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/csitte/mailwarden'

If you have feedback or need assistance with the MCP directory API, please join our Discord server