Skip to main content
Glama

gcloud MCP Cloud Run

by addhe
server.js6.9 kB
// Minimal HTTP server for Cloud Run that proxies request body to gcloud-mcp CLI bundle const http = require('http'); const { spawn } = require('child_process'); const PORT = process.env.PORT || 8080; const handler = async (req, res) => { // Normalize path (strip query, fragment, or accidental semicolon suffixes) const rawUrl = req.url || '/'; const path = rawUrl.split(/[?#;]/)[0]; // Health endpoint if (req.method === 'GET' && path === '/health') { res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ status: 'ok', time: new Date().toISOString() })); return; } // Diagnostic endpoint: run `gcloud --version` to check runtime availability if (req.method === 'GET' && path === '/diag') { const child = spawn('gcloud', ['--version'], { stdio: ['ignore', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; child.stdout.on('data', (c) => { stdout += c.toString(); }); child.stderr.on('data', (c) => { stderr += c.toString(); }); child.on('error', (err) => { res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: err.message, stderr })); }); child.on('close', (code) => { if (code !== 0) { res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ code, stderr: stderr.trim(), stdout: stdout.trim() })); return; } res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end(stdout || ''); }); return; } let body = []; req.on('data', (chunk) => body.push(chunk)); req.on('end', () => { body = Buffer.concat(body).toString() || ''; console.log(`${new Date().toISOString()} ${req.method} ${rawUrl} - received body length=${body.length}`); // Try to interpret a simple JSON shape that directly maps to the run_gcloud_command // tool: { "tool": "run_gcloud_command", "input": { "args": ["compute", "instances", "list"] } } // If present, run `gcloud` directly with those args and return the result. This avoids // having to implement the full MCP stdio protocol in HTTP wrapper. let parsed = null; try { if (body) parsed = JSON.parse(body); } catch (e) { // Not JSON — fall back to MCP/stdio behavior below parsed = null; } if (parsed && parsed.tool === 'run_gcloud_command' && parsed.input && Array.isArray(parsed.input.args)) { console.log('[info] Detected direct run_gcloud_command request — executing gcloud directly'); const gcloudArgs = parsed.input.args; const child = spawn('gcloud', gcloudArgs, { stdio: ['ignore', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; child.stdout.on('data', (c) => { stdout += c.toString(); }); child.stderr.on('data', (c) => { stderr += c.toString(); }); child.on('error', (err) => { console.error('gcloud child error', err); if (!res.headersSent) { res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: err.message, stderr })); } }); child.on('close', (code) => { console.log(`gcloud exited code=${code}`); console.log('[gcloud captured stdout]', stdout ? stdout.replace(/\n+$/,'') : '<empty>'); console.error('[gcloud captured stderr]', stderr ? stderr.replace(/\n+$/,'') : '<empty>'); if (!res.headersSent) { if (code !== 0) { res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ code, stderr: stderr.trim(), stdout: stdout.trim() })); return; } // If stdout is empty, return a helpful JSON diagnostic so callers can see stderr if (!stdout || stdout.trim().length === 0) { res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ message: 'gcloud ran successfully but produced no stdout', stderr: stderr.trim() })); return; } // Try to parse stdout as JSON, else return plain text try { const parsedOut = JSON.parse(stdout); res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(parsedOut)); } catch (e) { res.setHeader('Content-Type', 'text/plain'); res.end(stdout); } } }); return; } // Default behaviour: spawn the gcloud-mcp CLI (via npx) and pipe the raw request body to it. console.log('[info] Spawning gcloud-mcp via npx'); const child = spawn('npx', ['-y', '@google-cloud/gcloud-mcp'], { stdio: ['pipe', 'pipe', 'pipe'] }); // Safety timeout for child const childTimeoutMs = parseInt(process.env.CHILD_TIMEOUT_MS || '25000', 10); const killer = setTimeout(() => { console.error(`Child exceeded timeout (${childTimeoutMs}ms). Killing.`); try { child.kill('SIGKILL'); } catch (e) {} }, childTimeoutMs); child.stdin.write(body); child.stdin.end(); let stdout = ''; let stderr = ''; child.stdout.on('data', (c) => { const s = c.toString(); stdout += s; // also mirror to container logs for debugging console.log('[child stdout]', s.replace(/\n+$/,'')); }); child.stderr.on('data', (c) => { const s = c.toString(); stderr += s; console.error('[child stderr]', s.replace(/\n+$/,'')); }); child.on('error', (err) => { clearTimeout(killer); console.error('Child process error', err); if (!res.headersSent) { res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: err.message, stderr })); } }); child.on('close', (code) => { clearTimeout(killer); console.log(`Child process exited with code=${code}`); console.log('[child captured stdout]', stdout); console.error('[child captured stderr]', stderr); if (!res.headersSent) { if (code !== 0) { res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ code, stderr: stderr.trim(), stdout: stdout.trim() })); return; } // Try to parse stdout as JSON try { const parsed = JSON.parse(stdout); res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify(parsed)); } catch (e) { res.setHeader('Content-Type', 'text/plain'); res.end(stdout); } } }); }); }; const server = http.createServer(handler); server.listen(PORT, () => { console.log(`Server listening on port ${PORT}`); });

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/addhe/gcloud-mcp-cloudrun'

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