mcp-google-multi 5.2.0-alpha.1 → 5.2.0-alpha.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/auth.js +33 -0
- package/dist/discovery-client.js +13 -0
- package/dist/executor.d.ts +39 -0
- package/dist/executor.js +75 -0
- package/dist/index.js +42 -3
- package/dist/registry.js +3 -2
- package/dist/tools/generated/_shared.d.ts +21 -0
- package/dist/tools/generated/_shared.js +35 -0
- package/dist/tools/generated/admin.d.ts +2 -0
- package/dist/tools/generated/admin.js +1937 -0
- package/dist/tools/generated/appsmarket.d.ts +2 -0
- package/dist/tools/generated/appsmarket.js +33 -0
- package/dist/tools/generated/calendar.d.ts +2 -0
- package/dist/tools/generated/calendar.js +428 -0
- package/dist/tools/generated/chat.d.ts +2 -0
- package/dist/tools/generated/chat.js +666 -0
- package/dist/tools/generated/classroom.d.ts +2 -0
- package/dist/tools/generated/classroom.js +1610 -0
- package/dist/tools/generated/cloudidentity.d.ts +2 -0
- package/dist/tools/generated/cloudidentity.js +955 -0
- package/dist/tools/generated/cloudsearch.d.ts +2 -0
- package/dist/tools/generated/cloudsearch.js +754 -0
- package/dist/tools/generated/contacts.d.ts +2 -0
- package/dist/tools/generated/contacts.js +215 -0
- package/dist/tools/generated/drive.d.ts +2 -0
- package/dist/tools/generated/drive.js +543 -0
- package/dist/tools/generated/driveactivity.d.ts +2 -0
- package/dist/tools/generated/driveactivity.js +19 -0
- package/dist/tools/generated/drivelabels.d.ts +2 -0
- package/dist/tools/generated/drivelabels.js +389 -0
- package/dist/tools/generated/forms.d.ts +2 -0
- package/dist/tools/generated/forms.js +49 -0
- package/dist/tools/generated/gmail.d.ts +2 -0
- package/dist/tools/generated/gmail.js +835 -0
- package/dist/tools/generated/groupsmigration.d.ts +2 -0
- package/dist/tools/generated/groupsmigration.js +18 -0
- package/dist/tools/generated/groupssettings.d.ts +2 -0
- package/dist/tools/generated/groupssettings.js +47 -0
- package/dist/tools/generated/index.d.ts +5 -0
- package/dist/tools/generated/index.js +54 -0
- package/dist/tools/generated/keep.d.ts +2 -0
- package/dist/tools/generated/keep.js +102 -0
- package/dist/tools/generated/licensing.d.ts +2 -0
- package/dist/tools/generated/licensing.js +116 -0
- package/dist/tools/generated/meet.d.ts +2 -0
- package/dist/tools/generated/meet.js +186 -0
- package/dist/tools/generated/postmaster.d.ts +2 -0
- package/dist/tools/generated/postmaster.js +66 -0
- package/dist/tools/generated/reseller.d.ts +2 -0
- package/dist/tools/generated/reseller.js +249 -0
- package/dist/tools/generated/script.d.ts +2 -0
- package/dist/tools/generated/script.js +249 -0
- package/dist/tools/generated/searchconsole.d.ts +2 -0
- package/dist/tools/generated/searchconsole.js +19 -0
- package/dist/tools/generated/sheets.d.ts +2 -0
- package/dist/tools/generated/sheets.js +105 -0
- package/dist/tools/generated/tasks.d.ts +2 -0
- package/dist/tools/generated/tasks.js +35 -0
- package/dist/tools/generated/vault.d.ts +2 -0
- package/dist/tools/generated/vault.js +479 -0
- package/dist/tools/generated/workspaceevents.d.ts +2 -0
- package/dist/tools/generated/workspaceevents.js +223 -0
- package/dist/tools/google-api.js +9 -71
- package/package.json +3 -2
package/dist/auth.js
CHANGED
|
@@ -39,6 +39,39 @@ export const OPTIONAL_SCOPE_BUNDLES = {
|
|
|
39
39
|
'https://www.googleapis.com/auth/chat.messages',
|
|
40
40
|
'https://www.googleapis.com/auth/chat.messages.create',
|
|
41
41
|
],
|
|
42
|
+
classroom: [
|
|
43
|
+
'https://www.googleapis.com/auth/classroom.courses',
|
|
44
|
+
'https://www.googleapis.com/auth/classroom.coursework.me',
|
|
45
|
+
'https://www.googleapis.com/auth/classroom.coursework.students',
|
|
46
|
+
'https://www.googleapis.com/auth/classroom.courseworkmaterials',
|
|
47
|
+
'https://www.googleapis.com/auth/classroom.rosters',
|
|
48
|
+
'https://www.googleapis.com/auth/classroom.announcements',
|
|
49
|
+
'https://www.googleapis.com/auth/classroom.topics',
|
|
50
|
+
],
|
|
51
|
+
cloudidentity: [
|
|
52
|
+
'https://www.googleapis.com/auth/cloud-identity.groups',
|
|
53
|
+
'https://www.googleapis.com/auth/cloud-identity.devices',
|
|
54
|
+
],
|
|
55
|
+
cloudsearch: ['https://www.googleapis.com/auth/cloud_search'],
|
|
56
|
+
vault: ['https://www.googleapis.com/auth/ediscovery'],
|
|
57
|
+
keep: ['https://www.googleapis.com/auth/keep'],
|
|
58
|
+
driveactivity: ['https://www.googleapis.com/auth/drive.activity.readonly'],
|
|
59
|
+
drivelabels: [
|
|
60
|
+
'https://www.googleapis.com/auth/drive.labels',
|
|
61
|
+
'https://www.googleapis.com/auth/drive.admin.labels',
|
|
62
|
+
],
|
|
63
|
+
script: [
|
|
64
|
+
'https://www.googleapis.com/auth/script.projects',
|
|
65
|
+
'https://www.googleapis.com/auth/script.deployments',
|
|
66
|
+
'https://www.googleapis.com/auth/script.processes',
|
|
67
|
+
'https://www.googleapis.com/auth/script.metrics',
|
|
68
|
+
],
|
|
69
|
+
postmaster: ['https://www.googleapis.com/auth/postmaster.readonly'],
|
|
70
|
+
groupssettings: ['https://www.googleapis.com/auth/apps.groups.settings'],
|
|
71
|
+
groupsmigration: ['https://www.googleapis.com/auth/apps.groups.migration'],
|
|
72
|
+
licensing: ['https://www.googleapis.com/auth/apps.licensing'],
|
|
73
|
+
reseller: ['https://www.googleapis.com/auth/apps.order'],
|
|
74
|
+
appsmarket: ['https://www.googleapis.com/auth/appsmarketplace.license'],
|
|
42
75
|
};
|
|
43
76
|
export const ADMIN_SCOPES = [
|
|
44
77
|
'https://www.googleapis.com/auth/admin.reports.audit.readonly',
|
package/dist/discovery-client.js
CHANGED
|
@@ -18,7 +18,20 @@ export const WORKSPACE_APIS = {
|
|
|
18
18
|
drivelabels: { id: 'drivelabels', version: 'v2' },
|
|
19
19
|
admin_directory: { id: 'admin', version: 'directory_v1' },
|
|
20
20
|
admin_reports: { id: 'admin', version: 'reports_v1' },
|
|
21
|
+
admin_datatransfer: { id: 'admin', version: 'datatransfer_v1' },
|
|
21
22
|
groupssettings: { id: 'groupssettings', version: 'v1' },
|
|
23
|
+
appsmarket: { id: 'appsmarket', version: 'v2' },
|
|
24
|
+
classroom: { id: 'classroom', version: 'v1' },
|
|
25
|
+
cloudidentity: { id: 'cloudidentity', version: 'v1' },
|
|
26
|
+
cloudsearch: { id: 'cloudsearch', version: 'v1' },
|
|
27
|
+
groupsmigration: { id: 'groupsmigration', version: 'v1' },
|
|
28
|
+
keep: { id: 'keep', version: 'v1' },
|
|
29
|
+
licensing: { id: 'licensing', version: 'v1' },
|
|
30
|
+
postmaster: { id: 'gmailpostmastertools', version: 'v1' },
|
|
31
|
+
reseller: { id: 'reseller', version: 'v1' },
|
|
32
|
+
script: { id: 'script', version: 'v1' },
|
|
33
|
+
vault: { id: 'vault', version: 'v1' },
|
|
34
|
+
workspaceevents: { id: 'workspaceevents', version: 'v1' },
|
|
22
35
|
};
|
|
23
36
|
const TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
24
37
|
const STALE_RETRY_MS = 5 * 60 * 1000;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getClient } from './client.js';
|
|
2
|
+
export declare const MAX_RESPONSE_CHARS = 100000;
|
|
3
|
+
export type QueryValue = string | number | boolean;
|
|
4
|
+
export type QueryParams = Record<string, QueryValue | QueryValue[]>;
|
|
5
|
+
export interface ApiMethodRef {
|
|
6
|
+
id: string;
|
|
7
|
+
httpMethod: string;
|
|
8
|
+
path: string;
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
requiredParams: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface ExecuteDeps {
|
|
13
|
+
getClientFn?: typeof getClient;
|
|
14
|
+
}
|
|
15
|
+
export interface ExecuteArgs {
|
|
16
|
+
account: string;
|
|
17
|
+
pathParams?: Record<string, string | number>;
|
|
18
|
+
queryParams?: QueryParams;
|
|
19
|
+
body?: unknown;
|
|
20
|
+
}
|
|
21
|
+
export declare function jsonResult(payload: unknown, isError?: boolean): {
|
|
22
|
+
content: {
|
|
23
|
+
type: "text";
|
|
24
|
+
text: string;
|
|
25
|
+
}[];
|
|
26
|
+
} | {
|
|
27
|
+
isError: true;
|
|
28
|
+
content: {
|
|
29
|
+
type: "text";
|
|
30
|
+
text: string;
|
|
31
|
+
}[];
|
|
32
|
+
};
|
|
33
|
+
export declare function buildQueryString(queryParams: QueryParams | undefined): string;
|
|
34
|
+
export declare function executeApiMethod(method: ApiMethodRef, args: ExecuteArgs, deps?: ExecuteDeps): Promise<{
|
|
35
|
+
content: {
|
|
36
|
+
type: "text";
|
|
37
|
+
text: string;
|
|
38
|
+
}[];
|
|
39
|
+
}>;
|
package/dist/executor.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getClient } from './client.js';
|
|
2
|
+
import { expandPath, isGoogleApiUrl } from './discovery-client.js';
|
|
3
|
+
import { handleGoogleApiError } from './tools/_errors.js';
|
|
4
|
+
export const MAX_RESPONSE_CHARS = 100_000;
|
|
5
|
+
export function jsonResult(payload, isError = false) {
|
|
6
|
+
const base = { content: [{ type: 'text', text: JSON.stringify(payload) }] };
|
|
7
|
+
return isError ? { ...base, isError: true } : base;
|
|
8
|
+
}
|
|
9
|
+
export function buildQueryString(queryParams) {
|
|
10
|
+
const usp = new URLSearchParams();
|
|
11
|
+
const merged = { alt: 'json', ...queryParams };
|
|
12
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
for (const v of value)
|
|
15
|
+
usp.append(key, String(v));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
usp.append(key, String(value));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return usp.toString();
|
|
22
|
+
}
|
|
23
|
+
export async function executeApiMethod(method, args, deps = {}) {
|
|
24
|
+
if (args.queryParams?.alt === 'media') {
|
|
25
|
+
return jsonResult({
|
|
26
|
+
error: 'binary_unsupported',
|
|
27
|
+
message: 'Binary media download (alt=media) returns no usable JSON through this tool.',
|
|
28
|
+
hint: 'Use drive_download / drive_export for file content.',
|
|
29
|
+
retriable: false,
|
|
30
|
+
}, true);
|
|
31
|
+
}
|
|
32
|
+
let url;
|
|
33
|
+
try {
|
|
34
|
+
url = method.baseUrl + expandPath(method.path, args.pathParams ?? {});
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
return jsonResult({
|
|
38
|
+
error: 'invalid_params',
|
|
39
|
+
message: err.message,
|
|
40
|
+
hint: `Required params for ${method.id}: ${method.requiredParams.join(', ') || '(none)'}`,
|
|
41
|
+
retriable: false,
|
|
42
|
+
}, true);
|
|
43
|
+
}
|
|
44
|
+
if (!isGoogleApiUrl(url)) {
|
|
45
|
+
return jsonResult({
|
|
46
|
+
error: 'untrusted_host',
|
|
47
|
+
message: `Refusing to send credentials to a non-googleapis.com host for "${method.id}".`,
|
|
48
|
+
hint: 'The discovery cache may be corrupt; delete DISCOVERY_CACHE_PATH and retry.',
|
|
49
|
+
retriable: false,
|
|
50
|
+
}, true);
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const getClientFn = deps.getClientFn ?? getClient;
|
|
54
|
+
const auth = await getClientFn(args.account);
|
|
55
|
+
const res = await auth.request({
|
|
56
|
+
// query string built by hand: gaxios comma-joins arrays, Google needs repeated keys
|
|
57
|
+
url: `${url}?${buildQueryString(args.queryParams)}`,
|
|
58
|
+
method: method.httpMethod,
|
|
59
|
+
data: args.body ?? undefined,
|
|
60
|
+
});
|
|
61
|
+
const text = JSON.stringify(res.data ?? null);
|
|
62
|
+
if (text.length > MAX_RESPONSE_CHARS) {
|
|
63
|
+
return jsonResult({
|
|
64
|
+
truncated: true,
|
|
65
|
+
totalChars: text.length,
|
|
66
|
+
head: text.slice(0, MAX_RESPONSE_CHARS),
|
|
67
|
+
hint: 'Narrow the request (fields mask, pageSize) to get complete JSON.',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return { content: [{ type: 'text', text }] };
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return handleGoogleApiError(error, args.account);
|
|
74
|
+
}
|
|
75
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import { registerFormsTools } from './tools/forms.js';
|
|
|
19
19
|
import { registerChatTools } from './tools/chat.js';
|
|
20
20
|
import { registerAdminTools } from './tools/admin.js';
|
|
21
21
|
import { getOptionalBundles, getAdminAccounts } from './auth.js';
|
|
22
|
+
import { GENERATED_SERVICES } from './tools/generated/index.js';
|
|
22
23
|
import { ToolRegistry } from './registry.js';
|
|
23
24
|
import { registerDiscoverTools } from './discover.js';
|
|
24
25
|
import { registerEscapeTools } from './tools/google-api.js';
|
|
@@ -42,11 +43,35 @@ const SERVICES = [
|
|
|
42
43
|
{ name: 'chat', register: registerChatTools, enabled: () => new Set(getOptionalBundles()).has('chat') },
|
|
43
44
|
{ name: 'admin', register: registerAdminTools, enabled: () => getAdminAccounts().length > 0 },
|
|
44
45
|
];
|
|
46
|
+
// Opt-in gates for generated-only services whose scopes are not granted by
|
|
47
|
+
// default; shared services (admin, forms, chat) reuse their curated gate below.
|
|
48
|
+
// workspaceevents has no dedicated scope (subscriptions use the underlying
|
|
49
|
+
// resource scopes), so it registers ungated.
|
|
50
|
+
const bundleGate = (name) => ({
|
|
51
|
+
enabled: () => new Set(getOptionalBundles()).has(name),
|
|
52
|
+
hint: `add "${name}" to GOOGLE_OPTIONAL_SCOPES`,
|
|
53
|
+
});
|
|
54
|
+
const GENERATED_GATES = {
|
|
55
|
+
appsmarket: bundleGate('appsmarket'),
|
|
56
|
+
classroom: bundleGate('classroom'),
|
|
57
|
+
cloudidentity: bundleGate('cloudidentity'),
|
|
58
|
+
cloudsearch: bundleGate('cloudsearch'),
|
|
59
|
+
driveactivity: bundleGate('driveactivity'),
|
|
60
|
+
drivelabels: bundleGate('drivelabels'),
|
|
61
|
+
groupsmigration: bundleGate('groupsmigration'),
|
|
62
|
+
groupssettings: bundleGate('groupssettings'),
|
|
63
|
+
keep: bundleGate('keep'),
|
|
64
|
+
licensing: bundleGate('licensing'),
|
|
65
|
+
postmaster: bundleGate('postmaster'),
|
|
66
|
+
reseller: bundleGate('reseller'),
|
|
67
|
+
script: bundleGate('script'),
|
|
68
|
+
vault: bundleGate('vault'),
|
|
69
|
+
};
|
|
45
70
|
function buildRegistry(server, policy) {
|
|
46
71
|
const registry = new ToolRegistry(server, policy);
|
|
47
72
|
const toolsets = getToolsets();
|
|
48
73
|
if (toolsets !== 'all') {
|
|
49
|
-
const known = new Set(SERVICES.map((s) => s.name));
|
|
74
|
+
const known = new Set([...SERVICES.map((s) => s.name), ...GENERATED_SERVICES.map((s) => s.name)]);
|
|
50
75
|
for (const requested of toolsets) {
|
|
51
76
|
if (!known.has(requested)) {
|
|
52
77
|
process.stderr.write(`GOOGLE_TOOLSETS: unknown service "${requested}" ignored\n`);
|
|
@@ -65,10 +90,24 @@ function buildRegistry(server, policy) {
|
|
|
65
90
|
}
|
|
66
91
|
svc.register(registry);
|
|
67
92
|
}
|
|
93
|
+
for (const gen of GENERATED_SERVICES) {
|
|
94
|
+
if (!toolsetEnabled(toolsets, gen.name))
|
|
95
|
+
continue;
|
|
96
|
+
const curated = SERVICES.find((s) => s.name === gen.name);
|
|
97
|
+
const gate = curated?.enabled ?? GENERATED_GATES[gen.name]?.enabled;
|
|
98
|
+
if (gate && !gate()) {
|
|
99
|
+
if (!curated && toolsets !== 'all') {
|
|
100
|
+
process.stderr.write(`GOOGLE_TOOLSETS: "${gen.name}" requested but not enabled — ${GENERATED_GATES[gen.name].hint}\n`);
|
|
101
|
+
}
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
gen.register(registry);
|
|
105
|
+
}
|
|
68
106
|
if (registry.services().length === 0) {
|
|
107
|
+
const known = [...new Set([...SERVICES.map((s) => s.name), ...GENERATED_SERVICES.map((s) => s.name)])].sort();
|
|
69
108
|
throw new Error(`GOOGLE_TOOLSETS="${process.env.GOOGLE_TOOLSETS ?? ''}" selected no enabled services. ` +
|
|
70
|
-
`Known services: ${
|
|
71
|
-
`Note:
|
|
109
|
+
`Known services: ${known.join(', ')}. ` +
|
|
110
|
+
`Note: optional services require their bundle in GOOGLE_OPTIONAL_SCOPES, admin requires GOOGLE_ADMIN_ACCOUNTS.`);
|
|
72
111
|
}
|
|
73
112
|
registerDiscoverTools(registry, policy);
|
|
74
113
|
registerEscapeTools(registry, policy);
|
package/dist/registry.js
CHANGED
|
@@ -44,7 +44,7 @@ export class ToolRegistry {
|
|
|
44
44
|
this.policy = policy;
|
|
45
45
|
this.registerTool = ((name, config, handler) => {
|
|
46
46
|
const service = SERVICE_OVERRIDES[name] ?? (name.includes('_') ? name.slice(0, name.indexOf('_')) : name);
|
|
47
|
-
const cud = inferCud(name);
|
|
47
|
+
const cud = config.cud ?? inferCud(name);
|
|
48
48
|
// destructiveHint=false claims "additive only" (MCP spec) — updates overwrite, so they stay true.
|
|
49
49
|
const annotations = {
|
|
50
50
|
readOnlyHint: cud === 'read',
|
|
@@ -84,7 +84,8 @@ export class ToolRegistry {
|
|
|
84
84
|
const finalHandler = this.compactOutput
|
|
85
85
|
? async (...args) => compactResult(await guarded(...args))
|
|
86
86
|
: guarded;
|
|
87
|
-
|
|
87
|
+
const { cud: _cud, ...sdkConfig } = config;
|
|
88
|
+
return server.registerTool(name, { ...sdkConfig, inputSchema: inputShape, annotations }, finalHandler);
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
91
|
registerMeta = ((name, config, handler) => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ApiMethodRef, type ExecuteDeps } from '../../executor.js';
|
|
3
|
+
import type { Cud, ToolRegistry } from '../../registry.js';
|
|
4
|
+
export interface GeneratedParam {
|
|
5
|
+
field: string;
|
|
6
|
+
api: string;
|
|
7
|
+
location: 'path' | 'query';
|
|
8
|
+
}
|
|
9
|
+
export interface GeneratedToolDef {
|
|
10
|
+
name: string;
|
|
11
|
+
cud: Cud;
|
|
12
|
+
description: string;
|
|
13
|
+
method: ApiMethodRef;
|
|
14
|
+
params: GeneratedParam[];
|
|
15
|
+
hasBody: boolean;
|
|
16
|
+
shape: z.ZodRawShape;
|
|
17
|
+
}
|
|
18
|
+
export declare function accountField(): z.ZodEnum<{
|
|
19
|
+
[x: string]: string;
|
|
20
|
+
}>;
|
|
21
|
+
export declare function registerGeneratedTool(registry: ToolRegistry, def: GeneratedToolDef, deps?: ExecuteDeps): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ACCOUNTS } from '../../accounts.js';
|
|
3
|
+
import { executeApiMethod } from '../../executor.js';
|
|
4
|
+
export function accountField() {
|
|
5
|
+
return z.enum(ACCOUNTS).describe('Google account alias');
|
|
6
|
+
}
|
|
7
|
+
export function registerGeneratedTool(registry, def, deps = {}) {
|
|
8
|
+
// Widened locally: `cud` is a registry extension the SDK config type doesn't
|
|
9
|
+
// carry; the registry reads it before handing the config to the SDK.
|
|
10
|
+
const register = registry.registerTool;
|
|
11
|
+
register(def.name, {
|
|
12
|
+
description: def.description,
|
|
13
|
+
inputSchema: def.shape,
|
|
14
|
+
cud: def.cud,
|
|
15
|
+
annotations: { openWorldHint: true },
|
|
16
|
+
}, async (args) => {
|
|
17
|
+
const pathParams = {};
|
|
18
|
+
const queryParams = {};
|
|
19
|
+
for (const p of def.params) {
|
|
20
|
+
const value = args[p.field];
|
|
21
|
+
if (value === undefined)
|
|
22
|
+
continue;
|
|
23
|
+
if (p.location === 'path')
|
|
24
|
+
pathParams[p.api] = value;
|
|
25
|
+
else
|
|
26
|
+
queryParams[p.api] = value;
|
|
27
|
+
}
|
|
28
|
+
return executeApiMethod(def.method, {
|
|
29
|
+
account: args.account,
|
|
30
|
+
pathParams,
|
|
31
|
+
queryParams,
|
|
32
|
+
body: def.hasBody ? args.body : undefined,
|
|
33
|
+
}, deps);
|
|
34
|
+
});
|
|
35
|
+
}
|