multisite-cms-mcp 1.0.21 → 1.0.23

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.
@@ -1 +1 @@
1
- {"version":3,"file":"get-conversion-guide.d.ts","sourceRoot":"","sources":["../../src/tools/get-conversion-guide.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AA4tB1H;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAkD1E"}
1
+ {"version":3,"file":"get-conversion-guide.d.ts","sourceRoot":"","sources":["../../src/tools/get-conversion-guide.ts"],"names":[],"mappings":"AAAA,KAAK,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AA80B1H;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAkD1E"}
@@ -230,6 +230,43 @@ This allows same or different paths for list vs detail pages (e.g., /videos for
230
230
  2. **Detail Templates** - Single item pages (blog_post.html)
231
231
  3. **Static Pages** - Fixed content with data-edit-key
232
232
 
233
+ ## Static UI vs Dynamic Content
234
+
235
+ When converting templates, distinguish between:
236
+
237
+ ### 1. Static UI Elements (Keep as-is)
238
+ Logos, icons, decorative backgrounds - these stay as \`/public/\` paths:
239
+ \`\`\`html
240
+ <!-- KEEP these static -->
241
+ <img src="/public/images/logo.png" alt="Company Logo">
242
+ <img src="/public/images/icons/arrow.svg" alt="">
243
+ \`\`\`
244
+
245
+ ### 2. Dynamic Content (Replace with CMS tokens)
246
+ Blog posts, team members, products - replace example content with \`{{#each}}\` loops:
247
+
248
+ \`\`\`html
249
+ <!-- WRONG: Hardcoded example content -->
250
+ <article class="video-card">
251
+ <img src="/images/example-video.jpg" alt="Example Video">
252
+ <h2>My Example Video Title</h2>
253
+ <p>This is a sample description...</p>
254
+ </article>
255
+
256
+ <!-- CORRECT: CMS tokens with loop -->
257
+ {{#each video sort="publishedAt" order="desc"}}
258
+ <article class="video-card">
259
+ {{#if videothumbnail}}
260
+ <img src="{{videothumbnail}}" alt="{{name}}">
261
+ {{/if}}
262
+ <h2><a href="{{url}}">{{name}}</a></h2>
263
+ <p>{{shortdescription}}</p>
264
+ </article>
265
+ {{/each}}
266
+ \`\`\`
267
+
268
+ **Rule of thumb:** If it's site branding/design → keep static. If it's content that changes per item → use CMS tokens.
269
+
233
270
  ## Index Template Pattern (Blog List)
234
271
 
235
272
  \`\`\`html
@@ -616,11 +653,11 @@ document.querySelectorAll('form[data-form-name]').forEach(form => {
616
653
  **Tenant context is automatic** - the system handles associating forms with the correct site.`,
617
654
  assets: `# Asset Path Rules
618
655
 
619
- ## The Rule
656
+ ## Static Assets (CSS, JS, Fonts, Site Images)
620
657
 
621
- **ALL asset paths must use /public/ prefix**
658
+ **ALL static asset paths must use /public/ prefix**
622
659
 
623
- ## HTML Examples
660
+ ### HTML Examples
624
661
 
625
662
  \`\`\`html
626
663
  <!-- CSS -->
@@ -629,14 +666,14 @@ document.querySelectorAll('form[data-form-name]').forEach(form => {
629
666
  <!-- JavaScript -->
630
667
  <script src="/public/js/main.js"></script>
631
668
 
632
- <!-- Images -->
669
+ <!-- Static Images (logos, icons, decorative) -->
633
670
  <img src="/public/images/logo.png" alt="Logo">
634
671
 
635
672
  <!-- Favicon -->
636
673
  <link rel="icon" href="/public/images/favicon.ico">
637
674
  \`\`\`
638
675
 
639
- ## CSS Examples
676
+ ### CSS Examples
640
677
 
641
678
  \`\`\`css
642
679
  /* Correct */
@@ -648,7 +685,7 @@ background-image: url('../images/hero.jpg');
648
685
  background-image: url('images/hero.jpg');
649
686
  \`\`\`
650
687
 
651
- ## Conversion Table
688
+ ### Conversion Table
652
689
 
653
690
  | Original | Converted |
654
691
  |----------|-----------|
@@ -657,6 +694,79 @@ background-image: url('images/hero.jpg');
657
694
  | ./images/logo.png | /public/images/logo.png |
658
695
  | /images/logo.png | /public/images/logo.png |
659
696
 
697
+ ---
698
+
699
+ ## CMS Content Images (CRITICAL)
700
+
701
+ **CMS-managed images are different from static assets. They are uploaded by users through the CMS dashboard and served from a CDN.**
702
+
703
+ ### Two Types of Images
704
+
705
+ 1. **Static assets** (\`/public/images/\`) - Site logos, icons, decorative elements bundled in the package
706
+ 2. **CMS images** (\`{{fieldName}}\`) - User-uploaded content images managed through the dashboard
707
+
708
+ ### CMS Image Handling
709
+
710
+ **ALWAYS use CMS tokens for content images - NEVER hardcode URLs**
711
+
712
+ \`\`\`html
713
+ <!-- CORRECT: CMS token for dynamic content -->
714
+ {{#if mainImage}}
715
+ <img src="{{mainImage}}" alt="{{name}}">
716
+ {{/if}}
717
+
718
+ <!-- WRONG: Hardcoded example content -->
719
+ <img src="/images/example-post.jpg" alt="Example Post">
720
+ \`\`\`
721
+
722
+ ### CMS Image URL Format
723
+
724
+ When users upload images through the CMS dashboard, they are stored and served from:
725
+ \`\`\`
726
+ https://api.fastmode.ai/media/{tenantId}/{filename}
727
+ \`\`\`
728
+
729
+ **Templates should NEVER reference this URL directly** - the CMS automatically provides the correct URL through the token.
730
+
731
+ ### Image Field Examples
732
+
733
+ \`\`\`html
734
+ <!-- Blog post images -->
735
+ <img src="{{mainImage}}" alt="{{name}}">
736
+ <img src="{{thumbnailImage}}" alt="{{name}}">
737
+
738
+ <!-- Team member photos -->
739
+ <img src="{{photo}}" alt="{{name}}">
740
+
741
+ <!-- Author photos -->
742
+ <img src="{{picture}}" alt="{{name}}">
743
+
744
+ <!-- Custom collection images (field slug becomes token) -->
745
+ <img src="{{videothumbnail}}" alt="{{name}}">
746
+ <img src="{{ogimage}}" alt="{{name}}">
747
+ <img src="{{productImage}}" alt="{{name}}">
748
+ \`\`\`
749
+
750
+ ### Common Mistake: Mixing Static and CMS Images
751
+
752
+ \`\`\`html
753
+ <!-- WRONG: Don't mix hardcoded images with CMS content -->
754
+ {{#each video}}
755
+ <img src="/images/video-placeholder.jpg" alt="Video"> <!-- BAD -->
756
+ <h2>{{name}}</h2>
757
+ {{/each}}
758
+
759
+ <!-- CORRECT: All content comes from CMS -->
760
+ {{#each video}}
761
+ {{#if videothumbnail}}
762
+ <img src="{{videothumbnail}}" alt="{{name}}">
763
+ {{/if}}
764
+ <h2>{{name}}</h2>
765
+ {{/each}}
766
+ \`\`\`
767
+
768
+ ---
769
+
660
770
  ## External Assets
661
771
 
662
772
  Keep external URLs unchanged:
@@ -694,6 +804,10 @@ Keep external URLs unchanged:
694
804
  - [ ] Rich text uses {{{triple braces}}}
695
805
  - [ ] Parent refs (../) only inside loops
696
806
  - [ ] Correct field names used
807
+ - [ ] **Static UI images** (logos, icons) use \`/public/\` paths
808
+ - [ ] **Content images** (blog photos, team headshots) use CMS tokens
809
+ - [ ] **No hardcoded example content** - dynamic items use \`{{#each}}\` loops
810
+ - [ ] Index pages iterate over collections, not static example cards
697
811
 
698
812
  ## ✓ Field Names
699
813
 
@@ -1,4 +1,4 @@
1
- type ExampleType = 'manifest_basic' | 'manifest_custom_paths' | 'manifest_minimal_with_ui' | 'blog_index_template' | 'blog_post_template' | 'team_template' | 'downloads_template' | 'authors_template' | 'author_detail_template' | 'custom_collection_template' | 'form_handling' | 'asset_paths' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts' | 'parent_context' | 'equality_comparison';
1
+ type ExampleType = 'manifest_basic' | 'manifest_custom_paths' | 'manifest_minimal_with_ui' | 'blog_index_template' | 'blog_post_template' | 'team_template' | 'downloads_template' | 'authors_template' | 'author_detail_template' | 'custom_collection_template' | 'form_handling' | 'asset_paths' | 'image_handling' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts' | 'parent_context' | 'equality_comparison';
2
2
  /**
3
3
  * Returns example code for a specific pattern
4
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"get-example.d.ts","sourceRoot":"","sources":["../../src/tools/get-example.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GACZ,gBAAgB,GAChB,uBAAuB,GACvB,0BAA0B,GAC1B,qBAAqB,GACrB,oBAAoB,GACpB,eAAe,GACf,oBAAoB,GACpB,kBAAkB,GAClB,wBAAwB,GACxB,4BAA4B,GAC5B,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,CAAC;AA04B1B;;GAEG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAE1E"}
1
+ {"version":3,"file":"get-example.d.ts","sourceRoot":"","sources":["../../src/tools/get-example.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GACZ,gBAAgB,GAChB,uBAAuB,GACvB,0BAA0B,GAC1B,qBAAqB,GACrB,oBAAoB,GACpB,eAAe,GACf,oBAAoB,GACpB,kBAAkB,GAClB,wBAAwB,GACxB,4BAA4B,GAC5B,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,CAAC;AA+gC1B;;GAEG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAE1E"}
@@ -554,6 +554,138 @@ background-image: url('images/hero-bg.jpg');
554
554
  - \`href="css/style.css"\` → \`href="/public/css/style.css"\`
555
555
  - \`src="../images/logo.png"\` → \`src="/public/images/logo.png"\`
556
556
  - \`url('../fonts/custom.woff')\` → \`url('/public/fonts/custom.woff')\``,
557
+ image_handling: `# Image Handling in Templates
558
+
559
+ ## Two Types of Images
560
+
561
+ ### 1. Static/UI Images (Keep as /public/ paths)
562
+ Logos, icons, decorative backgrounds, UI elements - these are bundled with the site:
563
+ \`\`\`html
564
+ <!-- KEEP these as static paths -->
565
+ <img src="/public/images/logo.png" alt="Company Logo">
566
+ <img src="/public/images/icons/arrow.svg" alt="">
567
+ <link rel="icon" href="/public/images/favicon.ico">
568
+ <div style="background-image: url('/public/images/hero-pattern.svg')">
569
+ \`\`\`
570
+
571
+ ### 2. Content Images (Use CMS Tokens)
572
+ Blog photos, team headshots, product images - these are managed through the CMS:
573
+ \`\`\`html
574
+ <!-- USE CMS tokens for content -->
575
+ <img src="{{mainImage}}" alt="{{name}}">
576
+ <img src="{{thumbnailImage}}" alt="{{name}}">
577
+ <img src="{{photo}}" alt="{{name}}">
578
+ \`\`\`
579
+
580
+ ---
581
+
582
+ ## How to Decide: Static vs CMS Token
583
+
584
+ | Image Type | Use | Example |
585
+ |------------|-----|---------|
586
+ | Company logo | \`/public/images/logo.png\` | Header/footer logo |
587
+ | UI icons | \`/public/images/icons/...\` | Arrows, social icons |
588
+ | Decorative backgrounds | \`/public/images/...\` | Hero patterns, textures |
589
+ | Favicon | \`/public/images/favicon.ico\` | Browser tab icon |
590
+ | Blog post images | \`{{mainImage}}\` | Article hero image |
591
+ | Team member photos | \`{{photo}}\` | Staff headshots |
592
+ | Product images | \`{{productImage}}\` | E-commerce photos |
593
+ | User-uploaded content | \`{{fieldName}}\` | Any CMS-managed media |
594
+
595
+ **Rule of thumb:** If the image is part of the site's design/branding → static. If it's content that changes per item → CMS token.
596
+
597
+ ---
598
+
599
+ ## Content Images: Avoid Hardcoded Examples
600
+
601
+ When converting a static site with example blog posts or team members, replace the example content with CMS tokens:
602
+
603
+ **WRONG - Hardcoded example content:**
604
+ \`\`\`html
605
+ <div class="videos-grid">
606
+ <article class="video-card">
607
+ <img src="/images/example-video.jpg" alt="Example">
608
+ <h2>Example Video Title</h2>
609
+ <p>Example description text...</p>
610
+ </article>
611
+ </div>
612
+ \`\`\`
613
+
614
+ **CORRECT - CMS tokens with loop:**
615
+ \`\`\`html
616
+ <div class="videos-grid">
617
+ {{#each video sort="publishedAt" order="desc"}}
618
+ <article class="video-card">
619
+ {{#if videothumbnail}}
620
+ <img src="{{videothumbnail}}" alt="{{name}}">
621
+ {{/if}}
622
+ <h2><a href="{{url}}">{{name}}</a></h2>
623
+ <p>{{shortdescription}}</p>
624
+ </article>
625
+ {{/each}}
626
+ </div>
627
+ \`\`\`
628
+
629
+ ---
630
+
631
+ ## Always Wrap Content Images in Conditionals
632
+
633
+ Since CMS images may not be uploaded, always check:
634
+ \`\`\`html
635
+ {{#if mainImage}}
636
+ <img src="{{mainImage}}" alt="{{name}}" class="hero-image">
637
+ {{/if}}
638
+ \`\`\`
639
+
640
+ Or provide a CSS fallback (not a hardcoded placeholder image):
641
+ \`\`\`html
642
+ {{#if mainImage}}
643
+ <img src="{{mainImage}}" alt="{{name}}">
644
+ {{else}}
645
+ <div class="placeholder-image"></div>
646
+ {{/if}}
647
+ \`\`\`
648
+
649
+ ---
650
+
651
+ ## Image Field Names by Collection
652
+
653
+ **Blog Posts:**
654
+ - \`{{mainImage}}\` - Hero/cover image
655
+ - \`{{thumbnailImage}}\` - Small preview image
656
+
657
+ **Team Members:**
658
+ - \`{{photo}}\` - Headshot
659
+
660
+ **Authors:**
661
+ - \`{{picture}}\` - Author photo
662
+
663
+ **Custom Collections:**
664
+ - **Token name = field slug** defined when creating the collection
665
+ - e.g., field slug \`videothumbnail\` → \`{{videothumbnail}}\`
666
+ - e.g., field slug \`ogimage\` → \`{{ogimage}}\`
667
+ - e.g., field slug \`productImage\` → \`{{productImage}}\`
668
+
669
+ **Use \`get_tenant_schema\` tool to see exact field slugs for a tenant's custom collections.**
670
+
671
+ ---
672
+
673
+ ## How CMS Images Work
674
+
675
+ 1. User creates a custom collection with image fields (e.g., \`videothumbnail\`)
676
+ 2. User uploads images via CMS dashboard
677
+ 3. Template uses the field slug as the token: \`{{videothumbnail}}\`
678
+ 4. CMS automatically provides the correct URL at runtime
679
+
680
+ ---
681
+
682
+ ## Common Mistakes
683
+
684
+ 1. **Replacing logos with CMS tokens** - Keep static UI images as \`/public/\` paths
685
+ 2. **Hardcoding example blog/product images** - Use \`{{#each}}\` loops with CMS tokens
686
+ 3. **Wrong field names** - \`{{photo}}\` for team, \`{{picture}}\` for authors
687
+ 4. **Missing conditionals** - Always wrap optional content images in \`{{#if}}\`
688
+ 5. **Hardcoded placeholder images** - Use CSS placeholders, not image files`,
557
689
  data_edit_keys: `# Inline Editing with data-edit-key
558
690
 
559
691
  Make text editable in the CMS visual editor:
@@ -1 +1 @@
1
- {"version":3,"file":"get-schema.d.ts","sourceRoot":"","sources":["../../src/tools/get-schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CA4ajD"}
1
+ {"version":3,"file":"get-schema.d.ts","sourceRoot":"","sources":["../../src/tools/get-schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CA4fjD"}
@@ -323,6 +323,86 @@ For HTML content that should NOT be escaped:
323
323
 
324
324
  ---
325
325
 
326
+ ## Image Handling in Templates
327
+
328
+ ### Two Types of Images
329
+
330
+ **1. Static/UI Images** - Logos, icons, decorative backgrounds, UI elements
331
+ - These are bundled with the site package
332
+ - Use \`/public/\` paths
333
+ - Keep these as static files
334
+
335
+ **2. Content Images** - Blog photos, team headshots, product images, user-uploaded media
336
+ - These are managed through the CMS dashboard
337
+ - Use CMS tokens like \`{{mainImage}}\`, \`{{photo}}\`
338
+ - Never hardcode example/placeholder content images
339
+
340
+ ### Static UI Images (Keep as /public/ paths)
341
+
342
+ \`\`\`html
343
+ <!-- These are CORRECT - static UI elements -->
344
+ <img src="/public/images/logo.png" alt="Company Logo">
345
+ <img src="/public/images/icons/arrow.svg" alt="">
346
+ <div style="background-image: url('/public/images/hero-bg.jpg')">
347
+ <link rel="icon" href="/public/images/favicon.ico">
348
+ \`\`\`
349
+
350
+ ### Content Images (Use CMS Tokens)
351
+
352
+ \`\`\`html
353
+ <!-- These are CORRECT - CMS-managed content -->
354
+ <img src="{{mainImage}}" alt="{{name}}">
355
+ <img src="{{thumbnailImage}}" alt="{{name}}">
356
+ <img src="{{photo}}" alt="{{name}}">
357
+
358
+ <!-- WRONG - Don't hardcode example/placeholder content -->
359
+ <img src="/images/sample-blog-post.jpg" alt="Sample Post">
360
+ <img src="/images/placeholder-team.jpg" alt="Team Member">
361
+ \`\`\`
362
+
363
+ ### Image Field Pattern
364
+
365
+ Always wrap image fields in conditionals since they may not be set:
366
+
367
+ \`\`\`html
368
+ {{#if mainImage}}
369
+ <img src="{{mainImage}}" alt="{{name}}" class="hero-image">
370
+ {{/if}}
371
+ \`\`\`
372
+
373
+ ### Custom Collection Image Fields
374
+
375
+ For custom collections, the **token name matches the field slug** defined when creating the collection.
376
+
377
+ **Example:** A "video" collection with these fields:
378
+ - \`videothumbnail\` (type: image)
379
+ - \`ogimage\` (type: image)
380
+ - \`shortdescription\` (type: text)
381
+
382
+ **Template uses the field slugs as tokens:**
383
+ \`\`\`html
384
+ {{#each video sort="publishedAt" order="desc"}}
385
+ <article class="video-card">
386
+ {{#if videothumbnail}}
387
+ <img src="{{videothumbnail}}" alt="{{name}}">
388
+ {{/if}}
389
+ <h2><a href="{{url}}">{{name}}</a></h2>
390
+ <p>{{shortdescription}}</p>
391
+ </article>
392
+ {{/each}}
393
+ \`\`\`
394
+
395
+ **To find exact field slugs for a tenant's custom collections, use the \`get_tenant_schema\` tool.**
396
+
397
+ ### Common Mistakes to Avoid
398
+
399
+ 1. **Replacing logos/icons with CMS tokens** - Static UI images should stay as \`/public/\` paths
400
+ 2. **Hardcoding example blog/product content** - Use \`{{#each}}\` loops with CMS tokens
401
+ 3. **Missing \`{{#each}}\` loops** - Dynamic content requires iteration over the collection
402
+ 4. **Wrong field names** - Token must match the exact field slug (case-sensitive)
403
+
404
+ ---
405
+
326
406
  ## CMS Template Configuration
327
407
 
328
408
  Templates are configured in manifest.json using a **flat format** that is the SINGLE SOURCE OF TRUTH for URL paths.
@@ -1 +1 @@
1
- {"version":3,"file":"validate-manifest.d.ts","sourceRoot":"","sources":["../../src/tools/validate-manifest.ts"],"names":[],"mappings":"AA8BA;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAsI5E"}
1
+ {"version":3,"file":"validate-manifest.d.ts","sourceRoot":"","sources":["../../src/tools/validate-manifest.ts"],"names":[],"mappings":"AAyCA;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4N5E"}
@@ -8,7 +8,8 @@ const pageSchema = zod_1.z.object({
8
8
  title: zod_1.z.string(),
9
9
  editable: zod_1.z.boolean().optional(),
10
10
  });
11
- const cmsTemplatesSchema = zod_1.z.object({
11
+ // Built-in collection templates
12
+ const builtInTemplatesSchema = zod_1.z.object({
12
13
  blogIndex: zod_1.z.string().optional(),
13
14
  blogIndexPath: zod_1.z.string().startsWith('/').optional(),
14
15
  blogPost: zod_1.z.string().optional(),
@@ -20,8 +21,17 @@ const cmsTemplatesSchema = zod_1.z.object({
20
21
  authorsIndex: zod_1.z.string().optional(),
21
22
  authorDetail: zod_1.z.string().optional(),
22
23
  authorsIndexPath: zod_1.z.string().startsWith('/').optional(),
24
+ // Legacy format - deprecated
23
25
  collectionPaths: zod_1.z.record(zod_1.z.string().startsWith('/')).optional(),
24
- }).optional();
26
+ collectionTemplates: zod_1.z.record(zod_1.z.object({
27
+ path: zod_1.z.string().startsWith('/').optional(),
28
+ indexTemplate: zod_1.z.string().optional(),
29
+ detailTemplate: zod_1.z.string().optional(),
30
+ })).optional(),
31
+ });
32
+ // Custom collection templates use flat format: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath
33
+ // This schema allows any additional string keys for custom collections
34
+ const cmsTemplatesSchema = builtInTemplatesSchema.catchall(zod_1.z.string()).optional();
25
35
  const manifestSchema = zod_1.z.object({
26
36
  pages: zod_1.z.array(pageSchema),
27
37
  cmsTemplates: cmsTemplatesSchema,
@@ -85,6 +95,7 @@ The manifest.json file is not valid JSON. Check for:
85
95
  }
86
96
  // Check cmsTemplates
87
97
  const templates = m.cmsTemplates;
98
+ const customCollections = new Set();
88
99
  if (templates) {
89
100
  // Check template file paths
90
101
  const templateFields = ['blogIndex', 'blogPost', 'team', 'downloads', 'authorsIndex', 'authorDetail'];
@@ -120,9 +131,85 @@ The manifest.json file is not valid JSON. Check for:
120
131
  if (templates.authorDetail && !templates.authorsIndex) {
121
132
  warnings.push('- authorDetail template defined but no authorsIndex - authors need both templates');
122
133
  }
134
+ // Detect and validate custom collection templates (flat format)
135
+ // Pattern: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath
136
+ const allKeys = Object.keys(templates);
137
+ for (const key of allKeys) {
138
+ // Skip built-in keys
139
+ if (['blogIndex', 'blogIndexPath', 'blogPost', 'blogPostPath', 'team', 'teamPath',
140
+ 'downloads', 'downloadsPath', 'authorsIndex', 'authorDetail', 'authorsIndexPath',
141
+ 'collectionPaths', 'collectionTemplates'].includes(key)) {
142
+ continue;
143
+ }
144
+ // Extract collection slug from key pattern
145
+ let slug = null;
146
+ if (key.endsWith('Index') && !key.endsWith('IndexPath')) {
147
+ slug = key.slice(0, -5); // Remove 'Index'
148
+ }
149
+ else if (key.endsWith('Detail') && !key.endsWith('DetailPath')) {
150
+ slug = key.slice(0, -6); // Remove 'Detail'
151
+ }
152
+ else if (key.endsWith('IndexPath')) {
153
+ slug = key.slice(0, -9); // Remove 'IndexPath'
154
+ }
155
+ else if (key.endsWith('DetailPath')) {
156
+ slug = key.slice(0, -10); // Remove 'DetailPath'
157
+ }
158
+ if (slug && slug.length > 0) {
159
+ customCollections.add(slug);
160
+ }
161
+ }
162
+ // Validate each custom collection
163
+ for (const slug of customCollections) {
164
+ const indexTemplate = templates[`${slug}Index`];
165
+ const detailTemplate = templates[`${slug}Detail`];
166
+ const indexPath = templates[`${slug}IndexPath`];
167
+ const detailPath = templates[`${slug}DetailPath`];
168
+ // Check template file paths
169
+ if (typeof indexTemplate === 'string') {
170
+ if (!indexTemplate.endsWith('.html')) {
171
+ warnings.push(`- cmsTemplates.${slug}Index: should end with .html (got: ${indexTemplate})`);
172
+ }
173
+ }
174
+ if (typeof detailTemplate === 'string') {
175
+ if (!detailTemplate.endsWith('.html')) {
176
+ warnings.push(`- cmsTemplates.${slug}Detail: should end with .html (got: ${detailTemplate})`);
177
+ }
178
+ }
179
+ // Check path format
180
+ if (typeof indexPath === 'string' && !indexPath.startsWith('/')) {
181
+ errors.push(`- cmsTemplates.${slug}IndexPath: must start with "/" (got: ${indexPath})`);
182
+ }
183
+ if (typeof detailPath === 'string' && !detailPath.startsWith('/')) {
184
+ errors.push(`- cmsTemplates.${slug}DetailPath: must start with "/" (got: ${detailPath})`);
185
+ }
186
+ // Check consistency - if you have one, you should have the others
187
+ if (indexTemplate && !indexPath) {
188
+ warnings.push(`- Custom collection "${slug}": has ${slug}Index but no ${slug}IndexPath`);
189
+ }
190
+ if (detailTemplate && !detailPath) {
191
+ warnings.push(`- Custom collection "${slug}": has ${slug}Detail but no ${slug}DetailPath`);
192
+ }
193
+ if (indexPath && !indexTemplate) {
194
+ warnings.push(`- Custom collection "${slug}": has ${slug}IndexPath but no ${slug}Index template`);
195
+ }
196
+ if (detailPath && !detailTemplate) {
197
+ warnings.push(`- Custom collection "${slug}": has ${slug}DetailPath but no ${slug}Detail template`);
198
+ }
199
+ // Note: indexPath and detailPath can be the same (e.g., both "/videos")
200
+ // The system distinguishes by whether a slug segment exists after the path
201
+ }
202
+ // Warn about deprecated collectionTemplates format
203
+ if (templates.collectionTemplates) {
204
+ warnings.push('- cmsTemplates.collectionTemplates: This nested format is deprecated. Use flat format instead: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath');
205
+ }
123
206
  }
124
207
  // Build result
125
208
  let output = '';
209
+ // Build custom collections summary
210
+ const customCollectionsSummary = customCollections.size > 0
211
+ ? `\n- Custom collections: ${Array.from(customCollections).join(', ')} (using flat format: {slug}Index, {slug}Detail, {slug}IndexPath, {slug}DetailPath)`
212
+ : '';
126
213
  if (errors.length === 0 && warnings.length === 0) {
127
214
  output = `✅ MANIFEST VALID
128
215
 
@@ -130,7 +217,7 @@ The manifest.json structure is correct.
130
217
 
131
218
  Summary:
132
219
  - ${m.pages?.length || 0} static page(s) defined
133
- - CMS templates: ${templates ? 'configured in manifest' : 'not configured (can be set via Settings → CMS Templates after upload)'}
220
+ - CMS templates: ${templates ? 'configured in manifest' : 'not configured (can be set via Settings → CMS Templates after upload)'}${customCollectionsSummary}
134
221
  - Head HTML: ${m.defaultHeadHtml ? 'configured' : 'not configured'}`;
135
222
  }
136
223
  else if (errors.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multisite-cms-mcp",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "MCP server for AI-assisted website conversion to CMS format. Provides validation, examples, and schema tools.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {