DocsGuidesBuilding with AI

Building TopGun Apps with AI Coding Agents

Drop the right context into Claude Code, Cursor, or Codex once, and your agent will scaffold a working TopGun app in under five minutes — no manual API discovery required. This guide walks through the prompt template, context block, common pitfalls to pre-empt, and how to give your agent live database access via MCP.

1. Pick your agent

TopGun works with any AI coding agent. Three are well-suited for this workflow:

AgentSuitsInstall / docs
Claude CodeAgentic CLI sessions — reads your repo, runs commands, commits codedocs.anthropic.com/claude-code
CursorIDE-integrated workflow — inline completions + Agent panel in VS Code forkcursor.sh/docs
CodexAPI-driven pipelines — scriptable, headless, OpenAI-nativeplatform.openai.com/docs/guides/codex

TopGun is post-LLM pretraining cutoff, so all three agents need the context block in section 2 to produce correct code on first iteration — they will not have TopGun in their training data.

2. Give it context

The key step: give your agent the llms-full.txt bundle so it has the full API surface in its context window.

Paste this block at the start of every new TopGun session:

Paste this into your agent
You are building a TopGun app. TopGun is a local-first real-time sync library.
Load the full API reference from: https://topgun.build/llms-full.txt

Key mental model:
- useQuery<T>('mapName') for reads (returns live-updating array)
- useMutation<T>('mapName') for writes (create / update / delete)
- record._key is the unique identifier on every record
- pnpm start:server boots the local backend (zero-config, embedded storage)

Canonical app shape (always follow this):

import { useQuery, useMutation, TopGunProvider } from '@topgunbuild/react';
import { TopGunClient } from '@topgunbuild/client';
import { IDBAdapter } from '@topgunbuild/adapters';
import { type Todo } from './schema';

const client = new TopGunClient({
  serverUrl: 'ws://localhost:8080',
  storage: new IDBAdapter(),
});
await client.start();

function TodoApp() {
  const { data: todos = [] } = useQuery<Todo>('todos');
  const { create } = useMutation<Todo>('todos');
  const add = () => create(crypto.randomUUID(), { text: 'Ship it', done: false });
  return (
    <>
      <button onClick={add}>Add</button>
      <ul>{todos.map(t => <li key={t._key}>{t.text}</li>)}</ul>
    </>
  );
}

export default () => <TopGunProvider client={client}><TodoApp /></TopGunProvider>;

This gives your agent three things:

  1. The complete API reference loaded from https://topgun.build/llms-full.txt
  2. The canonical hook-first snippet (the same shape as the Quick Start canonical app)
  3. A one-line mental model: hooks for reads (useQuery), useMutation for writes, schema in Zod, pnpm start:server for the local backend

Without this block, agents reliably hallucinate methods like client.subscribe() or reach for raw map getters for reads, which won’t re-render on data changes.

After giving your agent the context block above, describe what you want to build. Use this template as your starting point:

Prompt template
Build me a [todo / chat / drawing] app using TopGun.
Schema: [list the fields you need, e.g., text:string, done:boolean].
Permissions: [open / per-user / role-based].
Use the hook-first API: useQuery for reads, useMutation for writes.
Load the full API reference first: https://topgun.build/llms-full.txt

Examples:

  • Build me a todo app using TopGun. Schema: text:string, done:boolean. Permissions: open.
  • Build me a chat app using TopGun. Schema: author:string, body:string, ts:number. Permissions: per-user.
  • Build me a drawing app using TopGun. Schema: x:number, y:number, color:string. Permissions: open.

Replace the brackets with your actual requirements. The Use the hook-first API line is load-bearing — include it verbatim to steer the agent toward useQuery / useMutation from the first generation.

4. Common agent mistakes and how to fix them

Mistake 1: Used client.getMap instead of useQuery

Symptom: The app renders initial data but doesn’t update when other users add or change records.

Why it happens: client.getMap('todos') returns a raw map object — it doesn’t subscribe to React state. The agent reaches for it because it looks like a standard getter.

Fix: Swap every read to useQuery<T>('mapName'). Cross-reference: Quick Start canonical app.

// Wrong — no re-renders when data changes
const map = client.getMap('todos');

// Correct — live-updating, re-renders on every change
const { data: todos = [] } = useQuery<Todo>('todos');

Mistake 2: Hallucinated a method that doesn’t exist

Symptom: The agent generates code calling client.subscribe(), client.watch(), client.on(), or a similar method that doesn’t exist in the TopGun client API.

Why it happens: Agents extrapolate from Firebase / Supabase patterns they were trained on.

Fix: Connect the agent to the MCP server (see section 5). With live database access, the agent can call topgun_list_maps and topgun_schema tools to introspect the real API surface, eliminating hallucination. See the MCP Server guide.

Mistake 3: Forgot the server bootstrap

Symptom: WebSocket connection to 'ws://localhost:8080' failed or ECONNREFUSED errors in the browser console.

Why it happens: The agent generates correct client code but doesn’t know a server needs to be running locally.

Fix: Run pnpm start:server in a second terminal before opening the app. See Quick Start — Start the Server.

# In a second terminal — keep this running while you develop
pnpm start:server

5. Live database access via MCP

Connect your agent to a running TopGun server so it can query your real data, introspect the schema, and avoid hallucination entirely.

TopGun ships @topgunbuild/mcp-server, which implements the Model Context Protocol and exposes eight tools to any MCP-compatible agent: topgun_query, topgun_mutate, topgun_search, topgun_subscribe, topgun_schema, topgun_stats, topgun_explain, topgun_list_maps.

Step 1: Make sure pnpm start:server is running.

Step 2: Add the TopGun MCP server to your agent’s config.

Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):

Claude Desktop config
{
  "mcpServers": {
    "topgun": {
      "command": "npx",
      "args": ["@topgunbuild/mcp-server"],
      "env": {
        "TOPGUN_URL": "ws://localhost:8080"
      }
    }
  }
}

Cursor (.cursor/mcp.json):

Cursor MCP config
{
  "mcp": {
    "servers": {
      "topgun": {
        "command": "npx",
        "args": ["@topgunbuild/mcp-server"],
        "env": {
          "TOPGUN_URL": "ws://localhost:8080"
        }
      }
    }
  }
}

For the full MCP reference — all available tools, authentication, and remote server configuration — see the MCP Server guide.

6. Worked example

Here is a complete session transcript: one context paste, one prompt, one correction, working app.


User (context block paste + prompt):

You are building a TopGun app. TopGun is a local-first real-time sync library. Load the full API reference from: https://topgun.build/llms-full.txt [… seed prompt from section 2 …]

Build me a task list app using TopGun. Schema: text:string, done:boolean. Permissions: open. Use the hook-first API: useQuery for reads, useMutation for writes.

Agent (first generation):

The agent reads llms-full.txt, sees the canonical hook-first snippet, and produces:

app.tsx (first generation)
import { useQuery, useMutation, TopGunProvider } from '@topgunbuild/react';
import { TopGunClient } from '@topgunbuild/client';
import { IDBAdapter } from '@topgunbuild/adapters';
import { z } from 'zod';

const TaskSchema = z.object({ text: z.string(), done: z.boolean() });
type Task = z.infer<typeof TaskSchema>;

const client = new TopGunClient({
  serverUrl: 'ws://localhost:8080',
  storage: new IDBAdapter(),
});
await client.start();

function TaskList() {
  const { data: tasks = [] } = useQuery<Task>('tasks');
  const { create, update } = useMutation<Task>('tasks');

  return (
    <ul>
      {tasks.map(task => (
        <li key={task._key}>
          <input
            type="checkbox"
            checked={task.done}
            onChange={() => update(task._key, { done: !task.done })}
          />
          {task.text}
        </li>
      ))}
      <li>
        <button onClick={() => create(crypto.randomUUID(), { text: 'New task', done: false })}>
          Add task
        </button>
      </li>
    </ul>
  );
}

export default () => <TopGunProvider client={client}><TaskList /></TopGunProvider>;

User (one correction):

The checkbox isn’t persisting after reload. The update call looks right — make sure await client.start() runs before the component mounts, not inside it.

Agent (correction):

The agent moves await client.start() to module scope (before the component function), which is already correct in the example above. With await client.start() at module level, the client is connected before the first render, and update calls go through immediately.

Result: Working app in under five minutes. Data persists across reloads via IndexedDB and syncs across browser tabs in real time.


Why this works reliably: The llms-full.txt bundle gives the agent ~120 KB of accurate TopGun API documentation in its context window. Agents don’t need to be pre-trained on TopGun — they just need accurate docs at the start of the session.