gogcli-mcp-contacts 4.6.3 → 4.7.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/dist/index.js +103 -42
- package/manifest.json +23 -2
- package/package.json +2 -2
- package/src/tools/contacts-extra.ts +17 -17
- package/tests/tool-annotations.test.ts +10 -0
package/dist/index.js
CHANGED
|
@@ -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) {
|
|
@@ -34731,6 +34746,7 @@ function escapeKey(key) {
|
|
|
34731
34746
|
var DEFAULT_REQUEST_KEY = "confirmation";
|
|
34732
34747
|
var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
|
|
34733
34748
|
var UNSUPPORTED_NOTE = "Nothing was done because this client cannot show a confirmation prompt (it declares no MCP elicitation capability), and this action is never taken without one";
|
|
34749
|
+
var DISABLED_NOTE = "Nothing was done because confirmation prompts are turned off for this server, and this action is never taken without one";
|
|
34734
34750
|
var STATE_PREFIX = "mcpu.confirm.v1.";
|
|
34735
34751
|
function bindingKey(binding) {
|
|
34736
34752
|
if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
|
|
@@ -34803,6 +34819,15 @@ ${JSON.stringify(preview, null, 2)}`,
|
|
|
34803
34819
|
};
|
|
34804
34820
|
const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
|
|
34805
34821
|
const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
|
|
34822
|
+
if (options.elicitation === false) {
|
|
34823
|
+
return textResult({
|
|
34824
|
+
confirmed: false,
|
|
34825
|
+
dispatched: false,
|
|
34826
|
+
action: options.action,
|
|
34827
|
+
reason: "confirmation-unsupported",
|
|
34828
|
+
note: options.unsupportedNote ? `${DISABLED_NOTE}. ${options.unsupportedNote}` : `${DISABLED_NOTE}.`
|
|
34829
|
+
});
|
|
34830
|
+
}
|
|
34806
34831
|
if (response.kind === "missing") {
|
|
34807
34832
|
if (callerAcceptsFormElicitation(ctx) === false) {
|
|
34808
34833
|
return textResult({
|
|
@@ -34997,7 +35022,7 @@ async function tokenConfirmation(action, fb) {
|
|
|
34997
35022
|
}
|
|
34998
35023
|
async function requireConfirmationWithFallback(ctx, options) {
|
|
34999
35024
|
const { tokenFallback, ...confirmation } = options;
|
|
35000
|
-
if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
|
|
35025
|
+
if (tokenFallback && (options.elicitation === false || callerAcceptsFormElicitation(ctx) === false)) {
|
|
35001
35026
|
return tokenConfirmation(options.action, tokenFallback);
|
|
35002
35027
|
}
|
|
35003
35028
|
return requireConfirmation(ctx, confirmation);
|
|
@@ -35017,7 +35042,7 @@ function readEnvVar(key, opts = {}) {
|
|
|
35017
35042
|
if (typeof raw === "string") {
|
|
35018
35043
|
const trimmed = raw.trim();
|
|
35019
35044
|
if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
|
|
35020
|
-
return trimmed;
|
|
35045
|
+
return opts.trim === false ? raw : trimmed;
|
|
35021
35046
|
}
|
|
35022
35047
|
}
|
|
35023
35048
|
return opts.default;
|
|
@@ -35143,6 +35168,20 @@ function readConfirmMode(env = process.env) {
|
|
|
35143
35168
|
}
|
|
35144
35169
|
return "refuse";
|
|
35145
35170
|
}
|
|
35171
|
+
function readConfirmElicitation(env = process.env) {
|
|
35172
|
+
const raw = readEnvVar("MCP_CONFIRM_ELICITATION", { env })?.trim().toLowerCase();
|
|
35173
|
+
if (!raw || raw === "on")
|
|
35174
|
+
return "on";
|
|
35175
|
+
if (raw === "off")
|
|
35176
|
+
return "off";
|
|
35177
|
+
const key = `elicitation:${raw}`;
|
|
35178
|
+
if (!warned.has(key)) {
|
|
35179
|
+
warned.add(key);
|
|
35180
|
+
process.stderr.write(`MCP_CONFIRM_ELICITATION="${raw}" is not one of on, off; treating it as on.
|
|
35181
|
+
`);
|
|
35182
|
+
}
|
|
35183
|
+
return "on";
|
|
35184
|
+
}
|
|
35146
35185
|
function readConfirmTtl(env) {
|
|
35147
35186
|
const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
|
|
35148
35187
|
if (!raw)
|
|
@@ -35187,6 +35226,7 @@ function spentTokenStoreFromEnv(env = process.env) {
|
|
|
35187
35226
|
}
|
|
35188
35227
|
var CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
|
|
35189
35228
|
var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
|
|
35229
|
+
var ELICITATION_OFF_HINT = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
|
|
35190
35230
|
var BAD_TTL_HINT = "MCP_CONFIRM_TTL_SECONDS on the server is not a positive whole number of seconds; fix it to allow two-step confirmation.";
|
|
35191
35231
|
function boundArgs(args) {
|
|
35192
35232
|
if (args === null || typeof args !== "object" || Array.isArray(args) || !("confirmToken" in args))
|
|
@@ -35197,19 +35237,29 @@ function boundArgs(args) {
|
|
|
35197
35237
|
}
|
|
35198
35238
|
function confirmationFromEnv(options) {
|
|
35199
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
|
+
}
|
|
35200
35243
|
const spent2 = callerSpent ?? spentTokenStoreFromEnv(env);
|
|
35201
35244
|
const mode = readConfirmMode(env);
|
|
35202
35245
|
const ttlSeconds = readConfirmTtl(env);
|
|
35203
|
-
const
|
|
35204
|
-
const
|
|
35246
|
+
const elicitationOff = readConfirmElicitation(env) === "off";
|
|
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
|
+
};
|
|
35253
|
+
const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
|
|
35205
35254
|
if (mode === "refuse" || ttlSeconds === void 0) {
|
|
35206
|
-
const
|
|
35255
|
+
const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
|
|
35256
|
+
const hint = elicitationOff ? `${ELICITATION_OFF_HINT} ${reason}` : reason;
|
|
35207
35257
|
return {
|
|
35208
35258
|
...confirmation,
|
|
35209
35259
|
unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
|
|
35210
35260
|
};
|
|
35211
35261
|
}
|
|
35212
|
-
const boundSubject =
|
|
35262
|
+
const boundSubject = async () => {
|
|
35213
35263
|
const read = await subject();
|
|
35214
35264
|
if ("content" in read)
|
|
35215
35265
|
return read;
|
|
@@ -35236,12 +35286,14 @@ var SERVER_PROTOCOL_VERSIONS = Object.freeze([
|
|
|
35236
35286
|
...SUPPORTED_PROTOCOL_VERSIONS
|
|
35237
35287
|
]);
|
|
35238
35288
|
function hintResultOrRethrow(err) {
|
|
35289
|
+
if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
|
|
35290
|
+
throw err;
|
|
35239
35291
|
if (err instanceof McpToolError && err.hint) {
|
|
35240
35292
|
return errorResult(`${err.message}
|
|
35241
35293
|
|
|
35242
35294
|
Hint: ${err.hint}`);
|
|
35243
35295
|
}
|
|
35244
|
-
|
|
35296
|
+
return errorResult(messageOf(err));
|
|
35245
35297
|
}
|
|
35246
35298
|
function mcpRequestFrom(args) {
|
|
35247
35299
|
const ctx = args.at(-1);
|
|
@@ -35475,7 +35527,7 @@ var PositiveInt = external_exports.number().int().positive();
|
|
|
35475
35527
|
var NonNegInt = external_exports.number().int().nonnegative();
|
|
35476
35528
|
var NonEmptyString = external_exports.string().min(1);
|
|
35477
35529
|
var IsoDate = external_exports.iso.date();
|
|
35478
|
-
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"));
|
|
35479
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.");
|
|
35480
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.');
|
|
35481
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.");
|
|
@@ -36162,7 +36214,7 @@ function registerRunTool(server, options) {
|
|
|
36162
36214
|
}
|
|
36163
36215
|
server.registerTool(`gog_${service}_run`, {
|
|
36164
36216
|
description,
|
|
36165
|
-
annotations: { destructiveHint: true },
|
|
36217
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36166
36218
|
inputSchema: external_exports.object(inputSchema)
|
|
36167
36219
|
}, async (rawArgs) => {
|
|
36168
36220
|
const { subcommand, args, account } = rawArgs;
|
|
@@ -36330,11 +36382,20 @@ async function requireDispatchConfirmation(ctx, options) {
|
|
|
36330
36382
|
confirmationLabel: options.confirmationLabel,
|
|
36331
36383
|
...options.unsupportedNote ? { unsupportedNote: options.unsupportedNote } : {}
|
|
36332
36384
|
};
|
|
36333
|
-
if (!fallback)
|
|
36385
|
+
if (!fallback) {
|
|
36386
|
+
if (readConfirmElicitation() !== "off") return requireConfirmationWithFallback(ctx, confirmation);
|
|
36387
|
+
const offNote = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
|
|
36388
|
+
return requireConfirmationWithFallback(ctx, {
|
|
36389
|
+
...confirmation,
|
|
36390
|
+
elicitation: false,
|
|
36391
|
+
unsupportedNote: confirmation.unsupportedNote ? `${offNote} ${confirmation.unsupportedNote}` : offNote
|
|
36392
|
+
});
|
|
36393
|
+
}
|
|
36334
36394
|
return requireConfirmationWithFallback(ctx, confirmationFromEnv({
|
|
36335
36395
|
...confirmation,
|
|
36336
36396
|
tool: fallback.tool,
|
|
36337
36397
|
account: fallback.account ?? readEnvVar("GOG_ACCOUNT") ?? "",
|
|
36398
|
+
args: fallback.args,
|
|
36338
36399
|
confirmToken: fallback.confirmToken,
|
|
36339
36400
|
subject: fallback.subject,
|
|
36340
36401
|
instruction: fallback.instruction ?? CONFIRM_ACTION_INSTRUCTION,
|
|
@@ -36343,13 +36404,13 @@ async function requireDispatchConfirmation(ctx, options) {
|
|
|
36343
36404
|
}
|
|
36344
36405
|
|
|
36345
36406
|
// ../gogcli-mcp/src/tools/auth.ts
|
|
36346
|
-
var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "
|
|
36407
|
+
var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "alias"];
|
|
36347
36408
|
function registerAuthToolsWith(server, defaultServices) {
|
|
36348
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.`;
|
|
36349
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.";
|
|
36350
36411
|
server.registerTool("gog_auth_list", {
|
|
36351
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.",
|
|
36352
|
-
annotations: { readOnlyHint: true },
|
|
36413
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
36353
36414
|
inputSchema: external_exports.object({})
|
|
36354
36415
|
}, async () => {
|
|
36355
36416
|
try {
|
|
@@ -36360,7 +36421,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36360
36421
|
});
|
|
36361
36422
|
server.registerTool("gog_auth_status", {
|
|
36362
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.",
|
|
36363
|
-
annotations: { readOnlyHint: true },
|
|
36424
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
36364
36425
|
inputSchema: external_exports.object({})
|
|
36365
36426
|
}, async () => {
|
|
36366
36427
|
try {
|
|
@@ -36371,7 +36432,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36371
36432
|
});
|
|
36372
36433
|
server.registerTool("gog_auth_health", {
|
|
36373
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.`,
|
|
36374
|
-
annotations: { readOnlyHint: true },
|
|
36435
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36375
36436
|
inputSchema: external_exports.object({})
|
|
36376
36437
|
}, async () => {
|
|
36377
36438
|
try {
|
|
@@ -36382,7 +36443,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36382
36443
|
});
|
|
36383
36444
|
server.registerTool("gog_auth_services", {
|
|
36384
36445
|
description: "List all Google services supported by gogcli and the OAuth scopes each requires.",
|
|
36385
|
-
annotations: { readOnlyHint: true },
|
|
36446
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
36386
36447
|
inputSchema: external_exports.object({})
|
|
36387
36448
|
}, async () => {
|
|
36388
36449
|
try {
|
|
@@ -36393,7 +36454,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36393
36454
|
});
|
|
36394
36455
|
server.registerTool("gog_auth_add", {
|
|
36395
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.",
|
|
36396
|
-
annotations: { destructiveHint: true },
|
|
36457
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36397
36458
|
inputSchema: external_exports.object({
|
|
36398
36459
|
email: external_exports.string().describe("Google account email to authorize"),
|
|
36399
36460
|
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
|
|
@@ -36413,7 +36474,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36413
36474
|
});
|
|
36414
36475
|
server.registerTool("gog_auth_add_url", {
|
|
36415
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.",
|
|
36416
|
-
annotations: { readOnlyHint: true },
|
|
36477
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
36417
36478
|
inputSchema: external_exports.object({
|
|
36418
36479
|
email: external_exports.string().describe("Google account email to authorize"),
|
|
36419
36480
|
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
|
|
@@ -36430,7 +36491,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36430
36491
|
});
|
|
36431
36492
|
server.registerTool("gog_auth_add_complete", {
|
|
36432
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.",
|
|
36433
|
-
annotations: { destructiveHint: true },
|
|
36494
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36434
36495
|
inputSchema: external_exports.object({
|
|
36435
36496
|
email: external_exports.string().describe("Google account email being authorized (same as step 1)"),
|
|
36436
36497
|
redirectUrl: external_exports.string().describe(
|
|
@@ -36466,7 +36527,7 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
36466
36527
|
});
|
|
36467
36528
|
registerRunTool(server, {
|
|
36468
36529
|
service: "auth",
|
|
36469
|
-
examples: '"
|
|
36530
|
+
examples: '"alias", "list", "status"',
|
|
36470
36531
|
omitAccount: true,
|
|
36471
36532
|
allowedSubcommands: AUTH_RUN_SUBCOMMANDS,
|
|
36472
36533
|
note: "For browser-based authorization, use gog_auth_add instead."
|
|
@@ -36536,7 +36597,7 @@ function vetContactsRun(subcommand, args) {
|
|
|
36536
36597
|
function registerContactsTools(server) {
|
|
36537
36598
|
server.registerTool("gog_contacts_search", {
|
|
36538
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.",
|
|
36539
|
-
annotations: { readOnlyHint: true },
|
|
36600
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36540
36601
|
inputSchema: external_exports.object({
|
|
36541
36602
|
query: external_exports.string().describe("Search query (name, email, or phone)"),
|
|
36542
36603
|
account: accountParam
|
|
@@ -36546,7 +36607,7 @@ function registerContactsTools(server) {
|
|
|
36546
36607
|
});
|
|
36547
36608
|
server.registerTool("gog_contacts_list", {
|
|
36548
36609
|
description: "List all Google Contacts.",
|
|
36549
|
-
annotations: { readOnlyHint: true },
|
|
36610
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36550
36611
|
inputSchema: external_exports.object({
|
|
36551
36612
|
account: accountParam
|
|
36552
36613
|
})
|
|
@@ -36555,7 +36616,7 @@ function registerContactsTools(server) {
|
|
|
36555
36616
|
});
|
|
36556
36617
|
server.registerTool("gog_contacts_get", {
|
|
36557
36618
|
description: "Get a contact by resource name.",
|
|
36558
|
-
annotations: { readOnlyHint: true },
|
|
36619
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36559
36620
|
inputSchema: external_exports.object({
|
|
36560
36621
|
resourceName: external_exports.string().describe("Contact resource name (e.g. people/c12345)"),
|
|
36561
36622
|
account: accountParam
|
|
@@ -36565,7 +36626,7 @@ function registerContactsTools(server) {
|
|
|
36565
36626
|
});
|
|
36566
36627
|
server.registerTool("gog_contacts_create", {
|
|
36567
36628
|
description: "Create a new Google Contact.",
|
|
36568
|
-
annotations: { destructiveHint: true },
|
|
36629
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36569
36630
|
inputSchema: external_exports.object({
|
|
36570
36631
|
givenName: external_exports.string().describe("Given (first) name"),
|
|
36571
36632
|
familyName: external_exports.string().optional().describe("Family (last) name"),
|
|
@@ -36620,7 +36681,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
36620
36681
|
);
|
|
36621
36682
|
|
|
36622
36683
|
// ../gogcli-mcp/src/server.ts
|
|
36623
|
-
var VERSION = true ? "4.
|
|
36684
|
+
var VERSION = true ? "4.7.1" : "0.0.0";
|
|
36624
36685
|
|
|
36625
36686
|
// ../gogcli-mcp/src/bootstrap-auth.ts
|
|
36626
36687
|
import { join as join7 } from "node:path";
|
|
@@ -36698,7 +36759,7 @@ var MAX_BLOB_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
|
36698
36759
|
function registerExtraContactsTools(server) {
|
|
36699
36760
|
server.registerTool("gog_people_me", {
|
|
36700
36761
|
description: "Show your own People profile (people/me).",
|
|
36701
|
-
annotations: { readOnlyHint: true },
|
|
36762
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36702
36763
|
inputSchema: external_exports.object({
|
|
36703
36764
|
account: accountParam
|
|
36704
36765
|
})
|
|
@@ -36707,7 +36768,7 @@ function registerExtraContactsTools(server) {
|
|
|
36707
36768
|
});
|
|
36708
36769
|
server.registerTool("gog_people_get", {
|
|
36709
36770
|
description: "Get a People profile by resource name.",
|
|
36710
|
-
annotations: { readOnlyHint: true },
|
|
36771
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36711
36772
|
inputSchema: external_exports.object({
|
|
36712
36773
|
userId: external_exports.string().describe("Person resource name (people/...) or email"),
|
|
36713
36774
|
account: accountParam
|
|
@@ -36717,7 +36778,7 @@ function registerExtraContactsTools(server) {
|
|
|
36717
36778
|
});
|
|
36718
36779
|
server.registerTool("gog_people_search", {
|
|
36719
36780
|
description: "Search the Google Workspace directory (covers internal users, unlike contacts search which is limited to your personal contacts).",
|
|
36720
|
-
annotations: { readOnlyHint: true },
|
|
36781
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36721
36782
|
inputSchema: external_exports.object({
|
|
36722
36783
|
query: external_exports.string().describe("Search query (name, email, etc.)"),
|
|
36723
36784
|
max: external_exports.number().optional().describe("Max results (default: 50)"),
|
|
@@ -36736,7 +36797,7 @@ function registerExtraContactsTools(server) {
|
|
|
36736
36797
|
});
|
|
36737
36798
|
server.registerTool("gog_people_relations", {
|
|
36738
36799
|
description: "Get relations (manager, reports, etc.) for a user. Defaults to self when userId is omitted.",
|
|
36739
|
-
annotations: { readOnlyHint: true },
|
|
36800
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36740
36801
|
inputSchema: external_exports.object({
|
|
36741
36802
|
userId: external_exports.string().optional().describe("Person resource name (defaults to self when omitted)"),
|
|
36742
36803
|
type: external_exports.string().optional().describe('Filter to a specific relation type (e.g. "manager")'),
|
|
@@ -36750,7 +36811,7 @@ function registerExtraContactsTools(server) {
|
|
|
36750
36811
|
});
|
|
36751
36812
|
server.registerTool("gog_contacts_update", {
|
|
36752
36813
|
description: "Update an existing Google Contact. Empty string clears a field; repeatable fields (url/address/custom/relation) take comma/semicolon-separated lists.",
|
|
36753
|
-
annotations: { destructiveHint: true },
|
|
36814
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36754
36815
|
inputSchema: external_exports.object({
|
|
36755
36816
|
resourceName: external_exports.string().describe("Contact resource name (people/...)"),
|
|
36756
36817
|
given: external_exports.string().optional().describe("Given (first) name"),
|
|
@@ -36783,7 +36844,7 @@ function registerExtraContactsTools(server) {
|
|
|
36783
36844
|
});
|
|
36784
36845
|
server.registerTool("gog_contacts_delete", {
|
|
36785
36846
|
description: "Delete a Google Contact by resource name.",
|
|
36786
|
-
annotations: { destructiveHint: true },
|
|
36847
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36787
36848
|
inputSchema: external_exports.object({
|
|
36788
36849
|
resourceName: external_exports.string().describe("Contact resource name (people/...)"),
|
|
36789
36850
|
account: accountParam
|
|
@@ -36793,13 +36854,13 @@ function registerExtraContactsTools(server) {
|
|
|
36793
36854
|
});
|
|
36794
36855
|
server.registerTool("gog_contacts_batch_get", {
|
|
36795
36856
|
description: "Fetch exact Google Contacts by People resource name in native API batches (up to 200).",
|
|
36796
|
-
annotations: { readOnlyHint: true },
|
|
36857
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36797
36858
|
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 })
|
|
36798
36859
|
}, async ({ resourceNames, account }) => runOrDiagnose(["contacts", "batch", "get", ...resourceNames.map(pos)], { account }));
|
|
36799
36860
|
server.registerTool("gog_contacts_batch_create", {
|
|
36800
36861
|
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.",
|
|
36801
36862
|
// Same kind of write as gog_contacts_create, so the same annotation.
|
|
36802
|
-
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
36863
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
36803
36864
|
inputSchema: external_exports.object({ peopleJson: external_exports.string().describe("JSON array of People API Person resources, up to 200 contacts"), account: accountParam })
|
|
36804
36865
|
}, async ({ peopleJson, account }) => {
|
|
36805
36866
|
let people;
|
|
@@ -36814,7 +36875,7 @@ function registerExtraContactsTools(server) {
|
|
|
36814
36875
|
server.registerTool("gog_contacts_batch_update", {
|
|
36815
36876
|
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.",
|
|
36816
36877
|
// Overwrites fields like gog_contacts_update, so the same annotation.
|
|
36817
|
-
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
36878
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
36818
36879
|
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 })
|
|
36819
36880
|
}, async ({ peopleByResourceName, account }) => {
|
|
36820
36881
|
let people;
|
|
@@ -36828,7 +36889,7 @@ function registerExtraContactsTools(server) {
|
|
|
36828
36889
|
});
|
|
36829
36890
|
server.registerTool("gog_contacts_batch_delete", {
|
|
36830
36891
|
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.",
|
|
36831
|
-
annotations: { destructiveHint: true },
|
|
36892
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36832
36893
|
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 })
|
|
36833
36894
|
}, async ({ resourceNames, account, confirmToken }, ctx) => {
|
|
36834
36895
|
const confirmation = await requireDispatchConfirmation(ctx, {
|
|
@@ -36836,7 +36897,7 @@ function registerExtraContactsTools(server) {
|
|
|
36836
36897
|
message: "Review and confirm permanent deletion of these Google Contacts:",
|
|
36837
36898
|
confirmationLabel: "Confirm permanent deletion of the listed contacts.",
|
|
36838
36899
|
details: { resourceNames },
|
|
36839
|
-
fallback: { tool: "gog_contacts_batch_delete", account, confirmToken, subject: () => ({ target: "contacts", payload: { resourceNames }, preview: { resourceNames } }) }
|
|
36900
|
+
fallback: { args: { resourceNames, account }, tool: "gog_contacts_batch_delete", account, confirmToken, subject: () => ({ target: "contacts", payload: { resourceNames }, preview: { resourceNames } }) }
|
|
36840
36901
|
});
|
|
36841
36902
|
if (confirmation) return confirmation;
|
|
36842
36903
|
return runOrDiagnose(["contacts", "batch", "delete", ...resourceNames.map(pos), "--force"], { account });
|
|
@@ -36844,7 +36905,7 @@ function registerExtraContactsTools(server) {
|
|
|
36844
36905
|
server.registerTool("gog_contacts_export", {
|
|
36845
36906
|
description: "Export contacts as vCard (.vcf). Provide a selector (resource name, email, or name), or use query / all to export multiple.",
|
|
36846
36907
|
// Writes a file on the gog host and can overwrite one: not read-only (audit SEC-4).
|
|
36847
|
-
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
36908
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
36848
36909
|
inputSchema: external_exports.object({
|
|
36849
36910
|
selector: external_exports.string().optional().describe("Contact resource name (people/...), email, or name"),
|
|
36850
36911
|
query: external_exports.string().optional().describe("Search query to export (max 30 results)"),
|
|
@@ -36869,7 +36930,7 @@ function registerExtraContactsTools(server) {
|
|
|
36869
36930
|
});
|
|
36870
36931
|
server.registerTool("gog_contacts_dedupe", {
|
|
36871
36932
|
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.",
|
|
36872
|
-
annotations: { destructiveHint: true },
|
|
36933
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
36873
36934
|
inputSchema: external_exports.object({
|
|
36874
36935
|
match: external_exports.string().optional().describe("Match fields, comma-separated from email,phone,name (default: email,phone)"),
|
|
36875
36936
|
max: external_exports.number().optional().describe("Max contacts to scan (0 = all). Mutually exclusive with resource \u2014 gog rejects passing both."),
|
|
@@ -36890,7 +36951,7 @@ function registerExtraContactsTools(server) {
|
|
|
36890
36951
|
});
|
|
36891
36952
|
server.registerTool("gog_contacts_directory_list", {
|
|
36892
36953
|
description: "List people from the Google Workspace directory (domain shared contacts).",
|
|
36893
|
-
annotations: { readOnlyHint: true },
|
|
36954
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36894
36955
|
inputSchema: external_exports.object({
|
|
36895
36956
|
max: external_exports.number().optional().describe("Max results (default: 50)"),
|
|
36896
36957
|
pageToken: pageTokenParam,
|
|
@@ -36908,7 +36969,7 @@ function registerExtraContactsTools(server) {
|
|
|
36908
36969
|
});
|
|
36909
36970
|
server.registerTool("gog_contacts_other_list", {
|
|
36910
36971
|
description: 'List "other contacts" \u2014 auto-collected addresses (e.g. people you have emailed) that are not in your saved contacts.',
|
|
36911
|
-
annotations: { readOnlyHint: true },
|
|
36972
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36912
36973
|
inputSchema: external_exports.object({
|
|
36913
36974
|
max: external_exports.number().optional().describe("Max results (default: 100)"),
|
|
36914
36975
|
pageToken: pageTokenParam,
|
|
@@ -36926,7 +36987,7 @@ function registerExtraContactsTools(server) {
|
|
|
36926
36987
|
});
|
|
36927
36988
|
server.registerTool("gog_contacts_other_search", {
|
|
36928
36989
|
description: 'Search "other contacts" \u2014 auto-collected addresses not in your saved contacts.',
|
|
36929
|
-
annotations: { readOnlyHint: true },
|
|
36990
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36930
36991
|
inputSchema: external_exports.object({
|
|
36931
36992
|
query: external_exports.string().describe("Search query"),
|
|
36932
36993
|
max: external_exports.number().optional().describe("Max results (default: 50)"),
|
|
@@ -36939,7 +37000,7 @@ function registerExtraContactsTools(server) {
|
|
|
36939
37000
|
});
|
|
36940
37001
|
server.registerTool("gog_people_raw", {
|
|
36941
37002
|
description: "Dump the raw People API response as JSON (lossless; for scripting and LLM consumption).",
|
|
36942
|
-
annotations: { readOnlyHint: true },
|
|
37003
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
36943
37004
|
inputSchema: external_exports.object({
|
|
36944
37005
|
userId: external_exports.string().describe("Person resource name (people/...) or email"),
|
|
36945
37006
|
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.
|
|
6
|
+
"version": "4.7.1",
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-contacts",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.1",
|
|
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,7 +24,7 @@
|
|
|
24
24
|
"test:coverage": "vitest run --coverage"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@chrischall/mcp-utils": "^
|
|
27
|
+
"@chrischall/mcp-utils": "^3.0.0",
|
|
28
28
|
"@modelcontextprotocol/server": "^2.3.0",
|
|
29
29
|
"zod": "^4.6.1"
|
|
30
30
|
},
|
|
@@ -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
|
+
});
|