@inspectr/mcplab-mcp-server 1.7.2 → 1.8.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 +2 -0
- package/dist/runtime.contracts.test.js +149 -3
- package/dist/runtime.contracts.test.js.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +612 -92
- package/dist/runtime.js.map +1 -1
- package/dist/skills/mcplab-assistant/SKILL.md +291 -0
- package/dist/skills/mcplab-assistant/references/cli-recipes.md +104 -0
- package/dist/skills/mcplab-assistant/references/config-recipes.md +253 -0
- package/dist/skills/mcplab-assistant/references/examples-map.md +33 -0
- package/dist/skills/mcplab-assistant/references/troubleshooting.md +35 -0
- package/dist/skills.d.ts +47 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +190 -0
- package/dist/skills.js.map +1 -0
- package/dist/skills.test.d.ts +2 -0
- package/dist/skills.test.d.ts.map +1 -0
- package/dist/skills.test.js +150 -0
- package/dist/skills.test.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Use this table to map symptoms to likely causes and fastest fixes.
|
|
4
|
+
|
|
5
|
+
## Common Failures
|
|
6
|
+
|
|
7
|
+
| Symptom | Likely Cause | Fix |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `fetch failed` | Server unavailable, wrong URL, or auth missing | Verify server is running, confirm `mcp_servers[].url`, check auth env vars are set |
|
|
10
|
+
| `Unknown agents: ...` | `--agents` includes undefined agent id | Use only agent ids loaded from config/library |
|
|
11
|
+
| Scenario not found/selected | Wrong `-s/--scenario` id | Use exact scenario `id` from config |
|
|
12
|
+
| `Runs must be a positive number` | `-n/--runs` is `0`, negative, or non-numeric | Set `-n` to integer `>= 1` |
|
|
13
|
+
| `Port must be a positive number` | Invalid `--port` value for `mcplab app` | Use port integer `>= 1` |
|
|
14
|
+
| OAuth token retrieval failures | Wrong token URL or missing client env vars | Confirm `token_url`, `client_id_env`, `client_secret_env`, and optional scope/audience |
|
|
15
|
+
| Assertion failures with otherwise valid response | Literal/regex/jsonpath mismatch | Prefer literal assertions (`contains`, `equals`, etc.) for stable text, use regex only for variable text, and verify JSONPath against valid JSON output |
|
|
16
|
+
|
|
17
|
+
## Fast Triage Procedure
|
|
18
|
+
|
|
19
|
+
1. Capture exact command and full error text.
|
|
20
|
+
2. Confirm config references:
|
|
21
|
+
- scenario -> agent exists
|
|
22
|
+
- scenario server labels are present and mapped to reachable MCP server definitions
|
|
23
|
+
3. Validate auth env vars by name (never print secret values).
|
|
24
|
+
4. Re-run with minimal case:
|
|
25
|
+
- one scenario
|
|
26
|
+
- one agent
|
|
27
|
+
- one run
|
|
28
|
+
5. If failing, inspect `trace.jsonl` for last successful/failed tool step.
|
|
29
|
+
|
|
30
|
+
## Output-Driven Debugging
|
|
31
|
+
|
|
32
|
+
- `results.json`: check per-scenario fail reason, assertions, tool stats.
|
|
33
|
+
- `summary.md`: quick pass/fail and latency overview.
|
|
34
|
+
- `trace.jsonl`: exact sequence of LLM responses and tool calls.
|
|
35
|
+
- `report.html`: interactive filters for scenario and run analysis.
|
package/dist/skills.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export type SkillManifestEntry = {
|
|
2
|
+
path: string;
|
|
3
|
+
bytes: number;
|
|
4
|
+
sha256: string;
|
|
5
|
+
};
|
|
6
|
+
export type SkillManifest = {
|
|
7
|
+
name: string;
|
|
8
|
+
files: SkillManifestEntry[];
|
|
9
|
+
};
|
|
10
|
+
export type LoadedSkill = {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
/** Relative file paths within the skill directory, SKILL.md always first. */
|
|
14
|
+
files: string[];
|
|
15
|
+
manifest: SkillManifest;
|
|
16
|
+
};
|
|
17
|
+
export declare function resolveSkillsDir(): string;
|
|
18
|
+
export declare function loadSkills(logger?: Pick<Console, 'error'>): LoadedSkill[];
|
|
19
|
+
export declare function readSkillFile(name: string, file: string): string;
|
|
20
|
+
export type MinimalResourceServer = {
|
|
21
|
+
registerResource: (
|
|
22
|
+
name: string,
|
|
23
|
+
uri: string,
|
|
24
|
+
config: {
|
|
25
|
+
title?: string;
|
|
26
|
+
mimeType?: string;
|
|
27
|
+
},
|
|
28
|
+
readCallback: (uri: URL) => Promise<{
|
|
29
|
+
contents: Array<{
|
|
30
|
+
uri: string;
|
|
31
|
+
mimeType?: string;
|
|
32
|
+
text: string;
|
|
33
|
+
}>;
|
|
34
|
+
}>
|
|
35
|
+
) => unknown;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Registers each loaded skill's files (plus a `_manifest` resource with sha256 hashes) as
|
|
39
|
+
* MCP resources under `skill://<name>/<relative-path>` URIs, so MCP clients can fetch skill
|
|
40
|
+
* content directly. Content is served via `readSkillFile`, which re-applies path-traversal
|
|
41
|
+
* and symlink-escape protections on every read (the file list here is only used to build URIs).
|
|
42
|
+
*/
|
|
43
|
+
export declare function registerSkills(
|
|
44
|
+
server: MinimalResourceServer,
|
|
45
|
+
logger?: Pick<Console, 'error'>
|
|
46
|
+
): LoadedSkill[];
|
|
47
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,aAAa,CAAC;CACzB,CAAC;AAEF,wBAAgB,gBAAgB,IAAI,MAAM,CASzC;AAuED,wBAAgB,UAAU,CAAC,MAAM,GAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAW,GAAG,WAAW,EAAE,CA2BlF;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAiChE;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,gBAAgB,EAAE,CAChB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAC7C,YAAY,EAAE,CACZ,GAAG,EAAE,GAAG,KACL,OAAO,CAAC;QAAE,QAAQ,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,KAChF,OAAO,CAAC;CACd,CAAC;AASF;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,qBAAqB,EAC7B,MAAM,GAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAW,GACvC,WAAW,EAAE,CAwCf"}
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { parse as parseYaml } from 'yaml';
|
|
6
|
+
export function resolveSkillsDir() {
|
|
7
|
+
const override = process.env.MCPLAB_SKILLS_DIR?.trim();
|
|
8
|
+
if (override)
|
|
9
|
+
return resolve(override);
|
|
10
|
+
const bundled = fileURLToPath(new URL('./skills', import.meta.url));
|
|
11
|
+
if (existsSync(bundled))
|
|
12
|
+
return bundled;
|
|
13
|
+
// Development runs load this module from src/, while production loads the
|
|
14
|
+
// copied resources from dist/. Fall back to the repository skill source.
|
|
15
|
+
const repositorySkills = fileURLToPath(new URL('../../../skills', import.meta.url));
|
|
16
|
+
return repositorySkills;
|
|
17
|
+
}
|
|
18
|
+
function parseFrontmatterDescription(skillMdContent) {
|
|
19
|
+
const match = skillMdContent.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
20
|
+
if (!match)
|
|
21
|
+
return '';
|
|
22
|
+
try {
|
|
23
|
+
const frontmatter = parseYaml(match[1]);
|
|
24
|
+
const description = frontmatter && typeof frontmatter === 'object'
|
|
25
|
+
? frontmatter.description
|
|
26
|
+
: undefined;
|
|
27
|
+
return typeof description === 'string' ? description.trim() : '';
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Lists files within a skill directory. Symlinks (files or directories) are skipped entirely. */
|
|
34
|
+
function listFilesRecursive(dir, base) {
|
|
35
|
+
const out = [];
|
|
36
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
37
|
+
if (entry.isSymbolicLink())
|
|
38
|
+
continue;
|
|
39
|
+
const abs = join(dir, entry.name);
|
|
40
|
+
if (entry.isDirectory()) {
|
|
41
|
+
out.push(...listFilesRecursive(abs, base));
|
|
42
|
+
}
|
|
43
|
+
else if (entry.isFile()) {
|
|
44
|
+
out.push(relative(base, abs).split(sep).join('/'));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out.sort();
|
|
48
|
+
}
|
|
49
|
+
// Skill names and relative file paths are interpolated directly into `skill://<name>/<path>`
|
|
50
|
+
// resource URIs (see registerSkills below). Restricting both to a safe character set at load
|
|
51
|
+
// time guarantees every URI we build is parseable (e.g. `new URL('skill://my skill/SKILL.md')`
|
|
52
|
+
// throws) and avoids reserved/ambiguous characters, without needing to encode/decode later.
|
|
53
|
+
const SAFE_SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
54
|
+
const SAFE_RELATIVE_FILE_PATH_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9._/-]*$/;
|
|
55
|
+
function loadSkill(skillsRoot, name, logger) {
|
|
56
|
+
const dir = join(skillsRoot, name);
|
|
57
|
+
const relativeFiles = listFilesRecursive(dir, dir).filter((relPath) => {
|
|
58
|
+
if (SAFE_RELATIVE_FILE_PATH_PATTERN.test(relPath))
|
|
59
|
+
return true;
|
|
60
|
+
logger.error(`[mcplab-mcp] skipping file '${relPath}' in skill '${name}': unsafe characters for a skill:// resource URI`);
|
|
61
|
+
return false;
|
|
62
|
+
});
|
|
63
|
+
if (!relativeFiles.includes('SKILL.md')) {
|
|
64
|
+
throw new Error(`Skill '${name}' is missing SKILL.md`);
|
|
65
|
+
}
|
|
66
|
+
const orderedFiles = ['SKILL.md', ...relativeFiles.filter((f) => f !== 'SKILL.md')];
|
|
67
|
+
let skillMdContent = '';
|
|
68
|
+
const manifestFiles = orderedFiles.map((relPath) => {
|
|
69
|
+
const content = readFileSync(join(dir, relPath));
|
|
70
|
+
if (relPath === 'SKILL.md')
|
|
71
|
+
skillMdContent = content.toString('utf8');
|
|
72
|
+
return {
|
|
73
|
+
path: relPath,
|
|
74
|
+
bytes: content.byteLength,
|
|
75
|
+
sha256: createHash('sha256').update(content).digest('hex')
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const description = parseFrontmatterDescription(skillMdContent);
|
|
79
|
+
return {
|
|
80
|
+
name,
|
|
81
|
+
description,
|
|
82
|
+
files: orderedFiles,
|
|
83
|
+
manifest: { name, files: manifestFiles }
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export function loadSkills(logger = console) {
|
|
87
|
+
const skillsRoot = resolveSkillsDir();
|
|
88
|
+
if (!existsSync(skillsRoot)) {
|
|
89
|
+
logger.error(`[mcplab-mcp] skills directory not found at ${skillsRoot}; skipping skill registration`);
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const names = readdirSync(skillsRoot, { withFileTypes: true })
|
|
93
|
+
.filter((entry) => entry.isDirectory())
|
|
94
|
+
.map((entry) => entry.name)
|
|
95
|
+
.sort();
|
|
96
|
+
const skills = [];
|
|
97
|
+
for (const name of names) {
|
|
98
|
+
if (!SAFE_SKILL_NAME_PATTERN.test(name)) {
|
|
99
|
+
logger.error(`[mcplab-mcp] skipping skill '${name}': name contains characters unsafe for a skill:// resource URI`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
skills.push(loadSkill(skillsRoot, name, logger));
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
logger.error(`[mcplab-mcp] failed to load skill '${name}':`, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return skills;
|
|
110
|
+
}
|
|
111
|
+
export function readSkillFile(name, file) {
|
|
112
|
+
const skillsRoot = resolve(resolveSkillsDir());
|
|
113
|
+
const dir = resolve(join(skillsRoot, name));
|
|
114
|
+
const dirWithinRoot = dir === skillsRoot || dir.startsWith(skillsRoot + sep);
|
|
115
|
+
if (!dirWithinRoot || !existsSync(dir) || !statSync(dir).isDirectory()) {
|
|
116
|
+
throw new Error(`Unknown skill: ${name}`);
|
|
117
|
+
}
|
|
118
|
+
// Re-check containment against realpaths: `dir` itself, or any path segment
|
|
119
|
+
// leading up to it, could be a symlink that lands outside `skillsRoot` even
|
|
120
|
+
// though the lexical (pre-symlink-resolution) path above looked contained.
|
|
121
|
+
const realSkillsRoot = realpathSync(skillsRoot);
|
|
122
|
+
const realDir = realpathSync(dir);
|
|
123
|
+
const dirReallyWithinRoot = realDir === realSkillsRoot || realDir.startsWith(realSkillsRoot + sep);
|
|
124
|
+
if (!dirReallyWithinRoot) {
|
|
125
|
+
throw new Error(`Unknown skill: ${name}`);
|
|
126
|
+
}
|
|
127
|
+
const target = resolve(join(dir, file));
|
|
128
|
+
const withinDir = target === dir || target.startsWith(dir + sep);
|
|
129
|
+
if (!withinDir || !existsSync(target)) {
|
|
130
|
+
throw new Error(`Unknown skill file: ${name}/${file}`);
|
|
131
|
+
}
|
|
132
|
+
// Same idea as above: `target` may look lexically contained in `dir` while
|
|
133
|
+
// actually resolving (via a symlinked leaf file OR a symlinked intermediate
|
|
134
|
+
// directory, e.g. `shared/secret.txt` where `shared` is a symlink) to a
|
|
135
|
+
// location outside `realDir`. Comparing realpaths catches both cases.
|
|
136
|
+
const realTarget = realpathSync(target);
|
|
137
|
+
const reallyWithinDir = realTarget === realDir || realTarget.startsWith(realDir + sep);
|
|
138
|
+
if (!reallyWithinDir || !statSync(realTarget).isFile()) {
|
|
139
|
+
throw new Error(`Unknown skill file: ${name}/${file}`);
|
|
140
|
+
}
|
|
141
|
+
return readFileSync(target, 'utf8');
|
|
142
|
+
}
|
|
143
|
+
function mimeTypeFor(relPath) {
|
|
144
|
+
if (relPath === '_manifest')
|
|
145
|
+
return 'application/json';
|
|
146
|
+
if (relPath.endsWith('.md'))
|
|
147
|
+
return 'text/markdown';
|
|
148
|
+
if (relPath.endsWith('.json'))
|
|
149
|
+
return 'application/json';
|
|
150
|
+
return 'text/plain';
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Registers each loaded skill's files (plus a `_manifest` resource with sha256 hashes) as
|
|
154
|
+
* MCP resources under `skill://<name>/<relative-path>` URIs, so MCP clients can fetch skill
|
|
155
|
+
* content directly. Content is served via `readSkillFile`, which re-applies path-traversal
|
|
156
|
+
* and symlink-escape protections on every read (the file list here is only used to build URIs).
|
|
157
|
+
*/
|
|
158
|
+
export function registerSkills(server, logger = console) {
|
|
159
|
+
const skills = loadSkills(logger);
|
|
160
|
+
for (const skill of skills) {
|
|
161
|
+
// Isolate one skill's resource registration from the rest, mirroring loadSkills' per-skill
|
|
162
|
+
// try/catch above: a single bad skill (e.g. a URI collision from a file literally named
|
|
163
|
+
// `_manifest`, which registerResource rejects as a duplicate) must not crash registration
|
|
164
|
+
// for every other skill, or the whole server, since this runs from createConfiguredServer().
|
|
165
|
+
try {
|
|
166
|
+
for (const relPath of skill.files) {
|
|
167
|
+
const uri = `skill://${skill.name}/${relPath}`;
|
|
168
|
+
const mimeType = mimeTypeFor(relPath);
|
|
169
|
+
server.registerResource(`skill-${skill.name}-${relPath}`, uri, { title: `${skill.name}: ${relPath}`, mimeType }, async () => ({
|
|
170
|
+
contents: [{ uri, mimeType, text: readSkillFile(skill.name, relPath) }]
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
const manifestUri = `skill://${skill.name}/_manifest`;
|
|
174
|
+
server.registerResource(`skill-${skill.name}-manifest`, manifestUri, { title: `${skill.name}: manifest`, mimeType: 'application/json' }, async () => ({
|
|
175
|
+
contents: [
|
|
176
|
+
{
|
|
177
|
+
uri: manifestUri,
|
|
178
|
+
mimeType: 'application/json',
|
|
179
|
+
text: JSON.stringify(skill.manifest, null, 2)
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
logger.error(`[mcplab-mcp] failed to register resources for skill '${skill.name}':`, error);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return skills;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.test.d.ts","sourceRoot":"","sources":["../src/skills.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
const originalEnv = { ...process.env };
|
|
7
|
+
const roots = [];
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
for (const key of Object.keys(process.env)) {
|
|
10
|
+
if (!(key in originalEnv))
|
|
11
|
+
delete process.env[key];
|
|
12
|
+
}
|
|
13
|
+
Object.assign(process.env, originalEnv);
|
|
14
|
+
for (const root of roots.splice(0))
|
|
15
|
+
rmSync(root, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
function makeFixtureSkillsDir() {
|
|
18
|
+
const root = mkdtempSync(join(tmpdir(), 'mcplab-skills-'));
|
|
19
|
+
roots.push(root);
|
|
20
|
+
const skillDir = join(root, 'demo-skill');
|
|
21
|
+
mkdirSync(join(skillDir, 'references'), { recursive: true });
|
|
22
|
+
writeFileSync(join(skillDir, 'SKILL.md'), '---\nname: demo-skill\ndescription: A demo skill for tests.\n---\n\n# Demo Skill\n');
|
|
23
|
+
writeFileSync(join(skillDir, 'references', 'notes.md'), '# Notes\n');
|
|
24
|
+
process.env.MCPLAB_SKILLS_DIR = root;
|
|
25
|
+
return root;
|
|
26
|
+
}
|
|
27
|
+
describe('loadSkills', () => {
|
|
28
|
+
it('falls back to repository skills when running from source', async () => {
|
|
29
|
+
delete process.env.MCPLAB_SKILLS_DIR;
|
|
30
|
+
const { resolveSkillsDir } = await import('./skills.js');
|
|
31
|
+
expect(resolveSkillsDir()).toMatch(/(?:^|\/)skills$/);
|
|
32
|
+
});
|
|
33
|
+
it('loads a skill with description parsed from frontmatter and SKILL.md listed first', async () => {
|
|
34
|
+
makeFixtureSkillsDir();
|
|
35
|
+
const { loadSkills } = await import('./skills.js');
|
|
36
|
+
const skills = loadSkills();
|
|
37
|
+
expect(skills).toHaveLength(1);
|
|
38
|
+
expect(skills[0].name).toBe('demo-skill');
|
|
39
|
+
expect(skills[0].description).toBe('A demo skill for tests.');
|
|
40
|
+
expect(skills[0].files[0]).toBe('SKILL.md');
|
|
41
|
+
expect(skills[0].files).toContain('references/notes.md');
|
|
42
|
+
});
|
|
43
|
+
it('computes a stable sha256 manifest across repeated loads', async () => {
|
|
44
|
+
makeFixtureSkillsDir();
|
|
45
|
+
const { loadSkills } = await import('./skills.js');
|
|
46
|
+
const first = loadSkills();
|
|
47
|
+
const second = loadSkills();
|
|
48
|
+
expect(first[0].manifest).toEqual(second[0].manifest);
|
|
49
|
+
const skillMdEntry = first[0].manifest.files.find((f) => f.path === 'SKILL.md');
|
|
50
|
+
expect(skillMdEntry?.sha256).toMatch(/^[0-9a-f]{64}$/);
|
|
51
|
+
const expectedHash = createHash('sha256')
|
|
52
|
+
.update('---\nname: demo-skill\ndescription: A demo skill for tests.\n---\n\n# Demo Skill\n')
|
|
53
|
+
.digest('hex');
|
|
54
|
+
expect(skillMdEntry?.sha256).toBe(expectedHash);
|
|
55
|
+
});
|
|
56
|
+
it('returns an empty list and does not throw when the skills directory is missing', async () => {
|
|
57
|
+
process.env.MCPLAB_SKILLS_DIR = join(tmpdir(), 'does-not-exist-mcplab-skills');
|
|
58
|
+
const { loadSkills } = await import('./skills.js');
|
|
59
|
+
expect(loadSkills({ error: () => { } })).toEqual([]);
|
|
60
|
+
});
|
|
61
|
+
it('returns an empty list and does not throw when the skills directory exists but has no subdirectories', async () => {
|
|
62
|
+
const root = mkdtempSync(join(tmpdir(), 'mcplab-skills-empty-'));
|
|
63
|
+
roots.push(root);
|
|
64
|
+
process.env.MCPLAB_SKILLS_DIR = root;
|
|
65
|
+
const { loadSkills } = await import('./skills.js');
|
|
66
|
+
expect(loadSkills({ error: () => { } })).toEqual([]);
|
|
67
|
+
});
|
|
68
|
+
it('skips a skill subdirectory missing SKILL.md and still returns valid skills', async () => {
|
|
69
|
+
const root = makeFixtureSkillsDir();
|
|
70
|
+
mkdirSync(join(root, 'broken-skill'), { recursive: true });
|
|
71
|
+
writeFileSync(join(root, 'broken-skill', 'notes.md'), 'no SKILL.md here\n');
|
|
72
|
+
const { loadSkills } = await import('./skills.js');
|
|
73
|
+
const skills = loadSkills({ error: () => { } });
|
|
74
|
+
expect(skills).toHaveLength(1);
|
|
75
|
+
expect(skills[0].name).toBe('demo-skill');
|
|
76
|
+
});
|
|
77
|
+
it('skips a skill directory whose name is unsafe for a skill:// resource URI, keeping other skills', async () => {
|
|
78
|
+
const root = makeFixtureSkillsDir();
|
|
79
|
+
mkdirSync(join(root, 'bad skill name'), { recursive: true });
|
|
80
|
+
writeFileSync(join(root, 'bad skill name', 'SKILL.md'), '---\nname: bad skill name\ndescription: Should be skipped.\n---\n\n# Bad\n');
|
|
81
|
+
const { loadSkills } = await import('./skills.js');
|
|
82
|
+
const skills = loadSkills({ error: () => { } });
|
|
83
|
+
expect(skills).toHaveLength(1);
|
|
84
|
+
expect(skills[0].name).toBe('demo-skill');
|
|
85
|
+
});
|
|
86
|
+
it('excludes a file whose relative path is unsafe for a skill:// resource URI, keeping the rest of the skill', async () => {
|
|
87
|
+
const root = makeFixtureSkillsDir();
|
|
88
|
+
writeFileSync(join(root, 'demo-skill', 'references', 'my notes.md'), '# My Notes\n');
|
|
89
|
+
const { loadSkills } = await import('./skills.js');
|
|
90
|
+
const skills = loadSkills({ error: () => { } });
|
|
91
|
+
expect(skills).toHaveLength(1);
|
|
92
|
+
expect(skills[0].name).toBe('demo-skill');
|
|
93
|
+
expect(skills[0].files).not.toContain('references/my notes.md');
|
|
94
|
+
expect(skills[0].files).toContain('references/notes.md');
|
|
95
|
+
expect(skills[0].manifest.files.map((f) => f.path)).not.toContain('references/my notes.md');
|
|
96
|
+
});
|
|
97
|
+
it('does not false-positive on a normal kebab-case skill with ordinary filenames', async () => {
|
|
98
|
+
makeFixtureSkillsDir();
|
|
99
|
+
const { loadSkills } = await import('./skills.js');
|
|
100
|
+
const skills = loadSkills({ error: () => { } });
|
|
101
|
+
expect(skills).toHaveLength(1);
|
|
102
|
+
expect(skills[0].name).toBe('demo-skill');
|
|
103
|
+
expect(skills[0].files).toEqual(['SKILL.md', 'references/notes.md']);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('readSkillFile', () => {
|
|
107
|
+
it('reads a file within a skill by relative path', async () => {
|
|
108
|
+
makeFixtureSkillsDir();
|
|
109
|
+
const { readSkillFile } = await import('./skills.js');
|
|
110
|
+
expect(readSkillFile('demo-skill', 'references/notes.md')).toBe('# Notes\n');
|
|
111
|
+
});
|
|
112
|
+
it('throws for an unknown skill', async () => {
|
|
113
|
+
makeFixtureSkillsDir();
|
|
114
|
+
const { readSkillFile } = await import('./skills.js');
|
|
115
|
+
expect(() => readSkillFile('missing-skill', 'SKILL.md')).toThrow(/Unknown skill/);
|
|
116
|
+
});
|
|
117
|
+
it('throws for an unknown file within a known skill', async () => {
|
|
118
|
+
makeFixtureSkillsDir();
|
|
119
|
+
const { readSkillFile } = await import('./skills.js');
|
|
120
|
+
expect(() => readSkillFile('demo-skill', 'nope.md')).toThrow(/Unknown skill file/);
|
|
121
|
+
});
|
|
122
|
+
it('throws when the skill name traverses outside the skills root', async () => {
|
|
123
|
+
makeFixtureSkillsDir();
|
|
124
|
+
const { readSkillFile } = await import('./skills.js');
|
|
125
|
+
expect(() => readSkillFile('..', 'package.json')).toThrow(/Unknown skill/);
|
|
126
|
+
expect(() => readSkillFile('../../etc', 'passwd')).toThrow(/Unknown skill/);
|
|
127
|
+
});
|
|
128
|
+
it('throws rather than following a symlink that escapes the skill directory', async () => {
|
|
129
|
+
const root = makeFixtureSkillsDir();
|
|
130
|
+
const secretRoot = mkdtempSync(join(tmpdir(), 'mcplab-skills-secret-'));
|
|
131
|
+
roots.push(secretRoot);
|
|
132
|
+
const secretPath = join(secretRoot, 'secret.txt');
|
|
133
|
+
writeFileSync(secretPath, 'TOP SECRET\n');
|
|
134
|
+
const linkPath = join(root, 'demo-skill', 'leak.md');
|
|
135
|
+
symlinkSync(secretPath, linkPath);
|
|
136
|
+
const { readSkillFile } = await import('./skills.js');
|
|
137
|
+
expect(() => readSkillFile('demo-skill', 'leak.md')).toThrow(/Unknown skill file/);
|
|
138
|
+
});
|
|
139
|
+
it('throws rather than following a symlinked subdirectory that escapes the skill directory', async () => {
|
|
140
|
+
const root = makeFixtureSkillsDir();
|
|
141
|
+
const secretRoot = mkdtempSync(join(tmpdir(), 'mcplab-skills-secret-dir-'));
|
|
142
|
+
roots.push(secretRoot);
|
|
143
|
+
writeFileSync(join(secretRoot, 'secret.txt'), 'TOP SECRET\n');
|
|
144
|
+
const linkPath = join(root, 'demo-skill', 'shared');
|
|
145
|
+
symlinkSync(secretRoot, linkPath, 'dir');
|
|
146
|
+
const { readSkillFile } = await import('./skills.js');
|
|
147
|
+
expect(() => readSkillFile('demo-skill', 'shared/secret.txt')).toThrow(/Unknown skill file/);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
//# sourceMappingURL=skills.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.test.js","sourceRoot":"","sources":["../src/skills.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzD,MAAM,WAAW,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;AACvC,MAAM,KAAK,GAAa,EAAE,CAAC;AAE3B,SAAS,CAAC,GAAG,EAAE;IACb,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAC,CAAC,GAAG,IAAI,WAAW,CAAC;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrD,CAAC;IACD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IACxC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QAAE,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACrF,CAAC,CAAC,CAAC;AAEH,SAAS,oBAAoB;IAC3B,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAC3D,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;IAC1C,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,aAAa,CACX,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,EAC1B,oFAAoF,CACrF,CAAC;IACF,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,EAAE,UAAU,CAAC,EAAE,WAAW,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,IAAI,CAAC;IACrC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;QACrC,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACzD,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kFAAkF,EAAE,KAAK,IAAI,EAAE;QAChG,oBAAoB,EAAE,CAAC;QACvB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAC9D,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,oBAAoB,EAAE,CAAC;QACvB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACnD,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAC5B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACtD,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;QAChF,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACvD,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,CAAC;aACtC,MAAM,CAAC,oFAAoF,CAAC;aAC5F,MAAM,CAAC,KAAK,CAAC,CAAC;QACjB,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+EAA+E,EAAE,KAAK,IAAI,EAAE;QAC7F,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,8BAA8B,CAAC,CAAC;QAC/E,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACnD,MAAM,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qGAAqG,EAAE,KAAK,IAAI,EAAE;QACnH,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAAC;QACjE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,IAAI,CAAC;QACrC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACnD,MAAM,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4EAA4E,EAAE,KAAK,IAAI,EAAE;QAC1F,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;QACpC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,UAAU,CAAC,EAAE,oBAAoB,CAAC,CAAC;QAC5E,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gGAAgG,EAAE,KAAK,IAAI,EAAE;QAC9G,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;QACpC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,aAAa,CACX,IAAI,CAAC,IAAI,EAAE,gBAAgB,EAAE,UAAU,CAAC,EACxC,4EAA4E,CAC7E,CAAC;QACF,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0GAA0G,EAAE,KAAK,IAAI,EAAE;QACxH,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;QACpC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE,cAAc,CAAC,CAAC;QACrF,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;QAChE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;QACzD,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8EAA8E,EAAE,KAAK,IAAI,EAAE;QAC5F,oBAAoB,EAAE,CAAC;QACvB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,qBAAqB,CAAC,CAAC,CAAC;IACvE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;QAC5D,oBAAoB,EAAE,CAAC;QACvB,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACtD,MAAM,CAAC,aAAa,CAAC,YAAY,EAAE,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,oBAAoB,EAAE,CAAC;QACvB,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,oBAAoB,EAAE,CAAC;QACvB,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,oBAAoB,EAAE,CAAC;QACvB,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAC3E,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,KAAK,IAAI,EAAE;QACvF,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;QACpC,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,uBAAuB,CAAC,CAAC,CAAC;QACxE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;QAClD,aAAa,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;QACrD,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAClC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wFAAwF,EAAE,KAAK,IAAI,EAAE;QACtG,MAAM,IAAI,GAAG,oBAAoB,EAAE,CAAC;QACpC,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,2BAA2B,CAAC,CAAC,CAAC;QAC5E,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACvB,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,cAAc,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;QACpD,WAAW,CAAC,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QACzC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inspectr/mcplab-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "MCP server that exposes MCPLab evaluation tools — query runs, results, and traces via the Model Context Protocol",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -47,15 +47,15 @@
|
|
|
47
47
|
"mcplab-mcp-server": "dist/index.js"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
|
-
"build": "rm -rf dist && tsc -b --force",
|
|
50
|
+
"build": "rm -rf dist && tsc -b --force && node ../../scripts/copy-mcp-server-skills.mjs",
|
|
51
51
|
"test": "vitest run"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"vitest": "^4.1.8"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@inspectr/mcplab-core": "1.
|
|
58
|
-
"@inspectr/mcplab-reporting": "1.2.
|
|
57
|
+
"@inspectr/mcplab-core": "1.22.0",
|
|
58
|
+
"@inspectr/mcplab-reporting": "1.2.2",
|
|
59
59
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
60
60
|
"yaml": "^2.8.4",
|
|
61
61
|
"zod": "^3.25.0"
|