@aaronsb/google-workspace-mcp 3.0.0 → 4.0.1
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.
- package/LICENSE +201 -21
- package/LICENSE-MIT +21 -0
- package/NOTICE +24 -0
- package/README.md +90 -52
- package/build/accounts/auth.d.ts +1 -1
- package/build/accounts/auth.js +1 -1
- package/build/accounts/oauth.js +3 -3
- package/build/accounts/oauth.js.map +1 -1
- package/build/accounts/token-service.js +1 -1
- package/build/accounts/token-service.js.map +1 -1
- package/build/coverage/analyze.d.ts +1 -1
- package/build/coverage/analyze.js +6 -4
- package/build/coverage/analyze.js.map +1 -1
- package/build/coverage/baseline.js +37 -20
- package/build/coverage/baseline.js.map +1 -1
- package/build/coverage/compare.d.ts +1 -1
- package/build/coverage/compare.js +19 -10
- package/build/coverage/compare.js.map +1 -1
- package/build/coverage/discover.d.ts +5 -5
- package/build/coverage/discover.js +71 -155
- package/build/coverage/discover.js.map +1 -1
- package/build/coverage/report.js +3 -3
- package/build/coverage/report.js.map +1 -1
- package/build/coverage/types.d.ts +19 -4
- package/build/executor/index.d.ts +0 -3
- package/build/executor/index.js +1 -2
- package/build/executor/index.js.map +1 -1
- package/build/factory/defaults.js +30 -4
- package/build/factory/defaults.js.map +1 -1
- package/build/factory/generator.d.ts +10 -2
- package/build/factory/generator.js +54 -69
- package/build/factory/generator.js.map +1 -1
- package/build/factory/manifest/README.md +4 -3
- package/build/factory/manifest/calendar.yaml +1 -11
- package/build/factory/manifest/docs.yaml +15 -6
- package/build/factory/manifest/drive.yaml +1 -5
- package/build/factory/manifest/gmail.yaml +9 -24
- package/build/factory/manifest/meet.yaml +1 -1
- package/build/factory/manifest/sheets.yaml +2 -5
- package/build/factory/manifest/tasks.yaml +40 -2
- package/build/factory/patches.js +2 -0
- package/build/factory/patches.js.map +1 -1
- package/build/factory/safety.js +4 -4
- package/build/factory/safety.js.map +1 -1
- package/build/factory/types.d.ts +16 -15
- package/build/google/client.d.ts +45 -0
- package/build/google/client.js +283 -0
- package/build/google/client.js.map +1 -0
- package/build/google/descriptor.d.ts +40 -0
- package/build/google/descriptor.js +37 -0
- package/build/google/descriptor.js.map +1 -0
- package/build/google/descriptor.json +5271 -0
- package/build/google/errors.d.ts +45 -0
- package/build/google/errors.js +44 -0
- package/build/google/errors.js.map +1 -0
- package/build/google/methods.d.ts +37 -0
- package/build/google/methods.js +13 -0
- package/build/google/methods.js.map +1 -0
- package/build/index.js +3 -3
- package/build/index.js.map +1 -1
- package/build/node-floor.js +1 -1
- package/build/node-floor.js.map +1 -1
- package/build/server/formatting/markdown.d.ts +10 -1
- package/build/server/formatting/markdown.js +24 -1
- package/build/server/formatting/markdown.js.map +1 -1
- package/build/server/formatting/next-steps.js +1 -1
- package/build/server/formatting/next-steps.js.map +1 -1
- package/build/server/handlers/accounts.js +2 -2
- package/build/server/handlers/accounts.js.map +1 -1
- package/build/server/scratchpad/adapters/import-doc.js +15 -13
- package/build/server/scratchpad/adapters/import-doc.js.map +1 -1
- package/build/server/scratchpad/adapters/import-drive.js +16 -11
- package/build/server/scratchpad/adapters/import-drive.js.map +1 -1
- package/build/server/scratchpad/adapters/import-email.js +3 -15
- package/build/server/scratchpad/adapters/import-email.js.map +1 -1
- package/build/server/scratchpad/adapters/import-meet.js +5 -17
- package/build/server/scratchpad/adapters/import-meet.js.map +1 -1
- package/build/server/scratchpad/adapters/import-sheet.js +5 -6
- package/build/server/scratchpad/adapters/import-sheet.js.map +1 -1
- package/build/server/scratchpad/adapters/send-calendar.js +18 -13
- package/build/server/scratchpad/adapters/send-calendar.js.map +1 -1
- package/build/server/scratchpad/adapters/send-doc.js +22 -17
- package/build/server/scratchpad/adapters/send-doc.js.map +1 -1
- package/build/server/scratchpad/adapters/send-email-draft.d.ts +1 -1
- package/build/server/scratchpad/adapters/send-email-draft.js +17 -25
- package/build/server/scratchpad/adapters/send-email-draft.js.map +1 -1
- package/build/server/scratchpad/adapters/send-email.d.ts +2 -2
- package/build/server/scratchpad/adapters/send-email.js +21 -25
- package/build/server/scratchpad/adapters/send-email.js.map +1 -1
- package/build/server/scratchpad/adapters/send-sheet.js +10 -10
- package/build/server/scratchpad/adapters/send-sheet.js.map +1 -1
- package/build/server/scratchpad/adapters/send-task.js +9 -8
- package/build/server/scratchpad/adapters/send-task.js.map +1 -1
- package/build/server/scratchpad/handler.js +22 -28
- package/build/server/scratchpad/handler.js.map +1 -1
- package/build/server/scratchpad/manager.d.ts +1 -1
- package/build/server/server.js +13 -10
- package/build/server/server.js.map +1 -1
- package/build/server/session/tracker.js +44 -27
- package/build/server/session/tracker.js.map +1 -1
- package/build/server/tools.d.ts +1 -1
- package/build/server/tools.js +3 -3
- package/build/server/tools.js.map +1 -1
- package/build/services/calendar/patch.js +178 -68
- package/build/services/calendar/patch.js.map +1 -1
- package/build/services/docs/patch.d.ts +5 -3
- package/build/services/docs/patch.js +89 -19
- package/build/services/docs/patch.js.map +1 -1
- package/build/services/drive/patch.d.ts +1 -1
- package/build/services/drive/patch.js +115 -143
- package/build/services/drive/patch.js.map +1 -1
- package/build/services/gmail/attachments.js +10 -15
- package/build/services/gmail/attachments.js.map +1 -1
- package/build/services/gmail/mail.d.ts +34 -0
- package/build/services/gmail/mail.js +245 -0
- package/build/services/gmail/mail.js.map +1 -0
- package/build/services/gmail/mime.d.ts +45 -4
- package/build/services/gmail/mime.js +138 -4
- package/build/services/gmail/mime.js.map +1 -1
- package/build/services/gmail/patch.js +109 -90
- package/build/services/gmail/patch.js.map +1 -1
- package/build/services/meet/patch.js +24 -31
- package/build/services/meet/patch.js.map +1 -1
- package/build/services/sheets/patch.js +30 -35
- package/build/services/sheets/patch.js.map +1 -1
- package/build/services/tasks/patch.d.ts +18 -0
- package/build/services/tasks/patch.js +37 -0
- package/build/services/tasks/patch.js.map +1 -0
- package/build/version.d.ts +1 -1
- package/build/version.js +1 -1
- package/package.json +12 -6
- package/build/executor/errors.d.ts +0 -15
- package/build/executor/errors.js +0 -37
- package/build/executor/errors.js.map +0 -1
- package/build/executor/gws.d.ts +0 -26
- package/build/executor/gws.js +0 -197
- package/build/executor/gws.js.map +0 -1
- package/build/server/handlers/calendar.d.ts +0 -2
- package/build/server/handlers/calendar.js +0 -97
- package/build/server/handlers/calendar.js.map +0 -1
- package/build/server/handlers/drive.d.ts +0 -2
- package/build/server/handlers/drive.js +0 -58
- package/build/server/handlers/drive.js.map +0 -1
- package/build/server/handlers/email.d.ts +0 -2
- package/build/server/handlers/email.js +0 -119
- package/build/server/handlers/email.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
tool_name: manage_tasks
|
|
2
2
|
description: "Manage task lists and tasks in Google Tasks."
|
|
3
3
|
requires_email: true
|
|
4
|
-
|
|
4
|
+
google_service: tasks
|
|
5
5
|
operations:
|
|
6
6
|
|
|
7
7
|
# --- Task lists ---
|
|
@@ -22,10 +22,18 @@ operations:
|
|
|
22
22
|
required: true
|
|
23
23
|
maps_to: tasklist
|
|
24
24
|
|
|
25
|
+
# A TaskList's only meaningful field is its title, and it rides in the BODY —
|
|
26
|
+
# tasklists.insert declares no parameters. Without it this could only ever create
|
|
27
|
+
# nameless task lists, and did so without complaint.
|
|
25
28
|
createTaskList:
|
|
26
29
|
type: action
|
|
27
30
|
description: "create a new task list"
|
|
28
31
|
resource: tasklists.insert
|
|
32
|
+
params:
|
|
33
|
+
title:
|
|
34
|
+
type: string
|
|
35
|
+
description: "Task list name"
|
|
36
|
+
required: true
|
|
29
37
|
|
|
30
38
|
deleteTaskList:
|
|
31
39
|
type: action
|
|
@@ -75,6 +83,10 @@ operations:
|
|
|
75
83
|
required: true
|
|
76
84
|
maps_to: task
|
|
77
85
|
|
|
86
|
+
# title / notes / due are BODY fields (the Task resource). tasks.insert declares only
|
|
87
|
+
# `tasklist` (path) and `parent`/`previous` (query), so anything else goes in the body.
|
|
88
|
+
# Without them, `create` sent an empty Task and produced a BLANK task — no title, no
|
|
89
|
+
# content, no error. A titleless task is not a degraded task; it is a useless one.
|
|
78
90
|
create:
|
|
79
91
|
type: action
|
|
80
92
|
description: "create a new task"
|
|
@@ -85,10 +97,23 @@ operations:
|
|
|
85
97
|
description: "Task list ID"
|
|
86
98
|
required: true
|
|
87
99
|
maps_to: tasklist
|
|
100
|
+
title:
|
|
101
|
+
type: string
|
|
102
|
+
description: "Task title"
|
|
103
|
+
required: true
|
|
104
|
+
notes:
|
|
105
|
+
type: string
|
|
106
|
+
description: "Task notes / details"
|
|
107
|
+
due:
|
|
108
|
+
type: string
|
|
109
|
+
description: "Due date (RFC 3339, e.g. 2026-07-20T00:00:00.000Z — Google stores the date, not the time)"
|
|
88
110
|
|
|
111
|
+
# An `update` with no updatable fields could only ever PATCH an empty body, which
|
|
112
|
+
# Google Tasks answers with a 500 "Internal error encountered". So this operation did
|
|
113
|
+
# not merely fail to update — it failed EVERY time, and blamed Google while doing it.
|
|
89
114
|
update:
|
|
90
115
|
type: action
|
|
91
|
-
description: "update a task (patch semantics)"
|
|
116
|
+
description: "update a task (patch semantics — only the fields you pass are changed)"
|
|
92
117
|
resource: tasks.patch
|
|
93
118
|
params:
|
|
94
119
|
taskListId:
|
|
@@ -101,6 +126,19 @@ operations:
|
|
|
101
126
|
description: "Task ID"
|
|
102
127
|
required: true
|
|
103
128
|
maps_to: task
|
|
129
|
+
title:
|
|
130
|
+
type: string
|
|
131
|
+
description: "New task title"
|
|
132
|
+
notes:
|
|
133
|
+
type: string
|
|
134
|
+
description: "New task notes / details"
|
|
135
|
+
due:
|
|
136
|
+
type: string
|
|
137
|
+
description: "New due date (RFC 3339)"
|
|
138
|
+
status:
|
|
139
|
+
type: string
|
|
140
|
+
description: "Task status"
|
|
141
|
+
enum: [needsAction, completed]
|
|
104
142
|
|
|
105
143
|
complete:
|
|
106
144
|
type: action
|
package/build/factory/patches.js
CHANGED
|
@@ -8,6 +8,7 @@ import { drivePatch } from '../services/drive/patch.js';
|
|
|
8
8
|
import { docsPatch } from '../services/docs/patch.js';
|
|
9
9
|
import { meetPatch } from '../services/meet/patch.js';
|
|
10
10
|
import { sheetsPatch } from '../services/sheets/patch.js';
|
|
11
|
+
import { tasksPatch } from '../services/tasks/patch.js';
|
|
11
12
|
export const patches = {
|
|
12
13
|
gmail: gmailPatch,
|
|
13
14
|
calendar: calendarPatch,
|
|
@@ -15,5 +16,6 @@ export const patches = {
|
|
|
15
16
|
docs: docsPatch,
|
|
16
17
|
meet: meetPatch,
|
|
17
18
|
sheets: sheetsPatch,
|
|
19
|
+
tasks: tasksPatch,
|
|
18
20
|
};
|
|
19
21
|
//# sourceMappingURL=patches.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"patches.js","sourceRoot":"","sources":["../../src/factory/patches.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"patches.js","sourceRoot":"","sources":["../../src/factory/patches.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAGxD,MAAM,CAAC,MAAM,OAAO,GAAiC;IACnD,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE,aAAa;IACvB,KAAK,EAAE,UAAU;IACjB,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,WAAW;IACnB,KAAK,EAAE,UAAU;CAClB,CAAC"}
|
package/build/factory/safety.js
CHANGED
|
@@ -113,7 +113,7 @@ export const auditLog = {
|
|
|
113
113
|
applies: () => true,
|
|
114
114
|
evaluate: (_args, ctx, service) => {
|
|
115
115
|
if (!isReadOperation(ctx.operation)) {
|
|
116
|
-
process.stderr.write(`[
|
|
116
|
+
process.stderr.write(`[google-workspace-mcp] AUDIT: ${service}.${ctx.operation} account=${ctx.account} ` +
|
|
117
117
|
`args=${JSON.stringify(ctx.params)}\n`);
|
|
118
118
|
}
|
|
119
119
|
return { action: 'allow' };
|
|
@@ -126,7 +126,7 @@ let activePolicies = [];
|
|
|
126
126
|
export function configurePolicies(policies) {
|
|
127
127
|
activePolicies = policies;
|
|
128
128
|
if (policies.length > 0) {
|
|
129
|
-
process.stderr.write(`[
|
|
129
|
+
process.stderr.write(`[google-workspace-mcp] safety: ${policies.length} policy(ies) active: ` +
|
|
130
130
|
`${policies.map(p => p.name).join(', ')}\n`);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
@@ -144,11 +144,11 @@ export function evaluatePolicies(args, ctx, service) {
|
|
|
144
144
|
continue;
|
|
145
145
|
const result = policy.evaluate(args, ctx, service);
|
|
146
146
|
if (result.action === 'block') {
|
|
147
|
-
process.stderr.write(`[
|
|
147
|
+
process.stderr.write(`[google-workspace-mcp] safety: BLOCKED ${service}.${ctx.operation} by ${policy.name}: ${result.reason}\n`);
|
|
148
148
|
return result;
|
|
149
149
|
}
|
|
150
150
|
if (result.action === 'downgrade' && result.replacementArgs) {
|
|
151
|
-
process.stderr.write(`[
|
|
151
|
+
process.stderr.write(`[google-workspace-mcp] safety: DOWNGRADED ${service}.${ctx.operation} by ${policy.name}: ${result.reason}\n`);
|
|
152
152
|
return result;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
@@ -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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"}
|
package/build/factory/types.d.ts
CHANGED
|
@@ -10,19 +10,19 @@ export interface ParamDef {
|
|
|
10
10
|
required?: boolean;
|
|
11
11
|
default?: string | number | boolean;
|
|
12
12
|
max?: number;
|
|
13
|
-
/** Maps this param name to a different key in the
|
|
13
|
+
/** Maps this param name to a different key in the params sent to Google. */
|
|
14
14
|
maps_to?: string;
|
|
15
15
|
enum?: string[];
|
|
16
16
|
/**
|
|
17
17
|
* If true, the param is for the formatter/handler only and is NOT forwarded
|
|
18
|
-
* to
|
|
18
|
+
* to Google. Used for client-side controls like `bodyFormat` (ADR-305)
|
|
19
19
|
* that affect rendering, not the API call itself.
|
|
20
20
|
*/
|
|
21
21
|
client_only?: boolean;
|
|
22
22
|
}
|
|
23
23
|
/** Hydration config — fetch detail for each item in a list result. */
|
|
24
24
|
export interface HydrationDef {
|
|
25
|
-
/**
|
|
25
|
+
/** Google resource path for the detail call (e.g. "users.messages.get"). */
|
|
26
26
|
resource: string;
|
|
27
27
|
/** Format parameter passed to the detail call. */
|
|
28
28
|
format?: string;
|
|
@@ -34,20 +34,16 @@ export interface OperationDef {
|
|
|
34
34
|
/** Categorizes the operation for default formatting. */
|
|
35
35
|
type: 'list' | 'detail' | 'action';
|
|
36
36
|
description: string;
|
|
37
|
-
/**
|
|
37
|
+
/** Google resource path (e.g. "users.messages.list"). */
|
|
38
38
|
resource?: string;
|
|
39
|
-
/** gws helper shorthand (e.g. "+triage", "+send"). */
|
|
40
|
-
helper?: string;
|
|
41
39
|
/** Named parameters the caller can provide. */
|
|
42
40
|
params?: Record<string, ParamDef>;
|
|
43
|
-
/** Default values merged into the
|
|
41
|
+
/** Default values merged into the params sent to Google. */
|
|
44
42
|
defaults?: Record<string, unknown>;
|
|
45
43
|
/** Post-fetch hydration (list operations only). */
|
|
46
44
|
hydration?: HydrationDef;
|
|
47
|
-
/** Fields to extract from
|
|
45
|
+
/** Fields to extract from Google's response for the result. */
|
|
48
46
|
fields?: string;
|
|
49
|
-
/** CLI-style args instead of --params JSON (for helpers). */
|
|
50
|
-
cli_args?: Record<string, string>;
|
|
51
47
|
}
|
|
52
48
|
/** A service declaration in the manifest. */
|
|
53
49
|
export interface ServiceDef {
|
|
@@ -55,8 +51,8 @@ export interface ServiceDef {
|
|
|
55
51
|
description: string;
|
|
56
52
|
/** Whether this service requires an email account param. */
|
|
57
53
|
requires_email: boolean;
|
|
58
|
-
/** The
|
|
59
|
-
|
|
54
|
+
/** The Google service name (e.g. "gmail", "calendar", "drive"). */
|
|
55
|
+
google_service: string;
|
|
60
56
|
operations: Record<string, OperationDef>;
|
|
61
57
|
}
|
|
62
58
|
/** Top-level manifest shape. */
|
|
@@ -69,9 +65,14 @@ export interface PatchContext {
|
|
|
69
65
|
params: Record<string, unknown>;
|
|
70
66
|
account: string;
|
|
71
67
|
}
|
|
72
|
-
/**
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
/**
|
|
69
|
+
* A patch hook that runs after the manifest's param mapping, before the call.
|
|
70
|
+
*
|
|
71
|
+
* It receives the PARAMS Google will be sent. Hooks operate on the param object
|
|
72
|
+
* directly — never on a serialised command line. See ADR-103.
|
|
73
|
+
*/
|
|
74
|
+
export type BeforeExecuteHook = (params: Record<string, unknown>, ctx: PatchContext) => Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
75
|
+
/** A patch hook that runs after Google returns, before formatting. */
|
|
75
76
|
export type AfterExecuteHook = (result: unknown, ctx: PatchContext) => Promise<unknown> | unknown;
|
|
76
77
|
/** A formatter override for a specific operation type. */
|
|
77
78
|
export type FormatHook = (data: unknown, ctx: PatchContext) => HandlerResponse;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ApiDescriptor } from './descriptor.js';
|
|
2
|
+
import type { GoogleService, ServiceMethods } from './methods.js';
|
|
3
|
+
export interface CallOptions {
|
|
4
|
+
/** Account email — the token is minted for this identity. */
|
|
5
|
+
account: string;
|
|
6
|
+
descriptor?: ApiDescriptor;
|
|
7
|
+
fetchImpl?: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
export interface BuiltRequest {
|
|
10
|
+
url: string;
|
|
11
|
+
method: string;
|
|
12
|
+
body?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
/** Pure: descriptor + params -> the request. No I/O, so it is testable offline. */
|
|
15
|
+
export declare function buildRequest(descriptor: ApiDescriptor, service: string, resourcePath: string, params?: Record<string, unknown>): BuiltRequest;
|
|
16
|
+
/**
|
|
17
|
+
* Call a Google API method. Returns RAW Google JSON — no envelope, no reshaping.
|
|
18
|
+
* Throws GoogleApiError carrying Google's real error body.
|
|
19
|
+
*/
|
|
20
|
+
export declare function call<S extends GoogleService>(service: S, resourcePath: ServiceMethods[S], params: Record<string, unknown>, options: CallOptions): Promise<unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* Download media to disk.
|
|
23
|
+
*
|
|
24
|
+
* STREAMED, deliberately. The bytes go from the socket to the file and are never a
|
|
25
|
+
* string at all. Never buffer a download through an in-memory string: accumulating
|
|
26
|
+
* the whole response and JSON.parse-ing it turns a 30 MB attachment into a ~40 MB
|
|
27
|
+
* string, then an object, then a Buffer — uncapped, for every download.
|
|
28
|
+
*/
|
|
29
|
+
export declare function download<S extends GoogleService>(service: S, resourcePath: ServiceMethods[S], params: Record<string, unknown>, outputPath: string, options: CallOptions): Promise<string>;
|
|
30
|
+
export interface UploadOptions extends CallOptions {
|
|
31
|
+
media: Buffer;
|
|
32
|
+
contentType: string;
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
/** Non-final chunks must be a multiple of 256 KiB. */
|
|
35
|
+
chunkSize?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resumable, chunked media upload.
|
|
39
|
+
*
|
|
40
|
+
* The descriptor carries the upload paths for both protocols because Discovery
|
|
41
|
+
* DECLARES them. Verified live (ADR-103 item 4): a 25 MB attachment — 34.2 MB as
|
|
42
|
+
* RFC822, 93% of Google's declared 36,700,160-byte cap — uploaded in 5 chunks and
|
|
43
|
+
* read back byte-for-byte identical.
|
|
44
|
+
*/
|
|
45
|
+
export declare function upload<S extends GoogleService>(service: S, resourcePath: ServiceMethods[S], params: Record<string, unknown>, options: UploadOptions): Promise<unknown>;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Google API client.
|
|
3
|
+
*
|
|
4
|
+
* The whole of a resource operation: get a token, make the call, hand back the JSON.
|
|
5
|
+
*
|
|
6
|
+
* LOAD-BEARING CONSTRAINT — this layer has NO OPINIONS. It never reshapes, never
|
|
7
|
+
* "fixes", never fills in. It returns exactly what Google returned. Interpretation
|
|
8
|
+
* happens ABOVE it, in the layer we already own and aim at the MCP contract
|
|
9
|
+
* (patches / formatters / next-steps). The moment this file gets helpful, it
|
|
10
|
+
* becomes something that can be subtly wrong in a way no test catches — the exact
|
|
11
|
+
* defect class ADR-101 spent six review rounds learning to fear.
|
|
12
|
+
*
|
|
13
|
+
* Everything here is a consequence of something the DESCRIPTOR said. Where a rule
|
|
14
|
+
* looks arbitrary, it is not: it is a fact we learned by being wrong first, live,
|
|
15
|
+
* against Google (ADR-103, item 2).
|
|
16
|
+
*
|
|
17
|
+
* See ADR-103.
|
|
18
|
+
*/
|
|
19
|
+
import { createWriteStream } from 'node:fs';
|
|
20
|
+
import { mkdir } from 'node:fs/promises';
|
|
21
|
+
import { dirname } from 'node:path';
|
|
22
|
+
import { Readable } from 'node:stream';
|
|
23
|
+
import { pipeline } from 'node:stream/promises';
|
|
24
|
+
import { getAccessToken } from '../accounts/token-service.js';
|
|
25
|
+
import { GoogleApiError } from './errors.js';
|
|
26
|
+
import { loadDescriptor } from './descriptor.js';
|
|
27
|
+
function resolve(descriptor, service, resourcePath) {
|
|
28
|
+
const svc = descriptor.services[service];
|
|
29
|
+
if (!svc)
|
|
30
|
+
throw new Error(`descriptor has no service '${service}'`);
|
|
31
|
+
const method = svc.methods[resourcePath];
|
|
32
|
+
if (!method)
|
|
33
|
+
throw new Error(`descriptor has no method '${service}.${resourcePath}'`);
|
|
34
|
+
return { svc, method };
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Expand a path template.
|
|
38
|
+
*
|
|
39
|
+
* `{+var}` is RFC 6570 RESERVED expansion — reserved characters, notably `/`,
|
|
40
|
+
* must NOT be percent-encoded. Meet's identifiers ARE paths
|
|
41
|
+
* ("conferenceRecords/abc"), so encoding that slash to %2F 404s every Meet
|
|
42
|
+
* sub-resource operation. The `+` is the descriptor telling us this. Honour it.
|
|
43
|
+
*/
|
|
44
|
+
function expandPath(template, pathParams) {
|
|
45
|
+
return template.replace(/\{(\+?)([^}]+)\}/g, (_m, reserved, name) => {
|
|
46
|
+
if (!(name in pathParams))
|
|
47
|
+
throw new Error(`missing required path param '${name}'`);
|
|
48
|
+
const raw = String(pathParams[name]);
|
|
49
|
+
return reserved
|
|
50
|
+
? raw.split('/').map(encodeURIComponent).join('/')
|
|
51
|
+
: encodeURIComponent(raw);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Split params into path / query / body by what the descriptor DECLARES.
|
|
56
|
+
*
|
|
57
|
+
* Global parameters (`fields`, `alt`, `quotaUser`, `prettyPrint`) are declared
|
|
58
|
+
* ONCE at the document root, not per method. A dispatcher that consults only the
|
|
59
|
+
* method's parameters cannot place `fields`, drops it into the body, and a GET
|
|
60
|
+
* silently discards it — which is exactly how drive.listComments died with
|
|
61
|
+
* "The 'fields' parameter is required". Merge both; the method wins on conflict.
|
|
62
|
+
*/
|
|
63
|
+
function splitParams(svc, method, params) {
|
|
64
|
+
const declared = { ...svc.globalParameters, ...method.parameters };
|
|
65
|
+
const path = {};
|
|
66
|
+
const query = {};
|
|
67
|
+
const body = {};
|
|
68
|
+
for (const [key, value] of Object.entries(params)) {
|
|
69
|
+
if (value === undefined || value === null)
|
|
70
|
+
continue;
|
|
71
|
+
const decl = declared[key];
|
|
72
|
+
const location = decl?.location;
|
|
73
|
+
// A REPEATED parameter is sent once per value (`?h=From&h=Subject`), never as one
|
|
74
|
+
// comma-joined string. Google does not split it for us: asked for the single header
|
|
75
|
+
// named "From,Subject,Date,To", it finds none and returns a payload with no headers
|
|
76
|
+
// at all — which rendered a whole mail thread with empty senders and empty subjects,
|
|
77
|
+
// and raised no error. The descriptor already records which params are repeated, so
|
|
78
|
+
// accept a comma-separated string and expand it rather than trusting every caller
|
|
79
|
+
// to remember.
|
|
80
|
+
const coerced = decl?.repeated && typeof value === 'string'
|
|
81
|
+
? value.split(',').map((v) => v.trim()).filter(Boolean)
|
|
82
|
+
: value;
|
|
83
|
+
if (location === 'path')
|
|
84
|
+
path[key] = coerced;
|
|
85
|
+
else if (location === 'query')
|
|
86
|
+
query[key] = coerced;
|
|
87
|
+
else
|
|
88
|
+
body[key] = coerced; // undeclared -> request body; Google validates it, not us
|
|
89
|
+
}
|
|
90
|
+
return { path, query, body };
|
|
91
|
+
}
|
|
92
|
+
function applyQuery(url, query) {
|
|
93
|
+
for (const [key, value] of Object.entries(query)) {
|
|
94
|
+
if (Array.isArray(value))
|
|
95
|
+
value.forEach((v) => url.searchParams.append(key, String(v)));
|
|
96
|
+
else
|
|
97
|
+
url.searchParams.set(key, String(value));
|
|
98
|
+
}
|
|
99
|
+
return url;
|
|
100
|
+
}
|
|
101
|
+
/** Pure: descriptor + params -> the request. No I/O, so it is testable offline. */
|
|
102
|
+
export function buildRequest(descriptor, service, resourcePath, params = {}) {
|
|
103
|
+
const { svc, method } = resolve(descriptor, service, resourcePath);
|
|
104
|
+
const { path, query, body } = splitParams(svc, method, params);
|
|
105
|
+
const base = svc.rootUrl.replace(/\/$/, '') + '/' + svc.servicePath.replace(/^\//, '');
|
|
106
|
+
const url = applyQuery(new URL((base.replace(/\/$/, '') + '/' + expandPath(method.path, path)).replace(/\/+$/, '')), query);
|
|
107
|
+
const hasBody = Object.keys(body).length > 0 && method.httpMethod !== 'GET';
|
|
108
|
+
return { url: url.toString(), method: method.httpMethod, body: hasBody ? body : undefined };
|
|
109
|
+
}
|
|
110
|
+
function parseJson(text) {
|
|
111
|
+
if (!text)
|
|
112
|
+
return {};
|
|
113
|
+
try {
|
|
114
|
+
return JSON.parse(text);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return { error: { message: text.slice(0, 300) } };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Call a Google API method. Returns RAW Google JSON — no envelope, no reshaping.
|
|
122
|
+
* Throws GoogleApiError carrying Google's real error body.
|
|
123
|
+
*/
|
|
124
|
+
export async function call(service, resourcePath, params, options) {
|
|
125
|
+
const descriptor = options.descriptor ?? await loadDescriptor();
|
|
126
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
127
|
+
const request = buildRequest(descriptor, service, resourcePath, params);
|
|
128
|
+
const token = await getAccessToken(options.account);
|
|
129
|
+
for (let attempt = 0;; attempt++) {
|
|
130
|
+
const response = await doFetch(request.url, {
|
|
131
|
+
method: request.method,
|
|
132
|
+
headers: {
|
|
133
|
+
Authorization: `Bearer ${token}`,
|
|
134
|
+
...(request.body ? { 'Content-Type': 'application/json' } : {}),
|
|
135
|
+
},
|
|
136
|
+
...(request.body ? { body: JSON.stringify(request.body) } : {}),
|
|
137
|
+
});
|
|
138
|
+
const text = await response.text();
|
|
139
|
+
const json = parseJson(text);
|
|
140
|
+
if (response.ok)
|
|
141
|
+
return json;
|
|
142
|
+
// Google throttles per user, not per process. Two clients on one account — a
|
|
143
|
+
// desktop app and an editor, say — share the quota, so a burst of reads can be
|
|
144
|
+
// rejected even though nothing is wrong with the request. That is a TRANSIENT
|
|
145
|
+
// condition and the documented response to it is to back off and try again:
|
|
146
|
+
// https://developers.google.com/workspace/gmail/api/guides/handle-errors
|
|
147
|
+
//
|
|
148
|
+
// Retrying belongs here rather than in each caller. A caller that must decide
|
|
149
|
+
// for itself whether a 429 is fatal will get it wrong somewhere, and the way it
|
|
150
|
+
// gets it wrong is by treating "I could not read this" as "there is nothing to
|
|
151
|
+
// read" — which is how a rate-limited inbox rendered as rows with no sender and
|
|
152
|
+
// no subject.
|
|
153
|
+
if (shouldRetry(response.status) && attempt < RETRY_ATTEMPTS) {
|
|
154
|
+
await sleep(retryDelayMs(attempt, response.headers.get('retry-after')));
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
throw new GoogleApiError(response.status, json, {
|
|
158
|
+
url: request.url,
|
|
159
|
+
method: request.method,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/** 429 = rate limited. 5xx = Google having a moment. Both are worth another go. */
|
|
164
|
+
const RETRY_ATTEMPTS = 4;
|
|
165
|
+
function shouldRetry(status) {
|
|
166
|
+
return status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Exponential backoff with full jitter. The jitter is not decoration: without it, a
|
|
170
|
+
* batch of calls throttled together retries together, re-creating the burst that
|
|
171
|
+
* caused the throttling.
|
|
172
|
+
*
|
|
173
|
+
* `Retry-After` wins when Google sends one — it is Google telling us what it wants.
|
|
174
|
+
*/
|
|
175
|
+
function retryDelayMs(attempt, retryAfter) {
|
|
176
|
+
if (retryAfter) {
|
|
177
|
+
const seconds = Number(retryAfter);
|
|
178
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
179
|
+
return Math.min(seconds * 1000, 30_000);
|
|
180
|
+
}
|
|
181
|
+
const ceiling = Math.min(1000 * 2 ** attempt, 16_000);
|
|
182
|
+
return Math.random() * ceiling;
|
|
183
|
+
}
|
|
184
|
+
function sleep(ms) {
|
|
185
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Download media to disk.
|
|
189
|
+
*
|
|
190
|
+
* STREAMED, deliberately. The bytes go from the socket to the file and are never a
|
|
191
|
+
* string at all. Never buffer a download through an in-memory string: accumulating
|
|
192
|
+
* the whole response and JSON.parse-ing it turns a 30 MB attachment into a ~40 MB
|
|
193
|
+
* string, then an object, then a Buffer — uncapped, for every download.
|
|
194
|
+
*/
|
|
195
|
+
export async function download(service, resourcePath, params, outputPath, options) {
|
|
196
|
+
const descriptor = options.descriptor ?? await loadDescriptor();
|
|
197
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
198
|
+
const request = buildRequest(descriptor, service, resourcePath, params);
|
|
199
|
+
const token = await getAccessToken(options.account);
|
|
200
|
+
const response = await doFetch(request.url, {
|
|
201
|
+
method: request.method,
|
|
202
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
203
|
+
});
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
const json = parseJson(await response.text());
|
|
206
|
+
throw new GoogleApiError(response.status, json, {
|
|
207
|
+
url: request.url,
|
|
208
|
+
method: request.method,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (!response.body)
|
|
212
|
+
throw new Error(`no response body for ${service}.${resourcePath}`);
|
|
213
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
214
|
+
await pipeline(Readable.fromWeb(response.body), createWriteStream(outputPath));
|
|
215
|
+
return outputPath;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Resumable, chunked media upload.
|
|
219
|
+
*
|
|
220
|
+
* The descriptor carries the upload paths for both protocols because Discovery
|
|
221
|
+
* DECLARES them. Verified live (ADR-103 item 4): a 25 MB attachment — 34.2 MB as
|
|
222
|
+
* RFC822, 93% of Google's declared 36,700,160-byte cap — uploaded in 5 chunks and
|
|
223
|
+
* read back byte-for-byte identical.
|
|
224
|
+
*/
|
|
225
|
+
export async function upload(service, resourcePath, params, options) {
|
|
226
|
+
const descriptor = options.descriptor ?? await loadDescriptor();
|
|
227
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
228
|
+
const { svc, method } = resolve(descriptor, service, resourcePath);
|
|
229
|
+
if (!method.mediaUpload)
|
|
230
|
+
throw new Error(`${service}.${resourcePath} does not support media upload`);
|
|
231
|
+
// Google DECLARES the ceiling. Refusing locally beats a confusing 400 from a
|
|
232
|
+
// 35 MB request that already crossed the wire.
|
|
233
|
+
const max = Number(method.mediaUpload.maxSize);
|
|
234
|
+
if (max && options.media.length > max) {
|
|
235
|
+
throw new Error(`${service}.${resourcePath}: payload is ${options.media.length} bytes; ` +
|
|
236
|
+
`Google's declared limit is ${max} bytes`);
|
|
237
|
+
}
|
|
238
|
+
const token = await getAccessToken(options.account);
|
|
239
|
+
const { path, query } = splitParams(svc, method, params);
|
|
240
|
+
const uploadPath = method.mediaUpload.resumable ?? method.mediaUpload.simple;
|
|
241
|
+
if (!uploadPath)
|
|
242
|
+
throw new Error(`${service}.${resourcePath} declares no upload protocol`);
|
|
243
|
+
const initiateUrl = applyQuery(new URL(expandPath(uploadPath, path), svc.rootUrl), { ...query, uploadType: 'resumable' }).toString();
|
|
244
|
+
const initiate = await doFetch(initiateUrl, {
|
|
245
|
+
method: method.httpMethod,
|
|
246
|
+
headers: {
|
|
247
|
+
Authorization: `Bearer ${token}`,
|
|
248
|
+
'Content-Type': 'application/json; charset=UTF-8',
|
|
249
|
+
'X-Upload-Content-Type': options.contentType,
|
|
250
|
+
'X-Upload-Content-Length': String(options.media.length),
|
|
251
|
+
},
|
|
252
|
+
body: JSON.stringify(options.metadata ?? {}),
|
|
253
|
+
});
|
|
254
|
+
if (!initiate.ok) {
|
|
255
|
+
const json = parseJson(await initiate.text());
|
|
256
|
+
throw new GoogleApiError(initiate.status, json, { url: initiateUrl, method: method.httpMethod });
|
|
257
|
+
}
|
|
258
|
+
const session = initiate.headers.get('location');
|
|
259
|
+
if (!session)
|
|
260
|
+
throw new Error('resumable upload: initiate returned no Location header');
|
|
261
|
+
const chunkSize = options.chunkSize ?? 8 * 1024 * 1024;
|
|
262
|
+
let offset = 0;
|
|
263
|
+
while (offset < options.media.length) {
|
|
264
|
+
const end = Math.min(offset + chunkSize, options.media.length);
|
|
265
|
+
const response = await doFetch(session, {
|
|
266
|
+
method: 'PUT',
|
|
267
|
+
headers: { 'Content-Range': `bytes ${offset}-${end - 1}/${options.media.length}` },
|
|
268
|
+
body: options.media.subarray(offset, end),
|
|
269
|
+
});
|
|
270
|
+
// 308 "Resume Incomplete" is the protocol working, not an error.
|
|
271
|
+
if (response.status === 308) {
|
|
272
|
+
offset = end;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const text = await response.text();
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
throw new GoogleApiError(response.status, parseJson(text), { url: session, method: 'PUT' });
|
|
278
|
+
}
|
|
279
|
+
return parseJson(text);
|
|
280
|
+
}
|
|
281
|
+
throw new Error('resumable upload: media consumed without a terminal response');
|
|
282
|
+
}
|
|
283
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/google/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface ApiParam {
|
|
2
|
+
location: 'path' | 'query';
|
|
3
|
+
required?: boolean;
|
|
4
|
+
repeated?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface ApiMediaUpload {
|
|
7
|
+
maxSize?: string;
|
|
8
|
+
accept?: string[];
|
|
9
|
+
simple?: string;
|
|
10
|
+
resumable?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ApiMethod {
|
|
13
|
+
path: string;
|
|
14
|
+
httpMethod: string;
|
|
15
|
+
parameters: Record<string, ApiParam>;
|
|
16
|
+
scopes?: string[];
|
|
17
|
+
supportsMediaDownload?: boolean;
|
|
18
|
+
mediaUpload?: ApiMediaUpload;
|
|
19
|
+
}
|
|
20
|
+
export interface ApiService {
|
|
21
|
+
version: string;
|
|
22
|
+
rootUrl: string;
|
|
23
|
+
servicePath: string;
|
|
24
|
+
discoveryUrl: string;
|
|
25
|
+
/** `fields`, `alt`, `quotaUser`… declared once at the document root, not per method. */
|
|
26
|
+
globalParameters: Record<string, ApiParam>;
|
|
27
|
+
methods: Record<string, ApiMethod>;
|
|
28
|
+
}
|
|
29
|
+
export interface ApiDescriptor {
|
|
30
|
+
generatedFrom: string;
|
|
31
|
+
services: Record<string, ApiService>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Load the descriptor. Resolved as a sibling of this module, which makes it work
|
|
35
|
+
* under `src/` (vitest) and under `build/` (the shipped server) alike — the same
|
|
36
|
+
* trick `loadManifest()` uses, and the reason `npx` works.
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadDescriptor(): Promise<ApiDescriptor>;
|
|
39
|
+
/** Tests only: drop the memoised descriptor. */
|
|
40
|
+
export declare function resetDescriptorCache(): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The API descriptor: a generated, committed transcription of Google's Discovery
|
|
3
|
+
* documents, pruned to what making a REQUEST needs.
|
|
4
|
+
*
|
|
5
|
+
* Generated at BUILD time by `scripts/generate-descriptor.mjs` and committed, so
|
|
6
|
+
* there is no network dependency at startup and nothing breaks offline. Drift is a
|
|
7
|
+
* CI check, not a production surprise.
|
|
8
|
+
*
|
|
9
|
+
* It records what Discovery says about how to CALL Google. It records nothing
|
|
10
|
+
* about what a RESPONSE means — Discovery's `schemas` block (~90% of the raw
|
|
11
|
+
* document) is deliberately DISCARDED. A descriptor that knows response shapes is
|
|
12
|
+
* a descriptor that can start helpfully reshaping them.
|
|
13
|
+
*
|
|
14
|
+
* See ADR-103.
|
|
15
|
+
*/
|
|
16
|
+
import { readFile } from 'node:fs/promises';
|
|
17
|
+
import { dirname, resolve } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
let cached;
|
|
20
|
+
/**
|
|
21
|
+
* Load the descriptor. Resolved as a sibling of this module, which makes it work
|
|
22
|
+
* under `src/` (vitest) and under `build/` (the shipped server) alike — the same
|
|
23
|
+
* trick `loadManifest()` uses, and the reason `npx` works.
|
|
24
|
+
*/
|
|
25
|
+
export async function loadDescriptor() {
|
|
26
|
+
if (cached)
|
|
27
|
+
return cached;
|
|
28
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const raw = await readFile(resolve(here, 'descriptor.json'), 'utf-8');
|
|
30
|
+
cached = JSON.parse(raw);
|
|
31
|
+
return cached;
|
|
32
|
+
}
|
|
33
|
+
/** Tests only: drop the memoised descriptor. */
|
|
34
|
+
export function resetDescriptorCache() {
|
|
35
|
+
cached = undefined;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=descriptor.js.map
|