Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
bilalmk avatar

Building Chat Interfaces

  • 14 installs
  • 1 repo stars
  • Updated January 27, 2026
  • bilalmk/todo_correct

building-chat-interfaces is a skill for building AI chat interfaces with ChatKitServer and useChatKit, adding custom backends, JWT auth, and context injection.

About

building-chat-interfaces is a skill for building AI chat interfaces with custom backends, authentication, and context injection. On the frontend it uses useChatKit with a custom fetch to inject auth headers and page context; on the backend it uses ChatKitServer with a custom agent, database persistence, and JWT/JWKS auth. A developer uses it to integrate a chat UI with AI agents, add auth, or inject user and page context. It is not meant for simple chatbots without persistence or custom agent integration.

  • Builds AI chat interfaces with ChatKitServer and useChatKit
  • Injects auth headers and page/user context via a custom fetch interceptor
  • Covers DB persistence and JWT/JWKS authentication for the chat backend

Building Chat Interfaces by the numbers

  • 14 all-time installs (skills.sh)
  • Ranked #11,296 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Jul 28, 2026 (Skillselion catalog sync)
At a glance

building-chat-interfaces capabilities & compatibility

Requires an LLM/agent API key for the backend agent

Capabilities
chat ui building · agent integration · context injection
Works with
openai
Use cases
frontend · api development · orchestration
Pricing
Bring your own API key
From the docs

What building-chat-interfaces says it does

Build AI chat interfaces with custom backends, authentication, and context injection.
SKILL.md
Covers ChatKitServer, useChatKit, and MCP auth patterns.
SKILL.md
// Custom fetch to inject auth and context
SKILL.md
npx skills add https://github.com/bilalmk/todo_correct --skill building-chat-interfaces

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs14
repo stars1
Last updatedJanuary 27, 2026
Repositorybilalmk/todo_correct

What it does

Build an AI chat interface with ChatKit, custom backend, JWT auth, and page/user context injection.

Who is it for?

Integrating a chat UI with a custom AI agent backend and auth

Skip if: Simple chatbots without persistence or custom agent integration

When should I use this skill?

Integrating chat UI with AI agents, adding auth to chat, or injecting user/page context

What you get

A chat interface with a custom agent backend, DB persistence, JWT auth, and injected context.

  • ChatKit server with custom agent
  • React chat UI with context injection
  • JWT-authenticated chat backend

By the numbers

  • Three backend patterns: custom-agent server, DB persistence, JWT/JWKS auth

Files

SKILL.mdMarkdownGitHub ↗

Building Chat Interfaces

Build production-grade AI chat interfaces with custom backend integration.

Quick Start

# Backend (Python)
uv add chatkit-sdk agents httpx

# Frontend (React)
npm install @openai/chatkit-react

---

Core Architecture

Frontend (React)                    Backend (Python)
┌─────────────────┐                ┌─────────────────┐
│  useChatKit()   │───HTTP/SSE───>│  ChatKitServer  │
│  - custom fetch │                │  - respond()    │
│  - auth headers │                │  - store        │
│  - page context │                │  - agent        │
└─────────────────┘                └─────────────────┘

---

Backend Patterns

1. ChatKit Server with Custom Agent

from chatkit.server import ChatKitServer
from chatkit.agents import stream_agent_response
from agents import Agent, Runner

class CustomChatKitServer(ChatKitServer[RequestContext]):
    """Extend ChatKit server with custom agent."""

    async def respond(
        self,
        thread: ThreadMetadata,
        input_user_message: UserMessageItem | None,
        context: RequestContext,
    ) -> AsyncIterator[ThreadStreamEvent]:
        if not input_user_message:
            return

        # Load conversation history
        previous_items = await self.store.load_thread_items(
            thread.id, after=None, limit=10, order="desc", context=context
        )

        # Build history string for prompt
        history_str = "\n".join([
            f"{item.role}: {item.content}"
            for item in reversed(previous_items.data)
        ])

        # Extract context from metadata
        user_info = context.metadata.get('userInfo', {})
        page_context = context.metadata.get('pageContext', {})

        # Create agent with context in instructions
        agent = Agent(
            name="Assistant",
            tools=[your_search_tool],
            instructions=f"{history_str}\nUser: {user_info.get('name')}\n{system_prompt}",
        )

        # Run agent with streaming
        result = Runner.run_streamed(agent, input_user_message.content)
        async for event in stream_agent_response(context, result):
            yield event

2. Database Persistence

from sqlmodel.ext.asyncio.session import AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine

DATABASE_URL = os.getenv("DATABASE_URL").replace("postgresql://", "postgresql+asyncpg://")
engine = create_async_engine(DATABASE_URL, pool_pre_ping=True)

# Pre-warm connections on startup
async def warmup_pool():
    async with engine.begin() as conn:
        await conn.execute(text("SELECT 1"))

3. JWT/JWKS Authentication

from jose import jwt
import httpx

async def get_current_user(authorization: str = Header()):
    token = authorization.replace("Bearer ", "")
    async with httpx.AsyncClient() as client:
        jwks = (await client.get(JWKS_URL)).json()
    payload = jwt.decode(token, jwks, algorithms=["RS256"])
    return payload

---

Frontend Patterns

1. Custom Fetch Interceptor

const { control, sendUserMessage } = useChatKit({
  api: {
    url: `${backendUrl}/chatkit`,
    domainKey: domainKey,

    // Custom fetch to inject auth and context
    fetch: async (url: string, options: RequestInit) => {
      if (!isLoggedIn) {
        throw new Error('User must be logged in');
      }

      const pageContext = getPageContext();
      const userInfo = { id: userId, name: user.name };

      // Inject metadata into request body
      let modifiedOptions = { ...options };
      if (modifiedOptions.body && typeof modifiedOptions.body === 'string') {
        const parsed = JSON.parse(modifiedOptions.body);
        if (parsed.params?.input) {
          parsed.params.input.metadata = {
            userId, userInfo, pageContext,
            ...parsed.params.input.metadata,
          };
          modifiedOptions.body = JSON.stringify(parsed);
        }
      }

      return fetch(url, {
        ...modifiedOptions,
        headers: {
          ...modifiedOptions.headers,
          'X-User-ID': userId,
          'Content-Type': 'application/json',
        },
      });
    },
  },
});

2. Page Context Extraction

const getPageContext = useCallback(() => {
  if (typeof window === 'undefined') return null;

  const metaDescription = document.querySelector('meta[name="description"]')
    ?.getAttribute('content') || '';

  const mainContent = document.querySelector('article') ||
                     document.querySelector('main') ||
                     document.body;

  const headings = Array.from(mainContent.querySelectorAll('h1, h2, h3'))
    .slice(0, 5)
    .map(h => h.textContent?.trim())
    .filter(Boolean)
    .join(', ');

  return {
    url: window.location.href,
    title: document.title,
    path: window.location.pathname,
    description: metaDescription,
    headings: headings,
  };
}, []);

3. Script Loading Detection

const [scriptStatus, setScriptStatus] = useState<'pending' | 'ready' | 'error'>(
  isBrowser && window.customElements?.get('openai-chatkit') ? 'ready' : 'pending'
);

useEffect(() => {
  if (!isBrowser || scriptStatus !== 'pending') return;

  if (window.customElements?.get('openai-chatkit')) {
    setScriptStatus('ready');
    return;
  }

  customElements.whenDefined('openai-chatkit').then(() => {
    setScriptStatus('ready');
  });
}, []);

// Only render when ready
{isOpen && scriptStatus === 'ready' && <ChatKit control={control} />}

---

Next.js Integration

httpOnly Cookie Proxy

When auth tokens are in httpOnly cookies (can't be read by JavaScript):

// app/api/chatkit/route.ts
import { NextRequest, NextResponse } from "next/server";
import { cookies } from "next/headers";

export async function POST(request: NextRequest) {
  const cookieStore = await cookies();
  const idToken = cookieStore.get("auth_token")?.value;

  if (!idToken) {
    return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
  }

  const response = await fetch(`${API_BASE}/chatkit`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${idToken}`,
      "Content-Type": "application/json",
    },
    body: await request.text(),
  });

  // Handle SSE streaming
  if (response.headers.get("content-type")?.includes("text/event-stream")) {
    return new Response(response.body, {
      status: response.status,
      headers: {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
      },
    });
  }

  return NextResponse.json(await response.json(), { status: response.status });
}

Script Loading Strategy

// app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        {/* MUST be beforeInteractive for web components */}
        <Script
          src="https://cdn.platform.openai.com/deployments/chatkit/chatkit.js"
          strategy="beforeInteractive"
        />
      </head>
      <body>{children}</body>
    </html>
  );
}

---

MCP Tool Authentication

MCP protocol doesn't forward auth headers. Pass credentials via system prompt:

SYSTEM_PROMPT = """You are Assistant.

## Authentication Context
- User ID: {user_id}
- Access Token: {access_token}

CRITICAL: When calling ANY MCP tool, include:
- user_id: "{user_id}"
- access_token: "{access_token}"
"""

# Format with credentials
instructions = SYSTEM_PROMPT.format(
    user_id=context.user_id,
    access_token=context.metadata.get("access_token", ""),
)

---

Common Pitfalls

IssueSymptomFix
History not in promptAgent doesn't remember conversationInclude history as string in system prompt
Context not transmittedAgent missing user/page infoAdd to request metadata, extract in backend
Script not loadedComponent fails to renderDetect script loading, wait before rendering
Auth headers missingBackend rejects requestsUse custom fetch interceptor
httpOnly cookiesCan't read token from JSCreate server-side API route proxy
First request slow7+ second delayPre-warm database connection pool

---

Verification

Run: python3 scripts/verify.py

Expected: ✓ building-chat-interfaces skill ready

If Verification Fails

1. Check: references/ folder has chatkit-integration-patterns.md 2. Stop and report if still failing

Related Skills (Tiered System)

  • streaming-llm-responses - Tier 2: Response lifecycle, progress updates, client effects
  • building-chat-widgets - Tier 3: Interactive widgets, entity tagging, composer tools
  • fetching-library-docs - ChatKit docs: --library-id /openai/chatkit --topic useChatKit

References

  • references/chatkit-integration-patterns.md - Complete patterns with evidence
  • references/nextjs-httponly-proxy.md - Next.js cookie proxy patterns

Related skills

FAQ

How is auth and context added to requests?

A custom fetch interceptor in useChatKit injects auth headers and page/user context into the request body and headers.

When should I not use this skill?

Not when building simple chatbots without persistence or custom agent integration.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.