@app-connect/core 1.7.18 → 1.7.19

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 (57) hide show
  1. package/connector/proxy/index.js +2 -1
  2. package/handlers/log.js +181 -10
  3. package/handlers/plugin.js +27 -0
  4. package/handlers/user.js +31 -2
  5. package/index.js +99 -22
  6. package/lib/authSession.js +21 -12
  7. package/lib/callLogComposer.js +1 -1
  8. package/lib/debugTracer.js +20 -2
  9. package/lib/util.js +21 -4
  10. package/mcp/README.md +392 -0
  11. package/mcp/mcpHandler.js +293 -82
  12. package/mcp/tools/checkAuthStatus.js +27 -34
  13. package/mcp/tools/createCallLog.js +13 -9
  14. package/mcp/tools/createContact.js +2 -6
  15. package/mcp/tools/doAuth.js +27 -157
  16. package/mcp/tools/findContactByName.js +6 -9
  17. package/mcp/tools/findContactByPhone.js +2 -6
  18. package/mcp/tools/getGoogleFilePicker.js +5 -9
  19. package/mcp/tools/getHelp.js +2 -3
  20. package/mcp/tools/getPublicConnectors.js +41 -28
  21. package/mcp/tools/index.js +11 -36
  22. package/mcp/tools/logout.js +5 -10
  23. package/mcp/tools/rcGetCallLogs.js +3 -20
  24. package/mcp/ui/App/App.tsx +361 -0
  25. package/mcp/ui/App/components/AuthInfoForm.tsx +113 -0
  26. package/mcp/ui/App/components/AuthSuccess.tsx +22 -0
  27. package/mcp/ui/App/components/ConnectorList.tsx +82 -0
  28. package/mcp/ui/App/components/DebugPanel.tsx +43 -0
  29. package/mcp/ui/App/components/OAuthConnect.tsx +270 -0
  30. package/mcp/ui/App/lib/callTool.ts +130 -0
  31. package/mcp/ui/App/lib/debugLog.ts +41 -0
  32. package/mcp/ui/App/lib/developerPortal.ts +111 -0
  33. package/mcp/ui/App/main.css +6 -0
  34. package/mcp/ui/App/root.tsx +13 -0
  35. package/mcp/ui/dist/index.html +53 -0
  36. package/mcp/ui/index.html +13 -0
  37. package/mcp/ui/package-lock.json +6356 -0
  38. package/mcp/ui/package.json +25 -0
  39. package/mcp/ui/tsconfig.json +26 -0
  40. package/mcp/ui/vite.config.ts +16 -0
  41. package/models/llmSessionModel.js +14 -0
  42. package/package.json +72 -72
  43. package/releaseNotes.json +12 -0
  44. package/test/handlers/plugin.test.js +287 -0
  45. package/test/lib/util.test.js +379 -1
  46. package/test/mcp/tools/createCallLog.test.js +3 -3
  47. package/test/mcp/tools/doAuth.test.js +40 -303
  48. package/test/mcp/tools/findContactByName.test.js +3 -3
  49. package/test/mcp/tools/findContactByPhone.test.js +3 -3
  50. package/test/mcp/tools/getGoogleFilePicker.test.js +7 -7
  51. package/test/mcp/tools/getPublicConnectors.test.js +49 -70
  52. package/test/mcp/tools/logout.test.js +2 -2
  53. package/mcp/SupportedPlatforms.md +0 -12
  54. package/mcp/tools/collectAuthInfo.js +0 -91
  55. package/mcp/tools/setConnector.js +0 -69
  56. package/test/mcp/tools/collectAuthInfo.test.js +0 -234
  57. package/test/mcp/tools/setConnector.test.js +0 -177
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@app-connect/mcp-ui",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "dependencies": {
11
+ "@openai/apps-sdk-ui": "^0.2.0",
12
+ "react": "^18.3.1",
13
+ "react-dom": "^18.3.1"
14
+ },
15
+ "devDependencies": {
16
+ "@tailwindcss/vite": "^4.0.0",
17
+ "@types/react": "^18.3.12",
18
+ "@types/react-dom": "^18.3.1",
19
+ "@vitejs/plugin-react": "^4.3.4",
20
+ "tailwindcss": "^4.0.0",
21
+ "typescript": "^5.6.3",
22
+ "vite": "^6.0.1",
23
+ "vite-plugin-singlefile": "^2.3.0"
24
+ }
25
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true,
15
+ "jsx": "react-jsx",
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "types": []
23
+ },
24
+ "include": ["App"]
25
+ }
26
+
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+ import tailwindcss from '@tailwindcss/vite'
4
+ import { viteSingleFile } from 'vite-plugin-singlefile'
5
+
6
+ export default defineConfig({
7
+ plugins: [
8
+ react(),
9
+ tailwindcss(),
10
+ viteSingleFile(),
11
+ ],
12
+ build: {
13
+ outDir: 'dist',
14
+ }
15
+ })
16
+
@@ -0,0 +1,14 @@
1
+ const Sequelize = require('sequelize');
2
+ const { sequelize } = require('./sequelize');
3
+
4
+ // Model for Admin data
5
+ exports.LlmSessionModel = sequelize.define('llmSessions', {
6
+ // LLM session ID
7
+ id: {
8
+ type: Sequelize.STRING,
9
+ primaryKey: true,
10
+ },
11
+ jwtToken: {
12
+ type: Sequelize.STRING,
13
+ }
14
+ });
package/package.json CHANGED
@@ -1,72 +1,72 @@
1
- {
2
- "name": "@app-connect/core",
3
- "version": "1.7.18",
4
- "description": "RingCentral App Connect Core",
5
- "main": "index.js",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/ringcentral/rc-unified-crm-extension.git"
9
- },
10
- "keywords": [
11
- "RingCentral",
12
- "App Connect"
13
- ],
14
- "author": "RingCentral Labs",
15
- "license": "MIT",
16
- "peerDependencies": {
17
- "axios": "^1.12.2",
18
- "express": "^4.22.1",
19
- "moment": "^2.29.4",
20
- "moment-timezone": "^0.5.39",
21
- "pg": "^8.8.0",
22
- "sequelize": "^6.29.0"
23
- },
24
- "dependencies": {
25
- "@aws-sdk/client-dynamodb": "^3.751.0",
26
- "@aws-sdk/client-s3": "^3.947.0",
27
- "@aws-sdk/s3-request-presigner": "^3.947.0",
28
- "@modelcontextprotocol/sdk": "^1.21.1",
29
- "awesome-phonenumber": "^5.6.0",
30
- "body-parser": "^1.20.4",
31
- "body-parser-xml": "^2.0.5",
32
- "client-oauth2": "^4.3.3",
33
- "cors": "^2.8.5",
34
- "country-state-city": "^3.2.1",
35
- "dotenv": "^16.0.3",
36
- "dynamoose": "^4.0.3",
37
- "jsonwebtoken": "^9.0.0",
38
- "mixpanel": "^0.18.0",
39
- "shortid": "^2.2.17",
40
- "tz-lookup": "^6.1.25",
41
- "ua-parser-js": "^1.0.38"
42
- },
43
- "scripts": {
44
- "test": "jest",
45
- "test:watch": "jest --watch",
46
- "test:coverage": "jest --coverage",
47
- "test:ci": "jest --ci --coverage --watchAll=false"
48
- },
49
- "devDependencies": {
50
- "@eslint/js": "^9.22.0",
51
- "@octokit/rest": "^19.0.5",
52
- "axios": "^1.12.2",
53
- "eslint": "^9.22.0",
54
- "express": "^4.22.1",
55
- "globals": "^16.0.0",
56
- "jest": "^29.3.1",
57
- "moment": "^2.29.4",
58
- "moment-timezone": "^0.5.39",
59
- "nock": "^13.2.9",
60
- "pg": "^8.8.0",
61
- "sequelize": "^6.29.0",
62
- "sqlite3": "^5.1.2",
63
- "supertest": "^6.3.1"
64
- },
65
- "overrides": {
66
- "js-object-utilities": "2.2.1"
67
- },
68
- "bugs": {
69
- "url": "https://github.com/ringcentral/rc-unified-crm-extension/issues"
70
- },
71
- "homepage": "https://github.com/ringcentral/rc-unified-crm-extension#readme"
72
- }
1
+ {
2
+ "name": "@app-connect/core",
3
+ "version": "1.7.19",
4
+ "description": "RingCentral App Connect Core",
5
+ "main": "index.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ringcentral/rc-unified-crm-extension.git"
9
+ },
10
+ "keywords": [
11
+ "RingCentral",
12
+ "App Connect"
13
+ ],
14
+ "author": "RingCentral Labs",
15
+ "license": "MIT",
16
+ "peerDependencies": {
17
+ "axios": "^1.12.2",
18
+ "express": "^4.22.1",
19
+ "moment": "^2.29.4",
20
+ "moment-timezone": "^0.5.39",
21
+ "pg": "^8.8.0",
22
+ "sequelize": "^6.29.0"
23
+ },
24
+ "dependencies": {
25
+ "@aws-sdk/client-dynamodb": "^3.751.0",
26
+ "@aws-sdk/client-s3": "^3.947.0",
27
+ "@aws-sdk/s3-request-presigner": "^3.947.0",
28
+ "@modelcontextprotocol/sdk": "^1.26.0",
29
+ "awesome-phonenumber": "^5.6.0",
30
+ "body-parser": "^1.20.4",
31
+ "body-parser-xml": "^2.0.5",
32
+ "client-oauth2": "^4.3.3",
33
+ "cors": "^2.8.5",
34
+ "country-state-city": "^3.2.1",
35
+ "dotenv": "^16.0.3",
36
+ "dynamoose": "^4.0.3",
37
+ "jsonwebtoken": "^9.0.0",
38
+ "mixpanel": "^0.18.0",
39
+ "shortid": "^2.2.17",
40
+ "tz-lookup": "^6.1.25",
41
+ "ua-parser-js": "^1.0.38"
42
+ },
43
+ "scripts": {
44
+ "test": "jest",
45
+ "test:watch": "jest --watch",
46
+ "test:coverage": "jest --coverage",
47
+ "test:ci": "jest --ci --coverage --watchAll=false"
48
+ },
49
+ "devDependencies": {
50
+ "@eslint/js": "^9.22.0",
51
+ "@octokit/rest": "^19.0.5",
52
+ "axios": "^1.12.2",
53
+ "eslint": "^9.22.0",
54
+ "express": "^4.22.1",
55
+ "globals": "^16.0.0",
56
+ "jest": "^29.3.1",
57
+ "moment": "^2.29.4",
58
+ "moment-timezone": "^0.5.39",
59
+ "nock": "^13.2.9",
60
+ "pg": "^8.8.0",
61
+ "sequelize": "^6.29.0",
62
+ "sqlite3": "^5.1.2",
63
+ "supertest": "^6.3.1"
64
+ },
65
+ "overrides": {
66
+ "js-object-utilities": "2.2.1"
67
+ },
68
+ "bugs": {
69
+ "url": "https://github.com/ringcentral/rc-unified-crm-extension/issues"
70
+ },
71
+ "homepage": "https://github.com/ringcentral/rc-unified-crm-extension#readme"
72
+ }
package/releaseNotes.json CHANGED
@@ -1,4 +1,16 @@
1
1
  {
2
+ "1.7.19": {
3
+ "global": [
4
+ {
5
+ "type": "Beta",
6
+ "description": "Plugin system infrastructure. No plugin available yet, but add soon. Take a quick look at its [overview](https://appconnect.labs.ringcentral.com/users/plugins)"
7
+ },
8
+ {
9
+ "type": "Better",
10
+ "description": "Click-to-dial with wider number match"
11
+ }
12
+ ]
13
+ },
2
14
  "1.7.18": {
3
15
  "global": [
4
16
  {
@@ -0,0 +1,287 @@
1
+ // Use in-memory SQLite for isolated model tests
2
+ jest.mock('../../models/sequelize', () => {
3
+ const { Sequelize } = require('sequelize');
4
+ return {
5
+ sequelize: new Sequelize({
6
+ dialect: 'sqlite',
7
+ storage: ':memory:',
8
+ logging: false,
9
+ }),
10
+ };
11
+ });
12
+
13
+ const pluginHandler = require('../../handlers/plugin');
14
+ const { CacheModel } = require('../../models/cacheModel');
15
+ const { sequelize } = require('../../models/sequelize');
16
+
17
+ describe('Plugin Handler', () => {
18
+ beforeAll(async () => {
19
+ await CacheModel.sync({ force: true });
20
+ });
21
+
22
+ afterEach(async () => {
23
+ await CacheModel.destroy({ where: {} });
24
+ jest.clearAllMocks();
25
+ });
26
+
27
+ afterAll(async () => {
28
+ await sequelize.close();
29
+ });
30
+
31
+ describe('getPluginAsyncTasks', () => {
32
+ test('should retrieve async task status by IDs from CacheModel', async () => {
33
+ // Arrange
34
+ await CacheModel.create({
35
+ id: 'user-123-task-1',
36
+ status: 'processing',
37
+ userId: 'user-123',
38
+ cacheKey: 'pluginTask-googleDrive'
39
+ });
40
+ await CacheModel.create({
41
+ id: 'user-123-task-2',
42
+ status: 'completed',
43
+ userId: 'user-123',
44
+ cacheKey: 'pluginTask-piiRedaction'
45
+ });
46
+
47
+ // Act
48
+ const result = await pluginHandler.getPluginAsyncTasks({
49
+ asyncTaskIds: ['user-123-task-1', 'user-123-task-2']
50
+ });
51
+
52
+ // Assert
53
+ expect(result).toHaveLength(2);
54
+ expect(result).toContainEqual({ cacheKey: 'pluginTask-googleDrive', status: 'processing' });
55
+ expect(result).toContainEqual({ cacheKey: 'pluginTask-piiRedaction', status: 'completed' });
56
+ });
57
+
58
+ test('should return empty array when no matching tasks found', async () => {
59
+ // Arrange - no tasks created
60
+
61
+ // Act
62
+ const result = await pluginHandler.getPluginAsyncTasks({
63
+ asyncTaskIds: ['non-existent-task-1', 'non-existent-task-2']
64
+ });
65
+
66
+ // Assert
67
+ expect(result).toEqual([]);
68
+ });
69
+
70
+ test('should filter and return only tasks with matching IDs', async () => {
71
+ // Arrange
72
+ await CacheModel.create({
73
+ id: 'user-123-task-1',
74
+ status: 'processing',
75
+ userId: 'user-123',
76
+ cacheKey: 'pluginTask-googleDrive'
77
+ });
78
+ await CacheModel.create({
79
+ id: 'user-456-task-2',
80
+ status: 'completed',
81
+ userId: 'user-456',
82
+ cacheKey: 'pluginTask-piiRedaction'
83
+ });
84
+ await CacheModel.create({
85
+ id: 'user-789-task-3',
86
+ status: 'failed',
87
+ userId: 'user-789',
88
+ cacheKey: 'pluginTask-other'
89
+ });
90
+
91
+ // Act - only request tasks for user-123 and user-789
92
+ const result = await pluginHandler.getPluginAsyncTasks({
93
+ asyncTaskIds: ['user-123-task-1', 'user-789-task-3']
94
+ });
95
+
96
+ // Assert
97
+ expect(result).toHaveLength(2);
98
+ expect(result).toContainEqual({ cacheKey: 'pluginTask-googleDrive', status: 'processing' });
99
+ expect(result).toContainEqual({ cacheKey: 'pluginTask-other', status: 'failed' });
100
+ expect(result).not.toContainEqual(expect.objectContaining({ cacheKey: 'pluginTask-piiRedaction' }));
101
+ });
102
+
103
+ test('should automatically remove completed tasks from cache after retrieval', async () => {
104
+ // Arrange
105
+ await CacheModel.create({
106
+ id: 'user-123-completed-task',
107
+ status: 'completed',
108
+ userId: 'user-123',
109
+ cacheKey: 'pluginTask-googleDrive'
110
+ });
111
+
112
+ // Act
113
+ const result = await pluginHandler.getPluginAsyncTasks({
114
+ asyncTaskIds: ['user-123-completed-task']
115
+ });
116
+
117
+ // Assert - result should contain the task
118
+ expect(result).toHaveLength(1);
119
+ expect(result[0]).toEqual({ cacheKey: 'pluginTask-googleDrive', status: 'completed' });
120
+
121
+ // Verify task was removed from cache
122
+ const remainingTask = await CacheModel.findByPk('user-123-completed-task');
123
+ expect(remainingTask).toBeNull();
124
+ });
125
+
126
+ test('should automatically remove failed tasks from cache after retrieval', async () => {
127
+ // Arrange
128
+ await CacheModel.create({
129
+ id: 'user-123-failed-task',
130
+ status: 'failed',
131
+ userId: 'user-123',
132
+ cacheKey: 'pluginTask-piiRedaction'
133
+ });
134
+
135
+ // Act
136
+ const result = await pluginHandler.getPluginAsyncTasks({
137
+ asyncTaskIds: ['user-123-failed-task']
138
+ });
139
+
140
+ // Assert - result should contain the task
141
+ expect(result).toHaveLength(1);
142
+ expect(result[0]).toEqual({ cacheKey: 'pluginTask-piiRedaction', status: 'failed' });
143
+
144
+ // Verify task was removed from cache
145
+ const remainingTask = await CacheModel.findByPk('user-123-failed-task');
146
+ expect(remainingTask).toBeNull();
147
+ });
148
+
149
+ test('should preserve pending tasks in cache after retrieval', async () => {
150
+ // Arrange
151
+ await CacheModel.create({
152
+ id: 'user-123-pending-task',
153
+ status: 'pending',
154
+ userId: 'user-123',
155
+ cacheKey: 'pluginTask-googleDrive'
156
+ });
157
+
158
+ // Act
159
+ const result = await pluginHandler.getPluginAsyncTasks({
160
+ asyncTaskIds: ['user-123-pending-task']
161
+ });
162
+
163
+ // Assert - result should contain the task
164
+ expect(result).toHaveLength(1);
165
+ expect(result[0]).toEqual({ cacheKey: 'pluginTask-googleDrive', status: 'pending' });
166
+
167
+ // Verify task was NOT removed from cache
168
+ const remainingTask = await CacheModel.findByPk('user-123-pending-task');
169
+ expect(remainingTask).not.toBeNull();
170
+ expect(remainingTask.status).toBe('pending');
171
+ });
172
+
173
+ test('should preserve processing tasks in cache after retrieval', async () => {
174
+ // Arrange
175
+ await CacheModel.create({
176
+ id: 'user-123-processing-task',
177
+ status: 'processing',
178
+ userId: 'user-123',
179
+ cacheKey: 'pluginTask-piiRedaction'
180
+ });
181
+
182
+ // Act
183
+ const result = await pluginHandler.getPluginAsyncTasks({
184
+ asyncTaskIds: ['user-123-processing-task']
185
+ });
186
+
187
+ // Assert - result should contain the task
188
+ expect(result).toHaveLength(1);
189
+ expect(result[0]).toEqual({ cacheKey: 'pluginTask-piiRedaction', status: 'processing' });
190
+
191
+ // Verify task was NOT removed from cache
192
+ const remainingTask = await CacheModel.findByPk('user-123-processing-task');
193
+ expect(remainingTask).not.toBeNull();
194
+ expect(remainingTask.status).toBe('processing');
195
+ });
196
+
197
+ test('should handle mixed task statuses - remove completed/failed but preserve pending/processing', async () => {
198
+ // Arrange
199
+ await CacheModel.create({
200
+ id: 'task-completed',
201
+ status: 'completed',
202
+ userId: 'user-123',
203
+ cacheKey: 'pluginTask-1'
204
+ });
205
+ await CacheModel.create({
206
+ id: 'task-failed',
207
+ status: 'failed',
208
+ userId: 'user-123',
209
+ cacheKey: 'pluginTask-2'
210
+ });
211
+ await CacheModel.create({
212
+ id: 'task-pending',
213
+ status: 'pending',
214
+ userId: 'user-123',
215
+ cacheKey: 'pluginTask-3'
216
+ });
217
+ await CacheModel.create({
218
+ id: 'task-processing',
219
+ status: 'processing',
220
+ userId: 'user-123',
221
+ cacheKey: 'pluginTask-4'
222
+ });
223
+
224
+ // Act
225
+ const result = await pluginHandler.getPluginAsyncTasks({
226
+ asyncTaskIds: ['task-completed', 'task-failed', 'task-pending', 'task-processing']
227
+ });
228
+
229
+ // Assert - all tasks should be in result
230
+ expect(result).toHaveLength(4);
231
+
232
+ // Verify completed and failed tasks were removed
233
+ expect(await CacheModel.findByPk('task-completed')).toBeNull();
234
+ expect(await CacheModel.findByPk('task-failed')).toBeNull();
235
+
236
+ // Verify pending and processing tasks were preserved
237
+ expect(await CacheModel.findByPk('task-pending')).not.toBeNull();
238
+ expect(await CacheModel.findByPk('task-processing')).not.toBeNull();
239
+ });
240
+
241
+ test('should handle empty asyncTaskIds array', async () => {
242
+ // Arrange
243
+ await CacheModel.create({
244
+ id: 'some-task',
245
+ status: 'completed',
246
+ userId: 'user-123',
247
+ cacheKey: 'pluginTask-test'
248
+ });
249
+
250
+ // Act
251
+ const result = await pluginHandler.getPluginAsyncTasks({
252
+ asyncTaskIds: []
253
+ });
254
+
255
+ // Assert
256
+ expect(result).toEqual([]);
257
+
258
+ // Verify existing task was not touched
259
+ const existingTask = await CacheModel.findByPk('some-task');
260
+ expect(existingTask).not.toBeNull();
261
+ });
262
+
263
+ test('should preserve initialized status tasks in cache', async () => {
264
+ // Arrange
265
+ await CacheModel.create({
266
+ id: 'user-123-initialized-task',
267
+ status: 'initialized',
268
+ userId: 'user-123',
269
+ cacheKey: 'pluginTask-googleDrive'
270
+ });
271
+
272
+ // Act
273
+ const result = await pluginHandler.getPluginAsyncTasks({
274
+ asyncTaskIds: ['user-123-initialized-task']
275
+ });
276
+
277
+ // Assert
278
+ expect(result).toHaveLength(1);
279
+ expect(result[0]).toEqual({ cacheKey: 'pluginTask-googleDrive', status: 'initialized' });
280
+
281
+ // Verify task was NOT removed from cache
282
+ const remainingTask = await CacheModel.findByPk('user-123-initialized-task');
283
+ expect(remainingTask).not.toBeNull();
284
+ });
285
+ });
286
+ });
287
+