Skip to content
Draft
62 changes: 62 additions & 0 deletions examples/html-widgets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# HTML Widgets Example

This example bot demonstrates the HTML widget contract for Teams bots using the Teams SDK.

## What it tests

| Command | Purpose | Widget callbacks |
|---------|---------|-----------------|
| `/simple` | Static widget rendering | None |
| `/calltool` | Widget calling bot tools | `htmlwidget/calltool` invoke |
| `/messageback` | Widget sending messageBack | `onMessage` |
| `/fullscreen` | Widget requesting display mode change | `onRequestDisplayMode` (client-side) |
| `/multi` | Multiple tool dispatch | `htmlwidget/calltool` with different tool names |
| `/raw` | Direct markdown helper usage | None |
| `/permissions` | Widget requesting permissions | None (host-enforced) |

## Architecture

```
Bot sends message:
textFormat: 'extendedmarkdown'
text: "...\n```html-widget\n{JSON payload}\n```"
Teams client:
McpWidgetRenderer loads widget HTML in sandboxed iframe
@modelcontextprotocol/ext-apps SDK provides callServerTool API
Widget calls tool:
ext-apps SDK -> postMessage -> McpWidgetRenderer -> htmlwidget/calltool invoke -> Bot
Bot returns:
{ status: 200, body: { content: [...], structuredContent: {...}, isError: false } }
```

## Running

1. Copy credentials:
```
cp ../../../bots/.env .env
```

2. Start a devtunnel and update the Azure Bot endpoint

3. Run the bot:
```
npm run dev
```

4. In Teams, message the bot with `/help` to see available commands

## Note on widget HTML

The widget HTML in `src/widgets/` is static HTML for rendering verification.
Interactive behavior (callTool, messageBack, displayMode) requires the
`@modelcontextprotocol/ext-apps` SDK which is provided by the Teams widget host
page (`mcpwidget.html` on `widget-renderer.usercontent.microsoft`).

The bot-side code (`widget.callTool` handler in `src/index.ts`) is the primary
test target. It verifies that:
- The SDK's invoke route alias correctly matches `htmlwidget/calltool`
- The typed handler receives `{ name, arguments }` in `activity.value`
- The response body format (`McpUiCallToolResult`) is accepted by the client
1 change: 1 addition & 0 deletions examples/html-widgets/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@microsoft/teams.config/eslint.config').default;
31 changes: 31 additions & 0 deletions examples/html-widgets/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@examples/html-widgets",
"version": "0.0.1",
"private": true,
"license": "MIT",
"main": "dist/index",
"types": "dist/index",
"files": [
"dist",
"README.md"
],
"scripts": {
"clean": "npx rimraf ./dist",
"lint": "npx eslint",
"lint:fix": "npx eslint --fix",
"build": "npx tsc",
"start": "node .",
"dev": "tsx watch -r dotenv/config src/index.ts"
},
"dependencies": {
"@microsoft/teams.apps": "*"
},
"devDependencies": {
"@microsoft/teams.config": "*",
"@types/node": "^22.5.4",
"dotenv": "^16.4.5",
"rimraf": "^6.0.1",
"tsx": "^4.20.6",
"typescript": "^5.4.5"
}
}
Loading
Loading