Analytics an agent can install, and an agent can read.
One command wires StepMetrics into your project. It finds your framework, puts the tracker in the right file, and writes the SDK bootstrap if your code talks to a model. After that your agents report what they did and query the results over MCP, and your web traffic lands in the same database.
npx stepmetrics init --endpoint https://app.stepmetrics.co --site YOUR_SITE_ID Free while it is in early access. One Fastify and Postgres service, and you can run it yourself.
What the one command does
Hand it to your coding agent or run it yourself. It reads the project, decides where the tracker belongs, and edits that file. Running it a second time changes nothing.
It knows your framework
Next.js App Router gets a next/script tag in the layout. Astro, Vite and
plain HTML each get the snippet in the file that actually renders on every page.
It wires the agent SDK
If your package.json pulls in an AI library, it writes a
stepmetrics.ts bootstrap for you to import where your agent runs.
It refuses to guess
If it cannot identify the project, it prints the snippet and exits non-zero. A tag written somewhere plausible and wrong shows up as missing data weeks later.
Your agents report what they did
Runs, model calls, tool calls, tokens and cost. Wrap an existing Anthropic client and every call is recorded with the provider's own token counts, without touching a single call site.
import Anthropic from "@anthropic-ai/sdk";
import { instrumentAnthropic } from "@stepmetrics/sdk";
const run = analytics.startRun({ name: "triage" });
const client = instrumentAnthropic(new Anthropic(), run); Cost is priced on the server
Send tokens, not money. A model with no pricing row is recorded as unknown cost instead of zero, and the ingest response tells you which, so an undercount is visible while you can still fix it.
Retries cannot double-count
Span keys are generated per run, so a replayed run regenerates the same keys and the server writes over the originals. Pass your own run key to make that hold for anything nondeterministic.
Ingest keys are write-only, and they are their own scope rather than a weaker read key. A leaked ingest key can add telemetry and do nothing else.
Your agents read it back over MCP
Ten read-only tools, so an agent can ask what happened instead of waiting for someone to export a CSV.
claude mcp add --transport http analytics https://app.stepmetrics.co/mcp \
--header "Authorization: Bearer dak_..." Same query functions as the dashboard
The MCP tools call the dashboard's own functions. An agent and the dashboard cannot disagree about what "visitors" means, and a test checks that both report the same number for the same segment.
Nothing writes
None of the ten creates, deletes or mints anything, and they take a read-scoped key. "The model would not do that" is not a security control.
It is a real web analytics tool too
Which pages get traffic, where it came from, which countries, devices and browsers, where people landed and left, how many are on the site right now, and how all of that compares with last period. Then how many of them downloaded, started checkout, and bought.
Segments compose
Narrow any number by country, device, browser, OS, source or entry path. "Only mobile visitors from Hacker News" is one query string, and it narrows the funnel too.
Funnels
Ordered conversion steps. Your segment filters who enters the funnel, not only who reaches step three, so the rate you read belongs to the people you asked about.
Bot traffic stays visible
Filtered traffic is kept and marked. One parameter shows you exactly what the filter took out, so you can check whether it was right.
How a visitor gets counted
A visitor id is a hash of four things: a daily salt, the site, the IP address and the user agent. The IP and the user agent go into the hash and are then thrown away. Neither one is written to any table, and there is no cookie banner because there is no cookie.
visitor_id = base64url(sha256(daily_salt || site_id || ip || user_agent))[:22] The salt rotates at UTC midnight
The same person gets a different id tomorrow. Old salts are deleted, and once they are gone nobody can recompute a historical visitor id, including someone holding the whole database and a suspect's IP address. Deleting the salt is the privacy control.
One service, one database
No third party ever sees a request, and no visitor is handed a cookie or any other durable identifier. The tracker honours Do Not Track and Global Privacy Control, and writes nothing to localStorage.
What you give up
Each of these follows from not storing a durable per-person id. The API responses and the dashboard say so too, so you will run into them inside the product as well.
- Visitors is a distinct-per-day count. Someone who visits Monday and Tuesday counts twice over a 30-day window. A true "unique visitors over 30 days" number needs a cookie.
- Funnels cannot cross UTC midnight. The window caps at 24 hours, and a longer one is rejected outright instead of quietly clamped. For conversions that really happen days later, attribute from billing records, where the durable identifier is the order.
- Geography stops at country. A city plus a timestamp starts to identify individuals, so there is no city setting to switch on by accident.
- There is no session replay, no user profiles and no cohort retention. All three need the identifier this design refuses to store.
Ask us something
This form posts to the analytics server, which delivers it to an inbox held in server-side configuration. No address appears on this page, so there is nothing here for a scraper to collect.