Skip to main content
Glama

Hi-GCloud

by su-record
sqlProxy.ts11.1 kB
import { executeGcloud, getProjectId } from '../utils/exec.js'; import { readConfig, CloudSqlInstance } from '../utils/config.js'; import { exec, spawn } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); export const gcpSqlProxyDefinition = { name: 'gcp_sql_proxy', description: 'Cloud SQL 프록시|DB 연결|sql proxy|database connect - Cloud SQL Proxy를 실행하여 로컬에서 Cloud SQL에 연결합니다', annotations: { title: 'Cloud SQL Proxy 실행', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true, }, inputSchema: { type: 'object' as const, properties: { instance: { type: 'string', description: 'Cloud SQL 인스턴스 이름 (예: my-instance)', }, port: { type: 'number', description: '로컬 포트 (기본: 5432 for PostgreSQL, 3306 for MySQL)', default: 5432, }, project_id: { type: 'string', description: 'GCP 프로젝트 ID (기본: 현재 설정된 프로젝트)', }, region: { type: 'string', description: '리전 (예: asia-northeast3)', }, action: { type: 'string', enum: ['start', 'status', 'stop'], description: '수행할 작업 (기본: status)', default: 'status', }, }, required: [], }, }; interface SqlProxyArgs { instance?: string; port?: number; project_id?: string; region?: string; action?: 'start' | 'status' | 'stop'; } // Track running proxy processes const runningProxies: Map<string, { pid: number; port: number; instance: string }> = new Map(); export async function gcpSqlProxy(args: SqlProxyArgs) { const action = args.action || 'status'; try { switch (action) { case 'status': return await getProxyStatus(); case 'start': return await startProxy(args); case 'stop': return await stopProxy(args); default: return { content: [{ type: 'text', text: `알 수 없는 액션: ${action}` }], isError: true, }; } } catch (error: any) { return { content: [{ type: 'text', text: `오류: ${error.message || error}` }], isError: true, }; } } async function getProxyStatus() { const lines: string[] = ['📊 Cloud SQL Proxy 상태', '']; // Check if cloud-sql-proxy is installed let proxyInstalled = false; let proxyPath = ''; const possiblePaths = [ 'cloud-sql-proxy', 'cloud_sql_proxy', '/usr/local/bin/cloud-sql-proxy', '/usr/local/bin/cloud_sql_proxy', `${process.env.HOME}/cloud-sql-proxy`, `${process.env.HOME}/google-cloud-sdk/bin/cloud-sql-proxy`, ]; for (const path of possiblePaths) { try { await execAsync(`${path} --version`, { timeout: 3000 }); proxyInstalled = true; proxyPath = path; break; } catch {} } if (!proxyInstalled) { lines.push('❌ Cloud SQL Proxy가 설치되지 않았습니다.'); lines.push(''); lines.push('## 설치 방법'); lines.push('```bash'); lines.push('# macOS'); lines.push('curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.14.1/cloud-sql-proxy.darwin.arm64'); lines.push('chmod +x cloud-sql-proxy'); lines.push('sudo mv cloud-sql-proxy /usr/local/bin/'); lines.push(''); lines.push('# Linux'); lines.push('curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.14.1/cloud-sql-proxy.linux.amd64'); lines.push('chmod +x cloud-sql-proxy'); lines.push('sudo mv cloud-sql-proxy /usr/local/bin/'); lines.push('```'); return { content: [{ type: 'text', text: lines.join('\n') }], }; } lines.push(`✅ Cloud SQL Proxy 설치됨: ${proxyPath}`); lines.push(''); // Check running proxy processes try { const { stdout } = await execAsync('ps aux | grep -E "cloud[-_]sql[-_]proxy" | grep -v grep', { timeout: 5000 }); const processes = stdout.trim().split('\n').filter(line => line.length > 0); if (processes.length > 0) { lines.push('## 실행 중인 프록시'); for (const proc of processes) { const parts = proc.split(/\s+/); const pid = parts[1]; // Extract instance connection string from command const instanceMatch = proc.match(/([a-zA-Z0-9-]+:[a-zA-Z0-9-]+:[a-zA-Z0-9-]+)/); const portMatch = proc.match(/--port[= ](\d+)/) || proc.match(/:(\d+)$/); lines.push(`- PID: ${pid}`); if (instanceMatch) lines.push(` 인스턴스: ${instanceMatch[1]}`); if (portMatch) lines.push(` 포트: ${portMatch[1]}`); } } else { lines.push('실행 중인 프록시가 없습니다.'); } } catch { lines.push('실행 중인 프록시가 없습니다.'); } // Check config for saved Cloud SQL instances const config = await readConfig(); if (config?.cloud_sql?.length) { lines.push('## 설정된 Cloud SQL 인스턴스 (.hi-gcloud.json)'); for (const sql of config.cloud_sql) { lines.push(`- **${sql.name}** (${sql.region}, 포트: ${sql.port || 5432})`); } lines.push(''); lines.push('💡 바로 실행: `gcp_sql_proxy(action: "start")`'); lines.push(''); } // List available Cloud SQL instances from GCP lines.push('## GCP Cloud SQL 인스턴스'); try { const result = await executeGcloud('sql instances list --format="table(name,region,databaseVersion,state)"', 15000); if (result.stdout.trim()) { lines.push('```'); lines.push(result.stdout.trim()); lines.push('```'); } else { lines.push('사용 가능한 인스턴스가 없습니다.'); } } catch (error: any) { lines.push(`인스턴스 목록 조회 실패: ${error.message || error}`); } if (!config?.cloud_sql?.length) { lines.push(''); lines.push('## 프록시 시작'); lines.push('`gcp_sql_proxy(action: "start", instance: "인스턴스명", region: "리전")`'); lines.push(''); lines.push('💡 gcp_setup으로 .hi-gcloud.json 생성 시 Cloud SQL 정보가 자동 저장됩니다.'); } return { content: [{ type: 'text', text: lines.join('\n') }], }; } async function startProxy(args: SqlProxyArgs) { // Try to get instance info from config if not provided let instanceName = args.instance; let region = args.region; let port = args.port; // If instance not provided, try to get from config if (!instanceName) { const config = await readConfig(); if (config?.cloud_sql?.length) { const firstInstance = config.cloud_sql[0]; instanceName = firstInstance.name; region = region || firstInstance.region; port = port || firstInstance.port; } } if (!instanceName) { return { content: [{ type: 'text', text: '❌ instance가 필요합니다.\n\n예: gcp_sql_proxy(action: "start", instance: "my-instance", region: "asia-northeast3")\n\n💡 또는 gcp_setup으로 .hi-gcloud.json에 cloud_sql 설정을 추가하세요.', }], isError: true, }; } // If region not provided, try to get from config if (!region) { const config = await readConfig(); const sqlConfig = config?.cloud_sql?.find((sql: CloudSqlInstance) => sql.name === instanceName); if (sqlConfig) { region = sqlConfig.region; port = port || sqlConfig.port; } } if (!region) { return { content: [{ type: 'text', text: `❌ region이 필요합니다.\n\n예: gcp_sql_proxy(action: "start", instance: "${instanceName}", region: "asia-northeast3")`, }], isError: true, }; } const projectId = await getProjectId(args.project_id); port = port || 5432; const connectionName = `${projectId}:${region}:${instanceName}`; // Find cloud-sql-proxy path let proxyPath = ''; const possiblePaths = [ 'cloud-sql-proxy', 'cloud_sql_proxy', '/usr/local/bin/cloud-sql-proxy', '/usr/local/bin/cloud_sql_proxy', ]; for (const path of possiblePaths) { try { await execAsync(`${path} --version`, { timeout: 3000 }); proxyPath = path; break; } catch {} } if (!proxyPath) { return { content: [{ type: 'text', text: '❌ Cloud SQL Proxy가 설치되지 않았습니다.\n\ngcp_sql_proxy(action: "status")로 설치 방법을 확인하세요.', }], isError: true, }; } // Check if port is already in use try { await execAsync(`lsof -i :${port}`, { timeout: 3000 }); return { content: [{ type: 'text', text: `❌ 포트 ${port}가 이미 사용 중입니다.\n\n다른 포트를 지정하세요: gcp_sql_proxy(action: "start", instance: "${instanceName}", port: ${port + 1})`, }], isError: true, }; } catch { // Port is available } // Start proxy in background const proxyProcess = spawn(proxyPath, [ connectionName, '--port', port.toString(), ], { detached: true, stdio: 'ignore', }); proxyProcess.unref(); // Wait a moment and check if it started await new Promise(resolve => setTimeout(resolve, 2000)); // Verify proxy is running try { const { stdout } = await execAsync(`lsof -i :${port}`, { timeout: 3000 }); if (stdout.includes('cloud')) { return { content: [{ type: 'text', text: `✅ Cloud SQL Proxy 시작됨 📡 연결 정보: - 인스턴스: ${connectionName} - 로컬 포트: ${port} - PID: ${proxyProcess.pid} 💡 연결 예시: \`\`\`bash # PostgreSQL psql -h localhost -p ${port} -U postgres -d your_database # MySQL mysql -h 127.0.0.1 -P ${port} -u root -p \`\`\` 🛑 중지: gcp_sql_proxy(action: "stop")`, }], }; } } catch {} return { content: [{ type: 'text', text: `⚠️ 프록시 시작을 시도했지만 확인이 필요합니다. 연결 이름: ${connectionName} 포트: ${port} gcp_sql_proxy(action: "status")로 상태를 확인하세요.`, }], }; } async function stopProxy(args: SqlProxyArgs) { const lines: string[] = []; try { const { stdout } = await execAsync('ps aux | grep -E "cloud[-_]sql[-_]proxy" | grep -v grep', { timeout: 5000 }); const processes = stdout.trim().split('\n').filter(line => line.length > 0); if (processes.length === 0) { return { content: [{ type: 'text', text: '실행 중인 Cloud SQL Proxy가 없습니다.' }], }; } for (const proc of processes) { const parts = proc.split(/\s+/); const pid = parts[1]; try { await execAsync(`kill ${pid}`, { timeout: 3000 }); lines.push(`✅ PID ${pid} 종료됨`); } catch (error: any) { lines.push(`❌ PID ${pid} 종료 실패: ${error.message}`); } } return { content: [{ type: 'text', text: lines.join('\n') }], }; } catch { return { content: [{ type: 'text', text: '실행 중인 Cloud SQL Proxy가 없습니다.' }], }; } }

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/su-record/hi-gcloud'

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