@aws/ml-container-creator 1.0.3 → 1.1.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 +10 -1
- package/bin/cli.js +57 -0
- package/config/agent.json +16 -0
- package/infra/ci-harness/lib/ci-harness-stack.ts +43 -0
- package/package.json +5 -2
- package/pyproject.toml +3 -0
- package/servers/agent-knowledge/index.js +592 -0
- package/servers/agent-knowledge/package.json +15 -0
- package/servers/base-image-picker/index.js +65 -18
- package/servers/instance-sizer/index.js +32 -0
- package/servers/lib/catalogs/fleet-drivers.json +38 -0
- package/servers/lib/catalogs/model-arch-support.json +51 -0
- package/servers/lib/catalogs/model-servers.json +2842 -1730
- package/servers/lib/schemas/image-catalog.schema.json +12 -0
- package/src/agent/__init__.py +2 -0
- package/src/agent/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/agent/__pycache__/config_loader.cpython-312.pyc +0 -0
- package/src/agent/__pycache__/context.cpython-312.pyc +0 -0
- package/src/agent/__pycache__/health_check.cpython-312.pyc +0 -0
- package/src/agent/agent.py +513 -0
- package/src/agent/config_loader.py +215 -0
- package/src/agent/context.py +380 -0
- package/src/agent/data/capability-matrix.json +106 -0
- package/src/agent/health_check.py +341 -0
- package/src/agent/prompts/system.md +173 -0
- package/src/agent/requirements-agent.txt +3 -0
- package/src/app.js +6 -4
- package/src/lib/generated/cli-options.js +1 -1
- package/src/lib/generated/parameter-matrix.js +1 -1
- package/src/lib/generated/validation-rules.js +1 -1
- package/src/lib/mcp-query-runner.js +110 -3
- package/src/lib/prompt-runner.js +66 -22
- package/src/lib/template-variable-resolver.js +8 -0
- package/src/lib/train-config-builder.js +339 -0
- package/src/lib/tune-config-state.js +89 -68
- package/templates/do/.benchmark_writer.py +3 -0
- package/templates/do/.eval_helper.py +409 -0
- package/templates/do/.register_helper.py +185 -11
- package/templates/do/.train_build_request.py +102 -113
- package/templates/do/.train_helper.py +433 -0
- package/templates/do/__pycache__/.register_helper.cpython-312.pyc +0 -0
- package/templates/do/adapter +157 -0
- package/templates/do/benchmark +60 -3
- package/templates/do/config +6 -1
- package/templates/do/deploy.d/managed-inference.ejs +83 -0
- package/templates/do/evaluate +272 -0
- package/templates/do/lib/resolve-instance.sh +155 -0
- package/templates/do/register +5 -0
- package/templates/do/test +1 -0
- package/templates/do/train +879 -126
- package/templates/do/training/config.yaml +83 -11
- package/templates/do/training/dpo/accelerate_config.yaml +24 -0
- package/templates/do/training/dpo/defaults.yaml +26 -0
- package/templates/do/training/dpo/prompts.json +8 -0
- package/templates/do/training/dpo/train.py +363 -0
- package/templates/do/training/sft/accelerate_config.yaml +22 -0
- package/templates/do/training/sft/defaults.yaml +18 -0
- package/templates/do/training/sft/prompts.json +7 -0
- package/templates/do/training/sft/train.py +310 -0
- package/templates/do/tune +11 -2
- package/src/lib/auto-prompt-builder.js +0 -172
- package/src/lib/cli-handler.js +0 -529
- package/src/lib/community-reports-validator.js +0 -91
- package/src/lib/configuration-exporter.js +0 -204
- package/src/lib/dataset-slug.js +0 -152
- package/src/lib/docker-introspection-validator.js +0 -51
- package/src/lib/known-flags-validator.js +0 -200
- package/src/lib/schema-validator.js +0 -157
- package/src/lib/train-config-parser.js +0 -136
- package/src/lib/train-config-persistence.js +0 -143
- package/src/lib/train-config-validator.js +0 -112
- package/src/lib/train-feedback.js +0 -46
- package/src/lib/train-idempotency.js +0 -97
- package/src/lib/train-request-builder.js +0 -120
- package/src/lib/tune-dataset-validator.js +0 -279
- package/src/lib/tune-output-resolver.js +0 -66
- package/templates/do/.train_poll_parser.py +0 -135
- package/templates/do/.train_status_parser.py +0 -187
- /package/templates/do/training/{train.py → custom/train.py} +0 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Agent Knowledge MCP Server
|
|
7
|
+
*
|
|
8
|
+
* A bundled MCP server that provides project knowledge not covered by
|
|
9
|
+
* other specialized servers: script reference, config documentation,
|
|
10
|
+
* troubleshooting patterns, and capability matrix.
|
|
11
|
+
*
|
|
12
|
+
* Tool: query_knowledge
|
|
13
|
+
* Accepts: { topic, filter? }
|
|
14
|
+
* Returns: topic-specific structured data
|
|
15
|
+
*
|
|
16
|
+
* Topics:
|
|
17
|
+
* - script_reference: do/* script metadata (purpose, flags, lifecycle position)
|
|
18
|
+
* - config_reference: do/config exported variables and documentation
|
|
19
|
+
* - troubleshooting: parsed TROUBLESHOOTING.md patterns
|
|
20
|
+
* - capability_matrix: agent capability matrix (full or filtered)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
24
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
25
|
+
import { z } from 'zod';
|
|
26
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
import { resolve, dirname, basename } from 'node:path';
|
|
29
|
+
|
|
30
|
+
// ── Path setup ───────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
33
|
+
const __dirname = dirname(__filename);
|
|
34
|
+
const PACKAGE_ROOT = resolve(__dirname, '../../');
|
|
35
|
+
|
|
36
|
+
// ── Logging ──────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
function log(message) {
|
|
39
|
+
process.stderr.write(`[agent-knowledge] ${message}\n`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── In-memory cache ──────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
const cache = new Map();
|
|
45
|
+
|
|
46
|
+
function getCached(key, loader) {
|
|
47
|
+
if (cache.has(key)) {
|
|
48
|
+
return cache.get(key);
|
|
49
|
+
}
|
|
50
|
+
const value = loader();
|
|
51
|
+
cache.set(key, value);
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Script Reference Parser ──────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Known lifecycle positions for do/* scripts.
|
|
59
|
+
* Provides ordering context for the agent.
|
|
60
|
+
*/
|
|
61
|
+
const LIFECYCLE_POSITIONS = {
|
|
62
|
+
config: 'configuration',
|
|
63
|
+
build: 'build',
|
|
64
|
+
run: 'local-test',
|
|
65
|
+
test: 'local-test',
|
|
66
|
+
push: 'publish',
|
|
67
|
+
deploy: 'deploy',
|
|
68
|
+
status: 'monitor',
|
|
69
|
+
logs: 'monitor',
|
|
70
|
+
clean: 'teardown',
|
|
71
|
+
validate: 'pre-deploy',
|
|
72
|
+
register: 'publish',
|
|
73
|
+
stage: 'publish',
|
|
74
|
+
optimize: 'pre-deploy',
|
|
75
|
+
benchmark: 'post-deploy',
|
|
76
|
+
evaluate: 'post-deploy',
|
|
77
|
+
adapter: 'post-deploy',
|
|
78
|
+
'add-ic': 'post-deploy',
|
|
79
|
+
train: 'training',
|
|
80
|
+
tune: 'training',
|
|
81
|
+
ci: 'ci',
|
|
82
|
+
submit: 'build',
|
|
83
|
+
export: 'publish',
|
|
84
|
+
manifest: 'metadata'
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Parse a single do/* script file to extract metadata from header comments.
|
|
89
|
+
*
|
|
90
|
+
* Looks for:
|
|
91
|
+
* - First comment block for purpose description
|
|
92
|
+
* - Usage: lines for flags
|
|
93
|
+
* - `source` directives for reads
|
|
94
|
+
* - Variable exports for writes
|
|
95
|
+
*/
|
|
96
|
+
function parseScriptFile(filePath) {
|
|
97
|
+
try {
|
|
98
|
+
const content = readFileSync(filePath, 'utf8');
|
|
99
|
+
const name = basename(filePath);
|
|
100
|
+
|
|
101
|
+
// Extract purpose from first comment block (lines starting with #, after shebang)
|
|
102
|
+
const lines = content.split('\n');
|
|
103
|
+
let purpose = '';
|
|
104
|
+
let flags = [];
|
|
105
|
+
const reads = [];
|
|
106
|
+
const writes = [];
|
|
107
|
+
const commonFailures = [];
|
|
108
|
+
|
|
109
|
+
// Skip shebang and copyright, find first descriptive comment
|
|
110
|
+
const inHeader = true;
|
|
111
|
+
const headerComments = [];
|
|
112
|
+
for (const line of lines) {
|
|
113
|
+
if (line.startsWith('#!')) continue;
|
|
114
|
+
if (line.startsWith('# Copyright')) continue;
|
|
115
|
+
if (line.startsWith('# SPDX-License-Identifier')) continue;
|
|
116
|
+
if (line === '#' || line === '') {
|
|
117
|
+
if (headerComments.length > 0) break;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (inHeader && line.startsWith('#')) {
|
|
121
|
+
headerComments.push(line.replace(/^#\s?/, ''));
|
|
122
|
+
} else {
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// First non-empty header comment is the purpose
|
|
128
|
+
purpose = headerComments.filter(c => c.trim()).join(' ').trim();
|
|
129
|
+
|
|
130
|
+
// Parse Usage block for flags
|
|
131
|
+
const usageMatch = content.match(/# Usage:\s*\n((?:#\s+.*\n)*)/);
|
|
132
|
+
if (usageMatch) {
|
|
133
|
+
const usageLines = usageMatch[1].split('\n')
|
|
134
|
+
.map(l => l.replace(/^#\s*/, '').trim())
|
|
135
|
+
.filter(Boolean);
|
|
136
|
+
flags = usageLines;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Parse individual flag definitions (--flag patterns in comments)
|
|
140
|
+
const flagPattern = /^#\s+(--[\w-]+(?:\s+\S+)?)\s+(.+)/gm;
|
|
141
|
+
let flagMatch;
|
|
142
|
+
while ((flagMatch = flagPattern.exec(content)) !== null) {
|
|
143
|
+
if (!flags.includes(flagMatch[1])) {
|
|
144
|
+
flags.push(`${flagMatch[1]} ${flagMatch[2]}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Detect source reads (source "..." or source '...')
|
|
149
|
+
const sourcePattern = /source\s+["']?\$\{?SCRIPT_DIR\}?["']?\/?([^"'\s;]+)/g;
|
|
150
|
+
let srcMatch;
|
|
151
|
+
while ((srcMatch = sourcePattern.exec(content)) !== null) {
|
|
152
|
+
reads.push(`do/${srcMatch[1]}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Detect config sourcing
|
|
156
|
+
if (content.includes('source "${SCRIPT_DIR}/config"') || content.includes('source \'${SCRIPT_DIR}/config\'')) {
|
|
157
|
+
if (!reads.includes('do/config')) {
|
|
158
|
+
reads.push('do/config');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Detect writes (common output patterns)
|
|
163
|
+
if (content.includes('docker build')) writes.push('Docker image');
|
|
164
|
+
if (content.includes('docker push') || content.includes('ecr')) writes.push('ECR repository');
|
|
165
|
+
if (content.includes('aws sagemaker create-endpoint')) writes.push('SageMaker endpoint');
|
|
166
|
+
if (content.includes('aws sagemaker create-model')) writes.push('SageMaker model');
|
|
167
|
+
if (content.includes('aws sagemaker delete-')) writes.push('SageMaker resources (delete)');
|
|
168
|
+
|
|
169
|
+
// Common failure patterns from script error handling
|
|
170
|
+
const exitPattern = /echo\s+"(?:\u274c|\u26a0\ufe0f)\s+(.+?)"\s*\n\s*(echo\s+".+?")?\s*\n?\s*exit\s+\d+/gu;
|
|
171
|
+
let exitMatch;
|
|
172
|
+
while ((exitMatch = exitPattern.exec(content)) !== null) {
|
|
173
|
+
commonFailures.push(exitMatch[1].replace(/\$\{[^}]+\}/g, '<variable>'));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
name,
|
|
178
|
+
purpose: purpose || `do/${name} script`,
|
|
179
|
+
flags,
|
|
180
|
+
reads,
|
|
181
|
+
writes,
|
|
182
|
+
lifecycle_position: LIFECYCLE_POSITIONS[name] || 'unknown',
|
|
183
|
+
common_failures: commonFailures
|
|
184
|
+
};
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return {
|
|
187
|
+
name: basename(filePath),
|
|
188
|
+
purpose: 'Unable to parse',
|
|
189
|
+
flags: [],
|
|
190
|
+
reads: [],
|
|
191
|
+
writes: [],
|
|
192
|
+
lifecycle_position: LIFECYCLE_POSITIONS[basename(filePath)] || 'unknown',
|
|
193
|
+
common_failures: [],
|
|
194
|
+
error: err.message,
|
|
195
|
+
partial: true
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Load and parse all do/* scripts from templates/do/.
|
|
202
|
+
* Skips hidden files, directories, and non-executable templates (e.g., EJS partials).
|
|
203
|
+
*/
|
|
204
|
+
function loadScriptReference() {
|
|
205
|
+
const doDir = resolve(PACKAGE_ROOT, 'templates/do');
|
|
206
|
+
try {
|
|
207
|
+
const entries = readdirSync(doDir);
|
|
208
|
+
const scripts = [];
|
|
209
|
+
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
// Skip hidden files, directories, __pycache__, README
|
|
212
|
+
if (entry.startsWith('.') || entry === '__pycache__' || entry === 'README.md') continue;
|
|
213
|
+
|
|
214
|
+
const fullPath = resolve(doDir, entry);
|
|
215
|
+
const stat = statSync(fullPath);
|
|
216
|
+
if (stat.isDirectory()) continue;
|
|
217
|
+
|
|
218
|
+
const parsed = parseScriptFile(fullPath);
|
|
219
|
+
// Skip EJS-only templates (contain only <%- include(...) %>)
|
|
220
|
+
if (parsed.purpose === '' && parsed.flags.length === 0) {
|
|
221
|
+
// Check if it's just an include directive
|
|
222
|
+
const content = readFileSync(fullPath, 'utf8').trim();
|
|
223
|
+
if (content.startsWith('<%') && content.length < 200) {
|
|
224
|
+
parsed.purpose = 'Template include (delegates to sub-template)';
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
scripts.push(parsed);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return scripts;
|
|
231
|
+
} catch (err) {
|
|
232
|
+
return { error: `Failed to read templates/do/: ${err.message}`, partial: true };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── Config Reference Parser ──────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Parse the do/config template to extract exported variables and their documentation.
|
|
240
|
+
*/
|
|
241
|
+
function loadConfigReference() {
|
|
242
|
+
const configPath = resolve(PACKAGE_ROOT, 'templates/do/config');
|
|
243
|
+
try {
|
|
244
|
+
const content = readFileSync(configPath, 'utf8');
|
|
245
|
+
const lines = content.split('\n');
|
|
246
|
+
|
|
247
|
+
const doConfigVars = [];
|
|
248
|
+
const icEnvVars = [];
|
|
249
|
+
const trainingConfig = [];
|
|
250
|
+
|
|
251
|
+
let currentComment = '';
|
|
252
|
+
let currentSection = 'do_config';
|
|
253
|
+
|
|
254
|
+
for (const line of lines) {
|
|
255
|
+
// Track sections
|
|
256
|
+
if (line.includes('Training') || line.includes('training')) {
|
|
257
|
+
currentSection = 'training';
|
|
258
|
+
}
|
|
259
|
+
if (line.includes('IC ') || line.includes('inference component') || line.includes('Inference Component')) {
|
|
260
|
+
currentSection = 'ic';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Collect comments
|
|
264
|
+
if (line.startsWith('#') && !line.startsWith('#!') && !line.startsWith('# Copyright') && !line.startsWith('# SPDX')) {
|
|
265
|
+
const comment = line.replace(/^#\s?/, '').trim();
|
|
266
|
+
if (comment && !comment.startsWith('──')) {
|
|
267
|
+
currentComment = comment;
|
|
268
|
+
}
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Parse export lines
|
|
273
|
+
const exportMatch = line.match(/^export\s+(\w+)=(.*)$/);
|
|
274
|
+
if (exportMatch) {
|
|
275
|
+
const varName = exportMatch[1];
|
|
276
|
+
const defaultValue = exportMatch[2]
|
|
277
|
+
.replace(/\$\{[^:}]+:-([^}]*)\}/g, '$1') // Extract default from ${VAR:-default}
|
|
278
|
+
.replace(/["'<>%=\s]/g, '')
|
|
279
|
+
.trim();
|
|
280
|
+
|
|
281
|
+
const entry = {
|
|
282
|
+
name: varName,
|
|
283
|
+
description: currentComment || '',
|
|
284
|
+
default: defaultValue || null
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
if (currentSection === 'training') {
|
|
288
|
+
trainingConfig.push(entry);
|
|
289
|
+
} else if (currentSection === 'ic') {
|
|
290
|
+
icEnvVars.push(entry);
|
|
291
|
+
} else {
|
|
292
|
+
doConfigVars.push(entry);
|
|
293
|
+
}
|
|
294
|
+
currentComment = '';
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Parse commented-out export lines (optional vars)
|
|
299
|
+
const commentedExport = line.match(/^#\s*export\s+(\w+)=(.*)$/);
|
|
300
|
+
if (commentedExport) {
|
|
301
|
+
const varName = commentedExport[1];
|
|
302
|
+
const entry = {
|
|
303
|
+
name: varName,
|
|
304
|
+
description: currentComment || '(optional, commented out)',
|
|
305
|
+
default: null,
|
|
306
|
+
optional: true
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
if (currentSection === 'training') {
|
|
310
|
+
trainingConfig.push(entry);
|
|
311
|
+
} else if (currentSection === 'ic') {
|
|
312
|
+
icEnvVars.push(entry);
|
|
313
|
+
} else {
|
|
314
|
+
doConfigVars.push(entry);
|
|
315
|
+
}
|
|
316
|
+
currentComment = '';
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// EJS conditionals reset comment
|
|
321
|
+
if (line.startsWith('<%')) {
|
|
322
|
+
currentComment = '';
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
do_config_vars: doConfigVars,
|
|
328
|
+
ic_env_vars: icEnvVars,
|
|
329
|
+
training_config: trainingConfig
|
|
330
|
+
};
|
|
331
|
+
} catch (err) {
|
|
332
|
+
return { error: `Failed to parse config: ${err.message}`, partial: true };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ── Troubleshooting Parser ───────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Parse TROUBLESHOOTING.md into structured patterns.
|
|
340
|
+
* Each H3 (###) section becomes a troubleshooting entry with pattern, root cause,
|
|
341
|
+
* diagnostic steps, and fix.
|
|
342
|
+
*/
|
|
343
|
+
function loadTroubleshooting() {
|
|
344
|
+
const tsPath = resolve(PACKAGE_ROOT, 'docs/TROUBLESHOOTING.md');
|
|
345
|
+
try {
|
|
346
|
+
const content = readFileSync(tsPath, 'utf8');
|
|
347
|
+
const patterns = [];
|
|
348
|
+
|
|
349
|
+
// Split by ### headings (H3 = individual issues)
|
|
350
|
+
const sections = content.split(/^### /gm).slice(1); // Skip content before first ###
|
|
351
|
+
|
|
352
|
+
for (const section of sections) {
|
|
353
|
+
const lines = section.split('\n');
|
|
354
|
+
const pattern = lines[0].trim();
|
|
355
|
+
|
|
356
|
+
let rootCause = '';
|
|
357
|
+
const diagnosticSteps = [];
|
|
358
|
+
let fix = '';
|
|
359
|
+
|
|
360
|
+
let currentBlock = '';
|
|
361
|
+
let inCodeBlock = false;
|
|
362
|
+
let codeContent = '';
|
|
363
|
+
|
|
364
|
+
for (let i = 1; i < lines.length; i++) {
|
|
365
|
+
const line = lines[i];
|
|
366
|
+
|
|
367
|
+
// Track code blocks
|
|
368
|
+
if (line.startsWith('```')) {
|
|
369
|
+
if (inCodeBlock) {
|
|
370
|
+
inCodeBlock = false;
|
|
371
|
+
if (currentBlock === 'fix' || currentBlock === 'diagnostic') {
|
|
372
|
+
if (currentBlock === 'fix') {
|
|
373
|
+
fix += `${codeContent.trim() }\n`;
|
|
374
|
+
} else {
|
|
375
|
+
diagnosticSteps.push(codeContent.trim());
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
codeContent = '';
|
|
379
|
+
} else {
|
|
380
|
+
inCodeBlock = true;
|
|
381
|
+
}
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (inCodeBlock) {
|
|
386
|
+
codeContent += `${line }\n`;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Detect section markers
|
|
391
|
+
const lower = line.toLowerCase();
|
|
392
|
+
if (lower.includes('**root cause') || lower.includes('**root cause:**') || lower.startsWith('**root cause')) {
|
|
393
|
+
currentBlock = 'root_cause';
|
|
394
|
+
const afterColon = line.replace(/\*\*[Rr]oot [Cc]ause:?\*\*:?\s*/, '').trim();
|
|
395
|
+
if (afterColon) rootCause = afterColon;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (lower.includes('**fix') || lower.includes('**workaround')) {
|
|
399
|
+
currentBlock = 'fix';
|
|
400
|
+
const afterColon = line.replace(/\*\*[Ff]ix:?\*\*:?\s*|\*\*[Ww]orkaround:?\*\*:?\s*/, '').trim();
|
|
401
|
+
if (afterColon) fix = afterColon;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (lower.includes('**symptoms') || lower.includes('**debug')) {
|
|
405
|
+
currentBlock = 'diagnostic';
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Accumulate content into current block
|
|
410
|
+
if (currentBlock === 'root_cause' && line.trim()) {
|
|
411
|
+
rootCause += (rootCause ? ' ' : '') + line.trim().replace(/\*\*/g, '');
|
|
412
|
+
} else if (currentBlock === 'fix' && line.trim()) {
|
|
413
|
+
fix += (fix ? ' ' : '') + line.trim().replace(/\*\*/g, '');
|
|
414
|
+
} else if (currentBlock === 'diagnostic' && line.trim()) {
|
|
415
|
+
diagnosticSteps.push(line.trim().replace(/\*\*/g, ''));
|
|
416
|
+
} else if (!currentBlock && line.trim() && !line.startsWith('#')) {
|
|
417
|
+
// Content before any explicit block — treat as root cause
|
|
418
|
+
rootCause += (rootCause ? ' ' : '') + line.trim().replace(/\*\*/g, '');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Only include entries that have meaningful content
|
|
423
|
+
if (pattern && (rootCause || fix || diagnosticSteps.length > 0)) {
|
|
424
|
+
patterns.push({
|
|
425
|
+
pattern: pattern.replace(/[`*]/g, ''),
|
|
426
|
+
root_cause: rootCause.trim() || 'See documentation',
|
|
427
|
+
diagnostic_steps: diagnosticSteps.length > 0 ? diagnosticSteps : ['Check logs for error details'],
|
|
428
|
+
fix: fix.trim() || 'See documentation'
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return patterns;
|
|
434
|
+
} catch (err) {
|
|
435
|
+
return { error: `Failed to parse TROUBLESHOOTING.md: ${err.message}`, partial: true };
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ── Capability Matrix Loader ─────────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Load the capability matrix from src/agent/data/capability-matrix.json.
|
|
443
|
+
* Returns full matrix or filtered by optional filter string.
|
|
444
|
+
*/
|
|
445
|
+
function loadCapabilityMatrix(filter) {
|
|
446
|
+
const matrixPath = resolve(PACKAGE_ROOT, 'src/agent/data/capability-matrix.json');
|
|
447
|
+
try {
|
|
448
|
+
const content = readFileSync(matrixPath, 'utf8');
|
|
449
|
+
const matrix = JSON.parse(content);
|
|
450
|
+
|
|
451
|
+
if (!filter) return matrix;
|
|
452
|
+
|
|
453
|
+
// Filter matrix entries by keyword match
|
|
454
|
+
const filterLower = filter.toLowerCase();
|
|
455
|
+
|
|
456
|
+
if (Array.isArray(matrix)) {
|
|
457
|
+
return matrix.filter(entry => {
|
|
458
|
+
const text = JSON.stringify(entry).toLowerCase();
|
|
459
|
+
return text.includes(filterLower);
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// If matrix is an object with named categories, filter by key or values
|
|
464
|
+
if (typeof matrix === 'object') {
|
|
465
|
+
const filtered = {};
|
|
466
|
+
for (const [key, value] of Object.entries(matrix)) {
|
|
467
|
+
const keyMatch = key.toLowerCase().includes(filterLower);
|
|
468
|
+
const valueMatch = JSON.stringify(value).toLowerCase().includes(filterLower);
|
|
469
|
+
if (keyMatch || valueMatch) {
|
|
470
|
+
filtered[key] = value;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return filtered;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return matrix;
|
|
477
|
+
} catch (err) {
|
|
478
|
+
if (err.code === 'ENOENT') {
|
|
479
|
+
return { error: 'capability-matrix.json not found — run Task 3 to generate it', partial: true };
|
|
480
|
+
}
|
|
481
|
+
return { error: `Failed to load capability matrix: ${err.message}`, partial: true };
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ── Tool Handler ─────────────────────────────────────────────────────────────
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Main tool handler for query_knowledge.
|
|
489
|
+
*/
|
|
490
|
+
async function handleQueryKnowledge({ topic, filter }) {
|
|
491
|
+
log(`Query: topic=${topic}, filter=${filter || 'none'}`);
|
|
492
|
+
|
|
493
|
+
let result;
|
|
494
|
+
|
|
495
|
+
switch (topic) {
|
|
496
|
+
case 'script_reference': {
|
|
497
|
+
let scripts = getCached('script_reference', loadScriptReference);
|
|
498
|
+
if (filter && Array.isArray(scripts)) {
|
|
499
|
+
const filterLower = filter.toLowerCase();
|
|
500
|
+
scripts = scripts.filter(s =>
|
|
501
|
+
s.name.toLowerCase().includes(filterLower) ||
|
|
502
|
+
s.purpose.toLowerCase().includes(filterLower) ||
|
|
503
|
+
s.lifecycle_position.toLowerCase().includes(filterLower)
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
result = scripts;
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
case 'config_reference': {
|
|
511
|
+
result = getCached('config_reference', loadConfigReference);
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
case 'troubleshooting': {
|
|
516
|
+
let patterns = getCached('troubleshooting', loadTroubleshooting);
|
|
517
|
+
if (filter && Array.isArray(patterns)) {
|
|
518
|
+
const filterLower = filter.toLowerCase();
|
|
519
|
+
patterns = patterns.filter(p =>
|
|
520
|
+
p.pattern.toLowerCase().includes(filterLower) ||
|
|
521
|
+
p.root_cause.toLowerCase().includes(filterLower) ||
|
|
522
|
+
p.fix.toLowerCase().includes(filterLower)
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
result = patterns;
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
case 'capability_matrix': {
|
|
530
|
+
// Capability matrix supports filter at load time for efficiency
|
|
531
|
+
const cacheKey = `capability_matrix:${filter || ''}`;
|
|
532
|
+
result = getCached(cacheKey, () => loadCapabilityMatrix(filter || null));
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
default:
|
|
537
|
+
result = {
|
|
538
|
+
error: `Unknown topic: "${topic}". Valid topics: script_reference, config_reference, troubleshooting, capability_matrix`,
|
|
539
|
+
partial: true
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return {
|
|
544
|
+
content: [{
|
|
545
|
+
type: 'text',
|
|
546
|
+
text: JSON.stringify(result, null, 2)
|
|
547
|
+
}]
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ── MCP Server setup ─────────────────────────────────────────────────────────
|
|
552
|
+
|
|
553
|
+
const server = new McpServer({
|
|
554
|
+
name: 'agent-knowledge',
|
|
555
|
+
version: '1.0.0'
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
server.tool(
|
|
559
|
+
'query_knowledge',
|
|
560
|
+
'Query project knowledge base. Returns structured data for script reference, config documentation, troubleshooting patterns, or capability matrix. Call this tool BEFORE answering questions about do/* scripts, configuration variables, or common issues.',
|
|
561
|
+
{
|
|
562
|
+
topic: z.enum(['script_reference', 'config_reference', 'troubleshooting', 'capability_matrix'])
|
|
563
|
+
.describe('Knowledge topic to query'),
|
|
564
|
+
filter: z.string().optional()
|
|
565
|
+
.describe('Optional filter — narrows results by keyword match (e.g., script name, lifecycle stage, error pattern)')
|
|
566
|
+
},
|
|
567
|
+
async (params) => {
|
|
568
|
+
return handleQueryKnowledge(params);
|
|
569
|
+
}
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
// ── Exports for testing ──────────────────────────────────────────────────────
|
|
573
|
+
|
|
574
|
+
export {
|
|
575
|
+
handleQueryKnowledge,
|
|
576
|
+
loadScriptReference,
|
|
577
|
+
loadConfigReference,
|
|
578
|
+
loadTroubleshooting,
|
|
579
|
+
loadCapabilityMatrix,
|
|
580
|
+
server,
|
|
581
|
+
PACKAGE_ROOT
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
// ── Transport connection (main module only) ──────────────────────────────────
|
|
585
|
+
|
|
586
|
+
const isMain = process.argv[1] && resolve(process.argv[1]) === __filename;
|
|
587
|
+
|
|
588
|
+
if (isMain) {
|
|
589
|
+
log('Starting agent-knowledge MCP server (stdio transport)');
|
|
590
|
+
const transport = new StdioServerTransport();
|
|
591
|
+
await server.connect(transport);
|
|
592
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@amzn/ml-container-creator-agent-knowledge",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "MCP server providing script reference, config documentation, troubleshooting patterns, and capability matrix for the agent.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"license": "Apache-2.0",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node test.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
14
|
+
}
|
|
15
|
+
}
|