@vfarcic/dot-ai 0.153.0 → 0.155.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.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Authentication module for HTTP transport
3
+ *
4
+ * Provides Bearer token authentication for the MCP HTTP server.
5
+ * Authentication is optional and only enabled when DOT_AI_AUTH_TOKEN is set.
6
+ */
7
+ import { IncomingMessage } from 'node:http';
8
+ export interface AuthResult {
9
+ authorized: boolean;
10
+ message?: string;
11
+ }
12
+ /**
13
+ * Check Bearer token authentication for HTTP requests.
14
+ *
15
+ * Authentication is only required when DOT_AI_AUTH_TOKEN environment variable is set.
16
+ * Uses constant-time comparison to prevent timing attacks.
17
+ *
18
+ * @param req - The incoming HTTP request
19
+ * @returns AuthResult indicating if request is authorized
20
+ */
21
+ export declare function checkBearerAuth(req: IncomingMessage): AuthResult;
22
+ /**
23
+ * Check if authentication is enabled (DOT_AI_AUTH_TOKEN is set)
24
+ */
25
+ export declare function isAuthEnabled(): boolean;
26
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/interfaces/auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,eAAe,GAAG,UAAU,CAoEhE;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * Authentication module for HTTP transport
4
+ *
5
+ * Provides Bearer token authentication for the MCP HTTP server.
6
+ * Authentication is optional and only enabled when DOT_AI_AUTH_TOKEN is set.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.checkBearerAuth = checkBearerAuth;
10
+ exports.isAuthEnabled = isAuthEnabled;
11
+ const node_crypto_1 = require("node:crypto");
12
+ /**
13
+ * Check Bearer token authentication for HTTP requests.
14
+ *
15
+ * Authentication is only required when DOT_AI_AUTH_TOKEN environment variable is set.
16
+ * Uses constant-time comparison to prevent timing attacks.
17
+ *
18
+ * @param req - The incoming HTTP request
19
+ * @returns AuthResult indicating if request is authorized
20
+ */
21
+ function checkBearerAuth(req) {
22
+ const configuredToken = process.env.DOT_AI_AUTH_TOKEN;
23
+ // If no token configured, authentication is disabled (backward compatible)
24
+ if (!configuredToken) {
25
+ return { authorized: true };
26
+ }
27
+ const rawAuthHeader = req.headers['authorization'];
28
+ // Check if Authorization header is present
29
+ if (!rawAuthHeader) {
30
+ return {
31
+ authorized: false,
32
+ message: 'Authentication required. Provide Authorization: Bearer <token> header.'
33
+ };
34
+ }
35
+ // Normalize header to string (handle array case)
36
+ const authHeader = Array.isArray(rawAuthHeader)
37
+ ? (rawAuthHeader[0] ?? '')
38
+ : rawAuthHeader;
39
+ // Parse Bearer token (case-insensitive per RFC 7235)
40
+ // Use split instead of regex to avoid ReDoS vulnerability
41
+ const trimmedHeader = authHeader.trim();
42
+ const spaceIndex = trimmedHeader.indexOf(' ');
43
+ if (spaceIndex === -1) {
44
+ return {
45
+ authorized: false,
46
+ message: 'Invalid authorization format. Expected: Bearer <token>'
47
+ };
48
+ }
49
+ const scheme = trimmedHeader.slice(0, spaceIndex);
50
+ const providedToken = trimmedHeader.slice(spaceIndex + 1).trim();
51
+ // Validate Bearer scheme (case-insensitive)
52
+ if (scheme.toLowerCase() !== 'bearer') {
53
+ return {
54
+ authorized: false,
55
+ message: 'Invalid authorization format. Expected: Bearer <token>'
56
+ };
57
+ }
58
+ // Check if token is empty
59
+ if (!providedToken) {
60
+ return { authorized: false, message: 'Bearer token is empty.' };
61
+ }
62
+ // Use constant-time comparison to prevent timing attacks
63
+ // Both buffers must be same length for timingSafeEqual
64
+ const configuredBuffer = Buffer.from(configuredToken, 'utf8');
65
+ const providedBuffer = Buffer.from(providedToken, 'utf8');
66
+ // If lengths differ, tokens don't match (but still do constant-time operation to avoid timing leak)
67
+ if (configuredBuffer.length !== providedBuffer.length) {
68
+ // Perform a dummy comparison to maintain constant time
69
+ (0, node_crypto_1.timingSafeEqual)(configuredBuffer, configuredBuffer);
70
+ return { authorized: false, message: 'Invalid authentication token.' };
71
+ }
72
+ if (!(0, node_crypto_1.timingSafeEqual)(configuredBuffer, providedBuffer)) {
73
+ return { authorized: false, message: 'Invalid authentication token.' };
74
+ }
75
+ return { authorized: true };
76
+ }
77
+ /**
78
+ * Check if authentication is enabled (DOT_AI_AUTH_TOKEN is set)
79
+ */
80
+ function isAuthEnabled() {
81
+ return !!process.env.DOT_AI_AUTH_TOKEN;
82
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shared error response formatting for HTTP interfaces
3
+ *
4
+ * Provides consistent error response format across MCP server and REST API.
5
+ */
6
+ import { ServerResponse } from 'node:http';
7
+ export interface ErrorResponseBody {
8
+ success: false;
9
+ error: {
10
+ code: string;
11
+ message: string;
12
+ details?: unknown;
13
+ };
14
+ }
15
+ /**
16
+ * Format an error response body in the standard REST API format.
17
+ *
18
+ * @param code - Error code (e.g., 'UNAUTHORIZED', 'INTERNAL_ERROR')
19
+ * @param message - Human-readable error message
20
+ * @param details - Optional additional details
21
+ * @returns Formatted error response body
22
+ */
23
+ export declare function formatErrorResponse(code: string, message: string, details?: unknown): ErrorResponseBody;
24
+ /**
25
+ * Send an error response with consistent formatting.
26
+ *
27
+ * @param res - HTTP server response
28
+ * @param statusCode - HTTP status code
29
+ * @param code - Error code
30
+ * @param message - Human-readable error message
31
+ * @param details - Optional additional details
32
+ */
33
+ export declare function sendErrorResponse(res: ServerResponse, statusCode: number, code: string, message: string, details?: unknown): void;
34
+ //# sourceMappingURL=error-response.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-response.d.ts","sourceRoot":"","sources":["../../src/interfaces/error-response.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,OAAO,GAChB,iBAAiB,CASnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,cAAc,EACnB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,OAAO,GAChB,IAAI,CAIN"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * Shared error response formatting for HTTP interfaces
4
+ *
5
+ * Provides consistent error response format across MCP server and REST API.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.formatErrorResponse = formatErrorResponse;
9
+ exports.sendErrorResponse = sendErrorResponse;
10
+ /**
11
+ * Format an error response body in the standard REST API format.
12
+ *
13
+ * @param code - Error code (e.g., 'UNAUTHORIZED', 'INTERNAL_ERROR')
14
+ * @param message - Human-readable error message
15
+ * @param details - Optional additional details
16
+ * @returns Formatted error response body
17
+ */
18
+ function formatErrorResponse(code, message, details) {
19
+ return {
20
+ success: false,
21
+ error: {
22
+ code,
23
+ message,
24
+ ...(details !== undefined && { details })
25
+ }
26
+ };
27
+ }
28
+ /**
29
+ * Send an error response with consistent formatting.
30
+ *
31
+ * @param res - HTTP server response
32
+ * @param statusCode - HTTP status code
33
+ * @param code - Error code
34
+ * @param message - Human-readable error message
35
+ * @param details - Optional additional details
36
+ */
37
+ function sendErrorResponse(res, statusCode, code, message, details) {
38
+ const body = formatErrorResponse(code, message, details);
39
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
40
+ res.end(JSON.stringify(body));
41
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/interfaces/mcp.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAgDtC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;CACxC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,UAAU,CAAC,CAAkC;IACrD,OAAO,CAAC,aAAa,CAAC,CAAgC;IACtD,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe;IAsCjD;;OAEG;IACH,OAAO,CAAC,YAAY;IA2BpB;;OAEG;IACH,OAAO,CAAC,aAAa;IA6HrB;;OAEG;IACH,OAAO,CAAC,eAAe;IAqCvB,OAAO,CAAC,iBAAiB;IAInB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAkBd,mBAAmB;YAMnB,kBAAkB;YA6GlB,gBAAgB;IAexB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3B,OAAO,IAAI,OAAO;CAGnB"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/interfaces/mcp.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAkDtC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;CACxC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,UAAU,CAAC,CAAkC;IACrD,OAAO,CAAC,aAAa,CAAC,CAAgC;IACtD,OAAO,CAAC,YAAY,CAAmB;IACvC,OAAO,CAAC,aAAa,CAAgB;gBAEzB,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe;IAsCjD;;OAEG;IACH,OAAO,CAAC,YAAY;IA2BpB;;OAEG;IACH,OAAO,CAAC,aAAa;IA6HrB;;OAEG;IACH,OAAO,CAAC,eAAe;IAqCvB,OAAO,CAAC,iBAAiB;IAInB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAkBd,mBAAmB;YAMnB,kBAAkB;YAmHlB,gBAAgB;IAexB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3B,OAAO,IAAI,OAAO;CAGnB"}
@@ -23,6 +23,8 @@ const project_setup_1 = require("../tools/project-setup");
23
23
  const prompts_1 = require("../tools/prompts");
24
24
  const rest_registry_1 = require("./rest-registry");
25
25
  const rest_api_1 = require("./rest-api");
26
+ const auth_1 = require("./auth");
27
+ const error_response_1 = require("./error-response");
26
28
  const tracing_1 = require("../core/tracing");
27
29
  const api_1 = require("@opentelemetry/api");
28
30
  class MCPServer {
@@ -215,13 +217,21 @@ class MCPServer {
215
217
  // Handle CORS for browser-based clients
216
218
  res.setHeader('Access-Control-Allow-Origin', '*');
217
219
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
218
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Session-Id');
220
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Session-Id, Authorization');
219
221
  if (req.method === 'OPTIONS') {
220
222
  res.writeHead(204);
221
223
  res.end();
222
224
  endSpan(204);
223
225
  return;
224
226
  }
227
+ // Check Bearer token authentication (only when DOT_AI_AUTH_TOKEN is set)
228
+ const authResult = (0, auth_1.checkBearerAuth)(req);
229
+ if (!authResult.authorized) {
230
+ this.logger.warn('Authentication failed', { message: authResult.message });
231
+ (0, error_response_1.sendErrorResponse)(res, 401, 'UNAUTHORIZED', authResult.message || 'Authentication required');
232
+ endSpan(401);
233
+ return;
234
+ }
225
235
  // Parse request body for POST requests
226
236
  let body = undefined;
227
237
  if (req.method === 'POST') {
@@ -240,8 +250,7 @@ class MCPServer {
240
250
  catch (error) {
241
251
  this.logger.error('REST API request failed', error);
242
252
  if (!res.headersSent) {
243
- res.writeHead(500, { 'Content-Type': 'application/json' });
244
- res.end(JSON.stringify({ error: 'REST API internal server error' }));
253
+ (0, error_response_1.sendErrorResponse)(res, 500, 'INTERNAL_ERROR', 'REST API internal server error');
245
254
  }
246
255
  endSpan(500);
247
256
  return;
@@ -258,8 +267,7 @@ class MCPServer {
258
267
  catch (error) {
259
268
  this.logger.error('Error handling MCP HTTP request', error);
260
269
  if (!res.headersSent) {
261
- res.writeHead(500, { 'Content-Type': 'application/json' });
262
- res.end(JSON.stringify({ error: 'MCP internal server error' }));
270
+ (0, error_response_1.sendErrorResponse)(res, 500, 'INTERNAL_ERROR', 'MCP internal server error');
263
271
  }
264
272
  endSpan(500);
265
273
  }
@@ -270,8 +278,7 @@ class MCPServer {
270
278
  span.recordException(error);
271
279
  endSpan(500);
272
280
  if (!res.headersSent) {
273
- res.writeHead(500, { 'Content-Type': 'application/json' });
274
- res.end(JSON.stringify({ error: 'Internal server error' }));
281
+ (0, error_response_1.sendErrorResponse)(res, 500, 'INTERNAL_ERROR', 'Internal server error');
275
282
  }
276
283
  }
277
284
  }); // Close context.with()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vfarcic/dot-ai",
3
- "version": "0.153.0",
3
+ "version": "0.155.0",
4
4
  "description": "AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance",
5
5
  "mcpName": "io.github.vfarcic/dot-ai",
6
6
  "main": "dist/index.js",
@@ -154,9 +154,17 @@ Please enter 1 or 2:
154
154
 
155
155
  ### Option 1: Start Working Now
156
156
 
157
- If user chooses option 1, execute: **prd-start [issue-id]**
157
+ If user chooses option 1, first commit and push the PRD (same as Option 2), then instruct them:
158
158
 
159
- This will help identify the first task and set up the development workflow.
159
+ ---
160
+
161
+ **PRD committed and pushed.**
162
+
163
+ To start working on this PRD, run the `prd-start` prompt with the PRD ID: `prd-start [issue-id]`
164
+
165
+ *Note: Different agents/clients may have different syntax for executing commands and prompts (e.g., `/prd-start [issue-id]` in Claude Code, or other syntax in different MCP clients). Start a new conversation/context to run the prompt.*
166
+
167
+ ---
160
168
 
161
169
  ### Option 2: Commit and Push for Later
162
170