@recapt/mcp 0.0.4-beta → 0.0.6-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.js +24 -75
- package/dist/cli/commands/setup.d.ts +7 -0
- package/dist/cli/commands/setup.js +180 -0
- package/dist/cli/commands/skill.d.ts +23 -0
- package/dist/cli/commands/skill.js +251 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.js +24 -0
- package/dist/cli/skill.d.ts +14 -0
- package/dist/cli/skill.js +249 -0
- package/dist/cli/utils/ide-config.d.ts +31 -0
- package/dist/cli/utils/ide-config.js +290 -0
- package/dist/cli/utils/prompts.d.ts +22 -0
- package/dist/cli/utils/prompts.js +133 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.js +233 -260
- 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 +194 -0
- package/dist/tools/catalog/toolCatalog.json +16246 -0
- package/package.json +12 -6
- package/skills/deep-dive.md +92 -0
- package/skills/regression-hunt.md +120 -0
- package/skills/self-healing.md +95 -0
|
@@ -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,194 @@
|
|
|
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 normalizedQuery = query.toLowerCase();
|
|
42
|
+
// Extract words, keeping short ones that might be meaningful (e.g., "ux", "js")
|
|
43
|
+
const queryWords = normalizedQuery
|
|
44
|
+
.split(/[\s_-]+/)
|
|
45
|
+
.filter((w) => w.length >= 2);
|
|
46
|
+
// Also check for the full query as a phrase
|
|
47
|
+
const queryPhrases = [normalizedQuery];
|
|
48
|
+
// Common synonyms and related terms
|
|
49
|
+
const synonyms = {
|
|
50
|
+
error: ["console", "js", "javascript", "bug", "crash", "exception"],
|
|
51
|
+
page: ["pages", "route", "url", "path"],
|
|
52
|
+
user: ["users", "session", "visitor"],
|
|
53
|
+
click: ["clicks", "tap", "press", "rage"],
|
|
54
|
+
form: ["forms", "input", "field", "submit"],
|
|
55
|
+
flow: ["flows", "journey", "funnel", "navigation", "path"],
|
|
56
|
+
issue: ["issues", "problem", "bug", "friction"],
|
|
57
|
+
fix: ["fixes", "remediation", "repair", "resolve"],
|
|
58
|
+
compare: ["comparison", "diff", "versus", "cohort"],
|
|
59
|
+
health: ["score", "metrics", "ux"],
|
|
60
|
+
dead: ["unresponsive", "broken", "stuck"],
|
|
61
|
+
rage: ["angry", "frustrated", "frustration"],
|
|
62
|
+
};
|
|
63
|
+
// Expand query words with synonyms
|
|
64
|
+
const expandedWords = new Set(queryWords);
|
|
65
|
+
for (const word of queryWords) {
|
|
66
|
+
if (synonyms[word]) {
|
|
67
|
+
synonyms[word].forEach((syn) => expandedWords.add(syn));
|
|
68
|
+
}
|
|
69
|
+
// Also check if query word is a synonym value
|
|
70
|
+
for (const [key, values] of Object.entries(synonyms)) {
|
|
71
|
+
if (values.includes(word)) {
|
|
72
|
+
expandedWords.add(key);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return catalog
|
|
77
|
+
.map((tool) => {
|
|
78
|
+
const toolName = tool.name.toLowerCase().replace(/_/g, " ");
|
|
79
|
+
const toolNameParts = tool.name.toLowerCase().split("_");
|
|
80
|
+
const text = `${toolName} ${tool.description} ${tool.category}`.toLowerCase();
|
|
81
|
+
let score = 0;
|
|
82
|
+
// Phrase match in description (highest value)
|
|
83
|
+
for (const phrase of queryPhrases) {
|
|
84
|
+
if (phrase.length > 3 && text.includes(phrase))
|
|
85
|
+
score += 5;
|
|
86
|
+
}
|
|
87
|
+
// Word matches
|
|
88
|
+
for (const word of expandedWords) {
|
|
89
|
+
// Exact word in tool name parts (e.g., "page" matches "get_page_metrics")
|
|
90
|
+
if (toolNameParts.includes(word))
|
|
91
|
+
score += 4;
|
|
92
|
+
// Word appears in tool name
|
|
93
|
+
if (toolName.includes(word))
|
|
94
|
+
score += 3;
|
|
95
|
+
// Category match
|
|
96
|
+
if (tool.category.toLowerCase() === word)
|
|
97
|
+
score += 2;
|
|
98
|
+
// Word in description
|
|
99
|
+
if (text.includes(word))
|
|
100
|
+
score += 1;
|
|
101
|
+
}
|
|
102
|
+
return { tool, score };
|
|
103
|
+
})
|
|
104
|
+
.filter((s) => s.score > 0)
|
|
105
|
+
.sort((a, b) => b.score - a.score)
|
|
106
|
+
.slice(0, limit)
|
|
107
|
+
.map((s) => s.tool);
|
|
108
|
+
}
|
|
109
|
+
async function searchBySemantic(query, limit, queryEmbedding) {
|
|
110
|
+
const catalog = loadCatalog();
|
|
111
|
+
return catalog
|
|
112
|
+
.filter((t) => t.embedding && t.embedding.length > 0)
|
|
113
|
+
.map((tool) => ({
|
|
114
|
+
tool,
|
|
115
|
+
score: cosineSimilarity(queryEmbedding, tool.embedding),
|
|
116
|
+
}))
|
|
117
|
+
.sort((a, b) => b.score - a.score)
|
|
118
|
+
.slice(0, limit)
|
|
119
|
+
.map((s) => s.tool);
|
|
120
|
+
}
|
|
121
|
+
export async function searchTools(query, limit = 5, queryEmbedding) {
|
|
122
|
+
if (queryEmbedding && queryEmbedding.length > 0) {
|
|
123
|
+
return searchBySemantic(query, limit, queryEmbedding);
|
|
124
|
+
}
|
|
125
|
+
return searchByKeyword(query, limit);
|
|
126
|
+
}
|
|
127
|
+
export function getToolByName(name) {
|
|
128
|
+
return loadCatalog().find((t) => t.name === name);
|
|
129
|
+
}
|
|
130
|
+
export function getAllTools() {
|
|
131
|
+
return loadCatalog();
|
|
132
|
+
}
|
|
133
|
+
const DEFAULT_TOOL_LIMIT = 5;
|
|
134
|
+
const searchToolsSchema = z.object({
|
|
135
|
+
query: z
|
|
136
|
+
.string()
|
|
137
|
+
.describe("Natural language description of what data or analysis capability you need. " +
|
|
138
|
+
"Describe what you want to learn or investigate."),
|
|
139
|
+
limit: z
|
|
140
|
+
.number()
|
|
141
|
+
.optional()
|
|
142
|
+
.describe("Maximum number of tools to return (default 5)"),
|
|
143
|
+
});
|
|
144
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
145
|
+
export function registerSearchTools(server) {
|
|
146
|
+
server.registerTool("search_tools", {
|
|
147
|
+
description: "Discover analysis tools by describing what data or capability you need. " +
|
|
148
|
+
"You have access to 40+ specialized tools covering sessions, pages, behaviors, journeys, " +
|
|
149
|
+
"forms, errors, performance, cohorts, issues, and remediation. Keyword search matches your intent to " +
|
|
150
|
+
"relevant tools. Returns tool names, descriptions, categories, and parameters. " +
|
|
151
|
+
"Use call_tool to execute discovered tools.",
|
|
152
|
+
inputSchema: searchToolsSchema,
|
|
153
|
+
}, async ({ query, limit }) => {
|
|
154
|
+
const searchLimit = limit ?? DEFAULT_TOOL_LIMIT;
|
|
155
|
+
const matches = await searchTools(query, searchLimit);
|
|
156
|
+
if (matches.length === 0) {
|
|
157
|
+
return {
|
|
158
|
+
content: [
|
|
159
|
+
{
|
|
160
|
+
type: "text",
|
|
161
|
+
text: JSON.stringify({
|
|
162
|
+
tools: [],
|
|
163
|
+
message: "No matching tools found. Try rephrasing your query or use broader terms like 'page', 'session', 'issue', 'flow', or 'form'.",
|
|
164
|
+
}),
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const tools = matches.map((tool) => ({
|
|
170
|
+
name: tool.name,
|
|
171
|
+
description: tool.description.length > 250
|
|
172
|
+
? tool.description.slice(0, 250) + "..."
|
|
173
|
+
: tool.description,
|
|
174
|
+
category: tool.category,
|
|
175
|
+
parameters: Object.entries(tool.parameters).map(([name, prop]) => ({
|
|
176
|
+
name,
|
|
177
|
+
type: prop.type,
|
|
178
|
+
required: prop.required ?? false,
|
|
179
|
+
description: prop.description,
|
|
180
|
+
})),
|
|
181
|
+
}));
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: JSON.stringify({
|
|
187
|
+
tools,
|
|
188
|
+
usage: "Use call_tool with the tool name and arguments to execute. Example: call_tool({ tool_name: 'get_page_metrics', arguments: { page_path: '/checkout' } })",
|
|
189
|
+
}),
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
}
|