
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
Minimal Gmail client (refresh-token based) with built-in access token refresh and common Gmail actions.
Minimal Gmail client for Node.js with OAuth refresh tokens and automatic token management. Perfect for server-side applications, APIs, and automation scripts.
npm install mcp-gmail
Requirements: Node.js 18+ (for native fetch support)
http://localhost:3000/callback to authorized redirect URIsclient_id and client_secretimport { 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);
}
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: ['recipient@example.com'],
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');
}
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
}
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>
}
Use Gmail's powerful search syntax with listEmails():
// 📥 Unread emails
await gmail.listEmails({ q: 'is:unread' });
// 👤 From specific sender
await gmail.listEmails({ q: 'from:boss@company.com' });
// 📎 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:notifications@github.com has:attachment after:2024/01/01',
maxResults: 50
});
INBOX, SENT, DRAFT, TRASHIMPORTANT, STARRED, UNREADSPAM, CATEGORY_PERSONAL, CATEGORY_SOCIAL// ✅ 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']
});
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 });
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');
}
}
# 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"
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: ['test@example.com'],
// subject: 'Test from mcp-gmail',
// body: 'Hello from Node.js!'
// });
// console.log('Email sent!');
}
test().catch(console.error);
Run test:
node test.js
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);
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}`);
// 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`);
}
"Token refresh failed"
"Insufficient permissions"
"Port already in use"
"Invalid email format"
// 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);
}
Contributions welcome! Please:
MIT - see LICENSE file for details
Made in India with ❤️ by brain.webnexs.com
FAQs
Minimal Gmail client (refresh-token based) with built-in access token refresh and common Gmail actions.
We found that mcp-gmail demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.