@reqvire-org/ads 0.3.17 → 0.3.20
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/atomic-composition.mjs +1 -1
- package/dist/behaviors.mjs +5 -5
- package/dist/bootstrap-ds/ADS.md +2 -2
- package/dist/bootstrap-ds/README.md +1 -1
- package/dist/bootstrap-ds/design-system/index.ts +0 -1
- package/dist/bootstrap-ds/design-system/review/index.ts +1 -0
- package/dist/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +1 -1
- package/dist/bootstrap-ds/ds.config.json +1 -1
- package/dist/bootstrap-ds/tsconfig.json +18 -3
- package/dist/bootstrap-ds/vite.config.ts +1 -1
- package/dist/boundaries.mjs +4 -2
- package/dist/check.mjs +49 -15
- package/dist/config.mjs +13 -2
- package/dist/init.mjs +17 -14
- package/dist/investigations.mjs +34 -3
- package/dist/layers.mjs +288 -0
- package/dist/linaria/preserve-values.cjs +21 -2
- package/dist/lint-config.mjs +1 -1
- package/dist/mcp/behavior-pages.mjs +36 -0
- package/dist/mcp/context.mjs +5 -2
- package/dist/mcp/edit-preparation.mjs +3 -3
- package/dist/mcp/execution-results.mjs +146 -0
- package/dist/mcp/mutations.mjs +4 -4
- package/dist/mcp/prompts.mjs +1 -1
- package/dist/mcp/result-pages.mjs +87 -0
- package/dist/mcp/server.mjs +49 -30
- package/dist/mcp/widget.bundle.js +1 -1
- package/dist/oxlint.mjs +3 -1
- package/dist/preview-resources.mjs +11 -1
- package/dist/property-registrations.mjs +39 -0
- package/dist/public-api.mjs +13 -6
- package/dist/scenario-trace.mjs +7 -7
- package/dist/scenarios-vite.mjs +2 -1
- package/dist/scenarios.mjs +10 -7
- package/dist/scoped-tokens.mjs +6 -0
- package/dist/showcase/client.bundle.js +1 -1
- package/dist/showcase/sandbox.bundle.js +1 -1
- package/dist/skills/ads-break-ui/SKILL.md +27 -10
- package/dist/skills/ads-break-ui/references/atomic-styles.md +48 -0
- package/dist/skills/ads-break-ui/references/captures.md +6 -1
- package/dist/skills/ads-break-ui/references/evidence.md +52 -5
- package/dist/skills/ads-break-ui/references/guarded-editing.md +9 -6
- package/dist/skills/ads-break-ui/references/layers.md +123 -0
- package/dist/skills/ads-break-ui/references/review.md +96 -0
- package/dist/skills/ads-break-ui/references/scenarios.md +24 -3
- package/dist/skills/ads-designer/SKILL.md +28 -11
- package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +2 -2
- package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/index.ts +0 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/review/index.ts +1 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/ds.config.json +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/tsconfig.json +18 -3
- package/dist/skills/ads-designer/assets/bootstrap-ds/vite.config.ts +1 -1
- package/dist/skills/ads-designer/references/atomic-styles.md +48 -0
- package/dist/skills/ads-designer/references/captures.md +6 -1
- package/dist/skills/ads-designer/references/evidence.md +52 -5
- package/dist/skills/ads-designer/references/guarded-editing.md +9 -6
- package/dist/skills/ads-designer/references/layers.md +123 -0
- package/dist/skills/ads-designer/references/review.md +96 -0
- package/dist/skills/ads-designer/references/scenarios.md +24 -3
- package/dist/skills/ads-import/SKILL.md +27 -10
- package/dist/skills/ads-import/references/atomic-styles.md +48 -0
- package/dist/skills/ads-import/references/captures.md +6 -1
- package/dist/skills/ads-import/references/evidence.md +52 -5
- package/dist/skills/ads-import/references/guarded-editing.md +9 -6
- package/dist/skills/ads-import/references/layers.md +123 -0
- package/dist/skills/ads-import/references/review.md +96 -0
- package/dist/skills/ads-import/references/scenarios.md +24 -3
- package/dist/skills/ads-migrate/SKILL.md +27 -10
- package/dist/skills/ads-migrate/references/atomic-styles.md +48 -0
- package/dist/skills/ads-migrate/references/captures.md +6 -1
- package/dist/skills/ads-migrate/references/evidence.md +52 -5
- package/dist/skills/ads-migrate/references/guarded-editing.md +9 -6
- package/dist/skills/ads-migrate/references/layers.md +123 -0
- package/dist/skills/ads-migrate/references/review.md +96 -0
- package/dist/skills/ads-migrate/references/scenarios.md +24 -3
- package/dist/skills/ads-motion/SKILL.md +28 -11
- package/dist/skills/ads-motion/references/atomic-styles.md +48 -0
- package/dist/skills/ads-motion/references/audit.md +1 -1
- package/dist/skills/ads-motion/references/captures.md +6 -1
- package/dist/skills/ads-motion/references/evidence.md +52 -5
- package/dist/skills/ads-motion/references/guarded-editing.md +9 -6
- package/dist/skills/ads-motion/references/layers.md +123 -0
- package/dist/skills/ads-motion/references/motion-review.md +35 -0
- package/dist/skills/ads-motion/references/review.md +96 -35
- package/dist/skills/ads-motion/references/scenarios.md +24 -3
- package/dist/units.mjs +17 -14
- package/docs/ADS.md +42 -5
- package/docs/ARCHITECTURE.md +64 -4
- package/docs/BEHAVIORS.md +21 -2
- package/docs/CHANGELOG.md +40 -0
- package/docs/GUIDE.md +3 -2
- package/docs/MCP.md +87 -20
- package/package.json +2 -1
- package/dist/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +0 -4
- package/dist/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +0 -8
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +0 -4
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +0 -8
- /package/dist/bootstrap-ds/design-system/{patterns → review}/token-preview/TokenPreview.tsx +0 -0
- /package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/{patterns → review}/token-preview/TokenPreview.tsx +0 -0
package/dist/mcp/mutations.mjs
CHANGED
|
@@ -178,7 +178,7 @@ export function createMutationController(directory, { captures = createCaptureCo
|
|
|
178
178
|
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > mutationLimits.readChars) throw fail("mutation-range", "Use a non-negative character offset and a limit between 1 and 12000.", file);
|
|
179
179
|
const options = loadConfig(root), source = read(options, file);
|
|
180
180
|
const inventory = inspectUnits(options), unit = inventory.owner(inventory.relative(file));
|
|
181
|
-
if (!unit && inventory.relative(file) !== inventory.publicEntry) throw fail("mutation-unit", "This file is outside the project's ADS and registered configuration/extension units.", file);
|
|
181
|
+
if (!unit && inventory.relative(file) !== inventory.publicEntry && inventory.relative(file) !== inventory.reviewEntry) throw fail("mutation-unit", "This file is outside the project's ADS and registered configuration/extension units.", file);
|
|
182
182
|
if (!source.exists) return { ...source, offset: 0, nextOffset: null, totalChars: 0, startLine: 1 };
|
|
183
183
|
if (source.binary) return { ...source, unit, content: null, note: "Binary asset: inspect its metadata or preview; edit using a Git binary patch, optionally through patchFile." };
|
|
184
184
|
if (offset > source.content.length) throw fail("mutation-range", "Read offset exceeds the current file length.", file);
|
|
@@ -279,7 +279,7 @@ export function createMutationController(directory, { captures = createCaptureCo
|
|
|
279
279
|
const expected = snapshot(candidateOptions);
|
|
280
280
|
candidateRevision=digest(Buffer.from(expected));
|
|
281
281
|
const result = checkProject(candidate, { trackingRoot: root });
|
|
282
|
-
if (!result.ok) return lastResult = mutationEvidence({ ok: false, applied: false, diagnostics: result.diagnostics
|
|
282
|
+
if (!result.ok) return lastResult = mutationEvidence({ ok: false, applied: false, diagnostics: result.diagnostics, total: result.diagnostics.length, checks: verified },candidateRevision);
|
|
283
283
|
verified.push({ name: "ads check", passed: true });
|
|
284
284
|
const lintConfig = generateLintConfig(candidate).file;
|
|
285
285
|
const require = createRequire(path.join(candidate, "package.json"));
|
|
@@ -305,7 +305,7 @@ export function createMutationController(directory, { captures = createCaptureCo
|
|
|
305
305
|
let evidence;
|
|
306
306
|
try { evidence = await verification.run({ units: behaviorUnits, persist: false }, signal); }
|
|
307
307
|
finally { await verification.close(); }
|
|
308
|
-
if (!evidence.ok) return lastResult = mutationEvidence({ ok: false, applied: false, diagnostics: [...evidence.results.filter(item => item.status !== "passed").map(item => ({ rule: "behavior-check", failure:item.failure, message: `${item.unit}#${item.check}: ${item.status}. ${item.message}` })),...evidence.diagnostics]
|
|
308
|
+
if (!evidence.ok) return lastResult = mutationEvidence({ ok: false, applied: false, diagnostics: [...evidence.results.filter(item => item.status !== "passed").map(item => ({ rule: "behavior-check", failure:item.failure, message: `${item.unit}#${item.check}: ${item.status}. ${item.message}` })),...evidence.diagnostics], checks: verified, behavior: evidence },candidateRevision);
|
|
309
309
|
verified.push({ name: "registered behavior checks", passed: true, results: evidence.results });
|
|
310
310
|
}
|
|
311
311
|
let screenshots;
|
|
@@ -353,7 +353,7 @@ export function createMutationController(directory, { captures = createCaptureCo
|
|
|
353
353
|
const warnings = [];
|
|
354
354
|
if (!dryRun && (assets || configuration)) { try { await generateBrandAssets(root); } catch (error) { warnings.push({ rule: "mutation-assets", message: `Source was applied; generated brand output needs recovery: ${error.message}` }); } }
|
|
355
355
|
if (!dryRun && captures.store.fingerprint()) { try { captures.store.promote(previewSourceRevision(loadConfig(root)), inspectScenarios(loadConfig(root)).scenarios); } catch (error) { warnings.push({ rule: "mutation-capture", message: `Source was applied; capture promotion needs recovery: ${error.message}` }); } }
|
|
356
|
-
return lastResult = mutationEvidence({ ok: true, ...(patches ? {units,transaction:true} : {unit}), applied: !dryRun, changes: changes.map(change => ({ path: change.path, operation: change.content === null ? "delete" : change.expectedDigest === null ? "create" : "update", digest: change.content === null ? null : digest(Buffer.from(change.content)) })), checks: verified, ...(result.advisories?.length ? { advisories: result.advisories
|
|
356
|
+
return lastResult = mutationEvidence({ ok: true, ...(patches ? {units,transaction:true} : {unit}), applied: !dryRun, changes: changes.map(change => ({ path: change.path, operation: change.content === null ? "delete" : change.expectedDigest === null ? "create" : "update", digest: change.content === null ? null : digest(Buffer.from(change.content)) })), checks: verified, ...(result.advisories?.length ? { advisories: result.advisories, advisoryCount: result.advisories.length } : {}), ...(warnings.length ? { warnings } : {}),
|
|
357
357
|
...(screenshots ? { screenshots } : {}),
|
|
358
358
|
preview: "Source validation passed. Inspect the managed preview build and rendered scenario after applying; validation is not visual verification." },candidateRevision);
|
|
359
359
|
} catch (error) {
|
package/dist/mcp/prompts.mjs
CHANGED
|
@@ -2,7 +2,7 @@ export const reasoning = `ADS is development tooling. The project owns its compo
|
|
|
2
2
|
Start with ads_status and use its projectRoot, workflow and project commands; the MCP workspace may be nested inside the client repository. If ADS tools are not exposed, read ads://project/guide then ads://project/status. Find existing public UI with ads_catalog and load one relevant ADS workflow via ads_skills/ads_read_skill. UI implementation/refinement uses ads-designer; library adaptation ads-migrate; ADS transfer ads-import; motion ads-motion; stress testing ads-break-ui. Domain-model workflows are separate. Inspect the relevant public API, local guide, token definitions and real showcase fixture before proposing changes. Keep source/instruction discovery scoped to the target project, ancestry and selected external sources; broad scans of /mnt or home are not needed to discover this ADS.
|
|
3
3
|
When ads_status.mutations.enabled, discover one owner with ads_units, read relevant ranges with ads_read_file and send only a Git unified diff through ads_apply_patch({unit,patch,dryRun}). A unit includes its own scenarios/fixtures; imported peers remain separate. tokens is the token foundation unit. Only that owner's public re-exports may change in the shared entry; preserve unrelated statements including export {};. Token manifests list companionFiles: include required foundation import changes only for canonical token files created/deleted in the same patch, preserving other imports/comments and canonical order. Use dryRun to review validation. Rejected patches leave source unchanged; fix diagnostics rather than bypassing checks with direct writes. Shared globals use foundation; guides use documentation; assets use discovered assets/* units. Intentional configuration, registered project guards/builders and rule exceptions use configuration, which validates the proposed settings and refreshes its compiler. Keep mandatory ADS gates. For large/binary diffs, pass patchFile pointing to .ds/patches/<name>.patch rather than loading bytes into context; Git binary patches require installed Git. When editing is disabled, use local editing tools.
|
|
4
4
|
Reason through Data (design-token values, semantic roles and relationships), UX (structure, behavior, accessibility, states and layout), and Skin (token-backed identity and visual states). Business data is a separate application-owned input, supplied through typed props or injected callbacks/services. The project uses Linaria extraction for owned styles; MCP adds no production runtime.
|
|
5
|
-
Choose an existing public component or pattern first. Keep application data/routing/effects in the application and fixture data/state beside its scenario. Change presentation in its owning ADS implementation, then demonstrate the same public API in the showcase. Put demo-only sizing and framing in a
|
|
5
|
+
Choose an existing public component or pattern first. Keep application data/routing/effects in the application and fixture data/state beside its scenario. Change presentation in its owning ADS implementation, then demonstrate the same public API in the showcase. Put demo-only sizing and framing in a development-only review/ helper, exported through the separate review entry rather than the production barrel; add component props for deliberate runtime API needs and preserve required behavior. Do not create a widget-only replica or override component internals.
|
|
6
6
|
For token source inspection, call ads_tokens with a focused query. For token samples, components, patterns and composition, use ads_scenarios to select a path-derived id and invoke ads_show_scenario. Showcase and MCP mount that exact entry and its colocated fixture; no page initialization is required. The showcase is a minimal tool-owned shell plus an MCP client and Apps host. Its menus come from tools; it invokes the same entry tools and refreshes the active scenario through notifications. The reusable App loads framework runtime, foundation CSS, fonts and selected modules via MCP resources inside its sandbox. Keep project execution and styles out of the outer host; preserve mount/unmount cleanup and lazy dependencies. Use the existing ads_status showcase URL, or ads showcase when no MCP runtime is connected. Projects supply scenarios/fixtures and the compiler, not browser page files or another shell. Tool metadata selects an MCP App; a resources/read of HTML alone is not proof of rendered UI. If the host has no Apps view, give the configured showcase URL and use text/resources.
|
|
7
7
|
Check ads_status.previewWatch before launching a separate preview builder. A configured watcher is managed by ads mcp. Check ads_status.build or ads://project/build after changes: running does not mean ready, and unreported freshness is not evidence of current source. Reported preview failures include structured diagnostics; fix the owning source and wait for a successful rebuild, then invoke the preview tool again. Notifications may not reach the agent, so read status explicitly. Build revisions identify reported compilation attempts, not proof of runtime/visual correctness. Do not invent replacement UI to bypass a failed preview.
|
|
8
8
|
Skills: use ads_skills to select a workflow and read its SKILL.md with ads_read_skill. Resolve relative supporting files from that skill URI. Reading instructions does not prove native skill activation or authorize unrelated actions.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const resultBudgets = { summary: 4096, page: 8192, row: 2048, defaultLimit: 10, maxLimit: 20 };
|
|
4
|
+
export const jsonBytes = value => Buffer.byteLength(JSON.stringify(value));
|
|
5
|
+
|
|
6
|
+
/** Projection is only for delivery; validation always uses the complete result. */
|
|
7
|
+
export function compactValue(input, { string = 500, array = 8, keys = 24, depth = 5 } = {}) {
|
|
8
|
+
let truncated = false;
|
|
9
|
+
function visit(value, level) {
|
|
10
|
+
if (typeof value === "string") {
|
|
11
|
+
if (value.length <= string) return value;
|
|
12
|
+
truncated = true; return `${value.slice(0, string)}…`;
|
|
13
|
+
}
|
|
14
|
+
if (value === null || typeof value === "number" || typeof value === "boolean" || value === undefined) return value;
|
|
15
|
+
if (level >= depth) { truncated = true; return "[omitted]"; }
|
|
16
|
+
if (Array.isArray(value)) { if (value.length > array) truncated = true; return value.slice(0, array).map(item => visit(item, level + 1)); }
|
|
17
|
+
const entries = Object.entries(value);
|
|
18
|
+
if (entries.length > keys) truncated = true;
|
|
19
|
+
return Object.fromEntries(entries.slice(0, keys).map(([key, item]) => [key, visit(item, level + 1)]));
|
|
20
|
+
}
|
|
21
|
+
const value = visit(input, 0);
|
|
22
|
+
return { value, truncated };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function compactRow(input, bytes = resultBudgets.row) {
|
|
26
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) input = { value: input };
|
|
27
|
+
for (const limits of [{}, { string: 160, array: 3, keys: 18 }, { string: 60, array: 1, keys: 12, depth: 3 }]) {
|
|
28
|
+
const { value, truncated } = compactValue(input, limits);
|
|
29
|
+
const row = { ...value, ...(truncated ? { detailTruncated: true } : {}) };
|
|
30
|
+
if (jsonBytes(row) <= bytes) return row;
|
|
31
|
+
}
|
|
32
|
+
const row = Object.fromEntries(["recordIndex", "id", "unit", "check", "status", "passed", "rule", "file", "path", "name"].filter(key => input[key] !== undefined).slice(0, 4).map(key => [key, typeof input[key] === "string" ? input[key].slice(0, 20) : input[key]]));
|
|
33
|
+
const result = { ...row, detailTruncated: true };
|
|
34
|
+
return jsonBytes(result) <= bytes ? result : { detailTruncated: true };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function compactAcceptance(value) {
|
|
38
|
+
if (!value) return undefined;
|
|
39
|
+
const establishedCount=value.establishedCount??value.established?.length??0,unverifiedCount=value.unverifiedCount??value.unverified?.length??0;
|
|
40
|
+
return { scope: value.scope, status: value.status, establishedCount, unverifiedCount,
|
|
41
|
+
established: (value.established ?? []).slice(0, 3).map(item => String(item).slice(0, 100)),
|
|
42
|
+
unverified: (value.unverified ?? []).slice(0, 12).map(item => String(item).slice(0, 100)),
|
|
43
|
+
...(value.detailTruncated || establishedCount > 3 || unverifiedCount > 12 ? { detailTruncated: true } : {}) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The cursor binds the immutable result/query, not just an array offset. */
|
|
47
|
+
export function resultPage(items, { cursor, limit = resultBudgets.defaultLimit, identity, header = {}, key = "items" } = {}) {
|
|
48
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > resultBudgets.maxLimit) throw new Error("Use a page limit from 1 to 20.");
|
|
49
|
+
const stamp = createHash("sha256").update(JSON.stringify(identity)).digest("hex").slice(0, 24);
|
|
50
|
+
let offset = 0;
|
|
51
|
+
if (cursor !== undefined) {
|
|
52
|
+
if (typeof cursor !== "string" || cursor.length > 300) throw new Error("Invalid result cursor.");
|
|
53
|
+
const match = /^([a-f0-9]{24}):(\d+)$/.exec(cursor);
|
|
54
|
+
offset = match && match[1] === stamp ? Number(match[2]) : -1;
|
|
55
|
+
if (!Number.isSafeInteger(offset) || offset <= 0 || offset >= items.length) throw new Error("Invalid or stale result cursor; restart this listing.");
|
|
56
|
+
}
|
|
57
|
+
const selected = [];
|
|
58
|
+
const envelope = () => ({ ...header, total: items.length, returned: selected.length, byteLimit: resultBudgets.page, [key]: selected,
|
|
59
|
+
...(offset + selected.length < items.length ? { nextCursor: `${stamp}:${offset + selected.length}` } : {}) });
|
|
60
|
+
for (const item of items.slice(offset, offset + limit)) {
|
|
61
|
+
selected.push(compactRow(item));
|
|
62
|
+
if (jsonBytes(envelope()) > resultBudgets.page) { selected.pop(); break; }
|
|
63
|
+
}
|
|
64
|
+
if (offset < items.length && !selected.length) throw new Error("The page envelope exceeds the response budget.");
|
|
65
|
+
const result = envelope();
|
|
66
|
+
if (jsonBytes(result) > resultBudgets.page) throw new Error("The page exceeds its response budget.");
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function compactProgress(value) {
|
|
71
|
+
if (!value) return undefined;
|
|
72
|
+
const { stage, profile, elapsedMs, timeoutMs, total, completed, pending, workers } = value;
|
|
73
|
+
return { stage, profile, elapsedMs, timeoutMs, total, completed, pending, workers, activeCount: value.active?.length ?? 0,
|
|
74
|
+
active: (value.active ?? []).slice(0, 3).map(item => compactRow(item, 512)) };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Exact JSON ranges complement lossy row previews without unbounded replies. */
|
|
78
|
+
export function resultRecord(rows, { recordIndex, offset = 0, chars = 2000, header = {} } = {}) {
|
|
79
|
+
if (!Number.isInteger(recordIndex) || recordIndex < 0 || recordIndex >= rows.length || !Number.isInteger(offset) || offset < 0 || !Number.isInteger(chars) || chars < 1 || chars > 2000) throw new Error("Read a valid recordIndex with an offset and 1–2000 characters.");
|
|
80
|
+
const source=JSON.stringify(rows[recordIndex]);
|
|
81
|
+
if(offset>source.length)throw new Error("Offset exceeds the selected record.");
|
|
82
|
+
let end=Math.min(source.length,offset+chars);
|
|
83
|
+
const result=()=>({...header,recordIndex,offset,totalChars:source.length,text:source.slice(offset,end),nextOffset:end<source.length?end:null,byteLimit:resultBudgets.page});
|
|
84
|
+
while(jsonBytes(result())>resultBudgets.page&&end>offset)end=offset+Math.floor((end-offset)*0.8);
|
|
85
|
+
if(jsonBytes(result())>resultBudgets.page||end===offset&&offset<source.length)throw new Error("Record identity exceeds the response budget.");
|
|
86
|
+
return result();
|
|
87
|
+
}
|
package/dist/mcp/server.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/s
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { checkProject } from "../check.mjs";
|
|
7
7
|
import { loadConfig, sourceFiles } from "../config.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import { INVESTIGATIONS_URI, investigationIndex,
|
|
8
|
+
import { createVerificationController, BEHAVIOR_URI } from "../verification.mjs";
|
|
9
|
+
import { INVESTIGATIONS_URI, investigationIndex, investigationPage, investigationSections, readInvestigationResource } from "../investigations.mjs";
|
|
10
10
|
import { failure } from "../evidence.mjs";
|
|
11
11
|
import { inspectTokens } from "../tokens.mjs";
|
|
12
12
|
import { inspectScenarios } from "../scenarios.mjs";
|
|
@@ -26,6 +26,9 @@ import { reconcileTokenSamples } from "../token-fixtures.mjs";
|
|
|
26
26
|
import { createCaptureController } from "../capture.mjs";
|
|
27
27
|
import { CAPTURES_URI, captureSpec, screenshotFields } from "../capture-store.mjs";
|
|
28
28
|
import { previewSourceRevision } from "../preview-inputs.mjs";
|
|
29
|
+
import { createExecutionResults, RESULTS_URI, resultSections } from "./execution-results.mjs";
|
|
30
|
+
import { behaviorPage, behaviorSections } from "./behavior-pages.mjs";
|
|
31
|
+
import { compactProgress, resultBudgets } from "./result-pages.mjs";
|
|
29
32
|
|
|
30
33
|
const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
31
34
|
class InvalidParamsError extends ProtocolError {
|
|
@@ -40,10 +43,18 @@ function captureResponse(value, resourceLinks = false) {
|
|
|
40
43
|
}
|
|
41
44
|
const empty = z.object({}).strict();
|
|
42
45
|
const query = z.object({ query: z.string().max(200).optional(), limit: z.number().int().min(1).max(50).default(20) }).strict();
|
|
46
|
+
const recordRange = { recordIndex: z.number().int().min(0).optional(), offset: z.number().int().min(0).optional(), chars: z.number().int().min(1).max(2000).optional() };
|
|
47
|
+
const paged = { cursor: z.string().max(300).optional(), limit: z.number().int().min(1).max(resultBudgets.maxLimit).optional() };
|
|
43
48
|
const annotations = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
44
49
|
const schemas = { params: z.object({ cursor: z.string().optional() }).passthrough(), result: z.object({ resultType: z.literal("complete") }).passthrough() };
|
|
45
50
|
const ownerPatch = z.object({ unit: z.string().min(1).max(256), patch: z.string().min(1).max(mutationLimits.patchBytes).optional(), patchFile: z.string().min(1).max(500).optional() }).strict().refine(args => (args.patch !== undefined) !== (args.patchFile !== undefined), "Provide exactly one of patch or patchFile.");
|
|
46
51
|
|
|
52
|
+
function resourceQuery(uri, allowed) {
|
|
53
|
+
const url=new URL(uri);
|
|
54
|
+
if([...url.searchParams.keys()].some(key=>!allowed.includes(key)))throw new Error("Unknown paged resource query.");
|
|
55
|
+
return Object.fromEntries([...url.searchParams].map(([key,value])=>[key,["limit","recordIndex","offset","chars"].includes(key)?Number(value):value]));
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollInterval = 1000, previewWatch, showcaseUrl, notifications, inspection, mutations = false } = {}) {
|
|
48
59
|
const root = path.resolve(directory);
|
|
49
60
|
const cache = inspection ?? {};
|
|
@@ -51,6 +62,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
51
62
|
const captures = cache.captures ??= createCaptureController(root);
|
|
52
63
|
const edits = cache.edits ??= createMutationController(root, { captures });
|
|
53
64
|
const verification = cache.verification ??= createVerificationController(root);
|
|
65
|
+
const executionResults = cache.executionResults ??= createExecutionResults(root);
|
|
54
66
|
let options = loadConfig(root);
|
|
55
67
|
const signature = `${root}:${JSON.stringify(options.config)}`;
|
|
56
68
|
if (cache.signature !== signature) {
|
|
@@ -105,7 +117,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
105
117
|
function resources() {
|
|
106
118
|
const context = project();
|
|
107
119
|
const documents = getDocuments();
|
|
108
|
-
const promoted = new Set([context.publicEntryUri, ...context.guides.map((guide) => guide.uri)]);
|
|
120
|
+
const promoted = new Set([context.publicEntryUri, context.review.sourceUri, ...context.guides.map((guide) => guide.uri)]);
|
|
109
121
|
const skillEntries = new Set(skills.skills.map((skill) => skill.uri));
|
|
110
122
|
return [
|
|
111
123
|
{ uri: GUIDE_URI, name: "Start here: design-system workflow", description: "Read first for UI/ADS tasks, especially when only resource access is exposed. Explains the exact workspace, tool sequence, skill selection, previews, project commands and fallback.", mimeType: "text/markdown" },
|
|
@@ -113,6 +125,8 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
113
125
|
{ uri: "ads://guides/evidence", name: "Failure boundaries and compact investigations", mimeType: "text/markdown" },
|
|
114
126
|
{ uri: "ads://guides/atomic-styles", name: "Atomic styles: composition conflicts and custom-property extraction", mimeType: "text/markdown" },
|
|
115
127
|
{ uri: "ads://guides/scenarios", name: "Scenario coverage and public class-helper contracts", mimeType: "text/markdown" },
|
|
128
|
+
{ uri: "ads://guides/layers", name: "Component capabilities, pattern composition and dependency ownership", mimeType: "text/markdown" },
|
|
129
|
+
{ uri: "ads://guides/review", name: "Development-only review UI, token renderers and migration", mimeType: "text/markdown" },
|
|
116
130
|
{ uri: "ads://guides/captures", name: "On-demand captures and candidate visual review", mimeType: "text/markdown" },
|
|
117
131
|
{ uri: `${PROJECT_URI}/status`, name: "ADS project status", mimeType: "application/json" },
|
|
118
132
|
{ uri: BUILD_URI, name: "Preview build status and diagnostics", mimeType: "application/json" },
|
|
@@ -120,6 +134,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
120
134
|
{ uri: `${PROJECT_URI}/catalog`, name: "Public components and patterns", mimeType: "application/json" },
|
|
121
135
|
{ uri: `${PROJECT_URI}/scenarios`, name: "Independently mountable scenarios", mimeType: "application/json" },
|
|
122
136
|
{ uri: BEHAVIOR_URI, name: "Behavior contracts, demonstrations and current verification evidence", mimeType: "application/json" },
|
|
137
|
+
{ uri: RESULTS_URI, name: "Latest execution summaries and paged outcomes", mimeType: "application/json" },
|
|
123
138
|
{ uri: INVESTIGATIONS_URI, name: "Latest per-check investigation evidence", mimeType: "application/json" },
|
|
124
139
|
{ uri: CAPTURES_URI, name: "On-demand current and candidate screenshots", mimeType: "application/json" },
|
|
125
140
|
{ uri: `${PROJECT_URI}/checks`, name: "Current ADS architecture diagnostics", mimeType: "application/json" },
|
|
@@ -136,18 +151,18 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
136
151
|
];
|
|
137
152
|
}
|
|
138
153
|
function status() {
|
|
139
|
-
const context = project();
|
|
154
|
+
const context = project(), mutationState = mutations ? edits.status() : null, behaviorState = verification.status();
|
|
140
155
|
return { projectRoot: root, workflow: workflow([...previews(), ...scenarioApps()]), project: context,
|
|
141
156
|
package: pkg.name, version: pkg.version, dsDir: options.config.dsDir, publicAlias: options.config.publicAlias,
|
|
142
157
|
components: getCatalog().filter((item) => item.kind === "components" && item.apiKind === "ui").length,
|
|
143
158
|
patterns: getCatalog().filter((item) => item.kind === "patterns" && item.apiKind === "ui").length,
|
|
144
159
|
tokenCount: inspectTokens(root).tokens.length, showcaseUrl: getShowcaseUrl(),
|
|
145
160
|
previewWatch: previewWatch?.status() ?? { state: "disabled" },
|
|
146
|
-
mutations: mutations ?
|
|
161
|
+
mutations: mutations ? { ...mutationState, progress: compactProgress(mutationState.progress), lastResult: executionResults.latest("mutation") } : { enabled: false },
|
|
147
162
|
build: summarizeBuild(builds()),
|
|
148
163
|
previews: previews(), skills: skills.skills.map(({ uri, frontmatter }) => ({ uri, name: frontmatter.name, description: frontmatter.description })),
|
|
149
164
|
scenarios: { count: getRegistry().scenarios.length, diagnostics: getRegistry().diagnostics, resourceUri: `${PROJECT_URI}/scenarios`, tool: "ads_show_scenario" },
|
|
150
|
-
behaviors: { resourceUri: BEHAVIOR_URI, discoveryTool: "ads_behaviors", verificationTool: "ads_verify", investigationTool: "ads_investigate", investigationResourceUri: INVESTIGATIONS_URI, ...
|
|
165
|
+
behaviors: { resourceUri: BEHAVIOR_URI, discoveryTool: "ads_behaviors", verificationTool: "ads_verify", investigationTool: "ads_investigate", investigationResourceUri: INVESTIGATIONS_URI, ...behaviorState, progress: compactProgress(behaviorState.progress), lastResult: executionResults.latest("verification"), resultsTool: "ads_results", resultsResourceUri: RESULTS_URI },
|
|
151
166
|
captures: { resourceUri: CAPTURES_URI, tool: "ads_capture", clearTool: "ads_capture_clear", ...captures.status() },
|
|
152
167
|
experimental: { apps: true, nativeSkills: era === "modern", nativeSkillActivation: "host-controlled" } };
|
|
153
168
|
}
|
|
@@ -157,6 +172,8 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
157
172
|
if (uri === "ads://guides/evidence") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/evidence.md", import.meta.url), "utf8") }] };
|
|
158
173
|
if (uri === "ads://guides/atomic-styles") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/atomic-styles.md", import.meta.url), "utf8") }] };
|
|
159
174
|
if (uri === "ads://guides/scenarios") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/scenarios.md", import.meta.url), "utf8") }] };
|
|
175
|
+
if (uri === "ads://guides/layers") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/layers.md", import.meta.url), "utf8") }] };
|
|
176
|
+
if (uri === "ads://guides/review") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/review.md", import.meta.url), "utf8") }] };
|
|
160
177
|
if (uri === "ads://guides/captures") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/captures.md", import.meta.url), "utf8") }] };
|
|
161
178
|
if (skills.files.has(uri)) return { contents: [skills.read(uri)] };
|
|
162
179
|
let value;
|
|
@@ -166,8 +183,9 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
166
183
|
else if (uri === `${PROJECT_URI}/catalog`) value = { items: getCatalog() };
|
|
167
184
|
else if (uri === `${PROJECT_URI}/scenarios`) value = getRegistry();
|
|
168
185
|
else if (uri === `${PROJECT_URI}/checks`) value = checkProject(root);
|
|
169
|
-
else if (uri === BEHAVIOR_URI) value =
|
|
170
|
-
else if (uri === INVESTIGATIONS_URI) value =
|
|
186
|
+
else if (uri === BEHAVIOR_URI || uri.startsWith(`${BEHAVIOR_URI}?`)) value = behaviorPage(options, resourceQuery(uri, ["unit", "api", "query", "section", "cursor", "limit"]));
|
|
187
|
+
else if (uri === INVESTIGATIONS_URI || uri.startsWith(`${INVESTIGATIONS_URI}?`)) value = investigationPage(root, resourceQuery(uri, ["unit", "check", "runId", "section", "cursor", "limit", "recordIndex", "offset", "chars"]));
|
|
188
|
+
else if (uri === RESULTS_URI || uri.startsWith("ads://results/")) return { contents: [executionResults.readResource(uri)] };
|
|
171
189
|
else if (uri === CAPTURES_URI) value = captureIndex();
|
|
172
190
|
else if (uri === PREVIEW_MANIFEST_URI && options.config.mcp.previewManifest) {
|
|
173
191
|
value = readPreviewManifest(previewOptions());
|
|
@@ -209,6 +227,11 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
209
227
|
return build;
|
|
210
228
|
}
|
|
211
229
|
|
|
230
|
+
async function finishExecution(kind, result) {
|
|
231
|
+
const summary = executionResults.save(kind, result);
|
|
232
|
+
try { await refresh(); } catch (error) { process.stderr.write(`ads mcp: result saved; notification failed: ${error.message}\n`); }
|
|
233
|
+
return { ...json(summary), ...(!result.ok ? { isError: true } : {}) };
|
|
234
|
+
}
|
|
212
235
|
async function applyEdits(args,signal,progress) {
|
|
213
236
|
const result = await edits.apply(args,signal,progress);
|
|
214
237
|
if (result.screenshots) await refresh();
|
|
@@ -219,7 +242,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
219
242
|
await refresh(true);
|
|
220
243
|
} catch (error) {result.warnings=[...(result.warnings ?? []),{rule:"mutation-notification",message:`Changes were applied, but preview refresh failed: ${error.message.slice(0,2000)}. Recheck ads_status and reopen the scenario.`}];}
|
|
221
244
|
}
|
|
222
|
-
return
|
|
245
|
+
return finishExecution("mutation", { ...(args.patches ? {units:args.patches.map(item=>item.unit),transaction:true} : {unit:args.unit}), ...result });
|
|
223
246
|
}
|
|
224
247
|
function tools() {
|
|
225
248
|
return [
|
|
@@ -283,25 +306,14 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
283
306
|
}
|
|
284
307
|
return json({ widget: { resourceUri: TOKENS_APP_URI }, category: null, tokenCount: inspectTokens(root).tokens.length, dataUri: `${PROJECT_URI}/tokens`, showcaseUrl: getShowcaseUrl() });
|
|
285
308
|
} },
|
|
286
|
-
{ name: "ads_behaviors", description: "Discover
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (unit) return item;
|
|
291
|
-
const apis = Object.values(item.subjects).filter(subject => subject.api).map(subject => subject.api);
|
|
292
|
-
const demonstrations = [...new Set(Object.values(item.demonstrations).flat())];
|
|
293
|
-
return { unit: item.unit, file: item.file, apis: apis.slice(0, 20), apiCount: apis.length,
|
|
294
|
-
guarantees: item.guarantees.slice(0, 5).map(guarantee => ({ id: guarantee.id, result: guarantee.result })), guaranteeCount: item.guarantees.length,
|
|
295
|
-
demonstrations: demonstrations.slice(0, 10), demonstrationCount: demonstrations.length, checkCount: Object.keys(item.checks).length };
|
|
296
|
-
}), diagnostics: inventory.diagnostics.slice(0, 50), resourceUri: BEHAVIOR_URI });
|
|
297
|
-
} },
|
|
298
|
-
{ name: "ads_investigate", title: "Inspect check evidence", description: "Read the latest compact investigation for an exact registered unit/check, including candidate runs. Returns failure boundary, exact input/observed preview revisions, source/failing step, observed viewport, failed startup requests and available artifact resource URIs. Does not rerun tests or infer root cause. Bundles replace the previous entry per check; compare revisions with ads_behaviors before claiming freshness.", schema: z.object({unit:z.string().min(1).max(256),check:z.string().min(1).max(100)}).strict(), call: args => json(readInvestigation(root,args)) },
|
|
299
|
-
{ name: "ads_verify", description: "Run one owner's registered development behavior checks using its configured runner/profile. Executes trusted project commands with bounded output, cancellation and process-tree cleanup; installs nothing. Progress and per-check timings are available through ads_status; a profile timeout bounds the whole run. Browser profiles use an isolated managed showcase/MCP App. React StrictMode requires a project development harness, not a production widget. Missing/skipped cases, stale inputs and process exit alone cannot produce passing evidence. Results separate failure boundaries and explicit acceptance scope; inspect retained source/request/artifact evidence with ads_investigate. Discover exact unit/check IDs with ads_behaviors first.", schema: z.object({ unit: z.string().min(1).max(256), checks: z.array(z.string().min(1).max(100)).min(1).max(100).optional() }).strict(), annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, call: async (args, signal, progress) => {
|
|
309
|
+
{ name: "ads_behaviors", description: "Discover behavior owners, then page an exact unit's checks, guarantees, subjects, demonstrations or profiles. Summary listings never embed runner logs. Default 10/max 20 entries and 8 KiB JSON per page; continue with nextCursor and identical filters. Cursors expire when discovery/evidence changes. Read sourceFile with ads_read_file for a complete contract. Metadata and reachable calls do not prove execution; use ads_verify for registered checks.", schema: z.object({ query: z.string().max(200).optional(), unit: z.string().min(1).max(256).optional(), api: z.string().max(200).optional(), section: z.enum(behaviorSections).optional(), ...paged }).strict(), call: args => json(behaviorPage(options,args)) },
|
|
310
|
+
{ name: "ads_results", title: "Read execution outcomes", description: "Read saved ads_verify/ads_apply_patch/ads_apply_transaction outcomes without rerunning anything. Pass the returned resultId; select outcomes, checks, diagnostics, advisories, warnings, changes, captures, acceptance, investigation or selection. Default 10/max 20 rows, at most 8 KiB JSON per page. Follow nextCursor with identical section/status. Rows marked detailTruncated can be read in full using recordIndex and offset/chars (max 2000); follow nextOffset. Only the latest verification and mutation snapshots are retained; replaced IDs fail explicitly. For one outcome's browser evidence use ads_investigate with its exact unit/check/runId.", schema: z.object({ resultId: z.string().uuid(), section: z.enum(resultSections).optional(), status: z.enum(["all","passed","not_passed"]).optional(), ...paged, ...recordRange }).strict(), call: args => json(executionResults.page(args)) },
|
|
311
|
+
{ name: "ads_investigate", title: "Inspect check evidence", description: "Read a summary for an exact unit/check, or list latest investigations when no check is selected. Pass the outcome's runId to reject superseded evidence. Summary is at most 4 KiB JSON; details, requests, browserErrors and artifacts are separate paged sections (default 10/max 20 entries, 8 KiB). Read truncated rows with recordIndex/offset/chars and the exact runId. Artifact URIs are read separately. Does not rerun checks or infer root cause. Only the latest bundle per check is retained, including candidate runs.", schema: z.object({unit:z.string().min(1).max(256).optional(),check:z.string().min(1).max(100).optional(),runId:z.string().min(1).max(100).optional(),section:z.enum(investigationSections).optional(),...paged,...recordRange}).strict(), call: args => json(investigationPage(root,args)) },
|
|
312
|
+
{ name: "ads_verify", description: "Run one owner's registered development behavior checks using its configured runner/profile. Executes trusted project commands with bounded output, cancellation and process-tree cleanup; installs nothing. Progress and per-check timings are available through ads_status; a profile timeout bounds the whole run. Browser profiles use an isolated managed showcase/MCP App. React StrictMode requires a project development harness, not a production widget. Missing/skipped cases, stale inputs and process exit alone cannot produce passing evidence. Replies contain counts, acceptance, at most three failure summaries and a resultId (4 KiB JSON maximum). Page outcomes with ads_results; inspect one exact unit/check/runId with ads_investigate. Discover exact unit/check IDs with ads_behaviors first.", schema: z.object({ unit: z.string().min(1).max(256), checks: z.array(z.string().min(1).max(100)).min(1).max(100).optional() }).strict(), annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, call: async (args, signal, progress) => {
|
|
300
313
|
const result = await verification.run(args, signal, progress);
|
|
301
|
-
|
|
302
|
-
return { ...json(result), ...(!result.ok ? { isError: true } : {}) };
|
|
314
|
+
return finishExecution("verification", {unit:args.unit,...result});
|
|
303
315
|
} },
|
|
304
|
-
{ name: "ads_units", description: "Discover editable ADS units and their files: components/patterns with colocated previews; shared controllers; composition; tokens; foundation shared files; documentation including shared credits/license notices; assets/brand, assets/fonts and asset groups; configuration with ds.config.json, package/build/lint configuration and registered project guard/compiler sources. List summaries with query, or pass an exact unit ID for its manifest. Manifests include filePolicy for supported new documentation paths and companionFiles with permitted shared-entry or owner-only style-boundary bookkeeping; preserve peer permissions and unrelated configuration. Imported peers stay separate. New owners follow the existing colocation convention.", schema: query.extend({ unit: z.string().min(1).max(256).optional() }), call: ({ unit, query: search = "", limit }) => {
|
|
316
|
+
{ name: "ads_units", description: "Discover editable ADS units and their files: components/patterns with colocated previews; shared controllers; development review/ helpers with a separate export entry; composition; tokens; foundation shared files; documentation including shared credits/license notices; assets/brand, assets/fonts and asset groups; configuration with ds.config.json, package/build/lint configuration and registered project guard/compiler sources. List summaries with query, or pass an exact unit ID for its manifest. Manifests include filePolicy for supported new documentation paths and companionFiles with permitted shared-entry or owner-only style-boundary bookkeeping; preserve peer permissions and unrelated configuration. Imported peers stay separate. New owners follow the existing colocation convention.", schema: query.extend({ unit: z.string().min(1).max(256).optional() }), call: ({ unit, query: search = "", limit }) => {
|
|
305
317
|
const matches = edits.units().filter(item => unit ? item.id === unit : item.id.toLowerCase().includes(search.toLowerCase()));
|
|
306
318
|
return json({ total: matches.length, units: matches.slice(0, limit).map(item => ({ id: item.id, kind: item.kind, fileCount: item.files.length, ...(unit ? { files: item.files.slice(0, 200), filesTruncated: item.files.length > 200, ...(item.companionFiles ? { companionFiles: item.companionFiles } : {}), ...(item.filePolicy ? { filePolicy: item.filePolicy } : {}) } : {}) })) });
|
|
307
319
|
} },
|
|
@@ -311,11 +323,11 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
311
323
|
const {patch,...result}=reconcileTokenSamples(options,args);
|
|
312
324
|
return json({...result,removed:result.removed.slice(0,50),unresolved:result.unresolved.slice(0,50),unresolvedCount:result.unresolved.length,...storePreparedPatch(options,patch)});
|
|
313
325
|
}},
|
|
314
|
-
{ name: "ads_apply_patch", description: "Apply one Git diff to one ADS unit from ads_units. Pass exactly one of patch (inline) or patchFile (a regular .patch/.diff under .ds/patches/, for large/binary diffs without model-context bytes). Text uses --- a/path, +++ b/path and exact @@ hunks; /dev/null creates/deletes. Git binary patches require git diff --binary --full-index and installed Git. A unit owns its implementation, previews and fixtures; imported peers are separate. Foundation, documentation, assets and configuration are editable too. Configuration changes may register/edit project guards and builders; the proposed schema, mandatory core/Oxlint and candidate project checks must pass. Only selected-unit public re-exports may change; preserve unrelated statements. A real selected-unit re-export may remove a redundant export {}; placeholder. A tokens patch may include its companionFiles foundation entry only to add/remove canonical imports required by token files created/deleted in this patch, preserving other imports/comments and canonical order. A runtime owner patch may also include its ds.config.json companion solely for that owner's inlineStyleFiles/styleWrites entries alongside its source. Unused permissions fail validation. Set dryRun true to validate without writing. Optional screenshot:true plus captures:[{scenario,target?,theme?,viewport?}] captures current/candidate only after gates pass. For genuinely coupled owner changes use ads_apply_transaction with one patch per owner. Fix rejected patches and inspect current previews after acceptance.",
|
|
326
|
+
{ name: "ads_apply_patch", description: "Apply one Git diff to one ADS unit from ads_units. Pass exactly one of patch (inline) or patchFile (a regular .patch/.diff under .ds/patches/, for large/binary diffs without model-context bytes). Text uses --- a/path, +++ b/path and exact @@ hunks; /dev/null creates/deletes. Git binary patches require git diff --binary --full-index and installed Git. A unit owns its implementation, previews and fixtures; imported peers are separate. Foundation, documentation, assets and configuration are editable too. Configuration changes may register/edit project guards and builders; the proposed schema, mandatory core/Oxlint and candidate project checks must pass. Only selected-unit public re-exports may change; preserve unrelated statements. A real selected-unit re-export may remove a redundant export {}; placeholder. A tokens patch may include its companionFiles foundation entry only to add/remove canonical imports required by token files created/deleted in this patch, preserving other imports/comments and canonical order. A runtime owner patch may also include its ds.config.json companion solely for that owner's inlineStyleFiles/styleWrites entries alongside its source. Unused permissions fail validation. Set dryRun true to validate without writing. Optional screenshot:true plus captures:[{scenario,target?,theme?,viewport?}] captures current/candidate only after gates pass. For genuinely coupled owner changes use ads_apply_transaction with one patch per owner. Fix rejected patches and inspect current previews after acceptance. Reply is a 4 KiB summary; read diagnostics, outcomes and requested captures through ads_results using its resultId.",
|
|
315
327
|
schema: z.object({ unit: z.string().min(1).max(256), dryRun: z.boolean(), patch: z.string().min(1).max(mutationLimits.patchBytes).optional(), patchFile: z.string().min(1).max(500).optional(), ...screenshotFields }).strict().refine(args => (args.patch !== undefined) !== (args.patchFile !== undefined), "Provide exactly one of patch or patchFile."),
|
|
316
328
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, _meta: { ui: { visibility: ["model"] } },
|
|
317
329
|
call: applyEdits,
|
|
318
|
-
}, {name:"ads_apply_transaction",description:"Apply genuinely coupled ADS changes as 2–8 distinct owner Git patches. Each patch must obey its unit and narrow companion rules. Patches run in order; later patches use the earlier candidate's shared-entry content. Core guards, registered Oxlint/project checks and affected behavior checks validate the complete final candidate before any live source is written. Maximum 32 distinct files/16 MiB combined. Dry runs, failures and cancellation publish nothing; publication rolls back on write failure. Optional screenshot:true with dryRun:true and explicit captures selects current/candidate images. Prefer ads_apply_patch for independent edits.",schema:z.object({patches:z.array(ownerPatch).min(2).max(mutationLimits.units),dryRun:z.boolean(),...screenshotFields}).strict(),annotations:{readOnlyHint:false,destructiveHint:true,idempotentHint:false,openWorldHint:false},_meta:{ui:{visibility:["model"]}},call:applyEdits}] : []),
|
|
330
|
+
}, {name:"ads_apply_transaction",description:"Apply genuinely coupled ADS changes as 2–8 distinct owner Git patches. Each patch must obey its unit and narrow companion rules. Patches run in order; later patches use the earlier candidate's shared-entry content. Core guards, registered Oxlint/project checks and affected behavior checks validate the complete final candidate before any live source is written. Maximum 32 distinct files/16 MiB combined. Dry runs, failures and cancellation publish nothing; publication rolls back on write failure. Optional screenshot:true with dryRun:true and explicit captures selects current/candidate images. Prefer ads_apply_patch for independent edits. Reply is a bounded summary; page diagnostics, outcomes and captures with ads_results using the returned resultId.",schema:z.object({patches:z.array(ownerPatch).min(2).max(mutationLimits.units),dryRun:z.boolean(),...screenshotFields}).strict(),annotations:{readOnlyHint:false,destructiveHint:true,idempotentHint:false,openWorldHint:false},_meta:{ui:{visibility:["model"]}},call:applyEdits}] : []),
|
|
319
331
|
...options.config.mcp.apps.map((app) => ({ name: `ads_show_${app.id.replaceAll("-", "_")}`, title: app.title, description: `${app.description ?? app.title}. Renders the project's built ${app.kind} examples through MCP Apps; text fallback provides the showcase URL.`, schema: empty, _meta: { ui: { resourceUri: appUri(app.id) } }, call: async (_args, signal) => {
|
|
320
332
|
// Validate readiness without embedding executable HTML in model results.
|
|
321
333
|
const build = await previewBuild(app, signal);
|
|
@@ -339,7 +351,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
339
351
|
let progress = 0;
|
|
340
352
|
const token = context.mcpReq._meta?.progressToken;
|
|
341
353
|
const notify = token === undefined ? undefined : value => {
|
|
342
|
-
void context.mcpReq.notify({ method: "notifications/progress", params: { progressToken: token, progress: ++progress, message: value.message ?? `${value.profile}: ${value.completed}/${value.total} completed; ${value.active.map(item => `${item.unit}#${item.check}`).join(", ") || "no active check reported"}; ${value.elapsedMs} ms elapsed` } }).catch(() => {});
|
|
354
|
+
void context.mcpReq.notify({ method: "notifications/progress", params: { progressToken: token, progress: ++progress, message: value.message ?? `${value.profile}: ${value.completed}/${value.total} completed; ${(value.active ?? []).slice(0,3).map(item => `${item.unit}#${item.check}`).join(", ") || "no active check reported"}; ${value.elapsedMs} ms elapsed` } }).catch(() => {});
|
|
343
355
|
};
|
|
344
356
|
try { return await tool.call(input.data, context.mcpReq.signal, notify); }
|
|
345
357
|
catch (error) { return { isError: true, content: [{ type: "text", text: error.message }], structuredContent: {ok:false,message:error.message,failure:failure(error.build?"build":"runner",error.build?"preview-build":"tool-execution",error.build?.code??"ADS_TOOL_ERROR",{certainty:"observed-boundary"}),...(error.build?{build:error.build}:{})} }; }
|
|
@@ -352,11 +364,15 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
352
364
|
});
|
|
353
365
|
server.setRequestHandler("resources/templates/list", () => ({ resourceTemplates: [
|
|
354
366
|
{ uriTemplate: "ads://captures/{key}/{slot}", name: "Current/candidate screenshot", description: "Exact keys and available slots come from ads://project/captures. Slots are replaced in place, not revision history.", mimeType: "image/png" },
|
|
367
|
+
{ uriTemplate: "ads://results/{resultId}", name: "Saved execution summary", description: "Use resultId from execution or ads://project/results. Query section/status/cursor/limit for pages, or section/recordIndex/offset/chars for exact record ranges. Only the latest result of each kind remains available.", mimeType: "application/json" },
|
|
355
368
|
{ uriTemplate: "ads://investigations/{unit}/{check}", name: "Latest check investigation", description: "Use encoded unit/check segments from ads://project/investigations. One latest bundle per check, including candidate verification.", mimeType: "application/json" },
|
|
356
369
|
] }));
|
|
357
370
|
server.setRequestHandler("resources/read", ({ params: { uri } }, context) => read(uri, context.mcpReq.signal));
|
|
358
371
|
server.setRequestHandler("resources/subscribe", ({ params: { uri } }) => {
|
|
359
372
|
if (!resources().some((entry) => entry.uri === uri)) {
|
|
373
|
+
if(uri.startsWith("ads://results/")) {
|
|
374
|
+
try {executionResults.readResource(uri);subscribed.add(uri);return {};}catch{throw new ResourceNotFoundError(uri,"Unknown or superseded execution result.");}
|
|
375
|
+
}
|
|
360
376
|
if(uri.startsWith("ads://investigations/")) {
|
|
361
377
|
try {readInvestigationResource(root,uri);subscribed.add(uri);return {};}catch{throw new ResourceNotFoundError(uri,"Unknown check investigation.");}
|
|
362
378
|
}
|
|
@@ -402,6 +418,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
402
418
|
let previousCaptureUris = captures.store.resourceUris();
|
|
403
419
|
const investigationFingerprint=()=>fileFingerprint([path.join(root,".ds/investigations.json")]);
|
|
404
420
|
let previousInvestigations=investigationFingerprint();
|
|
421
|
+
let previousResults=executionResults.fingerprint();
|
|
405
422
|
async function refresh(force = false) {
|
|
406
423
|
// Polling covers replaced files and new directories consistently across OSes.
|
|
407
424
|
cache.documents = undefined;
|
|
@@ -415,7 +432,8 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
415
432
|
const assetsChanged = assets !== previousAssets;
|
|
416
433
|
const capturesChanged = captures.store.fingerprint() !== previousCaptures;
|
|
417
434
|
const investigationsChanged=investigationFingerprint()!==previousInvestigations;
|
|
418
|
-
|
|
435
|
+
const resultsChanged=executionResults.fingerprint()!==previousResults;
|
|
436
|
+
if (!filesChanged && !buildChanged && !artifactsChanged && !assetsChanged && !capturesChanged && !investigationsChanged && !resultsChanged) return false;
|
|
419
437
|
if (filesChanged) {
|
|
420
438
|
const updated = loadConfig(root);
|
|
421
439
|
await previewWatch?.reconfigure?.(updated.config.mcp);
|
|
@@ -433,6 +451,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
433
451
|
previousBuild = JSON.stringify(builds());
|
|
434
452
|
const targets = filesChanged ? era === "modern" ? resources().map((entry) => entry.uri).filter((uri) => !skills.files.has(uri)) : [...subscribed]
|
|
435
453
|
: [BUILD_URI, BEHAVIOR_URI, INVESTIGATIONS_URI, `${PROJECT_URI}/status`, ...(options.config.mcp.previewManifest ? [PREVIEW_MANIFEST_URI] : []), ...(artifactsChanged ? allApps().map(app => appUri(app.id)) : [])].filter((uri) => era === "modern" || subscribed.has(uri));
|
|
454
|
+
if(resultsChanged){targets.push(...[RESULTS_URI,...executionResults.index().results.map(entry=>entry.resourceUri),...[...subscribed].filter(uri=>uri.startsWith("ads://results/"))].filter(uri=>era==="modern"||subscribed.has(uri)));previousResults=executionResults.fingerprint();}
|
|
436
455
|
if(investigationsChanged){targets.push(...[INVESTIGATIONS_URI,...investigationIndex(root).entries.map(entry=>entry.resourceUri),...[...subscribed].filter(uri=>uri.startsWith("ads://investigations/"))].filter(uri=>era==="modern"||subscribed.has(uri)));previousInvestigations=investigationFingerprint();}
|
|
437
456
|
if (capturesChanged || filesChanged) {
|
|
438
457
|
const current = (captures.store.fingerprint() ? captureIndex().captures : []).flatMap(record => [record.current?.resourceUri, record.candidate?.resourceUri].filter(Boolean));
|
|
@@ -138,7 +138,7 @@ Boolean requesting whether a visible border and background is provided by the ho
|
|
|
138
138
|
- omitted: host decides border`)}),q_=i.object({method:i.literal("ui/request-display-mode"),params:i.object({mode:Sa.describe("The display mode being requested.")})}),N_=i.object({mode:Sa.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),wR=i.union([i.literal("model"),i.literal("app")]).describe("Tool visibility scope - who can access the tool."),KT=i.object({resourceUri:i.string().optional(),visibility:i.array(wR).optional().describe(`Who can access this tool. Default: ["model", "app"]
|
|
139
139
|
- "model": Tool visible to and callable by the agent
|
|
140
140
|
- "app": Tool callable by the app from this server only`),csp:i.never().optional(),permissions:i.never().optional()}),GT=i.object({mimeTypes:i.array(i.string()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')}),xR=i.object({method:i.literal("ui/download-file"),params:i.object({contents:i.array(i.union([ht,mt])).describe("Resource contents to download \u2014 embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})}),TR=i.object({method:i.literal("ui/message"),params:i.object({role:i.literal("user").describe('Message role, currently only "user" is supported.'),content:i.array(Ee).describe("Message content blocks (text, image, etc.).")})}),YT=i.object({method:i.literal("ui/notifications/sandbox-resource-ready"),params:i.object({html:i.string().describe("HTML content to load into the inner iframe."),sandbox:i.string().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:Ef.optional().describe("CSP configuration from resource metadata."),permissions:kf.optional().describe("Sandbox permissions from resource metadata.")})}),QT=i.object({method:i.literal("ui/notifications/tool-result"),params:Ye.describe("Standard MCP tool execution result.")}),j_=i.object({toolInfo:i.object({id:Te.optional().describe("JSON-RPC id of the tools/call request."),tool:Ge.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:cR.optional().describe("Current color theme preference."),styles:_R.optional().describe("Style configuration for theming the app."),displayMode:Sa.optional().describe("How the UI is currently displayed."),availableDisplayModes:i.array(Sa).optional().describe("Display modes the host supports."),containerDimensions:i.union([i.object({height:i.number().describe("Fixed container height in pixels.")}),i.object({maxHeight:i.union([i.number(),i.undefined()]).optional().describe("Maximum container height in pixels.")})]).and(i.union([i.object({width:i.number().describe("Fixed container width in pixels.")}),i.object({maxWidth:i.union([i.number(),i.undefined()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
|
|
141
|
-
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:i.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:i.string().optional().describe("User's timezone in IANA format."),userAgent:i.string().optional().describe("Host application identifier."),platform:i.union([i.literal("web"),i.literal("desktop"),i.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:i.object({touch:i.boolean().optional().describe("Whether the device supports touch input."),hover:i.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:i.object({top:i.number().describe("Top safe area inset in pixels."),right:i.number().describe("Right safe area inset in pixels."),bottom:i.number().describe("Bottom safe area inset in pixels."),left:i.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),XT=i.object({method:i.literal("ui/notifications/host-context-changed"),params:j_.describe("Partial context update containing only changed fields.")}),PR=i.object({method:i.literal("ui/update-model-context"),params:i.object({content:i.array(Ee).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:i.record(i.string(),i.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),ER=i.object({method:i.literal("ui/initialize"),params:i.object({appInfo:Pe.describe("App identification (name and version)."),appCapabilities:zR.describe("Features and capabilities this app provides."),protocolVersion:i.string().describe("Protocol version this app supports.")})}),kR=i.object({protocolVersion:i.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Pe.describe("Host application identification and version."),hostCapabilities:vR.describe("Features and capabilities provided by the host."),hostContext:j_.describe("Rich context about the host environment.")}).passthrough();var Ql=class{eventTarget;eventSource;messageListener;constructor(t=window.parent,o){this.eventTarget=t,this.eventSource=o,this.messageListener=r=>{if(o&&r.source!==this.eventSource){console.debug("Ignoring message from unknown source",r);return}let n=ct.safeParse(r.data);n.success?(console.debug("Parsed message",n.data),this.onmessage?.(n.data)):r.data?.jsonrpc!=="2.0"?console.debug("Ignoring non-JSON-RPC message",n.error.message,r):(console.error("Failed to parse message",n.error.message,r),this.onerror?.(Error("Invalid JSON-RPC message received: "+n.error.message)))}}async start(){window.addEventListener("message",this.messageListener)}async send(t,o){t.method!==aR&&console.debug("Sending message",t),this.eventTarget.postMessage(t,"*")}async close(){window.removeEventListener("message",this.messageListener),this.onclose?.()}onclose;onerror;onmessage;sessionId;setProtocolVersion};var $R=[A_],Xl=class extends ma{_client;_hostInfo;_hostCapabilities;_appCapabilities;_hostContext={};_appInfo;_initializedReceived=!1;_registeredEvents=new Set;_events=new Tf;_methods=new Pf;setRequestHandler=(...t)=>{let[o,...r]=O_("request",t)??t;this._methods.claim(o,"setRequestHandler"),super.setRequestHandler.call(this,o,...r)};setNotificationHandler=(...t)=>{let[o,...r]=O_("notification",t)??t;this._methods.claim(o,"setNotificationHandler"),super.setNotificationHandler.call(this,o,...r)};removeRequestHandler=t=>{this._methods.release(t),super.removeRequestHandler(t)};removeNotificationHandler=t=>{this._methods.release(t),super.removeNotificationHandler(t)};replaceRequestHandler=(t,...o)=>{this._methods.replace(t),super.setRequestHandler.call(this,t,...o)};_wrapHandler(t,o){let r=super._wrapHandler(t,o);return t==="ui/initialize"||t==="ping"?r:async(n,s)=>(this._initializedReceived||console.warn(`[ext-apps] AppBridge received '${t}' before ui/notifications/initialized. The View is calling host methods before completing the handshake; it should await app.connect() first.`),r(n,s))}eventSchemas={sizechange:gR,sandboxready:fR,initialized:RR,requestteardown:yR,loggingmessage:bo};setEventHandler(t,o){this._ensureEventSlot(t),this._events.setHandler(t,o)}getEventHandler(t){return this._events.getHandler(t)}addEventListener(t,o){this._ensureEventSlot(t),this._events.addEventListener(t,o)}removeEventListener(t,o){this._events.removeEventListener(t,o)}_ensureEventSlot(t){if(this._registeredEvents.has(t))return;let o=this.eventSchemas[t];if(!o)throw Error(`Unknown event: ${String(t)}`);let r=n=>{t==="initialized"&&(this._initializedReceived=!0),this._events.dispatch(t,n)};t==="loggingmessage"?this.setNotificationHandler("notifications/message",n=>{r(n.params)}):this.setNotificationHandler(o.shape.method.value,{params:o.shape.params},n=>r(n)),this._registeredEvents.add(t)}warnIfRequestHandlerReplaced(t,o,r){o&&r&&console.warn(`[MCP Apps] ${t} handler replaced. Previous handler will no longer be called.`)}constructor(t,o,r,n){super(n),this._client=t,this._hostInfo=o,this._hostCapabilities=r,this._methods.replace("notifications/cancelled"),this._methods.replace("notifications/progress"),this._ensureEventSlot("initialized"),this._hostContext=n?.hostContext||{},this.replaceRequestHandler("ui/initialize",{params:ER.shape.params,result:kR},s=>this._onAppsInitialize(s)),this.replaceRequestHandler("ping",(s,a)=>(this.onping?.(s.params,a),{})),this.replaceRequestHandler("ui/request-display-mode",{params:q_.shape.params,result:N_},()=>({mode:this._hostContext.displayMode??"inline"}))}buildContext(t){return t}assertCapabilityForMethod(t){}assertRequestHandlerCapability(t){}assertNotificationCapability(t){}getAppCapabilities(){return this._appCapabilities}getAppVersion(){return this._appInfo}onping;get onsizechange(){return this.getEventHandler("sizechange")}set onsizechange(t){this.setEventHandler("sizechange",t)}get onsandboxready(){return this.getEventHandler("sandboxready")}set onsandboxready(t){this.setEventHandler("sandboxready",t)}get oninitialized(){return this.getEventHandler("initialized")}set oninitialized(t){this.setEventHandler("initialized",t)}_onmessage;get onmessage(){return this._onmessage}set onmessage(t){this.warnIfRequestHandlerReplaced("onmessage",this._onmessage,t),this._onmessage=t,this.replaceRequestHandler("ui/message",{params:TR.shape.params,result:mR},async(o,r)=>{if(!this._onmessage)throw Error("No onmessage handler set");return this._onmessage(o,r)})}_onopenlink;get onopenlink(){return this._onopenlink}set onopenlink(t){this.warnIfRequestHandlerReplaced("onopenlink",this._onopenlink,t),this._onopenlink=t,this.replaceRequestHandler("ui/open-link",{params:dR.shape.params,result:pR},async(o,r)=>{if(!this._onopenlink)throw Error("No onopenlink handler set");return this._onopenlink(o,r)})}_ondownloadfile;get ondownloadfile(){return this._ondownloadfile}set ondownloadfile(t){this.warnIfRequestHandlerReplaced("ondownloadfile",this._ondownloadfile,t),this._ondownloadfile=t,this.replaceRequestHandler("ui/download-file",{params:xR.shape.params,result:hR},async(o,r)=>{if(!this._ondownloadfile)throw Error("No ondownloadfile handler set");return this._ondownloadfile(o,r)})}get onrequestteardown(){return this.getEventHandler("requestteardown")}set onrequestteardown(t){this.setEventHandler("requestteardown",t)}_onrequestdisplaymode;get onrequestdisplaymode(){return this._onrequestdisplaymode}set onrequestdisplaymode(t){this.warnIfRequestHandlerReplaced("onrequestdisplaymode",this._onrequestdisplaymode,t),this._onrequestdisplaymode=t,this.replaceRequestHandler("ui/request-display-mode",{params:q_.shape.params,result:N_},async(o,r)=>{if(!this._onrequestdisplaymode)throw Error("No onrequestdisplaymode handler set");return this._onrequestdisplaymode(o,r)})}get onloggingmessage(){return this.getEventHandler("loggingmessage")}set onloggingmessage(t){this.setEventHandler("loggingmessage",t)}_onupdatemodelcontext;get onupdatemodelcontext(){return this._onupdatemodelcontext}set onupdatemodelcontext(t){this.warnIfRequestHandlerReplaced("onupdatemodelcontext",this._onupdatemodelcontext,t),this._onupdatemodelcontext=t,this.replaceRequestHandler("ui/update-model-context",{params:PR.shape.params,result:We},async(o,r)=>{if(!this._onupdatemodelcontext)throw Error("No onupdatemodelcontext handler set");return this._onupdatemodelcontext(o,r)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler("tools/call",async(o,r)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(o.params,r)})}_oncreatesamplingmessage;get oncreatesamplingmessage(){return this._oncreatesamplingmessage}set oncreatesamplingmessage(t){this.warnIfRequestHandlerReplaced("oncreatesamplingmessage",this._oncreatesamplingmessage,t),this._oncreatesamplingmessage=t,this.replaceRequestHandler("sampling/createMessage",async(o,r)=>{if(!this._oncreatesamplingmessage)throw Error("No oncreatesamplingmessage handler set");return this._oncreatesamplingmessage(o.params,r)})}sendToolListChanged(t={}){return this.notification({method:"notifications/tools/list_changed",params:t})}_onlistresources;get onlistresources(){return this._onlistresources}set onlistresources(t){this.warnIfRequestHandlerReplaced("onlistresources",this._onlistresources,t),this._onlistresources=t,this.replaceRequestHandler("resources/list",async(o,r)=>{if(!this._onlistresources)throw Error("No onlistresources handler set");return this._onlistresources(o.params,r)})}_onlistresourcetemplates;get onlistresourcetemplates(){return this._onlistresourcetemplates}set onlistresourcetemplates(t){this.warnIfRequestHandlerReplaced("onlistresourcetemplates",this._onlistresourcetemplates,t),this._onlistresourcetemplates=t,this.replaceRequestHandler("resources/templates/list",async(o,r)=>{if(!this._onlistresourcetemplates)throw Error("No onlistresourcetemplates handler set");return this._onlistresourcetemplates(o.params,r)})}_onreadresource;get onreadresource(){return this._onreadresource}set onreadresource(t){this.warnIfRequestHandlerReplaced("onreadresource",this._onreadresource,t),this._onreadresource=t,this.replaceRequestHandler("resources/read",async(o,r)=>{if(!this._onreadresource)throw Error("No onreadresource handler set");return this._onreadresource(o.params,r)})}sendResourceListChanged(t={}){return this.notification({method:"notifications/resources/list_changed",params:t})}_onlistprompts;get onlistprompts(){return this._onlistprompts}set onlistprompts(t){this.warnIfRequestHandlerReplaced("onlistprompts",this._onlistprompts,t),this._onlistprompts=t,this.replaceRequestHandler("prompts/list",async(o,r)=>{if(!this._onlistprompts)throw Error("No onlistprompts handler set");return this._onlistprompts(o.params,r)})}sendPromptListChanged(t={}){return this.notification({method:"notifications/prompts/list_changed",params:t})}getCapabilities(){return this._hostCapabilities}async _onAppsInitialize(t){let o=t.protocolVersion;return this._appInfo!==void 0&&console.warn("[ext-apps] AppBridge received a second ui/initialize. The View may be double-mounting (e.g. React StrictMode in dev) without closing the previous App instance. Responding normally; the latest appInfo/appCapabilities replace the previous values."),this._appCapabilities=t.appCapabilities,this._appInfo=t.appInfo,{protocolVersion:$R.includes(o)?o:A_,hostCapabilities:this.getCapabilities(),hostInfo:this._hostInfo,hostContext:this._hostContext}}setHostContext(t){let o={},r=!1;for(let n of Object.keys(t)){let s=this._hostContext[n],a=t[n];CR(s,a)||(o[n]=a,r=!0)}r&&(this._hostContext=t,this.sendHostContextChange(o))}sendHostContextChange(t){return this.notification({method:"ui/notifications/host-context-changed",params:t})}sendToolInput(t){return this.notification({method:"ui/notifications/tool-input",params:t})}sendToolInputPartial(t){return this.notification({method:"ui/notifications/tool-input-partial",params:t})}sendToolResult(t){return this.notification({method:"ui/notifications/tool-result",params:t})}sendToolCancelled(t){return this.notification({method:"ui/notifications/tool-cancelled",params:t})}sendSandboxResourceReady(t){return this.notification({method:"ui/notifications/sandbox-resource-ready",params:t})}teardownResource(t,o){return this.request({method:"ui/resource-teardown",params:t},bR,o)}sendResourceTeardown=this.teardownResource;callTool(t,o){return this.request({method:"tools/call",params:t},o)}listTools(t,o){return this.request({method:"tools/list",params:t},o)}async connect(t){if(this.transport)throw Error("AppBridge is already connected. Call close() before connecting again.");if(this._initializedReceived=!1,this._client){let o=this._client.getServerCapabilities();if(!o)throw Error("Client server capabilities not available");o.tools&&(this.oncalltool=async(r,n)=>this._client.request({method:"tools/call",params:r},{signal:n.mcpReq.signal}),o.tools.listChanged&&this._client.setNotificationHandler("notifications/tools/list_changed",r=>this.sendToolListChanged(r.params))),o.resources&&(this.onlistresources=async(r,n)=>this._client.request({method:"resources/list",params:r},{signal:n.mcpReq.signal}),this.onlistresourcetemplates=async(r,n)=>this._client.request({method:"resources/templates/list",params:r},{signal:n.mcpReq.signal}),this.onreadresource=async(r,n)=>this._client.request({method:"resources/read",params:r},{signal:n.mcpReq.signal}),o.resources.listChanged&&this._client.setNotificationHandler("notifications/resources/list_changed",r=>this.sendResourceListChanged(r.params))),o.prompts&&(this.onlistprompts=async(r,n)=>this._client.request({method:"prompts/list",params:r},{signal:n.mcpReq.signal}),o.prompts.listChanged&&this._client.setNotificationHandler("notifications/prompts/list_changed",r=>this.sendPromptListChanged(r.params)))}return super.connect(t)}};function CR(e,t){return JSON.stringify(e)===JSON.stringify(t)}function L_(e,t){let o=new DOMParser().parseFromString(e,"text/html"),r=o.createElement("meta");if(r.httpEquiv="Content-Security-Policy",r.content=t,o.head.prepend(r),!o.querySelector("base[href]")){let n=o.querySelector("base")??o.createElement("base");n.setAttribute("href","about:srcdoc"),r.after(n)}return"<!doctype html>"+o.documentElement.outerHTML}var $e=new Yl({name:"ADS preview",version:"1.0.0"},{},{autoResize:!0}),Wr=document.getElementById("ads-token-root"),Lt=document.getElementById("ads-widget-status"),tu=document.getElementById("ads-token-theme"),eu=document.getElementById("ads-scenario-root"),To,Me=0,U_="",Br=30,Z_=document.createElement("style");Wr&&document.head.append(Z_);function ou(e){e!=="light"&&e!=="dark"||(document.documentElement.dataset.theme=e,tu&&(tu.value=e),Wr&&To&&wo())}function Oe(e,t){let o=document.createElement(e);return o.textContent=t,o}function IR(e){let t=Oe("li","");t.dataset.token=e.name;let o=getComputedStyle(document.documentElement).getPropertyValue(e.name).trim(),r=Oe("details","");r.className="token-source";let n=Oe("summary","");n.title=`${e.name}: ${o||"Conditional or unset"}. Click to inspect source definitions.`;let s=Oe("code",e.name);s.className="token-name";let a=Oe("span",o||"Conditional or unset");n.append(s,a);let l=Oe("dl","");l.append(Oe("dt","Computed in preview"),Oe("dd",o||"Conditional or unset")),e.meaning&&l.append(Oe("dt","Meaning"),Oe("dd",e.meaning));for(let u of e.definitions)l.append(Oe("dt",u.scope),Oe("dd",`${u.value} \xB7 ${u.file}:${u.line}`));return r.append(n,l),t.append(r),t}function wo(){Z_.textContent=To.css;let e=To.tokens.filter(t=>`${t.name} ${t.type??""}`.toLowerCase().includes(U_.toLowerCase()));document.getElementById("ads-token-list").replaceChildren(...e.slice(Me,Me+Br).map(IR)),document.querySelector("footer").hidden=e.length<=Br,document.getElementById("ads-token-count").textContent=e.length?`${Me+1}\u2013${Math.min(Me+Br,e.length)} of ${e.length}`:"No matching tokens",document.getElementById("ads-token-prev").disabled=Me===0,document.getElementById("ads-token-next").disabled=Me+Br>=e.length,Lt.textContent=`${e.length} matching definitions \xB7 ${To.diagnostics.length} diagnostics`}Wr&&($e.ontoolinput=()=>{Me=0,To&&wo()},To=JSON.parse(document.getElementById("ads-token-seed").textContent),ou("light"),document.getElementById("ads-token-search").addEventListener("input",e=>{U_=e.target.value,Me=0,wo()}),tu.addEventListener("change",()=>ou(tu.value)),document.getElementById("ads-token-prev").addEventListener("click",()=>{Me=Math.max(0,Me-Br),wo()}),document.getElementById("ads-token-next").addEventListener("click",()=>{Me+=Br,wo()}),document.getElementById("ads-token-refresh").addEventListener("click",async()=>{try{let e=await $e.readServerResource({uri:"ads://project/tokens"});To=JSON.parse(e.contents.find(t=>"text"in t).text),Me=0,wo()}catch(e){Lt.textContent=`Refresh failed: ${e.message}`}}));var xo,$f,D_=!1,_a=0;async function H_(e){if($f=e,!D_||!eu)return;let t=e?.structuredContent?.scenario?.resourceUri;if(!t||!t.startsWith("ui://ads/previews/"))return;let o=++_a;Wr&&(Wr.hidden=!0,Lt=document.getElementById("ads-scenario-status")),eu.hidden=!1;try{if(!$e.getHostCapabilities()?.serverResources)throw new Error("This Apps host does not forward server resources. Use the project showcase URL from the tool result.");let n=(await $e.readServerResource({uri:t})).contents.find(u=>u.mimeType==="text/html;profile=mcp-app"&&typeof u.text=="string");if(!n)throw new Error("Scenario HTML is unavailable.");if(o!==_a)return;xo&&(await xo.close(),xo=void 0);let s=document.createElement("iframe");s.title=e.structuredContent.scenario.title,s.setAttribute("sandbox","allow-scripts"),eu.replaceChildren(s);let a=new Xl(null,{name:"ADS scenario host",version:"1.0.0"},{serverResources:{},serverTools:{}},{hostContext:$e.getHostContext()});xo=a,a.onreadresource=u=>$e.readServerResource(u),a.oncalltool=u=>$e.callServerTool(u),a.onsizechange=({height:u})=>{u&&(s.height=String(Math.min(1e4,Math.max(120,u))))},a.oninitialized=async()=>{await a.sendToolInput({arguments:{}}),await a.sendToolResult(e),o===_a&&window.parent.postMessage({type:"ads-preview-ready"},"*")},await a.connect(new Ql(s.contentWindow,s.contentWindow));let l="default-src 'none'; script-src 'unsafe-inline' blob:; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; worker-src blob: data:; connect-src 'none'; base-uri about:; object-src 'none'";s.srcdoc=L_(n.text,l),Lt.hidden=!0}catch(r){o===_a&&(Lt.hidden=!1,Lt.textContent=r.message,window.parent.postMessage({type:"ads-preview-ready"},"*"))}}eu&&($e.ontoolresult=e=>{H_(e)},$e.onteardown=async()=>{++_a;let e=xo;xo=void 0;try{await e?.teardownResource({},{timeout:500})}catch{}return await e?.close(),{}});$e.onhostcontextchanged=e=>{ou(e.theme),xo?.setHostContext(e)};try{await $e.connect(),D_=!0,$f&&H_($f),ou($e.getHostContext()?.theme),Wr&&(document.getElementById("ads-token-refresh").disabled=!$e.getHostCapabilities()?.serverResources,wo())}catch{Lt&&(Lt.textContent="Apps bridge unavailable. Samples remain usable; inspect project data through ADS tools/resources.")}
|
|
141
|
+
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:i.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:i.string().optional().describe("User's timezone in IANA format."),userAgent:i.string().optional().describe("Host application identifier."),platform:i.union([i.literal("web"),i.literal("desktop"),i.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:i.object({touch:i.boolean().optional().describe("Whether the device supports touch input."),hover:i.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:i.object({top:i.number().describe("Top safe area inset in pixels."),right:i.number().describe("Right safe area inset in pixels."),bottom:i.number().describe("Bottom safe area inset in pixels."),left:i.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),XT=i.object({method:i.literal("ui/notifications/host-context-changed"),params:j_.describe("Partial context update containing only changed fields.")}),PR=i.object({method:i.literal("ui/update-model-context"),params:i.object({content:i.array(Ee).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:i.record(i.string(),i.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),ER=i.object({method:i.literal("ui/initialize"),params:i.object({appInfo:Pe.describe("App identification (name and version)."),appCapabilities:zR.describe("Features and capabilities this app provides."),protocolVersion:i.string().describe("Protocol version this app supports.")})}),kR=i.object({protocolVersion:i.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Pe.describe("Host application identification and version."),hostCapabilities:vR.describe("Features and capabilities provided by the host."),hostContext:j_.describe("Rich context about the host environment.")}).passthrough();var Ql=class{eventTarget;eventSource;messageListener;constructor(t=window.parent,o){this.eventTarget=t,this.eventSource=o,this.messageListener=r=>{if(o&&r.source!==this.eventSource){console.debug("Ignoring message from unknown source",r);return}let n=ct.safeParse(r.data);n.success?(console.debug("Parsed message",n.data),this.onmessage?.(n.data)):r.data?.jsonrpc!=="2.0"?console.debug("Ignoring non-JSON-RPC message",n.error.message,r):(console.error("Failed to parse message",n.error.message,r),this.onerror?.(Error("Invalid JSON-RPC message received: "+n.error.message)))}}async start(){window.addEventListener("message",this.messageListener)}async send(t,o){t.method!==aR&&console.debug("Sending message",t),this.eventTarget.postMessage(t,"*")}async close(){window.removeEventListener("message",this.messageListener),this.onclose?.()}onclose;onerror;onmessage;sessionId;setProtocolVersion};var $R=[A_],Xl=class extends ma{_client;_hostInfo;_hostCapabilities;_appCapabilities;_hostContext={};_appInfo;_initializedReceived=!1;_registeredEvents=new Set;_events=new Tf;_methods=new Pf;setRequestHandler=(...t)=>{let[o,...r]=O_("request",t)??t;this._methods.claim(o,"setRequestHandler"),super.setRequestHandler.call(this,o,...r)};setNotificationHandler=(...t)=>{let[o,...r]=O_("notification",t)??t;this._methods.claim(o,"setNotificationHandler"),super.setNotificationHandler.call(this,o,...r)};removeRequestHandler=t=>{this._methods.release(t),super.removeRequestHandler(t)};removeNotificationHandler=t=>{this._methods.release(t),super.removeNotificationHandler(t)};replaceRequestHandler=(t,...o)=>{this._methods.replace(t),super.setRequestHandler.call(this,t,...o)};_wrapHandler(t,o){let r=super._wrapHandler(t,o);return t==="ui/initialize"||t==="ping"?r:async(n,s)=>(this._initializedReceived||console.warn(`[ext-apps] AppBridge received '${t}' before ui/notifications/initialized. The View is calling host methods before completing the handshake; it should await app.connect() first.`),r(n,s))}eventSchemas={sizechange:gR,sandboxready:fR,initialized:RR,requestteardown:yR,loggingmessage:bo};setEventHandler(t,o){this._ensureEventSlot(t),this._events.setHandler(t,o)}getEventHandler(t){return this._events.getHandler(t)}addEventListener(t,o){this._ensureEventSlot(t),this._events.addEventListener(t,o)}removeEventListener(t,o){this._events.removeEventListener(t,o)}_ensureEventSlot(t){if(this._registeredEvents.has(t))return;let o=this.eventSchemas[t];if(!o)throw Error(`Unknown event: ${String(t)}`);let r=n=>{t==="initialized"&&(this._initializedReceived=!0),this._events.dispatch(t,n)};t==="loggingmessage"?this.setNotificationHandler("notifications/message",n=>{r(n.params)}):this.setNotificationHandler(o.shape.method.value,{params:o.shape.params},n=>r(n)),this._registeredEvents.add(t)}warnIfRequestHandlerReplaced(t,o,r){o&&r&&console.warn(`[MCP Apps] ${t} handler replaced. Previous handler will no longer be called.`)}constructor(t,o,r,n){super(n),this._client=t,this._hostInfo=o,this._hostCapabilities=r,this._methods.replace("notifications/cancelled"),this._methods.replace("notifications/progress"),this._ensureEventSlot("initialized"),this._hostContext=n?.hostContext||{},this.replaceRequestHandler("ui/initialize",{params:ER.shape.params,result:kR},s=>this._onAppsInitialize(s)),this.replaceRequestHandler("ping",(s,a)=>(this.onping?.(s.params,a),{})),this.replaceRequestHandler("ui/request-display-mode",{params:q_.shape.params,result:N_},()=>({mode:this._hostContext.displayMode??"inline"}))}buildContext(t){return t}assertCapabilityForMethod(t){}assertRequestHandlerCapability(t){}assertNotificationCapability(t){}getAppCapabilities(){return this._appCapabilities}getAppVersion(){return this._appInfo}onping;get onsizechange(){return this.getEventHandler("sizechange")}set onsizechange(t){this.setEventHandler("sizechange",t)}get onsandboxready(){return this.getEventHandler("sandboxready")}set onsandboxready(t){this.setEventHandler("sandboxready",t)}get oninitialized(){return this.getEventHandler("initialized")}set oninitialized(t){this.setEventHandler("initialized",t)}_onmessage;get onmessage(){return this._onmessage}set onmessage(t){this.warnIfRequestHandlerReplaced("onmessage",this._onmessage,t),this._onmessage=t,this.replaceRequestHandler("ui/message",{params:TR.shape.params,result:mR},async(o,r)=>{if(!this._onmessage)throw Error("No onmessage handler set");return this._onmessage(o,r)})}_onopenlink;get onopenlink(){return this._onopenlink}set onopenlink(t){this.warnIfRequestHandlerReplaced("onopenlink",this._onopenlink,t),this._onopenlink=t,this.replaceRequestHandler("ui/open-link",{params:dR.shape.params,result:pR},async(o,r)=>{if(!this._onopenlink)throw Error("No onopenlink handler set");return this._onopenlink(o,r)})}_ondownloadfile;get ondownloadfile(){return this._ondownloadfile}set ondownloadfile(t){this.warnIfRequestHandlerReplaced("ondownloadfile",this._ondownloadfile,t),this._ondownloadfile=t,this.replaceRequestHandler("ui/download-file",{params:xR.shape.params,result:hR},async(o,r)=>{if(!this._ondownloadfile)throw Error("No ondownloadfile handler set");return this._ondownloadfile(o,r)})}get onrequestteardown(){return this.getEventHandler("requestteardown")}set onrequestteardown(t){this.setEventHandler("requestteardown",t)}_onrequestdisplaymode;get onrequestdisplaymode(){return this._onrequestdisplaymode}set onrequestdisplaymode(t){this.warnIfRequestHandlerReplaced("onrequestdisplaymode",this._onrequestdisplaymode,t),this._onrequestdisplaymode=t,this.replaceRequestHandler("ui/request-display-mode",{params:q_.shape.params,result:N_},async(o,r)=>{if(!this._onrequestdisplaymode)throw Error("No onrequestdisplaymode handler set");return this._onrequestdisplaymode(o,r)})}get onloggingmessage(){return this.getEventHandler("loggingmessage")}set onloggingmessage(t){this.setEventHandler("loggingmessage",t)}_onupdatemodelcontext;get onupdatemodelcontext(){return this._onupdatemodelcontext}set onupdatemodelcontext(t){this.warnIfRequestHandlerReplaced("onupdatemodelcontext",this._onupdatemodelcontext,t),this._onupdatemodelcontext=t,this.replaceRequestHandler("ui/update-model-context",{params:PR.shape.params,result:We},async(o,r)=>{if(!this._onupdatemodelcontext)throw Error("No onupdatemodelcontext handler set");return this._onupdatemodelcontext(o,r)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler("tools/call",async(o,r)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(o.params,r)})}_oncreatesamplingmessage;get oncreatesamplingmessage(){return this._oncreatesamplingmessage}set oncreatesamplingmessage(t){this.warnIfRequestHandlerReplaced("oncreatesamplingmessage",this._oncreatesamplingmessage,t),this._oncreatesamplingmessage=t,this.replaceRequestHandler("sampling/createMessage",async(o,r)=>{if(!this._oncreatesamplingmessage)throw Error("No oncreatesamplingmessage handler set");return this._oncreatesamplingmessage(o.params,r)})}sendToolListChanged(t={}){return this.notification({method:"notifications/tools/list_changed",params:t})}_onlistresources;get onlistresources(){return this._onlistresources}set onlistresources(t){this.warnIfRequestHandlerReplaced("onlistresources",this._onlistresources,t),this._onlistresources=t,this.replaceRequestHandler("resources/list",async(o,r)=>{if(!this._onlistresources)throw Error("No onlistresources handler set");return this._onlistresources(o.params,r)})}_onlistresourcetemplates;get onlistresourcetemplates(){return this._onlistresourcetemplates}set onlistresourcetemplates(t){this.warnIfRequestHandlerReplaced("onlistresourcetemplates",this._onlistresourcetemplates,t),this._onlistresourcetemplates=t,this.replaceRequestHandler("resources/templates/list",async(o,r)=>{if(!this._onlistresourcetemplates)throw Error("No onlistresourcetemplates handler set");return this._onlistresourcetemplates(o.params,r)})}_onreadresource;get onreadresource(){return this._onreadresource}set onreadresource(t){this.warnIfRequestHandlerReplaced("onreadresource",this._onreadresource,t),this._onreadresource=t,this.replaceRequestHandler("resources/read",async(o,r)=>{if(!this._onreadresource)throw Error("No onreadresource handler set");return this._onreadresource(o.params,r)})}sendResourceListChanged(t={}){return this.notification({method:"notifications/resources/list_changed",params:t})}_onlistprompts;get onlistprompts(){return this._onlistprompts}set onlistprompts(t){this.warnIfRequestHandlerReplaced("onlistprompts",this._onlistprompts,t),this._onlistprompts=t,this.replaceRequestHandler("prompts/list",async(o,r)=>{if(!this._onlistprompts)throw Error("No onlistprompts handler set");return this._onlistprompts(o.params,r)})}sendPromptListChanged(t={}){return this.notification({method:"notifications/prompts/list_changed",params:t})}getCapabilities(){return this._hostCapabilities}async _onAppsInitialize(t){let o=t.protocolVersion;return this._appInfo!==void 0&&console.warn("[ext-apps] AppBridge received a second ui/initialize. The View may be double-mounting (e.g. React StrictMode in dev) without closing the previous App instance. Responding normally; the latest appInfo/appCapabilities replace the previous values."),this._appCapabilities=t.appCapabilities,this._appInfo=t.appInfo,{protocolVersion:$R.includes(o)?o:A_,hostCapabilities:this.getCapabilities(),hostInfo:this._hostInfo,hostContext:this._hostContext}}setHostContext(t){let o={},r=!1;for(let n of Object.keys(t)){let s=this._hostContext[n],a=t[n];CR(s,a)||(o[n]=a,r=!0)}r&&(this._hostContext=t,this.sendHostContextChange(o))}sendHostContextChange(t){return this.notification({method:"ui/notifications/host-context-changed",params:t})}sendToolInput(t){return this.notification({method:"ui/notifications/tool-input",params:t})}sendToolInputPartial(t){return this.notification({method:"ui/notifications/tool-input-partial",params:t})}sendToolResult(t){return this.notification({method:"ui/notifications/tool-result",params:t})}sendToolCancelled(t){return this.notification({method:"ui/notifications/tool-cancelled",params:t})}sendSandboxResourceReady(t){return this.notification({method:"ui/notifications/sandbox-resource-ready",params:t})}teardownResource(t,o){return this.request({method:"ui/resource-teardown",params:t},bR,o)}sendResourceTeardown=this.teardownResource;callTool(t,o){return this.request({method:"tools/call",params:t},o)}listTools(t,o){return this.request({method:"tools/list",params:t},o)}async connect(t){if(this.transport)throw Error("AppBridge is already connected. Call close() before connecting again.");if(this._initializedReceived=!1,this._client){let o=this._client.getServerCapabilities();if(!o)throw Error("Client server capabilities not available");o.tools&&(this.oncalltool=async(r,n)=>this._client.request({method:"tools/call",params:r},{signal:n.mcpReq.signal}),o.tools.listChanged&&this._client.setNotificationHandler("notifications/tools/list_changed",r=>this.sendToolListChanged(r.params))),o.resources&&(this.onlistresources=async(r,n)=>this._client.request({method:"resources/list",params:r},{signal:n.mcpReq.signal}),this.onlistresourcetemplates=async(r,n)=>this._client.request({method:"resources/templates/list",params:r},{signal:n.mcpReq.signal}),this.onreadresource=async(r,n)=>this._client.request({method:"resources/read",params:r},{signal:n.mcpReq.signal}),o.resources.listChanged&&this._client.setNotificationHandler("notifications/resources/list_changed",r=>this.sendResourceListChanged(r.params))),o.prompts&&(this.onlistprompts=async(r,n)=>this._client.request({method:"prompts/list",params:r},{signal:n.mcpReq.signal}),o.prompts.listChanged&&this._client.setNotificationHandler("notifications/prompts/list_changed",r=>this.sendPromptListChanged(r.params)))}return super.connect(t)}};function CR(e,t){return JSON.stringify(e)===JSON.stringify(t)}function L_(e,t){let o=new DOMParser().parseFromString(e,"text/html"),r=o.createElement("meta");if(r.httpEquiv="Content-Security-Policy",r.content=t,o.head.prepend(r),!o.querySelector("base[href]")){let n=o.querySelector("base")??o.createElement("base");n.setAttribute("href","about:srcdoc"),r.after(n)}return"<!doctype html>"+o.documentElement.outerHTML}var $e=new Yl({name:"ADS preview",version:"1.0.0"},{},{autoResize:!0}),Wr=document.getElementById("ads-token-root"),Lt=document.getElementById("ads-widget-status"),tu=document.getElementById("ads-token-theme"),eu=document.getElementById("ads-scenario-root"),To,Me=0,U_="",Br=30,Z_=document.createElement("style");Wr&&document.head.append(Z_);function ou(e){e!=="light"&&e!=="dark"||(document.documentElement.dataset.theme=e,tu&&(tu.value=e),Wr&&To&&wo())}function Oe(e,t){let o=document.createElement(e);return o.textContent=t,o}function IR(e){let t=Oe("li","");t.dataset.token=e.name;let o=getComputedStyle(document.documentElement).getPropertyValue(e.name).trim(),r=Oe("details","");r.className="token-source";let n=Oe("summary","");n.title=`${e.name}: ${o||"Conditional or unset"}. Click to inspect source definitions.`;let s=Oe("code",e.name);s.className="token-name";let a=Oe("span",o||"Conditional or unset");n.append(s,a);let l=Oe("dl","");l.append(Oe("dt","Computed in preview"),Oe("dd",o||"Conditional or unset")),e.meaning&&l.append(Oe("dt","Meaning"),Oe("dd",e.meaning));for(let u of e.definitions)l.append(Oe("dt",u.scope),Oe("dd",`${u.value} \xB7 ${u.file}:${u.line}`));return r.append(n,l),t.append(r),t}function wo(){Z_.textContent=To.css;let e=To.tokens.filter(t=>`${t.name} ${t.type??""}`.toLowerCase().includes(U_.toLowerCase()));document.getElementById("ads-token-list").replaceChildren(...e.slice(Me,Me+Br).map(IR)),document.querySelector("footer").hidden=e.length<=Br,document.getElementById("ads-token-count").textContent=e.length?`${Me+1}\u2013${Math.min(Me+Br,e.length)} of ${e.length}`:"No matching tokens",document.getElementById("ads-token-prev").disabled=Me===0,document.getElementById("ads-token-next").disabled=Me+Br>=e.length,Lt.textContent=`${e.length} matching definitions \xB7 ${To.diagnostics.length} diagnostics`}Wr&&($e.ontoolinput=()=>{Me=0,To&&wo()},To=JSON.parse(document.getElementById("ads-token-seed").textContent),ou("light"),document.getElementById("ads-token-search").addEventListener("input",e=>{U_=e.target.value,Me=0,wo()}),tu.addEventListener("change",()=>ou(tu.value)),document.getElementById("ads-token-prev").addEventListener("click",()=>{Me=Math.max(0,Me-Br),wo()}),document.getElementById("ads-token-next").addEventListener("click",()=>{Me+=Br,wo()}),document.getElementById("ads-token-refresh").addEventListener("click",async()=>{try{let e=await $e.readServerResource({uri:"ads://project/tokens"});To=JSON.parse(e.contents.find(t=>"text"in t).text),Me=0,wo()}catch(e){Lt.textContent=`Refresh failed: ${e.message}`}}));var xo,$f,D_=!1,_a=0;async function H_(e){if($f=e,!D_||!eu)return;let t=e?.structuredContent?.scenario?.resourceUri;if(!t||!t.startsWith("ui://ads/previews/"))return;let o=++_a;Wr&&(Wr.hidden=!0,Lt=document.getElementById("ads-scenario-status")),eu.hidden=!1;try{if(!$e.getHostCapabilities()?.serverResources)throw new Error("This Apps host does not forward server resources. Use the project showcase URL from the tool result.");let n=(await $e.readServerResource({uri:t})).contents.find(u=>u.mimeType==="text/html;profile=mcp-app"&&typeof u.text=="string");if(!n)throw new Error("Scenario HTML is unavailable.");if(o!==_a)return;xo&&(await xo.close(),xo=void 0);let s=document.createElement("iframe");s.title=e.structuredContent.scenario.title,s.setAttribute("sandbox","allow-scripts allow-forms"),eu.replaceChildren(s);let a=new Xl(null,{name:"ADS scenario host",version:"1.0.0"},{serverResources:{},serverTools:{}},{hostContext:$e.getHostContext()});xo=a,a.onreadresource=u=>$e.readServerResource(u),a.oncalltool=u=>$e.callServerTool(u),a.onsizechange=({height:u})=>{u&&(s.height=String(Math.min(1e4,Math.max(120,u))))},a.oninitialized=async()=>{await a.sendToolInput({arguments:{}}),await a.sendToolResult(e),o===_a&&window.parent.postMessage({type:"ads-preview-ready"},"*")},await a.connect(new Ql(s.contentWindow,s.contentWindow));let l="default-src 'none'; script-src 'unsafe-inline' blob:; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; worker-src blob: data:; connect-src 'none'; base-uri about:; object-src 'none'; form-action 'none'";s.srcdoc=L_(n.text,l),Lt.hidden=!0}catch(r){o===_a&&(Lt.hidden=!1,Lt.textContent=r.message,window.parent.postMessage({type:"ads-preview-ready"},"*"))}}eu&&($e.ontoolresult=e=>{H_(e)},$e.onteardown=async()=>{++_a;let e=xo;xo=void 0;try{await e?.teardownResource({},{timeout:500})}catch{}return await e?.close(),{}});$e.onhostcontextchanged=e=>{ou(e.theme),xo?.setHostContext(e)};try{await $e.connect(),D_=!0,$f&&H_($f),ou($e.getHostContext()?.theme),Wr&&(document.getElementById("ads-token-refresh").disabled=!$e.getHostCapabilities()?.serverResources,wo())}catch{Lt&&(Lt.textContent="Apps bridge unavailable. Samples remain usable; inspect project data through ADS tools/resources.")}
|
|
142
142
|
/*! Bundled license information:
|
|
143
143
|
|
|
144
144
|
@modelcontextprotocol/client/dist/src-WCy6ifGf.mjs:
|
package/dist/oxlint.mjs
CHANGED
|
@@ -9,6 +9,8 @@ const schema = [{
|
|
|
9
9
|
dsDir: { type: "string" },
|
|
10
10
|
sourceDir: { type: "string" },
|
|
11
11
|
publicAlias: { type: "string" },
|
|
12
|
+
publicEntry: { type: "string" },
|
|
13
|
+
reviewEntry: { type: "string" },
|
|
12
14
|
stylesEntry: { type: "string" },
|
|
13
15
|
inlineStyleFiles: { type: "array", items: { type: "string" } },
|
|
14
16
|
configurationFiles: { type: "array", items: { type: "string" } },
|
|
@@ -66,7 +68,7 @@ export default {
|
|
|
66
68
|
},
|
|
67
69
|
},
|
|
68
70
|
"no-inline-style": {
|
|
69
|
-
meta: { type: "problem", schema, messages: { inline: "Own visual styles in component/pattern Linaria. Put demo-only sizing and framing in a
|
|
71
|
+
meta: { type: "problem", schema, messages: { inline: "Own visual styles in component/pattern Linaria. Put demo-only sizing and framing in a development-only review/ helper. Reserve inlineStyleFiles for documented measured geometry; add component props for deliberate product API needs." } },
|
|
70
72
|
create(context) {
|
|
71
73
|
const { root, config } = options(context);
|
|
72
74
|
const allowed = config.inlineStyleFiles.some((file) => path.resolve(root, file) === path.resolve(context.getFilename()));
|
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameS
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import ts from "typescript";
|
|
5
5
|
import valueParser from "postcss-value-parser";
|
|
6
|
-
import { isInside } from "./config.mjs";
|
|
6
|
+
import { isInside, loadConfig } from "./config.mjs";
|
|
7
7
|
import { previewArtifactPath, previewManifestPath } from "./preview-scope.mjs";
|
|
8
8
|
|
|
9
9
|
export const PREVIEW_APP_URI = "ui://ads/preview";
|
|
@@ -21,9 +21,19 @@ function atomic(file, bytes) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/** Registry entries remain project-owned; these virtual entries normalize mount/unmount. */
|
|
24
|
+
export function configureReviewAlias(config, { dsRoot, config: ads }) {
|
|
25
|
+
const aliases = config.resolve?.alias ?? [];
|
|
26
|
+
const existing = Array.isArray(aliases) ? aliases : Object.entries(aliases).map(([find, replacement]) => ({ find, replacement }));
|
|
27
|
+
config.resolve = { ...config.resolve, alias: [{ find: `${ads.publicAlias}/review`, replacement: path.join(dsRoot, ads.reviewEntry) }, ...existing] };
|
|
28
|
+
}
|
|
29
|
+
|
|
24
30
|
export function previewEntriesPlugin(entries, mountEntry) {
|
|
25
31
|
const ids = new Map(entries.map((entry, index) => [`ads-preview-entry:${index}`, entry]));
|
|
26
32
|
return { name: "ads-preview-resource-entries",
|
|
33
|
+
config(config) {
|
|
34
|
+
if (!entries[0]?.root) return;
|
|
35
|
+
configureReviewAlias(config, loadConfig(entries[0].root));
|
|
36
|
+
},
|
|
27
37
|
resolveId(id) { if (ids.has(id)) return `\0${id}`; },
|
|
28
38
|
load(id) {
|
|
29
39
|
const entry = ids.get(id.slice(1));
|