coding-tool-x 3.9.4 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.9.4",
3
+ "version": "3.9.5-261009",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "source": "models.dev",
3
3
  "sourceUrl": "https://models.dev/api.json",
4
- "lastUpdated": "2026-09-28",
4
+ "lastUpdated": "2026-10-09",
5
5
  "defaultModels": {
6
6
  "claude": [
7
7
  "claude-opus-4-7",
@@ -18,7 +18,9 @@
18
18
  "claude-opus-4-5",
19
19
  "claude-haiku-4-5",
20
20
  "claude-sonnet-4-5",
21
- "claude-opus-5-5"
21
+ "claude-opus-5-5",
22
+ "claude-sonnet-5-5",
23
+ "claude-haiku-5-5"
22
24
  ],
23
25
  "codex": [
24
26
  "gpt-5.5",
@@ -58,7 +60,8 @@
58
60
  "gpt-6-luna",
59
61
  "gpt-6-sol",
60
62
  "gpt-daybreak-blue-latest",
61
- "gpt-daybreak-red-latest"
63
+ "gpt-daybreak-red-latest",
64
+ "gpt-6.1-sol"
62
65
  ],
63
66
  "gemini": [
64
67
  "gemini-3.1-pro-preview",
@@ -95,6 +98,7 @@
95
98
  "claude-opus-4.7": "claude-opus-4-7",
96
99
  "claude-sonnet-4-5": "claude-sonnet-4-5-20250929",
97
100
  "claude-sonnet-4-6": "claude-sonnet-4-6",
101
+ "codestral-2508": "mistral/codestral-2508",
98
102
  "codestral-latest": "mistral/codestral-latest",
99
103
  "deepseek-flash": "deepseek/deepseek-flash",
100
104
  "deepseek-v4-flash": "deepseek/deepseek-v4-flash",
@@ -142,6 +146,7 @@
142
146
  "kimi-k3": "moonshotai/kimi-k3",
143
147
  "mistral-large-2411": "mistral/mistral-large-2411",
144
148
  "mistral-large-2512": "mistral/mistral-large-2512",
149
+ "mistral-large-4": "mistral/mistral-large-4",
145
150
  "mistral-large-latest": "mistral/mistral-large-latest",
146
151
  "mistral-medium-2505": "mistral/mistral-medium-2505",
147
152
  "mistral-medium-2508": "mistral/mistral-medium-2508",
@@ -1506,6 +1511,33 @@
1506
1511
  "omp"
1507
1512
  ]
1508
1513
  },
1514
+ "claude-haiku-5-5": {
1515
+ "id": "claude-haiku-5-5",
1516
+ "input": [
1517
+ "text",
1518
+ "image"
1519
+ ],
1520
+ "limit": {
1521
+ "context": 1000000,
1522
+ "output": 128000
1523
+ },
1524
+ "name": "Claude Haiku 5.5",
1525
+ "pricing": {
1526
+ "cacheCreation": 0.125,
1527
+ "cacheRead": 0.01,
1528
+ "input": 0.1,
1529
+ "output": 0.5
1530
+ },
1531
+ "provider": "anthropic",
1532
+ "reasoning": true,
1533
+ "sourceId": "anthropic/claude-haiku-5-5",
1534
+ "supportsTools": true,
1535
+ "toolTypes": [
1536
+ "claude",
1537
+ "opencode",
1538
+ "omp"
1539
+ ]
1540
+ },
1509
1541
  "claude-opus-4-5": {
1510
1542
  "id": "claude-opus-4-5",
1511
1543
  "input": [
@@ -1702,7 +1734,7 @@
1702
1734
  "image"
1703
1735
  ],
1704
1736
  "limit": {
1705
- "context": 1000000,
1737
+ "context": 200000,
1706
1738
  "output": 64000
1707
1739
  },
1708
1740
  "name": "Claude Sonnet 4.5 (latest)",
@@ -1729,7 +1761,7 @@
1729
1761
  "image"
1730
1762
  ],
1731
1763
  "limit": {
1732
- "context": 1000000,
1764
+ "context": 200000,
1733
1765
  "output": 64000
1734
1766
  },
1735
1767
  "name": "Claude Sonnet 4.5",
@@ -1803,6 +1835,33 @@
1803
1835
  "omp"
1804
1836
  ]
1805
1837
  },
1838
+ "claude-sonnet-5-5": {
1839
+ "id": "claude-sonnet-5-5",
1840
+ "input": [
1841
+ "text",
1842
+ "image"
1843
+ ],
1844
+ "limit": {
1845
+ "context": 1000000,
1846
+ "output": 128000
1847
+ },
1848
+ "name": "Claude Sonnet 5.5",
1849
+ "pricing": {
1850
+ "cacheCreation": 2.5,
1851
+ "cacheRead": 0.1,
1852
+ "input": 2,
1853
+ "output": 10
1854
+ },
1855
+ "provider": "anthropic",
1856
+ "reasoning": true,
1857
+ "sourceId": "anthropic/claude-sonnet-5-5",
1858
+ "supportsTools": true,
1859
+ "toolTypes": [
1860
+ "claude",
1861
+ "opencode",
1862
+ "omp"
1863
+ ]
1864
+ },
1806
1865
  "deepseek/deepseek-flash": {
1807
1866
  "id": "deepseek/deepseek-flash",
1808
1867
  "input": [
@@ -1889,9 +1948,9 @@
1889
1948
  },
1890
1949
  "name": "DeepSeek V4 Pro",
1891
1950
  "pricing": {
1892
- "cacheRead": 0.003625,
1893
- "input": 0.435,
1894
- "output": 0.87
1951
+ "cacheRead": 0.022,
1952
+ "input": 0.66,
1953
+ "output": 1.98
1895
1954
  },
1896
1955
  "provider": "deepseek",
1897
1956
  "reasoning": true,
@@ -3181,6 +3240,33 @@
3181
3240
  "omp"
3182
3241
  ]
3183
3242
  },
3243
+ "gpt-6.1-sol": {
3244
+ "id": "gpt-6.1-sol",
3245
+ "input": [
3246
+ "text",
3247
+ "image"
3248
+ ],
3249
+ "limit": {
3250
+ "context": 1050000,
3251
+ "output": 128000
3252
+ },
3253
+ "name": "GPT-6.1 Sol",
3254
+ "pricing": {
3255
+ "cacheCreation": 2.5,
3256
+ "cacheRead": 0.1,
3257
+ "input": 2,
3258
+ "output": 10
3259
+ },
3260
+ "provider": "openai",
3261
+ "reasoning": true,
3262
+ "sourceId": "openai/gpt-6.1-sol",
3263
+ "supportsTools": true,
3264
+ "toolTypes": [
3265
+ "codex",
3266
+ "opencode",
3267
+ "omp"
3268
+ ]
3269
+ },
3184
3270
  "gpt-daybreak-blue-latest": {
3185
3271
  "id": "gpt-daybreak-blue-latest",
3186
3272
  "input": [
@@ -3261,6 +3347,30 @@
3261
3347
  "omp"
3262
3348
  ]
3263
3349
  },
3350
+ "mistral/codestral-2508": {
3351
+ "id": "mistral/codestral-2508",
3352
+ "input": [
3353
+ "text"
3354
+ ],
3355
+ "limit": {
3356
+ "context": 256000,
3357
+ "output": 8192
3358
+ },
3359
+ "name": "Codestral 25.08",
3360
+ "pricing": {
3361
+ "cacheRead": 0.03,
3362
+ "input": 0.3,
3363
+ "output": 0.9
3364
+ },
3365
+ "provider": "mistral",
3366
+ "reasoning": false,
3367
+ "sourceId": "mistral/codestral-2508",
3368
+ "supportsTools": true,
3369
+ "toolTypes": [
3370
+ "opencode",
3371
+ "omp"
3372
+ ]
3373
+ },
3264
3374
  "mistral/codestral-latest": {
3265
3375
  "id": "mistral/codestral-latest",
3266
3376
  "input": [
@@ -3272,6 +3382,7 @@
3272
3382
  },
3273
3383
  "name": "Codestral (latest)",
3274
3384
  "pricing": {
3385
+ "cacheRead": 0.03,
3275
3386
  "input": 0.3,
3276
3387
  "output": 0.9
3277
3388
  },
@@ -3319,6 +3430,7 @@
3319
3430
  },
3320
3431
  "name": "Mistral Large 3",
3321
3432
  "pricing": {
3433
+ "cacheRead": 0.05,
3322
3434
  "input": 0.5,
3323
3435
  "output": 1.5
3324
3436
  },
@@ -3331,6 +3443,31 @@
3331
3443
  "omp"
3332
3444
  ]
3333
3445
  },
3446
+ "mistral/mistral-large-4": {
3447
+ "id": "mistral/mistral-large-4",
3448
+ "input": [
3449
+ "text",
3450
+ "image"
3451
+ ],
3452
+ "limit": {
3453
+ "context": 1048576,
3454
+ "output": 262144
3455
+ },
3456
+ "name": "Mistral Large 4",
3457
+ "pricing": {
3458
+ "cacheRead": 0.07,
3459
+ "input": 0.68,
3460
+ "output": 2.09
3461
+ },
3462
+ "provider": "mistral",
3463
+ "reasoning": true,
3464
+ "sourceId": "mistral/mistral-large-4",
3465
+ "supportsTools": true,
3466
+ "toolTypes": [
3467
+ "opencode",
3468
+ "omp"
3469
+ ]
3470
+ },
3334
3471
  "mistral/mistral-large-latest": {
3335
3472
  "id": "mistral/mistral-large-latest",
3336
3473
  "input": [
@@ -3343,6 +3480,7 @@
3343
3480
  },
3344
3481
  "name": "Mistral Large (latest)",
3345
3482
  "pricing": {
3483
+ "cacheRead": 0.05,
3346
3484
  "input": 0.5,
3347
3485
  "output": 1.5
3348
3486
  },
@@ -3415,6 +3553,7 @@
3415
3553
  },
3416
3554
  "name": "Mistral Medium 3.5",
3417
3555
  "pricing": {
3556
+ "cacheRead": 0.15,
3418
3557
  "input": 1.5,
3419
3558
  "output": 7.5
3420
3559
  },
@@ -3439,6 +3578,7 @@
3439
3578
  },
3440
3579
  "name": "Mistral Medium (latest)",
3441
3580
  "pricing": {
3581
+ "cacheRead": 0.15,
3442
3582
  "input": 1.5,
3443
3583
  "output": 7.5
3444
3584
  },
@@ -3505,11 +3645,12 @@
3505
3645
  "image"
3506
3646
  ],
3507
3647
  "limit": {
3508
- "context": 256000,
3648
+ "context": 262144,
3509
3649
  "output": 256000
3510
3650
  },
3511
3651
  "name": "Mistral Small 4",
3512
3652
  "pricing": {
3653
+ "cacheRead": 0.015,
3513
3654
  "input": 0.15,
3514
3655
  "output": 0.6
3515
3656
  },
@@ -3529,11 +3670,12 @@
3529
3670
  "image"
3530
3671
  ],
3531
3672
  "limit": {
3532
- "context": 256000,
3673
+ "context": 262144,
3533
3674
  "output": 256000
3534
3675
  },
3535
3676
  "name": "Mistral Small (latest)",
3536
3677
  "pricing": {
3678
+ "cacheRead": 0.015,
3537
3679
  "input": 0.15,
3538
3680
  "output": 0.6
3539
3681
  },
@@ -3778,6 +3920,7 @@
3778
3920
  },
3779
3921
  "name": "Kimi K3",
3780
3922
  "pricing": {
3923
+ "cacheCreation": 3,
3781
3924
  "cacheRead": 0.3,
3782
3925
  "input": 3,
3783
3926
  "output": 15
@@ -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 readDshMcpEntries(context = {}) {
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[server.id] = dshMcpConfigToSpec(server.config || {});
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
- const profileName = profileNameForRequest(context, server);
363
- const id = String(server?.id || '').trim();
364
- if (!id) throw new Error('MCP server id is required');
365
- const patch = requirePatchResult(
366
- upsertProfilePatchRow(context, profileName, {
367
- id,
368
- name: '@deepseek-ai/dsh-mcp-client',
369
- config: dshMcpSpecToConfig(server.server || {}, id)
370
- }),
371
- profileName
372
- );
373
- return resultFor(mcpContext('sync'), 'ok', patch);
374
- };
375
- driver.remove = async serverId => {
376
- const profileName = profileNameForRequest(context);
377
- const id = String(serverId || '').trim();
378
- if (!id) throw new Error('MCP server id is required');
379
- if (!profileName) return resultFor(mcpContext('remove'), 'ok', true);
380
- const patch = requirePatchResult(deleteProfilePatchRow(context, profileName, id), profileName);
381
- return resultFor(mcpContext('remove'), 'ok', patch);
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
- const entries = readDshMcpEntries(context);
385
- let count = 0;
386
- for (const [id, spec] of Object.entries(entries)) {
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
- const now = Date.now();
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
 
@@ -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
- ? `成功从 ${resolved.key} 导入 ${count} 个 MCP 服务器`
330
- : `${resolved.key} 没有可导入的 MCP 服务器`
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[id]) {
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[id]) {
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 };