@vfarcic/dot-ai 0.154.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.
- package/dist/interfaces/auth.d.ts +26 -0
- package/dist/interfaces/auth.d.ts.map +1 -0
- package/dist/interfaces/auth.js +82 -0
- package/dist/interfaces/error-response.d.ts +34 -0
- package/dist/interfaces/error-response.d.ts.map +1 -0
- package/dist/interfaces/error-response.js +41 -0
- package/dist/interfaces/mcp.d.ts.map +1 -1
- package/dist/interfaces/mcp.js +14 -7
- package/package.json +1 -1
|
@@ -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;
|
|
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"}
|
package/dist/interfaces/mcp.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|