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

# MCP Demo Application

> A complete public example of embedding Numeral's MCP server in your own product.

We maintain a small, complete example application you can clone and run locally. It plays the role of a fictional platform ("Numeral MCP Sandbox") and shows what it takes to put a tax assistant, running under your own brand, in front of your users.

<Card title="NumeralHQ/numeral-mcp-demo" icon="github" href="https://github.com/NumeralHQ/numeral-mcp-demo">
  Public repository — a Next.js and TypeScript example you can clone and adapt.
</Card>

The app has two surfaces:

* **An embedded tax assistant** — a chat agent that answers questions about nexus, filings, registrations, transactions, and products using live Numeral data. Every tool call the agent makes is displayed in the UI, so you can see exactly how it reached its answer.
* **A tool explorer** — renders the MCP server's live capability catalog and lets you invoke any tool with raw arguments, so you can see precisely what an agent sees.

## Run it locally

You need a Numeral secret API key and an Anthropic API key.

<Steps>
  <Step title="Clone and configure">
    ```bash theme={null}
    git clone https://github.com/NumeralHQ/numeral-mcp-demo.git
    cd numeral-mcp-demo
    cp .env.example .env
    ```

    Fill in `.env`:

    ```bash theme={null}
    NUMERAL_API_KEY=sk_test_...   # from dashboard.numeralhq.com/developers
    ANTHROPIC_API_KEY=sk-ant-...  # from console.anthropic.com
    ```
  </Step>

  <Step title="Install and start">
    ```bash theme={null}
    npm install
    npm run dev
    ```

    Open [localhost:3100](http://localhost:3100).
  </Step>

  <Step title="Ask something">
    Try *"Which states have I triggered nexus in?"* or *"Are any filings waiting on my approval?"* — then open the tool explorer at `/tools` to see the raw catalog.
  </Step>
</Steps>

<Tip>
  Use an `sk_test_` key to start. Everything stays scoped to your sandbox data.
</Tip>

## How it's wired

```
Browser ──> your Next.js API routes ──> Numeral MCP (Bearer sk_ key)
                     │
                     └──> Anthropic API (the agent model)
```

The important rule: **your Numeral key never reaches the browser.** The browser talks only to the app's own routes, and the server holds the key. This is not a detail specific to the demo — it is how any production integration must be structured, because a secret key grants full read access to your tax data.

Only two files carry the integration. Everything else is UI.

### `lib/mcp.ts` — the connection

About 50 lines. Connects with the official `@modelcontextprotocol/sdk` client and exposes `listTools`, `callTool`, and `getServerInstructions`.

```ts theme={null}
const client = new Client({ name: "numeral-mcp-demo", version: "1.0.0" })
const transport = new StreamableHTTPClientTransport(new URL(MCP_URL), {
  requestInit: {
    headers: { Authorization: `Bearer ${apiKey}` },
  },
})
await client.connect(transport)
```

Because the server is stateless, a fresh connection per request is both cheap and correct — there is no session to pool or keep alive.

### `app/api/chat/route.ts` — the agent loop

About 100 lines, and it is the whole integration:

1. Connect to the MCP server and discover its tools with `tools/list`.
2. Hand those tools to Claude along with the conversation.
3. When Claude asks to use a tool, forward the call with `tools/call` and feed the result back.
4. Repeat until Claude answers in plain text.

Nothing in the loop is specific to any individual Numeral tool. New tools the server ships appear automatically on the next request and the model can use them with no code change.

## Patterns worth copying

<AccordionGroup>
  <Accordion title="Pass the server's instructions to the model" icon="scroll">
    The MCP handshake returns an `instructions` document explaining auth, testmode, how merchants resolve, and how the tools relate to one another. The demo injects it into the system prompt via `client.getInstructions()`.

    This is free grounding written by the people who built the tools — without it the model has to infer relationships between tools from names alone.
  </Accordion>

  <Accordion title="Use prompt caching on the system prompt and tool catalog" icon="bolt">
    The system prompt and the tool catalog are byte-identical on every request, while the conversation is short and changes each turn. The demo marks both as `cache_control: { type: "ephemeral" }` breakpoints, so repeat requests reprocess only the conversation suffix.

    With 14 tool schemas plus the instructions document, this is a meaningful latency and cost difference on every turn after the first.
  </Accordion>

  <Accordion title="Tell the model today's date" icon="calendar">
    Models do not know the current date. Without it, "this month" or "last quarter" resolves to the model's training-data era and it will silently query the wrong range.

    The demo injects `Today's date is YYYY-MM-DD` into the system prompt. Any agent calling date-filtered tools like `list_filings` or `get_sales_summary` needs this.
  </Accordion>

  <Accordion title="Tell the model that jurisdiction IDs are uppercase" icon="location-dot">
    Jurisdiction filters are matched exactly, so `us-ca` returns an empty list while `US-CA` returns data — and because an empty list is not an error, the agent will confidently report "no filings in California" instead of correcting itself.

    The demo's system prompt states the rule explicitly: *"Jurisdiction IDs are uppercase (e.g. US-FL, US-CA, CA-BC); always pass jurisdiction\_id filters in uppercase."* This is the single highest-value line in that prompt.
  </Accordion>

  <Accordion title="Return tool errors to the model, don't throw" icon="triangle-exclamation">
    The demo forwards failures as `tool_result` blocks with `is_error: true`, carrying the `{ error_code, error_message }` payload through to the model. The system prompt instructs it to read `error_code` and adjust rather than retrying blindly.

    Because Numeral's error codes are stable and specific, this makes the agent self-correcting. See [handling errors in an agent loop](/mcp/authentication#handling-errors-in-an-agent-loop).
  </Accordion>

  <Accordion title="Cap the agent's step count" icon="hand">
    The loop runs at most `MAX_AGENT_STEPS` (8) iterations before returning a message asking the user to narrow the question. Without a cap, a confused agent can loop on tool calls until it exhausts your rate limit.
  </Accordion>

  <Accordion title="Show the tool calls in the UI" icon="eye">
    The demo renders every tool call and its result alongside the answer. For a tax product this is close to essential — users need to see which data produced a number before they will trust it, and it makes debugging your prompt dramatically easier.
  </Accordion>
</AccordionGroup>

## Before you ship

The demo optimizes for local exploration, not production. Three things to change:

* **Add your own user authentication** in front of routes like `app/api/mcp/call`. The demo leaves them open so you can poke at them; in production they would let anyone query your tax data.
* **Scope what your users can reach.** Your Numeral key covers your whole account. If you serve multiple merchants, enforce in your own routes that a user's requests are constrained to their own `merchant_id` — the MCP server isolates tenants at the *account* level, not between merchants inside your account.
* **Handle rate limits.** Add backoff on `429`. See [rate limits](/mcp/authentication#rate-limits).

## Connecting other clients instead

If you just want to query your own data rather than build a product surface, you do not need the demo at all — point an existing MCP client at the server. See the [quickstart](/mcp/quickstart) for Claude Code, Claude Desktop, and Cursor configuration.
