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
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 硅基流动 DeepSeek-OCR API 客户端
|
|
3
|
+
* 基于 OpenAI 兼容 API
|
|
4
|
+
*/
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import { logger } from './utils/logger.js';
|
|
7
|
+
/**
|
|
8
|
+
* 硅基流动 API 客户端
|
|
9
|
+
*/
|
|
10
|
+
export class SiliconFlowClient {
|
|
11
|
+
config;
|
|
12
|
+
apiEndpoint = 'https://api.siliconflow.cn/v1/chat/completions';
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 分析图片
|
|
18
|
+
*/
|
|
19
|
+
async analyzeImage(imageDataUrl, prompt, enableThinking) {
|
|
20
|
+
const requestBody = {
|
|
21
|
+
model: this.config.model,
|
|
22
|
+
messages: [
|
|
23
|
+
{
|
|
24
|
+
role: 'user',
|
|
25
|
+
content: [
|
|
26
|
+
{
|
|
27
|
+
type: 'image_url',
|
|
28
|
+
image_url: {
|
|
29
|
+
url: imageDataUrl,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'text',
|
|
34
|
+
text: prompt,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
temperature: this.config.temperature,
|
|
40
|
+
max_tokens: this.config.maxTokens,
|
|
41
|
+
top_p: this.config.topP,
|
|
42
|
+
stream: false,
|
|
43
|
+
};
|
|
44
|
+
logger.info('Calling SiliconFlow DeepSeek-OCR API', {
|
|
45
|
+
model: this.config.model,
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
const response = await axios.post(this.apiEndpoint, requestBody, {
|
|
49
|
+
headers: {
|
|
50
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
},
|
|
53
|
+
timeout: 60000, // 60秒超时
|
|
54
|
+
});
|
|
55
|
+
if (!response.data.choices || response.data.choices.length === 0) {
|
|
56
|
+
throw new Error('No response from DeepSeek-OCR');
|
|
57
|
+
}
|
|
58
|
+
const result = response.data.choices[0].message.content;
|
|
59
|
+
const usage = response.data.usage;
|
|
60
|
+
logger.info('SiliconFlow API call successful', {
|
|
61
|
+
tokens: usage?.total_tokens || 0,
|
|
62
|
+
model: response.data.model
|
|
63
|
+
});
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
logger.error('SiliconFlow API call failed', {
|
|
68
|
+
error: error instanceof Error ? error.message : String(error)
|
|
69
|
+
});
|
|
70
|
+
if (axios.isAxiosError(error)) {
|
|
71
|
+
const message = error.response?.data?.error?.message || error.message;
|
|
72
|
+
const status = error.response?.status;
|
|
73
|
+
throw new Error(`SiliconFlow API error (${status || 'unknown'}): ${message}`);
|
|
74
|
+
}
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 获取模型名称
|
|
80
|
+
*/
|
|
81
|
+
getModelName() {
|
|
82
|
+
return this.config.model;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=siliconflow-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"siliconflow-client.js","sourceRoot":"","sources":["../src/siliconflow-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 视觉模型客户端统一接口
|
|
3
|
+
*/
|
|
4
|
+
export interface VisionClient {
|
|
5
|
+
/**
|
|
6
|
+
* 分析图片
|
|
7
|
+
* @param imageDataUrl 图片 Data URL 或 URL
|
|
8
|
+
* @param prompt 分析提示词
|
|
9
|
+
* @param enableThinking 是否启用思考模式(如果模型支持)
|
|
10
|
+
* @returns 分析结果文本
|
|
11
|
+
*/
|
|
12
|
+
analyzeImage(imageDataUrl: string, prompt: string, enableThinking?: boolean): Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* 获取模型名称
|
|
15
|
+
*/
|
|
16
|
+
getModelName(): string;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=vision-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision-client.d.ts","sourceRoot":"","sources":["../src/vision-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,YAAY;IAC3B;;;;;;OAMG;IACH,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE9F;;OAEG;IACH,YAAY,IAAI,MAAM,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vision-client.js","sourceRoot":"","sources":["../src/vision-client.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/build/zhipu-client.d.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* 智谱 GLM-4.5V API 客户端
|
|
3
3
|
*/
|
|
4
4
|
import type { LumaConfig } from './config.js';
|
|
5
|
+
import type { VisionClient } from './vision-client.js';
|
|
5
6
|
/**
|
|
6
7
|
* 智谱 API 客户端
|
|
7
8
|
*/
|
|
8
|
-
export declare class ZhipuClient {
|
|
9
|
+
export declare class ZhipuClient implements VisionClient {
|
|
9
10
|
private config;
|
|
10
11
|
private apiEndpoint;
|
|
11
12
|
constructor(config: LumaConfig);
|
|
@@ -13,5 +14,9 @@ export declare class ZhipuClient {
|
|
|
13
14
|
* 分析图片
|
|
14
15
|
*/
|
|
15
16
|
analyzeImage(imageDataUrl: string, prompt: string, enableThinking?: boolean): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* 获取模型名称
|
|
19
|
+
*/
|
|
20
|
+
getModelName(): string;
|
|
16
21
|
}
|
|
17
22
|
//# sourceMappingURL=zhipu-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zhipu-client.d.ts","sourceRoot":"","sources":["../src/zhipu-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"zhipu-client.d.ts","sourceRoot":"","sources":["../src/zhipu-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AA4CvD;;GAEG;AACH,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,WAAW,CAA2D;gBAElE,MAAM,EAAE,UAAU;IAI9B;;OAEG;IACG,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IA4EnG;;OAEG;IACH,YAAY,IAAI,MAAM;CAGvB"}
|
package/build/zhipu-client.js
CHANGED
|
@@ -38,10 +38,11 @@ export class ZhipuClient {
|
|
|
38
38
|
temperature: this.config.temperature,
|
|
39
39
|
max_tokens: this.config.maxTokens,
|
|
40
40
|
top_p: this.config.topP,
|
|
41
|
+
thinking: { type: 'enabled' }, // 默认启用思考模式,提高分析准确性
|
|
41
42
|
};
|
|
42
|
-
//
|
|
43
|
-
if (this.config.enableThinking || enableThinking ===
|
|
44
|
-
requestBody.thinking
|
|
43
|
+
// 允许显式禁用 thinking(如需要更快速度)
|
|
44
|
+
if (this.config.enableThinking === false || enableThinking === false) {
|
|
45
|
+
delete requestBody.thinking;
|
|
45
46
|
}
|
|
46
47
|
logger.info('Calling GLM-4.5V API', {
|
|
47
48
|
model: this.config.model,
|
|
@@ -78,5 +79,11 @@ export class ZhipuClient {
|
|
|
78
79
|
throw error;
|
|
79
80
|
}
|
|
80
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* 获取模型名称
|
|
84
|
+
*/
|
|
85
|
+
getModelName() {
|
|
86
|
+
return this.config.model;
|
|
87
|
+
}
|
|
81
88
|
}
|
|
82
89
|
//# sourceMappingURL=zhipu-client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zhipu-client.js","sourceRoot":"","sources":["../src/zhipu-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"zhipu-client.js","sourceRoot":"","sources":["../src/zhipu-client.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "luma-mcp",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Multi-model vision understanding MCP server. Supports GLM-4.5V (Zhipu) and DeepSeek-OCR (SiliconFlow - Free)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"luma-mcp": "build/index.js"
|
|
@@ -19,7 +19,12 @@
|
|
|
19
19
|
"ai",
|
|
20
20
|
"glm-4.5v",
|
|
21
21
|
"zhipu",
|
|
22
|
-
"
|
|
22
|
+
"deepseek-ocr",
|
|
23
|
+
"siliconflow",
|
|
24
|
+
"ocr",
|
|
25
|
+
"free",
|
|
26
|
+
"image-understanding",
|
|
27
|
+
"multi-model"
|
|
23
28
|
],
|
|
24
29
|
"author": "Jochen",
|
|
25
30
|
"license": "MIT",
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 直接测试 DeepSeek-OCR API(无任何包装)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
|
|
9
|
+
async function testDeepSeekOCR(imagePath: string) {
|
|
10
|
+
console.log('\n🧪 测试 DeepSeek-OCR API(原始调用)\n');
|
|
11
|
+
|
|
12
|
+
const apiKey = 'sk-skrldwndjawxvzzomztwmoinnwmvumezqyejysqutjwkjcdt';
|
|
13
|
+
|
|
14
|
+
// 读取图片并转为 base64
|
|
15
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
16
|
+
const base64Image = imageBuffer.toString('base64');
|
|
17
|
+
const mimeType = imagePath.endsWith('.png') ? 'image/png' : 'image/jpeg';
|
|
18
|
+
const imageDataUrl = `data:${mimeType};base64,${base64Image}`;
|
|
19
|
+
|
|
20
|
+
console.log(`📸 图片: ${imagePath}`);
|
|
21
|
+
console.log(`📦 大小: ${(imageBuffer.length / 1024).toFixed(2)} KB\n`);
|
|
22
|
+
|
|
23
|
+
// 测试不同的 prompt
|
|
24
|
+
const prompts = [
|
|
25
|
+
'识别图片中的所有文字',
|
|
26
|
+
'OCR',
|
|
27
|
+
'Extract all text from this image',
|
|
28
|
+
'What do you see in this image?',
|
|
29
|
+
'请详细描述这张图片'
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
for (const prompt of prompts) {
|
|
33
|
+
console.log(`\n🔍 测试 Prompt: "${prompt}"`);
|
|
34
|
+
console.log('─'.repeat(50));
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const response = await axios.post(
|
|
38
|
+
'https://api.siliconflow.cn/v1/chat/completions',
|
|
39
|
+
{
|
|
40
|
+
model: 'deepseek-ai/DeepSeek-OCR',
|
|
41
|
+
messages: [
|
|
42
|
+
{
|
|
43
|
+
role: 'user',
|
|
44
|
+
content: [
|
|
45
|
+
{
|
|
46
|
+
type: 'image_url',
|
|
47
|
+
image_url: {
|
|
48
|
+
url: imageDataUrl,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'text',
|
|
53
|
+
text: prompt,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
temperature: 0.7,
|
|
59
|
+
max_tokens: 4096,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
headers: {
|
|
63
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
},
|
|
66
|
+
timeout: 60000,
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const result = response.data.choices[0].message.content;
|
|
71
|
+
const usage = response.data.usage;
|
|
72
|
+
|
|
73
|
+
console.log(`✅ Tokens: ${usage.total_tokens} (prompt: ${usage.prompt_tokens}, completion: ${usage.completion_tokens})`);
|
|
74
|
+
console.log(`📝 响应长度: ${result?.length || 0} 字符`);
|
|
75
|
+
|
|
76
|
+
if (result && result.trim().length > 0) {
|
|
77
|
+
console.log('\n📊 结果:');
|
|
78
|
+
console.log('─'.repeat(50));
|
|
79
|
+
console.log(result);
|
|
80
|
+
console.log('─'.repeat(50));
|
|
81
|
+
console.log('\n✅ 找到有效响应!');
|
|
82
|
+
break;
|
|
83
|
+
} else {
|
|
84
|
+
console.log('❌ 空响应');
|
|
85
|
+
}
|
|
86
|
+
} catch (error: any) {
|
|
87
|
+
console.log(`❌ 错误: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 运行测试
|
|
93
|
+
const imagePath = path.join(process.cwd(), 'test.png');
|
|
94
|
+
testDeepSeekOCR(imagePath).catch(console.error);
|
package/test/test-local.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { loadConfig } from '../src/config.js';
|
|
7
|
+
import type { VisionClient } from '../src/vision-client.js';
|
|
7
8
|
import { ZhipuClient } from '../src/zhipu-client.js';
|
|
9
|
+
import { SiliconFlowClient } from '../src/siliconflow-client.js';
|
|
8
10
|
import { imageToBase64, validateImageSource } from '../src/image-processor.js';
|
|
9
11
|
import { buildAnalysisPrompt } from '../src/prompts.js';
|
|
10
12
|
import { logger } from '../src/utils/logger.js';
|
|
@@ -18,7 +20,7 @@ async function testImageAnalysis(imagePath: string, question?: string) {
|
|
|
18
20
|
// 1. 加载配置
|
|
19
21
|
console.log('📝 加载配置...');
|
|
20
22
|
const config = loadConfig();
|
|
21
|
-
console.log(`✅ 配置加载成功: 模型 ${config.model}\n`);
|
|
23
|
+
console.log(`✅ 配置加载成功: 提供商 ${config.provider}, 模型 ${config.model}\n`);
|
|
22
24
|
|
|
23
25
|
// 2. 验证图片
|
|
24
26
|
console.log('🔍 验证图片来源...');
|
|
@@ -33,12 +35,19 @@ async function testImageAnalysis(imagePath: string, question?: string) {
|
|
|
33
35
|
|
|
34
36
|
// 4. 构建提示词
|
|
35
37
|
console.log('💬 构建提示词...');
|
|
36
|
-
|
|
38
|
+
// DeepSeek-OCR 需要简洁 prompt
|
|
39
|
+
const prompt = config.provider === 'siliconflow'
|
|
40
|
+
? (question || '请详细分析这张图片的内容')
|
|
41
|
+
: buildAnalysisPrompt(question);
|
|
37
42
|
console.log(`✅ 提示词: ${question || '通用描述'}\n`);
|
|
38
43
|
|
|
39
|
-
// 5.
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
// 5. 创建客户端并调用API
|
|
45
|
+
const client: VisionClient = config.provider === 'siliconflow'
|
|
46
|
+
? new SiliconFlowClient(config)
|
|
47
|
+
: new ZhipuClient(config);
|
|
48
|
+
|
|
49
|
+
const modelName = config.provider === 'siliconflow' ? 'DeepSeek-OCR' : 'GLM-4.5V';
|
|
50
|
+
console.log(`🤖 调用 ${modelName} API...`);
|
|
42
51
|
const result = await client.analyzeImage(imageDataUrl, prompt);
|
|
43
52
|
|
|
44
53
|
// 6. 显示结果
|
|
@@ -76,8 +85,12 @@ if (args.length === 0) {
|
|
|
76
85
|
npm run test:local https://example.com/image.jpg
|
|
77
86
|
|
|
78
87
|
环境变量:
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
# 使用智谱 GLM-4.5V
|
|
89
|
+
ZHIPU_API_KEY=your-api-key
|
|
90
|
+
|
|
91
|
+
# 使用硅基流动 DeepSeek-OCR
|
|
92
|
+
MODEL_PROVIDER=siliconflow
|
|
93
|
+
SILICONFLOW_API_KEY=your-api-key
|
|
81
94
|
`);
|
|
82
95
|
process.exit(1);
|
|
83
96
|
}
|
package/mcp-server/README.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# ZAI MCP Server
|
|
2
|
-
|
|
3
|
-
[中文文档](https://docs.bigmodel.cn/cn/coding-plan/mcp/vision-mcp-server) | [English Document](https://docs.z.ai/devpack/mcp/vision-mcp-server)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
A Model Context Protocol (MCP) server that provides AI capabilities powered by Z.AI.
|
|
7
|
-
|
|
8
|
-
## Environment Variables
|
|
9
|
-
|
|
10
|
-
- `Z_AI_MODE` - The platform to use for AI services (default: `ZHIPU`) ZHIPU or ZAI
|
|
11
|
-
- `Z_AI_API_KEY` - Your API key
|
|
12
|
-
|
|
13
|
-
For ZAI: Use ZAI platform https://z.ai/model-api
|
|
14
|
-
|
|
15
|
-
For ZHIPU: Use Zhipu AI platform https://bigmodel.cn
|
|
16
|
-
|
|
17
|
-
## Usage
|
|
18
|
-
|
|
19
|
-
### Use this MCP Server in Claude Code
|
|
20
|
-
|
|
21
|
-
It is recommended to use the version Node.js 18+, Claude Code 2.0.14+.
|
|
22
|
-
|
|
23
|
-
**Note**: You need to configure `Z_AI_MODE` as `ZAI` or `ZHIPU` depending on the platform.
|
|
24
|
-
|
|
25
|
-
For ZAI Use,
|
|
26
|
-
|
|
27
|
-
```shell
|
|
28
|
-
claude mcp add zai-mcp-server --env Z_AI_API_KEY=your_api_key Z_AI_MODE=ZAI -- npx -y "@z_ai/mcp-server"
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
For ZHIPU Use,
|
|
32
|
-
|
|
33
|
-
```shell
|
|
34
|
-
claude mcp add zai-mcp-server --env Z_AI_API_KEY=your_api_key Z_AI_MODE=ZHIPU -- npx -y "@z_ai/mcp-server"
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Use this MCP Server in Others MCP Client
|
|
38
|
-
|
|
39
|
-
For Z.ai Use, refer the [Vision MCP Doc](https://docs.z.ai/devpack/mcp/vision-mcp-server)
|
|
40
|
-
|
|
41
|
-
For ZhiPU Use, refer the [Vision MCP Doc](https://docs.bigmodel.cn/cn/coding-plan/mcp/vision-mcp-server)
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# ZAI MCP 服务器
|
|
2
|
-
|
|
3
|
-
[中文文档](https://docs.bigmodel.cn/cn/coding-plan/mcp/vision-mcp-server) | [English Document](https://docs.z.ai/devpack/mcp/vision-mcp-server)
|
|
4
|
-
|
|
5
|
-
一个基于 Z.AI 提供 AI 能力的模型上下文协议 (MCP) 服务器。
|
|
6
|
-
|
|
7
|
-
## MCP 工具
|
|
8
|
-
|
|
9
|
-
该服务器实现了模型上下文协议,可与任何兼容 MCP 的客户端一起使用。服务器提供以下工具:
|
|
10
|
-
|
|
11
|
-
- `image_analysis` - 分析图像并提供详细描述
|
|
12
|
-
- `video_analysis` - 分析视频并提供详细描述
|
|
13
|
-
|
|
14
|
-
## 环境变量
|
|
15
|
-
|
|
16
|
-
- `Z_AI_MODE` - 用于 AI 服务的平台(默认:`ZHIPU`)ZHIPU 或 ZAI
|
|
17
|
-
- `Z_AI_API_KEY` - 您的 API 密钥
|
|
18
|
-
|
|
19
|
-
对于 `Z_AI_MODE=ZHIPU`:使用智谱 AI 平台 https://bigmodel.cn
|
|
20
|
-
|
|
21
|
-
对于 `Z_AI_MODE=ZAI`:使用 ZAI 平台 https://z.ai/model-api
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
## 安装
|
|
25
|
-
|
|
26
|
-
### 从 npm 安装
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm i @z_ai/mcp-server
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## 使用方法
|
|
33
|
-
|
|
34
|
-
### 在 Claude Code 中使用此 MCP 服务器
|
|
35
|
-
|
|
36
|
-
```shell
|
|
37
|
-
claude mcp add zai-mcp-server --env Z_AI_API_KEY=your_api_key -- npx -y "@z_ai/mcp-server"
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### 在其他客户端中使用此 MCP 服务器
|
|
41
|
-
|
|
42
|
-
参考文档 [视觉理解 MCP](https://docs.bigmodel.cn/cn/coding-plan/mcp/vision-mcp-server)
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Create multimodal message content
|
|
3
|
-
* @param content Content array including images, text, etc.
|
|
4
|
-
* @param prompt Text prompt
|
|
5
|
-
* @returns Formatted message array
|
|
6
|
-
*/
|
|
7
|
-
export function createMultiModalMessage(content, prompt) {
|
|
8
|
-
return [{
|
|
9
|
-
role: 'user',
|
|
10
|
-
content: [...content, { type: 'text', text: prompt }]
|
|
11
|
-
}];
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Create text message
|
|
15
|
-
* @param prompt Text content
|
|
16
|
-
* @returns Formatted message array
|
|
17
|
-
*/
|
|
18
|
-
export function createTextMessage(prompt) {
|
|
19
|
-
return [{
|
|
20
|
-
role: 'user',
|
|
21
|
-
content: [{ type: 'text', text: prompt }]
|
|
22
|
-
}];
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Create image message content
|
|
26
|
-
* @param imageUrl Image URL or base64 data
|
|
27
|
-
* @returns Image content object
|
|
28
|
-
*/
|
|
29
|
-
export function createImageContent(imageUrl) {
|
|
30
|
-
return {
|
|
31
|
-
type: 'image_url',
|
|
32
|
-
image_url: { url: imageUrl }
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Create video message content
|
|
37
|
-
* @param videoUrl Video URL or base64 data
|
|
38
|
-
* @returns Video content object
|
|
39
|
-
*/
|
|
40
|
-
export function createVideoContent(videoUrl) {
|
|
41
|
-
return {
|
|
42
|
-
type: 'video_url', // Most AI models treat video as image_url type
|
|
43
|
-
video_url: { url: videoUrl }
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Create error response
|
|
48
|
-
* @param message Error message
|
|
49
|
-
* @param error Optional error object
|
|
50
|
-
* @returns Standardized error response
|
|
51
|
-
*/
|
|
52
|
-
export function createErrorResponse(message, error) {
|
|
53
|
-
return {
|
|
54
|
-
success: false,
|
|
55
|
-
error: message,
|
|
56
|
-
timestamp: Date.now(),
|
|
57
|
-
...(error && { context: { stack: error.stack, name: error.name } })
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Create success response
|
|
62
|
-
* @param data Response data
|
|
63
|
-
* @returns Standardized success response
|
|
64
|
-
*/
|
|
65
|
-
export function createSuccessResponse(data) {
|
|
66
|
-
return {
|
|
67
|
-
success: true,
|
|
68
|
-
data,
|
|
69
|
-
timestamp: Date.now()
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Format response content to MCP format
|
|
74
|
-
* @param response API response
|
|
75
|
-
* @returns MCP tool response format
|
|
76
|
-
*/
|
|
77
|
-
export function formatMcpResponse(response) {
|
|
78
|
-
if (response.success) {
|
|
79
|
-
const text = typeof response.data === 'string'
|
|
80
|
-
? response.data
|
|
81
|
-
: JSON.stringify(response.data, null, 2);
|
|
82
|
-
return {
|
|
83
|
-
content: [{ type: 'text', text }]
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
return {
|
|
88
|
-
content: [{
|
|
89
|
-
type: 'text',
|
|
90
|
-
text: `Error: ${response.error}`
|
|
91
|
-
}],
|
|
92
|
-
isError: true
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Create async function with retry mechanism
|
|
98
|
-
* @param fn Async function to execute
|
|
99
|
-
* @param maxRetries Maximum retry attempts
|
|
100
|
-
* @param delay Retry delay in milliseconds
|
|
101
|
-
* @returns Wrapped function
|
|
102
|
-
*/
|
|
103
|
-
export function withRetry(fn, maxRetries = 3, delay = 1000) {
|
|
104
|
-
return async (...args) => {
|
|
105
|
-
let lastError;
|
|
106
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
107
|
-
try {
|
|
108
|
-
return await fn(...args);
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
112
|
-
if (attempt === maxRetries) {
|
|
113
|
-
throw lastError;
|
|
114
|
-
}
|
|
115
|
-
// Exponential backoff
|
|
116
|
-
const waitTime = delay * Math.pow(2, attempt);
|
|
117
|
-
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
throw lastError;
|
|
121
|
-
};
|
|
122
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { ApiError } from '../types/index.js';
|
|
2
|
-
import { configurationService } from './environment.js';
|
|
3
|
-
import { EnvironmentService } from './environment.js';
|
|
4
|
-
/**
|
|
5
|
-
* ZAI API service implementation
|
|
6
|
-
*/
|
|
7
|
-
export class ChatService {
|
|
8
|
-
environmentService;
|
|
9
|
-
constructor(environmentService = EnvironmentService.getInstance()) {
|
|
10
|
-
this.environmentService = environmentService;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* ZAI chat completions API for vision analysis
|
|
14
|
-
*/
|
|
15
|
-
async visionCompletions(messages) {
|
|
16
|
-
const visionConfig = configurationService.getVisionConfig();
|
|
17
|
-
const requestBody = {
|
|
18
|
-
model: visionConfig.model,
|
|
19
|
-
messages,
|
|
20
|
-
thinking: { type: 'enabled' },
|
|
21
|
-
stream: false,
|
|
22
|
-
temperature: visionConfig.temperature,
|
|
23
|
-
top_p: visionConfig.topP,
|
|
24
|
-
max_tokens: visionConfig.maxTokens
|
|
25
|
-
};
|
|
26
|
-
console.info('Request ZAI chat completions API for vision analysis', { model: visionConfig.model, messageCount: messages.length });
|
|
27
|
-
try {
|
|
28
|
-
const response = await this.chatCompletions(visionConfig.url, requestBody);
|
|
29
|
-
const result = response.choices?.[0]?.message?.content;
|
|
30
|
-
if (!result) {
|
|
31
|
-
throw new ApiError('Invalid API response: missing content');
|
|
32
|
-
}
|
|
33
|
-
console.info('Request chat completions API for vision analysis successful');
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
console.error('Request chat completions API for vision analysis failed', { error: error instanceof Error ? error.message : String(error) });
|
|
38
|
-
throw error instanceof ApiError ? error : new ApiError(`API call failed: ${error}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Make HTTP request to ZAI API with proper headers and error handling
|
|
43
|
-
*/
|
|
44
|
-
async chatCompletions(url, body) {
|
|
45
|
-
const apiConfig = configurationService.getVisionConfig();
|
|
46
|
-
const apiKey = this.environmentService.getApiKey();
|
|
47
|
-
const controller = new AbortController();
|
|
48
|
-
const timeoutId = setTimeout(() => controller.abort(), apiConfig.timeout);
|
|
49
|
-
try {
|
|
50
|
-
const response = await fetch(url, {
|
|
51
|
-
method: 'POST',
|
|
52
|
-
headers: {
|
|
53
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
54
|
-
'Content-Type': 'application/json',
|
|
55
|
-
'X-Title': '4.5V MCP Local',
|
|
56
|
-
'Accept-Language': 'en-US,en'
|
|
57
|
-
},
|
|
58
|
-
body: JSON.stringify(body),
|
|
59
|
-
signal: controller.signal
|
|
60
|
-
});
|
|
61
|
-
clearTimeout(timeoutId);
|
|
62
|
-
if (!response.ok) {
|
|
63
|
-
const errorText = await response.text();
|
|
64
|
-
throw new ApiError(`HTTP ${response.status}: ${errorText}`);
|
|
65
|
-
}
|
|
66
|
-
return await response.json();
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
clearTimeout(timeoutId);
|
|
70
|
-
if (error instanceof ApiError) {
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
throw new ApiError(`Network error: ${error}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* ZAI API chat completions service instance
|
|
79
|
-
*/
|
|
80
|
-
export const chatService = new ChatService();
|