Hodoflow speaks the Model Context Protocol at POST /api/mcp. Connect an
assistant — Claude Desktop, Claude Code, an IDE agent — and it can answer
questions about your records, your workflows, and the product itself, and build
the pipeline that produces them, using tools instead of guesswork.
The surface reads and it authors. An assistant can create a data model, save a workflow, activate it, and build the dashboard that reads the result — the same work the builder does, done over tools. What it never does is destroy: nothing here deletes a workflow, a data model, a dashboard, or a widget.
Twenty-eight tools, eighteen of which only read. A read tool opens its
description with "Read-only." and is served with a readOnlyHint annotation; a
write tool does neither. A client that asks for approval before a tool runs
therefore asks for exactly the ten that change something.
Authenticate with a personal access token
Unlike the other API surfaces, MCP does not use a service account. It uses a personal access token, which belongs to you rather than to one workspace:
| Service account | Personal access token | |
|---|---|---|
| Belongs to | One workspace | One person |
| Reaches | That workspace | Every organization you are an active member of |
| Acts as | Its run-as member | You |
| Created at | Settings → Service Accounts | Your profile |
One token is enough no matter how many organizations you work in, which is why an assistant is configured once rather than once per organization.
Mint a token
- Open your profile from the account menu.
- Under Personal access token, select Create token.
- Copy the value immediately.
The token starts with pat_ and is shown once; there is no way to read it back
later. You hold one at a time, and it does not expire on a schedule — a
credential that dies on its own only ever dies at an inconvenient moment, and an
expiry date does nothing to protect a token that has already leaked.
Two controls replace it:
| Control | Effect |
|---|---|
| Rotate | Issues a new token and kills the old value in the same moment. |
| Expire | Kills the token and leaves you with none until you create another. |
Neither has a grace period, which is the point: after rotating, the leaked value is dead with nothing left to clean up.
What the token consents to
A token does whatever its owner can do, in every organization its owner belongs to. It sees the organizations you are an active member of, and it stops seeing an organization the moment your membership or that organization is deactivated.
There is no scope to narrow it with, and authoring is not gated on role: Hodoflow lets any active member build and run pipelines, so a token can create, change, and activate workflows in every organization you belong to, exactly as you can in the application. Hold it as you hold your password, and expire it when you are done with the tool that has it.
The step-by-step version for people who are not integrating anything lives in the Help Center under Credentials & connections → Personal access tokens.
Connect the assistant
claude mcp add --transport http hodoflow https://hodoflow.com/api/mcp \
--header "Authorization: Bearer pat_..."
Other clients take the same three pieces: the URL https://hodoflow.com/api/mcp,
the streamable HTTP transport, and an Authorization: Bearer header carrying the
token. A self-hosted deployment substitutes its own application origin.
Every rejected request — missing token, mistyped token, rotated-away token,
expired token — answers 401 with the same body, so a failing connection means
"check the token", not "the token exists but something else is wrong".
Ask for an organization first
An assistant starts with no organization selected, because the token reaches several. The first call is always discovery:
list_my_organizationsreturns each organization with adomain.- Every tool that touches your data takes that value as its
organizationargument. - Tools also accept an optional
workspaceargument — aslugfromlist_workspaces. Leave it out and the organization's default workspace is used, the same one the application opens on.
Naming an organization you are not a member of returns an error that points back
at list_my_organizations. It never returns another organization's data, and it
never reveals whether that organization exists.
What an assistant can read
Discovery
| Tool | Returns |
|---|---|
list_my_organizations |
The organizations you can work in: domain, name, and your role |
list_workspaces |
The workspaces in one organization: slug, name, and which is the default |
Records
| Tool | Returns |
|---|---|
list_data_models |
The data models in a workspace, each with its JSON Schema |
list_data_model_fields |
One data model's fields — the columns you can filter, sort, and aggregate by |
query_records |
Records for one data model: the latest state per entity, or the full timeline |
aggregate_records |
One number, one bucket per value, or one point per time bucket, without returning the records themselves |
query_records takes a data_model_slug plus an optional time window, filters,
sort, and paging. It returns at most 250 records per call along with a total
count, so an assistant can page rather than guess. Filters name a field of that
data model; anything else is rejected.
aggregate_records answers "how many orders per status this month" in one call.
It is also what to run before saving a widget, since a widget draws the same
aggregation.
Workflows
| Tool | Returns |
|---|---|
list_workflows |
Workflow versions: name, version number, type, and whether the version is active |
get_workflow |
One version's whole authored graph: its steps with their configuration and dependencies, its triggers, and the field mappings of any step that writes to a data model |
list_workflow_runs |
Runs, newest first, with state, timing, and any error message |
get_run_steps |
How each step of one run turned out: status, attempt, timing, error |
list_triggers |
What starts each workflow, and whether its version is live |
get_run_steps is the tool that answers "why did this run fail?". It reports
outcomes only — the data flowing through a step is never returned.
get_workflow is also the first half of every edit, because saving replaces the
whole graph. It returns a webhook trigger's ingest URL, the one credential-shaped
value any tool returns: a webhook nobody can deliver to is not authored, and the
workflow screen shows the same value to the same readers. No other secret is
returned by any tool, list_triggers included.
Integrations
| Tool | Returns |
|---|---|
list_integrations |
Connectors, service accounts, and imported API specs in one call — the ids and names authoring needs |
get_api_spec |
One imported spec's operations, so a step can be bound to one by operation_ref |
Stored credentials, client secrets, and the uploaded spec document itself are never returned.
Dashboards
| Tool | Returns |
|---|---|
list_dashboards |
The dashboards in a workspace |
list_widgets |
The charts, tables, and stat cards a workspace has |
Sharing links are never returned by either tool.
Authoring reference and documentation
| Tool | Returns |
|---|---|
describe_authoring_schema |
The vocabulary for authoring: every step and trigger type with its configuration keys, the shape of a workflow save, data model field types, the formula language, and the widget and aggregation vocabulary |
search_docs |
The best-matching Help Center and developer pages for a question |
read_doc |
One documentation page in full, as markdown |
These three take no organization: they serve content compiled into the release,
the same for everyone. search_docs and read_doc let an assistant answer "how
do I do X in Hodoflow?" from the current documentation rather than from memory.
describe_authoring_schema is the one to call before writing anything. None of
the authoring vocabulary is guessable, and it is deliberately kept out of the
other tools' descriptions so that it costs one call rather than riding in every
request.
What an assistant can change
Eleven tools write, and every one of them is additive: they create records or change the ones you name, and nothing else moves.
Records
| Tool | Does |
|---|---|
create_data_model |
Creates a data model and its fields in one transaction. A name or identifier already in use is refused rather than overwritten |
update_data_model |
Renames a model, rewrites its description, or adds fields. Existing fields, and the records already written, are untouched |
The identifier other tools address a model by is fixed once the model exists, and a field a workflow may be writing to cannot be renamed or removed. How long records are kept is not changeable here at all: shortening retention deletes records, so it stays a decision made in the app.
Workflows
| Tool | Does |
|---|---|
save_workflow |
Replaces one version's whole graph — a step or trigger left out of the document is deleted from it |
activate_workflow |
Puts a version into service |
deactivate_workflow |
Takes a version out of service |
test_step |
Issues the request an http_request step is configured to make, once, and reports the fields it answered with |
Because the save is a whole-graph replace, an edit starts by reading the version
with get_workflow and sending it back changed. Saving over a version that has
been published never edits it: the save forks a new version and leaves the
published one running untouched. Each save is one transaction, so a document the
server refuses changes nothing — correct it and send it again.
test_step is how an assistant learns a source's field names before authoring a
step that reads them. Nothing is saved and no workflow needs to exist yet.
Dashboards
| Tool | Does |
|---|---|
save_dashboard |
Creates a dashboard, or renames and re-describes one that exists |
save_widget |
Creates a chart, table, or stat card, or rewrites one that exists |
place_widget |
Puts an existing widget on a dashboard, or moves one already there |
A widget belongs to the workspace rather than to one dashboard. The same widget
can sit on several, save_widget changes it everywhere it is shown, and
place_widget places rather than copies.
Integrations
| Tool | Does |
|---|---|
create_service_account |
Creates the service account a webhook trigger names as the sender it accepts, and answers with its id |
import_api_spec |
Stores an OpenAPI or Swagger document so a step can be bound to one of its operations |
The document is sent as text; nothing is fetched from a URL. Importing the same document twice stores a second copy rather than replacing the first.
create_service_account is the one place this surface asks for a role
rather than membership: creating one is an owner or admin act, and a member is
refused. The account is created with the webhooks permission and no other, and
it runs as the caller — widening its permissions, changing whose permissions it
borrows, and every other change to it stay in the app.
Its secret is not in the answer and cannot be recovered from this surface. The answer carries the id, which is all a trigger needs; the secret is revealed once under Settings → Service Accounts, and whoever configures the calling system copies it from there.
What activation and running mean
Activation is where authoring becomes production. activate_workflow opens the
version's triggers to deliveries and starts its schedules,
and deactivates whichever version of the same workflow was live. A first
activation marks the version published, after which saving over it forks rather
than edits. deactivate_workflow reverses all of that and leaves runs already
under way to finish.
Activation is refused, with the reason, when the graph is not ready to run: a source schema has gone stale, the workflow has no trigger, a trigger reaches no step, a step is not fully configured, a step that writes to a data model has no mappings, the steps form a cycle, or a step is unreachable.
Two consequences leave the deployment, and are worth stating plainly:
test_stepreally makes the request. The third party sees it, and anything it does in response is done. Only the shape of the answer comes back — how many records it held and the typed field paths of one of them — never the response body. Requests to private or internal addresses are refused.- What an assistant builds counts against the plan. Data models, fields, workflows, active workflows, dashboards, widgets, and API specs all draw on the organization's allowances, as do the runs an activated workflow performs and the records and storage they produce. A call that would exceed an allowance is refused, naming what was exceeded.
What stays in the app
Deliberately absent from the tool surface, and reachable in the application by someone with the right role:
- Deleting anything. No tool removes a workflow, data model, field, dashboard, or widget, or takes a widget off a dashboard.
- Publishing and sharing. Public dashboard embeds and share links are neither created nor returned.
- Retention. Shortening how long records are kept deletes records.
- People and credentials. Invitations, memberships, roles, service accounts, connector secrets, and billing.
Rate limits
MCP requests are counted per person, against the platform default of 300 requests per 60 seconds — see Rate limits. Reads and writes count alike. The organization's own API entitlement does not apply here, because one token spans several organizations and no organization is chosen until the individual tool call; the organization's plan limits still apply to everything a write tool creates.
Exceeding the window returns 429 with a Retry-After header, in the same
error shape as the rest of the API.
What an assistant cannot reach
- Anything outside your own memberships.
- Anything in an organization or workspace you did not name in the call.
- Secret material, in either direction. Nothing returns a webhook secret, a
sharing link, a service-account secret, or another person's token — and no
tool accepts one as an argument, which is why connectors are created in the
app: making one means supplying an API key, and an argument cannot be
withheld the way an answer can. An assistant may create the reference and
never handles the secret behind it:
create_service_accountmints the account and answers with its id, and the secret is revealed only here. The single exception in the other direction is a webhook trigger's own ingest URL, returned byget_workflowto readers who already see it on the workflow screen. - The contents of a workflow step's data.
- Anything destructive: deletion, publishing, retention, and administration all stay in the app.