multisite-cms-mcp 1.0.0

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.
@@ -0,0 +1,234 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateTemplate = validateTemplate;
4
+ const BLOG_FIELDS = [
5
+ { name: 'name', isRichText: false, isRequired: true },
6
+ { name: 'slug', isRichText: false, isRequired: true },
7
+ { name: 'mainImage', isRichText: false },
8
+ { name: 'thumbnailImage', isRichText: false },
9
+ { name: 'postSummary', isRichText: false },
10
+ { name: 'postBody', isRichText: true },
11
+ { name: 'featured', isRichText: false },
12
+ { name: 'publishedAt', isRichText: false },
13
+ { name: 'url', isRichText: false },
14
+ ];
15
+ const AUTHOR_FIELDS = [
16
+ { name: 'name', isRichText: false, isRequired: true },
17
+ { name: 'slug', isRichText: false, isRequired: true },
18
+ { name: 'bio', isRichText: true },
19
+ { name: 'picture', isRichText: false },
20
+ { name: 'email', isRichText: false },
21
+ { name: 'twitterProfileLink', isRichText: false },
22
+ { name: 'linkedinProfileLink', isRichText: false },
23
+ { name: 'url', isRichText: false },
24
+ ];
25
+ const TEAM_FIELDS = [
26
+ { name: 'name', isRichText: false, isRequired: true },
27
+ { name: 'slug', isRichText: false },
28
+ { name: 'role', isRichText: false },
29
+ { name: 'bio', isRichText: true },
30
+ { name: 'photo', isRichText: false },
31
+ { name: 'email', isRichText: false },
32
+ { name: 'order', isRichText: false },
33
+ ];
34
+ const DOWNLOAD_FIELDS = [
35
+ { name: 'title', isRichText: false, isRequired: true },
36
+ { name: 'slug', isRichText: false },
37
+ { name: 'description', isRichText: false },
38
+ { name: 'fileUrl', isRichText: false },
39
+ { name: 'category', isRichText: false },
40
+ { name: 'order', isRichText: false },
41
+ ];
42
+ // Common wrong field names and their corrections
43
+ const COMMON_MISTAKES = {
44
+ 'title': 'name (for blog posts) or title (for downloads)',
45
+ 'body': 'postBody',
46
+ 'content': 'postBody',
47
+ 'excerpt': 'postSummary',
48
+ 'summary': 'postSummary',
49
+ 'image': 'mainImage or thumbnailImage',
50
+ 'coverImage': 'mainImage',
51
+ 'coverImageUrl': 'mainImage',
52
+ 'heroImage': 'mainImage',
53
+ 'thumbnail': 'thumbnailImage',
54
+ 'date': 'publishedAt',
55
+ 'createdAt': 'publishedAt',
56
+ 'author_name': 'author.name',
57
+ 'authorName': 'author.name',
58
+ 'link': 'url',
59
+ 'href': 'url',
60
+ 'headshot': 'photo (for team) or picture (for authors)',
61
+ 'headshotUrl': 'photo (for team) or picture (for authors)',
62
+ 'position': 'role',
63
+ 'jobTitle': 'role',
64
+ };
65
+ /**
66
+ * Validates an HTML template for correct CMS token usage
67
+ */
68
+ async function validateTemplate(html, templateType, collectionSlug) {
69
+ const errors = [];
70
+ const warnings = [];
71
+ const suggestions = [];
72
+ // Get expected fields based on template type
73
+ let expectedFields = [];
74
+ let collectionName = '';
75
+ switch (templateType) {
76
+ case 'blog_index':
77
+ case 'blog_post':
78
+ expectedFields = BLOG_FIELDS;
79
+ collectionName = 'blogs';
80
+ break;
81
+ case 'authors_index':
82
+ case 'author_detail':
83
+ expectedFields = AUTHOR_FIELDS;
84
+ collectionName = 'authors';
85
+ break;
86
+ case 'team':
87
+ expectedFields = TEAM_FIELDS;
88
+ collectionName = 'team';
89
+ break;
90
+ case 'downloads':
91
+ expectedFields = DOWNLOAD_FIELDS;
92
+ collectionName = 'downloads';
93
+ break;
94
+ case 'custom_index':
95
+ case 'custom_detail':
96
+ collectionName = collectionSlug || 'collection';
97
+ break;
98
+ case 'static_page':
99
+ // Static pages just use data-edit-key
100
+ break;
101
+ }
102
+ // Extract all tokens from the HTML
103
+ const doubleTokens = html.match(/\{\{([^{}#/]+)\}\}/g) || [];
104
+ const tripleTokens = html.match(/\{\{\{([^{}]+)\}\}\}/g) || [];
105
+ const eachLoops = html.match(/\{\{#each\s+(\w+)[^}]*\}\}/g) || [];
106
+ const conditionals = html.match(/\{\{#if\s+([^}]+)\}\}/g) || [];
107
+ // Check for common mistakes
108
+ for (const token of doubleTokens) {
109
+ const fieldName = token.replace(/\{\{|\}\}/g, '').trim();
110
+ // Skip control structures
111
+ if (fieldName.startsWith('#') || fieldName.startsWith('/') || fieldName === 'else' || fieldName.startsWith('@')) {
112
+ continue;
113
+ }
114
+ // Check against common mistakes
115
+ const baseName = fieldName.split('.')[0];
116
+ if (COMMON_MISTAKES[baseName]) {
117
+ errors.push(`- Wrong token: {{${fieldName}}} → Use ${COMMON_MISTAKES[baseName]}`);
118
+ }
119
+ }
120
+ // Check richText fields are using triple braces
121
+ for (const field of expectedFields) {
122
+ if (field.isRichText) {
123
+ const doublePattern = new RegExp(`\\{\\{${field.name}\\}\\}`, 'g');
124
+ const triplePattern = new RegExp(`\\{\\{\\{${field.name}\\}\\}\\}`, 'g');
125
+ const hasDouble = doublePattern.test(html);
126
+ const hasTriple = triplePattern.test(html);
127
+ if (hasDouble && !hasTriple) {
128
+ errors.push(`- {{${field.name}}} must use triple braces {{{${field.name}}}} because it contains HTML`);
129
+ }
130
+ }
131
+ }
132
+ // Check for nested author fields in double braces (should be fine)
133
+ const authorBioDouble = /\{\{author\.bio\}\}/g.test(html);
134
+ if (authorBioDouble) {
135
+ errors.push('- {{author.bio}} must use triple braces {{{author.bio}}} because it contains HTML');
136
+ }
137
+ // Check loop syntax for index templates
138
+ if (templateType.endsWith('_index') || templateType === 'team' || templateType === 'downloads') {
139
+ if (eachLoops.length === 0) {
140
+ warnings.push(`- No {{#each}} loop found - index templates usually need a loop to display items`);
141
+ }
142
+ else {
143
+ // Check if loop uses correct collection name
144
+ for (const loop of eachLoops) {
145
+ const match = loop.match(/\{\{#each\s+(\w+)/);
146
+ if (match) {
147
+ const usedCollection = match[1];
148
+ if (collectionName && usedCollection !== collectionName) {
149
+ warnings.push(`- Loop uses '${usedCollection}' but expected '${collectionName}'`);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ // Check for {{#each}} without closing {{/each}}
156
+ const eachOpens = (html.match(/\{\{#each/g) || []).length;
157
+ const eachCloses = (html.match(/\{\{\/each\}\}/g) || []).length;
158
+ if (eachOpens !== eachCloses) {
159
+ errors.push(`- Unbalanced {{#each}} loops: ${eachOpens} opens, ${eachCloses} closes`);
160
+ }
161
+ // Check for {{#if}} without closing {{/if}}
162
+ const ifOpens = (html.match(/\{\{#if/g) || []).length;
163
+ const ifCloses = (html.match(/\{\{\/if\}\}/g) || []).length;
164
+ if (ifOpens !== ifCloses) {
165
+ errors.push(`- Unbalanced {{#if}} conditionals: ${ifOpens} opens, ${ifCloses} closes`);
166
+ }
167
+ // Check for {{#unless}} without closing
168
+ const unlessOpens = (html.match(/\{\{#unless/g) || []).length;
169
+ const unlessCloses = (html.match(/\{\{\/unless\}\}/g) || []).length;
170
+ if (unlessOpens !== unlessCloses) {
171
+ errors.push(`- Unbalanced {{#unless}}: ${unlessOpens} opens, ${unlessCloses} closes`);
172
+ }
173
+ // Check for data-edit-key in static pages
174
+ if (templateType === 'static_page') {
175
+ const editKeys = html.match(/data-edit-key="[^"]+"/g) || [];
176
+ if (editKeys.length === 0) {
177
+ suggestions.push('- Consider adding data-edit-key attributes to make text editable in the CMS');
178
+ }
179
+ }
180
+ // Check asset paths
181
+ const wrongAssetPaths = html.match(/(href|src)=["'](?!\/public\/|https?:\/\/|#|\/\?|mailto:|tel:)[^"']+["']/g) || [];
182
+ for (const path of wrongAssetPaths.slice(0, 5)) { // Limit to 5 examples
183
+ if (!path.includes('{{') && !path.includes('data:')) {
184
+ warnings.push(`- Asset path may need /public/ prefix: ${path}`);
185
+ }
186
+ }
187
+ if (wrongAssetPaths.length > 5) {
188
+ warnings.push(`- ...and ${wrongAssetPaths.length - 5} more asset paths that may need /public/ prefix`);
189
+ }
190
+ // Build result
191
+ let output = '';
192
+ if (errors.length === 0 && warnings.length === 0) {
193
+ output = `✅ TEMPLATE VALID
194
+
195
+ The ${templateType} template structure looks correct.
196
+
197
+ Found:
198
+ - ${doubleTokens.length} double-brace tokens
199
+ - ${tripleTokens.length} triple-brace tokens
200
+ - ${eachLoops.length} {{#each}} loop(s)
201
+ - ${conditionals.length} {{#if}} conditional(s)`;
202
+ if (suggestions.length > 0) {
203
+ output += `
204
+
205
+ Suggestions:
206
+ ${suggestions.join('\n')}`;
207
+ }
208
+ }
209
+ else if (errors.length === 0) {
210
+ output = `⚠️ TEMPLATE VALID WITH WARNINGS
211
+
212
+ Warnings:
213
+ ${warnings.join('\n')}`;
214
+ if (suggestions.length > 0) {
215
+ output += `
216
+
217
+ Suggestions:
218
+ ${suggestions.join('\n')}`;
219
+ }
220
+ }
221
+ else {
222
+ output = `❌ TEMPLATE HAS ERRORS
223
+
224
+ Errors (must fix):
225
+ ${errors.join('\n')}`;
226
+ if (warnings.length > 0) {
227
+ output += `
228
+
229
+ Warnings:
230
+ ${warnings.join('\n')}`;
231
+ }
232
+ }
233
+ return output;
234
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "multisite-cms-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for AI-assisted website conversion to CMS format. Provides validation, examples, and schema tools.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "multisite-cms-mcp": "./dist/index.js"
8
+ },
9
+ "type": "commonjs",
10
+ "scripts": {
11
+ "build": "NODE_OPTIONS='--max-old-space-size=4096' tsc --skipLibCheck",
12
+ "dev": "ts-node src/index.ts",
13
+ "start": "node dist/index.js",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "mcp",
18
+ "model-context-protocol",
19
+ "cms",
20
+ "website-conversion",
21
+ "ai-tools",
22
+ "claude",
23
+ "cursor",
24
+ "validation"
25
+ ],
26
+ "author": "Arih Goldstein",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/arihgoldstein/multisite-cms.git",
31
+ "directory": "packages/mcp-server"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18.0.0"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.0.0",
42
+ "zod": "^3.23.8"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^20.11.0",
46
+ "typescript": "^5.3.3",
47
+ "ts-node": "^10.9.2"
48
+ }
49
+ }