Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .changeset/agent-scoped-submission-tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
'@formio/mcp': minor
'@formio/ai': minor
---

Add five `submission_*` MCP tools that let the agent seed the Resource a `select` reads and write test submissions. Each tool reaches only submissions this server created and signed for the calling working directory.

**`@formio/mcp`** adds `submission_create`, `submission_list`, `submission_get`, `submission_update`, and `submission_delete`.

- **The tag.** Every submission the agent writes carries a server-written `metadata.agent` tag with exactly four fields: `source`, `session` (a label derived from the directory's key, so list queries return only that directory's rows), `purpose`, and `sig`. The signature is a deterministic HMAC-SHA256 over the project, form, owner, `session`, `purpose`, and the full stored `data`. The server gets that data from a `?dryrun=1` pass, which validates and normalizes without running actions or saving. The key is 32 random bytes per working directory, kept in `~/.formio/mcp-submission-keys.json` with mode `0600`. It never appears in a tool result or error.
- **Reads.** Every read forces the tag filters and builds its query from structured `data.*` filters. Agent parameters are never forwarded, because Form.io copies unrecognized query parameters straight into its Mongo filter. Each returned record's signature is re-verified, and a record that fails is dropped before the agent sees it. A get by id answers a 404 and a record the agent did not create the same way: not found, with none of its content.
- **Writes.** Update and delete verify the target first. Update keeps the record's `session` and `purpose`, re-signs over the dry-run-normalized data, and refuses caller-supplied `metadata`. Every write result names the form actions it ran.
- **Server instructions.** They now state the scope, the ban on reaching any other submission by another route, and the `action_list` check before a write.
- **API errors.** `FormioApiError` now carries the HTTP `status` and the response `body`, so a dry-run 400 can report Form.io's validation messages.

**`@formio/ai`** adds one canonical guideline, `formio-mcp-setup/references/agent-submissions.md`, covering when the agent writes a submission:

- the two purposes, `reference-data` and `test`;
- no reference rows in user-type Resources or in forms with a Login, Role Assignment, or Group Assignment action;
- invented values on a reserved domain;
- `action_list`, a preview, and approval before every write, with a warning for a live project;
- test-row cleanup.

The skills now use it:

- `formio-form-builder` offers to seed an empty dropdown source and to write a test submission after SAVE.
- `formio-application` gains Step 3.6, which offers the reference rows the planner marks with a new `Seed: reference-data` line.
- `formio-actions` documents testing an action with a test submission.

The earlier wording that said no submission tool exists, that no tool returns submission data, and that only an administrator in the portal seeds reference data now names the scoped tools. The ban on reading an end user's submission by any route stays.
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

`@formio/ai` — the Form.io agentic coding toolset. A pnpm + Turborepo monorepo shipping three things: the `@formio/mcp` Model Context Protocol server (`packages/mcp-server/`, exposing `form_*` / `role_* `/ `action_*` / `project_*` tools), the `@formio/ai` agent plugin (`plugin/`, bundling the server + skill library behind three manifests — `.claude-plugin/plugin.json` for Claude Code, `.cursor-plugin/plugin.json` for Cursor, and `plugin.json` + `mcp.json` for the vendor-neutral Agent Plugins layout), and a `@formio/skill-tests` package (`packages/skill-tests/`) that runs the `formio-sdk` skill's doc examples against the real `@formio/js`. The skill library lives at `plugin/skills/` (twelve activatable skills). It is also installable skills-only with `npx skills add formio/ai`, in which case [`plugin/skills/formio-mcp-setup/`](plugin/skills/formio-mcp-setup/) connects the MCP server on first use — every other skill carries a preflight that hands off to it when the Form.io tools are missing, and is forbidden from working around the gap with raw HTTP.
`@formio/ai` — the Form.io agentic coding toolset. A pnpm + Turborepo monorepo shipping three things: the `@formio/mcp` Model Context Protocol server (`packages/mcp-server/`, exposing `form_*` / `role_*` / `action_*` / `submission_*` / `project_*` tools), the `@formio/ai` agent plugin (`plugin/`, bundling the server + skill library behind three manifests — `.claude-plugin/plugin.json` for Claude Code, `.cursor-plugin/plugin.json` for Cursor, and `plugin.json` + `mcp.json` for the vendor-neutral Agent Plugins layout), and a `@formio/skill-tests` package (`packages/skill-tests/`) that runs the `formio-sdk` skill's doc examples against the real `@formio/js`. The skill library lives at `plugin/skills/` (twelve activatable skills). It is also installable skills-only with `npx skills add formio/ai`, in which case [`plugin/skills/formio-mcp-setup/`](plugin/skills/formio-mcp-setup/) connects the MCP server on first use — every other skill carries a preflight that hands off to it when the Form.io tools are missing, and is forbidden from working around the gap with raw HTTP.

## Repository Info

Expand Down Expand Up @@ -38,6 +38,8 @@ A record holds a project and its deployment as a PAIR, and resolution picks ONE

Skills never interview for these URLs and never restate the guidance the server owns — the one exception is `plugin/skills/formio-mcp-setup/references/project-urls.md`, the canonical copy every other document links to instead of restating. Before its first deployment-touching call, a tool-calling skill calls the `project_get` MCP tool with `cwd` set to the user's working directory and branches on the `status` it returns (`ok` / `not-configured` / `base-url-unresolved`), recording whatever value the report names with `project_set`; a call that fails outright rather than returning a status is a broken record, not an absent one, so it is relayed rather than interviewed around. Skills do NOT shell out to `npx @formio/mcp project get` for this — the connected server answers it over the open transport with the same resolver every other tool uses. The `project get` / `project set` CLI subcommands remain, for `formio-mcp-setup`, which runs before any tool exists to call. `formio-mcp-setup` is the handoff target for a resolution failure; `formio-resource-planner` is exempt because it calls no MCP tool. Writing a URL into a user's application (`Formio.setBaseUrl`, `Formio.setProjectUrl`, `FormioAppConfig`'s `appUrl` = Project URL and `apiUrl` = Base URL) needs the values rather than a deployment, so it takes them from `project_get` when the tools are callable and from the user when they are not — never hardcoded.

Submissions: the `submission_*` tools reach only submissions the server created and signed for the calling working directory. Each one carries a server-written `metadata.agent` tag, and its HMAC covers the project, form, owner, tag fields and the full stored `data` (obtained with a `?dryrun=1` pass first). The key is per directory, in `~/.formio/mcp-submission-keys.json`. Reads force the tag filters, build the query from structured input (never forwarding agent params), and drop every record that fails verification before the agent sees it. The rules for when the agent seeds reference data or writes test submissions live in one canonical guideline, [`plugin/skills/formio-mcp-setup/references/agent-submissions.md`](plugin/skills/formio-mcp-setup/references/agent-submissions.md), which other skills link to rather than restate. No skill reads an end user's submission by any route.

Authentication: the MCP server uses a browser-based portal-login flow — a short-lived local Express server renders the Form.io portal login form and captures the returned JWT via a `/callback` endpoint; `formioFetch` then attaches `x-jwt-token` on every request. Skills do NOT use PKCE or API-key auth.

Skill authoring conventions (not enforced by automated tests): the router's frontmatter and three-clause description, required reference files present and non-empty, the required reference-doc heading layout, the canonical portal-login JWT auth paragraph (except in `server-status.md`), and scope consistency. Terminology is strict, and one spelling per job: an `FORMIO_*` name means the **environment variable** and nothing else. That rule IS enforced for the two URL names — `FORMIO_PROJECT_URL` and `FORMIO_BASE_URL` — over every markdown file under `plugin/skills/`: see `packages/skill-tests/src/skill-descriptions/url-terminology.test.ts`, which rejects either name used as a substitution slot (`${…}`, `$…`, `{…}`, `{{…}}`, `<…>`, `YOUR_…`) or as the name of a value passed between phases, and allows the bare name only in a paragraph whose subject is the environment (an `env` block, an environment variable, the resolution order). `formio-angular/BOOTSTRAP.md`'s `FORMIO_ANGULAR_VERSION` / `FORMIO_JS_VERSION` and `formio-react/BOOTSTRAP.md`'s `FORMIO_REACT_VERSION` capture labels are outside that rule's scope — they name resolved npm versions, not URLs, and no environment variable of either name exists — so if the rule is ever meant to reach them, widen the validator rather than relying on the prose. A substitution slot in an endpoint heading or code example is `{projectUrl}` / `{baseUrl}` — single braces, so it stays distinct from Postman's `{{baseUrl}}`, which must not appear unresolved in prose. A value handed between phases or skills is named in prose ("the Project URL") or as a field called `projectUrl` / `baseUrl`. Spelling a slot or a handoff value `${FORMIO_PROJECT_URL}` tells an agent to read an environment variable in order to build a URL, which is a different and wrong action.
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,18 @@ The bundled `@formio/mcp` server exposes these tools. Skills prefer these over r
| `action_update` | Update an action. |
| `action_delete` | Detach an action from a form. |

### Submissions

Scoped to the agent's own work: each tool reaches only submissions this server created and signed for the calling working directory, and reports any other submission as not found. Used to seed a Resource a `select` reads and to write test submissions; see [`agent-submissions.md`](plugin/skills/formio-mcp-setup/references/agent-submissions.md).

| Tool | Purpose |
| --- | --- |
| `submission_create` | Create a signed `reference-data` or `test` submission (dry-run first, then write). |
| `submission_list` | List this directory's signed submissions on a form, by purpose and data filters. |
| `submission_get` | Get one of this directory's signed submissions. |
| `submission_update` | Replace the data of one of this directory's signed submissions. |
| `submission_delete` | Delete one of this directory's signed submissions. |

### Project

| Tool | Purpose |
Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/agent-scoped-submission-tools/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-tdd
created: 2026-09-29
Loading
Loading