mcp-google-multi 5.3.0 → 5.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ The most complete **local Google Workspace MCP server**: Gmail, Drive, Calendar,
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/mcp-google-multi?label=npm&color=cb3837)](https://www.npmjs.com/package/mcp-google-multi)
6
6
 
7
- - 🧰 **Exhaustive** — 871 tools across 28 services + an escape hatch for anything else → [COVERAGE.md](./COVERAGE.md)
7
+ - 🧰 **Exhaustive** — 872 tools across 28 services + an escape hatch for anything else → [COVERAGE.md](./COVERAGE.md)
8
8
  - 🔑 **Multi-account** — drive any number of Google accounts by alias, or fan one call out across all of them
9
9
  - 🔒 **Private by design** — your own OAuth app, tokens encrypted at rest (AES-256-GCM), writes deny-by-default, no telemetry, no metering — it talks only to Google
10
10
 
package/dist/accounts.js CHANGED
@@ -3,20 +3,15 @@ import { fileURLToPath } from 'node:url';
3
3
  import path from 'node:path';
4
4
  import { homedir } from 'node:os';
5
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
- // quiet: dotenv v17 prints a banner to stdout by default, which corrupts the
7
- // stdio JSON-RPC channel (DOTENV_CONFIG_QUIET can't help it would be read
8
- // from .env after config() already ran).
6
+ // dotenv v17 prints a banner to stdout, corrupting the stdio JSON-RPC channel;
7
+ // DOTENV_CONFIG_QUIET cannot help, it would be read from .env after config() ran.
9
8
  dotenv.config({ quiet: true });
10
9
  dotenv.config({ path: path.resolve(__dirname, '..', '.env'), quiet: true });
11
10
  const defaultTokenDir = path.join(process.env.XDG_CONFIG_HOME || path.join(homedir(), '.config'), 'mcp-google-multi', 'tokens');
12
11
  const tokenDir = process.env.TOKEN_STORE_PATH
13
12
  ? path.resolve(process.env.TOKEN_STORE_PATH)
14
13
  : defaultTokenDir;
15
- /**
16
- * Parse accounts from the GOOGLE_ACCOUNTS env var.
17
- * Format: "alias1:email1,alias2:email2,..."
18
- * Example: "work:me@company.com,personal:me@gmail.com"
19
- */
14
+ /** Format: GOOGLE_ACCOUNTS="alias1:email1,alias2:email2". */
20
15
  function parseAccounts() {
21
16
  const raw = process.env.GOOGLE_ACCOUNTS;
22
17
  if (!raw || raw.trim() === '') {
package/dist/auth.d.ts CHANGED
@@ -5,9 +5,6 @@ export declare const ADMIN_SCOPES: string[];
5
5
  export declare function getOptionalBundles(): string[];
6
6
  /** Account aliases granted ADMIN_SCOPES via GOOGLE_ADMIN_ACCOUNTS. */
7
7
  export declare function getAdminAccounts(): string[];
8
- /**
9
- * Compose the scope list for a single account at consent time.
10
- * Resolves env flags: GOOGLE_OPTIONAL_SCOPES (global) and GOOGLE_ADMIN_ACCOUNTS (per-account allowlist).
11
- */
8
+ /** Scopes are fixed at consent time: changing GOOGLE_OPTIONAL_SCOPES or GOOGLE_ADMIN_ACCOUNTS requires re-running auth. */
12
9
  export declare function resolveScopesForAccount(alias: string): string[];
13
10
  export declare function runAuthFlow(args: string[]): Promise<void>;
package/dist/auth.js CHANGED
@@ -1,19 +1,11 @@
1
- import { google } from 'googleapis';
1
+ import { OAuth2Client } from 'googleapis-common';
2
2
  import http from 'node:http';
3
3
  import { URL } from 'node:url';
4
4
  import { randomBytes } from 'node:crypto';
5
5
  import open from 'open';
6
- import destroyer from 'server-destroy';
7
6
  import { ACCOUNTS, ACCOUNT_CONFIG } from './accounts.js';
8
7
  import { writeToken } from './token-store.js';
9
- // ─── Scope tiers ────────────────────────────────────────────────────────
10
- //
11
- // BASE: always granted. Existing v3 surface + Tasks + Meet (added in v4.0.0).
12
- // OPTIONAL: per-account opt-in via env GOOGLE_OPTIONAL_SCOPES="slides,forms,chat".
13
- // ADMIN: per-account opt-in via env GOOGLE_ADMIN_ACCOUNTS="alias1,alias2".
14
- //
15
- // Personal Gmail accounts will 403 on admin scopes — never grant by default.
16
- // ────────────────────────────────────────────────────────────────────────
8
+ // Personal (non-Workspace) accounts 403 on admin scopes; ADMIN_SCOPES stays per-account opt-in, never granted by default.
17
9
  export const BASE_SCOPES = [
18
10
  'https://www.googleapis.com/auth/gmail.modify',
19
11
  'https://www.googleapis.com/auth/gmail.send',
@@ -39,10 +31,8 @@ export const OPTIONAL_SCOPE_BUNDLES = {
39
31
  'https://www.googleapis.com/auth/chat.messages',
40
32
  'https://www.googleapis.com/auth/chat.messages.create',
41
33
  ],
42
- // Unlike the service bundles these extend the always-on gmail service:
43
- // users.settings.* writes only accept the settings scopes (reads already
44
- // work via gmail.modify). sharing is separate — it governs delegation,
45
- // auto-forwarding and send-as, a different risk profile from e.g. filters.
34
+ // These extend the always-on gmail service: users.settings.* writes require these
35
+ // scopes (reads already work via gmail.modify); sharing is split out as riskier.
46
36
  gmail_settings: [
47
37
  'https://www.googleapis.com/auth/gmail.settings.basic',
48
38
  ],
@@ -104,10 +94,7 @@ export function getOptionalBundles() {
104
94
  export function getAdminAccounts() {
105
95
  return parseCsvEnv('GOOGLE_ADMIN_ACCOUNTS');
106
96
  }
107
- /**
108
- * Compose the scope list for a single account at consent time.
109
- * Resolves env flags: GOOGLE_OPTIONAL_SCOPES (global) and GOOGLE_ADMIN_ACCOUNTS (per-account allowlist).
110
- */
97
+ /** Scopes are fixed at consent time: changing GOOGLE_OPTIONAL_SCOPES or GOOGLE_ADMIN_ACCOUNTS requires re-running auth. */
111
98
  export function resolveScopesForAccount(alias) {
112
99
  const scopes = [...BASE_SCOPES];
113
100
  for (const bundle of getOptionalBundles()) {
@@ -141,7 +128,7 @@ export async function runAuthFlow(args) {
141
128
  console.error('MASTER_KEY is not set. Generate one (openssl rand -base64 32) and add it to .env before authenticating.');
142
129
  process.exit(1);
143
130
  }
144
- const oauth2Client = new google.auth.OAuth2(process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, 'http://localhost:4242/oauth2callback');
131
+ const oauth2Client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, 'http://localhost:4242/oauth2callback');
145
132
  // CSRF protection for the OAuth callback (RFC 6749 §10.12).
146
133
  const expectedState = randomBytes(32).toString('hex');
147
134
  const authorizeUrl = oauth2Client.generateAuthUrl({
@@ -167,7 +154,8 @@ export async function runAuthFlow(args) {
167
154
  if (error) {
168
155
  res.writeHead(400, { 'Content-Type': 'text/plain' });
169
156
  res.end(`Authorization denied: ${error}`);
170
- server.destroy();
157
+ server.close();
158
+ server.closeAllConnections();
171
159
  reject(new Error(`Authorization denied: ${error}`));
172
160
  return;
173
161
  }
@@ -175,7 +163,8 @@ export async function runAuthFlow(args) {
175
163
  if (!code) {
176
164
  res.writeHead(400, { 'Content-Type': 'text/plain' });
177
165
  res.end('No authorization code received.');
178
- server.destroy();
166
+ server.close();
167
+ server.closeAllConnections();
179
168
  reject(new Error('No authorization code received'));
180
169
  return;
181
170
  }
@@ -183,7 +172,8 @@ export async function runAuthFlow(args) {
183
172
  if (returnedState !== expectedState) {
184
173
  res.writeHead(400, { 'Content-Type': 'text/plain' });
185
174
  res.end('State mismatch — possible CSRF attempt. Aborting.');
186
- server.destroy();
175
+ server.close();
176
+ server.closeAllConnections();
187
177
  reject(new Error('OAuth state token mismatch'));
188
178
  return;
189
179
  }
@@ -191,7 +181,8 @@ export async function runAuthFlow(args) {
191
181
  writeToken(alias, tokens);
192
182
  res.writeHead(200, { 'Content-Type': 'text/html' });
193
183
  res.end('<h2>Authentication successful!</h2><p>You can close this tab.</p>');
194
- server.destroy();
184
+ server.close();
185
+ server.closeAllConnections();
195
186
  console.log(`Token saved (encrypted) for ${alias}.`);
196
187
  console.log('Next: authenticate your other aliases, then verify with: mcp-google-multi config check');
197
188
  resolve();
@@ -200,7 +191,8 @@ export async function runAuthFlow(args) {
200
191
  catch (e) {
201
192
  res.writeHead(500, { 'Content-Type': 'text/plain' });
202
193
  res.end('Internal error during authentication.');
203
- server.destroy();
194
+ server.close();
195
+ server.closeAllConnections();
204
196
  reject(e);
205
197
  }
206
198
  })
@@ -210,7 +202,6 @@ export async function runAuthFlow(args) {
210
202
  console.log(`Opening your browser to authorize "${alias}". If nothing opens, visit:\n${authorizeUrl}`);
211
203
  open(authorizeUrl, { wait: false }).then((cp) => cp.unref());
212
204
  });
213
- destroyer(server);
214
205
  server.on('error', (err) => {
215
206
  if (err.code === 'EADDRINUSE') {
216
207
  console.error('Port 4242 is already in use. Close the process using it and retry.');
package/dist/client.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ import { OAuth2Client } from 'googleapis-common';
1
2
  import type { Account } from './accounts.js';
2
- export declare function getClient(account: Account): Promise<import("google-auth-library").OAuth2Client>;
3
+ export declare function getClient(account: Account): Promise<OAuth2Client>;
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- import { google } from 'googleapis';
1
+ import { OAuth2Client } from 'googleapis-common';
2
2
  import { ACCOUNT_CONFIG } from './accounts.js';
3
3
  import { readToken, updateToken } from './token-store.js';
4
4
  export async function getClient(account) {
@@ -7,7 +7,7 @@ export async function getClient(account) {
7
7
  throw new Error('GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET must be set. ' +
8
8
  'Check that .env exists in the project root or pass them as env vars.');
9
9
  }
10
- const oauth2Client = new google.auth.OAuth2(process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, 'http://localhost:4242/oauth2callback');
10
+ const oauth2Client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, 'http://localhost:4242/oauth2callback');
11
11
  const tokenData = readToken(account);
12
12
  if (!tokenData) {
13
13
  throw new Error(`No token found for account "${account}" (${config.email}). ` +
package/dist/executor.js CHANGED
@@ -20,11 +20,7 @@ export function buildQueryString(queryParams) {
20
20
  }
21
21
  return usp.toString();
22
22
  }
23
- // GET/HEAD must never carry a request body: no Google Discovery GET/HEAD method
24
- // declares a request schema, and undici/fetch throw ("Request with GET/HEAD method
25
- // cannot have body") if one is attached. gaxios stringifies any object `data`
26
- // without checking the verb, so a caller-supplied `{}` on a read would otherwise
27
- // crash the request. Write verbs keep prior semantics: null/undefined -> no body.
23
+ // GET/HEAD bodies are stripped undici rejects them and gaxios attaches `data` regardless of verb (docs/internals.md).
28
24
  export function resolveRequestBody(httpMethod, body) {
29
25
  const verb = httpMethod.toUpperCase();
30
26
  if (verb === 'GET' || verb === 'HEAD')
package/dist/services.js CHANGED
@@ -27,10 +27,8 @@ export const SERVICES = [
27
27
  { name: 'chat', register: registerChatTools, enabled: () => new Set(getOptionalBundles()).has('chat') },
28
28
  { name: 'admin', register: registerAdminTools, enabled: () => getAdminAccounts().length > 0 },
29
29
  ];
30
- // Opt-in gates for generated-only services whose scopes are not granted by
31
- // default; shared services (admin, forms, chat) reuse their curated gate in
32
- // buildRegistry. workspaceevents has no dedicated scope (subscriptions use the
33
- // underlying resource scopes), so it registers ungated.
30
+ // Generated-only services with opt-in scopes; admin/forms/chat reuse their curated gate in buildRegistry,
31
+ // and workspaceevents is deliberately absent no dedicated scope (subscriptions use resource scopes).
34
32
  const bundleGate = (name) => ({
35
33
  enabled: () => new Set(getOptionalBundles()).has(name),
36
34
  hint: `add "${name}" to GOOGLE_OPTIONAL_SCOPES`,
@@ -93,9 +93,8 @@ function withTokenLock(alias, fn) {
93
93
  }
94
94
  catch (ownerError) {
95
95
  const code = ownerError.code;
96
- // EPERM: the PID exists but is not signalable (recycled by another
97
- // user) — treat as alive and wait out the timeout rather than
98
- // break a lock we cannot verify.
96
+ // EPERM: PID exists but is not signalable (recycled by another user);
97
+ // treat as alive, never break a lock we cannot verify.
99
98
  if (code === 'ESRCH')
100
99
  ownerDead = true;
101
100
  else if (code !== 'EPERM')
@@ -158,11 +157,7 @@ function writeTokenAtomic(alias, data) {
158
157
  }
159
158
  }
160
159
  }
161
- // Windows uses classic rename semantics (MoveFileExW without POSIX semantics),
162
- // so replacing a token file that another process momentarily holds open — a
163
- // concurrent readToken, antivirus, an indexer — fails with a transient
164
- // EPERM/EACCES/EBUSY. Reads take no lock, so the token lock cannot prevent
165
- // this; a short bounded retry absorbs it. POSIX rename never fails this way.
160
+ // Windows only: renaming over a momentarily-open file throws transient EPERM/EACCES/EBUSY (reads take no lock); see docs/internals.md.
166
161
  function renameWithRetry(from, to) {
167
162
  for (let attempt = 1;; attempt++) {
168
163
  try {
@@ -1,8 +1,2 @@
1
1
  import type { ToolRegistry } from '../registry.js';
2
- /**
3
- * Admin SDK tools require Workspace super-admin (or delegated admin) privileges on the target account.
4
- * Personal `@gmail.com` accounts will 403 on every endpoint.
5
- *
6
- * Writes (e.g. admin_users_update) are gated by write-control like any CUD tool.
7
- */
8
2
  export declare function registerAdminTools(server: ToolRegistry): void;
@@ -1,16 +1,11 @@
1
1
  import { z } from 'zod';
2
2
  import { coerceBoolean } from './_coerce.js';
3
- import { google } from 'googleapis';
3
+ import { admin as adminClient } from '@googleapis/admin';
4
4
  import { ACCOUNTS } from '../accounts.js';
5
5
  import { getClient } from '../client.js';
6
6
  import { handleGoogleApiError } from './_errors.js';
7
7
  const accountEnum = z.enum(ACCOUNTS);
8
- /**
9
- * Admin SDK tools require Workspace super-admin (or delegated admin) privileges on the target account.
10
- * Personal `@gmail.com` accounts will 403 on every endpoint.
11
- *
12
- * Writes (e.g. admin_users_update) are gated by write-control like any CUD tool.
13
- */
8
+ // Admin SDK requires Workspace super-admin (or delegated admin) on the account — personal @gmail.com accounts 403 on every endpoint.
14
9
  export function registerAdminTools(server) {
15
10
  // ─── Reports / audit log ───────────────────────────────────────────────
16
11
  server.registerTool('reports_activities_list', {
@@ -40,7 +35,7 @@ export function registerAdminTools(server) {
40
35
  }, async ({ account, applicationName, userKey, startTime, endTime, eventName, actorIpAddress, filters, orgUnitID, groupIdFilter, customerId, maxResults, pageToken }) => {
41
36
  try {
42
37
  const auth = await getClient(account);
43
- const reports = google.admin({ version: 'reports_v1', auth });
38
+ const reports = adminClient({ version: 'reports_v1', auth });
44
39
  const res = await reports.activities.list({
45
40
  applicationName,
46
41
  userKey: userKey ?? 'all',
@@ -80,7 +75,7 @@ export function registerAdminTools(server) {
80
75
  }, async ({ account, customer, domain, query, maxResults, pageToken, orderBy, showDeleted, projection }) => {
81
76
  try {
82
77
  const auth = await getClient(account);
83
- const directory = google.admin({ version: 'directory_v1', auth });
78
+ const directory = adminClient({ version: 'directory_v1', auth });
84
79
  const res = await directory.users.list({
85
80
  customer: customer ?? 'my_customer',
86
81
  domain,
@@ -109,7 +104,7 @@ export function registerAdminTools(server) {
109
104
  }, async ({ account, userKey, projection }) => {
110
105
  try {
111
106
  const auth = await getClient(account);
112
- const directory = google.admin({ version: 'directory_v1', auth });
107
+ const directory = adminClient({ version: 'directory_v1', auth });
113
108
  const res = await directory.users.get({
114
109
  userKey,
115
110
  projection: projection ?? 'basic',
@@ -137,7 +132,7 @@ export function registerAdminTools(server) {
137
132
  }, async ({ account, userKey, givenName, familyName, suspended, password, changePasswordAtNextLogin, orgUnitPath }) => {
138
133
  try {
139
134
  const auth = await getClient(account);
140
- const directory = google.admin({ version: 'directory_v1', auth });
135
+ const directory = adminClient({ version: 'directory_v1', auth });
141
136
  const requestBody = {};
142
137
  if (givenName !== undefined || familyName !== undefined) {
143
138
  requestBody.name = {};
@@ -181,7 +176,7 @@ export function registerAdminTools(server) {
181
176
  }, async ({ account, customer, domain, userKey, query, maxResults, pageToken }) => {
182
177
  try {
183
178
  const auth = await getClient(account);
184
- const directory = google.admin({ version: 'directory_v1', auth });
179
+ const directory = adminClient({ version: 'directory_v1', auth });
185
180
  const res = await directory.groups.list({
186
181
  customer: customer ?? 'my_customer',
187
182
  domain,
@@ -211,7 +206,7 @@ export function registerAdminTools(server) {
211
206
  }, async ({ account, groupKey, roles, includeDerivedMembership, maxResults, pageToken }) => {
212
207
  try {
213
208
  const auth = await getClient(account);
214
- const directory = google.admin({ version: 'directory_v1', auth });
209
+ const directory = adminClient({ version: 'directory_v1', auth });
215
210
  const res = await directory.members.list({
216
211
  groupKey,
217
212
  roles,
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { coerceArray, coerceBoolean } from './_coerce.js';
3
- import { google } from 'googleapis';
3
+ import { calendar as calendarClient } from '@googleapis/calendar';
4
4
  import { ACCOUNTS } from '../accounts.js';
5
5
  import { getClient } from '../client.js';
6
6
  import { handleGoogleApiError } from './_errors.js';
@@ -15,7 +15,7 @@ export function registerCalendarTools(server) {
15
15
  }, async ({ account }) => {
16
16
  try {
17
17
  const auth = await getClient(account);
18
- const cal = google.calendar({ version: 'v3', auth });
18
+ const cal = calendarClient({ version: 'v3', auth });
19
19
  const res = await cal.calendarList.list();
20
20
  const calendars = (res.data.items ?? []).map((c) => ({
21
21
  id: c.id,
@@ -50,7 +50,7 @@ export function registerCalendarTools(server) {
50
50
  }, async ({ account, calendarId, query, timeMin, timeMax, maxResults }) => {
51
51
  try {
52
52
  const auth = await getClient(account);
53
- const cal = google.calendar({ version: 'v3', auth });
53
+ const cal = calendarClient({ version: 'v3', auth });
54
54
  const params = {
55
55
  calendarId: calendarId ?? 'primary',
56
56
  maxResults: maxResults ?? 25,
@@ -87,7 +87,7 @@ export function registerCalendarTools(server) {
87
87
  }, async ({ account, eventId, calendarId }) => {
88
88
  try {
89
89
  const auth = await getClient(account);
90
- const cal = google.calendar({ version: 'v3', auth });
90
+ const cal = calendarClient({ version: 'v3', auth });
91
91
  const res = await cal.events.get({
92
92
  calendarId: calendarId ?? 'primary',
93
93
  eventId,
@@ -119,7 +119,7 @@ export function registerCalendarTools(server) {
119
119
  }, async ({ account, summary, start, end, description, location, attendees, calendarId, allDay }) => {
120
120
  try {
121
121
  const auth = await getClient(account);
122
- const cal = google.calendar({ version: 'v3', auth });
122
+ const cal = calendarClient({ version: 'v3', auth });
123
123
  const event = { summary };
124
124
  if (allDay) {
125
125
  event.start = { date: start };
@@ -169,7 +169,7 @@ export function registerCalendarTools(server) {
169
169
  }, async ({ account, eventId, summary, start, end, description, location, attendees, calendarId }) => {
170
170
  try {
171
171
  const auth = await getClient(account);
172
- const cal = google.calendar({ version: 'v3', auth });
172
+ const cal = calendarClient({ version: 'v3', auth });
173
173
  // Fetch the event first so a 404 surfaces before we attempt the patch.
174
174
  await cal.events.get({
175
175
  calendarId: calendarId ?? 'primary',
@@ -222,7 +222,7 @@ export function registerCalendarTools(server) {
222
222
  }, async ({ account, eventId, calendarId }) => {
223
223
  try {
224
224
  const auth = await getClient(account);
225
- const cal = google.calendar({ version: 'v3', auth });
225
+ const cal = calendarClient({ version: 'v3', auth });
226
226
  await cal.events.delete({
227
227
  calendarId: calendarId ?? 'primary',
228
228
  eventId,
@@ -247,7 +247,7 @@ export function registerCalendarTools(server) {
247
247
  }, async ({ account, calendarId, text, sendNotifications }) => {
248
248
  try {
249
249
  const auth = await getClient(account);
250
- const cal = google.calendar({ version: 'v3', auth });
250
+ const cal = calendarClient({ version: 'v3', auth });
251
251
  const res = await cal.events.quickAdd({
252
252
  calendarId: calendarId ?? 'primary',
253
253
  text,
@@ -273,7 +273,7 @@ export function registerCalendarTools(server) {
273
273
  }, async ({ account, calendarId, eventId, destinationCalendarId, sendNotifications }) => {
274
274
  try {
275
275
  const auth = await getClient(account);
276
- const cal = google.calendar({ version: 'v3', auth });
276
+ const cal = calendarClient({ version: 'v3', auth });
277
277
  const res = await cal.events.move({
278
278
  calendarId,
279
279
  eventId,
@@ -303,7 +303,7 @@ export function registerCalendarTools(server) {
303
303
  }, async ({ account, calendarId, eventId, timeMin, timeMax, maxResults }) => {
304
304
  try {
305
305
  const auth = await getClient(account);
306
- const cal = google.calendar({ version: 'v3', auth });
306
+ const cal = calendarClient({ version: 'v3', auth });
307
307
  const res = await cal.events.instances({
308
308
  calendarId: calendarId ?? 'primary',
309
309
  eventId,
@@ -332,7 +332,7 @@ export function registerCalendarTools(server) {
332
332
  }, async ({ account, calendarIds, timeMin, timeMax, timeZone }) => {
333
333
  try {
334
334
  const auth = await getClient(account);
335
- const cal = google.calendar({ version: 'v3', auth });
335
+ const cal = calendarClient({ version: 'v3', auth });
336
336
  const res = await cal.freebusy.query({
337
337
  requestBody: {
338
338
  timeMin,
@@ -360,7 +360,7 @@ export function registerCalendarTools(server) {
360
360
  }, async ({ account, summary, description, timeZone }) => {
361
361
  try {
362
362
  const auth = await getClient(account);
363
- const cal = google.calendar({ version: 'v3', auth });
363
+ const cal = calendarClient({ version: 'v3', auth });
364
364
  const res = await cal.calendars.insert({
365
365
  requestBody: {
366
366
  summary,
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { coerceJson } from './_coerce.js';
3
- import { google } from 'googleapis';
3
+ import { chat as chatClient } from '@googleapis/chat';
4
4
  import { ACCOUNTS } from '../accounts.js';
5
5
  import { getClient } from '../client.js';
6
6
  import { handleGoogleApiError } from './_errors.js';
@@ -17,7 +17,7 @@ export function registerChatTools(server) {
17
17
  }, async ({ account, pageSize, pageToken, filter }) => {
18
18
  try {
19
19
  const auth = await getClient(account);
20
- const chat = google.chat({ version: 'v1', auth });
20
+ const chat = chatClient({ version: 'v1', auth });
21
21
  const res = await chat.spaces.list({
22
22
  pageSize: pageSize ?? 100,
23
23
  pageToken,
@@ -40,7 +40,7 @@ export function registerChatTools(server) {
40
40
  }, async ({ account, name }) => {
41
41
  try {
42
42
  const auth = await getClient(account);
43
- const chat = google.chat({ version: 'v1', auth });
43
+ const chat = chatClient({ version: 'v1', auth });
44
44
  const res = await chat.spaces.get({ name });
45
45
  return {
46
46
  content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
@@ -66,7 +66,7 @@ export function registerChatTools(server) {
66
66
  return { content: [{ type: 'text', text: JSON.stringify({ error: 'Either text or cardsV2 must be provided' }) }], isError: true };
67
67
  }
68
68
  const auth = await getClient(account);
69
- const chat = google.chat({ version: 'v1', auth });
69
+ const chat = chatClient({ version: 'v1', auth });
70
70
  const requestBody = {};
71
71
  if (text)
72
72
  requestBody.text = text;
@@ -100,7 +100,7 @@ export function registerChatTools(server) {
100
100
  }, async ({ account, parent, pageSize, pageToken, filter, orderBy }) => {
101
101
  try {
102
102
  const auth = await getClient(account);
103
- const chat = google.chat({ version: 'v1', auth });
103
+ const chat = chatClient({ version: 'v1', auth });
104
104
  const res = await chat.spaces.messages.list({
105
105
  parent,
106
106
  pageSize: pageSize ?? 100,
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { google } from 'googleapis';
2
+ import { people as peopleClient } from '@googleapis/people';
3
3
  import { ACCOUNTS } from '../accounts.js';
4
4
  import { getClient } from '../client.js';
5
5
  import { handleGoogleApiError } from './_errors.js';
@@ -43,7 +43,7 @@ export function registerContactsTools(server) {
43
43
  }, async ({ account, query, pageSize }) => {
44
44
  try {
45
45
  const auth = await getClient(account);
46
- const people = google.people({ version: 'v1', auth });
46
+ const people = peopleClient({ version: 'v1', auth });
47
47
  // Warmup request required by the People API
48
48
  await people.people.searchContacts({
49
49
  query: '',
@@ -72,7 +72,7 @@ export function registerContactsTools(server) {
72
72
  }, async ({ account, resourceName }) => {
73
73
  try {
74
74
  const auth = await getClient(account);
75
- const people = google.people({ version: 'v1', auth });
75
+ const people = peopleClient({ version: 'v1', auth });
76
76
  const res = await people.people.get({
77
77
  resourceName,
78
78
  personFields: PERSON_FIELDS,
@@ -103,7 +103,7 @@ export function registerContactsTools(server) {
103
103
  }, async ({ account, pageSize, pageToken, sortOrder }) => {
104
104
  try {
105
105
  const auth = await getClient(account);
106
- const people = google.people({ version: 'v1', auth });
106
+ const people = peopleClient({ version: 'v1', auth });
107
107
  const res = await people.people.connections.list({
108
108
  resourceName: 'people/me',
109
109
  personFields: PERSON_FIELDS,
@@ -142,7 +142,7 @@ export function registerContactsTools(server) {
142
142
  }, async ({ account, givenName, familyName, email, emailType, phone, phoneType, organization, jobTitle }) => {
143
143
  try {
144
144
  const auth = await getClient(account);
145
- const people = google.people({ version: 'v1', auth });
145
+ const people = peopleClient({ version: 'v1', auth });
146
146
  const requestBody = {
147
147
  names: [{ givenName, familyName: familyName ?? '' }],
148
148
  };
@@ -189,7 +189,7 @@ export function registerContactsTools(server) {
189
189
  }, async ({ account, resourceName, givenName, familyName, email, emailType, phone, phoneType, organization, jobTitle }) => {
190
190
  try {
191
191
  const auth = await getClient(account);
192
- const people = google.people({ version: 'v1', auth });
192
+ const people = peopleClient({ version: 'v1', auth });
193
193
  // Fetch current contact to get etag
194
194
  const current = await people.people.get({
195
195
  resourceName,
@@ -251,7 +251,7 @@ export function registerContactsTools(server) {
251
251
  }, async ({ account, resourceName }) => {
252
252
  try {
253
253
  const auth = await getClient(account);
254
- const people = google.people({ version: 'v1', auth });
254
+ const people = peopleClient({ version: 'v1', auth });
255
255
  await people.people.deleteContact({ resourceName });
256
256
  return {
257
257
  content: [{ type: 'text', text: JSON.stringify({
@@ -273,7 +273,7 @@ export function registerContactsTools(server) {
273
273
  }, async ({ account, pageSize }) => {
274
274
  try {
275
275
  const auth = await getClient(account);
276
- const people = google.people({ version: 'v1', auth });
276
+ const people = peopleClient({ version: 'v1', auth });
277
277
  const res = await people.contactGroups.list({
278
278
  pageSize: pageSize ?? 100,
279
279
  groupFields: 'name,groupType,memberCount',
@@ -303,7 +303,7 @@ export function registerContactsTools(server) {
303
303
  }, async ({ account, groupResourceName, maxMembers }) => {
304
304
  try {
305
305
  const auth = await getClient(account);
306
- const people = google.people({ version: 'v1', auth });
306
+ const people = peopleClient({ version: 'v1', auth });
307
307
  const groupRes = await people.contactGroups.get({
308
308
  resourceName: groupResourceName,
309
309
  maxMembers: maxMembers ?? 100,
@@ -344,7 +344,7 @@ export function registerContactsTools(server) {
344
344
  }, async ({ account, name }) => {
345
345
  try {
346
346
  const auth = await getClient(account);
347
- const people = google.people({ version: 'v1', auth });
347
+ const people = peopleClient({ version: 'v1', auth });
348
348
  const res = await people.contactGroups.create({
349
349
  requestBody: {
350
350
  contactGroup: { name },