Skip to main content
Glama
cli.ts8.44 kB
#!/usr/bin/env node /** * PartnerCore Proxy CLI * * Command-line interface for starting and managing the proxy server. */ import { Command } from 'commander'; import * as fs from 'fs'; import * as path from 'path'; import { createRequire } from 'module'; import { loadConfig, validateConfig, findALWorkspace } from './config/loader.js'; import { createLogger, getLogger } from './utils/logger.js'; import { ALExtensionManager } from './al/extension-manager.js'; import { ALLanguageServer } from './al/language-server.js'; import { CloudRelayClient } from './cloud/relay-client.js'; import { ToolRouter } from './router/tool-router.js'; import { PartnerCoreMcpServer } from './mcp/server.js'; const program = new Command(); // Read version from package.json using createRequire for ESM compatibility const require = createRequire(import.meta.url); const packageJson = require('../package.json') as { version: string }; const PROXY_VERSION = packageJson.version; program .name('partnercore') .description('MCP Server for Business Central AL Development') .version(PROXY_VERSION); interface StartOptions { workspace?: string; verbose?: boolean; cloud?: boolean; } // Default action: start server (when run without subcommand) program .option('-w, --workspace <path>', 'AL workspace root (containing app.json)') .option('-v, --verbose', 'Enable verbose logging') .option('--no-cloud', 'Disable cloud connection (local tools only)') .action((options: StartOptions) => { void startServer(options); }); // Explicit 'start' subcommand (for backwards compatibility) program .command('start') .description('Start the MCP server') .option('-w, --workspace <path>', 'AL workspace root (containing app.json)') .option('-v, --verbose', 'Enable verbose logging') .option('--no-cloud', 'Disable cloud connection (local tools only)') .action((options: StartOptions) => { void startServer(options); }); program .command('check') .description('Check configuration and connections') .action(() => { void checkConfiguration(); }); program .command('download-extension') .description('Download/update the AL Language extension') .action(() => { void downloadExtension(); }); program.parse(); /** * Start the proxy server */ async function startServer(options: StartOptions): Promise<void> { // Initialize logger const logLevel = options.verbose ? 'debug' : 'info'; createLogger(logLevel); const logger = getLogger(); logger.info('PartnerCore Proxy starting...'); // Load configuration const config = loadConfig(); // Override workspace if provided via CLI option if (options.workspace) { config.al.workspaceRoot = options.workspace; } else if (!config.al.workspaceRoot || !fs.existsSync(path.join(config.al.workspaceRoot, 'app.json'))) { // Auto-detect workspace if not set or invalid const detected = findALWorkspace(); if (detected) { config.al.workspaceRoot = detected; logger.info(`Auto-detected AL workspace: ${detected}`); } else { logger.warn('No AL workspace detected. Workspace will be auto-detected when tools are used.'); } } // Validate configuration const validation = validateConfig(config); if (!validation.valid) { for (const error of validation.errors) { logger.error(`Configuration error: ${error}`); } logger.error(''); logger.error('PartnerCore requires API_KEY to function.'); logger.error('Set the API_KEY environment variable in your MCP configuration:'); logger.error(''); logger.error(' "env": {'); logger.error(' "API_KEY": "your-api-key"'); logger.error(' }'); logger.error(''); process.exit(1); } // Log warnings separately for (const warning of validation.warnings) { logger.warn(`Configuration warning: ${warning}`); } // Initialize AL extension logger.info('Initializing AL extension...'); const extensionManager = new ALExtensionManager(config.al); const extensionInfo = await extensionManager.getExtension(); logger.info(`Using AL extension: ${extensionInfo.version}`); // Initialize AL Language Server (use detected workspace or fallback to cwd) const workspaceRoot = config.al.workspaceRoot || process.cwd(); logger.info(`Using workspace: ${workspaceRoot}`); logger.info('Starting AL Language Server...'); const alServer = new ALLanguageServer(extensionInfo, workspaceRoot); await alServer.initialize(); // Initialize Cloud Client logger.info('Connecting to PartnerCore Cloud...'); const cloudClient = new CloudRelayClient({ cloudUrl: config.cloudUrl, apiKey: config.apiKey, }); const connected = await cloudClient.checkConnection(); if (connected) { const apiKeyValid = await cloudClient.validateApiKey(); if (apiKeyValid) { logger.info('Cloud connected and API key validated'); } else { logger.error(''); logger.error('API key is invalid. Please check your API_KEY.'); logger.error(''); process.exit(1); } } else { logger.warn('Cloud connection unavailable - cloud features may not work'); } // Initialize Router (workspace will be auto-detected when tools are used) const router = new ToolRouter(config.al.workspaceRoot || undefined); router.setALServer(alServer); router.setCloudClient(cloudClient); // Initialize MCP Server const server = new PartnerCoreMcpServer(router); // Handle shutdown const shutdown = async (): Promise<void> => { logger.info('Shutting down...'); await server.stop(); await alServer.shutdown(); process.exit(0); }; process.on('SIGINT', () => void shutdown()); process.on('SIGTERM', () => void shutdown()); // Start server await server.start(); } /** * Check configuration and connections */ async function checkConfiguration(): Promise<void> { createLogger('info'); const logger = getLogger(); logger.info('Checking PartnerCore Proxy configuration...\n'); const config = loadConfig(); // Check configuration console.log('Configuration:'); console.log(` Cloud URL: ${config.cloudUrl}`); console.log(` API Key: ${config.apiKey ? '(set)' : '(not set)'}`); console.log(` Workspace: ${config.al.workspaceRoot}`); console.log(` Extension Version: ${config.al.extensionVersion}`); console.log(); // Validate configuration const validation = validateConfig(config); console.log('Validation:'); if (validation.valid) { console.log(' ✓ Configuration valid'); } else { for (const error of validation.errors) { console.log(` ✗ ${error}`); } } console.log(); // Check AL extension console.log('AL Extension:'); const extensionManager = new ALExtensionManager(config.al); try { const extensionInfo = await extensionManager.getExtension(); console.log(` ✓ Found: ${extensionInfo.version}`); console.log(` Path: ${extensionInfo.path}`); } catch (error) { console.log(` ✗ Not found: ${error instanceof Error ? error.message : 'Unknown error'}`); } console.log(); // Check cloud connection console.log('Cloud Connection:'); if (config.apiKey) { const cloudClient = new CloudRelayClient({ cloudUrl: config.cloudUrl, apiKey: config.apiKey, }); const connected = await cloudClient.checkConnection(); if (connected) { const apiKeyValid = await cloudClient.validateApiKey(); console.log(' ✓ Connected'); console.log(` API Key: ${apiKeyValid ? 'valid' : 'INVALID'}`); if (!apiKeyValid) { console.log(' ✗ API key validation failed - server will not start'); } } else { console.log(' ✗ Not connected'); } } else { console.log(' ✗ API_KEY is required but not set'); console.log(' Set the API_KEY environment variable to start the server'); } } /** * Download the AL extension */ async function downloadExtension(): Promise<void> { createLogger('info'); const logger = getLogger(); logger.info('Downloading AL Language extension...'); const config = loadConfig(); const extensionManager = new ALExtensionManager(config.al); try { const extensionInfo = await extensionManager.getExtension(); logger.info(`Downloaded: ${extensionInfo.version}`); logger.info(`Path: ${extensionInfo.path}`); } catch (error) { logger.error('Failed to download extension:', error); process.exit(1); } }

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/ciellosinc/partnercore-proxy'

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