@monoharada/wcf-mcp 0.9.1 → 0.10.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/README.md +23 -3
- package/core/constants.mjs +1 -1
- package/core/plugins.mjs +184 -3
- package/core/register.mjs +1007 -145
- package/core/tokens.mjs +70 -17
- package/core.mjs +13 -0
- package/data/design-tokens.json +932 -2
- package/data/guidelines-index.json +88 -24
- package/examples/plugins/custom-validation-plugin.mjs +61 -0
- package/package.json +1 -1
- package/runtime-data.mjs +41 -5
- package/server.mjs +5 -0
- package/validator.mjs +209 -31
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
|
|
|
78
78
|
}
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
## 提供機能(
|
|
81
|
+
## 提供機能(19 tools + 1 prompt + 5 resources)
|
|
82
82
|
|
|
83
83
|
### ガードレール
|
|
84
84
|
|
|
@@ -101,6 +101,11 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
|
|
|
101
101
|
| ツール | 説明 |
|
|
102
102
|
|--------|------|
|
|
103
103
|
| `validate_markup` | HTML スニペットを検証し、セマンティック検証(下表)で `suggestion` 付き診断を返す |
|
|
104
|
+
| `validate_files` | 複数のマークアップファイルをまとめて検証し、ファイル別診断と集計を返す |
|
|
105
|
+
| `validate_project` | ディレクトリを走査し、include/exclude glob に一致する複数ファイルをまとめて検証する |
|
|
106
|
+
|
|
107
|
+
`validate_*` 系は common template syntax(`{{ }}`, `{% %}`, `<% %>`, `<? ?>`, script/style blocks)をそのまま HTML と誤認しないようにマスクして検証します。
|
|
108
|
+
`validate_project` の既定 include は `*.html`, `*.htm`, `*.njk`, `*.liquid`, `*.astro`, `*.twig`, `*.hbs` です。
|
|
104
109
|
|
|
105
110
|
#### validate_markup 検出コード一覧
|
|
106
111
|
|
|
@@ -119,6 +124,7 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
|
|
|
119
124
|
| `roleAlertNotRecommended` | warning | `role="alert"` の使用(DADS 非推奨) | `role="alert"` |
|
|
120
125
|
| `emptyLabel` | error | 空の `label` 属性(アクセシビリティ違反) | `<dads-input-text label="">` |
|
|
121
126
|
| `emptyAriaLabel` | error | 空の `aria-label` 属性(アクセシビリティ違反) | `<dads-button aria-label="">` |
|
|
127
|
+
| `duplicateId` | error | 同一ドキュメント内で `id` が重複している | `<div id="hero">...</div><section id="hero">...</section>` |
|
|
122
128
|
| `forbiddenAttribute` | warning | 禁止属性 | `placeholder` |
|
|
123
129
|
|
|
124
130
|
### UI パターン
|
|
@@ -140,10 +146,11 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
|
|
|
140
146
|
|
|
141
147
|
| ツール | 説明 |
|
|
142
148
|
|--------|------|
|
|
143
|
-
| `get_design_tokens` | デザイントークンを type/category/query/theme
|
|
149
|
+
| `get_design_tokens` | デザイントークンを type/category/query/theme で検索(現状データは `light` のみ。`all` は利用可能テーマ全体、`dark` は非サポート) |
|
|
144
150
|
| `get_design_token_detail` | 単一トークンの詳細(references/referencedBy/relatedTokens/usageExamples)を取得 |
|
|
145
151
|
| `get_accessibility_docs` | component/topic/wcagLevel で A11y チェックリストとガイドライン要点を検索(`topic=all` では両ソースを混在返却) |
|
|
146
152
|
| `search_guidelines` | ガイドライン(topic/query)をスコア付きで検索 |
|
|
153
|
+
| `search_design_system_knowledge` | components/patterns/guidelines/tokens/skills を横断して検索し、exact/prefix/intent-aware ranking と source ごとの follow-up 導線を返す |
|
|
147
154
|
|
|
148
155
|
#### `get_design_tokens` の使用例
|
|
149
156
|
|
|
@@ -226,7 +233,8 @@ claude mcp add wcf -- npx @monoharada/wcf-mcp
|
|
|
226
233
|
}
|
|
227
234
|
```
|
|
228
235
|
|
|
229
|
-
> **注意**:
|
|
236
|
+
> **注意**: 現在の実データは `light` のみです。`theme="all"` は利用可能テーマ全体として `light` を返します。`theme="dark"` は `INVALID_THEME` エラーです。
|
|
237
|
+
> `var(--token, #fff)` のような literal fallback は relationship graph には含めません。`var(--token-a, var(--token-b))` のような token fallback のみ参照関係として扱います。
|
|
230
238
|
|
|
231
239
|
### Prompt
|
|
232
240
|
|
|
@@ -336,13 +344,20 @@ npx @monoharada/wcf-mcp --transport=http --port=3100
|
|
|
336
344
|
詳細仕様: [docs/plugin-contract-v1.md](../../docs/plugin-contract-v1.md)
|
|
337
345
|
|
|
338
346
|
- `plugins[].name` / `plugins[].version` は必須
|
|
347
|
+
- `plugins[].validators` を使うと `validate_markup` / `validate_files` / `validate_project` に独自診断を差し込めます
|
|
348
|
+
- `plugins[].prompts` / `plugins[].resources` を使うと MCP prompt / resource を追加できます
|
|
349
|
+
- `plugins[].resourceTemplates` を使うと MCP resource template も追加できます
|
|
339
350
|
- tool 名は組み込みツール名と重複不可(例: `list_components` など)
|
|
351
|
+
- prompt の `argsSchema` は plain shape に加えて zod object shape も使えます
|
|
340
352
|
- `dataSources` で差し替え可能な key は次のみ
|
|
341
353
|
- `custom-elements.json`
|
|
342
354
|
- `install-registry.json`
|
|
343
355
|
- `pattern-registry.json`
|
|
356
|
+
- `component-selector-guide.json`
|
|
344
357
|
- `design-tokens.json`
|
|
345
358
|
- `guidelines-index.json`
|
|
359
|
+
- `skills-registry.json`
|
|
360
|
+
- `llms-full.txt`
|
|
346
361
|
|
|
347
362
|
## structuredContent rollback
|
|
348
363
|
|
|
@@ -636,8 +651,13 @@ CEM やレジストリを更新した後:
|
|
|
636
651
|
```bash
|
|
637
652
|
npm run mcp:build # データファイルをパッケージにコピー
|
|
638
653
|
npm run mcp:check # データが最新かチェック(CI用)
|
|
654
|
+
npm run mcp:summary # MCP inventory + quality summary(JSON)を生成
|
|
655
|
+
npm run mcp:summary:check # summary JSON の drift をチェック
|
|
639
656
|
```
|
|
640
657
|
|
|
658
|
+
- machine-readable summary: `packages/mcp-server/mcp-spec-test/summary/v3-final.json`
|
|
659
|
+
- `npm run mcp:check:response-size` は human-readable stdout、`node scripts/mcp/check-response-size.mjs --json` は JSON 出力に使えます
|
|
660
|
+
|
|
641
661
|
### パッケージ構成
|
|
642
662
|
|
|
643
663
|
```
|
package/core/constants.mjs
CHANGED
|
@@ -13,7 +13,7 @@ export const CANONICAL_PREFIX = 'dads';
|
|
|
13
13
|
export const MAX_PREFIX_LENGTH = 64;
|
|
14
14
|
export const STRUCTURED_CONTENT_DISABLE_FLAG = 'WCF_MCP_DISABLE_STRUCTURED_CONTENT';
|
|
15
15
|
export const MAX_TOOL_RESULT_BYTES = 100 * 1024;
|
|
16
|
-
export const PLUGIN_TOOL_NOTICE = 'Plugin tool (contract v1.
|
|
16
|
+
export const PLUGIN_TOOL_NOTICE = 'Plugin tool (contract v1.4).';
|
|
17
17
|
export const PACKAGE_VERSION = String(packageMeta?.version ?? '0.0.0');
|
|
18
18
|
|
|
19
19
|
export const CATEGORY_MAP = {
|
package/core/plugins.mjs
CHANGED
|
@@ -5,17 +5,20 @@
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { PLUGIN_TOOL_NOTICE } from './constants.mjs';
|
|
7
7
|
|
|
8
|
-
export const PLUGIN_CONTRACT_VERSION = '1.
|
|
8
|
+
export const PLUGIN_CONTRACT_VERSION = '1.4.0';
|
|
9
9
|
|
|
10
10
|
// Single-module constants (DD-14)
|
|
11
11
|
const PLUGIN_DATA_SOURCE_KEYS = Object.freeze(new Set([
|
|
12
12
|
'custom-elements.json',
|
|
13
13
|
'install-registry.json',
|
|
14
14
|
'pattern-registry.json',
|
|
15
|
+
'component-selector-guide.json',
|
|
15
16
|
'design-tokens.json',
|
|
16
17
|
'guidelines-index.json',
|
|
18
|
+
'skills-registry.json',
|
|
19
|
+
'llms-full.txt',
|
|
17
20
|
]));
|
|
18
|
-
const BUILTIN_TOOL_NAMES = Object.freeze(new Set([
|
|
21
|
+
export const BUILTIN_TOOL_NAMES = Object.freeze(new Set([
|
|
19
22
|
'get_design_system_overview',
|
|
20
23
|
'list_components',
|
|
21
24
|
'search_icons',
|
|
@@ -23,6 +26,8 @@ const BUILTIN_TOOL_NAMES = Object.freeze(new Set([
|
|
|
23
26
|
'generate_usage_snippet',
|
|
24
27
|
'get_install_recipe',
|
|
25
28
|
'validate_markup',
|
|
29
|
+
'validate_files',
|
|
30
|
+
'validate_project',
|
|
26
31
|
'list_patterns',
|
|
27
32
|
'get_pattern_recipe',
|
|
28
33
|
'generate_pattern_snippet',
|
|
@@ -30,9 +35,23 @@ const BUILTIN_TOOL_NAMES = Object.freeze(new Set([
|
|
|
30
35
|
'get_design_token_detail',
|
|
31
36
|
'get_accessibility_docs',
|
|
32
37
|
'search_guidelines',
|
|
38
|
+
'search_design_system_knowledge',
|
|
33
39
|
'generate_full_page_html',
|
|
34
40
|
'get_component_selector_guide',
|
|
35
41
|
]));
|
|
42
|
+
const BUILTIN_PROMPT_NAMES = Object.freeze(new Set([
|
|
43
|
+
'figma_to_wcf',
|
|
44
|
+
]));
|
|
45
|
+
const BUILTIN_RESOURCE_URIS = Object.freeze(new Set([
|
|
46
|
+
'wcf://components',
|
|
47
|
+
'wcf://tokens',
|
|
48
|
+
'wcf://guidelines/{topic}',
|
|
49
|
+
'wcf://llms-full',
|
|
50
|
+
'wcf://skills',
|
|
51
|
+
]));
|
|
52
|
+
const BUILTIN_RESOURCE_TEMPLATE_URIS = Object.freeze(new Set([
|
|
53
|
+
'wcf://guidelines/{topic}',
|
|
54
|
+
]));
|
|
36
55
|
|
|
37
56
|
function isPlainObject(value) {
|
|
38
57
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -109,11 +128,151 @@ function normalizePluginTools(pluginName, tools) {
|
|
|
109
128
|
return out;
|
|
110
129
|
}
|
|
111
130
|
|
|
131
|
+
function normalizePluginValidators(pluginName, validators) {
|
|
132
|
+
if (!Array.isArray(validators)) return [];
|
|
133
|
+
const out = [];
|
|
134
|
+
const seen = new Set();
|
|
135
|
+
for (const rawValidator of validators) {
|
|
136
|
+
if (!isPlainObject(rawValidator)) {
|
|
137
|
+
throw new Error(toPluginErrorMessage(pluginName, 'validators entries must be objects'));
|
|
138
|
+
}
|
|
139
|
+
const name = String(rawValidator.name ?? '').trim();
|
|
140
|
+
if (!name) throw new Error(toPluginErrorMessage(pluginName, 'validator.name is required'));
|
|
141
|
+
if (seen.has(name)) {
|
|
142
|
+
throw new Error(toPluginErrorMessage(pluginName, `duplicate validator name: ${name}`));
|
|
143
|
+
}
|
|
144
|
+
seen.add(name);
|
|
145
|
+
if (typeof rawValidator.handler !== 'function') {
|
|
146
|
+
throw new Error(toPluginErrorMessage(pluginName, `validator "${name}" needs handler`));
|
|
147
|
+
}
|
|
148
|
+
out.push({
|
|
149
|
+
name,
|
|
150
|
+
description: String(rawValidator.description ?? '').trim() || `Validator hook provided by ${pluginName}.`,
|
|
151
|
+
handler: rawValidator.handler,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function normalizePluginPrompts(pluginName, prompts) {
|
|
158
|
+
if (!Array.isArray(prompts)) return [];
|
|
159
|
+
const out = [];
|
|
160
|
+
const seen = new Set();
|
|
161
|
+
for (const rawPrompt of prompts) {
|
|
162
|
+
if (!isPlainObject(rawPrompt)) {
|
|
163
|
+
throw new Error(toPluginErrorMessage(pluginName, 'prompts entries must be objects'));
|
|
164
|
+
}
|
|
165
|
+
const name = String(rawPrompt.name ?? '').trim();
|
|
166
|
+
if (!name) throw new Error(toPluginErrorMessage(pluginName, 'prompt.name is required'));
|
|
167
|
+
if (seen.has(name)) {
|
|
168
|
+
throw new Error(toPluginErrorMessage(pluginName, `duplicate prompt name: ${name}`));
|
|
169
|
+
}
|
|
170
|
+
seen.add(name);
|
|
171
|
+
const hasHandler = typeof rawPrompt.handler === 'function';
|
|
172
|
+
const hasStaticText = typeof rawPrompt.text === 'string';
|
|
173
|
+
if (!hasHandler && !hasStaticText) {
|
|
174
|
+
throw new Error(toPluginErrorMessage(pluginName, `prompt "${name}" needs handler or text`));
|
|
175
|
+
}
|
|
176
|
+
out.push({
|
|
177
|
+
name,
|
|
178
|
+
title: String(rawPrompt.title ?? name).trim(),
|
|
179
|
+
description: String(rawPrompt.description ?? '').trim() || `Prompt provided by ${pluginName}.`,
|
|
180
|
+
argsSchema: isPlainObject(rawPrompt.argsSchema) ? rawPrompt.argsSchema : {},
|
|
181
|
+
handler: hasHandler ? rawPrompt.handler : undefined,
|
|
182
|
+
text: hasStaticText ? rawPrompt.text : undefined,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function normalizePluginResources(pluginName, resources) {
|
|
189
|
+
if (!Array.isArray(resources)) return [];
|
|
190
|
+
const out = [];
|
|
191
|
+
const seenNames = new Set();
|
|
192
|
+
const seenUris = new Set();
|
|
193
|
+
for (const rawResource of resources) {
|
|
194
|
+
if (!isPlainObject(rawResource)) {
|
|
195
|
+
throw new Error(toPluginErrorMessage(pluginName, 'resources entries must be objects'));
|
|
196
|
+
}
|
|
197
|
+
const name = String(rawResource.name ?? '').trim();
|
|
198
|
+
const uri = String(rawResource.uri ?? '').trim();
|
|
199
|
+
if (!name) throw new Error(toPluginErrorMessage(pluginName, 'resource.name is required'));
|
|
200
|
+
if (!uri) throw new Error(toPluginErrorMessage(pluginName, `resource "${name}" needs uri`));
|
|
201
|
+
if (seenNames.has(name)) {
|
|
202
|
+
throw new Error(toPluginErrorMessage(pluginName, `duplicate resource name: ${name}`));
|
|
203
|
+
}
|
|
204
|
+
if (seenUris.has(uri)) {
|
|
205
|
+
throw new Error(toPluginErrorMessage(pluginName, `duplicate resource uri: ${uri}`));
|
|
206
|
+
}
|
|
207
|
+
seenNames.add(name);
|
|
208
|
+
seenUris.add(uri);
|
|
209
|
+
const hasHandler = typeof rawResource.handler === 'function';
|
|
210
|
+
const hasText = typeof rawResource.text === 'string';
|
|
211
|
+
const hasPayload = Object.prototype.hasOwnProperty.call(rawResource, 'payload');
|
|
212
|
+
if (!hasHandler && !hasText && !hasPayload) {
|
|
213
|
+
throw new Error(toPluginErrorMessage(pluginName, `resource "${name}" needs handler, text, or payload`));
|
|
214
|
+
}
|
|
215
|
+
out.push({
|
|
216
|
+
name,
|
|
217
|
+
uri,
|
|
218
|
+
title: String(rawResource.title ?? name).trim(),
|
|
219
|
+
description: String(rawResource.description ?? '').trim() || `Resource provided by ${pluginName}.`,
|
|
220
|
+
mimeType: String(rawResource.mimeType ?? '').trim() || undefined,
|
|
221
|
+
handler: hasHandler ? rawResource.handler : undefined,
|
|
222
|
+
text: hasText ? rawResource.text : undefined,
|
|
223
|
+
payload: hasPayload ? rawResource.payload : undefined,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function normalizePluginResourceTemplates(pluginName, resourceTemplates) {
|
|
230
|
+
if (!Array.isArray(resourceTemplates)) return [];
|
|
231
|
+
const out = [];
|
|
232
|
+
const seenNames = new Set();
|
|
233
|
+
const seenTemplates = new Set();
|
|
234
|
+
for (const rawTemplate of resourceTemplates) {
|
|
235
|
+
if (!isPlainObject(rawTemplate)) {
|
|
236
|
+
throw new Error(toPluginErrorMessage(pluginName, 'resourceTemplates entries must be objects'));
|
|
237
|
+
}
|
|
238
|
+
const name = String(rawTemplate.name ?? '').trim();
|
|
239
|
+
const uriTemplate = String(rawTemplate.uriTemplate ?? '').trim();
|
|
240
|
+
if (!name) throw new Error(toPluginErrorMessage(pluginName, 'resourceTemplate.name is required'));
|
|
241
|
+
if (!uriTemplate) throw new Error(toPluginErrorMessage(pluginName, `resourceTemplate "${name}" needs uriTemplate`));
|
|
242
|
+
if (seenNames.has(name)) throw new Error(toPluginErrorMessage(pluginName, `duplicate resourceTemplate name: ${name}`));
|
|
243
|
+
if (seenTemplates.has(uriTemplate)) throw new Error(toPluginErrorMessage(pluginName, `duplicate resourceTemplate uriTemplate: ${uriTemplate}`));
|
|
244
|
+
seenNames.add(name);
|
|
245
|
+
seenTemplates.add(uriTemplate);
|
|
246
|
+
const hasHandler = typeof rawTemplate.handler === 'function';
|
|
247
|
+
const hasText = typeof rawTemplate.text === 'string';
|
|
248
|
+
const hasPayload = Object.prototype.hasOwnProperty.call(rawTemplate, 'payload');
|
|
249
|
+
if (!hasHandler && !hasText && !hasPayload) {
|
|
250
|
+
throw new Error(toPluginErrorMessage(pluginName, `resourceTemplate "${name}" needs handler, text, or payload`));
|
|
251
|
+
}
|
|
252
|
+
out.push({
|
|
253
|
+
name,
|
|
254
|
+
uriTemplate,
|
|
255
|
+
title: String(rawTemplate.title ?? name).trim(),
|
|
256
|
+
description: String(rawTemplate.description ?? '').trim() || `Resource template provided by ${pluginName}.`,
|
|
257
|
+
mimeType: String(rawTemplate.mimeType ?? '').trim() || undefined,
|
|
258
|
+
handler: hasHandler ? rawTemplate.handler : undefined,
|
|
259
|
+
text: hasText ? rawTemplate.text : undefined,
|
|
260
|
+
payload: hasPayload ? rawTemplate.payload : undefined,
|
|
261
|
+
list: Array.isArray(rawTemplate.list) ? rawTemplate.list : undefined,
|
|
262
|
+
complete: isPlainObject(rawTemplate.complete) ? rawTemplate.complete : undefined,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
|
|
112
268
|
export function normalizePlugins(plugins = []) {
|
|
113
269
|
if (!Array.isArray(plugins)) throw new Error('Invalid plugin configuration: plugins must be an array');
|
|
114
270
|
const normalized = [];
|
|
115
271
|
const seenPluginNames = new Set();
|
|
116
272
|
const seenToolNames = new Set(BUILTIN_TOOL_NAMES);
|
|
273
|
+
const seenPromptNames = new Set(BUILTIN_PROMPT_NAMES);
|
|
274
|
+
const seenResourceUris = new Set(BUILTIN_RESOURCE_URIS);
|
|
275
|
+
const seenResourceTemplateUris = new Set(BUILTIN_RESOURCE_TEMPLATE_URIS);
|
|
117
276
|
|
|
118
277
|
for (const rawPlugin of plugins) {
|
|
119
278
|
if (!isPlainObject(rawPlugin)) throw new Error('Invalid plugin configuration: each plugin must be an object');
|
|
@@ -124,15 +283,37 @@ export function normalizePlugins(plugins = []) {
|
|
|
124
283
|
seenPluginNames.add(name);
|
|
125
284
|
|
|
126
285
|
const tools = normalizePluginTools(name, rawPlugin.tools);
|
|
286
|
+
const validators = normalizePluginValidators(name, rawPlugin.validators);
|
|
287
|
+
const prompts = normalizePluginPrompts(name, rawPlugin.prompts);
|
|
288
|
+
const resources = normalizePluginResources(name, rawPlugin.resources);
|
|
289
|
+
const resourceTemplates = normalizePluginResourceTemplates(name, rawPlugin.resourceTemplates);
|
|
127
290
|
for (const tool of tools) {
|
|
128
291
|
if (seenToolNames.has(tool.name)) {
|
|
129
292
|
throw new Error(toPluginErrorMessage(name, `tool name collision: ${tool.name}`));
|
|
130
293
|
}
|
|
131
294
|
seenToolNames.add(tool.name);
|
|
132
295
|
}
|
|
296
|
+
for (const prompt of prompts) {
|
|
297
|
+
if (seenPromptNames.has(prompt.name)) {
|
|
298
|
+
throw new Error(toPluginErrorMessage(name, `prompt name collision: ${prompt.name}`));
|
|
299
|
+
}
|
|
300
|
+
seenPromptNames.add(prompt.name);
|
|
301
|
+
}
|
|
302
|
+
for (const resource of resources) {
|
|
303
|
+
if (seenResourceUris.has(resource.uri)) {
|
|
304
|
+
throw new Error(toPluginErrorMessage(name, `resource uri collision: ${resource.uri}`));
|
|
305
|
+
}
|
|
306
|
+
seenResourceUris.add(resource.uri);
|
|
307
|
+
}
|
|
308
|
+
for (const resourceTemplate of resourceTemplates) {
|
|
309
|
+
if (seenResourceTemplateUris.has(resourceTemplate.uriTemplate)) {
|
|
310
|
+
throw new Error(toPluginErrorMessage(name, `resourceTemplate uri collision: ${resourceTemplate.uriTemplate}`));
|
|
311
|
+
}
|
|
312
|
+
seenResourceTemplateUris.add(resourceTemplate.uriTemplate);
|
|
313
|
+
}
|
|
133
314
|
|
|
134
315
|
const dataSources = normalizePluginDataSources(name, rawPlugin.dataSources);
|
|
135
|
-
normalized.push({ name, version, tools, dataSources });
|
|
316
|
+
normalized.push({ name, version, tools, validators, prompts, resources, resourceTemplates, dataSources });
|
|
136
317
|
}
|
|
137
318
|
|
|
138
319
|
return normalized;
|