import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { number, z } from "zod";
import { getUnreadEmails, saveDraftReplyToGmail } from "./gmail-api.js";
import { authorizeNewToken } from "./gmail-auth.js";
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import { emailSchema, type Email } from "./schema.js";
import { getStyleGuidePrompt } from "./email-templates.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const TOKEN_PATH = path.join(__dirname, "../secrets/token.json");
async function main() {
const server = new McpServer({
name: "gmail-mcp-server",
version: "1.0.0",
});
// Check for authorization on startup
try {
await fs.access(TOKEN_PATH);
console.error("✓ Gmail authorized");
} catch {
// No token yet - run interactive auth
console.error("\n⚠️ Gmail not authorized yet!");
console.error("Starting authorization flow...");
console.error("Your browser will open automatically.\n");
try {
await authorizeNewToken();
console.error("✓ Authorization complete! Gmail is now authorized.");
} catch (error) {
console.error("Authorization failed:", error);
console.error("\nPlease ensure:");
console.error("1. You have secrets/google-credentials.json");
console.error(
"2. You added http://localhost to authorized redirect URIs in Google Cloud Console"
);
console.error(
" (Visit https://console.cloud.google.com/apis/credentials)\n"
);
process.exit(1);
}
}
// Get emails tool
server.registerTool(
"get_unread_emails",
{
title: "Get Unread Emails",
description: "Gets the last n unread emails from the user's Gmail inbox",
inputSchema: {
numberOfEmails: number()
.min(1)
.max(100)
.describe("Number of emails to retrieve"),
},
},
async ({ numberOfEmails }) => {
const emails = await getUnreadEmails(numberOfEmails, server);
return {
content: [
{
type: "text",
text: `Result: ${JSON.stringify(emails, null, 2)}`,
},
],
};
}
);
// Draft email tool
server.registerTool(
"create_suggested_draft_reply",
{
title: "Create Draft Reply to email",
description: "Creates a suggestion for draft reply to a specific email",
inputSchema: {
email: emailSchema,
instructions: z
.string()
.optional()
.describe("Instructions for the reply"),
},
},
async ({ email, instructions }) => {
const category = email.category;
const styleGuide = getStyleGuidePrompt(category);
const response = await server.server.createMessage({
messages: [
{
role: "user",
content: {
type: "text",
text: `${styleGuide}
EMAIL TO REPLY TO:
From: ${email.from}
To: ${email.to}
Subject: ${email.subject}
Date: ${email.date}
${email.snippet}
${
instructions
? `\nADDITIONAL INSTRUCTIONS:\n${instructions}`
: ""
}
Draft a reply following the style guide above.`,
},
},
],
maxTokens: 500,
});
return {
content: [
{
type: "text",
text:
response.content.type === "text"
? response.content.text
: "Unable to generate summary",
},
],
};
}
);
server.registerTool(
"save_draft_reply_to_gmail",
{
title: "Save Draft Reply to Gmail",
description:
"Saves a draft reply to a specific email in the user's Gmail account",
inputSchema: {
draftEmail: z.string().describe("Draft email content"),
email: emailSchema,
},
},
async ({ draftEmail, email }) => {
const response = await saveDraftReplyToGmail(draftEmail, email);
return {
content: [
{
type: "text",
text: response
? `Draft saved successfully, ID: ${response.id}, Thread ID: ${response.message}`
: "Failed to save draft.",
},
],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch((err) => {
console.error("MCP server failed:", err);
process.exit(1);
});