@app-connect/core 1.7.10 → 1.7.12

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 (59) 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 +135 -22
  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 +7 -145
  9. package/handlers/log.js +174 -258
  10. package/handlers/user.js +19 -6
  11. package/index.js +280 -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 +110 -0
  25. package/mcp/tools/collectAuthInfo.js +91 -0
  26. package/mcp/tools/createCallLog.js +308 -0
  27. package/mcp/tools/createContact.js +117 -0
  28. package/mcp/tools/createMessageLog.js +283 -0
  29. package/mcp/tools/doAuth.js +190 -0
  30. package/mcp/tools/findContactByName.js +92 -0
  31. package/mcp/tools/findContactByPhone.js +101 -0
  32. package/mcp/tools/getCallLog.js +98 -0
  33. package/mcp/tools/getGoogleFilePicker.js +103 -0
  34. package/mcp/tools/getHelp.js +44 -0
  35. package/mcp/tools/getPublicConnectors.js +53 -0
  36. package/mcp/tools/index.js +64 -0
  37. package/mcp/tools/logout.js +68 -0
  38. package/mcp/tools/rcGetCallLogs.js +78 -0
  39. package/mcp/tools/setConnector.js +69 -0
  40. package/mcp/tools/updateCallLog.js +122 -0
  41. package/models/cacheModel.js +3 -0
  42. package/package.json +71 -70
  43. package/releaseNotes.json +24 -0
  44. package/test/handlers/log.test.js +11 -4
  45. package/test/lib/logger.test.js +206 -0
  46. package/test/lib/ringcentral.test.js +0 -6
  47. package/test/lib/sharedSMSComposer.test.js +1084 -0
  48. package/test/mcp/tools/collectAuthInfo.test.js +234 -0
  49. package/test/mcp/tools/createCallLog.test.js +425 -0
  50. package/test/mcp/tools/createMessageLog.test.js +580 -0
  51. package/test/mcp/tools/doAuth.test.js +376 -0
  52. package/test/mcp/tools/findContactByName.test.js +263 -0
  53. package/test/mcp/tools/findContactByPhone.test.js +284 -0
  54. package/test/mcp/tools/getCallLog.test.js +286 -0
  55. package/test/mcp/tools/getGoogleFilePicker.test.js +281 -0
  56. package/test/mcp/tools/getPublicConnectors.test.js +128 -0
  57. package/test/mcp/tools/logout.test.js +169 -0
  58. package/test/mcp/tools/setConnector.test.js +177 -0
  59. package/test/mcp/tools/updateCallLog.test.js +346 -0
@@ -0,0 +1,284 @@
1
+ const findContactByPhone = require('../../../mcp/tools/findContactByPhone');
2
+ const jwt = require('../../../lib/jwt');
3
+ const connectorRegistry = require('../../../connector/registry');
4
+ const contactCore = require('../../../handlers/contact');
5
+
6
+ // Mock dependencies
7
+ jest.mock('../../../lib/jwt');
8
+ jest.mock('../../../connector/registry');
9
+ jest.mock('../../../handlers/contact');
10
+
11
+ describe('MCP Tool: findContactByPhone', () => {
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ describe('tool definition', () => {
17
+ test('should have correct tool definition', () => {
18
+ expect(findContactByPhone.definition).toBeDefined();
19
+ expect(findContactByPhone.definition.name).toBe('findContactByPhone');
20
+ expect(findContactByPhone.definition.description).toContain('REQUIRES AUTHENTICATION');
21
+ expect(findContactByPhone.definition.inputSchema).toBeDefined();
22
+ });
23
+
24
+ test('should require jwtToken and phoneNumber parameters', () => {
25
+ expect(findContactByPhone.definition.inputSchema.required).toContain('jwtToken');
26
+ expect(findContactByPhone.definition.inputSchema.required).toContain('phoneNumber');
27
+ });
28
+
29
+ test('should have optional parameters', () => {
30
+ expect(findContactByPhone.definition.inputSchema.properties).toHaveProperty('overridingFormat');
31
+ expect(findContactByPhone.definition.inputSchema.properties).toHaveProperty('isExtension');
32
+ });
33
+ });
34
+
35
+ describe('execute', () => {
36
+ test('should find contact by phone successfully', async () => {
37
+ // Arrange
38
+ const mockContact = {
39
+ id: 'contact-123',
40
+ name: 'John Doe',
41
+ phone: '+1234567890',
42
+ type: 'Contact'
43
+ };
44
+
45
+ jwt.decodeJwt.mockReturnValue({
46
+ id: 'user-123',
47
+ platform: 'testCRM'
48
+ });
49
+
50
+ const mockConnector = {
51
+ findContact: jest.fn()
52
+ };
53
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
54
+
55
+ contactCore.findContact.mockResolvedValue({
56
+ successful: true,
57
+ contact: mockContact,
58
+ returnMessage: { message: 'Contact found' }
59
+ });
60
+
61
+ // Act
62
+ const result = await findContactByPhone.execute({
63
+ jwtToken: 'mock-jwt-token',
64
+ phoneNumber: '+1234567890'
65
+ });
66
+
67
+ // Assert
68
+ expect(result).toEqual({
69
+ success: true,
70
+ data: mockContact
71
+ });
72
+ expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
73
+ expect(connectorRegistry.getConnector).toHaveBeenCalledWith('testCRM');
74
+ expect(contactCore.findContact).toHaveBeenCalledWith({
75
+ platform: 'testCRM',
76
+ userId: 'user-123',
77
+ phoneNumber: '+1234567890',
78
+ overridingFormat: '',
79
+ isExtension: false
80
+ });
81
+ });
82
+
83
+ test('should handle overriding format parameter', async () => {
84
+ // Arrange
85
+ const mockContact = {
86
+ id: 'contact-456',
87
+ name: 'Jane Smith',
88
+ phone: '1234567890'
89
+ };
90
+
91
+ jwt.decodeJwt.mockReturnValue({
92
+ id: 'user-123',
93
+ platform: 'testCRM'
94
+ });
95
+
96
+ const mockConnector = {
97
+ findContact: jest.fn()
98
+ };
99
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
100
+
101
+ contactCore.findContact.mockResolvedValue({
102
+ successful: true,
103
+ contact: mockContact
104
+ });
105
+
106
+ // Act
107
+ const result = await findContactByPhone.execute({
108
+ jwtToken: 'mock-jwt-token',
109
+ phoneNumber: '+1234567890',
110
+ overridingFormat: '1234567890'
111
+ });
112
+
113
+ // Assert
114
+ expect(result.success).toBe(true);
115
+ expect(contactCore.findContact).toHaveBeenCalledWith({
116
+ platform: 'testCRM',
117
+ userId: 'user-123',
118
+ phoneNumber: '+1234567890',
119
+ overridingFormat: '1234567890',
120
+ isExtension: false
121
+ });
122
+ });
123
+
124
+ test('should handle isExtension parameter', async () => {
125
+ // Arrange
126
+ const mockContact = {
127
+ id: 'contact-789',
128
+ name: 'Bob Johnson',
129
+ phone: '101'
130
+ };
131
+
132
+ jwt.decodeJwt.mockReturnValue({
133
+ id: 'user-123',
134
+ platform: 'testCRM'
135
+ });
136
+
137
+ const mockConnector = {
138
+ findContact: jest.fn()
139
+ };
140
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
141
+
142
+ contactCore.findContact.mockResolvedValue({
143
+ successful: true,
144
+ contact: mockContact
145
+ });
146
+
147
+ // Act
148
+ const result = await findContactByPhone.execute({
149
+ jwtToken: 'mock-jwt-token',
150
+ phoneNumber: '101',
151
+ isExtension: true
152
+ });
153
+
154
+ // Assert
155
+ expect(result.success).toBe(true);
156
+ expect(contactCore.findContact).toHaveBeenCalledWith({
157
+ platform: 'testCRM',
158
+ userId: 'user-123',
159
+ phoneNumber: '101',
160
+ overridingFormat: '',
161
+ isExtension: true
162
+ });
163
+ });
164
+
165
+ test('should return error when contact not found', async () => {
166
+ // Arrange
167
+ jwt.decodeJwt.mockReturnValue({
168
+ id: 'user-123',
169
+ platform: 'testCRM'
170
+ });
171
+
172
+ const mockConnector = {
173
+ findContact: jest.fn()
174
+ };
175
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
176
+
177
+ contactCore.findContact.mockResolvedValue({
178
+ successful: false,
179
+ contact: null,
180
+ returnMessage: { message: 'Contact not found' }
181
+ });
182
+
183
+ // Act
184
+ const result = await findContactByPhone.execute({
185
+ jwtToken: 'mock-jwt-token',
186
+ phoneNumber: '+9999999999'
187
+ });
188
+
189
+ // Assert
190
+ expect(result).toEqual({
191
+ success: false,
192
+ error: 'Contact not found'
193
+ });
194
+ });
195
+
196
+ test('should return error when JWT is invalid', async () => {
197
+ // Arrange
198
+ jwt.decodeJwt.mockReturnValue({
199
+ platform: 'testCRM'
200
+ // id is missing
201
+ });
202
+
203
+ // Act
204
+ const result = await findContactByPhone.execute({
205
+ jwtToken: 'invalid-token',
206
+ phoneNumber: '+1234567890'
207
+ });
208
+
209
+ // Assert
210
+ expect(result.success).toBe(false);
211
+ expect(result.error).toContain('Invalid JWT token');
212
+ });
213
+
214
+ test('should return error when platform connector not found', async () => {
215
+ // Arrange
216
+ jwt.decodeJwt.mockReturnValue({
217
+ id: 'user-123',
218
+ platform: 'unknownCRM'
219
+ });
220
+
221
+ connectorRegistry.getConnector.mockReturnValue(null);
222
+
223
+ // Act
224
+ const result = await findContactByPhone.execute({
225
+ jwtToken: 'mock-jwt-token',
226
+ phoneNumber: '+1234567890'
227
+ });
228
+
229
+ // Assert
230
+ expect(result.success).toBe(false);
231
+ expect(result.error).toContain('Platform connector not found');
232
+ });
233
+
234
+ test('should return error when findContact is not implemented', async () => {
235
+ // Arrange
236
+ jwt.decodeJwt.mockReturnValue({
237
+ id: 'user-123',
238
+ platform: 'testCRM'
239
+ });
240
+
241
+ const mockConnector = {}; // No findContact method
242
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
243
+
244
+ // Act
245
+ const result = await findContactByPhone.execute({
246
+ jwtToken: 'mock-jwt-token',
247
+ phoneNumber: '+1234567890'
248
+ });
249
+
250
+ // Assert
251
+ expect(result.success).toBe(false);
252
+ expect(result.error).toContain('not implemented');
253
+ });
254
+
255
+ test('should handle unexpected errors gracefully', async () => {
256
+ // Arrange
257
+ jwt.decodeJwt.mockReturnValue({
258
+ id: 'user-123',
259
+ platform: 'testCRM'
260
+ });
261
+
262
+ const mockConnector = {
263
+ findContact: jest.fn()
264
+ };
265
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
266
+
267
+ contactCore.findContact.mockRejectedValue(
268
+ new Error('Database connection failed')
269
+ );
270
+
271
+ // Act
272
+ const result = await findContactByPhone.execute({
273
+ jwtToken: 'mock-jwt-token',
274
+ phoneNumber: '+1234567890'
275
+ });
276
+
277
+ // Assert
278
+ expect(result.success).toBe(false);
279
+ expect(result.error).toBe('Database connection failed');
280
+ expect(result.errorDetails).toBeDefined();
281
+ });
282
+ });
283
+ });
284
+
@@ -0,0 +1,286 @@
1
+ const getCallLog = require('../../../mcp/tools/getCallLog');
2
+ const jwt = require('../../../lib/jwt');
3
+ const connectorRegistry = require('../../../connector/registry');
4
+ const logCore = require('../../../handlers/log');
5
+
6
+ // Mock dependencies
7
+ jest.mock('../../../lib/jwt');
8
+ jest.mock('../../../connector/registry');
9
+ jest.mock('../../../handlers/log');
10
+
11
+ describe('MCP Tool: getCallLog', () => {
12
+ beforeEach(() => {
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ describe('tool definition', () => {
17
+ test('should have correct tool definition', () => {
18
+ expect(getCallLog.definition).toBeDefined();
19
+ expect(getCallLog.definition.name).toBe('getCallLog');
20
+ expect(getCallLog.definition.description).toContain('REQUIRES AUTHENTICATION');
21
+ expect(getCallLog.definition.inputSchema).toBeDefined();
22
+ });
23
+
24
+ test('should require jwtToken and sessionIds parameters', () => {
25
+ expect(getCallLog.definition.inputSchema.required).toContain('jwtToken');
26
+ expect(getCallLog.definition.inputSchema.required).toContain('sessionIds');
27
+ });
28
+
29
+ test('should have requireDetails optional parameter', () => {
30
+ expect(getCallLog.definition.inputSchema.properties).toHaveProperty('requireDetails');
31
+ });
32
+ });
33
+
34
+ describe('execute', () => {
35
+ test('should get call log successfully', async () => {
36
+ // Arrange
37
+ const mockLogs = [
38
+ {
39
+ id: 'log-123',
40
+ sessionId: 'session-123',
41
+ contactId: 'contact-123',
42
+ phoneNumber: '+1234567890',
43
+ callDirection: 'Inbound',
44
+ callDuration: 120
45
+ }
46
+ ];
47
+
48
+ jwt.decodeJwt.mockReturnValue({
49
+ id: 'user-123',
50
+ platform: 'testCRM'
51
+ });
52
+
53
+ const mockConnector = {
54
+ getCallLog: jest.fn()
55
+ };
56
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
57
+
58
+ logCore.getCallLog.mockResolvedValue({
59
+ successful: true,
60
+ logs: mockLogs,
61
+ returnMessage: { message: 'Logs retrieved' }
62
+ });
63
+
64
+ // Act
65
+ const result = await getCallLog.execute({
66
+ jwtToken: 'mock-jwt-token',
67
+ sessionIds: 'session-123'
68
+ });
69
+
70
+ // Assert
71
+ expect(result).toEqual({
72
+ success: true,
73
+ data: mockLogs
74
+ });
75
+ expect(jwt.decodeJwt).toHaveBeenCalledWith('mock-jwt-token');
76
+ expect(connectorRegistry.getConnector).toHaveBeenCalledWith('testCRM');
77
+ expect(logCore.getCallLog).toHaveBeenCalledWith({
78
+ userId: 'user-123',
79
+ sessionIds: 'session-123',
80
+ platform: 'testCRM',
81
+ requireDetails: false
82
+ });
83
+ });
84
+
85
+ test('should get call logs with multiple session IDs', async () => {
86
+ // Arrange
87
+ const mockLogs = [
88
+ { sessionId: 'session-123', contactId: 'contact-123' },
89
+ { sessionId: 'session-456', contactId: 'contact-456' }
90
+ ];
91
+
92
+ jwt.decodeJwt.mockReturnValue({
93
+ id: 'user-123',
94
+ platform: 'testCRM'
95
+ });
96
+
97
+ const mockConnector = {
98
+ getCallLog: jest.fn()
99
+ };
100
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
101
+
102
+ logCore.getCallLog.mockResolvedValue({
103
+ successful: true,
104
+ logs: mockLogs
105
+ });
106
+
107
+ // Act
108
+ const result = await getCallLog.execute({
109
+ jwtToken: 'mock-jwt-token',
110
+ sessionIds: 'session-123,session-456'
111
+ });
112
+
113
+ // Assert
114
+ expect(result.success).toBe(true);
115
+ expect(result.data).toHaveLength(2);
116
+ expect(logCore.getCallLog).toHaveBeenCalledWith({
117
+ userId: 'user-123',
118
+ sessionIds: 'session-123,session-456',
119
+ platform: 'testCRM',
120
+ requireDetails: false
121
+ });
122
+ });
123
+
124
+ test('should get call logs with detailed information', async () => {
125
+ // Arrange
126
+ const mockLogs = [
127
+ {
128
+ sessionId: 'session-123',
129
+ contactId: 'contact-123',
130
+ detailedInfo: { recording: 'url', transcript: 'text' }
131
+ }
132
+ ];
133
+
134
+ jwt.decodeJwt.mockReturnValue({
135
+ id: 'user-123',
136
+ platform: 'testCRM'
137
+ });
138
+
139
+ const mockConnector = {
140
+ getCallLog: jest.fn()
141
+ };
142
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
143
+
144
+ logCore.getCallLog.mockResolvedValue({
145
+ successful: true,
146
+ logs: mockLogs
147
+ });
148
+
149
+ // Act
150
+ const result = await getCallLog.execute({
151
+ jwtToken: 'mock-jwt-token',
152
+ sessionIds: 'session-123',
153
+ requireDetails: true
154
+ });
155
+
156
+ // Assert
157
+ expect(result.success).toBe(true);
158
+ expect(result.data).toEqual(mockLogs);
159
+ expect(logCore.getCallLog).toHaveBeenCalledWith({
160
+ userId: 'user-123',
161
+ sessionIds: 'session-123',
162
+ platform: 'testCRM',
163
+ requireDetails: true
164
+ });
165
+ });
166
+
167
+ test('should return error when logs not found', async () => {
168
+ // Arrange
169
+ jwt.decodeJwt.mockReturnValue({
170
+ id: 'user-123',
171
+ platform: 'testCRM'
172
+ });
173
+
174
+ const mockConnector = {
175
+ getCallLog: jest.fn()
176
+ };
177
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
178
+
179
+ logCore.getCallLog.mockResolvedValue({
180
+ successful: false,
181
+ logs: [],
182
+ returnMessage: { message: 'Logs not found' }
183
+ });
184
+
185
+ // Act
186
+ const result = await getCallLog.execute({
187
+ jwtToken: 'mock-jwt-token',
188
+ sessionIds: 'non-existent-session'
189
+ });
190
+
191
+ // Assert
192
+ expect(result).toEqual({
193
+ success: false,
194
+ error: 'Logs not found'
195
+ });
196
+ });
197
+
198
+ test('should return error when JWT is invalid', async () => {
199
+ // Arrange
200
+ jwt.decodeJwt.mockReturnValue({
201
+ platform: 'testCRM'
202
+ // id is missing
203
+ });
204
+
205
+ // Act
206
+ const result = await getCallLog.execute({
207
+ jwtToken: 'invalid-token',
208
+ sessionIds: 'session-123'
209
+ });
210
+
211
+ // Assert
212
+ expect(result.success).toBe(false);
213
+ expect(result.error).toContain('Invalid JWT token');
214
+ });
215
+
216
+ test('should return error when platform connector not found', async () => {
217
+ // Arrange
218
+ jwt.decodeJwt.mockReturnValue({
219
+ id: 'user-123',
220
+ platform: 'unknownCRM'
221
+ });
222
+
223
+ connectorRegistry.getConnector.mockReturnValue(null);
224
+
225
+ // Act
226
+ const result = await getCallLog.execute({
227
+ jwtToken: 'mock-jwt-token',
228
+ sessionIds: 'session-123'
229
+ });
230
+
231
+ // Assert
232
+ expect(result.success).toBe(false);
233
+ expect(result.error).toContain('Platform connector not found');
234
+ });
235
+
236
+ test('should return error when getCallLog is not implemented', async () => {
237
+ // Arrange
238
+ jwt.decodeJwt.mockReturnValue({
239
+ id: 'user-123',
240
+ platform: 'testCRM'
241
+ });
242
+
243
+ const mockConnector = {}; // No getCallLog method
244
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
245
+
246
+ // Act
247
+ const result = await getCallLog.execute({
248
+ jwtToken: 'mock-jwt-token',
249
+ sessionIds: 'session-123'
250
+ });
251
+
252
+ // Assert
253
+ expect(result.success).toBe(false);
254
+ expect(result.error).toContain('not implemented');
255
+ });
256
+
257
+ test('should handle unexpected errors gracefully', async () => {
258
+ // Arrange
259
+ jwt.decodeJwt.mockReturnValue({
260
+ id: 'user-123',
261
+ platform: 'testCRM'
262
+ });
263
+
264
+ const mockConnector = {
265
+ getCallLog: jest.fn()
266
+ };
267
+ connectorRegistry.getConnector.mockReturnValue(mockConnector);
268
+
269
+ logCore.getCallLog.mockRejectedValue(
270
+ new Error('Database query failed')
271
+ );
272
+
273
+ // Act
274
+ const result = await getCallLog.execute({
275
+ jwtToken: 'mock-jwt-token',
276
+ sessionIds: 'session-123'
277
+ });
278
+
279
+ // Assert
280
+ expect(result.success).toBe(false);
281
+ expect(result.error).toBe('Database query failed');
282
+ expect(result.errorDetails).toBeDefined();
283
+ });
284
+ });
285
+ });
286
+