gogcli-mcp 2.27.0 → 2.28.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +3 -3
- package/SKILL.md +2 -2
- package/dist/index.js +434 -127
- package/dist/lib.js +435 -128
- package/manifest.json +5 -1
- package/mint.yaml +1 -1
- package/package.json +3 -3
- package/server.json +2 -2
- package/src/runner.ts +1 -1
- package/src/tools/chat.ts +36 -1
- package/src/worker.ts +1 -1
- package/tests/server.test.ts +7 -1
- package/tests/tools/chat.test.ts +61 -1
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.
|
|
6
|
+
"version": "2.28.0",
|
|
7
7
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
|
@@ -452,6 +452,10 @@
|
|
|
452
452
|
"name": "gog_chat_messages_list",
|
|
453
453
|
"description": "Read messages in a space, with mentions and reaction summaries"
|
|
454
454
|
},
|
|
455
|
+
{
|
|
456
|
+
"name": "gog_chat_messages_search",
|
|
457
|
+
"description": "Search Chat messages across every space and DM the account can see"
|
|
458
|
+
},
|
|
455
459
|
{
|
|
456
460
|
"name": "gog_chat_messages_send",
|
|
457
461
|
"description": "Post a message to a Chat space (immediately visible; supports attachments)"
|
package/mint.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/gogcli-mcp",
|
|
5
5
|
"description": "MCP server wrapping gogcli for Google service access",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
"test:coverage": "vitest run --coverage"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@chrischall/mcp-utils": "^0.
|
|
44
|
+
"@chrischall/mcp-utils": "^0.17.1",
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
46
46
|
"zod": "^4.4.3"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@types/node": "^26.
|
|
49
|
+
"@types/node": "^26.4.0",
|
|
50
50
|
"@vitest/coverage-v8": "^4.1.8",
|
|
51
51
|
"esbuild": "^0.28.2",
|
|
52
52
|
"typescript": "^7.0.2",
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"source": "github",
|
|
8
8
|
"subfolder": "packages/gogcli-mcp"
|
|
9
9
|
},
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.28.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp",
|
|
15
|
-
"version": "2.
|
|
15
|
+
"version": "2.28.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
package/src/runner.ts
CHANGED
|
@@ -217,7 +217,7 @@ const TIMEOUT_MS = 30_000;
|
|
|
217
217
|
// so the requirement change is surfaced in the release notes (see
|
|
218
218
|
// .github/release.yml). This is the single source of truth for the required
|
|
219
219
|
// version; keep the README/CLAUDE.md mention in sync.
|
|
220
|
-
export const MIN_GOG_VERSION = '0.
|
|
220
|
+
export const MIN_GOG_VERSION = '0.39.0';
|
|
221
221
|
|
|
222
222
|
// Interpret the GOG_READONLY kill-switch. `readEnvVar` already treats blank
|
|
223
223
|
// values, 'undefined'/'null' sentinels, and unresolved .mcpb placeholders
|
package/src/tools/chat.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { GogArg } from '../runner.js';
|
|
|
11
11
|
import { attachInlineParam, inlineAttachmentArgs } from '../attachments.js';
|
|
12
12
|
|
|
13
13
|
// Google Chat (gog >= 0.38.0 for the mention/reaction metadata in
|
|
14
|
-
// `messages list
|
|
14
|
+
// `messages list`, >= 0.39.0 for `messages search`; the rest is older).
|
|
15
15
|
//
|
|
16
16
|
// TWO NAMING SYSTEMS MEET HERE, and mixing them is the mistake this module's
|
|
17
17
|
// descriptions exist to prevent. Chat identifies everything by RESOURCE NAME —
|
|
@@ -135,6 +135,41 @@ export function registerChatTools(server: McpServer): void {
|
|
|
135
135
|
return runOrDiagnose(args, { account });
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
+
server.registerTool('gog_chat_messages_search', {
|
|
139
|
+
description:
|
|
140
|
+
'Search Chat messages ACROSS every space and DM the account can see (gog >= 0.39.0) — the tool to reach for when the '
|
|
141
|
+
+ 'user asks "where did we discuss X" without naming a room, since gog_chat_messages_list needs a space up front. '
|
|
142
|
+
+ 'The query is Google Chat filter syntax, so it takes plain keywords or filters like sender, space, date, mention, '
|
|
143
|
+
+ 'unread, link and attachment. It is a search, NOT an export: Chat excludes some conversations (muted spaces among '
|
|
144
|
+
+ 'them), so an empty result does not prove a message never existed. view="full" adds each hit\'s read state and space '
|
|
145
|
+
+ 'mute setting; read state works on an ordinary chat grant, but the mute setting needs chat.users.spacesettings, '
|
|
146
|
+
+ 'which gog\'s chat scope set does NOT request (re-auth with extraScopes to get it). Missing metadata is OMITTED '
|
|
147
|
+
+ 'rather than defaulted, so an absent `read` means unknown while an explicit false means unread.' + workspaceOnlyNote,
|
|
148
|
+
annotations: { readOnlyHint: true },
|
|
149
|
+
inputSchema: {
|
|
150
|
+
query: z.string().describe('Google Chat filter-syntax query — keywords, or filters such as "from:alice@example.com budget"'),
|
|
151
|
+
order: z.enum(['create_time desc', 'relevance desc']).optional().describe(
|
|
152
|
+
'Sort order. NOTE the snake_case, which differs from gog_chat_messages_list\'s camelCase. "relevance desc" needs '
|
|
153
|
+
+ 'Google Developer Preview access and errors without it',
|
|
154
|
+
),
|
|
155
|
+
view: z.enum(['basic', 'full']).optional().describe(
|
|
156
|
+
'Result view (default "basic"). "full" also requests read state (covered by an ordinary chat grant) and space '
|
|
157
|
+
+ 'mute setting (needs chat.users.spacesettings, which that grant does not include)',
|
|
158
|
+
),
|
|
159
|
+
markup: z.enum(['chat', 'markdown']).optional().describe('Syntax to render each hit\'s formatted text in'),
|
|
160
|
+
...paginationParams,
|
|
161
|
+
max: z.number().int().min(1).max(100).optional().describe('Max results per page (1-100; Chat search caps a page at 100)'),
|
|
162
|
+
account: accountParam,
|
|
163
|
+
},
|
|
164
|
+
}, async ({ query, order, view, markup, max, pageToken, page, all, account }) => {
|
|
165
|
+
const args = ['chat', 'messages', 'search', query];
|
|
166
|
+
if (order) args.push(`--order=${order}`);
|
|
167
|
+
if (view) args.push(`--view=${view}`);
|
|
168
|
+
if (markup) args.push(`--markup=${markup}`);
|
|
169
|
+
pushPaginationFlags(args, { max, pageToken, page, all });
|
|
170
|
+
return runOrDiagnose(args, { account });
|
|
171
|
+
});
|
|
172
|
+
|
|
138
173
|
server.registerTool('gog_chat_messages_send', {
|
|
139
174
|
description:
|
|
140
175
|
'Post a message to a Chat space. THIS IS IMMEDIATELY VISIBLE TO EVERYONE IN THE SPACE and cannot be unsent through '
|
package/src/worker.ts
CHANGED
|
@@ -38,7 +38,7 @@ import { gogAuth, CONNECTOR_INSTRUCTIONS, type GogProps } from './connector-auth
|
|
|
38
38
|
// connector with all ~360 tools at once. Add whichever paths you want as separate
|
|
39
39
|
// connectors in claude.ai (each authorizes with the same connector key).
|
|
40
40
|
|
|
41
|
-
const VERSION = '2.
|
|
41
|
+
const VERSION = '2.28.0'; // x-release-please-version
|
|
42
42
|
|
|
43
43
|
// Build an McpAgent subclass whose init() registers `registrars` onto its server,
|
|
44
44
|
// each handler wrapped in the ALS scope carrying the per-session Fly executor.
|
package/tests/server.test.ts
CHANGED
|
@@ -90,8 +90,14 @@ describe('published tool counts match the registrars', () => {
|
|
|
90
90
|
expect(rows.length).toBeGreaterThan(0);
|
|
91
91
|
const service = (row: string) => row.toLowerCase().replace(/[^a-z]/g, '');
|
|
92
92
|
// Map a table label onto the gog_<service>_ prefix its tools carry.
|
|
93
|
+
// Only labels whose normalised form differs from their tool prefix belong
|
|
94
|
+
// here. "Apps Script" normalises to `appsscript` but the tools are
|
|
95
|
+
// `gog_appscript_`, so that mapping is load-bearing. A `discoveryapi` entry
|
|
96
|
+
// was not: both tables label the row "API", which normalises to `api` and
|
|
97
|
+
// already resolves — so it never matched and only implied a label that
|
|
98
|
+
// does not exist.
|
|
93
99
|
const prefixFor: Record<string, string> = {
|
|
94
|
-
appsscript: 'appscript',
|
|
100
|
+
appsscript: 'appscript',
|
|
95
101
|
};
|
|
96
102
|
for (const row of rows) {
|
|
97
103
|
const stated = Number(row[2] ?? row[3]);
|
package/tests/tools/chat.test.ts
CHANGED
|
@@ -46,7 +46,7 @@ describe('gog_chat_spaces_list', () => {
|
|
|
46
46
|
return undefined as never;
|
|
47
47
|
});
|
|
48
48
|
registerChatTools(server);
|
|
49
|
-
expect(configs.size).toBe(
|
|
49
|
+
expect(configs.size).toBe(13);
|
|
50
50
|
for (const [name, config] of configs) {
|
|
51
51
|
expect(config.description, name).toMatch(/consumer accounts|Workspace/i);
|
|
52
52
|
}
|
|
@@ -132,6 +132,66 @@ describe('gog_chat_messages_list', () => {
|
|
|
132
132
|
});
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
describe('gog_chat_messages_search', () => {
|
|
136
|
+
it('searches across every space the account can see', async () => {
|
|
137
|
+
vi.mocked(runner.run).mockResolvedValue('{"results":[]}');
|
|
138
|
+
const harness = await setupHandlers();
|
|
139
|
+
await harness.callTool('gog_chat_messages_search', { query: 'project decision' });
|
|
140
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
141
|
+
['chat', 'messages', 'search', 'project decision'],
|
|
142
|
+
{ account: undefined },
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('passes view, markup, order and pagination flags', async () => {
|
|
147
|
+
vi.mocked(runner.run).mockResolvedValue('{"results":[]}');
|
|
148
|
+
const harness = await setupHandlers();
|
|
149
|
+
await harness.callTool('gog_chat_messages_search', {
|
|
150
|
+
query: 'from:alice@example.com budget',
|
|
151
|
+
view: 'full',
|
|
152
|
+
markup: 'markdown',
|
|
153
|
+
order: 'create_time desc',
|
|
154
|
+
max: 100,
|
|
155
|
+
pageToken: 'tok',
|
|
156
|
+
all: true,
|
|
157
|
+
});
|
|
158
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
159
|
+
['chat', 'messages', 'search', 'from:alice@example.com budget', '--order=create_time desc',
|
|
160
|
+
'--view=full', '--markup=markdown', '--max=100', '--page=tok', '--all'],
|
|
161
|
+
{ account: undefined },
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Chat's own ordering vocabulary is snake_case here and camelCase in
|
|
166
|
+
// `messages list`; a model that copies the list tool's "createTime desc"
|
|
167
|
+
// would get a gog error at runtime, so the enum has to reject it here.
|
|
168
|
+
it('rejects an order value search does not accept', async () => {
|
|
169
|
+
const harness = await setupHandlers();
|
|
170
|
+
const result = await harness.callTool('gog_chat_messages_search', {
|
|
171
|
+
query: 'x', order: 'createTime desc',
|
|
172
|
+
});
|
|
173
|
+
expect(result.isError).toBe(true);
|
|
174
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Chat caps a search page at 100; `messages list` allows more. Letting the
|
|
178
|
+
// larger number through would surface as a gog error on a call the tool
|
|
179
|
+
// already had enough information to refuse.
|
|
180
|
+
it('rejects a page size above Chat search\'s cap of 100', async () => {
|
|
181
|
+
const harness = await setupHandlers();
|
|
182
|
+
const result = await harness.callTool('gog_chat_messages_search', { query: 'x', max: 250 });
|
|
183
|
+
expect(result.isError).toBe(true);
|
|
184
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('returns error text on failure', async () => {
|
|
188
|
+
vi.mocked(runner.run).mockRejectedValue(new Error('Search failed'));
|
|
189
|
+
const harness = await setupHandlers();
|
|
190
|
+
const result = await harness.callTool('gog_chat_messages_search', { query: 'x' });
|
|
191
|
+
expect(result.content[0].text).toBe('Error: Search failed');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
135
195
|
describe('gog_chat_messages_send', () => {
|
|
136
196
|
it('sends text to a space', async () => {
|
|
137
197
|
vi.mocked(runner.run).mockResolvedValue('{}');
|