@gwdn/nexus-mcp 0.7.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.
Files changed (133) hide show
  1. package/.github/workflows/ci.yml +38 -0
  2. package/.github/workflows/release.yml +57 -0
  3. package/README.md +89 -0
  4. package/dist/auth.d.ts +39 -0
  5. package/dist/auth.d.ts.map +1 -0
  6. package/dist/auth.js +47 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/nexus-api.d.ts +35 -0
  9. package/dist/nexus-api.d.ts.map +1 -0
  10. package/dist/nexus-api.js +90 -0
  11. package/dist/nexus-api.js.map +1 -0
  12. package/dist/server.d.ts +67 -0
  13. package/dist/server.d.ts.map +1 -0
  14. package/dist/server.js +189 -0
  15. package/dist/server.js.map +1 -0
  16. package/dist/tools/add-task-note.d.ts +34 -0
  17. package/dist/tools/add-task-note.d.ts.map +1 -0
  18. package/dist/tools/add-task-note.js +39 -0
  19. package/dist/tools/add-task-note.js.map +1 -0
  20. package/dist/tools/append-session-entry.d.ts +53 -0
  21. package/dist/tools/append-session-entry.d.ts.map +1 -0
  22. package/dist/tools/append-session-entry.js +68 -0
  23. package/dist/tools/append-session-entry.js.map +1 -0
  24. package/dist/tools/create-task.d.ts +52 -0
  25. package/dist/tools/create-task.d.ts.map +1 -0
  26. package/dist/tools/create-task.js +51 -0
  27. package/dist/tools/create-task.js.map +1 -0
  28. package/dist/tools/decision-comments.d.ts +54 -0
  29. package/dist/tools/decision-comments.d.ts.map +1 -0
  30. package/dist/tools/decision-comments.js +81 -0
  31. package/dist/tools/decision-comments.js.map +1 -0
  32. package/dist/tools/get-document.d.ts +47 -0
  33. package/dist/tools/get-document.d.ts.map +1 -0
  34. package/dist/tools/get-document.js +68 -0
  35. package/dist/tools/get-document.js.map +1 -0
  36. package/dist/tools/get-project-memory.d.ts +47 -0
  37. package/dist/tools/get-project-memory.d.ts.map +1 -0
  38. package/dist/tools/get-project-memory.js +53 -0
  39. package/dist/tools/get-project-memory.js.map +1 -0
  40. package/dist/tools/get-related-entities.d.ts +44 -0
  41. package/dist/tools/get-related-entities.d.ts.map +1 -0
  42. package/dist/tools/get-related-entities.js +60 -0
  43. package/dist/tools/get-related-entities.js.map +1 -0
  44. package/dist/tools/governance.d.ts +90 -0
  45. package/dist/tools/governance.d.ts.map +1 -0
  46. package/dist/tools/governance.js +127 -0
  47. package/dist/tools/governance.js.map +1 -0
  48. package/dist/tools/ingest-document.d.ts +40 -0
  49. package/dist/tools/ingest-document.d.ts.map +1 -0
  50. package/dist/tools/ingest-document.js +49 -0
  51. package/dist/tools/ingest-document.js.map +1 -0
  52. package/dist/tools/letter-inbox.d.ts +80 -0
  53. package/dist/tools/letter-inbox.d.ts.map +1 -0
  54. package/dist/tools/letter-inbox.js +118 -0
  55. package/dist/tools/letter-inbox.js.map +1 -0
  56. package/dist/tools/letters.d.ts +91 -0
  57. package/dist/tools/letters.d.ts.map +1 -0
  58. package/dist/tools/letters.js +112 -0
  59. package/dist/tools/letters.js.map +1 -0
  60. package/dist/tools/list-documents.d.ts +33 -0
  61. package/dist/tools/list-documents.d.ts.map +1 -0
  62. package/dist/tools/list-documents.js +47 -0
  63. package/dist/tools/list-documents.js.map +1 -0
  64. package/dist/tools/list-tasks.d.ts +39 -0
  65. package/dist/tools/list-tasks.d.ts.map +1 -0
  66. package/dist/tools/list-tasks.js +46 -0
  67. package/dist/tools/list-tasks.js.map +1 -0
  68. package/dist/tools/project-list.d.ts +28 -0
  69. package/dist/tools/project-list.d.ts.map +1 -0
  70. package/dist/tools/project-list.js +43 -0
  71. package/dist/tools/project-list.js.map +1 -0
  72. package/dist/tools/reviews.d.ts +145 -0
  73. package/dist/tools/reviews.d.ts.map +1 -0
  74. package/dist/tools/reviews.js +220 -0
  75. package/dist/tools/reviews.js.map +1 -0
  76. package/dist/tools/search-knowledge.d.ts +48 -0
  77. package/dist/tools/search-knowledge.d.ts.map +1 -0
  78. package/dist/tools/search-knowledge.js +54 -0
  79. package/dist/tools/search-knowledge.js.map +1 -0
  80. package/dist/tools/sessions.d.ts +81 -0
  81. package/dist/tools/sessions.d.ts.map +1 -0
  82. package/dist/tools/sessions.js +123 -0
  83. package/dist/tools/sessions.js.map +1 -0
  84. package/dist/tools/skill-assign.d.ts +77 -0
  85. package/dist/tools/skill-assign.d.ts.map +1 -0
  86. package/dist/tools/skill-assign.js +108 -0
  87. package/dist/tools/skill-assign.js.map +1 -0
  88. package/dist/tools/skills.d.ts +138 -0
  89. package/dist/tools/skills.d.ts.map +1 -0
  90. package/dist/tools/skills.js +195 -0
  91. package/dist/tools/skills.js.map +1 -0
  92. package/dist/tools/update-task-status.d.ts +48 -0
  93. package/dist/tools/update-task-status.d.ts.map +1 -0
  94. package/dist/tools/update-task-status.js +51 -0
  95. package/dist/tools/update-task-status.js.map +1 -0
  96. package/package.json +30 -0
  97. package/src/__tests__/auth.test.ts +162 -0
  98. package/src/__tests__/decision-comments.test.ts +173 -0
  99. package/src/__tests__/e2e-integration.test.ts +443 -0
  100. package/src/__tests__/helpers.ts +58 -0
  101. package/src/__tests__/layer1-knowledge.test.ts +302 -0
  102. package/src/__tests__/layer2-coordination.test.ts +842 -0
  103. package/src/__tests__/layer3-governance.test.ts +205 -0
  104. package/src/__tests__/project-list-and-skill-assign.test.ts +282 -0
  105. package/src/__tests__/reviews.test.ts +420 -0
  106. package/src/__tests__/server.test.ts +250 -0
  107. package/src/__tests__/setup.ts +15 -0
  108. package/src/__tests__/task-list-and-doc-list.test.ts +226 -0
  109. package/src/auth.ts +81 -0
  110. package/src/nexus-api.ts +127 -0
  111. package/src/server.ts +520 -0
  112. package/src/tools/add-task-note.ts +50 -0
  113. package/src/tools/append-session-entry.ts +84 -0
  114. package/src/tools/create-task.ts +66 -0
  115. package/src/tools/decision-comments.ts +103 -0
  116. package/src/tools/get-document.ts +80 -0
  117. package/src/tools/get-project-memory.ts +65 -0
  118. package/src/tools/get-related-entities.ts +73 -0
  119. package/src/tools/governance.ts +165 -0
  120. package/src/tools/ingest-document.ts +65 -0
  121. package/src/tools/letter-inbox.ts +157 -0
  122. package/src/tools/letters.ts +144 -0
  123. package/src/tools/list-documents.ts +58 -0
  124. package/src/tools/list-tasks.ts +57 -0
  125. package/src/tools/project-list.ts +52 -0
  126. package/src/tools/reviews.ts +281 -0
  127. package/src/tools/search-knowledge.ts +68 -0
  128. package/src/tools/sessions.ts +157 -0
  129. package/src/tools/skill-assign.ts +142 -0
  130. package/src/tools/skills.ts +255 -0
  131. package/src/tools/update-task-status.ts +64 -0
  132. package/tsconfig.json +20 -0
  133. package/vitest.config.ts +8 -0
@@ -0,0 +1,281 @@
1
+ /**
2
+ * rv_list + rv_get + rv_create + rv_decide + rv_comment -- Layer 4 Reviews
3
+ *
4
+ * Review lifecycle management tools for the Nexus platform.
5
+ * Reviews cover skills, agents, and other governed entities.
6
+ * Delegates to POST /api/mcp/reviews.
7
+ */
8
+
9
+ import { z } from 'zod'
10
+ import { nexusPost } from '../nexus-api.js'
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // rv_list
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export const rvListSchema = {
17
+ entity_type: z
18
+ .enum(['skill', 'agent'])
19
+ .optional()
20
+ .describe('Filter by entity type (skill or agent)'),
21
+ status: z
22
+ .string()
23
+ .max(50)
24
+ .optional()
25
+ .describe('Filter by review status'),
26
+ limit: z
27
+ .number()
28
+ .int()
29
+ .min(1)
30
+ .max(100)
31
+ .optional()
32
+ .describe('Maximum number of reviews to return'),
33
+ }
34
+
35
+ type RvListArgs = {
36
+ entity_type?: string
37
+ status?: string
38
+ limit?: number
39
+ user_id: string
40
+ }
41
+
42
+ export async function rvList(args: RvListArgs) {
43
+ const result = await nexusPost('/api/mcp/reviews', {
44
+ action: 'rv_list',
45
+ entity_type: args.entity_type,
46
+ status: args.status,
47
+ limit: args.limit,
48
+ })
49
+
50
+ if (!result.ok) {
51
+ return {
52
+ content: [
53
+ {
54
+ type: 'text' as const,
55
+ text: JSON.stringify({ error: result.error }, null, 2),
56
+ },
57
+ ],
58
+ isError: true,
59
+ }
60
+ }
61
+
62
+ return {
63
+ content: [
64
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
65
+ ],
66
+ }
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // rv_get
71
+ // ---------------------------------------------------------------------------
72
+
73
+ export const rvGetSchema = {
74
+ review_id: z
75
+ .string()
76
+ .uuid()
77
+ .optional()
78
+ .describe('Review UUID (look up by review ID)'),
79
+ entity_type: z
80
+ .string()
81
+ .max(50)
82
+ .optional()
83
+ .describe('Entity type (used with entity_id for lookup by entity)'),
84
+ entity_id: z
85
+ .string()
86
+ .uuid()
87
+ .optional()
88
+ .describe('Entity UUID (used with entity_type for lookup by entity)'),
89
+ }
90
+
91
+ type RvGetArgs = {
92
+ review_id?: string
93
+ entity_type?: string
94
+ entity_id?: string
95
+ user_id: string
96
+ }
97
+
98
+ export async function rvGet(args: RvGetArgs) {
99
+ const result = await nexusPost('/api/mcp/reviews', {
100
+ action: 'rv_get',
101
+ review_id: args.review_id,
102
+ entity_type: args.entity_type,
103
+ entity_id: args.entity_id,
104
+ })
105
+
106
+ if (!result.ok) {
107
+ return {
108
+ content: [
109
+ {
110
+ type: 'text' as const,
111
+ text: JSON.stringify({ error: result.error }, null, 2),
112
+ },
113
+ ],
114
+ isError: true,
115
+ }
116
+ }
117
+
118
+ return {
119
+ content: [
120
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
121
+ ],
122
+ }
123
+ }
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // rv_create
127
+ // ---------------------------------------------------------------------------
128
+
129
+ export const rvCreateSchema = {
130
+ entity_type: z
131
+ .enum(['skill', 'agent'])
132
+ .describe('Type of entity to create a review for'),
133
+ entity_id: z
134
+ .string()
135
+ .uuid()
136
+ .describe('UUID of the entity to review'),
137
+ }
138
+
139
+ type RvCreateArgs = {
140
+ entity_type: string
141
+ entity_id: string
142
+ user_id: string
143
+ }
144
+
145
+ export async function rvCreate(args: RvCreateArgs) {
146
+ const result = await nexusPost('/api/mcp/reviews', {
147
+ action: 'rv_create',
148
+ entity_type: args.entity_type,
149
+ entity_id: args.entity_id,
150
+ })
151
+
152
+ if (!result.ok) {
153
+ return {
154
+ content: [
155
+ {
156
+ type: 'text' as const,
157
+ text: JSON.stringify({ error: result.error }, null, 2),
158
+ },
159
+ ],
160
+ isError: true,
161
+ }
162
+ }
163
+
164
+ return {
165
+ content: [
166
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
167
+ ],
168
+ }
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // rv_decide
173
+ // ---------------------------------------------------------------------------
174
+
175
+ export const rvDecideSchema = {
176
+ review_id: z.string().uuid().describe('Review UUID'),
177
+ transition: z
178
+ .enum(['submit', 'accept', 'reject', 'request_revision', 'resubmit', 'archive'])
179
+ .describe('State transition to apply'),
180
+ rationale: z
181
+ .string()
182
+ .max(10_000)
183
+ .optional()
184
+ .describe('Optional rationale for the decision'),
185
+ }
186
+
187
+ type RvDecideArgs = {
188
+ review_id: string
189
+ transition: string
190
+ rationale?: string
191
+ user_id: string
192
+ }
193
+
194
+ export async function rvDecide(args: RvDecideArgs) {
195
+ const result = await nexusPost('/api/mcp/reviews', {
196
+ action: 'rv_decide',
197
+ review_id: args.review_id,
198
+ transition: args.transition,
199
+ rationale: args.rationale,
200
+ })
201
+
202
+ if (!result.ok) {
203
+ return {
204
+ content: [
205
+ {
206
+ type: 'text' as const,
207
+ text: JSON.stringify({ error: result.error }, null, 2),
208
+ },
209
+ ],
210
+ isError: true,
211
+ }
212
+ }
213
+
214
+ return {
215
+ content: [
216
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
217
+ ],
218
+ }
219
+ }
220
+
221
+ // ---------------------------------------------------------------------------
222
+ // rv_comment
223
+ // ---------------------------------------------------------------------------
224
+
225
+ export const rvCommentSchema = {
226
+ review_id: z.string().uuid().describe('Review UUID'),
227
+ body: z.string().max(100_000).describe('Comment body (markdown supported)'),
228
+ agent_id: z
229
+ .string()
230
+ .max(200)
231
+ .optional()
232
+ .describe('Agent identifier if comment is posted by an agent'),
233
+ line_start: z
234
+ .number()
235
+ .int()
236
+ .optional()
237
+ .describe('Start line number for inline comments'),
238
+ line_end: z
239
+ .number()
240
+ .int()
241
+ .optional()
242
+ .describe('End line number for inline comments'),
243
+ }
244
+
245
+ type RvCommentArgs = {
246
+ review_id: string
247
+ body: string
248
+ agent_id?: string
249
+ line_start?: number
250
+ line_end?: number
251
+ user_id: string
252
+ }
253
+
254
+ export async function rvComment(args: RvCommentArgs) {
255
+ const result = await nexusPost('/api/mcp/reviews', {
256
+ action: 'rv_comment',
257
+ review_id: args.review_id,
258
+ body: args.body,
259
+ agent_id: args.agent_id,
260
+ line_start: args.line_start,
261
+ line_end: args.line_end,
262
+ })
263
+
264
+ if (!result.ok) {
265
+ return {
266
+ content: [
267
+ {
268
+ type: 'text' as const,
269
+ text: JSON.stringify({ error: result.error }, null, 2),
270
+ },
271
+ ],
272
+ isError: true,
273
+ }
274
+ }
275
+
276
+ return {
277
+ content: [
278
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
279
+ ],
280
+ }
281
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * kb_search -- Layer 1 Knowledge Access
3
+ *
4
+ * Searches project knowledge using keyword, semantic, or hybrid mode.
5
+ * Delegates to POST /api/mcp/search.
6
+ */
7
+
8
+ import { z } from 'zod'
9
+ import { nexusPost } from '../nexus-api.js'
10
+
11
+ export const searchKnowledgeSchema = {
12
+ query: z.string().max(1000).describe('Free-text search query'),
13
+ project_id: z.string().uuid().describe('Project UUID to scope the search'),
14
+ entity_types: z
15
+ .array(
16
+ z.enum([
17
+ 'session',
18
+ 'decision',
19
+ 'letter',
20
+ 'task',
21
+ 'research_note',
22
+ 'planning_item',
23
+ 'ingest_item',
24
+ ]),
25
+ )
26
+ .optional()
27
+ .describe('Filter to specific entity types'),
28
+ search_mode: z
29
+ .enum(['keyword', 'semantic', 'hybrid'])
30
+ .default('keyword')
31
+ .describe('Search mode: keyword, semantic, or hybrid'),
32
+ limit: z
33
+ .number()
34
+ .int()
35
+ .min(1)
36
+ .max(50)
37
+ .default(10)
38
+ .describe('Maximum results to return'),
39
+ }
40
+
41
+ type SearchArgs = {
42
+ query: string
43
+ project_id: string
44
+ entity_types?: string[]
45
+ search_mode?: string
46
+ limit?: number
47
+ }
48
+
49
+ export async function searchKnowledge(args: SearchArgs) {
50
+ const result = await nexusPost('/api/mcp/search', {
51
+ project_id: args.project_id,
52
+ query: args.query,
53
+ entity_types: args.entity_types,
54
+ search_mode: args.search_mode ?? 'keyword',
55
+ limit: args.limit ?? 10,
56
+ })
57
+
58
+ if (!result.ok) {
59
+ return {
60
+ content: [{ type: 'text' as const, text: JSON.stringify({ error: result.error }, null, 2) }],
61
+ isError: true,
62
+ }
63
+ }
64
+
65
+ return {
66
+ content: [{ type: 'text' as const, text: JSON.stringify(result.data, null, 2) }],
67
+ }
68
+ }
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Session lifecycle tools -- Layer 2 Coordination
3
+ *
4
+ * session_create: Start a new work session for a project
5
+ * session_close: End a session with summary and next entry point
6
+ * session_list: List open sessions for a project
7
+ *
8
+ * Delegates to POST /api/mcp/sessions.
9
+ */
10
+
11
+ import { z } from 'zod'
12
+ import { nexusPost } from '../nexus-api.js'
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // create_session -> session_create
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export const createSessionSchema = {
19
+ project_id: z.string().uuid().describe('Project UUID'),
20
+ title: z.string().max(500).describe('Session title (e.g., "Architecture review")'),
21
+ agent_id: z
22
+ .string()
23
+ .max(200)
24
+ .optional()
25
+ .describe('Agent identifier starting this session'),
26
+ }
27
+
28
+ type CreateSessionArgs = {
29
+ project_id: string
30
+ title: string
31
+ agent_id?: string
32
+ user_id: string
33
+ }
34
+
35
+ export async function createSession(args: CreateSessionArgs) {
36
+ const result = await nexusPost('/api/mcp/sessions', {
37
+ action: 'session_create',
38
+ project_id: args.project_id,
39
+ title: args.title,
40
+ })
41
+
42
+ if (!result.ok) {
43
+ return {
44
+ content: [
45
+ {
46
+ type: 'text' as const,
47
+ text: JSON.stringify({ error: result.error }, null, 2),
48
+ },
49
+ ],
50
+ isError: true,
51
+ }
52
+ }
53
+
54
+ return {
55
+ content: [
56
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
57
+ ],
58
+ }
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // close_session -> session_close
63
+ // ---------------------------------------------------------------------------
64
+
65
+ export const closeSessionSchema = {
66
+ session_id: z.string().uuid().describe('Session UUID to close'),
67
+ summary: z
68
+ .string()
69
+ .max(100_000)
70
+ .optional()
71
+ .describe('Summary of what was accomplished in the session'),
72
+ next_entry_point: z
73
+ .string()
74
+ .max(10_000)
75
+ .optional()
76
+ .describe('Suggested starting point for the next session'),
77
+ }
78
+
79
+ type CloseSessionArgs = {
80
+ session_id: string
81
+ summary?: string
82
+ next_entry_point?: string
83
+ user_id: string
84
+ }
85
+
86
+ export async function closeSession(args: CloseSessionArgs) {
87
+ const result = await nexusPost('/api/mcp/sessions', {
88
+ action: 'session_close',
89
+ session_id: args.session_id,
90
+ summary: args.summary,
91
+ next_entry_point: args.next_entry_point,
92
+ })
93
+
94
+ if (!result.ok) {
95
+ return {
96
+ content: [
97
+ {
98
+ type: 'text' as const,
99
+ text: JSON.stringify({ error: result.error }, null, 2),
100
+ },
101
+ ],
102
+ isError: true,
103
+ }
104
+ }
105
+
106
+ return {
107
+ content: [
108
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
109
+ ],
110
+ }
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // list_open_sessions -> session_list
115
+ // ---------------------------------------------------------------------------
116
+
117
+ export const listOpenSessionsSchema = {
118
+ project_id: z.string().uuid().describe('Project UUID'),
119
+ limit: z
120
+ .number()
121
+ .int()
122
+ .min(1)
123
+ .max(100)
124
+ .default(25)
125
+ .describe('Maximum number of sessions to return (default 25)'),
126
+ }
127
+
128
+ type ListOpenSessionsArgs = {
129
+ project_id: string
130
+ limit?: number
131
+ }
132
+
133
+ export async function listOpenSessions(args: ListOpenSessionsArgs) {
134
+ const result = await nexusPost('/api/mcp/sessions', {
135
+ action: 'session_list',
136
+ project_id: args.project_id,
137
+ limit: args.limit ?? 25,
138
+ })
139
+
140
+ if (!result.ok) {
141
+ return {
142
+ content: [
143
+ {
144
+ type: 'text' as const,
145
+ text: JSON.stringify({ error: result.error }, null, 2),
146
+ },
147
+ ],
148
+ isError: true,
149
+ }
150
+ }
151
+
152
+ return {
153
+ content: [
154
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
155
+ ],
156
+ }
157
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * sk_assign + sk_unassign + sk_export -- Layer 2 Coordination
3
+ *
4
+ * Skill-project assignment management tools for the Nexus platform.
5
+ * Delegates to POST /api/mcp/skills.
6
+ */
7
+
8
+ import { z } from 'zod'
9
+ import { nexusPost } from '../nexus-api.js'
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // sk_assign
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export const skAssignSchema = {
16
+ project_id: z.string().uuid().describe('Project UUID'),
17
+ skill_id: z.string().max(200).describe('Skill identifier or UUID'),
18
+ pinned_version: z
19
+ .number()
20
+ .int()
21
+ .optional()
22
+ .describe('Pin to a specific skill version'),
23
+ enabled: z
24
+ .boolean()
25
+ .optional()
26
+ .describe('Whether the assignment is enabled (default: true)'),
27
+ }
28
+
29
+ type SkAssignArgs = {
30
+ project_id: string
31
+ skill_id: string
32
+ pinned_version?: number
33
+ enabled?: boolean
34
+ user_id: string
35
+ }
36
+
37
+ export async function skAssign(args: SkAssignArgs) {
38
+ const result = await nexusPost('/api/mcp/skills', {
39
+ action: 'sk_assign',
40
+ project_id: args.project_id,
41
+ skill_id: args.skill_id,
42
+ pinned_version: args.pinned_version,
43
+ enabled: args.enabled,
44
+ })
45
+
46
+ if (!result.ok) {
47
+ return {
48
+ content: [
49
+ {
50
+ type: 'text' as const,
51
+ text: JSON.stringify({ error: result.error }, null, 2),
52
+ },
53
+ ],
54
+ isError: true,
55
+ }
56
+ }
57
+
58
+ return {
59
+ content: [
60
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
61
+ ],
62
+ }
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // sk_unassign
67
+ // ---------------------------------------------------------------------------
68
+
69
+ export const skUnassignSchema = {
70
+ project_id: z.string().uuid().describe('Project UUID'),
71
+ skill_id: z.string().max(200).describe('Skill identifier or UUID'),
72
+ }
73
+
74
+ type SkUnassignArgs = {
75
+ project_id: string
76
+ skill_id: string
77
+ user_id: string
78
+ }
79
+
80
+ export async function skUnassign(args: SkUnassignArgs) {
81
+ const result = await nexusPost('/api/mcp/skills', {
82
+ action: 'sk_unassign',
83
+ project_id: args.project_id,
84
+ skill_id: args.skill_id,
85
+ })
86
+
87
+ if (!result.ok) {
88
+ return {
89
+ content: [
90
+ {
91
+ type: 'text' as const,
92
+ text: JSON.stringify({ error: result.error }, null, 2),
93
+ },
94
+ ],
95
+ isError: true,
96
+ }
97
+ }
98
+
99
+ return {
100
+ content: [
101
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
102
+ ],
103
+ }
104
+ }
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // sk_export
108
+ // ---------------------------------------------------------------------------
109
+
110
+ export const skExportSchema = {
111
+ project_id: z.string().uuid().describe('Project UUID'),
112
+ }
113
+
114
+ type SkExportArgs = {
115
+ project_id: string
116
+ user_id: string
117
+ }
118
+
119
+ export async function skExport(args: SkExportArgs) {
120
+ const result = await nexusPost('/api/mcp/skills', {
121
+ action: 'sk_export',
122
+ project_id: args.project_id,
123
+ })
124
+
125
+ if (!result.ok) {
126
+ return {
127
+ content: [
128
+ {
129
+ type: 'text' as const,
130
+ text: JSON.stringify({ error: result.error }, null, 2),
131
+ },
132
+ ],
133
+ isError: true,
134
+ }
135
+ }
136
+
137
+ return {
138
+ content: [
139
+ { type: 'text' as const, text: JSON.stringify(result.data, null, 2) },
140
+ ],
141
+ }
142
+ }