@recapt/mcp 0.0.43 → 0.0.45

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 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
- try {
64
- const res = await fetch(url.toString(), {
65
- method,
66
- headers,
67
- body: requestBody ? JSON.stringify(requestBody) : void 0,
68
- signal: controller.signal
69
- });
70
- clearTimeout(timeout);
71
- if (!res.ok) {
72
- const errorBody = await res.json().catch(() => ({}));
73
- return { error: errorBody.error || `HTTP ${res.status}` };
74
- }
75
- const data = await res.json();
76
- return { data };
77
- } catch (err) {
78
- clearTimeout(timeout);
79
- if (err instanceof Error && err.name === "AbortError") {
80
- return { error: `Request timed out after ${REQUEST_TIMEOUT_MS / 1e3}s` };
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 });
@@ -1067,7 +1086,16 @@ var getSessionDetailsTool = {
1067
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).",
1068
1087
  inputSchema: z21.object({
1069
1088
  session_id: z21.string().optional().describe("Single session ID to get details for"),
1070
- session_ids: z21.array(z21.string()).max(20).optional().describe("Array of session IDs to get details for (max 20)")
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)")
1071
1099
  }),
1072
1100
  handler: async (args) => {
1073
1101
  const { session_id, session_ids } = args;
@@ -1102,7 +1130,16 @@ var getSessionPagesTool = {
1102
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).",
1103
1131
  inputSchema: z22.object({
1104
1132
  session_id: z22.string().optional().describe("Single session ID to get pages for"),
1105
- session_ids: z22.array(z22.string()).max(20).optional().describe("Array of session IDs to get pages for (max 20)")
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)")
1106
1143
  }),
1107
1144
  handler: async (args) => {
1108
1145
  const { session_id, session_ids } = args;
@@ -1326,9 +1363,9 @@ var recordImprovementActionTool = {
1326
1363
  return apiNotConfiguredResult();
1327
1364
  }
1328
1365
  if (remediation_id) {
1329
- const { data: remediation, error: remediationError } = await apiGet(`/remediations/${remediation_id}`);
1330
- if (remediationError || !remediation) {
1331
- return errorResult(`Remediation ${remediation_id} not found`);
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.`);
1332
1369
  }
1333
1370
  }
1334
1371
  const { data, error } = await apiPost(`/improvement-runs/${run_id}/actions`, {
@@ -1746,14 +1783,18 @@ var evaluateFixTool = {
1746
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.",
1747
1784
  inputSchema: z30.object({
1748
1785
  remediation_id: z30.string().describe("The ID of the remediation to evaluate"),
1749
- 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)")
1750
1788
  }),
1751
1789
  handler: async (args) => {
1752
- const { remediation_id, min_hours } = args;
1790
+ const { remediation_id, min_hours, skip_status_update } = args;
1753
1791
  if (!isApiConfigured()) {
1754
1792
  return apiNotConfiguredResult();
1755
1793
  }
1756
- const { data, error } = await apiPost(`/remediations/${remediation_id}/evaluate`, { min_hours: min_hours ?? 24 });
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
+ });
1757
1798
  if (error) {
1758
1799
  return errorResult(error);
1759
1800
  }
@@ -2017,7 +2058,16 @@ var triageSessionsTool = {
2017
2058
  name: "triage_sessions",
2018
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.",
2019
2060
  inputSchema: z33.object({
2020
- session_ids: z33.array(z33.string()).optional().describe("Triage specific sessions by ID. If provided, other filters are ignored."),
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."),
2021
2071
  days: z33.number().optional().default(7).describe("Lookback period in days (default: 7). Ignored if session_ids is provided."),
2022
2072
  page_path: z33.string().optional().describe("Filter to sessions that visited a specific page"),
2023
2073
  min_severity: z33.enum(["critical", "high", "medium", "low"]).optional().describe("Minimum severity to include in results"),
@@ -2808,6 +2858,138 @@ var listRepositoryFilesTool = {
2808
2858
  }
2809
2859
  }
2810
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
+ }
2811
2993
 
2812
2994
  // ../../libraries/mcp-tools/dist/tools/index.js
2813
2995
  var allTools = [
@@ -2890,7 +3072,9 @@ var allTools = [
2890
3072
  createMergeRequestTool,
2891
3073
  checkMrStatusTool,
2892
3074
  listOpenMrsTool,
2893
- listRepositoryFilesTool
3075
+ listRepositoryFilesTool,
3076
+ getRepoTreeTool,
3077
+ searchCodeTool
2894
3078
  ];
2895
3079
  var EXPOSED_TOOL_NAMES = [
2896
3080
  "get_domains",
@@ -2900,6 +3084,7 @@ var EXPOSED_TOOL_NAMES = [
2900
3084
  "search_tools",
2901
3085
  "call_tool",
2902
3086
  "memory_save",
3087
+ "query_memory",
2903
3088
  "memory_recall",
2904
3089
  "memory_list",
2905
3090
  "memory_delete",
@@ -3297,20 +3482,21 @@ function createMemoryStore() {
3297
3482
  }
3298
3483
 
3299
3484
  // ../../libraries/mcp-tools/dist/wrapToolsWithMemory.js
3300
- var SUMMARIZE_THRESHOLD = 2e3;
3485
+ var SUMMARIZE_THRESHOLD = 500;
3301
3486
  var MAX_ARRAY_ITEMS = 2;
3302
3487
  var MAX_STRING_LENGTH = 120;
3303
3488
  var MAX_DEPTH = 3;
3304
3489
  var PASSTHROUGH_TOOLS = /* @__PURE__ */ new Set([
3305
3490
  "memory_save",
3306
3491
  "memory_recall",
3492
+ "query_memory",
3307
3493
  "memory_list",
3308
3494
  "memory_delete",
3309
3495
  "memory_clear",
3310
3496
  "search_tools",
3311
3497
  "get_domains"
3312
3498
  ]);
3313
- function isRecord(value) {
3499
+ function isRecord2(value) {
3314
3500
  return typeof value === "object" && value !== null && !Array.isArray(value);
3315
3501
  }
3316
3502
  function wrapToolResult(toolName, args, result, memory, options) {
@@ -3330,7 +3516,7 @@ function wrapToolResult(toolName, args, result, memory, options) {
3330
3516
  } catch {
3331
3517
  return result;
3332
3518
  }
3333
- if (isRecord(parsed) && parsed.error) {
3519
+ if (isRecord2(parsed) && parsed.error) {
3334
3520
  return result;
3335
3521
  }
3336
3522
  const key = MemoryStore.buildKey(toolName, args);
@@ -3349,7 +3535,7 @@ function wrapToolResult(toolName, args, result, memory, options) {
3349
3535
  function summarizeTruncate(data, memoryKey) {
3350
3536
  const summary = truncateDeep(data, 0);
3351
3537
  addMemoryMetadata(summary, memoryKey, "truncate");
3352
- return isRecord(summary) ? summary : { data: summary, _memory_key: memoryKey };
3538
+ return isRecord2(summary) ? summary : { data: summary, _memory_key: memoryKey };
3353
3539
  }
3354
3540
  function truncateDeep(value, depth) {
3355
3541
  if (value == null || typeof value === "number" || typeof value === "boolean")
@@ -3364,7 +3550,7 @@ function truncateDeep(value, depth) {
3364
3550
  }
3365
3551
  return items;
3366
3552
  }
3367
- if (isRecord(value)) {
3553
+ if (isRecord2(value)) {
3368
3554
  if (depth >= MAX_DEPTH) {
3369
3555
  const keys = Object.keys(value);
3370
3556
  return `{${keys.slice(0, 5).join(", ")}${keys.length > 5 ? ", ..." : ""}}`;
@@ -3380,7 +3566,7 @@ function truncateDeep(value, depth) {
3380
3566
  function summarizeSchema(data, memoryKey) {
3381
3567
  const summary = describeSchema(data, 0);
3382
3568
  addMemoryMetadata(summary, memoryKey, "schema");
3383
- return isRecord(summary) ? summary : { data: summary, _memory_key: memoryKey };
3569
+ return isRecord2(summary) ? summary : { data: summary, _memory_key: memoryKey };
3384
3570
  }
3385
3571
  var SCHEMA_MAX_DISTINCT = 6;
3386
3572
  var SCHEMA_PRIMITIVE_CAP = 5;
@@ -3399,7 +3585,7 @@ function describeSchema(value, depth) {
3399
3585
  if (Array.isArray(value)) {
3400
3586
  if (value.length === 0)
3401
3587
  return [];
3402
- if (value[0] !== void 0 && isRecord(value[0])) {
3588
+ if (value[0] !== void 0 && isRecord2(value[0])) {
3403
3589
  return describeObjectArray(value);
3404
3590
  }
3405
3591
  if (value.length <= SCHEMA_PRIMITIVE_CAP)
@@ -3409,7 +3595,7 @@ function describeSchema(value, depth) {
3409
3595
  `+${value.length - SCHEMA_PRIMITIVE_CAP} more`
3410
3596
  ];
3411
3597
  }
3412
- if (isRecord(value)) {
3598
+ if (isRecord2(value)) {
3413
3599
  if (depth >= SCHEMA_DEPTH_LIMIT) {
3414
3600
  return { _keys: Object.keys(value) };
3415
3601
  }
@@ -3450,7 +3636,7 @@ function computeFieldStat(values) {
3450
3636
  const avgLen = arrays.reduce((sum, a) => sum + a.length, 0) / arrays.length;
3451
3637
  return { type: "array", avg_length: round4(avgLen) };
3452
3638
  }
3453
- if (values.every((v) => isRecord(v))) {
3639
+ if (values.every((v) => isRecord2(v))) {
3454
3640
  const nestedKeys = /* @__PURE__ */ new Set();
3455
3641
  for (const v of values) {
3456
3642
  for (const k of Object.keys(v)) {
@@ -3481,19 +3667,19 @@ function describeObjectArray(items) {
3481
3667
  };
3482
3668
  }
3483
3669
  function addMemoryMetadata(summary, memoryKey, strategy) {
3484
- if (!isRecord(summary))
3670
+ if (!isRecord2(summary))
3485
3671
  return;
3486
3672
  summary._memory_key = memoryKey;
3487
3673
  if (strategy === "truncate") {
3488
- summary._recall = `Full data available via memory_recall with key "${memoryKey}"`;
3674
+ summary._recall = `Full data available via query_memory with key "${memoryKey}". Use where/select/limit to query slices.`;
3489
3675
  } else {
3490
3676
  const arrayPaths = detectArrayPaths(summary);
3491
3677
  const pathHint = arrayPaths.length > 0 ? ` Array fields: ${arrayPaths.join(", ")}.` : "";
3492
- summary._hint = `Use memory_recall with key "${memoryKey}" to get full data.${pathHint}`;
3678
+ summary._hint = `Use query_memory with key "${memoryKey}" to get full data. Supports where, sort, aggregate, group_by, select, limit.${pathHint}`;
3493
3679
  }
3494
3680
  }
3495
3681
  function detectArrayPaths(data) {
3496
- if (!isRecord(data))
3682
+ if (!isRecord2(data))
3497
3683
  return [];
3498
3684
  const paths = [];
3499
3685
  for (const [key, value] of Object.entries(data)) {
@@ -595,92 +595,57 @@
595
595
  }
596
596
  },
597
597
  {
598
- "name": "dismiss_issue",
599
- "description": "Dismiss an issue as a false positive. Creates site knowledge to prevent re-flagging.",
598
+ "name": "propose_fix",
599
+ "description": "Propose a fix for a UX issue. REQUIRED: page_path, diagnosis, proposed_fix, confidence. Creates a remediation record with baseline metrics for later evaluation. Returns remediation_id for tracking.",
600
600
  "input_schema": {
601
601
  "type": "object",
602
602
  "properties": {
603
- "issue_id": {
604
- "description": "The ID of the issue to dismiss",
605
- "type": "string"
606
- },
607
- "reason": {
608
- "description": "Reason for dismissal",
609
- "type": "string"
610
- },
611
- "explanation": {
612
- "description": "Detailed explanation",
603
+ "page_path": {
604
+ "description": "The page path where the issue occurs",
613
605
  "type": "string"
614
606
  },
615
- "create_knowledge": {
616
- "description": "Whether to create site knowledge entry (default: true)",
617
- "type": "boolean"
618
- }
619
- },
620
- "required": [
621
- "issue_id",
622
- "reason",
623
- "explanation"
624
- ]
625
- }
626
- },
627
- {
628
- "name": "mark_intended_behavior",
629
- "description": "Mark an issue as intended behavior. Creates site knowledge to prevent re-flagging.",
630
- "input_schema": {
631
- "type": "object",
632
- "properties": {
633
- "issue_id": {
634
- "description": "The ID of the issue to mark as intended",
607
+ "category": {
608
+ "description": "The issue category (code_error, dead_click, rage_click, ux_friction, performance, form_issue, behavioral_anomaly, structural_issue)",
635
609
  "type": "string"
636
610
  },
637
- "explanation": {
638
- "description": "Explanation of why this behavior is intended",
639
- "type": "string"
640
- }
641
- },
642
- "required": [
643
- "issue_id",
644
- "explanation"
645
- ]
646
- }
647
- },
648
- {
649
- "name": "propose_fix",
650
- "description": "Propose a fix for an issue. Creates a remediation record with baseline metrics for later evaluation. Use this to create a new remediation, even without an existing issue ID.",
651
- "input_schema": {
652
- "type": "object",
653
- "properties": {
654
- "issue_id": {
655
- "description": "The ID of the issue to fix",
611
+ "severity": {
612
+ "description": "The issue severity (critical, high, medium, low, info)",
656
613
  "type": "string"
657
614
  },
658
615
  "diagnosis": {
659
- "description": "Detailed analysis of the root cause",
616
+ "description": "Detailed analysis of the root cause of the issue",
660
617
  "type": "string"
661
618
  },
662
619
  "proposed_fix": {
663
- "description": "Description of the proposed fix",
620
+ "description": "Description of the proposed fix and how it addresses the root cause",
664
621
  "type": "string"
665
622
  },
666
623
  "code_snippet": {
667
- "description": "Suggested code changes",
624
+ "description": "Suggested code changes (if applicable)",
668
625
  "type": "string"
669
626
  },
670
627
  "affected_files": {
671
- "description": "List of files to modify",
628
+ "description": "List of files that need to be modified",
672
629
  "type": "array",
673
630
  "items": {
674
631
  "type": "string"
675
632
  }
676
633
  },
677
634
  "confidence": {
678
- "description": "Confidence level (0-1)",
635
+ "description": "Confidence level in the fix (0-1). Use <0.7 when uncertain.",
679
636
  "type": "number"
637
+ },
638
+ "title": {
639
+ "description": "User-friendly title describing the issue from the user's perspective",
640
+ "type": "string"
641
+ },
642
+ "execution_metadata": {
643
+ "description": "AI execution metadata for tracking model selection and performance",
644
+ "type": "object"
680
645
  }
681
646
  },
682
647
  "required": [
683
- "issue_id",
648
+ "page_path",
684
649
  "diagnosis",
685
650
  "proposed_fix",
686
651
  "confidence"
@@ -722,6 +687,29 @@
722
687
  "required": []
723
688
  }
724
689
  },
690
+ {
691
+ "name": "list_remediations_by_status",
692
+ "description": "List remediations filtered by one or more statuses. Use to find all 'waiting' PRs or 'deployed' fixes ready for evaluation.",
693
+ "input_schema": {
694
+ "type": "object",
695
+ "properties": {
696
+ "statuses": {
697
+ "description": "List of statuses to filter by (e.g. ['waiting', 'deployed'])",
698
+ "type": "array",
699
+ "items": {
700
+ "type": "string"
701
+ }
702
+ },
703
+ "include_execution": {
704
+ "description": "Include execution metadata (difficulty class, model used)",
705
+ "type": "boolean"
706
+ }
707
+ },
708
+ "required": [
709
+ "statuses"
710
+ ]
711
+ }
712
+ },
725
713
  {
726
714
  "name": "confirm_deployment",
727
715
  "description": "Confirm that a proposed fix has been deployed to production. Starts the evaluation timer.",
@@ -1318,6 +1306,48 @@
1318
1306
  "required": []
1319
1307
  }
1320
1308
  },
1309
+ {
1310
+ "name": "get_repo_tree",
1311
+ "description": "Get a condensed directory-only tree of the repository. Returns a nested object showing the folder hierarchy up to a configurable depth. Use FIRST to understand project layout before navigating with list_repository_files.",
1312
+ "input_schema": {
1313
+ "type": "object",
1314
+ "properties": {
1315
+ "max_depth": {
1316
+ "description": "Maximum directory depth (default: 4)",
1317
+ "type": "number"
1318
+ },
1319
+ "branch": {
1320
+ "description": "Branch name (default: main)",
1321
+ "type": "string"
1322
+ }
1323
+ },
1324
+ "required": []
1325
+ }
1326
+ },
1327
+ {
1328
+ "name": "search_code",
1329
+ "description": "Search for code patterns, symbols, or imports across the repository. Use to find existing conventions, verify import paths, or discover reusable utilities. Rate-limited (~10 req/min) — use sparingly with specific queries.",
1330
+ "input_schema": {
1331
+ "type": "object",
1332
+ "properties": {
1333
+ "query": {
1334
+ "description": "Search terms (e.g., \"import { Button }\", \"useMemo\", \"className={styles.\")",
1335
+ "type": "string"
1336
+ },
1337
+ "path_filter": {
1338
+ "description": "Restrict search to a directory (e.g., \"client/src/components\")",
1339
+ "type": "string"
1340
+ },
1341
+ "extension": {
1342
+ "description": "Filter by file extension (e.g., \"tsx\", \"ts\", \"css\")",
1343
+ "type": "string"
1344
+ }
1345
+ },
1346
+ "required": [
1347
+ "query"
1348
+ ]
1349
+ }
1350
+ },
1321
1351
  {
1322
1352
  "name": "get_file_content",
1323
1353
  "description": "Read file contents from the repository.",