Build interactive AI apps for MCP Apps and ChatGPT. The Modern TypeScript Way.
Quick Start • Documentation • Examples • Issues
| Feature | Description |
|---|---|
| File-Based Development | Tools, widgets, and workflows auto-discovered from your filesystem |
| Convention over Configuration | tools/get-weather.ts → getWeather tool, automatically |
| Colocated Widgets | React UIs in ui/widgets/ auto-bind to matching tools |
| Type-Safe End-to-End | Full TypeScript inference from inputs to UI props |
| Dual Platform | Single codebase deploys to MCP Apps & ChatGPT |
| Hot Module Reload | Vite-powered HMR with React Fast Refresh for widgets |
| Zero Boilerplate | No manifest files to maintain — codegen handles it |
npx @mcp-apps-kit/create-app@latest my-app
cd my-app && npm run devOr manually set up a file-based project:
npm install @mcp-apps-kit/core @mcp-apps-kit/codegen zodMCP AppsKit uses file-based conventions to eliminate boilerplate. Drop files in the right directories and everything wires up automatically.
my-app/
├── mcp.config.ts # App configuration (single source of truth)
├── tools/
│ ├── get-weather.ts # → getWeather tool
│ └── search-location.ts # → searchLocation tool
├── workflows/
│ └── daily-briefing.ts # → dailyBriefing workflow
├── ui/widgets/
│ ├── get-weather.tsx # → Auto-bound to getWeather tool
│ └── daily-briefing.tsx # → Auto-bound to dailyBriefing
├── middleware/
│ └── logging.ts # Runs on every tool call
├── handlers/
│ └── app-lifecycle.ts # Server events (start, shutdown)
└── __generated__/ # Auto-generated (gitignored)
└── app-manifest.ts
// mcp.config.ts
import { defineConfig } from "@mcp-apps-kit/codegen";
export default defineConfig({
name: "weather-app",
version: "1.0.0",
directories: {
tools: "tools",
workflows: "workflows",
uiWidgets: "ui/widgets",
middleware: "middleware",
handlers: "handlers",
},
config: {
protocol: "mcp", // or "openai" for ChatGPT
cors: { origin: true },
},
});// tools/get-weather.ts
import { tool } from "@mcp-apps-kit/core";
import { z } from "zod";
export default tool
.describe("Get current weather for a location")
.input({
location: z.string().describe("City name"),
})
.output(
z.object({
temperature: z.number(),
conditions: z.string(),
humidity: z.number(),
})
)
.handle(async ({ location }) => {
const data = await fetchWeather(location);
return {
temperature: data.temp,
conditions: data.weather,
humidity: data.humidity,
};
});Widgets in ui/widgets/ automatically bind to tools with matching filenames:
// ui/widgets/get-weather.tsx
import { useToolResult, useHostContext } from "@mcp-apps-kit/ui-react";
import type { WidgetMetadata } from "@mcp-apps-kit/core";
export default function WeatherWidget() {
const result = useToolResult<{ temperature: number; conditions: string }>();
const { theme } = useHostContext();
if (!result) return <div>Loading...</div>;
return (
<div className={theme === "dark" ? "dark" : ""}>
<h2>{result.temperature}°C</h2>
<p>{result.conditions}</p>
</div>
);
}
// Widget metadata (optional)
export const ui: WidgetMetadata = {
name: "Weather Display",
prefersBorder: true,
};npm run devThe codegen watches for changes and regenerates the manifest. Your tools and widgets are instantly available. Widget changes are picked up by Vite's HMR with React Fast Refresh — no full reload needed.
Files are automatically converted to camelCase identifiers:
| File | Tool Name |
|---|---|
get-current-weather.ts |
getCurrentWeather |
search_locations.ts |
searchLocations |
DailyBriefing.ts |
dailyBriefing |
_shared.ts |
(ignored — underscore prefix) |
| Package | Description |
|---|---|
@mcp-apps-kit/core |
Server framework with tool/workflow builders |
@mcp-apps-kit/codegen |
File-based discovery and manifest generation |
@mcp-apps-kit/ui |
Client SDK (vanilla JS) |
@mcp-apps-kit/ui-react |
React bindings and hooks |
@mcp-apps-kit/ui-react-builder |
Vite plugin for widget bundling and HMR |
@mcp-apps-kit/create-app |
CLI scaffolding tool |
@mcp-apps-kit/testing |
Test utilities and mocks |
Add cross-cutting concerns like logging, auth, or metrics:
// middleware/logging.ts
import { defineMiddleware } from "@mcp-apps-kit/codegen";
export default defineMiddleware({
before: async (ctx) => {
ctx.state.set("startTime", Date.now());
console.log(`Tool called: ${ctx.toolName}`);
},
after: async (ctx) => {
const duration = Date.now() - ctx.state.get("startTime");
console.log(`Completed in ${duration}ms`);
},
});React to server lifecycle events:
// handlers/app-lifecycle.ts
import { defineHandler, Events } from "@mcp-apps-kit/codegen";
export default defineHandler({
event: Events.APP_START,
handler: async ({ port }) => {
console.log(`Server started on port ${port}`);
},
});Support multiple API versions from a single codebase:
// mcp.config.ts
export default defineConfig({
name: "my-api",
versions: {
v1: {
version: "1.0.0",
// Uses versions/v1/tools, versions/v1/workflows, etc.
},
v2: {
version: "2.0.0",
config: { debug: { level: "debug" } },
},
},
});Compose multi-step operations:
// workflows/order-process.ts
import { workflow, toolStep } from "@mcp-apps-kit/core";
import { z } from "zod";
export default workflow("process_order")
.input({ orderId: z.string() })
.output({ success: z.boolean() })
.step("validate", toolStep("validate_order"))
.step("payment", toolStep("process_payment"), {
retry: { maxAttempts: 3, backoff: "exponential" },
})
.parallel("notify", [toolStep("send_email"), toolStep("send_sms")])
.build();| Feature | MCP Apps | ChatGPT Apps |
|---|---|---|
| Tool Calling | Yes | Yes |
| Structured Output | Yes | Yes |
| React Widgets | Yes | Yes |
| OAuth 2.1 | Yes | Yes |
| Theme Support | Yes | Yes |
| Persisted State | No | Yes |
| Tool Cancellation | Yes | No |
Full-featured example with tools, widgets, middleware, and workflows:
git clone https://github.com/AndurilCode/mcp-apps-kit.git
cd mcp-apps-kit/examples/weather-app
pnpm install && pnpm devProduction-ready example with tool calling, React widgets, plugins, middleware, and events:
git clone https://github.com/AndurilCode/kanban-mcp-example.git
cd kanban-mcp-example
npm install && npm run dev// Handled automatically by codegen
npm run start// mcp.config.ts
export default defineConfig({
// ...
config: {
transport: "stdio",
},
});import { createFileBasedApp } from "@mcp-apps-kit/codegen";
const app = await createFileBasedApp();
export default {
async fetch(request: Request) {
return app.handleRequest(request);
},
};- Node.js: >= 18 (runtime), >= 20 (development/CLI)
- React: 18.x or 19.x
- Zod: ^4.0.0
- Vite: 5.x, 6.x, or 7.x
Full Documentation — TypeDoc-generated API reference
Package documentation:
See CONTRIBUTING.md for development setup. Issues and pull requests welcome.