nx-mcp-server 1.0.8 → 1.0.9
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/README.md +36 -0
- package/package.json +2 -2
- package/src/config.js +0 -4
- package/src/errors.js +24 -0
- package/src/index.js +4 -2
- package/src/nx-api.js +16 -11
package/README.md
CHANGED
|
@@ -173,6 +173,41 @@ MCP_TRANSPORT=http MCP_PORT=3000 NX_API_KEY=your-key node src/index.js
|
|
|
173
173
|
|
|
174
174
|
---
|
|
175
175
|
|
|
176
|
+
## 错误响应
|
|
177
|
+
|
|
178
|
+
失败时返回 `isError: true`,`content[0].text` 为 JSON 字符串:
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"error": "未配置 API Key",
|
|
183
|
+
"code": "MISSING_API_KEY",
|
|
184
|
+
"help": "请设置环境变量 NX_API_KEY 或传入 apiKey 参数…",
|
|
185
|
+
"doc": ""
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
| 字段 | 类型 | 说明 |
|
|
190
|
+
|------|------|------|
|
|
191
|
+
| `error` | `string` | 错误描述 |
|
|
192
|
+
| `code` | `string` | 错误码,用于客户端程序判断(如 `MISSING_API_KEY`、`NETWORK_ERROR`、`REQUEST_TIMEOUT`) |
|
|
193
|
+
| `help` | `string` | 建议/帮助信息,可直接展示给用户 |
|
|
194
|
+
| `doc` | `string` | 文档链接(可选) |
|
|
195
|
+
|
|
196
|
+
**常见错误码:**
|
|
197
|
+
|
|
198
|
+
| code | 说明 |
|
|
199
|
+
|------|------|
|
|
200
|
+
| `MISSING_API_KEY` | 未配置 API Key |
|
|
201
|
+
| `MISSING_PARAMS` | 缺少 urls/files 参数 |
|
|
202
|
+
| `FILE_NOT_FOUND` | 本地文件不存在 |
|
|
203
|
+
| `DOWNLOAD_FAILED` | 远程图片下载失败 |
|
|
204
|
+
| `NETWORK_ERROR` | 网络请求失败 |
|
|
205
|
+
| `REQUEST_TIMEOUT` | 压缩请求超时 |
|
|
206
|
+
| `API_AUTH_FAILED` | API Key 无效 |
|
|
207
|
+
| `API_MISSING_AUTH` | 缺少认证头 |
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
176
211
|
## 注意事项
|
|
177
212
|
|
|
178
213
|
1. **远程 MCP 只能传 HTTP(S) 链接**,先上传到 CDN 再调用
|
|
@@ -190,6 +225,7 @@ MCP_TRANSPORT=http MCP_PORT=3000 NX_API_KEY=your-key node src/index.js
|
|
|
190
225
|
src/
|
|
191
226
|
├── index.js # MCP Server 主入口(Stdio/HTTP 双模式)
|
|
192
227
|
├── config.js # 配置加载(config.json + 环境变量)
|
|
228
|
+
├── errors.js # 统一错误类(NxError:结构化 code/help/doc)
|
|
193
229
|
├── nx-api.js # 压缩 API 客户端(JSON/FormData 双模式 + 本地文件读取)
|
|
194
230
|
└── tools/
|
|
195
231
|
├── definitions.js # MCP 工具定义
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nx-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "NX MCP Server — 远程图片压缩 MCP 工具",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"bin": {
|
|
8
|
-
"nx-mcp": "
|
|
8
|
+
"nx-mcp": "src/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node src/index.js",
|
package/src/config.js
CHANGED
|
@@ -29,10 +29,6 @@ function getConfig(){
|
|
|
29
29
|
||'https://ai.nxtici.com';
|
|
30
30
|
const apiKey=process.env.NX_API_KEY||'';
|
|
31
31
|
|
|
32
|
-
if(!apiKey){
|
|
33
|
-
console.error('⚠ 未配置 API Key,请设置环境变量 NX_API_KEY。获取 Key 请联系微信 zhjian_2026');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
32
|
_config={
|
|
37
33
|
api:{baseUrl:apiBaseUrl,apiKey},
|
|
38
34
|
};
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// MCP 统一错误类 —— 附带结构化字段,客户端可解析展示
|
|
2
|
+
|
|
3
|
+
class NxError extends Error{
|
|
4
|
+
constructor(message,{code,help,doc}={}){
|
|
5
|
+
super(message);
|
|
6
|
+
this.name='NxError';
|
|
7
|
+
this.code=code||'UNKNOWN';
|
|
8
|
+
this.help=help||'';
|
|
9
|
+
this.doc=doc||'';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 序列化为 MCP content text(JSON 字符串)
|
|
13
|
+
toMcpText(){
|
|
14
|
+
const payload={
|
|
15
|
+
error:this.message,
|
|
16
|
+
code:this.code,
|
|
17
|
+
help:this.help,
|
|
18
|
+
doc:this.doc,
|
|
19
|
+
};
|
|
20
|
+
return JSON.stringify(payload);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {NxError};
|
package/src/index.js
CHANGED
|
@@ -28,8 +28,10 @@ function createServer(){
|
|
|
28
28
|
content:[{type:'text',text:JSON.stringify(result,null,2)}],
|
|
29
29
|
};
|
|
30
30
|
}catch(err){
|
|
31
|
+
// 结构化错误(NxError)→ JSON;普通 Error → 纯文本,兼容旧格式
|
|
32
|
+
const text=err.toMcpText?err.toMcpText():`错误: ${err.message}`;
|
|
31
33
|
return {
|
|
32
|
-
content:[{type:'text',text
|
|
34
|
+
content:[{type:'text',text}],
|
|
33
35
|
isError:true,
|
|
34
36
|
};
|
|
35
37
|
}
|
|
@@ -109,7 +111,7 @@ async function startHttp(){
|
|
|
109
111
|
app.delete('/mcp',mcpDeleteHandler);
|
|
110
112
|
|
|
111
113
|
// 全局错误处理 — 捕获 async handler 的异常
|
|
112
|
-
app.use((err,_req,res)=>{
|
|
114
|
+
app.use((err,_req,res,_next)=>{
|
|
113
115
|
console.error('[MCP HTTP Error]',err.message);
|
|
114
116
|
res.status(500).json({jsonrpc:'2.0',error:{code:-32603,message:err.message||'Internal error'},id:null});
|
|
115
117
|
});
|
package/src/nx-api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {getConfig} from './config.js';
|
|
2
|
+
import {NxError} from './errors.js';
|
|
2
3
|
import {readFileSync,existsSync,writeFileSync,mkdirSync} from 'fs';
|
|
3
4
|
import {basename,dirname,join} from 'path';
|
|
4
5
|
|
|
@@ -11,7 +12,7 @@ function isFilePath(str){
|
|
|
11
12
|
async function loadImageBuffer(src){
|
|
12
13
|
// 本地文件路径
|
|
13
14
|
if(isFilePath(src)){
|
|
14
|
-
if(!existsSync(src)) throw new
|
|
15
|
+
if(!existsSync(src)) throw new NxError(`文件不存在: ${src}`,{code:'FILE_NOT_FOUND'});
|
|
15
16
|
const buf=readFileSync(src);
|
|
16
17
|
const ext=basename(src).split('.').pop()||'png';
|
|
17
18
|
const mime=ext==='jpg'?'jpeg':ext;
|
|
@@ -20,7 +21,7 @@ async function loadImageBuffer(src){
|
|
|
20
21
|
// data URL
|
|
21
22
|
if(src.startsWith('data:')){
|
|
22
23
|
const [head,base64]=src.split(',');
|
|
23
|
-
if(!base64) throw new
|
|
24
|
+
if(!base64) throw new NxError('data URL 格式无效',{code:'INVALID_DATA_URL'});
|
|
24
25
|
const mime=head.match(/:(.*?);/)?.[1]||'image/png';
|
|
25
26
|
const ext=(mime.split('/')[1]||'png').replace('jpeg','jpg');
|
|
26
27
|
const buf=Buffer.from(base64,'base64');
|
|
@@ -28,7 +29,7 @@ async function loadImageBuffer(src){
|
|
|
28
29
|
}
|
|
29
30
|
// HTTP(S) URL
|
|
30
31
|
const res=await fetch(src);
|
|
31
|
-
if(!res.ok) throw new
|
|
32
|
+
if(!res.ok) throw new NxError(`下载失败 HTTP ${res.status}`,{code:'DOWNLOAD_FAILED'});
|
|
32
33
|
const buf=Buffer.from(await res.arrayBuffer());
|
|
33
34
|
const ext=src.split('.').pop().split('?')[0]||'png';
|
|
34
35
|
const contentType=res.headers.get('content-type')||'';
|
|
@@ -40,7 +41,7 @@ async function loadImageBuffer(src){
|
|
|
40
41
|
// 下载 CDN 压缩图并写入本地
|
|
41
42
|
async function downloadToFile(cdnUrl,localPath){
|
|
42
43
|
const res=await fetch(cdnUrl);
|
|
43
|
-
if(!res.ok) throw new
|
|
44
|
+
if(!res.ok) throw new NxError(`下载 CDN 失败 HTTP ${res.status}`,{code:'CDN_DOWNLOAD_FAILED'});
|
|
44
45
|
const buf=Buffer.from(await res.arrayBuffer());
|
|
45
46
|
const dir=dirname(localPath);
|
|
46
47
|
if(!existsSync(dir)) mkdirSync(dir,{recursive:true});
|
|
@@ -65,12 +66,15 @@ async function compressImage({urls=[],files=[],quality=90,output=null,apiKey=nul
|
|
|
65
66
|
// API Key:参数传入 > 环境变量 NX_API_KEY
|
|
66
67
|
const key=apiKey||api.apiKey;
|
|
67
68
|
if(!key){
|
|
68
|
-
throw new
|
|
69
|
+
throw new NxError('未配置 API Key',{
|
|
70
|
+
code:'MISSING_API_KEY',
|
|
71
|
+
help:'请设置环境变量 NX_API_KEY 或传入 apiKey 参数。获取 Key 请联系微信 zhjian_2026。',
|
|
72
|
+
});
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
// 合并所有来源,保持顺序和原始引用
|
|
72
76
|
const sources=[...urls,...files];
|
|
73
|
-
if(!sources.length) throw new
|
|
77
|
+
if(!sources.length) throw new NxError('请提供 urls 或 files 参数,至少需要一个',{code:'MISSING_PARAMS'});
|
|
74
78
|
|
|
75
79
|
// 判断请求模式:全是 HTTP URL → JSON 模式;否则 → FormData 模式
|
|
76
80
|
const allHttp=sources.every(u=>isHttpUrl(u));
|
|
@@ -100,14 +104,15 @@ async function compressImage({urls=[],files=[],quality=90,output=null,apiKey=nul
|
|
|
100
104
|
const {buffer,name,mime}=await loadImageBuffer(src);
|
|
101
105
|
return {blob:new Blob([buffer],{type:mime}),name,src};
|
|
102
106
|
}catch(e){
|
|
103
|
-
throw new
|
|
107
|
+
throw new NxError(`加载图片失败: ${e.message}`,{code:'LOAD_IMAGE_FAILED'});
|
|
104
108
|
}
|
|
105
109
|
}));
|
|
106
110
|
loaded.forEach((r)=>{
|
|
107
111
|
if(r.status==='fulfilled'){
|
|
108
112
|
fd.append('files',r.value.blob,r.value.name);
|
|
109
113
|
}else{
|
|
110
|
-
|
|
114
|
+
// 保留原始 NxError 结构,普通 Error 则包装
|
|
115
|
+
throw r.reason instanceof NxError?r.reason:new NxError(r.reason.message,{code:'LOAD_IMAGE_FAILED'});
|
|
111
116
|
}
|
|
112
117
|
});
|
|
113
118
|
fd.append('quality',String(quality));
|
|
@@ -121,9 +126,9 @@ async function compressImage({urls=[],files=[],quality=90,output=null,apiKey=nul
|
|
|
121
126
|
}catch(err){
|
|
122
127
|
clearTimeout(timer);
|
|
123
128
|
if(err.name==='AbortError'){
|
|
124
|
-
throw new
|
|
129
|
+
throw new NxError('压缩请求超时 (180s)',{code:'REQUEST_TIMEOUT',help:'图片可能过大,建议换小图重试'});
|
|
125
130
|
}
|
|
126
|
-
throw new
|
|
131
|
+
throw new NxError('网络请求失败',{code:'NETWORK_ERROR',help:err.message});
|
|
127
132
|
}
|
|
128
133
|
clearTimeout(timer);
|
|
129
134
|
|
|
@@ -138,7 +143,7 @@ async function compressImage({urls=[],files=[],quality=90,output=null,apiKey=nul
|
|
|
138
143
|
missing_params:'缺少 urls 或 files 参数',
|
|
139
144
|
};
|
|
140
145
|
const hint=errMap[errType]||data.message||'未知错误';
|
|
141
|
-
throw new
|
|
146
|
+
throw new NxError(hint,{code:`API_${errType.toUpperCase()}`});
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
const rawItems=(data.data&&data.data.items)||[];
|