DEV Community

Paulo Steinberg
Paulo Steinberg

Posted on AI-assisted

Making sure the AI publishes the form I actually looked at

I build a form builder (trulyform), and for a while the AI story was the usual one: a "generate with AI" box in the editor, as if everyone was trying to be ChatGPT.

It works, but it's the wrong place for it. If I'm already in Claude or Cursor working out what to ask a client, I don't want to switch tabs and retype it. And the box in my editor doesn't have my context or my skills. The assistant I already use does.

So I put an MCP server in front of the same thing the editor uses. You add one URL, sign in, approve access, and that's it. No API key to paste.

https://trulyform.com/mcp
Enter fullscreen mode Exit fullscreen mode

It works in Claude, Claude Code, ChatGPT, Cursor, Codex, VS Code with GitHub Copilot and Mistral Vibe, and it's on the free plan.

Then you just ask:

Make a client intake form for my design studio. Ask about their business, their goals, and any date they're working toward.

and a draft shows up in your account.

So here is what Claude did:

Claude creating a client intake form in trulyform through MCP

And the form:

The client intake draft in the trulyform editor

What the assistant gets

The server is small on purpose. The model gets these tools:

  • list_forms and get_form to find and read forms
  • create_form and save_form_draft to make and edit drafts
  • list_field_types to learn the form format
  • publish_form to put a specific version live
  • list_responses, get_response and form_stats for the answers
  • read_docs for everything else

list_field_types turned out to matter more than I expected. Instead of describing the form schema in a long tool description and hoping the model remembers it, the tool returns every question type with its settings, its logic operators, the shape of its answer and a ready-to-edit template. It also hands out fresh ids for new questions and choices. The model copies a template and fills it in, instead of guessing what a dropdown looks like.

What it's not allowed to do

The part I spent the most time on is what the assistant is not allowed to do. Saving never publishes. It can create and edit drafts, read forms and responses, and publish when you tell it to publish that specific version. I didn't want a typo in a prompt to put a half-finished form in front of a real client.

"That specific version" is doing a lot of work in that sentence, so here's how it actually works.

Every draft has a revision number. When the model reads a form, it gets the current draft_revision. To save, it has to send that number back as base_revision. If I changed the form in the browser in the meantime, the numbers don't match, the save is rejected with a draft_conflict error, and the model has to read the form again and redo its change on top of mine. Without that check, an assistant working from a stale copy would quietly overwrite whatever I'd just fixed by hand.

Publishing works the same way. publish_form doesn't publish "whatever the draft is now". It publishes the exact revision I reviewed. So the flow is: the model saves a draft, I open it in the editor and look, I say "publish it". If anything touched the draft between my look and that call, the revision is stale and the publish is refused. It's also marked as a destructive tool, so clients that respect tool annotations ask before running it, and its description tells the model to only call it when I ask for it.

A couple of smaller things fall out of the same idea:

  • Model clients retry more than you'd think, usually after a dropped reply. Every save carries a mutation_id, and a retry with the same id and the same payload counts as the same save, not a second one.
  • create_form can't be made safe to retry that way, since each call makes a new form. Its description tells the model to check list_forms first if a reply got lost, so I don't end up with three copies of "Client intake".
  • Republishing identical content doesn't create a new version, and responses people already sent stay tied to the version they actually answered.

None of this is specific to MCP. It's what you'd do for any API where two writers can touch the same document. But with an agent as one of the writers, it stops being optional.

Reading the answers

Reading responses has been the more useful half for me than I expected. "Group the free-text answers by theme and tell me how many people said each" is a decent first pass. It's only a first pass, though. It will happily sound confident about 12 responses, so I still skim the raw answers before I act on a summary. trulyform hands over the responses; the summary is the model's work, and I treat it that way.

If you try it and something breaks, tell me here. It's just me, so I'll see it.

Top comments (0)