@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
@@ -0,0 +1,98 @@
1
+ const jwt = require('../../lib/jwt');
2
+ const connectorRegistry = require('../../connector/registry');
3
+ const logCore = require('../../handlers/log');
4
+
5
+ /**
6
+ * MCP Tool: Get Call Log
7
+ *
8
+ * This tool retrieves call logs from the CRM platform by session IDs.
9
+ */
10
+
11
+ const toolDefinition = {
12
+ name: 'getCallLog',
13
+ description: '⚠️ REQUIRES AUTHENTICATION: User must first authenticate using the "auth" tool to obtain a JWT token before using this tool. | Get call logs from the CRM platform by session IDs. Returns log details if found.',
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ jwtToken: {
18
+ type: 'string',
19
+ description: 'JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.'
20
+ },
21
+ sessionIds: {
22
+ type: 'string',
23
+ description: 'Session IDs to retrieve, seprated by commas'
24
+ },
25
+ requireDetails: {
26
+ type: 'boolean',
27
+ description: 'Whether to require detailed log information. If true, will call CRM API. Otherwise will just query in our own database',
28
+ default: false
29
+ }
30
+ },
31
+ required: ['jwtToken', 'sessionIds']
32
+ }
33
+ };
34
+
35
+ /**
36
+ * Execute the getCallLog tool
37
+ * @param {Object} args - The tool arguments
38
+ * @param {string} args.jwtToken - JWT token with user and platform info
39
+ * @param {string} args.sessionIds - Session IDs to retrieve, seprated by commas
40
+ * @param {boolean} [args.requireDetails] - Whether to require detailed log information. If true, will call CRM API. Otherwise will just query in our own database.
41
+ * @returns {Object} Result object with call log information
42
+ */
43
+ async function execute(args) {
44
+ try {
45
+ const { jwtToken, sessionIds, requireDetails = false } = args;
46
+
47
+ // Decode JWT to get userId and platform
48
+ const { id: userId, platform } = jwt.decodeJwt(jwtToken);
49
+
50
+ if (!userId) {
51
+ throw new Error('Invalid JWT token: userId not found');
52
+ }
53
+
54
+ // Get the platform connector module
55
+ const platformModule = connectorRegistry.getConnector(platform);
56
+
57
+ if (!platformModule) {
58
+ throw new Error(`Platform connector not found for: ${platform}`);
59
+ }
60
+
61
+ // Check if getCallLog is implemented
62
+ if (!platformModule.getCallLog) {
63
+ throw new Error(`getCallLog is not implemented for platform: ${platform}`);
64
+ }
65
+
66
+ // Call the getCallLog method
67
+ const { successful, logs, returnMessage } = await logCore.getCallLog({
68
+ userId,
69
+ sessionIds,
70
+ platform,
71
+ requireDetails
72
+ });
73
+
74
+ if (successful) {
75
+ return {
76
+ success: true,
77
+ data: logs,
78
+ };
79
+ }
80
+ else {
81
+ return {
82
+ success: false,
83
+ error: returnMessage.message,
84
+ };
85
+ }
86
+ }
87
+ catch (error) {
88
+ return {
89
+ success: false,
90
+ error: error.message || 'Unknown error occurred',
91
+ errorDetails: error.stack
92
+ };
93
+ }
94
+ }
95
+
96
+ exports.definition = toolDefinition;
97
+ exports.execute = execute;
98
+
@@ -0,0 +1,39 @@
1
+ /**
2
+ * MCP Tool: Get Help
3
+ *
4
+ * This tool provides users with a friendly guide to available capabilities
5
+ * and how to get started with the RingCentral CRM integration.
6
+ */
7
+
8
+ const toolDefinition = {
9
+ name: 'getHelp',
10
+ description: 'Get a quick guide on what this integration can do and how to get started.',
11
+ inputSchema: {
12
+ type: 'object',
13
+ properties: {},
14
+ required: []
15
+ }
16
+ };
17
+
18
+ /**
19
+ * Execute the getHelp tool
20
+ * @returns {Object} Result object with help information
21
+ */
22
+ async function execute() {
23
+ return {
24
+ success: true,
25
+ data: {
26
+ overview: "I help you connect RingCentral with your CRM to log calls.",
27
+ steps:[
28
+ '1. Tell me to show all connectors and let me connect you to your CRM',
29
+ '2. Follow authorization flow to authorize your CRM',
30
+ '3. Once authorized, I can help find contacts and log your calls'
31
+ ],
32
+ supportedCRMs: ["Clio"],
33
+ }
34
+ };
35
+ }
36
+
37
+ exports.definition = toolDefinition;
38
+ exports.execute = execute;
39
+
@@ -0,0 +1,46 @@
1
+ const developerPortal = require('../../connector/developerPortal');
2
+
3
+ /**
4
+ * MCP Tool: Get Public Connectors
5
+ *
6
+ * This tool retrieves a list of public connectors from the developer portal.
7
+ */
8
+
9
+ const toolDefinition = {
10
+ name: 'getPublicConnectors',
11
+ description: 'Auth flow step.1. Get a list of all available connectors from the developer portal. Returns a list of connector names for users to choose. Next step is calling step.2 "setConnector" tool.',
12
+ inputSchema: {
13
+ type: 'object',
14
+ properties: {},
15
+ required: []
16
+ }
17
+ };
18
+
19
+ /**
20
+ * Execute the getPublicConnectors tool
21
+ * @returns {Object} Result object with connector names
22
+ */
23
+ async function execute() {
24
+ try {
25
+ const { connectors: publicConnectorList } = await developerPortal.getPublicConnectorList();
26
+ const connectorList = publicConnectorList;
27
+ if(process.env.RC_ACCOUNT_ID) {
28
+ const { privateConnectors } = await developerPortal.getPrivateConnectorList();
29
+ connectorList.push(...privateConnectors);
30
+ }
31
+ return {
32
+ success: true,
33
+ data: connectorList.map(c => c.displayName)
34
+ };
35
+ }
36
+ catch (error) {
37
+ return {
38
+ success: false,
39
+ error: error.message || 'Unknown error occurred',
40
+ errorDetails: error.stack
41
+ };
42
+ }
43
+ }
44
+
45
+ exports.definition = toolDefinition;
46
+ exports.execute = execute;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * MCP Tools Index
3
+ *
4
+ * This file exports all available MCP tools for the RC Unified CRM Extension.
5
+ */
6
+
7
+ // const auth = require('./auth');
8
+ const getHelp = require('./getHelp');
9
+ const getPublicConnectors = require('./getPublicConnectors');
10
+ const setConnector = require('./setConnector');
11
+ const collectAuthInfo = require('./collectAuthInfo');
12
+ const doAuth = require('./doAuth');
13
+ const checkAuthStatus = require('./checkAuthStatus');
14
+ const logout = require('./logout');
15
+ const findContact = require('./findContactByPhone');
16
+ const findContactWithName = require('./findContactByName');
17
+ const getCallLog = require('./getCallLog');
18
+ const createCallLog = require('./createCallLog');
19
+ const updateCallLog = require('./updateCallLog');
20
+ const createMessageLog = require('./createMessageLog');
21
+ const rcGetCallLogs = require('./rcGetCallLogs');
22
+
23
+ // Export all tools
24
+ module.exports = {
25
+ getHelp,
26
+ getPublicConnectors,
27
+ setConnector,
28
+ collectAuthInfo,
29
+ doAuth,
30
+ checkAuthStatus,
31
+ logout,
32
+ findContact,
33
+ findContactWithName,
34
+ getCallLog,
35
+ createCallLog,
36
+ updateCallLog,
37
+ createMessageLog,
38
+ rcGetCallLogs
39
+ };
40
+
41
+ // Export tools as an array for easy iteration
42
+ module.exports.tools = [
43
+ getHelp,
44
+ getPublicConnectors,
45
+ setConnector,
46
+ collectAuthInfo,
47
+ doAuth,
48
+ checkAuthStatus,
49
+ logout,
50
+ findContact,
51
+ findContactWithName,
52
+ getCallLog,
53
+ createCallLog,
54
+ updateCallLog,
55
+ createMessageLog,
56
+ rcGetCallLogs
57
+ ];
58
+
@@ -0,0 +1,63 @@
1
+ const jwt = require('../../lib/jwt');
2
+ const { UserModel } = require('../../models/userModel');
3
+ const connectorRegistry = require('../../connector/registry');
4
+
5
+ /**
6
+ * MCP Tool: Logout
7
+ *
8
+ * This tool logs out the user from the CRM platform.
9
+ */
10
+
11
+ const toolDefinition = {
12
+ name: 'logout',
13
+ description: 'Logout the user from the CRM platform.',
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ jwtToken: {
18
+ type: 'string',
19
+ description: 'JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.'
20
+ }
21
+ }
22
+ }
23
+ };
24
+
25
+ /**
26
+ * Execute the logout tool
27
+ * @param {Object} args - The tool arguments
28
+ * @param {string} args.jwtToken - JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.
29
+ * @returns {Object} Result object with logout information
30
+ */
31
+ async function execute(args) {
32
+ try {
33
+ const { jwtToken } = args;
34
+ const { platform, id } = jwt.decodeJwt(jwtToken);
35
+
36
+ const userToLogout = await UserModel.findByPk(id);
37
+ if (!userToLogout) {
38
+ return {
39
+ success: false,
40
+ error: "User not found",
41
+ errorDetails: "User not found",
42
+ };
43
+ }
44
+ const platformModule = connectorRegistry.getConnector(platform);
45
+ await platformModule.unAuthorize({ user: userToLogout });
46
+ return {
47
+ success: true,
48
+ data: {
49
+ message: "IMPORTANT: Logout successful. Clear jwtToken, connectorManifest, connectorDisplayName, hostname and connectorName in memory. User is logged out from the CRM platform.",
50
+ }
51
+ }
52
+ }
53
+ catch (error) {
54
+ return {
55
+ success: false,
56
+ error: error.message || 'Unknown error occurred',
57
+ errorDetails: error.stack
58
+ };
59
+ }
60
+ }
61
+
62
+ exports.definition = toolDefinition;
63
+ exports.execute = execute;
@@ -0,0 +1,73 @@
1
+ const { RingCentral } = require('../../lib/ringcentral');
2
+ const jwt = require('../../lib/jwt');
3
+ const { CallLogModel } = require('../../models/callLogModel');
4
+
5
+ const toolDefinition = {
6
+ name: 'rcGetCallLogs',
7
+ description: '⚠️ REQUIRES AUTHENTICATION: User must first authenticate using the "auth" tool to obtain a JWT token before using this tool. | Get call logs from RingCentral',
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ jwtToken: {
12
+ type: 'string',
13
+ description: 'JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.'
14
+ },
15
+ timeFrom: {
16
+ type: 'string',
17
+ description: 'MUST be ISO string. Default is 24 hours ago.'
18
+ },
19
+ timeTo: {
20
+ type: 'string',
21
+ description: 'MUST be ISO string. Default is now.'
22
+ }
23
+ },
24
+ required: ['jwtToken', 'timeFrom', 'timeTo']
25
+ }
26
+ }
27
+
28
+ async function execute(args) {
29
+ try {
30
+ const { jwtToken, rcAccessToken, timeFrom, timeTo } = args;
31
+ if (!rcAccessToken) {
32
+ throw new Error('RingCentral access token not found');
33
+ }
34
+ const { id: userId } = jwt.decodeJwt(jwtToken);
35
+ if (!userId) {
36
+ throw new Error('Invalid JWT token: userId not found');
37
+ }
38
+ const rcSDK = new RingCentral({
39
+ server: process.env.RINGCENTRAL_SERVER,
40
+ clientId: process.env.RINGCENTRAL_CLIENT_ID,
41
+ clientSecret: process.env.RINGCENTRAL_CLIENT_SECRET,
42
+ redirectUri: `${process.env.APP_SERVER}/ringcentral/oauth/callback`
43
+ });
44
+ const callLogData = await rcSDK.getCallLogData({
45
+ token: { access_token: rcAccessToken, token_type: 'Bearer' },
46
+ timeFrom: timeFrom ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
47
+ timeTo: timeTo ?? new Date().toISOString(),
48
+ });
49
+ // hack: remove already logged calls
50
+ const existingCalls = [];
51
+ for (const call of callLogData.records) {
52
+ const existingCallLog = await CallLogModel.findOne({
53
+ where: {
54
+ sessionId: call.sessionId
55
+ }
56
+ });
57
+ if (existingCallLog) {
58
+ existingCalls.push(existingCallLog.sessionId);
59
+ }
60
+ }
61
+ callLogData.records = callLogData.records.filter(call => !existingCalls.includes(call.sessionId));
62
+ return callLogData;
63
+ }
64
+ catch (e) {
65
+ return {
66
+ success: false,
67
+ error: e.message
68
+ }
69
+ }
70
+ }
71
+
72
+ exports.definition = toolDefinition;
73
+ exports.execute = execute;
@@ -0,0 +1,64 @@
1
+ const developerPortal = require('../../connector/developerPortal');
2
+
3
+ /**
4
+ * MCP Tool: Set Connector
5
+ *
6
+ * This tool helps the user set the connector.
7
+ */
8
+
9
+ const toolDefinition = {
10
+ name: 'setConnector',
11
+ description: 'Auth flow step.2. Save connectorManifest to memory if successful. Next step is optional, if it is oauth, go to step.3 "collectAuthInfo" tool.',
12
+ inputSchema: {
13
+ type: 'object',
14
+ properties: {
15
+ connectorDisplayName: {
16
+ type: 'string',
17
+ description: 'Connector displayname to set'
18
+ }
19
+ },
20
+ required: ['connectorDisplayName']
21
+ }
22
+ };
23
+
24
+ /**
25
+ * Execute the setConnector tool
26
+ * @param {Object} args - The tool arguments
27
+ * @param {string} args.connectorDisplayName - Connector display name to set
28
+ * @returns {Object} Result object with connector information
29
+ */
30
+ async function execute(args) {
31
+ try {
32
+ const { connectorDisplayName } = args;
33
+ const { connectors: publicConnectorList } = await developerPortal.getPublicConnectorList();
34
+ const { privateConnectors } = await developerPortal.getPrivateConnectorList();
35
+ const connectorList = [...publicConnectorList, ...privateConnectors];
36
+ const connector = connectorList.find(c => c.displayName === connectorDisplayName);
37
+ const connectorName = connector.name;
38
+ const connectorManifest = await developerPortal.getConnectorManifest({ connectorId: connector.id, isPrivate: connector.status === 'private' });
39
+ if (!connectorManifest) {
40
+ throw new Error(`Connector manifest not found: ${connectorDisplayName}`);
41
+ }
42
+ return {
43
+ success: true,
44
+ data: {
45
+ connectorManifest,
46
+ connectorDisplayName,
47
+ connectorName,
48
+ // Add explicit instruction
49
+ message: "IMPORTANT: Use connectorManifest, connectorDisplayName, and connectorName in the next few authentication steps.",
50
+
51
+ }
52
+ };
53
+ }
54
+ catch (error) {
55
+ return {
56
+ success: false,
57
+ error: error.message || 'Unknown error occurred',
58
+ errorDetails: error.stack
59
+ };
60
+ }
61
+ }
62
+
63
+ exports.definition = toolDefinition;
64
+ exports.execute = execute;
@@ -0,0 +1,122 @@
1
+ const jwt = require('../../lib/jwt');
2
+ const connectorRegistry = require('../../connector/registry');
3
+ const logCore = require('../../handlers/log');
4
+ const util = require('../../lib/util');
5
+
6
+ /**
7
+ * MCP Tool: Update Call Log
8
+ *
9
+ * This tool updates an existing call log in the CRM platform.
10
+ */
11
+
12
+ const toolDefinition = {
13
+ name: 'updateCallLog',
14
+ description: '⚠️ REQUIRES AUTHENTICATION: User must first authenticate using the "auth" tool to obtain a JWT token before using this tool. | Update an existing call log in the CRM platform. Returns the updated log ID if successful.',
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ jwtToken: {
19
+ type: 'string',
20
+ description: 'JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.'
21
+ },
22
+ incomingData: {
23
+ type: 'object',
24
+ description: 'Call log update data including sessionId, note, additionalSubmission, etc.',
25
+ properties: {
26
+ sessionId: {
27
+ type: 'string',
28
+ description: 'Session ID of the call log to update'
29
+ },
30
+ note: {
31
+ type: 'string',
32
+ description: 'Updated call note/description'
33
+ },
34
+ additionalSubmission: {
35
+ type: 'object',
36
+ description: 'Additional platform-specific fields to update'
37
+ },
38
+ accountId: {
39
+ type: 'string',
40
+ description: 'RingCentral account ID'
41
+ }
42
+ },
43
+ required: ['sessionId']
44
+ }
45
+ },
46
+ required: ['jwtToken', 'incomingData']
47
+ }
48
+ };
49
+
50
+ /**
51
+ * Execute the updateCallLog tool
52
+ * @param {Object} args - The tool arguments
53
+ * @param {string} args.jwtToken - JWT token with user and platform info
54
+ * @param {Object} args.incomingData - Call log update data
55
+ * @returns {Object} Result object with updated log ID
56
+ */
57
+ async function execute(args) {
58
+ try {
59
+ const { jwtToken, incomingData } = args;
60
+
61
+ // Decode JWT to get userId and platform
62
+ const { id: userId, platform } = jwt.decodeJwt(jwtToken);
63
+
64
+ if (!userId) {
65
+ throw new Error('Invalid JWT token: userId not found');
66
+ }
67
+
68
+ // Get the platform connector module
69
+ const platformModule = connectorRegistry.getConnector(platform);
70
+
71
+ if (!platformModule) {
72
+ throw new Error(`Platform connector not found for: ${platform}`);
73
+ }
74
+
75
+ // Check if updateCallLog is implemented
76
+ if (!platformModule.updateCallLog) {
77
+ throw new Error(`updateCallLog is not implemented for platform: ${platform}`);
78
+ }
79
+
80
+ // Calculate hashed account ID
81
+ const hashedAccountId = incomingData.accountId
82
+ ? util.getHashValue(incomingData.accountId, process.env.HASH_KEY)
83
+ : undefined;
84
+
85
+ // Call the updateCallLog method
86
+ const { successful, logId, updatedNote, returnMessage } = await logCore.updateCallLog({
87
+ platform,
88
+ userId,
89
+ incomingData,
90
+ hashedAccountId,
91
+ isFromSSCL: false
92
+ });
93
+
94
+ if (successful) {
95
+ return {
96
+ success: true,
97
+ data: {
98
+ logId,
99
+ updatedNote,
100
+ message: returnMessage?.message || 'Call log updated successfully'
101
+ }
102
+ };
103
+ }
104
+ else {
105
+ return {
106
+ success: false,
107
+ error: returnMessage?.message || 'Failed to update call log',
108
+ };
109
+ }
110
+ }
111
+ catch (error) {
112
+ return {
113
+ success: false,
114
+ error: error.message || 'Unknown error occurred',
115
+ errorDetails: error.stack
116
+ };
117
+ }
118
+ }
119
+
120
+ exports.definition = toolDefinition;
121
+ exports.execute = execute;
122
+
@@ -17,6 +17,9 @@ exports.CacheModel = sequelize.define('cache', {
17
17
  cacheKey: {
18
18
  type: Sequelize.STRING,
19
19
  },
20
+ data: {
21
+ type: Sequelize.JSON
22
+ },
20
23
  expiry: {
21
24
  type: Sequelize.DATE
22
25
  }