gogcli-mcp-calendar 2.7.1 → 2.18.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/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-calendar",
5
5
  "display_name": "gogcli (Calendar)",
6
- "version": "2.7.1",
6
+ "version": "2.18.0",
7
7
  "description": "Extended Google Calendar for Claude via gogcli — auth + Calendar events + Meet space management",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -58,6 +58,18 @@
58
58
  "name": "gog_auth_add",
59
59
  "description": "Authorize a Google account via browser-based OAuth"
60
60
  },
61
+ {
62
+ "name": "gog_auth_health",
63
+ "description": "Live per-account token health: validity, age, and pre-expiry warnings (detects invalid_grant)"
64
+ },
65
+ {
66
+ "name": "gog_auth_add_url",
67
+ "description": "Begin remote/headless OAuth (step 1): return a sign-in URL to open in any browser"
68
+ },
69
+ {
70
+ "name": "gog_auth_add_complete",
71
+ "description": "Complete remote/headless OAuth (step 2): exchange the pasted redirect URL and store the token"
72
+ },
61
73
  {
62
74
  "name": "gog_auth_list",
63
75
  "description": "List all Google accounts stored in gogcli"
@@ -110,6 +122,10 @@
110
122
  "name": "gog_calendar_search",
111
123
  "description": "Full-text search for events with optional time filters"
112
124
  },
125
+ {
126
+ "name": "gog_calendar_changed",
127
+ "description": "List most recently changed events (including cancellations) across calendars"
128
+ },
113
129
  {
114
130
  "name": "gog_calendar_freebusy",
115
131
  "description": "Query free/busy intervals for one or more calendars"
@@ -130,6 +146,14 @@
130
146
  "name": "gog_calendar_out_of_office",
131
147
  "description": "Create an Out of Office event that auto-declines invitations"
132
148
  },
149
+ {
150
+ "name": "gog_calendar_unsubscribe",
151
+ "description": "Remove a calendar from your calendar list"
152
+ },
153
+ {
154
+ "name": "gog_calendar_delete_calendar",
155
+ "description": "Delete an owned secondary calendar"
156
+ },
133
157
  {
134
158
  "name": "gog_meet_create",
135
159
  "description": "Create a Google Meet space"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-calendar",
3
- "version": "2.7.1",
3
+ "version": "2.18.0",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-calendar",
5
5
  "description": "Extended Google Calendar + Meet MCP server via gogcli — auth + Calendar events + Meet space management",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -19,11 +19,12 @@
19
19
  "build": "tsc --noEmit && npm run bundle",
20
20
  "bundle": "node ../../scripts/bundle.js src/index.ts dist/index.js",
21
21
  "typecheck": "tsc --noEmit",
22
- "test": "vitest run",
22
+ "test": "vitest run --coverage",
23
23
  "test:watch": "vitest",
24
24
  "test:coverage": "vitest run --coverage"
25
25
  },
26
26
  "dependencies": {
27
+ "@chrischall/mcp-utils": "^0.13.3",
27
28
  "@modelcontextprotocol/sdk": "^1.29.0",
28
29
  "zod": "^4.4.3"
29
30
  },
package/src/index.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { createServer, registerAuthTools, registerCalendarTools } from '../../gogcli-mcp/src/lib.js';
2
+ import { runMcp } from '@chrischall/mcp-utils';
3
+ import { VERSION, authToolsFor, registerCalendarTools } from '../../gogcli-mcp/src/lib.js';
4
4
  import { registerExtraCalendarTools } from './tools/calendar-extra.js';
5
5
 
6
- const server = createServer({ name: 'gogcli-calendar' });
7
- registerAuthTools(server);
8
- registerCalendarTools(server);
9
- registerExtraCalendarTools(server);
10
-
11
- const transport = new StdioServerTransport();
12
- await server.connect(transport);
6
+ await runMcp({
7
+ name: 'gogcli-calendar',
8
+ version: VERSION,
9
+ tools: [authToolsFor('calendar'), registerCalendarTools, registerExtraCalendarTools],
10
+ });
@@ -54,7 +54,7 @@ export function registerExtraCalendarTools(server: McpServer): void {
54
54
  account: accountParam,
55
55
  },
56
56
  }, async ({ meetingCode, account }) => {
57
- return runOrDiagnose(['meet', 'end', meetingCode], { account });
57
+ return runOrDiagnose(['meet', 'end', meetingCode, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
58
58
  });
59
59
 
60
60
  server.registerTool('gog_meet_history', {
@@ -159,6 +159,27 @@ export function registerExtraCalendarTools(server: McpServer): void {
159
159
  return runOrDiagnose(args, { account });
160
160
  });
161
161
 
162
+ server.registerTool('gog_calendar_changed', {
163
+ description: 'List most recently changed events (including cancellations/deletions) across one or more calendars, ordered by last-modification time. Requires gog >= 0.31.1.',
164
+ annotations: { readOnlyHint: true },
165
+ inputSchema: {
166
+ calendarId: z.string().optional().describe('Calendar ID (default: primary)'),
167
+ calendarIds: z.string().optional().describe('Comma-separated calendar IDs, names, or indices'),
168
+ since: z.string().optional().describe('Lower bound for last-modification time (RFC3339, date, or Go duration like 24h, 168h; default: 720h / 30 days). Google rejects windows too far in the past (410 updatedMinTooLongAgo) — retry with a shorter duration if that happens'),
169
+ max: z.number().optional().describe('Max results (default: 10)'),
170
+ all: z.boolean().optional().describe('Fetch from all calendars'),
171
+ account: accountParam,
172
+ },
173
+ }, async ({ calendarId, calendarIds, since, max, all, account }) => {
174
+ const args = ['calendar', 'changed'];
175
+ if (calendarId) args.push(calendarId);
176
+ if (calendarIds) args.push(`--calendars=${calendarIds}`);
177
+ if (since) args.push(`--since=${since}`);
178
+ if (max !== undefined) args.push(`--max=${max}`);
179
+ if (all) args.push('--all');
180
+ return runOrDiagnose(args, { account });
181
+ });
182
+
162
183
  server.registerTool('gog_calendar_freebusy', {
163
184
  description: 'Query free/busy intervals for one or more calendars over a time window.',
164
185
  annotations: { readOnlyHint: true },
@@ -245,6 +266,28 @@ export function registerExtraCalendarTools(server: McpServer): void {
245
266
  return runOrDiagnose(args, { account });
246
267
  });
247
268
 
269
+ server.registerTool('gog_calendar_unsubscribe', {
270
+ description: 'Remove a calendar from your calendar list (the underlying calendar is not deleted — you can re-subscribe). For deleting a secondary calendar you own, use gog_calendar_delete_calendar.',
271
+ annotations: { destructiveHint: true },
272
+ inputSchema: {
273
+ calendarId: z.string().describe('Calendar ID or alias to remove from your calendar list'),
274
+ account: accountParam,
275
+ },
276
+ }, async ({ calendarId, account }) => {
277
+ return runOrDiagnose(['calendar', 'unsubscribe', calendarId], { account });
278
+ });
279
+
280
+ server.registerTool('gog_calendar_delete_calendar', {
281
+ description: 'Permanently delete an owned secondary calendar and all its events. Cannot delete your primary calendar. To merely remove a calendar you do not own from your list, use gog_calendar_unsubscribe.',
282
+ annotations: { destructiveHint: true },
283
+ inputSchema: {
284
+ calendarId: z.string().describe('Owned secondary calendar ID or alias'),
285
+ account: accountParam,
286
+ },
287
+ }, async ({ calendarId, account }) => {
288
+ return runOrDiagnose(['calendar', 'delete-calendar', calendarId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
289
+ });
290
+
248
291
  server.registerTool('gog_meet_participants', {
249
292
  description: 'List participants from the latest (or a specific) Meet call.',
250
293
  annotations: { readOnlyHint: true },
@@ -1,7 +1,8 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { registerExtraCalendarTools } from '../../src/tools/calendar-extra.js';
3
3
  import * as lib from '../../../gogcli-mcp/src/lib.js';
4
- import { setupHandlers, toText, type ToolHandler } from '../../../gogcli-mcp/tests/helpers/test-harness.js';
4
+ import { createTestHarness, type TestHarness } from '@chrischall/mcp-utils/test';
5
+ import { rawTextResult } from '@chrischall/mcp-utils';
5
6
 
6
7
  vi.mock('../../../gogcli-mcp/src/lib.js', async (importOriginal) => {
7
8
  const actual = await importOriginal<typeof lib>();
@@ -11,22 +12,22 @@ vi.mock('../../../gogcli-mcp/src/lib.js', async (importOriginal) => {
11
12
  };
12
13
  });
13
14
 
14
- let handlers: Map<string, ToolHandler>;
15
+ let harness: TestHarness;
15
16
 
16
- beforeEach(() => {
17
+ beforeEach(async () => {
17
18
  vi.clearAllMocks();
18
- vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
19
- handlers = setupHandlers(registerExtraCalendarTools);
19
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
20
+ harness = await createTestHarness(registerExtraCalendarTools);
20
21
  });
21
22
 
22
23
  describe('gog_meet_create', () => {
23
24
  it('calls runOrDiagnose with no flags', async () => {
24
- await handlers.get('gog_meet_create')!({});
25
+ await harness.callTool('gog_meet_create', {});
25
26
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['meet', 'create'], { account: undefined });
26
27
  });
27
28
 
28
29
  it('passes --access and --open when provided', async () => {
29
- await handlers.get('gog_meet_create')!({ access: 'open', open: true });
30
+ await harness.callTool('gog_meet_create', { access: 'open', open: true });
30
31
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
31
32
  ['meet', 'create', '--access=open', '--open'],
32
33
  { account: undefined },
@@ -34,21 +35,21 @@ describe('gog_meet_create', () => {
34
35
  });
35
36
 
36
37
  it('omits --open when false', async () => {
37
- await handlers.get('gog_meet_create')!({ open: false });
38
+ await harness.callTool('gog_meet_create', { open: false });
38
39
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['meet', 'create'], { account: undefined });
39
40
  });
40
41
  });
41
42
 
42
43
  describe('gog_meet_get', () => {
43
44
  it('calls runOrDiagnose with meetingCode', async () => {
44
- await handlers.get('gog_meet_get')!({ meetingCode: 'abc-defg-hij' });
45
+ await harness.callTool('gog_meet_get', { meetingCode: 'abc-defg-hij' });
45
46
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['meet', 'get', 'abc-defg-hij'], { account: undefined });
46
47
  });
47
48
  });
48
49
 
49
50
  describe('gog_meet_update', () => {
50
51
  it('calls runOrDiagnose with meetingCode and --access', async () => {
51
- await handlers.get('gog_meet_update')!({ meetingCode: 'abc-defg-hij', access: 'restricted' });
52
+ await harness.callTool('gog_meet_update', { meetingCode: 'abc-defg-hij', access: 'restricted' });
52
53
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
53
54
  ['meet', 'update', 'abc-defg-hij', '--access=restricted'],
54
55
  { account: undefined },
@@ -56,26 +57,26 @@ describe('gog_meet_update', () => {
56
57
  });
57
58
 
58
59
  it('omits --access when not provided', async () => {
59
- await handlers.get('gog_meet_update')!({ meetingCode: 'abc-defg-hij' });
60
+ await harness.callTool('gog_meet_update', { meetingCode: 'abc-defg-hij' });
60
61
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['meet', 'update', 'abc-defg-hij'], { account: undefined });
61
62
  });
62
63
  });
63
64
 
64
65
  describe('gog_meet_end', () => {
65
66
  it('calls runOrDiagnose with meetingCode', async () => {
66
- await handlers.get('gog_meet_end')!({ meetingCode: 'abc-defg-hij' });
67
- expect(lib.runOrDiagnose).toHaveBeenCalledWith(['meet', 'end', 'abc-defg-hij'], { account: undefined });
67
+ await harness.callTool('gog_meet_end', { meetingCode: 'abc-defg-hij' });
68
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['meet', 'end', 'abc-defg-hij', '--force'], { account: undefined });
68
69
  });
69
70
  });
70
71
 
71
72
  describe('gog_meet_history', () => {
72
73
  it('calls runOrDiagnose with meetingCode', async () => {
73
- await handlers.get('gog_meet_history')!({ meetingCode: 'abc-defg-hij' });
74
+ await harness.callTool('gog_meet_history', { meetingCode: 'abc-defg-hij' });
74
75
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['meet', 'history', 'abc-defg-hij'], { account: undefined });
75
76
  });
76
77
 
77
78
  it('passes pagination flags', async () => {
78
- await handlers.get('gog_meet_history')!({
79
+ await harness.callTool('gog_meet_history', {
79
80
  meetingCode: 'abc-defg-hij',
80
81
  max: 50,
81
82
  page: 'tok',
@@ -88,7 +89,7 @@ describe('gog_meet_history', () => {
88
89
  });
89
90
 
90
91
  it('omits --all when false', async () => {
91
- await handlers.get('gog_meet_history')!({ meetingCode: 'abc-defg-hij', all: false });
92
+ await harness.callTool('gog_meet_history', { meetingCode: 'abc-defg-hij', all: false });
92
93
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['meet', 'history', 'abc-defg-hij'], { account: undefined });
93
94
  });
94
95
  });
@@ -97,7 +98,7 @@ describe('gog_meet_history', () => {
97
98
 
98
99
  describe('gog_zoom_auth_setup', () => {
99
100
  it('passes credentials and uses default alias when omitted', async () => {
100
- await handlers.get('gog_zoom_auth_setup')!({
101
+ await harness.callTool('gog_zoom_auth_setup', {
101
102
  accountId: 'acct',
102
103
  clientId: 'cid',
103
104
  clientSecret: 'csecret',
@@ -112,7 +113,7 @@ describe('gog_zoom_auth_setup', () => {
112
113
  });
113
114
 
114
115
  it('passes --alias and --skip-validate when provided', async () => {
115
- await handlers.get('gog_zoom_auth_setup')!({
116
+ await harness.callTool('gog_zoom_auth_setup', {
116
117
  accountId: 'acct', clientId: 'cid', clientSecret: 'csecret',
117
118
  alias: 'work', skipValidate: true,
118
119
  });
@@ -130,12 +131,12 @@ describe('gog_zoom_auth_setup', () => {
130
131
 
131
132
  describe('gog_zoom_auth_doctor', () => {
132
133
  it('validates default alias', async () => {
133
- await handlers.get('gog_zoom_auth_doctor')!({});
134
+ await harness.callTool('gog_zoom_auth_doctor', {});
134
135
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['zoom', 'auth', 'doctor'], { account: undefined });
135
136
  });
136
137
 
137
138
  it('passes --alias when provided', async () => {
138
- await handlers.get('gog_zoom_auth_doctor')!({ alias: 'work' });
139
+ await harness.callTool('gog_zoom_auth_doctor', { alias: 'work' });
139
140
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
140
141
  ['zoom', 'auth', 'doctor', '--alias=work'],
141
142
  { account: undefined },
@@ -147,12 +148,12 @@ describe('gog_zoom_auth_doctor', () => {
147
148
 
148
149
  describe('gog_calendar_calendars', () => {
149
150
  it('calls runOrDiagnose with no flags', async () => {
150
- await handlers.get('gog_calendar_calendars')!({});
151
+ await harness.callTool('gog_calendar_calendars', {});
151
152
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'calendars'], { account: undefined });
152
153
  });
153
154
 
154
155
  it('passes pagination flags', async () => {
155
- await handlers.get('gog_calendar_calendars')!({ max: 50, page: 'tok', all: true });
156
+ await harness.callTool('gog_calendar_calendars', { max: 50, page: 'tok', all: true });
156
157
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
157
158
  ['calendar', 'calendars', '--max=50', '--page=tok', '--all'],
158
159
  { account: undefined },
@@ -160,19 +161,19 @@ describe('gog_calendar_calendars', () => {
160
161
  });
161
162
 
162
163
  it('omits --all when false', async () => {
163
- await handlers.get('gog_calendar_calendars')!({ all: false });
164
+ await harness.callTool('gog_calendar_calendars', { all: false });
164
165
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'calendars'], { account: undefined });
165
166
  });
166
167
  });
167
168
 
168
169
  describe('gog_calendar_search', () => {
169
170
  it('calls runOrDiagnose with just the query', async () => {
170
- await handlers.get('gog_calendar_search')!({ query: 'standup' });
171
+ await harness.callTool('gog_calendar_search', { query: 'standup' });
171
172
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'search', 'standup'], { account: undefined });
172
173
  });
173
174
 
174
175
  it('passes all filter flags when provided', async () => {
175
- await handlers.get('gog_calendar_search')!({
176
+ await harness.callTool('gog_calendar_search', {
176
177
  query: 'standup',
177
178
  from: 'today',
178
179
  to: 'tomorrow',
@@ -197,16 +198,43 @@ describe('gog_calendar_search', () => {
197
198
  });
198
199
 
199
200
  it('omits boolean flags when false', async () => {
200
- await handlers.get('gog_calendar_search')!({
201
+ await harness.callTool('gog_calendar_search', {
201
202
  query: 'standup', today: false, tomorrow: false, week: false,
202
203
  });
203
204
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'search', 'standup'], { account: undefined });
204
205
  });
205
206
  });
206
207
 
208
+ describe('gog_calendar_changed', () => {
209
+ it('calls runOrDiagnose with no flags by default', async () => {
210
+ await harness.callTool('gog_calendar_changed', {});
211
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'changed'], { account: undefined });
212
+ });
213
+
214
+ it('passes calendarId positionally and all flags', async () => {
215
+ await harness.callTool('gog_calendar_changed', {
216
+ calendarId: 'primary',
217
+ calendarIds: 'work,personal',
218
+ since: '48h',
219
+ max: 25,
220
+ all: true,
221
+ account: 'me@example.com',
222
+ });
223
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
224
+ ['calendar', 'changed', 'primary', '--calendars=work,personal', '--since=48h', '--max=25', '--all'],
225
+ { account: 'me@example.com' },
226
+ );
227
+ });
228
+
229
+ it('omits --all when false', async () => {
230
+ await harness.callTool('gog_calendar_changed', { all: false });
231
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'changed'], { account: undefined });
232
+ });
233
+ });
234
+
207
235
  describe('gog_calendar_freebusy', () => {
208
236
  it('calls runOrDiagnose with required from/to only', async () => {
209
- await handlers.get('gog_calendar_freebusy')!({ from: 'A', to: 'B' });
237
+ await harness.callTool('gog_calendar_freebusy', { from: 'A', to: 'B' });
210
238
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
211
239
  ['calendar', 'freebusy', '--from=A', '--to=B'],
212
240
  { account: undefined },
@@ -214,7 +242,7 @@ describe('gog_calendar_freebusy', () => {
214
242
  });
215
243
 
216
244
  it('passes calendarIds and --all when provided', async () => {
217
- await handlers.get('gog_calendar_freebusy')!({
245
+ await harness.callTool('gog_calendar_freebusy', {
218
246
  from: 'A', to: 'B', calendarIds: 'primary,team@x.com', all: true,
219
247
  });
220
248
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
@@ -224,7 +252,7 @@ describe('gog_calendar_freebusy', () => {
224
252
  });
225
253
 
226
254
  it('omits --all when false', async () => {
227
- await handlers.get('gog_calendar_freebusy')!({ from: 'A', to: 'B', all: false });
255
+ await harness.callTool('gog_calendar_freebusy', { from: 'A', to: 'B', all: false });
228
256
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
229
257
  ['calendar', 'freebusy', '--from=A', '--to=B'],
230
258
  { account: undefined },
@@ -234,19 +262,19 @@ describe('gog_calendar_freebusy', () => {
234
262
 
235
263
  describe('gog_calendar_colors', () => {
236
264
  it('calls runOrDiagnose with the colors subcommand', async () => {
237
- await handlers.get('gog_calendar_colors')!({});
265
+ await harness.callTool('gog_calendar_colors', {});
238
266
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'colors'], { account: undefined });
239
267
  });
240
268
  });
241
269
 
242
270
  describe('gog_calendar_acl', () => {
243
271
  it('calls runOrDiagnose with calendarId only', async () => {
244
- await handlers.get('gog_calendar_acl')!({ calendarId: 'primary' });
272
+ await harness.callTool('gog_calendar_acl', { calendarId: 'primary' });
245
273
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'acl', 'primary'], { account: undefined });
246
274
  });
247
275
 
248
276
  it('passes pagination flags', async () => {
249
- await handlers.get('gog_calendar_acl')!({ calendarId: 'primary', max: 25, page: 'tok', all: true });
277
+ await harness.callTool('gog_calendar_acl', { calendarId: 'primary', max: 25, page: 'tok', all: true });
250
278
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
251
279
  ['calendar', 'acl', 'primary', '--max=25', '--page=tok', '--all'],
252
280
  { account: undefined },
@@ -254,14 +282,14 @@ describe('gog_calendar_acl', () => {
254
282
  });
255
283
 
256
284
  it('omits --all when false', async () => {
257
- await handlers.get('gog_calendar_acl')!({ calendarId: 'primary', all: false });
285
+ await harness.callTool('gog_calendar_acl', { calendarId: 'primary', all: false });
258
286
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'acl', 'primary'], { account: undefined });
259
287
  });
260
288
  });
261
289
 
262
290
  describe('gog_calendar_move', () => {
263
291
  it('calls runOrDiagnose with the three positionals', async () => {
264
- await handlers.get('gog_calendar_move')!({
292
+ await harness.callTool('gog_calendar_move', {
265
293
  calendarId: 'primary', eventId: 'ev1', destinationCalendarId: 'team@x.com',
266
294
  });
267
295
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
@@ -271,7 +299,7 @@ describe('gog_calendar_move', () => {
271
299
  });
272
300
 
273
301
  it('passes --send-updates when provided', async () => {
274
- await handlers.get('gog_calendar_move')!({
302
+ await harness.callTool('gog_calendar_move', {
275
303
  calendarId: 'primary', eventId: 'ev1', destinationCalendarId: 'team@x.com',
276
304
  sendUpdates: 'all',
277
305
  });
@@ -284,7 +312,7 @@ describe('gog_calendar_move', () => {
284
312
 
285
313
  describe('gog_calendar_out_of_office', () => {
286
314
  it('calls runOrDiagnose with required from/to only', async () => {
287
- await handlers.get('gog_calendar_out_of_office')!({ from: '2026-06-01', to: '2026-06-05' });
315
+ await harness.callTool('gog_calendar_out_of_office', { from: '2026-06-01', to: '2026-06-05' });
288
316
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
289
317
  ['calendar', 'out-of-office', '--from=2026-06-01', '--to=2026-06-05'],
290
318
  { account: undefined },
@@ -292,7 +320,7 @@ describe('gog_calendar_out_of_office', () => {
292
320
  });
293
321
 
294
322
  it('passes calendarId and all optional flags when provided', async () => {
295
- await handlers.get('gog_calendar_out_of_office')!({
323
+ await harness.callTool('gog_calendar_out_of_office', {
296
324
  from: '2026-06-01', to: '2026-06-05',
297
325
  calendarId: 'primary',
298
326
  summary: 'Vacation',
@@ -312,7 +340,7 @@ describe('gog_calendar_out_of_office', () => {
312
340
  });
313
341
 
314
342
  it('omits --all-day when false', async () => {
315
- await handlers.get('gog_calendar_out_of_office')!({ from: '2026-06-01', to: '2026-06-05', allDay: false });
343
+ await harness.callTool('gog_calendar_out_of_office', { from: '2026-06-01', to: '2026-06-05', allDay: false });
316
344
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
317
345
  ['calendar', 'out-of-office', '--from=2026-06-01', '--to=2026-06-05'],
318
346
  { account: undefined },
@@ -322,7 +350,7 @@ describe('gog_calendar_out_of_office', () => {
322
350
 
323
351
  describe('gog_meet_participants', () => {
324
352
  it('calls runOrDiagnose with meetingCode', async () => {
325
- await handlers.get('gog_meet_participants')!({ meetingCode: 'abc-defg-hij' });
353
+ await harness.callTool('gog_meet_participants', { meetingCode: 'abc-defg-hij' });
326
354
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
327
355
  ['meet', 'participants', 'abc-defg-hij'],
328
356
  { account: undefined },
@@ -330,7 +358,7 @@ describe('gog_meet_participants', () => {
330
358
  });
331
359
 
332
360
  it('passes --conference and pagination flags', async () => {
333
- await handlers.get('gog_meet_participants')!({
361
+ await harness.callTool('gog_meet_participants', {
334
362
  meetingCode: 'abc-defg-hij',
335
363
  conference: 'conf123',
336
364
  max: 100,
@@ -350,10 +378,46 @@ describe('gog_meet_participants', () => {
350
378
  });
351
379
 
352
380
  it('omits --all when false', async () => {
353
- await handlers.get('gog_meet_participants')!({ meetingCode: 'abc-defg-hij', all: false });
381
+ await harness.callTool('gog_meet_participants', { meetingCode: 'abc-defg-hij', all: false });
354
382
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(
355
383
  ['meet', 'participants', 'abc-defg-hij'],
356
384
  { account: undefined },
357
385
  );
358
386
  });
359
387
  });
388
+
389
+ describe('gog_calendar_unsubscribe', () => {
390
+ it('calls runOrDiagnose with calendarId', async () => {
391
+ await harness.callTool('gog_calendar_unsubscribe', { calendarId: 'cal@group.calendar.google.com' });
392
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
393
+ ['calendar', 'unsubscribe', 'cal@group.calendar.google.com'],
394
+ { account: undefined },
395
+ );
396
+ });
397
+
398
+ it('passes account through', async () => {
399
+ await harness.callTool('gog_calendar_unsubscribe', { calendarId: 'cal', account: 'me@x.com' });
400
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
401
+ ['calendar', 'unsubscribe', 'cal'],
402
+ { account: 'me@x.com' },
403
+ );
404
+ });
405
+ });
406
+
407
+ describe('gog_calendar_delete_calendar', () => {
408
+ it('calls runOrDiagnose with calendarId', async () => {
409
+ await harness.callTool('gog_calendar_delete_calendar', { calendarId: 'cal' });
410
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
411
+ ['calendar', 'delete-calendar', 'cal', '--force'],
412
+ { account: undefined },
413
+ );
414
+ });
415
+
416
+ it('passes account through', async () => {
417
+ await harness.callTool('gog_calendar_delete_calendar', { calendarId: 'cal', account: 'me@x.com' });
418
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
419
+ ['calendar', 'delete-calendar', 'cal', '--force'],
420
+ { account: 'me@x.com' },
421
+ );
422
+ });
423
+ });
package/tsconfig.json CHANGED
@@ -5,5 +5,5 @@
5
5
  "rootDir": "../"
6
6
  },
7
7
  "include": ["src/**/*", "../gogcli-mcp/src/**/*"],
8
- "exclude": ["node_modules", "dist"]
8
+ "exclude": ["node_modules", "dist", "../gogcli-mcp/src/worker.ts"]
9
9
  }