This project is a Proof of Concept for an Employee Portal built as a ChatGPT App using the OpenAI Apps SDK. It demonstrates how to render interactive UI widgets within ChatGPT (or compatible clients like MCPJam) by leveraging the Model Context Protocol (MCP).
The application consists of two main components:
- Frontend (Vite + React): A standard web application that renders the UI.
- Backend (FastMCP): An MCP server that exposes tools and resources to ChatGPT.
graph LR
User[User] <--> ChatGPT["ChatGPT / MCP Client"]
ChatGPT <-->|"MCP Protocol (SSE)"| Backend["FastMCP Server"]
Backend -->|"Refers to"| Frontend["React Frontend"]
ChatGPT -->|"Embeds via iframe"| Frontend
The key to rendering UI is the interaction between the Tool Call, the Structured Response, and the Resource.
sequenceDiagram
participant Client as ChatGPT / MCPJam
participant Server as FastMCP Server
participant Frontend as React App
Note over Client, Server: 1. Tool Execution
Client->>Server: CallToolRequest (name="get_employee_info")
activate Server
Server->>Server: Process Logic (Fetch Data)
Server-->>Client: CallToolResult
deactivate Server
Note right of Server: Returns structuredContent (Data)<br/>AND _meta (Template URI)
Note over Client, Frontend: 2. UI Hydration
Client->>Client: Detects _meta.openai.outputTemplate
Client->>Server: ReadResourceRequest (uri="employee://ui/my-info")
activate Server
Server-->>Client: ReadResourceResult (text/html+skybridge)
deactivate Server
Note right of Server: Returns HTML with <iframe><br/>pointing to Frontend URL
Client->>Frontend: Render <iframe>
Frontend-->>Client: Display UI
- Node.js (v18+)
- Python 3.10+
pip
The frontend must be running and accessible. We bind to 0.0.0.0 to ensure accessibility from iframes.
cd frontend
npm install
npm run dev -- --hostRuns on http://localhost:5174
The backend serves the MCP protocol over SSE.
# Install dependencies
pip install fastmcp uvicorn
# Run Server
python backend/server.pyRuns on http://0.0.0.0:8000/mcp
Building a ChatGPT App requires a specific pattern in your MCP server to enable UI rendering. Standard FastMCP decorators (@mcp.tool()) are not sufficient because they don't support the required response structure.
Your server must expose resources with the MIME type text/html+skybridge. These resources return the HTML that embeds your frontend.
RESOURCES = {
"app://ui/widget": {
"html": '<iframe src="http://localhost:5174/widget" ...></iframe>'
}
}You must override the default FastMCP handlers to return structuredContent and _meta.
Why?
structuredContent: Passes the actual data (JSON) to the widget._meta: Tells ChatGPT which UI template to use (openai/outputTemplate).
@mcp._mcp_server.list_tools()
async def _list_tools() -> List[types.Tool]:
# Return tools with _meta definitions
...
async def _call_tool_request(req: types.CallToolRequest) -> types.ServerResult:
# ... Logic ...
return types.ServerResult(
types.CallToolResult(
content=[types.TextContent(type="text", text="Executed")],
structuredContent={"key": "value"}, # DATA FOR WIDGET
_meta={
"openai/outputTemplate": "app://ui/widget", # UI REFERENCE
"openai/toolInvocation/invoking": "Loading...",
"openai/toolInvocation/invoked": "Done"
}
)
)
# Register the handler
mcp._mcp_server.request_handlers[types.CallToolRequest] = _call_tool_request- Use MCPJam (Beta) or the ChatGPT Desktop App.
- Connect to your server's SSE endpoint (e.g.,
http://localhost:8000/mcp). - Run a tool and verify the UI renders.
-
frontend/: Vite-React Applicationsrc/pages/: Contains the UI views (MyInfo,Payroll,Travel).src/components/: Reusable UI components.tailwind.config.js: Styling configuration.
-
backend/: FastMCP Serverserver.py: The core server implementation.- Defines
TOOLSandRESOURCESdictionaries. - Implements
_call_tool_requestand_handle_read_resource. - Configures SSE transport.
- Defines