cellium-mcp-client 2.0.9 → 2.0.10

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/client.d.ts CHANGED
@@ -31,6 +31,11 @@ export declare class CelliumMCPClient {
31
31
  private setupServer;
32
32
  private getSafeErrorResponse;
33
33
  private makeHttpRequest;
34
+ private getErrorStatusCode;
35
+ private createClassifiedRequestError;
36
+ private extractBackendReason;
37
+ private classifyDenialCondition;
38
+ private getSafeDeniedMessage;
34
39
  connect(): Promise<void>;
35
40
  private setupTransportEventListeners;
36
41
  serve(): Promise<void>;
package/dist/client.js CHANGED
@@ -307,6 +307,12 @@ class CelliumMCPClient {
307
307
  }
308
308
  getSafeErrorResponse(methodName, error) {
309
309
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
310
+ const classification = this.classifyDenialCondition({
311
+ statusCode: this.getErrorStatusCode(error),
312
+ message: errorMessage
313
+ });
314
+ const safeDeniedMessage = this.getSafeDeniedMessage(classification, errorMessage);
315
+ const responseErrorMessage = classification.isDenial ? safeDeniedMessage : errorMessage;
310
316
  switch (methodName) {
311
317
  case 'tools/list':
312
318
  return { tools: [] };
@@ -316,7 +322,7 @@ class CelliumMCPClient {
316
322
  return {
317
323
  content: [{
318
324
  type: 'text',
319
- text: `Error calling tool: ${errorMessage}`
325
+ text: `Error calling tool: ${safeDeniedMessage}`
320
326
  }],
321
327
  isError: true
322
328
  };
@@ -325,13 +331,13 @@ class CelliumMCPClient {
325
331
  contents: [{
326
332
  uri: '',
327
333
  mimeType: 'text/plain',
328
- text: `Error reading resource: ${errorMessage}`
334
+ text: `Error reading resource: ${safeDeniedMessage}`
329
335
  }]
330
336
  };
331
337
  case 'ping':
332
338
  return {};
333
339
  default:
334
- return { error: errorMessage };
340
+ return { error: responseErrorMessage };
335
341
  }
336
342
  }
337
343
  async makeHttpRequest(method, params) {
@@ -409,7 +415,19 @@ class CelliumMCPClient {
409
415
  duration
410
416
  });
411
417
  if (!response.ok) {
412
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
418
+ const classification = this.classifyDenialCondition({
419
+ statusCode: response.status,
420
+ message: response.statusText,
421
+ responseText
422
+ });
423
+ const backendReason = this.extractBackendReason(responseText);
424
+ const safeMessage = this.getSafeDeniedMessage({ ...classification, reason: backendReason ?? classification.reason }, `HTTP ${response.status}: ${response.statusText}`);
425
+ throw this.createClassifiedRequestError(safeMessage, {
426
+ statusCode: response.status,
427
+ denialCategory: classification.category,
428
+ denialReason: backendReason ?? classification.reason,
429
+ rawResponseSnippet: responseText.substring(0, 1000)
430
+ });
413
431
  }
414
432
  let jsonResponse;
415
433
  try {
@@ -432,12 +450,30 @@ class CelliumMCPClient {
432
450
  jsonrpcId: jsonResponse.id
433
451
  });
434
452
  if (jsonResponse.error) {
453
+ const classification = this.classifyDenialCondition({
454
+ statusCode: response.status,
455
+ message: jsonResponse.error?.message,
456
+ responseText,
457
+ remoteError: jsonResponse.error
458
+ });
459
+ const backendReason = this.extractBackendReason(jsonResponse.error) ?? this.extractBackendReason(responseText);
460
+ const safeMessage = this.getSafeDeniedMessage({ ...classification, reason: backendReason ?? classification.reason }, 'Remote server error');
435
461
  this.config.logger.error({
436
462
  requestId,
437
463
  method,
438
- serverError: jsonResponse.error
464
+ statusCode: response.status,
465
+ serverError: jsonResponse.error,
466
+ denialCategory: classification.category,
467
+ denialReason: backendReason ?? classification.reason,
468
+ rawResponseSnippet: responseText.substring(0, 1000)
439
469
  }, 'Remote server returned error');
440
- throw new Error(`Remote server error: ${jsonResponse.error.message}`);
470
+ throw this.createClassifiedRequestError(safeMessage, {
471
+ statusCode: response.status,
472
+ denialCategory: classification.category,
473
+ denialReason: backendReason ?? classification.reason,
474
+ rawResponseSnippet: responseText.substring(0, 1000),
475
+ remoteError: jsonResponse.error
476
+ });
441
477
  }
442
478
  // Mark as connected on first successful response for http-stream endpoints
443
479
  if (!this.isConnected && this.config.endpoint.includes('/http-stream')) {
@@ -449,6 +485,7 @@ class CelliumMCPClient {
449
485
  }
450
486
  catch (error) {
451
487
  const duration = Date.now() - startTime;
488
+ const classifiedError = error;
452
489
  this.config.logger.error({
453
490
  error: error instanceof Error ? {
454
491
  name: error.name,
@@ -458,12 +495,107 @@ class CelliumMCPClient {
458
495
  requestId,
459
496
  method,
460
497
  duration,
461
- endpoint: endpoint
498
+ endpoint: endpoint,
499
+ statusCode: classifiedError?.statusCode,
500
+ denialCategory: classifiedError?.denialCategory,
501
+ denialReason: classifiedError?.denialReason,
502
+ rawResponseSnippet: classifiedError?.rawResponseSnippet,
503
+ remoteError: classifiedError?.remoteError
462
504
  }, 'HTTP request to remote server failed');
463
505
  this.isConnected = false; // Mark as disconnected on error
464
506
  throw error;
465
507
  }
466
508
  }
509
+ getErrorStatusCode(error) {
510
+ if (!error || typeof error !== 'object') {
511
+ return undefined;
512
+ }
513
+ const possibleStatus = error.statusCode;
514
+ return typeof possibleStatus === 'number' ? possibleStatus : undefined;
515
+ }
516
+ createClassifiedRequestError(message, details) {
517
+ const classifiedError = new Error(message);
518
+ Object.assign(classifiedError, details);
519
+ return classifiedError;
520
+ }
521
+ extractBackendReason(input) {
522
+ if (!input) {
523
+ return undefined;
524
+ }
525
+ const normalize = (value) => {
526
+ if (typeof value !== 'string') {
527
+ return undefined;
528
+ }
529
+ const trimmed = value.trim();
530
+ return trimmed.length > 0 ? trimmed.substring(0, 300) : undefined;
531
+ };
532
+ if (typeof input === 'string') {
533
+ const direct = normalize(input);
534
+ if (direct) {
535
+ try {
536
+ const parsed = JSON.parse(input);
537
+ return normalize(parsed?.error?.message)
538
+ ?? normalize(parsed?.error?.data?.reason)
539
+ ?? normalize(parsed?.error?.data?.message)
540
+ ?? normalize(parsed?.message)
541
+ ?? normalize(parsed?.reason)
542
+ ?? direct;
543
+ }
544
+ catch {
545
+ return direct;
546
+ }
547
+ }
548
+ return undefined;
549
+ }
550
+ if (typeof input === 'object') {
551
+ const value = input;
552
+ return normalize(value?.error?.message)
553
+ ?? normalize(value?.error?.data?.reason)
554
+ ?? normalize(value?.error?.data?.message)
555
+ ?? normalize(value?.message)
556
+ ?? normalize(value?.reason)
557
+ ?? normalize(value?.detail)
558
+ ?? normalize(value?.error_description);
559
+ }
560
+ return undefined;
561
+ }
562
+ classifyDenialCondition(params) {
563
+ const backendReason = this.extractBackendReason(params.remoteError)
564
+ ?? this.extractBackendReason(params.responseText)
565
+ ?? this.extractBackendReason(params.message);
566
+ const text = `${params.message ?? ''} ${params.responseText ?? ''} ${backendReason ?? ''}`.toLowerCase();
567
+ const statusCode = params.statusCode;
568
+ const hasSessionPattern = /(session|concurrent|too many.*session|max.*session)/i.test(text);
569
+ const hasQuotaPattern = /(quota|rate.?limit|too many requests|credit|usage.*limit|limit exceeded)/i.test(text);
570
+ const hasPermissionPattern = /(forbidden|access denied|permission denied|insufficient permissions?|not authorized|not allowed|requires? (?:a )?(?:higher )?(?:plan|tier|subscription)|upgrade (?:your )?(?:plan|subscription)|subscription (?:required|inactive|expired)|plan (?:required|does not include)|tier (?:required|does not include))/i.test(text);
571
+ const hasAuthPattern = /(unauthorized|invalid token|expired token|authentication|auth failed|bearer)/i.test(text);
572
+ if (statusCode === 401 || hasAuthPattern) {
573
+ return { isDenial: true, category: 'auth', reason: backendReason, statusCode };
574
+ }
575
+ if (hasSessionPattern) {
576
+ return { isDenial: true, category: 'session_limit', reason: backendReason, statusCode };
577
+ }
578
+ if (statusCode === 429 || hasQuotaPattern) {
579
+ return { isDenial: true, category: 'quota', reason: backendReason, statusCode };
580
+ }
581
+ if (statusCode === 403 || hasPermissionPattern) {
582
+ return { isDenial: true, category: 'permission_tier', reason: backendReason, statusCode };
583
+ }
584
+ return { isDenial: false, reason: backendReason, statusCode };
585
+ }
586
+ getSafeDeniedMessage(classification, fallbackMessage) {
587
+ if (!classification.isDenial) {
588
+ return fallbackMessage;
589
+ }
590
+ const categoryLabel = {
591
+ auth: 'authentication failed',
592
+ session_limit: 'session limit reached',
593
+ permission_tier: 'permission or subscription tier restriction',
594
+ quota: 'quota or rate limit exceeded'
595
+ };
596
+ const prefix = categoryLabel[classification.category];
597
+ return `Request denied (${prefix})`;
598
+ }
467
599
  async connect() {
468
600
  try {
469
601
  const transportMode = this.config.useStdio ? 'stdio' : 'http-streaming';
@@ -658,12 +790,55 @@ class CelliumMCPClient {
658
790
  statusText: response.statusText,
659
791
  duration
660
792
  });
793
+ const responseText = await response.text();
661
794
  if (!response.ok) {
662
- throw new Error(`Connection test failed: HTTP ${response.status}`);
795
+ const classification = this.classifyDenialCondition({
796
+ statusCode: response.status,
797
+ message: response.statusText,
798
+ responseText
799
+ });
800
+ const backendReason = this.extractBackendReason(responseText);
801
+ const safeMessage = this.getSafeDeniedMessage({ ...classification, reason: backendReason ?? classification.reason }, `HTTP ${response.status}: ${response.statusText}`);
802
+ this.config.logger.error({
803
+ statusCode: response.status,
804
+ statusText: response.statusText,
805
+ denialCategory: classification.category,
806
+ denialReason: backendReason ?? classification.reason,
807
+ rawResponseSnippet: responseText.substring(0, 1000)
808
+ }, 'Connection test received non-OK response');
809
+ throw new Error(`Connection test failed: ${safeMessage}`);
810
+ }
811
+ let result;
812
+ try {
813
+ result = JSON.parse(responseText);
814
+ }
815
+ catch (parseError) {
816
+ this.config.logger.error({
817
+ statusCode: response.status,
818
+ parseError: parseError instanceof Error ? parseError.message : parseError,
819
+ rawResponseSnippet: responseText.substring(0, 1000)
820
+ }, 'Connection test response was not valid JSON');
821
+ throw new Error('Connection test failed: Invalid JSON response from server');
663
822
  }
664
- const result = await response.json();
665
823
  if (result.error) {
666
- throw new Error(`Connection test failed: ${result.error.message}`);
824
+ const backendReason = this.extractBackendReason(result.error) ?? this.extractBackendReason(responseText);
825
+ const rawErrorMessage = backendReason
826
+ ?? (typeof result.error?.message === 'string' ? result.error.message : 'Remote server error');
827
+ const classification = this.classifyDenialCondition({
828
+ statusCode: response.status,
829
+ message: rawErrorMessage,
830
+ responseText,
831
+ remoteError: result.error
832
+ });
833
+ const safeMessage = this.getSafeDeniedMessage({ ...classification, reason: backendReason ?? classification.reason }, 'Remote server returned an error during startup');
834
+ this.config.logger.error({
835
+ statusCode: response.status,
836
+ denialCategory: classification.category,
837
+ denialReason: backendReason ?? classification.reason,
838
+ remoteError: result.error,
839
+ rawResponseSnippet: responseText.substring(0, 1000)
840
+ }, 'Connection test returned JSON-RPC error');
841
+ throw new Error(`Connection test failed: ${safeMessage}`);
667
842
  }
668
843
  this.config.logger.debug({ duration }, 'Connection test successful');
669
844
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cellium-mcp-client",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
4
4
  "description": "MCP client for connecting to remote Cellium processor server",
5
5
  "main": "dist/index.js",
6
6
  "bin": {