DEV Community

Keo Fung | FormLM
Keo Fung | FormLM

Posted on

From a Sentence to a Live Form: The Complete Timeline

People keep asking me: "How long does it actually take?" Like, from the moment you type a sentence to the moment you have a live, shareable URL people can fill in. Not the marketing answer ("90 seconds!") — the real answer, including every tool call, every round-trip, every second of waiting.

So I timed it. Here's the complete timeline of building a personality assessment form with Claude Desktop and the formlm-cli MCP server, from first keystroke to live URL.

The Prompt (0:00)

I typed this into Claude Desktop:

"Build a Big Five personality assessment. 10 items, 5-point Likert scale (1=Strongly Disagree to 5=Strongly Agree). Two items per trait: openness, conscientiousness, extraversion, agreeableness, neuroticism. Mix some reverse-scored items in. Name it 'Big Five Personality Quick Check.' Then publish it and give me the URL."

One sentence. 43 words. No field IDs, no type names, no API parameters.

Tool Call 1: app_create (0:03)

Claude → MCP: app_create
  name: "Big Five Personality Quick Check"
  description: (omitted)
Enter fullscreen mode Exit fullscreen mode
MCP → Server: POST /api/v1/mcp/exec
  cmd: "assess app create --name 'Big Five Personality Quick Check' --json"
Enter fullscreen mode Exit fullscreen mode
Server  MCP: { code: 0, data: { appId: "a8f3k2", name: "Big Five Personality Quick Check" } }
Enter fullscreen mode Exit fullscreen mode

Round-trip time: ~800ms. The app was created.

Elapsed: 0:04

Tool Call 2: field_schema (0:04)

Before adding any fields, Claude checked what field types were available.

Claude → MCP: field_schema
Enter fullscreen mode Exit fullscreen mode
Server response: input, textarea, radio, scale, date, ...
Enter fullscreen mode Exit fullscreen mode

This is a pattern I've seen consistently — Claude always checks the schema before adding fields. It's reading the available types like a developer reading API docs.

Round-trip time: ~600ms.

Elapsed: 0:05

Tool Call 3: field_config (0:05)

Claude wanted to know what properties a scale field accepts.

Claude → MCP: field_config
  type: "scale"
Enter fullscreen mode Exit fullscreen mode
Server response: { min, max, title, required, score, ... }
Enter fullscreen mode Exit fullscreen mode

It found min and max — exactly what it needed for a 5-point Likert scale.

Round-trip time: ~500ms.

Elapsed: 0:06

Tool Calls 4-13: field_add × 10 (0:06 - 0:22)

Claude added 10 scale fields, one for each item. Here's the first one:

Claude → MCP: field_add
  appId: "a8f3k2"
  id: "o1"
  type: "scale"
  title: "I have a vivid imagination"
  min: 1
  max: 5
Enter fullscreen mode Exit fullscreen mode
Server response: { code: 0, data: { fieldId: "o1", ... } }
Enter fullscreen mode Exit fullscreen mode

Each field_add call took about 1.5 seconds (including Claude's thinking time between calls). 10 fields × 1.5s = 15 seconds.

Here's the full field list Claude generated:

ID Trait Question
o1 Openness I have a vivid imagination
o2 Openness I prefer variety over routine
c1 Conscientiousness I am organized and pay attention to details
c2 Conscientiousness I tend to leave things until the last minute (reverse)
e1 Extraversion I feel energized around other people
e2 Extraversion I prefer quiet, solitary activities (reverse)
a1 Agreeableness I trust people easily
a2 Agreeableness I can be cold and uncaring (reverse)
n1 Neuroticism I worry about things more than others
n2 Neuroticism I stay calm under pressure (reverse)

Claude correctly identified which items should be reverse-scored and marked them with "(reverse)" in the title. It didn't have a --reverse flag to set (the MCP tools don't expose reverse-scoring configuration yet), so it used textual cues.

Elapsed: 0:22

Tool Call 14: field_list (0:22)

Before publishing, Claude called field_list to verify all 10 fields were there.

Claude → MCP: field_list
  appId: "a8f3k2"
Enter fullscreen mode Exit fullscreen mode
Server response: [o1, o2, c1, c2, e1, e2, a1, a2, n1, n2]
Enter fullscreen mode Exit fullscreen mode

10 fields confirmed. This is a verification step — Claude checking its own work before moving to the publish phase.

Round-trip time: ~700ms.

Elapsed: 0:23

Tool Call 15: share_publish (0:23)

Claude → MCP: share_publish
  appId: "a8f3k2"
Enter fullscreen mode Exit fullscreen mode
Server response: { code: 0, data: { published: true, formDay: 3650000 } }
Enter fullscreen mode Exit fullscreen mode

The form was live. The formDay: 3650000 is the hardcoded "never expires" default — the AI doesn't need to decide on an expiry date.

Round-trip time: ~900ms.

Elapsed: 0:24

Tool Call 16: share_url (0:24)

Claude → MCP: share_url
  appId: "a8f3k2"
Enter fullscreen mode Exit fullscreen mode
Server response: "https://formlm.me/s/a8f3k2"
Enter fullscreen mode Exit fullscreen mode

The MCP server constructs the full URL by combining the server's base URL with the relative path returned by the API. Claude received the URL and displayed it to me.

Round-trip time: ~500ms.

Elapsed: 0:25

The Total

From first keystroke to live URL: 25 seconds.

16 tool calls. 6 round-trips to the server for API operations (app create, schema, config, field add ×10 batched mentally, field list, publish, URL). Total server processing time: ~7 seconds. Total Claude thinking time: ~18 seconds.

The form was live, accessible, and accepting responses. 10 fields, 5-point Likert scale, all 10 items present and correctly ordered.

What's Missing

The form worked, but it wasn't a complete assessment. Here's what was missing:

No scoring. The form collected answers but didn't compute trait scores. Each trait has 2 items, and 4 of them are reverse-scored — but the scoring engine needs that information configured in the Scale module, which the MCP tools didn't expose at the time. So the form was a data collection tool, not an assessment engine.

No dimension labels. The fields have IDs like o1, o2, c1, c2 — meaningful to me, but not to the system. The dimension mapping (o1+o2 = openness score) needs to be configured separately.

No report template. When someone completes the assessment, they get a "thank you" page, not a personality profile. The report generation is a separate module.

So 25 seconds gets you a live form. Getting a live assessment — with scoring, dimensions, and a generated report — took another 5-10 minutes of manual configuration in the web UI. Still fast, but not the "one sentence to full assessment" dream.

The Tool Call Breakdown

Phase Tool Calls Time Round-trips
App creation 1 (app_create) 4s 1
Schema discovery 2 (field_schema, field_config) 2s 2
Field creation 10 (field_add ×10) 16s 10
Verification 1 (field_list) 1s 1
Publishing 2 (share_publish, share_url) 2s 2
Total 16 25s 16

16 round-trips in 25 seconds. That's roughly 1.5 seconds per round-trip, which includes network latency, server processing, and Claude's thinking time between calls.

What Slowed It Down

Nothing, really. This was a clean run — no errors, no retries, no duplicate fields. The 25 seconds was almost entirely Claude thinking and the network round-tripping.

The slowest part was the 10 field_add calls. Each one is a separate API call. If I added a batch field_add_many tool that accepted an array of fields in one call, I could cut the round-trips from 16 to 7, and the total time from 25 seconds to maybe 12 seconds.

But batching has a downside: if one field in the batch fails, do you fail the whole batch or skip the failed one? I haven't figured out the right error semantics yet, so I'm keeping individual field_add for now.

The Honest Answer

"How long does it take?" — 25 seconds for a 10-field form, from sentence to live URL. That's the honest answer.

But 25 seconds gets you a form. A real assessment — with scoring, reverse items, dimensions, and a report — used to take another 5-10 minutes of manual work.


The formlm-cli MCP server can build and publish forms in under 30 seconds. Open source at github.com/formlm/cli — try it at formlm.me.

Top comments (0)