@softerist/heuristic-mcp 3.0.15 → 3.0.16

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.
Files changed (49) hide show
  1. package/README.md +104 -104
  2. package/config.jsonc +173 -173
  3. package/features/ann-config.js +131 -0
  4. package/features/clear-cache.js +84 -0
  5. package/features/find-similar-code.js +291 -0
  6. package/features/hybrid-search.js +544 -0
  7. package/features/index-codebase.js +3268 -0
  8. package/features/lifecycle.js +1189 -0
  9. package/features/package-version.js +302 -0
  10. package/features/register.js +408 -0
  11. package/features/resources.js +156 -0
  12. package/features/set-workspace.js +265 -0
  13. package/index.js +96 -96
  14. package/lib/cache-ops.js +22 -22
  15. package/lib/cache-utils.js +565 -565
  16. package/lib/cache.js +1870 -1870
  17. package/lib/call-graph.js +396 -396
  18. package/lib/cli.js +1 -1
  19. package/lib/config.js +517 -517
  20. package/lib/constants.js +39 -39
  21. package/lib/embed-query-process.js +7 -7
  22. package/lib/embedding-process.js +7 -7
  23. package/lib/embedding-worker.js +299 -299
  24. package/lib/ignore-patterns.js +316 -316
  25. package/lib/json-worker.js +14 -14
  26. package/lib/json-writer.js +337 -337
  27. package/lib/logging.js +164 -164
  28. package/lib/memory-logger.js +13 -13
  29. package/lib/onnx-backend.js +193 -193
  30. package/lib/project-detector.js +84 -84
  31. package/lib/server-lifecycle.js +165 -165
  32. package/lib/settings-editor.js +754 -754
  33. package/lib/tokenizer.js +256 -256
  34. package/lib/utils.js +428 -428
  35. package/lib/vector-store-binary.js +627 -627
  36. package/lib/vector-store-sqlite.js +95 -95
  37. package/lib/workspace-env.js +28 -28
  38. package/mcp_config.json +9 -9
  39. package/package.json +86 -75
  40. package/scripts/clear-cache.js +20 -0
  41. package/scripts/download-model.js +43 -0
  42. package/scripts/mcp-launcher.js +49 -0
  43. package/scripts/postinstall.js +12 -0
  44. package/search-configs.js +36 -36
  45. package/.prettierrc +0 -7
  46. package/debug-pids.js +0 -30
  47. package/eslint.config.js +0 -36
  48. package/specs/plan.md +0 -23
  49. package/vitest.config.js +0 -39
@@ -1,754 +1,754 @@
1
- function detectNewline(text) {
2
- return text.includes('\r\n') ? '\r\n' : '\n';
3
- }
4
-
5
- function detectIndentUnit(text) {
6
- const lines = text.split(/\r?\n/);
7
- for (const line of lines) {
8
- const match = line.match(/^(\s+)"/);
9
- if (match) return match[1];
10
- }
11
- return ' ';
12
- }
13
-
14
- function getLineIndent(text, index) {
15
- const lineStart = Math.max(text.lastIndexOf('\n', index - 1), text.lastIndexOf('\r', index - 1));
16
- const start = lineStart === -1 ? 0 : lineStart + 1;
17
- let i = start;
18
- while (i < text.length && (text[i] === ' ' || text[i] === '\t')) i += 1;
19
- return text.slice(start, i);
20
- }
21
-
22
- function stripJsonComments(text) {
23
- let out = '';
24
- let inString = false;
25
- let escape = false;
26
- let inLine = false;
27
- let inBlock = false;
28
-
29
- for (let i = 0; i < text.length; i += 1) {
30
- const ch = text[i];
31
- const next = text[i + 1];
32
-
33
- if (inLine) {
34
- if (ch === '\n') {
35
- inLine = false;
36
- out += ch;
37
- }
38
- continue;
39
- }
40
-
41
- if (inBlock) {
42
- if (ch === '*' && next === '/') {
43
- inBlock = false;
44
- i += 1;
45
- }
46
- continue;
47
- }
48
-
49
- if (inString) {
50
- out += ch;
51
- if (escape) {
52
- escape = false;
53
- } else if (ch === '\\') {
54
- escape = true;
55
- } else if (ch === '"') {
56
- inString = false;
57
- }
58
- continue;
59
- }
60
-
61
- if (ch === '/' && next === '/') {
62
- inLine = true;
63
- i += 1;
64
- continue;
65
- }
66
- if (ch === '/' && next === '*') {
67
- inBlock = true;
68
- i += 1;
69
- continue;
70
- }
71
-
72
- if (ch === '"') {
73
- inString = true;
74
- }
75
- out += ch;
76
- }
77
-
78
- return out;
79
- }
80
-
81
- function stripTrailingCommas(text) {
82
- let out = '';
83
- let inString = false;
84
- let escape = false;
85
-
86
- for (let i = 0; i < text.length; i += 1) {
87
- const ch = text[i];
88
-
89
- if (inString) {
90
- out += ch;
91
- if (escape) {
92
- escape = false;
93
- } else if (ch === '\\') {
94
- escape = true;
95
- } else if (ch === '"') {
96
- inString = false;
97
- }
98
- continue;
99
- }
100
-
101
- if (ch === '"') {
102
- inString = true;
103
- out += ch;
104
- continue;
105
- }
106
-
107
- if (ch === ',') {
108
- let j = i + 1;
109
- while (j < text.length && /\s/.test(text[j])) j += 1;
110
- if (text[j] === '}' || text[j] === ']') {
111
- continue;
112
- }
113
- }
114
-
115
- out += ch;
116
- }
117
-
118
- return out;
119
- }
120
-
121
- export function parseJsonc(text) {
122
- const cleaned = stripTrailingCommas(stripJsonComments(text));
123
- try {
124
- return JSON.parse(cleaned);
125
- } catch {
126
- return null;
127
- }
128
- }
129
-
130
- function skipWhitespaceAndComments(text, start, end) {
131
- let i = start;
132
- while (i < end) {
133
- const ch = text[i];
134
- const next = text[i + 1];
135
- if (ch === ' ' || ch === '\t' || ch === '\r' || ch === '\n') {
136
- i += 1;
137
- continue;
138
- }
139
- if (ch === '/' && next === '/') {
140
- i += 2;
141
- while (i < end && text[i] !== '\n') i += 1;
142
- continue;
143
- }
144
- if (ch === '/' && next === '*') {
145
- i += 2;
146
- while (i < end - 1 && !(text[i] === '*' && text[i + 1] === '/')) i += 1;
147
- i += 2;
148
- continue;
149
- }
150
- break;
151
- }
152
- return i;
153
- }
154
-
155
- function readString(text, start, end) {
156
- let i = start + 1;
157
- let value = '';
158
- let escape = false;
159
- while (i < end) {
160
- const ch = text[i];
161
- if (escape) {
162
- value += ch;
163
- escape = false;
164
- } else if (ch === '\\') {
165
- value += ch;
166
- escape = true;
167
- } else if (ch === '"') {
168
- return { value, end: i + 1 };
169
- } else {
170
- value += ch;
171
- }
172
- i += 1;
173
- }
174
- return { value, end: i };
175
- }
176
-
177
- function scanComposite(text, start, end, openChar, closeChar) {
178
- let depth = 0;
179
- let inString = false;
180
- let escape = false;
181
- let inLine = false;
182
- let inBlock = false;
183
-
184
- for (let i = start; i < end; i += 1) {
185
- const ch = text[i];
186
- const next = text[i + 1];
187
-
188
- if (inLine) {
189
- if (ch === '\n') inLine = false;
190
- continue;
191
- }
192
- if (inBlock) {
193
- if (ch === '*' && next === '/') {
194
- inBlock = false;
195
- i += 1;
196
- }
197
- continue;
198
- }
199
- if (inString) {
200
- if (escape) {
201
- escape = false;
202
- } else if (ch === '\\') {
203
- escape = true;
204
- } else if (ch === '"') {
205
- inString = false;
206
- }
207
- continue;
208
- }
209
-
210
- if (ch === '/' && next === '/') {
211
- inLine = true;
212
- i += 1;
213
- continue;
214
- }
215
- if (ch === '/' && next === '*') {
216
- inBlock = true;
217
- i += 1;
218
- continue;
219
- }
220
-
221
- if (ch === '"') {
222
- inString = true;
223
- continue;
224
- }
225
-
226
- if (ch === openChar) {
227
- depth += 1;
228
- continue;
229
- }
230
- if (ch === closeChar) {
231
- depth -= 1;
232
- if (depth === 0) return i + 1;
233
- }
234
- }
235
- return end;
236
- }
237
-
238
- function scanValue(text, start, end) {
239
- let i = skipWhitespaceAndComments(text, start, end);
240
- const valueStart = i;
241
- if (i >= end) {
242
- return { valueStart: end, valueEnd: end, valueEndWithComma: end };
243
- }
244
-
245
- const ch = text[i];
246
- let valueEnd = i;
247
-
248
- if (ch === '{') {
249
- valueEnd = scanComposite(text, i, end, '{', '}');
250
- } else if (ch === '[') {
251
- valueEnd = scanComposite(text, i, end, '[', ']');
252
- } else if (ch === '"') {
253
- const result = readString(text, i, end);
254
- valueEnd = result.end;
255
- } else {
256
- let inString = false;
257
- let escape = false;
258
- let inLine = false;
259
- let inBlock = false;
260
- for (; i < end; i += 1) {
261
- const c = text[i];
262
- const next = text[i + 1];
263
- if (inLine) {
264
- if (c === '\n') inLine = false;
265
- continue;
266
- }
267
- if (inBlock) {
268
- if (c === '*' && next === '/') {
269
- inBlock = false;
270
- i += 1;
271
- }
272
- continue;
273
- }
274
- if (inString) {
275
- if (escape) {
276
- escape = false;
277
- } else if (c === '\\') {
278
- escape = true;
279
- } else if (c === '"') {
280
- inString = false;
281
- }
282
- continue;
283
- }
284
- if (c === '/' && next === '/') {
285
- inLine = true;
286
- i += 1;
287
- continue;
288
- }
289
- if (c === '/' && next === '*') {
290
- inBlock = true;
291
- i += 1;
292
- continue;
293
- }
294
- if (c === '"') {
295
- inString = true;
296
- continue;
297
- }
298
- if (c === ',' || c === '}' || c === ']') {
299
- break;
300
- }
301
- }
302
- valueEnd = i;
303
- }
304
-
305
- let valueEndWithComma = valueEnd;
306
- let j = skipWhitespaceAndComments(text, valueEnd, end);
307
- if (text[j] === ',') {
308
- valueEndWithComma = j + 1;
309
- }
310
-
311
- return { valueStart, valueEnd, valueEndWithComma };
312
- }
313
-
314
- function findRootObjectRange(text) {
315
- let inString = false;
316
- let escape = false;
317
- let inLine = false;
318
- let inBlock = false;
319
-
320
- for (let i = 0; i < text.length; i += 1) {
321
- const ch = text[i];
322
- const next = text[i + 1];
323
-
324
- if (inLine) {
325
- if (ch === '\n') inLine = false;
326
- continue;
327
- }
328
- if (inBlock) {
329
- if (ch === '*' && next === '/') {
330
- inBlock = false;
331
- i += 1;
332
- }
333
- continue;
334
- }
335
- if (inString) {
336
- if (escape) {
337
- escape = false;
338
- } else if (ch === '\\') {
339
- escape = true;
340
- } else if (ch === '"') {
341
- inString = false;
342
- }
343
- continue;
344
- }
345
-
346
- if (ch === '/' && next === '/') {
347
- inLine = true;
348
- i += 1;
349
- continue;
350
- }
351
- if (ch === '/' && next === '*') {
352
- inBlock = true;
353
- i += 1;
354
- continue;
355
- }
356
-
357
- if (ch === '"') {
358
- inString = true;
359
- continue;
360
- }
361
-
362
- if (ch === '{') {
363
- const end = scanComposite(text, i, text.length, '{', '}');
364
- if (end > i) return { start: i, end };
365
- }
366
- }
367
-
368
- return null;
369
- }
370
-
371
- function findPropertyValueRange(text, objRange, key) {
372
- let i = objRange.start + 1;
373
- const end = objRange.end - 1;
374
- let expectKey = true;
375
-
376
- while (i < end) {
377
- i = skipWhitespaceAndComments(text, i, end);
378
- if (i >= end) break;
379
- const ch = text[i];
380
- if (ch === '}') break;
381
- if (ch === ',') {
382
- expectKey = true;
383
- i += 1;
384
- continue;
385
- }
386
- if (!expectKey) {
387
- const valueInfo = scanValue(text, i, end);
388
- i = valueInfo.valueEndWithComma;
389
- expectKey = true;
390
- continue;
391
- }
392
- if (ch !== '"') {
393
- i += 1;
394
- continue;
395
- }
396
- const keyResult = readString(text, i, end);
397
- const keyName = keyResult.value;
398
- const keyStart = i;
399
- const keyEnd = keyResult.end;
400
- let afterKey = skipWhitespaceAndComments(text, keyEnd, end);
401
- if (text[afterKey] !== ':') {
402
- i = keyEnd;
403
- continue;
404
- }
405
- const valueInfo = scanValue(text, afterKey + 1, end);
406
- if (keyName === key) {
407
- return {
408
- keyStart,
409
- keyEnd,
410
- valueStart: valueInfo.valueStart,
411
- valueEnd: valueInfo.valueEnd,
412
- valueEndWithComma: valueInfo.valueEndWithComma,
413
- };
414
- }
415
- i = valueInfo.valueEndWithComma;
416
- expectKey = true;
417
- }
418
-
419
- return null;
420
- }
421
-
422
- function formatJsonValue(value, indentUnit, parentIndent, newline) {
423
- const raw = JSON.stringify(value, null, indentUnit);
424
- if (!raw.includes('\n')) return raw;
425
- return raw.replace(/\n/g, `${newline}${parentIndent}`);
426
- }
427
-
428
- function findObjectRangeFromValue(text, valueStart, valueEnd) {
429
- const start = skipWhitespaceAndComments(text, valueStart, valueEnd);
430
- if (text[start] !== '{') return null;
431
- const end = scanComposite(text, start, valueEnd, '{', '}');
432
- return { start, end };
433
- }
434
-
435
- function insertPropertyIntoObject(text, objRange, key, valueText, indentUnit, newline) {
436
- const objectIndent = getLineIndent(text, objRange.start);
437
- const propertyIndent = `${objectIndent}${indentUnit}`;
438
- const entry = `${propertyIndent}"${key}": ${valueText}`;
439
-
440
- const before = text.slice(0, objRange.start + 1);
441
- const inside = text.slice(objRange.start + 1, objRange.end - 1);
442
- const after = text.slice(objRange.end - 1);
443
-
444
- const contentIndex = skipWhitespaceAndComments(text, objRange.start + 1, objRange.end - 1);
445
- const hasEntries = contentIndex < objRange.end - 1;
446
- if (!hasEntries) {
447
- return `${before}${newline}${entry}${newline}${objectIndent}}${after.slice(1)}`;
448
- }
449
-
450
- const insideEndIndex = objRange.start + 1 + inside.length;
451
- let insertPoint = insideEndIndex;
452
- let j = objRange.end - 2;
453
- while (j > objRange.start && /\s/.test(text[j])) j -= 1;
454
- const needsComma = text[j] !== ',';
455
- const comma = needsComma ? ',' : '';
456
-
457
- return (
458
- text.slice(0, insertPoint) +
459
- comma +
460
- newline +
461
- entry +
462
- newline +
463
- objectIndent +
464
- text.slice(objRange.end - 1)
465
- );
466
- }
467
-
468
- function replaceRange(text, start, end, replacement) {
469
- return text.slice(0, start) + replacement + text.slice(end);
470
- }
471
-
472
- function resolveContainer(text, rootRange, preferredContainerKey = 'mcpServers') {
473
- const baseContainers = [
474
- { type: 'key', key: 'mcpServers' },
475
- { type: 'key', key: 'servers' },
476
- { type: 'key', key: 'cline.mcpServers' },
477
- { type: 'nested', key: 'cline', child: 'mcpServers' },
478
- ];
479
-
480
- const preferredIndex = baseContainers.findIndex(
481
- (candidate) => candidate.type === 'key' && candidate.key === preferredContainerKey
482
- );
483
- if (preferredIndex > 0) {
484
- const [preferred] = baseContainers.splice(preferredIndex, 1);
485
- baseContainers.unshift(preferred);
486
- }
487
-
488
- for (const candidate of baseContainers) {
489
- if (candidate.type === 'key') {
490
- const entry = findPropertyValueRange(text, rootRange, candidate.key);
491
- if (entry) {
492
- return { entry, containerKey: candidate.key };
493
- }
494
- continue;
495
- }
496
- if (candidate.type === 'nested') {
497
- const parent = findPropertyValueRange(text, rootRange, candidate.key);
498
- if (!parent) continue;
499
- const parentObj = findObjectRangeFromValue(text, parent.valueStart, parent.valueEnd);
500
- if (!parentObj) {
501
- return {
502
- entry: parent,
503
- containerKey: candidate.key,
504
- needsObjectReplace: true,
505
- childKey: candidate.child,
506
- };
507
- }
508
- const child = findPropertyValueRange(text, parentObj, candidate.child);
509
- if (child) {
510
- return {
511
- entry: child,
512
- containerKey: candidate.child,
513
- parentRange: parentObj,
514
- parentEntry: parent,
515
- };
516
- }
517
- return {
518
- entry: null,
519
- containerKey: candidate.child,
520
- parentRange: parentObj,
521
- parentEntry: parent,
522
- };
523
- }
524
- }
525
-
526
- return null;
527
- }
528
-
529
- export function upsertMcpServerEntryInText(
530
- text,
531
- serverName,
532
- serverConfig,
533
- preferredContainerKey = 'mcpServers'
534
- ) {
535
- const newline = detectNewline(text);
536
- const indentUnit = detectIndentUnit(text);
537
- const trimmed = text.trim();
538
-
539
- if (!trimmed) {
540
- const payload = {
541
- [preferredContainerKey]: {
542
- [serverName]: serverConfig,
543
- },
544
- };
545
- return JSON.stringify(payload, null, indentUnit) + newline;
546
- }
547
-
548
- const rootRange = findRootObjectRange(text);
549
- if (!rootRange) {
550
- return null;
551
- }
552
-
553
- const container = resolveContainer(text, rootRange, preferredContainerKey);
554
-
555
- if (!container) {
556
- const objectIndent = getLineIndent(text, rootRange.start);
557
- const propertyIndent = `${objectIndent}${indentUnit}`;
558
- const valueText = formatJsonValue(
559
- { [serverName]: serverConfig },
560
- indentUnit,
561
- propertyIndent,
562
- newline
563
- );
564
- return insertPropertyIntoObject(
565
- text,
566
- rootRange,
567
- preferredContainerKey,
568
- valueText,
569
- indentUnit,
570
- newline
571
- );
572
- }
573
-
574
- if (container.needsObjectReplace) {
575
- const replacementValue = container.childKey
576
- ? { [container.childKey]: { [serverName]: serverConfig } }
577
- : { [serverName]: serverConfig };
578
- const parentIndent = getLineIndent(text, container.entry.keyStart);
579
- const valueText = formatJsonValue(replacementValue, indentUnit, parentIndent, newline);
580
- return replaceRange(text, container.entry.valueStart, container.entry.valueEnd, valueText);
581
- }
582
-
583
- if (container.parentRange && !container.entry) {
584
- const propertyIndent = `${getLineIndent(text, container.parentRange.start)}${indentUnit}`;
585
- const valueText = formatJsonValue(
586
- { [serverName]: serverConfig },
587
- indentUnit,
588
- propertyIndent,
589
- newline
590
- );
591
- return insertPropertyIntoObject(
592
- text,
593
- container.parentRange,
594
- container.containerKey,
595
- valueText,
596
- indentUnit,
597
- newline
598
- );
599
- }
600
-
601
- if (!container.entry) {
602
- return null;
603
- }
604
-
605
- const containerObject = findObjectRangeFromValue(
606
- text,
607
- container.entry.valueStart,
608
- container.entry.valueEnd
609
- );
610
- if (!containerObject) {
611
- const parentIndent = getLineIndent(text, container.entry.keyStart);
612
- const valueText = formatJsonValue(
613
- { [serverName]: serverConfig },
614
- indentUnit,
615
- parentIndent,
616
- newline
617
- );
618
- return replaceRange(text, container.entry.valueStart, container.entry.valueEnd, valueText);
619
- }
620
-
621
- const existingEntry = findPropertyValueRange(text, containerObject, serverName);
622
- if (existingEntry) {
623
- const entryIndent = getLineIndent(text, existingEntry.keyStart);
624
- const valueText = formatJsonValue(serverConfig, indentUnit, entryIndent, newline);
625
- return replaceRange(text, existingEntry.valueStart, existingEntry.valueEnd, valueText);
626
- }
627
-
628
- const objectIndent = getLineIndent(text, containerObject.start);
629
- const propertyIndent = `${objectIndent}${indentUnit}`;
630
- const valueText = formatJsonValue(serverConfig, indentUnit, propertyIndent, newline);
631
- return insertPropertyIntoObject(
632
- text,
633
- containerObject,
634
- serverName,
635
- valueText,
636
- indentUnit,
637
- newline
638
- );
639
- }
640
-
641
- export function findMcpServerEntry(config, serverName) {
642
- if (!config || typeof config !== 'object') return null;
643
- if (config.mcpServers && config.mcpServers[serverName]) {
644
- return { containerKey: 'mcpServers', entry: config.mcpServers[serverName] };
645
- }
646
- if (config.servers && config.servers[serverName]) {
647
- return { containerKey: 'servers', entry: config.servers[serverName] };
648
- }
649
- if (config['cline.mcpServers'] && config['cline.mcpServers'][serverName]) {
650
- return {
651
- containerKey: 'cline.mcpServers',
652
- entry: config['cline.mcpServers'][serverName],
653
- };
654
- }
655
- if (config.cline && config.cline.mcpServers && config.cline.mcpServers[serverName]) {
656
- return {
657
- containerKey: 'cline.mcpServers',
658
- entry: config.cline.mcpServers[serverName],
659
- };
660
- }
661
- return null;
662
- }
663
-
664
- function formatTomlString(value) {
665
- return JSON.stringify(String(value));
666
- }
667
-
668
- function formatTomlArray(values) {
669
- const list = Array.isArray(values) ? values : [];
670
- return `[${list.map((value) => formatTomlString(value)).join(', ')}]`;
671
- }
672
-
673
- function formatTomlMcpSection(serverName, serverConfig, newline) {
674
- const lines = [`[mcp_servers.${serverName}]`];
675
- if (serverConfig.command !== undefined) {
676
- lines.push(`command = ${formatTomlString(serverConfig.command)}`);
677
- }
678
- if (serverConfig.args !== undefined) {
679
- lines.push(`args = ${formatTomlArray(serverConfig.args)}`);
680
- }
681
- if (serverConfig.disabled !== undefined) {
682
- lines.push(`disabled = ${serverConfig.disabled ? 'true' : 'false'}`);
683
- }
684
- return lines.join(newline);
685
- }
686
-
687
- function findTomlSectionRange(source, sectionName) {
688
- const headerRegex = /^\s*\[([^\]\r\n]+)\]\s*$/gm;
689
- let start = -1;
690
- let end = source.length;
691
- let match;
692
-
693
- while ((match = headerRegex.exec(source)) !== null) {
694
- const currentSection = String(match[1] || '').trim();
695
- if (start === -1) {
696
- if (currentSection === sectionName) {
697
- start = match.index;
698
- }
699
- continue;
700
- }
701
-
702
- end = match.index;
703
- break;
704
- }
705
-
706
- if (start === -1) {
707
- return null;
708
- }
709
-
710
- return { start, end };
711
- }
712
-
713
- export function upsertMcpServerEntryInToml(text, serverName, serverConfig) {
714
- const source = String(text || '');
715
- const newline = detectNewline(source || '\n');
716
- const section = formatTomlMcpSection(serverName, serverConfig, newline);
717
- const sectionName = `mcp_servers.${serverName}`;
718
- const range = findTomlSectionRange(source, sectionName);
719
-
720
- if (!source.trim()) {
721
- return `${section}${newline}`;
722
- }
723
-
724
- if (range) {
725
- const before = source.slice(0, range.start);
726
- const after = source.slice(range.end).replace(/^\s*\r?\n?/, '');
727
- const normalizedBefore =
728
- before.endsWith('\n') || before.endsWith('\r') || !before ? before : `${before}${newline}`;
729
- const between = after ? newline : '';
730
- return `${normalizedBefore}${section}${between}${after}`;
731
- }
732
-
733
- const withTrailingNewline = source.endsWith('\n') || source.endsWith('\r') ? source : `${source}${newline}`;
734
- return `${withTrailingNewline}${newline}${section}${newline}`;
735
- }
736
-
737
- export function setMcpServerDisabledInToml(text, serverName, disabled) {
738
- const source = String(text || '');
739
- const sectionName = `mcp_servers.${serverName}`;
740
- const range = findTomlSectionRange(source, sectionName);
741
-
742
- if (!range) {
743
- return source;
744
- }
745
-
746
- const sectionBlock = source.slice(range.start, range.end);
747
- const newline = detectNewline(sectionBlock || '\n');
748
- const disabledLine = `disabled = ${disabled ? 'true' : 'false'}`;
749
- const updatedSection = /^\s*disabled\s*=.*$/m.test(sectionBlock)
750
- ? sectionBlock.replace(/^\s*disabled\s*=.*$/m, disabledLine)
751
- : `${sectionBlock.trimEnd()}${newline}${disabledLine}${newline}`;
752
-
753
- return `${source.slice(0, range.start)}${updatedSection}${source.slice(range.end)}`;
754
- }
1
+ function detectNewline(text) {
2
+ return text.includes('\r\n') ? '\r\n' : '\n';
3
+ }
4
+
5
+ function detectIndentUnit(text) {
6
+ const lines = text.split(/\r?\n/);
7
+ for (const line of lines) {
8
+ const match = line.match(/^(\s+)"/);
9
+ if (match) return match[1];
10
+ }
11
+ return ' ';
12
+ }
13
+
14
+ function getLineIndent(text, index) {
15
+ const lineStart = Math.max(text.lastIndexOf('\n', index - 1), text.lastIndexOf('\r', index - 1));
16
+ const start = lineStart === -1 ? 0 : lineStart + 1;
17
+ let i = start;
18
+ while (i < text.length && (text[i] === ' ' || text[i] === '\t')) i += 1;
19
+ return text.slice(start, i);
20
+ }
21
+
22
+ function stripJsonComments(text) {
23
+ let out = '';
24
+ let inString = false;
25
+ let escape = false;
26
+ let inLine = false;
27
+ let inBlock = false;
28
+
29
+ for (let i = 0; i < text.length; i += 1) {
30
+ const ch = text[i];
31
+ const next = text[i + 1];
32
+
33
+ if (inLine) {
34
+ if (ch === '\n') {
35
+ inLine = false;
36
+ out += ch;
37
+ }
38
+ continue;
39
+ }
40
+
41
+ if (inBlock) {
42
+ if (ch === '*' && next === '/') {
43
+ inBlock = false;
44
+ i += 1;
45
+ }
46
+ continue;
47
+ }
48
+
49
+ if (inString) {
50
+ out += ch;
51
+ if (escape) {
52
+ escape = false;
53
+ } else if (ch === '\\') {
54
+ escape = true;
55
+ } else if (ch === '"') {
56
+ inString = false;
57
+ }
58
+ continue;
59
+ }
60
+
61
+ if (ch === '/' && next === '/') {
62
+ inLine = true;
63
+ i += 1;
64
+ continue;
65
+ }
66
+ if (ch === '/' && next === '*') {
67
+ inBlock = true;
68
+ i += 1;
69
+ continue;
70
+ }
71
+
72
+ if (ch === '"') {
73
+ inString = true;
74
+ }
75
+ out += ch;
76
+ }
77
+
78
+ return out;
79
+ }
80
+
81
+ function stripTrailingCommas(text) {
82
+ let out = '';
83
+ let inString = false;
84
+ let escape = false;
85
+
86
+ for (let i = 0; i < text.length; i += 1) {
87
+ const ch = text[i];
88
+
89
+ if (inString) {
90
+ out += ch;
91
+ if (escape) {
92
+ escape = false;
93
+ } else if (ch === '\\') {
94
+ escape = true;
95
+ } else if (ch === '"') {
96
+ inString = false;
97
+ }
98
+ continue;
99
+ }
100
+
101
+ if (ch === '"') {
102
+ inString = true;
103
+ out += ch;
104
+ continue;
105
+ }
106
+
107
+ if (ch === ',') {
108
+ let j = i + 1;
109
+ while (j < text.length && /\s/.test(text[j])) j += 1;
110
+ if (text[j] === '}' || text[j] === ']') {
111
+ continue;
112
+ }
113
+ }
114
+
115
+ out += ch;
116
+ }
117
+
118
+ return out;
119
+ }
120
+
121
+ export function parseJsonc(text) {
122
+ const cleaned = stripTrailingCommas(stripJsonComments(text));
123
+ try {
124
+ return JSON.parse(cleaned);
125
+ } catch {
126
+ return null;
127
+ }
128
+ }
129
+
130
+ function skipWhitespaceAndComments(text, start, end) {
131
+ let i = start;
132
+ while (i < end) {
133
+ const ch = text[i];
134
+ const next = text[i + 1];
135
+ if (ch === ' ' || ch === '\t' || ch === '\r' || ch === '\n') {
136
+ i += 1;
137
+ continue;
138
+ }
139
+ if (ch === '/' && next === '/') {
140
+ i += 2;
141
+ while (i < end && text[i] !== '\n') i += 1;
142
+ continue;
143
+ }
144
+ if (ch === '/' && next === '*') {
145
+ i += 2;
146
+ while (i < end - 1 && !(text[i] === '*' && text[i + 1] === '/')) i += 1;
147
+ i += 2;
148
+ continue;
149
+ }
150
+ break;
151
+ }
152
+ return i;
153
+ }
154
+
155
+ function readString(text, start, end) {
156
+ let i = start + 1;
157
+ let value = '';
158
+ let escape = false;
159
+ while (i < end) {
160
+ const ch = text[i];
161
+ if (escape) {
162
+ value += ch;
163
+ escape = false;
164
+ } else if (ch === '\\') {
165
+ value += ch;
166
+ escape = true;
167
+ } else if (ch === '"') {
168
+ return { value, end: i + 1 };
169
+ } else {
170
+ value += ch;
171
+ }
172
+ i += 1;
173
+ }
174
+ return { value, end: i };
175
+ }
176
+
177
+ function scanComposite(text, start, end, openChar, closeChar) {
178
+ let depth = 0;
179
+ let inString = false;
180
+ let escape = false;
181
+ let inLine = false;
182
+ let inBlock = false;
183
+
184
+ for (let i = start; i < end; i += 1) {
185
+ const ch = text[i];
186
+ const next = text[i + 1];
187
+
188
+ if (inLine) {
189
+ if (ch === '\n') inLine = false;
190
+ continue;
191
+ }
192
+ if (inBlock) {
193
+ if (ch === '*' && next === '/') {
194
+ inBlock = false;
195
+ i += 1;
196
+ }
197
+ continue;
198
+ }
199
+ if (inString) {
200
+ if (escape) {
201
+ escape = false;
202
+ } else if (ch === '\\') {
203
+ escape = true;
204
+ } else if (ch === '"') {
205
+ inString = false;
206
+ }
207
+ continue;
208
+ }
209
+
210
+ if (ch === '/' && next === '/') {
211
+ inLine = true;
212
+ i += 1;
213
+ continue;
214
+ }
215
+ if (ch === '/' && next === '*') {
216
+ inBlock = true;
217
+ i += 1;
218
+ continue;
219
+ }
220
+
221
+ if (ch === '"') {
222
+ inString = true;
223
+ continue;
224
+ }
225
+
226
+ if (ch === openChar) {
227
+ depth += 1;
228
+ continue;
229
+ }
230
+ if (ch === closeChar) {
231
+ depth -= 1;
232
+ if (depth === 0) return i + 1;
233
+ }
234
+ }
235
+ return end;
236
+ }
237
+
238
+ function scanValue(text, start, end) {
239
+ let i = skipWhitespaceAndComments(text, start, end);
240
+ const valueStart = i;
241
+ if (i >= end) {
242
+ return { valueStart: end, valueEnd: end, valueEndWithComma: end };
243
+ }
244
+
245
+ const ch = text[i];
246
+ let valueEnd = i;
247
+
248
+ if (ch === '{') {
249
+ valueEnd = scanComposite(text, i, end, '{', '}');
250
+ } else if (ch === '[') {
251
+ valueEnd = scanComposite(text, i, end, '[', ']');
252
+ } else if (ch === '"') {
253
+ const result = readString(text, i, end);
254
+ valueEnd = result.end;
255
+ } else {
256
+ let inString = false;
257
+ let escape = false;
258
+ let inLine = false;
259
+ let inBlock = false;
260
+ for (; i < end; i += 1) {
261
+ const c = text[i];
262
+ const next = text[i + 1];
263
+ if (inLine) {
264
+ if (c === '\n') inLine = false;
265
+ continue;
266
+ }
267
+ if (inBlock) {
268
+ if (c === '*' && next === '/') {
269
+ inBlock = false;
270
+ i += 1;
271
+ }
272
+ continue;
273
+ }
274
+ if (inString) {
275
+ if (escape) {
276
+ escape = false;
277
+ } else if (c === '\\') {
278
+ escape = true;
279
+ } else if (c === '"') {
280
+ inString = false;
281
+ }
282
+ continue;
283
+ }
284
+ if (c === '/' && next === '/') {
285
+ inLine = true;
286
+ i += 1;
287
+ continue;
288
+ }
289
+ if (c === '/' && next === '*') {
290
+ inBlock = true;
291
+ i += 1;
292
+ continue;
293
+ }
294
+ if (c === '"') {
295
+ inString = true;
296
+ continue;
297
+ }
298
+ if (c === ',' || c === '}' || c === ']') {
299
+ break;
300
+ }
301
+ }
302
+ valueEnd = i;
303
+ }
304
+
305
+ let valueEndWithComma = valueEnd;
306
+ let j = skipWhitespaceAndComments(text, valueEnd, end);
307
+ if (text[j] === ',') {
308
+ valueEndWithComma = j + 1;
309
+ }
310
+
311
+ return { valueStart, valueEnd, valueEndWithComma };
312
+ }
313
+
314
+ function findRootObjectRange(text) {
315
+ let inString = false;
316
+ let escape = false;
317
+ let inLine = false;
318
+ let inBlock = false;
319
+
320
+ for (let i = 0; i < text.length; i += 1) {
321
+ const ch = text[i];
322
+ const next = text[i + 1];
323
+
324
+ if (inLine) {
325
+ if (ch === '\n') inLine = false;
326
+ continue;
327
+ }
328
+ if (inBlock) {
329
+ if (ch === '*' && next === '/') {
330
+ inBlock = false;
331
+ i += 1;
332
+ }
333
+ continue;
334
+ }
335
+ if (inString) {
336
+ if (escape) {
337
+ escape = false;
338
+ } else if (ch === '\\') {
339
+ escape = true;
340
+ } else if (ch === '"') {
341
+ inString = false;
342
+ }
343
+ continue;
344
+ }
345
+
346
+ if (ch === '/' && next === '/') {
347
+ inLine = true;
348
+ i += 1;
349
+ continue;
350
+ }
351
+ if (ch === '/' && next === '*') {
352
+ inBlock = true;
353
+ i += 1;
354
+ continue;
355
+ }
356
+
357
+ if (ch === '"') {
358
+ inString = true;
359
+ continue;
360
+ }
361
+
362
+ if (ch === '{') {
363
+ const end = scanComposite(text, i, text.length, '{', '}');
364
+ if (end > i) return { start: i, end };
365
+ }
366
+ }
367
+
368
+ return null;
369
+ }
370
+
371
+ function findPropertyValueRange(text, objRange, key) {
372
+ let i = objRange.start + 1;
373
+ const end = objRange.end - 1;
374
+ let expectKey = true;
375
+
376
+ while (i < end) {
377
+ i = skipWhitespaceAndComments(text, i, end);
378
+ if (i >= end) break;
379
+ const ch = text[i];
380
+ if (ch === '}') break;
381
+ if (ch === ',') {
382
+ expectKey = true;
383
+ i += 1;
384
+ continue;
385
+ }
386
+ if (!expectKey) {
387
+ const valueInfo = scanValue(text, i, end);
388
+ i = valueInfo.valueEndWithComma;
389
+ expectKey = true;
390
+ continue;
391
+ }
392
+ if (ch !== '"') {
393
+ i += 1;
394
+ continue;
395
+ }
396
+ const keyResult = readString(text, i, end);
397
+ const keyName = keyResult.value;
398
+ const keyStart = i;
399
+ const keyEnd = keyResult.end;
400
+ let afterKey = skipWhitespaceAndComments(text, keyEnd, end);
401
+ if (text[afterKey] !== ':') {
402
+ i = keyEnd;
403
+ continue;
404
+ }
405
+ const valueInfo = scanValue(text, afterKey + 1, end);
406
+ if (keyName === key) {
407
+ return {
408
+ keyStart,
409
+ keyEnd,
410
+ valueStart: valueInfo.valueStart,
411
+ valueEnd: valueInfo.valueEnd,
412
+ valueEndWithComma: valueInfo.valueEndWithComma,
413
+ };
414
+ }
415
+ i = valueInfo.valueEndWithComma;
416
+ expectKey = true;
417
+ }
418
+
419
+ return null;
420
+ }
421
+
422
+ function formatJsonValue(value, indentUnit, parentIndent, newline) {
423
+ const raw = JSON.stringify(value, null, indentUnit);
424
+ if (!raw.includes('\n')) return raw;
425
+ return raw.replace(/\n/g, `${newline}${parentIndent}`);
426
+ }
427
+
428
+ function findObjectRangeFromValue(text, valueStart, valueEnd) {
429
+ const start = skipWhitespaceAndComments(text, valueStart, valueEnd);
430
+ if (text[start] !== '{') return null;
431
+ const end = scanComposite(text, start, valueEnd, '{', '}');
432
+ return { start, end };
433
+ }
434
+
435
+ function insertPropertyIntoObject(text, objRange, key, valueText, indentUnit, newline) {
436
+ const objectIndent = getLineIndent(text, objRange.start);
437
+ const propertyIndent = `${objectIndent}${indentUnit}`;
438
+ const entry = `${propertyIndent}"${key}": ${valueText}`;
439
+
440
+ const before = text.slice(0, objRange.start + 1);
441
+ const inside = text.slice(objRange.start + 1, objRange.end - 1);
442
+ const after = text.slice(objRange.end - 1);
443
+
444
+ const contentIndex = skipWhitespaceAndComments(text, objRange.start + 1, objRange.end - 1);
445
+ const hasEntries = contentIndex < objRange.end - 1;
446
+ if (!hasEntries) {
447
+ return `${before}${newline}${entry}${newline}${objectIndent}}${after.slice(1)}`;
448
+ }
449
+
450
+ const insideEndIndex = objRange.start + 1 + inside.length;
451
+ let insertPoint = insideEndIndex;
452
+ let j = objRange.end - 2;
453
+ while (j > objRange.start && /\s/.test(text[j])) j -= 1;
454
+ const needsComma = text[j] !== ',';
455
+ const comma = needsComma ? ',' : '';
456
+
457
+ return (
458
+ text.slice(0, insertPoint) +
459
+ comma +
460
+ newline +
461
+ entry +
462
+ newline +
463
+ objectIndent +
464
+ text.slice(objRange.end - 1)
465
+ );
466
+ }
467
+
468
+ function replaceRange(text, start, end, replacement) {
469
+ return text.slice(0, start) + replacement + text.slice(end);
470
+ }
471
+
472
+ function resolveContainer(text, rootRange, preferredContainerKey = 'mcpServers') {
473
+ const baseContainers = [
474
+ { type: 'key', key: 'mcpServers' },
475
+ { type: 'key', key: 'servers' },
476
+ { type: 'key', key: 'cline.mcpServers' },
477
+ { type: 'nested', key: 'cline', child: 'mcpServers' },
478
+ ];
479
+
480
+ const preferredIndex = baseContainers.findIndex(
481
+ (candidate) => candidate.type === 'key' && candidate.key === preferredContainerKey
482
+ );
483
+ if (preferredIndex > 0) {
484
+ const [preferred] = baseContainers.splice(preferredIndex, 1);
485
+ baseContainers.unshift(preferred);
486
+ }
487
+
488
+ for (const candidate of baseContainers) {
489
+ if (candidate.type === 'key') {
490
+ const entry = findPropertyValueRange(text, rootRange, candidate.key);
491
+ if (entry) {
492
+ return { entry, containerKey: candidate.key };
493
+ }
494
+ continue;
495
+ }
496
+ if (candidate.type === 'nested') {
497
+ const parent = findPropertyValueRange(text, rootRange, candidate.key);
498
+ if (!parent) continue;
499
+ const parentObj = findObjectRangeFromValue(text, parent.valueStart, parent.valueEnd);
500
+ if (!parentObj) {
501
+ return {
502
+ entry: parent,
503
+ containerKey: candidate.key,
504
+ needsObjectReplace: true,
505
+ childKey: candidate.child,
506
+ };
507
+ }
508
+ const child = findPropertyValueRange(text, parentObj, candidate.child);
509
+ if (child) {
510
+ return {
511
+ entry: child,
512
+ containerKey: candidate.child,
513
+ parentRange: parentObj,
514
+ parentEntry: parent,
515
+ };
516
+ }
517
+ return {
518
+ entry: null,
519
+ containerKey: candidate.child,
520
+ parentRange: parentObj,
521
+ parentEntry: parent,
522
+ };
523
+ }
524
+ }
525
+
526
+ return null;
527
+ }
528
+
529
+ export function upsertMcpServerEntryInText(
530
+ text,
531
+ serverName,
532
+ serverConfig,
533
+ preferredContainerKey = 'mcpServers'
534
+ ) {
535
+ const newline = detectNewline(text);
536
+ const indentUnit = detectIndentUnit(text);
537
+ const trimmed = text.trim();
538
+
539
+ if (!trimmed) {
540
+ const payload = {
541
+ [preferredContainerKey]: {
542
+ [serverName]: serverConfig,
543
+ },
544
+ };
545
+ return JSON.stringify(payload, null, indentUnit) + newline;
546
+ }
547
+
548
+ const rootRange = findRootObjectRange(text);
549
+ if (!rootRange) {
550
+ return null;
551
+ }
552
+
553
+ const container = resolveContainer(text, rootRange, preferredContainerKey);
554
+
555
+ if (!container) {
556
+ const objectIndent = getLineIndent(text, rootRange.start);
557
+ const propertyIndent = `${objectIndent}${indentUnit}`;
558
+ const valueText = formatJsonValue(
559
+ { [serverName]: serverConfig },
560
+ indentUnit,
561
+ propertyIndent,
562
+ newline
563
+ );
564
+ return insertPropertyIntoObject(
565
+ text,
566
+ rootRange,
567
+ preferredContainerKey,
568
+ valueText,
569
+ indentUnit,
570
+ newline
571
+ );
572
+ }
573
+
574
+ if (container.needsObjectReplace) {
575
+ const replacementValue = container.childKey
576
+ ? { [container.childKey]: { [serverName]: serverConfig } }
577
+ : { [serverName]: serverConfig };
578
+ const parentIndent = getLineIndent(text, container.entry.keyStart);
579
+ const valueText = formatJsonValue(replacementValue, indentUnit, parentIndent, newline);
580
+ return replaceRange(text, container.entry.valueStart, container.entry.valueEnd, valueText);
581
+ }
582
+
583
+ if (container.parentRange && !container.entry) {
584
+ const propertyIndent = `${getLineIndent(text, container.parentRange.start)}${indentUnit}`;
585
+ const valueText = formatJsonValue(
586
+ { [serverName]: serverConfig },
587
+ indentUnit,
588
+ propertyIndent,
589
+ newline
590
+ );
591
+ return insertPropertyIntoObject(
592
+ text,
593
+ container.parentRange,
594
+ container.containerKey,
595
+ valueText,
596
+ indentUnit,
597
+ newline
598
+ );
599
+ }
600
+
601
+ if (!container.entry) {
602
+ return null;
603
+ }
604
+
605
+ const containerObject = findObjectRangeFromValue(
606
+ text,
607
+ container.entry.valueStart,
608
+ container.entry.valueEnd
609
+ );
610
+ if (!containerObject) {
611
+ const parentIndent = getLineIndent(text, container.entry.keyStart);
612
+ const valueText = formatJsonValue(
613
+ { [serverName]: serverConfig },
614
+ indentUnit,
615
+ parentIndent,
616
+ newline
617
+ );
618
+ return replaceRange(text, container.entry.valueStart, container.entry.valueEnd, valueText);
619
+ }
620
+
621
+ const existingEntry = findPropertyValueRange(text, containerObject, serverName);
622
+ if (existingEntry) {
623
+ const entryIndent = getLineIndent(text, existingEntry.keyStart);
624
+ const valueText = formatJsonValue(serverConfig, indentUnit, entryIndent, newline);
625
+ return replaceRange(text, existingEntry.valueStart, existingEntry.valueEnd, valueText);
626
+ }
627
+
628
+ const objectIndent = getLineIndent(text, containerObject.start);
629
+ const propertyIndent = `${objectIndent}${indentUnit}`;
630
+ const valueText = formatJsonValue(serverConfig, indentUnit, propertyIndent, newline);
631
+ return insertPropertyIntoObject(
632
+ text,
633
+ containerObject,
634
+ serverName,
635
+ valueText,
636
+ indentUnit,
637
+ newline
638
+ );
639
+ }
640
+
641
+ export function findMcpServerEntry(config, serverName) {
642
+ if (!config || typeof config !== 'object') return null;
643
+ if (config.mcpServers && config.mcpServers[serverName]) {
644
+ return { containerKey: 'mcpServers', entry: config.mcpServers[serverName] };
645
+ }
646
+ if (config.servers && config.servers[serverName]) {
647
+ return { containerKey: 'servers', entry: config.servers[serverName] };
648
+ }
649
+ if (config['cline.mcpServers'] && config['cline.mcpServers'][serverName]) {
650
+ return {
651
+ containerKey: 'cline.mcpServers',
652
+ entry: config['cline.mcpServers'][serverName],
653
+ };
654
+ }
655
+ if (config.cline && config.cline.mcpServers && config.cline.mcpServers[serverName]) {
656
+ return {
657
+ containerKey: 'cline.mcpServers',
658
+ entry: config.cline.mcpServers[serverName],
659
+ };
660
+ }
661
+ return null;
662
+ }
663
+
664
+ function formatTomlString(value) {
665
+ return JSON.stringify(String(value));
666
+ }
667
+
668
+ function formatTomlArray(values) {
669
+ const list = Array.isArray(values) ? values : [];
670
+ return `[${list.map((value) => formatTomlString(value)).join(', ')}]`;
671
+ }
672
+
673
+ function formatTomlMcpSection(serverName, serverConfig, newline) {
674
+ const lines = [`[mcp_servers.${serverName}]`];
675
+ if (serverConfig.command !== undefined) {
676
+ lines.push(`command = ${formatTomlString(serverConfig.command)}`);
677
+ }
678
+ if (serverConfig.args !== undefined) {
679
+ lines.push(`args = ${formatTomlArray(serverConfig.args)}`);
680
+ }
681
+ if (serverConfig.disabled !== undefined) {
682
+ lines.push(`disabled = ${serverConfig.disabled ? 'true' : 'false'}`);
683
+ }
684
+ return lines.join(newline);
685
+ }
686
+
687
+ function findTomlSectionRange(source, sectionName) {
688
+ const headerRegex = /^\s*\[([^\]\r\n]+)\]\s*$/gm;
689
+ let start = -1;
690
+ let end = source.length;
691
+ let match;
692
+
693
+ while ((match = headerRegex.exec(source)) !== null) {
694
+ const currentSection = String(match[1] || '').trim();
695
+ if (start === -1) {
696
+ if (currentSection === sectionName) {
697
+ start = match.index;
698
+ }
699
+ continue;
700
+ }
701
+
702
+ end = match.index;
703
+ break;
704
+ }
705
+
706
+ if (start === -1) {
707
+ return null;
708
+ }
709
+
710
+ return { start, end };
711
+ }
712
+
713
+ export function upsertMcpServerEntryInToml(text, serverName, serverConfig) {
714
+ const source = String(text || '');
715
+ const newline = detectNewline(source || '\n');
716
+ const section = formatTomlMcpSection(serverName, serverConfig, newline);
717
+ const sectionName = `mcp_servers.${serverName}`;
718
+ const range = findTomlSectionRange(source, sectionName);
719
+
720
+ if (!source.trim()) {
721
+ return `${section}${newline}`;
722
+ }
723
+
724
+ if (range) {
725
+ const before = source.slice(0, range.start);
726
+ const after = source.slice(range.end).replace(/^\s*\r?\n?/, '');
727
+ const normalizedBefore =
728
+ before.endsWith('\n') || before.endsWith('\r') || !before ? before : `${before}${newline}`;
729
+ const between = after ? newline : '';
730
+ return `${normalizedBefore}${section}${between}${after}`;
731
+ }
732
+
733
+ const withTrailingNewline = source.endsWith('\n') || source.endsWith('\r') ? source : `${source}${newline}`;
734
+ return `${withTrailingNewline}${newline}${section}${newline}`;
735
+ }
736
+
737
+ export function setMcpServerDisabledInToml(text, serverName, disabled) {
738
+ const source = String(text || '');
739
+ const sectionName = `mcp_servers.${serverName}`;
740
+ const range = findTomlSectionRange(source, sectionName);
741
+
742
+ if (!range) {
743
+ return source;
744
+ }
745
+
746
+ const sectionBlock = source.slice(range.start, range.end);
747
+ const newline = detectNewline(sectionBlock || '\n');
748
+ const disabledLine = `disabled = ${disabled ? 'true' : 'false'}`;
749
+ const updatedSection = /^\s*disabled\s*=.*$/m.test(sectionBlock)
750
+ ? sectionBlock.replace(/^\s*disabled\s*=.*$/m, disabledLine)
751
+ : `${sectionBlock.trimEnd()}${newline}${disabledLine}${newline}`;
752
+
753
+ return `${source.slice(0, range.start)}${updatedSection}${source.slice(range.end)}`;
754
+ }