fluidcad 0.0.32 → 0.0.34

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 (279) hide show
  1. package/README.md +3 -2
  2. package/bin/commands/init.js +55 -0
  3. package/bin/commands/mcp.js +33 -0
  4. package/bin/commands/serve.js +77 -0
  5. package/bin/fluidcad.js +15 -107
  6. package/lib/dist/common/scene-object.d.ts +4 -1
  7. package/lib/dist/common/scene-object.js +9 -2
  8. package/lib/dist/common/solid.d.ts +4 -1
  9. package/lib/dist/common/solid.js +13 -0
  10. package/lib/dist/core/2d/tarc.d.ts +20 -2
  11. package/lib/dist/core/2d/tarc.js +24 -0
  12. package/lib/dist/core/index.d.ts +2 -1
  13. package/lib/dist/core/index.js +1 -0
  14. package/lib/dist/core/interfaces.d.ts +107 -2
  15. package/lib/dist/core/load.d.ts +2 -2
  16. package/lib/dist/core/repeat.js +62 -46
  17. package/lib/dist/core/rib.d.ts +18 -0
  18. package/lib/dist/core/rib.js +37 -0
  19. package/lib/dist/features/2d/arc.d.ts +8 -2
  20. package/lib/dist/features/2d/arc.js +94 -17
  21. package/lib/dist/features/2d/back.js +3 -2
  22. package/lib/dist/features/2d/sketch.d.ts +4 -0
  23. package/lib/dist/features/2d/sketch.js +21 -0
  24. package/lib/dist/features/2d/tarc-constrained.d.ts +2 -0
  25. package/lib/dist/features/2d/tarc-constrained.js +8 -0
  26. package/lib/dist/features/2d/tarc-radius-to-object.d.ts +16 -0
  27. package/lib/dist/features/2d/tarc-radius-to-object.js +58 -0
  28. package/lib/dist/features/2d/tarc-to-object.d.ts +18 -0
  29. package/lib/dist/features/2d/tarc-to-object.js +66 -0
  30. package/lib/dist/features/2d/tarc-to-point-tangent.d.ts +2 -0
  31. package/lib/dist/features/2d/tarc-to-point-tangent.js +3 -0
  32. package/lib/dist/features/2d/tarc-to-point.d.ts +2 -0
  33. package/lib/dist/features/2d/tarc-to-point.js +3 -0
  34. package/lib/dist/features/2d/tarc-with-tangent.d.ts +2 -0
  35. package/lib/dist/features/2d/tarc-with-tangent.js +3 -0
  36. package/lib/dist/features/2d/tarc.d.ts +2 -0
  37. package/lib/dist/features/2d/tarc.js +3 -0
  38. package/lib/dist/features/extrude-base.d.ts +9 -0
  39. package/lib/dist/features/extrude-base.js +22 -0
  40. package/lib/dist/features/extrude-to-face.js +1 -5
  41. package/lib/dist/features/extrude-two-distances.js +1 -2
  42. package/lib/dist/features/extrude.js +1 -2
  43. package/lib/dist/features/load.d.ts +6 -0
  44. package/lib/dist/features/load.js +53 -1
  45. package/lib/dist/features/mirror-feature.d.ts +3 -2
  46. package/lib/dist/features/mirror-feature.js +1 -1
  47. package/lib/dist/features/repeat-circular.d.ts +3 -3
  48. package/lib/dist/features/repeat-circular.js +8 -1
  49. package/lib/dist/features/repeat-linear.d.ts +4 -2
  50. package/lib/dist/features/repeat-linear.js +10 -1
  51. package/lib/dist/features/repeat-matrix.d.ts +3 -1
  52. package/lib/dist/features/repeat-matrix.js +7 -2
  53. package/lib/dist/features/rib.d.ts +31 -0
  54. package/lib/dist/features/rib.js +321 -0
  55. package/lib/dist/features/select.d.ts +1 -0
  56. package/lib/dist/features/select.js +81 -10
  57. package/lib/dist/features/shell.d.ts +4 -1
  58. package/lib/dist/features/shell.js +14 -3
  59. package/lib/dist/filters/edge/belongs-to-face.d.ts +12 -9
  60. package/lib/dist/filters/edge/belongs-to-face.js +64 -15
  61. package/lib/dist/filters/filter-builder-base.d.ts +25 -0
  62. package/lib/dist/filters/filter-builder-base.js +47 -0
  63. package/lib/dist/filters/filter.js +39 -14
  64. package/lib/dist/filters/from-object.d.ts +4 -0
  65. package/lib/dist/filters/from-object.js +10 -0
  66. package/lib/dist/helpers/clone-transform.d.ts +2 -1
  67. package/lib/dist/helpers/scene-helpers.d.ts +1 -1
  68. package/lib/dist/helpers/scene-helpers.js +146 -12
  69. package/lib/dist/index.d.ts +7 -1
  70. package/lib/dist/index.js +3 -3
  71. package/lib/dist/io/file-import.d.ts +5 -1
  72. package/lib/dist/io/file-import.js +29 -18
  73. package/lib/dist/math/lazy-matrix.d.ts +31 -0
  74. package/lib/dist/math/lazy-matrix.js +66 -0
  75. package/lib/dist/oc/color-transfer.d.ts +19 -8
  76. package/lib/dist/oc/color-transfer.js +70 -12
  77. package/lib/dist/oc/constraints/constraint-solver-adaptor.d.ts +5 -0
  78. package/lib/dist/oc/constraints/constraint-solver-adaptor.js +16 -0
  79. package/lib/dist/oc/constraints/constraint-solver.d.ts +4 -0
  80. package/lib/dist/oc/constraints/curve/curve-constraint-solver.d.ts +4 -0
  81. package/lib/dist/oc/constraints/curve/curve-constraint-solver.js +3 -0
  82. package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.d.ts +6 -1
  83. package/lib/dist/oc/constraints/geometric/geometric-constraint-solver.js +4 -0
  84. package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.d.ts +8 -0
  85. package/lib/dist/oc/constraints/geometric/tangent-arc-from-point-tangent.js +111 -0
  86. package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.d.ts +8 -0
  87. package/lib/dist/oc/constraints/geometric/tangent-arc-radius-to-object.js +161 -0
  88. package/lib/dist/oc/extrude-ops.d.ts +2 -1
  89. package/lib/dist/oc/extrude-ops.js +51 -2
  90. package/lib/dist/oc/mesh.d.ts +9 -4
  91. package/lib/dist/oc/mesh.js +14 -13
  92. package/lib/dist/oc/rib-ops.d.ts +35 -0
  93. package/lib/dist/oc/rib-ops.js +619 -0
  94. package/lib/dist/oc/shell-ops.d.ts +2 -1
  95. package/lib/dist/oc/shell-ops.js +5 -2
  96. package/lib/dist/oc/topology-index.d.ts +6 -0
  97. package/lib/dist/oc/topology-index.js +36 -0
  98. package/lib/dist/rendering/mesh-builder.d.ts +3 -0
  99. package/lib/dist/rendering/mesh-builder.js +8 -4
  100. package/lib/dist/rendering/render-edge.d.ts +2 -1
  101. package/lib/dist/rendering/render-edge.js +2 -2
  102. package/lib/dist/rendering/render-face.d.ts +2 -1
  103. package/lib/dist/rendering/render-face.js +2 -2
  104. package/lib/dist/rendering/render-solid.d.ts +2 -1
  105. package/lib/dist/rendering/render-solid.js +3 -5
  106. package/lib/dist/rendering/render-wire.d.ts +2 -1
  107. package/lib/dist/rendering/render-wire.js +2 -2
  108. package/lib/dist/rendering/render.d.ts +4 -0
  109. package/lib/dist/rendering/render.js +50 -2
  110. package/lib/dist/rendering/scene-compare.js +3 -0
  111. package/lib/dist/rendering/scene.d.ts +1 -0
  112. package/lib/dist/rendering/scene.js +4 -0
  113. package/lib/dist/scene-manager.d.ts +4 -2
  114. package/lib/dist/scene-manager.js +12 -4
  115. package/lib/dist/tests/features/2d/arc.test.js +64 -0
  116. package/lib/dist/tests/features/2d/back.test.js +17 -1
  117. package/lib/dist/tests/features/2d/tarc.test.js +157 -0
  118. package/lib/dist/tests/features/color-lineage.test.js +18 -0
  119. package/lib/dist/tests/features/filter-positional.test.d.ts +1 -0
  120. package/lib/dist/tests/features/filter-positional.test.js +129 -0
  121. package/lib/dist/tests/features/repeat-user-repro.test.d.ts +1 -0
  122. package/lib/dist/tests/features/repeat-user-repro.test.js +60 -0
  123. package/lib/dist/tests/features/rib.test.d.ts +1 -0
  124. package/lib/dist/tests/features/rib.test.js +598 -0
  125. package/lib/dist/tests/features/shell.test.js +36 -0
  126. package/lib/dist/tests/global-setup.js +2 -1
  127. package/lib/dist/tests/helpers/extract-blocks.d.ts +9 -0
  128. package/lib/dist/tests/helpers/extract-blocks.js +56 -0
  129. package/lib/dist/tests/llm-docs-examples.test.d.ts +1 -0
  130. package/lib/dist/tests/llm-docs-examples.test.js +62 -0
  131. package/lib/dist/tests/scene-compare.test.d.ts +1 -0
  132. package/lib/dist/tests/scene-compare.test.js +77 -0
  133. package/lib/dist/tests/setup.js +2 -1
  134. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  135. package/llm-docs/.coverage-allowlist.txt +9 -0
  136. package/llm-docs/api/arc.md +48 -0
  137. package/llm-docs/api/axis.md +42 -0
  138. package/llm-docs/api/bezier.md +42 -0
  139. package/llm-docs/api/booleans.md +44 -0
  140. package/llm-docs/api/chamfer.md +40 -0
  141. package/llm-docs/api/circle.md +36 -0
  142. package/llm-docs/api/color.md +34 -0
  143. package/llm-docs/api/connect.md +41 -0
  144. package/llm-docs/api/constraint-qualifiers.md +48 -0
  145. package/llm-docs/api/copy.md +63 -0
  146. package/llm-docs/api/cursor-lines.md +50 -0
  147. package/llm-docs/api/cursor-move.md +61 -0
  148. package/llm-docs/api/cut.md +55 -0
  149. package/llm-docs/api/draft.md +36 -0
  150. package/llm-docs/api/edge-filter.md +57 -0
  151. package/llm-docs/api/ellipse.md +34 -0
  152. package/llm-docs/api/extrude.md +74 -0
  153. package/llm-docs/api/face-filter.md +61 -0
  154. package/llm-docs/api/fillet.md +51 -0
  155. package/llm-docs/api/index.json +139 -0
  156. package/llm-docs/api/line.md +42 -0
  157. package/llm-docs/api/load.md +37 -0
  158. package/llm-docs/api/local.md +38 -0
  159. package/llm-docs/api/loft.md +37 -0
  160. package/llm-docs/api/mirror.md +44 -0
  161. package/llm-docs/api/offset.md +36 -0
  162. package/llm-docs/api/part.md +40 -0
  163. package/llm-docs/api/plane.md +44 -0
  164. package/llm-docs/api/polygon.md +37 -0
  165. package/llm-docs/api/primitive-solids.md +39 -0
  166. package/llm-docs/api/project-intersect.md +48 -0
  167. package/llm-docs/api/rect.md +48 -0
  168. package/llm-docs/api/remove.md +32 -0
  169. package/llm-docs/api/repeat.md +79 -0
  170. package/llm-docs/api/revolve.md +38 -0
  171. package/llm-docs/api/rib.md +40 -0
  172. package/llm-docs/api/rotate.md +37 -0
  173. package/llm-docs/api/select.md +41 -0
  174. package/llm-docs/api/shell.md +41 -0
  175. package/llm-docs/api/sketch.md +76 -0
  176. package/llm-docs/api/slot.md +36 -0
  177. package/llm-docs/api/split-trim.md +42 -0
  178. package/llm-docs/api/sweep.md +43 -0
  179. package/llm-docs/api/tarc.md +45 -0
  180. package/llm-docs/api/tcircle.md +38 -0
  181. package/llm-docs/api/tline.md +42 -0
  182. package/llm-docs/api/translate.md +40 -0
  183. package/llm-docs/api/types/aline.md +35 -0
  184. package/llm-docs/api/types/arc-angles.md +29 -0
  185. package/llm-docs/api/types/arc-points.md +48 -0
  186. package/llm-docs/api/types/axis-like.md +38 -0
  187. package/llm-docs/api/types/axis.md +21 -0
  188. package/llm-docs/api/types/boolean-operation.md +50 -0
  189. package/llm-docs/api/types/circular-repeat-options.md +31 -0
  190. package/llm-docs/api/types/common.md +32 -0
  191. package/llm-docs/api/types/cut.md +125 -0
  192. package/llm-docs/api/types/draft.md +21 -0
  193. package/llm-docs/api/types/extrudable-geometry.md +23 -0
  194. package/llm-docs/api/types/extrude.md +194 -0
  195. package/llm-docs/api/types/geometry.md +51 -0
  196. package/llm-docs/api/types/hline.md +35 -0
  197. package/llm-docs/api/types/linear-repeat-options.md +31 -0
  198. package/llm-docs/api/types/loft.md +154 -0
  199. package/llm-docs/api/types/mirror.md +35 -0
  200. package/llm-docs/api/types/offset.md +31 -0
  201. package/llm-docs/api/types/plane-like.md +35 -0
  202. package/llm-docs/api/types/plane-transform-options.md +29 -0
  203. package/llm-docs/api/types/plane.md +21 -0
  204. package/llm-docs/api/types/point-like.md +22 -0
  205. package/llm-docs/api/types/point2dlike.md +26 -0
  206. package/llm-docs/api/types/polygon.md +46 -0
  207. package/llm-docs/api/types/rect.md +128 -0
  208. package/llm-docs/api/types/revolve.md +102 -0
  209. package/llm-docs/api/types/rib.md +133 -0
  210. package/llm-docs/api/types/scene-object.md +33 -0
  211. package/llm-docs/api/types/select.md +21 -0
  212. package/llm-docs/api/types/shell.md +54 -0
  213. package/llm-docs/api/types/slot.md +43 -0
  214. package/llm-docs/api/types/sweep.md +189 -0
  215. package/llm-docs/api/types/tangent-arc-two-objects.md +46 -0
  216. package/llm-docs/api/types/transformable.md +93 -0
  217. package/llm-docs/api/types/trim.md +27 -0
  218. package/llm-docs/api/types/two-objects-tangent-line.md +46 -0
  219. package/llm-docs/api/types/vertex.md +17 -0
  220. package/llm-docs/api/types/vline.md +35 -0
  221. package/llm-docs/concepts/coordinate-system.md +45 -0
  222. package/llm-docs/concepts/history-and-rollback.md +40 -0
  223. package/llm-docs/concepts/last-selection.md +49 -0
  224. package/llm-docs/concepts/scene-graph.md +37 -0
  225. package/llm-docs/index.json +1750 -0
  226. package/mcp/dist/client.d.ts +64 -0
  227. package/mcp/dist/client.js +248 -0
  228. package/mcp/dist/discovery.d.ts +11 -0
  229. package/mcp/dist/discovery.js +78 -0
  230. package/mcp/dist/docs-index.d.ts +81 -0
  231. package/mcp/dist/docs-index.js +261 -0
  232. package/mcp/dist/resources.d.ts +4 -0
  233. package/mcp/dist/resources.js +115 -0
  234. package/mcp/dist/server.d.ts +12 -0
  235. package/mcp/dist/server.js +489 -0
  236. package/mcp/dist/tools/coordination.d.ts +9 -0
  237. package/mcp/dist/tools/coordination.js +46 -0
  238. package/mcp/dist/tools/docs.d.ts +66 -0
  239. package/mcp/dist/tools/docs.js +122 -0
  240. package/mcp/dist/tools/engine.d.ts +56 -0
  241. package/mcp/dist/tools/engine.js +145 -0
  242. package/mcp/dist/tools/inspection.d.ts +75 -0
  243. package/mcp/dist/tools/inspection.js +121 -0
  244. package/mcp/dist/tools/screenshot.d.ts +63 -0
  245. package/mcp/dist/tools/screenshot.js +263 -0
  246. package/mcp/dist/tools/source.d.ts +84 -0
  247. package/mcp/dist/tools/source.js +434 -0
  248. package/mcp/dist/tools/workspaces.d.ts +13 -0
  249. package/mcp/dist/tools/workspaces.js +33 -0
  250. package/mcp/dist/types.d.ts +18 -0
  251. package/mcp/dist/types.js +11 -0
  252. package/package.json +19 -5
  253. package/server/dist/code-editor.d.ts +36 -0
  254. package/server/dist/code-editor.js +8 -0
  255. package/server/dist/fluidcad-server.d.ts +50 -0
  256. package/server/dist/fluidcad-server.js +153 -1
  257. package/server/dist/global-registry.d.ts +30 -0
  258. package/server/dist/global-registry.js +126 -0
  259. package/server/dist/index.js +171 -26
  260. package/server/dist/instance-file.d.ts +31 -0
  261. package/server/dist/instance-file.js +73 -0
  262. package/server/dist/lint-fluid-js.d.ts +15 -0
  263. package/server/dist/lint-fluid-js.js +271 -0
  264. package/server/dist/routes/editor.d.ts +24 -0
  265. package/server/dist/routes/editor.js +44 -0
  266. package/server/dist/routes/export.d.ts +1 -1
  267. package/server/dist/routes/export.js +45 -8
  268. package/server/dist/routes/health.d.ts +7 -0
  269. package/server/dist/routes/health.js +14 -0
  270. package/server/dist/routes/lint.d.ts +10 -0
  271. package/server/dist/routes/lint.js +28 -0
  272. package/server/dist/routes/render.d.ts +33 -0
  273. package/server/dist/routes/render.js +34 -0
  274. package/server/dist/routes/scene.d.ts +5 -0
  275. package/server/dist/routes/scene.js +48 -0
  276. package/server/dist/routes/screenshot.js +68 -1
  277. package/server/dist/ws-protocol.d.ts +56 -2
  278. package/ui/dist/assets/{index-DMw0OYCF.js → index-BdqrMDRu.js} +30 -30
  279. package/ui/dist/index.html +1 -1
@@ -0,0 +1,261 @@
1
+ // In-memory index over `llm-docs/`.
2
+ //
3
+ // Loaded once at MCP startup. Resolves the on-disk docs root (either the
4
+ // published `node_modules/fluidcad/llm-docs/` or the in-repo `<root>/llm-docs/`
5
+ // during dev), reads the two manifests written by `scripts/build-llm-docs.ts`,
6
+ // and builds an inverted index for `search_docs`.
7
+ //
8
+ // Bodies are loaded lazily on first read and cached. The seed set is small
9
+ // (single-digit MB at most) so we don't bother with any external search dep
10
+ // or LRU bookkeeping — a Map is plenty.
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import { fileURLToPath } from 'url';
14
+ import { createRequire } from 'module';
15
+ const FIELD_WEIGHT = {
16
+ title: 5,
17
+ summary: 3,
18
+ tags: 4,
19
+ body: 1,
20
+ };
21
+ // Small fixed stopword set. Kept tiny on purpose — the corpus is small and we
22
+ // want exact lookups like "z" or "ts" to still work for users.
23
+ const STOPWORDS = new Set([
24
+ 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'for', 'from', 'has',
25
+ 'have', 'in', 'into', 'is', 'it', 'its', 'of', 'on', 'or', 'that', 'the', 'to',
26
+ 'was', 'were', 'will', 'with',
27
+ ]);
28
+ export class DocsIndex {
29
+ /** Absolute path to the docs root directory. */
30
+ root;
31
+ /** Every doc record, in the order they were emitted by the build script. */
32
+ docs;
33
+ /** Symbol -> docId (from `llm-docs/api/index.json`). */
34
+ symbols;
35
+ /**
36
+ * Type-name -> docId, restricted to docs under `api/types/`. Populated by
37
+ * filtering `symbols` so the same symbol map drives both `get_api_signature`
38
+ * and `get_type_definition`, but the latter rejects function symbols with a
39
+ * clear "not a type" error rather than returning a feature signature.
40
+ */
41
+ types;
42
+ byId;
43
+ bodyCache = new Map();
44
+ /** token -> docId -> per-field hit counts. */
45
+ inverted;
46
+ constructor(root, index, apiIndex) {
47
+ this.root = root;
48
+ this.docs = index.docs;
49
+ this.symbols = apiIndex.symbols;
50
+ this.types = Object.fromEntries(Object.entries(apiIndex.symbols).filter(([, docId]) => docId.startsWith('api/types/')));
51
+ this.byId = new Map(index.docs.map((d) => [d.id, d]));
52
+ this.inverted = this.buildInverted();
53
+ }
54
+ /** All docs, optionally filtered to those carrying a given tag. */
55
+ list(tag) {
56
+ if (!tag) {
57
+ return [...this.docs];
58
+ }
59
+ return this.docs.filter((d) => (d.tags ?? []).includes(tag));
60
+ }
61
+ get(id) {
62
+ return this.byId.get(id) ?? null;
63
+ }
64
+ /**
65
+ * Load the markdown body for a doc id, stripping the YAML frontmatter so
66
+ * callers see the same text the docs author wrote (no metadata clutter).
67
+ */
68
+ body(id) {
69
+ const cached = this.bodyCache.get(id);
70
+ if (cached !== undefined) {
71
+ return cached;
72
+ }
73
+ const doc = this.byId.get(id);
74
+ if (!doc) {
75
+ return null;
76
+ }
77
+ const raw = fs.readFileSync(path.join(this.root, doc.file), 'utf8');
78
+ const body = stripFrontmatter(raw);
79
+ this.bodyCache.set(id, body);
80
+ return body;
81
+ }
82
+ /** Keyword search. Returns ranked hits with an ~80-char snippet per result. */
83
+ search(query, limit = 10) {
84
+ const tokens = tokenize(query);
85
+ if (tokens.length === 0) {
86
+ return [];
87
+ }
88
+ const scores = new Map();
89
+ for (const token of tokens) {
90
+ const postings = this.inverted.get(token);
91
+ if (!postings) {
92
+ continue;
93
+ }
94
+ for (const [docId, hits] of postings) {
95
+ let local = 0;
96
+ local += hits.title * FIELD_WEIGHT.title;
97
+ local += hits.summary * FIELD_WEIGHT.summary;
98
+ local += hits.tags * FIELD_WEIGHT.tags;
99
+ local += hits.body * FIELD_WEIGHT.body;
100
+ scores.set(docId, (scores.get(docId) ?? 0) + local);
101
+ }
102
+ }
103
+ // Exact-symbol boost — agents often type the bare function name.
104
+ for (const token of tokens) {
105
+ const docId = this.symbols[token];
106
+ if (docId) {
107
+ scores.set(docId, (scores.get(docId) ?? 0) + 10);
108
+ }
109
+ }
110
+ const ranked = [...scores.entries()]
111
+ .sort((a, b) => b[1] - a[1])
112
+ .slice(0, limit);
113
+ return ranked.map(([docId, score]) => {
114
+ const doc = this.byId.get(docId);
115
+ return {
116
+ id: doc.id,
117
+ title: doc.title,
118
+ snippet: this.snippet(doc.id, tokens),
119
+ score,
120
+ };
121
+ });
122
+ }
123
+ /** First fenced code block in the body, used as a symbol's signature surface. */
124
+ firstCodeBlock(id) {
125
+ const body = this.body(id);
126
+ if (!body) {
127
+ return null;
128
+ }
129
+ const match = body.match(/```[a-zA-Z0-9_-]*\n([\s\S]*?)```/);
130
+ return match ? match[1].trimEnd() : null;
131
+ }
132
+ buildInverted() {
133
+ const inverted = new Map();
134
+ const add = (token, docId, field) => {
135
+ let perDoc = inverted.get(token);
136
+ if (!perDoc) {
137
+ perDoc = new Map();
138
+ inverted.set(token, perDoc);
139
+ }
140
+ let hits = perDoc.get(docId);
141
+ if (!hits) {
142
+ hits = { title: 0, summary: 0, tags: 0, body: 0 };
143
+ perDoc.set(docId, hits);
144
+ }
145
+ hits[field] += 1;
146
+ };
147
+ for (const doc of this.docs) {
148
+ for (const t of tokenize(doc.title)) {
149
+ add(t, doc.id, 'title');
150
+ }
151
+ for (const t of tokenize(doc.summary)) {
152
+ add(t, doc.id, 'summary');
153
+ }
154
+ for (const tag of doc.tags ?? []) {
155
+ for (const t of tokenize(tag)) {
156
+ add(t, doc.id, 'tags');
157
+ }
158
+ }
159
+ const body = this.body(doc.id);
160
+ if (body) {
161
+ for (const t of tokenize(body)) {
162
+ add(t, doc.id, 'body');
163
+ }
164
+ }
165
+ }
166
+ return inverted;
167
+ }
168
+ snippet(docId, tokens) {
169
+ const body = this.body(docId);
170
+ if (!body) {
171
+ return '';
172
+ }
173
+ const lower = body.toLowerCase();
174
+ let bestIdx = -1;
175
+ for (const token of tokens) {
176
+ const i = lower.indexOf(token);
177
+ if (i !== -1 && (bestIdx === -1 || i < bestIdx)) {
178
+ bestIdx = i;
179
+ }
180
+ }
181
+ if (bestIdx === -1) {
182
+ return body.slice(0, 160).replace(/\s+/g, ' ').trim();
183
+ }
184
+ const start = Math.max(0, bestIdx - 40);
185
+ const end = Math.min(body.length, bestIdx + 120);
186
+ const prefix = start > 0 ? '…' : '';
187
+ const suffix = end < body.length ? '…' : '';
188
+ return prefix + body.slice(start, end).replace(/\s+/g, ' ').trim() + suffix;
189
+ }
190
+ }
191
+ /**
192
+ * Locate the on-disk `llm-docs/` directory.
193
+ *
194
+ * Resolution order:
195
+ * 1. The installed package — `require.resolve('fluidcad/package.json')` gives
196
+ * us the consumer's `node_modules/fluidcad/`, and `llm-docs/` sits next to
197
+ * that `package.json`.
198
+ * 2. The in-repo path — walk up from this file (`mcp/src/` or `mcp/dist/`)
199
+ * until we hit a sibling `llm-docs/` directory.
200
+ *
201
+ * We never trust `process.cwd()`: the MCP process is launched by an external
202
+ * client (Claude Desktop, etc.) so its CWD is whatever they happen to be in.
203
+ */
204
+ export function resolveDocsRoot() {
205
+ const here = fileURLToPath(import.meta.url);
206
+ const require_ = createRequire(here);
207
+ try {
208
+ const pkg = require_.resolve('fluidcad/package.json');
209
+ const candidate = path.join(path.dirname(pkg), 'llm-docs');
210
+ if (fs.existsSync(path.join(candidate, 'index.json'))) {
211
+ return candidate;
212
+ }
213
+ }
214
+ catch {
215
+ // Falls through to the dev-walkup path below.
216
+ }
217
+ let dir = path.dirname(here);
218
+ for (let i = 0; i < 8; i++) {
219
+ const candidate = path.join(dir, 'llm-docs');
220
+ if (fs.existsSync(path.join(candidate, 'index.json'))) {
221
+ return candidate;
222
+ }
223
+ const parent = path.dirname(dir);
224
+ if (parent === dir) {
225
+ break;
226
+ }
227
+ dir = parent;
228
+ }
229
+ throw new Error(`Could not locate llm-docs/ from ${here}. ` +
230
+ `Run \`npm run build:llm-docs\` to generate the manifest, ` +
231
+ `or ensure the package is installed.`);
232
+ }
233
+ /**
234
+ * Load the manifests from `<root>/index.json` and `<root>/api/index.json` and
235
+ * construct the in-memory index. Throws (with a helpful message) if either
236
+ * manifest is missing — that almost always means `build:llm-docs` was skipped.
237
+ */
238
+ export function loadDocsIndex(root) {
239
+ const resolved = root ?? resolveDocsRoot();
240
+ const indexPath = path.join(resolved, 'index.json');
241
+ const apiIndexPath = path.join(resolved, 'api', 'index.json');
242
+ if (!fs.existsSync(indexPath)) {
243
+ throw new Error(`llm-docs manifest missing at ${indexPath}. Run \`npm run build:llm-docs\`.`);
244
+ }
245
+ if (!fs.existsSync(apiIndexPath)) {
246
+ throw new Error(`llm-docs api manifest missing at ${apiIndexPath}. Run \`npm run build:llm-docs\`.`);
247
+ }
248
+ const index = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
249
+ const apiIndex = JSON.parse(fs.readFileSync(apiIndexPath, 'utf8'));
250
+ return new DocsIndex(resolved, index, apiIndex);
251
+ }
252
+ function tokenize(text) {
253
+ return text
254
+ .toLowerCase()
255
+ .split(/[^a-z0-9_]+/)
256
+ .filter((t) => t.length > 0 && !STOPWORDS.has(t));
257
+ }
258
+ function stripFrontmatter(raw) {
259
+ const match = raw.match(/^---\n[\s\S]*?\n---\n?([\s\S]*)$/);
260
+ return match ? match[1] : raw;
261
+ }
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { DocsIndex } from './docs-index.ts';
3
+ export declare const URI_OVERVIEW = "fluidcad-docs://overview";
4
+ export declare function registerDocResources(server: McpServer, index: DocsIndex): void;
@@ -0,0 +1,115 @@
1
+ // MCP resource registrations for `fluidcad-docs://*`.
2
+ //
3
+ // Resources are a bonus surface — clients that auto-attach them (e.g. Claude
4
+ // Desktop) get the docs for free without burning tool calls. Clients that
5
+ // ignore resources fall back to the `list_docs` / `read_doc` tools, so the
6
+ // two surfaces are designed to be self-sufficient.
7
+ //
8
+ // Every resource is registered statically. The seed set is small (under a
9
+ // dozen entries) so we trade the template machinery for a simple per-doc
10
+ // `registerResource` call — the resulting list shows up explicitly in the
11
+ // MCP client's resource picker.
12
+ export const URI_OVERVIEW = 'fluidcad-docs://overview';
13
+ export function registerDocResources(server, index) {
14
+ server.registerResource('fluidcad-docs-overview', URI_OVERVIEW, {
15
+ title: 'FluidCAD docs — overview',
16
+ description: 'Single-document aggregate of the FluidCAD doc set: titles, summaries, and tags for every API symbol and concept.',
17
+ mimeType: 'text/markdown',
18
+ }, async (uri) => ({
19
+ contents: [
20
+ {
21
+ uri: uri.href,
22
+ mimeType: 'text/markdown',
23
+ text: renderOverview(index),
24
+ },
25
+ ],
26
+ }));
27
+ // One static resource per API symbol. We use the symbol map (not the doc
28
+ // list) so a single doc that documents multiple symbols still appears under
29
+ // each. The doc body is identical across symbol resources — that's fine,
30
+ // clients dedupe by content.
31
+ for (const [symbol, docId] of Object.entries(index.symbols)) {
32
+ const doc = index.get(docId);
33
+ if (!doc) {
34
+ continue;
35
+ }
36
+ server.registerResource(`fluidcad-docs-api-${symbol}`, `fluidcad-docs://api/${symbol}`, {
37
+ title: doc.title,
38
+ description: doc.summary,
39
+ mimeType: 'text/markdown',
40
+ }, async (uri) => ({
41
+ contents: [
42
+ {
43
+ uri: uri.href,
44
+ mimeType: 'text/markdown',
45
+ text: index.body(doc.id) ?? '',
46
+ },
47
+ ],
48
+ }));
49
+ }
50
+ // One static resource per non-API doc, exposed as `guide/<slug>`. Slug is
51
+ // the doc id stripped of any leading category prefix (e.g.
52
+ // `concepts/scene-graph` → `scene-graph`). If two ids collide on slug, we
53
+ // keep the first and skip the rest — collisions are flagged here rather
54
+ // than during manifest validation since slug uniqueness is a resource-layer
55
+ // concern, not a doc-content concern.
56
+ const seenSlugs = new Set();
57
+ for (const doc of index.docs) {
58
+ if (isApiDoc(doc)) {
59
+ continue;
60
+ }
61
+ const slug = guideSlug(doc.id);
62
+ if (seenSlugs.has(slug)) {
63
+ continue;
64
+ }
65
+ seenSlugs.add(slug);
66
+ server.registerResource(`fluidcad-docs-guide-${slug}`, `fluidcad-docs://guide/${slug}`, {
67
+ title: doc.title,
68
+ description: doc.summary,
69
+ mimeType: 'text/markdown',
70
+ }, async (uri) => ({
71
+ contents: [
72
+ {
73
+ uri: uri.href,
74
+ mimeType: 'text/markdown',
75
+ text: index.body(doc.id) ?? '',
76
+ },
77
+ ],
78
+ }));
79
+ }
80
+ }
81
+ function isApiDoc(doc) {
82
+ return doc.id.startsWith('api/');
83
+ }
84
+ function guideSlug(id) {
85
+ const slash = id.indexOf('/');
86
+ return slash === -1 ? id : id.slice(slash + 1);
87
+ }
88
+ function renderOverview(index) {
89
+ const lines = [];
90
+ lines.push('# FluidCAD docs');
91
+ lines.push('');
92
+ lines.push('Generated from `llm-docs/`. Each entry lists a doc id, its title, and a one-line summary.');
93
+ lines.push('');
94
+ const apiDocs = index.docs.filter(isApiDoc);
95
+ const otherDocs = index.docs.filter((d) => !isApiDoc(d));
96
+ if (apiDocs.length > 0) {
97
+ lines.push('## API');
98
+ lines.push('');
99
+ for (const doc of apiDocs) {
100
+ lines.push(`- **${doc.id}** — ${doc.title}`);
101
+ lines.push(` ${doc.summary}`);
102
+ }
103
+ lines.push('');
104
+ }
105
+ if (otherDocs.length > 0) {
106
+ lines.push('## Concepts and guides');
107
+ lines.push('');
108
+ for (const doc of otherDocs) {
109
+ lines.push(`- **${doc.id}** — ${doc.title}`);
110
+ lines.push(` ${doc.summary}`);
111
+ }
112
+ lines.push('');
113
+ }
114
+ return lines.join('\n');
115
+ }
@@ -0,0 +1,12 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { type DocsIndex } from './docs-index.ts';
4
+ export declare const SERVER_NAME = "FluidCAD";
5
+ export declare const SERVER_VERSION: string;
6
+ export type BuildServerOptions = {
7
+ /** Pre-built docs index. Tests use this to inject a custom docs root. */
8
+ docsIndex?: DocsIndex;
9
+ };
10
+ export declare function buildServer(options?: BuildServerOptions): McpServer;
11
+ export declare function runStdio(): Promise<void>;
12
+ export { z };