@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,51 @@
1
+ /**
2
+ * scan_site tool
3
+ *
4
+ * One-shot site health scan across all pages.
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 registerScanSite(server) {
10
+ server.registerTool("scan_site", {
11
+ description: "One-shot site health scan. Lists all pages and computes aggregate metrics " +
12
+ "for the top-N pages (default 10) sorted by session count. Returns a ranked " +
13
+ "table with frustration and health grade per page. " +
14
+ "Use this as the FIRST tool when the user asks a broad question without naming a specific page.",
15
+ inputSchema: z.object({
16
+ top_n: z
17
+ .number()
18
+ .optional()
19
+ .default(10)
20
+ .describe("Number of top pages to analyze (default 10)"),
21
+ offset: z
22
+ .number()
23
+ .optional()
24
+ .default(0)
25
+ .describe("Skip first N pages for pagination (default 0)"),
26
+ }),
27
+ }, async ({ top_n, offset }) => {
28
+ if (!isApiConfigured()) {
29
+ return {
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: JSON.stringify({ error: "API not configured" }),
34
+ },
35
+ ],
36
+ isError: true,
37
+ };
38
+ }
39
+ const { data, error } = await apiGet("/site/overview", {
40
+ top_n: top_n ?? 10,
41
+ offset: offset ?? 0,
42
+ });
43
+ if (error) {
44
+ return {
45
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
46
+ isError: true,
47
+ };
48
+ }
49
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
50
+ });
51
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * search_sessions tool
3
+ *
4
+ * Search sessions by natural language query using vector search.
5
+ */
6
+ export declare function registerSearchSessions(server: any): void;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * search_sessions tool
3
+ *
4
+ * Search sessions by natural language query using vector search.
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 registerSearchSessions(server) {
10
+ server.registerTool("search_sessions", {
11
+ description: 'Search sessions by natural language query. Examples: "frustrated users on checkout", "rage clicks on pricing page", "confused users who abandoned cart".',
12
+ inputSchema: z.object({
13
+ query: z
14
+ .string()
15
+ .describe("Natural language search query describing the sessions you want to find"),
16
+ page_path: z
17
+ .string()
18
+ .optional()
19
+ .describe("Optional: filter to a specific page path"),
20
+ limit: z
21
+ .number()
22
+ .optional()
23
+ .default(10)
24
+ .describe("Maximum number of sessions to return (default: 10)"),
25
+ }),
26
+ }, async ({ query, page_path, limit, }) => {
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
+ const { data, error } = await apiPost("/sessions/search", {
39
+ query,
40
+ page_path,
41
+ limit: limit ?? 10,
42
+ });
43
+ if (error) {
44
+ return {
45
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
46
+ isError: true,
47
+ };
48
+ }
49
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
50
+ });
51
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * triage_sessions tool
3
+ *
4
+ * Automatically triages sessions to find compromised user experiences.
5
+ * Analyzes user comments, frustration signals, rage clicks, and console errors.
6
+ * Thin proxy to external-api /sessions/triage endpoint.
7
+ */
8
+ export declare function registerTriageSessions(server: any): void;
@@ -0,0 +1,195 @@
1
+ /**
2
+ * triage_sessions tool
3
+ *
4
+ * Automatically triages sessions to find compromised user experiences.
5
+ * Analyzes user comments, frustration signals, rage clicks, and console errors.
6
+ * Thin proxy to external-api /sessions/triage endpoint.
7
+ */
8
+ import { z } from "zod";
9
+ import { apiGet, isApiConfigured } from "../api/client.js";
10
+ function formatDuration(seconds) {
11
+ if (!seconds)
12
+ return "unknown";
13
+ if (seconds < 60)
14
+ return `${seconds}s`;
15
+ if (seconds < 3600)
16
+ return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
17
+ const hours = Math.floor(seconds / 3600);
18
+ const mins = Math.floor((seconds % 3600) / 60);
19
+ return `${hours}h ${mins}m`;
20
+ }
21
+ function getActionReason(session) {
22
+ const reasons = [];
23
+ if (session.user_comments.length > 0) {
24
+ reasons.push(`USER FEEDBACK: ${session.user_comments.length} comment(s) - "${session.user_comments[0].text.slice(0, 100)}${session.user_comments[0].text.length > 100 ? "..." : ""}"`);
25
+ }
26
+ if (session.behavioral_signals.frustration >= 0.5) {
27
+ reasons.push(`HIGH FRUSTRATION: ${(session.behavioral_signals.frustration * 100).toFixed(0)}%`);
28
+ }
29
+ else if (session.behavioral_signals.frustration >= 0.3) {
30
+ reasons.push(`MODERATE FRUSTRATION: ${(session.behavioral_signals.frustration * 100).toFixed(0)}%`);
31
+ }
32
+ if (session.behavioral_signals.has_rage_clicks) {
33
+ reasons.push("RAGE CLICKS detected");
34
+ }
35
+ if (session.console_errors.length > 0) {
36
+ const errorCount = session.console_errors.reduce((sum, e) => sum + e.count, 0);
37
+ reasons.push(`JS ERRORS: ${errorCount} error(s) - "${session.console_errors[0].message.slice(0, 80)}..."`);
38
+ }
39
+ if (!session.behavioral_signals.has_behavioral_data) {
40
+ reasons.push("NOTE: No behavioral analysis data available for this session");
41
+ }
42
+ if (reasons.length === 0) {
43
+ reasons.push("Low severity - no major issues detected");
44
+ }
45
+ return reasons.join("\n - ");
46
+ }
47
+ function formatTriageOutput(data) {
48
+ const lines = [];
49
+ // Summary
50
+ lines.push("# SESSION TRIAGE RESULTS\n");
51
+ lines.push(`Total sessions analyzed: ${data.summary.total_sessions}`);
52
+ lines.push(`Sessions with issues: ${data.summary.flagged_sessions}`);
53
+ lines.push(`Sessions with user comments: ${data.summary.sessions_with_comments}`);
54
+ if (data.sessions.length === 0) {
55
+ lines.push("\nNo sessions require attention.");
56
+ return lines.join("\n");
57
+ }
58
+ // Group by severity
59
+ const critical = data.sessions.filter((s) => s.severity === "critical");
60
+ const high = data.sessions.filter((s) => s.severity === "high");
61
+ const medium = data.sessions.filter((s) => s.severity === "medium");
62
+ const low = data.sessions.filter((s) => s.severity === "low");
63
+ lines.push("\n## PRIORITY SUMMARY");
64
+ lines.push(`- CRITICAL: ${critical.length} session(s) - investigate immediately`);
65
+ lines.push(`- HIGH: ${high.length} session(s) - investigate soon`);
66
+ lines.push(`- MEDIUM: ${medium.length} session(s) - review when possible`);
67
+ lines.push(`- LOW: ${low.length} session(s) - likely okay`);
68
+ // Action recommendations
69
+ lines.push("\n## RECOMMENDED ACTIONS\n");
70
+ if (critical.length > 0) {
71
+ lines.push("### CRITICAL - Investigate Immediately\n");
72
+ for (const s of critical) {
73
+ lines.push(`**Session ${s.session_id.slice(-8)}** (${formatDuration(s.duration_seconds)}, ${s.device})`);
74
+ lines.push(` Replay: ${s.replay_url}`);
75
+ lines.push(` Score: ${(s.triage_score * 100).toFixed(0)}%`);
76
+ lines.push(` Why: ${getActionReason(s)}`);
77
+ if (s.pages_visited.length > 0) {
78
+ lines.push(` Pages: ${s.pages_visited.slice(0, 3).join(", ")}${s.pages_visited.length > 3 ? ` (+${s.pages_visited.length - 3} more)` : ""}`);
79
+ }
80
+ lines.push("");
81
+ }
82
+ }
83
+ if (high.length > 0) {
84
+ lines.push("### HIGH - Investigate Soon\n");
85
+ for (const s of high) {
86
+ lines.push(`**Session ${s.session_id.slice(-8)}** (${formatDuration(s.duration_seconds)}, ${s.device})`);
87
+ lines.push(` Replay: ${s.replay_url}`);
88
+ lines.push(` Score: ${(s.triage_score * 100).toFixed(0)}%`);
89
+ lines.push(` Why: ${getActionReason(s)}`);
90
+ if (s.pages_visited.length > 0) {
91
+ lines.push(` Pages: ${s.pages_visited.slice(0, 3).join(", ")}${s.pages_visited.length > 3 ? ` (+${s.pages_visited.length - 3} more)` : ""}`);
92
+ }
93
+ lines.push("");
94
+ }
95
+ }
96
+ if (medium.length > 0) {
97
+ lines.push("### MEDIUM - Review When Possible\n");
98
+ for (const s of medium) {
99
+ lines.push(`- Session ${s.session_id.slice(-8)} (${formatDuration(s.duration_seconds)}): ${getActionReason(s).split("\n")[0]}`);
100
+ lines.push(` ${s.replay_url}`);
101
+ }
102
+ lines.push("");
103
+ }
104
+ if (low.length > 0) {
105
+ lines.push("### LOW - Likely Okay\n");
106
+ lines.push(`${low.length} session(s) with minor or no issues detected.`);
107
+ if (low.length <= 3) {
108
+ for (const s of low) {
109
+ lines.push(`- Session ${s.session_id.slice(-8)}: ${s.replay_url}`);
110
+ }
111
+ }
112
+ lines.push("");
113
+ }
114
+ // Raw data for reference (compact)
115
+ lines.push("\n## RAW DATA (for detailed analysis)\n");
116
+ lines.push("```json");
117
+ lines.push(JSON.stringify({
118
+ summary: data.summary,
119
+ sessions: data.sessions.map((s) => ({
120
+ id: s.session_id,
121
+ severity: s.severity,
122
+ score: s.triage_score,
123
+ url: s.replay_url,
124
+ comments: s.user_comments.length,
125
+ frustration: s.behavioral_signals.frustration,
126
+ rage_clicks: s.behavioral_signals.has_rage_clicks,
127
+ errors: s.console_errors.length,
128
+ has_data: s.behavioral_signals.has_behavioral_data,
129
+ })),
130
+ }, null, 2));
131
+ lines.push("```");
132
+ return lines.join("\n");
133
+ }
134
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
135
+ export function registerTriageSessions(server) {
136
+ server.registerTool("triage_sessions", {
137
+ description: "Automatically triage sessions to find compromised user experiences. " +
138
+ "Analyzes user comments, frustration signals, rage clicks, and console errors. " +
139
+ "Returns flagged sessions with evidence and replay links. Use this to identify " +
140
+ "sessions that need attention without manually reviewing every recording. " +
141
+ "Can triage specific sessions by ID(s) or scan for problematic sessions.",
142
+ inputSchema: z.object({
143
+ session_ids: z
144
+ .array(z.string())
145
+ .optional()
146
+ .describe("Triage specific sessions by ID. If provided, other filters are ignored."),
147
+ days: z
148
+ .number()
149
+ .optional()
150
+ .default(7)
151
+ .describe("Lookback period in days (default: 7). Ignored if session_ids is provided."),
152
+ page_path: z
153
+ .string()
154
+ .optional()
155
+ .describe("Filter to sessions that visited a specific page"),
156
+ min_severity: z
157
+ .enum(["critical", "high", "medium", "low"])
158
+ .optional()
159
+ .describe("Minimum severity to include in results"),
160
+ limit: z
161
+ .number()
162
+ .optional()
163
+ .default(20)
164
+ .describe("Maximum sessions to return (default: 20, max: 100)"),
165
+ }),
166
+ }, async ({ session_ids, days, page_path, min_severity, limit, }) => {
167
+ if (!isApiConfigured()) {
168
+ return {
169
+ content: [
170
+ {
171
+ type: "text",
172
+ text: JSON.stringify({ error: "API not configured" }),
173
+ },
174
+ ],
175
+ isError: true,
176
+ };
177
+ }
178
+ const { data, error } = await apiGet("/sessions/triage", {
179
+ session_ids: session_ids?.join(","),
180
+ days: session_ids?.length ? undefined : (days ?? 7),
181
+ page_path: session_ids?.length ? undefined : page_path,
182
+ min_severity,
183
+ limit: session_ids?.length ? session_ids.length : (limit ?? 20),
184
+ });
185
+ if (error) {
186
+ return {
187
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
188
+ isError: true,
189
+ };
190
+ }
191
+ // Format output for better LLM comprehension
192
+ const formattedOutput = formatTriageOutput(data);
193
+ return { content: [{ type: "text", text: formattedOutput }] };
194
+ });
195
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Organization context for the MCP server.
3
+ * Reads RECAPT_ORG_ID from environment and validates it.
4
+ */
5
+ export declare function getOrgId(): string;
6
+ export declare function hasOrgId(): boolean;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Organization context for the MCP server.
3
+ * Reads RECAPT_ORG_ID from environment and validates it.
4
+ */
5
+ let cachedOrgId = null;
6
+ export function getOrgId() {
7
+ if (cachedOrgId)
8
+ return cachedOrgId;
9
+ const orgId = process.env.RECAPT_ORG_ID;
10
+ if (!orgId) {
11
+ throw new Error('RECAPT_ORG_ID environment variable is required. ' +
12
+ 'Set it to the MongoDB ObjectId of the organization to query.');
13
+ }
14
+ if (!/^[a-f0-9]{24}$/i.test(orgId)) {
15
+ throw new Error(`RECAPT_ORG_ID must be a valid 24-character MongoDB ObjectId. Got: ${orgId}`);
16
+ }
17
+ cachedOrgId = orgId;
18
+ return orgId;
19
+ }
20
+ export function hasOrgId() {
21
+ return !!process.env.RECAPT_ORG_ID;
22
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@recapt/mcp",
3
+ "version": "0.0.2-beta",
4
+ "description": "MCP exposing recapt behavioral intelligence to AI coding agents",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "clean": "rimraf ./dist",
11
+ "dev": "tsx --watch src/index.ts",
12
+ "start": "node dist/index.js",
13
+ "prettify": "prettier --write .",
14
+ "type-check": "tsc --noEmit"
15
+ },
16
+ "bin": {
17
+ "recapt-mcp": "./dist/index.js"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+
23
+ "author": "Recapt",
24
+ "license": "ISC",
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "^1.12.0",
27
+ "zod": "^3.24.0"
28
+ },
29
+ "peerDependencies": {
30
+ "@types/node": "^24.3.1",
31
+ "dotenv": "^17.2.2",
32
+ "prettier": "^3.8.1",
33
+ "rimraf": "^6.0.1",
34
+ "tsx": "^4.16.5",
35
+ "typescript": "5.9.3"
36
+ }
37
+ }