Skip to main content
Glama

Superglue MCP

Official
by superglue-ai
utils.ts10.1 kB
import { findMatchingIntegration, integrations } from '@superglue/shared'; import { clsx, type ClassValue } from "clsx"; import prettierPluginBabel from 'prettier/plugins/babel'; import prettierPluginEstree from 'prettier/plugins/estree'; import prettier from 'prettier/standalone'; import type { SimpleIcon } from 'simple-icons'; import * as simpleIcons from 'simple-icons'; import { twMerge } from "tailwind-merge"; import { StepExecutionResult } from './client-utils'; export const inputErrorStyles = "border-red-500 focus:border-red-500 focus:ring-red-500"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } export function composeUrl(host: string, path: string | undefined) { if (!host && !path) return ''; // Handle empty/undefined inputs if (!host) host = ''; if (!path) path = ''; // Trim slashes in one pass const cleanHost = host.endsWith('/') ? host.slice(0, -1) : host; const cleanPath = path.startsWith('/') ? path.slice(1) : path; return `${cleanHost}/${cleanPath}`; } export function getIntegrationIcon(integration: { id: string; urlHost?: string }): string | null { // First try exact ID match with known integrations if (integrations[integration.id]) { return integrations[integration.id].icon; } // Second try: strip any numeric suffix (e.g., "firebase-1" -> "firebase") const baseId = integration.id.replace(/-\d+$/, ''); if (baseId !== integration.id && integrations[baseId]) { return integrations[baseId].icon; } // Finally try using the proper regex-based matching if (integration.urlHost) { const match = findMatchingIntegration(integration.urlHost); if (match) { return match.integration.icon; } } return null; } export const isEmptyData = (value: any): boolean => { if (value === null || value === undefined) return true; if (typeof value === 'string') { const trimmed = value.trim(); if (trimmed.length === 0) return true; const first = trimmed[0]; if (first === '{' || first === '[') { try { const parsed = JSON.parse(trimmed); return isEmptyData(parsed); } catch { return false; } } return false; } if (Array.isArray(value)) return value.length === 0; if (typeof value === 'object') return Object.keys(value).length === 0; return false; }; export const computeStepOutput = ( result: StepExecutionResult ): { output: any; failed: boolean; emptyHint?: boolean; error?: string } => { const failed = !result?.success; if (failed) { return { output: result?.error || 'Step execution failed', failed: true, error: result?.error }; } return { output: result?.data, failed: false, emptyHint: isEmptyData(result?.data), error: result?.error }; }; // Truncation constants for display export const MAX_DISPLAY_SIZE = 1024 * 1024; // 1MB limit for JSON display export const MAX_DISPLAY_LINES = 3000; // Max lines to show in any JSON view export const MAX_STRING_PREVIEW_LENGTH = 3000; // Max chars for individual string values export const MAX_ARRAY_PREVIEW_ITEMS = 10; // Max array items to show before truncating export const MAX_TRUNCATION_DEPTH = 10; // Max depth for nested object traversal export const MAX_OBJECT_PREVIEW_KEYS = 100; // Max object keys to show before truncating export const truncateValue = (value: any, depth: number = 0): any => { if (depth > MAX_TRUNCATION_DEPTH) { if (Array.isArray(value)) return '[...]'; if (typeof value === 'object' && value !== null) return '{...}'; return '...'; } if (typeof value === 'string') { if (value.length > MAX_STRING_PREVIEW_LENGTH) { return value.substring(0, MAX_STRING_PREVIEW_LENGTH) + `... [${value.length.toLocaleString()} chars total]`; } return value; } if (Array.isArray(value)) { if (value.length > MAX_ARRAY_PREVIEW_ITEMS) { return [...value.slice(0, MAX_ARRAY_PREVIEW_ITEMS).map(v => truncateValue(v, depth + 1)), `... ${value.length - MAX_ARRAY_PREVIEW_ITEMS} more items`]; } return value.map(v => truncateValue(v, depth + 1)); } if (typeof value === 'object' && value !== null) { const result: any = {}; const keys = Object.keys(value); const keysToShow = keys.slice(0, MAX_OBJECT_PREVIEW_KEYS); for (const key of keysToShow) { result[key] = truncateValue(value[key], depth + 1); } if (keys.length > MAX_OBJECT_PREVIEW_KEYS) { result['...'] = `${(keys.length - MAX_OBJECT_PREVIEW_KEYS).toLocaleString()} more keys`; } return result; } return value; }; export const truncateForDisplay = (data: any): { value: string, truncated: boolean } => { if (data === null || data === undefined) { return { value: '{}', truncated: false }; } if (typeof data === 'string') { if (data.length > MAX_STRING_PREVIEW_LENGTH) { const truncatedString = data.substring(0, MAX_STRING_PREVIEW_LENGTH); const lastNewline = truncatedString.lastIndexOf('\n'); const cleanTruncated = truncatedString.substring(0, lastNewline > 0 ? lastNewline : MAX_STRING_PREVIEW_LENGTH); const note = `\n\n... [String truncated - showing ${MAX_STRING_PREVIEW_LENGTH.toLocaleString()} of ${data.length.toLocaleString()} characters]`; const combined = `${cleanTruncated}${note}`; return { value: JSON.stringify(combined), truncated: true }; } return { value: JSON.stringify(data), truncated: false }; } try { const truncatedData = truncateValue(data); let jsonString = JSON.stringify(truncatedData, null, 2); if (jsonString.length > MAX_DISPLAY_SIZE) { jsonString = jsonString.substring(0, MAX_DISPLAY_SIZE); const lastNewline = jsonString.lastIndexOf('\n'); if (lastNewline > 0) jsonString = jsonString.substring(0, lastNewline); return { value: jsonString + '\n\n... [Data truncated - exceeds size limit]', truncated: true }; } const lines = jsonString.split('\n'); if (lines.length > MAX_DISPLAY_LINES) { return { value: lines.slice(0, MAX_DISPLAY_LINES).join('\n') + '\n\n... [Truncated - too many lines]', truncated: true }; } const originalJson = JSON.stringify(data, null, 2); const wasTruncated = originalJson !== jsonString; return { value: jsonString, truncated: wasTruncated }; } catch { const stringValue = String(data); if (stringValue.length > MAX_STRING_PREVIEW_LENGTH) { const preview = stringValue.substring(0, MAX_STRING_PREVIEW_LENGTH) + '... [Truncated]'; return { value: JSON.stringify(preview), truncated: true }; } return { value: JSON.stringify(stringValue), truncated: false }; } }; export const truncateLines = (text: string, maxLines: number): string => { if (!text) return text; const lines = text.split('\n'); if (lines.length <= maxLines) return text; return lines.slice(0, maxLines).join('\n') + `\n... truncated ${lines.length - maxLines} more lines ...`; }; export function getSimpleIcon(name: string): SimpleIcon | null { if (!name || name === "default") return null; const formatted = name.charAt(0).toUpperCase() + name.slice(1).toLowerCase(); const iconKey = `si${formatted}`; try { // @ts-ignore - The type definitions don't properly handle string indexing let icon = simpleIcons[iconKey]; return icon || null; } catch (e) { return null; } } export const buildEvolvingPayload = (initialPayload: any, steps: any[], stepResults: Record<string, any>, upToIndex: number) => { let evolvingPayload = { ...initialPayload }; for (let i = 0; i <= upToIndex && i < steps.length; i++) { const step = steps[i]; const result = stepResults[step.id]; if (result !== undefined && result !== null) { const dataToMerge = (typeof result === 'object' && 'data' in result && 'success' in result) ? result.data : result; evolvingPayload = { ...evolvingPayload, [`${step.id}`]: dataToMerge }; } } return evolvingPayload; }; const PRETTIER_PLUGINS = [ (prettierPluginBabel as any).default ?? (prettierPluginBabel as any), (prettierPluginEstree as any).default ?? (prettierPluginEstree as any), ]; export async function formatJavaScriptCode(code: string): Promise<string> { if (!code || typeof code !== 'string') return code; try { const formatted = await prettier.format(code, { parser: 'babel', plugins: PRETTIER_PLUGINS, semi: true, singleQuote: true, trailingComma: 'es5', tabWidth: 2, printWidth: 100, arrowParens: 'always' }); return formatted.trimEnd(); } catch (error) { console.debug('Code formatting failed:', error); return code; } } export function getGroupedTimezones(): Record<string, Array<{ value: string, label: string }>> { const timezones = Intl.supportedValuesOf('timeZone').map(tz => ({ value: tz, label: tz.replace(/_/g, ' ') })); return timezones.reduce((acc, timezone) => { const group = timezone.value.split('/')[0]; // Use value instead of label for grouping if (!acc[group]) { acc[group] = []; } acc[group].push(timezone); return acc; }, {} as Record<string, Array<{ value: string, label: string }>>); } export function isValidSourceDataArrowFunction(code: string | undefined | null): boolean { if (!code) return false; const text = String(code); const patterns = [ /^\s*\(?\s*\(\s*sourceData\s*\)\s*=>\s*\{[\s\S]*\}\s*\)?\s*;?\s*$/, // block body /^\s*\(?\s*\(\s*sourceData\s*\)\s*=>\s*\([\s\S]*\)\s*\)?\s*;?\s*$/, // parenthesized expr /^\s*\(?\s*\(\s*sourceData\s*\)\s*=>[\s\S]*\)?\s*;?\s*$/ // tolerant bare expr ]; return patterns.some((re) => re.test(text)); } export function ensureSourceDataArrowFunction(code: string | undefined | null): string { const fallback = `(sourceData) => {\n return sourceData;\n}`; const text = (code ?? '').trim(); if (!text) return fallback; if (isValidSourceDataArrowFunction(text)) return text; return `(sourceData) => {\n${text}\n}`; }

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