@qikdev/mcp 6.6.48 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/tools/campaign.d.ts +41 -0
- package/build/src/tools/campaign.d.ts.map +1 -0
- package/build/src/tools/campaign.js +331 -0
- package/build/src/tools/campaign.js.map +1 -0
- package/build/src/tools/checkin.d.ts +25 -0
- package/build/src/tools/checkin.d.ts.map +1 -0
- package/build/src/tools/checkin.js +199 -0
- package/build/src/tools/checkin.js.map +1 -0
- package/build/src/tools/definition.d.ts +90 -0
- package/build/src/tools/definition.d.ts.map +1 -0
- package/build/src/tools/definition.js +803 -0
- package/build/src/tools/definition.js.map +1 -0
- package/build/src/tools/filters.d.ts +11 -0
- package/build/src/tools/filters.d.ts.map +1 -0
- package/build/src/tools/filters.js +146 -0
- package/build/src/tools/filters.js.map +1 -0
- package/build/src/tools/glossary.d.ts +9 -0
- package/build/src/tools/glossary.d.ts.map +1 -1
- package/build/src/tools/glossary.js +200 -23
- package/build/src/tools/glossary.js.map +1 -1
- package/build/src/tools/index.d.ts.map +1 -1
- package/build/src/tools/index.js +56 -1
- package/build/src/tools/index.js.map +1 -1
- package/build/src/tools/list.d.ts.map +1 -1
- package/build/src/tools/list.js +41 -1
- package/build/src/tools/list.js.map +1 -1
- package/build/src/tools/relationships.d.ts +11 -0
- package/build/src/tools/relationships.d.ts.map +1 -0
- package/build/src/tools/relationships.js +130 -0
- package/build/src/tools/relationships.js.map +1 -0
- package/build/src/tools/reporting.d.ts +22 -0
- package/build/src/tools/reporting.d.ts.map +1 -0
- package/build/src/tools/reporting.js +219 -0
- package/build/src/tools/reporting.js.map +1 -0
- package/build/src/tools/sms.d.ts +13 -0
- package/build/src/tools/sms.d.ts.map +1 -0
- package/build/src/tools/sms.js +128 -0
- package/build/src/tools/sms.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
import { ConfigManager } from "../config.js";
|
|
2
|
+
import { getUserSessionData, getAvailableScopes, getScopeTitle } from "./user.js";
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// TOOL DEFINITIONS
|
|
5
|
+
// ============================================================================
|
|
6
|
+
export const getDefinitionSchemaTool = {
|
|
7
|
+
name: "get_definition_schema",
|
|
8
|
+
description: "Get the complete schema reference for creating definitions - includes valid field types, widgets, base types, and validation options. Call this before creating definitions to understand what's available.",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {},
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export const searchSimilarDefinitionsTool = {
|
|
15
|
+
name: "search_similar_definitions",
|
|
16
|
+
description: "Search existing definitions to find similar content types that might be reused or extended. Use this before creating new definitions to check for existing types.",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
query: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Natural language description of what you're looking for (e.g., 'inventory tracking', 'product catalog', 'customer records')"
|
|
23
|
+
},
|
|
24
|
+
category: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Optional category to filter by"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
required: ["query"]
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
export const createDefinitionTool = {
|
|
33
|
+
name: "create_definition",
|
|
34
|
+
description: "Create a new content type definition in Qik. Definitions are blueprints that specify the fields, validation rules, and behavior of content you store.",
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
title: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Singular title for the content type (e.g., 'Car Part', 'Supplier', 'Invoice')"
|
|
41
|
+
},
|
|
42
|
+
plural: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "Plural title (e.g., 'Car Parts', 'Suppliers'). Auto-generated if not provided."
|
|
45
|
+
},
|
|
46
|
+
definesType: {
|
|
47
|
+
type: "string",
|
|
48
|
+
enum: ["article", "submission", "scope", "event", "profile", "email", "integration", "workflowcard", "roster"],
|
|
49
|
+
description: "The base content type to extend. Use 'article' for most custom content, 'submission' for forms with payments, 'event' for calendar items."
|
|
50
|
+
},
|
|
51
|
+
category: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Category for organizing in admin menus (e.g., 'Inventory', 'Sales', 'People')"
|
|
54
|
+
},
|
|
55
|
+
fields: {
|
|
56
|
+
type: "array",
|
|
57
|
+
description: "Array of field definitions",
|
|
58
|
+
items: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
title: { type: "string", description: "Display name for the field" },
|
|
62
|
+
key: { type: "string", description: "Database key (camelCase). Auto-generated from title if not provided." },
|
|
63
|
+
type: {
|
|
64
|
+
type: "string",
|
|
65
|
+
enum: ["string", "number", "integer", "boolean", "date", "email", "url", "reference", "object", "group"],
|
|
66
|
+
description: "Data type for the field"
|
|
67
|
+
},
|
|
68
|
+
widget: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "UI widget type (input, textarea, select, button, richtext, datepicker, upload, currency, checkbox, switch, code)"
|
|
71
|
+
},
|
|
72
|
+
description: { type: "string", description: "Help text shown to users" },
|
|
73
|
+
minimum: { type: "number", description: "Minimum required answers. 0 = optional, 1+ = required." },
|
|
74
|
+
maximum: { type: "number", description: "Maximum answers allowed. 0 = unlimited, 1 = single value." },
|
|
75
|
+
referenceType: { type: "string", description: "For reference fields - the content type key to link to" },
|
|
76
|
+
options: {
|
|
77
|
+
type: "array",
|
|
78
|
+
description: "For select/button widgets - predefined choices",
|
|
79
|
+
items: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
title: { type: "string" },
|
|
83
|
+
value: { type: "string" }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
defaultValues: {
|
|
88
|
+
type: "array",
|
|
89
|
+
description: "Default values for the field"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
required: ["title", "type"]
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
scope: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Scope ID to create the definition in"
|
|
98
|
+
},
|
|
99
|
+
defaultScopes: {
|
|
100
|
+
type: "array",
|
|
101
|
+
items: { type: "string" },
|
|
102
|
+
description: "Default scopes for content created with this definition"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
required: ["title", "definesType"]
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
export const updateDefinitionTool = {
|
|
109
|
+
name: "update_definition",
|
|
110
|
+
description: "Update an existing definition - add/remove fields, change settings, or modify configuration",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
definitionId: {
|
|
115
|
+
type: "string",
|
|
116
|
+
description: "The ID of the definition to update"
|
|
117
|
+
},
|
|
118
|
+
definitionKey: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "Alternatively, the key of the definition to update (e.g., 'carPart')"
|
|
121
|
+
},
|
|
122
|
+
changes: {
|
|
123
|
+
type: "object",
|
|
124
|
+
description: "The changes to apply",
|
|
125
|
+
properties: {
|
|
126
|
+
title: { type: "string", description: "New title" },
|
|
127
|
+
plural: { type: "string", description: "New plural title" },
|
|
128
|
+
category: { type: "string", description: "New category" },
|
|
129
|
+
addFields: {
|
|
130
|
+
type: "array",
|
|
131
|
+
description: "New fields to add (same format as create_definition fields)"
|
|
132
|
+
},
|
|
133
|
+
removeFields: {
|
|
134
|
+
type: "array",
|
|
135
|
+
items: { type: "string" },
|
|
136
|
+
description: "Field keys to remove"
|
|
137
|
+
},
|
|
138
|
+
updateFields: {
|
|
139
|
+
type: "array",
|
|
140
|
+
description: "Fields to modify (matched by key)"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
required: ["changes"]
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
export const listDefinitionsTool = {
|
|
149
|
+
name: "list_definitions",
|
|
150
|
+
description: "List and search custom definitions (content types) in the platform",
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
search: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "Search term to filter definitions by title, key, or description"
|
|
157
|
+
},
|
|
158
|
+
definesType: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "Filter by base type (e.g., 'article', 'submission', 'event')"
|
|
161
|
+
},
|
|
162
|
+
category: {
|
|
163
|
+
type: "string",
|
|
164
|
+
description: "Filter by category"
|
|
165
|
+
},
|
|
166
|
+
includeFields: {
|
|
167
|
+
type: "boolean",
|
|
168
|
+
description: "Include field definitions in response (default: false)"
|
|
169
|
+
},
|
|
170
|
+
page: {
|
|
171
|
+
type: "object",
|
|
172
|
+
description: "Pagination settings",
|
|
173
|
+
properties: {
|
|
174
|
+
size: { type: "number", description: "Items per page (default: 20)" },
|
|
175
|
+
index: { type: "number", description: "Page number, 1-based (default: 1)" }
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
// ============================================================================
|
|
182
|
+
// HANDLERS
|
|
183
|
+
// ============================================================================
|
|
184
|
+
export async function handleGetDefinitionSchema() {
|
|
185
|
+
const schema = {
|
|
186
|
+
baseTypes: [
|
|
187
|
+
{ key: "article", description: "General content items with title, body, tags. Use for most custom content types." },
|
|
188
|
+
{ key: "submission", description: "Form submissions with payment support, automation, and profile linking." },
|
|
189
|
+
{ key: "event", description: "Calendar events with start/end dates, recurrence, and scheduling." },
|
|
190
|
+
{ key: "profile", description: "People/contacts with name, contact info, address, and relationships." },
|
|
191
|
+
{ key: "scope", description: "Organizational units for multi-tenancy and hierarchical permissions." },
|
|
192
|
+
{ key: "email", description: "Email templates with variables and formatting." },
|
|
193
|
+
{ key: "integration", description: "Third-party service integrations and webhooks." },
|
|
194
|
+
{ key: "workflowcard", description: "Process automation templates with state machines." },
|
|
195
|
+
{ key: "roster", description: "Scheduling and staffing management." }
|
|
196
|
+
],
|
|
197
|
+
fieldTypes: [
|
|
198
|
+
{ type: "string", description: "Basic text", widgets: ["input", "textarea", "select", "button", "richtext", "code"] },
|
|
199
|
+
{ type: "number", description: "Decimal numbers", widgets: ["input", "slider", "currency"] },
|
|
200
|
+
{ type: "integer", description: "Whole numbers", widgets: ["input", "slider"] },
|
|
201
|
+
{ type: "boolean", description: "True/false", widgets: ["checkbox", "switch"] },
|
|
202
|
+
{ type: "date", description: "Date/time values", widgets: ["datepicker", "dateobject"] },
|
|
203
|
+
{ type: "email", description: "Email addresses", widgets: ["input"] },
|
|
204
|
+
{ type: "url", description: "URLs/links", widgets: ["input"] },
|
|
205
|
+
{ type: "reference", description: "Links to other content", widgets: ["select", "button", "upload", "form"] },
|
|
206
|
+
{ type: "object", description: "Complex/nested data", widgets: ["filter", "dateobject", "location"] },
|
|
207
|
+
{ type: "group", description: "Visual field grouping (no data storage)", widgets: null }
|
|
208
|
+
],
|
|
209
|
+
widgetDetails: {
|
|
210
|
+
input: "Single-line text input",
|
|
211
|
+
textarea: "Multi-line text area",
|
|
212
|
+
select: "Dropdown selection",
|
|
213
|
+
button: "Button-style selection (radio or checkbox)",
|
|
214
|
+
richtext: "Rich text editor with formatting",
|
|
215
|
+
code: "Code editor with syntax highlighting",
|
|
216
|
+
datepicker: "Date and/or time picker",
|
|
217
|
+
dateobject: "Structured date object with separate fields",
|
|
218
|
+
upload: "File/image upload",
|
|
219
|
+
currency: "Currency input with formatting",
|
|
220
|
+
checkbox: "Checkbox for boolean",
|
|
221
|
+
switch: "Toggle switch for boolean",
|
|
222
|
+
slider: "Slider for number ranges",
|
|
223
|
+
filter: "Complex filter builder",
|
|
224
|
+
location: "Location/address picker",
|
|
225
|
+
form: "Embedded form for references"
|
|
226
|
+
},
|
|
227
|
+
validationOptions: {
|
|
228
|
+
minimum: "Minimum required answers (0 = optional, 1+ = required)",
|
|
229
|
+
maximum: "Maximum answers allowed (0 = unlimited)",
|
|
230
|
+
minValue: "For numbers - minimum value allowed",
|
|
231
|
+
maxValue: "For numbers - maximum value allowed",
|
|
232
|
+
allowedValues: "Restrict to specific values only"
|
|
233
|
+
},
|
|
234
|
+
referenceOptions: {
|
|
235
|
+
referenceType: "Content type key to link to (e.g., 'profile', 'partCategory')",
|
|
236
|
+
lockFilter: "Filter to restrict which items can be selected",
|
|
237
|
+
searchable: "Allow searching within reference items"
|
|
238
|
+
},
|
|
239
|
+
examples: {
|
|
240
|
+
simpleTextField: {
|
|
241
|
+
title: "Name",
|
|
242
|
+
type: "string",
|
|
243
|
+
widget: "input",
|
|
244
|
+
minimum: 1,
|
|
245
|
+
maximum: 1
|
|
246
|
+
},
|
|
247
|
+
optionalTextArea: {
|
|
248
|
+
title: "Description",
|
|
249
|
+
type: "string",
|
|
250
|
+
widget: "textarea",
|
|
251
|
+
minimum: 0,
|
|
252
|
+
maximum: 1
|
|
253
|
+
},
|
|
254
|
+
currencyField: {
|
|
255
|
+
title: "Price",
|
|
256
|
+
type: "number",
|
|
257
|
+
widget: "currency",
|
|
258
|
+
minimum: 1
|
|
259
|
+
},
|
|
260
|
+
selectField: {
|
|
261
|
+
title: "Status",
|
|
262
|
+
type: "string",
|
|
263
|
+
widget: "select",
|
|
264
|
+
minimum: 1,
|
|
265
|
+
options: [
|
|
266
|
+
{ title: "Active", value: "active" },
|
|
267
|
+
{ title: "Inactive", value: "inactive" },
|
|
268
|
+
{ title: "Archived", value: "archived" }
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
referenceField: {
|
|
272
|
+
title: "Category",
|
|
273
|
+
type: "reference",
|
|
274
|
+
widget: "select",
|
|
275
|
+
referenceType: "category",
|
|
276
|
+
minimum: 1
|
|
277
|
+
},
|
|
278
|
+
dateField: {
|
|
279
|
+
title: "Due Date",
|
|
280
|
+
type: "date",
|
|
281
|
+
widget: "datepicker",
|
|
282
|
+
minimum: 0
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
return {
|
|
287
|
+
content: [{
|
|
288
|
+
type: "text",
|
|
289
|
+
text: `# Definition Schema Reference
|
|
290
|
+
|
|
291
|
+
## Base Types
|
|
292
|
+
|
|
293
|
+
${schema.baseTypes.map(t => `**${t.key}**: ${t.description}`).join('\n')}
|
|
294
|
+
|
|
295
|
+
## Field Types
|
|
296
|
+
|
|
297
|
+
${schema.fieldTypes.map(t => `**${t.type}**: ${t.description}${t.widgets ? ` (widgets: ${t.widgets.join(', ')})` : ''}`).join('\n')}
|
|
298
|
+
|
|
299
|
+
## Widget Details
|
|
300
|
+
|
|
301
|
+
${Object.entries(schema.widgetDetails).map(([k, v]) => `**${k}**: ${v}`).join('\n')}
|
|
302
|
+
|
|
303
|
+
## Validation Options
|
|
304
|
+
|
|
305
|
+
${Object.entries(schema.validationOptions).map(([k, v]) => `**${k}**: ${v}`).join('\n')}
|
|
306
|
+
|
|
307
|
+
## Reference Field Options
|
|
308
|
+
|
|
309
|
+
${Object.entries(schema.referenceOptions).map(([k, v]) => `**${k}**: ${v}`).join('\n')}
|
|
310
|
+
|
|
311
|
+
## Field Examples
|
|
312
|
+
|
|
313
|
+
\`\`\`json
|
|
314
|
+
${JSON.stringify(schema.examples, null, 2)}
|
|
315
|
+
\`\`\`
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
Use this schema to construct valid field definitions when calling create_definition.`
|
|
319
|
+
}]
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
export async function handleSearchSimilarDefinitions(args) {
|
|
323
|
+
try {
|
|
324
|
+
const configManager = new ConfigManager();
|
|
325
|
+
const config = await configManager.loadConfig();
|
|
326
|
+
if (!config) {
|
|
327
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
328
|
+
}
|
|
329
|
+
// Fetch all definitions from glossary
|
|
330
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/glossary/ai`, {
|
|
331
|
+
headers: {
|
|
332
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
333
|
+
'Content-Type': 'application/json',
|
|
334
|
+
},
|
|
335
|
+
});
|
|
336
|
+
if (!response.ok) {
|
|
337
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
338
|
+
}
|
|
339
|
+
const glossary = await response.json();
|
|
340
|
+
// Filter to only custom definitions (those with definesType set)
|
|
341
|
+
let definitions = glossary.filter(item => item.definesType);
|
|
342
|
+
// Filter by category if provided
|
|
343
|
+
if (args.category) {
|
|
344
|
+
const categoryLower = args.category.toLowerCase();
|
|
345
|
+
definitions = definitions.filter(d => d.category && d.category.toLowerCase().includes(categoryLower));
|
|
346
|
+
}
|
|
347
|
+
// Search and score by relevance
|
|
348
|
+
const searchTerms = args.query.toLowerCase().split(/\s+/);
|
|
349
|
+
const scored = definitions.map(def => {
|
|
350
|
+
let score = 0;
|
|
351
|
+
const title = (def.title || '').toLowerCase();
|
|
352
|
+
const plural = (def.plural || '').toLowerCase();
|
|
353
|
+
const key = (def.key || '').toLowerCase();
|
|
354
|
+
const category = (def.category || '').toLowerCase();
|
|
355
|
+
for (const term of searchTerms) {
|
|
356
|
+
if (title.includes(term))
|
|
357
|
+
score += 10;
|
|
358
|
+
if (plural.includes(term))
|
|
359
|
+
score += 8;
|
|
360
|
+
if (key.includes(term))
|
|
361
|
+
score += 6;
|
|
362
|
+
if (category.includes(term))
|
|
363
|
+
score += 4;
|
|
364
|
+
// Partial matches
|
|
365
|
+
if (term.includes(title) || title.includes(term))
|
|
366
|
+
score += 2;
|
|
367
|
+
}
|
|
368
|
+
return { ...def, score };
|
|
369
|
+
});
|
|
370
|
+
// Sort by score and filter out zero scores
|
|
371
|
+
const matches = scored
|
|
372
|
+
.filter(d => d.score > 0)
|
|
373
|
+
.sort((a, b) => b.score - a.score)
|
|
374
|
+
.slice(0, 10);
|
|
375
|
+
if (matches.length === 0) {
|
|
376
|
+
return {
|
|
377
|
+
content: [{
|
|
378
|
+
type: "text",
|
|
379
|
+
text: `No existing definitions found matching "${args.query}".
|
|
380
|
+
|
|
381
|
+
This means you can create a new definition for this purpose. Use create_definition to create one.`
|
|
382
|
+
}]
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
const resultText = matches.map((def, i) => {
|
|
386
|
+
return `**${i + 1}. ${def.title}** (${def.plural})
|
|
387
|
+
Key: \`${def.key}\`
|
|
388
|
+
Extends: ${def.definesType}
|
|
389
|
+
Category: ${def.category || 'None'}
|
|
390
|
+
Relevance: ${Math.round((def.score / 10) * 100)}%`;
|
|
391
|
+
}).join('\n\n');
|
|
392
|
+
return {
|
|
393
|
+
content: [{
|
|
394
|
+
type: "text",
|
|
395
|
+
text: `# Similar Definitions Found
|
|
396
|
+
|
|
397
|
+
${resultText}
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
**Options:**
|
|
401
|
+
- Use an existing definition if it matches your needs
|
|
402
|
+
- Create a new definition if none of these fit
|
|
403
|
+
- Use \`get_content_type_details\` with any key above to see full field details`
|
|
404
|
+
}]
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
catch (error) {
|
|
408
|
+
return {
|
|
409
|
+
content: [{
|
|
410
|
+
type: "text",
|
|
411
|
+
text: `Failed to search definitions: ${error.message}`
|
|
412
|
+
}]
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
export async function handleCreateDefinition(args) {
|
|
417
|
+
try {
|
|
418
|
+
const configManager = new ConfigManager();
|
|
419
|
+
const config = await configManager.loadConfig();
|
|
420
|
+
if (!config) {
|
|
421
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
422
|
+
}
|
|
423
|
+
// Validate required fields
|
|
424
|
+
if (!args.title) {
|
|
425
|
+
return createErrorResponse('Title is required for creating a definition');
|
|
426
|
+
}
|
|
427
|
+
if (!args.definesType) {
|
|
428
|
+
return createErrorResponse('definesType is required. Use "article" for most custom content types.');
|
|
429
|
+
}
|
|
430
|
+
// Get user session for permissions
|
|
431
|
+
const userSession = await getUserSessionData();
|
|
432
|
+
const createPermission = 'definition.create';
|
|
433
|
+
const availableScopes = getAvailableScopes(userSession, createPermission);
|
|
434
|
+
if (availableScopes.length === 0) {
|
|
435
|
+
return createErrorResponse(`You don't have permission to create definitions. Required permission: ${createPermission}`);
|
|
436
|
+
}
|
|
437
|
+
// Handle scope selection
|
|
438
|
+
const selectedScopeId = args.scope;
|
|
439
|
+
if (!selectedScopeId && availableScopes.length > 1) {
|
|
440
|
+
return createScopeSelectionPrompt('definition', availableScopes, userSession);
|
|
441
|
+
}
|
|
442
|
+
const targetScopeId = selectedScopeId || availableScopes[0];
|
|
443
|
+
if (!availableScopes.includes(targetScopeId)) {
|
|
444
|
+
return createErrorResponse(`Invalid scope "${targetScopeId}". Available scopes: ${availableScopes.map(id => `${getScopeTitle(userSession, id)} (${id})`).join(', ')}`);
|
|
445
|
+
}
|
|
446
|
+
// Generate key from title
|
|
447
|
+
const key = generateKey(args.title);
|
|
448
|
+
// Build definition payload
|
|
449
|
+
const definitionPayload = {
|
|
450
|
+
title: args.title,
|
|
451
|
+
plural: args.plural || `${args.title}s`,
|
|
452
|
+
key: key,
|
|
453
|
+
definesType: args.definesType,
|
|
454
|
+
category: args.category || '',
|
|
455
|
+
fields: normalizeFields(args.fields || []),
|
|
456
|
+
defaultScopes: args.defaultScopes || [],
|
|
457
|
+
restrictScopes: [],
|
|
458
|
+
weight: 0,
|
|
459
|
+
meta: {
|
|
460
|
+
scopes: [targetScopeId],
|
|
461
|
+
status: 'active'
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
// Create via API
|
|
465
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/definition/create`, {
|
|
466
|
+
method: 'POST',
|
|
467
|
+
headers: {
|
|
468
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
469
|
+
'Content-Type': 'application/json',
|
|
470
|
+
},
|
|
471
|
+
body: JSON.stringify(definitionPayload)
|
|
472
|
+
});
|
|
473
|
+
if (!response.ok) {
|
|
474
|
+
const errorText = await response.text();
|
|
475
|
+
throw new Error(`HTTP ${response.status} - ${errorText}`);
|
|
476
|
+
}
|
|
477
|
+
const createdDefinition = await response.json();
|
|
478
|
+
return {
|
|
479
|
+
content: [{
|
|
480
|
+
type: "text",
|
|
481
|
+
text: `Successfully created definition "${args.title}"!
|
|
482
|
+
|
|
483
|
+
**Details:**
|
|
484
|
+
- ID: ${createdDefinition._id}
|
|
485
|
+
- Key: \`${key}\`
|
|
486
|
+
- Base Type: ${args.definesType}
|
|
487
|
+
- Category: ${args.category || 'None'}
|
|
488
|
+
- Fields: ${(args.fields || []).length}
|
|
489
|
+
- Scope: ${getScopeTitle(userSession, targetScopeId)}
|
|
490
|
+
|
|
491
|
+
**Next Steps:**
|
|
492
|
+
- Create content with: \`create_content\` using typeKey: \`${key}\`
|
|
493
|
+
- View full details: \`get_content_type_details\` with key: \`${key}\`
|
|
494
|
+
- Add more fields: \`update_definition\` with definitionKey: \`${key}\``
|
|
495
|
+
}]
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
return createErrorResponse(`Failed to create definition: ${error.message}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
export async function handleUpdateDefinition(args) {
|
|
503
|
+
try {
|
|
504
|
+
const configManager = new ConfigManager();
|
|
505
|
+
const config = await configManager.loadConfig();
|
|
506
|
+
if (!config) {
|
|
507
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
508
|
+
}
|
|
509
|
+
if (!args.changes) {
|
|
510
|
+
return createErrorResponse('No changes provided');
|
|
511
|
+
}
|
|
512
|
+
// Need either definitionId or definitionKey
|
|
513
|
+
let definitionId = args.definitionId;
|
|
514
|
+
if (!definitionId && args.definitionKey) {
|
|
515
|
+
// Look up the definition by key
|
|
516
|
+
const listResponse = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/definition/list`, {
|
|
517
|
+
method: 'POST',
|
|
518
|
+
headers: {
|
|
519
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
520
|
+
'Content-Type': 'application/json',
|
|
521
|
+
},
|
|
522
|
+
body: JSON.stringify({
|
|
523
|
+
filter: {
|
|
524
|
+
operator: 'and',
|
|
525
|
+
filters: [
|
|
526
|
+
{ key: 'key', comparator: 'equal', value: args.definitionKey }
|
|
527
|
+
]
|
|
528
|
+
},
|
|
529
|
+
page: { size: 1, index: 1 }
|
|
530
|
+
})
|
|
531
|
+
});
|
|
532
|
+
if (!listResponse.ok) {
|
|
533
|
+
throw new Error(`Failed to find definition: HTTP ${listResponse.status}`);
|
|
534
|
+
}
|
|
535
|
+
const listData = await listResponse.json();
|
|
536
|
+
if (!listData.items || listData.items.length === 0) {
|
|
537
|
+
return createErrorResponse(`Definition with key "${args.definitionKey}" not found`);
|
|
538
|
+
}
|
|
539
|
+
definitionId = listData.items[0]._id;
|
|
540
|
+
}
|
|
541
|
+
if (!definitionId) {
|
|
542
|
+
return createErrorResponse('Either definitionId or definitionKey must be provided');
|
|
543
|
+
}
|
|
544
|
+
// First, get the current definition
|
|
545
|
+
const getResponse = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/${definitionId}`, {
|
|
546
|
+
headers: {
|
|
547
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
548
|
+
'Content-Type': 'application/json',
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
if (!getResponse.ok) {
|
|
552
|
+
throw new Error(`Failed to get definition: HTTP ${getResponse.status}`);
|
|
553
|
+
}
|
|
554
|
+
const currentDefinition = await getResponse.json();
|
|
555
|
+
// Build update payload
|
|
556
|
+
const updatePayload = { ...currentDefinition };
|
|
557
|
+
if (args.changes.title) {
|
|
558
|
+
updatePayload.title = args.changes.title;
|
|
559
|
+
}
|
|
560
|
+
if (args.changes.plural) {
|
|
561
|
+
updatePayload.plural = args.changes.plural;
|
|
562
|
+
}
|
|
563
|
+
if (args.changes.category) {
|
|
564
|
+
updatePayload.category = args.changes.category;
|
|
565
|
+
}
|
|
566
|
+
// Handle field changes
|
|
567
|
+
let fields = [...(currentDefinition.fields || [])];
|
|
568
|
+
// Remove fields
|
|
569
|
+
if (args.changes.removeFields && args.changes.removeFields.length > 0) {
|
|
570
|
+
fields = fields.filter(f => !args.changes.removeFields.includes(f.key));
|
|
571
|
+
}
|
|
572
|
+
// Update fields
|
|
573
|
+
if (args.changes.updateFields && args.changes.updateFields.length > 0) {
|
|
574
|
+
for (const updateField of args.changes.updateFields) {
|
|
575
|
+
const index = fields.findIndex(f => f.key === updateField.key);
|
|
576
|
+
if (index !== -1) {
|
|
577
|
+
fields[index] = { ...fields[index], ...normalizeField(updateField) };
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Add fields
|
|
582
|
+
if (args.changes.addFields && args.changes.addFields.length > 0) {
|
|
583
|
+
const normalizedNewFields = args.changes.addFields.map(f => normalizeField(f));
|
|
584
|
+
fields = [...fields, ...normalizedNewFields];
|
|
585
|
+
}
|
|
586
|
+
updatePayload.fields = fields;
|
|
587
|
+
// Update via API
|
|
588
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/${definitionId}`, {
|
|
589
|
+
method: 'PUT',
|
|
590
|
+
headers: {
|
|
591
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
592
|
+
'Content-Type': 'application/json',
|
|
593
|
+
},
|
|
594
|
+
body: JSON.stringify(updatePayload)
|
|
595
|
+
});
|
|
596
|
+
if (!response.ok) {
|
|
597
|
+
const errorText = await response.text();
|
|
598
|
+
throw new Error(`HTTP ${response.status} - ${errorText}`);
|
|
599
|
+
}
|
|
600
|
+
const updatedDefinition = await response.json();
|
|
601
|
+
return {
|
|
602
|
+
content: [{
|
|
603
|
+
type: "text",
|
|
604
|
+
text: `Successfully updated definition "${updatedDefinition.title}"!
|
|
605
|
+
|
|
606
|
+
**Changes Applied:**
|
|
607
|
+
${args.changes.title ? `- Title changed to: ${args.changes.title}` : ''}
|
|
608
|
+
${args.changes.plural ? `- Plural changed to: ${args.changes.plural}` : ''}
|
|
609
|
+
${args.changes.category ? `- Category changed to: ${args.changes.category}` : ''}
|
|
610
|
+
${args.changes.addFields ? `- Added ${args.changes.addFields.length} field(s)` : ''}
|
|
611
|
+
${args.changes.removeFields ? `- Removed ${args.changes.removeFields.length} field(s)` : ''}
|
|
612
|
+
${args.changes.updateFields ? `- Updated ${args.changes.updateFields.length} field(s)` : ''}
|
|
613
|
+
|
|
614
|
+
**Current Field Count:** ${fields.length}`
|
|
615
|
+
}]
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
catch (error) {
|
|
619
|
+
return createErrorResponse(`Failed to update definition: ${error.message}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
export async function handleListDefinitions(args) {
|
|
623
|
+
try {
|
|
624
|
+
const configManager = new ConfigManager();
|
|
625
|
+
const config = await configManager.loadConfig();
|
|
626
|
+
if (!config) {
|
|
627
|
+
throw new Error('Qik MCP server not configured. Run setup first.');
|
|
628
|
+
}
|
|
629
|
+
// Build filter
|
|
630
|
+
const filters = [];
|
|
631
|
+
if (args.search) {
|
|
632
|
+
// Search in title and key
|
|
633
|
+
filters.push({
|
|
634
|
+
operator: 'or',
|
|
635
|
+
filters: [
|
|
636
|
+
{ key: 'title', comparator: 'contains', value: args.search },
|
|
637
|
+
{ key: 'key', comparator: 'contains', value: args.search }
|
|
638
|
+
]
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
if (args.definesType) {
|
|
642
|
+
filters.push({ key: 'definesType', comparator: 'equal', value: args.definesType });
|
|
643
|
+
}
|
|
644
|
+
if (args.category) {
|
|
645
|
+
filters.push({ key: 'category', comparator: 'contains', value: args.category });
|
|
646
|
+
}
|
|
647
|
+
const listPayload = {
|
|
648
|
+
sort: { key: 'title', direction: 'asc', type: 'string' },
|
|
649
|
+
page: {
|
|
650
|
+
size: args.page?.size || 20,
|
|
651
|
+
index: args.page?.index || 1
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
if (filters.length > 0) {
|
|
655
|
+
listPayload.filter = {
|
|
656
|
+
operator: 'and',
|
|
657
|
+
filters: filters
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
// Select fields based on includeFields option
|
|
661
|
+
if (args.includeFields) {
|
|
662
|
+
listPayload.select = ['_id', 'title', 'plural', 'key', 'definesType', 'category', 'fields', 'meta'];
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
listPayload.select = ['_id', 'title', 'plural', 'key', 'definesType', 'category', 'meta'];
|
|
666
|
+
}
|
|
667
|
+
const response = await fetch(`${config.apiUrl || 'https://api.qik.dev'}/content/definition/list`, {
|
|
668
|
+
method: 'POST',
|
|
669
|
+
headers: {
|
|
670
|
+
'Authorization': `Bearer ${config.accessToken}`,
|
|
671
|
+
'Content-Type': 'application/json',
|
|
672
|
+
},
|
|
673
|
+
body: JSON.stringify(listPayload)
|
|
674
|
+
});
|
|
675
|
+
if (!response.ok) {
|
|
676
|
+
const errorText = await response.text();
|
|
677
|
+
throw new Error(`HTTP ${response.status} - ${errorText}`);
|
|
678
|
+
}
|
|
679
|
+
const results = await response.json();
|
|
680
|
+
const items = results.items || [];
|
|
681
|
+
const total = results.total || items.length;
|
|
682
|
+
const currentPage = args.page?.index || 1;
|
|
683
|
+
const pageSize = args.page?.size || 20;
|
|
684
|
+
if (items.length === 0) {
|
|
685
|
+
return {
|
|
686
|
+
content: [{
|
|
687
|
+
type: "text",
|
|
688
|
+
text: `No definitions found matching your criteria.
|
|
689
|
+
|
|
690
|
+
Use create_definition to create a new content type.`
|
|
691
|
+
}]
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
let resultText = `# Definitions (${total} total)`;
|
|
695
|
+
if (total > pageSize) {
|
|
696
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
697
|
+
resultText += ` - Page ${currentPage} of ${totalPages}`;
|
|
698
|
+
}
|
|
699
|
+
resultText += '\n\n';
|
|
700
|
+
items.forEach((item, index) => {
|
|
701
|
+
const itemNumber = ((currentPage - 1) * pageSize) + index + 1;
|
|
702
|
+
resultText += `**${itemNumber}. ${item.title}** (${item.plural || item.title + 's'})\n`;
|
|
703
|
+
resultText += ` Key: \`${item.key}\`\n`;
|
|
704
|
+
resultText += ` Extends: ${item.definesType}\n`;
|
|
705
|
+
if (item.category) {
|
|
706
|
+
resultText += ` Category: ${item.category}\n`;
|
|
707
|
+
}
|
|
708
|
+
if (args.includeFields && item.fields) {
|
|
709
|
+
resultText += ` Fields: ${item.fields.length} (${item.fields.slice(0, 3).map((f) => f.title).join(', ')}${item.fields.length > 3 ? '...' : ''})\n`;
|
|
710
|
+
}
|
|
711
|
+
resultText += '\n';
|
|
712
|
+
});
|
|
713
|
+
if (total > pageSize && currentPage < Math.ceil(total / pageSize)) {
|
|
714
|
+
resultText += `\nTo see more: use page: { index: ${currentPage + 1} }`;
|
|
715
|
+
}
|
|
716
|
+
return {
|
|
717
|
+
content: [{
|
|
718
|
+
type: "text",
|
|
719
|
+
text: resultText
|
|
720
|
+
}]
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
return createErrorResponse(`Failed to list definitions: ${error.message}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
// ============================================================================
|
|
728
|
+
// HELPER FUNCTIONS
|
|
729
|
+
// ============================================================================
|
|
730
|
+
function createErrorResponse(message) {
|
|
731
|
+
return {
|
|
732
|
+
content: [{
|
|
733
|
+
type: "text",
|
|
734
|
+
text: `${message}`
|
|
735
|
+
}]
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function createScopeSelectionPrompt(contentType, availableScopes, userSession) {
|
|
739
|
+
const scopeOptions = availableScopes.map(scopeId => {
|
|
740
|
+
const title = getScopeTitle(userSession, scopeId);
|
|
741
|
+
return `- **${title}** (ID: \`${scopeId}\`)`;
|
|
742
|
+
}).join('\n');
|
|
743
|
+
return {
|
|
744
|
+
content: [{
|
|
745
|
+
type: "text",
|
|
746
|
+
text: `You can create this ${contentType} in multiple scopes. Please select one:
|
|
747
|
+
|
|
748
|
+
${scopeOptions}
|
|
749
|
+
|
|
750
|
+
Call create_definition again with the scope parameter:
|
|
751
|
+
\`\`\`json
|
|
752
|
+
{
|
|
753
|
+
"title": "Your Title",
|
|
754
|
+
"definesType": "article",
|
|
755
|
+
"scope": "scope_id_here"
|
|
756
|
+
}
|
|
757
|
+
\`\`\``
|
|
758
|
+
}]
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
function generateKey(title) {
|
|
762
|
+
// Convert "Car Part" to "carPart"
|
|
763
|
+
return title
|
|
764
|
+
.toLowerCase()
|
|
765
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
766
|
+
.split(/\s+/)
|
|
767
|
+
.map((word, i) => i === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
|
|
768
|
+
.join('');
|
|
769
|
+
}
|
|
770
|
+
function normalizeFields(fields) {
|
|
771
|
+
return fields.map(field => normalizeField(field));
|
|
772
|
+
}
|
|
773
|
+
function normalizeField(field) {
|
|
774
|
+
return {
|
|
775
|
+
title: field.title,
|
|
776
|
+
key: field.key || generateKey(field.title),
|
|
777
|
+
type: field.type || 'string',
|
|
778
|
+
widget: field.widget || getDefaultWidget(field.type),
|
|
779
|
+
description: field.description || '',
|
|
780
|
+
minimum: field.minimum ?? 0,
|
|
781
|
+
maximum: field.maximum ?? 1,
|
|
782
|
+
referenceType: field.referenceType,
|
|
783
|
+
options: field.options || [],
|
|
784
|
+
defaultValues: field.defaultValues || [],
|
|
785
|
+
lockFilter: field.lockFilter,
|
|
786
|
+
expressions: field.expressions
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function getDefaultWidget(type) {
|
|
790
|
+
const defaults = {
|
|
791
|
+
'string': 'input',
|
|
792
|
+
'number': 'input',
|
|
793
|
+
'integer': 'input',
|
|
794
|
+
'boolean': 'checkbox',
|
|
795
|
+
'date': 'datepicker',
|
|
796
|
+
'email': 'input',
|
|
797
|
+
'url': 'input',
|
|
798
|
+
'reference': 'select',
|
|
799
|
+
'object': 'input'
|
|
800
|
+
};
|
|
801
|
+
return defaults[type] || 'input';
|
|
802
|
+
}
|
|
803
|
+
//# sourceMappingURL=definition.js.map
|