fastmcp/docs/v3/apps/providers/form.mdx
Jeremiah Lowin c556f07a66
Archive v3 docs and publish v4 as the primary version (#4613)
* Archive v3 docs under /v3 and publish v4 as the primary version

* Label primary docs version v4.0.0 (alpha 1)

* Add What's New in v4 page; fix upgrade-guide phrasing; point banner at What's New

* Rewrite What's New around v4's new capabilities, not the sampling deprecation

* Lead What's New with the SDK v2 engine swap and the SEPs it brings

* State ships now (link Session State); tasks arrive next alpha

* Exclude docs/v3 frozen snapshots from doc-example import validation
2026-07-23 19:47:57 -04:00

105 lines
3.5 KiB
Text

---
title: Form Input
sidebarTitle: Form Input
description: Collect structured data from users via Pydantic models
icon: rectangle-list
tag: NEW
---
import { VersionBadge } from '/snippets/version-badge.mdx'
<VersionBadge version="3.2.0" />
`FormInput` generates a validated form from a Pydantic model. The user fills it out, and the submission is validated against the model before being returned. Structured elicitation that can't be hallucinated.
<Frame>
<img src="/apps/images/app-form.png" alt="The FormInput provider shown in Goose, with a bug report form" />
</Frame>
```python
from typing import Literal
from pydantic import BaseModel, Field
from fastmcp import FastMCP
from fastmcp.apps.form import FormInput
class BugReport(BaseModel):
title: str = Field(description="Brief summary")
severity: Literal["low", "medium", "high", "critical"]
description: str = Field(
description="Detailed description",
json_schema_extra={"ui": {"type": "textarea"}},
)
mcp = FastMCP("My Server")
mcp.add_provider(FormInput(model=BugReport))
```
This registers two tools:
| Tool | Visibility | Purpose |
|------|-----------|---------|
| `collect_bugreport` | Model | Opens the form UI |
| `submit_form` | App only | Validates and processes the submission |
The tool name is derived from the model class name, lowercased: `collect_{modelname}`. So `BugReport` becomes `collect_bugreport`, `ShippingAddress` becomes `collect_shippingaddress`. Use `tool_name` to override if needed. The LLM calls it with a prompt explaining what it needs, and the user gets a form with fields matching the model.
## Field mapping
`FormInput` uses Prefab's `Form.from_model()`, which maps Pydantic types to form components:
| Python type | Form component |
|------------|---------------|
| `str` | Text input |
| `int`, `float` | Number input |
| `bool` | Checkbox |
| `datetime.date` | Date picker |
| `Literal[...]` | Select dropdown |
| `SecretStr` | Password input |
Use `Field()` metadata to control labels (`title`), placeholders (`description`), and validation (`min_length`, `max_length`, `ge`, `le`). Use `json_schema_extra={"ui": {"type": "textarea"}}` for multiline text.
## Callback
By default, the validated model is returned as JSON. Provide an `on_submit` callback to process the data server-side:
```python
def save_report(report: BugReport) -> str:
db.insert(report.model_dump())
return f"Bug #{db.last_id} filed: {report.title}"
mcp.add_provider(FormInput(model=BugReport, on_submit=save_report))
```
The callback receives a validated model instance and returns a string that becomes the tool result.
## Configuration
```python
FormInput(
model=BugReport, # Required: the Pydantic model
name="BugTracker", # App name (default: model name)
title="File a Bug", # Card heading (default: model name)
tool_name="file_bug", # Tool name (default: collect_{model})
submit_text="Submit Report", # Button label (default: "Submit")
on_submit=save_report, # Optional callback
send_message=True, # Push result as a chat message
)
```
Set `send_message=True` to push the result back into the conversation via `SendMessage`, triggering the LLM's next turn. Without it, the result is just the tool return value.
## Multiple forms
Add multiple providers for different models — each gets its own tool:
```python
mcp = FastMCP(
"My Server",
providers=[
FormInput(model=ShippingAddress),
FormInput(model=BugReport),
FormInput(model=ContactInfo),
],
)
```