Skip to main content
Glama

SSH MCP Server

by mfangtao
utils.js9.77 kB
'use strict'; const { SFTP } = require('./protocol/SFTP.js'); const MAX_CHANNEL = 2 ** 32 - 1; function onChannelOpenFailure(self, recipient, info, cb) { self._chanMgr.remove(recipient); if (typeof cb !== 'function') return; let err; if (info instanceof Error) { err = info; } else if (typeof info === 'object' && info !== null) { err = new Error(`(SSH) Channel open failure: ${info.description}`); err.reason = info.reason; } else { err = new Error( '(SSH) Channel open failure: server closed channel unexpectedly' ); err.reason = ''; } cb(err); } function onCHANNEL_CLOSE(self, recipient, channel, err, dead) { if (typeof channel === 'function') { // We got CHANNEL_CLOSE instead of CHANNEL_OPEN_FAILURE when // requesting to open a channel onChannelOpenFailure(self, recipient, err, channel); return; } if (typeof channel !== 'object' || channel === null) return; if (channel.incoming && channel.incoming.state === 'closed') return; self._chanMgr.remove(recipient); if (channel.server && channel.constructor.name === 'Session') return; channel.incoming.state = 'closed'; if (channel.readable) channel.push(null); if (channel.server) { if (channel.stderr.writable) channel.stderr.end(); } else if (channel.stderr.readable) { channel.stderr.push(null); } if (channel.constructor !== SFTP && (channel.outgoing.state === 'open' || channel.outgoing.state === 'eof') && !dead) { channel.close(); } if (channel.outgoing.state === 'closing') channel.outgoing.state = 'closed'; const readState = channel._readableState; const writeState = channel._writableState; if (writeState && !writeState.ending && !writeState.finished && !dead) channel.end(); // Take care of any outstanding channel requests const chanCallbacks = channel._callbacks; channel._callbacks = []; for (let i = 0; i < chanCallbacks.length; ++i) chanCallbacks[i](true); if (channel.server) { if (!channel.readable || channel.destroyed || (readState && readState.endEmitted)) { channel.emit('close'); } else { channel.once('end', () => channel.emit('close')); } } else { let doClose; switch (channel.type) { case 'direct-streamlocal@openssh.com': case 'direct-tcpip': doClose = () => channel.emit('close'); break; default: { // Align more with node child processes, where the close event gets // the same arguments as the exit event const exit = channel._exit; doClose = () => { if (exit.code === null) channel.emit('close', exit.code, exit.signal, exit.dump, exit.desc); else channel.emit('close', exit.code); }; } } if (!channel.readable || channel.destroyed || (readState && readState.endEmitted)) { doClose(); } else { channel.once('end', doClose); } const errReadState = channel.stderr._readableState; if (!channel.stderr.readable || channel.stderr.destroyed || (errReadState && errReadState.endEmitted)) { channel.stderr.emit('close'); } else { channel.stderr.once('end', () => channel.stderr.emit('close')); } } } class ChannelManager { constructor(client) { this._client = client; this._channels = {}; this._cur = -1; this._count = 0; } add(val) { // Attempt to reserve an id let id; // Optimized paths if (this._cur < MAX_CHANNEL) { id = ++this._cur; } else if (this._count === 0) { // Revert and reset back to fast path once we no longer have any channels // open this._cur = 0; id = 0; } else { // Slower lookup path // This path is triggered we have opened at least MAX_CHANNEL channels // while having at least one channel open at any given time, so we have // to search for a free id. const channels = this._channels; for (let i = 0; i < MAX_CHANNEL; ++i) { if (channels[i] === undefined) { id = i; break; } } } if (id === undefined) return -1; this._channels[id] = (val || true); ++this._count; return id; } update(id, val) { if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id)) throw new Error(`Invalid channel id: ${id}`); if (val && this._channels[id]) this._channels[id] = val; } get(id) { if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id)) throw new Error(`Invalid channel id: ${id}`); return this._channels[id]; } remove(id) { if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id)) throw new Error(`Invalid channel id: ${id}`); if (this._channels[id]) { delete this._channels[id]; if (this._count) --this._count; } } cleanup(err) { const channels = this._channels; this._channels = {}; this._cur = -1; this._count = 0; const chanIDs = Object.keys(channels); const client = this._client; for (let i = 0; i < chanIDs.length; ++i) { const id = +chanIDs[i]; const channel = channels[id]; onCHANNEL_CLOSE(client, id, channel._channel || channel, err, true); } } } const isRegExp = (() => { const toString = Object.prototype.toString; return (val) => toString.call(val) === '[object RegExp]'; })(); function generateAlgorithmList(algoList, defaultList, supportedList) { if (Array.isArray(algoList) && algoList.length > 0) { // Exact list for (let i = 0; i < algoList.length; ++i) { if (supportedList.indexOf(algoList[i]) === -1) throw new Error(`Unsupported algorithm: ${algoList[i]}`); } return algoList; } if (typeof algoList === 'object' && algoList !== null) { // Operations based on the default list const keys = Object.keys(algoList); let list = defaultList; for (let i = 0; i < keys.length; ++i) { const key = keys[i]; let val = algoList[key]; switch (key) { case 'append': if (!Array.isArray(val)) val = [val]; if (Array.isArray(val)) { for (let j = 0; j < val.length; ++j) { const append = val[j]; if (typeof append === 'string') { if (!append || list.indexOf(append) !== -1) continue; if (supportedList.indexOf(append) === -1) throw new Error(`Unsupported algorithm: ${append}`); if (list === defaultList) list = list.slice(); list.push(append); } else if (isRegExp(append)) { for (let k = 0; k < supportedList.length; ++k) { const algo = supportedList[k]; if (append.test(algo)) { if (list.indexOf(algo) !== -1) continue; if (list === defaultList) list = list.slice(); list.push(algo); } } } } } break; case 'prepend': if (!Array.isArray(val)) val = [val]; if (Array.isArray(val)) { for (let j = val.length; j >= 0; --j) { const prepend = val[j]; if (typeof prepend === 'string') { if (!prepend || list.indexOf(prepend) !== -1) continue; if (supportedList.indexOf(prepend) === -1) throw new Error(`Unsupported algorithm: ${prepend}`); if (list === defaultList) list = list.slice(); list.unshift(prepend); } else if (isRegExp(prepend)) { for (let k = supportedList.length; k >= 0; --k) { const algo = supportedList[k]; if (prepend.test(algo)) { if (list.indexOf(algo) !== -1) continue; if (list === defaultList) list = list.slice(); list.unshift(algo); } } } } } break; case 'remove': if (!Array.isArray(val)) val = [val]; if (Array.isArray(val)) { for (let j = 0; j < val.length; ++j) { const search = val[j]; if (typeof search === 'string') { if (!search) continue; const idx = list.indexOf(search); if (idx === -1) continue; if (list === defaultList) list = list.slice(); list.splice(idx, 1); } else if (isRegExp(search)) { for (let k = 0; k < list.length; ++k) { if (search.test(list[k])) { if (list === defaultList) list = list.slice(); list.splice(k, 1); --k; } } } } } break; } } return list; } return defaultList; } module.exports = { ChannelManager, generateAlgorithmList, onChannelOpenFailure, onCHANNEL_CLOSE, isWritable: (stream) => { // XXX: hack to workaround regression in node // See: https://github.com/nodejs/node/issues/36029 return (stream && stream.writable && stream._readableState && stream._readableState.ended === false); }, };

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

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