@recapt/mcp 0.0.2-beta → 0.0.4-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/api/client.d.ts +3 -1
- package/dist/api/client.js +51 -2
- package/dist/index.js +86 -1
- package/dist/tools/diagnostic.d.ts +6 -0
- package/dist/tools/diagnostic.js +102 -0
- package/dist/tools/knowledge.d.ts +6 -0
- package/dist/tools/knowledge.js +186 -0
- package/dist/tools/remediation.d.ts +6 -0
- package/dist/tools/remediation.js +223 -0
- package/dist/tools/triage.d.ts +6 -0
- package/dist/tools/triage.js +114 -0
- package/package.json +1 -1
package/dist/api/client.d.ts
CHANGED
|
@@ -8,5 +8,7 @@ interface ApiResponse<T> {
|
|
|
8
8
|
error?: string;
|
|
9
9
|
}
|
|
10
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
|
|
11
|
+
export declare function apiPost<T>(path: string, body?: Record<string, unknown>): Promise<ApiResponse<T>>;
|
|
12
|
+
export declare function apiPatch<T>(path: string, body?: Record<string, unknown>): Promise<ApiResponse<T>>;
|
|
13
|
+
export declare function apiDelete<T>(path: string): Promise<ApiResponse<T>>;
|
|
12
14
|
export {};
|
package/dist/api/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HTTP client for calling external-api query endpoints.
|
|
3
3
|
*/
|
|
4
|
-
const API_URL = process.env.EXTERNAL_API_URL || "
|
|
4
|
+
const API_URL = process.env.EXTERNAL_API_URL || "https://api.recapt.app";
|
|
5
5
|
const SECRET_KEY = process.env.RECAPT_SECRET_KEY;
|
|
6
6
|
export function isApiConfigured() {
|
|
7
7
|
return !!SECRET_KEY;
|
|
@@ -40,7 +40,7 @@ export async function apiGet(path, params) {
|
|
|
40
40
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
|
-
export async function apiPost(path, body) {
|
|
43
|
+
export async function apiPost(path, body = {}) {
|
|
44
44
|
if (!SECRET_KEY) {
|
|
45
45
|
return { error: "RECAPT_SECRET_KEY not configured" };
|
|
46
46
|
}
|
|
@@ -65,3 +65,52 @@ export async function apiPost(path, body) {
|
|
|
65
65
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
export async function apiPatch(path, body = {}) {
|
|
69
|
+
if (!SECRET_KEY) {
|
|
70
|
+
return { error: "RECAPT_SECRET_KEY not configured" };
|
|
71
|
+
}
|
|
72
|
+
const url = `${API_URL}/v1beta/query${path}`;
|
|
73
|
+
try {
|
|
74
|
+
const res = await fetch(url, {
|
|
75
|
+
method: "PATCH",
|
|
76
|
+
headers: {
|
|
77
|
+
"x-private-key": SECRET_KEY,
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify(body),
|
|
81
|
+
});
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
const responseData = (await res.json().catch(() => ({})));
|
|
84
|
+
return { error: responseData.error || `HTTP ${res.status}` };
|
|
85
|
+
}
|
|
86
|
+
const data = (await res.json());
|
|
87
|
+
return { data };
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export async function apiDelete(path) {
|
|
94
|
+
if (!SECRET_KEY) {
|
|
95
|
+
return { error: "RECAPT_SECRET_KEY not configured" };
|
|
96
|
+
}
|
|
97
|
+
const url = `${API_URL}/v1beta/query${path}`;
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(url, {
|
|
100
|
+
method: "DELETE",
|
|
101
|
+
headers: {
|
|
102
|
+
"x-private-key": SECRET_KEY,
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const responseData = (await res.json().catch(() => ({})));
|
|
108
|
+
return { error: responseData.error || `HTTP ${res.status}` };
|
|
109
|
+
}
|
|
110
|
+
const data = (await res.json());
|
|
111
|
+
return { data };
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
115
|
+
}
|
|
116
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,10 @@ import { registerPredictOutcomes } from "./tools/predictOutcomes.js";
|
|
|
39
39
|
import { registerMemoryTools } from "./tools/memory.js";
|
|
40
40
|
import { registerListSessions } from "./tools/listSessions.js";
|
|
41
41
|
import { registerTriageSessions } from "./tools/triageSessions.js";
|
|
42
|
+
import { registerDiagnosticTools } from "./tools/diagnostic.js";
|
|
43
|
+
import { registerTriageTools } from "./tools/triage.js";
|
|
44
|
+
import { registerRemediationTools } from "./tools/remediation.js";
|
|
45
|
+
import { registerKnowledgeTools } from "./tools/knowledge.js";
|
|
42
46
|
async function main() {
|
|
43
47
|
console.error("[MCP] Starting recapt behavioral intelligence server...");
|
|
44
48
|
if (!isApiConfigured()) {
|
|
@@ -177,7 +181,83 @@ When triaging, prioritize:
|
|
|
177
181
|
3. Sessions with console errors + high frustration (likely bugs)
|
|
178
182
|
|
|
179
183
|
### Working Memory
|
|
180
|
-
- \`memory_save\`, \`memory_recall\`, \`memory_list\`, \`memory_delete\`, \`memory_clear\`: Store and retrieve intermediate results
|
|
184
|
+
- \`memory_save\`, \`memory_recall\`, \`memory_list\`, \`memory_delete\`, \`memory_clear\`: Store and retrieve intermediate results
|
|
185
|
+
|
|
186
|
+
## Self-Healing Workflow
|
|
187
|
+
|
|
188
|
+
When asked to "fix all issues", "heal the site", or "improve my site via recapt":
|
|
189
|
+
|
|
190
|
+
### 1. Full Diagnostic
|
|
191
|
+
- Call \`run_full_diagnostic\` to get prioritized issue list with severity scores
|
|
192
|
+
- Call \`get_site_knowledge\` to load known false positives and intended behaviors
|
|
193
|
+
- Review the \`recommended_investigation_order\` to prioritize your work
|
|
194
|
+
|
|
195
|
+
### 2. Triage Each Issue
|
|
196
|
+
For each issue in priority order, determine if it's actionable:
|
|
197
|
+
- Call \`validate_issue\` to check if still occurring (not stale)
|
|
198
|
+
- Check against site knowledge for known false positives
|
|
199
|
+
- If recommendation is "dismiss_stale", consider dismissing
|
|
200
|
+
- If uncertain, call \`investigate_issue\` for deep context
|
|
201
|
+
- **Always ask user to confirm** if behavior is intended when unclear
|
|
202
|
+
|
|
203
|
+
### 3. Propose Fixes
|
|
204
|
+
For confirmed issues:
|
|
205
|
+
- Analyze root cause from evidence and element context
|
|
206
|
+
- Call \`get_similar_fixes\` to see what worked before for similar issues
|
|
207
|
+
- Call \`get_fix_history\` to see past attempts on this specific issue
|
|
208
|
+
- Call \`propose_fix\` with detailed diagnosis and code suggestion
|
|
209
|
+
- Explain confidence level and reasoning to user
|
|
210
|
+
- **Ask for confirmation when confidence < 0.7**
|
|
211
|
+
|
|
212
|
+
### 4. Track Deployments
|
|
213
|
+
- Show user all pending fixes with \`list_pending_fixes\`
|
|
214
|
+
- When user confirms "I have deployed this fix", call \`confirm_deployment\`
|
|
215
|
+
- Explain that evaluation will happen after 24-48 hours of data collection
|
|
216
|
+
|
|
217
|
+
### 5. Evaluate Results
|
|
218
|
+
- After deployment window (24-48h), call \`evaluate_fix\`
|
|
219
|
+
- If outcome is "success": celebrate, the issue will be auto-resolved
|
|
220
|
+
- If outcome is "partial": discuss with user whether to iterate
|
|
221
|
+
- If outcome is "failed": analyze why, propose alternative approach
|
|
222
|
+
- Store learnings with \`add_site_knowledge\` for future reference
|
|
223
|
+
|
|
224
|
+
### Evaluation Thresholds
|
|
225
|
+
- **Success**: frustration decreased >15% OR health score increased >10 points
|
|
226
|
+
- **Partial**: frustration decreased 5-15% OR health score increased 3-10 points
|
|
227
|
+
- **Failed**: no improvement or regression
|
|
228
|
+
|
|
229
|
+
### Asking for Confirmation
|
|
230
|
+
Always ask user before:
|
|
231
|
+
- Dismissing an issue as false positive
|
|
232
|
+
- Marking behavior as intended
|
|
233
|
+
- When confidence in fix is <0.7
|
|
234
|
+
- Before evaluating (ensure they've actually deployed)
|
|
235
|
+
|
|
236
|
+
### Self-Healing Tools
|
|
237
|
+
|
|
238
|
+
#### Diagnostic
|
|
239
|
+
- \`run_full_diagnostic\`: Comprehensive site analysis with prioritized issues
|
|
240
|
+
- \`investigate_issue\`: Deep-dive into specific issue with sessions and errors
|
|
241
|
+
- \`validate_issue\`: Check if issue is still occurring (not stale)
|
|
242
|
+
|
|
243
|
+
#### Triage
|
|
244
|
+
- \`dismiss_issue\`: Mark issue as false positive with reason
|
|
245
|
+
- \`mark_intended_behavior\`: Flag behavior as by design
|
|
246
|
+
- \`get_issue_history\`: View past occurrences and fix attempts
|
|
247
|
+
|
|
248
|
+
#### Remediation
|
|
249
|
+
- \`propose_fix\`: Create remediation record with baseline metrics
|
|
250
|
+
- \`list_pending_fixes\`: Show fixes awaiting deployment
|
|
251
|
+
- \`list_remediations\`: Browse all remediation records
|
|
252
|
+
- \`confirm_deployment\`: Mark fix as deployed, start evaluation timer
|
|
253
|
+
- \`evaluate_fix\`: Compare post-deploy metrics to baseline
|
|
254
|
+
- \`get_fix_history\`: View all fix attempts for an issue
|
|
255
|
+
|
|
256
|
+
#### Knowledge
|
|
257
|
+
- \`get_site_knowledge\`: Retrieve learned patterns and false positives
|
|
258
|
+
- \`add_site_knowledge\`: Store new learning for future reference
|
|
259
|
+
- \`delete_site_knowledge\`: Remove outdated knowledge
|
|
260
|
+
- \`get_similar_fixes\`: Find past fixes for similar issues`,
|
|
181
261
|
});
|
|
182
262
|
registerGetDomains(server);
|
|
183
263
|
registerGetPageMetrics(server);
|
|
@@ -207,6 +287,11 @@ When triaging, prioritize:
|
|
|
207
287
|
registerMemoryTools(server);
|
|
208
288
|
registerListSessions(server);
|
|
209
289
|
registerTriageSessions(server);
|
|
290
|
+
// Self-healing tools
|
|
291
|
+
registerDiagnosticTools(server);
|
|
292
|
+
registerTriageTools(server);
|
|
293
|
+
registerRemediationTools(server);
|
|
294
|
+
registerKnowledgeTools(server);
|
|
210
295
|
const transport = new StdioServerTransport();
|
|
211
296
|
await server.connect(transport);
|
|
212
297
|
console.error("[MCP] Server running on stdio");
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostic tools for self-healing workflow.
|
|
3
|
+
*
|
|
4
|
+
* Provides comprehensive site diagnostics, issue investigation, and validation.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { apiGet, apiPost, isApiConfigured } from "../api/client.js";
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
export function registerDiagnosticTools(server) {
|
|
10
|
+
server.registerTool("run_full_diagnostic", {
|
|
11
|
+
description: "Run a comprehensive site diagnostic. Returns prioritized issues, problem pages, regressions, and recommended investigation order. Use this as the first step in a self-healing workflow.",
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
days: z
|
|
14
|
+
.number()
|
|
15
|
+
.optional()
|
|
16
|
+
.default(7)
|
|
17
|
+
.describe("Number of days to analyze (default: 7)"),
|
|
18
|
+
}),
|
|
19
|
+
}, async ({ days }) => {
|
|
20
|
+
if (!isApiConfigured()) {
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
isError: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const { data, error } = await apiGet("/diagnostic/full", { days: days ?? 7 });
|
|
32
|
+
if (error) {
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
35
|
+
isError: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
39
|
+
});
|
|
40
|
+
server.registerTool("investigate_issue", {
|
|
41
|
+
description: "Deep-dive into a specific issue. Returns affected sessions, element friction data, related console errors, similar issues, and page context. Use after identifying an issue to understand root cause.",
|
|
42
|
+
inputSchema: z.object({
|
|
43
|
+
issue_id: z.string().describe("The ID of the issue to investigate"),
|
|
44
|
+
days: z
|
|
45
|
+
.number()
|
|
46
|
+
.optional()
|
|
47
|
+
.default(7)
|
|
48
|
+
.describe("Number of days to look back (default: 7)"),
|
|
49
|
+
}),
|
|
50
|
+
}, async ({ issue_id, days }) => {
|
|
51
|
+
if (!isApiConfigured()) {
|
|
52
|
+
return {
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
isError: true,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const { data, error } = await apiGet(`/issues/${issue_id}/investigate`, { days: days ?? 7 });
|
|
63
|
+
if (error) {
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
66
|
+
isError: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
70
|
+
});
|
|
71
|
+
server.registerTool("validate_issue", {
|
|
72
|
+
description: "Check if an issue is still actively occurring. Returns staleness info and recommendation (investigate, dismiss_stale, or monitor). Use to filter out stale issues before proposing fixes.",
|
|
73
|
+
inputSchema: z.object({
|
|
74
|
+
issue_id: z.string().describe("The ID of the issue to validate"),
|
|
75
|
+
lookback_days: z
|
|
76
|
+
.number()
|
|
77
|
+
.optional()
|
|
78
|
+
.default(3)
|
|
79
|
+
.describe("Number of days to look back for recent occurrences (default: 3)"),
|
|
80
|
+
}),
|
|
81
|
+
}, async ({ issue_id, lookback_days, }) => {
|
|
82
|
+
if (!isApiConfigured()) {
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
isError: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const { data, error } = await apiPost(`/issues/${issue_id}/validate`, { lookback_days: lookback_days ?? 3 });
|
|
94
|
+
if (error) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge tools for self-healing workflow.
|
|
3
|
+
*
|
|
4
|
+
* Provides site-specific knowledge management for learning from past fixes.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { apiGet, apiPost, apiDelete, isApiConfigured } from "../api/client.js";
|
|
8
|
+
const KNOWLEDGE_CATEGORIES = [
|
|
9
|
+
"false_positive",
|
|
10
|
+
"intended_behavior",
|
|
11
|
+
"fix_pattern",
|
|
12
|
+
"context",
|
|
13
|
+
];
|
|
14
|
+
const ISSUE_CATEGORIES = [
|
|
15
|
+
"code_error",
|
|
16
|
+
"dead_click",
|
|
17
|
+
"rage_click",
|
|
18
|
+
"ux_friction",
|
|
19
|
+
"performance",
|
|
20
|
+
"form_issue",
|
|
21
|
+
"behavioral_anomaly",
|
|
22
|
+
"structural_issue",
|
|
23
|
+
];
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
export function registerKnowledgeTools(server) {
|
|
26
|
+
server.registerTool("get_site_knowledge", {
|
|
27
|
+
description: "Retrieve site-specific learnings including known false positives, intended behaviors, and successful fix patterns. Use at the start of a self-healing workflow to avoid re-flagging known issues.",
|
|
28
|
+
inputSchema: z.object({
|
|
29
|
+
category: z
|
|
30
|
+
.enum(KNOWLEDGE_CATEGORIES)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Filter by knowledge category"),
|
|
33
|
+
page_path: z.string().optional().describe("Filter by page path"),
|
|
34
|
+
issue_category: z
|
|
35
|
+
.enum(ISSUE_CATEGORIES)
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Filter by issue category"),
|
|
38
|
+
limit: z
|
|
39
|
+
.number()
|
|
40
|
+
.optional()
|
|
41
|
+
.default(50)
|
|
42
|
+
.describe("Maximum number of entries to return (default: 50)"),
|
|
43
|
+
}),
|
|
44
|
+
}, async ({ category, page_path, issue_category, limit, }) => {
|
|
45
|
+
if (!isApiConfigured()) {
|
|
46
|
+
return {
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
isError: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const { data, error } = await apiGet("/knowledge", {
|
|
57
|
+
category,
|
|
58
|
+
page_path,
|
|
59
|
+
issue_category,
|
|
60
|
+
limit: limit ?? 50,
|
|
61
|
+
});
|
|
62
|
+
if (error) {
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
69
|
+
});
|
|
70
|
+
server.registerTool("add_site_knowledge", {
|
|
71
|
+
description: "Store a site-specific learning. Use to record false positives, intended behaviors, successful fix patterns, or important context about the site.",
|
|
72
|
+
inputSchema: z.object({
|
|
73
|
+
category: z
|
|
74
|
+
.enum(KNOWLEDGE_CATEGORIES)
|
|
75
|
+
.describe("Category of knowledge: false_positive, intended_behavior, fix_pattern, or context"),
|
|
76
|
+
subject: z
|
|
77
|
+
.string()
|
|
78
|
+
.describe("Unique identifier/title for this knowledge entry"),
|
|
79
|
+
content: z.string().describe("Detailed description of the learning"),
|
|
80
|
+
page_path: z
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("Associated page path (if applicable)"),
|
|
84
|
+
element_selector: z
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Associated element selector (if applicable)"),
|
|
88
|
+
issue_category: z
|
|
89
|
+
.enum(ISSUE_CATEGORIES)
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("Associated issue category (if applicable)"),
|
|
92
|
+
}),
|
|
93
|
+
}, async ({ category, subject, content, page_path, element_selector, issue_category, }) => {
|
|
94
|
+
if (!isApiConfigured()) {
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const { data, error } = await apiPost("/knowledge", {
|
|
106
|
+
category,
|
|
107
|
+
subject,
|
|
108
|
+
content,
|
|
109
|
+
page_path,
|
|
110
|
+
element_selector,
|
|
111
|
+
issue_category,
|
|
112
|
+
source: "agent",
|
|
113
|
+
});
|
|
114
|
+
if (error) {
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
117
|
+
isError: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
121
|
+
});
|
|
122
|
+
server.registerTool("delete_site_knowledge", {
|
|
123
|
+
description: "Delete a site knowledge entry. Use when a learning is no longer valid or was created in error.",
|
|
124
|
+
inputSchema: z.object({
|
|
125
|
+
knowledge_id: z
|
|
126
|
+
.string()
|
|
127
|
+
.describe("The ID of the knowledge entry to delete"),
|
|
128
|
+
}),
|
|
129
|
+
}, async ({ knowledge_id }) => {
|
|
130
|
+
if (!isApiConfigured()) {
|
|
131
|
+
return {
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: "text",
|
|
135
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
isError: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const { data, error } = await apiDelete(`/knowledge/${knowledge_id}`);
|
|
142
|
+
if (error) {
|
|
143
|
+
return {
|
|
144
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
145
|
+
isError: true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
149
|
+
});
|
|
150
|
+
server.registerTool("get_similar_fixes", {
|
|
151
|
+
description: "Find past fixes for similar issues. Returns successful and failed remediation attempts for issues with the same category, page, or element. Use before proposing a fix to learn from past attempts.",
|
|
152
|
+
inputSchema: z.object({
|
|
153
|
+
issue_id: z
|
|
154
|
+
.string()
|
|
155
|
+
.describe("The ID of the issue to find similar fixes for"),
|
|
156
|
+
limit: z
|
|
157
|
+
.number()
|
|
158
|
+
.optional()
|
|
159
|
+
.default(5)
|
|
160
|
+
.describe("Maximum number of similar fixes to return (default: 5)"),
|
|
161
|
+
}),
|
|
162
|
+
}, async ({ issue_id, limit }) => {
|
|
163
|
+
if (!isApiConfigured()) {
|
|
164
|
+
return {
|
|
165
|
+
content: [
|
|
166
|
+
{
|
|
167
|
+
type: "text",
|
|
168
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
isError: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const { data, error } = await apiGet("/knowledge/similar-fixes", {
|
|
175
|
+
issue_id,
|
|
176
|
+
limit: limit ?? 5,
|
|
177
|
+
});
|
|
178
|
+
if (error) {
|
|
179
|
+
return {
|
|
180
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
181
|
+
isError: true,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
185
|
+
});
|
|
186
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remediation tools for self-healing workflow.
|
|
3
|
+
*
|
|
4
|
+
* Provides fix proposal, deployment confirmation, evaluation, and history tracking.
|
|
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 registerRemediationTools(server) {
|
|
10
|
+
server.registerTool("propose_fix", {
|
|
11
|
+
description: "Propose a fix for an issue. Creates a remediation record with baseline metrics for later evaluation. Include detailed diagnosis and code suggestions when possible.",
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
issue_id: z.string().describe("The ID of the issue to fix"),
|
|
14
|
+
diagnosis: z
|
|
15
|
+
.string()
|
|
16
|
+
.describe("Detailed analysis of the root cause of the issue"),
|
|
17
|
+
proposed_fix: z
|
|
18
|
+
.string()
|
|
19
|
+
.describe("Description of the proposed fix and how it addresses the root cause"),
|
|
20
|
+
code_snippet: z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Suggested code changes (if applicable)"),
|
|
24
|
+
affected_files: z
|
|
25
|
+
.array(z.string())
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("List of files that need to be modified"),
|
|
28
|
+
confidence: z
|
|
29
|
+
.number()
|
|
30
|
+
.min(0)
|
|
31
|
+
.max(1)
|
|
32
|
+
.describe("Confidence level in the fix (0-1). Use <0.7 when uncertain."),
|
|
33
|
+
}),
|
|
34
|
+
}, async ({ issue_id, diagnosis, proposed_fix, code_snippet, affected_files, confidence, }) => {
|
|
35
|
+
if (!isApiConfigured()) {
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
isError: true,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const { data, error } = await apiPost("/remediations", {
|
|
47
|
+
issue_id,
|
|
48
|
+
diagnosis,
|
|
49
|
+
proposed_fix,
|
|
50
|
+
code_snippet,
|
|
51
|
+
affected_files,
|
|
52
|
+
confidence,
|
|
53
|
+
});
|
|
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
|
+
server.registerTool("list_pending_fixes", {
|
|
63
|
+
description: "List all proposed fixes awaiting deployment. Use to show the user what fixes are ready to be deployed.",
|
|
64
|
+
inputSchema: z.object({}),
|
|
65
|
+
}, async () => {
|
|
66
|
+
if (!isApiConfigured()) {
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
isError: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const { data, error } = await apiGet("/remediations/pending");
|
|
78
|
+
if (error) {
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
81
|
+
isError: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
85
|
+
});
|
|
86
|
+
server.registerTool("list_remediations", {
|
|
87
|
+
description: "List remediation records with optional filters. Use to review past and current fix attempts.",
|
|
88
|
+
inputSchema: z.object({
|
|
89
|
+
status: z
|
|
90
|
+
.enum([
|
|
91
|
+
"proposed",
|
|
92
|
+
"deployed",
|
|
93
|
+
"evaluating",
|
|
94
|
+
"succeeded",
|
|
95
|
+
"failed",
|
|
96
|
+
"reverted",
|
|
97
|
+
])
|
|
98
|
+
.optional()
|
|
99
|
+
.describe("Filter by remediation status"),
|
|
100
|
+
issue_id: z.string().optional().describe("Filter by issue ID"),
|
|
101
|
+
page_path: z.string().optional().describe("Filter by page path"),
|
|
102
|
+
limit: z
|
|
103
|
+
.number()
|
|
104
|
+
.optional()
|
|
105
|
+
.default(20)
|
|
106
|
+
.describe("Maximum number of results (default: 20)"),
|
|
107
|
+
}),
|
|
108
|
+
}, async ({ status, issue_id, page_path, limit, }) => {
|
|
109
|
+
if (!isApiConfigured()) {
|
|
110
|
+
return {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
isError: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const { data, error } = await apiGet("/remediations", {
|
|
121
|
+
status,
|
|
122
|
+
issue_id,
|
|
123
|
+
page_path,
|
|
124
|
+
limit: limit ?? 20,
|
|
125
|
+
});
|
|
126
|
+
if (error) {
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
129
|
+
isError: true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
133
|
+
});
|
|
134
|
+
server.registerTool("confirm_deployment", {
|
|
135
|
+
description: "Confirm that a proposed fix has been deployed to production. This starts the evaluation timer. Call this when the user confirms they have deployed the fix.",
|
|
136
|
+
inputSchema: z.object({
|
|
137
|
+
remediation_id: z
|
|
138
|
+
.string()
|
|
139
|
+
.describe("The ID of the remediation to mark as deployed"),
|
|
140
|
+
}),
|
|
141
|
+
}, async ({ remediation_id }) => {
|
|
142
|
+
if (!isApiConfigured()) {
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
isError: true,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const { data, error } = await apiPatch(`/remediations/${remediation_id}/deploy`, {});
|
|
154
|
+
if (error) {
|
|
155
|
+
return {
|
|
156
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
157
|
+
isError: true,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
161
|
+
});
|
|
162
|
+
server.registerTool("evaluate_fix", {
|
|
163
|
+
description: "Evaluate if a deployed fix improved metrics. Compares post-deployment metrics to baseline. Returns success/partial/failed outcome with detailed verdict. Wait at least 24 hours after deployment for reliable results.",
|
|
164
|
+
inputSchema: z.object({
|
|
165
|
+
remediation_id: z
|
|
166
|
+
.string()
|
|
167
|
+
.describe("The ID of the remediation to evaluate"),
|
|
168
|
+
min_hours: z
|
|
169
|
+
.number()
|
|
170
|
+
.optional()
|
|
171
|
+
.default(24)
|
|
172
|
+
.describe("Minimum hours since deployment required (default: 24)"),
|
|
173
|
+
}),
|
|
174
|
+
}, async ({ remediation_id, min_hours, }) => {
|
|
175
|
+
if (!isApiConfigured()) {
|
|
176
|
+
return {
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: "text",
|
|
180
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
isError: true,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const { data, error } = await apiPost(`/remediations/${remediation_id}/evaluate`, { min_hours: min_hours ?? 24 });
|
|
187
|
+
if (error) {
|
|
188
|
+
return {
|
|
189
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
190
|
+
isError: true,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
194
|
+
});
|
|
195
|
+
server.registerTool("get_fix_history", {
|
|
196
|
+
description: "Get the full remediation history for an issue. Shows all fix attempts, their outcomes, and current status. Use before proposing a new fix to learn from past attempts.",
|
|
197
|
+
inputSchema: z.object({
|
|
198
|
+
issue_id: z
|
|
199
|
+
.string()
|
|
200
|
+
.describe("The ID of the issue to get fix history for"),
|
|
201
|
+
}),
|
|
202
|
+
}, async ({ issue_id }) => {
|
|
203
|
+
if (!isApiConfigured()) {
|
|
204
|
+
return {
|
|
205
|
+
content: [
|
|
206
|
+
{
|
|
207
|
+
type: "text",
|
|
208
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
isError: true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const { data, error } = await apiGet(`/remediations/history/${issue_id}`);
|
|
215
|
+
if (error) {
|
|
216
|
+
return {
|
|
217
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
218
|
+
isError: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
222
|
+
});
|
|
223
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Triage tools for self-healing workflow.
|
|
3
|
+
*
|
|
4
|
+
* Provides issue dismissal, marking intended behavior, and issue history.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { apiGet, apiPost, isApiConfigured } from "../api/client.js";
|
|
8
|
+
const DISMISS_REASONS = [
|
|
9
|
+
"stale",
|
|
10
|
+
"intended",
|
|
11
|
+
"duplicate",
|
|
12
|
+
"false_positive",
|
|
13
|
+
];
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
|
+
export function registerTriageTools(server) {
|
|
16
|
+
server.registerTool("dismiss_issue", {
|
|
17
|
+
description: "Dismiss an issue as a false positive. Creates site knowledge to prevent re-flagging. Use when an issue is stale, intended behavior, a duplicate, or a false positive. Always confirm with user before dismissing.",
|
|
18
|
+
inputSchema: z.object({
|
|
19
|
+
issue_id: z.string().describe("The ID of the issue to dismiss"),
|
|
20
|
+
reason: z
|
|
21
|
+
.enum(DISMISS_REASONS)
|
|
22
|
+
.describe("Reason for dismissal: stale, intended, duplicate, or false_positive"),
|
|
23
|
+
explanation: z
|
|
24
|
+
.string()
|
|
25
|
+
.describe("Detailed explanation of why this issue is being dismissed"),
|
|
26
|
+
create_knowledge: z
|
|
27
|
+
.boolean()
|
|
28
|
+
.optional()
|
|
29
|
+
.default(true)
|
|
30
|
+
.describe("Whether to create site knowledge entry (default: true)"),
|
|
31
|
+
}),
|
|
32
|
+
}, async ({ issue_id, reason, explanation, create_knowledge, }) => {
|
|
33
|
+
if (!isApiConfigured()) {
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
isError: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const { data, error } = await apiPost(`/issues/${issue_id}/dismiss`, {
|
|
45
|
+
reason,
|
|
46
|
+
explanation,
|
|
47
|
+
create_knowledge: create_knowledge ?? true,
|
|
48
|
+
});
|
|
49
|
+
if (error) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
52
|
+
isError: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
56
|
+
});
|
|
57
|
+
server.registerTool("mark_intended_behavior", {
|
|
58
|
+
description: "Mark an issue as intended behavior. This is a shortcut for dismissing with reason 'intended' and always creates site knowledge. Use when the detected behavior is by design (e.g., disabled button clicks are expected).",
|
|
59
|
+
inputSchema: z.object({
|
|
60
|
+
issue_id: z
|
|
61
|
+
.string()
|
|
62
|
+
.describe("The ID of the issue to mark as intended"),
|
|
63
|
+
explanation: z
|
|
64
|
+
.string()
|
|
65
|
+
.describe("Explanation of why this behavior is intended"),
|
|
66
|
+
}),
|
|
67
|
+
}, async ({ issue_id, explanation, }) => {
|
|
68
|
+
if (!isApiConfigured()) {
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
isError: true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const { data, error } = await apiPost(`/issues/${issue_id}/mark-intended`, { explanation });
|
|
80
|
+
if (error) {
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
83
|
+
isError: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
87
|
+
});
|
|
88
|
+
server.registerTool("get_issue_history", {
|
|
89
|
+
description: "Get the full history of an issue including status changes, remediation attempts, and outcomes. Use to understand past fix attempts before proposing new ones.",
|
|
90
|
+
inputSchema: z.object({
|
|
91
|
+
issue_id: z.string().describe("The ID of the issue to get history for"),
|
|
92
|
+
}),
|
|
93
|
+
}, async ({ issue_id }) => {
|
|
94
|
+
if (!isApiConfigured()) {
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const { data, error } = await apiGet(`/issues/${issue_id}/history`);
|
|
106
|
+
if (error) {
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
109
|
+
isError: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
113
|
+
});
|
|
114
|
+
}
|