Skip to main content
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.

NumeralHQ/numeral-mcp-demo

Public repository — a Next.js and TypeScript example you can clone and adapt.
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.
1

Clone and configure

Fill in .env:
2

Install and start

Open localhost:3100.
3

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.
Use an sk_test_ key to start. Everything stays scoped to your sandbox data.

How it’s wired

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.
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

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.
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.
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.
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.
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.
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.
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.

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.

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 for Claude Code, Claude Desktop, and Cursor configuration.