@uptimizr/mcp 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +265 -28
- package/README.md +92 -22
- package/dist/bin.js +9 -2
- package/dist/bin.js.map +1 -1
- package/dist/capabilities.d.ts +21 -3
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +86 -15
- package/dist/capabilities.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +32 -83
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts +23 -4
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +66 -4
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts +65 -8
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +150 -25
- package/dist/server.js.map +1 -1
- package/llms.txt +190 -15
- package/package.json +11 -10
- package/skills/attention-hotspots/SKILL.md +88 -0
- package/skills/conversion-investigation/SKILL.md +97 -0
- package/skills/performance-regression-triage/SKILL.md +106 -0
- package/skills/weekly-scene-health/SKILL.md +105 -0
- package/skills/xr-comfort-audit/SKILL.md +95 -0
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAIL,KAAK,eAAe,EAErB,MAAM,sBAAsB,CAAC;AAqE9B;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAwBD;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAW9F;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,sBAA2B,GACnC,SAAS,CAiFX"}
|
package/dist/server.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import {
|
|
3
|
-
import { readTools } from "@uptimizr/agent-core";
|
|
2
|
+
import { rawTools, readTools, writeTools, } from "@uptimizr/agent-core";
|
|
4
3
|
import { registerResources } from "./resources.js";
|
|
5
4
|
import { registerPrompts } from "./prompts.js";
|
|
6
5
|
import { version } from "./version.js";
|
|
7
6
|
/**
|
|
8
|
-
* Normalise a collector response into the `{ rows }` envelope
|
|
9
|
-
*
|
|
7
|
+
* Normalise a collector response into the `{ rows }` envelope a `format=full`
|
|
8
|
+
* result is reported in. Aggregate endpoints already return an array; the
|
|
10
9
|
* single-object reads (a session descriptor, a scene representation, a one-row
|
|
11
10
|
* summary) become a one-element array so a client can treat every tool's
|
|
12
11
|
* structured result the same way. A `204`-style empty body becomes no rows.
|
|
@@ -19,8 +18,36 @@ function toRows(data) {
|
|
|
19
18
|
return [data];
|
|
20
19
|
}
|
|
21
20
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Whether the collector really answered with the envelope the call asked for:
|
|
22
|
+
* `format=table` with a `meta` block around its rows, `format=summary` with a
|
|
23
|
+
* `kind`-tagged digest and its `reading`. Both keys are required so a *row* that
|
|
24
|
+
* happens to have a `kind` column is never mistaken for a digest.
|
|
25
|
+
*/
|
|
26
|
+
function isEnvelope(data, format) {
|
|
27
|
+
if (data == null || typeof data !== "object" || Array.isArray(data))
|
|
28
|
+
return false;
|
|
29
|
+
const payload = data;
|
|
30
|
+
if (format === "table")
|
|
31
|
+
return "meta" in payload && "rows" in payload;
|
|
32
|
+
if (format === "summary")
|
|
33
|
+
return "kind" in payload && "reading" in payload;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The structured payload for one tool result: the collector's response in the
|
|
38
|
+
* envelope the call asked for, validated against the tool's registry-derived
|
|
39
|
+
* output schema and returned **parsed**.
|
|
40
|
+
*
|
|
41
|
+
* `format=table` and `format=summary` are returned as they arrive — the whole
|
|
42
|
+
* point of those envelopes is the `meta` / `reading` they carry, and stripping
|
|
43
|
+
* them here is precisely the bug that made the SDK reject the recommended path
|
|
44
|
+
* with `-32602` (#350). `full` keeps the `{ rows }` wrapping it has always had.
|
|
45
|
+
* Which envelope to expect is decided by the `format` the request actually
|
|
46
|
+
* carried — not guessed from the payload — and the payload must then actually
|
|
47
|
+
* *be* that envelope (`meta` + `rows`, or a `kind`-tagged digest). Anything
|
|
48
|
+
* else is wrapped as rows, which is what keeps a modern client working against
|
|
49
|
+
* an **older collector** that does not know `format` and answers a `table`
|
|
50
|
+
* request with the bare rows (or the bare record) it always returned.
|
|
24
51
|
*
|
|
25
52
|
* The registry's numeric columns are strict `z.number()`: since ADR 0051 §2 the
|
|
26
53
|
* *collector* guarantees numbers, coercing each dialect's wire format
|
|
@@ -28,32 +55,106 @@ function toRows(data) {
|
|
|
28
55
|
* point rows leave its driver. So this is a check, not a repair — the advertised
|
|
29
56
|
* schema describes the API, and normalising here would hide a store regression.
|
|
30
57
|
*
|
|
31
|
-
* If the
|
|
32
|
-
* own output validation then reports the offending column by name, which
|
|
33
|
-
* honest outcome for a collector that is out of contract.
|
|
58
|
+
* If the payload does not match the schema it is passed through unchanged; the
|
|
59
|
+
* SDK's own output validation then reports the offending column by name, which
|
|
60
|
+
* is the honest outcome for a collector that is out of contract.
|
|
61
|
+
*/
|
|
62
|
+
function structuredResult(tool, data, format) {
|
|
63
|
+
// A generated per-metric tool returns one metric's rows, so the `format`
|
|
64
|
+
// envelope above is the default. The `query` tool (ADR 0051 §3) chooses its
|
|
65
|
+
// own shape — what comes back depends on the `format` that was asked for —
|
|
66
|
+
// so it supplies the wrapper itself.
|
|
67
|
+
const payload = tool.structuredContent?.(data) ?? (isEnvelope(data, format) ? data : { rows: toRows(data) });
|
|
68
|
+
const parsed = tool.outputSchema.safeParse(payload);
|
|
69
|
+
return parsed.success ? parsed.data : payload;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Server-level `instructions` for a session whose key capabilities are known.
|
|
73
|
+
*
|
|
74
|
+
* Only built when {@link CreateMcpServerOptions.capabilities} is supplied, so
|
|
75
|
+
* the stdio server's `initialize` result stays byte-for-byte what it has always
|
|
76
|
+
* been. Capability names are not secret — `GET /api/v1/whoami` returns the same
|
|
77
|
+
* list to the key's holder — and naming them saves an agent a round of
|
|
78
|
+
* trial-and-error against tools it could never call.
|
|
79
|
+
*/
|
|
80
|
+
function instructionsFor(capabilities) {
|
|
81
|
+
const granted = capabilities.length > 0 ? capabilities.join(", ") : "none";
|
|
82
|
+
return ("Every tool here reads one aggregate metric from the connected Uptimizr collector, " +
|
|
83
|
+
"always scoped to the project the API key belongs to — no cross-project access, and no " +
|
|
84
|
+
"personally identifying data. " +
|
|
85
|
+
`The key this session is bound to holds these capabilities: ${granted}. ` +
|
|
86
|
+
"Tools outside that set are not registered, and the collector refuses them independently. " +
|
|
87
|
+
"Read the uptimizr://capabilities resource first: it gives every metric's grain, column " +
|
|
88
|
+
"units, row limits, interpretation and caveats, so a query can be planned rather than guessed.");
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Ask the collector what the configured key may do (`GET /api/v1/whoami`).
|
|
92
|
+
*
|
|
93
|
+
* Never throws: an older collector without the route, an unreachable one, or a
|
|
94
|
+
* key that cannot even read all yield an empty capability set, and the server
|
|
95
|
+
* then starts read-only rather than not starting at all. A degraded but useful
|
|
96
|
+
* server beats no server.
|
|
34
97
|
*/
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
98
|
+
export async function fetchKeyCapabilities(client) {
|
|
99
|
+
try {
|
|
100
|
+
const whoami = (await client.get("/api/v1/whoami"));
|
|
101
|
+
return Array.isArray(whoami?.capabilities)
|
|
102
|
+
? whoami.capabilities.filter((capability) => typeof capability === "string")
|
|
103
|
+
: [];
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
39
108
|
}
|
|
40
109
|
/**
|
|
41
|
-
* Build the Uptimizr MCP server:
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
110
|
+
* Build the Uptimizr MCP server: an `McpServer` whose tools each wrap one
|
|
111
|
+
* collector endpoint via the injected `CollectorClient`. The server holds no
|
|
112
|
+
* business logic — it forwards validated arguments and returns the collector's
|
|
113
|
+
* JSON (ADR 0005 / ADR 0017). Alongside the tools it exposes
|
|
45
114
|
* capability-discovery **resources** and curated analysis **prompts** so agents
|
|
46
115
|
* can self-orient (ADR 0050 §7).
|
|
47
116
|
*
|
|
48
|
-
* The tool catalog is generated from the
|
|
117
|
+
* The **analytics** tool catalog is generated from the metric registry
|
|
49
118
|
* (ADR 0051 §1), so `tools/list` covers every metric the collector serves on an
|
|
50
119
|
* endpoint. Each tool advertises the registry-derived `outputSchema` and returns
|
|
51
|
-
* both `structuredContent`
|
|
52
|
-
*
|
|
120
|
+
* both `structuredContent` — the `format` envelope the call asked for, which
|
|
121
|
+
* since #336 is `{ meta, rows }` by default — and the `content` text a client
|
|
122
|
+
* without structured-output support still reads. That catalog is entirely
|
|
123
|
+
* read-only: **events cannot be written, altered or deleted through this
|
|
124
|
+
* server** (ADR 0051 §9).
|
|
125
|
+
*
|
|
126
|
+
* `options.capabilities` is what the **key** behind `client` is allowed to do
|
|
127
|
+
* (`GET /api/v1/whoami`). Tools whose endpoint needs more than the ordinary
|
|
128
|
+
* `query` capability are registered only when the key really holds it, so
|
|
129
|
+
* `tools/list` describes what this session can actually do rather than what the
|
|
130
|
+
* collector could do for somebody else: the raw-session tools of #314 behind
|
|
131
|
+
* `query:raw`, the **metadata** tools of #310 — annotations, glossary, saved
|
|
132
|
+
* analyses — behind `annotate`. Omit it and only the `query` surface is served,
|
|
133
|
+
* the safe default for a caller that has not looked the key up.
|
|
134
|
+
*
|
|
135
|
+
* The factory is **transport-agnostic**: `bin.ts` connects it to stdio, and the
|
|
136
|
+
* collector connects one instance per authenticated Streamable HTTP session at
|
|
137
|
+
* `/mcp` (ADR 0051 §7), passing that session's key capabilities through
|
|
138
|
+
* `options`. Both get the same tools, resources and prompts from this one place.
|
|
53
139
|
*/
|
|
54
|
-
export function createMcpServer(client) {
|
|
55
|
-
|
|
56
|
-
|
|
140
|
+
export function createMcpServer(client, options = {}) {
|
|
141
|
+
// The default is the ordinary read surface, never "nothing": a caller that has
|
|
142
|
+
// not looked the key up still gets every `query` tool, exactly as before.
|
|
143
|
+
// `instructions`, by contrast, keys off the *supplied* set, so a stdio server
|
|
144
|
+
// built without one keeps the `initialize` result it has always had.
|
|
145
|
+
const capabilities = options.capabilities ?? ["query"];
|
|
146
|
+
const server = new McpServer({ name: "uptimizr-mcp", version }, {
|
|
147
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
148
|
+
...(options.capabilities ? { instructions: instructionsFor(options.capabilities) } : {}),
|
|
149
|
+
});
|
|
150
|
+
const tools = [
|
|
151
|
+
...readTools,
|
|
152
|
+
// `query:raw` additionally requires `ENABLE_RAW_SESSION_RETENTION` on the
|
|
153
|
+
// collector (ADR 0003), which this process cannot see — a narrative tool on
|
|
154
|
+
// a retention-disabled collector still answers 403, and says so.
|
|
155
|
+
...(capabilities.includes("query:raw") ? rawTools : []),
|
|
156
|
+
];
|
|
157
|
+
for (const tool of tools) {
|
|
57
158
|
server.registerTool(tool.name, {
|
|
58
159
|
title: tool.title,
|
|
59
160
|
description: tool.description,
|
|
@@ -68,7 +169,7 @@ export function createMcpServer(client) {
|
|
|
68
169
|
return { content: [{ type: "text", text }] };
|
|
69
170
|
return {
|
|
70
171
|
content: [{ type: "text", text }],
|
|
71
|
-
structuredContent:
|
|
172
|
+
structuredContent: structuredResult(tool, data, params.format),
|
|
72
173
|
};
|
|
73
174
|
}
|
|
74
175
|
catch (err) {
|
|
@@ -77,7 +178,31 @@ export function createMcpServer(client) {
|
|
|
77
178
|
}
|
|
78
179
|
});
|
|
79
180
|
}
|
|
80
|
-
|
|
181
|
+
// Metadata tools (#310, ADR 0051 §5): annotations, glossary, saved analyses.
|
|
182
|
+
//
|
|
183
|
+
// They appear in `tools/list` only for a key that holds `annotate`. That is a
|
|
184
|
+
// usability decision, not the security boundary — the collector refuses the
|
|
185
|
+
// write either way with a 403 — but offering an agent a tool it will always
|
|
186
|
+
// be refused for wastes its context and its patience.
|
|
187
|
+
//
|
|
188
|
+
// Nothing here can write, alter or delete an event: the tools call the three
|
|
189
|
+
// metadata endpoints and nothing else, and every call is audited by the
|
|
190
|
+
// collector (ADR 0051 §7/§9).
|
|
191
|
+
if (capabilities.includes("annotate")) {
|
|
192
|
+
for (const tool of writeTools) {
|
|
193
|
+
server.registerTool(tool.name, { title: tool.title, description: tool.description, inputSchema: tool.inputSchema }, async (args) => {
|
|
194
|
+
try {
|
|
195
|
+
const data = await tool.execute(client, args);
|
|
196
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
200
|
+
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
registerResources(server, client, { capabilities });
|
|
81
206
|
registerPrompts(server);
|
|
82
207
|
return server;
|
|
83
208
|
}
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/llms.txt
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# @uptimizr/mcp
|
|
2
2
|
|
|
3
|
-
> A
|
|
4
|
-
>
|
|
3
|
+
> A Model Context Protocol server over an Uptimizr collector's API. Lets an agent query a consumer's
|
|
4
|
+
> own 3D analytics; thin wrapper, no data egress. Events are read-only; project-metadata writes
|
|
5
|
+
> (annotations, glossary, saved analyses, pinned panels) are registered only for a key holding
|
|
6
|
+
> `annotate`.
|
|
5
7
|
|
|
6
8
|
## Docs
|
|
7
9
|
|
|
@@ -10,7 +12,7 @@
|
|
|
10
12
|
- [Integration & API reference](https://github.com/RaananW/Uptimizr/blob/main/docs/integration.md): the underlying query endpoints.
|
|
11
13
|
- [Architecture Decision Records](https://github.com/RaananW/Uptimizr/tree/main/docs/adr): privacy model (0003), thin backends (0005), consumer-facing agents (0017).
|
|
12
14
|
|
|
13
|
-
##
|
|
15
|
+
## Analytics tools (read-only)
|
|
14
16
|
|
|
15
17
|
<!-- generated:registry-tool-names:start — generated by `pnpm gen:docs`; edit the metric registry, not this list -->
|
|
16
18
|
|
|
@@ -20,23 +22,196 @@
|
|
|
20
22
|
`position_heatmap`, `session_trajectory`, `aggregate_paths`, `scene_coverage`, `camera_distance`,
|
|
21
23
|
`click_rays`, `flow_links`, `top_meshes`, `mesh_sources`, `mesh_trend`, `mesh_dwell`,
|
|
22
24
|
`mesh_blind_spots`, `mesh_interaction_kinds`, `mesh_reachability`, `dead_clicks`, `rage_clicks`,
|
|
23
|
-
`hover_dwell`, `interaction_sources`, `top_input_actions`, `
|
|
24
|
-
`
|
|
25
|
-
`
|
|
26
|
-
`
|
|
27
|
-
`
|
|
28
|
-
`
|
|
29
|
-
`
|
|
30
|
-
`
|
|
31
|
-
`scene_retention`, `load_bounce_funnel`, `variant_leaderboard
|
|
25
|
+
`hover_dwell`, `interaction_sources`, `top_input_actions`, `custom_event_vocabulary`,
|
|
26
|
+
`camera_gestures`, `navigation_stats`, `backtrack_ratio`, `perf_summary`, `render_scale_truth`,
|
|
27
|
+
`perf_distribution`, `fps_histogram`, `frame_time_percentiles`, `jank_rate`, `perf_churn`,
|
|
28
|
+
`perf_by_device`, `perf_by_scene`, `perf_heatmap`, `compile_stalls`, `resource_summary`,
|
|
29
|
+
`resource_percentiles`, `stability_counts`, `graphics_diagnostics`, `error_heatmap`,
|
|
30
|
+
`rendering_technology`, `capability_changes`, `xr_rotation`, `xr_sources`, `xr_abandonment`,
|
|
31
|
+
`xr_locomotion`, `xr_tracking_quality`, `boundary_heatmap`, `boundary_heatmap_stats`,
|
|
32
|
+
`xr_boundary_contacts`, `ar_placement_time_to_place`, `ar_placement_attempts`,
|
|
33
|
+
`ar_placement_surfaces`, `funnel`, `scene_retention`, `load_bounce_funnel`, `variant_leaderboard`,
|
|
34
|
+
`insight_baseline`, `insight_movers`, `insight_anomalies`, `insight_significance`,
|
|
35
|
+
`insight_scene_health`
|
|
36
|
+
|
|
37
|
+
Only on a key holding `query:raw`, and only when the collector runs with
|
|
38
|
+
`ENABLE_RAW_SESSION_RETENTION` (ADR 0003):
|
|
39
|
+
|
|
40
|
+
`session_narrative`
|
|
32
41
|
|
|
33
42
|
<!-- generated:registry-tool-names:end -->
|
|
34
43
|
|
|
44
|
+
## Insight primitives (ADR 0051 §4)
|
|
45
|
+
|
|
46
|
+
Five of the tools are readings *about* the other metrics, and they are the right first call on an
|
|
47
|
+
open-ended question — "how are things?" does not mean "call thirty tools".
|
|
48
|
+
|
|
49
|
+
- `insight_scene_health` — one 0-100 score per scene over six weighted factors (perf stability,
|
|
50
|
+
jank, errors, dead clicks, coverage, XR abandonment), least healthy first. The right *unscoped*
|
|
51
|
+
starting point: it answers which scene to open. Every factor carries the `metric` id behind it,
|
|
52
|
+
its `raw` value and the project `baseline` it was compared with, so the next call and the
|
|
53
|
+
sentence to write are both already in the row. **50 is the project norm, not a pass mark** — the
|
|
54
|
+
score compares a scene with the rest of the project's recent past — and a factor with
|
|
55
|
+
`score: null` was not counted (its `note` says why).
|
|
56
|
+
- `insight_movers` — what changed against a reference window (the previous equal one by default),
|
|
57
|
+
for every comparable metric in scope, ranked by a robust z-score: the change divided by how much
|
|
58
|
+
that metric normally swings, so a noisy metric must move much further than a steady one to reach
|
|
59
|
+
the top. Bounded: at most 24 metrics are scanned per request.
|
|
60
|
+
- `insight_baseline` — what is normal for one metric in one scene: `median` and `mad`, the
|
|
61
|
+
`p10`..`p90` band, and the drift `slope`. Call it on whatever moved, to say whether the new level
|
|
62
|
+
is actually outside normal rather than merely different.
|
|
63
|
+
- `insight_anomalies` — *when* one metric went wrong. One row per bucket that does not belong in
|
|
64
|
+
the series: `kind: "spike" | "drop"` for a bucket more than `sensitivity` (default 3) standard
|
|
65
|
+
deviations from the trailing window, and `kind: "shift"` at the bucket where the level moved and
|
|
66
|
+
stayed moved. Where the metric declares a dimension it can be split by, `contributor` names the
|
|
67
|
+
mesh, source, input action, event type or scene holding the largest `share` of the excess.
|
|
68
|
+
Bounded: one scan for the series plus at most three attribution scans per request.
|
|
69
|
+
- `insight_significance` — whether a difference between two windows could be chance: `effect`,
|
|
70
|
+
`ci95`, `p`, and the `test` that produced them (a two-proportion z with Wilson intervals for a
|
|
71
|
+
declared rate, Welch's t over the per-bucket values for a level, an exact Poisson rate test for a
|
|
72
|
+
bare count — picked from the measure, not from you). **Read `ci95` before `p`**: an interval that
|
|
73
|
+
straddles 0 means you cannot tell yet, and `powerNote` says what this much data could have
|
|
74
|
+
detected at all. Welch's `n` is the number of **buckets**, not events. It compares two *windows*,
|
|
75
|
+
not two segments: a variant-versus-variant question is a `400`, not a wrong answer.
|
|
76
|
+
|
|
77
|
+
Read `insight_anomalies`' `z` as standard deviations and `insight_movers`' as the same ratio
|
|
78
|
+
unscaled — they differ by a constant factor (1.4826) and must not be compared directly. A sustained
|
|
79
|
+
level change is reported both as a `shift` and, for the days right after it, as `drop`/`spike`
|
|
80
|
+
rows: two true statements about one event.
|
|
81
|
+
|
|
82
|
+
Two fields decide whether a mover is worth reporting, and both are easy to misread:
|
|
83
|
+
|
|
84
|
+
- `direction` is the registry's opinion of what a **rise** in that metric means — `up` (good),
|
|
85
|
+
`down` (bad) or `neutral`. It is *not* the direction of the move: read it with the sign of
|
|
86
|
+
`delta`, so a rise in a `down` metric (errors, dead clicks, jank) is a regression.
|
|
87
|
+
- `aboveMinSample: false` means the delta is real arithmetic but the denominator is below the
|
|
88
|
+
metric's declared minimum. Such rows are returned rather than dropped — "we cannot tell" and
|
|
89
|
+
"nothing changed" are different answers — and must never be reported as findings.
|
|
90
|
+
|
|
91
|
+
Statistics are computed in pure TypeScript over portable day/hour buckets, so the same data yields
|
|
92
|
+
the same answer on every storage engine. Only `comparable` metrics with a faithful per-bucket form
|
|
93
|
+
participate; asking for one that has none returns `400` naming every id that does.
|
|
94
|
+
|
|
95
|
+
## Result formats (`format`)
|
|
96
|
+
|
|
97
|
+
Every aggregate tool takes `format` — the envelope its rows arrive in, not a filter (ADR 0051 §2).
|
|
98
|
+
Omit it and you get `table`: the tools' own default, sent explicitly, so the HTTP endpoints still
|
|
99
|
+
default to `full` for every other client. All three validate against the tool's `outputSchema`.
|
|
100
|
+
|
|
101
|
+
- `summary` — **prefer this.** A bounded digest: `ranked` top rows, a `series` trend, merged spatial
|
|
102
|
+
`clusters`, or a single `record`, with shares, a sample size, the metric's `caveats` and a
|
|
103
|
+
templated `reading` sentence. Capped at the metric's `limits.maxSummaryRows`, so a 500-bin heatmap
|
|
104
|
+
costs the same as a 5-bin one. `reading` and `caveats` are templated by pure code — no model —
|
|
105
|
+
so identical rows always produce identical words.
|
|
106
|
+
- `table` — **the default.** `{ meta, rows }`: every row plus the metric, range, applied filters,
|
|
107
|
+
sample size, row count, a truncation flag and the registry limits. Self-describing, but not
|
|
108
|
+
bounded — the rows are still all of them, so ask for `summary` when the answer could be large.
|
|
109
|
+
- `full` — the bare rows, unchanged, with no envelope at all.
|
|
110
|
+
|
|
111
|
+
Shares appear only where the measure can honestly be summed (an FPS or ratio metric reports
|
|
112
|
+
`total: null` and no shares). Cluster coordinates are grid indices — multiply by the effective
|
|
113
|
+
`cellSize` for world space. When the scene has a registered proxy and named regions, a world-space
|
|
114
|
+
cluster is also labelled with `region` (the smallest containing region), `regions[]` (all containing
|
|
115
|
+
ids), `nearestMesh` and `distance` (world units, `0` when the mesh box contains it), and
|
|
116
|
+
`drill.region` becomes that region id — pass it straight back as the `region` argument. Quote the
|
|
117
|
+
label rather than the coordinate; a `null` means the scene registered nothing that could answer (the
|
|
118
|
+
`caveats` say which), so do not invent a landmark. `session_meta` and `scene_representation` are
|
|
119
|
+
stored records, not aggregations, and take no `format`.
|
|
120
|
+
|
|
121
|
+
## Resources and prompts
|
|
122
|
+
|
|
123
|
+
- `uptimizr://context` (`application/json`) — the live project context document
|
|
124
|
+
(`GET /api/v1/context`, ADR 0051 §5): scene ids + labels + named region ids, the custom-event
|
|
125
|
+
vocabulary with its `props` keys and coarse types, top meshes, bound input actions, data freshness,
|
|
126
|
+
retention flags, store engine, glossary, recent annotations, and `metrics.disabledByCapture` —
|
|
127
|
+
metrics that return empty because their capture channel is off. Bounded and cached per project.
|
|
128
|
+
**Read it first**, and use the ids and names it gives you instead of inferring your own.
|
|
129
|
+
- `uptimizr://capabilities` (`application/json`) — static descriptor: schema version, canonical
|
|
130
|
+
event types, the tool catalog, the parameter-semantics glossary, and `metrics` (the whole registry
|
|
131
|
+
with grain, column units, row JSON Schema, filters, limits, interpretation, caveats, source
|
|
132
|
+
channels). No collector call. Read it after the context.
|
|
133
|
+
- `uptimizr://scenes` (`application/json`) — the live scene ids with recent activity, i.e. the valid
|
|
134
|
+
values for the `scene` parameter. Fetched through the read-only query API.
|
|
135
|
+
- `uptimizr://skills` (`application/json`) — the packaged methodology skills below: name, title,
|
|
136
|
+
description, the tools each method relies on, the capabilities it needs and the arguments it
|
|
137
|
+
takes. Served from the package; no collector call.
|
|
138
|
+
- Prompts: one per packaged skill (`attention_hotspots`, `conversion_investigation`,
|
|
139
|
+
`performance_regression_triage`, `weekly_scene_health`, `xr_comfort_audit`) — each opens by
|
|
140
|
+
telling the agent to read `uptimizr://context` first, then renders one user message sequencing
|
|
141
|
+
the read-only tools; they fetch nothing themselves.
|
|
142
|
+
|
|
143
|
+
## Packaged methodology skills (ADR 0051 §7)
|
|
144
|
+
|
|
145
|
+
Every prompt template is a packaged skill: an Agent Skills file under `skills/<name>/SKILL.md` in
|
|
146
|
+
this tarball, whose body is the investigation as numbered steps. The catalog is also the
|
|
147
|
+
`uptimizr://skills` resource; `prompts/get` returns the rendered method.
|
|
148
|
+
|
|
149
|
+
<!-- generated:registry-skill-names:start — generated by `pnpm gen:docs`; edit the SKILL.md files, not this list -->
|
|
150
|
+
|
|
151
|
+
- `attention_hotspots` (scene (required), 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.
|
|
152
|
+
Method: `skills/attention-hotspots/SKILL.md`. Tools: `camera_heatmap`, `flow_links`, `click_rays`, `top_meshes`, `mesh_dwell`, `mesh_blind_spots`, `query`.
|
|
153
|
+
- `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.
|
|
154
|
+
Method: `skills/conversion-investigation/SKILL.md`. Tools: `funnel`, `load_bounce_funnel`, `scene_retention`, `variant_leaderboard`, `dead_clicks`, `rage_clicks`, `mesh_reachability`, `flow_links`, `insight_significance`, `insight_movers`, `query`.
|
|
155
|
+
- `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.
|
|
156
|
+
Method: `skills/performance-regression-triage/SKILL.md`. Tools: `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`.
|
|
157
|
+
- `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.
|
|
158
|
+
Method: `skills/weekly-scene-health/SKILL.md`. Tools: `insight_scene_health`, `insight_movers`, `insight_baseline`, `insight_significance`, `insight_anomalies`, `event_counts`, `timeseries`, `perf_summary`, `top_meshes`, `list_sessions`, `query`.
|
|
159
|
+
- `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.
|
|
160
|
+
Method: `skills/xr-comfort-audit/SKILL.md`. Tools: `xr_rotation`, `xr_locomotion`, `xr_abandonment`, `xr_sources`, `xr_tracking_quality`, `xr_boundary_contacts`, `boundary_heatmap_stats`, `insight_scene_health`, `insight_movers`, `query`.
|
|
161
|
+
|
|
162
|
+
<!-- generated:registry-skill-names:end -->
|
|
163
|
+
|
|
164
|
+
## Transports
|
|
165
|
+
|
|
166
|
+
Two transports, one server: `createMcpServer()` is transport-agnostic.
|
|
167
|
+
|
|
168
|
+
- **stdio** — `npx @uptimizr/mcp`, launched by a desktop client. What this package's `bin` does.
|
|
169
|
+
- **Collector-hosted Streamable HTTP** — the collector serves the same server at `POST/GET/DELETE
|
|
170
|
+
/mcp` when it is started with `COLLECTOR_MCP_HTTP=1` (ADR 0051 §7). A remote client connects with
|
|
171
|
+
a URL plus `x-api-key` (or `Authorization: Bearer <key>`); tool calls are audited with
|
|
172
|
+
`surface: "mcp-http"`. The catalog, resources and prompts are identical to stdio's.
|
|
173
|
+
|
|
174
|
+
## Required key capability
|
|
175
|
+
|
|
176
|
+
`UPTIMIZR_API_KEY` needs the `query` capability
|
|
177
|
+
(`uptimizr new-key <projectId> --capabilities query --label "mcp-agent"`; `query` is the default).
|
|
178
|
+
Add `annotate` only to let the agent write project metadata — the server calls
|
|
179
|
+
`GET /api/v1/whoami` once at start-up and registers the metadata tools only when the key holds it.
|
|
180
|
+
`query:raw` is **optional and off by default**: the default catalog is aggregate reads only, and a
|
|
181
|
+
key that holds it gains exactly one more tool, `session_narrative` (see below). `ingest` is never
|
|
182
|
+
used. A key missing a required capability is refused with `403`.
|
|
183
|
+
|
|
35
184
|
## Key exports
|
|
36
185
|
|
|
37
|
-
- `createMcpServer(client)` — build the
|
|
186
|
+
- `createMcpServer(client, options?)` — build the MCP server. `options.capabilities` is the bound
|
|
187
|
+
key's capability set, passed by the collector-hosted transport (or read from `/api/v1/whoami` by
|
|
188
|
+
the stdio bin) so a session's surface matches its key: metadata write tools are registered only
|
|
189
|
+
when it includes `annotate`, and omitting it registers the read catalog alone.
|
|
190
|
+
- `fetchKeyCapabilities(client)` — ask `GET /api/v1/whoami` what the key holds (never throws).
|
|
38
191
|
- `createCollectorClient(config)` — the thin GET-only collector client.
|
|
39
192
|
- `readMcpConfig()` — read `UPTIMIZR_COLLECTOR_URL` + `UPTIMIZR_API_KEY` from the environment.
|
|
40
193
|
- `readTools` — the read-only tool catalog (one entry per query endpoint), generated from the
|
|
41
|
-
`@uptimizr/metrics` metric registry
|
|
42
|
-
|
|
194
|
+
`@uptimizr/metrics` metric registry plus `query` and `list_subscriptions`: 77 tools, each with an
|
|
195
|
+
input schema, an MCP `outputSchema` covering all three `format` envelopes, and the metric's
|
|
196
|
+
interpretation notes and caveats in its description (ADR 0051).
|
|
197
|
+
- `writeTools` — the project-metadata tools (`annotate`, `define_term`, `save_analysis`,
|
|
198
|
+
`pin_panel`, `unpin_panel`, `list_annotations`, `list_glossary`, `list_analyses`,
|
|
199
|
+
`list_panels`). A separate export from `readTools` on
|
|
200
|
+
purpose, so an integration's read-only stance stays inspectable. They write metadata only — no
|
|
201
|
+
event can be written, altered or deleted through them. `pin_panel` stores a panel **spec** — a
|
|
202
|
+
metric id, a chart name, some column names — that the dashboard draws with panels it already
|
|
203
|
+
ships; send the `query` document with `range: "inherit"` and a chart the metric's grain supports,
|
|
204
|
+
or it is a `400` naming the charts that would have worked. `unpin_panel` removes a panel for
|
|
205
|
+
everyone on the project. At most 50 pinned panels per project (`409` past the cap).
|
|
206
|
+
|
|
207
|
+
## Capability-gated tools (ADR 0051 §7)
|
|
208
|
+
|
|
209
|
+
- The default catalog is the `query` surface: aggregate reads only, no raw per-session tool.
|
|
210
|
+
- `createMcpServer(client, { capabilities })` takes the key's capability set (from `GET /api/v1/whoami`). When it includes `query:raw`, the server also registers `session_narrative` — an ordered, bounded account of what one session did, gated on the collector also running with `ENABLE_RAW_SESSION_RETENTION`. Omit the option and only the `query` surface is served.
|
|
211
|
+
- The `uptimizr-mcp` binary discovers the capability set at start-up (best effort; a failure falls back to the `query` surface).
|
|
212
|
+
|
|
213
|
+
## Non-metric reads
|
|
214
|
+
|
|
215
|
+
- `list_subscriptions` — the project's conditional subscriptions (ADR 0051 §6): what the
|
|
216
|
+
collector is watching for and how each one last went. No arguments. Read-only; creating or
|
|
217
|
+
deleting one needs `annotate` and is not exposed as a tool.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uptimizr/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Read-only Model Context Protocol (MCP) server over an Uptimizr collector's query API — let an agent ask questions of your own 3D analytics, on your own infrastructure.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"uptimizr",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"files": [
|
|
42
42
|
"dist",
|
|
43
|
+
"skills",
|
|
43
44
|
"README.md",
|
|
44
45
|
"LICENSE",
|
|
45
46
|
"AGENTS.md",
|
|
@@ -48,27 +49,27 @@
|
|
|
48
49
|
"sideEffects": false,
|
|
49
50
|
"dependencies": {
|
|
50
51
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
51
|
-
"zod": "^4.5
|
|
52
|
-
"@uptimizr/agent-core": "1.
|
|
53
|
-
"@uptimizr/metrics": "0.
|
|
54
|
-
"@uptimizr/schema": "1.
|
|
52
|
+
"zod": "^4.6.5",
|
|
53
|
+
"@uptimizr/agent-core": "1.2.0",
|
|
54
|
+
"@uptimizr/metrics": "0.2.0",
|
|
55
|
+
"@uptimizr/schema": "1.2.0"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
|
-
"@types/node": "^26.
|
|
58
|
-
"tsx": "^4.23.
|
|
59
|
-
"vitest": "^
|
|
58
|
+
"@types/node": "^26.6.2",
|
|
59
|
+
"tsx": "^4.23.15",
|
|
60
|
+
"vitest": "^5.0.1"
|
|
60
61
|
},
|
|
61
62
|
"engines": {
|
|
62
63
|
"node": ">=22"
|
|
63
64
|
},
|
|
64
65
|
"scripts": {
|
|
65
|
-
"build": "tsc -p tsconfig.json",
|
|
66
|
+
"build": "tsc -p tsconfig.json && node ../../../scripts/copy-dir.mjs ../agent-core/skills skills",
|
|
66
67
|
"dev": "tsc -p tsconfig.json --watch",
|
|
67
68
|
"serve": "tsx src/bin.ts",
|
|
68
69
|
"start": "node dist/bin.js",
|
|
69
70
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
70
71
|
"test": "vitest run",
|
|
71
72
|
"lint": "eslint .",
|
|
72
|
-
"clean": "rimraf -g dist *.tsbuildinfo"
|
|
73
|
+
"clean": "rimraf -g dist skills *.tsbuildinfo"
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: attention_hotspots
|
|
3
|
+
title: Attention hot-spots for a scene
|
|
4
|
+
description: >-
|
|
5
|
+
Find where visitors look and click in a scene: view-direction concentration, gaze→mesh flow, the
|
|
6
|
+
objects that draw the most interaction, and the ones nobody ever notices. USE FOR: deciding where
|
|
7
|
+
to put a call to action, finding ignored or invisible content, explaining why an object gets no
|
|
8
|
+
clicks, laying out a scene around what people actually look at. Trigger phrases: what do people
|
|
9
|
+
look at, attention hotspots, where do visitors click, which meshes get ignored, blind spots,
|
|
10
|
+
gaze heatmap, is anyone seeing this object.
|
|
11
|
+
tools:
|
|
12
|
+
- camera_heatmap
|
|
13
|
+
- flow_links
|
|
14
|
+
- click_rays
|
|
15
|
+
- top_meshes
|
|
16
|
+
- mesh_dwell
|
|
17
|
+
- mesh_blind_spots
|
|
18
|
+
- query
|
|
19
|
+
capabilities:
|
|
20
|
+
- query
|
|
21
|
+
args:
|
|
22
|
+
- name: scene
|
|
23
|
+
required: true
|
|
24
|
+
description: The scene id to analyse (see the uptimizr://scenes resource).
|
|
25
|
+
- name: range
|
|
26
|
+
required: false
|
|
27
|
+
default: the last 7 days
|
|
28
|
+
description: The window to analyse, in words — e.g. "the last 7 days", "since launch".
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
Where does attention concentrate in scene "{{scene}}" over {{range}}?
|
|
32
|
+
|
|
33
|
+
Work through the method below with the read-only tools it names — all of them scoped with
|
|
34
|
+
`scene="{{scene}}"` — and synthesise one answer.
|
|
35
|
+
|
|
36
|
+
1. **Orient before you ask anything.** Read the `uptimizr://context` resource first: it gives the
|
|
37
|
+
real scene ids, the scene's **named regions** and the custom-event names this project emits, and
|
|
38
|
+
it tells you which metrics are empty because their capture channel is off. Name regions the way
|
|
39
|
+
the project names them — "the checkout counter", not "the cluster at x≈3".
|
|
40
|
+
|
|
41
|
+
2. **Where do they look?** `camera_heatmap` (`scene="{{scene}}"`) gives the view-direction
|
|
42
|
+
distribution — what people point the camera at, whether or not they ever click it. Ask for
|
|
43
|
+
`format: "summary"`: the digest merges neighbouring cells into a handful of clusters with a
|
|
44
|
+
share each and a plain-language `reading`, which is what you want here; the raw grid is
|
|
45
|
+
thousands of cells you cannot describe.
|
|
46
|
+
|
|
47
|
+
3. **Does looking turn into touching?** `flow_links` (`scene="{{scene}}"`) links where the gaze was
|
|
48
|
+
to the mesh that was then clicked. A strong link is a working call to action; a heavy look with
|
|
49
|
+
no outgoing link is content that draws the eye and then disappoints.
|
|
50
|
+
|
|
51
|
+
4. **Where do the clicks land?** `click_rays` (`scene="{{scene}}"`) gives view-gated clicks per
|
|
52
|
+
voxel and mesh — clicks attributed to what the visitor could actually see, not to whatever the
|
|
53
|
+
ray happened to pass through.
|
|
54
|
+
|
|
55
|
+
5. **Rank the objects.** `top_meshes` (`scene="{{scene}}"`) for the most-interacted meshes, and
|
|
56
|
+
`mesh_dwell` (`scene="{{scene}}"`) for how long attention rests on each one. Dwell without
|
|
57
|
+
interaction is hesitation, and it usually means the object looks clickable and is not, or is
|
|
58
|
+
clickable and does not look it.
|
|
59
|
+
|
|
60
|
+
6. **Name the cold half.** `mesh_blind_spots` (`scene="{{scene}}"`) lists the meshes that are
|
|
61
|
+
present and essentially never noticed. A hot-spot report that only names hot spots tells you
|
|
62
|
+
nothing about the content you paid to build.
|
|
63
|
+
|
|
64
|
+
7. **Narrow it with the DSL.** For anything the canned tools do not expose, use the single `query`
|
|
65
|
+
tool: pick the `metric`, bound it with `range`, filter it, and set `format: "summary"` for a
|
|
66
|
+
bounded digest — each summary row carries a `drillQuery` you can send straight back instead of
|
|
67
|
+
rebuilding the filter. Set `compare: { range: <previous window> }` to see whether a hot spot is
|
|
68
|
+
new, and `explain: true` when a result looks wrong or empty: the plan names the capture channel,
|
|
69
|
+
the sample size and the row cap behind it.
|
|
70
|
+
|
|
71
|
+
## What to report
|
|
72
|
+
|
|
73
|
+
- The two or three real hot-spots, named with the project's own region and mesh names, each with
|
|
74
|
+
its share of attention.
|
|
75
|
+
- The cold areas and the meshes nobody notices.
|
|
76
|
+
- Where gaze fails to convert into interaction, and what that implies for layout and
|
|
77
|
+
call-to-action placement.
|
|
78
|
+
|
|
79
|
+
Carry the caveats: heatmaps are gated on the view/pointer capture channels and are sampled
|
|
80
|
+
(ADR 0012), so a share is a share _of the sampled events_; say so, and say when a result was
|
|
81
|
+
truncated (`meta.truncated`) or sits below the metric's own minimum sample. Do not turn a voxel
|
|
82
|
+
cluster into a claim about one object unless `click_rays` or `flow_links` attributes it to that
|
|
83
|
+
mesh.
|
|
84
|
+
|
|
85
|
+
End with 2–3 concrete layout or content recommendations. If an `annotate` tool is available, leave
|
|
86
|
+
a note on the region you want revisited — a region-scoped annotation is what makes the next report
|
|
87
|
+
open where this one ended. If a `pin_panel` tool is available, pin the heatmap panel you reasoned
|
|
88
|
+
from.
|