@xiaozhi-client/config 1.9.7-beta.17 → 1.9.7-beta.19

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.
@@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import type { Mock } from "vitest";
3
3
  import {
4
4
  ConfigValidationError,
5
- convertLegacyToNew,
5
+ normalizeServiceConfig,
6
6
  isModelScopeURL,
7
7
  MCPTransportType,
8
8
  } from "../adapter.js";
@@ -34,13 +34,13 @@ describe("ConfigAdapter 配置适配器测试", () => {
34
34
  describe("SSE 类型推断", () => {
35
35
  it("应该根据 /sse 路径推断为 SSE 类型", () => {
36
36
  const config = { url: "https://example.com/sse" };
37
- const result = convertLegacyToNew("test-service", config);
37
+ const result = normalizeServiceConfig(config);
38
38
  expect(result.type).toBe(MCPTransportType.SSE);
39
39
  });
40
40
 
41
41
  it("应该正确处理带有查询参数的 SSE URL", () => {
42
42
  const config = { url: "https://mcp.amap.com/sse?key=test&token=abc" };
43
- const result = convertLegacyToNew("amap-service", config);
43
+ const result = normalizeServiceConfig(config);
44
44
  expect(result.type).toBe(MCPTransportType.SSE);
45
45
  });
46
46
 
@@ -52,14 +52,14 @@ describe("ConfigAdapter 配置适配器测试", () => {
52
52
  ];
53
53
 
54
54
  for (const url of testCases) {
55
- const result = convertLegacyToNew("test-service", { url });
55
+ const result = normalizeServiceConfig({ url });
56
56
  expect(result.type).toBe(MCPTransportType.SSE);
57
57
  }
58
58
  });
59
59
 
60
60
  it("应该正确处理带端口的 SSE URL", () => {
61
61
  const config = { url: "https://localhost:3000/sse" };
62
- const result = convertLegacyToNew("local-service", config);
62
+ const result = normalizeServiceConfig(config);
63
63
  expect(result.type).toBe(MCPTransportType.SSE);
64
64
  });
65
65
  });
@@ -69,7 +69,7 @@ describe("ConfigAdapter 配置适配器测试", () => {
69
69
  const config = {
70
70
  url: "https://mcp.api-inference.modelscope.net/8928ccc99fa34b/mcp",
71
71
  };
72
- const result = convertLegacyToNew("modelscope-service", config);
72
+ const result = normalizeServiceConfig(config);
73
73
  expect(result.type).toBe(MCPTransportType.HTTP);
74
74
  });
75
75
 
@@ -81,7 +81,7 @@ describe("ConfigAdapter 配置适配器测试", () => {
81
81
  ];
82
82
 
83
83
  for (const url of testCases) {
84
- const result = convertLegacyToNew("test-service", { url });
84
+ const result = normalizeServiceConfig({ url });
85
85
  expect(result.type).toBe(MCPTransportType.HTTP);
86
86
  }
87
87
  });
@@ -90,7 +90,7 @@ describe("ConfigAdapter 配置适配器测试", () => {
90
90
  const config = {
91
91
  url: "https://mcp.api-inference.modelscope.net/8928ccc99fa34b/mcp",
92
92
  };
93
- const result = convertLegacyToNew("modelscope-service", config);
93
+ const result = normalizeServiceConfig(config);
94
94
  expect(result.type).toBe(MCPTransportType.HTTP);
95
95
  });
96
96
  });
@@ -98,7 +98,7 @@ describe("ConfigAdapter 配置适配器测试", () => {
98
98
  describe("无效 URL 处理", () => {
99
99
  it("应该为无效 URL 默认推断为 HTTP", () => {
100
100
  const config = { url: "not-a-valid-url" };
101
- const result = convertLegacyToNew("test-service", config);
101
+ const result = normalizeServiceConfig(config);
102
102
  expect(result.type).toBe(MCPTransportType.HTTP);
103
103
  });
104
104
  });
@@ -107,7 +107,7 @@ describe("ConfigAdapter 配置适配器测试", () => {
107
107
  describe("显式类型指定", () => {
108
108
  it("应该优先使用显式指定的 sse 类型", () => {
109
109
  const config = { type: "sse" as const, url: "https://example.com/custom" };
110
- const result = convertLegacyToNew("test-service", config);
110
+ const result = normalizeServiceConfig(config);
111
111
  expect(result.type).toBe(MCPTransportType.SSE);
112
112
  });
113
113
 
@@ -116,7 +116,7 @@ describe("ConfigAdapter 配置适配器测试", () => {
116
116
  type: "http" as const,
117
117
  url: "https://example.com/sse",
118
118
  };
119
- const result = convertLegacyToNew("test-service", config);
119
+ const result = normalizeServiceConfig(config);
120
120
  expect(result.type).toBe(MCPTransportType.HTTP);
121
121
  });
122
122
  });
@@ -136,14 +136,6 @@ describe("ConfigAdapter 配置适配器测试", () => {
136
136
  expect(isModelScopeURL("https://example.com/sse")).toBe(false);
137
137
  expect(isModelScopeURL("https://mcp.amap.com/sse")).toBe(false);
138
138
  });
139
-
140
- it("应该为 ModelScope SSE 服务添加认证标识", () => {
141
- const config = {
142
- url: "https://mcp.api-inference.modelscope.net/f0fed2f733514b/sse",
143
- };
144
- const result = convertLegacyToNew("modelscope-service", config);
145
- expect(result.modelScopeAuth).toBe(true);
146
- });
147
139
  });
148
140
 
149
141
  describe("本地 stdio 配置", () => {
@@ -152,7 +144,7 @@ describe("ConfigAdapter 配置适配器测试", () => {
152
144
  command: "node",
153
145
  args: ["./server.js"],
154
146
  };
155
- const result = convertLegacyToNew("local-service", config);
147
+ const result = normalizeServiceConfig(config);
156
148
  expect(result.type).toBe(MCPTransportType.STDIO);
157
149
  expect(result.command).toBe("node");
158
150
  // 相对路径会被解析为绝对路径
@@ -165,7 +157,7 @@ describe("ConfigAdapter 配置适配器测试", () => {
165
157
  command: "python",
166
158
  args: ["./script.py", "./config.json"],
167
159
  };
168
- const result = convertLegacyToNew("local-service", config);
160
+ const result = normalizeServiceConfig(config);
169
161
  // 验证 args 已被解析为绝对路径
170
162
  expect(result.args).toBeDefined();
171
163
  expect(result.args![0]).toMatch(/\/script\.py$/);
@@ -174,21 +166,15 @@ describe("ConfigAdapter 配置适配器测试", () => {
174
166
  });
175
167
 
176
168
  describe("错误处理", () => {
177
- it("应该为缺少服务名称的配置抛出错误", () => {
178
- expect(() => convertLegacyToNew("", { url: "https://example.com/sse" })).toThrow(
179
- ConfigValidationError
180
- );
181
- });
182
-
183
169
  it("应该为空配置对象抛出错误", () => {
184
170
  expect(() =>
185
- convertLegacyToNew("test", null as unknown as any)
171
+ normalizeServiceConfig(null as unknown as any)
186
172
  ).toThrow(ConfigValidationError);
187
173
  });
188
174
 
189
175
  it("应该为无效的传输类型抛出错误", () => {
190
176
  const config = { type: "invalid-type" as any, url: "https://example.com" };
191
- expect(() => convertLegacyToNew("test-service", config)).toThrow(
177
+ expect(() => normalizeServiceConfig(config)).toThrow(
192
178
  ConfigValidationError
193
179
  );
194
180
  });
@@ -225,7 +211,7 @@ describe("ConfigAdapter 配置适配器测试", () => {
225
211
 
226
212
  for (const { url, expected, description } of testCases) {
227
213
  it(`应该正确推断: ${description}`, () => {
228
- const result = convertLegacyToNew("test-service", { url });
214
+ const result = normalizeServiceConfig({ url });
229
215
  expect(result.type).toBe(expected);
230
216
  });
231
217
  }
package/src/adapter.ts CHANGED
@@ -7,8 +7,6 @@ import { isAbsolute, resolve, dirname } from "node:path";
7
7
  import type {
8
8
  LocalMCPServerConfig,
9
9
  MCPServerConfig,
10
- SSEMCPServerConfig,
11
- StreamableHTTPMCPServerConfig,
12
10
  } from "./manager.js";
13
11
  import { ConfigResolver } from "./resolver.js";
14
12
 
@@ -20,10 +18,7 @@ import { ConfigResolver } from "./resolver.js";
20
18
  * 配置验证错误类
21
19
  */
22
20
  export class ConfigValidationError extends Error {
23
- constructor(
24
- message: string,
25
- public readonly configName?: string
26
- ) {
21
+ constructor(message: string) {
27
22
  super(message);
28
23
  this.name = "ConfigValidationError";
29
24
  }
@@ -38,14 +33,12 @@ export enum MCPTransportType {
38
33
 
39
34
  // 定义简化的 MCPServiceConfig 接口
40
35
  export interface MCPServiceConfig {
41
- name: string;
42
36
  type: MCPTransportType;
43
37
  command?: string;
44
38
  args?: string[];
45
39
  env?: Record<string, string>;
46
40
  url?: string;
47
41
  headers?: Record<string, string>;
48
- modelScopeAuth?: boolean;
49
42
  }
50
43
 
51
44
  /**
@@ -74,39 +67,33 @@ function inferTransportTypeFromUrl(url: string): MCPTransportType {
74
67
  }
75
68
 
76
69
  /**
77
- * 将旧的 MCPServerConfig 转换为新的 MCPServiceConfig
70
+ * 将各种配置格式标准化为统一的服务配置格式
78
71
  */
79
- export function convertLegacyToNew(
80
- serviceName: string,
81
- legacyConfig: MCPServerConfig
72
+ export function normalizeServiceConfig(
73
+ config: MCPServerConfig
82
74
  ): MCPServiceConfig {
83
- console.log("转换配置", { serviceName, legacyConfig });
75
+ console.log("转换配置", { config });
84
76
 
85
77
  try {
86
78
  // 验证输入参数
87
- if (!serviceName || typeof serviceName !== "string") {
88
- throw new ConfigValidationError("服务名称必须是非空字符串");
89
- }
90
-
91
- if (!legacyConfig || typeof legacyConfig !== "object") {
92
- throw new ConfigValidationError("配置对象不能为空", serviceName);
79
+ if (!config || typeof config !== "object") {
80
+ throw new ConfigValidationError("配置对象不能为空");
93
81
  }
94
82
 
95
83
  // 根据配置类型进行转换
96
- const newConfig = convertByConfigType(serviceName, legacyConfig);
84
+ const newConfig = convertByConfigType(config);
97
85
 
98
86
  // 验证转换后的配置
99
87
  validateNewConfig(newConfig);
100
88
 
101
- console.log("配置转换成功", { serviceName, type: newConfig.type });
89
+ console.log("配置转换成功", { type: newConfig.type });
102
90
  return newConfig;
103
91
  } catch (error) {
104
- console.error("配置转换失败", { serviceName, error });
92
+ console.error("配置转换失败", { error });
105
93
  throw error instanceof ConfigValidationError
106
94
  ? error
107
95
  : new ConfigValidationError(
108
- `配置转换失败: ${error instanceof Error ? error.message : String(error)}`,
109
- serviceName
96
+ `配置转换失败: ${error instanceof Error ? error.message : String(error)}`
110
97
  );
111
98
  }
112
99
  }
@@ -115,66 +102,66 @@ export function convertLegacyToNew(
115
102
  * 根据配置类型进行转换
116
103
  */
117
104
  function convertByConfigType(
118
- serviceName: string,
119
- legacyConfig: MCPServerConfig
105
+ config: MCPServerConfig
120
106
  ): MCPServiceConfig {
121
107
  // 检查是否为本地 stdio 配置(最高优先级)
122
- if (isLocalConfig(legacyConfig)) {
123
- return convertLocalConfig(serviceName, legacyConfig);
108
+ if (isLocalConfig(config)) {
109
+ return convertLocalConfig(config);
124
110
  }
125
111
 
126
112
  // 检查是否有显式指定的类型
127
- if ("type" in legacyConfig) {
128
- switch (legacyConfig.type) {
113
+ if ("type" in config) {
114
+ switch (config.type) {
129
115
  case "sse":
130
- return convertSSEConfig(serviceName, legacyConfig);
116
+ return convertSSEConfig(config);
131
117
  case "http":
132
118
  case "streamable-http": // 向后兼容
133
- return convertHTTPConfig(serviceName, legacyConfig);
119
+ return convertHTTPConfig(config);
134
120
  default:
135
121
  throw new ConfigValidationError(
136
- `不支持的传输类型: ${legacyConfig.type}`,
137
- serviceName
122
+ `不支持的传输类型: ${config.type}`
138
123
  );
139
124
  }
140
125
  }
141
126
 
142
127
  // 检查是否为网络配置(自动推断类型)
143
- if ("url" in legacyConfig) {
128
+ if ("url" in config) {
144
129
  // 如果 URL 是 undefined 或 null,抛出错误
145
- if (legacyConfig.url === undefined || legacyConfig.url === null) {
146
- throw new ConfigValidationError(
147
- "网络配置必须包含有效的 url 字段",
148
- serviceName
149
- );
130
+ if (config.url === undefined || config.url === null) {
131
+ throw new ConfigValidationError("网络配置必须包含有效的 url 字段");
150
132
  }
151
133
 
152
134
  // 先推断类型,然后根据推断的类型选择正确的转换函数
153
- const inferredType = inferTransportTypeFromUrl(legacyConfig.url || "");
135
+ const inferredType = inferTransportTypeFromUrl(config.url || "");
154
136
 
155
137
  if (inferredType === MCPTransportType.SSE) {
156
138
  // 为SSE类型添加显式type字段
157
- const sseConfig = { ...legacyConfig, type: "sse" as const };
158
- return convertSSEConfig(serviceName, sseConfig);
139
+ const sseConfig = { ...config, type: "sse" as const };
140
+ return convertSSEConfig(sseConfig);
159
141
  }
160
142
  // 为HTTP类型添加显式type字段
161
- const httpConfig = { ...legacyConfig, type: "http" as const };
162
- return convertHTTPConfig(serviceName, httpConfig);
143
+ const httpConfig = { ...config, type: "http" as const };
144
+ return convertHTTPConfig(httpConfig);
163
145
  }
164
146
 
165
- throw new ConfigValidationError("无法识别的配置类型", serviceName);
147
+ throw new ConfigValidationError("无法识别的配置类型");
166
148
  }
167
149
 
168
- /** * 转换本地 stdio 配置 */
150
+ /**
151
+ * 转换本地 stdio 配置
152
+ */
169
153
  function convertLocalConfig(
170
- serviceName: string,
171
- config: LocalMCPServerConfig
154
+ config: MCPServerConfig
172
155
  ): MCPServiceConfig {
173
- if (!config.command) {
174
- throw new ConfigValidationError(
175
- "本地配置必须包含 command 字段",
176
- serviceName
177
- );
156
+ // 类型守卫:确保是 LocalMCPServerConfig
157
+ if (!isLocalConfig(config)) {
158
+ throw new ConfigValidationError("无效的本地配置类型");
159
+ }
160
+
161
+ const { command, args, env } = config;
162
+
163
+ if (!command) {
164
+ throw new ConfigValidationError("本地配置必须包含 command 字段");
178
165
  }
179
166
 
180
167
  // 获取配置文件所在目录作为工作目录
@@ -195,18 +182,18 @@ function convertLocalConfig(
195
182
  }
196
183
 
197
184
  // 解析 command 中的相对路径
198
- let resolvedCommand = config.command;
199
- if (isRelativePath(config.command)) {
200
- resolvedCommand = resolve(workingDir, config.command);
185
+ let resolvedCommand = command;
186
+ if (isRelativePath(command)) {
187
+ resolvedCommand = resolve(workingDir, command);
201
188
  console.log("解析 command 相对路径", {
202
- command: config.command,
189
+ command,
203
190
  resolvedCommand,
204
191
  workingDir,
205
192
  });
206
193
  }
207
194
 
208
195
  // 解析 args 中的相对路径
209
- const resolvedArgs = (config.args || []).map((arg) => {
196
+ const resolvedArgs = (args || []).map((arg: string) => {
210
197
  // 检查是否为相对路径(以 ./ 开头或不以 / 开头且包含文件扩展名)
211
198
  if (isRelativePath(arg)) {
212
199
  const resolvedPath = resolve(workingDir, arg);
@@ -217,11 +204,10 @@ function convertLocalConfig(
217
204
  });
218
205
 
219
206
  return {
220
- name: serviceName,
221
207
  type: MCPTransportType.STDIO,
222
208
  command: resolvedCommand,
223
209
  args: resolvedArgs,
224
- env: config.env, // 传递环境变量
210
+ ...(env !== undefined && { env }), // 只在 env 存在时添加该字段
225
211
  };
226
212
  }
227
213
 
@@ -229,82 +215,71 @@ function convertLocalConfig(
229
215
  * 转换 SSE 配置
230
216
  */
231
217
  function convertSSEConfig(
232
- serviceName: string,
233
- config: SSEMCPServerConfig
218
+ config: MCPServerConfig
234
219
  ): MCPServiceConfig {
235
- if (config.url === undefined || config.url === null) {
236
- throw new ConfigValidationError("SSE 配置必须包含 url 字段", serviceName);
220
+ const url = (config as any).url;
221
+ const type = (config as any).type;
222
+ const headers = (config as any).headers;
223
+
224
+ if (url === undefined || url === null) {
225
+ throw new ConfigValidationError("SSE 配置必须包含 url 字段");
237
226
  }
238
227
 
239
228
  // 优先使用显式指定的类型,如果没有则进行推断
240
229
  const inferredType =
241
- config.type === "sse"
230
+ type === "sse"
242
231
  ? MCPTransportType.SSE
243
- : inferTransportTypeFromUrl(config.url || "");
244
- const isModelScope = config.url ? isModelScopeURL(config.url) : false;
245
-
246
- const baseConfig: MCPServiceConfig = {
247
- name: serviceName,
248
- type: inferredType,
249
- url: config.url,
250
- headers: config.headers,
251
- };
252
-
253
- // 如果是 ModelScope 服务,添加特殊配置
254
- if (isModelScope) {
255
- baseConfig.modelScopeAuth = true;
256
- }
232
+ : inferTransportTypeFromUrl(url || "");
233
+ const isModelScope = url ? isModelScopeURL(url) : false;
257
234
 
258
235
  console.log("SSE配置转换", {
259
- serviceName,
260
- url: config.url,
236
+ url,
261
237
  inferredType,
262
238
  isModelScope,
263
239
  });
264
240
 
265
- return baseConfig;
241
+ return {
242
+ type: inferredType,
243
+ url,
244
+ headers,
245
+ };
266
246
  }
267
247
 
268
248
  /**
269
249
  * 转换 HTTP 配置
270
250
  */
271
251
  function convertHTTPConfig(
272
- serviceName: string,
273
- config: StreamableHTTPMCPServerConfig
252
+ config: MCPServerConfig
274
253
  ): MCPServiceConfig {
254
+ const url = (config as any).url;
255
+ const headers = (config as any).headers;
256
+
275
257
  // 检查 URL 是否存在
276
- if (config.url === undefined || config.url === null) {
277
- throw new ConfigValidationError(
278
- "HTTP 配置必须包含 url 字段",
279
- serviceName
280
- );
258
+ if (url === undefined || url === null) {
259
+ throw new ConfigValidationError("HTTP 配置必须包含 url 字段");
281
260
  }
282
261
 
283
- const url = config.url || "";
284
-
285
262
  return {
286
- name: serviceName,
287
263
  type: MCPTransportType.HTTP,
288
- url,
289
- headers: config.headers,
264
+ url: url || "",
265
+ headers,
290
266
  };
291
267
  }
292
268
 
293
269
  /**
294
- * 批量转换配置
270
+ * 批量标准化配置
295
271
  */
296
- export function convertLegacyConfigBatch(
272
+ export function normalizeServiceConfigBatch(
297
273
  legacyConfigs: Record<string, MCPServerConfig>
298
274
  ): Record<string, MCPServiceConfig> {
299
275
  const newConfigs: Record<string, MCPServiceConfig> = {};
300
- const errors: Array<{ serviceName: string; error: Error }> = [];
276
+ const errors: Array<{ error: Error }> = [];
301
277
 
302
- for (const [serviceName, legacyConfig] of Object.entries(legacyConfigs)) {
278
+ for (const [name, config] of Object.entries(legacyConfigs)) {
303
279
  try {
304
- newConfigs[serviceName] = convertLegacyToNew(serviceName, legacyConfig);
280
+ newConfigs[name] = normalizeServiceConfig(config);
305
281
  } catch (error) {
306
282
  errors.push({
307
- serviceName,
308
283
  error: error instanceof Error ? error : new Error(String(error)),
309
284
  });
310
285
  }
@@ -312,7 +287,7 @@ export function convertLegacyConfigBatch(
312
287
 
313
288
  if (errors.length > 0) {
314
289
  const errorMessages = errors
315
- .map(({ serviceName, error }) => `${serviceName}: ${error.message}`)
290
+ .map(({ error }, index) => `[${index}]: ${error.message}`)
316
291
  .join("; ");
317
292
  throw new ConfigValidationError(`批量配置转换失败: ${errorMessages}`);
318
293
  }
@@ -378,10 +353,6 @@ export function isModelScopeURL(url: string): boolean {
378
353
  * 验证新配置格式
379
354
  */
380
355
  function validateNewConfig(config: MCPServiceConfig): void {
381
- if (!config.name || typeof config.name !== "string") {
382
- throw new ConfigValidationError("配置必须包含有效的 name 字段");
383
- }
384
-
385
356
  if (config.type && !Object.values(MCPTransportType).includes(config.type)) {
386
357
  throw new ConfigValidationError(`无效的传输类型: ${config.type}`);
387
358
  }