UNPKG

@yusukedev/gemini-cli-mcp

Version:

A secure Model Context Protocol server for Gemini CLI with automatic fallback, extended timeout (180s), and model transparency features

350 lines (312 loc) 10.7 kB
#!/usr/bin/env node // Enhanced polyfill for web streams - must be first thing executed (function setupWebStreamsPolyfill() { // Force polyfill even if TransformStream appears to exist but is broken try { const { TransformStream, ReadableStream, WritableStream } = require("stream/web"); // Test if TransformStream actually works try { new TransformStream(); globalThis.TransformStream = TransformStream; globalThis.ReadableStream = ReadableStream; globalThis.WritableStream = WritableStream; console.error('[Wrapper] Using Node.js stream/web API'); } catch (testError) { throw new Error('TransformStream test failed'); } } catch (error) { // Enhanced fallback polyfill console.error('[Wrapper] Using fallback TransformStream polyfill'); const { Transform } = require('stream'); globalThis.TransformStream = class TransformStream { constructor(transformer = {}) { const transform = new Transform({ objectMode: false, transform(chunk, encoding, callback) { if (transformer.transform) { try { const result = transformer.transform(chunk, this); callback(null, result); } catch (err) { callback(err); } } else { callback(null, chunk); } } }); this.readable = transform; this.writable = transform; } }; // Basic ReadableStream/WritableStream polyfills if (!globalThis.ReadableStream) { const { Readable } = require('stream'); globalThis.ReadableStream = class ReadableStream { constructor() { this._readable = new Readable({ read() {} }); } }; } if (!globalThis.WritableStream) { const { Writable } = require('stream'); globalThis.WritableStream = class WritableStream { constructor() { this._writable = new Writable({ write() {} }); } }; } } })(); // Execute the original gemini CLI const { spawn } = require('child_process'); const path = require('path'); // Find the real gemini executable let geminiPath; try { const { execSync } = require('child_process'); const isWindows = process.platform === 'win32'; const command = isWindows ? 'where gemini' : 'which gemini'; const results = execSync(command, { encoding: 'utf8' }) .trim() .split('\n') .map(line => line.trim()); geminiPath = isWindows ? results.find(p => p.endsWith('.cmd')) || results[0] : results[0]; } catch (error) { console.error('Error: gemini CLI not found in PATH'); process.exit(1); } // Forward all arguments to the real gemini CLI const args = process.argv.slice(2); // Create a preload script for TransformStream polyfill const fs = require('fs'); const preloadPath = path.join(path.dirname(__filename), 'polyfill-preload.cjs'); const preloadContent = ` // Comprehensive Web API polyfills for Node.js environment console.error('[Preload] Setting up Web API polyfills...'); // TransformStream polyfill if (typeof globalThis.TransformStream === "undefined") { try { const { TransformStream, ReadableStream, WritableStream } = require("stream/web"); globalThis.TransformStream = TransformStream; globalThis.ReadableStream = ReadableStream; globalThis.WritableStream = WritableStream; console.error('[Preload] Applied Node.js stream/web polyfill'); } catch (error) { const { Transform } = require('stream'); globalThis.TransformStream = class TransformStream { constructor(transformer = {}) { const transform = new Transform({ objectMode: false, transform(chunk, encoding, callback) { callback(null, chunk); } }); this.readable = transform; this.writable = transform; } }; console.error('[Preload] Applied fallback TransformStream polyfill'); } } // Blob polyfill if (typeof globalThis.Blob === "undefined") { try { const { Blob } = require("buffer"); globalThis.Blob = Blob; console.error('[Preload] Applied Node.js Blob polyfill'); } catch (error) { // Basic Blob polyfill globalThis.Blob = class Blob { constructor(parts = [], options = {}) { this.size = 0; this.type = options.type || ''; this._parts = parts; if (Array.isArray(parts)) { this.size = parts.reduce((acc, part) => { if (typeof part === 'string') return acc + Buffer.byteLength(part); if (Buffer.isBuffer(part)) return acc + part.length; return acc; }, 0); } } slice(start = 0, end = this.size, contentType = '') { return new Blob(this._parts.slice(start, end), { type: contentType }); } async text() { return Buffer.concat(this._parts.map(p => Buffer.from(p))).toString(); } async arrayBuffer() { return Buffer.concat(this._parts.map(p => Buffer.from(p))).buffer; } }; console.error('[Preload] Applied fallback Blob polyfill'); } } // File polyfill (extends Blob) if (typeof globalThis.File === "undefined") { globalThis.File = class File extends globalThis.Blob { constructor(fileBits, fileName, options = {}) { super(fileBits, options); this.name = fileName; this.lastModified = options.lastModified || Date.now(); } }; console.error('[Preload] Applied File polyfill'); } // FormData polyfill if (typeof globalThis.FormData === "undefined") { globalThis.FormData = class FormData { constructor() { this._data = new Map(); } append(name, value, filename) { if (!this._data.has(name)) { this._data.set(name, []); } this._data.get(name).push({ value, filename }); } set(name, value, filename) { this._data.set(name, [{ value, filename }]); } get(name) { const values = this._data.get(name); return values ? values[0].value : null; } getAll(name) { const values = this._data.get(name); return values ? values.map(v => v.value) : []; } has(name) { return this._data.has(name); } delete(name) { this._data.delete(name); } entries() { const entries = []; for (const [name, values] of this._data) { for (const { value } of values) { entries.push([name, value]); } } return entries[Symbol.iterator](); } }; console.error('[Preload] Applied FormData polyfill'); } // DOMException polyfill if (typeof globalThis.DOMException === "undefined") { globalThis.DOMException = class DOMException extends Error { constructor(message = "", name = "Error") { super(message); this.name = name; this.code = 0; } static get ABORT_ERR() { return 20; } static get DATA_CLONE_ERR() { return 25; } static get DOMSTRING_SIZE_ERR() { return 2; } static get HIERARCHY_REQUEST_ERR() { return 3; } static get INDEX_SIZE_ERR() { return 1; } static get INUSE_ATTRIBUTE_ERR() { return 10; } static get INVALID_ACCESS_ERR() { return 15; } static get INVALID_CHARACTER_ERR() { return 5; } static get INVALID_MODIFICATION_ERR() { return 13; } static get INVALID_NODE_TYPE_ERR() { return 24; } static get INVALID_STATE_ERR() { return 11; } static get NAMESPACE_ERR() { return 14; } static get NETWORK_ERR() { return 19; } static get NOT_FOUND_ERR() { return 8; } static get NOT_SUPPORTED_ERR() { return 9; } static get NO_DATA_ALLOWED_ERR() { return 6; } static get NO_MODIFICATION_ALLOWED_ERR() { return 7; } static get QUOTA_EXCEEDED_ERR() { return 22; } static get SECURITY_ERR() { return 18; } static get SYNTAX_ERR() { return 12; } static get TIMEOUT_ERR() { return 23; } static get TYPE_MISMATCH_ERR() { return 17; } static get URL_MISMATCH_ERR() { return 21; } static get VALIDATION_ERR() { return 16; } static get WRONG_DOCUMENT_ERR() { return 4; } }; console.error('[Preload] Applied DOMException polyfill'); } // Additional common Web APIs that might be needed if (typeof globalThis.TextEncoder === "undefined") { const { TextEncoder, TextDecoder } = require('util'); globalThis.TextEncoder = TextEncoder; globalThis.TextDecoder = TextDecoder; console.error('[Preload] Applied TextEncoder/TextDecoder polyfill'); } if (typeof globalThis.AbortController === "undefined") { globalThis.AbortController = class AbortController { constructor() { this.signal = { aborted: false, addEventListener() {}, removeEventListener() {} }; } abort() { this.signal.aborted = true; } }; console.error('[Preload] Applied AbortController polyfill'); } if (typeof globalThis.Event === "undefined") { globalThis.Event = class Event { constructor(type, options = {}) { this.type = type; this.bubbles = options.bubbles || false; this.cancelable = options.cancelable || false; this.composed = options.composed || false; this.target = null; this.currentTarget = null; this.defaultPrevented = false; } preventDefault() { this.defaultPrevented = true; } stopPropagation() {} stopImmediatePropagation() {} }; console.error('[Preload] Applied Event polyfill'); } console.error('[Preload] Web API polyfills setup complete'); `; try { fs.writeFileSync(preloadPath, preloadContent); } catch (error) { console.error('Warning: Could not create preload script'); } // Windows .cmd files need to be executed directly, not through node const isWindows = process.platform === 'win32'; const child = isWindows && geminiPath.endsWith('.cmd') ? spawn(geminiPath, args, { stdio: 'inherit', shell: true, env: { ...process.env, NODE_OPTIONS: '--experimental-global-webcrypto --experimental-fetch --no-warnings' } }) : spawn('node', ['-r', preloadPath, geminiPath, ...args], { stdio: 'inherit', env: { ...process.env, NODE_OPTIONS: '--experimental-global-webcrypto --experimental-fetch --no-warnings' } }); // Clean up preload file on exit child.on('exit', () => { try { fs.unlinkSync(preloadPath); } catch (error) { // Ignore cleanup errors } }); child.on('exit', (code) => { process.exit(code || 0); }); child.on('error', (error) => { console.error('Error executing gemini CLI:', error.message); process.exit(1); });