@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 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
  });
@@ -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 log = await CallLogModel.findOne({
10
+ const existingCallLog = await CallLogModel.findOne({
11
11
  where: {
12
12
  sessionId
13
13
  }
14
14
  });
15
- if (!log) {
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: log,
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) => {
@@ -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>, ext. &lt;extension&gt;</div><br>`;
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}**, ext. \\<extension\\>\n\n`;
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
 
@@ -31,6 +31,11 @@ const toolDefinition = {
31
31
  }
32
32
  },
33
33
  required: ['connectorManifest', 'connectorName']
34
+ },
35
+ annotations: {
36
+ readOnlyHint: true,
37
+ openWorldHint: false,
38
+ destructiveHint: false
34
39
  }
35
40
  };
36
41
 
@@ -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
+
@@ -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
- let customState = '';
167
- if (platform.auth.oauth.customState) {
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=${customState === '' ? encodeURIComponent(stateParam) : customState}` +
185
+ `&state=${encodeURIComponent(stateParam)}` +
181
186
  `&redirect_uri=${process.env.APP_SERVER}/oauth-callback`;
182
187
  }
183
188
 
@@ -25,6 +25,11 @@ const toolDefinition = {
25
25
  }
26
26
  },
27
27
  required: ['jwtToken', 'name']
28
+ },
29
+ annotations: {
30
+ readOnlyHint: true,
31
+ openWorldHint: true,
32
+ destructiveHint: false
28
33
  }
29
34
  };
30
35
 
@@ -33,6 +33,11 @@ const toolDefinition = {
33
33
  }
34
34
  },
35
35
  required: ['jwtToken', 'phoneNumber']
36
+ },
37
+ annotations: {
38
+ readOnlyHint: true,
39
+ openWorldHint: true,
40
+ destructiveHint: false
36
41
  }
37
42
  };
38
43
 
@@ -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
+