Skip to main content
MCPulse installs a package inside a server you own and reads every tool call that server handles. That is a lot of trust, so the boundaries are worth stating plainly.

The package sits beside your traffic, not in it

MCPulse is not a proxy. It is an npm package inside your own process. This matters twice over. Directory-listed MCP servers cannot change their URL, and OAuth breaks if traffic is redirected — so a proxy would be unusable for exactly the servers most worth measuring. And because the package sits beside the traffic rather than in it, if MCPulse is down, your server keeps working. A failed flush costs a data point. It must never cost a tool call.

What never leaves your process

The rule is one sentence and it cannot be un-broken: sizes and hashes only, never arguments, never results. args_hash is twelve characters of a SHA-256 over the arguments with keys sorted. It exists for one purpose: telling whether two calls within 30 seconds used the same arguments, which is what separates a retry from ordinary pagination. It is deliberately too short to be brute-forced back into anything meaningful, and it is not reversible. There is no option that turns any of this off, in either direction. You cannot configure the SDK to send more, because a guarantee you can switch off is not a guarantee.

The three rules the package holds to

  1. Never throw. Every entry point is wrapped in a try/catch that swallows. If instrumentation cannot be attached, your server is handed back untouched and runs without analytics.
  2. Never block. Record, buffer, return. Nothing awaits the network on the path a model is waiting on. Ingest replies 202 before the write happens for the same reason.
  3. Never store customer data. The table above.
The buffer is capped at 1000 payloads and sheds the oldest first. If the network is down the batch is dropped rather than retried — your server running out of memory over our analytics is the one failure we must never cause.

Account isolation

Every request is scoped to the account derived from the credential:
  • An ingest key resolves to exactly one MCP. Telemetry cannot be posted into another account’s MCP by any value in the body, because no value in the body is read for that purpose.
  • A session token resolves to one account and one role. Requesting an MCP outside that account returns 404, not 403, so ids outside your account are not even confirmed to exist.
Underneath, every per-MCP table carries account_id directly and it is derived by a database trigger from the row’s own mcp_id — never taken from the caller. A row inserted claiming someone else’s account is corrected before it lands.

API key handling

  • Generated with 32 characters of cryptographic randomness, prefixed mp_live_.
  • Only a SHA-256 hash and the first 12 characters are stored. The raw key is returned once, at creation, and is unrecoverable afterwards.
  • Scoped to one MCP, named at creation, and revocable at any time from Installation or Keys.
  • Revoking clears the in-memory resolution cache immediately, so a revoked key stops working now rather than within five minutes.
See Authentication.

Roles

Three roles, account-wide. Reads are open to everyone in the account — an analytics product nobody on the team can read is not doing its job. Only writing needs rank. Every one of these is enforced in the API, not in the interface. Hiding a button is a courtesy; the guards are the enforcement, and anything talking to the API directly — including the MCP server — gets no such courtesy. The last owner cannot be demoted or removed, because an account with no owner cannot be administered by anyone. See Roles and permissions.

MCP access runs the same guards

The MCP endpoint registers 24 tools, and 10 of them write — 7 that change something and 3 that remove it. That is not a second permission surface: every one calls the same service function the REST route calls and hits the same guard, so a member is refused over MCP exactly as they are over HTTP. Two writes deliberately do not exist. There is no create_key, because the full key would land in a model’s context and from there in transcripts and provider logs. There is no delete_account, because a typed-name confirmation proves nothing when the model already read the name a moment earlier. See MCP tools.

Retention and deletion

Deletion is the database’s job. Every foreign key cascades:
  • Deleting one MCP removes its keys, its calls, its hourly counters, its sessions and its tool pairs. Its siblings are untouched.
  • Deleting the account removes every MCP in it and everything underneath, plus every teammate’s access, in a single statement.
There is no cleanup code and no soft delete. Deleting your account is owner-only and asks you to type the account name first. Raw per-call rows in calls exist so the nightly pass can walk them and so the live feed has something to show; every other figure comes from tool_hours, which stays small and aggregates correctly across any range. A cleanup of calls by your plan’s retention — 7, 90 or 365 days — is built and deliberately not switched on yet.

Rate limiting

Ingest is limited to 10,000 payloads per minute per key, counted in payloads rather than requests — a batch of 500 costs 500. Over the limit returns 429 with Retry-After. See Rate limits.

Questions and disclosure

For security questions or to report a vulnerability, open a private report on GitHub. Please don’t disclose a suspected vulnerability publicly before we have had a chance to respond.