Skip to main content
Glama

Superglue MCP

Official
by superglue-ai
InteractiveApiPlayground.tsx10.6 kB
'use client' import { useConfig } from '@/src/app/config-context' import { useToast } from '@/src/hooks/use-toast' import { ApiConfig, CacheMode, SuperglueClient } from '@superglue/client' import { Loader2, CopyIcon } from 'lucide-react' import { useEffect, useMemo, useState } from 'react' import { AutoSizer, List } from 'react-virtualized' import JsonSchemaEditor from '../utils/JsonSchemaEditor' import { Button } from '../ui/button' import { Card, CardContent } from '../ui/card' import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs' interface InteractiveApiPlaygroundProps { configId: string instruction: string onInstructionChange?: (instruction: string) => void responseSchema: string onResponseSchemaChange: (schema: string) => void initialRawResponse?: any responseMapping?: any onMappedResponse?: (response: any) => void onRun?: () => Promise<void> isRunning?: boolean mappedResponseData?: any hideRunButton?: boolean } interface CustomRequestOptions { cacheMode?: CacheMode; timeout?: number; retries?: number; retryDelay?: number; webhookUrl?: string; responseSchema?: object; } export function InteractiveApiPlayground({ configId, instruction, onInstructionChange, responseSchema, onResponseSchemaChange, initialRawResponse, responseMapping, onMappedResponse, onRun, isRunning, mappedResponseData, hideRunButton }: InteractiveApiPlaygroundProps) { const [isLoading, setIsLoading] = useState(false) const [rawResponse, setRawResponse] = useState<any>(initialRawResponse || null) const [mappedResponse, setMappedResponse] = useState<any>(null) const [activeTab, setActiveTab] = useState('raw') const { toast } = useToast() const superglueConfig = useConfig() const [config, setConfig] = useState<ApiConfig | null>(null) const fetchConfig = async () => { try { const superglueClient = new SuperglueClient({ endpoint: superglueConfig.superglueEndpoint, apiKey: superglueConfig.superglueApiKey }) const data = await superglueClient.getApi(configId) setConfig(data) } catch (error) { console.error('Error fetching config:', error) } } const handleCopy = (content: string, contentType: string) => { if (!content) return; navigator.clipboard.writeText(content) .then(() => { toast({ title: `${contentType} Copied`, description: `${contentType} content copied to clipboard.`, }) }) } // Fetch config on mount useEffect(() => { if (configId) { fetchConfig() } }, [configId]) const handleRun = async () => { // TODO: deduplicate this with ConfigCreateStepper.tsx if (onRun) { return onRun() } setIsLoading(true) try { const superglueClient = new SuperglueClient({ endpoint: superglueConfig.superglueEndpoint, apiKey: superglueConfig.superglueApiKey }) // 1. First upsert the API config with the new schema and instruction await superglueClient.upsertApi(configId, { id: configId, instruction, responseSchema: JSON.parse(responseSchema) }) // 2. Call the API using the config ID and get mapped response const mappedResult = await superglueClient.call({ id: configId, options: { cacheMode: CacheMode.WRITEONLY } }) if (mappedResult.error) { throw new Error(mappedResult.error) } // 3. Set the mapped response setMappedResponse(mappedResult.data) onMappedResponse?.(mappedResult.data) setActiveTab('mapped') } catch (error: any) { console.error('Error running API:', error) toast({ title: 'Error Running API', description: error?.message || 'An error occurred while running the API', variant: 'destructive' }) } finally { setIsLoading(false) } } // Update mapped response when it comes from props useEffect(() => { if (mappedResponseData) { setMappedResponse(mappedResponseData) setActiveTab('mapped') } }, [mappedResponseData]) // Memoize the line splitting for each content type const rawResponseLines = useMemo(() => { return rawResponse ? JSON.stringify(rawResponse, null, 2).split('\n') : ['Response will appear here...'] }, [rawResponse]) const mappedResponseLines = useMemo(() => { if (isRunning || isLoading) return ['Loading...'] return mappedResponse ? JSON.stringify(mappedResponse, null, 2).split('\n') : ['Output will appear here...'] }, [mappedResponse, isRunning, isLoading]) const renderRow = (lines: string[]) => ({ index, key, style }: any) => { const line = lines[index] const indentMatch = line?.match(/^(\s*)/) const indentLevel = indentMatch ? indentMatch[0].length : 0 return ( <div key={key} style={{ ...style, whiteSpace: 'pre', paddingLeft: `${indentLevel * 8}px`, }} className="font-mono text-xs overflow-hidden text-ellipsis" > {line?.trimLeft()} </div> ) } const getLineCount = (lines: string[]) => lines.length return ( <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 h-full"> {/* Left Column */} <div className="flex flex-col space-y-4 overflow-hidden h-full"> <div className="flex-1 min-h-0 overflow-hidden flex flex-col"> <div className="flex-1 min-h-0 bg-background h-full"> <JsonSchemaEditor value={responseSchema} onChange={onResponseSchemaChange} /> </div> </div> {!hideRunButton && ( <div className="flex justify-end"> <Button onClick={handleRun} disabled={isRunning || isLoading} > {isRunning || isLoading ? ( <> <Loader2 className="mr-2 h-4 w-4 animate-spin" /> Running... </> ) : ( <> ✨ Run </> )} </Button> </div> )} </div> {/* Right Column */} <div className="flex flex-col h-full overflow-hidden rounded-lg"> <Tabs value={activeTab} onValueChange={setActiveTab} className="h-full flex flex-col"> <Card className="h-full flex flex-col"> <CardContent className="p-0 h-full flex flex-col bg-secondary"> <TabsList className="w-full rounded-t-lg rounded-b-none"> <TabsTrigger value="raw" className="flex-1">Raw API Response</TabsTrigger> <TabsTrigger value="mapped" className="flex-1">🍯 Output</TabsTrigger> <TabsTrigger value="jsonata" className="flex-1">Response Mapping</TabsTrigger> </TabsList> <div className="flex-1 min-h-0"> <TabsContent value="raw" className="m-0 h-full data-[state=active]:flex flex-col"> <div className="flex-1 min-h-0 p-4 overflow-hidden relative"> {rawResponse && ( <Button variant="ghost" size="icon" className="absolute top-2 right-8 z-10 h-6 w-6" onClick={() => handleCopy(JSON.stringify(rawResponse, null, 2), 'Raw Response')} > <CopyIcon className="h-4 w-4" /> </Button> )} <AutoSizer> {({ height, width }) => ( <List width={width} height={height} rowCount={getLineCount(rawResponseLines)} rowHeight={18} rowRenderer={renderRow(rawResponseLines)} overscanRowCount={100} className="overflow-auto" /> )} </AutoSizer> </div> </TabsContent> <TabsContent value="mapped" className="m-0 h-full data-[state=active]:flex flex-col"> <div className="flex-1 min-h-0 p-4 overflow-hidden relative"> {mappedResponse && !(isRunning || isLoading) && ( <Button variant="ghost" size="icon" className="absolute top-2 right-8 z-10 h-6 w-6" onClick={() => handleCopy(JSON.stringify(mappedResponse, null, 2), 'Output')} > <CopyIcon className="h-4 w-4" /> </Button> )} <AutoSizer> {({ height, width }) => ( <List width={width} height={height} rowCount={getLineCount(mappedResponseLines)} rowHeight={18} rowRenderer={renderRow(mappedResponseLines)} overscanRowCount={100} className="overflow-auto" /> )} </AutoSizer> </div> </TabsContent> <TabsContent value="jsonata" className="m-0 h-full data-[state=active]:flex flex-col"> <div className="flex-1 min-h-0 p-4 overflow-y-auto relative"> {responseMapping && !(isRunning || isLoading) && ( <Button variant="ghost" size="icon" className="absolute top-2 right-8 z-10 h-6 w-6" onClick={() => handleCopy(responseMapping, 'Response Mapping')} > <CopyIcon className="h-4 w-4" /> </Button> )} <pre className="text-xs whitespace-pre-wrap leading-[18px]"> {isRunning || isLoading ? 'Loading...' : (responseMapping || 'No JSONata mapping available')} </pre> </div> </TabsContent> </div> </CardContent> </Card> </Tabs> </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