@maxisoft/instantcms-mcp 1.2.3

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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/dist/__tests__/artifact-tool.test.js +196 -0
  4. package/dist/__tests__/db-tool.test.js +152 -0
  5. package/dist/__tests__/define-tool.test.js +106 -0
  6. package/dist/__tests__/find-tool.test.js +185 -0
  7. package/dist/__tests__/get-component-api.test.js +144 -0
  8. package/dist/__tests__/hardening.test.js +73 -0
  9. package/dist/__tests__/hooks-tool.test.js +173 -0
  10. package/dist/__tests__/mcp-integration-extra.test.js +166 -0
  11. package/dist/__tests__/mcp-integration.test.js +53 -0
  12. package/dist/__tests__/pagination.test.js +114 -0
  13. package/dist/__tests__/performance-baseline.test.js +56 -0
  14. package/dist/__tests__/project-patch.test.js +144 -0
  15. package/dist/__tests__/project-source.test.js +44 -0
  16. package/dist/__tests__/project-workflows-extra.test.js +165 -0
  17. package/dist/__tests__/project-workflows.test.js +51 -0
  18. package/dist/__tests__/scaffold-addon-roundtrip.test.js +287 -0
  19. package/dist/__tests__/serialization.test.js +170 -0
  20. package/dist/__tests__/source-knowledge-parsers.test.js +42 -0
  21. package/dist/__tests__/template-development.test.js +78 -0
  22. package/dist/__tests__/template-productivity.test.js +87 -0
  23. package/dist/__tests__/tools.test.js +2143 -0
  24. package/dist/data/components.js +2818 -0
  25. package/dist/data/controllers-map.js +7571 -0
  26. package/dist/data/core-api.js +9356 -0
  27. package/dist/data/database-schema.js +4555 -0
  28. package/dist/data/events-map.js +1339 -0
  29. package/dist/data/fields-map.js +3692 -0
  30. package/dist/data/hooks.js +2257 -0
  31. package/dist/data/js-api.js +123 -0
  32. package/dist/data/libs-api.js +264 -0
  33. package/dist/data/routes-map.js +203 -0
  34. package/dist/data/schemas-validation.js +255 -0
  35. package/dist/data/schemas.js +1404 -0
  36. package/dist/data/traits-map.js +1004 -0
  37. package/dist/data/version-profiles.js +41 -0
  38. package/dist/data/widgets-map.js +131 -0
  39. package/dist/data/wysiwyg-map.js +486 -0
  40. package/dist/generated/components-source.js +14748 -0
  41. package/dist/generated/hooks-source.js +3399 -0
  42. package/dist/generated/knowledge-meta.js +101 -0
  43. package/dist/index.js +13 -0
  44. package/dist/registry/database-tools.js +112 -0
  45. package/dist/registry/extension-tools.js +473 -0
  46. package/dist/registry/generator-tools.js +506 -0
  47. package/dist/registry/knowledge-tools.js +441 -0
  48. package/dist/registry/language-tools.js +99 -0
  49. package/dist/registry/meta-tools.js +152 -0
  50. package/dist/registry/project-tools.js +48 -0
  51. package/dist/registry/resources.js +98 -0
  52. package/dist/registry/source-tools.js +211 -0
  53. package/dist/registry/template-development-tools.js +79 -0
  54. package/dist/server.js +32 -0
  55. package/dist/tools/addon-tool.js +1437 -0
  56. package/dist/tools/admin-partial-tool.js +498 -0
  57. package/dist/tools/api-tool.js +294 -0
  58. package/dist/tools/artifact-tool.js +95 -0
  59. package/dist/tools/cache-tool.js +360 -0
  60. package/dist/tools/component-tool.js +415 -0
  61. package/dist/tools/controllers-tool.js +125 -0
  62. package/dist/tools/cron-tool.js +197 -0
  63. package/dist/tools/crud-tool.js +659 -0
  64. package/dist/tools/db-tool.js +198 -0
  65. package/dist/tools/email-tool.js +222 -0
  66. package/dist/tools/external-api-tool.js +596 -0
  67. package/dist/tools/filter-tool.js +341 -0
  68. package/dist/tools/form-tool.js +275 -0
  69. package/dist/tools/grid-tool.js +189 -0
  70. package/dist/tools/hooks-tool.js +104 -0
  71. package/dist/tools/import-export-tool.js +548 -0
  72. package/dist/tools/lang-tool.js +251 -0
  73. package/dist/tools/layout-override-tool.js +125 -0
  74. package/dist/tools/layout-tool.js +548 -0
  75. package/dist/tools/maria-tool.js +127 -0
  76. package/dist/tools/mariadb.js +201 -0
  77. package/dist/tools/migration-tool.js +329 -0
  78. package/dist/tools/oauth-tool.js +520 -0
  79. package/dist/tools/parser/components-parser.js +76 -0
  80. package/dist/tools/parser/controllers-parser.js +313 -0
  81. package/dist/tools/parser/core-parser.js +294 -0
  82. package/dist/tools/parser/coverage-generator.js +424 -0
  83. package/dist/tools/parser/events-parser.js +127 -0
  84. package/dist/tools/parser/fields-parser.js +382 -0
  85. package/dist/tools/parser/hooks-parser.js +106 -0
  86. package/dist/tools/parser/sql-parser.js +197 -0
  87. package/dist/tools/parser/traits-parser.js +161 -0
  88. package/dist/tools/parser/widgets-parser.js +150 -0
  89. package/dist/tools/permission-tool.js +348 -0
  90. package/dist/tools/project-patch-tool.js +73 -0
  91. package/dist/tools/project-source-tool.js +188 -0
  92. package/dist/tools/project-workflow-tool.js +186 -0
  93. package/dist/tools/requirement-tool.js +212 -0
  94. package/dist/tools/scaffold-tool.js +815 -0
  95. package/dist/tools/seo-tool.js +412 -0
  96. package/dist/tools/source-tool.js +155 -0
  97. package/dist/tools/template-development-tool.js +271 -0
  98. package/dist/tools/template-overrides-tool.js +294 -0
  99. package/dist/tools/template-productivity-tool.js +319 -0
  100. package/dist/tools/template-tool.js +665 -0
  101. package/dist/tools/test-tool.js +183 -0
  102. package/dist/tools/webhook-tool.js +427 -0
  103. package/dist/tools/widget-tool.js +331 -0
  104. package/dist/tools/wysiwyg-tool.js +137 -0
  105. package/dist/types/scaffold.js +68 -0
  106. package/dist/utils/define-tool.js +37 -0
  107. package/dist/utils/find-tool.js +97 -0
  108. package/dist/utils/mcp-result.js +18 -0
  109. package/dist/utils/pagination.js +27 -0
  110. package/dist/utils/serialization.js +27 -0
  111. package/package.json +94 -0
@@ -0,0 +1,331 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview Widget scaffolding tool for InstantCMS
4
+ * Generates widgets with options, templates, and configuration
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.widgetToolSchema = exports.WidgetFieldTypeEnum = void 0;
8
+ exports.scaffoldWidget = scaffoldWidget;
9
+ const zod_1 = require("zod");
10
+ const scaffold_1 = require("../types/scaffold");
11
+ /**
12
+ * Widget option field types
13
+ */
14
+ exports.WidgetFieldTypeEnum = zod_1.z.enum([
15
+ 'text',
16
+ 'number',
17
+ 'select',
18
+ 'checkbox',
19
+ 'textarea',
20
+ 'image',
21
+ ]);
22
+ /**
23
+ * Generates widget class
24
+ */
25
+ function generateWidgetClass(name, widget, Widget, options, options_config) {
26
+ const optionDefaults = options
27
+ .map(o => {
28
+ const defaultVal = typeof o.default === 'string' ? `'${o.default}'` : (o.default ?? 'null');
29
+ return ` '${o.name}' => ${defaultVal},`;
30
+ })
31
+ .join('\n');
32
+ return `<?php
33
+ // InstantCMS 2. ${name}/widgets/${widget}.php
34
+
35
+ class ${Widget}Widget extends cmsWidget {
36
+
37
+ public $is_cacheable = ${options_config.with_cache};
38
+
39
+ public function run() {
40
+ $options = $this->getOptions([
41
+ ${optionDefaults}
42
+ ]);
43
+
44
+ $data = [
45
+ 'widget' => $this,
46
+ 'options' => $options,
47
+ 'title' => $options['title'],
48
+ ];
49
+
50
+ return $this->renderTemplate('${widget}', $data);
51
+ }
52
+
53
+ public function getCacheKey() {
54
+ return [
55
+ cmsConfig::get('cur_lang'),
56
+ $this->getOption('widget_id'),
57
+ $this->getOption('page'),
58
+ ];
59
+ }
60
+
61
+ public function validateOptions($options) {
62
+ return $options;
63
+ }
64
+
65
+ public function getSizeOptions() {
66
+ return [
67
+ 'full' => 'Во всю ширину',
68
+ 'half' => 'Половина ширины',
69
+ 'third' => 'Треть ширины',
70
+ ];
71
+ }
72
+ }`;
73
+ }
74
+ /**
75
+ * Generates widget options class
76
+ */
77
+ function generateWidgetOptions(widget, Widget, options) {
78
+ const fieldsCode = options
79
+ .map(o => {
80
+ switch (o.type) {
81
+ case 'text':
82
+ return ` $form->addField('${o.name}', new fieldString('${o.label}'));`;
83
+ case 'number':
84
+ return ` $form->addField('${o.name}', new fieldNumber('${o.label}'));`;
85
+ case 'select': {
86
+ const optsStr = (o.options || [])
87
+ .map(opt => `'${opt.value}' => '${opt.label}'`)
88
+ .join(', ');
89
+ return ` $form->addField('${o.name}', new fieldList('${o.label}', ['options' => [${optsStr}]]));`;
90
+ }
91
+ case 'checkbox':
92
+ return ` $form->addField('${o.name}', new fieldCheckbox('${o.label}'));`;
93
+ case 'textarea':
94
+ return ` $form->addField('${o.name}', new fieldText('${o.label}'));`;
95
+ case 'image':
96
+ return ` $form->addField('${o.name}', new fieldImage('${o.label}'));`;
97
+ default:
98
+ return ` $form->addField('${o.name}', new fieldString('${o.label}'));`;
99
+ }
100
+ })
101
+ .join('\n');
102
+ return `<?php
103
+ // InstantCMS 2. widgets/${widget}.options.php
104
+
105
+ class ${Widget}WidgetOptions {
106
+
107
+ public static function getConfigForm($form) {
108
+ ${fieldsCode}
109
+
110
+ return $form;
111
+ }
112
+
113
+ public static function getDefaultOptions() {
114
+ return [
115
+ ${options
116
+ .map(o => {
117
+ const defaultVal = typeof o.default === 'string' ? `'${o.default}'` : (o.default ?? 'null');
118
+ return ` '${o.name}' => ${defaultVal},`;
119
+ })
120
+ .join('\n')}
121
+ ];
122
+ }
123
+
124
+ public static function getOptionLabels() {
125
+ return [
126
+ ${options.map(o => ` '${o.name}' => '${o.label}',`).join('\n')}
127
+ ];
128
+ }
129
+ }`;
130
+ }
131
+ /**
132
+ * Generates widget template
133
+ */
134
+ function generateWidgetTemplate(widget, _Widget, _options) {
135
+ return `<?php
136
+ // InstantCMS 2. widgets/${widget}.html.php
137
+
138
+ \\$widget = \\$data['widget'];
139
+ \\$options = \\$data['options'];
140
+ \\$title = \\$options['title'] ?? '';
141
+ \\$limit = \\$options['limit'] ?? 5;
142
+ ?>
143
+
144
+ <?php if (\\$title): ?>
145
+ <h3 class="widget-title"><?php echo \\$title; ?></h3>
146
+ <?php endif; ?>
147
+
148
+ <div class="${widget}-widget">
149
+ <div class="widget-content">
150
+ <!-- Widget content here -->
151
+ </div>
152
+ </div>
153
+
154
+ <style>
155
+ .widget-${widget} {
156
+ padding: 0;
157
+ }
158
+ .widget-${widget} .widget-title {
159
+ margin: 0 0 15px 0;
160
+ font-size: 18px;
161
+ }
162
+ .widget-${widget} .widget-content {
163
+ padding: 10px 0;
164
+ }
165
+ </style>
166
+ `;
167
+ }
168
+ /**
169
+ * Generates widget CSS styles
170
+ */
171
+ function generateWidgetStyles(widget, _options) {
172
+ return `/* InstantCMS 2. ${widget} widget styles */
173
+
174
+ .${widget}-widget {
175
+ padding: 0;
176
+ }
177
+
178
+ .${widget}-widget .widget-title {
179
+ margin: 0 0 15px 0;
180
+ font-size: 18px;
181
+ font-weight: 600;
182
+ }
183
+
184
+ .${widget}-widget .widget-content {
185
+ padding: 10px 0;
186
+ }
187
+
188
+ .${widget}-widget .item {
189
+ padding: 8px 0;
190
+ border-bottom: 1px solid #eee;
191
+ }
192
+
193
+ .${widget}-widget .item:last-child {
194
+ border-bottom: none;
195
+ }
196
+
197
+ .${widget}-widget .item-title {
198
+ font-weight: 500;
199
+ }
200
+
201
+ .${widget}-widget .item-date {
202
+ font-size: 12px;
203
+ color: #999;
204
+ }
205
+ `;
206
+ }
207
+ /**
208
+ * Generates widget configuration
209
+ */
210
+ function generateWidgetConfig(name, widget, Widget, options) {
211
+ return `<?php
212
+ // InstantCMS 2. system/config/widgets/${widget}.php
213
+
214
+ return [
215
+ 'widget' => '${widget}',
216
+ 'component' => '${name}',
217
+ 'class' => '${Widget}Widget',
218
+ 'options_class' => '${Widget}WidgetOptions',
219
+ 'title' => '${Widget}',
220
+ 'description' => '',
221
+ 'version' => '1.0.0',
222
+ 'author' => '',
223
+ 'options' => [
224
+ ${options.map(o => ` '${o.name}' => '${o.label}',`).join('\n')}
225
+ ],
226
+ 'size_options' => [
227
+ 'full',
228
+ 'half',
229
+ 'third',
230
+ ],
231
+ ];`;
232
+ }
233
+ /**
234
+ * Generates a complete widget for InstantCMS
235
+ *
236
+ * @param opts - Configuration options for the widget
237
+ * @returns Object containing generated files and metadata
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * const result = scaffoldWidget({
242
+ * addon_name: 'blog',
243
+ * widget_name: 'recent_posts',
244
+ * options: [
245
+ * { name: 'title', type: 'text', label: 'Заголовок', default: 'Последние записи' },
246
+ * { name: 'limit', type: 'number', label: 'Количество', default: 5 }
247
+ * ]
248
+ * });
249
+ * ```
250
+ */
251
+ function scaffoldWidget(opts) {
252
+ const { lowercase } = (0, scaffold_1.normalizeAddonName)(opts.addon_name);
253
+ const widget = opts.widget_name.toLowerCase().replace(/[^a-z0-9_]/g, '_');
254
+ const Widget = widget
255
+ .split('_')
256
+ .map(s => s.charAt(0).toUpperCase() + s.slice(1))
257
+ .join('');
258
+ const files = {};
259
+ const options_config = {
260
+ with_template: opts.options_config?.with_template ?? true,
261
+ with_styles: opts.options_config?.with_styles ?? true,
262
+ with_cache: opts.options_config?.with_cache ?? true,
263
+ };
264
+ const options = opts.options || [
265
+ { name: 'title', type: 'text', label: 'Заголовок', default: '' },
266
+ { name: 'limit', type: 'number', label: 'Количество', default: 5 },
267
+ ];
268
+ files[`${lowercase}/widgets/${widget}.php`] = generateWidgetClass(lowercase, widget, Widget, options, options_config);
269
+ files[`${lowercase}/widgets/${widget}.options.php`] = generateWidgetOptions(widget, Widget, options);
270
+ files[`${lowercase}/widgets/${widget}.html.php`] = generateWidgetTemplate(widget, Widget, options);
271
+ if (options_config.with_styles) {
272
+ files[`${lowercase}/widgets/${widget}.css`] = generateWidgetStyles(widget, options);
273
+ }
274
+ files[`system/config/widgets/${widget}.php`] = generateWidgetConfig(lowercase, widget, Widget, options);
275
+ return {
276
+ addon_name: lowercase,
277
+ files,
278
+ widget_name: widget,
279
+ options_count: options.length,
280
+ options_config,
281
+ };
282
+ }
283
+ exports.widgetToolSchema = {
284
+ name: 'scaffold_widget',
285
+ description: 'Генерация виджета InstantCMS с настройками и шаблонами',
286
+ inputSchema: {
287
+ type: 'object',
288
+ properties: {
289
+ addon_name: { type: 'string', description: 'Имя компонента' },
290
+ widget_name: { type: 'string', description: 'Имя виджета' },
291
+ options: zod_1.z
292
+ .array(zod_1.z.object({
293
+ name: zod_1.z.string().describe('Имя опции'),
294
+ type: zod_1.z
295
+ .enum(['text', 'number', 'select', 'checkbox', 'textarea', 'image'])
296
+ .describe('Тип поля'),
297
+ label: zod_1.z.string().describe('Название'),
298
+ options: zod_1.z
299
+ .array(zod_1.z.object({ value: zod_1.z.string(), label: zod_1.z.string() }))
300
+ .optional()
301
+ .describe('Опции для select'),
302
+ default: zod_1.z
303
+ .union([zod_1.z.string(), zod_1.z.boolean(), zod_1.z.number()])
304
+ .optional()
305
+ .describe('Значение по умолчанию'),
306
+ }))
307
+ .optional()
308
+ .describe('Опции виджета'),
309
+ options_config: zod_1.z
310
+ .object({
311
+ with_template: zod_1.z.boolean().optional().describe('С шаблоном'),
312
+ with_styles: zod_1.z.boolean().optional().describe('Со стилями'),
313
+ with_cache: zod_1.z.boolean().optional().describe('С кэшированием'),
314
+ })
315
+ .optional()
316
+ .describe('Конфигурация'),
317
+ },
318
+ required: ['addon_name', 'widget_name'],
319
+ },
320
+ inputExamples: [
321
+ {
322
+ addon_name: 'blog',
323
+ widget_name: 'recent_posts',
324
+ options: [
325
+ { name: 'title', type: 'text', label: 'Заголовок', default: 'Последние записи' },
326
+ { name: 'limit', type: 'number', label: 'Количество', default: 5 },
327
+ { name: 'show_date', type: 'checkbox', label: 'Показывать дату', default: true },
328
+ ],
329
+ },
330
+ ],
331
+ };
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listWysiwygEditors = listWysiwygEditors;
4
+ exports.getWysiwygEditor = getWysiwygEditor;
5
+ exports.getWysiwygOptions = getWysiwygOptions;
6
+ exports.getWysiwygPlugins = getWysiwygPlugins;
7
+ exports.searchWysiwygEditors = searchWysiwygEditors;
8
+ exports.getWysiwygButtons = getWysiwygButtons;
9
+ const wysiwyg_map_js_1 = require("../data/wysiwyg-map.js");
10
+ function listWysiwygEditors() {
11
+ return {
12
+ total: wysiwyg_map_js_1.wysiwygMap.editorCount,
13
+ editors: wysiwyg_map_js_1.wysiwygMap.editors.map(e => ({
14
+ name: e.name,
15
+ className: e.className,
16
+ description: e.description,
17
+ features: e.features,
18
+ plugins_count: e.plugins?.length ?? 0,
19
+ })),
20
+ };
21
+ }
22
+ function getWysiwygEditor(editorName) {
23
+ const editor = wysiwyg_map_js_1.wysiwygMap.byName[editorName.toLowerCase()];
24
+ if (!editor) {
25
+ const similar = Object.keys(wysiwyg_map_js_1.wysiwygMap.byName).filter(name => name.includes(editorName.toLowerCase()));
26
+ return {
27
+ error: `Редактор "${editorName}" не найден`,
28
+ available: Object.keys(wysiwyg_map_js_1.wysiwygMap.byName),
29
+ similar: similar.length > 0 ? similar : undefined,
30
+ };
31
+ }
32
+ return {
33
+ name: editor.name,
34
+ className: editor.className,
35
+ filePath: editor.filePath,
36
+ optionsFormPath: editor.optionsFormPath,
37
+ description: editor.description,
38
+ features: editor.features,
39
+ options: editor.options,
40
+ options_descriptions: editor.optionsDescriptions,
41
+ plugins: editor.plugins,
42
+ buttons: editor.buttons,
43
+ usage: {
44
+ instantiation: `cmsWysiwyg::getEditor('${editor.name}', $config);`,
45
+ display: `$editor = cmsWysiwyg::getEditor('${editor.name}');\n$editor->displayEditor('content', $content);`,
46
+ options_form: editor.optionsFormPath,
47
+ },
48
+ example_config: buildExampleConfig(editor),
49
+ };
50
+ }
51
+ function getWysiwygOptions(editorName) {
52
+ const editor = wysiwyg_map_js_1.wysiwygMap.byName[editorName.toLowerCase()];
53
+ if (!editor) {
54
+ return { error: `Редактор "${editorName}" не найден` };
55
+ }
56
+ return {
57
+ editor: editor.name,
58
+ options: Object.entries(editor.optionsDescriptions).map(([key, opt]) => ({
59
+ name: key,
60
+ type: opt.type,
61
+ description: opt.description,
62
+ default: opt.default,
63
+ current: editor.options[key],
64
+ })),
65
+ };
66
+ }
67
+ function getWysiwygPlugins(editorName) {
68
+ const editor = wysiwyg_map_js_1.wysiwygMap.byName[editorName.toLowerCase()];
69
+ if (!editor) {
70
+ return { error: `Редактор "${editorName}" не найден` };
71
+ }
72
+ if (!editor.plugins) {
73
+ return {
74
+ editor: editor.name,
75
+ plugins: [],
76
+ note: `${editor.name} не поддерживает плагины`,
77
+ };
78
+ }
79
+ return {
80
+ editor: editor.name,
81
+ plugins: editor.plugins,
82
+ };
83
+ }
84
+ function searchWysiwygEditors(query) {
85
+ const lower = query.toLowerCase();
86
+ const results = wysiwyg_map_js_1.wysiwygMap.editors.filter(e => e.name.includes(lower) ||
87
+ e.description.toLowerCase().includes(lower) ||
88
+ e.features.some(f => f.toLowerCase().includes(lower)) ||
89
+ e.plugins?.some(p => p.name.includes(lower) || p.description.toLowerCase().includes(lower)));
90
+ return {
91
+ query,
92
+ total: results.length,
93
+ results: results.map(e => ({
94
+ name: e.name,
95
+ description: e.description,
96
+ matching_features: e.features.filter(f => f.toLowerCase().includes(lower)),
97
+ matching_plugins: e.plugins?.filter(p => p.name.includes(lower) || p.description.toLowerCase().includes(lower)) ?? [],
98
+ })),
99
+ };
100
+ }
101
+ function getWysiwygButtons(editorName) {
102
+ const editor = wysiwyg_map_js_1.wysiwygMap.byName[editorName.toLowerCase()];
103
+ if (!editor) {
104
+ return { error: `Редактор "${editorName}" не найден` };
105
+ }
106
+ if (editorName === 'markitup' && editor.buttons) {
107
+ return {
108
+ editor: editor.name,
109
+ type: 'markitup_buttons',
110
+ buttons: editor.buttons.map((btn, index) => ({
111
+ id: index,
112
+ ...(typeof btn === 'string' ? { name: btn } : btn),
113
+ })),
114
+ };
115
+ }
116
+ if (editor.buttons && editor.buttons.every(b => typeof b === 'string')) {
117
+ return {
118
+ editor: editor.name,
119
+ type: 'toolbar_buttons',
120
+ buttons: editor.buttons,
121
+ };
122
+ }
123
+ return {
124
+ editor: editor.name,
125
+ buttons: editor.buttons ?? [],
126
+ note: `${editor.name} не имеет списка кнопок`,
127
+ };
128
+ }
129
+ function buildExampleConfig(editor) {
130
+ const example = {};
131
+ Object.entries(editor.optionsDescriptions).forEach(([key, opt]) => {
132
+ if (opt.default !== undefined) {
133
+ example[key] = opt.default;
134
+ }
135
+ });
136
+ return example;
137
+ }
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createError = createError;
4
+ exports.createSuccess = createSuccess;
5
+ exports.validateAddonName = validateAddonName;
6
+ exports.normalizeAddonName = normalizeAddonName;
7
+ exports.sanitizePath = sanitizePath;
8
+ exports.getCurrentDate = getCurrentDate;
9
+ /**
10
+ * Creates a standardized error response
11
+ */
12
+ function createError(code, message, details) {
13
+ return {
14
+ success: false,
15
+ error: { code, message, details },
16
+ };
17
+ }
18
+ /**
19
+ * Creates a standardized success response
20
+ */
21
+ function createSuccess(data) {
22
+ return {
23
+ success: true,
24
+ data,
25
+ };
26
+ }
27
+ /**
28
+ * Validates addon name format
29
+ */
30
+ function validateAddonName(name) {
31
+ if (!name) {
32
+ return { valid: false, error: 'Addon name is required' };
33
+ }
34
+ if (!/^[a-z][a-z0-9_]*$/.test(name)) {
35
+ return {
36
+ valid: false,
37
+ error: 'Addon name must start with lowercase letter and contain only lowercase letters, numbers, and underscores',
38
+ };
39
+ }
40
+ if (name.length > 50) {
41
+ return { valid: false, error: 'Addon name must be 50 characters or less' };
42
+ }
43
+ return { valid: true };
44
+ }
45
+ /**
46
+ * Converts addon name to various case formats
47
+ */
48
+ function normalizeAddonName(name) {
49
+ const lowercase = name.toLowerCase().replace(/[^a-z0-9_]/g, '_');
50
+ const UpperCamelCase = lowercase
51
+ .split('_')
52
+ .map(s => s.charAt(0).toUpperCase() + s.slice(1))
53
+ .join('');
54
+ const lowercaseSnake = lowercase.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
55
+ return { lowercase, UpperCamelCase, lowercaseSnake };
56
+ }
57
+ /**
58
+ * Sanitizes a string for use in file paths
59
+ */
60
+ function sanitizePath(str) {
61
+ return str.replace(/[^a-zA-Z0-9_\/-]/g, '_').replace(/__+/g, '_');
62
+ }
63
+ /**
64
+ * Generates a timestamp for use in version strings
65
+ */
66
+ function getCurrentDate() {
67
+ return new Date().toISOString().split('T')[0];
68
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineTool = defineTool;
4
+ exports.defineToolWithManualResult = defineToolWithManualResult;
5
+ const mcp_result_js_1 = require("./mcp-result.js");
6
+ /**
7
+ * Регистрирует MCP-инструмент и нормализует контракт ответа.
8
+ *
9
+ * Любое исключение из handler превращается в errorResult (isError: true,
10
+ * code: TOOL_EXECUTION_ERROR), не пробрасывается наружу. Это сохраняет
11
+ * инвариант MCP: каждый tools/call возвращает корректный результат.
12
+ *
13
+ * Schema намеренно типизирована как `unknown`, чтобы не воевать с zod v4 ↔
14
+ * MCP SDK generic-конфликтами; контракт реэкспортируется из sdk в site-вызова.
15
+ */
16
+ function defineTool(server, name, description, schema, handler) {
17
+ server.tool(name, description, schema, async (rawArgs) => {
18
+ try {
19
+ const data = await handler((rawArgs ?? {}));
20
+ return (0, mcp_result_js_1.successResult)(data);
21
+ }
22
+ catch (err) {
23
+ const message = err instanceof Error ? err.message : String(err);
24
+ return (0, mcp_result_js_1.errorResult)('TOOL_EXECUTION_ERROR', message, {
25
+ tool: name,
26
+ cause: err instanceof Error ? err.stack : undefined,
27
+ });
28
+ }
29
+ });
30
+ }
31
+ /**
32
+ * Вариант, когда handler сам решает вернуть success- или error-result
33
+ * (например, для ожидаемых отказов вроде "unknown lookup").
34
+ */
35
+ function defineToolWithManualResult(server, name, description, schema, handler) {
36
+ server.tool(name, description, schema, handler);
37
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Matching логика для find_tool: token-based matching с ранжированием.
4
+ *
5
+ * Приоритет:
6
+ * 1. exact match ключевого слова (category.keywords.includes(query.normalized))
7
+ * 2. prefix match (keyword.startsWith(queryToken) || queryToken.startsWith(keyword))
8
+ * 3. substring match (keyword.includes(queryToken) || queryToken.includes(keyword))
9
+ *
10
+ * Запрос токенизируется по Unicode-словным границам; multi-word запросы
11
+ * матчатся, если ВСЕ токены попадают в keywords одной категории.
12
+ * Возвращает категории, отсортированные по score desc, плюс дополнительные
13
+ * метрики.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.rankToolCategories = rankToolCategories;
17
+ exports.findToolCategories = findToolCategories;
18
+ const MAX_TOKEN_LENGTH = 64;
19
+ function normalize(s) {
20
+ return s.toLowerCase().trim();
21
+ }
22
+ function tokenize(s) {
23
+ const norm = normalize(s);
24
+ // Unicode-словные границы, фильтр пустых и длинных токенов.
25
+ return norm
26
+ .split(/[\s,.!?;:"'()[\]{}<>|/\\\-_+=]+/u)
27
+ .filter(t => t.length > 0 && t.length <= MAX_TOKEN_LENGTH);
28
+ }
29
+ function scoreCategory(queryTokens, category) {
30
+ if (queryTokens.length === 0 || category.keywords.length === 0) {
31
+ return { score: 0, matched: [] };
32
+ }
33
+ const keywords = category.keywords.map(normalize);
34
+ const matched = [];
35
+ let totalScore = 0;
36
+ for (const token of queryTokens) {
37
+ let bestScore = 0;
38
+ let matchedKeyword = null;
39
+ for (const keyword of keywords) {
40
+ let s = 0;
41
+ if (keyword === token) {
42
+ s = 100;
43
+ }
44
+ else if (keyword.startsWith(token) || token.startsWith(keyword)) {
45
+ s = 60;
46
+ }
47
+ else if (keyword.includes(token) || token.includes(keyword)) {
48
+ s = 30;
49
+ }
50
+ if (s > bestScore) {
51
+ bestScore = s;
52
+ matchedKeyword = keyword;
53
+ }
54
+ }
55
+ if (bestScore > 0) {
56
+ totalScore += bestScore;
57
+ if (matchedKeyword)
58
+ matched.push(matchedKeyword);
59
+ }
60
+ }
61
+ // Multi-word: matched at least once per token.
62
+ if (matched.length < queryTokens.length) {
63
+ // Partial match: даём меньший score, чтобы категории с полным совпадением
64
+ // были выше.
65
+ totalScore = Math.floor(totalScore * 0.5);
66
+ }
67
+ return { score: totalScore, matched };
68
+ }
69
+ /**
70
+ * Ранжирует категории по релевантности запросу. Возвращает не более maxResults,
71
+ * отсортированных по score desc. Категории с score=0 НЕ включаются.
72
+ */
73
+ function rankToolCategories(query, categories, maxResults = 5) {
74
+ const tokens = tokenize(query);
75
+ if (tokens.length === 0)
76
+ return [];
77
+ return categories
78
+ .map(category => {
79
+ const { score, matched } = scoreCategory(tokens, category);
80
+ return { category: category.category, tools: category.tools, score, matchedTokens: matched };
81
+ })
82
+ .filter(result => result.score > 0)
83
+ .sort((a, b) => b.score - a.score)
84
+ .slice(0, maxResults);
85
+ }
86
+ /**
87
+ * Удобная обёртка: вернуть список объектов ToolCategory в исходном формате,
88
+ * отсортированных по релевантности. Пустой результат → полный каталог.
89
+ */
90
+ function findToolCategories(query, categories, fallbackAll = true) {
91
+ const ranked = rankToolCategories(query, categories);
92
+ const matches = categories.filter(c => ranked.some(r => r.category === c.category));
93
+ if (matches.length === 0 && fallbackAll) {
94
+ return { matches: categories, ranked: [] };
95
+ }
96
+ return { matches, ranked };
97
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.successResult = successResult;
4
+ exports.errorResult = errorResult;
5
+ function successResult(data, summary) {
6
+ return {
7
+ content: [{ type: 'text', text: summary ?? JSON.stringify(data, null, 2) }],
8
+ structuredContent: data,
9
+ };
10
+ }
11
+ function errorResult(code, message, details = {}) {
12
+ const data = { code, message, ...details };
13
+ return {
14
+ isError: true,
15
+ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
16
+ structuredContent: data,
17
+ };
18
+ }