@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/LICENSE +190 -0
- package/dist/bin.d.ts +15 -0
- package/dist/bin.js +68 -0
- package/dist/cli-runner.d.ts +66 -0
- package/dist/cli-runner.js +94 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +40 -0
- package/dist/resources.d.ts +60 -0
- package/dist/resources.js +128 -0
- package/dist/server.d.ts +68 -0
- package/dist/server.js +190 -0
- package/dist/tools.d.ts +67 -0
- package/dist/tools.js +314 -0
- package/package.json +76 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file resources.ts
|
|
3
|
+
* @description MCP resource implementations for LumenFlow
|
|
4
|
+
*
|
|
5
|
+
* WU-1412: Resources available: context, wu/{id}, backlog
|
|
6
|
+
*
|
|
7
|
+
* Resources provide read access to LumenFlow data via URI patterns.
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'node:fs/promises';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { runCliCommand } from './cli-runner.js';
|
|
12
|
+
// Lazy load core module for context
|
|
13
|
+
let coreModule = null;
|
|
14
|
+
async function getCore() {
|
|
15
|
+
if (!coreModule) {
|
|
16
|
+
coreModule = await import('@lumenflow/core');
|
|
17
|
+
}
|
|
18
|
+
return coreModule;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* context - Current LumenFlow context
|
|
22
|
+
*/
|
|
23
|
+
export const contextResource = {
|
|
24
|
+
uri: 'lumenflow://context',
|
|
25
|
+
name: 'LumenFlow Context',
|
|
26
|
+
description: 'Current LumenFlow context including location, git state, and active WU',
|
|
27
|
+
mimeType: 'application/json',
|
|
28
|
+
async fetch(_uri, options) {
|
|
29
|
+
try {
|
|
30
|
+
const core = await getCore();
|
|
31
|
+
const context = await core.computeWuContext({
|
|
32
|
+
cwd: options?.projectRoot,
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
success: true,
|
|
36
|
+
content: JSON.stringify(context, null, 2),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: err instanceof Error ? err.message : String(err),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* wu/{id} - WU specification by ID
|
|
49
|
+
*/
|
|
50
|
+
export const wuResource = {
|
|
51
|
+
uriTemplate: 'lumenflow://wu/{id}',
|
|
52
|
+
name: 'Work Unit',
|
|
53
|
+
description: 'Work Unit specification by ID',
|
|
54
|
+
mimeType: 'application/json',
|
|
55
|
+
async fetch(uri, options) {
|
|
56
|
+
try {
|
|
57
|
+
// Extract ID from URI: lumenflow://wu/WU-1412 -> WU-1412
|
|
58
|
+
const match = /^lumenflow:\/\/wu\/(.+)$/.exec(uri);
|
|
59
|
+
if (!match) {
|
|
60
|
+
return { success: false, error: 'Invalid WU URI format' };
|
|
61
|
+
}
|
|
62
|
+
const id = match[1];
|
|
63
|
+
// Use CLI to get WU status (includes full WU context)
|
|
64
|
+
const result = await runCliCommand('wu:status', ['--id', id, '--json'], {
|
|
65
|
+
projectRoot: options?.projectRoot,
|
|
66
|
+
});
|
|
67
|
+
if (result.success) {
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
content: result.stdout,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
error: result.stderr || result.error?.message || 'Failed to get WU',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: err instanceof Error ? err.message : String(err),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* backlog - Current backlog state
|
|
90
|
+
* Reads the backlog.md file directly (generated by LumenFlow CLI)
|
|
91
|
+
*/
|
|
92
|
+
export const backlogResource = {
|
|
93
|
+
uri: 'lumenflow://backlog',
|
|
94
|
+
name: 'Backlog',
|
|
95
|
+
description: 'Current LumenFlow backlog with all WUs grouped by status',
|
|
96
|
+
mimeType: 'text/markdown',
|
|
97
|
+
async fetch(_uri, options) {
|
|
98
|
+
try {
|
|
99
|
+
const projectRoot = options?.projectRoot || process.cwd();
|
|
100
|
+
// Security: path is constructed from known static segments, not user input
|
|
101
|
+
const backlogPath = path.join(projectRoot, 'docs', '04-operations', 'tasks', 'backlog.md');
|
|
102
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
103
|
+
const content = await fs.readFile(backlogPath, 'utf-8');
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
content,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
return {
|
|
111
|
+
success: false,
|
|
112
|
+
error: err instanceof Error ? err.message : String(err),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* All static resources (fixed URIs)
|
|
119
|
+
*/
|
|
120
|
+
export const staticResources = [contextResource, backlogResource];
|
|
121
|
+
/**
|
|
122
|
+
* All resource templates (parameterized URIs)
|
|
123
|
+
*/
|
|
124
|
+
export const resourceTemplates = [wuResource];
|
|
125
|
+
/**
|
|
126
|
+
* All resources
|
|
127
|
+
*/
|
|
128
|
+
export const allResources = [...staticResources, ...resourceTemplates];
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file server.ts
|
|
3
|
+
* @description MCP server factory and configuration
|
|
4
|
+
*
|
|
5
|
+
* WU-1412: MCP server runs via npx @lumenflow/mcp over stdio
|
|
6
|
+
*
|
|
7
|
+
* Creates an MCP server that exposes LumenFlow tools and resources.
|
|
8
|
+
* Supports configuration via environment variables:
|
|
9
|
+
* - LUMENFLOW_PROJECT_ROOT: Project root directory
|
|
10
|
+
* - LUMENFLOW_MCP_LOG_LEVEL: Log level (debug, info, warn, error)
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Log levels supported by the MCP server
|
|
14
|
+
*/
|
|
15
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
16
|
+
/**
|
|
17
|
+
* MCP server configuration
|
|
18
|
+
*/
|
|
19
|
+
export interface McpServerConfig {
|
|
20
|
+
/** Project root directory (default: process.cwd()) */
|
|
21
|
+
projectRoot?: string;
|
|
22
|
+
/** Log level (default: 'info') */
|
|
23
|
+
logLevel?: LogLevel;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* MCP server instance with LumenFlow tools and resources
|
|
27
|
+
*/
|
|
28
|
+
export interface McpServer {
|
|
29
|
+
/** Server name */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Server configuration */
|
|
32
|
+
config: Required<McpServerConfig>;
|
|
33
|
+
/** List available tools */
|
|
34
|
+
listTools: () => Array<{
|
|
35
|
+
name: string;
|
|
36
|
+
description: string;
|
|
37
|
+
}>;
|
|
38
|
+
/** List available static resources */
|
|
39
|
+
listResources: () => Array<{
|
|
40
|
+
uri: string;
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
}>;
|
|
44
|
+
/** List available resource templates */
|
|
45
|
+
listResourceTemplates: () => Array<{
|
|
46
|
+
uriTemplate: string;
|
|
47
|
+
name: string;
|
|
48
|
+
description: string;
|
|
49
|
+
}>;
|
|
50
|
+
/** Start the server (connects stdio transport) */
|
|
51
|
+
start: () => Promise<void>;
|
|
52
|
+
/** Stop the server */
|
|
53
|
+
stop: () => Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Create an MCP server with LumenFlow tools and resources
|
|
57
|
+
*
|
|
58
|
+
* @param config - Server configuration
|
|
59
|
+
* @returns MCP server instance
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* const server = createMcpServer({
|
|
63
|
+
* projectRoot: process.env.LUMENFLOW_PROJECT_ROOT,
|
|
64
|
+
* logLevel: process.env.LUMENFLOW_MCP_LOG_LEVEL as LogLevel,
|
|
65
|
+
* });
|
|
66
|
+
* await server.start();
|
|
67
|
+
*/
|
|
68
|
+
export declare function createMcpServer(config?: McpServerConfig): McpServer;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file server.ts
|
|
3
|
+
* @description MCP server factory and configuration
|
|
4
|
+
*
|
|
5
|
+
* WU-1412: MCP server runs via npx @lumenflow/mcp over stdio
|
|
6
|
+
*
|
|
7
|
+
* Creates an MCP server that exposes LumenFlow tools and resources.
|
|
8
|
+
* Supports configuration via environment variables:
|
|
9
|
+
* - LUMENFLOW_PROJECT_ROOT: Project root directory
|
|
10
|
+
* - LUMENFLOW_MCP_LOG_LEVEL: Log level (debug, info, warn, error)
|
|
11
|
+
*/
|
|
12
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
13
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
14
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListResourceTemplatesRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import { allTools } from './tools.js';
|
|
17
|
+
import { staticResources, resourceTemplates } from './resources.js';
|
|
18
|
+
/**
|
|
19
|
+
* Convert a Zod schema to JSON Schema format for MCP
|
|
20
|
+
*/
|
|
21
|
+
function zodToJsonSchema(schema) {
|
|
22
|
+
return z.toJSONSchema(schema);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Create an MCP server with LumenFlow tools and resources
|
|
26
|
+
*
|
|
27
|
+
* @param config - Server configuration
|
|
28
|
+
* @returns MCP server instance
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const server = createMcpServer({
|
|
32
|
+
* projectRoot: process.env.LUMENFLOW_PROJECT_ROOT,
|
|
33
|
+
* logLevel: process.env.LUMENFLOW_MCP_LOG_LEVEL as LogLevel,
|
|
34
|
+
* });
|
|
35
|
+
* await server.start();
|
|
36
|
+
*/
|
|
37
|
+
export function createMcpServer(config = {}) {
|
|
38
|
+
const resolvedConfig = {
|
|
39
|
+
projectRoot: config.projectRoot || process.env.LUMENFLOW_PROJECT_ROOT || process.cwd(),
|
|
40
|
+
logLevel: config.logLevel || process.env.LUMENFLOW_MCP_LOG_LEVEL || 'info',
|
|
41
|
+
};
|
|
42
|
+
// Create the MCP SDK server
|
|
43
|
+
const server = new Server({
|
|
44
|
+
name: '@lumenflow/mcp',
|
|
45
|
+
version: '2.10.0',
|
|
46
|
+
}, {
|
|
47
|
+
capabilities: {
|
|
48
|
+
tools: {},
|
|
49
|
+
resources: {},
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
// Register tool handlers
|
|
53
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
54
|
+
return {
|
|
55
|
+
tools: allTools.map((tool) => ({
|
|
56
|
+
name: tool.name,
|
|
57
|
+
description: tool.description,
|
|
58
|
+
inputSchema: zodToJsonSchema(tool.inputSchema),
|
|
59
|
+
})),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
63
|
+
const { name, arguments: args } = request.params;
|
|
64
|
+
const tool = allTools.find((t) => t.name === name);
|
|
65
|
+
if (!tool) {
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: 'text', text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],
|
|
68
|
+
isError: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const result = await tool.execute(args || {}, { projectRoot: resolvedConfig.projectRoot });
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
75
|
+
isError: !result.success,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: 'text',
|
|
83
|
+
text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }),
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
isError: true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// Register resource handlers
|
|
91
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
92
|
+
return {
|
|
93
|
+
resources: staticResources
|
|
94
|
+
.filter((r) => r.uri !== undefined)
|
|
95
|
+
.map((r) => ({
|
|
96
|
+
uri: r.uri,
|
|
97
|
+
name: r.name,
|
|
98
|
+
description: r.description,
|
|
99
|
+
mimeType: r.mimeType,
|
|
100
|
+
})),
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
104
|
+
return {
|
|
105
|
+
resourceTemplates: resourceTemplates
|
|
106
|
+
.filter((r) => r.uriTemplate !== undefined)
|
|
107
|
+
.map((r) => ({
|
|
108
|
+
uriTemplate: r.uriTemplate,
|
|
109
|
+
name: r.name,
|
|
110
|
+
description: r.description,
|
|
111
|
+
mimeType: r.mimeType,
|
|
112
|
+
})),
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
116
|
+
const { uri } = request.params;
|
|
117
|
+
// Find matching resource
|
|
118
|
+
let resource;
|
|
119
|
+
// Check static resources first
|
|
120
|
+
resource = staticResources.find((r) => r.uri === uri);
|
|
121
|
+
// Check resource templates
|
|
122
|
+
if (!resource) {
|
|
123
|
+
for (const template of resourceTemplates) {
|
|
124
|
+
if (template.uriTemplate) {
|
|
125
|
+
// Simple template matching for lumenflow://wu/{id} pattern
|
|
126
|
+
// Security: pattern is derived from our own static uriTemplate, not user input
|
|
127
|
+
const pattern = template.uriTemplate.replace(/\{[^}]+\}/g, '([^/]+)');
|
|
128
|
+
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
129
|
+
const regex = new RegExp(`^${pattern}$`);
|
|
130
|
+
if (regex.test(uri)) {
|
|
131
|
+
resource = template;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!resource) {
|
|
138
|
+
return {
|
|
139
|
+
contents: [{ uri, text: `Resource not found: ${uri}`, mimeType: 'text/plain' }],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const result = await resource.fetch(uri, { projectRoot: resolvedConfig.projectRoot });
|
|
143
|
+
return {
|
|
144
|
+
contents: [
|
|
145
|
+
{
|
|
146
|
+
uri,
|
|
147
|
+
text: result.success ? (result.content ?? '') : `Error: ${result.error}`,
|
|
148
|
+
mimeType: resource.mimeType,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
// Build the McpServer wrapper
|
|
154
|
+
let transport = null;
|
|
155
|
+
return {
|
|
156
|
+
name: '@lumenflow/mcp',
|
|
157
|
+
config: resolvedConfig,
|
|
158
|
+
listTools() {
|
|
159
|
+
return allTools.map((t) => ({ name: t.name, description: t.description }));
|
|
160
|
+
},
|
|
161
|
+
listResources() {
|
|
162
|
+
return staticResources
|
|
163
|
+
.filter((r) => r.uri !== undefined)
|
|
164
|
+
.map((r) => ({
|
|
165
|
+
uri: r.uri,
|
|
166
|
+
name: r.name,
|
|
167
|
+
description: r.description,
|
|
168
|
+
}));
|
|
169
|
+
},
|
|
170
|
+
listResourceTemplates() {
|
|
171
|
+
return resourceTemplates
|
|
172
|
+
.filter((r) => r.uriTemplate !== undefined)
|
|
173
|
+
.map((r) => ({
|
|
174
|
+
uriTemplate: r.uriTemplate,
|
|
175
|
+
name: r.name,
|
|
176
|
+
description: r.description,
|
|
177
|
+
}));
|
|
178
|
+
},
|
|
179
|
+
async start() {
|
|
180
|
+
transport = new StdioServerTransport();
|
|
181
|
+
await server.connect(transport);
|
|
182
|
+
},
|
|
183
|
+
async stop() {
|
|
184
|
+
if (transport) {
|
|
185
|
+
await server.close();
|
|
186
|
+
transport = null;
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
/**
|
|
13
|
+
* Tool result structure matching MCP SDK expectations
|
|
14
|
+
*/
|
|
15
|
+
export interface ToolResult {
|
|
16
|
+
success: boolean;
|
|
17
|
+
data?: unknown;
|
|
18
|
+
error?: {
|
|
19
|
+
message: string;
|
|
20
|
+
code?: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Base tool definition
|
|
25
|
+
*/
|
|
26
|
+
export interface ToolDefinition {
|
|
27
|
+
name: string;
|
|
28
|
+
description: string;
|
|
29
|
+
inputSchema: z.ZodType;
|
|
30
|
+
execute: (input: Record<string, unknown>, options?: {
|
|
31
|
+
projectRoot?: string;
|
|
32
|
+
}) => Promise<ToolResult>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* context_get - Get current WU context (location, git state, WU state)
|
|
36
|
+
*/
|
|
37
|
+
export declare const contextGetTool: ToolDefinition;
|
|
38
|
+
/**
|
|
39
|
+
* wu_list - List all WUs with optional status filter
|
|
40
|
+
* Uses CLI shell-out for consistency with other tools
|
|
41
|
+
*/
|
|
42
|
+
export declare const wuListTool: ToolDefinition;
|
|
43
|
+
/**
|
|
44
|
+
* wu_status - Get status of a specific WU
|
|
45
|
+
* Uses CLI shell-out for consistency
|
|
46
|
+
*/
|
|
47
|
+
export declare const wuStatusTool: ToolDefinition;
|
|
48
|
+
/**
|
|
49
|
+
* wu_create - Create a new WU
|
|
50
|
+
*/
|
|
51
|
+
export declare const wuCreateTool: ToolDefinition;
|
|
52
|
+
/**
|
|
53
|
+
* wu_claim - Claim a WU and create worktree
|
|
54
|
+
*/
|
|
55
|
+
export declare const wuClaimTool: ToolDefinition;
|
|
56
|
+
/**
|
|
57
|
+
* wu_done - Complete a WU (must be run from main checkout)
|
|
58
|
+
*/
|
|
59
|
+
export declare const wuDoneTool: ToolDefinition;
|
|
60
|
+
/**
|
|
61
|
+
* gates_run - Run quality gates
|
|
62
|
+
*/
|
|
63
|
+
export declare const gatesRunTool: ToolDefinition;
|
|
64
|
+
/**
|
|
65
|
+
* All available tools
|
|
66
|
+
*/
|
|
67
|
+
export declare const allTools: ToolDefinition[];
|