github-mcp-auto-git
Version:
GitHub MCP Auto Git v3.0 - メモリ効率化・統合MCP・モジュール化完了の完全自動Git操作システム
348 lines • 13.4 kB
JavaScript
/**
* Unified MCP Manager - Centralized MCP Client Management
* Consolidates all MCP operations following Constitutional AI principles
*/
import { spawn } from 'child_process';
export class UnifiedMCPManager {
constructor(config) {
this.servers = new Map();
this.serverConfigs = new Map();
this.initialized = new Set();
this.config = config;
this.setupServerConfigurations();
}
/**
* Initialize all configured MCP servers
* Fail Fast: Comprehensive validation and early error detection
* Be Lazy: Initialize only enabled servers
* TypeScript First: Complete type safety for server management
*/
async initialize() {
console.log('🔗 Unified MCP Manager を初期化中...');
const enabledServers = Array.from(this.serverConfigs.values())
.filter(server => server.enabled);
if (enabledServers.length === 0) {
console.log('ℹ️ 有効なMCPサーバーがありません');
return;
}
// Initialize servers in parallel for efficiency (Be Lazy)
const initPromises = enabledServers.map(server => this.initializeServer(server).catch(error => {
console.warn(`⚠️ ${server.name} 初期化失敗:`, error.message);
return null;
}));
const results = await Promise.allSettled(initPromises);
const successCount = results.filter(result => result.status === 'fulfilled' && result.value !== null).length;
console.log(`✅ MCP Manager 初期化完了: ${successCount}/${enabledServers.length} サーバー`);
}
/**
* Execute MCP operation with automatic fallback
* Fail Fast: Immediate server validation and error handling
* Be Lazy: Smart server selection and operation optimization
*/
async executeOperation(operation) {
const { server: serverName, operation: operationName, params, timeout = 30000 } = operation;
// Validate server exists and is initialized
if (!this.isServerAvailable(serverName)) {
return {
success: false,
error: `MCP server '${serverName}' is not available`
};
}
try {
console.log(`🔄 MCP操作実行: ${serverName}.${operationName}`);
const request = {
method: 'tools/call',
params: {
name: operationName,
arguments: params
}
};
const response = await this.sendRequest(serverName, request, timeout);
if (response.success) {
console.log(`✅ MCP操作成功: ${serverName}.${operationName}`);
}
else {
console.warn(`⚠️ MCP操作失敗: ${serverName}.${operationName} - ${response.error}`);
}
return response;
}
catch (error) {
console.error(`❌ MCP操作エラー: ${serverName}.${operationName}`, error);
return {
success: false,
error: `MCP operation failed: ${error}`
};
}
}
/**
* GitHub-specific operations with unified interface
* TypeScript First: Strongly typed GitHub operations
*/
async createPullRequest(options) {
return this.executeOperation({
server: 'github',
operation: 'create_pull_request',
params: {
owner: this.config.github.owner,
repo: this.config.github.repo,
...options
}
});
}
async mergePullRequest(options) {
return this.executeOperation({
server: 'github',
operation: 'merge_pull_request',
params: {
owner: this.config.github.owner,
repo: this.config.github.repo,
pull_number: options.pullNumber,
merge_method: options.mergeMethod || 'squash',
commit_title: options.commitTitle,
commit_message: options.commitMessage
}
});
}
async getPullRequestStatus(pullNumber) {
return this.executeOperation({
server: 'github',
operation: 'get_pull_request',
params: {
owner: this.config.github.owner,
repo: this.config.github.repo,
pull_number: pullNumber
}
});
}
async deleteBranch(branchName) {
return this.executeOperation({
server: 'github',
operation: 'delete_branch',
params: {
owner: this.config.github.owner,
repo: this.config.github.repo,
ref: `heads/${branchName}`
}
});
}
/**
* Check if a specific server is available
* Be Lazy: Efficient server availability checking
*/
isServerAvailable(serverName) {
return this.initialized.has(serverName) &&
this.servers.has(serverName) &&
!this.servers.get(serverName)?.killed;
}
/**
* Get status of all MCP servers
* TypeScript First: Type-safe status reporting
*/
getServerStatus() {
return Array.from(this.serverConfigs.entries()).map(([name, config]) => ({
server: name,
status: this.isServerAvailable(name) ? 'active' :
this.servers.has(name) ? 'error' : 'inactive',
enabled: config.enabled
}));
}
/**
* Cleanup all MCP servers
* Fail Fast: Comprehensive cleanup with error handling
*/
async cleanup() {
console.log('🧹 MCP Manager クリーンアップ中...');
const cleanupPromises = Array.from(this.servers.entries()).map(async ([name, process]) => {
try {
console.log(`🛑 ${name} サーバー停止中...`);
process.kill();
await this.waitForProcessExit(process, 5000);
console.log(`✅ ${name} サーバー停止完了`);
}
catch (error) {
console.warn(`⚠️ ${name} サーバー停止失敗:`, error);
}
});
await Promise.allSettled(cleanupPromises);
this.servers.clear();
this.initialized.clear();
console.log('✅ MCP Manager クリーンアップ完了');
}
/**
* Setup server configurations
* Be Lazy: Configuration-driven server setup
*/
setupServerConfigurations() {
// GitHub MCP Server
this.serverConfigs.set('github', {
name: 'GitHub MCP',
command: 'mcp-server-github',
args: [],
env: {
GITHUB_PERSONAL_ACCESS_TOKEN: this.config.github.token || '',
GITHUB_OWNER: this.config.github.owner || '',
GITHUB_REPO: this.config.github.repo || ''
},
enabled: Boolean(this.config.github.token)
});
// Future: Add more MCP servers here
// this.serverConfigs.set('slack', { ... });
// this.serverConfigs.set('notion', { ... });
}
/**
* Initialize a specific MCP server
* Fail Fast: Immediate server validation and error handling
*/
async initializeServer(serverConfig) {
try {
console.log(`🚀 ${serverConfig.name} 初期化中...`);
const mcpProcess = spawn(serverConfig.command, serverConfig.args, {
env: { ...process.env, ...serverConfig.env },
stdio: ['pipe', 'pipe', 'pipe']
});
if (!mcpProcess.stdout || !mcpProcess.stderr || !mcpProcess.stdin) {
throw new Error(`${serverConfig.name} プロセスの stdio が利用できません`);
}
// Setup error handling
mcpProcess.on('error', (error) => {
console.error(`❌ ${serverConfig.name} プロセスエラー:`, error);
this.handleServerError(serverConfig.name, error);
});
mcpProcess.stderr.on('data', (data) => {
console.warn(`⚠️ ${serverConfig.name} 警告:`, data.toString().trim());
});
mcpProcess.on('exit', (code, signal) => {
console.warn(`⚠️ ${serverConfig.name} プロセス終了: code=${code}, signal=${signal}`);
this.initialized.delete(serverConfig.name);
});
// Store the process
this.servers.set(serverConfig.name, mcpProcess);
// Wait for initialization
await this.waitForServerInitialization(serverConfig.name, mcpProcess);
this.initialized.add(serverConfig.name);
console.log(`✅ ${serverConfig.name} 初期化完了`);
}
catch (error) {
console.error(`❌ ${serverConfig.name} 初期化失敗:`, error);
throw error;
}
}
/**
* Send request to MCP server
* TypeScript First: Type-safe request/response handling
*/
async sendRequest(serverName, request, timeout = 30000) {
const process = this.servers.get(serverName);
if (!process?.stdin || !process?.stdout) {
return {
success: false,
error: `${serverName} server process not available`
};
}
return new Promise((resolve) => {
let responseData = '';
let timeoutHandle;
const cleanup = () => {
if (timeoutHandle)
clearTimeout(timeoutHandle);
process.stdout?.off('data', onData);
};
const onData = (data) => {
responseData += data.toString();
try {
const response = JSON.parse(responseData);
cleanup();
if (response.error) {
resolve({
success: false,
error: response.error.message || 'MCP server error'
});
}
else {
resolve({
success: true,
data: response.result
});
}
}
catch {
// Incomplete JSON, continue waiting
}
};
// Setup response handler
process.stdout?.on('data', onData);
// Setup timeout
timeoutHandle = setTimeout(() => {
cleanup();
resolve({
success: false,
error: `Request timeout (${timeout}ms)`
});
}, timeout);
// Send request
const jsonRequest = {
jsonrpc: '2.0',
id: Date.now(),
...request
};
process.stdin?.write(JSON.stringify(jsonRequest) + '\n');
});
}
/**
* Wait for server initialization
* Fail Fast: Timeout-based initialization validation
*/
async waitForServerInitialization(serverName, process, timeout = 10000) {
return new Promise((resolve, reject) => {
const timeoutHandle = setTimeout(() => {
reject(new Error(`${serverName} initialization timeout`));
}, timeout);
// For now, use simple timeout-based initialization
// In a real implementation, wait for specific initialization message
setTimeout(() => {
clearTimeout(timeoutHandle);
resolve();
}, 2000);
});
}
/**
* Wait for process to exit
* Be Lazy: Efficient process cleanup with timeout
*/
async waitForProcessExit(process, timeout = 5000) {
return new Promise((resolve) => {
const timeoutHandle = setTimeout(() => {
process.kill('SIGKILL'); // Force kill if timeout
resolve();
}, timeout);
process.on('exit', () => {
clearTimeout(timeoutHandle);
resolve();
});
});
}
/**
* Handle server errors with recovery attempts
* Fail Fast: Immediate error detection and recovery
*/
handleServerError(serverName, error) {
console.error(`❌ ${serverName} サーバーエラー:`, error);
// Remove from initialized set
this.initialized.delete(serverName);
// Optionally attempt restart (could be configurable)
setTimeout(async () => {
const serverConfig = this.serverConfigs.get(serverName);
if (serverConfig?.enabled) {
console.log(`🔄 ${serverName} 自動復旧を試行中...`);
try {
await this.initializeServer(serverConfig);
console.log(`✅ ${serverName} 自動復旧成功`);
}
catch (restartError) {
console.error(`❌ ${serverName} 自動復旧失敗:`, restartError);
}
}
}, 5000); // Wait 5 seconds before restart attempt
}
}
//# sourceMappingURL=unified-mcp-manager.js.map