codeloop-mcp-server 0.1.48 → 0.1.49
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/auth/critical_floors.d.ts +8 -4
- package/dist/auth/critical_floors.d.ts.map +1 -1
- package/dist/auth/critical_floors.js +13 -17
- package/dist/auth/critical_floors.js.map +1 -1
- package/dist/auth/init_hint_cache.d.ts +35 -0
- package/dist/auth/init_hint_cache.d.ts.map +1 -0
- package/dist/auth/init_hint_cache.js +143 -0
- package/dist/auth/init_hint_cache.js.map +1 -0
- package/dist/evidence/screenshot_diff.d.ts +23 -0
- package/dist/evidence/screenshot_diff.d.ts.map +1 -1
- package/dist/evidence/screenshot_diff.js +46 -13
- package/dist/evidence/screenshot_diff.js.map +1 -1
- package/dist/index.js +168 -11
- package/dist/index.js.map +1 -1
- package/dist/runners/csproj_output_path.d.ts +22 -0
- package/dist/runners/csproj_output_path.d.ts.map +1 -0
- package/dist/runners/csproj_output_path.js +108 -0
- package/dist/runners/csproj_output_path.js.map +1 -0
- package/dist/runners/png_dims.d.ts +20 -0
- package/dist/runners/png_dims.d.ts.map +1 -0
- package/dist/runners/png_dims.js +58 -0
- package/dist/runners/png_dims.js.map +1 -0
- package/dist/runners/window_manager.d.ts +17 -4
- package/dist/runners/window_manager.d.ts.map +1 -1
- package/dist/runners/window_manager.js +135 -22
- package/dist/runners/window_manager.js.map +1 -1
- package/dist/tools/design_compare.d.ts.map +1 -1
- package/dist/tools/design_compare.js +14 -0
- package/dist/tools/design_compare.js.map +1 -1
- package/dist/tools/desktop_app_mode.d.ts +48 -0
- package/dist/tools/desktop_app_mode.d.ts.map +1 -0
- package/dist/tools/desktop_app_mode.js +86 -0
- package/dist/tools/desktop_app_mode.js.map +1 -0
- package/dist/tools/self_test.d.ts +40 -0
- package/dist/tools/self_test.d.ts.map +1 -0
- package/dist/tools/self_test.js +205 -0
- package/dist/tools/self_test.js.map +1 -0
- package/dist/tools/verify.d.ts.map +1 -1
- package/dist/tools/verify.js +4 -5
- package/dist/tools/verify.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { readFileSync, writeFileSync, existsSync, readdirSync } from "fs";
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "fs";
|
|
6
6
|
function dirHasFile(dir, predicate) {
|
|
7
7
|
try {
|
|
8
8
|
if (!existsSync(dir))
|
|
@@ -20,6 +20,7 @@ import { loadConfig } from "./config.js";
|
|
|
20
20
|
import { validateApiKey, isActivationRequired } from "./auth/api_key.js";
|
|
21
21
|
import { identifyKeySource, buildRevokedKeyDiagnostic } from "./auth/key_source.js";
|
|
22
22
|
import { warmCliCache } from "./auth/cli_cache_warmer.js";
|
|
23
|
+
import { recordInitialisedDir, wasInitialisedAtPath, } from "./auth/init_hint_cache.js";
|
|
23
24
|
import { startUpdateCheck, getUpdateInfo, formatUpdateNotice, getRunningVersion, } from "./auth/update_check.js";
|
|
24
25
|
import { applyUpdate, applyUpdateInputSchema, } from "./tools/apply_update.js";
|
|
25
26
|
import { trackUsage } from "./auth/usage_tracker.js";
|
|
@@ -87,6 +88,49 @@ if (!process.env.CODELOOP_PROJECT_DIR &&
|
|
|
87
88
|
`or set CODELOOP_PROJECT_DIR in your MCP config so future calls auto-resolve. ` +
|
|
88
89
|
`codeloop_init_project will REFUSE to scaffold here.`);
|
|
89
90
|
}
|
|
91
|
+
// 0.1.49 — stale CODELOOP_PROJECT_DIR detection.
|
|
92
|
+
//
|
|
93
|
+
// When init writes a workspace pin into .cursor/mcp.json, it bakes
|
|
94
|
+
// the absolute path of the workspace at the time. If the user later
|
|
95
|
+
// renames or moves the workspace folder (common on Windows when a
|
|
96
|
+
// project graduates from D:\Work\<name> to D:\Repos\<name>), the pin
|
|
97
|
+
// keeps pointing at the old path that no longer exists, and every
|
|
98
|
+
// MCP boot resolves projectDir to a non-existent directory — which
|
|
99
|
+
// silently turns init/verify/gate into no-ops because every "does
|
|
100
|
+
// the .codeloop/ folder exist?" check returns false.
|
|
101
|
+
//
|
|
102
|
+
// We log a single, loud, agent-readable line on stderr so the agent
|
|
103
|
+
// knows to re-run `npx codeloop init` (which rewrites the pin to
|
|
104
|
+
// the workspace's current absolute path — see G8 in the CLI).
|
|
105
|
+
{
|
|
106
|
+
const pinned = process.env.CODELOOP_PROJECT_DIR;
|
|
107
|
+
if (pinned) {
|
|
108
|
+
let stale = false;
|
|
109
|
+
let reason = "";
|
|
110
|
+
try {
|
|
111
|
+
if (!existsSync(pinned)) {
|
|
112
|
+
stale = true;
|
|
113
|
+
reason = "path does not exist";
|
|
114
|
+
}
|
|
115
|
+
else if (!statSync(pinned).isDirectory()) {
|
|
116
|
+
stale = true;
|
|
117
|
+
reason = "path is not a directory";
|
|
118
|
+
}
|
|
119
|
+
else if (!existsSync(join(pinned, ".codeloop", "config.json"))) {
|
|
120
|
+
stale = true;
|
|
121
|
+
reason = "no .codeloop/config.json under the pinned path";
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
stale = true;
|
|
126
|
+
reason = e.message;
|
|
127
|
+
}
|
|
128
|
+
if (stale) {
|
|
129
|
+
console.error(`[CodeLoop] ⚠ CODELOOP_PROJECT_DIR=${pinned} is stale (${reason}) — falling back to discovery. ` +
|
|
130
|
+
`Re-run \`npx codeloop init\` from the workspace's current location to rewrite the pin.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
90
134
|
const config = loadConfig(projectDir);
|
|
91
135
|
const apiKey = process.env.CODELOOP_API_KEY || config.api_key;
|
|
92
136
|
// Pre-warm the npx cache for the `codeloop` CLI in the background so
|
|
@@ -310,6 +354,13 @@ function rememberInitializedDir(dir) {
|
|
|
310
354
|
return;
|
|
311
355
|
if (isProjectInitialized(dir)) {
|
|
312
356
|
lastInitializedDir = dir;
|
|
357
|
+
// 0.1.49 — also persist to ~/.codeloop/init-hint-cache.json so
|
|
358
|
+
// the next MCP server boot (every IDE restart) doesn't false-
|
|
359
|
+
// positive the "project not initialised" hint until the agent
|
|
360
|
+
// has happened to forward `dir` to a handler that calls back
|
|
361
|
+
// into this function. Best-effort; failures swallowed inside
|
|
362
|
+
// recordInitialisedDir.
|
|
363
|
+
recordInitialisedDir(dir);
|
|
313
364
|
}
|
|
314
365
|
}
|
|
315
366
|
function withInitHint(content, dir) {
|
|
@@ -336,7 +387,12 @@ function withInitHint(content, dir) {
|
|
|
336
387
|
// home folder on Windows / Cursor — see CODELOOP_PROJECT_DIR
|
|
337
388
|
// auto-injection notes in setup-project.ts).
|
|
338
389
|
const candidates = [dir, lastInitializedDir, projectDir].filter((d) => typeof d === "string" && d.length > 0);
|
|
339
|
-
|
|
390
|
+
// 0.1.49 — also consult the persistent cache so the very first
|
|
391
|
+
// tool call after an IDE restart doesn't false-positive the hint
|
|
392
|
+
// when `dir` wasn't passed and `lastInitializedDir` is empty (the
|
|
393
|
+
// session's not warmed up yet).
|
|
394
|
+
const anyInitialized = candidates.some((d) => isProjectInitialized(d)) ||
|
|
395
|
+
candidates.some((d) => wasInitialisedAtPath(d));
|
|
340
396
|
if (!anyInitialized) {
|
|
341
397
|
head.push({ type: "text", text: INIT_HINT });
|
|
342
398
|
}
|
|
@@ -1206,13 +1262,12 @@ Returns: confirmation + the captured image as an MCP ImageContent block so you c
|
|
|
1206
1262
|
// agent forgot app_name — and the auto-fix loop would then
|
|
1207
1263
|
// burn cycles trying to "fix design diffs" against a
|
|
1208
1264
|
// screenshot of the editor.
|
|
1209
|
-
const { detectPlatform } = await import("./tools/verify.js");
|
|
1210
1265
|
const { loadConfig } = await import("./config.js");
|
|
1211
|
-
const
|
|
1212
|
-
const
|
|
1266
|
+
const { isDesktopAppProject } = await import("./tools/desktop_app_mode.js");
|
|
1267
|
+
const desktopApp = isDesktopAppProject(cwd);
|
|
1213
1268
|
const cfg = loadConfig(cwd);
|
|
1214
1269
|
const targetApp = params.app_name ?? cfg.evidence?.target_app;
|
|
1215
|
-
const result = await captureScreenshot(screenshotsDir, params.screen_name, targetApp, undefined, { desktopAppMode:
|
|
1270
|
+
const result = await captureScreenshot(screenshotsDir, params.screen_name, targetApp, undefined, { desktopAppMode: desktopApp });
|
|
1216
1271
|
// Photometry-DB E2E 8 follow-on: when we capture a desktop app
|
|
1217
1272
|
// window, also resolve its on-screen bounds so the agent can
|
|
1218
1273
|
// (a) compute window-relative coords from the returned image
|
|
@@ -1223,7 +1278,7 @@ Returns: confirmation + the captured image as an MCP ImageContent block so you c
|
|
|
1223
1278
|
// of the image and clicked tens or hundreds of pixels off the
|
|
1224
1279
|
// intended target.
|
|
1225
1280
|
let windowBounds = null;
|
|
1226
|
-
if (
|
|
1281
|
+
if (desktopApp && targetApp && result.captured) {
|
|
1227
1282
|
try {
|
|
1228
1283
|
const wm = await import("./runners/window_manager.js");
|
|
1229
1284
|
const b = await wm.getWindowBounds(targetApp);
|
|
@@ -2012,10 +2067,25 @@ Returns: checklist of completed and pending verification steps.`, {
|
|
|
2012
2067
|
const verdict = evaluateDepth(coverage, minimums, discoverySnapshot);
|
|
2013
2068
|
const b = coverage.buckets;
|
|
2014
2069
|
const breakdown = `click=${b.click}, navigation=${b.navigation}, input=${b.input}, commit=${b.commit}, toggle=${b.toggle}, gesture=${b.gesture}, upload=${b.upload}, keystroke=${b.keystroke}, inspect=${b.inspect}`;
|
|
2070
|
+
// 0.1.49: coordinate_clicks_without_intent is now a HARD
|
|
2071
|
+
// step-7 PENDING blocker so the agent sees the gap BEFORE
|
|
2072
|
+
// gate_check, not after. Pre-0.1.49 this only surfaced as
|
|
2073
|
+
// a verify postscript note, which agents commonly ignored
|
|
2074
|
+
// until the user_journey_evidence gate failed at the
|
|
2075
|
+
// bottom of a long verify→capture→video→gate cycle —
|
|
2076
|
+
// wasting the entire UI-evidence loop.
|
|
2077
|
+
const coordsWithoutIntent = coverage.coordinate_clicks_without_intent;
|
|
2015
2078
|
if (!minimums.enabled) {
|
|
2016
2079
|
depthStatus = "n/a";
|
|
2017
2080
|
depthDetail = `Depth gate disabled in .codeloop/config.json. Observed buckets: ${breakdown}.`;
|
|
2018
2081
|
}
|
|
2082
|
+
else if (coordsWithoutIntent > 0) {
|
|
2083
|
+
depthStatus = "PENDING";
|
|
2084
|
+
depthDetail =
|
|
2085
|
+
`${coverage.successful} successful interactions across ${runs.length} run(s) (${breakdown}). ` +
|
|
2086
|
+
`BLOCKER: ${coordsWithoutIntent} coordinate-only click(s) dispatched without intent / description / purpose / step fields — the CRUD classifier in user_journey_evidence cannot credit them as edit/delete/create. ` +
|
|
2087
|
+
`Re-run codeloop_interact for those clicks WITH \`intent\` (e.g. intent="confirm delete dialog", intent="save form"). Otherwise gate_check will return continue_fixing on user_journey_evidence even after the rest of the workflow is green.`;
|
|
2088
|
+
}
|
|
2019
2089
|
else if (verdict.passed) {
|
|
2020
2090
|
depthStatus = "done";
|
|
2021
2091
|
depthDetail = `${coverage.successful} successful interactions across ${runs.length} run(s) (${breakdown}). Depth minimums met.`;
|
|
@@ -2180,7 +2250,8 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2180
2250
|
description: z.string().optional().describe("[Alias for intent] Same semantics."),
|
|
2181
2251
|
purpose: z.string().optional().describe("[Alias for intent] Same semantics."),
|
|
2182
2252
|
step: z.string().optional().describe("Plan-step name when this interaction is driving a codeloop_plan_user_journey arc (e.g. 'edit', 'delete', 'create', 'save', 'verify'). Logged alongside `intent` and read by the CRUD classifier."),
|
|
2183
|
-
coords: z.enum(["auto", "window", "screen"]).optional().describe("How to interpret x/y for desktop click/double_click/right_click/hover/scroll/drag/long_press. `auto` (default): if `app_name` resolves to a visible window AND (x, y) fits inside the window's client area, treat as window-relative and auto-offset by the window origin; otherwise leave as raw screen-absolute coords. `window`: ALWAYS add the window origin offset (errors if the window isn't found). `screen`: ALWAYS pass through (legacy behaviour, matches CGEvent / user32.dll / xdotool semantics). Fixes the Photometry-DB E2E 8 failure mode where the agent captured a 1600×900 window screenshot, computed click coords against the image, and missed the sidebar because the window's actual top-left was (286, 286) on a 5120×1440 screen."),
|
|
2253
|
+
coords: z.enum(["auto", "window", "screen", "screenshot"]).optional().describe("How to interpret x/y for desktop click/double_click/right_click/hover/scroll/drag/long_press. `auto` (default): if `app_name` resolves to a visible window AND (x, y) fits inside the window's client area, treat as window-relative and auto-offset by the window origin; otherwise leave as raw screen-absolute coords. `window`: ALWAYS add the window origin offset (errors if the window isn't found). `screen`: ALWAYS pass through (legacy behaviour, matches CGEvent / user32.dll / xdotool semantics). `screenshot` (most accurate for vision-driven agents): treat (x, y) as coordinates against a captured screenshot — provide `screenshot_path` so the runner can read the image's actual width/height, scale (x, y) to the window's true pixel dimensions, then add the window origin and apply DPI. Use this whenever you computed coords from the image returned by codeloop_capture_screenshot, especially when the MCP transport may have downscaled the PNG. Fixes the Photometry-DB E2E 8 failure mode where the agent captured a 1600×900 window screenshot, computed click coords against the image, and missed the sidebar because the window's actual top-left was (286, 286) on a 5120×1440 screen."),
|
|
2254
|
+
screenshot_path: z.string().optional().describe("Absolute path to the screenshot PNG that x/y were computed against. Used with `coords: \"screenshot\"` to scale agent-supplied coords from the captured image dimensions to the window's actual pixel dimensions before applying the window origin and DPI factor. Pass the `path` field returned by codeloop_capture_screenshot."),
|
|
2184
2255
|
project_dir: z.string().optional().describe("Absolute path to project root."),
|
|
2185
2256
|
workspace_root: z.string().optional().describe("[Alias for project_dir] Pass either; they're equivalent."),
|
|
2186
2257
|
}, async (params) => {
|
|
@@ -2211,6 +2282,7 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2211
2282
|
}
|
|
2212
2283
|
// Bring the app to front before desktop interactions (non-browser, non-mobile).
|
|
2213
2284
|
let windowOriginOffset = null;
|
|
2285
|
+
let screenshotDims = null;
|
|
2214
2286
|
if (tt === "desktop") {
|
|
2215
2287
|
const appName = params.app_name || vr.getActiveRecordingAppName();
|
|
2216
2288
|
if (appName && action !== "wait") {
|
|
@@ -2233,14 +2305,35 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2233
2305
|
try {
|
|
2234
2306
|
const b = await wm.getWindowBounds(appName);
|
|
2235
2307
|
if (b && b.width > 0 && b.height > 0) {
|
|
2236
|
-
windowOriginOffset = {
|
|
2308
|
+
windowOriginOffset = {
|
|
2309
|
+
dx: b.x,
|
|
2310
|
+
dy: b.y,
|
|
2311
|
+
width: b.width,
|
|
2312
|
+
height: b.height,
|
|
2313
|
+
dpiX: b.dpi_x,
|
|
2314
|
+
dpiY: b.dpi_y,
|
|
2315
|
+
};
|
|
2237
2316
|
}
|
|
2238
2317
|
}
|
|
2239
2318
|
catch { /* best-effort */ }
|
|
2240
2319
|
}
|
|
2320
|
+
// For coords:"screenshot", load the actual PNG dims so we
|
|
2321
|
+
// can scale agent-supplied (x, y) up from the (possibly
|
|
2322
|
+
// MCP-downscaled) image to the window's true pixel size.
|
|
2323
|
+
if (coordsMode === "screenshot" && params.screenshot_path) {
|
|
2324
|
+
try {
|
|
2325
|
+
const { readPngDims } = await import("./runners/png_dims.js");
|
|
2326
|
+
screenshotDims = readPngDims(params.screenshot_path);
|
|
2327
|
+
}
|
|
2328
|
+
catch { /* best-effort */ }
|
|
2329
|
+
}
|
|
2241
2330
|
}
|
|
2242
2331
|
}
|
|
2243
2332
|
// Helper used by every coordinate-driven desktop action below.
|
|
2333
|
+
// Photometry-DB E2E 8 + 0.1.49 hardening: handles four modes
|
|
2334
|
+
// (auto / window / screen / screenshot) plus an optional DPI
|
|
2335
|
+
// factor on the window bounds so high-DPI Windows / Retina
|
|
2336
|
+
// displays don't drop clicks 100s of pixels off-target.
|
|
2244
2337
|
const translateXY = (x, y) => {
|
|
2245
2338
|
if (tt !== "desktop" || x == null || y == null || !windowOriginOffset) {
|
|
2246
2339
|
return { x, y };
|
|
@@ -2248,8 +2341,35 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2248
2341
|
const mode = params.coords ?? "auto";
|
|
2249
2342
|
if (mode === "screen")
|
|
2250
2343
|
return { x, y };
|
|
2344
|
+
const applyDpi = (px, py) => {
|
|
2345
|
+
// window_manager records DPI in physical-pixel-per-logical
|
|
2346
|
+
// form (1.0 = 96 DPI baseline; 2.0 = 200% / Retina). When
|
|
2347
|
+
// the screenshot was captured in logical pixels but the
|
|
2348
|
+
// OS click API expects physical pixels (Win32 user32.dll
|
|
2349
|
+
// and modern macOS CGEvent both expect physical), scale up.
|
|
2350
|
+
const dpiX = windowOriginOffset.dpiX ?? 1;
|
|
2351
|
+
const dpiY = windowOriginOffset.dpiY ?? 1;
|
|
2352
|
+
if (dpiX === 1 && dpiY === 1)
|
|
2353
|
+
return { x: px, y: py };
|
|
2354
|
+
return { x: px * dpiX, y: py * dpiY };
|
|
2355
|
+
};
|
|
2356
|
+
if (mode === "screenshot") {
|
|
2357
|
+
// Scale (x, y) from screenshot dims → window dims,
|
|
2358
|
+
// then add the window origin, then DPI.
|
|
2359
|
+
let sx = x;
|
|
2360
|
+
let sy = y;
|
|
2361
|
+
if (screenshotDims && screenshotDims.width > 0 && screenshotDims.height > 0) {
|
|
2362
|
+
const ratioX = windowOriginOffset.width / screenshotDims.width;
|
|
2363
|
+
const ratioY = windowOriginOffset.height / screenshotDims.height;
|
|
2364
|
+
sx = x * ratioX;
|
|
2365
|
+
sy = y * ratioY;
|
|
2366
|
+
}
|
|
2367
|
+
const dpi = applyDpi(sx, sy);
|
|
2368
|
+
return { x: dpi.x + windowOriginOffset.dx, y: dpi.y + windowOriginOffset.dy };
|
|
2369
|
+
}
|
|
2251
2370
|
if (mode === "window") {
|
|
2252
|
-
|
|
2371
|
+
const dpi = applyDpi(x, y);
|
|
2372
|
+
return { x: dpi.x + windowOriginOffset.dx, y: dpi.y + windowOriginOffset.dy };
|
|
2253
2373
|
}
|
|
2254
2374
|
// auto: if (x, y) fits inside the window's client area,
|
|
2255
2375
|
// assume the agent computed against a window-cropped
|
|
@@ -2258,7 +2378,8 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2258
2378
|
const inside = x >= 0 && x <= windowOriginOffset.width &&
|
|
2259
2379
|
y >= 0 && y <= windowOriginOffset.height;
|
|
2260
2380
|
if (inside) {
|
|
2261
|
-
|
|
2381
|
+
const dpi = applyDpi(x, y);
|
|
2382
|
+
return { x: dpi.x + windowOriginOffset.dx, y: dpi.y + windowOriginOffset.dy };
|
|
2262
2383
|
}
|
|
2263
2384
|
return { x, y };
|
|
2264
2385
|
};
|
|
@@ -3113,6 +3234,42 @@ No project_dir / workspace_root required — this tool is workspace-independent.
|
|
|
3113
3234
|
]),
|
|
3114
3235
|
};
|
|
3115
3236
|
});
|
|
3237
|
+
server.tool("codeloop_self_test", TOOL_BOOTSTRAP + `Pre-flight smoke test for CodeLoop on the current workspace. Run this on any NEW project BEFORE your first verify cycle, or whenever something looks off (silent IDE captures, "no .exe found", design_compare returning 0%, etc.).
|
|
3238
|
+
|
|
3239
|
+
It validates every critical pre-condition synthetically (no live build, no live screenshot, no network past the platform sniff):
|
|
3240
|
+
- Workspace exists on disk and is a directory
|
|
3241
|
+
- codeloop_init_project has been run (.codeloop/config.json present)
|
|
3242
|
+
- Platform detection produced a known platform
|
|
3243
|
+
- isDesktopAppProject correctly identifies the project type (so captureScreenshot won't silently fall back to fullscreen)
|
|
3244
|
+
- evidence.target_app is set when desktop-app mode is ON (so launchDesktopApp + captureScreenshot can resolve a window)
|
|
3245
|
+
- PNG decoder skip path is wired (corrupt PNGs become skip warnings, not 0% match)
|
|
3246
|
+
- Coordinate translation round-trips on a synthetic high-DPI fixture (so clicks land where the agent expects on Retina / 200%-DPI displays)
|
|
3247
|
+
|
|
3248
|
+
Returns a structured pass/fail report with per-check fix suggestions and a single \`next_step\` directive.
|
|
3249
|
+
|
|
3250
|
+
Use this tool FIRST when:
|
|
3251
|
+
- The user reports CodeLoop "isn't working" or evidence is missing
|
|
3252
|
+
- Switching to a project / repo you've never run CodeLoop against
|
|
3253
|
+
- Debugging unexplained gate failures that don't match the agent's mental model
|
|
3254
|
+
|
|
3255
|
+
Idempotent and free — safe to call as the first step of every new chat.`, {
|
|
3256
|
+
project_dir: z.string().optional().describe("Absolute path to the project root. Defaults to CODELOOP_PROJECT_DIR env var or auto-discovered project directory."),
|
|
3257
|
+
workspace_root: z.string().optional().describe("[Alias for project_dir] Same semantics."),
|
|
3258
|
+
}, async (params) => {
|
|
3259
|
+
const result = await withAuth(async () => {
|
|
3260
|
+
const cwd = (params.project_dir || params.workspace_root || projectDir);
|
|
3261
|
+
const { runSelfTest } = await import("./tools/self_test.js");
|
|
3262
|
+
return runSelfTest(cwd);
|
|
3263
|
+
}, { tool: "codeloop_self_test", cwd: (params.project_dir || params.workspace_root || projectDir), input: params });
|
|
3264
|
+
if (typeof result === "object" && result !== null && "error" in result) {
|
|
3265
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3266
|
+
}
|
|
3267
|
+
return {
|
|
3268
|
+
content: withInitHint([
|
|
3269
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
3270
|
+
]),
|
|
3271
|
+
};
|
|
3272
|
+
});
|
|
3116
3273
|
server.tool("codeloop_apply_update", TOOL_BOOTSTRAP + `Apply a pending CodeLoop MCP server update to the current chat session — without asking the user to restart their IDE.
|
|
3117
3274
|
|
|
3118
3275
|
Use this tool when:
|