Skip to main content
Glama

Convex MCP server

Official
by get-convex
FunctionCallTree.tsx6.95 kB
import { CheckCircledIcon, CrossCircledIcon, SewingPinFilledIcon, } from "@radix-ui/react-icons"; import { useEffect, useMemo, useRef } from "react"; import { UdfLog, UdfLogOutcome } from "@common/lib/useLogs"; import { FunctionNameOption } from "@common/elements/FunctionNameOption"; import { msFormat } from "@common/lib/format"; import { Spinner } from "@ui/Spinner"; import { cn } from "@ui/cn"; type ExecutionNode = { executionId: string; functionName: string; startTime: number; executionTime?: number; status: "success" | "failure" | "running"; parentExecutionId?: string | null; caller?: string; environment?: string; identityType?: string; children: ExecutionNode[]; error?: string; udfType: string; }; export function FunctionCallTree({ logs, currentLog, }: { logs: UdfLog[]; currentLog: UdfLog; }) { const executionNodes = useMemo(() => createExecutionNodes(logs), [logs]); if (executionNodes.length === 0) { return null; } const rootNode = executionNodes[0]; return ( <div className="max-h-full animate-fadeInFromLoading p-2 text-xs"> <p className="pb-2 text-content-tertiary"> This is an outline of the functions called in this request. </p> {rootNode && ( <div> {executionNodes.map((node) => ( <ExecutionTreeNode key={node.executionId} node={node} level={0} currentLog={currentLog} /> ))} </div> )} </div> ); } function ExecutionTreeNode({ node, level, currentLog, }: { node: ExecutionNode; level: number; currentLog: UdfLog; }) { const hasChildren = node.children.length > 0; const nodeRef = useRef<HTMLDivElement>(null); const isCurrent = node.executionId === currentLog.executionId; useEffect(() => { if (isCurrent && nodeRef.current) { nodeRef.current.scrollIntoView({ block: "nearest", }); } }, [isCurrent]); return ( <div className="font-mono text-xs"> <div ref={nodeRef} className={cn( "flex h-[30px] w-full items-center rounded-md pr-2 pl-4", isCurrent && "-ml-px rounded border bg-background-highlight", )} > <div className="flex h-full items-center"> {level !== 0 && Array.from({ length: level }).map((_, index) => ( <div key={index} className="mr-4 h-full w-[1px] shrink-0 bg-border-transparent" /> ))} </div> <div className="-ml-1.5 flex shrink items-center gap-1"> {node.status === "running" ? ( <Spinner className="size-4" /> ) : node.error ? ( <CrossCircledIcon className="size-4 text-content-error" aria-label="Function call failed" /> ) : node.status === "success" ? ( <CheckCircledIcon className="size-4 text-content-success" aria-label="Function call succeeded" /> ) : null} <FunctionNameOption label={node.functionName} error={!!node.error} /> </div> {node.executionTime && ( <span className="ml-1 text-content-secondary"> ({msFormat(node.executionTime)}) </span> )} {isCurrent && <SewingPinFilledIcon />} </div> {hasChildren && ( <div> {node.children.map((child) => ( <ExecutionTreeNode key={child.executionId} node={child} level={level + 1} currentLog={currentLog} /> ))} </div> )} </div> ); } function createCompletedExecutionNodes(logs: UdfLog[]): { nodeMap: Map<string, ExecutionNode>; outcomeSet: Set<string>; } { const nodeMap = new Map<string, ExecutionNode>(); const outcomeSet = new Set<string>(); logs .filter((log): log is UdfLog & UdfLogOutcome => log.kind === "outcome") .forEach((log) => { const node: ExecutionNode = { executionId: log.executionId, functionName: log.call, startTime: log.executionTimestamp || log.timestamp, executionTime: log.executionTimeMs ?? undefined, status: log.outcome.status, parentExecutionId: log.parentExecutionId, caller: log.caller, environment: log.environment, identityType: log.identityType, children: [], error: log.error, udfType: log.udfType, }; nodeMap.set(log.executionId, node); outcomeSet.add(log.executionId); }); return { nodeMap, outcomeSet }; } function createRunningExecutionNodes( logs: UdfLog[], nodeMap: Map<string, ExecutionNode>, outcomeSet: Set<string>, ): void { const executionLogMap = new Map<string, UdfLog[]>(); logs .filter((log) => log.kind === "log") .forEach((log) => { if (!executionLogMap.has(log.executionId)) { executionLogMap.set(log.executionId, []); } executionLogMap.get(log.executionId)!.push(log); }); executionLogMap.forEach((logEntries, executionId) => { if (!outcomeSet.has(executionId)) { const firstLog = logEntries[0]; // Try to find parent info from any log entry (might be in overrides) const { parentExecutionId } = firstLog as any; const node: ExecutionNode = { executionId: firstLog.executionId, functionName: firstLog.call, startTime: firstLog.timestamp, executionTime: undefined, status: "running", parentExecutionId: parentExecutionId || undefined, caller: undefined, environment: undefined, identityType: undefined, children: [], error: undefined, udfType: firstLog.udfType, }; nodeMap.set(executionId, node); } }); } function buildExecutionTree( nodeMap: Map<string, ExecutionNode>, ): ExecutionNode[] { const sortChildren = (node: ExecutionNode) => { node.children.sort((a, b) => a.startTime - b.startTime); node.children.forEach(sortChildren); }; const rootNodes: ExecutionNode[] = []; const allNodes = Array.from(nodeMap.values()); // Build the tree structure allNodes.forEach((node) => { if (!node.parentExecutionId) { rootNodes.push(node); } else { const parent = nodeMap.get(node.parentExecutionId); if (parent) { parent.children.push(node); } else { rootNodes.push(node); } } }); rootNodes.sort((a, b) => a.startTime - b.startTime); rootNodes.forEach((node) => sortChildren(node)); return rootNodes; } export function createExecutionNodes(logs: UdfLog[]): ExecutionNode[] { const { nodeMap, outcomeSet } = createCompletedExecutionNodes(logs); createRunningExecutionNodes(logs, nodeMap, outcomeSet); return buildExecutionTree(nodeMap); }

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/get-convex/convex-backend'

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