@mpowr/nexus-mcp 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -0
- package/dist/auth.d.ts +39 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +47 -0
- package/dist/auth.js.map +1 -0
- package/dist/nexus-api.d.ts +29 -0
- package/dist/nexus-api.d.ts.map +1 -0
- package/dist/nexus-api.js +76 -0
- package/dist/nexus-api.js.map +1 -0
- package/dist/server.d.ts +65 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +183 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/add-task-note.d.ts +34 -0
- package/dist/tools/add-task-note.d.ts.map +1 -0
- package/dist/tools/add-task-note.js +39 -0
- package/dist/tools/add-task-note.js.map +1 -0
- package/dist/tools/append-session-entry.d.ts +53 -0
- package/dist/tools/append-session-entry.d.ts.map +1 -0
- package/dist/tools/append-session-entry.js +67 -0
- package/dist/tools/append-session-entry.js.map +1 -0
- package/dist/tools/create-task.d.ts +52 -0
- package/dist/tools/create-task.d.ts.map +1 -0
- package/dist/tools/create-task.js +51 -0
- package/dist/tools/create-task.js.map +1 -0
- package/dist/tools/decision-comments.d.ts +54 -0
- package/dist/tools/decision-comments.d.ts.map +1 -0
- package/dist/tools/decision-comments.js +80 -0
- package/dist/tools/decision-comments.js.map +1 -0
- package/dist/tools/get-document.d.ts +47 -0
- package/dist/tools/get-document.d.ts.map +1 -0
- package/dist/tools/get-document.js +68 -0
- package/dist/tools/get-document.js.map +1 -0
- package/dist/tools/get-project-memory.d.ts +47 -0
- package/dist/tools/get-project-memory.d.ts.map +1 -0
- package/dist/tools/get-project-memory.js +53 -0
- package/dist/tools/get-project-memory.js.map +1 -0
- package/dist/tools/get-related-entities.d.ts +44 -0
- package/dist/tools/get-related-entities.d.ts.map +1 -0
- package/dist/tools/get-related-entities.js +60 -0
- package/dist/tools/get-related-entities.js.map +1 -0
- package/dist/tools/governance.d.ts +90 -0
- package/dist/tools/governance.d.ts.map +1 -0
- package/dist/tools/governance.js +124 -0
- package/dist/tools/governance.js.map +1 -0
- package/dist/tools/ingest-document.d.ts +40 -0
- package/dist/tools/ingest-document.d.ts.map +1 -0
- package/dist/tools/ingest-document.js +48 -0
- package/dist/tools/ingest-document.js.map +1 -0
- package/dist/tools/letter-inbox.d.ts +80 -0
- package/dist/tools/letter-inbox.d.ts.map +1 -0
- package/dist/tools/letter-inbox.js +118 -0
- package/dist/tools/letter-inbox.js.map +1 -0
- package/dist/tools/letters.d.ts +91 -0
- package/dist/tools/letters.d.ts.map +1 -0
- package/dist/tools/letters.js +112 -0
- package/dist/tools/letters.js.map +1 -0
- package/dist/tools/project-list.d.ts +28 -0
- package/dist/tools/project-list.d.ts.map +1 -0
- package/dist/tools/project-list.js +43 -0
- package/dist/tools/project-list.js.map +1 -0
- package/dist/tools/reviews.d.ts +145 -0
- package/dist/tools/reviews.d.ts.map +1 -0
- package/dist/tools/reviews.js +216 -0
- package/dist/tools/reviews.js.map +1 -0
- package/dist/tools/search-knowledge.d.ts +48 -0
- package/dist/tools/search-knowledge.d.ts.map +1 -0
- package/dist/tools/search-knowledge.js +54 -0
- package/dist/tools/search-knowledge.js.map +1 -0
- package/dist/tools/sessions.d.ts +81 -0
- package/dist/tools/sessions.d.ts.map +1 -0
- package/dist/tools/sessions.js +120 -0
- package/dist/tools/sessions.js.map +1 -0
- package/dist/tools/skill-assign.d.ts +77 -0
- package/dist/tools/skill-assign.d.ts.map +1 -0
- package/dist/tools/skill-assign.js +108 -0
- package/dist/tools/skill-assign.js.map +1 -0
- package/dist/tools/skills.d.ts +138 -0
- package/dist/tools/skills.d.ts.map +1 -0
- package/dist/tools/skills.js +192 -0
- package/dist/tools/skills.js.map +1 -0
- package/dist/tools/update-task-status.d.ts +48 -0
- package/dist/tools/update-task-status.d.ts.map +1 -0
- package/dist/tools/update-task-status.js +51 -0
- package/dist/tools/update-task-status.js.map +1 -0
- package/package.json +30 -0
- package/src/__tests__/auth.test.ts +162 -0
- package/src/__tests__/decision-comments.test.ts +173 -0
- package/src/__tests__/helpers.ts +58 -0
- package/src/__tests__/layer1-knowledge.test.ts +302 -0
- package/src/__tests__/layer2-coordination.test.ts +775 -0
- package/src/__tests__/layer3-governance.test.ts +205 -0
- package/src/__tests__/project-list-and-skill-assign.test.ts +282 -0
- package/src/__tests__/reviews.test.ts +420 -0
- package/src/__tests__/server.test.ts +238 -0
- package/src/__tests__/setup.ts +15 -0
- package/src/auth.ts +81 -0
- package/src/nexus-api.ts +110 -0
- package/src/server.ts +499 -0
- package/src/tools/add-task-note.ts +50 -0
- package/src/tools/append-session-entry.ts +83 -0
- package/src/tools/create-task.ts +66 -0
- package/src/tools/decision-comments.ts +102 -0
- package/src/tools/get-document.ts +80 -0
- package/src/tools/get-project-memory.ts +65 -0
- package/src/tools/get-related-entities.ts +73 -0
- package/src/tools/governance.ts +162 -0
- package/src/tools/ingest-document.ts +64 -0
- package/src/tools/letter-inbox.ts +157 -0
- package/src/tools/letters.ts +144 -0
- package/src/tools/project-list.ts +52 -0
- package/src/tools/reviews.ts +277 -0
- package/src/tools/search-knowledge.ts +68 -0
- package/src/tools/sessions.ts +154 -0
- package/src/tools/skill-assign.ts +142 -0
- package/src/tools/skills.ts +252 -0
- package/src/tools/update-task-status.ts +64 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sk_list + sk_get + sk_create + sk_update + sk_activate -- Layer 2 Coordination
|
|
3
|
+
*
|
|
4
|
+
* Skill management tools for the Nexus platform.
|
|
5
|
+
* Skills are tenant-scoped instruction sets for agent sessions.
|
|
6
|
+
* Delegates to POST /api/mcp/skills.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { nexusPost } from '../nexus-api.js';
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// sk_list
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
export const skListSchema = {
|
|
14
|
+
status_filter: z
|
|
15
|
+
.array(z.enum(['draft', 'active', 'archived']))
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Filter by skill status (default: draft, active)'),
|
|
18
|
+
limit: z
|
|
19
|
+
.number()
|
|
20
|
+
.min(1)
|
|
21
|
+
.max(100)
|
|
22
|
+
.default(50)
|
|
23
|
+
.describe('Max number of skills to return'),
|
|
24
|
+
};
|
|
25
|
+
export async function skList(args) {
|
|
26
|
+
const result = await nexusPost('/api/mcp/skills', {
|
|
27
|
+
action: 'sk_list',
|
|
28
|
+
status_filter: args.status_filter,
|
|
29
|
+
limit: args.limit ?? 50,
|
|
30
|
+
});
|
|
31
|
+
if (!result.ok) {
|
|
32
|
+
return {
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
type: 'text',
|
|
36
|
+
text: JSON.stringify({ error: result.error }, null, 2),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
isError: true,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
content: [
|
|
44
|
+
{ type: 'text', text: JSON.stringify(result.data, null, 2) },
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// sk_get
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
export const skGetSchema = {
|
|
52
|
+
skill_id: z
|
|
53
|
+
.string()
|
|
54
|
+
.describe('Skill identifier (e.g., nx-init-nexus) or UUID'),
|
|
55
|
+
};
|
|
56
|
+
export async function skGet(args) {
|
|
57
|
+
const result = await nexusPost('/api/mcp/skills', {
|
|
58
|
+
action: 'sk_get',
|
|
59
|
+
skill_id: args.skill_id,
|
|
60
|
+
});
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: JSON.stringify({ error: result.error }, null, 2),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{ type: 'text', text: JSON.stringify(result.data, null, 2) },
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// sk_create
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
export const skCreateSchema = {
|
|
82
|
+
skill_id: z.string().describe('Skill identifier (e.g., nx-init-nexus)'),
|
|
83
|
+
name: z.string().describe('Human-readable skill name'),
|
|
84
|
+
description: z.string().optional().describe('Short description'),
|
|
85
|
+
body: z.string().describe('Full markdown instruction content'),
|
|
86
|
+
auto_generate_command: z
|
|
87
|
+
.boolean()
|
|
88
|
+
.default(true)
|
|
89
|
+
.describe('Auto-generate an OpenCode command (default: true)'),
|
|
90
|
+
};
|
|
91
|
+
export async function skCreate(args) {
|
|
92
|
+
const result = await nexusPost('/api/mcp/skills', {
|
|
93
|
+
action: 'sk_create',
|
|
94
|
+
skill_id: args.skill_id,
|
|
95
|
+
name: args.name,
|
|
96
|
+
description: args.description,
|
|
97
|
+
body: args.body,
|
|
98
|
+
auto_generate_command: args.auto_generate_command ?? true,
|
|
99
|
+
});
|
|
100
|
+
if (!result.ok) {
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: JSON.stringify({ error: result.error }, null, 2),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{ type: 'text', text: JSON.stringify(result.data, null, 2) },
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// sk_update
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
export const skUpdateSchema = {
|
|
121
|
+
skill_id: z
|
|
122
|
+
.string()
|
|
123
|
+
.describe('Skill identifier (e.g., nx-init-nexus) or UUID'),
|
|
124
|
+
name: z.string().optional().describe('Updated name'),
|
|
125
|
+
description: z.string().optional().describe('Updated description'),
|
|
126
|
+
body: z.string().optional().describe('Updated markdown content'),
|
|
127
|
+
auto_generate_command: z
|
|
128
|
+
.boolean()
|
|
129
|
+
.optional()
|
|
130
|
+
.describe('Toggle command auto-generation'),
|
|
131
|
+
};
|
|
132
|
+
export async function skUpdate(args) {
|
|
133
|
+
const result = await nexusPost('/api/mcp/skills', {
|
|
134
|
+
action: 'sk_update',
|
|
135
|
+
skill_id: args.skill_id,
|
|
136
|
+
name: args.name,
|
|
137
|
+
description: args.description,
|
|
138
|
+
body: args.body,
|
|
139
|
+
auto_generate_command: args.auto_generate_command,
|
|
140
|
+
});
|
|
141
|
+
if (!result.ok) {
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: 'text',
|
|
146
|
+
text: JSON.stringify({ error: result.error }, null, 2),
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
isError: true,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{ type: 'text', text: JSON.stringify(result.data, null, 2) },
|
|
155
|
+
],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// sk_activate
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
export const skActivateSchema = {
|
|
162
|
+
skill_id: z
|
|
163
|
+
.string()
|
|
164
|
+
.describe('Skill identifier (e.g., nx-init-nexus) or UUID'),
|
|
165
|
+
status: z
|
|
166
|
+
.enum(['active', 'archived', 'draft'])
|
|
167
|
+
.describe('New status for the skill'),
|
|
168
|
+
};
|
|
169
|
+
export async function skActivate(args) {
|
|
170
|
+
const result = await nexusPost('/api/mcp/skills', {
|
|
171
|
+
action: 'sk_activate',
|
|
172
|
+
skill_id: args.skill_id,
|
|
173
|
+
status: args.status,
|
|
174
|
+
});
|
|
175
|
+
if (!result.ok) {
|
|
176
|
+
return {
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: 'text',
|
|
180
|
+
text: JSON.stringify({ error: result.error }, null, 2),
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
isError: true,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
content: [
|
|
188
|
+
{ type: 'text', text: JSON.stringify(result.data, null, 2) },
|
|
189
|
+
],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/tools/skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task_update -- Layer 2 Coordination
|
|
3
|
+
*
|
|
4
|
+
* Updates the status (and optionally priority/assignee) of an existing task.
|
|
5
|
+
* Delegates to POST /api/mcp/tasks (action: task_update).
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
export declare const updateTaskStatusSchema: {
|
|
9
|
+
task_id: z.ZodString;
|
|
10
|
+
status: z.ZodEnum<{
|
|
11
|
+
open: "open";
|
|
12
|
+
in_progress: "in_progress";
|
|
13
|
+
blocked: "blocked";
|
|
14
|
+
done: "done";
|
|
15
|
+
cancelled: "cancelled";
|
|
16
|
+
}>;
|
|
17
|
+
priority: z.ZodOptional<z.ZodEnum<{
|
|
18
|
+
low: "low";
|
|
19
|
+
high: "high";
|
|
20
|
+
urgent: "urgent";
|
|
21
|
+
medium: "medium";
|
|
22
|
+
}>>;
|
|
23
|
+
assignee: z.ZodOptional<z.ZodString>;
|
|
24
|
+
agent_id: z.ZodOptional<z.ZodString>;
|
|
25
|
+
};
|
|
26
|
+
type UpdateTaskStatusArgs = {
|
|
27
|
+
task_id: string;
|
|
28
|
+
status: string;
|
|
29
|
+
priority?: string;
|
|
30
|
+
assignee?: string;
|
|
31
|
+
user_id: string;
|
|
32
|
+
agent_id?: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function updateTaskStatus(args: UpdateTaskStatusArgs): Promise<{
|
|
35
|
+
content: {
|
|
36
|
+
type: "text";
|
|
37
|
+
text: string;
|
|
38
|
+
}[];
|
|
39
|
+
isError: boolean;
|
|
40
|
+
} | {
|
|
41
|
+
content: {
|
|
42
|
+
type: "text";
|
|
43
|
+
text: string;
|
|
44
|
+
}[];
|
|
45
|
+
isError?: undefined;
|
|
46
|
+
}>;
|
|
47
|
+
export {};
|
|
48
|
+
//# sourceMappingURL=update-task-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-task-status.d.ts","sourceRoot":"","sources":["../../src/tools/update-task-status.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;CAelC,CAAA;AAED,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB;;;;;;;;;;;;GA2BhE"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task_update -- Layer 2 Coordination
|
|
3
|
+
*
|
|
4
|
+
* Updates the status (and optionally priority/assignee) of an existing task.
|
|
5
|
+
* Delegates to POST /api/mcp/tasks (action: task_update).
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { nexusPost } from '../nexus-api.js';
|
|
9
|
+
export const updateTaskStatusSchema = {
|
|
10
|
+
task_id: z.string().uuid().describe('Task UUID to update'),
|
|
11
|
+
status: z
|
|
12
|
+
.enum(['open', 'in_progress', 'blocked', 'done', 'cancelled'])
|
|
13
|
+
.describe('New task status'),
|
|
14
|
+
priority: z
|
|
15
|
+
.enum(['low', 'medium', 'high', 'urgent'])
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Updated priority (optional)'),
|
|
18
|
+
assignee: z
|
|
19
|
+
.string()
|
|
20
|
+
.uuid()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('UUID of the newly assigned user (optional)'),
|
|
23
|
+
agent_id: z.string().optional().describe('Agent identifier if applicable'),
|
|
24
|
+
};
|
|
25
|
+
export async function updateTaskStatus(args) {
|
|
26
|
+
const result = await nexusPost('/api/mcp/tasks', {
|
|
27
|
+
action: 'task_update',
|
|
28
|
+
task_id: args.task_id,
|
|
29
|
+
status: args.status,
|
|
30
|
+
priority: args.priority,
|
|
31
|
+
assignee: args.assignee,
|
|
32
|
+
agent_id: args.agent_id,
|
|
33
|
+
});
|
|
34
|
+
if (!result.ok) {
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: 'text',
|
|
39
|
+
text: JSON.stringify({ error: result.error }, null, 2),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{ type: 'text', text: JSON.stringify(result.data, null, 2) },
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=update-task-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-task-status.js","sourceRoot":"","sources":["../../src/tools/update-task-status.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,qBAAqB,CAAC;IAC1D,MAAM,EAAE,CAAC;SACN,IAAI,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;SAC7D,QAAQ,CAAC,iBAAiB,CAAC;IAC9B,QAAQ,EAAE,CAAC;SACR,IAAI,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;SACzC,QAAQ,EAAE;SACV,QAAQ,CAAC,6BAA6B,CAAC;IAC1C,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,IAAI,EAAE;SACN,QAAQ,EAAE;SACV,QAAQ,CAAC,4CAA4C,CAAC;IACzD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gCAAgC,CAAC;CAC3E,CAAA;AAWD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAA0B;IAC/D,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,gBAAgB,EAAE;QAC/C,MAAM,EAAE,aAAa;QACrB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAA;IAEF,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;iBACvD;aACF;YACD,OAAO,EAAE,IAAI;SACd,CAAA;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE;YACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;SACtE;KACF,CAAA;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mpowr/nexus-mcp",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "MCP server for the mpowr-nexus platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"nexus-mcp": "dist/server.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsx src/server.ts",
|
|
13
|
+
"start": "node dist/server.js",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
20
|
+
"zod": "^4.3.6"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsx": "^4.21.0",
|
|
24
|
+
"typescript": "^5.8.3",
|
|
25
|
+
"vitest": "^4.1.4"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for src/auth.ts
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - initIdentity() with valid/invalid tokens
|
|
6
|
+
* - getIdentity() before/after init
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
10
|
+
|
|
11
|
+
// Mock the nexus-api module before importing auth
|
|
12
|
+
vi.mock('../nexus-api.js', () => ({
|
|
13
|
+
nexusGet: vi.fn(),
|
|
14
|
+
}))
|
|
15
|
+
|
|
16
|
+
import { nexusGet } from '../nexus-api.js'
|
|
17
|
+
import { TEST_IDS } from './helpers'
|
|
18
|
+
|
|
19
|
+
describe('MCP Auth', () => {
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
vi.resetModules()
|
|
22
|
+
vi.restoreAllMocks()
|
|
23
|
+
// Suppress console.error
|
|
24
|
+
vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('initIdentity', () => {
|
|
28
|
+
it('should resolve identity from a valid token', async () => {
|
|
29
|
+
vi.mocked(nexusGet).mockResolvedValue({
|
|
30
|
+
ok: true,
|
|
31
|
+
status: 200,
|
|
32
|
+
data: {
|
|
33
|
+
userId: TEST_IDS.userId,
|
|
34
|
+
email: 'test@example.com',
|
|
35
|
+
displayName: 'Test User',
|
|
36
|
+
isPlatformAdmin: true,
|
|
37
|
+
isPlatformOwner: false,
|
|
38
|
+
tenantId: TEST_IDS.tenantId,
|
|
39
|
+
memberships: [],
|
|
40
|
+
agentAssignments: [],
|
|
41
|
+
},
|
|
42
|
+
error: null,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
process.env.NEXUS_PRIVATE_TOKEN =
|
|
46
|
+
'nxs_pat_valid-test-token-1234567890abcde'
|
|
47
|
+
|
|
48
|
+
const { initIdentity } = await import('../auth.js')
|
|
49
|
+
const identity = await initIdentity()
|
|
50
|
+
|
|
51
|
+
expect(identity).toBeDefined()
|
|
52
|
+
expect(identity.userId).toBe(TEST_IDS.userId)
|
|
53
|
+
expect(identity.displayName).toBe('Test User')
|
|
54
|
+
expect(identity.email).toBe('test@example.com')
|
|
55
|
+
expect(identity.isPlatformAdmin).toBe(true)
|
|
56
|
+
expect(identity.isPlatformOwner).toBe(false)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('should throw if NEXUS_PRIVATE_TOKEN is not set', async () => {
|
|
60
|
+
delete process.env.NEXUS_PRIVATE_TOKEN
|
|
61
|
+
|
|
62
|
+
const { initIdentity } = await import('../auth.js')
|
|
63
|
+
await expect(initIdentity()).rejects.toThrow(
|
|
64
|
+
'NEXUS_PRIVATE_TOKEN is not set',
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('should throw if API returns an error', async () => {
|
|
69
|
+
vi.mocked(nexusGet).mockResolvedValue({
|
|
70
|
+
ok: false,
|
|
71
|
+
status: 401,
|
|
72
|
+
data: null,
|
|
73
|
+
error: 'Invalid token',
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
process.env.NEXUS_PRIVATE_TOKEN = 'nxs_pat_invalid-token-123456789012345'
|
|
77
|
+
|
|
78
|
+
const { initIdentity } = await import('../auth.js')
|
|
79
|
+
await expect(initIdentity()).rejects.toThrow(
|
|
80
|
+
'Identity resolution failed',
|
|
81
|
+
)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('should throw if API returns null data', async () => {
|
|
85
|
+
vi.mocked(nexusGet).mockResolvedValue({
|
|
86
|
+
ok: true,
|
|
87
|
+
status: 200,
|
|
88
|
+
data: null,
|
|
89
|
+
error: null,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
process.env.NEXUS_PRIVATE_TOKEN = 'nxs_pat_null-data-token-12345678901234'
|
|
93
|
+
|
|
94
|
+
const { initIdentity } = await import('../auth.js')
|
|
95
|
+
await expect(initIdentity()).rejects.toThrow(
|
|
96
|
+
'Identity resolution failed',
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('should resolve platform_owner identity with both admin and owner flags', async () => {
|
|
101
|
+
vi.mocked(nexusGet).mockResolvedValue({
|
|
102
|
+
ok: true,
|
|
103
|
+
status: 200,
|
|
104
|
+
data: {
|
|
105
|
+
userId: TEST_IDS.userId,
|
|
106
|
+
email: 'owner@example.com',
|
|
107
|
+
displayName: 'Platform Owner',
|
|
108
|
+
isPlatformAdmin: true,
|
|
109
|
+
isPlatformOwner: true,
|
|
110
|
+
tenantId: TEST_IDS.tenantId,
|
|
111
|
+
memberships: [],
|
|
112
|
+
agentAssignments: [],
|
|
113
|
+
},
|
|
114
|
+
error: null,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
process.env.NEXUS_PRIVATE_TOKEN = 'nxs_pat_owner-test-token-12345678901'
|
|
118
|
+
|
|
119
|
+
const { initIdentity } = await import('../auth.js')
|
|
120
|
+
const identity = await initIdentity()
|
|
121
|
+
|
|
122
|
+
expect(identity.isPlatformAdmin).toBe(true)
|
|
123
|
+
expect(identity.isPlatformOwner).toBe(true)
|
|
124
|
+
expect(identity.displayName).toBe('Platform Owner')
|
|
125
|
+
expect(identity.email).toBe('owner@example.com')
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe('getIdentity', () => {
|
|
130
|
+
it('should throw if called before initIdentity', async () => {
|
|
131
|
+
const { getIdentity } = await import('../auth.js')
|
|
132
|
+
expect(() => getIdentity()).toThrow('Identity not initialized')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should return cached identity after init', async () => {
|
|
136
|
+
vi.mocked(nexusGet).mockResolvedValue({
|
|
137
|
+
ok: true,
|
|
138
|
+
status: 200,
|
|
139
|
+
data: {
|
|
140
|
+
userId: TEST_IDS.userId,
|
|
141
|
+
email: 'cached@example.com',
|
|
142
|
+
displayName: 'Cached User',
|
|
143
|
+
isPlatformAdmin: false,
|
|
144
|
+
isPlatformOwner: false,
|
|
145
|
+
tenantId: TEST_IDS.tenantId,
|
|
146
|
+
memberships: [],
|
|
147
|
+
agentAssignments: [],
|
|
148
|
+
},
|
|
149
|
+
error: null,
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
process.env.NEXUS_PRIVATE_TOKEN = 'nxs_pat_cached-test-token-1234567890'
|
|
153
|
+
|
|
154
|
+
const { initIdentity, getIdentity } = await import('../auth.js')
|
|
155
|
+
await initIdentity()
|
|
156
|
+
|
|
157
|
+
const identity = getIdentity()
|
|
158
|
+
expect(identity.userId).toBe(TEST_IDS.userId)
|
|
159
|
+
expect(identity.displayName).toBe('Cached User')
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
})
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for decision comment tools.
|
|
3
|
+
* All tools delegate to the Nexus API via nexusPost().
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
7
|
+
import { mockApiError, mockApiSuccess, parseToolResponse, TEST_IDS } from './helpers'
|
|
8
|
+
|
|
9
|
+
vi.mock('../nexus-api.js', () => ({
|
|
10
|
+
nexusPost: vi.fn(),
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
import { nexusPost } from '../nexus-api.js'
|
|
14
|
+
|
|
15
|
+
describe('Decision Comments tools', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.restoreAllMocks()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('addDecisionComment', () => {
|
|
21
|
+
it('should add a comment to an existing decision', async () => {
|
|
22
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
23
|
+
mockApiSuccess({
|
|
24
|
+
action: 'dc_add',
|
|
25
|
+
comment_id: 'comment-id-1',
|
|
26
|
+
decision_id: TEST_IDS.adrId,
|
|
27
|
+
decision_title: 'Test ADR',
|
|
28
|
+
project_id: TEST_IDS.projectId,
|
|
29
|
+
}),
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const { addDecisionComment } = await import('../tools/decision-comments.js')
|
|
33
|
+
const result = await addDecisionComment({
|
|
34
|
+
decision_id: TEST_IDS.adrId,
|
|
35
|
+
body: 'This is a comment',
|
|
36
|
+
user_id: TEST_IDS.userId,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
expect(result.isError).toBeUndefined()
|
|
40
|
+
const parsed = parseToolResponse(result)
|
|
41
|
+
expect(parsed.action).toBe('dc_add')
|
|
42
|
+
expect(parsed.comment_id).toBe('comment-id-1')
|
|
43
|
+
expect(parsed.decision_id).toBe(TEST_IDS.adrId)
|
|
44
|
+
expect(parsed.decision_title).toBe('Test ADR')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should support agent_id in comment', async () => {
|
|
48
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
49
|
+
mockApiSuccess({
|
|
50
|
+
action: 'dc_add',
|
|
51
|
+
comment_id: 'comment-id-2',
|
|
52
|
+
decision_id: TEST_IDS.adrId,
|
|
53
|
+
decision_title: 'ADR',
|
|
54
|
+
project_id: TEST_IDS.projectId,
|
|
55
|
+
}),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const { addDecisionComment } = await import('../tools/decision-comments.js')
|
|
59
|
+
const result = await addDecisionComment({
|
|
60
|
+
decision_id: TEST_IDS.adrId,
|
|
61
|
+
body: 'Agent comment',
|
|
62
|
+
agent_id: TEST_IDS.agentId,
|
|
63
|
+
user_id: TEST_IDS.userId,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
expect(result.isError).toBeUndefined()
|
|
67
|
+
// Verify agent_id was passed to the API
|
|
68
|
+
expect(nexusPost).toHaveBeenCalledWith('/api/mcp/governance', {
|
|
69
|
+
action: 'dc_add',
|
|
70
|
+
decision_id: TEST_IDS.adrId,
|
|
71
|
+
body: 'Agent comment',
|
|
72
|
+
agent_id: TEST_IDS.agentId,
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should return error if decision not found', async () => {
|
|
77
|
+
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Decision not found', 404))
|
|
78
|
+
|
|
79
|
+
const { addDecisionComment } = await import('../tools/decision-comments.js')
|
|
80
|
+
const result = await addDecisionComment({
|
|
81
|
+
decision_id: TEST_IDS.adrId,
|
|
82
|
+
body: 'Comment',
|
|
83
|
+
user_id: TEST_IDS.userId,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
expect(result.isError).toBe(true)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('should return error on API failure', async () => {
|
|
90
|
+
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Failed to add comment'))
|
|
91
|
+
|
|
92
|
+
const { addDecisionComment } = await import('../tools/decision-comments.js')
|
|
93
|
+
const result = await addDecisionComment({
|
|
94
|
+
decision_id: TEST_IDS.adrId,
|
|
95
|
+
body: 'Comment',
|
|
96
|
+
user_id: TEST_IDS.userId,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
expect(result.isError).toBe(true)
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('listDecisionComments', () => {
|
|
104
|
+
it('should list comments for a decision', async () => {
|
|
105
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
106
|
+
mockApiSuccess({
|
|
107
|
+
decision_id: TEST_IDS.adrId,
|
|
108
|
+
decision_title: 'Test ADR',
|
|
109
|
+
total: 2,
|
|
110
|
+
comments: [
|
|
111
|
+
{
|
|
112
|
+
id: 'c1',
|
|
113
|
+
author_id: TEST_IDS.userId,
|
|
114
|
+
agent_id: null,
|
|
115
|
+
body: 'First comment',
|
|
116
|
+
created_at: '2026-01-01T00:00:00Z',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'c2',
|
|
120
|
+
author_id: TEST_IDS.userId,
|
|
121
|
+
agent_id: TEST_IDS.agentId,
|
|
122
|
+
body: 'Second comment',
|
|
123
|
+
created_at: '2026-01-01T01:00:00Z',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
}),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
const { listDecisionComments } = await import('../tools/decision-comments.js')
|
|
130
|
+
const result = await listDecisionComments({
|
|
131
|
+
decision_id: TEST_IDS.adrId,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
expect(result.isError).toBeUndefined()
|
|
135
|
+
const parsed = parseToolResponse(result)
|
|
136
|
+
expect(parsed.decision_title).toBe('Test ADR')
|
|
137
|
+
expect(parsed.total).toBe(2)
|
|
138
|
+
expect(parsed.comments).toHaveLength(2)
|
|
139
|
+
expect(parsed.comments[0].body).toBe('First comment')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should return empty list for decision with no comments', async () => {
|
|
143
|
+
vi.mocked(nexusPost).mockResolvedValue(
|
|
144
|
+
mockApiSuccess({
|
|
145
|
+
decision_id: TEST_IDS.adrId,
|
|
146
|
+
decision_title: 'ADR',
|
|
147
|
+
total: 0,
|
|
148
|
+
comments: [],
|
|
149
|
+
}),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const { listDecisionComments } = await import('../tools/decision-comments.js')
|
|
153
|
+
const result = await listDecisionComments({
|
|
154
|
+
decision_id: TEST_IDS.adrId,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const parsed = parseToolResponse(result)
|
|
158
|
+
expect(parsed.total).toBe(0)
|
|
159
|
+
expect(parsed.comments).toHaveLength(0)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('should return error if decision not found', async () => {
|
|
163
|
+
vi.mocked(nexusPost).mockResolvedValue(mockApiError('Decision not found', 404))
|
|
164
|
+
|
|
165
|
+
const { listDecisionComments } = await import('../tools/decision-comments.js')
|
|
166
|
+
const result = await listDecisionComments({
|
|
167
|
+
decision_id: TEST_IDS.adrId,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
expect(result.isError).toBe(true)
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
})
|