@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/server.js CHANGED
@@ -1,115 +1,56 @@
1
1
  #!/usr/bin/env node
2
-
3
- /**
4
- * AdiaUI MCP Server
5
- *
6
- * Exposes A2UI generative tools via MCP protocol.
7
- * Runs as a stdio server for Claude Desktop / Claude Code.
8
- *
9
- * Tools:
10
- * generate_ui — Generate A2UI components from intent
11
- * validate_schema — Validate A2UI messages
12
- * lookup_component — Component API lookup
13
- * get_component_map — Full catalog
14
- * search_patterns — Composition library search (kept for back-compat; backed by composition-library since §64)
15
- * classify_intent — Domain classification
16
- *
17
- * Usage:
18
- * node packages/a2ui/mcp/server.js
19
- */
20
-
21
- // ── Load .env for API keys (Node doesn't read .env automatically) ──
22
- import '../../../scripts/load-env.mjs';
23
-
24
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
25
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
26
- import { z } from 'zod';
27
-
28
- // ── Import from a2ui ──
29
- import { generateUI } from '../compose/core/generator.js';
30
- import { validateSchema } from '../validator/validator.js';
31
- import { validateMessages as validateCatalogMessages } from '../validator/catalog-validator.js';
2
+ import "../../../scripts/load-env.mjs";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ import { generateUI } from "../compose/core/generator.js";
32
7
  import {
33
8
  getCatalog,
34
- getComponent,
35
- getComponentsByCategory,
36
- getTraits,
37
- getTraitsByCategory,
38
9
  getFullCatalog,
39
- } from '../retrieval/catalog.js';
40
- import { serializeEntry } from '../retrieval/component-entry.js';
41
- import { classifyIntent, getDomain, getAllDomains } from '../retrieval/domain-router.js';
42
- import { getAntiPatterns, checkAllAntiPatterns } from '../retrieval/anti-patterns.js';
43
- import { assembleContext } from '../retrieval/context-assembler.js';
44
-
45
- // ── Zettel (composition) engine ──
10
+ getTraits
11
+ } from "../retrieval/catalog.js";
12
+ import { serializeEntry } from "../retrieval/component-entry.js";
13
+ import { classifyIntent, getDomain, getAllDomains } from "../retrieval/domain-router.js";
14
+ import { getAntiPatterns } from "../retrieval/anti-patterns.js";
46
15
  import {
47
16
  loadAll as loadZettelCorpus,
48
- getComposition as getZettelComposition,
49
- getAllCompositions as getAllZettelCompositions,
50
- getGraph as getZettelGraph,
51
- searchAll as searchCompositions,
52
- } from '../compose/strategies/zettel/composition-library.js';
53
- import {
54
- resolveComposition as resolveZettelComposition,
55
- templateToMessages as zettelTemplateToMessages,
56
- } from '../compose/strategies/zettel/composer.js';
57
- // Zettel bootstrap is still needed for get_fragment/resolve_composition tools;
58
- // the generate_ui tool now dispatches through the unified registry in gen-ui.
59
-
60
- // Bootstrap zettel composition corpus
17
+ getAllCompositions as getAllZettelCompositions
18
+ } from "../compose/strategies/zettel/composition-library.js";
61
19
  const _zettelBoot = loadZettelCorpus();
62
20
  console.error(
63
- `[adiaui-mcp] zettel corpus: ${_zettelBoot.compositionCount} compositions`,
21
+ `[adiaui-mcp] zettel corpus: ${_zettelBoot.compositionCount} compositions`
64
22
  );
65
-
66
- // ── Gen-UI training-chunk corpus ──
67
- import {
68
- getChunk as getGenUIChunk,
69
- getChunkIndex,
70
- lookupChunksByPrimary,
71
- searchChunks as searchGenUIChunks,
72
- } from '../corpus/scripts/chunk-library.js';
73
-
74
- // ── Inline-tool deps (transpiler / wiring / feedback) ──
75
- import { transpileHTML } from '../compose/transpiler/transpiler.js';
76
- import { getWiringCatalog } from '../retrieval/wiring-catalog.js';
77
- import { FeedbackCollector } from '../retrieval/feedback/feedback.js';
78
- import { feedbackStore } from '../retrieval/feedback/feedback-store.js';
79
-
80
- // ── Tools extracted to tools/ for modularity ──
81
- import { registerSynthesisTools } from './tools/synthesis.js';
82
-
23
+ import { getChunkIndex } from "../corpus/scripts/chunk-library.js";
83
24
  const _chunkIndex = getChunkIndex();
84
25
  if (_chunkIndex) {
26
+ const idx = _chunkIndex;
27
+ const byKind = idx["by_kind"] ?? {};
85
28
  console.error(
86
- `[adiaui-mcp] gen-ui chunks: ${_chunkIndex.unique_names} unique chunks (${_chunkIndex.total_instances} instances; block=${_chunkIndex.by_kind.block || 0}, panel=${_chunkIndex.by_kind.panel || 0}, page=${_chunkIndex.by_kind.page || 0})`,
29
+ `[adiaui-mcp] gen-ui chunks: ${idx["unique_names"]} unique chunks (${idx["total_instances"]} instances; block=${byKind["block"] ?? 0}, panel=${byKind["panel"] ?? 0}, page=${byKind["page"] ?? 0})`
87
30
  );
88
31
  } else {
89
- console.error('[adiaui-mcp] gen-ui chunks: index not found run `npm run harvest:chunks`');
32
+ console.error("[adiaui-mcp] gen-ui chunks: index not found \u2014 run `npm run harvest:chunks`");
90
33
  }
91
-
92
- // ── Server ──
93
-
34
+ import { registerSynthesisTools } from "./tools/synthesis.js";
35
+ import { registerValidationTools } from "./tools/validation.js";
36
+ import { registerFeedbackTools } from "./tools/feedback.js";
37
+ import { registerCorpusTools } from "./tools/corpus.js";
38
+ import { registerZettelTools } from "./tools/zettel.js";
94
39
  const server = new McpServer({
95
- name: 'adia-ui',
96
- version: '0.1.0',
40
+ name: "adia-ui",
41
+ version: "0.1.0"
97
42
  });
98
-
99
43
  server.tool(
100
- 'plan_app_state',
44
+ "plan_app_state",
101
45
  `Analyze a natural language prompt and extract the top-level Generative UI Ontology structures (Intent, Domain, Tasks, Experience).
102
46
 
103
47
  Use this tool BEFORE generating UI to ensure you have walked the Reasoning Ladder and properly modeled the nouns and verbs of the feature. This bounds hallucination and forces a focus on tasks over raw layouts.`,
104
48
  {
105
- prompt: z.string().describe('The natural language request (e.g., "Build a dashboard for incoming sales leads")'),
49
+ prompt: z.string().describe('The natural language request (e.g., "Build a dashboard for incoming sales leads")')
106
50
  },
107
51
  async ({ prompt }) => {
108
- // We utilize the LLM directly here to act as the reasoning gate.
109
- // This is a minimal, fast call designed to output structured JSON.
110
- const { createAdapter } = await import('../../llm/llm-bridge.js');
52
+ const { createAdapter } = await import("../../llm/llm-bridge.js");
111
53
  const llm = await createAdapter();
112
-
113
54
  const systemPrompt = `You are the A2UI Ontology Planner.
114
55
  Given a user prompt, you must extract the Core App State using the 5-Gate Reasoning Ladder.
115
56
 
@@ -132,59 +73,55 @@ Output ONLY a JSON object matching this schema, nothing else:
132
73
  "shell": "admin-shell | chat-shell | a2ui-root"
133
74
  }
134
75
  }`;
135
-
136
76
  try {
137
77
  const response = await llm.complete({
138
- messages: [{ role: 'user', content: prompt }],
78
+ messages: [{ role: "user", content: prompt }],
139
79
  system: systemPrompt,
140
80
  temperature: 0.2
141
81
  });
142
-
143
- let jsonMatch = response.text.match(/\{[\s\S]*\}/);
82
+ const jsonMatch = response.content.match(/\{[\s\S]*\}/);
144
83
  if (!jsonMatch) {
145
- throw new Error("LLM failed to output valid JSON for the ontology plan.");
84
+ throw new Error("LLM failed to output valid JSON for the ontology plan.");
146
85
  }
147
-
148
86
  const plan = JSON.parse(jsonMatch[0]);
149
-
150
87
  return {
151
88
  content: [
152
89
  {
153
- type: 'text',
154
- text: JSON.stringify(plan, null, 2),
155
- },
156
- ],
90
+ type: "text",
91
+ text: JSON.stringify(plan, null, 2)
92
+ }
93
+ ]
157
94
  };
158
95
  } catch (e) {
96
+ const err = e instanceof Error ? e : new Error(String(e));
159
97
  return {
160
98
  content: [
161
- { type: 'text', text: `Failed to plan app state: ${e.message}` }
99
+ { type: "text", text: `Failed to plan app state: ${err.message}` }
162
100
  ],
163
- isError: true,
164
- }
101
+ isError: true
102
+ };
165
103
  }
166
104
  }
167
105
  );
168
-
169
106
  server.tool(
170
- 'generate_ui',
107
+ "generate_ui",
171
108
  `Generate A2UI components from a natural language description.
172
109
 
173
110
  Engine selection:
174
- - "monolithic" (default) pattern-match + adapt. Searches a corpus of 96+ pre-authored monolithic templates and adapts the best match. Battle-tested; highest F1 on held-out intents.
175
- - "zettel" fragment-graph composer. Composes UI from named atomic fragments (labeled-input, card-header-with-description, etc.) with a precomputed backlink graph. Higher reusability; supports composition-iterated refinement on multi-turn edits.
111
+ - "monolithic" (default) \u2014 pattern-match + adapt. Searches a corpus of 96+ pre-authored monolithic templates and adapts the best match. Battle-tested; highest F1 on held-out intents.
112
+ - "zettel" \u2014 fragment-graph composer. Composes UI from named atomic fragments (labeled-input, card-header-with-description, etc.) with a precomputed backlink graph. Higher reusability; supports composition-iterated refinement on multi-turn edits.
176
113
 
177
114
  Mode selection (monolithic only; zettel uses "instant"):
178
- - "pro" (default) LLM-powered generation with pattern adaptation.
179
- - "thinking" Full LLM-powered generation with semantic search, decomposition, and streaming.
180
- - "instant" fast pattern matching, no LLM.
115
+ - "pro" (default) \u2014 LLM-powered generation with pattern adaptation.
116
+ - "thinking" \u2014 Full LLM-powered generation with semantic search, decomposition, and streaming.
117
+ - "instant" \u2014 fast pattern matching, no LLM.
181
118
 
182
119
  The generator knows 96+ UI patterns across 5 domains: forms, data, layout, agent, navigation.`,
183
120
  {
184
- intent: z.string().describe('Description of the UI to generate'),
185
- engine: z.enum(['monolithic', 'zettel']).optional().describe('Generation engine. "monolithic" (default) is pattern-match + adapt. "zettel" is fragment-graph composition.'),
186
- mode: z.enum(['instant', 'pro', 'thinking']).optional().describe('Generation mode (monolithic). "pro" (default) uses LLM with pattern adaptation. "thinking" uses full LLM generation. "instant" uses fast pattern matching.'),
187
- sessionId: z.string().optional().describe('Opaque session identifier for multi-turn iteration (zettel only). When provided, follow-up calls with the same sessionId modify the prior turn\'s canvas instead of regenerating from scratch. Omit for stateless generation.'),
121
+ intent: z.string().describe("Description of the UI to generate"),
122
+ engine: z.enum(["monolithic", "zettel"]).optional().describe('Generation engine. "monolithic" (default) is pattern-match + adapt. "zettel" is fragment-graph composition.'),
123
+ mode: z.enum(["instant", "pro", "thinking"]).optional().describe('Generation mode (monolithic). "pro" (default) uses LLM with pattern adaptation. "thinking" uses full LLM generation. "instant" uses fast pattern matching.'),
124
+ sessionId: z.string().optional().describe("Opaque session identifier for multi-turn iteration (zettel only). When provided, follow-up calls with the same sessionId modify the prior turn's canvas instead of regenerating from scratch. Omit for stateless generation."),
188
125
  context: z.object({
189
126
  domain: z.object({
190
127
  entities: z.array(z.string()).optional(),
@@ -194,519 +131,145 @@ The generator knows 96+ UI patterns across 5 domains: forms, data, layout, agent
194
131
  primary: z.array(z.string()).optional(),
195
132
  inspection: z.array(z.string()).optional()
196
133
  }).optional()
197
- }).optional().describe('Ontology context parsed by plan_app_state. If provided, the generator uses these constraints to bound hallucinations and strictly align components with domain tasks.')
134
+ }).optional().describe("Ontology context parsed by plan_app_state. If provided, the generator uses these constraints to bound hallucinations and strictly align components with domain tasks.")
198
135
  },
199
136
  async ({ intent, engine, mode, sessionId, context }) => {
200
137
  try {
201
- const selectedEngine = engine || 'monolithic';
202
- const effectiveMode = selectedEngine === 'zettel' ? 'instant' : (mode || 'pro');
138
+ const selectedEngine = engine ?? "monolithic";
139
+ const effectiveMode = selectedEngine === "zettel" ? "instant" : mode ?? "pro";
203
140
  const result = await generateUI({
204
141
  intent,
205
142
  engine: selectedEngine,
206
143
  mode: effectiveMode,
207
144
  sessionId,
208
- context, // Pass the ontology context down to the composer
145
+ context
146
+ // Pass the ontology context down to the composer
209
147
  });
210
148
  return {
211
149
  content: [{
212
- type: 'text',
150
+ type: "text",
213
151
  text: JSON.stringify({
214
- engine: result.engine || selectedEngine,
215
- executionId: result.executionId,
216
- strategy: result.strategy,
217
- messages: result.messages,
218
- validation: result.validation,
219
- suggestions: result.suggestions,
220
- composition: result.composition,
221
- fragments_used: result.fragments_used,
222
- sessionTurns: result.sessionTurns,
223
- }, null, 2),
224
- }],
152
+ engine: result["engine"] ?? selectedEngine,
153
+ executionId: result["executionId"],
154
+ strategy: result["strategy"],
155
+ messages: result["messages"],
156
+ validation: result["validation"],
157
+ suggestions: result["suggestions"],
158
+ composition: result["composition"],
159
+ fragments_used: result["fragments_used"],
160
+ sessionTurns: result["sessionTurns"]
161
+ }, null, 2)
162
+ }]
225
163
  };
226
164
  } catch (err) {
227
- return { content: [{ type: 'text', text: `Generation error: ${err.message}` }], isError: true };
165
+ const e = err instanceof Error ? err : new Error(String(err));
166
+ return { content: [{ type: "text", text: `Generation error: ${e.message}` }], isError: true };
228
167
  }
229
168
  }
230
169
  );
231
-
232
170
  server.tool(
233
- 'validate_schema',
234
- 'Validate A2UI messages against schema rules.',
171
+ "classify_intent",
172
+ "Classify intent into a UI domain.",
235
173
  {
236
- messages: z.string().describe('JSON array of A2UI messages'),
237
- },
238
- async ({ messages }) => {
239
- try {
240
- const parsed = JSON.parse(messages);
241
- const msgs = Array.isArray(parsed) ? parsed : [parsed];
242
- // Two orthogonal checks:
243
- // 1. scored — weighted heuristic validator (intent alignment, card model, etc.)
244
- // 2. catalog — AJV against v0.9 catalog schema (type-level structural correctness)
245
- // Both run; results returned together so callers see structural + quality signal.
246
- const scored = validateSchema(msgs);
247
- const catalog = await validateCatalogMessages(msgs);
248
- const result = { ...scored, catalog };
249
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
250
- } catch (err) {
251
- return { content: [{ type: 'text', text: `Parse error: ${err.message}` }], isError: true };
252
- }
253
- }
254
- );
255
-
256
- server.tool(
257
- 'lookup_component',
258
- 'Look up a AdiaUI component by type name.',
259
- {
260
- type: z.string().describe('Component type (e.g., "Card", "Button")'),
261
- level: z.enum(['index', 'summary', 'reference']).optional().describe('Detail level (default: reference)'),
262
- },
263
- async ({ type, level }) => {
264
- const entry = await getComponent(type);
265
- if (!entry) return { content: [{ type: 'text', text: `Not found: ${type}` }], isError: true };
266
- const serialized = serializeEntry(entry, level || 'reference');
267
- return { content: [{ type: 'text', text: JSON.stringify(serialized, null, 2) }] };
268
- }
269
- );
270
-
271
- server.tool(
272
- 'get_component_map',
273
- 'Get the full AdiaUI component catalog.',
274
- {},
275
- async () => {
276
- const catalog = await getCatalog();
277
- const summary = [...catalog.entries.values()]
278
- .map(e => `${e.type} -> <${e.tag}>: ${e.description?.slice(0, 80) || ''}`)
279
- .join('\n');
280
- return { content: [{ type: 'text', text: summary }] };
281
- }
282
- );
283
-
284
- server.tool(
285
- 'search_patterns',
286
- `Search the composition library for reusable UI templates. Returns matching compositions with full A2UI component trees that can be used directly or adapted.
287
-
288
- 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.
289
-
290
- Keyword search (§64 v0.4.6 migration: now backed by composition-library; the historical "pattern" library is retired).`,
291
- {
292
- query: z.string().describe('Search query (natural language)'),
293
- },
294
- async ({ query }) => {
295
- const results = searchCompositions(query);
296
- return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
297
- }
298
- );
299
-
300
- server.tool(
301
- 'classify_intent',
302
- 'Classify intent into a UI domain.',
303
- {
304
- text: z.string().describe('Intent text'),
174
+ text: z.string().describe("Intent text")
305
175
  },
306
176
  async ({ text }) => {
307
- return { content: [{ type: 'text', text: JSON.stringify(classifyIntent(text), null, 2) }] };
308
- }
309
- );
310
-
311
- server.tool(
312
- 'assemble_context',
313
- `Assemble progressive-disclosure context for a given intent and budget tier. Returns domain-relevant components, matching patterns, and anti-patterns.
314
-
315
- Tier 0: domain only. Tier 1: components. Tier 2: +patterns. Tier 3: +anti-patterns. Tier 4: full catalog.
316
-
317
- Use this when you want to manually compose A2UI output rather than using generate_ui. The returned context gives you the building blocks.`,
318
- {
319
- intent: z.string().describe('Natural language intent'),
320
- tier: z.number().min(0).max(4).optional().describe('Budget tier 0-4 (default: 1)'),
321
- },
322
- async ({ intent, tier }) => {
323
- const result = assembleContext({ intent, tier: tier ?? 1 });
324
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
325
- }
326
- );
327
-
328
- server.tool(
329
- 'check_anti_patterns',
330
- 'Check HTML against all anti-patterns. Returns only violations.',
331
- {
332
- html: z.string().describe('HTML string to check'),
333
- },
334
- async ({ html }) => {
335
- const violations = checkAllAntiPatterns(html);
336
- return { content: [{ type: 'text', text: JSON.stringify(violations, null, 2) }] };
177
+ return { content: [{ type: "text", text: JSON.stringify(classifyIntent(text), null, 2) }] };
337
178
  }
338
179
  );
339
-
340
180
  server.tool(
341
- 'get_traits',
342
- 'Get the trait catalog, optionally filtered by category.',
181
+ "run_eval",
182
+ "Run the offline eval harness against the held-out intent set. Returns aggregate scores and per-intent results.",
343
183
  {
344
- category: z.string().optional().describe('Trait category filter (e.g., "input-interaction", "motion-positioning")'),
345
- },
346
- async ({ category }) => {
347
- const traits = category ? getTraitsByCategory(category) : getTraits();
348
- return { content: [{ type: 'text', text: JSON.stringify(traits, null, 2) }] };
349
- }
350
- );
351
-
352
- // ── Transpiler & Wiring Tools ──
353
-
354
- server.tool(
355
- 'convert_html',
356
- 'Convert HTML markup to A2UI flat adjacency component messages. Maps HTML tags to AdiaUI components, infers layout from styles, enforces Card content model.',
357
- {
358
- html: z.string().describe('HTML markup to convert'),
359
- mode: z.enum(['instant', 'reasoning']).optional().describe('instant = rules only, reasoning = LLM for complex layouts'),
360
- },
361
- async ({ html, mode }) => {
362
- try {
363
- const result = await transpileHTML(html, { mode: mode || 'instant' });
364
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
365
- } catch (err) {
366
- return { content: [{ type: 'text', text: `Transpile error: ${err.message}` }], isError: true };
367
- }
368
- }
369
- );
370
-
371
- server.tool(
372
- 'get_wiring_catalog',
373
- 'Get the AdiaUI wiring catalog: available controllers, action handlers, refresh strategies, value sources, and association types.',
374
- {},
375
- async () => {
376
- const catalog = getWiringCatalog();
377
- return { content: [{ type: 'text', text: JSON.stringify(catalog, null, 2) }] };
378
- }
379
- );
380
-
381
- // ── Feedback Tools ──
382
-
383
- const feedbackCollector = new FeedbackCollector();
384
-
385
- server.tool(
386
- 'submit_feedback',
387
- 'Submit structured feedback for a generation execution. Used by the evolution engine to learn from each generation.',
388
- {
389
- executionId: z.string().describe('Execution ID from generate_ui'),
390
- rating: z.number().min(1).max(5).describe('Overall quality 1-5'),
391
- intent: z.string().optional(),
392
- domain: z.string().optional(),
393
- intentAlignment: z.number().min(1).max(5).optional(),
394
- visualQuality: z.number().min(1).max(5).optional(),
395
- componentChoice: z.number().min(1).max(5).optional(),
396
- userEdited: z.boolean().optional(),
397
- editSummary: z.string().optional(),
398
- notes: z.string().optional(),
399
- shouldBePattern: z.boolean().optional(),
400
- suggestedName: z.string().optional(),
401
- },
402
- async (args) => {
403
- feedbackCollector.collectFeedback(args.executionId, {
404
- rating: args.rating,
405
- intentAlignment: args.intentAlignment,
406
- visualQuality: args.visualQuality,
407
- componentChoice: args.componentChoice,
408
- userEdited: args.userEdited,
409
- editSummary: args.editSummary,
410
- notes: args.notes,
411
- });
412
- if (args.shouldBePattern != null) {
413
- feedbackCollector.collectPatternFeedback(args.executionId, {
414
- shouldBePattern: args.shouldBePattern,
415
- suggestedName: args.suggestedName,
416
- });
417
- }
418
- return { content: [{ type: 'text', text: JSON.stringify({ recorded: true, executionId: args.executionId, totalEntries: feedbackCollector.size }) }] };
419
- }
420
- );
421
-
422
- // ── Quality metrics tool ──
423
-
424
- server.tool(
425
- 'get_quality_metrics',
426
- 'Get aggregated quality metrics from the feedback store: avg score, thumb-up rate, per-domain breakdown, training gaps.',
427
- {},
428
- async () => {
429
- const metrics = await feedbackStore.getQualityMetrics();
430
- return { content: [{ type: 'text', text: JSON.stringify(metrics, null, 2) }] };
431
- }
432
- );
433
-
434
- server.tool(
435
- 'get_training_gaps',
436
- 'Get training gap signals from LLM self-critique: missing patterns, weak domain keywords, component gaps.',
437
- {},
438
- async () => {
439
- const gaps = await feedbackStore.getGapSummary();
440
- return { content: [{ type: 'text', text: JSON.stringify(gaps, null, 2) }] };
441
- }
442
- );
443
-
444
- server.tool(
445
- 'run_eval',
446
- 'Run the offline eval harness against the held-out intent set. Returns aggregate scores and per-intent results.',
447
- {
448
- domain: z.string().optional().describe('Filter by domain (forms, data, layout, agent, navigation)'),
449
- limit: z.number().optional().describe('Max intents to evaluate'),
184
+ domain: z.string().optional().describe("Filter by domain (forms, data, layout, agent, navigation)"),
185
+ limit: z.number().optional().describe("Max intents to evaluate")
450
186
  },
451
187
  async ({ domain, limit }) => {
452
188
  try {
453
- const { runHarness } = await import('../a2ui/evals/harness.mjs');
189
+ const { runHarness } = await import("../a2ui/evals/harness.mjs");
454
190
  const summary = await runHarness({
455
- generate: (args) => generateUI({ intent: args.intent, mode: 'instant' }),
191
+ generate: (args) => generateUI({ intent: args["intent"], mode: "instant" }),
456
192
  domain,
457
193
  limit,
458
- mode: 'instant',
194
+ mode: "instant"
459
195
  });
460
- return { content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] };
196
+ return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
461
197
  } catch (err) {
462
- return { content: [{ type: 'text', text: `Eval error: ${err.message}` }], isError: true };
198
+ const e = err instanceof Error ? err : new Error(String(err));
199
+ return { content: [{ type: "text", text: `Eval error: ${e.message}` }], isError: true };
463
200
  }
464
201
  }
465
202
  );
466
-
467
- // ── Resources ──
468
-
469
203
  server.resource(
470
- 'catalog',
471
- 'a2ui://catalog/manifest',
204
+ "catalog",
205
+ "a2ui://catalog/manifest",
472
206
  async (uri) => {
473
207
  const catalog = await getFullCatalog();
474
208
  const serializable = {
475
209
  version: catalog.version,
476
210
  totalTypes: catalog.totalTypes,
477
211
  totalTraits: catalog.totalTraits,
478
- components: [...catalog.entries.values()].map(e => serializeEntry(e, 'summary')),
479
- traits: catalog.traits,
212
+ components: [...catalog.entries.values()].map((e) => serializeEntry(e, "summary")),
213
+ traits: catalog.traits
480
214
  };
481
215
  return {
482
216
  contents: [{
483
217
  uri: uri.href,
484
- mimeType: 'application/json',
485
- text: JSON.stringify(serializable, null, 2),
486
- }],
218
+ mimeType: "application/json",
219
+ text: JSON.stringify(serializable, null, 2)
220
+ }]
487
221
  };
488
222
  }
489
223
  );
490
-
491
224
  server.resource(
492
- 'compositions',
493
- 'a2ui://catalog/compositions',
225
+ "compositions",
226
+ "a2ui://catalog/compositions",
494
227
  async (uri) => ({
495
228
  contents: [{
496
229
  uri: uri.href,
497
- mimeType: 'application/json',
498
- text: JSON.stringify(getAllZettelCompositions(), null, 2),
499
- }],
230
+ mimeType: "application/json",
231
+ text: JSON.stringify(getAllZettelCompositions(), null, 2)
232
+ }]
500
233
  })
501
234
  );
502
-
503
235
  server.resource(
504
- 'anti-patterns',
505
- 'a2ui://catalog/anti-patterns',
236
+ "anti-patterns",
237
+ "a2ui://catalog/anti-patterns",
506
238
  async (uri) => ({
507
239
  contents: [{
508
240
  uri: uri.href,
509
- mimeType: 'application/json',
510
- text: JSON.stringify(getAntiPatterns(), null, 2),
511
- }],
241
+ mimeType: "application/json",
242
+ text: JSON.stringify(getAntiPatterns(), null, 2)
243
+ }]
512
244
  })
513
245
  );
514
-
515
246
  server.resource(
516
- 'domains',
517
- 'a2ui://catalog/domains',
247
+ "domains",
248
+ "a2ui://catalog/domains",
518
249
  async (uri) => {
519
- const domains = getAllDomains().map(name => ({
250
+ const domains = getAllDomains().map((name) => ({
520
251
  name,
521
- ...getDomain(name),
252
+ ...getDomain(name)
522
253
  }));
523
254
  return {
524
255
  contents: [{
525
256
  uri: uri.href,
526
- mimeType: 'application/json',
527
- text: JSON.stringify(domains, null, 2),
528
- }],
257
+ mimeType: "application/json",
258
+ text: JSON.stringify(domains, null, 2)
259
+ }]
529
260
  };
530
261
  }
531
262
  );
532
-
533
- // ── Zettel composition-inspection tools ────────────────────────────────
534
- // (Fragment-inspection tools retired in §37; fragments no longer exist as
535
- // a separate corpus class. Compositions are now flat A2UI templates.)
536
-
537
- server.tool(
538
- 'get_composition',
539
- 'Fetch a composition by name. Returns the flat A2UI template (compositions are pre-inlined; no $fragment refs). Zettel-only.',
540
- { name: z.string() },
541
- async ({ name }) => {
542
- const c = getZettelComposition(name);
543
- if (!c) return { content: [{ type: 'text', text: `Composition not found: ${name}` }], isError: true };
544
- return { content: [{ type: 'text', text: JSON.stringify(c, null, 2) }] };
545
- },
546
- );
547
-
548
- server.tool(
549
- 'resolve_composition',
550
- 'Return the flat A2UI template + updateComponents messages for a composition. Zettel-only. (Pre-inlined since §37 — `resolve` is now a defensive copy + strip pass.)',
551
- { name: z.string() },
552
- async ({ name }) => {
553
- const c = getZettelComposition(name);
554
- if (!c) return { content: [{ type: 'text', text: `Composition not found: ${name}` }], isError: true };
555
- const template = resolveZettelComposition(c);
556
- const messages = zettelTemplateToMessages(template);
557
- return {
558
- content: [{
559
- type: 'text',
560
- text: JSON.stringify({ template, messages }, null, 2),
561
- }],
562
- };
563
- },
564
- );
565
-
566
- server.tool(
567
- 'get_graph',
568
- 'Return the composition catalog. Zettel-only. (Backlinks to fragments retired in §37; only composition nodes remain.)',
569
- {},
570
- async () => ({ content: [{ type: 'text', text: JSON.stringify(getZettelGraph(), null, 2) }] }),
571
- );
572
-
573
- server.tool(
574
- 'zettel_stats',
575
- 'Zettel corpus stats — composition count + average node count. (Fragment stats retired in §37.)',
576
- {},
577
- async () => {
578
- const comps = getAllZettelCompositions();
579
- const totalNodes = comps.reduce((s, c) => s + (c.template?.length || 0), 0);
580
- return {
581
- content: [{
582
- type: 'text',
583
- text: JSON.stringify({
584
- compositions: comps.length,
585
- avg_nodes_per_composition: comps.length ? Math.round(totalNodes / comps.length) : 0,
586
- total_nodes: totalNodes,
587
- }, null, 2),
588
- }],
589
- };
590
- },
591
- );
592
-
593
- // ── Gen-UI training-chunk tools ──────────────────────────────────────
594
- // Spec: docs/specs/genui-chunk-marker.md (Draft v0.1.0).
595
- // Plan: docs/plans/training-pipeline-chunk-harvest-2026-04-27.md.
596
-
597
- server.tool(
598
- 'search_chunks',
599
- `Search the gen-UI training-chunk corpus by keyword.
600
-
601
- The chunk corpus comes from \`packages/a2ui/corpus/chunks/\` — JSON records
602
- extracted from every \`[data-chunk]\` element in site/pages/* and the corpus
603
- exemplars. There are three kinds:
604
- - block (default): atomic UI fragment (KPI grid, sign-in form, table)
605
- - panel: tab-panel fragment of a page (e.g. dashboard-overview-panel)
606
- - page: full-page composition (e.g. dashboard-admin-page)
607
-
608
- Returns ranked candidates with chunk name, kind, primary tag, and a relevance
609
- score. Use \`get_chunk\` to fetch the full record (HTML + slot bindings + nested
610
- chunks) for a specific name.`,
611
- {
612
- query: z.string().describe('Keyword query — chunk name fragment, intent words, primary-tag name'),
613
- kind: z.enum(['block', 'panel', 'page']).optional().describe('Filter by chunk kind'),
614
- limit: z.number().int().min(1).max(50).default(20).describe('Max results'),
615
- },
616
- async ({ query, kind, limit }) => {
617
- const results = searchGenUIChunks(query, { kind, limit });
618
- return {
619
- content: [{
620
- type: 'text',
621
- text: JSON.stringify({ query, kind: kind || 'any', count: results.length, results }, null, 2),
622
- }],
623
- };
624
- },
625
- );
626
-
627
- server.tool(
628
- 'get_chunk',
629
- `Fetch the full record for a single gen-UI training chunk by name.
630
-
631
- Returns the chunk's bounding HTML, slot annotations, nested chunk names, and
632
- metadata (primary tag, kind, source page). For chunks that appear on multiple
633
- pages (reusable slot chunks like \`auth-card-header\`, \`reg-step-header\`),
634
- returns an \`instances\` array — one entry per page where the chunk appears.
635
-
636
- The HTML is suitable for direct rendering / inclusion in an A2UI message
637
- construction prompt.`,
638
- {
639
- name: z.string().describe('The chunk name, e.g. "dashboard-kpi-grid", "auth-signin-card-email", "code-language"'),
640
- },
641
- async ({ name }) => {
642
- const rec = getGenUIChunk(name);
643
- if (!rec) {
644
- return {
645
- isError: true,
646
- content: [{ type: 'text', text: JSON.stringify({ error: 'chunk not found', name }, null, 2) }],
647
- };
648
- }
649
- return { content: [{ type: 'text', text: JSON.stringify(rec, null, 2) }] };
650
- },
651
- );
652
-
653
- server.tool(
654
- 'lookup_chunk',
655
- `List every chunk whose primary element is \`<component_name>\`.
656
-
657
- Useful for "show me every page that opens with a \`<card-ui raw>\`" or "every
658
- chunk built around a \`<grid-ui>\` root." Returns chunk names + kinds + sources.
659
-
660
- Pair with \`get_chunk\` to fetch full records for any of the returned names.`,
661
- {
662
- component_name: z.string().describe('Component tag name, e.g. "card-ui", "grid-ui", "drawer-ui"'),
663
- },
664
- async ({ component_name }) => {
665
- const recs = lookupChunksByPrimary(component_name);
666
- return {
667
- content: [{
668
- type: 'text',
669
- text: JSON.stringify({
670
- component: component_name,
671
- count: recs.length,
672
- chunks: recs.map((r) => ({
673
- name: r.name,
674
- kind: r.kind,
675
- page: r.page || r.instances?.[0]?.page,
676
- slots: (r.slots || r.instances?.[0]?.slots || []).map((s) => s.name),
677
- nested: r.nested || r.instances?.[0]?.nested || [],
678
- })),
679
- }, null, 2),
680
- }],
681
- };
682
- },
683
- );
684
-
685
- // ── Chunk-aware composition + multi-turn refinement ──────────────────
686
- // `compose_from_chunks`, `refine_composition`, `get_state`, `report_issue`
687
- // share the LLM bridge + state-cache + issue-reporter + chunk-refiner
688
- // stack. Extracted to tools/synthesis.js.
689
- //
690
- // Spec: docs/specs/genui-multiturn-architecture.md (Phase A) +
691
- // docs/specs/genui-chunk-marker.md.
692
-
263
+ registerValidationTools(server);
264
+ registerFeedbackTools(server);
265
+ registerCorpusTools(server);
266
+ registerZettelTools(server);
693
267
  registerSynthesisTools(server);
694
-
695
-
696
- // ── Start ──
697
-
698
268
  async function main() {
699
269
  const transport = new StdioServerTransport();
700
-
701
- // (Auto-ingest of exemplar-derived patterns retired 2026-04-28 in
702
- // mcp 0.0.5. The chunk corpus + chunk-aware synthesizer are the
703
- // training surface now; the legacy extract → ingest path that pulled
704
- // 70 patterns from prose exemplars on every server boot is gone.)
705
-
706
270
  await server.connect(transport);
707
271
  const catalog = await getCatalog();
708
272
  const traits = getTraits();
709
273
  console.error(`AdiaUI MCP Server running (${catalog.totalTypes} components, ${traits.length} traits, ${_zettelBoot.compositionCount} compositions)`);
710
274
  }
711
-
712
275
  main().catch(console.error);