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.
@@ -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
+ };