Skip to main content
Glama
mcp.ts5.43 kB
import { useEffect } from 'react' import { type z } from 'zod' export function useMcpUiInit() { useEffect(() => { window.parent.postMessage({ type: 'ui-lifecycle-iframe-ready' }, '*') const height = document.documentElement.scrollHeight const width = document.documentElement.scrollWidth window.parent.postMessage( { type: 'ui-size-change', payload: { height, width } }, '*', ) }, []) } type MessageOptions = { schema?: z.ZodSchema signal?: AbortSignal timeoutMs?: number } type McpMessageReturnType<Options> = Promise< Options extends { schema: z.ZodSchema } ? z.infer<Options['schema']> : unknown > type McpMessageTypes = { tool: { toolName: string; params: Record<string, unknown> } prompt: { prompt: string } link: { url: string } } type McpMessageType = keyof McpMessageTypes function sendMcpMessage<Options extends MessageOptions>( type: 'tool', payload: McpMessageTypes['tool'], options?: Options, ): McpMessageReturnType<Options> function sendMcpMessage<Options extends MessageOptions>( type: 'prompt', payload: McpMessageTypes['prompt'], options?: Options, ): McpMessageReturnType<Options> function sendMcpMessage<Options extends MessageOptions>( type: 'link', payload: McpMessageTypes['link'], options?: Options, ): McpMessageReturnType<Options> function sendMcpMessage<TypeType extends McpMessageType>( type: TypeType, payload: McpMessageTypes[TypeType], options: MessageOptions = {}, ): McpMessageReturnType<typeof options> { debugger // if (type === 'tool') { // // Goose does not currentlly support tool calls, so change this to a prompt // const { toolName, params } = payload as McpMessageTypes['tool'] // type = 'prompt' as TypeType // payload = { // prompt: `Please call the tool ${toolName} with the following parameters: ${JSON.stringify(params)}`, // } as McpMessageTypes[TypeType] // } const { signal: givenSignal, schema, timeoutMs = 3_000 } = options const timeoutSignal = typeof timeoutMs === 'number' ? AbortSignal.timeout(timeoutMs) : undefined const signals = [givenSignal, timeoutSignal].filter(Boolean) const signal = signals.length > 0 ? AbortSignal.any(signals) : undefined const messageId = crypto.randomUUID() return new Promise((resolve, reject) => { if (signal?.aborted) { reject(new Error('Operation aborted before it began')) return } if (!window.parent || window.parent === window) { console.log(`[MCP] No parent frame available. Would have sent message:`, { type, messageId, payload, }) reject(new Error('No parent frame available')) return } console.log('posting to parent', { type, messageId, payload }) window.parent.postMessage({ type, messageId, payload }, '*') function handleMessage(event: MessageEvent) { if (event.data.type === 'ui-message-response') { const { messageId: responseMessageId, payload: { response, error }, } = event.data if (responseMessageId === messageId) { window.removeEventListener('message', handleMessage) if (error) return reject(new Error(error)) if (!schema) return resolve(response) const parseResult = schema.safeParse(response) if (!parseResult.success) { return reject(new Error(parseResult.error.message)) } return resolve(parseResult.data) } } } window.addEventListener('message', handleMessage, { signal }) }) } export { sendMcpMessage } // Module-level queue for render data events const renderDataQueue: Array<{ type: string; payload: any }> = [] // Set up global listener immediately when module loads (only in the client) if (typeof document !== 'undefined') { window.addEventListener('message', (event) => { if (event.data?.type === 'ui-lifecycle-iframe-render-data') { renderDataQueue.push(event.data) } }) } export function waitForRenderData<RenderData>( schema: z.ZodSchema<RenderData>, opts: { signal?: AbortSignal; timeoutMs?: number } = {}, ): Promise<RenderData> { const { signal: givenSignal, timeoutMs = 3_000 } = opts const timeoutSignal = typeof timeoutMs === 'number' ? AbortSignal.timeout(timeoutMs) : undefined const signals = [givenSignal, timeoutSignal].filter(Boolean) const signal = AbortSignal.any(signals) return new Promise((resolve, reject) => { // Check if we already received the data const queuedEvent = renderDataQueue.find( (event) => event.type === 'ui-lifecycle-iframe-render-data', ) if (queuedEvent) { const result = schema.safeParse(queuedEvent.payload.renderData) return result.success ? resolve(result.data) : reject(result.error) } // Otherwise, set up the normal listening logic function cleanup() { window.removeEventListener('message', handleMessage) signal.removeEventListener?.('abort', onAbort as EventListener) } function onAbort() { cleanup() const reason = (signal as any).reason ?? new DOMException('Timed out waiting for render data', 'TimeoutError') reject(reason) } function handleMessage(event: MessageEvent) { if (event.data?.type !== 'ui-lifecycle-iframe-render-data') return const result = schema.safeParse(event.data.payload) cleanup() return result.success ? resolve(result.data) : reject(result.error) } signal.addEventListener('abort', onAbort, { once: true }) window.addEventListener('message', handleMessage, { once: true, signal, }) if (signal.aborted) onAbort() }) }

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/epicweb-dev/epic-me-mcp'

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