FlightlessSomething exposes a REST API and an MCP (Model Context Protocol) server. Both interfaces share the same underlying logic and data, but differ in transport and authentication methods.
Browser-based users authenticate via Discord OAuth2. The flow is:
GET /auth/login— redirects to Discord's authorization page.- Discord redirects back to
GET /auth/login/callbackwith an authorization code. - The server exchanges the code for an access token, fetches the Discord profile, and creates a session cookie.
Session cookies are HttpOnly, SameSite=Lax, and optionally Secure (when the redirect URL uses HTTPS).
POST /auth/admin/login accepts {"username":"…","password":"…"} and creates a session for the built-in admin account. This endpoint is rate-limited to 3 attempts per 10 minutes (global lock). The counter resets on a successful login.
Authenticated users can create API tokens (up to 10 per user) via the web UI or API. Tokens are 64-character hex strings passed in the Authorization header:
Authorization: Bearer <token>
Bearer tokens work for all authenticated REST endpoints (including admin endpoints) and for the MCP server.
The MCP server (POST /mcp) uses the same Bearer token mechanism. The token is sent in the HTTP Authorization header of the JSON-RPC request. Tools are filtered by the caller's access level — unauthenticated callers only see public tools; authenticated callers see public + auth tools; admins see all tools.
| Scope | Limit | Window | Applies to |
|---|---|---|---|
| Benchmark uploads | 5 | 10 minutes | Non-admin users |
| Admin login attempts | 3 | 10 minutes | Global (resets on successful login) |
When a rate limit is exceeded, the server responds with 429 Too Many Requests:
{
"error": "rate limit exceeded: ...",
"retry_after_secs": 42
}| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check. Returns {"status":"ok","version":"…"}. |
GET |
/auth/login |
Initiates Discord OAuth flow. |
GET |
/auth/login/callback |
Discord OAuth callback. |
POST |
/auth/admin/login |
Admin username/password login (rate-limited). |
POST |
/auth/logout |
Clears the session cookie. No auth is enforced — a no-op if called without an active session. |
GET |
/api/auth/me |
Returns session-based user info, or 401 if no active session. Session-only; Bearer tokens are not recognized by this endpoint. |
GET |
/api/benchmarks |
List/search benchmarks (paginated). |
GET |
/api/benchmarks/:id |
Get benchmark metadata. |
GET |
/api/benchmarks/:id/data |
Get pre-calculated statistics for all runs. |
GET |
/api/benchmarks/:id/runs/:runIndex |
Get pre-calculated statistics for a single run. |
GET |
/api/benchmarks/:id/download |
Download benchmark as a ZIP of CSVs. |
POST |
/api/debugcalc |
Compute statistics from raw FPS/frametime data (for verification). |
These endpoints use the RequireAuthOrToken middleware — either a valid session or a Bearer token is accepted.
| Method | Path | Description |
|---|---|---|
POST |
/api/benchmarks |
Create a benchmark (multipart form with CSV files). |
PUT |
/api/benchmarks/:id |
Update title, description, or run labels. |
DELETE |
/api/benchmarks/:id |
Delete a benchmark and its data files. |
POST |
/api/benchmarks/:id/runs |
Add runs to an existing benchmark (multipart). |
DELETE |
/api/benchmarks/:id/runs/:run_index |
Delete a specific run from a benchmark. |
GET |
/api/tokens |
List the current user's API tokens. |
POST |
/api/tokens |
Create a new API token. |
DELETE |
/api/tokens/:id |
Delete an API token. |
For write operations on benchmarks (PUT, DELETE, POST runs), the caller must be either the benchmark owner or an admin.
Admin endpoints use RequireAuthOrToken followed by RequireAdmin. Both session cookies and Bearer tokens are accepted, provided the associated account has admin privileges.
| Method | Path | Description |
|---|---|---|
GET |
/api/admin/users |
List users (paginated, searchable). |
DELETE |
/api/admin/users/:id |
Delete a user account. |
DELETE |
/api/admin/users/:id/benchmarks |
Delete all benchmarks for a user. |
PUT |
/api/admin/users/:id/ban |
Ban or unban a user. |
PUT |
/api/admin/users/:id/admin |
Grant or revoke admin privileges. |
| Method | Path | Description |
|---|---|---|
POST |
/mcp |
JSON-RPC 2.0 MCP endpoint. |
GET |
/mcp |
Returns 405 Method Not Allowed — SSE is not supported. |
DELETE |
/mcp |
Session termination. Returns 200 OK with {"message": "session terminated"}. |
OPTIONS |
/mcp |
CORS preflight. Returns 204 No Content. |
The MCP endpoint includes CORS middleware that sets the following headers on every response:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
This enables browser-based MCP clients (such as the MCP Inspector) running on arbitrary localhost ports to connect without cross-origin errors. Bearer token authentication provides the security boundary.
Returns identity information for the currently authenticated session. This endpoint reads the session cookie directly and does not accept Bearer token authentication.
Response: 200 OK
{
"user_id": 42,
"username": "alice",
"is_admin": false
}Returns 401 if no active session exists.
List and search benchmarks with pagination and sorting.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
int | 1 |
Page number. |
per_page |
int | 10 |
Results per page (1–100). |
search |
string | — | Space-separated keywords (AND logic). Each keyword is matched against all enabled search_fields. |
search_fields |
string | title,description |
Comma-separated fields to search. Valid values: title, description, user, run_name, specifications. |
user_id |
int | — | Filter by user ID. |
sort_by |
string | created_at |
Sort field: title, created_at, updated_at. |
sort_order |
string | desc |
Sort direction: asc, desc. |
Response: 200 OK
{
"benchmarks": [ ... ],
"page": 1,
"per_page": 10,
"total": 42,
"total_pages": 5
}Get a single benchmark's metadata including run count and labels.
Response: 200 OK — A Benchmark object (see Data Objects).
Get pre-calculated statistics for all runs in a benchmark. Statistics are read from pre-computed .stats files (zstd-compressed gob) — no raw data is transferred to the client.
Response: 200 OK — JSON array of PreCalculatedRun objects.
Each object contains:
| Field | Type | Description |
|---|---|---|
label |
string | Run label. |
specOS |
string | Operating system. |
specCPU |
string | CPU model. |
specGPU |
string | GPU model. |
specRAM |
string | RAM amount. |
specLinuxKernel |
string | Linux kernel version (omitted if empty). |
specLinuxScheduler |
string | CPU scheduler (omitted if empty). |
totalDataPoints |
int | Total number of data points in the run. |
series |
object | Downsampled time-series per metric (LTTB, max 2,000 points): {"fps": [[index, value], ...], ...}. |
stats |
object | Per-metric MetricStats computed with linear interpolation. |
statsMangoHud |
object | Per-metric MetricStats computed with MangoHud threshold method. |
Each MetricStats object contains: min, max, avg, median, p01, p05, p10, p25, p75, p90, p95, p97, p99, iqr, stddev, variance, count (int), and density ([[roundedValue, count], ...] histogram filtered to p01–p97 range).
Metric keys: fps, frametime, cpu_load, gpu_load, cpu_temp, cpu_power, gpu_temp, gpu_core_clock, gpu_mem_clock, gpu_vram_used, gpu_power, ram_used, swap_used.
Get pre-calculated statistics for a single run within a benchmark.
Path parameters:
| Parameter | Type | Description |
|---|---|---|
id |
int | Benchmark ID. |
runIndex |
int | Zero-based run index. |
Response: 200 OK — A single PreCalculatedRun object (same structure as one element from GET /api/benchmarks/:id/data).
Download all benchmark runs as a ZIP archive. Each run is exported as a separate CSV file inside the ZIP.
Response: 200 OK — application/zip attachment (benchmark_<id>.zip).
Create a new benchmark. Requires authentication.
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Benchmark title (max 100 characters). |
description |
string | No | Description in Markdown (max 5,000 characters). |
files |
file(s) | Yes | One or more MangoHud CSV or Afterburner HML files. |
Limits:
- Max 500,000 data lines per run.
- Max 1,000,000 total data lines across all runs.
- Rate limited to 5 uploads per 10 minutes (non-admins).
Response: 201 Created — The created Benchmark object (see Data Objects).
Update a benchmark's metadata and/or run labels. Only the owner or an admin can update.
Request body (JSON):
{
"title": "New Title",
"description": "Updated description",
"labels": { "0": "Run A", "1": "Run B" }
}All fields are optional — only provided fields are updated. labels keys are run indices as strings; values are new label strings (max 100 characters each).
Response: 200 OK — The updated Benchmark object.
Delete a benchmark and all its data files. Only the owner or an admin can delete.
Response: 200 OK
{ "message": "benchmark deleted" }Add additional runs to an existing benchmark. Requires authentication; rate limited to 5 uploads per 10 minutes (non-admins).
Content-Type: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
files |
file(s) | Yes | Additional MangoHud CSV or Afterburner HML files. |
The total data lines across existing and new runs must not exceed 1,000,000.
Response: 200 OK — The updated Benchmark object.
Delete a specific run from a benchmark. Cannot delete the last remaining run. Only the owner or an admin can delete.
Response: 200 OK
{ "message": "run deleted successfully" }Compute statistics from raw FPS and/or frametime data. This public endpoint is used by the /debugcalc page to compare frontend and backend calculation results.
Request body (JSON):
{
"fps": [60.0, 59.5, 61.2, ...],
"frameTime": [16.67, 16.81, 16.34, ...]
}At least one of fps or frameTime must be provided. When frameTime is provided, it is used to derive FPS statistics (the correct method); frametime stats are also returned. When only fps is provided, only FPS stats are included in the response.
Response: 200 OK
{
"linear": {
"fps": { "min": ..., "max": ..., "avg": ..., "median": ..., "p01": ..., ... },
"frameTime": { ... }
},
"mangohud": {
"fps": { ... },
"frameTime": { ... }
}
}Each stats object is a MetricStats (same structure as returned by benchmark data endpoints). The linear key uses linear interpolation for percentiles; mangohud uses the MangoHud threshold method.
List all API tokens for the current user. Returns full token objects including the token string.
Response: 200 OK — Array of APIToken objects (see Data Objects).
Create a new API token.
Request body (JSON):
{ "name": "my-token" }name is required (1–100 characters). Maximum 10 tokens per user.
Response: 201 Created — The created APIToken object (see Data Objects). The token field contains the full 64-character hex string. Store it securely — this is the only time it is returned.
Delete an API token. Only the token owner can delete it.
Response: 200 OK
{ "message": "token deleted" }List users with optional search.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
int | 1 |
Page number. |
per_page |
int | 10 |
Results per page (1–100). |
search |
string | — | Filter by username or Discord ID (partial match). |
Response: 200 OK
{
"users": [ ... ],
"page": 1,
"per_page": 10,
"total": 20,
"total_pages": 2
}Each element is a User object (see Data Objects) with benchmark_count and api_token_count populated.
Delete a user. Cannot delete your own account.
Query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
delete_data |
bool | false |
If true, also deletes all benchmark data files belonging to the user before removing the account. |
Response: 200 OK
{ "message": "user deleted" }Delete all benchmarks (and their data files) belonging to a user.
Response: 200 OK
{ "message": "all user benchmarks deleted" }Ban or unban a user. Cannot ban your own account.
Request body (JSON):
{ "banned": true }Response: 200 OK — The updated User object.
Grant or revoke admin privileges. Cannot revoke your own admin privileges.
Request body (JSON):
{ "is_admin": true }Response: 200 OK — The updated User object.
{
"id": 1,
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z",
"created_at_humanized": "2 days ago",
"updated_at_humanized": "2 days ago",
"user_id": 42,
"title": "My Benchmark",
"description": "Markdown description",
"run_names": "Run A,Run B",
"specifications": "Linux,Intel i7-13700K,NVIDIA RTX 4090,32 GB",
"run_count": 2,
"run_labels": ["Run A", "Run B"],
"user": { "..." }
}| Field | Type | Notes |
|---|---|---|
id |
int | Benchmark ID. |
created_at / updated_at |
string | ISO 8601 UTC timestamps. |
created_at_humanized / updated_at_humanized |
string | Relative time strings (e.g. "2 days ago"). |
user_id |
int | Owner's user ID. |
title |
string | Max 100 characters. |
description |
string | Markdown; max 5,000 characters. |
run_names |
string | Comma-separated run labels, stored for search indexing. |
specifications |
string | Concatenated unique system specs, stored for search indexing. |
run_count |
int | Number of runs. Omitted when not loaded. |
run_labels |
array of string | Run labels in order. Omitted when not loaded. |
user |
object | Nested User object. |
{
"id": 42,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-15T10:30:00Z",
"discord_id": "123456789012345678",
"username": "alice",
"is_admin": false,
"is_banned": false,
"last_web_activity_at": "2025-01-15T09:00:00Z",
"last_api_activity_at": null,
"benchmark_count": 5,
"api_token_count": 2
}benchmark_count and api_token_count are only populated in admin list (GET /api/admin/users) responses. last_web_activity_at and last_api_activity_at may be null.
{
"id": 1,
"created_at": "2025-01-10T12:00:00Z",
"updated_at": "2025-01-10T12:00:00Z",
"user_id": 42,
"token": "a1b2c3d4...64hexcharacters",
"name": "my-token",
"last_used_at": "2025-01-15T08:00:00Z"
}The token field is the full 64-character hex string and is included in all API token list and creation responses.
The MCP server is a stateless JSON-RPC 2.0 endpoint at POST /mcp. It implements the Model Context Protocol specification (protocol version 2025-11-25).
| Method | Description |
|---|---|
initialize |
Returns server info, capabilities, and instructions for AI agents. |
notifications/initialized |
Client notification. Returns 202 Accepted. |
tools/list |
Lists available tools (filtered by caller's auth level). |
tools/call |
Invokes a tool by name with arguments. |
ping |
Returns an empty result. |
To connect an AI agent to the MCP server:
{
"mcpServers": {
"flightlesssomething": {
"url": "https://flightlesssomething.example.com/mcp",
"headers": {
"Authorization": "Bearer <your-api-token>"
}
}
}
}Without a token, only public (read-only) tools are available. With a token, authenticated tools become available. Admin tokens unlock all tools.
The initialize response includes contextual information in its instructions field:
- Server base URL — the full URL for constructing curl commands (e.g.,
https://flightlesssomething.ambrosia.one). - Authenticated user context — if an API token is provided, the response includes the user's ID, username, and admin status, eliminating the need for a separate "who am I" call.
- Anonymous mode notice — if no token is provided, the response indicates that only read-only operations are available.
| Tool | Description | Read-only |
|---|---|---|
list_benchmarks |
Search and list benchmarks with pagination, search, sorting, and username filtering. | Yes |
get_benchmark |
Get detailed benchmark metadata (title, description, user, run count, labels). | Yes |
get_benchmark_data |
Get benchmark metadata and computed statistics for all runs in a single call (min, max, avg, median, P1, P5, P10, P25, P75, P90, P95, P97, P99, IQR, std dev, variance, count). Optionally include downsampled raw data (up to 5,000 points). | Yes |
get_benchmark_run |
Get computed statistics for a single run. | Yes |
| Tool | Description | Read-only |
|---|---|---|
update_benchmark |
Update title, description, and/or run labels. Owner or admin only. | No |
| Tool | Description | Read-only |
|---|---|---|
list_users |
List all users with pagination and search. | Yes |
delete_user |
Delete a user account. Cannot delete your own account. | No |
delete_user_benchmarks |
Delete all benchmarks belonging to a user. | No |
ban_user |
Ban or unban a user. Cannot ban your own account. | No |
toggle_user_admin |
Grant or revoke admin privileges. Cannot revoke your own. | No |
The MCP server does not support benchmark data upload, download, or deletion operations — these involve large CSV file transfers which are not suitable for the MCP protocol. Use the web UI for uploading, downloading, or deleting benchmarks. API token management is also not available via MCP — use the web UI at /api-tokens to manage tokens.
Operations intentionally excluded from MCP:
- Benchmark file upload (
POST /api/benchmarks,POST /api/benchmarks/:id/runs) — requires multipart form data, unsuitable for MCP. - Benchmark ZIP download (
GET /api/benchmarks/:id/download) — large binary transfer, unsuitable for MCP. - Benchmark deletion (
DELETE /api/benchmarks/:id,DELETE /api/benchmarks/:id/runs/:run_index) — data operations, handled via web UI or REST API. - API token management (
GET /api/tokens,POST /api/tokens,DELETE /api/tokens/:id) — managed via web UI. - Current user info (
GET /api/auth/me) — user context is provided in theinitializeresponse instead, eliminating the need for a separate tool call.
All MCP tools support an optional jq parameter that applies a jq expression to the tool's JSON result server-side before returning it. This reduces response size and avoids wasting context tokens on unneeded data.
Example usage:
{
"name": "get_benchmark_data",
"arguments": {
"id": 42,
"jq": ".runs[0].metrics.fps | {avg, p01, p99}"
}
}This returns only the FPS stats instead of the full benchmark data response.
Each tool accepts a JSON object as arguments in the tools/call request. All tools support an optional jq parameter (string) for server-side result filtering. Below are the tool-specific parameters.
| Parameter | Type | Required | Description |
|---|---|---|---|
page |
int | No | Page number (default: 1). |
per_page |
int | No | Results per page, 1–100 (default: 10). |
search |
string | No | Search keywords (space-separated, AND logic). |
user_id |
int | No | Filter by user ID. |
username |
string | No | Filter by exact username (case-insensitive). Use instead of user_id when you know the username. |
sort_by |
string | No | title, created_at, or updated_at (default: created_at). |
sort_order |
string | No | asc or desc (default: desc). |
jq |
string | No | jq expression to filter/transform the result. |
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int | Yes | Benchmark ID. |
jq |
string | No | jq expression to filter/transform the result. |
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int | Yes | Benchmark ID. |
max_points |
int | No | Include downsampled raw data points per metric (0 = stats only, 1–5,000). When provided, each MetricSummary includes a data array of downsampled float64 values. |
jq |
string | No | jq expression to filter/transform the result. |
The MCP response wraps each run as a BenchmarkDataSummary with label, spec_os, spec_cpu, spec_gpu, spec_ram, spec_linux_kernel, spec_linux_scheduler, total_data_points, downsampled_to (when applicable), and metrics (map of metric key to MetricSummary).
Each MetricSummary contains: min, max, avg, median, p01, p05, p10, p25, p75, p90, p95, p97, p99, iqr, std_dev, variance, count, and optionally data (downsampled float64 array, only present when max_points > 0). Note: the density histogram is available in the REST API (GET /api/benchmarks/:id/data) but is not included in the MCP MetricSummary.
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int | Yes | Benchmark ID. |
run_index |
int | Yes | Zero-based run index. |
max_points |
int | No | Include downsampled raw data points per metric (0 = stats only, 1–5,000). |
jq |
string | No | jq expression to filter/transform the result. |
Returns a single BenchmarkDataSummary (same structure as one element from get_benchmark_data).
| Parameter | Type | Required | Description |
|---|---|---|---|
id |
int | Yes | Benchmark ID. |
title |
string | No | New title (max 100 characters). |
description |
string | No | New description in Markdown (max 5,000 characters). |
labels |
object | No | Map of run index (string key) to new label, e.g. {"0": "Run A"}. |
jq |
string | No | jq expression to filter/transform the result. |
| Parameter | Type | Required | Description |
|---|---|---|---|
page |
int | No | Page number (default: 1). |
per_page |
int | No | Results per page, 1–100 (default: 10). |
search |
string | No | Search by username or Discord ID. |
jq |
string | No | jq expression to filter/transform the result. |
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id |
int | Yes | User ID to delete. |
delete_data |
bool | No | Also delete all benchmark data files (default: false). |
jq |
string | No | jq expression to filter/transform the result. |
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id |
int | Yes | User ID whose benchmarks to delete. |
jq |
string | No | jq expression to filter/transform the result. |
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id |
int | Yes | User ID to ban/unban. |
banned |
bool | Yes | true to ban, false to unban. |
jq |
string | No | jq expression to filter/transform the result. |
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id |
int | Yes | User ID to modify. |
is_admin |
bool | Yes | true to grant admin, false to revoke. |
jq |
string | No | jq expression to filter/transform the result. |