Skip to content

Commit bb9b036

Browse files
ashwins01dannyrooseveltclaudemichelle0927Michelle Bergeron
authored
[21079] feat(gmail): add delete-label action (#21099)
* gmail: AI-optimized action set for MCP Three new actions (find-emails, get-thread, modify-labels) plus flattened schemas on existing write actions (send-email, create-draft, download-attachment) so every parameter is statically visible to MCP — no more additionalProps / reloadProps / async-options dropdowns. - find-emails: native Gmail query syntax (`q`), `format` defaults to `metadata` (headers + snippet only) so large inboxes don't blow through the token budget; responses hard-capped at 100k chars with a `truncated: true` marker. - get-thread: fetch an entire conversation by threadId; `metadata` vs `full` format. - modify-labels: one tool replacing add-label / remove-label / archive / bulk-archive / delete — archive is "remove INBOX", trash is "add TRASH", star is "add STARRED", etc. Accepts label names or IDs. - send-email / create-draft: replyAll is now a top-level static prop; new `attachmentContent` (inline text) prop for MCP/cloud runs with no local filesystem; `to`/`cc`/`bcc` accept `"me"` as a self-addressing shortcut (resolved to the authenticated user's email address inside the action). - create-label: colors are optional; idempotent on 409 (looks up the existing label by name and returns it with `alreadyExisted: true`). - download-attachment: messageId/attachmentId are plain strings instead of async-options dropdowns; convertToPdf behaviour preserved. - Shared parameters (attachments, attachmentFilenames, inReplyToMessageId, replyAll) defined once on gmail.app.mjs propDefinitions; a resolveMe() helper on the app expands "me" → auth'd email for recipient arrays. Breaking schema changes on existing actions: - send-email / create-draft: `inReplyTo` → `inReplyToMessageId`, `attachmentUrlsOrPaths` → `attachments`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * gmail: bump patch versions on components that depend on gmail.app.mjs The app file changed (new propDefinitions, resolveMe helper, getMessage signature), so every component that imports it needs a patch bump per the component-versions CI check. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * gmail: consolidate duplicated actions into AI-optimized set Collapse the legacy/new-file duplication left from the initial MCP rewrite. Two 1:1 pairs merged in place (legacy key preserved, new implementation wins), and five legacy label/archive/delete actions that `modify-labels` fully supersedes are deleted. - find-emails -> find-email (key preserved, version 0.1.11 -> 0.2.0, display name "Find Emails"). Breaking prop changes: `labels` -> `labelIds`; `metadataOnly`+`withTextPayload` -> `format: metadata|full`; default maxResults 20 -> 25. - get-thread -> list-thread-messages (key preserved, version 0.0.3 -> 0.1.0, display name "Get Thread"). Now returns the full thread object {id, historyId, messages[]} instead of the bare messages[] array; adds optional `format` prop. - Deleted: add-label-to-email, remove-label-from-email, archive-email, bulk-archive-emails, delete-email — all strict subsets of gmail-modify-labels (batchModify with addLabels/removeLabels). - Fixed a "Find Email" -> "Find Emails" cross-reference in get-current-user's description. - Package version 1.5.2 -> 1.5.3. Final AI-optimized action set: 9 files, zero duplicates. Existing workflows using the preserved keys continue to resolve to the prior published version until users opt into the new version. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * gmail: address CodeRabbit review on mcp/gmail Fixes for the actionable findings from CodeRabbit's two-round review: - download-attachment: sanitize `filename` via path.basename before path.join(stashDir, filename), rejecting traversal or separators (critical — crafted attachment name could escape stash dir); walk message payload parts recursively so nested multipart attachments resolve their MIME type instead of silently falling through. - find-email: replace the "is-uppercase" label-resolution heuristic with a proper byId/byName lookup so user labels like VIP or LEGAL resolve correctly; use the shared utils.getHeader helper instead of an inline closure. - list-thread-messages: route through this.gmail.getThread({threadId, format}) instead of bypassing the app abstraction via _client(); add the same 100k-char response cap used by find-email, with a fallback that degrades from `full` to `metadata`-level detail and then trims from the tail if still oversized. Decoration moved to a method that uses utils.getHeader. - modify-labels: destructiveHint: false (batchModify is reversible label mutation, not a permanent delete); same byId/byName label resolution as find-email. - send-email: throw ConfigurationError (not bare Error) for the attachments/attachmentFilenames length mismatch — matches @pipedream/platform convention for pre-flight input validation. - gmail.app.mjs: getThread() now accepts an optional `format` param, forwarded to users.threads.get, so list-thread-messages no longer needs _client() access; narrow the reply-threading catch block so only 404s fall back to using inReplyToMessageId as threadId — other errors (auth, rate limit, network) rethrow instead of silently producing a mis-threaded send. - common/utils.mjs: add getHeader(headers, name) — shared, case-insensitive header lookup used by find-email and list-thread-messages. Re-ran the 9 gmail evals: 9/9 pass, 100% tool coverage. Nits intentionally not addressed: - package.json major bump to 2.0.0 — breaking prop renames in the Pipedream monorepo consistently ship under minor/patch bumps. - removing the unused `$` parameter from getOptionsToSendEmail — changing a shared app method signature for style alone is not worth the downstream churn. - O(n²) JSON.stringify in find-email truncation — bounded by maxResults (default 25), negligible. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * gmail: address michelle + coderabbit round-2 review Incorporating michelle0927's review feedback and the follow-up CodeRabbit findings that landed after the first fix pass. - package.json: 1.5.3 -> 2.0.0. Michelle requested the major bump given the public prop contract changes (attachments rename, inReplyToMessageId, top-level replyAll, etc.) — reversing my earlier call to keep it minor. - Version corrections to match the original build-verification plan: create-draft 0.3.0 -> 0.2.0, create-label 0.2.0 -> 0.1.0, send-email 0.4.0 -> 0.3.0. - create-draft: import ConfigurationError from @pipedream/platform and use it (not bare Error) for the attachments/attachmentFilenames length mismatch — matches the send-email fix from the previous round. - gmail.app.mjs getOptionsToSendEmail: drop the unused `$` first parameter; updated all three callers (send-email, create-draft, approve-workflow) to match. CodeRabbit confirmed to Michelle that the method body never referenced `$`. approve-workflow patch-bumped (0.0.14 -> 0.0.15) since its call site changed. - download-attachment: escape HTML special chars (`&`, `<`, `>`) before wrapping plain-text attachments in <pre>...</pre> for PDF conversion. Prevents code/XML/HTML-source attachments from being silently corrupted (e.g. "<div>" swallowed as markup in the rendered PDF). New `escapeHtml` helper in the methods object. Re-ran evals: 9/9 pass, 100% tool coverage. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * versions * coderabbit suggestions * feat(gmail): add delete-label action * chore(gmail): update package version * fix(gmail): address coderabbit review comments * chore(gmail): update package versions --------- Co-authored-by: Danny Roosevelt <danny@pipedream.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: michelle0927 <michelle0927@users.noreply.github.com> Co-authored-by: Michelle Bergeron <michelle.bergeron@ahp7jvtgvfr.celonis.cloud>
1 parent ab130a8 commit bb9b036

24 files changed

Lines changed: 66 additions & 22 deletions

File tree

components/gmail/actions/approve-workflow/approve-workflow.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default {
44
key: "gmail-approve-workflow",
55
name: "Approve Workflow",
66
description: "Suspend the workflow until approved by email. [See the documentation](https://pipedream.com/docs/code/nodejs/rerun#flowsuspend)",
7-
version: "0.0.15",
7+
version: "0.0.16",
88
annotations: {
99
destructiveHint: false,
1010
openWorldHint: true,

components/gmail/actions/create-draft/create-draft.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default {
1111
+ " To draft to yourself, pass `\"me\"` in `to` — the action resolves it to the authenticated user's email address. No pre-call to **Get Current User** required."
1212
+ " Attachments use `file-ref` inputs and require matching `attachmentFilenames[]` entries."
1313
+ " [See the documentation](https://developers.google.com/gmail/api/reference/rest/v1/users.drafts/create).",
14-
version: "0.2.0",
14+
version: "0.2.1",
1515
annotations: {
1616
destructiveHint: false,
1717
openWorldHint: true,

components/gmail/actions/create-label/create-label.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default {
1313
+ " Nested labels are expressed with `/` — e.g. `Clients/Acme` creates or targets a sub-label under `Clients`."
1414
+ " `color` is optional; when provided, both `textColor` and `backgroundColor` must be supplied together and must come from Gmail's fixed palette."
1515
+ " [See the documentation](https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.labels/create).",
16-
version: "0.1.0",
16+
version: "0.1.1",
1717
annotations: {
1818
destructiveHint: false,
1919
openWorldHint: true,
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import gmail from "../../gmail.app.mjs";
2+
3+
export default {
4+
key: "gmail-delete-label",
5+
name: "Delete Label",
6+
description:
7+
"Immediately and permanently delete a user-created label from the authenticated Gmail mailbox, removing it from every message and thread it was applied to."
8+
+ " Only user-created labels can be deleted — Gmail's built-in system labels (`INBOX`, `SENT`, `SPAM`, `TRASH`, etc.) cannot."
9+
+ " This deletes the label *definition* itself; to merely detach a label from specific messages without removing it, use **Modify Labels** with `removeLabels` instead."
10+
+ " [See the documentation](https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.labels/delete).",
11+
version: "0.0.1",
12+
annotations: {
13+
destructiveHint: true,
14+
openWorldHint: true,
15+
readOnlyHint: false,
16+
},
17+
type: "action",
18+
props: {
19+
gmail,
20+
label: {
21+
type: "string",
22+
label: "Label ID",
23+
description:
24+
"The ID of the user-created label to permanently delete (e.g. `Label_42`)."
25+
+ " Use the **List Labels** action to look up label IDs by name — the Gmail API deletes by ID, not by display name."
26+
+ " System labels (`INBOX`, `SENT`, etc.) cannot be deleted.",
27+
},
28+
},
29+
async run({ $ }) {
30+
await this.gmail.deleteLabel(this.label);
31+
32+
$.export("$summary", `Deleted label ${this.label}`);
33+
return {
34+
id: this.label,
35+
deleted: true,
36+
};
37+
},
38+
};

components/gmail/actions/download-attachment/download-attachment.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export default {
1717
+ " If `filename` is omitted, the action looks up the attachment's filename from the message payload."
1818
+ " Set `convertToPdf: true` to convert image / HTML / plain-text / DOCX attachments to PDF during download; other MIME types are rejected."
1919
+ " [See the documentation](https://developers.google.com/gmail/api/reference/rest/v1/users.messages.attachments/get).",
20-
version: "0.1.0",
20+
version: "0.1.1",
2121
annotations: {
2222
destructiveHint: false,
2323
openWorldHint: true,

components/gmail/actions/find-email/find-email.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export default {
2424
+ " `format` defaults to `metadata` (headers + snippet only) to keep the response small; set it to `full` only when you actually need body text or attachment IDs."
2525
+ " Responses are hard-capped at 100k characters — anything beyond is truncated with a `[truncated]` marker so the caller knows to narrow the query."
2626
+ " [See the documentation](https://developers.google.com/gmail/api/reference/rest/v1/users.messages/list) and [Gmail search operators](https://support.google.com/mail/answer/7190).",
27-
version: "0.2.0",
27+
version: "0.2.1",
2828
annotations: {
2929
destructiveHint: false,
3030
openWorldHint: true,

components/gmail/actions/get-current-user/get-current-user.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default {
44
key: "gmail-get-current-user",
55
name: "Get Current User",
66
description: "Returns the authenticated Gmail user's name, email address, and mailbox stats (total messages and threads). Call this first when the user says 'my emails', 'my inbox', or needs identity context. Use the returned `emailAddress` to identify the user's own messages in **Find Emails** results. [See the documentation](https://developers.google.com/gmail/api/reference/rest/v1/users/getProfile).",
7-
version: "0.0.2",
7+
version: "0.0.3",
88
type: "action",
99
annotations: {
1010
destructiveHint: false,

components/gmail/actions/get-send-as-alias/get-send-as-alias.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default {
55
key: "gmail-get-send-as-alias",
66
name: "Get Send As Alias",
77
description: "Get a send as alias for the authenticated user. [See the documentation](https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.settings.sendAs/get)",
8-
version: "0.0.6",
8+
version: "0.0.7",
99
annotations: {
1010
destructiveHint: false,
1111
openWorldHint: true,

components/gmail/actions/list-delegate-options/list-delegate-options.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default {
44
key: "gmail-list-delegate-options",
55
name: "List Send as a Delegate Options",
66
description: "Retrieves available options for the Send as a Delegate field.",
7-
version: "0.0.2",
7+
version: "0.0.3",
88
type: "action",
99
annotations: {
1010
destructiveHint: false,

components/gmail/actions/list-labels/list-labels.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default {
88
+ " Call this before **Modify Labels** or **Find Emails** when you need to target a label that the user named rather than an obvious system label — it resolves a name like `Clients/Acme` to its opaque label ID."
99
+ " User labels are returned first, then system labels."
1010
+ " [See the documentation](https://developers.google.com/gmail/api/reference/rest/v1/users.labels/list).",
11-
version: "0.1.0",
11+
version: "0.1.1",
1212
annotations: {
1313
destructiveHint: false,
1414
openWorldHint: true,

0 commit comments

Comments
 (0)