converse-mcp-server 1.0.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.
- package/.env.example +177 -0
- package/README.md +425 -0
- package/bin/converse.js +45 -0
- package/docs/API.md +897 -0
- package/docs/ARCHITECTURE.md +552 -0
- package/docs/EXAMPLES.md +736 -0
- package/package.json +101 -0
- package/src/config.js +521 -0
- package/src/continuationStore.js +340 -0
- package/src/index.js +216 -0
- package/src/providers/google.js +441 -0
- package/src/providers/index.js +87 -0
- package/src/providers/openai.js +348 -0
- package/src/providers/xai.js +305 -0
- package/src/router.js +497 -0
- package/src/systemPrompts.js +90 -0
- package/src/tools/chat.js +336 -0
- package/src/tools/consensus.js +478 -0
- package/src/tools/index.js +156 -0
- package/src/transport/httpTransport.js +548 -0
- package/src/utils/console.js +64 -0
- package/src/utils/contextProcessor.js +475 -0
- package/src/utils/errorHandler.js +555 -0
- package/src/utils/logger.js +450 -0
- package/src/utils/tokenLimiter.js +217 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat Tool
|
|
3
|
+
*
|
|
4
|
+
* Single-provider conversational AI with context and continuation support.
|
|
5
|
+
* Handles context processing, provider calls, and state management.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createToolResponse, createToolError } from './index.js';
|
|
9
|
+
import { processUnifiedContext, createFileContext } from '../utils/contextProcessor.js';
|
|
10
|
+
import { generateContinuationId, addMessageToHistory } from '../continuationStore.js';
|
|
11
|
+
import { debugLog, debugError } from '../utils/console.js';
|
|
12
|
+
import { CHAT_PROMPT } from '../systemPrompts.js';
|
|
13
|
+
import { applyTokenLimit, getTokenLimit } from '../utils/tokenLimiter.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Chat tool implementation
|
|
17
|
+
* @param {object} args - Tool arguments
|
|
18
|
+
* @param {object} dependencies - Injected dependencies (config, providers, continuationStore)
|
|
19
|
+
* @returns {object} MCP tool response
|
|
20
|
+
*/
|
|
21
|
+
export async function chatTool(args, dependencies) {
|
|
22
|
+
try {
|
|
23
|
+
const { config, providers, continuationStore, contextProcessor } = dependencies;
|
|
24
|
+
|
|
25
|
+
// Validate required arguments
|
|
26
|
+
if (!args.prompt || typeof args.prompt !== 'string') {
|
|
27
|
+
return createToolError('Prompt is required and must be a string');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Extract and validate arguments
|
|
31
|
+
const {
|
|
32
|
+
prompt,
|
|
33
|
+
model = 'auto',
|
|
34
|
+
files = [],
|
|
35
|
+
continuation_id,
|
|
36
|
+
temperature = 0.5,
|
|
37
|
+
use_websearch = false,
|
|
38
|
+
images = [],
|
|
39
|
+
reasoning_effort = 'medium'
|
|
40
|
+
} = args;
|
|
41
|
+
|
|
42
|
+
let conversationHistory = [];
|
|
43
|
+
let continuationId = continuation_id;
|
|
44
|
+
|
|
45
|
+
// Load existing conversation if continuation_id provided
|
|
46
|
+
if (continuationId) {
|
|
47
|
+
try {
|
|
48
|
+
const existingState = await continuationStore.get(continuationId);
|
|
49
|
+
if (existingState) {
|
|
50
|
+
conversationHistory = existingState.messages || [];
|
|
51
|
+
} else {
|
|
52
|
+
// Invalid continuation ID - start fresh with new ID
|
|
53
|
+
continuationId = generateContinuationId();
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
debugError('Error loading conversation:', error);
|
|
57
|
+
// Continue with fresh conversation on error
|
|
58
|
+
continuationId = generateContinuationId();
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
// Generate new continuation ID for new conversation
|
|
62
|
+
continuationId = generateContinuationId();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Process context (files, images, web search)
|
|
66
|
+
let contextMessage = null;
|
|
67
|
+
if (files.length > 0 || images.length > 0 || use_websearch) {
|
|
68
|
+
try {
|
|
69
|
+
const contextRequest = {
|
|
70
|
+
files: Array.isArray(files) ? files : [],
|
|
71
|
+
images: Array.isArray(images) ? images : [],
|
|
72
|
+
webSearch: use_websearch ? prompt : null
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const contextResult = await contextProcessor.processUnifiedContext(contextRequest);
|
|
76
|
+
|
|
77
|
+
// Create context message from files
|
|
78
|
+
if (contextResult.files.length > 0) {
|
|
79
|
+
contextMessage = createFileContext(contextResult.files, {
|
|
80
|
+
includeMetadata: true,
|
|
81
|
+
includeErrors: true
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Add web search results if available (placeholder for now)
|
|
86
|
+
if (contextResult.webSearch && !contextResult.webSearch.placeholder) {
|
|
87
|
+
// Future implementation: add web search results to context
|
|
88
|
+
debugLog('[Chat] Web search results available but not yet implemented');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
} catch (error) {
|
|
92
|
+
debugError('Error processing context:', error);
|
|
93
|
+
// Continue without context if processing fails
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Build message array for provider
|
|
98
|
+
const messages = [];
|
|
99
|
+
|
|
100
|
+
// Add system prompt only if not already in conversation history
|
|
101
|
+
if (conversationHistory.length === 0 || conversationHistory[0].role !== 'system') {
|
|
102
|
+
messages.push({
|
|
103
|
+
role: 'system',
|
|
104
|
+
content: CHAT_PROMPT
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Add conversation history
|
|
109
|
+
messages.push(...conversationHistory);
|
|
110
|
+
|
|
111
|
+
// Add context message if available
|
|
112
|
+
if (contextMessage) {
|
|
113
|
+
messages.push(contextMessage);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Add user prompt
|
|
117
|
+
messages.push({
|
|
118
|
+
role: 'user',
|
|
119
|
+
content: prompt
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Select provider
|
|
123
|
+
let selectedProvider;
|
|
124
|
+
let providerName;
|
|
125
|
+
|
|
126
|
+
if (model === 'auto') {
|
|
127
|
+
// Auto-select first available provider
|
|
128
|
+
const availableProviders = Object.keys(providers).filter(name => {
|
|
129
|
+
const provider = providers[name];
|
|
130
|
+
return provider && provider.isAvailable && provider.isAvailable(config);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (availableProviders.length === 0) {
|
|
134
|
+
return createToolError('No providers available. Please configure at least one API key.');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
providerName = availableProviders[0];
|
|
138
|
+
selectedProvider = providers[providerName];
|
|
139
|
+
} else {
|
|
140
|
+
// Use specified provider/model
|
|
141
|
+
// Try to map model to provider
|
|
142
|
+
providerName = mapModelToProvider(model);
|
|
143
|
+
selectedProvider = providers[providerName];
|
|
144
|
+
|
|
145
|
+
if (!selectedProvider) {
|
|
146
|
+
return createToolError(`Provider not found for model: ${model}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!selectedProvider.isAvailable(config)) {
|
|
150
|
+
return createToolError(`Provider ${providerName} is not available. Check API key configuration.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Resolve model name and prepare provider options
|
|
155
|
+
const resolvedModel = resolveAutoModel(model, providerName);
|
|
156
|
+
const providerOptions = {
|
|
157
|
+
model: resolvedModel,
|
|
158
|
+
temperature,
|
|
159
|
+
reasoning_effort,
|
|
160
|
+
config
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Call provider
|
|
164
|
+
let response;
|
|
165
|
+
try {
|
|
166
|
+
response = await selectedProvider.invoke(messages, providerOptions);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
debugError(`Provider ${providerName} error:`, error);
|
|
169
|
+
return createToolError(`Provider error: ${error.message}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Validate response
|
|
173
|
+
if (!response || !response.content) {
|
|
174
|
+
return createToolError('Provider returned invalid response');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Add assistant response to conversation history
|
|
178
|
+
const assistantMessage = {
|
|
179
|
+
role: 'assistant',
|
|
180
|
+
content: response.content
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const updatedMessages = [...messages, assistantMessage];
|
|
184
|
+
|
|
185
|
+
// Save conversation state
|
|
186
|
+
try {
|
|
187
|
+
const conversationState = {
|
|
188
|
+
messages: updatedMessages,
|
|
189
|
+
provider: providerName,
|
|
190
|
+
model: model,
|
|
191
|
+
lastUpdated: Date.now()
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
await continuationStore.set(continuationId, conversationState);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
debugError('Error saving conversation:', error);
|
|
197
|
+
// Continue even if save fails
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Create response with continuation
|
|
201
|
+
const result = {
|
|
202
|
+
content: response.content,
|
|
203
|
+
continuation: {
|
|
204
|
+
id: continuationId,
|
|
205
|
+
provider: providerName,
|
|
206
|
+
model: model,
|
|
207
|
+
messageCount: updatedMessages.filter(msg => msg.role !== 'system').length
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Add metadata if available
|
|
212
|
+
if (response.metadata) {
|
|
213
|
+
result.metadata = response.metadata;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Apply token limiting to the final response
|
|
217
|
+
const tokenLimit = getTokenLimit(config);
|
|
218
|
+
const resultStr = JSON.stringify(result, null, 2);
|
|
219
|
+
const limitedResult = applyTokenLimit(resultStr, tokenLimit);
|
|
220
|
+
|
|
221
|
+
// Parse the limited result back to object format to preserve structure
|
|
222
|
+
let finalResult;
|
|
223
|
+
try {
|
|
224
|
+
finalResult = JSON.parse(limitedResult.content);
|
|
225
|
+
} catch (e) {
|
|
226
|
+
// Fallback if parsing fails - return original result
|
|
227
|
+
finalResult = result;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return createToolResponse(finalResult);
|
|
231
|
+
|
|
232
|
+
} catch (error) {
|
|
233
|
+
debugError('Chat tool error:', error);
|
|
234
|
+
return createToolError('Chat tool failed', error);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Map model name to provider name
|
|
240
|
+
* @param {string} model - Model name
|
|
241
|
+
* @returns {string} Provider name
|
|
242
|
+
*/
|
|
243
|
+
/**
|
|
244
|
+
* Resolve "auto" model to default model for the provider
|
|
245
|
+
*/
|
|
246
|
+
function resolveAutoModel(model, providerName) {
|
|
247
|
+
if (model.toLowerCase() !== 'auto') {
|
|
248
|
+
return model;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const defaults = {
|
|
252
|
+
'openai': 'gpt-4o-mini',
|
|
253
|
+
'xai': 'grok-4-0709',
|
|
254
|
+
'google': 'gemini-2.5-flash'
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
return defaults[providerName] || 'gpt-4o-mini';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function mapModelToProvider(model) {
|
|
261
|
+
const modelLower = model.toLowerCase();
|
|
262
|
+
|
|
263
|
+
// Handle "auto" - default to OpenAI
|
|
264
|
+
if (modelLower === 'auto') {
|
|
265
|
+
return 'openai';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// OpenAI models
|
|
269
|
+
if (modelLower.includes('gpt') || modelLower.includes('o1') ||
|
|
270
|
+
modelLower.includes('o3') || modelLower.includes('o4')) {
|
|
271
|
+
return 'openai';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// XAI models
|
|
275
|
+
if (modelLower.includes('grok')) {
|
|
276
|
+
return 'xai';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Google models
|
|
280
|
+
if (modelLower.includes('gemini') || modelLower.includes('flash') ||
|
|
281
|
+
modelLower.includes('pro') || modelLower === 'google') {
|
|
282
|
+
return 'google';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Default fallback
|
|
286
|
+
return 'openai';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Tool metadata
|
|
290
|
+
chatTool.description = 'GENERAL CHAT & COLLABORATIVE THINKING - For development assistance, brainstorming, and code analysis. Supports files, images, and conversation continuation.';
|
|
291
|
+
chatTool.inputSchema = {
|
|
292
|
+
type: 'object',
|
|
293
|
+
properties: {
|
|
294
|
+
prompt: {
|
|
295
|
+
type: 'string',
|
|
296
|
+
description: 'Your question or topic with relevant context. More detail enables better responses. Example: "How should I structure the authentication module for this Express.js API?"',
|
|
297
|
+
},
|
|
298
|
+
model: {
|
|
299
|
+
type: 'string',
|
|
300
|
+
description: 'AI model to use. Examples: "auto" (recommended), "gemini-2.5-flash", "o3", "grok-4-0709". Defaults to auto-selection.',
|
|
301
|
+
},
|
|
302
|
+
files: {
|
|
303
|
+
type: 'array',
|
|
304
|
+
items: { type: 'string' },
|
|
305
|
+
description: 'File paths to include as context (absolute paths required). Example: ["/path/to/src/auth.js", "/path/to/config.json"]',
|
|
306
|
+
},
|
|
307
|
+
images: {
|
|
308
|
+
type: 'array',
|
|
309
|
+
items: { type: 'string' },
|
|
310
|
+
description: 'Image paths for visual context (absolute paths or base64 data). Example: ["/path/to/diagram.png", "data:image/jpeg;base64,/9j/4AAQ..."]',
|
|
311
|
+
},
|
|
312
|
+
continuation_id: {
|
|
313
|
+
type: 'string',
|
|
314
|
+
description: 'Continuation ID for persistent conversation. Example: "chat_1703123456789_abc123"',
|
|
315
|
+
},
|
|
316
|
+
temperature: {
|
|
317
|
+
type: 'number',
|
|
318
|
+
description: 'Response randomness (0.0-1.0). Examples: 0.2 (focused), 0.5 (balanced), 0.8 (creative). Default: 0.5',
|
|
319
|
+
minimum: 0.0,
|
|
320
|
+
maximum: 1.0,
|
|
321
|
+
default: 0.5
|
|
322
|
+
},
|
|
323
|
+
reasoning_effort: {
|
|
324
|
+
type: 'string',
|
|
325
|
+
enum: ['minimal', 'low', 'medium', 'high', 'max'],
|
|
326
|
+
description: 'Reasoning depth for thinking models. Examples: "minimal" (quick), "medium" (balanced), "high" (complex analysis). Default: "medium"',
|
|
327
|
+
default: 'medium'
|
|
328
|
+
},
|
|
329
|
+
use_websearch: {
|
|
330
|
+
type: 'boolean',
|
|
331
|
+
description: 'Enable web search for current information and best practices. Example: true for framework documentation, false for private code analysis. Default: false',
|
|
332
|
+
default: false
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
required: ['prompt'],
|
|
336
|
+
};
|