Skip to main content
Glama
server.mjs9.17 kB
import express from 'express'; import dotenv from 'dotenv'; import path from 'path'; import { fileURLToPath } from 'url'; import axios from 'axios'; import { initDB, upsertSlackUser, getSlackConfigByInternalUserId } from './src/db.mjs'; dotenv.config(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const PORT = process.env.PORT || 3000; // Validate required environment variables const requiredEnvVars = [ 'SLACK_CLIENT_ID', 'SLACK_CLIENT_SECRET', 'SLACK_REDIRECT_URI', 'DATABASE_URL' ]; const missingEnvVars = requiredEnvVars.filter(key => !process.env[key]); if (missingEnvVars.length > 0) { console.error(`Missing required environment variables: ${missingEnvVars.join(', ')}`); process.exit(1); } // Initialize Database initDB().catch(err => { console.error('Failed to initialize database:', err); process.exit(1); }); // Serve static files from public directory app.use(express.static('public')); // 1. Healthcheck app.get('/health', (req, res) => { res.status(200).send('OK'); }); // 2. Landing Page // Handled by express.static for '/', explicitly adding here for clarity if needed, // but usually static middleware handles index.html on root. app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); // 3. Support & Privacy Pages app.get('/privacy', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'privacy.html')); }); app.get('/support', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'support.html')); }); // 4. Slack OAuth Start app.get('/slack/oauth/start', (req, res) => { const state = req.query.state || 'demo-user'; const scopes = 'chat:write,channels:read,groups:read'; // Bot scopes const userScopes = 'chat:write'; // User scopes const slackAuthUrl = new URL('https://slack.com/oauth/v2/authorize'); slackAuthUrl.searchParams.append('client_id', process.env.SLACK_CLIENT_ID); slackAuthUrl.searchParams.append('scope', scopes); slackAuthUrl.searchParams.append('user_scope', userScopes); slackAuthUrl.searchParams.append('redirect_uri', process.env.SLACK_REDIRECT_URI); slackAuthUrl.searchParams.append('state', state); console.log(`Redirecting to Slack OAuth: ${slackAuthUrl.toString()}`); res.redirect(slackAuthUrl.toString()); }); // 5. Slack OAuth Callback app.get('/slack/oauth/callback', async (req, res) => { const { code, state, error } = req.query; if (error) { console.error('Slack OAuth error:', error); return res.status(500).send(`<h1>OAuth Error</h1><p>${error}</p>`); } if (!code) { return res.status(400).send('Missing ?code from Slack'); } try { const response = await axios.post( 'https://slack.com/api/oauth.v2.access', new URLSearchParams({ client_id: process.env.SLACK_CLIENT_ID, client_secret: process.env.SLACK_CLIENT_SECRET, code: code.toString(), redirect_uri: process.env.SLACK_REDIRECT_URI, }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, } ); const data = response.data; if (!data.ok) { console.error('Slack API error:', data); return res.status(500).send(`<h1>Slack API Error</h1><pre>${JSON.stringify(data, null, 2)}</pre>`); } // Extract tokens const internalUserId = state; const slackUserId = data.authed_user.id; const slackTeamId = data.team.id; const slackUserToken = data.authed_user.access_token; const slackBotToken = data.access_token || null; await upsertSlackUser({ internalUserId, slackUserId, slackTeamId, slackUserToken, slackBotToken, }); const maskedBotToken = slackBotToken ? `${slackBotToken.slice(0, 10)}...` : 'Not Available'; const maskedUserToken = slackUserToken ? `${slackUserToken.slice(0, 10)}...` : 'Not Available'; console.log(`Tokens saved for user ${internalUserId}. Bot: ${maskedBotToken}, User: ${maskedUserToken}`); // Success page res.send(` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Slack Connected - Cursor EOD MCP</title> <link rel="stylesheet" href="/styles.css"> <style> body { display: flex; justify-content: center; align-items: center; min-height: 100vh; flex-direction: column; } .card { max-width: 600px; width: 90%; padding: 2rem; background: rgba(255,255,255,0.03); border-radius: 16px; backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.1); text-align: center; } h1 { color: white; margin-bottom: 1rem; font-size: 2rem; } p { color: #ccc; margin-bottom: 1.5rem; } .warning-note { background: rgba(66, 153, 225, 0.15); border: 1px solid rgba(66, 153, 225, 0.3); padding: 1rem; border-radius: 8px; color: #90cdf4; margin-bottom: 2rem; font-size: 0.9rem; text-align: left; } .tokens { background: #111; padding: 1rem; border-radius: 8px; text-align: left; overflow-x: auto; font-family: monospace; margin-bottom: 2rem; color: #0f0; border: 1px solid rgba(255,255,255,0.1); } .cta-group { display: flex; flex-direction: column; gap: 1rem; align-items: center; } .btn { padding: 12px 24px; border-radius: 8px; font-weight: 600; text-decoration: none; transition: all 0.2s ease; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; width: 100%; max-width: 300px; } .btn-content { display: flex; align-items: center; gap: 8px; } .btn-subtext { font-size: 0.75rem; font-weight: 400; opacity: 0.9; } .btn-primary { background: #6366f1; color: white; box-shadow: 0 4px 14px rgba(99, 102, 241, 0.3); } .btn-primary:hover { transform: translateY(-2px); background: #5558e6; box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4); } .btn-secondary { background: transparent; color: white; border: 1px solid rgba(255,255,255,0.2); } .btn-secondary:hover { background: rgba(255,255,255,0.1); border-color: white; } </style> </head> <body> <div class="background-glow"></div> <div class="card"> <h1>Slack Connected! 🎉</h1> <p>Your workspace has been authenticated successfully.</p> <div class="warning-note"> <strong>ℹ️ Step 1 Complete:</strong> Copy these tokens now. You will need to paste them into your configuration after clicking the install button below. </div> <div class="tokens"> SLACK_BOT_TOKEN = ${slackBotToken}<br> SLACK_USER_TOKEN = ${slackUserToken}<br> SLACK_DEFAULT_CHANNEL = halo </div> <div class="cta-group"> <a href="cursor://anysphere.cursor-deeplink/mcp/install?name=@techhalo/cursor-eod-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkB0ZWNoaGFsby9jdXJzb3ItZW9kLW1jcCJdLCJlbnYiOnsiU0xBQ0tfQk9UX1RPS0VOIjoiIiwiU0xBQ0tfVVNFUl9UT0tFTiI6IiIsIlNMQUNLX0RFRkFVTFRfQ0hBTk5FTCI6IiJ9fQ==" class="btn btn-primary"> <span class="btn-content"> ⚡ One-Click Install in Cursor </span> <span class="btn-subtext">Step 2: Paste tokens after install</span> </a> <a href="/" class="btn btn-secondary">Return to Home</a> </div> </div> </body> </html> `); } catch (err) { console.error('Error during token exchange:', err); res.status(500).send('<h1>Internal Server Error</h1>'); } }); // 6. MCP Config Endpoint app.get('/api/mcp-config/:internalUserId', async (req, res) => { const { internalUserId } = req.params; try { const config = await getSlackConfigByInternalUserId(internalUserId); if (!config) { return res.status(404).json({ error: 'not_found' }); } res.json({ SLACK_BOT_TOKEN: config.slackBotToken, SLACK_USER_TOKEN: config.slackUserToken || "", SLACK_DEFAULT_CHANNEL: config.slackDefaultChannel }); } catch (err) { console.error('Error fetching config:', err); res.status(500).json({ error: 'internal_server_error' }); } }); app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });

Latest Blog Posts

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/SackeyDavid/cursor-eod-mcp'

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