@adia-ai/a2ui-mcp 0.6.5 → 0.6.7

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.
@@ -0,0 +1,89 @@
1
+ import { z } from "zod";
2
+ import { getCatalog } from "../../retrieval/catalog.js";
3
+ import {
4
+ getAllCompositions
5
+ } from "../../compose/strategies/zettel/composition-library.js";
6
+ import { getChunk, getChunkIndex } from "../../corpus/scripts/chunk-library.js";
7
+ function registerDiscoveryTools(server) {
8
+ server.tool(
9
+ "list_patterns",
10
+ `List all composition patterns in the A2UI corpus. Optional filters narrow by domain (auth, settings, dashboard, etc.) or category (block, page, flow).`,
11
+ {
12
+ domain: z.string().optional().describe('Filter by domain (e.g. "forms", "data", "navigation")'),
13
+ category: z.string().optional().describe('Filter by category ("block", "page", "flow")')
14
+ },
15
+ async ({ domain, category }) => {
16
+ const all = getAllCompositions();
17
+ let filtered = all;
18
+ if (domain) filtered = filtered.filter((c) => c.domain === domain);
19
+ if (category) {
20
+ filtered = filtered.filter((c) => {
21
+ const raw = c.name ? getChunk(c.name) : null;
22
+ const k = raw?.kind ?? c.kind;
23
+ return k === category;
24
+ });
25
+ }
26
+ return {
27
+ content: [
28
+ {
29
+ type: "text",
30
+ text: JSON.stringify(
31
+ {
32
+ total: filtered.length,
33
+ patterns: filtered.map((c) => {
34
+ const raw = c.name ? getChunk(c.name) : null;
35
+ return {
36
+ name: c.name,
37
+ domain: c.domain,
38
+ kind: raw?.kind ?? c.kind ?? "composition",
39
+ description: c.description,
40
+ keywords: c.keywords ?? []
41
+ };
42
+ })
43
+ },
44
+ null,
45
+ 2
46
+ )
47
+ }
48
+ ]
49
+ };
50
+ }
51
+ );
52
+ server.tool(
53
+ "server_status",
54
+ `Returns operational status of the MCP server: transport, sampling capability, corpus stats, version.`,
55
+ {},
56
+ async () => {
57
+ const catalog = await getCatalog();
58
+ const compositionCount = getAllCompositions().length;
59
+ const chunkIndex = getChunkIndex();
60
+ const chunkCount = chunkIndex ? chunkIndex["unique_names"] ?? null : null;
61
+ const hasSampling = server.server?._clientCapabilities?.sampling ? true : false;
62
+ const transport = typeof process !== "undefined" && process.env?.MCP_HTTP_PORT ? "http" : "stdio";
63
+ return {
64
+ content: [
65
+ {
66
+ type: "text",
67
+ text: JSON.stringify(
68
+ {
69
+ version: "0.1.0",
70
+ transport,
71
+ sampling: hasSampling,
72
+ corpus: {
73
+ totalComponents: catalog.totalTypes ?? null,
74
+ compositionCount,
75
+ chunkCount
76
+ }
77
+ },
78
+ null,
79
+ 2
80
+ )
81
+ }
82
+ ]
83
+ };
84
+ }
85
+ );
86
+ }
87
+ export {
88
+ registerDiscoveryTools
89
+ };
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import { FeedbackCollector } from "../../retrieval/feedback/feedback.js";
3
+ import { feedbackStore } from "../../retrieval/feedback/feedback-store.js";
4
+ const feedbackCollector = new FeedbackCollector();
5
+ function registerFeedbackTools(server) {
6
+ server.tool(
7
+ "submit_feedback",
8
+ "Submit structured feedback for a generation execution. Used by the evolution engine to learn from each generation.",
9
+ {
10
+ executionId: z.string().describe("Execution ID from generate_ui"),
11
+ rating: z.number().min(1).max(5).describe("Overall quality 1-5"),
12
+ intent: z.string().optional(),
13
+ domain: z.string().optional(),
14
+ intentAlignment: z.number().min(1).max(5).optional(),
15
+ visualQuality: z.number().min(1).max(5).optional(),
16
+ componentChoice: z.number().min(1).max(5).optional(),
17
+ userEdited: z.boolean().optional(),
18
+ editSummary: z.string().optional(),
19
+ notes: z.string().optional(),
20
+ shouldBePattern: z.boolean().optional(),
21
+ suggestedName: z.string().optional()
22
+ },
23
+ async (args) => {
24
+ feedbackCollector.collectFeedback(args.executionId, {
25
+ rating: args.rating,
26
+ intentAlignment: args.intentAlignment,
27
+ visualQuality: args.visualQuality,
28
+ componentChoice: args.componentChoice,
29
+ userEdited: args.userEdited,
30
+ editSummary: args.editSummary,
31
+ notes: args.notes
32
+ });
33
+ if (args.shouldBePattern != null) {
34
+ feedbackCollector.collectPatternFeedback(args.executionId, {
35
+ shouldBePattern: args.shouldBePattern,
36
+ suggestedName: args.suggestedName
37
+ });
38
+ }
39
+ return { content: [{ type: "text", text: JSON.stringify({ recorded: true, executionId: args.executionId, totalEntries: feedbackCollector.size }) }] };
40
+ }
41
+ );
42
+ server.tool(
43
+ "get_quality_metrics",
44
+ "Get aggregated quality metrics from the feedback store: avg score, thumb-up rate, per-domain breakdown, training gaps.",
45
+ {},
46
+ async () => {
47
+ const metrics = await feedbackStore.getQualityMetrics();
48
+ return { content: [{ type: "text", text: JSON.stringify(metrics, null, 2) }] };
49
+ }
50
+ );
51
+ server.tool(
52
+ "get_training_gaps",
53
+ "Get training gap signals from LLM self-critique: missing patterns, weak domain keywords, component gaps.",
54
+ {},
55
+ async () => {
56
+ const gaps = await feedbackStore.getGapSummary();
57
+ return { content: [{ type: "text", text: JSON.stringify(gaps, null, 2) }] };
58
+ }
59
+ );
60
+ }
61
+ export {
62
+ registerFeedbackTools
63
+ };
@@ -0,0 +1,151 @@
1
+ import { z } from "zod";
2
+ import { resolveAdapter } from "../server.js";
3
+ const SYSTEM_PROMPT = `You are the A2UI Refiner.
4
+
5
+ You receive:
6
+ 1. The user's original intent.
7
+ 2. A previously generated A2UI message array (the "previous output").
8
+ 3. A list of validation errors that the previous output failed.
9
+
10
+ Your job: produce a corrected A2UI message array that fixes ONLY the
11
+ listed errors and preserves everything else about the previous output
12
+ (component types, ids, intent, layout, copy). Do not refactor.
13
+
14
+ Output ONLY a JSON object of the shape:
15
+ {
16
+ "messages": [ ...A2UI messages... ]
17
+ }
18
+
19
+ No prose, no markdown fences. The "messages" array must be valid A2UI
20
+ output \u2014 every component carries an id, a type, and the parent/layout
21
+ fields it had before unless the error explicitly required changing
22
+ them.`;
23
+ function extractJsonObject(text) {
24
+ const stripped = text.replace(/^\s*```(?:json)?\s*/i, "").replace(/\s*```\s*$/i, "").trim();
25
+ try {
26
+ return JSON.parse(stripped);
27
+ } catch {
28
+ }
29
+ const start = stripped.indexOf("{");
30
+ if (start < 0) throw new Error("No JSON object found in LLM response");
31
+ let depth = 0;
32
+ let inStr = false;
33
+ let esc = false;
34
+ for (let i = start; i < stripped.length; i++) {
35
+ const ch = stripped[i];
36
+ if (inStr) {
37
+ if (esc) esc = false;
38
+ else if (ch === "\\") esc = true;
39
+ else if (ch === '"') inStr = false;
40
+ continue;
41
+ }
42
+ if (ch === '"') inStr = true;
43
+ else if (ch === "{") depth++;
44
+ else if (ch === "}") {
45
+ depth--;
46
+ if (depth === 0) {
47
+ return JSON.parse(stripped.slice(start, i + 1));
48
+ }
49
+ }
50
+ }
51
+ throw new Error("Unbalanced JSON object in LLM response");
52
+ }
53
+ function registerRefineTools(server) {
54
+ server.tool(
55
+ "refine_ui",
56
+ `Refine a previous generate_ui result whose validation failed. Pass the messages from the prior result + the validation errors, and the tool produces a corrected version. For monolithic engine; zettel has refine_composition.`,
57
+ {
58
+ intent: z.string().describe("Original intent string"),
59
+ previousMessages: z.array(z.any()).describe("Messages from the prior generate_ui call"),
60
+ validationErrors: z.array(z.any()).describe("Errors from the prior result.validation.errors")
61
+ },
62
+ async ({ intent, previousMessages, validationErrors }) => {
63
+ try {
64
+ const llm = await resolveAdapter();
65
+ const userPrompt = [
66
+ `INTENT:`,
67
+ intent,
68
+ ``,
69
+ `PREVIOUS OUTPUT (messages):`,
70
+ JSON.stringify(previousMessages, null, 2),
71
+ ``,
72
+ `VALIDATION ERRORS:`,
73
+ JSON.stringify(validationErrors, null, 2),
74
+ ``,
75
+ `Return ONLY: {"messages": [...]} with the errors above fixed and everything else preserved.`
76
+ ].join("\n");
77
+ const result = await llm.complete({
78
+ messages: [{ role: "user", content: userPrompt }],
79
+ systemPrompt: SYSTEM_PROMPT
80
+ });
81
+ const raw = result?.content ?? "";
82
+ let parsed;
83
+ try {
84
+ parsed = extractJsonObject(raw);
85
+ } catch (err) {
86
+ const e = err instanceof Error ? err : new Error(String(err));
87
+ return {
88
+ content: [
89
+ {
90
+ type: "text",
91
+ text: JSON.stringify(
92
+ {
93
+ error: `Refiner returned unparseable output: ${e.message}`,
94
+ raw
95
+ },
96
+ null,
97
+ 2
98
+ )
99
+ }
100
+ ],
101
+ isError: true
102
+ };
103
+ }
104
+ const refinedMessages = Array.isArray(parsed?.messages) ? parsed.messages : null;
105
+ if (!refinedMessages) {
106
+ return {
107
+ content: [
108
+ {
109
+ type: "text",
110
+ text: JSON.stringify(
111
+ {
112
+ error: `Refiner response missing "messages" array`,
113
+ parsed
114
+ },
115
+ null,
116
+ 2
117
+ )
118
+ }
119
+ ],
120
+ isError: true
121
+ };
122
+ }
123
+ return {
124
+ content: [
125
+ {
126
+ type: "text",
127
+ text: JSON.stringify(
128
+ {
129
+ intent,
130
+ messages: refinedMessages,
131
+ errorsAddressed: validationErrors.length
132
+ },
133
+ null,
134
+ 2
135
+ )
136
+ }
137
+ ]
138
+ };
139
+ } catch (err) {
140
+ const e = err instanceof Error ? err : new Error(String(err));
141
+ return {
142
+ content: [{ type: "text", text: `refine_ui error: ${e.message}` }],
143
+ isError: true
144
+ };
145
+ }
146
+ }
147
+ );
148
+ }
149
+ export {
150
+ registerRefineTools
151
+ };
@@ -0,0 +1,131 @@
1
+ import { z } from "zod";
2
+ import { validateSchema } from "../../validator/validator.js";
3
+ import { validateMessages as validateCatalogMessages } from "../../validator/catalog-validator.js";
4
+ import {
5
+ getCatalog,
6
+ getComponent,
7
+ getTraits,
8
+ getTraitsByCategory
9
+ } from "../../retrieval/catalog.js";
10
+ import { serializeEntry } from "../../retrieval/component-entry.js";
11
+ import { getAntiPatterns, checkAllAntiPatterns } from "../../retrieval/anti-patterns.js";
12
+ import { assembleContext } from "../../retrieval/context-assembler.js";
13
+ import { transpileHTML } from "../../compose/transpiler/transpiler.js";
14
+ import { getWiringCatalog } from "../../retrieval/wiring-catalog.js";
15
+ function registerValidationTools(server) {
16
+ server.tool(
17
+ "validate_schema",
18
+ "Validate A2UI messages against schema rules.",
19
+ {
20
+ messages: z.string().describe("JSON array of A2UI messages")
21
+ },
22
+ async ({ messages }) => {
23
+ try {
24
+ const parsed = JSON.parse(messages);
25
+ const msgs = Array.isArray(parsed) ? parsed : [parsed];
26
+ const scored = validateSchema(msgs);
27
+ const catalog = await validateCatalogMessages(msgs);
28
+ const result = { ...scored, catalog };
29
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
30
+ } catch (err) {
31
+ const e = err instanceof Error ? err : new Error(String(err));
32
+ return { content: [{ type: "text", text: `Parse error: ${e.message}` }], isError: true };
33
+ }
34
+ }
35
+ );
36
+ server.tool(
37
+ "lookup_component",
38
+ "Look up a AdiaUI component by type name.",
39
+ {
40
+ type: z.string().describe('Component type (e.g., "Card", "Button")'),
41
+ level: z.enum(["index", "summary", "reference"]).optional().describe("Detail level (default: reference)")
42
+ },
43
+ async ({ type, level }) => {
44
+ const entry = await getComponent(type);
45
+ if (!entry) return { content: [{ type: "text", text: `Not found: ${type}` }], isError: true };
46
+ const serialized = serializeEntry(entry, level ?? "reference");
47
+ return { content: [{ type: "text", text: JSON.stringify(serialized, null, 2) }] };
48
+ }
49
+ );
50
+ server.tool(
51
+ "get_component_map",
52
+ "Get the full AdiaUI component catalog.",
53
+ {},
54
+ async () => {
55
+ const catalog = await getCatalog();
56
+ const summary = [...catalog.entries.values()].map((e) => {
57
+ const entry = e;
58
+ const desc = typeof entry["description"] === "string" ? entry["description"].slice(0, 80) : "";
59
+ return `${entry["type"]} -> <${entry["tag"]}>: ${desc}`;
60
+ }).join("\n");
61
+ return { content: [{ type: "text", text: summary }] };
62
+ }
63
+ );
64
+ server.tool(
65
+ "check_anti_patterns",
66
+ "Check HTML against all anti-patterns. Returns only violations.",
67
+ {
68
+ html: z.string().describe("HTML string to check")
69
+ },
70
+ async ({ html }) => {
71
+ const violations = checkAllAntiPatterns(html);
72
+ return { content: [{ type: "text", text: JSON.stringify(violations, null, 2) }] };
73
+ }
74
+ );
75
+ server.tool(
76
+ "get_traits",
77
+ "Get the trait catalog, optionally filtered by category.",
78
+ {
79
+ category: z.string().optional().describe('Trait category filter (e.g., "input-interaction", "motion-positioning")')
80
+ },
81
+ async ({ category }) => {
82
+ const traits = category ? getTraitsByCategory(category) : getTraits();
83
+ return { content: [{ type: "text", text: JSON.stringify(traits, null, 2) }] };
84
+ }
85
+ );
86
+ server.tool(
87
+ "assemble_context",
88
+ `Assemble progressive-disclosure context for a given intent and budget tier. Returns domain-relevant components, matching patterns, and anti-patterns.
89
+
90
+ Tier 0: domain only. Tier 1: components. Tier 2: +patterns. Tier 3: +anti-patterns. Tier 4: full catalog.
91
+
92
+ Use this when you want to manually compose A2UI output rather than using generate_ui. The returned context gives you the building blocks.`,
93
+ {
94
+ intent: z.string().describe("Natural language intent"),
95
+ tier: z.number().min(0).max(4).optional().describe("Budget tier 0-4 (default: 1)")
96
+ },
97
+ async ({ intent, tier }) => {
98
+ const result = assembleContext({ intent, tier: tier ?? 1 });
99
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
100
+ }
101
+ );
102
+ server.tool(
103
+ "convert_html",
104
+ "Convert HTML markup to A2UI flat adjacency component messages. Maps HTML tags to AdiaUI components, infers layout from styles, enforces Card content model.",
105
+ {
106
+ html: z.string().describe("HTML markup to convert"),
107
+ mode: z.enum(["instant", "reasoning"]).optional().describe("instant = rules only, reasoning = LLM for complex layouts")
108
+ },
109
+ async ({ html, mode }) => {
110
+ try {
111
+ const result = await transpileHTML(html, { mode: mode ?? "instant" });
112
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
113
+ } catch (err) {
114
+ const e = err instanceof Error ? err : new Error(String(err));
115
+ return { content: [{ type: "text", text: `Transpile error: ${e.message}` }], isError: true };
116
+ }
117
+ }
118
+ );
119
+ server.tool(
120
+ "get_wiring_catalog",
121
+ "Get the AdiaUI wiring catalog: available controllers, action handlers, refresh strategies, value sources, and association types.",
122
+ {},
123
+ async () => {
124
+ const catalog = getWiringCatalog();
125
+ return { content: [{ type: "text", text: JSON.stringify(catalog, null, 2) }] };
126
+ }
127
+ );
128
+ }
129
+ export {
130
+ registerValidationTools
131
+ };
@@ -0,0 +1,87 @@
1
+ import { z } from "zod";
2
+ import {
3
+ getComposition as getZettelComposition,
4
+ getAllCompositions as getAllZettelCompositions,
5
+ getGraph as getZettelGraph,
6
+ searchAll as searchCompositions
7
+ } from "../../compose/strategies/zettel/composition-library.js";
8
+ import {
9
+ resolveComposition as resolveZettelComposition,
10
+ templateToMessages as zettelTemplateToMessages
11
+ } from "../../compose/strategies/zettel/composer.js";
12
+ function registerZettelTools(server) {
13
+ server.tool(
14
+ "search_patterns",
15
+ `Search the composition library for reusable UI templates. Returns matching compositions with full A2UI component trees that can be used directly or adapted.
16
+
17
+ Use this to find a starting point before generating from scratch. If a good composition exists, pass it to generate_ui with instant mode. If no composition matches, use generate_ui with thinking mode.
18
+
19
+ Keyword search (\xA764 v0.4.6 migration: now backed by composition-library; the historical "pattern" library is retired).`,
20
+ {
21
+ query: z.string().describe("Search query (natural language)")
22
+ },
23
+ async ({ query }) => {
24
+ const results = searchCompositions(query);
25
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
26
+ }
27
+ );
28
+ server.tool(
29
+ "get_composition",
30
+ "Fetch a composition by name. Returns the flat A2UI template (compositions are pre-inlined; no $fragment refs). Zettel-only.",
31
+ { name: z.string() },
32
+ async ({ name }) => {
33
+ const c = getZettelComposition(name);
34
+ if (!c) return { content: [{ type: "text", text: `Composition not found: ${name}` }], isError: true };
35
+ return { content: [{ type: "text", text: JSON.stringify(c, null, 2) }] };
36
+ }
37
+ );
38
+ server.tool(
39
+ "resolve_composition",
40
+ "Return the flat A2UI template + updateComponents messages for a composition. Zettel-only. (Pre-inlined since \xA737 \u2014 `resolve` is now a defensive copy + strip pass.)",
41
+ { name: z.string() },
42
+ async ({ name }) => {
43
+ const c = getZettelComposition(name);
44
+ if (!c) return { content: [{ type: "text", text: `Composition not found: ${name}` }], isError: true };
45
+ const template = resolveZettelComposition(c);
46
+ const messages = zettelTemplateToMessages(template);
47
+ return {
48
+ content: [{
49
+ type: "text",
50
+ text: JSON.stringify({ template, messages }, null, 2)
51
+ }]
52
+ };
53
+ }
54
+ );
55
+ server.tool(
56
+ "get_graph",
57
+ "Return the composition catalog. Zettel-only. (Backlinks to fragments retired in \xA737; only composition nodes remain.)",
58
+ {},
59
+ async () => ({ content: [{ type: "text", text: JSON.stringify(getZettelGraph(), null, 2) }] })
60
+ );
61
+ server.tool(
62
+ "zettel_stats",
63
+ "Zettel corpus stats \u2014 composition count + average node count. (Fragment stats retired in \xA737.)",
64
+ {},
65
+ async () => {
66
+ const comps = getAllZettelCompositions();
67
+ const totalNodes = comps.reduce((s, c) => {
68
+ const comp = c;
69
+ const tpl = comp["template"];
70
+ return s + (Array.isArray(tpl) ? tpl.length : 0);
71
+ }, 0);
72
+ return {
73
+ content: [{
74
+ type: "text",
75
+ text: JSON.stringify({
76
+ compositions: comps.length,
77
+ avg_nodes_per_composition: comps.length ? Math.round(totalNodes / comps.length) : 0,
78
+ total_nodes: totalNodes
79
+ }, null, 2)
80
+ }]
81
+ };
82
+ }
83
+ );
84
+ }
85
+ export {
86
+ registerZettelTools
87
+ };