Skip to main content
Wrap your MCP server with withSurfa and every tool call it handles shows up in Surfa: tool name, arguments (secrets masked), latency, errors and response size. You don’t write any code per tool.
For coding agents: follow the steps in order. The only code change is wrapping the McpServer (or low-level Server) constructor call with withSurfa(...) and adding one import. Don’t change tool handlers.

Set up with your coding agent

Open your server’s project in Claude Code, Cursor, Copilot, Codex or any other coding agent and paste this:
Prompt
Then put your real key (from Settings → API Keys) where the agent left the placeholder, and check it’s working (step 4 below). To do it by hand instead, follow the steps below.

Prerequisites

  • An MCP server written in TypeScript or JavaScript using @modelcontextprotocol/sdk 1.5.0 or later (McpServer or Server). Tested from 1.5.0 to 1.32.1.
  • Node.js 18 or later.
  • A Surfa account (sign up) and an ingest key from Settings → API Keys in the dashboard. Keys start with sk_live_.

Setup

1

Install the package

In your server’s project folder:
2

Wrap your server (the one line)

Find where your server is created and wrap it with withSurfa, before you call connect():
Tools registered before or after this line are all captured. If you use the low-level API, wrap that instead: withSurfa(new Server(...)).
If your HTTP server creates a new server for each session (a getServer() factory, as in the SDK’s Streamable HTTP examples), put withSurfa inside the factory. Each MCP session then becomes one Surfa session.
3

Set your API key

Set SURFA_API_KEY in the environment your server runs in:
For a stdio server started by Claude Desktop, Cursor or another client, add it to that client’s MCP config under env:
Use an absolute path: most clients don’t start the server from your project folder.For a hosted server, set it in your host’s secret settings (for example fly secrets set, or the Vercel or Railway dashboard).
A .env file only works if something loads it, such as dotenv or node --env-file=.env. Node doesn’t read it on its own, and a stdio server started by a client won’t see it unless your code loads it. Don’t commit the key.
4

Check it's working

Start your server and call two or three tools from your MCP client or MCP Inspector:
Within a minute, the calls appear in the Surfa dashboard:
  • Analytics shows the tools and their latency.
  • Sessions lists one session per MCP connection. Open it to see each tool call with its arguments, latency and any error.
You’re done when your tool calls show up under Sessions with the right tool names.

Configuration

If you don’t use environment variables, pass the same settings as options:

What’s captured

For each tool call:
  • Tool name
  • Arguments, with secrets masked on your server, before sending: any argument key containing password, passwd, secret, token, api_key/apikey, authorization, cookie, private_key, access_key or credential is replaced with [REDACTED], at any depth. Long strings are shortened.
  • Latency in milliseconds
  • Success or error, with the error message
  • Response size in bytes
For each MCP connection: a session, with the client’s name and version (for example claude-ai 0.1.0) as reported when it connects. Not captured:
  • Tool results. Only their size is sent.
  • Your server’s environment, headers or auth tokens.
  • Calls to resources and prompts (tool calls only, for now).
Surfa also redacts email addresses, phone numbers, card numbers, US Social Security numbers and key-like strings on its side before storing events. This is on by default and can be changed under Settings → Privacy. See Privacy and PII.

Your server is never affected

  • withSurfa only observes requests and responses; it never changes them.
  • Uploads run in the background, one at a time. If Surfa is slow, down or rate limiting, events are dropped; your tool calls aren’t delayed and nothing throws. At most maxQueueSize events are held in memory.
  • Overhead is a few microseconds per tool call.
  • Logs go to stderr only, so stdio servers keep a clean stdout.

Sessions and shutdown

A Surfa session ends when the MCP connection closes, or after 15 minutes without tool calls (the next call starts a new session). Surfa computes per-session metrics such as redundant calls and batch opportunities when a session ends. You don’t need to do anything for this in most setups:
  • Tool calls are uploaded about a quarter of a second after they happen, so little is ever waiting in memory.
  • When a client disconnects, the session ends and uploads straight away. This covers stdio clients closing the connection (Claude Desktop), Claude Code stopping the server with SIGINT, and containers stopped with SIGTERM. After uploading (at most one second), your process exits exactly as it would have without Surfa.
  • If a server is killed before it can say goodbye (a crash, kill -9, a frozen serverless function), Surfa ends the session itself after 15 minutes without activity.
If your server has its own SIGINT or SIGTERM handler, withSurfa leaves exiting to you. Add one line before you exit, so the session ends cleanly:

Remote and serverless servers

withSurfa works the same over stdio, Streamable HTTP and the older SSE transport.
  • Long-running HTTP servers (Fly, Railway, a VPS, Docker): if you create one server per MCP session, put withSurfa inside that factory function. Each MCP session becomes one Surfa session.
  • Stateless HTTP servers (a new server for every request): every tool call is recorded, but each call shows up as its own session, without the client’s name.
  • Serverless functions (Vercel, AWS Lambda, Netlify): the platform can freeze your function as soon as the response is sent, before the background upload runs. Upload before returning:
Cloudflare Workers haven’t been tested yet. surfa-ingest uses Node.js APIs, so it needs the nodejs_compat compatibility flag at least, and may not work.

Troubleshooting

  1. Check your server’s stderr for a line starting with [surfa]. SURFA_API_KEY is not set means the key isn’t reaching the server process. For stdio servers, put it in the MCP client’s env config, not your shell.
  2. Set debug: true in the options to log every upload.
  3. Make sure withSurfa(...) wraps the server before connect() is called on it.
  4. Sessions appear after the first tool call. Connecting and listing tools alone doesn’t create a session.
The warning says why, for example API key not found (key_not_found) or API key has been revoked (key_revoked), and shows the start of the key that was used. Make sure SURFA_API_KEY holds the full key shown once when you created it (the keys list only shows the first few characters). Create a new key under Settings → API Keys if needed, and restart your server: tracking stays off until the restart.