gogcli-mcp-contacts 2.0.6
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/README.md +59 -0
- package/SKILL.md +43 -0
- package/dist/index.js +31179 -0
- package/manifest.json +128 -0
- package/package.json +42 -0
- package/src/index.ts +12 -0
- package/src/tools/contacts-extra.ts +79 -0
- package/tests/tools/contacts-extra.test.ts +98 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +17 -0
package/manifest.json
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/anthropics/dxt/main/dist/mcpb-manifest.schema.json",
|
|
3
|
+
"manifest_version": "0.3",
|
|
4
|
+
"name": "gogcli-mcp-contacts",
|
|
5
|
+
"display_name": "gogcli (Contacts)",
|
|
6
|
+
"version": "2.0.6",
|
|
7
|
+
"description": "Extended Google Contacts for Claude via gogcli — auth + Contacts + Workspace directory (People API)",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "Chris Hall",
|
|
10
|
+
"email": "chris.c.hall@gmail.com",
|
|
11
|
+
"url": "https://github.com/chrischall"
|
|
12
|
+
},
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/chrischall/gogcli-mcp"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/chrischall/gogcli-mcp",
|
|
18
|
+
"support": "https://github.com/chrischall/gogcli-mcp/issues",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"keywords": [
|
|
21
|
+
"google",
|
|
22
|
+
"contacts",
|
|
23
|
+
"people",
|
|
24
|
+
"directory",
|
|
25
|
+
"gogcli",
|
|
26
|
+
"gog"
|
|
27
|
+
],
|
|
28
|
+
"server": {
|
|
29
|
+
"type": "node",
|
|
30
|
+
"entry_point": "dist/index.js",
|
|
31
|
+
"mcp_config": {
|
|
32
|
+
"command": "node",
|
|
33
|
+
"args": [
|
|
34
|
+
"${__dirname}/dist/index.js"
|
|
35
|
+
],
|
|
36
|
+
"env": {
|
|
37
|
+
"GOG_ACCOUNT": "${user_config.gog_account}",
|
|
38
|
+
"GOG_PATH": "${user_config.gog_path}"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"user_config": {
|
|
43
|
+
"gog_account": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"title": "Google Account",
|
|
46
|
+
"description": "Default Google account email (optional — uses gogcli's configured default if omitted)",
|
|
47
|
+
"required": false
|
|
48
|
+
},
|
|
49
|
+
"gog_path": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"title": "gog Executable Path",
|
|
52
|
+
"description": "Path to the gog executable (optional — defaults to 'gog' on your PATH)",
|
|
53
|
+
"required": false
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"tools": [
|
|
57
|
+
{
|
|
58
|
+
"name": "gog_auth_add",
|
|
59
|
+
"description": "Authorize a Google account via browser-based OAuth"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"name": "gog_auth_list",
|
|
63
|
+
"description": "List all Google accounts stored in gogcli"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "gog_auth_status",
|
|
67
|
+
"description": "Show gogcli auth configuration"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "gog_auth_services",
|
|
71
|
+
"description": "List Google services and OAuth scopes supported by gogcli"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"name": "gog_auth_run",
|
|
75
|
+
"description": "Run any gog auth subcommand (escape hatch)"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "gog_contacts_search",
|
|
79
|
+
"description": "Search personal contacts by name, email, or phone"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "gog_contacts_list",
|
|
83
|
+
"description": "List all personal contacts"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"name": "gog_contacts_get",
|
|
87
|
+
"description": "Get a contact by resource name"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "gog_contacts_create",
|
|
91
|
+
"description": "Create a new contact"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "gog_contacts_run",
|
|
95
|
+
"description": "Run any gog contacts subcommand (escape hatch)"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "gog_people_me",
|
|
99
|
+
"description": "Show your own People profile"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "gog_people_get",
|
|
103
|
+
"description": "Get a People profile by resource name"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"name": "gog_people_search",
|
|
107
|
+
"description": "Search the Workspace directory"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "gog_people_relations",
|
|
111
|
+
"description": "Get manager/reports relations for a user"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
"name": "gog_people_raw",
|
|
115
|
+
"description": "Dump raw People API JSON for a person"
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
"compatibility": {
|
|
119
|
+
"platforms": [
|
|
120
|
+
"darwin",
|
|
121
|
+
"linux",
|
|
122
|
+
"win32"
|
|
123
|
+
],
|
|
124
|
+
"runtimes": {
|
|
125
|
+
"node": ">=18.0.0"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gogcli-mcp-contacts",
|
|
3
|
+
"version": "2.0.6",
|
|
4
|
+
"mcpName": "io.github.chrischall/gogcli-mcp-contacts",
|
|
5
|
+
"description": "Extended Google Contacts + People MCP server via gogcli — auth + Contacts + Workspace directory (People API)",
|
|
6
|
+
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/chrischall/gogcli-mcp"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"gogcli-mcp-contacts": "dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc --noEmit && npm run bundle",
|
|
20
|
+
"bundle": "node ../../scripts/bundle.js src/index.ts dist/index.js",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"test:coverage": "vitest run --coverage"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
28
|
+
"zod": "^4.4.3"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"model-context-protocol",
|
|
34
|
+
"claude",
|
|
35
|
+
"ai",
|
|
36
|
+
"google",
|
|
37
|
+
"gogcli",
|
|
38
|
+
"contacts",
|
|
39
|
+
"people",
|
|
40
|
+
"directory"
|
|
41
|
+
]
|
|
42
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { createServer, registerAuthTools, registerContactsTools } from '../../gogcli-mcp/src/lib.js';
|
|
4
|
+
import { registerExtraContactsTools } from './tools/contacts-extra.js';
|
|
5
|
+
|
|
6
|
+
const server = createServer({ name: 'gogcli-contacts' });
|
|
7
|
+
registerAuthTools(server);
|
|
8
|
+
registerContactsTools(server);
|
|
9
|
+
registerExtraContactsTools(server);
|
|
10
|
+
|
|
11
|
+
const transport = new StdioServerTransport();
|
|
12
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { accountParam, runOrDiagnose } from '../../../gogcli-mcp/src/lib.js';
|
|
4
|
+
|
|
5
|
+
export function registerExtraContactsTools(server: McpServer): void {
|
|
6
|
+
// ─── People API tools ──────────────────────────────────────────
|
|
7
|
+
// People is the richer API behind Google Contacts: Workspace directory
|
|
8
|
+
// search, profile fields, and relations.
|
|
9
|
+
|
|
10
|
+
server.registerTool('gog_people_me', {
|
|
11
|
+
description: 'Show your own People profile (people/me).',
|
|
12
|
+
annotations: { readOnlyHint: true },
|
|
13
|
+
inputSchema: {
|
|
14
|
+
account: accountParam,
|
|
15
|
+
},
|
|
16
|
+
}, async ({ account }) => {
|
|
17
|
+
return runOrDiagnose(['people', 'me'], { account });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
server.registerTool('gog_people_get', {
|
|
21
|
+
description: 'Get a People profile by resource name.',
|
|
22
|
+
annotations: { readOnlyHint: true },
|
|
23
|
+
inputSchema: {
|
|
24
|
+
userId: z.string().describe('Person resource name (people/...) or email'),
|
|
25
|
+
account: accountParam,
|
|
26
|
+
},
|
|
27
|
+
}, async ({ userId, account }) => {
|
|
28
|
+
return runOrDiagnose(['people', 'get', userId], { account });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
server.registerTool('gog_people_search', {
|
|
32
|
+
description: 'Search the Google Workspace directory (covers internal users, unlike contacts search which is limited to your personal contacts).',
|
|
33
|
+
annotations: { readOnlyHint: true },
|
|
34
|
+
inputSchema: {
|
|
35
|
+
query: z.string().describe('Search query (name, email, etc.)'),
|
|
36
|
+
max: z.number().optional().describe('Max results (default: 50)'),
|
|
37
|
+
page: z.string().optional().describe('Page token'),
|
|
38
|
+
all: z.boolean().optional().describe('Fetch all pages'),
|
|
39
|
+
account: accountParam,
|
|
40
|
+
},
|
|
41
|
+
}, async ({ query, max, page, all, account }) => {
|
|
42
|
+
const args = ['people', 'search', query];
|
|
43
|
+
if (max !== undefined) args.push(`--max=${max}`);
|
|
44
|
+
if (page) args.push(`--page=${page}`);
|
|
45
|
+
if (all) args.push('--all');
|
|
46
|
+
return runOrDiagnose(args, { account });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
server.registerTool('gog_people_relations', {
|
|
50
|
+
description: 'Get relations (manager, reports, etc.) for a user. Defaults to self when userId is omitted.',
|
|
51
|
+
annotations: { readOnlyHint: true },
|
|
52
|
+
inputSchema: {
|
|
53
|
+
userId: z.string().optional().describe('Person resource name (defaults to self when omitted)'),
|
|
54
|
+
type: z.string().optional().describe('Filter to a specific relation type (e.g. "manager")'),
|
|
55
|
+
account: accountParam,
|
|
56
|
+
},
|
|
57
|
+
}, async ({ userId, type, account }) => {
|
|
58
|
+
const args = ['people', 'relations'];
|
|
59
|
+
if (userId) args.push(userId);
|
|
60
|
+
if (type) args.push(`--type=${type}`);
|
|
61
|
+
return runOrDiagnose(args, { account });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
server.registerTool('gog_people_raw', {
|
|
65
|
+
description: 'Dump the raw People API response as JSON (lossless; for scripting and LLM consumption).',
|
|
66
|
+
annotations: { readOnlyHint: true },
|
|
67
|
+
inputSchema: {
|
|
68
|
+
userId: z.string().describe('Person resource name (people/...) or email'),
|
|
69
|
+
personFields: z.string().optional().describe('People API personFields mask (default: broad set)'),
|
|
70
|
+
pretty: z.boolean().optional().describe('Pretty-print JSON (default: compact single-line)'),
|
|
71
|
+
account: accountParam,
|
|
72
|
+
},
|
|
73
|
+
}, async ({ userId, personFields, pretty, account }) => {
|
|
74
|
+
const args = ['people', 'raw', userId];
|
|
75
|
+
if (personFields) args.push(`--person-fields=${personFields}`);
|
|
76
|
+
if (pretty) args.push('--pretty');
|
|
77
|
+
return runOrDiagnose(args, { account });
|
|
78
|
+
});
|
|
79
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { registerExtraContactsTools } from '../../src/tools/contacts-extra.js';
|
|
3
|
+
import * as lib from '../../../gogcli-mcp/src/lib.js';
|
|
4
|
+
import { setupExtrasHandlers, toText, type ToolHandler } from '../../../gogcli-mcp/tests/helpers/extras-harness.js';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../../gogcli-mcp/src/lib.js', async (importOriginal) => {
|
|
7
|
+
const actual = await importOriginal<typeof lib>();
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
runOrDiagnose: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
let handlers: Map<string, ToolHandler>;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
19
|
+
handlers = setupExtrasHandlers(registerExtraContactsTools);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('gog_people_me', () => {
|
|
23
|
+
it('calls runOrDiagnose with people me', async () => {
|
|
24
|
+
await handlers.get('gog_people_me')!({});
|
|
25
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['people', 'me'], { account: undefined });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('forwards account', async () => {
|
|
29
|
+
await handlers.get('gog_people_me')!({ account: 'a@b.com' });
|
|
30
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['people', 'me'], { account: 'a@b.com' });
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('gog_people_get', () => {
|
|
35
|
+
it('calls runOrDiagnose with userId', async () => {
|
|
36
|
+
await handlers.get('gog_people_get')!({ userId: 'people/c123' });
|
|
37
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['people', 'get', 'people/c123'], { account: undefined });
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('gog_people_search', () => {
|
|
42
|
+
it('calls runOrDiagnose with query', async () => {
|
|
43
|
+
await handlers.get('gog_people_search')!({ query: 'alice' });
|
|
44
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['people', 'search', 'alice'], { account: undefined });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('passes pagination flags', async () => {
|
|
48
|
+
await handlers.get('gog_people_search')!({ query: 'alice', max: 100, page: 'tok', all: true });
|
|
49
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
50
|
+
['people', 'search', 'alice', '--max=100', '--page=tok', '--all'],
|
|
51
|
+
{ account: undefined },
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('omits --all when false', async () => {
|
|
56
|
+
await handlers.get('gog_people_search')!({ query: 'x', all: false });
|
|
57
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['people', 'search', 'x'], { account: undefined });
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('gog_people_relations', () => {
|
|
62
|
+
it('calls runOrDiagnose with no userId', async () => {
|
|
63
|
+
await handlers.get('gog_people_relations')!({});
|
|
64
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['people', 'relations'], { account: undefined });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('passes userId and --type when provided', async () => {
|
|
68
|
+
await handlers.get('gog_people_relations')!({ userId: 'people/c123', type: 'manager' });
|
|
69
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
70
|
+
['people', 'relations', 'people/c123', '--type=manager'],
|
|
71
|
+
{ account: undefined },
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('gog_people_raw', () => {
|
|
77
|
+
it('calls runOrDiagnose with userId', async () => {
|
|
78
|
+
await handlers.get('gog_people_raw')!({ userId: 'people/c123' });
|
|
79
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['people', 'raw', 'people/c123'], { account: undefined });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('passes --person-fields and --pretty when provided', async () => {
|
|
83
|
+
await handlers.get('gog_people_raw')!({
|
|
84
|
+
userId: 'people/c123',
|
|
85
|
+
personFields: 'names,emailAddresses',
|
|
86
|
+
pretty: true,
|
|
87
|
+
});
|
|
88
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
89
|
+
['people', 'raw', 'people/c123', '--person-fields=names,emailAddresses', '--pretty'],
|
|
90
|
+
{ account: undefined },
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('omits --pretty when false', async () => {
|
|
95
|
+
await handlers.get('gog_people_raw')!({ userId: 'people/c123', pretty: false });
|
|
96
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['people', 'raw', 'people/c123'], { account: undefined });
|
|
97
|
+
});
|
|
98
|
+
});
|
package/tsconfig.json
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
coverage: {
|
|
6
|
+
provider: 'v8',
|
|
7
|
+
include: ['src/**/*.ts'],
|
|
8
|
+
exclude: ['src/index.ts'],
|
|
9
|
+
thresholds: {
|
|
10
|
+
lines: 100,
|
|
11
|
+
functions: 100,
|
|
12
|
+
branches: 100,
|
|
13
|
+
statements: 100,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
});
|