> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waniwani.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Scaffold an MCP app with `npx @waniwani/kit init`, or write the four files by hand: an app config, a tool, and a two-file widget.

```bash theme={null}
npx @waniwani/kit init oney
cd oney && npm run dev
```

`init` writes a folder that already answers: an app config, one tool, and the widget that displays what the tool returned. It installs, and the dev server is one command away. Running it inside an existing repo merges into that repo's `package.json` and `.gitignore` instead of replacing them.

## What init asks

In a terminal it asks three questions, arrow keys and Enter:

```text theme={null}
┌  A new MCP app
│
◇  App name
│  oney
│
◆  What should it come with?
│  ● A tool and a widget (the hand-off between them, wired up)
│  ○ Just a tool
│  ↑/↓ to navigate • Enter: confirm
└
```

The third question is where the app deploys (Vercel, Docker, Alpic, or "I don't know yet"). That answer decides the one config file the repo carries; see [Deploy](/kit/deploy).

Every question has a flag that answers it ahead of time, and a question whose answer is already in hand is skipped:

| Flag                                      | Answers                                                                                   |
| ----------------------------------------- | ----------------------------------------------------------------------------------------- |
| `--name <name>`                           | App name                                                                                  |
| `--minimal`                               | Just a tool, no widget                                                                    |
| `--host <vercel\|alpic\|container\|none>` | Where it deploys                                                                          |
| `--yes`                                   | Every default, no questions. Also what happens with no terminal to ask in: a pipe, or CI. |
| `--no-install`                            | Skip the install step                                                                     |
| `--force`                                 | Overwrite files that already exist                                                        |

Where the app lands follows the argument. `init oney` creates `oney/`, `init .` uses the current folder, and a bare `init` asks for a name and reads the answer as both: a name of its own creates `./<name>/`, while the offered default, your current folder's name, scaffolds in place.

## The same app, by hand

The rest of this page is what those files hold, written out. [examples/oney](https://github.com/WaniWani-AI/kit/tree/main/examples/oney) is the same app finished, if you would rather read it than type it.

<Steps>
  <Step title="Install">
    ```bash theme={null}
    mkdir oney && cd oney
    npm init -y
    npm i @waniwani/kit @waniwani/sdk react react-dom zod
    ```

    Set `"type": "module"` and the scripts:

    ```json package.json theme={null}
    {
      "type": "module",
      "scripts": {
        "check": "waniwani check",
        "dev": "waniwani dev",
        "build": "waniwani build",
        "start": "waniwani start"
      }
    }
    ```

    The kit asks for `@waniwani/sdk` 0.20 or later, React 19 and Zod 4 as peers.
  </Step>

  <Step title="Name the app and tell the model how to behave">
    ```ts waniwani.config.ts theme={null}
    import { defineApp } from "@waniwani/kit";

    export default defineApp({
      name: "oney",
      title: "Oney: split your payment",
      overview: `You help shoppers split a purchase into instalments with Oney.

    RULES:
    - Never quote a monthly amount yourself. Call check-eligibility and let it do the arithmetic.
    - Never list the plans in text. Show the select-plan widget and let it render them.`,
    });
    ```

    `overview` reaches the host LLM once, in the `initialize` handshake. It says what the app is and which tool to reach for when. How a single tool behaves goes in that tool's own `description`, which travels with every `tools/list`. The full option list is on [App config](/kit/app-config).
  </Step>

  <Step title="Write a tool">
    The filename becomes the tool name.

    ```ts tools/check-eligibility.ts theme={null}
    import { defineTool } from "@waniwani/kit";
    import { z } from "zod";
    import { buildPlans } from "../lib/plans.js";

    export default defineTool({
      title: "Check instalment eligibility",
      description:
        "Work out which instalment plans a basket qualifies for. Call this before showing any plans, and before quoting any figure.",
      input: {
        amount: z.number().positive().describe("Basket total in euros, e.g. 249.90"),
        country: z.enum(["FR", "ES", "PT"]).default("FR"),
      },
      output: {
        eligible: z.boolean(),
        plans: z.array(z.object({ id: z.string(), monthly: z.number(), fee: z.number() })),
      },
      hints: { readOnly: true },
      run: ({ amount, country }) =>
        amount < 50
          ? { eligible: false, plans: [] }
          : { eligible: true, plans: buildPlans(amount, country) },
    });
    ```

    `input` and `output` are Zod shapes, written as plain objects rather than `z.object({ … })`. `hints` becomes MCP annotations, with the runtime filling in the `title` that Claude's Connectors Directory requires. More on [Tools](/kit/tools).
  </Step>

  <Step title="Write a widget">
    The folder name becomes the tool name, and the widget takes two files.

    ```ts widgets/select-plan/widget.ts theme={null}
    import { defineWidget } from "@waniwani/kit";
    import { z } from "zod";

    const plan = z.object({
      id: z.string(),
      label: z.string().describe("Short label, e.g. '3×'."),
      monthly: z.number(),
      fee: z.number(),
    });

    export default defineWidget({
      title: "Choose an instalment plan",
      description:
        "Show the instalment plan picker. Call this once check-eligibility has returned plans, passing them straight through. The widget renders every figure itself: do NOT list the plans in text.",
      data: {
        amount: z.number(),
        plans: z.array(plan).describe("Plans returned by check-eligibility, unmodified."),
      },
      llmText: (data) =>
        `The picker is on screen with ${data.plans.length} plans. Wait for the user to pick one.`,
    });
    ```

    ```tsx widgets/select-plan/ui.tsx theme={null}
    import { useSendFollowUpMessage, useWidget } from "@waniwani/kit/web";
    import widget from "./widget.js";

    export default function SelectPlan() {
      const { data } = useWidget(widget);
      const sendFollowUp = useSendFollowUpMessage();
      if (!data) return <div className="font-sans text-ink-muted">Loading your plans…</div>;

      return (
        <div className="font-sans text-ink">
          {data.plans.map((plan) => (
            <button
              key={plan.id}
              type="button"
              onClick={() => sendFollowUp(`I'll take the ${plan.label} plan.`)}
            >
              {plan.label}: €{plan.monthly}/month
            </button>
          ))}
        </div>
      );
    }
    ```

    `widget.ts` is imported by the server and by the browser bundle, so it stays free of React and CSS. Why the split exists is on [Widgets](/kit/widgets).
  </Step>

  <Step title="Run it">
    ```bash theme={null}
    npm run dev
    ```

    `dev` watches the folder, mirrors changes into `.waniwani/`, and leaves nodemon and Vite HMR to do the rest. An edit to `tools/check-eligibility.ts` reaches the MCP endpoint in about a second. Point a client at `/mcp`, or open the dev server's root in a browser to call the tools without a chat client.
  </Step>
</Steps>

## Next

<CardGroup cols={2}>
  <Card title="Folder convention" icon="folder-tree" href="/kit/folder-convention">
    Every folder the kit reads, and what each one becomes.
  </Card>

  <Card title="Flows" icon="diagram-project" href="/kit/flows">
    Drop a compiled SDK flow into `flows/` and it registers as a tool.
  </Card>

  <Card title="Commands" icon="terminal" href="/kit/commands">
    `check`, `dev`, `build`, `start` and the four stages they share.
  </Card>

  <Card title="Deploy" icon="cloud-arrow-up" href="/kit/deploy">
    What `git push` needs on Vercel, Docker and Alpic.
  </Card>
</CardGroup>
