multisite-cms-mcp 1.5.4 → 1.5.6
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 +16 -10
- package/dist/index.js +12 -25
- package/dist/tools/create-site.d.ts +2 -1
- package/dist/tools/create-site.d.ts.map +1 -1
- package/dist/tools/create-site.js +76 -1
- package/dist/tools/deploy-package.d.ts +2 -3
- package/dist/tools/deploy-package.d.ts.map +1 -1
- package/dist/tools/deploy-package.js +64 -110
- package/dist/tools/get-conversion-guide.js +1 -1
- package/dist/tools/get-field-types.d.ts +1 -1
- package/dist/tools/get-field-types.d.ts.map +1 -1
- package/dist/tools/get-field-types.js +3 -4
- package/dist/tools/sync-schema.d.ts.map +1 -1
- package/dist/tools/sync-schema.js +144 -102
- package/dist/tools/validate-template.d.ts.map +1 -1
- package/dist/tools/validate-template.js +0 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,8 +9,7 @@ Fast Mode is a modern CMS platform that turns static HTML websites into fully ed
|
|
|
9
9
|
### Key Features
|
|
10
10
|
|
|
11
11
|
- **Keep Your Design** — Use any HTML/CSS. No themes, no page builders, no compromises.
|
|
12
|
-
- **
|
|
13
|
-
- **Custom Collections** — Create any content type you need with custom fields.
|
|
12
|
+
- **Custom Collections** — Create any content type you need (blog posts, team members, products, etc.)
|
|
14
13
|
- **Visual Editor** — Edit content directly on your live site.
|
|
15
14
|
- **Instant Deploy** — Push from GitHub or upload directly. Sites go live in seconds.
|
|
16
15
|
- **Forms & Submissions** — Collect form data without any backend code.
|
|
@@ -34,7 +33,6 @@ These tools work without authentication — perfect for converting and validatin
|
|
|
34
33
|
|
|
35
34
|
| Tool | Description |
|
|
36
35
|
|------|-------------|
|
|
37
|
-
| `get_schema` | Get the complete CMS schema with all collections and fields |
|
|
38
36
|
| `get_field_types` | Get available field types for creating custom fields |
|
|
39
37
|
| `validate_manifest` | Validate your manifest.json file |
|
|
40
38
|
| `validate_template` | Check HTML templates for correct token usage, form handling, and schema validation |
|
|
@@ -122,7 +120,7 @@ Add to your Claude config:
|
|
|
122
120
|
|
|
123
121
|
Ask your AI assistant:
|
|
124
122
|
|
|
125
|
-
> "Convert this website to Fast Mode format
|
|
123
|
+
> "Convert this website to Fast Mode format"
|
|
126
124
|
|
|
127
125
|
The AI will use validation tools to create a proper package structure.
|
|
128
126
|
|
|
@@ -159,7 +157,7 @@ Your site will be available at `https://your-site.fastmode.ai` and you can manag
|
|
|
159
157
|
|
|
160
158
|
## Schema Sync (Critical for Custom Fields)
|
|
161
159
|
|
|
162
|
-
When your templates use custom fields
|
|
160
|
+
When your templates use custom fields, you **must** create them in the CMS before deploying. The MCP server handles this automatically.
|
|
163
161
|
|
|
164
162
|
### Recommended Workflow
|
|
165
163
|
|
|
@@ -200,15 +198,14 @@ When you validate a template with a project ID, the tool will:
|
|
|
200
198
|
| `multiSelect` | Multiple dropdown choices (tags) |
|
|
201
199
|
| `relation` | Link to another collection (author → authors) |
|
|
202
200
|
|
|
203
|
-
### Example: Adding
|
|
201
|
+
### Example: Adding Fields to Existing Collection
|
|
204
202
|
|
|
205
203
|
```json
|
|
206
204
|
{
|
|
207
205
|
"projectId": "my-project",
|
|
208
206
|
"fieldsToAdd": [
|
|
209
207
|
{
|
|
210
|
-
"collectionSlug": "
|
|
211
|
-
"isBuiltin": true,
|
|
208
|
+
"collectionSlug": "posts",
|
|
212
209
|
"fields": [
|
|
213
210
|
{ "slug": "heroImage", "name": "Hero Image", "type": "image" },
|
|
214
211
|
{ "slug": "category", "name": "Category", "type": "select", "options": "Tech,Business,Lifestyle" }
|
|
@@ -239,10 +236,11 @@ When you validate a template with a project ID, the tool will:
|
|
|
239
236
|
```
|
|
240
237
|
|
|
241
238
|
**Features:**
|
|
239
|
+
- ✅ Two-phase creation: collections first, then fields (relation fields always work)
|
|
240
|
+
- ✅ Automatic retry for transient failures
|
|
242
241
|
- ✅ Validates all field types before creating
|
|
243
242
|
- ✅ Skips duplicates automatically (safe to re-run)
|
|
244
|
-
- ✅
|
|
245
|
-
- ✅ Reports detailed results (created/skipped)
|
|
243
|
+
- ✅ Reports detailed summary table with status
|
|
246
244
|
|
|
247
245
|
---
|
|
248
246
|
|
|
@@ -388,6 +386,14 @@ Use the `get_conversion_guide` tool for detailed instructions.
|
|
|
388
386
|
|
|
389
387
|
## Changelog
|
|
390
388
|
|
|
389
|
+
### v1.5.5
|
|
390
|
+
- **Removed `get_schema`** — Use `get_tenant_schema` for project-specific schema
|
|
391
|
+
- **Two-Phase Schema Sync** — Collections created first, then all fields (relation fields always work)
|
|
392
|
+
- **Automatic Retry** — Transient failures are retried automatically
|
|
393
|
+
- **Similar Project Check** — `create_site` warns about similar existing project names
|
|
394
|
+
- **Explicit Project Selection** — `deploy_package` now requires explicit project selection
|
|
395
|
+
- **Improved Output** — Clear summary tables for sync results
|
|
396
|
+
|
|
391
397
|
### v1.5.0
|
|
392
398
|
- **Form Validation** — Validates `data-form` attribute, input names, submit buttons
|
|
393
399
|
- **Static Page Validation** — Validates `{{#each}}` collection references against schema
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
5
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
6
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
-
const get_schema_1 = require("./tools/get-schema");
|
|
8
7
|
const validate_manifest_1 = require("./tools/validate-manifest");
|
|
9
8
|
const validate_template_1 = require("./tools/validate-template");
|
|
10
9
|
const validate_package_1 = require("./tools/validate-package");
|
|
@@ -26,15 +25,6 @@ const server = new index_js_1.Server({
|
|
|
26
25
|
});
|
|
27
26
|
// Define available tools
|
|
28
27
|
const TOOLS = [
|
|
29
|
-
{
|
|
30
|
-
name: 'get_schema',
|
|
31
|
-
description: 'Get the complete CMS schema including all built-in and custom collections with their fields. Use this to understand what content types are available and what tokens to use in templates.',
|
|
32
|
-
inputSchema: {
|
|
33
|
-
type: 'object',
|
|
34
|
-
properties: {},
|
|
35
|
-
required: [],
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
28
|
{
|
|
39
29
|
name: 'validate_manifest',
|
|
40
30
|
description: 'Validate a manifest.json file for the CMS package. Returns errors if the structure is incorrect or required fields are missing.',
|
|
@@ -161,7 +151,7 @@ const TOOLS = [
|
|
|
161
151
|
},
|
|
162
152
|
{
|
|
163
153
|
name: 'get_tenant_schema',
|
|
164
|
-
description: 'Fetch the complete schema for a specific project including
|
|
154
|
+
description: 'Fetch the complete schema for a specific project including all collections and their fields. Use this when converting a website for an existing project to see their specific CMS configuration. Requires authentication.',
|
|
165
155
|
inputSchema: {
|
|
166
156
|
type: 'object',
|
|
167
157
|
properties: {
|
|
@@ -175,7 +165,7 @@ const TOOLS = [
|
|
|
175
165
|
},
|
|
176
166
|
{
|
|
177
167
|
name: 'create_site',
|
|
178
|
-
description: 'Create a new Fast Mode site/project. Opens browser for authentication if not already logged in. After creation, use deploy_package to upload your website.',
|
|
168
|
+
description: 'Create a new Fast Mode site/project. Checks for similar existing project names first. Opens browser for authentication if not already logged in. After creation, use deploy_package to upload your website.',
|
|
179
169
|
inputSchema: {
|
|
180
170
|
type: 'object',
|
|
181
171
|
properties: {
|
|
@@ -187,13 +177,17 @@ const TOOLS = [
|
|
|
187
177
|
type: 'string',
|
|
188
178
|
description: 'Optional custom subdomain (auto-generated from name if not provided)',
|
|
189
179
|
},
|
|
180
|
+
confirmCreate: {
|
|
181
|
+
type: 'boolean',
|
|
182
|
+
description: 'Set to true to skip similar-name check (use only after user confirms they want a new project)',
|
|
183
|
+
},
|
|
190
184
|
},
|
|
191
185
|
required: ['name'],
|
|
192
186
|
},
|
|
193
187
|
},
|
|
194
188
|
{
|
|
195
189
|
name: 'deploy_package',
|
|
196
|
-
description: 'Deploy a website package (.zip file) to Fast Mode. Will check for GitHub sync and block deployment if connected
|
|
190
|
+
description: 'Deploy a website package (.zip file) to Fast Mode. Requires a projectId - use list_projects to find existing projects or create_site to create a new one first. Will check for GitHub sync and block deployment if connected. Use force:true to override.',
|
|
197
191
|
inputSchema: {
|
|
198
192
|
type: 'object',
|
|
199
193
|
properties: {
|
|
@@ -203,11 +197,7 @@ const TOOLS = [
|
|
|
203
197
|
},
|
|
204
198
|
projectId: {
|
|
205
199
|
type: 'string',
|
|
206
|
-
description: '
|
|
207
|
-
},
|
|
208
|
-
projectName: {
|
|
209
|
-
type: 'string',
|
|
210
|
-
description: 'Optional: Create a new project with this name and deploy to it. Required if projectId is not provided and no projects exist.',
|
|
200
|
+
description: 'Project ID to deploy to. Use list_projects to find existing projects, or create_site to create a new one.',
|
|
211
201
|
},
|
|
212
202
|
force: {
|
|
213
203
|
type: 'boolean',
|
|
@@ -219,7 +209,7 @@ const TOOLS = [
|
|
|
219
209
|
},
|
|
220
210
|
{
|
|
221
211
|
name: 'get_field_types',
|
|
222
|
-
description: 'Get the list of available field types for creating new fields in collections. Use this to see what field types you can use with sync_schema. This is NOT the list of fields in your schema - use
|
|
212
|
+
description: 'Get the list of available field types for creating new fields in collections. Use this to see what field types you can use with sync_schema. This is NOT the list of fields in your schema - use get_tenant_schema for that. No authentication required.',
|
|
223
213
|
inputSchema: {
|
|
224
214
|
type: 'object',
|
|
225
215
|
properties: {},
|
|
@@ -228,7 +218,7 @@ const TOOLS = [
|
|
|
228
218
|
},
|
|
229
219
|
{
|
|
230
220
|
name: 'sync_schema',
|
|
231
|
-
description: 'IMPORTANT: Create
|
|
221
|
+
description: 'IMPORTANT: Create collections and fields in Fast Mode. Uses two-phase creation: all collections are created first, then all fields - so relation fields between collections always work regardless of order. Includes automatic retry for transient failures. Skips duplicates automatically. Call this BEFORE deploying. Requires authentication.',
|
|
232
222
|
inputSchema: {
|
|
233
223
|
type: 'object',
|
|
234
224
|
properties: {
|
|
@@ -311,9 +301,6 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
311
301
|
try {
|
|
312
302
|
let result;
|
|
313
303
|
switch (name) {
|
|
314
|
-
case 'get_schema':
|
|
315
|
-
result = await (0, get_schema_1.getSchema)();
|
|
316
|
-
break;
|
|
317
304
|
case 'validate_manifest':
|
|
318
305
|
result = await (0, validate_manifest_1.validateManifest)(params.manifest);
|
|
319
306
|
break;
|
|
@@ -346,10 +333,10 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
346
333
|
result = await (0, get_tenant_schema_1.getTenantSchema)(params.projectId);
|
|
347
334
|
break;
|
|
348
335
|
case 'create_site':
|
|
349
|
-
result = await (0, create_site_1.createSite)(params.name, params.subdomain);
|
|
336
|
+
result = await (0, create_site_1.createSite)(params.name, params.subdomain, params.confirmCreate);
|
|
350
337
|
break;
|
|
351
338
|
case 'deploy_package':
|
|
352
|
-
result = await (0, deploy_package_1.deployPackage)(params.packagePath, params.projectId, params.
|
|
339
|
+
result = await (0, deploy_package_1.deployPackage)(params.packagePath, params.projectId, params.force);
|
|
353
340
|
break;
|
|
354
341
|
case 'get_field_types':
|
|
355
342
|
result = await (0, get_field_types_1.getFieldTypes)();
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @param name - The name of the project
|
|
5
5
|
* @param subdomain - Optional: Custom subdomain (auto-generated from name if not provided)
|
|
6
|
+
* @param confirmCreate - Optional: Skip similar-name check (set to true after user confirms)
|
|
6
7
|
*/
|
|
7
|
-
export declare function createSite(name: string, subdomain?: string): Promise<string>;
|
|
8
|
+
export declare function createSite(name: string, subdomain?: string, confirmCreate?: boolean): Promise<string>;
|
|
8
9
|
//# sourceMappingURL=create-site.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-site.d.ts","sourceRoot":"","sources":["../../src/tools/create-site.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create-site.d.ts","sourceRoot":"","sources":["../../src/tools/create-site.ts"],"names":[],"mappings":"AAiEA;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAwI3G"}
|
|
@@ -3,13 +3,46 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createSite = createSite;
|
|
4
4
|
const api_client_1 = require("../lib/api-client");
|
|
5
5
|
const device_flow_1 = require("../lib/device-flow");
|
|
6
|
+
/**
|
|
7
|
+
* Check for similar project names
|
|
8
|
+
*/
|
|
9
|
+
function findSimilarProjects(name, projects) {
|
|
10
|
+
const normalizedName = name.toLowerCase().trim();
|
|
11
|
+
return projects.filter(project => {
|
|
12
|
+
const projectName = project.name.toLowerCase();
|
|
13
|
+
// Exact match
|
|
14
|
+
if (projectName === normalizedName)
|
|
15
|
+
return true;
|
|
16
|
+
// One contains the other
|
|
17
|
+
if (projectName.includes(normalizedName) || normalizedName.includes(projectName))
|
|
18
|
+
return true;
|
|
19
|
+
// Similar words (check if main words overlap)
|
|
20
|
+
const nameWords = normalizedName.split(/\s+/).filter(w => w.length > 2);
|
|
21
|
+
const projectWords = projectName.split(/\s+/).filter(w => w.length > 2);
|
|
22
|
+
const overlap = nameWords.filter(w => projectWords.some(pw => pw.includes(w) || w.includes(pw)));
|
|
23
|
+
if (overlap.length > 0 && overlap.length >= nameWords.length * 0.5)
|
|
24
|
+
return true;
|
|
25
|
+
return false;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* List existing projects for the authenticated user
|
|
30
|
+
*/
|
|
31
|
+
async function listExistingProjects() {
|
|
32
|
+
const response = await (0, api_client_1.apiRequest)('/api/tenants');
|
|
33
|
+
if ((0, api_client_1.isApiError)(response)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
return response.data;
|
|
37
|
+
}
|
|
6
38
|
/**
|
|
7
39
|
* Create a new Fast Mode site/project
|
|
8
40
|
*
|
|
9
41
|
* @param name - The name of the project
|
|
10
42
|
* @param subdomain - Optional: Custom subdomain (auto-generated from name if not provided)
|
|
43
|
+
* @param confirmCreate - Optional: Skip similar-name check (set to true after user confirms)
|
|
11
44
|
*/
|
|
12
|
-
async function createSite(name, subdomain) {
|
|
45
|
+
async function createSite(name, subdomain, confirmCreate) {
|
|
13
46
|
// Check authentication
|
|
14
47
|
if (await (0, api_client_1.needsAuthentication)()) {
|
|
15
48
|
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
@@ -29,6 +62,48 @@ create_site(name: "My Awesome Website")
|
|
|
29
62
|
\`\`\`
|
|
30
63
|
`;
|
|
31
64
|
}
|
|
65
|
+
// Check for similar projects (unless confirmCreate is true)
|
|
66
|
+
if (!confirmCreate) {
|
|
67
|
+
const existingProjects = await listExistingProjects();
|
|
68
|
+
const similarProjects = findSimilarProjects(name, existingProjects);
|
|
69
|
+
if (similarProjects.length > 0) {
|
|
70
|
+
let output = `# Similar Project Found
|
|
71
|
+
|
|
72
|
+
Before creating "${name}", please note that you have similar existing project(s):
|
|
73
|
+
|
|
74
|
+
`;
|
|
75
|
+
similarProjects.forEach((project, index) => {
|
|
76
|
+
const url = project.customDomain || `${project.subdomain}.fastmode.ai`;
|
|
77
|
+
output += `${index + 1}. **${project.name}**
|
|
78
|
+
- ID: \`${project.id}\`
|
|
79
|
+
- URL: ${url}
|
|
80
|
+
|
|
81
|
+
`;
|
|
82
|
+
});
|
|
83
|
+
output += `---
|
|
84
|
+
|
|
85
|
+
## ACTION REQUIRED
|
|
86
|
+
|
|
87
|
+
**Ask the user:** "I found a similar project called '${similarProjects[0].name}'. Would you like to:
|
|
88
|
+
1. Deploy to the existing project '${similarProjects[0].name}'?
|
|
89
|
+
2. Create a new project called '${name}'?"
|
|
90
|
+
|
|
91
|
+
### If deploying to existing project:
|
|
92
|
+
\`\`\`
|
|
93
|
+
deploy_package(
|
|
94
|
+
packagePath: "./your-site.zip",
|
|
95
|
+
projectId: "${similarProjects[0].id}"
|
|
96
|
+
)
|
|
97
|
+
\`\`\`
|
|
98
|
+
|
|
99
|
+
### If creating new project anyway:
|
|
100
|
+
\`\`\`
|
|
101
|
+
create_site(name: "${name}", confirmCreate: true)
|
|
102
|
+
\`\`\`
|
|
103
|
+
`;
|
|
104
|
+
return output;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
32
107
|
// Generate subdomain from name if not provided
|
|
33
108
|
const finalSubdomain = subdomain || name
|
|
34
109
|
.toLowerCase()
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Deploy a website package to Fast Mode
|
|
3
3
|
*
|
|
4
4
|
* @param packagePath - Path to the zip file
|
|
5
|
-
* @param projectId -
|
|
6
|
-
* @param projectName - Optional: Create new project with this name
|
|
5
|
+
* @param projectId - Project ID to deploy to (use list_projects to find, or create_site to create new)
|
|
7
6
|
* @param force - Optional: Skip GitHub connection check and deploy anyway
|
|
8
7
|
*/
|
|
9
|
-
export declare function deployPackage(packagePath: string, projectId?: string,
|
|
8
|
+
export declare function deployPackage(packagePath: string, projectId?: string, force?: boolean): Promise<string>;
|
|
10
9
|
//# 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":"AA0QA;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,SAAS,CAAC,EAAE,MAAM,EAClB,KAAK,CAAC,EAAE,OAAO,GACd,OAAO,CAAC,MAAM,CAAC,CAmGjB"}
|
|
@@ -103,28 +103,6 @@ deploy_package(
|
|
|
103
103
|
**Site:** https://${subdomain}.fastmode.ai
|
|
104
104
|
`;
|
|
105
105
|
}
|
|
106
|
-
/**
|
|
107
|
-
* Create a new site/project
|
|
108
|
-
*/
|
|
109
|
-
async function createNewSite(name, subdomain) {
|
|
110
|
-
// Generate subdomain from name if not provided
|
|
111
|
-
const finalSubdomain = subdomain || name
|
|
112
|
-
.toLowerCase()
|
|
113
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
114
|
-
.replace(/^-|-$/g, '')
|
|
115
|
-
.slice(0, 30);
|
|
116
|
-
const response = await (0, api_client_1.apiRequest)('/api/tenants', {
|
|
117
|
-
method: 'POST',
|
|
118
|
-
body: {
|
|
119
|
-
name,
|
|
120
|
-
subdomain: finalSubdomain,
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
if ((0, api_client_1.isApiError)(response)) {
|
|
124
|
-
return { error: response.error };
|
|
125
|
-
}
|
|
126
|
-
return response.data;
|
|
127
|
-
}
|
|
128
106
|
/**
|
|
129
107
|
* Upload a package to a specific tenant
|
|
130
108
|
*/
|
|
@@ -187,52 +165,78 @@ async function readPackage(packagePath) {
|
|
|
187
165
|
/**
|
|
188
166
|
* Format project list for user selection
|
|
189
167
|
*/
|
|
190
|
-
function formatProjectChoice(projects) {
|
|
191
|
-
let output = `#
|
|
168
|
+
function formatProjectChoice(projects, packagePath) {
|
|
169
|
+
let output = `# Project Required
|
|
192
170
|
|
|
193
|
-
|
|
171
|
+
You need to specify which project to deploy to.
|
|
172
|
+
|
|
173
|
+
## Your Projects
|
|
194
174
|
|
|
195
175
|
`;
|
|
196
|
-
projects.
|
|
197
|
-
|
|
198
|
-
|
|
176
|
+
if (projects.length > 0) {
|
|
177
|
+
projects.forEach((project, index) => {
|
|
178
|
+
const url = project.customDomain || `${project.subdomain}.fastmode.ai`;
|
|
179
|
+
output += `${index + 1}. **${project.name}**
|
|
199
180
|
- ID: \`${project.id}\`
|
|
200
181
|
- URL: ${url}
|
|
201
182
|
- Status: ${project.site?.status || 'pending'}
|
|
202
183
|
|
|
203
184
|
`;
|
|
204
|
-
|
|
205
|
-
|
|
185
|
+
});
|
|
186
|
+
output += `---
|
|
187
|
+
|
|
188
|
+
## ACTION REQUIRED
|
|
206
189
|
|
|
207
|
-
|
|
190
|
+
**Ask the user:** "Which project should I deploy to? You can choose from the list above, or I can create a new project for you."
|
|
208
191
|
|
|
209
|
-
|
|
192
|
+
### To deploy to an existing project:
|
|
210
193
|
\`\`\`
|
|
211
194
|
deploy_package(
|
|
212
|
-
packagePath: "
|
|
195
|
+
packagePath: "${packagePath}",
|
|
213
196
|
projectId: "${projects[0]?.id || 'project-id-here'}"
|
|
214
197
|
)
|
|
215
198
|
\`\`\`
|
|
216
199
|
|
|
217
|
-
|
|
200
|
+
### To create a NEW project first:
|
|
201
|
+
\`\`\`
|
|
202
|
+
create_site(name: "Project Name")
|
|
203
|
+
\`\`\`
|
|
204
|
+
Then deploy with the returned project ID.
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
output += `You don't have any projects yet.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## ACTION REQUIRED
|
|
213
|
+
|
|
214
|
+
**Ask the user:** "What would you like to name your new project?"
|
|
215
|
+
|
|
216
|
+
Then create the project:
|
|
217
|
+
\`\`\`
|
|
218
|
+
create_site(name: "User's Project Name")
|
|
219
|
+
\`\`\`
|
|
220
|
+
|
|
221
|
+
And deploy with the returned project ID:
|
|
218
222
|
\`\`\`
|
|
219
223
|
deploy_package(
|
|
220
|
-
packagePath: "
|
|
221
|
-
|
|
224
|
+
packagePath: "${packagePath}",
|
|
225
|
+
projectId: "returned-project-id"
|
|
222
226
|
)
|
|
223
227
|
\`\`\`
|
|
224
228
|
`;
|
|
229
|
+
}
|
|
225
230
|
return output;
|
|
226
231
|
}
|
|
227
232
|
/**
|
|
228
233
|
* Deploy a website package to Fast Mode
|
|
229
234
|
*
|
|
230
235
|
* @param packagePath - Path to the zip file
|
|
231
|
-
* @param projectId -
|
|
232
|
-
* @param projectName - Optional: Create new project with this name
|
|
236
|
+
* @param projectId - Project ID to deploy to (use list_projects to find, or create_site to create new)
|
|
233
237
|
* @param force - Optional: Skip GitHub connection check and deploy anyway
|
|
234
238
|
*/
|
|
235
|
-
async function deployPackage(packagePath, projectId,
|
|
239
|
+
async function deployPackage(packagePath, projectId, force) {
|
|
236
240
|
// Check authentication
|
|
237
241
|
if (await (0, api_client_1.needsAuthentication)()) {
|
|
238
242
|
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
@@ -240,26 +244,10 @@ async function deployPackage(packagePath, projectId, projectName, force) {
|
|
|
240
244
|
return authResult.message;
|
|
241
245
|
}
|
|
242
246
|
}
|
|
243
|
-
// If no projectId specified,
|
|
244
|
-
if (!projectId
|
|
247
|
+
// If no projectId specified, list projects and prompt for selection
|
|
248
|
+
if (!projectId) {
|
|
245
249
|
const projects = await listExistingProjects();
|
|
246
|
-
|
|
247
|
-
return formatProjectChoice(projects);
|
|
248
|
-
}
|
|
249
|
-
// No projects exist - need a projectName
|
|
250
|
-
return `# No Projects Found
|
|
251
|
-
|
|
252
|
-
You don't have any FastMode projects yet.
|
|
253
|
-
|
|
254
|
-
To create a new project and deploy, provide a project name:
|
|
255
|
-
|
|
256
|
-
\`\`\`
|
|
257
|
-
deploy_package(
|
|
258
|
-
packagePath: "${packagePath}",
|
|
259
|
-
projectName: "My Website"
|
|
260
|
-
)
|
|
261
|
-
\`\`\`
|
|
262
|
-
`;
|
|
250
|
+
return formatProjectChoice(projects, packagePath);
|
|
263
251
|
}
|
|
264
252
|
// Read the package first
|
|
265
253
|
const packageResult = await readPackage(packagePath);
|
|
@@ -272,55 +260,21 @@ Please provide a valid .zip file path.
|
|
|
272
260
|
`;
|
|
273
261
|
}
|
|
274
262
|
const zipBuffer = packageResult;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// Check if it's an auth error
|
|
282
|
-
if (createResult.error.includes('401') || createResult.error.includes('auth')) {
|
|
283
|
-
const authResult = await (0, device_flow_1.ensureAuthenticated)();
|
|
284
|
-
if (!authResult.authenticated) {
|
|
285
|
-
return authResult.message;
|
|
286
|
-
}
|
|
287
|
-
// Retry
|
|
288
|
-
const retryResult = await createNewSite(projectName);
|
|
289
|
-
if ('error' in retryResult) {
|
|
290
|
-
return `# Failed to Create Project
|
|
291
|
-
|
|
292
|
-
${retryResult.error}
|
|
293
|
-
|
|
294
|
-
Please try again or create the project manually at app.fastmode.ai
|
|
295
|
-
`;
|
|
296
|
-
}
|
|
297
|
-
targetProjectId = retryResult.id;
|
|
298
|
-
subdomain = retryResult.subdomain;
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
return `# Failed to Create Project
|
|
263
|
+
// Get subdomain for the project
|
|
264
|
+
const projects = await listExistingProjects();
|
|
265
|
+
const project = projects.find(p => p.id === projectId);
|
|
266
|
+
const subdomain = project?.subdomain || '';
|
|
267
|
+
if (!project) {
|
|
268
|
+
return `# Project Not Found
|
|
302
269
|
|
|
303
|
-
|
|
270
|
+
Could not find project with ID: \`${projectId}\`
|
|
304
271
|
|
|
305
|
-
|
|
272
|
+
Use \`list_projects\` to see available projects, or \`create_site\` to create a new one.
|
|
306
273
|
`;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
targetProjectId = createResult.id;
|
|
311
|
-
subdomain = createResult.subdomain;
|
|
312
|
-
console.error(`Created new project: ${projectName} (${subdomain}.fastmode.ai)`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
// Get subdomain for existing project
|
|
316
|
-
if (!subdomain && targetProjectId) {
|
|
317
|
-
const projects = await listExistingProjects();
|
|
318
|
-
const project = projects.find(p => p.id === targetProjectId);
|
|
319
|
-
subdomain = project?.subdomain || '';
|
|
320
274
|
}
|
|
321
|
-
// Check for GitHub connection (unless force is true
|
|
322
|
-
if (!force
|
|
323
|
-
const githubStatus = await checkGitHubConnection(
|
|
275
|
+
// Check for GitHub connection (unless force is true)
|
|
276
|
+
if (!force) {
|
|
277
|
+
const githubStatus = await checkGitHubConnection(projectId);
|
|
324
278
|
if (githubStatus?.connected && githubStatus.repo) {
|
|
325
279
|
// GitHub is connected - block deployment
|
|
326
280
|
return formatGitHubBlockMessage(githubStatus.repo, githubStatus.branch || 'main', subdomain);
|
|
@@ -328,7 +282,7 @@ Please try again or create the project manually at app.fastmode.ai
|
|
|
328
282
|
}
|
|
329
283
|
// Upload the package
|
|
330
284
|
console.error(`Deploying to ${subdomain}.fastmode.ai...`);
|
|
331
|
-
const uploadResult = await uploadPackage(
|
|
285
|
+
const uploadResult = await uploadPackage(projectId, zipBuffer);
|
|
332
286
|
if ('error' in uploadResult) {
|
|
333
287
|
// Check if it's an auth error
|
|
334
288
|
if ((0, api_client_1.needsAuthError)({ success: false, error: uploadResult.error, statusCode: 401 })) {
|
|
@@ -337,7 +291,7 @@ Please try again or create the project manually at app.fastmode.ai
|
|
|
337
291
|
return authResult.message;
|
|
338
292
|
}
|
|
339
293
|
// Retry upload
|
|
340
|
-
const retryResult = await uploadPackage(
|
|
294
|
+
const retryResult = await uploadPackage(projectId, zipBuffer);
|
|
341
295
|
if ('error' in retryResult) {
|
|
342
296
|
return `# Upload Failed
|
|
343
297
|
|
|
@@ -349,7 +303,7 @@ Please check:
|
|
|
349
303
|
3. Try again or upload manually at app.fastmode.ai
|
|
350
304
|
`;
|
|
351
305
|
}
|
|
352
|
-
return formatSuccess(subdomain, retryResult
|
|
306
|
+
return formatSuccess(subdomain, retryResult);
|
|
353
307
|
}
|
|
354
308
|
return `# Upload Failed
|
|
355
309
|
|
|
@@ -361,15 +315,15 @@ Please check:
|
|
|
361
315
|
3. Try again or upload manually at app.fastmode.ai
|
|
362
316
|
`;
|
|
363
317
|
}
|
|
364
|
-
return formatSuccess(subdomain, uploadResult
|
|
318
|
+
return formatSuccess(subdomain, uploadResult);
|
|
365
319
|
}
|
|
366
320
|
/**
|
|
367
321
|
* Format success message
|
|
368
322
|
*/
|
|
369
|
-
function formatSuccess(subdomain, result
|
|
370
|
-
return `# Deployment Successful!
|
|
323
|
+
function formatSuccess(subdomain, result) {
|
|
324
|
+
return `# Deployment Successful!
|
|
371
325
|
|
|
372
|
-
|
|
326
|
+
**Package deployed!**
|
|
373
327
|
|
|
374
328
|
## Live Site
|
|
375
329
|
|
|
@@ -781,7 +781,7 @@ ${SECTIONS.checklist}
|
|
|
781
781
|
|
|
782
782
|
Use these MCP tools during conversion:
|
|
783
783
|
|
|
784
|
-
- \`
|
|
784
|
+
- \`get_field_types\` - Get available field types for sync_schema
|
|
785
785
|
- \`get_tenant_schema\` - Get exact collections and fields for a specific project
|
|
786
786
|
- \`get_example\` - Get code examples for specific patterns
|
|
787
787
|
- \`validate_manifest\` - Check your manifest.json
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Returns the list of field types that can be used when CREATING new fields
|
|
5
5
|
* in collections. This is NOT the list of fields in your schema - use
|
|
6
|
-
*
|
|
6
|
+
* get_tenant_schema for that.
|
|
7
7
|
*
|
|
8
8
|
* No authentication required.
|
|
9
9
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-field-types.d.ts","sourceRoot":"","sources":["../../src/tools/get-field-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,EAqE5C,CAAC;AAEF;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"get-field-types.d.ts","sourceRoot":"","sources":["../../src/tools/get-field-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,EAqE5C,CAAC;AAEF;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAyCrD"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Returns the list of field types that can be used when CREATING new fields
|
|
6
6
|
* in collections. This is NOT the list of fields in your schema - use
|
|
7
|
-
*
|
|
7
|
+
* get_tenant_schema for that.
|
|
8
8
|
*
|
|
9
9
|
* No authentication required.
|
|
10
10
|
*/
|
|
@@ -104,7 +104,7 @@ async function getFieldTypes() {
|
|
|
104
104
|
|
|
105
105
|
These are the field types you can use when CREATING new fields with the \`sync_schema\` tool.
|
|
106
106
|
|
|
107
|
-
**Note:** This is NOT the list of fields in your schema. Use \`
|
|
107
|
+
**Note:** This is NOT the list of fields in your schema. Use \`get_tenant_schema\` to see a specific project's schema.
|
|
108
108
|
|
|
109
109
|
## Field Types
|
|
110
110
|
|
|
@@ -118,8 +118,7 @@ When calling \`sync_schema\` to add fields, specify the \`type\` parameter:
|
|
|
118
118
|
{
|
|
119
119
|
"fieldsToAdd": [
|
|
120
120
|
{
|
|
121
|
-
"collectionSlug": "
|
|
122
|
-
"isBuiltin": true,
|
|
121
|
+
"collectionSlug": "posts",
|
|
123
122
|
"fields": [
|
|
124
123
|
{ "slug": "heroImage", "name": "Hero Image", "type": "image" },
|
|
125
124
|
{ "slug": "featured", "name": "Featured", "type": "boolean" },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-schema.d.ts","sourceRoot":"","sources":["../../src/tools/sync-schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,UAAU,WAAW;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED,UAAU,eAAe;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACnC,WAAW,CAAC,EAAE,WAAW,EAAE,CAAC;CAC7B;AAgJD;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"sync-schema.d.ts","sourceRoot":"","sources":["../../src/tools/sync-schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,UAAU,WAAW;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED,UAAU,eAAe;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACnC,WAAW,CAAC,EAAE,WAAW,EAAE,CAAC;CAC7B;AAgJD;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA2VxE"}
|
|
@@ -211,56 +211,34 @@ ${resolved.error}
|
|
|
211
211
|
return `# Error\n\n${existingResult.error}`;
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
|
-
|
|
214
|
+
let existingCollections = Array.isArray(existingResult) ? existingResult : [];
|
|
215
215
|
// Track results
|
|
216
|
-
const
|
|
216
|
+
const collectionResults = [];
|
|
217
|
+
const fieldResults = [];
|
|
217
218
|
const created = { collections: 0, fields: 0 };
|
|
218
219
|
const skipped = { collections: 0, fields: 0 };
|
|
219
|
-
|
|
220
|
+
const failed = { collections: 0, fields: 0 };
|
|
221
|
+
// Build a map of collection slug -> ID (for both existing and newly created)
|
|
222
|
+
const collectionIdMap = new Map();
|
|
223
|
+
const collectionFieldsMap = new Map();
|
|
224
|
+
// Initialize with existing collections
|
|
225
|
+
for (const col of existingCollections) {
|
|
226
|
+
collectionIdMap.set(col.slug.toLowerCase(), col.id);
|
|
227
|
+
collectionFieldsMap.set(col.slug.toLowerCase(), col.fields);
|
|
228
|
+
}
|
|
229
|
+
// ============ PHASE 1: Create/Resolve ALL Collections ============
|
|
220
230
|
if (collections && collections.length > 0) {
|
|
231
|
+
collectionResults.push('### Phase 1: Collections\n');
|
|
221
232
|
for (const col of collections) {
|
|
233
|
+
const slugLower = col.slug.toLowerCase();
|
|
222
234
|
// Check if collection already exists
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
results.push(`Skipped collection "${col.slug}" (already exists)`);
|
|
235
|
+
if (collectionIdMap.has(slugLower)) {
|
|
236
|
+
collectionResults.push(`| ${col.slug} | Skipped | Already exists |`);
|
|
226
237
|
skipped.collections++;
|
|
227
|
-
// But still try to add any new fields
|
|
228
|
-
if (col.fields && col.fields.length > 0) {
|
|
229
|
-
for (const field of col.fields) {
|
|
230
|
-
const fieldExists = existing.fields.some(f => f.slug.toLowerCase() === field.slug.toLowerCase());
|
|
231
|
-
if (fieldExists) {
|
|
232
|
-
results.push(` Skipped field "${field.slug}" (already exists)`);
|
|
233
|
-
skipped.fields++;
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
// Add field to existing collection
|
|
237
|
-
const fieldRes = await (0, api_client_1.apiRequest)(`/api/collections/${existing.id}/fields`, {
|
|
238
|
-
tenantId,
|
|
239
|
-
method: 'POST',
|
|
240
|
-
body: {
|
|
241
|
-
slug: field.slug,
|
|
242
|
-
name: field.name,
|
|
243
|
-
type: field.type,
|
|
244
|
-
description: field.description,
|
|
245
|
-
isRequired: field.isRequired,
|
|
246
|
-
options: field.options,
|
|
247
|
-
referenceCollection: field.referenceCollection,
|
|
248
|
-
},
|
|
249
|
-
});
|
|
250
|
-
if ((0, api_client_1.isApiError)(fieldRes)) {
|
|
251
|
-
results.push(` Failed to add field "${field.slug}": ${fieldRes.error}`);
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
results.push(` Added field "${field.slug}" (${field.type})`);
|
|
255
|
-
created.fields++;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
238
|
continue;
|
|
261
239
|
}
|
|
262
|
-
// Create new collection
|
|
263
|
-
|
|
240
|
+
// Create new collection (WITHOUT fields - those come in Phase 2)
|
|
241
|
+
let createRes = await (0, api_client_1.apiRequest)('/api/collections', {
|
|
264
242
|
tenantId,
|
|
265
243
|
method: 'POST',
|
|
266
244
|
body: {
|
|
@@ -271,93 +249,157 @@ ${resolved.error}
|
|
|
271
249
|
hasSlug: col.hasSlug ?? true,
|
|
272
250
|
},
|
|
273
251
|
});
|
|
252
|
+
// Retry once if failed
|
|
274
253
|
if ((0, api_client_1.isApiError)(createRes)) {
|
|
275
|
-
|
|
254
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
255
|
+
createRes = await (0, api_client_1.apiRequest)('/api/collections', {
|
|
256
|
+
tenantId,
|
|
257
|
+
method: 'POST',
|
|
258
|
+
body: {
|
|
259
|
+
slug: col.slug,
|
|
260
|
+
name: col.name,
|
|
261
|
+
nameSingular: col.nameSingular,
|
|
262
|
+
description: col.description,
|
|
263
|
+
hasSlug: col.hasSlug ?? true,
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
if ((0, api_client_1.isApiError)(createRes)) {
|
|
268
|
+
collectionResults.push(`| ${col.slug} | FAILED | ${createRes.error} |`);
|
|
269
|
+
failed.collections++;
|
|
276
270
|
continue;
|
|
277
271
|
}
|
|
278
|
-
|
|
272
|
+
// Add to our maps
|
|
273
|
+
collectionIdMap.set(slugLower, createRes.data.id);
|
|
274
|
+
collectionFieldsMap.set(slugLower, []); // New collection has no fields yet
|
|
275
|
+
collectionResults.push(`| ${col.slug} | Created | ${col.name} |`);
|
|
279
276
|
created.collections++;
|
|
280
|
-
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const fieldJobs = [];
|
|
280
|
+
// Fields from new collections
|
|
281
|
+
if (collections) {
|
|
282
|
+
for (const col of collections) {
|
|
281
283
|
if (col.fields && col.fields.length > 0) {
|
|
282
|
-
const collectionId = createRes.data.id;
|
|
283
284
|
for (const field of col.fields) {
|
|
284
|
-
|
|
285
|
-
tenantId,
|
|
286
|
-
method: 'POST',
|
|
287
|
-
body: {
|
|
288
|
-
slug: field.slug,
|
|
289
|
-
name: field.name,
|
|
290
|
-
type: field.type,
|
|
291
|
-
description: field.description,
|
|
292
|
-
isRequired: field.isRequired,
|
|
293
|
-
options: field.options,
|
|
294
|
-
referenceCollection: field.referenceCollection,
|
|
295
|
-
},
|
|
296
|
-
});
|
|
297
|
-
if ((0, api_client_1.isApiError)(fieldRes)) {
|
|
298
|
-
results.push(` Failed to add field "${field.slug}": ${fieldRes.error}`);
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
results.push(` Added field "${field.slug}" (${field.type})`);
|
|
302
|
-
created.fields++;
|
|
303
|
-
}
|
|
285
|
+
fieldJobs.push({ collectionSlug: col.slug, field });
|
|
304
286
|
}
|
|
305
287
|
}
|
|
306
288
|
}
|
|
307
289
|
}
|
|
308
|
-
//
|
|
309
|
-
if (fieldsToAdd
|
|
290
|
+
// Fields from fieldsToAdd
|
|
291
|
+
if (fieldsToAdd) {
|
|
310
292
|
for (const group of fieldsToAdd) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
293
|
+
for (const field of group.fields) {
|
|
294
|
+
fieldJobs.push({ collectionSlug: group.collectionSlug, field });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (fieldJobs.length > 0) {
|
|
299
|
+
fieldResults.push('### Phase 2: Fields\n');
|
|
300
|
+
fieldResults.push('| Collection | Field | Type | Status |');
|
|
301
|
+
fieldResults.push('|------------|-------|------|--------|');
|
|
302
|
+
for (const job of fieldJobs) {
|
|
303
|
+
const slugLower = job.collectionSlug.toLowerCase();
|
|
304
|
+
const collectionId = collectionIdMap.get(slugLower);
|
|
305
|
+
if (!collectionId) {
|
|
306
|
+
fieldResults.push(`| ${job.collectionSlug} | ${job.field.slug} | ${job.field.type} | FAILED: Collection not found |`);
|
|
307
|
+
failed.fields++;
|
|
316
308
|
continue;
|
|
317
309
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
310
|
+
// Check if field already exists
|
|
311
|
+
const existingFields = collectionFieldsMap.get(slugLower) || [];
|
|
312
|
+
const fieldExists = existingFields.some(f => f.slug.toLowerCase() === job.field.slug.toLowerCase());
|
|
313
|
+
if (fieldExists) {
|
|
314
|
+
fieldResults.push(`| ${job.collectionSlug} | ${job.field.slug} | ${job.field.type} | Skipped (exists) |`);
|
|
315
|
+
skipped.fields++;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
// Create the field with retry logic
|
|
319
|
+
let fieldRes = await (0, api_client_1.apiRequest)(`/api/collections/${collectionId}/fields`, {
|
|
320
|
+
tenantId,
|
|
321
|
+
method: 'POST',
|
|
322
|
+
body: {
|
|
323
|
+
slug: job.field.slug,
|
|
324
|
+
name: job.field.name,
|
|
325
|
+
type: job.field.type,
|
|
326
|
+
description: job.field.description,
|
|
327
|
+
isRequired: job.field.isRequired,
|
|
328
|
+
options: job.field.options,
|
|
329
|
+
referenceCollection: job.field.referenceCollection,
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
// Retry once if failed (network issues, temporary errors)
|
|
333
|
+
if ((0, api_client_1.isApiError)(fieldRes)) {
|
|
334
|
+
// Wait a moment and retry
|
|
335
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
336
|
+
fieldRes = await (0, api_client_1.apiRequest)(`/api/collections/${collectionId}/fields`, {
|
|
326
337
|
tenantId,
|
|
327
338
|
method: 'POST',
|
|
328
339
|
body: {
|
|
329
|
-
slug: field.slug,
|
|
330
|
-
name: field.name,
|
|
331
|
-
type: field.type,
|
|
332
|
-
description: field.description,
|
|
333
|
-
isRequired: field.isRequired,
|
|
334
|
-
options: field.options,
|
|
335
|
-
referenceCollection: field.referenceCollection,
|
|
340
|
+
slug: job.field.slug,
|
|
341
|
+
name: job.field.name,
|
|
342
|
+
type: job.field.type,
|
|
343
|
+
description: job.field.description,
|
|
344
|
+
isRequired: job.field.isRequired,
|
|
345
|
+
options: job.field.options,
|
|
346
|
+
referenceCollection: job.field.referenceCollection,
|
|
336
347
|
},
|
|
337
348
|
});
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
349
|
+
}
|
|
350
|
+
if ((0, api_client_1.isApiError)(fieldRes)) {
|
|
351
|
+
fieldResults.push(`| ${job.collectionSlug} | ${job.field.slug} | ${job.field.type} | FAILED: ${fieldRes.error} |`);
|
|
352
|
+
failed.fields++;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
fieldResults.push(`| ${job.collectionSlug} | ${job.field.slug} | ${job.field.type} | Created |`);
|
|
356
|
+
created.fields++;
|
|
357
|
+
// Update the fields map so subsequent checks know this field exists
|
|
358
|
+
const fields = collectionFieldsMap.get(slugLower) || [];
|
|
359
|
+
fields.push({ slug: job.field.slug });
|
|
360
|
+
collectionFieldsMap.set(slugLower, fields);
|
|
345
361
|
}
|
|
346
362
|
}
|
|
347
363
|
}
|
|
348
|
-
// Summary
|
|
349
|
-
|
|
364
|
+
// ============ Build Summary ============
|
|
365
|
+
const hasFailures = failed.collections > 0 || failed.fields > 0;
|
|
366
|
+
let output = `# Schema Sync ${hasFailures ? 'Completed with Errors' : 'Complete'}
|
|
350
367
|
|
|
351
368
|
**Project ID:** \`${tenantId}\`
|
|
352
369
|
|
|
353
370
|
## Summary
|
|
354
|
-
- Collections created: ${created.collections}
|
|
355
|
-
- Fields created: ${created.fields}
|
|
356
|
-
- Collections skipped (already exist): ${skipped.collections}
|
|
357
|
-
- Fields skipped (already exist): ${skipped.fields}
|
|
358
371
|
|
|
359
|
-
|
|
372
|
+
| Metric | Created | Skipped | Failed |
|
|
373
|
+
|--------|---------|---------|--------|
|
|
374
|
+
| Collections | ${created.collections} | ${skipped.collections} | ${failed.collections} |
|
|
375
|
+
| Fields | ${created.fields} | ${skipped.fields} | ${failed.fields} |
|
|
360
376
|
|
|
361
|
-
${results.join('\n')}
|
|
362
377
|
`;
|
|
378
|
+
if (collectionResults.length > 1) {
|
|
379
|
+
output += `## Collections
|
|
380
|
+
|
|
381
|
+
| Slug | Status | Details |
|
|
382
|
+
|------|--------|---------|
|
|
383
|
+
${collectionResults.slice(1).join('\n')}
|
|
384
|
+
|
|
385
|
+
`;
|
|
386
|
+
}
|
|
387
|
+
if (fieldResults.length > 0) {
|
|
388
|
+
output += `## Fields
|
|
389
|
+
|
|
390
|
+
${fieldResults.join('\n')}
|
|
391
|
+
|
|
392
|
+
`;
|
|
393
|
+
}
|
|
394
|
+
if (hasFailures) {
|
|
395
|
+
output += `---
|
|
396
|
+
|
|
397
|
+
## ACTION REQUIRED
|
|
398
|
+
|
|
399
|
+
Some items failed to create. Please review the errors above and:
|
|
400
|
+
1. Fix any issues with field types or parameters
|
|
401
|
+
2. Run sync_schema again - it will skip already-created items and retry failed ones
|
|
402
|
+
`;
|
|
403
|
+
}
|
|
404
|
+
return output;
|
|
363
405
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-template.d.ts","sourceRoot":"","sources":["../../src/tools/validate-template.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AAgRrE;;;;;;;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,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AAgRrE;;;;;;;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,CAobjB"}
|
|
@@ -511,7 +511,6 @@ Use the \`sync_schema\` tool to create the missing fields before deploying. This
|
|
|
511
511
|
"fieldsToAdd": [
|
|
512
512
|
{
|
|
513
513
|
"collectionSlug": "${targetCollection}",
|
|
514
|
-
"isBuiltin": false,
|
|
515
514
|
"fields": [
|
|
516
515
|
${missingFields.map(f => ` { "slug": "${f}", "name": "${f.charAt(0).toUpperCase() + f.slice(1).replace(/([A-Z])/g, ' $1').trim()}", "type": "YOUR_TYPE" }`).join(',\n')}
|
|
517
516
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "multisite-cms-mcp",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.6",
|
|
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": {
|