converse-mcp-server 2.3.1 → 2.4.1

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 (42) hide show
  1. package/README.md +771 -738
  2. package/docs/API.md +10 -1
  3. package/docs/PROVIDERS.md +8 -4
  4. package/package.json +12 -12
  5. package/src/async/asyncJobStore.js +82 -52
  6. package/src/async/eventBus.js +25 -20
  7. package/src/async/fileCache.js +121 -40
  8. package/src/async/jobRunner.js +65 -39
  9. package/src/async/providerStreamNormalizer.js +203 -117
  10. package/src/config.js +374 -102
  11. package/src/continuationStore.js +32 -24
  12. package/src/index.js +45 -25
  13. package/src/prompts/helpPrompt.js +328 -305
  14. package/src/providers/anthropic.js +303 -119
  15. package/src/providers/codex.js +103 -45
  16. package/src/providers/deepseek.js +24 -8
  17. package/src/providers/google.js +337 -93
  18. package/src/providers/index.js +1 -1
  19. package/src/providers/interface.js +16 -11
  20. package/src/providers/mistral.js +179 -69
  21. package/src/providers/openai-compatible.js +231 -94
  22. package/src/providers/openai.js +1094 -914
  23. package/src/providers/openrouter-endpoints-client.js +220 -216
  24. package/src/providers/openrouter.js +426 -381
  25. package/src/providers/xai.js +153 -56
  26. package/src/resources/helpResource.js +70 -67
  27. package/src/router.js +95 -67
  28. package/src/services/summarizationService.js +51 -24
  29. package/src/systemPrompts.js +89 -89
  30. package/src/tools/cancelJob.js +31 -19
  31. package/src/tools/chat.js +997 -883
  32. package/src/tools/checkStatus.js +86 -65
  33. package/src/tools/consensus.js +400 -234
  34. package/src/tools/index.js +39 -16
  35. package/src/transport/httpTransport.js +82 -55
  36. package/src/utils/contextProcessor.js +54 -37
  37. package/src/utils/errorHandler.js +95 -45
  38. package/src/utils/fileValidator.js +107 -98
  39. package/src/utils/formatStatus.js +122 -64
  40. package/src/utils/logger.js +459 -449
  41. package/src/utils/pathUtils.js +2 -2
  42. package/src/utils/tokenLimiter.js +216 -216
@@ -112,7 +112,7 @@ export function getSpawnOptions(additionalOptions = {}) {
112
112
  const options = {
113
113
  ...additionalOptions,
114
114
  // Ensure proper path resolution on Windows
115
- windowsVerbatimArguments: false
115
+ windowsVerbatimArguments: false,
116
116
  };
117
117
 
118
118
  // On Windows, we might need shell for certain operations
@@ -193,7 +193,7 @@ export function getPlatformName() {
193
193
  const platformMap = {
194
194
  win32: 'Windows',
195
195
  darwin: 'macOS',
196
- linux: 'Linux'
196
+ linux: 'Linux',
197
197
  };
198
198
  return platformMap[platform()] || platform();
199
199
  }
@@ -1,217 +1,217 @@
1
- /**
2
- * Token Limiter Utility
3
- *
4
- * Implements token limiting for MCP tool responses to prevent excessive output.
5
- * Based on the Python implementation's token limiting functionality.
6
- */
7
-
8
- import { createLogger } from './logger.js';
9
-
10
- const logger = createLogger('tokenLimiter');
11
-
12
- /**
13
- * Simple token estimation based on character count
14
- * Rough approximation: 1 token ≈ 4 characters for English text
15
- * @param {string} text - Text to estimate tokens for
16
- * @returns {number} Estimated token count
17
- */
18
- function estimateTokens(text) {
19
- if (!text || typeof text !== 'string') {
20
- return 0;
21
- }
22
- // Average of 4 characters per token for English text
23
- return Math.ceil(text.length / 4);
24
- }
25
-
26
- /**
27
- * Truncates text to fit within token limit while preserving structure
28
- * @param {string} text - Text to truncate
29
- * @param {number} maxTokens - Maximum allowed tokens
30
- * @returns {object} Object with truncated text and metadata
31
- */
32
- function truncateToTokenLimit(text, maxTokens) {
33
- if (!text || typeof text !== 'string') {
34
- return {
35
- content: text || '',
36
- truncated: false,
37
- originalTokens: 0,
38
- finalTokens: 0,
39
- truncationReason: null
40
- };
41
- }
42
-
43
- const originalTokens = estimateTokens(text);
44
-
45
- if (originalTokens <= maxTokens) {
46
- return {
47
- content: text,
48
- truncated: false,
49
- originalTokens,
50
- finalTokens: originalTokens,
51
- truncationReason: null
52
- };
53
- }
54
-
55
- // Calculate maximum characters to keep (with some buffer for safety)
56
- const maxChars = Math.floor(maxTokens * 3.8); // Slightly less than 4 chars per token
57
-
58
- // Find a good truncation point (prefer complete sentences or paragraphs)
59
- const truncationPoint = maxChars;
60
- const text_substring = text.substring(0, maxChars + 200); // Look ahead a bit
61
-
62
- // Try to find sentence endings
63
- const sentenceEndings = ['. ', '.\n', '!\n', '?\n', '! ', '? '];
64
- let bestTruncation = maxChars;
65
-
66
- for (const ending of sentenceEndings) {
67
- const lastIndex = text_substring.lastIndexOf(ending);
68
- if (lastIndex > maxChars * 0.8 && lastIndex < maxChars) {
69
- bestTruncation = lastIndex + ending.length;
70
- break;
71
- }
72
- }
73
-
74
- // If no good sentence ending, try paragraph breaks
75
- if (bestTruncation === maxChars) {
76
- const paragraphBreak = text_substring.lastIndexOf('\n\n');
77
- if (paragraphBreak > maxChars * 0.8 && paragraphBreak < maxChars) {
78
- bestTruncation = paragraphBreak + 2;
79
- }
80
- }
81
-
82
- // If still no good break, try line breaks
83
- if (bestTruncation === maxChars) {
84
- const lineBreak = text_substring.lastIndexOf('\n');
85
- if (lineBreak > maxChars * 0.9 && lineBreak < maxChars) {
86
- bestTruncation = lineBreak + 1;
87
- }
88
- }
89
-
90
- const truncatedText = text.substring(0, bestTruncation);
91
- const finalTokens = estimateTokens(truncatedText);
92
-
93
- const truncationMessage = `\n\n[Response truncated due to length. Original: ~${originalTokens} tokens, Truncated: ~${finalTokens} tokens, Limit: ${maxTokens} tokens]`;
94
-
95
- return {
96
- content: truncatedText + truncationMessage,
97
- truncated: true,
98
- originalTokens,
99
- finalTokens: finalTokens + estimateTokens(truncationMessage),
100
- truncationReason: 'Exceeded maximum token limit'
101
- };
102
- }
103
-
104
- /**
105
- * Validates and applies token limits to MCP tool responses
106
- * @param {any} response - The response content to validate
107
- * @param {number} maxTokens - Maximum allowed tokens
108
- * @returns {object} Processed response with token limiting applied
109
- */
110
- export function applyTokenLimit(response, maxTokens) {
111
- if (!maxTokens || maxTokens <= 0) {
112
- return {
113
- content: response,
114
- metadata: {
115
- tokenLimitApplied: false,
116
- originalTokens: 0,
117
- finalTokens: 0
118
- }
119
- };
120
- }
121
-
122
- // Handle different response types
123
- let textContent = '';
124
- const originalResponse = response;
125
-
126
- if (typeof response === 'string') {
127
- textContent = response;
128
- } else if (response && typeof response === 'object') {
129
- // Handle structured responses (like consensus tool)
130
- if (response.content && typeof response.content === 'string') {
131
- textContent = response.content;
132
- } else {
133
- // For complex objects, serialize to JSON for token counting
134
- textContent = JSON.stringify(response, null, 2);
135
- }
136
- } else {
137
- // Convert other types to string
138
- textContent = String(response);
139
- }
140
-
141
- const result = truncateToTokenLimit(textContent, maxTokens);
142
-
143
- if (result.truncated) {
144
- logger.warn('Response truncated due to token limit', {
145
- originalTokens: result.originalTokens,
146
- finalTokens: result.finalTokens,
147
- maxTokens,
148
- truncationRatio: (result.finalTokens / result.originalTokens).toFixed(3)
149
- });
150
- }
151
-
152
- // Return the processed response
153
- if (typeof originalResponse === 'string') {
154
- return {
155
- content: result.content,
156
- metadata: {
157
- tokenLimitApplied: result.truncated,
158
- originalTokens: result.originalTokens,
159
- finalTokens: result.finalTokens,
160
- truncationReason: result.truncationReason
161
- }
162
- };
163
- } else if (originalResponse && typeof originalResponse === 'object') {
164
- // For structured responses, update the content field
165
- return {
166
- ...originalResponse,
167
- content: result.content,
168
- metadata: {
169
- ...originalResponse.metadata,
170
- tokenLimitApplied: result.truncated,
171
- originalTokens: result.originalTokens,
172
- finalTokens: result.finalTokens,
173
- truncationReason: result.truncationReason
174
- }
175
- };
176
- } else {
177
- return {
178
- content: result.content,
179
- metadata: {
180
- tokenLimitApplied: result.truncated,
181
- originalTokens: result.originalTokens,
182
- finalTokens: result.finalTokens,
183
- truncationReason: result.truncationReason
184
- }
185
- };
186
- }
187
- }
188
-
189
- /**
190
- * Gets the configured token limit from environment or config
191
- * @param {object} config - Configuration object
192
- * @returns {number} Maximum token limit
193
- */
194
- export function getTokenLimit(config) {
195
- if (config && config.mcp && config.mcp.max_mcp_output_tokens) {
196
- return config.mcp.max_mcp_output_tokens;
197
- }
198
-
199
- // Fallback to environment variable
200
- const envLimit = process.env.MAX_MCP_OUTPUT_TOKENS;
201
- if (envLimit) {
202
- const limit = parseInt(envLimit, 10);
203
- if (!isNaN(limit) && limit > 0) {
204
- return limit;
205
- }
206
- }
207
-
208
- // Default limit
209
- return 25000;
210
- }
211
-
212
- /**
213
- * Estimates token count for a given text (exported for testing)
214
- * @param {string} text - Text to estimate
215
- * @returns {number} Estimated token count
216
- */
1
+ /**
2
+ * Token Limiter Utility
3
+ *
4
+ * Implements token limiting for MCP tool responses to prevent excessive output.
5
+ * Based on the Python implementation's token limiting functionality.
6
+ */
7
+
8
+ import { createLogger } from './logger.js';
9
+
10
+ const logger = createLogger('tokenLimiter');
11
+
12
+ /**
13
+ * Simple token estimation based on character count
14
+ * Rough approximation: 1 token ≈ 4 characters for English text
15
+ * @param {string} text - Text to estimate tokens for
16
+ * @returns {number} Estimated token count
17
+ */
18
+ function estimateTokens(text) {
19
+ if (!text || typeof text !== 'string') {
20
+ return 0;
21
+ }
22
+ // Average of 4 characters per token for English text
23
+ return Math.ceil(text.length / 4);
24
+ }
25
+
26
+ /**
27
+ * Truncates text to fit within token limit while preserving structure
28
+ * @param {string} text - Text to truncate
29
+ * @param {number} maxTokens - Maximum allowed tokens
30
+ * @returns {object} Object with truncated text and metadata
31
+ */
32
+ function truncateToTokenLimit(text, maxTokens) {
33
+ if (!text || typeof text !== 'string') {
34
+ return {
35
+ content: text || '',
36
+ truncated: false,
37
+ originalTokens: 0,
38
+ finalTokens: 0,
39
+ truncationReason: null,
40
+ };
41
+ }
42
+
43
+ const originalTokens = estimateTokens(text);
44
+
45
+ if (originalTokens <= maxTokens) {
46
+ return {
47
+ content: text,
48
+ truncated: false,
49
+ originalTokens,
50
+ finalTokens: originalTokens,
51
+ truncationReason: null,
52
+ };
53
+ }
54
+
55
+ // Calculate maximum characters to keep (with some buffer for safety)
56
+ const maxChars = Math.floor(maxTokens * 3.8); // Slightly less than 4 chars per token
57
+
58
+ // Find a good truncation point (prefer complete sentences or paragraphs)
59
+ const truncationPoint = maxChars;
60
+ const text_substring = text.substring(0, maxChars + 200); // Look ahead a bit
61
+
62
+ // Try to find sentence endings
63
+ const sentenceEndings = ['. ', '.\n', '!\n', '?\n', '! ', '? '];
64
+ let bestTruncation = maxChars;
65
+
66
+ for (const ending of sentenceEndings) {
67
+ const lastIndex = text_substring.lastIndexOf(ending);
68
+ if (lastIndex > maxChars * 0.8 && lastIndex < maxChars) {
69
+ bestTruncation = lastIndex + ending.length;
70
+ break;
71
+ }
72
+ }
73
+
74
+ // If no good sentence ending, try paragraph breaks
75
+ if (bestTruncation === maxChars) {
76
+ const paragraphBreak = text_substring.lastIndexOf('\n\n');
77
+ if (paragraphBreak > maxChars * 0.8 && paragraphBreak < maxChars) {
78
+ bestTruncation = paragraphBreak + 2;
79
+ }
80
+ }
81
+
82
+ // If still no good break, try line breaks
83
+ if (bestTruncation === maxChars) {
84
+ const lineBreak = text_substring.lastIndexOf('\n');
85
+ if (lineBreak > maxChars * 0.9 && lineBreak < maxChars) {
86
+ bestTruncation = lineBreak + 1;
87
+ }
88
+ }
89
+
90
+ const truncatedText = text.substring(0, bestTruncation);
91
+ const finalTokens = estimateTokens(truncatedText);
92
+
93
+ const truncationMessage = `\n\n[Response truncated due to length. Original: ~${originalTokens} tokens, Truncated: ~${finalTokens} tokens, Limit: ${maxTokens} tokens]`;
94
+
95
+ return {
96
+ content: truncatedText + truncationMessage,
97
+ truncated: true,
98
+ originalTokens,
99
+ finalTokens: finalTokens + estimateTokens(truncationMessage),
100
+ truncationReason: 'Exceeded maximum token limit',
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Validates and applies token limits to MCP tool responses
106
+ * @param {any} response - The response content to validate
107
+ * @param {number} maxTokens - Maximum allowed tokens
108
+ * @returns {object} Processed response with token limiting applied
109
+ */
110
+ export function applyTokenLimit(response, maxTokens) {
111
+ if (!maxTokens || maxTokens <= 0) {
112
+ return {
113
+ content: response,
114
+ metadata: {
115
+ tokenLimitApplied: false,
116
+ originalTokens: 0,
117
+ finalTokens: 0,
118
+ },
119
+ };
120
+ }
121
+
122
+ // Handle different response types
123
+ let textContent = '';
124
+ const originalResponse = response;
125
+
126
+ if (typeof response === 'string') {
127
+ textContent = response;
128
+ } else if (response && typeof response === 'object') {
129
+ // Handle structured responses (like consensus tool)
130
+ if (response.content && typeof response.content === 'string') {
131
+ textContent = response.content;
132
+ } else {
133
+ // For complex objects, serialize to JSON for token counting
134
+ textContent = JSON.stringify(response, null, 2);
135
+ }
136
+ } else {
137
+ // Convert other types to string
138
+ textContent = String(response);
139
+ }
140
+
141
+ const result = truncateToTokenLimit(textContent, maxTokens);
142
+
143
+ if (result.truncated) {
144
+ logger.warn('Response truncated due to token limit', {
145
+ originalTokens: result.originalTokens,
146
+ finalTokens: result.finalTokens,
147
+ maxTokens,
148
+ truncationRatio: (result.finalTokens / result.originalTokens).toFixed(3),
149
+ });
150
+ }
151
+
152
+ // Return the processed response
153
+ if (typeof originalResponse === 'string') {
154
+ return {
155
+ content: result.content,
156
+ metadata: {
157
+ tokenLimitApplied: result.truncated,
158
+ originalTokens: result.originalTokens,
159
+ finalTokens: result.finalTokens,
160
+ truncationReason: result.truncationReason,
161
+ },
162
+ };
163
+ } else if (originalResponse && typeof originalResponse === 'object') {
164
+ // For structured responses, update the content field
165
+ return {
166
+ ...originalResponse,
167
+ content: result.content,
168
+ metadata: {
169
+ ...originalResponse.metadata,
170
+ tokenLimitApplied: result.truncated,
171
+ originalTokens: result.originalTokens,
172
+ finalTokens: result.finalTokens,
173
+ truncationReason: result.truncationReason,
174
+ },
175
+ };
176
+ } else {
177
+ return {
178
+ content: result.content,
179
+ metadata: {
180
+ tokenLimitApplied: result.truncated,
181
+ originalTokens: result.originalTokens,
182
+ finalTokens: result.finalTokens,
183
+ truncationReason: result.truncationReason,
184
+ },
185
+ };
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Gets the configured token limit from environment or config
191
+ * @param {object} config - Configuration object
192
+ * @returns {number} Maximum token limit
193
+ */
194
+ export function getTokenLimit(config) {
195
+ if (config && config.mcp && config.mcp.max_mcp_output_tokens) {
196
+ return config.mcp.max_mcp_output_tokens;
197
+ }
198
+
199
+ // Fallback to environment variable
200
+ const envLimit = process.env.MAX_MCP_OUTPUT_TOKENS;
201
+ if (envLimit) {
202
+ const limit = parseInt(envLimit, 10);
203
+ if (!isNaN(limit) && limit > 0) {
204
+ return limit;
205
+ }
206
+ }
207
+
208
+ // Default limit
209
+ return 25000;
210
+ }
211
+
212
+ /**
213
+ * Estimates token count for a given text (exported for testing)
214
+ * @param {string} text - Text to estimate
215
+ * @returns {number} Estimated token count
216
+ */
217
217
  export { estimateTokens };