Repository navigation
feat(mcp)!: freeze the 1.0 tool surface - #91
Merged
Merged
Conversation
Step 2 of the 1.0 plan: singular tool names (server_status replaces hello), form_publish / form_revert split out of form_update, one list contract with total and hasMore, acceptNoHistory in place of the revisions consent prompts, structured tool errors, open output schemas, a tools/list size budget, and the matching skill and README updates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Typed Form.io errors (FormioApiError, FormioNetworkError) and a code on
every resolution error. Error results carry { code, status?, body? } in
_meta["io.form/error"] and start their text with [CODE]; they carry no
structuredContent, which clients validate against the success schema.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every published outputSchema is a looseObject at every level and Mixed Form.io fields accept null, so a valid response with fields the schema does not list (_vid, pdfComponents, controller, esign, null settings) is no longer rejected. FORMIO_INSECURE_TLS and FORMIO_FORCE_BROWSER share readBooleanEnv. package.json exports only ./package.json; main removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
action_types_list -> action_type_list, form_revisions_list -> form_revision_list, hello -> server_status (server name, version and the cwd's project resolution, no Form.io request). form_list, role_list, action_list and form_revision_list share limit (default 100) / skip / sort / select and return total + hasMore read from Content-Range; a skip past the end is an empty page. form_list tags match all given tags; form_revision_list is newest first with compact fields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nsent prompts form_publish (formId, note) and form_revert (formId, version, note) are their own tools; form_update keeps the full update and draft: true, and a draft save takes the draft fields of the body it is given. Saving without revision history - unlicensed deployment, a form with revisions off, or an explicit revisions: "" - requires acceptNoHistory: true, otherwise the write is refused with HISTORY_NOT_ACCEPTED. The elicitation and local browser consent pages and the persisted consent file are removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
role_create takes a role object like role_update. Every formId argument (the action tools, form_update, form_publish, form_revert) shares one ObjectId check whose refusal points to form_get for the _id; UNKNOWN_ACTION_TYPE is wired. tools/list is trimmed from 80,656 to 48,649 characters under a tested 50,000 budget: the cwd description is 192 characters with the full rules in the server instructions, and project_set's rationale moves to the README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Skills and READMEs name the current 23 tools: action_type_list, form_revision_list, server_status, form_publish, form_revert, nested role_create, the list contract (total / hasMore), [CODE] errors and acceptNoHistory. The shared preflight paragraph lists every writing tool. The server README gains a conventions section and a CLI reference (flags and exit codes). New skill-tests checks keep retired names out and the preflight writing-tool list complete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The revisions licence probe is three-state; only a definite answer is cached, and an undetermined licence neither strips history nor refuses as unlicensed. draft/publish/revert report the probe's own failure. - project_set, token validation and browser-login failures carry codes (INVALID_ARGUMENT, NETWORK_ERROR, AUTH_REQUIRED); role_update's roleId check moved into the handler. A test drives every registered tool into an error and requires a [CODE]. - form_revision_list excludes the draft row; form_revert refuses version "draft" and points to form_publish. - A full page with no reported total sets hasMore; the per-form history check selects only the fields it reads; Form.io errors given as arrays reach the message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- form_revert refuses any version that resolves to the draft revision
("latest", the draft's _id), checked on the fetched revision.
- A draft save refuses a changed non-draft field (title, path, access, ...)
instead of dropping it; values form_get returns unchanged pass.
- Login-form failures keep their code (NETWORK_ERROR, NOT_FOUND, ...);
only no-browser and timeout are AUTH_REQUIRED.
- An undetermined licence is remembered for 60 seconds.
- project_set refuses empty URLs with INVALID_ARGUMENT.
- Docs, descriptions and formio-api references state Form.io's PUT
semantics: top-level fields left out keep their stored value; arrays
and objects sent replace the stored ones whole.
- total is omitted when Form.io reports none; form_list refuses a tag
containing a comma; action_type_list returns its catalog directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a call passes no cwd, the server picks the directory to resolve the project for: the client's roots (MCP roots/list; one root as is, several narrowed to the one holding a project record, otherwise INVALID_ARGUMENT listing them), then CLAUDE_PROJECT_DIR, then its own working directory. Roots are cached, re-read after roots/list_changed, and time out after 2s. project_get and server_status report cwdSource; only a fallback to the server's directory tells the agent to pass cwd. cwd stays optional, and the skills' preflight calls project_get without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Several roots with no project record fall through to CLAUDE_PROJECT_DIR and the server's directory, so environment-only setups keep working; only roots resolving to different records are refused. - Roots are compared by the record they resolve to, so roots inside one repository sharing a formio.json are one project; root paths are normalized before de-duplication. - A failed or slow roots re-read keeps the last good roots. - A broken formio.json or mapping entry counts as a record, so its error surfaces instead of another root being chosen. - CLAUDE_PROJECT_DIR is a guess like the server's own directory: it is reported and tells the agent to pass cwd. - CLAUDE.md, the design and the skills describe the preflight without cwd. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cwd description is 82 characters (full rules stay in the server instructions); output schemas describe only the fields callers branch on (status, total, hasMore, remedy, ok) and declare only the document fields callers act on, staying open so every other field passes through; nested Form.io body fields leave their documentation to the skills; project_set states only the rules it enforces. No tool, argument or schema removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
1.0.0 makes the
@formio/mcptool surface a contract. Before this change it was inconsistent in ways an agent paid for on every call, and parts of it were wrong:helloreported nothing a caller needed.form_updatefolded four operations behind flags. Publish and revert ignored the requiredformbody, and draft mode refused fieldsform_getreturns.countwas the page length, and the multi-tag filter matched nothing._vid,pdfComponents,esign,settings: null).tools/listcost ~68.5k characters.What changes — BREAKING for any client calling the tools directly
action_types_list→action_type_list,form_revisions_list→form_revision_list,hello→server_status.server_statusreturns the server name and version plus thecwd's project resolution, without making a Form.io request. The old names are not registered.form_publish/form_revertare now separate tools.form_updatekeeps the full update anddraft: true. Draft mode saves the draft fields of the body it is given, soform_get's output can be passed back.formIdis always a 24-character ObjectId.formIdOrPathaccepts either form and is used byform_get,form_revision_listandform_revision_get. These follow the routes Form.io resolves by path: the action-type catalog route does not resolve a path.role_createtakes a nestedroleobject, likerole_update.limit(default 100),skip,sort,select.totalandhasMore, read fromContent-Range. A full page with an unknown total reportshasMore: true.countis removed; a skip past the end returns an empty page; tags match all of the given tags;form_revision_listis newest first and excludes the unpublished draft row.acceptNoHistoryreplaces the consent prompts. A write that would save without revision history needsacceptNoHistory: true, otherwise it is refused withHISTORY_NOT_ACCEPTEDso the agent can ask the user. That covers three cases: an unlicensed deployment, a form with revisions off, or an explicitrevisions: "". The elicitation path, the local browser consent pages and the persisted consent file are removed. The licence probe has three states. Only a definite answer is cached, and an undetermined licence never strips history.[CODE]from a fixed set, and the result carries{ code, status?, body? }in_meta["io.form/error"]. Error results carry nostructuredContent, because clients validate it against the tool's success schema even whenisErroris set. Text after the prefix is unchanged, so the error part is additive.null.tools/listis ~37.6k characters (from ~68.5k), under a tested 42,000 budget. Thecwddescription is 82 characters; output schemas describe only the fields callers branch on (status,total,hasMore,remedy,ok) and declare only the document fields callers act on — they stay open, so every other Form.io field passes through; nested Form.io body fields are documented by the skills;project_setstates only the rules it enforces.cwdcomes from the client. A call withoutcwdresolves its project for the client's workspace root (MCProots/list; one root as is, several narrowed to the one holding a project record, ambiguous ones refused withINVALID_ARGUMENT), thenCLAUDE_PROJECT_DIR, then the server's own directory — all through the one resolverproject getuses.project_get/server_statusreportcwdSource; only a guessed directory tells the agent to passcwd. Roots are cached, re-read onroots/list_changed, keep the last good list on a failed re-read, and time out after 2s.cwdstays optional; the skills' preflight callsproject_getwithout it.FORMIO_INSECURE_TLSandFORMIO_FORCE_BROWSERshare one boolean parser.@formio/mcpexports only./package.json; the package is itsformio-mcpbinary.formio-actionsandformio-apireferences, and therole_createexamples. The server README gains a conventions section and a CLI reference.~/.formio/projects.jsonkeeps its current format. That was decided during the proposal, because renaming its keys would need a migration or break existing mappings.Verification
pnpm test:@formio/mcp1543,@formio/skill-tests1273.pnpm lint,pnpm format,pnpm check:releasesandopenspec validate --strictalso pass.[CODE];tools/listsize budget;role_createfields, listcount, or the consent prompt;sort=-_vidputs the draft revision first,_vid__ne=draftremoves it, andContent-Rangereports the reduced total, including on a 206 partial page.b82b707), on this PR (10 findings, resolved inbdd5804), and on thecwdchange (9 resolved in54076dc, 1 declined).Release note
This carries a major changeset for
@formio/mcpand@formio/ai. Merging it does not publish anything. Hold the Version Packages PR until the remaining 1.0 changes land (auth and error hardening, local state and release, skill corrections, routing, planner delta mode, drift guards). Merging the Version Packages PR before then would release 1.0.0 early.Known follow-ups, not in this PR:
hello;.mcpbbundle reports version0.0.0(this predates the branch);plugin/README.md's environment-fallback sentence.OpenSpec change:
openspec/changes/freeze-1-0-tool-surface/.🤖 Generated with Claude Code