contentbase 0.0.2 → 0.0.4

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 (85) hide show
  1. package/.mcp.json +9 -0
  2. package/CLI.md +593 -0
  3. package/MCP-DESCRIPTIONS-REVIEW.md +122 -0
  4. package/MCP-SERVER-SPEC.md +453 -0
  5. package/PRIMER.md +540 -0
  6. package/README.md +289 -13
  7. package/bun.lock +43 -4
  8. package/dist/cnotes +0 -0
  9. package/docs/README.md +110 -0
  10. package/docs/TABLE-OF-CONTENTS.md +7 -0
  11. package/docs/models.ts +38 -0
  12. package/models.ts +38 -0
  13. package/package.json +12 -3
  14. package/public/web-demo/index.html +813 -0
  15. package/showcases/node_modules/.cache/.repl_history +3 -0
  16. package/showcases/vinyl-collection/artists/nina-simone.mdx +1 -1
  17. package/src/__tests__/semantic-search.integration.test.ts +284 -0
  18. package/src/api/endpoints/actions.ts +34 -0
  19. package/src/api/endpoints/doc.ts +187 -0
  20. package/src/api/endpoints/docs-index.ts +26 -0
  21. package/src/api/endpoints/document.ts +171 -0
  22. package/src/api/endpoints/documents.ts +71 -0
  23. package/src/api/endpoints/inspect.ts +16 -0
  24. package/src/api/endpoints/models.ts +10 -0
  25. package/src/api/endpoints/query.ts +88 -0
  26. package/src/api/endpoints/root.ts +7 -0
  27. package/src/api/endpoints/search-reindex.ts +65 -0
  28. package/src/api/endpoints/search-status.ts +55 -0
  29. package/src/api/endpoints/search.ts +104 -0
  30. package/src/api/endpoints/semantic-search.ts +120 -0
  31. package/src/api/endpoints/text-search.ts +63 -0
  32. package/src/api/endpoints/validate.ts +34 -0
  33. package/src/api/helpers.ts +97 -0
  34. package/src/base-model.ts +12 -0
  35. package/src/cli/commands/action.ts +82 -44
  36. package/src/cli/commands/console.ts +124 -0
  37. package/src/cli/commands/create.ts +179 -53
  38. package/src/cli/commands/embed.ts +323 -0
  39. package/src/cli/commands/export.ts +58 -24
  40. package/src/cli/commands/extract.ts +174 -0
  41. package/src/cli/commands/help.ts +81 -0
  42. package/src/cli/commands/index.ts +17 -0
  43. package/src/cli/commands/init.ts +72 -48
  44. package/src/cli/commands/inspect.ts +56 -46
  45. package/src/cli/commands/mcp.ts +1219 -0
  46. package/src/cli/commands/search.ts +285 -0
  47. package/src/cli/commands/serve.ts +348 -0
  48. package/src/cli/commands/summary.ts +60 -0
  49. package/src/cli/commands/teach.ts +86 -0
  50. package/src/cli/commands/text-search.ts +134 -0
  51. package/src/cli/commands/validate.ts +126 -64
  52. package/src/cli/index.ts +88 -19
  53. package/src/cli/load-collection.ts +144 -17
  54. package/src/cli/registry.ts +28 -0
  55. package/src/collection.ts +361 -10
  56. package/src/define-model.ts +101 -4
  57. package/src/document.ts +47 -1
  58. package/src/extract-sections.ts +1 -1
  59. package/src/index.ts +14 -2
  60. package/src/model-instance.ts +35 -5
  61. package/src/node-shortcuts.ts +1 -1
  62. package/src/query/collection-query.ts +96 -9
  63. package/src/query/index.ts +7 -0
  64. package/src/query/query-dsl.ts +259 -0
  65. package/src/relationships/has-many.ts +39 -0
  66. package/src/relationships/index.ts +7 -2
  67. package/src/section.ts +2 -0
  68. package/src/types.ts +23 -1
  69. package/src/utils/index.ts +1 -0
  70. package/src/utils/match-pattern.ts +65 -0
  71. package/src/validator.ts +18 -1
  72. package/test/collection.test.ts +118 -2
  73. package/test/fixtures/sdlc/MODELS.md +106 -0
  74. package/test/fixtures/sdlc/SKILL.md +404 -0
  75. package/test/fixtures/sdlc/index.ts +9 -0
  76. package/test/fixtures/sdlc/models.ts +8 -6
  77. package/test/fixtures/sdlc/stories/authentication/a-user-should-be-able-to-login.mdx +20 -0
  78. package/test/fixtures/sdlc/templates/epic.md +23 -0
  79. package/test/fixtures/sdlc/templates/story.md +19 -0
  80. package/test/pattern.test.ts +191 -0
  81. package/test/query-dsl.test.ts +431 -0
  82. package/test/query.test.ts +29 -0
  83. package/test/relationships.test.ts +130 -0
  84. package/test/section.test.ts +61 -0
  85. package/test/table-of-contents.test.ts +49 -5
@@ -0,0 +1,122 @@
1
+ # MCP Tool & Resource Descriptions Review
2
+
3
+ Edit the descriptions below. I'll incorporate your changes back into `src/cli/commands/mcp.ts`.
4
+
5
+ ---
6
+
7
+ ## Tools
8
+
9
+ ### `read_me`
10
+ ```
11
+ Returns the content collection guide. Call this BEFORE working with any documents.
12
+ Contains model definitions, available tools, query syntax, and recommended workflow.
13
+ Call this at the start of every session to understand the collection structure.
14
+ ```
15
+
16
+ ### `inspect`
17
+ ```
18
+ Overview of the collection — registered models, document count, available actions. Call `read_me` first if this is your first interaction.
19
+ ```
20
+
21
+ ### `get_model_info`
22
+ ```
23
+ Get detailed information about a single model — fields, sections, relationships, example document. Use when you need to understand a model before creating or editing its documents.
24
+ ```
25
+
26
+ ### `list_documents`
27
+ ```
28
+ List all document path IDs in the collection, optionally filtered by model name or prefix. The prefix before the slash indicates the model.
29
+ ```
30
+
31
+ ### `query`
32
+ ```
33
+ Query typed model instances with MongoDB-style filtering. See `read_me` output for full syntax reference. Where clause: keys are dot-notation paths, values are literals (implies $eq), arrays (implies $in), or operator objects like { "$gt": 5 }. Operators: $eq, $neq, $in, $notIn, $gt, $lt, $gte, $lte, $contains, $startsWith, $endsWith, $regex, $exists. Supports sort, limit, offset, select, and method (fetchAll/first/last/count).
34
+ ```
35
+
36
+ ### `search_content`
37
+ ```
38
+ Full-text regex search across all document content. Returns matching document IDs with context. Searches document body text, not metadata — for metadata filtering, use `query` instead.
39
+ ```
40
+
41
+ ### `text_search`
42
+ ```
43
+ Search file contents with pattern matching using ripgrep. Returns distinct file matches by default, or line-level detail with expanded=true.
44
+ ```
45
+
46
+ ### `validate`
47
+ ```
48
+ Validate a document against its model schema. Returns validation result with any errors. **ALWAYS call after create/update operations** to confirm the document conforms to its model.
49
+ ```
50
+
51
+ ### `create_document`
52
+ ```
53
+ **ALWAYS use this instead of writing markdown files directly.** Creates a new document with proper scaffolding from a model definition — generates correct frontmatter defaults and section headings. Call `validate` after creation.
54
+ ```
55
+
56
+ ### `update_document`
57
+ ```
58
+ Update a document's frontmatter and/or replace its entire content body. Use for frontmatter changes. For section-level edits, prefer `update_section` instead. Call `validate` after.
59
+ ```
60
+
61
+ ### `update_section`
62
+ ```
63
+ Preferred way to edit document content. Surgically edit a specific section — replace, append, or remove. Target a section by its heading name. Call `validate` after.
64
+ ```
65
+
66
+ ### `delete_document`
67
+ ```
68
+ Delete a document from the collection permanently. Cannot be undone except through version control.
69
+ ```
70
+
71
+ ### `run_action`
72
+ ```
73
+ Execute a registered collection action by name.
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Resources
79
+
80
+ ### `contentbase://schema`
81
+ ```
82
+ JSON schema of all registered models — fields, sections, relationships, computed properties
83
+ ```
84
+
85
+ ### `contentbase://toc`
86
+ ```
87
+ Markdown table of contents for all documents in the collection
88
+ ```
89
+
90
+ ### `contentbase://models-summary`
91
+ ```
92
+ Generated MODELS.md describing all model definitions with attributes, sections, and relationships
93
+ ```
94
+
95
+ ### `contentbase://primer`
96
+ ```
97
+ Combined teach output — models summary, table of contents, CLI reference, and API primer
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Prompts
103
+
104
+ ### `create-{model}` (per-model)
105
+ ```
106
+ Guide creation of a new {Model} document with proper schema and sections
107
+ ```
108
+
109
+ ### `review-document`
110
+ ```
111
+ Fetch a document, run validation, and present it for review
112
+ ```
113
+
114
+ ### `teach`
115
+ ```
116
+ Full contentbase documentation — models, table of contents, CLI reference, and API primer. For a quick-start behavioral guide, use the `read_me` tool instead.
117
+ ```
118
+
119
+ ### `query-guide`
120
+ ```
121
+ Show available models, fields, and query operators to help build queries
122
+ ```
@@ -0,0 +1,453 @@
1
+ # Contentbase MCP Server Design
2
+
3
+ ## Overview
4
+
5
+ The server wraps a loaded `Collection` instance and exposes its capabilities through the three MCP primitives. The key design principle: **Resources are for reading, Tools are for writing and computing, Prompts are for guiding LLM workflows.**
6
+
7
+ ---
8
+
9
+ ## Resources
10
+
11
+ Resources expose collection data as readable URIs. MCP supports both static resources and URI templates for dynamic access.
12
+
13
+ ### Static Resources
14
+
15
+ | URI | Description | Backed by |
16
+ |-----|-------------|-----------|
17
+ | `contentbase://schema` | All registered models with their meta fields, sections, relationships, computed properties, and defaults | `collection.modelDefinitions` + `introspectMetaSchema()` |
18
+ | `contentbase://toc` | Table of contents listing all documents grouped by model | `collection.tableOfContents()` |
19
+ | `contentbase://models-summary` | Full MODELS.md documentation | `collection.generateModelSummary()` |
20
+ | `contentbase://primer` | The teach output — full LLM context document | Same as `cnotes teach` |
21
+
22
+ The `schema` resource is the most important one. It's what an LLM reads first to understand what models exist, what fields they have, and how to query them. Structure it as JSON:
23
+
24
+ ```json
25
+ {
26
+ "name": "my-project",
27
+ "rootPath": "/path/to/content",
28
+ "models": [
29
+ {
30
+ "name": "Epic",
31
+ "prefix": "epics",
32
+ "meta": [
33
+ { "name": "priority", "type": "enum", "values": ["low","medium","high"], "required": false, "description": "Importance level" },
34
+ { "name": "status", "type": "enum", "values": ["created","in-progress","complete"], "required": false, "default": "created" }
35
+ ],
36
+ "sections": {},
37
+ "relationships": {
38
+ "stories": { "type": "hasMany", "target": "Story", "heading": "Stories" }
39
+ },
40
+ "computed": ["isComplete"],
41
+ "defaults": { "status": "created" },
42
+ "documentCount": 2
43
+ }
44
+ ],
45
+ "actions": ["rebuild-index"],
46
+ "totalDocuments": 15
47
+ }
48
+ ```
49
+
50
+ ### Dynamic Resources (URI Templates)
51
+
52
+ | URI Template | Description | Backed by |
53
+ |--------------|-------------|-----------|
54
+ | `contentbase://documents/{pathId}` | Raw document — id, title, meta, content, outline | `collection.document(pathId).toJSON()` + `.toOutline()` |
55
+ | `contentbase://models/{modelName}/{pathId}` | Typed model instance — meta, sections, computed, validation | `collection.getModel(pathId, def).toJSON({ sections: [...all], computed: [...all] })` |
56
+ | `contentbase://documents/{pathId}/sections/{sectionName}` | A single extracted section's content | `doc.querySection(heading)` stringified |
57
+
58
+ The `documents/{pathId}` resource returns the full document representation:
59
+
60
+ ```json
61
+ {
62
+ "id": "epics/authentication",
63
+ "title": "Authentication",
64
+ "slug": "authentication",
65
+ "meta": { "status": "in-progress", "priority": "high" },
66
+ "content": "# Authentication\n\n...",
67
+ "outline": "- Authentication\n - User Stories\n - Acceptance Criteria",
68
+ "model": "Epic"
69
+ }
70
+ ```
71
+
72
+ The `models/{modelName}/{pathId}` resource adds the typed layer — parsed sections, resolved computed properties, and validation state. This is the "rich" version.
73
+
74
+ **Resource listing**: The server should implement `resources/list` to return all known documents as resources, so an LLM client can browse them. Enumerate `collection.available` and map each to its URI.
75
+
76
+ ---
77
+
78
+ ## Tools
79
+
80
+ Tools handle writes, queries, and computations that go beyond simple data retrieval.
81
+
82
+ ### Query Tools
83
+
84
+ **`query`** — The primary query interface. This is the most powerful tool.
85
+
86
+ ```json
87
+ {
88
+ "name": "query",
89
+ "description": "Query documents of a specific model type with optional filters",
90
+ "inputSchema": {
91
+ "type": "object",
92
+ "properties": {
93
+ "model": { "type": "string", "description": "Model name (e.g. 'Epic', 'Story')" },
94
+ "where": {
95
+ "type": "array",
96
+ "items": {
97
+ "type": "object",
98
+ "properties": {
99
+ "path": { "type": "string", "description": "Dot-path field (e.g. 'meta.status', 'title')" },
100
+ "operator": { "type": "string", "enum": ["eq","neq","gt","lt","gte","lte","in","notIn","contains","startsWith","endsWith","regex","exists","notExists"] },
101
+ "value": {}
102
+ },
103
+ "required": ["path"]
104
+ },
105
+ "description": "Filter conditions (AND logic)"
106
+ },
107
+ "select": {
108
+ "type": "object",
109
+ "properties": {
110
+ "sections": { "type": "array", "items": { "type": "string" } },
111
+ "computed": { "type": "array", "items": { "type": "string" } },
112
+ "related": { "type": "array", "items": { "type": "string" } }
113
+ },
114
+ "description": "Which optional fields to include in results"
115
+ },
116
+ "limit": { "type": "string", "enum": ["all", "first", "last", "count"] }
117
+ },
118
+ "required": ["model"]
119
+ }
120
+ }
121
+ ```
122
+
123
+ Implementation maps directly to the `CollectionQuery` chain:
124
+
125
+ ```ts
126
+ let q = collection.query(modelDef);
127
+ for (const condition of input.where ?? []) {
128
+ q = q.where(condition.path, condition.operator ?? "eq", condition.value);
129
+ }
130
+ const results = await q[input.limit ?? "fetchAll"]();
131
+ return results.map(r => r.toJSON(input.select));
132
+ ```
133
+
134
+ **`search_content`** — Full-text search across document bodies.
135
+
136
+ ```json
137
+ {
138
+ "name": "search_content",
139
+ "description": "Search document content and titles for a text pattern",
140
+ "inputSchema": {
141
+ "properties": {
142
+ "pattern": { "type": "string", "description": "Text or regex pattern to search for" },
143
+ "model": { "type": "string", "description": "Optional: limit to a specific model type" }
144
+ },
145
+ "required": ["pattern"]
146
+ }
147
+ }
148
+ ```
149
+
150
+ Iterates `collection.documents`, calls `doc.toText()` or `doc.content`, and does string/regex matching. Returns matching document IDs with surrounding context snippets.
151
+
152
+ **`text_search`** — File-level text search using ripgrep.
153
+
154
+ ```json
155
+ {
156
+ "name": "text_search",
157
+ "description": "Search file contents with pattern matching using ripgrep. Returns distinct file matches by default, or line-level detail with expanded=true.",
158
+ "inputSchema": {
159
+ "properties": {
160
+ "pattern": { "type": "string", "description": "Text or regex pattern to search for" },
161
+ "expanded": { "type": "boolean", "description": "Return line-level matches instead of just file paths", "default": false },
162
+ "include": { "type": "string", "description": "Glob filter (e.g. '*.md')" },
163
+ "exclude": { "type": "string", "description": "Glob filter (e.g. 'node_modules')" },
164
+ "ignoreCase": { "type": "boolean", "description": "Case insensitive search", "default": false },
165
+ "maxResults": { "type": "number", "description": "Limit number of results" }
166
+ },
167
+ "required": ["pattern"]
168
+ }
169
+ }
170
+ ```
171
+
172
+ Returns `{ files: string[], count: number }` by default, or `{ files: [{ file, matches: [{ line, column?, content }] }], count }` when `expanded` is true.
173
+
174
+ ### Validation Tools
175
+
176
+ **`validate`** — Validate one or more documents.
177
+
178
+ ```json
179
+ {
180
+ "name": "validate",
181
+ "description": "Validate documents against their model schemas. Returns errors by document.",
182
+ "inputSchema": {
183
+ "properties": {
184
+ "target": { "type": "string", "description": "A pathId ('stories/login'), model name ('Story'), or 'all'" }
185
+ }
186
+ }
187
+ }
188
+ ```
189
+
190
+ Maps to `validateDocument(doc, definition)` for individual docs, or iterates all instances of a model. Returns structured validation results with field-level errors.
191
+
192
+ ### Mutation Tools
193
+
194
+ **`create_document`** — Create a new document for a model type.
195
+
196
+ ```json
197
+ {
198
+ "name": "create_document",
199
+ "description": "Create a new markdown document for a model, with frontmatter and section scaffolding",
200
+ "inputSchema": {
201
+ "properties": {
202
+ "model": { "type": "string", "description": "Model name" },
203
+ "title": { "type": "string", "description": "Document title" },
204
+ "meta": { "type": "object", "description": "Frontmatter values (merged over defaults)" },
205
+ "content": { "type": "string", "description": "Optional: full markdown body (overrides scaffolding)" }
206
+ },
207
+ "required": ["model", "title"]
208
+ }
209
+ }
210
+ ```
211
+
212
+ Mirrors what `cnotes create` does: applies Zod defaults, merges with definition defaults, checks for templates, scaffolds section headings, writes via `collection.saveItem()`. Returns the new pathId and full document.
213
+
214
+ **`update_document`** — Modify an existing document's frontmatter or content.
215
+
216
+ ```json
217
+ {
218
+ "name": "update_document",
219
+ "description": "Update a document's frontmatter metadata and/or markdown content",
220
+ "inputSchema": {
221
+ "properties": {
222
+ "pathId": { "type": "string" },
223
+ "meta": { "type": "object", "description": "Fields to merge into frontmatter" },
224
+ "content": { "type": "string", "description": "Replace the entire markdown body" }
225
+ },
226
+ "required": ["pathId"]
227
+ }
228
+ }
229
+ ```
230
+
231
+ Uses `doc.replaceContent()` and reconstructs frontmatter, then `doc.save()`.
232
+
233
+ **`update_section`** — Replace, append to, or remove a specific section.
234
+
235
+ ```json
236
+ {
237
+ "name": "update_section",
238
+ "description": "Modify a specific section within a document",
239
+ "inputSchema": {
240
+ "properties": {
241
+ "pathId": { "type": "string" },
242
+ "heading": { "type": "string", "description": "Section heading text" },
243
+ "action": { "type": "string", "enum": ["replace", "append", "remove"] },
244
+ "content": { "type": "string", "description": "Markdown content (required for replace/append)" }
245
+ },
246
+ "required": ["pathId", "heading", "action"]
247
+ }
248
+ }
249
+ ```
250
+
251
+ Maps to `doc.replaceSectionContent()`, `doc.appendToSection()`, or `doc.removeSection()`, then `doc.save()`. This lets an LLM surgically edit one section without touching the rest of the document.
252
+
253
+ **`delete_document`** — Delete a document.
254
+
255
+ ```json
256
+ {
257
+ "name": "delete_document",
258
+ "description": "Permanently delete a document from the collection",
259
+ "inputSchema": {
260
+ "properties": {
261
+ "pathId": { "type": "string" }
262
+ },
263
+ "required": ["pathId"]
264
+ }
265
+ }
266
+ ```
267
+
268
+ Maps to `collection.deleteItem(pathId)`.
269
+
270
+ ### Action Tools
271
+
272
+ **`run_action`** — Execute a registered collection action.
273
+
274
+ ```json
275
+ {
276
+ "name": "run_action",
277
+ "description": "Run a named action registered on the collection",
278
+ "inputSchema": {
279
+ "properties": {
280
+ "name": { "type": "string", "description": "Action name" },
281
+ "args": { "type": "object", "description": "Arguments passed to the action" }
282
+ },
283
+ "required": ["name"]
284
+ }
285
+ }
286
+ ```
287
+
288
+ Maps directly to `collection.runAction(name, ...args)`.
289
+
290
+ ### Introspection Tools
291
+
292
+ **`inspect`** — Get collection structure at a glance.
293
+
294
+ ```json
295
+ {
296
+ "name": "inspect",
297
+ "description": "Get collection metadata: registered models, document counts, available actions",
298
+ "inputSchema": { "properties": {} }
299
+ }
300
+ ```
301
+
302
+ Returns the same structured info as `cnotes inspect` but as JSON. Useful as a first-call tool for an LLM to orient itself.
303
+
304
+ **`list_documents`** — List all document IDs, optionally filtered by model.
305
+
306
+ ```json
307
+ {
308
+ "name": "list_documents",
309
+ "description": "List all document path IDs in the collection, optionally filtered by model",
310
+ "inputSchema": {
311
+ "properties": {
312
+ "model": { "type": "string", "description": "Optional model name to filter by" }
313
+ }
314
+ }
315
+ }
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Prompts
321
+
322
+ MCP prompts are reusable templates that guide LLM interactions. They can accept arguments and return structured messages.
323
+
324
+ ### `create-{modelName}`
325
+
326
+ One prompt per registered model, dynamically generated at server startup.
327
+
328
+ ```json
329
+ {
330
+ "name": "create-epic",
331
+ "description": "Guide for creating a new Epic document with proper schema and sections",
332
+ "arguments": [
333
+ { "name": "title", "required": true, "description": "Title for the new epic" }
334
+ ]
335
+ }
336
+ ```
337
+
338
+ When resolved, returns a message like:
339
+
340
+ ```
341
+ You are creating a new Epic document titled "{title}".
342
+
343
+ ## Required Frontmatter
344
+ - status: enum (created | in-progress | complete), default: "created"
345
+ - priority: enum (low | medium | high), optional
346
+
347
+ ## Sections to Include
348
+ This model has a "Stories" section (hasMany relationship).
349
+ Include a ## Stories heading with ### sub-headings for each story.
350
+
351
+ ## Template
352
+ If a template exists at templates/Epic.mdx, its structure should be followed.
353
+
354
+ Please draft the document content, then I'll create it using the create_document tool.
355
+ ```
356
+
357
+ Built from `introspectMetaSchema(def.meta)` + the sections/relationships on the definition. The prompt teaches the LLM the shape of the model so it can draft well-formed content.
358
+
359
+ ### `review-document`
360
+
361
+ ```json
362
+ {
363
+ "name": "review-document",
364
+ "description": "Review a document for schema compliance, completeness, and quality",
365
+ "arguments": [
366
+ { "name": "pathId", "required": true }
367
+ ]
368
+ }
369
+ ```
370
+
371
+ Resolves by fetching the document, running validation, and building a prompt like:
372
+
373
+ ```
374
+ Review the document "{title}" ({pathId}).
375
+
376
+ ## Model: Story
377
+ ## Validation: 1 error
378
+ - meta.status: Invalid enum value
379
+
380
+ ## Current Frontmatter
381
+ status: "unknown"
382
+ epic: "authentication"
383
+
384
+ ## Current Sections
385
+ - Acceptance Criteria: 3 items
386
+ - Mockups: 0 items (empty)
387
+
388
+ ## Document Content
389
+ [full content here]
390
+
391
+ Please suggest fixes for validation errors and check that all sections are complete.
392
+ ```
393
+
394
+ ### `teach`
395
+
396
+ ```json
397
+ {
398
+ "name": "teach",
399
+ "description": "Get full collection documentation for LLM context (models, API, CLI reference)",
400
+ "arguments": []
401
+ }
402
+ ```
403
+
404
+ Returns the same output as `cnotes teach` — MODELS.md + TOC + CLI.md + PRIMER.md. This is the "onboarding" prompt: paste it in and the LLM understands your entire content structure.
405
+
406
+ ### `query-guide`
407
+
408
+ ```json
409
+ {
410
+ "name": "query-guide",
411
+ "description": "Help construct a query against the collection",
412
+ "arguments": [
413
+ { "name": "intent", "required": true, "description": "What you're trying to find (natural language)" }
414
+ ]
415
+ }
416
+ ```
417
+
418
+ Returns available models, their filterable fields, and operator reference so the LLM can construct a proper `query` tool call.
419
+
420
+ ---
421
+
422
+ ## Architecture Opinions
423
+
424
+ **1. One collection per server instance.** The server takes a content folder path at startup, loads the collection once, and serves it. If you have multiple content collections, run multiple server instances. This keeps URIs clean and avoids routing complexity.
425
+
426
+ **2. Auto-register prompts from model definitions.** At startup, iterate `collection.modelDefinitions` and generate a `create-{name}` prompt for each. This means adding a new model to your content automatically gives the MCP server a new prompt — zero config.
427
+
428
+ **3. `query` is the star tool.** Most LLM interactions will flow through it. Make it flexible (all operators, all field paths) but keep the response lean — default to just `{ id, title, meta }` and require explicit `select` for sections/computed/relationships. This avoids blowing up context with data the LLM didn't ask for.
429
+
430
+ **4. Resource subscriptions for file watching.** MCP supports resource change notifications. Wire up a file watcher on `rootPath` and call `collection.load({ refresh: true })` on changes, then emit `notifications/resources/updated`. This makes the server reactive — edit a markdown file in your editor and the MCP client sees the update immediately.
431
+
432
+ **5. `update_section` is more valuable than `update_document`.** LLMs work better with surgical edits. An LLM that can say "append this acceptance criterion to the Acceptance Criteria section of stories/login" is more useful (and less error-prone) than one that rewrites the entire document body. Lean into the section-based architecture.
433
+
434
+ **6. Lean on `teach` for bootstrapping.** When an MCP client first connects, the `teach` prompt gives it everything it needs. This is unique to contentbase — most MCP servers don't have a built-in "here's how everything works" document. The primer + models summary is a competitive advantage.
435
+
436
+ **7. Validation as a guardrail, not a gate.** The `validate` tool should return structured errors, but `create_document` and `update_document` should save even if validation fails (matching contentbase's current behavior where Zod defaults are applied but documents aren't rejected). Let the LLM decide whether to fix errors.
437
+
438
+ ---
439
+
440
+ ## Suggested Package Structure
441
+
442
+ ```
443
+ contentbase-mcp/
444
+ src/
445
+ server.ts — MCP server setup, collection loading
446
+ resources.ts — Resource handlers (document, schema, toc)
447
+ tools.ts — Tool handlers (query, validate, create, update, etc.)
448
+ prompts.ts — Prompt generators (create-*, review, teach)
449
+ file-watcher.ts — FS watcher for resource change notifications
450
+ package.json — depends on "contentbase" as a peer dependency
451
+ ```
452
+
453
+ The server entry point takes a content folder path and optionally a collection module path (for custom index.ts setups). Mirror the CLI's 3-tier resolution for collection loading since that logic already exists in `src/cli/load-collection.ts` — worth extracting it into the core package so both CLI and MCP server share it.