@recapt/mcp 0.0.3-beta → 0.0.5-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.d.ts +12 -1
- package/dist/index.js +247 -163
- package/dist/tools/catalog/callTool.d.ts +22 -0
- package/dist/tools/catalog/callTool.js +92 -0
- package/dist/tools/catalog/index.d.ts +11 -0
- package/dist/tools/catalog/index.js +11 -0
- package/dist/tools/catalog/searchTools.d.ts +22 -0
- package/dist/tools/catalog/searchTools.js +149 -0
- 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 +6 -2
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call Tool — Universal tool proxy for executing any tool by name.
|
|
3
|
+
*
|
|
4
|
+
* This tool allows the agent to invoke ANY tool by name without having
|
|
5
|
+
* all tool descriptions in context. The agent discovers tools via search_tools,
|
|
6
|
+
* then calls them through this proxy.
|
|
7
|
+
*
|
|
8
|
+
* Benefits:
|
|
9
|
+
* - Minimal context: Only ~10 tools have descriptions in the prompt
|
|
10
|
+
* - All 40+ analysis tools accessible on-demand
|
|
11
|
+
* - Scales to any number of tools without context bloat
|
|
12
|
+
*/
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { getToolByName } from "./searchTools.js";
|
|
15
|
+
const toolRegistry = new Map();
|
|
16
|
+
export function registerToolHandler(name, handler) {
|
|
17
|
+
toolRegistry.set(name, handler);
|
|
18
|
+
}
|
|
19
|
+
export function getToolHandler(name) {
|
|
20
|
+
return toolRegistry.get(name);
|
|
21
|
+
}
|
|
22
|
+
const callToolSchema = z.object({
|
|
23
|
+
tool_name: z
|
|
24
|
+
.string()
|
|
25
|
+
.describe("The exact name of the tool to call (e.g., 'get_page_metrics', 'analyze_flow', 'compare_cohorts')"),
|
|
26
|
+
arguments: z
|
|
27
|
+
.record(z.string(), z.unknown())
|
|
28
|
+
.describe("The arguments to pass to the tool as a JSON object. Check the tool description from search_tools for required parameters."),
|
|
29
|
+
});
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
export function registerCallTool(server) {
|
|
32
|
+
server.registerTool("call_tool", {
|
|
33
|
+
description: "Execute any analysis tool by name. Use search_tools first to discover available tools, " +
|
|
34
|
+
"then call them through this proxy. Pass the exact tool name and arguments as a JSON object. " +
|
|
35
|
+
"Example: call_tool({ tool_name: 'get_page_metrics', arguments: { page_path: '/checkout' } })",
|
|
36
|
+
inputSchema: callToolSchema,
|
|
37
|
+
}, async ({ tool_name, arguments: args }) => {
|
|
38
|
+
const handler = toolRegistry.get(tool_name);
|
|
39
|
+
if (!handler) {
|
|
40
|
+
const catalogEntry = getToolByName(tool_name);
|
|
41
|
+
if (catalogEntry) {
|
|
42
|
+
return {
|
|
43
|
+
content: [
|
|
44
|
+
{
|
|
45
|
+
type: "text",
|
|
46
|
+
text: JSON.stringify({
|
|
47
|
+
error: `Tool "${tool_name}" exists in catalog but is not registered. This may be a server configuration issue.`,
|
|
48
|
+
tool_info: {
|
|
49
|
+
name: catalogEntry.name,
|
|
50
|
+
description: catalogEntry.description,
|
|
51
|
+
category: catalogEntry.category,
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
isError: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: JSON.stringify({
|
|
64
|
+
error: `Unknown tool: ${tool_name}`,
|
|
65
|
+
hint: "Use search_tools to discover available tools first.",
|
|
66
|
+
}),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return await handler(args);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
77
|
+
return {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: "text",
|
|
81
|
+
text: JSON.stringify({
|
|
82
|
+
error: `Tool execution failed: ${message}`,
|
|
83
|
+
tool_name,
|
|
84
|
+
arguments: args,
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Catalog — Registry and discovery for MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* - searchTools: Find tools by natural language query
|
|
6
|
+
* - registerSearchTools: Register the search_tools MCP tool
|
|
7
|
+
* - registerCallTool: Register the call_tool MCP tool
|
|
8
|
+
* - registerToolHandler: Register a tool handler for call_tool to invoke
|
|
9
|
+
*/
|
|
10
|
+
export { searchTools, getToolByName, getAllTools, registerSearchTools, type ToolEntry, } from "./searchTools.js";
|
|
11
|
+
export { registerCallTool, registerToolHandler, getToolHandler, type ToolHandler, } from "./callTool.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Catalog — Registry and discovery for MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* Exports:
|
|
5
|
+
* - searchTools: Find tools by natural language query
|
|
6
|
+
* - registerSearchTools: Register the search_tools MCP tool
|
|
7
|
+
* - registerCallTool: Register the call_tool MCP tool
|
|
8
|
+
* - registerToolHandler: Register a tool handler for call_tool to invoke
|
|
9
|
+
*/
|
|
10
|
+
export { searchTools, getToolByName, getAllTools, registerSearchTools, } from "./searchTools.js";
|
|
11
|
+
export { registerCallTool, registerToolHandler, getToolHandler, } from "./callTool.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Tools — Semantic and keyword search over the tool catalog.
|
|
3
|
+
*
|
|
4
|
+
* Allows the agent to discover specialized tools by describing what
|
|
5
|
+
* it needs in natural language. Uses pre-computed embeddings for
|
|
6
|
+
* fast, accurate matching with fallback to keyword search.
|
|
7
|
+
*/
|
|
8
|
+
export interface ToolEntry {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
category: string;
|
|
12
|
+
parameters: Record<string, {
|
|
13
|
+
type: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
description?: string;
|
|
16
|
+
}>;
|
|
17
|
+
embedding: number[];
|
|
18
|
+
}
|
|
19
|
+
export declare function searchTools(query: string, limit?: number, queryEmbedding?: number[]): Promise<ToolEntry[]>;
|
|
20
|
+
export declare function getToolByName(name: string): ToolEntry | undefined;
|
|
21
|
+
export declare function getAllTools(): ToolEntry[];
|
|
22
|
+
export declare function registerSearchTools(server: any): void;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Tools — Semantic and keyword search over the tool catalog.
|
|
3
|
+
*
|
|
4
|
+
* Allows the agent to discover specialized tools by describing what
|
|
5
|
+
* it needs in natural language. Uses pre-computed embeddings for
|
|
6
|
+
* fast, accurate matching with fallback to keyword search.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
let _catalog = null;
|
|
15
|
+
function loadCatalog() {
|
|
16
|
+
if (_catalog)
|
|
17
|
+
return _catalog;
|
|
18
|
+
try {
|
|
19
|
+
const catalogPath = join(__dirname, "toolCatalog.json");
|
|
20
|
+
const raw = readFileSync(catalogPath, "utf-8");
|
|
21
|
+
_catalog = JSON.parse(raw);
|
|
22
|
+
return _catalog;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
console.warn("[searchTools] Failed to load toolCatalog.json, using empty catalog");
|
|
26
|
+
_catalog = [];
|
|
27
|
+
return _catalog;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function cosineSimilarity(a, b) {
|
|
31
|
+
if (a.length === 0 || b.length === 0 || a.length !== b.length)
|
|
32
|
+
return 0;
|
|
33
|
+
let dot = 0;
|
|
34
|
+
for (let i = 0; i < a.length; i++) {
|
|
35
|
+
dot += a[i] * b[i];
|
|
36
|
+
}
|
|
37
|
+
return dot;
|
|
38
|
+
}
|
|
39
|
+
function searchByKeyword(query, limit) {
|
|
40
|
+
const catalog = loadCatalog();
|
|
41
|
+
const queryWords = query
|
|
42
|
+
.toLowerCase()
|
|
43
|
+
.split(/\s+/)
|
|
44
|
+
.filter((w) => w.length > 2);
|
|
45
|
+
return catalog
|
|
46
|
+
.map((tool) => {
|
|
47
|
+
const text = `${tool.name} ${tool.description} ${tool.category}`.toLowerCase();
|
|
48
|
+
let score = 0;
|
|
49
|
+
for (const word of queryWords) {
|
|
50
|
+
if (text.includes(word))
|
|
51
|
+
score += 1;
|
|
52
|
+
if (tool.name.toLowerCase().includes(word))
|
|
53
|
+
score += 2;
|
|
54
|
+
if (tool.category.toLowerCase() === word)
|
|
55
|
+
score += 1;
|
|
56
|
+
}
|
|
57
|
+
return { tool, score };
|
|
58
|
+
})
|
|
59
|
+
.filter((s) => s.score > 0)
|
|
60
|
+
.sort((a, b) => b.score - a.score)
|
|
61
|
+
.slice(0, limit)
|
|
62
|
+
.map((s) => s.tool);
|
|
63
|
+
}
|
|
64
|
+
async function searchBySemantic(query, limit, queryEmbedding) {
|
|
65
|
+
const catalog = loadCatalog();
|
|
66
|
+
return catalog
|
|
67
|
+
.filter((t) => t.embedding && t.embedding.length > 0)
|
|
68
|
+
.map((tool) => ({
|
|
69
|
+
tool,
|
|
70
|
+
score: cosineSimilarity(queryEmbedding, tool.embedding),
|
|
71
|
+
}))
|
|
72
|
+
.sort((a, b) => b.score - a.score)
|
|
73
|
+
.slice(0, limit)
|
|
74
|
+
.map((s) => s.tool);
|
|
75
|
+
}
|
|
76
|
+
export async function searchTools(query, limit = 5, queryEmbedding) {
|
|
77
|
+
if (queryEmbedding && queryEmbedding.length > 0) {
|
|
78
|
+
return searchBySemantic(query, limit, queryEmbedding);
|
|
79
|
+
}
|
|
80
|
+
return searchByKeyword(query, limit);
|
|
81
|
+
}
|
|
82
|
+
export function getToolByName(name) {
|
|
83
|
+
return loadCatalog().find((t) => t.name === name);
|
|
84
|
+
}
|
|
85
|
+
export function getAllTools() {
|
|
86
|
+
return loadCatalog();
|
|
87
|
+
}
|
|
88
|
+
const DEFAULT_TOOL_LIMIT = 5;
|
|
89
|
+
const searchToolsSchema = z.object({
|
|
90
|
+
query: z
|
|
91
|
+
.string()
|
|
92
|
+
.describe("Natural language description of what data or analysis capability you need. " +
|
|
93
|
+
"Describe what you want to learn or investigate."),
|
|
94
|
+
limit: z
|
|
95
|
+
.number()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Maximum number of tools to return (default 5)"),
|
|
98
|
+
});
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
export function registerSearchTools(server) {
|
|
101
|
+
server.registerTool("search_tools", {
|
|
102
|
+
description: "Discover analysis tools by describing what data or capability you need. " +
|
|
103
|
+
"You have access to 40+ specialized tools covering sessions, pages, behaviors, journeys, " +
|
|
104
|
+
"forms, errors, performance, cohorts, issues, and remediation. Keyword search matches your intent to " +
|
|
105
|
+
"relevant tools. Returns tool names, descriptions, categories, and parameters. " +
|
|
106
|
+
"Use call_tool to execute discovered tools.",
|
|
107
|
+
inputSchema: searchToolsSchema,
|
|
108
|
+
}, async ({ query, limit }) => {
|
|
109
|
+
const searchLimit = limit ?? DEFAULT_TOOL_LIMIT;
|
|
110
|
+
const matches = await searchTools(query, searchLimit);
|
|
111
|
+
if (matches.length === 0) {
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: JSON.stringify({
|
|
117
|
+
tools: [],
|
|
118
|
+
message: "No matching tools found. Try rephrasing your query or use broader terms like 'page', 'session', 'issue', 'flow', or 'form'.",
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const tools = matches.map((tool) => ({
|
|
125
|
+
name: tool.name,
|
|
126
|
+
description: tool.description.length > 250
|
|
127
|
+
? tool.description.slice(0, 250) + "..."
|
|
128
|
+
: tool.description,
|
|
129
|
+
category: tool.category,
|
|
130
|
+
parameters: Object.entries(tool.parameters).map(([name, prop]) => ({
|
|
131
|
+
name,
|
|
132
|
+
type: prop.type,
|
|
133
|
+
required: prop.required ?? false,
|
|
134
|
+
description: prop.description,
|
|
135
|
+
})),
|
|
136
|
+
}));
|
|
137
|
+
return {
|
|
138
|
+
content: [
|
|
139
|
+
{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: JSON.stringify({
|
|
142
|
+
tools,
|
|
143
|
+
usage: "Use call_tool with the tool name and arguments to execute. Example: call_tool({ tool_name: 'get_page_metrics', arguments: { page_path: '/checkout' } })",
|
|
144
|
+
}),
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}
|
|
@@ -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
|
+
}
|