Skip to main content
Glama
auth-proxy.js10.9 kB
#!/usr/bin/env node /** * Authentication & Monetization Proxy for ACF MCP Server * Handles token validation, rate limiting, usage tracking, and billing */ const express = require('express'); const httpProxy = require('http-proxy-middleware'); const rateLimit = require('express-rate-limit'); const helmet = require('helmet'); const cors = require('cors'); const compression = require('compression'); const { createClient } = require('@supabase/supabase-js'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const app = express(); const port = process.env.AUTH_PORT || 3000; const mcpProxyUrl = process.env.MCP_PROXY_URL || 'http://localhost:8080'; // Security middleware app.use(helmet()); app.use(compression()); app.use(cors({ origin: [ 'https://claude.ai', 'https://cursor.sh', 'https://codeium.com', 'https://continue.dev' ], credentials: true })); app.use(express.json({ limit: '10mb' })); app.use(express.static('public')); // Database setup (Supabase for simplicity) const supabase = process.env.SUPABASE_URL ? createClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY ) : null; // In-memory store for demo (use Redis/database in production) const tokenStore = new Map(); const usageStore = new Map(); // Token tiers configuration const TIERS = { free: { maxRequests: 100, maxToolsPerMonth: 15, resetInterval: 30 * 24 * 60 * 60 * 1000, // 30 days allowedTools: [ 'list_directory', 'read_file', 'write_file', 'execute_command', 'search_files', 'get_file_info', 'copy_file', 'move_file', 'delete_file', 'create_directory', 'tree', 'browser_navigate', 'browser_click', 'browser_type', 'browser_take_screenshot' ] }, pro: { maxRequests: 10000, maxToolsPerMonth: -1, // unlimited resetInterval: 30 * 24 * 60 * 60 * 1000, allowedTools: null // all tools }, enterprise: { maxRequests: 100000, maxToolsPerMonth: -1, resetInterval: 30 * 24 * 60 * 60 * 1000, allowedTools: null } }; // Rate limiting by tier const createRateLimit = (windowMs, max) => rateLimit({ windowMs, max, message: { error: 'Rate limit exceeded. Please upgrade your plan.' }, standardHeaders: true, legacyHeaders: false, }); // Token validation middleware async function validateToken(req, res, next) { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ error: 'Missing or invalid authorization header' }); } const token = authHeader.substring(7); // Check token in database first, then fallback to memory let tokenData = null; if (supabase) { const { data } = await supabase .from('tokens') .select('*') .eq('token', token) .single(); tokenData = data; } if (!tokenData) { tokenData = tokenStore.get(token); } if (!tokenData) { return res.status(401).json({ error: 'Invalid token' }); } // Check if token is active if (!tokenData.active) { return res.status(401).json({ error: 'Token deactivated' }); } // Check usage limits const usage = usageStore.get(token) || { requests: 0, lastReset: Date.now() }; const tier = TIERS[tokenData.tier] || TIERS.free; // Reset usage if interval passed if (Date.now() - usage.lastReset > tier.resetInterval) { usage.requests = 0; usage.lastReset = Date.now(); usageStore.set(token, usage); } if (usage.requests >= tier.maxRequests) { return res.status(429).json({ error: 'Usage limit exceeded', tier: tokenData.tier, limit: tier.maxRequests, resetDate: new Date(usage.lastReset + tier.resetInterval) }); } // Add token data to request req.tokenData = tokenData; req.usage = usage; req.tier = tier; next(); } catch (error) { console.error('Token validation error:', error); res.status(500).json({ error: 'Authentication service error' }); } } // Tool access control middleware function checkToolAccess(req, res, next) { try { const { tier, tokenData } = req; const requestData = req.body; if (requestData && requestData.params && requestData.params.name) { const toolName = requestData.params.name; // Check if tool is allowed for this tier if (tier.allowedTools && !tier.allowedTools.includes(toolName)) { return res.status(403).json({ error: 'Tool not available in your tier', tool: toolName, tier: tokenData.tier, upgradeUrl: `${process.env.BASE_URL}/upgrade` }); } } next(); } catch (error) { console.error('Tool access check error:', error); next(); } } // Usage tracking middleware function trackUsage(req, res, next) { try { const token = req.headers.authorization?.substring(7); if (token && req.tokenData) { const usage = req.usage; usage.requests++; usageStore.set(token, usage); // Log usage for analytics console.log(JSON.stringify({ timestamp: new Date().toISOString(), token: token.substring(0, 8) + '...', tier: req.tokenData.tier, tool: req.body?.params?.name, requests: usage.requests })); } } catch (error) { console.error('Usage tracking error:', error); } next(); } // Proxy configuration const proxyOptions = { target: mcpProxyUrl, changeOrigin: true, ws: true, onError: (err, req, res) => { console.error('Proxy error:', err); res.status(502).json({ error: 'Service temporarily unavailable' }); }, onProxyReq: (proxyReq, req, res) => { // Add custom headers proxyReq.setHeader('X-Forwarded-User', req.tokenData?.userId || 'anonymous'); proxyReq.setHeader('X-User-Tier', req.tokenData?.tier || 'free'); } }; const proxy = httpProxy.createProxyMiddleware(proxyOptions); // Apply rate limiting based on tier app.use((req, res, next) => { const tier = req.tokenData?.tier || 'free'; const limits = { free: createRateLimit(60 * 1000, 10), // 10 requests per minute pro: createRateLimit(60 * 1000, 100), // 100 requests per minute enterprise: createRateLimit(60 * 1000, 1000) // 1000 requests per minute }; if (limits[tier]) { limits[tier](req, res, next); } else { next(); } }); // Health check app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), service: 'acf-auth-proxy' }); }); // Metrics endpoint app.get('/metrics', (req, res) => { const metrics = { totalTokens: tokenStore.size, totalUsage: Array.from(usageStore.values()).reduce((sum, usage) => sum + usage.requests, 0), tierDistribution: {} }; for (const [token, data] of tokenStore) { metrics.tierDistribution[data.tier] = (metrics.tierDistribution[data.tier] || 0) + 1; } res.json(metrics); }); // Authentication endpoints app.post('/api/signup/free', async (req, res) => { try { const token = `acf_free_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const tokenData = { token, tier: 'free', active: true, created: Date.now(), userId: req.body.email || `user_${Date.now()}` }; tokenStore.set(token, tokenData); usageStore.set(token, { requests: 0, lastReset: Date.now() }); // Save to database if available if (supabase) { await supabase.from('tokens').insert(tokenData); } res.json({ token, tier: 'free', limits: TIERS.free, message: 'Free tier token created successfully' }); } catch (error) { console.error('Free signup error:', error); res.status(500).json({ error: 'Failed to create free account' }); } }); app.post('/api/create-checkout-session', async (req, res) => { try { if (!stripe) { return res.status(500).json({ error: 'Stripe not configured' }); } const { tier = 'pro' } = req.body; const prices = { pro: process.env.STRIPE_PRO_PRICE_ID, enterprise: process.env.STRIPE_ENTERPRISE_PRICE_ID }; const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [{ price: prices[tier], quantity: 1, }], mode: 'subscription', success_url: `${process.env.BASE_URL}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${process.env.BASE_URL}/cancel`, metadata: { tier, service: 'acf-mcp-proxy' } }); res.json({ sessionId: session.id }); } catch (error) { console.error('Checkout session error:', error); res.status(500).json({ error: 'Failed to create checkout session' }); } }); // Stripe webhook for subscription management app.post('/webhook/stripe', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; try { const event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET); if (event.type === 'checkout.session.completed') { const session = event.data.object; const tier = session.metadata.tier; // Create pro/enterprise token const token = `acf_${tier}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const tokenData = { token, tier, active: true, created: Date.now(), stripeSessionId: session.id, userId: session.customer_email || `user_${Date.now()}` }; tokenStore.set(token, tokenData); usageStore.set(token, { requests: 0, lastReset: Date.now() }); console.log(`Created ${tier} token:`, token); } res.json({ received: true }); } catch (error) { console.error('Webhook error:', error); res.status(400).send(`Webhook Error: ${error.message}`); } }); // Protected MCP proxy routes app.use('/sse', validateToken, checkToolAccess, trackUsage, proxy); app.use('/messages', validateToken, checkToolAccess, trackUsage, proxy); // Default route app.get('/', (req, res) => { res.json({ service: 'ACF MCP Proxy', status: 'running', documentation: `${process.env.BASE_URL}/docs`, signup: `${process.env.BASE_URL}/signup` }); }); // Error handling app.use((error, req, res, next) => { console.error('Application error:', error); res.status(500).json({ error: 'Internal server error', requestId: req.id }); }); app.listen(port, () => { console.log(`🔐 ACF Auth Proxy running on port ${port}`); console.log(`🎯 Proxying to MCP server at ${mcpProxyUrl}`); console.log(`💳 Stripe configured: ${!!process.env.STRIPE_SECRET_KEY}`); console.log(`🗄️ Database configured: ${!!supabase}`); });

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/FutureAtoms/agentic-control-framework'

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