docsui-cli 0.0.59

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.
Files changed (49) hide show
  1. package/README.md +279 -0
  2. package/dist/commands/add.d.ts +5 -0
  3. package/dist/commands/add.js +254 -0
  4. package/dist/commands/doctor.d.ts +5 -0
  5. package/dist/commands/doctor.js +250 -0
  6. package/dist/commands/init.d.ts +5 -0
  7. package/dist/commands/init.js +548 -0
  8. package/dist/commands/list.d.ts +5 -0
  9. package/dist/commands/list.js +84 -0
  10. package/dist/commands/mcp.d.ts +3 -0
  11. package/dist/commands/mcp.js +1562 -0
  12. package/dist/commands/new.d.ts +5 -0
  13. package/dist/commands/new.js +113 -0
  14. package/dist/commands/remove.d.ts +5 -0
  15. package/dist/commands/remove.js +134 -0
  16. package/dist/commands/save.d.ts +5 -0
  17. package/dist/commands/save.js +60 -0
  18. package/dist/commands/update.d.ts +5 -0
  19. package/dist/commands/update.js +247 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +88 -0
  22. package/dist/latex-to-primitives.d.ts +59 -0
  23. package/dist/latex-to-primitives.js +1019 -0
  24. package/dist/lib/component-registry.d.ts +33 -0
  25. package/dist/lib/component-registry.js +843 -0
  26. package/dist/lib/css-tokens.d.ts +8 -0
  27. package/dist/lib/css-tokens.js +294 -0
  28. package/dist/metadata.d.ts +1 -0
  29. package/dist/metadata.js +4 -0
  30. package/dist/symbol-map.d.ts +30 -0
  31. package/dist/symbol-map.js +1607 -0
  32. package/dist/utils/detect-structure.d.ts +16 -0
  33. package/dist/utils/detect-structure.js +58 -0
  34. package/dist/utils/fetch-component.d.ts +13 -0
  35. package/dist/utils/fetch-component.js +81 -0
  36. package/dist/utils/get-config.d.ts +14 -0
  37. package/dist/utils/get-config.js +19 -0
  38. package/dist/utils/install-deps.d.ts +3 -0
  39. package/dist/utils/install-deps.js +23 -0
  40. package/dist/utils/save-mdx-page.d.ts +35 -0
  41. package/dist/utils/save-mdx-page.js +44 -0
  42. package/dist/utils/scan-mdx.d.ts +20 -0
  43. package/dist/utils/scan-mdx.js +106 -0
  44. package/dist/utils/telemetry.d.ts +3 -0
  45. package/dist/utils/telemetry.js +42 -0
  46. package/dist/utils/write-component.d.ts +7 -0
  47. package/dist/utils/write-component.js +25 -0
  48. package/dist/webview-bundle.js +3478 -0
  49. package/package.json +94 -0
@@ -0,0 +1,1562 @@
1
+ import {
2
+ McpServer,
3
+ ResourceTemplate
4
+ } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { z } from "zod";
7
+ import axios from "axios";
8
+ import fs from "fs-extra";
9
+ import path from "path";
10
+ import { readFileSync } from "fs";
11
+ import {
12
+ SYMBOL_MAP,
13
+ searchSymbols,
14
+ symbolsByCategory,
15
+ buildMathStandard,
16
+ formatEntry,
17
+ CATEGORIES_LIST
18
+ } from "../symbol-map.js";
19
+ import {
20
+ latexToPrimitives,
21
+ convertMarkdownMath,
22
+ hasLatex,
23
+ parseSolution
24
+ } from "../latex-to-primitives.js";
25
+ import { fileURLToPath } from "url";
26
+ function getCliVersion() {
27
+ try {
28
+ const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
29
+ const pkg = JSON.parse(
30
+ readFileSync(path.join(__dirname2, "../../package.json"), "utf-8")
31
+ );
32
+ return typeof pkg.version === "string" ? pkg.version : "0.0.0";
33
+ } catch {
34
+ return "0.0.0";
35
+ }
36
+ }
37
+ const REGISTRY_URL = "https://raw.githubusercontent.com/suryaravikumar-space/mdx-ui/main/registry/registry.json";
38
+ const CACHE_TTL_MS = 5 * 60 * 1e3;
39
+ let cache = null;
40
+ async function loadLocalRegistry() {
41
+ try {
42
+ const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
43
+ const candidates = [
44
+ path.join(__dirname2, "../../../../registry/registry.json"),
45
+ path.join(__dirname2, "../../../registry/registry.json"),
46
+ path.join(__dirname2, "../../registry/registry.json")
47
+ ];
48
+ for (const p of candidates) {
49
+ if (await fs.pathExists(p)) {
50
+ return await fs.readJSON(p);
51
+ }
52
+ }
53
+ } catch (err) {
54
+ const msg = err instanceof Error ? err.message : String(err);
55
+ process.stderr.write(
56
+ `[docsui mcp] Failed to load local registry: ${msg}
57
+ `
58
+ );
59
+ }
60
+ return null;
61
+ }
62
+ async function fetchRegistry() {
63
+ if (cache && Date.now() - cache.fetchedAt < CACHE_TTL_MS) {
64
+ return cache.data;
65
+ }
66
+ const local = await loadLocalRegistry();
67
+ if (local) {
68
+ if (!Array.isArray(local.components)) {
69
+ throw new Error(
70
+ "Local registry.json is malformed \u2014 missing components array"
71
+ );
72
+ }
73
+ cache = { data: local, fetchedAt: Date.now() };
74
+ return local;
75
+ }
76
+ try {
77
+ const res = await axios.get(REGISTRY_URL, { timeout: 8e3 });
78
+ const data = res.data;
79
+ if (!data || !Array.isArray(data.components)) {
80
+ throw new Error("Remote registry returned malformed data");
81
+ }
82
+ cache = { data, fetchedAt: Date.now() };
83
+ return data;
84
+ } catch (err) {
85
+ if (cache) return cache.data;
86
+ const reason = err instanceof Error ? err.message : "network error";
87
+ throw new Error(`Could not load component registry \u2014 ${reason}`);
88
+ }
89
+ }
90
+ function levenshtein(a, b) {
91
+ const m = a.length;
92
+ const n = b.length;
93
+ const dp = Array.from(
94
+ { length: m + 1 },
95
+ (_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
96
+ );
97
+ for (let i = 1; i <= m; i++) {
98
+ for (let j = 1; j <= n; j++) {
99
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
100
+ }
101
+ }
102
+ return dp[m][n];
103
+ }
104
+ const normalize = (s) => s.toLowerCase().replace(/[-_\s]+/g, "");
105
+ function registryError(msg) {
106
+ return {
107
+ content: [{ type: "text", text: `\u26A0\uFE0F ${msg}` }],
108
+ isError: true
109
+ };
110
+ }
111
+ function formatComponent(c) {
112
+ const lines = [
113
+ `## ${c.name}`,
114
+ `**Type:** ${c.type}`,
115
+ `**Description:** ${c.description}`
116
+ ];
117
+ if (c.whenToUse) lines.push(`
118
+ **When to use:** ${c.whenToUse}`);
119
+ if (c.whenNotToUse) lines.push(`**When NOT to use:** ${c.whenNotToUse}`);
120
+ if (c.dependencies?.length)
121
+ lines.push(`
122
+ **npm dependencies:** ${c.dependencies.join(", ")}`);
123
+ if (c.registryDependencies?.length)
124
+ lines.push(`**Requires:** ${c.registryDependencies.join(", ")}`);
125
+ if (c.example) lines.push(`
126
+ **Example:**
127
+ \`\`\`mdx
128
+ ${c.example}
129
+ \`\`\``);
130
+ return lines.join("\n");
131
+ }
132
+ const STRUCTURE_STANDARD = `You generate content as MDX \u2014 Markdown with a small set of JSX components.
133
+
134
+ STRUCTURE
135
+ - Start sections with ## headings, subsections with ###, never go deeper
136
+ - Separate major sections with ---
137
+ - One blank line between block elements
138
+
139
+ STANDARD MARKDOWN (write these normally \u2014 they map to styled components automatically)
140
+ - Headings: ## and ###
141
+ - Bold: **text**, Italic: *text*
142
+ - Inline code: \`code\`
143
+ - Code blocks with language tag: \`\`\`lang
144
+ - Lists: - for unordered, 1. for ordered
145
+ - Tables: standard markdown | col | syntax
146
+ - Blockquotes: > text
147
+ - Horizontal rule: ---
148
+
149
+ CUSTOM COMPONENTS (use JSX for these only)
150
+
151
+ CALLOUT \u2014 notes, warnings, tips:
152
+ <Callout variant="info" title="Title">Content here.</Callout>
153
+ <Callout variant="warning" title="Title">Content here.</Callout>
154
+ <Callout variant="success" title="Title">Content here.</Callout>
155
+ <Callout variant="error" title="Title">Content here.</Callout>
156
+
157
+ STEPS \u2014 sequential procedures:
158
+ <Steps>
159
+ ### Step 1: Title
160
+ Step content here.
161
+ ### Step 2: Title
162
+ Step content here.
163
+ </Steps>
164
+
165
+ ACCORDION \u2014 optional depth, proofs, extra examples:
166
+ <Accordion>
167
+ <AccordionItem value="unique-id">
168
+ <AccordionTrigger>Label</AccordionTrigger>
169
+ <AccordionContent>Content here.</AccordionContent>
170
+ </AccordionItem>
171
+ </Accordion>
172
+
173
+ TABS \u2014 alternative explanations:
174
+ <Tabs>
175
+ <TabsList>
176
+ <TabsTrigger value="a">Label A</TabsTrigger>
177
+ <TabsTrigger value="b">Label B</TabsTrigger>
178
+ </TabsList>
179
+ <TabsContent value="a">Content A</TabsContent>
180
+ <TabsContent value="b">Content B</TabsContent>
181
+ </Tabs>
182
+
183
+ TOOLS \u2014 call these before writing math or solutions:
184
+
185
+ convert_latex(latex, block?)
186
+ \u2192 converts any LaTeX expression to primitive JSX deterministically
187
+ \u2192 USE THIS for any complex expression \u2014 do not guess the primitives yourself
188
+ \u2192 example: convert_latex("\\int_{-\\infty}^{\\infty} f(x) e^{-2\\pi i x t}\\,dx")
189
+
190
+ parse_solution(text)
191
+ \u2192 converts a plain-text step-by-step solution (with \u21D2 steps, parenthetical reasons)
192
+ into <Solution><SolutionStep>...</SolutionStep><SolutionAnswer/></Solution> MDX
193
+ \u2192 USE THIS whenever you write a worked example or derivation
194
+
195
+ convert_mdx_math(content)
196
+ \u2192 fixes an entire MDX string \u2014 replaces all $...$ and $$...$$ with primitives
197
+ \u2192 USE THIS if you accidentally produced dollar-sign math
198
+
199
+ search_symbols(query)
200
+ \u2192 find the right primitive component for any symbol or concept
201
+ \u2192 example: search_symbols("fourier") \u2192 ScriptF, Integral usage
202
+
203
+ MATH \u2014 use primitive components only, never $ signs or LaTeX strings.
204
+ Each primitive maps directly to readable English: <Frac num="a" den="b" /> means "a over b".
205
+
206
+ \u2500\u2500 BUILDING BLOCKS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
207
+
208
+ Fraction \u2192 <Frac num="1" den="2" /> (1/2)
209
+ Power / exponent \u2192 <Pow exp="2">x</Pow> (x\xB2)
210
+ Subscript \u2192 <Sub sub="n">a</Sub> (a\u2099)
211
+ Square root \u2192 <Sqrt>b\xB2 \u2212 4ac</Sqrt> (\u221A(b\xB2\u22124ac))
212
+ Nth root \u2192 <Sqrt n="3">x</Sqrt> (\u221Bx)
213
+ Absolute value \u2192 <Abs>x \u2212 y</Abs> (|x\u2212y|)
214
+ Parentheses \u2192 <Paren>a + b</Paren> ((a+b))
215
+ Infinity \u2192 <Inf /> (\u221E)
216
+ Degree symbol \u2192 <Deg>90</Deg> (90\xB0)
217
+
218
+ \u2500\u2500 CALCULUS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
219
+
220
+ Indefinite integral \u2192 <Integral>f(x) dx</Integral>
221
+ Definite integral \u2192 <Integral from="a" to="b">f(x) dx</Integral>
222
+ To infinity \u2192 <Integral from="0" to={<Inf />}>f(x) dx</Integral>
223
+ Summation \u2192 <Sum from="i=1" to="n"><Pow exp="2">i</Pow></Sum>
224
+ Product \u2192 <Prod from="i=1" to="n">i</Prod>
225
+ Limit \u2192 <Lim sub="x \u2192 0"><Frac num="sin x" den="x" /></Lim>
226
+
227
+ \u2500\u2500 GREEK LETTERS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
228
+
229
+ Lowercase: <Alpha /> <Beta /> <Gamma /> <Epsilon /> <Theta /> <Lambda />
230
+ <Mu /> <PiSym /> <Rho /> <SigmaSym /> <Tau /> <Phi /> <Omega />
231
+ Uppercase: <GammaU /> <DeltaU /> <ThetaU /> <LambdaU /> <SigmaU /> <PhiU /> <OmegaU />
232
+
233
+ \u2500\u2500 REAL EXAMPLES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
234
+
235
+ Quadratic formula:
236
+ x = <Frac num={<span>\u2212b \xB1 <Sqrt>b\xB2 \u2212 4ac</Sqrt></span>} den="2a" />
237
+
238
+ sin\xB2\u03B8 + cos\xB2\u03B8 = 1 (Pythagorean identity \u2014 write in plain text, use Greek inline):
239
+ sin\xB2<Theta /> + cos\xB2<Theta /> = 1
240
+
241
+ Euler's formula:
242
+ <Pow exp={<span>i<Theta /></span>}>e</Pow> = cos<Theta /> + i sin<Theta />
243
+
244
+ Derivative definition:
245
+ f\u2032(x) = <Lim sub="h \u2192 0"><Frac num={<span>f(x + h) \u2212 f(x)</span>} den="h" /></Lim>
246
+
247
+ Geometric series:
248
+ <Sum from="k=0" to={<Inf />}><Pow exp="k">r</Pow></Sum> = <Frac num="1" den="1 \u2212 r" /> when |r| < 1
249
+
250
+ Gaussian integral:
251
+ <Integral from={<span>\u2212<Inf /></span>} to={<Inf />}><Pow exp={<span>\u2212<Pow exp="2">x</Pow></span>}>e</Pow> dx</Integral> = <Sqrt><PiSym /></Sqrt>
252
+
253
+ Trig table cell (use in markdown table columns):
254
+ | <Theta /> | sin <Theta /> | cos <Theta /> | tan <Theta /> |
255
+ | 0\xB0 | 0 | 1 | 0 |
256
+ | 30\xB0 | <Frac num="1" den="2" /> | <Frac num={<Sqrt>3</Sqrt>} den="2" /> | <Frac num="1" den={<Sqrt>3</Sqrt>} /> |
257
+ | 45\xB0 | <Frac num={<Sqrt>2</Sqrt>} den="2" /> | <Frac num={<Sqrt>2</Sqrt>} den="2" /> | 1 |
258
+ | 60\xB0 | <Frac num={<Sqrt>3</Sqrt>} den="2" /> | <Frac num="1" den="2" /> | <Sqrt>3</Sqrt> |
259
+ | 90\xB0 | 1 | 0 | undefined |
260
+
261
+ Binomial coefficient (n choose k):
262
+ <Frac num={<span>n!</span>} den={<span>k!(n \u2212 k)!</span>} />
263
+
264
+ Integration by parts:
265
+ <Integral>u dv</Integral> = uv \u2212 <Integral>v du</Integral>
266
+
267
+ \u2500\u2500 COMPOSING NESTED EXPRESSIONS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
268
+
269
+ For complex numerators/denominators, wrap in {<span>...</span>}:
270
+ <Frac num={<span><Pow exp="2">x</Pow> + 2x + 1</span>} den={<span>x \u2212 1</span>} />
271
+
272
+ For exponents containing expressions:
273
+ <Pow exp={<span>\u2212<Frac num="x" den="2" /></span>}>e</Pow>
274
+
275
+ \u2500\u2500 SOLUTION BLOCKS \u2014 step-by-step worked math \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
276
+
277
+ <Solution title="Problem statement in plain English">
278
+ <SolutionStep reason="What this step does">expression with primitives</SolutionStep>
279
+ <SolutionStep reason="Key insight" highlight>key transformation</SolutionStep>
280
+ <SolutionAnswer>final result with primitives</SolutionAnswer>
281
+ <SolutionNote>Optional caveat or note.</SolutionNote>
282
+ </Solution>
283
+
284
+ `;
285
+ const OUTPUT_STANDARD = STRUCTURE_STANDARD + "\n\n" + buildMathStandard();
286
+ const CATEGORIES = {
287
+ "Layout & Structure": [
288
+ "accordion",
289
+ "callout",
290
+ "card",
291
+ "steps",
292
+ "tabs",
293
+ "reveal",
294
+ "spoiler"
295
+ ],
296
+ "Typography & Text": [
297
+ "blockquote",
298
+ "emphasis",
299
+ "heading",
300
+ "headings",
301
+ "highlight",
302
+ "horizontal-rule",
303
+ "inline-code",
304
+ "kbd",
305
+ "link",
306
+ "list",
307
+ "paragraph"
308
+ ],
309
+ Code: ["code-block", "code-group", "diff-block", "terminal"],
310
+ Math: ["math-equation", "math-primitives", "math-solution"],
311
+ "Data & Tables": [
312
+ "complexity-table",
313
+ "data-table",
314
+ "data-type-table",
315
+ "hardware-spec",
316
+ "pin-table",
317
+ "privacy-table",
318
+ "register-map",
319
+ "table"
320
+ ],
321
+ "Diagrams & Visualization": ["ds", "ds-tree", "file-tree", "mermaid", "tree"],
322
+ Media: ["image", "video"],
323
+ "Annotation & Reference": ["annotation", "glossary"],
324
+ "Metadata & Utility": [
325
+ "alert",
326
+ "badge",
327
+ "certification-badge",
328
+ "changelog",
329
+ "definition",
330
+ "invariant",
331
+ "json-ld",
332
+ "security-note"
333
+ ]
334
+ };
335
+ const ALLOWED_COMPONENTS = /* @__PURE__ */ new Set([
336
+ // layout & structure
337
+ "Callout",
338
+ "Steps",
339
+ "Step",
340
+ "Accordion",
341
+ "AccordionItem",
342
+ "AccordionTrigger",
343
+ "AccordionContent",
344
+ "Tabs",
345
+ "TabsList",
346
+ "TabsTrigger",
347
+ "TabsContent",
348
+ "CodeGroup",
349
+ "Solution",
350
+ "SolutionStep",
351
+ "SolutionAnswer",
352
+ "SolutionNote",
353
+ "Equation",
354
+ "EqSystem",
355
+ // math-primitives — structural
356
+ "Frac",
357
+ "Pow",
358
+ "Sub",
359
+ "Sqrt",
360
+ "Abs",
361
+ "Paren",
362
+ "Deg",
363
+ "Inf",
364
+ // math-primitives — calculus
365
+ "Integral",
366
+ "ContourIntegral",
367
+ "DoubleInt",
368
+ "TripleInt",
369
+ "SurfaceInt",
370
+ "VolumeInt",
371
+ "Sum",
372
+ "Prod",
373
+ "Lim",
374
+ "Limsup",
375
+ "Liminf",
376
+ "Deriv",
377
+ "PDeriv",
378
+ "Nabla",
379
+ "Laplacian",
380
+ "Overbrace",
381
+ "Underbrace",
382
+ // math-primitives — trig & functions
383
+ "Sin",
384
+ "Cos",
385
+ "Tan",
386
+ "Cot",
387
+ "Sec",
388
+ "Csc",
389
+ "ArcSin",
390
+ "ArcCos",
391
+ "ArcTan",
392
+ "Sinh",
393
+ "Cosh",
394
+ "Tanh",
395
+ "Log",
396
+ "Ln",
397
+ "Exp",
398
+ // math-primitives — algebra & combinatorics
399
+ "Factorial",
400
+ "Choose",
401
+ "Perm",
402
+ "Mod",
403
+ "GCD",
404
+ "LCM",
405
+ "Floor",
406
+ "Ceil",
407
+ // math-primitives — set theory
408
+ "SetOf",
409
+ "Cardinality",
410
+ "PowerSet",
411
+ "In",
412
+ "NotIn",
413
+ "Subset",
414
+ "SubsetEq",
415
+ "Supset",
416
+ "SupsetEq",
417
+ "ProperSubset",
418
+ "ProperSupset",
419
+ "NotSubset",
420
+ "NotSupset",
421
+ "Union",
422
+ "Intersect",
423
+ "Empty",
424
+ "SetMinus",
425
+ "BigUnion",
426
+ "BigIntersect",
427
+ "BigAnd",
428
+ "BigOr",
429
+ "NN",
430
+ "ZZ",
431
+ "QQ",
432
+ "RR",
433
+ "CC",
434
+ "PP",
435
+ "FF",
436
+ // math-primitives — logic & proof
437
+ "And",
438
+ "Or",
439
+ "Not",
440
+ "Xor",
441
+ "Nand",
442
+ "Nor",
443
+ "ForAll",
444
+ "Exists",
445
+ "NotExists",
446
+ "Therefore",
447
+ "Because",
448
+ "Turnstile",
449
+ "QED",
450
+ "Implies",
451
+ "Iff",
452
+ "Models",
453
+ "NotTurnstile",
454
+ "NotModels",
455
+ "Top",
456
+ "Bot",
457
+ "Contradiction",
458
+ "Tombstone",
459
+ // math-primitives — linear algebra
460
+ "Vec",
461
+ "Norm",
462
+ "Dot",
463
+ "Cross",
464
+ "Transpose",
465
+ "Det",
466
+ "Matrix",
467
+ "SpanOp",
468
+ "Rank",
469
+ "Dim",
470
+ "NullOp",
471
+ "Img",
472
+ "Trace",
473
+ // math-primitives — probability & statistics
474
+ "Prob",
475
+ "CondProb",
476
+ "Expected",
477
+ "Variance",
478
+ "StdDev",
479
+ "Cov",
480
+ "Corr",
481
+ "Dist",
482
+ // math-primitives — complex numbers
483
+ "Complex",
484
+ "Conj",
485
+ // math-primitives — Greek lowercase
486
+ "Greek",
487
+ "Alpha",
488
+ "Beta",
489
+ "Gamma",
490
+ "GDelta",
491
+ "Epsilon",
492
+ "Zeta",
493
+ "Eta",
494
+ "Theta",
495
+ "Iota",
496
+ "Kappa",
497
+ "Lambda",
498
+ "Mu",
499
+ "Nu",
500
+ "Xi",
501
+ "PiSym",
502
+ "Rho",
503
+ "SigmaSym",
504
+ "Tau",
505
+ "Upsilon",
506
+ "Phi",
507
+ "Chi",
508
+ "Psi",
509
+ "Omega",
510
+ // math-primitives — Greek uppercase
511
+ "GammaU",
512
+ "DeltaU",
513
+ "ThetaU",
514
+ "LambdaU",
515
+ "XiU",
516
+ "PiU",
517
+ "SigmaU",
518
+ "PhiU",
519
+ "PsiU",
520
+ "OmegaU",
521
+ // math-primitives — Greek variants
522
+ "Varsigma",
523
+ "Varepsilon",
524
+ "Varphi",
525
+ "Vartheta",
526
+ "Varpi",
527
+ "Varrho",
528
+ "Digamma",
529
+ // math-primitives — relations & operators
530
+ "Eq",
531
+ "Neq",
532
+ "NotEq",
533
+ "Approx",
534
+ "Equiv",
535
+ "Cong",
536
+ "NotCong",
537
+ "NotSim",
538
+ "Leq",
539
+ "Geq",
540
+ "Ll",
541
+ "Gg",
542
+ "Propto",
543
+ "Sim",
544
+ "PlusMinus",
545
+ "MinusPlus",
546
+ "Divides",
547
+ "NotDivides",
548
+ "Arrow",
549
+ "MapsTo",
550
+ "Compose",
551
+ "OTimes",
552
+ "DegNum",
553
+ "Prec",
554
+ "Succ",
555
+ "PrecEq",
556
+ "SuccEq",
557
+ "LessGreater",
558
+ "GreaterLess",
559
+ // math-primitives — equality & definition
560
+ "Approaches",
561
+ "DefinedAs",
562
+ "EqDef",
563
+ "Corresponds",
564
+ "Equiangular",
565
+ "AsympEq",
566
+ "NotAsympEq",
567
+ "DefEq",
568
+ // math-primitives — accents
569
+ "Bar",
570
+ "Hat",
571
+ "Tilde",
572
+ "DotAccent",
573
+ "DDot",
574
+ // math-primitives — prime notation
575
+ "Prime",
576
+ "DoublePrime",
577
+ "TriplePrime",
578
+ "PrimeOf",
579
+ // math-primitives — arrows
580
+ "LeftArrow",
581
+ "UpArrow",
582
+ "DownArrow",
583
+ "LeftRightArrow",
584
+ "UpDownArrow",
585
+ "NearArrow",
586
+ "SeArrow",
587
+ "SwArrow",
588
+ "NwArrow",
589
+ "HookRightArrow",
590
+ "HookLeftArrow",
591
+ "TwoHeadRight",
592
+ "TwoHeadLeft",
593
+ "DoubleLeftArrow",
594
+ "DoubleRightArrow",
595
+ "DoubleLeftRightArrow",
596
+ "DoubleUpArrow",
597
+ "DoubleDownArrow",
598
+ "DoubleUpDownArrow",
599
+ "LongRightArrow",
600
+ "LongLeftArrow",
601
+ "LongLeftRightArrow",
602
+ "LongMapsTo",
603
+ "RightHarpoonUp",
604
+ "RightHarpoonDown",
605
+ "LeftHarpoonUp",
606
+ "LeftHarpoonDown",
607
+ "EquilibriumArrow",
608
+ "DoubleHarpoon",
609
+ "CircleArrow",
610
+ "CircleArrowLeft",
611
+ // math-primitives — dots & ellipsis
612
+ "CDots",
613
+ "VDots",
614
+ "DDots",
615
+ "LDots",
616
+ "UpDots",
617
+ // math-primitives — brackets & intervals
618
+ "AngleBracket",
619
+ "DoubleBracket",
620
+ "Interval",
621
+ "Brace",
622
+ "Expr",
623
+ // math-primitives — piecewise
624
+ "Case",
625
+ "Cases",
626
+ // math-primitives — script letters
627
+ "ScriptA",
628
+ "ScriptB",
629
+ "ScriptC",
630
+ "ScriptD",
631
+ "ScriptE",
632
+ "ScriptF",
633
+ "ScriptG",
634
+ "ScriptH",
635
+ "ScriptI",
636
+ "ScriptJ",
637
+ "ScriptK",
638
+ "ScriptL",
639
+ "ScriptM",
640
+ "ScriptN",
641
+ "ScriptO",
642
+ "ScriptP",
643
+ "ScriptQ",
644
+ "ScriptR",
645
+ "ScriptS",
646
+ "ScriptT",
647
+ "ScriptU",
648
+ "ScriptV",
649
+ "ScriptW",
650
+ "ScriptX",
651
+ "ScriptY",
652
+ "ScriptZ",
653
+ "ScriptEll",
654
+ // math-primitives — physics
655
+ "HBar",
656
+ "Angstrom",
657
+ "Bra",
658
+ "Ket",
659
+ "BraKet",
660
+ "FrakR",
661
+ "FrakI",
662
+ "Weierstrass",
663
+ // math-primitives — extra operators
664
+ "DirectSum",
665
+ "Hadamard",
666
+ "CircledDiv",
667
+ "CircledStar",
668
+ "CircledPlus",
669
+ "CircledMinus",
670
+ "CircledTimes",
671
+ "WreathProduct",
672
+ "Star",
673
+ "Bullet",
674
+ "Dagger",
675
+ "DoubleDagger",
676
+ "Diamond",
677
+ "Bowtie",
678
+ "Amalg",
679
+ "SmallInt",
680
+ "Convo",
681
+ // math-primitives — school arithmetic
682
+ "Division",
683
+ "Times",
684
+ "Percent",
685
+ "Permille",
686
+ "Proportion",
687
+ "Ratio",
688
+ // math-primitives — geometry
689
+ "Angle",
690
+ "Triangle",
691
+ "Segment",
692
+ "Ray",
693
+ "Arc",
694
+ "Parallel",
695
+ "NotParallel",
696
+ "Perpendicular",
697
+ "RightAngle",
698
+ "RightAngleCorner",
699
+ "RightTriangle",
700
+ "Diameter",
701
+ "MeasuredAngle",
702
+ "SphericalAngle",
703
+ "GeoCong",
704
+ "GeoSim",
705
+ // geometry-2d (Fig* visual SVG components)
706
+ "FigScene",
707
+ "FigPoint",
708
+ "FigVector",
709
+ "FigLine",
710
+ "FigSegment",
711
+ "FigCircle",
712
+ "FigArc",
713
+ "FigAngle",
714
+ "FigPolygon",
715
+ "FigLabel",
716
+ "FigVisibleLine",
717
+ "FigHiddenLine",
718
+ "FigCenterLine",
719
+ "FigDimensionLine",
720
+ "FigExtensionLine",
721
+ "FigLeaderLine",
722
+ "FigSectionHatch",
723
+ "FigCuttingPlane",
724
+ "FigBreakLine",
725
+ "FigPhantomLine",
726
+ "FigEllipse",
727
+ "FigSemicircle",
728
+ "FigOval",
729
+ "FigCrescent",
730
+ "FigLens",
731
+ "FigLune",
732
+ "FigParabola",
733
+ "FigHyperbola",
734
+ "FigCardioid",
735
+ "FigLimacon",
736
+ "FigLemniscate",
737
+ "FigRightAngleMarker",
738
+ "FigEquilateralTriangle",
739
+ "FigIsoscelesTriangle",
740
+ "FigScaleneTriangle",
741
+ "FigRightTriangle",
742
+ "FigRect",
743
+ "FigSquare",
744
+ "FigRhombus",
745
+ "FigParallelogram",
746
+ "FigTrapezoid",
747
+ "FigKite",
748
+ "FigDart",
749
+ "FigCrossQuad",
750
+ "FigAntiparallelogram",
751
+ "FigCyclicQuad",
752
+ "FigTangentialQuad",
753
+ "FigRegularPolygon",
754
+ "FigPentagon",
755
+ "FigHexagon",
756
+ "FigHeptagon",
757
+ "FigOctagon",
758
+ "FigNonagon",
759
+ "FigDecagon",
760
+ "FigHendecagon",
761
+ "FigDodecagon",
762
+ "FigStarPolygon",
763
+ "FigPentagram",
764
+ "FigHexagram",
765
+ "FigOctagram",
766
+ "FigKochSnowflake",
767
+ "FigSierpinskiTriangle",
768
+ "FigMeasure",
769
+ "FigCirclePi",
770
+ "FigContour",
771
+ "FigFunnel",
772
+ "FigCuboid",
773
+ "FigFunction",
774
+ "FigDraggablePoint",
775
+ "FigParabolaExplorer",
776
+ "FigLineExplorer",
777
+ "FigTangentExplorer",
778
+ "FigSineExplorer",
779
+ "FigCircleExplorer",
780
+ "FigHyperbolicTangentExplorer",
781
+ "ElecWire",
782
+ "ElecNode",
783
+ "ElecGround",
784
+ "ElecResistor",
785
+ "ElecCapacitor",
786
+ "ElecInductor",
787
+ "ElecFuse",
788
+ "ElecBattery",
789
+ "ElecVoltageSource",
790
+ "ElecACSource",
791
+ "ElecCurrentSource",
792
+ "ElecSwitch",
793
+ "ElecDiode",
794
+ "ElecLED",
795
+ "ElecLamp",
796
+ "ElecVoltmeter",
797
+ "ElecAmmeter",
798
+ "ElecLabel",
799
+ // math-primitives — shapes
800
+ "Circle",
801
+ "FilledCircle",
802
+ "Square",
803
+ "FilledSquare",
804
+ "Rhombus",
805
+ "FilledRhombus",
806
+ "Pentagon",
807
+ "Hexagon",
808
+ "Ellipse",
809
+ // math-primitives — number theory operators
810
+ "Lcm",
811
+ "Gcd",
812
+ "Ord",
813
+ "Sgn",
814
+ "Arg",
815
+ "Re",
816
+ "Im",
817
+ "Res",
818
+ "Sup",
819
+ "Inf2",
820
+ "Max",
821
+ "Min",
822
+ "Ker",
823
+ "Hom",
824
+ "End",
825
+ "Aut",
826
+ "Der",
827
+ // math-primitives — chemistry
828
+ "ReactionArrow",
829
+ "GasMarker",
830
+ "PrecipitateMarker",
831
+ "ChemEquilibrium",
832
+ "SingleBond",
833
+ "DoubleBond",
834
+ "TripleBond",
835
+ // math-primitives — misc symbols
836
+ "Aleph",
837
+ "Beth",
838
+ "Gimel",
839
+ "Daleth",
840
+ "PartialDiff",
841
+ "FlatSymbol",
842
+ "SharpSymbol",
843
+ "NaturalSymbol",
844
+ "Differential",
845
+ // math-primitives — shorthands
846
+ "Squared",
847
+ "Cubed",
848
+ "Inverse",
849
+ "SubZero",
850
+ "SubOne",
851
+ "SubTwo",
852
+ // math-primitives — CS asymptotic notation
853
+ "BigO",
854
+ "BigTheta",
855
+ "BigOmega",
856
+ "LittleO",
857
+ "LittleOmega",
858
+ // math-primitives — basic arithmetic
859
+ "Plus",
860
+ "Minus",
861
+ "Mul",
862
+ "Div",
863
+ "Modulus"
864
+ ]);
865
+ const BANNED_HTML = /^<(div|span|p\b|b\b|i\b|strong|em|br|hr|section|article|main|aside|header|footer|nav)\s*[\s/>]/i;
866
+ const INVENTED_JSX = /^<([A-Z][a-zA-Z0-9]*)/;
867
+ const BANNED_MATH_COMPONENTS = /^<(InlineMath|BlockMath|Math\b|ME\b|BME\b|BM\b)\s/;
868
+ const INLINE_MATH_DOLLAR = /(?<!\d)\$(?!\$|\d)[^$\n]{2,}\$/;
869
+ const BLOCK_MATH_DOLLAR = /\$\$/;
870
+ function validateMdxContent(content) {
871
+ const issues = [];
872
+ const lines = content.split("\n");
873
+ let insideCodeFence = false;
874
+ let codeFenceMarker = "";
875
+ for (let i = 0; i < lines.length; i++) {
876
+ const line = lines[i];
877
+ const lineNo = i + 1;
878
+ const trimmed = line.trim();
879
+ const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
880
+ if (fenceMatch) {
881
+ if (!insideCodeFence) {
882
+ insideCodeFence = true;
883
+ codeFenceMarker = fenceMatch[1];
884
+ } else if (trimmed.startsWith(codeFenceMarker)) {
885
+ insideCodeFence = false;
886
+ codeFenceMarker = "";
887
+ }
888
+ continue;
889
+ }
890
+ if (insideCodeFence) continue;
891
+ if (BLOCK_MATH_DOLLAR.test(line) || INLINE_MATH_DOLLAR.test(line)) {
892
+ issues.push({
893
+ line: lineNo,
894
+ rule: "no-dollar-math",
895
+ text: `Dollar-sign math detected \u2014 use math primitive components instead (Frac, Pow, Integral, etc.)`
896
+ });
897
+ }
898
+ if (BANNED_MATH_COMPONENTS.test(trimmed)) {
899
+ issues.push({
900
+ line: lineNo,
901
+ rule: "no-katex-components",
902
+ text: `KaTeX component not allowed \u2014 use math primitives: <Frac>, <Pow>, <Integral>, <Sum>, Greek letters, etc.`
903
+ });
904
+ }
905
+ if (/^# [^#]/.test(line)) {
906
+ issues.push({
907
+ line: lineNo,
908
+ rule: "no-h1",
909
+ text: `H1 heading not allowed \u2014 start sections with ## minimum`
910
+ });
911
+ }
912
+ if (/^#{4,} /.test(line)) {
913
+ issues.push({
914
+ line: lineNo,
915
+ rule: "max-heading-depth",
916
+ text: `Heading depth exceeds ### \u2014 maximum allowed is ###`
917
+ });
918
+ }
919
+ if (BANNED_HTML.test(trimmed)) {
920
+ issues.push({
921
+ line: lineNo,
922
+ rule: "no-raw-html",
923
+ text: `Raw HTML tag not allowed \u2014 use Markdown or docsui components`
924
+ });
925
+ }
926
+ const jsxMatch = trimmed.match(INVENTED_JSX);
927
+ if (jsxMatch) {
928
+ const componentName = jsxMatch[1];
929
+ if (!ALLOWED_COMPONENTS.has(componentName)) {
930
+ issues.push({
931
+ line: lineNo,
932
+ rule: "no-invented-components",
933
+ text: `Unknown component <${componentName}> \u2014 only use components listed in the output standard`
934
+ });
935
+ }
936
+ }
937
+ }
938
+ return issues;
939
+ }
940
+ async function startMcpServer() {
941
+ const server = new McpServer({
942
+ name: "docsui",
943
+ version: getCliVersion()
944
+ });
945
+ server.registerResource(
946
+ "component-registry",
947
+ "registry://components",
948
+ {
949
+ title: "docsui Component Registry",
950
+ description: "Full list of all docsui components with metadata \u2014 name, description, whenToUse, whenNotToUse, example",
951
+ mimeType: "application/json"
952
+ },
953
+ async () => {
954
+ let registry;
955
+ try {
956
+ registry = await fetchRegistry();
957
+ } catch (err) {
958
+ const msg = err instanceof Error ? err.message : String(err);
959
+ return {
960
+ contents: [
961
+ {
962
+ uri: "registry://components",
963
+ mimeType: "application/json",
964
+ text: JSON.stringify({ error: msg })
965
+ }
966
+ ]
967
+ };
968
+ }
969
+ return {
970
+ contents: [
971
+ {
972
+ uri: "registry://components",
973
+ mimeType: "application/json",
974
+ text: JSON.stringify(registry, null, 2)
975
+ }
976
+ ]
977
+ };
978
+ }
979
+ );
980
+ server.registerResource(
981
+ "component",
982
+ new ResourceTemplate("registry://component/{name}", {
983
+ list: async () => {
984
+ try {
985
+ const registry = await fetchRegistry();
986
+ return {
987
+ resources: registry.components.map((c) => ({
988
+ uri: `registry://component/${c.name}`,
989
+ name: c.name,
990
+ description: c.description,
991
+ mimeType: "text/plain"
992
+ }))
993
+ };
994
+ } catch {
995
+ return { resources: [] };
996
+ }
997
+ }
998
+ }),
999
+ {
1000
+ title: "docsui Component",
1001
+ description: "Full schema for a single docsui component",
1002
+ mimeType: "text/plain"
1003
+ },
1004
+ async (uri, { name }) => {
1005
+ let registry;
1006
+ try {
1007
+ registry = await fetchRegistry();
1008
+ } catch (err) {
1009
+ const msg = err instanceof Error ? err.message : String(err);
1010
+ return {
1011
+ contents: [
1012
+ {
1013
+ uri: uri.href,
1014
+ mimeType: "text/plain",
1015
+ text: `Error: ${msg}`
1016
+ }
1017
+ ]
1018
+ };
1019
+ }
1020
+ const componentName = Array.isArray(name) ? name[0] : name;
1021
+ const component = registry.components.find((c) => c.name === componentName) ?? registry.components.find(
1022
+ (c) => normalize(c.name) === normalize(componentName ?? "")
1023
+ );
1024
+ return {
1025
+ contents: [
1026
+ {
1027
+ uri: uri.href,
1028
+ mimeType: "text/plain",
1029
+ text: component ? formatComponent(component) : `Component "${componentName}" not found.`
1030
+ }
1031
+ ]
1032
+ };
1033
+ }
1034
+ );
1035
+ server.registerResource(
1036
+ "output-standard",
1037
+ "registry://standard",
1038
+ {
1039
+ title: "MDX AI Output Standard",
1040
+ description: "The standard MDX output format for LLMs \u2014 inject this into your system prompt",
1041
+ mimeType: "text/plain"
1042
+ },
1043
+ async () => ({
1044
+ contents: [
1045
+ {
1046
+ uri: "registry://standard",
1047
+ mimeType: "text/plain",
1048
+ text: OUTPUT_STANDARD
1049
+ }
1050
+ ]
1051
+ })
1052
+ );
1053
+ server.registerPrompt(
1054
+ "generate_mdx",
1055
+ {
1056
+ title: "Generate MDX Content",
1057
+ description: "Generate valid docsui MDX content for a topic \u2014 injects the output standard and relevant components automatically",
1058
+ argsSchema: {
1059
+ topic: z.string().min(3, "Topic must be at least 3 characters").max(300, "Topic must be 300 characters or fewer").describe("The subject to generate content about"),
1060
+ level: z.enum(["beginner", "intermediate", "advanced"]).optional().describe("Audience level (default: intermediate)"),
1061
+ type: z.enum(["lesson", "reference", "exercise", "explanation"]).optional().describe("Content type (default: lesson)")
1062
+ }
1063
+ },
1064
+ async ({ topic, level = "intermediate", type = "lesson" }) => {
1065
+ let componentHint = "";
1066
+ try {
1067
+ const registry = await fetchRegistry();
1068
+ const contentComponents = registry.components.filter(
1069
+ (c) => [
1070
+ "callout",
1071
+ "steps",
1072
+ "accordion",
1073
+ "tabs",
1074
+ "math-primitives",
1075
+ "math-solution",
1076
+ "code-block",
1077
+ "code-group"
1078
+ ].includes(c.name)
1079
+ ).map((c) => `- **${c.name}**: ${c.description}`).join("\n");
1080
+ if (contentComponents) {
1081
+ componentHint = `
1082
+
1083
+ KEY COMPONENTS FOR THIS CONTENT:
1084
+ ${contentComponents}`;
1085
+ }
1086
+ } catch {
1087
+ }
1088
+ const typeInstructions = {
1089
+ lesson: "Structure as a complete lesson: introduction, core concept, examples, key points to remember.",
1090
+ reference: "Structure as a reference: concise definitions, syntax, parameters, and quick examples.",
1091
+ exercise: "Structure as a guided exercise: goal statement, steps to follow, expected outcome.",
1092
+ explanation: "Structure as a focused explanation: one concept, multiple perspectives, concrete analogies."
1093
+ };
1094
+ return {
1095
+ messages: [
1096
+ {
1097
+ role: "user",
1098
+ content: {
1099
+ type: "text",
1100
+ text: `${OUTPUT_STANDARD}${componentHint}
1101
+
1102
+ ---
1103
+
1104
+ Generate a ${level} ${type} about: **${topic}**
1105
+
1106
+ ${typeInstructions[type]}
1107
+
1108
+ BEFORE WRITING MATH:
1109
+ - Call convert_latex(latex) for any complex expression \u2014 never guess the primitives
1110
+ - Call parse_solution(text) for any worked example or step-by-step derivation
1111
+ - Call search_symbols(query) if unsure which component to use for a symbol
1112
+
1113
+ Output only the MDX content \u2014 no explanation, no code fences wrapping the whole thing.
1114
+
1115
+ MANDATORY SELF-CHECK BEFORE RESPONDING:
1116
+ 1. Call validate_mdx on your output.
1117
+ 2. If validate_mdx reports any dollar-math issues \u2192 call convert_mdx_math to fix them.
1118
+ 3. If validate_mdx reports unknown components \u2192 replace them with allowed primitives.
1119
+ 4. Return the corrected MDX \u2014 never return content that fails validate_mdx.`
1120
+ }
1121
+ }
1122
+ ]
1123
+ };
1124
+ }
1125
+ );
1126
+ server.registerPrompt(
1127
+ "review_mdx",
1128
+ {
1129
+ title: "Review MDX Content",
1130
+ description: "Review MDX content against the AI Output Standard and suggest fixes",
1131
+ argsSchema: {
1132
+ content: z.string().min(1, "Content cannot be empty").max(5e4, "Content must be 50,000 characters or fewer").describe("The MDX content to review")
1133
+ }
1134
+ },
1135
+ async ({ content }) => {
1136
+ const issues = validateMdxContent(content);
1137
+ const issueBlock = issues.length > 0 ? `
1138
+
1139
+ PRE-DETECTED ISSUES (${issues.length}):
1140
+ ${issues.map((i) => `- Line ${i.line}: [${i.rule}] ${i.text}`).join("\n")}` : "\n\nNo structural issues auto-detected \u2014 check for semantic and style problems.";
1141
+ return {
1142
+ messages: [
1143
+ {
1144
+ role: "user",
1145
+ content: {
1146
+ type: "text",
1147
+ text: `You are reviewing MDX content against the docsui AI Output Standard.
1148
+
1149
+ OUTPUT STANDARD:
1150
+ ${OUTPUT_STANDARD}
1151
+ ${issueBlock}
1152
+
1153
+ ---
1154
+
1155
+ CONTENT TO REVIEW:
1156
+ ${content}
1157
+
1158
+ ---
1159
+
1160
+ Review the content above. For each problem found:
1161
+ 1. Quote the problematic line
1162
+ 2. State which rule it breaks
1163
+ 3. Provide the corrected version
1164
+
1165
+ If the content contains $...$ or $$...$$ math, call convert_mdx_math(content) to fix it automatically.
1166
+ If the content has step-by-step solutions in plain text, call parse_solution(text) to convert them.
1167
+ If any LaTeX expression needs converting, call convert_latex(latex) first.
1168
+
1169
+ Then provide the fully corrected MDX at the end.`
1170
+ }
1171
+ }
1172
+ ]
1173
+ };
1174
+ }
1175
+ );
1176
+ server.registerTool(
1177
+ "list_components",
1178
+ {
1179
+ description: "List all available docsui components with descriptions. For a grouped view by category, use list_categories instead."
1180
+ },
1181
+ async () => {
1182
+ let registry;
1183
+ try {
1184
+ registry = await fetchRegistry();
1185
+ } catch (err) {
1186
+ return registryError(err instanceof Error ? err.message : String(err));
1187
+ }
1188
+ const lines = registry.components.map(
1189
+ (c) => `- **${c.name}** (${c.type}): ${c.description}`
1190
+ );
1191
+ return {
1192
+ content: [{ type: "text", text: lines.join("\n") }]
1193
+ };
1194
+ }
1195
+ );
1196
+ server.registerTool(
1197
+ "get_component",
1198
+ {
1199
+ description: "Get the full schema for a component \u2014 props, when to use, when not to use, and an MDX usage example",
1200
+ inputSchema: {
1201
+ name: z.string().min(1, "Component name cannot be empty").describe("Component name, e.g. accordion, complexity-table, dsbst")
1202
+ }
1203
+ },
1204
+ async ({ name }) => {
1205
+ let registry;
1206
+ try {
1207
+ registry = await fetchRegistry();
1208
+ } catch (err) {
1209
+ return registryError(err instanceof Error ? err.message : String(err));
1210
+ }
1211
+ const normalizedInput = normalize(name);
1212
+ const component = registry.components.find((c) => c.name === name.toLowerCase()) ?? registry.components.find((c) => normalize(c.name) === normalizedInput);
1213
+ if (!component) {
1214
+ const scored = registry.components.map((c) => ({
1215
+ name: c.name,
1216
+ dist: levenshtein(normalizedInput, normalize(c.name))
1217
+ })).sort((a, b) => a.dist - b.dist).slice(0, 3);
1218
+ const suggestions = scored[0].dist <= 4 ? `
1219
+
1220
+ Did you mean: ${scored.map((s) => `**${s.name}**`).join(", ")}?` : `
1221
+
1222
+ Use list_categories to browse all available components.`;
1223
+ return {
1224
+ content: [
1225
+ {
1226
+ type: "text",
1227
+ text: `Component "${name}" not found.${suggestions}`
1228
+ }
1229
+ ]
1230
+ };
1231
+ }
1232
+ return {
1233
+ content: [{ type: "text", text: formatComponent(component) }]
1234
+ };
1235
+ }
1236
+ );
1237
+ server.registerTool(
1238
+ "search_components",
1239
+ {
1240
+ description: "Search components by keyword or use case \u2014 e.g. 'math', 'tree', 'security', 'table'",
1241
+ inputSchema: {
1242
+ query: z.string().min(2, "Query must be at least 2 characters").max(200, "Query must be 200 characters or fewer").describe("Search keyword or use case description")
1243
+ }
1244
+ },
1245
+ async ({ query }) => {
1246
+ let registry;
1247
+ try {
1248
+ registry = await fetchRegistry();
1249
+ } catch (err) {
1250
+ return registryError(err instanceof Error ? err.message : String(err));
1251
+ }
1252
+ const words = query.toLowerCase().split(/\s+/).filter((w) => w.length > 1);
1253
+ const effectiveWords = words.length > 0 ? words : [query.toLowerCase().trim()];
1254
+ const matches = registry.components.filter((c) => {
1255
+ const haystack = [
1256
+ c.name,
1257
+ c.description,
1258
+ c.whenToUse ?? "",
1259
+ c.whenNotToUse ?? "",
1260
+ c.type
1261
+ ].join(" ").toLowerCase();
1262
+ return effectiveWords.every((w) => haystack.includes(w));
1263
+ });
1264
+ if (matches.length === 0) {
1265
+ return {
1266
+ content: [
1267
+ {
1268
+ type: "text",
1269
+ text: `No components found matching "${query}".`
1270
+ }
1271
+ ]
1272
+ };
1273
+ }
1274
+ const lines = [
1275
+ `Found ${matches.length} component${matches.length !== 1 ? "s" : ""} matching "${query}":
1276
+ `,
1277
+ ...matches.map((c) => `- **${c.name}**: ${c.description}`),
1278
+ `
1279
+ Use get_component(<name>) for full schema and examples.`
1280
+ ];
1281
+ return {
1282
+ content: [{ type: "text", text: lines.join("\n") }]
1283
+ };
1284
+ }
1285
+ );
1286
+ server.registerTool(
1287
+ "get_output_standard",
1288
+ {
1289
+ description: "Get the MDX AI Output Standard \u2014 the system prompt block to inject so an LLM generates valid MDX that renders correctly with docsui components"
1290
+ },
1291
+ async () => ({
1292
+ content: [{ type: "text", text: OUTPUT_STANDARD }]
1293
+ })
1294
+ );
1295
+ server.registerTool(
1296
+ "list_categories",
1297
+ {
1298
+ description: "List docsui components grouped by category \u2014 use this to discover components before calling get_component"
1299
+ },
1300
+ async () => {
1301
+ let registry;
1302
+ try {
1303
+ registry = await fetchRegistry();
1304
+ } catch (err) {
1305
+ return registryError(err instanceof Error ? err.message : String(err));
1306
+ }
1307
+ const byName = new Map(registry.components.map((c) => [c.name, c]));
1308
+ const lines = [];
1309
+ for (const [category, names] of Object.entries(CATEGORIES)) {
1310
+ lines.push(`
1311
+ ### ${category}`);
1312
+ for (const name of names) {
1313
+ const comp = byName.get(name);
1314
+ if (comp) lines.push(`- **${comp.name}**: ${comp.description}`);
1315
+ }
1316
+ }
1317
+ lines.push(
1318
+ "\nUse get_component(<name>) for full schema, or search_components(<query>) to find by use case."
1319
+ );
1320
+ return {
1321
+ content: [{ type: "text", text: lines.join("\n") }]
1322
+ };
1323
+ }
1324
+ );
1325
+ server.registerTool(
1326
+ "validate_mdx",
1327
+ {
1328
+ description: "Validate MDX content against the AI Output Standard \u2014 checks for dollar-sign math, raw HTML, H1 headings, heading depth, and unknown components",
1329
+ inputSchema: {
1330
+ content: z.string().min(1, "Content cannot be empty").max(5e4, "Content must be 50,000 characters or fewer").describe("The MDX content to validate")
1331
+ }
1332
+ },
1333
+ async ({ content }) => {
1334
+ const issues = validateMdxContent(content);
1335
+ if (issues.length === 0) {
1336
+ return {
1337
+ content: [
1338
+ {
1339
+ type: "text",
1340
+ text: "\u2705 No issues found \u2014 content follows the MDX AI Output Standard."
1341
+ }
1342
+ ]
1343
+ };
1344
+ }
1345
+ const lines = [
1346
+ `\u274C Found ${issues.length} issue${issues.length !== 1 ? "s" : ""}:
1347
+ `,
1348
+ ...issues.map((i) => `- **Line ${i.line}** [${i.rule}]: ${i.text}`),
1349
+ `
1350
+ Use the review_mdx prompt to get a corrected version.`
1351
+ ];
1352
+ return {
1353
+ content: [{ type: "text", text: lines.join("\n") }]
1354
+ };
1355
+ }
1356
+ );
1357
+ server.registerResource(
1358
+ "symbol-map",
1359
+ "registry://symbol-map",
1360
+ {
1361
+ title: "docsui Symbol Map",
1362
+ description: "Complete mapping of mathematical symbols / LaTeX names to docsui primitive components \u2014 use this to find the right component for any symbol",
1363
+ mimeType: "application/json"
1364
+ },
1365
+ async () => ({
1366
+ contents: [
1367
+ {
1368
+ uri: "registry://symbol-map",
1369
+ mimeType: "application/json",
1370
+ text: JSON.stringify(SYMBOL_MAP, null, 2)
1371
+ }
1372
+ ]
1373
+ })
1374
+ );
1375
+ server.registerTool(
1376
+ "search_symbols",
1377
+ {
1378
+ description: "Search the symbol map by concept name, LaTeX command, Unicode symbol, or category \u2014 returns the docsui primitive component and MDX usage example",
1379
+ inputSchema: {
1380
+ query: z.string().min(1, "Query cannot be empty").max(200).describe(
1381
+ "What to search for \u2014 e.g. 'integral', '\\\\frac', '\u2211', 'greek', 'geometry'"
1382
+ )
1383
+ }
1384
+ },
1385
+ async ({ query }) => {
1386
+ const results = searchSymbols(query);
1387
+ if (results.length === 0) {
1388
+ return {
1389
+ content: [
1390
+ {
1391
+ type: "text",
1392
+ text: `No symbols found for "${query}".
1393
+
1394
+ Try: 'integral', 'fraction', 'greek', 'geometry', 'arrow', 'logic', 'set', 'probability', 'physics', 'chemistry'`
1395
+ }
1396
+ ]
1397
+ };
1398
+ }
1399
+ const lines = [
1400
+ `Found ${results.length} symbol${results.length !== 1 ? "s" : ""} for "${query}":
1401
+ `,
1402
+ ...results.map(
1403
+ (e) => `**${e.name}** [${e.category}]
1404
+ Symbols: ${e.symbols.join(", ")}
1405
+ LaTeX: ${e.latex.slice(0, 2).join(" or ")}
1406
+ \u2192 \`${e.usage}\`
1407
+ `
1408
+ )
1409
+ ];
1410
+ return { content: [{ type: "text", text: lines.join("\n") }] };
1411
+ }
1412
+ );
1413
+ server.registerTool(
1414
+ "list_symbol_categories",
1415
+ {
1416
+ description: "List all symbol categories in the symbol map \u2014 use with search_symbols to explore by category"
1417
+ },
1418
+ async () => {
1419
+ const lines = CATEGORIES_LIST.map((cat) => {
1420
+ const count = symbolsByCategory(cat).length;
1421
+ return `- **${cat}** (${count} symbols)`;
1422
+ });
1423
+ return {
1424
+ content: [
1425
+ {
1426
+ type: "text",
1427
+ text: `Symbol map categories:
1428
+
1429
+ ${lines.join("\n")}
1430
+
1431
+ Use search_symbols with a category name to list all symbols in it.`
1432
+ }
1433
+ ]
1434
+ };
1435
+ }
1436
+ );
1437
+ server.registerTool(
1438
+ "get_symbol_cheatsheet",
1439
+ {
1440
+ description: "Get the complete symbol map as a formatted cheat sheet \u2014 every symbol with its component and usage, grouped by category",
1441
+ inputSchema: {
1442
+ category: z.string().optional().describe("Filter to a single category (e.g. 'calculus', 'greek')")
1443
+ }
1444
+ },
1445
+ async ({ category }) => {
1446
+ const entries = category ? symbolsByCategory(category) : SYMBOL_MAP;
1447
+ if (entries.length === 0) {
1448
+ return {
1449
+ content: [
1450
+ { type: "text", text: `No entries for category "${category}".` }
1451
+ ]
1452
+ };
1453
+ }
1454
+ const lines = [];
1455
+ let lastCat = "";
1456
+ for (const e of entries) {
1457
+ if (e.category !== lastCat) {
1458
+ lines.push(`
1459
+ ### ${e.category.toUpperCase().replace(/-/g, " ")}`);
1460
+ lastCat = e.category;
1461
+ }
1462
+ lines.push(` ${formatEntry(e)}`);
1463
+ }
1464
+ return {
1465
+ content: [{ type: "text", text: lines.join("\n") }]
1466
+ };
1467
+ }
1468
+ );
1469
+ server.registerTool(
1470
+ "convert_latex",
1471
+ {
1472
+ description: "Convert a LaTeX math expression to docsui primitive components deterministically. Use this whenever you need to write a complex math expression \u2014 pass the LaTeX and get back ready-to-paste MDX primitives.",
1473
+ inputSchema: {
1474
+ latex: z.string().min(1).max(2e3).describe(
1475
+ "LaTeX expression WITHOUT $ delimiters \u2014 e.g. \\\\int_{-\\\\infty}^{\\\\infty} f(x)\\\\,dx"
1476
+ ),
1477
+ block: z.boolean().optional().describe(
1478
+ "Wrap in <Equation> for display/block mode (default: false = inline)"
1479
+ )
1480
+ }
1481
+ },
1482
+ async ({ latex, block = false }) => {
1483
+ const result = latexToPrimitives(latex, block);
1484
+ return {
1485
+ content: [
1486
+ {
1487
+ type: "text",
1488
+ text: `**Input LaTeX:** \`${latex}\`
1489
+
1490
+ **MDX Primitives:**
1491
+ \`\`\`mdx
1492
+ ${result}
1493
+ \`\`\``
1494
+ }
1495
+ ]
1496
+ };
1497
+ }
1498
+ );
1499
+ server.registerTool(
1500
+ "convert_mdx_math",
1501
+ {
1502
+ description: "Convert all $...$ and $$...$$ LaTeX math in an MDX string to docsui primitives. Use this to fix AI-generated content that used dollar-sign math instead of components.",
1503
+ inputSchema: {
1504
+ content: z.string().min(1).max(5e4).describe("MDX content that may contain $...$ or $$...$$ math")
1505
+ }
1506
+ },
1507
+ async ({ content }) => {
1508
+ if (!hasLatex(content)) {
1509
+ return {
1510
+ content: [
1511
+ {
1512
+ type: "text",
1513
+ text: "\u2705 No LaTeX found \u2014 content already uses primitives."
1514
+ }
1515
+ ]
1516
+ };
1517
+ }
1518
+ const converted = convertMarkdownMath(content);
1519
+ return {
1520
+ content: [
1521
+ {
1522
+ type: "text",
1523
+ text: `**Converted MDX:**
1524
+ \`\`\`mdx
1525
+ ${converted}
1526
+ \`\`\``
1527
+ }
1528
+ ]
1529
+ };
1530
+ }
1531
+ );
1532
+ server.registerTool(
1533
+ "parse_solution",
1534
+ {
1535
+ description: "Convert a plain-text step-by-step math solution (with \u21D2 steps, parenthetical reasons, fractions like 3/2) into <Solution><SolutionStep>...</SolutionStep></Solution> MDX components automatically.",
1536
+ inputSchema: {
1537
+ text: z.string().min(10).max(1e4).describe(
1538
+ "The plain-text solution \u2014 include the problem statement and all \u21D2 steps with parenthetical reasons"
1539
+ )
1540
+ }
1541
+ },
1542
+ async ({ text }) => {
1543
+ const mdx = parseSolution(text);
1544
+ return {
1545
+ content: [
1546
+ {
1547
+ type: "text",
1548
+ text: `**Converted to MDX:**
1549
+ \`\`\`mdx
1550
+ ${mdx}
1551
+ \`\`\``
1552
+ }
1553
+ ]
1554
+ };
1555
+ }
1556
+ );
1557
+ const transport = new StdioServerTransport();
1558
+ await server.connect(transport);
1559
+ }
1560
+ export {
1561
+ startMcpServer
1562
+ };