codeloop-mcp-server 0.1.0 → 0.1.1

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.
@@ -1,18 +1,20 @@
1
+ /**
2
+ * Vision reviewer utilities.
3
+ *
4
+ * Direct vision API calls (Anthropic, OpenAI, Gemini) have been removed.
5
+ * Visual analysis is now delegated to the AI agent's own vision model
6
+ * via MCP ImageContent blocks — zero cloud LLM cost to CodeLoop.
7
+ *
8
+ * Retained utilities below are for parsing structured JSON responses
9
+ * if the agent returns them, and for reading images as base64.
10
+ */
1
11
  import type { VisualIssue } from "@codelooptech/shared";
2
- import type { CodeLoopConfig } from "@codelooptech/shared";
3
12
  import type { IssueSeverity } from "@codelooptech/shared";
4
- export interface VisionReviewRequest {
5
- imagePath: string;
6
- uxChecklistPath?: string;
7
- model?: string;
8
- }
9
- export interface VisionCompareRequest {
10
- referencePath: string;
11
- actualPath: string;
12
- screenName: string;
13
- model?: string;
14
- uxChecklistPath?: string;
15
- }
13
+ export declare function readImageBase64(imagePath: string): string;
14
+ type VisionImageMedia = "image/jpeg" | "image/png" | "image/gif" | "image/webp";
15
+ export declare function mimeForPath(path: string): VisionImageMedia;
16
+ export declare function parseJsonPayload(text: string): unknown;
17
+ export declare function parseVisualIssuesFromResponse(text: string): VisualIssue[];
16
18
  export interface DesignCompareResult {
17
19
  matchScore: number;
18
20
  differences: Array<{
@@ -22,6 +24,6 @@ export interface DesignCompareResult {
22
24
  fix_hint: string;
23
25
  }>;
24
26
  }
25
- export declare function reviewScreenshot(req: VisionReviewRequest, config: CodeLoopConfig): Promise<VisualIssue[]>;
26
- export declare function compareDesign(req: VisionCompareRequest, config: CodeLoopConfig): Promise<DesignCompareResult>;
27
+ export declare function parseCompareFromResponse(text: string): DesignCompareResult;
28
+ export {};
27
29
  //# sourceMappingURL=vision_reviewer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vision_reviewer.d.ts","sourceRoot":"","sources":["../../src/diagnosis/vision_reviewer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,aAAa,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACJ;AA8SD,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,mBAAmB,EACxB,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,WAAW,EAAE,CAAC,CA8BxB;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,oBAAoB,EACzB,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,mBAAmB,CAAC,CAsD9B"}
1
+ {"version":3,"file":"vision_reviewer.d.ts","sourceRoot":"","sources":["../../src/diagnosis/vision_reviewer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED,KAAK,gBAAgB,GAAG,YAAY,GAAG,WAAW,GAAG,WAAW,GAAG,YAAY,CAAC;AAEhF,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CAO1D;AAWD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAoBtD;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CA4BzE;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,KAAK,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,QAAQ,EAAE,aAAa,CAAC;QACxB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;CACJ;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAuC1E"}
@@ -1,73 +1,31 @@
1
+ /**
2
+ * Vision reviewer utilities.
3
+ *
4
+ * Direct vision API calls (Anthropic, OpenAI, Gemini) have been removed.
5
+ * Visual analysis is now delegated to the AI agent's own vision model
6
+ * via MCP ImageContent blocks — zero cloud LLM cost to CodeLoop.
7
+ *
8
+ * Retained utilities below are for parsing structured JSON responses
9
+ * if the agent returns them, and for reading images as base64.
10
+ */
1
11
  import { readFileSync, existsSync } from "fs";
2
- const REVIEW_JSON_INSTRUCTION = `Respond with ONLY valid JSON (no markdown, no commentary): a JSON array of objects, each with:
3
- - "issue": string (short title)
4
- - "severity": one of "critical", "high", "medium", "low"
5
- - "confidence": number 0-100
6
- - "evidence": string (what you observe in the image)
7
- - "fix_hint": string (actionable fix)`;
8
- const COMPARE_JSON_INSTRUCTION = `Respond with ONLY valid JSON (no markdown, no commentary): an object with:
9
- - "match_score": number from 0.0 (completely different) to 1.0 (pixel-perfect match in layout and styling intent)
10
- - "differences": array of { "area": string, "description": string, "severity": "critical"|"high"|"medium"|"low", "fix_hint": string }`;
11
- function readImageBase64(imagePath) {
12
+ export function readImageBase64(imagePath) {
12
13
  if (!existsSync(imagePath)) {
13
14
  throw new Error(`Image not found: ${imagePath}`);
14
15
  }
15
16
  return readFileSync(imagePath).toString("base64");
16
17
  }
17
- function getEnvKey(provider) {
18
- switch (provider) {
19
- case "claude":
20
- return process.env.ANTHROPIC_API_KEY;
21
- case "openai":
22
- return process.env.OPENAI_API_KEY;
23
- case "gemini":
24
- return process.env.GOOGLE_AI_KEY;
25
- default:
26
- return undefined;
27
- }
28
- }
29
- function resolveAutoProvider() {
30
- if (getEnvKey("claude"))
31
- return "claude";
32
- if (getEnvKey("openai"))
33
- return "openai";
34
- if (getEnvKey("gemini"))
35
- return "gemini";
36
- return null;
37
- }
38
- function resolveProvider(config) {
39
- const mode = config.vision_model;
40
- if (mode === "auto") {
41
- return resolveAutoProvider();
42
- }
43
- const map = {
44
- claude: "claude",
45
- gpt4o: "openai",
46
- gemini: "gemini",
47
- };
48
- const p = map[mode];
49
- if (!p || !getEnvKey(p)) {
50
- return null;
51
- }
52
- return p;
53
- }
54
- function noVisionKeyIssue() {
55
- return {
56
- issue: "No vision model configured",
57
- severity: "low",
58
- confidence: 100,
59
- evidence: "Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_KEY",
60
- fix_hint: "Configure a vision model API key in your environment",
61
- };
62
- }
63
- function warningIssue(message, evidence) {
64
- return {
65
- issue: message,
66
- severity: "low",
67
- confidence: 100,
68
- evidence,
69
- fix_hint: "Check API keys, network, and model availability; then retry.",
70
- };
18
+ export function mimeForPath(path) {
19
+ const lower = path.toLowerCase();
20
+ if (lower.endsWith(".png"))
21
+ return "image/png";
22
+ if (lower.endsWith(".webp"))
23
+ return "image/webp";
24
+ if (lower.endsWith(".gif"))
25
+ return "image/gif";
26
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg"))
27
+ return "image/jpeg";
28
+ return "image/png";
71
29
  }
72
30
  const SEVERITIES = ["critical", "high", "medium", "low"];
73
31
  function normalizeSeverity(s) {
@@ -76,7 +34,7 @@ function normalizeSeverity(s) {
76
34
  }
77
35
  return "medium";
78
36
  }
79
- function parseJsonPayload(text) {
37
+ export function parseJsonPayload(text) {
80
38
  const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
81
39
  const body = fenced ? fenced[1].trim() : text.trim();
82
40
  try {
@@ -100,7 +58,7 @@ function parseJsonPayload(text) {
100
58
  }
101
59
  }
102
60
  }
103
- function parseVisualIssuesFromResponse(text) {
61
+ export function parseVisualIssuesFromResponse(text) {
104
62
  let raw;
105
63
  try {
106
64
  raw = parseJsonPayload(text);
@@ -130,7 +88,7 @@ function parseVisualIssuesFromResponse(text) {
130
88
  }
131
89
  return out;
132
90
  }
133
- function parseCompareFromResponse(text) {
91
+ export function parseCompareFromResponse(text) {
134
92
  let raw;
135
93
  try {
136
94
  raw = parseJsonPayload(text);
@@ -170,176 +128,4 @@ function parseCompareFromResponse(text) {
170
128
  }
171
129
  return { matchScore, differences };
172
130
  }
173
- async function callClaudeVision(prompt, images, modelId) {
174
- const { Anthropic } = await import("@anthropic-ai/sdk");
175
- const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
176
- const content = [];
177
- for (const img of images) {
178
- content.push({
179
- type: "image",
180
- source: {
181
- type: "base64",
182
- media_type: img.mediaType,
183
- data: img.base64,
184
- },
185
- });
186
- }
187
- content.push({ type: "text", text: prompt });
188
- const model = modelId || "claude-3-5-sonnet-20241022";
189
- const res = await client.messages.create({
190
- model,
191
- max_tokens: 4096,
192
- messages: [{ role: "user", content }],
193
- });
194
- const block = res.content.find((b) => b.type === "text");
195
- return block && block.type === "text" ? block.text : "";
196
- }
197
- async function callOpenAiVision(prompt, imageUrls, modelId) {
198
- const { OpenAI } = await import("openai");
199
- const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
200
- const content = [{ type: "text", text: prompt }];
201
- for (const url of imageUrls) {
202
- content.push({
203
- type: "image_url",
204
- image_url: { url },
205
- });
206
- }
207
- const model = modelId || "gpt-4o";
208
- const res = await client.chat.completions.create({
209
- model,
210
- messages: [{ role: "user", content }],
211
- max_tokens: 4096,
212
- });
213
- return res.choices[0]?.message?.content ?? "";
214
- }
215
- async function callGeminiVision(prompt, parts, modelId) {
216
- const { GoogleGenerativeAI } = await import("@google/generative-ai");
217
- const apiKey = process.env.GOOGLE_AI_KEY;
218
- if (!apiKey)
219
- throw new Error("GOOGLE_AI_KEY not set");
220
- const genAI = new GoogleGenerativeAI(apiKey);
221
- const model = genAI.getGenerativeModel({ model: modelId || "gemini-1.5-pro" });
222
- const res = await model.generateContent([
223
- prompt,
224
- ...parts.map((p) => ({
225
- inlineData: { mimeType: p.mimeType, data: p.data },
226
- })),
227
- ]);
228
- const text = res.response.text();
229
- return text ?? "";
230
- }
231
- function mimeForPath(path) {
232
- const lower = path.toLowerCase();
233
- if (lower.endsWith(".png"))
234
- return "image/png";
235
- if (lower.endsWith(".webp"))
236
- return "image/webp";
237
- if (lower.endsWith(".gif"))
238
- return "image/gif";
239
- if (lower.endsWith(".jpg") || lower.endsWith(".jpeg"))
240
- return "image/jpeg";
241
- return "image/png";
242
- }
243
- async function runVision(provider, prompt, imagePaths, modelOverride) {
244
- const paths = imagePaths;
245
- if (provider === "claude") {
246
- const images = paths.map((p) => ({
247
- base64: readImageBase64(p),
248
- mediaType: mimeForPath(p),
249
- }));
250
- return callClaudeVision(prompt, images, modelOverride ?? "");
251
- }
252
- if (provider === "openai") {
253
- const urls = paths.map((p) => {
254
- const b64 = readImageBase64(p);
255
- const mime = mimeForPath(p);
256
- return `data:${mime};base64,${b64}`;
257
- });
258
- return callOpenAiVision(prompt, urls, modelOverride ?? "");
259
- }
260
- const parts = paths.map((p) => ({
261
- mimeType: mimeForPath(p),
262
- data: readImageBase64(p),
263
- }));
264
- return callGeminiVision(prompt, parts, modelOverride ?? "");
265
- }
266
- export async function reviewScreenshot(req, config) {
267
- const provider = resolveProvider(config);
268
- if (!provider) {
269
- return [noVisionKeyIssue()];
270
- }
271
- let checklist = "";
272
- if (req.uxChecklistPath && existsSync(req.uxChecklistPath)) {
273
- try {
274
- checklist = readFileSync(req.uxChecklistPath, "utf-8");
275
- }
276
- catch {
277
- checklist = "";
278
- }
279
- }
280
- const prompt = `You are an expert UX/UI reviewer. Analyze the screenshot for usability, visual hierarchy, spacing, typography, touch targets, affordances, consistency, and obvious accessibility problems.
281
-
282
- ${checklist ? `Apply this UX checklist where relevant:\n${checklist}\n\n` : ""}
283
- ${REVIEW_JSON_INSTRUCTION}`;
284
- try {
285
- const text = await runVision(provider, prompt, [req.imagePath], req.model);
286
- const issues = parseVisualIssuesFromResponse(text);
287
- return issues;
288
- }
289
- catch (e) {
290
- const msg = e instanceof Error ? e.message : String(e);
291
- return [
292
- warningIssue("Vision API error during screenshot review", msg),
293
- ];
294
- }
295
- }
296
- export async function compareDesign(req, config) {
297
- const provider = resolveProvider(config);
298
- if (!provider) {
299
- return {
300
- matchScore: 0,
301
- differences: [
302
- {
303
- area: "configuration",
304
- description: "No vision API key available for design comparison.",
305
- severity: "low",
306
- fix_hint: "Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_AI_KEY",
307
- },
308
- ],
309
- };
310
- }
311
- let checklist = "";
312
- if (req.uxChecklistPath && existsSync(req.uxChecklistPath)) {
313
- try {
314
- checklist = readFileSync(req.uxChecklistPath, "utf-8");
315
- }
316
- catch {
317
- checklist = "";
318
- }
319
- }
320
- const prompt = `Compare the REFERENCE design image (first image) with the ACTUAL implementation screenshot (second image) for screen "${req.screenName}".
321
- Assess layout, typography, colors, spacing, component placement, and visual hierarchy. Ignore benign anti-aliasing or compression artifacts.
322
-
323
- ${checklist ? `Consider these UX/design criteria where relevant:\n${checklist}\n\n` : ""}
324
- ${COMPARE_JSON_INSTRUCTION}`;
325
- try {
326
- const text = await runVision(provider, prompt, [req.referencePath, req.actualPath], req.model);
327
- const parsed = parseCompareFromResponse(text);
328
- return parsed;
329
- }
330
- catch (e) {
331
- const msg = e instanceof Error ? e.message : String(e);
332
- return {
333
- matchScore: 0,
334
- differences: [
335
- {
336
- area: "vision_api",
337
- description: "Vision API error during design comparison.",
338
- severity: "medium",
339
- fix_hint: msg,
340
- },
341
- ],
342
- };
343
- }
344
- }
345
131
  //# sourceMappingURL=vision_reviewer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"vision_reviewer.js","sourceRoot":"","sources":["../../src/diagnosis/vision_reviewer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vision_reviewer.js","sourceRoot":"","sources":["../../src/diagnosis/vision_reviewer.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,9 +2,32 @@
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, existsSync } from "fs";
5
6
  import { loadConfig } from "./config.js";
6
7
  import { validateApiKey, isActivationRequired } from "./auth/api_key.js";
7
8
  import { trackUsage } from "./auth/usage_tracker.js";
9
+ function readImageAsBase64(path) {
10
+ if (!existsSync(path))
11
+ return null;
12
+ try {
13
+ return readFileSync(path).toString("base64");
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ function mimeForPath(path) {
20
+ const lower = path.toLowerCase();
21
+ if (lower.endsWith(".png"))
22
+ return "image/png";
23
+ if (lower.endsWith(".webp"))
24
+ return "image/webp";
25
+ if (lower.endsWith(".gif"))
26
+ return "image/gif";
27
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg"))
28
+ return "image/jpeg";
29
+ return "image/png";
30
+ }
8
31
  const config = loadConfig();
9
32
  const apiKey = process.env.CODELOOP_API_KEY || config.api_key;
10
33
  const server = new McpServer({
@@ -102,19 +125,20 @@ Returns: pass/fail for each gate, overall confidence score, and recommendation.`
102
125
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
103
126
  };
104
127
  });
105
- // ── Stub Tools (registered for discovery, implemented in later phases) ──
128
+ // ── Vision Tools (agent-delegated: returns images for AI agent analysis) ──
106
129
  server.tool("codeloop_visual_review", `Capture and analyze screenshots of the current UI for visual issues. Use this tool when:
107
130
  - You have made UI changes and want to check for visual regressions
108
- - You want a vision-model review of screenshots against a UX checklist
131
+ - You want to review screenshots against a UX checklist
109
132
  - You need to compare the current UI against baseline screenshots
110
- Returns: visual issues with severity, diff scores against baselines, and layout warnings.`, {
133
+ Deterministic checks (pixel diffing, baseline comparison) run automatically. Screenshots are returned as images for you to analyze visually using your own vision capabilities.
134
+ Returns: deterministic diff results + screenshot images for visual analysis.`, {
111
135
  run_id: z.string().optional(),
112
136
  screenshots_dir: z.string().optional(),
113
137
  baseline_dir: z.string().optional(),
114
138
  ux_checklist_path: z.string().optional(),
115
139
  viewport_sizes: z.array(z.string()).optional(),
116
140
  }, async (params) => {
117
- const result = await withAuth(async () => {
141
+ const authResult = await withAuth(async () => {
118
142
  const { runVisualReview } = await import("./tools/visual_review.js");
119
143
  const input = {
120
144
  run_id: params.run_id,
@@ -123,26 +147,48 @@ Returns: visual issues with severity, diff scores against baselines, and layout
123
147
  ux_checklist_path: params.ux_checklist_path,
124
148
  viewport_sizes: params.viewport_sizes,
125
149
  };
126
- const output = await runVisualReview(input, config);
150
+ const result = await runVisualReview(input, config);
127
151
  await trackUsage(apiKey, "visual_review");
128
- return output;
152
+ return result;
129
153
  });
130
- return {
131
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
132
- };
154
+ if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
155
+ return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
156
+ }
157
+ const result = authResult;
158
+ const content = [
159
+ { type: "text", text: JSON.stringify(result.output, null, 2) },
160
+ ];
161
+ const imageBlocks = [];
162
+ for (const imgPath of result.screenshotPaths) {
163
+ const data = readImageAsBase64(imgPath);
164
+ if (data) {
165
+ imageBlocks.push({ type: "image", data, mimeType: mimeForPath(imgPath) });
166
+ }
167
+ }
168
+ if (imageBlocks.length > 0) {
169
+ let prompt = `Analyze these ${imageBlocks.length} screenshot(s) for visual issues.\nCheck: spacing, alignment, typography, color contrast, touch targets, visual hierarchy, accessibility, and responsiveness.`;
170
+ if (result.uxChecklist) {
171
+ prompt += `\n\nApply this UX checklist:\n${result.uxChecklist}`;
172
+ }
173
+ prompt += `\n\nReport issues as JSON array: [{ "issue": string, "severity": "critical"|"high"|"medium"|"low", "confidence": number, "evidence": string, "fix_hint": string }]`;
174
+ content.push({ type: "text", text: prompt });
175
+ content.push(...imageBlocks);
176
+ }
177
+ return { content };
133
178
  });
134
179
  server.tool("codeloop_design_compare", `Compare a reference design image against the actual coded UI. Use this tool when:
135
180
  - The user has provided a Figma mockup, screenshot, or design reference
136
181
  - You want to measure how closely the coded UI matches the intended design
137
182
  - You need structured differences to fix specific design mismatches
138
- Returns: match score, list of differences with fix hints, and comparison screenshots.`, {
183
+ Deterministic pixel-level comparison runs automatically. Reference and actual images are returned for you to visually compare using your own vision capabilities.
184
+ Returns: pixel diff score + reference, actual, and diff images for visual comparison.`, {
139
185
  reference_image_path: z.string(),
140
186
  screen_name: z.string(),
141
187
  platform: z.string(),
142
188
  viewport_sizes: z.array(z.string()).optional(),
143
189
  ux_checklist_path: z.string().optional(),
144
190
  }, async (params) => {
145
- const result = await withAuth(async () => {
191
+ const authResult = await withAuth(async () => {
146
192
  const { runDesignCompare } = await import("./tools/design_compare.js");
147
193
  const input = {
148
194
  reference_image_path: params.reference_image_path,
@@ -151,13 +197,39 @@ Returns: match score, list of differences with fix hints, and comparison screens
151
197
  viewport_sizes: params.viewport_sizes ?? [],
152
198
  ux_checklist_path: params.ux_checklist_path,
153
199
  };
154
- const output = await runDesignCompare(input, config);
200
+ const result = await runDesignCompare(input, config);
155
201
  await trackUsage(apiKey, "visual_review");
156
- return output;
202
+ return result;
157
203
  });
158
- return {
159
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
160
- };
204
+ if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
205
+ return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
206
+ }
207
+ const result = authResult;
208
+ const content = [
209
+ { type: "text", text: JSON.stringify(result.output, null, 2) },
210
+ ];
211
+ const refData = result.imagePaths.reference ? readImageAsBase64(result.imagePaths.reference) : null;
212
+ const actData = result.imagePaths.actual ? readImageAsBase64(result.imagePaths.actual) : null;
213
+ const diffData = result.imagePaths.diff ? readImageAsBase64(result.imagePaths.diff) : null;
214
+ if (refData || actData) {
215
+ let prompt = `Compare the REFERENCE design (1st image) vs the ACTUAL implementation (2nd image) for screen "${params.screen_name}".`;
216
+ if (diffData) {
217
+ prompt += `\nThe 3rd image shows the pixel-level diff highlight (red = changed pixels).`;
218
+ }
219
+ prompt += `\nAssess: layout, typography, colors, spacing, component placement, visual hierarchy.`;
220
+ if (result.uxChecklist) {
221
+ prompt += `\n\nApply this UX checklist:\n${result.uxChecklist}`;
222
+ }
223
+ prompt += `\n\nReport as JSON: { "match_score": 0-1, "differences": [{ "area": string, "description": string, "severity": "critical"|"high"|"medium"|"low", "fix_hint": string }] }`;
224
+ content.push({ type: "text", text: prompt });
225
+ if (refData)
226
+ content.push({ type: "image", data: refData, mimeType: mimeForPath(result.imagePaths.reference) });
227
+ if (actData)
228
+ content.push({ type: "image", data: actData, mimeType: mimeForPath(result.imagePaths.actual) });
229
+ if (diffData)
230
+ content.push({ type: "image", data: diffData, mimeType: mimeForPath(result.imagePaths.diff) });
231
+ }
232
+ return { content };
161
233
  });
162
234
  server.tool("codeloop_section_status", `Check the progress of multi-section app development. Use this tool when:
163
235
  - A master spec exists and you need to know which section to work on next
@@ -270,6 +342,81 @@ Returns: list of affected sections, new states, and recommended next actions.`,
270
342
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
271
343
  };
272
344
  });
345
+ server.tool("codeloop_interaction_replay", `Analyze a recorded video of a user interaction flow to verify it completes as expected. Use this tool when:
346
+ - You have a screen recording of a user flow and want to verify it works correctly
347
+ - You want to check if a multi-step interaction (signup, checkout, onboarding) completes without errors
348
+ - You need evidence-based assessment of a recorded flow against expected behavior
349
+ Key frames are extracted from the video and returned as images for you to analyze visually using your own vision capabilities. Requires ffmpeg installed on the system.
350
+ Returns: extracted key frames as images + expected flow description for visual analysis.`, {
351
+ video_path: z.string().optional(),
352
+ run_id: z.string().optional(),
353
+ expected_flow: z.string(),
354
+ }, async (params) => {
355
+ const authResult = await withAuth(async () => {
356
+ const { runInteractionReplay } = await import("./tools/interaction_replay.js");
357
+ const output = await runInteractionReplay({
358
+ video_path: params.video_path,
359
+ run_id: params.run_id,
360
+ expected_flow: params.expected_flow,
361
+ }, config);
362
+ await trackUsage(apiKey, "visual_review");
363
+ return output;
364
+ });
365
+ if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
366
+ return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
367
+ }
368
+ const result = authResult;
369
+ const content = [];
370
+ if (!result.ffmpeg_available && result.framePaths.length === 0) {
371
+ const msg = result.video_analyzed === "(none)"
372
+ ? "No video file provided or found in the latest run. Set evidence.capture_videos to true in .codeloop/config.json and re-run verification, or provide a video_path directly."
373
+ : "ffmpeg is not installed. Install ffmpeg to enable video frame extraction for interaction replay analysis.";
374
+ content.push({ type: "text", text: JSON.stringify({ error: true, message: msg, video_analyzed: result.video_analyzed }, null, 2) });
375
+ return { content };
376
+ }
377
+ const imageBlocks = [];
378
+ for (const framePath of result.framePaths) {
379
+ const data = readImageAsBase64(framePath);
380
+ if (data) {
381
+ imageBlocks.push({ type: "image", data, mimeType: "image/png" });
382
+ }
383
+ }
384
+ if (imageBlocks.length > 0) {
385
+ const prompt = `Analyze these ${imageBlocks.length} key frames extracted from a user interaction recording.
386
+ Video analyzed: ${result.video_analyzed}
387
+
388
+ Expected flow:
389
+ ${result.expected_flow}
390
+
391
+ Determine: Did the full expected flow complete? What UI steps/screens were observed? Were there errors, glitches, or missing steps?
392
+
393
+ Report as JSON: { "flow_completed": boolean, "completion_score": 0.0-1.0, "steps_observed": string[], "issues": [{ "step": string, "description": string, "severity": "critical"|"high"|"medium"|"low", "timestamp_hint": string }], "summary": string }`;
394
+ content.push({ type: "text", text: prompt });
395
+ content.push(...imageBlocks);
396
+ }
397
+ else {
398
+ content.push({ type: "text", text: JSON.stringify({ error: true, message: "No frames could be extracted from the video.", video_analyzed: result.video_analyzed }, null, 2) });
399
+ }
400
+ return { content };
401
+ });
402
+ server.tool("codeloop_recommend_action", `Context-aware recommendation router. Use this tool when:
403
+ - The user mentions a need (hosting, email, auth, analytics, etc.) but does not specify a category
404
+ - You want automatic tool recommendations based on the project context and user intent
405
+ - You prefer a single entry point rather than choosing between recommend_tool categories manually
406
+ Returns: inferred category and budget, ranked recommendations, and routing explanation.`, {
407
+ context: z.string(),
408
+ run_id: z.string().optional(),
409
+ }, async (params) => {
410
+ const result = await withAuth(async () => {
411
+ const { runRecommendAction } = await import("./tools/recommend_action.js");
412
+ const output = runRecommendAction({ context: params.context, run_id: params.run_id }, config);
413
+ await trackUsage(apiKey, "verification_run");
414
+ return output;
415
+ });
416
+ return {
417
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
418
+ };
419
+ });
273
420
  // ── Start Server ─────────────────────────────────────────────────
274
421
  const transport = new StdioServerTransport();
275
422
  await server.connect(transport);