@app-connect/core 1.7.18 → 1.7.20
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.
- package/connector/proxy/index.js +2 -1
- package/handlers/auth.js +30 -55
- package/handlers/log.js +182 -10
- package/handlers/plugin.js +27 -0
- package/handlers/user.js +31 -2
- package/index.js +115 -22
- package/lib/authSession.js +21 -12
- package/lib/callLogComposer.js +1 -1
- package/lib/debugTracer.js +20 -2
- package/lib/util.js +21 -4
- package/mcp/README.md +395 -0
- package/mcp/mcpHandler.js +318 -82
- package/mcp/tools/checkAuthStatus.js +28 -35
- package/mcp/tools/createCallLog.js +13 -9
- package/mcp/tools/createContact.js +2 -6
- package/mcp/tools/doAuth.js +27 -157
- package/mcp/tools/findContactByName.js +6 -9
- package/mcp/tools/findContactByPhone.js +2 -6
- package/mcp/tools/getGoogleFilePicker.js +5 -9
- package/mcp/tools/getHelp.js +2 -3
- package/mcp/tools/getPublicConnectors.js +55 -24
- package/mcp/tools/index.js +11 -36
- package/mcp/tools/logout.js +32 -13
- package/mcp/tools/rcGetCallLogs.js +3 -20
- package/mcp/ui/App/App.tsx +358 -0
- package/mcp/ui/App/components/AuthInfoForm.tsx +113 -0
- package/mcp/ui/App/components/AuthSuccess.tsx +22 -0
- package/mcp/ui/App/components/ConnectorList.tsx +82 -0
- package/mcp/ui/App/components/DebugPanel.tsx +43 -0
- package/mcp/ui/App/components/OAuthConnect.tsx +270 -0
- package/mcp/ui/App/lib/callTool.ts +130 -0
- package/mcp/ui/App/lib/debugLog.ts +41 -0
- package/mcp/ui/App/lib/developerPortal.ts +111 -0
- package/mcp/ui/App/main.css +6 -0
- package/mcp/ui/App/root.tsx +13 -0
- package/mcp/ui/dist/index.html +53 -0
- package/mcp/ui/index.html +13 -0
- package/mcp/ui/package-lock.json +6356 -0
- package/mcp/ui/package.json +25 -0
- package/mcp/ui/tsconfig.json +26 -0
- package/mcp/ui/vite.config.ts +16 -0
- package/models/llmSessionModel.js +14 -0
- package/models/userModel.js +3 -0
- package/package.json +2 -2
- package/releaseNotes.json +24 -0
- package/test/handlers/auth.test.js +31 -0
- package/test/handlers/plugin.test.js +287 -0
- package/test/lib/util.test.js +379 -1
- package/test/mcp/tools/createCallLog.test.js +3 -3
- package/test/mcp/tools/doAuth.test.js +40 -303
- package/test/mcp/tools/findContactByName.test.js +3 -3
- package/test/mcp/tools/findContactByPhone.test.js +3 -3
- package/test/mcp/tools/getGoogleFilePicker.test.js +7 -7
- package/test/mcp/tools/getPublicConnectors.test.js +49 -70
- package/test/mcp/tools/logout.test.js +17 -11
- package/mcp/SupportedPlatforms.md +0 -12
- package/mcp/tools/collectAuthInfo.js +0 -91
- package/mcp/tools/setConnector.js +0 -69
- package/test/mcp/tools/collectAuthInfo.test.js +0 -234
- 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
|
|
25
|
-
expect(logout.definition.inputSchema.properties).
|
|
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
|
|
|
@@ -94,8 +94,9 @@ describe('MCP Tool: logout', () => {
|
|
|
94
94
|
});
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
-
test('should
|
|
97
|
+
test('should treat CRM unAuthorize failure as non-fatal and still succeed', async () => {
|
|
98
98
|
// Arrange
|
|
99
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
99
100
|
const mockUser = {
|
|
100
101
|
id: 'test-user-id',
|
|
101
102
|
platform: 'testCRM'
|
|
@@ -120,10 +121,12 @@ describe('MCP Tool: logout', () => {
|
|
|
120
121
|
jwtToken: 'mock-jwt-token'
|
|
121
122
|
});
|
|
122
123
|
|
|
123
|
-
// Assert
|
|
124
|
-
expect(result.success).toBe(
|
|
125
|
-
expect(result.
|
|
126
|
-
expect(
|
|
124
|
+
// Assert — local session is cleared; CRM revoke errors are logged only
|
|
125
|
+
expect(result.success).toBe(true);
|
|
126
|
+
expect(result.data.message).toContain('IMPORTANT');
|
|
127
|
+
expect(mockConnector.unAuthorize).toHaveBeenCalled();
|
|
128
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
129
|
+
consoleSpy.mockRestore();
|
|
127
130
|
});
|
|
128
131
|
|
|
129
132
|
test('should handle invalid JWT token', async () => {
|
|
@@ -140,8 +143,9 @@ describe('MCP Tool: logout', () => {
|
|
|
140
143
|
expect(result.error).toBeDefined();
|
|
141
144
|
});
|
|
142
145
|
|
|
143
|
-
test('should
|
|
146
|
+
test('should succeed when platform connector is missing (unAuthorize skipped)', async () => {
|
|
144
147
|
// Arrange
|
|
148
|
+
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
145
149
|
const mockUser = {
|
|
146
150
|
id: 'test-user-id',
|
|
147
151
|
platform: 'unknownCRM'
|
|
@@ -160,9 +164,11 @@ describe('MCP Tool: logout', () => {
|
|
|
160
164
|
jwtToken: 'mock-jwt-token'
|
|
161
165
|
});
|
|
162
166
|
|
|
163
|
-
// Assert
|
|
164
|
-
expect(result.success).toBe(
|
|
165
|
-
expect(result.
|
|
167
|
+
// Assert — null connector throws on unAuthorize; error is caught, logout still succeeds locally
|
|
168
|
+
expect(result.success).toBe(true);
|
|
169
|
+
expect(result.data.message).toContain('IMPORTANT');
|
|
170
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
171
|
+
consoleSpy.mockRestore();
|
|
166
172
|
});
|
|
167
173
|
});
|
|
168
174
|
});
|
|
@@ -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
|
-
|