MCP Apps (SEP-1865) is the open standard that lets AI agents render interactive UIs inside the conversation. Cross-platform, backed by Anthropic + OpenAI, live in Claude and ChatGPT today. Here's how to build one that actually runs in production."
AI agents just crossed a line.
For years, every agent output was the same: text. Need to show a dashboard? Return JSON. Need a 20-field config form? Walk the user through each field, prompt by prompt. Need a product catalog? Describe it.
That era is over.
MCP Apps - the first official extension to the Model Context Protocol, co-authored by Anthropic and OpenAI - lets your MCP server return fully interactive HTML interfaces that render directly inside Claude, ChatGPT, VS Code, and Goose. Not in a new tab. Not alongside the conversation. Inside it.
This post covers how it works, how to build one with LeanMCP, and what it takes to run one in production.
Why "Text Wall" Was Always the Hard Ceiling
The problem isn't just UX. It's capability.
Commerce, data exploration, multi-step approval workflows, complex configuration - none of these map cleanly to a chat thread. You can approximate them with text. You can't actually do them well.
MCP Apps solves this with a security-first architecture:
- Pre-declared UI resources registered in the server manifest - hosts review and cache them before any tool is called
- Sandboxed iframes that isolate third-party code from the host application
- Bidirectional communication via existing MCP JSON-RPC over postMessage - no new SDKs, no new transport
- Full backward compatibility - text-only fallbacks for clients that don't yet support UI
How It Works
- At connection time, your server declares UI resources in the manifest. The host pre-fetches and caches them.
- The AI invokes a tool. If that tool has an associated UI resource URI, the host renders a UI.
- The host loads HTML into a sandboxed iframe. The UI hydrates with the tool's input and output data.
- The user interacts. The iframe communicates back to the host - and to the model - via JSON-RPC over postMessage.
You control exactly what the model sees vs. what the view sees. Sensitive data can pass to the view without polluting the model's context window.
Building an MCP App with LeanMCP
LeanMCP handles the production layer - auth, observability, multi-tenancy - so you can focus on the interface itself.
Step 1: Create your project
npm install -g @leanmcp/cli
npx @leanmcp/cli create my-mcp-app
cd my-mcp-app
npm install
This generates a clean structure:
my-mcp-app/
├── main.ts # Entry point
├── package.json
├── tsconfig.json
└── mcp/
└── example/
└── index.ts # Your services go here
Step 2: Define your tool
// mcp/analytics/index.ts
import { Tool, SchemaConstraint, Optional } from "@leanmcp/core";
class SalesReportInput {
@SchemaConstraint({ description: "Region to filter by", minLength: 1 })
region!: string;
@Optional()
@SchemaConstraint({
description: "Date range",
enum: ["7d", "30d", "90d"],
default: "30d"
})
range?: string;
}
export class AnalyticsService {
@Tool({
description: "Get sales report data",
inputClass: SalesReportInput
})
async getSalesReport(args: SalesReportInput) {
const data = await fetchSalesData(args.region, args.range);
// Return data - your MCP Apps UI resource
// is registered separately in the server manifest
return {
region: args.region,
totalRevenue: data.revenue,
topProducts: data.products,
chartData: data.chart
};
}
}
Step 3: Configure the HTTP server with logging
// main.ts
import { createHTTPServer } from "@leanmcp/core";
await createHTTPServer({
name: "my-mcp-app",
version: "1.0.0",
port: 8080,
cors: true,
logging: true, // tool calls, latency, errors - all tracked
autoDiscover: true // picks up everything in ./mcp automatically
});
Your server is live at http://localhost:8080/mcp.
Step 4: Add auth - one decorator
// mcp/analytics/index.ts
import { Tool, SchemaConstraint } from "@leanmcp/core";
import { AuthProvider, Authenticated } from "@leanmcp/auth";
const authProvider = new AuthProvider('cognito', {
region: process.env.AWS_REGION,
userPoolId: process.env.COGNITO_USER_POOL_ID,
clientId: process.env.COGNITO_CLIENT_ID
});
await authProvider.init();
// Entire service is protected - token validated automatically
// from _meta.authorization.token in each request
@Authenticated(authProvider)
export class AnalyticsService {
@Tool({
description: "Get sales report - authenticated users only",
inputClass: SalesReportInput
})
async getSalesReport(args: SalesReportInput) {
// No token parsing needed here - LeanMCP handles it
return { data: await fetchSalesData(args.region) };
}
}
Step 5: Add a new service anytime
leanmcp add dashboard
# Creates mcp/dashboard/index.ts with boilerplate
# Auto-registers in main.ts
# Ready to customize immediately
MCP Apps vs GPT Apps - Why Open Wins
OpenAI's GPT Apps launched in October 2025 with an impressive partner list. But it's ChatGPT-only, proprietary, and controlled by one company.
MCP Apps is an open standard, jointly governed by the MCP UI Community Working Group.
| GPT Apps | MCP Apps | With LeanMCP | |
|---|---|---|---|
| Platform | ChatGPT only | Claude, ChatGPT, VS Code, Goose | Both, one server |
| Standard | Proprietary (OpenAI) | Open (SEP-1865) | Supports both |
| Auth | Not included | Not included | ✅ @Authenticated decorator |
| Observability | Not included | Not included | ✅ logging: true
|
| Multi-tenancy | Your problem | Your problem | ✅ Per-user API keys built in |
| Type safety | Manual | Manual | ✅ @SchemaConstraint decorators |
| Service discovery | Manual | Manual | ✅ autoDiscover: true
|
The same LeanMCP server covers both ecosystems. One codebase, full coverage.
The Production Gap Nobody Talks About
The MCP spec gives you the protocol. It does not give you:
- Multi-tenant auth - how do you authenticate each user separately?
- Per-user tracking - who's consuming your API budget?
- Secrets management - how do you inject API keys safely per-request?
- Audit trails - for compliance, who did what and when?
- Type-safe schemas - catch input errors before they reach your tool logic
- Observability - when something breaks at 2am, how do you even know?
LeanMCP is purpose-built for this gap. It's the production runtime layer underneath your MCP App - not a framework on top, not another abstraction. The ground your agent runs on.
Here's what you get with one config line:
// Logging ON = dashboards for tool calls, latency, errors, per-user usage
await createHTTPServer({
name: "my-mcp-app",
version: "1.0.0",
logging: true
});
And the complete auth flow that usually takes a week to implement:
// Client calls your tool like this:
await mcpClient.callTool({
name: "getSalesReport",
arguments: { region: "APAC" },
_meta: {
authorization: {
type: "bearer",
token: "user-jwt-token"
}
}
});
// LeanMCP validates the token automatically.
// Your tool only runs if auth passes.
// No middleware code needed on your end.
Real-World Use Cases
What MCP Apps actually unlocks:
- 📊 Data exploration - Sales analytics tool returns a live dashboard. Filter by region, drill into accounts, export - without leaving the conversation
- ⚙️ Configuration wizards - Selecting 'production' reveals security options dynamically. No sequential prompts
- 📄 Document review - Contract tool displays PDF inline with highlighted clauses. Approve or flag in real time
- 🛒 Commerce - Interactive product catalogs, size selectors, cart flows embedded in agent conversations
- 📡 Real-time monitoring - Server health tool shows live metrics without re-running the tool
As of January 2026, officially supported in Claude (web + desktop), ChatGPT, Goose, and VS Code Insiders.
The Full Agent Stack
MCP Apps completes the platform:
| Layer | Technology |
|---|---|
| Tools - what agents can do | MCP protocol |
| Auth - who can use them | OAuth in MCP spec |
| UI - what users see | MCP Apps (SEP-1865) |
| Runtime - how it all runs in production | LeanMCP |
The ext-apps repository already includes working examples: threejs-server for 3D visualization, map-server for interactive maps, pdf-server for document viewing, system-monitor-server for live dashboards.
TL;DR
- 🚀 MCP Apps = interactive UIs inside Claude, ChatGPT, VS Code, Goose - open standard
- 🔓 Unlike GPT Apps, cross-platform: write once, run everywhere
- ⚡ LeanMCP = production runtime - auth, observability, type safety, multi-tenancy, all built in
- 🛠️ One LeanMCP server covers both GPT Apps and MCP Apps
# Start in 60 seconds
npm install -g @leanmcp/cli
npx @leanmcp/cli create my-mcp-app
cd my-mcp-app && npm start
# -> http://localhost:8080/mcp
Links:
- 🔗 LeanMCP GitHub - open source, MIT licensed
- 📖 Docs
- 🌐 leanmcp.com
- 💬 Discord
Building an MCP App? Drop a comment - would love to see what you're working on. 👇
Top comments (0)