cellium-mcp-client 1.1.3 → 1.1.4

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/dist/cli.js CHANGED
@@ -11,23 +11,32 @@ const program = new commander_1.Command();
11
11
  program
12
12
  .name('cellium-mcp-client')
13
13
  .description('MCP client for connecting to remote Cellium processor server')
14
- .version('1.1.2')
14
+ .version('1.1.3')
15
15
  .option('-t, --token <token>', 'Authentication token (format: user:username:hash)')
16
16
  .option('-e, --endpoint <url>', 'Server endpoint URL', 'http://localhost:3000/mcp')
17
17
  .option('-v, --verbose', 'Enable verbose logging')
18
+ .option('-d, --debug', 'Enable debug mode with extensive logging')
18
19
  .option('-r, --retry-attempts <num>', 'Number of retry attempts on connection failure', '3')
19
20
  .option('--retry-delay <ms>', 'Delay between retry attempts in milliseconds', '1000')
20
21
  .parse(process.argv);
21
22
  const options = program.opts();
22
- // Configure logging
23
+ // Configure logging with different levels based on options
24
+ let logLevel = 'info';
25
+ if (options.debug) {
26
+ logLevel = 'debug';
27
+ }
28
+ else if (options.verbose) {
29
+ logLevel = 'debug';
30
+ }
23
31
  const logger = (0, pino_1.default)({
24
- level: options.verbose ? 'debug' : 'info',
32
+ level: logLevel,
25
33
  transport: {
26
34
  target: 'pino-pretty',
27
35
  options: {
28
36
  colorize: true,
29
- translateTime: 'HH:MM:ss',
30
- ignore: 'pid,hostname'
37
+ translateTime: 'HH:MM:ss.l',
38
+ ignore: 'pid,hostname',
39
+ messageFormat: options.debug ? '[{level}] {msg}' : '{msg}'
31
40
  }
32
41
  }
33
42
  });
@@ -37,47 +46,86 @@ async function main() {
37
46
  const token = options.token || process.env.CELLIUM_MCP_TOKEN;
38
47
  if (!token) {
39
48
  logger.error('Authentication token required. Use --token option or CELLIUM_MCP_TOKEN environment variable');
49
+ logger.info('Token format should be: user:username:hash');
40
50
  process.exit(1);
41
51
  }
42
52
  // Validate token format
43
53
  if (!token.match(/^user:[^:]+:[a-f0-9]+$/)) {
44
54
  logger.error('Invalid token format. Expected: user:username:hash');
55
+ logger.info('Example: user:myusername:abc123def456...');
45
56
  process.exit(1);
46
57
  }
47
- logger.info('Starting Cellium MCP Client');
58
+ logger.info({
59
+ version: '1.1.3',
60
+ debugMode: !!options.debug,
61
+ verboseMode: !!options.verbose
62
+ }, 'Starting Cellium MCP Client');
48
63
  logger.debug({
49
64
  endpoint: options.endpoint,
50
65
  retryAttempts: parseInt(options.retryAttempts),
51
- retryDelay: parseInt(options.retryDelay)
52
- }, 'Configuration');
66
+ retryDelay: parseInt(options.retryDelay),
67
+ tokenPreview: `${token.split(':')[0]}:${token.split(':')[1]}:${token.split(':')[2]?.substring(0, 8)}...`
68
+ }, 'Configuration loaded');
53
69
  const client = new client_1.CelliumMCPClient({
54
70
  token,
55
71
  endpoint: options.endpoint,
56
72
  logger,
57
73
  retryAttempts: parseInt(options.retryAttempts),
58
- retryDelay: parseInt(options.retryDelay)
74
+ retryDelay: parseInt(options.retryDelay),
75
+ debugMode: !!options.debug
76
+ });
77
+ // Enhanced error handling for unhandled rejections
78
+ process.on('unhandledRejection', (reason, promise) => {
79
+ logger.error({
80
+ reason,
81
+ promise
82
+ }, 'Unhandled promise rejection');
83
+ });
84
+ process.on('uncaughtException', (error) => {
85
+ logger.fatal({ error }, 'Uncaught exception');
86
+ process.exit(1);
59
87
  });
60
88
  // Handle graceful shutdown
61
89
  process.on('SIGINT', async () => {
62
90
  logger.info('Received SIGINT, shutting down gracefully');
63
- await client.disconnect();
64
- process.exit(0);
91
+ try {
92
+ await client.disconnect();
93
+ process.exit(0);
94
+ }
95
+ catch (error) {
96
+ logger.error({ error }, 'Error during shutdown');
97
+ process.exit(1);
98
+ }
65
99
  });
66
100
  process.on('SIGTERM', async () => {
67
101
  logger.info('Received SIGTERM, shutting down gracefully');
68
- await client.disconnect();
69
- process.exit(0);
102
+ try {
103
+ await client.disconnect();
104
+ process.exit(0);
105
+ }
106
+ catch (error) {
107
+ logger.error({ error }, 'Error during shutdown');
108
+ process.exit(1);
109
+ }
70
110
  });
111
+ logger.debug('Connecting to MCP transport...');
71
112
  await client.connect();
113
+ logger.info('MCP Client ready - starting server...');
72
114
  // Start the MCP server and keep process alive for stdio communication
73
115
  await client.serve();
74
116
  }
75
117
  catch (error) {
76
- logger.error({ error }, 'Fatal error');
118
+ logger.fatal({
119
+ error: error instanceof Error ? {
120
+ name: error.name,
121
+ message: error.message,
122
+ stack: error.stack
123
+ } : error
124
+ }, 'Fatal error during startup');
77
125
  process.exit(1);
78
126
  }
79
127
  }
80
128
  main().catch((error) => {
81
- console.error('Unhandled error:', error);
129
+ console.error('Unhandled startup error:', error);
82
130
  process.exit(1);
83
131
  });
package/dist/client.d.ts CHANGED
@@ -5,17 +5,28 @@ export interface CelliumMCPClientConfig {
5
5
  logger: Logger;
6
6
  retryAttempts?: number;
7
7
  retryDelay?: number;
8
+ debugMode?: boolean;
8
9
  }
9
10
  export declare class CelliumMCPClient {
10
11
  private config;
11
12
  private localServer;
13
+ private transport?;
12
14
  private isConnected;
13
15
  private reconnectTimer?;
14
16
  private keepAliveInterval?;
17
+ private transportState;
18
+ private activeRequests;
19
+ private mcpProtocolVersion;
15
20
  constructor(config: CelliumMCPClientConfig);
21
+ private logDebug;
22
+ private logRequest;
23
+ private logResponse;
24
+ private setupTransportMonitoring;
16
25
  private setupServer;
26
+ private getSafeErrorResponse;
17
27
  private makeHttpRequest;
18
28
  connect(): Promise<void>;
29
+ private setupTransportEventListeners;
19
30
  private testConnectionInBackground;
20
31
  serve(): Promise<void>;
21
32
  private testConnection;
package/dist/client.js CHANGED
@@ -44,19 +44,33 @@ const InitializedNotificationSchema = zod_1.z.object({
44
44
  class CelliumMCPClient {
45
45
  config;
46
46
  localServer;
47
+ transport;
47
48
  isConnected = false;
48
49
  reconnectTimer;
49
50
  keepAliveInterval;
51
+ transportState = {
52
+ connected: false,
53
+ lastActivity: 0,
54
+ requestCount: 0,
55
+ errorCount: 0
56
+ };
57
+ activeRequests = new Map();
58
+ mcpProtocolVersion = '2024-11-05';
50
59
  constructor(config) {
51
60
  this.config = {
52
61
  retryAttempts: 3,
53
62
  retryDelay: 1000,
63
+ debugMode: false,
54
64
  ...config
55
65
  };
66
+ this.logDebug('Initializing CelliumMCPClient', {
67
+ endpoint: this.config.endpoint,
68
+ debugMode: this.config.debugMode
69
+ });
56
70
  // Local server that interfaces with AI assistants via stdio
57
71
  this.localServer = new mcp_js_1.McpServer({
58
72
  name: 'cellium-mcp-client',
59
- version: '1.0.0'
73
+ version: '1.1.3'
60
74
  }, {
61
75
  capabilities: {
62
76
  tools: {},
@@ -64,172 +78,422 @@ class CelliumMCPClient {
64
78
  }
65
79
  });
66
80
  this.setupServer();
81
+ this.setupTransportMonitoring();
82
+ }
83
+ logDebug(message, data) {
84
+ if (this.config.debugMode) {
85
+ this.config.logger.debug({
86
+ timestamp: new Date().toISOString(),
87
+ transportState: this.transportState,
88
+ activeRequests: Array.from(this.activeRequests.entries()),
89
+ ...data
90
+ }, `[CELLIUM-MCP-DEBUG] ${message}`);
91
+ }
92
+ }
93
+ logRequest(method, id, params) {
94
+ this.transportState.requestCount++;
95
+ this.transportState.lastActivity = Date.now();
96
+ const timing = {
97
+ start: Date.now(),
98
+ method,
99
+ id
100
+ };
101
+ this.activeRequests.set(id, timing);
102
+ this.config.logger.info({
103
+ requestId: id,
104
+ method,
105
+ params,
106
+ requestCount: this.transportState.requestCount,
107
+ activeRequestCount: this.activeRequests.size
108
+ }, `[MCP-REQUEST] Received ${method}`);
109
+ this.logDebug(`Request received: ${method}`, { id, params });
110
+ }
111
+ logResponse(method, id, success, result, error) {
112
+ const timing = this.activeRequests.get(id);
113
+ const duration = timing ? Date.now() - timing.start : 0;
114
+ this.activeRequests.delete(id);
115
+ this.transportState.lastActivity = Date.now();
116
+ if (!success) {
117
+ this.transportState.errorCount++;
118
+ }
119
+ this.config.logger.info({
120
+ requestId: id,
121
+ method,
122
+ success,
123
+ duration,
124
+ result: success ? result : undefined,
125
+ error: !success ? error : undefined,
126
+ activeRequestCount: this.activeRequests.size,
127
+ totalErrors: this.transportState.errorCount
128
+ }, `[MCP-RESPONSE] Completed ${method} in ${duration}ms`);
129
+ this.logDebug(`Response sent: ${method}`, {
130
+ id,
131
+ success,
132
+ duration,
133
+ result: success ? result : undefined,
134
+ error: !success ? error : undefined
135
+ });
136
+ }
137
+ setupTransportMonitoring() {
138
+ // Monitor transport health every 10 seconds
139
+ setInterval(() => {
140
+ const now = Date.now();
141
+ const timeSinceLastActivity = now - this.transportState.lastActivity;
142
+ this.logDebug('Transport health check', {
143
+ timeSinceLastActivity,
144
+ activeRequestsCount: this.activeRequests.size,
145
+ errorRate: this.transportState.errorCount / Math.max(this.transportState.requestCount, 1)
146
+ });
147
+ // Log warning if no activity for 2 minutes
148
+ if (timeSinceLastActivity > 120000 && this.transportState.requestCount > 0) {
149
+ this.config.logger.warn({
150
+ timeSinceLastActivity,
151
+ lastActivity: new Date(this.transportState.lastActivity).toISOString()
152
+ }, 'Transport appears idle for extended period');
153
+ }
154
+ // Log active requests that are taking too long (>30s)
155
+ for (const [id, timing] of this.activeRequests.entries()) {
156
+ const requestDuration = now - timing.start;
157
+ if (requestDuration > 30000) {
158
+ this.config.logger.warn({
159
+ requestId: id,
160
+ method: timing.method,
161
+ duration: requestDuration
162
+ }, 'Long-running request detected');
163
+ }
164
+ }
165
+ }, 10000);
67
166
  }
68
167
  setupServer() {
69
- // Handle MCP initialization
70
- this.localServer.server.setRequestHandler(InitializeSchema, async (_request) => {
71
- this.config.logger.debug('Received initialize request');
72
- return {
73
- protocolVersion: '2024-11-05',
168
+ this.logDebug('Setting up MCP server handlers');
169
+ // Add error boundary wrapper for all handlers
170
+ const withErrorBoundary = (handler, methodName) => {
171
+ return async (request) => {
172
+ const requestId = Math.random().toString(36).substring(2, 15);
173
+ try {
174
+ this.logRequest(methodName, requestId, request);
175
+ const result = await handler(request);
176
+ this.logResponse(methodName, requestId, true, result);
177
+ return result;
178
+ }
179
+ catch (error) {
180
+ this.logResponse(methodName, requestId, false, undefined, error);
181
+ // Log the full error for debugging
182
+ this.config.logger.error({
183
+ error: error instanceof Error ? {
184
+ name: error.name,
185
+ message: error.message,
186
+ stack: error.stack
187
+ } : error,
188
+ methodName,
189
+ requestId,
190
+ request
191
+ }, `Error in ${methodName} handler`);
192
+ // Don't re-throw - return safe fallback instead to prevent transport closure
193
+ return this.getSafeErrorResponse(methodName, error);
194
+ }
195
+ };
196
+ };
197
+ // Handle MCP initialization with protocol version checking
198
+ this.localServer.server.setRequestHandler(InitializeSchema, withErrorBoundary(async (request) => {
199
+ this.logDebug('Processing initialize request', request);
200
+ const clientProtocolVersion = request.params?.protocolVersion;
201
+ if (clientProtocolVersion && clientProtocolVersion !== this.mcpProtocolVersion) {
202
+ this.config.logger.warn({
203
+ clientVersion: clientProtocolVersion,
204
+ serverVersion: this.mcpProtocolVersion
205
+ }, 'Protocol version mismatch detected');
206
+ }
207
+ const initResponse = {
208
+ protocolVersion: this.mcpProtocolVersion,
74
209
  capabilities: {
75
210
  tools: {},
76
211
  resources: {}
77
212
  },
78
213
  serverInfo: {
79
214
  name: 'cellium-mcp-client',
80
- version: '1.1.2'
215
+ version: '1.1.3'
81
216
  }
82
217
  };
83
- });
218
+ this.transportState.connected = true;
219
+ this.transportState.lastActivity = Date.now();
220
+ this.logDebug('Initialize response prepared', initResponse);
221
+ return initResponse;
222
+ }, 'initialize'));
84
223
  // Override the underlying server's tool request handlers to proxy to remote
85
- this.localServer.server.setRequestHandler(ToolsListSchema, async () => {
86
- try {
87
- this.config.logger.debug('Proxying tools/list to remote server');
88
- const result = await this.makeHttpRequest('tools/list', {});
89
- this.config.logger.debug({ result }, 'tools/list result from remote server');
90
- return result;
91
- }
92
- catch (error) {
93
- this.config.logger.error({ error }, 'Error proxying tools/list');
94
- // Return empty tools list instead of throwing to prevent transport closure
95
- return { tools: [] };
224
+ this.localServer.server.setRequestHandler(ToolsListSchema, withErrorBoundary(async (request) => {
225
+ this.logDebug('Processing tools/list request', request);
226
+ // Add artificial delay to test timing issues
227
+ if (this.config.debugMode) {
228
+ await new Promise(resolve => setTimeout(resolve, 100));
96
229
  }
230
+ const result = await this.makeHttpRequest('tools/list', {});
231
+ this.logDebug('tools/list completed successfully', { toolCount: result.tools?.length || 0 });
232
+ return result;
233
+ }, 'tools/list'));
234
+ this.localServer.server.setRequestHandler(ToolsCallSchema, withErrorBoundary(async (request) => {
235
+ this.logDebug('Processing tools/call request', {
236
+ toolName: request.params?.name,
237
+ hasArguments: !!request.params?.arguments
238
+ });
239
+ const result = await this.makeHttpRequest('tools/call', request.params);
240
+ this.logDebug('tools/call completed successfully', {
241
+ toolName: request.params?.name,
242
+ resultType: typeof result
243
+ });
244
+ return result;
245
+ }, 'tools/call'));
246
+ // Handle resources as well
247
+ this.localServer.server.setRequestHandler(ResourcesListSchema, withErrorBoundary(async (request) => {
248
+ this.logDebug('Processing resources/list request', request);
249
+ const result = await this.makeHttpRequest('resources/list', {});
250
+ this.logDebug('resources/list completed successfully', { resourceCount: result.resources?.length || 0 });
251
+ return result;
252
+ }, 'resources/list'));
253
+ this.localServer.server.setRequestHandler(ResourcesReadSchema, withErrorBoundary(async (request) => {
254
+ this.logDebug('Processing resources/read request', { uri: request.params?.uri });
255
+ const result = await this.makeHttpRequest('resources/read', request.params);
256
+ this.logDebug('resources/read completed successfully', { uri: request.params?.uri });
257
+ return result;
258
+ }, 'resources/read'));
259
+ // Handle ping
260
+ this.localServer.server.setRequestHandler(PingSchema, withErrorBoundary(async (request) => {
261
+ this.logDebug('Processing ping request', request);
262
+ const result = await this.makeHttpRequest('ping', {});
263
+ this.logDebug('ping completed successfully');
264
+ return result;
265
+ }, 'ping'));
266
+ // Handle other common MCP methods
267
+ this.localServer.server.setNotificationHandler(InitializedNotificationSchema, async (notification) => {
268
+ const notificationId = Math.random().toString(36).substring(2, 15);
269
+ this.logRequest('notifications/initialized', notificationId, notification);
270
+ this.logDebug('Received initialized notification', notification);
271
+ this.transportState.connected = true;
272
+ this.transportState.lastActivity = Date.now();
273
+ this.logResponse('notifications/initialized', notificationId, true);
274
+ // No response needed for notifications
97
275
  });
98
- this.localServer.server.setRequestHandler(ToolsCallSchema, async (request) => {
99
- try {
100
- this.config.logger.debug({ toolName: request.params?.name }, 'Proxying tool call to remote server');
101
- const result = await this.makeHttpRequest('tools/call', request.params);
102
- return result;
103
- }
104
- catch (error) {
105
- this.config.logger.error({ error, toolName: request.params?.name }, 'Error proxying tool call');
106
- // Return error result instead of throwing
276
+ this.logDebug('MCP server handlers setup completed');
277
+ }
278
+ getSafeErrorResponse(methodName, error) {
279
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
280
+ switch (methodName) {
281
+ case 'tools/list':
282
+ return { tools: [] };
283
+ case 'resources/list':
284
+ return { resources: [] };
285
+ case 'tools/call':
107
286
  return {
108
287
  content: [{
109
288
  type: 'text',
110
- text: `Error calling tool: ${error instanceof Error ? error.message : 'Unknown error'}`
289
+ text: `Error calling tool: ${errorMessage}`
111
290
  }],
112
291
  isError: true
113
292
  };
114
- }
115
- });
116
- // Handle resources as well
117
- this.localServer.server.setRequestHandler(ResourcesListSchema, async () => {
118
- try {
119
- this.config.logger.debug('Proxying resources/list to remote server');
120
- const result = await this.makeHttpRequest('resources/list', {});
121
- return result;
122
- }
123
- catch (error) {
124
- this.config.logger.error({ error }, 'Error proxying resources/list');
125
- // Return empty resources list instead of throwing
126
- return { resources: [] };
127
- }
128
- });
129
- this.localServer.server.setRequestHandler(ResourcesReadSchema, async (request) => {
130
- try {
131
- this.config.logger.debug({ uri: request.params?.uri }, 'Proxying resources/read to remote server');
132
- const result = await this.makeHttpRequest('resources/read', request.params);
133
- return result;
134
- }
135
- catch (error) {
136
- this.config.logger.error({ error, uri: request.params?.uri }, 'Error proxying resources/read');
137
- // Return error result instead of throwing
293
+ case 'resources/read':
138
294
  return {
139
295
  contents: [{
140
- uri: request.params?.uri || '',
296
+ uri: '',
141
297
  mimeType: 'text/plain',
142
- text: `Error reading resource: ${error instanceof Error ? error.message : 'Unknown error'}`
298
+ text: `Error reading resource: ${errorMessage}`
143
299
  }]
144
300
  };
145
- }
146
- });
147
- // Handle ping
148
- this.localServer.server.setRequestHandler(PingSchema, async () => {
149
- try {
150
- const result = await this.makeHttpRequest('ping', {});
151
- return result;
152
- }
153
- catch (error) {
154
- this.config.logger.error({ error }, 'Error proxying ping');
155
- // Return empty result instead of throwing
301
+ case 'ping':
156
302
  return {};
157
- }
158
- });
159
- // Handle other common MCP methods
160
- this.localServer.server.setNotificationHandler(InitializedNotificationSchema, async () => {
161
- this.config.logger.debug('Received initialized notification');
162
- // No response needed for notifications
163
- });
303
+ default:
304
+ return { error: errorMessage };
305
+ }
164
306
  }
165
307
  async makeHttpRequest(method, params) {
308
+ const requestId = Math.random().toString(36).substring(2, 15);
309
+ const startTime = Date.now();
310
+ this.logDebug(`Starting HTTP request for ${method}`, {
311
+ requestId,
312
+ params,
313
+ isConnected: this.isConnected,
314
+ endpoint: this.config.endpoint
315
+ });
166
316
  // If not connected, try to connect first
167
317
  if (!this.isConnected) {
168
318
  try {
319
+ this.logDebug('Not connected, attempting connection test');
169
320
  await this.testConnection();
170
321
  this.isConnected = true;
171
322
  this.config.logger.info('Connected to remote Cellium server');
172
323
  }
173
324
  catch (error) {
174
- this.config.logger.error({ error }, 'Failed to connect to remote server');
325
+ this.config.logger.error({
326
+ error: error instanceof Error ? {
327
+ name: error.name,
328
+ message: error.message,
329
+ stack: error.stack
330
+ } : error
331
+ }, 'Failed to connect to remote server');
175
332
  throw new Error('Cannot connect to remote Cellium server');
176
333
  }
177
334
  }
178
335
  const mcpEndpoint = this.config.endpoint.replace('/sse', '/mcp');
179
336
  const requestBody = {
180
337
  jsonrpc: '2.0',
181
- id: Math.random().toString(36).substring(2, 15),
338
+ id: requestId,
182
339
  method,
183
340
  params
184
341
  };
185
- this.config.logger.debug({ method, endpoint: mcpEndpoint }, 'Making HTTP request to remote server');
342
+ this.logDebug(`Making HTTP request to ${mcpEndpoint}`, {
343
+ requestId,
344
+ method,
345
+ bodySize: JSON.stringify(requestBody).length
346
+ });
347
+ let response;
348
+ let responseText;
186
349
  try {
187
- const response = await fetch(mcpEndpoint, {
350
+ response = await fetch(mcpEndpoint, {
188
351
  method: 'POST',
189
352
  headers: {
190
353
  'Authorization': `Bearer ${this.config.token}`,
191
- 'Content-Type': 'application/json'
354
+ 'Content-Type': 'application/json',
355
+ 'User-Agent': 'cellium-mcp-client/1.1.3'
192
356
  },
193
357
  body: JSON.stringify(requestBody)
194
358
  });
359
+ responseText = await response.text();
360
+ const duration = Date.now() - startTime;
361
+ this.logDebug('HTTP response received', {
362
+ requestId,
363
+ method,
364
+ status: response.status,
365
+ statusText: response.statusText,
366
+ headers: Object.fromEntries(response.headers.entries()),
367
+ responseSize: responseText.length,
368
+ duration
369
+ });
195
370
  if (!response.ok) {
196
371
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
197
372
  }
198
- const jsonResponse = await response.json();
373
+ let jsonResponse;
374
+ try {
375
+ jsonResponse = JSON.parse(responseText);
376
+ }
377
+ catch (parseError) {
378
+ this.config.logger.error({
379
+ requestId,
380
+ method,
381
+ responseText: responseText.substring(0, 500),
382
+ parseError: parseError instanceof Error ? parseError.message : parseError
383
+ }, 'Failed to parse JSON response');
384
+ throw new Error('Invalid JSON response from server');
385
+ }
386
+ this.logDebug('HTTP response parsed', {
387
+ requestId,
388
+ method,
389
+ hasResult: !!jsonResponse.result,
390
+ hasError: !!jsonResponse.error,
391
+ jsonrpcId: jsonResponse.id
392
+ });
199
393
  if (jsonResponse.error) {
394
+ this.config.logger.error({
395
+ requestId,
396
+ method,
397
+ serverError: jsonResponse.error
398
+ }, 'Remote server returned error');
200
399
  throw new Error(`Remote server error: ${jsonResponse.error.message}`);
201
400
  }
202
401
  return jsonResponse.result;
203
402
  }
204
403
  catch (error) {
205
- this.config.logger.error({ error, method }, 'HTTP request to remote server failed');
404
+ const duration = Date.now() - startTime;
405
+ this.config.logger.error({
406
+ error: error instanceof Error ? {
407
+ name: error.name,
408
+ message: error.message,
409
+ stack: error.stack
410
+ } : error,
411
+ requestId,
412
+ method,
413
+ duration,
414
+ endpoint: mcpEndpoint
415
+ }, 'HTTP request to remote server failed');
206
416
  this.isConnected = false; // Mark as disconnected on error
207
417
  throw error;
208
418
  }
209
419
  }
210
420
  async connect() {
211
421
  try {
212
- this.config.logger.info({ endpoint: this.config.endpoint }, 'Starting Cellium MCP Server');
422
+ this.config.logger.info({
423
+ endpoint: this.config.endpoint,
424
+ debugMode: this.config.debugMode,
425
+ protocolVersion: this.mcpProtocolVersion
426
+ }, 'Starting Cellium MCP Server');
427
+ this.logDebug('Initializing stdio transport');
213
428
  // Set up the stdio transport for local MCP server immediately
214
- const stdioTransport = new stdio_js_1.StdioServerTransport();
215
- await this.localServer.connect(stdioTransport);
429
+ this.transport = new stdio_js_1.StdioServerTransport();
430
+ // Add transport event monitoring
431
+ this.setupTransportEventListeners(this.transport);
432
+ await this.localServer.connect(this.transport);
433
+ this.transportState.connected = true;
434
+ this.transportState.lastActivity = Date.now();
216
435
  this.config.logger.info('MCP Server connected and ready');
436
+ this.logDebug('Transport connected successfully', {
437
+ transportType: 'stdio',
438
+ serverName: 'cellium-mcp-client',
439
+ serverVersion: '1.1.3'
440
+ });
217
441
  // Keep the process alive with a minimal interval
218
442
  // This ensures the process doesn't exit when stdin closes
219
443
  this.keepAliveInterval = setInterval(() => {
220
- // Do nothing - just keep the event loop alive
444
+ this.logDebug('Keep-alive tick', {
445
+ uptime: Date.now() - this.transportState.lastActivity,
446
+ activeRequests: this.activeRequests.size
447
+ });
221
448
  }, 30000); // Check every 30 seconds
222
449
  this.config.logger.debug('Keep-alive interval started for persistent MCP communication');
223
450
  // Test connection to remote server in background, but don't block startup
224
451
  this.testConnectionInBackground();
225
452
  }
226
453
  catch (error) {
227
- this.config.logger.error({ error }, 'Failed to start MCP server');
454
+ this.config.logger.error({
455
+ error: error instanceof Error ? {
456
+ name: error.name,
457
+ message: error.message,
458
+ stack: error.stack
459
+ } : error
460
+ }, 'Failed to start MCP server');
461
+ this.transportState.connected = false;
228
462
  throw error;
229
463
  }
230
464
  }
465
+ setupTransportEventListeners(transport) {
466
+ this.logDebug('Setting up transport event listeners');
467
+ // Monitor transport events if available
468
+ try {
469
+ // Try to access transport events (may not be available in all SDK versions)
470
+ const transportAny = transport;
471
+ if (transportAny.on && typeof transportAny.on === 'function') {
472
+ transportAny.on('close', () => {
473
+ this.config.logger.warn('Transport close event detected');
474
+ this.transportState.connected = false;
475
+ });
476
+ transportAny.on('error', (error) => {
477
+ this.config.logger.error({ error }, 'Transport error event');
478
+ this.transportState.errorCount++;
479
+ });
480
+ transportAny.on('connect', () => {
481
+ this.config.logger.info('Transport connect event');
482
+ this.transportState.connected = true;
483
+ });
484
+ this.logDebug('Transport event listeners attached');
485
+ }
486
+ else {
487
+ this.logDebug('Transport does not support event listeners');
488
+ }
489
+ }
490
+ catch (error) {
491
+ this.logDebug('Could not attach transport event listeners', { error });
492
+ }
493
+ }
231
494
  async testConnectionInBackground() {
232
495
  try {
496
+ this.logDebug('Testing connection to remote server in background');
233
497
  await this.testConnection();
234
498
  this.isConnected = true;
235
499
  this.config.logger.info('Connected to remote Cellium server');
@@ -239,7 +503,9 @@ class CelliumMCPClient {
239
503
  }
240
504
  }
241
505
  catch (error) {
242
- this.config.logger.warn({ error }, 'Failed to connect to remote server, will retry on first request');
506
+ this.config.logger.warn({
507
+ error: error instanceof Error ? error.message : error
508
+ }, 'Failed to connect to remote server, will retry on first request');
243
509
  this.isConnected = false;
244
510
  }
245
511
  }
@@ -247,7 +513,22 @@ class CelliumMCPClient {
247
513
  // The MCP server is already connected via stdio in connect()
248
514
  // Keep the process alive indefinitely for persistent MCP communication
249
515
  // This ensures compatibility with MCP clients like Copilot that expect long-running servers
250
- this.config.logger.debug('Server is now serving and will stay alive for persistent MCP communication');
516
+ this.config.logger.debug({
517
+ transportConnected: this.transportState.connected,
518
+ serverName: 'cellium-mcp-client'
519
+ }, 'Server is now serving and will stay alive for persistent MCP communication');
520
+ this.logDebug('Entering serve mode - process will stay alive');
521
+ // Set up graceful shutdown handlers
522
+ process.on('SIGINT', async () => {
523
+ this.config.logger.info('Received SIGINT, shutting down gracefully...');
524
+ await this.disconnect();
525
+ process.exit(0);
526
+ });
527
+ process.on('SIGTERM', async () => {
528
+ this.config.logger.info('Received SIGTERM, shutting down gracefully...');
529
+ await this.disconnect();
530
+ process.exit(0);
531
+ });
251
532
  // Return a promise that never resolves to keep the process alive
252
533
  // The process will only exit via SIGINT/SIGTERM signals
253
534
  return new Promise(() => {
@@ -256,20 +537,32 @@ class CelliumMCPClient {
256
537
  });
257
538
  }
258
539
  async testConnection() {
540
+ const startTime = Date.now();
259
541
  const mcpEndpoint = this.config.endpoint.replace('/sse', '/mcp');
542
+ this.logDebug('Testing connection', {
543
+ endpoint: mcpEndpoint,
544
+ hasToken: !!this.config.token
545
+ });
260
546
  const response = await fetch(mcpEndpoint, {
261
547
  method: 'POST',
262
548
  headers: {
263
549
  'Authorization': `Bearer ${this.config.token}`,
264
- 'Content-Type': 'application/json'
550
+ 'Content-Type': 'application/json',
551
+ 'User-Agent': 'cellium-mcp-client/1.1.3'
265
552
  },
266
553
  body: JSON.stringify({
267
554
  jsonrpc: '2.0',
268
- id: 'test-connection',
555
+ id: 'connection-test',
269
556
  method: 'ping',
270
557
  params: {}
271
558
  })
272
559
  });
560
+ const duration = Date.now() - startTime;
561
+ this.logDebug('Connection test response', {
562
+ status: response.status,
563
+ statusText: response.statusText,
564
+ duration
565
+ });
273
566
  if (!response.ok) {
274
567
  throw new Error(`Connection test failed: HTTP ${response.status}`);
275
568
  }
@@ -277,11 +570,26 @@ class CelliumMCPClient {
277
570
  if (result.error) {
278
571
  throw new Error(`Connection test failed: ${result.error.message}`);
279
572
  }
280
- this.config.logger.debug('Connection test successful');
573
+ this.config.logger.debug({ duration }, 'Connection test successful');
281
574
  }
282
575
  async disconnect() {
283
576
  this.config.logger.info('Disconnecting from Cellium MCP Server');
577
+ this.logDebug('Starting disconnect process', {
578
+ activeRequestCount: this.activeRequests.size,
579
+ transportConnected: this.transportState.connected
580
+ });
284
581
  this.isConnected = false;
582
+ this.transportState.connected = false;
583
+ // Cancel any active requests
584
+ if (this.activeRequests.size > 0) {
585
+ this.config.logger.warn({
586
+ activeRequestCount: this.activeRequests.size
587
+ }, 'Cancelling active requests during disconnect');
588
+ for (const [id, timing] of this.activeRequests.entries()) {
589
+ this.logResponse(timing.method, id, false, undefined, 'Cancelled due to disconnect');
590
+ }
591
+ this.activeRequests.clear();
592
+ }
285
593
  if (this.reconnectTimer) {
286
594
  clearTimeout(this.reconnectTimer);
287
595
  this.reconnectTimer = undefined;
@@ -290,8 +598,20 @@ class CelliumMCPClient {
290
598
  clearInterval(this.keepAliveInterval);
291
599
  this.keepAliveInterval = undefined;
292
600
  }
293
- await this.localServer.close();
294
- this.config.logger.info('Disconnected successfully');
601
+ try {
602
+ await this.localServer.close();
603
+ this.logDebug('Local MCP server closed successfully');
604
+ }
605
+ catch (error) {
606
+ this.config.logger.error({
607
+ error: error instanceof Error ? error.message : error
608
+ }, 'Error closing local MCP server');
609
+ }
610
+ this.config.logger.info({
611
+ totalRequests: this.transportState.requestCount,
612
+ totalErrors: this.transportState.errorCount,
613
+ uptime: Date.now() - this.transportState.lastActivity
614
+ }, 'Disconnected successfully');
295
615
  }
296
616
  }
297
617
  exports.CelliumMCPClient = CelliumMCPClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cellium-mcp-client",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "MCP client for connecting to remote Cellium processor server",
5
5
  "main": "dist/index.js",
6
6
  "bin": {