---
url: https://saas.dev/docs/agents
md: https://saas.dev/docs/agents.md
---

# Build with your agent

Your coding agent builds and ships your product the way you would: it writes the files, pushes them to Test, fixes every error from the line and the fix each one names, and sends you the Test URL. Live waits for you.

Paste this into Claude Code, Cursor or any agent that runs terminal commands:

```text title="Prompt"
Read https://saas.dev/AGENTS.md. Then build me a SaaS on SaaS.Dev that tracks
[what it tracks, and for whom]: start with
`npx saas.dev init my-product --from leaseclock.com`, push to Test, fix every
error, and send me the Test URL it prints. Go Live only through the approval
link, when I say so.
```

The agent can run [`npx saas.dev`](https://saas.dev/docs/saas-dev-cli.md) in a terminal, or call SaaS.Dev's MCP server.

## Connect

SaaS.Dev's MCP server is `https://api.saas.dev/mcp`, over streamable HTTP. The first call opens SaaS.Dev's sign-in page, and the agent then works as you, on your products.

**Claude Code**

```bash title="Terminal"
claude mcp add --transport http saas-dev https://api.saas.dev/mcp
```

**Claude**

In **Settings → Connectors**, choose **Add custom connector**, name it SaaS.Dev and paste the address:

```text
https://api.saas.dev/mcp
```

**Cursor**

```json title=".cursor/mcp.json"
{
  "mcpServers": {
    "saas-dev": { "url": "https://api.saas.dev/mcp" }
  }
}
```

**VS Code**

```json title=".vscode/mcp.json"
{
  "servers": {
    "saas-dev": { "type": "http", "url": "https://api.saas.dev/mcp" }
  }
}
```

An agent on your own machine can run the same tools over stdio with `npx saas.dev mcp`, signed in as the CLI is. In CI, send `SAAS_DEV_TOKEN` as a bearer token in place of signing in.

## The tools

SaaS.Dev's server has the same three tools as every product it generates: `search` and `fetch` read your products, their versions and their errors, and `do` runs a platform Verb. A Verb's input is checked before it runs, and every write lands in your product's history.

### check

Checks files without saving anything, and returns every error at once.

| Input | Type | What it does |
| --- | --- | --- |
| `product` | string | The product, such as `my-product` |
| `files` | object | Each path and its text, such as `{ "Noun/Lease.mdx": "…" }` |

### preview

Returns what a push would change on each surface, and whether any change is breaking.

| Input | Type | What it does |
| --- | --- | --- |
| `product` | string | The product |
| `files` | object | The files to compare with the version Test serves |

### push

Checks the files and saves them as a new version in Test. It returns `{ ok, version, url }`, or `{ ok: false, errors }` with nothing saved.

| Input | Type | What it does |
| --- | --- | --- |
| `product` | string | The product |
| `files` | object | The files |
| `fix` | boolean | Applies every error's edit first |
| `yes` | boolean | Confirms a breaking change |

### rollback

Serves an earlier version again. Every later version stays saved.

| Input | Type | What it does |
| --- | --- | --- |
| `product` | string | The product |
| `version` | number | The version to serve |
| `live` | boolean | Rolls Live back instead of Test |

### go_live

Asks to serve a version Test already runs in Live. It returns an approval link for you to open. The version reaches customers only after you approve it there.

| Input | Type | What it does |
| --- | --- | --- |
| `product` | string | The product |
| `version` | number | The version, which must already run in Test |

Versions are records, read with `search`: `{ "type": "Version", "where": { "product": "my-product" } }` lists them with who pushed each and where it runs.

## The file your agent reads first

`npx saas.dev init` writes `AGENTS.md` into your product's folder, and the same file is at [saas.dev/AGENTS.md](https://saas.dev/AGENTS.md):

````md title="AGENTS.md"
# AGENTS.md

This folder is a product on SaaS.Dev. Its definition is the Markdown files in it: each file in `Noun/` is something the product tracks, with its fields and its Verbs, and each file in `Offer/` is a plan its customers buy. SaaS.Dev generates the app, API, CLI, MCP server, SDK, site, docs, blog and Stripe billing from these files, and runs them. None of the generated code lands in this folder.

## How to work here

1. Change the product by editing the files in `Noun/` and `Offer/`. Write only the keys in https://saas.dev/schema/noun.json and https://saas.dev/schema/offer.json (copies sit in `.saas/schema/`).
2. Push to Test: `npx saas.dev push`. Every file is checked, and every error comes back at once, each with its file, line, fix and edit.
3. Fix them all: `npx saas.dev push --fix` applies each error's edit, then previews.
4. Read what changes before it is saved: `npx saas.dev push --preview --json`.
5. Send the person the Test URL the push prints.

## What needs the person

- Live. `npx saas.dev push --live` prints an approval link, and Live serves the version only after the person approves it there.
- Tokens. The CLI keeps the person's sign-in for this machine. CI reads `$SAAS_DEV_TOKEN`. Write no token into a file.
- Money. `npx saas.dev stripe connect` opens Stripe for the person to sign in.

## The shape of a file

```mdx title="Noun/Lease.mdx"
---
name: Lease
fields:
  unit: { type: link, target: Unit, required: true }
  renter: { type: email, required: true }
  endsOn: { type: date, required: true, indexed: true }
  status: { type: choice, options: [{ key: active }, { key: ended }] }
verbs:
  renew:
    does: Move the end date forward
    input: { endsOn: { says: the new end date, type: date, required: true } }
    from: [active]
  end: { does: End the lease, from: [active], to: ended }
  remind:
    does: Email the renter that the lease ends in 60 days
    emails: renter
    from: [active]
    on: endsOn - 60 days
---
```

A Noun's name is PascalCase and singular. Field keys and Verb names are camelCase. A Verb with no function writes its input onto the record.

## An error

```json title="npx saas.dev push --json"
{
  "ok": false,
  "errors": [
    {
      "code": "invalid_name",
      "file": "Noun/Lease.mdx",
      "line": 6,
      "message": "field \"EndsOn\" must start lower-case.",
      "fix": "Rename it to \"endsOn\".",
      "edit": { "file": "Noun/Lease.mdx", "line": 6, "from": "EndsOn", "to": "endsOn" },
      "docs": "https://saas.dev/docs/errors#invalid_name"
    }
  ]
}
```

The command exits 1 on any error, and 2 on a breaking change without `--yes`.

## Commands

- `npx saas.dev init [folder] [--from <domain>]`: Asks for a name, signs you in on first run, starts from Leaseclock unless --from names another product, writes its files and AGENTS.md, and prints its Test URL
- `npx saas.dev login`: Signs you in through the browser and stores an id.org.ai token
- `npx saas.dev pull [name] [version]`: Writes a product's definition, or an earlier version, into a folder, with .saas/types.d.ts and .saas/schema/
- `npx saas.dev push`: Checks every file and pushes a new version to Test
- `npx saas.dev push --preview`: Checks every file and prints what changes on each surface, saving nothing
- `npx saas.dev push --live`: Pushes the version in Test to Live, after a preview
- `npx saas.dev versions`: Lists every version with who pushed it and where it runs
- `npx saas.dev rollback <version> [--live]`: Serves an earlier version again, keeping every later version
- `npx saas.dev open [path]`: Opens the product in Test in the browser
- `npx saas.dev domain add <domain>`: Serves the product on your own domain, with the DNS records to add: the site, and SPF and DKIM for its mail
- `npx saas.dev stripe connect`: Connects your Stripe account, once, for Test and Live
- `npx saas.dev token create <name> [--live]`: Creates a token for CI, which pushes to Test unless created with --live
- `npx saas.dev token revoke <name>`: Ends a token
- `npx saas.dev export`: Writes the definition files, every version and every record with its history, as JSON Lines, into a folder
- `npx saas.dev mcp`: Serves SaaS.Dev's MCP tools over stdio, for an agent on this machine

## More

- Every docs page as Markdown, in one file: https://saas.dev/llms-full.txt
- What SaaS.Dev is, and every page's address: https://saas.dev/llms.txt
- Every key a file takes: https://saas.dev/docs/agents.md
- Every error code: https://saas.dev/docs/errors.md
````

## A session

```console title="Claude Code"
> Build me a SaaS that tracks dog-walking bookings.

● Reading https://saas.dev/AGENTS.md
● npx saas.dev init my-product --from leaseclock.com
  https://my-product.test.saas.dev
● Renaming Lease to Booking, endsOn to startsAt, adding walker
● npx saas.dev push --json
  { "ok": false, "errors": [ { "code": "unknown_type", "file": "Noun/Booking.mdx",
    "line": 8, "fix": "Use \"team\" for a person in the workspace." } ] }
● npx saas.dev push --fix
  Pushed version 2 · https://my-product.test.saas.dev

Your product runs in Test at https://my-product.test.saas.dev. Bookings have a
start time, a walker and a status. Say when you want it Live, and open the
approval link I send.
```

## Without MCP

Every command takes `--json` and prints one JSON object, so an agent reads results without parsing a terminal.

```bash title="Terminal"
npx saas.dev push --json
npx saas.dev push --preview --json
```

A push returns `{ ok, errors }`. Each error carries `code`, `file`, `line`, `message`, `fix`, `edit: { file, line, from, to }` and `docs`, and `push --fix` applies every `edit`. A preview returns `{ version, changes: [{ surface, change, breaking }], breaking }`.

| Exit code | When |
| --- | --- |
| `0` | The command did what it says |
| `1` | Any error, such as a file that breaks a rule |
| `2` | A breaking change without --yes |

Without a terminal, `push --live` prints an approval link and waits until you approve it. Only a token made with `token create --live` pushes to Live without one.

## Every key

Every key a definition file takes. The same grammar is JSON Schema at [saas.dev/schema/noun.json](https://saas.dev/schema/noun.json) and [saas.dev/schema/offer.json](https://saas.dev/schema/offer.json), which `init` and `pull` save to `.saas/schema/`.

| In | Key | Takes | What it does |
| --- | --- | --- | --- |
| Noun | `name` | PascalCase, singular | The Noun: the list, the API path, the SDK class and the docs page take their names from it |
| Noun | `fields` | a map of field key to field | What each record holds |
| Noun | `verbs` | a map of Verb name to Verb | What people and agents do with a record |
| Field | `type` | one of the field types | What the field holds, and the input, column and JSON it gets |
| Field | `required` | true | A record cannot be saved without it |
| Field | `indexed` | true | Sorts and filters the list, and takes .before and .after on dates |
| Field | `many` | true | Holds a list of values |
| Field | `max` | a number | The longest text, or the most values in a list |
| Field | `pattern` | a regular expression | Every value must match it |
| Field | `rich` | true | Allows formatting in longText |
| Field | `options` | a list of { key, label } | The options of a choice or choices field |
| Field | `currency` | a three-letter code | A money field's currency, when it is not the Offer's |
| Field | `decimals` | a number | Decimal places shown and stored |
| Field | `range` | { min, max } | The least and most a number may be |
| Field | `unit` | text | A number's unit, shown beside it |
| Field | `start` | a number | An autoNumber's first value |
| Field | `classification` | NAICS, O*NET or UNSPSC | The standard a classification field draws from |
| Field | `target` | a Noun | The Noun a link points at |
| Field | `onDelete` | block, clear or cascade | What happens to linking records when the target is deleted |
| Field | `reverseLabel` | text | The heading on the target's page that lists linking records |
| Field | `takes` | people, agents or both | Who a team field can hold |
| Field | `accept` | a list of file types | The files a file field takes |
| Field | `maxMb` | a number up to 25 | A file's largest size |
| Field | `lookup` | { via, field } | Reads a field through a link |
| Field | `rollup` | { via, fn, field } | Counts or adds up over linking records |
| Verb | `does` | one sentence | What the Verb does: its button title, docs line and tool description |
| Verb | `input` | a map of input key to { says, type, required } | What the Verb asks for |
| Verb | `from` | a list of states | The states the Verb runs from |
| Verb | `to` | a state | The state the Verb leaves the record in |
| Verb | `emails` | an email field | Sends the Verb's does line to that address when it runs, from the product's own domain |
| Verb | `on` | a date field, plus or minus days | Runs the Verb by itself on that date, for each record in a from state, as the product |
| Offer | `name` | text | The plan's name on the pricing page, at checkout, in Stripe and on every invoice |
| Offer | `price` | { amount, currency, unit } | The price in whole units of the currency, billed each month or year |
| Offer | `limits` | a map of Noun to a number | The most records of each Noun one customer may hold |
| Offer | `personas` | text | Who the plan is for |
| Offer | `status` | Draft, Live or Retired | Where the Offer sells |
| Offer | `trial` | days, such as 14 days | A free trial before the first charge |
| Offer | `tax` | auto | Stripe works out, collects and reports sales tax and VAT from each customer's address |
| Field type | `text` | max, pattern | One line |
| Field type | `longText` | max, pattern, rich | Paragraphs |
| Field type | `email` | pattern | An email address, checked on save |
| Field type | `phone` | pattern | A phone number, shown as a call link |
| Field type | `url` | pattern | A web address, shown as a link |
| Field type | `number` | unit, decimals, range | A count or a measure |
| Field type | `money` | currency, decimals, range | An amount in a currency's main unit |
| Field type | `percent` | decimals, range | A rate |
| Field type | `autoNumber` | start | A number assigned on create, counting up |
| Field type | `date` | · | A day |
| Field type | `dateTime` | · | A moment, stored in UTC |
| Field type | `month` | · | A month |
| Field type | `yesNo` | · | Yes or no |
| Field type | `choice` | options | One option from a list |
| Field type | `choices` | options | Any of the options |
| Field type | `classification` | classification | A standard code |
| Field type | `link` | target, many, onDelete, reverseLabel | Another record |
| Field type | `team` | many, takes | People or agents in the customer's workspace |
| Field type | `file` | accept, maxMb, many | An uploaded file |
| Field type | `lookup` | lookup | A field read through a link |
| Field type | `rollup` | rollup | A count or sum over linked records |
