Connect Claude Code to your instance
You can point Claude Code on your laptop at your Turbobase instance. Once connected it can read your record catalog, your published knowledge base, and the record reference docs for your connected sources — and it can create, refine, and ship a hosted app.
This is the do-it-yourself path. If you’d rather describe the app in a chat and have Turbobase build it, use the template gallery in your app instead — Development › Proposals, the Templates icon in the header; see Development. The two paths meet in the middle — an app started from a proposal can be pulled down and refined here, and an app authored here shows up in the same place.
The two endpoints
Section titled “The two endpoints”Your instance exposes two MCP servers. They do different jobs and authenticate differently, and you’ll want both.
| Data | Authoring | |
|---|---|---|
| Endpoint | https://<slug>.turbobase.app/mcp |
https://<slug>.turbobase.app/mcp/authoring |
| Sign-in | OAuth — a browser window, your normal sign-in | A personal access token |
| Gives you | Your catalog, your knowledge base, record anatomy, live rows, Turbobase’s own documentation — plus customization search for an administrator | Create / pull / push apps, scaffold + deploy extensions, Turbobase’s own documentation, customization search |
| Scoped to | Your permissions, row by row | Instance admin |
The data endpoint is what makes the difference between an agent that guesses your schema and one that knows it. The authoring endpoint is what lets it ship.
Both endpoints can read these pages, so your session need not go out to the web for them. search_turbobase_docs takes a phrase and returns ranked hits; read_turbobase_doc takes a hit’s slug and returns the whole page. The pages are the ones your instance’s own release shipped with, so what your assistant reads describes the version you are building against, and every answer names that version. They are deliberately separate from search_documentation / read_document, which read your knowledge base — your processes and policies. Platform questions (“what does a hook receive”) go to the first pair; business questions (“how do we handle returns”) to the second. Either pair can be used inside a batch_read on the data endpoint, so a search and two page reads are one call. If you prefer plain HTTP, the same corpus is at GET /v1/catalog/docs/search?q=… and GET /v1/catalog/docs/document/:slug on the catalog:read scope your token already carries.
Customization search is on both, and on the data endpoint it is administrator-only. search_catalog and explain_object — “which script handles rebates?”, then that script in depth — answer over your whole connected account, so there is nothing to narrow person by person. On the data endpoint an administrator gets them with no token at all; anyone else is not offered them, and their assistant will say it has no tool for the job rather than refusing halfway through. On the authoring endpoint they come with the token, which is instance-admin by definition.
Shipping over the authoring endpoint needs your explicit approval
Section titled “Shipping over the authoring endpoint needs your explicit approval”Over the network, every tool that ships something asks for approve: true before it acts, and answers approval_required without it: deploy, apps_push, tables_apply, and — when your token carries analyzer:run / releases:write — every Analyzer tool that deploys a bundle, ships or rolls back a release, or toggles managed-script instrumentation. Claude Code shows you that answer and asks; say yes and it re-invokes the tool with the approval. Read-only tools never ask. The endpoint also takes one request per call: a JSON-RPC batch (an array body) is refused, which no MCP client sends in normal use.
1. Turn the MCP server on
Section titled “1. Turn the MCP server on”An administrator enables it at Settings › Integrations & AI access. Until it’s on, both endpoints refuse every caller. That page also shows both URLs, filled in for your instance.
2. Mint a token
Section titled “2. Mint a token”Sign in with your Turbobase account first: a token belongs to a Turbobase account, so this page is not offered to administrators who opened the app from inside NetSuite.
On the Access tokens page — your account menu, or /settings/tokens — mint a personal access token with:
extensions:deploy— the whole hosted-app surface (create, push, pull, list)mcp:authoring— the authoring endpoint itselfcatalog:read— read the record catalog
Copy it when shown; it’s stored hashed. Add analyzer:run and releases:write only if you also want the Analyzer and release tools. analyzer:run authorizes source-system work, including account capture and deploying a managed bundle into your connected Oracle NetSuite account. App work needs none of that.
3. Install the CLI and sign in
Section titled “3. Install the CLI and sign in”curl -fsSL https://console.turbobase.io/install-cli.sh | bashturbobase auth login --base-url https://<slug>.turbobase.app --token tb_pat_your_token_hereOn Windows: powershell -c "irm https://console.turbobase.io/install-cli.ps1 | iex".
Both commands — and a turbobase auth login line with your instance URL already
filled in — are in your instance under Settings → Integrations & AI access.
Prefer npm? npm install -g @turbobase/cli works too.
One instance, one login. Every command below uses it — you never pass a URL again.
4. Wire up Claude Code
Section titled “4. Wire up Claude Code”If you’re building an app, skip this step — turbobase apps init (below) lays the wiring down as part of the project.
For a hook-authoring workspace, run it in an empty directory:
turbobase project initEither way you get a .mcp.json already filled in with your instance URL and token (both MCP servers), a CLAUDE.md, and a .claude/ directory of skills. Start Claude Code in that directory. The first time it uses the data server it opens your instance’s sign-in page — there’s no client ID to paste.
.mcp.json holds a live token. App projects gitignore it for you; in a hooks workspace, keep it out of version control yourself.
Building an app
Section titled “Building an app”Start from scratch
Section titled “Start from scratch”turbobase apps init collections # scaffold the project + the Claude wiringcd collectionsturbobase apps create collections # register it on your instanceclaudeapps init lays down the same scaffold a proposal-generated app gets, and it is more than the SPA:
- The app — Vite + React + TypeScript, Tailwind v4 and shadcn/ui (New York, neutral) per the design system, the auth-wired client, and a starter component that already does a row-level-security-scoped read, a write and a delete.
- The rest of the deployed state —
hooks/with a workedbefore_saveexample,conduits/, andturbobase/sync.json+turbobase/schedules.json. See deploy, plan, promote: your repo is the desired state, and merging applies it. - The CI workflow —
.github/workflows/turbobase-app-deploy.yml: a preview URL per pull request (updated in place on every push), production on merge to your default branch. - The Claude Code wiring —
CLAUDE.md,.mcp.jsonfor both MCP servers,.claude/skills, and a session hook that refreshes your generated types, an instance grounding brief, andADOPT.md(platform changes this project should act on).
It also switches on a Turbobase consultant output style, written for ERP consultants, NetSuite administrators and business analysts: business outcome first, source-system vocabulary, and short answers unless you ask for detail. Pick a different style in /config, or delete the outputStyle line in .claude/settings.json, if you would rather have stock Claude Code.
Nothing to hand-write, and nothing to remember to run second. apps init never overwrites an existing file, so re-running it in an older project is how you pick up additions.
Its dev server proxies /rest, /realtime, /v1, /auth and /login to your instance, so npm run dev behaves same-origin: sign in on first load and reads, writes and Realtime all work exactly as they will in production. The app is served at http://localhost:3000/a/collections/ — not /, which redirects there.
Then ask Claude Code to build the thing. A useful prompt names the records, so it goes and looks them up:
Look at the customer and invoice record types in the catalog, read our knowledge base on the collections process, then rewrite src/App.tsx as a list of overdue invoices grouped by customer. Follow the Turbobase design system.
When you like it:
turbobase apps push collections # store the source on your instanceturbobase apps deploy collections # build + go liveYour app is live at https://<slug>.turbobase.app/a/collections.
Start from a proposal
Section titled “Start from a proposal”If the app was generated in Studio, pull it down and keep going:
turbobase apps pull collections ./collectionsRefine it with Claude Code, then push your changes back and deploy:
turbobase apps push collections ./collectionsturbobase apps deploy collectionsPushing is what keeps the instance holding the source of record. Skip it and your changes exist only on that laptop — the next apps pull, from any machine, returns the version before your edits.
Push and deploy are different things
Section titled “Push and deploy are different things”This trips people up, so it’s worth being explicit:
apps pushrecords source. Nothing is built, nothing changes for your users.apps deploybuilds the app on your machine and deploys the result. This is what goes live.
You can push a dozen times while iterating and deploy once.
There is one place the difference bites beyond “where does my source live”, and it is easy to miss: deploy triggers the knowledge ingest below, but push is what supplies the files it reads. A deploy uploads your built bundle and no markdown at all. Skip the push and the ingest runs against your previous revision, finds nothing new, and reports nothing wrong.
apps push sends text files only. It skips node_modules/, dist/, build/, coverage/, .git/, .claude/, .turbobase/, .next/, .svelte-kit/, .vercel/, .cache/, and anything that isn’t text — so images and fonts belong in your build pipeline or object storage, not the source tree. .claude/ is skipped because a coding agent writes nested worktrees under it, each a second full copy of the project; INSTANCE-LEARNINGS.md and TURBOBASE-FEEDBACK.md live at the project root and are still pushed.
It also never sends credential files: .mcp.json and .env* (except .env.example) are withheld, and it says so on stderr when it withholds one. Your token stays on your machine.
Feed what you learned back into your knowledge base
Section titled “Feed what you learned back into your knowledge base”Building against a live mirror teaches you things no page documents yet: the twin table nobody mentioned, the subsidiary number that turned out to be wrong, the rule that doesn’t actually hold. Write those down as you go, in a ## Suggested KB edits section (something is missing) or a ## Knowledge-base corrections section (something published is wrong) in any markdown file at your app root — INSTANCE-LEARNINGS.md is the conventional home.
Your instance ingests those sections on the next turbobase apps deploy and then stops. A deploy never writes your knowledge base; it only queues what it found. Turning a finding into a change is a separate, explicit step.
Push before you deploy, or there is nothing new to ingest. The deploy triggers the ingest; the ingest reads the source tree your last turbobase apps push recorded — not the files on your disk, and not the built bundle the deploy just uploaded. So the order is:
turbobase apps push <app> . # records INSTANCE-LEARNINGS.md and the restturbobase apps deploy <app> # goes live, and queues the ingest over that pushGet it backwards and nothing fails: the job runs, succeeds against the older revision, and writes nothing — after which kb push and skills push report an empty queue, which reads exactly like “it hasn’t finished yet”. apps deploy prints the source revision it queued the ingest against, and warns when there is none or when your local markdown is newer than it. Believe that line over the queue.
Then, to turn findings into changes:
turbobase kb push --dry-run # read the plan, write nothingturbobase kb push # same plan, then asks before writingThe unit is a page, not a finding. Everything you wrote about one page is one entry in the plan and one write: twelve findings about REC-01 append once, and four findings asking for the same page nobody has written yet create that page once. So the plan reads “19 pages”, not “53 things”, and you can see which findings were merged before you confirm.
The plan is printed before anything happens, and for each page it names the findings landing on it, the exact markdown it appends, and — loudest — whether confirming withdraws that page from readers. A correction whose verdict says a published claim is contradicted by live data pulls the page off the reader surface; the page’s text is not touched by that withdrawal, so publishing it again restores it exactly. Additive findings never withdraw anything.
Confirming writes the pages:
- An existing page is appended to, under a
## From app developersheading. Nothing already on the page is rewritten — the only edit is an insertion, so a push cannot destroy a sentence an editor wrote. - A new page lands unpublished, filed under a “From app developers” page for an editor to fold into the tree.
Either way nothing reaches readers until someone publishes a knowledge-base release, so a push is never a publication. --dry-run reviews without writing, --diff prints the exact markdown on the confirm path too, --yes skips the question for a scripted run, and --app / --section / --limit narrow what you act on.
Three things worth knowing:
- It’s admin only. Your token needs the same scope
apps pushuses, and your account has to be an administrator of the instance — writing the shared knowledge base is not a per-developer act. - The plan is binding. If someone else works through the queue while you’re reading, confirming fails with “the pending queue changed” and writes nothing. Re-run to see the current plan. You never confirm one plan and get another.
- A page someone is editing is skipped, not overwritten. If a colleague has unpublished work on a page your push targets, that page is left alone and its findings stay queued — the plan tells you who, before you confirm. Re-run once they’ve published.
What Claude Code can and can’t do
Section titled “What Claude Code can and can’t do”It can: read your catalog, search and read your published knowledge base, read the record anatomy docs for your connected sources, read live rows through your own permissions, and create, pull, push, and list apps. It can also scaffold, test, and deploy hooks, conduits, and processes.
It can’t:
- Create a proposal or run Studio’s AI generation. Those are in the app, by design — the metered generation step has no external API. Claude Code joins the loop at
apps pull. - Ship an app on its own. Shipping builds a local directory and uploads the result, which a server-side MCP endpoint can’t reach. Claude Code runs
turbobase apps deployas a shell command instead. - See anything your account can’t. Data reads run through the same row-level security as the app. A colleague connecting their own client sees their own slice.
- Read unpublished knowledge base drafts. Only the published release is served.
Troubleshooting
Section titled “Troubleshooting”Every call returns 503. The MCP server is off. An administrator turns it on at Settings › Integrations & AI access.
insufficient_scope on an authoring call. The token is missing a scope — the error names which one. Mint a new token with the full set; scopes are fixed at mint time.
401 on the authoring endpoint with a token that works elsewhere. The two endpoints are separate sign-ins. A data-endpoint OAuth token is rejected at /mcp/authoring, and a personal access token is rejected at /mcp. Check which URL the header is attached to.
429 on an authoring call, and your own token has barely been used. Two budgets guard /mcp/authoring: 60 requests a minute from one address, which applies before the token is read, and 60 a minute per token after it. A refusal carrying {"statusCode":429,"error":"Too Many Requests"} is the per-address one — something else is calling the same instance, typically another agent, a CI runner or a second container. It need not share your outbound address: until an instance’s network is configured to pass the caller’s address through, that budget is shared by every client of the instance. One carrying {"error":"rate_limited"} is your token’s own. Wait out the minute; nothing is revoked.
no source for this app. Nothing has been pushed or generated yet. turbobase apps list shows a dash in the SOURCE column for exactly these — push once and it fills in.
Claude Code doesn’t seem to know about a table. Ask it to call list_record_types or read the catalog:/// resource first. It reads the catalog on demand, not automatically.
Claude Code can’t find a field, and concludes the data isn’t there. list_record_types describes record TYPES and never their columns, so a field not named after its record is invisible to it — a customer’s credit limit lives on entity.credit_limit, and nothing in the record list says so. Ask it to call search_fields with the words you’d use (“exchange rate”, “credit limit”); it returns the record type, table and column that hold it.
Related
Section titled “Related”- Building apps — the full app contract: reads, writes, realtime
- App auth wiring — the one file you copy
- Design system — what apps here look like
- Knowledge base — where
turbobase kb pushpages land - MCP server — the data endpoint in detail
- Access tokens — token kinds, scopes, revocation
- CLI reference — every command