Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ GDRIVE_TOKEN_RETRY_DELAY=1000 # Initial retry delay (in milliseconds)
# GDRIVE_CREDENTIALS_PATH=~/.gdrive-server-credentials.json
# GDRIVE_OAUTH_PATH=./gcp-oauth.keys.json

# Optional: PAI contact resolution for Calendar module
# Set this to enable resolving contact names (e.g., "Mary") to email addresses
# Without this, all attendee inputs are treated as raw email addresses
# PAI_CONTACTS_PATH=/path/to/your/CONTACTS.md

# Optional: Logging configuration
LOG_LEVEL=info # Options: error, warn, info, debug, verbose

Expand Down
59 changes: 59 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,65 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.3.0] - 2026-01-08

### ✨ New Features

#### Google Calendar API Integration
Added complete Google Calendar functionality with 9 operations following the established operation-based architecture pattern.

**New Calendar Operations:**
- `listCalendars` - List all calendars with access role and time zone
- `getCalendar` - Get calendar details by ID
- `listEvents` - List events with time range filters and pagination
- `getEvent` - Get full event details including attendees and recurrence
- `createEvent` - Create events with attendees, recurrence, Google Meet, reminders
- `updateEvent` - Partial updates to existing events
- `deleteEvent` - Delete events with attendee notification options
- `quickAdd` - Create events from natural language strings
- `checkFreeBusy` - Query availability across multiple calendars

**PAI Contact Resolution:**
- Resolve contact names (e.g., "Mary") to email addresses automatically
- Supports mixed inputs: `["Mary", "user@example.com"]`
- Case-insensitive matching against PAI contact list

**New OAuth Scopes Required:**
```text
calendar.readonly - Read calendars and events
calendar.events - Create, update, delete events
```

⚠️ **Re-authentication required** - Users must re-authenticate after upgrading to grant Calendar permissions.

**New Files:**
- `src/modules/calendar/` - Complete Calendar module with 9 files
- `src/modules/calendar/types.ts` - TypeScript interfaces
- `src/modules/calendar/contacts.ts` - PAI contact resolution
- `src/modules/calendar/list.ts` - listCalendars, listEvents
- `src/modules/calendar/read.ts` - getCalendar, getEvent
- `src/modules/calendar/create.ts` - createEvent, quickAdd
- `src/modules/calendar/update.ts` - updateEvent
- `src/modules/calendar/delete.ts` - deleteEvent
- `src/modules/calendar/freebusy.ts` - checkFreeBusy
- `src/modules/calendar/index.ts` - Public exports

### 🧪 Testing

- Added 59 unit tests across 4 test suites
- Tests cover contacts, list, read, and freebusy operations
- Full caching and performance monitoring coverage

### 🏗️ Internal

- Added `CalendarContext` type extending `BaseContext`
- Calendar module follows Gmail module patterns exactly
- Full Redis caching support (5-minute TTL for reads, 60s for freebusy)
- Performance monitoring integrated for all operations
- Cache invalidation on write operations (create, update, delete)

---

## [3.2.0] - 2025-12-23

### ✨ New Features
Expand Down
20 changes: 20 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ This is a Model Context Protocol (MCP) server for Google Drive integration. It p
- Google Forms creation and management with question types
- **Google Docs API integration** - Create documents, insert text, replace text, apply formatting, insert tables
- **Gmail API integration** - Read, search, compose, send emails, manage labels (v3.2.0+)
- **Google Calendar API integration** - List calendars, manage events, check availability, quick add with natural language (v3.3.0+)
- **Batch file operations** - Process multiple files in a single operation (create, update, delete, move)
- Enhanced search with natural language parsing
- Forms response handling and analysis
Expand All @@ -62,6 +63,23 @@ This is a Model Context Protocol (MCP) server for Google Drive integration. It p
- Docker support for containerized deployment with Redis
- **BMAD Framework Integration** - Agent-driven development methodology for structured brownfield and greenfield projects

## Git Workflow

**IMPORTANT: Main branch is protected.** All changes must go through pull requests.

```bash
# Create feature branch
git checkout -b feature/your-feature-name

# Push and create PR
git push -u origin feature/your-feature-name
gh pr create --title "feat: Your feature" --body "Description"
```

- Direct pushes to `main` will be rejected
- PRs require review before merging
- Use conventional commit messages (feat:, fix:, docs:, etc.)

## Key Commands

### Build & Development
Expand Down Expand Up @@ -91,6 +109,7 @@ This is a Model Context Protocol (MCP) server for Google Drive integration. It p
- **Forms API Integration** - Google Forms v1 API for form creation and management
- **Docs API Integration** - Google Docs v1 API for document manipulation
- **Gmail API Integration** - Gmail v1 API for email operations (v3.2.0+)
- **Calendar API Integration** - Google Calendar v3 API for calendar and event management (v3.3.0+)
- **Redis Cache Manager** - High-performance caching with automatic invalidation
- **Performance Monitor** - Real-time performance tracking and statistics
- **Winston Logger** - Structured logging with file rotation and console output
Expand All @@ -104,6 +123,7 @@ This is a Model Context Protocol (MCP) server for Google Drive integration. It p
- **Forms Operations**: createForm, getForm, addQuestion, listResponses
- **Docs Operations**: createDocument, insertText, replaceText, applyTextStyle, insertTable
- **Gmail Operations**: listMessages, listThreads, getMessage, getThread, searchMessages, createDraft, sendMessage, sendDraft, listLabels, modifyLabels
- **Calendar Operations**: listCalendars, getCalendar, listEvents, getEvent, createEvent, updateEvent, deleteEvent, quickAdd, checkFreeBusy
- **Batch Operations**: batchFileOperations (create, update, delete, move multiple files)
- **Enhanced Search**: enhancedSearch with natural language parsing
- **Transport**: StdioServerTransport for MCP communication
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@
VOLUME ["/credentials"]

# Environment variables
# Note: *_PATH vars are file paths, not secrets - Docker warning is a false positive
ENV GDRIVE_OAUTH_PATH=/credentials/gcp-oauth.keys.json
ENV GDRIVE_TOKEN_STORAGE_PATH=/credentials/.gdrive-mcp-tokens.json

Check warning on line 36 in Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Security Scan

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "GDRIVE_TOKEN_STORAGE_PATH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 36 in Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Build and Test

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "GDRIVE_TOKEN_STORAGE_PATH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV GDRIVE_TOKEN_AUDIT_LOG_PATH=/app/logs/gdrive-mcp-audit.log

Check warning on line 37 in Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Security Scan

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "GDRIVE_TOKEN_AUDIT_LOG_PATH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 37 in Dockerfile

View workflow job for this annotation

GitHub Actions / Docker Build and Test

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "GDRIVE_TOKEN_AUDIT_LOG_PATH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ENV NODE_ENV=production

# Health check - using the built-in health check functionality
Expand Down
75 changes: 74 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,24 @@
ModifyLabelsOptions,
} from "./src/modules/gmail/index.js";

import type {
ListCalendarsOptions,
GetCalendarOptions,
ListEventsOptions,
GetEventOptions,
CreateEventOptions,
UpdateEventOptions,
DeleteEventOptions,
QuickAddOptions,
FreeBusyOptions,
} from "./src/modules/calendar/index.js";

const drive = google.drive("v3");
const sheets = google.sheets("v4");
const forms = google.forms("v1");
const docs = google.docs("v1");
const gmail = google.gmail("v1");
const calendar = google.calendar("v3");

// Performance monitoring types
interface PerformanceStats {
Expand Down Expand Up @@ -208,7 +221,7 @@
winston.format.colorize(),
winston.format.timestamp(),
winston.format.printf(({ timestamp, level, message, ...meta }) => {
return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? safeStringify(meta) : ''}`;

Check warning on line 224 in index.ts

View workflow job for this annotation

GitHub Actions / Test and Code Quality (22)

Invalid type "unknown" of template literal expression

Check warning on line 224 in index.ts

View workflow job for this annotation

GitHub Actions / Test and Code Quality (22)

Invalid type "unknown" of template literal expression
})
)
})
Expand Down Expand Up @@ -313,7 +326,7 @@
const data = await this.client?.get(key);
if (data) {
performanceMonitor.recordCacheHit();
return JSON.parse(data);

Check warning on line 329 in index.ts

View workflow job for this annotation

GitHub Actions / Test and Code Quality (22)

Unsafe return of a value of type `any`
}
performanceMonitor.recordCacheMiss();
return null;
Expand Down Expand Up @@ -541,6 +554,25 @@
},
required: ["operation", "params"]
}
},
{
name: "calendar",
description: "Google Calendar operations. Read gdrive://tools resource to see available operations.",
inputSchema: {
type: "object",
properties: {
operation: {
type: "string",
enum: ["listCalendars", "getCalendar", "listEvents", "getEvent", "createEvent", "updateEvent", "deleteEvent", "quickAdd", "checkFreeBusy"],
description: "Operation to perform"
},
params: {
type: "object",
description: "Operation-specific parameters. See gdrive://tools for details."
}
},
required: ["operation", "params"]
}
}
]
};
Expand Down Expand Up @@ -568,6 +600,7 @@
forms,
docs,
gmail,
calendar,
cacheManager,
performanceMonitor,
startTime,
Expand Down Expand Up @@ -740,6 +773,43 @@
break;
}

case "calendar": {
const calendarModule = await import('./src/modules/calendar/index.js');

switch (operation) {
case "listCalendars":
result = await calendarModule.listCalendars(params as ListCalendarsOptions, context);
break;
case "getCalendar":
result = await calendarModule.getCalendar(params as GetCalendarOptions, context);
break;
case "listEvents":
result = await calendarModule.listEvents(params as ListEventsOptions, context);
break;
case "getEvent":
result = await calendarModule.getEvent(params as GetEventOptions, context);
break;
case "createEvent":
result = await calendarModule.createEvent(params as CreateEventOptions, context);
break;
case "updateEvent":
result = await calendarModule.updateEvent(params as UpdateEventOptions, context);
break;
case "deleteEvent":
result = await calendarModule.deleteEvent(params as DeleteEventOptions, context);
break;
case "quickAdd":
result = await calendarModule.quickAdd(params as QuickAddOptions, context);
break;
case "checkFreeBusy":
result = await calendarModule.checkFreeBusy(params as FreeBusyOptions, context);
break;
default:
throw new Error(`Unknown calendar operation: ${operation}`);
}
break;
}

default:
throw new Error(`Unknown tool: ${name}`);
}
Expand Down Expand Up @@ -791,7 +861,10 @@
"https://www.googleapis.com/auth/gmail.readonly", // Read operations: listMessages, getMessage, getThread, searchMessages
"https://www.googleapis.com/auth/gmail.send", // messages.send only
"https://www.googleapis.com/auth/gmail.compose", // Draft operations: drafts.create, drafts.send
"https://www.googleapis.com/auth/gmail.modify" // Label/message modification: modifyLabels, listLabels
"https://www.googleapis.com/auth/gmail.modify", // Label/message modification: modifyLabels, listLabels
// Calendar scopes (added in v3.3.0)
"https://www.googleapis.com/auth/calendar.readonly", // Read calendars and events
"https://www.googleapis.com/auth/calendar.events" // Full event CRUD (create, update, delete)
],
});

Expand Down Expand Up @@ -834,8 +907,8 @@
}

const keysContent = fs.readFileSync(oauthPath, "utf-8");
const keys = JSON.parse(keysContent);

Check warning on line 910 in index.ts

View workflow job for this annotation

GitHub Actions / Test and Code Quality (22)

Unsafe assignment of an `any` value
const oauthKeys = keys.web ?? keys.installed;

Check warning on line 911 in index.ts

View workflow job for this annotation

GitHub Actions / Test and Code Quality (22)

Unsafe member access .installed on an `any` value

Check warning on line 911 in index.ts

View workflow job for this annotation

GitHub Actions / Test and Code Quality (22)

Unsafe member access .web on an `any` value

Check warning on line 911 in index.ts

View workflow job for this annotation

GitHub Actions / Test and Code Quality (22)

Unsafe assignment of an `any` value

if (!oauthKeys) {
logger.error("Invalid OAuth keys format. Expected 'web' or 'installed' configuration.");
Expand Down Expand Up @@ -1012,17 +1085,17 @@
});
} else if (process.argv[2] === "auth") {
authenticateAndSaveCredentials().catch((error) => {
logger.error('Unhandled error in auth flow', { error });

Check warning on line 1088 in index.ts

View workflow job for this annotation

GitHub Actions / Test and Code Quality (22)

Unsafe assignment of an `any` value
process.exit(1);
});
} else if (process.argv[2] === "rotate-key") {
rotateKey().catch((error) => {
logger.error('Unhandled error in rotate-key', { error });

Check warning on line 1093 in index.ts

View workflow job for this annotation

GitHub Actions / Test and Code Quality (22)

Unsafe assignment of an `any` value
process.exit(1);
});
} else if (process.argv[2] === "migrate-tokens") {
migrateTokens().catch((error) => {
logger.error('Unhandled error in migrate-tokens', { error });

Check warning on line 1098 in index.ts

View workflow job for this annotation

GitHub Actions / Test and Code Quality (22)

Unsafe assignment of an `any` value
process.exit(1);
});
} else if (process.argv[2] === "verify-keys") {
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@modelcontextprotocol/server-gdrive",
"version": "3.2.0",
"description": "MCP server for Google Workspace with operation-based progressive disclosure - direct API access to Drive, Sheets, Forms, Docs, and Gmail",
"version": "3.3.0",
"description": "MCP server for Google Workspace with operation-based progressive disclosure - direct API access to Drive, Sheets, Forms, Docs, Gmail, and Calendar",
"license": "MIT",
"author": "Anthropic, PBC (https://anthropic.com)",
"homepage": "https://modelcontextprotocol.io",
Expand Down
Loading
Loading