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.
- package/README.md +126 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +201 -0
- package/dist/tools/get-conversion-guide.d.ts +7 -0
- package/dist/tools/get-conversion-guide.d.ts.map +1 -0
- package/dist/tools/get-conversion-guide.js +529 -0
- package/dist/tools/get-example.d.ts +7 -0
- package/dist/tools/get-example.d.ts.map +1 -0
- package/dist/tools/get-example.js +673 -0
- package/dist/tools/get-schema.d.ts +5 -0
- package/dist/tools/get-schema.d.ts.map +1 -0
- package/dist/tools/get-schema.js +148 -0
- package/dist/tools/validate-manifest.d.ts +5 -0
- package/dist/tools/validate-manifest.d.ts.map +1 -0
- package/dist/tools/validate-manifest.js +146 -0
- package/dist/tools/validate-package.d.ts +5 -0
- package/dist/tools/validate-package.d.ts.map +1 -0
- package/dist/tools/validate-package.js +190 -0
- package/dist/tools/validate-template.d.ts +7 -0
- package/dist/tools/validate-template.d.ts.map +1 -0
- package/dist/tools/validate-template.js +234 -0
- package/package.json +49 -0
|
@@ -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
|
+
}
|