Skip to main content
Glama
index.js4.15 kB
#!/usr/bin/env node const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); const { v4: uuidv4 } = require('uuid'); const { NodeSSH } = require('node-ssh'); const app = express(); const PORT = process.env.PORT || 8787; app.use(cors()); app.use(bodyParser.json()); // --- Simple in-memory SSE clients list --- const sseClients = new Set(); app.get('/sse', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache, no-transform', Connection: 'keep-alive', 'X-Accel-Buffering': 'no', 'Access-Control-Allow-Origin': '*' }); const clientId = uuidv4(); const client = { id: clientId, res }; sseClients.add(client); // Initial hello event res.write(`event: message\n`); res.write(`data: ${JSON.stringify({ type: 'ready', id: clientId })}\n\n`); req.on('close', () => { sseClients.delete(client); }); }); function broadcastEvent(event) { const payload = `event: message\ndata: ${JSON.stringify(event)}\n\n`; for (const client of sseClients) { try { client.res.write(payload); } catch { // ignore broken pipe } } } // Helper to wrap content according to MCP-style response shape function wrapContent(obj) { return { content: [ { type: 'text', text: JSON.stringify(obj) } ] }; } // --- Tools implementation --- async function handleSearch(body) { // Dummy implementation: always return single ssh tool description return wrapContent({ tools: [ { name: 'ssh', description: 'Execute a command over SSH', type: 'ssh.exec' } ] }); } async function handleFetch(body) { // Dummy implementation mirroring search return handleSearch(body); } async function handleSshExec(args) { const { host, user, username, password, privateKey, command } = args || {}; if (!host || !(user || username) || !command) { return wrapContent({ error: 'host, user/username, and command are required' }); } if (!password && !privateKey) { return wrapContent({ error: 'either password or privateKey must be provided' }); } const ssh = new NodeSSH(); const resultPayload = { host, user: user || username, command, stdout: '', stderr: '', code: null, error: null }; try { await ssh.connect({ host, username: user || username, password, privateKey }); const result = await ssh.execCommand(command, { cwd: undefined }); resultPayload.stdout = result.stdout || ''; resultPayload.stderr = result.stderr || ''; resultPayload.code = typeof result.code === 'number' ? result.code : 0; await ssh.dispose(); } catch (err) { resultPayload.error = err && err.message ? err.message : String(err); } // Also push to SSE subscribers as an event broadcastEvent({ type: 'ssh.exec.result', data: resultPayload }); return wrapContent(resultPayload); } app.post('/query', async (req, res) => { const { tool, action, args } = req.body || {}; try { if (tool === 'search' || action === 'search') { const out = await handleSearch(req.body); return res.json(out); } if (tool === 'fetch' || action === 'fetch') { const out = await handleFetch(req.body); return res.json(out); } if (tool === 'ssh.exec' || action === 'ssh.exec') { const out = await handleSshExec(args || req.body.args || req.body); return res.json(out); } return res.status(400).json( wrapContent({ error: 'Unknown tool/action. Use search, fetch, or ssh.exec.' }) ); } catch (err) { return res.status(500).json( wrapContent({ error: err && err.message ? err.message : String(err) }) ); } }); app.get('/', (req, res) => { res.json({ status: 'ok', message: 'ssh-mcp-remote server', sse: '/sse', query: '/query' }); }); app.listen(PORT, () => { console.log('ssh-mcp-remote listening on port', PORT); console.log('SSE endpoint: GET /sse'); console.log('Query endpoint: POST /query'); });

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

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