@app-connect/core 1.7.11 → 1.7.15

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.
@@ -12,6 +12,11 @@ const toolDefinition = {
12
12
  type: 'object',
13
13
  properties: {},
14
14
  required: []
15
+ },
16
+ annotations: {
17
+ readOnlyHint: true,
18
+ openWorldHint: false,
19
+ destructiveHint: false
15
20
  }
16
21
  };
17
22
 
@@ -29,7 +34,7 @@ async function execute() {
29
34
  '2. Follow authorization flow to authorize your CRM',
30
35
  '3. Once authorized, I can help find contacts and log your calls'
31
36
  ],
32
- supportedCRMs: ["Clio"],
37
+ supportedCRMs: ["Clio", "Google Sheets"],
33
38
  }
34
39
  };
35
40
  }
@@ -13,9 +13,16 @@ const toolDefinition = {
13
13
  type: 'object',
14
14
  properties: {},
15
15
  required: []
16
+ },
17
+ annotations: {
18
+ readOnlyHint: true,
19
+ openWorldHint: false,
20
+ destructiveHint: false
16
21
  }
17
22
  };
18
23
 
24
+ const supportedPlatforms = ['googleSheets','clio'];
25
+
19
26
  /**
20
27
  * Execute the getPublicConnectors tool
21
28
  * @returns {Object} Result object with connector names
@@ -30,7 +37,7 @@ async function execute() {
30
37
  }
31
38
  return {
32
39
  success: true,
33
- data: connectorList.map(c => c.displayName)
40
+ data: connectorList.filter(c => supportedPlatforms.includes(c.name)).map(c => c.displayName)
34
41
  };
35
42
  }
36
43
  catch (error) {
@@ -19,6 +19,8 @@ const createCallLog = require('./createCallLog');
19
19
  const updateCallLog = require('./updateCallLog');
20
20
  const createMessageLog = require('./createMessageLog');
21
21
  const rcGetCallLogs = require('./rcGetCallLogs');
22
+ const getGoogleFilePicker = require('./getGoogleFilePicker');
23
+ const createContact = require('./createContact');
22
24
 
23
25
  // Export all tools
24
26
  module.exports = {
@@ -31,11 +33,13 @@ module.exports = {
31
33
  logout,
32
34
  findContact,
33
35
  findContactWithName,
34
- getCallLog,
36
+ //getCallLog,
35
37
  createCallLog,
36
- updateCallLog,
37
- createMessageLog,
38
- rcGetCallLogs
38
+ //updateCallLog,
39
+ //createMessageLog,
40
+ rcGetCallLogs,
41
+ getGoogleFilePicker,
42
+ createContact
39
43
  };
40
44
 
41
45
  // Export tools as an array for easy iteration
@@ -49,10 +53,12 @@ module.exports.tools = [
49
53
  logout,
50
54
  findContact,
51
55
  findContactWithName,
52
- getCallLog,
56
+ //getCallLog,
53
57
  createCallLog,
54
- updateCallLog,
55
- createMessageLog,
56
- rcGetCallLogs
58
+ //updateCallLog,
59
+ //createMessageLog,
60
+ rcGetCallLogs,
61
+ getGoogleFilePicker,
62
+ createContact
57
63
  ];
58
64
 
@@ -19,6 +19,11 @@ const toolDefinition = {
19
19
  description: 'JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.'
20
20
  }
21
21
  }
22
+ },
23
+ annotations: {
24
+ readOnlyHint: false,
25
+ openWorldHint: false,
26
+ destructiveHint: true
22
27
  }
23
28
  };
24
29
 
@@ -22,6 +22,11 @@ const toolDefinition = {
22
22
  }
23
23
  },
24
24
  required: ['jwtToken', 'timeFrom', 'timeTo']
25
+ },
26
+ annotations: {
27
+ readOnlyHint: true,
28
+ openWorldHint: false,
29
+ destructiveHint: false
25
30
  }
26
31
  }
27
32
 
@@ -8,7 +8,7 @@ const developerPortal = require('../../connector/developerPortal');
8
8
 
9
9
  const toolDefinition = {
10
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.',
11
+ description: 'Auth flow step.2. Save connectorManifest to memory if successful.',
12
12
  inputSchema: {
13
13
  type: 'object',
14
14
  properties: {
@@ -18,6 +18,11 @@ const toolDefinition = {
18
18
  }
19
19
  },
20
20
  required: ['connectorDisplayName']
21
+ },
22
+ annotations: {
23
+ readOnlyHint: true,
24
+ openWorldHint: false,
25
+ destructiveHint: false
21
26
  }
22
27
  };
23
28
 
@@ -46,7 +51,7 @@ async function execute(args) {
46
51
  connectorDisplayName,
47
52
  connectorName,
48
53
  // Add explicit instruction
49
- message: "IMPORTANT: Use connectorManifest, connectorDisplayName, and connectorName in the next few authentication steps.",
54
+ message: "IMPORTANT: Use connectorManifest, connectorDisplayName, and connectorName in the next few authentication steps. Call 'collectAuthInfo' tool if the connector is oauth, unless connectorManifest.platform[0].environment.type == 'fixed'.",
50
55
 
51
56
  }
52
57
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@app-connect/core",
3
- "version": "1.7.11",
3
+ "version": "1.7.15",
4
4
  "description": "RingCentral App Connect Core",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -23,9 +23,10 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@aws-sdk/client-dynamodb": "^3.751.0",
26
- "@modelcontextprotocol/sdk": "^1.21.1",
27
26
  "@aws-sdk/client-s3": "^3.947.0",
28
27
  "@aws-sdk/s3-request-presigner": "^3.947.0",
28
+ "@modelcontextprotocol/sdk": "^1.21.1",
29
+ "awesome-phonenumber": "^5.6.0",
29
30
  "body-parser": "^1.20.4",
30
31
  "body-parser-xml": "^2.0.5",
31
32
  "client-oauth2": "^4.3.3",
package/releaseNotes.json CHANGED
@@ -1,4 +1,28 @@
1
1
  {
2
+ "1.7.15": {
3
+ "global": [
4
+ {
5
+ "type": "Fix",
6
+ "description": "Click-to-dial injection for HTML shadow roots"
7
+ },
8
+ {
9
+ "type": "Fix",
10
+ "description": "A not-found error for user mapping"
11
+ }
12
+ ]
13
+ },
14
+ "1.7.12": {
15
+ "global": [
16
+ {
17
+ "type": "New",
18
+ "description": "A button on Developer settings page to re-initialize user mapping"
19
+ },
20
+ {
21
+ "type": "Better",
22
+ "description": "Click-to-SMS button is disabled if RingCentral SMS service is not activated"
23
+ }
24
+ ]
25
+ },
2
26
  "1.7.11": {
3
27
  "global": [
4
28
  {
@@ -163,7 +187,7 @@
163
187
  }
164
188
  ]
165
189
  },
166
- "1.6.11":{
190
+ "1.6.11": {
167
191
  "global": [
168
192
  {
169
193
  "type": "Fix",
@@ -691,10 +691,12 @@ describe('Log Handler', () => {
691
691
  platformAdditionalInfo: {}
692
692
  });
693
693
 
694
+ // Create existing message log with same conversationLogId
694
695
  await MessageLogModel.create({
695
696
  id: 'msg-1',
696
697
  platform: 'testCRM',
697
698
  conversationId: 'conv-123',
699
+ conversationLogId: 'conv-log-123',
698
700
  thirdPartyLogId: 'existing-log',
699
701
  userId: 'test-user-id'
700
702
  });
@@ -720,7 +722,7 @@ describe('Log Handler', () => {
720
722
  ],
721
723
  correspondents: [{ phoneNumber: '+1234567890' }],
722
724
  conversationId: 'conv-123',
723
- conversationLogId: 'new-conv-log-123'
725
+ conversationLogId: 'conv-log-123' // Same conversationLogId as existing record
724
726
  },
725
727
  contactId: 'contact-123',
726
728
  contactType: 'Contact',
@@ -736,7 +738,7 @@ describe('Log Handler', () => {
736
738
 
737
739
  // Assert
738
740
  expect(result.successful).toBe(true);
739
- // msg-1 is skipped (already logged), msg-2 uses updateMessageLog because same conversationId exists
741
+ // msg-1 is skipped (already logged), msg-2 uses updateMessageLog because same conversationLogId exists
740
742
  expect(mockConnector.createMessageLog).toHaveBeenCalledTimes(0);
741
743
  expect(mockConnector.updateMessageLog).toHaveBeenCalledTimes(1);
742
744
  });
@@ -771,7 +773,8 @@ describe('Log Handler', () => {
771
773
 
772
774
  // Assert
773
775
  expect(result.successful).toBe(false);
774
- expect(result.returnMessage).toBe('Error saving note cache');
776
+ expect(result.returnMessage.message).toBe('Error performing saveNoteCache');
777
+ expect(result.returnMessage.messageType).toBe('warning');
775
778
  });
776
779
  });
777
780
 
@@ -126,9 +126,6 @@ describe('ringcentral', () => {
126
126
  expect(result.refresh_token).toBe('new-refresh-token');
127
127
  expect(result.expire_time).toBeDefined();
128
128
  expect(result.refresh_token_expire_time).toBeDefined();
129
- // These should not be included
130
- expect(result.scope).toBeUndefined();
131
- expect(result.endpoint_id).toBeUndefined();
132
129
  });
133
130
 
134
131
  test('should throw error on failed token generation', async () => {
@@ -317,9 +314,6 @@ describe('ringcentral', () => {
317
314
  }, token);
318
315
 
319
316
  expect(result.id).toBe('sub-123');
320
- // These should not be included in result
321
- expect(result.uri).toBeUndefined();
322
- expect(result.creationTime).toBeUndefined();
323
317
  });
324
318
  });
325
319
 
@@ -61,7 +61,7 @@ describe('sharedSMSComposer', () => {
61
61
  timezoneOffset: '+00:00'
62
62
  });
63
63
 
64
- expect(result.subject).toBe('<b>SMS conversation with John Customer</b>');
64
+ expect(result.subject).toBe('SMS conversation with John Customer');
65
65
  expect(result.body).toContain('<b>Conversation summary</b>');
66
66
  expect(result.body).toContain('<b>Participants</b>');
67
67
  expect(result.body).toContain('<li>');
@@ -22,6 +22,13 @@ describe('MCP Tool: collectAuthInfo', () => {
22
22
  platforms: {
23
23
  salesforce: {
24
24
  name: 'salesforce',
25
+ auth: {
26
+ type: 'oauth',
27
+ oauth: {
28
+ authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
29
+ clientId: 'test-client-id'
30
+ }
31
+ },
25
32
  environment: {
26
33
  type: 'selectable',
27
34
  selections: [
@@ -56,6 +63,13 @@ describe('MCP Tool: collectAuthInfo', () => {
56
63
  platforms: {
57
64
  netsuite: {
58
65
  name: 'netsuite',
66
+ auth: {
67
+ type: 'oauth',
68
+ oauth: {
69
+ authUrl: 'https://system.netsuite.com/app/login/oauth2/authorize.nl',
70
+ clientId: 'test-client-id'
71
+ }
72
+ },
59
73
  environment: {
60
74
  type: 'dynamic'
61
75
  }
@@ -86,6 +100,13 @@ describe('MCP Tool: collectAuthInfo', () => {
86
100
  platforms: {
87
101
  salesforce: {
88
102
  name: 'salesforce',
103
+ auth: {
104
+ type: 'oauth',
105
+ oauth: {
106
+ authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
107
+ clientId: 'test-client-id'
108
+ }
109
+ },
89
110
  environment: {
90
111
  type: 'selectable',
91
112
  selections: [
@@ -115,6 +136,13 @@ describe('MCP Tool: collectAuthInfo', () => {
115
136
  platforms: {
116
137
  netsuite: {
117
138
  name: 'netsuite',
139
+ auth: {
140
+ type: 'oauth',
141
+ oauth: {
142
+ authUrl: 'https://system.netsuite.com/app/login/oauth2/authorize.nl',
143
+ clientId: 'test-client-id'
144
+ }
145
+ },
118
146
  environment: {
119
147
  type: 'dynamic'
120
148
  }
@@ -141,6 +169,13 @@ describe('MCP Tool: collectAuthInfo', () => {
141
169
  platforms: {
142
170
  salesforce: {
143
171
  name: 'salesforce',
172
+ auth: {
173
+ type: 'oauth',
174
+ oauth: {
175
+ authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
176
+ clientId: 'test-client-id'
177
+ }
178
+ },
144
179
  environment: {
145
180
  type: 'selectable',
146
181
  selections: [
@@ -169,6 +204,13 @@ describe('MCP Tool: collectAuthInfo', () => {
169
204
  platforms: {
170
205
  netsuite: {
171
206
  name: 'netsuite',
207
+ auth: {
208
+ type: 'oauth',
209
+ oauth: {
210
+ authUrl: 'https://system.netsuite.com/app/login/oauth2/authorize.nl',
211
+ clientId: 'test-client-id'
212
+ }
213
+ },
172
214
  environment: {
173
215
  type: 'dynamic'
174
216
  }
@@ -2,6 +2,7 @@ const createCallLog = require('../../../mcp/tools/createCallLog');
2
2
  const jwt = require('../../../lib/jwt');
3
3
  const connectorRegistry = require('../../../connector/registry');
4
4
  const logCore = require('../../../handlers/log');
5
+ const contactCore = require('../../../handlers/contact');
5
6
  const util = require('../../../lib/util');
6
7
  const { CallLogModel } = require('../../../models/callLogModel');
7
8
 
@@ -9,6 +10,7 @@ const { CallLogModel } = require('../../../models/callLogModel');
9
10
  jest.mock('../../../lib/jwt');
10
11
  jest.mock('../../../connector/registry');
11
12
  jest.mock('../../../handlers/log');
13
+ jest.mock('../../../handlers/contact');
12
14
  jest.mock('../../../lib/util');
13
15
  jest.mock('../../../models/callLogModel');
14
16
 
@@ -34,10 +36,8 @@ describe('MCP Tool: createCallLog', () => {
34
36
  test('should have detailed inputSchema for incomingData', () => {
35
37
  const incomingDataSchema = createCallLog.definition.inputSchema.properties.incomingData;
36
38
  expect(incomingDataSchema.properties).toHaveProperty('logInfo');
37
- expect(incomingDataSchema.properties).toHaveProperty('contactId');
38
39
  expect(incomingDataSchema.properties).toHaveProperty('note');
39
40
  expect(incomingDataSchema.required).toContain('logInfo');
40
- expect(incomingDataSchema.required).toContain('contactId');
41
41
  });
42
42
  });
43
43
 
@@ -55,7 +55,6 @@ describe('MCP Tool: createCallLog', () => {
55
55
  to: { phoneNumber: '+0987654321', name: 'Company' },
56
56
  accountId: 'rc-account-123'
57
57
  },
58
- contactId: 'contact-123',
59
58
  contactName: 'John Doe',
60
59
  contactType: 'Contact',
61
60
  note: 'Test call note'
@@ -75,6 +74,12 @@ describe('MCP Tool: createCallLog', () => {
75
74
 
76
75
  util.getHashValue.mockReturnValue('hashed-account-id');
77
76
 
77
+ // Mock contactCore.findContact to return a contact
78
+ contactCore.findContact.mockResolvedValue({
79
+ successful: true,
80
+ contact: [{ id: 'contact-123', isNewContact: false }]
81
+ });
82
+
78
83
  logCore.createCallLog.mockResolvedValue({
79
84
  successful: true,
80
85
  logId: 'crm-log-123',
@@ -99,13 +104,6 @@ describe('MCP Tool: createCallLog', () => {
99
104
  expect(CallLogModel.findOne).toHaveBeenCalledWith({
100
105
  where: { sessionId: 'session-123' }
101
106
  });
102
- expect(logCore.createCallLog).toHaveBeenCalledWith({
103
- platform: 'testCRM',
104
- userId: 'user-123',
105
- incomingData: mockIncomingData,
106
- hashedAccountId: 'hashed-account-id',
107
- isFromSSCL: false
108
- });
109
107
  });
110
108
 
111
109
  test('should create call log with AI note and transcript', async () => {
@@ -120,7 +118,6 @@ describe('MCP Tool: createCallLog', () => {
120
118
  from: { phoneNumber: '+0987654321' },
121
119
  to: { phoneNumber: '+1234567890' }
122
120
  },
123
- contactId: 'contact-456',
124
121
  aiNote: 'AI generated summary of the call',
125
122
  transcript: 'Full call transcript text'
126
123
  };
@@ -137,6 +134,12 @@ describe('MCP Tool: createCallLog', () => {
137
134
 
138
135
  CallLogModel.findOne.mockResolvedValue(null);
139
136
 
137
+ // Mock contactCore.findContact to return a contact
138
+ contactCore.findContact.mockResolvedValue({
139
+ successful: true,
140
+ contact: [{ id: 'contact-456', isNewContact: false }]
141
+ });
142
+
140
143
  logCore.createCallLog.mockResolvedValue({
141
144
  successful: true,
142
145
  logId: 'crm-log-456'
@@ -165,7 +168,6 @@ describe('MCP Tool: createCallLog', () => {
165
168
  from: { phoneNumber: '+1234567890' },
166
169
  to: { phoneNumber: '+0987654321' }
167
170
  },
168
- contactId: 'contact-789',
169
171
  additionalSubmission: {
170
172
  isAssignedToUser: true,
171
173
  adminAssignedUserToken: 'admin-jwt-token',
@@ -185,6 +187,12 @@ describe('MCP Tool: createCallLog', () => {
185
187
 
186
188
  CallLogModel.findOne.mockResolvedValue(null);
187
189
 
190
+ // Mock contactCore.findContact to return a contact
191
+ contactCore.findContact.mockResolvedValue({
192
+ successful: true,
193
+ contact: [{ id: 'contact-789', isNewContact: false }]
194
+ });
195
+
188
196
  logCore.createCallLog.mockResolvedValue({
189
197
  successful: true,
190
198
  logId: 'crm-log-789'
@@ -352,8 +360,7 @@ describe('MCP Tool: createCallLog', () => {
352
360
  duration: 45,
353
361
  from: { phoneNumber: '+1234567890' },
354
362
  to: { phoneNumber: '+0987654321' }
355
- },
356
- contactId: 'contact-999'
363
+ }
357
364
  };
358
365
 
359
366
  jwt.decodeJwt.mockReturnValue({
@@ -368,6 +375,12 @@ describe('MCP Tool: createCallLog', () => {
368
375
 
369
376
  CallLogModel.findOne.mockResolvedValue(null);
370
377
 
378
+ // Mock contactCore.findContact to return a contact
379
+ contactCore.findContact.mockResolvedValue({
380
+ successful: true,
381
+ contact: [{ id: 'contact-999', isNewContact: false }]
382
+ });
383
+
371
384
  logCore.createCallLog.mockResolvedValue({
372
385
  successful: false,
373
386
  returnMessage: { message: 'Failed to create log in CRM' }
@@ -37,7 +37,7 @@ describe('MCP Tool: doAuth', () => {
37
37
  platforms: {
38
38
  testCRM: {
39
39
  name: 'testCRM',
40
- auth: { type: 'apiKey' },
40
+ auth: { type: 'apiKey', apiKey: { name: 'apiKey' } },
41
41
  environment: { type: 'fixed' }
42
42
  }
43
43
  }
@@ -88,7 +88,7 @@ describe('MCP Tool: doAuth', () => {
88
88
  platforms: {
89
89
  testCRM: {
90
90
  name: 'testCRM',
91
- auth: { type: 'apiKey' }
91
+ auth: { type: 'apiKey', apiKey: { name: 'apiKey' } }
92
92
  }
93
93
  }
94
94
  };
@@ -135,7 +135,7 @@ describe('MCP Tool: doAuth', () => {
135
135
  platforms: {
136
136
  testCRM: {
137
137
  name: 'testCRM',
138
- auth: { type: 'apiKey' }
138
+ auth: { type: 'apiKey', apiKey: { name: 'apiKey' } }
139
139
  }
140
140
  }
141
141
  };
@@ -203,7 +203,10 @@ describe('MCP Tool: doAuth', () => {
203
203
  name: 'salesforce',
204
204
  auth: {
205
205
  type: 'oauth',
206
- oauth: {}
206
+ oauth: {
207
+ authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
208
+ clientId: 'test-client-id'
209
+ }
207
210
  }
208
211
  }
209
212
  }
@@ -254,7 +257,10 @@ describe('MCP Tool: doAuth', () => {
254
257
  name: 'salesforce',
255
258
  auth: {
256
259
  type: 'oauth',
257
- oauth: {}
260
+ oauth: {
261
+ authUrl: 'https://login.salesforce.com/services/oauth2/authorize',
262
+ clientId: 'test-client-id'
263
+ }
258
264
  }
259
265
  }
260
266
  }
@@ -264,12 +270,12 @@ describe('MCP Tool: doAuth', () => {
264
270
  userInfo: null
265
271
  });
266
272
 
267
- // Act
273
+ // Act - callbackUri needs code= and state= to be treated as OAuth callback
268
274
  const result = await doAuth.execute({
269
275
  connectorManifest: mockManifest,
270
276
  connectorName: 'salesforce',
271
277
  hostname: 'login.salesforce.com',
272
- callbackUri: 'https://redirect.com?error=access_denied'
278
+ callbackUri: 'https://redirect.com?code=invalid-code&state=test-state'
273
279
  });
274
280
 
275
281
  // Assert
@@ -305,9 +311,16 @@ describe('MCP Tool: doAuth', () => {
305
311
  connectorName: 'customCRM'
306
312
  });
307
313
 
308
- // Assert
314
+ // Assert - state is now URL-encoded and includes sessionId, platform, hostname, plus customState
309
315
  expect(result.success).toBe(true);
310
- expect(result.data.authUri).toContain('state=custom=state&other=value');
316
+ // The state parameter now contains session info and custom state appended
317
+ // Decode and verify custom state is included
318
+ const stateMatch = result.data.authUri.match(/state=([^&]+)/);
319
+ expect(stateMatch).toBeTruthy();
320
+ const decodedState = decodeURIComponent(stateMatch[1]);
321
+ expect(decodedState).toContain('custom=state&other=value');
322
+ expect(decodedState).toContain('sessionId=');
323
+ expect(decodedState).toContain('platform=customCRM');
311
324
  });
312
325
  });
313
326
 
@@ -318,7 +331,7 @@ describe('MCP Tool: doAuth', () => {
318
331
  platforms: {
319
332
  testCRM: {
320
333
  name: 'testCRM',
321
- auth: { type: 'apiKey' }
334
+ auth: { type: 'apiKey', apiKey: { name: 'apiKey' } }
322
335
  }
323
336
  }
324
337
  };