Claude Connectors Tutorial: Build and Deploy a Connector to Claude (May 2026)
Building an interactive Claude Connector.
TL;DR: A Claude Connector is an MCP server. Build one by creating tools that return data from your service, optionally add MCP App resources for interactive UI, test locally with the multi-host inspector, then connect to Claude via Settings > Connectors. This tutorial walks through the full flow.
Claude Connectors are MCP servers that extend what Claude can do. Over 200 connectors are listed in the Connectors Directory, from Google Drive and Slack to Figma, Adobe, and Canva. Some return data for Claude to reason over. Others render full interactive UIs inside the chat, complete with cards, charts, forms, and action buttons.
In this tutorial, you’ll build an interactive connector that renders a support ticket card inside Claude, test it locally across multiple hosts, and deploy it to a real Claude session.
Two Types of Connectors
Before building, it helps to understand the two types. (For a deeper comparison, see Claude Connectors vs Claude Apps.)
Standard connectors expose tools that return data. Claude calls the tool, gets structured data back, and uses it in a text response. A standard connector linking Claude to your issue tracker would let Claude look up tickets and describe them in prose.
Interactive connectors also include MCP App resources: React components that render UI inside the chat. Instead of describing a ticket in text, Claude renders your component as a visual card with status badges, assignee avatars, and action buttons. In the Connectors Directory, interactive connectors are marked with an “Interactive” badge. Figma, Canva, Asana, Adobe, Slack, Box, Hex, and monday.com are all interactive connectors.
Both types are MCP servers. An interactive connector is a standard connector with a UI layer on top. This tutorial builds the interactive kind.
Anatomy of an Interactive Connector
An interactive Claude Connector has three parts:
- A tool that Claude calls. The tool has a schema (what arguments it accepts) and a handler (what it does when called). The handler fetches data from your service and returns structured content.
- A resource (UI component) that renders the tool’s output inside the conversation. This is a React component that receives the tool’s structured content and displays it as a card, chart, form, or whatever UI makes sense.
- A simulation (optional, for testing). A JSON fixture that defines a reproducible tool state, so you can develop and test your UI without calling the real backend every time.
The tool links to the resource by name. When Claude calls the tool and the handler returns structuredContent, Claude renders the linked resource component with that data. This is part of the MCP App standard (ext-apps, now at v1.7.1 with 2,200+ GitHub stars), which means the same pattern works across ChatGPT, Claude, Goose, VS Code, Postman, and MCPJam.
Prerequisites
You need Node.js 20 or later and pnpm. You do not need a Claude account for local development.
Step 1: Scaffold the Project
This tutorial uses sunpeak to scaffold the project because it sets up the full MCP server structure (tools, resources, simulations, inspector) in one command:
npx sunpeak new
Name your project and pick any starter resources. We’re building a new resource from scratch, so the selection doesn’t matter. cd into your project directory.
Step 2: Build the Resource (UI)
Create src/resources/ticket/ticket.tsx:
import { useToolData, SafeArea } from 'sunpeak';
import type { ResourceConfig } from 'sunpeak';
export const resource: ResourceConfig = {
title: 'Ticket',
description: 'Display a support ticket',
};
interface TicketData {
id: string;
title: string;
status: 'open' | 'in_progress' | 'resolved';
priority: 'low' | 'medium' | 'high';
assignee: string;
created: string;
description: string;
}
const statusColors = {
open: 'bg-yellow-100 text-yellow-800',
in_progress: 'bg-blue-100 text-blue-800',
resolved: 'bg-green-100 text-green-800',
};
const priorityColors = {
low: 'bg-gray-100 text-gray-700',
medium: 'bg-orange-100 text-orange-700',
high: 'bg-red-100 text-red-700',
};
export function TicketResource() {
const { output } = useToolData<unknown, TicketData>(undefined, undefined);
if (!output) return null;
return (
<SafeArea className="p-5 font-sans max-w-md mx-auto">
<div className="flex items-start justify-between mb-3">
<div>
<span className="text-xs text-gray-400 font-mono">{output.id}</span>
<h1 className="text-lg font-bold mt-0.5">{output.title}</h1>
</div>
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${priorityColors[output.priority]}`}>
{output.priority}
</span>
</div>
<p className="text-sm text-gray-600 mb-4">{output.description}</p>
<div className="flex items-center gap-3 text-sm">
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${statusColors[output.status]}`}>
{output.status.replace('_', ' ')}
</span>
<span className="text-gray-400">|</span>
<span className="text-gray-600">{output.assignee}</span>
<span className="text-gray-400">|</span>
<span className="text-gray-400">{output.created}</span>
</div>
</SafeArea>
);
}
The component receives ticket data via useToolData and renders it as a card with status and priority badges. SafeArea handles padding so the content doesn’t overlap with host UI chrome. See the resource docs for all config options.
sunpeak provides 20+ typed React hooks for building resources. Beyond useToolData, you can use useHostContext to detect which host your app is running in, useDisplayMode to adapt your layout to the host’s display mode, useTheme to pick up light/dark theming, and useCallServerTool to call back to your MCP server from the UI. See the interactive MCP Apps guide for patterns using useAppState and other hooks.
Step 3: Build the Tool (Backend)
Create src/tools/show-ticket.ts:
import { z } from 'zod';
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
export const tool: AppToolConfig = {
resource: 'ticket',
title: 'Show Ticket',
description: 'Look up a support ticket and display it',
annotations: { readOnlyHint: true },
};
export const schema = {
ticketId: z.string().describe('Ticket ID to look up (e.g. TICK-1234)'),
};
type Args = z.infer<z.ZodObject<typeof schema>>;
export default async function (args: Args, _extra: ToolHandlerExtra) {
// In production, fetch from your ticket system API using args.ticketId
return {
structuredContent: {
id: 'TICK-1234',
title: 'Search results not loading on mobile',
status: 'in_progress',
priority: 'high',
assignee: 'Sarah Chen',
created: '2026-03-04',
description:
'Users on iOS Safari report that search results fail to render after the latest deploy. Affects approximately 12% of mobile traffic.',
},
};
}
The resource: 'ticket' field links this tool to the ticket resource. When Claude calls this tool, the structured content gets passed to your React component. The annotations field is required for Connectors Directory submission. Every tool must declare readOnlyHint: true (for read operations) or destructiveHint: true (for write/delete operations). Missing annotations cause about 30% of rejections.
If your connector needs to call external APIs from the resource iframe (for example, loading images from a CDN), you’ll need to configure CSP domains. See the CSP guide for how to set up resourceDomains, frameDomains, and connectDomains.
See the tool docs for all config options.
Step 4: Add a Simulation (Test Data)
Create tests/simulations/show-ticket.json:
{
"tool": "show-ticket",
"userMessage": "Show me ticket TICK-1234",
"toolInput": {
"ticketId": "TICK-1234"
},
"toolResult": {
"structuredContent": {
"id": "TICK-1234",
"title": "Search results not loading on mobile",
"status": "in_progress",
"priority": "high",
"assignee": "Sarah Chen",
"created": "2026-03-04",
"description": "Users on iOS Safari report that search results fail to render after the latest deploy. Affects approximately 12% of mobile traffic."
}
}
}
Simulations are JSON fixtures that define a reproducible tool state: the tool input Claude would send and the output your handler would return. The inspector loads them automatically so you can develop your UI against known data without calling a real backend or spending host credits. You can create multiple simulations per tool to cover different states (success, empty results, error cases). You can also load simulations in Playwright tests for automated E2E testing. For a complete walkthrough of simulations, see the MCP App tutorial.
Step 5: Test Locally
pnpm dev
Open http://localhost:3000. The sunpeak inspector opens with your connector running. Select Claude from the Host dropdown in the sidebar. Your ticket card renders inside Claude’s conversation chrome with the warm beige palette.
Switch to ChatGPT in the dropdown to verify it works there too. The same component renders in both hosts because both implement the MCP App standard. This is one of the main advantages of building on the MCP standard: your connector works across hosts without code changes.
No Claude account needed for any of this. The local inspector replicates both runtimes on localhost, so you can iterate without spending credits or clicking through manual refresh flows.
Step 6: Write Automated Tests
Before connecting to a real Claude session, add automated tests so you can catch regressions on every code change. sunpeak includes a full testing framework with several test layers:
E2E tests use the inspector Playwright fixture to render your resource in a real browser:
import { test, expect } from 'sunpeak/test';
test('renders ticket card', async ({ inspector }) => {
const result = await inspector.renderTool('show-ticket', {});
const app = result.app();
await expect(app.locator('text=TICK-1234')).toBeVisible();
await expect(app.locator('text=high')).toBeVisible();
});
Tests run against both ChatGPT and Claude hosts by default via Playwright projects, so you get cross-host coverage automatically. You can also add unit tests for your tool handlers, visual regression tests to catch UI drift, and multi-model evals to verify that different LLMs call your tools correctly.
Run the full test suite:
pnpm test
This runs unit tests (Vitest) and E2E tests (Playwright). For more granular control, use pnpm test:unit, pnpm test:e2e, or pnpm test:visual. See the testing guide for a full walkthrough of all test types.
Step 7: Connect to a Real Claude Session
When you’re ready to test in a real Claude session, you need a publicly accessible URL. Claude cannot reach localhost.
Create a tunnel
Use ngrok to expose your local server:
ngrok http 8000
Copy the forwarding URL (something like https://abc123.ngrok-free.app).
Add the custom connector in Claude
- Open Claude Connectors.
- Click the + button in the input area and select Add a connector.
- Enter your ngrok URL with the
/mcppath:https://abc123.ngrok-free.app/mcp - Click Add.
Only Pro, Max, Team, and Enterprise plans can add custom connectors. Directory connectors are available on all plans including Free. For Team and Enterprise accounts, an admin must enable connectors before users can access them.
Use it in a conversation
Ask Claude: “Show me ticket TICK-1234.”
Claude calls your show-ticket tool, and your ticket card renders inside the chat. For more on live testing, see live testing Claude Connectors in ChatGPT.
How Claude handles resource bundles
Claude’s iframe sandbox blocks HTTP script sources, which means it can’t load resources from a Vite dev server. When Claude fetches your resource, it needs self-contained HTML with all JavaScript inlined.
sunpeak handles this automatically: it detects Claude’s user-agent and serves the pre-built production bundle. When you save a file change during development, sunpeak auto-rebuilds and sends a notifications/resources/list_changed notification so Claude re-fetches the updated resource. If you’re building without sunpeak, your server needs to handle this same pattern.
Step 8: Submit to the Connectors Directory
To distribute your connector to all Claude users, submit it to the Connectors Directory.
Requirements
Before submitting, check these requirements:
- Transport: Streamable HTTP. Your server must be internet-accessible over HTTPS. SSE transport was deprecated in the March 2025 MCP spec, and platforms are actively dropping support for it. If you’re migrating from SSE, see the SSE to Streamable HTTP migration guide.
- Annotations: Every tool must include
readOnlyHintordestructiveHintinannotations. Missing annotations cause about 30% of rejections. - Token limit: Tool results cannot exceed 25,000 tokens.
- Timeout: Tool handlers must complete within 5 minutes (300 seconds).
- Auth: If your connector requires authentication, use OAuth with user consent flow. Provide a test account for Anthropic’s reviewers. Pure client credentials flow (machine-to-machine without user interaction) is not supported. See the OAuth guide for details.
- OAuth callback: Allowlist both
https://claude.ai/api/mcp/auth_callbackandhttps://claude.com/api/mcp/auth_callbackas redirect URIs. - Screenshots: For interactive connectors, include screenshots showing your UI rendering inside the chat.
Annotations example
// Read-only tool
export const tool: AppToolConfig = {
resource: 'ticket',
title: 'Show Ticket',
description: 'Look up a support ticket and display it',
annotations: { readOnlyHint: true },
};
// Destructive tool
export const tool: AppToolConfig = {
resource: 'ticket',
title: 'Delete Ticket',
description: 'Permanently delete a support ticket',
annotations: { destructiveHint: true },
};
Pre-submission testing
Before you submit, run your full test suite to catch issues that would cause rejections. The pre-submission testing checklist covers what reviewers look for, including annotation coverage, token limits, timeout behavior, and OAuth flow correctness.
Submit
Fill out the Connectors Directory server review form. Anthropic reviews submissions manually. The Directory submission guide covers what reviewers look for and how to design your tool schemas for the best results.
Your Connector Works Everywhere
The connector you just built is an MCP server. It works with any MCP-compatible host, not just Claude. The same resource component and tool handler run in ChatGPT, Claude, Goose, VS Code (via Copilot), Postman, MCPJam, and any future host that implements the MCP App standard.
ChatGPT calls these “Apps” rather than “Connectors,” but the underlying technology is the same: an MCP server with tools that return structuredContent, rendered by your React resource component. You can publish the same server to both the Claude Connectors Directory and the ChatGPT App marketplace. For a deeper look at building for ChatGPT specifically, see the ChatGPT App tutorial.
To verify cross-host rendering automatically, write Playwright tests that load your simulations in the inspector:
import { test, expect } from 'sunpeak/test';
test('renders ticket card', async ({ inspector }) => {
const result = await inspector.renderTool('show-ticket', {});
const app = result.app();
await expect(app.locator('text=TICK-1234')).toBeVisible();
});
Tests run against both hosts by default, so you get cross-host coverage without writing separate test suites. See the testing guide for a full walkthrough, or get started with sunpeak’s testing framework.
Get Started
npx sunpeak new
Further Reading
- Claude Connectors vs Claude Apps - the full relationship between standard and interactive connectors
- What are Claude Connectors - overview of types, data access, auth, and troubleshooting
- Claude Connector OAuth Authentication - deep dive on when and how to wire up auth
- Testing Claude Connectors - unit tests, E2E tests, and cross-host coverage
- Deploying Claude Connectors - hosting options and production deployment
- Claude Connector Directory Submission - what reviewers look for
- MCP App tutorial - the complete step-by-step for building resources, tools, simulations, and automated tests
- Claude inspector - covers the multi-host inspector in detail
- MCP App evals - test that LLMs call your tools correctly across models
- Anthropic Connectors Directory FAQ - has the latest submission requirements
- Claude Connector Framework - overview of sunpeak's Claude Connector capabilities
Frequently Asked Questions
How do I build a Claude Connector in 2026?
A Claude Connector is an MCP server that exposes tools to Claude. Build an MCP server with tools that return data from your service, deploy it to a public HTTPS URL, and add it via Claude Settings > Connectors > Add custom connector. For interactive connectors that render UI inside the chat, your server also needs to register MCP App resources. Your server must use Streamable HTTP transport, as SSE was deprecated in the March 2025 MCP spec.
What is the difference between a standard Claude Connector and an interactive one?
A standard connector returns data that Claude weaves into text responses. An interactive connector also includes MCP App resources that render UI (cards, dashboards, forms) inside the chat. Interactive connectors are marked with an "Interactive" badge in the Connectors Directory. Figma, Canva, Asana, Adobe, Slack, Box, Hex, and monday.com are all interactive connectors. Both types are MCP servers.
How do I connect my MCP server to Claude?
Run your MCP server on a publicly accessible HTTPS URL (use ngrok for development). In Claude, go to Settings > Connectors > Add custom connector, enter your server URL with the /mcp path, and save. Enable the connector per conversation via the + button. Only Pro, Max, Team, and Enterprise plans can add custom connectors. Directory connectors are available on all plans including Free.
Do I need a paid Claude account to develop a Claude Connector?
No. You can build and test your connector locally without any Claude account. sunpeak includes a multi-host inspector that replicates both the ChatGPT and Claude runtimes at localhost:3000. You can verify tool behavior and UI rendering entirely offline. You only need a Claude account (Pro or above) when you want to connect your finished connector to a real Claude session.
How do I submit my connector to the Claude Connectors Directory?
Fill out the Connectors Directory server review form on the Anthropic website. All tools must include readOnlyHint or destructiveHint annotations, as missing annotations cause about 30% of rejections. Your server must use Streamable HTTP transport, and you need to provide a test account if authentication is required. Interactive connectors should include screenshots showing the UI.
Does my Claude Connector also work in ChatGPT and other hosts?
Yes. If your connector is built on the MCP standard, it works with any MCP-compatible host. MCP App resources render in ChatGPT, Claude, Goose, VS Code (via Copilot), Postman, and MCPJam, so the same tools and UI components work across hosts without code changes. The ext-apps open standard (v1.7.1, 2,200+ GitHub stars) defines how this works.
Why does Claude need a pre-built bundle instead of Vite HMR?
Claude's iframe sandbox blocks HTTP script sources, so it cannot load from a local Vite dev server. Your framework needs to detect Claude's user-agent and serve a pre-built production bundle instead. On file changes, the server sends a notifications/resources/list_changed notification so Claude re-fetches the resource. sunpeak handles this automatically.
What are the requirements for the Claude Connectors Directory?
Your server must use Streamable HTTP transport (SSE is deprecated). All tools need readOnlyHint or destructiveHint annotations. Tool results cannot exceed 25,000 tokens. Tool handlers must complete within 5 minutes. If your connector requires authentication, use OAuth with user consent flow and provide a test account for review. Allowlist both claude.ai and claude.com callback URLs. Pure client credentials flow (machine-to-machine OAuth without user interaction) is not supported.