lace-mcp 0.0.2-alpha.16 → 0.0.2-alpha.19
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/README.md +7 -6
- package/SKILL.md +9 -9
- package/dist/cli.js +261 -224
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ MCP server for [Lace](https://inlace.co). Connects your coding agent to approved
|
|
|
5
5
|
## How it works
|
|
6
6
|
|
|
7
7
|
1. You review your product in Lace and approve a decision on the [Decision Canvas](https://docs.inlace.co/concepts/decision-canvas)
|
|
8
|
-
2. Your coding agent calls `
|
|
8
|
+
2. Your coding agent calls `search_decisions` to find what's been approved
|
|
9
9
|
3. It calls `apply_decision` to get screenshots, element targeting, and step-by-step instructions
|
|
10
10
|
4. The agent writes the code change and you review the diff
|
|
11
11
|
|
|
@@ -28,18 +28,19 @@ The Lace desktop app must be open and signed in for the MCP server to work.
|
|
|
28
28
|
|
|
29
29
|
| Tool | Description |
|
|
30
30
|
| --- | --- |
|
|
31
|
-
| `
|
|
32
|
-
| `apply_decision` |
|
|
33
|
-
| `
|
|
31
|
+
| `search_decisions` | Search and filter resolved decisions with pagination. No params returns the top 20 |
|
|
32
|
+
| `apply_decision` | Implementation kit for a decision: screenshots, targeting, instructions. `include` controls detail level |
|
|
33
|
+
| `list_canvases` | Browse review canvases with page names and decision counts |
|
|
34
|
+
| `list_projects` | Browse projects with thread and canvas counts |
|
|
34
35
|
|
|
35
|
-
Use `
|
|
36
|
+
Use `search_decisions` to discover, then `apply_decision` to act. Use `list_canvases` and `list_projects` to find IDs for filtering.
|
|
36
37
|
|
|
37
38
|
## Usage
|
|
38
39
|
|
|
39
40
|
Once installed, ask your coding agent:
|
|
40
41
|
|
|
41
42
|
```
|
|
42
|
-
"
|
|
43
|
+
"Search Lace decisions"
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
Or to implement directly:
|
package/SKILL.md
CHANGED
|
@@ -9,15 +9,15 @@ Lace surfaces adoption insights as actionable decisions. When a decision is appr
|
|
|
9
9
|
|
|
10
10
|
## Steps
|
|
11
11
|
|
|
12
|
-
1. Call the `
|
|
13
|
-
2. If
|
|
14
|
-
3.
|
|
15
|
-
4.
|
|
16
|
-
5.
|
|
17
|
-
6.
|
|
18
|
-
7.
|
|
12
|
+
1. Call the `search_decisions` MCP tool with no params to see recently approved decisions, or pass filters such as `query`, `canvasId`, `projectId`, `appName`, or `elementRole` when the user has given scope.
|
|
13
|
+
2. If the workspace has many canvases or projects, call `list_canvases` or `list_projects` first and use the returned IDs/titles to scope `search_decisions`.
|
|
14
|
+
3. If there is exactly one relevant decision, proceed with it. If there are multiple plausible matches, ask the user which one to implement.
|
|
15
|
+
4. Call `apply_decision` with the chosen decision ID. Use `target: "code"` for code changes, `target: "slideshow"` for slide content, `target: "graphic"` for graphic specs, or `target: "prototype"` for prototype data.
|
|
16
|
+
5. Use `include: "implementation"` by default. Use `include: "image"` for a cropped element image, `include: "context"` for thread rationale, or `include: "all"` when you need screenshots, cropped image, and rationale together.
|
|
17
|
+
6. Follow the implementation instructions returned by `apply_decision` exactly — they include screenshots, element targeting, and constraints.
|
|
18
|
+
7. Implement the minimal code change described. Do not refactor or modify anything outside the target element.
|
|
19
19
|
|
|
20
20
|
## Troubleshooting
|
|
21
21
|
|
|
22
|
-
- If `
|
|
23
|
-
- If no decisions are returned, the user may need to approve a decision in the Lace overlay first.
|
|
22
|
+
- If `search_decisions` returns an authentication error, tell the user to run `npx lace-mcp install <editor>` to re-authenticate.
|
|
23
|
+
- If no decisions are returned, the user may need to approve a decision in the Lace overlay first or provide a narrower canvas/project scope.
|
package/dist/cli.js
CHANGED
|
@@ -41598,9 +41598,9 @@ var require_balanced_match = __commonJS({
|
|
|
41598
41598
|
}
|
|
41599
41599
|
});
|
|
41600
41600
|
|
|
41601
|
-
// ../../node_modules/.pnpm/brace-expansion@2.
|
|
41601
|
+
// ../../node_modules/.pnpm/brace-expansion@2.1.1/node_modules/brace-expansion/index.js
|
|
41602
41602
|
var require_brace_expansion = __commonJS({
|
|
41603
|
-
"../../node_modules/.pnpm/brace-expansion@2.
|
|
41603
|
+
"../../node_modules/.pnpm/brace-expansion@2.1.1/node_modules/brace-expansion/index.js"(exports, module2) {
|
|
41604
41604
|
var balanced = require_balanced_match();
|
|
41605
41605
|
module2.exports = expandTop;
|
|
41606
41606
|
var escSlash = "\0SLASH" + Math.random() + "\0";
|
|
@@ -41637,13 +41637,15 @@ var require_brace_expansion = __commonJS({
|
|
|
41637
41637
|
parts.push.apply(parts, p);
|
|
41638
41638
|
return parts;
|
|
41639
41639
|
}
|
|
41640
|
-
function expandTop(str) {
|
|
41640
|
+
function expandTop(str, options) {
|
|
41641
41641
|
if (!str)
|
|
41642
41642
|
return [];
|
|
41643
|
+
options = options || {};
|
|
41644
|
+
var max = options.max == null ? Infinity : options.max;
|
|
41643
41645
|
if (str.substr(0, 2) === "{}") {
|
|
41644
41646
|
str = "\\{\\}" + str.substr(2);
|
|
41645
41647
|
}
|
|
41646
|
-
return expand2(escapeBraces(str), true).map(unescapeBraces);
|
|
41648
|
+
return expand2(escapeBraces(str), max, true).map(unescapeBraces);
|
|
41647
41649
|
}
|
|
41648
41650
|
function embrace(str) {
|
|
41649
41651
|
return "{" + str + "}";
|
|
@@ -41657,14 +41659,14 @@ var require_brace_expansion = __commonJS({
|
|
|
41657
41659
|
function gte(i, y) {
|
|
41658
41660
|
return i >= y;
|
|
41659
41661
|
}
|
|
41660
|
-
function expand2(str, isTop) {
|
|
41662
|
+
function expand2(str, max, isTop) {
|
|
41661
41663
|
var expansions = [];
|
|
41662
41664
|
var m = balanced("{", "}", str);
|
|
41663
41665
|
if (!m) return [str];
|
|
41664
41666
|
var pre = m.pre;
|
|
41665
|
-
var post = m.post.length ? expand2(m.post, false) : [""];
|
|
41667
|
+
var post = m.post.length ? expand2(m.post, max, false) : [""];
|
|
41666
41668
|
if (/\$$/.test(m.pre)) {
|
|
41667
|
-
for (var k = 0; k < post.length; k++) {
|
|
41669
|
+
for (var k = 0; k < post.length && k < max; k++) {
|
|
41668
41670
|
var expansion = pre + "{" + m.body + "}" + post[k];
|
|
41669
41671
|
expansions.push(expansion);
|
|
41670
41672
|
}
|
|
@@ -41676,7 +41678,7 @@ var require_brace_expansion = __commonJS({
|
|
|
41676
41678
|
if (!isSequence && !isOptions) {
|
|
41677
41679
|
if (m.post.match(/,(?!,).*\}/)) {
|
|
41678
41680
|
str = m.pre + "{" + m.body + escClose + m.post;
|
|
41679
|
-
return expand2(str);
|
|
41681
|
+
return expand2(str, max, true);
|
|
41680
41682
|
}
|
|
41681
41683
|
return [str];
|
|
41682
41684
|
}
|
|
@@ -41686,7 +41688,7 @@ var require_brace_expansion = __commonJS({
|
|
|
41686
41688
|
} else {
|
|
41687
41689
|
n = parseCommaParts(m.body);
|
|
41688
41690
|
if (n.length === 1) {
|
|
41689
|
-
n = expand2(n[0], false).map(embrace);
|
|
41691
|
+
n = expand2(n[0], max, false).map(embrace);
|
|
41690
41692
|
if (n.length === 1) {
|
|
41691
41693
|
return post.map(function(p) {
|
|
41692
41694
|
return m.pre + n[0] + p;
|
|
@@ -41708,7 +41710,7 @@ var require_brace_expansion = __commonJS({
|
|
|
41708
41710
|
}
|
|
41709
41711
|
var pad = n.some(isPadded);
|
|
41710
41712
|
N = [];
|
|
41711
|
-
for (var i = x; test(i, y); i += incr) {
|
|
41713
|
+
for (var i = x; test(i, y) && N.length < max; i += incr) {
|
|
41712
41714
|
var c;
|
|
41713
41715
|
if (isAlphaSequence) {
|
|
41714
41716
|
c = String.fromCharCode(i);
|
|
@@ -41732,11 +41734,11 @@ var require_brace_expansion = __commonJS({
|
|
|
41732
41734
|
} else {
|
|
41733
41735
|
N = [];
|
|
41734
41736
|
for (var j = 0; j < n.length; j++) {
|
|
41735
|
-
N.push.apply(N, expand2(n[j], false));
|
|
41737
|
+
N.push.apply(N, expand2(n[j], max, false));
|
|
41736
41738
|
}
|
|
41737
41739
|
}
|
|
41738
41740
|
for (var j = 0; j < N.length; j++) {
|
|
41739
|
-
for (var k = 0; k < post.length; k++) {
|
|
41741
|
+
for (var k = 0; k < post.length && expansions.length < max; k++) {
|
|
41740
41742
|
var expansion = pre + N[j] + post[k];
|
|
41741
41743
|
if (!isTop || isSequence || expansion)
|
|
41742
41744
|
expansions.push(expansion);
|
|
@@ -82163,10 +82165,13 @@ var init_zod2 = __esm({
|
|
|
82163
82165
|
});
|
|
82164
82166
|
|
|
82165
82167
|
// ../../libs/shared/src/decisionCanvas/types.ts
|
|
82168
|
+
function isFullPageBbox(target) {
|
|
82169
|
+
return target.bbox_x1 <= 0 && target.bbox_y1 <= 0 && target.bbox_x2 >= 1 && target.bbox_y2 >= 1;
|
|
82170
|
+
}
|
|
82166
82171
|
function isCanvasDecisionAnnotation(annotation) {
|
|
82167
82172
|
return annotation.kind == null || annotation.kind === CANVAS_ANNOTATION_KIND_DECISION;
|
|
82168
82173
|
}
|
|
82169
|
-
var CANVAS_ANNOTATION_KIND_ELEMENT, CANVAS_ANNOTATION_KIND_DECISION, CANVAS_ANNOTATION_KIND_VALUES, CANVAS_ANNOTATION_STATUS_VALUES, CANVAS_ANNOTATION_ANCHOR_STATE_VALUES, CANVAS_ANNOTATION_ANCHOR_SOURCE_VALUES, CANVAS_ANNOTATION_HEADLINE_MAX_LENGTH, uuidSchema, optionalCanvasTextSchema, canvasBboxCoordinateSchema, optionalIsoDateSchema, createCanvasAnnotationInputSchema, CANVAS_ANNOTATION_CHANGE_EVENT_TYPES, CANVAS_PAGE_CHANGE_EVENT_TYPES, CANVAS_THREAD_CHANGE_EVENT_TYPES, CANVAS_CHANGE_EVENT_TYPES;
|
|
82174
|
+
var CANVAS_ANNOTATION_KIND_ELEMENT, CANVAS_ANNOTATION_KIND_DECISION, CANVAS_ANNOTATION_KIND_VALUES, CANVAS_ANNOTATION_STATUS_VALUES, CANVAS_ANNOTATION_ANCHOR_STATE_VALUES, CANVAS_ANNOTATION_ANCHOR_SOURCE_VALUES, CANVAS_ANNOTATION_HEADLINE_MAX_LENGTH, uuidSchema, optionalCanvasTextSchema, canvasBboxCoordinateSchema, optionalIsoDateSchema, createCanvasAnnotationInputSchema, CANVAS_ANNOTATION_CHANGE_EVENT_TYPES, CANVAS_PAGE_CHANGE_EVENT_TYPES, CANVAS_REPLY_CHANGE_EVENT_TYPES, CANVAS_THREAD_CHANGE_EVENT_TYPES, CANVAS_CHANGE_EVENT_TYPES, LACE_MCP_INSTRUCTIONS;
|
|
82170
82175
|
var init_types4 = __esm({
|
|
82171
82176
|
"../../libs/shared/src/decisionCanvas/types.ts"() {
|
|
82172
82177
|
"use strict";
|
|
@@ -82229,14 +82234,29 @@ var init_types4 = __esm({
|
|
|
82229
82234
|
"annotation-reopened"
|
|
82230
82235
|
];
|
|
82231
82236
|
CANVAS_PAGE_CHANGE_EVENT_TYPES = ["page-added", "page-removed", "page-reordered", "page-updated"];
|
|
82237
|
+
CANVAS_REPLY_CHANGE_EVENT_TYPES = ["reply-created", "reply-updated", "reply-deleted"];
|
|
82232
82238
|
CANVAS_THREAD_CHANGE_EVENT_TYPES = ["thread-linked", "thread-unlinked"];
|
|
82233
82239
|
CANVAS_CHANGE_EVENT_TYPES = [
|
|
82234
82240
|
...CANVAS_ANNOTATION_CHANGE_EVENT_TYPES,
|
|
82235
82241
|
...CANVAS_PAGE_CHANGE_EVENT_TYPES,
|
|
82242
|
+
...CANVAS_REPLY_CHANGE_EVENT_TYPES,
|
|
82236
82243
|
"canvas-created",
|
|
82237
82244
|
"canvas-updated",
|
|
82238
82245
|
...CANVAS_THREAD_CHANGE_EVENT_TYPES
|
|
82239
82246
|
];
|
|
82247
|
+
LACE_MCP_INSTRUCTIONS = `Lace surfaces UI decisions from product reviews. Decisions drive code changes, slideshows, graphics, and prototypes.
|
|
82248
|
+
|
|
82249
|
+
Discovery:
|
|
82250
|
+
- search_decisions: find decisions by text, canvas, project, app, time, or element. No params = top 20 ranked.
|
|
82251
|
+
- list_canvases: browse review canvases to find canvas IDs for filtering.
|
|
82252
|
+
- list_projects: browse projects to find project IDs for scoping.
|
|
82253
|
+
|
|
82254
|
+
Action:
|
|
82255
|
+
- apply_decision: get the implementation kit for a decision. Set target to control output format (code, slideshow, graphic, prototype). Set include to control detail level (implementation, image, context, all).
|
|
82256
|
+
|
|
82257
|
+
Workflow: search_decisions -> pick a decision -> apply_decision with the right target.
|
|
82258
|
+
|
|
82259
|
+
All tools scope to canvases you have access to. Some canvases may be view-only (title and status visible, no decisions). Decision IDs from search_decisions are valid for apply_decision.`;
|
|
82240
82260
|
}
|
|
82241
82261
|
});
|
|
82242
82262
|
|
|
@@ -82267,9 +82287,6 @@ function currentCommentPlacements(placements) {
|
|
|
82267
82287
|
function resolvedDecisionPlacements(placements) {
|
|
82268
82288
|
return currentCommentPlacements(placements).filter(isResolvedCommentPlacement);
|
|
82269
82289
|
}
|
|
82270
|
-
function resolvedDecisionViews(placements) {
|
|
82271
|
-
return resolvedDecisionPlacements(placements);
|
|
82272
|
-
}
|
|
82273
82290
|
var init_readModel = __esm({
|
|
82274
82291
|
"../../libs/shared/src/decisionCanvas/readModel.ts"() {
|
|
82275
82292
|
"use strict";
|
|
@@ -82292,60 +82309,6 @@ function optionalAttr(name, value) {
|
|
|
82292
82309
|
function deriveDecisionStatus(decision) {
|
|
82293
82310
|
return decision.status;
|
|
82294
82311
|
}
|
|
82295
|
-
function canvasAnnotationToMcpDecisionView(decision) {
|
|
82296
|
-
return {
|
|
82297
|
-
id: decision.id,
|
|
82298
|
-
commentLineageId: decision.comment_lineage_id,
|
|
82299
|
-
source: "canvas",
|
|
82300
|
-
headline: decision.headline,
|
|
82301
|
-
body: decision.body,
|
|
82302
|
-
elementEid: decision.element_eid,
|
|
82303
|
-
shotId: decision.page.shot_id,
|
|
82304
|
-
bbox: [decision.bbox_x1, decision.bbox_y1, decision.bbox_x2, decision.bbox_y2],
|
|
82305
|
-
status: deriveDecisionStatus(decision),
|
|
82306
|
-
anchorState: decision.anchor_state,
|
|
82307
|
-
appName: decision.page.app_name,
|
|
82308
|
-
windowTitle: decision.page.window_title,
|
|
82309
|
-
createdAt: decision.created_at,
|
|
82310
|
-
resolvedAt: decision.resolved_at
|
|
82311
|
-
};
|
|
82312
|
-
}
|
|
82313
|
-
function dedupeKey(view) {
|
|
82314
|
-
if (view.commentLineageId) return `lineage:${view.commentLineageId}`;
|
|
82315
|
-
if (view.elementEid && view.shotId) return `${view.elementEid}:${view.shotId}`;
|
|
82316
|
-
return `canvas:${view.id}`;
|
|
82317
|
-
}
|
|
82318
|
-
function decisionPlacementTime(view) {
|
|
82319
|
-
const createdAt = Date.parse(view.createdAt);
|
|
82320
|
-
if (Number.isFinite(createdAt)) return createdAt;
|
|
82321
|
-
const resolvedAt = view.resolvedAt ? Date.parse(view.resolvedAt) : Number.NaN;
|
|
82322
|
-
if (Number.isFinite(resolvedAt)) return resolvedAt;
|
|
82323
|
-
return 0;
|
|
82324
|
-
}
|
|
82325
|
-
function scoreDecision(view, maxAgeMs, now) {
|
|
82326
|
-
const resolvedAt = view.resolvedAt ? Date.parse(view.resolvedAt) : Number.NaN;
|
|
82327
|
-
const ageMs = Number.isFinite(resolvedAt) ? Math.max(0, now.getTime() - resolvedAt) : maxAgeMs;
|
|
82328
|
-
const recency = maxAgeMs <= 0 ? 1 : 1 - Math.min(ageMs, maxAgeMs) / maxAgeMs;
|
|
82329
|
-
const elementBoost = view.elementEid != null ? 0.3 : 0;
|
|
82330
|
-
const bodyBoost = view.body != null && view.body.length > 20 ? 1 : 0;
|
|
82331
|
-
return 0.5 * recency + 0.3 * elementBoost + 0.2 * bodyBoost;
|
|
82332
|
-
}
|
|
82333
|
-
function rankMcpDecisionViews(views, now = /* @__PURE__ */ new Date()) {
|
|
82334
|
-
const ages = views.map((view) => view.resolvedAt ? now.getTime() - Date.parse(view.resolvedAt) : 0).filter((age) => Number.isFinite(age) && age >= 0);
|
|
82335
|
-
const maxAgeMs = Math.max(0, ...ages);
|
|
82336
|
-
return views.slice().sort((a, b) => scoreDecision(b, maxAgeMs, now) - scoreDecision(a, maxAgeMs, now));
|
|
82337
|
-
}
|
|
82338
|
-
function dedupeMcpDecisionEntries(entries) {
|
|
82339
|
-
const seen = /* @__PURE__ */ new Map();
|
|
82340
|
-
for (const entry of entries) {
|
|
82341
|
-
const key = dedupeKey(entry.view);
|
|
82342
|
-
const existing = seen.get(key);
|
|
82343
|
-
if (!existing || decisionPlacementTime(entry.view) > decisionPlacementTime(existing.view)) {
|
|
82344
|
-
seen.set(key, entry);
|
|
82345
|
-
}
|
|
82346
|
-
}
|
|
82347
|
-
return Array.from(seen.values());
|
|
82348
|
-
}
|
|
82349
82312
|
function deriveFlowContext(decision, allDecisions) {
|
|
82350
82313
|
const siblings = resolvedDecisionPlacements(allDecisions).filter(
|
|
82351
82314
|
(candidate) => candidate.canvas_id === decision.canvas_id
|
|
@@ -82381,8 +82344,8 @@ function canvasAnnotationToDecisionXmlInput(decision, allDecisions) {
|
|
|
82381
82344
|
elementEid: decision.element_eid,
|
|
82382
82345
|
createdAt: decision.created_at,
|
|
82383
82346
|
resolvedAt: decision.resolved_at,
|
|
82384
|
-
resolvedBy: null,
|
|
82385
|
-
bbox: decision.anchor_state === "detached" ? null : [decision.bbox_x1, decision.bbox_y1, decision.bbox_x2, decision.bbox_y2],
|
|
82347
|
+
resolvedBy: decision.approved_by ?? null,
|
|
82348
|
+
bbox: decision.anchor_state === "detached" || isFullPageBbox(decision) ? null : [decision.bbox_x1, decision.bbox_y1, decision.bbox_x2, decision.bbox_y2],
|
|
82386
82349
|
element: {
|
|
82387
82350
|
role: decision.element_role,
|
|
82388
82351
|
label: decision.element_visible_text,
|
|
@@ -82426,10 +82389,21 @@ function serializeDecisionXml(decision) {
|
|
|
82426
82389
|
"</decision>"
|
|
82427
82390
|
].filter(Boolean).join("\n");
|
|
82428
82391
|
}
|
|
82429
|
-
function formatApplyDecisionText(decision, allDecisions, vocabulary) {
|
|
82392
|
+
function formatApplyDecisionText(decision, allDecisions, vocabulary, threadContext) {
|
|
82430
82393
|
const xml = serializeDecisionXml(canvasAnnotationToDecisionXmlInput(decision, allDecisions));
|
|
82431
82394
|
const parts = [xml];
|
|
82432
82395
|
if (vocabulary) parts.push(vocabulary);
|
|
82396
|
+
if (threadContext && threadContext.length > 0) {
|
|
82397
|
+
const messages = threadContext.map((m) => {
|
|
82398
|
+
if ("role" in m) {
|
|
82399
|
+
return ` <message role="${m.role}" at="${m.createdAt}">${esc3(m.visibleText ?? "")}</message>`;
|
|
82400
|
+
}
|
|
82401
|
+
return ` <message>${esc3(m.text)}</message>`;
|
|
82402
|
+
});
|
|
82403
|
+
parts.push(`<thread>
|
|
82404
|
+
${messages.join("\n")}
|
|
82405
|
+
</thread>`);
|
|
82406
|
+
}
|
|
82433
82407
|
parts.push(
|
|
82434
82408
|
"Apply the resolved Decision Canvas change. Use the element context, bbox, and screen metadata above as implementation grounding."
|
|
82435
82409
|
);
|
|
@@ -82449,6 +82423,7 @@ var init_mcpFormat = __esm({
|
|
|
82449
82423
|
"../../libs/shared/src/decisionCanvas/mcpFormat.ts"() {
|
|
82450
82424
|
"use strict";
|
|
82451
82425
|
init_readModel();
|
|
82426
|
+
init_types4();
|
|
82452
82427
|
MAX_CANVAS_VOCABULARY_ELEMENTS = 100;
|
|
82453
82428
|
MCP_FETCH_TIMEOUT_MS = 1e4;
|
|
82454
82429
|
}
|
|
@@ -82491,12 +82466,6 @@ var init_mcpDecisionLoader = __esm({
|
|
|
82491
82466
|
});
|
|
82492
82467
|
|
|
82493
82468
|
// ../../libs/shared/src/mcpToolRegistration.ts
|
|
82494
|
-
function notFoundError(id) {
|
|
82495
|
-
return {
|
|
82496
|
-
content: [{ type: "text", text: `Decision '${id}' not found. Use list_decisions to see available decisions.` }],
|
|
82497
|
-
isError: true
|
|
82498
|
-
};
|
|
82499
|
-
}
|
|
82500
82469
|
function loaderErrorResponse(error51, fallback) {
|
|
82501
82470
|
if (error51 instanceof DecisionLoaderError) {
|
|
82502
82471
|
return { content: [{ type: "text", text: error51.contentText }], isError: true };
|
|
@@ -82504,10 +82473,11 @@ function loaderErrorResponse(error51, fallback) {
|
|
|
82504
82473
|
return { content: [{ type: "text", text: fallback }], isError: true };
|
|
82505
82474
|
}
|
|
82506
82475
|
function loaderScope(scope) {
|
|
82507
|
-
return { orgId: scope.orgId };
|
|
82476
|
+
return { orgId: scope.orgId, userId: scope.userId };
|
|
82508
82477
|
}
|
|
82509
82478
|
function isValidBbox(decision) {
|
|
82510
|
-
return [decision.bbox_x1, decision.bbox_y1, decision.bbox_x2, decision.bbox_y2].every(Number.isFinite) && decision.bbox_x1 < decision.bbox_x2 && decision.bbox_y1 < decision.bbox_y2;
|
|
82479
|
+
return [decision.bbox_x1, decision.bbox_y1, decision.bbox_x2, decision.bbox_y2].every(Number.isFinite) && decision.bbox_x1 < decision.bbox_x2 && decision.bbox_y1 < decision.bbox_y2 && // Page-scoped comments are stored as a full-page box; they have no region to crop.
|
|
82480
|
+
!isFullPageBbox(decision);
|
|
82511
82481
|
}
|
|
82512
82482
|
function placementTimestamp(decision) {
|
|
82513
82483
|
const createdAt = Date.parse(decision.created_at);
|
|
@@ -82534,122 +82504,194 @@ function decisionBbox(decision) {
|
|
|
82534
82504
|
if (!isValidBbox(decision)) return null;
|
|
82535
82505
|
return [decision.bbox_x1, decision.bbox_y1, decision.bbox_x2, decision.bbox_y2];
|
|
82536
82506
|
}
|
|
82537
|
-
function
|
|
82538
|
-
|
|
82539
|
-
|
|
82540
|
-
|
|
82541
|
-
|
|
82542
|
-
${message.visibleText}`;
|
|
82543
|
-
}).filter((line) => Boolean(line));
|
|
82544
|
-
return lines.length > 0 ? lines.join("\n\n") : null;
|
|
82507
|
+
function decisionNotFoundResult(id) {
|
|
82508
|
+
return {
|
|
82509
|
+
content: [{ type: "text", text: `Decision "${id}" not found. Use search_decisions to find valid IDs.` }],
|
|
82510
|
+
isError: true
|
|
82511
|
+
};
|
|
82545
82512
|
}
|
|
82546
|
-
async function
|
|
82547
|
-
const detail = await loader.loadDecisionDetail(id, scope);
|
|
82548
|
-
if (!detail)
|
|
82549
|
-
|
|
82550
|
-
|
|
82513
|
+
async function appendImplementationContent(context2, include) {
|
|
82514
|
+
const detail = await context2.loader.loadDecisionDetail(context2.id, context2.scope);
|
|
82515
|
+
if (!detail) return null;
|
|
82516
|
+
context2.content.push(...detail.images);
|
|
82517
|
+
const vocabulary = await context2.loader.loadElementVocabulary(detail.decision.canvas_id, context2.scope);
|
|
82518
|
+
const vocabStr = vocabulary.length > 0 ? buildCanvasVocabulary(vocabulary) : null;
|
|
82519
|
+
const threadContext = include === "all" ? await context2.loader.loadThreadContext(context2.id, context2.scope) : void 0;
|
|
82520
|
+
context2.content.push({
|
|
82521
|
+
type: "text",
|
|
82522
|
+
text: formatApplyDecisionText(detail.decision, detail.allDecisions, vocabStr, threadContext)
|
|
82523
|
+
});
|
|
82551
82524
|
return detail;
|
|
82552
82525
|
}
|
|
82553
|
-
async function
|
|
82526
|
+
async function appendImageContent(context2, existingDetail) {
|
|
82527
|
+
const detail = existingDetail ?? await context2.loader.loadDecisionDetail(context2.id, context2.scope);
|
|
82528
|
+
if (!detail) return null;
|
|
82554
82529
|
const cropSource = resolveCropSource(detail);
|
|
82555
|
-
|
|
82556
|
-
|
|
82557
|
-
|
|
82558
|
-
|
|
82559
|
-
|
|
82560
|
-
}
|
|
82561
|
-
const bbox = decisionBbox(cropSource);
|
|
82562
|
-
const image = bbox ? await loader.cropElement(cropSource.page.artifact_id, bbox, scope) : null;
|
|
82563
|
-
return image ? { type: "image", data: image, mimeType: "image/png" } : {
|
|
82564
|
-
type: "text",
|
|
82565
|
-
text: "No element image available - decision has no valid bounding box or screenshot."
|
|
82566
|
-
};
|
|
82530
|
+
const bbox = cropSource ? decisionBbox(cropSource) : null;
|
|
82531
|
+
if (!cropSource || !bbox) return detail;
|
|
82532
|
+
const cropped = await context2.loader.cropElement(cropSource.page.artifact_id, bbox, context2.scope);
|
|
82533
|
+
if (cropped) context2.content.push({ type: "image", data: cropped, mimeType: "image/png" });
|
|
82534
|
+
return detail;
|
|
82567
82535
|
}
|
|
82568
|
-
async function
|
|
82569
|
-
const
|
|
82570
|
-
|
|
82536
|
+
async function appendContextContent(content, loader, id, scope) {
|
|
82537
|
+
const messages = await loader.loadThreadContext(id, scope);
|
|
82538
|
+
if (messages.length === 0) {
|
|
82539
|
+
content.push({ type: "text", text: "No thread context available for this decision." });
|
|
82540
|
+
return;
|
|
82541
|
+
}
|
|
82542
|
+
const formatted = messages.map((message) => "role" in message ? `[${message.role}] ${message.visibleText ?? ""}` : message.text).join("\n");
|
|
82543
|
+
content.push({ type: "text", text: formatted });
|
|
82571
82544
|
}
|
|
82572
|
-
async function
|
|
82573
|
-
const detail = await loadDetail(loader, id, scope);
|
|
82545
|
+
async function handleApplyDecision(loader, id, include, scope) {
|
|
82574
82546
|
const content = [];
|
|
82547
|
+
const context2 = { content, loader, id, scope };
|
|
82548
|
+
let detail = null;
|
|
82549
|
+
if (include === "implementation" || include === "all") {
|
|
82550
|
+
detail = await appendImplementationContent(context2, include);
|
|
82551
|
+
if (!detail) return decisionNotFoundResult(id);
|
|
82552
|
+
}
|
|
82575
82553
|
if (include === "image" || include === "all") {
|
|
82576
|
-
|
|
82554
|
+
detail = await appendImageContent(context2, detail);
|
|
82577
82555
|
}
|
|
82578
|
-
if (include === "context"
|
|
82579
|
-
|
|
82556
|
+
if (include === "context" && content.length === 0) {
|
|
82557
|
+
await appendContextContent(content, loader, id, scope);
|
|
82580
82558
|
}
|
|
82581
|
-
return content;
|
|
82559
|
+
return content.length > 0 ? { content } : decisionNotFoundResult(id);
|
|
82582
82560
|
}
|
|
82583
|
-
function
|
|
82561
|
+
function registerSearchDecisionsTool(server2, loader, getScope, onToolInvoked) {
|
|
82584
82562
|
server2.registerTool(
|
|
82585
|
-
"
|
|
82563
|
+
"search_decisions",
|
|
82586
82564
|
{
|
|
82587
|
-
title: "
|
|
82588
|
-
description: "
|
|
82589
|
-
annotations: { readOnlyHint: true, destructiveHint: false }
|
|
82565
|
+
title: "Search Decisions",
|
|
82566
|
+
description: "Search and filter resolved decisions with cursor pagination. With no parameters, returns the top 20 ranked by recency and detail. For full detail on a specific decision, use apply_decision.",
|
|
82567
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
82568
|
+
inputSchema: {
|
|
82569
|
+
query: external_exports.string().optional().describe("Text search across headline, body, and element text"),
|
|
82570
|
+
status: external_exports.enum(["resolved", "suggested", "all"]).default("resolved").optional().describe("Filter by annotation status (default: resolved)"),
|
|
82571
|
+
limit: external_exports.number().int().min(1).max(100).default(20).optional().describe("Results per page (1-100, default 20)"),
|
|
82572
|
+
cursor: external_exports.string().optional().describe("Opaque pagination cursor from a previous response"),
|
|
82573
|
+
sort: external_exports.enum(["relevance", "resolved_at_desc", "created_at_desc"]).default("resolved_at_desc").optional().describe("Sort order"),
|
|
82574
|
+
canvasId: external_exports.string().uuid().optional().describe("Exact canvas ID"),
|
|
82575
|
+
canvasTitle: external_exports.string().optional().describe("Canvas title substring match"),
|
|
82576
|
+
canvasStatus: external_exports.enum(["active", "archived"]).optional().describe("Canvas lifecycle status"),
|
|
82577
|
+
pageId: external_exports.string().uuid().optional().describe("Exact page ID"),
|
|
82578
|
+
pageName: external_exports.string().optional().describe("Page name substring match"),
|
|
82579
|
+
projectId: external_exports.string().uuid().optional().describe("Exact project ID"),
|
|
82580
|
+
projectName: external_exports.string().optional().describe("Project name substring match"),
|
|
82581
|
+
threadId: external_exports.string().uuid().optional(),
|
|
82582
|
+
appName: external_exports.string().optional().describe("App name substring match"),
|
|
82583
|
+
windowTitle: external_exports.string().optional().describe("Window title substring match"),
|
|
82584
|
+
url: external_exports.string().optional().describe("URL substring match"),
|
|
82585
|
+
anchorState: external_exports.enum(["anchored", "reanchored", "detached"]).optional(),
|
|
82586
|
+
elementEid: external_exports.string().optional().describe("Exact element accessibility ID"),
|
|
82587
|
+
elementRole: external_exports.string().optional().describe("Exact element ARIA role"),
|
|
82588
|
+
elementInteractivity: external_exports.boolean().optional(),
|
|
82589
|
+
minEndorsements: external_exports.number().int().min(0).optional().describe("Minimum endorsement count"),
|
|
82590
|
+
hasReplies: external_exports.boolean().optional().describe("Filter to decisions with replies"),
|
|
82591
|
+
createdBy: external_exports.string().uuid().optional().describe("Creator user ID"),
|
|
82592
|
+
resolvedFrom: external_exports.string().optional().describe("ISO date lower bound for resolved_at"),
|
|
82593
|
+
resolvedTo: external_exports.string().optional().describe("ISO date upper bound for resolved_at"),
|
|
82594
|
+
createdFrom: external_exports.string().optional().describe("ISO date lower bound for created_at"),
|
|
82595
|
+
createdTo: external_exports.string().optional().describe("ISO date upper bound for created_at")
|
|
82596
|
+
}
|
|
82590
82597
|
},
|
|
82591
|
-
async () => {
|
|
82592
|
-
onToolInvoked?.("
|
|
82598
|
+
async (params) => {
|
|
82599
|
+
onToolInvoked?.("search_decisions");
|
|
82593
82600
|
try {
|
|
82594
|
-
const
|
|
82595
|
-
const
|
|
82596
|
-
|
|
82597
|
-
|
|
82598
|
-
|
|
82599
|
-
|
|
82600
|
-
|
|
82601
|
-
|
|
82601
|
+
const result = await loader.searchDecisions(params, loaderScope(getScope()));
|
|
82602
|
+
const text = result.nextCursor ? `Showing ${result.decisions.length} of ${result.totalCount} results. Pass cursor "${result.nextCursor}" to see more.` : `Showing ${result.decisions.length} of ${result.totalCount} results.`;
|
|
82603
|
+
return {
|
|
82604
|
+
content: [
|
|
82605
|
+
{ type: "text", text },
|
|
82606
|
+
{ type: "text", text: JSON.stringify(result) }
|
|
82607
|
+
]
|
|
82608
|
+
};
|
|
82609
|
+
} catch (e) {
|
|
82610
|
+
return loaderErrorResponse(e, "Failed to search decisions. Please try again.");
|
|
82602
82611
|
}
|
|
82603
82612
|
}
|
|
82604
82613
|
);
|
|
82614
|
+
}
|
|
82615
|
+
function registerApplyDecisionTool(server2, loader, getScope, onToolInvoked) {
|
|
82605
82616
|
server2.registerTool(
|
|
82606
82617
|
"apply_decision",
|
|
82607
82618
|
{
|
|
82608
82619
|
title: "Apply Decision",
|
|
82609
|
-
description: "
|
|
82610
|
-
inputSchema: {
|
|
82611
|
-
|
|
82620
|
+
description: "Act on a resolved decision. Returns implementation kit with screenshot, element targeting, and step-by-step instructions. Set target to control output format: code changes, slideshow content, graphic specs, or prototype data. Set include to control detail level.",
|
|
82621
|
+
inputSchema: {
|
|
82622
|
+
id: external_exports.string().describe("Decision ID from search_decisions"),
|
|
82623
|
+
include: external_exports.enum(["implementation", "image", "context", "all"]).default("implementation").optional().describe(
|
|
82624
|
+
"What to return: implementation (screenshot + instructions), image (cropped element), context (thread discussion), all (everything)"
|
|
82625
|
+
),
|
|
82626
|
+
target: external_exports.enum(["code", "slideshow", "graphic", "prototype"]).default("code").optional().describe("Output format for instructions")
|
|
82627
|
+
},
|
|
82628
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
82612
82629
|
},
|
|
82613
|
-
async ({ id }) => {
|
|
82630
|
+
async ({ id, include = "implementation", target = "code" }) => {
|
|
82614
82631
|
onToolInvoked?.("apply_decision");
|
|
82615
82632
|
try {
|
|
82616
|
-
|
|
82617
|
-
|
|
82618
|
-
|
|
82619
|
-
|
|
82620
|
-
return {
|
|
82621
|
-
content: [
|
|
82622
|
-
...detail.images,
|
|
82623
|
-
{ type: "text", text: formatApplyDecisionText(detail.decision, detail.allDecisions, vocabulary) }
|
|
82624
|
-
]
|
|
82625
|
-
};
|
|
82626
|
-
} catch (error51) {
|
|
82627
|
-
return loaderErrorResponse(error51, "Failed to apply decision. Please try again.");
|
|
82633
|
+
void target;
|
|
82634
|
+
return await handleApplyDecision(loader, id, include, loaderScope(getScope()));
|
|
82635
|
+
} catch (e) {
|
|
82636
|
+
return loaderErrorResponse(e, "Failed to apply decision. Please try again.");
|
|
82628
82637
|
}
|
|
82629
82638
|
}
|
|
82630
82639
|
);
|
|
82640
|
+
}
|
|
82641
|
+
function registerListCanvasesTool(server2, loader, getScope, onToolInvoked) {
|
|
82631
82642
|
server2.registerTool(
|
|
82632
|
-
"
|
|
82643
|
+
"list_canvases",
|
|
82633
82644
|
{
|
|
82634
|
-
title: "
|
|
82635
|
-
description: "
|
|
82645
|
+
title: "List Canvases",
|
|
82646
|
+
description: "List review canvases in the current org. Returns title, project, page names, and decision counts per page. Call this when the user mentions a specific design file, screen, or review session by name \u2014 use the returned canvas IDs and titles to scope search_decisions queries.",
|
|
82636
82647
|
inputSchema: {
|
|
82637
|
-
|
|
82638
|
-
|
|
82648
|
+
status: external_exports.enum(["active", "archived"]).default("active").optional(),
|
|
82649
|
+
projectId: external_exports.string().uuid().optional().describe("Filter by project ID"),
|
|
82650
|
+
limit: external_exports.number().int().min(1).max(100).default(50).optional(),
|
|
82651
|
+
cursor: external_exports.string().optional()
|
|
82639
82652
|
},
|
|
82640
|
-
annotations: { readOnlyHint: true, destructiveHint: false }
|
|
82653
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
82641
82654
|
},
|
|
82642
|
-
async (
|
|
82643
|
-
onToolInvoked?.("
|
|
82655
|
+
async (params) => {
|
|
82656
|
+
onToolInvoked?.("list_canvases");
|
|
82644
82657
|
try {
|
|
82645
|
-
const
|
|
82646
|
-
return { content:
|
|
82647
|
-
} catch (
|
|
82648
|
-
return loaderErrorResponse(
|
|
82658
|
+
const result = await loader.listCanvases(params, loaderScope(getScope()));
|
|
82659
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
82660
|
+
} catch (e) {
|
|
82661
|
+
return loaderErrorResponse(e, "Failed to list canvases. Please try again.");
|
|
82662
|
+
}
|
|
82663
|
+
}
|
|
82664
|
+
);
|
|
82665
|
+
}
|
|
82666
|
+
function registerListProjectsTool(server2, loader, getScope, onToolInvoked) {
|
|
82667
|
+
server2.registerTool(
|
|
82668
|
+
"list_projects",
|
|
82669
|
+
{
|
|
82670
|
+
title: "List Projects",
|
|
82671
|
+
description: "List projects in the current org. Returns name, thread count, and canvas count. Call this when the user references a project by name \u2014 use the returned project IDs to pass as projectId or projectName filters to search_decisions.",
|
|
82672
|
+
inputSchema: {
|
|
82673
|
+
limit: external_exports.number().int().min(1).max(100).default(50).optional(),
|
|
82674
|
+
cursor: external_exports.string().optional()
|
|
82675
|
+
},
|
|
82676
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
82677
|
+
},
|
|
82678
|
+
async (params) => {
|
|
82679
|
+
onToolInvoked?.("list_projects");
|
|
82680
|
+
try {
|
|
82681
|
+
const result = await loader.listProjects(params, loaderScope(getScope()));
|
|
82682
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
82683
|
+
} catch (e) {
|
|
82684
|
+
return loaderErrorResponse(e, "Failed to list projects. Please try again.");
|
|
82649
82685
|
}
|
|
82650
82686
|
}
|
|
82651
82687
|
);
|
|
82652
82688
|
}
|
|
82689
|
+
function registerMcpTools(server2, loader, getScope, onToolInvoked) {
|
|
82690
|
+
registerSearchDecisionsTool(server2, loader, getScope, onToolInvoked);
|
|
82691
|
+
registerApplyDecisionTool(server2, loader, getScope, onToolInvoked);
|
|
82692
|
+
registerListCanvasesTool(server2, loader, getScope, onToolInvoked);
|
|
82693
|
+
registerListProjectsTool(server2, loader, getScope, onToolInvoked);
|
|
82694
|
+
}
|
|
82653
82695
|
var init_mcpToolRegistration = __esm({
|
|
82654
82696
|
"../../libs/shared/src/mcpToolRegistration.ts"() {
|
|
82655
82697
|
"use strict";
|
|
@@ -82667,9 +82709,12 @@ function readMcpCredentials() {
|
|
|
82667
82709
|
if (apiBase && token && orgId) return { apiBase, token, orgId };
|
|
82668
82710
|
return loadCachedCredentials();
|
|
82669
82711
|
}
|
|
82670
|
-
async function fetchCanvasDecisions(config4) {
|
|
82712
|
+
async function fetchCanvasDecisions(config4, options = {}) {
|
|
82671
82713
|
try {
|
|
82672
|
-
const
|
|
82714
|
+
const params = new URLSearchParams();
|
|
82715
|
+
if (options.canvasId) params.set("canvasId", options.canvasId);
|
|
82716
|
+
const query = params.toString();
|
|
82717
|
+
const response = await fetch(`${config4.apiBase}/lace/canvases/decisions${query ? `?${query}` : ""}`, {
|
|
82673
82718
|
headers: {
|
|
82674
82719
|
Accept: "application/json",
|
|
82675
82720
|
Authorization: `Bearer ${config4.token}`,
|
|
@@ -82687,44 +82732,25 @@ async function fetchCanvasDecisions(config4) {
|
|
|
82687
82732
|
return { ok: false, status: 0, error: "Network error or timeout" };
|
|
82688
82733
|
}
|
|
82689
82734
|
}
|
|
82690
|
-
function
|
|
82691
|
-
|
|
82692
|
-
|
|
82693
|
-
|
|
82694
|
-
|
|
82695
|
-
|
|
82696
|
-
return
|
|
82697
|
-
}
|
|
82698
|
-
async function loadDecisionEntriesOrThrow() {
|
|
82699
|
-
const result = await loadMcpDecisionEntries();
|
|
82700
|
-
const errorText = loadErrorText(result);
|
|
82701
|
-
if (errorText) throw new DecisionLoaderError(errorText);
|
|
82702
|
-
return { entries: result.entries, allDecisions: result.allDecisions };
|
|
82735
|
+
async function fetchJson(config4, path2) {
|
|
82736
|
+
const response = await fetch(`${config4.apiBase}${path2}`, {
|
|
82737
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${config4.token}`, "x-org-id": config4.orgId },
|
|
82738
|
+
signal: AbortSignal.timeout(API_FETCH_TIMEOUT_MS)
|
|
82739
|
+
});
|
|
82740
|
+
if (!response.ok) throw new DecisionLoaderError(`Request failed (${response.status}: ${response.statusText})`);
|
|
82741
|
+
return await response.json();
|
|
82703
82742
|
}
|
|
82704
|
-
|
|
82705
|
-
const
|
|
82706
|
-
|
|
82707
|
-
|
|
82743
|
+
function searchParamsFromRecord(query) {
|
|
82744
|
+
const params = new URLSearchParams();
|
|
82745
|
+
for (const [key, value] of Object.entries(query)) {
|
|
82746
|
+
if (value != null) params.set(key, String(value));
|
|
82708
82747
|
}
|
|
82709
|
-
|
|
82710
|
-
|
|
82711
|
-
|
|
82712
|
-
|
|
82713
|
-
|
|
82714
|
-
|
|
82715
|
-
credentialsMissing: false
|
|
82716
|
-
};
|
|
82717
|
-
}
|
|
82718
|
-
const decisions = resolvedDecisionViews(result.decisions);
|
|
82719
|
-
const canvasEntries = decisions.map((decision) => ({
|
|
82720
|
-
view: canvasAnnotationToMcpDecisionView(decision),
|
|
82721
|
-
canvas: decision
|
|
82722
|
-
}));
|
|
82723
|
-
const allDecisions = canvasEntries.map((entry) => entry.canvas);
|
|
82724
|
-
const deduped = dedupeMcpDecisionEntries(canvasEntries);
|
|
82725
|
-
const ranked = rankMcpDecisionViews(deduped.map((entry) => entry.view)).slice(0, 8);
|
|
82726
|
-
const entries = ranked.map((view) => deduped.find((entry) => entry.view.id === view.id)).filter((entry) => Boolean(entry));
|
|
82727
|
-
return { entries, allDecisions, error: null, credentialsMissing: false };
|
|
82748
|
+
return params;
|
|
82749
|
+
}
|
|
82750
|
+
async function fetchDecisionSearch(config4, query) {
|
|
82751
|
+
const params = searchParamsFromRecord(query);
|
|
82752
|
+
const qs = params.toString();
|
|
82753
|
+
return fetchJson(config4, `/lace/canvases/decisions/search${qs ? `?${qs}` : ""}`);
|
|
82728
82754
|
}
|
|
82729
82755
|
async function fetchCanvasElementAnnotations(canvasId, config4) {
|
|
82730
82756
|
try {
|
|
@@ -82831,38 +82857,59 @@ function requireConfig() {
|
|
|
82831
82857
|
}
|
|
82832
82858
|
return config4;
|
|
82833
82859
|
}
|
|
82834
|
-
function
|
|
82835
|
-
|
|
82860
|
+
async function loadDecisionDetailFromHttp(id, config4) {
|
|
82861
|
+
const searchResult = await fetchDecisionSearch(config4, { id, limit: 1 });
|
|
82862
|
+
const view = searchResult.decisions[0];
|
|
82863
|
+
if (!view) return null;
|
|
82864
|
+
const rawResult = await fetchCanvasDecisions(config4, { canvasId: view.canvasId });
|
|
82865
|
+
if (!rawResult.ok) {
|
|
82866
|
+
throw new DecisionLoaderError(`Decision detail failed (${rawResult.status}: ${rawResult.error})`);
|
|
82867
|
+
}
|
|
82868
|
+
const decision = rawResult.decisions.find((candidate) => candidate.id === id) ?? null;
|
|
82869
|
+
if (!decision) return null;
|
|
82870
|
+
return {
|
|
82871
|
+
decision,
|
|
82872
|
+
allDecisions: rawResult.decisions,
|
|
82873
|
+
images: await loadCanvasImages(decision)
|
|
82874
|
+
};
|
|
82836
82875
|
}
|
|
82837
82876
|
function createHttpDecisionLoader() {
|
|
82838
82877
|
return {
|
|
82839
|
-
async
|
|
82840
|
-
|
|
82841
|
-
return result.entries.map((entry) => entry.view);
|
|
82878
|
+
async searchDecisions(query, _scope) {
|
|
82879
|
+
return fetchDecisionSearch(requireConfig(), query);
|
|
82842
82880
|
},
|
|
82843
82881
|
async loadDecisionDetail(id) {
|
|
82844
|
-
|
|
82845
|
-
const entry = result.entries.find((candidate) => candidate.view.id === id);
|
|
82846
|
-
if (!entry) return null;
|
|
82847
|
-
return {
|
|
82848
|
-
decision: entry.canvas,
|
|
82849
|
-
allDecisions: result.allDecisions,
|
|
82850
|
-
images: await loadCanvasImages(entry.canvas)
|
|
82851
|
-
};
|
|
82882
|
+
return loadDecisionDetailFromHttp(id, requireConfig());
|
|
82852
82883
|
},
|
|
82853
82884
|
async cropElement(artifactId, bbox) {
|
|
82854
82885
|
const config4 = requireConfig();
|
|
82855
82886
|
return await fetchCrop(config4, artifactId, bbox) ?? "";
|
|
82856
82887
|
},
|
|
82857
82888
|
async loadThreadContext(decisionId) {
|
|
82858
|
-
const
|
|
82859
|
-
const decision =
|
|
82889
|
+
const detail = await loadDecisionDetailFromHttp(decisionId, requireConfig());
|
|
82890
|
+
const decision = detail?.decision;
|
|
82860
82891
|
if (!decision) return [];
|
|
82861
82892
|
const context2 = await fetchDecisionThreadContext(decision);
|
|
82862
82893
|
return context2 ? [{ text: context2 }] : [];
|
|
82863
82894
|
},
|
|
82864
82895
|
async loadElementVocabulary(canvasId, _scope) {
|
|
82865
82896
|
return fetchCanvasElementAnnotations(canvasId, requireConfig());
|
|
82897
|
+
},
|
|
82898
|
+
async listCanvases(query, _scope) {
|
|
82899
|
+
const config4 = requireConfig();
|
|
82900
|
+
const params = new URLSearchParams();
|
|
82901
|
+
if (query.status) params.set("status", query.status);
|
|
82902
|
+
if (query.projectId) params.set("projectId", query.projectId);
|
|
82903
|
+
params.set("limit", String(query.limit ?? 50));
|
|
82904
|
+
if (query.cursor) params.set("cursor", query.cursor);
|
|
82905
|
+
return fetchJson(config4, `/lace/canvases?${params}`);
|
|
82906
|
+
},
|
|
82907
|
+
async listProjects(query, _scope) {
|
|
82908
|
+
const config4 = requireConfig();
|
|
82909
|
+
const params = new URLSearchParams();
|
|
82910
|
+
params.set("limit", String(query.limit ?? 50));
|
|
82911
|
+
if (query.cursor) params.set("cursor", query.cursor);
|
|
82912
|
+
return fetchJson(config4, `/projects?${params}`);
|
|
82866
82913
|
}
|
|
82867
82914
|
};
|
|
82868
82915
|
}
|
|
@@ -82882,28 +82929,18 @@ function getLocalScope() {
|
|
|
82882
82929
|
return { orgId: readMcpCredentials()?.orgId ?? "", userId: "local-mcp" };
|
|
82883
82930
|
}
|
|
82884
82931
|
function createLaceServer() {
|
|
82885
|
-
const server2 = new McpServer({ name: "lace", version: "0.0
|
|
82932
|
+
const server2 = new McpServer({ name: "lace", version: "2.0.0" }, { instructions: LACE_MCP_INSTRUCTIONS });
|
|
82886
82933
|
registerMcpTools(server2, createHttpDecisionLoader(), getLocalScope, trackToolInvoked);
|
|
82887
82934
|
return server2;
|
|
82888
82935
|
}
|
|
82889
|
-
var LACE_INSTRUCTIONS;
|
|
82890
82936
|
var init_server3 = __esm({
|
|
82891
82937
|
"src/server.ts"() {
|
|
82892
82938
|
"use strict";
|
|
82893
82939
|
init_mcp();
|
|
82940
|
+
init_types4();
|
|
82894
82941
|
init_mcpToolRegistration();
|
|
82895
82942
|
init_analytics();
|
|
82896
82943
|
init_decisionLoaderHttp();
|
|
82897
|
-
LACE_INSTRUCTIONS = `Lace is an adoption-diagnosis companion for desktop apps. It surfaces UI insights in an overlay and lets users approve decisions that should become code changes.
|
|
82898
|
-
|
|
82899
|
-
When the user asks about Lace decisions, approved changes, or wants to implement an approved decision:
|
|
82900
|
-
|
|
82901
|
-
1. Call list_decisions to see all approved decisions.
|
|
82902
|
-
2. Call apply_decision with the decision ID \u2014 this returns a screenshot, the decision card, element targeting, and implementation instructions. Follow those instructions to implement the change.
|
|
82903
|
-
3. If you need a cropped image of the targeted element (e.g., for a prototype), call get_decision_detail with include="image".
|
|
82904
|
-
4. If you need to understand why a decision was made, call get_decision_detail with include="context".
|
|
82905
|
-
|
|
82906
|
-
If the user says "implement the Lace decision", "apply the approved change", "what was approved", or references a Lace insight \u2014 start with list_decisions.`;
|
|
82907
82944
|
}
|
|
82908
82945
|
});
|
|
82909
82946
|
|