FoxForm

Build, publish and read scored forms and quizzes where the score picks the next screen.

我該用這個嗎

品質與安全性

B
說明品質
100%
結構描述完整度
97%
命名品質
80%
汙染風險
40%
權限相符程度
100%
協定合規性
100%

發現項目(4)

  • HIGHTool poisoning patterns detected
  • MEDIUMTool description contains URL to non-standard domain在 foxform_list_forms 中
  • MEDIUMTool description contains URL to non-standard domain在 foxform_get_form 中
  • MEDIUMTool description contains URL to non-standard domain在 foxform_publish_form 中

根據工具定義與協定合規性的自動化分析。

上下文成本

~6,056Token(工具定義)
~2.1 KB典型回應大小
顯著的注意力影響(128k 上下文的 4.73%)

這是每次將伺服器的工具載入模型上下文時所消耗的約略 token 數量。數量越高,可用於其他工作的注意力就越少。

安裝

一鍵安裝

將以下內容加入你的 `claude_desktop_config.json` 檔案:

{
  "mcpServers": {
    "foxform": {
      "url": "https://mcp.foxform.app/mcp"
    }
  }
}

遠端端點

https://mcp.foxform.app/mcpstreamable-http

它能做什麼

工具清單

工具(10)

🟢 唯讀🟡 寫入🔴 刪除⚪ 未知
🟢foxform_list_forms(page, limit, response_format)

List the forms owned by the authenticated FoxForm account, newest first. Args: - page (number): 1-based page number (default 1) - limit (number): page size, 1-100 (default 20) - response_format ('markdown' | 'json'): output format (default markdown) Returns: { total, page, limit, count, forms: [{ id, title, status, slug, public_url, questions_count, updated_at }] } The public link of a form is `public_url` (`https://forms.foxform.app/<slug>`) — always use this exact value, do NOT build the URL yourself (the public domain is forms.foxform.app, not foxform.app). `public_url` is only live when `status` is 'published'. Use this first to discover form IDs, then call foxform_get_form / foxform_get_form_analytics.

輸入結構描述

{
  "type": "object",
  "properties": {
    "page": {
      "type": "integer",
      "minimum": 1,
      "default": 1,
      "description": "1-based page number"
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 20,
      "description": "Page size (1-100)"
    },
    "response_format": {
      "type": "string",
      "enum": [
        "markdown",
        "json"
      ],
      "default": "markdown",
      "description": "Output format: 'markdown' (human-readable) or 'json' (machine-readable)"
    }
  },
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟢foxform_get_form(form_id, response_format)

Fetch a single form by ID, including its full question list, per-screen conditional logic and settings. Args: - form_id (string): the form's ID (from foxform_list_forms) - response_format ('markdown' | 'json') Returns { form, public_url }: the full form object (id, title, description, slug, status, theme, questions[], thank_you_message, timestamps, plus `format` and — when set — `sections`, `chatbot_skin`, `chatbot_profile`) plus `public_url` — the public link (`https://forms.foxform.app/<slug>`), which is live only when status is 'published'. Use `public_url` verbatim; never build the URL yourself (the public domain is forms.foxform.app, not foxform.app). Each screen in `questions[]` carries its own `logic` (branching / conditional display) and, for choice screens, `choices[]`/`images[]` with their `points` and `value`. The markdown output summarises every rule; use response_format 'json' to get the exact stored objects (that's the shape foxform_update_form expects back). 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. 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.

輸入結構描述

{
  "type": "object",
  "properties": {
    "form_id": {
      "type": "string",
      "minLength": 1,
      "description": "Form ID"
    },
    "response_format": {
      "type": "string",
      "enum": [
        "markdown",
        "json"
      ],
      "default": "markdown",
      "description": "Output format: 'markdown' (human-readable) or 'json' (machine-readable)"
    }
  },
  "required": [
    "form_id"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟢foxform_list_responses(form_id, page, limit, response_format)

List submitted responses for a form, newest first. Args: - form_id (string): the form's ID - page (number): 1-based page (default 1) - limit (number): page size, 1-100 (default 20) - response_format ('markdown' | 'json') Returns: { total, page, limit, count, responses: [{ id, submitted_at, answers }] }. For aggregate metrics use foxform_get_form_analytics; for a full dump use foxform_export_responses.

輸入結構描述

{
  "type": "object",
  "properties": {
    "form_id": {
      "type": "string",
      "minLength": 1,
      "description": "Form ID"
    },
    "page": {
      "type": "integer",
      "minimum": 1,
      "default": 1
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "default": 20
    },
    "response_format": {
      "type": "string",
      "enum": [
        "markdown",
        "json"
      ],
      "default": "markdown",
      "description": "Output format: 'markdown' (human-readable) or 'json' (machine-readable)"
    }
  },
  "required": [
    "form_id"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟢foxform_get_response(response_id, response_format)

Fetch one response by its ID (from foxform_list_responses). Args: - response_id (string) - response_format ('markdown' | 'json') Returns the full response object (id, form_id, answers, submitted_at, metadata).

輸入結構描述

{
  "type": "object",
  "properties": {
    "response_id": {
      "type": "string",
      "minLength": 1,
      "description": "Response ID"
    },
    "response_format": {
      "type": "string",
      "enum": [
        "markdown",
        "json"
      ],
      "default": "markdown",
      "description": "Output format: 'markdown' (human-readable) or 'json' (machine-readable)"
    }
  },
  "required": [
    "response_id"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟢foxform_get_form_analytics(form_id, response_format)

Aggregated analytics for a form: overview KPIs (total responses, form views, response rate, avg response time), the responses-over-time timeline, and per-question stats. Args: - form_id (string) - response_format ('markdown' | 'json') Returns: { overview, timeline: [{date,count}], questions: [...], response_time_distribution?, views_timeline? }. Note: form_views / response_rate are null until the form has tracked views (forward-only).

輸入結構描述

{
  "type": "object",
  "properties": {
    "form_id": {
      "type": "string",
      "minLength": 1,
      "description": "Form ID"
    },
    "response_format": {
      "type": "string",
      "enum": [
        "markdown",
        "json"
      ],
      "default": "markdown",
      "description": "Output format: 'markdown' (human-readable) or 'json' (machine-readable)"
    }
  },
  "required": [
    "form_id"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟢foxform_export_responses(form_id)

Export all responses for a form as CSV text (one row per response, columns = questions). Large exports are truncated — use foxform_list_responses with pagination for very large datasets. Args: - form_id (string) Returns: raw CSV text.

輸入結構描述

{
  "type": "object",
  "properties": {
    "form_id": {
      "type": "string",
      "minLength": 1,
      "description": "Form ID"
    }
  },
  "required": [
    "form_id"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟡foxform_create_form(title, description, theme, questions, thank_you_message, ...)

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 midnight|ocean|sunset|forest|lavender|minimal (default sunset = Ember) - 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. 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.

輸入結構描述

{
  "type": "object",
  "properties": {
    "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 255,
      "description": "Form title"
    },
    "description": {
      "type": "string",
      "maxLength": 2000,
      "description": "Optional description"
    },
    "theme": {
      "type": "string",
      "description": "Theme key (default sunset/Ember)"
    },
    "questions": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": {}
      },
      "description": "Screen objects; omit for an empty form. Conditional logic goes in each screen's `logic` (see the tool description)."
    },
    "thank_you_message": {
      "type": "string",
      "maxLength": 2000
    },
    "format": {
      "type": "string",
      "enum": [
        "single_question",
        "single_page",
        "chatbot"
      ],
      "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`)."
    },
    "sections": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "additionalProperties": false
      },
      "description": "single_page only: the page groups as `[{ id }]`, in order. The 'Section N' label is derived from position; sections have no editable title."
    },
    "chatbot_skin": {
      "type": "string",
      "enum": [
        "instagram",
        "whatsapp"
      ],
      "description": "chatbot only: chat chrome to imitate — 'instagram' (default) or 'whatsapp'."
    },
    "chatbot_profile": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Display name in the chat header (both skins; default 'Sua Marca')."
        },
        "handle": {
          "type": "string",
          "description": "Instagram @handle (Instagram skin only)."
        },
        "followers_text": {
          "type": "string",
          "description": "Free text under the name, e.g. followers/posts (Instagram only)."
        },
        "phone": {
          "type": "string",
          "description": "Shown phone number (WhatsApp skin only)."
        },
        "status": {
          "type": "string",
          "description": "Header status line (both skins; empty on WhatsApp shows the phone)."
        },
        "verified": {
          "type": "boolean",
          "description": "Blue verified badge (Instagram only; default true)."
        },
        "avatar_url": {
          "type": "string",
          "description": "Avatar image URL; falls back to the name's initials."
        }
      },
      "additionalProperties": false,
      "description": "chatbot only: the profile shown in the chat header."
    }
  },
  "required": [
    "title"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🔴foxform_update_form(form_id, title, description, theme, questions, ...)

Update an existing form's fields, including each screen's conditional logic (branching). Requires a WRITE-scoped API key. Only the fields you pass are changed. Args: - form_id (string): the form to update (required) - title (string, optional) - description (string, optional) - theme (string, optional) - questions (array, optional): replaces the FULL screen list — there is no per-screen patch. To add logic to one screen: call foxform_get_form with response_format 'json', edit that screen's `logic`, and send the whole array back. - thank_you_message (string, optional) - format ('single_question' | 'single_page' | 'chatbot', optional) - sections (array, optional): single_page page groups, [{ id }] - chatbot_skin ('instagram' | 'whatsapp', optional) - chatbot_profile (object, optional): chatbot header profile ({ name, handle, followers_text, phone, status, verified, avatar_url }) Returns: { form } with the updated form. Screen fields are validated: unknown fields are REJECTED instead of being stored and ignored (the API accepts arbitrary keys but no renderer reads them), `then.targetScreenId` must be the id of a screen in the same payload, and `{{variables}}` that no screen exposes come back as warnings. 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. 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.

輸入結構描述

{
  "type": "object",
  "properties": {
    "form_id": {
      "type": "string",
      "minLength": 1,
      "description": "Form ID"
    },
    "title": {
      "type": "string",
      "minLength": 1,
      "maxLength": 255
    },
    "description": {
      "type": "string",
      "maxLength": 2000
    },
    "theme": {
      "type": "string"
    },
    "questions": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": {}
      },
      "description": "Replaces the full screen list. Conditional logic goes in each screen's `logic` (see the tool description)."
    },
    "thank_you_message": {
      "type": "string",
      "maxLength": 2000
    },
    "format": {
      "type": "string",
      "enum": [
        "single_question",
        "single_page",
        "chatbot"
      ],
      "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`)."
    },
    "sections": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "additionalProperties": false
      },
      "description": "single_page only: the page groups as `[{ id }]`, in order. The 'Section N' label is derived from position; sections have no editable title."
    },
    "chatbot_skin": {
      "type": "string",
      "enum": [
        "instagram",
        "whatsapp"
      ],
      "description": "chatbot only: chat chrome to imitate — 'instagram' (default) or 'whatsapp'."
    },
    "chatbot_profile": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string",
          "description": "Display name in the chat header (both skins; default 'Sua Marca')."
        },
        "handle": {
          "type": "string",
          "description": "Instagram @handle (Instagram skin only)."
        },
        "followers_text": {
          "type": "string",
          "description": "Free text under the name, e.g. followers/posts (Instagram only)."
        },
        "phone": {
          "type": "string",
          "description": "Shown phone number (WhatsApp skin only)."
        },
        "status": {
          "type": "string",
          "description": "Header status line (both skins; empty on WhatsApp shows the phone)."
        },
        "verified": {
          "type": "boolean",
          "description": "Blue verified badge (Instagram only; default true)."
        },
        "avatar_url": {
          "type": "string",
          "description": "Avatar image URL; falls back to the name's initials."
        }
      },
      "additionalProperties": false,
      "description": "chatbot only: the profile shown in the chat header."
    }
  },
  "required": [
    "form_id"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🟡foxform_publish_form(form_id)

Publish a form so it's live at its public URL and can accept responses. Requires a WRITE-scoped API key. Args: - form_id (string) Returns: { form, public_url } — the published form (status 'published') and its live public link `https://forms.foxform.app/<slug>`. When sharing the link with the user, use `public_url` EXACTLY as returned; do NOT build the URL yourself (the public domain is forms.foxform.app, NOT foxform.app or the API/app domain). (May fail with a plan-limit error on Free accounts.)

輸入結構描述

{
  "type": "object",
  "properties": {
    "form_id": {
      "type": "string",
      "minLength": 1,
      "description": "Form ID"
    }
  },
  "required": [
    "form_id"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}
🔴foxform_unpublish_form(form_id)

Unpublish a form (takes it offline; stops accepting responses). Requires a WRITE-scoped API key. Args: - form_id (string) Returns: a confirmation message.

輸入結構描述

{
  "type": "object",
  "properties": {
    "form_id": {
      "type": "string",
      "minLength": 1,
      "description": "Form ID"
    }
  },
  "required": [
    "form_id"
  ],
  "additionalProperties": false,
  "$schema": "http://json-schema.org/draft-07/schema#"
}

社群

為此伺服器評分

證據

近期觀測

已驗證未記錄版本10 個工具
已驗證未記錄版本10 個工具