@app-connect/core 1.7.10 → 1.7.11

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.
Files changed (55) hide show
  1. package/connector/developerPortal.js +43 -0
  2. package/connector/proxy/index.js +10 -3
  3. package/connector/registry.js +8 -6
  4. package/handlers/admin.js +44 -21
  5. package/handlers/auth.js +89 -67
  6. package/handlers/calldown.js +10 -4
  7. package/handlers/contact.js +4 -104
  8. package/handlers/disposition.js +4 -142
  9. package/handlers/log.js +172 -257
  10. package/handlers/user.js +19 -6
  11. package/index.js +213 -47
  12. package/lib/analytics.js +3 -1
  13. package/lib/authSession.js +68 -0
  14. package/lib/callLogComposer.js +498 -420
  15. package/lib/errorHandler.js +206 -0
  16. package/lib/jwt.js +2 -0
  17. package/lib/logger.js +190 -0
  18. package/lib/oauth.js +21 -10
  19. package/lib/ringcentral.js +2 -10
  20. package/lib/sharedSMSComposer.js +471 -0
  21. package/mcp/SupportedPlatforms.md +12 -0
  22. package/mcp/lib/validator.js +91 -0
  23. package/mcp/mcpHandler.js +166 -0
  24. package/mcp/tools/checkAuthStatus.js +90 -0
  25. package/mcp/tools/collectAuthInfo.js +86 -0
  26. package/mcp/tools/createCallLog.js +299 -0
  27. package/mcp/tools/createMessageLog.js +283 -0
  28. package/mcp/tools/doAuth.js +185 -0
  29. package/mcp/tools/findContactByName.js +87 -0
  30. package/mcp/tools/findContactByPhone.js +96 -0
  31. package/mcp/tools/getCallLog.js +98 -0
  32. package/mcp/tools/getHelp.js +39 -0
  33. package/mcp/tools/getPublicConnectors.js +46 -0
  34. package/mcp/tools/index.js +58 -0
  35. package/mcp/tools/logout.js +63 -0
  36. package/mcp/tools/rcGetCallLogs.js +73 -0
  37. package/mcp/tools/setConnector.js +64 -0
  38. package/mcp/tools/updateCallLog.js +122 -0
  39. package/models/cacheModel.js +3 -0
  40. package/package.json +71 -70
  41. package/releaseNotes.json +12 -0
  42. package/test/handlers/log.test.js +6 -2
  43. package/test/lib/logger.test.js +206 -0
  44. package/test/lib/sharedSMSComposer.test.js +1084 -0
  45. package/test/mcp/tools/collectAuthInfo.test.js +192 -0
  46. package/test/mcp/tools/createCallLog.test.js +412 -0
  47. package/test/mcp/tools/createMessageLog.test.js +580 -0
  48. package/test/mcp/tools/doAuth.test.js +363 -0
  49. package/test/mcp/tools/findContactByName.test.js +263 -0
  50. package/test/mcp/tools/findContactByPhone.test.js +284 -0
  51. package/test/mcp/tools/getCallLog.test.js +286 -0
  52. package/test/mcp/tools/getPublicConnectors.test.js +128 -0
  53. package/test/mcp/tools/logout.test.js +169 -0
  54. package/test/mcp/tools/setConnector.test.js +177 -0
  55. package/test/mcp/tools/updateCallLog.test.js +346 -0
package/index.js CHANGED
@@ -6,7 +6,6 @@ const dynamoose = require('dynamoose');
6
6
  const axios = require('axios');
7
7
  const { UserModel } = require('./models/userModel');
8
8
  const { CallDownListModel } = require('./models/callDownListModel');
9
- const { Op } = require('sequelize');
10
9
  const { CallLogModel } = require('./models/callLogModel');
11
10
  const { MessageLogModel } = require('./models/messageLogModel');
12
11
  const { AdminConfigModel } = require('./models/adminConfigModel');
@@ -26,14 +25,19 @@ const analytics = require('./lib/analytics');
26
25
  const util = require('./lib/util');
27
26
  const connectorRegistry = require('./connector/registry');
28
27
  const calldown = require('./handlers/calldown');
28
+ const mcpHandler = require('./mcp/mcpHandler');
29
+ const logger = require('./lib/logger');
29
30
  const { DebugTracer } = require('./lib/debugTracer');
30
31
  const s3ErrorLogReport = require('./lib/s3ErrorLogReport');
32
+ const { handleDatabaseError } = require('./lib/errorHandler');
33
+ const { updateAuthSession } = require('./lib/authSession');
31
34
 
32
35
  let packageJson = null;
33
36
  try {
34
37
  packageJson = require('./package.json');
35
38
  }
36
39
  catch (e) {
40
+ logger.error('Error loading package.json', { stack: e.stack });
37
41
  packageJson = require('../package.json');
38
42
  }
39
43
 
@@ -52,7 +56,7 @@ axios.defaults.headers.common['Unified-CRM-Extension-Version'] = packageJson.ver
52
56
 
53
57
  async function initDB() {
54
58
  if (!process.env.DISABLE_SYNC_DB_TABLE) {
55
- console.log('creating db tables if not exist...');
59
+ logger.info('creating db tables if not exist...');
56
60
  await UserModel.sync();
57
61
  await CallLogModel.sync();
58
62
  await MessageLogModel.sync();
@@ -129,6 +133,7 @@ function createCoreRouter() {
129
133
  }
130
134
  }
131
135
  catch (e) {
136
+ logger.error('Error getting crm manifest', { stack: e.stack });
132
137
  res.status(400).send('Platform not found');
133
138
  }
134
139
  });
@@ -281,7 +286,7 @@ function createCoreRouter() {
281
286
  }
282
287
  }
283
288
  catch (e) {
284
- console.log(`platform: ${platformName} \n${e.stack}`);
289
+ logger.error('Auth validation failed', { platform: platformName, stack: e.stack });
285
290
  tracer?.traceError('authValidation:error', e);
286
291
  statusCode = e.response?.status ?? 'unknown';
287
292
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
@@ -333,7 +338,7 @@ function createCoreRouter() {
333
338
  }
334
339
  }
335
340
  catch (e) {
336
- console.log(`${e.stack}`);
341
+ logger.error('Set admin settings failed', { stack: e.stack });
337
342
  tracer?.traceError('setAdminSettings:error', e);
338
343
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
339
344
  success = false;
@@ -458,7 +463,7 @@ function createCoreRouter() {
458
463
  }
459
464
  }
460
465
  catch (e) {
461
- console.log(`${e.stack}`);
466
+ logger.error('Get user mapping failed', { stack: e.stack });
462
467
  tracer?.traceError('getUserMapping:error', e);
463
468
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
464
469
  }
@@ -509,7 +514,7 @@ function createCoreRouter() {
509
514
  success = true;
510
515
  }
511
516
  catch (e) {
512
- console.log(`${e.stack}`);
517
+ logger.error('Get server logging settings failed', { stack: e.stack });
513
518
  tracer?.traceError('getServerLoggingSettings:error', e);
514
519
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
515
520
  }
@@ -565,7 +570,7 @@ function createCoreRouter() {
565
570
  success = true;
566
571
  }
567
572
  catch (e) {
568
- console.log(`${e.stack}`);
573
+ logger.error('Set server logging settings failed', { stack: e.stack });
569
574
  tracer?.traceError('setServerLoggingSettings:error', e);
570
575
  res.status(400).send(tracer ? tracer.wrapResponse({ successful: false, returnMessage: { messageType: 'warning', message: 'Server logging settings update failed', ttl: 5000 } }) : { successful: false, returnMessage: { messageType: 'warning', message: 'Server logging settings update failed', ttl: 5000 } });
571
576
  success = false;
@@ -601,7 +606,7 @@ function createCoreRouter() {
601
606
  }
602
607
  }
603
608
  catch (e) {
604
- console.log(`${e.stack}`);
609
+ logger.error('Get user preload settings failed', { stack: e.stack });
605
610
  tracer?.traceError('getUserSettingsByAdmin:error', e);
606
611
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
607
612
  }
@@ -640,7 +645,7 @@ function createCoreRouter() {
640
645
  }
641
646
  }
642
647
  catch (e) {
643
- console.log(`platform: ${platformName} \n${e.stack}`);
648
+ logger.error('Get user settings failed', { platform: platformName, stack: e.stack });
644
649
  tracer?.traceError('getUserSettings:error', e, { platform: platformName });
645
650
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
646
651
  }
@@ -693,7 +698,7 @@ function createCoreRouter() {
693
698
  }
694
699
  }
695
700
  catch (e) {
696
- console.log(`platform: ${platformName} \n${e.stack}`);
701
+ logger.error('Set user settings failed', { platform: platformName, stack: e.stack });
697
702
  tracer?.traceError('setUserSettings:error', e, { platform: platformName });
698
703
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
699
704
  }
@@ -733,7 +738,7 @@ function createCoreRouter() {
733
738
  }
734
739
  }
735
740
  catch (e) {
736
- console.log(`${e.stack}`);
741
+ logger.error('Get hostname failed', { stack: e.stack });
737
742
  tracer?.traceError('hostname:error', e);
738
743
  res.status(500).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
739
744
  }
@@ -744,17 +749,28 @@ function createCoreRouter() {
744
749
  tracer?.trace('oauth-callback:start', { query: req.query });
745
750
  let platformName = null;
746
751
  let success = false;
752
+ let sessionId = null;
747
753
  const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
748
754
  try {
749
755
  if (!req.query?.callbackUri || req.query.callbackUri === 'undefined') {
750
- tracer?.trace('oauth-callback:missingCallbackUri', {});
751
- res.status(400).send(tracer ? tracer.wrapResponse('Missing callbackUri') : 'Missing callbackUri');
752
- return;
756
+ // case: from mcp
757
+ if (req.query.code) {
758
+ // eslint-disable-next-line no-param-reassign
759
+ req.query.callbackUri = `${process.env.APP_SERVER}/oauth-callback?code=${req.query.code}`;
760
+ }
761
+ else {
762
+ tracer?.trace('oauth-callback:missingCallbackUri', {});
763
+ res.status(400).send(tracer ? tracer.wrapResponse('Missing callbackUri') : 'Missing callbackUri');
764
+ return;
765
+ }
753
766
  }
754
- platformName = req.query.state ?
755
- req.query.state.split('platform=')[1] :
756
- decodeURIComponent(decodeURIComponent(req.originalUrl).split('state=')[1].split('&')[0]).split('platform=')[1];
757
- const hostname = req.query.hostname;
767
+ const state = new URL(req.query.callbackUri).searchParams.get('state') ?? req.query.state;
768
+ const stateParams = new URLSearchParams(state ? decodeURIComponent(state) : '');
769
+ platformName = stateParams.get('platform');
770
+ // Extract mcp auth sessionId if present
771
+ sessionId = stateParams?.get('sessionId');
772
+ const isFromMCP = !!sessionId;
773
+ const hostname = req.query.hostname ?? stateParams.get('hostname');
758
774
  const tokenUrl = req.query.tokenUrl;
759
775
  if (!platformName) {
760
776
  tracer?.trace('oauth-callback:missingPlatformName', {});
@@ -770,25 +786,53 @@ function createCoreRouter() {
770
786
  platform: platformName,
771
787
  hostname,
772
788
  tokenUrl,
773
- query: req.query
789
+ query: req.query,
790
+ proxyId: req.query.proxyId,
791
+ isFromMCP
774
792
  });
775
793
  if (userInfo) {
776
794
  const jwtToken = jwt.generateJwt({
777
795
  id: userInfo.id.toString(),
778
796
  platform: platformName
779
797
  });
780
- res.status(200).send(tracer ? tracer.wrapResponse({ jwtToken, name: userInfo.name, returnMessage }) : { jwtToken, name: userInfo.name, returnMessage });
781
- success = true;
798
+ // Store in session if sessionId exists (MCP flow)
799
+ if (isFromMCP) {
800
+ await updateAuthSession(sessionId, {
801
+ status: 'completed',
802
+ jwtToken,
803
+ userInfo: {
804
+ id: userInfo.id,
805
+ name: userInfo.name
806
+ }
807
+ });
808
+ res.status(200).send("Authentication successful. Please go back to AI Agent and confirm it.");
809
+ success = true;
810
+ }
811
+ else {
812
+ res.status(200).send(tracer ? tracer.wrapResponse({ jwtToken, name: userInfo.name, returnMessage }) : { jwtToken, name: userInfo.name, returnMessage });
813
+ success = true;
814
+ }
782
815
  }
783
816
  else {
784
817
  res.status(200).send(tracer ? tracer.wrapResponse({ returnMessage }) : { returnMessage });
785
- success = false;
818
+ await updateAuthSession(sessionId, {
819
+ status: 'failed',
820
+ errorMessage: returnMessage?.message || 'Authentication failed'
821
+ });
786
822
  }
823
+ success = false;
787
824
  }
788
825
  catch (e) {
789
- console.log(`platform: ${platformName} \n${e.stack}`);
826
+ logger.error('OAuth callback failed', { platform: platformName, stack: e.stack });
790
827
  tracer?.traceError('oauth-callback:error', e, { platform: platformName });
791
828
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
829
+ if (sessionId) {
830
+ await updateAuthSession(sessionId, {
831
+ status: 'failed',
832
+ errorMessage: e.message || e.toString()
833
+ });
834
+ }
835
+
792
836
  success = false;
793
837
  }
794
838
  const requestEndTime = new Date().getTime();
@@ -805,7 +849,7 @@ function createCoreRouter() {
805
849
  author,
806
850
  eventAddedVia
807
851
  });
808
- })
852
+ });
809
853
  router.post('/apiKeyLogin', async function (req, res) {
810
854
  const requestStartTime = new Date().getTime();
811
855
  const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
@@ -845,7 +889,7 @@ function createCoreRouter() {
845
889
  }
846
890
  }
847
891
  catch (e) {
848
- console.log(`platform: ${platformName} \n${e.stack}`);
892
+ logger.error('API key login failed', { platform: platformName, stack: e.stack });
849
893
  tracer?.traceError('apiKeyLogin:error', e, { platform: platformName });
850
894
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
851
895
  success = false;
@@ -895,7 +939,7 @@ function createCoreRouter() {
895
939
  }
896
940
  }
897
941
  catch (e) {
898
- console.log(`platform: ${platformName} \n${e.stack}`);
942
+ logger.error('Unauthorize failed', { platform: platformName, stack: e.stack });
899
943
  tracer?.traceError('unAuthorize:error', e, { platform: platformName });
900
944
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
901
945
  success = false;
@@ -924,7 +968,7 @@ function createCoreRouter() {
924
968
  res.status(200).send(tracer ? tracer.wrapResponse({ extensionId, accountId }) : { extensionId, accountId });
925
969
  }
926
970
  catch (e) {
927
- console.log(`${e.stack}`);
971
+ logger.error('Get user info hash failed', { stack: e.stack });
928
972
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
929
973
  tracer?.traceError('userInfoHash:error', e);
930
974
  }
@@ -978,7 +1022,7 @@ function createCoreRouter() {
978
1022
  }
979
1023
  }
980
1024
  catch (e) {
981
- console.log(`platform: ${platformName} \n${e.stack}`);
1025
+ logger.error('Find contact failed', { platform: platformName, stack: e.stack });
982
1026
  tracer?.traceError('findContact:error', e, { platform: platformName });
983
1027
  extraData.statusCode = e.response?.status ?? 'unknown';
984
1028
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
@@ -1036,7 +1080,7 @@ function createCoreRouter() {
1036
1080
  }
1037
1081
  }
1038
1082
  catch (e) {
1039
- console.log(`platform: ${platformName} \n${e.stack}`);
1083
+ logger.error('Create contact failed', { platform: platformName, stack: e.stack });
1040
1084
  tracer?.traceError('createContact:error', e, { platform: platformName });
1041
1085
  extraData.statusCode = e.response?.status ?? 'unknown';
1042
1086
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
@@ -1079,7 +1123,7 @@ function createCoreRouter() {
1079
1123
  }
1080
1124
  const { id: userId, platform } = decodedToken;
1081
1125
  platformName = platform;
1082
- const { successful, returnMessage, extraDataTracking } = await logCore.saveNoteCache({ sessionId: req.body.sessionId, note: req.body.note });
1126
+ const { successful, returnMessage, extraDataTracking } = await logCore.saveNoteCache({ platform, userId, sessionId: req.body.sessionId, note: req.body.note });
1083
1127
  res.status(200).send(tracer ? tracer.wrapResponse({ successful, returnMessage }) : { successful, returnMessage });
1084
1128
  success = true;
1085
1129
  if (extraDataTracking) {
@@ -1087,7 +1131,7 @@ function createCoreRouter() {
1087
1131
  }
1088
1132
  }
1089
1133
  } catch (e) {
1090
- console.log(`platform: ${platformName} \n${e.stack}`);
1134
+ logger.error('Save note cache failed', { platform: platformName, stack: e.stack });
1091
1135
  tracer?.traceError('saveNoteCache:error', e, { platform: platformName });
1092
1136
  extraData.statusCode = e.response?.status ?? 'unknown';
1093
1137
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
@@ -1101,6 +1145,14 @@ function createCoreRouter() {
1101
1145
  accountId: hashedAccountId,
1102
1146
  extensionId: hashedExtensionId,
1103
1147
  success,
1148
+ requestDuration: (requestEndTime - requestStartTime) / 1000,
1149
+ userAgent,
1150
+ ip,
1151
+ author,
1152
+ eventAddedVia,
1153
+ extras: {
1154
+ ...extraData
1155
+ }
1104
1156
  });
1105
1157
  })
1106
1158
  router.get('/callLog', async function (req, res) {
@@ -1137,7 +1189,7 @@ function createCoreRouter() {
1137
1189
  }
1138
1190
  }
1139
1191
  catch (e) {
1140
- console.log(`platform: ${platformName} \n${e.stack}`);
1192
+ logger.error('Get call log failed', { platform: platformName, stack: e.stack });
1141
1193
  extraData.statusCode = e.response?.status ?? 'unknown';
1142
1194
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1143
1195
  tracer?.traceError('getCallLog:error', e, { platform: platformName });
@@ -1194,7 +1246,7 @@ function createCoreRouter() {
1194
1246
  }
1195
1247
  }
1196
1248
  catch (e) {
1197
- console.log(`platform: ${platformName} \n${e.stack}`);
1249
+ logger.error('Create call log failed', { platform: platformName, stack: e.stack });
1198
1250
  extraData.statusCode = e.response?.status ?? 'unknown';
1199
1251
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1200
1252
  tracer?.traceError('createCallLog:error', e, { platform: platformName });
@@ -1251,7 +1303,7 @@ function createCoreRouter() {
1251
1303
  }
1252
1304
  }
1253
1305
  catch (e) {
1254
- console.log(`platform: ${platformName} \n${e.stack}`);
1306
+ logger.error('Update call log failed', { platform: platformName, stack: e.stack });
1255
1307
  extraData.statusCode = e.response?.status ?? 'unknown';
1256
1308
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1257
1309
  tracer?.traceError('updateCallLog:error', e, { platform: platformName });
@@ -1313,7 +1365,7 @@ function createCoreRouter() {
1313
1365
  }
1314
1366
  }
1315
1367
  catch (e) {
1316
- console.log(`platform: ${platformName} \n${e.stack}`);
1368
+ logger.error('Upsert call disposition failed', { platform: platformName, stack: e.stack });
1317
1369
  extraData.statusCode = e.response?.status ?? 'unknown';
1318
1370
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1319
1371
  tracer?.traceError('upsertCallDisposition:error', e, { platform: platformName });
@@ -1371,7 +1423,7 @@ function createCoreRouter() {
1371
1423
  }
1372
1424
  }
1373
1425
  catch (e) {
1374
- console.log(`platform: ${platformName} \n${e.stack}`);
1426
+ logger.error('Create message log failed', { platform: platformName, stack: e.stack });
1375
1427
  statusCode = e.response?.status ?? 'unknown';
1376
1428
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1377
1429
  tracer?.traceError('createMessageLog:error', e, { platform: platformName });
@@ -1411,11 +1463,16 @@ function createCoreRouter() {
1411
1463
  res.status(400).send(tracer ? tracer.wrapResponse('Please go to Settings and authorize CRM platform') : 'Please go to Settings and authorize CRM platform');
1412
1464
  return;
1413
1465
  }
1414
- const { id } = await calldown.schedule({ jwtToken, rcAccessToken: req.query.rcAccessToken, body: req.body });
1415
- success = true;
1416
- res.status(200).send(tracer ? tracer.wrapResponse({ successful: true, id }) : { successful: true, id });
1466
+ try {
1467
+ const { id } = await calldown.schedule({ jwtToken, rcAccessToken: req.query.rcAccessToken, body: req.body });
1468
+ success = true;
1469
+ res.status(200).send(tracer ? tracer.wrapResponse({ successful: true, id }) : { successful: true, id });
1470
+ }
1471
+ catch (e) {
1472
+ return handleDatabaseError(e, 'Error scheduling call down');
1473
+ }
1417
1474
  } catch (e) {
1418
- console.log(`platform: ${platformName} \n${e.stack}`);
1475
+ logger.error('Schedule call down failed', { platform: platformName, stack: e.stack });
1419
1476
  statusCode = e.response?.status ?? 'unknown';
1420
1477
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1421
1478
  tracer?.traceError('scheduleCallDown:error', e, { platform: platformName });
@@ -1458,7 +1515,7 @@ function createCoreRouter() {
1458
1515
  success = true;
1459
1516
  res.status(200).send(tracer ? tracer.wrapResponse({ successful: true, items }) : { successful: true, items });
1460
1517
  } catch (e) {
1461
- console.log(`platform: ${platformName} \n${e.stack}`);
1518
+ logger.error('Get call down list failed', { platform: platformName, stack: e.stack });
1462
1519
  statusCode = e.response?.status ?? 'unknown';
1463
1520
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1464
1521
  tracer?.traceError('getCallDownList:error', e, { platform: platformName });
@@ -1506,7 +1563,7 @@ function createCoreRouter() {
1506
1563
  success = true;
1507
1564
  res.status(200).send(tracer ? tracer.wrapResponse({ successful: true }) : { successful: true });
1508
1565
  } catch (e) {
1509
- console.log(`platform: ${platformName} \n${e.stack}`);
1566
+ logger.error('Delete call down item failed', { platform: platformName, stack: e.stack });
1510
1567
  statusCode = e.response?.status ?? 'unknown';
1511
1568
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1512
1569
  tracer?.traceError('deleteCallDownItem:error', e, { platform: platformName });
@@ -1553,7 +1610,7 @@ function createCoreRouter() {
1553
1610
  success = true;
1554
1611
  res.status(200).send(tracer ? tracer.wrapResponse({ successful: true }) : { successful: true });
1555
1612
  } catch (e) {
1556
- console.log(`platform: ${platformName} \n${e.stack}`);
1613
+ logger.error('Mark call down called failed', { platform: platformName, stack: e.stack });
1557
1614
  statusCode = e.response?.status ?? 'unknown';
1558
1615
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1559
1616
  tracer?.traceError('markCallDownCalled:error', e, { platform: platformName });
@@ -1581,7 +1638,6 @@ function createCoreRouter() {
1581
1638
  tracer?.trace('contactSearchByName:start', { query: req.query });
1582
1639
  let platformName = null;
1583
1640
  let success = false;
1584
- let resultCount = 0;
1585
1641
  let statusCode = 200;
1586
1642
  const { hashedExtensionId, hashedAccountId, userAgent, ip, author } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
1587
1643
  try {
@@ -1601,7 +1657,7 @@ function createCoreRouter() {
1601
1657
 
1602
1658
  }
1603
1659
  catch (e) {
1604
- console.log(`platform: ${platformName} \n${e.stack}`);
1660
+ logger.error('Contact search by name failed', { platform: platformName, stack: e.stack });
1605
1661
  statusCode = e.response?.status ?? 'unknown';
1606
1662
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1607
1663
  tracer?.traceError('contactSearchByName:error', e, { platform: platformName });
@@ -1651,7 +1707,7 @@ function createCoreRouter() {
1651
1707
  success = false;
1652
1708
  }
1653
1709
  catch (e) {
1654
- console.log(`${e.stack}`);
1710
+ logger.error('Get admin report failed', { stack: e.stack });
1655
1711
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1656
1712
  tracer?.traceError('getAdminReport:error', e, { platform: platformName });
1657
1713
  }
@@ -1696,7 +1752,7 @@ function createCoreRouter() {
1696
1752
  success = false;
1697
1753
  }
1698
1754
  catch (e) {
1699
- console.log(`${e.stack}`);
1755
+ logger.error('Get user report failed', { stack: e.stack });
1700
1756
  res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
1701
1757
  tracer?.traceError('getUserReport:error', e, { platform: platformName });
1702
1758
  }
@@ -1822,6 +1878,116 @@ function createCoreRouter() {
1822
1878
  }
1823
1879
  });
1824
1880
  }
1881
+ // --- METADATA ENDPOINT 1: Resource Metadata ---
1882
+ // Tells the client "I am protected" and "Here is who protects me"
1883
+ router.get('/.well-known/oauth-protected-resource', (req, res) => {
1884
+ res.json({
1885
+ resource: process.env.APP_SERVER,
1886
+ // CHANGE THIS: Point to your own server so the client fetches YOUR metadata next
1887
+ authorization_servers: [process.env.APP_SERVER],
1888
+ scopes_supported: ["ReadAccounts"]
1889
+ });
1890
+ });
1891
+
1892
+ // --- METADATA ENDPOINT 2: Auth Server Metadata ---
1893
+ // Usually, you can redirect to your provider's configuration.
1894
+ // If your provider supports OIDC discovery, this is often sufficient.
1895
+ router.get('/.well-known/oauth-authorization-server', (req, res) => {
1896
+ res.json({
1897
+ issuer: process.env.APP_SERVER,
1898
+ registration_endpoint: `${process.env.APP_SERVER}/oauth/register`,
1899
+
1900
+ // CHANGE THIS: Don't point to RingCentral. Point to your own Shim.
1901
+ authorization_endpoint: `${process.env.APP_SERVER}/oauth/authorize_shim`,
1902
+
1903
+ // Keep the token endpoint pointing to RingCentral (that usually works fine)
1904
+ token_endpoint: `${process.env.RINGCENTRAL_SERVER}/restapi/oauth/token`,
1905
+ token_endpoint_auth_methods_supported: ["client_secret_basic"],
1906
+ response_types_supported: ["code"]
1907
+ });
1908
+ });
1909
+
1910
+ router.get('/oauth/authorize_shim', (req, res) => {
1911
+ // 1. Get the parameters ChatGPT sent us
1912
+ const { response_type, client_id, redirect_uri, state, scope } = req.query;
1913
+
1914
+ // 2. Rebuild the query string for RingCentral
1915
+ // We explicitly LEAVE OUT 'resource' and any other junk
1916
+ const params = new URLSearchParams({
1917
+ response_type,
1918
+ client_id, // This will be your RC Client ID (since ChatGPT got it from /register)
1919
+ redirect_uri,
1920
+ state,
1921
+ scope
1922
+ });
1923
+
1924
+ // 3. Redirect the user's browser to the REAL RingCentral URL
1925
+ const rcUrl = `${process.env.RINGCENTRAL_SERVER}/restapi/oauth/authorize?${params.toString()}`;
1926
+
1927
+ console.log("Proxying OAuth request to:", rcUrl); // Helpful for debugging
1928
+ res.redirect(rcUrl);
1929
+ });
1930
+
1931
+ router.post('/oauth/register', (req, res) => {
1932
+ // The MCP client calls this to get credentials.
1933
+ // We simply return our hardcoded RingCentral app credentials.
1934
+ res.json({
1935
+ client_id: process.env.RINGCENTRAL_CLIENT_ID,
1936
+ client_secret: process.env.RINGCENTRAL_CLIENT_SECRET
1937
+ });
1938
+ });
1939
+
1940
+ router.use('/mcp', (req, res, next) => {// LOG EVERYTHING
1941
+ console.log(`[${req.method}] /mcp`);
1942
+ console.log("Headers:", JSON.stringify(req.headers['authorization'] ? "Auth Token Present" : "No Auth"));
1943
+ console.log("Body:", JSON.stringify(req.body));
1944
+ // return next();
1945
+ // Capture the response finish to see the status code
1946
+ res.on('finish', () => {
1947
+ console.log(`[Response] Status: ${res.statusCode}`);
1948
+ console.log(`[Response] data: ${JSON.stringify(res.data)}`);
1949
+ });
1950
+
1951
+ const authHeader = req.headers.authorization;
1952
+ const token = authHeader?.split(' ')[1]; // Remove "Bearer "
1953
+ // Allow the initial connection (GET) and CORS checks (OPTIONS) to pass freely.
1954
+ // We only want to block the actual commands (POST).
1955
+ if (req.method === 'GET' || req.method === 'OPTIONS') {
1956
+ return next();
1957
+ }
1958
+ // SCENARIO 1: No Token provided. Kick off the OAuth flow.
1959
+ if (!token) {
1960
+ res.setHeader('WWW-Authenticate', `Bearer realm="mcp", resource_metadata="${process.env.APP_SERVER}/.well-known/oauth-protected-resource"`);
1961
+ return res.status(401).send();
1962
+ }
1963
+
1964
+ // SCENARIO 2: Token provided. Verify it.
1965
+ try {
1966
+ next();
1967
+ } catch (error) {
1968
+ console.error("Token validation failed:", error.message);
1969
+ // Token is invalid or expired
1970
+ res.setHeader('WWW-Authenticate', `Bearer realm="mcp", resource_metadata="${process.env.APP_SERVER}/.well-known/oauth-protected-resource"`);
1971
+ return res.status(401).send();
1972
+ }
1973
+ });
1974
+ // Handle OPTIONS for CORS preflight
1975
+ router.options('/mcp', (req, res) => {
1976
+ res.setHeader('Access-Control-Allow-Origin', '*');
1977
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
1978
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept');
1979
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
1980
+ res.status(200).end();
1981
+ });
1982
+
1983
+ // Dedicated endpoint for all MCP traffic
1984
+ router.post('/mcp', async (req, res) => {
1985
+ // Set CORS headers
1986
+ res.setHeader('Access-Control-Allow-Origin', '*');
1987
+ res.setHeader('Content-Type', 'application/json');
1988
+
1989
+ await mcpHandler.handleMcpRequest(req, res);
1990
+ });
1825
1991
 
1826
1992
  return router;
1827
1993
  }
package/lib/analytics.js CHANGED
@@ -1,10 +1,12 @@
1
1
  const Mixpanel = require('mixpanel');
2
2
  const parser = require('ua-parser-js');
3
+ const logger = require('./logger');
3
4
  let packageJson = null;
4
5
  try {
5
6
  packageJson = require('../package.json');
6
7
  }
7
8
  catch (e) {
9
+ logger.warn('Error loading package.json', { stack: e.stack });
8
10
  packageJson = require('../../package.json');
9
11
  }
10
12
  const appName = 'App Connect';
@@ -49,5 +51,5 @@ exports.track = function track({ eventName, interfaceName, connectorName, accoun
49
51
  author,
50
52
  ...extras
51
53
  });
52
- console.log(`Event: ${eventName}`);
54
+ logger.info(`Event: ${eventName}`);
53
55
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Auth Session Helper
3
+ *
4
+ * Helper functions for managing OAuth auth sessions using CacheModel
5
+ */
6
+
7
+ const { CacheModel } = require('../models/cacheModel');
8
+
9
+ const AUTH_SESSION_PREFIX = 'auth-session';
10
+ const SESSION_EXPIRY_MINUTES = 5;
11
+
12
+ /**
13
+ * Create a new auth session
14
+ */
15
+ async function createAuthSession(sessionId, data) {
16
+ await CacheModel.create({
17
+ id: `${AUTH_SESSION_PREFIX}-${sessionId}`,
18
+ cacheKey: AUTH_SESSION_PREFIX,
19
+ userId: sessionId,
20
+ status: 'pending',
21
+ data: {
22
+ ...data,
23
+ createdAt: new Date().toISOString()
24
+ },
25
+ expiry: new Date(Date.now() + SESSION_EXPIRY_MINUTES * 60 * 1000)
26
+ });
27
+ }
28
+
29
+ /**
30
+ * Get an auth session by ID
31
+ */
32
+ async function getAuthSession(sessionId) {
33
+ const record = await CacheModel.findByPk(`${AUTH_SESSION_PREFIX}-${sessionId}`);
34
+
35
+ if (!record) return null;
36
+
37
+ return {
38
+ sessionId: record.userId,
39
+ status: record.status,
40
+ ...record.data
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Update an auth session
46
+ */
47
+ async function updateAuthSession(sessionId, data) {
48
+ const record = await CacheModel.findByPk(`${AUTH_SESSION_PREFIX}-${sessionId}`);
49
+
50
+ if (!record) return;
51
+
52
+ const existingData = record.data || {};
53
+ await record.update({
54
+ status: data.status || record.status,
55
+ data: {
56
+ ...existingData,
57
+ ...data,
58
+ updatedAt: new Date().toISOString()
59
+ }
60
+ });
61
+ }
62
+
63
+ module.exports = {
64
+ createAuthSession,
65
+ getAuthSession,
66
+ updateAuthSession
67
+ };
68
+