Skip to main content
Glama
index.js12.9 kB
const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const { runRemoteCommand } = require('./ssh'); const { startInstance, stopInstance } = require('./gcp'); const app = express(); const PORT = process.env.PORT || 3000; // Store for OAuth clients (in production, use a database) const oauthClients = new Map(); // --- Middleware --- app.use(cors()); app.use(bodyParser.json()); // Helper function to send JSON-RPC response function sendJsonRpcResponse(res, id, result = null, error = null) { const response = { jsonrpc: '2.0', id }; if (error) { response.error = error; } else { response.result = result; } res.json(response); } // Helper function to send JSON-RPC notification function sendJsonRpcNotification(res, method, params = {}) { const message = { jsonrpc: '2.0', method, params }; res.write(`data: ${JSON.stringify(message)}\n\n`); } // Define MCP tools // --- MCP tools (improved descriptions for Cursor suggestions) --- const tools = [ { name: 'run_command', description: 'Execute a SSH command on the dev server (always connects to "dev" host regardless of host specified). Useful for checking disk usage, running maintenance commands, or viewing logs on servers.', inputSchema: { type: 'object', properties: { host: { type: 'string', description: 'The hostname or IP address (ignored - always uses "dev")' }, cmdKey: { type: 'string', description: 'The command you want to run, e.g., "df -h" or "ls -la"' } }, required: ['host', 'cmdKey'] } }, { name: 'start_instance', description: 'Start a Google Cloud Compute Engine (GCE) virtual machine. Use for powering up instances in your project and zone.', inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'GCP project ID containing the VM' }, zone: { type: 'string', description: 'Zone where the VM resides, e.g., us-central1-a' }, instance: { type: 'string', description: 'The name of the instance to start' } }, required: ['project', 'zone', 'instance'] } }, { name: 'stop_instance', description: 'Stop a Google Cloud Compute Engine (GCE) virtual machine. Use to safely shut down a VM to save costs or perform maintenance.', inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'GCP project ID containing the VM' }, zone: { type: 'string', description: 'Zone where the VM resides, e.g., us-central1-a' }, instance: { type: 'string', description: 'The name of the instance to stop' } }, required: ['project', 'zone', 'instance'] } } ]; // --- MCP JSON-RPC endpoint (POST /) --- app.post('/', async (req, res) => { try { const { method, params, id, jsonrpc } = req.body || {}; if (!method) { console.error('Missing method in request'); return res.status(400).json({ error: 'Missing method' }); } if (jsonrpc !== '2.0') { console.error('Invalid JSON-RPC version:', jsonrpc); return sendJsonRpcResponse(res, id, null, { code: -32600, message: 'Invalid Request' }); } // Handle notifications (no id) if (id === undefined) { switch (method) { case 'initialized': console.log('Client initialized'); return res.status(200).json({ jsonrpc: '2.0' }); default: console.log(`Received notification: ${method}`); return res.status(200).json({ jsonrpc: '2.0' }); } } // Handle requests (with id) switch (method) { case 'initialize': console.log('Initializing MCP server...'); sendJsonRpcResponse(res, id, { protocolVersion: params.protocolVersion || '2025-06-18', capabilities: { tools: { listChanged: false }, prompts: {}, resources: {} }, serverInfo: { name: 'mcp-remote', version: '1.0.0' } }); break; case 'tools/list': console.log('Listing tools...'); sendJsonRpcResponse(res, id, { tools }); break; case 'tools/call': try { if (!params || !params.name) { throw new Error('Missing tool name in params'); } const { name, arguments: args } = params; console.log(`Calling tool: ${name} with args:`, args); let result; switch (name) { case 'run_command': try { // IGNORE the host parameter from user - always use "dev" from ~/.ssh/config // The host alias "dev" must be defined in ~/.ssh/config const hostAlias = 'dev'; const command = args.cmdKey || args.command || ''; if (!command) { throw new Error('Command is required'); } // Log that we're ignoring the user's host and using dev instead const userSpecifiedHost = args.host || 'unspecified'; console.log(`Ignoring host "${userSpecifiedHost}" - always using "dev" from ~/.ssh/config`); console.log(`Executing command on ${hostAlias} (from ~/.ssh/config): ${command}`); // Connection details are read from ~/.ssh/config for the "dev" host const sshResult = await runRemoteCommand({ host: hostAlias, // Always "dev" regardless of user input command }); // Build output message let output = `Command executed on "dev" (from ~/.ssh/config):\n\n`; output += `Command: ${command}\n\n`; if (sshResult.stdout) { output += `STDOUT:\n${sshResult.stdout}\n`; } if (sshResult.stderr) { output += `STDERR:\n${sshResult.stderr}\n`; } output += `\nExit code: ${sshResult.code}`; result = { content: [ { type: 'text', text: output } ] }; } catch (error) { console.error('SSH command execution error:', error); throw new Error(`Failed to execute SSH command: ${error.message}`); } break; case 'start_instance': try { await startInstance({ project: args.project, zone: args.zone, instance: args.instance }); result = { content: [ { type: 'text', text: `Instance '${args.instance}' started successfully` } ] }; } catch (error) { throw new Error(`Failed to start instance: ${error.message}`); } break; case 'stop_instance': try { await stopInstance({ project: args.project, zone: args.zone, instance: args.instance }); result = { content: [ { type: 'text', text: `Instance '${args.instance}' stopped successfully` } ] }; } catch (error) { throw new Error(`Failed to stop instance: ${error.message}`); } break; default: throw new Error(`Unknown tool: ${name}`); } sendJsonRpcResponse(res, id, result); } catch (error) { sendJsonRpcResponse(res, id, null, { code: -32603, message: error.message || 'Internal error' }); } break; case 'ping': sendJsonRpcResponse(res, id, {}); break; default: console.log(`Unknown method: ${method}`); sendJsonRpcResponse(res, id, null, { code: -32601, message: `Method not found: ${method}` }); } } catch (error) { console.error('Error processing request:', error); if (id !== undefined) { sendJsonRpcResponse(res, id, null, { code: -32603, message: error.message || 'Internal error' }); } else { res.status(500).json({ error: 'Internal server error' }); } } }); // --- SSE endpoint for MCP (GET / with text/event-stream) --- app.get('/', (req, res) => { if (req.headers.accept && req.headers.accept.includes('text/event-stream')) { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Headers', 'Cache-Control'); // Send initial connection message res.write(': connected\n\n'); // Keep connection alive const keepAlive = setInterval(() => { res.write(': keepalive\n\n'); }, 30000); req.on('close', () => { clearInterval(keepAlive); res.end(); }); } else { res.status(404).json({ error: 'Not found' }); } }); // --- OAuth Well-known endpoints --- app.get('/.well-known/oauth-protected-resource', (req, res) => { res.json({ resource: 'mcp-remote', scopes_supported: ['mcp'], bearer_methods_supported: ['none'] }); }); app.get('/.well-known/oauth-authorization-server', (req, res) => { res.json({ issuer: `http://localhost:${PORT}`, authorization_endpoint: `http://localhost:${PORT}/authorize`, token_endpoint: `http://localhost:${PORT}/token`, registration_endpoint: `http://localhost:${PORT}/register`, scopes_supported: ['mcp'], response_types_supported: ['code'], grant_types_supported: ['authorization_code', 'refresh_token'], token_endpoint_auth_methods_supported: ['none'] }); }); app.get('/.well-known/openid-configuration', (req, res) => { res.json({ issuer: `http://localhost:${PORT}`, authorization_endpoint: `http://localhost:${PORT}/authorize`, token_endpoint: `http://localhost:${PORT}/token`, registration_endpoint: `http://localhost:${PORT}/register`, scopes_supported: ['mcp', 'openid'], response_types_supported: ['code'], grant_types_supported: ['authorization_code', 'refresh_token'], token_endpoint_auth_methods_supported: ['none'] }); }); // --- OAuth registration endpoint --- app.post('/register', (req, res) => { const { redirect_uris, client_name } = req.body; // Generate a client ID and secret const clientId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const clientSecret = `secret_${Math.random().toString(36).substr(2, 32)}`; const client = { client_id: clientId, client_secret: clientSecret, redirect_uris: redirect_uris || [], client_name: client_name || 'MCP Client', token_endpoint_auth_method: 'none', grant_types: ['authorization_code', 'refresh_token'], response_types: ['code'] }; oauthClients.set(clientId, client); console.log('Registered OAuth client:', clientId); res.json({ client_id: clientId, client_secret: clientSecret, client_id_issued_at: Math.floor(Date.now() / 1000), client_secret_expires_at: 0, // Never expires redirect_uris: client.redirect_uris, grant_types: client.grant_types, response_types: client.response_types, token_endpoint_auth_method: client.token_endpoint_auth_method }); }); // --- OAuth authorize endpoint (placeholder) --- app.get('/authorize', (req, res) => { // For MCP, we might not need full OAuth flow // This is a placeholder res.json({ message: 'OAuth authorization endpoint', note: 'MCP may handle authentication differently' }); }); // --- OAuth token endpoint (placeholder) --- app.post('/token', (req, res) => { // For MCP, we might not need full OAuth flow // This is a placeholder res.json({ access_token: 'placeholder_token', token_type: 'Bearer', expires_in: 3600 }); }); // --- Legacy endpoints (for backward compatibility) --- app.get('/manifest', (req, res) => { res.json({ name: 'mcp-remote', version: '1.0.0', description: 'Local MCP for SSH and GCE', tools }); }); // --- Start server --- app.listen(PORT, () => { console.log(`MCP server running on http://localhost:${PORT}`); console.log('Ready to accept MCP connections from Cursor'); });

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/rpavez/ssh-mcp'

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