agent-tracking

Agent Tracking

What AI agents do on your site

See recognized assistant referrals and supported browser WebMCP actions. Optional origin logs and site-server receipts add separate crawler and outcome evidence. The snippet sets no cookies.

Sign in or create an accountSee the demo page

Install

Add your site in the dashboard, then put this line on every page. The domain in data-domain has to be the one you registered; events for any other domain are dropped at the door.

<script defer data-domain="example.com" src="https://agenttracking.co/agent.js"></script>

Under five kilobytes, plain JavaScript, no framework, no dependencies. It sends small batches to /api/event with sendBeacon, so a click that leaves the page still lands. The script has no integrity hash on purpose: it is updated in place when a new agent appears in the list below.

Three layers

A. AI referrals

A visitor arrives from an assistant: the referrer is matched against a maintained list of assistant hosts, plus utm_source patterns. Recorded as the assistant and the landing path.

B. AI fetches

An assistant loads a page and runs its scripts: the user agent of the request that carries the beacon is matched against the same list. The matching is done on our server from the request headers, not in the snippet, so the list stays in one versioned place and the snippet stays small.

C. WebMCP tool calls

The snippet wraps supported document.modelContext and legacy navigator.modelContext calls while it is active. It records technical outcomes, duration and safe error classes. Declarative forms produce submit attempts. Remote MCP calls require the separate server adapter.

What this cannot see

A crawler that does not run JavaScript never executes the snippet. GPTBot, ClaudeBot and their kind mostly fetch raw HTML, so they appear here only when they render the page. Counting them needs the server log. For agenttracking.co itself the collector runs every quarter hour. A fetch burst means at least three distinct relevant paths close together in one import batch; it does not reveal a question or intent. For your site, upload an append-only full log on the settings page or use a collector with stable record identities. Where a vendor publishes address ranges, a claimed agent is checked against them. Missing or stale range data and mismatches stay separate from confirmed counts.

Tools and goals

Nothing to change in how you register tools. Register them as the specification says and the snippet sees them:

await document.modelContext.registerTool({
  name: "search_products",
  description: "Search the catalogue by free text.",
  inputSchema: { type: "object", properties: { query: { type: "string" } }, required: ["query"] },
  annotations: { readOnlyHint: true },
  execute: async ({ query }, { signal }) => { /* ... */ },
});
// agent.js observes supported registrations and calls while active:
// technical outcome, duration and safe error class; argument values are not stored.

Declarative tools are forms with a toolname. Add data-agent-goal to mark a browser goal attempt. A confirmed inquiry or booking needs a separate server receipt:

<form toolname="book_table" tooldescription="Book a table for a date and party size."
      action="/book" method="post">
  <input name="date" type="date" required>
  <input name="guests" type="number" min="1" required>
  <button type="submit" data-agent-goal="table_booked">Book</button>
</form>

If you publish a manifest at /.well-known/webmcp, the snippet hashes it once per visit, on the page the visit enters through. The dashboard shows when it last changed; Pro accounts can be alerted.

What is recorded, and what is not

RecordedNever recorded
Page path, without query stringQuery strings, fragments, form values
Referrer host and utm_source, when they name an assistantFull referrer URLs
Observed tool name, duration, technical outcome and sanitized error classInput values, output values
A session id: daily random salt, your domain, a coarse browser class and the address, hashedThe address itself, cookies, storage, fingerprints, any durable identifier
Recognized source claim and evidence status (list version 2026-09-25)Full user agent strings

Raw events are kept for 90 days and then deleted; daily totals are kept for as long as the site exists. Everything is stored on our own server in Germany. Removing a site deletes all of it. Our privacy notice has the formal version, and the data processing agreement is concluded when you add a site.

Plans

During the pilot every account is on Free, and paid plans are not open yet. Need more while the pilot runs? Write to hi@finalmaster.net and we switch your account by hand. The plans section has the terms of the pilot. Or run the whole thing yourself: the source is on GitHub under AGPL-3.0, with a docker compose file.

PlanSitesAgent events per monthHistoryExtras
Free110,00030 daysnone
Pro5500,000365 daysmanifest change alerts
Agencyunlimited5,000,000365 daysmanifest change alerts, white-label badge

Prices are shown at checkout in the dashboard. Free needs no card.

Your numbers, in your own tools

Aggregate site statistics are available as JSON and through an MCP read tool, so authorized scripts and agents can read them. Create the token on a site's settings page; it is shown once and belongs to the account, so it reads owned and explicitly shared sites. Read-only, aggregate data; no raw events.

curl -s https://agenttracking.co/api/stats/example.com?days=30 \
  -H "Authorization: Bearer wmt_your_token"

The same token sends your server log. Append-only full snapshots can be retried; stable source, generation and record headers are required for rotated or overlapping chunks. Previously accepted positions are skipped, so nothing is counted twice:

curl -sS -X POST https://agenttracking.co/api/logs/example.com \
  -H "Authorization: Bearer wmt_your_token" \
  -H "Content-Type: text/plain" --data-binary @/var/log/nginx/access.log

GET /api/stats with the same header lists the sites the token can read. days is capped by the plan's history. The answer carries totals, the previous period for trends, the day series, the agents with share and trend, the tools with success rate and average duration, and the busiest pages.

Over MCP, add our server with the token as a header and ask in words. Claude Desktop, Cursor and most clients take a configuration like this:

{
  "mcpServers": {
    "agent-tracking": {
      "url": "https://agenttracking.co/api/mcp",
      "headers": { "Authorization": "Bearer wmt_your_token" }
    }
  }
}

Then: “Which agents read example.com this week?” The client calls get_agent_stats and gets the same JSON. A client that cannot set headers can pass the token in the tool's token argument instead.

Connect a ChatGPT plugin or MCP server to AgentTracking

The integration runs in your Node.js/TypeScript MCP server. The website snippet alone cannot capture remote MCP calls. You can test with a privately connected plugin before publication.

What goes in the plugin ZIP?

Put the tracking wrapper in the server project serving your MCP endpoint. Do not put tracking tokens in plugin.json. This integration keeps the MCP URL and tool descriptors unchanged; the ZIP needs no tracking configuration. Manifest or exposed-tool changes must be handled separately.

1. Create two write credentials

Sign in and open your site → Settings → Server integrations. Create one tool telemetry credential and one outcome credential. The read-only Stats token cannot write here. The registered site domain must match domain in the code. Open workspace

2. Download the wrapper and configure your server

Download TypeScript wrapper and save it as lib/agenttracking-mcp.ts in your server project. It needs Node.js with fetch and node:crypto; it does not run in the Vercel Edge Runtime.

In Vercel: project → Settings → Environment Variables. Set these values for the desired deployment environment, then redeploy. On other hosts use their server environment variables. Do not use NEXT_PUBLIC_ variables.

VariableValue
TRACKING_TOOL_TOKENTool telemetry write credential
TRACKING_OUTCOME_TOKENOutcome write credential
TRACKING_ENVIRONMENTtest / production
import { MCPTracking } from "./lib/agenttracking-mcp";

const tracking = new MCPTracking({
  origin: "https://agenttracking.co",
  domain: "example.com", // your registered domain
  toolToken: process.env.TRACKING_TOOL_TOKEN!,
  outcomeToken: process.env.TRACKING_OUTCOME_TOKEN!,
  pluginVersion: "1.0.2",
  environment: process.env.TRACKING_ENVIRONMENT === "production"
    ? "production" : "test",
});

3. Wrap existing tool handlers

This example uses the MCP server registerTool method. calculateDescriptor, pdfDescriptor and the handlers stand for your existing code; adapt createAndSavePdf to your PDF store. Preserve the result format your plugin already expects.

// Keep the existing descriptor and business logic.
server.registerTool("calculate_quote", calculateDescriptor,
  tracking.wrap("calculate_quote", calculateQuoteHandler)
);

server.registerTool("create_quote_pdf", pdfDescriptor,
  tracking.wrap("create_quote_pdf", async (args, extra, context) => {
    // Save context.invocationId with the PDF in your backend.
    const pdf = await createAndSavePdf(args, context);
    const delivered = await tracking.outcome({
      ...context,
      receiptId: "created_" + context.invocationId,
      kind: "pdf_created",
      occurredAt: Date.now(),
    });
    // If false, retry a durably stored copy of this same receipt.
    return { structuredContent: { downloadUrl: pdf.downloadUrl } };
  })
);

The wrapper captures tool name, duration, version and technical outcomes, including isError: true. It sends no arguments, responses, customer names or PDF URLs. Tracking failures preserve tool results and errors; each delivery waits at most 800 ms by default. Store receipts with the business action for reliable retries.

4. Confirm PDF delivery

Store the creation invocationId privately with the PDF. After a successful full GET delivery, send a second receipt. HEAD requests, failed or partial transfers are not full deliveries. Retry the same receipt ID and timestamp after network failures.

// After your backend confirms a successful full GET delivery:
await tracking.outcome({
  invocationId: pdf.trackingInvocationId,
  receiptId: storedDelivery.id, // opaque ID, 16–64 characters
  kind: "pdf_fetched",
  occurredAt: storedDelivery.deliveredAt, // milliseconds, unchanged on retry
});

A PDF fetch confirms delivery, not reading: automatic prefetches can also trigger downloads. Use inquiry_created and booking_created for inquiries and bookings. A shared conversation identifier alone does not prove a connected flow.

5. Test privately and verify

  1. Set TRACKING_ENVIRONMENT to test and deploy the MCP server.
  2. Use your privately connected plugin: calculate a quote, create a PDF, fetch the PDF.
  3. Open your site → Tools → Test data. Check calls, version, duration, PDF created and PDF fetched. Also test a controlled tool failure.
  4. For real usage explicitly set the production environment to production and redeploy. Continue marking demo and review runs as test.

6. Inspect timelines, warnings and versions

Tools lists the latest 50 MCP calls. Open shows the technical outcome and up to 100 receipts matching the exact site, environment and invocation ID. Missing outcome receipts remain unknown. Event time and receipt time are displayed separately.

Dashboard warnings appear with at least 20 calls, at least 3 failures/timeouts and a failure rate of at least 10%, or at least 20 measured durations with p95 ≥ 5,000 ms. Production warnings also appear in Insights. No email or external message is sent.

In the version comparison select a tool and two known versions. Both columns use the same environment and receipt-time window. Samples, unknown outcomes and duration coverage remain visible; a difference does not prove a causal improvement. Outcome counts represent calls with a confirmed receipt, not users or a conversion rate.

No data? Check the domain, the two credential purposes and the new deployment. Invalid fields return HTTP 400; invalid tokens return HTTP 401. Missing outcome receipts mean unknown, not automatically a lost customer. Test data also consumes event quota.

Confirm inquiries and bookings from your server

Create separate site write credentials for outcomes and remote MCP tool calls in the site settings. Keep them on your server. After your application successfully saves an inquiry, POST a stable receipt to /api/outcomes/example.com with Authorization: Bearer atw_.... The account's stats token cannot write here.

{"receiptId":"receipt_1234567890123","kind":"inquiry_created","status":"confirmed","occurredAt":1790337600000,"taskId":"task_1234567890123456"}

Use a new receipt ID per actual inquiry and retry the same ID after network failure. A repeated receipt returns duplicate; a changed payload for the same ID returns 409. Do not send contact, payment or order content. Optional task and invocation IDs link observations without proving the actor.

For external MCP servers, send a separate credential to /api/server-tools/example.com with an invocation ID, optional task ID, tool name, time, technical outcome and actor kind. An agent actor is reported by your server, not independently verified. The browser snippet does not observe every remote MCP call. The runnable TypeScript example is in examples/inquiry-app in the repository.

Private task checks, fixes and client reports

The public stats page shows selected 30-day aggregates. Sign in to a site you own or have been invited to read for source status and the protected workflow. Owners can run one deterministic Chrome inquiry check against a test. or staging. host. Its synthetic input and result are kept separate from production counters; a third-party model-agent test is not configured without an adapter.

A documented correction links a failed run and a later run of the same task with version IDs, sample sizes and unknown outcomes. Findings can reference versioned diagnostic recipes, but recipes are suggestions until a real retest confirms the specific case. The printable site report and JSON export show coverage, findings, corrections, retests and open points.

Owners can create a seven-day, email-bound invitation link for a reader; the product does not send the invitation mail. Readers can access only their granted site, and revoked access stops private report and export access. Public stats links do not expose internal findings, task runs or server receipts. Read the task-check contract and agency workflow in the open-source repository.

The agents on the list

Version 2026-09-25. Referrers: ChatGPT, Perplexity, Claude, Microsoft Copilot, Google Gemini, You.com, Mistral Le Chat, Meta AI, Grok, DeepSeek, Phind, DuckDuckGo AI, Poe, HuggingChat. User agents: ChatGPT-User, OAI-SearchBot, GPTBot, ClaudeBot, Claude-User, Claude-SearchBot, Claude-Web, anthropic-ai, PerplexityBot, Perplexity-User, Gemini Deep Research, Bytespider, Amazonbot, Meta-ExternalAgent, Meta-ExternalFetcher, DuckAssistBot, YouBot, MistralAI-User, cohere-ai, AI2Bot, img2dataset, iAskBot, Browser Use. Missing one? Write to hi@finalmaster.net, or open a pull request against lib/tracking/ai-sources.json.