Repository navigation
feat: send first-party analytics headers on Kosli API requests - #106
Conversation
The Kosli server can see that a request came from the MCP server, through the User-Agent, but not which tool or catalog action made it, or which requests belong to the same session. Steve Tooke asked for first-party headers so product analytics can count MCP usage per operation and per session, alongside the query events the server now records (kosli-dev/server#7728). Every Kosli API request now sends X-Kosli-Client: mcp/<version>. Requests from execute_read_action and execute_write_action also send X-Kosli-Operation: "<tool> <action id>" and X-Kosli-Invocation-Id, a UUID made once per createServer, so one per MCP session. The User-Agent is unchanged. Only names are sent: the action id is the catalog's public OpenAPI operationId, and no argument, param value, search query or prompt text goes into a header. search_actions makes no request, and the redirect download goes to an object store rather than Kosli, so neither carries them. The server treats every header as optional and falls back to the User-Agent, so this can ship before the server reads them and older releases keep working. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N9LH8PXUvMmSKYK9Mr65we
|
Claude finished @tooky's task in 2m 35s —— View job Review: analytics headers
No blocking findings. The change is tight and holds the repo's invariants:
Two minor notes, neither worth blocking on: a write or |
|
Thanks for the review. On the two minor notes:
Generated by Claude Code |
Requested by Steve Tooke · Slack thread
Before: the Kosli server can tell a request came from the MCP server (via the
User-Agent), but not which tool or catalog action made it, or which requests belong to the same MCP session.After: every Kosli API request carries first-party analytics headers naming the client, and requests from the execute tools also name the tool, the action and the session, so product analytics can count MCP usage per operation and per session.
X-Kosli-Clientmcp/<version>(fromsrc/version.ts, the same source as theUser-Agent)X-Kosli-Operation<tool> <action id>, e.g.execute_read_action get_trail,execute_write_action create_controlexecute_read_action/execute_write_actionX-Kosli-Invocation-IdcreateServer, so one per MCP sessionexecute_read_action/execute_write_actionUser-Agent: kosli-mcp-server/<version>is unchanged.search_actionssearches the bundled catalog and makes no request, so it sends nothing. The redirect download for evidence files goes to a pre-signed object-store URL, not Kosli, so it keeps only theUser-Agentas before.Why: Kosli's product analytics (Segment to Mixpanel, grouped by org) only see write events today, and can only tell "CLI" from "other" by the User-Agent. These headers let the server attribute each API call to a client, its version, the operation and one run or session, so we learn how customers actually use Kosli with the data they put in. A follow-up server change reads the headers and adds them to the events. Until then the server ignores them, and they are all optional, so older clients keep working and the server falls back to the User-Agent.
What the MCP headers will let us see:
execute_read_action get_trail.Tool and action names only, never arguments or param values, and the headers go only to the Kosli API.
Privacy: only names go in these headers: the tool name and the catalog action id, which is a public OpenAPI operationId. No tool argument, param value, org, search query or prompt text is ever put in a header. Tests send arguments containing a marker string through both execute tools and assert it appears in no header other than
Authorization.Compatibility: the server ignores these headers until it reads them, and treats each as optional with a fallback to the
User-Agent, so this can ship first and older releases keep working. Related: kosli-dev/server#7728 (server-side query events). There is no opt-out, by decision.Changes
src/client/kosli-client.ts:X-Kosli-Clienton every request;execute()takes an optionalRequestOrigin(tool,invocationId) that adds the other two.src/tools/execute-action.ts:ExecuteOptions.originpassed through to the client.src/server.ts: onerandomUUID()percreateServer; each execute tool passes its own name.CLAUDE.md: documents the headers and the names-only rule next to theUser-Agentconvention.test/client/kosli-client.test.tsandtest/server.test.ts(over the in-memory transport): header values, operation per tool, one invocation id per session and a new one per server, no arguments in any header, no request fromsearch_actions.npm run build,npm run typecheck,npm test(147 passed) and the offlinenpm run smokeall pass locally.Related PRs
X-Kosli-Cli-FlagsandX-Kosli-CiThe header names are shared across clients:
X-Kosli-Client,X-Kosli-OperationandX-Kosli-Invocation-Id. The CLI also sendsX-Kosli-Cli-FlagsandX-Kosli-Ci.Generated by Claude Code