Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 43 additions & 170 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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.
Comment on lines +28 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing critical error handling pattern.

The Patterns section references defineTool, defineUI, middleware, and exports, but omits guidance on error handling. Per your established learnings, developers should use AppError and ErrorCode from mcp-apps-kit/core for consistent error handling across packages. This is especially important since the codebase maintains strict patterns for reliability.

Consider adding:

Use `AppError` and `ErrorCode` from `mcp-apps-kit/core` - ensures consistent error handling and makes debugging easier across packages.
🤖 Prompt for AI Agents
In @CLAUDE.md around lines 28 - 34, Add a new pattern to the Patterns to Follow
section recommending the use of AppError and ErrorCode from mcp-apps-kit/core
for consistent error handling; update the CLAUDE.md section that lists patterns
(where defineTool, defineUI, middleware, and exports are described) to include a
short sentence stating "Use AppError and ErrorCode from mcp-apps-kit/core" and a
brief justification about consistent error handling and easier debugging so
contributors follow the established error-handling conventions.


## 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
<!-- Add specific lessons learned during development. Format: what happened → what to do instead -->

- [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)
<!-- Example:
- Forgot to rebuild ui package before testing ui-react → Always `pnpm build` from root, not package
- Type error was hidden because of `any` in test mock → Use proper typed mocks from vitest
-->