# Custom events, tags and debug mode

Telltale for MCP servers and Telltale for APIs record every agent call with no content. Three extras help you read that record: **tags** on each call (which plan, which region), **custom events** for moments in your own product (a report exported, a plan upgraded), and **debug mode** to see what the SDK sends while you set it up. All three stay shape-only: Telltale never receives a message, an argument, a body or free text.

## Tags

A tag is a category you attach to each call, so you can filter **Tools › All tools** by it, for example "how do Pro accounts' calls fail?".

```javascript
// MCP server
const telltale = withTelltale(server, {
  endpoint: 'https://app.trytelltale.dev',
  serverKey: process.env.TELLTALE_SERVER_KEY,
  identify: ({ authInfo }) => ({ accountId: authInfo?.extra?.accountId }),
  tags: ({ authInfo }) => ({ plan: authInfo?.extra?.plan, region: 'eu', beta: false }),
});

// API middleware
const telltale = createTelltaleApi({
  endpoint: 'https://app.trytelltale.dev',
  serverKey: process.env.TELLTALE_SERVER_KEY,
  tags: req => ({ plan: req.user?.plan }),
});
```

Tags are categories, so the rules are strict. The SDK applies them before sending and the server applies them again:

- **Keys** are lowercase letters, digits and `_`, start with a letter, and are at most 32 characters. Keys that suggest personal data or secrets are refused: `email`, `phone`, `name`, `address`, `password`, `token`, `secret`, `auth`, `cookie`, `session`, `ip`, `ssn`, `card`, `iban`, `dob`, `birth`, anything ending in `key`, and `user`, `account`, `customer`, `person` or `member` with or without `_id`. Who made a call goes in `identify` (`accountId`, `userId`), never in a tag.
- **Values** are `true`/`false`, a number, or a short lowercase word (letters, digits and `_ . : -`, at most 32 characters). A value that looks like an identifier (six or more digits in a row, 16 or more hex characters, a UUID) is refused.
- **At most 10 tags** a call. A key with more than 50 different values in a day is not a category: the extra values are stored as `(other)`.

An invalid tag is dropped on its own; the call is still recorded. The answer to each batch counts them (`tagsDropped`), and debug mode logs the number (never the value). Tags ride on MCP calls and on API calls with agent evidence; the per-route counts of all API calls carry no tags.

## Custom events

A custom event is a named moment in your product. Telltale counts it per day and per account; it never stores a message or payload, so there is nothing in it to leak.

```javascript
// After a report export succeeds, in a tool handler or anywhere on your server:
telltale.event('report.exported', { accountId: 'acct_42', tags: { format: 'csv' } });
```

- **Name:** lowercase letters, digits, `_` and `.`, starting with a letter, at most 64 characters (`report.exported`, `plan.upgraded`). A name with spaces or capitals is refused (`event()` returns `false`).
- **Optional:** `accountId` and `userId` (as from `identify`), `tags` (the same rules as above), `at` (epoch milliseconds, within the last 7 days) and `channel` (`mcp`, `api` or `server`; the SDK sets its own).
- Events are queued and sent in the background with the calls; `flush()` and `close()` send them too.
- A resent event (same `eventId`) is counted once.

**Tools › All tools › Custom events** shows each name with how many times it happened, how many accounts it reached, by channel and by tag value.

Without the SDK, POST to the server API with the Site's server key. Here `eventId` and `at` are required: `eventId` is your own unique ID for the event (8 to 128 letters, digits, `_` or `-`) and `at` is when it happened, in epoch milliseconds within the last 7 days (the example uses the current time):

```bash
curl -X POST https://app.trytelltale.dev/api/v1/events \
  -H "Authorization: Bearer $TELLTALE_SERVER_KEY" -H "Content-Type: application/json" \
  -d '{"events":[{"eventId":"evt_8f2a91c0d3","name":"report.exported","at":'"$(date +%s000)"',"accountId":"acct_42","tags":{"format":"csv"}}]}'
```

The answer is `{accepted, replayed, rejected, tagsDropped?}`. Any field other than `eventId`, `name`, `at`, `accountId`, `userId`, `channel` and `tags` is refused (a `message` or `payload` field rejects that event), so free text cannot be stored by mistake. Custom-event rows for an account are erased with the account and included in its subject export.

## Tool performance and notices

**Tools › All tools** lists every MCP tool and API route with its agent calls, failures, error rate and time (p50 is the typical call, p95 the slow end: one call in twenty is slower), split by agent client. Filter by client or by a tag. For an API route you also see **all calls**, including callers we could not confirm as agents (Unknown, which is not the same as a person); those times are "at most" figures, the upper edge of a time band.

These notices reach the bell and your webhooks:

- **A tool or route is failing** (`agent_call.errors`): at least 5 failed calls and at least a quarter of its finished calls in 30 minutes. One notice per tool every 6 hours.
- **A slow tool call** (`agent_call.slow`): a call ran longer than your slow-call limit (Issues › Limits; 30 s by default). One notice per tool every 6 hours. Not an error.
- **An account's first agent call** (`agent_call.new_account`): the first MCP or API agent call from an account your server named. At most 10 a Site a day.
- **A new client** (`agent_client.new`): an MCP client name or API agent name seen on the Site for the first time. At most 10 a Site a day.
- **An issue came back** (`issue.regressed`): an issue you resolved in Issues happened again.

All carry counts, the tool or route and your own account ID, never content. See [Webhooks](/docs/webhooks) for the payloads.

## Debug mode

Debug mode shows what the SDK does while you set it up. It writes to **stderr** (never stdout, which a stdio MCP server uses for the protocol), or to your own function. It stays in your process: the SDK sends no diagnostics about itself anywhere, debug or not.

```javascript
withTelltale(server, { endpoint, serverKey, debug: true });               // stderr
createTelltaleApi({ endpoint, serverKey, debug: line => logger.debug(line) }); // your logger
```

Or set `TELLTALE_DEBUG=1` in the environment without changing code. Lines look like:

```
[telltale-mcp] debug on: sending shapes only to https://telltale.example.com | protected tools: 1 | why field on: 0 tools | batches every 5000 ms
[telltale-mcp] queued call export_report ok 182 ms | account: yes | user: yes | session: transport | tags: plan
[telltale-mcp] sent 12 calls: accepted 12 replayed 0 rejected 0
[telltale-api] queued call POST /v1/invoices 201 41 ms | account: yes | user: no | agent evidence: ip,declared | tags: plan
```

A debug line carries shapes only: the tool or route, the outcome, the time, whether an account and user were named, which tag keys were sent. Never arguments, results, bodies, headers, account or user IDs, tag values or keys. Turn it off in production; it is off by default.
