multisite-cms-mcp 1.0.13 → 1.0.16

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.
@@ -84,6 +84,94 @@ Downloadable files and documents.
84
84
 
85
85
  ---
86
86
 
87
+ ## Custom Collections
88
+
89
+ Custom collections are tenant-defined content types created in the CMS dashboard. Each custom collection has its own slug and user-defined fields.
90
+
91
+ **Token syntax:** Same as built-in collections
92
+ - \`{{fieldSlug}}\` for text, number, boolean, image, url, date, select fields
93
+ - \`{{{fieldSlug}}}\` for richText fields (MUST use triple braces)
94
+
95
+ **Built-in tokens for all custom collections:**
96
+ - \`{{slug}}\` - Item URL slug (if collection has slugs enabled)
97
+ - \`{{url}}\` - Full item URL (e.g., /services/my-service)
98
+ - \`{{publishedAt}}\` - Publish date (if collection has publish dates enabled)
99
+
100
+ **Example - A "Services" collection with fields: title, image, description (richText), price:**
101
+ \`\`\`html
102
+ <section class="services">
103
+ {{#each services}}
104
+ <div class="service-card">
105
+ <img src="{{image}}" alt="{{title}}">
106
+ <h2><a href="{{url}}">{{title}}</a></h2>
107
+ {{{description}}}
108
+ <p class="price">\${{price}}</p>
109
+ </div>
110
+ {{/each}}
111
+ </section>
112
+ \`\`\`
113
+
114
+ **Example - A "Testimonials" collection with fields: quote (richText), author, company, rating:**
115
+ \`\`\`html
116
+ <section class="testimonials">
117
+ {{#each testimonials limit=6}}
118
+ <blockquote class="testimonial">
119
+ {{{quote}}}
120
+ <cite>
121
+ <strong>{{author}}</strong>
122
+ {{#if company}}<span>{{company}}</span>{{/if}}
123
+ </cite>
124
+ </blockquote>
125
+ {{/each}}
126
+ </section>
127
+ \`\`\`
128
+
129
+ **To see exact fields for a tenant's custom collections, use the \`get_tenant_schema\` tool.**
130
+
131
+ ---
132
+
133
+ ## Custom Fields on Built-in Collections
134
+
135
+ Tenants can add custom fields to Blog Posts, Authors, Team Members, and Downloads. These custom fields work exactly like built-in fields.
136
+
137
+ **Token syntax:**
138
+ - \`{{customFieldSlug}}\` for text, number, boolean, image, url, date, select
139
+ - \`{{{customFieldSlug}}}\` for richText fields
140
+
141
+ **Example - "category" field added to blogs:**
142
+ \`\`\`html
143
+ {{#each blogs}}
144
+ <article class="post-card">
145
+ <h2>{{name}}</h2>
146
+ {{#if category}}
147
+ <span class="category-badge">{{category}}</span>
148
+ {{/if}}
149
+ <p>{{postSummary}}</p>
150
+ </article>
151
+ {{/each}}
152
+ \`\`\`
153
+
154
+ **Example - "department" and "linkedinUrl" fields added to team:**
155
+ \`\`\`html
156
+ {{#each team sort="order" order="asc"}}
157
+ <div class="team-member">
158
+ <img src="{{photo}}" alt="{{name}}">
159
+ <h3>{{name}}</h3>
160
+ <p class="role">{{role}}</p>
161
+ {{#if department}}
162
+ <p class="department">{{department}}</p>
163
+ {{/if}}
164
+ {{#if linkedinUrl}}
165
+ <a href="{{linkedinUrl}}">LinkedIn</a>
166
+ {{/if}}
167
+ </div>
168
+ {{/each}}
169
+ \`\`\`
170
+
171
+ **To see exact custom fields for a tenant, use the \`get_tenant_schema\` tool.**
172
+
173
+ ---
174
+
87
175
  ## Template Syntax Reference
88
176
 
89
177
  ### Loops
@@ -103,6 +191,7 @@ Downloadable files and documents.
103
191
 
104
192
  ### Conditionals
105
193
  \`\`\`html
194
+ <!-- Check if a field has a value -->
106
195
  {{#if mainImage}}
107
196
  <img src="{{mainImage}}" alt="{{name}}">
108
197
  {{else}}
@@ -114,6 +203,50 @@ Downloadable files and documents.
114
203
  {{/unless}}
115
204
  \`\`\`
116
205
 
206
+ ### Collection Empty Checks (Page Level)
207
+ Check if a collection has items - useful for "No posts yet" fallbacks:
208
+
209
+ \`\`\`html
210
+ {{#each blogs}}
211
+ <article>{{name}}</article>
212
+ {{/each}}
213
+
214
+ {{#unless blogs}}
215
+ <p>No posts yet. Check back soon!</p>
216
+ {{/unless}}
217
+ \`\`\`
218
+
219
+ **Important:** The \`{{#if blogs}}\` and \`{{#unless blogs}}\` syntax checks if the collection has any published items. Use this OUTSIDE of \`{{#each}}\` loops for empty state handling.
220
+
221
+ ### Equality Comparisons in Conditionals
222
+ Compare two field values using the \`(eq field1 field2)\` helper:
223
+
224
+ \`\`\`html
225
+ <!-- Show content when fields ARE equal -->
226
+ {{#if (eq author.slug ../slug)}}
227
+ <span>This is your post!</span>
228
+ {{/if}}
229
+
230
+ <!-- Show content when fields are NOT equal -->
231
+ {{#unless (eq slug ../slug)}}
232
+ <a href="{{url}}">{{name}}</a>
233
+ {{/unless}}
234
+ \`\`\`
235
+
236
+ **Common use case - Related Posts (exclude current):**
237
+ \`\`\`html
238
+ <h3>Other Posts</h3>
239
+ {{#each blogs limit=3}}
240
+ {{#unless (eq slug ../slug)}}
241
+ <article>
242
+ <a href="{{url}}">{{name}}</a>
243
+ </article>
244
+ {{/unless}}
245
+ {{/each}}
246
+ \`\`\`
247
+
248
+ This pattern is essential for "Related Posts" sections where you want to show other posts but NOT the current one.
249
+
117
250
  ### Loop Variables
118
251
  Inside \`{{#each}}\` blocks:
119
252
  - \`{{@first}}\` - true for first item
@@ -149,17 +282,24 @@ Inside loops, access the parent scope (page's current item) using \`../\`:
149
282
  - Category pages: filter items by current category
150
283
  - Related items: match based on current detail page
151
284
 
152
- ### Equality Comparisons
153
- Compare two values in conditionals:
285
+ ### Equality Helpers Summary
154
286
 
287
+ | Syntax | When to use | Shows content when... |
288
+ |--------|-------------|----------------------|
289
+ | \`{{#if (eq field1 field2)}}\` | Compare two fields | Fields ARE equal |
290
+ | \`{{#unless (eq field1 field2)}}\` | Exclude matches | Fields are NOT equal |
291
+ | \`{{#eq field "value"}}\` | Compare to string | Field equals string |
292
+
293
+ **Examples:**
155
294
  \`\`\`html
156
- {{#if (eq author.name ../name)}}
157
- <!-- True when fields match -->
158
- {{/if}}
295
+ <!-- Show only when author matches current page -->
296
+ {{#if (eq author.slug ../slug)}}...{{/if}}
297
+
298
+ <!-- Show all EXCEPT current item (Related Posts pattern) -->
299
+ {{#unless (eq slug ../slug)}}...{{/unless}}
159
300
 
160
- {{#eq status "published"}}
161
- <!-- Compare field to literal string -->
162
- {{/eq}}
301
+ <!-- Compare field to a literal value -->
302
+ {{#eq status "published"}}...{{/eq}}
163
303
  \`\`\`
164
304
 
165
305
  ### Rich Text (Triple Braces)
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Fetches the complete schema for a specific project including:
3
+ * - Built-in collections with their standard fields
4
+ * - Custom fields added to built-in collections
5
+ * - Custom collections with all their fields
6
+ *
7
+ * Uses stored credentials or triggers device flow for authentication.
8
+ *
9
+ * @param projectId - Project ID (UUID) or project name
10
+ */
11
+ export declare function getTenantSchema(projectId: string): Promise<string>;
12
+ //# sourceMappingURL=get-tenant-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-tenant-schema.d.ts","sourceRoot":"","sources":["../../src/tools/get-tenant-schema.ts"],"names":[],"mappings":"AA2EA;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmFxE"}
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTenantSchema = getTenantSchema;
4
+ const api_client_1 = require("../lib/api-client");
5
+ const device_flow_1 = require("../lib/device-flow");
6
+ /**
7
+ * Resolve a project identifier to a tenant ID
8
+ * Accepts either a UUID or a project name
9
+ */
10
+ async function resolveProjectId(projectIdentifier) {
11
+ // Check if it looks like a UUID
12
+ const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13
+ if (uuidPattern.test(projectIdentifier)) {
14
+ // It's already a UUID, use it directly
15
+ return { tenantId: projectIdentifier };
16
+ }
17
+ // Otherwise, look up by name using /api/tenants
18
+ const response = await (0, api_client_1.apiRequest)('/api/tenants');
19
+ if ((0, api_client_1.isApiError)(response)) {
20
+ return { error: `Failed to look up project: ${response.error}` };
21
+ }
22
+ const projects = response.data;
23
+ // Find by exact name match (case-insensitive)
24
+ const match = projects.find(p => p.name.toLowerCase() === projectIdentifier.toLowerCase());
25
+ if (match) {
26
+ return { tenantId: match.id };
27
+ }
28
+ // Try partial match
29
+ const partialMatch = projects.find(p => p.name.toLowerCase().includes(projectIdentifier.toLowerCase()));
30
+ if (partialMatch) {
31
+ return { tenantId: partialMatch.id };
32
+ }
33
+ // List available projects for the user
34
+ const availableProjects = projects.map(p => `- ${p.name} (${p.id})`).join('\n');
35
+ return {
36
+ error: `Project "${projectIdentifier}" not found.\n\nAvailable projects:\n${availableProjects || 'None'}\n\nUse list_projects to see all your projects.`
37
+ };
38
+ }
39
+ /**
40
+ * Fetches the complete schema for a specific project including:
41
+ * - Built-in collections with their standard fields
42
+ * - Custom fields added to built-in collections
43
+ * - Custom collections with all their fields
44
+ *
45
+ * Uses stored credentials or triggers device flow for authentication.
46
+ *
47
+ * @param projectId - Project ID (UUID) or project name
48
+ */
49
+ async function getTenantSchema(projectId) {
50
+ // Check if we need to authenticate
51
+ if (await (0, api_client_1.needsAuthentication)()) {
52
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
53
+ if (!authResult.authenticated) {
54
+ return authResult.message;
55
+ }
56
+ }
57
+ // Resolve project identifier to tenant ID
58
+ const resolved = await resolveProjectId(projectId);
59
+ if ('error' in resolved) {
60
+ return `# Project Not Found
61
+
62
+ ${resolved.error}
63
+ `;
64
+ }
65
+ const { tenantId } = resolved;
66
+ const apiUrl = (0, api_client_1.getApiUrl)();
67
+ // Fetch the AI prompt for this tenant
68
+ const response = await (0, api_client_1.apiRequest)('/api/cms/ai-prompt', {
69
+ tenantId,
70
+ });
71
+ if ((0, api_client_1.isApiError)(response)) {
72
+ // If auth error, try to re-authenticate
73
+ if ((0, api_client_1.needsAuthError)(response)) {
74
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
75
+ if (!authResult.authenticated) {
76
+ return authResult.message;
77
+ }
78
+ // Retry the request
79
+ const retryResponse = await (0, api_client_1.apiRequest)('/api/cms/ai-prompt', {
80
+ tenantId,
81
+ });
82
+ if ((0, api_client_1.isApiError)(retryResponse)) {
83
+ return `# API Error
84
+
85
+ Failed to fetch project schema: ${retryResponse.error}
86
+
87
+ **Project ID:** ${tenantId}
88
+ **Status:** ${retryResponse.statusCode}
89
+
90
+ Please try authenticating again.
91
+ `;
92
+ }
93
+ return formatSchemaResponse(retryResponse.data, tenantId);
94
+ }
95
+ if (response.statusCode === 403) {
96
+ return `# Authorization Error
97
+
98
+ You don't have permission to access this project's schema.
99
+
100
+ **Project ID:** ${tenantId}
101
+
102
+ Make sure:
103
+ 1. You have membership in this project
104
+ 2. Try running \`list_projects\` to see your accessible projects
105
+ `;
106
+ }
107
+ return `# API Error
108
+
109
+ Failed to fetch project schema: ${response.error}
110
+
111
+ **Project ID:** ${tenantId}
112
+ **API URL:** ${apiUrl}
113
+ **Status:** ${response.statusCode}
114
+
115
+ Please verify:
116
+ 1. The project ID is correct
117
+ 2. You have network connectivity
118
+ `;
119
+ }
120
+ return formatSchemaResponse(response.data, tenantId);
121
+ }
122
+ /**
123
+ * Format the schema response for display
124
+ */
125
+ function formatSchemaResponse(data, tenantId) {
126
+ if (!data?.prompt) {
127
+ return `# Unexpected Response
128
+
129
+ The API returned a successful response but the prompt data was missing.
130
+
131
+ **Project ID:** ${tenantId}
132
+ **Response:** ${JSON.stringify(data, null, 2).slice(0, 500)}
133
+ `;
134
+ }
135
+ // Return the full AI prompt which includes all schema information
136
+ return `# Project Schema
137
+
138
+ **Project ID:** \`${tenantId}\`
139
+
140
+ The following schema is specific to this project and includes all custom collections and custom fields.
141
+
142
+ ---
143
+
144
+ ${data.prompt}
145
+ `;
146
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * List all FastMode projects the authenticated user has access to
3
+ */
4
+ export declare function listProjects(): Promise<string>;
5
+ //# sourceMappingURL=list-projects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-projects.d.ts","sourceRoot":"","sources":["../../src/tools/list-projects.ts"],"names":[],"mappings":"AAmBA;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAkDpD"}
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listProjects = listProjects;
4
+ const api_client_1 = require("../lib/api-client");
5
+ const device_flow_1 = require("../lib/device-flow");
6
+ /**
7
+ * List all FastMode projects the authenticated user has access to
8
+ */
9
+ async function listProjects() {
10
+ // Check if we need to authenticate
11
+ if (await (0, api_client_1.needsAuthentication)()) {
12
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
13
+ if (!authResult.authenticated) {
14
+ return authResult.message;
15
+ }
16
+ }
17
+ // Fetch user's tenants/projects
18
+ const response = await (0, api_client_1.apiRequest)('/api/tenants');
19
+ if ((0, api_client_1.isApiError)(response)) {
20
+ // If auth error, try to re-authenticate
21
+ if ((0, api_client_1.needsAuthError)(response)) {
22
+ const authResult = await (0, device_flow_1.ensureAuthenticated)();
23
+ if (!authResult.authenticated) {
24
+ return authResult.message;
25
+ }
26
+ // Retry the request
27
+ const retryResponse = await (0, api_client_1.apiRequest)('/api/tenants');
28
+ if ((0, api_client_1.isApiError)(retryResponse)) {
29
+ return `# API Error
30
+
31
+ Failed to fetch projects: ${retryResponse.error}
32
+
33
+ **Status:** ${retryResponse.statusCode}
34
+
35
+ Please try authenticating again.
36
+ `;
37
+ }
38
+ return formatProjectList(retryResponse.data);
39
+ }
40
+ return `# API Error
41
+
42
+ Failed to fetch projects: ${response.error}
43
+
44
+ **Status:** ${response.statusCode}
45
+
46
+ Please check:
47
+ 1. Your authentication is valid
48
+ 2. The API URL is correct
49
+ 3. You have network connectivity
50
+ `;
51
+ }
52
+ return formatProjectList(response.data);
53
+ }
54
+ /**
55
+ * Format the project list for display
56
+ */
57
+ function formatProjectList(projects) {
58
+ if (!projects || projects.length === 0) {
59
+ return `# No Projects Found
60
+
61
+ You don't have access to any FastMode projects.
62
+
63
+ **To create a project:**
64
+ 1. Go to app.fastmode.ai
65
+ 2. Click "Create New Project"
66
+ 3. Run \`list_projects\` again to see it here
67
+ `;
68
+ }
69
+ // Format the project list
70
+ let output = `# Your FastMode Projects
71
+
72
+ Found ${projects.length} project${projects.length > 1 ? 's' : ''}:
73
+
74
+ `;
75
+ projects.forEach((project, index) => {
76
+ const siteStatus = project.site?.status || 'pending';
77
+ output += `## ${index + 1}. ${project.name}
78
+
79
+ - **Project ID:** \`${project.id}\`
80
+ - **Subdomain:** ${project.subdomain}.fastmode.ai
81
+ ${project.customDomain ? `- **Custom Domain:** ${project.customDomain}\n` : ''}- **Your Role:** ${project.role}
82
+ - **Site Status:** ${siteStatus}
83
+
84
+ `;
85
+ });
86
+ output += `---
87
+
88
+ ## How to Use
89
+
90
+ To get the schema for a project, use:
91
+ \`\`\`
92
+ get_tenant_schema(projectId: "${projects[0]?.id || 'project-id-here'}")
93
+ \`\`\`
94
+
95
+ Or use the project name:
96
+ \`\`\`
97
+ get_tenant_schema(projectId: "${projects[0]?.name || 'Project Name'}")
98
+ \`\`\`
99
+ `;
100
+ return output;
101
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multisite-cms-mcp",
3
- "version": "1.0.13",
3
+ "version": "1.0.16",
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": {