@skillsmith/mcp-server 0.4.10 → 0.4.12
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/CHANGELOG.md +8 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/__tests__/tool-dispatch.envelope.test.d.ts +12 -0
- package/dist/src/__tests__/tool-dispatch.envelope.test.d.ts.map +1 -0
- package/dist/src/__tests__/tool-dispatch.envelope.test.js +205 -0
- package/dist/src/__tests__/tool-dispatch.envelope.test.js.map +1 -0
- package/dist/src/index.js +10 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/middleware/license.d.ts +6 -6
- package/dist/src/middleware/license.d.ts.map +1 -1
- package/dist/src/middleware/license.js +11 -8
- package/dist/src/middleware/license.js.map +1 -1
- package/dist/src/tool-dispatch.d.ts.map +1 -1
- package/dist/src/tool-dispatch.js +58 -19
- package/dist/src/tool-dispatch.js.map +1 -1
- package/dist/src/tools/audit-tools.d.ts +6 -6
- package/dist/src/tools/install.d.ts +9 -3
- package/dist/src/tools/install.d.ts.map +1 -1
- package/dist/src/tools/install.js +52 -11
- package/dist/src/tools/install.js.map +1 -1
- package/dist/src/tools/install.test.d.ts +17 -0
- package/dist/src/tools/install.test.d.ts.map +1 -0
- package/dist/src/tools/install.test.js +237 -0
- package/dist/src/tools/install.test.js.map +1 -0
- package/dist/src/tools/integration-tools.d.ts +8 -8
- package/dist/src/tools/rbac-tools.d.ts +4 -4
- package/dist/src/tools/recommend.types.d.ts +3 -3
- package/dist/src/tools/registry-tools.d.ts +2 -2
- package/dist/src/tools/registry-tools.d.ts.map +1 -1
- package/dist/src/tools/registry-tools.js +42 -4
- package/dist/src/tools/registry-tools.js.map +1 -1
- package/dist/src/tools/skill-diff.d.ts +2 -2
- package/dist/src/tools/sso-tools.d.ts +2 -2
- package/dist/src/tools/suggest.d.ts +4 -4
- package/dist/src/tools/team-resolver.d.ts +29 -0
- package/dist/src/tools/team-resolver.d.ts.map +1 -0
- package/dist/src/tools/team-resolver.js +46 -0
- package/dist/src/tools/team-resolver.js.map +1 -0
- package/dist/src/tools/team-resolver.test.d.ts +6 -0
- package/dist/src/tools/team-resolver.test.d.ts.map +1 -0
- package/dist/src/tools/team-resolver.test.js +73 -0
- package/dist/src/tools/team-resolver.test.js.map +1 -0
- package/dist/src/tools/team-workspace.d.ts +18 -4
- package/dist/src/tools/team-workspace.d.ts.map +1 -1
- package/dist/src/tools/team-workspace.js +149 -75
- package/dist/src/tools/team-workspace.js.map +1 -1
- package/dist/src/tools/team-workspace.live.d.ts +34 -0
- package/dist/src/tools/team-workspace.live.d.ts.map +1 -0
- package/dist/src/tools/team-workspace.live.js +182 -0
- package/dist/src/tools/team-workspace.live.js.map +1 -0
- package/dist/src/tools/team-workspace.live.test.d.ts +10 -0
- package/dist/src/tools/team-workspace.live.test.d.ts.map +1 -0
- package/dist/src/tools/team-workspace.live.test.js +177 -0
- package/dist/src/tools/team-workspace.live.test.js.map +1 -0
- package/dist/src/tools/team-workspace.stub.d.ts.map +1 -1
- package/dist/src/tools/team-workspace.stub.js +3 -1
- package/dist/src/tools/team-workspace.stub.js.map +1 -1
- package/dist/src/tools/team-workspace.test.d.ts +1 -0
- package/dist/src/tools/team-workspace.test.d.ts.map +1 -1
- package/dist/src/tools/team-workspace.test.js +43 -1
- package/dist/src/tools/team-workspace.test.js.map +1 -1
- package/dist/src/tools/uninstall.d.ts +1 -1
- package/dist/src/validation.d.ts +64 -0
- package/dist/src/validation.d.ts.map +1 -0
- package/dist/src/validation.js +73 -0
- package/dist/src/validation.js.map +1 -0
- package/dist/src/validation.test.d.ts +6 -0
- package/dist/src/validation.test.d.ts.map +1 -0
- package/dist/src/validation.test.js +102 -0
- package/dist/src/validation.test.js.map +1 -0
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* - Wires onProgress to MCP protocol notifications
|
|
15
15
|
*/
|
|
16
16
|
import type { ToolContext } from '../context.js';
|
|
17
|
-
import { type
|
|
17
|
+
import { type InstallResult } from './install.types.js';
|
|
18
18
|
export { installTool } from './install.tool.js';
|
|
19
19
|
export { default } from './install.tool.js';
|
|
20
20
|
export { installInputSchema, type InstallInput, type InstallResult } from './install.types.js';
|
|
@@ -24,9 +24,15 @@ export { installInputSchema, type InstallInput, type InstallResult } from './ins
|
|
|
24
24
|
* Delegates core logic to SkillInstallationService from @skillsmith/core.
|
|
25
25
|
* Adds MCP-specific conflict resolution (three-way merge, backup).
|
|
26
26
|
*
|
|
27
|
-
*
|
|
27
|
+
* SMI-4288 / GitHub #599: Signature accepts `unknown` so the Zod `safeParse`
|
|
28
|
+
* guard actually runs at the MCP tool boundary. The prior `InstallInput`
|
|
29
|
+
* parameter type made the guard unreachable — callers passed a pre-typed
|
|
30
|
+
* object, leaving the underlying `request.params.arguments` crash surface
|
|
31
|
+
* unprotected when the dispatcher forwards raw args.
|
|
32
|
+
*
|
|
33
|
+
* @param input - Raw MCP tool arguments (unvalidated); parsed via Zod here
|
|
28
34
|
* @param _context - Optional tool context (falls back to singleton)
|
|
29
35
|
* @returns Installation result with success status, security report, and dep intel
|
|
30
36
|
*/
|
|
31
|
-
export declare function installSkill(input:
|
|
37
|
+
export declare function installSkill(input: unknown, _context?: ToolContext): Promise<InstallResult>;
|
|
32
38
|
//# sourceMappingURL=install.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/tools/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAQH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/tools/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAQH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAO3E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAG3C,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAkC9F;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CA4EjG"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { SkillInstallationService, emitInstallEvent, } from '@skillsmith/core';
|
|
17
17
|
import { getToolContext } from '../context.js';
|
|
18
|
+
import { installInputSchema } from './install.types.js';
|
|
18
19
|
import { loadManifest, lookupSkillFromRegistry } from './install.helpers.js';
|
|
19
20
|
// SMI-1867: Conflict resolution logic (extracted per governance review)
|
|
20
21
|
import { checkForConflicts } from './install.conflict.js';
|
|
@@ -36,17 +37,57 @@ class McpRegistryLookup {
|
|
|
36
37
|
return lookupSkillFromRegistry(skillId, this.context);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Build an application-level validation failure result.
|
|
42
|
+
*
|
|
43
|
+
* SMI-4288 / GitHub #599: When an MCP caller passes a malformed argument
|
|
44
|
+
* payload (e.g. `{}`, wrong `skillId` type, invalid `conflictAction` enum),
|
|
45
|
+
* return a structured `InstallResult` with `success: false` rather than
|
|
46
|
+
* throwing. Matches the existing `team-workspace.ts` error-envelope
|
|
47
|
+
* convention (application-level failure, not MCP protocol-level `isError`).
|
|
48
|
+
*
|
|
49
|
+
* @see #599
|
|
50
|
+
*/
|
|
51
|
+
function buildValidationError(message) {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
skillId: '',
|
|
55
|
+
installPath: '',
|
|
56
|
+
error: `Invalid install input: ${message}`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
39
59
|
/**
|
|
40
60
|
* Install a skill from GitHub to the local Claude Code skills directory.
|
|
41
61
|
*
|
|
42
62
|
* Delegates core logic to SkillInstallationService from @skillsmith/core.
|
|
43
63
|
* Adds MCP-specific conflict resolution (three-way merge, backup).
|
|
44
64
|
*
|
|
45
|
-
*
|
|
65
|
+
* SMI-4288 / GitHub #599: Signature accepts `unknown` so the Zod `safeParse`
|
|
66
|
+
* guard actually runs at the MCP tool boundary. The prior `InstallInput`
|
|
67
|
+
* parameter type made the guard unreachable — callers passed a pre-typed
|
|
68
|
+
* object, leaving the underlying `request.params.arguments` crash surface
|
|
69
|
+
* unprotected when the dispatcher forwards raw args.
|
|
70
|
+
*
|
|
71
|
+
* @param input - Raw MCP tool arguments (unvalidated); parsed via Zod here
|
|
46
72
|
* @param _context - Optional tool context (falls back to singleton)
|
|
47
73
|
* @returns Installation result with success status, security report, and dep intel
|
|
48
74
|
*/
|
|
49
75
|
export async function installSkill(input, _context) {
|
|
76
|
+
// SMI-4288 / #599: Zod validation boundary. Unlike the previous typed
|
|
77
|
+
// signature, this runs at every call site (tool-dispatch, runFirstTimeSetup,
|
|
78
|
+
// integration tests). Validation failures return a structured InstallResult
|
|
79
|
+
// instead of throwing, preserving the MCP application-level error envelope.
|
|
80
|
+
const parsed = installInputSchema.safeParse(input);
|
|
81
|
+
if (!parsed.success) {
|
|
82
|
+
const message = parsed.error.issues
|
|
83
|
+
.map((issue) => {
|
|
84
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : '<root>';
|
|
85
|
+
return `${path}: ${issue.message}`;
|
|
86
|
+
})
|
|
87
|
+
.join('; ');
|
|
88
|
+
return buildValidationError(message);
|
|
89
|
+
}
|
|
90
|
+
const validInput = parsed.data;
|
|
50
91
|
const context = _context ?? getToolContext();
|
|
51
92
|
// SMI-3483: Create core service instance with MCP context wiring
|
|
52
93
|
// SMI-3873: aiDefenceFeedback omitted -- MCP server cannot call Ruflo tools.
|
|
@@ -60,13 +101,13 @@ export async function installSkill(input, _context) {
|
|
|
60
101
|
});
|
|
61
102
|
// SMI-1867: Pre-flight conflict check for reinstall with force
|
|
62
103
|
// This is MCP-specific (three-way merge UI, backup, storeOriginal)
|
|
63
|
-
if (
|
|
104
|
+
if (validInput.force && validInput.conflictAction) {
|
|
64
105
|
try {
|
|
65
106
|
const manifest = await loadManifest();
|
|
66
|
-
const skillName = extractSkillName(
|
|
107
|
+
const skillName = extractSkillName(validInput.skillId);
|
|
67
108
|
if (manifest.installedSkills[skillName]) {
|
|
68
109
|
const installPath = manifest.installedSkills[skillName].installPath;
|
|
69
|
-
const conflictCheck = await checkForConflicts(skillName, installPath, manifest,
|
|
110
|
+
const conflictCheck = await checkForConflicts(skillName, installPath, manifest, validInput.conflictAction, validInput.skillId);
|
|
70
111
|
if (!conflictCheck.shouldProceed) {
|
|
71
112
|
return conflictCheck.earlyReturn;
|
|
72
113
|
}
|
|
@@ -78,16 +119,16 @@ export async function installSkill(input, _context) {
|
|
|
78
119
|
}
|
|
79
120
|
// Delegate to core service
|
|
80
121
|
const installStart = Date.now();
|
|
81
|
-
const result = await service.install(
|
|
82
|
-
force:
|
|
83
|
-
skipScan:
|
|
84
|
-
skipOptimize:
|
|
85
|
-
conflictAction:
|
|
86
|
-
confirmed:
|
|
122
|
+
const result = await service.install(validInput.skillId, {
|
|
123
|
+
force: validInput.force,
|
|
124
|
+
skipScan: validInput.skipScan,
|
|
125
|
+
skipOptimize: validInput.skipOptimize,
|
|
126
|
+
conflictAction: validInput.conflictAction,
|
|
127
|
+
confirmed: validInput.confirmed,
|
|
87
128
|
});
|
|
88
129
|
// SMI-4182: fire-and-forget install telemetry for usage report funnel
|
|
89
130
|
void emitInstallEvent({
|
|
90
|
-
skillId:
|
|
131
|
+
skillId: validInput.skillId,
|
|
91
132
|
source: 'mcp',
|
|
92
133
|
success: result.success,
|
|
93
134
|
durationMs: Date.now() - installStart,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/tools/install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EACL,wBAAwB,EACxB,gBAAgB,GAGjB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/tools/install.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unit tests for install_skill MCP tool Zod boundary guard
|
|
3
|
+
* @see SMI-4288: Zod validation guard at MCP tool boundary
|
|
4
|
+
* @see https://github.com/smith-horn/skillsmith/issues/599
|
|
5
|
+
*
|
|
6
|
+
* These tests cover the behaviour introduced by the signature change from
|
|
7
|
+
* `installSkill(input: InstallInput, ...)` to `installSkill(input: unknown, ...)`.
|
|
8
|
+
* The guard protects against malformed MCP payloads (e.g. `{}`,
|
|
9
|
+
* `{ skillId: 123 }`, invalid enum) reaching the core installation service.
|
|
10
|
+
*
|
|
11
|
+
* The happy path mocks `@skillsmith/core` so no real filesystem or network
|
|
12
|
+
* work happens — this file is a unit test for the tool-boundary validation
|
|
13
|
+
* shim, not an integration test for the install flow itself (that lives
|
|
14
|
+
* in `tests/integration/install.integration.test.ts`).
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=install.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.test.d.ts","sourceRoot":"","sources":["../../../src/tools/install.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG"}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Unit tests for install_skill MCP tool Zod boundary guard
|
|
3
|
+
* @see SMI-4288: Zod validation guard at MCP tool boundary
|
|
4
|
+
* @see https://github.com/smith-horn/skillsmith/issues/599
|
|
5
|
+
*
|
|
6
|
+
* These tests cover the behaviour introduced by the signature change from
|
|
7
|
+
* `installSkill(input: InstallInput, ...)` to `installSkill(input: unknown, ...)`.
|
|
8
|
+
* The guard protects against malformed MCP payloads (e.g. `{}`,
|
|
9
|
+
* `{ skillId: 123 }`, invalid enum) reaching the core installation service.
|
|
10
|
+
*
|
|
11
|
+
* The happy path mocks `@skillsmith/core` so no real filesystem or network
|
|
12
|
+
* work happens — this file is a unit test for the tool-boundary validation
|
|
13
|
+
* shim, not an integration test for the install flow itself (that lives
|
|
14
|
+
* in `tests/integration/install.integration.test.ts`).
|
|
15
|
+
*/
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
17
|
+
// SMI-4288: Mock the core service so the happy-path test exercises only the
|
|
18
|
+
// Zod gate + delegation. `vi.hoisted` is required because `vi.mock` is
|
|
19
|
+
// hoisted above regular `const` declarations and would otherwise reference
|
|
20
|
+
// the stubs before they are initialised.
|
|
21
|
+
const { mockInstall, mockEmitInstallEvent } = vi.hoisted(() => ({
|
|
22
|
+
mockInstall: vi.fn(),
|
|
23
|
+
mockEmitInstallEvent: vi.fn(),
|
|
24
|
+
}));
|
|
25
|
+
vi.mock('@skillsmith/core', async (importActual) => {
|
|
26
|
+
const actual = await importActual();
|
|
27
|
+
class MockSkillInstallationService {
|
|
28
|
+
install = mockInstall;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
...actual,
|
|
32
|
+
SkillInstallationService: MockSkillInstallationService,
|
|
33
|
+
emitInstallEvent: mockEmitInstallEvent,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
// Prevent getToolContext() from throwing when no context is passed — the
|
|
37
|
+
// installSkill helper calls it before delegating. A minimal stub is enough
|
|
38
|
+
// because the mocked SkillInstallationService ignores the params.
|
|
39
|
+
vi.mock('../context.js', () => ({
|
|
40
|
+
getToolContext: vi.fn().mockReturnValue({
|
|
41
|
+
db: {},
|
|
42
|
+
skillRepository: {},
|
|
43
|
+
skillDependencyRepository: {},
|
|
44
|
+
coInstallRepository: undefined,
|
|
45
|
+
sessionInstalledSkillIds: [],
|
|
46
|
+
}),
|
|
47
|
+
}));
|
|
48
|
+
// SMI-4288: Mock install.helpers so the conflict-preflight path exercises
|
|
49
|
+
// deterministic behaviour. Each test configures loadManifest explicitly.
|
|
50
|
+
const { mockLoadManifest, mockLookupSkillFromRegistry } = vi.hoisted(() => ({
|
|
51
|
+
mockLoadManifest: vi.fn(),
|
|
52
|
+
mockLookupSkillFromRegistry: vi.fn(),
|
|
53
|
+
}));
|
|
54
|
+
vi.mock('./install.helpers.js', () => ({
|
|
55
|
+
loadManifest: mockLoadManifest,
|
|
56
|
+
lookupSkillFromRegistry: mockLookupSkillFromRegistry,
|
|
57
|
+
}));
|
|
58
|
+
// Conflict check helper — return a shouldProceed:true stub so the flow
|
|
59
|
+
// falls through to the core service unless a test overrides.
|
|
60
|
+
const { mockCheckForConflicts } = vi.hoisted(() => ({
|
|
61
|
+
mockCheckForConflicts: vi.fn(),
|
|
62
|
+
}));
|
|
63
|
+
vi.mock('./install.conflict.js', () => ({
|
|
64
|
+
checkForConflicts: mockCheckForConflicts,
|
|
65
|
+
}));
|
|
66
|
+
import { installSkill } from './install.js';
|
|
67
|
+
const HAPPY_RESULT = {
|
|
68
|
+
success: true,
|
|
69
|
+
skillId: 'owner/repo/test-skill',
|
|
70
|
+
installPath: '/tmp/mock/test-skill',
|
|
71
|
+
};
|
|
72
|
+
describe('installSkill() Zod boundary guard (SMI-4288 / #599)', () => {
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
mockInstall.mockReset();
|
|
75
|
+
mockEmitInstallEvent.mockReset();
|
|
76
|
+
mockLoadManifest.mockReset();
|
|
77
|
+
mockLookupSkillFromRegistry.mockReset();
|
|
78
|
+
mockCheckForConflicts.mockReset();
|
|
79
|
+
mockInstall.mockResolvedValue(HAPPY_RESULT);
|
|
80
|
+
// By default no conflict preflight interception.
|
|
81
|
+
mockLoadManifest.mockResolvedValue({ version: '1', installedSkills: {} });
|
|
82
|
+
mockCheckForConflicts.mockResolvedValue({ shouldProceed: true });
|
|
83
|
+
});
|
|
84
|
+
afterEach(() => {
|
|
85
|
+
vi.clearAllMocks();
|
|
86
|
+
});
|
|
87
|
+
describe('happy path', () => {
|
|
88
|
+
it('delegates a valid InstallInput to SkillInstallationService.install', async () => {
|
|
89
|
+
const result = await installSkill({
|
|
90
|
+
skillId: 'owner/repo/test-skill',
|
|
91
|
+
force: false,
|
|
92
|
+
skipScan: true,
|
|
93
|
+
skipOptimize: true,
|
|
94
|
+
confirmed: true,
|
|
95
|
+
});
|
|
96
|
+
expect(result).toEqual(HAPPY_RESULT);
|
|
97
|
+
expect(mockInstall).toHaveBeenCalledTimes(1);
|
|
98
|
+
expect(mockInstall).toHaveBeenCalledWith('owner/repo/test-skill', {
|
|
99
|
+
force: false,
|
|
100
|
+
skipScan: true,
|
|
101
|
+
skipOptimize: true,
|
|
102
|
+
conflictAction: undefined,
|
|
103
|
+
confirmed: true,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe('validation failures return structured InstallResult', () => {
|
|
108
|
+
it('rejects undefined input with success: false and surfaces the Zod issue', async () => {
|
|
109
|
+
const result = await installSkill(undefined);
|
|
110
|
+
expect(result.success).toBe(false);
|
|
111
|
+
expect(result.skillId).toBe('');
|
|
112
|
+
expect(result.installPath).toBe('');
|
|
113
|
+
expect(result.error).toBeDefined();
|
|
114
|
+
expect(result.error).toContain('Invalid install input');
|
|
115
|
+
// Core service must never be invoked when validation fails.
|
|
116
|
+
expect(mockInstall).not.toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
it('rejects input missing skillId', async () => {
|
|
119
|
+
const result = await installSkill({});
|
|
120
|
+
expect(result.success).toBe(false);
|
|
121
|
+
expect(result.error).toContain('Invalid install input');
|
|
122
|
+
expect(result.error).toContain('skillId');
|
|
123
|
+
expect(mockInstall).not.toHaveBeenCalled();
|
|
124
|
+
});
|
|
125
|
+
it('rejects input with non-string skillId', async () => {
|
|
126
|
+
const result = await installSkill({ skillId: 123 });
|
|
127
|
+
expect(result.success).toBe(false);
|
|
128
|
+
expect(result.error).toContain('Invalid install input');
|
|
129
|
+
expect(result.error).toContain('skillId');
|
|
130
|
+
expect(mockInstall).not.toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
it('rejects input with invalid conflictAction enum value', async () => {
|
|
133
|
+
const result = await installSkill({
|
|
134
|
+
skillId: 'owner/repo/test-skill',
|
|
135
|
+
conflictAction: 'stomp',
|
|
136
|
+
});
|
|
137
|
+
expect(result.success).toBe(false);
|
|
138
|
+
expect(result.error).toContain('Invalid install input');
|
|
139
|
+
expect(result.error).toContain('conflictAction');
|
|
140
|
+
expect(mockInstall).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
it('rejects empty-string skillId (min(1) constraint)', async () => {
|
|
143
|
+
const result = await installSkill({ skillId: '' });
|
|
144
|
+
expect(result.success).toBe(false);
|
|
145
|
+
expect(result.error).toContain('Invalid install input');
|
|
146
|
+
expect(mockInstall).not.toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('pre-existing failure paths still work after guard', () => {
|
|
150
|
+
it('surfaces a service-level failure result untouched', async () => {
|
|
151
|
+
const serviceFailure = {
|
|
152
|
+
success: false,
|
|
153
|
+
skillId: 'owner/repo/test-skill',
|
|
154
|
+
installPath: '',
|
|
155
|
+
error: 'Skill indexed for discovery only',
|
|
156
|
+
};
|
|
157
|
+
mockInstall.mockResolvedValueOnce(serviceFailure);
|
|
158
|
+
const result = await installSkill({
|
|
159
|
+
skillId: 'owner/repo/test-skill',
|
|
160
|
+
skipScan: true,
|
|
161
|
+
});
|
|
162
|
+
expect(result).toEqual(serviceFailure);
|
|
163
|
+
expect(result.error).toBe('Skill indexed for discovery only');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
describe('conflict preflight path (force + conflictAction)', () => {
|
|
167
|
+
it('returns the early-exit conflict result when checkForConflicts signals stop', async () => {
|
|
168
|
+
const conflictResult = {
|
|
169
|
+
success: false,
|
|
170
|
+
skillId: 'owner/repo/test-skill',
|
|
171
|
+
installPath: '/existing/path',
|
|
172
|
+
error: 'User cancelled due to local modifications',
|
|
173
|
+
};
|
|
174
|
+
mockLoadManifest.mockResolvedValueOnce({
|
|
175
|
+
version: '1',
|
|
176
|
+
installedSkills: {
|
|
177
|
+
'test-skill': {
|
|
178
|
+
id: 'owner/repo/test-skill',
|
|
179
|
+
name: 'test-skill',
|
|
180
|
+
version: '1.0.0',
|
|
181
|
+
source: 'registry',
|
|
182
|
+
installPath: '/existing/path',
|
|
183
|
+
installedAt: '2026-01-01T00:00:00Z',
|
|
184
|
+
lastUpdated: '2026-01-01T00:00:00Z',
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
mockCheckForConflicts.mockResolvedValueOnce({
|
|
189
|
+
shouldProceed: false,
|
|
190
|
+
earlyReturn: conflictResult,
|
|
191
|
+
});
|
|
192
|
+
const result = await installSkill({
|
|
193
|
+
skillId: 'owner/repo/test-skill',
|
|
194
|
+
force: true,
|
|
195
|
+
conflictAction: 'cancel',
|
|
196
|
+
});
|
|
197
|
+
expect(result).toEqual(conflictResult);
|
|
198
|
+
expect(mockCheckForConflicts).toHaveBeenCalledTimes(1);
|
|
199
|
+
expect(mockInstall).not.toHaveBeenCalled();
|
|
200
|
+
});
|
|
201
|
+
it('falls through to core install when manifest lookup throws', async () => {
|
|
202
|
+
// Conflict preflight swallows errors and continues with normal install.
|
|
203
|
+
mockLoadManifest.mockRejectedValueOnce(new Error('manifest missing'));
|
|
204
|
+
const result = await installSkill({
|
|
205
|
+
skillId: 'owner/repo/test-skill',
|
|
206
|
+
force: true,
|
|
207
|
+
conflictAction: 'overwrite',
|
|
208
|
+
});
|
|
209
|
+
expect(result).toEqual(HAPPY_RESULT);
|
|
210
|
+
expect(mockInstall).toHaveBeenCalledTimes(1);
|
|
211
|
+
});
|
|
212
|
+
it('resolves bare skillId (no slash) via extractSkillName', async () => {
|
|
213
|
+
mockLoadManifest.mockResolvedValueOnce({
|
|
214
|
+
version: '1',
|
|
215
|
+
installedSkills: {
|
|
216
|
+
'bare-name': {
|
|
217
|
+
id: 'bare-name',
|
|
218
|
+
name: 'bare-name',
|
|
219
|
+
version: '1.0.0',
|
|
220
|
+
source: 'registry',
|
|
221
|
+
installPath: '/x',
|
|
222
|
+
installedAt: '2026-01-01T00:00:00Z',
|
|
223
|
+
lastUpdated: '2026-01-01T00:00:00Z',
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
const result = await installSkill({
|
|
228
|
+
skillId: 'bare-name',
|
|
229
|
+
force: true,
|
|
230
|
+
conflictAction: 'overwrite',
|
|
231
|
+
});
|
|
232
|
+
expect(result).toEqual(HAPPY_RESULT);
|
|
233
|
+
expect(mockCheckForConflicts).toHaveBeenCalledWith('bare-name', '/x', expect.objectContaining({ installedSkills: expect.any(Object) }), 'overwrite', 'bare-name');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
//# sourceMappingURL=install.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.test.js","sourceRoot":"","sources":["../../../src/tools/install.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,17 +18,17 @@ export declare const webhookConfigureInputSchema: z.ZodObject<{
|
|
|
18
18
|
webhookId: z.ZodOptional<z.ZodString>;
|
|
19
19
|
description: z.ZodOptional<z.ZodString>;
|
|
20
20
|
}, "strip", z.ZodTypeAny, {
|
|
21
|
-
action: "test" | "
|
|
21
|
+
action: "test" | "get" | "create" | "list" | "delete" | "rotate_secret";
|
|
22
22
|
description?: string | undefined;
|
|
23
23
|
url?: string | undefined;
|
|
24
|
-
webhookId?: string | undefined;
|
|
25
24
|
events?: string[] | undefined;
|
|
25
|
+
webhookId?: string | undefined;
|
|
26
26
|
}, {
|
|
27
|
-
action: "test" | "
|
|
27
|
+
action: "test" | "get" | "create" | "list" | "delete" | "rotate_secret";
|
|
28
28
|
description?: string | undefined;
|
|
29
29
|
url?: string | undefined;
|
|
30
|
-
webhookId?: string | undefined;
|
|
31
30
|
events?: string[] | undefined;
|
|
31
|
+
webhookId?: string | undefined;
|
|
32
32
|
}>;
|
|
33
33
|
export type WebhookConfigureInput = z.infer<typeof webhookConfigureInputSchema>;
|
|
34
34
|
export declare const apiKeyManageInputSchema: z.ZodObject<{
|
|
@@ -38,17 +38,17 @@ export declare const apiKeyManageInputSchema: z.ZodObject<{
|
|
|
38
38
|
permissions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
39
39
|
expiresIn: z.ZodDefault<z.ZodOptional<z.ZodEnum<["30d", "90d", "365d", "never"]>>>;
|
|
40
40
|
}, "strip", z.ZodTypeAny, {
|
|
41
|
-
action: "
|
|
42
|
-
expiresIn: "
|
|
41
|
+
action: "get" | "create" | "list" | "revoke";
|
|
42
|
+
expiresIn: "30d" | "90d" | "365d" | "never";
|
|
43
43
|
name?: string | undefined;
|
|
44
44
|
permissions?: string[] | undefined;
|
|
45
45
|
keyId?: string | undefined;
|
|
46
46
|
}, {
|
|
47
|
-
action: "
|
|
47
|
+
action: "get" | "create" | "list" | "revoke";
|
|
48
48
|
name?: string | undefined;
|
|
49
49
|
permissions?: string[] | undefined;
|
|
50
50
|
keyId?: string | undefined;
|
|
51
|
-
expiresIn?: "
|
|
51
|
+
expiresIn?: "30d" | "90d" | "365d" | "never" | undefined;
|
|
52
52
|
}>;
|
|
53
53
|
export type ApiKeyManageInput = z.infer<typeof apiKeyManageInputSchema>;
|
|
54
54
|
export declare const webhookConfigureToolSchema: {
|
|
@@ -58,18 +58,18 @@ export declare const rbacCreatePolicyInputSchema: z.ZodObject<{
|
|
|
58
58
|
resources: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
59
59
|
actions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
60
60
|
}, "strip", z.ZodTypeAny, {
|
|
61
|
-
action: "
|
|
61
|
+
action: "get" | "create" | "list" | "delete";
|
|
62
62
|
name?: string | undefined;
|
|
63
|
+
resources?: string[] | undefined;
|
|
63
64
|
policyId?: string | undefined;
|
|
64
65
|
effect?: "allow" | "deny" | undefined;
|
|
65
|
-
resources?: string[] | undefined;
|
|
66
66
|
actions?: string[] | undefined;
|
|
67
67
|
}, {
|
|
68
|
-
action: "
|
|
68
|
+
action: "get" | "create" | "list" | "delete";
|
|
69
69
|
name?: string | undefined;
|
|
70
|
+
resources?: string[] | undefined;
|
|
70
71
|
policyId?: string | undefined;
|
|
71
72
|
effect?: "allow" | "deny" | undefined;
|
|
72
|
-
resources?: string[] | undefined;
|
|
73
73
|
actions?: string[] | undefined;
|
|
74
74
|
}>;
|
|
75
75
|
export type RbacCreatePolicyInput = z.infer<typeof rbacCreatePolicyInputSchema>;
|
|
@@ -30,14 +30,14 @@ export declare const recommendInputSchema: z.ZodObject<{
|
|
|
30
30
|
detect_overlap: boolean;
|
|
31
31
|
min_similarity: number;
|
|
32
32
|
project_context?: string | undefined;
|
|
33
|
-
role?: "testing" | "documentation" | "security" | "
|
|
33
|
+
role?: "testing" | "documentation" | "security" | "code-quality" | "workflow" | "development-partner" | undefined;
|
|
34
34
|
}, {
|
|
35
35
|
installed_skills?: string[] | undefined;
|
|
36
|
-
project_context?: string | undefined;
|
|
37
36
|
limit?: number | undefined;
|
|
37
|
+
project_context?: string | undefined;
|
|
38
38
|
detect_overlap?: boolean | undefined;
|
|
39
39
|
min_similarity?: number | undefined;
|
|
40
|
-
role?: "testing" | "documentation" | "security" | "
|
|
40
|
+
role?: "testing" | "documentation" | "security" | "code-quality" | "workflow" | "development-partner" | undefined;
|
|
41
41
|
}>;
|
|
42
42
|
/**
|
|
43
43
|
* Input type (before parsing, allows optional fields)
|
|
@@ -31,11 +31,11 @@ export declare const privateRegistryManageInputSchema: z.ZodObject<{
|
|
|
31
31
|
skillId: z.ZodOptional<z.ZodString>;
|
|
32
32
|
version: z.ZodOptional<z.ZodString>;
|
|
33
33
|
}, "strip", z.ZodTypeAny, {
|
|
34
|
-
action: "
|
|
34
|
+
action: "get" | "list" | "deprecate" | "undeprecate";
|
|
35
35
|
skillId?: string | undefined;
|
|
36
36
|
version?: string | undefined;
|
|
37
37
|
}, {
|
|
38
|
-
action: "
|
|
38
|
+
action: "get" | "list" | "deprecate" | "undeprecate";
|
|
39
39
|
skillId?: string | undefined;
|
|
40
40
|
version?: string | undefined;
|
|
41
41
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry-tools.d.ts","sourceRoot":"","sources":["../../../src/tools/registry-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"registry-tools.d.ts","sourceRoot":"","sources":["../../../src/tools/registry-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAQhD,eAAO,MAAM,iCAAiC;;;;;;;;;;;;EAU5C,CAAA;AAEF,MAAM,MAAM,2BAA2B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iCAAiC,CAAC,CAAA;AAE3F,eAAO,MAAM,gCAAgC;;;;;;;;;;;;EAQ3C,CAAA;AAEF,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAA;AAMzF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;CAwB5C,CAAA;AAED,eAAO,MAAM,+BAA+B;;;;;;;;;;;;;;;;;;;;;;CAwB3C,CAAA;AAMD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3B,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,OAAO,CAAA;IAChB,UAAU,EAAE,MAAM,GAAG,MAAM,CAAA;IAC3B,MAAM,CAAC,EAAE,aAAa,EAAE,CAAA;IACxB,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAMD,MAAM,WAAW,sBAAsB;IACrC,OAAO,CACL,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,aAAa,CAAC,CAAA;IACzB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAA;IAChE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAA;IACrF,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC5D,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC/D;AAMD,qCAAqC;AACrC,wBAAgB,yBAAyB,IAAI,sBAAsB,CA6ClE;AAKD,mFAAmF;AACnF,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,sBAAsB,GAAG,IAAI,CAE3E;AAED,gDAAgD;AAChD,wBAAgB,yBAAyB,IAAI,sBAAsB,CAElE;AAkCD;;GAEG;AACH,wBAAsB,6BAA6B,CACjD,KAAK,EAAE,2BAA2B,EAClC,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,4BAA4B,CAAC,CAsBvC;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,0BAA0B,EACjC,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,2BAA2B,CAAC,CAiFtC"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { z } from 'zod';
|
|
14
14
|
import { isSupabaseConfigured } from '../supabase-client.js';
|
|
15
|
+
import { resolveLicenseTeamId, readLicenseKey } from './team-resolver.js';
|
|
15
16
|
// ============================================================================
|
|
16
17
|
// Input schemas
|
|
17
18
|
// ============================================================================
|
|
@@ -151,17 +152,44 @@ export function getPrivateRegistryService() {
|
|
|
151
152
|
// ============================================================================
|
|
152
153
|
/**
|
|
153
154
|
* Resolve team ID from license key.
|
|
154
|
-
*
|
|
155
|
+
*
|
|
156
|
+
* SMI-4292 (finding C3): unified resolution — calls the same
|
|
157
|
+
* `resolve_team_from_license` RPC as team-workspace.ts. When Supabase is
|
|
158
|
+
* configured but the license key is missing/invalid, the caller receives
|
|
159
|
+
* a typed error (bubbled up via thrown Error).
|
|
160
|
+
*
|
|
161
|
+
* Falls back to a static stub id when Supabase is not configured (local dev).
|
|
155
162
|
*/
|
|
156
163
|
async function resolveTeamId() {
|
|
157
|
-
|
|
164
|
+
if (!isSupabaseConfigured())
|
|
165
|
+
return 'team_stub_00000000-0000-0000-0000-000000000000';
|
|
166
|
+
const licenseKey = readLicenseKey();
|
|
167
|
+
if (!licenseKey) {
|
|
168
|
+
throw new Error('SKILLSMITH_LICENSE_KEY is required for private registry operations. ' +
|
|
169
|
+
'Set it in your MCP server config — shell exports do not reach MCP subprocesses.');
|
|
170
|
+
}
|
|
171
|
+
const teamId = await resolveLicenseTeamId(licenseKey);
|
|
172
|
+
if (!teamId) {
|
|
173
|
+
throw new Error('Unable to resolve team from license key. Ensure the key is active and attached to a Team-tier subscription.');
|
|
174
|
+
}
|
|
175
|
+
return teamId;
|
|
158
176
|
}
|
|
159
177
|
/**
|
|
160
178
|
* Execute a private_registry_publish operation.
|
|
161
179
|
*/
|
|
162
180
|
export async function executePrivateRegistryPublish(input, _context) {
|
|
163
181
|
const dataSource = isSupabaseConfigured() ? 'live' : 'stub';
|
|
164
|
-
|
|
182
|
+
let teamId;
|
|
183
|
+
try {
|
|
184
|
+
teamId = await resolveTeamId();
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
return {
|
|
188
|
+
success: false,
|
|
189
|
+
dataSource,
|
|
190
|
+
error: err instanceof Error ? err.message : 'Failed to resolve team from license key.',
|
|
191
|
+
};
|
|
192
|
+
}
|
|
165
193
|
const skill = await service.publish(teamId, input.skillId, input.version, input.description);
|
|
166
194
|
return {
|
|
167
195
|
success: true,
|
|
@@ -176,7 +204,17 @@ export async function executePrivateRegistryPublish(input, _context) {
|
|
|
176
204
|
*/
|
|
177
205
|
export async function executePrivateRegistryManage(input, _context) {
|
|
178
206
|
const dataSource = isSupabaseConfigured() ? 'live' : 'stub';
|
|
179
|
-
|
|
207
|
+
let teamId;
|
|
208
|
+
try {
|
|
209
|
+
teamId = await resolveTeamId();
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
dataSource,
|
|
215
|
+
error: err instanceof Error ? err.message : 'Failed to resolve team from license key.',
|
|
216
|
+
};
|
|
217
|
+
}
|
|
180
218
|
switch (input.action) {
|
|
181
219
|
case 'list': {
|
|
182
220
|
const skills = await service.list(teamId, input.version);
|