@app-connect/core 1.7.25 → 1.7.26

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 (137) hide show
  1. package/.env.test +5 -5
  2. package/README.md +441 -441
  3. package/connector/developerPortal.js +31 -31
  4. package/connector/mock.js +84 -77
  5. package/connector/proxy/engine.js +164 -164
  6. package/connector/proxy/index.js +500 -500
  7. package/connector/registry.js +252 -252
  8. package/docs/README.md +50 -50
  9. package/docs/architecture.md +93 -93
  10. package/docs/connectors.md +116 -116
  11. package/docs/handlers.md +125 -125
  12. package/docs/libraries.md +101 -101
  13. package/docs/models.md +144 -144
  14. package/docs/routes.md +115 -115
  15. package/docs/tests.md +73 -73
  16. package/handlers/admin.js +523 -523
  17. package/handlers/appointment.js +193 -0
  18. package/handlers/auth.js +296 -296
  19. package/handlers/calldown.js +99 -99
  20. package/handlers/contact.js +280 -280
  21. package/handlers/disposition.js +82 -80
  22. package/handlers/log.js +984 -973
  23. package/handlers/managedAuth.js +446 -446
  24. package/handlers/plugin.js +208 -208
  25. package/handlers/user.js +142 -142
  26. package/index.js +3140 -2652
  27. package/jest.config.js +56 -56
  28. package/lib/analytics.js +54 -54
  29. package/lib/authSession.js +109 -109
  30. package/lib/cacheCleanup.js +21 -0
  31. package/lib/callLogComposer.js +898 -898
  32. package/lib/callLogLookup.js +34 -0
  33. package/lib/constants.js +8 -8
  34. package/lib/debugTracer.js +177 -177
  35. package/lib/encode.js +30 -30
  36. package/lib/errorHandler.js +218 -206
  37. package/lib/generalErrorMessage.js +41 -41
  38. package/lib/jwt.js +18 -18
  39. package/lib/logger.js +190 -190
  40. package/lib/migrateCallLogsSchema.js +116 -0
  41. package/lib/ringcentral.js +266 -266
  42. package/lib/s3ErrorLogReport.js +65 -65
  43. package/lib/sharedSMSComposer.js +471 -471
  44. package/lib/util.js +67 -67
  45. package/mcp/README.md +412 -395
  46. package/mcp/lib/validator.js +91 -91
  47. package/mcp/mcpHandler.js +425 -425
  48. package/mcp/tools/cancelAppointment.js +101 -0
  49. package/mcp/tools/checkAuthStatus.js +105 -105
  50. package/mcp/tools/confirmAppointment.js +101 -0
  51. package/mcp/tools/createAppointment.js +157 -0
  52. package/mcp/tools/createCallLog.js +327 -316
  53. package/mcp/tools/createContact.js +117 -117
  54. package/mcp/tools/createMessageLog.js +287 -287
  55. package/mcp/tools/doAuth.js +60 -60
  56. package/mcp/tools/findContactByName.js +93 -93
  57. package/mcp/tools/findContactByPhone.js +101 -101
  58. package/mcp/tools/getCallLog.js +111 -102
  59. package/mcp/tools/getGoogleFilePicker.js +99 -99
  60. package/mcp/tools/getHelp.js +43 -43
  61. package/mcp/tools/getPublicConnectors.js +94 -94
  62. package/mcp/tools/getSessionInfo.js +90 -90
  63. package/mcp/tools/index.js +51 -41
  64. package/mcp/tools/listAppointments.js +163 -0
  65. package/mcp/tools/logout.js +96 -96
  66. package/mcp/tools/rcGetCallLogs.js +65 -65
  67. package/mcp/tools/updateAppointment.js +154 -0
  68. package/mcp/tools/updateCallLog.js +130 -126
  69. package/mcp/ui/App/App.tsx +358 -358
  70. package/mcp/ui/App/components/AuthInfoForm.tsx +113 -113
  71. package/mcp/ui/App/components/AuthSuccess.tsx +22 -22
  72. package/mcp/ui/App/components/ConnectorList.tsx +82 -82
  73. package/mcp/ui/App/components/DebugPanel.tsx +43 -43
  74. package/mcp/ui/App/components/OAuthConnect.tsx +270 -270
  75. package/mcp/ui/App/lib/callTool.ts +130 -130
  76. package/mcp/ui/App/lib/debugLog.ts +41 -41
  77. package/mcp/ui/App/lib/developerPortal.ts +111 -111
  78. package/mcp/ui/App/main.css +5 -5
  79. package/mcp/ui/App/root.tsx +13 -13
  80. package/mcp/ui/index.html +13 -13
  81. package/mcp/ui/package-lock.json +6356 -6356
  82. package/mcp/ui/package.json +25 -25
  83. package/mcp/ui/tsconfig.json +26 -26
  84. package/mcp/ui/vite.config.ts +16 -16
  85. package/models/accountDataModel.js +33 -33
  86. package/models/adminConfigModel.js +35 -35
  87. package/models/cacheModel.js +30 -26
  88. package/models/callDownListModel.js +34 -34
  89. package/models/callLogModel.js +33 -27
  90. package/models/dynamo/connectorSchema.js +146 -146
  91. package/models/dynamo/lockSchema.js +24 -24
  92. package/models/dynamo/noteCacheSchema.js +29 -29
  93. package/models/llmSessionModel.js +17 -17
  94. package/models/messageLogModel.js +25 -25
  95. package/models/sequelize.js +16 -16
  96. package/models/userModel.js +45 -45
  97. package/package.json +1 -1
  98. package/releaseNotes.json +1093 -1081
  99. package/test/connector/proxy/engine.test.js +126 -126
  100. package/test/connector/proxy/index.test.js +279 -279
  101. package/test/connector/proxy/sample.json +161 -161
  102. package/test/connector/registry.test.js +415 -415
  103. package/test/handlers/admin.test.js +616 -616
  104. package/test/handlers/auth.test.js +1018 -1018
  105. package/test/handlers/contact.test.js +1014 -1014
  106. package/test/handlers/log.test.js +1298 -1160
  107. package/test/handlers/managedAuth.test.js +457 -457
  108. package/test/handlers/plugin.test.js +380 -380
  109. package/test/index.test.js +105 -105
  110. package/test/lib/cacheCleanup.test.js +42 -0
  111. package/test/lib/callLogComposer.test.js +1231 -1231
  112. package/test/lib/debugTracer.test.js +328 -328
  113. package/test/lib/jwt.test.js +176 -176
  114. package/test/lib/logger.test.js +206 -206
  115. package/test/lib/oauth.test.js +359 -359
  116. package/test/lib/ringcentral.test.js +467 -467
  117. package/test/lib/sharedSMSComposer.test.js +1084 -1084
  118. package/test/lib/util.test.js +329 -329
  119. package/test/mcp/tools/checkAuthStatus.test.js +83 -83
  120. package/test/mcp/tools/createCallLog.test.js +436 -436
  121. package/test/mcp/tools/createContact.test.js +58 -58
  122. package/test/mcp/tools/createMessageLog.test.js +595 -595
  123. package/test/mcp/tools/doAuth.test.js +113 -113
  124. package/test/mcp/tools/findContactByName.test.js +275 -275
  125. package/test/mcp/tools/findContactByPhone.test.js +296 -296
  126. package/test/mcp/tools/getCallLog.test.js +298 -298
  127. package/test/mcp/tools/getGoogleFilePicker.test.js +281 -281
  128. package/test/mcp/tools/getPublicConnectors.test.js +107 -107
  129. package/test/mcp/tools/getSessionInfo.test.js +127 -127
  130. package/test/mcp/tools/logout.test.js +233 -233
  131. package/test/mcp/tools/rcGetCallLogs.test.js +56 -56
  132. package/test/mcp/tools/updateCallLog.test.js +360 -360
  133. package/test/models/accountDataModel.test.js +98 -98
  134. package/test/models/dynamo/connectorSchema.test.js +189 -189
  135. package/test/models/models.test.js +568 -539
  136. package/test/routes/managedAuthRoutes.test.js +104 -104
  137. package/test/setup.js +178 -178
package/mcp/README.md CHANGED
@@ -1,395 +1,412 @@
1
- # MCP Module Documentation
2
-
3
- ## Overview
4
-
5
- The MCP (Model Context Protocol) module provides an AI assistant interface for the RingCentral Unified CRM Extension. It enables AI assistants like ChatGPT to interact with the CRM integration through a standardized protocol, allowing users to authenticate, manage contacts, and log calls via conversational AI.
6
-
7
- ## Architecture
8
-
9
- ```
10
- packages/core/mcp/
11
- ├── mcpHandler.js # Main MCP server handler + WIDGET_VERSION constant
12
- ├── lib/
13
- │ └── validator.js # Connector manifest validation
14
- ├── tools/ # MCP tool implementations
15
- │ ├── index.js # Tool registry (tools + widgetTools)
16
- │ ├── getHelp.js # Help/onboarding tool
17
- │ ├── getPublicConnectors.js # Triggers widget, resolves RC account ID + rcExtensionId + openaiSessionId
18
- │ ├── doAuth.js # OAuth session creation (widget-only)
19
- │ ├── checkAuthStatus.js # Poll OAuth status (widget-only)
20
- │ ├── logout.js # Logout from CRM
21
- │ ├── findContactByPhone.js # Search contact by phone
22
- │ ├── findContactByName.js # Search contact by name
23
- │ ├── createContact.js # Create new contact
24
- │ ├── createCallLog.js # Create call log entry
25
- │ ├── rcGetCallLogs.js # Fetch RingCentral call logs
26
- │ ├── getGoogleFilePicker.js # Google Sheets picker (disabled)
27
- │ ├── getCallLog.js # Get call log (disabled)
28
- │ ├── updateCallLog.js # Update call log (disabled)
29
- └── createMessageLog.js # Create message log (disabled)
30
- └── ui/ # ChatGPT Widget UI
31
- ├── index.html # Entry HTML
32
- ├── package.json # UI dependencies
33
- ├── vite.config.ts # Vite build config
34
- ├── App/
35
- │ ├── root.tsx # React entry point
36
- ├── App.tsx # Multi-step auth flow orchestrator
37
- ├── main.css # Tailwind + OpenAI styles
38
- ├── lib/
39
- │ │ ├── callTool.ts # Direct fetch to /mcp/widget-tool-call
40
- ├── developerPortal.ts # Client-side developer portal API calls
41
- │ └── debugLog.ts # Debug logger
42
- └── components/
43
- ├── ConnectorList.tsx # Connector selection widget
44
- ├── AuthInfoForm.tsx # Hostname/environment input form
45
- ├── OAuthConnect.tsx # OAuth link + status polling
46
- ├── AuthSuccess.tsx # Success banner
47
- └── DebugPanel.tsx # Collapsible debug log panel
48
- └── dist/ # Built widget output
49
- ```
50
-
51
- ## Core Components
52
-
53
- ### MCP Handler (`mcpHandler.js`)
54
-
55
- A stateless, hand-rolled JSON-RPC handler — no `@modelcontextprotocol/sdk`, no SSE, no in-memory sessions. Each POST request is handled independently, making it fully compatible with stateless deployments like AWS Lambda.
56
-
57
- **Key Features:**
58
- - Defines `WIDGET_VERSION` — the **single source of truth** for the widget cache-busting URI
59
- - Handles `initialize`, `tools/list`, `tools/call`, `resources/list`, `resources/read`, and `ping` methods
60
- - Defines `inputSchema` (JSON Schema) for every tool that takes parameters required so ChatGPT forwards arguments
61
- - Injects `rcAccessToken`, `openaiSessionId`, and `rcExtensionId` into every `tools/call` request
62
- - Verifies the RC access token against the RC API and caches `rcExtensionId` in `CacheModel` keyed by `openaiSessionId` (24h TTL) — subsequent requests hit the cache instead of the RC API
63
- - Automatically looks up and injects `jwtToken` from `LlmSessionModel` using `rcExtensionId` (or `openaiSessionId` as a fallback), only when the linked `User` row still has a CRM `accessToken`
64
- - Stamps `WIDGET_URI` into `getPublicConnectors`'s `_meta['openai/outputTemplate']` at response time
65
- - Serves the widget HTML via `resources/read`
66
- - Exposes `handleWidgetToolCall` which searches both `tools.tools` and `tools.widgetTools`
67
-
68
- **Request Flow:**
69
- 1. Receives `POST /mcp` with a JSON-RPC body
70
- 2. Extracts `rcAccessToken` from `Authorization` header and `openaiSessionId` from `params._meta['openai/session']`
71
- 3. On `tools/call`: checks `CacheModel` for a cached `rcExtensionId`; if missing, verifies via RC API and persists to cache
72
- 4. Injects server-side context (`rcAccessToken`, `openaiSessionId`, `rcExtensionId`, `jwtToken`) into tool args
73
- 5. Routes to the appropriate tool handler via a `switch` on `method`
74
- 6. Returns a JSON-RPC response immediately — no streaming, no SSE
75
-
76
- ### Widget Version Management
77
-
78
- `WIDGET_VERSION` in `mcpHandler.js` is the **only place** that needs to change when bumping the widget version:
79
-
80
- ```js
81
- // mcpHandler.js
82
- const WIDGET_VERSION = 6;
83
- const WIDGET_URI = `ui://widget/ConnectorList-v${WIDGET_VERSION}.html`;
84
- ```
85
-
86
- At registration time, `mcpHandler.js` stamps `WIDGET_URI` into `getPublicConnectors`'s `_meta['openai/outputTemplate']`. `getPublicConnectors.js` itself does **not** contain a version number.
87
-
88
- **To deploy a new widget build:**
89
- 1. Rebuild the widget: `cd packages/core/mcp/ui && npm run build`
90
- 2. Increment `WIDGET_VERSION` in `mcpHandler.js`
91
- 3. Restart the server
92
-
93
- ### Manifest Validator (`lib/validator.js`)
94
-
95
- Validates connector manifest structures before authentication operations.
96
-
97
- ## MCP Tools
98
-
99
- ### Tool Registry
100
-
101
- Tools are split into two registries in `tools/index.js`:
102
-
103
- | Registry | Purpose |
104
- |----------|---------|
105
- | `tools` | Registered in the MCP server — visible to and callable by the AI model |
106
- | `widgetTools` | Accessible only via `POST /mcp/widget-tool-call` — hidden from the AI model |
107
-
108
- ### Argument Handling
109
-
110
- `mcpHandler.js` automatically injects server-side values into every tool's args before calling `execute()`:
111
-
112
- | Injected arg | Source | Purpose |
113
- |---|---|---|
114
- | `rcAccessToken` | `Authorization` request header | RingCentral API calls |
115
- | `openaiSessionId` | `params._meta['openai/session']` | Stable ChatGPT conversation ID |
116
- | `rcExtensionId` | RC API (`/extension/~`), verified once and cached in `sessionContext` | Cryptographically verified RC identity; used as `LlmSessionModel` key |
117
- | `jwtToken` | `LlmSessionModel.findByPk(rcExtensionId)` (fallback: `findByPk(openaiSessionId)`) | CRM auth token (after OAuth) |
118
-
119
- Tools do **not** need ChatGPT to pass `jwtToken` explicitly — it is resolved from the session automatically. The `rcExtensionId` is verified via the RC API on the **first tool call** of each session and cached for all subsequent calls within the same conversation (0 additional API calls after that). If the user has logged out or the CRM token was cleared, `jwtToken` is not injected even when a stale row exists in `LlmSessionModel`.
120
-
121
- New or refreshed LLM session JWTs are written only when the user record has an `accessToken`, so disconnected users are not issued a new tool JWT.
122
-
123
- Note: `widgetTools` are called via `POST /mcp/widget-tool-call` which bypasses the MCP session layer entirely. No server-side injection occurs for widget tool calls — all required values must be passed explicitly by the widget in the request body.
124
-
125
- ### AI-Visible Tools (`tools`)
126
-
127
- #### `getHelp`
128
- Provides onboarding guidance for new users.
129
-
130
- | Property | Value |
131
- |----------|-------|
132
- | Read-only | Yes |
133
- | Parameters | None |
134
- | Returns | Overview, steps |
135
-
136
- #### `getPublicConnectors`
137
- Triggers the interactive connector selection widget. The widget fetches the connector list and manifests directly from the developer portal on the client side.
138
-
139
- | Property | Value |
140
- |----------|-------|
141
- | Read-only | Yes |
142
- | Parameters | None (server injects `rcAccessToken` and `openaiSessionId`) |
143
- | Returns | `structuredContent` with `serverUrl`, `rcAccountId`, `rcExtensionId`, and `openaiSessionId` |
144
- | Widget | `ui://widget/ConnectorList-v{WIDGET_VERSION}.html` (versioned by `mcpHandler.js`) |
145
-
146
- #### `logout`
147
- Logs out user from the CRM platform.
148
-
149
- | Property | Value |
150
- |----------|-------|
151
- | Destructive | Yes |
152
- | Parameters | `jwtToken` (optional — injected from session if not passed) |
153
- | Action | Clears user credentials |
154
- | Note | Local session cleanup always runs. Failures from the connector `unAuthorize` call are logged; the tool still returns success so the client can clear context. |
155
-
156
- #### Contact & Call Log Tools
157
-
158
- `jwtToken` is injected automatically from the session — ChatGPT does not need to pass it:
159
-
160
- | Tool | Parameters | Description |
161
- |------|-----------|-------------|
162
- | `findContactByPhone` | `phoneNumber` (E.164) | Search contact by phone |
163
- | `findContactByName` | `name` | Search contact by name |
164
- | `createContact` | `phoneNumber`, `newContactName?` | Create new CRM contact |
165
- | `rcGetCallLogs` | `timeFrom`, `timeTo` (ISO 8601) | Fetch RingCentral call logs. Each `records[i]` item can be passed directly as `incomingData.logInfo` to `createCallLog`. |
166
- | `createCallLog` | `incomingData` (with `logInfo` = single `rcGetCallLogs` record), `contactId?`, `note?` | Create call log in CRM. Pass a `records[i]` item from `rcGetCallLogs` directly as `incomingData.logInfo`. |
167
-
168
- ### Widget-Only Tools (`widgetTools`)
169
-
170
- Not registered in the MCP server; only callable by the widget iframe via `POST /mcp/widget-tool-call`. No server-side arg injection the widget passes all required values directly.
171
-
172
- #### `doAuth`
173
- Creates a server-side OAuth session for the given `sessionId`.
174
-
175
- | Property | Value |
176
- |----------|-------|
177
- | Parameters | `sessionId`, `connectorName`, `hostname` |
178
- | Returns | `{ success: true }` |
179
- | Note | The widget generates `sessionId` and the OAuth URL client-side; `doAuth` just registers the session in the DB so the callback can resolve it |
180
-
181
- #### `checkAuthStatus`
182
- Polls the OAuth session status. Called exclusively by the widget during the OAuth flow — the AI model never calls this directly.
183
-
184
- | Property | Value |
185
- |----------|-------|
186
- | Parameters | `sessionId`, `rcExtensionId?` (passed by widget from `getPublicConnectors` structuredContent) |
187
- | Returns | `{ data: { status, ... } }` for all states |
188
- | On Success | `data.jwtToken` and `data.userInfo` included; JWT stored in `LlmSessionModel` keyed by `rcExtensionId` |
189
- | Statuses | `pending` · `completed` · `failed` — all return consistent `{ data: { status } }` for reliable widget parsing |
190
-
191
- ## ChatGPT Widget UI
192
-
193
- The UI module provides a single interactive widget that drives the full authentication flow inside a ChatGPT iframe. Users select a connector, provide any required environment info, authorize via OAuth (opened in a new tab), and see a success confirmation — all without leaving the widget.
194
-
195
- ### Technology Stack
196
- - **React 18** with TypeScript
197
- - **Vite** with `vite-plugin-singlefile` for self-contained HTML
198
- - **Tailwind CSS 4** with `@openai/apps-sdk-ui` components
199
- - **OpenAI Apps SDK UI** for consistent ChatGPT styling
200
-
201
- ### Widget Communication
202
-
203
- **1. Receiving the initial server context** via four mechanisms (whichever fires first):
204
-
205
- | Mechanism | Description |
206
- |-----------|-------------|
207
- | `window.openai.toolOutput` | Synchronous read on mount |
208
- | `openai:set_globals` event | ChatGPT pushes globals into the iframe |
209
- | `ui/notifications/tool-result` postMessage | MCP Apps bridge notification |
210
- | Polling `window.openai.toolOutput` | Fallback for async population |
211
-
212
- The initial payload is `{ serverUrl, rcAccountId, rcExtensionId, openaiSessionId }`.
213
-
214
- **2. Fetching connectors and manifests** via direct `fetch()` to `appconnect.labs.ringcentral.com`:
215
-
216
- ```typescript
217
- import { fetchConnectors, fetchManifest } from './lib/developerPortal'
218
-
219
- const connectors = await fetchConnectors(rcAccountId) // list with id, name, displayName
220
- const manifest = await fetchManifest(connector.id, isPrivate, rcAccountId)
221
- ```
222
-
223
- **3. Calling widget tools** — via direct `fetch()` to `POST /mcp/widget-tool-call`:
224
-
225
- ```typescript
226
- import { callTool } from './lib/callTool'
227
-
228
- const result = await callTool('doAuth', { sessionId, connectorName, hostname })
229
- const status = await callTool('checkAuthStatus', { sessionId, rcExtensionId })
230
- ```
231
-
232
- `window.openai.callTool()` is **intentionally not used** for widget tool calls. Direct `fetch()` to `/mcp/widget-tool-call` forwards all arguments correctly and works for both `doAuth` and `checkAuthStatus`.
233
-
234
- ### Widget Auth Flow
235
-
236
- The widget (`App.tsx`) acts as a multi-step wizard:
237
-
238
- ```
239
- loadingConnectors → select → loading → authInfo (if dynamic/selectable env) → oauth → success
240
- oauth (if fixed env) error
241
- ```
242
-
243
- | Step | Component | Description |
244
- |------|-----------|-------------|
245
- | `loadingConnectors` | (spinner) | Widget fetches connector list from developer portal |
246
- | `select` | `ConnectorList` | Displays available connectors |
247
- | `loading` | (spinner) | Shown while manifest is being fetched |
248
- | `authInfo` | `AuthInfoForm` | Collects hostname (dynamic) or environment (selectable). Resolved locally — no server call |
249
- | `oauth` | `OAuthConnect` | Uses `openaiSessionId` as the OAuth session ID (falls back to `crypto.randomUUID()`). Generates the OAuth URL client-side, calls `doAuth` in background to register the session in the DB, then shows "Authorize" button. After click, polls `checkAuthStatus` every 5 seconds via direct fetch |
250
- | `success` | `AuthSuccess` | Shows connected CRM name and user info |
251
- | `error` | (inline) | Shows error with "Back to connector list" link |
252
-
253
- ### Components
254
-
255
- #### ConnectorList
256
- Displays available CRM connectors with public/private badges. On selection, delegates to `App.tsx` which fetches the manifest directly.
257
-
258
- #### AuthInfoForm
259
- Form for environment info collection. Handles `dynamic` (text input) and `selectable` (button list) environment types. Hostname resolution happens inline in `App.tsx`.
260
-
261
- #### OAuthConnect
262
- Handles the full OAuth step. Uses `openaiSessionId` (from the initial tool output) as the session ID so the OAuth callback can be correlated with the ChatGPT conversation — falls back to `crypto.randomUUID()` when running outside ChatGPT. Calls `doAuth` in the background, shows an "Authorize in [CRM]" button (disabled until session is created), then polls `checkAuthStatus` every 5 seconds via `callTool()` (direct fetch). On success, fires `updateModelContext` to push the jwtToken into ChatGPT's context, then calls `onSuccess`.
263
-
264
- #### AuthSuccess
265
- Success banner showing the connected CRM name and optional user info.
266
-
267
- ### Developer Portal Client (`lib/developerPortal.ts`)
268
-
269
- Calls `appconnect.labs.ringcentral.com/public-api` directly from the browser:
270
-
271
- | Function | Description |
272
- |----------|-------------|
273
- | `fetchConnectors(rcAccountId?)` | Fetches public + private connectors, filters to `SUPPORTED_PLATFORMS` |
274
- | `fetchManifest(connectorId, isPrivate, rcAccountId?)` | Fetches connector manifest by ID |
275
-
276
- `SUPPORTED_PLATFORMS` is defined in this file: `['clio']`.
277
-
278
- ### Building the Widget
279
-
280
- **Production build** (generates `dist/index.html` with all JS, CSS, and assets inlined):
281
-
282
- ```bash
283
- cd packages/core/mcp/ui
284
- npm install
285
- npm run build # Output: dist/index.html (single file)
286
- ```
287
-
288
- **Development server** (hot reload for local testing):
289
-
290
- ```bash
291
- cd packages/core/mcp/ui
292
- npm run dev
293
- ```
294
-
295
- **Cache busting after a build:** Increment `WIDGET_VERSION` in `mcpHandler.js` and restart the server. That is the only file that needs to change — `getPublicConnectors.js` and the README do not contain a version number.
296
-
297
- ## API Integration
298
-
299
- ### Endpoints
300
-
301
- | Endpoint | Purpose |
302
- |----------|---------|
303
- | `POST /mcp` | Full MCP protocol endpoint for AI assistants (ChatGPT, etc.) |
304
- | `POST /mcp/widget-tool-call` | Lightweight direct tool call for the widget iframe (bypasses MCP session protocol) |
305
-
306
- #### `POST /mcp/widget-tool-call`
307
-
308
- Called by the widget via `fetch()` to invoke `doAuth` and `checkAuthStatus` with full argument support.
309
-
310
- **`doAuth` request:**
311
- ```json
312
- {
313
- "tool": "doAuth",
314
- "toolArgs": {
315
- "sessionId": "<openaiSessionId or random UUID>",
316
- "connectorName": "clio",
317
- "hostname": "app.clio.com"
318
- }
319
- }
320
- ```
321
-
322
- **`checkAuthStatus` request:**
323
- ```json
324
- {
325
- "tool": "checkAuthStatus",
326
- "toolArgs": {
327
- "sessionId": "<same sessionId used in doAuth>",
328
- "rcExtensionId": "<from getPublicConnectors structuredContent>"
329
- }
330
- }
331
- ```
332
-
333
- **Response:** The raw result from `tool.execute()` — shape varies by tool.
334
-
335
- ### Headers (for `/mcp`)
336
-
337
- | Header | Value |
338
- |--------|-------|
339
- | `Content-Type` | `application/json` |
340
- | `Authorization` | `Bearer <RC_ACCESS_TOKEN>` (optional, for RingCentral API calls) |
341
-
342
- ## Supported Platforms
343
-
344
- Currently supported for MCP integration:
345
- - **Clio** - Legal practice management
346
-
347
- ## Security Considerations
348
-
349
- 1. **JWT Tokens**: Stored server-side in `LlmSessionModel`, **keyed by `rcExtensionId`** (a cryptographically verified RingCentral identity). Tools receive the JWT via server injection — it is never sent back to ChatGPT as a visible parameter.
350
- 2. **RC Identity Verification**: On the first tool call of each session, `mcpHandler.js` calls `GET /restapi/v1.0/extension/~` with the Bearer token from the request. If the RC token is invalid, the call throws and `rcExtensionId` remains `null`. The result is cached in `sessionContext` so at most **one RC API call** is made per conversation.
351
- 3. **Session key binding**: The sessions Map is keyed on the stable `openai/session` ID so the same `sessionContext` (and its verified `rcExtensionId`) is reused for all tool calls within a ChatGPT conversation. A session can only access credentials stored under its own verified `rcExtensionId`.
352
- 4. **Session Management**: MCP sessions are server-side and automatically cleaned up on transport close.
353
- 5. **OAuth Flows**: Uses secure OAuth 2.0 with server-side callback handling.
354
- 6. **RC Account ID**: Resolved server-side via RC API and passed to the widget — never requires exposing secrets to the browser.
355
- 7. **CORS**: Widget calls `appconnect.labs.ringcentral.com` directly; the developer portal public API supports browser fetch.
356
-
357
- | | Before | After |
358
- |---|---|---|
359
- | Identity proof | `openai/session` (unverified, from request body) | `rcExtensionId` (verified via RC API) |
360
- | RC token verified? | No | Yes — first tool call per conversation |
361
- | API calls per session | 0 (no verify) | 1 (cached after first call) |
362
- | `LlmSessionModel` key | arbitrary session ID | stable, verified RC extension ID |
363
-
364
- ## Error Handling
365
-
366
- All tools return standardized response objects:
367
-
368
- **Success:**
369
- ```json
370
- { "success": true, "data": { ... } }
371
- ```
372
-
373
- **Failure:**
374
- ```json
375
- { "success": false, "error": "Error message" }
376
- ```
377
-
378
- ## Usage Example
379
-
380
- A typical conversation flow:
381
-
382
- 1. **User**: "Connect me to my CRM"
383
- 2. **AI**: Calls `getPublicConnectors` server verifies RC token, resolves `rcExtensionId` + RC account ID + `openaiSessionId` → shows widget
384
- 3. **Widget**: Fetches connector list from developer portal → displays connector cards
385
- 4. **User**: Clicks "Clio" in the widget
386
- 5. **Widget**: Fetches Clio manifest → shows environment selector (US/EU/AU/CA)
387
- 6. **User**: Selects region widget calls `doAuth` with `openaiSessionId` as OAuth session key → shows "Authorize in Clio" button
388
- 7. **User**: Clicks button → Clio OAuth page opens in new tab → user authorizes
389
- 8. **Widget**: Polls `checkAuthStatus` every 5 seconds via direct fetch (passes `sessionId` + `rcExtensionId`) → "Waiting for authorization..."
390
- 9. **Widget**: Auth completes → jwtToken stored in `LlmSessionModel[rcExtensionId]` → widget fires `updateModelContext` with jwtToken → shows success banner
391
- 10. **AI**: "You're now connected! What would you like to do?"
392
- 11. **User**: "Find contacts named Test"
393
- 12. **AI**: Calls `findContactByName(name="Test")` — server injects jwtToken automatically using cached `rcExtensionId` as lookup key → returns results
394
-
395
- Steps 3–9 happen entirely within the widget iframe. The AI assistant is not involved until authentication is complete.
1
+ # MCP Module Documentation
2
+
3
+ ## Overview
4
+
5
+ The MCP (Model Context Protocol) module provides an AI assistant interface for the RingCentral Unified CRM Extension. It enables AI assistants like ChatGPT to interact with the CRM integration through a standardized protocol, allowing users to authenticate, manage contacts, and log calls via conversational AI.
6
+
7
+ ## Architecture
8
+
9
+ ```
10
+ packages/core/mcp/
11
+ ├── mcpHandler.js # Main MCP server handler + WIDGET_VERSION constant
12
+ ├── lib/
13
+ │ └── validator.js # Connector manifest validation
14
+ ├── tools/ # MCP tool implementations
15
+ │ ├── index.js # Tool registry (tools + widgetTools)
16
+ │ ├── getHelp.js # Help/onboarding tool
17
+ │ ├── getPublicConnectors.js # Triggers widget, resolves RC account ID + rcExtensionId + openaiSessionId
18
+ │ ├── doAuth.js # OAuth session creation (widget-only)
19
+ │ ├── checkAuthStatus.js # Poll OAuth status (widget-only)
20
+ │ ├── logout.js # Logout from CRM
21
+ │ ├── findContactByPhone.js # Search contact by phone
22
+ │ ├── findContactByName.js # Search contact by name
23
+ │ ├── createContact.js # Create new contact
24
+ │ ├── createCallLog.js # Create call log entry
25
+ │ ├── rcGetCallLogs.js # Fetch RingCentral call logs
26
+ │ ├── listAppointments.js # List appointments (upcoming/today/past/all/custom)
27
+ │ ├── createAppointment.js # Create a new appointment/event
28
+ │ ├── updateAppointment.js # Update/reschedule an appointment/event
29
+ ├── confirmAppointment.js # Confirm an appointment/event
30
+ │ ├── cancelAppointment.js # Cancel an appointment/event
31
+ ├── getGoogleFilePicker.js # Google Sheets picker (disabled)
32
+ ├── getCallLog.js # Get call log (disabled)
33
+ ├── updateCallLog.js # Update call log (disabled)
34
+ │ └── createMessageLog.js # Create message log (disabled)
35
+ └── ui/ # ChatGPT Widget UI
36
+ ├── index.html # Entry HTML
37
+ ├── package.json # UI dependencies
38
+ ├── vite.config.ts # Vite build config
39
+ ├── App/
40
+ │ ├── root.tsx # React entry point
41
+ ├── App.tsx # Multi-step auth flow orchestrator
42
+ ├── main.css # Tailwind + OpenAI styles
43
+ ├── lib/
44
+ ├── callTool.ts # Direct fetch to /mcp/widget-tool-call
45
+ ├── developerPortal.ts # Client-side developer portal API calls
46
+ │ └── debugLog.ts # Debug logger
47
+ └── components/
48
+ │ ├── ConnectorList.tsx # Connector selection widget
49
+ │ ├── AuthInfoForm.tsx # Hostname/environment input form
50
+ │ ├── OAuthConnect.tsx # OAuth link + status polling
51
+ │ ├── AuthSuccess.tsx # Success banner
52
+ │ └── DebugPanel.tsx # Collapsible debug log panel
53
+ └── dist/ # Built widget output
54
+ ```
55
+
56
+ ## Core Components
57
+
58
+ ### MCP Handler (`mcpHandler.js`)
59
+
60
+ A stateless, hand-rolled JSON-RPC handler no `@modelcontextprotocol/sdk`, no SSE, no in-memory sessions. Each POST request is handled independently, making it fully compatible with stateless deployments like AWS Lambda.
61
+
62
+ **Key Features:**
63
+ - Defines `WIDGET_VERSION` the **single source of truth** for the widget cache-busting URI
64
+ - Handles `initialize`, `tools/list`, `tools/call`, `resources/list`, `resources/read`, and `ping` methods
65
+ - Defines `inputSchema` (JSON Schema) for every tool that takes parameters — required so ChatGPT forwards arguments
66
+ - Injects `rcAccessToken`, `openaiSessionId`, and `rcExtensionId` into every `tools/call` request
67
+ - Verifies the RC access token against the RC API and caches `rcExtensionId` in `CacheModel` keyed by `openaiSessionId` (24h TTL) — subsequent requests hit the cache instead of the RC API
68
+ - Automatically looks up and injects `jwtToken` from `LlmSessionModel` using `rcExtensionId` (or `openaiSessionId` as a fallback), only when the linked `User` row still has a CRM `accessToken`
69
+ - Stamps `WIDGET_URI` into `getPublicConnectors`'s `_meta['openai/outputTemplate']` at response time
70
+ - Serves the widget HTML via `resources/read`
71
+ - Exposes `handleWidgetToolCall` which searches both `tools.tools` and `tools.widgetTools`
72
+
73
+ **Request Flow:**
74
+ 1. Receives `POST /mcp` with a JSON-RPC body
75
+ 2. Extracts `rcAccessToken` from `Authorization` header and `openaiSessionId` from `params._meta['openai/session']`
76
+ 3. On `tools/call`: checks `CacheModel` for a cached `rcExtensionId`; if missing, verifies via RC API and persists to cache
77
+ 4. Injects server-side context (`rcAccessToken`, `openaiSessionId`, `rcExtensionId`, `jwtToken`) into tool args
78
+ 5. Routes to the appropriate tool handler via a `switch` on `method`
79
+ 6. Returns a JSON-RPC response immediately — no streaming, no SSE
80
+
81
+ ### Widget Version Management
82
+
83
+ `WIDGET_VERSION` in `mcpHandler.js` is the **only place** that needs to change when bumping the widget version:
84
+
85
+ ```js
86
+ // mcpHandler.js
87
+ const WIDGET_VERSION = 6;
88
+ const WIDGET_URI = `ui://widget/ConnectorList-v${WIDGET_VERSION}.html`;
89
+ ```
90
+
91
+ At registration time, `mcpHandler.js` stamps `WIDGET_URI` into `getPublicConnectors`'s `_meta['openai/outputTemplate']`. `getPublicConnectors.js` itself does **not** contain a version number.
92
+
93
+ **To deploy a new widget build:**
94
+ 1. Rebuild the widget: `cd packages/core/mcp/ui && npm run build`
95
+ 2. Increment `WIDGET_VERSION` in `mcpHandler.js`
96
+ 3. Restart the server
97
+
98
+ ### Manifest Validator (`lib/validator.js`)
99
+
100
+ Validates connector manifest structures before authentication operations.
101
+
102
+ ## MCP Tools
103
+
104
+ ### Tool Registry
105
+
106
+ Tools are split into two registries in `tools/index.js`:
107
+
108
+ | Registry | Purpose |
109
+ |----------|---------|
110
+ | `tools` | Registered in the MCP server visible to and callable by the AI model |
111
+ | `widgetTools` | Accessible only via `POST /mcp/widget-tool-call` — hidden from the AI model |
112
+
113
+ ### Argument Handling
114
+
115
+ `mcpHandler.js` automatically injects server-side values into every tool's args before calling `execute()`:
116
+
117
+ | Injected arg | Source | Purpose |
118
+ |---|---|---|
119
+ | `rcAccessToken` | `Authorization` request header | RingCentral API calls |
120
+ | `openaiSessionId` | `params._meta['openai/session']` | Stable ChatGPT conversation ID |
121
+ | `rcExtensionId` | RC API (`/extension/~`), verified once and cached in `sessionContext` | Cryptographically verified RC identity; used as `LlmSessionModel` key |
122
+ | `jwtToken` | `LlmSessionModel.findByPk(rcExtensionId)` (fallback: `findByPk(openaiSessionId)`) | CRM auth token (after OAuth) |
123
+
124
+ Tools do **not** need ChatGPT to pass `jwtToken` explicitly — it is resolved from the session automatically. The `rcExtensionId` is verified via the RC API on the **first tool call** of each session and cached for all subsequent calls within the same conversation (0 additional API calls after that). If the user has logged out or the CRM token was cleared, `jwtToken` is not injected even when a stale row exists in `LlmSessionModel`.
125
+
126
+ New or refreshed LLM session JWTs are written only when the user record has an `accessToken`, so disconnected users are not issued a new tool JWT.
127
+
128
+ Note: `widgetTools` are called via `POST /mcp/widget-tool-call` which bypasses the MCP session layer entirely. No server-side injection occurs for widget tool calls — all required values must be passed explicitly by the widget in the request body.
129
+
130
+ ### AI-Visible Tools (`tools`)
131
+
132
+ #### `getHelp`
133
+ Provides onboarding guidance for new users.
134
+
135
+ | Property | Value |
136
+ |----------|-------|
137
+ | Read-only | Yes |
138
+ | Parameters | None |
139
+ | Returns | Overview, steps |
140
+
141
+ #### `getPublicConnectors`
142
+ Triggers the interactive connector selection widget. The widget fetches the connector list and manifests directly from the developer portal on the client side.
143
+
144
+ | Property | Value |
145
+ |----------|-------|
146
+ | Read-only | Yes |
147
+ | Parameters | None (server injects `rcAccessToken` and `openaiSessionId`) |
148
+ | Returns | `structuredContent` with `serverUrl`, `rcAccountId`, `rcExtensionId`, and `openaiSessionId` |
149
+ | Widget | `ui://widget/ConnectorList-v{WIDGET_VERSION}.html` (versioned by `mcpHandler.js`) |
150
+
151
+ #### `logout`
152
+ Logs out user from the CRM platform.
153
+
154
+ | Property | Value |
155
+ |----------|-------|
156
+ | Destructive | Yes |
157
+ | Parameters | `jwtToken` (optional — injected from session if not passed) |
158
+ | Action | Clears user credentials |
159
+ | Note | Local session cleanup always runs. Failures from the connector `unAuthorize` call are logged; the tool still returns success so the client can clear context. |
160
+
161
+ #### Contact & Call Log Tools
162
+
163
+ `jwtToken` is injected automatically from the session ChatGPT does not need to pass it:
164
+
165
+ | Tool | Parameters | Description |
166
+ |------|-----------|-------------|
167
+ | `findContactByPhone` | `phoneNumber` (E.164) | Search contact by phone |
168
+ | `findContactByName` | `name` | Search contact by name |
169
+ | `createContact` | `phoneNumber`, `newContactName?` | Create new CRM contact |
170
+ | `rcGetCallLogs` | `timeFrom`, `timeTo` (ISO 8601) | Fetch RingCentral call logs. Each `records[i]` item can be passed directly as `incomingData.logInfo` to `createCallLog`. |
171
+ | `createCallLog` | `incomingData` (with `logInfo` = single `rcGetCallLogs` record), `contactId?`, `note?` | Create call log in CRM. Pass a `records[i]` item from `rcGetCallLogs` directly as `incomingData.logInfo`. |
172
+
173
+ #### Appointment / Event Tools
174
+
175
+ `jwtToken` is injected automatically from the session — ChatGPT does not need to pass it:
176
+
177
+ | Tool | Parameters | Description |
178
+ |------|-----------|-------------|
179
+ | `listAppointments` | `filter?` (`upcoming`/`today`/`past`/`all`/`custom`), `startDate?`, `endDate?` (YYYY-MM-DD), `mineOnly?` | List appointments from the CRM. Named filters auto-compute date windows (±90 days). Use `custom` with `startDate`/`endDate` for a specific range. |
180
+ | `createAppointment` | `title`, `startTimeUtc` (ISO 8601), `durationMinutes`, `summary?`, `contacts?` | Create a new appointment or event in the CRM. `contacts` is an array of CRM contact IDs to invite as attendees. |
181
+ | `updateAppointment` | `appointmentId`, `title?`, `summary?`, `startTimeUtc?`, `durationMinutes?`, `contacts?` | Update or reschedule an existing appointment. Only the supplied fields are changed. `contacts` replaces the entire attendee list when provided. |
182
+ | `confirmAppointment` | `appointmentId` | Mark an existing appointment as confirmed. |
183
+ | `cancelAppointment` | `appointmentId` | Cancel (delete) an existing appointment. Destructive — cannot be undone. |
184
+
185
+ ### Widget-Only Tools (`widgetTools`)
186
+
187
+ Not registered in the MCP server; only callable by the widget iframe via `POST /mcp/widget-tool-call`. No server-side arg injection — the widget passes all required values directly.
188
+
189
+ #### `doAuth`
190
+ Creates a server-side OAuth session for the given `sessionId`.
191
+
192
+ | Property | Value |
193
+ |----------|-------|
194
+ | Parameters | `sessionId`, `connectorName`, `hostname` |
195
+ | Returns | `{ success: true }` |
196
+ | Note | The widget generates `sessionId` and the OAuth URL client-side; `doAuth` just registers the session in the DB so the callback can resolve it |
197
+
198
+ #### `checkAuthStatus`
199
+ Polls the OAuth session status. Called exclusively by the widget during the OAuth flow — the AI model never calls this directly.
200
+
201
+ | Property | Value |
202
+ |----------|-------|
203
+ | Parameters | `sessionId`, `rcExtensionId?` (passed by widget from `getPublicConnectors` structuredContent) |
204
+ | Returns | `{ data: { status, ... } }` for all states |
205
+ | On Success | `data.jwtToken` and `data.userInfo` included; JWT stored in `LlmSessionModel` keyed by `rcExtensionId` |
206
+ | Statuses | `pending` · `completed` · `failed` — all return consistent `{ data: { status } }` for reliable widget parsing |
207
+
208
+ ## ChatGPT Widget UI
209
+
210
+ The UI module provides a single interactive widget that drives the full authentication flow inside a ChatGPT iframe. Users select a connector, provide any required environment info, authorize via OAuth (opened in a new tab), and see a success confirmation — all without leaving the widget.
211
+
212
+ ### Technology Stack
213
+ - **React 18** with TypeScript
214
+ - **Vite** with `vite-plugin-singlefile` for self-contained HTML
215
+ - **Tailwind CSS 4** with `@openai/apps-sdk-ui` components
216
+ - **OpenAI Apps SDK UI** for consistent ChatGPT styling
217
+
218
+ ### Widget Communication
219
+
220
+ **1. Receiving the initial server context** — via four mechanisms (whichever fires first):
221
+
222
+ | Mechanism | Description |
223
+ |-----------|-------------|
224
+ | `window.openai.toolOutput` | Synchronous read on mount |
225
+ | `openai:set_globals` event | ChatGPT pushes globals into the iframe |
226
+ | `ui/notifications/tool-result` postMessage | MCP Apps bridge notification |
227
+ | Polling `window.openai.toolOutput` | Fallback for async population |
228
+
229
+ The initial payload is `{ serverUrl, rcAccountId, rcExtensionId, openaiSessionId }`.
230
+
231
+ **2. Fetching connectors and manifests** — via direct `fetch()` to `appconnect.labs.ringcentral.com`:
232
+
233
+ ```typescript
234
+ import { fetchConnectors, fetchManifest } from './lib/developerPortal'
235
+
236
+ const connectors = await fetchConnectors(rcAccountId) // list with id, name, displayName
237
+ const manifest = await fetchManifest(connector.id, isPrivate, rcAccountId)
238
+ ```
239
+
240
+ **3. Calling widget tools** — via direct `fetch()` to `POST /mcp/widget-tool-call`:
241
+
242
+ ```typescript
243
+ import { callTool } from './lib/callTool'
244
+
245
+ const result = await callTool('doAuth', { sessionId, connectorName, hostname })
246
+ const status = await callTool('checkAuthStatus', { sessionId, rcExtensionId })
247
+ ```
248
+
249
+ `window.openai.callTool()` is **intentionally not used** for widget tool calls. Direct `fetch()` to `/mcp/widget-tool-call` forwards all arguments correctly and works for both `doAuth` and `checkAuthStatus`.
250
+
251
+ ### Widget Auth Flow
252
+
253
+ The widget (`App.tsx`) acts as a multi-step wizard:
254
+
255
+ ```
256
+ loadingConnectors select loading authInfo (if dynamic/selectable env) oauth success
257
+ → oauth (if fixed env) → error
258
+ ```
259
+
260
+ | Step | Component | Description |
261
+ |------|-----------|-------------|
262
+ | `loadingConnectors` | (spinner) | Widget fetches connector list from developer portal |
263
+ | `select` | `ConnectorList` | Displays available connectors |
264
+ | `loading` | (spinner) | Shown while manifest is being fetched |
265
+ | `authInfo` | `AuthInfoForm` | Collects hostname (dynamic) or environment (selectable). Resolved locally — no server call |
266
+ | `oauth` | `OAuthConnect` | Uses `openaiSessionId` as the OAuth session ID (falls back to `crypto.randomUUID()`). Generates the OAuth URL client-side, calls `doAuth` in background to register the session in the DB, then shows "Authorize" button. After click, polls `checkAuthStatus` every 5 seconds via direct fetch |
267
+ | `success` | `AuthSuccess` | Shows connected CRM name and user info |
268
+ | `error` | (inline) | Shows error with "Back to connector list" link |
269
+
270
+ ### Components
271
+
272
+ #### ConnectorList
273
+ Displays available CRM connectors with public/private badges. On selection, delegates to `App.tsx` which fetches the manifest directly.
274
+
275
+ #### AuthInfoForm
276
+ Form for environment info collection. Handles `dynamic` (text input) and `selectable` (button list) environment types. Hostname resolution happens inline in `App.tsx`.
277
+
278
+ #### OAuthConnect
279
+ Handles the full OAuth step. Uses `openaiSessionId` (from the initial tool output) as the session ID so the OAuth callback can be correlated with the ChatGPT conversation — falls back to `crypto.randomUUID()` when running outside ChatGPT. Calls `doAuth` in the background, shows an "Authorize in [CRM]" button (disabled until session is created), then polls `checkAuthStatus` every 5 seconds via `callTool()` (direct fetch). On success, fires `updateModelContext` to push the jwtToken into ChatGPT's context, then calls `onSuccess`.
280
+
281
+ #### AuthSuccess
282
+ Success banner showing the connected CRM name and optional user info.
283
+
284
+ ### Developer Portal Client (`lib/developerPortal.ts`)
285
+
286
+ Calls `appconnect.labs.ringcentral.com/public-api` directly from the browser:
287
+
288
+ | Function | Description |
289
+ |----------|-------------|
290
+ | `fetchConnectors(rcAccountId?)` | Fetches public + private connectors, filters to `SUPPORTED_PLATFORMS` |
291
+ | `fetchManifest(connectorId, isPrivate, rcAccountId?)` | Fetches connector manifest by ID |
292
+
293
+ `SUPPORTED_PLATFORMS` is defined in this file: `['clio']`.
294
+
295
+ ### Building the Widget
296
+
297
+ **Production build** (generates `dist/index.html` with all JS, CSS, and assets inlined):
298
+
299
+ ```bash
300
+ cd packages/core/mcp/ui
301
+ npm install
302
+ npm run build # Output: dist/index.html (single file)
303
+ ```
304
+
305
+ **Development server** (hot reload for local testing):
306
+
307
+ ```bash
308
+ cd packages/core/mcp/ui
309
+ npm run dev
310
+ ```
311
+
312
+ **Cache busting after a build:** Increment `WIDGET_VERSION` in `mcpHandler.js` and restart the server. That is the only file that needs to change — `getPublicConnectors.js` and the README do not contain a version number.
313
+
314
+ ## API Integration
315
+
316
+ ### Endpoints
317
+
318
+ | Endpoint | Purpose |
319
+ |----------|---------|
320
+ | `POST /mcp` | Full MCP protocol endpoint for AI assistants (ChatGPT, etc.) |
321
+ | `POST /mcp/widget-tool-call` | Lightweight direct tool call for the widget iframe (bypasses MCP session protocol) |
322
+
323
+ #### `POST /mcp/widget-tool-call`
324
+
325
+ Called by the widget via `fetch()` to invoke `doAuth` and `checkAuthStatus` with full argument support.
326
+
327
+ **`doAuth` request:**
328
+ ```json
329
+ {
330
+ "tool": "doAuth",
331
+ "toolArgs": {
332
+ "sessionId": "<openaiSessionId or random UUID>",
333
+ "connectorName": "clio",
334
+ "hostname": "app.clio.com"
335
+ }
336
+ }
337
+ ```
338
+
339
+ **`checkAuthStatus` request:**
340
+ ```json
341
+ {
342
+ "tool": "checkAuthStatus",
343
+ "toolArgs": {
344
+ "sessionId": "<same sessionId used in doAuth>",
345
+ "rcExtensionId": "<from getPublicConnectors structuredContent>"
346
+ }
347
+ }
348
+ ```
349
+
350
+ **Response:** The raw result from `tool.execute()` shape varies by tool.
351
+
352
+ ### Headers (for `/mcp`)
353
+
354
+ | Header | Value |
355
+ |--------|-------|
356
+ | `Content-Type` | `application/json` |
357
+ | `Authorization` | `Bearer <RC_ACCESS_TOKEN>` (optional, for RingCentral API calls) |
358
+
359
+ ## Supported Platforms
360
+
361
+ Currently supported for MCP integration:
362
+ - **Clio** - Legal practice management
363
+
364
+ ## Security Considerations
365
+
366
+ 1. **JWT Tokens**: Stored server-side in `LlmSessionModel`, **keyed by `rcExtensionId`** (a cryptographically verified RingCentral identity). Tools receive the JWT via server injection — it is never sent back to ChatGPT as a visible parameter.
367
+ 2. **RC Identity Verification**: On the first tool call of each session, `mcpHandler.js` calls `GET /restapi/v1.0/extension/~` with the Bearer token from the request. If the RC token is invalid, the call throws and `rcExtensionId` remains `null`. The result is cached in `sessionContext` so at most **one RC API call** is made per conversation.
368
+ 3. **Session key binding**: The sessions Map is keyed on the stable `openai/session` ID so the same `sessionContext` (and its verified `rcExtensionId`) is reused for all tool calls within a ChatGPT conversation. A session can only access credentials stored under its own verified `rcExtensionId`.
369
+ 4. **Session Management**: MCP sessions are server-side and automatically cleaned up on transport close.
370
+ 5. **OAuth Flows**: Uses secure OAuth 2.0 with server-side callback handling.
371
+ 6. **RC Account ID**: Resolved server-side via RC API and passed to the widget — never requires exposing secrets to the browser.
372
+ 7. **CORS**: Widget calls `appconnect.labs.ringcentral.com` directly; the developer portal public API supports browser fetch.
373
+
374
+ | | Before | After |
375
+ |---|---|---|
376
+ | Identity proof | `openai/session` (unverified, from request body) | `rcExtensionId` (verified via RC API) |
377
+ | RC token verified? | No | Yes — first tool call per conversation |
378
+ | API calls per session | 0 (no verify) | 1 (cached after first call) |
379
+ | `LlmSessionModel` key | arbitrary session ID | stable, verified RC extension ID |
380
+
381
+ ## Error Handling
382
+
383
+ All tools return standardized response objects:
384
+
385
+ **Success:**
386
+ ```json
387
+ { "success": true, "data": { ... } }
388
+ ```
389
+
390
+ **Failure:**
391
+ ```json
392
+ { "success": false, "error": "Error message" }
393
+ ```
394
+
395
+ ## Usage Example
396
+
397
+ A typical conversation flow:
398
+
399
+ 1. **User**: "Connect me to my CRM"
400
+ 2. **AI**: Calls `getPublicConnectors` → server verifies RC token, resolves `rcExtensionId` + RC account ID + `openaiSessionId` → shows widget
401
+ 3. **Widget**: Fetches connector list from developer portal → displays connector cards
402
+ 4. **User**: Clicks "Clio" in the widget
403
+ 5. **Widget**: Fetches Clio manifest → shows environment selector (US/EU/AU/CA)
404
+ 6. **User**: Selects region → widget calls `doAuth` with `openaiSessionId` as OAuth session key → shows "Authorize in Clio" button
405
+ 7. **User**: Clicks button → Clio OAuth page opens in new tab → user authorizes
406
+ 8. **Widget**: Polls `checkAuthStatus` every 5 seconds via direct fetch (passes `sessionId` + `rcExtensionId`) → "Waiting for authorization..."
407
+ 9. **Widget**: Auth completes → jwtToken stored in `LlmSessionModel[rcExtensionId]` → widget fires `updateModelContext` with jwtToken → shows success banner
408
+ 10. **AI**: "You're now connected! What would you like to do?"
409
+ 11. **User**: "Find contacts named Test"
410
+ 12. **AI**: Calls `findContactByName(name="Test")` — server injects jwtToken automatically using cached `rcExtensionId` as lookup key → returns results
411
+
412
+ Steps 3–9 happen entirely within the widget iframe. The AI assistant is not involved until authentication is complete.