Skip to main content
Glama
vm-helpers.ts13.2 kB
import ivm from 'isolated-vm'; /** * Inject individual helper functions using context.global.set * This is the most reliable method for isolated-vm */ export async function injectVMHelpersIndividually(context: ivm.Context): Promise<void> { // Use evalSync to inject all helpers at once // The code will run in the context and create global functions context.evalSync(` // String.prototype.matchAll polyfill if (!String.prototype.matchAll) { String.prototype.matchAll = function(regexp) { if (typeof regexp === 'string') { regexp = new RegExp(regexp, 'g'); } if (!(regexp instanceof RegExp)) { regexp = new RegExp(regexp, 'g'); } if (!regexp.global) { throw new TypeError('matchAll requires a global RegExp'); } const matches = []; const str = this; const regex = new RegExp(regexp.source, regexp.flags); let match; while ((match = regex.exec(str)) !== null) { matches.push(match); } return matches[Symbol.iterator] ? matches[Symbol.iterator]() : (function* () { for (let i = 0; i < matches.length; i++) { yield matches[i]; } })(); }; } // String.prototype.replaceAll polyfill if (!String.prototype.replaceAll) { String.prototype.replaceAll = function(search, replace) { if (search instanceof RegExp) { if (!search.global) { throw new TypeError('replaceAll requires a global RegExp'); } return this.replace(search, replace); } if (typeof replace === 'function') { const parts = this.split(search); let result = parts[0]; for (let i = 1; i < parts.length; i++) { result += replace(search, result.length, this) + parts[i]; } return result; } return this.split(search).join(replace); }; } // Array.prototype.flat polyfill if (!Array.prototype.flat) { Array.prototype.flat = function(depth) { depth = depth === undefined ? 1 : Math.floor(depth); if (depth < 1) return Array.prototype.slice.call(this); return (function flatten(arr, d) { return arr.reduce(function(acc, val) { return acc.concat(d > 1 && Array.isArray(val) ? flatten(val, d - 1) : val); }, []); })(this, depth); }; } // Array.prototype.flatMap polyfill if (!Array.prototype.flatMap) { Array.prototype.flatMap = function(callback, thisArg) { return this.map(callback, thisArg).flat(1); }; } // Object.fromEntries polyfill if (!Object.fromEntries) { Object.fromEntries = function(entries) { const obj = {}; for (const [key, value] of entries) { obj[key] = value; } return obj; }; } // String.prototype.trimStart/trimEnd polyfills if (!String.prototype.trimStart) { String.prototype.trimStart = function() { return this.replace(/^\\s+/, ''); }; String.prototype.trimLeft = String.prototype.trimStart; } if (!String.prototype.trimEnd) { String.prototype.trimEnd = function() { return this.replace(/\\s+$/, ''); }; String.prototype.trimRight = String.prototype.trimEnd; } // String.prototype.padStart/padEnd polyfills if (!String.prototype.padStart) { String.prototype.padStart = function(targetLength, padString) { targetLength = targetLength >> 0; padString = String(typeof padString !== 'undefined' ? padString : ' '); if (this.length >= targetLength || padString.length === 0) { return String(this); } targetLength = targetLength - this.length; if (targetLength > padString.length) { const repeatCount = Math.ceil(targetLength / padString.length); padString = padString.repeat(repeatCount); } return padString.slice(0, targetLength) + String(this); }; } if (!String.prototype.padEnd) { String.prototype.padEnd = function(targetLength, padString) { targetLength = targetLength >> 0; padString = String(typeof padString !== 'undefined' ? padString : ' '); if (this.length >= targetLength || padString.length === 0) { return String(this); } targetLength = targetLength - this.length; if (targetLength > padString.length) { const repeatCount = Math.ceil(targetLength / padString.length); padString = padString.repeat(repeatCount); } return String(this) + padString.slice(0, targetLength); }; } // Array.prototype.at polyfill if (!Array.prototype.at) { Array.prototype.at = function(index) { index = Math.trunc(index) || 0; const len = this.length; const relativeIndex = index >= 0 ? index : len + index; if (relativeIndex < 0 || relativeIndex >= len) { return undefined; } return this[relativeIndex]; }; String.prototype.at = Array.prototype.at; } // Array.prototype.findLast/findLastIndex polyfills if (!Array.prototype.findLast) { Array.prototype.findLast = function(callback, thisArg) { for (let i = this.length - 1; i >= 0; i--) { if (callback.call(thisArg, this[i], i, this)) { return this[i]; } } return undefined; }; } if (!Array.prototype.findLastIndex) { Array.prototype.findLastIndex = function(callback, thisArg) { for (let i = this.length - 1; i >= 0; i--) { if (callback.call(thisArg, this[i], i, this)) { return i; } } return -1; }; } // Object.hasOwn polyfill if (!Object.hasOwn) { Object.hasOwn = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }; } // Base64 encoding btoa = function(str) { if (!str) return ''; const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; let binary = ''; // Convert string to binary for (let i = 0; i < str.length; i++) { const code = str.charCodeAt(i); if (code > 255) { throw new Error('btoa failed: The string to be encoded contains characters outside of the Latin1 range.'); } binary += code.toString(2).padStart(8, '0'); } // Pad binary to make it divisible by 6 while (binary.length % 6 !== 0) { binary += '0'; } let result = ''; // Convert 6-bit chunks to base64 characters for (let i = 0; i < binary.length; i += 6) { const chunk = binary.substr(i, 6); const index = parseInt(chunk, 2); result += chars[index]; } // Add padding while (result.length % 4 !== 0) { result += '='; } return result; }; // Base64 decoding atob = function(str) { if (!str) return ''; str = str.replace(/-/g, '+').replace(/_/g, '/'); const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; str = str.replace(/=+$/, ''); let binary = ''; for (let i = 0; i < str.length; i++) { const index = chars.indexOf(str[i]); if (index === -1) continue; binary += index.toString(2).padStart(6, '0'); } const bytes = []; for (let i = 0; i < binary.length; i += 8) { if (i + 8 <= binary.length) { bytes.push(parseInt(binary.substr(i, 8), 2)); } } let result = ''; for (let i = 0; i < bytes.length; i++) { result += String.fromCharCode(bytes[i]); } return result; }; // escape function - encode each byte of UTF-8 escape = function(str) { let result = ''; for (let i = 0; i < str.length; i++) { const char = str[i]; const code = char.charCodeAt(0); if (code < 128) { // ASCII characters - don't encode safe characters including ! if (/[A-Za-z0-9_.~!*'()-]/.test(char)) { result += char; } else { result += '%' + code.toString(16).toUpperCase().padStart(2, '0'); } } else { // For UTF-8 multi-byte sequences, we need to encode each byte // This is a simplified approach - just encode the raw char code result += '%' + ((code >> 8) & 0xFF).toString(16).toUpperCase().padStart(2, '0'); result += '%' + (code & 0xFF).toString(16).toUpperCase().padStart(2, '0'); } } return result; }; // decodeURIComponent function decodeURIComponent = function(str) { return str.replace(/%([0-9A-F]{2})/gi, function(match, p1) { return String.fromCharCode(parseInt(p1, 16)); }); }; // Buffer object Buffer = { from: function(str, encoding) { if (encoding === 'base64') { return { toString: function(enc) { if (enc === 'utf-8' || enc === 'utf8') { const decoded = atob(str); // Proper UTF-8 decoding const bytes = []; for (let i = 0; i < decoded.length; i++) { bytes.push(decoded.charCodeAt(i)); } let result = ''; let i = 0; while (i < bytes.length) { const byte1 = bytes[i]; if (byte1 < 0x80) { // 1-byte sequence (ASCII) result += String.fromCharCode(byte1); i++; } else if ((byte1 & 0xE0) === 0xC0) { // 2-byte sequence const byte2 = bytes[i + 1]; const codePoint = ((byte1 & 0x1F) << 6) | (byte2 & 0x3F); result += String.fromCharCode(codePoint); i += 2; } else if ((byte1 & 0xF0) === 0xE0) { // 3-byte sequence const byte2 = bytes[i + 1]; const byte3 = bytes[i + 2]; const codePoint = ((byte1 & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F); result += String.fromCharCode(codePoint); i += 3; } else if ((byte1 & 0xF8) === 0xF0) { // 4-byte sequence (surrogate pairs for JS) const byte2 = bytes[i + 1]; const byte3 = bytes[i + 2]; const byte4 = bytes[i + 3]; const codePoint = ((byte1 & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F); // Convert to surrogate pair const temp = codePoint - 0x10000; result += String.fromCharCode((temp >> 10) + 0xD800, (temp & 0x3FF) + 0xDC00); i += 4; } else { // Invalid sequence, skip i++; } } return result; } return str; } }; } // Default: treat as string to encode return { toString: function(enc) { if (enc === 'base64') { return btoa(str); } return str; } }; } }; `); // Inject Node's native URL constructor for full spec compliance await context.global.set('_nativeURLParser', new ivm.Reference(function(urlString: string, base?: string) { try { const parsed = new URL(urlString, base); return new ivm.ExternalCopy({ href: parsed.href, protocol: parsed.protocol, host: parsed.host, hostname: parsed.hostname, port: parsed.port, pathname: parsed.pathname, search: parsed.search, hash: parsed.hash, origin: parsed.origin, searchParams: Object.fromEntries(parsed.searchParams.entries()) }).copyInto(); } catch (error: any) { throw new Error(error.message); } })); // Create URL constructor wrapper in VM context context.evalSync(` URL = function(url, base) { const parsed = _nativeURLParser.applySync(undefined, [url, base]); Object.assign(this, parsed); this.toString = function() { return this.href; }; this.toJSON = function() { return this.href; }; }; `); // Inject crypto.randomUUID await context.global.set('_nativeRandomUUID', new ivm.Reference(function() { return crypto.randomUUID(); })); // Wrap it in a crypto object context.evalSync(` crypto = { randomUUID: function() { return _nativeRandomUUID.applySync(undefined, []); } }; `); }

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/superglue-ai/superglue'

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