Connects the Form.io MCP server (@formio/mcp) to whatever coding agent is running, so the Form.io skills have tools to call...
The Form.io skills call tools — form_list, form_create, project_import, project_set, project_get, and the rest. Those tools come from the @formio/mcp server. Skills installed on their own (npx skills add formio/ai) arrive without it, because that installer handles skills only and never touches MCP configuration.
Your job here is to write that configuration, get it approved, and hand control back.
formio-auth is about how an application's own users sign in, not about this.You are offering to capture which Form.io project to use — see Step 4. That is one errand for the user rather than two, and it means the first Form.io tool call after the reload works instead of failing with "no project configured". It is an offer, never a requirement.
Check whether tools named form_list, form_create, project_import, project_set, and project_get are available to you.
project_get → the connected server predates that tool, which shipped after the rest of the Form.io tool surface. Skip to "Already connected, but too old" below; the configuration exists, and whether its pinned version can be moved forward is the first thing that branch checks. Decide this from the tool list you can actually call, never from a version number written in prose: the launch commands in this document are restamped on every release and sentences about them are not, so a number here goes stale by one release the moment project_get ships.Do not write a fresh configuration and do not treat this as a missing server. First ask where the server came from, because a plugin install has no file for you to edit: if the user installed the plugin through a marketplace (Claude Code /plugin install, Cursor's Customize panel, copilot plugin install, VS Code's Install Plugin From Source, or the Codex plugin directory), the pinned version belongs to the plugin, so tell them to update the plugin through the same route they installed it and then reload — do not go hunting for an mcpServers entry nothing wrote, and do not write one, which would leave two servers configured.
Otherwise the entry is in a file: find the existing formio-mcp entry in the file your client reads — identified the way Step 2 identifies it — read the version it pins, and compare that against the version the Step 2 blocks below carry. Branch on the comparison rather than editing straight away.
project_get, so the client is still running an older process. Do not edit the file: send the user to Step 5 to reload, and check the tool list again afterwards.A not-yet-reloaded install is a different case from a stale pin, and it looks the same from here. If the tools are missing ENTIRELY and the user says they already installed the plugin through a marketplace (the routes above), the server came with it and the likely cause is that the client has not reloaded since — send them to Step 5 rather than writing files. If the tools are present and only project_get is missing, a reload changes nothing: the plugin's own pin is what is stale, so it is the plugin that has to be updated. Ask which of the two they are in rather than guessing, because the remedies do not overlap.
There is no universal MCP configuration file. Each client reads a different path, and two of them do not use the mcpServers key at all:
| Client | File | Key |
|---|---|---|
| Claude Code | .mcp.json |
mcpServers |
| Cursor | .cursor/mcp.json |
mcpServers |
| VS Code / GitHub Copilot | .vscode/mcp.json |
servers |
| Codex / ChatGPT | .codex/config.toml |
TOML [mcp_servers.formio-mcp] |
Write the one file the client you are running in reads. Four files for a user who runs one client is three changes they have to review, keep, or delete for products they do not use. Establish the host in this order, stopping at the first step that answers:
Do not use the filesystem as the signal. An existing .vscode/, .cursor/, or .claude/ directory says somebody once opened this workspace in that editor, not that it is running you now; a workspace that has all three would resolve to whichever you checked first.
Write more than one file when — and only when — the user says they work in more than one client here. Every path in the table is workspace-relative; never write into the user's home directory.
Show the user every file you intend to write, in full, before writing anything. The blocks below carry all four; show the ones you selected.
.mcp.json — Claude Code
{
"mcpServers": {
"formio-mcp": {
"command": "npx",
"args": ["-y", "@formio/mcp@1.0.1"]
}
}
}
.cursor/mcp.json — Cursor
{
"mcpServers": {
"formio-mcp": {
"command": "npx",
"args": ["-y", "@formio/mcp@1.0.1"]
}
}
}
.vscode/mcp.json — VS Code and GitHub Copilot. Note the key: servers, not mcpServers.
{
"servers": {
"formio-mcp": {
"command": "npx",
"args": ["-y", "@formio/mcp@1.0.1"]
}
}
}
.codex/config.toml — Codex and ChatGPT. TOML, with no JSON equivalent.
[mcp_servers.formio-mcp]
command = "npx"
args = ["-y", "@formio/mcp@1.0.1"]
Every block above launches the package at the exact version written into it — @formio/mcp@<version> — never a floating @formio/mcp. An unpinned npx resolves whatever the registry serves at the moment the client starts the server, so the code that gains tool access to the user's Form.io deployment is chosen at run time rather than reviewed once. Pinned, the user runs the exact build this skill was written against, and an upgrade is a visible edit to a file they approved. Write the version exactly as it appears here — do not substitute latest, a caret range, or a version you looked up yourself. If the user asks for a newer server, change the number in every file you wrote and tell them what changed.
The package is first-party: @formio/mcp is published from the same repository as these skills (formio/ai) through npm Trusted Publishing, so npm carries a provenance attestation for the build.
Read each file first if it exists. Add the formio-mcp entry alongside any existing servers — never overwrite a file that already configures other MCP servers, and never reformat entries you did not add. If a formio-mcp entry already exists and matches, leave it alone and go to Step 4.
Mention once, without deciding for the user: these files carry no secrets, so committing them lets teammates on other agents pick up the same server. A user who would rather keep them local should add them to .gitignore.
Ask for explicit approval before writing. Write exactly the set Step 2 selected — all of it, minus any file that already carries a matching entry, or none of it. Do not quietly add files the user did not approve, and do not drop one you showed them.
After writing, list the paths you created or modified.
The server starts with no project. Left unconfigured, the first Form.io tool call after the reload fails with an actionable error — a good error, but one the user resolves mid-task when it could have been resolved here. So probe now, and ask only for what the probe says is missing.
references/project-urls.md is this library's canonical description of the two values you may end up asking for — what a Project URL is in each of the three deployment shapes, how the Base URL is derived from it, and the one shape where it cannot be. Read it before you ask the user for either one, here or on behalf of a skill that has no server to ask. Every other skill in the library links to that file rather than restating it, and so does this one.
npx -y @formio/mcp@1.0.1 project get --cwd "$(pwd)"
On a zero exit it prints the Project URL, the Base URL, and which source supplied each. The work is already done: report both URLs in one line and go to Step 5. Do not interview for something already on record.
One trap to know about: an @formio/mcp too old to have the project command ignores these arguments, starts its stdio server, reads end-of-input and exits 0 with no output — so project get looks like a success that found nothing, and project set writes nothing while reporting nothing. Empty output is not a project. Never report a mapping you did not read in the output, and never tell the user a project was persisted when project set printed nothing.
Read the Source: line for what it is: this command runs in your shell, and the MCP server's own environment is not visible from there. That does not make the answer unreliable — the environment is the weakest source, so a FORMIO_PROJECT_URL or FORMIO_BASE_URL set in the server's own env block cannot override what this command reports. When the line names your shell's FORMIO_PROJECT_URL, that is a value you can still redirect: record one here and the mapping wins. When it names the working-directory mapping or a committed formio.json, that is what is on disk and what the server resolves.
On exit 1 — nothing is recorded for this directory — the command explains what is missing and names the command that fixes it. Relay that instruction to the user, ask for the single value it names, and persist it with the command it named:
npx -y @formio/mcp@1.0.1 project set --project-url "<project url>" --cwd "$(pwd)"
When the working directory is inside a git repository, offer the choice of where to record it, in the same round you ask for the URL. A committed formio.json in the application's own folder records the target with the code — shared with everyone who clones the repository, and it survives a fresh checkout — while project set records it for this machine only. The committed file is hand-authored: the server reads it and never writes it, so if the user picks it, write the file yourself, in the application's own folder and never an ancestor (discovery walks upward, so a file placed higher governs every unrelated folder beneath it):
{ "projectUrl": "<project url>" }
Say the consequence in one line and let the user pick; do not explain how the two are ranked, because project get reports which one supplied a value. Outside a git repository, do not offer the committed file — nothing would be tracking it.
Then re-run project get. Most of the time that is the end of it: the Base URL is derived from the Project URL — https://api.form.io for a project on a form.io host, the parent path for a project addressed as a sub-directory — so there is no second value to collect.
The exception is a Project URL that is a plain sub-domain of the user's own domain, e.g. https://myproject.mysite.com, whose deployment is a sibling sub-domain that nothing in the Project URL names. A record holds a project and its deployment together, so that shape cannot be recorded on its own: the project set above exits 1 naming --base-url as the value it still needs, and prints the command carrying both. Ask the user for the Base URL alone at that point — never before, and never by assuming a default — and run what it printed:
npx -y @formio/mcp@1.0.1 project set --project-url "<project url>" --base-url "<base url>" --cwd "$(pwd)"
A deployment on its own is a valid update only for the record that already holds the project — this directory's own mapping. Where the project lives in a committed formio.json, the refusal names that file's path and the "baseUrl" key to add beside "projectUrl" — an edit you make to the file, since the server never writes one. Where it lives in the environment, the refusal names the mapping write carrying both URLs. Either way the user is asked for one value: the message carries the Project URL the report already printed.
An exit 3 from either command is the same round reached from the other side. The project is already recorded — in a committed formio.json, in this directory's own mapping, or in the environment — and only its deployment is missing, which is what happens in a repository that ships a formio.json naming a project whose deployment cannot be derived, or in a container configured entirely by environment. Ask the user for the Base URL alone and do exactly what the report says: run the command it printed, or, for a committed formio.json, add the "baseUrl" key to the file it names. It carries the Project URL for you.
project set exits 3 for the state it just left the directory in, not for a write that failed: the record it was given went to disk, and the committed file that governs the directory still supplies no deployment. Its stdout carries the same block get prints — the pair that RESOLVES, with the record just written named in the note beneath it — and stderr carries the report, so treat it exactly as the get above — ask for the Base URL alone and make the edit the report names. Do not re-run the same project set; it would write the same record and report the same gap.
Exit 1 from either command is an answer, not a failure. It names one missing value and the command that supplies it. That is the code to act on.
Exit 2 is none of those branches. It means the command could not answer at all — an unreadable ~/.formio/projects.json, a formio.json that will not parse, a malformed URL. Do not interview and do not run project set: it would fail for the same unreported reason, and the user would see an interview-then-error loop that never names the cause. Relay the message, which names what to fix and whether a repair has to happen by hand first, and treat the step as skipped.
Do not compose your own version of this guidance. The server's messages carry the valid URL shapes, an example of each, and why a value cannot be guessed — they reach an agent that never read this skill, so they are the single copy. Relay them; do not paraphrase them, and do not add shape rules of your own here.
Report what the final project get prints — and if it prints nothing, report that nothing was persisted rather than that the project was set. The mapping is read at tool-call time, so it is live the moment the server starts; there is nothing further to configure after the reload.
Two things never to do here. Never edit ~/.formio/projects.json yourself: its shape, its 0600 mode, and its merge rules belong to the server, and the commands above are how you reach them. And never put FORMIO_PROJECT_URL into a client configuration file's env block — not because it would pin anything, but because it is the wrong scope for the value: an env block is one answer for every directory that client opens, while a Form.io project is one-to-one with the application built against it. Record it per directory with the commands above instead.
FORMIO_BASE_URL is safe in an env block, read from the other end: a project and its deployment resolve from ONE record, and the environment is the weakest of the three — so those variables answer only where neither a committed formio.json nor this directory's mapping names a project, and they are ignored entirely as soon as one does. Where the environment IS the record that answers, its FORMIO_BASE_URL is that project's deployment, exactly as a baseUrl recorded in either of the other two records would be. Setting it blocks no later project_set; do not strip it from a configuration that has it, and do not add it to one that does not — that value is the host's prompt to own.
Skipping is a normal outcome, not a failure. Skip when:
Say what happens instead, in one line: the first Form.io tool call will raise the same actionable message, and project_set will handle it then. Then continue to Step 5. Do not re-ask, and do not describe setup as incomplete.
If the command fails, or succeeds while printing nothing — the pinned version unavailable, a blocked registry, or an older binary that swallowed the arguments — treat it exactly like a skip. Report what failed in one line, name project_set on the first tool call as the fallback, and carry on to Step 5. The server configuration you wrote in Step 3 is still good.
MCP configuration is read when a session starts, not when a tool is called, so the new server does not exist until the client reloads. Tell the user the step for the client Step 2 identified. If Step 2 fell back to writing everything, give the whole list instead:
/mcp to reconnect.formio-mcp off and on under Customize → MCP, or restart Cursor..codex/config.toml.Then stop. Ask them to reload and re-issue the request that brought them here, and say which skill will pick it up. Do not claim the original task is finished, and do not attempt it without tools.
Every configuration in this skill names an exact server version. If npx fails with E404 — npm error 404 Not Found - GET https://registry.npmjs.org/@formio%2fmcp naming the pinned version specifically — the package exists but that release has not landed on npm yet. It is a release still in flight, not a broken configuration, and it resolves itself within minutes.
Tell the user that, then offer one of two things rather than editing the pin yourself: wait and reload, or install the immediately preceding published version — npm view @formio/mcp versions lists what npm actually has, and the highest entry below the pinned one is the fallback. Say which version you used and that it is a temporary substitute. Never replace the pin with @latest or an unpinned @formio/mcp: that is the pattern the pin exists to remove, and it silently outlives the release it was meant to work around.
npx cannot reach the registrySome environments block the public npm registry — an air-gapped network, a locked-down corporate host that serves an internal mirror instead. Two alternatives, in order of preference:
Global install from an internal registry or a cached tarball, then point the configuration at the binary instead of npx:
npm install -g @formio/mcp@1.0.1
Replace "command": "npx", "args": ["-y", "@formio/mcp@1.0.1"] with "command": "formio-mcp", "args": [] in every file you write (and the TOML equivalent).
The desktop bundle. For Claude Desktop and other hosts that accept one, the .mcpb bundle attached to each GitHub release carries the server with no registry access required.
If neither is possible, say so plainly rather than leaving the user with a configuration that cannot start.
In Claude Code, the formio-ai plugin runs the server it carries rather than launching it through npx, and Claude Code installs that server's dependencies itself, from the plugin's lockfile, when the plugin is installed or updated. That install downloads straight from the public npm registry and does not read .npmrc, so on a network that reaches npm only through a private mirror it fails, and the plugin's skills arrive with no Form.io tools.
If the plugin is installed and the tools are still missing, run Steps 2 to 5 for Claude Code exactly as you would without the plugin, writing the .mcp.json Step 2 previews. Its npx launch reads .npmrc, so it installs through the mirror; if npx cannot reach any registry either, the section above applies. Keep the plugin enabled — it still supplies the skills — and tell the user that if the plugin's own server later starts, they can delete the .mcp.json entry so the tools are not listed twice. Do not edit, patch, or install anything inside the installed plugin's directory: Claude Code replaces that directory on every update.
If the user declines setup, or setup cannot complete, stop. Do not fall back to direct HTTP requests against a Form.io deployment, and do not write a throwaway script that makes them for you. The skills document the full REST surface, which makes hand-rolling requests tempting and wrong: it bypasses the guardrails the tools enforce, and it can write to a live deployment in ways nobody reviewed. Report what is blocking and let the user decide.
This is about build-time work — what you do in this session on the user's behalf. An application built with these skills calls the Form.io REST API at runtime as a matter of course; that code is not a workaround and is not covered by this rule.