This document provides context and guidelines for Claude (and other AI assistants) working on the ToolHive Cloud UI codebase.
ToolHive Cloud UI is an open-source Next.js 16 application for visualizing and managing MCP (Model Context Protocol) servers running in user infrastructure. It provides a catalog interface with detailed server information, tool listings, and URL copy functionality.
Repository: https://github.com/stacklok/toolhive-cloud-ui
Backend API: https://github.com/stacklok/toolhive-registry-server
License: Apache 2.0 (Open Source)
- Framework: Next.js 16.0.3 (App Router)
- React: 19.2.0 with React Compiler (babel-plugin-react-compiler)
- TypeScript: Strict mode enabled
- Styling: Tailwind CSS 4
- UI Components: shadcn/ui
- Authentication: Better Auth (OIDC, stateless JWT)
- API Client: hey-api (@hey-api/openapi-ts) with React Query
- Testing: Vitest + Testing Library
- Linting: Biome (replaces ESLint + Prettier)
- Package Manager: pnpm
Before suggesting any code or architecture changes:
- Consult Next.js Documentation - Verify your approach is correct
- This is App Router, not Pages Router - Different conventions and patterns
- Validate Server vs Client boundaries - Understand what runs where
- Check built-in features - Next.js provides routing, caching, data fetching, etc.
- Don't reinvent the wheel - Use Next.js conventions and built-ins
Common mistakes AI assistants make with Next.js:
- ❌ Mixing Pages Router and App Router patterns
- ❌ Creating custom routing when file-system routing exists
- ❌ Not understanding Server Components (no hooks, no event handlers)
- ❌ Adding
'use client'everywhere or forgetting it when needed - ❌ Ignoring Next.js caching and revalidation mechanisms
- ❌ Using old data fetching patterns (getServerSideProps, getStaticProps)
When in doubt: Check the Next.js App Router documentation first, then propose a solution.
- Default to Server Components - They're faster, reduce bundle size, and can directly access backend resources
- Use Client Components only when necessary:
- Event handlers (onClick, onChange, etc.)
- Browser APIs (window, localStorage, clipboard)
- React hooks (useState, useEffect, useContext)
- hey-api React Query hooks
Why: Server Components reduce JavaScript sent to the client, improve initial page load, and allow direct data fetching.
- JWT-based authentication with no server-side session storage
- OIDC provider agnostic (works with any OIDC-compliant provider)
- Better Auth handles token validation and renewal
Why: Stateless auth scales horizontally without session storage, crucial for cloud deployments.
- Never write manual fetch logic - Always use hey-api generated functions
- Never edit generated files - They are regenerated from OpenAPI spec and changes will be lost
- Use server actions for all API calls - Client components should not call the API directly
- API client regenerated from OpenAPI spec via custom script
- Type-safe API calls with proper error handling
Why: Generated client ensures type safety and stays in sync with backend API. Server-only API access keeps the backend URL secure and reduces client bundle size.
- Always use async/await - Never use
.then()chains - Better error handling with try/catch
- More readable, sequential code flow
Why: Async/await is more readable, easier to debug, and handles errors better than promise chains.
- 🚫 NEVER use
anytype - STRICTLY FORBIDDEN in this codebase - Use
unknownwith type guards for truly unknown types - Use proper interfaces, types, or generics instead
- Prefer
as constoverenum - Use proper type inference and utility types
Why: The any type defeats the entire purpose of TypeScript. It eliminates type safety, hides bugs, and makes code unmaintainable. Strict typing catches bugs at compile time, improves IDE support, and serves as living documentation.
pnpm install # Install dependencies
pnpm generate-client # Generate API client from backendpnpm dev # Start Next.js + OIDC mock
pnpm dev:next # Start only Next.js
pnpm oidc # Start only OIDC mock
pnpm lint # Run Biome linter
pnpm format # Auto-format code
pnpm test # Run tests
pnpm type-check # TypeScript validationpnpm generate-client # Fetch swagger.json and regenerate
pnpm generate-client:nofetch # Regenerate without fetching- Read project documentation first - Check AGENTS.md and CLAUDE.md for all guidelines
- Use Server Components by default - Mark Client Components explicitly
- Use hey-api hooks exclusively - No manual fetch in components
- Use async/await - Never
.then()chains - Use shadcn/ui components - Don't create custom UI primitives
- Handle errors properly - Always show user-friendly error messages
- Add JSDoc for complex functions - Explain why, not what
- Keep code DRY - Extract repeated logic
- Follow existing patterns - Check similar files before creating new patterns
- Run linter and type-check - Before considering task complete
- 🚫 Don't EVER use
anytype - STRICTLY FORBIDDEN. Useunknown+ type guards or proper types - 🚫 Don't EVER edit generated files -
src/generated/*is auto-generated and will be overwritten- Generated files are overwritten on every
pnpm generate-clientrun - If you need to configure or extend the client, do it in your own files
- Generated files are overwritten on every
- Don't use
'use client'everywhere - Only when necessary - Don't create custom fetch logic - Use hey-api hooks
- Don't use
.then()- Use async/await - Don't create custom UI components - Use shadcn/ui
- Don't ignore TypeScript errors - Fix them, don't suppress
- Don't mass refactor - Make targeted, incremental improvements
- Don't create API routes for simple mutations - Use Server Actions
- Don't skip error handling - Always handle errors gracefully
- Base URL: Configured via
API_BASE_URL(server-side only) - Format: Official MCP Registry API (upstream compatible)
- Endpoints:
GET /api/v0/servers- List all MCP serversGET /api/v0/servers/{name}- Get server detailsGET /api/v0/deployed- List deployed instancesGET /api/v0/deployed/{name}- Get deployed instance details
- Script (
scripts/generate-swagger.ts) fetchesswagger.jsonfrom backend - hey-api (
@hey-api/openapi-ts) generates:- TypeScript types (
types.gen.ts) - API client functions
- React Query hooks
- Utility functions
- TypeScript types (
- Import generated hooks in components:
@/generated/client/@tanstack/react-query.gen
- User accesses protected route
- Redirected to
/signin - Better Auth initiates OIDC flow with configured provider
- Provider redirects back with authorization code
- Better Auth exchanges code for tokens
- JWT stored in secure HTTP-only cookie
- User redirected to original destination
- Mock OIDC provider runs on separate port (started via
pnpm dev) - No real authentication required
- MSW mocks API responses
- Simulates production auth flow for testing
- User interactions (clicks, form submissions)
- Authentication flows (login, logout, protected routes)
- Error scenarios (API failures, network errors)
- Loading states and skeleton screens
- Accessibility (keyboard navigation, screen readers)
- Prefer
toBeVisible()overtoBeInTheDocument()-toBeVisible()checks that an element is actually visible to the user (not hidden via CSS,aria-hidden, etc.), whiletoBeInTheDocument()only checks DOM presence. UsetoBeVisible()for positive assertions and.not.toBeInTheDocument()for absence checks.
-
MSW Auto-Mocker
- Auto-generates handlers from
swagger.jsonand creates fixtures undersrc/mocks/fixtureson first run. - Strict validation with Ajv + ajv-formats; fixtures are type-checked against
@api/types.genby default. - Hand-written, non-schema mocks live in
src/mocks/customHandlersand take precedence over schema-based mocks. - Dev:
pnpm mock:serverstarts a standalone HTTP mock onhttp://localhost:9090(configurable viaAPI_BASE_URL). In dev, Next rewrites proxy/registry/*there; always use relative URLs like/registry/v0.1/servers. - Regenerate by deleting specific fixture files.
- Create new fixtures by calling the desired endpoint in a Vitest test (or via the app in dev). The first call generates a TypeScript fixture file; customize the payload by editing that file instead of writing a new custom handler when you only need different sample data.
- Prefer global test setup for common mocks: add shared mocks to
vitest.setup.ts(e.g.,next/headers,next/navigation,next/image,sonner, auth client). Before adding a mock in a specific test file, check if it belongs in the global setup.
- Auto-generates handlers from
-
Vitest - Test runner (faster than Jest)
-
Testing Library - Component testing
-
jsdom - DOM simulation
- End-to-end tests live under
tests/e2eand run against a production build. - Commands:
pnpm test:e2e– builds the app and runs E2E testspnpm test:e2e:ui– builds and opens Playwright UI mode for interactive debuggingpnpm test:e2e:debug– builds and runs with Playwright Inspector
- CI runs E2E tests via
.github/workflows/e2e.yml(builds first, then tests) - Install browsers locally once:
pnpm exec playwright install
Tests use custom fixtures for authentication. The authenticatedPage fixture handles login automatically.
import { render, screen, waitFor } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
import { describe, it, expect } from "vitest";
describe("ServerList", () => {
it("displays servers and handles copy", async () => {
render(<ServerList />);
await waitFor(() => {
expect(screen.getByText("Server 1")).toBeVisible();
});
const copyButton = screen.getByRole("button", { name: /copy url/i });
await userEvent.click(copyButton);
expect(screen.getByText(/copied/i)).toBeVisible();
});
});- Multi-stage Dockerfile for optimized image size
- Built automatically on Git tag push
- Published to GitHub Container Registry
- Helm chart in
helm/directory - Optimized for Kind (local Kubernetes)
- Includes HPA, health checks, security contexts
- Deploy:
make kind-setup
git tag v0.x.x
git push origin v0.x.x
# GitHub Actions builds and publishes Docker image- Don't edit
src/generated/* - Fix: Regenerate with
pnpm generate-client - If persists, check backend API schema
- Development:
- Ensure OIDC mock is running (
pnpm oidc) or start the full stack withpnpm dev - Dev provider issues refresh tokens unconditionally and uses a short AccessToken TTL (15s) to exercise the refresh flow
- If you see origin errors (403), ensure
BETTER_AUTH_URLmatches the port you use (defaulthttp://localhost:3000) or include it inTRUSTED_ORIGINS
- Ensure OIDC mock is running (
- Production: Check environment variables:
OIDC_ISSUER_URL- OIDC provider URLOIDC_CLIENT_ID- OAuth2 client IDOIDC_CLIENT_SECRET- OAuth2 client secretOIDC_PROVIDER_ID- Provider identifier (e.g., "okta", "oidc") - Required, server-side only.BETTER_AUTH_URL- Application base URLBETTER_AUTH_SECRET- Secret for token encryption
- Check
API_BASE_URLenvironment variable - Verify backend API is running
- Check browser console for CORS errors
- Run
pnpm lintto see all errors - Run
pnpm formatto auto-fix formatting - Check
biome.jsonfor configuration
- Ask questions - If requirements are unclear, ask before implementing
- Check existing code - Look for similar patterns in the codebase
- Read the documentation - All guidelines are in AGENTS.md, CLAUDE.md, and copilot-instructions.md
- Incremental changes - Small, focused changes are better than large refactors
- Test your changes - Run linter, type-check, and tests
- Explain reasoning - Use JSDoc to explain why, not what
- Check if similar feature exists
- Identify if Server or Client Component needed
- Use hey-api hooks for API calls
- Use shadcn/ui for UI components
- Implement proper error handling
- Add loading states
- Write tests
- Update documentation if needed
- Understand the root cause (don't just patch symptoms)
- Check if it's a TypeScript or linting error first
- Look at the full context (don't just fix one file)
- Add tests to prevent regression
- Verify fix doesn't break other functionality
- Project Documentation: AGENTS.md, CLAUDE.md, copilot-instructions.md (READ FIRST)
- Next.js Docs: https://nextjs.org/docs
- React Docs: https://react.dev
- Better Auth: https://www.better-auth.com
- hey-api: https://heyapi.vercel.app
- shadcn/ui: https://ui.shadcn.com
- Tailwind CSS 4: https://tailwindcss.com
- Biome: https://biomejs.dev
For questions or issues:
- Backend API: https://github.com/stacklok/toolhive-registry-server
- Frontend UI: https://github.com/stacklok/toolhive-cloud-ui
- MCP Registry: https://github.com/modelcontextprotocol/registry
Remember: This is an open-source project. Write clean, maintainable code that others can understand and contribute to. When in doubt, favor simplicity over cleverness.