luma-mcp 1.0.0 → 1.0.1
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/.claude/settings.local.json +10 -0
- package/README.md +4 -3
- package/build/index.js +9 -9
- package/build/index.js.map +1 -1
- package/mcp-server/README.md +41 -0
- package/mcp-server/README.zh-CN.md +42 -0
- package/mcp-server/build/core/api-common.js +122 -0
- package/mcp-server/build/core/chat-service.js +80 -0
- package/mcp-server/build/core/environment.js +128 -0
- package/mcp-server/build/core/error-handler.js +376 -0
- package/mcp-server/build/core/file-service.js +126 -0
- package/mcp-server/build/index.js +160 -0
- package/mcp-server/build/tools/image-analysis.js +125 -0
- package/mcp-server/build/tools/video-analysis.js +125 -0
- package/mcp-server/build/types/index.js +35 -0
- package/mcp-server/build/types/validation-types.js +1 -0
- package/mcp-server/build/utils/logger.js +120 -0
- package/mcp-server/build/utils/validation.js +198 -0
- package/mcp-server/package.json +53 -0
- package/package.json +3 -3
|
@@ -0,0 +1,376 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
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;
|
|
@@ -0,0 +1,160 @@
|
|
|
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();
|