diff --git a/CLAUDE.md b/CLAUDE.md index 3d89f67f..d150ba63 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,197 +1,70 @@ -# MCP AppsKit Development Guidelines +# MCP AppsKit -A TypeScript framework for building interactive MCP applications that work with both **MCP Apps** and **ChatGPT (OpenAI Apps SDK)** from a single codebase. +pnpm monorepo + Nx. Dual-protocol framework: MCP Apps + ChatGPT from single codebase. -## Project Structure - -This is a **pnpm monorepo** with **Nx** for orchestration: - -```text -packages/ -├── core/ # @mcp-apps-kit/core - Server framework (createApp, adapters, middleware, plugins, events) -├── ui/ # @mcp-apps-kit/ui - Client SDK (vanilla JS, protocol detection, adapters) -├── ui-react/ # @mcp-apps-kit/ui-react - React bindings (context, hooks) -└── create-app/ # @mcp-apps-kit/create-app - CLI scaffolder - -examples/ -├── minimal/ # Simple hello-world example -└── restaurant-finder/ - -# Full-featured kanban example: https://github.com/AndurilCode/kanban-mcp-example -``` - -Each package has its own README with detailed API documentation. - -## Commands +## Before Finishing Any Task ```bash -# Root commands -pnpm install && pnpm build && pnpm test && pnpm lint && pnpm typecheck - -# Package-specific -pnpm -C packages/core test # Test single package -pnpm -C examples/minimal dev # Run minimal example with hot reload - -# Release -pnpm release:version:patch|minor|major -``` - -## Key Dependencies - -| Package | Purpose | -| ----------------------------------- | ----------------------------- | -| `@modelcontextprotocol/sdk` ^1.25.1 | MCP protocol | -| `express` ^5.1.0 | HTTP server | -| `zod` ^4.0.0 | Schema validation | -| `typescript` ^5.9.3 | Strict mode enabled | -| `vitest` ^4.0.16 | Testing (80% coverage target) | - -## Code Conventions - -- **Strict TypeScript**: No `any` types, use `unknown` and narrow -- **No unused variables**: Remove or prefix with `_` -- **Public API**: Exports only in `index.ts` -- **Tests**: Mirror source in `tests/` (unit/, integration/, contract/) - -## Tool Definition Pattern - -Always use `defineTool` and `defineUI` for type inference: - -```typescript -import { defineTool, defineUI } from "@mcp-apps-kit/core"; -import { z } from "zod"; - -// Define UI for the tool (colocated pattern) -const myWidget = defineUI({ - name: "My Widget", - description: "Widget description", - html: "./ui/dist/index.html", - prefersBorder: true, -}); - -const tool = defineTool({ - title: "My Tool", - description: "Tool description", - input: z.object({ name: z.string() }), - output: z.object({ message: z.string() }), - ui: myWidget, // Optional: links to UI resource - visibility: "both", // "model", "app", or "both" - handler: async (input) => ({ message: `Hello, ${input.name}!` }), -}); -``` - -## Middleware Pattern - -Koa-style with `async/await`: - -```typescript -app.use(async (context, next) => { - console.log("Before:", context.toolName); - await next(); // Must call next() - console.log("After:", context.toolName); -}); +pnpm build && pnpm test && pnpm lint && pnpm typecheck ``` -## Error Handling - -```typescript -import { AppError, ErrorCode } from "@mcp-apps-kit/core"; -throw new AppError({ code: ErrorCode.VALIDATION_ERROR, message: "Invalid input" }); -``` - -## Plugin System - -```typescript -const plugin: Plugin = { - name: "my-plugin", - onInit: (app) => {}, - onStart: (app) => {}, - onShutdown: (app) => {}, - beforeToolCall: (context) => {}, - afterToolCall: (context, result) => {}, - onToolError: (context, error) => {}, -}; -``` +All four must pass. No exceptions. Broken builds block the whole team. -## Event System +## Quick Commands -```typescript -app.events.on("app:init", () => {}); -app.events.on("tool:call", ({ toolName, input }) => {}); -app.events.once("app:start", ({ port }) => {}); +```bash +pnpm -C packages/core test # Test single package (faster iteration) +pnpm -C examples/minimal dev # Run example with hot reload +pnpm release:version:patch # Also: minor, major ``` -## UI React Hooks - -| Hook | Purpose | -| ---------------- | ----------------------------------- | -| `useAppsClient` | Client instance for tool calls | -| `useToolResult` | Current tool result data | -| `useToolInput` | Tool input parameters | -| `useHostContext` | Host info (theme, viewport, locale) | -| `useWidgetState` | Persisted state across reloads | -| `useDisplayMode` | Fullscreen/panel mode control | -| `useFileUpload` | File upload functionality | -| `useModal` | Modal dialog management | - -Other hooks: `useSafeAreaInsets`, `useHostStyleVariables`, `useDocumentTheme`, `useOnToolCancelled`, `useOnTeardown`, `useIntrinsicHeight`, `useView` +## What Makes This Codebase Different -## Deployment Options +- **Zod 4** (not 3) - Breaking changes from v3, check migration if something looks wrong +- **Express 5** (not 4) - Async error handling works differently +- **Strict TS** - No `any`. Use `unknown` + narrowing. We've had production bugs from implicit any +- **80% test coverage** - CI fails below this. Tests mirror source in `tests/` -```typescript -// Express server (default) -await app.start({ port: 3000 }); +## Patterns to Follow -// Custom Express middleware -expressApp.use("/mcp", app.handler()); +Use `defineTool` and `defineUI` (or `defineReactUI` for React components) - they provide type inference. See `examples/minimal/src/index.ts` for usage. -// Stdio for CLI tools -await app.getServer().connect(new StdioTransport()); +Middleware is Koa-style: always `await next()` or the chain breaks. -// Serverless -export default { - async fetch(request) { - return app.handleRequest(request); - }, -}; -``` +Exports only through `index.ts` - keeps the public API clean and refactoring safe. -## Protocol Abstraction +## Common Mistakes -The framework auto-generates platform-specific metadata: +- Forgetting `export type` for type-only exports (causes runtime imports of types) +- Creating circular deps between packages (Nx will catch this but it's annoying) +- Committing without running the full check suite (CI will fail, wastes time) -```typescript -// Your definition (using colocated UI) -const myWidget = defineUI({ name: "Widget", html: "./ui/dist/index.html" }); -tools: { my_tool: defineTool({ ui: myWidget, visibility: "both", ... }) } +## Project Map -// MCP Apps: _meta.ui.resourceUri + visibility -// ChatGPT: _meta["openai/outputTemplate"] + ["openai/visibility"] +```text +packages/core → Server framework (createApp, adapters, middleware) +packages/ui → Client SDK (vanilla JS, protocol detection) +packages/ui-react → React hooks (useAppsClient, useToolResult, useHostContext...) +packages/ui-react-builder → React UI builder (defineReactUI, vite plugin) +packages/testing → Test utilities (mocks, matchers for vitest/jest) +packages/create-app → CLI scaffolder +examples/ → Working examples to test against ``` -## HTTP Endpoints - -| Endpoint | Purpose | -| --------- | --------------------------------------------- | -| `/health` | Health check | -| `/mcp` | MCP protocol (configurable via `serverRoute`) | +Each package README has the detailed API. Don't duplicate here. -## Compatibility +## External Docs -- **Node.js**: >= 20 -- **React**: 18.x or 19.x (peer dependency) -- **Zod**: ^4.0.0 required +- [MCP Spec](https://modelcontextprotocol.io/specification/2025-11-25) - Protocol details, message formats +- [OpenAI Apps SDK](https://developers.openai.com/apps-sdk) - ChatGPT integration specifics -## Important Notes +--- -- Run full checks before PR: `pnpm build && pnpm test && pnpm lint && pnpm typecheck` -- Keep packages independent - avoid circular dependencies -- Use `export type` for type-only exports -- Maintain 80% minimum test coverage -- Never commit secrets - use environment variables +## Learnings -## External Documentation + -- [MCP Specification](https://modelcontextprotocol.io/specification/2025-11-25) -- [MCP Apps Extension](https://blog.modelcontextprotocol.io/posts/2025-11-21-mcp-apps/) -- [OpenAI Apps SDK](https://developers.openai.com/apps-sdk) +