# API Reference
Source: https://docs.surfa.dev/api-reference
Complete API documentation for Surfa Ingest and Query APIs
## Overview
Surfa provides two main APIs:
1. **Ingest API** - Track events from your MCP server
2. **MCP Query API** - Query analytics with natural language
Both APIs use API key authentication with Bearer tokens.
***
## Ingest API
### POST /api/v1/ingest/events
Track events from your MCP server or application.
**Endpoint:**
```
POST https://surfa-web.vercel.app/api/v1/ingest/events
```
**Headers:**
```http theme={null}
Authorization: Bearer sk_live_your_key_here
Content-Type: application/json
```
**Request Body:**
```json theme={null}
{
"session_id": "string (required)",
"events": [
{
"kind": "string (required)",
"subtype": "string (required)",
// ... additional event fields
}
],
"execution_id": "string (optional)",
"runtime": {
"provider": "string (optional)",
"model": "string (optional)",
"mode": "string (optional)"
}
}
```
**Field Descriptions:**
| Field | Type | Required | Description |
| -------------- | ------ | -------- | ------------------------------------------------------------------------- |
| `session_id` | string | ✅ | Unique identifier for this session (e.g., `live_session_abc123`) |
| `events` | array | ✅ | Array of event objects to track |
| `execution_id` | string | ❌ | Execution ID (returned from first request, reuse for subsequent requests) |
| `runtime` | object | ❌ | Runtime metadata (provider, model, mode) |
**Event Object:**
| Field | Type | Required | Description |
| ----------------- | ------ | -------- | ----------------------------------------------------------- |
| `kind` | string | ✅ | Event category: `tool`, `session`, `runtime`, `error`, etc. |
| `subtype` | string | ✅ | Event type: `call_started`, `call_completed`, `error`, etc. |
| Additional fields | any | ❌ | Any custom fields (tool\_name, status, latency\_ms, etc.) |
**Response (Success):**
```json theme={null}
{
"ok": true,
"execution_id": "exec_abc123xyz",
"events_received": 1
}
```
**Response (Error):**
```json theme={null}
{
"ok": false,
"error": "Invalid API key"
}
```
**Status Codes:**
| Code | Meaning |
| ----- | ----------------------------------------- |
| `200` | Success - events tracked |
| `401` | Unauthorized - invalid or missing API key |
| `403` | Forbidden - API key revoked |
| `422` | Validation error - invalid payload |
| `429` | Rate limit exceeded |
| `500` | Server error |
***
## Example Requests
### Track a Tool Call
```bash theme={null}
curl -X POST https://surfa-web.vercel.app/api/v1/ingest/events \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"session_id": "live_session_abc123",
"events": [
{
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search",
"status": "success",
"latency_ms": 245
}
]
}'
```
### Track Multiple Events
```bash theme={null}
curl -X POST https://surfa-web.vercel.app/api/v1/ingest/events \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"session_id": "live_session_abc123",
"execution_id": "exec_xyz789",
"events": [
{
"kind": "tool",
"subtype": "call_started",
"tool_name": "search",
"query": "AI agents"
},
{
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search",
"status": "success",
"latency_ms": 245,
"results_count": 10
}
]
}'
```
### Track with Runtime Info
```bash theme={null}
curl -X POST https://surfa-web.vercel.app/api/v1/ingest/events \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"session_id": "live_session_abc123",
"runtime": {
"provider": "anthropic",
"model": "claude-3-5-sonnet-20241022",
"mode": "mcp"
},
"events": [
{
"kind": "session",
"subtype": "started",
"client_id": "claude_desktop_v1"
}
]
}'
```
***
## Event Types
### Tool Events
Track MCP tool calls:
```json theme={null}
{
"kind": "tool",
"subtype": "call_started" | "call_completed" | "call_failed",
"tool_name": "string",
"status": "success" | "error",
"latency_ms": 123,
"error_message": "string (if failed)"
}
```
### Session Events
Track session lifecycle:
```json theme={null}
{
"kind": "session",
"subtype": "started" | "ended",
"client_id": "string",
"total_calls": 10
}
```
### Error Events
Track errors:
```json theme={null}
{
"kind": "error",
"subtype": "error",
"error_type": "string",
"error_message": "string",
"stack_trace": "string (optional)"
}
```
### Custom Events
Track anything:
```json theme={null}
{
"kind": "custom",
"subtype": "your_event_type",
"custom_field_1": "value",
"custom_field_2": 123
}
```
***
## MCP Query API
Used by the Surfa MCP Server to query analytics. You typically don't call these directly - use the MCP server instead.
### GET /api/v1/mcp/analytics/metrics
Get high-level analytics metrics.
**Headers:**
```http theme={null}
Authorization: Bearer sk_live_your_key
```
**Response:**
```json theme={null}
{
"ok": true,
"data": {
"totalSessions": 150,
"successRate": 85,
"avgExecutionTime": 245,
"activeSessions": 12
}
}
```
### GET /api/v1/mcp/analytics/events
Query events with filters.
**Query Parameters:**
| Parameter | Type | Description |
| ----------- | ------ | ------------------------------------ |
| `status` | string | Filter by status: `success`, `error` |
| `tool_name` | string | Filter by tool name |
| `limit` | number | Max results (default: 100) |
**Response:**
```json theme={null}
{
"ok": true,
"data": {
"events": [...],
"total": 150
}
}
```
### GET /api/v1/mcp/analytics/sessions/:sessionId
Get all events for a specific session.
**Response:**
```json theme={null}
{
"ok": true,
"data": {
"session_id": "live_session_abc123",
"status": "completed",
"events": [...],
"event_count": 10
}
}
```
***
## Rate Limits
### Ingest API
The Ingest API has rate limiting to prevent abuse:
* **Limit:** 1,000 events per minute per API key
* **Window:** 60 seconds (sliding window)
* **Response:** `429 Too Many Requests` when exceeded
**Rate Limit Response:**
```json theme={null}
{
"ok": false,
"error": "Rate limit exceeded"
}
```
The SDK automatically retries with exponential backoff when rate limited.
### MCP Query API
The MCP Query API currently has no rate limits. Use responsibly.
***
## Error Handling
### Authentication Errors (401, 403)
```json theme={null}
{
"ok": false,
"error": "Invalid API key"
}
```
**Don't retry** - fix your API key.
### Validation Errors (422)
```json theme={null}
{
"ok": false,
"error": "Missing required field: session_id"
}
```
**Don't retry** - fix your payload.
### Rate Limit Errors (429)
```json theme={null}
{
"ok": false,
"error": "Rate limit exceeded"
}
```
**Retry with exponential backoff.**
### Server Errors (500, 502, 503, 504)
```json theme={null}
{
"ok": false,
"error": "Internal server error"
}
```
**Retry with exponential backoff** (max 3 attempts).
***
## SDK vs Direct API
### Using the SDK (Recommended)
```python theme={null}
from surfa_ingest import SurfaClient
analytics = SurfaClient(
ingest_key="sk_live_your_key",
api_url="https://surfa-web.vercel.app"
)
analytics.track({
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search",
"status": "success"
})
```
**Benefits:**
* ✅ Automatic retries
* ✅ Buffering and batching
* ✅ Session management
* ✅ Error handling
* ✅ Runtime metadata
### Direct API Calls
Use direct API calls if:
* You're not using Python
* You need custom behavior
* You're building your own SDK
**Example (JavaScript):**
```javascript theme={null}
const response = await fetch('https://surfa-web.vercel.app/api/v1/ingest/events', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: 'live_session_abc123',
events: [{
kind: 'tool',
subtype: 'call_completed',
tool_name: 'search',
status: 'success'
}]
})
});
const data = await response.json();
console.log(data);
```
***
## Next Steps
Get your API key and set up authentication
Start tracking events in 5 minutes
Explore the Python SDK
Learn what data Surfa tracks
# Authentication
Source: https://docs.surfa.dev/authentication
Get your API key and start tracking events
## Get Your API Key
Authentication with Surfa is simple - you just need an API key.
Create a free account at [surfa.dev](https://surfa.dev)
Go to your [dashboard](https://surfa-web.vercel.app/dashboard)
Navigate to **Settings → API Keys** and click **"Create New Key"**
Your key will start with `sk_live_` - copy it and store it securely
**Success!** You now have your API key. Keep it secure and never commit it to Git.
## Using Your API Key
**Building your own integration?** See the [API Reference](/api-reference) for endpoint details, payload schemas, and request/response examples.
### For SDK (Tracking Events)
Use your API key when initializing the Surfa SDK:
```python theme={null}
from surfa_ingest import SurfaClient
import os
analytics = SurfaClient(
ingest_key=os.getenv("SURFA_INGEST_KEY"),
api_url=os.getenv("SURFA_API_URL", "https://surfa-web.vercel.app")
)
```
**Set as environment variable:**
```bash theme={null}
export SURFA_INGEST_KEY=sk_live_your_key_here
export SURFA_API_URL=https://surfa-web.vercel.app
```
Or add to your `.env` file:
```bash theme={null}
SURFA_INGEST_KEY=sk_live_your_key_here
SURFA_API_URL=https://surfa-web.vercel.app
```
### For MCP Server (Querying Analytics)
Add your API key to Claude Desktop config:
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
```json theme={null}
{
"mcpServers": {
"surfa": {
"command": "/path/to/uv",
"args": ["--directory", "/path/to/surfa-mcp", "run", "surfa-mcp"],
"env": {
"SURFA_API_KEY": "sk_live_your_key_here",
"SURFA_API_URL": "https://surfa-web.vercel.app"
}
}
}
}
```
## API Key Types
Surfa uses different key prefixes for different environments:
| Prefix | Environment | Use Case |
| ---------- | ----------- | --------------------------------- |
| `sk_live_` | Production | Live data, production MCP servers |
| `sk_test_` | Testing | Development and testing |
Test keys are isolated from production data. Use them for development and CI/CD.
## Security Best Practices
**Bad:**
```python theme={null}
# ❌ Don't do this!
analytics = SurfaClient(ingest_key="sk_live_abc123...")
```
**Good:**
```python theme={null}
# ✅ Use environment variables
analytics = SurfaClient(ingest_key=os.getenv("SURFA_INGEST_KEY"))
```
Add `.env` to your `.gitignore`:
```
.env
.env.local
```
Store keys in environment variables, not in code:
**Development:**
```bash theme={null}
# .env file
SURFA_INGEST_KEY=sk_live_your_key
```
**Production:**
Set environment variables in your deployment platform:
* Vercel: Settings → Environment Variables
* Railway: Variables tab
* Fly.io: `fly secrets set SURFA_INGEST_KEY=...`
Rotate your API keys periodically for security:
1. Create a new key in dashboard
2. Update environment variables
3. Deploy changes
4. Delete old key
Keep the old key active for 24 hours during rotation to avoid downtime.
Use different keys for different environments:
* **Production:** `sk_live_prod_abc123`
* **Staging:** `sk_live_staging_xyz789`
* **Development:** `sk_test_dev_123456`
This isolates data and makes it easier to track issues.
## Workspace Isolation
Each API key is tied to a specific workspace. This means:
* ✅ **Data isolation** - You only see your workspace's data
* ✅ **Multi-tenant safe** - No cross-workspace leakage
* ✅ **Team collaboration** - Share workspace access with team members
Want to track multiple projects? Create separate workspaces in your dashboard.
## Troubleshooting
**Possible causes:**
1. Key is incorrect or has typos
2. Key was deleted from dashboard
3. Using test key in production (or vice versa)
**Solution:**
* Verify key in dashboard
* Check environment variables are set correctly
* Ensure no extra spaces or quotes
**Possible causes:**
1. API key not included in request
2. Environment variable not set
3. Key expired or revoked
**Solution:**
```python theme={null}
# Verify key is loaded
import os
print(os.getenv("SURFA_INGEST_KEY")) # Should print your key
```
**Check:**
1. API key is correct
2. API URL is correct (`https://surfa-web.vercel.app`)
3. No firewall blocking outbound requests
4. Events are being tracked (check logs)
**Debug:**
```python theme={null}
import logging
logging.basicConfig(level=logging.DEBUG)
# You'll see API requests in logs
analytics.track({...})
```
## Rate Limits
The Ingest API is rate limited to prevent abuse:
* **1,000 events per minute** per API key
* **60 second sliding window**
If you exceed the rate limit, requests will return `429 Too Many Requests`. The SDK automatically retries with exponential backoff.
The MCP Query API (for Claude Desktop) currently has no rate limits.
## Next Steps
Start tracking events in 5 minutes
Complete API documentation with examples
Learn what data Surfa tracks
Query analytics with Claude Desktop
# Claude Desktop Setup
Source: https://docs.surfa.dev/claude-desktop-setup
Install and configure the Surfa MCP Server for Claude Desktop
Query your Surfa analytics with natural language through Claude Desktop.
## Prerequisites
* Claude Desktop installed ([download here](https://claude.ai/download))
* Surfa account with API key ([sign up at surfa.dev](https://surfa.dev))
* `uv` package manager ([install guide](https://docs.astral.sh/uv/getting-started/installation/))
Get your Surfa API key from your [dashboard](https://surfa-web.vercel.app/dashboard).
## Step 1: Install the MCP Server
Clone the Surfa MCP repository:
```bash theme={null}
git clone https://github.com/gamladz/surfa-mcp.git
cd surfa-mcp
```
Create a virtual environment and install dependencies:
```bash theme={null}
uv venv
uv pip install -e .
```
The `-e` flag installs in editable mode, so you can update the code later with `git pull`.
## Step 2: Find Your uv Path
You'll need the full path to `uv` for the Claude Desktop config.
**macOS/Linux:**
```bash theme={null}
which uv
```
**Common paths:**
* `/Users/yourname/.local/bin/uv` (macOS)
* `/home/yourname/.local/bin/uv` (Linux)
* `/opt/homebrew/bin/uv` (macOS with Homebrew)
Use the **full absolute path**, not just `uv`. Claude Desktop needs the complete path.
## Step 3: Configure Claude Desktop
Find your Claude Desktop config file:
**macOS:**
```bash theme={null}
~/Library/Application Support/Claude/claude_desktop_config.json
```
**Windows:**
```bash theme={null}
%APPDATA%\Claude\claude_desktop_config.json
```
**Linux:**
```bash theme={null}
~/.config/Claude/claude_desktop_config.json
```
Open the file and add the Surfa MCP server:
```json theme={null}
{
"mcpServers": {
"surfa": {
"command": "/Users/yourname/.local/bin/uv",
"args": [
"--directory",
"/Users/yourname/path/to/surfa-mcp",
"run",
"surfa-mcp"
],
"env": {
"SURFA_API_KEY": "sk_live_your_key_here",
"SURFA_API_URL": "https://surfa-web.vercel.app"
}
}
}
}
```
**Replace these values:**
* `/Users/yourname/.local/bin/uv` → Your actual `uv` path from Step 2
* `/Users/yourname/path/to/surfa-mcp` → Your actual clone directory
* `sk_live_your_key_here` → Your Surfa API key
### If You Have Other MCP Servers
If you already have MCP servers configured, just add Surfa to the existing `mcpServers` object:
```json theme={null}
{
"mcpServers": {
"existing-server": {
"command": "...",
"args": [...]
},
"surfa": {
"command": "/Users/yourname/.local/bin/uv",
"args": [
"--directory",
"/Users/yourname/path/to/surfa-mcp",
"run",
"surfa-mcp"
],
"env": {
"SURFA_API_KEY": "sk_live_your_key_here",
"SURFA_API_URL": "https://surfa-web.vercel.app"
}
}
}
}
```
## Step 4: Restart Claude Desktop
**Important:** You must completely quit and reopen Claude Desktop for changes to take effect.
**macOS:**
1. Cmd+Q to quit Claude Desktop
2. Reopen from Applications
**Windows:**
1. Right-click system tray → Quit
2. Reopen from Start menu
## Step 5: Verify Connection
Look for the 🔌 icon in the bottom-right corner of Claude Desktop.
Click it to see connected MCP servers. You should see **"surfa"** in the list.
## Step 6: Test Your First Query
Ask Claude:
```
Show me my Surfa analytics overview
```
You should get a response like:
```
Your Surfa analytics:
- Total Sessions: 35
- Success Rate: 85%
- Average Execution Time: 245ms
- Active Sessions: 2
```
**Success!** Your Surfa MCP Server is now connected and working.
## Using Remote MCP
Want to use a remote MCP instead of local?
### Option 1: Fly.io Proxy (Experimental)
```json theme={null}
{
"mcpServers": {
"surfa-remote": {
"command": "fly",
"args": ["mcp", "proxy", "https://surfa-mcp.fly.dev/sse"],
"env": {
"SURFA_API_KEY": "sk_live_your_key"
}
}
}
}
```
The `fly mcp proxy` command is experimental and may have issues. For production use, we recommend using the Surfa web platform to test remote MCPs.
### Option 2: Direct HTTP (Coming Soon)
Claude Desktop doesn't yet support direct HTTP MCP connections. Use the Surfa web platform instead.
Learn how to deploy and test remote MCPs
## Example Queries
Try these queries to explore your analytics:
```text Get Started theme={null}
"Show me my Surfa analytics overview"
```
```text Find Errors theme={null}
"Show me all errors from the last 24 hours"
```
```text Performance theme={null}
"What were the slowest queries this week?"
```
```text Deep Dive theme={null}
"Analyze my product health and give me recommendations"
```
## Troubleshooting
**Check:**
1. Config file path is correct
2. JSON syntax is valid (use [jsonlint.com](https://jsonlint.com))
3. No trailing commas in JSON
4. File saved after editing
**Debug:**
Check Claude Desktop logs:
```bash theme={null}
# macOS
tail -f ~/Library/Logs/Claude/mcp*.log
# Windows
type %APPDATA%\Claude\Logs\mcp*.log
```
**Check:**
1. `uv` path is correct (run `which uv` again)
2. Surfa MCP directory path is absolute, not relative
3. API key is correct (check dashboard)
4. Internet connection is working
**Test manually:**
```bash theme={null}
cd /path/to/surfa-mcp
export SURFA_API_KEY=sk_live_your_key
export SURFA_API_URL=https://surfa-web.vercel.app
uv run surfa-mcp
```
If this works, the issue is in your Claude Desktop config.
**Check:**
1. You have events in your Surfa dashboard
2. API key has access to the workspace
3. Events are from "live" source (not test data)
**Verify:**
Go to [your dashboard](https://surfa-web.vercel.app/dashboard) and confirm you see events.
**Solution:**
Reinstall dependencies:
```bash theme={null}
cd /path/to/surfa-mcp
uv pip install -e .
```
Then restart Claude Desktop.
**Common mistakes:**
* Trailing comma after last item
* Missing quotes around strings
* Backslashes in Windows paths (use forward slashes or double backslashes)
**Fix:**
Copy your config to [jsonlint.com](https://jsonlint.com) to validate.
**Example of valid JSON:**
```json theme={null}
{
"mcpServers": {
"surfa": {
"command": "/path/to/uv",
"args": ["--directory", "/path/to/surfa-mcp", "run", "surfa-mcp"],
"env": {
"SURFA_API_KEY": "sk_live_key",
"SURFA_API_URL": "https://surfa-web.vercel.app"
}
}
}
}
```
## Advanced Configuration
### Using Production API
If you're using the production Surfa API (not localhost):
```json theme={null}
{
"env": {
"SURFA_API_KEY": "sk_live_your_key_here",
"SURFA_API_URL": "https://surfa.dev"
}
}
```
### Custom Timeout
Set a custom timeout (in seconds):
```json theme={null}
{
"env": {
"SURFA_API_KEY": "sk_live_your_key_here",
"SURFA_API_URL": "https://surfa-web.vercel.app",
"SURFA_TIMEOUT": "60"
}
}
```
### Multiple Workspaces
Use different API keys for different workspaces:
```json theme={null}
{
"mcpServers": {
"surfa-production": {
"command": "/path/to/uv",
"args": ["--directory", "/path/to/surfa-mcp", "run", "surfa-mcp"],
"env": {
"SURFA_API_KEY": "sk_live_prod_key",
"SURFA_API_URL": "https://surfa.dev"
}
},
"surfa-staging": {
"command": "/path/to/uv",
"args": ["--directory", "/path/to/surfa-mcp", "run", "surfa-mcp"],
"env": {
"SURFA_API_KEY": "sk_live_staging_key",
"SURFA_API_URL": "https://staging.surfa.dev"
}
}
}
}
```
## Updating the MCP Server
To update to the latest version:
```bash theme={null}
cd /path/to/surfa-mcp
git pull
uv pip install -e .
```
Then restart Claude Desktop.
## Uninstalling
To remove the Surfa MCP Server:
1. Remove the `"surfa"` entry from `claude_desktop_config.json`
2. Restart Claude Desktop
3. Optionally delete the cloned directory:
```bash theme={null}
rm -rf /path/to/surfa-mcp
```
## Next Steps
Learn about all 4 analytics tools and example queries
Complete API documentation with examples
Start tracking events in 5 minutes
View source and contribute
## Need Help?
Get help from the community and Surfa team
# Introduction
Source: https://docs.surfa.dev/introduction
Observability for AI Agents and MCP Servers
## What is Surfa?
Surfa is the easiest way to understand what's happening inside your AI agents and MCP servers.
**Stop debugging logs. Start asking questions.**
Instead of digging through scattered logs, just ask Claude:
* "What's my success rate this week?"
* "Find all errors from yesterday"
* "Which tools are slowest?"
Get instant answers powered by your real-time analytics.
## The Problem
Building AI agents? You're probably:
* 🔍 **Debugging scattered logs** across multiple tools
* 📊 **Manually tracking metrics** in spreadsheets
* ❓ **Guessing why tools fail** without visibility
* ⏰ **Wasting hours** investigating issues
## The Solution
Add a few lines of code. Get instant insights.
```python theme={null}
from surfa_ingest import SurfaClient
analytics = SurfaClient(ingest_key="sk_live_...")
analytics.track({
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search",
"status": "success"
})
```
Then ask Claude: **"Show me my analytics overview"** → Get instant answers with real data.
## How It Works
Add the Surfa SDK to your MCP server (2 lines of code)
Events are auto-captured in real-time to your dashboard
Ask questions in natural language through Claude Desktop
Get AI-powered analysis, recommendations, and alerts
## Quick Start
Get Surfa running in 5 minutes with our step-by-step guide.
Query your analytics with natural language through Claude.
## Two Products
Track events from your MCP server. Auto-capture sessions, latency, errors, and runtime metadata.
Query your analytics with natural language through Claude Desktop. No dashboards needed.
## Why Surfa?
No complex logging infrastructure. No data pipelines. Just add the SDK and you're done.
Built specifically for MCP patterns. Understands sessions, tools, and agent workflows out of the box.
Events appear in your dashboard instantly. No waiting for batch jobs or ETL.
Ask questions in natural language. Get insights and recommendations from AI that understands your data.
## Perfect For
Track adoption, success rates, and user behavior without SQL.
Debug faster with session replay and error tracking.
Monitor performance, latency, and uptime in real-time.
## Example Queries
See what you can ask Claude with the Surfa MCP Server:
```text Analytics Overview theme={null}
"Show me my analytics overview"
```
```text Error Investigation theme={null}
"Show me all errors from the last 24 hours"
```
```text Performance Analysis theme={null}
"What were the slowest queries this week?"
```
```text Session Deep-Dive theme={null}
"Show me details for session abc123"
```
**Response Format:**
All tools return JSON for easy parsing by AI agents:
```json theme={null}
{
"ok": true,
"data": {
"totalSessions": 150,
"successRate": 85,
"avgExecutionTime": 245,
"activeSessions": 12
}
}
```
## Multi-Query Workflows
The PM Agent can chain queries together for complex analysis:
**Example workflow:**
1. **Get analytics** → sees low success rate (54%)
2. **Query events** with `status=error` → finds "Database unavailable" errors
3. **Analyze patterns** → identifies Feb 21-22 outage
4. **Provide recommendations** → Add health checks, circuit breakers
**All in seconds. No dashboards. No SQL.**
## Ready to Start?
Follow our quickstart guide and be up and running in 5 minutes.
***
Star us on GitHub and contribute to the project.
Get help, share feedback, and connect with other users.
# MCP Server Overview
Source: https://docs.surfa.dev/mcp-server-overview
Query your Surfa analytics with natural language
## What is the Surfa MCP Server?
The Surfa MCP Server lets you query your analytics data using natural language through Claude Desktop or any MCP-compatible client.
**Instead of dashboards, just ask:**
* "What's my success rate this week?"
* "Find all errors from yesterday"
* "Which tools are slowest?"
Get instant answers powered by your real-time analytics data.
## How It Works
Clone and install the Surfa MCP Server
Add your API key to Claude Desktop config
Ask questions in natural language
Get AI-powered insights and recommendations
## Features
Ask questions in plain English. No SQL, no dashboards.
Core analytics + V2 cost optimization tools with multi-model estimates.
Structured data optimized for AI agent consumption.
Chain queries together for complex analysis.
## Remote vs Local MCP
### Local MCP (STDIO)
* Runs on your machine
* Fast, low latency
* Claude Desktop only
* No deployment needed
### Remote MCP (HTTP/SSE)
* Deployed to cloud (Fly.io, Railway, etc.)
* Accessible from anywhere
* Web applications supported
* Requires deployment
Learn how to deploy your MCP to production with HTTP/SSE transport
## Available Tools
### Core Analytics (4 tools)
#### 1. `get_analytics`
Get high-level metrics about your MCP server.
**Example queries:**
* "Show me my analytics overview"
* "What's my success rate?"
* "How many sessions do I have?"
**Returns:**
```json theme={null}
{
"ok": true,
"data": {
"totalSessions": 150,
"successRate": 85,
"avgExecutionTime": 245,
"activeSessions": 12
}
}
```
***
#### 2. `query_events`
Filter and search through your events.
**Example queries:**
* "Show me all errors from the last 24 hours"
* "Find tool calls with latency over 1000ms"
* "Get all session events from yesterday"
**Parameters:**
* `tool_name` - Filter by tool name
* `min_latency` / `max_latency` - Latency range in ms
* `start_date` / `end_date` - ISO 8601 timestamps
* `kind` - Event kind (tool, session, runtime)
* `status` - Event status (success, error)
* `limit` - Max results (default: 100)
***
#### 3. `find_highest_latency`
Find your slowest queries.
**Example queries:**
* "What were the slowest queries this week?"
* "Show me the top 5 slowest tool calls today"
**Parameters:**
* `time_range` - hour, day, week, or month
* `tool_name` - Optional: filter by specific tool
* `limit` - Number of results (default: 10)
***
#### 4. `get_session`
Deep-dive into a specific session.
**Example queries:**
* "Show me details for session abc123"
* "What happened in session xyz789?"
**Parameters:**
* `session_id` - The session ID to retrieve
**Returns:**
```json theme={null}
{
"ok": true,
"data": {
"session_id": "abc123",
"status": "completed",
"started_at": "2026-03-04T12:00:00Z",
"completed_at": "2026-03-04T12:05:00Z",
"runtime": {
"provider": "anthropic",
"model": "claude-3-5-sonnet",
"mode": "stdio"
},
"events": [...],
"event_count": 15
}
}
```
***
### V2 Analytics - Cost Optimization (4 tools)
#### 5. `find_redundant_executions`
Find executions with redundant tool calls that waste tokens and costs.
**Example queries:**
* "Which sessions have redundant tool calls?"
* "Show me executions wasting the most money on duplicate calls"
* "Find redundancy from the last month"
**Parameters:**
* `time_range` - hour, day, week, or month (default: week)
* `min_redundancy` - Minimum redundancy score 0.0-1.0 (default: 0.3)
* `limit` - Max results (default: 20)
**Returns:**
```json theme={null}
{
"ok": true,
"data": {
"total_found": 20,
"total_potential_savings": {
"calls_saved": 327,
"tokens_estimated": 49050,
"cost_usd": {
"gpt_4_turbo": 0.00049,
"claude_sonnet": 0.00015
}
},
"executions": [...]
}
}
```
***
#### 6. `find_batch_opportunities`
Find tools called multiple times per session that could benefit from batch endpoints.
**Example queries:**
* "Which tools should I add batch endpoints for?"
* "What's my biggest batching opportunity?"
* "Show me tools that could be batched"
**Parameters:**
* `time_range` - hour, day, week, or month (default: week)
* `min_call_count` - Minimum calls per execution (default: 3)
* `limit` - Max results (default: 20)
**Returns:**
```json theme={null}
{
"ok": true,
"data": {
"total_opportunities": 5,
"opportunities": [
{
"tool_name": "get_user",
"execution_count": 15,
"total_calls": 78,
"potential_savings": {
"calls_saved": 63,
"tokens_estimated": 9450,
"cost_usd": {...}
},
"recommendation": "Add batch_get_user endpoint"
}
]
}
}
```
***
#### 7. `get_tool_insights`
Get comprehensive performance insights for a specific tool.
**Example queries:**
* "How is get\_user performing?"
* "Analyze the search\_database tool"
* "Show me insights for my slowest tool"
**Parameters:**
* `tool_name` - Name of the tool to analyze (required)
* `time_range` - hour, day, week, or month (default: week)
**Returns:**
```json theme={null}
{
"ok": true,
"data": {
"tool_name": "get_user",
"usage": {
"execution_count": 45,
"total_calls": 120,
"avg_calls_per_execution": 2.7
},
"redundancy": {
"avg_redundancy_rate": 0.25,
"potential_savings": {...}
},
"latency": {
"mean_ms": 245,
"p95_ms": 890,
"max_ms": 1200
},
"batch_potential": {...},
"recommendations": [
"Consider caching: 25% of calls are redundant",
"Batch potential: 15 executions make multiple calls"
]
}
}
```
***
#### 8. `get_execution_metrics`
Get detailed V2 metrics for a specific execution (enhanced version of `get_session`).
**Example queries:**
* "Show me everything about execution abc123"
* "Debug execution xyz789 with full metrics"
* "Get V2 metrics for the worst redundancy offender"
**Parameters:**
* `execution_id` - The execution ID to retrieve (required)
**Returns:**
```json theme={null}
{
"ok": true,
"data": {
"execution_id": "abc123",
"session_outcome": "complete",
"metrics_v2": {
"redundancy": {
"score": 0.45,
"redundant_calls": 12,
"potential_savings": {...}
},
"batch_opportunities": [...],
"latency": {
"mean_ms": 340,
"slowest_tool": "search_database"
}
},
"total_optimization_potential": {
"calls_saved": 15,
"tokens_estimated": 2250,
"cost_usd": {...}
}
}
}
```
***
## Multi-Query Workflows
The real power comes from chaining queries together. Claude can:
1. **Get analytics** → sees low success rate
2. **Query events** → finds error patterns
3. **Analyze** → provides recommendations
**Example:**
```
You: "Analyze my product health"
Claude:
1. Calls get_analytics() → sees 54% success rate
2. Calls query_events(status="error") → finds "Database unavailable"
3. Analyzes patterns → identifies Feb 21-22 outage
4. Provides recommendations:
- Add database health checks
- Implement circuit breakers
- Separate metrics by time window
```
**All in seconds. No dashboards. No SQL.**
## Use Cases
**Track product metrics without SQL:**
* "What's my weekly active users trend?"
* "Which features are most used?"
* "What's causing the drop in success rate?"
Get insights in natural language, share with team instantly.
**Debug faster:**
* "Show me all errors in the last hour"
* "Which tool is failing most often?"
* "What happened in session abc123?"
Skip log diving, get straight to the issue.
**Monitor performance:**
* "What were the slowest queries today?"
* "Show me latency trends this week"
* "Are there any performance regressions?"
Proactive monitoring through conversation.
**Autonomous analytics:**
Build PM agents that automatically:
* Monitor metrics
* Detect anomalies
* Investigate issues
* Generate reports
JSON responses make it easy for agents to consume.
## Why Use the MCP Server?
Query analytics without leaving Claude. No switching to dashboards.
Ask questions like you would a teammate. No learning query languages.
Claude analyzes the data and provides recommendations automatically.
JSON responses perfect for building autonomous PM agents.
## Getting Started
Follow our step-by-step guide to install and configure the Surfa MCP Server.
## Example Queries
```text Analytics theme={null}
"Show me my analytics overview"
"What's my success rate this week?"
"How many active sessions do I have?"
```
```text Errors theme={null}
"Show me all errors from yesterday"
"Find errors with 'timeout' in the message"
"Which tool has the most errors?"
```
```text Performance theme={null}
"What were the slowest queries today?"
"Show me tool calls over 1 second"
"Find performance regressions this week"
```
```text Sessions theme={null}
"Show me session abc123"
"What happened in the last failed session?"
"Get details for session xyz789"
```
```text Cost Optimization theme={null}
"Which sessions have redundant tool calls?"
"Show me the biggest batching opportunity"
"How much money could I save by optimizing?"
"Compare GPT-4 vs Claude Sonnet costs"
```
## Architecture
```
You (Natural Language)
↓
Claude Desktop
↓
Surfa MCP Server (Python)
↓
Surfa API (HTTP + Bearer Auth)
↓
Your Analytics Data
```
**Security:**
* API key stored locally in Claude Desktop config
* Never exposed to Claude
* Workspace-scoped (you only see your data)
## Next Steps
Install and configure the MCP server
Complete API documentation with examples
Start tracking events in 5 minutes
View source code and contribute
# Testing MCP Connections
Source: https://docs.surfa.dev/mcp-testing
Test and validate your MCP server connections
## Overview
The Surfa platform includes a built-in connection tester to validate your MCP setup before creating test scenarios.
## Using the Test Connection Feature
### 1. Navigate to MCP Settings
Go to **Settings → MCP** in your Surfa workspace.
### 2. Add Your MCP
Click **Add New MCP** and fill in:
* **Name:** Descriptive name (e.g., "Production Surfa MCP")
* **Transport:** SSE, STDIO, or Streamable HTTP
* **Endpoint:** Your MCP URL (e.g., `https://surfa-mcp.fly.dev/sse`)
* **Headers:** Authentication headers (optional)
### 3. Click "Test Connection"
The platform will:
1. Send an `initialize` request
2. Send a `tools/list` request
3. Parse the response
4. Display discovered tools
## What Happens During Testing
### For SSE Transport
POST /sse with initialize request
Read SSE stream for response
POST /sse with tools/list request
Parse tool definitions
Display results in UI
### Success Response
```
✅ Connection successful!
Discovered 4 tools:
- get_analytics
- query_events
- find_highest_latency
- get_session
```
### Retry Behavior
If the server returns `202 Accepted` (machine starting):
Wait 9 seconds, try again
Wait 9 seconds, try again
Wait 9 seconds, try again
You'll see progress messages:
```
Retry attempt 1 of 3... (waiting for server to start)
Retry attempt 2 of 3... (waiting for server to start)
✅ Connection successful!
```
## Common Test Results
### ✅ Success
**Message:** "Connection successful!"
**Tools:** List of discovered tools displayed
**Next:** Create test scenarios
***
### ⚠️ Cold Start (202)
**Message:** "Retry attempt X of 3..."
**Action:** Wait for retries to complete
**Duration:** Up to 27 seconds
***
### ❌ Timeout
**Message:** "Machine is starting up but taking longer than expected"
**Cause:** Server not responding after 27 seconds
**Fix:**
* Check server is running
* Increase timeout
* Configure always-on
***
### ❌ Unauthorized (401)
**Message:** "Authentication failed"
**Cause:** Invalid API key or missing headers
**Fix:**
* Verify API key
* Check Authorization header format
* Ensure key has correct permissions
***
### ❌ Not Found (404)
**Message:** "Endpoint not found"
**Cause:** Wrong URL or path
**Fix:**
* Verify endpoint URL
* Check transport type matches
* Test with curl first
***
### ❌ No Tools Found
**Message:** "Connection successful but no tools discovered"
**Cause:** `tools/list` returned empty array
**Fix:**
* Check MCP server has tools defined
* Verify tools are registered
* Test `tools/list` manually
## Manual Testing with curl
### Test Initialize
```bash theme={null}
curl -X POST https://your-mcp.fly.dev/sse \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "test-init",
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "test-client", "version": "1.0.0"}
}
}'
```
### Test Tools List
```bash theme={null}
curl -X POST https://your-mcp.fly.dev/sse \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "test-tools",
"method": "tools/list",
"params": {}
}'
```
### Expected Response
```json theme={null}
{
"jsonrpc": "2.0",
"id": "test-tools",
"result": {
"tools": [
{
"name": "get_analytics",
"description": "Get analytics metrics",
"inputSchema": {
"type": "object",
"properties": {},
"required": []
}
}
]
}
}
```
## Debugging Failed Tests
### Enable Debug Logging
**In Surfa Platform:**
Check browser console (F12) for detailed logs:
```
Initialize response: {...}
Tools/list response status: 200
Found tools in response: [...]
```
**In Your MCP Server:**
```python theme={null}
import logging
logging.basicConfig(level=logging.DEBUG)
```
### Check Server Logs
**Fly.io:**
```bash theme={null}
fly logs --app your-app
```
**Local:**
```bash theme={null}
tail -f server.log
```
### Verify Network
**Test connectivity:**
```bash theme={null}
ping your-mcp.fly.dev
curl -I https://your-mcp.fly.dev
```
**Check firewall:**
* Ensure port 443 (HTTPS) is open
* Verify no VPN blocking requests
## Best Practices
Validate your MCP works on localhost before deploying
Test manually with curl to isolate issues
Review server logs for detailed error messages
Test without auth first, then add complexity
## Troubleshooting Guide
**Possible causes:**
* Server is starting (cold start)
* Network issues
* Firewall blocking requests
**Solutions:**
* Wait for retry logic to complete
* Check server status: `fly status`
* Test with curl to verify connectivity
**Possible causes:**
* MCP server not returning tools
* Wrong endpoint path
* Tools not registered
**Solutions:**
* Test `tools/list` manually with curl
* Verify endpoint URL is correct
* Check server logs for errors
**Possible causes:**
* Invalid API key
* Wrong header format
* Missing Authorization header
**Solutions:**
* Verify API key is correct
* Use format: `Bearer sk_live_...`
* Check headers are set in MCP config
**Possible causes:**
* Invalid JSON response
* Server returning HTML instead of JSON
* CORS issues
**Solutions:**
* Check server is returning valid JSON
* Verify Content-Type header
* Enable CORS if needed
## Next Steps
Deploy your MCP to production
Build test cases for your MCP
Connect Claude Desktop to your MCP
Complete API documentation
# Privacy & PII
Source: https://docs.surfa.dev/privacy-and-pii
What data Surfa tracks and how to protect user privacy
## Privacy-First Design
Surfa is built with privacy as a core principle. We help you track analytics **without collecting personal information**.
**TL;DR:** Surfa tracks technical metrics (latency, errors, tool names) but **never** user prompts, responses, or personal data by default.
## What We Track
### ✅ We DO Track
**Technical Metrics:**
* Tool names (e.g., "search", "get\_weather")
* Event types (tool\_call, session\_started, etc.)
* Status (success, error)
* Latency in milliseconds
* Timestamps
**Session Data:**
* Session IDs (random UUIDs, not linked to users)
* Request IDs (for correlation)
* Client IDs (MCP client identifier)
**Runtime Metadata:**
* Provider (e.g., "anthropic", "openai")
* Model (e.g., "claude-3-5-sonnet")
* Mode (stdio, sse, http)
**Example of what we track:**
```json theme={null}
{
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search",
"status": "success",
"latency_ms": 245,
"session_id": "uuid-random-123",
"timestamp": "2026-03-04T12:00:00Z"
}
```
### ❌ We DON'T Track
**User Content:**
* ❌ User prompts or queries
* ❌ Tool responses or outputs
* ❌ File contents
* ❌ API keys or credentials
* ❌ Personal identifiable information (PII)
**Sensitive Data:**
* ❌ Email addresses
* ❌ Names
* ❌ IP addresses (beyond basic geolocation)
* ❌ Device identifiers
* ❌ Cookies or tracking pixels
## User Opt-Out
Users can disable Surfa tracking entirely via environment variable:
```bash theme={null}
export SURFA_DISABLE_TRACKING=true
```
When set, the SDK will:
* ✅ Not send any events to Surfa
* ✅ Not make any network requests
* ✅ Fail silently (no errors)
* ✅ Continue to work normally otherwise
Respect user privacy by documenting this opt-out option in your MCP server's README.
## What You Control (MCP Builders)
As an MCP builder, **you decide what data to track**. Here's how to do it responsibly:
### ✅ Good Practices
**Track technical metrics only:**
```python theme={null}
from surfa_ingest import SurfaClient
analytics = SurfaClient(ingest_key="sk_live_...")
# ✅ Good - no PII
analytics.track({
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search",
"status": "success",
"latency_ms": 245
})
```
**Track aggregated data:**
```python theme={null}
# ✅ Good - counts, not content
analytics.track({
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search",
"results_count": 10, # How many results, not what they are
"status": "success"
})
```
**Track error types, not messages:**
```python theme={null}
# ✅ Good - error type only
analytics.track({
"kind": "tool",
"subtype": "call_failed",
"tool_name": "database_query",
"error_type": "DatabaseUnavailable", # Type, not message
"status": "error"
})
```
### ❌ Bad Practices
**Don't track user input:**
```python theme={null}
# ❌ Bad - contains user's query
analytics.track({
"kind": "tool",
"tool_name": "search",
"query": "user's private search query", # Don't do this!
"status": "success"
})
```
**Don't track tool outputs:**
```python theme={null}
# ❌ Bad - contains response data
analytics.track({
"kind": "tool",
"tool_name": "get_user_info",
"response": {"name": "John Doe", "email": "..."}, # Don't do this!
"status": "success"
})
```
**Don't track error messages with PII:**
```python theme={null}
# ❌ Bad - error message might contain PII
analytics.track({
"kind": "tool",
"tool_name": "send_email",
"error_message": "Failed to send to john@example.com", # Don't do this!
"status": "error"
})
```
## GDPR Compliance
Surfa is designed to be GDPR-compliant out of the box:
We only collect the minimum data needed for analytics:
* Technical metrics (latency, errors)
* Session correlation (random UUIDs)
* No personal data by default
Users can opt-out at any time:
```bash theme={null}
export SURFA_DISABLE_TRACKING=true
```
Document this in your MCP server's README.
Users can request data deletion:
1. Contact [support@surfa.dev](mailto:support@surfa.dev)
2. Provide workspace ID
3. Data deleted within 30 days
Or delete via dashboard: Settings → Delete Workspace
Each workspace is isolated:
* No cross-workspace data sharing
* Workspace-scoped API keys
* No data aggregation across workspaces
Data retention by tier:
* Free: 7 days
* Pro: 30 days
* Team: 90 days
* Enterprise: Custom (up to 1 year)
After retention period, data is automatically deleted.
## Best Practices for MCP Builders
### 1. Document What You Track
Add a privacy section to your MCP server's README:
````markdown theme={null}
## Privacy
This MCP server uses Surfa for analytics. We track:
- Tool usage (which tools are called)
- Success/error rates
- Performance metrics (latency)
We DO NOT track:
- Your prompts or queries
- Tool responses
- Any personal information
To disable tracking:
```bash
export SURFA_DISABLE_TRACKING=true
````
````
### 2. Sanitize Error Messages
Remove PII from error messages before tracking:
```python
def sanitize_error(error_msg: str) -> str:
"""Remove potential PII from error messages."""
# Remove email addresses
error_msg = re.sub(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', '[EMAIL]', error_msg)
# Remove URLs with potential tokens
error_msg = re.sub(r'https?://[^\s]+', '[URL]', error_msg)
return error_msg
# Use it
try:
result = call_api()
except Exception as e:
analytics.track({
"kind": "tool",
"subtype": "call_failed",
"tool_name": "api_call",
"error_type": type(e).__name__,
"error_message": sanitize_error(str(e)), # Sanitized
"status": "error"
})
````
### 3. Use Custom Fields Wisely
Only track what you need:
```python theme={null}
# ✅ Good - useful for debugging
analytics.track({
"kind": "tool",
"tool_name": "database_query",
"query_type": "SELECT", # Type, not actual query
"table_name": "users", # Table, not data
"row_count": 42, # Count, not content
"status": "success"
})
# ❌ Bad - too much detail
analytics.track({
"kind": "tool",
"tool_name": "database_query",
"sql": "SELECT * FROM users WHERE email='...'", # Don't do this!
"results": [{...}], # Don't do this!
"status": "success"
})
```
### 4. Respect User Preferences
Check for opt-out before tracking:
```python theme={null}
import os
def should_track() -> bool:
"""Check if tracking is enabled."""
return os.getenv("SURFA_DISABLE_TRACKING", "").lower() != "true"
if should_track():
analytics.track({...})
```
The Surfa SDK already handles this automatically. This is just for reference.
## Data Security
### In Transit
* ✅ All data encrypted with TLS 1.3
* ✅ HTTPS only (no HTTP)
* ✅ Certificate pinning
### At Rest
* ✅ Encrypted database storage
* ✅ Workspace-level isolation
* ✅ Role-based access control (RBAC)
### Access Control
* ✅ API key authentication
* ✅ Workspace-scoped permissions
* ✅ No cross-workspace access
## Compliance Certifications
Surfa is working towards:
* [ ] SOC 2 Type II
* [ ] GDPR compliance certification
* [ ] ISO 27001
Enterprise customers can request compliance documentation at [support@surfa.dev](mailto:support@surfa.dev)
## Transparency
We believe in transparency:
* ✅ **Open source MCP server** - [View on GitHub](https://github.com/gamladz/surfa-mcp)
* ✅ **Public SDK** - [View on GitHub](https://github.com/gamladz/surfa-ingest)
* ✅ **Clear documentation** - You're reading it!
* ✅ **No hidden tracking** - Only what you explicitly send
## Questions?
Email us with privacy questions
See exactly what we track
Learn about API key security
More tips for responsible tracking
## Summary
**Surfa is privacy-first:**
* No PII collected by default
* Users can opt-out anytime
* You control what data is tracked
* GDPR-compliant design
* Transparent and open source
**Remember:** Track technical metrics, not user content. When in doubt, don't track it!
# Quickstart
Source: https://docs.surfa.dev/quickstart
Get Surfa running in 5 minutes
Get Surfa up and running in 5 minutes. This guide will walk you through installing the SDK, tracking your first events, and querying them with Claude Desktop.
## Prerequisites
* Python 3.10 or higher
* A Surfa account (sign up at [surfa.dev](https://surfa.dev))
* Your Surfa API key
## Step 1: Install the SDK
Install the Surfa SDK in your MCP server project:
```bash theme={null}
pip install surfa-ingest
```
Or with `uv`:
```bash theme={null}
uv pip install surfa-ingest
```
## Step 2: Track Your First Event
Add Surfa to your MCP server code:
```python theme={null}
from surfa_ingest import SurfaClient
import os
# Initialize analytics client
analytics = SurfaClient(
ingest_key=os.getenv("SURFA_INGEST_KEY"),
api_url=os.getenv("SURFA_API_URL", "https://surfa-web.vercel.app")
)
# Set runtime info once at startup
analytics.set_runtime(
provider="anthropic",
model="claude-3-5-sonnet",
mode="stdio"
)
# Track events throughout your code
analytics.track({
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search",
"status": "success",
"latency_ms": 245
})
```
The SDK auto-captures `client_id` and `request_id` from MCP context when you pass `ctx` parameter. You don't need to extract them manually!
## Step 3: Configure Your API Key
Set your Surfa API key as an environment variable:
```bash theme={null}
export SURFA_INGEST_KEY=sk_live_your_key_here
export SURFA_API_URL=https://surfa-web.vercel.app
```
Or add to your `.env` file:
```bash theme={null}
SURFA_INGEST_KEY=sk_live_your_key_here
SURFA_API_URL=https://surfa-web.vercel.app
```
Get your API key from your [Surfa dashboard](https://surfa-web.vercel.app/dashboard).
## Step 4: Run Your MCP Server
Start your MCP server and trigger some tool calls:
```bash theme={null}
# Example with FastMCP
uv run fastmcp run server.py
```
Events will be automatically tracked and sent to Surfa in real-time.
## Step 5: View Your Analytics
Go to your [Surfa dashboard](https://surfa-web.vercel.app/dashboard) to see:
* Total sessions
* Success rate
* Average execution time
* Event timeline
* Error tracking
## Step 6: Test Your MCP Connection (Optional)
Want to validate your MCP is working correctly?
Go to **Settings → MCP** in Surfa
Click **Add New MCP**
Enter your MCP details (endpoint, transport, headers)
Click **Test Connection**
See discovered tools ✅
Learn how to test and debug MCP connections
## Next: Query with Natural Language (Optional)
Want to query your analytics by asking Claude questions in natural language?
Install the Surfa MCP Server to query analytics through Claude Desktop
**What you can do:**
* Ask "What's my success rate?" → Get instant answers
* "Find all errors from yesterday" → Filtered results
* "Analyze my product health" → AI-powered recommendations
**No dashboards. No SQL. Just conversation.**
## Example: Complete MCP Server
Here's a complete example of an MCP server with Surfa tracking:
```python theme={null}
from fastmcp import FastMCP, Context
from surfa_ingest import SurfaClient
import os
# Initialize analytics
analytics = SurfaClient(
ingest_key=os.getenv("SURFA_INGEST_KEY"),
api_url=os.getenv("SURFA_API_URL", "https://surfa-web.vercel.app")
)
# Set runtime info
analytics.set_runtime(
provider="mcp",
model="my-mcp-server",
mode="stdio"
)
# Initialize MCP server
mcp = FastMCP("my-mcp-server")
@mcp.tool()
def search(query: str, ctx: Context) -> str:
"""Search for information."""
# Track tool call start (ctx auto-extracts client_id, request_id)
analytics.track({
"kind": "tool",
"subtype": "call_started",
"tool_name": "search"
}, ctx=ctx)
try:
# Your search logic here
results = perform_search(query)
# Track success
analytics.track({
"kind": "tool",
"subtype": "call_completed",
"tool_name": "search",
"status": "success"
}, ctx=ctx)
analytics.flush()
return results
except Exception as e:
# Track error
analytics.track({
"kind": "tool",
"subtype": "call_failed",
"tool_name": "search",
"status": "error"
}, ctx=ctx)
analytics.flush()
raise
if __name__ == "__main__":
mcp.run()
```
## What's Tracked Automatically?
When you pass the `ctx` parameter, the Surfa SDK automatically extracts:
* ✅ **Client ID** - Identifies the MCP client (e.g., Claude Desktop) from MCP context
* ✅ **Request ID** - Tracks individual requests from MCP context
* ✅ **Timestamps** - When events occurred
* ✅ **Sequence Numbers** - Event ordering within a session
* ✅ **Runtime Metadata** - Provider, model, mode (via `set_runtime()`)
You need to provide:
* **kind** - Event kind (e.g., "tool", "session", "runtime")
* **subtype** - Event subtype (e.g., "call\_started", "call\_completed")
* **tool\_name** - Name of the tool being called
* **Custom fields** - Status, latency, error messages, etc.
## Next Steps
Learn how to track different types of events
Full guide to setting up Claude Desktop integration
Explore all MCP server tools for querying analytics
Tips for getting the most out of Surfa
## Troubleshooting
**Check:**
* API key is correct (`SURFA_INGEST_KEY`)
* API URL is set (`SURFA_API_URL`)
* Your MCP server is actually running
* No firewall blocking outbound requests
**Debug:**
```python theme={null}
import logging
logging.basicConfig(level=logging.DEBUG)
```
**Check:**
* Config file path is correct
* JSON syntax is valid (no trailing commas)
* `uv` path is correct (run `which uv` to find it)
* Surfa MCP directory path is absolute, not relative
**Debug:**
Check Claude Desktop logs:
```bash theme={null}
tail -f ~/Library/Logs/Claude/mcp*.log
```
**Solution:**
Make sure you installed the SDK in the correct environment:
```bash theme={null}
pip install surfa-ingest
# or
uv pip install surfa-ingest
```
Verify installation:
```bash theme={null}
pip list | grep surfa
```
## Need Help?
Browse the full documentation
Get help from the community
Report bugs or request features
Contact our support team
# Remote MCP Deployment
Source: https://docs.surfa.dev/remote-mcp-deployment
Deploy your MCP server to production with HTTP/SSE transport
## Overview
Deploy your MCP server to the cloud for remote access via HTTP/SSE transport. This enables:
* Access from anywhere (not just localhost)
* Integration with web applications
* Multi-user support
* Always-available analytics
## Deployment Options
### Fly.io (Recommended)
**Why Fly.io:**
* Built-in MCP support (`fly mcp wrap`)
* Automatic HTTPS
* Global edge network
* Free tier available
**Steps:**
1. **Install flyctl**
```bash theme={null}
curl -L https://fly.io/install.sh | sh
```
2. **Wrap your MCP server**
```bash theme={null}
fly mcp wrap --port 8080
```
3. **Deploy**
```bash theme={null}
fly deploy
```
4. **Get your endpoint**
```bash theme={null}
fly status
# Your MCP is now at: https://your-app.fly.dev/sse
```
Fly.io machines auto-stop after 5 minutes of inactivity. The Surfa platform handles cold starts automatically with retry logic (3 attempts × 9 seconds).
### Other Platforms
**Railway, Render, Heroku:**
* Use HTTP/SSE transport
* Expose port 8080
* Set environment variables
* Enable HTTPS
## Transport: SSE vs STDIO
### STDIO (Local Only)
**Pros:**
* ✅ Fast, low latency
* ✅ Simple setup
**Cons:**
* ❌ Only works on localhost
* ❌ Can't be accessed remotely
### SSE (Remote-Ready)
**Pros:**
* ✅ Works over HTTP/HTTPS
* ✅ Accessible from anywhere
* ✅ Web-compatible
**Cons:**
* ⚠️ Slightly higher latency
* ⚠️ May have cold starts
**When to use SSE:**
* Deploying to cloud platforms
* Building web applications
* Multi-user scenarios
* Remote access needed
## Authentication
### API Key via Headers
```bash theme={null}
curl -X POST https://your-mcp.fly.dev/sse \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}'
```
### In Surfa Platform
The platform automatically handles authentication when you add your MCP with headers:
```json theme={null}
{
"Authorization": "Bearer sk_live_your_key"
}
```
## Cold Start Handling
Remote MCPs may "sleep" after inactivity. The Surfa platform handles this automatically:
Returns 202 (machine starting)
Wait 9 seconds, try again
Wait 9 seconds, try again
Wait 9 seconds, try again
Tools discovered and displayed
**Total wait time:** Up to 27 seconds for cold starts.
**To avoid cold starts:**
* Keep machines always-on (costs more)
* Use health check pings
* Accept the retry delay
## Testing Your Deployment
### 1. Test with curl
```bash theme={null}
curl -X POST https://your-mcp.fly.dev/sse \
-H "Authorization: Bearer sk_live_your_key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/list","params":{}}'
```
### 2. Test in Surfa Platform
Go to **Settings → MCP**
Click **Add New MCP**
* Name: "My Remote MCP"
* Transport: SSE
* Endpoint: `https://your-mcp.fly.dev/sse`
* Headers: `{"Authorization": "Bearer sk_live_your_key"}`
Click **Test Connection**
Wait for retries (if cold start)
See discovered tools ✅
## CORS Configuration
If accessing from web apps, enable CORS:
```python theme={null}
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://surfa.dev"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
```
## Security Best Practices
Never expose API keys over HTTP. Use HTTPS in production.
Check API keys on every request. Reject invalid keys immediately.
Prevent abuse by limiting requests per IP or API key.
Track all API requests for auditing and debugging.
Regularly rotate API keys and revoke old ones.
## Troubleshooting
**Cause:** Machine is starting (cold start)
**Solution:**
* Wait for retry logic to complete
* Check Fly.io logs: `fly logs`
* Verify machine is running: `fly status`
**Cause:** Invalid or missing API key
**Solution:**
* Check API key is correct
* Verify Authorization header format: `Bearer sk_live_...`
* Ensure key has correct permissions
**Cause:** Server not responding after 27 seconds
**Solution:**
* Increase timeout in Surfa platform
* Check machine is running: `fly status`
* Configure always-on if needed
**Cause:** MCP server not returning tools
**Solution:**
* Verify `/sse` endpoint is correct
* Check `initialize` request works
* Test `tools/list` manually with curl
* Review server logs for errors
## Next Steps
Learn how to test your MCP connection
Connect Claude Desktop to your remote MCP
Complete API documentation
View source and contribute