gmail-workspace-mcp-server 0.0.3

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 ADDED
@@ -0,0 +1,141 @@
1
+ # Gmail Workspace MCP Server
2
+
3
+ An MCP (Model Context Protocol) server that provides Gmail integration for AI assistants using Google Workspace service accounts with domain-wide delegation.
4
+
5
+ ## Features
6
+
7
+ - **List Recent Emails**: Retrieve recent emails within a specified time horizon
8
+ - **Get Email Details**: Fetch full email content including body and attachments info
9
+ - **Service Account Authentication**: Secure domain-wide delegation for Google Workspace organizations
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install gmail-workspace-mcp-server
15
+ ```
16
+
17
+ Or run directly with npx:
18
+
19
+ ```bash
20
+ npx gmail-workspace-mcp-server
21
+ ```
22
+
23
+ ## Prerequisites
24
+
25
+ This server requires a Google Cloud service account with domain-wide delegation to access Gmail on behalf of users in your Google Workspace domain.
26
+
27
+ ### Setup Steps
28
+
29
+ 1. Go to [Google Cloud Console](https://console.cloud.google.com/)
30
+ 2. Create or select a project
31
+ 3. Enable the Gmail API
32
+ 4. Create a service account with domain-wide delegation enabled
33
+ 5. In [Google Workspace Admin Console](https://admin.google.com/), grant the service account access to the `https://www.googleapis.com/auth/gmail.readonly` scope
34
+ 6. Download the JSON key file
35
+
36
+ ### Environment Variables
37
+
38
+ | Variable | Required | Description |
39
+ | ------------------------------------ | -------- | ---------------------------------------- |
40
+ | `GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL` | Yes | Service account email address |
41
+ | `GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY` | Yes | Service account private key (PEM format) |
42
+ | `GMAIL_IMPERSONATE_EMAIL` | Yes | Email address to impersonate |
43
+
44
+ You can find the `client_email` and `private_key` values in your service account JSON key file.
45
+
46
+ ## Configuration
47
+
48
+ ### Claude Desktop
49
+
50
+ Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`):
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "gmail": {
56
+ "command": "npx",
57
+ "args": ["gmail-workspace-mcp-server"],
58
+ "env": {
59
+ "GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL": "my-service-account@my-project.iam.gserviceaccount.com",
60
+ "GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----",
61
+ "GMAIL_IMPERSONATE_EMAIL": "user@yourdomain.com"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ **Note:** For the private key, you can either:
69
+
70
+ 1. Use the key directly with `\n` for newlines (as shown above)
71
+ 2. Set the environment variable from a shell that preserves newlines
72
+
73
+ ## Available Tools
74
+
75
+ ### gmail_list_recent_emails
76
+
77
+ List recent emails from Gmail within a specified time horizon.
78
+
79
+ **Parameters:**
80
+
81
+ - `hours` (number, optional): Time horizon in hours (default: 24)
82
+ - `labels` (string, optional): Comma-separated label IDs (default: "INBOX")
83
+ - `max_results` (number, optional): Maximum emails to return (default: 10, max: 100)
84
+
85
+ **Example:**
86
+
87
+ ```json
88
+ {
89
+ "hours": 48,
90
+ "labels": "INBOX,STARRED",
91
+ "max_results": 20
92
+ }
93
+ ```
94
+
95
+ ### gmail_get_email
96
+
97
+ Retrieve the full content of a specific email by its ID.
98
+
99
+ **Parameters:**
100
+
101
+ - `email_id` (string, required): The unique identifier of the email
102
+
103
+ **Example:**
104
+
105
+ ```json
106
+ {
107
+ "email_id": "18abc123def456"
108
+ }
109
+ ```
110
+
111
+ ## Development
112
+
113
+ ### Setup
114
+
115
+ ```bash
116
+ # Install dependencies
117
+ npm run install-all
118
+
119
+ # Build
120
+ npm run build
121
+
122
+ # Run in development mode
123
+ npm run dev
124
+ ```
125
+
126
+ ### Testing
127
+
128
+ ```bash
129
+ # Run functional tests
130
+ npm test
131
+
132
+ # Run integration tests
133
+ npm run test:integration
134
+
135
+ # Run manual tests (requires service account credentials)
136
+ npm run test:manual
137
+ ```
138
+
139
+ ## License
140
+
141
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Integration test entry point with mock data
4
+ * Used for running integration tests without real Gmail API access
5
+ */
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { createMCPServer } from '../shared/index.js';
8
+ import { logServerStart, logError } from '../shared/logging.js';
9
+ // =============================================================================
10
+ // MOCK DATA
11
+ // =============================================================================
12
+ const MOCK_EMAILS = [
13
+ {
14
+ id: 'msg_001',
15
+ threadId: 'thread_001',
16
+ labelIds: ['INBOX', 'UNREAD'],
17
+ snippet: 'Hey, just wanted to check in about the project...',
18
+ historyId: '12345',
19
+ internalDate: String(Date.now() - 1000 * 60 * 30), // 30 minutes ago
20
+ payload: {
21
+ mimeType: 'text/plain',
22
+ headers: [
23
+ { name: 'Subject', value: 'Project Update' },
24
+ { name: 'From', value: 'alice@example.com' },
25
+ { name: 'To', value: 'me@example.com' },
26
+ { name: 'Date', value: new Date(Date.now() - 1000 * 60 * 30).toISOString() },
27
+ ],
28
+ body: {
29
+ size: 150,
30
+ data: Buffer.from('Hey, just wanted to check in about the project. How is everything going?').toString('base64url'),
31
+ },
32
+ },
33
+ sizeEstimate: 1024,
34
+ },
35
+ {
36
+ id: 'msg_002',
37
+ threadId: 'thread_002',
38
+ labelIds: ['INBOX'],
39
+ snippet: 'Meeting reminder for tomorrow at 2pm...',
40
+ historyId: '12346',
41
+ internalDate: String(Date.now() - 1000 * 60 * 60 * 2), // 2 hours ago
42
+ payload: {
43
+ mimeType: 'text/plain',
44
+ headers: [
45
+ { name: 'Subject', value: 'Meeting Reminder' },
46
+ { name: 'From', value: 'calendar@example.com' },
47
+ { name: 'To', value: 'me@example.com' },
48
+ { name: 'Date', value: new Date(Date.now() - 1000 * 60 * 60 * 2).toISOString() },
49
+ ],
50
+ body: {
51
+ size: 200,
52
+ data: Buffer.from('Meeting reminder for tomorrow at 2pm. Please confirm your attendance.').toString('base64url'),
53
+ },
54
+ },
55
+ sizeEstimate: 2048,
56
+ },
57
+ ];
58
+ // =============================================================================
59
+ // MOCK CLIENT
60
+ // =============================================================================
61
+ function createMockClient() {
62
+ return {
63
+ async listMessages(options) {
64
+ // Filter by label if specified
65
+ let filtered = MOCK_EMAILS;
66
+ if (options?.labelIds && options.labelIds.length > 0) {
67
+ filtered = MOCK_EMAILS.filter((email) => options.labelIds.some((label) => email.labelIds?.includes(label)));
68
+ }
69
+ // Apply maxResults
70
+ const maxResults = options?.maxResults ?? 10;
71
+ const messages = filtered.slice(0, maxResults).map((e) => ({
72
+ id: e.id,
73
+ threadId: e.threadId,
74
+ }));
75
+ return {
76
+ messages,
77
+ resultSizeEstimate: messages.length,
78
+ };
79
+ },
80
+ async getMessage(messageId, _options) {
81
+ const email = MOCK_EMAILS.find((e) => e.id === messageId);
82
+ if (!email) {
83
+ throw new Error(`Message not found: ${messageId}`);
84
+ }
85
+ return email;
86
+ },
87
+ };
88
+ }
89
+ // =============================================================================
90
+ // MAIN ENTRY POINT
91
+ // =============================================================================
92
+ async function main() {
93
+ // Create server with mock client
94
+ const { server, registerHandlers } = createMCPServer();
95
+ // Register handlers with mock client factory
96
+ await registerHandlers(server, createMockClient);
97
+ // Start server with stdio transport
98
+ const transport = new StdioServerTransport();
99
+ await server.connect(transport);
100
+ logServerStart('Gmail (Mock)');
101
+ }
102
+ // Run the server
103
+ main().catch((error) => {
104
+ logError('main', error);
105
+ process.exit(1);
106
+ });
package/build/index.js ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { createMCPServer } from '../shared/index.js';
4
+ import { logServerStart, logError } from '../shared/logging.js';
5
+ // =============================================================================
6
+ // ENVIRONMENT VALIDATION
7
+ // =============================================================================
8
+ function validateEnvironment() {
9
+ const missing = [];
10
+ if (!process.env.GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL) {
11
+ missing.push('GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL');
12
+ }
13
+ if (!process.env.GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY) {
14
+ missing.push('GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY');
15
+ }
16
+ if (!process.env.GMAIL_IMPERSONATE_EMAIL) {
17
+ missing.push('GMAIL_IMPERSONATE_EMAIL');
18
+ }
19
+ if (missing.length > 0) {
20
+ logError('validateEnvironment', 'Missing required environment variables:');
21
+ console.error('\nThis MCP server requires a Google Cloud service account with');
22
+ console.error('domain-wide delegation to access Gmail on behalf of users.');
23
+ console.error('\nRequired environment variables:');
24
+ console.error(' GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL: Service account email address');
25
+ console.error(' Example: my-service-account@my-project.iam.gserviceaccount.com');
26
+ console.error(' GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY: Service account private key (PEM format)');
27
+ console.error(' Example: -----BEGIN PRIVATE KEY-----\\nMIIE...\\n-----END PRIVATE KEY-----');
28
+ console.error(' GMAIL_IMPERSONATE_EMAIL: Email address to impersonate');
29
+ console.error(' Example: user@yourdomain.com');
30
+ console.error('\nSetup steps:');
31
+ console.error(' 1. Go to https://console.cloud.google.com/');
32
+ console.error(' 2. Create a service account with domain-wide delegation');
33
+ console.error(' 3. In Google Workspace Admin, grant gmail.readonly scope');
34
+ console.error(' 4. Download the JSON key file and extract client_email and private_key');
35
+ console.error('\n======================================================\n');
36
+ process.exit(1);
37
+ }
38
+ }
39
+ // =============================================================================
40
+ // MAIN ENTRY POINT
41
+ // =============================================================================
42
+ async function main() {
43
+ // Step 1: Validate environment variables
44
+ validateEnvironment();
45
+ // Step 2: Create server using factory
46
+ const { server, registerHandlers } = createMCPServer();
47
+ // Step 3: Register all handlers (tools)
48
+ await registerHandlers(server);
49
+ // Step 4: Start server with stdio transport
50
+ const transport = new StdioServerTransport();
51
+ await server.connect(transport);
52
+ logServerStart('Gmail');
53
+ }
54
+ // Run the server
55
+ main().catch((error) => {
56
+ logError('main', error);
57
+ process.exit(1);
58
+ });
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "gmail-workspace-mcp-server",
3
+ "version": "0.0.3",
4
+ "description": "MCP server for Gmail integration with service account support",
5
+ "main": "build/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "gmail-workspace-mcp-server": "./build/index.js"
9
+ },
10
+ "files": [
11
+ "build/**/*.js",
12
+ "build/**/*.d.ts",
13
+ "shared/**/*.js",
14
+ "shared/**/*.d.ts",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc && npm run build:integration",
19
+ "build:integration": "tsc -p tsconfig.integration.json",
20
+ "start": "node build/index.js",
21
+ "dev": "tsx src/index.ts",
22
+ "predev": "cd ../shared && npm run build && cd ../local && node setup-dev.js",
23
+ "prebuild": "cd ../shared && npm run build && cd ../local && node setup-dev.js",
24
+ "prepublishOnly": "node prepare-publish.js && node ../scripts/prepare-npm-readme.js",
25
+ "lint": "eslint . --ext .ts,.tsx",
26
+ "lint:fix": "eslint . --ext .ts,.tsx --fix",
27
+ "format": "prettier --write .",
28
+ "format:check": "prettier --check .",
29
+ "stage-publish": "npm version"
30
+ },
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.19.1",
33
+ "google-auth-library": "^10.5.0",
34
+ "zod": "^3.24.1"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.10.6",
38
+ "tsx": "^4.19.4",
39
+ "typescript": "^5.7.3"
40
+ },
41
+ "keywords": [
42
+ "mcp",
43
+ "gmail",
44
+ "model-context-protocol"
45
+ ],
46
+ "author": "PulseMCP",
47
+ "license": "MIT"
48
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Handles Gmail API errors with structured error messages
3
+ * @param status HTTP status code
4
+ * @param operation Description of the operation that failed
5
+ * @param resourceId Optional resource identifier (e.g., message ID)
6
+ */
7
+ export declare function handleApiError(status: number, operation: string, resourceId?: string): never;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Handles Gmail API errors with structured error messages
3
+ * @param status HTTP status code
4
+ * @param operation Description of the operation that failed
5
+ * @param resourceId Optional resource identifier (e.g., message ID)
6
+ */
7
+ export function handleApiError(status, operation, resourceId) {
8
+ if (status === 401) {
9
+ throw new Error('Service account authentication failed. Verify the key file and domain-wide delegation.');
10
+ }
11
+ if (status === 403) {
12
+ throw new Error('Permission denied. Ensure gmail.readonly scope is granted in Google Workspace Admin.');
13
+ }
14
+ if (status === 429) {
15
+ throw new Error('Gmail API rate limit exceeded. Please try again later.');
16
+ }
17
+ if (status === 404) {
18
+ throw new Error(resourceId ? `Resource not found: ${resourceId}` : 'Gmail resource not found.');
19
+ }
20
+ throw new Error(`Gmail API error while ${operation}: HTTP ${status}`);
21
+ }
@@ -0,0 +1,9 @@
1
+ import type { Email } from '../../types.js';
2
+ /**
3
+ * Gets a specific message by ID
4
+ * Supports different format levels for detail control
5
+ */
6
+ export declare function getMessage(baseUrl: string, headers: Record<string, string>, messageId: string, options?: {
7
+ format?: 'minimal' | 'full' | 'raw' | 'metadata';
8
+ metadataHeaders?: string[];
9
+ }): Promise<Email>;
@@ -0,0 +1,24 @@
1
+ import { handleApiError } from './api-errors.js';
2
+ /**
3
+ * Gets a specific message by ID
4
+ * Supports different format levels for detail control
5
+ */
6
+ export async function getMessage(baseUrl, headers, messageId, options) {
7
+ const params = new URLSearchParams();
8
+ if (options?.format) {
9
+ params.set('format', options.format);
10
+ }
11
+ if (options?.metadataHeaders && options.metadataHeaders.length > 0) {
12
+ options.metadataHeaders.forEach((header) => params.append('metadataHeaders', header));
13
+ }
14
+ const queryString = params.toString();
15
+ const url = `${baseUrl}/messages/${messageId}${queryString ? `?${queryString}` : ''}`;
16
+ const response = await fetch(url, {
17
+ method: 'GET',
18
+ headers,
19
+ });
20
+ if (!response.ok) {
21
+ handleApiError(response.status, 'getting message', messageId);
22
+ }
23
+ return (await response.json());
24
+ }
@@ -0,0 +1,15 @@
1
+ import type { EmailListItem } from '../../types.js';
2
+ /**
3
+ * Lists messages matching a query
4
+ * Uses pagination to fetch results
5
+ */
6
+ export declare function listMessages(baseUrl: string, headers: Record<string, string>, options?: {
7
+ q?: string;
8
+ maxResults?: number;
9
+ pageToken?: string;
10
+ labelIds?: string[];
11
+ }): Promise<{
12
+ messages: EmailListItem[];
13
+ nextPageToken?: string;
14
+ resultSizeEstimate?: number;
15
+ }>;
@@ -0,0 +1,35 @@
1
+ import { handleApiError } from './api-errors.js';
2
+ /**
3
+ * Lists messages matching a query
4
+ * Uses pagination to fetch results
5
+ */
6
+ export async function listMessages(baseUrl, headers, options) {
7
+ const params = new URLSearchParams();
8
+ if (options?.q) {
9
+ params.set('q', options.q);
10
+ }
11
+ if (options?.maxResults) {
12
+ params.set('maxResults', options.maxResults.toString());
13
+ }
14
+ if (options?.pageToken) {
15
+ params.set('pageToken', options.pageToken);
16
+ }
17
+ if (options?.labelIds && options.labelIds.length > 0) {
18
+ options.labelIds.forEach((labelId) => params.append('labelIds', labelId));
19
+ }
20
+ const queryString = params.toString();
21
+ const url = `${baseUrl}/messages${queryString ? `?${queryString}` : ''}`;
22
+ const response = await fetch(url, {
23
+ method: 'GET',
24
+ headers,
25
+ });
26
+ if (!response.ok) {
27
+ handleApiError(response.status, 'listing messages');
28
+ }
29
+ const data = (await response.json());
30
+ return {
31
+ messages: data.messages ?? [],
32
+ nextPageToken: data.nextPageToken,
33
+ resultSizeEstimate: data.resultSizeEstimate,
34
+ };
35
+ }
@@ -0,0 +1,4 @@
1
+ export { createMCPServer, createDefaultClient, ServiceAccountGmailClient, type IGmailClient, type ClientFactory, type ServiceAccountCredentials, } from './server.js';
2
+ export { createRegisterTools, registerTools } from './tools.js';
3
+ export { logServerStart, logError, logWarning, logDebug } from './logging.js';
4
+ export type { Email, EmailListItem, EmailHeader, EmailPart, Label, Thread, PaginatedResponse, } from './types.js';
@@ -0,0 +1,7 @@
1
+ // Main exports for Gmail MCP Server
2
+ // Server and client
3
+ export { createMCPServer, createDefaultClient, ServiceAccountGmailClient, } from './server.js';
4
+ // Tools
5
+ export { createRegisterTools, registerTools } from './tools.js';
6
+ // Logging utilities
7
+ export { logServerStart, logError, logWarning, logDebug } from './logging.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Centralized logging utilities for Gmail MCP Server
3
+ *
4
+ * IMPORTANT: All logging uses console.error() to write to stderr.
5
+ * The MCP protocol requires stdout to contain only JSON messages.
6
+ */
7
+ export declare function logServerStart(serverName: string, transport?: string): void;
8
+ export declare function logError(context: string, error: unknown): void;
9
+ export declare function logWarning(context: string, message: string): void;
10
+ export declare function logDebug(context: string, message: string): void;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Centralized logging utilities for Gmail MCP Server
3
+ *
4
+ * IMPORTANT: All logging uses console.error() to write to stderr.
5
+ * The MCP protocol requires stdout to contain only JSON messages.
6
+ */
7
+ export function logServerStart(serverName, transport = 'stdio') {
8
+ console.error(`MCP server ${serverName} running on ${transport}`);
9
+ }
10
+ export function logError(context, error) {
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ console.error(`[ERROR] ${context}: ${message}`);
13
+ }
14
+ export function logWarning(context, message) {
15
+ console.error(`[WARN] ${context}: ${message}`);
16
+ }
17
+ export function logDebug(context, message) {
18
+ if (process.env.NODE_ENV === 'development' || process.env.DEBUG) {
19
+ console.error(`[DEBUG] ${context}: ${message}`);
20
+ }
21
+ }
@@ -0,0 +1,117 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import type { Email, EmailListItem } from './types.js';
3
+ /**
4
+ * Gmail API client interface
5
+ * Defines all methods for interacting with the Gmail API
6
+ */
7
+ export interface IGmailClient {
8
+ /**
9
+ * List messages matching a query
10
+ */
11
+ listMessages(options?: {
12
+ q?: string;
13
+ maxResults?: number;
14
+ pageToken?: string;
15
+ labelIds?: string[];
16
+ }): Promise<{
17
+ messages: EmailListItem[];
18
+ nextPageToken?: string;
19
+ resultSizeEstimate?: number;
20
+ }>;
21
+ /**
22
+ * Get a specific message by ID
23
+ */
24
+ getMessage(messageId: string, options?: {
25
+ format?: 'minimal' | 'full' | 'raw' | 'metadata';
26
+ metadataHeaders?: string[];
27
+ }): Promise<Email>;
28
+ }
29
+ /**
30
+ * Service account credentials structure
31
+ */
32
+ export interface ServiceAccountCredentials {
33
+ type: string;
34
+ project_id: string;
35
+ private_key_id: string;
36
+ private_key: string;
37
+ client_email: string;
38
+ client_id: string;
39
+ auth_uri: string;
40
+ token_uri: string;
41
+ auth_provider_x509_cert_url: string;
42
+ client_x509_cert_url: string;
43
+ }
44
+ /**
45
+ * Gmail API client implementation using service account with domain-wide delegation
46
+ */
47
+ export declare class ServiceAccountGmailClient implements IGmailClient {
48
+ private impersonateEmail;
49
+ private baseUrl;
50
+ private jwtClient;
51
+ private cachedToken;
52
+ private tokenExpiry;
53
+ private refreshPromise;
54
+ constructor(credentials: ServiceAccountCredentials, impersonateEmail: string);
55
+ private refreshToken;
56
+ private getHeaders;
57
+ listMessages(options?: {
58
+ q?: string;
59
+ maxResults?: number;
60
+ pageToken?: string;
61
+ labelIds?: string[];
62
+ }): Promise<{
63
+ messages: EmailListItem[];
64
+ nextPageToken?: string;
65
+ resultSizeEstimate?: number;
66
+ }>;
67
+ getMessage(messageId: string, options?: {
68
+ format?: 'minimal' | 'full' | 'raw' | 'metadata';
69
+ metadataHeaders?: string[];
70
+ }): Promise<Email>;
71
+ }
72
+ export type ClientFactory = () => IGmailClient;
73
+ /**
74
+ * Creates the default Gmail client based on environment variables.
75
+ * Uses service account with domain-wide delegation:
76
+ * - GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL: Service account email address
77
+ * - GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY: Service account private key (PEM format)
78
+ * - GMAIL_IMPERSONATE_EMAIL: Email address to impersonate
79
+ */
80
+ export declare function createDefaultClient(): IGmailClient;
81
+ export declare function createMCPServer(): {
82
+ server: Server<{
83
+ method: string;
84
+ params?: {
85
+ [x: string]: unknown;
86
+ _meta?: {
87
+ [x: string]: unknown;
88
+ progressToken?: string | number | undefined;
89
+ "io.modelcontextprotocol/related-task"?: {
90
+ taskId: string;
91
+ } | undefined;
92
+ } | undefined;
93
+ } | undefined;
94
+ }, {
95
+ method: string;
96
+ params?: {
97
+ [x: string]: unknown;
98
+ _meta?: {
99
+ [x: string]: unknown;
100
+ progressToken?: string | number | undefined;
101
+ "io.modelcontextprotocol/related-task"?: {
102
+ taskId: string;
103
+ } | undefined;
104
+ } | undefined;
105
+ } | undefined;
106
+ }, {
107
+ [x: string]: unknown;
108
+ _meta?: {
109
+ [x: string]: unknown;
110
+ progressToken?: string | number | undefined;
111
+ "io.modelcontextprotocol/related-task"?: {
112
+ taskId: string;
113
+ } | undefined;
114
+ } | undefined;
115
+ }>;
116
+ registerHandlers: (server: Server, clientFactory?: ClientFactory) => Promise<void>;
117
+ };
@@ -0,0 +1,117 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { JWT } from 'google-auth-library';
3
+ import { createRegisterTools } from './tools.js';
4
+ /**
5
+ * Gmail API client implementation using service account with domain-wide delegation
6
+ */
7
+ export class ServiceAccountGmailClient {
8
+ impersonateEmail;
9
+ baseUrl = 'https://gmail.googleapis.com/gmail/v1/users/me';
10
+ jwtClient;
11
+ cachedToken = null;
12
+ tokenExpiry = 0;
13
+ refreshPromise = null;
14
+ constructor(credentials, impersonateEmail) {
15
+ this.impersonateEmail = impersonateEmail;
16
+ this.jwtClient = new JWT({
17
+ email: credentials.client_email,
18
+ key: credentials.private_key,
19
+ scopes: ['https://www.googleapis.com/auth/gmail.readonly'],
20
+ subject: impersonateEmail,
21
+ });
22
+ }
23
+ async refreshToken() {
24
+ const tokenResponse = await this.jwtClient.authorize();
25
+ if (!tokenResponse.access_token) {
26
+ throw new Error('Failed to obtain access token from service account');
27
+ }
28
+ this.cachedToken = tokenResponse.access_token;
29
+ // Token typically expires in 1 hour, but use the actual expiry if provided
30
+ this.tokenExpiry = tokenResponse.expiry_date || Date.now() + 3600000;
31
+ }
32
+ async getHeaders() {
33
+ // Check if we have a valid cached token (with 60 second buffer)
34
+ if (this.cachedToken && Date.now() < this.tokenExpiry - 60000) {
35
+ return {
36
+ Authorization: `Bearer ${this.cachedToken}`,
37
+ 'Content-Type': 'application/json',
38
+ };
39
+ }
40
+ // Use mutex pattern to prevent concurrent token refresh
41
+ if (!this.refreshPromise) {
42
+ this.refreshPromise = this.refreshToken().finally(() => {
43
+ this.refreshPromise = null;
44
+ });
45
+ }
46
+ await this.refreshPromise;
47
+ return {
48
+ Authorization: `Bearer ${this.cachedToken}`,
49
+ 'Content-Type': 'application/json',
50
+ };
51
+ }
52
+ async listMessages(options) {
53
+ const headers = await this.getHeaders();
54
+ const { listMessages } = await import('./gmail-client/lib/list-messages.js');
55
+ return listMessages(this.baseUrl, headers, options);
56
+ }
57
+ async getMessage(messageId, options) {
58
+ const headers = await this.getHeaders();
59
+ const { getMessage } = await import('./gmail-client/lib/get-message.js');
60
+ return getMessage(this.baseUrl, headers, messageId, options);
61
+ }
62
+ }
63
+ /**
64
+ * Creates the default Gmail client based on environment variables.
65
+ * Uses service account with domain-wide delegation:
66
+ * - GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL: Service account email address
67
+ * - GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY: Service account private key (PEM format)
68
+ * - GMAIL_IMPERSONATE_EMAIL: Email address to impersonate
69
+ */
70
+ export function createDefaultClient() {
71
+ const clientEmail = process.env.GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL;
72
+ // Handle both literal \n in JSON configs and actual newlines
73
+ const privateKey = process.env.GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY?.replace(/\\n/g, '\n');
74
+ const impersonateEmail = process.env.GMAIL_IMPERSONATE_EMAIL;
75
+ if (!clientEmail) {
76
+ throw new Error('GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL environment variable must be set. ' +
77
+ 'This is the email address from your Google Cloud service account.');
78
+ }
79
+ if (!privateKey) {
80
+ throw new Error('GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY environment variable must be set. ' +
81
+ 'This is the private key from your Google Cloud service account (PEM format).');
82
+ }
83
+ if (!impersonateEmail) {
84
+ throw new Error('GMAIL_IMPERSONATE_EMAIL environment variable must be set. ' +
85
+ 'This is the email address of the user to access Gmail as.');
86
+ }
87
+ const credentials = {
88
+ type: 'service_account',
89
+ project_id: '',
90
+ private_key_id: '',
91
+ private_key: privateKey,
92
+ client_email: clientEmail,
93
+ client_id: '',
94
+ auth_uri: 'https://accounts.google.com/o/oauth2/auth',
95
+ token_uri: 'https://oauth2.googleapis.com/token',
96
+ auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs',
97
+ client_x509_cert_url: '',
98
+ };
99
+ return new ServiceAccountGmailClient(credentials, impersonateEmail);
100
+ }
101
+ export function createMCPServer() {
102
+ const server = new Server({
103
+ name: 'gmail-workspace-mcp-server',
104
+ version: '0.0.2',
105
+ }, {
106
+ capabilities: {
107
+ tools: {},
108
+ },
109
+ });
110
+ const registerHandlers = async (server, clientFactory) => {
111
+ // Use provided factory or create default client
112
+ const factory = clientFactory || createDefaultClient;
113
+ const registerTools = createRegisterTools(factory);
114
+ registerTools(server);
115
+ };
116
+ return { server, registerHandlers };
117
+ }
@@ -0,0 +1,37 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { z } from 'zod';
3
+ import type { ClientFactory } from '../server.js';
4
+ export declare const GetEmailSchema: z.ZodObject<{
5
+ email_id: z.ZodString;
6
+ }, "strip", z.ZodTypeAny, {
7
+ email_id: string;
8
+ }, {
9
+ email_id: string;
10
+ }>;
11
+ export declare function getEmailTool(server: Server, clientFactory: ClientFactory): {
12
+ name: string;
13
+ description: string;
14
+ inputSchema: {
15
+ type: "object";
16
+ properties: {
17
+ email_id: {
18
+ type: string;
19
+ description: string;
20
+ };
21
+ };
22
+ required: string[];
23
+ };
24
+ handler: (args: unknown) => Promise<{
25
+ content: {
26
+ type: string;
27
+ text: string;
28
+ }[];
29
+ isError?: undefined;
30
+ } | {
31
+ content: {
32
+ type: string;
33
+ text: string;
34
+ }[];
35
+ isError: boolean;
36
+ }>;
37
+ };
@@ -0,0 +1,192 @@
1
+ import { z } from 'zod';
2
+ import { getHeader } from '../utils/email-helpers.js';
3
+ const PARAM_DESCRIPTIONS = {
4
+ email_id: 'The unique identifier of the email to retrieve. ' +
5
+ 'Obtain this from gmail_list_recent_emails.',
6
+ };
7
+ export const GetEmailSchema = z.object({
8
+ email_id: z.string().min(1).describe(PARAM_DESCRIPTIONS.email_id),
9
+ });
10
+ const TOOL_DESCRIPTION = `Retrieve the full content of a specific email by its ID.
11
+
12
+ Returns the complete email including headers, body content, and attachment information.
13
+
14
+ **Parameters:**
15
+ - email_id: The unique identifier of the email (required)
16
+
17
+ **Returns:**
18
+ Full email details including:
19
+ - Subject, From, To, Cc, Date headers
20
+ - Full message body (plain text preferred, HTML as fallback)
21
+ - List of attachments (if any)
22
+ - Labels assigned to the email
23
+
24
+ **Use cases:**
25
+ - Read the full content of an email after listing recent emails
26
+ - Extract specific information from an email body
27
+ - Check attachment details
28
+
29
+ **Note:** Use gmail_list_recent_emails first to get email IDs.`;
30
+ /**
31
+ * Decodes base64url encoded content
32
+ */
33
+ function decodeBase64Url(data) {
34
+ // Convert base64url to base64
35
+ const base64 = data.replace(/-/g, '+').replace(/_/g, '/');
36
+ // Decode from base64
37
+ return Buffer.from(base64, 'base64').toString('utf-8');
38
+ }
39
+ /**
40
+ * Recursively extracts body content from email parts
41
+ * Prefers text/plain over text/html
42
+ */
43
+ function extractBodyContent(parts, preferredType = 'text/plain') {
44
+ if (!parts)
45
+ return null;
46
+ // First pass: look for exact match
47
+ for (const part of parts) {
48
+ if (part.mimeType === preferredType && part.body?.data) {
49
+ return decodeBase64Url(part.body.data);
50
+ }
51
+ if (part.parts) {
52
+ const nested = extractBodyContent(part.parts, preferredType);
53
+ if (nested)
54
+ return nested;
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ /**
60
+ * Gets the body content from an email
61
+ */
62
+ function getEmailBody(email) {
63
+ // Check if body is directly on payload
64
+ if (email.payload?.body?.data) {
65
+ return decodeBase64Url(email.payload.body.data);
66
+ }
67
+ // Try to extract from parts
68
+ if (email.payload?.parts) {
69
+ // Prefer plain text
70
+ const plainText = extractBodyContent(email.payload.parts, 'text/plain');
71
+ if (plainText)
72
+ return plainText;
73
+ // Fall back to HTML
74
+ const html = extractBodyContent(email.payload.parts, 'text/html');
75
+ if (html) {
76
+ // Strip HTML tags for readability
77
+ return html
78
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
79
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
80
+ .replace(/<[^>]+>/g, ' ')
81
+ .replace(/\s+/g, ' ')
82
+ .trim();
83
+ }
84
+ }
85
+ return '(No body content available)';
86
+ }
87
+ /**
88
+ * Extracts attachment information from email parts
89
+ */
90
+ function getAttachments(parts) {
91
+ if (!parts)
92
+ return [];
93
+ const attachments = [];
94
+ for (const part of parts) {
95
+ if (part.filename && part.body?.attachmentId) {
96
+ attachments.push({
97
+ filename: part.filename,
98
+ mimeType: part.mimeType,
99
+ size: part.body.size,
100
+ });
101
+ }
102
+ if (part.parts) {
103
+ attachments.push(...getAttachments(part.parts));
104
+ }
105
+ }
106
+ return attachments;
107
+ }
108
+ /**
109
+ * Formats an email for display
110
+ */
111
+ function formatFullEmail(email) {
112
+ const subject = getHeader(email, 'Subject') || '(No Subject)';
113
+ const from = getHeader(email, 'From') || 'Unknown';
114
+ const to = getHeader(email, 'To') || 'Unknown';
115
+ const cc = getHeader(email, 'Cc');
116
+ const date = getHeader(email, 'Date') || 'Unknown date';
117
+ const body = getEmailBody(email);
118
+ const attachments = getAttachments(email.payload?.parts);
119
+ const labels = email.labelIds?.join(', ') || 'None';
120
+ let output = `# Email Details
121
+
122
+ **ID:** ${email.id}
123
+ **Thread ID:** ${email.threadId}
124
+
125
+ ## Headers
126
+ **Subject:** ${subject}
127
+ **From:** ${from}
128
+ **To:** ${to}`;
129
+ if (cc) {
130
+ output += `\n**Cc:** ${cc}`;
131
+ }
132
+ output += `
133
+ **Date:** ${date}
134
+ **Labels:** ${labels}
135
+
136
+ ## Body
137
+
138
+ ${body}`;
139
+ if (attachments.length > 0) {
140
+ output += `\n\n## Attachments (${attachments.length})\n`;
141
+ attachments.forEach((att, i) => {
142
+ const sizeKb = Math.round(att.size / 1024);
143
+ output += `${i + 1}. ${att.filename} (${att.mimeType}, ${sizeKb} KB)\n`;
144
+ });
145
+ }
146
+ return output;
147
+ }
148
+ export function getEmailTool(server, clientFactory) {
149
+ return {
150
+ name: 'gmail_get_email',
151
+ description: TOOL_DESCRIPTION,
152
+ inputSchema: {
153
+ type: 'object',
154
+ properties: {
155
+ email_id: {
156
+ type: 'string',
157
+ description: PARAM_DESCRIPTIONS.email_id,
158
+ },
159
+ },
160
+ required: ['email_id'],
161
+ },
162
+ handler: async (args) => {
163
+ try {
164
+ const parsed = GetEmailSchema.parse(args ?? {});
165
+ const client = clientFactory();
166
+ const email = await client.getMessage(parsed.email_id, {
167
+ format: 'full',
168
+ });
169
+ const formattedEmail = formatFullEmail(email);
170
+ return {
171
+ content: [
172
+ {
173
+ type: 'text',
174
+ text: formattedEmail,
175
+ },
176
+ ],
177
+ };
178
+ }
179
+ catch (error) {
180
+ return {
181
+ content: [
182
+ {
183
+ type: 'text',
184
+ text: `Error retrieving email: ${error instanceof Error ? error.message : 'Unknown error'}`,
185
+ },
186
+ ],
187
+ isError: true,
188
+ };
189
+ }
190
+ },
191
+ };
192
+ }
@@ -0,0 +1,54 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { z } from 'zod';
3
+ import type { ClientFactory } from '../server.js';
4
+ export declare const ListRecentEmailsSchema: z.ZodObject<{
5
+ hours: z.ZodDefault<z.ZodNumber>;
6
+ labels: z.ZodDefault<z.ZodOptional<z.ZodString>>;
7
+ max_results: z.ZodDefault<z.ZodNumber>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ hours: number;
10
+ labels: string;
11
+ max_results: number;
12
+ }, {
13
+ hours?: number | undefined;
14
+ labels?: string | undefined;
15
+ max_results?: number | undefined;
16
+ }>;
17
+ export declare function listRecentEmailsTool(server: Server, clientFactory: ClientFactory): {
18
+ name: string;
19
+ description: string;
20
+ inputSchema: {
21
+ type: "object";
22
+ properties: {
23
+ hours: {
24
+ type: string;
25
+ default: number;
26
+ description: string;
27
+ };
28
+ labels: {
29
+ type: string;
30
+ default: string;
31
+ description: string;
32
+ };
33
+ max_results: {
34
+ type: string;
35
+ default: number;
36
+ description: "Maximum number of emails to return. Default: 10. Max: 100.";
37
+ };
38
+ };
39
+ required: never[];
40
+ };
41
+ handler: (args: unknown) => Promise<{
42
+ content: {
43
+ type: string;
44
+ text: string;
45
+ }[];
46
+ isError?: undefined;
47
+ } | {
48
+ content: {
49
+ type: string;
50
+ text: string;
51
+ }[];
52
+ isError: boolean;
53
+ }>;
54
+ };
@@ -0,0 +1,133 @@
1
+ import { z } from 'zod';
2
+ import { getHeader } from '../utils/email-helpers.js';
3
+ const PARAM_DESCRIPTIONS = {
4
+ hours: 'Time horizon in hours to look back for emails. Default: 24. ' +
5
+ 'Example: 48 for the last 2 days.',
6
+ labels: 'Comma-separated list of label IDs to filter by. Default: INBOX. ' +
7
+ 'Common labels: INBOX, SENT, DRAFTS, SPAM, TRASH, STARRED, IMPORTANT, UNREAD.',
8
+ max_results: 'Maximum number of emails to return. Default: 10. Max: 100.',
9
+ };
10
+ export const ListRecentEmailsSchema = z.object({
11
+ hours: z.number().positive().default(24).describe(PARAM_DESCRIPTIONS.hours),
12
+ labels: z.string().optional().default('INBOX').describe(PARAM_DESCRIPTIONS.labels),
13
+ max_results: z.number().positive().max(100).default(10).describe(PARAM_DESCRIPTIONS.max_results),
14
+ });
15
+ const TOOL_DESCRIPTION = `List recent emails from Gmail within a specified time horizon.
16
+
17
+ Returns a list of recent emails with their subject, sender, date, and a snippet preview. Use get_email to retrieve the full content of a specific email.
18
+
19
+ **Parameters:**
20
+ - hours: How far back to look for emails (default: 24 hours)
21
+ - labels: Which labels/folders to search (default: INBOX)
22
+ - max_results: Maximum emails to return (default: 10, max: 100)
23
+
24
+ **Returns:**
25
+ A formatted list of emails with:
26
+ - Email ID (needed for get_email)
27
+ - Subject line
28
+ - Sender (From)
29
+ - Date received
30
+ - Snippet preview
31
+
32
+ **Use cases:**
33
+ - Check recent inbox activity
34
+ - Monitor for new emails in a time window
35
+ - List recent emails from specific labels like SENT or STARRED
36
+
37
+ **Note:** This tool only returns email metadata and snippets. Use get_email with an email ID to retrieve the full message content.`;
38
+ /**
39
+ * Formats an email for display
40
+ */
41
+ function formatEmail(email) {
42
+ const subject = getHeader(email, 'Subject') || '(No Subject)';
43
+ const from = getHeader(email, 'From') || 'Unknown';
44
+ const date = getHeader(email, 'Date') || 'Unknown date';
45
+ const snippet = email.snippet || '';
46
+ return `**ID:** ${email.id}
47
+ **Subject:** ${subject}
48
+ **From:** ${from}
49
+ **Date:** ${date}
50
+ **Preview:** ${snippet}`;
51
+ }
52
+ export function listRecentEmailsTool(server, clientFactory) {
53
+ return {
54
+ name: 'gmail_list_recent_emails',
55
+ description: TOOL_DESCRIPTION,
56
+ inputSchema: {
57
+ type: 'object',
58
+ properties: {
59
+ hours: {
60
+ type: 'number',
61
+ default: 24,
62
+ description: PARAM_DESCRIPTIONS.hours,
63
+ },
64
+ labels: {
65
+ type: 'string',
66
+ default: 'INBOX',
67
+ description: PARAM_DESCRIPTIONS.labels,
68
+ },
69
+ max_results: {
70
+ type: 'number',
71
+ default: 10,
72
+ description: PARAM_DESCRIPTIONS.max_results,
73
+ },
74
+ },
75
+ required: [],
76
+ },
77
+ handler: async (args) => {
78
+ try {
79
+ const parsed = ListRecentEmailsSchema.parse(args ?? {});
80
+ const client = clientFactory();
81
+ // Calculate the timestamp for the time horizon
82
+ const now = new Date();
83
+ const cutoffDate = new Date(now.getTime() - parsed.hours * 60 * 60 * 1000);
84
+ const afterTimestamp = Math.floor(cutoffDate.getTime() / 1000);
85
+ // Build the Gmail query
86
+ const query = `after:${afterTimestamp}`;
87
+ // Parse labels
88
+ const labelIds = parsed.labels.split(',').map((l) => l.trim().toUpperCase());
89
+ // List messages
90
+ const { messages } = await client.listMessages({
91
+ q: query,
92
+ maxResults: parsed.max_results,
93
+ labelIds,
94
+ });
95
+ if (messages.length === 0) {
96
+ return {
97
+ content: [
98
+ {
99
+ type: 'text',
100
+ text: `No emails found in the last ${parsed.hours} hour(s) with labels: ${labelIds.join(', ')}`,
101
+ },
102
+ ],
103
+ };
104
+ }
105
+ // Fetch full details for each message
106
+ const emailDetails = await Promise.all(messages.map((msg) => client.getMessage(msg.id, {
107
+ format: 'metadata',
108
+ metadataHeaders: ['Subject', 'From', 'Date'],
109
+ })));
110
+ const formattedEmails = emailDetails.map(formatEmail).join('\n\n---\n\n');
111
+ return {
112
+ content: [
113
+ {
114
+ type: 'text',
115
+ text: `Found ${messages.length} email(s) in the last ${parsed.hours} hour(s):\n\n${formattedEmails}`,
116
+ },
117
+ ],
118
+ };
119
+ }
120
+ catch (error) {
121
+ return {
122
+ content: [
123
+ {
124
+ type: 'text',
125
+ text: `Error listing emails: ${error instanceof Error ? error.message : 'Unknown error'}`,
126
+ },
127
+ ],
128
+ isError: true,
129
+ };
130
+ }
131
+ },
132
+ };
133
+ }
@@ -0,0 +1,10 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { ClientFactory } from './server.js';
3
+ /**
4
+ * Creates a function to register all tools with the server
5
+ */
6
+ export declare function createRegisterTools(clientFactory: ClientFactory): (server: Server) => void;
7
+ /**
8
+ * Backward compatibility export
9
+ */
10
+ export declare function registerTools(server: Server): void;
@@ -0,0 +1,45 @@
1
+ import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
2
+ import { listRecentEmailsTool } from './tools/list-recent-emails.js';
3
+ import { getEmailTool } from './tools/get-email.js';
4
+ /**
5
+ * All available tools
6
+ */
7
+ const ALL_TOOLS = [listRecentEmailsTool, getEmailTool];
8
+ /**
9
+ * Creates a function to register all tools with the server
10
+ */
11
+ export function createRegisterTools(clientFactory) {
12
+ return (server) => {
13
+ // Create tool instances
14
+ const tools = ALL_TOOLS.map((factory) => factory(server, clientFactory));
15
+ // List available tools
16
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
17
+ return {
18
+ tools: tools.map((tool) => ({
19
+ name: tool.name,
20
+ description: tool.description,
21
+ inputSchema: tool.inputSchema,
22
+ })),
23
+ };
24
+ });
25
+ // Handle tool calls
26
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
27
+ const { name, arguments: args } = request.params;
28
+ const tool = tools.find((t) => t.name === name);
29
+ if (!tool) {
30
+ throw new Error(`Unknown tool: ${name}`);
31
+ }
32
+ return await tool.handler(args);
33
+ });
34
+ };
35
+ }
36
+ /**
37
+ * Backward compatibility export
38
+ */
39
+ export function registerTools(server) {
40
+ const factory = () => {
41
+ throw new Error('No client factory provided - use createRegisterTools for dependency injection');
42
+ };
43
+ const register = createRegisterTools(factory);
44
+ register(server);
45
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Gmail API Types
3
+ * Based on Gmail API responses
4
+ */
5
+ export interface EmailHeader {
6
+ name: string;
7
+ value: string;
8
+ }
9
+ export interface EmailPart {
10
+ partId: string;
11
+ mimeType: string;
12
+ filename?: string;
13
+ headers?: EmailHeader[];
14
+ body?: {
15
+ attachmentId?: string;
16
+ size: number;
17
+ data?: string;
18
+ };
19
+ parts?: EmailPart[];
20
+ }
21
+ export interface Email {
22
+ id: string;
23
+ threadId: string;
24
+ labelIds?: string[];
25
+ snippet: string;
26
+ historyId: string;
27
+ internalDate: string;
28
+ payload?: {
29
+ partId?: string;
30
+ mimeType: string;
31
+ filename?: string;
32
+ headers?: EmailHeader[];
33
+ body?: {
34
+ attachmentId?: string;
35
+ size: number;
36
+ data?: string;
37
+ };
38
+ parts?: EmailPart[];
39
+ };
40
+ sizeEstimate?: number;
41
+ }
42
+ export interface EmailListItem {
43
+ id: string;
44
+ threadId: string;
45
+ }
46
+ export interface Label {
47
+ id: string;
48
+ name: string;
49
+ messageListVisibility?: 'show' | 'hide';
50
+ labelListVisibility?: 'labelShow' | 'labelShowIfUnread' | 'labelHide';
51
+ type?: 'system' | 'user';
52
+ color?: {
53
+ textColor: string;
54
+ backgroundColor: string;
55
+ };
56
+ }
57
+ export interface Thread {
58
+ id: string;
59
+ historyId: string;
60
+ messages?: Email[];
61
+ }
62
+ export interface PaginatedResponse<T> {
63
+ items: T[];
64
+ nextPageToken?: string;
65
+ resultSizeEstimate?: number;
66
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Gmail API Types
3
+ * Based on Gmail API responses
4
+ */
5
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { Email } from '../types.js';
2
+ /**
3
+ * Extracts a header value from an email by header name (case-insensitive)
4
+ */
5
+ export declare function getHeader(email: Email, headerName: string): string | undefined;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Extracts a header value from an email by header name (case-insensitive)
3
+ */
4
+ export function getHeader(email, headerName) {
5
+ return email.payload?.headers?.find((h) => h.name.toLowerCase() === headerName.toLowerCase())
6
+ ?.value;
7
+ }