Skip to main content
Glama
index.ts8.48 kB
#!/usr/bin/env node import { SearchService } from './services/searchService.js'; import { CompassSearchProvider } from './services/core/search/CompassSearchProvider.js'; import { ServerService, TransportType, TransportConfig } from './services/core/server/index.js'; import logger from './utils/logger.js'; import path from 'path'; import { spawn } from 'child_process'; import fs from 'fs'; import os from 'os'; import { randomBytes } from 'crypto'; import { GetMcpSearchProvider } from './services/core/search/GetMcpSearchProvider.js'; import { MeilisearchSearchProvider } from './services/core/search/MeilisearchSearchProvider.js'; import { getParamValue } from '@chatmcp/sdk/utils/index.js'; import { NacosMcpProvider } from './services/core/search/NacosMcpProvider.js'; import type { SearchProvider } from './types/index.js'; /** * Main application entry point * Initializes services and starts the MCP server */ async function main() { try { logger.info('Starting MCP Advisor application'); // Get parameters from command line or environment variables const mode = getParamValue('mode') || process.env.TRANSPORT_TYPE || 'stdio'; const port = parseInt( getParamValue('port') || process.env.SERVER_PORT || '3000', 10, ); const host = getParamValue('host') || process.env.SERVER_HOST || 'localhost'; const messagePath = getParamValue('messagePath') || '/messages'; const endpoint = getParamValue('endpoint') || process.env.ENDPOINT || '/rest'; // Configure transport const transportConfig: TransportConfig = { port, host, ssePath: '/sse', messagePath, endpoint, }; // If user opts in, ensure local Meilisearch is running and configured const wantLocalMeili = process.argv.includes('--local-meilisearch'); if (wantLocalMeili) { await ensureLocalMeilisearch(); } // Initialize search providers const searchProviders: SearchProvider[] = [ new MeilisearchSearchProvider(), new CompassSearchProvider(), new GetMcpSearchProvider() ]; // Add Nacos provider if environment variables are configured const nacosServerAddr = process.env.NACOS_SERVER_ADDR; const nacosUsername = process.env.NACOS_USERNAME; const nacosPassword = process.env.NACOS_PASSWORD; if (nacosServerAddr && nacosUsername && nacosPassword) { try { // Ensure required environment variables are defined const mcpHost = process.env.MCP_HOST || 'localhost'; const mcpPort = process.env.MCP_PORT ? parseInt(process.env.MCP_PORT, 10) : 3000; const authToken = process.env.AUTH_TOKEN || ''; const debug = process.env.NACOS_DEBUG === 'true'; const nacosProvider = new NacosMcpProvider({ serverAddr: nacosServerAddr, username: nacosUsername, password: nacosPassword, mcpHost, mcpPort, authToken, debug }); // Initialize the provider asynchronously await nacosProvider.init(); searchProviders.push(nacosProvider); logger.info('Nacos MCP provider initialized successfully'); } catch (error) { logger.error( `Failed to initialize Nacos MCP provider: ${error instanceof Error ? error.message : String(error)}`, { error } ); } } else { logger.warn( 'Nacos MCP provider not initialized: Missing required environment variables (NACOS_SERVER_ADDR, NACOS_USERNAME, NACOS_PASSWORD)' ); } const searchService = new SearchService(searchProviders); // Best-effort async bootstrap for local Meilisearch try { if ((process.env.MEILISEARCH_INSTANCE || 'cloud') === 'local') { // Try multiple potential script locations const possiblePaths = [ path.resolve(process.cwd(), 'scripts', 'meilisearch', 'meilisearch.bootstrap.mjs'), path.resolve(process.cwd(), 'scripts', 'bootstrap-meilisearch.mjs'), ]; const scriptPath = possiblePaths.find(p => { try { return require('fs').existsSync(p); } catch { return false; } }); if (scriptPath) { const child = spawn(process.execPath, ['--no-deprecation', scriptPath], { env: { ...process.env }, stdio: 'ignore', detached: true, }); child.unref(); logger.info('Triggered async Meilisearch bootstrap'); } else { logger.debug('Bootstrap script not found, skipping'); } } } catch (e) { logger.warn('Skip Meilisearch bootstrap'); } // Determine transport type let transportType = TransportType.STDIO; if (mode === 'sse') { transportType = TransportType.SSE; } else if (mode === 'rest') { transportType = TransportType.REST; } // Start server const serverService = new ServerService(searchService); await serverService.start(transportType, transportConfig); logger.info(`MCP Advisor server started with ${transportType} transport,endpoint:${endpoint}`); } catch (error) { logger.error( `Fatal error in main(): ${error instanceof Error ? error.message : String(error)}`, ); process.exit(1); } } main().catch(error => { logger.error( `Fatal error in main(): ${error instanceof Error ? error.message : String(error)}`, ); process.exit(1); }); // Helpers async function ensureLocalMeilisearch(): Promise<void> { const baseDir = path.join(os.homedir(), '.meilisearch'); const dbPath = path.join(baseDir, 'data.ms'); const host = process.env.MEILISEARCH_LOCAL_HOST || 'http://localhost:7700'; const indexName = process.env.MEILISEARCH_INDEX_NAME || 'mcp_servers'; const healthy = async (): Promise<boolean> => { try { const fetchFn: any = (globalThis as any).fetch; if (!fetchFn) return false; const res = await fetchFn(`${host}/health`); return res.ok; } catch { return false; } }; if (await healthy()) { // Just enforce local instance mode for provider alignment process.env.MEILISEARCH_INSTANCE = 'local'; process.env.MEILISEARCH_LOCAL_HOST = host; if (!process.env.MEILISEARCH_INDEX_NAME) process.env.MEILISEARCH_INDEX_NAME = indexName; logger.info('Detected running local Meilisearch; using it'); return; } // Try to start a local Meilisearch instance try { fs.mkdirSync(baseDir, { recursive: true }); } catch {} const candidates = [ process.env.MEILISEARCH_BIN, 'meilisearch', path.join(baseDir, 'bin', 'meilisearch'), ].filter(Boolean) as string[]; const pickExisting = (): string | null => { for (const c of candidates) { try { // If absolute path, check fs; otherwise rely on PATH execution if (path.isAbsolute(c)) { if (fs.existsSync(c)) return c; } else { return c; // let spawn resolve from PATH } } catch {} } return null; }; const bin = pickExisting(); if (!bin) { logger.warn('Meilisearch binary not found in PATH or ~/.meilisearch/bin; skip auto-start'); return; } const masterKey = process.env.MEILISEARCH_MASTER_KEY || randomKey(); const args = [`--master-key=${masterKey}`, `--db-path=${dbPath}`]; try { const child = spawn(bin, args, { stdio: 'ignore', detached: true }); child.unref(); logger.info('Starting local Meilisearch (background)...'); } catch (e) { logger.warn('Failed to start Meilisearch automatically'); return; } // Wait for health up to 60s const startedAt = Date.now(); while (Date.now() - startedAt < 60000) { if (await healthy()) break; await new Promise(r => setTimeout(r, 1000)); } if (!(await healthy())) { logger.warn('Meilisearch did not become healthy in 60s; continuing without local instance'); return; } // Configure in-process env so provider uses local process.env.MEILISEARCH_INSTANCE = 'local'; process.env.MEILISEARCH_LOCAL_HOST = host; process.env.MEILISEARCH_MASTER_KEY = masterKey; process.env.MEILISEARCH_INDEX_NAME = indexName; process.env.MEILISEARCH_DB_PATH = dbPath; logger.info('Local Meilisearch is ready; environment configured in-process'); } function randomKey(): string { // 64 hex chars using Node crypto return randomBytes(32).toString('hex'); }

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/istarwyh/mcpadvisor'

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