Skip to main content
Glama

Superglue MCP

Official
by superglue-ai
TransformPlayground.tsx27.1 kB
"use client"; import { useConfig } from "@/src/app/config-context"; import { ToolResultsView as WorkflowResultsView } from "@/src/components/tools/ToolResultsView"; import JsonSchemaEditor from "@/src/components/utils/JsonSchemaEditor"; import { CacheMode, SuperglueClient, TransformResult, WorkflowResult } from "@superglue/client"; import { X } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from "react"; import { useToast } from "../../hooks/use-toast"; import { Button } from "../ui/button"; import { Card, CardContent } from "../ui/card"; import { Input } from "../ui/input"; import { Label } from "../ui/label"; import { Textarea } from "../ui/textarea"; type InputType = 'raw' | 'url' | 'file'; interface SavedInputData { inputType: InputType; rawData: string; inputUrl: string; fileName?: string; } // Utility to detect binary file formats that need extraction const isBinaryFile = (filename: string): boolean => { const binaryExtensions = [ '.xlsx', '.xls', '.xlsm', '.xlsb', // Excel formats '.docx', '.doc', '.docm', // Word formats '.pptx', '.ppt', '.pptm', // PowerPoint formats '.zip', '.rar', '.7z', // Archive formats '.pdf', // PDF format '.odt', '.ods', '.odp', // OpenDocument formats ]; const ext = filename.toLowerCase().split('.').pop(); return binaryExtensions.includes(`.${ext}`); }; // Cookie utility functions const saveInputDataToCookie = (transformId: string, inputData: SavedInputData) => { if (!transformId) return; const cookieName = `transform_input_${transformId}`; const cookieValue = JSON.stringify(inputData); // Set cookie with 30 days expiration const expirationDate = new Date(); expirationDate.setDate(expirationDate.getDate() + 30); document.cookie = `${cookieName}=${encodeURIComponent(cookieValue)}; expires=${expirationDate.toUTCString()}; path=/`; }; const loadInputDataFromCookie = (transformId: string): SavedInputData | null => { if (!transformId) return null; const cookieName = `transform_input_${transformId}`; const cookies = document.cookie.split(';'); for (const cookie of cookies) { const [name, value] = cookie.trim().split('='); if (name === cookieName) { try { return JSON.parse(decodeURIComponent(value)); } catch (error) { console.error('Error parsing cookie data:', error); return null; } } } return null; }; export default function TransformPlayground({ id }: { id?: string }) { const router = useRouter(); const { toast } = useToast(); const config = useConfig(); // Transform identification const [transformId, setTransformId] = useState(id || ''); // Input configuration const [inputType, setInputType] = useState<InputType>('raw'); const [rawData, setRawData] = useState(''); const [inputUrl, setInputUrl] = useState(''); const [inputFile, setInputFile] = useState<File | null>(null); // Transform configuration const [transform, setTransform] = useState(`(sourceData) => {\n return sourceData;\n}`); const [instruction, setInstruction] = useState(''); const [responseSchema, setResponseSchema] = useState<string | null>(``); // Execution state const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [result, setResult] = useState<WorkflowResult | null>(null); const [activeResultTab, setActiveResultTab] = useState<'results' | 'transform' | 'final' | 'instructions'>('results'); const [executionError, setExecutionError] = useState<string | null>(null); const fillJsonExample = () => { setInputType('raw'); setRawData(`[ {"fullName": "Alice Johnson", "birthDate": "2010-03-15", "department": "Engineering"}, {"fullName": "Bob Smith", "birthDate": "1993-07-22", "department": "Marketing"}, {"fullName": "Charlie Brown", "birthDate": "1988-11-08", "department": "Sales"} ]`); setResponseSchema(`{ "properties": { "results": { "type": "array", "items": { "type": "object", "properties": { "firstName": { "type": "string" }, "lastName": { "type": "string" }, "age": { "type": "number" }, "isAdult": { "type": "boolean" } }, "required": [ "firstName", "lastName", "age", "isAdult" ] } } }, "type": "object", "required": [ "results" ] }`); toast({ title: "Example loaded", description: "JSON transformation example has been loaded", }); }; const loadTransform = async (idToLoad: string) => { try { if (!idToLoad) return; setLoading(true); setResult(null); setExecutionError(null); const superglueClient = new SuperglueClient({ endpoint: config.superglueEndpoint, apiKey: config.superglueApiKey, }); const transformConfig = await superglueClient.getTransform(idToLoad); if (!transformConfig) { throw new Error(`Transform with ID "${idToLoad}" not found.`); } setTransformId(transformConfig.id); setInstruction(transformConfig.instruction || ''); setTransform(transformConfig.responseMapping || ''); if (transformConfig.responseSchema === null || transformConfig.responseSchema === undefined) { setResponseSchema(null); } else { setResponseSchema(JSON.stringify(transformConfig.responseSchema)); } // Load saved input data from cookie const savedInputData = loadInputDataFromCookie(idToLoad); if (savedInputData) { setInputType(savedInputData.inputType); setRawData(savedInputData.rawData); setInputUrl(savedInputData.inputUrl); // For file input, we can't restore the actual file, but we can show the filename if (savedInputData.inputType === 'file' && savedInputData.fileName) { // Clear the file input but show a message about the previous file setInputFile(null); toast({ title: "Previous file reference found", description: `Previously used file: ${savedInputData.fileName}`, }); } } } catch (error: any) { console.error("Error loading transform:", error); toast({ title: "Error loading transform", description: error.message, variant: "destructive", }); // Reset state, keeping the attempted ID in the input field for convenience setInstruction(''); setTransform(`(sourceData) => {\n return sourceData;\n}`); setResponseSchema(null); setResult(null); setExecutionError(null); } finally { setLoading(false); } }; useEffect(() => { if (id) { loadTransform(id); } else { // Reset to a clean slate if id is removed or not provided setTransformId(''); setInstruction(''); setTransform(`(sourceData) => {\n return sourceData;\n}`); setResponseSchema(null); setResult(null); setExecutionError(null); } }, [id]); const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { const file = event.target.files?.[0]; if (file) { setInputFile(file); } }; const saveTransform = async () => { try { const savingId = transformId || `transform-${Date.now()}`; setSaving(true); const transformData = { id: savingId, instruction: instruction, responseMapping: transform, responseSchema: responseSchema ? JSON.parse(responseSchema) : undefined }; const superglueClient = new SuperglueClient({ endpoint: config.superglueEndpoint, apiKey: config.superglueApiKey, }); await superglueClient.upsertTransformation(savingId, transformData); // Save input data to cookie const inputDataToSave: SavedInputData = { inputType, rawData, inputUrl, fileName: inputFile?.name }; saveInputDataToCookie(savingId, inputDataToSave); toast({ title: "Transform saved", description: `"${savingId}" saved successfully`, }); router.push(`/transforms/${savingId}`); } catch (error: any) { console.error("Error saving transform:", error); toast({ title: "Error saving transform", description: error.message, variant: "destructive", }); } finally { setSaving(false); } }; const executeTransform = async () => { const startTime = new Date(); let inputData: any; try { setLoading(true); setResult(null); setExecutionError(null); // Save input data to cookie before execution if (transformId) { const inputDataToSave: SavedInputData = { inputType, rawData, inputUrl, fileName: inputFile?.name }; saveInputDataToCookie(transformId, inputDataToSave); } const superglueClient = new SuperglueClient({ endpoint: config.superglueEndpoint, apiKey: config.superglueApiKey, }); // Prepare input based on type switch (inputType) { case 'raw': inputData = rawData; break; case 'url': if (!inputUrl.trim()) { throw new Error('URL cannot be empty'); } // Fetch data from URL const response = await fetch(`https://api.allorigins.win/get?url=${encodeURIComponent(inputUrl)}`); if (!response.ok) { throw new Error(`Failed to fetch data from URL: ${response.statusText}`); } const responseData = await response.json(); inputData = responseData.contents; break; case 'file': if (!inputFile) { throw new Error('No file selected'); } if (isBinaryFile(inputFile.name)) { // Use extract to convert binary files to JSON first const extractResult = await superglueClient.extract({ file: inputFile, endpoint: { id: `extract-${Date.now()}`, instruction: "Extract structured data from this file" } }); if (!extractResult.success) { throw new Error(`Failed to extract data from binary file: ${extractResult.error}`); } inputData = extractResult.data; } else { // Handle text files normally const fileContent = await inputFile.text(); inputData = fileContent; } break; default: throw new Error('Invalid input type'); } // Execute transform let transformResult: TransformResult & { data: any }; if (inputType === 'raw' || inputType === 'file') { transformResult = await superglueClient.transform({ data: inputData, endpoint: { id: transformId, instruction: instruction, responseSchema: responseSchema ? JSON.parse(responseSchema) : undefined, responseMapping: transform } }); } else if (inputType === 'url') { transformResult = await superglueClient.transform({ data: inputData, endpoint: { id: transformId, instruction: instruction, responseMapping: transform, responseSchema: responseSchema ? JSON.parse(responseSchema) : undefined }, options: { cacheMode: CacheMode.DISABLED } }); } else { throw new Error('Invalid input type'); } const endTime = new Date(); setResult({ success: transformResult.success, data: transformResult.data, startedAt: transformResult.startedAt, completedAt: transformResult.completedAt, config: { steps: [], ...transformResult.config }, stepResults: [{ stepId: transformId || 'transform', success: transformResult.success, rawData: inputData, transformedData: transformResult.data, error: null }], id: transformId || 'transform' }); setTransform(transformResult.config?.responseMapping || ''); setActiveResultTab('final'); } catch (error: any) { console.error("Error executing transform:", error); const endTime = new Date(); setResult({ success: false, error: error.message, startedAt: startTime, completedAt: endTime, config: { steps: [], id: transformId || 'transform' }, stepResults: [{ stepId: transformId || 'transform', success: false, rawData: inputData, transformedData: null, error: error.message }], id: transformId || 'transform' }); setExecutionError(error.message); toast({ title: "Error executing transform", description: error.message, variant: "destructive", }); } finally { setLoading(false); } }; const isInputEmpty = () => { switch (inputType) { case 'raw': return !rawData.trim(); case 'url': return !inputUrl.trim(); case 'file': return !inputFile; default: return true; } }; const renderInputSection = () => { return ( <div className="space-y-4"> {/* Transform ID */} <div className="mb-3"> <div className="flex items-center justify-between mb-1"> <Label htmlFor="transformId">Transform ID</Label> </div> <div className="flex items-center gap-2"> <Input id="transformId" value={transformId} onChange={(e) => setTransformId(e.target.value)} placeholder="Enter transform ID to load or save" className="flex-grow" /> <Button variant="outline" size="sm" onClick={() => loadTransform(transformId)} disabled={loading || saving || !transformId} className="flex-shrink-0" > {loading && !saving ? "Loading..." : "Load"} </Button> <Button variant="outline" size="sm" onClick={fillJsonExample} disabled={loading || saving} className="flex-shrink-0" > Example </Button> </div> </div> {/* Instruction */} <div> <Label htmlFor="instruction" className="mb-1 block">Instruction</Label> <Input id="instruction" value={instruction} onChange={(e) => setInstruction(e.target.value)} placeholder="Transform to the new schema" className="text-sm" /> </div> <div> <Label className="mb-3 block">Input Type</Label> <div className="grid grid-cols-3 gap-1 p-1 border rounded-lg"> <Button variant={inputType === 'raw' ? 'default' : 'ghost'} size="sm" onClick={() => setInputType('raw')} className="text-sm font-medium" > Raw Data </Button> <Button variant={inputType === 'url' ? 'default' : 'ghost'} size="sm" onClick={() => setInputType('url')} className="text-sm font-medium" > URL </Button> <Button variant={inputType === 'file' ? 'default' : 'ghost'} size="sm" onClick={() => setInputType('file')} className="text-sm font-medium" > File Upload </Button> </div> </div> { inputType === 'raw' && ( <div> <Label htmlFor="rawData" className="mb-1 block">Raw Data (JSON, XML, CSV)</Label> <Textarea id="rawData" value={rawData} onChange={(e) => setRawData(e.target.value)} placeholder="Enter data" className="font-mono text-xs min-h-[200px]" /> {!rawData.trim() && ( <div className="text-xs text-amber-800 dark:text-amber-300 flex items-center gap-1.5 bg-amber-500/10 py-1 px-2 rounded mt-1"> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /> <line x1="12" y1="9" x2="12" y2="13" /> <line x1="12" y1="17" x2="12.01" y2="17" /> </svg> No data provided </div> )} </div> )} { inputType === 'url' && ( <div> <Label htmlFor="inputUrl" className="mb-1 block">File URL (JSON, XML, CSV)</Label> <Input id="inputUrl" type="url" value={inputUrl} onChange={(e) => setInputUrl(e.target.value)} placeholder="https://api.example.com/data" /> {!inputUrl.trim() && ( <div className="text-xs text-amber-800 dark:text-amber-300 flex items-center gap-1.5 bg-amber-500/10 py-1 px-2 rounded mt-1"> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /> <line x1="12" y1="9" x2="12" y2="13" /> <line x1="12" y1="17" x2="12.01" y2="17" /> </svg> No URL provided </div> )} </div> )} { inputType === 'file' && ( <div> <Label htmlFor="inputFile" className="mb-1 block">Upload File (JSON, XML, CSV, Excel)</Label> <Input id="inputFile" type="file" accept=".json,.txt,.csv,.xml,.xlsx" onChange={handleFileChange} /> {inputFile && ( <p className="text-xs text-gray-500 mt-1"> Selected: {inputFile.name} ({(inputFile.size / 1024).toFixed(1)} KB) </p> )} {!inputFile && ( <div className="text-xs text-amber-800 dark:text-amber-300 flex items-center gap-1.5 bg-amber-500/10 py-1 px-2 rounded mt-1"> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" /> <line x1="12" y1="9" x2="12" y2="13" /> <line x1="12" y1="17" x2="12.01" y2="17" /> </svg> No file selected </div> )} </div> )} </div> ); }; return ( <div className="flex flex-col h-full"> <div className="flex items-center justify-between mb-4"> <h1 className="text-2xl font-semibold">Create New Transform</h1> <Button variant="ghost" size="icon" className="shrink-0" onClick={() => router.push('/configs')} > <X className="h-4 w-4" /> </Button> </div> <div className="p-6 max-w-none w-full"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> {/* Left Column - Transform Configuration */} <Card className="flex flex-col"> <CardContent className="p-4 overflow-auto flex-grow space-y-4"> {/* Input Section */} {renderInputSection()} {/* Response Schema */} <div className="flex-1 flex flex-col min-h-0"> <JsonSchemaEditor isOptional={true} value={responseSchema} onChange={setResponseSchema} /> </div> {/* Save and Execute Buttons */} <div className="flex gap-2 -mt-4 mb-4"> <Button variant="outline" onClick={saveTransform} disabled={saving || loading} className="w-full" > {saving ? "Saving..." : "Save Transform"} </Button> <Button onClick={executeTransform} disabled={loading || saving || isInputEmpty()} className="w-full" > {loading ? "Transforming..." : "Execute Transform"} </Button> </div> </CardContent> </Card> {/* Right Column - Results */} <WorkflowResultsView activeTab={activeResultTab} setActiveTab={setActiveResultTab} executionResult={result} finalTransform={transform} setFinalTransform={setTransform} finalResult={result?.data} isExecuting={loading} executionError={executionError} /> </div> </div> </div> ); }

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