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,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuation Store - State Management
|
|
3
|
+
*
|
|
4
|
+
* Manages conversation history and state for persistent conversations.
|
|
5
|
+
* Pluggable implementation that can be swapped for different storage backends.
|
|
6
|
+
* Provides a consistent interface (get/set/delete) for state management.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { randomUUID } from 'crypto';
|
|
10
|
+
import { debugLog, debugError } from './utils/console.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Storage backend interface that all continuation stores must implement
|
|
14
|
+
* This ensures pluggable backend replacement without changing the API
|
|
15
|
+
*/
|
|
16
|
+
export class ContinuationStoreInterface {
|
|
17
|
+
/**
|
|
18
|
+
* Store conversation state
|
|
19
|
+
* @param {string} continuationId - Unique continuation identifier
|
|
20
|
+
* @param {object} state - Conversation state
|
|
21
|
+
* @returns {Promise<void>}
|
|
22
|
+
*/
|
|
23
|
+
async set(continuationId, state) {
|
|
24
|
+
throw new Error('set() method must be implemented by storage backend');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Retrieve conversation state
|
|
29
|
+
* @param {string} continuationId - Unique continuation identifier
|
|
30
|
+
* @returns {Promise<object|null>} State or null if not found
|
|
31
|
+
*/
|
|
32
|
+
async get(continuationId) {
|
|
33
|
+
throw new Error('get() method must be implemented by storage backend');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Delete conversation state
|
|
38
|
+
* @param {string} continuationId - Unique continuation identifier
|
|
39
|
+
* @returns {Promise<boolean>} True if deleted, false if not found
|
|
40
|
+
*/
|
|
41
|
+
async delete(continuationId) {
|
|
42
|
+
throw new Error('delete() method must be implemented by storage backend');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if continuation exists
|
|
47
|
+
* @param {string} continuationId - Unique continuation identifier
|
|
48
|
+
* @returns {Promise<boolean>} True if exists
|
|
49
|
+
*/
|
|
50
|
+
async exists(continuationId) {
|
|
51
|
+
const state = await this.get(continuationId);
|
|
52
|
+
return state !== null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get storage statistics
|
|
57
|
+
* @returns {Promise<object>} Backend-specific statistics
|
|
58
|
+
*/
|
|
59
|
+
async getStats() {
|
|
60
|
+
throw new Error('getStats() method must be implemented by storage backend');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Clean up old data
|
|
65
|
+
* @param {number} maxAgeMs - Maximum age in milliseconds
|
|
66
|
+
* @returns {Promise<number>} Number of items cleaned up
|
|
67
|
+
*/
|
|
68
|
+
async cleanup(maxAgeMs) {
|
|
69
|
+
throw new Error('cleanup() method must be implemented by storage backend');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Custom error class for continuation store operations
|
|
75
|
+
*/
|
|
76
|
+
export class ContinuationStoreError extends Error {
|
|
77
|
+
constructor(message, code = 'CONTINUATION_ERROR') {
|
|
78
|
+
super(message);
|
|
79
|
+
this.name = 'ContinuationStoreError';
|
|
80
|
+
this.code = code;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* In-memory continuation store implementation
|
|
86
|
+
* Implements the ContinuationStoreInterface for pluggable backend replacement
|
|
87
|
+
*/
|
|
88
|
+
class MemoryContinuationStore extends ContinuationStoreInterface {
|
|
89
|
+
constructor() {
|
|
90
|
+
super(); // Call parent constructor
|
|
91
|
+
this.conversations = new Map();
|
|
92
|
+
this.maxConversations = 1000; // Prevent memory leaks
|
|
93
|
+
this.maxMessagesPerConversation = 100;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Store conversation state
|
|
98
|
+
* @param {string} continuationId - Unique continuation identifier
|
|
99
|
+
* @param {object} state - Conversation state to store
|
|
100
|
+
* @returns {Promise<void>}
|
|
101
|
+
* @throws {ContinuationStoreError} If storage fails
|
|
102
|
+
*/
|
|
103
|
+
async set(continuationId, state) {
|
|
104
|
+
try {
|
|
105
|
+
// Validate continuation ID
|
|
106
|
+
if (!continuationId || typeof continuationId !== 'string') {
|
|
107
|
+
throw new ContinuationStoreError(
|
|
108
|
+
'Invalid continuation ID: must be a non-empty string',
|
|
109
|
+
'INVALID_CONTINUATION_ID'
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Validate state object
|
|
114
|
+
if (!state || typeof state !== 'object') {
|
|
115
|
+
throw new ContinuationStoreError(
|
|
116
|
+
'Invalid state: must be an object',
|
|
117
|
+
'INVALID_STATE'
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
// Cleanup old conversations if we hit the limit
|
|
121
|
+
if (this.conversations.size >= this.maxConversations) {
|
|
122
|
+
const oldestKey = this.conversations.keys().next().value;
|
|
123
|
+
this.conversations.delete(oldestKey);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Limit messages per conversation to prevent memory issues
|
|
127
|
+
const sanitizedState = { ...state };
|
|
128
|
+
if (sanitizedState.messages && sanitizedState.messages.length > this.maxMessagesPerConversation) {
|
|
129
|
+
sanitizedState.messages = sanitizedState.messages.slice(-this.maxMessagesPerConversation);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Store with metadata
|
|
133
|
+
this.conversations.set(continuationId, {
|
|
134
|
+
...sanitizedState,
|
|
135
|
+
lastAccessed: Date.now(),
|
|
136
|
+
createdAt: this.conversations.has(continuationId)
|
|
137
|
+
? this.conversations.get(continuationId).createdAt
|
|
138
|
+
: Date.now(),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error instanceof ContinuationStoreError) {
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
throw new ContinuationStoreError(
|
|
146
|
+
`Failed to store continuation: ${error.message}`,
|
|
147
|
+
'STORAGE_ERROR'
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Retrieve conversation state
|
|
154
|
+
* @param {string} continuationId - Unique continuation identifier
|
|
155
|
+
* @returns {Promise<object|null>} Conversation state or null if not found
|
|
156
|
+
* @throws {ContinuationStoreError} If retrieval fails
|
|
157
|
+
*/
|
|
158
|
+
async get(continuationId) {
|
|
159
|
+
try {
|
|
160
|
+
// Validate continuation ID
|
|
161
|
+
if (!continuationId || typeof continuationId !== 'string') {
|
|
162
|
+
throw new ContinuationStoreError(
|
|
163
|
+
'Invalid continuation ID: must be a non-empty string',
|
|
164
|
+
'INVALID_CONTINUATION_ID'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const state = this.conversations.get(continuationId);
|
|
169
|
+
if (state) {
|
|
170
|
+
// Update last accessed time
|
|
171
|
+
state.lastAccessed = Date.now();
|
|
172
|
+
// Return copy without internal metadata
|
|
173
|
+
const { createdAt, lastAccessed, ...cleanState } = state;
|
|
174
|
+
return {
|
|
175
|
+
...cleanState,
|
|
176
|
+
_metadata: { createdAt, lastAccessed }
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof ContinuationStoreError) {
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
throw new ContinuationStoreError(
|
|
186
|
+
`Failed to retrieve continuation: ${error.message}`,
|
|
187
|
+
'RETRIEVAL_ERROR'
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Delete conversation state
|
|
194
|
+
* @param {string} continuationId - Unique continuation identifier
|
|
195
|
+
* @returns {Promise<boolean>} True if deleted, false if not found
|
|
196
|
+
* @throws {ContinuationStoreError} If deletion fails
|
|
197
|
+
*/
|
|
198
|
+
async delete(continuationId) {
|
|
199
|
+
try {
|
|
200
|
+
// Validate continuation ID
|
|
201
|
+
if (!continuationId || typeof continuationId !== 'string') {
|
|
202
|
+
throw new ContinuationStoreError(
|
|
203
|
+
'Invalid continuation ID: must be a non-empty string',
|
|
204
|
+
'INVALID_CONTINUATION_ID'
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const existed = this.conversations.has(continuationId);
|
|
209
|
+
this.conversations.delete(continuationId);
|
|
210
|
+
return existed;
|
|
211
|
+
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (error instanceof ContinuationStoreError) {
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
throw new ContinuationStoreError(
|
|
217
|
+
`Failed to delete continuation: ${error.message}`,
|
|
218
|
+
'DELETION_ERROR'
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get storage statistics
|
|
225
|
+
* @returns {Promise<object>} Store statistics
|
|
226
|
+
*/
|
|
227
|
+
async getStats() {
|
|
228
|
+
return {
|
|
229
|
+
backend: 'memory',
|
|
230
|
+
totalConversations: this.conversations.size,
|
|
231
|
+
maxConversations: this.maxConversations,
|
|
232
|
+
maxMessagesPerConversation: this.maxMessagesPerConversation,
|
|
233
|
+
memoryUsage: process.memoryUsage(),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Clean up old conversations
|
|
239
|
+
* @param {number} maxAgeMs - Maximum age in milliseconds (default: 24 hours)
|
|
240
|
+
* @returns {Promise<number>} Number of conversations cleaned up
|
|
241
|
+
*/
|
|
242
|
+
async cleanup(maxAgeMs = 24 * 60 * 60 * 1000) {
|
|
243
|
+
const now = Date.now();
|
|
244
|
+
let cleanedCount = 0;
|
|
245
|
+
|
|
246
|
+
// Special case: if maxAgeMs is 0, clean up all conversations
|
|
247
|
+
if (maxAgeMs === 0) {
|
|
248
|
+
cleanedCount = this.conversations.size;
|
|
249
|
+
this.conversations.clear();
|
|
250
|
+
return cleanedCount;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (const [id, state] of this.conversations.entries()) {
|
|
254
|
+
if (now - state.lastAccessed > maxAgeMs) {
|
|
255
|
+
this.conversations.delete(id);
|
|
256
|
+
cleanedCount++;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return cleanedCount;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Singleton instance - can be replaced for different backends
|
|
265
|
+
let continuationStore = null;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get the continuation store instance
|
|
269
|
+
* @returns {ContinuationStoreInterface} Continuation store instance
|
|
270
|
+
*/
|
|
271
|
+
export function getContinuationStore() {
|
|
272
|
+
if (!continuationStore) {
|
|
273
|
+
continuationStore = new MemoryContinuationStore();
|
|
274
|
+
|
|
275
|
+
// Set up periodic cleanup (runs every hour)
|
|
276
|
+
setInterval(async () => {
|
|
277
|
+
try {
|
|
278
|
+
const cleaned = await continuationStore.cleanup();
|
|
279
|
+
if (cleaned > 0) {
|
|
280
|
+
debugLog(`ContinuationStore: Cleaned up ${cleaned} old conversations`);
|
|
281
|
+
}
|
|
282
|
+
} catch (error) {
|
|
283
|
+
debugError('ContinuationStore cleanup failed:', error);
|
|
284
|
+
}
|
|
285
|
+
}, 60 * 60 * 1000);
|
|
286
|
+
}
|
|
287
|
+
return continuationStore;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Set a custom continuation store backend (for testing or different implementations)
|
|
292
|
+
* @param {ContinuationStoreInterface} store - Custom store implementation
|
|
293
|
+
*/
|
|
294
|
+
export function setContinuationStore(store) {
|
|
295
|
+
if (!(store instanceof ContinuationStoreInterface)) {
|
|
296
|
+
throw new ContinuationStoreError(
|
|
297
|
+
'Store must extend ContinuationStoreInterface',
|
|
298
|
+
'INVALID_STORE'
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
continuationStore = store;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Generate a new UUID-based continuation ID
|
|
306
|
+
* @returns {string} Unique continuation ID
|
|
307
|
+
*/
|
|
308
|
+
export function generateContinuationId() {
|
|
309
|
+
return `conv_${randomUUID()}`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Validate continuation ID format
|
|
314
|
+
* @param {string} continuationId - ID to validate
|
|
315
|
+
* @returns {boolean} True if valid format
|
|
316
|
+
*/
|
|
317
|
+
export function isValidContinuationId(continuationId) {
|
|
318
|
+
if (!continuationId || typeof continuationId !== 'string') {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Check for conv_ prefix and UUID format
|
|
323
|
+
const uuidPattern = /^conv_[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
324
|
+
return uuidPattern.test(continuationId);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Helper function to add a message to conversation history
|
|
329
|
+
* @param {object} state - Current conversation state
|
|
330
|
+
* @param {object} message - Message to add
|
|
331
|
+
* @returns {object} Updated state
|
|
332
|
+
*/
|
|
333
|
+
export function addMessageToHistory(state, message) {
|
|
334
|
+
const messages = state.messages || [];
|
|
335
|
+
return {
|
|
336
|
+
...state,
|
|
337
|
+
messages: [...messages, message],
|
|
338
|
+
lastUpdated: Date.now(),
|
|
339
|
+
};
|
|
340
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converse MCP Server - Main Entry Point
|
|
5
|
+
*
|
|
6
|
+
* Simplified, functional Node.js implementation of MCP server
|
|
7
|
+
* with chat and consensus tools using modern Node.js practices.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
+
import { loadConfig, validateRuntimeConfig, getMcpClientConfig, getHttpTransportConfig } from './config.js';
|
|
13
|
+
import { createRouter } from './router.js';
|
|
14
|
+
import { createHTTPTransport } from './transport/httpTransport.js';
|
|
15
|
+
import { createLogger, startTimer } from './utils/logger.js';
|
|
16
|
+
import { debugError } from './utils/console.js';
|
|
17
|
+
import { ConfigurationError } from './utils/errorHandler.js';
|
|
18
|
+
|
|
19
|
+
const logger = createLogger('server');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Show help message
|
|
23
|
+
*/
|
|
24
|
+
function showHelp() {
|
|
25
|
+
debugError(`
|
|
26
|
+
Converse MCP Server
|
|
27
|
+
|
|
28
|
+
Usage: node src/index.js [OPTIONS]
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--transport <type> Transport type: http (default) or stdio
|
|
32
|
+
--transport=<type> Alternative format for transport type
|
|
33
|
+
--help Show this help message
|
|
34
|
+
|
|
35
|
+
Environment Variables:
|
|
36
|
+
MCP_TRANSPORT Transport type (http or stdio)
|
|
37
|
+
PORT HTTP server port (default: 3000)
|
|
38
|
+
HOST HTTP server host (default: localhost)
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
node src/index.js # Start with HTTP transport (default)
|
|
42
|
+
node src/index.js --transport http # Start with HTTP transport
|
|
43
|
+
node src/index.js --transport stdio # Start with stdio transport
|
|
44
|
+
npm start # Start with HTTP transport
|
|
45
|
+
MCP_TRANSPORT=stdio npm start # Start with stdio transport
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Determine transport type from command line arguments or environment
|
|
51
|
+
*/
|
|
52
|
+
function getTransportType() {
|
|
53
|
+
// Check command line arguments
|
|
54
|
+
const args = process.argv.slice(2);
|
|
55
|
+
|
|
56
|
+
// Support --transport=value format
|
|
57
|
+
const transportEqualArg = args.find(arg => arg.startsWith('--transport='));
|
|
58
|
+
if (transportEqualArg) {
|
|
59
|
+
const transport = transportEqualArg.split('=')[1];
|
|
60
|
+
if (transport && ['http', 'stdio'].includes(transport)) {
|
|
61
|
+
return transport;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Support --transport value format
|
|
66
|
+
const transportIndex = args.findIndex(arg => arg === '--transport');
|
|
67
|
+
if (transportIndex >= 0 && transportIndex + 1 < args.length) {
|
|
68
|
+
const transport = args[transportIndex + 1];
|
|
69
|
+
if (transport && ['http', 'stdio'].includes(transport)) {
|
|
70
|
+
return transport;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check environment variable
|
|
75
|
+
if (process.env.MCP_TRANSPORT) {
|
|
76
|
+
const transport = process.env.MCP_TRANSPORT.toLowerCase();
|
|
77
|
+
if (['http', 'stdio'].includes(transport)) {
|
|
78
|
+
return transport;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Default to HTTP for better development experience
|
|
83
|
+
return 'http';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function main() {
|
|
87
|
+
// Check for help flag
|
|
88
|
+
const args = process.argv.slice(2);
|
|
89
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
90
|
+
showHelp();
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const serverTimer = startTimer('server-startup', 'server');
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
logger.info('Starting Converse MCP Server');
|
|
98
|
+
|
|
99
|
+
// Load and validate configuration
|
|
100
|
+
const config = await loadConfig();
|
|
101
|
+
await validateRuntimeConfig(config);
|
|
102
|
+
|
|
103
|
+
// Get MCP client configuration
|
|
104
|
+
const mcpConfig = getMcpClientConfig(config);
|
|
105
|
+
|
|
106
|
+
// Determine transport type
|
|
107
|
+
const transportType = getTransportType();
|
|
108
|
+
logger.info('Using transport type', { data: { transport: transportType } });
|
|
109
|
+
|
|
110
|
+
logger.debug('Creating MCP server instance', {
|
|
111
|
+
data: { name: mcpConfig.name, version: mcpConfig.version }
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Create MCP server with configuration
|
|
115
|
+
const server = new Server(
|
|
116
|
+
{
|
|
117
|
+
name: mcpConfig.name,
|
|
118
|
+
version: mcpConfig.version,
|
|
119
|
+
},
|
|
120
|
+
mcpConfig
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Set up router with server and config
|
|
124
|
+
await createRouter(server, config);
|
|
125
|
+
|
|
126
|
+
// Start server with appropriate transport
|
|
127
|
+
if (transportType === 'http') {
|
|
128
|
+
// HTTP streaming transport with full configuration
|
|
129
|
+
const httpConfig = getHttpTransportConfig(config);
|
|
130
|
+
const httpTransport = await createHTTPTransport(server, httpConfig);
|
|
131
|
+
|
|
132
|
+
await httpTransport.start();
|
|
133
|
+
const status = httpTransport.getStatus();
|
|
134
|
+
|
|
135
|
+
const startupTime = serverTimer('completed');
|
|
136
|
+
logger.info('Converse MCP Server started successfully with HTTP transport', {
|
|
137
|
+
data: {
|
|
138
|
+
startupTime: `${startupTime}ms`,
|
|
139
|
+
endpoint: `http://${status.host}:${status.port}/mcp`,
|
|
140
|
+
host: status.host,
|
|
141
|
+
port: status.port
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Store reference for shutdown
|
|
146
|
+
process.httpTransport = httpTransport;
|
|
147
|
+
} else {
|
|
148
|
+
// Stdio transport (legacy)
|
|
149
|
+
const transport = new StdioServerTransport();
|
|
150
|
+
await server.connect(transport);
|
|
151
|
+
|
|
152
|
+
const startupTime = serverTimer('completed');
|
|
153
|
+
logger.info('Converse MCP Server started successfully with stdio transport', {
|
|
154
|
+
data: { startupTime: `${startupTime}ms` }
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
serverTimer('failed');
|
|
159
|
+
|
|
160
|
+
if (error instanceof ConfigurationError) {
|
|
161
|
+
logger.error('Configuration error during startup', { error });
|
|
162
|
+
debugError('Configuration Error:');
|
|
163
|
+
debugError(error.message);
|
|
164
|
+
if (error.details?.errors) {
|
|
165
|
+
debugError('\nDetailed errors:');
|
|
166
|
+
error.details.errors.forEach(err => debugError(` - ${err}`));
|
|
167
|
+
}
|
|
168
|
+
process.exit(1);
|
|
169
|
+
} else {
|
|
170
|
+
logger.error('Failed to start Converse MCP Server', { error });
|
|
171
|
+
debugError('Failed to start Converse MCP Server:', error.message);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Handle graceful shutdown
|
|
178
|
+
async function gracefulShutdown(signal) {
|
|
179
|
+
logger.info(`Received ${signal}, shutting down gracefully`);
|
|
180
|
+
debugError('Shutting down Converse MCP Server...');
|
|
181
|
+
|
|
182
|
+
if (process.httpTransport) {
|
|
183
|
+
try {
|
|
184
|
+
await process.httpTransport.stop();
|
|
185
|
+
logger.info('HTTP transport stopped successfully');
|
|
186
|
+
} catch (error) {
|
|
187
|
+
logger.error('Error stopping HTTP transport', { error });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
195
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
196
|
+
|
|
197
|
+
process.on('uncaughtException', (error) => {
|
|
198
|
+
logger.error('Uncaught exception', { error });
|
|
199
|
+
debugError('Fatal error:', error);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
204
|
+
logger.error('Unhandled promise rejection', {
|
|
205
|
+
error: reason,
|
|
206
|
+
data: { promise: promise.toString() }
|
|
207
|
+
});
|
|
208
|
+
debugError('Unhandled promise rejection:', reason);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
main().catch((error) => {
|
|
213
|
+
logger.error('Fatal error in main', { error });
|
|
214
|
+
debugError('Fatal error:', error);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
});
|