luma-mcp 1.0.2 → 1.1.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/.env.example +39 -0
- package/CHANGELOG.md +50 -0
- package/README.md +187 -32
- package/build/config.d.ts +2 -0
- package/build/config.d.ts.map +1 -1
- package/build/config.js +24 -8
- package/build/config.js.map +1 -1
- package/build/index.js +17 -6
- package/build/index.js.map +1 -1
- package/build/siliconflow-client.d.ts +23 -0
- package/build/siliconflow-client.d.ts.map +1 -0
- package/build/siliconflow-client.js +85 -0
- package/build/siliconflow-client.js.map +1 -0
- package/build/vision-client.d.ts +18 -0
- package/build/vision-client.d.ts.map +1 -0
- package/build/vision-client.js +5 -0
- package/build/vision-client.js.map +1 -0
- package/build/zhipu-client.d.ts +6 -1
- package/build/zhipu-client.d.ts.map +1 -1
- package/build/zhipu-client.js +10 -3
- package/build/zhipu-client.js.map +1 -1
- package/package.json +8 -3
- package/test/test-deepseek-raw.ts +94 -0
- package/test/test-local.ts +20 -7
- package/.claude/settings.local.json +0 -10
- package/mcp-server/README.md +0 -41
- package/mcp-server/README.zh-CN.md +0 -42
- package/mcp-server/build/core/api-common.js +0 -122
- package/mcp-server/build/core/chat-service.js +0 -80
- package/mcp-server/build/core/environment.js +0 -128
- package/mcp-server/build/core/error-handler.js +0 -376
- package/mcp-server/build/core/file-service.js +0 -126
- package/mcp-server/build/index.js +0 -160
- package/mcp-server/build/tools/image-analysis.js +0 -125
- package/mcp-server/build/tools/video-analysis.js +0 -125
- package/mcp-server/build/types/index.js +0 -35
- package/mcp-server/build/types/validation-types.js +0 -1
- package/mcp-server/build/utils/logger.js +0 -120
- package/mcp-server/build/utils/validation.js +0 -198
- package/mcp-server/package.json +0 -53
|
@@ -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();
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { FileNotFoundError, ApiError } from '../types/index.js';
|
|
3
|
-
import { ToolExecutionError } from '../core/error-handler.js';
|
|
4
|
-
import { CommonSchemas, ToolSchemaBuilder } from '../utils/validation.js';
|
|
5
|
-
import { createMultiModalMessage, createImageContent, formatMcpResponse, createSuccessResponse, createErrorResponse, withRetry } from '../core/api-common.js';
|
|
6
|
-
import { fileService } from '../core/file-service.js';
|
|
7
|
-
import { chatService } from '../core/chat-service.js';
|
|
8
|
-
/**
|
|
9
|
-
* Image analysis service class
|
|
10
|
-
*/
|
|
11
|
-
export class ImageAnalysisService {
|
|
12
|
-
chatService = chatService;
|
|
13
|
-
fileService = fileService;
|
|
14
|
-
MAX_IMAGE_SIZE_MB = 5;
|
|
15
|
-
/**
|
|
16
|
-
* Execute image analysis
|
|
17
|
-
* @param request Image analysis request
|
|
18
|
-
* @returns Analysis result
|
|
19
|
-
*/
|
|
20
|
-
async analyzeImage(request) {
|
|
21
|
-
console.info('Starting image analysis', {
|
|
22
|
-
imageSource: request.imageSource,
|
|
23
|
-
prompt: request.prompt
|
|
24
|
-
});
|
|
25
|
-
try {
|
|
26
|
-
// Validate image source (file or URL) and size
|
|
27
|
-
await this.fileService.validateImageSource(request.imageSource, this.MAX_IMAGE_SIZE_MB);
|
|
28
|
-
// Validate prompt
|
|
29
|
-
if (!request.prompt || request.prompt.trim().length === 0) {
|
|
30
|
-
throw new ToolExecutionError('Prompt is required for image analysis', 'image-analysis', 'VALIDATION_ERROR', {
|
|
31
|
-
toolName: 'image-analysis',
|
|
32
|
-
operation: 'analyzeImage',
|
|
33
|
-
metadata: { imageSource: request.imageSource }
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
// Handle image source (URL or local file)
|
|
37
|
-
let imageContent;
|
|
38
|
-
if (this.fileService.isUrl(request.imageSource)) {
|
|
39
|
-
// For URLs, pass directly without base64 encoding
|
|
40
|
-
imageContent = createImageContent(request.imageSource);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
// For local files, encode to base64
|
|
44
|
-
const imageData = await this.fileService.encodeImageToBase64(request.imageSource);
|
|
45
|
-
imageContent = createImageContent(imageData);
|
|
46
|
-
}
|
|
47
|
-
// Create multimodal message
|
|
48
|
-
const messages = createMultiModalMessage([imageContent], request.prompt);
|
|
49
|
-
const result = await this.chatService.visionCompletions(messages);
|
|
50
|
-
console.info('Image analysis completed', {
|
|
51
|
-
imageSource: request.imageSource
|
|
52
|
-
});
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
console.error('Image analysis failed', {
|
|
57
|
-
error: error instanceof Error ? error.message : String(error),
|
|
58
|
-
imageSource: request.imageSource
|
|
59
|
-
});
|
|
60
|
-
if (error instanceof ToolExecutionError) {
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
63
|
-
// Wrap unknown errors
|
|
64
|
-
throw new ToolExecutionError(`Image analysis failed: ${error.message}`, 'image-analysis', 'EXECUTION_ERROR', {
|
|
65
|
-
toolName: 'image-analysis',
|
|
66
|
-
operation: 'analyzeImage',
|
|
67
|
-
metadata: { imageSource: request.imageSource, originalError: error }
|
|
68
|
-
}, error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Register image analysis tool with MCP server
|
|
74
|
-
* @param server MCP server instance
|
|
75
|
-
*/
|
|
76
|
-
export function registerImageAnalysisTool(server) {
|
|
77
|
-
const analysisService = new ImageAnalysisService();
|
|
78
|
-
const retryableAnalyze = withRetry(analysisService.analyzeImage.bind(analysisService), 2, // Maximum 2 retries
|
|
79
|
-
1000 // 1 second delay
|
|
80
|
-
);
|
|
81
|
-
server.tool('analyze_image', 'Analyze an image using advanced AI vision models with comprehensive understanding capabilities. Supports both local files and remote URL. Maximum file size: 5MB', {
|
|
82
|
-
image_source: z.string().describe('Local file path or remote URL to the image (supports PNG, JPG, JPEG)'),
|
|
83
|
-
prompt: z.string().describe('Detailed text prompt. If the task is **front-end code replication**, the prompt you provide must be: "Describe in detail the layout structure, color style, main components, and interactive elements of the website in this image to facilitate subsequent code generation by the model." + your additional requirements. \ For **other tasks**, the prompt you provide must clearly describe what to analyze, extract, or understand from the image.')
|
|
84
|
-
}, async (params) => {
|
|
85
|
-
try {
|
|
86
|
-
// Validate parameters
|
|
87
|
-
const validationSchema = new ToolSchemaBuilder()
|
|
88
|
-
.required('image_source', CommonSchemas.nonEmptyString)
|
|
89
|
-
.required('prompt', CommonSchemas.nonEmptyString)
|
|
90
|
-
.build();
|
|
91
|
-
validationSchema.parse(params);
|
|
92
|
-
// Build request object
|
|
93
|
-
const request = {
|
|
94
|
-
imageSource: params.image_source,
|
|
95
|
-
prompt: params.prompt
|
|
96
|
-
};
|
|
97
|
-
// Execute analysis
|
|
98
|
-
const result = await retryableAnalyze(request);
|
|
99
|
-
const response = createSuccessResponse(result);
|
|
100
|
-
return formatMcpResponse(response);
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
console.error('Tool execution failed', {
|
|
104
|
-
error: error instanceof Error ? error.message : String(error),
|
|
105
|
-
params
|
|
106
|
-
});
|
|
107
|
-
let errorResponse;
|
|
108
|
-
if (error instanceof z.ZodError) {
|
|
109
|
-
const validationErrors = error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
|
|
110
|
-
errorResponse = createErrorResponse(`Validation failed: ${validationErrors}`);
|
|
111
|
-
}
|
|
112
|
-
else if (error instanceof FileNotFoundError) {
|
|
113
|
-
errorResponse = createErrorResponse(`Image file not found: ${error.message}`);
|
|
114
|
-
}
|
|
115
|
-
else if (error instanceof ApiError) {
|
|
116
|
-
errorResponse = createErrorResponse(`API error: ${error.message}`);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
errorResponse = createErrorResponse(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
|
|
120
|
-
}
|
|
121
|
-
return formatMcpResponse(errorResponse);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
console.info('Image analysis tool registered successfully');
|
|
125
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { FileNotFoundError, ApiError } from '../types/index.js';
|
|
3
|
-
import { ToolExecutionError } from '../core/error-handler.js';
|
|
4
|
-
import { CommonSchemas, ToolSchemaBuilder } from '../utils/validation.js';
|
|
5
|
-
import { createMultiModalMessage, createVideoContent, formatMcpResponse, createSuccessResponse, createErrorResponse, withRetry } from '../core/api-common.js';
|
|
6
|
-
import { fileService } from '../core/file-service.js';
|
|
7
|
-
import { chatService } from '../core/chat-service.js';
|
|
8
|
-
/**
|
|
9
|
-
* Video analysis service class
|
|
10
|
-
*/
|
|
11
|
-
export class VideoAnalysisService {
|
|
12
|
-
chatService = chatService;
|
|
13
|
-
fileService = fileService;
|
|
14
|
-
MAX_VIDEO_SIZE_MB = 8;
|
|
15
|
-
/**
|
|
16
|
-
* Execute video analysis
|
|
17
|
-
* @param request Video analysis request
|
|
18
|
-
* @returns Analysis result
|
|
19
|
-
*/
|
|
20
|
-
async analyzeVideo(request) {
|
|
21
|
-
console.info('Starting video analysis', {
|
|
22
|
-
videoSource: request.videoSource,
|
|
23
|
-
prompt: request.prompt
|
|
24
|
-
});
|
|
25
|
-
try {
|
|
26
|
-
// Validate video source (file or URL) and size
|
|
27
|
-
await this.fileService.validateVideoSource(request.videoSource, this.MAX_VIDEO_SIZE_MB);
|
|
28
|
-
// Validate prompt
|
|
29
|
-
if (!request.prompt || request.prompt.trim().length === 0) {
|
|
30
|
-
throw new ToolExecutionError('Prompt is required for video analysis', 'video-analysis', 'VALIDATION_ERROR', {
|
|
31
|
-
toolName: 'video-analysis',
|
|
32
|
-
operation: 'analyzeVideo',
|
|
33
|
-
metadata: { videoSource: request.videoSource }
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
// Handle video source (URL or local file)
|
|
37
|
-
let videoContent;
|
|
38
|
-
if (this.fileService.isUrl(request.videoSource)) {
|
|
39
|
-
// For URLs, pass directly without base64 encoding
|
|
40
|
-
videoContent = createVideoContent(request.videoSource);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
// For local files, encode to base64
|
|
44
|
-
const videoData = await this.fileService.encodeVideoToBase64(request.videoSource);
|
|
45
|
-
videoContent = createVideoContent(videoData);
|
|
46
|
-
}
|
|
47
|
-
// Create multimodal message
|
|
48
|
-
const messages = createMultiModalMessage([videoContent], request.prompt);
|
|
49
|
-
const result = await this.chatService.visionCompletions(messages);
|
|
50
|
-
console.info('Video analysis completed', {
|
|
51
|
-
videoSource: request.videoSource
|
|
52
|
-
});
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
console.error('Video analysis failed', {
|
|
57
|
-
error: error instanceof Error ? error.message : String(error),
|
|
58
|
-
videoSource: request.videoSource
|
|
59
|
-
});
|
|
60
|
-
if (error instanceof ToolExecutionError) {
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
63
|
-
// Wrap unknown errors
|
|
64
|
-
throw new ToolExecutionError(`Video analysis failed: ${error.message}`, 'video-analysis', 'EXECUTION_ERROR', {
|
|
65
|
-
toolName: 'video-analysis',
|
|
66
|
-
operation: 'analyzeVideo',
|
|
67
|
-
metadata: { videoSource: request.videoSource, originalError: error }
|
|
68
|
-
}, error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Register video analysis tool with MCP server
|
|
74
|
-
* @param server MCP server instance
|
|
75
|
-
*/
|
|
76
|
-
export function registerVideoAnalysisTool(server) {
|
|
77
|
-
const analysisService = new VideoAnalysisService();
|
|
78
|
-
const retryableAnalyze = withRetry(analysisService.analyzeVideo.bind(analysisService), 2, // Maximum 2 retries
|
|
79
|
-
1000 // 1 second delay
|
|
80
|
-
);
|
|
81
|
-
server.tool('analyze_video', 'Analyze a video using advanced AI vision models with comprehensive understanding capabilities. Supports both local files and remote URL. Maximum local file size: 8MB', {
|
|
82
|
-
video_source: z.string().describe('Local file path or remote URL to the video (supports MP4, MOV, M4V)'),
|
|
83
|
-
prompt: z.string().describe('Detailed text prompt describing what to analyze, extract, or understand from the video')
|
|
84
|
-
}, async (params) => {
|
|
85
|
-
try {
|
|
86
|
-
// Validate parameters
|
|
87
|
-
const validationSchema = new ToolSchemaBuilder()
|
|
88
|
-
.required('video_source', CommonSchemas.nonEmptyString)
|
|
89
|
-
.required('prompt', CommonSchemas.nonEmptyString)
|
|
90
|
-
.build();
|
|
91
|
-
validationSchema.parse(params);
|
|
92
|
-
// Build request object
|
|
93
|
-
const request = {
|
|
94
|
-
videoSource: params.video_source,
|
|
95
|
-
prompt: params.prompt
|
|
96
|
-
};
|
|
97
|
-
// Execute analysis
|
|
98
|
-
const result = await retryableAnalyze(request);
|
|
99
|
-
const response = createSuccessResponse(result);
|
|
100
|
-
return formatMcpResponse(response);
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
console.error('Tool execution failed', {
|
|
104
|
-
error: error instanceof Error ? error.message : String(error),
|
|
105
|
-
params
|
|
106
|
-
});
|
|
107
|
-
let errorResponse;
|
|
108
|
-
if (error instanceof z.ZodError) {
|
|
109
|
-
const validationErrors = error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ');
|
|
110
|
-
errorResponse = createErrorResponse(`Validation failed: ${validationErrors}`);
|
|
111
|
-
}
|
|
112
|
-
else if (error instanceof FileNotFoundError) {
|
|
113
|
-
errorResponse = createErrorResponse(`Video file not found: ${error.message}`);
|
|
114
|
-
}
|
|
115
|
-
else if (error instanceof ApiError) {
|
|
116
|
-
errorResponse = createErrorResponse(`API error: ${error.message}`);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
errorResponse = createErrorResponse(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
|
|
120
|
-
}
|
|
121
|
-
return formatMcpResponse(errorResponse);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
console.info('Video analysis tool registered successfully');
|
|
125
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// Error types
|
|
2
|
-
export class McpError extends Error {
|
|
3
|
-
code;
|
|
4
|
-
context;
|
|
5
|
-
constructor(message, code, context) {
|
|
6
|
-
super(message);
|
|
7
|
-
this.code = code;
|
|
8
|
-
this.context = context;
|
|
9
|
-
this.name = 'McpError';
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export class ValidationError extends McpError {
|
|
13
|
-
field;
|
|
14
|
-
constructor(message, context, field) {
|
|
15
|
-
super(message, 'VALIDATION_ERROR', context);
|
|
16
|
-
this.field = field;
|
|
17
|
-
this.name = 'ValidationError';
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
export class ApiError extends McpError {
|
|
21
|
-
statusCode;
|
|
22
|
-
details;
|
|
23
|
-
constructor(message, context, statusCode, details) {
|
|
24
|
-
super(message, 'API_ERROR', context);
|
|
25
|
-
this.statusCode = statusCode;
|
|
26
|
-
this.details = details;
|
|
27
|
-
this.name = 'ApiError';
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
export class FileNotFoundError extends McpError {
|
|
31
|
-
constructor(filePath) {
|
|
32
|
-
super(`File not found: ${filePath}`, 'FILE_NOT_FOUND', { filePath });
|
|
33
|
-
this.name = 'FileNotFoundError';
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Logger utility that writes to stderr and a log file.
|
|
3
|
-
* Stderr keeps MCP JSON on stdout clean; file provides persistent logs.
|
|
4
|
-
*/
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import * as os from 'os';
|
|
8
|
-
class Logger {
|
|
9
|
-
logStream;
|
|
10
|
-
logFilePath;
|
|
11
|
-
constructor(logFilePath) {
|
|
12
|
-
if (logFilePath) {
|
|
13
|
-
this.setLogFile(logFilePath);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
setLogFile(logFilePath) {
|
|
17
|
-
try {
|
|
18
|
-
this.logFilePath = logFilePath;
|
|
19
|
-
const dir = path.dirname(logFilePath);
|
|
20
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
21
|
-
if (this.logStream) {
|
|
22
|
-
this.logStream.end();
|
|
23
|
-
}
|
|
24
|
-
this.logStream = fs.createWriteStream(logFilePath, { flags: 'a' });
|
|
25
|
-
}
|
|
26
|
-
catch (err) {
|
|
27
|
-
const timestamp = new Date().toISOString();
|
|
28
|
-
const msg = `[${timestamp}] ERROR: Failed to initialize log file '${logFilePath}': ${String(err)}`;
|
|
29
|
-
process.stderr.write(msg + '\n');
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
safeStringify(obj) {
|
|
33
|
-
// Serialize Error objects so that message/stack and custom fields are visible
|
|
34
|
-
const replacer = (_key, value) => {
|
|
35
|
-
if (value instanceof Error) {
|
|
36
|
-
const base = {
|
|
37
|
-
name: value.name,
|
|
38
|
-
message: value.message,
|
|
39
|
-
stack: value.stack
|
|
40
|
-
};
|
|
41
|
-
// Include enumerable own properties (e.g., code, statusCode, details, context)
|
|
42
|
-
for (const k of Object.keys(value)) {
|
|
43
|
-
if (!(k in base)) {
|
|
44
|
-
base[k] = value[k];
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return base;
|
|
48
|
-
}
|
|
49
|
-
return value;
|
|
50
|
-
};
|
|
51
|
-
try {
|
|
52
|
-
return JSON.stringify(obj, replacer);
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
try {
|
|
56
|
-
return String(obj);
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
return '[Unserializable]';
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
write(level, message, ...args) {
|
|
64
|
-
const timestamp = new Date().toISOString();
|
|
65
|
-
const serializedArgs = args.length > 0 ? ` ${this.safeStringify(args)}` : '';
|
|
66
|
-
const logMessage = `[${timestamp}] ${level.toUpperCase()}: ${message}${serializedArgs}`;
|
|
67
|
-
// Write to stderr (visible console output without polluting stdout)
|
|
68
|
-
process.stderr.write(logMessage + '\n');
|
|
69
|
-
// Write to log file if configured
|
|
70
|
-
if (this.logStream) {
|
|
71
|
-
this.logStream.write(logMessage + '\n');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
info(message, ...args) {
|
|
75
|
-
this.write('info', message, ...args);
|
|
76
|
-
}
|
|
77
|
-
error(message, ...args) {
|
|
78
|
-
this.write('error', message, ...args);
|
|
79
|
-
}
|
|
80
|
-
warn(message, ...args) {
|
|
81
|
-
this.write('warn', message, ...args);
|
|
82
|
-
}
|
|
83
|
-
debug(message, ...args) {
|
|
84
|
-
this.write('debug', message, ...args);
|
|
85
|
-
}
|
|
86
|
-
log(message, ...args) {
|
|
87
|
-
this.write('info', message, ...args);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
export const logger = new Logger();
|
|
91
|
-
/**
|
|
92
|
-
* Override global console to redirect to stderr
|
|
93
|
-
* This prevents console output from interfering with MCP JSON protocol
|
|
94
|
-
*/
|
|
95
|
-
export function setupConsoleRedirection() {
|
|
96
|
-
const originalConsole = { ...console };
|
|
97
|
-
// Cross-platform log file path:
|
|
98
|
-
// - If env ZAI_MCP_LOG_PATH is set, use it
|
|
99
|
-
// - Otherwise use user home directory: ~/.zai/zai-mcp-YYYY-MM-DD.log (Windows/macOS/Linux)
|
|
100
|
-
const resolveLogFilePath = () => {
|
|
101
|
-
const envPath = process.env.ZAI_MCP_LOG_PATH;
|
|
102
|
-
if (envPath && envPath.trim().length > 0) {
|
|
103
|
-
return path.resolve(envPath);
|
|
104
|
-
}
|
|
105
|
-
const homeDir = os.homedir();
|
|
106
|
-
const now = new Date();
|
|
107
|
-
const yyyy = now.getFullYear();
|
|
108
|
-
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
109
|
-
const dd = String(now.getDate()).padStart(2, '0');
|
|
110
|
-
const dateStr = `${yyyy}-${mm}-${dd}`;
|
|
111
|
-
return path.join(homeDir, '.zai', `zai-mcp-${dateStr}.log`);
|
|
112
|
-
};
|
|
113
|
-
logger.setLogFile(resolveLogFilePath());
|
|
114
|
-
console.info = logger.info.bind(logger);
|
|
115
|
-
console.error = logger.error.bind(logger);
|
|
116
|
-
console.warn = logger.warn.bind(logger);
|
|
117
|
-
console.debug = logger.debug.bind(logger);
|
|
118
|
-
console.log = logger.log.bind(logger);
|
|
119
|
-
return originalConsole;
|
|
120
|
-
}
|