@codemcp/agentskills-cli 0.0.9 → 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/dist/__tests__/install-agentskills-mcp.test.d.ts +12 -0
- package/dist/__tests__/install-agentskills-mcp.test.d.ts.map +1 -0
- package/dist/__tests__/install-agentskills-mcp.test.js +848 -0
- package/dist/__tests__/install-agentskills-mcp.test.js.map +1 -0
- package/dist/__tests__/install-mcp-validation.test.d.ts +9 -0
- package/dist/__tests__/install-mcp-validation.test.d.ts.map +1 -0
- package/dist/__tests__/install-mcp-validation.test.js +1495 -0
- package/dist/__tests__/install-mcp-validation.test.js.map +1 -0
- package/dist/__tests__/install-with-mcp.test.d.ts +12 -0
- package/dist/__tests__/install-with-mcp.test.d.ts.map +1 -0
- package/dist/__tests__/install-with-mcp.test.js +2258 -0
- package/dist/__tests__/install-with-mcp.test.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +305 -9
- package/dist/commands/install.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,1495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for CLI install command with MCP dependency validation
|
|
3
|
+
*
|
|
4
|
+
* TDD RED PHASE - Tests written before implementation
|
|
5
|
+
* These tests define the expected behavior of MCP dependency validation
|
|
6
|
+
* during skill installation via the CLI install command.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
|
9
|
+
import { installCommand } from "../commands/install.js";
|
|
10
|
+
import { PackageConfigManager, SkillInstaller, MCPConfigManager, MCPDependencyChecker } from "@codemcp/agentskills-core";
|
|
11
|
+
// Mock fs module
|
|
12
|
+
vi.mock("fs", () => ({
|
|
13
|
+
promises: {
|
|
14
|
+
mkdir: vi.fn().mockResolvedValue(undefined),
|
|
15
|
+
access: vi.fn().mockResolvedValue(undefined),
|
|
16
|
+
readFile: vi.fn(),
|
|
17
|
+
writeFile: vi.fn(),
|
|
18
|
+
stat: vi.fn(),
|
|
19
|
+
rm: vi.fn()
|
|
20
|
+
}
|
|
21
|
+
}));
|
|
22
|
+
// Mock all dependencies
|
|
23
|
+
vi.mock("@codemcp/agentskills-core", () => {
|
|
24
|
+
const actualCore = vi.importActual("@codemcp/agentskills-core");
|
|
25
|
+
return {
|
|
26
|
+
...actualCore,
|
|
27
|
+
PackageConfigManager: vi.fn(),
|
|
28
|
+
SkillInstaller: vi.fn(),
|
|
29
|
+
MCPConfigManager: vi.fn(),
|
|
30
|
+
MCPDependencyChecker: vi.fn()
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
vi.mock("ora", () => ({
|
|
34
|
+
default: vi.fn(() => ({
|
|
35
|
+
start: vi.fn().mockReturnThis(),
|
|
36
|
+
stop: vi.fn().mockReturnThis(),
|
|
37
|
+
succeed: vi.fn().mockReturnThis(),
|
|
38
|
+
fail: vi.fn().mockReturnThis()
|
|
39
|
+
}))
|
|
40
|
+
}));
|
|
41
|
+
describe("Install Command - MCP Dependency Validation", () => {
|
|
42
|
+
let mockConfigManager;
|
|
43
|
+
let mockInstaller;
|
|
44
|
+
let mockMCPConfigManager;
|
|
45
|
+
let mockMCPDependencyChecker;
|
|
46
|
+
let consoleLogSpy;
|
|
47
|
+
let consoleErrorSpy;
|
|
48
|
+
let processExitSpy;
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
// Setup mocks
|
|
51
|
+
mockConfigManager = {
|
|
52
|
+
loadConfig: vi.fn()
|
|
53
|
+
};
|
|
54
|
+
mockInstaller = {
|
|
55
|
+
install: vi.fn(),
|
|
56
|
+
generateLockFile: vi.fn(),
|
|
57
|
+
loadInstalledSkills: vi.fn()
|
|
58
|
+
};
|
|
59
|
+
mockMCPConfigManager = {
|
|
60
|
+
isServerConfigured: vi.fn()
|
|
61
|
+
};
|
|
62
|
+
mockMCPDependencyChecker = {
|
|
63
|
+
collectDependencies: vi.fn(),
|
|
64
|
+
checkDependencies: vi.fn()
|
|
65
|
+
};
|
|
66
|
+
vi.mocked(PackageConfigManager).mockImplementation(() => mockConfigManager);
|
|
67
|
+
vi.mocked(SkillInstaller).mockImplementation(() => mockInstaller);
|
|
68
|
+
vi.mocked(MCPConfigManager).mockImplementation(() => mockMCPConfigManager);
|
|
69
|
+
vi.mocked(MCPDependencyChecker).mockImplementation(() => mockMCPDependencyChecker);
|
|
70
|
+
// Setup spies
|
|
71
|
+
consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => { });
|
|
72
|
+
consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => { });
|
|
73
|
+
processExitSpy = vi
|
|
74
|
+
.spyOn(process, "exit")
|
|
75
|
+
.mockImplementation((() => { }));
|
|
76
|
+
// Default mock implementations
|
|
77
|
+
mockConfigManager.loadConfig.mockResolvedValue({
|
|
78
|
+
skills: {},
|
|
79
|
+
config: {
|
|
80
|
+
skillsDirectory: ".agentskills/skills",
|
|
81
|
+
autoDiscover: [],
|
|
82
|
+
maxSkillSize: 5000,
|
|
83
|
+
logLevel: "info"
|
|
84
|
+
},
|
|
85
|
+
source: {
|
|
86
|
+
type: "file",
|
|
87
|
+
path: "/test/package.json"
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
vi.clearAllMocks();
|
|
93
|
+
consoleLogSpy.mockRestore();
|
|
94
|
+
consoleErrorSpy.mockRestore();
|
|
95
|
+
processExitSpy.mockRestore();
|
|
96
|
+
});
|
|
97
|
+
describe("MCP Agent Parameter", () => {
|
|
98
|
+
it("should use explicit --agent parameter for MCP validation", async () => {
|
|
99
|
+
// Setup
|
|
100
|
+
const config = {
|
|
101
|
+
skills: {
|
|
102
|
+
"test-skill": "github:user/repo#v1.0.0"
|
|
103
|
+
},
|
|
104
|
+
config: {
|
|
105
|
+
skillsDirectory: ".agentskills/skills",
|
|
106
|
+
autoDiscover: [],
|
|
107
|
+
maxSkillSize: 5000,
|
|
108
|
+
logLevel: "info"
|
|
109
|
+
},
|
|
110
|
+
source: {
|
|
111
|
+
type: "file",
|
|
112
|
+
path: "/test/package.json"
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
116
|
+
mockMCPConfigManager.isServerConfigured.mockResolvedValue(true);
|
|
117
|
+
mockInstaller.install.mockResolvedValue({
|
|
118
|
+
success: true,
|
|
119
|
+
name: "test-skill",
|
|
120
|
+
spec: "github:user/repo#v1.0.0",
|
|
121
|
+
resolvedVersion: "1.0.0",
|
|
122
|
+
integrity: "sha512-abc123",
|
|
123
|
+
installPath: "/test/.agentskills/skills/test-skill"
|
|
124
|
+
});
|
|
125
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue([]);
|
|
126
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
|
|
127
|
+
// Execute with explicit agent
|
|
128
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
129
|
+
// Verify - should proceed normally
|
|
130
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
131
|
+
});
|
|
132
|
+
it("should proceed without MCP validation if no --agent specified", async () => {
|
|
133
|
+
// Setup
|
|
134
|
+
const config = {
|
|
135
|
+
skills: {
|
|
136
|
+
"test-skill": "github:user/repo#v1.0.0"
|
|
137
|
+
},
|
|
138
|
+
config: {
|
|
139
|
+
skillsDirectory: ".agentskills/skills",
|
|
140
|
+
autoDiscover: [],
|
|
141
|
+
maxSkillSize: 5000,
|
|
142
|
+
logLevel: "info"
|
|
143
|
+
},
|
|
144
|
+
source: {
|
|
145
|
+
type: "file",
|
|
146
|
+
path: "/test/package.json"
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
150
|
+
mockInstaller.install.mockResolvedValue({
|
|
151
|
+
success: true,
|
|
152
|
+
name: "test-skill",
|
|
153
|
+
spec: "github:user/repo#v1.0.0",
|
|
154
|
+
resolvedVersion: "1.0.0",
|
|
155
|
+
integrity: "sha512-abc123",
|
|
156
|
+
installPath: "/test/.agentskills/skills/test-skill"
|
|
157
|
+
});
|
|
158
|
+
mockInstaller.generateLockFile.mockResolvedValue(undefined);
|
|
159
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue([]);
|
|
160
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
|
|
161
|
+
// Execute without agent parameter
|
|
162
|
+
await installCommand({ cwd: "/test" });
|
|
163
|
+
// Verify - should not check MCP configuration (no agent specified)
|
|
164
|
+
expect(mockMCPConfigManager.isServerConfigured).not.toHaveBeenCalled();
|
|
165
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
166
|
+
});
|
|
167
|
+
it("should handle all supported MCP agent types via --agent", async () => {
|
|
168
|
+
const agentMap = {
|
|
169
|
+
claude: "claude-desktop",
|
|
170
|
+
"claude-desktop": "claude-desktop",
|
|
171
|
+
cline: "cline",
|
|
172
|
+
continue: "continue",
|
|
173
|
+
cursor: "cursor",
|
|
174
|
+
junie: "junie",
|
|
175
|
+
zed: "zed",
|
|
176
|
+
vscode: "cline"
|
|
177
|
+
};
|
|
178
|
+
for (const [agentName, clientType] of Object.entries(agentMap)) {
|
|
179
|
+
vi.clearAllMocks();
|
|
180
|
+
const config = {
|
|
181
|
+
skills: {
|
|
182
|
+
"test-skill": "github:user/repo#v1.0.0"
|
|
183
|
+
},
|
|
184
|
+
config: {
|
|
185
|
+
skillsDirectory: ".agentskills/skills",
|
|
186
|
+
autoDiscover: [],
|
|
187
|
+
maxSkillSize: 5000,
|
|
188
|
+
logLevel: "info"
|
|
189
|
+
},
|
|
190
|
+
source: {
|
|
191
|
+
type: "file",
|
|
192
|
+
path: "/test/package.json"
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
196
|
+
mockMCPConfigManager.isServerConfigured.mockResolvedValue(true);
|
|
197
|
+
mockInstaller.install.mockResolvedValue({
|
|
198
|
+
success: true,
|
|
199
|
+
name: "test-skill",
|
|
200
|
+
spec: "github:user/repo#v1.0.0",
|
|
201
|
+
resolvedVersion: "1.0.0",
|
|
202
|
+
integrity: "sha512-abc123",
|
|
203
|
+
installPath: "/test/.agentskills/skills/test-skill"
|
|
204
|
+
});
|
|
205
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue([]);
|
|
206
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
|
|
207
|
+
// Execute with specific agent
|
|
208
|
+
await installCommand({ cwd: "/test", agent: agentName });
|
|
209
|
+
// Verify
|
|
210
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
describe("Dependency Collection", () => {
|
|
215
|
+
it("should collect MCP dependencies from installed skills", async () => {
|
|
216
|
+
// Setup
|
|
217
|
+
const config = {
|
|
218
|
+
skills: {
|
|
219
|
+
"file-manager": "github:user/file-manager#v1.0.0"
|
|
220
|
+
},
|
|
221
|
+
config: {
|
|
222
|
+
skillsDirectory: ".agentskills/skills",
|
|
223
|
+
autoDiscover: [],
|
|
224
|
+
maxSkillSize: 5000,
|
|
225
|
+
logLevel: "info"
|
|
226
|
+
},
|
|
227
|
+
source: {
|
|
228
|
+
type: "file",
|
|
229
|
+
path: "/test/package.json"
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
const installedSkills = [
|
|
233
|
+
{
|
|
234
|
+
metadata: {
|
|
235
|
+
name: "file-manager",
|
|
236
|
+
description: "File management skill",
|
|
237
|
+
requiresMcpServers: [
|
|
238
|
+
{
|
|
239
|
+
name: "filesystem",
|
|
240
|
+
description: "File system access",
|
|
241
|
+
command: "npx",
|
|
242
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
body: "Skill content"
|
|
247
|
+
}
|
|
248
|
+
];
|
|
249
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
250
|
+
mockInstaller.install.mockResolvedValue({
|
|
251
|
+
success: true,
|
|
252
|
+
name: "file-manager",
|
|
253
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
254
|
+
resolvedVersion: "1.0.0",
|
|
255
|
+
integrity: "sha512-abc123",
|
|
256
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
257
|
+
});
|
|
258
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
259
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
|
|
260
|
+
// Execute
|
|
261
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
262
|
+
// Verify
|
|
263
|
+
expect(mockMCPDependencyChecker.collectDependencies).toHaveBeenCalledWith(installedSkills);
|
|
264
|
+
});
|
|
265
|
+
it("should handle skills with no MCP dependencies", async () => {
|
|
266
|
+
// Setup
|
|
267
|
+
const config = {
|
|
268
|
+
skills: {
|
|
269
|
+
"simple-skill": "github:user/simple-skill#v1.0.0"
|
|
270
|
+
},
|
|
271
|
+
config: {
|
|
272
|
+
skillsDirectory: ".agentskills/skills",
|
|
273
|
+
autoDiscover: [],
|
|
274
|
+
maxSkillSize: 5000,
|
|
275
|
+
logLevel: "info"
|
|
276
|
+
},
|
|
277
|
+
source: {
|
|
278
|
+
type: "file",
|
|
279
|
+
path: "/test/package.json"
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
const installedSkills = [
|
|
283
|
+
{
|
|
284
|
+
metadata: {
|
|
285
|
+
name: "simple-skill",
|
|
286
|
+
description: "Simple skill without MCP dependencies"
|
|
287
|
+
},
|
|
288
|
+
body: "Skill content"
|
|
289
|
+
}
|
|
290
|
+
];
|
|
291
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
292
|
+
mockInstaller.install.mockResolvedValue({
|
|
293
|
+
success: true,
|
|
294
|
+
name: "simple-skill",
|
|
295
|
+
spec: "github:user/simple-skill#v1.0.0",
|
|
296
|
+
resolvedVersion: "1.0.0",
|
|
297
|
+
integrity: "sha512-abc123",
|
|
298
|
+
installPath: "/test/.agentskills/skills/simple-skill"
|
|
299
|
+
});
|
|
300
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
301
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
|
|
302
|
+
// Execute
|
|
303
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
304
|
+
// Verify - should still check but find no dependencies
|
|
305
|
+
expect(mockMCPDependencyChecker.collectDependencies).toHaveBeenCalledWith(installedSkills);
|
|
306
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
307
|
+
});
|
|
308
|
+
it("should collect dependencies from multiple skills", async () => {
|
|
309
|
+
// Setup
|
|
310
|
+
const config = {
|
|
311
|
+
skills: {
|
|
312
|
+
"file-manager": "github:user/file-manager#v1.0.0",
|
|
313
|
+
"github-helper": "github:user/github-helper#v1.0.0"
|
|
314
|
+
},
|
|
315
|
+
config: {
|
|
316
|
+
skillsDirectory: ".agentskills/skills",
|
|
317
|
+
autoDiscover: [],
|
|
318
|
+
maxSkillSize: 5000,
|
|
319
|
+
logLevel: "info"
|
|
320
|
+
},
|
|
321
|
+
source: {
|
|
322
|
+
type: "file",
|
|
323
|
+
path: "/test/package.json"
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const installedSkills = [
|
|
327
|
+
{
|
|
328
|
+
metadata: {
|
|
329
|
+
name: "file-manager",
|
|
330
|
+
description: "File management skill",
|
|
331
|
+
requiresMcpServers: [
|
|
332
|
+
{
|
|
333
|
+
name: "filesystem",
|
|
334
|
+
description: "File system access",
|
|
335
|
+
command: "npx",
|
|
336
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
337
|
+
}
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
body: "Skill content"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
metadata: {
|
|
344
|
+
name: "github-helper",
|
|
345
|
+
description: "GitHub operations",
|
|
346
|
+
requiresMcpServers: [
|
|
347
|
+
{
|
|
348
|
+
name: "github",
|
|
349
|
+
description: "GitHub API access",
|
|
350
|
+
command: "npx",
|
|
351
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
352
|
+
}
|
|
353
|
+
]
|
|
354
|
+
},
|
|
355
|
+
body: "Skill content"
|
|
356
|
+
}
|
|
357
|
+
];
|
|
358
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
359
|
+
mockInstaller.install
|
|
360
|
+
.mockResolvedValueOnce({
|
|
361
|
+
success: true,
|
|
362
|
+
name: "file-manager",
|
|
363
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
364
|
+
resolvedVersion: "1.0.0",
|
|
365
|
+
integrity: "sha512-abc123",
|
|
366
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
367
|
+
})
|
|
368
|
+
.mockResolvedValueOnce({
|
|
369
|
+
success: true,
|
|
370
|
+
name: "github-helper",
|
|
371
|
+
spec: "github:user/github-helper#v1.0.0",
|
|
372
|
+
resolvedVersion: "1.0.0",
|
|
373
|
+
integrity: "sha512-def456",
|
|
374
|
+
installPath: "/test/.agentskills/skills/github-helper"
|
|
375
|
+
});
|
|
376
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
377
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
|
|
378
|
+
// Execute
|
|
379
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
380
|
+
// Verify
|
|
381
|
+
expect(mockMCPDependencyChecker.collectDependencies).toHaveBeenCalledWith(installedSkills);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
describe("Dependency Checking", () => {
|
|
385
|
+
it("should check dependencies against MCP client configuration", async () => {
|
|
386
|
+
// Setup
|
|
387
|
+
const config = {
|
|
388
|
+
skills: {
|
|
389
|
+
"file-manager": "github:user/file-manager#v1.0.0"
|
|
390
|
+
},
|
|
391
|
+
config: {
|
|
392
|
+
skillsDirectory: ".agentskills/skills",
|
|
393
|
+
autoDiscover: [],
|
|
394
|
+
maxSkillSize: 5000,
|
|
395
|
+
logLevel: "info"
|
|
396
|
+
},
|
|
397
|
+
source: {
|
|
398
|
+
type: "file",
|
|
399
|
+
path: "/test/package.json"
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
const installedSkills = [
|
|
403
|
+
{
|
|
404
|
+
metadata: {
|
|
405
|
+
name: "file-manager",
|
|
406
|
+
description: "File management skill",
|
|
407
|
+
requiresMcpServers: [
|
|
408
|
+
{
|
|
409
|
+
name: "filesystem",
|
|
410
|
+
description: "File system access",
|
|
411
|
+
command: "npx",
|
|
412
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
body: "Skill content"
|
|
417
|
+
}
|
|
418
|
+
];
|
|
419
|
+
const dependencies = [
|
|
420
|
+
{
|
|
421
|
+
serverName: "filesystem",
|
|
422
|
+
neededBy: ["file-manager"],
|
|
423
|
+
spec: {
|
|
424
|
+
name: "filesystem",
|
|
425
|
+
description: "File system access",
|
|
426
|
+
command: "npx",
|
|
427
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
];
|
|
431
|
+
const checkResult = {
|
|
432
|
+
allConfigured: true,
|
|
433
|
+
missing: [],
|
|
434
|
+
configured: ["filesystem"]
|
|
435
|
+
};
|
|
436
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
437
|
+
mockInstaller.install.mockResolvedValue({
|
|
438
|
+
success: true,
|
|
439
|
+
name: "file-manager",
|
|
440
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
441
|
+
resolvedVersion: "1.0.0",
|
|
442
|
+
integrity: "sha512-abc123",
|
|
443
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
444
|
+
});
|
|
445
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
446
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
447
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
448
|
+
// Execute
|
|
449
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
450
|
+
// Verify
|
|
451
|
+
expect(mockMCPDependencyChecker.checkDependencies).toHaveBeenCalledWith("claude-desktop", dependencies, expect.any(Object), // MCPConfigManager instance
|
|
452
|
+
"/test");
|
|
453
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
454
|
+
});
|
|
455
|
+
it("should skip dependency checking if no dependencies are found", async () => {
|
|
456
|
+
// Setup
|
|
457
|
+
const config = {
|
|
458
|
+
skills: {
|
|
459
|
+
"simple-skill": "github:user/simple-skill#v1.0.0"
|
|
460
|
+
},
|
|
461
|
+
config: {
|
|
462
|
+
skillsDirectory: ".agentskills/skills",
|
|
463
|
+
autoDiscover: [],
|
|
464
|
+
maxSkillSize: 5000,
|
|
465
|
+
logLevel: "info"
|
|
466
|
+
},
|
|
467
|
+
source: {
|
|
468
|
+
type: "file",
|
|
469
|
+
path: "/test/package.json"
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
const installedSkills = [
|
|
473
|
+
{
|
|
474
|
+
metadata: {
|
|
475
|
+
name: "simple-skill",
|
|
476
|
+
description: "Simple skill"
|
|
477
|
+
},
|
|
478
|
+
body: "Skill content"
|
|
479
|
+
}
|
|
480
|
+
];
|
|
481
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
482
|
+
mockInstaller.install.mockResolvedValue({
|
|
483
|
+
success: true,
|
|
484
|
+
name: "simple-skill",
|
|
485
|
+
spec: "github:user/simple-skill#v1.0.0",
|
|
486
|
+
resolvedVersion: "1.0.0",
|
|
487
|
+
integrity: "sha512-abc123",
|
|
488
|
+
installPath: "/test/.agentskills/skills/simple-skill"
|
|
489
|
+
});
|
|
490
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
491
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
|
|
492
|
+
// Execute
|
|
493
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
494
|
+
// Verify
|
|
495
|
+
expect(mockMCPDependencyChecker.checkDependencies).not.toHaveBeenCalled();
|
|
496
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
describe("Success Cases - All Dependencies Configured", () => {
|
|
500
|
+
it("should succeed when all MCP dependencies are configured", async () => {
|
|
501
|
+
// Setup
|
|
502
|
+
const config = {
|
|
503
|
+
skills: {
|
|
504
|
+
"file-manager": "github:user/file-manager#v1.0.0"
|
|
505
|
+
},
|
|
506
|
+
config: {
|
|
507
|
+
skillsDirectory: ".agentskills/skills",
|
|
508
|
+
autoDiscover: [],
|
|
509
|
+
maxSkillSize: 5000,
|
|
510
|
+
logLevel: "info"
|
|
511
|
+
},
|
|
512
|
+
source: {
|
|
513
|
+
type: "file",
|
|
514
|
+
path: "/test/package.json"
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
const installedSkills = [
|
|
518
|
+
{
|
|
519
|
+
metadata: {
|
|
520
|
+
name: "file-manager",
|
|
521
|
+
description: "File management skill",
|
|
522
|
+
requiresMcpServers: [
|
|
523
|
+
{
|
|
524
|
+
name: "filesystem",
|
|
525
|
+
description: "File system access",
|
|
526
|
+
command: "npx",
|
|
527
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
528
|
+
}
|
|
529
|
+
]
|
|
530
|
+
},
|
|
531
|
+
body: "Skill content"
|
|
532
|
+
}
|
|
533
|
+
];
|
|
534
|
+
const dependencies = [
|
|
535
|
+
{
|
|
536
|
+
serverName: "filesystem",
|
|
537
|
+
neededBy: ["file-manager"],
|
|
538
|
+
spec: {
|
|
539
|
+
name: "filesystem",
|
|
540
|
+
description: "File system access",
|
|
541
|
+
command: "npx",
|
|
542
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
];
|
|
546
|
+
const checkResult = {
|
|
547
|
+
allConfigured: true,
|
|
548
|
+
missing: [],
|
|
549
|
+
configured: ["filesystem"]
|
|
550
|
+
};
|
|
551
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
552
|
+
mockInstaller.install.mockResolvedValue({
|
|
553
|
+
success: true,
|
|
554
|
+
name: "file-manager",
|
|
555
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
556
|
+
resolvedVersion: "1.0.0",
|
|
557
|
+
integrity: "sha512-abc123",
|
|
558
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
559
|
+
});
|
|
560
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
561
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
562
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
563
|
+
// Execute
|
|
564
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
565
|
+
// Verify
|
|
566
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
567
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Successfully installed"));
|
|
568
|
+
});
|
|
569
|
+
it("should succeed with multiple skills when all dependencies are configured", async () => {
|
|
570
|
+
// Setup
|
|
571
|
+
const config = {
|
|
572
|
+
skills: {
|
|
573
|
+
"file-manager": "github:user/file-manager#v1.0.0",
|
|
574
|
+
"github-helper": "github:user/github-helper#v1.0.0"
|
|
575
|
+
},
|
|
576
|
+
config: {
|
|
577
|
+
skillsDirectory: ".agentskills/skills",
|
|
578
|
+
autoDiscover: [],
|
|
579
|
+
maxSkillSize: 5000,
|
|
580
|
+
logLevel: "info"
|
|
581
|
+
},
|
|
582
|
+
source: {
|
|
583
|
+
type: "file",
|
|
584
|
+
path: "/test/package.json"
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
const installedSkills = [
|
|
588
|
+
{
|
|
589
|
+
metadata: {
|
|
590
|
+
name: "file-manager",
|
|
591
|
+
description: "File management skill",
|
|
592
|
+
requiresMcpServers: [
|
|
593
|
+
{
|
|
594
|
+
name: "filesystem",
|
|
595
|
+
description: "File system access",
|
|
596
|
+
command: "npx",
|
|
597
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
598
|
+
}
|
|
599
|
+
]
|
|
600
|
+
},
|
|
601
|
+
body: "Skill content"
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
metadata: {
|
|
605
|
+
name: "github-helper",
|
|
606
|
+
description: "GitHub operations",
|
|
607
|
+
requiresMcpServers: [
|
|
608
|
+
{
|
|
609
|
+
name: "github",
|
|
610
|
+
description: "GitHub API access",
|
|
611
|
+
command: "npx",
|
|
612
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
613
|
+
}
|
|
614
|
+
]
|
|
615
|
+
},
|
|
616
|
+
body: "Skill content"
|
|
617
|
+
}
|
|
618
|
+
];
|
|
619
|
+
const dependencies = [
|
|
620
|
+
{
|
|
621
|
+
serverName: "filesystem",
|
|
622
|
+
neededBy: ["file-manager"],
|
|
623
|
+
spec: {
|
|
624
|
+
name: "filesystem",
|
|
625
|
+
description: "File system access",
|
|
626
|
+
command: "npx",
|
|
627
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
serverName: "github",
|
|
632
|
+
neededBy: ["github-helper"],
|
|
633
|
+
spec: {
|
|
634
|
+
name: "github",
|
|
635
|
+
description: "GitHub API access",
|
|
636
|
+
command: "npx",
|
|
637
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
];
|
|
641
|
+
const checkResult = {
|
|
642
|
+
allConfigured: true,
|
|
643
|
+
missing: [],
|
|
644
|
+
configured: ["filesystem", "github"]
|
|
645
|
+
};
|
|
646
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
647
|
+
mockInstaller.install
|
|
648
|
+
.mockResolvedValueOnce({
|
|
649
|
+
success: true,
|
|
650
|
+
name: "file-manager",
|
|
651
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
652
|
+
resolvedVersion: "1.0.0",
|
|
653
|
+
integrity: "sha512-abc123",
|
|
654
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
655
|
+
})
|
|
656
|
+
.mockResolvedValueOnce({
|
|
657
|
+
success: true,
|
|
658
|
+
name: "github-helper",
|
|
659
|
+
spec: "github:user/github-helper#v1.0.0",
|
|
660
|
+
resolvedVersion: "1.0.0",
|
|
661
|
+
integrity: "sha512-def456",
|
|
662
|
+
installPath: "/test/.agentskills/skills/github-helper"
|
|
663
|
+
});
|
|
664
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
665
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
666
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
667
|
+
// Execute
|
|
668
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
669
|
+
// Verify
|
|
670
|
+
expect(processExitSpy).toHaveBeenCalledWith(0);
|
|
671
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(expect.stringContaining("Successfully installed 2 skills"));
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
describe("Failure Cases - Missing Dependencies", () => {
|
|
675
|
+
it("should fail when MCP dependencies are missing", async () => {
|
|
676
|
+
// Setup
|
|
677
|
+
const config = {
|
|
678
|
+
skills: {
|
|
679
|
+
"file-manager": "github:user/file-manager#v1.0.0"
|
|
680
|
+
},
|
|
681
|
+
config: {
|
|
682
|
+
skillsDirectory: ".agentskills/skills",
|
|
683
|
+
autoDiscover: [],
|
|
684
|
+
maxSkillSize: 5000,
|
|
685
|
+
logLevel: "info"
|
|
686
|
+
},
|
|
687
|
+
source: {
|
|
688
|
+
type: "file",
|
|
689
|
+
path: "/test/package.json"
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
const installedSkills = [
|
|
693
|
+
{
|
|
694
|
+
metadata: {
|
|
695
|
+
name: "file-manager",
|
|
696
|
+
description: "File management skill",
|
|
697
|
+
requiresMcpServers: [
|
|
698
|
+
{
|
|
699
|
+
name: "filesystem",
|
|
700
|
+
description: "File system access",
|
|
701
|
+
command: "npx",
|
|
702
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
703
|
+
}
|
|
704
|
+
]
|
|
705
|
+
},
|
|
706
|
+
body: "Skill content"
|
|
707
|
+
}
|
|
708
|
+
];
|
|
709
|
+
const dependencies = [
|
|
710
|
+
{
|
|
711
|
+
serverName: "filesystem",
|
|
712
|
+
neededBy: ["file-manager"],
|
|
713
|
+
spec: {
|
|
714
|
+
name: "filesystem",
|
|
715
|
+
description: "File system access",
|
|
716
|
+
command: "npx",
|
|
717
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
];
|
|
721
|
+
const checkResult = {
|
|
722
|
+
allConfigured: false,
|
|
723
|
+
missing: dependencies,
|
|
724
|
+
configured: []
|
|
725
|
+
};
|
|
726
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
727
|
+
mockInstaller.install.mockResolvedValue({
|
|
728
|
+
success: true,
|
|
729
|
+
name: "file-manager",
|
|
730
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
731
|
+
resolvedVersion: "1.0.0",
|
|
732
|
+
integrity: "sha512-abc123",
|
|
733
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
734
|
+
});
|
|
735
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
736
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
737
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
738
|
+
// Execute
|
|
739
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
740
|
+
// Verify
|
|
741
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
742
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Missing MCP server dependencies"));
|
|
743
|
+
});
|
|
744
|
+
it("should display helpful error message with missing server details", async () => {
|
|
745
|
+
// Setup
|
|
746
|
+
const config = {
|
|
747
|
+
skills: {
|
|
748
|
+
"file-manager": "github:user/file-manager#v1.0.0"
|
|
749
|
+
},
|
|
750
|
+
config: {
|
|
751
|
+
skillsDirectory: ".agentskills/skills",
|
|
752
|
+
autoDiscover: [],
|
|
753
|
+
maxSkillSize: 5000,
|
|
754
|
+
logLevel: "info"
|
|
755
|
+
},
|
|
756
|
+
source: {
|
|
757
|
+
type: "file",
|
|
758
|
+
path: "/test/package.json"
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
const installedSkills = [
|
|
762
|
+
{
|
|
763
|
+
metadata: {
|
|
764
|
+
name: "file-manager",
|
|
765
|
+
description: "File management skill",
|
|
766
|
+
requiresMcpServers: [
|
|
767
|
+
{
|
|
768
|
+
name: "filesystem",
|
|
769
|
+
description: "File system access",
|
|
770
|
+
command: "npx",
|
|
771
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
772
|
+
}
|
|
773
|
+
]
|
|
774
|
+
},
|
|
775
|
+
body: "Skill content"
|
|
776
|
+
}
|
|
777
|
+
];
|
|
778
|
+
const dependencies = [
|
|
779
|
+
{
|
|
780
|
+
serverName: "filesystem",
|
|
781
|
+
neededBy: ["file-manager"],
|
|
782
|
+
spec: {
|
|
783
|
+
name: "filesystem",
|
|
784
|
+
description: "File system access",
|
|
785
|
+
command: "npx",
|
|
786
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
];
|
|
790
|
+
const checkResult = {
|
|
791
|
+
allConfigured: false,
|
|
792
|
+
missing: dependencies,
|
|
793
|
+
configured: []
|
|
794
|
+
};
|
|
795
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
796
|
+
mockInstaller.install.mockResolvedValue({
|
|
797
|
+
success: true,
|
|
798
|
+
name: "file-manager",
|
|
799
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
800
|
+
resolvedVersion: "1.0.0",
|
|
801
|
+
integrity: "sha512-abc123",
|
|
802
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
803
|
+
});
|
|
804
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
805
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
806
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
807
|
+
// Execute
|
|
808
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
809
|
+
// Verify error message contains server name
|
|
810
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("filesystem"));
|
|
811
|
+
});
|
|
812
|
+
it("should show which skills need each missing server", async () => {
|
|
813
|
+
// Setup
|
|
814
|
+
const config = {
|
|
815
|
+
skills: {
|
|
816
|
+
"file-manager": "github:user/file-manager#v1.0.0",
|
|
817
|
+
"file-reader": "github:user/file-reader#v1.0.0"
|
|
818
|
+
},
|
|
819
|
+
config: {
|
|
820
|
+
skillsDirectory: ".agentskills/skills",
|
|
821
|
+
autoDiscover: [],
|
|
822
|
+
maxSkillSize: 5000,
|
|
823
|
+
logLevel: "info"
|
|
824
|
+
},
|
|
825
|
+
source: {
|
|
826
|
+
type: "file",
|
|
827
|
+
path: "/test/package.json"
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
const installedSkills = [
|
|
831
|
+
{
|
|
832
|
+
metadata: {
|
|
833
|
+
name: "file-manager",
|
|
834
|
+
description: "File management skill",
|
|
835
|
+
requiresMcpServers: [
|
|
836
|
+
{
|
|
837
|
+
name: "filesystem",
|
|
838
|
+
description: "File system access",
|
|
839
|
+
command: "npx",
|
|
840
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
841
|
+
}
|
|
842
|
+
]
|
|
843
|
+
},
|
|
844
|
+
body: "Skill content"
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
metadata: {
|
|
848
|
+
name: "file-reader",
|
|
849
|
+
description: "File reading skill",
|
|
850
|
+
requiresMcpServers: [
|
|
851
|
+
{
|
|
852
|
+
name: "filesystem",
|
|
853
|
+
description: "File system access",
|
|
854
|
+
command: "npx",
|
|
855
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
856
|
+
}
|
|
857
|
+
]
|
|
858
|
+
},
|
|
859
|
+
body: "Skill content"
|
|
860
|
+
}
|
|
861
|
+
];
|
|
862
|
+
const dependencies = [
|
|
863
|
+
{
|
|
864
|
+
serverName: "filesystem",
|
|
865
|
+
neededBy: ["file-manager", "file-reader"],
|
|
866
|
+
spec: {
|
|
867
|
+
name: "filesystem",
|
|
868
|
+
description: "File system access",
|
|
869
|
+
command: "npx",
|
|
870
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
];
|
|
874
|
+
const checkResult = {
|
|
875
|
+
allConfigured: false,
|
|
876
|
+
missing: dependencies,
|
|
877
|
+
configured: []
|
|
878
|
+
};
|
|
879
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
880
|
+
mockInstaller.install
|
|
881
|
+
.mockResolvedValueOnce({
|
|
882
|
+
success: true,
|
|
883
|
+
name: "file-manager",
|
|
884
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
885
|
+
resolvedVersion: "1.0.0",
|
|
886
|
+
integrity: "sha512-abc123",
|
|
887
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
888
|
+
})
|
|
889
|
+
.mockResolvedValueOnce({
|
|
890
|
+
success: true,
|
|
891
|
+
name: "file-reader",
|
|
892
|
+
spec: "github:user/file-reader#v1.0.0",
|
|
893
|
+
resolvedVersion: "1.0.0",
|
|
894
|
+
integrity: "sha512-def456",
|
|
895
|
+
installPath: "/test/.agentskills/skills/file-reader"
|
|
896
|
+
});
|
|
897
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
898
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
899
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
900
|
+
// Execute
|
|
901
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
902
|
+
// Verify error message mentions both skills
|
|
903
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("file-manager"));
|
|
904
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("file-reader"));
|
|
905
|
+
});
|
|
906
|
+
it("should suggest using --with-mcp flag for auto-install", async () => {
|
|
907
|
+
// Setup
|
|
908
|
+
const config = {
|
|
909
|
+
skills: {
|
|
910
|
+
"file-manager": "github:user/file-manager#v1.0.0"
|
|
911
|
+
},
|
|
912
|
+
config: {
|
|
913
|
+
skillsDirectory: ".agentskills/skills",
|
|
914
|
+
autoDiscover: [],
|
|
915
|
+
maxSkillSize: 5000,
|
|
916
|
+
logLevel: "info"
|
|
917
|
+
},
|
|
918
|
+
source: {
|
|
919
|
+
type: "file",
|
|
920
|
+
path: "/test/package.json"
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
const installedSkills = [
|
|
924
|
+
{
|
|
925
|
+
metadata: {
|
|
926
|
+
name: "file-manager",
|
|
927
|
+
description: "File management skill",
|
|
928
|
+
requiresMcpServers: [
|
|
929
|
+
{
|
|
930
|
+
name: "filesystem",
|
|
931
|
+
description: "File system access",
|
|
932
|
+
command: "npx",
|
|
933
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
934
|
+
}
|
|
935
|
+
]
|
|
936
|
+
},
|
|
937
|
+
body: "Skill content"
|
|
938
|
+
}
|
|
939
|
+
];
|
|
940
|
+
const dependencies = [
|
|
941
|
+
{
|
|
942
|
+
serverName: "filesystem",
|
|
943
|
+
neededBy: ["file-manager"],
|
|
944
|
+
spec: {
|
|
945
|
+
name: "filesystem",
|
|
946
|
+
description: "File system access",
|
|
947
|
+
command: "npx",
|
|
948
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
];
|
|
952
|
+
const checkResult = {
|
|
953
|
+
allConfigured: false,
|
|
954
|
+
missing: dependencies,
|
|
955
|
+
configured: []
|
|
956
|
+
};
|
|
957
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
958
|
+
mockInstaller.install.mockResolvedValue({
|
|
959
|
+
success: true,
|
|
960
|
+
name: "file-manager",
|
|
961
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
962
|
+
resolvedVersion: "1.0.0",
|
|
963
|
+
integrity: "sha512-abc123",
|
|
964
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
965
|
+
});
|
|
966
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
967
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
968
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
969
|
+
// Execute
|
|
970
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
971
|
+
// Verify suggestion for --with-mcp flag
|
|
972
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--with-mcp"));
|
|
973
|
+
});
|
|
974
|
+
it("should fail with multiple missing dependencies", async () => {
|
|
975
|
+
// Setup
|
|
976
|
+
const config = {
|
|
977
|
+
skills: {
|
|
978
|
+
"devops-tool": "github:user/devops-tool#v1.0.0"
|
|
979
|
+
},
|
|
980
|
+
config: {
|
|
981
|
+
skillsDirectory: ".agentskills/skills",
|
|
982
|
+
autoDiscover: [],
|
|
983
|
+
maxSkillSize: 5000,
|
|
984
|
+
logLevel: "info"
|
|
985
|
+
},
|
|
986
|
+
source: {
|
|
987
|
+
type: "file",
|
|
988
|
+
path: "/test/package.json"
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
const installedSkills = [
|
|
992
|
+
{
|
|
993
|
+
metadata: {
|
|
994
|
+
name: "devops-tool",
|
|
995
|
+
description: "DevOps operations",
|
|
996
|
+
requiresMcpServers: [
|
|
997
|
+
{
|
|
998
|
+
name: "filesystem",
|
|
999
|
+
description: "File system access",
|
|
1000
|
+
command: "npx",
|
|
1001
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
name: "github",
|
|
1005
|
+
description: "GitHub API access",
|
|
1006
|
+
command: "npx",
|
|
1007
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
name: "slack",
|
|
1011
|
+
description: "Slack API access",
|
|
1012
|
+
command: "npx",
|
|
1013
|
+
args: ["-y", "@modelcontextprotocol/server-slack"]
|
|
1014
|
+
}
|
|
1015
|
+
]
|
|
1016
|
+
},
|
|
1017
|
+
body: "Skill content"
|
|
1018
|
+
}
|
|
1019
|
+
];
|
|
1020
|
+
const dependencies = [
|
|
1021
|
+
{
|
|
1022
|
+
serverName: "filesystem",
|
|
1023
|
+
neededBy: ["devops-tool"],
|
|
1024
|
+
spec: {
|
|
1025
|
+
name: "filesystem",
|
|
1026
|
+
description: "File system access",
|
|
1027
|
+
command: "npx",
|
|
1028
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
{
|
|
1032
|
+
serverName: "github",
|
|
1033
|
+
neededBy: ["devops-tool"],
|
|
1034
|
+
spec: {
|
|
1035
|
+
name: "github",
|
|
1036
|
+
description: "GitHub API access",
|
|
1037
|
+
command: "npx",
|
|
1038
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
{
|
|
1042
|
+
serverName: "slack",
|
|
1043
|
+
neededBy: ["devops-tool"],
|
|
1044
|
+
spec: {
|
|
1045
|
+
name: "slack",
|
|
1046
|
+
description: "Slack API access",
|
|
1047
|
+
command: "npx",
|
|
1048
|
+
args: ["-y", "@modelcontextprotocol/server-slack"]
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
];
|
|
1052
|
+
const checkResult = {
|
|
1053
|
+
allConfigured: false,
|
|
1054
|
+
missing: dependencies,
|
|
1055
|
+
configured: []
|
|
1056
|
+
};
|
|
1057
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
1058
|
+
mockInstaller.install.mockResolvedValue({
|
|
1059
|
+
success: true,
|
|
1060
|
+
name: "devops-tool",
|
|
1061
|
+
spec: "github:user/devops-tool#v1.0.0",
|
|
1062
|
+
resolvedVersion: "1.0.0",
|
|
1063
|
+
integrity: "sha512-abc123",
|
|
1064
|
+
installPath: "/test/.agentskills/skills/devops-tool"
|
|
1065
|
+
});
|
|
1066
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
1067
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
1068
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
1069
|
+
// Execute
|
|
1070
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
1071
|
+
// Verify
|
|
1072
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
1073
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("filesystem"));
|
|
1074
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("github"));
|
|
1075
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("slack"));
|
|
1076
|
+
});
|
|
1077
|
+
it("should fail with partially missing dependencies", async () => {
|
|
1078
|
+
// Setup
|
|
1079
|
+
const config = {
|
|
1080
|
+
skills: {
|
|
1081
|
+
"file-manager": "github:user/file-manager#v1.0.0",
|
|
1082
|
+
"github-helper": "github:user/github-helper#v1.0.0"
|
|
1083
|
+
},
|
|
1084
|
+
config: {
|
|
1085
|
+
skillsDirectory: ".agentskills/skills",
|
|
1086
|
+
autoDiscover: [],
|
|
1087
|
+
maxSkillSize: 5000,
|
|
1088
|
+
logLevel: "info"
|
|
1089
|
+
},
|
|
1090
|
+
source: {
|
|
1091
|
+
type: "file",
|
|
1092
|
+
path: "/test/package.json"
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
const installedSkills = [
|
|
1096
|
+
{
|
|
1097
|
+
metadata: {
|
|
1098
|
+
name: "file-manager",
|
|
1099
|
+
description: "File management skill",
|
|
1100
|
+
requiresMcpServers: [
|
|
1101
|
+
{
|
|
1102
|
+
name: "filesystem",
|
|
1103
|
+
description: "File system access",
|
|
1104
|
+
command: "npx",
|
|
1105
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
1106
|
+
}
|
|
1107
|
+
]
|
|
1108
|
+
},
|
|
1109
|
+
body: "Skill content"
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
metadata: {
|
|
1113
|
+
name: "github-helper",
|
|
1114
|
+
description: "GitHub operations",
|
|
1115
|
+
requiresMcpServers: [
|
|
1116
|
+
{
|
|
1117
|
+
name: "github",
|
|
1118
|
+
description: "GitHub API access",
|
|
1119
|
+
command: "npx",
|
|
1120
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
1121
|
+
}
|
|
1122
|
+
]
|
|
1123
|
+
},
|
|
1124
|
+
body: "Skill content"
|
|
1125
|
+
}
|
|
1126
|
+
];
|
|
1127
|
+
const dependencies = [
|
|
1128
|
+
{
|
|
1129
|
+
serverName: "filesystem",
|
|
1130
|
+
neededBy: ["file-manager"],
|
|
1131
|
+
spec: {
|
|
1132
|
+
name: "filesystem",
|
|
1133
|
+
description: "File system access",
|
|
1134
|
+
command: "npx",
|
|
1135
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
serverName: "github",
|
|
1140
|
+
neededBy: ["github-helper"],
|
|
1141
|
+
spec: {
|
|
1142
|
+
name: "github",
|
|
1143
|
+
description: "GitHub API access",
|
|
1144
|
+
command: "npx",
|
|
1145
|
+
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
];
|
|
1149
|
+
// filesystem is configured, github is missing
|
|
1150
|
+
const checkResult = {
|
|
1151
|
+
allConfigured: false,
|
|
1152
|
+
missing: [dependencies[1]], // only github is missing
|
|
1153
|
+
configured: ["filesystem"]
|
|
1154
|
+
};
|
|
1155
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
1156
|
+
mockInstaller.install
|
|
1157
|
+
.mockResolvedValueOnce({
|
|
1158
|
+
success: true,
|
|
1159
|
+
name: "file-manager",
|
|
1160
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
1161
|
+
resolvedVersion: "1.0.0",
|
|
1162
|
+
integrity: "sha512-abc123",
|
|
1163
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
1164
|
+
})
|
|
1165
|
+
.mockResolvedValueOnce({
|
|
1166
|
+
success: true,
|
|
1167
|
+
name: "github-helper",
|
|
1168
|
+
spec: "github:user/github-helper#v1.0.0",
|
|
1169
|
+
resolvedVersion: "1.0.0",
|
|
1170
|
+
integrity: "sha512-def456",
|
|
1171
|
+
installPath: "/test/.agentskills/skills/github-helper"
|
|
1172
|
+
});
|
|
1173
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
1174
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
1175
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
1176
|
+
// Execute
|
|
1177
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
1178
|
+
// Verify
|
|
1179
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
1180
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("github"));
|
|
1181
|
+
// Should not mention filesystem since it's configured
|
|
1182
|
+
const calls = consoleErrorSpy.mock.calls.map((call) => call[0]).join(" ");
|
|
1183
|
+
expect(calls).not.toContain("filesystem");
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1186
|
+
describe("Edge Cases", () => {
|
|
1187
|
+
it("should handle error during dependency collection", async () => {
|
|
1188
|
+
// Setup
|
|
1189
|
+
const config = {
|
|
1190
|
+
skills: {
|
|
1191
|
+
"test-skill": "github:user/repo#v1.0.0"
|
|
1192
|
+
},
|
|
1193
|
+
config: {
|
|
1194
|
+
skillsDirectory: ".agentskills/skills",
|
|
1195
|
+
autoDiscover: [],
|
|
1196
|
+
maxSkillSize: 5000,
|
|
1197
|
+
logLevel: "info"
|
|
1198
|
+
},
|
|
1199
|
+
source: {
|
|
1200
|
+
type: "file",
|
|
1201
|
+
path: "/test/package.json"
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
1205
|
+
mockInstaller.install.mockResolvedValue({
|
|
1206
|
+
success: true,
|
|
1207
|
+
name: "test-skill",
|
|
1208
|
+
spec: "github:user/repo#v1.0.0",
|
|
1209
|
+
resolvedVersion: "1.0.0",
|
|
1210
|
+
integrity: "sha512-abc123",
|
|
1211
|
+
installPath: "/test/.agentskills/skills/test-skill"
|
|
1212
|
+
});
|
|
1213
|
+
mockInstaller.loadInstalledSkills.mockRejectedValue(new Error("Failed to load skills"));
|
|
1214
|
+
// Execute
|
|
1215
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
1216
|
+
// Verify - should handle gracefully
|
|
1217
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
1218
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
1219
|
+
});
|
|
1220
|
+
it("should handle error during dependency checking", async () => {
|
|
1221
|
+
// Setup
|
|
1222
|
+
const config = {
|
|
1223
|
+
skills: {
|
|
1224
|
+
"test-skill": "github:user/repo#v1.0.0"
|
|
1225
|
+
},
|
|
1226
|
+
config: {
|
|
1227
|
+
skillsDirectory: ".agentskills/skills",
|
|
1228
|
+
autoDiscover: [],
|
|
1229
|
+
maxSkillSize: 5000,
|
|
1230
|
+
logLevel: "info"
|
|
1231
|
+
},
|
|
1232
|
+
source: {
|
|
1233
|
+
type: "file",
|
|
1234
|
+
path: "/test/package.json"
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
const installedSkills = [
|
|
1238
|
+
{
|
|
1239
|
+
metadata: {
|
|
1240
|
+
name: "test-skill",
|
|
1241
|
+
description: "Test skill",
|
|
1242
|
+
requiresMcpServers: [
|
|
1243
|
+
{
|
|
1244
|
+
name: "filesystem",
|
|
1245
|
+
description: "File system access",
|
|
1246
|
+
command: "npx",
|
|
1247
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
1248
|
+
}
|
|
1249
|
+
]
|
|
1250
|
+
},
|
|
1251
|
+
body: "Skill content"
|
|
1252
|
+
}
|
|
1253
|
+
];
|
|
1254
|
+
const dependencies = [
|
|
1255
|
+
{
|
|
1256
|
+
serverName: "filesystem",
|
|
1257
|
+
neededBy: ["test-skill"],
|
|
1258
|
+
spec: {
|
|
1259
|
+
name: "filesystem",
|
|
1260
|
+
description: "File system access",
|
|
1261
|
+
command: "npx",
|
|
1262
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
];
|
|
1266
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
1267
|
+
mockInstaller.install.mockResolvedValue({
|
|
1268
|
+
success: true,
|
|
1269
|
+
name: "test-skill",
|
|
1270
|
+
spec: "github:user/repo#v1.0.0",
|
|
1271
|
+
resolvedVersion: "1.0.0",
|
|
1272
|
+
integrity: "sha512-abc123",
|
|
1273
|
+
installPath: "/test/.agentskills/skills/test-skill"
|
|
1274
|
+
});
|
|
1275
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
1276
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
1277
|
+
mockMCPDependencyChecker.checkDependencies.mockRejectedValue(new Error("Failed to check dependencies"));
|
|
1278
|
+
// Execute
|
|
1279
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
1280
|
+
// Verify - should handle gracefully
|
|
1281
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
1282
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
1283
|
+
});
|
|
1284
|
+
it("should handle skills that fail to install before MCP validation", async () => {
|
|
1285
|
+
// Setup
|
|
1286
|
+
const config = {
|
|
1287
|
+
skills: {
|
|
1288
|
+
"failing-skill": "github:user/failing-skill#v1.0.0",
|
|
1289
|
+
"working-skill": "github:user/working-skill#v1.0.0"
|
|
1290
|
+
},
|
|
1291
|
+
config: {
|
|
1292
|
+
skillsDirectory: ".agentskills/skills",
|
|
1293
|
+
autoDiscover: [],
|
|
1294
|
+
maxSkillSize: 5000,
|
|
1295
|
+
logLevel: "info"
|
|
1296
|
+
},
|
|
1297
|
+
source: {
|
|
1298
|
+
type: "file",
|
|
1299
|
+
path: "/test/package.json"
|
|
1300
|
+
}
|
|
1301
|
+
};
|
|
1302
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
1303
|
+
mockInstaller.install
|
|
1304
|
+
.mockResolvedValueOnce({
|
|
1305
|
+
success: false,
|
|
1306
|
+
name: "failing-skill",
|
|
1307
|
+
spec: "github:user/failing-skill#v1.0.0",
|
|
1308
|
+
error: {
|
|
1309
|
+
code: "INSTALL_FAILED",
|
|
1310
|
+
message: "Failed to download"
|
|
1311
|
+
}
|
|
1312
|
+
})
|
|
1313
|
+
.mockResolvedValueOnce({
|
|
1314
|
+
success: true,
|
|
1315
|
+
name: "working-skill",
|
|
1316
|
+
spec: "github:user/working-skill#v1.0.0",
|
|
1317
|
+
resolvedVersion: "1.0.0",
|
|
1318
|
+
integrity: "sha512-abc123",
|
|
1319
|
+
installPath: "/test/.agentskills/skills/working-skill"
|
|
1320
|
+
});
|
|
1321
|
+
const installedSkills = [
|
|
1322
|
+
{
|
|
1323
|
+
metadata: {
|
|
1324
|
+
name: "working-skill",
|
|
1325
|
+
description: "Working skill"
|
|
1326
|
+
},
|
|
1327
|
+
body: "Skill content"
|
|
1328
|
+
}
|
|
1329
|
+
];
|
|
1330
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
1331
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue([]);
|
|
1332
|
+
// Execute
|
|
1333
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
1334
|
+
// Verify - should still run MCP validation on successful installs
|
|
1335
|
+
expect(mockMCPDependencyChecker.collectDependencies).toHaveBeenCalledWith(installedSkills);
|
|
1336
|
+
});
|
|
1337
|
+
});
|
|
1338
|
+
describe("Error Message Formatting", () => {
|
|
1339
|
+
it("should format error message with proper structure", async () => {
|
|
1340
|
+
// Setup
|
|
1341
|
+
const config = {
|
|
1342
|
+
skills: {
|
|
1343
|
+
"file-manager": "github:user/file-manager#v1.0.0"
|
|
1344
|
+
},
|
|
1345
|
+
config: {
|
|
1346
|
+
skillsDirectory: ".agentskills/skills",
|
|
1347
|
+
autoDiscover: [],
|
|
1348
|
+
maxSkillSize: 5000,
|
|
1349
|
+
logLevel: "info"
|
|
1350
|
+
},
|
|
1351
|
+
source: {
|
|
1352
|
+
type: "file",
|
|
1353
|
+
path: "/test/package.json"
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
const installedSkills = [
|
|
1357
|
+
{
|
|
1358
|
+
metadata: {
|
|
1359
|
+
name: "file-manager",
|
|
1360
|
+
description: "File management skill",
|
|
1361
|
+
requiresMcpServers: [
|
|
1362
|
+
{
|
|
1363
|
+
name: "filesystem",
|
|
1364
|
+
description: "File system access",
|
|
1365
|
+
command: "npx",
|
|
1366
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
1367
|
+
}
|
|
1368
|
+
]
|
|
1369
|
+
},
|
|
1370
|
+
body: "Skill content"
|
|
1371
|
+
}
|
|
1372
|
+
];
|
|
1373
|
+
const dependencies = [
|
|
1374
|
+
{
|
|
1375
|
+
serverName: "filesystem",
|
|
1376
|
+
neededBy: ["file-manager"],
|
|
1377
|
+
spec: {
|
|
1378
|
+
name: "filesystem",
|
|
1379
|
+
description: "File system access",
|
|
1380
|
+
command: "npx",
|
|
1381
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
];
|
|
1385
|
+
const checkResult = {
|
|
1386
|
+
allConfigured: false,
|
|
1387
|
+
missing: dependencies,
|
|
1388
|
+
configured: []
|
|
1389
|
+
};
|
|
1390
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
1391
|
+
mockInstaller.install.mockResolvedValue({
|
|
1392
|
+
success: true,
|
|
1393
|
+
name: "file-manager",
|
|
1394
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
1395
|
+
resolvedVersion: "1.0.0",
|
|
1396
|
+
integrity: "sha512-abc123",
|
|
1397
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
1398
|
+
});
|
|
1399
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
1400
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
1401
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
1402
|
+
// Execute
|
|
1403
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
1404
|
+
// Verify error message structure
|
|
1405
|
+
const errorCalls = consoleErrorSpy.mock.calls.map((call) => call[0]);
|
|
1406
|
+
const errorOutput = errorCalls.join("\n");
|
|
1407
|
+
// Should contain main error message
|
|
1408
|
+
expect(errorOutput).toContain("Missing MCP server dependencies");
|
|
1409
|
+
// Should contain server name
|
|
1410
|
+
expect(errorOutput).toContain("filesystem");
|
|
1411
|
+
// Should contain needed by information
|
|
1412
|
+
expect(errorOutput).toContain("file-manager");
|
|
1413
|
+
// Should contain suggestion
|
|
1414
|
+
expect(errorOutput).toContain("--with-mcp");
|
|
1415
|
+
});
|
|
1416
|
+
it("should use chalk for colorized output in error messages", async () => {
|
|
1417
|
+
// Setup
|
|
1418
|
+
const config = {
|
|
1419
|
+
skills: {
|
|
1420
|
+
"file-manager": "github:user/file-manager#v1.0.0"
|
|
1421
|
+
},
|
|
1422
|
+
config: {
|
|
1423
|
+
skillsDirectory: ".agentskills/skills",
|
|
1424
|
+
autoDiscover: [],
|
|
1425
|
+
maxSkillSize: 5000,
|
|
1426
|
+
logLevel: "info"
|
|
1427
|
+
},
|
|
1428
|
+
source: {
|
|
1429
|
+
type: "file",
|
|
1430
|
+
path: "/test/package.json"
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
const installedSkills = [
|
|
1434
|
+
{
|
|
1435
|
+
metadata: {
|
|
1436
|
+
name: "file-manager",
|
|
1437
|
+
description: "File management skill",
|
|
1438
|
+
requiresMcpServers: [
|
|
1439
|
+
{
|
|
1440
|
+
name: "filesystem",
|
|
1441
|
+
description: "File system access",
|
|
1442
|
+
command: "npx",
|
|
1443
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
1444
|
+
}
|
|
1445
|
+
]
|
|
1446
|
+
},
|
|
1447
|
+
body: "Skill content"
|
|
1448
|
+
}
|
|
1449
|
+
];
|
|
1450
|
+
const dependencies = [
|
|
1451
|
+
{
|
|
1452
|
+
serverName: "filesystem",
|
|
1453
|
+
neededBy: ["file-manager"],
|
|
1454
|
+
spec: {
|
|
1455
|
+
name: "filesystem",
|
|
1456
|
+
description: "File system access",
|
|
1457
|
+
command: "npx",
|
|
1458
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
];
|
|
1462
|
+
const checkResult = {
|
|
1463
|
+
allConfigured: false,
|
|
1464
|
+
missing: dependencies,
|
|
1465
|
+
configured: []
|
|
1466
|
+
};
|
|
1467
|
+
mockConfigManager.loadConfig.mockResolvedValue(config);
|
|
1468
|
+
mockInstaller.install.mockResolvedValue({
|
|
1469
|
+
success: true,
|
|
1470
|
+
name: "file-manager",
|
|
1471
|
+
spec: "github:user/file-manager#v1.0.0",
|
|
1472
|
+
resolvedVersion: "1.0.0",
|
|
1473
|
+
integrity: "sha512-abc123",
|
|
1474
|
+
installPath: "/test/.agentskills/skills/file-manager"
|
|
1475
|
+
});
|
|
1476
|
+
mockInstaller.loadInstalledSkills.mockResolvedValue(installedSkills);
|
|
1477
|
+
mockMCPDependencyChecker.collectDependencies.mockReturnValue(dependencies);
|
|
1478
|
+
mockMCPDependencyChecker.checkDependencies.mockResolvedValue(checkResult);
|
|
1479
|
+
// Execute
|
|
1480
|
+
await installCommand({ cwd: "/test", agent: "claude" });
|
|
1481
|
+
// Verify chalk was used
|
|
1482
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
1483
|
+
// Check that error output contains expected content (chalk's job is to colorize)
|
|
1484
|
+
// In test environment, chalk may strip colors, but the content should still be there
|
|
1485
|
+
const allOutput = consoleErrorSpy.mock.calls
|
|
1486
|
+
.map((call) => String(call[0]))
|
|
1487
|
+
.join(" ");
|
|
1488
|
+
// Just verify the messages are present - chalk may strip colors in test env
|
|
1489
|
+
expect(allOutput).toContain("Missing MCP server dependencies");
|
|
1490
|
+
expect(allOutput).toContain("filesystem");
|
|
1491
|
+
expect(allOutput).toContain("file-manager");
|
|
1492
|
+
});
|
|
1493
|
+
});
|
|
1494
|
+
});
|
|
1495
|
+
//# sourceMappingURL=install-mcp-validation.test.js.map
|