# React (/docs/quickstart/react)

This page builds a small React app, served by Vite, with an "AI mode" button and a chat that searches the site's catalog through its tools. It follows the bookshop demo in `apps/demo` of the AI Connect repository.

You need React 19 or later, and Chrome with AI Wallet installed and one account connected to try it.

## Install the packages [#install-the-packages]

```bash
npm install @aiconnect/core @aiconnect/react
```

<Callout type="warn">
The packages are not published on npm yet. Until they are, build a checkout of the AI Connect repository with `npm run build` and point your `package.json` at its folders, as Taamtaam does:

```json title="package.json"
{
  "dependencies": {
    "@aiconnect/core": "file:../aiconnect/packages/core",
    "@aiconnect/react": "file:../aiconnect/packages/react"
  }
}
```
</Callout>

`@aiconnect/react` depends on `@aiconnect/design`, which holds the CSS tokens, so you do not install it yourself.

## Wrap the app in the provider [#wrap-the-app-in-the-provider]

Create one `AiConnect` client for the page and give it to `AiConnectProvider`. Import the connect window's stylesheet once.

```tsx title="src/main.tsx"
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { AiConnect } from '@aiconnect/core';
import { AiConnectProvider } from '@aiconnect/react';
import '@aiconnect/react/styles.css';
import { App } from './App.tsx';

// The visitor's AI, through the AI Wallet extension. The client looks for the wallet as soon as it is built.
const client = new AiConnect();

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <AiConnectProvider client={client}>
      <App />
    </AiConnectProvider>
  </StrictMode>,
);
```

A visitor who approved your site on an earlier visit is connected a moment after the page loads, with no click.

## Add the entry point [#add-the-entry-point]

`useAiConnect` gives you the connection state and `openPicker`, which shows the connect window. The window's button calls `client.connect()`, and the window closes on its own once the wallet connects the site.

```tsx title="src/ConnectButton.tsx"
import { useAiConnect } from '@aiconnect/react';

export function ConnectButton() {
  const { state, client, openPicker } = useAiConnect();
  if (state.status === 'connected') {
    return (
      <p>
        Connected with {state.providerName}{state.model ? ` (${state.model})` : ''}{' '}
        <button type="button" onClick={() => client.disconnect()}>Disconnect</button>
      </p>
    );
  }
  return <button type="button" onClick={openPicker}>AI mode</button>;
}
```

`client.disconnect()` removes your site from the visitor's approved sites in the wallet. The next `connect` asks the visitor again.

## Chat with your tools [#chat-with-your-tools]

`useAiChat` runs the conversation on the visitor's account. Give it a system prompt and a tool source. `McpToolSource` reads the tools from your MCP server; the [MCP guide](/docs/guides/mcp-tools) shows how to serve one.

```tsx title="src/Chat.tsx"
import { useState } from 'react';
import type { FormEvent } from 'react';
import { McpToolSource } from '@aiconnect/core';
import { useAiChat } from '@aiconnect/react';

const SYSTEM = 'You recommend books from this site\'s catalog. Always search the catalog before recommending.';

// Built once: the source keeps its MCP connection and the tool list it read.
const tools = new McpToolSource('/api/mcp', 'Bookshop');

export function Chat() {
  const { turns, send, busy, error, connected } = useAiChat({ system: SYSTEM, tools });
  const [question, setQuestion] = useState('');

  const submit = (event: FormEvent) => {
    event.preventDefault();
    send(question);
    setQuestion('');
  };

  if (!connected) {
    return <p>Connect your AI to ask for a book that fits your mood.</p>;
  }
  return (
    <section>
      {turns.map((turn, index) => (
        <article key={index}>
          <p>{turn.question}</p>
          {turn.tools.map(({ call, result }) => (
            <pre key={call.id}>{call.name}({JSON.stringify(call.arguments)}) {result ?? '…'}</pre>
          ))}
          <p>{turn.answer}</p>
        </article>
      ))}
      {error ? <p role="alert">{error}</p> : null}
      <form onSubmit={submit}>
        <input value={question} onChange={(event) => setQuestion(event.target.value)} placeholder="A calm book for tonight" />
        <button type="submit" disabled={busy}>Ask</button>
      </form>
    </section>
  );
}
```

Each turn holds the visitor's question, the answer as it streams, and every tool call with its raw result. Render those results as your own cards when your tools return structured data, as the [MCP guide](/docs/guides/mcp-tools#show-tool-results-as-your-own-cards) shows.

`send` does not queue questions. Disable your send button while `busy` is true.

## Try it without a server [#try-it-without-a-server]

Any object with `listTools` and `callTool` is a tool source. The demo serves a tiny catalog from memory, which lets you try the chat before your MCP server exists:

```ts title="src/demoTools.ts"
import type { ToolDefinition, ToolSource } from '@aiconnect/core';

const BOOKS = [
  { title: 'Piranesi', author: 'Susanna Clarke', mood: ['dreamy', 'calm', 'mysterious'], pages: 272 },
  { title: 'Gilead', author: 'Marilynne Robinson', mood: ['calm', 'thoughtful', 'hopeful'], pages: 247 },
];

const DEFINITIONS: ToolDefinition[] = [
  {
    name: 'search_books',
    description: 'Find books in the catalog by mood and maximum length.',
    inputSchema: {
      type: 'object',
      properties: {
        mood: { type: 'string', description: 'One mood word, such as calm, funny or adventurous' },
        maxPages: { type: 'number' },
      },
    },
  },
];

export const demoTools: ToolSource = {
  listTools: async () => DEFINITIONS,
  async callTool(name, args) {
    if (name !== 'search_books') {
      throw new Error(`Unknown tool ${name}`);
    }
    const mood = typeof args.mood === 'string' ? args.mood.toLowerCase() : null;
    const maxPages = typeof args.maxPages === 'number' ? args.maxPages : Infinity;
    return JSON.stringify(BOOKS.filter((book) => (!mood || book.mood.includes(mood)) && book.pages <= maxPages));
  },
};
```

Pass `tools: demoTools` to `useAiChat` in place of the `McpToolSource`.

## Run it [#run-it]

1. Start your dev server on `localhost`. AI Wallet serves pages on HTTPS and on `http://localhost` or `http://127.0.0.1`.
2. Open the page in Chrome with AI Wallet installed and an account connected in the wallet.
3. Click **AI mode**, then **Connect AI Wallet**. Approve your site in the window the wallet opens.
4. Ask a question. The answer streams from the account the wallet chose for your site.

## Already using an AI SDK [#already-using-an-ai-sdk]

A site that already calls a model through the OpenAI SDK, the Anthropic SDK, Google Gen AI, the Vercel AI SDK, LangChain or LlamaIndex keeps its code. It builds the SDK's client from the same `AiConnect` client it passes to `AiConnectProvider`:

```tsx
import OpenAI from 'openai';
import { AiConnect } from '@aiconnect/core';

const client = new AiConnect();
const openai = new OpenAI(client.sdkOptions);
```

The [AI SDK guide](/docs/guides/existing-sdks) shows the change for each SDK.

## Next steps [#next-steps]

- Match the connect window to your design system with the [theming guide](/docs/guides/theming).
- Read every option of the hooks in the [React reference](/docs/reference/react).
