mcp-google-multi 6.0.0-beta.2 → 6.0.0-beta.3
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/dist/accounts.d.ts +17 -1
- package/dist/accounts.js +52 -15
- package/dist/api-probe.d.ts +5 -0
- package/dist/api-probe.js +20 -15
- package/dist/arg-normalize.js +6 -5
- package/dist/arg-strict.d.ts +7 -0
- package/dist/arg-strict.js +34 -4
- package/dist/auth.d.ts +4 -3
- package/dist/auth.js +11 -13
- package/dist/boot-gates.d.ts +26 -0
- package/dist/boot-gates.js +38 -0
- package/dist/cimd-cache.d.ts +38 -0
- package/dist/cimd-cache.js +117 -0
- package/dist/client.d.ts +32 -2
- package/dist/client.js +0 -0
- package/dist/compose.d.ts +11 -0
- package/dist/compose.js +109 -0
- package/dist/config-file.d.ts +13 -0
- package/dist/config-file.js +37 -1
- package/dist/discover.js +3 -1
- package/dist/discovery-client.d.ts +3 -0
- package/dist/discovery-client.js +25 -2
- package/dist/doctor.d.ts +6 -2
- package/dist/doctor.js +119 -23
- package/dist/executor.d.ts +3 -0
- package/dist/executor.js +4 -2
- package/dist/fanout.d.ts +9 -1
- package/dist/fanout.js +44 -10
- package/dist/fs-atomic.d.ts +11 -0
- package/dist/fs-atomic.js +219 -28
- package/dist/hint-provider.d.ts +129 -0
- package/dist/hint-provider.js +64 -0
- package/dist/http-transport.d.ts +62 -4
- package/dist/http-transport.js +343 -24
- package/dist/identity.d.ts +4 -1
- package/dist/identity.js +25 -6
- package/dist/index.js +66 -138
- package/dist/master-key.js +13 -5
- package/dist/mcp-token.d.ts +136 -19
- package/dist/mcp-token.js +569 -72
- package/dist/migrate-config.js +2 -2
- package/dist/oauth-as.d.ts +103 -3
- package/dist/oauth-as.js +374 -79
- package/dist/oauth-consent.d.ts +10 -0
- package/dist/oauth-consent.js +16 -0
- package/dist/outbound-allowlist.js +5 -2
- package/dist/reauth-hint.d.ts +1 -1
- package/dist/reauth-hint.js +12 -8
- package/dist/registry-transfer.d.ts +5 -0
- package/dist/registry-transfer.js +65 -5
- package/dist/registry.d.ts +18 -1
- package/dist/registry.js +71 -15
- package/dist/scope-catalog.d.ts +11 -0
- package/dist/scope-catalog.js +23 -6
- package/dist/scope-observability.d.ts +8 -6
- package/dist/scope-observability.js +29 -13
- package/dist/services.d.ts +15 -10
- package/dist/services.js +59 -15
- package/dist/ssrf-guard.d.ts +23 -9
- package/dist/ssrf-guard.js +101 -62
- package/dist/tenant-purge.d.ts +26 -0
- package/dist/tenant-purge.js +67 -0
- package/dist/token-store.d.ts +20 -2
- package/dist/token-store.js +0 -0
- package/dist/tools/_errors.d.ts +3 -0
- package/dist/tools/_errors.js +53 -24
- package/dist/tools/_local-files.d.ts +9 -0
- package/dist/tools/_local-files.js +18 -0
- package/dist/tools/account-wizard.d.ts +10 -1
- package/dist/tools/account-wizard.js +74 -17
- package/dist/tools/accounts-tool.d.ts +7 -0
- package/dist/tools/accounts-tool.js +34 -18
- package/dist/tools/admin.d.ts +10 -1
- package/dist/tools/admin.js +15 -11
- package/dist/tools/analytics.d.ts +10 -1
- package/dist/tools/analytics.js +13 -8
- package/dist/tools/calendar.d.ts +2 -1
- package/dist/tools/calendar.js +18 -14
- package/dist/tools/chat.d.ts +10 -1
- package/dist/tools/chat.js +13 -8
- package/dist/tools/contacts.d.ts +2 -1
- package/dist/tools/contacts.js +17 -13
- package/dist/tools/docs.d.ts +2 -1
- package/dist/tools/docs.js +34 -30
- package/dist/tools/drive.d.ts +2 -1
- package/dist/tools/drive.js +65 -50
- package/dist/tools/forms.d.ts +10 -1
- package/dist/tools/forms.js +19 -12
- package/dist/tools/generated/_shared.d.ts +2 -1
- package/dist/tools/generated/_shared.js +7 -3
- package/dist/tools/generated/admin.d.ts +2 -1
- package/dist/tools/generated/admin.js +265 -265
- package/dist/tools/generated/analytics.d.ts +2 -1
- package/dist/tools/generated/analytics.js +125 -125
- package/dist/tools/generated/appsmarket.d.ts +2 -1
- package/dist/tools/generated/appsmarket.js +5 -5
- package/dist/tools/generated/calendar.d.ts +2 -1
- package/dist/tools/generated/calendar.js +55 -55
- package/dist/tools/generated/chat.d.ts +2 -1
- package/dist/tools/generated/chat.js +95 -95
- package/dist/tools/generated/classroom.d.ts +2 -1
- package/dist/tools/generated/classroom.js +209 -209
- package/dist/tools/generated/cloudidentity.d.ts +2 -1
- package/dist/tools/generated/cloudidentity.js +133 -133
- package/dist/tools/generated/cloudsearch.d.ts +2 -1
- package/dist/tools/generated/cloudsearch.js +99 -99
- package/dist/tools/generated/contacts.d.ts +2 -1
- package/dist/tools/generated/contacts.js +29 -29
- package/dist/tools/generated/drive.d.ts +2 -1
- package/dist/tools/generated/drive.js +71 -71
- package/dist/tools/generated/driveactivity.d.ts +2 -1
- package/dist/tools/generated/driveactivity.js +3 -3
- package/dist/tools/generated/drivelabels.d.ts +2 -1
- package/dist/tools/generated/drivelabels.js +53 -53
- package/dist/tools/generated/forms.d.ts +2 -1
- package/dist/tools/generated/forms.js +7 -7
- package/dist/tools/generated/gmail.d.ts +2 -1
- package/dist/tools/generated/gmail.js +117 -117
- package/dist/tools/generated/groupsmigration.d.ts +2 -1
- package/dist/tools/generated/groupsmigration.js +3 -3
- package/dist/tools/generated/groupssettings.d.ts +2 -1
- package/dist/tools/generated/groupssettings.js +7 -7
- package/dist/tools/generated/index.d.ts +2 -1
- package/dist/tools/generated/keep.d.ts +2 -1
- package/dist/tools/generated/keep.js +15 -15
- package/dist/tools/generated/licensing.d.ts +2 -1
- package/dist/tools/generated/licensing.js +15 -15
- package/dist/tools/generated/meet.d.ts +2 -1
- package/dist/tools/generated/meet.js +27 -27
- package/dist/tools/generated/postmaster.d.ts +2 -1
- package/dist/tools/generated/postmaster.js +9 -9
- package/dist/tools/generated/reseller.d.ts +2 -1
- package/dist/tools/generated/reseller.js +35 -35
- package/dist/tools/generated/script.d.ts +2 -1
- package/dist/tools/generated/script.js +33 -33
- package/dist/tools/generated/searchconsole.d.ts +2 -1
- package/dist/tools/generated/searchconsole.js +3 -3
- package/dist/tools/generated/sheets.d.ts +2 -1
- package/dist/tools/generated/sheets.js +15 -15
- package/dist/tools/generated/tasks.d.ts +2 -1
- package/dist/tools/generated/tasks.js +5 -5
- package/dist/tools/generated/vault.d.ts +2 -1
- package/dist/tools/generated/vault.js +67 -67
- package/dist/tools/generated/workspaceevents.d.ts +2 -1
- package/dist/tools/generated/workspaceevents.js +31 -31
- package/dist/tools/gmail.d.ts +2 -1
- package/dist/tools/gmail.js +45 -33
- package/dist/tools/google-api.d.ts +2 -3
- package/dist/tools/google-api.js +30 -16
- package/dist/tools/meet.d.ts +10 -1
- package/dist/tools/meet.js +13 -9
- package/dist/tools/searchconsole.d.ts +2 -1
- package/dist/tools/searchconsole.js +17 -13
- package/dist/tools/sheets.d.ts +2 -1
- package/dist/tools/sheets.js +36 -32
- package/dist/tools/slides.d.ts +10 -1
- package/dist/tools/slides.js +17 -10
- package/dist/tools/tasks.d.ts +2 -1
- package/dist/tools/tasks.js +19 -15
- package/dist/trim.d.ts +5 -0
- package/dist/trim.js +20 -0
- package/dist/usage-metrics.d.ts +23 -0
- package/dist/usage-metrics.js +65 -47
- package/dist/write-control.js +10 -2
- package/package.json +27 -1
package/dist/tools/slides.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { slides as slidesClient } from '@googleapis/slides';
|
|
3
|
-
import {
|
|
3
|
+
import { accountArgLive } from '../accounts.js';
|
|
4
4
|
import { getClient } from '../client.js';
|
|
5
5
|
import { handleGoogleApiError } from './_errors.js';
|
|
6
6
|
import { coerceArray, coerceBoolean, coerceJson } from './_coerce.js';
|
|
7
7
|
import { sliceClean } from '../trim.js';
|
|
8
|
-
const accountEnum = accountAliasSchema.optional();
|
|
9
8
|
const SLIDE_TEXT_DIGEST_CHARS = 200;
|
|
10
|
-
export function registerSlidesTools(server) {
|
|
9
|
+
export function registerSlidesTools(server, deps = {}) {
|
|
10
|
+
// Per-registry, LIVE account enum + injectable client (S1.10): the
|
|
11
|
+
// schema follows the registry's account view at parse time, and the
|
|
12
|
+
// custody path is the context's, not the process global.
|
|
13
|
+
const accountEnum = accountArgLive(() => server.accountAliases()).optional();
|
|
14
|
+
const getClientFn = deps.getClientFn ?? getClient;
|
|
11
15
|
server.registerTool('slides_create', {
|
|
12
16
|
description: 'Create a new Google Slides presentation (starts with one blank slide)',
|
|
13
17
|
inputSchema: {
|
|
@@ -16,7 +20,7 @@ export function registerSlidesTools(server) {
|
|
|
16
20
|
},
|
|
17
21
|
}, async ({ account, title }) => {
|
|
18
22
|
try {
|
|
19
|
-
const auth = await
|
|
23
|
+
const auth = await getClientFn(account);
|
|
20
24
|
const slides = slidesClient({ version: 'v1', auth });
|
|
21
25
|
const res = await slides.presentations.create({ requestBody: { title } });
|
|
22
26
|
return {
|
|
@@ -41,7 +45,7 @@ export function registerSlidesTools(server) {
|
|
|
41
45
|
},
|
|
42
46
|
}, async ({ account, presentationId, full }) => {
|
|
43
47
|
try {
|
|
44
|
-
const auth = await
|
|
48
|
+
const auth = await getClientFn(account);
|
|
45
49
|
const slides = slidesClient({ version: 'v1', auth });
|
|
46
50
|
const res = await slides.presentations.get({ presentationId });
|
|
47
51
|
const payload = full ? res.data : summarizePresentation(res.data);
|
|
@@ -62,7 +66,7 @@ export function registerSlidesTools(server) {
|
|
|
62
66
|
},
|
|
63
67
|
}, async ({ account, presentationId, pageObjectId }) => {
|
|
64
68
|
try {
|
|
65
|
-
const auth = await
|
|
69
|
+
const auth = await getClientFn(account);
|
|
66
70
|
const slides = slidesClient({ version: 'v1', auth });
|
|
67
71
|
const res = await slides.presentations.pages.get({ presentationId, pageObjectId });
|
|
68
72
|
return {
|
|
@@ -85,7 +89,7 @@ export function registerSlidesTools(server) {
|
|
|
85
89
|
},
|
|
86
90
|
}, async ({ account, presentationId, pageObjectId, mimeType, thumbnailSize }) => {
|
|
87
91
|
try {
|
|
88
|
-
const auth = await
|
|
92
|
+
const auth = await getClientFn(account);
|
|
89
93
|
const slides = slidesClient({ version: 'v1', auth });
|
|
90
94
|
const res = await slides.presentations.pages.getThumbnail({
|
|
91
95
|
presentationId,
|
|
@@ -116,7 +120,7 @@ export function registerSlidesTools(server) {
|
|
|
116
120
|
},
|
|
117
121
|
}, async ({ account, presentationId, requests, writeControl }) => {
|
|
118
122
|
try {
|
|
119
|
-
const auth = await
|
|
123
|
+
const auth = await getClientFn(account);
|
|
120
124
|
const slides = slidesClient({ version: 'v1', auth });
|
|
121
125
|
const res = await slides.presentations.batchUpdate({
|
|
122
126
|
presentationId,
|
|
@@ -180,6 +184,9 @@ export function summarizePresentation(p) {
|
|
|
180
184
|
hint: 'Pass full:true for the raw Presentation JSON, or slides_page_get for one slide.',
|
|
181
185
|
};
|
|
182
186
|
}
|
|
183
|
-
function handleSlidesError(error, account) {
|
|
184
|
-
return handleGoogleApiError(error, account,
|
|
187
|
+
export function handleSlidesError(error, account) {
|
|
188
|
+
return handleGoogleApiError(error, account, {
|
|
189
|
+
scope: "Slides tools require the optional \"slides\" scope bundle. Add GOOGLE_OPTIONAL_SCOPES=slides (or include \"slides\" in the list) and re-auth.",
|
|
190
|
+
bundle: 'slides',
|
|
191
|
+
});
|
|
185
192
|
}
|
package/dist/tools/tasks.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { ToolRegistry } from '../registry.js';
|
|
2
|
-
|
|
2
|
+
import { type CuratedToolDeps } from '../client.js';
|
|
3
|
+
export declare function registerTasksTools(server: ToolRegistry, deps?: CuratedToolDeps): void;
|
package/dist/tools/tasks.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { coerceBoolean } from './_coerce.js';
|
|
3
3
|
import { tasks as tasksClient } from '@googleapis/tasks';
|
|
4
|
-
import {
|
|
4
|
+
import { accountArgLive } from '../accounts.js';
|
|
5
5
|
import { getClient } from '../client.js';
|
|
6
6
|
import { handleGoogleApiError, invalidParams } from './_errors.js';
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export function registerTasksTools(server, deps = {}) {
|
|
8
|
+
// Per-registry, LIVE account enum + injectable client (S1.10): the
|
|
9
|
+
// schema follows the registry's account view at parse time, and the
|
|
10
|
+
// custody path is the context's, not the process global.
|
|
11
|
+
const accountEnum = accountArgLive(() => server.accountAliases()).optional();
|
|
12
|
+
const getClientFn = deps.getClientFn ?? getClient;
|
|
9
13
|
// ─── Tasklists ─────────────────────────────────────────────────────────
|
|
10
14
|
server.registerTool('tasks_lists_list', {
|
|
11
15
|
description: 'List all tasklists (the user\'s top-level task containers)',
|
|
@@ -16,7 +20,7 @@ export function registerTasksTools(server) {
|
|
|
16
20
|
},
|
|
17
21
|
}, async ({ account, maxResults, pageToken }) => {
|
|
18
22
|
try {
|
|
19
|
-
const auth = await
|
|
23
|
+
const auth = await getClientFn(account);
|
|
20
24
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
21
25
|
const res = await tasks.tasklists.list({
|
|
22
26
|
maxResults: maxResults ?? 100,
|
|
@@ -38,7 +42,7 @@ export function registerTasksTools(server) {
|
|
|
38
42
|
},
|
|
39
43
|
}, async ({ account, tasklistId }) => {
|
|
40
44
|
try {
|
|
41
|
-
const auth = await
|
|
45
|
+
const auth = await getClientFn(account);
|
|
42
46
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
43
47
|
const res = await tasks.tasklists.get({ tasklist: tasklistId });
|
|
44
48
|
return {
|
|
@@ -57,7 +61,7 @@ export function registerTasksTools(server) {
|
|
|
57
61
|
},
|
|
58
62
|
}, async ({ account, title }) => {
|
|
59
63
|
try {
|
|
60
|
-
const auth = await
|
|
64
|
+
const auth = await getClientFn(account);
|
|
61
65
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
62
66
|
const res = await tasks.tasklists.insert({
|
|
63
67
|
requestBody: { title },
|
|
@@ -79,7 +83,7 @@ export function registerTasksTools(server) {
|
|
|
79
83
|
},
|
|
80
84
|
}, async ({ account, tasklistId, title }) => {
|
|
81
85
|
try {
|
|
82
|
-
const auth = await
|
|
86
|
+
const auth = await getClientFn(account);
|
|
83
87
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
84
88
|
const res = await tasks.tasklists.patch({
|
|
85
89
|
tasklist: tasklistId,
|
|
@@ -101,7 +105,7 @@ export function registerTasksTools(server) {
|
|
|
101
105
|
},
|
|
102
106
|
}, async ({ account, tasklistId }) => {
|
|
103
107
|
try {
|
|
104
|
-
const auth = await
|
|
108
|
+
const auth = await getClientFn(account);
|
|
105
109
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
106
110
|
await tasks.tasklists.delete({ tasklist: tasklistId });
|
|
107
111
|
return {
|
|
@@ -132,7 +136,7 @@ export function registerTasksTools(server) {
|
|
|
132
136
|
},
|
|
133
137
|
}, async ({ account, tasklistId, ...params }) => {
|
|
134
138
|
try {
|
|
135
|
-
const auth = await
|
|
139
|
+
const auth = await getClientFn(account);
|
|
136
140
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
137
141
|
const res = await tasks.tasks.list({
|
|
138
142
|
tasklist: tasklistId,
|
|
@@ -155,7 +159,7 @@ export function registerTasksTools(server) {
|
|
|
155
159
|
},
|
|
156
160
|
}, async ({ account, tasklistId, taskId }) => {
|
|
157
161
|
try {
|
|
158
|
-
const auth = await
|
|
162
|
+
const auth = await getClientFn(account);
|
|
159
163
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
160
164
|
const res = await tasks.tasks.get({ tasklist: tasklistId, task: taskId });
|
|
161
165
|
return {
|
|
@@ -180,7 +184,7 @@ export function registerTasksTools(server) {
|
|
|
180
184
|
},
|
|
181
185
|
}, async ({ account, tasklistId, title, notes, due, status, parent, previous }) => {
|
|
182
186
|
try {
|
|
183
|
-
const auth = await
|
|
187
|
+
const auth = await getClientFn(account);
|
|
184
188
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
185
189
|
const requestBody = { title };
|
|
186
190
|
if (notes !== undefined)
|
|
@@ -216,7 +220,7 @@ export function registerTasksTools(server) {
|
|
|
216
220
|
},
|
|
217
221
|
}, async ({ account, tasklistId, taskId, title, notes, due, status }) => {
|
|
218
222
|
try {
|
|
219
|
-
const auth = await
|
|
223
|
+
const auth = await getClientFn(account);
|
|
220
224
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
221
225
|
const requestBody = {};
|
|
222
226
|
if (title !== undefined)
|
|
@@ -252,7 +256,7 @@ export function registerTasksTools(server) {
|
|
|
252
256
|
},
|
|
253
257
|
}, async ({ account, tasklistId, taskId }) => {
|
|
254
258
|
try {
|
|
255
|
-
const auth = await
|
|
259
|
+
const auth = await getClientFn(account);
|
|
256
260
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
257
261
|
await tasks.tasks.delete({ tasklist: tasklistId, task: taskId });
|
|
258
262
|
return {
|
|
@@ -275,7 +279,7 @@ export function registerTasksTools(server) {
|
|
|
275
279
|
},
|
|
276
280
|
}, async ({ account, tasklistId, taskId, parent, previous, destinationTasklist }) => {
|
|
277
281
|
try {
|
|
278
|
-
const auth = await
|
|
282
|
+
const auth = await getClientFn(account);
|
|
279
283
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
280
284
|
const res = await tasks.tasks.move({
|
|
281
285
|
tasklist: tasklistId,
|
|
@@ -300,7 +304,7 @@ export function registerTasksTools(server) {
|
|
|
300
304
|
},
|
|
301
305
|
}, async ({ account, tasklistId }) => {
|
|
302
306
|
try {
|
|
303
|
-
const auth = await
|
|
307
|
+
const auth = await getClientFn(account);
|
|
304
308
|
const tasks = tasksClient({ version: 'v1', auth });
|
|
305
309
|
await tasks.tasks.clear({ tasklist: tasklistId });
|
|
306
310
|
return {
|
package/dist/trim.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export interface CappedText {
|
|
|
6
6
|
}
|
|
7
7
|
export declare function capText(text: string, maxChars: number, offset?: number): CappedText;
|
|
8
8
|
export declare function sliceClean(text: string, max: number): string;
|
|
9
|
+
/** An untrusted value made fit for one log line: never throws, at most `max`
|
|
10
|
+
* characters of it (then `...(len=N)`), each unsafe character as a literal
|
|
11
|
+
* `\uXXXX`. Backslash stays as is, so a value already through here comes back
|
|
12
|
+
* unchanged and a clean short value keeps its exact text. */
|
|
13
|
+
export declare function logSafe(value: unknown, max: number): string;
|
|
9
14
|
/** Largest prefix whose JSON encoding (the wrapping quotes included) fits
|
|
10
15
|
* `maxEncoded`. Slicing raw characters overshoots whenever the text contains
|
|
11
16
|
* quotes or backslashes, which JSON.stringify doubles: the escape hatch's
|
package/dist/trim.js
CHANGED
|
@@ -11,6 +11,26 @@ export function sliceClean(text, max) {
|
|
|
11
11
|
const sliced = text.slice(0, max);
|
|
12
12
|
return /[\uD800-\uDBFF]$/.test(sliced) ? sliced.slice(0, -1) : sliced;
|
|
13
13
|
}
|
|
14
|
+
// C0, DEL and C1 (NEL, 8-bit CSI and OSC) can end a line or drive a terminal;
|
|
15
|
+
// the line and paragraph separators end a line for many readers; the bidi and
|
|
16
|
+
// invisible format controls make a line read differently from its bytes.
|
|
17
|
+
const LOG_UNSAFE = /[\p{Cc}\u061c\u200e\u200f\u202a-\u202e\u2028\u2029\u2066-\u2069]/gu;
|
|
18
|
+
/** An untrusted value made fit for one log line: never throws, at most `max`
|
|
19
|
+
* characters of it (then `...(len=N)`), each unsafe character as a literal
|
|
20
|
+
* `\uXXXX`. Backslash stays as is, so a value already through here comes back
|
|
21
|
+
* unchanged and a clean short value keeps its exact text. */
|
|
22
|
+
export function logSafe(value, max) {
|
|
23
|
+
let text;
|
|
24
|
+
try {
|
|
25
|
+
text = String(value);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return '<unprintable>';
|
|
29
|
+
}
|
|
30
|
+
const cut = text.length > max ? sliceClean(text, max) : text;
|
|
31
|
+
const escaped = cut.replace(LOG_UNSAFE, (c) => `\\u${c.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
32
|
+
return cut === text ? escaped : `${escaped}...(len=${text.length})`;
|
|
33
|
+
}
|
|
14
34
|
/** Largest prefix whose JSON encoding (the wrapping quotes included) fits
|
|
15
35
|
* `maxEncoded`. Slicing raw characters overshoots whenever the text contains
|
|
16
36
|
* quotes or backslashes, which JSON.stringify doubles: the escape hatch's
|
package/dist/usage-metrics.d.ts
CHANGED
|
@@ -86,6 +86,26 @@ export interface InitOptions {
|
|
|
86
86
|
now?: () => number;
|
|
87
87
|
monotonic?: () => number;
|
|
88
88
|
log?: (line: string) => void;
|
|
89
|
+
/** Default true. Three chains correlate CONSECUTIVE calls, which is only
|
|
90
|
+
* meaningful when one caller owns the whole process: the retry and bigram
|
|
91
|
+
* chains, and pendingMid (the escape hatch's resolved methodId, attached to
|
|
92
|
+
* the next google_api_call event's `mid`). A host serving several callers
|
|
93
|
+
* sets false so none of the three ever cross-link a call between tenants.
|
|
94
|
+
* DayAgg shape is unchanged (retries/bigrams stay empty maps);
|
|
95
|
+
* escape.methods still counts every resolved call either way. */
|
|
96
|
+
sequencingEnabled?: boolean;
|
|
97
|
+
/** Default true. When false, no events.jsonl / events.1.jsonl is ever
|
|
98
|
+
* created or appended to; only agg/ exists. A host serving several callers
|
|
99
|
+
* sets false: each event line carries a minute timestamp, the boot id and a
|
|
100
|
+
* per-process sequence number, which can be joined against a per-caller log
|
|
101
|
+
* such as an audit trail; the day aggregate keeps the same counters without
|
|
102
|
+
* order. */
|
|
103
|
+
eventLog?: boolean;
|
|
104
|
+
/** Default none. A host's own closed slug set, unioned with
|
|
105
|
+
* KNOWN_ERROR_SLUGS for this instance only; each entry must match
|
|
106
|
+
* /^[A-Za-z][A-Za-z0-9_]{0,63}$/ or it is dropped silently. Absent,
|
|
107
|
+
* behavior is byte-identical to today. */
|
|
108
|
+
extraErrorSlugs?: readonly string[];
|
|
89
109
|
}
|
|
90
110
|
/** Deep-add b into a for the DayAgg shape (numbers add, maps union). */
|
|
91
111
|
export declare function mergeDay(a: DayAgg, b: DayAgg): DayAgg;
|
|
@@ -108,6 +128,9 @@ export declare class Metrics {
|
|
|
108
128
|
private dirty;
|
|
109
129
|
private failures;
|
|
110
130
|
private disabled;
|
|
131
|
+
private readonly sequencing;
|
|
132
|
+
private readonly eventLog;
|
|
133
|
+
private readonly errorSlugs;
|
|
111
134
|
constructor(opts: InitOptions);
|
|
112
135
|
private day;
|
|
113
136
|
private initStorage;
|
package/dist/usage-metrics.js
CHANGED
|
@@ -33,10 +33,11 @@ const TOOL_NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;
|
|
|
33
33
|
// Declared argument keys are camelCase identifiers, some dotted
|
|
34
34
|
// (groupKey.id, debugOptions.enableDebugging).
|
|
35
35
|
const ARG_KEY_RE = /^[A-Za-z][A-Za-z0-9_.]{0,63}$/;
|
|
36
|
+
const EXTRA_ERROR_SLUG_RE = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
36
37
|
/** Union of every `error:` slug literal emitted anywhere in src/ (kept honest
|
|
37
38
|
* by a set-equality grep test). Anything outside buckets to `other`. */
|
|
38
39
|
export const KNOWN_ERROR_SLUGS = new Set([
|
|
39
|
-
'E_ALIAS_EXISTS', 'E_CIMD_INVALID', 'E_ENV_ACCOUNTS_MODE', 'E_MCP_TOKEN_INVALID',
|
|
40
|
+
'E_ALIAS_EXISTS', 'E_CIMD_INVALID', 'E_CIMD_SSRF_BLOCKED', 'E_ENV_ACCOUNTS_MODE', 'E_MCP_TOKEN_INVALID',
|
|
40
41
|
'E_NO_DEFAULT_ACCOUNT', 'E_UNKNOWN_BUNDLE', 'E_VALIDATION', 'ambiguous',
|
|
41
42
|
'api_not_enabled', 'auth_required', 'bad_request', 'binary', 'binary_unsupported',
|
|
42
43
|
'confirmation_declined', 'diagnose_failed', 'discovery_unavailable', 'dispatch_timeout',
|
|
@@ -44,7 +45,7 @@ export const KNOWN_ERROR_SLUGS = new Set([
|
|
|
44
45
|
'insufficient_scope', 'internal', 'invalid_client', 'invalid_client_metadata',
|
|
45
46
|
'invalid_grant', 'invalid_params', 'invalid_query', 'invalid_request',
|
|
46
47
|
'invalid_scope', 'network_error', 'not_found', 'rate_limited',
|
|
47
|
-
'reauth_required', 'recipient_not_allowed', 'too_large', 'toolset_disabled', 'unknown_api',
|
|
48
|
+
'reauth_required', 'recipient_not_allowed', 'temporarily_unavailable', 'tenant_not_found', 'too_large', 'toolset_disabled', 'unknown_api',
|
|
48
49
|
'unknown_argument', 'unknown_method', 'unsupported_grant_type', 'unsupported_type',
|
|
49
50
|
'untrusted_host', 'upstream_error', 'validation_error', 'write_disabled',
|
|
50
51
|
]);
|
|
@@ -221,7 +222,14 @@ export class Metrics {
|
|
|
221
222
|
dirty = false;
|
|
222
223
|
failures = 0;
|
|
223
224
|
disabled = false;
|
|
225
|
+
sequencing;
|
|
226
|
+
eventLog;
|
|
227
|
+
errorSlugs;
|
|
224
228
|
constructor(opts) {
|
|
229
|
+
this.sequencing = opts.sequencingEnabled !== false;
|
|
230
|
+
this.eventLog = opts.eventLog !== false;
|
|
231
|
+
const extras = (opts.extraErrorSlugs ?? []).filter((s) => EXTRA_ERROR_SLUG_RE.test(s));
|
|
232
|
+
this.errorSlugs = extras.length ? new Set([...KNOWN_ERROR_SLUGS, ...extras]) : KNOWN_ERROR_SLUGS;
|
|
225
233
|
this.dir = opts.dir ?? path.join(stateDir(opts.env), METRICS_DIR_NAME);
|
|
226
234
|
this.aggDir = path.join(this.dir, 'agg');
|
|
227
235
|
this.eventsFile = path.join(this.dir, 'events.jsonl');
|
|
@@ -250,7 +258,8 @@ export class Metrics {
|
|
|
250
258
|
}
|
|
251
259
|
}
|
|
252
260
|
this.pruneAggs();
|
|
253
|
-
|
|
261
|
+
if (this.eventLog)
|
|
262
|
+
this.pruneEventAge(this.eventsFile);
|
|
254
263
|
}
|
|
255
264
|
catch (e) {
|
|
256
265
|
this.log(`local usage metrics: init failed (${e.message}); disabled for this process\n`);
|
|
@@ -318,7 +327,7 @@ export class Metrics {
|
|
|
318
327
|
if (typeof first === 'string' && first.length < 65_536) {
|
|
319
328
|
const env = JSON.parse(first);
|
|
320
329
|
if (typeof env.error === 'string')
|
|
321
|
-
slug =
|
|
330
|
+
slug = this.errorSlugs.has(env.error) ? env.error : 'other';
|
|
322
331
|
hinted = typeof env.hint === 'string' && env.hint.length > 0;
|
|
323
332
|
}
|
|
324
333
|
}
|
|
@@ -357,45 +366,51 @@ export class Metrics {
|
|
|
357
366
|
f.calls += 1;
|
|
358
367
|
addInto(f, fan);
|
|
359
368
|
}
|
|
360
|
-
// retry self-correction chain (spec section 1): in-memory only.
|
|
369
|
+
// retry self-correction chain (spec section 1): in-memory only. Both
|
|
370
|
+
// chains link consecutive calls, so they only run when one caller owns
|
|
371
|
+
// the process (sequencingEnabled).
|
|
361
372
|
const nowM = this.mono();
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
373
|
+
if (this.sequencing) {
|
|
374
|
+
const prev = this.lastError.get(entry.name);
|
|
375
|
+
if (prev && nowM - prev.at <= RETRY_WINDOW_MS) {
|
|
376
|
+
const rr = (this.deltas.retries[prev.slug] ??= { hintedOk: 0, hintedFail: 0, unhintedOk: 0, unhintedFail: 0 });
|
|
377
|
+
const key = `${prev.hinted ? 'hinted' : 'unhinted'}${ok ? 'Ok' : 'Fail'}`;
|
|
378
|
+
rr[key] += 1;
|
|
379
|
+
this.lastError.delete(entry.name);
|
|
380
|
+
}
|
|
381
|
+
if (!ok && slug)
|
|
382
|
+
this.lastError.set(entry.name, { slug, hinted: hinted === true, at: nowM });
|
|
383
|
+
// bigrams: ordered pairs within the process, 5-minute gap breaks the chain.
|
|
384
|
+
if (this.lastTool && nowM - this.lastTool.at <= BIGRAM_GAP_MS) {
|
|
385
|
+
addInto(this.deltas.bigrams, `${this.lastTool.name}>${entry.name}`);
|
|
386
|
+
}
|
|
387
|
+
this.lastTool = { name: entry.name, at: nowM };
|
|
374
388
|
}
|
|
375
|
-
this.
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
389
|
+
if (this.eventLog) {
|
|
390
|
+
const event = {
|
|
391
|
+
ts: new Date(this.now()).toISOString().slice(0, 16) + ':00Z',
|
|
392
|
+
boot: this.bootId,
|
|
393
|
+
seq: this.seq++,
|
|
394
|
+
tool: entry.name,
|
|
395
|
+
ok,
|
|
396
|
+
ms: Math.round(ms),
|
|
397
|
+
chars: charsBucket(chars),
|
|
398
|
+
src: 'handler',
|
|
399
|
+
};
|
|
400
|
+
if (slug)
|
|
401
|
+
event.err = slug;
|
|
402
|
+
if (hinted !== undefined)
|
|
403
|
+
event.hint = hinted;
|
|
404
|
+
if (fan)
|
|
405
|
+
event.fan = fan;
|
|
406
|
+
// mid: set mid-dispatch by recordEscapeMethod, attached to the escape
|
|
407
|
+
// call's own event line (per-event field, never a synthetic event).
|
|
408
|
+
if (this.sequencing && entry.name === 'google_api_call' && this.pendingMid) {
|
|
409
|
+
event.mid = this.pendingMid;
|
|
410
|
+
this.pendingMid = undefined;
|
|
411
|
+
}
|
|
412
|
+
this.events.push(JSON.stringify(event));
|
|
397
413
|
}
|
|
398
|
-
this.events.push(JSON.stringify(event));
|
|
399
414
|
this.dirty = true;
|
|
400
415
|
}
|
|
401
416
|
/** Escape hatch: only the RESOLVED methodId (double-gated) or a miss. */
|
|
@@ -407,7 +422,8 @@ export class Metrics {
|
|
|
407
422
|
}
|
|
408
423
|
else if (idOrNull.length <= METHOD_ID_MAX && METHOD_ID_RE.test(idOrNull)) {
|
|
409
424
|
addInto(this.deltas.escape.methods, idOrNull);
|
|
410
|
-
this.
|
|
425
|
+
if (this.sequencing)
|
|
426
|
+
this.pendingMid = idOrNull;
|
|
411
427
|
}
|
|
412
428
|
this.dirty = true;
|
|
413
429
|
}
|
|
@@ -475,12 +491,14 @@ export class Metrics {
|
|
|
475
491
|
else {
|
|
476
492
|
addInto(this.deltas.rpc, RPC_CODES.has(kind) ? `rpc_error_${kind}` : 'rpc_error_other');
|
|
477
493
|
}
|
|
478
|
-
this.
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
494
|
+
if (this.eventLog) {
|
|
495
|
+
this.events.push(JSON.stringify({
|
|
496
|
+
ts: new Date(this.now()).toISOString().slice(0, 16) + ':00Z',
|
|
497
|
+
boot: this.bootId, seq: this.seq++,
|
|
498
|
+
tool: tool && TOOL_NAME_RE.test(tool) ? tool : 'other',
|
|
499
|
+
ok: false, ms: 0, chars: 'le64', src: 'rpc',
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
484
502
|
this.dirty = true;
|
|
485
503
|
}
|
|
486
504
|
catch { /* never throws outward */ }
|
package/dist/write-control.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hints } from './hint-provider.js';
|
|
1
2
|
const PROFILES = ['read-only', 'safe-writes', 'full-writes'];
|
|
2
3
|
/** The irreversible set (frozen, cc-write-control): real sends leave the
|
|
3
4
|
* user's (or org's) identity unrecallably; permanent deletes bypass Trash.
|
|
@@ -155,10 +156,17 @@ export function writeDisabledResult(tool, policy, account) {
|
|
|
155
156
|
// Recomputed rather than passed in: every call site has already run
|
|
156
157
|
// isAllowed, and a hint that disagrees with the verdict is worse than none.
|
|
157
158
|
const reason = denyReason(tool, policy) ?? { rule: 'profile', profile: policy.profile };
|
|
159
|
+
const rendered = hints()?.render({
|
|
160
|
+
kind: 'write_denied',
|
|
161
|
+
tool: { name: tool.name, service: tool.service, cud: tool.cud },
|
|
162
|
+
reason,
|
|
163
|
+
policy,
|
|
164
|
+
});
|
|
158
165
|
const envelope = {
|
|
159
166
|
error: 'write_disabled',
|
|
160
|
-
message:
|
|
161
|
-
|
|
167
|
+
message: rendered?.message ??
|
|
168
|
+
`"${tool.name}" (${tool.cud}) is disabled by the current write-control policy (profile: ${policy.profile}${policy.readOnly ? ', GOOGLE_READ_ONLY=true' : ''}).`,
|
|
169
|
+
hint: rendered?.hint ?? denyHint(tool, reason),
|
|
162
170
|
retriable: false,
|
|
163
171
|
...(account ? { account } : {}),
|
|
164
172
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-google-multi",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.3",
|
|
4
4
|
"mcpName": "io.github.bakissation/mcp-google-multi",
|
|
5
5
|
"description": "Local MCP server for Google Workspace (Gmail, Drive, Calendar, Sheets, Docs, Contacts, Tasks, Meet, Search Console, +Forms/Chat/Admin) across multiple accounts — OAuth-only, encrypted token storage, deny-by-default writes.",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,32 @@
|
|
|
9
9
|
"mcp-google-multi": "dist/index.js"
|
|
10
10
|
},
|
|
11
11
|
"main": "dist/index.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./identity": "./dist/identity.js",
|
|
15
|
+
"./compose": "./dist/compose.js",
|
|
16
|
+
"./registry": "./dist/registry.js",
|
|
17
|
+
"./oauth-as": "./dist/oauth-as.js",
|
|
18
|
+
"./accounts": "./dist/accounts.js",
|
|
19
|
+
"./token-store": "./dist/token-store.js",
|
|
20
|
+
"./http-transport": "./dist/http-transport.js",
|
|
21
|
+
"./http-config": "./dist/http-config.js",
|
|
22
|
+
"./client": "./dist/client.js",
|
|
23
|
+
"./config-file": "./dist/config-file.js",
|
|
24
|
+
"./fs-atomic": "./dist/fs-atomic.js",
|
|
25
|
+
"./master-key": "./dist/master-key.js",
|
|
26
|
+
"./mcp-token": "./dist/mcp-token.js",
|
|
27
|
+
"./boot-gates": "./dist/boot-gates.js",
|
|
28
|
+
"./tenant-purge": "./dist/tenant-purge.js",
|
|
29
|
+
"./doctor": "./dist/doctor.js",
|
|
30
|
+
"./write-control": "./dist/write-control.js",
|
|
31
|
+
"./scope-catalog": "./dist/scope-catalog.js",
|
|
32
|
+
"./auth": "./dist/auth.js",
|
|
33
|
+
"./usage-metrics": "./dist/usage-metrics.js",
|
|
34
|
+
"./metrics-tap": "./dist/metrics-tap.js",
|
|
35
|
+
"./hint-provider": "./dist/hint-provider.js",
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
12
38
|
"files": [
|
|
13
39
|
"dist"
|
|
14
40
|
],
|