@lumenflow/mcp 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tools.js ADDED
@@ -0,0 +1,314 @@
1
+ /**
2
+ * @file tools.ts
3
+ * @description MCP tool implementations for LumenFlow operations
4
+ *
5
+ * WU-1412: Tools available: context_get, wu_list, wu_status, wu_create, wu_claim, wu_done, gates_run
6
+ *
7
+ * Architecture:
8
+ * - Read operations (context_get) use @lumenflow/core directly for context
9
+ * - All other operations shell out to CLI for consistency and safety
10
+ */
11
+ import { z } from 'zod';
12
+ import { runCliCommand } from './cli-runner.js';
13
+ // Import core functions for context operations only
14
+ let coreModule = null;
15
+ async function getCore() {
16
+ if (!coreModule) {
17
+ coreModule = await import('@lumenflow/core');
18
+ }
19
+ return coreModule;
20
+ }
21
+ /**
22
+ * Error codes used by tool implementations
23
+ */
24
+ const ErrorCodes = {
25
+ MISSING_PARAMETER: 'MISSING_PARAMETER',
26
+ CONTEXT_ERROR: 'CONTEXT_ERROR',
27
+ WU_LIST_ERROR: 'WU_LIST_ERROR',
28
+ WU_STATUS_ERROR: 'WU_STATUS_ERROR',
29
+ WU_CREATE_ERROR: 'WU_CREATE_ERROR',
30
+ WU_CLAIM_ERROR: 'WU_CLAIM_ERROR',
31
+ WU_DONE_ERROR: 'WU_DONE_ERROR',
32
+ WRONG_LOCATION: 'WRONG_LOCATION',
33
+ GATES_ERROR: 'GATES_ERROR',
34
+ };
35
+ /**
36
+ * Error messages used by tool implementations
37
+ */
38
+ const ErrorMessages = {
39
+ ID_REQUIRED: 'id is required',
40
+ LANE_REQUIRED: 'lane is required',
41
+ TITLE_REQUIRED: 'title is required',
42
+ };
43
+ /**
44
+ * Create a successful tool result
45
+ */
46
+ function success(data) {
47
+ return { success: true, data };
48
+ }
49
+ /**
50
+ * Create an error tool result
51
+ */
52
+ function error(message, code) {
53
+ return { success: false, error: { message, code } };
54
+ }
55
+ // ============================================================================
56
+ // Read Operations (via @lumenflow/core)
57
+ // ============================================================================
58
+ /**
59
+ * context_get - Get current WU context (location, git state, WU state)
60
+ */
61
+ export const contextGetTool = {
62
+ name: 'context_get',
63
+ description: 'Get current LumenFlow context including location, git state, and active WU',
64
+ inputSchema: z.object({}).optional(),
65
+ async execute(_input, options) {
66
+ try {
67
+ const core = await getCore();
68
+ const context = await core.computeWuContext({
69
+ cwd: options?.projectRoot,
70
+ });
71
+ return success(context);
72
+ }
73
+ catch (err) {
74
+ return error(err instanceof Error ? err.message : String(err), ErrorCodes.CONTEXT_ERROR);
75
+ }
76
+ },
77
+ };
78
+ /**
79
+ * wu_list - List all WUs with optional status filter
80
+ * Uses CLI shell-out for consistency with other tools
81
+ */
82
+ export const wuListTool = {
83
+ name: 'wu_list',
84
+ description: 'List all Work Units (WUs) with optional status filter',
85
+ inputSchema: z.object({
86
+ status: z.enum(['ready', 'in_progress', 'blocked', 'waiting', 'done']).optional(),
87
+ lane: z.string().optional(),
88
+ }),
89
+ async execute(input, options) {
90
+ // Use spec:linter which validates and lists all WUs
91
+ const cliOptions = { projectRoot: options?.projectRoot };
92
+ // Shell out to get all WU YAMLs via validate --all
93
+ const result = await runCliCommand('wu:validate', ['--all', '--json'], cliOptions);
94
+ if (result.success) {
95
+ try {
96
+ const data = JSON.parse(result.stdout);
97
+ let wus = Array.isArray(data) ? data : data.wus || [];
98
+ // Apply filters
99
+ if (input.status) {
100
+ wus = wus.filter((wu) => wu.status === input.status);
101
+ }
102
+ if (input.lane) {
103
+ wus = wus.filter((wu) => wu.lane === input.lane);
104
+ }
105
+ return success(wus);
106
+ }
107
+ catch {
108
+ // If JSON parse fails, return raw output
109
+ return success({ message: result.stdout });
110
+ }
111
+ }
112
+ else {
113
+ return error(result.stderr || result.error?.message || 'wu_list failed', ErrorCodes.WU_LIST_ERROR);
114
+ }
115
+ },
116
+ };
117
+ /**
118
+ * wu_status - Get status of a specific WU
119
+ * Uses CLI shell-out for consistency
120
+ */
121
+ export const wuStatusTool = {
122
+ name: 'wu_status',
123
+ description: 'Get detailed status of a specific Work Unit',
124
+ inputSchema: z.object({
125
+ id: z.string().describe('WU ID (e.g., WU-1412)'),
126
+ }),
127
+ async execute(input, options) {
128
+ if (!input.id) {
129
+ return error(ErrorMessages.ID_REQUIRED, ErrorCodes.MISSING_PARAMETER);
130
+ }
131
+ const args = ['--id', input.id, '--json'];
132
+ const cliOptions = { projectRoot: options?.projectRoot };
133
+ const result = await runCliCommand('wu:status', args, cliOptions);
134
+ if (result.success) {
135
+ try {
136
+ const data = JSON.parse(result.stdout);
137
+ return success(data);
138
+ }
139
+ catch {
140
+ return success({ message: result.stdout });
141
+ }
142
+ }
143
+ else {
144
+ return error(result.stderr || result.error?.message || 'wu:status failed', ErrorCodes.WU_STATUS_ERROR);
145
+ }
146
+ },
147
+ };
148
+ // ============================================================================
149
+ // Write Operations (via CLI shell-out)
150
+ // ============================================================================
151
+ /**
152
+ * wu_create - Create a new WU
153
+ */
154
+ export const wuCreateTool = {
155
+ name: 'wu_create',
156
+ description: 'Create a new Work Unit specification',
157
+ inputSchema: z.object({
158
+ id: z.string().optional().describe('WU ID (auto-generated if omitted)'),
159
+ lane: z.string().describe('Lane (e.g., "Framework: CLI")'),
160
+ title: z.string().describe('WU title'),
161
+ description: z.string().optional().describe('Context: ... Problem: ... Solution: ...'),
162
+ acceptance: z.array(z.string()).optional().describe('Acceptance criteria'),
163
+ code_paths: z.array(z.string()).optional().describe('Code paths'),
164
+ exposure: z.enum(['ui', 'api', 'backend-only', 'documentation']).optional(),
165
+ }),
166
+ async execute(input, options) {
167
+ if (!input.lane) {
168
+ return error(ErrorMessages.LANE_REQUIRED, ErrorCodes.MISSING_PARAMETER);
169
+ }
170
+ if (!input.title) {
171
+ return error(ErrorMessages.TITLE_REQUIRED, ErrorCodes.MISSING_PARAMETER);
172
+ }
173
+ const args = ['--lane', input.lane, '--title', input.title];
174
+ if (input.id)
175
+ args.push('--id', input.id);
176
+ if (input.description)
177
+ args.push('--description', input.description);
178
+ if (input.acceptance) {
179
+ for (const criterion of input.acceptance) {
180
+ args.push('--acceptance', criterion);
181
+ }
182
+ }
183
+ if (input.code_paths) {
184
+ for (const p of input.code_paths) {
185
+ args.push('--code-paths', p);
186
+ }
187
+ }
188
+ if (input.exposure)
189
+ args.push('--exposure', input.exposure);
190
+ const cliOptions = { projectRoot: options?.projectRoot };
191
+ const result = await runCliCommand('wu:create', args, cliOptions);
192
+ if (result.success) {
193
+ return success({ message: result.stdout || 'WU created successfully' });
194
+ }
195
+ else {
196
+ return error(result.stderr || result.error?.message || 'wu:create failed', ErrorCodes.WU_CREATE_ERROR);
197
+ }
198
+ },
199
+ };
200
+ /**
201
+ * wu_claim - Claim a WU and create worktree
202
+ */
203
+ export const wuClaimTool = {
204
+ name: 'wu_claim',
205
+ description: 'Claim a Work Unit and create worktree for implementation',
206
+ inputSchema: z.object({
207
+ id: z.string().describe('WU ID to claim'),
208
+ lane: z.string().describe('Lane for the WU'),
209
+ }),
210
+ async execute(input, options) {
211
+ if (!input.id) {
212
+ return error(ErrorMessages.ID_REQUIRED, ErrorCodes.MISSING_PARAMETER);
213
+ }
214
+ if (!input.lane) {
215
+ return error(ErrorMessages.LANE_REQUIRED, ErrorCodes.MISSING_PARAMETER);
216
+ }
217
+ const args = ['--id', input.id, '--lane', input.lane];
218
+ const cliOptions = { projectRoot: options?.projectRoot };
219
+ const result = await runCliCommand('wu:claim', args, cliOptions);
220
+ if (result.success) {
221
+ return success({ message: result.stdout || 'WU claimed successfully' });
222
+ }
223
+ else {
224
+ return error(result.stderr || result.error?.message || 'wu:claim failed', ErrorCodes.WU_CLAIM_ERROR);
225
+ }
226
+ },
227
+ };
228
+ /**
229
+ * wu_done - Complete a WU (must be run from main checkout)
230
+ */
231
+ export const wuDoneTool = {
232
+ name: 'wu_done',
233
+ description: 'Complete a Work Unit (merge, stamp, cleanup). MUST be run from main checkout.',
234
+ inputSchema: z.object({
235
+ id: z.string().describe('WU ID to complete'),
236
+ skip_gates: z.boolean().optional().describe('Skip gates (requires reason)'),
237
+ reason: z.string().optional().describe('Reason for skipping gates'),
238
+ fix_wu: z.string().optional().describe('WU ID that will fix the skipped issue'),
239
+ }),
240
+ async execute(input, options) {
241
+ if (!input.id) {
242
+ return error(ErrorMessages.ID_REQUIRED, ErrorCodes.MISSING_PARAMETER);
243
+ }
244
+ // Fail fast if not on main checkout (AC: wu_done fails fast if not on main checkout)
245
+ try {
246
+ const core = await getCore();
247
+ const context = await core.computeWuContext({
248
+ cwd: options?.projectRoot,
249
+ });
250
+ if (context.location.type === 'worktree') {
251
+ return error('wu_done must be run from main checkout, not from a worktree. ' +
252
+ 'Run "pnpm wu:prep" first from the worktree, then cd to main and run wu:done.', ErrorCodes.WRONG_LOCATION);
253
+ }
254
+ }
255
+ catch {
256
+ // If we can't determine context, proceed anyway - CLI will validate
257
+ }
258
+ const args = ['--id', input.id];
259
+ if (input.skip_gates) {
260
+ args.push('--skip-gates');
261
+ if (input.reason)
262
+ args.push('--reason', input.reason);
263
+ if (input.fix_wu)
264
+ args.push('--fix-wu', input.fix_wu);
265
+ }
266
+ const cliOptions = { projectRoot: options?.projectRoot };
267
+ const result = await runCliCommand('wu:done', args, cliOptions);
268
+ if (result.success) {
269
+ return success({ message: result.stdout || 'WU completed successfully' });
270
+ }
271
+ else {
272
+ return error(result.stderr || result.error?.message || 'wu:done failed', ErrorCodes.WU_DONE_ERROR);
273
+ }
274
+ },
275
+ };
276
+ /**
277
+ * gates_run - Run quality gates
278
+ */
279
+ export const gatesRunTool = {
280
+ name: 'gates_run',
281
+ description: 'Run LumenFlow quality gates (lint, typecheck, tests)',
282
+ inputSchema: z.object({
283
+ docs_only: z.boolean().optional().describe('Run docs-only gates (skip lint/typecheck/tests)'),
284
+ }),
285
+ async execute(input, options) {
286
+ const args = [];
287
+ if (input.docs_only) {
288
+ args.push('--docs-only');
289
+ }
290
+ const cliOptions = {
291
+ projectRoot: options?.projectRoot,
292
+ timeout: 600000, // 10 minutes for gates
293
+ };
294
+ const result = await runCliCommand('gates', args, cliOptions);
295
+ if (result.success) {
296
+ return success({ message: result.stdout || 'All gates passed' });
297
+ }
298
+ else {
299
+ return error(result.stderr || result.error?.message || 'Gates failed', ErrorCodes.GATES_ERROR);
300
+ }
301
+ },
302
+ };
303
+ /**
304
+ * All available tools
305
+ */
306
+ export const allTools = [
307
+ contextGetTool,
308
+ wuListTool,
309
+ wuStatusTool,
310
+ wuCreateTool,
311
+ wuClaimTool,
312
+ wuDoneTool,
313
+ gatesRunTool,
314
+ ];
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@lumenflow/mcp",
3
+ "version": "2.11.0",
4
+ "description": "MCP stdio server for LumenFlow workflow framework",
5
+ "keywords": [
6
+ "lumenflow",
7
+ "mcp",
8
+ "model-context-protocol",
9
+ "workflow",
10
+ "wu",
11
+ "work-unit"
12
+ ],
13
+ "homepage": "https://github.com/hellmai/os",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://github.com/hellmai/os.git",
17
+ "directory": "packages/@lumenflow/mcp"
18
+ },
19
+ "license": "Apache-2.0",
20
+ "author": {
21
+ "name": "HellmAI",
22
+ "url": "https://hellm.ai"
23
+ },
24
+ "type": "module",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./server": {
33
+ "types": "./dist/server.d.ts",
34
+ "import": "./dist/server.js"
35
+ },
36
+ "./tools": {
37
+ "types": "./dist/tools.d.ts",
38
+ "import": "./dist/tools.js"
39
+ },
40
+ "./cli-runner": {
41
+ "types": "./dist/cli-runner.d.ts",
42
+ "import": "./dist/cli-runner.js"
43
+ }
44
+ },
45
+ "bin": {
46
+ "lumenflow-mcp": "./dist/bin.js"
47
+ },
48
+ "files": [
49
+ "dist",
50
+ "LICENSE",
51
+ "README.md"
52
+ ],
53
+ "dependencies": {
54
+ "@modelcontextprotocol/sdk": "^1.12.1",
55
+ "zod": "^4.3.5",
56
+ "@lumenflow/core": "2.11.0"
57
+ },
58
+ "devDependencies": {
59
+ "@vitest/coverage-v8": "^4.0.17",
60
+ "typescript": "^5.9.3",
61
+ "vitest": "^4.0.17"
62
+ },
63
+ "engines": {
64
+ "node": ">=22"
65
+ },
66
+ "publishConfig": {
67
+ "access": "public"
68
+ },
69
+ "scripts": {
70
+ "build": "tsc",
71
+ "build:dist": "tsc -p tsconfig.build.json",
72
+ "pack:dist": "pnpm pack",
73
+ "clean": "rm -rf dist *.tgz",
74
+ "test": "vitest run"
75
+ }
76
+ }