Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ follow [SemVer](https://semver.org/).

## [Unreleased]

### Added
- **Portable MCP Apps transport** — capability-aware tool metadata links
form and workspace renderers to one self-contained `ui://` resource.
User actions post back through the existing validation tools, and only
validated results are offered to host model context. Non-supporting and
partially supporting hosts retain the meaningful structured/text result
and name the manual fallback visibly.

## [0.9.1] — 2026-08-29

### Added
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ and serves six MCP tools — `elicitation_render_form`,
`elicitation_collect_workspace_action` — from this package via `uvx`. Decision cards,
pushback cards, progress forms, deliberation cards, triage boards,
confirm gates, ranking lists, and assumption reviews work out of the
box; rich HTML renders where the host
supports widgets, degrades to plain questions where it doesn't, and
renders as portable markdown on text-only hosts — with typed replies
box. MCP Apps hosts discover one shared `ui://` resource, render the
rich surface inline, send user actions through the same server-side
validator, and return the validated result to the conversation. Other
hosts degrade to plain questions where possible and render as portable
markdown on text-only hosts — with typed replies
parsed back into the same validator.

**As a Python library:**
Expand Down Expand Up @@ -102,6 +104,13 @@ if select_form_surface(form) == "widget":

## One schema, every surface

- **MCP Apps transport** — capable hosts advertise
`io.modelcontextprotocol/ui`, receive UI metadata only after that
negotiation, and render the shared `ui://attune-forms/dynamic-surface/v1`
resource. App submissions call the existing collector tools; only a
successful validated result is offered back to model context. Hosts
missing app-to-server or app-to-chat capabilities show an explicit
manual-continuation state rather than a dead control.
- **Renderers** — `form_to_widget_html` (self-contained interactive
widget with postback), `form_to_askuserquestion` (batched payloads),
`form_to_elicitation_schema` (native MCP elicitation), and
Expand Down
20 changes: 13 additions & 7 deletions plugin/skills/forms/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,22 +207,28 @@ wearing a new construct.

## Choosing a surface

1. **Widget host** (the client renders HTML): call
`elicitation_render_widget` and show the returned `html`. The form
posts answers back as a JSON block marked
1. **MCP Apps host**: call `elicitation_render_widget` (or
`elicitation_render_workspace`). After capability negotiation the host
discovers the linked `ui://attune-forms/dynamic-surface/v1` resource and
renders it inline. Its actions call the named server-side collector; only
a validated result is offered back to model context. If the embedded view
names a missing submission or continuation capability, continue with the
native or text path below — do not treat the rendered click as authority.
2. **Legacy widget host** (the client renders returned HTML): show the
returned `html`. The form posts answers back as a JSON block marked
`__elicitation_response__` — parse it and validate with
`elicitation_collect_response`.
2. **Native elicitation host**: call `elicitation_ask`; on
`action: "unsupported"`, fall back to (3).
3. **Plain conversation**: call `elicitation_render_form` and map each
3. **Native elicitation host**: call `elicitation_ask`; on
`action: "unsupported"`, fall back to (4).
4. **Plain conversation**: call `elicitation_render_form` and map each
batched payload to your host's question tool (or plain prose):
recommendation-first ordering, `multi_select` → multi-select,
constructs → single-select with the recommended option first and
tradeoffs folded into option descriptions (a triage board arrives
pre-expanded as one single-select per item; a ranking as one
single-select per rank slot; an assumption review as one
single-select per assumption plus its paired text question).
4. **No widget, no question tool** (text-only hosts): render the form
5. **No widget, no question tool** (text-only hosts): render the form
with `form_to_markdown` (library) and relay the markdown verbatim.
It ends with a JSON answer skeleton — the widget's exact postback
shape — and documents the line shorthand (`field_id: value`,
Expand Down
18 changes: 18 additions & 0 deletions src/attune_forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@
)
from attune_forms.markdown_ingestion import markdown_to_answers, problems_to_markdown
from attune_forms.markdown_surface import form_to_markdown
from attune_forms.mcp_app import (
MCP_APP_MIME_TYPE,
MCP_APP_PROTOCOL_VERSION,
MCP_APP_RESOURCE_URI,
MCP_APPS_EXTENSION,
client_supports_mcp_apps,
mcp_app_resource,
mcp_app_result,
mcp_app_tool_meta,
)
from attune_forms.models import (
ASSUMPTION_RULINGS,
FormQuestion,
Expand Down Expand Up @@ -138,6 +148,14 @@
"keyboard_mode_enabled",
"list_templates",
"markdown_to_answers",
"MCP_APPS_EXTENSION",
"MCP_APP_MIME_TYPE",
"MCP_APP_PROTOCOL_VERSION",
"MCP_APP_RESOURCE_URI",
"client_supports_mcp_apps",
"mcp_app_resource",
"mcp_app_result",
"mcp_app_tool_meta",
"needs_widget",
"problems_to_markdown",
"select_form_surface",
Expand Down
Loading
Loading