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 +15 -2
- package/package.json +1 -1
- package/src/config.js +24 -9
- package/src/providers/copilot.js +97 -28
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
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'
|
|
441
|
-
|
|
442
|
-
|
|
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
|
-
|
|
464
|
+
// Normalize Git Bash paths (/c/Users/... -> C:\Users\...)
|
|
465
|
+
config.server.client_cwd = normalizeGitBashPath(detectedCwd);
|
|
451
466
|
configLogger.debug(
|
|
452
|
-
`
|
|
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(
|
package/src/providers/copilot.js
CHANGED
|
@@ -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
|
-
|
|
546
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|