@recapt/mcp 0.0.42 → 0.0.44
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 +215 -33
- package/dist/tools/catalog/anthropicToolCatalog.json +87 -239
- package/dist/tools/catalog/toolCatalog.json +1686 -4517
- package/package.json +1 -1
- package/skills/regression-hunt.md +16 -16
- package/skills/self-improvement.md +341 -127
package/dist/index.js
CHANGED
|
@@ -8,6 +8,14 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
8
8
|
// ../../libraries/mcp-tools/dist/client.js
|
|
9
9
|
var DEFAULT_API_URL = "https://api.recapt.app";
|
|
10
10
|
var REQUEST_TIMEOUT_MS = 12e4;
|
|
11
|
+
var MAX_RETRIES = 3;
|
|
12
|
+
var RETRY_BASE_MS = 500;
|
|
13
|
+
function isTransientError(err) {
|
|
14
|
+
if (!(err instanceof Error))
|
|
15
|
+
return false;
|
|
16
|
+
const msg = err.message.toLowerCase();
|
|
17
|
+
return msg === "fetch failed" || msg.includes("econnreset") || msg.includes("econnrefused") || msg.includes("socket hang up") || msg.includes("network") || err.name === "AbortError";
|
|
18
|
+
}
|
|
11
19
|
var _config = {};
|
|
12
20
|
function getApiUrl() {
|
|
13
21
|
return _config.apiUrl || process.env.RECAPT_API_URL || process.env.MCP_API_URL || process.env.EXTERNAL_API_URL || DEFAULT_API_URL;
|
|
@@ -50,8 +58,6 @@ async function request(options) {
|
|
|
50
58
|
requestBody = { ...requestBody, domain: domainFilter.join(",") };
|
|
51
59
|
}
|
|
52
60
|
}
|
|
53
|
-
const controller = new AbortController();
|
|
54
|
-
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
55
61
|
const headers = {
|
|
56
62
|
"x-private-key": secretKey,
|
|
57
63
|
"Content-Type": "application/json"
|
|
@@ -60,27 +66,40 @@ async function request(options) {
|
|
|
60
66
|
if (orgId) {
|
|
61
67
|
headers["x-organization-id"] = orgId;
|
|
62
68
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return {
|
|
69
|
+
const bodyStr = requestBody ? JSON.stringify(requestBody) : void 0;
|
|
70
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
73
|
+
try {
|
|
74
|
+
const res = await fetch(url.toString(), {
|
|
75
|
+
method,
|
|
76
|
+
headers,
|
|
77
|
+
body: bodyStr,
|
|
78
|
+
signal: controller.signal
|
|
79
|
+
});
|
|
80
|
+
clearTimeout(timeout);
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const errorBody = await res.json().catch(() => ({}));
|
|
83
|
+
return { error: errorBody.error || `HTTP ${res.status}` };
|
|
84
|
+
}
|
|
85
|
+
const data = await res.json();
|
|
86
|
+
return { data };
|
|
87
|
+
} catch (err) {
|
|
88
|
+
clearTimeout(timeout);
|
|
89
|
+
if (attempt < MAX_RETRIES && isTransientError(err)) {
|
|
90
|
+
const delay = RETRY_BASE_MS * 2 ** attempt;
|
|
91
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
95
|
+
return {
|
|
96
|
+
error: `Request timed out after ${REQUEST_TIMEOUT_MS / 1e3}s`
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
81
100
|
}
|
|
82
|
-
return { error: err instanceof Error ? err.message : String(err) };
|
|
83
101
|
}
|
|
102
|
+
return { error: "Request failed after retries" };
|
|
84
103
|
}
|
|
85
104
|
function apiGet(path, params) {
|
|
86
105
|
return request({ method: "GET", path, params });
|
|
@@ -145,8 +164,6 @@ function registerAllToolHandlers() {
|
|
|
145
164
|
toolHandlers.set("analyze_funnel", createApiHandler("POST", "/flows/funnel"));
|
|
146
165
|
toolHandlers.set("get_flow_friction", createApiHandler("GET", "/flow-friction"));
|
|
147
166
|
toolHandlers.set("get_journey_patterns", createApiHandler("GET", "/flows/patterns"));
|
|
148
|
-
toolHandlers.set("get_issues", createApiHandler("GET", "/issues"));
|
|
149
|
-
toolHandlers.set("get_actionable_issues", createApiHandler("GET", "/actionable-issues"));
|
|
150
167
|
toolHandlers.set("get_anomalies", createApiHandler("GET", "/anomalies"));
|
|
151
168
|
toolHandlers.set("detect_regressions", createApiHandler("GET", "/regressions"));
|
|
152
169
|
toolHandlers.set("detect_drift", createApiHandler("GET", "/drift"));
|
|
@@ -1069,7 +1086,16 @@ var getSessionDetailsTool = {
|
|
|
1069
1086
|
description: "Get behavioral timeline for one or more sessions. Shows how frustration, confusion, and confidence evolved over time. Accepts a single session_id or an array of session_ids (max 20).",
|
|
1070
1087
|
inputSchema: z21.object({
|
|
1071
1088
|
session_id: z21.string().optional().describe("Single session ID to get details for"),
|
|
1072
|
-
session_ids: z21.
|
|
1089
|
+
session_ids: z21.preprocess((val) => {
|
|
1090
|
+
if (typeof val === "string") {
|
|
1091
|
+
try {
|
|
1092
|
+
return JSON.parse(val);
|
|
1093
|
+
} catch {
|
|
1094
|
+
return val;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return val;
|
|
1098
|
+
}, z21.array(z21.string()).max(20).optional()).describe("Array of session IDs to get details for (max 20)")
|
|
1073
1099
|
}),
|
|
1074
1100
|
handler: async (args) => {
|
|
1075
1101
|
const { session_id, session_ids } = args;
|
|
@@ -1104,7 +1130,16 @@ var getSessionPagesTool = {
|
|
|
1104
1130
|
description: "Get the pages visited within one or more sessions. Returns navigation history with timestamps, source pages, and dwell times. Use this to understand the user's journey through the site during a session. Accepts a single session_id or an array of session_ids (max 20).",
|
|
1105
1131
|
inputSchema: z22.object({
|
|
1106
1132
|
session_id: z22.string().optional().describe("Single session ID to get pages for"),
|
|
1107
|
-
session_ids: z22.
|
|
1133
|
+
session_ids: z22.preprocess((val) => {
|
|
1134
|
+
if (typeof val === "string") {
|
|
1135
|
+
try {
|
|
1136
|
+
return JSON.parse(val);
|
|
1137
|
+
} catch {
|
|
1138
|
+
return val;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return val;
|
|
1142
|
+
}, z22.array(z22.string()).max(20).optional()).describe("Array of session IDs to get pages for (max 20)")
|
|
1108
1143
|
}),
|
|
1109
1144
|
handler: async (args) => {
|
|
1110
1145
|
const { session_id, session_ids } = args;
|
|
@@ -1328,9 +1363,9 @@ var recordImprovementActionTool = {
|
|
|
1328
1363
|
return apiNotConfiguredResult();
|
|
1329
1364
|
}
|
|
1330
1365
|
if (remediation_id) {
|
|
1331
|
-
const {
|
|
1332
|
-
if (remediationError
|
|
1333
|
-
|
|
1366
|
+
const { error: remediationError } = await apiGet(`/remediations/${remediation_id}`);
|
|
1367
|
+
if (remediationError) {
|
|
1368
|
+
console.warn(`Preflight remediation check failed for ${remediation_id}: ${remediationError}. Proceeding with POST.`);
|
|
1334
1369
|
}
|
|
1335
1370
|
}
|
|
1336
1371
|
const { data, error } = await apiPost(`/improvement-runs/${run_id}/actions`, {
|
|
@@ -1748,14 +1783,18 @@ var evaluateFixTool = {
|
|
|
1748
1783
|
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.",
|
|
1749
1784
|
inputSchema: z30.object({
|
|
1750
1785
|
remediation_id: z30.string().describe("The ID of the remediation to evaluate"),
|
|
1751
|
-
min_hours: z30.number().optional().default(24).describe("Minimum hours since deployment required (default: 24)")
|
|
1786
|
+
min_hours: z30.number().optional().default(24).describe("Minimum hours since deployment required (default: 24)"),
|
|
1787
|
+
skip_status_update: z30.boolean().optional().default(false).describe("When true, skips writing remediation status (used in phased mode where applyEvaluateUpdates is authoritative)")
|
|
1752
1788
|
}),
|
|
1753
1789
|
handler: async (args) => {
|
|
1754
|
-
const { remediation_id, min_hours } = args;
|
|
1790
|
+
const { remediation_id, min_hours, skip_status_update } = args;
|
|
1755
1791
|
if (!isApiConfigured()) {
|
|
1756
1792
|
return apiNotConfiguredResult();
|
|
1757
1793
|
}
|
|
1758
|
-
const { data, error } = await apiPost(`/remediations/${remediation_id}/evaluate`, {
|
|
1794
|
+
const { data, error } = await apiPost(`/remediations/${remediation_id}/evaluate`, {
|
|
1795
|
+
min_hours: min_hours ?? 24,
|
|
1796
|
+
skip_status_update: skip_status_update ?? false
|
|
1797
|
+
});
|
|
1759
1798
|
if (error) {
|
|
1760
1799
|
return errorResult(error);
|
|
1761
1800
|
}
|
|
@@ -2019,7 +2058,16 @@ var triageSessionsTool = {
|
|
|
2019
2058
|
name: "triage_sessions",
|
|
2020
2059
|
description: "Automatically triage sessions to find compromised user experiences. Analyzes user comments, frustration signals, rage clicks, and console errors. Returns flagged sessions with evidence and replay links. Use this to identify sessions that need attention without manually reviewing every recording. Can triage specific sessions by ID(s) or scan for problematic sessions. NOTE: Session replay URLs require a paid plan (Starter+). Free tier users can see session summaries and triage scores but not watch replays.",
|
|
2021
2060
|
inputSchema: z33.object({
|
|
2022
|
-
session_ids: z33.
|
|
2061
|
+
session_ids: z33.preprocess((val) => {
|
|
2062
|
+
if (typeof val === "string") {
|
|
2063
|
+
try {
|
|
2064
|
+
return JSON.parse(val);
|
|
2065
|
+
} catch {
|
|
2066
|
+
return val;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
return val;
|
|
2070
|
+
}, z33.array(z33.string()).optional()).describe("Triage specific sessions by ID. If provided, other filters are ignored."),
|
|
2023
2071
|
days: z33.number().optional().default(7).describe("Lookback period in days (default: 7). Ignored if session_ids is provided."),
|
|
2024
2072
|
page_path: z33.string().optional().describe("Filter to sessions that visited a specific page"),
|
|
2025
2073
|
min_severity: z33.enum(["critical", "high", "medium", "low"]).optional().describe("Minimum severity to include in results"),
|
|
@@ -2810,6 +2858,138 @@ var listRepositoryFilesTool = {
|
|
|
2810
2858
|
}
|
|
2811
2859
|
}
|
|
2812
2860
|
};
|
|
2861
|
+
var getRepoTreeTool = {
|
|
2862
|
+
name: "get_repo_tree",
|
|
2863
|
+
description: "Get a condensed directory-only tree of the repository structure. Returns a nested object showing the folder hierarchy up to a configurable depth. Much faster than recursive list_repository_files for understanding project layout. Use this FIRST to orient yourself, then list_repository_files on specific directories.",
|
|
2864
|
+
inputSchema: z35.object({
|
|
2865
|
+
max_depth: z35.number().optional().default(4).describe("Maximum directory depth to return (default: 4)"),
|
|
2866
|
+
branch: z35.string().optional().describe("Branch to read from (default: main)")
|
|
2867
|
+
}),
|
|
2868
|
+
handler: async (args) => {
|
|
2869
|
+
try {
|
|
2870
|
+
const ctx = getGitContext();
|
|
2871
|
+
const maxDepth = args.max_depth || 4;
|
|
2872
|
+
const branch = args.branch || "main";
|
|
2873
|
+
if (ctx.provider === "github") {
|
|
2874
|
+
const res = await githubRequest(`/repos/${ctx.repoFullName}/git/trees/${branch}?recursive=1`);
|
|
2875
|
+
if (!res.ok) {
|
|
2876
|
+
return errorResult(`Failed to get repo tree: ${res.statusText}`);
|
|
2877
|
+
}
|
|
2878
|
+
const data = await res.json();
|
|
2879
|
+
const dirs = data.tree.filter((item) => item.type === "tree").map((item) => item.path);
|
|
2880
|
+
const tree = buildNestedTree(dirs, maxDepth);
|
|
2881
|
+
const totalDirs = dirs.length;
|
|
2882
|
+
return successResult({
|
|
2883
|
+
tree,
|
|
2884
|
+
total_directories: totalDirs,
|
|
2885
|
+
depth: maxDepth,
|
|
2886
|
+
provider: "github"
|
|
2887
|
+
});
|
|
2888
|
+
} else {
|
|
2889
|
+
const encodedPath = encodeURIComponent(ctx.repoFullName);
|
|
2890
|
+
const res = await gitlabRequest(`/projects/${encodedPath}/repository/tree?ref=${branch}&recursive=true&per_page=100`);
|
|
2891
|
+
if (!res.ok) {
|
|
2892
|
+
return errorResult(`Failed to get repo tree: ${res.statusText}`);
|
|
2893
|
+
}
|
|
2894
|
+
const data = await res.json();
|
|
2895
|
+
const dirs = data.filter((item) => item.type === "tree").map((item) => item.path);
|
|
2896
|
+
const tree = buildNestedTree(dirs, maxDepth);
|
|
2897
|
+
const totalDirs = dirs.length;
|
|
2898
|
+
return successResult({
|
|
2899
|
+
tree,
|
|
2900
|
+
total_directories: totalDirs,
|
|
2901
|
+
depth: maxDepth,
|
|
2902
|
+
provider: "gitlab"
|
|
2903
|
+
});
|
|
2904
|
+
}
|
|
2905
|
+
} catch (error) {
|
|
2906
|
+
return errorResult(error instanceof Error ? error.message : "Failed to get repo tree");
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
};
|
|
2910
|
+
var searchCodeTool = {
|
|
2911
|
+
name: "search_code",
|
|
2912
|
+
description: "Search for code patterns, symbols, or imports across the repository. Use this to find existing conventions, verify import paths, or discover reusable utilities before writing code. Rate-limited (~10 req/min on GitHub) \u2014 use sparingly and with specific queries.",
|
|
2913
|
+
inputSchema: z35.object({
|
|
2914
|
+
query: z35.string().describe('Search terms (e.g., "import { Button }", "useMemo", "className={styles.")'),
|
|
2915
|
+
path_filter: z35.string().optional().describe('Restrict search to a directory (e.g., "client/src/components")'),
|
|
2916
|
+
extension: z35.string().optional().describe('Filter by file extension (e.g., "tsx", "ts", "css")')
|
|
2917
|
+
}),
|
|
2918
|
+
handler: async (args) => {
|
|
2919
|
+
try {
|
|
2920
|
+
const ctx = getGitContext();
|
|
2921
|
+
const query = args.query;
|
|
2922
|
+
const pathFilter = args.path_filter;
|
|
2923
|
+
const extension = args.extension;
|
|
2924
|
+
if (ctx.provider === "github") {
|
|
2925
|
+
let q = `${query} repo:${ctx.repoFullName}`;
|
|
2926
|
+
if (pathFilter)
|
|
2927
|
+
q += ` path:${pathFilter}`;
|
|
2928
|
+
if (extension)
|
|
2929
|
+
q += ` extension:${extension}`;
|
|
2930
|
+
const res = await githubRequest(`/search/code?q=${encodeURIComponent(q)}&per_page=10`, {
|
|
2931
|
+
headers: {
|
|
2932
|
+
Accept: "application/vnd.github.text-match+json"
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
if (!res.ok) {
|
|
2936
|
+
const err = await res.json();
|
|
2937
|
+
return errorResult(err.message || `Code search failed: ${res.status}`);
|
|
2938
|
+
}
|
|
2939
|
+
const data = await res.json();
|
|
2940
|
+
const results = data.items.map((item) => ({
|
|
2941
|
+
path: item.path,
|
|
2942
|
+
score: item.score,
|
|
2943
|
+
matched_lines: (item.text_matches ?? []).map((m) => m.fragment),
|
|
2944
|
+
url: item.html_url
|
|
2945
|
+
}));
|
|
2946
|
+
return successResult({
|
|
2947
|
+
total_count: data.total_count,
|
|
2948
|
+
results,
|
|
2949
|
+
provider: "github"
|
|
2950
|
+
});
|
|
2951
|
+
} else {
|
|
2952
|
+
const encodedPath = encodeURIComponent(ctx.repoFullName);
|
|
2953
|
+
const searchQuery = pathFilter ? `${query} filename:${pathFilter}` : query;
|
|
2954
|
+
const res = await gitlabRequest(`/projects/${encodedPath}/search?scope=blobs&search=${encodeURIComponent(searchQuery)}&per_page=10`);
|
|
2955
|
+
if (!res.ok) {
|
|
2956
|
+
const err = await res.json();
|
|
2957
|
+
return errorResult(err.message || `Code search failed: ${res.status}`);
|
|
2958
|
+
}
|
|
2959
|
+
const data = await res.json();
|
|
2960
|
+
const results = data.map((item) => ({
|
|
2961
|
+
path: item.path,
|
|
2962
|
+
matched_lines: [item.data],
|
|
2963
|
+
startline: item.startline
|
|
2964
|
+
}));
|
|
2965
|
+
return successResult({
|
|
2966
|
+
total_count: results.length,
|
|
2967
|
+
results,
|
|
2968
|
+
provider: "gitlab"
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
} catch (error) {
|
|
2972
|
+
return errorResult(error instanceof Error ? error.message : "Code search failed");
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
};
|
|
2976
|
+
function buildNestedTree(dirs, maxDepth) {
|
|
2977
|
+
const tree = {};
|
|
2978
|
+
for (const dir of dirs) {
|
|
2979
|
+
const parts = dir.split("/");
|
|
2980
|
+
if (parts.length > maxDepth)
|
|
2981
|
+
continue;
|
|
2982
|
+
let current = tree;
|
|
2983
|
+
for (const part of parts) {
|
|
2984
|
+
const key = `${part}/`;
|
|
2985
|
+
if (!current[key]) {
|
|
2986
|
+
current[key] = {};
|
|
2987
|
+
}
|
|
2988
|
+
current = current[key];
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
return tree;
|
|
2992
|
+
}
|
|
2813
2993
|
|
|
2814
2994
|
// ../../libraries/mcp-tools/dist/tools/index.js
|
|
2815
2995
|
var allTools = [
|
|
@@ -2892,7 +3072,9 @@ var allTools = [
|
|
|
2892
3072
|
createMergeRequestTool,
|
|
2893
3073
|
checkMrStatusTool,
|
|
2894
3074
|
listOpenMrsTool,
|
|
2895
|
-
listRepositoryFilesTool
|
|
3075
|
+
listRepositoryFilesTool,
|
|
3076
|
+
getRepoTreeTool,
|
|
3077
|
+
searchCodeTool
|
|
2896
3078
|
];
|
|
2897
3079
|
var EXPOSED_TOOL_NAMES = [
|
|
2898
3080
|
"get_domains",
|