coding-tool-x 3.9.3-261008 → 3.9.5-261009
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/package.json +1 -1
- package/src/config/model-metadata.json +2 -2
- package/src/platforms/drivers/dsh/api-operations.js +46 -43
- package/src/server/api/mcp.js +2 -2
- package/src/server/services/mcp-service.js +2 -19
- package/src/shared/driver-factories/mcp.js +2 -8
- package/src/shared/mcp-import.js +107 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "models.dev",
|
|
3
3
|
"sourceUrl": "https://models.dev/api.json",
|
|
4
|
-
"lastUpdated": "2026-10-
|
|
4
|
+
"lastUpdated": "2026-10-09",
|
|
5
5
|
"defaultModels": {
|
|
6
6
|
"claude": [
|
|
7
7
|
"claude-opus-4-7",
|
|
@@ -3450,7 +3450,7 @@
|
|
|
3450
3450
|
"image"
|
|
3451
3451
|
],
|
|
3452
3452
|
"limit": {
|
|
3453
|
-
"context":
|
|
3453
|
+
"context": 1048576,
|
|
3454
3454
|
"output": 262144
|
|
3455
3455
|
},
|
|
3456
3456
|
"name": "Mistral Large 4",
|
|
@@ -9,6 +9,7 @@ const { installPlugin, uninstallPlugin, updatePlugin } = require('./plugin-manag
|
|
|
9
9
|
const { createDriver: createChannelsDriver } = require('./channels');
|
|
10
10
|
const { createDriver: createProxyDriver } = require('./proxy');
|
|
11
11
|
const { createDriver: createStatisticsDriver } = require('./statistics');
|
|
12
|
+
const { importMcpServer } = require('../../../shared/mcp-import');
|
|
12
13
|
|
|
13
14
|
function requiredId(value, label) {
|
|
14
15
|
const id = String(value || '').trim();
|
|
@@ -153,18 +154,34 @@ function dshMcpSpecToConfig(spec = {}, serverName) {
|
|
|
153
154
|
return config;
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
function
|
|
157
|
+
function dshMcpServerName(server, fallbackId) {
|
|
158
|
+
const requested = String(server?.name || '').trim();
|
|
159
|
+
if (/^[A-Za-z0-9_-]{1,32}$/.test(requested)) return requested;
|
|
160
|
+
return String(fallbackId || '')
|
|
161
|
+
.replace(/[^A-Za-z0-9_-]/g, '_')
|
|
162
|
+
.slice(0, 32) || 'mcp';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function readDshMcpRecords(context = {}) {
|
|
157
166
|
const profile = resolveProfileName(context);
|
|
158
|
-
if (!profile) return
|
|
167
|
+
if (!profile) return [];
|
|
159
168
|
const result = listProfileMcp(context, profile);
|
|
160
|
-
const entries =
|
|
169
|
+
const entries = [];
|
|
161
170
|
for (const server of result?.servers || []) {
|
|
162
171
|
if (!server?.id || server.disabled) continue;
|
|
163
|
-
entries
|
|
172
|
+
entries.push({
|
|
173
|
+
id: server.id,
|
|
174
|
+
name: server.config?.serverName || server.id,
|
|
175
|
+
spec: dshMcpConfigToSpec(server.config || {})
|
|
176
|
+
});
|
|
164
177
|
}
|
|
165
178
|
return entries;
|
|
166
179
|
}
|
|
167
180
|
|
|
181
|
+
function readDshMcpEntries(context = {}) {
|
|
182
|
+
return Object.fromEntries(readDshMcpRecords(context).map(({ id, spec }) => [id, spec]));
|
|
183
|
+
}
|
|
184
|
+
|
|
168
185
|
function promptContentFromDshConfig(config = {}) {
|
|
169
186
|
for (const key of ['content', 'prompt', 'text']) {
|
|
170
187
|
if (typeof config[key] === 'string') return config[key];
|
|
@@ -359,47 +376,33 @@ function createDriver(context = {}) {
|
|
|
359
376
|
driver.read = () => resultFor(mcpContext('read'), 'ok', { mcpServers: readDshMcpEntries(context) });
|
|
360
377
|
driver.normalize = spec => resultFor(mcpContext('normalize'), 'ok', spec);
|
|
361
378
|
driver.sync = async server => {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
379
|
+
const profileName = profileNameForRequest(context, server);
|
|
380
|
+
const id = String(server?.id || '').trim();
|
|
381
|
+
if (!id) throw new Error('MCP server id is required');
|
|
382
|
+
const patch = requirePatchResult(
|
|
383
|
+
upsertProfilePatchRow(context, profileName, {
|
|
384
|
+
id,
|
|
385
|
+
name: '@deepseek-ai/dsh-mcp-client',
|
|
386
|
+
config: dshMcpSpecToConfig(server.server || {}, dshMcpServerName(server, id))
|
|
387
|
+
}),
|
|
388
|
+
profileName
|
|
389
|
+
);
|
|
390
|
+
return resultFor(mcpContext('sync'), 'ok', patch);
|
|
391
|
+
};
|
|
392
|
+
driver.remove = async serverId => {
|
|
393
|
+
const profileName = profileNameForRequest(context);
|
|
394
|
+
const id = String(serverId || '').trim();
|
|
395
|
+
if (!id) throw new Error('MCP server id is required');
|
|
396
|
+
if (!profileName) return resultFor(mcpContext('remove'), 'ok', true);
|
|
397
|
+
const patch = requirePatchResult(deleteProfilePatchRow(context, profileName, id), profileName);
|
|
398
|
+
return resultFor(mcpContext('remove'), 'ok', patch);
|
|
399
|
+
};
|
|
383
400
|
driver.import = async servers => {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if (servers[id]) {
|
|
388
|
-
servers[id].apps = { ...(servers[id].apps || {}), dsh: true };
|
|
389
|
-
continue;
|
|
401
|
+
let count = 0;
|
|
402
|
+
for (const entry of readDshMcpRecords(context)) {
|
|
403
|
+
if (importMcpServer(servers, { ...entry, platform: 'dsh' })) count++;
|
|
390
404
|
}
|
|
391
|
-
|
|
392
|
-
servers[id] = {
|
|
393
|
-
id,
|
|
394
|
-
name: id,
|
|
395
|
-
server: spec,
|
|
396
|
-
apps: { dsh: true },
|
|
397
|
-
createdAt: now,
|
|
398
|
-
updatedAt: now
|
|
399
|
-
};
|
|
400
|
-
count++;
|
|
401
|
-
}
|
|
402
|
-
return resultFor(mcpContext('import'), 'ok', count);
|
|
405
|
+
return resultFor(mcpContext('import'), 'ok', count);
|
|
403
406
|
};
|
|
404
407
|
}
|
|
405
408
|
|
package/src/server/api/mcp.js
CHANGED
|
@@ -326,8 +326,8 @@ router.post('/import/:platform', async (req, res) => {
|
|
|
326
326
|
success: true,
|
|
327
327
|
imported: count,
|
|
328
328
|
message: count > 0
|
|
329
|
-
?
|
|
330
|
-
: `${resolved.key}
|
|
329
|
+
? `已从 ${resolved.key} 导入或更新 ${count} 个 MCP 服务器`
|
|
330
|
+
: `${resolved.key} 的 MCP 配置没有新增或变更`
|
|
331
331
|
});
|
|
332
332
|
} catch (error) {
|
|
333
333
|
console.error('[MCP API] Import failed:', sanitizeErrorMessage(error.message || error));
|
|
@@ -9,6 +9,7 @@ const path = require('path');
|
|
|
9
9
|
const { spawn } = require('child_process');
|
|
10
10
|
const { McpClient, buildMissingCommandMessage, createMissingCommandHint } = require('./mcp-client');
|
|
11
11
|
const mcpFormat = require('../../shared/mcp-format');
|
|
12
|
+
const { importMcpServer } = require('../../shared/mcp-import');
|
|
12
13
|
const { convertToOmpMcpFormat, convertFromOmpMcpFormat } = mcpFormat;
|
|
13
14
|
const { PATHS } = require('../../config/paths');
|
|
14
15
|
const { ControlManifestStore } = require('./control-manifest-store');
|
|
@@ -1072,25 +1073,7 @@ async function importGenericMcpServers(driver, platform, servers) {
|
|
|
1072
1073
|
|
|
1073
1074
|
for (const [id, spec] of Object.entries(mcpServers)) {
|
|
1074
1075
|
if (!spec || typeof spec !== 'object' || Array.isArray(spec)) continue;
|
|
1075
|
-
if (servers
|
|
1076
|
-
servers[id].apps = normalizeServerApps(servers[id].apps, {});
|
|
1077
|
-
if (!servers[id].apps[platform]) {
|
|
1078
|
-
servers[id].apps[platform] = true;
|
|
1079
|
-
count++;
|
|
1080
|
-
}
|
|
1081
|
-
continue;
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
const now = Date.now();
|
|
1085
|
-
servers[id] = {
|
|
1086
|
-
id,
|
|
1087
|
-
name: id,
|
|
1088
|
-
server: normalizeServerSpec(spec),
|
|
1089
|
-
apps: { [platform]: true },
|
|
1090
|
-
createdAt: now,
|
|
1091
|
-
updatedAt: now
|
|
1092
|
-
};
|
|
1093
|
-
count++;
|
|
1076
|
+
if (importMcpServer(servers, { id, platform, spec: normalizeServerSpec(spec) })) count++;
|
|
1094
1077
|
}
|
|
1095
1078
|
|
|
1096
1079
|
return count;
|
|
@@ -8,6 +8,7 @@ const tomlStringify = require('@iarna/toml').stringify;
|
|
|
8
8
|
const { ok, failed } = require('../driver-result');
|
|
9
9
|
const { extractServerSpec, convertToCodexFormat, convertFromCodexFormat, convertToOmpMcpFormat, convertFromOmpMcpFormat, convertToOpenCodeFormat, convertFromOpenCodeFormat } = require('../mcp-format');
|
|
10
10
|
const { redactSecrets, validateMcpId } = require('../project-config');
|
|
11
|
+
const { importMcpServer } = require('../mcp-import');
|
|
11
12
|
|
|
12
13
|
const SCHEMA_URL = 'https://raw.githubusercontent.com/can1357/oh-my-omp/main/packages/coding-agent/src/config/mcp-schema.json';
|
|
13
14
|
|
|
@@ -192,14 +193,7 @@ function createMcpDriver({ platform, ...context } = {}) {
|
|
|
192
193
|
let count = 0;
|
|
193
194
|
for (const [id, spec] of Object.entries(map)) {
|
|
194
195
|
if (!spec || typeof spec !== 'object' || Array.isArray(spec)) continue;
|
|
195
|
-
if (servers
|
|
196
|
-
servers[id].apps = { ...(servers[id].apps || {}), [platform]: true };
|
|
197
|
-
count += 1;
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
const now = Date.now();
|
|
201
|
-
servers[id] = { id, name: id, server: internalSpec(spec), apps: { [platform]: true }, createdAt: now, updatedAt: now };
|
|
202
|
-
count += 1;
|
|
196
|
+
if (importMcpServer(servers, { id, platform, spec: internalSpec(spec) })) count += 1;
|
|
203
197
|
}
|
|
204
198
|
return count;
|
|
205
199
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isDeepStrictEqual } = require('util');
|
|
4
|
+
const { validateMcpId } = require('./project-config');
|
|
5
|
+
|
|
6
|
+
const REDACTED_VALUES = new Set(['[REDACTED]', '[redacted]', '[EXPRESSION]']);
|
|
7
|
+
|
|
8
|
+
function normalizeImportedSpec(spec) {
|
|
9
|
+
if (!spec || typeof spec !== 'object' || Array.isArray(spec)) return spec;
|
|
10
|
+
return { ...spec, type: spec.type || 'stdio' };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function restoreRedactedUrl(incoming, existing) {
|
|
14
|
+
if (typeof incoming !== 'string' || typeof existing !== 'string') return incoming;
|
|
15
|
+
try {
|
|
16
|
+
const nextUrl = new URL(incoming);
|
|
17
|
+
const previousUrl = new URL(existing);
|
|
18
|
+
const isRedactedPart = value => {
|
|
19
|
+
try {
|
|
20
|
+
return REDACTED_VALUES.has(decodeURIComponent(value));
|
|
21
|
+
} catch (_) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const decodePart = value => {
|
|
26
|
+
try {
|
|
27
|
+
return decodeURIComponent(value);
|
|
28
|
+
} catch (_) {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
if (isRedactedPart(nextUrl.username) && previousUrl.username) {
|
|
33
|
+
nextUrl.username = decodePart(previousUrl.username);
|
|
34
|
+
}
|
|
35
|
+
if (isRedactedPart(nextUrl.password) && previousUrl.password) {
|
|
36
|
+
nextUrl.password = decodePart(previousUrl.password);
|
|
37
|
+
}
|
|
38
|
+
for (const [key, value] of nextUrl.searchParams.entries()) {
|
|
39
|
+
if (REDACTED_VALUES.has(value) && previousUrl.searchParams.has(key)) {
|
|
40
|
+
nextUrl.searchParams.set(key, previousUrl.searchParams.get(key));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return nextUrl.toString();
|
|
44
|
+
} catch (_) {
|
|
45
|
+
return incoming;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function restoreRedactedValues(incoming, existing, key = '') {
|
|
50
|
+
if (REDACTED_VALUES.has(incoming)) return existing === undefined ? incoming : existing;
|
|
51
|
+
if (key === 'url') return restoreRedactedUrl(incoming, existing);
|
|
52
|
+
if (Array.isArray(incoming)) {
|
|
53
|
+
const previous = Array.isArray(existing) ? existing : [];
|
|
54
|
+
return incoming.map((value, index) => restoreRedactedValues(value, previous[index]));
|
|
55
|
+
}
|
|
56
|
+
if (!incoming || typeof incoming !== 'object') return incoming;
|
|
57
|
+
const previous = existing && typeof existing === 'object' && !Array.isArray(existing) ? existing : {};
|
|
58
|
+
return Object.fromEntries(Object.entries(incoming).map(([childKey, value]) => [
|
|
59
|
+
childKey,
|
|
60
|
+
restoreRedactedValues(value, previous[childKey], childKey)
|
|
61
|
+
]));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function importMcpServer(servers, { id, platform, spec, name } = {}) {
|
|
65
|
+
let normalizedId;
|
|
66
|
+
try {
|
|
67
|
+
normalizedId = validateMcpId(id);
|
|
68
|
+
} catch (_) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
const importedSpec = normalizeImportedSpec(spec);
|
|
72
|
+
const existing = servers[normalizedId];
|
|
73
|
+
|
|
74
|
+
if (!existing) {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
servers[normalizedId] = {
|
|
77
|
+
id: normalizedId,
|
|
78
|
+
name: typeof name === 'string' && name.trim() ? name : normalizedId,
|
|
79
|
+
server: importedSpec,
|
|
80
|
+
apps: { [platform]: true },
|
|
81
|
+
createdAt: now,
|
|
82
|
+
updatedAt: now
|
|
83
|
+
};
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Treat imports as full snapshots, restoring only values hidden by the platform.
|
|
88
|
+
const nextSpec = restoreRedactedValues(importedSpec, existing.server);
|
|
89
|
+
const nextApps = {
|
|
90
|
+
...(existing.apps && typeof existing.apps === 'object' && !Array.isArray(existing.apps)
|
|
91
|
+
? existing.apps
|
|
92
|
+
: {}),
|
|
93
|
+
[platform]: true
|
|
94
|
+
};
|
|
95
|
+
const nextName = typeof name === 'string' && name.trim() ? name : existing.name || normalizedId;
|
|
96
|
+
const changed = !isDeepStrictEqual(existing.server, nextSpec)
|
|
97
|
+
|| !isDeepStrictEqual(existing.apps || {}, nextApps)
|
|
98
|
+
|| existing.name !== nextName;
|
|
99
|
+
|
|
100
|
+
existing.server = nextSpec;
|
|
101
|
+
existing.apps = nextApps;
|
|
102
|
+
existing.name = nextName;
|
|
103
|
+
if (changed) existing.updatedAt = Date.now();
|
|
104
|
+
return changed;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { importMcpServer };
|