google-calendar-workspace-mcp-server 0.0.1 → 0.0.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/README.md +30 -0
- package/build/index.integration-with-mock.js +9 -1
- package/build/index.js +9 -1
- package/package.json +1 -1
- package/shared/index.d.ts +2 -1
- package/shared/server.d.ts +17 -1
- package/shared/server.js +3 -3
- package/shared/tools.d.ts +37 -1
- package/shared/tools.js +83 -7
package/README.md
CHANGED
|
@@ -116,6 +116,36 @@ Add this configuration to your Claude Desktop config file:
|
|
|
116
116
|
}
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
+
## Tool Groups
|
|
120
|
+
|
|
121
|
+
By default, all tools are enabled (read + write access). You can restrict the server to read-only operations by setting the `TOOL_GROUPS` environment variable:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"mcpServers": {
|
|
126
|
+
"google-calendar": {
|
|
127
|
+
"command": "npx",
|
|
128
|
+
"args": ["google-calendar-workspace-mcp-server"],
|
|
129
|
+
"env": {
|
|
130
|
+
"GCAL_SERVICE_ACCOUNT_CLIENT_EMAIL": "...",
|
|
131
|
+
"GCAL_SERVICE_ACCOUNT_PRIVATE_KEY": "...",
|
|
132
|
+
"GCAL_IMPERSONATE_EMAIL": "...",
|
|
133
|
+
"TOOL_GROUPS": "calendar_readonly"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Available tool groups:**
|
|
141
|
+
|
|
142
|
+
| Group | Tools Included |
|
|
143
|
+
| ------------------- | --------------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `calendar` | All tools (read + write) - default |
|
|
145
|
+
| `calendar_readonly` | Read-only tools: `gcal_list_events`, `gcal_get_event`, `gcal_list_calendars`, `gcal_query_freebusy` |
|
|
146
|
+
|
|
147
|
+
When using `calendar_readonly`, the `gcal_create_event` tool is not available.
|
|
148
|
+
|
|
119
149
|
## Available Tools
|
|
120
150
|
|
|
121
151
|
### gcal_list_events
|
|
@@ -3,9 +3,17 @@
|
|
|
3
3
|
* Integration test entry point with mock Google Calendar client
|
|
4
4
|
* Used by TestMCPClient for integration tests
|
|
5
5
|
*/
|
|
6
|
+
import { readFileSync } from 'fs';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
6
9
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
10
|
import { createMCPServer } from '../shared/index.js';
|
|
8
11
|
import { logServerStart } from '../shared/logging.js';
|
|
12
|
+
// Read version from package.json
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const packageJsonPath = join(__dirname, '..', 'package.json');
|
|
15
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
16
|
+
const VERSION = packageJson.version;
|
|
9
17
|
/**
|
|
10
18
|
* Mock Google Calendar client for integration tests
|
|
11
19
|
*/
|
|
@@ -185,7 +193,7 @@ class MockCalendarClient {
|
|
|
185
193
|
}
|
|
186
194
|
}
|
|
187
195
|
async function main() {
|
|
188
|
-
const { server, registerHandlers } = createMCPServer();
|
|
196
|
+
const { server, registerHandlers } = createMCPServer({ version: VERSION });
|
|
189
197
|
// Register handlers with mock client factory
|
|
190
198
|
await registerHandlers(server, () => new MockCalendarClient());
|
|
191
199
|
const transport = new StdioServerTransport();
|
package/build/index.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
2
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
6
|
import { createMCPServer } from '../shared/index.js';
|
|
4
7
|
import { logServerStart, logError } from '../shared/logging.js';
|
|
8
|
+
// Read version from package.json
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const packageJsonPath = join(__dirname, '..', 'package.json');
|
|
11
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
12
|
+
const VERSION = packageJson.version;
|
|
5
13
|
async function main() {
|
|
6
14
|
// Validate required environment variables
|
|
7
15
|
const requiredVars = [
|
|
@@ -20,7 +28,7 @@ async function main() {
|
|
|
20
28
|
process.exit(1);
|
|
21
29
|
}
|
|
22
30
|
try {
|
|
23
|
-
const { server, registerHandlers } = createMCPServer();
|
|
31
|
+
const { server, registerHandlers } = createMCPServer({ version: VERSION });
|
|
24
32
|
// Register handlers with default client factory
|
|
25
33
|
await registerHandlers(server);
|
|
26
34
|
const transport = new StdioServerTransport();
|
package/package.json
CHANGED
package/shared/index.d.ts
CHANGED
package/shared/server.d.ts
CHANGED
|
@@ -83,6 +83,22 @@ export declare class ServiceAccountCalendarClient implements ICalendarClient {
|
|
|
83
83
|
queryFreebusy(request: FreeBusyRequest): Promise<FreeBusyResponse>;
|
|
84
84
|
}
|
|
85
85
|
export type ClientFactory = () => ICalendarClient;
|
|
86
|
+
/**
|
|
87
|
+
* Options for creating the MCP server
|
|
88
|
+
*/
|
|
89
|
+
export interface CreateMCPServerOptions {
|
|
90
|
+
version: string;
|
|
91
|
+
/**
|
|
92
|
+
* Optional comma-separated list of tool groups to enable.
|
|
93
|
+
* Available groups:
|
|
94
|
+
* - 'calendar': All calendar tools (read + write)
|
|
95
|
+
* - 'calendar_readonly': Calendar tools (read only - excludes create_event)
|
|
96
|
+
*
|
|
97
|
+
* If not specified, defaults to 'calendar' (full access).
|
|
98
|
+
* Can also be set via TOOL_GROUPS environment variable.
|
|
99
|
+
*/
|
|
100
|
+
enabledToolGroups?: string;
|
|
101
|
+
}
|
|
86
102
|
/**
|
|
87
103
|
* Creates the default Google Calendar client based on environment variables.
|
|
88
104
|
* Uses service account with domain-wide delegation:
|
|
@@ -91,7 +107,7 @@ export type ClientFactory = () => ICalendarClient;
|
|
|
91
107
|
* - GCAL_IMPERSONATE_EMAIL: Email address to impersonate
|
|
92
108
|
*/
|
|
93
109
|
export declare function createDefaultClient(): ICalendarClient;
|
|
94
|
-
export declare function createMCPServer(): {
|
|
110
|
+
export declare function createMCPServer(options: CreateMCPServerOptions): {
|
|
95
111
|
server: Server<{
|
|
96
112
|
method: string;
|
|
97
113
|
params?: {
|
package/shared/server.js
CHANGED
|
@@ -113,10 +113,10 @@ export function createDefaultClient() {
|
|
|
113
113
|
};
|
|
114
114
|
return new ServiceAccountCalendarClient(credentials, impersonateEmail);
|
|
115
115
|
}
|
|
116
|
-
export function createMCPServer() {
|
|
116
|
+
export function createMCPServer(options) {
|
|
117
117
|
const server = new Server({
|
|
118
118
|
name: 'google-calendar-workspace-mcp-server',
|
|
119
|
-
version:
|
|
119
|
+
version: options.version,
|
|
120
120
|
}, {
|
|
121
121
|
capabilities: {
|
|
122
122
|
tools: {},
|
|
@@ -125,7 +125,7 @@ export function createMCPServer() {
|
|
|
125
125
|
const registerHandlers = async (server, clientFactory) => {
|
|
126
126
|
// Use provided factory or create default client
|
|
127
127
|
const factory = clientFactory || createDefaultClient;
|
|
128
|
-
const registerTools = createRegisterTools(factory);
|
|
128
|
+
const registerTools = createRegisterTools(factory, options.enabledToolGroups);
|
|
129
129
|
registerTools(server);
|
|
130
130
|
};
|
|
131
131
|
return { server, registerHandlers };
|
package/shared/tools.d.ts
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import type { ClientFactory } from './server.js';
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Tool group definitions - groups of related tools that can be enabled/disabled together
|
|
5
|
+
*
|
|
6
|
+
* Each group has two variants:
|
|
7
|
+
* - Base group (e.g., 'calendar'): Includes all tools (read + write operations)
|
|
8
|
+
* - Readonly group (e.g., 'calendar_readonly'): Includes only read operations
|
|
9
|
+
*
|
|
10
|
+
* Groups:
|
|
11
|
+
* - calendar / calendar_readonly: All calendar tools (events, calendars, freebusy)
|
|
12
|
+
*/
|
|
13
|
+
export type ToolGroup = 'calendar' | 'calendar_readonly';
|
|
14
|
+
/**
|
|
15
|
+
* Parse enabled tool groups from environment variable or parameter
|
|
16
|
+
* @param enabledGroupsParam - Comma-separated list of tool groups (e.g., "calendar_readonly")
|
|
17
|
+
* @returns Array of enabled tool groups
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseEnabledToolGroups(enabledGroupsParam?: string): ToolGroup[];
|
|
20
|
+
/**
|
|
21
|
+
* Creates a function to register all tools with the server.
|
|
22
|
+
* This pattern uses individual tool files for better modularity and testability.
|
|
23
|
+
*
|
|
24
|
+
* Each tool is defined in its own file under the `tools/` directory and follows
|
|
25
|
+
* a factory pattern that accepts the server and clientFactory as parameters.
|
|
26
|
+
*
|
|
27
|
+
* Tool groups can be enabled/disabled via the TOOL_GROUPS environment variable
|
|
28
|
+
* (comma-separated list, e.g., "calendar_readonly"). If not set, all
|
|
29
|
+
* base tool groups are enabled by default (full read+write access).
|
|
30
|
+
*
|
|
31
|
+
* Available tool groups:
|
|
32
|
+
* - calendar: All calendar tools (read + write)
|
|
33
|
+
* - calendar_readonly: Calendar tools (read only - excludes create_event)
|
|
34
|
+
*
|
|
35
|
+
* @param clientFactory - Factory function that creates client instances
|
|
36
|
+
* @param enabledGroups - Optional comma-separated list of enabled tool groups (overrides env var)
|
|
37
|
+
* @returns Function that registers all tools with a server
|
|
38
|
+
*/
|
|
39
|
+
export declare function createRegisterTools(clientFactory: ClientFactory, enabledGroups?: string): (server: Server) => void;
|
package/shared/tools.js
CHANGED
|
@@ -5,15 +5,91 @@ import { createEventTool } from './tools/create-event.js';
|
|
|
5
5
|
import { listCalendarsTool } from './tools/list-calendars.js';
|
|
6
6
|
import { queryFreebusyTool } from './tools/query-freebusy.js';
|
|
7
7
|
const ALL_TOOLS = [
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
listCalendarsTool,
|
|
12
|
-
queryFreebusyTool,
|
|
8
|
+
// Calendar tools - read operations
|
|
9
|
+
{ factory: listEventsTool, group: 'calendar', isWriteOperation: false },
|
|
10
|
+
{ factory: getEventTool, group: 'calendar', isWriteOperation: false },
|
|
11
|
+
{ factory: listCalendarsTool, group: 'calendar', isWriteOperation: false },
|
|
12
|
+
{ factory: queryFreebusyTool, group: 'calendar', isWriteOperation: false },
|
|
13
|
+
// Calendar tools - write operations
|
|
14
|
+
{ factory: createEventTool, group: 'calendar', isWriteOperation: true },
|
|
13
15
|
];
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* All valid tool groups (base groups and their _readonly variants)
|
|
18
|
+
*/
|
|
19
|
+
const VALID_TOOL_GROUPS = ['calendar', 'calendar_readonly'];
|
|
20
|
+
/**
|
|
21
|
+
* Base groups (without _readonly suffix) - used for default "all groups" behavior
|
|
22
|
+
*/
|
|
23
|
+
const BASE_TOOL_GROUPS = ['calendar'];
|
|
24
|
+
/**
|
|
25
|
+
* Parse enabled tool groups from environment variable or parameter
|
|
26
|
+
* @param enabledGroupsParam - Comma-separated list of tool groups (e.g., "calendar_readonly")
|
|
27
|
+
* @returns Array of enabled tool groups
|
|
28
|
+
*/
|
|
29
|
+
export function parseEnabledToolGroups(enabledGroupsParam) {
|
|
30
|
+
const groupsStr = enabledGroupsParam || process.env.TOOL_GROUPS || '';
|
|
31
|
+
if (!groupsStr) {
|
|
32
|
+
// Default: all base groups enabled (full read+write access)
|
|
33
|
+
return [...BASE_TOOL_GROUPS];
|
|
34
|
+
}
|
|
35
|
+
const groups = groupsStr.split(',').map((g) => g.trim());
|
|
36
|
+
const validGroups = [];
|
|
37
|
+
for (const group of groups) {
|
|
38
|
+
if (VALID_TOOL_GROUPS.includes(group) &&
|
|
39
|
+
!validGroups.includes(group)) {
|
|
40
|
+
validGroups.push(group);
|
|
41
|
+
}
|
|
42
|
+
else if (!VALID_TOOL_GROUPS.includes(group)) {
|
|
43
|
+
console.warn(`Unknown tool group: ${group}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return validGroups;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if a tool should be included based on enabled groups
|
|
50
|
+
* @param toolDef - The tool definition to check
|
|
51
|
+
* @param enabledGroups - Array of enabled tool groups
|
|
52
|
+
* @returns true if the tool should be included
|
|
53
|
+
*/
|
|
54
|
+
function shouldIncludeTool(toolDef, enabledGroups) {
|
|
55
|
+
const baseGroup = toolDef.group;
|
|
56
|
+
const readonlyGroup = `${baseGroup}_readonly`;
|
|
57
|
+
// Check if the base group (full access) is enabled
|
|
58
|
+
if (enabledGroups.includes(baseGroup)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
// Check if the readonly group is enabled (only include read operations)
|
|
62
|
+
if (enabledGroups.includes(readonlyGroup) && !toolDef.isWriteOperation) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Creates a function to register all tools with the server.
|
|
69
|
+
* This pattern uses individual tool files for better modularity and testability.
|
|
70
|
+
*
|
|
71
|
+
* Each tool is defined in its own file under the `tools/` directory and follows
|
|
72
|
+
* a factory pattern that accepts the server and clientFactory as parameters.
|
|
73
|
+
*
|
|
74
|
+
* Tool groups can be enabled/disabled via the TOOL_GROUPS environment variable
|
|
75
|
+
* (comma-separated list, e.g., "calendar_readonly"). If not set, all
|
|
76
|
+
* base tool groups are enabled by default (full read+write access).
|
|
77
|
+
*
|
|
78
|
+
* Available tool groups:
|
|
79
|
+
* - calendar: All calendar tools (read + write)
|
|
80
|
+
* - calendar_readonly: Calendar tools (read only - excludes create_event)
|
|
81
|
+
*
|
|
82
|
+
* @param clientFactory - Factory function that creates client instances
|
|
83
|
+
* @param enabledGroups - Optional comma-separated list of enabled tool groups (overrides env var)
|
|
84
|
+
* @returns Function that registers all tools with a server
|
|
85
|
+
*/
|
|
86
|
+
export function createRegisterTools(clientFactory, enabledGroups) {
|
|
15
87
|
return (server) => {
|
|
16
|
-
const
|
|
88
|
+
const enabledToolGroups = parseEnabledToolGroups(enabledGroups);
|
|
89
|
+
// Filter tools based on enabled groups
|
|
90
|
+
const enabledTools = ALL_TOOLS.filter((toolDef) => shouldIncludeTool(toolDef, enabledToolGroups));
|
|
91
|
+
// Create tool instances
|
|
92
|
+
const tools = enabledTools.map((toolDef) => toolDef.factory(server, clientFactory));
|
|
17
93
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
18
94
|
tools: tools.map((tool) => ({
|
|
19
95
|
name: tool.name,
|