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:
| Agent | Suits | Install / docs |
|---|---|---|
| Claude Code | Agentic CLI sessions — reads your repo, runs commands, commits code | docs.anthropic.com/claude-code |
| Cursor | IDE-integrated workflow — inline completions + Agent panel in VS Code fork | cursor.sh/docs |
| Codex | API-driven pipelines — scriptable, headless, OpenAI-native | platform.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:
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:
- The complete API reference loaded from
https://topgun.build/llms-full.txt - The canonical hook-first snippet (the same shape as the Quick Start canonical app)
- A one-line mental model: hooks for reads (
useQuery),useMutationfor writes, schema in Zod,pnpm start:serverfor 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.
3. Recommended prompt template
After giving your agent the context block above, describe what you want to build. Use this template as your starting point:
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):
{
"mcpServers": {
"topgun": {
"command": "npx",
"args": ["@topgunbuild/mcp-server"],
"env": {
"TOPGUN_URL": "ws://localhost:8080"
}
}
}
} Cursor (.cursor/mcp.json):
{
"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:
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
updatecall looks right — make sureawait 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.