import {readFileSync} from 'fs';
import * as https from 'https';
import express, {Request, Response, NextFunction} from 'express';
import {google} from 'googleapis';
import {loadConfig} from './config.js';
import {GmailMCPServer} from './mcp/server.js';
import {OAuthHandler} from './auth/gmail/oauth-handler.js';
import {GmailOAuthServerProvider} from './auth/gmail/oauth-provider.js';
import {registerAuthRoutes} from './auth/routes.js';
import {registerMcpRoutes} from './mcp/routes.js';
import {registerHealthRoutes} from './health.js';
import {logger} from './logger.js';
const GOOGLE_API_TIMEOUT_MS = 30_000;
google.options({timeout: GOOGLE_API_TIMEOUT_MS});
const config = loadConfig();
const oauthHandler = new OAuthHandler(config.oauth);
const provider = new GmailOAuthServerProvider(
config.oauth,
oauthHandler,
config.tokenEncryptionKey
);
const REQUEST_SIZE_LIMIT = '16kb';
const app = express();
app.use(express.json({limit: REQUEST_SIZE_LIMIT}));
app.use(express.urlencoded({extended: true, limit: REQUEST_SIZE_LIMIT}));
function securityHeaders(req: Request, res: Response, next: NextFunction): void {
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
res.setHeader('X-XSS-Protection', '0');
next();
}
function requestLogger(req: Request, res: Response, next: NextFunction): void {
const start = Date.now();
logger.debug({
method: req.method,
url: req.url,
headers: {
'mcp-session-id': req.headers['mcp-session-id'],
'authorization': req.headers.authorization ? 'Bearer ***' : undefined,
},
}, 'Request received');
res.on('finish', () => {
logger.debug({
method: req.method,
url: req.url,
status: res.statusCode,
durationMs: Date.now() - start,
}, 'Request completed');
});
next();
}
app.use(securityHeaders);
app.use(requestLogger);
const mcpServer = new GmailMCPServer();
const baseUrl = new URL(`https://${config.host}:${config.port}`);
registerHealthRoutes(app);
registerAuthRoutes(app, provider, baseUrl);
registerMcpRoutes(app, mcpServer, provider, baseUrl, config.sessionTtl * 1000);
const httpsOptions = {
key: readFileSync(config.tls.keyPath),
cert: readFileSync(config.tls.certPath),
};
const GRACEFUL_SHUTDOWN_TIMEOUT_MS = 10000;
const KEEP_ALIVE_TIMEOUT_MS = 30000;
const HEADERS_TIMEOUT_MS = 35000;
const httpsServer = https.createServer(httpsOptions, app);
httpsServer.keepAliveTimeout = KEEP_ALIVE_TIMEOUT_MS;
httpsServer.headersTimeout = HEADERS_TIMEOUT_MS;
const gracefulShutdown = (signal: string) => {
logger.info({signal}, 'Received shutdown signal, starting graceful shutdown');
httpsServer.close(() => {
logger.info('All connections closed');
process.exit(0);
});
setTimeout(() => {
logger.warn('Graceful shutdown timeout, forcing exit');
process.exit(0);
}, GRACEFUL_SHUTDOWN_TIMEOUT_MS);
};
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
httpsServer.listen(config.port, () => {
logger.info({port: config.port}, 'Gmail MCP Server started (HTTPS)');
});