Skip to main content
Glama

Ops MCP Server

by johnohhh1
gmail-worker.js•8.1 kB
// gmail-worker.js // Gmail worker for fetching and classifying emails import 'dotenv/config'; import fs from 'fs'; import { google } from 'googleapis'; import { nanoid } from 'nanoid'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc.js'; import timezone from 'dayjs/plugin/timezone.js'; import * as chrono from 'chrono-node'; dayjs.extend(utc); dayjs.extend(timezone); const TZ = process.env.TIMEZONE || 'America/Detroit'; // Gmail OAuth setup const oAuth2Client = new google.auth.OAuth2( process.env.GOOGLE_CLIENT_ID, process.env.GOOGLE_CLIENT_SECRET, process.env.GOOGLE_REDIRECT || 'http://localhost:3000/oauth2/callback' ); // Load tokens from file const TOKEN_PATH = './data/gmail_token.json'; if (fs.existsSync(TOKEN_PATH)) { const tokens = JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf8')); oAuth2Client.setCredentials(tokens); } const gmail = google.gmail({ version: 'v1', auth: oAuth2Client }); // Query definitions const QUERIES = { hotschedules_12h: { query: 'newer_than:12h (from:(*@hotschedules.com) OR subject:("Shift Notes" OR "called off" OR "no-show" OR "coverage needed"))', window: 12, type: '911' }, brinker_24h: { query: 'newer_than:24h from:(allen.woods@brinker.com OR *@brinker.com OR *@chilis.com OR c00605mgr@chilis.com) subject:(schedule OR due OR deadline OR submit OR report OR EOP OR Fusion OR Oracle)', window: 24, type: 'deliverable' }, brinker_backstop: { query: 'newer_than:7d from:(allen.woods@brinker.com OR *@brinker.com OR *@chilis.com OR c00605mgr@chilis.com) subject:(schedule OR due OR deadline OR submit OR report OR EOP OR Fusion OR Oracle)', window: 168, type: 'deliverable' } }; // Search messages async function searchMessages(query, maxResults = 50) { try { const response = await gmail.users.messages.list({ userId: 'me', q: query, maxResults }); return response.data.messages || []; } catch (error) { console.error('Gmail search error:', error.message); return []; } } // Get full message async function getMessage(messageId) { try { const msg = await gmail.users.messages.get({ userId: 'me', id: messageId, format: 'full' }); const payload = msg.data.payload || {}; const headers = payload.headers || []; const getHeader = (name) => headers.find(h => h.name.toLowerCase() === name.toLowerCase())?.value || ''; let body = ''; const parts = [payload]; while (parts.length) { const part = parts.shift(); if (part.parts) parts.push(...part.parts); if (part.body?.data) { body += Buffer.from(part.body.data, 'base64').toString('utf8'); } } return { id: msg.data.id, threadId: msg.data.threadId, subject: getHeader('Subject'), from: getHeader('From'), date: getHeader('Date'), body: body.substring(0, 5000), snippet: msg.data.snippet, link: `https://mail.google.com/mail/u/0/#inbox/${msg.data.threadId}` }; } catch (error) { console.error('Error getting message:', error.message); return null; } } // Classify message function classifyMessage(message) { if (!message) return null; const text = `${message.subject} ${message.body}`.toLowerCase(); const is911 = /\b(call[\s-]?off|called out|no[\s-]?show|coverage needed|left early)\b/.test(text); const isDeliverable = /\b(due|deadline|submit|by friday|eop|end of period|schedule ready)\b/.test(text); let dueDate = null; try { const parsed = chrono.parseDate(text); if (parsed) { dueDate = dayjs(parsed).tz(TZ).format('YYYY-MM-DD'); } } catch (e) { // Date parsing failed } return { id: nanoid(), messageId: message.id, threadId: message.threadId, type: is911 ? '911' : (isDeliverable ? 'deliverable' : 'info'), priority: is911 ? 'high' : 'normal', subject: message.subject, from: message.from, date: message.date, dueDate: dueDate, snippet: message.snippet, link: message.link, status: 'open', created: new Date().toISOString() }; } // Build report function buildReport(results) { const lines = []; lines.push('# Talked to Gmail\n'); lines.push(`Generated: ${dayjs().tz(TZ).format('MMM D, YYYY h:mm A z')}\n`); if (results.hs911.length > 0) { lines.push('## 🚨 911 - HotSchedules Alerts'); results.hs911.forEach(item => { lines.push(`- **${item.subject}**`); lines.push(` From: ${item.from}`); lines.push(` [Open in Gmail](${item.link})`); }); } else { lines.push('## āœ… No 911 Issues'); lines.push('No call-offs or coverage issues in the last 12 hours'); } lines.push(''); if (results.deliverables.length > 0) { lines.push('## šŸ“… Deadlines & Deliverables'); results.deliverables.forEach(item => { const due = item.dueDate ? `Due: ${dayjs(item.dueDate).format('MMM D')}` : ''; lines.push(`- **${item.subject}** ${due}`); lines.push(` From: ${item.from}`); lines.push(` [Open in Gmail](${item.link})`); }); } else { lines.push('## No New Deadlines'); } lines.push(''); lines.push(`**Summary:** ${results.hs911.length} urgent, ${results.deliverables.length} deadlines, ${results.all.length} total`); return lines.join('\n'); } // Main worker async function runGmailWorker() { console.log('šŸ”„ Starting Gmail worker...'); console.log(` Timezone: ${TZ}`); console.log(` Store: ${process.env.STORE_NAME || 'Not configured'}`); const results = { hs911: [], deliverables: [], all: [] }; for (const [key, config] of Object.entries(QUERIES)) { console.log(`\nšŸ“§ Running query: ${key}`); const messages = await searchMessages(config.query); console.log(` Found ${messages.length} messages`); for (const msg of messages) { const fullMessage = await getMessage(msg.id); if (fullMessage) { const classified = classifyMessage(fullMessage); if (classified) { results.all.push(classified); if (classified.type === '911') { results.hs911.push(classified); } else if (classified.type === 'deliverable') { results.deliverables.push(classified); } } } } } console.log(`\nšŸ“Š Results:`); console.log(` 911 items: ${results.hs911.length}`); console.log(` Deliverables: ${results.deliverables.length}`); console.log(` Total messages: ${results.all.length}`); const report = buildReport(results); // Save results const payload = { ok: true, report: report, counts: { hs: results.all.filter(r => r.from?.includes('hotschedules')).length, brinker: results.all.filter(r => r.from?.includes('brinker')).length, hs911: results.hs911.length, deliverables: results.deliverables.length }, tasks: results.all, timestamp: new Date().toISOString() }; if (!fs.existsSync('./data')) { fs.mkdirSync('./data', { recursive: true }); } const dataPath = './data/latest_report.json'; fs.writeFileSync(dataPath, JSON.stringify(payload, null, 2)); console.log('āœ… Saved for MCP pickup'); if (!fs.existsSync('./data/reports')) { fs.mkdirSync('./data/reports', { recursive: true }); } const localPath = './data/reports/' + dayjs().format('YYYY-MM-DD-HHmmss') + '.json'; fs.writeFileSync(localPath, JSON.stringify(payload, null, 2)); console.log('āœ… Saved report:', localPath); return { report, results }; } // Run console.log('Gmail Worker - Standalone Mode'); runGmailWorker() .then(result => { console.log('\nšŸ“Š Report Preview:'); console.log('================'); console.log(result.report); console.log('\nāœ… Gmail worker completed successfully'); process.exit(0); }) .catch(error => { console.error('\nāŒ Error:', error.message); if (error.message.includes('invalid_grant')) { console.error('Token expired. Please run: npm run setup'); } process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/johnohhh1/ops-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server