@app-connect/core 1.7.17 → 1.7.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/connector/proxy/index.js +2 -1
  2. package/handlers/log.js +181 -10
  3. package/handlers/plugin.js +27 -0
  4. package/handlers/user.js +31 -2
  5. package/index.js +99 -22
  6. package/lib/authSession.js +21 -12
  7. package/lib/callLogComposer.js +1 -1
  8. package/lib/debugTracer.js +20 -2
  9. package/lib/util.js +21 -4
  10. package/mcp/README.md +392 -0
  11. package/mcp/mcpHandler.js +293 -82
  12. package/mcp/tools/checkAuthStatus.js +27 -34
  13. package/mcp/tools/createCallLog.js +13 -9
  14. package/mcp/tools/createContact.js +2 -6
  15. package/mcp/tools/doAuth.js +27 -157
  16. package/mcp/tools/findContactByName.js +6 -9
  17. package/mcp/tools/findContactByPhone.js +2 -6
  18. package/mcp/tools/getGoogleFilePicker.js +5 -9
  19. package/mcp/tools/getHelp.js +2 -3
  20. package/mcp/tools/getPublicConnectors.js +41 -28
  21. package/mcp/tools/index.js +11 -36
  22. package/mcp/tools/logout.js +5 -10
  23. package/mcp/tools/rcGetCallLogs.js +3 -20
  24. package/mcp/ui/App/App.tsx +361 -0
  25. package/mcp/ui/App/components/AuthInfoForm.tsx +113 -0
  26. package/mcp/ui/App/components/AuthSuccess.tsx +22 -0
  27. package/mcp/ui/App/components/ConnectorList.tsx +82 -0
  28. package/mcp/ui/App/components/DebugPanel.tsx +43 -0
  29. package/mcp/ui/App/components/OAuthConnect.tsx +270 -0
  30. package/mcp/ui/App/lib/callTool.ts +130 -0
  31. package/mcp/ui/App/lib/debugLog.ts +41 -0
  32. package/mcp/ui/App/lib/developerPortal.ts +111 -0
  33. package/mcp/ui/App/main.css +6 -0
  34. package/mcp/ui/App/root.tsx +13 -0
  35. package/mcp/ui/dist/index.html +53 -0
  36. package/mcp/ui/index.html +13 -0
  37. package/mcp/ui/package-lock.json +6356 -0
  38. package/mcp/ui/package.json +25 -0
  39. package/mcp/ui/tsconfig.json +26 -0
  40. package/mcp/ui/vite.config.ts +16 -0
  41. package/models/llmSessionModel.js +14 -0
  42. package/package.json +2 -2
  43. package/releaseNotes.json +13 -1
  44. package/test/handlers/plugin.test.js +287 -0
  45. package/test/lib/util.test.js +379 -1
  46. package/test/mcp/tools/createCallLog.test.js +3 -3
  47. package/test/mcp/tools/doAuth.test.js +40 -303
  48. package/test/mcp/tools/findContactByName.test.js +3 -3
  49. package/test/mcp/tools/findContactByPhone.test.js +3 -3
  50. package/test/mcp/tools/getGoogleFilePicker.test.js +7 -7
  51. package/test/mcp/tools/getPublicConnectors.test.js +49 -70
  52. package/test/mcp/tools/logout.test.js +2 -2
  53. package/mcp/SupportedPlatforms.md +0 -12
  54. package/mcp/tools/collectAuthInfo.js +0 -91
  55. package/mcp/tools/setConnector.js +0 -69
  56. package/test/mcp/tools/collectAuthInfo.test.js +0 -234
  57. package/test/mcp/tools/setConnector.test.js +0 -177
@@ -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
- const { isManifestValid } = require('../lib/validator');
2
+
6
3
  /**
7
- * MCP Tool: Do Authentication
8
- *
9
- * This tool does the authentication.
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: 'Auth flow step.4. Do the authentication. Next step is calling step.5 "checkAuthStatus" tool.',
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: 'connectorName variable from above conversation.'
20
+ description: 'Connector platform name'
25
21
  },
26
22
  hostname: {
27
23
  type: 'string',
28
- description: 'Hostname to authenticate to.'
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: ['connectorManifest', 'connectorName', 'hostname']
27
+ required: ['connectorName']
58
28
  },
59
29
  annotations: {
60
30
  readOnlyHint: false,
61
- openWorldHint: true,
62
- destructiveHint: true
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 { connectorManifest, connectorName, hostname, apiKey, additionalInfo, callbackUri } = args;
80
- const { isValid, errors } = isManifestValid({ connectorManifest, connectorName });
81
- if (!isValid) {
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
- catch (error) {
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 AUTHENTICATION: User must first authenticate using the "auth" tool to obtain a JWT token before using this tool. | Search for a contact in the CRM platform by name. Returns contact details if found.',
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: ['jwtToken', 'name']
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 { jwtToken, name } = args;
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 AUTHENTICATION: User must first authenticate using the "auth" tool to obtain a JWT token before using this tool. | Search for a contact in the CRM platform by phone number. Returns contact details if found.',
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: ['jwtToken', 'phoneNumber']
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 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.',
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: ['jwtToken']
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 authenticate with googleSheets platform first using the doAuth tool.'
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 authenticate with googleSheets platform first.'
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,
@@ -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 developerPortal = require('../../connector/developerPortal');
1
+ const axios = require('axios');
2
2
 
3
3
  /**
4
4
  * MCP Tool: Get Public Connectors
5
- *
6
- * This tool retrieves a list of public connectors from the developer portal.
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: 'Auth flow step.1. Get a list of all available connectors from the developer portal. Returns a list of connector names for users to choose. Next step is calling step.2 "setConnector" tool.',
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: false,
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
- * @returns {Object} Result object with connector names
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
- try {
32
- const { connectors: publicConnectorList } = await developerPortal.getPublicConnectorList();
33
- const connectorList = publicConnectorList;
34
- if(process.env.RC_ACCOUNT_ID) {
35
- const { privateConnectors } = await developerPortal.getPrivateConnectorList();
36
- connectorList.push(...privateConnectors);
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;
@@ -1,64 +1,39 @@
1
1
  /**
2
2
  * MCP Tools Index
3
- *
4
- * This file exports all available MCP tools for the RC Unified CRM Extension.
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
- // Export all tools
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
- // Export tools as an array for easy iteration
46
- module.exports.tools = [
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
 
@@ -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: false,
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. If user does not have this, direct them to use the "auth" tool first.
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
- if (!userToLogout) {
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 AUTHENTICATION: User must first authenticate using the "auth" tool to obtain a JWT token before using this tool. | Get call logs from RingCentral',
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: ['jwtToken', 'timeFrom', 'timeTo']
20
+ required: []
25
21
  },
26
22
  annotations: {
27
23
  readOnlyHint: true,
28
- openWorldHint: false,
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) {