@aaronsb/google-workspace-mcp 4.3.0 → 4.4.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.
Files changed (38) hide show
  1. package/README.md +54 -7
  2. package/build/accounts/oauth.d.ts +16 -0
  3. package/build/accounts/oauth.js +24 -0
  4. package/build/accounts/oauth.js.map +1 -1
  5. package/build/coverage/compare.js +13 -8
  6. package/build/coverage/compare.js.map +1 -1
  7. package/build/factory/defaults.js +1 -1
  8. package/build/factory/defaults.js.map +1 -1
  9. package/build/factory/generator.d.ts +8 -1
  10. package/build/factory/generator.js +15 -3
  11. package/build/factory/generator.js.map +1 -1
  12. package/build/factory/manifest/contacts.yaml +8 -0
  13. package/build/factory/manifest/gmail.yaml +15 -0
  14. package/build/factory/safety.d.ts +61 -5
  15. package/build/factory/safety.js +109 -4
  16. package/build/factory/safety.js.map +1 -1
  17. package/build/factory/types.d.ts +16 -0
  18. package/build/server/batch.d.ts +20 -0
  19. package/build/server/batch.js +292 -0
  20. package/build/server/batch.js.map +1 -0
  21. package/build/server/handler.d.ts +5 -0
  22. package/build/server/handler.js +21 -9
  23. package/build/server/handler.js.map +1 -1
  24. package/build/server/queue.js +11 -1
  25. package/build/server/queue.js.map +1 -1
  26. package/build/server/scratchpad/handler.js +65 -0
  27. package/build/server/scratchpad/handler.js.map +1 -1
  28. package/build/server/server.js +15 -5
  29. package/build/server/server.js.map +1 -1
  30. package/build/server/tools.js +54 -6
  31. package/build/server/tools.js.map +1 -1
  32. package/build/services/contacts/patch.d.ts +26 -0
  33. package/build/services/contacts/patch.js +7 -1
  34. package/build/services/contacts/patch.js.map +1 -1
  35. package/build/services/docs/patch.js +1 -1
  36. package/build/version.d.ts +1 -1
  37. package/build/version.js +1 -1
  38. package/package.json +1 -1
@@ -11,13 +11,28 @@
11
11
  * - Log/audit destructive actions
12
12
  *
13
13
  * Policies are composable — multiple can apply to the same operation.
14
- * They're configured per-deployment, not per-service.
15
14
  *
16
- * Example use cases:
15
+ * Most are configured per-deployment via GWS_SAFETY_POLICY:
17
16
  * - "Draft-only mode" — agents can read email but not send
18
17
  * - "No-delete mode" — prevent permanent deletion across all services
19
18
  * - "Audit mode" — log all write operations to stderr
19
+ *
20
+ * `account-access` is the exception and is always active. It enforces a per-ACCOUNT
21
+ * choice the user made at consent time rather than a deployment-wide one, and it blocks
22
+ * by returning a reason rather than throwing. See ADR-202.
23
+ *
24
+ * WHO RUNS THIS: `generateHandler` calls it for every factory-generated operation, and
25
+ * `manage_scratchpad` calls it explicitly for its send and sync writes (#171) because it
26
+ * is hand-registered and gets no policy check for free. Any future hand-registered tool
27
+ * that writes to Google has to do the same — nothing enforces that from here, which is
28
+ * exactly how scratchpad went uncovered.
29
+ *
30
+ * `manage_accounts` and `manage_workspace` are also hand-registered and need no check:
31
+ * neither writes through a Google API.
20
32
  */
33
+ import { readCredential } from '../accounts/credentials.js';
34
+ import { anyScopesFor, writeScopesFor } from '../accounts/oauth.js';
35
+ import { loadDescriptor } from '../google/descriptor.js';
21
36
  // ── Built-in policies ────────────────────────────────────────────────
22
37
  /**
23
38
  * Draft-only email policy — blocks send/reply/forward, allows everything else.
@@ -107,6 +122,96 @@ export const readOnly = {
107
122
  return { action: 'allow' };
108
123
  },
109
124
  };
125
+ /**
126
+ * Access policy — refuse a write from an account that was authorized read-only, and say
127
+ * so in a sentence, before the request leaves. ADR-202.
128
+ *
129
+ * This is the enforcement half of per-account access. The consent half narrows the token;
130
+ * this half explains what happened when the narrowed token is asked to do more. Without
131
+ * it Google still refuses — with a 403 that names neither the account, nor the scope, nor
132
+ * the way out.
133
+ *
134
+ * UNLIKE EVERY OTHER POLICY HERE, this one is always on. The others express a deployment
135
+ * choice an operator opts into with GWS_SAFETY_POLICY. This one enforces a choice the
136
+ * *user* already made at consent time, so requiring a second opt-in to honour it would
137
+ * make the first one decorative.
138
+ *
139
+ * It fails OPEN in every case where it cannot be sure: an account with no credential, an
140
+ * unreadable credential file, a service with no scope map, an unknown method. A safety
141
+ * check that blocks on its own uncertainty stops being a safety check and becomes an
142
+ * outage — and the operations at issue are ones Google will independently refuse anyway.
143
+ */
144
+ export const accountAccess = {
145
+ name: 'account-access',
146
+ description: 'Refuse writes from a read-only account, naming the account and the fix',
147
+ applies: () => true,
148
+ evaluate: async (_args, ctx, _service, op) => {
149
+ // No account (a service that needs none) or no manifest info: nothing to check against.
150
+ if (!ctx.account || !op)
151
+ return { action: 'allow' };
152
+ let granted;
153
+ try {
154
+ const cred = await readCredential(ctx.account);
155
+ granted = new Set(cred.scopes ?? []);
156
+ }
157
+ catch (err) {
158
+ // Not authenticated, or the credential is unreadable. The auth path already has a
159
+ // good error for that; inventing a second one here would only obscure it.
160
+ //
161
+ // Say so on stderr regardless. Blocks are logged; a silent allow is not, so a
162
+ // truncated credential file would otherwise disable enforcement for that account
163
+ // with no signal anywhere — the failure mode you least want to be quiet.
164
+ process.stderr.write(`[google-workspace-mcp] safety: account-access NOT enforcing for ${ctx.account} — ` +
165
+ `could not read its credential (${err instanceof Error ? err.message : String(err)})\n`);
166
+ return { action: 'allow' };
167
+ }
168
+ if (granted.size === 0)
169
+ return { action: 'allow' };
170
+ const required = await requiredScopes(op);
171
+ if (required.length === 0)
172
+ return { action: 'allow' };
173
+ if (required.some((scope) => granted.has(scope)))
174
+ return { action: 'allow' };
175
+ // Nothing the operation accepts was granted. Two different causes, two different fixes.
176
+ const holdsSomething = anyScopesFor(op.service).some((scope) => granted.has(scope));
177
+ const reconsent = `manage_accounts {operation:'scopes', email:'${ctx.account}', ` +
178
+ `services:'${op.service}', access:'readwrite'}`;
179
+ return {
180
+ action: 'block',
181
+ reason: holdsSomething
182
+ ? `'${ctx.operation}' needs write access to ${op.service}. ` +
183
+ `Account ${ctx.account} was authorized read-only for ${op.service}. ` +
184
+ `Re-authorize with ${reconsent}, or use an account that already has it.`
185
+ : `'${ctx.operation}' needs access to ${op.service}, which account ${ctx.account} ` +
186
+ `has never authorized. Grant it with ${reconsent}, or use an account that already has it.`,
187
+ };
188
+ },
189
+ };
190
+ /**
191
+ * The scopes Google will accept for this operation.
192
+ *
193
+ * Prefers the descriptor, which is Google's own per-method scope list (ADR-103) and so
194
+ * cannot drift from what Google actually enforces. A hand-kept table of write operations
195
+ * would be a second source of truth — the #161 failure.
196
+ *
197
+ * Seven of 95 operations declare no `resource` because they are pure custom handlers that
198
+ * make several calls (`gmail.send`, `drive.upload`, `calendar.create` and friends), and
199
+ * six of those seven are writes. Leaving them unenforced would exempt exactly the
200
+ * operations most worth enforcing, so they fall back to the manifest's declared `type`,
201
+ * which a test already pins against the descriptor's httpMethod.
202
+ */
203
+ async function requiredScopes(op) {
204
+ if (op.resource) {
205
+ const descriptor = await loadDescriptor();
206
+ const method = descriptor.services[op.googleService]?.methods?.[op.resource];
207
+ // An unknown method means the manifest and the descriptor disagree. That is a bug
208
+ // worth failing on, but not here, at the cost of the user's request.
209
+ if (method?.scopes?.length)
210
+ return method.scopes;
211
+ return [];
212
+ }
213
+ return op.type === 'action' ? writeScopesFor(op.service) : anyScopesFor(op.service);
214
+ }
110
215
  /**
111
216
  * Audit policy — allows everything but logs destructive operations to stderr.
112
217
  * Useful for monitoring what an agent does without blocking it.
@@ -142,11 +247,11 @@ export function getActivePolicies() {
142
247
  * Run all active policies against an operation.
143
248
  * First block wins. Returns the most restrictive result.
144
249
  */
145
- export function evaluatePolicies(args, ctx, service) {
250
+ export async function evaluatePolicies(args, ctx, service, op) {
146
251
  for (const policy of activePolicies) {
147
252
  if (!policy.applies(service, ctx.operation))
148
253
  continue;
149
- const result = policy.evaluate(args, ctx, service);
254
+ const result = await policy.evaluate(args, ctx, service, op);
150
255
  if (result.action === 'block') {
151
256
  process.stderr.write(`[google-workspace-mcp] safety: BLOCKED ${service}.${ctx.operation} by ${policy.name}: ${result.reason}\n`);
152
257
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/factory/safety.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/factory/safety.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,22 @@ export interface OperationDef {
36
36
  description: string;
37
37
  /** Google resource path (e.g. "users.messages.list"). */
38
38
  resource?: string;
39
+ /**
40
+ * How this operation is done for many resources in ONE request. ADR-308.
41
+ *
42
+ * Present only where Google publishes such a method — six operations across two
43
+ * services. Its absence is what makes an operation unbatchable, so the batchable set is
44
+ * derived from the manifest and never listed anywhere else.
45
+ *
46
+ * `resource` here is not always the singular method pluralised: Gmail has no bulk
47
+ * trash, so `trash` batches through `users.messages.batchModify` with `defaults`
48
+ * supplying the TRASH label. `defaults` is merged into the request body and never
49
+ * enters the generated schema, exactly as the operation-level `defaults` does.
50
+ */
51
+ batch?: {
52
+ resource: string;
53
+ defaults?: Record<string, unknown>;
54
+ };
39
55
  /** Named parameters the caller can provide. */
40
56
  params?: Record<string, ParamDef>;
41
57
  /** Default values merged into the params sent to Google. */
@@ -0,0 +1,20 @@
1
+ import type { HandlerResponse } from './formatting/markdown.js';
2
+ /** One batchable operation, as the manifest declares it. */
3
+ export interface BatchableOp {
4
+ tool: string;
5
+ operation: string;
6
+ service: string;
7
+ googleService: string;
8
+ resource: string;
9
+ defaults: Record<string, unknown>;
10
+ type: 'list' | 'detail' | 'action';
11
+ }
12
+ /** Every operation the manifest says can batch, keyed `tool.operation`. */
13
+ export declare function batchableOperations(): Map<string, BatchableOp>;
14
+ /**
15
+ * Run one batch call.
16
+ *
17
+ * Refusing is as important as running: an operation with no `batch:` block cannot be
18
+ * batched, and saying so with the list of what can turns a dead end into the answer.
19
+ */
20
+ export declare function handleBatch(params: Record<string, unknown>): Promise<HandlerResponse>;
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Batch mode for `bulk_operations` — one Google request across many resources. ADR-308.
3
+ *
4
+ * The rule that makes one shape serve five different Google methods: **top-level
5
+ * arguments are shared across the batch, `items` carry what differs**. A batch is one
6
+ * call with one set of parameters applied to many resources, so a parameter that varies
7
+ * per item is not a batch — it is a queue.
8
+ *
9
+ * Which operations can batch is DERIVED from the manifest's `batch:` blocks. Nothing here
10
+ * lists them. This repository has produced the hand-maintained-list-beside-a-generated-one
11
+ * defect three times (the hardcoded queue tool enum, the stale coverage baseline, #161),
12
+ * and a list of batchable operations would be the fourth.
13
+ */
14
+ import { loadManifest } from '../factory/generator.js';
15
+ import { evaluatePolicies } from '../factory/safety.js';
16
+ import { call } from '../google/client.js';
17
+ import { buildPerson } from '../services/contacts/patch.js';
18
+ /** Every operation the manifest says can batch, keyed `tool.operation`. */
19
+ export function batchableOperations() {
20
+ const out = new Map();
21
+ for (const [service, def] of Object.entries(loadManifest().services)) {
22
+ for (const [operation, op] of Object.entries(def.operations)) {
23
+ if (!op.batch)
24
+ continue;
25
+ out.set(`${def.tool_name}.${operation}`, {
26
+ tool: def.tool_name,
27
+ operation,
28
+ service,
29
+ googleService: def.google_service,
30
+ resource: op.batch.resource,
31
+ defaults: op.batch.defaults ?? {},
32
+ type: op.type,
33
+ });
34
+ }
35
+ }
36
+ return out;
37
+ }
38
+ /** `manage_email modify, manage_contacts create, …` — for telling a caller what does batch. */
39
+ function batchableList() {
40
+ return [...batchableOperations().values()]
41
+ .map((b) => `${b.tool} ${b.operation}`)
42
+ .join(', ');
43
+ }
44
+ /**
45
+ * The one field an item is mostly likely to be, per method — so a caller with a list of
46
+ * ids can pass a list of ids.
47
+ *
48
+ * `items: ['people/c1', 'people/c2']` says everything `items: [{contactId:'people/c1'},
49
+ * {contactId:'people/c2'}]` says, with less to get wrong. The object form still works for
50
+ * the operations that carry more than an id.
51
+ */
52
+ const ITEM_ID_FIELD = {
53
+ 'users.messages.batchModify': 'messageId',
54
+ 'people.batchDeleteContacts': 'contactId',
55
+ 'people.getBatchGet': 'contactId',
56
+ 'people.batchUpdateContacts': 'contactId',
57
+ };
58
+ /**
59
+ * Accept a bare id where an object is expected, and a bare contact id where Google wants
60
+ * the `people/` prefix — the same courtesy the single-contact operations extend.
61
+ */
62
+ function normalizeItems(items, resource) {
63
+ const field = ITEM_ID_FIELD[resource];
64
+ return items.map((item) => {
65
+ const obj = (typeof item === 'string' && field)
66
+ ? { [field]: item }
67
+ : item;
68
+ const id = obj?.contactId ?? obj?.resourceName;
69
+ if (typeof id === 'string' && id && !id.startsWith('people/')) {
70
+ return { ...obj, contactId: `people/${id}` };
71
+ }
72
+ return obj;
73
+ });
74
+ }
75
+ const BODY_BUILDERS = {
76
+ // ids + the label changes, which are shared by definition: one call sets one label set.
77
+ 'users.messages.batchModify': (shared, items) => ({
78
+ ids: items.map((i) => str(i.messageId)),
79
+ ...(shared.addLabelIds ? { addLabelIds: list(shared.addLabelIds) } : {}),
80
+ ...(shared.removeLabelIds ? { removeLabelIds: list(shared.removeLabelIds) } : {}),
81
+ }),
82
+ // Each contact is different, so the whole payload comes from items. `buildPerson` is
83
+ // the same converter single `create` uses, so a batch item takes the same flat fields —
84
+ // name, contactEmail, phone. Passing them raw is a 400 from Google, measured; the raw
85
+ // People shape is still accepted via `person` for anything the flat fields cannot say.
86
+ 'people.batchCreateContacts': (shared, items) => ({
87
+ contacts: items.map((i) => ({ contactPerson: i.person ?? buildPerson(i).person })),
88
+ readMask: shared.readMask ?? 'names,emailAddresses,phoneNumbers,organizations,metadata',
89
+ }),
90
+ 'people.batchDeleteContacts': (_shared, items) => ({
91
+ resourceNames: items.map((i) => str(i.contactId ?? i.resourceName)),
92
+ }),
93
+ // A map keyed by resource name rather than an array — Google's shape, not ours. Each
94
+ // person must carry its current etag, which `prepare` below fetches; without it Google
95
+ // answers FAILED_PRECONDITION (measured).
96
+ 'people.batchUpdateContacts': (shared, items) => {
97
+ const touched = new Set();
98
+ const contacts = {};
99
+ for (const i of items) {
100
+ const built = i.person ? { person: i.person, touched: new Set() } : buildPerson(i);
101
+ for (const f of built.touched)
102
+ touched.add(f);
103
+ contacts[str(i.contactId ?? i.resourceName)] = { ...built.person, etag: i.etag };
104
+ }
105
+ return {
106
+ contacts,
107
+ // Derived from the fields the caller actually supplied, exactly as single `update`
108
+ // does. A fixed mask would name fields nobody touched, and naming a field with no
109
+ // value in the body CLEARS it.
110
+ updateMask: shared.updateMask ?? [...touched].join(','),
111
+ readMask: shared.readMask ?? 'names,emailAddresses,phoneNumbers,organizations,metadata',
112
+ };
113
+ },
114
+ // A GET: resourceNames is a repeated QUERY parameter, not a body.
115
+ 'people.getBatchGet': (shared, items) => ({
116
+ resourceNames: items.map((i) => str(i.contactId ?? i.resourceName)),
117
+ personFields: shared.personFields ?? 'names,emailAddresses,phoneNumbers,organizations,metadata',
118
+ }),
119
+ };
120
+ function str(v) {
121
+ return typeof v === 'string' ? v : String(v ?? '');
122
+ }
123
+ /** Accept `'A,B'` or `['A','B']` — Google always wants the array. */
124
+ function list(v) {
125
+ if (Array.isArray(v))
126
+ return v.map(str);
127
+ return str(v).split(',').map((s) => s.trim()).filter(Boolean);
128
+ }
129
+ /**
130
+ * Work some methods need before the batch call itself.
131
+ *
132
+ * `batchUpdateContacts` requires each person's current etag — Google answers
133
+ * FAILED_PRECONDITION without one (measured). Fetching them is one `getBatchGet`, so a
134
+ * batch update costs two calls for N contacts rather than the 2N a queue would.
135
+ */
136
+ const PREPARE = {
137
+ 'people.batchUpdateContacts': async (items, account) => {
138
+ const names = items.map((i) => str(i.contactId ?? i.resourceName));
139
+ const got = await call('people', 'people.getBatchGet', {
140
+ resourceNames: names,
141
+ personFields: 'names,emailAddresses,phoneNumbers,organizations,biographies,metadata',
142
+ }, { account });
143
+ const etags = new Map();
144
+ for (const r of got.responses ?? []) {
145
+ const person = r.person;
146
+ if (person?.resourceName)
147
+ etags.set(str(person.resourceName), person.etag);
148
+ }
149
+ return items.map((i) => {
150
+ const name = str(i.contactId ?? i.resourceName);
151
+ const etag = etags.get(name);
152
+ if (etag === undefined)
153
+ throw new Error(`No contact found for ${name} — cannot update what does not exist.`);
154
+ return { ...i, contactId: name, etag };
155
+ });
156
+ },
157
+ };
158
+ /** A person as one line, matching how the single-contact operations print. */
159
+ function personLine(person) {
160
+ const names = person.names ?? [];
161
+ const display = names[0]?.displayName ?? [names[0]?.givenName, names[0]?.familyName].filter(Boolean).join(' ');
162
+ const emails = person.emailAddresses ?? [];
163
+ const parts = [str(person.resourceName), str(display || '(no name)')];
164
+ if (emails[0]?.value)
165
+ parts.push(str(emails[0].value));
166
+ return parts.join(' | ');
167
+ }
168
+ /**
169
+ * Render what came back, so a batch READ is worth making.
170
+ *
171
+ * `refs` never reaches the model — the server returns `result.text` only — so anything
172
+ * the agent must act on has to be in the text. Live, `get` answered "2 results returned"
173
+ * and threw the two people away.
174
+ */
175
+ function renderPeople(data) {
176
+ const rows = [];
177
+ for (const r of data.responses ?? []) {
178
+ if (r.person)
179
+ rows.push(personLine(r.person));
180
+ }
181
+ for (const r of data.createdPeople ?? []) {
182
+ if (r.person)
183
+ rows.push(personLine(r.person));
184
+ }
185
+ const updated = data.updateResult;
186
+ for (const r of Object.values(updated ?? {})) {
187
+ if (r.person)
188
+ rows.push(personLine(r.person));
189
+ }
190
+ return rows.length ? rows.join('\n') : null;
191
+ }
192
+ /**
193
+ * Run one batch call.
194
+ *
195
+ * Refusing is as important as running: an operation with no `batch:` block cannot be
196
+ * batched, and saying so with the list of what can turns a dead end into the answer.
197
+ */
198
+ export async function handleBatch(params) {
199
+ const tool = params.tool;
200
+ const operation = params.operation;
201
+ const rawItems = params.items;
202
+ if (!tool || !operation) {
203
+ return err(`mode:'batch' needs \`tool\` and \`operation\`. Operations that batch: ${batchableList()}.`);
204
+ }
205
+ if (!Array.isArray(rawItems) || rawItems.length === 0) {
206
+ return err(`mode:'batch' needs a non-empty \`items\` array — one entry per resource to act on.`);
207
+ }
208
+ const batchable = batchableOperations();
209
+ const spec = batchable.get(`${tool}.${operation}`);
210
+ if (!spec) {
211
+ // Deliberately does not claim "Google publishes no batch method". For docs and
212
+ // sheets that is false — they publish `batchUpdate`, which batches many edits to ONE
213
+ // document rather than one edit across many. Saying otherwise would teach the caller
214
+ // something untrue about Google's API.
215
+ return err(`${tool} '${operation}' cannot be batched. ` +
216
+ `Operations that can: ${batchableList()}. ` +
217
+ `Use mode:'queue' instead, which works for every operation.`);
218
+ }
219
+ const items = normalizeItems(rawItems, spec.resource);
220
+ const build = BODY_BUILDERS[spec.resource];
221
+ if (!build) {
222
+ // A manifest declared a batch resource this file cannot build a body for. That is a
223
+ // bug in this repo, not in the caller's request, so it says so plainly.
224
+ return err(`${tool} '${operation}' declares batch resource ${spec.resource}, which has no body builder.`);
225
+ }
226
+ const account = params.email;
227
+ if (!account)
228
+ return err(`mode:'batch' needs \`email\` — the account to act as.`);
229
+ // Batch reaches Google without passing through generateHandler, so it gets no policy
230
+ // check for free — the same way manage_scratchpad did not (#171). Doing many writes at
231
+ // once is the last place to skip this: a read-only account would otherwise be refused
232
+ // one contact deletion and permitted two hundred.
233
+ const decision = await evaluatePolicies([], { operation: spec.operation, params, account }, spec.googleService, {
234
+ service: spec.service,
235
+ googleService: spec.googleService,
236
+ resource: spec.resource,
237
+ type: spec.type,
238
+ });
239
+ if (decision.action === 'block') {
240
+ return {
241
+ text: `**Blocked by safety policy:** ${decision.reason}`,
242
+ refs: { blocked: true, error: true, policy: decision.reason },
243
+ };
244
+ }
245
+ // Shared arguments are everything the caller passed that is not batch plumbing.
246
+ const { mode: _m, tool: _t, operation: _o, items: _i, email: _e, detail: _d, ...shared } = params;
247
+ let prepared = items;
248
+ const prepare = PREPARE[spec.resource];
249
+ if (prepare) {
250
+ try {
251
+ prepared = await prepare(items, account);
252
+ }
253
+ catch (e) {
254
+ return err(e instanceof Error ? e.message : String(e));
255
+ }
256
+ }
257
+ const body = { ...spec.defaults, ...build({ ...spec.defaults, ...shared }, prepared) };
258
+ const data = await call(spec.googleService, spec.resource, body, { account });
259
+ const rendered = renderPeople((data ?? {}));
260
+ return {
261
+ text: `## Batch: ${tool} ${operation}\n\n` +
262
+ `${items.length} item${items.length === 1 ? '' : 's'} in one request ` +
263
+ `(\`${spec.resource}\`).\n\n${rendered ?? summarize(data)}`,
264
+ refs: { batch: true, tool, operation, resource: spec.resource, count: items.length, data },
265
+ };
266
+ }
267
+ /**
268
+ * Say what Google said, without inventing per-item detail it did not send.
269
+ *
270
+ * The methods differ here and the difference is not ours to paper over: batchModify and
271
+ * batchDelete answer 204 with no body, so a partial failure is not distinguishable per
272
+ * item; batchCreateContacts answers with a result per contact. Presenting one uniform
273
+ * shape would mean fabricating the missing half.
274
+ */
275
+ function summarize(data) {
276
+ if (data === undefined || data === null || (typeof data === 'object' && Object.keys(data).length === 0)) {
277
+ return 'Google accepted the request and returned no body, which is how it reports success for this method. It does not report per-item status.';
278
+ }
279
+ const rec = data;
280
+ for (const key of ['createContacts', 'updateResult', 'responses']) {
281
+ const arr = rec[key];
282
+ if (Array.isArray(arr))
283
+ return `${arr.length} result${arr.length === 1 ? '' : 's'} returned.`;
284
+ }
285
+ if (rec.responses || rec.createContacts || rec.updateResult)
286
+ return 'Completed.';
287
+ return 'Completed.';
288
+ }
289
+ function err(text) {
290
+ return { text, refs: { error: true } };
291
+ }
292
+ //# sourceMappingURL=batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.js","sourceRoot":"","sources":["../../src/server/batch.ts"],"names":[],"mappings":"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"}
@@ -4,4 +4,9 @@ import type { HandlerResponse } from './formatting/markdown.js';
4
4
  export declare function getEpoch(): number;
5
5
  /** Increment and return the new epoch. Called once per tool dispatch. */
6
6
  export declare function advanceEpoch(): number;
7
+ /**
8
+ * The names that reach the bulk handler. `queue_operations` is the former name, kept
9
+ * working for one minor release — ADR-308. Both are advertised and both dispatch here.
10
+ */
11
+ export declare const BULK_TOOL_NAMES: string[];
7
12
  export declare function handleToolCall(toolName: string, params: Record<string, unknown>): Promise<HandlerResponse>;
@@ -2,6 +2,7 @@ import { handleAccounts } from './handlers/accounts.js';
2
2
  import { handleWorkspace } from './handlers/workspace.js';
3
3
  import { handleScratchpad } from './scratchpad/handler.js';
4
4
  import { handleQueue } from './queue.js';
5
+ import { handleBatch } from './batch.js';
5
6
  import { generatedTools } from '../factory/registry.js';
6
7
  import { getSessionTracker, sessionContext } from './session/index.js';
7
8
  // ── Epoch counter ─────────────────────────────────────────
@@ -27,25 +28,36 @@ for (const tool of generatedTools) {
27
28
  domainHandlers[tool.schema.name] = tool.handler;
28
29
  }
29
30
  /**
30
- * What a queued operation may call: every tool, including `queue_operations`.
31
+ * The names that reach the bulk handler. `queue_operations` is the former name, kept
32
+ * working for one minor release — ADR-308. Both are advertised and both dispatch here.
33
+ */
34
+ export const BULK_TOOL_NAMES = ['bulk_operations', 'queue_operations'];
35
+ /**
36
+ * What a queued operation may call: every tool, including `bulk_operations` itself.
37
+ *
38
+ * A bulk call is a tool call like any other, so the set of things it can name is the set
39
+ * of tools — no carve-out, and nothing for a caller to discover by being refused. Nesting
40
+ * is bounded by depth inside handleQueue rather than by omission here, so the limit
41
+ * arrives as a sentence saying what it is instead of an "Unknown tool" for something the
42
+ * server plainly advertises.
31
43
  *
32
- * A queue is a tool call like any other, so the set of things it can name is the set of
33
- * tools — no carve-out, and nothing for a caller to discover by being refused. Nesting is
34
- * bounded by depth inside handleQueue rather than by omission here, so the limit arrives
35
- * as a sentence saying what it is instead of an "Unknown tool" for something the server
36
- * plainly advertises.
44
+ * The old name is queueable too. An agent that learned it before the rename should not
45
+ * find it working at the top level and failing one level down.
37
46
  */
38
47
  function queueableHandlers(depth) {
48
+ const nested = (params) => handleQueue(params, queueableHandlers(depth + 1), depth + 1);
39
49
  return {
40
50
  ...domainHandlers,
41
- queue_operations: (params) => handleQueue(params, queueableHandlers(depth + 1), depth + 1),
51
+ ...Object.fromEntries(BULK_TOOL_NAMES.map((name) => [name, nested])),
42
52
  };
43
53
  }
44
54
  export async function handleToolCall(toolName, params) {
45
55
  const currentEpoch = advanceEpoch();
46
56
  const tracker = getSessionTracker();
47
- // Queue wraps the domain handlers (each queued op also advances the epoch)
48
- if (toolName === 'queue_operations') {
57
+ // Bulk wraps the domain handlers (each queued op also advances the epoch)
58
+ if (BULK_TOOL_NAMES.includes(toolName)) {
59
+ if (params.mode === 'batch')
60
+ return await handleBatch(params);
49
61
  const result = await handleQueue(params, queueableHandlers(1), 1);
50
62
  const queueEmail = extractEmailFromQueue(params);
51
63
  if (queueEmail) {
@@ -1 +1 @@
1
- {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/server/handler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../src/server/handler.ts"],"names":[],"mappings":"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"}
@@ -23,7 +23,7 @@ function stripNextSteps(text) {
23
23
  const MAX_QUEUE_DEPTH = 3;
24
24
  export async function handleQueue(params, handlers, depth = 1) {
25
25
  if (depth > MAX_QUEUE_DEPTH) {
26
- throw new Error(`queue_operations nested more than ${MAX_QUEUE_DEPTH} deep. Each level multiplies the ` +
26
+ throw new Error(`bulk_operations nested more than ${MAX_QUEUE_DEPTH} deep. Each level multiplies the ` +
27
27
  `work — at 10 operations per queue that ceiling is already 1,000 calls. Flatten the ` +
28
28
  `inner queues into their parent.`);
29
29
  }
@@ -65,6 +65,16 @@ export async function handleQueue(params, handlers, depth = 1) {
65
65
  advanceEpoch(); // Each queued operation is a logical tool call
66
66
  const response = await handler(resolvedArgs);
67
67
  const text = stripNextSteps(response.text);
68
+ // A safety policy declines by RETURNING, not by throwing, so the catch below never
69
+ // sees it. Counting that as a success made a queue report "2/2 succeeded" having
70
+ // written nothing, and left `onError: 'bail'` running the rest of a queue whose
71
+ // first step was refused.
72
+ if (response.refs?.blocked) {
73
+ results.push({ index: i, tool: op.tool, status: 'error', error: text });
74
+ if (errorStrategy === 'bail')
75
+ bailedAt = i;
76
+ continue;
77
+ }
68
78
  results.push({ index: i, tool: op.tool, status: 'success', text, refs: response.refs });
69
79
  lastSuccessText = response.text; // keep next-steps from last success
70
80
  }