gogcli-mcp-contacts 4.7.0 → 4.8.0

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/index.js CHANGED
@@ -19790,7 +19790,7 @@ function date4(params) {
19790
19790
  return _coercedDate(ZodDate, params);
19791
19791
  }
19792
19792
 
19793
- // ../../node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
19793
+ // ../../node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
19794
19794
  var LATEST_PROTOCOL_VERSION = "2025-11-25";
19795
19795
  var SUPPORTED_PROTOCOL_VERSIONS = [
19796
19796
  LATEST_PROTOCOL_VERSION,
@@ -34537,11 +34537,19 @@ var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
34537
34537
  var McpToolError = class extends Error {
34538
34538
  /** Actionable remediation text, when one applies. */
34539
34539
  hint;
34540
+ /** The upstream HTTP status this failure came from, when known. */
34541
+ status;
34542
+ /** What kind of failure this is, when the thrower knows. */
34543
+ kind;
34540
34544
  constructor(message, opts) {
34541
34545
  super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
34542
34546
  this.name = "McpToolError";
34543
34547
  if (opts?.hint !== void 0)
34544
34548
  this.hint = opts.hint;
34549
+ if (opts?.status !== void 0)
34550
+ this.status = opts.status;
34551
+ if (opts?.kind !== void 0)
34552
+ this.kind = opts.kind;
34545
34553
  Object.setPrototypeOf(this, new.target.prototype);
34546
34554
  }
34547
34555
  };
@@ -34566,7 +34574,7 @@ var API_KEY_RE = new RegExp([
34566
34574
  // webhook signing secret (Stripe-style)
34567
34575
  ].map((p) => `\\b${p}`).join("|"), "g");
34568
34576
  var GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
34569
- var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
34577
+ var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
34570
34578
  var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
34571
34579
  var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
34572
34580
  var HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
@@ -34577,6 +34585,8 @@ var JSON_SECRET_KEYS = [
34577
34585
  "api[_-]?secret",
34578
34586
  "(?:x[_-])?api[_-]?key",
34579
34587
  "private[_-]?key",
34588
+ "code[_-]?verifier",
34589
+ // PKCE; a bare `verifier` key is too generic for JSON
34580
34590
  `x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
34581
34591
  // bounded like HEADER_SECRET_RE
34582
34592
  "(?:proxy-)?authorization",
@@ -34607,6 +34617,11 @@ function redactSecrets(text) {
34607
34617
  return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(GOOGLE_OAUTH_TOKEN_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_COOKIE_DQ_RE, redactJsonCookie('"')).replace(JSON_COOKIE_SQ_RE, redactJsonCookie("'")).replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_NUM_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
34608
34618
  }
34609
34619
  var ERROR_REDACTION_INPUT_MAX = 64 * 1024;
34620
+ function messageOf(err) {
34621
+ if (err instanceof Error)
34622
+ return err.message;
34623
+ return String(err);
34624
+ }
34610
34625
 
34611
34626
  // ../../node_modules/@chrischall/mcp-utils/dist/response/view.js
34612
34627
  function minifiedResult(data) {
@@ -35027,7 +35042,7 @@ function readEnvVar(key, opts = {}) {
35027
35042
  if (typeof raw === "string") {
35028
35043
  const trimmed = raw.trim();
35029
35044
  if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
35030
- return trimmed;
35045
+ return opts.trim === false ? raw : trimmed;
35031
35046
  }
35032
35047
  }
35033
35048
  return opts.default;
@@ -35222,12 +35237,19 @@ function boundArgs(args) {
35222
35237
  }
35223
35238
  function confirmationFromEnv(options) {
35224
35239
  const { tool, account, confirmToken, subject, instruction, args, env = process.env, spent: callerSpent, ...rest } = options;
35240
+ if (args === void 0 || args === null) {
35241
+ throw new TypeError(`confirmationFromEnv: ${tool} passed no args; pass the tool's validated arguments ({} for a tool with none).`);
35242
+ }
35225
35243
  const spent2 = callerSpent ?? spentTokenStoreFromEnv(env);
35226
35244
  const mode = readConfirmMode(env);
35227
35245
  const ttlSeconds = readConfirmTtl(env);
35228
35246
  const elicitationOff = readConfirmElicitation(env) === "off";
35229
- const bound = args === void 0 ? void 0 : boundArgs(args);
35230
- const bindingAdded = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
35247
+ const bound = boundArgs(args);
35248
+ const elicitationBound = account === void 0 ? { args: bound } : { account, args: bound };
35249
+ const bindingAdded = rest.binding ? rest : {
35250
+ ...rest,
35251
+ binding: { key: confirmKeyFromEnv(env), args: elicitationBound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 }
35252
+ };
35231
35253
  const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
35232
35254
  if (mode === "refuse" || ttlSeconds === void 0) {
35233
35255
  const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
@@ -35237,7 +35259,7 @@ function confirmationFromEnv(options) {
35237
35259
  unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
35238
35260
  };
35239
35261
  }
35240
- const boundSubject = bound === void 0 ? subject : async () => {
35262
+ const boundSubject = async () => {
35241
35263
  const read = await subject();
35242
35264
  if ("content" in read)
35243
35265
  return read;
@@ -35264,12 +35286,14 @@ var SERVER_PROTOCOL_VERSIONS = Object.freeze([
35264
35286
  ...SUPPORTED_PROTOCOL_VERSIONS
35265
35287
  ]);
35266
35288
  function hintResultOrRethrow(err) {
35289
+ if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
35290
+ throw err;
35267
35291
  if (err instanceof McpToolError && err.hint) {
35268
35292
  return errorResult(`${err.message}
35269
35293
 
35270
35294
  Hint: ${err.hint}`);
35271
35295
  }
35272
- throw err;
35296
+ return errorResult(messageOf(err));
35273
35297
  }
35274
35298
  function mcpRequestFrom(args) {
35275
35299
  const ctx = args.at(-1);
@@ -35503,7 +35527,7 @@ var PositiveInt = external_exports.number().int().positive();
35503
35527
  var NonNegInt = external_exports.number().int().nonnegative();
35504
35528
  var NonEmptyString = external_exports.string().min(1);
35505
35529
  var IsoDate = external_exports.iso.date();
35506
- var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
35530
+ var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30").overwrite((v) => v.padStart(5, "0"));
35507
35531
  var NumericIdString = external_exports.string().regex(/^\d+$/, "must be a numeric id (digits only)").describe("A numeric id string (digits only), safe to interpolate into a URL path.");
35508
35532
  var SafePathSegment = external_exports.string().min(1).regex(/^[^/?#\s]+$/, 'must not contain "/", "?", "#", or whitespace').refine((v) => !v.includes(".."), 'must not contain ".."').describe('A single URL path segment, safe to interpolate: no "/", "..", "?", "#", or whitespace.');
35509
35533
  var schemaOrigin = external_exports.string().optional().describe("Portal origin (e.g. https://<vendor>.example.co) selecting which active session to use. Optional when only one session is active.");
@@ -36190,7 +36214,7 @@ function registerRunTool(server, options) {
36190
36214
  }
36191
36215
  server.registerTool(`gog_${service}_run`, {
36192
36216
  description,
36193
- annotations: { destructiveHint: true },
36217
+ annotations: { destructiveHint: true, openWorldHint: true },
36194
36218
  inputSchema: external_exports.object(inputSchema)
36195
36219
  }, async (rawArgs) => {
36196
36220
  const { subcommand, args, account } = rawArgs;
@@ -36371,6 +36395,7 @@ async function requireDispatchConfirmation(ctx, options) {
36371
36395
  ...confirmation,
36372
36396
  tool: fallback.tool,
36373
36397
  account: fallback.account ?? readEnvVar("GOG_ACCOUNT") ?? "",
36398
+ args: fallback.args,
36374
36399
  confirmToken: fallback.confirmToken,
36375
36400
  subject: fallback.subject,
36376
36401
  instruction: fallback.instruction ?? CONFIRM_ACTION_INSTRUCTION,
@@ -36379,13 +36404,13 @@ async function requireDispatchConfirmation(ctx, options) {
36379
36404
  }
36380
36405
 
36381
36406
  // ../gogcli-mcp/src/tools/auth.ts
36382
- var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "remove", "alias"];
36407
+ var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "alias"];
36383
36408
  function registerAuthToolsWith(server, defaultServices) {
36384
36409
  const servicesDescribe = `Services to authorize: "all" or comma-separated list (e.g. "sheets,gmail,calendar"). Default: "${defaultServices}". Prefer the narrowest set you need \u2014 requesting a service whose Google API is not enabled on the OAuth client's project makes Google reject the WHOLE request with invalid_scope.`;
36385
36410
  const extraScopesDescribe = "Additional raw OAuth scope URIs to request, comma-separated, on top of the ones `services` implies. Use for scopes no service covers \u2014 e.g. https://www.googleapis.com/auth/bigquery.readonly, required before gog_sheets_datasource_* can read BigQuery-backed Connected Sheets. Leave unset otherwise: an extra scope whose API is not enabled on the OAuth client project makes Google reject the WHOLE authorization with invalid_scope.";
36386
36411
  server.registerTool("gog_auth_list", {
36387
36412
  description: "List the Google accounts stored in gogcli, with their scopes. This reads local configuration only \u2014 it does not contact Google and does NOT tell you whether an account still works: a signed-out account whose refresh token expired or was revoked is listed here exactly like a healthy one, scopes and all. Use gog_auth_health to check whether an account can actually authenticate.",
36388
- annotations: { readOnlyHint: true },
36413
+ annotations: { readOnlyHint: true, openWorldHint: false },
36389
36414
  inputSchema: external_exports.object({})
36390
36415
  }, async () => {
36391
36416
  try {
@@ -36396,7 +36421,7 @@ function registerAuthToolsWith(server, defaultServices) {
36396
36421
  });
36397
36422
  server.registerTool("gog_auth_status", {
36398
36423
  description: "Show gogcli auth CONFIGURATION: keyring backend, credential files, and auth setup. Despite the name this is not a health check \u2014 it reads local setup and does not contact Google, so it says nothing about whether an account can still authenticate. Use gog_auth_health for that.",
36399
- annotations: { readOnlyHint: true },
36424
+ annotations: { readOnlyHint: true, openWorldHint: false },
36400
36425
  inputSchema: external_exports.object({})
36401
36426
  }, async () => {
36402
36427
  try {
@@ -36407,7 +36432,7 @@ function registerAuthToolsWith(server, defaultServices) {
36407
36432
  });
36408
36433
  server.registerTool("gog_auth_health", {
36409
36434
  description: `Check the LIVE health of each stored Google account. Unlike gog_auth_status (which only reports keyring/config setup), this performs a real token refresh against Google, so it detects expired or revoked (invalid_grant) refresh tokens \u2014 the account-wide sign-out that blocks every service. Reports per account: whether the token is currently valid, the mapped cause when it is not, how long ago it was authorized, and a warning as it approaches the 7-day refresh-token limit that applies to OAuth apps whose consent screen is still in "Testing" mode. Run it proactively to re-authorize on your own schedule instead of mid-task. On a hosted deployment this is the ONLY check that measures Google: a connector showing "connected" or "refreshed" has verified the client's connection to the MCP host, and nothing else \u2014 the Google credential lives in gog's keyring on that host and can be dead while the connection looks perfectly healthy.`,
36410
- annotations: { readOnlyHint: true },
36435
+ annotations: { readOnlyHint: true, openWorldHint: true },
36411
36436
  inputSchema: external_exports.object({})
36412
36437
  }, async () => {
36413
36438
  try {
@@ -36418,7 +36443,7 @@ function registerAuthToolsWith(server, defaultServices) {
36418
36443
  });
36419
36444
  server.registerTool("gog_auth_services", {
36420
36445
  description: "List all Google services supported by gogcli and the OAuth scopes each requires.",
36421
- annotations: { readOnlyHint: true },
36446
+ annotations: { readOnlyHint: true, openWorldHint: false },
36422
36447
  inputSchema: external_exports.object({})
36423
36448
  }, async () => {
36424
36449
  try {
@@ -36429,7 +36454,7 @@ function registerAuthToolsWith(server, defaultServices) {
36429
36454
  });
36430
36455
  server.registerTool("gog_auth_add", {
36431
36456
  description: "Authorize a Google account via browser-based OAuth. Opens a browser window where the user must sign in and grant access. Blocks for up to 5 minutes waiting for the user to complete authorization. If the browser does not open automatically, a fallback URL is included in the response. Use gog_auth_list to check which accounts are already configured.",
36432
- annotations: { destructiveHint: true },
36457
+ annotations: { destructiveHint: true, openWorldHint: true },
36433
36458
  inputSchema: external_exports.object({
36434
36459
  email: external_exports.string().describe("Google account email to authorize"),
36435
36460
  services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
@@ -36449,7 +36474,7 @@ function registerAuthToolsWith(server, defaultServices) {
36449
36474
  });
36450
36475
  server.registerTool("gog_auth_add_url", {
36451
36476
  description: "Begin REMOTE/headless Google authorization (step 1 of 2). Returns a sign-in URL to open in any browser \u2014 no local server or terminal on the gogcli host is needed, so this works on a hosted deployment where the interactive gog_auth_add cannot. Hand the URL to the user; after they sign in, the browser is redirected to a localhost URL that fails to load \u2014 that is expected. They copy that full redirected URL (from the address bar) and you pass it to gog_auth_add_complete. The link is valid for 10 minutes. If you pass a custom `services` here, pass the SAME value to gog_auth_add_complete or the second step will not match this one.",
36452
- annotations: { readOnlyHint: true },
36477
+ annotations: { readOnlyHint: true, openWorldHint: false },
36453
36478
  inputSchema: external_exports.object({
36454
36479
  email: external_exports.string().describe("Google account email to authorize"),
36455
36480
  services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
@@ -36466,7 +36491,7 @@ function registerAuthToolsWith(server, defaultServices) {
36466
36491
  });
36467
36492
  server.registerTool("gog_auth_add_complete", {
36468
36493
  description: "Complete REMOTE/headless Google authorization (step 2 of 2). Takes the full redirected localhost URL the user copied after finishing gog_auth_add_url, exchanges it for a refresh token, and stores it. Use the SAME `services` value you passed to gog_auth_add_url. Must run within 10 minutes of step 1 and against the same gogcli host.",
36469
- annotations: { destructiveHint: true },
36494
+ annotations: { destructiveHint: true, openWorldHint: true },
36470
36495
  inputSchema: external_exports.object({
36471
36496
  email: external_exports.string().describe("Google account email being authorized (same as step 1)"),
36472
36497
  redirectUrl: external_exports.string().describe(
@@ -36502,7 +36527,7 @@ function registerAuthToolsWith(server, defaultServices) {
36502
36527
  });
36503
36528
  registerRunTool(server, {
36504
36529
  service: "auth",
36505
- examples: '"remove", "alias", "list"',
36530
+ examples: '"alias", "list", "status"',
36506
36531
  omitAccount: true,
36507
36532
  allowedSubcommands: AUTH_RUN_SUBCOMMANDS,
36508
36533
  note: "For browser-based authorization, use gog_auth_add instead."
@@ -36572,7 +36597,7 @@ function vetContactsRun(subcommand, args) {
36572
36597
  function registerContactsTools(server) {
36573
36598
  server.registerTool("gog_contacts_search", {
36574
36599
  description: "Search personal Google Contacts by name, email, or phone. For searching the Workspace directory (internal users not in your personal contacts), use gog_people_search from gogcli-mcp-contacts.",
36575
- annotations: { readOnlyHint: true },
36600
+ annotations: { readOnlyHint: true, openWorldHint: true },
36576
36601
  inputSchema: external_exports.object({
36577
36602
  query: external_exports.string().describe("Search query (name, email, or phone)"),
36578
36603
  account: accountParam
@@ -36582,7 +36607,7 @@ function registerContactsTools(server) {
36582
36607
  });
36583
36608
  server.registerTool("gog_contacts_list", {
36584
36609
  description: "List all Google Contacts.",
36585
- annotations: { readOnlyHint: true },
36610
+ annotations: { readOnlyHint: true, openWorldHint: true },
36586
36611
  inputSchema: external_exports.object({
36587
36612
  account: accountParam
36588
36613
  })
@@ -36591,7 +36616,7 @@ function registerContactsTools(server) {
36591
36616
  });
36592
36617
  server.registerTool("gog_contacts_get", {
36593
36618
  description: "Get a contact by resource name.",
36594
- annotations: { readOnlyHint: true },
36619
+ annotations: { readOnlyHint: true, openWorldHint: true },
36595
36620
  inputSchema: external_exports.object({
36596
36621
  resourceName: external_exports.string().describe("Contact resource name (e.g. people/c12345)"),
36597
36622
  account: accountParam
@@ -36601,7 +36626,7 @@ function registerContactsTools(server) {
36601
36626
  });
36602
36627
  server.registerTool("gog_contacts_create", {
36603
36628
  description: "Create a new Google Contact.",
36604
- annotations: { destructiveHint: true },
36629
+ annotations: { destructiveHint: true, openWorldHint: true },
36605
36630
  inputSchema: external_exports.object({
36606
36631
  givenName: external_exports.string().describe("Given (first) name"),
36607
36632
  familyName: external_exports.string().optional().describe("Family (last) name"),
@@ -36624,6 +36649,11 @@ function registerContactsTools(server) {
36624
36649
  }
36625
36650
 
36626
36651
  // ../gogcli-mcp/src/tools/gmail.ts
36652
+ var signatureSchema = {
36653
+ signature: external_exports.boolean().optional().describe("Append the Gmail signature from the active send-as address"),
36654
+ signatureFrom: external_exports.string().optional().describe("Append the Gmail signature from this send-as email address"),
36655
+ signatureFile: external_exports.string().optional().describe("Append a local signature file (plain text or HTML), read on the gog server. Must be inside GOG_FILE_ROOTS. Cannot be combined with signature/signatureFrom.")
36656
+ };
36627
36657
  var replySchema = {
36628
36658
  messageId: external_exports.string().describe("Gmail message ID to reply to \u2014 the short hex `id` from gog_gmail_get / _search (or gog_gmail_messages_search, gogcli-mcp-gmail only). NOT the threadId, NOT the RFC822 `<\u2026@host>` Message-Id header."),
36629
36659
  body: external_exports.string().optional().describe("Reply body (plain text; required unless bodyHtml or bodyHtmlFile is set). Any size \u2014 a large body is written to a temp file on the gog server rather than inlined into the command line. Note gog strips trailing newlines from a file-delivered body."),
@@ -36639,9 +36669,7 @@ var replySchema = {
36639
36669
  attachInline: attachInlineParam,
36640
36670
  from: external_exports.string().optional().describe("Send from this email address (must be a verified send-as alias)"),
36641
36671
  autoFromAddressedAlias: external_exports.boolean().optional().describe("When from is omitted, send from the verified send-as alias the original message was addressed TO, instead of the account's primary address \u2014 so a reply to mail sent to an alias goes back out from that alias. Ignored when from is set."),
36642
- signature: external_exports.boolean().optional().describe("Append the Gmail signature from the active send-as address"),
36643
- signatureFrom: external_exports.string().optional().describe("Append the Gmail signature from this send-as email address"),
36644
- signatureFile: external_exports.string().optional().describe("Append a local signature file (plain text or HTML), read on the gog server"),
36672
+ ...signatureSchema,
36645
36673
  account: accountParam
36646
36674
  };
36647
36675
  var sendReplySchema = external_exports.object({ ...replySchema, confirmToken: confirmTokenParam });
@@ -36656,7 +36684,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
36656
36684
  );
36657
36685
 
36658
36686
  // ../gogcli-mcp/src/server.ts
36659
- var VERSION = true ? "4.7.0" : "0.0.0";
36687
+ var VERSION = true ? "4.8.0" : "0.0.0";
36660
36688
 
36661
36689
  // ../gogcli-mcp/src/bootstrap-auth.ts
36662
36690
  import { join as join7 } from "node:path";
@@ -36734,7 +36762,7 @@ var MAX_BLOB_UPLOAD_BYTES = 100 * 1024 * 1024;
36734
36762
  function registerExtraContactsTools(server) {
36735
36763
  server.registerTool("gog_people_me", {
36736
36764
  description: "Show your own People profile (people/me).",
36737
- annotations: { readOnlyHint: true },
36765
+ annotations: { readOnlyHint: true, openWorldHint: true },
36738
36766
  inputSchema: external_exports.object({
36739
36767
  account: accountParam
36740
36768
  })
@@ -36743,7 +36771,7 @@ function registerExtraContactsTools(server) {
36743
36771
  });
36744
36772
  server.registerTool("gog_people_get", {
36745
36773
  description: "Get a People profile by resource name.",
36746
- annotations: { readOnlyHint: true },
36774
+ annotations: { readOnlyHint: true, openWorldHint: true },
36747
36775
  inputSchema: external_exports.object({
36748
36776
  userId: external_exports.string().describe("Person resource name (people/...) or email"),
36749
36777
  account: accountParam
@@ -36753,7 +36781,7 @@ function registerExtraContactsTools(server) {
36753
36781
  });
36754
36782
  server.registerTool("gog_people_search", {
36755
36783
  description: "Search the Google Workspace directory (covers internal users, unlike contacts search which is limited to your personal contacts).",
36756
- annotations: { readOnlyHint: true },
36784
+ annotations: { readOnlyHint: true, openWorldHint: true },
36757
36785
  inputSchema: external_exports.object({
36758
36786
  query: external_exports.string().describe("Search query (name, email, etc.)"),
36759
36787
  max: external_exports.number().optional().describe("Max results (default: 50)"),
@@ -36772,7 +36800,7 @@ function registerExtraContactsTools(server) {
36772
36800
  });
36773
36801
  server.registerTool("gog_people_relations", {
36774
36802
  description: "Get relations (manager, reports, etc.) for a user. Defaults to self when userId is omitted.",
36775
- annotations: { readOnlyHint: true },
36803
+ annotations: { readOnlyHint: true, openWorldHint: true },
36776
36804
  inputSchema: external_exports.object({
36777
36805
  userId: external_exports.string().optional().describe("Person resource name (defaults to self when omitted)"),
36778
36806
  type: external_exports.string().optional().describe('Filter to a specific relation type (e.g. "manager")'),
@@ -36786,7 +36814,7 @@ function registerExtraContactsTools(server) {
36786
36814
  });
36787
36815
  server.registerTool("gog_contacts_update", {
36788
36816
  description: "Update an existing Google Contact. Empty string clears a field; repeatable fields (url/address/custom/relation) take comma/semicolon-separated lists.",
36789
- annotations: { destructiveHint: true },
36817
+ annotations: { destructiveHint: true, openWorldHint: true },
36790
36818
  inputSchema: external_exports.object({
36791
36819
  resourceName: external_exports.string().describe("Contact resource name (people/...)"),
36792
36820
  given: external_exports.string().optional().describe("Given (first) name"),
@@ -36819,7 +36847,7 @@ function registerExtraContactsTools(server) {
36819
36847
  });
36820
36848
  server.registerTool("gog_contacts_delete", {
36821
36849
  description: "Delete a Google Contact by resource name.",
36822
- annotations: { destructiveHint: true },
36850
+ annotations: { destructiveHint: true, openWorldHint: true },
36823
36851
  inputSchema: external_exports.object({
36824
36852
  resourceName: external_exports.string().describe("Contact resource name (people/...)"),
36825
36853
  account: accountParam
@@ -36829,13 +36857,13 @@ function registerExtraContactsTools(server) {
36829
36857
  });
36830
36858
  server.registerTool("gog_contacts_batch_get", {
36831
36859
  description: "Fetch exact Google Contacts by People resource name in native API batches (up to 200).",
36832
- annotations: { readOnlyHint: true },
36860
+ annotations: { readOnlyHint: true, openWorldHint: true },
36833
36861
  inputSchema: external_exports.object({ resourceNames: external_exports.array(external_exports.string().min(1)).min(1).max(200).describe("Exact resource names such as people/c123; maximum 200"), account: accountParam })
36834
36862
  }, async ({ resourceNames, account }) => runOrDiagnose(["contacts", "batch", "get", ...resourceNames.map(pos)], { account }));
36835
36863
  server.registerTool("gog_contacts_batch_create", {
36836
36864
  description: "Create Google Contacts in native People API batches (up to 200). peopleJson is a JSON array of People API Person resources. The payload is materialized in a temporary file on the gog host.",
36837
36865
  // Same kind of write as gog_contacts_create, so the same annotation.
36838
- annotations: { readOnlyHint: false, destructiveHint: true },
36866
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
36839
36867
  inputSchema: external_exports.object({ peopleJson: external_exports.string().describe("JSON array of People API Person resources, up to 200 contacts"), account: accountParam })
36840
36868
  }, async ({ peopleJson, account }) => {
36841
36869
  let people;
@@ -36850,7 +36878,7 @@ function registerExtraContactsTools(server) {
36850
36878
  server.registerTool("gog_contacts_batch_update", {
36851
36879
  description: "Update Google Contacts in native People API batches. peopleByResourceName is a JSON object keyed by People resource name; each value must include the CONTACT source metadata and etag returned by a prior read. gog preserves those etag guards and reports partial progress.",
36852
36880
  // Overwrites fields like gog_contacts_update, so the same annotation.
36853
- annotations: { readOnlyHint: false, destructiveHint: true },
36881
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
36854
36882
  inputSchema: external_exports.object({ peopleByResourceName: external_exports.string().describe("JSON object keyed by people/... resource name, with Person resources including CONTACT source metadata and etag"), account: accountParam })
36855
36883
  }, async ({ peopleByResourceName, account }) => {
36856
36884
  let people;
@@ -36864,7 +36892,7 @@ function registerExtraContactsTools(server) {
36864
36892
  });
36865
36893
  server.registerTool("gog_contacts_batch_delete", {
36866
36894
  description: "Permanently delete exact Google Contacts in native People API batches (up to 500). Requires explicit user confirmation; deleted contacts cannot be restored from Trash.",
36867
- annotations: { destructiveHint: true },
36895
+ annotations: { destructiveHint: true, openWorldHint: true },
36868
36896
  inputSchema: external_exports.object({ resourceNames: external_exports.array(external_exports.string().min(1)).min(1).max(500).describe("Exact People resource names; maximum 500"), account: accountParam, confirmToken: confirmTokenParam })
36869
36897
  }, async ({ resourceNames, account, confirmToken }, ctx) => {
36870
36898
  const confirmation = await requireDispatchConfirmation(ctx, {
@@ -36872,7 +36900,7 @@ function registerExtraContactsTools(server) {
36872
36900
  message: "Review and confirm permanent deletion of these Google Contacts:",
36873
36901
  confirmationLabel: "Confirm permanent deletion of the listed contacts.",
36874
36902
  details: { resourceNames },
36875
- fallback: { tool: "gog_contacts_batch_delete", account, confirmToken, subject: () => ({ target: "contacts", payload: { resourceNames }, preview: { resourceNames } }) }
36903
+ fallback: { args: { resourceNames, account }, tool: "gog_contacts_batch_delete", account, confirmToken, subject: () => ({ target: "contacts", payload: { resourceNames }, preview: { resourceNames } }) }
36876
36904
  });
36877
36905
  if (confirmation) return confirmation;
36878
36906
  return runOrDiagnose(["contacts", "batch", "delete", ...resourceNames.map(pos), "--force"], { account });
@@ -36880,7 +36908,7 @@ function registerExtraContactsTools(server) {
36880
36908
  server.registerTool("gog_contacts_export", {
36881
36909
  description: "Export contacts as vCard (.vcf). Provide a selector (resource name, email, or name), or use query / all to export multiple.",
36882
36910
  // Writes a file on the gog host and can overwrite one: not read-only (audit SEC-4).
36883
- annotations: { readOnlyHint: false, destructiveHint: true },
36911
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
36884
36912
  inputSchema: external_exports.object({
36885
36913
  selector: external_exports.string().optional().describe("Contact resource name (people/...), email, or name"),
36886
36914
  query: external_exports.string().optional().describe("Search query to export (max 30 results)"),
@@ -36905,7 +36933,7 @@ function registerExtraContactsTools(server) {
36905
36933
  });
36906
36934
  server.registerTool("gog_contacts_dedupe", {
36907
36935
  description: "Find likely duplicate personal contacts. Defaults to a read-only preview of the merge plan; set apply to actually merge each duplicate group and delete the redundant contacts (etag-checked, with ambiguous or unmergeable groups refused). Scope a risky apply with resource. Always preview first.",
36908
- annotations: { destructiveHint: true },
36936
+ annotations: { destructiveHint: true, openWorldHint: true },
36909
36937
  inputSchema: external_exports.object({
36910
36938
  match: external_exports.string().optional().describe("Match fields, comma-separated from email,phone,name (default: email,phone)"),
36911
36939
  max: external_exports.number().optional().describe("Max contacts to scan (0 = all). Mutually exclusive with resource \u2014 gog rejects passing both."),
@@ -36926,7 +36954,7 @@ function registerExtraContactsTools(server) {
36926
36954
  });
36927
36955
  server.registerTool("gog_contacts_directory_list", {
36928
36956
  description: "List people from the Google Workspace directory (domain shared contacts).",
36929
- annotations: { readOnlyHint: true },
36957
+ annotations: { readOnlyHint: true, openWorldHint: true },
36930
36958
  inputSchema: external_exports.object({
36931
36959
  max: external_exports.number().optional().describe("Max results (default: 50)"),
36932
36960
  pageToken: pageTokenParam,
@@ -36944,7 +36972,7 @@ function registerExtraContactsTools(server) {
36944
36972
  });
36945
36973
  server.registerTool("gog_contacts_other_list", {
36946
36974
  description: 'List "other contacts" \u2014 auto-collected addresses (e.g. people you have emailed) that are not in your saved contacts.',
36947
- annotations: { readOnlyHint: true },
36975
+ annotations: { readOnlyHint: true, openWorldHint: true },
36948
36976
  inputSchema: external_exports.object({
36949
36977
  max: external_exports.number().optional().describe("Max results (default: 100)"),
36950
36978
  pageToken: pageTokenParam,
@@ -36962,7 +36990,7 @@ function registerExtraContactsTools(server) {
36962
36990
  });
36963
36991
  server.registerTool("gog_contacts_other_search", {
36964
36992
  description: 'Search "other contacts" \u2014 auto-collected addresses not in your saved contacts.',
36965
- annotations: { readOnlyHint: true },
36993
+ annotations: { readOnlyHint: true, openWorldHint: true },
36966
36994
  inputSchema: external_exports.object({
36967
36995
  query: external_exports.string().describe("Search query"),
36968
36996
  max: external_exports.number().optional().describe("Max results (default: 50)"),
@@ -36975,7 +37003,7 @@ function registerExtraContactsTools(server) {
36975
37003
  });
36976
37004
  server.registerTool("gog_people_raw", {
36977
37005
  description: "Dump the raw People API response as JSON (lossless; for scripting and LLM consumption).",
36978
- annotations: { readOnlyHint: true },
37006
+ annotations: { readOnlyHint: true, openWorldHint: true },
36979
37007
  inputSchema: external_exports.object({
36980
37008
  userId: external_exports.string().describe("Person resource name (people/...) or email"),
36981
37009
  personFields: external_exports.string().optional().describe("People API personFields mask (default: broad set)"),
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-contacts",
5
5
  "display_name": "gogcli (Contacts)",
6
- "version": "4.7.0",
6
+ "version": "4.8.0",
7
7
  "description": "Extended Google Contacts for Claude via gogcli — auth + Contacts + Workspace directory (People API)",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -36,7 +36,10 @@
36
36
  "env": {
37
37
  "GOG_ACCOUNT": "${user_config.gog_account}",
38
38
  "GOG_PATH": "${user_config.gog_path}",
39
- "GOG_FILE_ROOTS": "${user_config.gog_file_roots}"
39
+ "GOG_FILE_ROOTS": "${user_config.gog_file_roots}",
40
+ "DISPLAY_TZ": "${user_config.display_tz}",
41
+ "GOG_READONLY": "${user_config.gog_readonly}",
42
+ "GOG_TIMEZONE": "${user_config.gog_timezone}"
40
43
  }
41
44
  }
42
45
  },
@@ -58,6 +61,24 @@
58
61
  "title": "Allowed file directories",
59
62
  "description": "Directories tools may read files from (attachments, uploads) or write files to (exports, downloads), separated by ':' (';' on Windows). Optional — defaults to ~/gogcli-mcp-files",
60
63
  "required": false
64
+ },
65
+ "display_tz": {
66
+ "type": "string",
67
+ "title": "Display time zone",
68
+ "description": "zone for *Display fields and for interpreting naive gog values; defaults to America/New_York",
69
+ "required": false
70
+ },
71
+ "gog_readonly": {
72
+ "type": "string",
73
+ "title": "Read-only mode",
74
+ "description": "block all mutating gog API requests (injects gog's --readonly); set to 0/false/no/off (or unset) to allow writes",
75
+ "required": false
76
+ },
77
+ "gog_timezone": {
78
+ "type": "string",
79
+ "title": "gog time zone",
80
+ "description": "zone gog formats naive dates in; unset, runner.ts hands gog DISPLAY_TZ so the two cannot diverge",
81
+ "required": false
61
82
  }
62
83
  },
63
84
  "tools": [
package/mint.yaml CHANGED
@@ -92,7 +92,7 @@ dependencies:
92
92
  # bumping these nine pins, and the pin stored on each mcp-host registration.
93
93
  - kind: github-release
94
94
  repo: openclaw/gogcli
95
- tag: v0.43.0
95
+ tag: v0.44.0
96
96
  asset: "gogcli_*_linux_amd64.tar.gz"
97
97
  bin: [gog]
98
98
  state:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-contacts",
3
- "version": "4.7.0",
3
+ "version": "4.8.0",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-contacts",
5
5
  "description": "Extended Google Contacts + People MCP server via gogcli — auth + Contacts + Workspace directory (People API)",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -24,8 +24,8 @@
24
24
  "test:coverage": "vitest run --coverage"
25
25
  },
26
26
  "dependencies": {
27
- "@chrischall/mcp-utils": "^2.15.0",
28
- "@modelcontextprotocol/server": "^2.3.0",
27
+ "@chrischall/mcp-utils": "^3.0.0",
28
+ "@modelcontextprotocol/server": "^2.3.1",
29
29
  "zod": "^4.6.1"
30
30
  },
31
31
  "license": "MIT",
@@ -8,7 +8,7 @@ import type { GogArg } from '../../../gogcli-mcp/src/lib.js';
8
8
  export function registerExtraContactsTools(server: McpServer): void {
9
9
  server.registerTool('gog_people_me', {
10
10
  description: 'Show your own People profile (people/me).',
11
- annotations: { readOnlyHint: true },
11
+ annotations: { readOnlyHint: true, openWorldHint: true },
12
12
  inputSchema: z.object({
13
13
  account: accountParam,
14
14
  }),
@@ -18,7 +18,7 @@ export function registerExtraContactsTools(server: McpServer): void {
18
18
 
19
19
  server.registerTool('gog_people_get', {
20
20
  description: 'Get a People profile by resource name.',
21
- annotations: { readOnlyHint: true },
21
+ annotations: { readOnlyHint: true, openWorldHint: true },
22
22
  inputSchema: z.object({
23
23
  userId: z.string().describe('Person resource name (people/...) or email'),
24
24
  account: accountParam,
@@ -29,7 +29,7 @@ export function registerExtraContactsTools(server: McpServer): void {
29
29
 
30
30
  server.registerTool('gog_people_search', {
31
31
  description: 'Search the Google Workspace directory (covers internal users, unlike contacts search which is limited to your personal contacts).',
32
- annotations: { readOnlyHint: true },
32
+ annotations: { readOnlyHint: true, openWorldHint: true },
33
33
  inputSchema: z.object({
34
34
  query: z.string().describe('Search query (name, email, etc.)'),
35
35
  max: z.number().optional().describe('Max results (default: 50)'),
@@ -49,7 +49,7 @@ export function registerExtraContactsTools(server: McpServer): void {
49
49
 
50
50
  server.registerTool('gog_people_relations', {
51
51
  description: 'Get relations (manager, reports, etc.) for a user. Defaults to self when userId is omitted.',
52
- annotations: { readOnlyHint: true },
52
+ annotations: { readOnlyHint: true, openWorldHint: true },
53
53
  inputSchema: z.object({
54
54
  userId: z.string().optional().describe('Person resource name (defaults to self when omitted)'),
55
55
  type: z.string().optional().describe('Filter to a specific relation type (e.g. "manager")'),
@@ -64,7 +64,7 @@ export function registerExtraContactsTools(server: McpServer): void {
64
64
 
65
65
  server.registerTool('gog_contacts_update', {
66
66
  description: 'Update an existing Google Contact. Empty string clears a field; repeatable fields (url/address/custom/relation) take comma/semicolon-separated lists.',
67
- annotations: { destructiveHint: true },
67
+ annotations: { destructiveHint: true, openWorldHint: true },
68
68
  inputSchema: z.object({
69
69
  resourceName: z.string().describe('Contact resource name (people/...)'),
70
70
  given: z.string().optional().describe('Given (first) name'),
@@ -98,7 +98,7 @@ export function registerExtraContactsTools(server: McpServer): void {
98
98
 
99
99
  server.registerTool('gog_contacts_delete', {
100
100
  description: 'Delete a Google Contact by resource name.',
101
- annotations: { destructiveHint: true },
101
+ annotations: { destructiveHint: true, openWorldHint: true },
102
102
  inputSchema: z.object({
103
103
  resourceName: z.string().describe('Contact resource name (people/...)'),
104
104
  account: accountParam,
@@ -109,14 +109,14 @@ export function registerExtraContactsTools(server: McpServer): void {
109
109
 
110
110
  server.registerTool('gog_contacts_batch_get', {
111
111
  description: 'Fetch exact Google Contacts by People resource name in native API batches (up to 200).',
112
- annotations: { readOnlyHint: true },
112
+ annotations: { readOnlyHint: true, openWorldHint: true },
113
113
  inputSchema: z.object({ resourceNames: z.array(z.string().min(1)).min(1).max(200).describe('Exact resource names such as people/c123; maximum 200'), account: accountParam }),
114
114
  }, async ({ resourceNames, account }) => runOrDiagnose(['contacts', 'batch', 'get', ...resourceNames.map(pos)], { account }));
115
115
 
116
116
  server.registerTool('gog_contacts_batch_create', {
117
117
  description: 'Create Google Contacts in native People API batches (up to 200). peopleJson is a JSON array of People API Person resources. The payload is materialized in a temporary file on the gog host.',
118
118
  // Same kind of write as gog_contacts_create, so the same annotation.
119
- annotations: { readOnlyHint: false, destructiveHint: true },
119
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
120
120
  inputSchema: z.object({ peopleJson: z.string().describe('JSON array of People API Person resources, up to 200 contacts'), account: accountParam }),
121
121
  }, async ({ peopleJson, account }) => {
122
122
  let people: unknown;
@@ -128,7 +128,7 @@ export function registerExtraContactsTools(server: McpServer): void {
128
128
  server.registerTool('gog_contacts_batch_update', {
129
129
  description: 'Update Google Contacts in native People API batches. peopleByResourceName is a JSON object keyed by People resource name; each value must include the CONTACT source metadata and etag returned by a prior read. gog preserves those etag guards and reports partial progress.',
130
130
  // Overwrites fields like gog_contacts_update, so the same annotation.
131
- annotations: { readOnlyHint: false, destructiveHint: true },
131
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
132
132
  inputSchema: z.object({ peopleByResourceName: z.string().describe('JSON object keyed by people/... resource name, with Person resources including CONTACT source metadata and etag'), account: accountParam }),
133
133
  }, async ({ peopleByResourceName, account }) => {
134
134
  let people: unknown;
@@ -139,7 +139,7 @@ export function registerExtraContactsTools(server: McpServer): void {
139
139
 
140
140
  server.registerTool('gog_contacts_batch_delete', {
141
141
  description: 'Permanently delete exact Google Contacts in native People API batches (up to 500). Requires explicit user confirmation; deleted contacts cannot be restored from Trash.',
142
- annotations: { destructiveHint: true },
142
+ annotations: { destructiveHint: true, openWorldHint: true },
143
143
  inputSchema: z.object({ resourceNames: z.array(z.string().min(1)).min(1).max(500).describe('Exact People resource names; maximum 500'), account: accountParam, confirmToken: confirmTokenParam }),
144
144
  }, async ({ resourceNames, account, confirmToken }, ctx) => {
145
145
  const confirmation = await requireDispatchConfirmation(ctx, {
@@ -147,7 +147,7 @@ export function registerExtraContactsTools(server: McpServer): void {
147
147
  message: 'Review and confirm permanent deletion of these Google Contacts:',
148
148
  confirmationLabel: 'Confirm permanent deletion of the listed contacts.',
149
149
  details: { resourceNames },
150
- fallback: { tool: 'gog_contacts_batch_delete', account, confirmToken, subject: () => ({ target: 'contacts', payload: { resourceNames }, preview: { resourceNames } }) },
150
+ fallback: { args: { resourceNames, account }, tool: 'gog_contacts_batch_delete', account, confirmToken, subject: () => ({ target: 'contacts', payload: { resourceNames }, preview: { resourceNames } }) },
151
151
  });
152
152
  if (confirmation) return confirmation;
153
153
  return runOrDiagnose(['contacts', 'batch', 'delete', ...resourceNames.map(pos), '--force'], { account });
@@ -156,7 +156,7 @@ export function registerExtraContactsTools(server: McpServer): void {
156
156
  server.registerTool('gog_contacts_export', {
157
157
  description: 'Export contacts as vCard (.vcf). Provide a selector (resource name, email, or name), or use query / all to export multiple.',
158
158
  // Writes a file on the gog host and can overwrite one: not read-only (audit SEC-4).
159
- annotations: { readOnlyHint: false, destructiveHint: true },
159
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
160
160
  inputSchema: z.object({
161
161
  selector: z.string().optional().describe('Contact resource name (people/...), email, or name'),
162
162
  query: z.string().optional().describe('Search query to export (max 30 results)'),
@@ -182,7 +182,7 @@ export function registerExtraContactsTools(server: McpServer): void {
182
182
 
183
183
  server.registerTool('gog_contacts_dedupe', {
184
184
  description: 'Find likely duplicate personal contacts. Defaults to a read-only preview of the merge plan; set apply to actually merge each duplicate group and delete the redundant contacts (etag-checked, with ambiguous or unmergeable groups refused). Scope a risky apply with resource. Always preview first.',
185
- annotations: { destructiveHint: true },
185
+ annotations: { destructiveHint: true, openWorldHint: true },
186
186
  inputSchema: z.object({
187
187
  match: z.string().optional().describe('Match fields, comma-separated from email,phone,name (default: email,phone)'),
188
188
  max: z.number().optional().describe('Max contacts to scan (0 = all). Mutually exclusive with resource — gog rejects passing both.'),
@@ -204,7 +204,7 @@ export function registerExtraContactsTools(server: McpServer): void {
204
204
 
205
205
  server.registerTool('gog_contacts_directory_list', {
206
206
  description: 'List people from the Google Workspace directory (domain shared contacts).',
207
- annotations: { readOnlyHint: true },
207
+ annotations: { readOnlyHint: true, openWorldHint: true },
208
208
  inputSchema: z.object({
209
209
  max: z.number().optional().describe('Max results (default: 50)'),
210
210
  pageToken: pageTokenParam,
@@ -223,7 +223,7 @@ export function registerExtraContactsTools(server: McpServer): void {
223
223
 
224
224
  server.registerTool('gog_contacts_other_list', {
225
225
  description: 'List "other contacts" — auto-collected addresses (e.g. people you have emailed) that are not in your saved contacts.',
226
- annotations: { readOnlyHint: true },
226
+ annotations: { readOnlyHint: true, openWorldHint: true },
227
227
  inputSchema: z.object({
228
228
  max: z.number().optional().describe('Max results (default: 100)'),
229
229
  pageToken: pageTokenParam,
@@ -242,7 +242,7 @@ export function registerExtraContactsTools(server: McpServer): void {
242
242
 
243
243
  server.registerTool('gog_contacts_other_search', {
244
244
  description: 'Search "other contacts" — auto-collected addresses not in your saved contacts.',
245
- annotations: { readOnlyHint: true },
245
+ annotations: { readOnlyHint: true, openWorldHint: true },
246
246
  inputSchema: z.object({
247
247
  query: z.string().describe('Search query'),
248
248
  max: z.number().optional().describe('Max results (default: 50)'),
@@ -256,7 +256,7 @@ export function registerExtraContactsTools(server: McpServer): void {
256
256
 
257
257
  server.registerTool('gog_people_raw', {
258
258
  description: 'Dump the raw People API response as JSON (lossless; for scripting and LLM consumption).',
259
- annotations: { readOnlyHint: true },
259
+ annotations: { readOnlyHint: true, openWorldHint: true },
260
260
  inputSchema: z.object({
261
261
  userId: z.string().describe('Person resource name (people/...) or email'),
262
262
  personFields: z.string().optional().describe('People API personFields mask (default: broad set)'),
@@ -0,0 +1,10 @@
1
+ import { authToolsFor, registerContactsTools } from '../../gogcli-mcp/src/lib.js';
2
+ import { registerExtraContactsTools } from '../src/tools/contacts-extra.js';
3
+ import { describeToolAnnotations, LOCAL_AUTH_TOOLS } from '../../gogcli-mcp/tests/support/tool-annotations.js';
4
+
5
+ // The same registrar list src/index.ts hands to runMcp.
6
+ describeToolAnnotations([authToolsFor('contacts'), registerContactsTools, registerExtraContactsTools] as never, {
7
+ tools: 29,
8
+ destructive: 12,
9
+ local: [...LOCAL_AUTH_TOOLS],
10
+ });