NumeralHQ/numeral-mcp-demo
Public repository — a Next.js and TypeScript example you can clone and adapt.
- 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
.env:2
Install and start
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.How it’s wired
lib/mcp.ts — the connection
About 50 lines. Connects with the official @modelcontextprotocol/sdk client and exposes listTools, callTool, and getServerInstructions.
app/api/chat/route.ts — the agent loop
About 100 lines, and it is the whole integration:
- Connect to the MCP server and discover its tools with
tools/list. - Hand those tools to Claude along with the conversation.
- When Claude asks to use a tool, forward the call with
tools/calland feed the result back. - Repeat until Claude answers in plain text.
Patterns worth copying
Pass the server's instructions to the model
Pass the server's instructions to the model
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.Use prompt caching on the system prompt and tool catalog
Use prompt caching on the system prompt and tool catalog
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.Tell the model today's date
Tell the model today's date
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.Tell the model that jurisdiction IDs are uppercase
Tell the model that jurisdiction IDs are uppercase
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.Return tool errors to the model, don't throw
Return tool errors to the model, don't throw
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.Cap the agent's step count
Cap the agent's step count
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.Show the tool calls in the UI
Show the tool calls in the UI
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.