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

# MCP

Your customers' agents connect to `leaseclock.com/mcp` and get three tools: `search`, `fetch` and `do`. `do` runs any Verb, so a new Verb reaches every connected agent without a new tool.

## In short

[An agent chat renewing lease 4B through the leaseclock do tool](https://leaseclock.com/mcp)

- **Address:** `https://leaseclock.com/mcp`, over streamable HTTP. It goes live with your product, with nothing to set up.
- **Sign-in:** the agent's app opens Leaseclock's own sign-in page. Scripts send a Leaseclock token instead.
- **Tools:** `search` finds records, `fetch` reads one in full, and `do` runs a Verb.
- **Permissions:** an agent works as the person who connected it, in that person's workspace, with that person's role.
- **Brand:** the server is named `leaseclock`, and nothing an agent reads mentions SaaS.Dev.

## A renewal from Claude

Priya Shah is the leasing coordinator at Northgate Rentals, one of Leaseclock's customers. She has connected Claude to Leaseclock and asks it:

> Renew Dana Whitfield's lease in 4B through November 30, 2027.

Claude finds the lease with `search`:

```json title="search"
{ "query": "Dana Whitfield 4B" }
```

```json title="Result"
{
  "results": [
    {
      "id": "leases/4b",
      "title": "Lease 4B · dana.whitfield@example.com · ends 2026-11-30",
      "url": "https://leaseclock.com/app/leases/4b"
    }
  ]
}
```

Then it renews the lease with `do`:

```json title="do"
{
  "verb": "Lease.renew",
  "id": "4b",
  "input": { "endsOn": "2027-11-30" }
}
```

```json title="Result"
{
  "id": "leases/4b",
  "$type": "Lease",
  "unit": "4b",
  "renter": "dana.whitfield@example.com",
  "endsOn": "2027-11-30",
  "status": "active",
  "renewedBy": "priya@northgaterentals.com",
  "url": "https://leaseclock.com/app/leases/4b"
}
```

The renewal appears in Lease 4B's history in the app, under Priya's name, and anyone on her team with the right role can undo it there. Connect your own agent to [leaseclock.com](https://leaseclock.com) and try it.

## Connect an agent

Any MCP client that speaks streamable HTTP connects with the one address.

**Claude**

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

```text
https://leaseclock.com/mcp
```

**ChatGPT**

In **Settings → Apps & Connectors**, create a connector named Leaseclock with the address:

```text
https://leaseclock.com/mcp
```

`search` and `fetch` take the shapes ChatGPT expects, so Leaseclock also works in ChatGPT's deep research.

**Claude Code**

```bash title="Terminal"
claude mcp add --transport http leaseclock https://leaseclock.com/mcp
```

**Cursor**

```json title=".cursor/mcp.json"
{
  "mcpServers": {
    "leaseclock": { "url": "https://leaseclock.com/mcp" }
  }
}
```

**VS Code**

```json title=".vscode/mcp.json"
{
  "servers": {
    "leaseclock": { "type": "http", "url": "https://leaseclock.com/mcp" }
  }
}
```

### Sign in

The first tool call opens Leaseclock's sign-in page, in Leaseclock's teal. Priya signs in with her Northgate Rentals account and approves Claude. From then on Claude sees Northgate's records and nobody else's, and it can run only the Verbs her role allows.

Each connected agent is listed in the app under **Settings → Connected agents**, where Priya or a Northgate admin can disconnect it.

### Connect with a token

An agent that runs without a person, such as a nightly script or a CI job, sends a Leaseclock token in place of signing in. Create one in the app under **Settings → API keys** and keep it in the environment:

```bash title="Terminal"
claude mcp add --transport http leaseclock https://leaseclock.com/mcp \
  --header "Authorization: Bearer $LEASECLOCK_TOKEN"
```

A token carries the role it was created with. A Test token reaches the Test copy of the workspace, so an agent can try every Verb without touching a real lease.

## The tools

### search

Finds records by text, by Noun or by field. Every result carries an `id` that `fetch` and `do` accept, and a `url` to the record in the app.

```json title="search: active leases ending this year"
{
  "type": "Lease",
  "where": { "status": "active", "endsOn": { "before": "2027-01-01" } }
}
```

```json title="Result"
{
  "results": [
    {
      "id": "leases/4b",
      "title": "Lease 4B · dana.whitfield@example.com · ends 2026-11-30",
      "url": "https://leaseclock.com/app/leases/4b"
    },
    {
      "id": "leases/2a",
      "title": "Lease 2A · marcus.bell@example.com · ends 2026-12-15",
      "url": "https://leaseclock.com/app/leases/2a"
    }
  ]
}
```

| Input | Type | What it does |
| --- | --- | --- |
| `query` | string | Matches the text of any field |
| `type` | string | Limits results to one Noun, such as `Lease` |
| `where` | object | Filters on field values. An indexed field also takes `before` and `after`, as in `"endsOn": { "before": "2027-01-01" }` |
| `limit` | number | Caps the results. The default is 20 and the most is 100. |

### fetch

Reads one record in full: its fields, its linked records and the Verbs that can run on it now.

```json title="fetch"
{ "id": "leases/4b" }
```

```json title="Result"
{
  "id": "leases/4b",
  "title": "Lease 4B",
  "url": "https://leaseclock.com/app/leases/4b",
  "text": "Lease on Unit 4B, Alder Court. Renter dana.whitfield@example.com. Ends 2026-11-30. Status active.",
  "metadata": {
    "$type": "Lease",
    "unit": { "id": "units/4b", "label": "4B", "building": "Alder Court" },
    "renter": "dana.whitfield@example.com",
    "endsOn": "2026-11-30",
    "status": "active",
    "verbs": ["renew", "end"]
  }
}
```

`verbs` lists only what this record and this person allow. On Lease 5A, which has ended, the list is empty.

### do

Runs a Verb on a record. `do` also creates and updates records, through the `create` and `update` every Noun has.

| Input | Type | What it does |
| --- | --- | --- |
| `verb` | string | The Noun and Verb, such as `Lease.renew` or `Unit.create` |
| `id` | string | The record to run it on. `create` takes none. |
| `input` | object | The Verb's input, such as `{ "endsOn": "2027-11-30" }` |
| `preview` | boolean | Returns the change without making it |

With `preview`, nothing is saved:

```json title="do"
{
  "verb": "Lease.renew",
  "id": "4b",
  "input": { "endsOn": "2027-11-30" },
  "preview": true
}
```

```json title="Result"
{
  "preview": true,
  "id": "leases/4b",
  "changes": { "endsOn": { "from": "2026-11-30", "to": "2027-11-30" } }
}
```

`search` and `fetch` are marked read-only, so agent apps run them without asking. `do` is marked as a write, so most agent apps ask the person to approve each call before it runs.

## New Verbs reach every agent

The `do` tool's description lists every Verb with its input, written from your definition. Add a Verb to Lease:

```diff title="Lease.mdx"
 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 is ending, from: [active] }
```

Push it, and `Lease.remind` is in the `do` tool on the next request. Connected agents get the MCP tools-changed notice and pick it up in the same conversation, so Priya can ask Claude to remind every renter whose lease ends in December.

## Your team's agent

Your own team connects the same server from the admin. Beside your Nouns, the admin has the ones every product keeps for you: Workspace, Member, Subscription, Invoice and Changelog entry. Your team's agent searches them like any other record:

```json title="search: subscriptions that renew this week"
{
  "type": "Subscription",
  "where": { "renewsOn": { "before": "2026-11-23" } }
}
```

```json title="Result"
{
  "results": [
    {
      "id": "subscriptions/northgate-rentals",
      "title": "Northgate Rentals · Leaseclock Pro · $29.00 a month · renews 2026-11-21",
      "url": "https://leaseclock.com/admin/billing/subscriptions/northgate-rentals"
    }
  ]
}
```

## What your definition sets

Every line an agent reads comes from your files.

| In your definition | What an agent gets |
| --- | --- |
| `name: Lease` | `Lease` in search results, and `Lease.renew` and `Lease.end` in `do` |
| `does: Move the end date forward` | The description the agent reads before it calls `Lease.renew` |
| `says: the new end date` | The hint on the `endsOn` input |
| `from: [active]` | `renew` offered only on active leases, in `fetch`'s `verbs` |
| `endsOn: { type: date, indexed: true }` | `where` filters on the end date in `search` |
| `limits: { Unit: 200 }` in `pro.mdx` | `Unit.create` stops at 200 Units on Leaseclock Pro, with an upgrade link |
| `price: { amount: 29, currency: USD, unit: month }` in `pro.mdx` | Subscription records, with `renewsOn` and the amount, for your team's agent |
| Each push that customers can see | A Changelog entry record, readable by your team's agent before it is published |

Write each `does:` line for an agent as much as for a person. It is the sentence an agent uses to decide which Verb fits the request.

## Errors

A call that cannot run returns an MCP tool error with the reason, the fix and a link to the Verb's page in Leaseclock's docs. Nothing is saved. Renewing Lease 5A, which has ended:

```json title="Result"
{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "Lease 5A is ended, and renew runs only on an active Lease. To keep Unit 5A let, create a new Lease with Lease.create. https://leaseclock.com/docs/lease#renew"
    }
  ]
}
```

The agent reads the fix and tries again, or asks the person what to do. Every code is listed in [Errors](https://saas.dev/docs/errors.md).

## Questions

**Can I add my own tool?**
Add a Verb. Write its logic as the Verb's function, and `do` runs it with the same permissions, preview and undo as every other Verb.

**Which agents work with it?**
Any MCP client over streamable HTTP, including Claude, ChatGPT, Claude Code, Cursor and VS Code.

**Can an agent see another customer's records?**
No. An agent works inside the workspace of the person who connected it, and the server applies the same permissions as the app and the API.

**Can an agent change something by mistake?**
`do` runs only the Verbs the person's role allows, most agent apps ask before each `do` call, and every change is in the record's history with an undo.

**Do MCP calls count against rate limits?**
Yes. A connected agent shares the rate limits of the person or token it acts as, the same as calls to the [API](https://saas.dev/docs/api.md).
