What Croft is
Agents can write working software in minutes. What they cannot do is keep it running. Croft is the part that keeps it running.
You describe a recurring task in your own words. Claude writes a workflow — ordinary TypeScript against the Croft SDK, not a drag-and-drop diagram and not a DSL — and Croft stores it, compiles it, and runs it: on a schedule, on a webhook, on a poll, or on demand as something Claude can call back later. It runs on our machines, with the connections it needs, whether or not you are online and whether or not anything else broke that day.
A workflow is a repeatable sequence of steps that takes a process from its start to a finished goal. If you have ever written “every month: first do this, then this” on the back of an envelope, you have written one down already. The only difference here is that something else does the steps.
Durable means something specific. Every awaited effect in a workflow is a
step, recorded in the run’s step log the first time it happens. If the
machine dies mid-run, the run re-executes and every recorded step returns its
result instantly instead of happening twice — no duplicate email, no duplicate
charge. A workflow that calls ctx.sleep("7 days") is not a process holding a
timer for a week; it is a database row and a checkpoint, woken on time.
Croft is not a chat product and not an agent. It is the layer your agent stands on.
Before you start
You need three things, and only the first takes any time.
A Croft account. Go to croft.now and enter your email address. There is no password anywhere in Croft — logging in means proving you can read mail at an address. We send one email containing both a magic link and a six-digit code; either half works, and redeeming either one retires the other. Credentials are good for fifteen minutes, and you can have at most five live ones per address per hour. Typing an address nobody has used before creates the account; signing up and logging in are the same act, and there is no waitlist, invite code, or approval step.
A workspace, and its address. Your first successful login provisions an org
and a workspace called Personal inside it, and drops you on the canvas at
https://croft.now/{org}/{workspace}. Those two slugs are the workspace’s
address — acme/personal, say — and you will need that exact string, written as
{org}/{workspace}, because every Croft tool takes it as a required scope
argument. Read it out of the URL. (If you arrived through an invitation you
land in the inviting org instead, and no personal workspace is made for you.)
Claude, with custom connectors available. Nothing to install, and no API key to paste — the connection is set up over OAuth.
You do not need a connected Google account, Slack, or anything else to start. Those are only needed once a workflow actually has to reach one of them, and connecting them is a separate, human step.
Connect it to Claude
In Claude, open Settings → Connectors, choose Add custom connector, and give it the server URL:
https://croft.now/mcp
Transport is streamable HTTP. There is nothing else to fill in: Croft’s authorization server supports dynamic client registration (RFC 7591), so Claude registers itself. Your browser will open a Croft consent screen; sign in if you are not already, read what is being granted, and approve. That is the whole setup.
To confirm it worked, ask Claude:
List my Croft workspaces.
You should get back one row per workspace you can open, each with the
{org}/{workspace} scope string you will use in every later request. That
answer comes from the croft://workspaces resource, and getting it is proof
that discovery, registration, the OAuth round trip, and the token all worked.
For a reviewer checking the protocol
- An unauthenticated
tools/listagainsthttps://croft.now/mcpreturns401with aWWW-Authenticate: Bearer resource_metadata=...challenge pointing at/.well-known/oauth-protected-resource. - Authorization server metadata is at
/.well-known/oauth-authorization-server(RFC 8414). It advertises the scopes listed below, the/oauth/authorize,/oauth/token,/oauth/register,/oauth/revokeand/oauth/introspectendpoints, and PKCE withS256. - A token authenticates a person, not a workspace. One connection therefore
spans every workspace its user can open, and each call’s
scopeargument picks which one. Membership is re-checked on every single call, so losing access to a workspace takes effect on the caller’s next request — there is no token to hunt down and revoke. tools/listis filtered per request against the caller’s granted scopes, so a tool a caller cannot use is invisible rather than advertised and refused.- A
scopenaming a workspace you cannot reach returns exactly the same not-found as a workspace that does not exist. So does a malformed UUID, and so does someone else’s UUID. The error is never an oracle.
Headless clients that cannot do a browser round trip can use a workspace API key instead; a key is an agent identity, fixed to one workspace.
What each permission grants
Nine scopes are declared, and the consent screen collapses read/write pairs into one sentence each. In plain language:
| Scope | What it lets an agent do |
|---|---|
workflows:read |
See your workflows and their source — the workspace index, a workflow’s config and files, its derived graph, its tests, and its recent runs. |
workflows:write |
Create, edit, run, and delete workflows. Also mints sibling workspaces. |
runs:read |
Inspect run history — a run, its steps, and each step’s input and output. |
runs:write |
Resume a parked or failed run, replay one in shadow mode, pin one as a test, and run a test suite. |
data:read |
Read documents, record streams, and files, and run read-only SQL over the streams. |
data:write |
Write and delete documents, append records, and write files. |
connections:read |
See which external accounts are connected. |
artifacts:write |
Create, update, and delete hosted artifact pages. |
hooks:respond |
Answer a workflow that is parked waiting on a human. |
Nothing here grants an agent the ability to create a connection to a third-party service or to grant itself one. That is human-only, by construction — see Connecting your other accounts.
Your first workflow
Ten minutes, start to finish, assuming you have an account.
-
Get your scope string. Open
https://croft.now, sign in, and read the two slugs out of the URL you land on. That is your{org}/{workspace}. -
Give the workflow something to work with. Ask Claude:
In Croft workspace
acme/personal, create a records stream calledorderswith these rows: 2026-08-27, widget, 3, 45.00; 2026-08-27, gasket, 1, 12.50; 2026-08-26, widget, 2, 30.00.Claude calls
records_append, which creates the stream on first write. -
Ask for the workflow.
In
acme/personal, write me a Croft workflow that runs every weekday at 8am UTC, totals yesterday’s rows from theordersstream, and emails me a one-line summary at [email protected].Claude will read
croft://sdk/referencefor the SDK, then callworkflow_write. Claude will ask you to confirm — that is expected, and why is worth reading once. -
Read the diagnostics. The write comes back with compile errors, analyzer lints, the derived graph, test results, and any connection problems, all in one envelope. Claude iterates against that rather than compiling separately, so a workflow that does not compile is usually fixed before you see it.
-
Run it now. Ask “run it once so I can see it work”. Claude calls
workflow_run; approve the confirmation. -
Look at it. Open
https://croft.now/{org}/{workspace}. The workflow is there, its schedule is armed, and the run you just triggered is on the run rail with every step it took.
If step 3 comes back saying the email connection is missing, that is the system working as designed — go to Connecting your other accounts.
Example prompts and what to expect
These are the shapes that actually compose the tools well.
“Write me a workflow that pulls yesterday’s orders and emails a summary every
morning.”
Claude reads the SDK reference, calls workflow_write with a schedule trigger
and an email connection, and shows you the diagnostics envelope. On a clean
compile the bundle goes live immediately and the schedule is armed — there is no
separate deploy. You should see the workflow appear on your canvas within
seconds.
“Show me my Croft workspaces.”
Reads croft://workspaces. Returns each workspace’s name, org, and scope
string. This is the right first prompt after connecting.
“What’s in workspace acme/personal?”
Reads croft://acme/personal: workflows, the connections this workspace can
actually reach, document names, record streams, files, artifacts, and usage
counters.
“Run the weekly report now.”
workflow_run. Claude will ask to confirm, because running a workflow performs
real effects. Pass an idempotency key if you are retrying something you are not
sure landed — the same key returns the same run marked duplicate rather than
starting a second one.
“Why did last night’s run fail?”
Reads the workflow’s recent runs, then the failing run, then walks to the
failing step’s recorded input and output. Claude proposes a fix, and can
shadow-replay recent runs against the candidate before you accept it — a
run_fork never sends anything.
“The summary is missing refunds. Fix it.”
workflow_edit — exact-string replacement, the same semantics as an editor’s
find-and-replace, with the same diagnostics envelope coming back. Compiling runs
the workflow’s pinned test suite, so you find out in the same reply whether the
fix broke a case you had pinned.
“Pin last night’s good run as a test.”
run_pin_as_test freezes that run’s recorded results as fixtures, so every
later compile checks against it.
“Ask me before it sends anything.”
Claude rewrites the workflow to park on a hook. The run stops at that point and
waits; the pending hook shows on the run page and on the run resource. When you
answer — in chat, on the canvas, or by pressing a button on a hosted page —
hook_resume relays your answer, it is validated against the hook’s schema, and
the run continues from where it stopped.
“Query the orders stream: what did we sell most of last week?”
records_query runs read-only SQL over your record streams, each mounted as a
view named after the stream. Read the limits before you rely on
the shape of the result.
“Make a page my team can open with this week’s numbers.”
artifact_create returns a hosted, shareable URL. Artifacts are single
self-contained HTML pages that can read the workspace data they declare up
front — an undeclared read is refused at request time — and they default to
workspace-members-only.
Tool reference
Eighteen tools, and only eighteen: the vocabulary is fixed rather than growing a
verb per feature. Every tool takes a required scope string in
{org}/{workspace} form; it is left out of the parameter notes below because it
is on all of them.
Read
| Tool | Title | Scope | What it does |
|---|---|---|---|
records_query |
Query records | data:read |
Read-only SQL over the workspace’s record streams, each mounted as a view named after its stream. |
That really is the only read tool. Everything else you read comes from
resources, which is where the workflows:read,
runs:read and data:read scopes do their work.
Write
| Tool | Title | Scope | Confirms? | What it does |
|---|---|---|---|---|
workflow_write |
Write workflow code | workflows:write |
yes | Whole-file write of TypeScript. Omit the workflow id to create a new one. |
workflow_edit |
Edit workflow code | workflows:write |
yes | Exact-string replacement in one file of an existing workflow. |
workflow_run |
Run workflow | workflows:write |
yes | Manual trigger, and the only way to invoke a callable workflow. |
workflow_delete |
Delete workflow | workflows:write |
yes | Deletes a workflow. |
run_resume |
Resume run | runs:write |
yes | Continues a parked or failed run from a given step. |
run_fork |
Replay run | runs:write |
no | Shadow-mode what-if replay, optionally against a candidate bundle. |
run_pin_as_test |
Pin run as test | runs:write |
no | Freezes a run’s recorded results as a test fixture. |
test_run |
Run tests | runs:write |
no | Runs a workflow’s suite, or a single pinned case. |
hook_resume |
Answer paused run | hooks:respond |
yes | Relays a human’s answer into a run parked on a hook. |
data_put |
Save document | data:write |
yes | Writes one named document. Not for tabular data. |
data_delete |
Delete document | data:write |
yes | Deletes a workspace-level document by name. |
records_append |
Append records | data:write |
no | Appends rows to a records stream; the first call creates the stream. |
file_put |
Save file | data:write |
yes | Writes a blob at a path in workspace file storage. |
artifact_create |
Create artifact | artifacts:write |
no | Creates a hosted, shareable page and returns its URL. |
artifact_update |
Update artifact | artifacts:write |
yes | Replaces an artifact’s content or settings. |
artifact_delete |
Delete artifact | artifacts:write |
yes | Permanently deletes an artifact; its URLs stop resolving. |
workspace_create |
Create workspace | workflows:write |
no | Creates a sibling workspace in your org and mints an agent identity in it. |
Why some of these ask you first
A tool marked destructive always prompts in Claude. That is expected, and it is worth knowing which ones are marked and why, so you can approve them without squinting:
workflow_writeandworkflow_editare destructive, which surprises people, because writing code sounds harmless. It is not, here: a successful compile immediately runs the workflow’s pinned test suite, and those tests execute the workflow’s real steps against real providers. Writing code can send an email. They are also marked open-world for the same reason.workflow_run,run_resumeandhook_resumeall cause real effects to happen — starting, continuing, or releasing a run.data_put,data_delete,file_put,artifact_update,artifact_deleteandworkflow_deleteoverwrite or remove something that was there.run_forkandtest_runare open-world (they can reach outside Croft) but not destructive; a fork is a shadow replay and sends nothing.records_queryis the one tool marked read-only.
Two of the unmarked ones deserve a second look anyway, because Claude will not
stop to ask about them: records_append creates a stream that no tool can
delete, and workspace_create mints an API key that is shown once and is not
recoverable.
Resources and prompts
Reading is done through MCP resources under croft://, not through tools. Slugs
scope; UUIDs identify.
Not tied to a workspace
| URI | What it is |
|---|---|
croft://sdk/reference |
The current @croft/sdk documentation, versioned with the SDK. This is what Claude reads before writing a workflow. |
croft://sdk/examples |
Canonical example workflows as annotated source. |
croft://workspaces |
Every workspace this token can open, each with its scope string. |
Inside a workspace ({org} and {ws} are your two slugs)
| URI template | What it returns |
|---|---|
croft://{org}/{ws} |
The workspace index: workflows, this identity’s connection grants, document names, record streams, files, artifacts, usage counters. |
croft://{org}/{ws}/workflows/{id} |
One workflow: config, files, connections, input schema, status, live bundle hash. |
croft://{org}/{ws}/workflows/{id}/files/{path} |
One source file. |
croft://{org}/{ws}/workflows/{id}/graph |
The derived graph — nodes, edges, dynamic regions. |
croft://{org}/{ws}/workflows/{id}/graph.svg |
The same graph as a standalone SVG. |
croft://{org}/{ws}/workflows/{id}/tests |
Pinned test cases, their fixtures and assertions, and the last result. |
croft://{org}/{ws}/workflows/{id}/runs |
The workflow’s recent runs, newest first. |
croft://{org}/{ws}/runs/{id} |
One run: its steps, its hooks, and the pending hook hoisted to the top. |
croft://{org}/{ws}/runs/{id}/steps/{n} |
One step’s full input, output, and metadata. |
croft://{org}/{ws}/data/{name} |
One document, and whether it resolved at workspace or org level. |
croft://{org}/{ws}/records/{stream} |
A stream’s stats and its inferred columns. |
croft://{org}/{ws}/files/{path} |
One workspace file. |
croft://{org}/{ws}/artifacts/{id} |
An artifact’s declaration, hosted URL, share URL, and visibility. |
Prompts. Seven, each a starting point you can pick in Claude rather than typing the whole framing yourself. Where you can reach exactly one workspace, its slugs are filled in for you.
| Prompt | Arguments | What it does |
|---|---|---|
solve |
problem (optional) |
The flagship: read the problem, check the workspace, iterate until a workflow is live. |
create-workflow |
goal, trigger, delivery |
The structured version of solve, for when you already know what you want. |
improve-workflow |
workflow, feedback |
Read a workflow’s source and recent traces, apply feedback, show the shadow diff and test results before updating. |
debug-run |
run |
Fetch a run, walk to the failing step’s input and output, propose a fix, shadow-fork recent runs against it. |
add-test |
workflow, expectation |
Pin the latest good run as fixtures and turn a stated expectation into assertions. |
import-data |
description |
Take data out of the conversation into documents or records, then suggest the workflows it enables. |
create-artifact |
purpose, data (optional) |
Design and ship a hosted, shareable page. |
Connecting your other accounts
A workflow that only touches Croft’s own data needs no connections. One that reads your inbox or writes to a spreadsheet needs a connection: a stored credential, held by Croft, that a workflow refers to by alias. The workflow’s own config lists the aliases it may use, and anything else is denied at execute time whatever the code says. Connector calls run in Elixir, never inside the workflow sandbox, so credentials never enter workflow memory, and revoking a connection stops the next call cold.
What exists today, honestly:
| Connector | State |
|---|---|
| Real. Markdown in, HTML out, delivered through Croft’s mail provider, with an outbox row you can read. | |
| GitHub | Real, read-only actions. |
| Google Sheets | Real, connected through a browser OAuth flow. |
| Attio | Real, connected through a browser OAuth flow. |
| Stripe | Real, connected through a browser OAuth flow. Payments, billing, refunds and disputes, plus an incremental read of the money ledger. |
| HTTP | Real, and default-deny: it will only call URL prefixes you have allowed. An empty allowlist denies everything. |
| Slack | Real when a token is configured; otherwise it runs in stub mode and reports that it did. |
| Web search | Real, and off until you switch it on. One lookup against a search index (Google Programmable Search by default, Brave optionally). No account to connect — the switch is the whole setup. |
| X | Stub in v1. Returns deterministic canned metrics, not real API data. |
sheets |
Deprecated stub. Use google_sheets. |
The three with a browser connect flow today are Google, Attio and Stripe. The rest are configured with credentials rather than through a consent screen.
An agent cannot connect an account to itself, and that is deliberate. There
is no tool in the eighteen that creates a connection. A human member adds them
on https://croft.now/{org}/{ws}/connections, and connecting requires org owner
or admin. A plain member sees the same page read-only.
Reach is decided per workspace, not per agent. Every agent identity in a workspace can use every connection that workspace can see: the ones connected there, plus any org-level connection an org admin has exposed to it. There is no second, per-identity approval step — the human who connects the account is the authorization, and the way to limit a connection’s reach is to control which workspaces have it.
What the agent gets instead is a clear report: when a workflow names a
connection its workspace does not have, the diagnostics envelope comes back with
a connection_issues entry marked not_found, naming the alias. Claude will
tell you which connection is missing. Then you connect it, and ask Claude to
compile again.
One thing worth knowing: a workspace with no email connection configured at all can still send email to an address that belongs to a member of the workspace or its org. It cannot mail strangers.
Web search is the one you switch on rather than connect. The API key
belongs to the server, not to your workspace, so its row on the connections page
is a plain on/off switch and it starts off. An owner or admin presses Enable
once and every workflow in the workspace can call
ctx.web_search.search({ query }); Disable turns it back off and the next
call stops cold, exactly like revoking any other connection. It is off by
default on purpose: every search costs the server a metered query and sends your
phrasing to a search engine, which is a decision worth making rather than
inheriting.
It is a search index, not a model. One lookup, one ranked list of
{ title, url, snippet }, and the same query returns the same results — nothing
reads your query and decides what it meant. That matters when an AI is writing
the queries: the step replays identically, and what comes back is what the index
actually holds rather than a model’s recollection of the web. If you want the
results summarised, hand them to ctx.ai yourself; you will be able to see and
replay both halves.
A workspace can supply its own API key if it would rather bill searching to its own quota than the server’s. That is the only thing its credentials ever hold — turning the connector on requires nothing.
Two things it will tell you rather than paper over. If a workflow asks for a
domain outside the ones the connection allows, the call fails with
domain_not_allowed rather than quietly searching everywhere. And a rate limit
comes back as rate_limited with the index’s own retry_after, so the run
parks and picks up later instead of burning the rest of the day’s quota
retrying.
Known limits
Straight answers, because you will hit these.
Records
records_querytruncates results to 200 rows, and flags when it did.- Its result columns come back alphabetized, not in
SELECTorder — address them by name, never by position. - A zero-row result has an empty
columnslist, so do not infer the schema from an empty answer. - Mutating and DDL keywords (
INSERT,UPDATE,DELETE,CREATE,COPY,ATTACH,PRAGMA, and similar) are rejected. records_appendtakes flat maps only — scalar values, no nested objects or arrays — and rejects the call otherwise. Stream names are lowercase letters, digits,_and-. Column drift between appends is unioned by name.- There is no tool to delete a record stream once created. Choose stream names deliberately.
Documents and files
- A document value is capped at 5 MB encoded; a file at 100 MB.
data_deleteonly removes a workspace-level document. An org-level document of the same name survives and remains the read-side fallback.
Workflows and runs
workflow_deleterefuses an active workflow — it must be paused or done first. There is no MCP tool that pauses a workflow, so today pausing is a button on the workflow page and deleting an active workflow needs a human.- On a new workflow,
workflow_writealways saves the file asindex.ts, whatever path you pass. workflow_editrequiresold_stringto match exactly once; otherwise it reports the match count and changes nothing.replace_allopts out.- Both accept an
expected_hashand return a conflict rather than a lost update if someone else wrote in between. - A multi-file workflow’s first write cannot compile, because
index.tsimports a sibling that has not been written yet. Expect an error, write the sibling, and it goes green. - A write that does not compile leaves the live bundle untouched, so a workflow mid-refactor keeps running its last good version.
run_resumetruncates the run at the step you name and continues live, which produces a new run id.- The sandbox has no ambient network, filesystem, or entropy; the clock is virtual and randomness is seeded per run. This is what makes replay safe, and it means a workflow cannot reach the internet except through a connector.
Workspaces and artifacts
workspace_createonly makes a sibling workspace in your own org — an agent identity never reaches outside it. The new workspace starts with no connections, and an org-level connection does not follow it there, so its identity reaches nothing outside croft until a human connects something.- The API key it returns is shown once and cannot be recovered.
- Artifacts are versioned and content-hashed, but there is no instant rollback,
and
artifact_deleteis permanent — the hosted and share URLs stop resolving.
Not there yet
- There is no resource that lists the providers you could connect. What an agent can see is the connections its own workspace can already reach, in the workspace resource.
- There is no workspace-wide run list; runs are listed per workflow.
- Croft does not charge anyone today. There is no paid plan and no billing system. Usage is counted per workspace so we know what the service costs, and account holders will be told by email before that ever changes.
When something goes wrong
Claude says it cannot find the workspace. The scope argument is
{org}/{workspace} — two slugs from the canvas URL, separated by a slash, not
the workspace’s display name. A scope you cannot reach and a scope that does not
exist return the same error on purpose, so the message will not tell you which
it was. Ask Claude to list your workspaces and copy the string it gives you.
A tool you expected is missing. tools/list is filtered against your
granted scopes. If you approved a narrower set at the consent screen, the tools
those scopes cover simply are not there. Reconnect and approve the ones you
need.
The workflow compiles but reports a connection problem. That is
connection_issues, and it means the alias the workflow names has no connection
in this workspace. Fix it on the connections page; the agent cannot.
A run is sitting there not finishing. Check whether it is parked on a hook waiting for a human. The pending hook is hoisted to the top of the run resource for exactly this reason. Answer it in chat and Claude will relay it.
A workflow deletes fine in your head but not over MCP. Pause it on its page
first; workflow_delete refuses an active workflow.
Privacy, terms, and support
The privacy policy says exactly what Croft holds and what it does with it, including how connected-account credentials are encrypted and what deletion removes. The terms cover the rest of the deal, including the part specific to a product whose code is model-written: a generated workflow is not guaranteed to be correct, and it acts with the access you gave it.
For anything about this connector or the product, email [email protected]. For a privacy or data question specifically, [email protected].