@poai/mcpm-aider
Version:
Your MCP CLI Manager for aider(forked from @mcpm/cli). Helps you install/remove/search your MCP projects. You can also manage your MCP in Claude App with natural language.
183 lines (182 loc) • 7.34 kB
JavaScript
/**
* Copyright (C) 2024 PoAI (Lutz Leonhardt)
* This file is part of mcpm, based on work by MCP Club
* Licensed under the GNU AGPL v3.0
* See LICENSE file for details
*/
import { spawn } from 'child_process';
import * as path from 'path';
import * as fs from 'fs';
import { fileURLToPath } from 'url';
/**
* Checks if UV is installed on the system
* @returns Promise that resolves to true if UV is installed, false otherwise
*/
async function isUVInstalled() {
return new Promise((resolve) => {
const uvProcess = spawn('uv', ['--version']);
uvProcess.on('error', () => {
// Command not found
resolve(false);
});
uvProcess.on('close', (code) => {
// If exit code is 0, UV is installed
resolve(code === 0);
});
});
}
/**
* Starts the MCP-Bridge Python service
* @param options Command options
*/
/**
* Syncs Python dependencies using UV
* @param mcpBridgePath Path to the mcp-bridge directory
* @param currentDir Current working directory
*/
async function syncDependencies(mcpBridgePath, currentDir) {
try {
// Change to mcp-bridge directory
process.chdir(mcpBridgePath);
// Handle existing lock file by removing it and creating a new one
if (fs.existsSync(path.join(mcpBridgePath, 'uv.lock'))) {
// console.log('Existing uv.lock , removing and regenerating...');
try {
fs.unlinkSync(path.join(mcpBridgePath, 'uv.lock'));
// console.log('Removed existing uv.lock file');
}
catch (e) {
console.warn(`Failed to remove lock file: ${e.message}`);
}
// Now generate a new lock file
await new Promise((resolve) => {
const lockProcess = spawn('uv', ['lock'], { stdio: 'inherit' });
lockProcess.on('error', (err) => {
console.warn(`Warning: Failed to create new lock file: ${err.message}`);
resolve();
});
lockProcess.on('close', (code) => {
if (code === 0) {
// console.log('✓ Lock file created successfully');
}
else {
console.warn(`Warning: Lock file creation exited with code ${code}`);
}
resolve();
});
});
}
// Now run uv sync
await new Promise((resolve) => {
const syncProcess = spawn('uv', ['sync'], { stdio: 'inherit' });
syncProcess.on('error', (err) => {
console.warn(`Warning: Failed to run uv sync: ${err.message}`);
// Just warn and continue
resolve();
});
syncProcess.on('close', (code) => {
if (code === 0) {
console.log('✓ Python dependencies synced successfully');
resolve();
}
else {
console.warn(`Warning: uv sync exited with code ${code}`);
// Just warn and continue
resolve();
}
});
});
}
catch (error) {
console.warn(`Warning during dependency sync: ${error.message}`);
// Continue with bridge starting process anyway
}
finally {
// Change back to original directory
process.chdir(currentDir);
}
}
import { HostService, HostType } from '@mcpm/sdk';
/**
* Gets enabled MCP servers in a simplified format as a JSON string
* @returns A JSON string of server names to their command and args
*/
export async function getEnabledMCPServersAsJson() {
const claudeSrv = HostService.getInstanceByType(HostType.CLAUDE);
const enabledServers = await claudeSrv.getEnabledMCPServers();
const result = {};
for (const [name, serverInfo] of Object.entries(enabledServers)) {
result[name] = {
command: serverInfo.appConfig.command,
args: serverInfo.appConfig.args || []
};
}
return JSON.stringify(result, null, 2);
}
export async function startBridge(options = {}) {
const serverUrl = options.server || 'https://api.anthropic.com/v1/';
console.log('Starting MCP-Bridge...');
// Check if UV is installed
const uvInstalled = await isUVInstalled();
if (!uvInstalled) {
console.error('\nError: UV package manager not found.');
console.log('\nPlease install UV by following the instructions at:');
console.log('https://github.com/astral-sh/uv#installation');
console.log('\nFor example, on most systems you can install UV with:');
console.log('curl -LsSf https://astral.sh/uv/install.sh | sh');
throw new Error('UV package manager is required to run MCP-Bridge');
}
console.log('✓ UV is installed');
// Always run uv sync
console.log('Syncing Python dependencies...');
// Get the directory where this script is located in ESM context
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Determine if we're in development or production mode
const isDev = fs.existsSync(path.join(__dirname, '..', '..', 'src'));
// Set the appropriate path to mcp-bridge directory
const mcpBridgePath = isDev
? path.join(__dirname, '..', '..', 'python', 'mcp-bridge')
: path.join(__dirname, '..', 'python', 'mcp-bridge');
// Store current directory
const currentDir = process.cwd();
await syncDependencies(mcpBridgePath, currentDir);
// Get the enabled MCP servers as JSON
const mcpServersJson = await getEnabledMCPServersAsJson();
console.log(`Starting MCP-Bridge with server: ${serverUrl}`);
// Change to mcp-bridge directory to run the bridge
process.chdir(mcpBridgePath);
try {
// Start the MCP-Bridge Python process with inherited stdio to forward all output
const bridgeProcess = spawn('uv', [
'run',
'mcp_bridge/main.py',
'--inference_server.base_url', serverUrl,
'--mcp_servers', mcpServersJson
], {
stdio: 'inherit',
// Use shell option on Windows to handle special characters in the JSON string
shell: process.platform === 'win32'
});
// Set up proper handling for process exit
bridgeProcess.on('error', (error) => {
console.error(`Error starting MCP-Bridge: ${error.message}`);
process.chdir(currentDir); // Return to original directory
});
// Handle SIGINT (Ctrl+C) to gracefully terminate the bridge
process.on('SIGINT', () => {
console.log('\nTerminating MCP-Bridge...');
bridgeProcess.kill();
process.chdir(currentDir);
// Give the process a moment to clean up before exiting
setTimeout(() => process.exit(0), 500);
});
// We don't have a return statement here, so the CLI process will remain open
// until the bridge process terminates
}
catch (error) {
// Change back to original directory if an error occurs
process.chdir(currentDir);
throw error;
}
}