luma-mcp 1.0.1 → 1.0.3

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.
@@ -1,376 +0,0 @@
1
- import { ApiError, ValidationError } from '../types/index.js';
2
- /**
3
- * Error severity levels
4
- */
5
- export var ErrorSeverity;
6
- (function (ErrorSeverity) {
7
- ErrorSeverity["LOW"] = "low";
8
- ErrorSeverity["MEDIUM"] = "medium";
9
- ErrorSeverity["HIGH"] = "high";
10
- ErrorSeverity["CRITICAL"] = "critical";
11
- })(ErrorSeverity || (ErrorSeverity = {}));
12
- /**
13
- * Error categories
14
- */
15
- export var ErrorCategory;
16
- (function (ErrorCategory) {
17
- ErrorCategory["VALIDATION"] = "validation";
18
- ErrorCategory["AUTHENTICATION"] = "authentication";
19
- ErrorCategory["AUTHORIZATION"] = "authorization";
20
- ErrorCategory["NETWORK"] = "network";
21
- ErrorCategory["API"] = "api";
22
- ErrorCategory["SYSTEM"] = "system";
23
- ErrorCategory["BUSINESS"] = "business";
24
- ErrorCategory["UNKNOWN"] = "unknown";
25
- })(ErrorCategory || (ErrorCategory = {}));
26
- /**
27
- * Base error class
28
- */
29
- export class BaseError extends Error {
30
- code;
31
- severity;
32
- category;
33
- context;
34
- cause;
35
- recoverable;
36
- constructor(message, code, severity, category, context = {}, cause, recoverable = true) {
37
- super(message);
38
- this.name = this.constructor.name;
39
- this.code = code;
40
- this.severity = severity;
41
- this.category = category;
42
- this.context = {
43
- timestamp: Date.now(),
44
- ...context
45
- };
46
- this.cause = cause;
47
- this.recoverable = recoverable;
48
- // Preserve stack trace
49
- if (Error.captureStackTrace) {
50
- Error.captureStackTrace(this, this.constructor);
51
- }
52
- }
53
- /**
54
- * Convert to JSON format
55
- */
56
- toJSON() {
57
- return {
58
- name: this.name,
59
- message: this.message,
60
- code: this.code,
61
- severity: this.severity,
62
- category: this.category,
63
- context: this.context,
64
- recoverable: this.recoverable,
65
- stack: this.stack,
66
- cause: this.cause ? {
67
- name: this.cause.name,
68
- message: this.cause.message,
69
- stack: this.cause.stack
70
- } : undefined
71
- };
72
- }
73
- }
74
- /**
75
- * Business logic error
76
- */
77
- export class BusinessError extends BaseError {
78
- constructor(message, code = 'BUSINESS_ERROR', context = {}, cause) {
79
- super(message, code, ErrorSeverity.MEDIUM, ErrorCategory.BUSINESS, context, cause, true);
80
- }
81
- getUserMessage() {
82
- return this.message;
83
- }
84
- }
85
- /**
86
- * System error
87
- */
88
- export class SystemError extends BaseError {
89
- constructor(message, code = 'SYSTEM_ERROR', context = {}, cause) {
90
- super(message, code, ErrorSeverity.HIGH, ErrorCategory.SYSTEM, context, cause, false);
91
- }
92
- getUserMessage() {
93
- return 'An internal system error occurred. Please try again later.';
94
- }
95
- }
96
- /**
97
- * Network error
98
- */
99
- export class NetworkError extends BaseError {
100
- constructor(message, code = 'NETWORK_ERROR', context = {}, cause) {
101
- super(message, code, ErrorSeverity.MEDIUM, ErrorCategory.NETWORK, context, cause, true);
102
- }
103
- getUserMessage() {
104
- return 'Network connection error. Please check your connection and try again.';
105
- }
106
- }
107
- /**
108
- * Authentication error
109
- */
110
- export class AuthenticationError extends BaseError {
111
- constructor(message, code = 'AUTHENTICATION_ERROR', context = {}, cause) {
112
- super(message, code, ErrorSeverity.HIGH, ErrorCategory.AUTHENTICATION, context, cause, false);
113
- }
114
- getUserMessage() {
115
- return 'Authentication failed. Please check your credentials.';
116
- }
117
- }
118
- /**
119
- * Authorization error
120
- */
121
- export class AuthorizationError extends BaseError {
122
- constructor(message, code = 'AUTHORIZATION_ERROR', context = {}, cause) {
123
- super(message, code, ErrorSeverity.HIGH, ErrorCategory.AUTHORIZATION, context, cause, false);
124
- }
125
- getUserMessage() {
126
- return 'Access denied. You do not have permission to perform this action.';
127
- }
128
- }
129
- /**
130
- * Tool execution error
131
- */
132
- export class ToolExecutionError extends BaseError {
133
- constructor(message, toolName, code = 'TOOL_EXECUTION_ERROR', context = {}, cause) {
134
- super(message, code, ErrorSeverity.MEDIUM, ErrorCategory.BUSINESS, { ...context, toolName }, cause, true);
135
- }
136
- getUserMessage() {
137
- return `Tool execution failed: ${this.message}`;
138
- }
139
- }
140
- /**
141
- * Default error handling strategy
142
- */
143
- export class DefaultErrorHandlingStrategy {
144
- canHandle(error) {
145
- return true; // Default strategy handles all errors
146
- }
147
- async handle(error, context = { timestamp: Date.now() }) {
148
- // If already a standardized error, return directly
149
- if (error instanceof BaseError) {
150
- return error;
151
- }
152
- // Handle known error types
153
- if (error instanceof ValidationError) {
154
- return new BusinessError(error.message, 'VALIDATION_ERROR', context, error);
155
- }
156
- if (error instanceof ApiError) {
157
- return new SystemError(error.message, 'API_ERROR', context, error);
158
- }
159
- // Handle unknown errors
160
- return new SystemError(error.message || 'An unknown error occurred', 'UNKNOWN_ERROR', context, error);
161
- }
162
- }
163
- /**
164
- * Network error handling strategy
165
- */
166
- export class NetworkErrorHandlingStrategy {
167
- canHandle(error) {
168
- return error.message.includes('network') ||
169
- error.message.includes('timeout') ||
170
- error.message.includes('connection') ||
171
- error.name === 'NetworkError';
172
- }
173
- async handle(error, context = { timestamp: Date.now() }) {
174
- return new NetworkError(error.message, 'NETWORK_CONNECTION_ERROR', context, error);
175
- }
176
- }
177
- /**
178
- * Retry recovery strategy
179
- */
180
- export class RetryRecoveryStrategy {
181
- maxRetries;
182
- retryDelay;
183
- constructor(maxRetries = 3, retryDelay = 1000) {
184
- this.maxRetries = maxRetries;
185
- this.retryDelay = retryDelay;
186
- }
187
- canRecover(error) {
188
- return error.recoverable &&
189
- (error.category === ErrorCategory.NETWORK ||
190
- error.category === ErrorCategory.API);
191
- }
192
- async recover(error) {
193
- console.info(`Attempting recovery for error: ${error.code}`, {
194
- maxRetries: this.maxRetries,
195
- retryDelay: this.retryDelay
196
- });
197
- // Implement specific retry logic here
198
- // For example, re-execute the failed operation
199
- await new Promise(resolve => setTimeout(resolve, this.retryDelay));
200
- return { recovered: true, strategy: 'retry' };
201
- }
202
- }
203
- /**
204
- * Unified error handler
205
- */
206
- export class ErrorHandler {
207
- strategies = [];
208
- recoveryStrategies = [];
209
- constructor() {
210
- // Register default strategy
211
- this.addStrategy(new NetworkErrorHandlingStrategy());
212
- this.addStrategy(new DefaultErrorHandlingStrategy()); // Default strategy goes last
213
- // Register recovery strategies
214
- this.addRecoveryStrategy(new RetryRecoveryStrategy());
215
- }
216
- /**
217
- * Add error handling strategy
218
- * @param strategy Error handling strategy
219
- */
220
- addStrategy(strategy) {
221
- this.strategies.push(strategy);
222
- }
223
- /**
224
- * Add error recovery strategy
225
- * @param strategy Error recovery strategy
226
- */
227
- addRecoveryStrategy(strategy) {
228
- this.recoveryStrategies.push(strategy);
229
- }
230
- /**
231
- * Handle error
232
- * @param error Original error
233
- * @param context Error context
234
- * @returns Standardized error
235
- */
236
- async handleError(error, context) {
237
- const errorContext = {
238
- timestamp: Date.now(),
239
- ...context
240
- };
241
- try {
242
- // Find appropriate handling strategy
243
- const strategy = this.strategies.find(s => s.canHandle(error));
244
- if (!strategy) {
245
- console.warn('No suitable error handling strategy found', { error: error.message });
246
- return new SystemError('No error handler available', 'NO_HANDLER_ERROR', errorContext, error);
247
- }
248
- // Handle error
249
- const standardError = await strategy.handle(error, errorContext);
250
- // Log error
251
- this.logError(standardError);
252
- // Attempt recovery
253
- await this.attemptRecovery(standardError);
254
- return standardError;
255
- }
256
- catch (handlingError) {
257
- console.error('Error occurred while handling error', {
258
- originalError: error.message,
259
- handlingError: handlingError instanceof Error ? handlingError.message : handlingError
260
- });
261
- return new SystemError('Error handling failed', 'ERROR_HANDLER_FAILURE', errorContext, error);
262
- }
263
- }
264
- /**
265
- * Attempt error recovery
266
- * @param error Standardized error
267
- */
268
- async attemptRecovery(error) {
269
- const recoveryStrategy = this.recoveryStrategies.find(s => s.canRecover(error));
270
- if (recoveryStrategy) {
271
- try {
272
- await recoveryStrategy.recover(error);
273
- console.info('Error recovery successful', { errorCode: error.code });
274
- }
275
- catch (recoveryError) {
276
- console.warn('Error recovery failed', {
277
- errorCode: error.code,
278
- recoveryError: recoveryError instanceof Error ? recoveryError.message : recoveryError
279
- });
280
- }
281
- }
282
- }
283
- /**
284
- * Log error
285
- * @param error Standardized error
286
- */
287
- logError(error) {
288
- const logData = {
289
- code: error.code,
290
- message: error.message,
291
- severity: error.severity,
292
- category: error.category,
293
- context: error.context,
294
- recoverable: error.recoverable
295
- };
296
- switch (error.severity) {
297
- case ErrorSeverity.CRITICAL:
298
- console.error('Critical error occurred', logData);
299
- break;
300
- case ErrorSeverity.HIGH:
301
- console.error('High severity error occurred', logData);
302
- break;
303
- case ErrorSeverity.MEDIUM:
304
- console.warn('Medium severity error occurred', logData);
305
- break;
306
- case ErrorSeverity.LOW:
307
- console.info('Low severity error occurred', logData);
308
- break;
309
- default:
310
- console.warn('Unknown severity error occurred', logData);
311
- }
312
- }
313
- /**
314
- * Create error context
315
- * @param options Context options
316
- */
317
- static createContext(options = {}) {
318
- return {
319
- timestamp: Date.now(),
320
- ...options
321
- };
322
- }
323
- }
324
- /**
325
- * Global error handler instance
326
- */
327
- export const globalErrorHandler = new ErrorHandler();
328
- /**
329
- * Convenience function: handle error
330
- * @param error Error object
331
- * @param context Error context
332
- */
333
- export async function handleError(error, context) {
334
- return globalErrorHandler.handleError(error, context);
335
- }
336
- /**
337
- * Convenience function: create business error
338
- * @param message Error message
339
- * @param code Error code
340
- * @param context Error context
341
- */
342
- export function createBusinessError(message, code, context) {
343
- return new BusinessError(message, code, context);
344
- }
345
- /**
346
- * Convenience function: create system error
347
- * @param message Error message
348
- * @param code Error code
349
- * @param context Error context
350
- */
351
- export function createSystemError(message, code, context) {
352
- return new SystemError(message, code, context);
353
- }
354
- /**
355
- * Error handling decorator
356
- * @param errorHandler Error handler (optional)
357
- */
358
- export function HandleErrors(errorHandler) {
359
- return function (target, propertyKey, descriptor) {
360
- const originalMethod = descriptor.value;
361
- const handler = errorHandler || globalErrorHandler;
362
- descriptor.value = async function (...args) {
363
- try {
364
- return await originalMethod.apply(this, args);
365
- }
366
- catch (error) {
367
- const context = ErrorHandler.createContext({
368
- operation: `${target.constructor.name}.${propertyKey}`
369
- });
370
- const standardError = await handler.handleError(error, context);
371
- throw standardError;
372
- }
373
- };
374
- return descriptor;
375
- };
376
- }
@@ -1,126 +0,0 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
- import { FileNotFoundError, ValidationError } from '../types/index.js';
4
- /**
5
- * File operations service
6
- */
7
- export class FileService {
8
- /**
9
- * Check if a string is a URL
10
- */
11
- static isUrl(source) {
12
- try {
13
- const url = new URL(source);
14
- return url.protocol === 'http:' || url.protocol === 'https:';
15
- }
16
- catch {
17
- return false;
18
- }
19
- }
20
- /**
21
- * Validate if image source exists and check size limit
22
- * @param imageSource Path to image file or URL
23
- * @param maxSizeMB Maximum file size in MB (default: 5MB)
24
- */
25
- static async validateImageSource(imageSource, maxSizeMB = 5) {
26
- if (this.isUrl(imageSource)) {
27
- return;
28
- }
29
- if (!fs.existsSync(imageSource)) {
30
- throw new FileNotFoundError(`Image file not found: ${imageSource}`);
31
- }
32
- const stats = fs.statSync(imageSource);
33
- const maxSizeBytes = maxSizeMB * 1024 * 1024;
34
- if (stats.size > maxSizeBytes) {
35
- throw new ValidationError(`Image file too large: ${(stats.size / (1024 * 1024)).toFixed(2)}MB. Maximum allowed: ${maxSizeMB}MB`);
36
- }
37
- const ext = path.extname(imageSource).toLowerCase();
38
- const supportedExts = ['.jpg', '.jpeg', '.png'];
39
- if (!supportedExts.includes(ext)) {
40
- throw new ValidationError(`Unsupported image format: ${ext}. Supported formats: ${supportedExts.join(', ')}`);
41
- }
42
- }
43
- /**
44
- * Validate if video source exists and check size limit
45
- * @param videoSource Path to video file or URL
46
- * @param maxSizeMB Maximum file size in MB (default: 8MB)
47
- */
48
- static async validateVideoSource(videoSource, maxSizeMB = 8) {
49
- if (this.isUrl(videoSource)) {
50
- return;
51
- }
52
- if (!fs.existsSync(videoSource)) {
53
- throw new FileNotFoundError(videoSource);
54
- }
55
- const stats = fs.statSync(videoSource);
56
- const fileSizeMB = stats.size / (1024 * 1024);
57
- if (fileSizeMB > maxSizeMB) {
58
- throw new Error(`Video file size (${fileSizeMB.toFixed(2)}MB) exceeds maximum allowed size (${maxSizeMB}MB)`);
59
- }
60
- }
61
- /**
62
- * Encode image to base64 data URL (from file or URL)
63
- * @param imageSource Path to image file or URL
64
- * @returns Base64 encoded image data or URL
65
- */
66
- static async encodeImageToBase64(imageSource) {
67
- if (this.isUrl(imageSource)) {
68
- // For URLs, return the URL directly (no base64 encoding needed)
69
- return imageSource;
70
- }
71
- // For local files, encode to base64
72
- const imageBuffer = fs.readFileSync(imageSource);
73
- const ext = path.extname(imageSource).toLowerCase().slice(1);
74
- const mimeType = this.getMimeType(ext);
75
- console.debug('Encoded image to base64', { imageSource, mimeType });
76
- return `data:${mimeType};base64,${imageBuffer.toString('base64')}`;
77
- }
78
- /**
79
- * Encode video to base64 data URL (from file or URL)
80
- * @param videoSource Path to video file or URL
81
- * @returns Base64 encoded video data URL
82
- */
83
- static async encodeVideoToBase64(videoSource) {
84
- if (this.isUrl(videoSource)) {
85
- // For URLs, return the URL directly (no base64 encoding needed)
86
- return videoSource;
87
- }
88
- // For local files, encode to base64
89
- const videoBuffer = fs.readFileSync(videoSource);
90
- const ext = path.extname(videoSource).toLowerCase().slice(1);
91
- const mimeType = this.getVideoMimeType(ext);
92
- console.debug('Encoded video to base64', { videoSource, mimeType });
93
- return `data:${mimeType};base64,${videoBuffer.toString('base64')}`;
94
- }
95
- /**
96
- * Get MIME type for image file extension
97
- */
98
- static getMimeType(extension) {
99
- const mimeTypes = {
100
- png: 'image/png',
101
- jpg: 'image/jpeg',
102
- jpeg: 'image/jpeg'
103
- };
104
- return mimeTypes[extension] || 'image/png';
105
- }
106
- /**
107
- * Get MIME type for video file extension
108
- * @param extension File extension without dot
109
- * @returns MIME type string
110
- */
111
- static getVideoMimeType(extension) {
112
- const mimeTypes = {
113
- mp4: 'video/mp4',
114
- avi: 'video/x-msvideo',
115
- mov: 'video/quicktime',
116
- wmv: 'video/x-ms-wmv',
117
- webm: 'video/webm',
118
- m4v: 'video/x-m4v'
119
- };
120
- return mimeTypes[extension] || 'video/mp4';
121
- }
122
- }
123
- /**
124
- * File service for image operations
125
- */
126
- export const fileService = FileService;
@@ -1,160 +0,0 @@
1
- #!/usr/bin/env node
2
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { configurationService } from './core/environment.js';
5
- import { handleError } from './core/error-handler.js';
6
- import { setupConsoleRedirection } from './utils/logger.js';
7
- // Setup console redirection BEFORE any other code to prevent stdout pollution
8
- setupConsoleRedirection();
9
- // Import tool registration functions
10
- import { registerImageAnalysisTool } from './tools/image-analysis.js';
11
- import { registerVideoAnalysisTool } from './tools/video-analysis.js';
12
- import { McpError } from "./types/index.js";
13
- /**
14
- * MCP Server Application class
15
- */
16
- class McpServerApplication {
17
- server;
18
- constructor() {
19
- this.server = new McpServer({
20
- name: configurationService.getServerConfig().name,
21
- version: configurationService.getServerConfig().version
22
- }, {
23
- capabilities: {
24
- tools: {}
25
- }
26
- });
27
- this.setupErrorHandling();
28
- console.info('MCP Server Application initialized');
29
- }
30
- /**
31
- * Register all tools
32
- */
33
- async registerTools() {
34
- try {
35
- // Register tools directly with server
36
- registerImageAnalysisTool(this.server);
37
- registerVideoAnalysisTool(this.server);
38
- console.info('Successfully registered all tools');
39
- }
40
- catch (error) {
41
- const standardError = await handleError(error, {
42
- operation: 'tool-registration',
43
- metadata: { component: 'McpServerApplication' }
44
- });
45
- console.error('Failed to register tools', standardError);
46
- throw standardError;
47
- }
48
- }
49
- /**
50
- * Setup error handling
51
- */
52
- setupErrorHandling() {
53
- process.on('uncaughtException', (error) => {
54
- console.error('Uncaught exception:', error);
55
- this.gracefulShutdown(1);
56
- });
57
- process.on('unhandledRejection', (reason, promise) => {
58
- console.error('Unhandled rejection at:', { promise, reason });
59
- this.gracefulShutdown(1);
60
- });
61
- process.on('SIGINT', () => {
62
- console.info('Received SIGINT, shutting down gracefully...');
63
- this.gracefulShutdown(0);
64
- });
65
- process.on('SIGTERM', () => {
66
- console.info('Received SIGTERM, shutting down gracefully...');
67
- this.gracefulShutdown(0);
68
- });
69
- }
70
- /**
71
- * Graceful shutdown
72
- */
73
- gracefulShutdown(exitCode) {
74
- try {
75
- console.info('Performing graceful shutdown...');
76
- // Cleanup logic can be added here
77
- process.exit(exitCode);
78
- }
79
- catch (error) {
80
- console.error('Error during graceful shutdown:', { error });
81
- process.exit(1);
82
- }
83
- }
84
- /**
85
- * Start server
86
- */
87
- async start() {
88
- try {
89
- console.info('Starting MCP server...');
90
- // Set up global error handling
91
- process.on('uncaughtException', async (error) => {
92
- const standardError = await handleError(error, {
93
- operation: 'uncaughtException',
94
- metadata: { source: 'process' }
95
- });
96
- console.error('Uncaught exception:', standardError);
97
- process.exit(1);
98
- });
99
- process.on('unhandledRejection', async (reason) => {
100
- const error = reason instanceof Error ? reason : new Error(String(reason));
101
- const standardError = await handleError(error, {
102
- operation: 'unhandledRejection',
103
- metadata: { source: 'process' }
104
- });
105
- console.error('Unhandled Promise rejection:', standardError);
106
- process.exit(1);
107
- });
108
- // Register tools
109
- await this.registerTools();
110
- // Create transport layer
111
- const transport = new StdioServerTransport();
112
- // Start server
113
- await this.server.connect(transport);
114
- console.info('MCP Server started successfully', {
115
- mode: configurationService.getPlatformModel(),
116
- name: configurationService.getServerConfig().name,
117
- version: configurationService.getServerConfig().version
118
- });
119
- }
120
- catch (error) {
121
- const standardError = await handleError(error, {
122
- operation: 'server-start',
123
- metadata: { component: 'McpServerApplication' }
124
- });
125
- console.error('Server startup failed:', standardError);
126
- throw standardError;
127
- }
128
- }
129
- /**
130
- * Gracefully shutdown server
131
- */
132
- async shutdown() {
133
- try {
134
- console.info('Shutting down MCP server...');
135
- console.info('MCP server shutdown completed');
136
- }
137
- catch (error) {
138
- console.error('Error during server shutdown', { error });
139
- throw error;
140
- }
141
- }
142
- }
143
- // Start application
144
- async function main() {
145
- try {
146
- const app = new McpServerApplication();
147
- await app.start();
148
- }
149
- catch (error) {
150
- if (error instanceof McpError) {
151
- console.error('Application startup failed:', { message: error.message });
152
- }
153
- else {
154
- console.error('Application startup failed:', { error });
155
- }
156
- process.exit(1);
157
- }
158
- }
159
- // Start main program
160
- main();