Skip to main content
Glama

fieldtwin

CI License: Apache 2.0 Node 22+

Jobber, Housecall Pro, and QuickBooks Online, running on your laptop: real API shapes, a believable fake business, and every failure on demand. Build and test integrations and AI agents without touching a customer's account.

Watch the two-minute demo: the dashboard drives a twin, an agent's changes are listed and rewound, and a sync brings drifted books back into line.

Point your code at http://localhost:4010 instead of api.getjobber.com or quickbooks.api.intuit.com (or at http://localhost:4010/housecallpro instead of api.housecallpro.com). It gets the same paths, headers, and JSON back, from a fake trades business with customers, technicians, and months of jobs, quotes, invoices, and payments, kept in every system at once. Then make it hard: throttle QuickBooks, lose the answer to a write, deliver a webhook twice, jump the clock past every token's expiry. Afterwards, see exactly what your code changed, and undo it.

curl -X POST localhost:4010/_twin/checkpoints/before-agent           # name this moment
# ...let your integration or agent loose on http://localhost:4010...
curl -s "localhost:4010/_twin/checkpoints/before-agent/changes?source=api" | jq -r '.[].summary'
2026-07-13T11:00:00.000Z  quickbooks  CustomerCreated Id=26 "Dana Whitfield" SyncToken=0  (api)
2026-07-13T11:00:00.000Z  quickbooks  InvoiceCreated Id=29 DocNumber=1001 "Dana Whitfield" TotalAmt=185.00 Balance=185.00  (api)
2026-07-13T11:00:00.000Z  quickbooks  CustomerUpdated Id=3 "Harper Alder Jr" SyncToken=1  (api)
curl -X POST localhost:4010/_twin/checkpoints/before-agent/rewind    # and it never happened

Every run starts from a seed, so the same requests give the same results, byte for byte. A bug you hit once, you can hit again.

Why fieldtwin

If you build software for field-service and trades businesses, you know the options. Test against a client's live books and hope. Wait for vendor sandbox access, then fill an empty account by hand. Or write mocks that return exactly what you expected, and so never catch what you didn't. None of these suits an AI agent, which needs somewhere it can make mistakes many times over, in parallel.

A client's live account

Vendor sandbox

Hand-written mocks

fieldtwin

Safe to break

No

Yes

Yes

Yes

Ready to use

When the client trusts you

After a developer account, an app, and setup

After you write each endpoint

npx fieldtwin

Realistic data

Yes

Generic samples, or none

What you typed

A seeded business with months of history

Jobber, Housecall Pro, and QuickBooks describe the same business

Yes

No

No

Yes, or drifted on purpose

Back to a clean start

Never

By hand

Restart

POST /_twin/reset

429s, 5xx, lost answers, duplicate webhooks on demand

No

No

Only the ones you thought of

Yes, in the vendor's own error format

Skip ahead in time (token expiry, retries, scheduled events)

No

No

Rarely

Yes, instantly

See what your code changed, and undo it

No

No

No

Checkpoints, change lists, rewind

Offline, in CI, many copies in parallel

No

No

Yes

Yes

Exactly like production

Yes

Nearly

No

No. Modeled from the public docs, and FIDELITY.md says how closely

fieldtwin doesn't replace the vendors' sandboxes; it covers what they can't, and a final check in one before you go live is still worth doing. When you have a sandbox, fieldtwin's conformance suite records what the real API does and scores the twin against it.

Related MCP server: fetchsandbox-mcp

Who it's for

Developers building integrations for trades and field-service software. The bugs that cost you are the ones you can't make happen on purpose: a throttled API, a write whose answer never came back, a webhook that arrives twice, a token that expires halfway through a sync. fieldtwin makes them happen whenever you ask, in a business that looks real in every system, and twin time skips the waiting. The reference integration gets through more than a hundred retries and two hours of token expiry in a couple of seconds. Every run starts from a seed, so a failure replays exactly, in CI or on a teammate's laptop. You ship sooner, and you find the bad night in testing instead of in a customer's books.

Teams building AI agents that touch a business's money. An agent needs somewhere to make mistakes, many times over, and you need proof of what it actually did. Mark a checkpoint, let the agent work, and judge it on the exact list of changes it made, not on what it says it did. Then rewind and run it again. Load mismatched-books to see whether it notices the systems disagree, or flaky-apis to see whether it gives up. The dashboard shows all of it as it happens.

Agencies and integrators who sell to trades businesses. "We test against a full simulation of your Jobber or Housecall Pro and your QuickBooks before anything touches your account" is a promise you can keep, and a reason a cautious owner says yes.

The businesses themselves, though they never run it. They get syncs that don't create duplicate customers, leave invoices out of the books, or record a payment twice, and AI assistants that made their mistakes in a copy of the business instead of in theirs.

What you get

You get

Instead of

A working fake business, in Jobber, Housecall Pro, and QuickBooks at once, from one command

Developer accounts, app setup, and test data typed in by hand

Throttling, outages, lost answers, and duplicate webhooks whenever you ask

Meeting them for the first time in production

Hours of retry waits and token expiry, in seconds

Tests that sleep, or that never cover it

The same results from the same seed, every run

"Works on my machine"

An exact list of what your code or agent changed, and an undo

Guessing from logs, on an account you can never reset

Tests that run on every commit, offline, with no vendor account

Checking by hand in a sandbox before each release

fieldtwin is built by Nick George. Its page, with both demo videos, is at nickgeorgeai.com/fieldtwin; more of his work, and how to reach him, at nickgeorgeai.com.

What's modeled

QuickBooks Online

Jobber

Housecall Pro

Records

Customers, items, invoices, payments

Clients, jobs, visits, quotes, invoices, users

Customers and their addresses, employees, jobs with appointments and line items, estimates, invoices

Reading

By Id; the query language (WHERE with =, <, >, LIKE, IN, joined by AND; ORDERBY; paging; COUNT(*)); change data capture

GraphQL, with Relay pagination both ways, encoded ids, and dated API versions

REST lists with page and page_size, sorting, customer search, and the documented job and invoice filters

Writing

Customers and invoices created and updated with SyncTokens (5010 when stale), sparse or full; payments created and applied; requestid for safe retries

clientCreate

Customers created and updated, addresses added; jobs created, scheduled, and dispatched

Rate limits

500 requests a minute per company: HTTP 429

The query-cost budget: 10,000 points refilling 500 a second, THROTTLED

Not published: fault rules make 429s, with RateLimit-Reset

Webhooks

CloudEvents, signed, retried on Intuit's schedule

HMAC-signed, delivered at least once, retried

The whole record, signed with Api-Timestamp and Api-Signature, tried once

OAuth 2.0

Authorize, tokens, refresh (rolling 100 days), revoke

Authorize with PKCE, tokens, refresh tokens that rotate

Authorize, tokens (30 days), refresh tokens that rotate; API keys as Authorization: Token

Anything not listed fails clearly in the vendor's own error format rather than returning made-up data. FIDELITY.md has every behavior, how closely it matches, and the docs page it comes from.

Quick start

Requires Node 22+.

npx fieldtwin
fieldtwin: Rowntree Electric is open for business at http://localhost:4010
fieldtwin: auth is any-token: ANY Bearer token is accepted. Set FIELDTWIN_AUTH=oauth for real OAuth checks.

Open http://localhost:4010 in a browser for where everything is. Read a customer from the QuickBooks twin (by default any bearer token works; see Authentication):

curl -H "Authorization: Bearer dev" \
  http://localhost:4010/v3/company/9341000000000001/customer/1

List clients from the Jobber twin:

curl -X POST http://localhost:4010/api/graphql \
  -H "Authorization: Bearer dev" \
  -H "X-JOBBER-GRAPHQL-VERSION: 2025-04-16" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ clients(first: 5) { nodes { id name } totalCount } }"}'

List scheduled jobs from the Housecall Pro twin, with an API key (any key works):

curl -H "Authorization: Token dev" \
  "http://localhost:4010/housecallpro/jobs?work_status[]=scheduled&page_size=5"

Start it in a ready-made situation instead: npx fieldtwin flaky-apis (see Scenarios and faults). npx fieldtwin --help lists the scenarios and settings.

To work on fieldtwin itself, or run the dashboard and the worked example, clone it instead: git clone https://github.com/nickgeorgeseo/fieldtwin && cd fieldtwin && npm ci && npm start.

Using it with your code

Change only the base URL. Paths, headers, and body shapes match the real APIs.

Vendor

Real base URL

Twin base URL

QuickBooks Online

https://quickbooks.api.intuit.com

http://localhost:4010

Jobber

https://api.getjobber.com

http://localhost:4010

Housecall Pro

https://api.housecallpro.com

http://localhost:4010/housecallpro

The QuickBooks twin serves one company, realm 9341000000000001. The Housecall Pro twin serves the real paths under /housecallpro, because some of them (/api/invoices/{uuid}) start the way Jobber's do.

The QuickBooks and Jobber twins enforce the vendors' documented rate limits in twin time: Jobber answers THROTTLED when a query costs more than its budget has left, and QuickBooks answers HTTP 429 after 500 requests in a minute. Advancing the clock lets them recover; a reset starts them full. Housecall Pro doesn't publish its limits, so its twin throttles only when a fault rule says so. Faults can add more throttling on top of any of them.

Configuration

Variable

Default

What it does

FIELDTWIN_PORT

4010

The port to listen on

FIELDTWIN_HOST

127.0.0.1

The address to listen on. 0.0.0.0 reaches it from a container or another machine: only on a network you trust, since anyone who can reach the twin can control it (SECURITY.md)

FIELDTWIN_AUTH

any-token

oauth makes the vendor APIs check tokens the way the real ones do

FIELDTWIN_SEED

42

Which fake business to generate. The same seed always builds the same one

Testing on every commit

Your tests start a twin of their own and reach it the way your app does. Each test loads a fresh world, runs your code against it, and checks the change list: what your code really did, not what it logged. Twin time skips the waiting, so retries and token expiry take seconds, and every world comes from a seed, so a test that fails in CI fails the same way on your laptop.

1. Write a test. This one checks for the bug a lost answer causes. QuickBooks can accept two invoices with the same number, so code that retries a create after a timeout, without a requestid, books the same work twice:

// test/sync.test.ts in your project, with fieldtwin installed: npm install --save-dev fieldtwin
import assert from "node:assert/strict";
import { after, beforeEach, test } from "node:test";
import { startTwin } from "fieldtwin";
import { syncPaidInvoices } from "../src/sync.ts"; // your code

const running = await startTwin(); // a twin of this file's own, on a free port
after(() => running.close());

/** Something the twin recorded: what changed, in which vendor, with the whole record as that vendor's API returns it. */
interface Change {
  vendor: string;
  type: string;
  data: any;
}

/** A request to the twin's control plane, failing the test with the twin's reason if it refuses. */
async function twin(method: string, path: string) {
  const response = await fetch(`${running.url}/_twin${path}`, { method });
  if (!response.ok) throw new Error(`${method} /_twin${path}: ${await response.text()}`);
  return response.json();
}

beforeEach(async () => {
  await twin("POST", "/scenarios/flaky-apis"); // a fresh world for each test: 429s, 503s, and writes whose answer is lost
  await twin("POST", "/checkpoints/before-sync");
});

test("books each invoice once, even when QuickBooks loses the answer to a write", async () => {
  await syncPaidInvoices({ jobberUrl: running.url, quickbooksUrl: running.url });

  const changes: Change[] = await twin("GET", "/checkpoints/before-sync/changes?source=api"); // what your code did
  const booked = changes
    .filter((change) => change.vendor === "quickbooks" && change.type === "InvoiceCreated")
    .map((change) => change.data.invoice.DocNumber);

  assert.ok(booked.length > 0, "the sync booked no invoices");
  assert.equal(new Set(booked).size, booked.length, `an invoice was booked twice: ${booked.join(", ")}`);
});

It uses Node's built-in test runner, and Vitest works the same way. Jest needs its ESM mode, since fieldtwin is an ES module. Against a sync that retries without a requestid, it fails with the duplicate it found:

✖ books each invoice once, even when QuickBooks loses the answer to a write
  AssertionError [ERR_ASSERTION]: an invoice was booked twice: 41, 41

2. Run it. node --test (or npm test). There's nothing else to start: the test file starts its twin and closes it. Node runs a .ts test file as it is from 22.18 (and 23.6); on an older Node, run node --test --experimental-strip-types, or node --test finds no tests.

3. Run it on every commit. The same command runs in CI, since fieldtwin is a dev dependency like any other. If your tests aren't in Node, or you'd rather keep one twin running beside them, start it as a step of its own and wait until it answers. In GitHub Actions:

# .github/workflows/test.yml in your project
name: Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 22
      - name: Start the twin
        run: |
          nohup npx -y fieldtwin@0.5 > twin.log 2>&1 & # pin the version for repeatable runs
          timeout 60 bash -c 'until curl -sf localhost:4010/_twin > /dev/null; do sleep 1; done'
      - run: npm ci && npm test # your tests, with the twin at http://localhost:4010
      - name: Keep the twin's event log
        if: failure()
        run: curl -s localhost:4010/_twin/log.jsonl > fieldtwin-log.jsonl
      - if: failure()
        uses: actions/upload-artifact@v7
        with:
          name: fieldtwin-log
          path: fieldtwin-log.jsonl

When a run fails, its event log is kept with it: everything your code did to the twin, to read or to replay.

A few things to know:

  • Tests that share a twin share its world. Each file that calls startTwin() gets its own, so files can run in parallel; tests within a file take turns, as Node's runner and Vitest run them by default. One twin started beside the tests is shared by all of them: run the files one at a time (node --test --test-concurrency=1, Vitest's --no-file-parallelism, Jest's --runInBand).

  • startTwin() takes the settings the command does: seed, auth ("any-token" or "oauth"), scenario (a ready-made one's name or a YAML file's path), and port (a free one unless you say). It answers url, port, and close().

  • flaky-apis checks OAuth tokens for real, so your code connects through the twin's OAuth endpoints, as it will in production. To test retries alone, follow the scenario with PUT /_twin/auth and {"mode": "any-token"}.

  • For a stronger check, the reference integration's flaky test syncs through flaky-apis and checks the books end exactly as a clean run leaves them.

A worked example: Jobber to QuickBooks

examples/sync-jobber-invoice-to-quickbooks is a reference integration written like production code, and you're welcome to copy it. It books every paid Jobber invoice into QuickBooks (customer, invoice, payment), on a schedule and from Jobber's webhooks as customers pay. It handles everything the twins throw at it: 429s and 5xx with backoff, answers lost after a write, Jobber's query-cost throttle, expiring and rotating OAuth tokens, stale SyncTokens, customers spelled differently in each system, webhooks that come twice or late, and two syncs at once.

npm start                  # terminal 1
npm run example:sync       # terminal 2

Its tests show what testing against a twin buys you: the same sync under flaky-apis settles to exactly the books a clean run produces, through more than a hundred retries, and the two hours it waits for its tokens to expire take no real time.

Testing an AI agent

The twin gives an agent a whole business to work in and you a precise record of what it did. Give each test a checkpoint, let the agent work, then judge it on the change list rather than on what it says it did:

curl -X POST localhost:4010/_twin/checkpoints/before-agent
# ...run your agent against the twin...
curl "localhost:4010/_twin/checkpoints/before-agent/changes?source=api"
curl -X POST localhost:4010/_twin/checkpoints/before-agent/rewind

Load flaky-apis to see how it copes when the APIs fight back, or mismatched-books to see whether it notices that the field-service systems and QuickBooks disagree. To let a coding agent drive the twin itself, use the MCP server.

Authentication

By default the twin accepts any bearer token. That keeps the quick start to one command, and it is the wrong setting for testing your OAuth code. To make the twin check tokens the way the real APIs do, start it with FIELDTWIN_AUTH=oauth npx fieldtwin, put auth: oauth in a scenario, or switch a running twin: curl -X PUT localhost:4010/_twin/auth -H "Content-Type: application/json" -d '{"mode":"oauth"}'.

Each vendor has a twin OAuth 2.0 server at the vendor's own paths (under /housecallpro for Housecall Pro), so your OAuth client only needs new URLs:

Endpoint

Real URL

Twin URL

QuickBooks authorize

https://appcenter.intuit.com/connect/oauth2

http://localhost:4010/connect/oauth2

QuickBooks tokens

https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer

http://localhost:4010/oauth2/v1/tokens/bearer

QuickBooks revoke

https://developer.api.intuit.com/v2/oauth2/tokens/revoke

http://localhost:4010/v2/oauth2/tokens/revoke

Jobber authorize

https://api.getjobber.com/api/oauth/authorize

http://localhost:4010/api/oauth/authorize

Jobber tokens

https://api.getjobber.com/api/oauth/token

http://localhost:4010/api/oauth/token

Housecall Pro authorize

https://pro.housecallpro.com/oauth/authorize

http://localhost:4010/housecallpro/oauth/authorize

Housecall Pro tokens

https://api.housecallpro.com/oauth/token

http://localhost:4010/housecallpro/oauth/token

The twin approves every authorization request at once (there is no consent page) and accepts any client_id and client_secret. Everything after that follows the vendors' docs: codes work once, access tokens last 60 minutes (30 days for Housecall Pro), Jobber and Housecall Pro rotate refresh tokens and reject the old one, QuickBooks keeps the refresh token on a rolling 100 days. Tokens expire on twin time, so POST /_twin/clock/advance {"minutes": 60} tests your refresh path in a second. (Jobber's access tokens are JWTs whose exp is in twin time too, so compare it with the twin's clock, not your own.)

A Pro's own Housecall Pro integration uses an API key instead, sent as Authorization: Token <key>. The twin can't know which keys a Pro made, so it takes any key, in either auth mode.

Getting a Jobber token from the command line:

CODE=$(curl -s -o /dev/null -w '%{redirect_url}' \
  "http://localhost:4010/api/oauth/authorize?response_type=code&client_id=my-app&redirect_uri=http://localhost:3000/callback" \
  | sed -E 's/.*code=([^&]+).*/\1/')
curl -s -X POST http://localhost:4010/api/oauth/token \
  -d client_id=my-app -d client_secret=dev -d grant_type=authorization_code \
  -d code=$CODE -d redirect_uri=http://localhost:3000/callback

Webhooks

Every twin sends webhooks the way its vendor does. Each vendor sets them up per app (in a developer portal, or in a Pro's own settings for Housecall Pro); the twin's equivalent is the control plane (or the twin_set_webhooks MCP tool):

curl -X PUT localhost:4010/_twin/webhooks -H "Content-Type: application/json" -d '[
  {"vendor":"jobber","url":"http://localhost:3000/webhooks/jobber","topics":["INVOICE_UPDATE"],"clientId":"my-app","clientSecret":"dev"},
  {"vendor":"quickbooks","url":"http://localhost:3000/webhooks/qbo","topics":["qbo.payment.created.v1"],"clientId":"my-app","clientSecret":"my-verifier-token"},
  {"vendor":"housecallpro","url":"http://localhost:3000/webhooks/hcp","topics":["invoice.paid"],"clientId":"my-app","clientSecret":"my-signing-secret"}
]'
curl -X POST localhost:4010/_twin/actions/jobber/payInvoices -H "Content-Type: application/json" -d '2'
curl -X POST localhost:4010/_twin/actions/housecallpro/payInvoices -H "Content-Type: application/json" -d '2'

Jobber deliveries carry Jobber's documented payload ({ "data": { "webHookEvent": { "topic", "appId", "accountId", "itemId", "occurredAt" } } }) and an X-Jobber-Hmac-SHA256 signature made with the subscription's client secret, so your verification code runs unchanged. Like Jobber, the twin delivers at least once: paying an invoice fires INVOICE_UPDATE twice, and a delivery your app doesn't accept within a second is sent again later in twin time. CLIENT_CREATE and INVOICE_UPDATE are modeled so far.

QuickBooks deliveries are CloudEvents, the format Intuit sends today: a JSON array of events like { "specversion": "1.0", "type": "qbo.invoice.updated.v1", "intuitentityid": "130", "intuitaccountid": "9341000000000001", ... }, signed in intuit-signature with the subscription's clientSecret standing in for your app's verifier token. Customers, invoices, and payments created or updated through the API are announced (qbo.customer.created.v1, qbo.customer.updated.v1, qbo.invoice.created.v1, qbo.invoice.updated.v1, qbo.payment.created.v1), and a payment also announces each invoice it pays. Your app has 3 seconds to answer HTTP 200; otherwise the twin tries again on Intuit's schedule, from 10 seconds up to every 6 hours of twin time.

Housecall Pro deliveries carry the documented shared fields (event, company_id, event_occurred_at, event_created_at) and the whole record, as the event left it: { "event": "job.created", ..., "job": { ... } }. Api-Timestamp and Api-Signature follow the docs' verification steps, an HMAC-SHA256 of the timestamp, a dot, and the payload, keyed with the subscription's clientSecret as your signing secret. customer.created, customer.updated, job.created, job.scheduled, job.completed, job.paid, invoice.created, invoice.sent, invoice.payment.succeeded, and invoice.paid are modeled so far. Jobs get finished, invoiced, and paid by the completeJobs, invoiceJobs, and payInvoices actions, in a scenario's timeline or through the control plane: invoicing a job sends invoice.created and then invoice.sent, and paying an invoice sends invoice.payment.succeeded, invoice.paid, and job.paid, in that order. The docs promise no retries, so the twin tries each delivery once.

Each URL gets its deliveries one at a time, in order; an app that is slow to answer doesn't hold up deliveries to another. GET /_twin/webhooks shows every recent delivery and how your app answered it.

Fault rules with a webhook topic (INVOICE_UPDATE, qbo.invoice.updated.v1, job.created, or "*") make deliveries misbehave instead of requests: sent twice, or late enough that later ones overtake them. Faulted deliveries carry X-Twin-Fault.

faults:
  - name: jobber-webhooks-twice
    vendor: jobber
    webhook: INVOICE_UPDATE
    rate: 0.3
    effect: { duplicate: true }
  - name: jobber-webhooks-late
    vendor: jobber
    webhook: "*"
    rate: 0.2
    effect: { delayMs: 300000 }   # five minutes of twin time late

Scenarios and faults

A scenario is a YAML file: seed, start time, world size, faults, and what happens as time passes. Start the twin with a ready-made one by name, or with your own file:

npx fieldtwin heat-wave-monday
npx fieldtwin my-scenario.yaml

Scenario

What it tests

heat-wave-monday

A busy HVAC Monday: 120 customers, throttled QuickBooks writes, a slow Jobber, and new clients calling in all morning

mismatched-books

The field-service systems (Jobber and Housecall Pro) and QuickBooks disagree: customers spelled differently (same email), customers only in the field with unsynced work, customers only in QuickBooks, and a customer QuickBooks has as inactive who has paid for new work since

flaky-apis

Retry testing: real OAuth checks plus layered faults (QuickBooks 429s, 503s, and writes whose answer is lost; Jobber 429s, 502s, and webhooks sent twice or late; Housecall Pro 429s with RateLimit-Reset, 502s, writes whose answer is lost, and webhooks sent late) while customers keep paying

A fault rule matches requests by vendor, method, and path (pathIncludes, and pathExcludes to leave some alone, like a vendor's OAuth endpoints), and fires at a rate:

faults:
  - name: qbo-throttled-writes
    vendor: quickbooks
    method: POST
    rate: 0.2            # one in five matching requests
    effect: { status: 429, message: "ThrottleExceeded" }

Injected failures come back in the vendor's own error format, with the headers it sends (a Housecall Pro 429 has RateLimit-Reset, in twin time), and carry an X-Twin-Fault header, so you can tell them apart from real bugs. An effect can also slow a request down (latencyMs), or lose its answer: with lostResponse: true the twin does the work first and then answers with the error anyway, the way a gateway timeout hides a write that succeeded. That is the failure a retried write has to survive without happening twice (for QuickBooks, send a requestid):

  - name: qbo-lost-answers
    vendor: quickbooks
    method: POST
    rate: 0.1
    effect: { status: 504, lostResponse: true }

A scenario can also schedule what happens as twin time passes. Advancing the clock runs each entry at its own time, recorded with source scenario:

timeline:
  - at: "08:00"                  # business time; also "+90m", "+2h", or "2026-07-13T15:00:00Z"
    jobber: { newClients: 12 }   # new clients call in
  - at: "13:00"
    jobber: { payInvoices: 4 }   # the longest-open invoices get paid
  - at: "16:00"
    housecallpro: { completeJobs: 2, invoiceJobs: 2, payInvoices: 1 }   # techs finish work, the Pro invoices it, a customer pays

Housecall Pro has newCustomers too, like Jobber's newClients. A job is finished only once all of its visits have begun, so completeJobs never marks work done before its time. Finishing a job doesn't invoice it: invoiceJobs invoices finished work, the work finished longest ago first, and from then on payInvoices can pay it.

A scenario with a typo, an unknown key, or a time without a UTC offset is refused when it loads, with the reason, and the twin stays as it was.

The control plane

Everything under /_twin controls the fake world rather than imitating a vendor.

Request

What it does

GET /_twin

Company, seed, loaded scenario, twin time, event count, auth mode, active faults, webhook subscriptions, checkpoints, what's scheduled next

POST /_twin/reset

Back to the start: the seeded world (the loaded scenario's, if any), with its timeline and faults

POST /_twin/clock/advance {"minutes": 60}

Move time forward (rate limits refill, tokens expire, scheduled events and their webhooks happen)

POST /_twin/actions/:vendor/:action (body: params)

Make something happen now, as if done in the vendor's own app: jobber/payInvoices with 2, jobber/newClients with 3, housecallpro/invoiceJobs with 1

POST /_twin/checkpoints/:name

Name this moment

GET /_twin/checkpoints/:name/changes?source=api

What your code changed since then (or seed, scenario, control; everything without source): each event, with a one-line summary. Like the log, it takes after= to read on from an earlier read

POST /_twin/checkpoints/:name/rewind

Undo everything after the checkpoint. The same requests then meet the same faults

PUT /_twin/faults

Replace the active fault rules

PUT /_twin/auth {"mode": "oauth"}

Check bearer tokens for real (oauth) or accept any (any-token)

GET /_twin/scenarios

The ready-made scenarios and what each one tests

POST /_twin/scenarios/:name

Load a ready-made scenario

POST /_twin/scenario (YAML body)

Load your own scenario

GET /_twin/log.jsonl?since=:checkpoint

The event log, one event per line (all of it without since). With the seed, it is everything a bug report needs. Its X-Twin-Log-Position header says where the log ends

GET /_twin/log.jsonl?after=:position

Only what was recorded after a position an earlier read gave (or after an event's seq): how to follow the log as it grows. A 409 means the log has changed in between (a reset, a rewind, a scenario, or a restarted twin): read it again from the start

PUT /_twin/webhooks

Replace the webhook subscriptions (see Webhooks)

GET /_twin/webhooks

The subscriptions, and each recent delivery with how your app answered

The dashboard

dashboard/ is a web dashboard for a running twin: everything in the control plane from a browser, the event log as it grows, and QuickBooks side by side with Jobber or Housecall Pro, every customer and invoice marked as matching or drifted.

npm start                                   # terminal 1: the twin
cd dashboard && npm ci && npm run dev       # terminal 2: http://localhost:4020

It is a Next.js package of its own and a client of the twin like any other: it uses only /_twin, so watching your code spends no rate limit and draws no fault.

Using it from a coding agent (MCP)

npx fieldtwin mcp starts the twin and an MCP server over stdio (npm run --silent mcp in a clone, so npm's own lines stay off stdout). Its tools mirror the control plane:

Tool

What it does

twin_status

Company, seed, scenario, twin time, auth mode, active faults, checkpoints, what's scheduled next

twin_reset

Back to the start of the loaded scenario (or the default world)

twin_advance_clock

Move time forward; scheduled scenario events happen on the way

twin_checkpoint, twin_changes, twin_rewind

Mark a moment, list what changed since (optionally only your API calls), undo it

twin_list_scenarios, twin_load_scenario

Pick a ready-made scenario by name, or load your own YAML

twin_set_faults

Make the APIs misbehave on purpose

twin_set_auth

Accept any token, or check OAuth tokens for real

twin_export_log

The event log as JSON Lines, all of it or since a checkpoint

twin_run_action

Make something happen now: customers pay, clients call in, techs finish jobs, finished work is invoiced

twin_set_webhooks, twin_webhooks

Subscribe your app to webhooks; see what was delivered and how it answered

Claude Code:

claude mcp add fieldtwin -- npx -y fieldtwin mcp

Other MCP clients (Cursor, Claude Desktop, VS Code) take the same command in their JSON config:

{ "mcpServers": { "fieldtwin": { "command": "npx", "args": ["-y", "fieldtwin", "mcp"] } } }

It is in the official MCP Registry as io.github.nickgeorgeseo/fieldtwin, so clients and directories that read the registry install it with the same command and its settings.

Add a scenario's name after mcp to start in it. The MCP server starts a twin of its own, on port 4010 unless FIELDTWIN_PORT says otherwise, so stop any other twin there first (claude mcp add -e FIELDTWIN_PORT=4011 ... picks another port). It exits when the agent closes the session.

The MCP tools only control the test world. Your agent still calls the vendor APIs over HTTP, the same way production code will.

When something looks wrong

You see

What it means

fieldtwin: port 4010 is already in use

Another twin (or something else) has the port. FIELDTWIN_PORT=4011 npx fieldtwin

HTTP 401 from any vendor

The twin is checking tokens (auth is oauth, from FIELDTWIN_AUTH, a scenario, or PUT /_twin/auth). Get a token from the twin's OAuth endpoints, or switch back to any-token. Housecall Pro also answers 401 for an Authorization header not written exactly Token <key> or Bearer <token>

Jobber THROTTLED on every try

The query asks for more than the whole 10,000-point budget, so it can never run. Ask for fewer nodes with first or last

QuickBooks 5010 Stale Object

Your update sent an old SyncToken, as the real API would refuse too. Read the record again and retry

An X-Twin-Fault header

A fault rule fired, on purpose. GET /_twin lists the active ones

A 500 that mentions a bug in fieldtwin

Please open an issue with the twin's log and GET /_twin/log.jsonl

Project layout

src/
  core/        the twin runtime: event log, clock, seeded randomness, OAuth server, vendor contract
  world/       the vendor-neutral fake business: customers, price list, months of work history, drift
  vendors/
    quickbooks/  one folder per vendor, all with the same shape
    jobber/
    housecallpro/
  faults/      makes vendor APIs misbehave on purpose
  control/     /_twin HTTP routes, MCP tools, scenarios (one shared operations.ts)
  app.ts       wires it together
  start.ts     startTwin(): a twin on its own port, from code
  index.ts     what `import { startTwin } from "fieldtwin"` gets
  cli.ts       the fieldtwin command
  server.ts    entry point for `npm start`
  mcp.ts       entry point for `npm run mcp` and `fieldtwin mcp`
scenarios/     ready-made situations (heat-wave-monday, mismatched-books, flaky-apis)
examples/      code that uses the twins the way yours will: over HTTP, never importing src/
  sync-jobber-invoice-to-quickbooks/   the reference integration, its demo, and its tests
conformance/   the same scripted sessions against a real sandbox and the twin, compared and scored
dashboard/     a web dashboard for a running twin (Next.js, a package of its own), over HTTP like examples/
test/          tests by vendor and area, plus core and the architecture guards

ARCHITECTURE.md explains how it fits together and the three rules that keep it easy to change. Read it before changing anything in core/.

Contributing

Contributions are welcome, especially ones that make the twins more faithful. CONTRIBUTING.md walks through adding an endpoint, from the vendor's docs page to the test to the FIDELITY.md row; every vendor folder has the same shape, so once you've read one you can read them all. If you have a QuickBooks sandbox company, a Jobber testing account, or a Housecall Pro account on the MAX or XL plan that you keep for testing, the most valuable thing you can do is record the conformance suite against it: npm run conformance:record turns the twin's guesses into evidence. Check first that the vendor's API terms allow it (links to each). Build only from public documentation; never paste vendor code, private docs, or real customer data.

Where it's going: more Jobber writes as their inputs reach the public docs, recordings from real sandboxes, more of Housecall Pro, then more vendors (Samsara or Motive, EDI trading partners) and a benchmark for dispatch and operations agents. ARCHITECTURE.md has the roadmap.

Disclaimer and license

fieldtwin is an independent project. It is not affiliated with, endorsed by, or supported by Jobber, Intuit, or Housecall Pro. Jobber, QuickBooks, and Housecall Pro are trademarks of their respective owners.

Copyright 2026 Nick George. Licensed under the Apache License 2.0.

Available Tools

14 tools
twin_advance_clockA

Move twin time forward, up to ten years. 0 sends the webhook deliveries already due.

NameRequiredDescriptionDefault
minutesYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden. It does disclose a real side effect — advancing time can fire webhook deliveries, and 0 flushes those already due — which is genuinely beyond the schema. But it doesn't say what else advancing time triggers (scheduled actions, faults), whether the move is reversible, or what the call returns.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with zero waste; the primary action is front-loaded and the special-case behavior for 0 follows immediately.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter tool with no annotations or output schema, the description covers the essential action, the cap, and the 0 case, but omits what state changes result from advancing time and what the caller gets back, which an agent needs to chain it with twin_status or twin_changes.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the single parameter 'minutes' has no description, so the description must compensate. It adds the ten-year cap and the special meaning of 0, but never states the unit and the schema records no maximum, leaving ambiguity about the ten-year ceiling.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource: moving the twin's simulated clock forward, with an explicit bound (ten years). 'Forward' implicitly contrasts with the sibling twin_rewind, but no sibling is named, so the differentiator is inferred rather than stated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is implied: advance simulated time to trigger time-dependent behavior, and pass 0 to flush due webhook deliveries. There is no explicit guidance on when to prefer twin_rewind, twin_reset, or twin_run_action, nor any prerequisites or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_changesC

List what changed since a checkpoint. Set onlyFromApi to see only changes your code caused.

NameRequiredDescriptionDefault
checkpointYes
onlyFromApiNo

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations and no output schema, the description carries the full behavioral burden and largely does not meet it: it never says what a change entry looks like, whether results are paginated or ordered, or what happens if the checkpoint is unknown/expired. It does add real value by explaining that onlyFromApi restricts results to changes caused by the caller's code, but that is a single behavioral fact for a tool with zero structured coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with no waste, and the core purpose is front-loaded. The second sentence is efficient, though it spends the only elaboration on the optional parameter while the required one goes unaddressed.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read tool with no annotations, no output schema, and 0% schema coverage, the description should at minimum explain where a checkpoint comes from and what the returned changes describe. Neither is present, leaving an agent unable to call it confidently without inspecting sibling tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must define both parameters; it covers only onlyFromApi ("only changes your code caused"), which is genuinely useful beyond the boolean name. The required checkpoint parameter is left entirely undefined — no indication of whether it is an id, name, or token, or that it comes from twin_checkpoint.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

"List what changed since a checkpoint" gives a specific verb (List) plus resource (changes) and a scoping condition (since a checkpoint), which separates it from siblings like twin_checkpoint (creates), twin_rewind (reverts) and twin_export_log (exports). It stops short of naming a sibling or an explicit boundary, so it is clear but not fully differentiated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase "since a checkpoint" implies the workflow (call twin_checkpoint first, then diff), but this is never stated as a prerequisite, nor are any when-not conditions or alternatives given. The only concrete guidance is the conditional hint for onlyFromApi.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_checkpointB

Name the current moment so you can list changes since it or rewind to it.

NameRequiredDescriptionDefault
nameYes

TDQS

B3/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full disclosure burden for a mutating tool. It does not say whether reusing a name overwrites an existing checkpoint, whether names must be unique, whether checkpoints survive twin_reset or scenario loads, or what the call returns — all material for an agent deciding how to name and rely on checkpoints.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence that states the action and then its payoff. No filler, no repetition of the tool title, and the reason to call it appears before any detail.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For an unannotated state-mutating tool with no output schema and an undocumented parameter, the description leaves too much open: naming/overwrite rules, return value (checkpoint id vs the passed name), and interaction with twin_reset and twin_load_scenario are all unaddressed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With one parameter and 0% schema description coverage, the description must carry the semantics. It only implies that 'name' is an arbitrary label for the current moment; it gives no constraints on format, uniqueness, or collision behavior, so the agent is guessing how to construct a safe value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb and resource: it labels the current point in simulated state, and the second clause tells the agent what the label enables (diffing changes, rewinding). It is clearly distinct from siblings like twin_changes or twin_rewind, though it never names them, so differentiation is only implicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'so you can list changes since it or rewind to it' implies the use case (mark a point before making changes you may want to inspect or undo). However it never states when not to use it, whether checkpoints should be created before mutating actions like twin_run_action, or which sibling to call next by name.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_export_logB

The event log as JSON Lines, one event per line: everything, or only what happened since a checkpoint.

NameRequiredDescriptionDefault
checkpointNo

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It discloses the output format (JSON Lines, one event per line) and the checkpoint filtering behavior, but it does not state that the operation is read-only, whether it has side effects, or what permissions or limits apply.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single efficient sentence with no wasted words. The key information—export format, line structure, and optional checkpoint filtering—is front-loaded and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter export tool with no output schema, the description covers the output format and checkpoint filtering adequately. However, with no annotations and no schema descriptions, it leaves gaps around checkpoint source/format and how this tool relates to sibling tools such as twin_checkpoint.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for the single checkpoint parameter. It adds meaningful semantics by explaining that the checkpoint restricts output to events since that checkpoint, but it does not explain the expected checkpoint format or where to obtain one.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool returns the event log as JSON Lines, one event per line, and distinguishes between full and checkpoint-filtered output. It is specific about the resource and format, though it does not explicitly contrast itself with sibling tools like twin_changes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies that the optional checkpoint is used to retrieve only events since that point, but it gives no guidance on when to use this tool versus alternatives such as twin_changes or twin_checkpoint. There are no explicit when-to-use, when-not-to-use, or alternative-selection cues.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_list_scenariosB

The ready-made scenarios, and what each one tests.

NameRequiredDescriptionDefault

No parameters

TDQS

B3.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry the behavioral burden; it implies a read-only enumeration and says the response includes what each scenario tests, but it never states read-only-ness, ordering, or whether the list is static or config-dependent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single short sentence with no wasted words and the key information (scenarios plus what they test) front-loaded, though it borders on under-specified rather than tight.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter listing tool with no output schema, the description should say more about the return shape (names? IDs mapped to descriptions?) since nothing else documents it; 'what each one tests' only partly fills that gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate; the baseline for a no-arg tool applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The noun phrase identifies the resource (ready-made scenarios) and hints at the returned content (what each one tests), but it omits an explicit verb and never distinguishes itself from the sibling twin_load_scenario, which also deals with scenarios.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no statement of when to call this versus twin_load_scenario or twin_status, and no mention that you would call it before loading a scenario to discover valid names. Usage must be inferred entirely.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_load_scenarioA

Rebuild the world from a scenario: a ready-made one by name (see twin_list_scenarios), or your own YAML (seed, startTime, world, faults, auth, timeline).

NameRequiredDescriptionDefault
nameNo
yamlNo

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden. 'Rebuild the world' signals that this is a mutating, likely overwriting operation, but it does not state what happens to existing state, whether the rebuild is reversible, what permissions are required, or what errors occur for invalid YAML.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The definition is a single, front-loaded sentence that states the action first and then explains the two input modes. There is no redundant or filler text.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-parameter load tool with no output schema and no annotations, the description covers the essential input modes and points to twin_list_scenarios for scenario discovery. It could be more complete by clarifying the destructive effect on existing state and the expected result or status after loading.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It meaningfully maps 'name' to a ready-made scenario and 'yaml' to a custom scenario definition, and it enumerates the YAML fields (seed, startTime, world, faults, auth, timeline), adding substantial meaning beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Rebuild') and resource ('the world from a scenario'), and clearly distinguishes two source modes: ready-made by name or custom YAML. It also references the sibling tool twin_list_scenarios for discovering scenario names, so an agent can identify the tool's role without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It tells the agent that a scenario can come from a named ready-made scenario (pointing to twin_list_scenarios) or from custom YAML, and it lists the YAML fields. This gives clear usage context, though it does not explicitly state when not to use this tool or whether name and yaml are mutually exclusive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_resetB

Back to the start: the seeded world (the loaded scenario's, if any), with its timeline and faults.

NameRequiredDescriptionDefault

No parameters

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral disclosure burden. It usefully states that the reset restores the seeded world along with its timeline and faults, but it does not explicitly warn that current changes are discarded, nor does it describe permissions, irreversibility, or return behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single front-loaded sentence with no wasted clauses. It is somewhat dense and parenthetical, but appropriately concise for a zero-parameter action.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter reset tool with no output schema and no annotations, the description identifies the reset target and scope. It remains incomplete on destructive impact, return behavior, and sibling differentiation, making it minimally viable rather than fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is no parameter-level semantics to explain. The baseline for a parameterless tool applies, and the description introduces no parameter confusion.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific effect: returning to the seeded start state, including the loaded scenario's timeline and faults. It is clearer than a generic 'reset' and implies initial-state restoration, though it does not explicitly distinguish itself from sibling tools such as twin_rewind or twin_load_scenario.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit guidance on when to use this tool versus alternatives like twin_rewind, twin_checkpoint, or twin_load_scenario. The phrase 'Back to the start' only weakly implies the use case and provides no exclusions or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_rewindC

Undo everything after a checkpoint.

NameRequiredDescriptionDefault
checkpointYes

TDQS

C2.4/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the entire behavioral burden. 'Undo everything' hints at a destructive mutation but never states reversibility, whether the checkpoint itself survives, whether undo can be undone, or what permissions/state are required — all critical for a rollback operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One short sentence, front-loaded with the verb and no filler. It is efficient, though its brevity reflects under-specification rather than disciplined economy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a destructive rollback tool with no annotations, no output schema, and a completely undocumented parameter, this description is far too thin. An agent cannot predict the blast radius of the call or verify success from the definition alone.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% for the single required `checkpoint` param, so the description must explain it. 'After a checkpoint' only implies the param identifies a restore point; it never says whether it's a name, an ID emitted by twin_checkpoint, or a timestamp — leaving the caller unable to populate it confidently.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

"Undo everything after a checkpoint" names a verb (undo) and a scope (post-checkpoint state), so the core action is graspable. But it doesn't say what 'everything' covers — simulated time, data mutations, logs — nor how it differs from the sibling twin_reset, leaving real ambiguity for an agent choosing between two apparently similar rewinds.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no when-to-use guidance at all: no statement of prerequisites, no indication of when to prefer twin_rewind over twin_reset or twin_checkpoint, and no hint about what state the checkpoint must be in. The agent must infer selection criteria from sibling names alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_run_actionB

Make something happen now, as if someone did it in the vendor's own app: jobber newClients or payInvoices; housecallpro newCustomers, completeJobs, invoiceJobs, or payInvoices (quickbooks has none). Each takes params: how many. Webhooks for it go out before this answers.

NameRequiredDescriptionDefault
actionYes
paramsYes
vendorYes

TDQS

B3.1/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It usefully discloses that webhooks are emitted before the response, but does not describe permissions, idempotency, reversibility, error behavior, or what side effects occur beyond webhook ordering for these write-like vendor actions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single dense paragraph that front-loads the core purpose and then provides vendor/action specifics. It is efficient and mostly well-structured, with only slight run-on phrasing in the action list.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers enough to attempt invocation: purpose, vendor/action values, params meaning, and webhook timing. However, for a no-annotation action tool, it omits return behavior, failure modes, and permission requirements, leaving important context gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It does so by listing valid vendors, valid actions per vendor, and clarifying that params indicates 'how many,' though it still leaves the exact params object shape ambiguous.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states that it simulates vendor actions ('as if someone did it in the vendor's own app') and enumerates concrete actions for jobber and housecallpro. It is clear what the tool does, though it does not explicitly contrast itself with sibling tools like twin_advance_clock or twin_set_faults.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description lists available vendor/action combinations but gives no guidance on when to use this tool versus alternatives, nor any prerequisites or exclusions. An agent must infer usage context from the action list alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_set_authB

How the vendor APIs treat bearer tokens: "any-token" accepts anything; "oauth" accepts only unexpired tokens from the twin's OAuth endpoints.

NameRequiredDescriptionDefault
modeYes

TDQS

B3.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It usefully discloses that any-token accepts anything while oauth accepts only unexpired tokens from the twin's OAuth endpoints, but it omits whether the setting is persistent, what the default is, whether it affects existing tokens, and whether it survives reset/checkpoint operations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single compact sentence with no filler, and it maps directly onto the enum values. However, it front-loads a definition of token treatment rather than the action being configured, so it is concise but structurally not quite action-oriented.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

This is a low-complexity tool with one enum parameter and no output schema, and the core mode choice is explained. Still, the operational context is incomplete because the description does not clarify persistence, when the setting takes effect, or how it interacts with the other twin control tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for the otherwise bare enum. It does so by defining both allowed values precisely: any-token accepts anything, and oauth accepts only unexpired tokens from the twin's OAuth endpoints. It does not mention that mode is required or state a default, but the value meanings are well covered.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description defines what each bearer-token mode means, but it never explicitly states the action, e.g. that it sets or configures the twin's authentication mode; the verb is supplied only by the tool name. The two modes are clearly distinguished, so an agent can infer the purpose, but the description itself is more definition than action statement.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no guidance on when to choose any-token versus oauth, whether this should be set before running actions, or how it relates to sibling tools like twin_run_action or twin_load_scenario. The description provides mode semantics but no usage context or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_set_faultsA

Replace the active fault rules. Each rule: { name, vendor: quickbooks|jobber|housecallpro, method?, pathIncludes?, pathExcludes?, rate: 0-1, effect: { status?: HTTP error status, latencyMs?, message?, lostResponse?: do the work, then answer with status } }. A rule with webhook: TOPIC or "*" (and no method, pathIncludes, or pathExcludes) affects webhook deliveries instead: effect { duplicate?: true, delayMs? }. An empty list turns faults off.

NameRequiredDescriptionDefault
rulesYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden: it does convey replace-vs-append semantics and the empty-list reset, and it explains lostResponse ("do the work, then answer with status"). It omits scope (global vs. per-scenario/per-vendor), persistence across twin_reset or twin_rewind, and whether existing rules are lost silently or validated.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action, then escalating detail; nearly every clause carries information. The inline parenthetical gloss on lostResponse is slightly awkward but saves a sentence, and no content is padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single opaque parameter with no annotations and no output schema, the description covers the input contract well. It does not address what the call returns, whether it validates atomically, or how these webhook-delivery faults relate to twin_set_webhooks — a real ambiguity given the sibling list.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% and items are untyped (`items: {}`), so the description is the only documentation — and it delivers the rule shape, the vendor enum, the effect fields, and the special webhook rule form. It leaves some ambiguity: whether `rate` is a probability, per-request selection, and whether pathIncludes/pathExcludes are globs or regexes.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

"Replace the active fault rules" gives a specific verb and resource, and the following sentences make the fault-injection domain unmistakable. It does not name or contrast any sibling (e.g. twin_set_webhooks, which also touches webhook behavior), so the agent must infer the boundary itself.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"An empty list turns faults off" is the only operational hint, and it is genuinely useful. There is no statement of when to reach for this tool versus twin_reset / twin_set_webhooks, and no prerequisites (e.g. does a scenario need to be loaded first?).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_set_webhooksA

Replace the webhook subscriptions, as an app would set them up in the vendor's developer settings. Each: { vendor: quickbooks | jobber | housecallpro, url, topics, clientId, clientSecret (signs deliveries: for Jobber the app's OAuth client secret, for QuickBooks its verifier token, for Housecall Pro its signing secret) }. quickbooks topics: qbo.customer.created.v1, qbo.customer.updated.v1, qbo.invoice.created.v1, qbo.invoice.updated.v1, qbo.payment.created.v1. jobber topics: CLIENT_CREATE, INVOICE_UPDATE. housecallpro topics: customer.created, customer.updated, job.created, job.scheduled, job.completed, job.paid, invoice.created, invoice.sent, invoice.payment.succeeded, invoice.paid.

NameRequiredDescriptionDefault
subscriptionsYes

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It usefully discloses that clientSecret signs deliveries and explains the vendor-specific secret meaning, plus the replace semantics, but it does not explicitly state that existing subscriptions are overwritten/removed or describe auth/error behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the replace operation and then provides necessary nested detail. The long per-vendor topic lists are justified because the schema has no enum descriptions, though the length could be better structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter mutation tool with no annotations, no output schema, and a blank item schema, the description supplies enough detail to invoke it correctly. It is slightly incomplete on destructive overwrite behavior and auth prerequisites, but the core configuration contract is clear.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate, and it does: it documents the subscription item shape, the vendor enum, topic lists for each vendor, and the vendor-specific clientSecret meaning. Minor gaps remain around required/optional fields and URL constraints, but the parameter meaning is substantially clarified.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (Replace) and resource (webhook subscriptions), with vendor-specific scope and app-like setup semantics. An agent can distinguish this from the sibling twin_webhooks reader by the explicit 'Replace' framing.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies this is for configuring webhook subscriptions, but it never explicitly says when to use it instead of twin_webhooks or what prerequisites (such as auth setup) are required. Usage is inferable but not clearly guided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_statusC

Company, seed, scenario, twin time, event count, auth mode, faults, checkpoints, and what is scheduled next.

NameRequiredDescriptionDefault

No parameters

TDQS

C2.9/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must carry the full behavioral burden, yet it only enumerates return fields. It does not explicitly state that the operation is read-only/non-mutating, whether it requires auth, or whether calling it has side effects.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single short fragment with no filler, but its list structure is not front-loaded with a clear action. It is appropriately sized for a status tool, though it would read better as a sentence.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Because there is no output schema and no annotations, the description is the only source of return-value and behavioral context. It names several useful fields but leaves their formats, freshness, and read-only nature unexplained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With zero input parameters, the baseline is 4; there are no parameter semantics to clarify. The field list describes return content rather than inputs, so it adds some value without needing to compensate for schema gaps.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description is a noun-phrase list of reported fields with no verb or resource statement; an agent must infer from the name that it retrieves twin status. It is more specific than a tautology but does not explicitly say what the tool does or how it differs from siblings like twin_changes or twin_webhooks.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no when-to-use guidance, no alternatives named, and no prerequisites. The description neither routes the agent away from sibling status/log tools nor explains when a status snapshot is appropriate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

twin_webhooksC

The webhook subscriptions, and the latest deliveries with how each went.

NameRequiredDescriptionDefault

No parameters

TDQS

C2.7/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must carry the behavioral burden. It only states the content shown and does not clarify that this is a read-only operation, whether it has side effects, or what permissions are required.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is short and wastes no words, but it is a sentence fragment and its phrasing is slightly awkward. It is concise without being clearly structured around a specific action.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter tool with no output schema and no annotations, the description should clarify the return contents and whether the call is read-only. It mentions subscriptions and latest delivery outcomes, but leaves key operational context unspecified.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so parameter semantics are inherently simple and the schema has no gaps to compensate for. The description does not need to explain arguments.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names the webhook subscriptions and latest deliveries, so the resource is identifiable, but it is a noun phrase without a clear verb such as get, list, or inspect. It distinguishes itself from twin_set_webhooks only by implication, not explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no explicit guidance on when to use this tool, when not to use it, or which sibling to choose instead. The read-versus-configure distinction with twin_set_webhooks is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 14 tool updatesv0.5.2
    • First observedtwin_advance_clock
    • First observedtwin_changes
    • First observedtwin_checkpoint
    • First observedtwin_export_log
    • First observedtwin_list_scenarios
    • First observedtwin_load_scenario
    • First observedtwin_reset
    • First observedtwin_rewind
    • First observedtwin_run_action
    • First observedtwin_set_auth
    • First observedtwin_set_faults
    • First observedtwin_set_webhooks
    • First observedtwin_status
    • First observedtwin_webhooks

TDQS

B3.3/5.0

Scored across 14 tools

Disambiguation4/5

Each tool maps to a distinct operation (status, clock, actions, checkpoints, faults, auth, webhooks, export), and descriptions clarify the boundaries. The only real overlap is twin_reset vs twin_load_scenario (both rebuild the world) and the checkpoint family (checkpoint/changes/rewind), which are related but distinguishable by their descriptions.

Naming Consistency4/5

All tools share a uniform twin_ prefix and snake_case, giving a predictable namespace. Most are verb_noun (twin_load_scenario, twin_set_faults, twin_export_log), but a few are bare nouns (twin_status, twin_changes, twin_webhooks), a minor deviation.

Tool Count5/5

14 tools is comfortably in the well-scoped range. Each tool covers a distinct facet of the simulation (time, scenarios, faults, auth, webhooks, checkpointing, log export) with no filler.

Completeness4/5

The lifecycle is largely covered: seeding/loading scenarios, advancing time, injecting faults, configuring auth and webhooks, checkpointing, rewinding, and exporting the event log. Minor gaps exist, such as no tool to list or delete named checkpoints directly, and no way to clear or truncate the event log.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Hosted MCP endpoint that returns realistic fake data for prototyping agents. Paste one URL into Claude Code, Cursor, or Claude Desktop — 12 pre-built tools covering users, products, orders, events, email, and knowledge base search. No signup, no config, no auth. Built for developers who want to prototype agent workflows before wiring up a real backend.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    A deterministic eval engine for coding agents. Your agent claims it fixed the bug—this checks it. The same scenario runs twice against a sandbox of the services your code calls, with failures injected on purpose. It must fail on the old code and pass on the new. The verdict is an exit code, not a model's opinion. Every run leaves a receipt.
    19
    188 npm
    1
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    A local runtime for persistent, isolated replicas of services such as GitHub, GitLab, Bitbucket, Jira, Linear, and YouTrack, providing MCP surfaces for agents to interact with software-company resources.
    -
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI agents to read realistic, coherent data—repos, pull requests, issues, commits, users—through API-compatible, read-only replicas of 21 developer platforms including GitHub, Jira, Linear, Slack, Sentry and Salesforce, all preloaded with one simulated company so integrations can be built and tested with no accounts, credentials, rate-limit worry, or cleanup.
    10
    239 npm
    MIT