@skillsmith/mcp-server 0.4.7 → 0.4.8

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.
Files changed (196) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +20 -0
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/__tests__/tool-dispatch.test.d.ts +8 -0
  5. package/dist/src/__tests__/tool-dispatch.test.d.ts.map +1 -0
  6. package/dist/src/__tests__/tool-dispatch.test.js +144 -0
  7. package/dist/src/__tests__/tool-dispatch.test.js.map +1 -0
  8. package/dist/src/index.js +31 -2
  9. package/dist/src/index.js.map +1 -1
  10. package/dist/src/middleware/license.d.ts +23 -0
  11. package/dist/src/middleware/license.d.ts.map +1 -1
  12. package/dist/src/middleware/license.js +28 -0
  13. package/dist/src/middleware/license.js.map +1 -1
  14. package/dist/src/middleware/toolFeatureMapping.d.ts.map +1 -1
  15. package/dist/src/middleware/toolFeatureMapping.js +4 -1
  16. package/dist/src/middleware/toolFeatureMapping.js.map +1 -1
  17. package/dist/src/supabase-client.d.ts +24 -0
  18. package/dist/src/supabase-client.d.ts.map +1 -0
  19. package/dist/src/supabase-client.js +63 -0
  20. package/dist/src/supabase-client.js.map +1 -0
  21. package/dist/src/tool-dispatch.d.ts.map +1 -1
  22. package/dist/src/tool-dispatch.js +71 -62
  23. package/dist/src/tool-dispatch.js.map +1 -1
  24. package/dist/src/tools/analytics.d.ts +155 -0
  25. package/dist/src/tools/analytics.d.ts.map +1 -0
  26. package/dist/src/tools/analytics.js +321 -0
  27. package/dist/src/tools/analytics.js.map +1 -0
  28. package/dist/src/tools/analytics.service.d.ts +46 -0
  29. package/dist/src/tools/analytics.service.d.ts.map +1 -0
  30. package/dist/src/tools/analytics.service.js +69 -0
  31. package/dist/src/tools/analytics.service.js.map +1 -0
  32. package/dist/src/tools/analytics.service.test.d.ts +6 -0
  33. package/dist/src/tools/analytics.service.test.d.ts.map +1 -0
  34. package/dist/src/tools/analytics.service.test.js +151 -0
  35. package/dist/src/tools/analytics.service.test.js.map +1 -0
  36. package/dist/src/tools/analytics.stub.d.ts +27 -0
  37. package/dist/src/tools/analytics.stub.d.ts.map +1 -0
  38. package/dist/src/tools/analytics.stub.js +200 -0
  39. package/dist/src/tools/analytics.stub.js.map +1 -0
  40. package/dist/src/tools/analytics.test.d.ts +6 -0
  41. package/dist/src/tools/analytics.test.d.ts.map +1 -0
  42. package/dist/src/tools/analytics.test.js +146 -0
  43. package/dist/src/tools/analytics.test.js.map +1 -0
  44. package/dist/src/tools/analyze.d.ts.map +1 -1
  45. package/dist/src/tools/analyze.js +7 -1
  46. package/dist/src/tools/analyze.js.map +1 -1
  47. package/dist/src/tools/audit-tools.d.ts +172 -0
  48. package/dist/src/tools/audit-tools.d.ts.map +1 -0
  49. package/dist/src/tools/audit-tools.js +187 -0
  50. package/dist/src/tools/audit-tools.js.map +1 -0
  51. package/dist/src/tools/audit-tools.test.d.ts +10 -0
  52. package/dist/src/tools/audit-tools.test.d.ts.map +1 -0
  53. package/dist/src/tools/audit-tools.test.js +147 -0
  54. package/dist/src/tools/audit-tools.test.js.map +1 -0
  55. package/dist/src/tools/compliance-tools.d.ts +103 -0
  56. package/dist/src/tools/compliance-tools.d.ts.map +1 -0
  57. package/dist/src/tools/compliance-tools.js +257 -0
  58. package/dist/src/tools/compliance-tools.js.map +1 -0
  59. package/dist/src/tools/compliance-tools.service.d.ts +19 -0
  60. package/dist/src/tools/compliance-tools.service.d.ts.map +1 -0
  61. package/dist/src/tools/compliance-tools.service.js +107 -0
  62. package/dist/src/tools/compliance-tools.service.js.map +1 -0
  63. package/dist/src/tools/compliance-tools.service.test.d.ts +6 -0
  64. package/dist/src/tools/compliance-tools.service.test.d.ts.map +1 -0
  65. package/dist/src/tools/compliance-tools.service.test.js +140 -0
  66. package/dist/src/tools/compliance-tools.service.test.js.map +1 -0
  67. package/dist/src/tools/compliance-tools.test.d.ts +6 -0
  68. package/dist/src/tools/compliance-tools.test.d.ts.map +1 -0
  69. package/dist/src/tools/compliance-tools.test.js +136 -0
  70. package/dist/src/tools/compliance-tools.test.js.map +1 -0
  71. package/dist/src/tools/get-skill.d.ts.map +1 -1
  72. package/dist/src/tools/get-skill.js +24 -1
  73. package/dist/src/tools/get-skill.js.map +1 -1
  74. package/dist/src/tools/index-local.d.ts.map +1 -1
  75. package/dist/src/tools/index-local.js +4 -0
  76. package/dist/src/tools/index-local.js.map +1 -1
  77. package/dist/src/tools/install.d.ts.map +1 -1
  78. package/dist/src/tools/install.js +2 -0
  79. package/dist/src/tools/install.js.map +1 -1
  80. package/dist/src/tools/install.tool.d.ts +4 -0
  81. package/dist/src/tools/install.tool.d.ts.map +1 -1
  82. package/dist/src/tools/install.tool.js +4 -0
  83. package/dist/src/tools/install.tool.js.map +1 -1
  84. package/dist/src/tools/install.types.d.ts +4 -0
  85. package/dist/src/tools/install.types.d.ts.map +1 -1
  86. package/dist/src/tools/install.types.js +5 -0
  87. package/dist/src/tools/install.types.js.map +1 -1
  88. package/dist/src/tools/integration-tools.d.ts +210 -0
  89. package/dist/src/tools/integration-tools.d.ts.map +1 -0
  90. package/dist/src/tools/integration-tools.js +264 -0
  91. package/dist/src/tools/integration-tools.js.map +1 -0
  92. package/dist/src/tools/integration-tools.service.d.ts +54 -0
  93. package/dist/src/tools/integration-tools.service.d.ts.map +1 -0
  94. package/dist/src/tools/integration-tools.service.js +299 -0
  95. package/dist/src/tools/integration-tools.service.js.map +1 -0
  96. package/dist/src/tools/integration-tools.service.test.d.ts +8 -0
  97. package/dist/src/tools/integration-tools.service.test.d.ts.map +1 -0
  98. package/dist/src/tools/integration-tools.service.test.js +401 -0
  99. package/dist/src/tools/integration-tools.service.test.js.map +1 -0
  100. package/dist/src/tools/integration-tools.stub.d.ts +13 -0
  101. package/dist/src/tools/integration-tools.stub.d.ts.map +1 -0
  102. package/dist/src/tools/integration-tools.stub.js +133 -0
  103. package/dist/src/tools/integration-tools.stub.js.map +1 -0
  104. package/dist/src/tools/integration-tools.test.d.ts +6 -0
  105. package/dist/src/tools/integration-tools.test.d.ts.map +1 -0
  106. package/dist/src/tools/integration-tools.test.js +259 -0
  107. package/dist/src/tools/integration-tools.test.js.map +1 -0
  108. package/dist/src/tools/publish-private.d.ts +60 -0
  109. package/dist/src/tools/publish-private.d.ts.map +1 -0
  110. package/dist/src/tools/publish-private.js +85 -0
  111. package/dist/src/tools/publish-private.js.map +1 -0
  112. package/dist/src/tools/publish-private.test.d.ts +6 -0
  113. package/dist/src/tools/publish-private.test.d.ts.map +1 -0
  114. package/dist/src/tools/publish-private.test.js +118 -0
  115. package/dist/src/tools/publish-private.test.js.map +1 -0
  116. package/dist/src/tools/publish.types.d.ts +2 -2
  117. package/dist/src/tools/rbac-tools.d.ts +180 -0
  118. package/dist/src/tools/rbac-tools.d.ts.map +1 -0
  119. package/dist/src/tools/rbac-tools.js +300 -0
  120. package/dist/src/tools/rbac-tools.js.map +1 -0
  121. package/dist/src/tools/rbac-tools.test.d.ts +6 -0
  122. package/dist/src/tools/rbac-tools.test.d.ts.map +1 -0
  123. package/dist/src/tools/rbac-tools.test.js +299 -0
  124. package/dist/src/tools/rbac-tools.test.js.map +1 -0
  125. package/dist/src/tools/rbac-tools.types.d.ts +70 -0
  126. package/dist/src/tools/rbac-tools.types.d.ts.map +1 -0
  127. package/dist/src/tools/rbac-tools.types.js +119 -0
  128. package/dist/src/tools/rbac-tools.types.js.map +1 -0
  129. package/dist/src/tools/recommend.helpers.d.ts.map +1 -1
  130. package/dist/src/tools/recommend.helpers.js +2 -1
  131. package/dist/src/tools/recommend.helpers.js.map +1 -1
  132. package/dist/src/tools/recommend.js +3 -3
  133. package/dist/src/tools/recommend.js.map +1 -1
  134. package/dist/src/tools/registry-tools.d.ts +133 -0
  135. package/dist/src/tools/registry-tools.d.ts.map +1 -0
  136. package/dist/src/tools/registry-tools.js +246 -0
  137. package/dist/src/tools/registry-tools.js.map +1 -0
  138. package/dist/src/tools/registry-tools.test.d.ts +6 -0
  139. package/dist/src/tools/registry-tools.test.d.ts.map +1 -0
  140. package/dist/src/tools/registry-tools.test.js +167 -0
  141. package/dist/src/tools/registry-tools.test.js.map +1 -0
  142. package/dist/src/tools/search.formatter.d.ts.map +1 -1
  143. package/dist/src/tools/search.formatter.js +11 -1
  144. package/dist/src/tools/search.formatter.js.map +1 -1
  145. package/dist/src/tools/skill-audit.d.ts +3 -3
  146. package/dist/src/tools/skill-audit.d.ts.map +1 -1
  147. package/dist/src/tools/skill-audit.js +5 -5
  148. package/dist/src/tools/skill-audit.js.map +1 -1
  149. package/dist/src/tools/skill-audit.test.js +5 -5
  150. package/dist/src/tools/skill-audit.test.js.map +1 -1
  151. package/dist/src/tools/skill-rescan.d.ts.map +1 -1
  152. package/dist/src/tools/skill-rescan.js +2 -3
  153. package/dist/src/tools/skill-rescan.js.map +1 -1
  154. package/dist/src/tools/skill-rescan.test.js +2 -2
  155. package/dist/src/tools/skill-rescan.test.js.map +1 -1
  156. package/dist/src/tools/sso-tools.d.ts +136 -0
  157. package/dist/src/tools/sso-tools.d.ts.map +1 -0
  158. package/dist/src/tools/sso-tools.js +214 -0
  159. package/dist/src/tools/sso-tools.js.map +1 -0
  160. package/dist/src/tools/sso-tools.test.d.ts +6 -0
  161. package/dist/src/tools/sso-tools.test.d.ts.map +1 -0
  162. package/dist/src/tools/sso-tools.test.js +167 -0
  163. package/dist/src/tools/sso-tools.test.js.map +1 -0
  164. package/dist/src/tools/suggest.d.ts.map +1 -1
  165. package/dist/src/tools/suggest.js +7 -1
  166. package/dist/src/tools/suggest.js.map +1 -1
  167. package/dist/src/tools/team-workspace.d.ts +179 -0
  168. package/dist/src/tools/team-workspace.d.ts.map +1 -0
  169. package/dist/src/tools/team-workspace.js +243 -0
  170. package/dist/src/tools/team-workspace.js.map +1 -0
  171. package/dist/src/tools/team-workspace.stub.d.ts +13 -0
  172. package/dist/src/tools/team-workspace.stub.d.ts.map +1 -0
  173. package/dist/src/tools/team-workspace.stub.js +76 -0
  174. package/dist/src/tools/team-workspace.stub.js.map +1 -0
  175. package/dist/src/tools/team-workspace.test.d.ts +7 -0
  176. package/dist/src/tools/team-workspace.test.d.ts.map +1 -0
  177. package/dist/src/tools/team-workspace.test.js +273 -0
  178. package/dist/src/tools/team-workspace.test.js.map +1 -0
  179. package/dist/src/utils/team-resolver.d.ts +20 -0
  180. package/dist/src/utils/team-resolver.d.ts.map +1 -0
  181. package/dist/src/utils/team-resolver.js +52 -0
  182. package/dist/src/utils/team-resolver.js.map +1 -0
  183. package/dist/src/utils/url-validator.d.ts +24 -0
  184. package/dist/src/utils/url-validator.d.ts.map +1 -0
  185. package/dist/src/utils/url-validator.js +59 -0
  186. package/dist/src/utils/url-validator.js.map +1 -0
  187. package/dist/tests/integration/recommend.integration.test.js +1 -1
  188. package/dist/tests/integration/recommend.integration.test.js.map +1 -1
  189. package/dist/tests/recommend.test.js +1 -1
  190. package/dist/tests/recommend.test.js.map +1 -1
  191. package/package.json +3 -3
  192. package/server.json +2 -2
  193. package/dist/vitest.config.d.ts +0 -6
  194. package/dist/vitest.config.d.ts.map +0 -1
  195. package/dist/vitest.config.js +0 -23
  196. package/dist/vitest.config.js.map +0 -1
@@ -0,0 +1,243 @@
1
+ /**
2
+ * @fileoverview team_workspace and share_skill MCP tools
3
+ * @module @skillsmith/mcp-server/tools/team-workspace
4
+ * @see SMI-3895: Team Workspace + Share Skill MCP Tools
5
+ * @see SMI-3898: Skill Sharing Controls
6
+ *
7
+ * Registry-mediated architecture: workspace metadata lives in Supabase
8
+ * (server-side), not local SQLite. MCP tools call Supabase RPCs for
9
+ * workspace CRUD. License key resolves to team_id for auth.
10
+ *
11
+ * Tier gate: Team (team_workspaces feature flag).
12
+ */
13
+ import { z } from 'zod';
14
+ import { isSupabaseConfigured } from '../supabase-client.js';
15
+ import { createStubService } from './team-workspace.stub.js';
16
+ // Re-export stub factory for external consumers and tests
17
+ export { createStubService } from './team-workspace.stub.js';
18
+ // ============================================================================
19
+ // Input schemas
20
+ // ============================================================================
21
+ export const teamWorkspaceInputSchema = z.object({
22
+ action: z.enum(['create', 'list', 'get', 'delete']),
23
+ name: z.string().min(1).max(100).optional(),
24
+ description: z.string().max(500).optional(),
25
+ workspaceId: z.string().uuid().optional(),
26
+ });
27
+ export const shareSkillInputSchema = z.object({
28
+ action: z.enum(['add', 'remove', 'list']),
29
+ workspaceId: z.string().uuid(),
30
+ skillId: z
31
+ .string()
32
+ .regex(/^[^/]+\/[^/]+$/, 'Must be author/name format')
33
+ .optional(),
34
+ });
35
+ // ============================================================================
36
+ // Tool schemas for MCP registration
37
+ // ============================================================================
38
+ export const teamWorkspaceToolSchema = {
39
+ name: 'team_workspace',
40
+ description: 'Manage team workspaces (create, list, get, delete). Requires Team tier license.',
41
+ inputSchema: {
42
+ type: 'object',
43
+ properties: {
44
+ action: {
45
+ type: 'string',
46
+ enum: ['create', 'list', 'get', 'delete'],
47
+ description: 'Workspace operation to perform',
48
+ },
49
+ name: {
50
+ type: 'string',
51
+ description: 'Workspace name (required for create)',
52
+ },
53
+ description: {
54
+ type: 'string',
55
+ description: 'Workspace description (optional for create)',
56
+ },
57
+ workspaceId: {
58
+ type: 'string',
59
+ description: 'Workspace ID (required for get/delete)',
60
+ },
61
+ },
62
+ required: ['action'],
63
+ },
64
+ };
65
+ export const shareSkillToolSchema = {
66
+ name: 'share_skill',
67
+ description: 'Add, remove, or list skills in a team workspace. Requires Team tier license.',
68
+ inputSchema: {
69
+ type: 'object',
70
+ properties: {
71
+ action: {
72
+ type: 'string',
73
+ enum: ['add', 'remove', 'list'],
74
+ description: 'Sharing operation to perform',
75
+ },
76
+ workspaceId: {
77
+ type: 'string',
78
+ description: 'Workspace ID to share skill with',
79
+ },
80
+ skillId: {
81
+ type: 'string',
82
+ description: 'Skill ID in author/name format (required for add/remove)',
83
+ },
84
+ },
85
+ required: ['action', 'workspaceId'],
86
+ },
87
+ };
88
+ // ============================================================================
89
+ // SMI-3898: Sharing policy enforcement
90
+ // ============================================================================
91
+ /**
92
+ * Match a skill ID against a glob-like pattern.
93
+ * Supports star as a wildcard segment (e.g. "author/star", "star/name").
94
+ */
95
+ export function matchesPattern(skillId, pattern) {
96
+ const [skillAuthor, skillName] = skillId.split('/');
97
+ const [patternAuthor, patternName] = pattern.split('/');
98
+ if (!patternAuthor || !patternName)
99
+ return false;
100
+ const authorMatch = patternAuthor === '*' || patternAuthor === skillAuthor;
101
+ const nameMatch = patternName === '*' || patternName === skillName;
102
+ return authorMatch && nameMatch;
103
+ }
104
+ /**
105
+ * Check if a skill ID is allowed by the sharing policy.
106
+ * Returns an error message if denied, or null if allowed.
107
+ */
108
+ export function checkSharingPolicy(skillId, policy) {
109
+ if (!policy)
110
+ return null;
111
+ // Deny list takes precedence
112
+ if (policy.denyList.length > 0) {
113
+ const denied = policy.denyList.some((pattern) => matchesPattern(skillId, pattern));
114
+ if (denied) {
115
+ return `Skill "${skillId}" is blocked by the workspace deny list.`;
116
+ }
117
+ }
118
+ // If allow list is non-empty, skill must match at least one pattern
119
+ if (policy.allowList.length > 0) {
120
+ const allowed = policy.allowList.some((pattern) => matchesPattern(skillId, pattern));
121
+ if (!allowed) {
122
+ return `Skill "${skillId}" is not in the workspace allow list.`;
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+ // Module-level singleton (swapped when Supabase RPCs are ready)
128
+ let service = createStubService();
129
+ /** Replace the workspace service implementation (for testing or Supabase swap) */
130
+ export function setTeamWorkspaceService(svc) {
131
+ service = svc;
132
+ }
133
+ /** Get the current workspace service instance */
134
+ export function getTeamWorkspaceService() {
135
+ return service;
136
+ }
137
+ // ============================================================================
138
+ // Handlers
139
+ // ============================================================================
140
+ /**
141
+ * Execute a team_workspace operation.
142
+ *
143
+ * @param input - Validated workspace input
144
+ * @param _context - Tool context (unused until Supabase integration)
145
+ */
146
+ export async function executeTeamWorkspace(input, _context) {
147
+ const dataSource = isSupabaseConfigured() ? 'live' : 'stub';
148
+ // TODO: Extract license key from context/env and resolve team_id via Supabase
149
+ const licenseKey = process.env.SKILLSMITH_LICENSE_KEY ?? '';
150
+ const teamId = await service.resolveTeamId(licenseKey);
151
+ switch (input.action) {
152
+ case 'create': {
153
+ if (!input.name) {
154
+ return { success: false, dataSource, error: 'Name is required for workspace creation.' };
155
+ }
156
+ const ws = await service.createWorkspace(teamId, input.name, input.description);
157
+ return {
158
+ success: true,
159
+ dataSource,
160
+ workspace: ws,
161
+ message: `Workspace "${ws.name}" created.`,
162
+ };
163
+ }
164
+ case 'list': {
165
+ const list = await service.listWorkspaces(teamId);
166
+ return {
167
+ success: true,
168
+ dataSource,
169
+ workspaces: list,
170
+ message: `Found ${list.length} workspace(s).`,
171
+ };
172
+ }
173
+ case 'get': {
174
+ if (!input.workspaceId) {
175
+ return { success: false, dataSource, error: 'workspaceId is required for get.' };
176
+ }
177
+ const ws = await service.getWorkspace(teamId, input.workspaceId);
178
+ if (!ws)
179
+ return { success: false, dataSource, error: 'Workspace not found.' };
180
+ return { success: true, dataSource, workspace: ws };
181
+ }
182
+ case 'delete': {
183
+ if (!input.workspaceId) {
184
+ return { success: false, dataSource, error: 'workspaceId is required for delete.' };
185
+ }
186
+ const deleted = await service.deleteWorkspace(teamId, input.workspaceId);
187
+ if (!deleted)
188
+ return { success: false, dataSource, error: 'Workspace not found.' };
189
+ return { success: true, dataSource, message: 'Workspace deleted.' };
190
+ }
191
+ }
192
+ }
193
+ /**
194
+ * Execute a share_skill operation.
195
+ *
196
+ * SMI-3898: Checks allowList/denyList before adding a skill.
197
+ *
198
+ * @param input - Validated share input
199
+ * @param _context - Tool context (unused until Supabase integration)
200
+ */
201
+ export async function executeShareSkill(input, _context) {
202
+ const dataSource = isSupabaseConfigured() ? 'live' : 'stub';
203
+ const licenseKey = process.env.SKILLSMITH_LICENSE_KEY ?? '';
204
+ const teamId = await service.resolveTeamId(licenseKey);
205
+ switch (input.action) {
206
+ case 'add': {
207
+ if (!input.skillId) {
208
+ return { success: false, dataSource, error: 'skillId is required for add.' };
209
+ }
210
+ // SMI-3898: Check sharing policy before adding
211
+ const settings = await service.getWorkspaceSettings(teamId, input.workspaceId);
212
+ const policyError = checkSharingPolicy(input.skillId, settings.sharing);
213
+ if (policyError) {
214
+ return { success: false, dataSource, error: policyError };
215
+ }
216
+ const skill = await service.addSkill(teamId, input.workspaceId, input.skillId);
217
+ return {
218
+ success: true,
219
+ dataSource,
220
+ skills: [skill],
221
+ message: `Skill "${input.skillId}" shared to workspace.`,
222
+ };
223
+ }
224
+ case 'remove': {
225
+ if (!input.skillId) {
226
+ return { success: false, dataSource, error: 'skillId is required for remove.' };
227
+ }
228
+ const removed = await service.removeSkill(teamId, input.workspaceId, input.skillId);
229
+ if (!removed)
230
+ return { success: false, dataSource, error: 'Skill not found in workspace.' };
231
+ return {
232
+ success: true,
233
+ dataSource,
234
+ message: `Skill "${input.skillId}" removed from workspace.`,
235
+ };
236
+ }
237
+ case 'list': {
238
+ const list = await service.listSkills(teamId, input.workspaceId);
239
+ return { success: true, dataSource, skills: list, message: `${list.length} shared skill(s).` };
240
+ }
241
+ }
242
+ }
243
+ //# sourceMappingURL=team-workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-workspace.js","sourceRoot":"","sources":["../../../src/tools/team-workspace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview Stub service for team workspace MCP tools
3
+ * @module @skillsmith/mcp-server/tools/team-workspace.stub
4
+ * @see SMI-3895: Team Workspace + Share Skill MCP Tools
5
+ * @see SMI-3914: Wave 0 stub extraction
6
+ *
7
+ * Extracted from team-workspace.ts for file-size compliance.
8
+ * Provides in-memory stub implementation for workspace CRUD and skill sharing.
9
+ */
10
+ import type { TeamWorkspaceService } from './team-workspace.js';
11
+ /** @internal Exported for testing */
12
+ export declare function createStubService(): TeamWorkspaceService;
13
+ //# sourceMappingURL=team-workspace.stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-workspace.stub.d.ts","sourceRoot":"","sources":["../../../src/tools/team-workspace.stub.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,oBAAoB,EAIrB,MAAM,qBAAqB,CAAA;AAM5B,qCAAqC;AACrC,wBAAgB,iBAAiB,IAAI,oBAAoB,CAsExD"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @fileoverview Stub service for team workspace MCP tools
3
+ * @module @skillsmith/mcp-server/tools/team-workspace.stub
4
+ * @see SMI-3895: Team Workspace + Share Skill MCP Tools
5
+ * @see SMI-3914: Wave 0 stub extraction
6
+ *
7
+ * Extracted from team-workspace.ts for file-size compliance.
8
+ * Provides in-memory stub implementation for workspace CRUD and skill sharing.
9
+ */
10
+ // ============================================================================
11
+ // Stub service factory
12
+ // ============================================================================
13
+ /** @internal Exported for testing */
14
+ export function createStubService() {
15
+ // In-memory store for stub data
16
+ const workspaces = new Map();
17
+ const skills = new Map();
18
+ return {
19
+ async resolveTeamId(_licenseKey) {
20
+ // TODO: Replace with Supabase RPC: license_key -> subscription -> team_id
21
+ return 'team_stub_00000000-0000-0000-0000-000000000000';
22
+ },
23
+ async createWorkspace(teamId, name, description) {
24
+ const id = crypto.randomUUID();
25
+ const now = new Date().toISOString();
26
+ const ws = {
27
+ id,
28
+ name,
29
+ description: description ?? null,
30
+ teamId,
31
+ settings: {},
32
+ createdAt: now,
33
+ updatedAt: now,
34
+ };
35
+ workspaces.set(id, ws);
36
+ return ws;
37
+ },
38
+ async listWorkspaces(teamId) {
39
+ return [...workspaces.values()].filter((ws) => ws.teamId === teamId);
40
+ },
41
+ async getWorkspace(_teamId, workspaceId) {
42
+ return workspaces.get(workspaceId) ?? null;
43
+ },
44
+ async deleteWorkspace(_teamId, workspaceId) {
45
+ const existed = workspaces.has(workspaceId);
46
+ workspaces.delete(workspaceId);
47
+ skills.delete(workspaceId);
48
+ return existed;
49
+ },
50
+ async addSkill(_teamId, workspaceId, skillId) {
51
+ const entry = {
52
+ skillId,
53
+ addedBy: 'current-user',
54
+ addedAt: new Date().toISOString(),
55
+ };
56
+ const list = skills.get(workspaceId) ?? [];
57
+ list.push(entry);
58
+ skills.set(workspaceId, list);
59
+ return entry;
60
+ },
61
+ async removeSkill(_teamId, workspaceId, skillId) {
62
+ const list = skills.get(workspaceId) ?? [];
63
+ const filtered = list.filter((s) => s.skillId !== skillId);
64
+ skills.set(workspaceId, filtered);
65
+ return filtered.length < list.length;
66
+ },
67
+ async listSkills(_teamId, workspaceId) {
68
+ return skills.get(workspaceId) ?? [];
69
+ },
70
+ async getWorkspaceSettings(_teamId, workspaceId) {
71
+ const ws = workspaces.get(workspaceId);
72
+ return ws?.settings ?? {};
73
+ },
74
+ };
75
+ }
76
+ //# sourceMappingURL=team-workspace.stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-workspace.stub.js","sourceRoot":"","sources":["../../../src/tools/team-workspace.stub.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @fileoverview Unit tests for team_workspace and share_skill MCP tools
3
+ * @see SMI-3895: Team Workspace + Share Skill MCP Tools
4
+ * @see SMI-3898: Skill Sharing Controls
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=team-workspace.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-workspace.test.d.ts","sourceRoot":"","sources":["../../../src/tools/team-workspace.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,273 @@
1
+ /**
2
+ * @fileoverview Unit tests for team_workspace and share_skill MCP tools
3
+ * @see SMI-3895: Team Workspace + Share Skill MCP Tools
4
+ * @see SMI-3898: Skill Sharing Controls
5
+ */
6
+ import { describe, it, expect, beforeEach } from 'vitest';
7
+ import { teamWorkspaceInputSchema, shareSkillInputSchema, executeTeamWorkspace, executeShareSkill, createStubService, setTeamWorkspaceService, matchesPattern, checkSharingPolicy, } from './team-workspace.js';
8
+ // ============================================================================
9
+ // Helpers
10
+ // ============================================================================
11
+ function makeContext() {
12
+ return {};
13
+ }
14
+ // ============================================================================
15
+ // Schema validation
16
+ // ============================================================================
17
+ describe('teamWorkspaceInputSchema', () => {
18
+ it('accepts valid create input', () => {
19
+ const result = teamWorkspaceInputSchema.parse({
20
+ action: 'create',
21
+ name: 'My Workspace',
22
+ description: 'A test workspace',
23
+ });
24
+ expect(result.action).toBe('create');
25
+ expect(result.name).toBe('My Workspace');
26
+ });
27
+ it('accepts list action with no extra fields', () => {
28
+ const result = teamWorkspaceInputSchema.parse({ action: 'list' });
29
+ expect(result.action).toBe('list');
30
+ });
31
+ it('accepts get action with workspaceId', () => {
32
+ const result = teamWorkspaceInputSchema.parse({
33
+ action: 'get',
34
+ workspaceId: '00000000-0000-0000-0000-000000000001',
35
+ });
36
+ expect(result.workspaceId).toBeDefined();
37
+ });
38
+ it('rejects invalid action', () => {
39
+ expect(() => teamWorkspaceInputSchema.parse({ action: 'invalid' })).toThrow();
40
+ });
41
+ it('rejects non-UUID workspaceId', () => {
42
+ expect(() => teamWorkspaceInputSchema.parse({ action: 'get', workspaceId: 'not-a-uuid' })).toThrow();
43
+ });
44
+ });
45
+ describe('shareSkillInputSchema', () => {
46
+ it('accepts valid add input', () => {
47
+ const result = shareSkillInputSchema.parse({
48
+ action: 'add',
49
+ workspaceId: '00000000-0000-0000-0000-000000000001',
50
+ skillId: 'author/skill-name',
51
+ });
52
+ expect(result.action).toBe('add');
53
+ expect(result.skillId).toBe('author/skill-name');
54
+ });
55
+ it('accepts list action without skillId', () => {
56
+ const result = shareSkillInputSchema.parse({
57
+ action: 'list',
58
+ workspaceId: '00000000-0000-0000-0000-000000000001',
59
+ });
60
+ expect(result.action).toBe('list');
61
+ });
62
+ it('rejects invalid skillId format', () => {
63
+ expect(() => shareSkillInputSchema.parse({
64
+ action: 'add',
65
+ workspaceId: '00000000-0000-0000-0000-000000000001',
66
+ skillId: 'no-slash',
67
+ })).toThrow();
68
+ });
69
+ it('rejects missing workspaceId', () => {
70
+ expect(() => shareSkillInputSchema.parse({ action: 'list' })).toThrow();
71
+ });
72
+ });
73
+ // ============================================================================
74
+ // Pattern matching (SMI-3898)
75
+ // ============================================================================
76
+ describe('matchesPattern', () => {
77
+ it('matches exact skill ID', () => {
78
+ expect(matchesPattern('author/skill', 'author/skill')).toBe(true);
79
+ });
80
+ it('matches wildcard author', () => {
81
+ expect(matchesPattern('author/skill', '*/skill')).toBe(true);
82
+ });
83
+ it('matches wildcard name', () => {
84
+ expect(matchesPattern('author/skill', 'author/*')).toBe(true);
85
+ });
86
+ it('matches double wildcard', () => {
87
+ expect(matchesPattern('author/skill', '*/*')).toBe(true);
88
+ });
89
+ it('rejects mismatched author', () => {
90
+ expect(matchesPattern('author/skill', 'other/skill')).toBe(false);
91
+ });
92
+ it('rejects mismatched name', () => {
93
+ expect(matchesPattern('author/skill', 'author/other')).toBe(false);
94
+ });
95
+ it('rejects malformed pattern', () => {
96
+ expect(matchesPattern('author/skill', 'noslash')).toBe(false);
97
+ });
98
+ });
99
+ describe('checkSharingPolicy', () => {
100
+ it('returns null when no policy', () => {
101
+ expect(checkSharingPolicy('author/skill', undefined)).toBeNull();
102
+ });
103
+ it('returns null when policy has empty lists', () => {
104
+ const policy = { requireApproval: false, allowList: [], denyList: [] };
105
+ expect(checkSharingPolicy('author/skill', policy)).toBeNull();
106
+ });
107
+ it('returns error when skill matches deny list', () => {
108
+ const policy = {
109
+ requireApproval: false,
110
+ allowList: [],
111
+ denyList: ['untrusted/*'],
112
+ };
113
+ const result = checkSharingPolicy('untrusted/evil-skill', policy);
114
+ expect(result).toContain('blocked by the workspace deny list');
115
+ });
116
+ it('returns null when skill does NOT match deny list', () => {
117
+ const policy = {
118
+ requireApproval: false,
119
+ allowList: [],
120
+ denyList: ['untrusted/*'],
121
+ };
122
+ expect(checkSharingPolicy('trusted/good-skill', policy)).toBeNull();
123
+ });
124
+ it('returns error when skill not in allow list', () => {
125
+ const policy = {
126
+ requireApproval: false,
127
+ allowList: ['approved/*'],
128
+ denyList: [],
129
+ };
130
+ const result = checkSharingPolicy('other/skill', policy);
131
+ expect(result).toContain('not in the workspace allow list');
132
+ });
133
+ it('returns null when skill matches allow list', () => {
134
+ const policy = {
135
+ requireApproval: false,
136
+ allowList: ['approved/*'],
137
+ denyList: [],
138
+ };
139
+ expect(checkSharingPolicy('approved/skill', policy)).toBeNull();
140
+ });
141
+ it('deny list takes precedence over allow list', () => {
142
+ const policy = {
143
+ requireApproval: false,
144
+ allowList: ['*/*'],
145
+ denyList: ['evil/*'],
146
+ };
147
+ const result = checkSharingPolicy('evil/skill', policy);
148
+ expect(result).toContain('blocked by the workspace deny list');
149
+ });
150
+ });
151
+ // ============================================================================
152
+ // executeTeamWorkspace
153
+ // ============================================================================
154
+ describe('executeTeamWorkspace', () => {
155
+ beforeEach(() => {
156
+ setTeamWorkspaceService(createStubService());
157
+ });
158
+ it('creates a workspace', async () => {
159
+ const result = await executeTeamWorkspace({ action: 'create', name: 'Dev Team' }, makeContext());
160
+ expect(result.success).toBe(true);
161
+ expect(result.workspace).toBeDefined();
162
+ expect(result.workspace.name).toBe('Dev Team');
163
+ });
164
+ it('returns error when create missing name', async () => {
165
+ const result = await executeTeamWorkspace({ action: 'create' }, makeContext());
166
+ expect(result.success).toBe(false);
167
+ expect(result.error).toContain('Name is required');
168
+ });
169
+ it('lists workspaces', async () => {
170
+ await executeTeamWorkspace({ action: 'create', name: 'WS1' }, makeContext());
171
+ await executeTeamWorkspace({ action: 'create', name: 'WS2' }, makeContext());
172
+ const result = await executeTeamWorkspace({ action: 'list' }, makeContext());
173
+ expect(result.success).toBe(true);
174
+ expect(result.workspaces).toHaveLength(2);
175
+ });
176
+ it('gets a workspace by ID', async () => {
177
+ const created = await executeTeamWorkspace({ action: 'create', name: 'Test' }, makeContext());
178
+ const result = await executeTeamWorkspace({ action: 'get', workspaceId: created.workspace.id }, makeContext());
179
+ expect(result.success).toBe(true);
180
+ expect(result.workspace.name).toBe('Test');
181
+ });
182
+ it('returns error for get without workspaceId', async () => {
183
+ const result = await executeTeamWorkspace({ action: 'get' }, makeContext());
184
+ expect(result.success).toBe(false);
185
+ expect(result.error).toContain('workspaceId is required');
186
+ });
187
+ it('returns error for nonexistent workspace', async () => {
188
+ const result = await executeTeamWorkspace({ action: 'get', workspaceId: '00000000-0000-0000-0000-000000000099' }, makeContext());
189
+ expect(result.success).toBe(false);
190
+ expect(result.error).toContain('not found');
191
+ });
192
+ it('deletes a workspace', async () => {
193
+ const created = await executeTeamWorkspace({ action: 'create', name: 'ToDelete' }, makeContext());
194
+ const result = await executeTeamWorkspace({ action: 'delete', workspaceId: created.workspace.id }, makeContext());
195
+ expect(result.success).toBe(true);
196
+ expect(result.message).toContain('deleted');
197
+ });
198
+ it('returns error deleting nonexistent workspace', async () => {
199
+ const result = await executeTeamWorkspace({ action: 'delete', workspaceId: '00000000-0000-0000-0000-000000000099' }, makeContext());
200
+ expect(result.success).toBe(false);
201
+ });
202
+ });
203
+ // ============================================================================
204
+ // executeShareSkill
205
+ // ============================================================================
206
+ describe('executeShareSkill', () => {
207
+ let wsId;
208
+ beforeEach(async () => {
209
+ setTeamWorkspaceService(createStubService());
210
+ // Create a workspace for sharing tests
211
+ const ws = await executeTeamWorkspace({ action: 'create', name: 'Share Test' }, makeContext());
212
+ wsId = ws.workspace.id;
213
+ });
214
+ it('adds a skill to workspace', async () => {
215
+ const result = await executeShareSkill({ action: 'add', workspaceId: wsId, skillId: 'author/my-skill' }, makeContext());
216
+ expect(result.success).toBe(true);
217
+ expect(result.skills).toHaveLength(1);
218
+ expect(result.skills[0].skillId).toBe('author/my-skill');
219
+ });
220
+ it('returns error when add missing skillId', async () => {
221
+ const result = await executeShareSkill({ action: 'add', workspaceId: wsId }, makeContext());
222
+ expect(result.success).toBe(false);
223
+ expect(result.error).toContain('skillId is required');
224
+ });
225
+ it('lists shared skills', async () => {
226
+ await executeShareSkill({ action: 'add', workspaceId: wsId, skillId: 'a/one' }, makeContext());
227
+ await executeShareSkill({ action: 'add', workspaceId: wsId, skillId: 'b/two' }, makeContext());
228
+ const result = await executeShareSkill({ action: 'list', workspaceId: wsId }, makeContext());
229
+ expect(result.success).toBe(true);
230
+ expect(result.skills).toHaveLength(2);
231
+ });
232
+ it('removes a skill from workspace', async () => {
233
+ await executeShareSkill({ action: 'add', workspaceId: wsId, skillId: 'a/one' }, makeContext());
234
+ const result = await executeShareSkill({ action: 'remove', workspaceId: wsId, skillId: 'a/one' }, makeContext());
235
+ expect(result.success).toBe(true);
236
+ expect(result.message).toContain('removed');
237
+ });
238
+ it('returns error removing nonexistent skill', async () => {
239
+ const result = await executeShareSkill({ action: 'remove', workspaceId: wsId, skillId: 'a/nonexistent' }, makeContext());
240
+ expect(result.success).toBe(false);
241
+ expect(result.error).toContain('not found');
242
+ });
243
+ // SMI-3898: Deny list enforcement
244
+ it('rejects skill matching deny list pattern', async () => {
245
+ // Create a service with deny list
246
+ const svc = createStubService();
247
+ // Override getWorkspaceSettings to return a deny list
248
+ const origCreate = svc.createWorkspace.bind(svc);
249
+ const origGetSettings = svc.getWorkspaceSettings.bind(svc);
250
+ // We need a custom service that returns settings with a deny list
251
+ const customSvc = {
252
+ ...svc,
253
+ createWorkspace: origCreate,
254
+ async getWorkspaceSettings(teamId, workspaceId) {
255
+ const base = await origGetSettings(teamId, workspaceId);
256
+ return {
257
+ ...base,
258
+ sharing: {
259
+ requireApproval: false,
260
+ allowList: [],
261
+ denyList: ['evil-author/*'],
262
+ },
263
+ };
264
+ },
265
+ };
266
+ setTeamWorkspaceService(customSvc);
267
+ const ws = await executeTeamWorkspace({ action: 'create', name: 'Policy Test' }, makeContext());
268
+ const result = await executeShareSkill({ action: 'add', workspaceId: ws.workspace.id, skillId: 'evil-author/bad-skill' }, makeContext());
269
+ expect(result.success).toBe(false);
270
+ expect(result.error).toContain('blocked by the workspace deny list');
271
+ });
272
+ });
273
+ //# sourceMappingURL=team-workspace.test.js.map