# JavaScript (/docs/quickstart/javascript)

`@aiconnect/core` works without any framework. You build the connect button yourself from the client's state, and you run the conversation with `runConversation`. The connect window of `@aiconnect/react` is not part of this setup.

`@aiconnect/core` is an ES module that imports `@modelcontextprotocol/sdk`, so load it through your bundler, such as Vite, esbuild or webpack.

## Install the package [#install-the-package]

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

<Callout type="warn">
The package is not published on npm yet. Until it is, build a checkout of the AI Connect repository with `npm run build` and point your `package.json` at it with `"@aiconnect/core": "file:../aiconnect/packages/core"`.
</Callout>

## The page [#the-page]

```html title="index.html"
<button id="connect" type="button" hidden>Connect your AI</button>
<p id="status" role="status"></p>
<form id="ask" hidden>
  <label for="question">Your question</label>
  <input id="question" name="question" autocomplete="off" />
  <button type="submit">Ask</button>
</form>
<p id="answer"></p>
<script type="module" src="/src/aiMode.js"></script>
```

## Follow the connection [#follow-the-connection]

Create one client for the page. In a browser it looks for AI Wallet right away and asks whether your site is already approved. Render its current state once, then subscribe to the changes.

```js title="src/aiMode.js"
import { AiConnect, McpToolSource, finalAnswer, runConversation } from '@aiconnect/core';

const client = new AiConnect();

const connectButton = document.querySelector('#connect');
const statusLine = document.querySelector('#status');
const form = document.querySelector('#ask');
const answer = document.querySelector('#answer');

const STATUS_TEXT = {
  checking: 'Looking for AI Wallet',
  noWallet: 'Add AI Wallet to your browser, then reload this page.',
  disconnected: 'Use your own AI on this site.',
  connecting: 'Approve this site in the AI Wallet window.',
};

function render(state) {
  connectButton.hidden = state.status !== 'disconnected' && state.status !== 'error';
  form.hidden = state.status !== 'connected';
  if (state.status === 'connected') {
    statusLine.textContent = `Answered by your ${state.providerName} account.`;
  } else if (state.status === 'error') {
    statusLine.textContent = state.message;
  } else {
    statusLine.textContent = STATUS_TEXT[state.status];
  }
}

render(client.state);
client.subscribe(render);
connectButton.addEventListener('click', () => client.connect());
```

`subscribe` calls your listener on each change only, which is why the code renders `client.state` once first.

The states, in the order a first visit goes through them:

| Status | Meaning |
| --- | --- |
| `checking` | The client is looking for the wallet, or the page is rendering on a server. |
| `noWallet` | No wallet answered within 300 milliseconds. Show how to install AI Wallet. |
| `disconnected` | The wallet is there and your site is not connected. A wallet that is locked when the page loads reports this until the visitor unlocks it. |
| `connecting` | `connect()` is waiting for the visitor in the wallet's window. |
| `connected` | Your site may chat. The state carries `providerName`, `model` and `session`. |
| `error` | The connection failed. `message` holds the wallet's reason, such as "The connection was declined". |

## Run a conversation with your tools [#run-a-conversation-with-your-tools]

`runConversation` sends the conversation to the visitor's model, runs the tools it asks for, and returns the messages it added. Keep the transcript yourself and pass it back on the next question.

```js title="src/aiMode.js"
const SYSTEM = 'You recommend books from this site\'s catalog. Always search the catalog before recommending.';
const tools = new McpToolSource('/api/mcp', 'Bookshop');
let transcript = [{ role: 'system', content: SYSTEM }];

async function ask(question) {
  const { state } = client;
  if (state.status !== 'connected') {
    return;
  }
  const asked = { role: 'user', content: question };
  answer.textContent = '';
  const added = await runConversation({
    session: state.session,
    tools,
    messages: [...transcript, asked],
    onText: (delta) => {
      answer.textContent += delta;
    },
    onToolCall: (call) => {
      statusLine.textContent = `Running ${call.name}`;
    },
  });
  transcript = [...transcript, asked, ...added];
  // The streamed text covers every step of the loop; the final answer is the model's last message.
  answer.textContent = finalAnswer(added)?.content ?? answer.textContent;
  statusLine.textContent = `Answered by your ${state.providerName} account.`;
}

form.addEventListener('submit', (event) => {
  event.preventDefault();
  const question = String(new FormData(form).get('question') ?? '').trim();
  form.reset();
  if (!question) {
    return;
  }
  ask(question).catch((error) => {
    statusLine.textContent = error.message;
  });
});
```

`added` holds the assistant turns and the tool results of this question, so later questions see what the tools returned earlier. A chat request rejects with the wallet's message when it fails, for example when the visitor stops the answer from the wallet or when the provider refuses the call.

## Ask without tools [#ask-without-tools]

For a single answer with no tools, call the session directly:

```js
const { state } = client;
if (state.status === 'connected') {
  const { message } = await state.session.chat({
    messages: [{ role: 'user', content: 'Summarise this page in two sentences.' }],
    onText: (delta) => {
      answer.textContent += delta;
    },
  });
  console.log(message.content);
}
```

## Disconnect [#disconnect]

```js
await client.disconnect();
```

This removes your site from the visitor's approved sites in the wallet, and the state becomes `disconnected`. The next `connect()` asks the visitor again.

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

A page 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 and passes the client's `fetch` to the SDK:

```js
import OpenAI from 'openai';

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

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

The [core reference](/docs/reference/core) lists every export with its options.
