Skip to main content
Glama

Create a FoxForm form

foxform_create_form

Create a new form, including per-screen conditional logic. Requires a WRITE-scoped API key.

Args:

  • title (string): form title (required)

  • description (string, optional)

  • theme (string, optional): one of amber|base|midnight|ocean|sunset|forest|lavender|minimal (default amber, the copper-to-amber FoxForm theme)

  • questions (array, optional): array of screen objects ({ id, type, title, required, variableName?, choices?, logic?, ... }); omit to start empty

  • thank_you_message (string, optional)

  • format ('single_question' | 'single_page' | 'chatbot', optional): how the form is presented (see below)

  • sections (array, optional): single_page page groups, [{ id }]

  • chatbot_skin ('instagram' | 'whatsapp', optional): chatbot chrome

  • chatbot_profile (object, optional): chatbot header profile ({ name, handle, followers_text, phone, status, verified, avatar_url })

Returns: { form } with the created form (including its id and slug). The form starts as a draft — call foxform_publish_form to make it live.

Screen fields are validated: unknown fields are REJECTED instead of being stored and ignored, and branching rules are cross-checked against the screen ids in the same payload.

Form format (DEVF-296 / DEVF-322): pass format to choose how the form is presented.

  • 'single_question' (default): one screen at a time (classic).

  • 'single_page': every screen on one scrollable page. Optionally group screens into pages with sections: [{ id }] in order (the 'Section N' label comes from position; no editable title).

  • 'chatbot': an Instagram/WhatsApp DM-style conversation. Set chatbot_skin ('instagram' | 'whatsapp', default instagram) and chatbot_profile (all fields optional, each with a fallback). The question/branching engine is unchanged — only the presentation differs. These fields require the account's form-format feature to be enabled (live in production); when off the API ignores them and the form stays single_question.

End screen ('end'): title + description, an optional button (buttonEnabled false hides it; buttonText, buttonLink, buttonOpenNewTab) and an optional automatic redirect after submit: autoRedirect true + autoRedirectUrl (absolute http/https URL; the form link's query params such as utm_* are passed along and {{param}} tokens are filled from them) + autoRedirectDelay (seconds 0-10, default 3). The response is recorded before the redirect; respondents see a "Redirecting in N s" line with a "Go now" link.

CONDITIONAL LOGIC (branching), per screen — stored in questions[].logic:

logic.conditionalNavigationV2 = { enabled: true, groups: [ // groups are OR-joined; FIRST matching group wins { id: "grp-1", conditions: [ // conditions inside a group are AND-joined { id: "cond-1", left: "{{quer_testar}}", operator: "equal_to", right: "Ainda não" } ], then: { type: "specific_screen", targetScreenId: "s-motivos" } } ] }

  • then.type: 'next_screen' | 'previous_screen' | 'specific_screen' (needs targetScreenId = another screen's id) | 'end_form'. Add then.url (+ optional openNewTab) to redirect to an external URL instead.

  • operator: 'equal_to' | 'not_equal_to' | 'greater_than' | 'greater_or_equal_than' | 'less_than' | 'less_or_equal_than' | 'contains'.

  • left/right are EXPRESSION strings: a literal ("10", "Ainda não"), a variable ("{{score}}", "{{minha_var}}" = the screen's variableName), or arithmetic ("calc({{peso}}/(({{altura}}/100)*({{altura}}/100)))").

  • Comparing an ANSWER: use left: "{{<variableName of the deciding screen>}}" and right = the option's label OR its value (both match).

  • {{score}} is the running sum of points on the options picked so far (choices[].points, images[].points) — that is how score-based branching works.

  • A navigation group with no conditions NEVER matches. enabled: false stores the rules but disables them.

  • Screen-level conditional display uses the same group shape: logic.display = { enabled: true, groups: [...], showAfterSeconds?: n } (then is ignored — THEN means "show").

  • Other logic keys: logic.autoAdvance = { enabled, delaySeconds? }, logic.navigationBehavior = { onButtonClick?, onAutoAdvance?, targetScreenId? }.

  • logic.conditionalNavigation (legacy, pre-DEVF-161) is still read and migrated on load — don't author new rules with it.

Unknown fields are REJECTED (they used to be stored and silently ignored): logic as an array, or rules/branching/conditions/goto/jump/nextScreen anywhere, are not read by any renderer.

Input Schema

NameRequiredDescriptionDefault
themeNoTheme key: amber|base|midnight|ocean|sunset|forest|lavender|minimal (default amber)
titleYesForm title
formatNoForm format: 'single_question' (default — one screen at a time), 'single_page' (all screens on one scrollable page, grouped by `sections`), or 'chatbot' (an Instagram/WhatsApp DM-style conversation; set `chatbot_skin` + `chatbot_profile`).
sectionsNosingle_page only: the page groups as `[{ id }]`, in order. The 'Section N' label is derived from position; sections have no editable title.
questionsNoScreen objects; omit for an empty form. Conditional logic goes in each screen's `logic` (see the tool description).
descriptionNoOptional description
chatbot_skinNochatbot only: chat chrome to imitate — 'instagram' (default) or 'whatsapp'.
chatbot_profileNochatbot only: the profile shown in the chat header.
thank_you_messageNo

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / theme / description
      Previous value: -"Theme key (default sunset/Ember)"New value: +"Theme key: amber|base|midnight|ocean|sunset|forest|lavender|minimal (default amber)"
  2. Changed4 schema fields changed
    • addedInput schema / properties / chatbot_profile
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "chatbot only: the profile shown in the chat header.",
      +  "properties": {
      +    "avatar_url": {
      +      "description": "Avatar image URL; falls back to the name's initials.",
      +      "type": "string"
      +    },
      +    "followers_text": {
      +      "description": "Free text under the name, e.g. followers/posts (Instagram only).",
      +      "type": "string"
      +    },
      +    "handle": {
      +      "description": "Instagram @handle (Instagram skin only).",
      +      "type": "string"
      +    },
      +    "name": {
      +      "description": "Display name in the chat header (both skins; default 'Sua Marca').",
      +      "type": "string"
      +    },
      +    "phone": {
      +      "description": "Shown phone number (WhatsApp skin only).",
      +      "type": "string"
      +    },
      +    "status": {
      +      "description": "Header status line (both skins; empty on WhatsApp shows the phone).",
      +      "type": "string"
      +    },
      +    "verified": {
      +      "description": "Blue verified badge (Instagram only; default true).",
      +      "type": "boolean"
      +    }
      +  },
      +  "type": "object"
      +}
    • addedInput schema / properties / chatbot_skin
      Added value: +{
      +  "description": "chatbot only: chat chrome to imitate — 'instagram' (default) or 'whatsapp'.",
      +  "enum": [
      +    "instagram",
      +    "whatsapp"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / format
      Added value: +{
      +  "description": "Form format: 'single_question' (default — one screen at a time), 'single_page' (all screens on one scrollable page, grouped by `sections`), or 'chatbot' (an Instagram/WhatsApp DM-style conversation; set `chatbot_skin` + `chatbot_profile`).",
      +  "enum": [
      +    "single_question",
      +    "single_page",
      +    "chatbot"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / sections
      Added value: +{
      +  "description": "single_page only: the page groups as `[{ id }]`, in order. The 'Section N' label is derived from position; sections have no editable title.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "id": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
  3. First observed

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Well beyond the annotations, it discloses that unknown screen fields are REJECTED (a behavior change from silent ignoring), that branching rules are cross-checked against screen ids in the same payload, and that format-related fields are silently ignored unless the account's feature is enabled. That last item is critical: a call can succeed while the requested format is dropped, which no annotation conveys.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose, args, and returns before the deep reference material, which is the right order for a 9-parameter nested tool. It is long but the complexity justifies most of it; the main waste is the 'unknown fields are REJECTED' warning appearing twice and the theme enum being restated from the schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers the return shape ({ form } with id and slug), the draft/live lifecycle, validation failure modes, feature-flag degradation, and redirect semantics including query-param passing. With no output schema present, the description supplying the return contract is exactly what's needed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 89%, so the baseline would be 3, but the description adds substantial meaning: the default theme rationale, the full `questions[].logic` grammar (then.type, operator enum, expression syntax, {{score}} semantics, {{param}} substitution), and the end-screen field set with the 0-10 delay bound. The schema itself defers to the description for logic, so this carries real weight.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Create a new form') and immediately scopes it to include per-screen conditional logic, which distinguishes it from foxform_update_form. The access requirement ('Requires a WRITE-scoped API key') is stated in the first line, so an agent can rule itself in or out instantly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the follow-on step: the form 'starts as a draft — call foxform_publish_form to make it live', and 'omit to start empty' clarifies the minimal call. It does not state when to prefer foxform_update_form over creating a new form, but the create/update split is largely self-evident from the names.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources