multisite-cms-mcp 1.2.2 → 1.2.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.
- package/dist/index.js +7 -2
- package/dist/tools/deploy-package.d.ts +2 -1
- package/dist/tools/deploy-package.d.ts.map +1 -1
- package/dist/tools/deploy-package.js +64 -1
- package/dist/tools/get-example.d.ts +1 -1
- package/dist/tools/get-example.d.ts.map +1 -1
- package/dist/tools/get-example.js +91 -0
- package/dist/tools/validate-template.d.ts.map +1 -1
- package/dist/tools/validate-template.js +22 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -127,6 +127,7 @@ const TOOLS = [
|
|
|
127
127
|
'featured_posts',
|
|
128
128
|
'parent_context',
|
|
129
129
|
'equality_comparison',
|
|
130
|
+
'youtube_embed',
|
|
130
131
|
],
|
|
131
132
|
description: 'The type of example to retrieve',
|
|
132
133
|
},
|
|
@@ -192,7 +193,7 @@ const TOOLS = [
|
|
|
192
193
|
},
|
|
193
194
|
{
|
|
194
195
|
name: 'deploy_package',
|
|
195
|
-
description: 'Deploy a website package (.zip file) to Fast Mode.
|
|
196
|
+
description: 'Deploy a website package (.zip file) to Fast Mode. Will check for GitHub sync and block deployment if connected (to prevent conflicts). Use force:true to override. Opens browser for authentication if not already logged in.',
|
|
196
197
|
inputSchema: {
|
|
197
198
|
type: 'object',
|
|
198
199
|
properties: {
|
|
@@ -208,6 +209,10 @@ const TOOLS = [
|
|
|
208
209
|
type: 'string',
|
|
209
210
|
description: 'Optional: Create a new project with this name and deploy to it. Required if projectId is not provided and no projects exist.',
|
|
210
211
|
},
|
|
212
|
+
force: {
|
|
213
|
+
type: 'boolean',
|
|
214
|
+
description: 'Optional: Skip GitHub connection check and deploy anyway. Use with caution - may cause conflicts if GitHub auto-deploy is enabled.',
|
|
215
|
+
},
|
|
211
216
|
},
|
|
212
217
|
required: ['packagePath'],
|
|
213
218
|
},
|
|
@@ -345,7 +350,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
345
350
|
result = await (0, create_site_1.createSite)(params.name, params.subdomain);
|
|
346
351
|
break;
|
|
347
352
|
case 'deploy_package':
|
|
348
|
-
result = await (0, deploy_package_1.deployPackage)(params.packagePath, params.projectId, params.projectName);
|
|
353
|
+
result = await (0, deploy_package_1.deployPackage)(params.packagePath, params.projectId, params.projectName, params.force);
|
|
349
354
|
break;
|
|
350
355
|
case 'get_field_types':
|
|
351
356
|
result = await (0, get_field_types_1.getFieldTypes)();
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @param packagePath - Path to the zip file
|
|
5
5
|
* @param projectId - Optional: Deploy to existing project with this ID
|
|
6
6
|
* @param projectName - Optional: Create new project with this name
|
|
7
|
+
* @param force - Optional: Skip GitHub connection check and deploy anyway
|
|
7
8
|
*/
|
|
8
|
-
export declare function deployPackage(packagePath: string, projectId?: string, projectName?: string): Promise<string>;
|
|
9
|
+
export declare function deployPackage(packagePath: string, projectId?: string, projectName?: string, force?: boolean): Promise<string>;
|
|
9
10
|
//# sourceMappingURL=deploy-package.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy-package.d.ts","sourceRoot":"","sources":["../../src/tools/deploy-package.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deploy-package.d.ts","sourceRoot":"","sources":["../../src/tools/deploy-package.ts"],"names":[],"mappings":"AAmRA;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,CAAC,CAwJjB"}
|
|
@@ -49,6 +49,60 @@ async function listExistingProjects() {
|
|
|
49
49
|
}
|
|
50
50
|
return response.data;
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if a project has GitHub connected
|
|
54
|
+
*/
|
|
55
|
+
async function checkGitHubConnection(tenantId) {
|
|
56
|
+
const response = await (0, api_client_1.apiRequest)('/api/github/site-connection', { tenantId });
|
|
57
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
58
|
+
// If we can't check, assume not connected (fail open for better UX)
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return response.data;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Format the GitHub blocking message
|
|
65
|
+
*/
|
|
66
|
+
function formatGitHubBlockMessage(repo, branch, subdomain) {
|
|
67
|
+
return `# ⚠️ GitHub Sync Detected
|
|
68
|
+
|
|
69
|
+
This project has GitHub connected and will auto-deploy from:
|
|
70
|
+
- **Repository:** ${repo}
|
|
71
|
+
- **Branch:** ${branch}
|
|
72
|
+
|
|
73
|
+
## Why This Matters
|
|
74
|
+
|
|
75
|
+
Deploying via MCP while GitHub is connected can cause conflicts:
|
|
76
|
+
- Your MCP changes could be overwritten on the next GitHub push
|
|
77
|
+
- Or this deploy could overwrite changes from GitHub
|
|
78
|
+
|
|
79
|
+
## Options
|
|
80
|
+
|
|
81
|
+
### Option 1: Push to GitHub Instead (Recommended)
|
|
82
|
+
Push your changes to the connected repository and let GitHub handle the deploy:
|
|
83
|
+
\`\`\`bash
|
|
84
|
+
git push origin ${branch}
|
|
85
|
+
\`\`\`
|
|
86
|
+
|
|
87
|
+
### Option 2: Disconnect GitHub First
|
|
88
|
+
Go to **app.fastmode.ai** → Settings → GitHub and disconnect the repository.
|
|
89
|
+
|
|
90
|
+
### Option 3: Force Deploy Anyway
|
|
91
|
+
If you understand the risks and want to proceed:
|
|
92
|
+
\`\`\`
|
|
93
|
+
deploy_package(
|
|
94
|
+
packagePath: "./your-site.zip",
|
|
95
|
+
projectId: "...",
|
|
96
|
+
force: true
|
|
97
|
+
)
|
|
98
|
+
\`\`\`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
**Dashboard:** https://app.fastmode.ai
|
|
103
|
+
**Site:** https://${subdomain}.fastmode.ai
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
52
106
|
/**
|
|
53
107
|
* Create a new site/project
|
|
54
108
|
*/
|
|
@@ -176,8 +230,9 @@ deploy_package(
|
|
|
176
230
|
* @param packagePath - Path to the zip file
|
|
177
231
|
* @param projectId - Optional: Deploy to existing project with this ID
|
|
178
232
|
* @param projectName - Optional: Create new project with this name
|
|
233
|
+
* @param force - Optional: Skip GitHub connection check and deploy anyway
|
|
179
234
|
*/
|
|
180
|
-
async function deployPackage(packagePath, projectId, projectName) {
|
|
235
|
+
async function deployPackage(packagePath, projectId, projectName, force) {
|
|
181
236
|
// Check authentication
|
|
182
237
|
if (await (0, api_client_1.needsAuthentication)()) {
|
|
183
238
|
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
@@ -263,6 +318,14 @@ Please try again or create the project manually at app.fastmode.ai
|
|
|
263
318
|
const project = projects.find(p => p.id === targetProjectId);
|
|
264
319
|
subdomain = project?.subdomain || '';
|
|
265
320
|
}
|
|
321
|
+
// Check for GitHub connection (unless force is true or it's a new project)
|
|
322
|
+
if (!force && !projectName && targetProjectId) {
|
|
323
|
+
const githubStatus = await checkGitHubConnection(targetProjectId);
|
|
324
|
+
if (githubStatus?.connected && githubStatus.repo) {
|
|
325
|
+
// GitHub is connected - block deployment
|
|
326
|
+
return formatGitHubBlockMessage(githubStatus.repo, githubStatus.branch || 'main', subdomain);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
266
329
|
// Upload the package
|
|
267
330
|
console.error(`Deploying to ${subdomain}.fastmode.ai...`);
|
|
268
331
|
const uploadResult = await uploadPackage(targetProjectId, zipBuffer);
|
|
@@ -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' | 'image_handling' | 'relation_fields' | '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' | 'relation_fields' | 'data_edit_keys' | 'each_loop' | 'conditional_if' | 'nested_fields' | 'featured_posts' | 'parent_context' | 'equality_comparison' | 'youtube_embed';
|
|
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,iBAAiB,GACjB,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,CAAC;
|
|
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,iBAAiB,GACjB,gBAAgB,GAChB,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,gBAAgB,GAChB,qBAAqB,GACrB,eAAe,CAAC;AAsvCpB;;GAEG;AACH,wBAAsB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAE1E"}
|
|
@@ -1155,6 +1155,97 @@ The most common pattern for "Related Posts" or "Other Items" sections:
|
|
|
1155
1155
|
- Filter by current author/category - use \`{{#if (eq author.slug ../slug)}}\`
|
|
1156
1156
|
- Highlight active items - use \`{{#if (eq ...)}}\`
|
|
1157
1157
|
- Show badges for specific statuses - use \`{{#eq status "value"}}\``,
|
|
1158
|
+
youtube_embed: `# YouTube Video Embeds
|
|
1159
|
+
|
|
1160
|
+
**IMPORTANT:** YouTube iframes require specific attributes to work correctly. Missing attributes will cause Error 150/153 "Video player configuration error".
|
|
1161
|
+
|
|
1162
|
+
## Correct YouTube Embed Format
|
|
1163
|
+
|
|
1164
|
+
\`\`\`html
|
|
1165
|
+
<iframe
|
|
1166
|
+
src="{{videoUrl}}"
|
|
1167
|
+
title="{{name}}"
|
|
1168
|
+
frameborder="0"
|
|
1169
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
1170
|
+
referrerpolicy="strict-origin-when-cross-origin"
|
|
1171
|
+
allowfullscreen
|
|
1172
|
+
></iframe>
|
|
1173
|
+
\`\`\`
|
|
1174
|
+
|
|
1175
|
+
## Required Attributes
|
|
1176
|
+
|
|
1177
|
+
| Attribute | Required | Why |
|
|
1178
|
+
|-----------|----------|-----|
|
|
1179
|
+
| \`referrerpolicy="strict-origin-when-cross-origin"\` | **YES** | YouTube blocks embeds without this |
|
|
1180
|
+
| \`allowfullscreen\` | Recommended | Enables fullscreen button |
|
|
1181
|
+
| \`allow="..."\` | Recommended | Enables player features |
|
|
1182
|
+
| \`frameborder="0"\` | Recommended | Removes border |
|
|
1183
|
+
| \`title="..."\` | Recommended | Accessibility for screen readers |
|
|
1184
|
+
|
|
1185
|
+
## Common Mistake (Causes Error 153)
|
|
1186
|
+
|
|
1187
|
+
\`\`\`html
|
|
1188
|
+
<!-- ❌ WRONG - Missing referrerpolicy -->
|
|
1189
|
+
<iframe src="{{videoUrl}}" allowfullscreen></iframe>
|
|
1190
|
+
|
|
1191
|
+
<!-- ✅ CORRECT - All required attributes -->
|
|
1192
|
+
<iframe
|
|
1193
|
+
src="{{videoUrl}}"
|
|
1194
|
+
referrerpolicy="strict-origin-when-cross-origin"
|
|
1195
|
+
allowfullscreen
|
|
1196
|
+
></iframe>
|
|
1197
|
+
\`\`\`
|
|
1198
|
+
|
|
1199
|
+
## Video URL Format
|
|
1200
|
+
|
|
1201
|
+
Store YouTube URLs in embed format:
|
|
1202
|
+
\`\`\`
|
|
1203
|
+
https://www.youtube.com/embed/VIDEO_ID
|
|
1204
|
+
\`\`\`
|
|
1205
|
+
|
|
1206
|
+
**NOT** watch format:
|
|
1207
|
+
\`\`\`
|
|
1208
|
+
https://www.youtube.com/watch?v=VIDEO_ID ❌
|
|
1209
|
+
\`\`\`
|
|
1210
|
+
|
|
1211
|
+
## Full Example with Conditional
|
|
1212
|
+
|
|
1213
|
+
\`\`\`html
|
|
1214
|
+
<div class="video-container">
|
|
1215
|
+
{{#if videoUrl}}
|
|
1216
|
+
<iframe
|
|
1217
|
+
src="{{videoUrl}}"
|
|
1218
|
+
title="{{name}}"
|
|
1219
|
+
frameborder="0"
|
|
1220
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
1221
|
+
referrerpolicy="strict-origin-when-cross-origin"
|
|
1222
|
+
allowfullscreen
|
|
1223
|
+
></iframe>
|
|
1224
|
+
{{else}}
|
|
1225
|
+
<div class="video-placeholder">
|
|
1226
|
+
<p>Video coming soon</p>
|
|
1227
|
+
</div>
|
|
1228
|
+
{{/if}}
|
|
1229
|
+
</div>
|
|
1230
|
+
\`\`\`
|
|
1231
|
+
|
|
1232
|
+
## Responsive Video CSS
|
|
1233
|
+
|
|
1234
|
+
\`\`\`css
|
|
1235
|
+
.video-container {
|
|
1236
|
+
position: relative;
|
|
1237
|
+
width: 100%;
|
|
1238
|
+
padding-bottom: 56.25%; /* 16:9 aspect ratio */
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
.video-container iframe {
|
|
1242
|
+
position: absolute;
|
|
1243
|
+
top: 0;
|
|
1244
|
+
left: 0;
|
|
1245
|
+
width: 100%;
|
|
1246
|
+
height: 100%;
|
|
1247
|
+
}
|
|
1248
|
+
\`\`\``,
|
|
1158
1249
|
};
|
|
1159
1250
|
/**
|
|
1160
1251
|
* Returns example code for a specific pattern
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-template.d.ts","sourceRoot":"","sources":["../../src/tools/validate-template.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,eAAe,GAAG,eAAe,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AA6O7J;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,YAAY,EAC1B,cAAc,CAAC,EAAE,MAAM,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"validate-template.d.ts","sourceRoot":"","sources":["../../src/tools/validate-template.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG,YAAY,GAAG,WAAW,GAAG,MAAM,GAAG,WAAW,GAAG,eAAe,GAAG,eAAe,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AA6O7J;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,YAAY,EAC1B,cAAc,CAAC,EAAE,MAAM,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAubjB"}
|
|
@@ -302,6 +302,28 @@ async function validateTemplate(html, templateType, collectionSlug, projectId) {
|
|
|
302
302
|
if (wrongAssetPaths.length > 5) {
|
|
303
303
|
warnings.push(`- ...and ${wrongAssetPaths.length - 5} more asset paths that may need /public/ prefix`);
|
|
304
304
|
}
|
|
305
|
+
// Check YouTube iframes for required attributes
|
|
306
|
+
// Match all iframes - we'll check if they're video embeds
|
|
307
|
+
const allIframes = html.match(/<iframe[^>]*>/gi) || [];
|
|
308
|
+
for (const iframe of allIframes) {
|
|
309
|
+
// Check if it's likely a video embed (YouTube URL or template token in src)
|
|
310
|
+
const isYouTubeEmbed = /youtube\.com|youtu\.be/i.test(iframe);
|
|
311
|
+
const hasTemplateSrc = /src=["'][^"']*\{\{[^}]+\}\}[^"']*["']/i.test(iframe);
|
|
312
|
+
if (isYouTubeEmbed || hasTemplateSrc) {
|
|
313
|
+
// Check for referrerpolicy
|
|
314
|
+
if (!/referrerpolicy/i.test(iframe)) {
|
|
315
|
+
errors.push(`- YouTube iframe missing referrerpolicy attribute. Add: referrerpolicy="strict-origin-when-cross-origin" (without this, YouTube will show Error 153)`);
|
|
316
|
+
}
|
|
317
|
+
// Check for allowfullscreen
|
|
318
|
+
if (!/allowfullscreen/i.test(iframe)) {
|
|
319
|
+
warnings.push(`- iframe missing allowfullscreen attribute - fullscreen button won't work`);
|
|
320
|
+
}
|
|
321
|
+
// Check for title (accessibility)
|
|
322
|
+
if (!/title=/i.test(iframe)) {
|
|
323
|
+
suggestions.push(`- Consider adding a title attribute to iframe for accessibility`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
305
327
|
// Validate site tokens
|
|
306
328
|
const siteTokens = html.match(/\{\{site\.(\w+)\}\}/g) || [];
|
|
307
329
|
for (const token of siteTokens) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multisite-cms-mcp",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"description": "MCP server for Fast Mode CMS. Convert websites, validate packages, and deploy directly to Fast Mode. Includes authentication, project creation, schema sync, and one-click deployment.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|