@nimbus-dev/connectors 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Which Gmail tools need an OAuth scope beyond the connector's default grant, and whether the token
3
+ * the gateway passes holds it. The gateway sets `GOOGLE_OAUTH_SCOPES` (space-separated, the full
4
+ * scope URIs Google returns) from the `google.oauth` Vault entry.
5
+ *
6
+ * Unlike Outlook's policy, an ABSENT env does NOT register a scope-gated tool: a gateway that does
7
+ * not pass scopes (every gateway before the one that pins this release) never lists a write its
8
+ * token cannot perform. Every tool without an entry below registers as before.
9
+ */
10
+ const GMAIL_MODIFY = "https://www.googleapis.com/auth/gmail.modify";
11
+ /** The full-mailbox scope, a superset of `gmail.modify`. */
12
+ const GMAIL_FULL = "https://mail.google.com/";
13
+
14
+ const GMAIL_TOOL_MIN_SCOPES: Readonly<Record<string, readonly string[]>> = {
15
+ gmail_thread_archive: [GMAIL_MODIFY],
16
+ };
17
+
18
+ function scopeSatisfied(granted: ReadonlySet<string>, required: string): boolean {
19
+ if (granted.has(required)) return true;
20
+ return required === GMAIL_MODIFY && granted.has(GMAIL_FULL);
21
+ }
22
+
23
+ /** The scopes in `GOOGLE_OAUTH_SCOPES`, or `undefined` when it is unset or blank. */
24
+ export function parseGoogleOAuthScopesFromEnv(): string[] | undefined {
25
+ const raw = process.env["GOOGLE_OAUTH_SCOPES"];
26
+ if (raw === undefined) return undefined;
27
+ const parts = raw.split(/\s+/).filter((s) => s.length > 0);
28
+ return parts.length === 0 ? undefined : parts;
29
+ }
30
+
31
+ /** Whether `toolId` registers for a token holding `granted` (`undefined`: scopes unknown). */
32
+ export function gmailToolShouldRegister(toolId: string, granted: string[] | undefined): boolean {
33
+ const reqs = GMAIL_TOOL_MIN_SCOPES[toolId];
34
+ if (reqs === undefined) return true;
35
+ if (granted === undefined) return false;
36
+ const g = new Set(granted);
37
+ return reqs.every((r) => scopeSatisfied(g, r));
38
+ }
@@ -12,6 +12,7 @@ import {
12
12
  makeRestToolRegistrar,
13
13
  makeRestWriteToolRegistrar,
14
14
  } from "../../../shared/rest-tool-kit.ts";
15
+ import { gmailToolShouldRegister, parseGoogleOAuthScopesFromEnv } from "./tool-scope-policy.ts";
15
16
 
16
17
  const GMAIL_BASE = "https://gmail.googleapis.com/gmail/v1/users/me";
17
18
 
@@ -67,7 +68,7 @@ export function registerGmailTools(
67
68
  const registerWriteTool = createWriteToolRegistrar(server, {
68
69
  connector: "gmail",
69
70
  scopeEnv: "NIMBUS_MCP_GMAIL_WRITE_SCOPE",
70
- scopeKinds: ["recipient", "draft"],
71
+ scopeKinds: ["recipient", "draft", "thread"],
71
72
  });
72
73
 
73
74
  /**
@@ -206,4 +207,26 @@ export function registerGmailTools(
206
207
  () => `${GMAIL_BASE}/messages/send`,
207
208
  (data) => jsonPost({ raw: rawMessage(data) }),
208
209
  );
210
+
211
+ // Registered only when the token holds `gmail.modify` (or the full-mailbox scope): the default
212
+ // grant is read + compose, and a fresh `nimbus connector auth gmail` is what adds it.
213
+ if (gmailToolShouldRegister("gmail_thread_archive", parseGoogleOAuthScopesFromEnv())) {
214
+ const gmailThreadArchiveArgs = z.object({
215
+ // Gmail thread ids are hex; anything else is refused before any request.
216
+ threadId: z.string().regex(/^[A-Za-z0-9]{1,64}$/, "threadId must be a Gmail thread id"),
217
+ });
218
+
219
+ registerGmailWriteTool(
220
+ "gmail_thread_archive",
221
+ {
222
+ mutates: "gmail.thread.archive",
223
+ recoverable: true,
224
+ scopeTargetOf: (p) => ({ kind: "thread", value: p.threadId }),
225
+ },
226
+ "Archive a Gmail thread: remove it from the inbox (every message keeps its other labels; nothing is deleted). Requires Gateway HITL gmail.thread.archive.",
227
+ gmailThreadArchiveArgs,
228
+ (data) => `${GMAIL_BASE}/threads/${encodeURIComponent(data.threadId)}/modify`,
229
+ () => jsonPost({ removeLabelIds: ["INBOX"] }),
230
+ );
231
+ }
209
232
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nimbus-dev/connectors",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },