@memberjunction/server 3.4.0 → 4.0.0

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 (277) hide show
  1. package/dist/agents/skip-agent.d.ts +65 -0
  2. package/dist/agents/skip-agent.d.ts.map +1 -1
  3. package/dist/agents/skip-agent.js +63 -5
  4. package/dist/agents/skip-agent.js.map +1 -1
  5. package/dist/agents/skip-sdk.d.ts +163 -0
  6. package/dist/agents/skip-sdk.d.ts.map +1 -1
  7. package/dist/agents/skip-sdk.js +143 -12
  8. package/dist/agents/skip-sdk.js.map +1 -1
  9. package/dist/apolloServer/TransactionPlugin.d.ts +4 -0
  10. package/dist/apolloServer/TransactionPlugin.d.ts.map +1 -0
  11. package/dist/apolloServer/TransactionPlugin.js +46 -0
  12. package/dist/apolloServer/TransactionPlugin.js.map +1 -0
  13. package/dist/apolloServer/index.d.ts +0 -1
  14. package/dist/apolloServer/index.d.ts.map +1 -1
  15. package/dist/auth/APIKeyScopeAuth.d.ts +82 -0
  16. package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
  17. package/dist/auth/APIKeyScopeAuth.js +78 -0
  18. package/dist/auth/APIKeyScopeAuth.js.map +1 -1
  19. package/dist/auth/AuthProviderFactory.d.ts +35 -0
  20. package/dist/auth/AuthProviderFactory.d.ts.map +1 -1
  21. package/dist/auth/AuthProviderFactory.js +51 -4
  22. package/dist/auth/AuthProviderFactory.js.map +1 -1
  23. package/dist/auth/BaseAuthProvider.d.ts +21 -0
  24. package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
  25. package/dist/auth/BaseAuthProvider.js +24 -9
  26. package/dist/auth/BaseAuthProvider.js.map +1 -1
  27. package/dist/auth/IAuthProvider.d.ts +32 -0
  28. package/dist/auth/IAuthProvider.d.ts.map +1 -1
  29. package/dist/auth/__tests__/backward-compatibility.test.d.ts +2 -0
  30. package/dist/auth/__tests__/backward-compatibility.test.d.ts.map +1 -0
  31. package/dist/auth/__tests__/backward-compatibility.test.js +135 -0
  32. package/dist/auth/__tests__/backward-compatibility.test.js.map +1 -0
  33. package/dist/auth/exampleNewUserSubClass.d.ts +5 -1
  34. package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
  35. package/dist/auth/exampleNewUserSubClass.js +21 -6
  36. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  37. package/dist/auth/index.d.ts +14 -0
  38. package/dist/auth/index.d.ts.map +1 -1
  39. package/dist/auth/index.js +35 -22
  40. package/dist/auth/index.js.map +1 -1
  41. package/dist/auth/initializeProviders.d.ts +3 -0
  42. package/dist/auth/initializeProviders.d.ts.map +1 -1
  43. package/dist/auth/initializeProviders.js +6 -0
  44. package/dist/auth/initializeProviders.js.map +1 -1
  45. package/dist/auth/newUsers.js +11 -2
  46. package/dist/auth/newUsers.js.map +1 -1
  47. package/dist/auth/providers/Auth0Provider.d.ts +9 -0
  48. package/dist/auth/providers/Auth0Provider.d.ts.map +1 -1
  49. package/dist/auth/providers/Auth0Provider.js +10 -0
  50. package/dist/auth/providers/Auth0Provider.js.map +1 -1
  51. package/dist/auth/providers/CognitoProvider.d.ts +9 -0
  52. package/dist/auth/providers/CognitoProvider.d.ts.map +1 -1
  53. package/dist/auth/providers/CognitoProvider.js +10 -0
  54. package/dist/auth/providers/CognitoProvider.js.map +1 -1
  55. package/dist/auth/providers/GoogleProvider.d.ts +9 -0
  56. package/dist/auth/providers/GoogleProvider.d.ts.map +1 -1
  57. package/dist/auth/providers/GoogleProvider.js +11 -1
  58. package/dist/auth/providers/GoogleProvider.js.map +1 -1
  59. package/dist/auth/providers/MSALProvider.d.ts +9 -0
  60. package/dist/auth/providers/MSALProvider.d.ts.map +1 -1
  61. package/dist/auth/providers/MSALProvider.js +10 -0
  62. package/dist/auth/providers/MSALProvider.js.map +1 -1
  63. package/dist/auth/providers/OktaProvider.d.ts +9 -0
  64. package/dist/auth/providers/OktaProvider.d.ts.map +1 -1
  65. package/dist/auth/providers/OktaProvider.js +10 -0
  66. package/dist/auth/providers/OktaProvider.js.map +1 -1
  67. package/dist/config.d.ts +12 -0
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +42 -8
  70. package/dist/config.js.map +1 -1
  71. package/dist/context.d.ts +8 -1
  72. package/dist/context.d.ts.map +1 -1
  73. package/dist/context.js +26 -4
  74. package/dist/context.js.map +1 -1
  75. package/dist/directives/Public.js +2 -0
  76. package/dist/directives/Public.js.map +1 -1
  77. package/dist/entitySubclasses/entityPermissions.server.d.ts +7 -2
  78. package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
  79. package/dist/entitySubclasses/entityPermissions.server.js +26 -8
  80. package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
  81. package/dist/generated/generated.d.ts +529 -6
  82. package/dist/generated/generated.d.ts.map +1 -1
  83. package/dist/generated/generated.js +10054 -15076
  84. package/dist/generated/generated.js.map +1 -1
  85. package/dist/generic/DeleteOptionsInput.d.ts +3 -0
  86. package/dist/generic/DeleteOptionsInput.d.ts.map +1 -1
  87. package/dist/generic/DeleteOptionsInput.js +3 -2
  88. package/dist/generic/DeleteOptionsInput.js.map +1 -1
  89. package/dist/generic/KeyInputOutputTypes.js +0 -6
  90. package/dist/generic/KeyInputOutputTypes.js.map +1 -1
  91. package/dist/generic/KeyValuePairInput.d.ts +4 -0
  92. package/dist/generic/KeyValuePairInput.d.ts.map +1 -1
  93. package/dist/generic/KeyValuePairInput.js +4 -2
  94. package/dist/generic/KeyValuePairInput.js.map +1 -1
  95. package/dist/generic/PushStatusResolver.js +0 -3
  96. package/dist/generic/PushStatusResolver.js.map +1 -1
  97. package/dist/generic/ResolverBase.d.ts +58 -0
  98. package/dist/generic/ResolverBase.d.ts.map +1 -1
  99. package/dist/generic/ResolverBase.js +203 -18
  100. package/dist/generic/ResolverBase.js.map +1 -1
  101. package/dist/generic/RunViewResolver.d.ts +22 -0
  102. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  103. package/dist/generic/RunViewResolver.js +42 -108
  104. package/dist/generic/RunViewResolver.js.map +1 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +82 -37
  107. package/dist/index.js.map +1 -1
  108. package/dist/orm.d.ts.map +1 -1
  109. package/dist/orm.js +2 -1
  110. package/dist/orm.js.map +1 -1
  111. package/dist/resolvers/APIKeyResolver.d.ts +74 -0
  112. package/dist/resolvers/APIKeyResolver.d.ts.map +1 -1
  113. package/dist/resolvers/APIKeyResolver.js +49 -10
  114. package/dist/resolvers/APIKeyResolver.js.map +1 -1
  115. package/dist/resolvers/ActionResolver.d.ts +189 -0
  116. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  117. package/dist/resolvers/ActionResolver.js +152 -21
  118. package/dist/resolvers/ActionResolver.js.map +1 -1
  119. package/dist/resolvers/AskSkipResolver.d.ts +123 -0
  120. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -0
  121. package/dist/resolvers/AskSkipResolver.js +1788 -0
  122. package/dist/resolvers/AskSkipResolver.js.map +1 -0
  123. package/dist/resolvers/ColorResolver.js +0 -5
  124. package/dist/resolvers/ColorResolver.js.map +1 -1
  125. package/dist/resolvers/ComponentRegistryResolver.d.ts +65 -0
  126. package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
  127. package/dist/resolvers/ComponentRegistryResolver.js +118 -40
  128. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  129. package/dist/resolvers/CreateQueryResolver.d.ts +47 -0
  130. package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
  131. package/dist/resolvers/CreateQueryResolver.js +92 -116
  132. package/dist/resolvers/CreateQueryResolver.js.map +1 -1
  133. package/dist/resolvers/DatasetResolver.js +2 -14
  134. package/dist/resolvers/DatasetResolver.js.map +1 -1
  135. package/dist/resolvers/EntityCommunicationsResolver.d.ts +40 -0
  136. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  137. package/dist/resolvers/EntityCommunicationsResolver.js +2 -36
  138. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  139. package/dist/resolvers/EntityRecordNameResolver.js +0 -7
  140. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  141. package/dist/resolvers/FileCategoryResolver.js +13 -1
  142. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  143. package/dist/resolvers/FileResolver.d.ts +16 -0
  144. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  145. package/dist/resolvers/FileResolver.js +59 -74
  146. package/dist/resolvers/FileResolver.js.map +1 -1
  147. package/dist/resolvers/GetDataContextDataResolver.d.ts +18 -1
  148. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  149. package/dist/resolvers/GetDataContextDataResolver.js +17 -9
  150. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  151. package/dist/resolvers/GetDataResolver.d.ts +19 -0
  152. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  153. package/dist/resolvers/GetDataResolver.js +35 -35
  154. package/dist/resolvers/GetDataResolver.js.map +1 -1
  155. package/dist/resolvers/InfoResolver.d.ts.map +1 -1
  156. package/dist/resolvers/InfoResolver.js +4 -7
  157. package/dist/resolvers/InfoResolver.js.map +1 -1
  158. package/dist/resolvers/MCPResolver.d.ts +325 -1
  159. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  160. package/dist/resolvers/MCPResolver.js +931 -24
  161. package/dist/resolvers/MCPResolver.js.map +1 -1
  162. package/dist/resolvers/MergeRecordsResolver.js +3 -29
  163. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  164. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
  165. package/dist/resolvers/PotentialDuplicateRecordResolver.js +0 -3
  166. package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
  167. package/dist/resolvers/QueryResolver.d.ts +20 -0
  168. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  169. package/dist/resolvers/QueryResolver.js +44 -36
  170. package/dist/resolvers/QueryResolver.js.map +1 -1
  171. package/dist/resolvers/ReportResolver.d.ts +3 -0
  172. package/dist/resolvers/ReportResolver.d.ts.map +1 -1
  173. package/dist/resolvers/ReportResolver.js +9 -10
  174. package/dist/resolvers/ReportResolver.js.map +1 -1
  175. package/dist/resolvers/RunAIAgentResolver.d.ts +54 -0
  176. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  177. package/dist/resolvers/RunAIAgentResolver.js +116 -40
  178. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  179. package/dist/resolvers/RunAIPromptResolver.d.ts +42 -0
  180. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  181. package/dist/resolvers/RunAIPromptResolver.js +95 -22
  182. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  183. package/dist/resolvers/RunTemplateResolver.js +9 -6
  184. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  185. package/dist/resolvers/RunTestResolver.d.ts +12 -0
  186. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  187. package/dist/resolvers/RunTestResolver.js +35 -21
  188. package/dist/resolvers/RunTestResolver.js.map +1 -1
  189. package/dist/resolvers/SqlLoggingConfigResolver.d.ts +312 -0
  190. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  191. package/dist/resolvers/SqlLoggingConfigResolver.js +295 -45
  192. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  193. package/dist/resolvers/SyncDataResolver.d.ts +21 -0
  194. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  195. package/dist/resolvers/SyncDataResolver.js +36 -22
  196. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  197. package/dist/resolvers/SyncRolesUsersResolver.d.ts +14 -0
  198. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  199. package/dist/resolvers/SyncRolesUsersResolver.js +54 -21
  200. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  201. package/dist/resolvers/TaskResolver.d.ts +13 -0
  202. package/dist/resolvers/TaskResolver.d.ts.map +1 -1
  203. package/dist/resolvers/TaskResolver.js +22 -7
  204. package/dist/resolvers/TaskResolver.js.map +1 -1
  205. package/dist/resolvers/TelemetryResolver.d.ts +22 -0
  206. package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
  207. package/dist/resolvers/TelemetryResolver.js +45 -79
  208. package/dist/resolvers/TelemetryResolver.js.map +1 -1
  209. package/dist/resolvers/TransactionGroupResolver.js +11 -13
  210. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  211. package/dist/resolvers/UserFavoriteResolver.js +3 -12
  212. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  213. package/dist/resolvers/UserResolver.js +10 -0
  214. package/dist/resolvers/UserResolver.js.map +1 -1
  215. package/dist/resolvers/UserViewResolver.js +4 -0
  216. package/dist/resolvers/UserViewResolver.js.map +1 -1
  217. package/dist/resolvers/VersionHistoryResolver.d.ts +39 -0
  218. package/dist/resolvers/VersionHistoryResolver.d.ts.map +1 -0
  219. package/dist/resolvers/VersionHistoryResolver.js +208 -0
  220. package/dist/resolvers/VersionHistoryResolver.js.map +1 -0
  221. package/dist/rest/EntityCRUDHandler.d.ts +19 -0
  222. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  223. package/dist/rest/EntityCRUDHandler.js +55 -0
  224. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  225. package/dist/rest/OAuthCallbackHandler.d.ts +143 -0
  226. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -0
  227. package/dist/rest/OAuthCallbackHandler.js +634 -0
  228. package/dist/rest/OAuthCallbackHandler.js.map +1 -0
  229. package/dist/rest/RESTEndpointHandler.d.ts +120 -0
  230. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  231. package/dist/rest/RESTEndpointHandler.js +213 -24
  232. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  233. package/dist/rest/ViewOperationsHandler.d.ts +19 -0
  234. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  235. package/dist/rest/ViewOperationsHandler.js +39 -0
  236. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  237. package/dist/rest/index.d.ts +1 -0
  238. package/dist/rest/index.d.ts.map +1 -1
  239. package/dist/rest/index.js +1 -0
  240. package/dist/rest/index.js.map +1 -1
  241. package/dist/rest/setupRESTEndpoints.d.ts +35 -0
  242. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  243. package/dist/rest/setupRESTEndpoints.js +15 -1
  244. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  245. package/dist/scheduler/LearningCycleScheduler.d.ts +4 -0
  246. package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -0
  247. package/dist/scheduler/LearningCycleScheduler.js +4 -0
  248. package/dist/scheduler/LearningCycleScheduler.js.map +1 -0
  249. package/dist/services/ScheduledJobsService.d.ts +31 -0
  250. package/dist/services/ScheduledJobsService.d.ts.map +1 -1
  251. package/dist/services/ScheduledJobsService.js +38 -4
  252. package/dist/services/ScheduledJobsService.js.map +1 -1
  253. package/dist/services/TaskOrchestrator.d.ts +73 -0
  254. package/dist/services/TaskOrchestrator.d.ts.map +1 -1
  255. package/dist/services/TaskOrchestrator.js +137 -15
  256. package/dist/services/TaskOrchestrator.js.map +1 -1
  257. package/dist/types.d.ts +14 -0
  258. package/dist/types.d.ts.map +1 -1
  259. package/dist/types.js +0 -13
  260. package/dist/types.js.map +1 -1
  261. package/dist/util.d.ts +37 -1
  262. package/dist/util.d.ts.map +1 -1
  263. package/dist/util.js +55 -8
  264. package/dist/util.js.map +1 -1
  265. package/package.json +79 -78
  266. package/src/auth/exampleNewUserSubClass.ts +1 -5
  267. package/src/entitySubclasses/entityPermissions.server.ts +1 -3
  268. package/src/generated/generated.ts +4682 -2681
  269. package/src/index.ts +61 -62
  270. package/src/resolvers/InfoResolver.ts +5 -1
  271. package/src/resolvers/MCPResolver.ts +910 -10
  272. package/src/resolvers/PotentialDuplicateRecordResolver.ts +0 -4
  273. package/src/resolvers/VersionHistoryResolver.ts +177 -0
  274. package/src/rest/OAuthCallbackHandler.ts +766 -0
  275. package/src/rest/RESTEndpointHandler.ts +58 -35
  276. package/src/rest/index.ts +2 -1
  277. package/src/rest/setupRESTEndpoints.ts +13 -12
@@ -2,17 +2,26 @@
2
2
  * @fileoverview MCP GraphQL Resolver
3
3
  *
4
4
  * Provides GraphQL mutations for MCP (Model Context Protocol) operations
5
- * including tool synchronization with progress streaming.
5
+ * including tool synchronization with progress streaming and OAuth management.
6
6
  */
7
7
 
8
- import { Resolver, Mutation, Arg, Ctx, Field, ObjectType, InputType, PubSub } from 'type-graphql';
8
+ import { Resolver, Mutation, Query, Subscription, Arg, Ctx, Root, Field, ObjectType, InputType, PubSub, registerEnumType } from 'type-graphql';
9
9
  import { PubSubEngine } from 'type-graphql';
10
- import { LogError, LogStatus, UserInfo } from '@memberjunction/core';
11
- import { MCPClientManager, MCPSyncToolsResult, MCPToolCallResult } from '@memberjunction/ai-mcp-client';
10
+ import { LogError, LogStatus, UserInfo, Metadata, RunView } from '@memberjunction/core';
11
+ import {
12
+ MCPClientManager,
13
+ MCPSyncToolsResult,
14
+ MCPToolCallResult,
15
+ OAuthAuthorizationRequiredError,
16
+ OAuthReauthorizationRequiredError,
17
+ OAuthManager,
18
+ TokenManager
19
+ } from '@memberjunction/ai-mcp-client';
12
20
  import { AppContext } from '../types.js';
13
21
  import { ResolverBase } from '../generic/ResolverBase.js';
14
22
  import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
15
23
  import { GraphQLJSONObject } from 'graphql-type-json';
24
+ import { configInfo } from '../config.js';
16
25
 
17
26
  /**
18
27
  * Input type for syncing MCP tools
@@ -84,6 +93,36 @@ export class SyncMCPToolsResult {
84
93
  */
85
94
  @Field({ nullable: true })
86
95
  ConnectionName?: string;
96
+
97
+ /**
98
+ * Whether OAuth authorization is required before connecting
99
+ */
100
+ @Field({ nullable: true })
101
+ RequiresOAuth?: boolean;
102
+
103
+ /**
104
+ * OAuth authorization URL if authorization is required
105
+ */
106
+ @Field({ nullable: true })
107
+ AuthorizationUrl?: string;
108
+
109
+ /**
110
+ * OAuth state parameter for tracking the authorization flow
111
+ */
112
+ @Field({ nullable: true })
113
+ StateParameter?: string;
114
+
115
+ /**
116
+ * Whether OAuth re-authorization is required
117
+ */
118
+ @Field({ nullable: true })
119
+ RequiresReauthorization?: boolean;
120
+
121
+ /**
122
+ * Reason for re-authorization if required
123
+ */
124
+ @Field({ nullable: true })
125
+ ReauthorizationReason?: string;
87
126
  }
88
127
 
89
128
  /**
@@ -169,6 +208,253 @@ interface SyncProgressMessage {
169
208
  };
170
209
  }
171
210
 
211
+ // ============================================================================
212
+ // OAuth GraphQL Types
213
+ // ============================================================================
214
+
215
+ /**
216
+ * OAuth authorization status enum
217
+ */
218
+ enum MCPOAuthStatus {
219
+ PENDING = 'PENDING',
220
+ COMPLETED = 'COMPLETED',
221
+ FAILED = 'FAILED',
222
+ EXPIRED = 'EXPIRED'
223
+ }
224
+
225
+ // Register the enum with TypeGraphQL
226
+ registerEnumType(MCPOAuthStatus, {
227
+ name: 'MCPOAuthStatus',
228
+ description: 'Status of an OAuth authorization flow'
229
+ });
230
+
231
+ /**
232
+ * Input for initiating OAuth authorization
233
+ */
234
+ @InputType()
235
+ export class InitiateMCPOAuthInput {
236
+ @Field()
237
+ ConnectionID: string;
238
+
239
+ @Field({ nullable: true })
240
+ AdditionalScopes?: string;
241
+
242
+ @Field({ nullable: true, description: 'Frontend URL to use as redirect_uri. When provided, the frontend handles the OAuth callback instead of the API server.' })
243
+ FrontendCallbackUrl?: string;
244
+ }
245
+
246
+ /**
247
+ * Input for checking OAuth status
248
+ */
249
+ @InputType()
250
+ export class GetMCPOAuthStatusInput {
251
+ @Field()
252
+ StateParameter: string;
253
+ }
254
+
255
+ /**
256
+ * Input for revoking OAuth credentials
257
+ */
258
+ @InputType()
259
+ export class RevokeMCPOAuthInput {
260
+ @Field()
261
+ ConnectionID: string;
262
+
263
+ @Field({ nullable: true })
264
+ Reason?: string;
265
+ }
266
+
267
+ /**
268
+ * Input for refreshing OAuth tokens
269
+ */
270
+ @InputType()
271
+ export class RefreshMCPOAuthTokenInput {
272
+ @Field()
273
+ ConnectionID: string;
274
+ }
275
+
276
+ /**
277
+ * Result from initiating OAuth authorization
278
+ */
279
+ @ObjectType()
280
+ export class InitiateMCPOAuthResult {
281
+ @Field()
282
+ Success: boolean;
283
+
284
+ @Field({ nullable: true })
285
+ ErrorMessage?: string;
286
+
287
+ @Field({ nullable: true })
288
+ AuthorizationUrl?: string;
289
+
290
+ @Field({ nullable: true })
291
+ StateParameter?: string;
292
+
293
+ @Field({ nullable: true })
294
+ ExpiresAt?: Date;
295
+
296
+ @Field({ nullable: true })
297
+ UsedDynamicRegistration?: boolean;
298
+ }
299
+
300
+ /**
301
+ * Result from checking OAuth status
302
+ */
303
+ @ObjectType()
304
+ export class MCPOAuthStatusResult {
305
+ @Field()
306
+ Success: boolean;
307
+
308
+ @Field({ nullable: true })
309
+ ErrorMessage?: string;
310
+
311
+ @Field(() => MCPOAuthStatus, { nullable: true })
312
+ Status?: MCPOAuthStatus;
313
+
314
+ @Field({ nullable: true })
315
+ ConnectionID?: string;
316
+
317
+ @Field({ nullable: true })
318
+ InitiatedAt?: Date;
319
+
320
+ @Field({ nullable: true })
321
+ CompletedAt?: Date;
322
+
323
+ @Field({ nullable: true })
324
+ AuthErrorCode?: string;
325
+
326
+ @Field({ nullable: true })
327
+ AuthErrorDescription?: string;
328
+
329
+ @Field({ nullable: true })
330
+ IsRetryable?: boolean;
331
+ }
332
+
333
+ /**
334
+ * Result from revoking OAuth credentials
335
+ */
336
+ @ObjectType()
337
+ export class RevokeMCPOAuthResult {
338
+ @Field()
339
+ Success: boolean;
340
+
341
+ @Field({ nullable: true })
342
+ ErrorMessage?: string;
343
+
344
+ @Field({ nullable: true })
345
+ ConnectionID?: string;
346
+ }
347
+
348
+ /**
349
+ * Result from refreshing OAuth tokens
350
+ */
351
+ @ObjectType()
352
+ export class RefreshMCPOAuthTokenResult {
353
+ @Field()
354
+ Success: boolean;
355
+
356
+ @Field({ nullable: true })
357
+ ErrorMessage?: string;
358
+
359
+ @Field({ nullable: true })
360
+ ExpiresAt?: Date;
361
+
362
+ @Field({ nullable: true })
363
+ RequiresReauthorization?: boolean;
364
+ }
365
+
366
+ /**
367
+ * OAuth connection status information
368
+ */
369
+ @ObjectType()
370
+ export class MCPOAuthConnectionStatus {
371
+ @Field()
372
+ ConnectionID: string;
373
+
374
+ @Field()
375
+ IsOAuthEnabled: boolean;
376
+
377
+ @Field()
378
+ HasValidTokens: boolean;
379
+
380
+ @Field({ nullable: true })
381
+ IsAccessTokenExpired?: boolean;
382
+
383
+ @Field({ nullable: true })
384
+ TokenExpiresAt?: Date;
385
+
386
+ @Field({ nullable: true })
387
+ HasRefreshToken?: boolean;
388
+
389
+ @Field()
390
+ RequiresReauthorization: boolean;
391
+
392
+ @Field({ nullable: true })
393
+ ReauthorizationReason?: string;
394
+
395
+ @Field({ nullable: true })
396
+ IssuerUrl?: string;
397
+
398
+ @Field({ nullable: true })
399
+ GrantedScopes?: string;
400
+ }
401
+
402
+ // ========================================
403
+ // Subscription Topics and Types
404
+ // ========================================
405
+
406
+ /** PubSub topic for MCP OAuth events */
407
+ export const MCP_OAUTH_EVENTS_TOPIC = 'MCP_OAUTH_EVENTS';
408
+
409
+ /**
410
+ * OAuth event types for subscriptions
411
+ */
412
+ export type MCPOAuthEventType =
413
+ | 'authorizationRequired'
414
+ | 'authorizationCompleted'
415
+ | 'tokenRefreshed'
416
+ | 'tokenRefreshFailed';
417
+
418
+ /**
419
+ * Payload interface for OAuth subscription events
420
+ */
421
+ export interface MCPOAuthEventPayload {
422
+ eventType: MCPOAuthEventType;
423
+ connectionId: string;
424
+ timestamp: string;
425
+ authorizationUrl?: string;
426
+ stateParameter?: string;
427
+ errorMessage?: string;
428
+ requiresReauthorization?: boolean;
429
+ }
430
+
431
+ /**
432
+ * Notification type for OAuth events
433
+ */
434
+ @ObjectType()
435
+ export class MCPOAuthEventNotification {
436
+ @Field()
437
+ EventType: string;
438
+
439
+ @Field()
440
+ ConnectionID: string;
441
+
442
+ @Field()
443
+ Timestamp: Date;
444
+
445
+ @Field({ nullable: true })
446
+ AuthorizationUrl?: string;
447
+
448
+ @Field({ nullable: true })
449
+ StateParameter?: string;
450
+
451
+ @Field({ nullable: true })
452
+ ErrorMessage?: string;
453
+
454
+ @Field({ nullable: true })
455
+ RequiresReauthorization?: boolean;
456
+ }
457
+
172
458
  /**
173
459
  * MCP Resolver for GraphQL operations
174
460
  */
@@ -203,7 +489,8 @@ export class MCPResolver extends ResolverBase {
203
489
 
204
490
  // Get the MCP client manager instance and ensure it's initialized
205
491
  const manager = MCPClientManager.Instance;
206
- await manager.initialize(user);
492
+ const publicUrl = this.getPublicUrl();
493
+ await manager.initialize(user, { publicUrl });
207
494
 
208
495
  // Publish initial progress
209
496
  this.publishProgress(pubSub, sessionId, ConnectionID, 'connecting', 'Connecting to MCP server...');
@@ -215,6 +502,19 @@ export class MCPResolver extends ResolverBase {
215
502
  try {
216
503
  await manager.connect(ConnectionID, { contextUser: user });
217
504
  } catch (connectError) {
505
+ // Check for OAuth authorization required
506
+ if (connectError instanceof OAuthAuthorizationRequiredError) {
507
+ const authError = connectError as OAuthAuthorizationRequiredError;
508
+ this.publishProgress(pubSub, sessionId, ConnectionID, 'error',
509
+ `OAuth authorization required. Please authorize at: ${authError.authorizationUrl}`);
510
+ return this.createOAuthRequiredResult(authError.authorizationUrl, authError.stateParameter);
511
+ }
512
+ if (connectError instanceof OAuthReauthorizationRequiredError) {
513
+ const reAuthError = connectError as OAuthReauthorizationRequiredError;
514
+ this.publishProgress(pubSub, sessionId, ConnectionID, 'error',
515
+ `OAuth re-authorization required: ${reAuthError.reason}`);
516
+ return this.createOAuthReauthorizationResult(reAuthError.reason, reAuthError.authorizationUrl, reAuthError.stateParameter);
517
+ }
218
518
  const connectErrorMsg = connectError instanceof Error ? connectError.message : String(connectError);
219
519
  this.publishProgress(pubSub, sessionId, ConnectionID, 'error', `Connection failed: ${connectErrorMsg}`);
220
520
  return this.createErrorResult(`Failed to connect to MCP server: ${connectErrorMsg}`);
@@ -322,7 +622,8 @@ export class MCPResolver extends ResolverBase {
322
622
  // Get the MCP client manager instance and ensure it's initialized
323
623
  LogStatus(`MCPResolver: [${ToolName}] Step 2 - Initializing MCP client manager...`);
324
624
  const manager = MCPClientManager.Instance;
325
- await manager.initialize(user);
625
+ const publicUrl = this.getPublicUrl();
626
+ await manager.initialize(user, { publicUrl });
326
627
  LogStatus(`MCPResolver: [${ToolName}] Step 2 complete - Manager initialized (${Date.now() - startTime}ms)`);
327
628
 
328
629
  // Connect if not already connected
@@ -334,6 +635,34 @@ export class MCPResolver extends ResolverBase {
334
635
  await manager.connect(ConnectionID, { contextUser: user });
335
636
  LogStatus(`MCPResolver: [${ToolName}] Step 3 complete - Connected (${Date.now() - startTime}ms)`);
336
637
  } catch (connectError) {
638
+ // Check for OAuth authorization required
639
+ if (connectError instanceof OAuthAuthorizationRequiredError) {
640
+ const authError = connectError as OAuthAuthorizationRequiredError;
641
+ LogError(`MCPResolver: [${ToolName}] OAuth authorization required`);
642
+ return {
643
+ Success: false,
644
+ ErrorMessage: `OAuth authorization required. Please authorize at: ${authError.authorizationUrl}`,
645
+ Result: {
646
+ requiresOAuth: true,
647
+ authorizationUrl: authError.authorizationUrl,
648
+ stateParameter: authError.stateParameter
649
+ }
650
+ };
651
+ }
652
+ if (connectError instanceof OAuthReauthorizationRequiredError) {
653
+ const reAuthError = connectError as OAuthReauthorizationRequiredError;
654
+ LogError(`MCPResolver: [${ToolName}] OAuth re-authorization required: ${reAuthError.reason}`);
655
+ return {
656
+ Success: false,
657
+ ErrorMessage: `OAuth re-authorization required: ${reAuthError.reason}`,
658
+ Result: {
659
+ requiresReauthorization: true,
660
+ reason: reAuthError.reason,
661
+ authorizationUrl: reAuthError.authorizationUrl,
662
+ stateParameter: reAuthError.stateParameter
663
+ }
664
+ };
665
+ }
337
666
  const connectErrorMsg = connectError instanceof Error ? connectError.message : String(connectError);
338
667
  LogError(`MCPResolver: [${ToolName}] Connection failed: ${connectErrorMsg}`);
339
668
  return {
@@ -430,6 +759,492 @@ export class MCPResolver extends ResolverBase {
430
759
  }
431
760
  }
432
761
 
762
+ // ========================================================================
763
+ // OAuth Operations
764
+ // ========================================================================
765
+
766
+ /**
767
+ * Gets OAuth connection status for an MCP connection.
768
+ *
769
+ * @param connectionId - The MCP Server Connection ID
770
+ * @param ctx - GraphQL context
771
+ * @returns OAuth connection status
772
+ */
773
+ @Query(() => MCPOAuthConnectionStatus)
774
+ async GetMCPOAuthConnectionStatus(
775
+ @Arg('ConnectionID') connectionId: string,
776
+ @Ctx() ctx: AppContext
777
+ ): Promise<MCPOAuthConnectionStatus> {
778
+ const user = ctx.userPayload.userRecord;
779
+ if (!user) {
780
+ return {
781
+ ConnectionID: connectionId,
782
+ IsOAuthEnabled: false,
783
+ HasValidTokens: false,
784
+ RequiresReauthorization: false,
785
+ ReauthorizationReason: 'User is not authenticated'
786
+ };
787
+ }
788
+
789
+ try {
790
+ // Load connection and server config
791
+ const config = await this.loadConnectionOAuthConfig(connectionId, user);
792
+
793
+ if (!config) {
794
+ return {
795
+ ConnectionID: connectionId,
796
+ IsOAuthEnabled: false,
797
+ HasValidTokens: false,
798
+ RequiresReauthorization: false,
799
+ ReauthorizationReason: 'Connection not found'
800
+ };
801
+ }
802
+
803
+ if (!config.OAuthIssuerURL) {
804
+ return {
805
+ ConnectionID: connectionId,
806
+ IsOAuthEnabled: false,
807
+ HasValidTokens: false,
808
+ RequiresReauthorization: false
809
+ };
810
+ }
811
+
812
+ // Get status from OAuthManager
813
+ const oauthManager = new OAuthManager();
814
+ const status = await oauthManager.getConnectionStatus(connectionId, config, user);
815
+
816
+ return {
817
+ ConnectionID: status.connectionId,
818
+ IsOAuthEnabled: status.isOAuthEnabled,
819
+ HasValidTokens: status.hasValidTokens,
820
+ IsAccessTokenExpired: status.isAccessTokenExpired,
821
+ TokenExpiresAt: status.tokenExpiresAt,
822
+ HasRefreshToken: status.hasRefreshToken,
823
+ RequiresReauthorization: status.requiresReauthorization,
824
+ ReauthorizationReason: status.reauthorizationReason,
825
+ IssuerUrl: status.issuerUrl,
826
+ GrantedScopes: status.grantedScopes
827
+ };
828
+ } catch (error) {
829
+ const errorMsg = error instanceof Error ? error.message : String(error);
830
+ LogError(`MCPResolver: GetMCPOAuthConnectionStatus failed: ${errorMsg}`);
831
+ return {
832
+ ConnectionID: connectionId,
833
+ IsOAuthEnabled: false,
834
+ HasValidTokens: false,
835
+ RequiresReauthorization: true,
836
+ ReauthorizationReason: errorMsg
837
+ };
838
+ }
839
+ }
840
+
841
+ /**
842
+ * Gets OAuth authorization flow status by state parameter.
843
+ *
844
+ * @param input - Input containing state parameter
845
+ * @param ctx - GraphQL context
846
+ * @returns OAuth status result
847
+ */
848
+ @Query(() => MCPOAuthStatusResult)
849
+ async GetMCPOAuthStatus(
850
+ @Arg('input') input: GetMCPOAuthStatusInput,
851
+ @Ctx() ctx: AppContext
852
+ ): Promise<MCPOAuthStatusResult> {
853
+ const user = ctx.userPayload.userRecord;
854
+ if (!user) {
855
+ return {
856
+ Success: false,
857
+ ErrorMessage: 'User is not authenticated'
858
+ };
859
+ }
860
+
861
+ try {
862
+ const rv = new RunView();
863
+ const result = await rv.RunView<{
864
+ ID: string;
865
+ MCPServerConnectionID: string;
866
+ Status: string;
867
+ InitiatedAt: Date;
868
+ CompletedAt: Date | null;
869
+ ErrorCode: string | null;
870
+ ErrorDescription: string | null;
871
+ }>({
872
+ EntityName: 'MJ: O Auth Authorization States',
873
+ ExtraFilter: `StateParameter='${input.StateParameter.replace(/'/g, "''")}'`,
874
+ ResultType: 'simple'
875
+ }, user);
876
+
877
+ if (!result.Success || !result.Results || result.Results.length === 0) {
878
+ return {
879
+ Success: false,
880
+ ErrorMessage: 'Authorization state not found',
881
+ IsRetryable: true
882
+ };
883
+ }
884
+
885
+ const state = result.Results[0];
886
+ const statusMap: Record<string, MCPOAuthStatus> = {
887
+ 'Pending': MCPOAuthStatus.PENDING,
888
+ 'Completed': MCPOAuthStatus.COMPLETED,
889
+ 'Failed': MCPOAuthStatus.FAILED,
890
+ 'Expired': MCPOAuthStatus.EXPIRED
891
+ };
892
+
893
+ return {
894
+ Success: true,
895
+ Status: statusMap[state.Status] ?? MCPOAuthStatus.PENDING,
896
+ ConnectionID: state.MCPServerConnectionID,
897
+ InitiatedAt: new Date(state.InitiatedAt),
898
+ CompletedAt: state.CompletedAt ? new Date(state.CompletedAt) : undefined,
899
+ AuthErrorCode: state.ErrorCode ?? undefined,
900
+ AuthErrorDescription: state.ErrorDescription ?? undefined,
901
+ IsRetryable: state.Status === 'Failed' || state.Status === 'Expired'
902
+ };
903
+ } catch (error) {
904
+ const errorMsg = error instanceof Error ? error.message : String(error);
905
+ LogError(`MCPResolver: GetMCPOAuthStatus failed: ${errorMsg}`);
906
+ return {
907
+ Success: false,
908
+ ErrorMessage: errorMsg
909
+ };
910
+ }
911
+ }
912
+
913
+ /**
914
+ * Initiates an OAuth authorization flow for an MCP connection.
915
+ *
916
+ * @param input - Input containing connection ID and optional scopes
917
+ * @param ctx - GraphQL context
918
+ * @returns Initiation result with authorization URL
919
+ */
920
+ @Mutation(() => InitiateMCPOAuthResult)
921
+ async InitiateMCPOAuth(
922
+ @Arg('input') input: InitiateMCPOAuthInput,
923
+ @Ctx() ctx: AppContext
924
+ ): Promise<InitiateMCPOAuthResult> {
925
+ const user = ctx.userPayload.userRecord;
926
+ if (!user) {
927
+ return {
928
+ Success: false,
929
+ ErrorMessage: 'User is not authenticated'
930
+ };
931
+ }
932
+
933
+ try {
934
+ // Check API key scope authorization
935
+ await this.CheckAPIKeyScopeAuthorization('mcp:oauth', input.ConnectionID, ctx.userPayload);
936
+
937
+ // Load connection and server config
938
+ const config = await this.loadConnectionOAuthConfig(input.ConnectionID, user);
939
+
940
+ if (!config) {
941
+ return {
942
+ Success: false,
943
+ ErrorMessage: 'Connection not found'
944
+ };
945
+ }
946
+
947
+ if (!config.OAuthIssuerURL) {
948
+ return {
949
+ Success: false,
950
+ ErrorMessage: 'OAuth is not configured for this connection'
951
+ };
952
+ }
953
+
954
+ // Merge additional scopes if provided
955
+ let scopes = config.OAuthScopes;
956
+ if (input.AdditionalScopes) {
957
+ scopes = scopes
958
+ ? `${scopes} ${input.AdditionalScopes}`
959
+ : input.AdditionalScopes;
960
+ }
961
+
962
+ const oauthConfig = {
963
+ ...config,
964
+ OAuthScopes: scopes
965
+ };
966
+
967
+ // Initiate the OAuth flow
968
+ const oauthManager = new OAuthManager();
969
+ const publicUrl = this.getPublicUrl();
970
+
971
+ // Build options for the OAuth flow
972
+ const oauthOptions: { frontendReturnUrl?: string; frontendCallbackUrl?: string } = {};
973
+ if (input.FrontendCallbackUrl) {
974
+ oauthOptions.frontendCallbackUrl = input.FrontendCallbackUrl;
975
+ }
976
+
977
+ const result = await oauthManager.initiateAuthorizationFlow(
978
+ input.ConnectionID,
979
+ config.MCPServerID,
980
+ oauthConfig,
981
+ publicUrl,
982
+ user,
983
+ Object.keys(oauthOptions).length > 0 ? oauthOptions : undefined
984
+ );
985
+
986
+ LogStatus(`MCPResolver: Initiated OAuth flow for connection ${input.ConnectionID}`);
987
+
988
+ return {
989
+ Success: result.success,
990
+ ErrorMessage: result.errorMessage,
991
+ AuthorizationUrl: result.authorizationUrl,
992
+ StateParameter: result.stateParameter,
993
+ ExpiresAt: result.expiresAt,
994
+ UsedDynamicRegistration: result.usedDynamicRegistration
995
+ };
996
+ } catch (error) {
997
+ const errorMsg = error instanceof Error ? error.message : String(error);
998
+ LogError(`MCPResolver: InitiateMCPOAuth failed: ${errorMsg}`);
999
+ return {
1000
+ Success: false,
1001
+ ErrorMessage: errorMsg
1002
+ };
1003
+ }
1004
+ }
1005
+
1006
+ /**
1007
+ * Revokes OAuth credentials for an MCP connection.
1008
+ *
1009
+ * @param input - Input containing connection ID and optional reason
1010
+ * @param ctx - GraphQL context
1011
+ * @returns Revocation result
1012
+ */
1013
+ @Mutation(() => RevokeMCPOAuthResult)
1014
+ async RevokeMCPOAuth(
1015
+ @Arg('input') input: RevokeMCPOAuthInput,
1016
+ @Ctx() ctx: AppContext
1017
+ ): Promise<RevokeMCPOAuthResult> {
1018
+ const user = ctx.userPayload.userRecord;
1019
+ if (!user) {
1020
+ return {
1021
+ Success: false,
1022
+ ErrorMessage: 'User is not authenticated'
1023
+ };
1024
+ }
1025
+
1026
+ try {
1027
+ // Check API key scope authorization
1028
+ await this.CheckAPIKeyScopeAuthorization('mcp:oauth', input.ConnectionID, ctx.userPayload);
1029
+
1030
+ // Revoke the credentials
1031
+ const tokenManager = new TokenManager();
1032
+ await tokenManager.revokeCredentials(input.ConnectionID, user);
1033
+
1034
+ LogStatus(`MCPResolver: Revoked OAuth credentials for connection ${input.ConnectionID}${input.Reason ? ` (reason: ${input.Reason})` : ''}`);
1035
+
1036
+ return {
1037
+ Success: true,
1038
+ ConnectionID: input.ConnectionID
1039
+ };
1040
+ } catch (error) {
1041
+ const errorMsg = error instanceof Error ? error.message : String(error);
1042
+ LogError(`MCPResolver: RevokeMCPOAuth failed: ${errorMsg}`);
1043
+ return {
1044
+ Success: false,
1045
+ ErrorMessage: errorMsg,
1046
+ ConnectionID: input.ConnectionID
1047
+ };
1048
+ }
1049
+ }
1050
+
1051
+ /**
1052
+ * Manually refreshes OAuth tokens for an MCP connection.
1053
+ *
1054
+ * @param input - Input containing connection ID
1055
+ * @param ctx - GraphQL context
1056
+ * @returns Refresh result
1057
+ */
1058
+ @Mutation(() => RefreshMCPOAuthTokenResult)
1059
+ async RefreshMCPOAuthToken(
1060
+ @Arg('input') input: RefreshMCPOAuthTokenInput,
1061
+ @Ctx() ctx: AppContext
1062
+ ): Promise<RefreshMCPOAuthTokenResult> {
1063
+ const user = ctx.userPayload.userRecord;
1064
+ if (!user) {
1065
+ return {
1066
+ Success: false,
1067
+ ErrorMessage: 'User is not authenticated'
1068
+ };
1069
+ }
1070
+
1071
+ try {
1072
+ // Check API key scope authorization
1073
+ await this.CheckAPIKeyScopeAuthorization('mcp:oauth', input.ConnectionID, ctx.userPayload);
1074
+
1075
+ // Load connection config
1076
+ const config = await this.loadConnectionOAuthConfig(input.ConnectionID, user);
1077
+
1078
+ if (!config) {
1079
+ return {
1080
+ Success: false,
1081
+ ErrorMessage: 'Connection not found'
1082
+ };
1083
+ }
1084
+
1085
+ if (!config.OAuthIssuerURL) {
1086
+ return {
1087
+ Success: false,
1088
+ ErrorMessage: 'OAuth is not configured for this connection'
1089
+ };
1090
+ }
1091
+
1092
+ // Get the MCP client manager instance
1093
+ const manager = MCPClientManager.Instance;
1094
+ const publicUrl = this.getPublicUrl();
1095
+ await manager.initialize(user, { publicUrl });
1096
+
1097
+ // Try to get access token (will refresh if needed)
1098
+ const oauthManager = new OAuthManager();
1099
+ try {
1100
+ await oauthManager.getAccessToken(
1101
+ input.ConnectionID,
1102
+ config.MCPServerID,
1103
+ config,
1104
+ publicUrl,
1105
+ user
1106
+ );
1107
+
1108
+ // Get updated status
1109
+ const status = await oauthManager.getConnectionStatus(input.ConnectionID, config, user);
1110
+
1111
+ LogStatus(`MCPResolver: Refreshed OAuth tokens for connection ${input.ConnectionID}`);
1112
+
1113
+ return {
1114
+ Success: true,
1115
+ ExpiresAt: status.tokenExpiresAt,
1116
+ RequiresReauthorization: false
1117
+ };
1118
+ } catch (error) {
1119
+ if (error instanceof OAuthAuthorizationRequiredError ||
1120
+ error instanceof OAuthReauthorizationRequiredError) {
1121
+ return {
1122
+ Success: false,
1123
+ ErrorMessage: error.message,
1124
+ RequiresReauthorization: true
1125
+ };
1126
+ }
1127
+ throw error;
1128
+ }
1129
+ } catch (error) {
1130
+ const errorMsg = error instanceof Error ? error.message : String(error);
1131
+ LogError(`MCPResolver: RefreshMCPOAuthToken failed: ${errorMsg}`);
1132
+ return {
1133
+ Success: false,
1134
+ ErrorMessage: errorMsg
1135
+ };
1136
+ }
1137
+ }
1138
+
1139
+ // ========================================================================
1140
+ // Subscriptions
1141
+ // ========================================================================
1142
+
1143
+ /**
1144
+ * Subscribes to OAuth events for MCP connections.
1145
+ *
1146
+ * Clients can subscribe to receive real-time notifications when:
1147
+ * - Authorization is required for a connection
1148
+ * - Authorization completes successfully
1149
+ * - Token is refreshed
1150
+ * - Token refresh fails
1151
+ *
1152
+ * @param payload - The OAuth event payload
1153
+ * @returns OAuth event notification
1154
+ */
1155
+ @Subscription(() => MCPOAuthEventNotification, { topics: MCP_OAUTH_EVENTS_TOPIC })
1156
+ onMCPOAuthEvent(
1157
+ @Root() payload: MCPOAuthEventPayload
1158
+ ): MCPOAuthEventNotification {
1159
+ return {
1160
+ EventType: payload.eventType,
1161
+ ConnectionID: payload.connectionId,
1162
+ Timestamp: new Date(payload.timestamp),
1163
+ AuthorizationUrl: payload.authorizationUrl,
1164
+ StateParameter: payload.stateParameter,
1165
+ ErrorMessage: payload.errorMessage,
1166
+ RequiresReauthorization: payload.requiresReauthorization
1167
+ };
1168
+ }
1169
+
1170
+ // ========================================================================
1171
+ // Private Helper Methods
1172
+ // ========================================================================
1173
+
1174
+ /**
1175
+ * Loads OAuth configuration for a connection
1176
+ */
1177
+ private async loadConnectionOAuthConfig(
1178
+ connectionId: string,
1179
+ contextUser: UserInfo
1180
+ ): Promise<{
1181
+ MCPServerID: string;
1182
+ OAuthIssuerURL?: string;
1183
+ OAuthScopes?: string;
1184
+ OAuthMetadataCacheTTLMinutes?: number;
1185
+ OAuthClientID?: string;
1186
+ OAuthClientSecretEncrypted?: string;
1187
+ OAuthRequirePKCE?: boolean;
1188
+ } | null> {
1189
+ try {
1190
+ const rv = new RunView();
1191
+
1192
+ // First get connection to get server ID
1193
+ const connResult = await rv.RunView<{ MCPServerID: string }>({
1194
+ EntityName: 'MJ: MCP Server Connections',
1195
+ ExtraFilter: `ID='${connectionId}'`,
1196
+ Fields: ['MCPServerID'],
1197
+ ResultType: 'simple'
1198
+ }, contextUser);
1199
+
1200
+ if (!connResult.Success || !connResult.Results || connResult.Results.length === 0) {
1201
+ return null;
1202
+ }
1203
+
1204
+ const serverId = connResult.Results[0].MCPServerID;
1205
+
1206
+ // Then get server OAuth config
1207
+ const serverResult = await rv.RunView<{
1208
+ OAuthIssuerURL: string | null;
1209
+ OAuthScopes: string | null;
1210
+ OAuthMetadataCacheTTLMinutes: number | null;
1211
+ OAuthClientID: string | null;
1212
+ OAuthClientSecretEncrypted: string | null;
1213
+ OAuthRequirePKCE: boolean | null;
1214
+ }>({
1215
+ EntityName: 'MJ: MCP Servers',
1216
+ ExtraFilter: `ID='${serverId}'`,
1217
+ Fields: [
1218
+ 'OAuthIssuerURL',
1219
+ 'OAuthScopes',
1220
+ 'OAuthMetadataCacheTTLMinutes',
1221
+ 'OAuthClientID',
1222
+ 'OAuthClientSecretEncrypted',
1223
+ 'OAuthRequirePKCE'
1224
+ ],
1225
+ ResultType: 'simple'
1226
+ }, contextUser);
1227
+
1228
+ if (!serverResult.Success || !serverResult.Results || serverResult.Results.length === 0) {
1229
+ return null;
1230
+ }
1231
+
1232
+ const server = serverResult.Results[0];
1233
+ return {
1234
+ MCPServerID: serverId,
1235
+ OAuthIssuerURL: server.OAuthIssuerURL ?? undefined,
1236
+ OAuthScopes: server.OAuthScopes ?? undefined,
1237
+ OAuthMetadataCacheTTLMinutes: server.OAuthMetadataCacheTTLMinutes ?? undefined,
1238
+ OAuthClientID: server.OAuthClientID ?? undefined,
1239
+ OAuthClientSecretEncrypted: server.OAuthClientSecretEncrypted ?? undefined,
1240
+ OAuthRequirePKCE: server.OAuthRequirePKCE ?? undefined
1241
+ };
1242
+ } catch (error) {
1243
+ LogError(`MCPResolver: Failed to load connection OAuth config: ${error}`);
1244
+ return null;
1245
+ }
1246
+ }
1247
+
433
1248
  /**
434
1249
  * Publishes a progress update to the statusUpdates subscription
435
1250
  */
@@ -470,11 +1285,96 @@ export class MCPResolver extends ResolverBase {
470
1285
  Total: 0
471
1286
  };
472
1287
  }
473
- }
474
1288
 
1289
+ /**
1290
+ * Creates a result indicating OAuth authorization is required
1291
+ */
1292
+ private createOAuthRequiredResult(authorizationUrl: string, stateParameter: string): SyncMCPToolsResult {
1293
+ return {
1294
+ Success: false,
1295
+ ErrorMessage: 'OAuth authorization required',
1296
+ Added: 0,
1297
+ Updated: 0,
1298
+ Deprecated: 0,
1299
+ Total: 0,
1300
+ RequiresOAuth: true,
1301
+ AuthorizationUrl: authorizationUrl,
1302
+ StateParameter: stateParameter
1303
+ };
1304
+ }
1305
+
1306
+ /**
1307
+ * Creates a result indicating OAuth re-authorization is required
1308
+ */
1309
+ private createOAuthReauthorizationResult(
1310
+ reason: string,
1311
+ authorizationUrl?: string,
1312
+ stateParameter?: string
1313
+ ): SyncMCPToolsResult {
1314
+ return {
1315
+ Success: false,
1316
+ ErrorMessage: `OAuth re-authorization required: ${reason}`,
1317
+ Added: 0,
1318
+ Updated: 0,
1319
+ Deprecated: 0,
1320
+ Total: 0,
1321
+ RequiresReauthorization: true,
1322
+ ReauthorizationReason: reason,
1323
+ AuthorizationUrl: authorizationUrl,
1324
+ StateParameter: stateParameter
1325
+ };
1326
+ }
1327
+
1328
+ /**
1329
+ * Gets the public URL for OAuth callbacks
1330
+ */
1331
+ private getPublicUrl(): string {
1332
+ // Use publicUrl from config, falling back to constructed URL
1333
+ if (configInfo.publicUrl) {
1334
+ return configInfo.publicUrl;
1335
+ }
1336
+
1337
+ // Construct from baseUrl and graphqlPort
1338
+ const baseUrl = configInfo.baseUrl || 'http://localhost';
1339
+ const port = configInfo.graphqlPort || 4000;
1340
+ const rootPath = configInfo.graphqlRootPath || '/';
1341
+
1342
+ // Construct full URL
1343
+ let url = `${baseUrl}:${port}`;
1344
+ if (rootPath && rootPath !== '/') {
1345
+ url += rootPath;
1346
+ }
1347
+
1348
+ return url;
1349
+ }
1350
+ }
475
1351
  /**
476
- * Tree-shaking prevention function
1352
+ * Publishes an OAuth event to the subscription topic.
1353
+ * Can be called from other resolvers or handlers to notify clients of OAuth events.
1354
+ *
1355
+ * @param pubSub - PubSub engine
1356
+ * @param event - OAuth event details
477
1357
  */
478
- export function LoadMCPResolver(): void {
479
- // Ensures the resolver is not tree-shaken
1358
+ export async function publishMCPOAuthEvent(
1359
+ pubSub: PubSubEngine,
1360
+ event: {
1361
+ eventType: MCPOAuthEventType;
1362
+ connectionId: string;
1363
+ authorizationUrl?: string;
1364
+ stateParameter?: string;
1365
+ errorMessage?: string;
1366
+ requiresReauthorization?: boolean;
1367
+ }
1368
+ ): Promise<void> {
1369
+ const payload: MCPOAuthEventPayload = {
1370
+ eventType: event.eventType,
1371
+ connectionId: event.connectionId,
1372
+ timestamp: new Date().toISOString(),
1373
+ authorizationUrl: event.authorizationUrl,
1374
+ stateParameter: event.stateParameter,
1375
+ errorMessage: event.errorMessage,
1376
+ requiresReauthorization: event.requiresReauthorization
1377
+ };
1378
+
1379
+ await pubSub.publish(MCP_OAUTH_EVENTS_TOPIC, payload);
480
1380
  }