Skip to content

MCP server (AI agents)

@uptimizr/mcp is a Model Context Protocol server over your collector’s query API — read-only unless the key you give it says otherwise. It lets an AI agent answer natural-language questions about your 3D analytics (“what was the most-clicked mesh this week?”) by querying your own collector — nothing is sent to any third party.

It’s a thin wrapper: each analytics tool maps one-to-one to a documented query endpoint and performs GET requests only. There is no ingestion tool, and nothing in the server can write, alter or delete an analytics event — events are read-only.

The one exception is deliberate and gated: when the configured key holds the annotate capability, the server also registers the project-metadata tools annotate, define_term and save_analysis (plus list_annotations, list_glossary, list_analyses), which write notes, definitions and saved analyses through the metadata endpoints. The server asks GET /api/v1/whoami once at start-up, so a read-only key yields a read-only server — and every metadata write is recorded in the project’s agent audit log.

The tool catalog itself lives in the framework-agnostic, browser-safe @uptimizr/agent-core package, which @uptimizr/mcp imports. That means the agent tool surface is defined once and shared by every consumer (the MCP server, the dashboard assistant, and the demo assistant), so they can never drift apart on capabilities. See ADR 0050.

The MCP server talks only to your collector’s HTTP query API — it never opens the database (DuckDB/ClickHouse/Postgres) directly. The collector stays the single gateway to your data, so the same auth, scoping, and privacy rules apply whether a human uses the dashboard or an agent uses MCP:

AI agent ──stdio──▶ @uptimizr/mcp ──HTTPS GET + x-api-key──▶ collector ──▶ store (DuckDB / ClickHouse)

Because the collector resolves the project from the API key, an agent can only ever read its own project’s aggregated data — no cross-project access, no raw events, no PII (ADR 0003 / ADR 0017).

The collector can also host this same server itself over Streamable HTTP, so a remote agent connects with a URL and a key instead of running the package locally — see hosted transport.

The MCP server needs a project API key (utk_…) holding the query capability — and nothing else. Mint a dedicated one with the collector CLI; query is new-key’s default, so the --capabilities flag is not strictly required, but naming it keeps the intent in the shell history:

Terminal window
npx -p @uptimizr/collector-server uptimizr new-key <projectId> \
--capabilities query --label "mcp-agent"

Do not reuse the key uptimizr init printed. That one is the operator’s owner key — it also holds query:raw (raw per-session streams) and annotate (metadata writes), neither of which an MCP client needs.

The key is printed once — store it where you keep secrets, not in a repo. Giving the agent its own labelled key is also what makes its activity legible: the audit log records activity per key id, so a labelled key is what makes “what did the agent ask for?” answerable, and a per-key budget (--rate-limit-max 120 --rate-limit-window-ms 60000) keeps an agent from spending the dashboard’s allowance.

Confirm what a key holds before wiring it in:

Terminal window
curl -H "x-api-key: utk_…" https://collect.example.com/api/v1/whoami
{
"projectId": "3f2a…",
"keyId": "9c41…",
"capabilities": ["query"],
"label": "mcp-agent",
"rateLimit": { "max": 600, "windowMs": 60000 },
"rateLimitSource": "default",
}

query is all this server needs: every tool in the default catalog is an aggregate read, so query:raw is optional and off by default. A key that does hold it gains exactly one more tool — session_narrative, the compacted account of one session — and only on a collector running with ENABLE_RAW_SESSION_RETENTION; the server has no replay or live-follow tool either way. An ingest-only key is refused with 403. See API keys and capabilities for the full capability set.

Terminal window
UPTIMIZR_COLLECTOR_URL="https://collect.example.com" \
UPTIMIZR_API_KEY="utk_…" \
npx @uptimizr/mcp

No build or clone required — npx fetches the published package. Set the two environment variables to point at your collector; nothing is sent anywhere else.

Environment variable Required Notes
UPTIMIZR_COLLECTOR_URL yes Base URL of your collector.
UPTIMIZR_API_KEY yes Your project API key (x-api-key), read-only use.

Most MCP clients launch the server over stdio with the same shape — a command, args, and the two env vars. Point UPTIMIZR_COLLECTOR_URL at a local collector (http://localhost:4318) for development or your deployed collector in production.

{
"mcpServers": {
"uptimizr": {
"command": "npx",
"args": ["-y", "@uptimizr/mcp"],
"env": {
"UPTIMIZR_COLLECTOR_URL": "https://collect.example.com",
"UPTIMIZR_API_KEY": "utk_…",
},
},
},
}

Add the same server to ~/.copilot/mcp-config.json (create the file if it doesn’t exist), then restart the CLI so it loads the tools:

{
"mcpServers": {
"uptimizr": {
"type": "local",
"command": "npx",
"args": ["-y", "@uptimizr/mcp"],
"env": {
"UPTIMIZR_COLLECTOR_URL": "http://localhost:4318",
"UPTIMIZR_API_KEY": "utk_…",
},
"tools": ["*"],
},
},
}

Once connected, ask in natural language: “Using uptimizr, what were the most-clicked meshes this week and how’s the average FPS?” — the agent picks the right tools and answers from your data.

The catalog is generated from the semantic metric registry in @uptimizr/metrics (ADR 0051): every metric the collector serves on a read endpoint is a tool — 76 of them, of which a plain query key sees 75 (session_narrative needs query:raw) — so an agent sees the whole read surface rather than a hand-picked subset. Each tool’s description carries the metric’s interpretation notes and caveats (sample-size warnings, which capture channel has to be enabled), and each declares an MCP output schema covering every envelope the tool can answer with — the rows, the table envelope around them, or a summary digest — so a client can parse a result without guessing and validate it without the format it asked for being rejected.

Most tools accept an optional time range (since / until, epoch ms) plus the filters their endpoint supports (scene, session, source, bins, cellSize, limit, cameraMode, region, …). session_meta, session_trajectory and scene_representation take a required id.

The catalog is also evaluated, not just generated: a bank of ~48 real analytics questions is run against a deterministic fixture set through this exact tool surface on every change to it, and each answer is scored on tool selection, argument correctness and accuracy. That is what keeps the tool descriptions honest — every metric the collector serves has at least one question an agent is measured on. The harness lives in the repository at oss/packages/agent-eval.

Result formats — the tools default to table

Section titled “Result formats — the tools default to table”

For a large result — a 500-bin heatmap, a voxel cloud, a thousand-row list — bare rows are token-expensive and hard for a model to read, so every generated aggregate tool accepts a format argument that picks the envelope the rows arrive in (result formats):

format What the tool returns
table The tools’ default. { meta, rows } — the same rows plus the metric, range, applied filters, sample size, row count, whether the cap truncated them, and the registry’s limits.
summary A bounded digest: top rows, a trend or merged spatial clusters, with shares, a sample size, the metric’s caveats and a templated reading sentence.
full The bare rows, and nothing else.

Omit format and you get table: the same rows you always got, plus the context to judge them — which metric answered, over what range, with which filters, and whether the row cap truncated the result. (The HTTP endpoints still default to full, so the dashboard is unaffected; the default is the tool’s, and it travels as an explicit format=table on the wire.)

Reach for format=summary whenever the result could be large. It is capped at the metric’s maxSummaryRows, so a 500-bin heatmap costs the same as a 5-bin one, and the reading sentence and caveats come from the metric registry by pure code — no model is involved, so the same rows always produce the same words. Ask for full when you are post-processing the rows yourself and already know the sample is adequate. Narrowing with limit, scene and a tight since/until still helps on top of any format.

All three validate against the tool’s advertised output schema, so a client that checks structuredContent against tools/list accepts whichever one you asked for. The two single-record reads (session_meta, scene_representation) are stored resources rather than aggregations, so they take no format and always answer with their { rows } envelope.

One tool is not per-metric: query, whose input is the query DSL. It runs any metric below with any filter that metric declares, in one call:

{
"v": 1,
"metric": "mesh_sources",
"range": { "since": 1757000000000, "until": 1757600000000 },
"filters": { "scene": "lobby", "cameraMode": "first-person" },
"limit": 20,
"format": "summary",
}

range is required (both ends, epoch ms) and format defaults to table here rather than full. The grammar is closed — metrics, dimensions and filters are exactly the vocabulary uptimizr://capabilities lists — and naming something outside it returns an error whose issues[].accepted says what would have worked, so a wrong guess is a correction rather than an empty result.

Three things it does that no per-metric tool can, and that a model will otherwise do badly in prose:

  • compare — give it another { range } or { segment } and the result comes back already joined on the dimension key as { current, previous, delta, deltaPct }, with a significance test where the measure is a count and both windows are large enough. An agent should never run two queries and subtract them itself.
  • explain: true — the compiled plan instead of the rows, with warnings naming the reasons an answer might mislead: a capture channel that produced nothing in the window, a sample below the metric’s own minimum, a result cut off by limit. One call before reporting a zero.
  • drillQuery — every row of a summary carries the whole query narrowed to that row, ready to send straight back.

dimensions can be any subset a metric declares when its measure is a portable count; a spatial heatmap or a percentile is computed at one fixed grain and refuses anything else by name. See the query reference for which metrics are which.

Reach for the per-metric tools for discovery, and for query when a question needs a filter the canned tool does not expose — and for anything that compares, explains or drills.

One tool per registry metric that the collector serves on a read endpoint, grouped by the registry’s own categories. uptimizr://capabilities enumerates the same list at runtime with each tool’s grain, column units, limits and caveats.

Tool Endpoint Returns
list_sessions /api/v1/sessions Recent sessions
session_meta /api/v1/sessions/:id/meta Session descriptor
session_narrative /api/v1/sessions/:id/narrative Session narrative
scene_representation /api/v1/scenes/:sceneId/representation Scene representation
list_scenes /api/v1/scenes Active scenes
timeseries /api/v1/timeseries Event volume over time
event_counts /api/v1/event-counts Counts per event type
Tool Endpoint Returns
pointer_heatmap /api/v1/heatmaps/pointer 2D pointer heatmap
mesh_uv_heatmap /api/v1/heatmaps/mesh-uv Per-mesh UV (texture-space) heatmap
world_heatmap /api/v1/heatmaps/world 3D world-space pointer heatmap
world_heatmap_stats /api/v1/heatmaps/world/stats World heatmap totals
gaze_heatmap /api/v1/heatmaps/gaze World-space gaze heatmap
gaze_heatmap_stats /api/v1/heatmaps/gaze/stats Gaze heatmap totals
camera_heatmap /api/v1/heatmaps/camera View-direction heatmap
view_coverage_histogram /api/v1/coverage/view-histogram 360° view-coverage histogram
mesh_dwell /api/v1/meshes/dwell Per-object dwell / attention
mesh_blind_spots /api/v1/meshes/blind-spots Blind spots / never-noticed meshes
hover_dwell /api/v1/hover/dwell Hover hesitation per object
Tool Endpoint Returns
position_heatmap /api/v1/heatmaps/position Floor-plan camera-position heatmap
session_trajectory /api/v1/sessions/:sessionId/trajectory Session walked path
aggregate_paths /api/v1/paths Aggregate desire-line paths
scene_coverage /api/v1/coverage Scene coverage / dead zones
camera_distance /api/v1/camera/distance Camera distance / zoom distribution
camera_gestures /api/v1/camera-gestures Camera navigation gestures
navigation_stats /api/v1/navigation Navigation effort per session
backtrack_ratio /api/v1/backtrack Path retrace / backtracking
Tool Endpoint Returns
click_rays /api/v1/heatmaps/click-rays View-gated click rays
flow_links /api/v1/heatmaps/flow Gaze → mesh flow links
top_meshes /api/v1/meshes/top Most-interacted meshes
mesh_sources /api/v1/meshes/sources Mesh interactions by input source
mesh_trend /api/v1/meshes/trend Per-mesh interaction trend
mesh_interaction_kinds /api/v1/meshes/kinds Interaction kinds per mesh
mesh_reachability /api/v1/meshes/reachability Mesh reachability by distance
dead_clicks /api/v1/clicks/dead Dead-click rate
rage_clicks /api/v1/clicks/rage Rage-click clusters
interaction_sources /api/v1/interactions/sources Interactions by input source
top_input_actions /api/v1/input-actions/top Most-used shortcuts and actions
custom_event_vocabulary /api/v1/vocabulary/custom-events Discovered custom-event vocabulary
Tool Endpoint Returns
perf_summary /api/v1/perf Rendering performance summary
render_scale_truth /api/v1/perf/render-scale Render-scale truth
perf_distribution /api/v1/perf/distribution FPS distribution (per-session)
fps_histogram /api/v1/perf/fps-histogram Per-session median-FPS histogram
frame_time_percentiles /api/v1/perf/frame-time Frame-time percentiles
jank_rate /api/v1/perf/jank Jank rate
perf_churn /api/v1/perf/churn Perf-correlated churn
perf_by_device /api/v1/perf/by-device FPS by device class
perf_by_scene /api/v1/perf/by-scene FPS by scene
perf_heatmap /api/v1/heatmaps/perf Spatial FPS heatmap
compile_stalls /api/v1/perf/compile-stalls Shader / pipeline compile stalls
resource_summary /api/v1/perf/resources GPU / memory footprint summary
resource_percentiles /api/v1/perf/resource-percentiles GPU / memory footprint percentiles
rendering_technology /api/v1/rendering-technology Rendering-technology mix
Tool Endpoint Returns
stability_counts /api/v1/perf/stability Stability incidents
graphics_diagnostics /api/v1/graphics-diagnostics Engine diagnostic counts
error_heatmap /api/v1/heatmaps/errors Spatial error heatmap
capability_changes /api/v1/capabilities Capability / fidelity transitions
Tool Endpoint Returns
xr_rotation /api/v1/xr/rotation XR head-rotation rate
xr_sources /api/v1/xr/sources XR input-source usage
xr_abandonment /api/v1/xr/abandonment XR session abandonment
xr_locomotion /api/v1/xr/locomotion XR locomotion & comfort
xr_tracking_quality /api/v1/xr/tracking XR tracking quality
boundary_heatmap /api/v1/heatmaps/boundary Guardian / boundary-touch heatmap
boundary_heatmap_stats /api/v1/heatmaps/boundary/stats Boundary heatmap totals
xr_boundary_contacts /api/v1/xr/boundary-contacts Boundary contacts per session
Tool Endpoint Returns
ar_placement_time_to_place /api/v1/ar/placement/time-to-place AR time-to-place distribution
ar_placement_attempts /api/v1/ar/placement/attempts AR re-placement distribution
ar_placement_surfaces /api/v1/ar/placement/surfaces AR placement surfaces
Tool Endpoint Returns
funnel /api/v1/funnel Conversion funnel
scene_retention /api/v1/scene-retention Scene-to-scene retention
load_bounce_funnel /api/v1/load-bounce Load → bounce funnel
variant_leaderboard /api/v1/variant-leaderboard Variant → conversion leaderboard
Tool Endpoint Returns
insight_baseline /api/v1/insights/baseline Metric baseline
insight_movers /api/v1/insights/movers What changed
insight_anomalies /api/v1/insights/anomalies Anomalous buckets
insight_significance /api/v1/insights/significance Statistical significance
insight_scene_health /api/v1/insights/scene-health Scene health score

The server also exposes read-only MCP resources so an agent can self-discover what it can ask instead of guessing:

Resource URI Type Contents
uptimizr://capabilities application/json A machine-readable descriptor: schema version, the canonical event types, the tool catalog, the parameter semantics glossary, and metrics — the collector’s whole semantic metric registry. No collector call.
uptimizr://context application/json Read this first. The live project context document: the scenes and their named regions, the custom events this application emits and the props they carry, data freshness and retention flags, the store engine, and which metrics are empty because their capture channel is off.
uptimizr://scenes application/json The live list of scene ids with recent activity — the valid values for the scene parameter. Fetched via the read-only query API.
uptimizr://skills application/json The packaged methodology skills (below): what each investigation produces, when to use it, the tools its method names and the arguments it takes. The method itself comes from prompts/get. No collector call.

Point an agent at uptimizr://context first: it is the only one that describes this project — the real scene ids, region ids and custom-event names it must use, and which metrics cannot have data. uptimizr://capabilities is the companion: it enumerates every tool, its parameters, and what each parameter means, so the agent can plan a query without trial and error. Every packaged skill below opens by telling the agent to read the context.

uptimizr://capabilities carries a metrics array — the collector’s semantic metric registry serialised for agents. For every metric it gives:

Field What it tells an agent
grain What one row is: a project, a scene, a session, a mesh, a bin, a voxel, a bucket.
columns Per-column description and unit (ms, fps, count, ratio, world-units, …), plus which column is the measure to rank by and which names the row.
row The JSON Schema of a result row, so a client with no Zod can still validate or shape it.
filters / dimensions The parameters it accepts and the dimensions its rows are keyed by.
limits The registry-declared row caps, so nothing asks for an unbounded payload.
interpretation How to read the result — what a high or low value actually means.
caveats Small-sample, sampling-rate and capture-gating warnings. Read these before quoting a number.
sourceChannels The capture channels (ADR 0012) that feed it — if a channel is off, the metric is empty by design, not by accident.
related / comparable Metrics worth reading alongside it, and which column’s change is “the” change.

The collector serves an OpenAPI 3.1 description of its read API — no key required, because it is documentation and contains no project data:

Terminal window
curl https://collect.example.com/api/v1/openapi.json

It is generated from the same metric registry, so it lists exactly the aggregations that collector can compute: one path per endpoint, every parameter with the schema that actually validates it, and a 200 response schema per metric. The semantics OpenAPI has no vocabulary for ride along as vendor extensions on each operation — x-uptimizr-grain, x-uptimizr-units, x-uptimizr-caveats, x-uptimizr-interpretation, x-uptimizr-source-channels, x-uptimizr-limits, x-uptimizr-dimensions, x-uptimizr-related and x-uptimizr-comparable.

That makes the collector consumable by anything that speaks OpenAPI without MCP at all — generate a typed client, point an API explorer at it, or hand the document to an agent framework:

Terminal window
npx openapi-typescript https://collect.example.com/api/v1/openapi.json -o collector.d.ts

Authenticate ordinary calls with the apiKey security scheme the document declares: the x-api-key header, using a key with the query capability.

Prompts — the packaged methodology skills

Section titled “Prompts — the packaged methodology skills”

Curated MCP prompts package common investigations as one-click templates. Each renders a message that steers the agent to call the right read-only tools in a sensible order — the agent runs the tools; the prompt frames the task and supplies the method.

They are not written here. Each one is a packaged skill — an Agent Skills file, skills/<name>/SKILL.md, shipped inside both the @uptimizr/mcp and @uptimizr/agent-core tarballs and compiled into the server at build time. The same files back uptimizr agent report --skill on your collector and the starter prompts in the in-browser assistant, so a scheduled report and a chat session run the same investigation. Open one to read (or fork) the method:

Terminal window
cat node_modules/@uptimizr/mcp/skills/weekly-scene-health/SKILL.md

* marks a required argument.

Skill Arguments What it produces, and when to use it Tools its method names
attention_hotspots scene*, range? Find where visitors look and click in a scene: view-direction concentration, gaze→mesh flow, the objects that draw the most interaction, and the ones nobody ever notices. USE FOR: deciding where to put a call to action, finding ignored or invisible content, explaining why an object gets no clicks, laying out a scene around what people actually look at. camera_heatmap, flow_links, click_rays, top_meshes, mesh_dwell, mesh_blind_spots, query
conversion_investigation scene?, range? Find out where a funnel loses people and whether the loss is real: step-by-step drop-off, the bounce that happens before the funnel even starts, scene-to-scene retention, variant performance, and the interaction failures (dead clicks, rage clicks, unreachable meshes) that explain a stalled step. USE FOR: a funnel that converts worse than expected, an A/B variant comparison, “where do people drop off”, diagnosing a step nobody completes. funnel, load_bounce_funnel, scene_retention, variant_leaderboard, dead_clicks, rage_clicks, mesh_reachability, flow_links, insight_significance, insight_movers, query
performance_regression_triage scene?, range? Triage a frame-rate or stability regression: confirm it moved, date it, locate it (which scene, device class, place in the scene), and name the mechanism — jank, shader compile stalls, memory pressure, a render-scale change or a rendering-technology shift. USE FOR: “the app got slower”, a FPS drop after a release, stutter reports, deciding whether a regression is real or noise. insight_movers, insight_anomalies, insight_significance, insight_baseline, perf_summary, perf_distribution, frame_time_percentiles, jank_rate, perf_by_device, perf_by_scene, perf_heatmap, compile_stalls, resource_percentiles, render_scale_truth, rendering_technology, query
weekly_scene_health scene?, range? A weekly health check for a scene (or the whole project): a weighted health score with every factor traced back to the metric behind it, what changed against last week, traffic, event mix, performance, and the most-interacted meshes. USE FOR: the recurring “how is the scene doing?” review, a scheduled weekly or monthly report, a first look at a project you do not know yet, deciding which scene to investigate next. insight_scene_health, insight_movers, insight_baseline, insight_significance, insight_anomalies, event_counts, timeseries, perf_summary, top_meshes, list_sessions, query
xr_comfort_audit scene?, range? Audit VR/AR comfort for a scene (or the whole project): rapid head rotation, locomotion style, tracking quality, guardian/boundary contacts, input-source mix, and the short sessions that mean someone took the headset off. USE FOR: motion-sickness complaints, immersive sessions that end early, choosing a locomotion scheme, checking whether a play space is big enough. xr_rotation, xr_locomotion, xr_abandonment, xr_sources, xr_tracking_quality, xr_boundary_contacts, boundary_heatmap_stats, insight_scene_health, insight_movers, query

The catalog is also readable as the uptimizr://skills resource above, for a client whose UI has no prompt picker.

Leaving something behind (the annotate tools)

Section titled “Leaving something behind (the annotate tools)”

Read tools answer a question; these keep the answer. They appear in tools/list only when the key you configured holds the annotate capability.

Tool What it does
annotate Pin a note to the project, a scene, a mesh, a region, a metric or a period of time.
define_term Record what a name means in this project. Idempotent — defining it again replaces the meaning.
save_analysis Store a titled question plus the conclusion drawn from it.
list_annotations Read the notes already left — worth doing before explaining a spike someone has already explained.
list_glossary Read the project’s vocabulary before interpreting mesh names, scene ids or custom events.
list_analyses Read questions this project has asked before, and what they concluded.

Mint the key with the capability:

Terminal window
uptimizr new-key <projectId> --capabilities query,annotate --label "weekly-report-agent"

Without it the server starts read-only and never offers the tools; with it, every write is bounded at the collector’s edge and recorded in the agent audit log. They write metadata only — see Metadata endpoints for the shapes, the bounds and the privacy note.

Everything above runs the MCP server next to the client, over stdio. The collector can also host the very same server itself, over the MCP Streamable HTTP transport at /mcp — so a remote or containerised agent connects with a URL and a key, with no npx step and nothing installed on the client machine (ADR 0051 §7, which resolves the transport ADR 0050 §7 deferred pending auth).

AI agent ──HTTPS POST/GET /mcp + x-api-key──▶ collector ─(in-process)─▶ query API ──▶ store

Both transports serve an identical surface — the same tools, the same resources, the same prompts — because both are built by the same factory in @uptimizr/mcp. Pick stdio for a laptop pointed at a local collector, and the hosted transport when the agent is not on the same machine as the client, or when you would rather not distribute a key into a desktop config.

It is off by default: an extra authenticated, long-lived surface is something an operator opts into. Set one environment variable on the collector and restart it:

Terminal window
COLLECTOR_MCP_HTTP=1
Environment variable Default Notes
COLLECTOR_MCP_HTTP off 1/true serves MCP at /mcp. Unset → the route does not exist.
COLLECTOR_MCP_MAX_SESSIONS 50 Concurrent MCP sessions. One too many is refused with 503.
COLLECTOR_MCP_SESSION_TTL_MS 1800000 Idle timeout before a session is closed and its slot reclaimed (30 min).

See deploying the collector for the reverse-proxy requirements — chiefly that the proxy must not buffer the response.

Every request is authenticated; the session id is never a credential on its own. Send the same project API key the stdio server uses, as either header:

  • x-api-key: utk_… — the collector’s own header, or
  • Authorization: Bearer utk_… — the form MCP clients send, accepted as an alias on /mcp only.

The key must hold query. A missing or unknown key is 401, a key without query (an ingest-only key, say) is 403, and a session may only ever be driven by the key that opened it — so a leaked session id buys nothing on its own. Mint a dedicated, labelled key exactly as for stdio:

Terminal window
npx -p @uptimizr/collector-server uptimizr new-key <projectId> \
--capabilities query --label "mcp-remote"

Clients that support remote servers take a URL and a header map. Claude Desktop, VS Code and Cursor all accept this shape:

{
"mcpServers": {
"uptimizr": {
"type": "http",
"url": "https://collect.example.com/mcp",
"headers": {
"Authorization": "Bearer utk_…",
},
},
},
}

Some client versions spell the transport "transport": "http" (or "streamable-http") rather than "type", and a client that cannot send a custom Authorization header can use "x-api-key" in the same headers map instead. Clients with no remote support keep using the stdio block above — the same tools either way.

Verify from a shell before wiring a client in; a successful initialize returns the session id in the Mcp-Session-Id response header:

Terminal window
curl -sS -D- -o/dev/null https://collect.example.com/mcp \
-H "x-api-key: utk_…" \
-H "content-type: application/json" \
-H "accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
  • One MCP server per session, built with the capability set the key resolved to, so a session’s surface can only narrow to what its key may actually do.
  • Tool calls run the ordinary read path. The collector answers a tool call by dispatching to its own query route in process — no loopback socket, no second TLS hop — so a tool call and the equivalent curl are answered by the same handler, with the same validation, the same project scoping and the same result envelope.
  • Rate limits apply per key, exactly as for HTTP reads, including a key’s own --rate-limit-max budget. The inner read is not charged a second time.
  • Audit rows are tagged mcp-http, so the audit log tells a hosted-MCP tool call apart from a plain HTTP read. The stdio server is an ordinary HTTP client of the collector, so its calls are recorded as http: the surface records how a request reached the collector, not which program made it.
  • DELETE /mcp ends a session, and an idle one is reclaimed after COLLECTOR_MCP_SESSION_TTL_MS.

Sessions live in the collector process, so if you run several collector instances behind a load balancer, pin MCP traffic to one instance (sticky sessions) or point the client at a single instance’s URL.

The package also exports its building blocks for embedding in your own server:

import { createCollectorClient, createMcpServer, readMcpConfig } from "@uptimizr/mcp";

The tool catalogs and the collector client come from the framework-agnostic @uptimizr/agent-core package (re-exported here for convenience). If you’re building a non-MCP agent — a browser assistant, a Node service, a CLI, a bot — depend on @uptimizr/agent-core directly: it also ships a headless LLM provider-adapter interface and tool-calling loop over the same catalog.