@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
JavaScript
// 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);
});