@recapt/mcp 0.0.2-beta

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.
Files changed (63) hide show
  1. package/dist/api/client.d.ts +12 -0
  2. package/dist/api/client.js +67 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.js +217 -0
  5. package/dist/tools/analyzeFlow.d.ts +7 -0
  6. package/dist/tools/analyzeFlow.js +68 -0
  7. package/dist/tools/analyzeFunnel.d.ts +7 -0
  8. package/dist/tools/analyzeFunnel.js +63 -0
  9. package/dist/tools/compareCohorts.d.ts +6 -0
  10. package/dist/tools/compareCohorts.js +84 -0
  11. package/dist/tools/comparePeriods.d.ts +6 -0
  12. package/dist/tools/comparePeriods.js +54 -0
  13. package/dist/tools/detectDrift.d.ts +6 -0
  14. package/dist/tools/detectDrift.js +55 -0
  15. package/dist/tools/detectRegressions.d.ts +6 -0
  16. package/dist/tools/detectRegressions.js +46 -0
  17. package/dist/tools/discoverPersonas.d.ts +6 -0
  18. package/dist/tools/discoverPersonas.js +50 -0
  19. package/dist/tools/getActionableIssues.d.ts +7 -0
  20. package/dist/tools/getActionableIssues.js +55 -0
  21. package/dist/tools/getAnomalies.d.ts +6 -0
  22. package/dist/tools/getAnomalies.js +53 -0
  23. package/dist/tools/getConsoleErrors.d.ts +6 -0
  24. package/dist/tools/getConsoleErrors.js +61 -0
  25. package/dist/tools/getDeadClicks.d.ts +6 -0
  26. package/dist/tools/getDeadClicks.js +42 -0
  27. package/dist/tools/getDomains.d.ts +6 -0
  28. package/dist/tools/getDomains.js +34 -0
  29. package/dist/tools/getElementFriction.d.ts +6 -0
  30. package/dist/tools/getElementFriction.js +45 -0
  31. package/dist/tools/getFlowFriction.d.ts +7 -0
  32. package/dist/tools/getFlowFriction.js +57 -0
  33. package/dist/tools/getFormFriction.d.ts +6 -0
  34. package/dist/tools/getFormFriction.js +42 -0
  35. package/dist/tools/getIssues.d.ts +6 -0
  36. package/dist/tools/getIssues.js +82 -0
  37. package/dist/tools/getJourneyPatterns.d.ts +7 -0
  38. package/dist/tools/getJourneyPatterns.js +50 -0
  39. package/dist/tools/getPageMetrics.d.ts +6 -0
  40. package/dist/tools/getPageMetrics.js +47 -0
  41. package/dist/tools/getPageTrends.d.ts +6 -0
  42. package/dist/tools/getPageTrends.js +46 -0
  43. package/dist/tools/getSessionDetails.d.ts +6 -0
  44. package/dist/tools/getSessionDetails.js +70 -0
  45. package/dist/tools/getUxHealthReport.d.ts +7 -0
  46. package/dist/tools/getUxHealthReport.js +50 -0
  47. package/dist/tools/listPages.d.ts +6 -0
  48. package/dist/tools/listPages.js +50 -0
  49. package/dist/tools/listSessions.d.ts +7 -0
  50. package/dist/tools/listSessions.js +67 -0
  51. package/dist/tools/memory.d.ts +7 -0
  52. package/dist/tools/memory.js +119 -0
  53. package/dist/tools/predictOutcomes.d.ts +6 -0
  54. package/dist/tools/predictOutcomes.js +66 -0
  55. package/dist/tools/scanSite.d.ts +6 -0
  56. package/dist/tools/scanSite.js +51 -0
  57. package/dist/tools/searchSessions.d.ts +6 -0
  58. package/dist/tools/searchSessions.js +51 -0
  59. package/dist/tools/triageSessions.d.ts +8 -0
  60. package/dist/tools/triageSessions.js +195 -0
  61. package/dist/utils/orgContext.d.ts +6 -0
  62. package/dist/utils/orgContext.js +22 -0
  63. package/package.json +37 -0
@@ -0,0 +1,12 @@
1
+ /**
2
+ * HTTP client for calling external-api query endpoints.
3
+ */
4
+ export declare function isApiConfigured(): boolean;
5
+ export declare function getApiUrl(): string;
6
+ interface ApiResponse<T> {
7
+ data?: T;
8
+ error?: string;
9
+ }
10
+ export declare function apiGet<T>(path: string, params?: Record<string, string | number | undefined>): Promise<ApiResponse<T>>;
11
+ export declare function apiPost<T>(path: string, body: Record<string, unknown>): Promise<ApiResponse<T>>;
12
+ export {};
@@ -0,0 +1,67 @@
1
+ /**
2
+ * HTTP client for calling external-api query endpoints.
3
+ */
4
+ const API_URL = process.env.EXTERNAL_API_URL || "http://localhost:4000";
5
+ const SECRET_KEY = process.env.RECAPT_SECRET_KEY;
6
+ export function isApiConfigured() {
7
+ return !!SECRET_KEY;
8
+ }
9
+ export function getApiUrl() {
10
+ return API_URL;
11
+ }
12
+ export async function apiGet(path, params) {
13
+ if (!SECRET_KEY) {
14
+ return { error: "RECAPT_SECRET_KEY not configured" };
15
+ }
16
+ const url = new URL(`${API_URL}/v1beta/query${path}`);
17
+ if (params) {
18
+ for (const [key, value] of Object.entries(params)) {
19
+ if (value !== undefined) {
20
+ url.searchParams.set(key, String(value));
21
+ }
22
+ }
23
+ }
24
+ try {
25
+ const res = await fetch(url.toString(), {
26
+ method: "GET",
27
+ headers: {
28
+ "x-private-key": SECRET_KEY,
29
+ "Content-Type": "application/json",
30
+ },
31
+ });
32
+ if (!res.ok) {
33
+ const body = (await res.json().catch(() => ({})));
34
+ return { error: body.error || `HTTP ${res.status}` };
35
+ }
36
+ const data = (await res.json());
37
+ return { data };
38
+ }
39
+ catch (err) {
40
+ return { error: err instanceof Error ? err.message : String(err) };
41
+ }
42
+ }
43
+ export async function apiPost(path, body) {
44
+ if (!SECRET_KEY) {
45
+ return { error: "RECAPT_SECRET_KEY not configured" };
46
+ }
47
+ const url = `${API_URL}/v1beta/query${path}`;
48
+ try {
49
+ const res = await fetch(url, {
50
+ method: "POST",
51
+ headers: {
52
+ "x-private-key": SECRET_KEY,
53
+ "Content-Type": "application/json",
54
+ },
55
+ body: JSON.stringify(body),
56
+ });
57
+ if (!res.ok) {
58
+ const responseData = (await res.json().catch(() => ({})));
59
+ return { error: responseData.error || `HTTP ${res.status}` };
60
+ }
61
+ const data = (await res.json());
62
+ return { data };
63
+ }
64
+ catch (err) {
65
+ return { error: err instanceof Error ? err.message : String(err) };
66
+ }
67
+ }
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server entry point.
4
+ *
5
+ * Exposes recapt behavioral intelligence as tools for AI coding agents.
6
+ * Acts as a thin proxy to external-api query endpoints.
7
+ */
8
+ import "dotenv/config";
package/dist/index.js ADDED
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server entry point.
4
+ *
5
+ * Exposes recapt behavioral intelligence as tools for AI coding agents.
6
+ * Acts as a thin proxy to external-api query endpoints.
7
+ */
8
+ import "dotenv/config";
9
+ // @ts-ignore - MCP SDK types are complex
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ // @ts-ignore - MCP SDK types are complex
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import { isApiConfigured, getApiUrl } from "./api/client.js";
14
+ import { registerGetPageMetrics } from "./tools/getPageMetrics.js";
15
+ import { registerGetElementFriction } from "./tools/getElementFriction.js";
16
+ import { registerSearchSessions } from "./tools/searchSessions.js";
17
+ import { registerGetIssues } from "./tools/getIssues.js";
18
+ import { registerGetSessionDetails } from "./tools/getSessionDetails.js";
19
+ import { registerGetPageTrends } from "./tools/getPageTrends.js";
20
+ import { registerGetAnomalies } from "./tools/getAnomalies.js";
21
+ import { registerGetDomains } from "./tools/getDomains.js";
22
+ import { registerGetActionableIssues } from "./tools/getActionableIssues.js";
23
+ import { registerGetUxHealthReport } from "./tools/getUxHealthReport.js";
24
+ import { registerAnalyzeFlow } from "./tools/analyzeFlow.js";
25
+ import { registerAnalyzeFunnel } from "./tools/analyzeFunnel.js";
26
+ import { registerGetFlowFriction } from "./tools/getFlowFriction.js";
27
+ import { registerGetJourneyPatterns } from "./tools/getJourneyPatterns.js";
28
+ import { registerGetDeadClicks } from "./tools/getDeadClicks.js";
29
+ import { registerGetConsoleErrors } from "./tools/getConsoleErrors.js";
30
+ import { registerCompareCohorts } from "./tools/compareCohorts.js";
31
+ import { registerDetectRegressions } from "./tools/detectRegressions.js";
32
+ import { registerDiscoverPersonas } from "./tools/discoverPersonas.js";
33
+ import { registerGetFormFriction } from "./tools/getFormFriction.js";
34
+ import { registerScanSite } from "./tools/scanSite.js";
35
+ import { registerListPages } from "./tools/listPages.js";
36
+ import { registerComparePeriods } from "./tools/comparePeriods.js";
37
+ import { registerDetectDrift } from "./tools/detectDrift.js";
38
+ import { registerPredictOutcomes } from "./tools/predictOutcomes.js";
39
+ import { registerMemoryTools } from "./tools/memory.js";
40
+ import { registerListSessions } from "./tools/listSessions.js";
41
+ import { registerTriageSessions } from "./tools/triageSessions.js";
42
+ async function main() {
43
+ console.error("[MCP] Starting recapt behavioral intelligence server...");
44
+ if (!isApiConfigured()) {
45
+ console.error("[MCP] ERROR: RECAPT_SECRET_KEY environment variable is required");
46
+ process.exit(1);
47
+ }
48
+ console.error(`[MCP] API URL: ${getApiUrl()}`);
49
+ const server = new McpServer({
50
+ name: "recapt",
51
+ version: "1.0.0",
52
+ }, {
53
+ capabilities: { logging: {} },
54
+ instructions: `# Recapt Behavioral Intelligence
55
+
56
+ This server provides behavioral data from session recordings. Use these tools to understand user behavior, identify UX issues, and find friction points.
57
+
58
+ ## Metric Interpretation
59
+
60
+ All behavioral scores are 0-1 where higher = more of that signal:
61
+ - **frustration** > 0.3: Notable friction, > 0.5: High priority issue
62
+ - **confusion** > 0.3: Users uncertain, > 0.5: Significant clarity problem
63
+ - **confidence** < 0.4: Users hesitant, < 0.25: Major decision friction
64
+ - **health_score**: 0-100 where 85+: A (excellent), 70-84: B (good), 55-69: C (needs work), 40-54: D (poor), <40: F (critical)
65
+ - **drop_off_rate** > 0.3: Major leak point in a flow
66
+ - **spike_ratio** > 2: Critical frustration spike, likely recent regression
67
+
68
+ ## Tool Orchestration Strategies
69
+
70
+ ### Starting point - understand the landscape
71
+ 1. Use \`scan_site\` or \`list_pages\` to see all tracked pages with health grades
72
+ 2. Use \`get_ux_health_report\` for a comprehensive overview combining metrics, issues, and anomalies
73
+ 3. Use \`get_domains\` to see which domains are being tracked
74
+
75
+ ### Diagnosing a problematic page
76
+ 1. Start with \`get_page_metrics\` to see overall behavioral scores
77
+ 2. If frustration high → \`get_element_friction\` to find specific problematic elements
78
+ 3. If confusion high → \`get_dead_clicks\` to find misleading UI elements users click expecting action
79
+ 4. Check \`get_console_errors\` for JS errors that may cause unresponsiveness
80
+ 5. Use \`get_form_friction\` if the page has forms to find problematic fields
81
+
82
+ ### Understanding user flows
83
+ 1. \`get_journey_patterns\` for organic navigation discovery (where do users go?)
84
+ 2. \`analyze_flow\` between specific pages to see paths, bottlenecks, and friction
85
+ 3. \`analyze_funnel\` for conversion analysis through defined steps (cart → checkout → payment)
86
+ 4. \`get_flow_friction\` to automatically discover high-friction flows
87
+
88
+ ### Finding issues to fix
89
+ 1. \`get_issues\` for auto-detected problems (rage clicks, dead clicks, errors)
90
+ 2. \`get_actionable_issues\` for issues with element context
91
+ 3. \`detect_regressions\` to find recent degradations (compare last 24h vs baseline)
92
+ 4. \`get_anomalies\` for unusual sessions and frustration spikes
93
+
94
+ ### Comparing segments
95
+ 1. \`compare_cohorts\` to understand differences (mobile vs desktop, completed vs abandoned)
96
+ 2. \`compare_periods\` to measure impact of changes (before vs after deployment)
97
+ 3. \`discover_personas\` to find behavioral user segments
98
+
99
+ ### Deep-diving a session
100
+ 1. \`search_sessions\` with natural language to find relevant sessions
101
+ 2. \`list_sessions\` to browse by domain, status, or device
102
+ 3. \`get_session_details\` for behavioral timeline of a specific session
103
+
104
+ ### Monitoring over time
105
+ 1. \`get_page_trends\` for daily behavioral trends on a page
106
+ 2. \`detect_drift\` for gradual behavioral changes over weeks
107
+ 3. \`detect_regressions\` for sudden changes (deployment impact)
108
+
109
+ ## Reasoning Tips
110
+
111
+ - High rage clicks on body/root elements often indicate JS errors preventing interaction - check \`get_console_errors\`
112
+ - Backtrack hotspots (pages users return to repeatedly) suggest confusion or errors on subsequent pages
113
+ - Drop-off pages may be intentional exits (thank you page) or problems - check if it's a terminal page
114
+ - Compare frustration across device types with \`compare_cohorts\` - mobile often has different issues
115
+ - When frustration spikes recently, use \`detect_regressions\` to correlate with deployments
116
+ - Low confidence + high confusion = users don't understand what to do next
117
+ - High frustration + low confusion = users know what to do but can't (broken UI, errors)
118
+ - Use \`memory_save\` to store intermediate results when doing multi-step analysis
119
+
120
+ ## Available Tools
121
+
122
+ ### Site Overview
123
+ - \`get_domains\`: List configured domains
124
+ - \`scan_site\`: Quick health scan of top pages
125
+ - \`list_pages\`: List all tracked pages with metrics
126
+ - \`get_ux_health_report\`: Comprehensive health report
127
+
128
+ ### Page Analysis
129
+ - \`get_page_metrics\`: Behavioral metrics for a page
130
+ - \`get_element_friction\`: Per-element friction data
131
+ - \`get_page_trends\`: Daily trends over time
132
+ - \`get_dead_clicks\`: Unresponsive elements users click
133
+ - \`get_console_errors\`: JavaScript errors
134
+ - \`get_form_friction\`: Form field friction analysis
135
+
136
+ ### Flow Analysis
137
+ - \`analyze_flow\`: Navigation between specific pages
138
+ - \`analyze_funnel\`: Conversion through page sequence
139
+ - \`get_flow_friction\`: Discover high-friction flows
140
+ - \`get_journey_patterns\`: Navigation patterns and hotspots
141
+
142
+ ### Issue Detection
143
+ - \`get_issues\`: Auto-detected UX issues
144
+ - \`get_actionable_issues\`: Issues with element context
145
+ - \`get_anomalies\`: Unusual sessions and spikes
146
+ - \`detect_regressions\`: Recent degradations
147
+
148
+ ### Comparison & Segmentation
149
+ - \`compare_cohorts\`: Compare user segments
150
+ - \`compare_periods\`: Compare time periods
151
+ - \`discover_personas\`: Behavioral clustering
152
+ - \`detect_drift\`: Long-term behavioral changes
153
+
154
+ ### Session Analysis
155
+ - \`search_sessions\`: Natural language session search
156
+ - \`list_sessions\`: Filter sessions by criteria
157
+ - \`get_session_details\`: Session behavioral timeline
158
+ - \`predict_outcomes\`: Predict session outcomes
159
+ - \`triage_sessions\`: Auto-triage sessions with compromised UX
160
+
161
+ ### Session Triage
162
+ Use \`triage_sessions\` to automatically identify sessions that need attention:
163
+ - Surfaces sessions with user comments (direct feedback)
164
+ - Flags high-frustration sessions with rage clicks
165
+ - Includes console errors as evidence
166
+ - Returns replay URLs for quick investigation
167
+
168
+ Severity levels:
169
+ - **critical**: triage_score >= 0.7 OR has comment + high frustration
170
+ - **high**: triage_score >= 0.5
171
+ - **medium**: triage_score >= 0.3
172
+ - **low**: triage_score < 0.3
173
+
174
+ When triaging, prioritize:
175
+ 1. Sessions with user comments (explicit feedback is gold)
176
+ 2. Critical severity with rage clicks (users actively struggling)
177
+ 3. Sessions with console errors + high frustration (likely bugs)
178
+
179
+ ### Working Memory
180
+ - \`memory_save\`, \`memory_recall\`, \`memory_list\`, \`memory_delete\`, \`memory_clear\`: Store and retrieve intermediate results`,
181
+ });
182
+ registerGetDomains(server);
183
+ registerGetPageMetrics(server);
184
+ registerGetElementFriction(server);
185
+ registerSearchSessions(server);
186
+ registerGetIssues(server);
187
+ registerGetSessionDetails(server);
188
+ registerGetPageTrends(server);
189
+ registerGetAnomalies(server);
190
+ registerGetActionableIssues(server);
191
+ registerGetUxHealthReport(server);
192
+ registerAnalyzeFlow(server);
193
+ registerAnalyzeFunnel(server);
194
+ registerGetFlowFriction(server);
195
+ registerGetJourneyPatterns(server);
196
+ registerGetDeadClicks(server);
197
+ registerGetConsoleErrors(server);
198
+ registerCompareCohorts(server);
199
+ registerDetectRegressions(server);
200
+ registerDiscoverPersonas(server);
201
+ registerGetFormFriction(server);
202
+ registerScanSite(server);
203
+ registerListPages(server);
204
+ registerComparePeriods(server);
205
+ registerDetectDrift(server);
206
+ registerPredictOutcomes(server);
207
+ registerMemoryTools(server);
208
+ registerListSessions(server);
209
+ registerTriageSessions(server);
210
+ const transport = new StdioServerTransport();
211
+ await server.connect(transport);
212
+ console.error("[MCP] Server running on stdio");
213
+ }
214
+ main().catch((err) => {
215
+ console.error("[MCP] Fatal error:", err);
216
+ process.exit(1);
217
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * analyze_flow tool
3
+ *
4
+ * Analyzes user navigation flows between pages.
5
+ * Thin proxy to external-api /flows/analyze endpoint.
6
+ */
7
+ export declare function registerAnalyzeFlow(server: any): void;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * analyze_flow tool
3
+ *
4
+ * Analyzes user navigation flows between pages.
5
+ * Thin proxy to external-api /flows/analyze endpoint.
6
+ */
7
+ import { z } from "zod";
8
+ import { apiPost, isApiConfigured } from "../api/client.js";
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ export function registerAnalyzeFlow(server) {
11
+ server.registerTool("analyze_flow", {
12
+ description: "Analyze user navigation flows between pages. Returns paths with steps, " +
13
+ "behavioral metrics (frustration, confusion), success rates, and bottlenecks. " +
14
+ "Use this to understand how users navigate between specific pages.",
15
+ inputSchema: z.object({
16
+ start_page: z
17
+ .string()
18
+ .optional()
19
+ .describe("Starting page path (e.g., /pricing). Partial match supported."),
20
+ end_page: z
21
+ .string()
22
+ .optional()
23
+ .describe("Ending page path (e.g., /checkout). Partial match supported."),
24
+ days: z
25
+ .number()
26
+ .optional()
27
+ .default(7)
28
+ .describe("Number of days to analyze (default: 7)"),
29
+ }),
30
+ }, async ({ start_page, end_page, days, }) => {
31
+ if (!isApiConfigured()) {
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: JSON.stringify({ error: "API not configured" }),
37
+ },
38
+ ],
39
+ isError: true,
40
+ };
41
+ }
42
+ if (!start_page && !end_page) {
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: JSON.stringify({
48
+ error: "At least one of start_page or end_page is required",
49
+ }),
50
+ },
51
+ ],
52
+ isError: true,
53
+ };
54
+ }
55
+ const { data, error } = await apiPost("/flows/analyze", {
56
+ start_page,
57
+ end_page,
58
+ days: days ?? 7,
59
+ });
60
+ if (error) {
61
+ return {
62
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
63
+ isError: true,
64
+ };
65
+ }
66
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
67
+ });
68
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * analyze_funnel tool
3
+ *
4
+ * Analyzes conversion funnels through a defined sequence of pages.
5
+ * Thin proxy to external-api /flows/funnel endpoint.
6
+ */
7
+ export declare function registerAnalyzeFunnel(server: any): void;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * analyze_funnel tool
3
+ *
4
+ * Analyzes conversion funnels through a defined sequence of pages.
5
+ * Thin proxy to external-api /flows/funnel endpoint.
6
+ */
7
+ import { z } from "zod";
8
+ import { apiPost, isApiConfigured } from "../api/client.js";
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ export function registerAnalyzeFunnel(server) {
11
+ server.registerTool("analyze_funnel", {
12
+ description: "Analyze a conversion funnel through a sequence of pages. Returns per-step metrics " +
13
+ "(entered, converted, dropped, dwell time, frustration, confusion) and dropoff analysis " +
14
+ 'showing where users exit. Supports wildcards: "/checkout*" matches all checkout pages.',
15
+ inputSchema: z.object({
16
+ steps: z
17
+ .array(z.string())
18
+ .min(2)
19
+ .describe('Ordered page paths forming the funnel (e.g., ["/cart", "/checkout", "/payment"])'),
20
+ days: z
21
+ .number()
22
+ .optional()
23
+ .default(7)
24
+ .describe("Number of days to analyze (default: 7)"),
25
+ }),
26
+ }, async ({ steps, days }) => {
27
+ if (!isApiConfigured()) {
28
+ return {
29
+ content: [
30
+ {
31
+ type: "text",
32
+ text: JSON.stringify({ error: "API not configured" }),
33
+ },
34
+ ],
35
+ isError: true,
36
+ };
37
+ }
38
+ if (!steps || steps.length < 2) {
39
+ return {
40
+ content: [
41
+ {
42
+ type: "text",
43
+ text: JSON.stringify({
44
+ error: "At least 2 steps are required for funnel analysis",
45
+ }),
46
+ },
47
+ ],
48
+ isError: true,
49
+ };
50
+ }
51
+ const { data, error } = await apiPost("/flows/funnel", {
52
+ steps,
53
+ days: days ?? 7,
54
+ });
55
+ if (error) {
56
+ return {
57
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
58
+ isError: true,
59
+ };
60
+ }
61
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
62
+ });
63
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * compare_cohorts tool
3
+ *
4
+ * Compare two user cohorts side by side.
5
+ */
6
+ export declare function registerCompareCohorts(server: any): void;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * compare_cohorts tool
3
+ *
4
+ * Compare two user cohorts side by side.
5
+ */
6
+ import { z } from "zod";
7
+ import { apiPost, isApiConfigured } from "../api/client.js";
8
+ const cohortFilterSchema = z.object({
9
+ device: z
10
+ .enum(["desktop", "tablet", "mobile"])
11
+ .optional()
12
+ .describe("Filter by device type"),
13
+ outcome: z
14
+ .enum(["COMPLETED", "STRUGGLED", "BLOCKED", "DISENGAGED"])
15
+ .optional()
16
+ .describe("Filter by session outcome"),
17
+ pattern: z.string().optional().describe("Filter by behavioral pattern"),
18
+ page_path: z.string().optional().describe("Filter by page path"),
19
+ has_friction: z
20
+ .boolean()
21
+ .optional()
22
+ .describe("Filter for sessions with friction"),
23
+ has_rage_clicks: z
24
+ .boolean()
25
+ .optional()
26
+ .describe("Filter for sessions with rage clicks"),
27
+ });
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ export function registerCompareCohorts(server) {
30
+ server.registerTool("compare_cohorts", {
31
+ description: "Compare two user cohorts side by side. Each cohort is defined by filter criteria: " +
32
+ "device (desktop/tablet/mobile), outcome (COMPLETED/STRUGGLED/BLOCKED/DISENGAGED), " +
33
+ "pattern, has_friction, has_rage_clicks, or page_path. " +
34
+ "Returns behavioral metrics for each cohort and statistically significant differences. " +
35
+ 'Use this to answer "how do mobile users differ from desktop?", "what distinguishes ' +
36
+ 'users who complete vs abandon?", or "do form users struggle more than non-form users?"',
37
+ inputSchema: z.object({
38
+ cohort_a: cohortFilterSchema.describe('Filters for cohort A, e.g. {"device": "mobile"} or {"outcome": "COMPLETED"}'),
39
+ cohort_b: cohortFilterSchema.describe('Filters for cohort B, e.g. {"device": "desktop"} or {"outcome": "BLOCKED"}'),
40
+ cohort_a_label: z
41
+ .string()
42
+ .optional()
43
+ .describe("Human label for cohort A"),
44
+ cohort_b_label: z
45
+ .string()
46
+ .optional()
47
+ .describe("Human label for cohort B"),
48
+ page_path: z
49
+ .string()
50
+ .optional()
51
+ .describe("Scope comparison to a specific page"),
52
+ date_from: z.string().optional().describe("Start date (ISO format)"),
53
+ date_to: z.string().optional().describe("End date (ISO format)"),
54
+ }),
55
+ }, async ({ cohort_a, cohort_b, cohort_a_label, cohort_b_label, page_path, date_from, date_to, }) => {
56
+ if (!isApiConfigured()) {
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: JSON.stringify({ error: "API not configured" }),
62
+ },
63
+ ],
64
+ isError: true,
65
+ };
66
+ }
67
+ const { data, error } = await apiPost("/cohorts/compare", {
68
+ cohort_a,
69
+ cohort_b,
70
+ cohort_a_label,
71
+ cohort_b_label,
72
+ page_path,
73
+ date_from,
74
+ date_to,
75
+ });
76
+ if (error) {
77
+ return {
78
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
79
+ isError: true,
80
+ };
81
+ }
82
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
83
+ });
84
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * compare_periods tool
3
+ *
4
+ * Compare metrics between two time periods for a page.
5
+ */
6
+ export declare function registerComparePeriods(server: any): void;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * compare_periods tool
3
+ *
4
+ * Compare metrics between two time periods for a page.
5
+ */
6
+ import { z } from "zod";
7
+ import { apiPost, isApiConfigured } from "../api/client.js";
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ export function registerComparePeriods(server) {
10
+ server.registerTool("compare_periods", {
11
+ description: "Compare behavioral metrics between two time periods for a specific page. " +
12
+ "Returns metrics for each period and the deltas between them. " +
13
+ "Use this to measure the impact of changes or compare week-over-week performance.",
14
+ inputSchema: z.object({
15
+ page_path: z.string().describe("Page path to compare"),
16
+ period_a: z
17
+ .object({
18
+ from: z.string().describe("Start date for period A (ISO format)"),
19
+ to: z.string().describe("End date for period A (ISO format)"),
20
+ })
21
+ .describe("First time period"),
22
+ period_b: z
23
+ .object({
24
+ from: z.string().describe("Start date for period B (ISO format)"),
25
+ to: z.string().describe("End date for period B (ISO format)"),
26
+ })
27
+ .describe("Second time period"),
28
+ }),
29
+ }, async ({ page_path, period_a, period_b, }) => {
30
+ if (!isApiConfigured()) {
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: JSON.stringify({ error: "API not configured" }),
36
+ },
37
+ ],
38
+ isError: true,
39
+ };
40
+ }
41
+ const { data, error } = await apiPost("/pages/compare-periods", {
42
+ page_path,
43
+ period_a,
44
+ period_b,
45
+ });
46
+ if (error) {
47
+ return {
48
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
49
+ isError: true,
50
+ };
51
+ }
52
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
53
+ });
54
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * detect_drift tool
3
+ *
4
+ * Detect behavioral drift over time.
5
+ */
6
+ export declare function registerDetectDrift(server: any): void;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * detect_drift tool
3
+ *
4
+ * Detect behavioral drift over time.
5
+ */
6
+ import { z } from "zod";
7
+ import { apiGet, isApiConfigured } from "../api/client.js";
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ export function registerDetectDrift(server) {
10
+ server.registerTool("detect_drift", {
11
+ description: "Detect behavioral drift over time by analyzing how user behavior patterns change. " +
12
+ "Splits data into time windows and measures centroid shift between consecutive periods. " +
13
+ "Use this for long-term monitoring to detect gradual UX degradation.",
14
+ inputSchema: z.object({
15
+ page_path: z
16
+ .string()
17
+ .optional()
18
+ .describe("Scope drift detection to a specific page"),
19
+ days: z
20
+ .number()
21
+ .optional()
22
+ .default(30)
23
+ .describe("Number of days to analyze (default 30)"),
24
+ window_size: z
25
+ .number()
26
+ .optional()
27
+ .default(7)
28
+ .describe("Size of each time window in days (default 7)"),
29
+ }),
30
+ }, async ({ page_path, days, window_size, }) => {
31
+ if (!isApiConfigured()) {
32
+ return {
33
+ content: [
34
+ {
35
+ type: "text",
36
+ text: JSON.stringify({ error: "API not configured" }),
37
+ },
38
+ ],
39
+ isError: true,
40
+ };
41
+ }
42
+ const { data, error } = await apiGet("/drift", {
43
+ page_path,
44
+ days: days ?? 30,
45
+ window_size: window_size ?? 7,
46
+ });
47
+ if (error) {
48
+ return {
49
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
50
+ isError: true,
51
+ };
52
+ }
53
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
54
+ });
55
+ }