@recapt/mcp 0.0.12-beta → 0.0.14-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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Setup Self-Healing GitHub Command
3
+ *
4
+ * Interactive wizard to set up the self-healing agentic workflow for GitHub.
5
+ */
6
+ import { Command } from "commander";
7
+ export declare const setupSelfHealingGhCommand: Command;
@@ -0,0 +1,310 @@
1
+ /**
2
+ * Setup Self-Healing GitHub Command
3
+ *
4
+ * Interactive wizard to set up the self-healing agentic workflow for GitHub.
5
+ */
6
+ import { Command } from "commander";
7
+ import { exec, execSync } from "child_process";
8
+ import fs from "fs";
9
+ import path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import { installSkill } from "./skill.js";
12
+ import { confirm, select, multiSelect, input, header, print, success, error, info, warn, newline, } from "../utils/prompts.js";
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ const TEMPLATES_DIR = path.resolve(__dirname, "../../../templates");
16
+ const WORKFLOW_TEMPLATE = "self-healing.md";
17
+ const WORKFLOW_DEST = ".github/workflows/self-healing.md";
18
+ const ENGINES = [
19
+ { id: "claude", name: "Claude (Anthropic)", secretName: "ANTHROPIC_API_KEY" },
20
+ { id: "copilot", name: "GitHub Copilot", secretName: "COPILOT_GITHUB_TOKEN" },
21
+ { id: "codex", name: "Codex (OpenAI)", secretName: "OPENAI_API_KEY" },
22
+ ];
23
+ const API_URL = process.env.RECAPT_API_URL ||
24
+ process.env.EXTERNAL_API_URL ||
25
+ "https://api.recapt.app";
26
+ async function fetchDomains(secretKey) {
27
+ try {
28
+ const res = await fetch(`${API_URL}/v1beta/query/domains`, {
29
+ headers: {
30
+ "x-private-key": secretKey,
31
+ "Content-Type": "application/json",
32
+ },
33
+ });
34
+ if (!res.ok) {
35
+ return null;
36
+ }
37
+ const data = (await res.json());
38
+ return data;
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ function commandExists(cmd) {
45
+ try {
46
+ execSync(`which ${cmd}`, { stdio: "ignore" });
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ function ghExtensionInstalled(extension) {
54
+ try {
55
+ const output = execSync("gh extension list", { encoding: "utf-8" });
56
+ return output.includes(extension);
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ function isGitRepo() {
63
+ try {
64
+ execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
65
+ return true;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ function getRepoUrl() {
72
+ try {
73
+ const url = execSync("gh repo view --json url -q .url", {
74
+ encoding: "utf-8",
75
+ }).trim();
76
+ return url || null;
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ function openBrowser(url) {
83
+ const platform = process.platform;
84
+ let command;
85
+ switch (platform) {
86
+ case "darwin":
87
+ command = `open "${url}"`;
88
+ break;
89
+ case "win32":
90
+ command = `start "" "${url}"`;
91
+ break;
92
+ default:
93
+ command = `xdg-open "${url}"`;
94
+ }
95
+ exec(command, (err) => {
96
+ if (err) {
97
+ info(`Could not open browser automatically. Please visit: ${url}`);
98
+ }
99
+ });
100
+ }
101
+ async function runSetupSelfHealingGh() {
102
+ header("Recapt Self-Healing GitHub Setup");
103
+ const cwd = process.cwd();
104
+ print("Checking prerequisites...");
105
+ newline();
106
+ const hasGh = commandExists("gh");
107
+ print(` ${hasGh ? "[x]" : "[ ]"} gh CLI installed`);
108
+ if (!hasGh) {
109
+ newline();
110
+ error("GitHub CLI (gh) is required but not installed.");
111
+ info("Install it from: https://cli.github.com/");
112
+ process.exit(1);
113
+ }
114
+ const hasGhAw = ghExtensionInstalled("github/gh-aw");
115
+ print(` ${hasGhAw ? "[x]" : "[ ]"} gh-aw extension installed`);
116
+ if (!hasGhAw) {
117
+ newline();
118
+ const shouldInstall = await confirm("gh-aw extension is required. Install it now?", true);
119
+ if (shouldInstall) {
120
+ print("Installing gh-aw extension...");
121
+ try {
122
+ execSync("gh extension install github/gh-aw", { stdio: "inherit" });
123
+ success("gh-aw extension installed");
124
+ }
125
+ catch {
126
+ error("Failed to install gh-aw extension");
127
+ info("Try manually: gh extension install github/gh-aw");
128
+ process.exit(1);
129
+ }
130
+ }
131
+ else {
132
+ error("gh-aw extension is required to continue.");
133
+ process.exit(1);
134
+ }
135
+ }
136
+ const inGitRepo = isGitRepo();
137
+ print(` ${inGitRepo ? "[x]" : "[ ]"} Git repository detected`);
138
+ if (!inGitRepo) {
139
+ newline();
140
+ error("This command must be run from within a git repository.");
141
+ process.exit(1);
142
+ }
143
+ newline();
144
+ const engineOptions = ENGINES.map((engine) => ({
145
+ label: `${engine.name} (requires ${engine.secretName} secret)`,
146
+ value: engine,
147
+ }));
148
+ const selectedEngine = await select("Which AI engine do you want to use?", engineOptions);
149
+ if (!selectedEngine) {
150
+ error("No engine selected.");
151
+ process.exit(1);
152
+ }
153
+ newline();
154
+ // Domain selection
155
+ print("Configuring domain filter...");
156
+ info("You can optionally limit the workflow to specific domains.");
157
+ newline();
158
+ let selectedDomains = [];
159
+ const recaptKey = await input("Enter your RECAPT_SECRET_KEY to fetch domains (or press Enter to skip)");
160
+ if (recaptKey) {
161
+ const domains = await fetchDomains(recaptKey);
162
+ if (domains && domains.length > 0) {
163
+ const domainOptions = [
164
+ { label: "All domains", value: "__all__", selected: true },
165
+ ...domains.map((d) => ({
166
+ label: d.domainName,
167
+ value: d.domainName,
168
+ selected: false,
169
+ })),
170
+ ];
171
+ const selected = await multiSelect("Which domains should this workflow analyze?", domainOptions);
172
+ if (!selected.includes("__all__") && selected.length > 0) {
173
+ selectedDomains = selected;
174
+ success(`Selected domains: ${selectedDomains.join(", ")}`);
175
+ }
176
+ else {
177
+ info("Will analyze all domains");
178
+ }
179
+ }
180
+ else {
181
+ warn("Could not fetch domains. Will analyze all domains.");
182
+ }
183
+ }
184
+ else {
185
+ const manualDomain = await input("Enter a specific domain to filter (or press Enter for all domains)");
186
+ if (manualDomain) {
187
+ selectedDomains = [manualDomain];
188
+ success(`Selected domain: ${manualDomain}`);
189
+ }
190
+ else {
191
+ info("Will analyze all domains");
192
+ }
193
+ }
194
+ newline();
195
+ print("Installing self-healing skill...");
196
+ const skillInstalled = installSkill(cwd, "self-healing");
197
+ if (!skillInstalled) {
198
+ error("Failed to install self-healing skill");
199
+ process.exit(1);
200
+ }
201
+ newline();
202
+ print("Creating workflow...");
203
+ const templatePath = path.join(TEMPLATES_DIR, WORKFLOW_TEMPLATE);
204
+ const destPath = path.join(cwd, WORKFLOW_DEST);
205
+ const destDir = path.dirname(destPath);
206
+ if (!fs.existsSync(templatePath)) {
207
+ error(`Workflow template not found: ${templatePath}`);
208
+ process.exit(1);
209
+ }
210
+ if (!fs.existsSync(destDir)) {
211
+ fs.mkdirSync(destDir, { recursive: true });
212
+ }
213
+ const templateContent = fs.readFileSync(templatePath, "utf-8");
214
+ // Build domain config section
215
+ let domainConfig = "";
216
+ if (selectedDomains.length > 0) {
217
+ const domainList = selectedDomains.join(", ");
218
+ domainConfig = `### Domain Restriction
219
+
220
+ **Target domains:** ${domainList}
221
+
222
+ Before doing any work:
223
+ 1. Call \`get_domains\` to verify the target domain(s) exist
224
+ 2. If any target domain is NOT in the list, stop immediately and report: "Domain not found: [domain]. Available domains: [list]"
225
+ 3. If domains are valid, pass the \`domain\` parameter to all tools that support filtering (e.g., \`run_full_diagnostic\`, \`get_page_metrics\`, etc.)
226
+
227
+ Do NOT analyze or fix issues for domains not in the target list.
228
+
229
+ `;
230
+ }
231
+ else {
232
+ domainConfig = `### Domain Scope
233
+
234
+ **Target:** All domains
235
+
236
+ Analyze and fix issues across all domains configured in recapt. Call \`get_domains\` first to see available domains.
237
+
238
+ `;
239
+ }
240
+ // Replace placeholders (support both {{VAR}} and { { VAR } } formats)
241
+ const workflowContent = templateContent
242
+ .replace(/\{\s*\{\s*ENGINE\s*\}\s*\}/g, selectedEngine.id)
243
+ .replace(/\{\s*\{\s*DOMAIN_CONFIG\s*\}\s*\}/g, domainConfig);
244
+ if (fs.existsSync(destPath)) {
245
+ const overwrite = await confirm("Workflow file already exists. Overwrite?", false);
246
+ if (!overwrite) {
247
+ info("Skipping workflow creation");
248
+ }
249
+ else {
250
+ fs.writeFileSync(destPath, workflowContent);
251
+ success(`Created: ${WORKFLOW_DEST}`);
252
+ }
253
+ }
254
+ else {
255
+ fs.writeFileSync(destPath, workflowContent);
256
+ success(`Created: ${WORKFLOW_DEST}`);
257
+ }
258
+ newline();
259
+ print("Compiling workflow...");
260
+ try {
261
+ execSync("gh aw compile", { cwd, stdio: "inherit" });
262
+ success("Created: .github/workflows/self-healing.lock.yml");
263
+ }
264
+ catch {
265
+ warn("Failed to compile workflow. You can run 'gh aw compile' manually.");
266
+ }
267
+ newline();
268
+ print("GitHub Secrets Required:");
269
+ info(`1. ${selectedEngine.secretName} - For ${selectedEngine.name} to run the agent`);
270
+ info("2. RECAPT_SECRET_KEY - For recapt behavioral intelligence");
271
+ newline();
272
+ const repoUrl = getRepoUrl();
273
+ if (repoUrl) {
274
+ const secretsUrl = `${repoUrl}/settings/secrets/actions`;
275
+ const openSecrets = await confirm("Open GitHub secrets page?", true);
276
+ if (openSecrets) {
277
+ openBrowser(secretsUrl);
278
+ await new Promise((resolve) => setTimeout(resolve, 1000));
279
+ }
280
+ }
281
+ else {
282
+ info("Go to your repository Settings → Secrets and variables → Actions");
283
+ info(`Add ${selectedEngine.secretName} and RECAPT_SECRET_KEY as secrets.`);
284
+ }
285
+ newline();
286
+ success("Setup complete!");
287
+ newline();
288
+ print("Next steps:");
289
+ print(' 1. git add . && git commit -m "Add recapt self-healing workflow"');
290
+ print(" 2. git push");
291
+ print(" 3. Run manually: gh aw run self-healing");
292
+ newline();
293
+ warn("Important: Enable PR creation permissions");
294
+ info("The workflow needs permission to create pull requests.");
295
+ info("Go to Settings → Actions → General → Workflow permissions");
296
+ info('Enable "Allow GitHub Actions to create and approve pull requests"');
297
+ info("(This may need to be set at the organization level)");
298
+ newline();
299
+ }
300
+ export const setupSelfHealingGhCommand = new Command("setup-self-healing-gh")
301
+ .description("Set up automated self-healing workflow for GitHub Actions")
302
+ .action(async () => {
303
+ try {
304
+ await runSetupSelfHealingGh();
305
+ }
306
+ catch (err) {
307
+ error(`Setup failed: ${err}`);
308
+ process.exit(1);
309
+ }
310
+ });
@@ -12,6 +12,7 @@ interface SkillInfo {
12
12
  }
13
13
  export type { SkillInfo };
14
14
  export declare function getAvailableSkills(): SkillInfo[];
15
+ export declare function installSkill(cwd: string, skillName: string): boolean;
15
16
  export interface InstallResult {
16
17
  success: boolean;
17
18
  installed: string[];
@@ -115,7 +115,7 @@ ${MARKER_END}`;
115
115
  }
116
116
  writeAgentsMd(cwd, content + "\n");
117
117
  }
118
- function installSkill(cwd, skillName) {
118
+ export function installSkill(cwd, skillName) {
119
119
  let skills;
120
120
  try {
121
121
  skills = getAvailableSkills();
@@ -5,7 +5,8 @@
5
5
  * Unified command-line interface for recapt MCP server management.
6
6
  *
7
7
  * Commands:
8
- * setup - Configure recapt MCP server for your AI IDE(s)
9
- * skill - Manage recapt workflow skills
8
+ * setup - Configure recapt MCP server for your AI IDE(s)
9
+ * setup-self-healing-gh - Set up automated self-healing workflow for GitHub
10
+ * skill - Manage recapt workflow skills
10
11
  */
11
12
  export {};
package/dist/cli/index.js CHANGED
@@ -5,13 +5,15 @@
5
5
  * Unified command-line interface for recapt MCP server management.
6
6
  *
7
7
  * Commands:
8
- * setup - Configure recapt MCP server for your AI IDE(s)
9
- * skill - Manage recapt workflow skills
8
+ * setup - Configure recapt MCP server for your AI IDE(s)
9
+ * setup-self-healing-gh - Set up automated self-healing workflow for GitHub
10
+ * skill - Manage recapt workflow skills
10
11
  */
11
12
  import { Command } from "commander";
12
13
  import { createRequire } from "module";
13
14
  import { skillCommand } from "./commands/skill.js";
14
15
  import { setupCommand } from "./commands/setup.js";
16
+ import { setupSelfHealingGhCommand } from "./commands/setup-self-healing-gh.js";
15
17
  const require = createRequire(import.meta.url);
16
18
  const pkg = require("../../package.json");
17
19
  const program = new Command();
@@ -20,5 +22,6 @@ program
20
22
  .description("Recapt MCP CLI - Configure and manage recapt for AI IDEs")
21
23
  .version(pkg.version);
22
24
  program.addCommand(setupCommand);
25
+ program.addCommand(setupSelfHealingGhCommand);
23
26
  program.addCommand(skillCommand);
24
27
  program.parse();
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Interactive Prompts
3
3
  *
4
- * Readline-based prompts for CLI interactions.
4
+ * Arrow-key navigable prompts for CLI interactions using @inquirer/prompts.
5
5
  */
6
6
  export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
7
7
  export declare function input(message: string, defaultValue?: string): Promise<string>;
@@ -1,110 +1,47 @@
1
1
  /**
2
2
  * Interactive Prompts
3
3
  *
4
- * Readline-based prompts for CLI interactions.
4
+ * Arrow-key navigable prompts for CLI interactions using @inquirer/prompts.
5
5
  */
6
- import readline from "readline";
7
- function createInterface() {
8
- return readline.createInterface({
9
- input: process.stdin,
10
- output: process.stdout,
11
- });
12
- }
6
+ import { confirm as inquirerConfirm, input as inquirerInput, password as inquirerPassword, select as inquirerSelect, checkbox as inquirerCheckbox, } from "@inquirer/prompts";
13
7
  export async function confirm(message, defaultYes = true) {
14
- const hint = defaultYes ? "(Y/n)" : "(y/N)";
15
- const askOnce = () => {
16
- const rl = createInterface();
17
- return new Promise((resolve) => {
18
- rl.question(`${message} ${hint} `, (answer) => {
19
- rl.close();
20
- const normalized = answer.trim().toLowerCase();
21
- if (normalized === "") {
22
- resolve(defaultYes);
23
- }
24
- else if (normalized === "y" || normalized === "yes") {
25
- resolve(true);
26
- }
27
- else if (normalized === "n" || normalized === "no") {
28
- resolve(false);
29
- }
30
- else {
31
- resolve(null);
32
- }
33
- });
34
- });
35
- };
36
- let result = await askOnce();
37
- while (result === null) {
38
- console.log('Please enter "y" or "n"');
39
- result = await askOnce();
40
- }
41
- return result;
8
+ return inquirerConfirm({
9
+ message,
10
+ default: defaultYes,
11
+ });
42
12
  }
43
13
  export async function input(message, defaultValue) {
44
- const rl = createInterface();
45
- const hint = defaultValue ? ` (${defaultValue})` : "";
46
- return new Promise((resolve) => {
47
- rl.question(`${message}${hint}: `, (answer) => {
48
- rl.close();
49
- const value = answer.trim();
50
- resolve(value || defaultValue || "");
51
- });
14
+ return inquirerInput({
15
+ message,
16
+ default: defaultValue,
52
17
  });
53
18
  }
54
19
  export async function secret(message) {
55
- const rl = createInterface();
56
- return new Promise((resolve) => {
57
- rl.question(`${message}: `, (answer) => {
58
- rl.close();
59
- resolve(answer.trim());
60
- });
20
+ return inquirerPassword({
21
+ message,
22
+ mask: "*",
61
23
  });
62
24
  }
63
25
  export async function multiSelect(message, options) {
64
- const rl = createInterface();
65
- console.log(`\n${message}`);
66
- console.log("(Enter comma-separated numbers, or 'all' for all options)\n");
67
- options.forEach((opt, i) => {
68
- const marker = opt.selected ? "[x]" : "[ ]";
69
- console.log(` ${i + 1}. ${marker} ${opt.label}`);
70
- });
71
- return new Promise((resolve) => {
72
- rl.question("\nYour selection: ", (answer) => {
73
- rl.close();
74
- const normalized = answer.trim().toLowerCase();
75
- if (normalized === "all" || normalized === "a") {
76
- resolve(options.map((o) => o.value));
77
- return;
78
- }
79
- if (normalized === "" || normalized === "none" || normalized === "n") {
80
- resolve(options.filter((o) => o.selected).map((o) => o.value));
81
- return;
82
- }
83
- const indices = normalized
84
- .split(/[,\s]+/)
85
- .map((s) => parseInt(s, 10) - 1)
86
- .filter((i) => i >= 0 && i < options.length);
87
- resolve(indices.map((i) => options[i].value));
88
- });
26
+ return inquirerCheckbox({
27
+ message,
28
+ choices: options.map((opt) => ({
29
+ name: opt.label,
30
+ value: opt.value,
31
+ checked: opt.selected ?? false,
32
+ })),
89
33
  });
90
34
  }
91
35
  export async function select(message, options) {
92
- const rl = createInterface();
93
- console.log(`\n${message}\n`);
94
- options.forEach((opt, i) => {
95
- console.log(` ${i + 1}. ${opt.label}`);
96
- });
97
- return new Promise((resolve) => {
98
- rl.question("\nYour selection: ", (answer) => {
99
- rl.close();
100
- const index = parseInt(answer.trim(), 10) - 1;
101
- if (index >= 0 && index < options.length) {
102
- resolve(options[index].value);
103
- }
104
- else {
105
- resolve(null);
106
- }
107
- });
36
+ if (options.length === 0) {
37
+ return null;
38
+ }
39
+ return inquirerSelect({
40
+ message,
41
+ choices: options.map((opt) => ({
42
+ name: opt.label,
43
+ value: opt.value,
44
+ })),
108
45
  });
109
46
  }
110
47
  export function print(message) {
package/dist/index.js CHANGED
@@ -96,6 +96,46 @@ function registerHiddenTools() {
96
96
  // Session Analysis
97
97
  registerToolHandler("search_sessions", createApiHandler("POST", "/sessions/search"));
98
98
  registerToolHandler("list_sessions", createApiHandler("GET", "/sessions"));
99
+ registerToolHandler("get_session_pages", async (args) => {
100
+ if (!isApiConfigured()) {
101
+ return {
102
+ content: [
103
+ {
104
+ type: "text",
105
+ text: JSON.stringify({ error: "API not configured" }),
106
+ },
107
+ ],
108
+ isError: true,
109
+ };
110
+ }
111
+ const { session_id, session_ids } = args;
112
+ if (!session_id && (!session_ids || session_ids.length === 0)) {
113
+ return {
114
+ content: [
115
+ {
116
+ type: "text",
117
+ text: JSON.stringify({
118
+ error: "Either session_id or session_ids must be provided",
119
+ }),
120
+ },
121
+ ],
122
+ isError: true,
123
+ };
124
+ }
125
+ const isSingleSession = session_id && !session_ids;
126
+ const { data, error } = isSingleSession
127
+ ? await apiGet(`/sessions/${session_id}/pages`)
128
+ : await apiPost("/sessions/pages", {
129
+ session_ids: session_ids ?? [session_id],
130
+ });
131
+ if (error) {
132
+ return {
133
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
134
+ isError: true,
135
+ };
136
+ }
137
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
138
+ });
99
139
  registerToolHandler("get_session_details", async (args) => {
100
140
  if (!isApiConfigured()) {
101
141
  return {
@@ -14629,7 +14629,7 @@
14629
14629
  "category": {
14630
14630
  "type": "string",
14631
14631
  "required": false,
14632
- "description": "Filter by knowledge category"
14632
+ "description": "Filter by category: false_positive, intended_behavior, fix_pattern, or context"
14633
14633
  },
14634
14634
  "page_path": {
14635
14635
  "type": "string",
@@ -14639,7 +14639,7 @@
14639
14639
  "issue_category": {
14640
14640
  "type": "string",
14641
14641
  "required": false,
14642
- "description": "Filter by issue category"
14642
+ "description": "Filter by issue category: code_error, dead_click, rage_click, ux_friction, performance, form_issue, behavioral_anomaly, or structural_issue"
14643
14643
  },
14644
14644
  "limit": {
14645
14645
  "type": "number",
@@ -15042,7 +15042,7 @@
15042
15042
  "category": {
15043
15043
  "type": "string",
15044
15044
  "required": true,
15045
- "description": "Category of knowledge"
15045
+ "description": "Category: false_positive, intended_behavior, fix_pattern, or context"
15046
15046
  },
15047
15047
  "subject": {
15048
15048
  "type": "string",
@@ -15067,7 +15067,7 @@
15067
15067
  "issue_category": {
15068
15068
  "type": "string",
15069
15069
  "required": false,
15070
- "description": "Associated issue category"
15070
+ "description": "Issue category: code_error, dead_click, rage_click, ux_friction, performance, form_issue, behavioral_anomaly, or structural_issue"
15071
15071
  }
15072
15072
  },
15073
15073
  "embedding": [
@@ -0,0 +1,7 @@
1
+ /**
2
+ * get_session_pages tool
3
+ *
4
+ * Returns the pages visited within one or more sessions.
5
+ * Thin proxy to external-api /sessions/:id/pages and /sessions/pages endpoints.
6
+ */
7
+ export declare function registerGetSessionPages(server: any): void;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * get_session_pages tool
3
+ *
4
+ * Returns the pages visited within one or more sessions.
5
+ * Thin proxy to external-api /sessions/:id/pages and /sessions/pages endpoints.
6
+ */
7
+ import { z } from "zod";
8
+ import { apiGet, apiPost, isApiConfigured } from "../api/client.js";
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ export function registerGetSessionPages(server) {
11
+ server.registerTool("get_session_pages", {
12
+ description: "Get the pages visited within one or more sessions. Returns navigation history " +
13
+ "with timestamps, source pages, and dwell times. Use this to understand the " +
14
+ "user's journey through the site during a session. Accepts a single session_id " +
15
+ "or an array of session_ids (max 20).",
16
+ inputSchema: z.object({
17
+ session_id: z
18
+ .string()
19
+ .optional()
20
+ .describe("Single session ID to get pages for"),
21
+ session_ids: z
22
+ .array(z.string())
23
+ .max(20)
24
+ .optional()
25
+ .describe("Array of session IDs to get pages for (max 20)"),
26
+ }),
27
+ }, async ({ session_id, session_ids, }) => {
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
+ if (!session_id && (!session_ids || session_ids.length === 0)) {
40
+ return {
41
+ content: [
42
+ {
43
+ type: "text",
44
+ text: JSON.stringify({
45
+ error: "Either session_id or session_ids must be provided",
46
+ }),
47
+ },
48
+ ],
49
+ isError: true,
50
+ };
51
+ }
52
+ if (session_id && !session_ids) {
53
+ const { data, error } = await apiGet(`/sessions/${session_id}/pages`);
54
+ if (error) {
55
+ return {
56
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
57
+ isError: true,
58
+ };
59
+ }
60
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
61
+ }
62
+ const ids = session_ids ?? [session_id];
63
+ const { data, error } = await apiPost("/sessions/pages", {
64
+ session_ids: ids,
65
+ });
66
+ if (error) {
67
+ return {
68
+ content: [{ type: "text", text: JSON.stringify({ error }) }],
69
+ isError: true,
70
+ };
71
+ }
72
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
73
+ });
74
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recapt/mcp",
3
- "version": "0.0.12-beta",
3
+ "version": "0.0.14-beta",
4
4
  "description": "MCP exposing recapt behavioral intelligence to AI coding agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -21,11 +21,13 @@
21
21
  },
22
22
  "files": [
23
23
  "dist",
24
- "skills"
24
+ "skills",
25
+ "templates"
25
26
  ],
26
27
  "author": "Recapt",
27
28
  "license": "ISC",
28
29
  "dependencies": {
30
+ "@inquirer/prompts": "^8.3.2",
29
31
  "@modelcontextprotocol/sdk": "^1.12.0",
30
32
  "commander": "^14.0.3",
31
33
  "zod": "^3.24.0"
@@ -1,13 +1,21 @@
1
1
  # Recapt Self-Healing Workflow
2
2
 
3
- Use this workflow when asked to "fix issues", "heal the site", or improve UX based on behavioral data from recapt.
3
+ Use this workflow when asked to "heal the site" or improve UX based on behavioral data from recapt.
4
4
 
5
5
  ## When to Use
6
6
 
7
- - User asks to fix UX issues or friction points
8
- - User wants to improve conversion or reduce drop-offs
9
- - User asks to "heal" or "diagnose" their site
10
- - After identifying issues with `run_full_diagnostic`
7
+ This is a **comprehensive site improvement workflow**. Only use it when the user explicitly requests a full-site analysis:
8
+
9
+ - "Heal my site"
10
+ - "Run the self-healing workflow"
11
+ - "Analyze my whole site and improve it"
12
+ - "Do a full UX audit"
13
+ - "Make general improvements across the site"
14
+ **Do NOT use this workflow for:**
15
+ - Specific page fixes ("fix the checkout button")
16
+ - Single flow optimization ("improve the signup funnel")
17
+ - Investigating a specific issue ("why are users rage clicking here?")
18
+ For specific requests, use the appropriate tools directly — let the conversation guide which tools to call.
11
19
 
12
20
  ## Workflow
13
21
 
@@ -35,40 +43,119 @@ If fixes have sufficient data:
35
43
 
36
44
  Start with `run_full_diagnostic` (always available) to get a prioritized list of issues across the site.
37
45
 
38
- ### 2. Investigate
46
+ ### 2. Analyze Flows
47
+
48
+ After diagnosing issues, proactively look for flow optimization opportunities — even when nothing is "broken."
49
+
50
+ #### 2a. Discover Journey Patterns
51
+
52
+ - Search: "journey patterns" → `get_journey_patterns`
53
+ - Look for:
54
+ - **Backtrack hotspots** — pages users return to repeatedly (signals confusion or missing information)
55
+ - **Dropoff pages** — where sessions end unexpectedly (potential conversion leaks)
56
+ - **Unexpected paths** — users taking roundabout routes to reach goals
57
+
58
+ #### 2b. Analyze Key Funnels
59
+
60
+ - Search: "analyze funnel" → `analyze_funnel`
61
+ - Analyze critical conversion paths:
62
+ - Landing → Signup/Trial
63
+ - Pricing → Checkout → Success
64
+ - Onboarding flows
65
+ - For each funnel step, note:
66
+ - Dropoff rate (>30% is a red flag)
67
+ - Frustration/confusion scores
68
+ - Dwell time anomalies
69
+
70
+ #### 2c. Analyze Specific Flows
71
+
72
+ - Search: "analyze flow" → `analyze_flow`, `get_flow_friction`
73
+ - For pages with high dropoff or backtracking, analyze the flow in detail:
74
+ - What's the success rate for users entering this flow?
75
+ - Where are the bottlenecks?
76
+ - What's the friction score at each step?
77
+
78
+ #### 2d. Understand User Segments
79
+
80
+ - Search: "personas" → `discover_personas`
81
+ - Identify behavioral personas:
82
+ - Which personas struggle most?
83
+ - What are their risk factors?
84
+ - What interventions are recommended?
39
85
 
40
- For each high-priority issue, search for investigation tools:
86
+ #### 2e. Compare Success vs Failure
87
+
88
+ - Search: "compare cohorts" → `compare_cohorts`
89
+ - For flows with low conversion, compare:
90
+ - Users who completed vs dropped off
91
+ - Users on mobile vs desktop
92
+ - New vs returning users
93
+ - Look for patterns: What do successful users do differently?
94
+
95
+ #### Presenting Flow Opportunities
96
+
97
+ Present opportunities alongside issues, clearly labeled:
98
+
99
+ > **Issues Found:**
100
+ >
101
+ > 1. [CRITICAL] Rage clicks on checkout button — JS error
102
+ > 2. [HIGH] Dead clicks on pricing toggle
103
+ >
104
+ > **Flow Optimization Opportunities:**
105
+ >
106
+ > 1. [OPPORTUNITY] 40% backtrack from /pricing to /features — consider adding feature comparison on pricing page
107
+ > 2. [OPPORTUNITY] 65% dropoff at step 2 of onboarding — simplify or add progress indicator
108
+ > 3. [OPPORTUNITY] Mobile users 3x more likely to abandon checkout — review mobile UX
109
+
110
+ ### 3. Investigate
111
+
112
+ For each high-priority issue or opportunity, search for investigation tools:
41
113
 
42
114
  - Search: "investigate issue" → `investigate_issue`, `validate_issue`
43
115
  - This provides detailed context: affected sessions, element interactions, timing patterns
44
116
 
45
- ### 3. Triage
117
+ For flow opportunities, also consider:
118
+
119
+ - Watching session replays of users who dropped off vs completed
120
+ - Checking element friction on high-dropoff pages
121
+
122
+ ### 4. Triage
46
123
 
47
124
  Present findings to the user. Not all detected issues need fixing:
48
125
 
49
126
  - Search: "dismiss issue" → `dismiss_issue`, `mark_intended_behavior`
50
127
  - Some behaviors are intentional (e.g., rage clicks on a "copy" button)
51
- - Ask the user which issues to address before proceeding
128
+ - Some flow patterns may be acceptable (e.g., users comparing options before deciding)
129
+ - Ask the user which issues and opportunities to address before proceeding
52
130
 
53
- ### 4. Fix
131
+ ### 5. Fix
54
132
 
55
- Implement code changes to address the issues. After making changes:
133
+ Implement code changes to address issues and opportunities. After making changes:
56
134
 
57
135
  - Search: "propose fix" → `propose_fix`, `get_similar_fixes`, `get_fix_history`
58
136
  - Log the remediation so recapt can track its effectiveness
59
137
 
60
- ### 5. Track
138
+ For flow optimizations, common fixes include:
139
+
140
+ - Adding missing information to reduce backtracking
141
+ - Simplifying forms to reduce abandonment
142
+ - Adding progress indicators to multi-step flows
143
+ - Improving CTAs and visual hierarchy
144
+ - Adding social proof or trust signals at decision points
145
+
146
+ ### 6. Track
61
147
 
62
148
  Mark fixes as deployed so recapt can measure impact:
63
149
 
64
150
  - Search: "deployment" → `confirm_deployment`, `evaluate_fix`, `list_pending_fixes`
65
151
 
66
- ### 6. Learn
152
+ ### 7. Learn
67
153
 
68
154
  Build site knowledge for future reference:
69
155
 
70
156
  - Search: "site knowledge" → `get_site_knowledge`, `add_site_knowledge`
71
157
  - Document patterns, intended behaviors, and architectural decisions
158
+ - Record successful flow optimizations for future reference
72
159
 
73
160
  ## Post-Fix Behavior
74
161
 
@@ -88,6 +175,11 @@ If the user agrees:
88
175
  | ------------- | ------------------- | ---------------------------------------------------------- |
89
176
  | Check Pending | "pending fixes" | `list_pending_fixes`, `evaluate_fix` |
90
177
  | Diagnose | (always available) | `run_full_diagnostic` |
178
+ | Journey | "journey patterns" | `get_journey_patterns` |
179
+ | Funnels | "analyze funnel" | `analyze_funnel` |
180
+ | Flows | "analyze flow" | `analyze_flow`, `get_flow_friction` |
181
+ | Personas | "personas" | `discover_personas` |
182
+ | Compare | "compare cohorts" | `compare_cohorts` |
91
183
  | Investigate | "investigate issue" | `investigate_issue`, `validate_issue` |
92
184
  | Triage | "dismiss issue" | `dismiss_issue`, `mark_intended_behavior` |
93
185
  | Fix | "propose fix" | `propose_fix`, `get_similar_fixes`, `get_fix_history` |
@@ -0,0 +1,76 @@
1
+ ---
2
+ on:
3
+ schedule: daily
4
+ workflow_dispatch:
5
+ inputs:
6
+ branch:
7
+ description: "Branch to analyze"
8
+ default: "main"
9
+
10
+ engine: { { ENGINE } }
11
+
12
+ permissions:
13
+ contents: read
14
+ issues: read
15
+ pull-requests: read
16
+
17
+ mcp-servers:
18
+ recapt:
19
+ command: npx
20
+ args: ["@recapt/mcp@latest"]
21
+ env:
22
+ RECAPT_SECRET_KEY: "${{ secrets.RECAPT_SECRET_KEY }}"
23
+ allowed: ["*"]
24
+
25
+ safe-outputs:
26
+ create-pull-request:
27
+ title-prefix: "[self-healing] "
28
+ labels: [automated, ux-fix]
29
+ base-branch: main
30
+ draft: true
31
+ ---
32
+
33
+ ## Self-Healing UX Fixes
34
+
35
+ You are a UX engineer using recapt behavioral intelligence to automatically detect and fix user friction points.
36
+
37
+ ### Important: Autonomous Mode
38
+
39
+ You are running in a GitHub Actions sandbox with NO human interaction possible. You must:
40
+
41
+ - **Never wait for user input** - Make all decisions autonomously
42
+ - **Never ask questions** - Proceed with reasonable defaults
43
+ - **Complete all work in one run** - There is no "check back later"
44
+ - **Always track fixes** - Call `confirm_deployment` for every fix without asking
45
+ - **Triage autonomously** - Fix high-confidence issues, skip ambiguous ones (document skipped issues in the PR)
46
+ - **If data is insufficient** - Note it in the PR and proceed with available information
47
+
48
+ {{DOMAIN_CONFIG}}
49
+
50
+ ### Context
51
+
52
+ Get recent changes to understand what might have introduced issues:
53
+
54
+ ```bash
55
+ git log --since="24 hours ago" --oneline
56
+ ```
57
+
58
+ ### Workflow
59
+
60
+ Follow the self-healing workflow defined in `.agents/recapt/self-healing.md`. The recapt MCP server is connected and provides all necessary tools.
61
+
62
+ The workflow covers:
63
+
64
+ 1. Checking and evaluating any pending fixes from previous runs
65
+ 2. Running diagnostics to find current UX issues
66
+ 3. Investigating and triaging high-priority issues
67
+ 4. Implementing code fixes for actionable issues
68
+ 5. Tracking fixes for future evaluation
69
+
70
+ ### Output
71
+
72
+ Create a pull request containing:
73
+
74
+ - Evaluation results for any previously deployed fixes (improved/regressed/no change)
75
+ - New issues discovered and fixes implemented
76
+ - Clear commit messages explaining each change