@adia-ai/a2ui-mcp 0.6.4 → 0.6.6
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/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/server.js +110 -547
- package/tools/corpus.js +103 -0
- package/tools/corpus.ts +112 -0
- package/tools/feedback.js +63 -0
- package/tools/feedback.ts +73 -0
- package/tools/synthesis.js +143 -174
- package/tools/validation.js +131 -0
- package/tools/validation.ts +153 -0
- package/tools/zettel.js +87 -0
- package/tools/zettel.ts +98 -0
package/tools/corpus.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
getChunk as getGenUIChunk,
|
|
4
|
+
lookupChunksByPrimary,
|
|
5
|
+
searchChunks as searchGenUIChunks
|
|
6
|
+
} from "../../corpus/scripts/chunk-library.js";
|
|
7
|
+
function registerCorpusTools(server) {
|
|
8
|
+
server.tool(
|
|
9
|
+
"search_chunks",
|
|
10
|
+
`Search the gen-UI training-chunk corpus by keyword.
|
|
11
|
+
|
|
12
|
+
The chunk corpus comes from \`packages/a2ui/corpus/chunks/\` \u2014 JSON records
|
|
13
|
+
extracted from every \`[data-chunk]\` element in site/pages/* and the corpus
|
|
14
|
+
exemplars. There are three kinds:
|
|
15
|
+
- block (default): atomic UI fragment (KPI grid, sign-in form, table)
|
|
16
|
+
- panel: tab-panel fragment of a page (e.g. dashboard-overview-panel)
|
|
17
|
+
- page: full-page composition (e.g. dashboard-admin-page)
|
|
18
|
+
|
|
19
|
+
Returns ranked candidates with chunk name, kind, primary tag, and a relevance
|
|
20
|
+
score. Use \`get_chunk\` to fetch the full record (HTML + slot bindings + nested
|
|
21
|
+
chunks) for a specific name.`,
|
|
22
|
+
{
|
|
23
|
+
query: z.string().describe("Keyword query \u2014 chunk name fragment, intent words, primary-tag name"),
|
|
24
|
+
kind: z.enum(["block", "panel", "page"]).optional().describe("Filter by chunk kind"),
|
|
25
|
+
limit: z.number().int().min(1).max(50).default(20).describe("Max results")
|
|
26
|
+
},
|
|
27
|
+
async ({ query, kind, limit }) => {
|
|
28
|
+
const results = searchGenUIChunks(query, { kind, limit });
|
|
29
|
+
return {
|
|
30
|
+
content: [{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: JSON.stringify({ query, kind: kind ?? "any", count: results.length, results }, null, 2)
|
|
33
|
+
}]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
server.tool(
|
|
38
|
+
"get_chunk",
|
|
39
|
+
`Fetch the full record for a single gen-UI training chunk by name.
|
|
40
|
+
|
|
41
|
+
Returns the chunk's bounding HTML, slot annotations, nested chunk names, and
|
|
42
|
+
metadata (primary tag, kind, source page). For chunks that appear on multiple
|
|
43
|
+
pages (reusable slot chunks like \`auth-card-header\`, \`reg-step-header\`),
|
|
44
|
+
returns an \`instances\` array \u2014 one entry per page where the chunk appears.
|
|
45
|
+
|
|
46
|
+
The HTML is suitable for direct rendering / inclusion in an A2UI message
|
|
47
|
+
construction prompt.`,
|
|
48
|
+
{
|
|
49
|
+
name: z.string().describe('The chunk name, e.g. "dashboard-kpi-grid", "auth-signin-card-email", "code-language"')
|
|
50
|
+
},
|
|
51
|
+
async ({ name }) => {
|
|
52
|
+
const rec = getGenUIChunk(name);
|
|
53
|
+
if (!rec) {
|
|
54
|
+
return {
|
|
55
|
+
isError: true,
|
|
56
|
+
content: [{ type: "text", text: JSON.stringify({ error: "chunk not found", name }, null, 2) }]
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return { content: [{ type: "text", text: JSON.stringify(rec, null, 2) }] };
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
server.tool(
|
|
63
|
+
"lookup_chunk",
|
|
64
|
+
`List every chunk whose primary element is \`<component_name>\`.
|
|
65
|
+
|
|
66
|
+
Useful for "show me every page that opens with a \`<card-ui raw>\`" or "every
|
|
67
|
+
chunk built around a \`<grid-ui>\` root." Returns chunk names + kinds + sources.
|
|
68
|
+
|
|
69
|
+
Pair with \`get_chunk\` to fetch full records for any of the returned names.`,
|
|
70
|
+
{
|
|
71
|
+
component_name: z.string().describe('Component tag name, e.g. "card-ui", "grid-ui", "drawer-ui"')
|
|
72
|
+
},
|
|
73
|
+
async ({ component_name }) => {
|
|
74
|
+
const recs = lookupChunksByPrimary(component_name);
|
|
75
|
+
return {
|
|
76
|
+
content: [{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: JSON.stringify({
|
|
79
|
+
component: component_name,
|
|
80
|
+
count: recs.length,
|
|
81
|
+
chunks: recs.map((r) => {
|
|
82
|
+
const rec = r;
|
|
83
|
+
const instances = rec["instances"];
|
|
84
|
+
const firstInstance = instances?.[0];
|
|
85
|
+
const slots = rec["slots"] ?? firstInstance?.["slots"] ?? [];
|
|
86
|
+
const nested = rec["nested"] ?? firstInstance?.["nested"] ?? [];
|
|
87
|
+
return {
|
|
88
|
+
name: rec["name"],
|
|
89
|
+
kind: rec["kind"],
|
|
90
|
+
page: rec["page"] ?? firstInstance?.["page"],
|
|
91
|
+
slots: slots.map((s) => s.name),
|
|
92
|
+
nested
|
|
93
|
+
};
|
|
94
|
+
})
|
|
95
|
+
}, null, 2)
|
|
96
|
+
}]
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
registerCorpusTools
|
|
103
|
+
};
|
package/tools/corpus.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus tools — gen-UI training-chunk search and retrieval.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from server.ts. Registers:
|
|
5
|
+
* search_chunks, get_chunk, lookup_chunk
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
getChunk as getGenUIChunk,
|
|
13
|
+
lookupChunksByPrimary,
|
|
14
|
+
searchChunks as searchGenUIChunks,
|
|
15
|
+
} from '../../corpus/scripts/chunk-library.js';
|
|
16
|
+
|
|
17
|
+
export function registerCorpusTools(server: McpServer): void {
|
|
18
|
+
server.tool(
|
|
19
|
+
'search_chunks',
|
|
20
|
+
`Search the gen-UI training-chunk corpus by keyword.
|
|
21
|
+
|
|
22
|
+
The chunk corpus comes from \`packages/a2ui/corpus/chunks/\` — JSON records
|
|
23
|
+
extracted from every \`[data-chunk]\` element in site/pages/* and the corpus
|
|
24
|
+
exemplars. There are three kinds:
|
|
25
|
+
- block (default): atomic UI fragment (KPI grid, sign-in form, table)
|
|
26
|
+
- panel: tab-panel fragment of a page (e.g. dashboard-overview-panel)
|
|
27
|
+
- page: full-page composition (e.g. dashboard-admin-page)
|
|
28
|
+
|
|
29
|
+
Returns ranked candidates with chunk name, kind, primary tag, and a relevance
|
|
30
|
+
score. Use \`get_chunk\` to fetch the full record (HTML + slot bindings + nested
|
|
31
|
+
chunks) for a specific name.`,
|
|
32
|
+
{
|
|
33
|
+
query: z.string().describe('Keyword query — chunk name fragment, intent words, primary-tag name'),
|
|
34
|
+
kind: z.enum(['block', 'panel', 'page']).optional().describe('Filter by chunk kind'),
|
|
35
|
+
limit: z.number().int().min(1).max(50).default(20).describe('Max results'),
|
|
36
|
+
},
|
|
37
|
+
async ({ query, kind, limit }) => {
|
|
38
|
+
const results = searchGenUIChunks(query, { kind, limit });
|
|
39
|
+
return {
|
|
40
|
+
content: [{
|
|
41
|
+
type: 'text',
|
|
42
|
+
text: JSON.stringify({ query, kind: kind ?? 'any', count: results.length, results }, null, 2),
|
|
43
|
+
}],
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
server.tool(
|
|
49
|
+
'get_chunk',
|
|
50
|
+
`Fetch the full record for a single gen-UI training chunk by name.
|
|
51
|
+
|
|
52
|
+
Returns the chunk's bounding HTML, slot annotations, nested chunk names, and
|
|
53
|
+
metadata (primary tag, kind, source page). For chunks that appear on multiple
|
|
54
|
+
pages (reusable slot chunks like \`auth-card-header\`, \`reg-step-header\`),
|
|
55
|
+
returns an \`instances\` array — one entry per page where the chunk appears.
|
|
56
|
+
|
|
57
|
+
The HTML is suitable for direct rendering / inclusion in an A2UI message
|
|
58
|
+
construction prompt.`,
|
|
59
|
+
{
|
|
60
|
+
name: z.string().describe('The chunk name, e.g. "dashboard-kpi-grid", "auth-signin-card-email", "code-language"'),
|
|
61
|
+
},
|
|
62
|
+
async ({ name }) => {
|
|
63
|
+
const rec = getGenUIChunk(name);
|
|
64
|
+
if (!rec) {
|
|
65
|
+
return {
|
|
66
|
+
isError: true,
|
|
67
|
+
content: [{ type: 'text', text: JSON.stringify({ error: 'chunk not found', name }, null, 2) }],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { content: [{ type: 'text', text: JSON.stringify(rec, null, 2) }] };
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
server.tool(
|
|
75
|
+
'lookup_chunk',
|
|
76
|
+
`List every chunk whose primary element is \`<component_name>\`.
|
|
77
|
+
|
|
78
|
+
Useful for "show me every page that opens with a \`<card-ui raw>\`" or "every
|
|
79
|
+
chunk built around a \`<grid-ui>\` root." Returns chunk names + kinds + sources.
|
|
80
|
+
|
|
81
|
+
Pair with \`get_chunk\` to fetch full records for any of the returned names.`,
|
|
82
|
+
{
|
|
83
|
+
component_name: z.string().describe('Component tag name, e.g. "card-ui", "grid-ui", "drawer-ui"'),
|
|
84
|
+
},
|
|
85
|
+
async ({ component_name }) => {
|
|
86
|
+
const recs = lookupChunksByPrimary(component_name);
|
|
87
|
+
return {
|
|
88
|
+
content: [{
|
|
89
|
+
type: 'text',
|
|
90
|
+
text: JSON.stringify({
|
|
91
|
+
component: component_name,
|
|
92
|
+
count: recs.length,
|
|
93
|
+
chunks: recs.map((r) => {
|
|
94
|
+
const rec = r as Record<string, unknown>;
|
|
95
|
+
const instances = rec['instances'] as Array<Record<string, unknown>> | undefined;
|
|
96
|
+
const firstInstance = instances?.[0];
|
|
97
|
+
const slots = (rec['slots'] ?? firstInstance?.['slots'] ?? []) as Array<{ name: string }>;
|
|
98
|
+
const nested = (rec['nested'] ?? firstInstance?.['nested'] ?? []) as unknown[];
|
|
99
|
+
return {
|
|
100
|
+
name: rec['name'],
|
|
101
|
+
kind: rec['kind'],
|
|
102
|
+
page: rec['page'] ?? firstInstance?.['page'],
|
|
103
|
+
slots: slots.map((s) => s.name),
|
|
104
|
+
nested,
|
|
105
|
+
};
|
|
106
|
+
}),
|
|
107
|
+
}, null, 2),
|
|
108
|
+
}],
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
}
|
|
@@ -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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback tools — generation feedback collection and quality metrics.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from server.ts. Registers:
|
|
5
|
+
* submit_feedback, get_quality_metrics, get_training_gaps
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
|
+
|
|
11
|
+
import { FeedbackCollector } from '../../retrieval/feedback/feedback.js';
|
|
12
|
+
import { feedbackStore } from '../../retrieval/feedback/feedback-store.js';
|
|
13
|
+
|
|
14
|
+
const feedbackCollector = new FeedbackCollector();
|
|
15
|
+
|
|
16
|
+
export function registerFeedbackTools(server: McpServer): void {
|
|
17
|
+
server.tool(
|
|
18
|
+
'submit_feedback',
|
|
19
|
+
'Submit structured feedback for a generation execution. Used by the evolution engine to learn from each generation.',
|
|
20
|
+
{
|
|
21
|
+
executionId: z.string().describe('Execution ID from generate_ui'),
|
|
22
|
+
rating: z.number().min(1).max(5).describe('Overall quality 1-5'),
|
|
23
|
+
intent: z.string().optional(),
|
|
24
|
+
domain: z.string().optional(),
|
|
25
|
+
intentAlignment: z.number().min(1).max(5).optional(),
|
|
26
|
+
visualQuality: z.number().min(1).max(5).optional(),
|
|
27
|
+
componentChoice: z.number().min(1).max(5).optional(),
|
|
28
|
+
userEdited: z.boolean().optional(),
|
|
29
|
+
editSummary: z.string().optional(),
|
|
30
|
+
notes: z.string().optional(),
|
|
31
|
+
shouldBePattern: z.boolean().optional(),
|
|
32
|
+
suggestedName: z.string().optional(),
|
|
33
|
+
},
|
|
34
|
+
async (args) => {
|
|
35
|
+
feedbackCollector.collectFeedback(args.executionId, {
|
|
36
|
+
rating: args.rating,
|
|
37
|
+
intentAlignment: args.intentAlignment,
|
|
38
|
+
visualQuality: args.visualQuality,
|
|
39
|
+
componentChoice: args.componentChoice,
|
|
40
|
+
userEdited: args.userEdited,
|
|
41
|
+
editSummary: args.editSummary,
|
|
42
|
+
notes: args.notes,
|
|
43
|
+
});
|
|
44
|
+
if (args.shouldBePattern != null) {
|
|
45
|
+
feedbackCollector.collectPatternFeedback(args.executionId, {
|
|
46
|
+
shouldBePattern: args.shouldBePattern,
|
|
47
|
+
suggestedName: args.suggestedName,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return { content: [{ type: 'text', text: JSON.stringify({ recorded: true, executionId: args.executionId, totalEntries: feedbackCollector.size }) }] };
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
server.tool(
|
|
55
|
+
'get_quality_metrics',
|
|
56
|
+
'Get aggregated quality metrics from the feedback store: avg score, thumb-up rate, per-domain breakdown, training gaps.',
|
|
57
|
+
{},
|
|
58
|
+
async () => {
|
|
59
|
+
const metrics = await feedbackStore.getQualityMetrics();
|
|
60
|
+
return { content: [{ type: 'text', text: JSON.stringify(metrics, null, 2) }] };
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
server.tool(
|
|
65
|
+
'get_training_gaps',
|
|
66
|
+
'Get training gap signals from LLM self-critique: missing patterns, weak domain keywords, component gaps.',
|
|
67
|
+
{},
|
|
68
|
+
async () => {
|
|
69
|
+
const gaps = await feedbackStore.getGapSummary();
|
|
70
|
+
return { content: [{ type: 'text', text: JSON.stringify(gaps, null, 2) }] };
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|