@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.
- package/handlers/admin.js +91 -1
- package/handlers/auth.js +3 -3
- package/handlers/disposition.js +3 -3
- package/handlers/log.js +2 -1
- package/index.js +67 -0
- package/lib/sharedSMSComposer.js +2 -2
- package/mcp/tools/checkAuthStatus.js +22 -2
- package/mcp/tools/collectAuthInfo.js +5 -0
- package/mcp/tools/createCallLog.js +9 -0
- package/mcp/tools/createContact.js +117 -0
- package/mcp/tools/doAuth.js +15 -10
- package/mcp/tools/findContactByName.js +5 -0
- package/mcp/tools/findContactByPhone.js +5 -0
- package/mcp/tools/getGoogleFilePicker.js +103 -0
- package/mcp/tools/getHelp.js +6 -1
- package/mcp/tools/getPublicConnectors.js +8 -1
- package/mcp/tools/index.js +14 -8
- package/mcp/tools/logout.js +5 -0
- package/mcp/tools/rcGetCallLogs.js +5 -0
- package/mcp/tools/setConnector.js +7 -2
- package/package.json +3 -2
- package/releaseNotes.json +25 -1
- package/test/handlers/log.test.js +6 -3
- package/test/lib/ringcentral.test.js +0 -6
- package/test/lib/sharedSMSComposer.test.js +1 -1
- package/test/mcp/tools/collectAuthInfo.test.js +42 -0
- package/test/mcp/tools/createCallLog.test.js +27 -14
- package/test/mcp/tools/doAuth.test.js +23 -10
- package/test/mcp/tools/getGoogleFilePicker.test.js +281 -0
- package/test/mcp/tools/getPublicConnectors.test.js +8 -8
package/handlers/admin.js
CHANGED
|
@@ -377,6 +377,95 @@ async function getUserMapping({ user, hashedRcAccountId, rcExtensionList }) {
|
|
|
377
377
|
return [];
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
+
async function reinitializeUserMapping({ user, hashedRcAccountId, rcExtensionList }) {
|
|
381
|
+
const platformModule = connectorRegistry.getConnector(user.platform);
|
|
382
|
+
if (!platformModule.getUserList) {
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const proxyId = user.platformAdditionalInfo?.proxyId;
|
|
387
|
+
let proxyConfig = null;
|
|
388
|
+
if (proxyId) {
|
|
389
|
+
proxyConfig = await Connector.getProxyConfig(proxyId);
|
|
390
|
+
if (!proxyConfig?.operations?.getUserList) {
|
|
391
|
+
return [];
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const authType = await platformModule.getAuthType({ proxyId, proxyConfig });
|
|
396
|
+
let authHeader = '';
|
|
397
|
+
switch (authType) {
|
|
398
|
+
case 'oauth':
|
|
399
|
+
const oauthApp = oauth.getOAuthApp((await platformModule.getOauthInfo({ tokenUrl: user?.platformAdditionalInfo?.tokenUrl, hostname: user?.hostname, proxyId, proxyConfig })));
|
|
400
|
+
// eslint-disable-next-line no-param-reassign
|
|
401
|
+
user = await oauth.checkAndRefreshAccessToken(oauthApp, user);
|
|
402
|
+
authHeader = `Bearer ${user.accessToken}`;
|
|
403
|
+
break;
|
|
404
|
+
case 'apiKey':
|
|
405
|
+
const basicAuth = platformModule.getBasicAuth({ apiKey: user.accessToken });
|
|
406
|
+
authHeader = `Basic ${basicAuth}`;
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const crmUserList = await platformModule.getUserList({ user, authHeader, proxyConfig });
|
|
411
|
+
const userMappingResult = [];
|
|
412
|
+
const initialUserMappings = [];
|
|
413
|
+
|
|
414
|
+
// Auto-match CRM users with RC extensions by email or name
|
|
415
|
+
for (const crmUser of crmUserList) {
|
|
416
|
+
const rcExtensionForMapping = rcExtensionList.find(u =>
|
|
417
|
+
u.email === crmUser.email ||
|
|
418
|
+
u.name === crmUser.name ||
|
|
419
|
+
(`${u.firstName} ${u.lastName}` === crmUser.name)
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
if (rcExtensionForMapping) {
|
|
423
|
+
userMappingResult.push({
|
|
424
|
+
crmUser: {
|
|
425
|
+
id: crmUser.id,
|
|
426
|
+
name: crmUser.name ?? '',
|
|
427
|
+
email: crmUser.email ?? '',
|
|
428
|
+
},
|
|
429
|
+
rcUser: [{
|
|
430
|
+
extensionId: rcExtensionForMapping.id,
|
|
431
|
+
name: rcExtensionForMapping.name || `${rcExtensionForMapping.firstName} ${rcExtensionForMapping.lastName}`,
|
|
432
|
+
extensionNumber: rcExtensionForMapping?.extensionNumber ?? '',
|
|
433
|
+
email: rcExtensionForMapping?.email ?? ''
|
|
434
|
+
}]
|
|
435
|
+
});
|
|
436
|
+
initialUserMappings.push({
|
|
437
|
+
crmUserId: crmUser.id.toString(),
|
|
438
|
+
rcExtensionId: [rcExtensionForMapping.id.toString()]
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
userMappingResult.push({
|
|
443
|
+
crmUser: {
|
|
444
|
+
id: crmUser.id,
|
|
445
|
+
name: crmUser.name ?? '',
|
|
446
|
+
email: crmUser.email ?? '',
|
|
447
|
+
},
|
|
448
|
+
rcUser: []
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Overwrite existing mappings with fresh auto-matched mappings
|
|
454
|
+
try {
|
|
455
|
+
await upsertAdminSettings({
|
|
456
|
+
hashedRcAccountId,
|
|
457
|
+
adminSettings: {
|
|
458
|
+
userMappings: initialUserMappings
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
return handleDatabaseError(error, 'Error reinitializing user mapping');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return userMappingResult;
|
|
467
|
+
}
|
|
468
|
+
|
|
380
469
|
exports.validateAdminRole = validateAdminRole;
|
|
381
470
|
exports.upsertAdminSettings = upsertAdminSettings;
|
|
382
471
|
exports.getAdminSettings = getAdminSettings;
|
|
@@ -385,4 +474,5 @@ exports.getServerLoggingSettings = getServerLoggingSettings;
|
|
|
385
474
|
exports.updateServerLoggingSettings = updateServerLoggingSettings;
|
|
386
475
|
exports.getAdminReport = getAdminReport;
|
|
387
476
|
exports.getUserReport = getUserReport;
|
|
388
|
-
exports.getUserMapping = getUserMapping;
|
|
477
|
+
exports.getUserMapping = getUserMapping;
|
|
478
|
+
exports.reinitializeUserMapping = reinitializeUserMapping;
|
package/handlers/auth.js
CHANGED
|
@@ -36,9 +36,9 @@ async function onOAuthCallback({ platform, hostname, tokenUrl, query, isFromMCP
|
|
|
36
36
|
overridingOAuthOption = platformModule.getOverridingOAuthOption({ code });
|
|
37
37
|
}
|
|
38
38
|
const oauthApp = oauth.getOAuthApp(oauthInfo);
|
|
39
|
-
const { accessToken, refreshToken, expires } = await oauthApp.code.getToken(callbackUri, overridingOAuthOption);
|
|
39
|
+
const { accessToken, refreshToken, expires, data } = await oauthApp.code.getToken(callbackUri, overridingOAuthOption);
|
|
40
40
|
const authHeader = `Bearer ${accessToken}`;
|
|
41
|
-
const { successful, platformUserInfo, returnMessage } = await platformModule.getUserInfo({ authHeader, tokenUrl, apiUrl, hostname, platform, username, callbackUri, query, proxyId, proxyConfig, userEmail });
|
|
41
|
+
const { successful, platformUserInfo, returnMessage } = await platformModule.getUserInfo({ authHeader, tokenUrl, apiUrl, hostname, platform, username, callbackUri, query, proxyId, proxyConfig, userEmail, data });
|
|
42
42
|
|
|
43
43
|
if (successful) {
|
|
44
44
|
let userInfo = null;
|
|
@@ -52,7 +52,7 @@ async function onOAuthCallback({ platform, hostname, tokenUrl, query, isFromMCP
|
|
|
52
52
|
hostname: platformUserInfo?.overridingHostname ? platformUserInfo.overridingHostname : hostname,
|
|
53
53
|
accessToken,
|
|
54
54
|
refreshToken,
|
|
55
|
-
tokenExpiry: expires,
|
|
55
|
+
tokenExpiry: isNaN(expires) ? null : expires,
|
|
56
56
|
rcAccountId: query?.rcAccountId,
|
|
57
57
|
proxyId
|
|
58
58
|
});
|
package/handlers/disposition.js
CHANGED
|
@@ -7,12 +7,12 @@ const { handleApiError } = require('../lib/errorHandler');
|
|
|
7
7
|
|
|
8
8
|
async function upsertCallDisposition({ platform, userId, sessionId, dispositions }) {
|
|
9
9
|
try {
|
|
10
|
-
const
|
|
10
|
+
const existingCallLog = await CallLogModel.findOne({
|
|
11
11
|
where: {
|
|
12
12
|
sessionId
|
|
13
13
|
}
|
|
14
14
|
});
|
|
15
|
-
if (!
|
|
15
|
+
if (!existingCallLog) {
|
|
16
16
|
return {
|
|
17
17
|
successful: false,
|
|
18
18
|
returnMessage: {
|
|
@@ -54,7 +54,7 @@ async function upsertCallDisposition({ platform, userId, sessionId, dispositions
|
|
|
54
54
|
}
|
|
55
55
|
const { logId, returnMessage, extraDataTracking } = await platformModule.upsertCallDisposition({
|
|
56
56
|
user,
|
|
57
|
-
existingCallLog
|
|
57
|
+
existingCallLog,
|
|
58
58
|
authHeader,
|
|
59
59
|
dispositions,
|
|
60
60
|
proxyConfig
|
package/handlers/log.js
CHANGED
|
@@ -225,7 +225,7 @@ async function getCallLog({ userId, sessionIds, platform, requireDetails }) {
|
|
|
225
225
|
logs.push({ sessionId: sId, matched: false });
|
|
226
226
|
}
|
|
227
227
|
else {
|
|
228
|
-
const getCallLogResult = await platformModule.getCallLog({ user, callLogId: callLog.thirdPartyLogId, contactId: callLog.contactId, authHeader, proxyConfig });
|
|
228
|
+
const getCallLogResult = await platformModule.getCallLog({ user, telephonySessionId: callLog.id, callLogId: callLog.thirdPartyLogId, contactId: callLog.contactId, authHeader, proxyConfig });
|
|
229
229
|
returnMessage = getCallLogResult.returnMessage;
|
|
230
230
|
extraDataTracking = getCallLogResult.extraDataTracking;
|
|
231
231
|
logs.push({ sessionId: callLog.sessionId, matched: true, logId: callLog.thirdPartyLogId, logData: getCallLogResult.callLogInfo });
|
|
@@ -304,6 +304,7 @@ async function updateCallLog({ platform, userId, incomingData, hashedAccountId,
|
|
|
304
304
|
try {
|
|
305
305
|
const getLogResult = await platformModule.getCallLog({
|
|
306
306
|
user,
|
|
307
|
+
telephonySessionId: existingCallLog.id,
|
|
307
308
|
callLogId: existingCallLog.thirdPartyLogId,
|
|
308
309
|
contactId: existingCallLog.contactId,
|
|
309
310
|
authHeader,
|
package/index.js
CHANGED
|
@@ -482,6 +482,63 @@ function createCoreRouter() {
|
|
|
482
482
|
eventAddedVia
|
|
483
483
|
});
|
|
484
484
|
});
|
|
485
|
+
router.post('/admin/reinitializeUserMapping', async function (req, res) {
|
|
486
|
+
const requestStartTime = new Date().getTime();
|
|
487
|
+
const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
|
|
488
|
+
tracer?.trace('reinitializeUserMapping:start', { body: req.body });
|
|
489
|
+
let platformName = null;
|
|
490
|
+
let success = false;
|
|
491
|
+
const { hashedExtensionId, hashedAccountId, userAgent, ip, author, eventAddedVia } = getAnalyticsVariablesInReqHeaders({ headers: req.headers })
|
|
492
|
+
try {
|
|
493
|
+
const jwtToken = req.query.jwtToken;
|
|
494
|
+
if (jwtToken) {
|
|
495
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
496
|
+
platformName = unAuthData?.platform ?? 'Unknown';
|
|
497
|
+
const user = await UserModel.findByPk(unAuthData?.id);
|
|
498
|
+
if (!user) {
|
|
499
|
+
tracer?.trace('reinitializeUserMapping:userNotFound', {});
|
|
500
|
+
res.status(400).send(tracer ? tracer.wrapResponse('User not found') : 'User not found');
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const { isValidated, rcAccountId } = await adminCore.validateAdminRole({ rcAccessToken: req.query.rcAccessToken });
|
|
504
|
+
const hashedRcAccountId = util.getHashValue(rcAccountId, process.env.HASH_KEY);
|
|
505
|
+
if (isValidated) {
|
|
506
|
+
const userMapping = await adminCore.reinitializeUserMapping({ user, hashedRcAccountId, rcExtensionList: req.body.rcExtensionList });
|
|
507
|
+
res.status(200).send(tracer ? tracer.wrapResponse(userMapping) : userMapping);
|
|
508
|
+
success = true;
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
tracer?.trace('reinitializeUserMapping:adminValidationFailed', {});
|
|
512
|
+
res.status(401).send(tracer ? tracer.wrapResponse('Admin validation failed') : 'Admin validation failed');
|
|
513
|
+
success = true;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
tracer?.trace('reinitializeUserMapping:noToken', {});
|
|
518
|
+
res.status(400).send(tracer ? tracer.wrapResponse('Please go to Settings and authorize CRM platform') : 'Please go to Settings and authorize CRM platform');
|
|
519
|
+
success = false;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
catch (e) {
|
|
523
|
+
logger.error('Reinitialize user mapping failed', { stack: e.stack });
|
|
524
|
+
tracer?.traceError('reinitializeUserMapping:error', e);
|
|
525
|
+
res.status(400).send(tracer ? tracer.wrapResponse({ error: e.message || e }) : { error: e.message || e });
|
|
526
|
+
}
|
|
527
|
+
const requestEndTime = new Date().getTime();
|
|
528
|
+
analytics.track({
|
|
529
|
+
eventName: 'Reinitialize user mapping',
|
|
530
|
+
interfaceName: 'reinitializeUserMapping',
|
|
531
|
+
connectorName: platformName,
|
|
532
|
+
accountId: hashedAccountId,
|
|
533
|
+
extensionId: hashedExtensionId,
|
|
534
|
+
success,
|
|
535
|
+
requestDuration: (requestEndTime - requestStartTime) / 1000,
|
|
536
|
+
userAgent,
|
|
537
|
+
ip,
|
|
538
|
+
author,
|
|
539
|
+
eventAddedVia
|
|
540
|
+
});
|
|
541
|
+
});
|
|
485
542
|
router.get('/admin/serverLoggingSettings', async function (req, res) {
|
|
486
543
|
const requestStartTime = new Date().getTime();
|
|
487
544
|
const tracer = req.headers['is-debug'] === 'true' ? DebugTracer.fromRequest(req) : null;
|
|
@@ -767,6 +824,11 @@ function createCoreRouter() {
|
|
|
767
824
|
const state = new URL(req.query.callbackUri).searchParams.get('state') ?? req.query.state;
|
|
768
825
|
const stateParams = new URLSearchParams(state ? decodeURIComponent(state) : '');
|
|
769
826
|
platformName = stateParams.get('platform');
|
|
827
|
+
// backward compatibility
|
|
828
|
+
if(!platformName)
|
|
829
|
+
{
|
|
830
|
+
platformName = req.query.callbackUri?.split('platform=')[1] ?? state.split('platform=')[1];
|
|
831
|
+
}
|
|
770
832
|
// Extract mcp auth sessionId if present
|
|
771
833
|
sessionId = stateParams?.get('sessionId');
|
|
772
834
|
const isFromMCP = !!sessionId;
|
|
@@ -1878,6 +1940,11 @@ function createCoreRouter() {
|
|
|
1878
1940
|
}
|
|
1879
1941
|
});
|
|
1880
1942
|
}
|
|
1943
|
+
// For chatGPT verification
|
|
1944
|
+
router.get('/.well-known/openai-apps-challenge', (req, res) => {
|
|
1945
|
+
res.send(process.env.CHATGPT_VERIFICATION_CODE);
|
|
1946
|
+
});
|
|
1947
|
+
|
|
1881
1948
|
// --- METADATA ENDPOINT 1: Resource Metadata ---
|
|
1882
1949
|
// Tells the client "I am protected" and "Here is who protects me"
|
|
1883
1950
|
router.get('/.well-known/oauth-protected-resource', (req, res) => {
|
package/lib/sharedSMSComposer.js
CHANGED
|
@@ -373,7 +373,7 @@ function composeHTMLBody({ conversationCreatedDate, conversationUpdatedDate, con
|
|
|
373
373
|
// Owner/Call queue info
|
|
374
374
|
if (ownerInfo) {
|
|
375
375
|
if (ownerInfo.type === 'callQueue') {
|
|
376
|
-
body += `<div>Receiving call queue: <b>${escapeHtml(ownerInfo.name)}</b
|
|
376
|
+
body += `<div>Receiving call queue: <b>${escapeHtml(ownerInfo.name)}</b></div><br>`;
|
|
377
377
|
} else {
|
|
378
378
|
body += `<div>Owner: <b>${escapeHtml(ownerInfo.name)}</b></div><br>`;
|
|
379
379
|
}
|
|
@@ -422,7 +422,7 @@ function composeMarkdownBody({ conversationCreatedDate, conversationUpdatedDate,
|
|
|
422
422
|
// Owner/Call queue info
|
|
423
423
|
if (ownerInfo) {
|
|
424
424
|
if (ownerInfo.type === 'callQueue') {
|
|
425
|
-
body += `Receiving call queue: **${ownerInfo.name}
|
|
425
|
+
body += `Receiving call queue: **${ownerInfo.name}**\n\n`;
|
|
426
426
|
} else {
|
|
427
427
|
body += `Owner: **${ownerInfo.name}**\n\n`;
|
|
428
428
|
}
|
|
@@ -15,9 +15,18 @@ const toolDefinition = {
|
|
|
15
15
|
sessionId: {
|
|
16
16
|
type: 'string',
|
|
17
17
|
description: 'The session ID returned from doAuth tool'
|
|
18
|
+
},
|
|
19
|
+
platform: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'The platform to check the status of'
|
|
18
22
|
}
|
|
19
23
|
},
|
|
20
24
|
required: ['sessionId']
|
|
25
|
+
},
|
|
26
|
+
annotations: {
|
|
27
|
+
readOnlyHint: true,
|
|
28
|
+
openWorldHint: false,
|
|
29
|
+
destructiveHint: false
|
|
21
30
|
}
|
|
22
31
|
};
|
|
23
32
|
|
|
@@ -29,7 +38,7 @@ const toolDefinition = {
|
|
|
29
38
|
*/
|
|
30
39
|
async function execute(args) {
|
|
31
40
|
try {
|
|
32
|
-
const { sessionId } = args;
|
|
41
|
+
const { sessionId, platform } = args;
|
|
33
42
|
|
|
34
43
|
const session = await getAuthSession(sessionId);
|
|
35
44
|
|
|
@@ -45,13 +54,24 @@ async function execute(args) {
|
|
|
45
54
|
|
|
46
55
|
switch (session.status) {
|
|
47
56
|
case 'completed':
|
|
57
|
+
if(platform === 'googleSheets') {
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
data: {
|
|
61
|
+
status: 'completed',
|
|
62
|
+
jwtToken: session.jwtToken,
|
|
63
|
+
userInfo: session.userInfo,
|
|
64
|
+
message: 'IMPORTANT: Authentication successful! Keep jwtToken in memory for future use. DO NOT directly show it to user. Next step is to call getGoogleFilePicker tool.'
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
48
68
|
return {
|
|
49
69
|
success: true,
|
|
50
70
|
data: {
|
|
51
71
|
status: 'completed',
|
|
52
72
|
jwtToken: session.jwtToken,
|
|
53
73
|
userInfo: session.userInfo,
|
|
54
|
-
message: 'IMPORTANT: Authentication successful! Keep jwtToken in memory for future use.'
|
|
74
|
+
message: 'IMPORTANT: Authentication successful! Keep jwtToken in memory for future use. DO NOT directly show it to user.'
|
|
55
75
|
}
|
|
56
76
|
};
|
|
57
77
|
|
|
@@ -163,6 +163,11 @@ const toolDefinition = {
|
|
|
163
163
|
}
|
|
164
164
|
},
|
|
165
165
|
required: ['jwtToken', 'incomingData']
|
|
166
|
+
},
|
|
167
|
+
annotations: {
|
|
168
|
+
readOnlyHint: false,
|
|
169
|
+
openWorldHint: true,
|
|
170
|
+
destructiveHint: false
|
|
166
171
|
}
|
|
167
172
|
};
|
|
168
173
|
|
|
@@ -255,10 +260,14 @@ async function execute(args) {
|
|
|
255
260
|
else {
|
|
256
261
|
return {
|
|
257
262
|
success: false,
|
|
263
|
+
message: 'Cannot find the contact. Please create the contact first.',
|
|
258
264
|
error: 'Failed to get contact with number ' + contactNumber
|
|
259
265
|
}
|
|
260
266
|
}
|
|
261
267
|
}
|
|
268
|
+
else {
|
|
269
|
+
incomingData.contactId = contactId;
|
|
270
|
+
}
|
|
262
271
|
|
|
263
272
|
// Call the createCallLog method
|
|
264
273
|
const { successful, logId, returnMessage } = await logCore.createCallLog({
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const jwt = require('../../lib/jwt');
|
|
2
|
+
const connectorRegistry = require('../../connector/registry');
|
|
3
|
+
const contactCore = require('../../handlers/contact');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MCP Tool: Create Contact
|
|
7
|
+
*
|
|
8
|
+
* This tool creates a new contact in the CRM platform.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const toolDefinition = {
|
|
12
|
+
name: 'createContact',
|
|
13
|
+
description: '⚠️ REQUIRES AUTHENTICATION: User must first authenticate using the "auth" tool to obtain a JWT token before using this tool. | Create a new contact in the CRM platform. Returns the created contact information if successful.',
|
|
14
|
+
inputSchema: {
|
|
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
|
+
phoneNumber: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Phone number of the new contact (MUST BE in E.164 format, e.g., +14155551234)'
|
|
24
|
+
},
|
|
25
|
+
newContactName: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Full name of the new contact. If not provided, use phone number as the name'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
required: ['jwtToken', 'phoneNumber']
|
|
31
|
+
},
|
|
32
|
+
annotations: {
|
|
33
|
+
readOnlyHint: false,
|
|
34
|
+
openWorldHint: true,
|
|
35
|
+
destructiveHint: false
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Execute the createContact tool
|
|
41
|
+
* @param {Object} args - The tool arguments
|
|
42
|
+
* @param {string} args.jwtToken - JWT token with user and platform info
|
|
43
|
+
* @param {string} args.phoneNumber - Phone number of the new contact
|
|
44
|
+
* @param {string} args.newContactName - Name of the new contact
|
|
45
|
+
* @returns {Object} Result object with created contact information
|
|
46
|
+
*/
|
|
47
|
+
async function execute(args) {
|
|
48
|
+
try {
|
|
49
|
+
const { jwtToken, phoneNumber, newContactName } = args;
|
|
50
|
+
|
|
51
|
+
if (!jwtToken) {
|
|
52
|
+
throw new Error('Please go to Settings and authorize CRM platform');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!phoneNumber) {
|
|
56
|
+
throw new Error('Phone number is required');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!newContactName) {
|
|
60
|
+
throw new Error('Contact name is required');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Decode JWT to get userId and platform
|
|
64
|
+
const { id: userId, platform } = jwt.decodeJwt(jwtToken);
|
|
65
|
+
|
|
66
|
+
if (!userId) {
|
|
67
|
+
throw new Error('Invalid JWT token: userId not found');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get the platform connector module
|
|
71
|
+
const platformModule = connectorRegistry.getConnector(platform);
|
|
72
|
+
|
|
73
|
+
if (!platformModule) {
|
|
74
|
+
throw new Error(`Platform connector not found for: ${platform}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if createContact is implemented
|
|
78
|
+
if (!platformModule.createContact) {
|
|
79
|
+
throw new Error(`createContact is not implemented for platform: ${platform}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Call the createContact method
|
|
83
|
+
const { successful, returnMessage, contact } = await contactCore.createContact({
|
|
84
|
+
platform,
|
|
85
|
+
userId,
|
|
86
|
+
phoneNumber,
|
|
87
|
+
newContactName
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
if (successful) {
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
data: {
|
|
94
|
+
contact,
|
|
95
|
+
message: returnMessage?.message || 'Contact created successfully'
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
error: returnMessage?.message || 'Failed to create contact'
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
error: error.message || 'Unknown error occurred',
|
|
110
|
+
errorDetails: error.stack
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
exports.definition = toolDefinition;
|
|
116
|
+
exports.execute = execute;
|
|
117
|
+
|
package/mcp/tools/doAuth.js
CHANGED
|
@@ -55,6 +55,11 @@ const toolDefinition = {
|
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
57
|
required: ['connectorManifest', 'connectorName', 'hostname']
|
|
58
|
+
},
|
|
59
|
+
annotations: {
|
|
60
|
+
readOnlyHint: false,
|
|
61
|
+
openWorldHint: true,
|
|
62
|
+
destructiveHint: true
|
|
58
63
|
}
|
|
59
64
|
};
|
|
60
65
|
|
|
@@ -93,7 +98,7 @@ async function execute(args) {
|
|
|
93
98
|
success: true,
|
|
94
99
|
data: {
|
|
95
100
|
jwtToken,
|
|
96
|
-
message: "IMPORTANT: Authentication successful. Keep jwtToken in memory for future use.",
|
|
101
|
+
message: "IMPORTANT: Authentication successful. Keep jwtToken in memory for future use. DO NOT directly show it to user.",
|
|
97
102
|
}
|
|
98
103
|
}
|
|
99
104
|
}
|
|
@@ -119,7 +124,7 @@ async function execute(args) {
|
|
|
119
124
|
success: true,
|
|
120
125
|
data: {
|
|
121
126
|
jwtToken,
|
|
122
|
-
message: "IMPORTANT: Authentication successful. Keep jwtToken in memory for future use.",
|
|
127
|
+
message: "IMPORTANT: Authentication successful. Keep jwtToken in memory for future use. DO NOT directly show it to user.",
|
|
123
128
|
}
|
|
124
129
|
}
|
|
125
130
|
}
|
|
@@ -163,21 +168,21 @@ async function execute(args) {
|
|
|
163
168
|
}
|
|
164
169
|
|
|
165
170
|
function composeAuthUri({ platform, sessionId, hostname }) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
customState = platform.auth.oauth.customState;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Include sessionId in state if provided
|
|
172
|
-
const stateParam = sessionId ?
|
|
171
|
+
// Build base state param
|
|
172
|
+
let stateParam = sessionId ?
|
|
173
173
|
`sessionId=${sessionId}&platform=${platform.name}&hostname=${hostname}` :
|
|
174
174
|
`platform=${platform.name}&hostname=${hostname}`;
|
|
175
175
|
|
|
176
|
+
// Merge customState if provided
|
|
177
|
+
if (platform.auth.oauth.customState) {
|
|
178
|
+
stateParam += `&${platform.auth.oauth.customState}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
176
181
|
return `${platform.auth.oauth.authUrl}?` +
|
|
177
182
|
`response_type=code` +
|
|
178
183
|
`&client_id=${platform.auth.oauth.clientId}` +
|
|
179
184
|
`${!!platform.auth.oauth.scope && platform.auth.oauth.scope != '' ? `&${platform.auth.oauth.scope}` : ''}` +
|
|
180
|
-
`&state=${
|
|
185
|
+
`&state=${encodeURIComponent(stateParam)}` +
|
|
181
186
|
`&redirect_uri=${process.env.APP_SERVER}/oauth-callback`;
|
|
182
187
|
}
|
|
183
188
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const jwt = require('../../lib/jwt');
|
|
2
|
+
const { UserModel } = require('../../models/userModel');
|
|
3
|
+
const axios = require('axios');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* MCP Tool: Get Google File Picker
|
|
7
|
+
*
|
|
8
|
+
* Returns the URL for the Google Sheets file picker.
|
|
9
|
+
* The user must visit this URL in a browser to select a Google Sheet.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const toolDefinition = {
|
|
13
|
+
name: 'getGoogleFilePicker',
|
|
14
|
+
description: '⚠️ REQUIRES AUTHENTICATION: User must first authenticate with googleSheets platform. | 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
|
+
inputSchema: {
|
|
16
|
+
type: 'object',
|
|
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
|
+
sheetName: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'OPTIONAL. Name of the new sheet to create.'
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
required: ['jwtToken']
|
|
28
|
+
},
|
|
29
|
+
annotations: {
|
|
30
|
+
readOnlyHint: false,
|
|
31
|
+
openWorldHint: true,
|
|
32
|
+
destructiveHint: false
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Execute the getGoogleFilePicker tool
|
|
38
|
+
* @param {Object} args - The tool arguments
|
|
39
|
+
* @param {string} args.jwtToken - JWT token containing userId
|
|
40
|
+
* @param {string} args.sheetName - Name of the new sheet to create
|
|
41
|
+
* @returns {Object} Result object with file picker URL
|
|
42
|
+
*/
|
|
43
|
+
async function execute(args) {
|
|
44
|
+
try {
|
|
45
|
+
const { jwtToken, sheetName } = args;
|
|
46
|
+
|
|
47
|
+
if (!jwtToken) {
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
error: 'JWT token is required. Please authenticate with googleSheets platform first using the doAuth tool.'
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Decode JWT to get userId
|
|
55
|
+
const unAuthData = jwt.decodeJwt(jwtToken);
|
|
56
|
+
|
|
57
|
+
if (!unAuthData?.id) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: 'Invalid JWT token: userId not found'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Find the user
|
|
65
|
+
const user = await UserModel.findByPk(unAuthData.id);
|
|
66
|
+
|
|
67
|
+
if (!user) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
error: 'User not found. Please authenticate with googleSheets platform first.'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if (sheetName) {
|
|
76
|
+
const createSheetResponse = await axios.post(`${process.env.APP_SERVER}/googleSheets/sheet?jwtToken=${jwtToken}`, { name: sheetName });
|
|
77
|
+
return createSheetResponse.data;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Generate the file picker URL
|
|
81
|
+
const filePickerUrl = `${process.env.APP_SERVER}/googleSheets/filePicker?token=${jwtToken}}`;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
success: true,
|
|
85
|
+
data: {
|
|
86
|
+
filePickerUrl,
|
|
87
|
+
message: 'Please open this URL in a browser to select a Google Sheet. After selecting a sheet, it will be configured for logging your calls and messages.'
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: error.message || 'Unknown error occurred',
|
|
96
|
+
errorDetails: error.stack
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
exports.definition = toolDefinition;
|
|
102
|
+
exports.execute = execute;
|
|
103
|
+
|