@app-connect/core 1.7.17 → 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 +2 -2
  43. package/releaseNotes.json +13 -1
  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
@@ -21,8 +21,8 @@ describe('MCP Tool: logout', () => {
21
21
  expect(logout.definition.inputSchema).toBeDefined();
22
22
  });
23
23
 
24
- test('should have jwtToken property', () => {
25
- expect(logout.definition.inputSchema.properties).toHaveProperty('jwtToken');
24
+ test('should have empty properties (jwtToken is server-injected, not in schema)', () => {
25
+ expect(logout.definition.inputSchema.properties).toEqual({});
26
26
  });
27
27
  });
28
28
 
@@ -1,12 +0,0 @@
1
- # Supported platforms
2
-
3
- - Clio
4
- - Insightly
5
- - Redtail
6
- - NetSuite
7
-
8
- # Unsupported platforms
9
-
10
- - Bullhorn (unique OAuth)
11
- - Pipedrive (unique OAuth)
12
- - Google Sheets (extra sheet config)
@@ -1,91 +0,0 @@
1
- /**
2
- * MCP Tool: User Authentication
3
- *
4
- * This tool authenticates the user with the CRM platform.
5
- * It uses the platform-specific connector to authenticate the user.
6
- */
7
-
8
- const { isManifestValid } = require('../lib/validator');
9
-
10
- const toolDefinition = {
11
- name: 'collectAuthInfo',
12
- description: '(This step is skipped if auth type is "apiKey" or environment type is "fixed", this is a MUST if environment type is "dynamic" or "selectable") Auth flow step.3. Get information that is required for authentication. Next step is calling step.4 "doAuth" tool.',
13
- inputSchema: {
14
- type: 'object',
15
- properties: {
16
- connectorManifest: {
17
- type: 'object',
18
- description: 'connectorManifest variable from above conversation. Must be the full manifest object, not just serverUrl'
19
- },
20
- hostname: {
21
- type: 'string',
22
- description: 'For "dynamic" type environment. User is to login to CRM account then copy and paste the hostname over here.'
23
- },
24
- selection: {
25
- type: 'string',
26
- description: 'For "selectable" type environment. User is to select a name (NOT value) of the options from the selectable list'
27
- },
28
- connectorName: {
29
- type: 'string',
30
- description: 'connectorName variable from above conversation.'
31
- }
32
- },
33
- required: ['connectorManifest', 'connectorName']
34
- },
35
- annotations: {
36
- readOnlyHint: true,
37
- openWorldHint: false,
38
- destructiveHint: false
39
- }
40
- };
41
-
42
- /**
43
- * Execute the collectAuthInfo tool
44
- * @param {Object} args - The tool arguments
45
- * @param {string} args.connectorManifest - Connector manifest from conversation or memory.
46
- * @param {string} args.hostname - For "dynamic" type environment. User is to login to CRM account then copy and paste the hostname over here.
47
- * @param {string} args.selection - For "selectable" type environment. User is to select a name (NOT value) of the options from the selectable list
48
- * @param {string} args.connectorName - Connector name from conversation or memory.
49
- * @returns {Object} Result object with hostname or selection
50
- */
51
- async function execute(args) {
52
- try {
53
- const { connectorManifest, hostname, selection, connectorName } = args;
54
- const { isValid, errors } = isManifestValid({ connectorManifest, connectorName });
55
- if (!isValid) {
56
- return {
57
- success: false,
58
- error: "Invalid connector manifest",
59
- errorDetails: errors.join(', '),
60
- }
61
- }
62
- let result = '';
63
- switch (connectorManifest.platforms[connectorName].environment.type) {
64
- case 'selectable':
65
- result = connectorManifest.platforms[connectorName].environment.selections.find(s => s.name === selection).const;
66
- break;
67
- case 'dynamic':
68
- result = hostname;
69
- break;
70
- }
71
- const url = new URL(result);
72
- return {
73
- success: true,
74
- data: {
75
- hostname: url.hostname,
76
- // Add explicit instruction
77
- message: "IMPORTANT: Use hostname in the next few authentication steps.",
78
- }
79
- }
80
- }
81
- catch (error) {
82
- return {
83
- success: false,
84
- error: error.message || 'Unknown error occurred',
85
- errorDetails: error.stack
86
- };
87
- }
88
- }
89
-
90
- exports.definition = toolDefinition;
91
- exports.execute = execute;
@@ -1,69 +0,0 @@
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.',
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
- annotations: {
23
- readOnlyHint: true,
24
- openWorldHint: false,
25
- destructiveHint: false
26
- }
27
- };
28
-
29
- /**
30
- * Execute the setConnector tool
31
- * @param {Object} args - The tool arguments
32
- * @param {string} args.connectorDisplayName - Connector display name to set
33
- * @returns {Object} Result object with connector information
34
- */
35
- async function execute(args) {
36
- try {
37
- const { connectorDisplayName } = args;
38
- const { connectors: publicConnectorList } = await developerPortal.getPublicConnectorList();
39
- const { privateConnectors } = await developerPortal.getPrivateConnectorList();
40
- const connectorList = [...publicConnectorList, ...privateConnectors];
41
- const connector = connectorList.find(c => c.displayName === connectorDisplayName);
42
- const connectorName = connector.name;
43
- const connectorManifest = await developerPortal.getConnectorManifest({ connectorId: connector.id, isPrivate: connector.status === 'private' });
44
- if (!connectorManifest) {
45
- throw new Error(`Connector manifest not found: ${connectorDisplayName}`);
46
- }
47
- return {
48
- success: true,
49
- data: {
50
- connectorManifest,
51
- connectorDisplayName,
52
- connectorName,
53
- // Add explicit instruction
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'.",
55
-
56
- }
57
- };
58
- }
59
- catch (error) {
60
- return {
61
- success: false,
62
- error: error.message || 'Unknown error occurred',
63
- errorDetails: error.stack
64
- };
65
- }
66
- }
67
-
68
- exports.definition = toolDefinition;
69
- exports.execute = execute;
@@ -1,234 +0,0 @@
1
- const collectAuthInfo = require('../../../mcp/tools/collectAuthInfo');
2
-
3
- describe('MCP Tool: collectAuthInfo', () => {
4
- describe('tool definition', () => {
5
- test('should have correct tool definition', () => {
6
- expect(collectAuthInfo.definition).toBeDefined();
7
- expect(collectAuthInfo.definition.name).toBe('collectAuthInfo');
8
- expect(collectAuthInfo.definition.description).toContain('Auth flow step.3');
9
- expect(collectAuthInfo.definition.inputSchema).toBeDefined();
10
- });
11
-
12
- test('should require connectorManifest and connectorName parameters', () => {
13
- expect(collectAuthInfo.definition.inputSchema.required).toContain('connectorManifest');
14
- expect(collectAuthInfo.definition.inputSchema.required).toContain('connectorName');
15
- });
16
- });
17
-
18
- describe('execute', () => {
19
- test('should handle selectable environment type', async () => {
20
- // Arrange
21
- const mockManifest = {
22
- platforms: {
23
- salesforce: {
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
- },
32
- environment: {
33
- type: 'selectable',
34
- selections: [
35
- { name: 'Production', const: 'https://login.salesforce.com' },
36
- { name: 'Sandbox', const: 'https://test.salesforce.com' }
37
- ]
38
- }
39
- }
40
- }
41
- };
42
-
43
- // Act
44
- const result = await collectAuthInfo.execute({
45
- connectorManifest: mockManifest,
46
- connectorName: 'salesforce',
47
- selection: 'Production'
48
- });
49
-
50
- // Assert
51
- expect(result).toEqual({
52
- success: true,
53
- data: {
54
- hostname: 'login.salesforce.com',
55
- message: expect.stringContaining('IMPORTANT')
56
- }
57
- });
58
- });
59
-
60
- test('should handle dynamic environment type', async () => {
61
- // Arrange
62
- const mockManifest = {
63
- platforms: {
64
- netsuite: {
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
- },
73
- environment: {
74
- type: 'dynamic'
75
- }
76
- }
77
- }
78
- };
79
-
80
- // Act
81
- const result = await collectAuthInfo.execute({
82
- connectorManifest: mockManifest,
83
- connectorName: 'netsuite',
84
- hostname: 'https://1234567.app.netsuite.com'
85
- });
86
-
87
- // Assert
88
- expect(result).toEqual({
89
- success: true,
90
- data: {
91
- hostname: '1234567.app.netsuite.com',
92
- message: expect.stringContaining('IMPORTANT')
93
- }
94
- });
95
- });
96
-
97
- test('should handle sandbox selection', async () => {
98
- // Arrange
99
- const mockManifest = {
100
- platforms: {
101
- salesforce: {
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
- },
110
- environment: {
111
- type: 'selectable',
112
- selections: [
113
- { name: 'Production', const: 'https://login.salesforce.com' },
114
- { name: 'Sandbox', const: 'https://test.salesforce.com' }
115
- ]
116
- }
117
- }
118
- }
119
- };
120
-
121
- // Act
122
- const result = await collectAuthInfo.execute({
123
- connectorManifest: mockManifest,
124
- connectorName: 'salesforce',
125
- selection: 'Sandbox'
126
- });
127
-
128
- // Assert
129
- expect(result.success).toBe(true);
130
- expect(result.data.hostname).toBe('test.salesforce.com');
131
- });
132
-
133
- test('should handle invalid hostname URL', async () => {
134
- // Arrange
135
- const mockManifest = {
136
- platforms: {
137
- netsuite: {
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
- },
146
- environment: {
147
- type: 'dynamic'
148
- }
149
- }
150
- }
151
- };
152
-
153
- // Act
154
- const result = await collectAuthInfo.execute({
155
- connectorManifest: mockManifest,
156
- connectorName: 'netsuite',
157
- hostname: 'invalid-url'
158
- });
159
-
160
- // Assert
161
- expect(result.success).toBe(false);
162
- expect(result.error).toBeDefined();
163
- expect(result.errorDetails).toBeDefined();
164
- });
165
-
166
- test('should handle missing selection for selectable type', async () => {
167
- // Arrange
168
- const mockManifest = {
169
- platforms: {
170
- salesforce: {
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
- },
179
- environment: {
180
- type: 'selectable',
181
- selections: [
182
- { name: 'Production', const: 'https://login.salesforce.com' }
183
- ]
184
- }
185
- }
186
- }
187
- };
188
-
189
- // Act
190
- const result = await collectAuthInfo.execute({
191
- connectorManifest: mockManifest,
192
- connectorName: 'salesforce',
193
- selection: 'NonExistent'
194
- });
195
-
196
- // Assert
197
- expect(result.success).toBe(false);
198
- expect(result.error).toBeDefined();
199
- });
200
-
201
- test('should handle missing hostname for dynamic type', async () => {
202
- // Arrange
203
- const mockManifest = {
204
- platforms: {
205
- netsuite: {
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
- },
214
- environment: {
215
- type: 'dynamic'
216
- }
217
- }
218
- }
219
- };
220
-
221
- // Act
222
- const result = await collectAuthInfo.execute({
223
- connectorManifest: mockManifest,
224
- connectorName: 'netsuite'
225
- // hostname is missing
226
- });
227
-
228
- // Assert
229
- expect(result.success).toBe(false);
230
- expect(result.error).toBeDefined();
231
- });
232
- });
233
- });
234
-
@@ -1,177 +0,0 @@
1
- const setConnector = require('../../../mcp/tools/setConnector');
2
- const developerPortal = require('../../../connector/developerPortal');
3
-
4
- // Mock the developerPortal module
5
- jest.mock('../../../connector/developerPortal');
6
-
7
- describe('MCP Tool: setConnector', () => {
8
- beforeEach(() => {
9
- jest.clearAllMocks();
10
- });
11
-
12
- describe('tool definition', () => {
13
- test('should have correct tool definition', () => {
14
- expect(setConnector.definition).toBeDefined();
15
- expect(setConnector.definition.name).toBe('setConnector');
16
- expect(setConnector.definition.description).toContain('Auth flow step.2');
17
- expect(setConnector.definition.inputSchema).toBeDefined();
18
- });
19
-
20
- test('should require connectorDisplayName parameter', () => {
21
- expect(setConnector.definition.inputSchema.required).toContain('connectorDisplayName');
22
- });
23
- });
24
-
25
- describe('execute', () => {
26
- test('should set connector successfully', async () => {
27
- // Arrange
28
- const mockPublicConnectors = [
29
- { id: '1', name: 'salesforce', displayName: 'Salesforce', status: 'public' }
30
- ];
31
- const mockPrivateConnectors = [];
32
- const mockManifest = {
33
- platforms: {
34
- salesforce: {
35
- name: 'salesforce',
36
- auth: { type: 'oauth' },
37
- environment: { type: 'fixed' }
38
- }
39
- }
40
- };
41
-
42
- developerPortal.getPublicConnectorList.mockResolvedValue({
43
- connectors: mockPublicConnectors
44
- });
45
- developerPortal.getPrivateConnectorList.mockResolvedValue({
46
- privateConnectors: mockPrivateConnectors
47
- });
48
- developerPortal.getConnectorManifest.mockResolvedValue(mockManifest);
49
-
50
- // Act
51
- const result = await setConnector.execute({
52
- connectorDisplayName: 'Salesforce'
53
- });
54
-
55
- // Assert
56
- expect(result).toEqual({
57
- success: true,
58
- data: {
59
- connectorManifest: mockManifest,
60
- connectorDisplayName: 'Salesforce',
61
- connectorName: 'salesforce',
62
- message: expect.stringContaining('IMPORTANT')
63
- }
64
- });
65
- expect(developerPortal.getConnectorManifest).toHaveBeenCalledWith({
66
- connectorId: '1',
67
- isPrivate: false
68
- });
69
- });
70
-
71
- test('should handle private connector', async () => {
72
- // Arrange
73
- const mockPublicConnectors = [];
74
- const mockPrivateConnectors = [
75
- { id: '2', name: 'custom-crm', displayName: 'Custom CRM', status: 'private' }
76
- ];
77
- const mockManifest = {
78
- platforms: {
79
- 'custom-crm': {
80
- name: 'custom-crm',
81
- auth: { type: 'apiKey' }
82
- }
83
- }
84
- };
85
-
86
- developerPortal.getPublicConnectorList.mockResolvedValue({
87
- connectors: mockPublicConnectors
88
- });
89
- developerPortal.getPrivateConnectorList.mockResolvedValue({
90
- privateConnectors: mockPrivateConnectors
91
- });
92
- developerPortal.getConnectorManifest.mockResolvedValue(mockManifest);
93
-
94
- // Act
95
- const result = await setConnector.execute({
96
- connectorDisplayName: 'Custom CRM'
97
- });
98
-
99
- // Assert
100
- expect(result.success).toBe(true);
101
- expect(result.data.connectorName).toBe('custom-crm');
102
- expect(developerPortal.getConnectorManifest).toHaveBeenCalledWith({
103
- connectorId: '2',
104
- isPrivate: true
105
- });
106
- });
107
-
108
- test('should return error when connector manifest not found', async () => {
109
- // Arrange
110
- const mockPublicConnectors = [
111
- { id: '1', name: 'salesforce', displayName: 'Salesforce', status: 'public' }
112
- ];
113
- const mockPrivateConnectors = [];
114
-
115
- developerPortal.getPublicConnectorList.mockResolvedValue({
116
- connectors: mockPublicConnectors
117
- });
118
- developerPortal.getPrivateConnectorList.mockResolvedValue({
119
- privateConnectors: mockPrivateConnectors
120
- });
121
- developerPortal.getConnectorManifest.mockResolvedValue(null);
122
-
123
- // Act
124
- const result = await setConnector.execute({
125
- connectorDisplayName: 'Salesforce'
126
- });
127
-
128
- // Assert
129
- expect(result.success).toBe(false);
130
- expect(result.error).toContain('Connector manifest not found');
131
- });
132
-
133
- test('should return error when connector not found in list', async () => {
134
- // Arrange
135
- const mockPublicConnectors = [
136
- { id: '1', name: 'salesforce', displayName: 'Salesforce', status: 'public' }
137
- ];
138
- const mockPrivateConnectors = [];
139
-
140
- developerPortal.getPublicConnectorList.mockResolvedValue({
141
- connectors: mockPublicConnectors
142
- });
143
- developerPortal.getPrivateConnectorList.mockResolvedValue({
144
- privateConnectors: mockPrivateConnectors
145
- });
146
-
147
- // Act
148
- const result = await setConnector.execute({
149
- connectorDisplayName: 'NonExistentCRM'
150
- });
151
-
152
- // Assert
153
- expect(result.success).toBe(false);
154
- expect(result.error).toBeDefined();
155
- expect(result.errorDetails).toBeDefined();
156
- });
157
-
158
- test('should handle API errors gracefully', async () => {
159
- // Arrange
160
- const errorMessage = 'API connection failed';
161
- developerPortal.getPublicConnectorList.mockRejectedValue(
162
- new Error(errorMessage)
163
- );
164
-
165
- // Act
166
- const result = await setConnector.execute({
167
- connectorDisplayName: 'Salesforce'
168
- });
169
-
170
- // Assert
171
- expect(result.success).toBe(false);
172
- expect(result.error).toBe(errorMessage);
173
- expect(result.errorDetails).toBeDefined();
174
- });
175
- });
176
- });
177
-