Skip to main content

Build a Klar Attribution MCP Server with Claude

This guide shows you how to connect the Klar Public API to an AI assistant like Claude using the Model Context Protocol (MCP).

Written by Frank Birzle

Once it's set up, you can ask questions like "Which channels had the best ROAS last month under data-driven attribution?" or "What's our revenue and profit by product this quarter?" and Claude will query your Klar data and answer directly — no dashboards, exports, or copy-paste.

You don't have to write the server by hand. We provide a ready-made CLAUDE.md specification below: drop it into an empty project folder, point Claude Code (or any coding agent) at it, and ask it to build the server. The file contains everything the agent needs — the API reference, the constraints, and the tools to expose.

What's new in this API

Klar released a new Public API today, and it replaces the attribution-only API this guide previously covered. If you built a server from the earlier version of this article, here's what changed:

  • Authentication is simpler. A single long-lived API key goes straight into an X-API-Key header. There's no more refresh-token exchange for a 5-minute access token.

  • Three report types, not one. The API now covers attribution, marketing performance, and revenue & profit, each gated by its own permission on the key.

  • Accounts can hold multiple shops. A new shops endpoint lists the shops an API key can see; every report call now takes a shopId.

  • Results are paginated with a cursor. A report call no longer returns a JSON array directly. It returns a dataUrl; you follow that, then follow nextPage until it's gone.

  • The rate limit is higher. 5 requests per 30 seconds per key, up from 2.

  • No more 31-day window cap. startDate and endDate are both inclusive, and the documentation no longer caps how wide a single date range can be — pagination handles large result sets instead.

What is MCP?

MCP is an open standard that lets AI assistants call external tools and data sources through a single, consistent interface. An MCP server is a small program that exposes "tools" the assistant can use. Build one that wraps the Klar Public API, and any MCP-capable client — Claude Desktop, Claude Code, and others — can pull your attribution, marketing, and revenue & profit data on demand.

Before you start

You'll need:

  • A Klar Public API key. Created and managed from the Klar dashboard, scoped to the permissions you need (public_api.attribution, public_api.marketing, public_api.revenue_and_profit). Treat it like a password.

  • Node.js 20 or newer installed.

  • Claude Code (npm install -g @anthropic-ai/claude-code) or another coding agent to build the server, plus Claude Desktop if you want to use the finished server there.

Step 1 — Create a project folder and add CLAUDE.md

Create an empty folder for the project, then save the file below inside it as CLAUDE.md. This is the build specification — it tells the coding agent exactly what to build and how the Klar API behaves.

Claude.md:

# CLAUDE.md — Klar Public API MCP Server

## Project goal

Build a Model Context Protocol (MCP) server that wraps the Klar Public API so
that any MCP-capable client (Claude Desktop, Claude Code, etc.) can query a
Klar account's attribution, marketing performance, and revenue & profit data
in natural language. The server exposes each report as a typed tool, handles
authentication, and absorbs Klar's API constraints (two-step cursor
pagination, the rate limit, and shop resolution) so the user never has to
think about them.

## Tech stack

- TypeScript + Node.js (Node 20+).
- @modelcontextprotocol/sdk (latest) — use McpServer and StdioServerTransport.
- zod for input validation.
- stdio transport (local), so the server plugs straight into Claude Desktop
and Claude Code.
- Native fetch (built into Node 20+) — no extra HTTP client needed.

Python is a fine alternative (official mcp / FastMCP SDK). If you go that
route, keep the same tool surface and the same constraint handling.

## Build steps

1. Scaffold the project: "type": "module" in package.json, a tsconfig.json
targeting NodeNext, output to ./build, and a build script (tsc).
2. Implement a Klar API client module (auth header, shop resolution, cursor
pagination, rate limiting) honoring the constraints in the API reference
below.
3. Implement the MCP server with the tools described under "Tool surface".
4. Compile (npm run build) and confirm it builds cleanly.
5. Write a README that includes the exact claude_desktop_config.json entry.

Do not call the live API during the build. The user supplies a real API key
at runtime via an environment variable.

## Klar Public API reference

Base URL: https://api.getklar.com

### Authentication

Every request needs the API key in a header:

X-API-Key: <KLAR_API_KEY>

Keys are 72 characters: the prefix klar_pk_ followed by 64 hex characters.
Read the key from the KLAR_API_KEY environment variable. Never hardcode it
and never write it to logs — mask it (e.g. show only the last 4 characters)
in any debug output.

A key is scoped to one account and to a set of permissions
(public_api.attribution, public_api.marketing,
public_api.revenue_and_profit). A call to an endpoint the key is not
permitted for returns 403 Forbidden with a message naming the missing
permission — surface that message to the user instead of a generic error.

### Shops — resolve the shopId

- Endpoint: GET /v1/public/shops
- Returns an array of { shopId, name }. shopId is a signed value — always
use the value returned here, never construct it yourself.

Every report endpoint requires a shopId. If the user names a shop instead of
supplying an ID, call list_shops, match by name (case-insensitive), and use
the returned shopId. Cache the shop list for the life of the process; refresh
it if a report call reports an unrecognized shopId.

### Reports — two-step, cursor-based

None of the report endpoints (attribution, attribution-detail, marketing,
revenue-and-profit) return data directly. Instead:

1. Initiate the report, e.g.
GET /v1/public/attribution?shopId=...&startDate=...&endDate=...
The response is { "dataUrl": "https://api.getklar.com/v1/public/results/<token>?page=1" }.
2. Fetch data by calling dataUrl. Each page returns
{ "results": [...], "nextPage": "<url>" } — up to 1,000 rows per page.
Follow nextPage until it is absent from the response, concatenating
results across pages.

The cursor token is valid for 10 minutes from the first /results call. If
paging takes long enough that the token could expire, or a page call returns
400/401 saying the cursor expired, re-initiate the report and resume paging.

All fields in result rows are camelCase.

### Report endpoints

- GET /v1/public/attribution — startDate, endDate (required, inclusive,
YYYY-MM-DD), metric (optional enum: first_touch, last_touch,
data_driven [default], linear, any_click, any_click_unique, u_shape,
time_decay, marketing_mix), window (optional enum: unlimited [default],
1_day, 7_day, 28_day), date_breakdown (optional enum: order [default],
touch). Requires public_api.attribution.
- GET /v1/public/attribution-detail — order-level detail. startDate, endDate
(required), lookbackWindow (optional enum, default unlimited), sortField
(optional, default order_id), sortDirection (optional: asc/desc, default
desc). Requires public_api.attribution.
- GET /v1/public/marketing — startDate, endDate (required). dimensions
(optional, comma-separated, up to 5 of: channel_name, channel_category,
channel_group, campaign, term, content, date, calendar_week,
calendar_month, quarter, country; default [date]). Optional filters:
customer_type, product_uids, order_tags, customer_tags, discount_codes
(comma-separated strings). date_granularity (optional: event [default] or
order). Requires public_api.marketing.
- GET /v1/public/revenue-and-profit — startDate, endDate (required).
dimensions (optional, comma-separated, up to 5, from a long enum covering
order/product/channel/customer/geo fields — see the full list in the
OpenAPI document at https://api.getklar.com/public/docs; default
[calendar_date]). debundle (optional: 0 [default] or 1, splits bundled
products into their components). date_granularity (optional: event
[default] or order). Requires public_api.revenue_and_profit.

All three report endpoints also require shopId (see "Shops" above).

### Constraints the server MUST enforce

1. Rate limit: 5 requests per 30 seconds per API key, shared across every
endpoint (shops, all report-initiations, and every /results page).
Implement a token-bucket or queue so the client never exceeds this,
including during long paging sequences.
2. Cursor pagination as described above — always follow dataUrl, then
nextPage. Never assume a report returns all its rows in one call.
3. Shop resolution as described above.
4. Logging: on stdio transport, never write to stdout — it corrupts the
JSON-RPC stream. Use console.error (stderr) only.
5. Errors: catch failures and return
{ isError: true, content: [{ type: "text", text: "..." }] } instead of
throwing, so a single bad call doesn't kill the client session. Include
Klar's own error message (the "message" field) when available.

## Metric glossary

These are working definitions for tool descriptions. Klar's attribution
model and revenue math did not change with this release — only how the
data is delivered did. For authoritative formulas, see Klar's margin-metrics
documentation in the help center.

- orders — attributed orders. Fractional values are expected: attribution
models split partial credit for an order across the touchpoints that led
to it.
- nc / rc — attributed New-Customer and Returning-Customer orders (also
fractional).
- grossRevenue / netRevenue — attributed gross and net revenue.
- cost — ad spend for the row. ROAS is netRevenue / cost.
- clicks / impressions — ad engagement.
- cm1 / cm2 — contribution-margin tiers (CM1 after product/variable costs,
CM2 after marketing cost).
- clv_30 / clv_60 / clv_90 — projected customer lifetime value at 30 / 60 /
90 days.
- acm2 — adjusted CM2.
- ncGrossRevenue / rcGrossRevenue / ncNetRevenue / rcNetRevenue — gross and
net revenue split by new vs. returning customers.

Exact field names depend on the report and the dimensions requested — treat
this glossary as a guide, not a fixed schema.

## Tool surface

Core — build these first:

- list_shops — no params. Returns the shops available to the API key
({ shopId, name }[]). Call this whenever a shopId is needed and the user
has not supplied one.
- get_attribution_report — shopId, startDate, endDate, metric, window,
date_breakdown. Pages through the full result set and returns the
combined rows.
- get_marketing_report — shopId, startDate, endDate, dimensions, and the
optional filters listed above. Pages through and returns combined rows.
- get_revenue_and_profit_report — shopId, startDate, endDate, dimensions,
debundle, date_granularity. Pages through and returns combined rows.

Optional extensions (add if useful to the user):

- get_attribution_detail_report — order-level attribution detail (see
endpoint above).
- summarize_by_channel — takes the same params as get_attribution_report or
get_marketing_report; aggregates rows by channel dimension (sum orders,
cost, netRevenue, grossRevenue; derive ROAS = netRevenue / cost).
- compare_periods — two date ranges; returns per-channel deltas for any of
the three report types.

Validate every param with zod and surface the exact accepted values in the
schema. Bake the key interpretation rules — fractional orders, inclusive
date ranges, ROAS = netRevenue / cost, camelCase field names — into the tool
descriptions so the model reads the results correctly.

## Run & test

- npm run build
- npx @modelcontextprotocol/inspector node ./build/index.js

## README must include

A one-line description; the exact claude_desktop_config.json snippet; the
tool list with one-line descriptions; the required env var (KLAR_API_KEY);
and a short troubleshooting section covering cursor-token expiry, rate-limit
pauses, 403s from missing permissions, and the no-stdout-logging rule.

Step 2 — Let the agent build it

Open the folder in Claude Code (or your agent of choice) and give it a short prompt:

Read CLAUDE.md and build the MCP server it describes. Use TypeScript with
the official MCP SDK and stdio transport. When you're done, run the build
and show me the claude_desktop_config.json entry I need.

The agent will scaffold the project, implement the Klar API client (with the auth header, shop resolution, rate limiting, and cursor pagination), define the tools, and compile the server. Review the generated code, then build it.

Step 3 — Connect it to Claude Desktop

Open your Claude Desktop config file and add the server under mcpServers. On macOS it's at ~/Library/Application Support/Claude/claude_desktop_config.json; on Windows it's at %APPDATA%\Claude\claude_desktop_config.json.

{
"mcpServers": {
"klar-public-api": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/build/index.js"],
"env": {
"KLAR_API_KEY": "klar_pk_your-64-character-key"
}
}
}
}

Use the absolute path to the compiled build/index.js, paste in your Public API key, save the file, and restart Claude Desktop. The Klar tools will appear once the server starts cleanly.

If you're using Claude Code instead, you can register the same server from the command line with claude mcp add.

Step 4 — Ask away

Once connected, try prompts like:

  • "Which shops can this key see?"

  • "Show me last week's attribution by channel under data-driven attribution."

  • "Compare new-customer net revenue in March vs. February."

  • "Break down marketing spend by channel and country for Q1."

  • "What's our revenue and profit by product for the last 30 days, with bundles split into their components?"

Claude will call your server, fetch the data from Klar, and answer.

Good to know

  • API keys are scoped by permission. A key without all three permissions gets a 403 on the endpoints it isn't scoped for — that's expected, not a bug.

  • Cursor tokens expire after 10 minutes. The server re-initiates the report automatically if paging runs long — you don't need to do anything.

  • The rate limit is 5 requests per 30 seconds per key, shared across shops, every report start, and every results page. Large date ranges or wide dimension breakdowns take a little longer because of this.

  • Date ranges are inclusive on both ends. This is a change from the previous attribution-only API, where endDate was exclusive.

  • Attributed orders are often fractional. Attribution models split credit for an order across the touchpoints that led to it, so a channel can show, for example, 2.13 orders.

  • Keep your API key private. It's tied to your account and its granted permissions; never commit it to a repository or share it.

Did this answer your question?