Skip to content

feat: HTML widget support (MCP Apps UI)#490

Draft
corinagum wants to merge 14 commits into
mainfrom
cg/widgets
Draft

feat: HTML widget support (MCP Apps UI)#490
corinagum wants to merge 14 commits into
mainfrom
cg/widgets

Conversation

@corinagum

@corinagum corinagum commented Jun 30, 2026

Copy link
Copy Markdown
Contributor
image

Summary

Adds HTML widget support to the Teams Python SDK, enabling bots to send rich interactive MCP Apps UI widgets in Teams messages.

What's included

API types (microsoft_teams.api)

  • HtmlWidgetPayload, HtmlWidgetSecurityPolicy, HtmlWidgetPermissions
  • McpUiCallToolResult, HtmlWidgetCallToolResponse (wire-format wrapper)
  • HtmlWidgetCallToolInvokeActivity and invoke response mapping
  • extendedmarkdown added to TextFormat

App helpers (microsoft_teams.apps)

  • build_html_widget_markdown - wraps payload in ```html-widget code fence
  • build_html_widget_message - builds a ready-to-send MessageActivityInput
  • inject_widget_protocol - optional convenience helper that injects the MCP Apps protocol (ui/initialize handshake, size reporting, notification hooks)
  • validate_security_policy - static analysis of widget HTML against declared security policy
  • widget.call_tool invoke route + on_widget_call_tool decorator

Internal (not exported):

  • _validate_html_widget_payload - runtime validation (name, html, domain) called automatically by builders

Example bot (examples/html-widgets/)

  • 9 commands demonstrating the full widget contract
  • Typed on_widget_call_tool handler with multi-tool dispatch

Tests

  • 73 tests covering all helpers, protocol injection, validation, and security policy

Preview markers

  • All public APIs marked @experimental

Design decisions

  • Protocol injection is opt-in: inject_widget_protocol is called internally by the builders, but widgets that implement the MCP Apps protocol themselves are returned unchanged (detected by presence of ui/initialize)
  • Notification hooks are explicit: only mapped notification names are injected; unknown names are silently ignored
  • Payload validation throws early: prevents silent client-side "Couldn't load widget" failures
  • Default security policy: restrictive defaults per MCP Apps spec (no external network, self+data resources only)
  • Security validation is advisory: validate_security_policy is a static linter; CSP enforcement in the iframe is the actual security boundary
  • Returns MessageActivityInput: build_html_widget_message returns a proper model (not a dict) so it integrates with ctx.send() seamlessly

Related

Status

Draft - pending:

  • .NET core port
  • .NET Libraries port
  • Documentation in teams-sdk/teams.md

corinagum and others added 8 commits June 30, 2026 12:53
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Match the convention used by ExperimentalTeamsTargeted in the PY SDK.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Change examples/html-widgets requires-python to >=3.11,<4.0 (matches all other examples)
- Move HtmlWidgetMarkdownOptions import to file top (no inline imports per repo convention)
- Apply ruff formatting to new files

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces experimental HTML widget support to the Teams Python SDK, enabling bots to send MCP Apps UI-compatible HTML widgets in messages, plus a typed invoke route for widgets calling bot tools.

Changes:

  • Added API models for HTML widget payloads and the htmlwidget/calltool invoke request/response wire format.
  • Added app-side helpers for protocol injection, widget markdown/message builders, and advisory CSP/security-policy validation.
  • Added comprehensive unit tests and a new examples/html-widgets demo bot showcasing the widget contract.

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
uv.lock Updates workspace lockfile members and Python version marker.
packages/apps/tests/test_html_widget.py Adds test coverage for widget builders, protocol injection, and security-policy validation.
packages/apps/src/microsoft_teams/apps/utils/html_widget.py Implements widget helpers (builders, protocol injection, static security checks).
packages/apps/src/microsoft_teams/apps/routing/generated_handlers.py Adds on_widget_call_tool handler registration API.
packages/apps/src/microsoft_teams/apps/routing/activity_route_configs.py Registers widget.call_tool invoke routing selector and response type.
packages/apps/src/microsoft_teams/apps/init.py Re-exports widget helper APIs from the apps package.
packages/api/src/microsoft_teams/api/models/invoke_response.py Adds HtmlWidgetCallToolResponse to the invoke response body union.
packages/api/src/microsoft_teams/api/models/html_widget/html_widget_payload.py Adds HtmlWidgetPayload, HtmlWidgetSecurityPolicy, and permissions models.
packages/api/src/microsoft_teams/api/models/html_widget/call_tool_result.py Adds McpUiCallToolResult and HtmlWidgetCallToolResponse wire model.
packages/api/src/microsoft_teams/api/models/html_widget/call_tool_request.py Adds CallToolRequest model for htmlwidget/calltool invoke payloads.
packages/api/src/microsoft_teams/api/models/html_widget/init.py Exposes html-widget models via package __all__.
packages/api/src/microsoft_teams/api/models/init.py Exports html-widget models at the top-level models namespace.
packages/api/src/microsoft_teams/api/activities/invoke/html_widget/call_tool.py Adds typed HtmlWidgetCallToolInvokeActivity.
packages/api/src/microsoft_teams/api/activities/invoke/html_widget/init.py Exposes the html-widget invoke activity module.
packages/api/src/microsoft_teams/api/activities/invoke/init.py Adds html-widget invoke activity to the discriminated invoke union.
examples/html-widgets/src/widgets/update_context.py Adds demo widget for ui/update-model-context.
examples/html-widgets/src/widgets/simple.py Adds demo widget for basic rendering.
examples/html-widgets/src/widgets/open_link.py Adds demo widget for ui/open-link.
examples/html-widgets/src/widgets/multi_tool.py Adds demo widget exercising multi-tool dispatch via tools/call.
examples/html-widgets/src/widgets/messageback.py Adds demo widget for ui/message (messageBack-like) behavior.
examples/html-widgets/src/widgets/host_context.py Adds demo widget that inspects hostContext and listens for updates.
examples/html-widgets/src/widgets/fullscreen.py Adds demo widget requesting fullscreen display mode.
examples/html-widgets/src/widgets/calltool.py Adds demo widget calling a refresh tool via tools/call.
examples/html-widgets/src/widgets/init.py Re-exports widget HTML constants for the example bot.
examples/html-widgets/src/main.py Implements the example bot command router and typed on_widget_call_tool handler.
examples/html-widgets/README.md Documents the example bot commands and how to run it.
examples/html-widgets/pyproject.toml Adds the example package metadata and dependencies.

Comment thread examples/html-widgets/pyproject.toml Outdated
Comment thread packages/apps/src/microsoft_teams/apps/utils/html_widget.py
Comment thread packages/apps/src/microsoft_teams/apps/utils/html_widget.py Outdated
Comment thread packages/apps/src/microsoft_teams/apps/utils/html_widget.py Outdated
Comment thread packages/api/src/microsoft_teams/api/models/html_widget/call_tool_request.py Outdated
corinagum and others added 3 commits June 30, 2026 13:29
- Use McpUiCallToolResultContent instances instead of raw dicts
- Type args as dict[str, Any] to fix partial-unknown errors
- Cast counter/sides through int() with Any input

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Copy InjectWidgetProtocolOptions before overriding name (avoid caller mutation)
- Deep-copy DEFAULT_SECURITY_POLICY when used as fallback (avoid shared mutable state)
- Fix call_tool_request.py import to use relative ..custom_base_model (consistency)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rsion

The inline script injection used only backslash/quote escaping, which
allowed a </script> sequence in the widget name or version to break out
of the script context and inject arbitrary HTML/JS into the widget iframe.

Fix: add _escape_for_inline_script() that also escapes </ (prevents
</script> breakout) and newlines (prevents JS string literal breakout).

Added 3 tests covering the breakout cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread packages/apps/tests/test_html_widget.py Fixed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants