This is a post from the https://blog.fka.dev/blog/2025-11-22-mcp-apps-101-bringing-interactive-uis-to-ai-conversations/ about A beginner's guide to MCP Apps - the extension that brings interactive user interfaces to Model Context Protocol, allowing servers to deliver rich visual experiences beyond plain text.. Written by Fatih Kadir Akın on November 22, 2025 --- Fatih is a passionate software developer from Istanbul, Turkey, currently leading Developer Relations at Teknasyon. He has authored books on JavaScript and prompt engineering for AI tools. With a deep enthusiasm for web technologies and AI-assisted development, he actively contributes to open-source projects and builds innovative things on GitHub. Beyond coding, Fatih enjoys organizing conferences and sharing knowledge through talks. A strong advocate for open-source collaboration, he specializes in JavaScript and Ruby (particularly Ruby on Rails). He also created prompts.chat, a platform for exploring and optimizing AI prompts for LLMs. READ THIS POST CAREFULLY WITH ALL THE CHUNKS BEFORE RESPONDING. This post contains explanations of some concepts in given context, code examples and instructions about the topic. When you see a code block, analyze it and be ready to apply similar patterns. Pay attention to: 1. Code blocks marked with ```language-name - these contain example code 2. Explanatory text around the code that provides context 3. Any specific instructions or notes about implementation 4. Variable names and patterns that may need to be reused When implementing similar code (if exists), maintain consistent: - Naming conventions - Code style and formatting - Error handling patterns - Documentation approach The goal for the reader is to understand the concepts and be able to apply them appropriately in new situations.
Written by Fatih Kadir Akın, on November 22, 2025
---

# MCP Apps: Bringing Interactive UIs to AI Conversations

Back in May, I wrote about [the limitations of text-only AI interactions and my vision for on-demand UI generation](https://blog.fka.dev/blog/2025-05-16-beyond-text-only-ai-on-demand-ui-generation-for-better-conversational-experiences/). I explored how AI could dynamically generate the right interface components at the right time - forms for data collection, buttons for choices, tables for comparisons. The idea was simple but powerful: why force users to describe everything in text when AI could generate proper UIs on the fly?

Now, that vision is becoming reality. The Model Context Protocol community has formalized this concept into **MCP Apps** - an official extension that enables exactly what I was prototyping. The specification and SDK are now available in the [official MCP Apps repository](https://github.com/modelcontextprotocol/ext-apps), providing types, examples, and a draft specification (SEP-1865) that standardizes how MCP servers can display interactive UI elements in conversational AI clients.

Today, we'll explore how MCP Apps brings interactive user interfaces to AI conversations, turning theoretical possibilities into practical implementations.

## The Problem: When Text Isn't Enough

The Model Context Protocol (MCP) has revolutionized how AI assistants interact with external systems. As I've covered in previous posts, MCP provides powerful capabilities through resources, tools, and prompts. But as I identified in my earlier exploration of AI-generated UIs, there's been one significant limitation: **everything is text**.

Imagine asking an AI to show you weather data. Currently, you'd get something like:

```
Current weather in San Francisco:
Temperature: 68°F
Conditions: Partly cloudy
Wind: 12 mph NW
Humidity: 65%
```

But wouldn't it be better to see an actual weather widget with icons, colors, and maybe even an interactive forecast chart? That's where MCP Apps comes in.

## What Are MCP Apps?

MCP Apps is an extension to the Model Context Protocol that enables servers to deliver interactive user interfaces to AI hosts (like Claude, ChatGPT, or Cursor). Instead of just returning text or structured data, MCP servers can now provide rich HTML interfaces that users can see and interact with.

Think of it as giving your MCP server the ability to create mini web applications that appear right inside your AI conversation. These aren't just static displays – they can be fully interactive widgets with buttons, forms, charts, and real-time updates.

### Demo: See MCP Apps in Action (first)


Video: OpenAI-compatible MCP App demo (openai-oss.mp4)
## How MCP Apps Works: The Three Key Concepts MCP Apps introduces three fundamental concepts that work together: ### 1. UI Resources (The Templates) UI Resources are HTML templates that define how your interface looks. They use a special URI scheme `ui://` and are declared just like any other MCP resource: ```typescript // Declaring a UI resource { uri: "ui://weather/widget", name: "Weather Widget", description: "Interactive weather display", mimeType: "text/html+mcp" // Special MIME type for MCP UI } ``` ### 2. Tool-UI Linkage (The Connection) Tools can now reference UI resources through metadata. When a tool is called, the host knows to render the associated UI: ```typescript // Tool that uses the UI resource { name: "show_weather", description: "Display weather with interactive widget", _meta: { "ui/resourceUri": "ui://weather/widget" // Links to the UI resource } } ``` ### 3. Bidirectional Communication (The Interaction) The UI can communicate back to the host using MCP's standard JSON-RPC protocol over postMessage. This allows for dynamic updates and user interactions: ```javascript // Inside the UI (HTML/JavaScript) // Send a JSON-RPC request to the host window.parent.postMessage({ jsonrpc: "2.0", method: "tools/call", params: { name: "refresh_weather", arguments: { city: "New York" } }, id: 1 }, "*"); // Listen for the response window.addEventListener("message", (event) => { if (event.data.id === 1) { // Handle the response from the tool call console.log(event.data.result); } }); ``` ## Building Your First MCP Apps Server Let's build a simple MCP server that displays an interactive counter widget. This example will demonstrate all the key concepts of MCP Apps in action. ### Setting Up the Project First, create a new project and install dependencies: ```bash mkdir mcp-apps-demo cd mcp-apps-demo npm init -y npm install @modelcontextprotocol/sdk zod npm install -D typescript @types/node ``` Create a `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"] } ``` Update your `package.json` to include the module type: ```json { "name": "mcp-apps-demo", "version": "1.0.0", "type": "module", "scripts": { "build": "tsc", "start": "node build/index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.22.0", "zod": "^4.1.12" }, "devDependencies": { "typescript": "^5.9.3", "@types/node": "^24.10.1" } } ``` ### The Complete Server Implementation Create `src/index.ts`: ```typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // Initialize MCP server const server = new McpServer({ name: "counter-ui-demo", version: "1.0.0" }, { capabilities: { resources: {}, tools: {} } }); // Store counter state let counterValue = 0; // HTML template for our counter UI const COUNTER_UI_HTML = `

🎯 Interactive Counter

0
`; // Register the UI resource server.registerResource( 'counter-widget', 'ui://counter/widget', { title: 'Interactive counter widget UI', description: 'An interactive HTML counter widget', mimeType: 'text/html' }, async (uri) => { return { contents: [{ uri: uri.href, text: COUNTER_UI_HTML }] }; } ); // Register tools server.registerTool( 'show_counter', { title: 'Show counter', description: 'Display an interactive counter widget', inputSchema: {} }, async () => { return { content: [{ type: "text" as const, text: `Current counter value: ${counterValue}` }], // This tells the host to use the UI resource _meta: { "ui/resourceUri": "ui://counter/widget" } }; }); server.registerTool( 'get_counter', { title: 'Get counter', description: 'Get the current counter value', inputSchema: {} }, async () => { return { content: [{ type: "text" as const, text: JSON.stringify({ value: counterValue }) }] }; }); server.registerTool( 'update_counter', { title: 'Update counter', description: 'Update the counter value', inputSchema: { action: z.enum(["increment", "decrement", "reset"]).describe("The action to perform on the counter") } as any }, async ({ action }: any) => { // Update based on action switch (action) { case "increment": counterValue++; break; case "decrement": counterValue--; break; case "reset": counterValue = 0; break; } return { content: [{ type: "text" as const, text: JSON.stringify({ value: counterValue }) }] }; }); // Start the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Counter UI MCP server running on stdio"); } main().catch((error) => { console.error("Server error:", error); process.exit(1); }); ``` ### Understanding the Code Let's break down what's happening in this example: 1. **Server Setup**: We use the modern `Server` class from the MCP SDK with proper capability declarations for resources and tools. 2. **Resource Registration**: We use `server.registerResource()` to register our UI resource at `ui://counter/widget` with the HTML template. 3. **Tool Registration**: We use the `server.registerTool()` method to register three tools with Zod schemas: - `show_counter`: Returns the counter value with UI metadata to trigger the widget display - `get_counter`: Simple tool that returns the current counter value (called by the UI) - `update_counter`: Updates the counter based on an action (increment/decrement/reset) 4. **The HTML Template**: Contains: - Beautiful gradient UI with hover effects - Interactive buttons for increment/decrement/reset - JavaScript that communicates with the host using postMessage and JSON-RPC - Dynamic messages based on counter value 5. **Bidirectional Communication**: The UI communicates with the host using MCP's JSON-RPC protocol over postMessage: - UI sends requests to `window.parent.postMessage()` with JSON-RPC format - Host processes the request and sends back responses - The UI listens for messages and matches responses to pending requests by ID - This follows the standard MCP protocol, just over postMessage instead of stdio or HTTP ### Testing Your MCP Apps Server Build and test your server: ```bash # Build the TypeScript code npm run build # Test with MCP Inspector npx @modelcontextprotocol/inspector node build/index.js ``` In the MCP Inspector: 1. Connect to your server 2. Go to the "Tools" tab 3. Find and execute the `show_counter` tool Note: Not all MCP clients support the UI extension yet. The server will still work with text-only clients - they'll just see the text response without the interactive UI. ### Best Practices for Secure MCP Apps 1. **Validate all inputs** from the UI before processing 2. **Use CSP declarations** to limit external resources 3. **Keep UI logic simple** and server-side validation strong 4. **Avoid sensitive data** in UI templates ## Real-World Use Cases MCP Apps opens up exciting possibilities. In my earlier post, I demonstrated a shipping company support system prototype where AI generated forms for address changes, buttons for confirmations, and tables for comparing options. Now with MCP Apps, these concepts can be implemented in a standardized way: ### Data Visualization Instead of describing data trends, show interactive charts: - Stock price graphs with zoom and pan - System metrics dashboards - Analytics reports with drill-down capabilities ### Form Interfaces Create proper forms for complex inputs: - Configuration wizards - Multi-step surveys - Settings panels with live preview ### Media Displays Show rich media content: - Image galleries with thumbnails - Audio players with controls - Video previews with playback ### Interactive Tools Build mini-applications: - Calculator with history - Color picker for design work - Code formatter with syntax highlighting - File browsers with preview ## Comparing MCP Capabilities Here's how MCP Apps fits with other MCP features: | Feature | Purpose | User Experience | |---------|---------|-----------------| | **Resources** | Provide static data | AI reads and describes content | | **Tools** | Execute actions | AI performs operations and reports results | | **Prompts** | Generate text | AI creates formatted content | | **MCP Apps** | Interactive UIs | User sees and interacts with visual interfaces | ## What's Next for MCP Apps? The MCP Apps specification is still evolving. Future enhancements might include: - **External URLs**: Embedding existing web applications - **State persistence**: Saving widget state between sessions - **Widget communication**: Multiple widgets talking to each other - **More content types**: Native components beyond HTML ## Conclusion MCP Apps represents a significant leap forward for the Model Context Protocol. It's exciting to see the ideas I explored in my [earlier post about on-demand UI generation](https://blog.fka.dev/blog/2025-05-16-beyond-text-only-ai-on-demand-ui-generation-for-better-conversational-experiences/) now formalized into an official MCP extension. What started as experimental prototypes showing forms, buttons, and tables generated by AI has evolved into a standardized protocol that any MCP server can implement. By enabling rich, interactive user interfaces, MCP Apps bridges the gap between conversational AI and traditional applications. The ability to show actual interfaces instead of just describing them makes AI assistants far more useful for real-world tasks. Whether you're building a weather widget, a data dashboard, or an interactive form, MCP Apps provides the foundation for creating engaging experiences within AI conversations. As the ecosystem grows and more hosts adopt MCP Apps support, we'll see increasingly sophisticated integrations that blur the line between chatting with an AI and using a full application. The example we built today is just the beginning. With MCP Apps, your AI tools are no longer limited to text – they can now provide the rich, visual experiences users expect in modern applications. ## What's Next? In my next post, I'll show you how to build interactive UI apps specifically for OpenAI's ChatGPT using their Apps SDK. We'll explore how ChatGPT's implementation of MCP Apps works and build a practical example that runs directly in ChatGPT conversations. ## Resources - [MCP Apps Specification (Draft)](https://github.com/modelcontextprotocol/ext-apps) - [MCP UI Community Project](https://mcpui.dev/) - [Official MCP Documentation](https://modelcontextprotocol.io) _This article was proofread and edited with AI assistance._