FieldScroll ships a remote MCP server so AI coding tools can work with your instance directly: search these docs, inspect form schemas, read records, check whether a report was delivered, and look up webhook payload shapes while building an integration.
Most tools read. Seven write — they submit and patch records, build and
release forms, and create a webhook subscription. Writing requires
scopes that a key does not have unless you grant them, so a key minted
without records:write, forms:write, and webhooks:write can only
read. See Writing through the server
before you grant them.
Endpoint and authentication
https://fieldscroll.app/api/mcp
Every request needs an API key in the Authorization header — the same
keys the Customer API uses. Mint one in the
dashboard at /api-keys. A key with no scopes can still search and read
documentation; platform tools each need their matching read scope
(listed below).
Configure the header in your client. Claude Code:
claude mcp add fieldscroll --transport http https://fieldscroll.app/api/mcp \
--header "Authorization: Bearer fs_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"Cursor (.cursor/mcp.json), or any client that accepts a JSON server
entry:
{
"mcpServers": {
"fieldscroll": {
"url": "https://fieldscroll.app/api/mcp",
"headers": {
"Authorization": "Bearer fs_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
}
}For clients that only speak stdio, bridge with mcp-remote:
{
"mcpServers": {
"fieldscroll": {
"command": "npx",
"args": [
"mcp-remote", "https://fieldscroll.app/api/mcp",
"--header", "Authorization: Bearer fs_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
]
}
}
}The server does not use OAuth. If your client starts an OAuth discovery step and shows an error, set the static header as shown above instead.
Available tools
| Tool | What it returns | Scope |
|---|---|---|
search_docs | Ranked documentation sections with excerpts and links | any valid key |
list_docs | Every documentation page with its path and description | any valid key |
get_doc | One documentation page as markdown | any valid key |
get_webhook_event_schema | The exact JSON Schema of a webhook event payload, including the two report-delivery events | any valid key |
get_api_reference | The Customer API operation index, or one operation in full | any valid key |
list_organizations | Organizations the key can address | organizations:read |
get_instance | Instance branding and defaults (no secrets) | instance:read |
list_forms | Forms with name, identifier, and published version | forms:read |
get_form | One form with its full field schema | forms:read |
list_form_versions | Version history with compatibility classification | forms:read |
list_records | Submitted records for a form | records:read |
get_record | One record with its field values | records:read |
list_users | Organization members, or the instance directory | users:read |
list_webhooks | Webhook subscriptions | webhooks:read |
list_webhook_deliveries | Delivery attempts with status and response codes, for the last 90 days | webhooks:read |
list_data_sources | Lookup data sources with their column schemas | data-sources:read |
get_data_source_rows | Rows of one lookup data source, filterable by external id or change time | data-sources:read |
list_reports | Report templates with the form each renders from and its output format | reports:read |
get_report | One report template, including its merge-field mapping | reports:read |
list_report_destinations | Where a report is set up to be delivered — email, webhook, Box, or Google Drive — and whether each destination is on | reports:read |
list_report_deliveries | What was actually delivered — one row per destination per merge, with status and any error | reports:read |
create_form | Creates a draft form from a field tree | forms:write |
update_form | Updates a draft form | forms:write |
set_form_ready_for_testing | Puts the draft in the test slot | forms:write |
publish_form | Releases the draft as the next version | forms:write |
create_record | Submits a record against a form | records:write |
update_record | Merge-patches a submitted record | records:write |
create_webhook | Creates a webhook subscription, to a URL or a report | webhooks:write |
A call missing its scope returns a structured error naming the scope it needs, so the fix is always visible: mint a key that carries it.
list_webhook_deliveries only reaches back 90 days. Delivery history,
including the payload that was sent, is removed 90 days after a
delivery succeeds or fails for the last time, so an agent asked about
older traffic finds an empty list rather than an error.
get_webhook_event_schema also covers the two events a report
destination sends on its own — document.rendered and
report.delivery.succeeded. Each answer says whether the event can be
subscribed to; those two cannot, because they go to the URL set on the
destination rather than to a subscription. See
reports for both payloads.
Asking about reports
Four tools cover reports, all on reports:read.
Two of them answer questions that sound alike and are not:
list_report_destinationsis where a report is set up to go — the email addresses, webhook URLs, and Box or Google Drive folders saved on the template, each with its label and whether it is switched on. A Box or Google Drive destination also reports whether it is authorized. Signing secrets are never returned; asecret_setflag says only whether one is stored.list_report_deliveriesis where it actually went. One row per destination per merge, carrying the status (pending, delivering, delivered, failed, or needs attention), which destination it was, the error message when it failed, and the time it was delivered. A Box or Google Drive delivery also carries the file id, folder path, and link to the file, and either kind of destination with a callback URL adds a second row for the callback, marked as such.
"Did last night's report reach the client?" is a deliveries question. A
destination that is configured and switched on tells you what was meant
to happen, not what did. Because one merge fans out to every
destination, expect several rows per merge, and read
pagination.next_cursor rather than the row count to know when the list
has ended. Passing status narrows the list to failures.
There is deliberately no tool that runs a merge or creates a report template. A merge sends real email and fires real webhook deliveries to real people, so it is not something an agent can set off while answering a question. Run a merge from the dashboard or the Customer API, where a person is choosing to send it.
Writing through the server
Seven tools change data. Five things to know before you grant a key the scopes that allow it.
Writes are validated the same way the API validates them. A record
submitted through create_record is checked against the form version it
binds to, and update_record merges your keys into the existing record —
supplied keys replace, null clears, untouched fields stay untouched. By
default a patch is checked against the version the record is already
bound to, so publishing a new version does not break an agent mid-task.
A form written through create_form or update_form has its whole field
tree checked against the canonical schema, so a malformed field is a
validation error naming its path rather than something stored and
discovered later. The field reference
describes the shape those calls expect. Note that update_form replaces
the entire fields array: read the form first and send it back complete.
Both form-write tools also return a warnings list beside the saved
form — things that were accepted and are probably not what the author
meant, such as an icon name that will not draw or a repeated field id.
They never stop a write. An agent is exactly the author who cannot see
the builder's warnings panel, so this is the only place it hears about
them; see
warnings on a successful write.
Publishing is a separate, deliberate call. update_form edits the
draft and changes nothing a field user sees. set_form_ready_for_testing
puts the draft in a test slot that only assigned testers can fill.
publish_form is the one that reaches production. Keeping them apart
means an agent cannot ship a form to devices as a side effect of editing
it.
Writes do not trigger webhooks. Records written through this server
do not emit record.created or record.updated, matching writes made
through the Customer API. This matters in one specific case: an agent
that creates a webhook subscription here cannot then test it by writing
a record here. Nothing will be delivered. Trigger the event from the
mobile app or the dashboard instead.
Writes are at-least-once. Every write tool accepts an optional
idempotency_key. Retrying with the same key returns the original
result instead of writing twice. A new key on every attempt gives you no
protection — and an AI agent will generate a new one unless its framework
is threading a stable key for you. Assume a retried write can duplicate.
Keys are also per transport: reusing one key for both an MCP tool call and
a Customer API request returns a conflict rather than the earlier result,
because the two record different request shapes. Use separate keys.
A dispatched record is locked. A record checked out to someone for
offline editing cannot be patched until it is submitted or the dispatch
is revoked. update_record says so plainly rather than surfacing a
database error.
Organization scoping
The key decides which organization the tools see.
- Organization-scoped key — tools work inside that organization automatically. You never pass an organization id.
- Instance-scoped key — tools that read organization data need an
organization_idargument. Agents discover ids withlist_organizations.
This is the same model as the Customer API.
Limits
MCP calls draw from the same per-key, per-minute budget as Customer API requests — one key, one budget. When the budget is spent the server returns a rate-limit error with a retry delay; see rate limiting. Every MCP request, including the connection handshake, counts as one request.
Page size adapts to the payload. List tools accept a limit up to 100,
but a single tool result is capped so it does not flood the agent's context.
Rows vary in width — a data source with many columns, a form with many fields
— so a page of 100 sometimes will not fit. list_records and
get_data_source_rows, the two tools whose rows vary most, return a smaller
page in that case rather than an error. Other tools return a
response_too_large error asking you to narrow the call.
Read pagination.next_cursor, not the row count, to decide whether to keep
going. A page shorter than the limit you asked for is normal and does not
mean the list has ended; a null next_cursor does.
A note on safety
Treat an MCP key like any other credential: scope it to what the task needs, prefer organization-scoped keys, and revoke keys you no longer use. The server never exposes secrets.
The scopes on the key are the control. A key without records:write,
forms:write, and webhooks:write cannot change anything no matter what
the agent is asked to do, so give exploratory and reviewing work a
read-only key and keep write scopes for keys used deliberately.
forms:write deserves particular care: it can publish a form to every
device assigned to collect with it.
Worth understanding if you do grant write scopes: an agent reads content you did not write. A record value, a form label, or a documentation page can contain text that looks like an instruction. A well-behaved agent treats what it reads as data, and the server tells it to — but the boundary is stronger when the key simply cannot write. Scope accordingly, and prefer a key bound to one organization so the blast radius stops at that organization's data.