mcp-gmail
v1.0.1
Published
Minimal Gmail client (refresh-token based) with built-in access token refresh and common Gmail actions.
Maintainers
Readme
mcp-gmail
Minimal Gmail client for Node.js with OAuth refresh tokens and automatic token management. Perfect for server-side applications, APIs, and automation scripts.
✨ Features
- 🔐 Built-in OAuth flow — Interactive token generation with browser automation
- 🔄 Auto token refresh — Seamlessly handles expired access tokens
- 📧 Core Gmail actions — Send, read, search, modify, and delete emails
- 🚀 Modern Node.js — Uses native fetch (Node 18+), ESM modules
- 🛡️ Error handling — Clear error messages for debugging
- 📦 Lightweight — Zero dependencies beyond Node.js built-ins
📦 Installation
npm install mcp-gmailRequirements: Node.js 18+ (for native fetch support)
🚀 Quick Start
Step 1: Google Cloud Setup
- Go to Google Cloud Console
- Create a new project or select existing
- Enable the Gmail API
- Go to "APIs & Services" → "Credentials"
- Click "Create Credentials" → "OAuth client ID"
- Choose "Desktop application" or "Web application"
- For Web app, add
http://localhost:3000/callbackto authorized redirect URIs - Save your
client_idandclient_secret
Step 2: Generate Refresh Token (One-time)
import { GmailOAuth } from 'mcp-gmail';
const oauth = new GmailOAuth({
clientId: 'your-google-client-id',
clientSecret: 'your-google-client-secret'
});
// Interactive flow - opens browser, starts local server
try {
const tokens = await oauth.authorize({
scopes: [
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/gmail.settings.basic'
],
port: 3000 // optional, defaults to 3000
});
console.log('✅ Refresh Token:', tokens.refresh_token);
console.log('🎯 Scopes:', tokens.scope);
// Save this refresh_token - you'll need it for the Gmail client
} catch (error) {
console.error('❌ OAuth failed:', error.message);
}Step 3: Use Gmail Client
import { GmailMCP } from 'mcp-gmail';
const gmail = new GmailMCP({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
refreshToken: process.env.GMAIL_REFRESH_TOKEN, // from Step 2
});
// 📄 Get profile
const profile = await gmail.getProfile();
console.log(`📧 Email: ${profile.emailAddress}`);
// 📥 List recent emails
const emails = await gmail.listEmails({
q: 'is:unread',
maxResults: 10
});
console.log(`📬 Found ${emails.messages?.length || 0} unread emails`);
// 📖 Read a specific email
if (emails.messages?.[0]) {
const message = await gmail.getEmail(emails.messages[0].id);
const subject = message.payload.headers.find(h => h.name === 'Subject')?.value;
console.log(`📧 Subject: ${subject}`);
}
// ✉️ Send email
const sent = await gmail.sendEmail({
to: ['[email protected]'],
subject: '🎉 Hello from mcp-gmail!',
body: 'This email was sent using the mcp-gmail package.',
mimeType: 'text/plain' // or 'text/html'
});
console.log(`✅ Email sent: ${sent.id}`);
// 🏷️ Mark email as read
if (emails.messages?.[0]) {
await gmail.modifyEmail(emails.messages[0].id, {
removeLabelIds: ['UNREAD']
});
console.log('✅ Marked as read');
}📖 Complete API Reference
GmailOAuth Class
For generating refresh tokens (one-time setup):
class GmailOAuth {
constructor({
clientId: string,
clientSecret: string,
redirectUri?: string // defaults to 'http://localhost:3000/callback'
})
// Interactive OAuth flow (opens browser)
authorize(options?: {
port?: number, // default: 3000
scopes?: string[], // default: ['gmail.modify']
openBrowser?: boolean // default: true
}): Promise<TokenResponse>
// Generate auth URL manually
getAuthUrl(scopes?: string[]): string
// Exchange code for tokens
getTokens(code: string): Promise<TokenResponse>
}
interface TokenResponse {
access_token: string
refresh_token: string
expires_in: number
scope: string
token_type: string
}GmailMCP Class
Main Gmail client:
class GmailMCP {
constructor({
clientId: string,
clientSecret: string,
refreshToken: string
})
// Get authenticated user profile
getProfile(): Promise<{
emailAddress: string
messagesTotal: number
threadsTotal: number
historyId: string
}>
// List emails with Gmail search syntax
listEmails(options?: {
q?: string, // Gmail search query
maxResults?: number, // default: 10, max: 500
pageToken?: string, // for pagination
labelIds?: string[] // filter by label IDs
}): Promise<{
messages: Array<{ id: string, threadId: string }>
nextPageToken?: string
resultSizeEstimate: number
}>
// Get full email content
getEmail(messageId: string): Promise<{
id: string
threadId: string
labelIds: string[]
snippet: string
payload: {
headers: Array<{ name: string, value: string }>
body: { data?: string }
parts?: Array<...> // MIME parts
}
}>
// Send email
sendEmail({
to: string[],
subject: string,
body: string,
mimeType?: 'text/plain' | 'text/html' // default: 'text/plain'
}): Promise<{ id: string }>
// Modify email labels
modifyEmail(messageId: string, {
addLabelIds?: string[],
removeLabelIds?: string[]
}): Promise<{
id: string
labelIds: string[]
}>
// Delete email permanently
deleteEmail(messageId: string): Promise<void>
}🔍 Gmail Search Examples
Use Gmail's powerful search syntax with listEmails():
// 📥 Unread emails
await gmail.listEmails({ q: 'is:unread' });
// 👤 From specific sender
await gmail.listEmails({ q: 'from:[email protected]' });
// 📎 With attachments
await gmail.listEmails({ q: 'has:attachment' });
// 📅 Date range
await gmail.listEmails({ q: 'after:2024/01/01 before:2024/02/01' });
// 🔍 Subject contains
await gmail.listEmails({ q: 'subject:"meeting notes"' });
// 🏷️ Specific label
await gmail.listEmails({ q: 'label:important' });
// 🔗 Complex queries
await gmail.listEmails({
q: 'from:[email protected] has:attachment after:2024/01/01',
maxResults: 50
});🏷️ Common Label Operations
System Labels
INBOX,SENT,DRAFT,TRASHIMPORTANT,STARRED,UNREADSPAM,CATEGORY_PERSONAL,CATEGORY_SOCIAL
Examples
// ✅ Mark as read
await gmail.modifyEmail(messageId, {
removeLabelIds: ['UNREAD']
});
// ⭐ Star email
await gmail.modifyEmail(messageId, {
addLabelIds: ['STARRED']
});
// 🗂️ Archive (remove from inbox)
await gmail.modifyEmail(messageId, {
removeLabelIds: ['INBOX']
});
// ❗ Mark as important
await gmail.modifyEmail(messageId, {
addLabelIds: ['IMPORTANT']
});
// 🗑️ Move to trash
await gmail.modifyEmail(messageId, {
addLabelIds: ['TRASH'],
removeLabelIds: ['INBOX']
});🔐 OAuth Scopes
Choose appropriate scopes based on your needs:
const scopes = [
// ✅ Recommended: Full Gmail access
'https://www.googleapis.com/auth/gmail.modify',
// 📖 Read-only access
'https://www.googleapis.com/auth/gmail.readonly',
// ✉️ Send only
'https://www.googleapis.com/auth/gmail.send',
// 🏷️ Labels and filters
'https://www.googleapis.com/auth/gmail.settings.basic',
];
const tokens = await oauth.authorize({ scopes });⚠️ Error Handling
All methods throw descriptive errors:
try {
await gmail.sendEmail({
to: ['invalid-email'],
subject: 'Test',
body: 'Hello'
});
} catch (error) {
console.error('Send failed:', error.message);
// Check error details
if (error.status === 400) {
console.log('Bad request - check email format');
} else if (error.status === 403) {
console.log('Insufficient permissions - check scopes');
} else if (error.message.includes('Token refresh')) {
console.log('Invalid refresh token - re-run OAuth flow');
}
}🧪 Testing & Development
Environment Setup
# PowerShell (Windows)
$env:GOOGLE_CLIENT_ID="your-client-id"
$env:GOOGLE_CLIENT_SECRET="your-client-secret"
$env:GMAIL_REFRESH_TOKEN="your-refresh-token"
# Bash (Linux/Mac)
export GOOGLE_CLIENT_ID="your-client-id"
export GOOGLE_CLIENT_SECRET="your-client-secret"
export GMAIL_REFRESH_TOKEN="your-refresh-token"Test Script
Create test.js:
import { GmailMCP } from 'mcp-gmail';
const gmail = new GmailMCP({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
refreshToken: process.env.GMAIL_REFRESH_TOKEN,
});
async function test() {
// Test profile
const profile = await gmail.getProfile();
console.log('Profile:', profile.emailAddress);
// Test list
const list = await gmail.listEmails({ maxResults: 5 });
console.log(`Found ${list.messages?.length || 0} messages`);
// Test send (uncomment to actually send)
// await gmail.sendEmail({
// to: ['[email protected]'],
// subject: 'Test from mcp-gmail',
// body: 'Hello from Node.js!'
// });
// console.log('Email sent!');
}
test().catch(console.error);Run test:
node test.js🔧 Advanced Usage
Custom Redirect URI
const oauth = new GmailOAuth({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'https://yourapp.com/oauth/callback' // production callback
});
// Manual flow for web apps
const authUrl = oauth.getAuthUrl([
'https://www.googleapis.com/auth/gmail.modify'
]);
console.log('Visit:', authUrl);
// Later, exchange the code from your callback
const tokens = await oauth.getTokens(authCodeFromCallback);Pagination
let pageToken = undefined;
let allMessages = [];
do {
const response = await gmail.listEmails({
q: 'is:unread',
maxResults: 100,
pageToken
});
if (response.messages) {
allMessages.push(...response.messages);
}
pageToken = response.nextPageToken;
} while (pageToken);
console.log(`Total unread: ${allMessages.length}`);Batch Operations
// Mark multiple emails as read
const unreadEmails = await gmail.listEmails({ q: 'is:unread' });
if (unreadEmails.messages) {
for (const message of unreadEmails.messages) {
await gmail.modifyEmail(message.id, {
removeLabelIds: ['UNREAD']
});
}
console.log(`Marked ${unreadEmails.messages.length} emails as read`);
}🐛 Troubleshooting
Common Issues
"Token refresh failed"
- Refresh token expired or revoked
- Client ID/secret mismatch
- Solution: Re-run OAuth flow
"Insufficient permissions"
- Missing required OAuth scopes
- Solution: Add scopes during OAuth flow
"Port already in use"
- Another process using port 3000
- Solution: Change port or stop other process
"Invalid email format"
- Malformed recipient addresses
- Solution: Validate email addresses
Debug Mode
// Enable debug logging (custom implementation)
const gmail = new GmailMCP({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
refreshToken: process.env.GMAIL_REFRESH_TOKEN,
});
// Add error details logging
try {
await gmail.sendEmail({...});
} catch (error) {
console.error('Error:', error.message);
console.error('Status:', error.status);
console.error('Details:', error.details);
}📝 Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Submit a pull request
📄 License
MIT - see LICENSE file for details
🔗 Related Projects
Made in India with ❤️ by brain.webnexs.com
