Artifacts Platform
Agent DocsAppschatgpt-app

MCP Server

Route handler patterns and tool implementation for ChatGPT MCP integration

MCP Server Implementation

Table of Contents


Route Handler Structure

The MCP server is implemented as a Next.js Route Handler at app/mcp/route.ts.

Handler Initialization

The handler is initialized when app/mcp/route.ts module is evaluated:

  1. Load widget resources - each resource loader (e.g., loadArtifactViewer()) fetches and caches widget HTML
  2. Register resources using registerResource() helper
  3. Register tools using registerTool() helper, passing widget metadata
  4. Configure authentication - tools can optionally require OAuth (see authentication.md for details)

See app/mcp/route.ts for the complete implementation.

Request Flow

1. Tool Discovery

ChatGPT → GET /mcp

Handler responds with available tools and resources

ChatGPT displays tools in UI

2. Tool Invocation

ChatGPT → POST /mcp (tool call with tool name and input)

Route handler extracts tool names from request body

Checks if any tool requires authentication (see authentication.md)

Validates auth token if required, extracts userId

Tool handler executes with validated input and auth context

Returns response with content, structuredContent, and _meta

3. Resource Loading

ChatGPT → GET /mcp (resource read request)

Handler returns pre-loaded HTML cached at module initialization

ChatGPT renders widget in iframe

Tool Registration Pattern

Tools use a factory pattern. Each tool file in app/mcp/tools/ exports a factory function that returns an McpTool object with name, config, and handler.

Key requirements:

  1. Define Zod schemas for input/output validation
  2. Factory returns McpTool<InputSchema, OutputSchema, InputType>
  3. Handler uses resolveAuthContext(context.authInfo) to extract userId for authenticated tools
  4. Handler returns { content, structuredContent, _meta } triple
  5. Use satisfies ToolHandler<InputType> for type safety

See app/mcp/tools/artifact-list.ts, artifact-view.ts, or artifact-create.ts for complete implementation examples.

Resource Registration Pattern

Resources represent widget HTML templates that tools can reference. Each resource file in app/mcp/resources/ exports a loader function that:

  1. Fetches widget HTML via getAppsSdkCompatibleHtml(baseURL, '/page-route')
  2. Returns { resource, metadata } where resource is for registerResource() and metadata is for tool _meta
  3. Must use mimeType: 'text/html+skybridge' (required by ChatGPT)

When creating a new widget/resource: Use app/mcp/resources/artifact-viewer.ts as an implementation reference.

Widget HTML Loading

Widget HTML is loaded when app/mcp/route.ts module is evaluated (before handling any requests), not per-request, via getAppsSdkCompatibleHtml(baseURL, '/page-route').

This function:

  1. Uses baseURL from baseUrl.ts (resolved based on NODE_ENV and Vercel environment variables)
  2. Fetches the server-rendered HTML from the Next.js page route via HTTP
  3. Returns the HTML string, which is stored in the resource handler closure for all subsequent requests

Critical: Widget pages must be fully server-side rendered. Client-side hydration happens in the ChatGPT iframe.

See app/mcp/lib/shared.ts (function getAppsSdkCompatibleHtml) for the implementation.

Error Handling

Authentication Errors

Authentication errors are handled by the MCP route handler - see authentication.md for details.

Validation Errors

Input validation happens automatically via Zod schemas in tool configurations. The MCP SDK validates input against inputSchema before invoking the tool handler.

Pattern:

  • Define validation rules in the tool's inputSchema object using Zod methods (.min(), .max(), .optional(), etc.)
  • SDK performs validation before handler execution
  • Validation failures return structured error responses to ChatGPT
  • Tool handlers can also throw errors, which the MCP SDK catches and converts to error responses

Example validation rules:

const inputSchema = {
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
  items: z.array(z.string()).min(1).max(100),
  status: z.enum(['active', 'inactive']).optional()
};

Base URL Resolution

When implementing MCP tools or resources that need the application's URL (for OAuth metadata, widget HTML loading, etc.), import baseURL from baseUrl.ts:

import { baseURL } from '@/baseUrl';

Usage:

  • Pass to resource loader functions (e.g., loadArtifactViewer(baseURL))
  • Pass to getAppsSdkCompatibleHtml(baseURL, '/page-route') when fetching widget HTML
  • Never hardcode URLs like http://localhost:3000 or https://yourapp.vercel.app

The baseURL constant resolves automatically based on environment (development vs production). See baseUrl.ts for implementation details.

CORS Handling

CORS middleware in middleware.ts runs on all routes (matcher: '/:path*').

Behavior:

  • Handles OPTIONS preflight requests for all paths
  • Adds CORS headers (Access-Control-Allow-Origin: *) to all responses
  • Uses clerkMiddleware() only for /mcp routes (for session/auth context)
  • All routes including .well-known/* OAuth discovery endpoints receive CORS headers

See middleware.ts for the complete implementation.

References

  • app/mcp/route.ts - Main MCP handler
  • app/mcp/lib/tool-registry.ts - Tool registration helper
  • app/mcp/lib/resource-registry.ts - Resource registration helper
  • app/mcp/lib/shared.ts - Shared utilities (createToolMeta, resolveAuthContext)
  • app/mcp/tools/*.ts - Individual tool implementations
  • app/mcp/resources/*.ts - Individual resource loaders
  • baseUrl.ts - Environment-based URL resolution
  • middleware.ts - CORS configuration
  • next.config.ts - Asset prefix configuration

On this page