Skip to main content
Glama

Convex MCP server

Official
by get-convex
App.tsx6.62 kB
import { ReactElement, useCallback, useEffect, useRef } from "react"; import { ConvexProvider, ConvexReactClient } from "convex/react"; import { useMutation, useQuery } from "convex/react"; import { api } from "../convex/_generated/api"; import { useClientState, usePositions, useStateBoolFromUrl, useStateIntFromUrl, useUpdatingTimeAgo, } from "./hooks"; import { COLORS, Plot } from "./plot"; const address: string = import.meta.env.VITE_CONVEX_URL; declare global { // Global variables that live outside of React to prevent rerenders. These // could be passed around as refs or stuck in a context that shared refs, // but global variables are real convenient. interface Window { qps: number; contentious: boolean; } } export default function App() { const [numClients, setNumClients] = useStateIntFromUrl("clients", 1); const [numSubscriptions, setNumSubscriptions] = useStateIntFromUrl( "subscriptions-per-client", 1, ); useUpdatingTimeAgo(); return ( <main style={{ display: "flex", flexDirection: "column" }}> <h1>Are we multiplayer yet?</h1> <QPSSlider /> <span>{`${numSubscriptions} ${ numSubscriptions === 1 ? "subscription" : "subscriptions" } per client`}</span> <SubscriptionsPerClientSlider onChange={setNumSubscriptions} initial={numSubscriptions} /> <span>{`${numClients} ${numClients === 1 ? "client" : "clients"}`}</span> <ClientsSlider onChange={setNumClients} initial={numClients} /> <ContentionCheckbox /> <button style={{ maxWidth: 250 }} onClick={() => { const origNumClients = numClients; setNumClients(0); setTimeout(() => setNumClients(origNumClients), 100); }} > Clear </button> {Array.from({ length: numClients }) .map((_, i) => i) .map((i) => ( <WrappedClient key={i} first={i === 0} i={i} color={COLORS[i]} subscriptions={numSubscriptions} /> ))} </main> ); } function ExtraSubscriptions({ num, clientNum, }: { num: number; clientNum: number; }) { return ( <> {Array.from({ length: num }).map((_, i) => ( <ExtraSubscription key={i} nonce={`client${clientNum}_sub${i + 2}`} /> ))} </> ); } function ExtraSubscription({ nonce }: { nonce: string }) { useQuery(api.getPositions.default, { nonce }); return null; } type ClientProps = { first: boolean; i: number; color: string; subscriptions: number; }; function WrappedClient(props: ClientProps) { const client = useRef<ConvexReactClient>(); if (!client.current) client.current = new ConvexReactClient(address); useEffect(() => { return () => { client.current && client.current.close(); client.current = undefined; }; }, []); return ( <ConvexProvider client={client.current!}> <Client {...props} /> <ExtraSubscriptions num={props.subscriptions - 1} clientNum={props.i} /> </ConvexProvider> ); } function Client({ first, color }: ClientProps) { const { sessionId, latencyBuffer } = useClientState(); const { receivedNum, receivedTime, sentNum } = usePositions( latencyBuffer, sessionId, ); const clear = useMutation(api.clear.default); // Every time a first client on the page is created, clear the db. useEffect(() => { if (first) clear(); }, [first, clear]); const height = 100; return ( <> <div style={{ display: "flex" }}> <div className={sentNum % 2 === 0 ? "just-sent1" : "just-sent2"} style={{ width: 100, height, ...CENTER_STYLES, color: "white", border: `solid 10px ${color}`, }} > sent </div> <div className={ receivedNum % 2 === 0 ? "just-received1" : "just-received2" } style={{ width: 100, height, ...CENTER_STYLES, color: "white" }} > received update <br /> <span style={{ fontVariantNumeric: "tabular-nums" }} data-time={receivedTime} > 000 ms ago </span> </div> <Plot buffer={latencyBuffer} width={300} height={height} /> </div> </> ); } const CENTER_STYLES = { display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column", textAlign: "center", lineHeight: "1em", } as const; function QPSSlider(): ReactElement { const [initial] = useStateIntFromUrl("qps", 1); const onChange = useCallback((n: number) => { window.qps = n; document.querySelector("#timeoutValue")!.innerHTML = display(window.qps); }, []); if (window.qps === undefined) { window.qps = initial; } return ( <> <span id="timeoutValue">{display(window.qps)}</span> <input type="range" style={{ maxWidth: 400 }} defaultValue={initial} min="1" max={30} onInput={(e: any) => onChange(parseInt(e.target.value))} ></input> </> ); } function ClientsSlider({ onChange, initial, }: { onChange: (n: number) => void; initial: number; }): ReactElement { return ( <input type="range" style={{ maxWidth: 200 }} defaultValue={initial} min={0} max={8} onInput={(e: any) => onChange(parseInt(e.target.value))} ></input> ); } function SubscriptionsPerClientSlider({ onChange, initial, }: { onChange: (n: number) => void; initial: number; }): ReactElement { return ( <input type="range" style={{ maxWidth: 200 }} defaultValue={initial} min={1} max={20} onInput={(e: any) => onChange(parseInt(e.target.value))} ></input> ); } function ContentionCheckbox(): ReactElement { const [initial] = useStateBoolFromUrl("contentious"); if (window.contentious === undefined) { window.contentious = initial; } const onChange = useCallback((checked: boolean) => { window.contentious = checked; }, []); return ( <div> <label> <input style={{ marginRight: 4 }} type="checkbox" defaultChecked={initial} onChange={(e) => onChange(e.target.checked)} /> extra contention: find doc to modify via filter instead of db.get() </label> </div> ); } function display(qps: number): string { return `sending updates at ${qps} QPS, every ${Math.round(1000 / qps)}ms`; }

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