tasq/node_modules/agentic-flow/dist/cli/mcp-manager.js

475 lines
16 KiB
JavaScript

#!/usr/bin/env node
/**
* MCP Server Manager - CLI for adding/managing MCP servers
*
* Allows end users to add custom MCP servers without editing code:
* - npx agentic-flow mcp add weather --npm weather-mcp
* - npx agentic-flow mcp add local --local /path/to/server.js
* - npx agentic-flow mcp list
* - npx agentic-flow mcp remove weather
*/
import fs from 'fs';
import path from 'path';
import os from 'os';
import { execSync } from 'child_process';
import { Command } from 'commander';
// Configuration file location
const CONFIG_DIR = path.join(os.homedir(), '.agentic-flow');
const CONFIG_FILE = path.join(CONFIG_DIR, 'mcp-config.json');
/**
* Load MCP configuration
*/
function loadConfig() {
if (!fs.existsSync(CONFIG_FILE)) {
return { servers: {} };
}
try {
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
return JSON.parse(content);
}
catch (error) {
console.error('❌ Error loading MCP config:', error);
return { servers: {} };
}
}
/**
* Save MCP configuration
*/
function saveConfig(config) {
// Ensure directory exists
if (!fs.existsSync(CONFIG_DIR)) {
fs.mkdirSync(CONFIG_DIR, { recursive: true });
}
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
}
/**
* Add MCP server from JSON config (Claude-style)
*/
function addServerFromJson(name, configJson) {
const config = loadConfig();
// Check if already exists
if (config.servers[name]) {
console.error(`❌ MCP server '${name}' already exists`);
console.log('💡 Use update command or remove first');
process.exit(1);
}
try {
const serverConfig = JSON.parse(configJson);
// Validate required fields
if (!serverConfig.command) {
console.error('❌ Config must include "command" field');
process.exit(1);
}
// Build MCP server config
const mcpConfig = {
enabled: true,
type: serverConfig.npm ? 'npm' : 'local',
package: serverConfig.npm,
command: serverConfig.command,
args: serverConfig.args || [],
env: serverConfig.env || {},
description: serverConfig.description || `MCP server: ${name}`
};
config.servers[name] = mcpConfig;
saveConfig(config);
console.log(`✅ Added MCP server: ${name}`);
console.log(` Type: ${mcpConfig.type}`);
console.log(` Command: ${mcpConfig.command} ${mcpConfig.args.join(' ')}`);
if (Object.keys(mcpConfig.env).length > 0) {
console.log(` Environment: ${Object.keys(mcpConfig.env).length} vars`);
}
console.log('\n💡 Use it with: npx agentic-flow --agent coder --task "your task"');
}
catch (error) {
console.error('❌ Invalid JSON config:', error.message);
console.log('\nExpected format:');
console.log(' \'{"command":"npx","args":["-y","weather-mcp"],"env":{"API_KEY":"xxx"}}\'');
process.exit(1);
}
}
/**
* Add MCP server (flag-based)
*/
function addServer(options) {
const config = loadConfig();
// Check if already exists
if (config.servers[options.name]) {
console.error(`❌ MCP server '${options.name}' already exists`);
console.log('💡 Use update command or remove first');
process.exit(1);
}
let serverConfig;
if (options.npm) {
// NPM package
serverConfig = {
enabled: true,
type: 'npm',
package: options.npm,
command: 'npx',
args: ['-y', options.npm],
env: {},
description: options.desc || `MCP server from ${options.npm}`
};
}
else if (options.local) {
// Local path
serverConfig = {
enabled: true,
type: 'local',
command: options.command || 'node',
args: [options.local],
env: {},
description: options.desc || `Local MCP server at ${options.local}`
};
}
else if (options.command) {
// Custom command
const args = options.args ? options.args.split(' ') : [];
serverConfig = {
enabled: true,
type: 'local',
command: options.command,
args,
env: {},
description: options.desc || `Custom MCP server: ${options.command}`
};
}
else {
console.error('❌ Must specify --npm, --local, or --command');
process.exit(1);
}
// Add environment variables
if (options.env) {
for (const envStr of options.env) {
const [key, ...valueParts] = envStr.split('=');
const value = valueParts.join('=');
serverConfig.env[key] = value;
}
}
config.servers[options.name] = serverConfig;
saveConfig(config);
console.log(`✅ Added MCP server: ${options.name}`);
console.log(` Type: ${serverConfig.type}`);
if (serverConfig.package) {
console.log(` Package: ${serverConfig.package}`);
}
console.log(` Command: ${serverConfig.command} ${serverConfig.args.join(' ')}`);
if (Object.keys(serverConfig.env).length > 0) {
console.log(` Environment: ${Object.keys(serverConfig.env).length} vars`);
}
console.log('\n💡 Run tests with: npx agentic-flow mcp test ' + options.name);
}
/**
* List MCP servers
*/
function listServers(options) {
const config = loadConfig();
const servers = Object.entries(config.servers);
if (servers.length === 0) {
console.log('No MCP servers configured');
console.log('\n💡 Add a server with: npx agentic-flow mcp add NAME --npm PACKAGE');
return;
}
console.log('Configured MCP Servers:\n');
for (const [name, server] of servers) {
// Filter by enabled status
if (options.enabled && !server.enabled) {
continue;
}
const status = server.enabled ? '✅' : '❌';
const state = server.enabled ? 'enabled' : 'disabled';
console.log(`${status} ${name} (${state})`);
console.log(` Type: ${server.type}`);
if (server.package) {
console.log(` Package: ${server.package}`);
}
else {
console.log(` Command: ${server.command} ${server.args.join(' ')}`);
}
if (server.description) {
console.log(` Description: ${server.description}`);
}
if (options.verbose) {
console.log(` Environment:`);
for (const [key, value] of Object.entries(server.env)) {
const masked = value.length > 10 ? `${value.slice(0, 4)}***${value.slice(-4)}` : '***';
console.log(` ${key}: ${masked}`);
}
}
console.log('');
}
}
/**
* Remove MCP server
*/
function removeServer(name, options) {
const config = loadConfig();
if (!config.servers[name]) {
console.error(`❌ MCP server '${name}' not found`);
process.exit(1);
}
if (!options.confirm) {
console.log(`⚠️ This will remove MCP server: ${name}`);
console.log(' Use --confirm to proceed');
process.exit(0);
}
delete config.servers[name];
saveConfig(config);
console.log(`✅ Removed MCP server: ${name}`);
}
/**
* Enable/disable MCP server
*/
function toggleServer(name, enabled) {
const config = loadConfig();
if (!config.servers[name]) {
console.error(`❌ MCP server '${name}' not found`);
process.exit(1);
}
config.servers[name].enabled = enabled;
saveConfig(config);
const action = enabled ? 'Enabled' : 'Disabled';
console.log(`${action} MCP server: ${name}`);
}
/**
* Update MCP server
*/
function updateServer(name, options) {
const config = loadConfig();
if (!config.servers[name]) {
console.error(`❌ MCP server '${name}' not found`);
process.exit(1);
}
const server = config.servers[name];
// Update version for NPM packages
if (options.version && server.type === 'npm' && server.package) {
const packageName = server.package.split('@')[0];
server.package = `${packageName}@${options.version}`;
server.args = ['-y', server.package];
}
// Update environment variables
if (options.env) {
for (const envStr of options.env) {
const [key, ...valueParts] = envStr.split('=');
const value = valueParts.join('=');
server.env[key] = value;
}
}
// Update command
if (options.command) {
server.command = options.command;
}
// Update args
if (options.args) {
server.args = options.args.split(' ');
}
saveConfig(config);
console.log(`✅ Updated MCP server: ${name}`);
}
/**
* Test MCP server
*/
function testServer(name, options) {
const config = loadConfig();
if (!config.servers[name]) {
console.error(`❌ MCP server '${name}' not found`);
process.exit(1);
}
const server = config.servers[name];
console.log(`Testing MCP server: ${name}`);
console.log(`Command: ${server.command} ${server.args.join(' ')}\n`);
try {
// Build environment
const env = { ...process.env, ...server.env };
// Test if server responds (send tools/list request)
const testRequest = JSON.stringify({
jsonrpc: '2.0',
method: 'tools/list',
id: 1
});
const cmd = `echo '${testRequest}' | ${server.command} ${server.args.join(' ')}`;
const result = execSync(cmd, {
encoding: 'utf-8',
env,
timeout: 5000
});
if (options.verbose) {
console.log('Response:', result);
}
// Try to parse response
const response = JSON.parse(result.split('\n')[0]);
if (response.result?.tools) {
console.log(`✅ Server started successfully`);
console.log(`✅ Responds to tools/list`);
console.log(`✅ Found ${response.result.tools.length} tools:`);
for (const tool of response.result.tools) {
console.log(` - ${tool.name}: ${tool.description || 'No description'}`);
}
console.log('\n✅ Server is working correctly');
}
else {
console.log('⚠️ Server responded but no tools found');
}
}
catch (error) {
console.error('❌ Server test failed:', error.message);
process.exit(1);
}
}
/**
* Show server info
*/
function showInfo(name) {
const config = loadConfig();
if (!config.servers[name]) {
console.error(`❌ MCP server '${name}' not found`);
process.exit(1);
}
const server = config.servers[name];
console.log(`MCP Server: ${name}`);
console.log(`Status: ${server.enabled ? '✅ Enabled' : '❌ Disabled'}`);
console.log(`Type: ${server.type}`);
if (server.package) {
console.log(`Package: ${server.package}`);
}
console.log(`Command: ${server.command} ${server.args.join(' ')}`);
if (Object.keys(server.env).length > 0) {
console.log(`Environment:`);
for (const [key, value] of Object.entries(server.env)) {
const masked = value.length > 10 ? `***${key.slice(0, 3)}***` : '***';
console.log(` ${key}: ${masked}`);
}
}
if (server.description) {
console.log(`Description: ${server.description}`);
}
console.log(`\nConfig file: ${CONFIG_FILE}`);
}
/**
* Export configuration
*/
function exportConfig() {
const config = loadConfig();
console.log(JSON.stringify(config, null, 2));
}
/**
* Import configuration
*/
function importConfig(configJson) {
try {
const importedConfig = JSON.parse(configJson);
// Validate structure
if (!importedConfig.servers || typeof importedConfig.servers !== 'object') {
throw new Error('Invalid config format');
}
const config = loadConfig();
// Merge configs
for (const [name, server] of Object.entries(importedConfig.servers)) {
config.servers[name] = server;
}
saveConfig(config);
const count = Object.keys(importedConfig.servers).length;
console.log(`✅ Imported ${count} MCP server(s)`);
}
catch (error) {
console.error('❌ Import failed:', error.message);
process.exit(1);
}
}
// CLI setup
const program = new Command();
program
.name('agentic-flow mcp')
.description('Manage MCP servers for agentic-flow')
.version('1.0.0')
.addHelpText('after', `
Examples:
# Add MCP server (Claude-style JSON config)
$ npx agentic-flow mcp add weather '{"command":"npx","args":["-y","weather-mcp"],"env":{"API_KEY":"xxx"}}'
# Add MCP server (simple flags)
$ npx agentic-flow mcp add weather --npm weather-mcp --env "API_KEY=xxx"
# Add local MCP server
$ npx agentic-flow mcp add my-tools --local /path/to/server.js
# List all configured servers
$ npx agentic-flow mcp list
# Test server
$ npx agentic-flow mcp test weather
# Use in agents (automatic)
$ npx agentic-flow --agent researcher --task "Get weather for Tokyo"
Config file: ~/.agentic-flow/mcp-config.json
`);
program
.command('add <name> [config]')
.description('Add a new MCP server (config as JSON string or options)')
.option('--npm <package>', 'NPM package name (e.g., weather-mcp@latest)')
.option('--local <path>', 'Local file path (e.g., /path/to/server.js)')
.option('--command <cmd>', 'Custom command (e.g., python3)')
.option('--args <args>', 'Command arguments')
.option('--env <key=value>', 'Environment variable (can use multiple times)')
.option('--desc <description>', 'Server description')
.action((name, config, options) => {
if (config) {
// JSON config format (like Claude MCP)
addServerFromJson(name, config);
}
else {
// Flag-based format
addServer({ name, ...options });
}
});
program
.command('list')
.description('List configured MCP servers')
.option('--enabled', 'Show only enabled servers')
.option('--verbose', 'Show detailed information')
.action((options) => listServers(options));
program
.command('remove <name>')
.description('Remove an MCP server')
.option('--confirm', 'Confirm removal')
.action((name, options) => removeServer(name, options));
program
.command('enable <name>')
.description('Enable an MCP server')
.action((name) => toggleServer(name, true));
program
.command('disable <name>')
.description('Disable an MCP server')
.action((name) => toggleServer(name, false));
program
.command('update <name>')
.description('Update MCP server configuration')
.option('--version <version>', 'Update NPM package version')
.option('--env <key=value>', 'Update environment variable (can use multiple times)')
.option('--command <cmd>', 'Update command')
.option('--args <args>', 'Update arguments')
.action((name, options) => updateServer(name, options));
program
.command('test <name>')
.description('Test if MCP server works')
.option('--verbose', 'Show detailed output')
.action((name, options) => testServer(name, options));
program
.command('info <name>')
.description('Show MCP server information')
.action((name) => showInfo(name));
program
.command('export')
.description('Export MCP configuration to stdout')
.action(() => exportConfig());
program
.command('import')
.description('Import MCP configuration from stdin')
.action(() => {
let data = '';
process.stdin.on('data', (chunk) => (data += chunk));
process.stdin.on('end', () => importConfig(data));
});
program.parse();
//# sourceMappingURL=mcp-manager.js.map