Skip to main content
Glama

Superglue MCP

Official
by superglue-ai
client-utils.ts12 kB
import { Integration, SelfHealingMode, SuperglueClient, Workflow as Tool } from "@superglue/client"; export interface StepExecutionResult { stepId: string; success: boolean; data?: any; error?: string; updatedStep?: any; } export interface FinalTransformExecutionResult { success: boolean; data?: any; error?: string; updatedTransform?: string; updatedResponseSchema?: any; } export interface ToolExecutionState { originalTool: Tool; currentTool: Tool; stepResults: Record<string, StepExecutionResult>; completedSteps: string[]; failedSteps: string[]; isExecuting: boolean; currentStepIndex: number; interrupted?: boolean; } export function createSingleStepTool( tool: Tool, stepIndex: number, previousResults: Record<string, any> = {} ): Tool { if (stepIndex < 0 || stepIndex >= tool.steps.length) { throw new Error(`Invalid step index: ${stepIndex}`); } const step = tool.steps[stepIndex]; const singleStepTool: any = { id: `${tool.id}_step_${stepIndex}`, steps: [step], finalTransform: '(sourceData) => sourceData' }; return singleStepTool; } export async function executeSingleStep( client: SuperglueClient, tool: Tool, stepIndex: number, payload: any, previousResults: Record<string, any> = {}, selfHealing: boolean = true ): Promise<StepExecutionResult> { const step = tool.steps[stepIndex]; try { const singleStepTool = createSingleStepTool(tool, stepIndex, previousResults); const executionPayload = { ...payload, ...Object.keys(previousResults).reduce((acc, stepId) => ({ ...acc, [`${stepId}`]: previousResults[stepId] }), {}) }; const result = await client.executeWorkflow({ workflow: singleStepTool, payload: executionPayload, options: { testMode: false, selfHealing: selfHealing ? SelfHealingMode.ENABLED : SelfHealingMode.DISABLED } }); const stepResult: any = Array.isArray(result.stepResults) ? result.stepResults[0] : result.stepResults?.[step.id]; return { stepId: step.id, success: result.success, data: stepResult?.data || stepResult?.transformedData || stepResult || result.data, error: result.error, // Capture any updated configuration returned by the API (normalization, self-healing, etc.) updatedStep: result.config?.steps?.[0] }; } catch (error: any) { return { stepId: step.id, success: false, error: error.message || 'Step execution failed' }; } } export async function executeToolStepByStep( client: SuperglueClient, tool: Tool, payload: any, onStepComplete?: (stepIndex: number, result: StepExecutionResult) => void, selfHealing: boolean = true, shouldStop?: () => boolean ): Promise<ToolExecutionState> { const state: ToolExecutionState = { originalTool: tool, currentTool: { ...tool }, stepResults: {}, completedSteps: [], failedSteps: [], isExecuting: true, currentStepIndex: 0, interrupted: false }; const previousResults: Record<string, any> = {}; for (let i = 0; i < tool.steps.length; i++) { if (shouldStop && shouldStop()) { state.isExecuting = false; state.interrupted = true; return state; } state.currentStepIndex = i; const step = tool.steps[i]; const result = await executeSingleStep( client, state.currentTool, i, payload, previousResults, selfHealing ); state.stepResults[step.id] = result; if (result.success) { state.completedSteps.push(step.id); previousResults[step.id] = result.data; // Update the tool with any returned step configuration (normalization, self-healing, etc.) if (result.updatedStep) { state.currentTool = { ...state.currentTool, steps: state.currentTool.steps.map((s, idx) => idx === i ? result.updatedStep : s ) }; } } else { state.failedSteps.push(step.id); state.isExecuting = false; // Stop execution on failure if (onStepComplete) { onStepComplete(i, result); } return state; } if (onStepComplete) { onStepComplete(i, result); } if (shouldStop && shouldStop()) { state.isExecuting = false; state.interrupted = true; return state; } } if (tool.finalTransform && state.failedSteps.length === 0) { // Final guard before executing transform if (shouldStop && shouldStop()) { state.isExecuting = false; state.interrupted = true; return state; } const finalResult = await executeFinalTransform( client, tool.id || 'tool', state.currentTool.finalTransform || tool.finalTransform, tool.responseSchema, tool.inputSchema, payload, previousResults, selfHealing ); state.stepResults['__final_transform__'] = { stepId: '__final_transform__', success: finalResult.success, data: finalResult.data, error: finalResult.error }; if (finalResult.success) { state.completedSteps.push('__final_transform__'); if (finalResult.updatedTransform && selfHealing) { state.currentTool = { ...state.currentTool, finalTransform: finalResult.updatedTransform }; } } else { state.failedSteps.push('__final_transform__'); } } state.isExecuting = false; return state; } export async function executeFinalTransform( client: SuperglueClient, toolId: string, finalTransform: string, responseSchema: any, inputSchema: any, payload: any, previousResults: Record<string, any>, selfHealing: boolean = false ): Promise<FinalTransformExecutionResult> { try { const finalPayload = { ...payload, ...previousResults }; const result = await client.executeWorkflow({ workflow: { id: `${toolId}_final_transform`, steps: [], finalTransform, // Only include responseSchema if it's actually defined (not null/undefined) ...(responseSchema ? { responseSchema } : {}), inputSchema: inputSchema || { type: 'object' } }, payload: finalPayload, options: { testMode: !!responseSchema, // Always use test mode if responseSchema is provided selfHealing: selfHealing ? SelfHealingMode.ENABLED : SelfHealingMode.DISABLED } }); return { success: result.success, data: result.data, error: result.error, updatedTransform: result.config?.finalTransform, updatedResponseSchema: result.config?.responseSchema }; } catch (error: any) { return { success: false, error: error.message || 'Final transform execution failed' }; } } export function canExecuteStep( stepIndex: number, completedSteps: string[], tool: Tool, stepResults?: Record<string, any> ): boolean { if (stepIndex === 0) { return true; } // Check that all previous steps are completed and have results for (let i = 0; i < stepIndex; i++) { const stepId = tool.steps[i].id; if (!completedSteps.includes(stepId)) { return false; } // If stepResults is provided, also check that the step has a result if (stepResults && !stepResults[stepId]) { return false; } } return true; } export const isJsonEmpty = (inputJson: string): boolean => { try { if (!inputJson) return true const parsedJson = JSON.parse(inputJson) return Object.keys(parsedJson).length === 0 } catch (error) { // If invalid JSON, we consider it empty return true } } export const findArraysOfObjects = (obj: any): Record<string, any[]> => { const arrays: Record<string, any[]> = {}; const traverse = (value: any, path: string = '') => { if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object') { arrays[path] = value; } if (typeof value === 'object' && value !== null) { Object.entries(value).forEach(([key, val]) => { traverse(val, `${path ? `${path}.` : ''}${key}`); }); } }; traverse(obj); if (Object.keys(arrays).length === 0) { if (Object.keys(obj).length === 1) { const [key, value] = Object.entries(obj)[0]; return { [key]: [value] }; } return { response: [obj] }; } return arrays; }; export const parseCredentialsHelper = (simpleCreds: string): Record<string, string> => { try { const creds = simpleCreds?.trim() || "" if (!creds) { return {} } if (creds.startsWith('{')) { return JSON.parse(creds) } if (creds.startsWith('Bearer ')) { return { token: creds.replace('Bearer ', '') } } if (creds.startsWith('Basic ')) { return { token: creds.replace('Basic ', '') } } return { token: creds } } catch (error) { return {} } } export const splitUrl = (url: string) => { if (!url) { return { urlHost: '', urlPath: '' } } // Find the position after the protocol (://) const protocolEnd = url.indexOf('://'); // Find the first slash after the protocol const firstSlashAfterProtocol = url.indexOf('/', protocolEnd + 3); if (firstSlashAfterProtocol === -1) { // No path, entire URL is the host return { urlHost: url, urlPath: '' } } // Split at the first slash after protocol return { urlHost: url.substring(0, firstSlashAfterProtocol), urlPath: url.substring(firstSlashAfterProtocol) } } export function needsUIToTriggerDocFetch(newIntegration: Integration, oldIntegration: Integration | null): boolean { // If documentation was manually provided, no fetch needed. if (newIntegration.documentation && newIntegration.documentation.trim()) { return false; } // If it's a new integration with a doc URL, fetch is needed. if (!oldIntegration) { return true; } // If any of the relevant URLs have changed, fetch is needed. if (newIntegration.urlHost !== oldIntegration.urlHost || newIntegration.urlPath !== oldIntegration.urlPath || newIntegration.documentationUrl !== oldIntegration.documentationUrl) { return true; } return false; } export const deepMergePreferRight = (left: any, right: any): any => { if (Array.isArray(left) && Array.isArray(right)) return right; if (typeof left !== 'object' || left === null) return right ?? left; if (typeof right !== 'object' || right === null) return right ?? left; const result: Record<string, any> = { ...left }; for (const key of new Set([...Object.keys(left), ...Object.keys(right)])) { result[key] = deepMergePreferRight(left[key], right[key]); } return result; }; /** * Generate a UUID v4 with fallback for browsers that don't support crypto.randomUUID * Works in both secure and non-secure contexts */ export function generateUUID(): string { // Use native crypto.randomUUID if available (secure contexts only) if (typeof crypto !== 'undefined' && crypto.randomUUID) { try { return crypto.randomUUID(); } catch (e) { // Fall through to manual implementation } } // Fallback implementation using crypto.getRandomValues (available in more contexts) if (typeof crypto !== 'undefined' && crypto.getRandomValues) { const array = new Uint8Array(16); crypto.getRandomValues(array); // Set the version to 4 (random) array[6] = (array[6] & 0x0f) | 0x40; // Set the variant to 1 (RFC4122) array[8] = (array[8] & 0x3f) | 0x80; return [...array].map((b, i) => { const hex = b.toString(16).padStart(2, '0'); return (i === 4 || i === 6 || i === 8 || i === 10) ? `-${hex}` : hex; }).join(''); } // Final fallback for environments without crypto (should be rare) return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }

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