Skip to main content
Glama

Convex MCP server

Official
by get-convex
LogMetadata.tsx23.8 kB
import { ChevronDownIcon, ChevronUpIcon, PieChartIcon, QuestionMarkCircledIcon, } from "@radix-ui/react-icons"; import { useMemo } from "react"; import { UdfLog, UdfLogOutcome } from "@common/lib/useLogs"; import { Tooltip } from "@ui/Tooltip"; import { msFormat, formatBytes } from "@common/lib/format"; import { UsageStats } from "system-udfs/convex/_system/frontend/common"; import { FunctionNameOption } from "@common/elements/FunctionNameOption"; import { Disclosure } from "@headlessui/react"; import { Spinner } from "@ui/Spinner"; type RequestUsageStats = UsageStats & { runtimeMs: number; computeMbMs: number; returnBytes?: number; }; type OutcomeNode = { inProgress: boolean; functionName?: string; caller?: string; environment?: string; identityType?: string; executionTime?: number; localizedTimestamp?: string; endTime?: number; udfType?: string; cachedResult?: boolean; }; export function LogMetadata({ requestId, logs, executionId, }: { requestId: string; logs: UdfLog[]; executionId?: string; }) { const isExecutionView = !!executionId; const filteredLogs = useMemo(() => { if (!isExecutionView) { return logs.filter((log) => log.requestId === requestId); } return logs.filter((log) => log.executionId === executionId); }, [logs, isExecutionView, executionId, requestId]); const requestOutcomeNode = useMemo((): OutcomeNode | null => { const outcomeLog = logs.find( (log): log is UdfLog & UdfLogOutcome => log.kind === "outcome" && log.requestId === requestId && !("parentExecutionId" in log && log.parentExecutionId), ) || logs.find( (log): log is UdfLog => log.requestId === requestId && !("parentExecutionId" in log && log.parentExecutionId), ); return outcomeLog ? { inProgress: outcomeLog.kind !== "outcome", functionName: outcomeLog.call, caller: outcomeLog.kind === "outcome" ? outcomeLog.caller : undefined, environment: outcomeLog.kind === "outcome" ? outcomeLog.environment : undefined, identityType: outcomeLog.kind === "outcome" ? outcomeLog.identityType : undefined, executionTime: outcomeLog.kind === "outcome" ? (outcomeLog.executionTimeMs ?? undefined) : undefined, localizedTimestamp: outcomeLog.localizedTimestamp, endTime: "executionTimestamp" in outcomeLog ? outcomeLog.executionTimestamp : undefined, udfType: outcomeLog.udfType, cachedResult: outcomeLog.cachedResult, } : null; }, [logs, requestId]); const executionOutcomeNode = useMemo((): OutcomeNode | null => { if (!executionId) return null; const executionLogs = logs.filter((log) => log.executionId === executionId); // Find the outcome log for complete information const outcomeLog = executionLogs.find( (log): log is UdfLog & UdfLogOutcome => log.kind === "outcome", ); // Get function name and type from any log with this executionId const anyLog = executionLogs[0]; return { inProgress: !outcomeLog, functionName: outcomeLog?.call ?? anyLog?.call ?? undefined, caller: outcomeLog?.caller, environment: outcomeLog?.environment, identityType: outcomeLog?.identityType, executionTime: outcomeLog?.executionTimeMs ?? undefined, localizedTimestamp: outcomeLog?.localizedTimestamp ?? anyLog?.localizedTimestamp ?? undefined, endTime: outcomeLog?.executionTimestamp ?? undefined, udfType: outcomeLog?.udfType ?? anyLog?.udfType ?? undefined, cachedResult: outcomeLog?.cachedResult ?? anyLog?.cachedResult ?? undefined, }; }, [logs, executionId]); const usageStats = useMemo(() => { const totals: RequestUsageStats = { memoryUsedMb: 0, databaseReadBytes: 0, databaseReadDocuments: 0, databaseWriteBytes: 0, storageReadBytes: 0, storageWriteBytes: 0, vectorIndexReadBytes: 0, vectorIndexWriteBytes: 0, runtimeMs: 0, computeMbMs: 0, }; return filteredLogs.reduce((accumulated, log) => { const ret = accumulated; if ("usageStats" in log && log.usageStats) { for (const [key, value] of Object.entries(log.usageStats) as Array< [keyof UsageStats, number | null | undefined] >) { ret[key] += value ?? 0; } } if ("returnBytes" in log && log.returnBytes) { ret.returnBytes = (ret.returnBytes ?? 0) + log.returnBytes; } if (log.kind === "outcome") { const durationMs = log.executionTimeMs ?? 0; ret.runtimeMs += durationMs; const memoryMb = (log.usageStats?.memoryUsedMb ?? 0) as number; ret.computeMbMs += durationMs * memoryMb; } return ret; }, totals); }, [filteredLogs]); const isInProgress = executionId ? !executionOutcomeNode || executionOutcomeNode.inProgress : !requestOutcomeNode || requestOutcomeNode.inProgress; return ( <div className="animate-fadeInFromLoading p-2 text-xs"> {isExecutionView ? ( <ExecutionInfoList outcomeNode={executionOutcomeNode} executionId={executionId} /> ) : ( <RequestInfoList outcomeNode={requestOutcomeNode} requestId={requestId} /> )} <ResourcesUsed usageStats={usageStats} filteredLogs={filteredLogs} isExecutionView={isExecutionView} isInProgress={isInProgress} /> </div> ); } function ResourcesUsed({ usageStats, filteredLogs, isExecutionView, isInProgress, }: { usageStats: RequestUsageStats; filteredLogs: UdfLog[]; isExecutionView: boolean; isInProgress: boolean; }) { return ( <div className="mt-2"> <Disclosure defaultOpen> {({ open }) => ( <> <div className="flex items-center justify-between"> <Disclosure.Button className="flex items-center gap-1 text-xs"> <PieChartIcon className="size-3 text-content-secondary" /> <h6 className="font-semibold text-content-secondary"> Resources Used </h6> {open ? ( <ChevronUpIcon className="size-3" /> ) : ( <ChevronDownIcon className="size-3" /> )} </Disclosure.Button> </div> <Disclosure.Panel className="mt-2 animate-fadeInFromLoading"> {isInProgress && <Running />} {isInProgress && isExecutionView ? ( <span className="mt-2 text-content-tertiary"> Resource usage will appear here once the function call completes. </span> ) : ( <ul className="divide-y text-xs"> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="flex items-center gap-1 text-content-secondary"> Compute <Tooltip tip="Only compute from Actions incur additional cost. Query/Mutation compute are included."> <QuestionMarkCircledIcon /> </Tooltip> </span> <span className="min-w-0 text-content-primary"> <strong> {Number( usageStats.computeMbMs / (1024 * 3_600_000), ).toFixed(7)}{" "} GB-hr </strong>{" "} ({usageStats.memoryUsedMb ?? 0} MB for{" "} {Number(usageStats.runtimeMs / 1000).toFixed(2)}s) </span> </li> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">DB Bandwidth</span> <span className="min-w-0 text-content-primary"> Accessed{" "} <strong> {usageStats.databaseReadDocuments.toLocaleString()}{" "} {usageStats.databaseReadDocuments === 1 ? "document" : "documents"} </strong> ,{" "} <strong> {formatBytes(usageStats.databaseReadBytes)} </strong>{" "} read,{" "} <strong> {formatBytes(usageStats.databaseWriteBytes)} </strong>{" "} written </span> </li> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary"> File Bandwidth </span> <span className="min-w-0 text-content-primary"> <strong> {formatBytes(usageStats.storageReadBytes)} </strong>{" "} read,{" "} <strong> {formatBytes(usageStats.storageWriteBytes)} </strong>{" "} written </span> </li> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary"> Vector Bandwidth </span> <span className="min-w-0 text-content-primary"> <strong> {formatBytes(usageStats.vectorIndexReadBytes)} </strong>{" "} read,{" "} <strong> {formatBytes(usageStats.vectorIndexWriteBytes)} </strong>{" "} written </span> </li> {usageStats.returnBytes && ( <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="flex items-center gap-1 text-content-secondary"> Return Size <Tooltip tip="Bandwidth from sending the return value of a function call to the user does not incur costs."> <QuestionMarkCircledIcon /> </Tooltip> </span> <span className="min-w-0 text-content-primary"> <strong>{formatBytes(usageStats.returnBytes)}</strong>{" "} returned </span> </li> )} {filteredLogs.filter((log) => log.kind === "outcome").length > 1 && ( <li className="py-2 text-content-secondary"> Total resources used across{" "} {filteredLogs.filter((l) => l.kind === "outcome").length}{" "} executions {isExecutionView ? " in this execution" : " in this request"} . </li> )} </ul> )} </Disclosure.Panel> </> )} </Disclosure> </div> ); } function FunctionEnvironment({ environment }: { environment?: string }) { switch (environment) { case "isolate": return ( <div className="flex items-center gap-1"> Convex <Tooltip tip="This function was executed in Convex's isolated environment."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "node": return ( <div className="flex items-center gap-1"> Node <Tooltip tip="This function was executed in Convex's Node.js environment."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); default: return <Running />; } } function FunctionIdentity({ identity, caller, }: { identity?: string; caller?: string; }) { switch (identity) { case "instance_admin": return ( <div className="flex items-center gap-1"> Admin <Tooltip tip="This request was initiated by a Convex Developer with access to this deployment."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "user": return ( <div className="flex items-center gap-1"> User <Tooltip tip="This request was initiated by a user."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "member_acting_user": case "team_acting_user": return ( <div className="flex items-center gap-1"> Admin (Acting as user) <Tooltip tip="This request was initiated by a Convex Developer with access to this deployment while impersonating a user."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "system": return ( <div className="flex items-center gap-1"> System <Tooltip tip="This request was initiatedby the Convex system."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "unknown": return caller === "Scheduler" || caller === "Cron" ? ( <div className="flex items-center gap-1"> System <Tooltip tip="This function was executed by the Convex system."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ) : ( <div className="flex items-center gap-1"> Unknown <Tooltip tip="This identity for this function call is unknown."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); default: return <Running />; } } function FunctionCaller({ caller }: { caller?: string }) { switch (caller) { case "Tester": return ( <div className="flex items-center gap-1"> Function Runner <Tooltip tip="This function was executed through the Convex Dashboard or CLI."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "HttpApi": return ( <div className="flex items-center gap-1"> HTTP API <Tooltip tip="This function was called through the Convex HTTP API."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "HttpEndpoint": return ( <div className="flex items-center gap-1"> HTTP Endpoint <Tooltip tip="This HTTP Action was called by an HTTP request."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "SyncWorker": return ( <div className="flex items-center gap-1"> Websocket <Tooltip tip="This function was called through a websocket connection."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "Cron": return ( <div className="flex items-center gap-1"> Cron Job <Tooltip tip="This function was called by a scheduled Cron Job."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "Scheduler": return ( <div className="flex items-center gap-1"> Scheduler <Tooltip tip="This function was called by a scheduled job."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); case "Action": return ( <div className="flex items-center gap-1"> Action <Tooltip tip="This function was called by an action."> <QuestionMarkCircledIcon className="text-content-tertiary" /> </Tooltip> </div> ); default: return <Running />; } } function FunctionType({ udfType, cachedResult, }: { udfType?: string; cachedResult?: boolean; }) { const getTypeDisplay = () => { switch (udfType) { case "Query": return cachedResult ? "Query (cached)" : "Query"; case "Mutation": return "Mutation"; case "Action": return "Action"; case "HttpAction": return "HTTP Action"; default: return <Running />; } }; return <span>{getTypeDisplay()}</span>; } function RequestInfoList({ outcomeNode, requestId, }: { outcomeNode: OutcomeNode | null; requestId: string; }) { return ( <ul className="divide-y"> <li className="grid grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Request ID</span> <span className="truncate font-mono text-content-primary"> {requestId} </span> </li> <li className="grid grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Started at</span> <span className={ outcomeNode?.endTime && outcomeNode?.executionTime ? "truncate text-content-primary" : "truncate text-content-tertiary" } > {outcomeNode?.endTime && outcomeNode?.executionTime ? ( new Date( outcomeNode.endTime - outcomeNode.executionTime, ).toLocaleString() ) : ( <Running /> )} </span> </li> <li className="grid grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Completed at</span> <span className={ outcomeNode?.endTime ? "truncate text-content-primary" : "truncate text-content-tertiary" } > {outcomeNode?.endTime ? ( new Date(outcomeNode.endTime).toLocaleString() ) : ( <Running /> )} </span> </li> <li className="grid grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Duration</span> <span className={ outcomeNode?.executionTime ? "flex items-center gap-1 text-content-primary" : "flex items-center gap-1 text-content-tertiary" } > {outcomeNode?.executionTime ? ( msFormat(outcomeNode.executionTime) ) : ( <Running /> )} </span> </li> <li className="grid grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Identity</span> <span className="truncate text-content-primary"> <FunctionIdentity identity={outcomeNode?.identityType} caller={outcomeNode?.caller} /> </span> </li> <li className="grid grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Caller</span> <span className="truncate text-content-primary"> <FunctionCaller caller={outcomeNode?.caller} /> </span> </li> <li className="grid grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Environment</span> <span className="truncate text-content-primary"> <FunctionEnvironment environment={outcomeNode?.environment} /> </span> </li> </ul> ); } function ExecutionInfoList({ outcomeNode, executionId, }: { outcomeNode: OutcomeNode | null; executionId?: string; }) { const duration = typeof outcomeNode?.executionTime === "number" ? msFormat(outcomeNode.executionTime) : undefined; return ( <ul className="divide-y"> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Execution ID</span> <span className="min-w-0 truncate font-mono text-content-primary"> {executionId} </span> </li> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Function</span> <span className="min-w-0 truncate"> {outcomeNode?.functionName ? ( <span className="font-mono text-content-primary"> <FunctionNameOption label={outcomeNode.functionName} /> </span> ) : ( <Running /> )} </span> </li> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Type</span> <span className="min-w-0 truncate text-content-primary"> <FunctionType udfType={outcomeNode?.udfType} cachedResult={outcomeNode?.cachedResult} /> </span> </li> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Started at</span> <span className={ outcomeNode?.endTime && outcomeNode?.executionTime ? "truncate text-content-primary" : "truncate text-content-tertiary" } > {outcomeNode?.endTime && outcomeNode?.executionTime ? ( new Date( outcomeNode.endTime - outcomeNode.executionTime, ).toLocaleString() ) : ( <Running /> )} </span> </li> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Completed at</span> <span className={ outcomeNode?.endTime ? "truncate text-content-primary" : "truncate text-content-tertiary" } > {outcomeNode?.endTime ? ( new Date(outcomeNode.endTime).toLocaleString() ) : ( <Running /> )} </span> </li> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Duration</span> <span className={ duration ? "flex min-w-0 items-center gap-1 text-content-primary" : "flex min-w-0 items-center gap-1 text-content-tertiary" } > {duration || <Running />} </span> </li> <li className="grid min-w-fit grid-cols-2 items-center gap-2 py-1.5"> <span className="text-content-secondary">Environment</span> <span className="truncate text-content-primary"> <FunctionEnvironment environment={outcomeNode?.environment} /> </span> </li> </ul> ); } function Running() { return ( <span className="flex animate-fadeInFromLoading items-center gap-1 text-content-tertiary"> <Spinner className="ml-0 size-3" /> Running... </span> ); }

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