Skip to main content
Glama
index.ts11.3 kB
import express from 'express'; import bodyParser from 'body-parser'; import crypto from 'crypto'; import dotenv from 'dotenv'; import path from 'path'; import { RFP, ParsedRFP, QuoteEvaluationResult } from './types'; import { parseRFP } from './parser'; import { findSimilarQuotes, formatMatchesForReview } from './matcher'; import { estimateCostAndLeadTime, formatEstimateForReview } from './estimator'; import { generateQuote, formatQuoteDocument, formatQuoteEmail } from './generator'; import { initializeStorage, loadHistoricalQuotes, saveHistoricalQuote, saveEvaluation, getEvaluation, convertToHistoricalQuote, } from './storage'; dotenv.config(); const app = express(); app.use(bodyParser.json()); // Serve static files from public directory app.use(express.static(path.join(__dirname, '..', 'public'))); // Initialize storage on startup initializeStorage(); // ========================================== // MCP CAPABILITY FUNCTIONS // ========================================== /** * Capability: ingestRfp * Parses an RFP and extracts structured information */ app.post('/mcp/function/ingestRfp', async (req, res) => { try { const rfp: RFP = req.body; if (!rfp.rawText) { return res.status(400).json({ ok: false, error: 'rawText is required' }); } const parsed = parseRFP(rfp); return res.json({ ok: true, parsed }); } catch (error: any) { return res.status(500).json({ ok: false, error: String(error) }); } }); /** * Capability: findSimilarQuotes * Searches historical quotes for similar past work */ app.post('/mcp/function/findSimilarQuotes', async (req, res) => { try { const { parsedRfp } = req.body as { parsedRfp: ParsedRFP }; if (!parsedRfp) { return res.status(400).json({ ok: false, error: 'parsedRfp is required' }); } const historicalQuotes = loadHistoricalQuotes(); const matches = findSimilarQuotes(parsedRfp, historicalQuotes); return res.json({ ok: true, matches }); } catch (error: any) { return res.status(500).json({ ok: false, error: String(error) }); } }); /** * Capability: estimateCostLeadTime * Calculates cost and lead time estimate */ app.post('/mcp/function/estimateCostLeadTime', async (req, res) => { try { const { parsedRfp, matches = [] } = req.body as any; if (!parsedRfp) { return res.status(400).json({ ok: false, error: 'parsedRfp is required' }); } const estimate = estimateCostAndLeadTime(parsedRfp, matches); return res.json({ ok: true, estimate }); } catch (error: any) { return res.status(500).json({ ok: false, error: String(error) }); } }); /** * Capability: generateQuote * Creates a formatted quote document */ app.post('/mcp/function/generateQuote', async (req, res) => { try { const { parsedRfp, estimate } = req.body as any; if (!parsedRfp || !estimate) { return res.status(400).json({ ok: false, error: 'parsedRfp and estimate are required' }); } const doc = generateQuote(parsedRfp, estimate); return res.json({ ok: true, doc }); } catch (error: any) { return res.status(500).json({ ok: false, error: String(error) }); } }); /** * Capability: approveQuote * Marks a quote as approved and optionally saves to historical database */ app.post('/mcp/function/approveQuote', async (req, res) => { try { const { quoteId, approvedBy, saveToHistory = true } = req.body as any; if (!quoteId || !approvedBy) { return res.status(400).json({ ok: false, error: 'quoteId and approvedBy are required' }); } // In a real system, you'd update the quote in the database // For now, we'll just return success return res.json({ ok: true, approved: true, message: `Quote ${quoteId} approved by ${approvedBy}` }); } catch (error: any) { return res.status(500).json({ ok: false, error: String(error) }); } }); /** * Capability: sendQuote * Sends quote via email (dry-run mode for safety) */ app.post('/mcp/function/sendQuote', async (req, res) => { try { const { doc, dryRun = true } = req.body as any; if (!doc) { return res.status(400).json({ ok: false, error: 'doc is required' }); } const { subject, body } = formatQuoteEmail(doc); if (dryRun) { return res.json({ ok: true, sent: false, dryRun: true, preview: { to: doc.to, subject, body: body.substring(0, 500) + '...' } }); } // In production, integrate with nodemailer or email service // For now, just simulate success return res.json({ ok: true, sent: true, message: `Quote sent to ${doc.to}` }); } catch (error: any) { return res.status(500).json({ ok: false, error: String(error) }); } }); // ========================================== // COORDINATOR FUNCTION // ========================================== /** * Coordinator: evaluateRfpAndDraftQuote * Orchestrates the entire quote generation pipeline */ app.post('/mcp/invoke/evaluateRfpAndDraftQuote', async (req, res) => { try { const { rfp, idempotencyKey: providedKey } = req.body as any; if (!rfp) { return res.status(400).json({ ok: false, error: 'rfp is required' }); } // Generate or use provided idempotency key const idempotencyKey = providedKey || crypto.createHash('sha256').update(JSON.stringify(rfp)).digest('hex'); // Check if we've already processed this request const cached = getEvaluation(idempotencyKey); if (cached) { return res.json({ ok: true, cached: true, result: cached }); } console.log('\n' + '='.repeat(70)); console.log('PROCESSING NEW RFP'); console.log('='.repeat(70)); // Step 1: Parse RFP console.log('\n[1/4] Parsing RFP...'); const parsed = parseRFP(rfp); console.log(` ✓ Extracted: ${parsed.material}, ${parsed.qty} units`); console.log(` ✓ Processes: ${parsed.processes?.join(', ') || 'none specified'}`); console.log(` ✓ Confidence: ${parsed.confidence}`); // Step 2: Find similar quotes console.log('\n[2/4] Searching historical quotes...'); const historicalQuotes = loadHistoricalQuotes(); const matches = findSimilarQuotes(parsed, historicalQuotes); console.log(` ✓ Found ${matches.length} similar quotes`); if (matches.length > 0) { console.log(` ✓ Top match: ${matches[0].quote.id} (${(matches[0].score * 100).toFixed(1)}% similar)`); } // Step 3: Estimate cost and lead time console.log('\n[3/4] Calculating cost estimate...'); const estimate = estimateCostAndLeadTime(parsed, matches); console.log(` ✓ Total: $${estimate.totalPrice.toFixed(2)}`); console.log(` ✓ Lead time: ${estimate.leadDays} days`); console.log(` ✓ Confidence: ${estimate.confidence}`); // Step 4: Generate quote document console.log('\n[4/4] Generating quote document...'); const doc = generateQuote(parsed, estimate); console.log(` ✓ Quote ID: ${doc.quoteId}`); console.log(` ✓ Status: ${doc.status}`); // Save evaluation const result: QuoteEvaluationResult = { parsedRfp: parsed, matches, estimate, doc, idempotencyKey, }; saveEvaluation(result); console.log('\n' + '='.repeat(70)); console.log('QUOTE GENERATION COMPLETE'); console.log('='.repeat(70) + '\n'); return res.json({ ok: true, result }); } catch (error: any) { console.error('Error in coordinator:', error); return res.status(500).json({ ok: false, error: String(error) }); } }); // ========================================== // UTILITY ENDPOINTS // ========================================== /** * Get formatted review of a quote evaluation */ app.post('/mcp/utility/formatReview', async (req, res) => { try { const { result } = req.body as { result: QuoteEvaluationResult }; if (!result) { return res.status(400).json({ ok: false, error: 'result is required' }); } let review = ''; review += '\n' + '='.repeat(70) + '\n'; review += 'QUOTE EVALUATION REVIEW\n'; review += '='.repeat(70) + '\n\n'; review += 'PARSED RFP:\n'; review += ` Material: ${result.parsedRfp.material}\n`; review += ` Quantity: ${result.parsedRfp.qty}\n`; review += ` Processes: ${result.parsedRfp.processes?.join(', ') || 'none'}\n`; review += ` Tolerances: ${result.parsedRfp.tolerances || 'not specified'}\n`; review += ` Due Date: ${result.parsedRfp.dueDate || 'not specified'}\n`; review += ` Confidence: ${result.parsedRfp.confidence}\n\n`; review += formatMatchesForReview(result.matches) + '\n'; review += formatEstimateForReview(result.estimate) + '\n'; review += formatQuoteDocument(result.doc); return res.json({ ok: true, review }); } catch (error: any) { return res.status(500).json({ ok: false, error: String(error) }); } }); /** * Add a historical quote manually */ app.post('/mcp/utility/addHistoricalQuote', async (req, res) => { try { const quote = req.body; saveHistoricalQuote(quote); return res.json({ ok: true, message: 'Historical quote added' }); } catch (error: any) { return res.status(500).json({ ok: false, error: String(error) }); } }); /** * Get all historical quotes */ app.get('/mcp/utility/historicalQuotes', async (req, res) => { try { const quotes = loadHistoricalQuotes(); return res.json({ ok: true, quotes, count: quotes.length }); } catch (error: any) { return res.status(500).json({ ok: false, error: String(error) }); } }); /** * Health check */ app.get('/health', (req, res) => { res.json({ ok: true, service: 'mcp-quoting-system' }); }); /** * Root route - serve the web interface */ app.get('/', (req, res) => { res.sendFile(path.join(__dirname, '..', 'public', 'index.html')); }); // ========================================== // START SERVER // ========================================== const PORT = process.env.PORT || 3789; app.listen(PORT, () => { console.log('\n' + '='.repeat(70)); console.log('MCP QUOTING SYSTEM'); console.log('='.repeat(70)); console.log(`\n✓ Server running on http://localhost:${PORT}`); console.log(`✓ Historical quotes loaded: ${loadHistoricalQuotes().length}`); console.log('\nEndpoints:'); console.log(' POST /mcp/function/ingestRfp'); console.log(' POST /mcp/function/findSimilarQuotes'); console.log(' POST /mcp/function/estimateCostLeadTime'); console.log(' POST /mcp/function/generateQuote'); console.log(' POST /mcp/function/approveQuote'); console.log(' POST /mcp/function/sendQuote'); console.log(' POST /mcp/invoke/evaluateRfpAndDraftQuote (COORDINATOR)'); console.log('\nUtility:'); console.log(' POST /mcp/utility/formatReview'); console.log(' POST /mcp/utility/addHistoricalQuote'); console.log(' GET /mcp/utility/historicalQuotes'); console.log(' GET /health'); console.log('\n' + '='.repeat(70) + '\n'); });

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/r-long/mcp-quoting-system'

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