gmail-workspace-mcp-server 0.0.4 → 0.1.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/README.md +106 -35
- package/build/index.js +76 -21
- package/package.json +2 -2
- package/shared/index.d.ts +1 -1
- package/shared/index.js +1 -1
- package/shared/server.d.ts +70 -8
- package/shared/server.js +131 -28
- package/shared/tools/get-email-conversation.d.ts +7 -0
- package/shared/tools/get-email-conversation.js +51 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Gmail Workspace MCP Server
|
|
2
2
|
|
|
3
|
-
An MCP (Model Context Protocol) server that provides Gmail integration for AI assistants
|
|
3
|
+
An MCP (Model Context Protocol) server that provides Gmail integration for AI assistants. Supports both **personal Gmail accounts** (via OAuth2) and **Google Workspace accounts** (via service account with domain-wide delegation).
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ An MCP (Model Context Protocol) server that provides Gmail integration for AI as
|
|
|
10
10
|
- **Manage Emails**: Mark as read/unread, star/unstar, archive, apply labels
|
|
11
11
|
- **Create Drafts**: Compose draft emails with reply support
|
|
12
12
|
- **Send Emails**: Send new emails or replies, directly or from drafts
|
|
13
|
-
- **
|
|
13
|
+
- **Two Auth Methods**: OAuth2 for personal accounts, Service Account for Google Workspace
|
|
14
14
|
|
|
15
15
|
## Installation
|
|
16
16
|
|
|
@@ -24,11 +24,86 @@ Or run directly with npx:
|
|
|
24
24
|
npx gmail-workspace-mcp-server
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
##
|
|
27
|
+
## Authentication
|
|
28
28
|
|
|
29
|
-
This server
|
|
29
|
+
This server supports two authentication modes. Choose the one that matches your account type.
|
|
30
30
|
|
|
31
|
-
###
|
|
31
|
+
### Option 1: OAuth2 (Personal Gmail Accounts)
|
|
32
|
+
|
|
33
|
+
Use this for personal `@gmail.com` accounts or any Google account without Workspace admin access.
|
|
34
|
+
|
|
35
|
+
#### Prerequisites
|
|
36
|
+
|
|
37
|
+
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
38
|
+
2. Create or select a project
|
|
39
|
+
3. Enable the Gmail API
|
|
40
|
+
4. Configure the OAuth consent screen:
|
|
41
|
+
- Choose **External** user type
|
|
42
|
+
- Add yourself as a test user
|
|
43
|
+
- **Publish the app** (click "Publish" on the consent screen) to prevent refresh tokens from expiring every 7 days. No full Google verification is needed for personal use.
|
|
44
|
+
5. Create OAuth 2.0 credentials:
|
|
45
|
+
- Choose **Desktop app** as the application type
|
|
46
|
+
- **Important**: Create new credentials _after_ publishing the app
|
|
47
|
+
6. Copy the Client ID and Client Secret
|
|
48
|
+
|
|
49
|
+
#### Getting a Refresh Token
|
|
50
|
+
|
|
51
|
+
Run the one-time setup script from a clone of the repository:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
git clone https://github.com/pulsemcp/mcp-servers.git
|
|
55
|
+
cd mcp-servers/experimental/gmail
|
|
56
|
+
npx tsx scripts/oauth-setup.ts <client_id> <client_secret>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
You can also pass credentials via environment variables:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
GMAIL_OAUTH_CLIENT_ID=... GMAIL_OAUTH_CLIENT_SECRET=... npx tsx scripts/oauth-setup.ts
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Port conflict?** If port 3000 is already in use, specify a different port:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
PORT=3001 npx tsx scripts/oauth-setup.ts <client_id> <client_secret>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Desktop app credentials automatically allow `http://localhost` redirects on any port, so no additional Google Cloud Console configuration is needed.
|
|
72
|
+
|
|
73
|
+
This will open your browser for Google sign-in and print the refresh token. You only need to do this once — the refresh token does not expire (as long as the OAuth consent screen is published).
|
|
74
|
+
|
|
75
|
+
#### Environment Variables (OAuth2)
|
|
76
|
+
|
|
77
|
+
| Variable | Required | Description |
|
|
78
|
+
| --------------------------- | -------- | -------------------------------------------------- |
|
|
79
|
+
| `GMAIL_OAUTH_CLIENT_ID` | Yes | OAuth2 client ID from Google Cloud Console |
|
|
80
|
+
| `GMAIL_OAUTH_CLIENT_SECRET` | Yes | OAuth2 client secret |
|
|
81
|
+
| `GMAIL_OAUTH_REFRESH_TOKEN` | Yes | Refresh token from the setup script |
|
|
82
|
+
| `GMAIL_ENABLED_TOOLGROUPS` | No | Comma-separated list of tool groups (default: all) |
|
|
83
|
+
|
|
84
|
+
#### Claude Desktop Configuration (OAuth2)
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"mcpServers": {
|
|
89
|
+
"gmail": {
|
|
90
|
+
"command": "npx",
|
|
91
|
+
"args": ["gmail-workspace-mcp-server"],
|
|
92
|
+
"env": {
|
|
93
|
+
"GMAIL_OAUTH_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
|
|
94
|
+
"GMAIL_OAUTH_CLIENT_SECRET": "your-client-secret",
|
|
95
|
+
"GMAIL_OAUTH_REFRESH_TOKEN": "your-refresh-token"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Option 2: Service Account (Google Workspace)
|
|
103
|
+
|
|
104
|
+
Use this for Google Workspace organizations where a domain admin can grant domain-wide delegation.
|
|
105
|
+
|
|
106
|
+
#### Prerequisites
|
|
32
107
|
|
|
33
108
|
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
34
109
|
2. Create or select a project
|
|
@@ -41,7 +116,7 @@ This server requires a Google Cloud service account with domain-wide delegation
|
|
|
41
116
|
- `https://www.googleapis.com/auth/gmail.send` (send emails)
|
|
42
117
|
6. Download the JSON key file
|
|
43
118
|
|
|
44
|
-
|
|
119
|
+
#### Environment Variables (Service Account)
|
|
45
120
|
|
|
46
121
|
| Variable | Required | Description |
|
|
47
122
|
| ------------------------------------ | -------- | ------------------------------------------------------------------- |
|
|
@@ -52,7 +127,30 @@ This server requires a Google Cloud service account with domain-wide delegation
|
|
|
52
127
|
|
|
53
128
|
You can find the `client_email` and `private_key` values in your service account JSON key file.
|
|
54
129
|
|
|
55
|
-
|
|
130
|
+
#### Claude Desktop Configuration (Service Account)
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"mcpServers": {
|
|
135
|
+
"gmail": {
|
|
136
|
+
"command": "npx",
|
|
137
|
+
"args": ["gmail-workspace-mcp-server"],
|
|
138
|
+
"env": {
|
|
139
|
+
"GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL": "my-service-account@my-project.iam.gserviceaccount.com",
|
|
140
|
+
"GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----",
|
|
141
|
+
"GMAIL_IMPERSONATE_EMAIL": "user@yourdomain.com"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Note:** For the private key, you can either:
|
|
149
|
+
|
|
150
|
+
1. Use the key directly with `\n` for newlines (as shown above)
|
|
151
|
+
2. Set the environment variable from a shell that preserves newlines
|
|
152
|
+
|
|
153
|
+
## Tool Groups
|
|
56
154
|
|
|
57
155
|
The server supports three tool groups for permission-based access control:
|
|
58
156
|
|
|
@@ -77,33 +175,6 @@ GMAIL_ENABLED_TOOLGROUPS=readwrite_external
|
|
|
77
175
|
|
|
78
176
|
**Security Note:** The `send_email` tool is in a separate `readwrite_external` group because it can send emails externally, which carries higher risk than internal operations like modifying labels or creating drafts.
|
|
79
177
|
|
|
80
|
-
## Configuration
|
|
81
|
-
|
|
82
|
-
### Claude Desktop
|
|
83
|
-
|
|
84
|
-
Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"mcpServers": {
|
|
89
|
-
"gmail": {
|
|
90
|
-
"command": "npx",
|
|
91
|
-
"args": ["gmail-workspace-mcp-server"],
|
|
92
|
-
"env": {
|
|
93
|
-
"GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL": "my-service-account@my-project.iam.gserviceaccount.com",
|
|
94
|
-
"GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----",
|
|
95
|
-
"GMAIL_IMPERSONATE_EMAIL": "user@yourdomain.com"
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
**Note:** For the private key, you can either:
|
|
103
|
-
|
|
104
|
-
1. Use the key directly with `\n` for newlines (as shown above)
|
|
105
|
-
2. Set the environment variable from a shell that preserves newlines
|
|
106
|
-
|
|
107
178
|
## Available Tools
|
|
108
179
|
|
|
109
180
|
### list_email_conversations
|
|
@@ -262,7 +333,7 @@ npm test
|
|
|
262
333
|
# Run integration tests
|
|
263
334
|
npm run test:integration
|
|
264
335
|
|
|
265
|
-
# Run manual tests (requires
|
|
336
|
+
# Run manual tests (requires credentials)
|
|
266
337
|
npm run test:manual
|
|
267
338
|
```
|
|
268
339
|
|
package/build/index.js
CHANGED
|
@@ -14,40 +14,95 @@ const VERSION = packageJson.version;
|
|
|
14
14
|
// ENVIRONMENT VALIDATION
|
|
15
15
|
// =============================================================================
|
|
16
16
|
function validateEnvironment() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
// Check for OAuth2 credentials
|
|
18
|
+
const hasOAuth2 = process.env.GMAIL_OAUTH_CLIENT_ID &&
|
|
19
|
+
process.env.GMAIL_OAUTH_CLIENT_SECRET &&
|
|
20
|
+
process.env.GMAIL_OAUTH_REFRESH_TOKEN;
|
|
21
|
+
// Check for service account credentials
|
|
22
|
+
const hasServiceAccount = process.env.GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL &&
|
|
23
|
+
process.env.GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY &&
|
|
24
|
+
process.env.GMAIL_IMPERSONATE_EMAIL;
|
|
25
|
+
if (hasOAuth2 || hasServiceAccount) {
|
|
26
|
+
return;
|
|
20
27
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
// Check for partial OAuth2 configuration
|
|
29
|
+
const oauthVars = {
|
|
30
|
+
GMAIL_OAUTH_CLIENT_ID: process.env.GMAIL_OAUTH_CLIENT_ID,
|
|
31
|
+
GMAIL_OAUTH_CLIENT_SECRET: process.env.GMAIL_OAUTH_CLIENT_SECRET,
|
|
32
|
+
GMAIL_OAUTH_REFRESH_TOKEN: process.env.GMAIL_OAUTH_REFRESH_TOKEN,
|
|
33
|
+
};
|
|
34
|
+
const hasPartialOAuth2 = Object.values(oauthVars).some(Boolean);
|
|
35
|
+
if (hasPartialOAuth2) {
|
|
36
|
+
const missingOAuth = Object.entries(oauthVars)
|
|
37
|
+
.filter(([, v]) => !v)
|
|
38
|
+
.map(([k]) => k);
|
|
39
|
+
logError('validateEnvironment', 'Incomplete OAuth2 configuration. Missing:');
|
|
40
|
+
for (const varName of missingOAuth) {
|
|
41
|
+
console.error(` - ${varName}`);
|
|
42
|
+
}
|
|
43
|
+
console.error('\nOAuth2 mode requires all three variables:');
|
|
44
|
+
console.error(' GMAIL_OAUTH_CLIENT_ID: OAuth2 client ID from Google Cloud Console');
|
|
45
|
+
console.error(' GMAIL_OAUTH_CLIENT_SECRET: OAuth2 client secret');
|
|
46
|
+
console.error(' GMAIL_OAUTH_REFRESH_TOKEN: Refresh token from one-time consent flow');
|
|
47
|
+
console.error('\nRun the setup script to obtain a refresh token:');
|
|
48
|
+
console.error(' npx tsx scripts/oauth-setup.ts <client_id> <client_secret>');
|
|
49
|
+
console.error('\n======================================================\n');
|
|
50
|
+
process.exit(1);
|
|
26
51
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
52
|
+
// Check for partial service account configuration
|
|
53
|
+
const serviceAccountVars = {
|
|
54
|
+
GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL: process.env.GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL,
|
|
55
|
+
GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY: process.env.GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY,
|
|
56
|
+
GMAIL_IMPERSONATE_EMAIL: process.env.GMAIL_IMPERSONATE_EMAIL,
|
|
57
|
+
};
|
|
58
|
+
const hasPartialServiceAccount = Object.values(serviceAccountVars).some(Boolean);
|
|
59
|
+
if (hasPartialServiceAccount) {
|
|
60
|
+
const missingServiceAccount = Object.entries(serviceAccountVars)
|
|
61
|
+
.filter(([, v]) => !v)
|
|
62
|
+
.map(([k]) => k);
|
|
63
|
+
logError('validateEnvironment', 'Incomplete service account configuration. Missing:');
|
|
64
|
+
for (const varName of missingServiceAccount) {
|
|
65
|
+
console.error(` - ${varName}`);
|
|
66
|
+
}
|
|
67
|
+
console.error('\nService account mode requires all three variables:');
|
|
32
68
|
console.error(' GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL: Service account email address');
|
|
33
|
-
console.error(' Example: my-service-account@my-project.iam.gserviceaccount.com');
|
|
34
69
|
console.error(' GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY: Service account private key (PEM format)');
|
|
35
|
-
console.error(' Example: -----BEGIN PRIVATE KEY-----\\nMIIE...\\n-----END PRIVATE KEY-----');
|
|
36
70
|
console.error(' GMAIL_IMPERSONATE_EMAIL: Email address to impersonate');
|
|
37
|
-
console.error(' Example: user@yourdomain.com');
|
|
38
71
|
console.error('\nSetup steps:');
|
|
39
72
|
console.error(' 1. Go to https://console.cloud.google.com/');
|
|
40
73
|
console.error(' 2. Create a service account with domain-wide delegation');
|
|
41
74
|
console.error(' 3. In Google Workspace Admin, grant required Gmail API scopes');
|
|
42
75
|
console.error(' 4. Download the JSON key file and extract client_email and private_key');
|
|
43
|
-
console.error('\nOptional environment variables:');
|
|
44
|
-
console.error(' GMAIL_ENABLED_TOOLGROUPS: Comma-separated list of tool groups to enable');
|
|
45
|
-
console.error(' Valid groups: readonly, readwrite, readwrite_external');
|
|
46
|
-
console.error(' Default: all groups enabled');
|
|
47
|
-
console.error(' Example: GMAIL_ENABLED_TOOLGROUPS=readwrite');
|
|
48
76
|
console.error('\n======================================================\n');
|
|
49
77
|
process.exit(1);
|
|
50
78
|
}
|
|
79
|
+
// No credentials found at all
|
|
80
|
+
logError('validateEnvironment', 'Missing required environment variables:');
|
|
81
|
+
console.error('\nThis MCP server supports two authentication modes:\n');
|
|
82
|
+
console.error('--- Option 1: OAuth2 (for personal Gmail accounts) ---');
|
|
83
|
+
console.error(' GMAIL_OAUTH_CLIENT_ID: OAuth2 client ID from Google Cloud Console');
|
|
84
|
+
console.error(' GMAIL_OAUTH_CLIENT_SECRET: OAuth2 client secret');
|
|
85
|
+
console.error(' GMAIL_OAUTH_REFRESH_TOKEN: Refresh token from one-time consent flow');
|
|
86
|
+
console.error('\n Setup: Run `npx tsx scripts/oauth-setup.ts <client_id> <client_secret>`');
|
|
87
|
+
console.error('\n--- Option 2: Service Account (for Google Workspace) ---');
|
|
88
|
+
console.error(' GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL: Service account email address');
|
|
89
|
+
console.error(' Example: my-service-account@my-project.iam.gserviceaccount.com');
|
|
90
|
+
console.error(' GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY: Service account private key (PEM format)');
|
|
91
|
+
console.error(' Example: -----BEGIN PRIVATE KEY-----\\nMIIE...\\n-----END PRIVATE KEY-----');
|
|
92
|
+
console.error(' GMAIL_IMPERSONATE_EMAIL: Email address to impersonate');
|
|
93
|
+
console.error(' Example: user@yourdomain.com');
|
|
94
|
+
console.error('\n Setup steps:');
|
|
95
|
+
console.error(' 1. Go to https://console.cloud.google.com/');
|
|
96
|
+
console.error(' 2. Create a service account with domain-wide delegation');
|
|
97
|
+
console.error(' 3. In Google Workspace Admin, grant required Gmail API scopes');
|
|
98
|
+
console.error(' 4. Download the JSON key file and extract client_email and private_key');
|
|
99
|
+
console.error('\nOptional environment variables:');
|
|
100
|
+
console.error(' GMAIL_ENABLED_TOOLGROUPS: Comma-separated list of tool groups to enable');
|
|
101
|
+
console.error(' Valid groups: readonly, readwrite, readwrite_external');
|
|
102
|
+
console.error(' Default: all groups enabled');
|
|
103
|
+
console.error(' Example: GMAIL_ENABLED_TOOLGROUPS=readwrite');
|
|
104
|
+
console.error('\n======================================================\n');
|
|
105
|
+
process.exit(1);
|
|
51
106
|
}
|
|
52
107
|
// =============================================================================
|
|
53
108
|
// MAIN ENTRY POINT
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gmail-workspace-mcp-server",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "MCP server for Gmail integration with service account support",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Gmail integration with OAuth2 and service account support",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
package/shared/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createMCPServer, createDefaultClient, ServiceAccountGmailClient, type IGmailClient, type ClientFactory, type ServiceAccountCredentials, type CreateMCPServerOptions, type Draft, } from './server.js';
|
|
1
|
+
export { createMCPServer, createDefaultClient, ServiceAccountGmailClient, OAuth2GmailClient, GMAIL_SCOPES, type IGmailClient, type ClientFactory, type ServiceAccountCredentials, type CreateMCPServerOptions, type Draft, } from './server.js';
|
|
2
2
|
export { createRegisterTools, registerTools, parseEnabledToolGroups, getAvailableToolGroups, type ToolGroup, } from './tools.js';
|
|
3
3
|
export { logServerStart, logError, logWarning, logDebug } from './logging.js';
|
|
4
4
|
export type { Email, EmailListItem, EmailHeader, EmailPart, Label, Thread, PaginatedResponse, } from './types.js';
|
package/shared/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Main exports for Gmail MCP Server
|
|
2
2
|
// Server and client
|
|
3
|
-
export { createMCPServer, createDefaultClient, ServiceAccountGmailClient, } from './server.js';
|
|
3
|
+
export { createMCPServer, createDefaultClient, ServiceAccountGmailClient, OAuth2GmailClient, GMAIL_SCOPES, } from './server.js';
|
|
4
4
|
// Tools and tool groups
|
|
5
5
|
export { createRegisterTools, registerTools, parseEnabledToolGroups, getAvailableToolGroups, } from './tools.js';
|
|
6
6
|
// Logging utilities
|
package/shared/server.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import type { Email, EmailListItem } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Gmail API scopes required by this server.
|
|
5
|
+
* Shared between service account JWT, OAuth2 consent flow, and documentation.
|
|
6
|
+
*/
|
|
7
|
+
export declare const GMAIL_SCOPES: readonly ["https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/gmail.modify", "https://www.googleapis.com/auth/gmail.compose", "https://www.googleapis.com/auth/gmail.send"];
|
|
3
8
|
/**
|
|
4
9
|
* Draft message structure
|
|
5
10
|
*/
|
|
@@ -108,18 +113,32 @@ export interface ServiceAccountCredentials {
|
|
|
108
113
|
client_x509_cert_url: string;
|
|
109
114
|
}
|
|
110
115
|
/**
|
|
111
|
-
*
|
|
116
|
+
* Abstract base class for Gmail API clients.
|
|
117
|
+
* Provides shared token management (caching, mutex refresh) and all
|
|
118
|
+
* IGmailClient method implementations. Subclasses only need to implement
|
|
119
|
+
* the authentication-specific methods.
|
|
112
120
|
*/
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
private baseUrl;
|
|
116
|
-
private jwtClient;
|
|
121
|
+
declare abstract class BaseGmailClient implements IGmailClient {
|
|
122
|
+
protected baseUrl: string;
|
|
117
123
|
private cachedToken;
|
|
118
124
|
private tokenExpiry;
|
|
119
125
|
private refreshPromise;
|
|
120
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Perform the authentication-specific token refresh.
|
|
128
|
+
* Must set this.cachedToken and this.tokenExpiry via updateToken().
|
|
129
|
+
*/
|
|
130
|
+
protected abstract refreshTokenImpl(): Promise<{
|
|
131
|
+
token: string;
|
|
132
|
+
expiryDate: number;
|
|
133
|
+
}>;
|
|
134
|
+
/**
|
|
135
|
+
* Get the sender email address for composing/sending emails.
|
|
136
|
+
* Service account uses the impersonation email; OAuth2 fetches from profile API.
|
|
137
|
+
*/
|
|
138
|
+
protected abstract getSenderEmail(): Promise<string>;
|
|
139
|
+
protected updateToken(token: string, expiryDate: number): void;
|
|
121
140
|
private refreshToken;
|
|
122
|
-
|
|
141
|
+
protected getHeaders(): Promise<Record<string, string>>;
|
|
123
142
|
listMessages(options?: {
|
|
124
143
|
q?: string;
|
|
125
144
|
maxResults?: number;
|
|
@@ -173,16 +192,58 @@ export declare class ServiceAccountGmailClient implements IGmailClient {
|
|
|
173
192
|
}): Promise<Email>;
|
|
174
193
|
sendDraft(draftId: string): Promise<Email>;
|
|
175
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Gmail API client implementation using service account with domain-wide delegation
|
|
197
|
+
*/
|
|
198
|
+
export declare class ServiceAccountGmailClient extends BaseGmailClient {
|
|
199
|
+
private jwtClient;
|
|
200
|
+
private impersonateEmail;
|
|
201
|
+
constructor(credentials: ServiceAccountCredentials, impersonateEmail: string);
|
|
202
|
+
protected refreshTokenImpl(): Promise<{
|
|
203
|
+
token: string;
|
|
204
|
+
expiryDate: number;
|
|
205
|
+
}>;
|
|
206
|
+
protected getSenderEmail(): Promise<string>;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Gmail API client implementation using OAuth2 user authentication.
|
|
210
|
+
* Enables access to personal Gmail accounts (e.g., @gmail.com) that cannot
|
|
211
|
+
* use domain-wide delegation.
|
|
212
|
+
*/
|
|
213
|
+
export declare class OAuth2GmailClient extends BaseGmailClient {
|
|
214
|
+
private oauth2Client;
|
|
215
|
+
private userEmail;
|
|
216
|
+
constructor(clientId: string, clientSecret: string, refreshToken: string);
|
|
217
|
+
protected refreshTokenImpl(): Promise<{
|
|
218
|
+
token: string;
|
|
219
|
+
expiryDate: number;
|
|
220
|
+
}>;
|
|
221
|
+
/**
|
|
222
|
+
* Fetches the authenticated user's email address from the Gmail profile API.
|
|
223
|
+
* Caches the result for subsequent calls.
|
|
224
|
+
*/
|
|
225
|
+
protected getSenderEmail(): Promise<string>;
|
|
226
|
+
}
|
|
176
227
|
export type ClientFactory = () => IGmailClient;
|
|
177
228
|
export interface CreateMCPServerOptions {
|
|
178
229
|
version: string;
|
|
179
230
|
}
|
|
180
231
|
/**
|
|
181
232
|
* Creates the default Gmail client based on environment variables.
|
|
182
|
-
*
|
|
233
|
+
*
|
|
234
|
+
* Supports two authentication modes:
|
|
235
|
+
*
|
|
236
|
+
* 1. OAuth2 (for personal Gmail accounts):
|
|
237
|
+
* - GMAIL_OAUTH_CLIENT_ID: OAuth2 client ID from Google Cloud Console
|
|
238
|
+
* - GMAIL_OAUTH_CLIENT_SECRET: OAuth2 client secret
|
|
239
|
+
* - GMAIL_OAUTH_REFRESH_TOKEN: Refresh token from one-time consent flow
|
|
240
|
+
*
|
|
241
|
+
* 2. Service Account (for Google Workspace accounts):
|
|
183
242
|
* - GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL: Service account email address
|
|
184
243
|
* - GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY: Service account private key (PEM format)
|
|
185
244
|
* - GMAIL_IMPERSONATE_EMAIL: Email address to impersonate
|
|
245
|
+
*
|
|
246
|
+
* If OAuth2 credentials are present, OAuth2 mode is used. Otherwise, service account mode is used.
|
|
186
247
|
*/
|
|
187
248
|
export declare function createDefaultClient(): IGmailClient;
|
|
188
249
|
export declare function createMCPServer(options: CreateMCPServerOptions): {
|
|
@@ -222,3 +283,4 @@ export declare function createMCPServer(options: CreateMCPServerOptions): {
|
|
|
222
283
|
}>;
|
|
223
284
|
registerHandlers: (server: Server, clientFactory?: ClientFactory) => Promise<void>;
|
|
224
285
|
};
|
|
286
|
+
export {};
|
package/shared/server.js
CHANGED
|
@@ -1,38 +1,34 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { JWT } from 'google-auth-library';
|
|
2
|
+
import { JWT, OAuth2Client } from 'google-auth-library';
|
|
3
3
|
import { createRegisterTools } from './tools.js';
|
|
4
4
|
/**
|
|
5
|
-
* Gmail API
|
|
5
|
+
* Gmail API scopes required by this server.
|
|
6
|
+
* Shared between service account JWT, OAuth2 consent flow, and documentation.
|
|
6
7
|
*/
|
|
7
|
-
export
|
|
8
|
-
|
|
8
|
+
export const GMAIL_SCOPES = [
|
|
9
|
+
'https://www.googleapis.com/auth/gmail.readonly',
|
|
10
|
+
'https://www.googleapis.com/auth/gmail.modify',
|
|
11
|
+
'https://www.googleapis.com/auth/gmail.compose',
|
|
12
|
+
'https://www.googleapis.com/auth/gmail.send',
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Abstract base class for Gmail API clients.
|
|
16
|
+
* Provides shared token management (caching, mutex refresh) and all
|
|
17
|
+
* IGmailClient method implementations. Subclasses only need to implement
|
|
18
|
+
* the authentication-specific methods.
|
|
19
|
+
*/
|
|
20
|
+
class BaseGmailClient {
|
|
9
21
|
baseUrl = 'https://gmail.googleapis.com/gmail/v1/users/me';
|
|
10
|
-
jwtClient;
|
|
11
22
|
cachedToken = null;
|
|
12
23
|
tokenExpiry = 0;
|
|
13
24
|
refreshPromise = null;
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
this.
|
|
17
|
-
email: credentials.client_email,
|
|
18
|
-
key: credentials.private_key,
|
|
19
|
-
scopes: [
|
|
20
|
-
'https://www.googleapis.com/auth/gmail.readonly',
|
|
21
|
-
'https://www.googleapis.com/auth/gmail.modify',
|
|
22
|
-
'https://www.googleapis.com/auth/gmail.compose',
|
|
23
|
-
'https://www.googleapis.com/auth/gmail.send',
|
|
24
|
-
],
|
|
25
|
-
subject: impersonateEmail,
|
|
26
|
-
});
|
|
25
|
+
updateToken(token, expiryDate) {
|
|
26
|
+
this.cachedToken = token;
|
|
27
|
+
this.tokenExpiry = expiryDate;
|
|
27
28
|
}
|
|
28
29
|
async refreshToken() {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
throw new Error('Failed to obtain access token from service account');
|
|
32
|
-
}
|
|
33
|
-
this.cachedToken = tokenResponse.access_token;
|
|
34
|
-
// Token typically expires in 1 hour, but use the actual expiry if provided
|
|
35
|
-
this.tokenExpiry = tokenResponse.expiry_date || Date.now() + 3600000;
|
|
30
|
+
const { token, expiryDate } = await this.refreshTokenImpl();
|
|
31
|
+
this.updateToken(token, expiryDate);
|
|
36
32
|
}
|
|
37
33
|
async getHeaders() {
|
|
38
34
|
// Check if we have a valid cached token (with 60 second buffer)
|
|
@@ -71,8 +67,9 @@ export class ServiceAccountGmailClient {
|
|
|
71
67
|
}
|
|
72
68
|
async createDraft(options) {
|
|
73
69
|
const headers = await this.getHeaders();
|
|
70
|
+
const senderEmail = await this.getSenderEmail();
|
|
74
71
|
const { createDraft } = await import('./gmail-client/lib/drafts.js');
|
|
75
|
-
return createDraft(this.baseUrl, headers,
|
|
72
|
+
return createDraft(this.baseUrl, headers, senderEmail, options);
|
|
76
73
|
}
|
|
77
74
|
async getDraft(draftId) {
|
|
78
75
|
const headers = await this.getHeaders();
|
|
@@ -91,8 +88,9 @@ export class ServiceAccountGmailClient {
|
|
|
91
88
|
}
|
|
92
89
|
async sendMessage(options) {
|
|
93
90
|
const headers = await this.getHeaders();
|
|
91
|
+
const senderEmail = await this.getSenderEmail();
|
|
94
92
|
const { sendMessage } = await import('./gmail-client/lib/send-message.js');
|
|
95
|
-
return sendMessage(this.baseUrl, headers,
|
|
93
|
+
return sendMessage(this.baseUrl, headers, senderEmail, options);
|
|
96
94
|
}
|
|
97
95
|
async sendDraft(draftId) {
|
|
98
96
|
const headers = await this.getHeaders();
|
|
@@ -100,14 +98,119 @@ export class ServiceAccountGmailClient {
|
|
|
100
98
|
return sendDraft(this.baseUrl, headers, draftId);
|
|
101
99
|
}
|
|
102
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Gmail API client implementation using service account with domain-wide delegation
|
|
103
|
+
*/
|
|
104
|
+
export class ServiceAccountGmailClient extends BaseGmailClient {
|
|
105
|
+
jwtClient;
|
|
106
|
+
impersonateEmail;
|
|
107
|
+
constructor(credentials, impersonateEmail) {
|
|
108
|
+
super();
|
|
109
|
+
this.impersonateEmail = impersonateEmail;
|
|
110
|
+
this.jwtClient = new JWT({
|
|
111
|
+
email: credentials.client_email,
|
|
112
|
+
key: credentials.private_key,
|
|
113
|
+
scopes: [...GMAIL_SCOPES],
|
|
114
|
+
subject: impersonateEmail,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async refreshTokenImpl() {
|
|
118
|
+
const tokenResponse = await this.jwtClient.authorize();
|
|
119
|
+
if (!tokenResponse.access_token) {
|
|
120
|
+
throw new Error('Failed to obtain access token from service account');
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
token: tokenResponse.access_token,
|
|
124
|
+
// Token typically expires in 1 hour, but use the actual expiry if provided
|
|
125
|
+
expiryDate: tokenResponse.expiry_date || Date.now() + 3600000,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
async getSenderEmail() {
|
|
129
|
+
return this.impersonateEmail;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Gmail API client implementation using OAuth2 user authentication.
|
|
134
|
+
* Enables access to personal Gmail accounts (e.g., @gmail.com) that cannot
|
|
135
|
+
* use domain-wide delegation.
|
|
136
|
+
*/
|
|
137
|
+
export class OAuth2GmailClient extends BaseGmailClient {
|
|
138
|
+
oauth2Client;
|
|
139
|
+
userEmail = null;
|
|
140
|
+
constructor(clientId, clientSecret, refreshToken) {
|
|
141
|
+
super();
|
|
142
|
+
this.oauth2Client = new OAuth2Client(clientId, clientSecret);
|
|
143
|
+
this.oauth2Client.setCredentials({ refresh_token: refreshToken });
|
|
144
|
+
}
|
|
145
|
+
async refreshTokenImpl() {
|
|
146
|
+
const { token, res } = await this.oauth2Client.getAccessToken();
|
|
147
|
+
if (!token) {
|
|
148
|
+
throw new Error('Failed to obtain access token from OAuth2 refresh token');
|
|
149
|
+
}
|
|
150
|
+
// Use expiry from response if available, otherwise default to 1 hour
|
|
151
|
+
const expiryDate = res?.data?.expiry_date;
|
|
152
|
+
return {
|
|
153
|
+
token,
|
|
154
|
+
expiryDate: typeof expiryDate === 'number' ? expiryDate : Date.now() + 3600000,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Fetches the authenticated user's email address from the Gmail profile API.
|
|
159
|
+
* Caches the result for subsequent calls.
|
|
160
|
+
*/
|
|
161
|
+
async getSenderEmail() {
|
|
162
|
+
if (this.userEmail) {
|
|
163
|
+
return this.userEmail;
|
|
164
|
+
}
|
|
165
|
+
const headers = await this.getHeaders();
|
|
166
|
+
const response = await fetch(`${this.baseUrl}/profile`, {
|
|
167
|
+
method: 'GET',
|
|
168
|
+
headers,
|
|
169
|
+
});
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
const body = await response.text().catch(() => '');
|
|
172
|
+
throw new Error(`Failed to fetch Gmail profile: ${response.status} ${response.statusText}${body ? ` - ${body}` : ''}`);
|
|
173
|
+
}
|
|
174
|
+
const profile = (await response.json());
|
|
175
|
+
this.userEmail = profile.emailAddress;
|
|
176
|
+
return this.userEmail;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
103
179
|
/**
|
|
104
180
|
* Creates the default Gmail client based on environment variables.
|
|
105
|
-
*
|
|
181
|
+
*
|
|
182
|
+
* Supports two authentication modes:
|
|
183
|
+
*
|
|
184
|
+
* 1. OAuth2 (for personal Gmail accounts):
|
|
185
|
+
* - GMAIL_OAUTH_CLIENT_ID: OAuth2 client ID from Google Cloud Console
|
|
186
|
+
* - GMAIL_OAUTH_CLIENT_SECRET: OAuth2 client secret
|
|
187
|
+
* - GMAIL_OAUTH_REFRESH_TOKEN: Refresh token from one-time consent flow
|
|
188
|
+
*
|
|
189
|
+
* 2. Service Account (for Google Workspace accounts):
|
|
106
190
|
* - GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL: Service account email address
|
|
107
191
|
* - GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY: Service account private key (PEM format)
|
|
108
192
|
* - GMAIL_IMPERSONATE_EMAIL: Email address to impersonate
|
|
193
|
+
*
|
|
194
|
+
* If OAuth2 credentials are present, OAuth2 mode is used. Otherwise, service account mode is used.
|
|
109
195
|
*/
|
|
110
196
|
export function createDefaultClient() {
|
|
197
|
+
// Check for OAuth2 credentials first
|
|
198
|
+
const oauthClientId = process.env.GMAIL_OAUTH_CLIENT_ID;
|
|
199
|
+
const oauthClientSecret = process.env.GMAIL_OAUTH_CLIENT_SECRET;
|
|
200
|
+
const oauthRefreshToken = process.env.GMAIL_OAUTH_REFRESH_TOKEN;
|
|
201
|
+
if (oauthClientId && oauthClientSecret && oauthRefreshToken) {
|
|
202
|
+
return new OAuth2GmailClient(oauthClientId, oauthClientSecret, oauthRefreshToken);
|
|
203
|
+
}
|
|
204
|
+
// Warn if some OAuth2 vars are set but not all (likely misconfiguration)
|
|
205
|
+
if (oauthClientId || oauthClientSecret || oauthRefreshToken) {
|
|
206
|
+
const missing = [
|
|
207
|
+
!oauthClientId && 'GMAIL_OAUTH_CLIENT_ID',
|
|
208
|
+
!oauthClientSecret && 'GMAIL_OAUTH_CLIENT_SECRET',
|
|
209
|
+
!oauthRefreshToken && 'GMAIL_OAUTH_REFRESH_TOKEN',
|
|
210
|
+
].filter(Boolean);
|
|
211
|
+
console.warn(`Warning: Partial OAuth2 configuration detected. Missing: ${missing.join(', ')}. Falling back to service account mode.`);
|
|
212
|
+
}
|
|
213
|
+
// Fall back to service account mode
|
|
111
214
|
const clientEmail = process.env.GMAIL_SERVICE_ACCOUNT_CLIENT_EMAIL;
|
|
112
215
|
// Handle both literal \n in JSON configs and actual newlines
|
|
113
216
|
const privateKey = process.env.GMAIL_SERVICE_ACCOUNT_PRIVATE_KEY?.replace(/\\n/g, '\n');
|
|
@@ -3,10 +3,13 @@ import { z } from 'zod';
|
|
|
3
3
|
import type { ClientFactory } from '../server.js';
|
|
4
4
|
export declare const GetEmailConversationSchema: z.ZodObject<{
|
|
5
5
|
email_id: z.ZodString;
|
|
6
|
+
include_html: z.ZodOptional<z.ZodBoolean>;
|
|
6
7
|
}, "strip", z.ZodTypeAny, {
|
|
7
8
|
email_id: string;
|
|
9
|
+
include_html?: boolean | undefined;
|
|
8
10
|
}, {
|
|
9
11
|
email_id: string;
|
|
12
|
+
include_html?: boolean | undefined;
|
|
10
13
|
}>;
|
|
11
14
|
export declare function getEmailConversationTool(server: Server, clientFactory: ClientFactory): {
|
|
12
15
|
name: string;
|
|
@@ -18,6 +21,10 @@ export declare function getEmailConversationTool(server: Server, clientFactory:
|
|
|
18
21
|
type: string;
|
|
19
22
|
description: string;
|
|
20
23
|
};
|
|
24
|
+
include_html: {
|
|
25
|
+
type: string;
|
|
26
|
+
description: string;
|
|
27
|
+
};
|
|
21
28
|
};
|
|
22
29
|
required: string[];
|
|
23
30
|
};
|
|
@@ -3,9 +3,12 @@ import { getHeader } from '../utils/email-helpers.js';
|
|
|
3
3
|
const PARAM_DESCRIPTIONS = {
|
|
4
4
|
email_id: 'The unique identifier of the email to retrieve. ' +
|
|
5
5
|
'Obtain this from list_email_conversations or search_email_conversations.',
|
|
6
|
+
include_html: 'When true, includes the raw HTML body of the email (if available) in addition to the plain text. ' +
|
|
7
|
+
'Useful for rendering emails with original formatting, creating screenshots, or archival workflows.',
|
|
6
8
|
};
|
|
7
9
|
export const GetEmailConversationSchema = z.object({
|
|
8
10
|
email_id: z.string().min(1).describe(PARAM_DESCRIPTIONS.email_id),
|
|
11
|
+
include_html: z.boolean().optional().describe(PARAM_DESCRIPTIONS.include_html),
|
|
9
12
|
});
|
|
10
13
|
const TOOL_DESCRIPTION = `Retrieve the full content of a specific email conversation by its ID.
|
|
11
14
|
|
|
@@ -13,11 +16,13 @@ Returns the complete email including headers, body content, and attachment infor
|
|
|
13
16
|
|
|
14
17
|
**Parameters:**
|
|
15
18
|
- email_id: The unique identifier of the email (required)
|
|
19
|
+
- include_html: When true, includes raw HTML body in addition to plain text (optional)
|
|
16
20
|
|
|
17
21
|
**Returns:**
|
|
18
22
|
Full email details including:
|
|
19
23
|
- Subject, From, To, Cc, Date headers
|
|
20
24
|
- Full message body (plain text preferred, HTML as fallback)
|
|
25
|
+
- Raw HTML body (when include_html is true and HTML content is available)
|
|
21
26
|
- List of attachments (if any)
|
|
22
27
|
- Labels assigned to the email
|
|
23
28
|
|
|
@@ -25,6 +30,8 @@ Full email details including:
|
|
|
25
30
|
- Read the full content of an email after listing conversations
|
|
26
31
|
- Extract specific information from an email body
|
|
27
32
|
- Check attachment details
|
|
33
|
+
- Render emails with original HTML formatting (use include_html: true)
|
|
34
|
+
- Create email screenshots or archives with original styling
|
|
28
35
|
|
|
29
36
|
**Note:** Use list_email_conversations or search_email_conversations first to get email IDs.`;
|
|
30
37
|
/**
|
|
@@ -84,6 +91,22 @@ function getEmailBody(email) {
|
|
|
84
91
|
}
|
|
85
92
|
return '(No body content available)';
|
|
86
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Gets the raw HTML body content from an email (if available)
|
|
96
|
+
*/
|
|
97
|
+
function getEmailHtmlBody(email) {
|
|
98
|
+
// Check if body is directly on payload and it's HTML
|
|
99
|
+
if (email.payload?.body?.data && email.payload.mimeType === 'text/html') {
|
|
100
|
+
return decodeBase64Url(email.payload.body.data);
|
|
101
|
+
}
|
|
102
|
+
// Try to extract HTML from parts
|
|
103
|
+
if (email.payload?.parts) {
|
|
104
|
+
const html = extractBodyContent(email.payload.parts, 'text/html');
|
|
105
|
+
if (html)
|
|
106
|
+
return html;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
87
110
|
/**
|
|
88
111
|
* Extracts attachment information from email parts
|
|
89
112
|
*/
|
|
@@ -108,7 +131,7 @@ function getAttachments(parts) {
|
|
|
108
131
|
/**
|
|
109
132
|
* Formats an email for display
|
|
110
133
|
*/
|
|
111
|
-
function formatFullEmail(email) {
|
|
134
|
+
function formatFullEmail(email, options) {
|
|
112
135
|
const subject = getHeader(email, 'Subject') || '(No Subject)';
|
|
113
136
|
const from = getHeader(email, 'From') || 'Unknown';
|
|
114
137
|
const to = getHeader(email, 'To') || 'Unknown';
|
|
@@ -136,6 +159,26 @@ function formatFullEmail(email) {
|
|
|
136
159
|
## Body
|
|
137
160
|
|
|
138
161
|
${body}`;
|
|
162
|
+
// Include raw HTML body if requested
|
|
163
|
+
if (options?.includeHtml) {
|
|
164
|
+
const htmlBody = getEmailHtmlBody(email);
|
|
165
|
+
if (htmlBody) {
|
|
166
|
+
output += `
|
|
167
|
+
|
|
168
|
+
## HTML Body
|
|
169
|
+
|
|
170
|
+
\`\`\`html
|
|
171
|
+
${htmlBody}
|
|
172
|
+
\`\`\``;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
output += `
|
|
176
|
+
|
|
177
|
+
## HTML Body
|
|
178
|
+
|
|
179
|
+
(No HTML content available)`;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
139
182
|
if (attachments.length > 0) {
|
|
140
183
|
output += `\n\n## Attachments (${attachments.length})\n`;
|
|
141
184
|
attachments.forEach((att, i) => {
|
|
@@ -156,6 +199,10 @@ export function getEmailConversationTool(server, clientFactory) {
|
|
|
156
199
|
type: 'string',
|
|
157
200
|
description: PARAM_DESCRIPTIONS.email_id,
|
|
158
201
|
},
|
|
202
|
+
include_html: {
|
|
203
|
+
type: 'boolean',
|
|
204
|
+
description: PARAM_DESCRIPTIONS.include_html,
|
|
205
|
+
},
|
|
159
206
|
},
|
|
160
207
|
required: ['email_id'],
|
|
161
208
|
},
|
|
@@ -166,7 +213,9 @@ export function getEmailConversationTool(server, clientFactory) {
|
|
|
166
213
|
const email = await client.getMessage(parsed.email_id, {
|
|
167
214
|
format: 'full',
|
|
168
215
|
});
|
|
169
|
-
const formattedEmail = formatFullEmail(email
|
|
216
|
+
const formattedEmail = formatFullEmail(email, {
|
|
217
|
+
includeHtml: parsed.include_html,
|
|
218
|
+
});
|
|
170
219
|
return {
|
|
171
220
|
content: [
|
|
172
221
|
{
|