MCP Widget Development

This skill should be used when the user asks to "build a widget", "create UI component", "ChatGPT UI", "window.openai API", "widget template", "skybridge", "render in ChatGPT", "CSP configuration", or needs guidance on building interactive UI components for OpenAI Apps SDK that render inside ChatGPT.

16 stars

Best use case

MCP Widget Development is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

This skill should be used when the user asks to "build a widget", "create UI component", "ChatGPT UI", "window.openai API", "widget template", "skybridge", "render in ChatGPT", "CSP configuration", or needs guidance on building interactive UI components for OpenAI Apps SDK that render inside ChatGPT.

Teams using MCP Widget Development should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/mcp-widget-development/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/mcp-widget-development/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/mcp-widget-development/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How MCP Widget Development Compares

Feature / AgentMCP Widget DevelopmentStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

This skill should be used when the user asks to "build a widget", "create UI component", "ChatGPT UI", "window.openai API", "widget template", "skybridge", "render in ChatGPT", "CSP configuration", or needs guidance on building interactive UI components for OpenAI Apps SDK that render inside ChatGPT.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# MCP Widget Development for OpenAI Apps SDK

## Overview

Widgets are interactive UI components that render inside ChatGPT conversations. Built with HTML, CSS, and JavaScript, they run in a sandboxed iframe and communicate with the MCP server through the `window.openai` bridge.

## Widget Architecture

```
Tool Call → Server Returns _meta.openai/outputTemplate →
ChatGPT Loads Widget HTML → Widget Reads window.openai.toolOutput →
Widget Renders UI → User Interacts → Widget Calls Tools (optional)
```

### Key Components

| Component | Purpose |
|-----------|---------|
| HTML Template | Widget markup and styles |
| `window.openai` | Bridge to ChatGPT runtime |
| `_meta` | Widget-only data from server |
| CSP Config | Security allowlists |

## Registering Widget Templates

Widgets are served as MCP resources with the special mime type `text/html+skybridge`.

### Python

```python
@mcp.resource("ui://widget/main.html")
def main_widget() -> str:
    return """<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <style>/* styles */</style>
</head>
<body>
    <div id="app"></div>
    <script>/* widget code */</script>
</body>
</html>"""
```

### TypeScript

```typescript
server.setRequestHandler(ReadResourceRequestSchema, async (request) => ({
  contents: [{
    uri: request.params.uri,
    mimeType: "text/html+skybridge",
    text: widgetHtml
  }]
}));
```

## The window.openai Bridge

The `window.openai` object provides access to ChatGPT runtime:

### Data Access

```javascript
// Tool input parameters
const input = window.openai.toolInput;

// Tool output (structuredContent + _meta)
const output = window.openai.toolOutput;

// Response metadata
const meta = window.openai.toolResponseMetadata;
```

### Context Information

```javascript
// Theme: "light" or "dark"
const theme = window.openai.theme;

// Display mode: "inline" or "modal"
const displayMode = window.openai.displayMode;

// User's locale (BCP 47)
const locale = window.openai.locale;
```

### Tool Invocation

Call tools from the widget (requires `openai/widgetAccessible: true`):

```javascript
const result = await window.openai.callTool("tool_name", {
  param1: "value1",
  param2: "value2"
});
```

### State Management

```javascript
// Save widget state (persists across conversation turns)
await window.openai.setWidgetState({ key: "value" });

// Read previous state
const prevState = window.openai.toolResponseMetadata?.widgetState;
```

### Layout Control

```javascript
// Request modal display
await window.openai.requestModal();

// Set display mode
await window.openai.requestDisplayMode("modal"); // or "inline"

// Report content height for proper sizing
window.openai.notifyIntrinsicHeight(400);
```

### File Handling

```javascript
// Upload a file
const { fileId } = await window.openai.uploadFile(file);

// Get download URL for a file
const { downloadUrl } = await window.openai.getFileDownloadUrl({ fileId });
```

## Widget Template Structure

### Basic Template

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; }
        body {
            font-family: system-ui, -apple-system, sans-serif;
            padding: 16px;
            background: var(--bg-color, #ffffff);
            color: var(--text-color, #000000);
        }
        /* Theme support */
        body.dark {
            --bg-color: #1a1a1a;
            --text-color: #ffffff;
        }
    </style>
</head>
<body>
    <div id="app">Loading...</div>

    <script>
        // Apply theme
        if (window.openai?.theme === 'dark') {
            document.body.classList.add('dark');
        }

        // Get data from tool response
        const data = window.openai?.toolOutput?.structuredContent;
        const meta = window.openai?.toolOutput?._meta;

        // Render content
        const app = document.getElementById('app');
        if (data) {
            app.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
        }

        // Report height
        window.openai?.notifyIntrinsicHeight(document.body.scrollHeight);
    </script>
</body>
</html>
```

### Interactive Widget

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <style>
        .card { padding: 16px; border: 1px solid #ddd; border-radius: 8px; }
        .btn { padding: 8px 16px; background: #0066cc; color: white; border: none; border-radius: 4px; cursor: pointer; }
        .btn:hover { background: #0055aa; }
    </style>
</head>
<body>
    <div class="card">
        <h2 id="title">Item</h2>
        <p id="description"></p>
        <button class="btn" id="action">Take Action</button>
    </div>

    <script>
        const data = window.openai?.toolOutput?._meta?.item;

        document.getElementById('title').textContent = data?.name || 'Unknown';
        document.getElementById('description').textContent = data?.description || '';

        document.getElementById('action').addEventListener('click', async () => {
            try {
                const result = await window.openai.callTool('process_item', {
                    itemId: data?.id
                });
                alert('Success: ' + JSON.stringify(result));
            } catch (err) {
                alert('Error: ' + err.message);
            }
        });
    </script>
</body>
</html>
```

## Linking Tools to Widgets

### Server-Side

Return the widget URI in `_meta.openai/outputTemplate`:

```python
@mcp.tool()
def get_dashboard() -> dict:
    return {
        "structuredContent": {"summary": "Dashboard loaded"},
        "_meta": {
            "fullData": {...},  # Widget-only
            "openai/outputTemplate": "ui://widget/dashboard.html"
        }
    }
```

### Widget-Accessible Tools

Enable tools to be called from widgets:

```python
@mcp.tool()
def refresh_data() -> dict:
    return {
        "structuredContent": {"data": [...]},
        "_meta": {
            "openai/widgetAccessible": True
        }
    }
```

## CSP Configuration

Configure Content Security Policy for widgets:

```python
@mcp.resource("ui://widget/main.html")
def widget() -> dict:
    return {
        "contents": [{
            "uri": "ui://widget/main.html",
            "mimeType": "text/html+skybridge",
            "text": html_content
        }],
        "_meta": {
            "openai/widgetCSP": {
                "connect_domains": ["api.example.com"],
                "resource_domains": ["cdn.example.com"],
                "frame_domains": ["embed.example.com"]
            }
        }
    }
```

## Theme Support

Handle light and dark themes:

```javascript
const theme = window.openai?.theme || 'light';
document.documentElement.setAttribute('data-theme', theme);
```

```css
:root {
    --bg: #ffffff;
    --text: #000000;
}

[data-theme="dark"] {
    --bg: #1a1a1a;
    --text: #ffffff;
}

body {
    background: var(--bg);
    color: var(--text);
}
```

## Best Practices

1. **Keep widgets lightweight** - Minimize bundle size for fast loading
2. **Handle missing data gracefully** - Check for null/undefined
3. **Support both themes** - Test in light and dark mode
4. **Report content height** - Call `notifyIntrinsicHeight` after render
5. **Use semantic HTML** - Improve accessibility
6. **Avoid external dependencies** - Inline all code when possible

## Additional Resources

### Reference Files

For detailed patterns and examples:
- **`references/window-openai-api.md`** - Complete window.openai API reference
- **`references/csp-guide.md`** - CSP configuration guide

### Example Files

Working examples in `examples/`:
- **`examples/basic-widget.html`** - Simple data display widget
- **`examples/interactive-widget.html`** - Widget with tool calls

### Official Documentation

- Apps SDK UI Guidelines: https://developers.openai.com/apps-sdk/concepts/ui-guidelines/
- Widget Reference: https://developers.openai.com/apps-sdk/reference/

Related Skills

mobile-ui-development-rule

16
from diegosouzapw/awesome-omni-skill

General rules pertaining to Mobile UI development. Covers UI/UX best practices, state management, and navigation patterns.

mobile-development

16
from diegosouzapw/awesome-omni-skill

Cross-platform and native mobile development. Frameworks: React Native, Flutter, Swift/SwiftUI, Kotlin/Jetpack Compose. Capabilities: mobile UI, offline-first architecture, push notifications, deep linking, biometrics, app store deployment. Actions: build, create, implement, optimize, test, deploy mobile apps. Keywords: iOS, Android, React Native, Flutter, Swift, Kotlin, mobile app, offline sync, push notification, deep link, biometric auth, App Store, Play Store, iOS HIG, Material Design, battery optimization, memory management, mobile performance. Use when: building mobile apps, implementing mobile-first UX, choosing native vs cross-platform, optimizing battery/memory/network, deploying to app stores, handling mobile-specific features.

miniprogram-development

16
from diegosouzapw/awesome-omni-skill

WeChat Mini Program development rules. Use this skill when developing WeChat mini programs, integrating CloudBase capabilities, and deploying mini program projects.

minimalist-surgical-development

16
from diegosouzapw/awesome-omni-skill

Use when editing an existing codebase and the goal is minimal, standard, and non-invasive changes - prioritizes simplest solution, standard libraries first, and surgical modification without unsolicited refactors

mcp-development

16
from diegosouzapw/awesome-omni-skill

Model Context Protocol (MCP) server development and AI/ML integration patterns. Covers MCP server implementation, tool design, resource handling, and LLM integration best practices. Use when developing MCP servers, creating AI tools, integrating with LLMs, or when asking about MCP protocol, prompt engineering, or AI system architecture.

local-development

16
from diegosouzapw/awesome-omni-skill

Running functions and web app locally, troubleshooting emulator issues, Storybook. Use when running or debugging locally.

laravel-type-bridge-development

16
from diegosouzapw/awesome-omni-skill

Generate TypeScript/JavaScript type artifacts from Laravel PHP definitions — enums, i18n translations, and enum translator composables.

kafka-development-practices

16
from diegosouzapw/awesome-omni-skill

Applies general coding standards and best practices for Kafka development with Scala.

graphql-api-development

16
from diegosouzapw/awesome-omni-skill

Comprehensive guide for building GraphQL APIs including schema design, queries, mutations, subscriptions, resolvers, type system, error handling, authentication, authorization, caching strategies, and production best practices

Golang Backend Development

16
from diegosouzapw/awesome-omni-skill

Architectural standards and coding practices for the Go backend.

game-development

16
from diegosouzapw/awesome-omni-skill

Game development orchestrator. Routes to platform-specific skills based on project needs.

frontend-mobile-development-component-scaffold

16
from diegosouzapw/awesome-omni-skill

You are a React component architecture expert specializing in scaffolding production-ready, accessible, and performant components. Generate complete component implementations with TypeScript, tests, s