mcp-google-multi 4.4.0 → 4.5.0-alpha.2
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 +1 -0
- package/dist/accounts.js +4 -1
- package/dist/auth.js +7 -6
- package/dist/client.js +8 -19
- package/dist/index.js +20 -13
- package/dist/migrate-tokens.d.ts +1 -0
- package/dist/migrate-tokens.js +22 -0
- package/dist/registry.d.ts +13 -0
- package/dist/registry.js +29 -0
- package/dist/token-store.d.ts +7 -0
- package/dist/token-store.js +66 -0
- package/dist/tools/_coerce.d.ts +4 -0
- package/dist/tools/_coerce.js +36 -0
- package/dist/tools/_errors.d.ts +8 -1
- package/dist/tools/_errors.js +58 -31
- package/dist/tools/admin.d.ts +3 -3
- package/dist/tools/admin.js +5 -4
- package/dist/tools/calendar.d.ts +2 -2
- package/dist/tools/calendar.js +5 -4
- package/dist/tools/chat.d.ts +2 -2
- package/dist/tools/chat.js +2 -1
- package/dist/tools/contacts.d.ts +2 -2
- package/dist/tools/docs.d.ts +2 -2
- package/dist/tools/docs.js +15 -14
- package/dist/tools/drive.d.ts +2 -2
- package/dist/tools/drive.js +14 -13
- package/dist/tools/forms.d.ts +2 -2
- package/dist/tools/gmail.d.ts +2 -2
- package/dist/tools/gmail.js +11 -10
- package/dist/tools/meet.d.ts +2 -2
- package/dist/tools/searchconsole.d.ts +2 -2
- package/dist/tools/searchconsole.js +4 -3
- package/dist/tools/sheets.d.ts +2 -2
- package/dist/tools/sheets.js +38 -37
- package/dist/tools/tasks.d.ts +2 -2
- package/dist/tools/tasks.js +5 -4
- package/package.json +2 -1
package/dist/accounts.d.ts
CHANGED
package/dist/accounts.js
CHANGED
|
@@ -3,7 +3,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
5
|
dotenv.config({ path: path.resolve(__dirname, '..', '.env') });
|
|
6
|
-
const tokenDir =
|
|
6
|
+
const tokenDir = process.env.TOKEN_STORE_PATH
|
|
7
|
+
? path.resolve(process.env.TOKEN_STORE_PATH)
|
|
8
|
+
: path.resolve(__dirname, '..', 'tokens');
|
|
7
9
|
/**
|
|
8
10
|
* Parse accounts from the GOOGLE_ACCOUNTS env var.
|
|
9
11
|
* Format: "alias1:email1,alias2:email2,..."
|
|
@@ -39,6 +41,7 @@ function parseAccounts() {
|
|
|
39
41
|
configs[alias] = {
|
|
40
42
|
email,
|
|
41
43
|
tokenPath: path.join(tokenDir, alias, 'token.json'),
|
|
44
|
+
encPath: path.join(tokenDir, `${alias}.enc`),
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
if (aliases.length === 0) {
|
package/dist/auth.js
CHANGED
|
@@ -2,11 +2,10 @@ import { google } from 'googleapis';
|
|
|
2
2
|
import http from 'node:http';
|
|
3
3
|
import { URL } from 'node:url';
|
|
4
4
|
import { randomBytes } from 'node:crypto';
|
|
5
|
-
import fs from 'node:fs/promises';
|
|
6
|
-
import path from 'node:path';
|
|
7
5
|
import open from 'open';
|
|
8
6
|
import destroyer from 'server-destroy';
|
|
9
7
|
import { ACCOUNTS, ACCOUNT_CONFIG } from './accounts.js';
|
|
8
|
+
import { writeToken } from './token-store.js';
|
|
10
9
|
// ─── Scope tiers ────────────────────────────────────────────────────────
|
|
11
10
|
//
|
|
12
11
|
// BASE: always granted. Existing v3 surface + Tasks + Meet (added in v4.0.0).
|
|
@@ -98,6 +97,10 @@ export async function runAuthFlow(args) {
|
|
|
98
97
|
}
|
|
99
98
|
const config = ACCOUNT_CONFIG[alias];
|
|
100
99
|
const scopes = resolveScopesForAccount(alias);
|
|
100
|
+
if (!process.env.MASTER_KEY) {
|
|
101
|
+
console.error('MASTER_KEY is not set. Generate one (openssl rand -base64 32) and add it to .env before authenticating.');
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
101
104
|
const oauth2Client = new google.auth.OAuth2(process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, 'http://localhost:4242/oauth2callback');
|
|
102
105
|
// CSRF protection for the OAuth callback (RFC 6749 §10.12).
|
|
103
106
|
const expectedState = randomBytes(32).toString('hex');
|
|
@@ -145,13 +148,11 @@ export async function runAuthFlow(args) {
|
|
|
145
148
|
return;
|
|
146
149
|
}
|
|
147
150
|
const { tokens } = await oauth2Client.getToken(code);
|
|
148
|
-
|
|
149
|
-
// 0o600: tokens grant full account access; keep them user-only.
|
|
150
|
-
await fs.writeFile(config.tokenPath, JSON.stringify(tokens, null, 2), { mode: 0o600 });
|
|
151
|
+
writeToken(alias, tokens);
|
|
151
152
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
152
153
|
res.end('<h2>Authentication successful!</h2><p>You can close this tab.</p>');
|
|
153
154
|
server.destroy();
|
|
154
|
-
console.log(`Token saved
|
|
155
|
+
console.log(`Token saved (encrypted) for ${alias}.`);
|
|
155
156
|
resolve();
|
|
156
157
|
}
|
|
157
158
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { google } from 'googleapis';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
2
|
import { ACCOUNT_CONFIG } from './accounts.js';
|
|
3
|
+
import { readToken, writeToken } from './token-store.js';
|
|
4
4
|
export async function getClient(account) {
|
|
5
5
|
const config = ACCOUNT_CONFIG[account];
|
|
6
6
|
if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_CLIENT_SECRET) {
|
|
@@ -8,26 +8,15 @@ export async function getClient(account) {
|
|
|
8
8
|
'Check that .env exists in the project root or pass them as env vars.');
|
|
9
9
|
}
|
|
10
10
|
const oauth2Client = new google.auth.OAuth2(process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, 'http://localhost:4242/oauth2callback');
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
throw new Error(`No token file found for account "${account}" at ${config.tokenPath}. ` +
|
|
18
|
-
`Run: node dist/index.js auth --account ${account}`);
|
|
11
|
+
const tokenData = readToken(account);
|
|
12
|
+
if (!tokenData) {
|
|
13
|
+
throw new Error(`No token found for account "${account}" (${config.email}). ` +
|
|
14
|
+
`Run: npx mcp-google-multi auth --account ${account}`);
|
|
19
15
|
}
|
|
20
16
|
oauth2Client.setCredentials(tokenData);
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const existing = JSON.parse(await fs.readFile(config.tokenPath, 'utf-8'));
|
|
25
|
-
const merged = { ...existing, ...tokens };
|
|
26
|
-
await fs.writeFile(config.tokenPath, JSON.stringify(merged, null, 2), { mode: 0o600 });
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
await fs.writeFile(config.tokenPath, JSON.stringify(tokens, null, 2), { mode: 0o600 });
|
|
30
|
-
}
|
|
17
|
+
oauth2Client.on('tokens', (tokens) => {
|
|
18
|
+
const existing = readToken(account) ?? {};
|
|
19
|
+
writeToken(account, { ...existing, ...tokens });
|
|
31
20
|
});
|
|
32
21
|
return oauth2Client;
|
|
33
22
|
}
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { registerFormsTools } from './tools/forms.js';
|
|
|
18
18
|
import { registerChatTools } from './tools/chat.js';
|
|
19
19
|
import { registerAdminTools, registerAlertCenterTools } from './tools/admin.js';
|
|
20
20
|
import { getOptionalBundles, getAdminAccounts } from './auth.js';
|
|
21
|
+
import { ToolRegistry } from './registry.js';
|
|
21
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
23
|
const pkg = JSON.parse(readFileSync(path.resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
23
24
|
async function main() {
|
|
@@ -26,29 +27,35 @@ async function main() {
|
|
|
26
27
|
await runAuthFlow(process.argv);
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
30
|
+
if (process.argv.includes('migrate-tokens')) {
|
|
31
|
+
const { runMigrateTokens } = await import('./migrate-tokens.js');
|
|
32
|
+
runMigrateTokens();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
29
35
|
// MCP server mode — no console.log (stdio is the MCP channel)
|
|
30
36
|
const server = new McpServer({
|
|
31
37
|
name: 'mcp-google-multi',
|
|
32
38
|
version: pkg.version,
|
|
33
39
|
});
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
const registry = new ToolRegistry(server);
|
|
41
|
+
registerGmailTools(registry);
|
|
42
|
+
registerDriveTools(registry);
|
|
43
|
+
registerCalendarTools(registry);
|
|
44
|
+
registerSheetsTools(registry);
|
|
45
|
+
registerDocsTools(registry);
|
|
46
|
+
registerContactsTools(registry);
|
|
47
|
+
registerSearchConsoleTools(registry);
|
|
48
|
+
registerTasksTools(registry);
|
|
49
|
+
registerMeetTools(registry);
|
|
43
50
|
const optional = new Set(getOptionalBundles());
|
|
44
51
|
if (optional.has('forms'))
|
|
45
|
-
registerFormsTools(
|
|
52
|
+
registerFormsTools(registry);
|
|
46
53
|
if (optional.has('chat'))
|
|
47
|
-
registerChatTools(
|
|
54
|
+
registerChatTools(registry);
|
|
48
55
|
if (optional.has('alertcenter'))
|
|
49
|
-
registerAlertCenterTools(
|
|
56
|
+
registerAlertCenterTools(registry);
|
|
50
57
|
if (getAdminAccounts().length > 0)
|
|
51
|
-
registerAdminTools(
|
|
58
|
+
registerAdminTools(registry);
|
|
52
59
|
const transport = new StdioServerTransport();
|
|
53
60
|
await server.connect(transport);
|
|
54
61
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runMigrateTokens(): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { ACCOUNTS, ACCOUNT_CONFIG } from './accounts.js';
|
|
3
|
+
import { writeToken, hasToken } from './token-store.js';
|
|
4
|
+
export function runMigrateTokens() {
|
|
5
|
+
let migrated = 0;
|
|
6
|
+
let skipped = 0;
|
|
7
|
+
for (const alias of ACCOUNTS) {
|
|
8
|
+
const plain = ACCOUNT_CONFIG[alias].tokenPath;
|
|
9
|
+
if (!fs.existsSync(plain))
|
|
10
|
+
continue;
|
|
11
|
+
if (hasToken(alias)) {
|
|
12
|
+
console.log(`• ${alias}: encrypted token already exists, skipping`);
|
|
13
|
+
skipped++;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
writeToken(alias, JSON.parse(fs.readFileSync(plain, 'utf8')));
|
|
17
|
+
console.log(`✓ ${alias}: migrated ${plain} → encrypted store`);
|
|
18
|
+
migrated++;
|
|
19
|
+
}
|
|
20
|
+
console.log(`Done. ${migrated} migrated, ${skipped} skipped. ` +
|
|
21
|
+
'Delete the plaintext tokens/<alias>/token.json once verified.');
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
export type Cud = 'read' | 'create' | 'update' | 'delete';
|
|
3
|
+
export interface ToolEntry {
|
|
4
|
+
name: string;
|
|
5
|
+
service: string;
|
|
6
|
+
cud: Cud;
|
|
7
|
+
}
|
|
8
|
+
export declare function inferCud(name: string): Cud;
|
|
9
|
+
export declare class ToolRegistry {
|
|
10
|
+
readonly tools: ToolEntry[];
|
|
11
|
+
readonly registerTool: McpServer['registerTool'];
|
|
12
|
+
constructor(server: McpServer);
|
|
13
|
+
}
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const CUD_OVERRIDES = {
|
|
2
|
+
drive_untrash: 'update',
|
|
3
|
+
};
|
|
4
|
+
const DELETE_VERB = /(^|_)(delete|remove|trash|clear|empty)(_|$)/;
|
|
5
|
+
const CREATE_VERB = /(^|_)(create|add|insert|send|upload|copy|import|append|submit|duplicate|share|quick)(_|$)/;
|
|
6
|
+
const UPDATE_VERB = /(^|_)(update|patch|modify|set|move|write|format|merge|unmerge|sort|replace|resize|publish|resolve)(_|$)/;
|
|
7
|
+
export function inferCud(name) {
|
|
8
|
+
const override = CUD_OVERRIDES[name];
|
|
9
|
+
if (override)
|
|
10
|
+
return override;
|
|
11
|
+
if (DELETE_VERB.test(name))
|
|
12
|
+
return 'delete';
|
|
13
|
+
if (CREATE_VERB.test(name))
|
|
14
|
+
return 'create';
|
|
15
|
+
if (UPDATE_VERB.test(name))
|
|
16
|
+
return 'update';
|
|
17
|
+
return 'read';
|
|
18
|
+
}
|
|
19
|
+
export class ToolRegistry {
|
|
20
|
+
tools = [];
|
|
21
|
+
registerTool;
|
|
22
|
+
constructor(server) {
|
|
23
|
+
this.registerTool = ((name, ...rest) => {
|
|
24
|
+
const service = name.includes('_') ? name.slice(0, name.indexOf('_')) : name;
|
|
25
|
+
this.tools.push({ name, service, cud: inferCud(name) });
|
|
26
|
+
return server.registerTool(name, ...rest);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { TokenData } from './types.js';
|
|
2
|
+
export declare function deriveKey(masterKey: string): Buffer;
|
|
3
|
+
export declare function encryptToken(data: object, masterKey: string): string;
|
|
4
|
+
export declare function decryptToken(fileContents: string, masterKey: string): TokenData;
|
|
5
|
+
export declare function readToken(alias: string): TokenData | null;
|
|
6
|
+
export declare function writeToken(alias: string, data: object): void;
|
|
7
|
+
export declare function hasToken(alias: string): boolean;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { ACCOUNT_CONFIG } from './accounts.js';
|
|
5
|
+
const ENC_VERSION = 1;
|
|
6
|
+
const ALGO = 'aes-256-gcm';
|
|
7
|
+
export function deriveKey(masterKey) {
|
|
8
|
+
if (!masterKey) {
|
|
9
|
+
throw new Error('MASTER_KEY is required to encrypt/decrypt tokens. Generate one with: openssl rand -base64 32');
|
|
10
|
+
}
|
|
11
|
+
const raw = Buffer.from(masterKey, 'base64');
|
|
12
|
+
if (raw.length === 32)
|
|
13
|
+
return raw;
|
|
14
|
+
return createHash('sha256').update(masterKey, 'utf8').digest();
|
|
15
|
+
}
|
|
16
|
+
export function encryptToken(data, masterKey) {
|
|
17
|
+
const key = deriveKey(masterKey);
|
|
18
|
+
const iv = randomBytes(12);
|
|
19
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
20
|
+
const ciphertext = Buffer.concat([
|
|
21
|
+
cipher.update(Buffer.from(JSON.stringify(data), 'utf8')),
|
|
22
|
+
cipher.final(),
|
|
23
|
+
]);
|
|
24
|
+
const file = {
|
|
25
|
+
v: ENC_VERSION,
|
|
26
|
+
iv: iv.toString('base64'),
|
|
27
|
+
tag: cipher.getAuthTag().toString('base64'),
|
|
28
|
+
data: ciphertext.toString('base64'),
|
|
29
|
+
};
|
|
30
|
+
return JSON.stringify(file);
|
|
31
|
+
}
|
|
32
|
+
export function decryptToken(fileContents, masterKey) {
|
|
33
|
+
const key = deriveKey(masterKey);
|
|
34
|
+
const file = JSON.parse(fileContents);
|
|
35
|
+
if (file.v !== ENC_VERSION) {
|
|
36
|
+
throw new Error(`Unsupported token file version: ${file.v}`);
|
|
37
|
+
}
|
|
38
|
+
const decipher = createDecipheriv(ALGO, key, Buffer.from(file.iv, 'base64'));
|
|
39
|
+
decipher.setAuthTag(Buffer.from(file.tag, 'base64'));
|
|
40
|
+
const plaintext = Buffer.concat([
|
|
41
|
+
decipher.update(Buffer.from(file.data, 'base64')),
|
|
42
|
+
decipher.final(),
|
|
43
|
+
]);
|
|
44
|
+
return JSON.parse(plaintext.toString('utf8'));
|
|
45
|
+
}
|
|
46
|
+
function masterKey() {
|
|
47
|
+
return process.env.MASTER_KEY ?? '';
|
|
48
|
+
}
|
|
49
|
+
export function readToken(alias) {
|
|
50
|
+
let contents;
|
|
51
|
+
try {
|
|
52
|
+
contents = fs.readFileSync(ACCOUNT_CONFIG[alias].encPath, 'utf8');
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return decryptToken(contents, masterKey());
|
|
58
|
+
}
|
|
59
|
+
export function writeToken(alias, data) {
|
|
60
|
+
const p = ACCOUNT_CONFIG[alias].encPath;
|
|
61
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
62
|
+
fs.writeFileSync(p, encryptToken(data, masterKey()), { mode: 0o600 });
|
|
63
|
+
}
|
|
64
|
+
export function hasToken(alias) {
|
|
65
|
+
return fs.existsSync(ACCOUNT_CONFIG[alias].encPath);
|
|
66
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare function coerceArray<T extends z.ZodTypeAny>(element: T): z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodArray<T>>;
|
|
3
|
+
export declare function coerceJson<T extends z.ZodTypeAny>(schema: T): z.ZodPipe<z.ZodTransform<unknown, unknown>, T>;
|
|
4
|
+
export declare const coerceBoolean: z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodBoolean>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
function parseJsonLoose(s) {
|
|
3
|
+
try {
|
|
4
|
+
return JSON.parse(s);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return s;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function coerceArray(element) {
|
|
11
|
+
return z.preprocess((val) => {
|
|
12
|
+
if (typeof val !== 'string')
|
|
13
|
+
return val;
|
|
14
|
+
const t = val.trim();
|
|
15
|
+
if (t === '')
|
|
16
|
+
return [];
|
|
17
|
+
if (t.startsWith('['))
|
|
18
|
+
return parseJsonLoose(t);
|
|
19
|
+
return t.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
|
|
20
|
+
}, z.array(element));
|
|
21
|
+
}
|
|
22
|
+
export function coerceJson(schema) {
|
|
23
|
+
return z.preprocess((val) => (typeof val === 'string' ? parseJsonLoose(val) : val), schema);
|
|
24
|
+
}
|
|
25
|
+
export const coerceBoolean = z.preprocess((val) => {
|
|
26
|
+
if (typeof val === 'boolean')
|
|
27
|
+
return val;
|
|
28
|
+
if (typeof val === 'string') {
|
|
29
|
+
const t = val.trim().toLowerCase();
|
|
30
|
+
if (['true', '1', 'yes', 'y'].includes(t))
|
|
31
|
+
return true;
|
|
32
|
+
if (['false', '0', 'no', 'n'].includes(t))
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return val;
|
|
36
|
+
}, z.boolean());
|
package/dist/tools/_errors.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { Account } from '../accounts.js';
|
|
2
|
-
|
|
2
|
+
export interface ErrorEnvelope {
|
|
3
|
+
error: string;
|
|
4
|
+
message: string;
|
|
5
|
+
hint?: string;
|
|
6
|
+
retriable: boolean;
|
|
7
|
+
account: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function mapGoogleError(error: any, account: Account, forbiddenHint?: string): ErrorEnvelope;
|
|
3
10
|
export declare function handleGoogleApiError(error: any, account: Account, forbiddenHint?: string): {
|
|
4
11
|
content: {
|
|
5
12
|
type: "text";
|
package/dist/tools/_errors.js
CHANGED
|
@@ -1,42 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
function statusOf(error) {
|
|
2
|
+
const c = error?.code ?? error?.status ?? error?.response?.status;
|
|
3
|
+
const n = typeof c === 'string' ? Number(c) : c;
|
|
4
|
+
return Number.isFinite(n) ? n : undefined;
|
|
5
|
+
}
|
|
6
|
+
function reasonOf(error) {
|
|
7
|
+
return (error?.errors?.[0]?.reason ??
|
|
8
|
+
error?.response?.data?.error?.errors?.[0]?.reason ??
|
|
9
|
+
error?.response?.data?.error?.status);
|
|
10
|
+
}
|
|
11
|
+
function messageOf(error) {
|
|
12
|
+
return error?.response?.data?.error?.message ?? error?.message ?? String(error);
|
|
13
|
+
}
|
|
14
|
+
export function mapGoogleError(error, account, forbiddenHint) {
|
|
15
|
+
const status = statusOf(error);
|
|
16
|
+
const reason = reasonOf(error);
|
|
17
|
+
const message = messageOf(error);
|
|
18
|
+
if (status === 401) {
|
|
4
19
|
return {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
20
|
+
error: 'auth_required',
|
|
21
|
+
message: `Authentication failed for account "${account}".`,
|
|
22
|
+
hint: `Run: npx mcp-google-multi auth --account ${account}`,
|
|
23
|
+
retriable: false,
|
|
24
|
+
account,
|
|
10
25
|
};
|
|
11
26
|
}
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
if (status === 403) {
|
|
28
|
+
const scopeIssue = reason === 'insufficientPermissions' ||
|
|
29
|
+
reason === 'ACCESS_TOKEN_SCOPE_INSUFFICIENT' ||
|
|
30
|
+
/insufficient.*scope/i.test(message);
|
|
31
|
+
if (scopeIssue) {
|
|
32
|
+
return {
|
|
33
|
+
error: 'insufficient_scope',
|
|
34
|
+
message,
|
|
35
|
+
hint: forbiddenHint ?? `Re-auth "${account}" with the scope this operation needs.`,
|
|
36
|
+
retriable: false,
|
|
37
|
+
account,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return { error: 'forbidden', message, hint: forbiddenHint, retriable: false, account };
|
|
24
41
|
}
|
|
25
|
-
if (
|
|
26
|
-
|
|
42
|
+
if (status === 400 && /invalid[_ ]scope/i.test(message)) {
|
|
43
|
+
return { error: 'invalid_scope', message, retriable: false, account };
|
|
44
|
+
}
|
|
45
|
+
if (status === 404) {
|
|
46
|
+
return { error: 'not_found', message, retriable: false, account };
|
|
47
|
+
}
|
|
48
|
+
if (status === 429) {
|
|
49
|
+
const retryAfter = error?.response?.headers?.['retry-after'];
|
|
27
50
|
return {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
error: 'rate_limited',
|
|
52
|
+
message,
|
|
53
|
+
hint: retryAfter ? `Retry after ${retryAfter}s.` : 'Back off and retry.',
|
|
54
|
+
retriable: true,
|
|
55
|
+
account,
|
|
33
56
|
};
|
|
34
57
|
}
|
|
58
|
+
if (status !== undefined && status >= 500) {
|
|
59
|
+
return { error: 'upstream_error', message, retriable: true, account };
|
|
60
|
+
}
|
|
61
|
+
return { error: 'upstream_error', message, retriable: false, account };
|
|
62
|
+
}
|
|
63
|
+
export function handleGoogleApiError(error, account, forbiddenHint) {
|
|
64
|
+
const envelope = mapGoogleError(error, account, forbiddenHint);
|
|
35
65
|
return {
|
|
36
|
-
content: [{
|
|
37
|
-
type: 'text',
|
|
38
|
-
text: JSON.stringify({ error: error.message ?? String(error), code: error.code }),
|
|
39
|
-
}],
|
|
66
|
+
content: [{ type: 'text', text: JSON.stringify(envelope) }],
|
|
40
67
|
isError: true,
|
|
41
68
|
};
|
|
42
69
|
}
|
package/dist/tools/admin.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
2
|
/**
|
|
3
3
|
* Admin SDK tools require Workspace super-admin (or delegated admin) privileges on the target account.
|
|
4
4
|
* Personal `@gmail.com` accounts will 403 on every endpoint.
|
|
@@ -6,7 +6,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
6
6
|
* Writes are gated behind GOOGLE_ALLOW_ADMIN_WRITES=true to prevent accidents on small orgs
|
|
7
7
|
* (a stray users.update on a 3-person Workspace is a bad day).
|
|
8
8
|
*/
|
|
9
|
-
export declare function registerAdminTools(server:
|
|
9
|
+
export declare function registerAdminTools(server: ToolRegistry): void;
|
|
10
10
|
/**
|
|
11
11
|
* Alert Center tools. Gated behind the `alertcenter` optional bundle, NOT the
|
|
12
12
|
* admin bundle: the apps.alerts scope cannot be granted via this server's
|
|
@@ -14,4 +14,4 @@ export declare function registerAdminTools(server: McpServer): void;
|
|
|
14
14
|
* domain-wide delegation. Registered separately so a missing/ungrantable
|
|
15
15
|
* apps.alerts scope never blocks the working Admin SDK admin tools.
|
|
16
16
|
*/
|
|
17
|
-
export declare function registerAlertCenterTools(server:
|
|
17
|
+
export declare function registerAlertCenterTools(server: ToolRegistry): void;
|
package/dist/tools/admin.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { coerceBoolean } from './_coerce.js';
|
|
2
3
|
import { google } from 'googleapis';
|
|
3
4
|
import { ACCOUNTS } from '../accounts.js';
|
|
4
5
|
import { getClient } from '../client.js';
|
|
@@ -75,7 +76,7 @@ export function registerAdminTools(server) {
|
|
|
75
76
|
maxResults: z.number().min(1).max(500).optional(),
|
|
76
77
|
pageToken: z.string().optional(),
|
|
77
78
|
orderBy: z.enum(['email', 'familyName', 'givenName']).optional(),
|
|
78
|
-
showDeleted:
|
|
79
|
+
showDeleted: coerceBoolean.optional(),
|
|
79
80
|
projection: z.enum(['basic', 'custom', 'full']).optional(),
|
|
80
81
|
},
|
|
81
82
|
}, async ({ account, customer, domain, query, maxResults, pageToken, orderBy, showDeleted, projection }) => {
|
|
@@ -130,9 +131,9 @@ export function registerAdminTools(server) {
|
|
|
130
131
|
userKey: z.string().describe('User email or ID'),
|
|
131
132
|
givenName: z.string().optional(),
|
|
132
133
|
familyName: z.string().optional(),
|
|
133
|
-
suspended:
|
|
134
|
+
suspended: coerceBoolean.optional(),
|
|
134
135
|
password: z.string().optional(),
|
|
135
|
-
changePasswordAtNextLogin:
|
|
136
|
+
changePasswordAtNextLogin: coerceBoolean.optional(),
|
|
136
137
|
orgUnitPath: z.string().optional(),
|
|
137
138
|
},
|
|
138
139
|
}, async ({ account, userKey, givenName, familyName, suspended, password, changePasswordAtNextLogin, orgUnitPath }) => {
|
|
@@ -217,7 +218,7 @@ export function registerAdminTools(server) {
|
|
|
217
218
|
account: accountEnum.describe('Google account alias (must be a Workspace admin)'),
|
|
218
219
|
groupKey: z.string().describe('Group email or ID'),
|
|
219
220
|
roles: z.string().optional().describe('Comma-separated roles to include (OWNER, MANAGER, MEMBER)'),
|
|
220
|
-
includeDerivedMembership:
|
|
221
|
+
includeDerivedMembership: coerceBoolean.optional(),
|
|
221
222
|
maxResults: z.number().min(1).max(200).optional(),
|
|
222
223
|
pageToken: z.string().optional(),
|
|
223
224
|
},
|
package/dist/tools/calendar.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerCalendarTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerCalendarTools(server: ToolRegistry): void;
|
package/dist/tools/calendar.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { coerceArray, coerceBoolean } from './_coerce.js';
|
|
2
3
|
import { google } from 'googleapis';
|
|
3
4
|
import { ACCOUNTS } from '../accounts.js';
|
|
4
5
|
import { getClient } from '../client.js';
|
|
@@ -111,7 +112,7 @@ export function registerCalendarTools(server) {
|
|
|
111
112
|
.describe('Comma-separated email addresses of attendees'),
|
|
112
113
|
calendarId: z.string().default('primary').optional()
|
|
113
114
|
.describe('Calendar ID (default: primary)'),
|
|
114
|
-
allDay:
|
|
115
|
+
allDay: coerceBoolean.default(false).optional()
|
|
115
116
|
.describe('If true, start/end are dates (YYYY-MM-DD) not datetimes'),
|
|
116
117
|
},
|
|
117
118
|
}, async ({ account, summary, start, end, description, location, attendees, calendarId, allDay }) => {
|
|
@@ -240,7 +241,7 @@ export function registerCalendarTools(server) {
|
|
|
240
241
|
calendarId: z.string().default('primary').optional()
|
|
241
242
|
.describe('Calendar ID (default: primary)'),
|
|
242
243
|
text: z.string().describe('Natural language event, e.g. "Lunch with Farouk Thursday 1pm at Le Boulanger"'),
|
|
243
|
-
sendNotifications:
|
|
244
|
+
sendNotifications: coerceBoolean.optional().describe('Send notifications to attendees (default: false)'),
|
|
244
245
|
},
|
|
245
246
|
}, async ({ account, calendarId, text, sendNotifications }) => {
|
|
246
247
|
try {
|
|
@@ -266,7 +267,7 @@ export function registerCalendarTools(server) {
|
|
|
266
267
|
calendarId: z.string().describe('Source calendar ID'),
|
|
267
268
|
eventId: z.string().describe('Event ID to move'),
|
|
268
269
|
destinationCalendarId: z.string().describe('Destination calendar ID'),
|
|
269
|
-
sendNotifications:
|
|
270
|
+
sendNotifications: coerceBoolean.optional().describe('Send notifications (default: false)'),
|
|
270
271
|
},
|
|
271
272
|
}, async ({ account, calendarId, eventId, destinationCalendarId, sendNotifications }) => {
|
|
272
273
|
try {
|
|
@@ -322,7 +323,7 @@ export function registerCalendarTools(server) {
|
|
|
322
323
|
description: 'Check free/busy times for one or more calendars within a time window. Returns only busy blocks, not event details.',
|
|
323
324
|
inputSchema: {
|
|
324
325
|
account: accountEnum.describe('Google account alias'),
|
|
325
|
-
calendarIds:
|
|
326
|
+
calendarIds: coerceArray(z.string()).describe('Calendar IDs to check, e.g. ["primary", "user@example.com"]'),
|
|
326
327
|
timeMin: z.string().describe('ISO 8601 start of window'),
|
|
327
328
|
timeMax: z.string().describe('ISO 8601 end of window'),
|
|
328
329
|
timeZone: z.string().optional().describe('Timezone (default: UTC)'),
|
package/dist/tools/chat.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerChatTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerChatTools(server: ToolRegistry): void;
|
package/dist/tools/chat.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { coerceJson } from './_coerce.js';
|
|
2
3
|
import { google } from 'googleapis';
|
|
3
4
|
import { ACCOUNTS } from '../accounts.js';
|
|
4
5
|
import { getClient } from '../client.js';
|
|
@@ -55,7 +56,7 @@ export function registerChatTools(server) {
|
|
|
55
56
|
account: accountEnum.describe('Google account alias'),
|
|
56
57
|
parent: z.string().describe('Space resource name, format: spaces/{space}'),
|
|
57
58
|
text: z.string().optional().describe('Plain message text'),
|
|
58
|
-
cardsV2: z.array(z.record(z.string(), z.any())).optional().describe('Optional Card v2 payloads'),
|
|
59
|
+
cardsV2: coerceJson(z.array(z.record(z.string(), z.any()))).optional().describe('Optional Card v2 payloads'),
|
|
59
60
|
threadKey: z.string().optional().describe('Thread key to group messages'),
|
|
60
61
|
messageReplyOption: z.enum(['MESSAGE_REPLY_OPTION_UNSPECIFIED', 'REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD', 'REPLY_MESSAGE_OR_FAIL']).optional(),
|
|
61
62
|
},
|
package/dist/tools/contacts.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerContactsTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerContactsTools(server: ToolRegistry): void;
|
package/dist/tools/docs.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerDocsTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerDocsTools(server: ToolRegistry): void;
|
|
3
3
|
export declare function buildParagraphStyle(input: {
|
|
4
4
|
namedStyleType?: string;
|
|
5
5
|
alignment?: string;
|
package/dist/tools/docs.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { coerceBoolean, coerceJson } from './_coerce.js';
|
|
2
3
|
import { google } from 'googleapis';
|
|
3
4
|
import { ACCOUNTS } from '../accounts.js';
|
|
4
5
|
import { getClient } from '../client.js';
|
|
@@ -56,7 +57,7 @@ export function registerDocsTools(server) {
|
|
|
56
57
|
inputSchema: {
|
|
57
58
|
account: accountEnum.describe('Google account alias'),
|
|
58
59
|
documentId: z.string().describe('Google Docs document ID'),
|
|
59
|
-
includeTabsContent:
|
|
60
|
+
includeTabsContent: coerceBoolean.optional()
|
|
60
61
|
.describe('Include full content for every tab (default: false — first tab only)'),
|
|
61
62
|
suggestionsViewMode: z.enum([
|
|
62
63
|
'DEFAULT_FOR_CURRENT_ACCESS',
|
|
@@ -154,7 +155,7 @@ export function registerDocsTools(server) {
|
|
|
154
155
|
documentId: z.string().describe('Google Docs document ID'),
|
|
155
156
|
findText: z.string().describe('Text to search for'),
|
|
156
157
|
replaceText: z.string().describe('Replacement text'),
|
|
157
|
-
matchCase:
|
|
158
|
+
matchCase: coerceBoolean.default(true).optional()
|
|
158
159
|
.describe('Case-sensitive match (default: true)'),
|
|
159
160
|
},
|
|
160
161
|
}, async ({ account, documentId, findText, replaceText, matchCase }) => {
|
|
@@ -224,9 +225,9 @@ export function registerDocsTools(server) {
|
|
|
224
225
|
documentId: z.string().describe('Google Docs document ID'),
|
|
225
226
|
startIndex: z.number().min(1).describe('Start index (inclusive, 1-based)'),
|
|
226
227
|
endIndex: z.number().min(2).describe('End index (exclusive)'),
|
|
227
|
-
bold:
|
|
228
|
-
italic:
|
|
229
|
-
underline:
|
|
228
|
+
bold: coerceBoolean.optional().describe('Set bold'),
|
|
229
|
+
italic: coerceBoolean.optional().describe('Set italic'),
|
|
230
|
+
underline: coerceBoolean.optional().describe('Set underline'),
|
|
230
231
|
fontSize: z.number().min(1).optional().describe('Font size in points'),
|
|
231
232
|
fontFamily: z.string().optional().describe('Font family name (e.g. "Arial", "Times New Roman")'),
|
|
232
233
|
},
|
|
@@ -443,9 +444,9 @@ export function registerDocsTools(server) {
|
|
|
443
444
|
indentEnd: z.number().optional().describe('End indent in points'),
|
|
444
445
|
indentFirstLine: z.number().optional().describe('First-line indent in points'),
|
|
445
446
|
direction: z.enum(['LEFT_TO_RIGHT', 'RIGHT_TO_LEFT']).optional(),
|
|
446
|
-
keepLinesTogether:
|
|
447
|
-
keepWithNext:
|
|
448
|
-
avoidWidowAndOrphan:
|
|
447
|
+
keepLinesTogether: coerceBoolean.optional(),
|
|
448
|
+
keepWithNext: coerceBoolean.optional(),
|
|
449
|
+
avoidWidowAndOrphan: coerceBoolean.optional(),
|
|
449
450
|
},
|
|
450
451
|
}, async ({ account, documentId, startIndex, endIndex, ...style }) => {
|
|
451
452
|
try {
|
|
@@ -489,9 +490,9 @@ export function registerDocsTools(server) {
|
|
|
489
490
|
marginHeader: z.number().optional(),
|
|
490
491
|
marginFooter: z.number().optional(),
|
|
491
492
|
pageNumberStart: z.number().optional(),
|
|
492
|
-
useFirstPageHeaderFooter:
|
|
493
|
-
useEvenPageHeaderFooter:
|
|
494
|
-
useCustomHeaderFooterMargins:
|
|
493
|
+
useFirstPageHeaderFooter: coerceBoolean.optional(),
|
|
494
|
+
useEvenPageHeaderFooter: coerceBoolean.optional(),
|
|
495
|
+
useCustomHeaderFooterMargins: coerceBoolean.optional(),
|
|
495
496
|
},
|
|
496
497
|
}, async ({ account, documentId, ...style }) => {
|
|
497
498
|
try {
|
|
@@ -807,8 +808,8 @@ export function registerDocsTools(server) {
|
|
|
807
808
|
tableStartIndex: z.number().min(1).describe('Start index of the table in the document'),
|
|
808
809
|
rowIndex: z.number().min(0).describe('Target row (0-based)'),
|
|
809
810
|
columnIndex: z.number().min(0).describe('Target column (0-based)'),
|
|
810
|
-
insertBelow:
|
|
811
|
-
insertRight:
|
|
811
|
+
insertBelow: coerceBoolean.optional().describe('insertRow only: insert below the target row (default: false)'),
|
|
812
|
+
insertRight: coerceBoolean.optional().describe('insertColumn only: insert right of the target column (default: false)'),
|
|
812
813
|
rowSpan: z.number().min(1).optional().describe('mergeCells only: how many rows the merged cell spans (default 1)'),
|
|
813
814
|
columnSpan: z.number().min(1).optional().describe('mergeCells/unmergeCells: column span (default 1)'),
|
|
814
815
|
},
|
|
@@ -981,7 +982,7 @@ export function registerDocsTools(server) {
|
|
|
981
982
|
inputSchema: {
|
|
982
983
|
account: accountEnum.describe('Google account alias'),
|
|
983
984
|
documentId: z.string().describe('Google Docs document ID'),
|
|
984
|
-
requests: z.array(z.record(z.string(), z.any())).describe('Array of Request objects, each with one request-type key'),
|
|
985
|
+
requests: coerceJson(z.array(z.record(z.string(), z.any()))).describe('Array of Request objects, each with one request-type key'),
|
|
985
986
|
writeControl: z.object({
|
|
986
987
|
requiredRevisionId: z.string().optional(),
|
|
987
988
|
targetRevisionId: z.string().optional(),
|
package/dist/tools/drive.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerDriveTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerDriveTools(server: ToolRegistry): void;
|
package/dist/tools/drive.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { coerceArray, coerceBoolean } from './_coerce.js';
|
|
2
3
|
import { google } from 'googleapis';
|
|
3
4
|
import { ACCOUNTS } from '../accounts.js';
|
|
4
5
|
import { getClient } from '../client.js';
|
|
@@ -505,9 +506,9 @@ export function registerDriveTools(server) {
|
|
|
505
506
|
role: z.enum(['reader', 'commenter', 'writer', 'fileOrganizer', 'organizer', 'owner']).describe('Permission role'),
|
|
506
507
|
emailAddress: z.string().optional().describe('Required when type is "user" or "group"'),
|
|
507
508
|
domain: z.string().optional().describe('Required when type is "domain"'),
|
|
508
|
-
sendNotification:
|
|
509
|
+
sendNotification: coerceBoolean.optional().describe('Send notification email (default: true)'),
|
|
509
510
|
emailMessage: z.string().optional().describe('Custom message in notification email'),
|
|
510
|
-
transferOwnership:
|
|
511
|
+
transferOwnership: coerceBoolean.optional().describe('Transfer ownership to the recipient. Requires role="owner". Recipient must accept ownership.'),
|
|
511
512
|
expirationTime: z.string().optional().describe('RFC 3339 timestamp when access expires. Only valid for role="reader" or "commenter".'),
|
|
512
513
|
},
|
|
513
514
|
}, async ({ account, fileId, type, role, emailAddress, domain, sendNotification, emailMessage, transferOwnership, expirationTime }) => {
|
|
@@ -567,9 +568,9 @@ export function registerDriveTools(server) {
|
|
|
567
568
|
.describe('New role'),
|
|
568
569
|
expirationTime: z.string().optional()
|
|
569
570
|
.describe('New RFC 3339 expiration timestamp. Only valid for role "reader" or "commenter".'),
|
|
570
|
-
removeExpiration:
|
|
571
|
+
removeExpiration: coerceBoolean.optional()
|
|
571
572
|
.describe('Clear the existing expirationTime'),
|
|
572
|
-
transferOwnership:
|
|
573
|
+
transferOwnership: coerceBoolean.optional()
|
|
573
574
|
.describe('Promote to owner. Requires role="owner".'),
|
|
574
575
|
},
|
|
575
576
|
}, async ({ account, fileId, permissionId, role, expirationTime, removeExpiration, transferOwnership }) => {
|
|
@@ -658,7 +659,7 @@ export function registerDriveTools(server) {
|
|
|
658
659
|
inputSchema: {
|
|
659
660
|
account: accountEnum.describe('Google account alias'),
|
|
660
661
|
fileId: z.string().describe('Google Drive file ID'),
|
|
661
|
-
includeDeleted:
|
|
662
|
+
includeDeleted: coerceBoolean.optional().describe('Include deleted comments (default: false)'),
|
|
662
663
|
pageSize: z.number().min(1).max(100).optional().describe('Max comments per page (default: 20)'),
|
|
663
664
|
pageToken: z.string().optional().describe('Token from a previous page'),
|
|
664
665
|
startModifiedTime: z.string().optional().describe('Only return comments modified after this RFC 3339 timestamp'),
|
|
@@ -689,7 +690,7 @@ export function registerDriveTools(server) {
|
|
|
689
690
|
account: accountEnum.describe('Google account alias'),
|
|
690
691
|
fileId: z.string().describe('Google Drive file ID'),
|
|
691
692
|
commentId: z.string().describe('Comment ID'),
|
|
692
|
-
includeDeleted:
|
|
693
|
+
includeDeleted: coerceBoolean.optional(),
|
|
693
694
|
},
|
|
694
695
|
}, async ({ account, fileId, commentId, includeDeleted }) => {
|
|
695
696
|
try {
|
|
@@ -793,7 +794,7 @@ export function registerDriveTools(server) {
|
|
|
793
794
|
account: accountEnum.describe('Google account alias'),
|
|
794
795
|
fileId: z.string().describe('Google Drive file ID'),
|
|
795
796
|
commentId: z.string().describe('Parent comment ID'),
|
|
796
|
-
includeDeleted:
|
|
797
|
+
includeDeleted: coerceBoolean.optional(),
|
|
797
798
|
pageSize: z.number().min(1).max(100).optional(),
|
|
798
799
|
pageToken: z.string().optional(),
|
|
799
800
|
},
|
|
@@ -899,10 +900,10 @@ export function registerDriveTools(server) {
|
|
|
899
900
|
account: accountEnum.describe('Google account alias'),
|
|
900
901
|
fileId: z.string().describe('Google Drive file ID'),
|
|
901
902
|
revisionId: z.string().describe('Revision ID'),
|
|
902
|
-
keepForever:
|
|
903
|
-
published:
|
|
904
|
-
publishAuto:
|
|
905
|
-
publishedOutsideDomain:
|
|
903
|
+
keepForever: coerceBoolean.optional().describe('Pin this revision indefinitely'),
|
|
904
|
+
published: coerceBoolean.optional().describe('Toggle published state (Docs only)'),
|
|
905
|
+
publishAuto: coerceBoolean.optional().describe('Auto-publish subsequent revisions'),
|
|
906
|
+
publishedOutsideDomain: coerceBoolean.optional().describe('Allow publish outside domain'),
|
|
906
907
|
},
|
|
907
908
|
}, async ({ account, fileId, revisionId, keepForever, published, publishAuto, publishedOutsideDomain }) => {
|
|
908
909
|
try {
|
|
@@ -984,10 +985,10 @@ export function registerDriveTools(server) {
|
|
|
984
985
|
fileId: z.string().describe('Google Drive file ID'),
|
|
985
986
|
proposalId: z.string().describe('Access proposal ID'),
|
|
986
987
|
action: z.enum(['ACCEPT', 'DENY']).describe('Whether to accept or deny the proposal'),
|
|
987
|
-
role:
|
|
988
|
+
role: coerceArray(z.enum(['reader', 'commenter', 'writer', 'fileOrganizer'])).optional()
|
|
988
989
|
.describe('Required when action is ACCEPT'),
|
|
989
990
|
view: z.string().optional().describe('Optional view, e.g. "published"'),
|
|
990
|
-
sendNotification:
|
|
991
|
+
sendNotification: coerceBoolean.optional()
|
|
991
992
|
.describe('Email the requester about the resolution'),
|
|
992
993
|
},
|
|
993
994
|
}, async ({ account, fileId, proposalId, action, role, view, sendNotification }) => {
|
package/dist/tools/forms.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerFormsTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerFormsTools(server: ToolRegistry): void;
|
package/dist/tools/gmail.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerGmailTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerGmailTools(server: ToolRegistry): void;
|
package/dist/tools/gmail.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { coerceArray, coerceBoolean } from './_coerce.js';
|
|
2
3
|
import { google } from 'googleapis';
|
|
3
4
|
import { ACCOUNTS } from '../accounts.js';
|
|
4
5
|
import { getClient } from '../client.js';
|
|
@@ -319,8 +320,8 @@ export function registerGmailTools(server) {
|
|
|
319
320
|
inputSchema: {
|
|
320
321
|
account: accountEnum.describe('Google account alias'),
|
|
321
322
|
messageId: z.string().describe('Gmail message ID'),
|
|
322
|
-
addLabelIds:
|
|
323
|
-
removeLabelIds:
|
|
323
|
+
addLabelIds: coerceArray(z.string()).optional().describe('Label IDs to add'),
|
|
324
|
+
removeLabelIds: coerceArray(z.string()).optional().describe('Label IDs to remove'),
|
|
324
325
|
},
|
|
325
326
|
}, async ({ account, messageId, addLabelIds, removeLabelIds }) => {
|
|
326
327
|
try {
|
|
@@ -384,9 +385,9 @@ export function registerGmailTools(server) {
|
|
|
384
385
|
description: 'Add/remove labels across up to 1000 Gmail messages at once. Useful for bulk archiving, marking as read, etc.',
|
|
385
386
|
inputSchema: {
|
|
386
387
|
account: accountEnum.describe('Google account alias'),
|
|
387
|
-
messageIds:
|
|
388
|
-
addLabelIds:
|
|
389
|
-
removeLabelIds:
|
|
388
|
+
messageIds: coerceArray(z.string()).describe('Message IDs (up to 1000)'),
|
|
389
|
+
addLabelIds: coerceArray(z.string()).optional().describe('Label IDs to add'),
|
|
390
|
+
removeLabelIds: coerceArray(z.string()).optional().describe('Label IDs to remove'),
|
|
390
391
|
},
|
|
391
392
|
}, async ({ account, messageIds, addLabelIds, removeLabelIds }) => {
|
|
392
393
|
try {
|
|
@@ -412,7 +413,7 @@ export function registerGmailTools(server) {
|
|
|
412
413
|
description: 'Permanently delete multiple Gmail messages. Irreversible.',
|
|
413
414
|
inputSchema: {
|
|
414
415
|
account: accountEnum.describe('Google account alias'),
|
|
415
|
-
messageIds:
|
|
416
|
+
messageIds: coerceArray(z.string()).describe('Message IDs (up to 1000)'),
|
|
416
417
|
},
|
|
417
418
|
}, async ({ account, messageIds }) => {
|
|
418
419
|
try {
|
|
@@ -592,7 +593,7 @@ export function registerGmailTools(server) {
|
|
|
592
593
|
startHistoryId: z.string().describe('History ID from a previous gmail_get_profile or gmail_read response'),
|
|
593
594
|
maxResults: z.number().min(1).max(500).default(100).optional()
|
|
594
595
|
.describe('Max results to return (default: 100)'),
|
|
595
|
-
historyTypes:
|
|
596
|
+
historyTypes: coerceArray(z.enum(['messageAdded', 'messageDeleted', 'labelAdded', 'labelRemoved'])).optional()
|
|
596
597
|
.describe('Filter by history event types'),
|
|
597
598
|
},
|
|
598
599
|
}, async ({ account, startHistoryId, maxResults, historyTypes }) => {
|
|
@@ -635,13 +636,13 @@ export function registerGmailTools(server) {
|
|
|
635
636
|
description: 'Enable or disable Gmail vacation responder with a custom message',
|
|
636
637
|
inputSchema: {
|
|
637
638
|
account: accountEnum.describe('Google account alias'),
|
|
638
|
-
enableAutoReply:
|
|
639
|
+
enableAutoReply: coerceBoolean.describe('Whether to enable the vacation responder'),
|
|
639
640
|
responseSubject: z.string().optional().describe('Subject line for auto-reply'),
|
|
640
641
|
responseBodyPlainText: z.string().optional().describe('Plain text body for auto-reply'),
|
|
641
642
|
startTime: z.string().optional().describe('Start time as Unix timestamp in ms'),
|
|
642
643
|
endTime: z.string().optional().describe('End time as Unix timestamp in ms'),
|
|
643
|
-
restrictToContacts:
|
|
644
|
-
restrictToDomain:
|
|
644
|
+
restrictToContacts: coerceBoolean.optional().describe('Only reply to contacts (default: false)'),
|
|
645
|
+
restrictToDomain: coerceBoolean.optional().describe('Only reply to same domain (default: false)'),
|
|
645
646
|
},
|
|
646
647
|
}, async ({ account, enableAutoReply, responseSubject, responseBodyPlainText, startTime, endTime, restrictToContacts, restrictToDomain }) => {
|
|
647
648
|
try {
|
package/dist/tools/meet.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerMeetTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerMeetTools(server: ToolRegistry): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerSearchConsoleTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerSearchConsoleTools(server: ToolRegistry): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { coerceArray, coerceJson } from './_coerce.js';
|
|
2
3
|
import { google } from 'googleapis';
|
|
3
4
|
import { ACCOUNTS } from '../accounts.js';
|
|
4
5
|
import { getClient } from '../client.js';
|
|
@@ -169,18 +170,18 @@ export function registerSearchConsoleTools(server) {
|
|
|
169
170
|
siteUrl: z.string().describe('Site URL (e.g. "https://example.com/" or "sc-domain:example.com")'),
|
|
170
171
|
startDate: z.string().describe('Start date (YYYY-MM-DD). Data is available starting ~3 days ago.'),
|
|
171
172
|
endDate: z.string().describe('End date (YYYY-MM-DD)'),
|
|
172
|
-
dimensions:
|
|
173
|
+
dimensions: coerceArray(z.enum(['query', 'page', 'country', 'device', 'searchAppearance', 'date'])).optional()
|
|
173
174
|
.describe('Dimensions to group by. Common: ["query", "page"], ["date"], ["query", "date"]'),
|
|
174
175
|
type: z.enum(['web', 'image', 'video', 'news', 'discover', 'googleNews']).optional()
|
|
175
176
|
.describe('Search type filter (default: web)'),
|
|
176
|
-
dimensionFilterGroups: z.array(z.object({
|
|
177
|
+
dimensionFilterGroups: coerceJson(z.array(z.object({
|
|
177
178
|
groupType: z.enum(['and']).optional(),
|
|
178
179
|
filters: z.array(z.object({
|
|
179
180
|
dimension: z.enum(['query', 'page', 'country', 'device', 'searchAppearance']),
|
|
180
181
|
operator: z.enum(['contains', 'equals', 'notContains', 'notEquals', 'includingRegex', 'excludingRegex']),
|
|
181
182
|
expression: z.string(),
|
|
182
183
|
})),
|
|
183
|
-
})).optional()
|
|
184
|
+
}))).optional()
|
|
184
185
|
.describe('Filters to narrow results. Example: filter by page containing "/blog/" or query containing "keyword"'),
|
|
185
186
|
rowLimit: z.number().min(1).max(25000).optional()
|
|
186
187
|
.describe('Max rows to return (default: 1000, max: 25000)'),
|
package/dist/tools/sheets.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerSheetsTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerSheetsTools(server: ToolRegistry): void;
|
|
3
3
|
export declare function buildCellFormat(input: {
|
|
4
4
|
backgroundColor?: {
|
|
5
5
|
red?: number;
|
package/dist/tools/sheets.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { coerceArray, coerceBoolean, coerceJson } from './_coerce.js';
|
|
2
3
|
import { google } from 'googleapis';
|
|
3
4
|
import { ACCOUNTS } from '../accounts.js';
|
|
4
5
|
import { getClient } from '../client.js';
|
|
@@ -10,7 +11,7 @@ export function registerSheetsTools(server) {
|
|
|
10
11
|
inputSchema: {
|
|
11
12
|
account: accountEnum.describe('Google account alias'),
|
|
12
13
|
title: z.string().describe('Spreadsheet title'),
|
|
13
|
-
sheetTitles:
|
|
14
|
+
sheetTitles: coerceArray(z.string()).optional()
|
|
14
15
|
.describe('Optional tab/sheet names (default: one sheet named "Sheet1")'),
|
|
15
16
|
},
|
|
16
17
|
}, async ({ account, title, sheetTitles }) => {
|
|
@@ -108,7 +109,7 @@ export function registerSheetsTools(server) {
|
|
|
108
109
|
account: accountEnum.describe('Google account alias'),
|
|
109
110
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
110
111
|
range: z.string().describe('A1 notation target range, e.g. "Sheet1!A1:C3"'),
|
|
111
|
-
values: z.array(z.array(z.any())).describe('2D array of values (rows x columns)'),
|
|
112
|
+
values: coerceJson(z.array(z.array(z.any()))).describe('2D array of values (rows x columns)'),
|
|
112
113
|
valueInputOption: z.enum(['RAW', 'USER_ENTERED']).default('USER_ENTERED').optional()
|
|
113
114
|
.describe('How to interpret values: RAW (literal) or USER_ENTERED (formulas/dates parsed). Default: USER_ENTERED'),
|
|
114
115
|
},
|
|
@@ -141,7 +142,7 @@ export function registerSheetsTools(server) {
|
|
|
141
142
|
account: accountEnum.describe('Google account alias'),
|
|
142
143
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
143
144
|
range: z.string().describe('A1 notation range to search for the table (e.g. "Sheet1")'),
|
|
144
|
-
values: z.array(z.array(z.any())).describe('2D array of rows to append'),
|
|
145
|
+
values: coerceJson(z.array(z.array(z.any()))).describe('2D array of rows to append'),
|
|
145
146
|
valueInputOption: z.enum(['RAW', 'USER_ENTERED']).default('USER_ENTERED').optional()
|
|
146
147
|
.describe('How to interpret values. Default: USER_ENTERED'),
|
|
147
148
|
},
|
|
@@ -200,7 +201,7 @@ export function registerSheetsTools(server) {
|
|
|
200
201
|
inputSchema: {
|
|
201
202
|
account: accountEnum.describe('Google account alias'),
|
|
202
203
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
203
|
-
ranges:
|
|
204
|
+
ranges: coerceArray(z.string()).describe('Array of A1 notation ranges'),
|
|
204
205
|
valueRenderOption: z.enum(['FORMATTED_VALUE', 'UNFORMATTED_VALUE', 'FORMULA'])
|
|
205
206
|
.default('FORMATTED_VALUE').optional()
|
|
206
207
|
.describe('How to render values. Default: FORMATTED_VALUE'),
|
|
@@ -231,10 +232,10 @@ export function registerSheetsTools(server) {
|
|
|
231
232
|
inputSchema: {
|
|
232
233
|
account: accountEnum.describe('Google account alias'),
|
|
233
234
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
234
|
-
data: z.array(z.object({
|
|
235
|
+
data: coerceJson(z.array(z.object({
|
|
235
236
|
range: z.string().describe('A1 notation range'),
|
|
236
|
-
values: z.array(z.array(z.any())).describe('2D array of values'),
|
|
237
|
-
})).describe('Array of { range, values } objects to write'),
|
|
237
|
+
values: coerceJson(z.array(z.array(z.any()))).describe('2D array of values'),
|
|
238
|
+
}))).describe('Array of { range, values } objects to write'),
|
|
238
239
|
valueInputOption: z.enum(['RAW', 'USER_ENTERED']).default('USER_ENTERED').optional()
|
|
239
240
|
.describe('How to interpret values. Default: USER_ENTERED'),
|
|
240
241
|
},
|
|
@@ -373,7 +374,7 @@ export function registerSheetsTools(server) {
|
|
|
373
374
|
sheetId: z.number().describe('Sheet ID'),
|
|
374
375
|
title: z.string().optional().describe('New tab title'),
|
|
375
376
|
index: z.number().optional().describe('New position among tabs'),
|
|
376
|
-
hidden:
|
|
377
|
+
hidden: coerceBoolean.optional().describe('Hide the tab from the UI'),
|
|
377
378
|
tabColor: rgbColorSchema.optional().describe('Tab color (RGB 0..1)'),
|
|
378
379
|
frozenRowCount: z.number().min(0).optional().describe('Number of frozen header rows'),
|
|
379
380
|
frozenColumnCount: z.number().min(0).optional().describe('Number of frozen header columns'),
|
|
@@ -460,10 +461,10 @@ export function registerSheetsTools(server) {
|
|
|
460
461
|
backgroundColor: rgbColorSchema.optional().describe('Cell background (RGB 0..1)'),
|
|
461
462
|
textFormat: z.object({
|
|
462
463
|
foregroundColor: rgbColorSchema.optional(),
|
|
463
|
-
bold:
|
|
464
|
-
italic:
|
|
465
|
-
underline:
|
|
466
|
-
strikethrough:
|
|
464
|
+
bold: coerceBoolean.optional(),
|
|
465
|
+
italic: coerceBoolean.optional(),
|
|
466
|
+
underline: coerceBoolean.optional(),
|
|
467
|
+
strikethrough: coerceBoolean.optional(),
|
|
467
468
|
fontFamily: z.string().optional(),
|
|
468
469
|
fontSize: z.number().min(1).optional(),
|
|
469
470
|
}).optional(),
|
|
@@ -609,19 +610,19 @@ export function registerSheetsTools(server) {
|
|
|
609
610
|
inputSchema: {
|
|
610
611
|
account: accountEnum.describe('Google account alias'),
|
|
611
612
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
612
|
-
ranges: z.array(gridRangeSchema).describe('Ranges the rule applies to'),
|
|
613
|
+
ranges: coerceJson(z.array(gridRangeSchema)).describe('Ranges the rule applies to'),
|
|
613
614
|
index: z.number().min(0).optional().describe('Where in the rule order to insert (0 = highest priority)'),
|
|
614
615
|
booleanRule: z.object({
|
|
615
616
|
conditionType: z.string().describe(BOOLEAN_CONDITION_TYPE_DESC),
|
|
616
|
-
conditionValues:
|
|
617
|
+
conditionValues: coerceArray(z.string()).optional().describe('Values for the condition (e.g. ["100"] for NUMBER_GREATER, or ["=A1>10"] for CUSTOM_FORMULA)'),
|
|
617
618
|
format: z.object({
|
|
618
619
|
backgroundColor: rgbColorSchema.optional(),
|
|
619
620
|
textFormat: z.object({
|
|
620
621
|
foregroundColor: rgbColorSchema.optional(),
|
|
621
|
-
bold:
|
|
622
|
-
italic:
|
|
623
|
-
strikethrough:
|
|
624
|
-
underline:
|
|
622
|
+
bold: coerceBoolean.optional(),
|
|
623
|
+
italic: coerceBoolean.optional(),
|
|
624
|
+
strikethrough: coerceBoolean.optional(),
|
|
625
|
+
underline: coerceBoolean.optional(),
|
|
625
626
|
}).optional(),
|
|
626
627
|
}).describe('Format to apply when condition is true. Conditional formatting only supports bold/italic/strikethrough/underline/foregroundColor/backgroundColor.'),
|
|
627
628
|
}).optional(),
|
|
@@ -699,7 +700,7 @@ export function registerSheetsTools(server) {
|
|
|
699
700
|
account: accountEnum.describe('Google account alias'),
|
|
700
701
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
701
702
|
range: gridRangeSchema,
|
|
702
|
-
sortSpecs: z.array(sortSpecSchema).describe('One spec per sort column (in priority order)'),
|
|
703
|
+
sortSpecs: coerceJson(z.array(sortSpecSchema)).describe('One spec per sort column (in priority order)'),
|
|
703
704
|
},
|
|
704
705
|
}, async ({ account, spreadsheetId, range, sortSpecs }) => {
|
|
705
706
|
try {
|
|
@@ -725,13 +726,13 @@ export function registerSheetsTools(server) {
|
|
|
725
726
|
account: accountEnum.describe('Google account alias'),
|
|
726
727
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
727
728
|
range: gridRangeSchema,
|
|
728
|
-
sortSpecs: z.array(sortSpecSchema).optional(),
|
|
729
|
-
filterSpecs: z.array(z.object({
|
|
729
|
+
sortSpecs: coerceJson(z.array(sortSpecSchema)).optional(),
|
|
730
|
+
filterSpecs: coerceJson(z.array(z.object({
|
|
730
731
|
columnIndex: z.number().min(0),
|
|
731
|
-
hiddenValues:
|
|
732
|
+
hiddenValues: coerceArray(z.string()).optional().describe('Values to hide in this column'),
|
|
732
733
|
conditionType: z.string().optional().describe(BOOLEAN_CONDITION_TYPE_DESC),
|
|
733
|
-
conditionValues:
|
|
734
|
-
})).optional(),
|
|
734
|
+
conditionValues: coerceArray(z.string()).optional(),
|
|
735
|
+
}))).optional(),
|
|
735
736
|
},
|
|
736
737
|
}, async ({ account, spreadsheetId, range, sortSpecs, filterSpecs }) => {
|
|
737
738
|
try {
|
|
@@ -801,10 +802,10 @@ export function registerSheetsTools(server) {
|
|
|
801
802
|
scope: z.enum(['allSheets', 'sheet', 'range']).describe('Search scope'),
|
|
802
803
|
sheetId: z.number().optional().describe('Required when scope=sheet'),
|
|
803
804
|
range: gridRangeSchema.optional().describe('Required when scope=range'),
|
|
804
|
-
matchCase:
|
|
805
|
-
matchEntireCell:
|
|
806
|
-
searchByRegex:
|
|
807
|
-
includeFormulas:
|
|
805
|
+
matchCase: coerceBoolean.optional(),
|
|
806
|
+
matchEntireCell: coerceBoolean.optional(),
|
|
807
|
+
searchByRegex: coerceBoolean.optional(),
|
|
808
|
+
includeFormulas: coerceBoolean.optional(),
|
|
808
809
|
},
|
|
809
810
|
}, async ({ account, spreadsheetId, find, replacement, scope, sheetId, range, matchCase, matchEntireCell, searchByRegex, includeFormulas }) => {
|
|
810
811
|
try {
|
|
@@ -892,7 +893,7 @@ export function registerSheetsTools(server) {
|
|
|
892
893
|
dimension: z.enum(['ROWS', 'COLUMNS']),
|
|
893
894
|
startIndex: z.number().min(0).describe('0-based, inclusive'),
|
|
894
895
|
endIndex: z.number().min(1).describe('0-based, exclusive'),
|
|
895
|
-
inheritFromBefore:
|
|
896
|
+
inheritFromBefore: coerceBoolean.optional(),
|
|
896
897
|
},
|
|
897
898
|
}, async ({ account, spreadsheetId, sheetId, dimension, startIndex, endIndex, inheritFromBefore }) => {
|
|
898
899
|
try {
|
|
@@ -957,10 +958,10 @@ export function registerSheetsTools(server) {
|
|
|
957
958
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
958
959
|
range: gridRangeSchema,
|
|
959
960
|
conditionType: z.string().describe(BOOLEAN_CONDITION_TYPE_DESC),
|
|
960
|
-
conditionValues:
|
|
961
|
+
conditionValues: coerceArray(z.string()).optional().describe('Values per condition type (list items for ONE_OF_LIST, range A1 for ONE_OF_RANGE, etc.)'),
|
|
961
962
|
inputMessage: z.string().optional().describe('Tooltip shown when cell is selected'),
|
|
962
|
-
strict:
|
|
963
|
-
showCustomUi:
|
|
963
|
+
strict: coerceBoolean.optional().describe('Reject invalid input (default: false = warning only)'),
|
|
964
|
+
showCustomUi: coerceBoolean.optional().describe('Show dropdown UI for list-type validations'),
|
|
964
965
|
},
|
|
965
966
|
}, async ({ account, spreadsheetId, range, conditionType, conditionValues, inputMessage, strict, showCustomUi }) => {
|
|
966
967
|
try {
|
|
@@ -1048,7 +1049,7 @@ export function registerSheetsTools(server) {
|
|
|
1048
1049
|
inputSchema: {
|
|
1049
1050
|
account: accountEnum.describe('Google account alias'),
|
|
1050
1051
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
1051
|
-
ranges:
|
|
1052
|
+
ranges: coerceArray(z.string()).describe('Array of A1 notation ranges'),
|
|
1052
1053
|
},
|
|
1053
1054
|
}, async ({ account, spreadsheetId, ranges }) => {
|
|
1054
1055
|
try {
|
|
@@ -1073,10 +1074,10 @@ export function registerSheetsTools(server) {
|
|
|
1073
1074
|
inputSchema: {
|
|
1074
1075
|
account: accountEnum.describe('Google account alias'),
|
|
1075
1076
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
1076
|
-
requests: z.array(z.record(z.string(), z.any())).describe('Array of Request objects. Each object has exactly one key (the request type) like {repeatCell: {...}}, {addChart: {...}}, {updateBanding: {...}}, etc.'),
|
|
1077
|
-
includeSpreadsheetInResponse:
|
|
1078
|
-
responseRanges:
|
|
1079
|
-
responseIncludeGridData:
|
|
1077
|
+
requests: coerceJson(z.array(z.record(z.string(), z.any()))).describe('Array of Request objects. Each object has exactly one key (the request type) like {repeatCell: {...}}, {addChart: {...}}, {updateBanding: {...}}, etc.'),
|
|
1078
|
+
includeSpreadsheetInResponse: coerceBoolean.optional(),
|
|
1079
|
+
responseRanges: coerceArray(z.string()).optional(),
|
|
1080
|
+
responseIncludeGridData: coerceBoolean.optional(),
|
|
1080
1081
|
},
|
|
1081
1082
|
}, async ({ account, spreadsheetId, requests, includeSpreadsheetInResponse, responseRanges, responseIncludeGridData }) => {
|
|
1082
1083
|
try {
|
package/dist/tools/tasks.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function registerTasksTools(server:
|
|
1
|
+
import type { ToolRegistry } from '../registry.js';
|
|
2
|
+
export declare function registerTasksTools(server: ToolRegistry): void;
|
package/dist/tools/tasks.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { coerceBoolean } from './_coerce.js';
|
|
2
3
|
import { google } from 'googleapis';
|
|
3
4
|
import { ACCOUNTS } from '../accounts.js';
|
|
4
5
|
import { getClient } from '../client.js';
|
|
@@ -119,10 +120,10 @@ export function registerTasksTools(server) {
|
|
|
119
120
|
tasklistId: z.string().describe('Tasklist ID'),
|
|
120
121
|
maxResults: z.number().min(1).max(100).optional(),
|
|
121
122
|
pageToken: z.string().optional(),
|
|
122
|
-
showCompleted:
|
|
123
|
-
showDeleted:
|
|
124
|
-
showHidden:
|
|
125
|
-
showAssigned:
|
|
123
|
+
showCompleted: coerceBoolean.optional().describe('Include completed tasks (default: true)'),
|
|
124
|
+
showDeleted: coerceBoolean.optional(),
|
|
125
|
+
showHidden: coerceBoolean.optional(),
|
|
126
|
+
showAssigned: coerceBoolean.optional(),
|
|
126
127
|
completedMax: z.string().optional().describe('RFC 3339 upper bound on completion date'),
|
|
127
128
|
completedMin: z.string().optional(),
|
|
128
129
|
dueMax: z.string().optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-google-multi",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0-alpha.2",
|
|
4
4
|
"description": "MCP server for Gmail, Google Drive, Calendar, Sheets, Docs, and Contacts with multi-account support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"prepublishOnly": "npm run build",
|
|
45
45
|
"watch": "tsc --watch",
|
|
46
46
|
"auth": "node dist/index.js auth --account",
|
|
47
|
+
"migrate-tokens": "node dist/index.js migrate-tokens",
|
|
47
48
|
"lint": "eslint .",
|
|
48
49
|
"typecheck": "tsc --noEmit",
|
|
49
50
|
"test": "vitest run",
|