Skip to main content
Glama

Curupira

by drzln
index.ts.old9 kB
/** * Main MCP server entry point * * Orchestrates all components and starts the server */ import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { ListResourcesRequestSchema, ReadResourceRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js' import { MCPHandler } from './mcp-handler.js' import { ChromeClient } from '../chrome/client.js' import { RuntimeDomain } from '../chrome/domains/runtime.js' import { DOMDomain } from '../chrome/domains/dom.js' import { NetworkDomain } from '../chrome/domains/network.js' import { PageDomain } from '../chrome/domains/page.js' import { createFrameworkIntegrations } from '../integrations/index.js' import { createResourceProviders } from '../resources/index.js' import { TransportManager, type TransportType } from './transport.js' import { HealthChecker } from './health.js' import { SecurityManager } from '../security/index.js' import { logger } from '../config/logger.js' import { loadConfig } from '../config/config.js' import type { CDPConnectionOptions } from '@curupira/shared/types' export class CurupiraServer { private server: Server private handler: MCPHandler private chromeClient: ChromeClient private transportManager?: TransportManager private healthChecker: HealthChecker private securityManager: SecurityManager private isConnected = false constructor() { this.server = new Server( { name: 'curupira-debug', version: '1.0.0', }, { capabilities: { resources: { list: true, read: true, subscribe: false, }, tools: { list: true, call: true, }, prompts: { list: true, get: true, }, }, } ) this.chromeClient = new ChromeClient(logger) this.handler = new MCPHandler() this.healthChecker = new HealthChecker(this.chromeClient) // Initialize security with development defaults (will be configured in start()) this.securityManager = new SecurityManager({ enabled: false, environment: 'development', }) } /** * Initialize and start the server */ async start() { try { // Load configuration const config = loadConfig() logger.info({ config }, 'Starting Curupira MCP server') // Configure security based on environment const environment = process.env.NODE_ENV === 'production' ? 'production' : process.env.NODE_ENV === 'staging' ? 'staging' : 'development' this.securityManager = new SecurityManager({ enabled: environment !== 'development', environment, auth: { enabled: environment !== 'development', jwtSecret: process.env.CURUPIRA_JWT_SECRET, jwtPublicKey: process.env.CURUPIRA_JWT_PUBLIC_KEY, issuer: process.env.CURUPIRA_JWT_ISSUER, audience: process.env.CURUPIRA_JWT_AUDIENCE, }, }) // Connect to Chrome await this.connectToChrome(config.cdp) // Create a session for the default page const targets = await this.chromeClient.getTargets() const pageTarget = targets.find((t) => t.type === 'page') if (!pageTarget) { throw new Error('No page target found') } const session = await this.chromeClient.createSession(pageTarget.targetId) // Initialize domains const domains = await this.initializeDomains(session.id || session.sessionId) // Create integrations const integrations = createFrameworkIntegrations( domains.runtime, domains.dom ) // Create providers const resourceProviders = createResourceProviders( domains.runtime, domains.dom, domains.network, domains.page, integrations ) // Initialize handler with providers this.handler.initialize(resourceProviders) // Update health checker with counts const resources = await resourceProviders.listResources() this.healthChecker.updateCounts(resources.length, 0) // Tools handled by setupMCPHandlers // Setup request handlers this.setupHandlers() // Start transport const transportType = (process.env.CURUPIRA_TRANSPORT || 'stdio') as TransportType this.transportManager = new TransportManager(this.server, { type: transportType, port: config.server?.port, corsOrigins: config.server?.corsOrigins, healthChecker: this.healthChecker, securityManager: this.securityManager, }) await this.transportManager.start() logger.info({ transport: transportType }, 'MCP server started successfully') } catch (error) { logger.error({ error }, 'Failed to start MCP server') throw error } } /** * Connect to Chrome DevTools Protocol */ private async connectToChrome(options: CDPConnectionOptions) { try { await this.chromeClient.connect(options) this.isConnected = true logger.info('Connected to Chrome DevTools Protocol') // Setup connection event handlers this.chromeClient.on('disconnect', () => { logger.warn('Chrome connection lost, attempting reconnect...') this.handleReconnect(options) }) } catch (error) { logger.error({ error }, 'Failed to connect to Chrome') throw error } } /** * Initialize CDP domains */ private async initializeDomains(sessionId: string) { const runtime = new RuntimeDomain(this.chromeClient, sessionId) const dom = new DOMDomain(this.chromeClient, sessionId) const network = new NetworkDomain(this.chromeClient, sessionId) const page = new PageDomain(this.chromeClient, sessionId) // Enable domains await Promise.all([ runtime.enable(), dom.enable(), network.enable(), page.enable(), ]) logger.info('CDP domains initialized') return { runtime, dom, network, page } } /** * Setup MCP request handlers */ private setupHandlers() { // Resource handlers this.server.setRequestHandler( ListResourcesRequestSchema, async () => this.handler.listResources() ) this.server.setRequestHandler( ReadResourceRequestSchema, async (request) => this.handler.readResource(request.params) ) // Tool handlers this.server.setRequestHandler( ListToolsRequestSchema, async () => this.handler.listTools() ) this.server.setRequestHandler( CallToolRequestSchema, async (request) => this.handler.callTool(request.params) ) // Prompt handlers this.server.setRequestHandler( ListPromptsRequestSchema, async () => this.handler.listPrompts() ) this.server.setRequestHandler( GetPromptRequestSchema, async (request) => this.handler.getPrompt(request.params) ) // Note: Health and statistics are handled via HTTP endpoints, not MCP logger.info('Request handlers configured') } /** * Handle Chrome reconnection */ private async handleReconnect(options: CDPConnectionOptions) { const maxRetries = 5 const baseDelay = 1000 for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const delay = baseDelay * Math.pow(2, attempt - 1) logger.info({ attempt, delay }, 'Attempting reconnection') await new Promise(resolve => setTimeout(resolve, delay)) await this.chromeClient.connect(options) this.isConnected = true logger.info('Reconnected successfully') return } catch (error) { logger.error({ error, attempt }, 'Reconnection failed') if (attempt === maxRetries) { logger.error('Max reconnection attempts reached') this.isConnected = false } } } } /** * Graceful shutdown */ async shutdown() { logger.info('Shutting down MCP server') try { await this.chromeClient.disconnect() if (this.transportManager) { await this.transportManager.stop() } await this.server.close() logger.info('Server shut down successfully') } catch (error) { logger.error({ error }, 'Error during shutdown') } } } // Main entry point if (import.meta.url === `file://${process.argv[1]}`) { const server = new CurupiraServer() // Handle graceful shutdown process.on('SIGINT', async () => { logger.info('Received SIGINT signal') await server.shutdown() process.exit(0) }) process.on('SIGTERM', async () => { logger.info('Received SIGTERM signal') await server.shutdown() process.exit(0) }) // Start server server.start().catch((error) => { logger.error({ error }, 'Fatal error') 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/drzln/curupira'

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