Skip to content

Commit b36419d

Browse files
committed
feat: report funnel metrics on channel mcp
## Changed - Tool calls now emit a call_success or call_error, reported from inside CallTool's own try/catch so the original SDK error is still available to classify before it is rewritten - Shutdown flushes the queued events before exiting, capped at 2s - The cast through the SDK's protected `client` moved to a single guarded accessor, reused by the existing X-Lara-Client headers ## New - src/metrics.ts: batched, fire-and-forget event reporting to the integrations monitoring backend. Buys a token from /auth/issue-token, posts to /metrics/ingest-events every 2s, retries once on 401, requeues on 429/5xx, drops on any other rejection, queue capped at 10 000. A backend that is down never delays or fails a translation - Events: install (first run only), auth_success (deduplicated per account per process, since the HTTP transport builds a Translator per request), call_success, call_error, and auth_fail beside a call_error Lara answered with 401/403. Both transports report - accountId is the Lara account behind the credentials, read from the id claim of the token the SDK holds. No token means no event: a made-up id would be a fake account in every dashboard - Installation id persisted at ${LARA_HOME:-~/.lara}/installation-id, with a per-process fallback when the home is not writable - Opt-out via DO_NOT_TRACK: nothing is measured, queued, written to disk or sent. METRICS_URL / METRICS_API_KEY override the compiled-in defaults, which are empty until the backend is deployed - 60 tests covering the client in isolation and the server wiring
1 parent 2a1c076 commit b36419d

11 files changed

Lines changed: 1639 additions & 13 deletions

File tree

.env.example

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,15 @@ PORT=80
44
TRANSPORT=http
55

66
# Logging
7-
LOGGING_LEVEL=info
7+
LOGGING_LEVEL=info
8+
9+
# Usage metrics. Both are compiled in with a default, and these override them.
10+
# Either one empty = telemetry silently off.
11+
# METRICS_URL=http://localhost:8080
12+
# METRICS_API_KEY=dev-mcp-key
13+
14+
# Where the installation id is kept. Default: ~/.lara
15+
# LARA_HOME=/var/lib/lara
16+
17+
# Opt out of usage metrics entirely (https://consoledonottrack.com).
18+
# DO_NOT_TRACK=1

CLAUDE.md

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@ Core configuration (`src/env.ts`):
131131
- `HOST` / `PORT` - HTTP server binding (default: `0.0.0.0:3000`)
132132
- `LARA_ACCESS_KEY_ID` / `LARA_ACCESS_KEY_SECRET` - API credentials (required for STDIO mode)
133133
- `LOGGING_LEVEL` - Log level: `debug`, `info`, `warn`, `error` (default: `info`)
134+
- `METRICS_URL` / `METRICS_API_KEY` - Override the compiled-in metrics backend
135+
- `LARA_HOME` - Where the metrics installation id lives (default: `~/.lara`)
136+
- `DO_NOT_TRACK` - `1`/`true`/`yes` disables usage metrics entirely
134137

135138
### Error Handling
136139

@@ -145,17 +148,61 @@ Error handling in `src/mcp/tools.ts`:
145148
- Other unexpected errors are logged internally and returned as generic "An error occurred while processing your request" message
146149
- Privacy-sensitive translations (with `no_trace=true`) are logged for audit purposes
147150

151+
### Usage Metrics (`src/metrics.ts`)
152+
153+
Funnel telemetry for the `mcp` channel of the Lara integrations monitoring
154+
backend. Both transports report; the module is self-contained and its failures
155+
never reach a tool call.
156+
157+
- **Configuration**: `DEFAULT_METRICS_URL` / `DEFAULT_METRICS_API_KEY` are
158+
compiled in and overridden by `METRICS_URL` / `METRICS_API_KEY`. Either one
159+
empty, an unparseable URL, or `DO_NOT_TRACK` set to `1`/`true`/`yes` and
160+
`metricsEnabled()` is false — nothing is measured, queued or written to disk.
161+
- **Delivery**: `POST /auth/issue-token` (API key, `{ installationId }`) buys a
162+
token; events go to `POST /metrics/ingest-events` with it. Batched every 2s
163+
behind an `unref`'d timer, one retry on 401 only, requeued on 429/5xx, dropped
164+
on any other rejection, queue capped at 10 000. `flushNow()` is awaited once,
165+
on shutdown, capped at 2s.
166+
- **Installation id**: a UUID at `${LARA_HOME ?? ~/.lara}/installation-id`,
167+
resolved synchronously at module load. `writeFileSync(..., { flag: "wx" })`
168+
settles the race between two processes starting together. An unwritable home
169+
falls back to a per-process id. A freshly created file is what triggers the
170+
one `install` event.
171+
- **Events**: `install` (no `accountId`), `auth_success` (deduplicated per
172+
account per process — the HTTP transport is stateless and builds a Translator
173+
per request), `call_success`, `call_error`, and `auth_fail` beside a
174+
`call_error` Lara answered with 401/403.
175+
- **`accountId`**: the `id` claim of the Lara token the SDK holds, read through
176+
`getLaraClient()` in `src/lara-client.ts` (the one cast through the SDK's
177+
`protected client`). The SDK authenticates lazily, so it is only knowable
178+
after a call has succeeded. No token means no account and **no event** — a
179+
made-up id would be a fake account in every dashboard.
180+
- **`metadata`**: `feature` (`text` / `language_detection` /
181+
`resource_management` — a shared vocabulary, values are stable forever),
182+
`toolName`, `transport`, and `sourceLang`/`targetLang` on `translate`. Never
183+
the translated text, never a credential.
184+
- **Where it is emitted**: inside `CallTool`'s own try/catch, not from a wrapper
185+
around it — `CallTool` rewrites `LaraApiError` into `InvalidInputError` before
186+
it escapes, so only there is the original error still available to classify.
187+
148188
### Logging
149189

150190
The server uses Pino structured logging (`src/logger.ts`). Log level is controlled by `LOGGING_LEVEL` environment variable.
151191

152192
## Testing
153193

154194
Tests are located in `src/__tests__/` and mirror the source structure:
155-
- `tools/` - Individual tool tests (71 total tests)
156-
- `server/` - REST server tests
195+
- `tools/` - Individual tool tests
196+
- `server/` - REST server tests, plus `mcp.metrics.test.ts` for the metrics wiring
197+
- `metrics.test.ts` - The metrics client in isolation
157198
- `utils/mocks.ts` - Shared test utilities with Vitest mocks
158199

200+
The two metrics test files deliberately do **not** import `utils/mocks.ts`: its
201+
hoisted `vi.mock("@translated/lara")` would replace `LaraApiError`/`TimeoutError`,
202+
which `errorTypeFor` classifies with `instanceof`. They reset modules and
203+
re-import instead, because the queue, the token and the installation id are
204+
module state.
205+
159206
Tests use Vitest with coverage reporting (v8 provider).
160207

161208
## Security Features

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,50 @@ Then add to your MCP config:
234234

235235
---
236236

237+
## Usage Metrics
238+
239+
The server reports anonymous usage events to Lara so we can see how the
240+
integration is doing: an `install` the first time it runs, `auth_success` /
241+
`auth_fail` when your credentials are accepted or rejected, and a
242+
`call_success` / `call_error` per tool call.
243+
244+
**What is sent:** your Lara account id (`acc_...`), the tool name, the source
245+
and target language tags, how many characters were translated, how long the
246+
call took, and the server version.
247+
248+
**What is never sent:** the text you translate, your access key, your access
249+
key secret, and anything else that identifies you or your content.
250+
251+
An installation id (a random UUID, tied to nothing) is generated on first run
252+
and kept at `~/.lara/installation-id` — set `LARA_HOME` to move it. It exists
253+
so repeated runs of the same install count as one.
254+
255+
### Opting out
256+
257+
Set `DO_NOT_TRACK` to `1`, `true` or `yes` and nothing is measured, queued,
258+
written to disk or sent:
259+
260+
```json
261+
{
262+
"mcpServers": {
263+
"lara-translate": {
264+
"command": "npx",
265+
"args": ["-y", "@translated/lara-mcp@latest"],
266+
"env": {
267+
"LARA_ACCESS_KEY_ID": "<YOUR_ACCESS_KEY_ID>",
268+
"LARA_ACCESS_KEY_SECRET": "<YOUR_ACCESS_KEY_SECRET>",
269+
"DO_NOT_TRACK": "1"
270+
}
271+
}
272+
}
273+
}
274+
```
275+
276+
Metrics never block, slow down or fail a translation: events are batched, sent
277+
in the background, and a monitoring backend that is down is ignored.
278+
279+
---
280+
237281
## Support
238282

239283
- For issues with Lara Translate API: visit [Lara Translate Support](https://support.laratranslate.com)

0 commit comments

Comments
 (0)