@recapt/mcp 0.0.14-beta → 0.0.15-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.
- package/dist/index.js +28 -1
- package/dist/tools/catalog/toolCatalog.json +2080 -0
- package/dist/tools/diagnostic.js +5 -1
- package/dist/tools/healingRun.d.ts +6 -0
- package/dist/tools/healingRun.js +313 -0
- package/dist/tools/triageSessions.js +3 -1
- package/dist/tools/upgradeOptions.d.ts +7 -0
- package/dist/tools/upgradeOptions.js +67 -0
- package/package.json +1 -1
- package/skills/self-healing.md +237 -14
package/dist/tools/diagnostic.js
CHANGED
|
@@ -41,7 +41,11 @@ export function registerDiagnosticTools(server) {
|
|
|
41
41
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
42
42
|
});
|
|
43
43
|
server.registerTool("investigate_issue", {
|
|
44
|
-
description: "Deep-dive into a specific issue. Returns affected sessions, element friction data,
|
|
44
|
+
description: "Deep-dive into a specific issue. Returns affected sessions, element friction data, " +
|
|
45
|
+
"related console errors, similar issues, and page context. Use after identifying an " +
|
|
46
|
+
"issue to understand root cause. NOTE: Free tier users get issue summary and page " +
|
|
47
|
+
"context only. Detailed investigation data (affected sessions, element friction, " +
|
|
48
|
+
"related errors) requires a paid plan (Starter+).",
|
|
45
49
|
inputSchema: z.object({
|
|
46
50
|
issue_id: z.string().describe("The ID of the issue to investigate"),
|
|
47
51
|
days: z
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Healing Run tools for tracking self-healing workflow executions.
|
|
3
|
+
*
|
|
4
|
+
* Provides run registration, phase updates, action recording, and run listing.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { apiGet, apiPost, apiPatch, isApiConfigured } from "../api/client.js";
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
export function registerHealingRunTools(server) {
|
|
10
|
+
server.registerTool("start_healing_run", {
|
|
11
|
+
description: "Register the start of a self-healing workflow run. Call this at the beginning of a healing session to track the run and its outcomes. Returns a run ID to use for subsequent updates.",
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
trigger_type: z
|
|
14
|
+
.enum(["github_actions", "cron", "manual", "api"])
|
|
15
|
+
.describe("What triggered this healing run"),
|
|
16
|
+
trigger_metadata: z
|
|
17
|
+
.record(z.unknown())
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Additional metadata about the trigger (e.g., GitHub run ID, branch, actor)"),
|
|
20
|
+
phases: z
|
|
21
|
+
.array(z.object({
|
|
22
|
+
name: z.string(),
|
|
23
|
+
status: z
|
|
24
|
+
.enum(["pending", "running", "completed", "skipped", "failed"])
|
|
25
|
+
.default("pending"),
|
|
26
|
+
}))
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Initial phases to track (e.g., diagnose, investigate, fix)"),
|
|
29
|
+
}),
|
|
30
|
+
}, async ({ trigger_type, trigger_metadata, phases, }) => {
|
|
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 apiPost("/healing-runs", {
|
|
43
|
+
trigger_type,
|
|
44
|
+
trigger_metadata,
|
|
45
|
+
phases,
|
|
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
|
+
server.registerTool("update_healing_run", {
|
|
56
|
+
description: "Update a healing run's status, phases, or summary. Use to track progress through the workflow phases and record final outcomes.",
|
|
57
|
+
inputSchema: z.object({
|
|
58
|
+
run_id: z.string().describe("The ID of the healing run to update"),
|
|
59
|
+
status: z
|
|
60
|
+
.enum(["running", "completed", "failed", "cancelled"])
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("New status for the run"),
|
|
63
|
+
phases: z
|
|
64
|
+
.array(z.object({
|
|
65
|
+
name: z.string(),
|
|
66
|
+
status: z.enum([
|
|
67
|
+
"pending",
|
|
68
|
+
"running",
|
|
69
|
+
"completed",
|
|
70
|
+
"skipped",
|
|
71
|
+
"failed",
|
|
72
|
+
]),
|
|
73
|
+
startedAt: z.string().nullable().optional(),
|
|
74
|
+
completedAt: z.string().nullable().optional(),
|
|
75
|
+
output: z.record(z.unknown()).optional(),
|
|
76
|
+
}))
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("Updated phases array"),
|
|
79
|
+
summary: z
|
|
80
|
+
.object({
|
|
81
|
+
issuesFound: z.number().optional(),
|
|
82
|
+
issuesFixed: z.number().optional(),
|
|
83
|
+
issuesDeferred: z.number().optional(),
|
|
84
|
+
issuesDismissed: z.number().optional(),
|
|
85
|
+
prsCreated: z.number().optional(),
|
|
86
|
+
})
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("Updated summary counts"),
|
|
89
|
+
completed_at: z
|
|
90
|
+
.string()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("ISO timestamp when the run completed"),
|
|
93
|
+
duration_ms: z
|
|
94
|
+
.number()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("Total duration of the run in milliseconds"),
|
|
97
|
+
}),
|
|
98
|
+
}, async ({ run_id, status, phases, summary, completed_at, duration_ms, }) => {
|
|
99
|
+
if (!isApiConfigured()) {
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const { data, error } = await apiPatch(`/healing-runs/${run_id}`, {
|
|
111
|
+
status,
|
|
112
|
+
phases,
|
|
113
|
+
summary,
|
|
114
|
+
completed_at,
|
|
115
|
+
duration_ms,
|
|
116
|
+
});
|
|
117
|
+
if (error) {
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
124
|
+
});
|
|
125
|
+
server.registerTool("record_healing_action", {
|
|
126
|
+
description: "Record an action taken during a healing run. Use to track what was done for each issue: code fixes, deferrals, dismissals, etc. " +
|
|
127
|
+
"IMPORTANT: When recording code_fix actions, the code_changes.diff field must contain ACTUAL SOURCE CODE in unified diff format, " +
|
|
128
|
+
"not a description of what changed. Include the literal code lines with proper +/- prefixes.",
|
|
129
|
+
inputSchema: z.object({
|
|
130
|
+
run_id: z.string().describe("The ID of the healing run"),
|
|
131
|
+
issue_id: z
|
|
132
|
+
.string()
|
|
133
|
+
.optional()
|
|
134
|
+
.describe("The ID of the issue this action relates to (if any)"),
|
|
135
|
+
action_type: z
|
|
136
|
+
.enum([
|
|
137
|
+
"code_fix",
|
|
138
|
+
"needs_more_data",
|
|
139
|
+
"dismissed",
|
|
140
|
+
"marked_intended",
|
|
141
|
+
"knowledge_added",
|
|
142
|
+
])
|
|
143
|
+
.describe("Type of action taken"),
|
|
144
|
+
hypothesis: z.string().describe("AI's reasoning for this action"),
|
|
145
|
+
expected_improvement: z
|
|
146
|
+
.string()
|
|
147
|
+
.describe("What improvement is expected from this action"),
|
|
148
|
+
code_changes: z
|
|
149
|
+
.array(z.object({
|
|
150
|
+
file: z.string().describe("Path to the changed file"),
|
|
151
|
+
diff: z
|
|
152
|
+
.string()
|
|
153
|
+
.describe("CRITICAL: Must be actual source code in unified diff format, NOT a description of changes. " +
|
|
154
|
+
"This field must contain the literal code lines that were changed.\n\n" +
|
|
155
|
+
"FORMAT REQUIREMENTS:\n" +
|
|
156
|
+
"1. Start with @@ header: @@ -oldStart,oldCount +newStart,newCount @@\n" +
|
|
157
|
+
"2. Context lines (unchanged): prefix with single space ' '\n" +
|
|
158
|
+
"3. Removed lines: prefix with minus '-'\n" +
|
|
159
|
+
"4. Added lines: prefix with plus '+'\n" +
|
|
160
|
+
"5. Include 5 lines of context before and after the change\n\n" +
|
|
161
|
+
"CORRECT EXAMPLE:\n" +
|
|
162
|
+
"@@ -35,7 +35,9 @@\n" +
|
|
163
|
+
" import { Spinner } from '@chakra-ui/react';\n" +
|
|
164
|
+
" \n" +
|
|
165
|
+
" export function FilterDropdown({ filters }) {\n" +
|
|
166
|
+
"- const [isOpen, setIsOpen] = useState(false);\n" +
|
|
167
|
+
"+ const [isOpen, setIsOpen] = useState(false);\n" +
|
|
168
|
+
"+ const [isLoading, setIsLoading] = useState(false);\n" +
|
|
169
|
+
" const handleClick = () => {\n" +
|
|
170
|
+
" \n\n" +
|
|
171
|
+
"WRONG (do NOT do this):\n" +
|
|
172
|
+
"- 'Added isLoading state to provide feedback'\n" +
|
|
173
|
+
"- 'Changed the button to show a spinner'\n" +
|
|
174
|
+
"- Any prose description instead of actual code"),
|
|
175
|
+
startLine: z
|
|
176
|
+
.number()
|
|
177
|
+
.describe("Line number in the original file where the first line of the diff begins (the first context line)"),
|
|
178
|
+
linesAdded: z
|
|
179
|
+
.number()
|
|
180
|
+
.default(0)
|
|
181
|
+
.describe("Count of lines added (lines starting with '+')"),
|
|
182
|
+
linesRemoved: z
|
|
183
|
+
.number()
|
|
184
|
+
.default(0)
|
|
185
|
+
.describe("Count of lines removed (lines starting with '-')"),
|
|
186
|
+
}))
|
|
187
|
+
.optional()
|
|
188
|
+
.describe("Array of code changes. Each diff MUST contain actual source code lines, not descriptions."),
|
|
189
|
+
pr_url: z
|
|
190
|
+
.string()
|
|
191
|
+
.optional()
|
|
192
|
+
.describe("GitHub PR URL if a PR was created"),
|
|
193
|
+
pr_number: z.number().optional().describe("GitHub PR number"),
|
|
194
|
+
remediation_id: z
|
|
195
|
+
.string()
|
|
196
|
+
.optional()
|
|
197
|
+
.describe("ID of the associated remediation record (if any)"),
|
|
198
|
+
deferral_reason: z
|
|
199
|
+
.string()
|
|
200
|
+
.optional()
|
|
201
|
+
.describe("Explanation for why the issue was deferred (for needs_more_data actions)"),
|
|
202
|
+
dismissal_reason: z
|
|
203
|
+
.string()
|
|
204
|
+
.optional()
|
|
205
|
+
.describe("Explanation for why the issue was dismissed (for dismissed/marked_intended actions)"),
|
|
206
|
+
}),
|
|
207
|
+
}, async ({ run_id, issue_id, action_type, hypothesis, expected_improvement, code_changes, pr_url, pr_number, remediation_id, deferral_reason, dismissal_reason, }) => {
|
|
208
|
+
if (!isApiConfigured()) {
|
|
209
|
+
return {
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
type: "text",
|
|
213
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
isError: true,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const { data, error } = await apiPost(`/healing-runs/${run_id}/actions`, {
|
|
220
|
+
issue_id,
|
|
221
|
+
action_type,
|
|
222
|
+
hypothesis,
|
|
223
|
+
expected_improvement,
|
|
224
|
+
code_changes,
|
|
225
|
+
pr_url,
|
|
226
|
+
pr_number,
|
|
227
|
+
remediation_id,
|
|
228
|
+
deferral_reason,
|
|
229
|
+
dismissal_reason,
|
|
230
|
+
});
|
|
231
|
+
if (error) {
|
|
232
|
+
return {
|
|
233
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
234
|
+
isError: true,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
238
|
+
});
|
|
239
|
+
server.registerTool("get_healing_run", {
|
|
240
|
+
description: "Get details of a specific healing run including all actions taken. Use to review what happened during a run.",
|
|
241
|
+
inputSchema: z.object({
|
|
242
|
+
run_id: z.string().describe("The ID of the healing run to retrieve"),
|
|
243
|
+
}),
|
|
244
|
+
}, async ({ run_id }) => {
|
|
245
|
+
if (!isApiConfigured()) {
|
|
246
|
+
return {
|
|
247
|
+
content: [
|
|
248
|
+
{
|
|
249
|
+
type: "text",
|
|
250
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const { data, error } = await apiGet(`/healing-runs/${run_id}`);
|
|
257
|
+
if (error) {
|
|
258
|
+
return {
|
|
259
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
260
|
+
isError: true,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
264
|
+
});
|
|
265
|
+
server.registerTool("list_healing_runs", {
|
|
266
|
+
description: "List recent healing runs with optional filters. Use to review past runs and their outcomes.",
|
|
267
|
+
inputSchema: z.object({
|
|
268
|
+
status: z
|
|
269
|
+
.enum(["running", "completed", "failed", "cancelled"])
|
|
270
|
+
.optional()
|
|
271
|
+
.describe("Filter by run status"),
|
|
272
|
+
trigger_type: z
|
|
273
|
+
.enum(["github_actions", "cron", "manual", "api"])
|
|
274
|
+
.optional()
|
|
275
|
+
.describe("Filter by trigger type"),
|
|
276
|
+
limit: z
|
|
277
|
+
.number()
|
|
278
|
+
.optional()
|
|
279
|
+
.default(20)
|
|
280
|
+
.describe("Maximum number of results (default: 20)"),
|
|
281
|
+
offset: z
|
|
282
|
+
.number()
|
|
283
|
+
.optional()
|
|
284
|
+
.default(0)
|
|
285
|
+
.describe("Offset for pagination"),
|
|
286
|
+
}),
|
|
287
|
+
}, async ({ status, trigger_type, limit, offset, }) => {
|
|
288
|
+
if (!isApiConfigured()) {
|
|
289
|
+
return {
|
|
290
|
+
content: [
|
|
291
|
+
{
|
|
292
|
+
type: "text",
|
|
293
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
isError: true,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const { data, error } = await apiGet("/healing-runs", {
|
|
300
|
+
status,
|
|
301
|
+
trigger_type,
|
|
302
|
+
limit: limit ?? 20,
|
|
303
|
+
offset: offset ?? 0,
|
|
304
|
+
});
|
|
305
|
+
if (error) {
|
|
306
|
+
return {
|
|
307
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
308
|
+
isError: true,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
312
|
+
});
|
|
313
|
+
}
|
|
@@ -138,7 +138,9 @@ export function registerTriageSessions(server) {
|
|
|
138
138
|
"Analyzes user comments, frustration signals, rage clicks, and console errors. " +
|
|
139
139
|
"Returns flagged sessions with evidence and replay links. Use this to identify " +
|
|
140
140
|
"sessions that need attention without manually reviewing every recording. " +
|
|
141
|
-
"Can triage specific sessions by ID(s) or scan for problematic sessions."
|
|
141
|
+
"Can triage specific sessions by ID(s) or scan for problematic sessions. " +
|
|
142
|
+
"NOTE: Session replay URLs require a paid plan (Starter+). Free tier users " +
|
|
143
|
+
"can see session summaries and triage scores but not watch replays.",
|
|
142
144
|
inputSchema: z.object({
|
|
143
145
|
session_ids: z
|
|
144
146
|
.array(z.string())
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_upgrade_options tool
|
|
3
|
+
*
|
|
4
|
+
* Provides upgrade information for AI agents to communicate upgrade paths to users.
|
|
5
|
+
* Part of the "Diagnose Free, Solve Paid" pattern.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { apiGet, isApiConfigured } from "../api/client.js";
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
export function registerGetUpgradeOptions(server) {
|
|
11
|
+
server.registerTool("get_upgrade_options", {
|
|
12
|
+
description: "Get available upgrade options for the current plan. Use this when a user hits a " +
|
|
13
|
+
"feature limit or when you need to explain what's available on higher tiers. " +
|
|
14
|
+
"Returns the current plan, available upgrades with their features and benefits, " +
|
|
15
|
+
"and a direct link to the billing page. Use this to help users understand the " +
|
|
16
|
+
"value of upgrading when they encounter gated features.",
|
|
17
|
+
inputSchema: z.object({}),
|
|
18
|
+
}, async () => {
|
|
19
|
+
if (!isApiConfigured()) {
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
type: "text",
|
|
24
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
isError: true,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const { data, error } = await apiGet("/upgrade-options");
|
|
31
|
+
if (error) {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Format output for better LLM comprehension
|
|
38
|
+
const output = formatUpgradeOptions(data);
|
|
39
|
+
return { content: [{ type: "text", text: output }] };
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function formatUpgradeOptions(data) {
|
|
43
|
+
const lines = [];
|
|
44
|
+
lines.push("# UPGRADE OPTIONS\n");
|
|
45
|
+
lines.push(`**Current Plan:** ${data.currentPlan.name} (${data.currentPlan.tag})\n`);
|
|
46
|
+
if (data.availableUpgrades.length === 0) {
|
|
47
|
+
lines.push("You're on the highest tier! No upgrades available.");
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
lines.push("## Available Upgrades\n");
|
|
51
|
+
for (const upgrade of data.availableUpgrades) {
|
|
52
|
+
lines.push(`### ${upgrade.name}\n`);
|
|
53
|
+
lines.push("**Features:**");
|
|
54
|
+
for (const feature of upgrade.features) {
|
|
55
|
+
lines.push(`- ${feature}`);
|
|
56
|
+
}
|
|
57
|
+
lines.push("");
|
|
58
|
+
lines.push("**Benefits:**");
|
|
59
|
+
for (const benefit of upgrade.benefits) {
|
|
60
|
+
lines.push(`- ${benefit}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push("");
|
|
63
|
+
}
|
|
64
|
+
lines.push("---");
|
|
65
|
+
lines.push(`**Upgrade now:** ${data.upgradeUrl}`);
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|