fastmode-mcp 1.0.0 → 1.0.2

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 CHANGED
@@ -373,7 +373,7 @@ const TOOLS = [
373
373
  },
374
374
  {
375
375
  name: 'create_cms_item',
376
- description: 'FIRST: Call get_started(intent: "add_content") to see collections and field types. Creates a new CMS item. For relation fields, pass the related item ID (not name). For richText fields, use HTML content. Requires authentication.',
376
+ description: 'FIRST: Call get_started(intent: "add_content") to see collections, field types, AND relation field options with IDs. Creates a new CMS item. For relation fields, you MUST use item IDs (not names) - the response will warn you if relation fields were left empty and show available options. For richText fields, use HTML content. Requires authentication.',
377
377
  inputSchema: {
378
378
  type: 'object',
379
379
  properties: {
@@ -460,7 +460,7 @@ const TOOLS = [
460
460
  },
461
461
  {
462
462
  name: 'update_cms_item',
463
- description: 'FIRST: Call get_started(intent: "add_content") to see collections and field types. Updates an existing CMS item. Only provided fields are changed. Requires authentication.',
463
+ description: 'FIRST: Call get_started(intent: "add_content") to see collections, field types, AND relation field options with IDs. Updates an existing CMS item. Only provided fields are changed. For relation fields, use item IDs from get_relation_options or list_cms_items. Requires authentication.',
464
464
  inputSchema: {
465
465
  type: 'object',
466
466
  properties: {
@@ -518,6 +518,28 @@ const TOOLS = [
518
518
  required: ['projectId', 'collectionSlug', 'itemSlug', 'confirmDelete'],
519
519
  },
520
520
  },
521
+ {
522
+ name: 'get_relation_options',
523
+ description: 'Get available options (item IDs) for relation fields in a collection. Use this to see what items can be linked via relation fields. Shows item IDs, names, and slugs for easy reference when creating or updating items.',
524
+ inputSchema: {
525
+ type: 'object',
526
+ properties: {
527
+ projectId: {
528
+ type: 'string',
529
+ description: 'Project ID (UUID) or project name.',
530
+ },
531
+ collectionSlug: {
532
+ type: 'string',
533
+ description: 'The collection that has relation fields (e.g., "posts" if posts have an author field).',
534
+ },
535
+ fieldSlug: {
536
+ type: 'string',
537
+ description: 'Optional: Specific relation field to get options for. If not provided, shows all relation fields.',
538
+ },
539
+ },
540
+ required: ['projectId', 'collectionSlug'],
541
+ },
542
+ },
521
543
  ];
522
544
  // Handle list tools request
523
545
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
@@ -633,6 +655,13 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
633
655
  confirmDelete: params.confirmDelete,
634
656
  });
635
657
  break;
658
+ case 'get_relation_options':
659
+ result = await (0, cms_items_1.getRelationOptions)({
660
+ projectId: params.projectId,
661
+ collectionSlug: params.collectionSlug,
662
+ fieldSlug: params.fieldSlug,
663
+ });
664
+ break;
636
665
  default:
637
666
  return {
638
667
  content: [{ type: 'text', text: `Unknown tool: ${name}` }],
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/lib/credentials.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAG3C;AAYD;;GAEG;AACH,wBAAgB,eAAe,IAAI,iBAAiB,GAAG,IAAI,CAoB1D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAUpE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAUxC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,iBAAiB,EAAE,aAAa,GAAE,MAAU,GAAG,OAAO,CAIjG;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAyC1G;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAoB7E;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG3D"}
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../src/lib/credentials.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAG3C;AAYD;;GAEG;AACH,wBAAgB,eAAe,IAAI,iBAAiB,GAAG,IAAI,CAoB1D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAUpE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAUxC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,iBAAiB,EAAE,aAAa,GAAE,MAAU,GAAG,OAAO,CAIjG;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAkD1G;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAoB7E;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG3D"}
@@ -1 +1 @@
1
- {"version":3,"file":"device-flow.d.ts","sourceRoot":"","sources":["../../src/lib/device-flow.ts"],"names":[],"mappings":"AA0DA;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAqGvD;AA0ED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC;IAAE,aAAa,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA2BhG"}
1
+ {"version":3,"file":"device-flow.d.ts","sourceRoot":"","sources":["../../src/lib/device-flow.ts"],"names":[],"mappings":"AA0DA;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAqGvD;AA8ED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC;IAAE,aAAa,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA2BhG"}
@@ -53,4 +53,13 @@ export declare function deleteCmsItem(params: {
53
53
  itemSlug: string;
54
54
  confirmDelete: boolean;
55
55
  }): Promise<string>;
56
+ /**
57
+ * Get relation field options for a collection
58
+ * Shows all relation fields and their available values
59
+ */
60
+ export declare function getRelationOptions(params: {
61
+ projectId: string;
62
+ collectionSlug: string;
63
+ fieldSlug?: string;
64
+ }): Promise<string>;
56
65
  //# sourceMappingURL=cms-items.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cms-items.d.ts","sourceRoot":"","sources":["../../src/tools/cms-items.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAqFH;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuDlB;AAkBD;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6ClB;AA4CD;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6ClB;AAoBD;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GAAG,OAAO,CAAC,MAAM,CAAC,CA6ElB;AAkBD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwElB"}
1
+ {"version":3,"file":"cms-items.d.ts","sourceRoot":"","sources":["../../src/tools/cms-items.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA4NH;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,MAAM,CAAC,CA2DlB;AA+DD;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6ClB;AA4CD;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CA6ClB;AAoBD;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GAAG,OAAO,CAAC,MAAM,CAAC,CA6ElB;AAkBD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwElB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CA0FlB"}
@@ -10,8 +10,15 @@ exports.listCmsItems = listCmsItems;
10
10
  exports.getCmsItem = getCmsItem;
11
11
  exports.updateCmsItem = updateCmsItem;
12
12
  exports.deleteCmsItem = deleteCmsItem;
13
+ exports.getRelationOptions = getRelationOptions;
13
14
  const api_client_1 = require("../lib/api-client");
14
15
  const device_flow_1 = require("../lib/device-flow");
16
+ /**
17
+ * Type guard to check if prepare result failed
18
+ */
19
+ function prepFailed(prep) {
20
+ return !prep.success;
21
+ }
15
22
  /**
16
23
  * Helper to ensure authentication and resolve project ID
17
24
  */
@@ -48,12 +55,77 @@ async function getCollectionId(tenantId, collectionSlug) {
48
55
  }
49
56
  return { collectionId: collection.id };
50
57
  }
58
+ /**
59
+ * Get collection with full field details
60
+ */
61
+ async function getCollectionWithFields(tenantId, collectionSlug) {
62
+ const response = await (0, api_client_1.apiRequest)('/api/collections', { tenantId });
63
+ if ((0, api_client_1.isApiError)(response)) {
64
+ return { error: `Failed to fetch collections: ${response.error}` };
65
+ }
66
+ const collection = response.data.find(c => c.slug === collectionSlug);
67
+ if (!collection) {
68
+ return { error: `Collection "${collectionSlug}" not found.` };
69
+ }
70
+ // The collections endpoint returns fields, cast to proper type
71
+ return { collection: collection };
72
+ }
73
+ /**
74
+ * Get items from a collection (for relation options)
75
+ */
76
+ async function getCollectionItems(tenantId, collectionSlug, limit = 10) {
77
+ // First get collection ID
78
+ const collResult = await getCollectionId(tenantId, collectionSlug);
79
+ if ('error' in collResult) {
80
+ return [];
81
+ }
82
+ const response = await (0, api_client_1.apiRequest)(`/api/collections/${collResult.collectionId}/items?limit=${limit}`, { tenantId, method: 'GET' });
83
+ if ((0, api_client_1.isApiError)(response)) {
84
+ return [];
85
+ }
86
+ return response.data.map(item => ({
87
+ id: item.id,
88
+ name: item.name,
89
+ slug: item.slug,
90
+ }));
91
+ }
92
+ /**
93
+ * Check for empty relation fields and get their options
94
+ */
95
+ async function checkEmptyRelations(tenantId, collectionSlug, itemData) {
96
+ const collResult = await getCollectionWithFields(tenantId, collectionSlug);
97
+ if ('error' in collResult) {
98
+ return [];
99
+ }
100
+ const { collection } = collResult;
101
+ const emptyRelations = [];
102
+ // Check each relation field
103
+ for (const field of collection.fields || []) {
104
+ if (field.type === 'relation' && field.referenceCollection) {
105
+ const value = itemData[field.slug];
106
+ // Check if the field is empty (null, undefined, or empty string)
107
+ if (value === null || value === undefined || value === '') {
108
+ // Get options from the referenced collection
109
+ const options = await getCollectionItems(tenantId, field.referenceCollection, 10);
110
+ if (options.length > 0) {
111
+ emptyRelations.push({
112
+ fieldSlug: field.slug,
113
+ fieldName: field.name,
114
+ referenceCollection: field.referenceCollection,
115
+ options,
116
+ });
117
+ }
118
+ }
119
+ }
120
+ }
121
+ return emptyRelations;
122
+ }
51
123
  /**
52
124
  * Create a new item in a CMS collection
53
125
  */
54
126
  async function createCmsItem(params) {
55
127
  const prep = await prepareRequest(params.projectId);
56
- if (!prep.success)
128
+ if (prepFailed(prep))
57
129
  return prep.message;
58
130
  // Get collection ID from slug
59
131
  const collectionResult = await getCollectionId(prep.tenantId, params.collectionSlug);
@@ -89,14 +161,18 @@ async function createCmsItem(params) {
89
161
  if ((0, api_client_1.isApiError)(retry)) {
90
162
  return `# Error Creating Item\n\n${retry.error}\n\n**Status:** ${retry.statusCode}`;
91
163
  }
92
- return formatCreatedItem(retry.data, params.collectionSlug);
164
+ // Check for empty relation fields
165
+ const emptyRelations = await checkEmptyRelations(prep.tenantId, params.collectionSlug, retry.data.data);
166
+ return formatCreatedItem(retry.data, params.collectionSlug, params.projectId, emptyRelations);
93
167
  }
94
168
  return `# Error Creating Item\n\n${response.error}\n\n**Status:** ${response.statusCode}`;
95
169
  }
96
- return formatCreatedItem(response.data, params.collectionSlug);
170
+ // Check for empty relation fields
171
+ const emptyRelations = await checkEmptyRelations(prep.tenantId, params.collectionSlug, response.data.data);
172
+ return formatCreatedItem(response.data, params.collectionSlug, params.projectId, emptyRelations);
97
173
  }
98
- function formatCreatedItem(item, collectionSlug) {
99
- return `# Item Created Successfully
174
+ function formatCreatedItem(item, collectionSlug, projectId, emptyRelations) {
175
+ let output = `# Item Created Successfully
100
176
 
101
177
  **Collection:** ${collectionSlug}
102
178
  **Name:** ${item.name}
@@ -109,13 +185,49 @@ function formatCreatedItem(item, collectionSlug) {
109
185
  ${JSON.stringify(item.data, null, 2)}
110
186
  \`\`\`
111
187
  `;
188
+ // Add warning about empty relation fields
189
+ if (emptyRelations && emptyRelations.length > 0) {
190
+ output += `
191
+ ---
192
+
193
+ ## ⚠️ Empty Relation Fields
194
+
195
+ The following relation fields were not set. You may want to update them:
196
+
197
+ `;
198
+ for (const rel of emptyRelations) {
199
+ output += `### ${rel.fieldName} (\`${rel.fieldSlug}\`)
200
+ References: **${rel.referenceCollection}**
201
+
202
+ Available options:
203
+ `;
204
+ for (const opt of rel.options.slice(0, 5)) {
205
+ output += `- \`"${opt.id}"\` → ${opt.name}${opt.slug ? ` (${opt.slug})` : ''}\n`;
206
+ }
207
+ if (rel.options.length > 5) {
208
+ output += `- ... and ${rel.options.length - 5} more\n`;
209
+ }
210
+ output += '\n';
211
+ }
212
+ output += `**To update**, use:
213
+ \`\`\`
214
+ update_cms_item(
215
+ projectId: "${projectId || '...'}",
216
+ collectionSlug: "${collectionSlug}",
217
+ itemSlug: "${item.slug || '...'}",
218
+ data: { ${emptyRelations.map(r => `${r.fieldSlug}: "${r.options[0]?.id || 'ITEM_ID'}"`).join(', ')} }
219
+ )
220
+ \`\`\`
221
+ `;
222
+ }
223
+ return output;
112
224
  }
113
225
  /**
114
226
  * List items in a CMS collection
115
227
  */
116
228
  async function listCmsItems(params) {
117
229
  const prep = await prepareRequest(params.projectId);
118
- if (!prep.success)
230
+ if (prepFailed(prep))
119
231
  return prep.message;
120
232
  // Get collection ID from slug
121
233
  const collectionResult = await getCollectionId(prep.tenantId, params.collectionSlug);
@@ -189,7 +301,7 @@ Found ${items.length} item${items.length !== 1 ? 's' : ''}:
189
301
  */
190
302
  async function getCmsItem(params) {
191
303
  const prep = await prepareRequest(params.projectId);
192
- if (!prep.success)
304
+ if (prepFailed(prep))
193
305
  return prep.message;
194
306
  // Get collection ID from slug
195
307
  const collectionResult = await getCollectionId(prep.tenantId, params.collectionSlug);
@@ -243,7 +355,7 @@ ${JSON.stringify(item.data, null, 2)}
243
355
  */
244
356
  async function updateCmsItem(params) {
245
357
  const prep = await prepareRequest(params.projectId);
246
- if (!prep.success)
358
+ if (prepFailed(prep))
247
359
  return prep.message;
248
360
  // Get collection ID from slug
249
361
  const collectionResult = await getCollectionId(prep.tenantId, params.collectionSlug);
@@ -336,7 +448,7 @@ To delete the item "${params.itemSlug}" from collection "${params.collectionSlug
336
448
  `;
337
449
  }
338
450
  const prep = await prepareRequest(params.projectId);
339
- if (!prep.success)
451
+ if (prepFailed(prep))
340
452
  return prep.message;
341
453
  // Get collection ID from slug
342
454
  const collectionResult = await getCollectionId(prep.tenantId, params.collectionSlug);
@@ -374,3 +486,86 @@ Successfully deleted item "${params.itemSlug}" from collection "${params.collect
374
486
  Successfully deleted item "${params.itemSlug}" from collection "${params.collectionSlug}".
375
487
  `;
376
488
  }
489
+ /**
490
+ * Get relation field options for a collection
491
+ * Shows all relation fields and their available values
492
+ */
493
+ async function getRelationOptions(params) {
494
+ const prep = await prepareRequest(params.projectId);
495
+ if (prepFailed(prep))
496
+ return prep.message;
497
+ // Get collection with fields
498
+ const collResult = await getCollectionWithFields(prep.tenantId, params.collectionSlug);
499
+ if ('error' in collResult) {
500
+ return `# Error\n\n${collResult.error}`;
501
+ }
502
+ const { collection } = collResult;
503
+ // Find relation fields
504
+ const relationFields = (collection.fields || []).filter(f => f.type === 'relation' && f.referenceCollection);
505
+ if (relationFields.length === 0) {
506
+ return `# No Relation Fields
507
+
508
+ Collection **${params.collectionSlug}** has no relation fields.
509
+
510
+ Relation fields link items to other collections. To create one, use:
511
+ \`\`\`
512
+ sync_schema(
513
+ projectId: "${params.projectId}",
514
+ fieldsToAdd: [{
515
+ collectionSlug: "${params.collectionSlug}",
516
+ fields: [{ slug: "category", name: "Category", type: "relation", referenceCollection: "categories" }]
517
+ }]
518
+ )
519
+ \`\`\`
520
+ `;
521
+ }
522
+ // If a specific field is requested, filter to just that one
523
+ const fieldsToShow = params.fieldSlug
524
+ ? relationFields.filter(f => f.slug === params.fieldSlug)
525
+ : relationFields;
526
+ if (params.fieldSlug && fieldsToShow.length === 0) {
527
+ const availableFields = relationFields.map(f => f.slug).join(', ');
528
+ return `# Field Not Found
529
+
530
+ Field "${params.fieldSlug}" is not a relation field in collection "${params.collectionSlug}".
531
+
532
+ Available relation fields: ${availableFields || 'none'}
533
+ `;
534
+ }
535
+ let output = `# Relation Field Options for ${collection.name}
536
+
537
+ `;
538
+ for (const field of fieldsToShow) {
539
+ const options = await getCollectionItems(prep.tenantId, field.referenceCollection, 20);
540
+ output += `## ${field.name} (\`${field.slug}\`)
541
+ **References:** ${field.referenceCollection}
542
+
543
+ `;
544
+ if (options.length === 0) {
545
+ output += `*No items in ${field.referenceCollection} yet.*
546
+
547
+ To create items, use:
548
+ \`\`\`
549
+ create_cms_item(projectId: "${params.projectId}", collectionSlug: "${field.referenceCollection}", name: "Item Name", data: {...})
550
+ \`\`\`
551
+
552
+ `;
553
+ }
554
+ else {
555
+ output += `| ID | Name | Slug |
556
+ |----|------|------|
557
+ `;
558
+ for (const opt of options) {
559
+ output += `| \`${opt.id}\` | ${opt.name} | ${opt.slug || '-'} |\n`;
560
+ }
561
+ output += `
562
+ **Usage:** When creating/updating items in \`${params.collectionSlug}\`, use the ID:
563
+ \`\`\`
564
+ data: { ${field.slug}: "${options[0].id}" }
565
+ \`\`\`
566
+
567
+ `;
568
+ }
569
+ }
570
+ return output;
571
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"get-started.d.ts","sourceRoot":"","sources":["../../src/tools/get-started.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,eAAe,GAAG,SAAS,GAAG,QAAQ,CAAC;AAExF,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAmjBD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA4FxE"}
1
+ {"version":3,"file":"get-started.d.ts","sourceRoot":"","sources":["../../src/tools/get-started.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA8CH,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,eAAe,GAAG,SAAS,GAAG,QAAQ,CAAC;AAExF,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgnBD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CA4FxE"}
@@ -12,6 +12,27 @@ exports.getStarted = getStarted;
12
12
  const context_fetcher_1 = require("../lib/context-fetcher");
13
13
  const device_flow_1 = require("../lib/device-flow");
14
14
  const api_client_1 = require("../lib/api-client");
15
+ /**
16
+ * Fetch items from a collection for relation field options
17
+ */
18
+ async function fetchRelationOptions(tenantId, collectionSlug, limit = 5) {
19
+ // First get collection ID
20
+ const collectionsResponse = await (0, api_client_1.apiRequest)('/api/collections', { tenantId });
21
+ if ((0, api_client_1.isApiError)(collectionsResponse))
22
+ return [];
23
+ const collection = collectionsResponse.data.find(c => c.slug === collectionSlug);
24
+ if (!collection)
25
+ return [];
26
+ // Then get items
27
+ const itemsResponse = await (0, api_client_1.apiRequest)(`/api/collections/${collection.id}/items?limit=${limit}`, { tenantId, method: 'GET' });
28
+ if ((0, api_client_1.isApiError)(itemsResponse))
29
+ return [];
30
+ return itemsResponse.data.map(item => ({
31
+ id: item.id,
32
+ name: item.name,
33
+ slug: item.slug,
34
+ }));
35
+ }
15
36
  // ============ Response Builders ============
16
37
  /**
17
38
  * Build the explore response - overview of all projects
@@ -95,7 +116,7 @@ get_started(intent: "convert")
95
116
  /**
96
117
  * Build the add_content response with schema details
97
118
  */
98
- function buildAddContentResponse(project) {
119
+ async function buildAddContentResponse(project) {
99
120
  let output = `# Fast Mode MCP - Add Content
100
121
 
101
122
  ## Project: ${project.name}
@@ -116,8 +137,61 @@ get_started(intent: "update_schema", projectId: "${project.id}")
116
137
  output += `## Collections
117
138
 
118
139
  `;
140
+ // Collect all relation fields across collections
141
+ const allRelationFields = [];
119
142
  for (const collection of project.collections) {
120
143
  output += buildCollectionTable(collection);
144
+ // Track relation fields
145
+ for (const field of collection.fields) {
146
+ if (field.type === 'relation' && field.referenceCollection) {
147
+ allRelationFields.push({ collection: collection.slug, field });
148
+ }
149
+ }
150
+ }
151
+ // If there are relation fields, show their options
152
+ if (allRelationFields.length > 0) {
153
+ output += `---
154
+
155
+ ## 📋 Relation Field Options
156
+
157
+ **Important:** Relation fields require item IDs, not names. Here are the available options:
158
+
159
+ `;
160
+ // Group by referenced collection to avoid duplicate fetches
161
+ const byRefCollection = new Map();
162
+ for (const rf of allRelationFields) {
163
+ const key = rf.field.referenceCollection;
164
+ if (!byRefCollection.has(key)) {
165
+ byRefCollection.set(key, []);
166
+ }
167
+ byRefCollection.get(key).push(rf);
168
+ }
169
+ // Fetch options for each referenced collection
170
+ for (const [refCollection, fields] of byRefCollection) {
171
+ const options = await fetchRelationOptions(project.id, refCollection, 5);
172
+ const fieldNames = fields.map(f => `\`${f.field.slug}\` in ${f.collection}`).join(', ');
173
+ output += `### ${refCollection}
174
+ Used by: ${fieldNames}
175
+
176
+ `;
177
+ if (options.length === 0) {
178
+ output += `*No items yet.* Create some first:
179
+ \`\`\`
180
+ create_cms_item(projectId: "${project.id}", collectionSlug: "${refCollection}", name: "Item Name", data: {...})
181
+ \`\`\`
182
+
183
+ `;
184
+ }
185
+ else {
186
+ for (const opt of options) {
187
+ output += `- \`"${opt.id}"\` → **${opt.name}**${opt.slug ? ` (${opt.slug})` : ''}\n`;
188
+ }
189
+ if (options.length === 5) {
190
+ output += `- *... more available via* \`list_cms_items(projectId: "${project.id}", collectionSlug: "${refCollection}")\`\n`;
191
+ }
192
+ output += '\n';
193
+ }
194
+ }
121
195
  }
122
196
  output += `---
123
197
 
@@ -169,12 +243,14 @@ data: { author: "John Smith" }
169
243
  // CORRECT:
170
244
  data: { author: "abc-123-uuid-of-john" }
171
245
  \`\`\`
172
-
246
+ ${allRelationFields.length > 0 ? `
247
+ **See "Relation Field Options" above for available IDs.**
248
+ ` : `
173
249
  To get the ID, first list items in the related collection:
174
250
  \`\`\`
175
251
  list_cms_items(projectId: "${project.id}", collectionSlug: "authors")
176
252
  \`\`\`
177
-
253
+ `}
178
254
  ### Delete requires confirmation
179
255
  Always ask the user before deleting:
180
256
  \`\`\`
@@ -572,7 +648,7 @@ Could not find project: "${projectId}"
572
648
 
573
649
  Use \`list_projects\` to see available projects.`;
574
650
  }
575
- return buildAddContentResponse(context.selectedProject);
651
+ return await buildAddContentResponse(context.selectedProject);
576
652
  case 'update_schema':
577
653
  if (!context.selectedProject) {
578
654
  if (!projectId) {
@@ -617,7 +693,7 @@ Use \`list_projects\` to see available projects.`;
617
693
  default:
618
694
  // If projectId provided, show detailed project info
619
695
  if (context.selectedProject) {
620
- return buildAddContentResponse(context.selectedProject);
696
+ return await buildAddContentResponse(context.selectedProject);
621
697
  }
622
698
  return buildExploreResponse(context);
623
699
  }
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "fastmode-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "MCP server for FastMode CMS. Convert websites, validate packages, and deploy directly to FastMode. Includes authentication, project creation, schema sync, and one-click deployment.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
- "fastmode-mcp": "./bin/run.js"
7
+ "fastmode-mcp": "./dist/index.js"
8
8
  },
9
9
  "type": "commonjs",
10
10
  "scripts": {
11
11
  "build": "NODE_OPTIONS='--max-old-space-size=4096' tsc --skipLibCheck && chmod +x dist/index.js",
12
12
  "dev": "ts-node src/index.ts",
13
13
  "start": "node dist/index.js",
14
- "postinstall": "node scripts/postinstall.js"
14
+ "prepublishOnly": "npm run build"
15
15
  },
16
16
  "keywords": [
17
17
  "mcp",
@@ -48,7 +48,7 @@
48
48
  "devDependencies": {
49
49
  "@types/adm-zip": "^0.5.7",
50
50
  "@types/node": "^20.11.0",
51
- "ts-node": "^10.9.2",
52
- "typescript": "^5.3.3"
51
+ "typescript": "^5.3.3",
52
+ "ts-node": "^10.9.2"
53
53
  }
54
54
  }
@@ -1,5 +0,0 @@
1
- /**
2
- * Returns the complete CMS schema reference for custom collections
3
- */
4
- export declare function getSchema(): Promise<string>;
5
- //# sourceMappingURL=get-schema.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"get-schema.d.ts","sourceRoot":"","sources":["../../src/tools/get-schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAyTjD"}
@@ -1,320 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getSchema = getSchema;
4
- /**
5
- * Returns the complete CMS schema reference for custom collections
6
- */
7
- async function getSchema() {
8
- return `# CMS Schema Reference
9
-
10
- ## Collections Overview
11
-
12
- All CMS content is managed through **custom collections**. Collections are content types you define (e.g., Blog Posts, Team Members, Services, Products) with custom fields.
13
-
14
- **To see the exact collections and fields for a specific project, use the \`get_tenant_schema\` tool.**
15
-
16
- ---
17
-
18
- ## Collection Templates
19
-
20
- When creating a new collection, you can start from a **template** that pre-configures common field sets:
21
-
22
- - **Blog Posts** - Articles with images, summary, body, author reference
23
- - **Authors** - Content creators with bio, photo, social links
24
- - **Team Members** - Staff profiles with role, photo, bio
25
- - **Downloads** - Downloadable files with description and category
26
- - **FAQs** - Question and answer pairs
27
- - **Products** - Items with price, description, images
28
-
29
- Templates are just starting points - you can customize fields after creation.
30
-
31
- ---
32
-
33
- ## Token Syntax
34
-
35
- ### Basic Tokens
36
- \`\`\`html
37
- {{fieldSlug}} <!-- For text, number, boolean, image, url, date, select -->
38
- {{{fieldSlug}}} <!-- For richText fields (MUST use triple braces) -->
39
- \`\`\`
40
-
41
- ### Built-in Tokens (Available on ALL items)
42
- - \`{{name}}\` - Item name/title (REQUIRED - every item has a name)
43
- - \`{{slug}}\` - Item URL slug (if collection has detail pages)
44
- - \`{{url}}\` - Full item URL (e.g., /services/my-service)
45
-
46
- ### Date Tokens (Automatically tracked)
47
- - \`{{createdAt}}\` - When the item was first created
48
- - \`{{publishedAt}}\` - When the item was published (empty for drafts)
49
- - \`{{updatedAt}}\` - When the item was last updated
50
-
51
- ---
52
-
53
- ## Loop Syntax
54
-
55
- ### Basic Loop
56
- \`\`\`html
57
- {{#each collectionSlug}}
58
- <article>
59
- <h2>{{name}}</h2>
60
- </article>
61
- {{/each}}
62
- \`\`\`
63
-
64
- ### Loop Modifiers
65
- - \`limit=N\` - Limit to N items
66
- - \`featured=true\` - Only featured items (if collection has a boolean "featured" field)
67
- - \`sort="fieldName"\` - Sort by field
68
- - \`order="asc|desc"\` - Sort direction
69
-
70
- \`\`\`html
71
- {{#each posts limit=6 sort="publishedAt" order="desc"}}
72
- <article>{{name}}</article>
73
- {{/each}}
74
- \`\`\`
75
-
76
- ### Loop Variables
77
- Inside \`{{#each}}\` blocks:
78
- - \`{{@first}}\` - true for first item
79
- - \`{{@last}}\` - true for last item
80
- - \`{{@index}}\` - zero-based index
81
-
82
- \`\`\`html
83
- {{#each services limit=3}}
84
- <div class="{{#if @first}}featured{{/if}}">
85
- {{name}}
86
- </div>
87
- {{/each}}
88
- \`\`\`
89
-
90
- ---
91
-
92
- ## Conditionals
93
-
94
- ### Check if field has value
95
- \`\`\`html
96
- {{#if image}}
97
- <img src="{{image}}" alt="{{name}}">
98
- {{else}}
99
- <div class="placeholder"></div>
100
- {{/if}}
101
-
102
- {{#unless featured}}
103
- <span>Regular item</span>
104
- {{/unless}}
105
- \`\`\`
106
-
107
- ### Collection Empty Checks
108
- \`\`\`html
109
- {{#each posts}}
110
- <article>{{name}}</article>
111
- {{/each}}
112
-
113
- {{#unless posts}}
114
- <p>No posts yet. Check back soon!</p>
115
- {{/unless}}
116
- \`\`\`
117
-
118
- ---
119
-
120
- ## Equality Comparisons
121
-
122
- Compare field values using the \`(eq field1 field2)\` helper:
123
-
124
- \`\`\`html
125
- <!-- Show content when fields ARE equal -->
126
- {{#if (eq category.slug ../slug)}}
127
- <span>Current category</span>
128
- {{/if}}
129
-
130
- <!-- Show content when fields are NOT equal (exclude current) -->
131
- {{#unless (eq slug ../slug)}}
132
- <a href="{{url}}">{{name}}</a>
133
- {{/unless}}
134
- \`\`\`
135
-
136
- ### Related Items Pattern (Exclude Current)
137
- \`\`\`html
138
- <h3>Other Posts</h3>
139
- {{#each posts limit=3}}
140
- {{#unless (eq slug ../slug)}}
141
- <article>
142
- <a href="{{url}}">{{name}}</a>
143
- </article>
144
- {{/unless}}
145
- {{/each}}
146
- \`\`\`
147
-
148
- ---
149
-
150
- ## Parent Context References
151
-
152
- Inside loops, access the parent scope using \`../\`:
153
-
154
- \`\`\`html
155
- <!-- On author detail page, show only posts by THIS author -->
156
- {{#each posts}}
157
- {{#if (eq author.name ../name)}}
158
- <h3>{{name}}</h3>
159
- {{/if}}
160
- {{/each}}
161
- \`\`\`
162
-
163
- - \`../name\` - Parent item's name field
164
- - \`../slug\` - Parent item's slug
165
- - \`../fieldName\` - Any field from the parent scope
166
-
167
- ---
168
-
169
- ## Relation Fields
170
-
171
- Link items from one collection to another. Access related item data using dot notation:
172
-
173
- \`\`\`html
174
- {{#each projects}}
175
- <article>
176
- <h2>{{name}}</h2>
177
- {{#if category}}
178
- <span class="tag">{{category.name}}</span>
179
- <a href="{{category.url}}">View all {{category.name}}</a>
180
- {{/if}}
181
- </article>
182
- {{/each}}
183
- \`\`\`
184
-
185
- **Available tokens for related items:**
186
- - \`{{relationField.name}}\` - Related item's name
187
- - \`{{relationField.slug}}\` - Related item's slug
188
- - \`{{relationField.url}}\` - Related item's full URL
189
- - \`{{relationField.anyField}}\` - Any field from the related collection
190
-
191
- ---
192
-
193
- ## Rich Text (Triple Braces)
194
-
195
- For HTML content that should NOT be escaped:
196
- \`\`\`html
197
- {{{description}}} ✓ Correct - renders HTML
198
- {{description}} ✗ Wrong - escapes HTML as text
199
- \`\`\`
200
-
201
- ---
202
-
203
- ## Important Rules
204
-
205
- 1. **Triple braces for richText fields** - \`{{{body}}}\`, \`{{{bio}}}\`
206
- 2. **Double braces for everything else** - \`{{name}}\`, \`{{image}}\`
207
- 3. **Always wrap optional fields in {{#if}}** - Check before rendering
208
- 4. **Use {{url}} for links** - Generates correct path based on manifest
209
- 5. **Match field slugs exactly** - Case-sensitive
210
-
211
- ---
212
-
213
- ## Image Handling
214
-
215
- ### Static UI Images (logos, icons)
216
- \`\`\`html
217
- <img src="/public/images/logo.png" alt="Logo">
218
- \`\`\`
219
-
220
- ### CMS Content Images
221
- \`\`\`html
222
- {{#if heroImage}}
223
- <img src="{{heroImage}}" alt="{{name}}">
224
- {{/if}}
225
- \`\`\`
226
-
227
- ---
228
-
229
- ## CMS Template Configuration
230
-
231
- Configure templates in manifest.json:
232
-
233
- \`\`\`json
234
- {
235
- "cmsTemplates": {
236
- "postsIndex": "pages/blog.html",
237
- "postsIndexPath": "/blog",
238
- "postsDetail": "templates/blog-post.html",
239
- "postsDetailPath": "/blog",
240
-
241
- "servicesIndex": "pages/services.html",
242
- "servicesIndexPath": "/services",
243
- "servicesDetail": "templates/service-detail.html",
244
- "servicesDetailPath": "/services"
245
- }
246
- }
247
- \`\`\`
248
-
249
- ### Template Keys Pattern
250
- - **{collectionSlug}Index** - Template file for listing page
251
- - **{collectionSlug}IndexPath** - URL path for listing page
252
- - **{collectionSlug}Detail** - Template file for detail page
253
- - **{collectionSlug}DetailPath** - URL base for detail pages
254
-
255
- ---
256
-
257
- ## Form Handling
258
-
259
- Forms are automatically captured and stored in the CMS.
260
-
261
- \`\`\`html
262
- <form data-form-name="contact">
263
- <input type="text" name="firstName" required>
264
- <input type="email" name="email" required>
265
- <textarea name="message"></textarea>
266
- <button type="submit">Send</button>
267
- </form>
268
- \`\`\`
269
-
270
- ### Form Handler Script
271
- \`\`\`javascript
272
- document.querySelectorAll('form[data-form-name]').forEach(form => {
273
- form.addEventListener('submit', async (e) => {
274
- e.preventDefault();
275
- const formName = form.dataset.formName || 'general';
276
- const formData = new FormData(form);
277
- const data = Object.fromEntries(formData);
278
-
279
- const response = await fetch('/_forms/' + formName, {
280
- method: 'POST',
281
- headers: { 'Content-Type': 'application/json' },
282
- body: JSON.stringify(data)
283
- });
284
-
285
- if (response.ok) {
286
- form.reset();
287
- alert(form.dataset.successMessage || 'Thank you!');
288
- }
289
- });
290
- });
291
- \`\`\`
292
-
293
- **CRITICAL:** Endpoint is \`/_forms/{formName}\` - NOT \`/api/forms/submit\`
294
-
295
- ---
296
-
297
- ## SEO Template Tokens
298
-
299
- For CMS detail pages, use tokens in SEO templates:
300
- - \`{{name}}\` - Item name for title
301
- - \`{{description}}\` or summary field - For meta description
302
- - \`{{image}}\` field - For OG image
303
-
304
- SEO templates are configured in the Fast Mode Editor.
305
-
306
- ---
307
-
308
- ## Next Steps
309
-
310
- **To see exact collections and fields for a specific project, use:**
311
- \`\`\`
312
- get_tenant_schema(projectId: "your-project-name-or-id")
313
- \`\`\`
314
-
315
- This will show you:
316
- - All collections with their slugs
317
- - All fields with their tokens, types, and descriptions
318
- - Relation field targets
319
- `;
320
- }