@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.
- package/connector/proxy/index.js +2 -1
- package/handlers/log.js +181 -10
- package/handlers/plugin.js +27 -0
- package/handlers/user.js +31 -2
- package/index.js +99 -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 +392 -0
- package/mcp/mcpHandler.js +293 -82
- package/mcp/tools/checkAuthStatus.js +27 -34
- 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 +41 -28
- package/mcp/tools/index.js +11 -36
- package/mcp/tools/logout.js +5 -10
- package/mcp/tools/rcGetCallLogs.js +3 -20
- package/mcp/ui/App/App.tsx +361 -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/package.json +2 -2
- package/releaseNotes.json +13 -1
- 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 +2 -2
- 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
package/mcp/tools/doAuth.js
CHANGED
|
@@ -1,190 +1,60 @@
|
|
|
1
|
-
const authCore = require('../../handlers/auth');
|
|
2
|
-
const jwt = require('../../lib/jwt');
|
|
3
|
-
const crypto = require('crypto');
|
|
4
1
|
const { createAuthSession } = require('../../lib/authSession');
|
|
5
|
-
|
|
2
|
+
|
|
6
3
|
/**
|
|
7
|
-
* MCP Tool: Do Authentication
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* MCP Tool: Do Authentication (widget-only)
|
|
5
|
+
*
|
|
6
|
+
* Creates a server-side OAuth session for the given sessionId.
|
|
7
|
+
* The widget generates the sessionId and authUri client-side for instant display,
|
|
8
|
+
* then calls this endpoint in the background to register the session in the DB
|
|
9
|
+
* so the OAuth callback can resolve it.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const toolDefinition = {
|
|
13
13
|
name: 'doAuth',
|
|
14
|
-
description: '
|
|
14
|
+
description: 'Create a server-side OAuth session. Widget-only — not called by AI model.',
|
|
15
15
|
inputSchema: {
|
|
16
16
|
type: 'object',
|
|
17
17
|
properties: {
|
|
18
|
-
connectorManifest: {
|
|
19
|
-
type: 'object',
|
|
20
|
-
description: 'connectorManifest variable from above conversation. Must be the full manifest object, not just serverUrl'
|
|
21
|
-
},
|
|
22
18
|
connectorName: {
|
|
23
19
|
type: 'string',
|
|
24
|
-
description: '
|
|
20
|
+
description: 'Connector platform name'
|
|
25
21
|
},
|
|
26
22
|
hostname: {
|
|
27
23
|
type: 'string',
|
|
28
|
-
description: '
|
|
29
|
-
},
|
|
30
|
-
apiKey: {
|
|
31
|
-
type: 'string',
|
|
32
|
-
description: 'API key to authenticate to.'
|
|
33
|
-
},
|
|
34
|
-
additionalInfo: {
|
|
35
|
-
type: 'object',
|
|
36
|
-
description: 'Additional information to authenticate to.',
|
|
37
|
-
properties: {
|
|
38
|
-
username: {
|
|
39
|
-
type: 'string',
|
|
40
|
-
description: 'Username to authenticate to.'
|
|
41
|
-
},
|
|
42
|
-
password: {
|
|
43
|
-
type: 'string',
|
|
44
|
-
description: 'Password to authenticate to.'
|
|
45
|
-
},
|
|
46
|
-
apiUrl: {
|
|
47
|
-
type: 'string',
|
|
48
|
-
description: 'API URL to authenticate to.'
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
callbackUri: {
|
|
53
|
-
type: 'string',
|
|
54
|
-
description: 'Callback URI to authenticate to.'
|
|
24
|
+
description: 'Resolved hostname for the CRM instance'
|
|
55
25
|
}
|
|
56
26
|
},
|
|
57
|
-
required: ['
|
|
27
|
+
required: ['connectorName']
|
|
58
28
|
},
|
|
59
29
|
annotations: {
|
|
60
30
|
readOnlyHint: false,
|
|
61
|
-
openWorldHint:
|
|
62
|
-
destructiveHint:
|
|
31
|
+
openWorldHint: false,
|
|
32
|
+
destructiveHint: false
|
|
63
33
|
}
|
|
64
34
|
};
|
|
65
35
|
|
|
66
|
-
/**
|
|
67
|
-
* Execute the doAuth tool
|
|
68
|
-
* @param {Object} args - The tool arguments
|
|
69
|
-
* @param {string} args.connectorManifest - Connector manifest from conversation or memory.
|
|
70
|
-
* @param {string} args.connectorName - Connector name from conversation or memory.
|
|
71
|
-
* @param {string} args.hostname - Hostname to authenticate to.
|
|
72
|
-
* @param {string} args.apiKey - API key to authenticate to.
|
|
73
|
-
* @param {Object} args.additionalInfo - Additional information to authenticate to.
|
|
74
|
-
* @param {string} args.callbackUri - Callback URI to authenticate to.
|
|
75
|
-
* @returns {Object} Result object with authentication information
|
|
76
|
-
*/
|
|
77
36
|
async function execute(args) {
|
|
78
37
|
try {
|
|
79
|
-
const {
|
|
80
|
-
|
|
81
|
-
if (!
|
|
82
|
-
return {
|
|
83
|
-
success: false,
|
|
84
|
-
error: "Invalid connector manifest",
|
|
85
|
-
errorDetails: errors.join(', '),
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
const platform = connectorManifest.platforms[connectorName];
|
|
89
|
-
switch (platform.auth.type) {
|
|
90
|
-
case 'apiKey':
|
|
91
|
-
const { userInfo } = await authCore.onApiKeyLogin({ platform: platform.name, hostname, apiKey, additionalInfo });
|
|
92
|
-
if (userInfo) {
|
|
93
|
-
const jwtToken = jwt.generateJwt({
|
|
94
|
-
id: userInfo.id.toString(),
|
|
95
|
-
platform: platform.name
|
|
96
|
-
});
|
|
97
|
-
return {
|
|
98
|
-
success: true,
|
|
99
|
-
data: {
|
|
100
|
-
jwtToken,
|
|
101
|
-
message: "IMPORTANT: Authentication successful. Keep jwtToken in memory for future use. DO NOT directly show it to user.",
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
return {
|
|
107
|
-
success: false,
|
|
108
|
-
error: "Authentication failed",
|
|
109
|
-
errorDetails: "User info not found",
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
case 'oauth':
|
|
113
|
-
const hasValidCallbackUri = callbackUri && callbackUri.includes('code=') && callbackUri.includes('state=');
|
|
114
|
-
if (hasValidCallbackUri) {
|
|
115
|
-
const query = Object.fromEntries(new URL(callbackUri).searchParams);
|
|
116
|
-
query.hostname = hostname;
|
|
117
|
-
const { userInfo } = await authCore.onOAuthCallback({ platform: platform.name, hostname, callbackUri, query });
|
|
118
|
-
if (userInfo) {
|
|
119
|
-
const jwtToken = jwt.generateJwt({
|
|
120
|
-
id: userInfo.id.toString(),
|
|
121
|
-
platform: platform.name
|
|
122
|
-
});
|
|
123
|
-
return {
|
|
124
|
-
success: true,
|
|
125
|
-
data: {
|
|
126
|
-
jwtToken,
|
|
127
|
-
message: "IMPORTANT: Authentication successful. Keep jwtToken in memory for future use. DO NOT directly show it to user.",
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
return {
|
|
133
|
-
success: false,
|
|
134
|
-
error: "Authentication failed",
|
|
135
|
-
errorDetails: "User info not found",
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
// Generate unique session ID
|
|
141
|
-
const sessionId = crypto.randomUUID();
|
|
142
|
-
|
|
143
|
-
// Store session
|
|
144
|
-
await createAuthSession(sessionId, {
|
|
145
|
-
platform: platform.name,
|
|
146
|
-
hostname,
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const authUri = composeAuthUri({ platform, sessionId, hostname });
|
|
150
|
-
return {
|
|
151
|
-
success: true,
|
|
152
|
-
data: {
|
|
153
|
-
authUri,
|
|
154
|
-
sessionId,
|
|
155
|
-
message: "IMPORTANT: Show this uri as a clickable link for user to authorize. After user authorizes, use checkAuthStatus tool with this sessionId to get the jwtToken.",
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
38
|
+
const { sessionId, connectorName, hostname = '' } = args;
|
|
39
|
+
|
|
40
|
+
if (!sessionId || !connectorName) {
|
|
41
|
+
return { success: false, error: 'Missing required fields: sessionId, connectorName' };
|
|
159
42
|
}
|
|
160
|
-
|
|
161
|
-
|
|
43
|
+
|
|
44
|
+
await createAuthSession(sessionId, {
|
|
45
|
+
platform: connectorName,
|
|
46
|
+
hostname,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return { success: true };
|
|
50
|
+
} catch (error) {
|
|
162
51
|
return {
|
|
163
52
|
success: false,
|
|
164
53
|
error: error.message || 'Unknown error occurred',
|
|
165
|
-
errorDetails: error.stack
|
|
54
|
+
errorDetails: error.stack,
|
|
166
55
|
};
|
|
167
56
|
}
|
|
168
57
|
}
|
|
169
58
|
|
|
170
|
-
function composeAuthUri({ platform, sessionId, hostname }) {
|
|
171
|
-
// Build base state param
|
|
172
|
-
let stateParam = sessionId ?
|
|
173
|
-
`sessionId=${sessionId}&platform=${platform.name}&hostname=${hostname}` :
|
|
174
|
-
`platform=${platform.name}&hostname=${hostname}`;
|
|
175
|
-
|
|
176
|
-
// Merge customState if provided
|
|
177
|
-
if (platform.auth.oauth.customState) {
|
|
178
|
-
stateParam += `&${platform.auth.oauth.customState}`;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return `${platform.auth.oauth.authUrl}?` +
|
|
182
|
-
`response_type=code` +
|
|
183
|
-
`&client_id=${platform.auth.oauth.clientId}` +
|
|
184
|
-
`${!!platform.auth.oauth.scope && platform.auth.oauth.scope != '' ? `&${platform.auth.oauth.scope}` : ''}` +
|
|
185
|
-
`&state=${encodeURIComponent(stateParam)}` +
|
|
186
|
-
`&redirect_uri=${process.env.APP_SERVER}/oauth-callback`;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
59
|
exports.definition = toolDefinition;
|
|
190
|
-
exports.execute = execute;
|
|
60
|
+
exports.execute = execute;
|
|
@@ -11,20 +11,16 @@ const contactCore = require('../../handlers/contact');
|
|
|
11
11
|
|
|
12
12
|
const toolDefinition = {
|
|
13
13
|
name: 'findContactByName',
|
|
14
|
-
description: '⚠️ REQUIRES
|
|
14
|
+
description: '⚠️ REQUIRES CRM CONNECTION. | Search for a contact in the CRM platform by name. Returns contact details if found.',
|
|
15
15
|
inputSchema: {
|
|
16
16
|
type: 'object',
|
|
17
17
|
properties: {
|
|
18
|
-
jwtToken: {
|
|
19
|
-
type: 'string',
|
|
20
|
-
description: 'JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.'
|
|
21
|
-
},
|
|
22
18
|
name: {
|
|
23
19
|
type: 'string',
|
|
24
20
|
description: 'Name to search for'
|
|
25
21
|
}
|
|
26
22
|
},
|
|
27
|
-
required: ['
|
|
23
|
+
required: ['name']
|
|
28
24
|
},
|
|
29
25
|
annotations: {
|
|
30
26
|
readOnlyHint: true,
|
|
@@ -36,14 +32,15 @@ const toolDefinition = {
|
|
|
36
32
|
/**
|
|
37
33
|
* Execute the findContactByName tool
|
|
38
34
|
* @param {Object} args - The tool arguments
|
|
39
|
-
* @param {string} args.jwtToken - JWT token with user and platform info
|
|
40
35
|
* @param {string} args.name - Name to search for
|
|
41
36
|
* @returns {Object} Result object with contact information
|
|
42
37
|
*/
|
|
43
38
|
async function execute(args) {
|
|
44
39
|
try {
|
|
45
|
-
const {
|
|
46
|
-
|
|
40
|
+
const { name, jwtToken } = args;
|
|
41
|
+
if (!jwtToken) {
|
|
42
|
+
throw new Error('Not authenticated. Please connect to your CRM first.');
|
|
43
|
+
}
|
|
47
44
|
// Decode JWT to get userId and platform
|
|
48
45
|
const { id: userId, platform } = jwt.decodeJwt(jwtToken);
|
|
49
46
|
|
|
@@ -11,14 +11,10 @@ const contactCore = require('../../handlers/contact');
|
|
|
11
11
|
|
|
12
12
|
const toolDefinition = {
|
|
13
13
|
name: 'findContactByPhone',
|
|
14
|
-
description: '⚠️ REQUIRES
|
|
14
|
+
description: '⚠️ REQUIRES CRM CONNECTION. | Search for a contact in the CRM platform by phone number. Returns contact details if found.',
|
|
15
15
|
inputSchema: {
|
|
16
16
|
type: 'object',
|
|
17
17
|
properties: {
|
|
18
|
-
jwtToken: {
|
|
19
|
-
type: 'string',
|
|
20
|
-
description: 'JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.'
|
|
21
|
-
},
|
|
22
18
|
phoneNumber: {
|
|
23
19
|
type: 'string',
|
|
24
20
|
description: 'Phone number to search for, if not in E.164 format, convert it to E.164 format'
|
|
@@ -32,7 +28,7 @@ const toolDefinition = {
|
|
|
32
28
|
description: 'Whether the request is from an extension'
|
|
33
29
|
}
|
|
34
30
|
},
|
|
35
|
-
required: ['
|
|
31
|
+
required: ['phoneNumber']
|
|
36
32
|
},
|
|
37
33
|
annotations: {
|
|
38
34
|
readOnlyHint: true,
|
|
@@ -11,20 +11,16 @@ const axios = require('axios');
|
|
|
11
11
|
|
|
12
12
|
const toolDefinition = {
|
|
13
13
|
name: 'getGoogleFilePicker',
|
|
14
|
-
description: '⚠️ REQUIRES
|
|
14
|
+
description: '⚠️ REQUIRES CRM CONNECTION. | Returns a URL for the Google Sheets file picker (1st preference) OR create a new sheet with input sheet name (2nd preference). The user should open this URL in their browser to select a Google Sheet for logging.',
|
|
15
15
|
inputSchema: {
|
|
16
16
|
type: 'object',
|
|
17
17
|
properties: {
|
|
18
|
-
jwtToken: {
|
|
19
|
-
type: 'string',
|
|
20
|
-
description: 'JWT token obtained from authentication. If user does not have this, direct them to use the "doAuth" tool first with googleSheets platform.'
|
|
21
|
-
},
|
|
22
18
|
sheetName: {
|
|
23
19
|
type: 'string',
|
|
24
20
|
description: 'OPTIONAL. Name of the new sheet to create.'
|
|
25
21
|
}
|
|
26
22
|
},
|
|
27
|
-
required: [
|
|
23
|
+
required: []
|
|
28
24
|
},
|
|
29
25
|
annotations: {
|
|
30
26
|
readOnlyHint: false,
|
|
@@ -47,7 +43,7 @@ async function execute(args) {
|
|
|
47
43
|
if (!jwtToken) {
|
|
48
44
|
return {
|
|
49
45
|
success: false,
|
|
50
|
-
error: 'JWT token is required. Please
|
|
46
|
+
error: 'JWT token is required. Please connect to the CRM first using getPublicConnectors.'
|
|
51
47
|
};
|
|
52
48
|
}
|
|
53
49
|
|
|
@@ -67,7 +63,7 @@ async function execute(args) {
|
|
|
67
63
|
if (!user) {
|
|
68
64
|
return {
|
|
69
65
|
success: false,
|
|
70
|
-
error: 'User not found. Please
|
|
66
|
+
error: 'User not found. Please connect to the CRM first using getPublicConnectors.'
|
|
71
67
|
};
|
|
72
68
|
}
|
|
73
69
|
|
|
@@ -78,7 +74,7 @@ async function execute(args) {
|
|
|
78
74
|
}
|
|
79
75
|
else {
|
|
80
76
|
// Generate the file picker URL
|
|
81
|
-
const filePickerUrl = `${process.env.APP_SERVER}/googleSheets/filePicker?token=${jwtToken}
|
|
77
|
+
const filePickerUrl = `${process.env.APP_SERVER}/googleSheets/filePicker?token=${jwtToken}`;
|
|
82
78
|
|
|
83
79
|
return {
|
|
84
80
|
success: true,
|
package/mcp/tools/getHelp.js
CHANGED
|
@@ -29,12 +29,11 @@ async function execute() {
|
|
|
29
29
|
success: true,
|
|
30
30
|
data: {
|
|
31
31
|
overview: "I help you connect RingCentral with your CRM to log calls.",
|
|
32
|
-
steps:[
|
|
32
|
+
steps: [
|
|
33
33
|
'1. Tell me to show all connectors and let me connect you to your CRM',
|
|
34
34
|
'2. Follow authorization flow to authorize your CRM',
|
|
35
35
|
'3. Once authorized, I can help find contacts and log your calls'
|
|
36
|
-
]
|
|
37
|
-
supportedCRMs: ["Clio", "Google Sheets"],
|
|
36
|
+
]
|
|
38
37
|
}
|
|
39
38
|
};
|
|
40
39
|
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
const
|
|
1
|
+
const axios = require('axios');
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* MCP Tool: Get Public Connectors
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Triggers the connector selection widget. The widget fetches the connector
|
|
7
|
+
* list and manifests directly from the developer portal on the client side.
|
|
8
|
+
* This tool only needs to resolve the RC account ID (for private connector
|
|
9
|
+
* support) and return the server URL for widget-to-server tool calls.
|
|
7
10
|
*/
|
|
8
11
|
|
|
9
12
|
const toolDefinition = {
|
|
10
13
|
name: 'getPublicConnectors',
|
|
11
|
-
description: '
|
|
14
|
+
description: 'Get available connectors. Returns an interactive widget - do NOT summarize or list the results in text, just show the widget.',
|
|
12
15
|
inputSchema: {
|
|
13
16
|
type: 'object',
|
|
14
17
|
properties: {},
|
|
@@ -16,38 +19,48 @@ const toolDefinition = {
|
|
|
16
19
|
},
|
|
17
20
|
annotations: {
|
|
18
21
|
readOnlyHint: true,
|
|
19
|
-
openWorldHint:
|
|
22
|
+
openWorldHint: true,
|
|
20
23
|
destructiveHint: false
|
|
24
|
+
},
|
|
25
|
+
_meta: {
|
|
26
|
+
// openai/outputTemplate is injected at registration time by mcpHandler.js
|
|
27
|
+
// using the module-level WIDGET_URI — do not hardcode a version here.
|
|
28
|
+
"openai/toolBehavior": 'interactive',
|
|
29
|
+
"openai/widgetAccessible": true
|
|
21
30
|
}
|
|
22
31
|
};
|
|
23
32
|
|
|
24
|
-
const supportedPlatforms = ['googleSheets','clio'];
|
|
25
|
-
|
|
26
33
|
/**
|
|
27
|
-
* Execute the getPublicConnectors tool
|
|
28
|
-
*
|
|
34
|
+
* Execute the getPublicConnectors tool.
|
|
35
|
+
* Uses the RC access token (injected by mcpHandler) to resolve the account ID,
|
|
36
|
+
* which the widget needs to fetch private connectors directly from the developer portal.
|
|
29
37
|
*/
|
|
30
|
-
async function execute() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
async function execute({ rcAccessToken, openaiSessionId } = {}) {
|
|
39
|
+
let rcExtensionId = null;
|
|
40
|
+
let rcAccountId = null;
|
|
41
|
+
|
|
42
|
+
if (rcAccessToken) {
|
|
43
|
+
try {
|
|
44
|
+
const resp = await axios.get(
|
|
45
|
+
'https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~',
|
|
46
|
+
{ headers: { Authorization: `Bearer ${rcAccessToken}` } }
|
|
47
|
+
);
|
|
48
|
+
rcExtensionId = resp.data?.id ?? null;
|
|
49
|
+
rcAccountId = resp.data?.account?.id ?? null;
|
|
50
|
+
} catch {
|
|
51
|
+
// Non-fatal: widget will only show public connectors
|
|
37
52
|
}
|
|
38
|
-
return {
|
|
39
|
-
success: true,
|
|
40
|
-
data: connectorList.filter(c => supportedPlatforms.includes(c.name)).map(c => c.displayName)
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
return {
|
|
45
|
-
success: false,
|
|
46
|
-
error: error.message || 'Unknown error occurred',
|
|
47
|
-
errorDetails: error.stack
|
|
48
|
-
};
|
|
49
53
|
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
structuredContent: {
|
|
57
|
+
serverUrl: process.env.APP_SERVER || 'https://localhost:6066',
|
|
58
|
+
rcExtensionId,
|
|
59
|
+
rcAccountId,
|
|
60
|
+
openaiSessionId: openaiSessionId ?? null,
|
|
61
|
+
}
|
|
62
|
+
};
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
exports.definition = toolDefinition;
|
|
53
|
-
exports.execute = execute;
|
|
66
|
+
exports.execute = execute;
|
package/mcp/tools/index.js
CHANGED
|
@@ -1,64 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP Tools Index
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Two separate registries:
|
|
5
|
+
* - tools: Registered in the MCP server — visible to and callable by the AI model
|
|
6
|
+
* - widgetTools: Only accessible via POST /mcp/widget-tool-call — hidden from the AI model
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
|
-
// const auth = require('./auth');
|
|
8
9
|
const getHelp = require('./getHelp');
|
|
9
10
|
const getPublicConnectors = require('./getPublicConnectors');
|
|
10
|
-
const setConnector = require('./setConnector');
|
|
11
|
-
const collectAuthInfo = require('./collectAuthInfo');
|
|
12
11
|
const doAuth = require('./doAuth');
|
|
13
12
|
const checkAuthStatus = require('./checkAuthStatus');
|
|
14
13
|
const logout = require('./logout');
|
|
15
14
|
const findContact = require('./findContactByPhone');
|
|
16
15
|
const findContactWithName = require('./findContactByName');
|
|
17
|
-
const getCallLog = require('./getCallLog');
|
|
18
16
|
const createCallLog = require('./createCallLog');
|
|
19
|
-
const updateCallLog = require('./updateCallLog');
|
|
20
|
-
const createMessageLog = require('./createMessageLog');
|
|
21
17
|
const rcGetCallLogs = require('./rcGetCallLogs');
|
|
22
18
|
const getGoogleFilePicker = require('./getGoogleFilePicker');
|
|
23
19
|
const createContact = require('./createContact');
|
|
24
20
|
|
|
25
|
-
//
|
|
26
|
-
module.exports =
|
|
21
|
+
// AI-visible MCP tools — registered in the MCP server
|
|
22
|
+
module.exports.tools = [
|
|
27
23
|
getHelp,
|
|
28
24
|
getPublicConnectors,
|
|
29
|
-
setConnector,
|
|
30
|
-
collectAuthInfo,
|
|
31
|
-
doAuth,
|
|
32
|
-
checkAuthStatus,
|
|
33
25
|
logout,
|
|
34
26
|
findContact,
|
|
35
27
|
findContactWithName,
|
|
36
|
-
//getCallLog,
|
|
37
28
|
createCallLog,
|
|
38
|
-
//updateCallLog,
|
|
39
|
-
//createMessageLog,
|
|
40
29
|
rcGetCallLogs,
|
|
41
|
-
getGoogleFilePicker,
|
|
42
|
-
createContact
|
|
43
|
-
|
|
30
|
+
// getGoogleFilePicker,
|
|
31
|
+
createContact,
|
|
32
|
+
];
|
|
44
33
|
|
|
45
|
-
//
|
|
46
|
-
module.exports.
|
|
47
|
-
getHelp,
|
|
48
|
-
getPublicConnectors,
|
|
49
|
-
setConnector,
|
|
50
|
-
collectAuthInfo,
|
|
34
|
+
// Widget-only tools — callable via /mcp/widget-tool-call, NOT registered as MCP tools
|
|
35
|
+
module.exports.widgetTools = [
|
|
51
36
|
doAuth,
|
|
52
37
|
checkAuthStatus,
|
|
53
|
-
logout,
|
|
54
|
-
findContact,
|
|
55
|
-
findContactWithName,
|
|
56
|
-
//getCallLog,
|
|
57
|
-
createCallLog,
|
|
58
|
-
//updateCallLog,
|
|
59
|
-
//createMessageLog,
|
|
60
|
-
rcGetCallLogs,
|
|
61
|
-
getGoogleFilePicker,
|
|
62
|
-
createContact
|
|
63
38
|
];
|
|
64
39
|
|
package/mcp/tools/logout.js
CHANGED
|
@@ -13,16 +13,11 @@ const toolDefinition = {
|
|
|
13
13
|
description: 'Logout the user from the CRM platform.',
|
|
14
14
|
inputSchema: {
|
|
15
15
|
type: 'object',
|
|
16
|
-
properties: {
|
|
17
|
-
jwtToken: {
|
|
18
|
-
type: 'string',
|
|
19
|
-
description: 'JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.'
|
|
20
|
-
}
|
|
21
|
-
}
|
|
16
|
+
properties: {}
|
|
22
17
|
},
|
|
23
18
|
annotations: {
|
|
24
19
|
readOnlyHint: false,
|
|
25
|
-
openWorldHint:
|
|
20
|
+
openWorldHint: true,
|
|
26
21
|
destructiveHint: true
|
|
27
22
|
}
|
|
28
23
|
};
|
|
@@ -30,16 +25,16 @@ const toolDefinition = {
|
|
|
30
25
|
/**
|
|
31
26
|
* Execute the logout tool
|
|
32
27
|
* @param {Object} args - The tool arguments
|
|
33
|
-
* @param {string} args.jwtToken - JWT token containing userId and platform information.
|
|
28
|
+
* @param {string} args.jwtToken - JWT token containing userId and platform information. Injected automatically by the server after CRM connection.
|
|
34
29
|
* @returns {Object} Result object with logout information
|
|
35
30
|
*/
|
|
36
31
|
async function execute(args) {
|
|
37
32
|
try {
|
|
38
33
|
const { jwtToken } = args;
|
|
39
34
|
const { platform, id } = jwt.decodeJwt(jwtToken);
|
|
40
|
-
|
|
35
|
+
|
|
41
36
|
const userToLogout = await UserModel.findByPk(id);
|
|
42
|
-
|
|
37
|
+
if (!userToLogout) {
|
|
43
38
|
return {
|
|
44
39
|
success: false,
|
|
45
40
|
error: "User not found",
|
|
@@ -4,14 +4,10 @@ const { CallLogModel } = require('../../models/callLogModel');
|
|
|
4
4
|
|
|
5
5
|
const toolDefinition = {
|
|
6
6
|
name: 'rcGetCallLogs',
|
|
7
|
-
description: '⚠️ REQUIRES
|
|
7
|
+
description: '⚠️ REQUIRES CRM CONNECTION. | Get call logs from RingCentral. Returns a `records[]` array. Each item in `records` is a complete RingCentral call log object that can be passed DIRECTLY as `incomingData.logInfo` to the `createCallLog` tool — no field renaming or restructuring needed.',
|
|
8
8
|
inputSchema: {
|
|
9
9
|
type: 'object',
|
|
10
10
|
properties: {
|
|
11
|
-
jwtToken: {
|
|
12
|
-
type: 'string',
|
|
13
|
-
description: 'JWT token containing userId and platform information. If user does not have this, direct them to use the "auth" tool first.'
|
|
14
|
-
},
|
|
15
11
|
timeFrom: {
|
|
16
12
|
type: 'string',
|
|
17
13
|
description: 'MUST be ISO string. Default is 24 hours ago.'
|
|
@@ -21,11 +17,11 @@ const toolDefinition = {
|
|
|
21
17
|
description: 'MUST be ISO string. Default is now.'
|
|
22
18
|
}
|
|
23
19
|
},
|
|
24
|
-
required: [
|
|
20
|
+
required: []
|
|
25
21
|
},
|
|
26
22
|
annotations: {
|
|
27
23
|
readOnlyHint: true,
|
|
28
|
-
openWorldHint:
|
|
24
|
+
openWorldHint: true,
|
|
29
25
|
destructiveHint: false
|
|
30
26
|
}
|
|
31
27
|
}
|
|
@@ -51,19 +47,6 @@ async function execute(args) {
|
|
|
51
47
|
timeFrom: timeFrom ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
|
|
52
48
|
timeTo: timeTo ?? new Date().toISOString(),
|
|
53
49
|
});
|
|
54
|
-
// hack: remove already logged calls
|
|
55
|
-
const existingCalls = [];
|
|
56
|
-
for (const call of callLogData.records) {
|
|
57
|
-
const existingCallLog = await CallLogModel.findOne({
|
|
58
|
-
where: {
|
|
59
|
-
sessionId: call.sessionId
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
if (existingCallLog) {
|
|
63
|
-
existingCalls.push(existingCallLog.sessionId);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
callLogData.records = callLogData.records.filter(call => !existingCalls.includes(call.sessionId));
|
|
67
50
|
return callLogData;
|
|
68
51
|
}
|
|
69
52
|
catch (e) {
|