converse-mcp-server 2.19.2 → 2.20.0

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/bin/converse.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * Converse MCP Server - CLI Entry Point
@@ -10,6 +10,19 @@ import { fileURLToPath, pathToFileURL } from 'url';
10
10
  import { dirname, join } from 'path';
11
11
  import { createRequire } from 'module';
12
12
 
13
+ // Capture the caller's working directory before we chdir to the package root.
14
+ // This is critical for resolving relative file paths passed by MCP clients.
15
+ // Parse --cwd <path> from CLI args as an explicit override.
16
+ const cwdArgIndex = process.argv.indexOf('--cwd');
17
+ const callerCwd = (cwdArgIndex !== -1 && process.argv[cwdArgIndex + 1])
18
+ ? process.argv[cwdArgIndex + 1]
19
+ : process.cwd();
20
+
21
+ // Expose as env var so config.js can pick it up (only if not already set)
22
+ if (!process.env.CLIENT_CWD) {
23
+ process.env.CLIENT_CWD = callerCwd;
24
+ }
25
+
13
26
  // Get the directory of this script
14
27
  const __filename = fileURLToPath(import.meta.url);
15
28
  const __dirname = dirname(__filename);
@@ -24,7 +37,7 @@ process.chdir(projectRoot);
24
37
  try {
25
38
  const indexPath = join(projectRoot, 'src/index.js');
26
39
  const { main } = await import(pathToFileURL(indexPath).href);
27
-
40
+
28
41
  // The main function will handle all logging appropriately based on transport type
29
42
  await main();
30
43
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "converse-mcp-server",
3
- "version": "2.19.2",
3
+ "version": "2.20.0",
4
4
  "description": "Converse MCP Server - Converse with other LLMs with chat and consensus tools",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/config.js CHANGED
@@ -10,7 +10,7 @@ import dotenv from 'dotenv';
10
10
  import { createLogger, configureLogger } from './utils/logger.js';
11
11
  import { ConfigurationError } from './utils/errorHandler.js';
12
12
  import { fileURLToPath } from 'url';
13
- import { dirname, join } from 'path';
13
+ import { dirname, join, resolve } from 'path';
14
14
  import { readFileSync } from 'fs';
15
15
 
16
16
  // Load environment variables from appropriate .env file
@@ -407,6 +407,23 @@ function validateApiKeyFormat(provider, apiKey) {
407
407
  }
408
408
  }
409
409
 
410
+ /**
411
+ * Normalize Git Bash paths to Windows paths on Windows.
412
+ * Converts /c/Users/... to C:\Users\... so path.resolve() works correctly.
413
+ * On non-Windows platforms, returns the path unchanged.
414
+ */
415
+ function normalizeGitBashPath(inputPath) {
416
+ if (
417
+ process.platform === 'win32' &&
418
+ /^\/[a-zA-Z]\//.test(inputPath)
419
+ ) {
420
+ return resolve(
421
+ inputPath[1].toUpperCase() + ':' + inputPath.slice(2).replace(/\//g, '\\'),
422
+ );
423
+ }
424
+ return inputPath;
425
+ }
426
+
410
427
  /**
411
428
  * Loads and validates complete configuration from environment variables
412
429
  * @returns {Promise<object>} Validated configuration object
@@ -437,19 +454,17 @@ export async function loadConfig() {
437
454
  for (const [key, schema] of Object.entries(CONFIG_SCHEMA.server)) {
438
455
  try {
439
456
  // Special handling for CLIENT_CWD - auto-detect if not explicitly set
440
- if (key === 'CLIENT_CWD' && !process.env[key]) {
441
- // Try to detect the client's working directory from various sources
442
- // When run via npx, INIT_CWD contains the directory where npx was invoked
443
- // PWD is another common variable set to the working directory
444
- // npm_config_local_prefix is set when run via npm/npx
445
- const detectedCwd =
457
+ if (key === 'CLIENT_CWD') {
458
+ const explicitCwd = process.env[key];
459
+ const detectedCwd = explicitCwd ||
446
460
  process.env.INIT_CWD ||
447
461
  process.env.PWD ||
448
462
  process.env.npm_config_local_prefix ||
449
463
  process.cwd();
450
- config.server.client_cwd = detectedCwd;
464
+ // Normalize Git Bash paths (/c/Users/... -> C:\Users\...)
465
+ config.server.client_cwd = normalizeGitBashPath(detectedCwd);
451
466
  configLogger.debug(
452
- `Auto-detected client working directory: ${detectedCwd}`,
467
+ `Client working directory: ${config.server.client_cwd}${explicitCwd ? ' (from CLIENT_CWD)' : ' (auto-detected)'}`,
453
468
  );
454
469
  } else {
455
470
  config.server[key.toLowerCase()] = validateEnvVar(
@@ -54,6 +54,7 @@ const SUPPORTED_MODELS = {
54
54
  supportsImages: false,
55
55
  supportsTemperature: false,
56
56
  supportsWebSearch: false,
57
+ supportsReasoningEffort: true,
57
58
  timeout: 120000,
58
59
  description: 'OpenAI GPT-5 Mini via Copilot subscription',
59
60
  aliases: [],
@@ -67,6 +68,7 @@ const SUPPORTED_MODELS = {
67
68
  supportsImages: false,
68
69
  supportsTemperature: false,
69
70
  supportsWebSearch: false,
71
+ supportsReasoningEffort: true,
70
72
  timeout: 120000,
71
73
  description: 'OpenAI GPT-5.1 via Copilot subscription',
72
74
  aliases: [],
@@ -80,6 +82,7 @@ const SUPPORTED_MODELS = {
80
82
  supportsImages: false,
81
83
  supportsTemperature: false,
82
84
  supportsWebSearch: false,
85
+ supportsReasoningEffort: true,
83
86
  timeout: 300000,
84
87
  description: 'OpenAI GPT-5.1 Codex via Copilot subscription',
85
88
  aliases: [],
@@ -93,6 +96,7 @@ const SUPPORTED_MODELS = {
93
96
  supportsImages: false,
94
97
  supportsTemperature: false,
95
98
  supportsWebSearch: false,
99
+ supportsReasoningEffort: true,
96
100
  timeout: 300000,
97
101
  description: 'OpenAI GPT-5.1 Codex Mini via Copilot subscription',
98
102
  aliases: [],
@@ -106,6 +110,7 @@ const SUPPORTED_MODELS = {
106
110
  supportsImages: false,
107
111
  supportsTemperature: false,
108
112
  supportsWebSearch: false,
113
+ supportsReasoningEffort: true,
109
114
  timeout: 600000,
110
115
  description: 'OpenAI GPT-5.1 Codex Max via Copilot subscription',
111
116
  aliases: [],
@@ -119,6 +124,7 @@ const SUPPORTED_MODELS = {
119
124
  supportsImages: false,
120
125
  supportsTemperature: false,
121
126
  supportsWebSearch: false,
127
+ supportsReasoningEffort: true,
122
128
  timeout: 120000,
123
129
  description: 'OpenAI GPT-5.2 via Copilot subscription',
124
130
  aliases: ['gpt-5'],
@@ -132,6 +138,7 @@ const SUPPORTED_MODELS = {
132
138
  supportsImages: false,
133
139
  supportsTemperature: false,
134
140
  supportsWebSearch: false,
141
+ supportsReasoningEffort: true,
135
142
  timeout: 300000,
136
143
  description: 'OpenAI GPT-5.2 Codex via Copilot subscription',
137
144
  aliases: [],
@@ -145,6 +152,7 @@ const SUPPORTED_MODELS = {
145
152
  supportsImages: false,
146
153
  supportsTemperature: false,
147
154
  supportsWebSearch: false,
155
+ supportsReasoningEffort: true,
148
156
  timeout: 300000,
149
157
  description: 'OpenAI GPT-5.3 Codex via Copilot subscription',
150
158
  aliases: ['codex'],
@@ -451,6 +459,37 @@ function resolveModelAlias(name) {
451
459
  return null;
452
460
  }
453
461
 
462
+ /**
463
+ * Look up model config from SUPPORTED_MODELS by name or alias.
464
+ * Strips copilot: prefix and falls back to the base copilot config.
465
+ */
466
+ function findModelConfig(modelName) {
467
+ if (typeof modelName !== 'string') return null;
468
+
469
+ let name = modelName;
470
+ if (name.toLowerCase().startsWith('copilot:')) {
471
+ name = name.slice('copilot:'.length).trim();
472
+ }
473
+ if (!name) return SUPPORTED_MODELS.copilot;
474
+
475
+ const nameLower = name.toLowerCase();
476
+
477
+ if (SUPPORTED_MODELS[nameLower]) {
478
+ return SUPPORTED_MODELS[nameLower];
479
+ }
480
+
481
+ for (const config of Object.values(SUPPORTED_MODELS)) {
482
+ if (
483
+ config.aliases &&
484
+ config.aliases.some((alias) => alias.toLowerCase() === nameLower)
485
+ ) {
486
+ return config;
487
+ }
488
+ }
489
+
490
+ return null;
491
+ }
492
+
454
493
  /**
455
494
  * Resolve model to pass to SDK session
456
495
  * Precedence: explicit model param > config COPILOT_MODEL > omit (SDK default)
@@ -524,6 +563,25 @@ function mapReasoningEffort(effort) {
524
563
  return mapping[effort] || undefined;
525
564
  }
526
565
 
566
+ /**
567
+ * Check if a model supports reasoning effort via the Copilot SDK's models.list API.
568
+ * Results are cached by the SDK internally.
569
+ * Returns true if supported, false if not, or undefined if the check fails.
570
+ */
571
+ async function checkReasoningSupport(client, modelId) {
572
+ try {
573
+ const models = await client.listModels();
574
+ const match = models.find((m) => m.id === modelId);
575
+ if (match) {
576
+ return match.capabilities?.supports?.reasoningEffort === true;
577
+ }
578
+ return undefined;
579
+ } catch (err) {
580
+ debugLog('[Copilot SDK] Failed to query model capabilities: %s', err.message);
581
+ return undefined;
582
+ }
583
+ }
584
+
527
585
  async function* createStreamingGenerator(client, prompt, options, signal, config) {
528
586
  const { model, timeout = 120000, reasoning_effort } = options;
529
587
 
@@ -542,12 +600,46 @@ async function* createStreamingGenerator(client, prompt, options, signal, config
542
600
  if (reasoning_effort) {
543
601
  const mapped = mapReasoningEffort(reasoning_effort);
544
602
  if (mapped) {
545
- sessionConfig.reasoningEffort = mapped;
546
- debugLog(`[Copilot SDK] Setting reasoningEffort: ${mapped} (from ${reasoning_effort})`);
603
+ const effectiveModel = sessionModel || model;
604
+ const modelDef = findModelConfig(effectiveModel);
605
+
606
+ let supported;
607
+ if (modelDef && modelDef.supportsReasoningEffort !== undefined) {
608
+ // Known model with explicit flag — use it directly (fast path)
609
+ supported = modelDef.supportsReasoningEffort;
610
+ } else {
611
+ // Unknown model or no static flag — query the SDK
612
+ supported = await checkReasoningSupport(client, effectiveModel);
613
+ }
614
+
615
+ if (supported === true) {
616
+ sessionConfig.reasoningEffort = mapped;
617
+ debugLog(`[Copilot SDK] Setting reasoningEffort: ${mapped} (from ${reasoning_effort})`);
618
+ } else if (supported === false) {
619
+ debugLog(`[Copilot SDK] Model "${effectiveModel}" does not support reasoningEffort (ignored)`);
620
+ } else {
621
+ // Could not determine — try optimistically, retry without on failure
622
+ sessionConfig.reasoningEffort = mapped;
623
+ debugLog(`[Copilot SDK] Model "${effectiveModel}" support unknown — trying reasoningEffort: ${mapped}`);
624
+ }
547
625
  }
548
626
  }
549
627
 
550
- const session = await client.createSession(sessionConfig);
628
+ let session;
629
+ try {
630
+ session = await client.createSession(sessionConfig);
631
+ } catch (err) {
632
+ if (
633
+ sessionConfig.reasoningEffort &&
634
+ /does not support reasoning effort/i.test(err.message)
635
+ ) {
636
+ debugLog('[Copilot SDK] Model rejected reasoningEffort — retrying without it');
637
+ delete sessionConfig.reasoningEffort;
638
+ session = await client.createSession(sessionConfig);
639
+ } else {
640
+ throw err;
641
+ }
642
+ }
551
643
 
552
644
  try {
553
645
  yield {
@@ -716,7 +808,7 @@ export const copilotProvider = {
716
808
  const prompt = convertMessagesToPrompt(messages);
717
809
 
718
810
  const sessionModel = resolveSessionModel(model, config);
719
- const modelConfig = SUPPORTED_MODELS.copilot;
811
+ const modelConfig = findModelConfig(sessionModel || model) || SUPPORTED_MODELS.copilot;
720
812
  const invokeOptions = {
721
813
  model,
722
814
  timeout: modelConfig.timeout,
@@ -831,29 +923,6 @@ export const copilotProvider = {
831
923
  },
832
924
 
833
925
  getModelConfig(modelName) {
834
- if (typeof modelName !== 'string') return null;
835
-
836
- let name = modelName;
837
- if (name.toLowerCase().startsWith('copilot:')) {
838
- name = name.slice('copilot:'.length).trim();
839
- }
840
- if (!name) return SUPPORTED_MODELS.copilot;
841
-
842
- const nameLower = name.toLowerCase();
843
-
844
- if (SUPPORTED_MODELS[nameLower]) {
845
- return SUPPORTED_MODELS[nameLower];
846
- }
847
-
848
- for (const config of Object.values(SUPPORTED_MODELS)) {
849
- if (
850
- config.aliases &&
851
- config.aliases.some((alias) => alias.toLowerCase() === nameLower)
852
- ) {
853
- return config;
854
- }
855
- }
856
-
857
- return null;
926
+ return findModelConfig(modelName);
858
927
  },
859
928
  };