Claude Connectors Tutorial: Build and Deploy a Connector to Claude
Building an interactive Claude Connector with sunpeak.
TL;DR: Build an interactive Claude Connector with sunpeak in 5 minutes, test it in the local Claude simulator without an account, then connect it to a real Claude session with ngrok and a custom connector URL.
Claude Connectors are MCP servers that extend what Claude can do. Over 200 connectors are listed in the Connectors Directory at claude.ai, from Google Drive and Slack to Figma and Canva. Some return data for Claude to reason over. Others render full interactive UIs inside the chat.
In this tutorial, you will build an interactive connector (a Claude App) that renders a support ticket card inside Claude, test it locally, 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 connecting 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, and Slack 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.
Prerequisites
You need Node.js 20 or later and pnpm. You do not need a Claude account for local development. sunpeak’s simulator handles everything until you are ready to deploy.
Step 1: Scaffold the Project
pnpm add -g sunpeak
sunpeak new
Name your project and pick any starter resources. We are building a new resource from scratch, so the selection does not matter. cd into your project directory.
Step 2: Build the Connector
An interactive connector has three parts: a resource (UI), a tool (backend), and a simulation (test data).
The resource
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>
);
}
This component receives ticket data from useToolData and renders it as a card with status and priority badges. See the resource docs for all config options.
The tool
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.',
},
};
}
See the tool docs for all config options.
The simulation
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."
}
}
}
For a complete walkthrough of resources, tools, and simulations, see the MCP App tutorial.
Step 3: Test in the Claude Simulator
sunpeak dev
Open http://localhost:3000. 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.
No Claude account needed for any of this. The local simulator replicates both runtimes on localhost.
Step 4: Connect to a Real Claude Session
When you are 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.
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.
Claude-specific behavior
When Claude fetches your resource, sunpeak detects Claude’s user-agent and serves the pre-built production bundle instead of a Vite dev server page. Claude’s iframe sandbox blocks HTTP script sources, so it needs self-contained HTML.
During development, when you save a file change:
- sunpeak auto-rebuilds the affected resources.
- sunpeak sends a
notifications/resources/list_changednotification to Claude. - Claude re-fetches the updated resource.
This replaces the Vite HMR that ChatGPT gets. The local simulator works with full HMR for both hosts.
Step 5: 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.
- 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. Provide a test account for Anthropic’s reviewers. Pure client credentials flow (machine-to-machine without user interaction) is not supported.
- OAuth callback: Allowlist both
https://claude.ai/api/mcp/auth_callbackandhttps://claude.com/api/mcp/auth_callbackas redirect URIs.
sunpeak projects satisfy the transport requirement by default. The annotations field in your tool config handles the annotation requirement:
export const tool: AppToolConfig = {
resource: 'ticket',
title: 'Show Ticket',
description: 'Look up a support ticket and display it',
annotations: { readOnlyHint: true }, // required for Directory submission
};
For tools that modify data, use destructiveHint instead:
annotations: { destructiveHint: true },
Submit
Fill out the Connectors Directory server review form. Anthropic reviews submissions manually.
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, Goose, VS Code, and any future host that implements the MCP App standard.
To test across hosts locally:
import { test, expect } from '@playwright/test';
import { createSimulatorUrl } from 'sunpeak/chatgpt';
const hosts = ['chatgpt', 'claude'] as const;
for (const host of hosts) {
test(`renders ticket card [${host}]`, async ({ page }) => {
await page.goto(
createSimulatorUrl({ simulation: 'show-ticket', host })
);
const iframe = page.frameLocator('iframe');
await expect(iframe.locator('text=TICK-1234')).toBeVisible();
});
}
See the testing guide for a deep dive on testing.
Get Started
pnpm add -g sunpeak && sunpeak new
Further Reading
- Claude Connectors vs Claude Apps - explains the full relationship between standard and interactive connectors.
- MCP App tutorial - the complete step-by-step for building resources, tools, simulations, and automated tests.
- Claude simulator - covers the multi-host simulator in detail.
- Testing guide - covers unit tests and e2e tests with cross-host coverage.
- MCP Overview
- Tools
- Resources
- MCP Apps introduction - has the protocol architecture and full API reference.
- Tool reference - documents tool annotations, schema, and handler extras.
- 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?
A Claude Connector is an MCP server that exposes tools to Claude. Build an MCP server with tools that return data from your service, then connect it to Claude via Settings > Connectors > Add custom connector. For an interactive connector that renders UI, use sunpeak to add MCP App resources to your project.
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. Both are MCP servers.
How do I connect my MCP server to Claude?
Run your MCP server on a publicly accessible URL (use ngrok for development). In Claude, go to Settings > Connectors > Add custom connector, enter your server URL with /mcp path, and save. Enable the connector per conversation via the + button. Free users can add one custom connector. Pro, Max, Team, and Enterprise plans support more.
Do I need a paid Claude account to develop a Claude Connector?
No. Use sunpeak to build and test your connector locally without any Claude account. The local simulator replicates Claude and ChatGPT runtimes on localhost. You only need a Claude account (any paid plan) 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 30% of rejections. Your server must use Streamable HTTP transport and provide a test account if authentication is required.
Does my Claude Connector also work in ChatGPT?
Yes. A connector built with sunpeak is an MCP server that works with any MCP-compatible host. The same MCP App resources render in both Claude and ChatGPT without code changes. Build once, deploy to both.
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. sunpeak detects Claude's user-agent and automatically serves the pre-built production bundle. When you save a file, sunpeak auto-rebuilds and notifies Claude to re-fetch the resource. The local simulator works with HMR for both hosts.
What are the requirements for the Claude Connectors Directory?
Your server must use Streamable HTTP transport (SSE support may be 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 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.