gogcli-mcp 2.23.1 → 2.23.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +234 -40
- package/dist/lib.js +243 -41
- package/manifest.json +3 -3
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/gmail-results.ts +228 -0
- package/src/lib.ts +9 -0
- package/src/pagination.ts +108 -0
- package/src/tools/calendar.ts +18 -5
- package/src/tools/classroom.ts +46 -28
- package/src/tools/drive.ts +6 -4
- package/src/tools/gmail.ts +29 -4
- package/src/tools/utils.ts +36 -5
- package/src/worker.ts +1 -1
- package/tests/gmail-results.test.ts +285 -0
- package/tests/page-cursor-contract.test.ts +50 -0
- package/tests/pagination.test.ts +102 -0
- package/tests/tools/calendar.test.ts +51 -0
- package/tests/tools/gmail.test.ts +146 -0
package/dist/lib.js
CHANGED
|
@@ -23431,6 +23431,55 @@ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSource
|
|
|
23431
23431
|
return JSON.stringify(parsed, null, detectIndent(text));
|
|
23432
23432
|
}
|
|
23433
23433
|
|
|
23434
|
+
// src/pagination.ts
|
|
23435
|
+
function detectIndent2(text) {
|
|
23436
|
+
const match = /\n(\s+)\S/.exec(text);
|
|
23437
|
+
return match ? match[1].replace(/\t/g, " ").length : 0;
|
|
23438
|
+
}
|
|
23439
|
+
function stripConsumedPageToken(text) {
|
|
23440
|
+
const trimmed = text.trim();
|
|
23441
|
+
if (trimmed === "" || !trimmed.startsWith("{")) return text;
|
|
23442
|
+
let parsed;
|
|
23443
|
+
try {
|
|
23444
|
+
parsed = JSON.parse(trimmed);
|
|
23445
|
+
} catch {
|
|
23446
|
+
return text;
|
|
23447
|
+
}
|
|
23448
|
+
const obj = parsed;
|
|
23449
|
+
if (obj.nextPageToken !== "") return text;
|
|
23450
|
+
delete obj.nextPageToken;
|
|
23451
|
+
return JSON.stringify(obj, null, detectIndent2(text));
|
|
23452
|
+
}
|
|
23453
|
+
function truncationWarning(returned, count) {
|
|
23454
|
+
const scope = count.total !== void 0 ? `returned ${returned} of ${count.total} matches` : count.atLeast !== void 0 ? `returned ${returned} of at least ${count.atLeast} matches` : `returned ${returned} matches and MORE EXIST beyond this page`;
|
|
23455
|
+
return `INCOMPLETE RESULT SET: ${scope}. Do not report an absence of results based on this response. Page with nextPageToken or narrow the query.`;
|
|
23456
|
+
}
|
|
23457
|
+
function annotateTruncation(out, returned, count) {
|
|
23458
|
+
out.truncated = true;
|
|
23459
|
+
out.returned = returned;
|
|
23460
|
+
if (count.total !== void 0) out.totalMatches = count.total;
|
|
23461
|
+
if (count.atLeast !== void 0) out.totalMatchesAtLeast = count.atLeast;
|
|
23462
|
+
out.warning = truncationWarning(returned, count);
|
|
23463
|
+
}
|
|
23464
|
+
function hasMorePages(parsed) {
|
|
23465
|
+
return typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
|
|
23466
|
+
}
|
|
23467
|
+
function annotateTruncatedList(result, itemsKey) {
|
|
23468
|
+
const first = result.content[0];
|
|
23469
|
+
if (result.isError || first?.type !== "text") return result;
|
|
23470
|
+
let parsed;
|
|
23471
|
+
try {
|
|
23472
|
+
parsed = JSON.parse(first.text);
|
|
23473
|
+
} catch {
|
|
23474
|
+
return result;
|
|
23475
|
+
}
|
|
23476
|
+
const items = parsed[itemsKey];
|
|
23477
|
+
if (!Array.isArray(items) || !hasMorePages(parsed)) return result;
|
|
23478
|
+
const out = { ...parsed };
|
|
23479
|
+
annotateTruncation(out, items.length, {});
|
|
23480
|
+
return rawTextResult(JSON.stringify(out));
|
|
23481
|
+
}
|
|
23482
|
+
|
|
23434
23483
|
// src/tools/utils.ts
|
|
23435
23484
|
var PAYLOAD_INLINE_MAX = 4096;
|
|
23436
23485
|
function payloadArg(inlineFlag, fileFlag, value, ext) {
|
|
@@ -23466,14 +23515,25 @@ var ids = {
|
|
|
23466
23515
|
// People API uses fully-qualified resource names ("people/c123") not bare IDs.
|
|
23467
23516
|
person: external_exports.string().describe("Person resource name (people/...) or email")
|
|
23468
23517
|
};
|
|
23518
|
+
var pageTokenParam = external_exports.string().optional().describe(
|
|
23519
|
+
"Cursor for the NEXT page. Pass back the nextPageToken from a previous response verbatim, keeping the query and max identical: call once, then call again with pageToken=<that value>. A response with NO nextPageToken is the last page."
|
|
23520
|
+
);
|
|
23521
|
+
var pageAliasParam = external_exports.string().optional().describe(
|
|
23522
|
+
"Deprecated alias for pageToken, accepted so existing callers keep working. Use pageToken \u2014 it matches the nextPageToken field in the response."
|
|
23523
|
+
);
|
|
23524
|
+
function resolvePageToken(p) {
|
|
23525
|
+
return p.pageToken ?? p.page;
|
|
23526
|
+
}
|
|
23469
23527
|
var paginationParams = {
|
|
23470
23528
|
max: external_exports.number().int().optional().describe("Max results"),
|
|
23471
|
-
|
|
23529
|
+
pageToken: pageTokenParam,
|
|
23530
|
+
page: pageAliasParam,
|
|
23472
23531
|
all: external_exports.boolean().optional().describe("Fetch all pages")
|
|
23473
23532
|
};
|
|
23474
23533
|
function pushPaginationFlags(args, p) {
|
|
23475
23534
|
if (p.max !== void 0) args.push(`--max=${p.max}`);
|
|
23476
|
-
|
|
23535
|
+
const token = resolvePageToken(p);
|
|
23536
|
+
if (token) args.push(`--page=${token}`);
|
|
23477
23537
|
if (p.all) args.push("--all");
|
|
23478
23538
|
}
|
|
23479
23539
|
function registerRunTool(server, options) {
|
|
@@ -23548,7 +23608,7 @@ ${accounts || "(none)"}${hint}`);
|
|
|
23548
23608
|
async function runOrDiagnose(args, options) {
|
|
23549
23609
|
try {
|
|
23550
23610
|
const raw = await run(args, options);
|
|
23551
|
-
return rawTextResult(options.lossless ? raw : normalizeTimestamps(raw));
|
|
23611
|
+
return rawTextResult(options.lossless ? raw : stripConsumedPageToken(normalizeTimestamps(raw)));
|
|
23552
23612
|
} catch (err) {
|
|
23553
23613
|
return diagnose(err);
|
|
23554
23614
|
}
|
|
@@ -23779,7 +23839,7 @@ function authToolsFor(defaultServices) {
|
|
|
23779
23839
|
// src/tools/calendar.ts
|
|
23780
23840
|
function registerCalendarTools(server) {
|
|
23781
23841
|
server.registerTool("gog_calendar_events", {
|
|
23782
|
-
description:
|
|
23842
|
+
description: 'List calendar events. Filters can be combined (e.g. --from + --to for a range, or --today for just today). gog returns only 10 events by default, so a wide date range is USUALLY INCOMPLETE: raise max, or page with pageToken until the response carries no nextPageToken. A response carrying "truncated": true is an incomplete view \u2014 never conclude an event does not exist from one.',
|
|
23783
23843
|
annotations: { readOnlyHint: true },
|
|
23784
23844
|
inputSchema: {
|
|
23785
23845
|
calendarId: external_exports.string().optional().describe("Calendar ID (default: primary calendar)"),
|
|
@@ -23787,22 +23847,29 @@ function registerCalendarTools(server) {
|
|
|
23787
23847
|
to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language)"),
|
|
23788
23848
|
today: external_exports.boolean().optional().describe("Only show today's events"),
|
|
23789
23849
|
query: external_exports.string().optional().describe("Free text search within events"),
|
|
23790
|
-
|
|
23850
|
+
max: external_exports.number().int().optional().describe("Max events to return. gog defaults to 10, which silently hides the rest \u2014 raise it, or page with pageToken."),
|
|
23851
|
+
pageToken: pageTokenParam,
|
|
23852
|
+
page: pageAliasParam,
|
|
23853
|
+
all: external_exports.boolean().optional().describe('Fetch events from ALL CALENDARS. NOTE: unlike the gmail search tools, this does NOT mean "all pages" \u2014 it widens the calendar set, not the page window. Use pageToken to reach later pages.'),
|
|
23791
23854
|
eventTypes: external_exports.array(external_exports.enum(["default", "birthday", "focus-time", "from-gmail", "out-of-office", "working-location"])).optional().describe("Filter to specific event types (repeatable)"),
|
|
23792
23855
|
timezone: external_exports.string().optional().describe(`Display timezone for event times (IANA name, e.g. America/New_York, or "local" for the system timezone). Default: each event's timezone, then its calendar's timezone.`),
|
|
23793
23856
|
account: accountParam
|
|
23794
23857
|
}
|
|
23795
|
-
}, async ({ calendarId, from, to, today, query, all, eventTypes, timezone, account }) => {
|
|
23858
|
+
}, async ({ calendarId, from, to, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
|
|
23796
23859
|
const args = ["calendar", "events"];
|
|
23797
23860
|
if (calendarId) args.push(calendarId);
|
|
23798
23861
|
if (from) args.push(`--from=${from}`);
|
|
23799
23862
|
if (to) args.push(`--to=${to}`);
|
|
23800
23863
|
if (today) args.push("--today");
|
|
23801
23864
|
if (query) args.push(`--query=${query}`);
|
|
23865
|
+
if (max !== void 0) args.push(`--max=${max}`);
|
|
23866
|
+
const token = resolvePageToken({ pageToken, page });
|
|
23867
|
+
if (token) args.push(`--page=${token}`);
|
|
23802
23868
|
if (all) args.push("--all");
|
|
23803
23869
|
if (eventTypes) for (const t of eventTypes) args.push(`--event-types=${t}`);
|
|
23804
23870
|
if (timezone) args.push(`--timezone=${timezone}`);
|
|
23805
|
-
|
|
23871
|
+
const result = await runOrDiagnose(args, { account });
|
|
23872
|
+
return annotateTruncatedList(result, "events");
|
|
23806
23873
|
});
|
|
23807
23874
|
server.registerTool("gog_calendar_get", {
|
|
23808
23875
|
description: "Get a specific calendar event by ID.",
|
|
@@ -23919,17 +23986,19 @@ function registerClassroomTools(server) {
|
|
|
23919
23986
|
teacher: external_exports.string().optional().describe("Filter by teacher user ID"),
|
|
23920
23987
|
student: external_exports.string().optional().describe("Filter by student user ID"),
|
|
23921
23988
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
23922
|
-
|
|
23989
|
+
pageToken: pageTokenParam,
|
|
23990
|
+
page: pageAliasParam,
|
|
23923
23991
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
23924
23992
|
account: accountParam
|
|
23925
23993
|
}
|
|
23926
|
-
}, async ({ state, teacher, student, max, page, all, account }) => {
|
|
23994
|
+
}, async ({ state, teacher, student, max, pageToken, page, all, account }) => {
|
|
23927
23995
|
const args = ["classroom", "courses", "list"];
|
|
23928
23996
|
if (state) args.push(`--state=${state}`);
|
|
23929
23997
|
if (teacher) args.push(`--teacher=${teacher}`);
|
|
23930
23998
|
if (student) args.push(`--student=${student}`);
|
|
23931
23999
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
23932
|
-
|
|
24000
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24001
|
+
if (token) args.push(`--page=${token}`);
|
|
23933
24002
|
if (all) args.push("--all");
|
|
23934
24003
|
return runOrDiagnose(args, { account });
|
|
23935
24004
|
});
|
|
@@ -23949,14 +24018,16 @@ function registerClassroomTools(server) {
|
|
|
23949
24018
|
inputSchema: {
|
|
23950
24019
|
courseId: external_exports.string().describe("Course ID"),
|
|
23951
24020
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
23952
|
-
|
|
24021
|
+
pageToken: pageTokenParam,
|
|
24022
|
+
page: pageAliasParam,
|
|
23953
24023
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
23954
24024
|
account: accountParam
|
|
23955
24025
|
}
|
|
23956
|
-
}, async ({ courseId, max, page, all, account }) => {
|
|
24026
|
+
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
23957
24027
|
const args = ["classroom", "students", "list", courseId];
|
|
23958
24028
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
23959
|
-
|
|
24029
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24030
|
+
if (token) args.push(`--page=${token}`);
|
|
23960
24031
|
if (all) args.push("--all");
|
|
23961
24032
|
return runOrDiagnose(args, { account });
|
|
23962
24033
|
});
|
|
@@ -23977,14 +24048,16 @@ function registerClassroomTools(server) {
|
|
|
23977
24048
|
inputSchema: {
|
|
23978
24049
|
courseId: external_exports.string().describe("Course ID"),
|
|
23979
24050
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
23980
|
-
|
|
24051
|
+
pageToken: pageTokenParam,
|
|
24052
|
+
page: pageAliasParam,
|
|
23981
24053
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
23982
24054
|
account: accountParam
|
|
23983
24055
|
}
|
|
23984
|
-
}, async ({ courseId, max, page, all, account }) => {
|
|
24056
|
+
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
23985
24057
|
const args = ["classroom", "teachers", "list", courseId];
|
|
23986
24058
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
23987
|
-
|
|
24059
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24060
|
+
if (token) args.push(`--page=${token}`);
|
|
23988
24061
|
if (all) args.push("--all");
|
|
23989
24062
|
return runOrDiagnose(args, { account });
|
|
23990
24063
|
});
|
|
@@ -24007,16 +24080,18 @@ function registerClassroomTools(server) {
|
|
|
24007
24080
|
students: external_exports.boolean().optional().describe("Include students only"),
|
|
24008
24081
|
teachers: external_exports.boolean().optional().describe("Include teachers only"),
|
|
24009
24082
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
24010
|
-
|
|
24083
|
+
pageToken: pageTokenParam,
|
|
24084
|
+
page: pageAliasParam,
|
|
24011
24085
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
24012
24086
|
account: accountParam
|
|
24013
24087
|
}
|
|
24014
|
-
}, async ({ courseId, students, teachers, max, page, all, account }) => {
|
|
24088
|
+
}, async ({ courseId, students, teachers, max, pageToken, page, all, account }) => {
|
|
24015
24089
|
const args = ["classroom", "roster", courseId];
|
|
24016
24090
|
if (students) args.push("--students");
|
|
24017
24091
|
if (teachers) args.push("--teachers");
|
|
24018
24092
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
24019
|
-
|
|
24093
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24094
|
+
if (token) args.push(`--page=${token}`);
|
|
24020
24095
|
if (all) args.push("--all");
|
|
24021
24096
|
return runOrDiagnose(args, { account });
|
|
24022
24097
|
});
|
|
@@ -24029,18 +24104,20 @@ function registerClassroomTools(server) {
|
|
|
24029
24104
|
topic: external_exports.string().optional().describe("Filter by topic ID"),
|
|
24030
24105
|
orderBy: external_exports.string().optional().describe('Sort order (e.g. "updateTime desc")'),
|
|
24031
24106
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
24032
|
-
|
|
24107
|
+
pageToken: pageTokenParam,
|
|
24108
|
+
page: pageAliasParam,
|
|
24033
24109
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
24034
24110
|
scanPages: external_exports.number().optional().describe("Max pages to scan when filtering"),
|
|
24035
24111
|
account: accountParam
|
|
24036
24112
|
}
|
|
24037
|
-
}, async ({ courseId, state, topic, orderBy, max, page, all, scanPages, account }) => {
|
|
24113
|
+
}, async ({ courseId, state, topic, orderBy, max, pageToken, page, all, scanPages, account }) => {
|
|
24038
24114
|
const args = ["classroom", "coursework", "list", courseId];
|
|
24039
24115
|
if (state) args.push(`--state=${state}`);
|
|
24040
24116
|
if (topic) args.push(`--topic=${topic}`);
|
|
24041
24117
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
24042
24118
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
24043
|
-
|
|
24119
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24120
|
+
if (token) args.push(`--page=${token}`);
|
|
24044
24121
|
if (all) args.push("--all");
|
|
24045
24122
|
if (scanPages !== void 0) args.push(`--scan-pages=${scanPages}`);
|
|
24046
24123
|
return runOrDiagnose(args, { account });
|
|
@@ -24066,17 +24143,19 @@ function registerClassroomTools(server) {
|
|
|
24066
24143
|
late: external_exports.enum(["late", "not-late"]).optional().describe("Filter by late status"),
|
|
24067
24144
|
user: external_exports.string().optional().describe("Filter by student user ID"),
|
|
24068
24145
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
24069
|
-
|
|
24146
|
+
pageToken: pageTokenParam,
|
|
24147
|
+
page: pageAliasParam,
|
|
24070
24148
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
24071
24149
|
account: accountParam
|
|
24072
24150
|
}
|
|
24073
|
-
}, async ({ courseId, courseworkId, state, late, user, max, page, all, account }) => {
|
|
24151
|
+
}, async ({ courseId, courseworkId, state, late, user, max, pageToken, page, all, account }) => {
|
|
24074
24152
|
const args = ["classroom", "submissions", "list", courseId, courseworkId];
|
|
24075
24153
|
if (state) args.push(`--state=${state}`);
|
|
24076
24154
|
if (late) args.push(`--late=${late}`);
|
|
24077
24155
|
if (user) args.push(`--user=${user}`);
|
|
24078
24156
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
24079
|
-
|
|
24157
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24158
|
+
if (token) args.push(`--page=${token}`);
|
|
24080
24159
|
if (all) args.push("--all");
|
|
24081
24160
|
return runOrDiagnose(args, { account });
|
|
24082
24161
|
});
|
|
@@ -24153,16 +24232,18 @@ function registerClassroomTools(server) {
|
|
|
24153
24232
|
state: external_exports.string().optional().describe("Filter by announcement state"),
|
|
24154
24233
|
orderBy: external_exports.string().optional().describe("Sort order"),
|
|
24155
24234
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
24156
|
-
|
|
24235
|
+
pageToken: pageTokenParam,
|
|
24236
|
+
page: pageAliasParam,
|
|
24157
24237
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
24158
24238
|
account: accountParam
|
|
24159
24239
|
}
|
|
24160
|
-
}, async ({ courseId, state, orderBy, max, page, all, account }) => {
|
|
24240
|
+
}, async ({ courseId, state, orderBy, max, pageToken, page, all, account }) => {
|
|
24161
24241
|
const args = ["classroom", "announcements", "list", courseId];
|
|
24162
24242
|
if (state) args.push(`--state=${state}`);
|
|
24163
24243
|
if (orderBy) args.push(`--order-by=${orderBy}`);
|
|
24164
24244
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
24165
|
-
|
|
24245
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24246
|
+
if (token) args.push(`--page=${token}`);
|
|
24166
24247
|
if (all) args.push("--all");
|
|
24167
24248
|
return runOrDiagnose(args, { account });
|
|
24168
24249
|
});
|
|
@@ -24198,14 +24279,16 @@ function registerClassroomTools(server) {
|
|
|
24198
24279
|
inputSchema: {
|
|
24199
24280
|
courseId: external_exports.string().describe("Course ID"),
|
|
24200
24281
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
24201
|
-
|
|
24282
|
+
pageToken: pageTokenParam,
|
|
24283
|
+
page: pageAliasParam,
|
|
24202
24284
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
24203
24285
|
account: accountParam
|
|
24204
24286
|
}
|
|
24205
|
-
}, async ({ courseId, max, page, all, account }) => {
|
|
24287
|
+
}, async ({ courseId, max, pageToken, page, all, account }) => {
|
|
24206
24288
|
const args = ["classroom", "topics", "list", courseId];
|
|
24207
24289
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
24208
|
-
|
|
24290
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24291
|
+
if (token) args.push(`--page=${token}`);
|
|
24209
24292
|
if (all) args.push("--all");
|
|
24210
24293
|
return runOrDiagnose(args, { account });
|
|
24211
24294
|
});
|
|
@@ -24227,16 +24310,18 @@ function registerClassroomTools(server) {
|
|
|
24227
24310
|
course: external_exports.string().optional().describe("Filter by course ID"),
|
|
24228
24311
|
user: external_exports.string().optional().describe("Filter by user ID"),
|
|
24229
24312
|
max: external_exports.number().optional().describe("Max results per page"),
|
|
24230
|
-
|
|
24313
|
+
pageToken: pageTokenParam,
|
|
24314
|
+
page: pageAliasParam,
|
|
24231
24315
|
all: external_exports.boolean().optional().describe("Fetch all pages"),
|
|
24232
24316
|
account: accountParam
|
|
24233
24317
|
}
|
|
24234
|
-
}, async ({ course, user, max, page, all, account }) => {
|
|
24318
|
+
}, async ({ course, user, max, pageToken, page, all, account }) => {
|
|
24235
24319
|
const args = ["classroom", "invitations", "list"];
|
|
24236
24320
|
if (course) args.push(`--course=${course}`);
|
|
24237
24321
|
if (user) args.push(`--user=${user}`);
|
|
24238
24322
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
24239
|
-
|
|
24323
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24324
|
+
if (token) args.push(`--page=${token}`);
|
|
24240
24325
|
if (all) args.push("--all");
|
|
24241
24326
|
return runOrDiagnose(args, { account });
|
|
24242
24327
|
});
|
|
@@ -24446,16 +24531,18 @@ function registerDriveTools(server) {
|
|
|
24446
24531
|
inputSchema: {
|
|
24447
24532
|
folderId: external_exports.string().optional().describe("Folder ID to list (default: root)"),
|
|
24448
24533
|
max: external_exports.number().optional().describe("Max results (default: 20)"),
|
|
24449
|
-
|
|
24534
|
+
pageToken: pageTokenParam,
|
|
24535
|
+
page: pageAliasParam,
|
|
24450
24536
|
query: external_exports.string().optional().describe(`Drive query filter (e.g. "name contains 'budget'")`),
|
|
24451
24537
|
allDrives: external_exports.boolean().optional().describe("Include shared drives (default: true). Set false for My Drive only."),
|
|
24452
24538
|
account: accountParam
|
|
24453
24539
|
}
|
|
24454
|
-
}, async ({ folderId, max, page, query, allDrives, account }) => {
|
|
24540
|
+
}, async ({ folderId, max, pageToken, page, query, allDrives, account }) => {
|
|
24455
24541
|
const args = ["drive", "ls"];
|
|
24456
24542
|
if (folderId) args.push(`--parent=${folderId}`);
|
|
24457
24543
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
24458
|
-
|
|
24544
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24545
|
+
if (token) args.push(`--page=${token}`);
|
|
24459
24546
|
if (query) args.push(`--query=${query}`);
|
|
24460
24547
|
if (allDrives === false) args.push("--no-all-drives");
|
|
24461
24548
|
return runOrDiagnose(args, { account });
|
|
@@ -24645,22 +24732,130 @@ function registerDriveTools(server) {
|
|
|
24645
24732
|
registerRunTool(server, { service: "drive", examples: '"copy", "upload", "download", "permissions"' });
|
|
24646
24733
|
}
|
|
24647
24734
|
|
|
24735
|
+
// src/gmail-results.ts
|
|
24736
|
+
function sortKey(item) {
|
|
24737
|
+
for (const raw of [item.internalDateIso, item.date]) {
|
|
24738
|
+
if (typeof raw !== "string" || !raw) continue;
|
|
24739
|
+
const t = Date.parse(raw);
|
|
24740
|
+
if (!Number.isNaN(t)) return t;
|
|
24741
|
+
}
|
|
24742
|
+
return Number.NEGATIVE_INFINITY;
|
|
24743
|
+
}
|
|
24744
|
+
function sortNewestFirst(items) {
|
|
24745
|
+
return [...items].sort((a, b) => {
|
|
24746
|
+
const ka = sortKey(a);
|
|
24747
|
+
const kb = sortKey(b);
|
|
24748
|
+
return ka === kb ? 0 : kb - ka;
|
|
24749
|
+
});
|
|
24750
|
+
}
|
|
24751
|
+
var COUNT_PROBE_PAGE_SIZE = 500;
|
|
24752
|
+
async function countMatches(method, itemsKey, query, account) {
|
|
24753
|
+
try {
|
|
24754
|
+
const params = JSON.stringify({
|
|
24755
|
+
userId: "me",
|
|
24756
|
+
q: query,
|
|
24757
|
+
maxResults: COUNT_PROBE_PAGE_SIZE,
|
|
24758
|
+
fields: `${itemsKey}/id,nextPageToken`
|
|
24759
|
+
});
|
|
24760
|
+
const raw = await run(["api", "call", "gmail", "v1", method, `--params=${params}`], { account });
|
|
24761
|
+
const parsed = JSON.parse(raw);
|
|
24762
|
+
const items = parsed[itemsKey];
|
|
24763
|
+
if (!Array.isArray(items)) return {};
|
|
24764
|
+
const more = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
|
|
24765
|
+
return more ? { atLeast: items.length } : { total: items.length };
|
|
24766
|
+
} catch {
|
|
24767
|
+
return {};
|
|
24768
|
+
}
|
|
24769
|
+
}
|
|
24770
|
+
async function finalizeGmailSearch(result, options) {
|
|
24771
|
+
const { itemsKey, method, query, account, queryIsExact = true } = options;
|
|
24772
|
+
const first = result.content[0];
|
|
24773
|
+
if (result.isError || first?.type !== "text") return result;
|
|
24774
|
+
let parsed;
|
|
24775
|
+
try {
|
|
24776
|
+
parsed = JSON.parse(first.text);
|
|
24777
|
+
} catch {
|
|
24778
|
+
return result;
|
|
24779
|
+
}
|
|
24780
|
+
const items = parsed[itemsKey];
|
|
24781
|
+
if (!Array.isArray(items)) return result;
|
|
24782
|
+
const sorted = sortNewestFirst(items);
|
|
24783
|
+
const out = { ...parsed, [itemsKey]: sorted };
|
|
24784
|
+
if (hasMorePages(parsed)) {
|
|
24785
|
+
const count = queryIsExact ? await countMatches(method, itemsKey, query, account) : {};
|
|
24786
|
+
annotateTruncation(out, sorted.length, count);
|
|
24787
|
+
}
|
|
24788
|
+
return rawTextResult(JSON.stringify(out));
|
|
24789
|
+
}
|
|
24790
|
+
async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
|
|
24791
|
+
const merged = [];
|
|
24792
|
+
let base;
|
|
24793
|
+
let token = startToken;
|
|
24794
|
+
for (let pages = 0; pages < maxPages; pages++) {
|
|
24795
|
+
const result = await runPage(token);
|
|
24796
|
+
const parsed = parsePage(result, itemsKey);
|
|
24797
|
+
if (parsed === void 0) {
|
|
24798
|
+
return base === void 0 ? result : finish(base, itemsKey, merged, token);
|
|
24799
|
+
}
|
|
24800
|
+
base = parsed;
|
|
24801
|
+
merged.push(...parsed[itemsKey]);
|
|
24802
|
+
token = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "" ? parsed.nextPageToken : void 0;
|
|
24803
|
+
if (token === void 0) break;
|
|
24804
|
+
}
|
|
24805
|
+
return finish(base, itemsKey, merged, token);
|
|
24806
|
+
}
|
|
24807
|
+
function parsePage(result, itemsKey) {
|
|
24808
|
+
const first = result.content[0];
|
|
24809
|
+
if (result.isError || first?.type !== "text") return void 0;
|
|
24810
|
+
let parsed;
|
|
24811
|
+
try {
|
|
24812
|
+
parsed = JSON.parse(first.text);
|
|
24813
|
+
} catch {
|
|
24814
|
+
return void 0;
|
|
24815
|
+
}
|
|
24816
|
+
if (parsed === null || typeof parsed !== "object") return void 0;
|
|
24817
|
+
const obj = parsed;
|
|
24818
|
+
return Array.isArray(obj[itemsKey]) ? obj : void 0;
|
|
24819
|
+
}
|
|
24820
|
+
function finish(base, itemsKey, merged, token) {
|
|
24821
|
+
const out = { ...base, [itemsKey]: merged };
|
|
24822
|
+
if (token === void 0) delete out.nextPageToken;
|
|
24823
|
+
else out.nextPageToken = token;
|
|
24824
|
+
return rawTextResult(JSON.stringify(out));
|
|
24825
|
+
}
|
|
24826
|
+
|
|
24648
24827
|
// src/tools/gmail.ts
|
|
24649
24828
|
function registerGmailTools(server) {
|
|
24650
24829
|
server.registerTool("gog_gmail_search", {
|
|
24651
|
-
description:
|
|
24830
|
+
description: 'Search Gmail threads using Gmail query syntax (e.g. "from:alice subject:invoice is:unread"). The query is passed verbatim to Gmail; a bare name token (from:alison) matches per Gmail\'s own heuristics, a full address (from:alison@example.com) is exact. To match a contact across several addresses, OR them: from:(a@x.com OR b@y.com). Results are ALWAYS newest-first by Gmail\'s internalDate \u2014 the wrapper sorts them, so the first result is the most recent match and a recent message can never be buried below older ones. IMPORTANT \u2014 a response carrying "truncated": true is an INCOMPLETE view of the matches: NEVER report that a message does not exist, or that there is no such mail, on the strength of one. Page through it (pass nextPageToken back as `pageToken`), set maxPages to walk several pages in one call, or narrow the query, and only then draw a conclusion. If you already know the thread, do not search for it at all \u2014 read it directly with gog_gmail_thread_get, which returns the whole thread and cannot be truncated or mis-ranked.',
|
|
24652
24831
|
annotations: { readOnlyHint: true },
|
|
24653
24832
|
inputSchema: {
|
|
24654
24833
|
query: external_exports.string().describe("Gmail search query"),
|
|
24655
24834
|
max: external_exports.number().int().optional().describe("Max results to return (default: 10)"),
|
|
24835
|
+
pageToken: pageTokenParam,
|
|
24836
|
+
page: pageAliasParam,
|
|
24837
|
+
maxPages: external_exports.number().int().positive().max(20).optional().describe('Walk up to this many pages in ONE call and merge the results, instead of returning a single page. Use it for existence questions ("is there any mail matching X?"), which a single page cannot answer. Stops early at the last page; if pages remain when the cap is hit the response is still marked truncated. Prefer this over all=true, which is unbounded.'),
|
|
24838
|
+
all: external_exports.boolean().optional().describe('Fetch every page instead of one. Removes truncation entirely, at the cost of one API round-trip per page \u2014 the reliable way to answer "does any message match?" for a query with few expected hits.'),
|
|
24656
24839
|
fromContact: external_exports.string().optional().describe("Resolve a Google Contact (name or email) to its addresses and AND a from:(addr OR addr) clause onto the query \u2014 saves looking the contact up first when you only know who, not which address."),
|
|
24657
24840
|
account: accountParam
|
|
24658
24841
|
}
|
|
24659
|
-
}, async ({ query, max, fromContact, account }) => {
|
|
24842
|
+
}, async ({ query, max, pageToken, page, maxPages, all, fromContact, account }) => {
|
|
24660
24843
|
const args = ["gmail", "search", query];
|
|
24661
24844
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
24845
|
+
if (all) args.push("--all");
|
|
24662
24846
|
if (fromContact) args.push(`--from-contact=${fromContact}`);
|
|
24663
|
-
|
|
24847
|
+
const runPage = (tok) => runOrDiagnose(tok ? [...args, `--page=${tok}`] : args, { account });
|
|
24848
|
+
const token = resolvePageToken({ pageToken, page });
|
|
24849
|
+
const result = maxPages !== void 0 ? await fetchGmailPages(runPage, "threads", maxPages, token) : await runPage(token);
|
|
24850
|
+
return finalizeGmailSearch(result, {
|
|
24851
|
+
itemsKey: "threads",
|
|
24852
|
+
method: "users.threads.list",
|
|
24853
|
+
query,
|
|
24854
|
+
account,
|
|
24855
|
+
// --from-contact is expanded INSIDE gog, against the People API, so the
|
|
24856
|
+
// query Gmail actually saw is not the one we hold here.
|
|
24857
|
+
queryIsExact: !fromContact
|
|
24858
|
+
});
|
|
24664
24859
|
});
|
|
24665
24860
|
server.registerTool("gog_gmail_get", {
|
|
24666
24861
|
description: "Get a Gmail message by ID.",
|
|
@@ -25025,7 +25220,7 @@ function registerTasksTools(server) {
|
|
|
25025
25220
|
}
|
|
25026
25221
|
|
|
25027
25222
|
// src/server.ts
|
|
25028
|
-
var VERSION = true ? "2.23.
|
|
25223
|
+
var VERSION = true ? "2.23.2" : "0.0.0";
|
|
25029
25224
|
var BASE_TOOL_REGISTRARS = [
|
|
25030
25225
|
registerApiTools,
|
|
25031
25226
|
registerAuthTools,
|
|
@@ -25529,12 +25724,17 @@ export {
|
|
|
25529
25724
|
PAYLOAD_INLINE_MAX,
|
|
25530
25725
|
VERSION,
|
|
25531
25726
|
accountParam,
|
|
25727
|
+
annotateTruncatedList,
|
|
25532
25728
|
authToolsFor,
|
|
25533
25729
|
diagnose,
|
|
25534
25730
|
errorText,
|
|
25731
|
+
fetchGmailPages,
|
|
25732
|
+
finalizeGmailSearch,
|
|
25535
25733
|
ids,
|
|
25536
25734
|
isGogFileArg,
|
|
25537
25735
|
normalizeTimestamps,
|
|
25736
|
+
pageAliasParam,
|
|
25737
|
+
pageTokenParam,
|
|
25538
25738
|
paginationParams,
|
|
25539
25739
|
payloadArg,
|
|
25540
25740
|
pushPaginationFlags,
|
|
@@ -25550,9 +25750,11 @@ export {
|
|
|
25550
25750
|
registerSheetsTools,
|
|
25551
25751
|
registerSlidesTools,
|
|
25552
25752
|
registerTasksTools,
|
|
25753
|
+
resolvePageToken,
|
|
25553
25754
|
run,
|
|
25554
25755
|
runBinary,
|
|
25555
25756
|
runExecutor,
|
|
25556
25757
|
runOrDiagnose,
|
|
25758
|
+
stripConsumedPageToken,
|
|
25557
25759
|
useRemoteGogRunner
|
|
25558
25760
|
};
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp",
|
|
5
5
|
"display_name": "gogcli",
|
|
6
|
-
"version": "2.23.
|
|
6
|
+
"version": "2.23.2",
|
|
7
7
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
"name": "gog_gmail_search",
|
|
105
|
-
"description": "Search Gmail threads
|
|
105
|
+
"description": "Search Gmail threads using Gmail query syntax; newest-first, and flags a truncated result set"
|
|
106
106
|
},
|
|
107
107
|
{
|
|
108
108
|
"name": "gog_gmail_get",
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
"name": "gog_calendar_events",
|
|
121
|
-
"description": "List calendar events
|
|
121
|
+
"description": "List calendar events; paginated (gog returns only 10 by default) and flags a truncated range"
|
|
122
122
|
},
|
|
123
123
|
{
|
|
124
124
|
"name": "gog_calendar_get",
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"source": "github",
|
|
8
8
|
"subfolder": "packages/gogcli-mcp"
|
|
9
9
|
},
|
|
10
|
-
"version": "2.23.
|
|
10
|
+
"version": "2.23.2",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp",
|
|
15
|
-
"version": "2.23.
|
|
15
|
+
"version": "2.23.2",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|