@memberjunction/server 3.4.0 → 4.1.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.
- package/README.md +689 -513
- package/dist/agents/skip-agent.d.ts +65 -0
- package/dist/agents/skip-agent.d.ts.map +1 -1
- package/dist/agents/skip-agent.js +63 -5
- package/dist/agents/skip-agent.js.map +1 -1
- package/dist/agents/skip-sdk.d.ts +163 -0
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +143 -12
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/apolloServer/index.d.ts +0 -1
- package/dist/apolloServer/index.d.ts.map +1 -1
- package/dist/auth/APIKeyScopeAuth.d.ts +82 -0
- package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
- package/dist/auth/APIKeyScopeAuth.js +78 -0
- package/dist/auth/APIKeyScopeAuth.js.map +1 -1
- package/dist/auth/AuthProviderFactory.d.ts +35 -0
- package/dist/auth/AuthProviderFactory.d.ts.map +1 -1
- package/dist/auth/AuthProviderFactory.js +51 -4
- package/dist/auth/AuthProviderFactory.js.map +1 -1
- package/dist/auth/BaseAuthProvider.d.ts +21 -0
- package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
- package/dist/auth/BaseAuthProvider.js +24 -9
- package/dist/auth/BaseAuthProvider.js.map +1 -1
- package/dist/auth/IAuthProvider.d.ts +32 -0
- package/dist/auth/IAuthProvider.d.ts.map +1 -1
- package/dist/auth/exampleNewUserSubClass.d.ts +5 -1
- package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
- package/dist/auth/exampleNewUserSubClass.js +21 -6
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/index.d.ts +14 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +35 -22
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.d.ts +3 -0
- package/dist/auth/initializeProviders.d.ts.map +1 -1
- package/dist/auth/initializeProviders.js +6 -0
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +14 -3
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/auth/providers/Auth0Provider.d.ts +9 -0
- package/dist/auth/providers/Auth0Provider.d.ts.map +1 -1
- package/dist/auth/providers/Auth0Provider.js +10 -0
- package/dist/auth/providers/Auth0Provider.js.map +1 -1
- package/dist/auth/providers/CognitoProvider.d.ts +9 -0
- package/dist/auth/providers/CognitoProvider.d.ts.map +1 -1
- package/dist/auth/providers/CognitoProvider.js +10 -0
- package/dist/auth/providers/CognitoProvider.js.map +1 -1
- package/dist/auth/providers/GoogleProvider.d.ts +9 -0
- package/dist/auth/providers/GoogleProvider.d.ts.map +1 -1
- package/dist/auth/providers/GoogleProvider.js +11 -1
- package/dist/auth/providers/GoogleProvider.js.map +1 -1
- package/dist/auth/providers/MSALProvider.d.ts +9 -0
- package/dist/auth/providers/MSALProvider.d.ts.map +1 -1
- package/dist/auth/providers/MSALProvider.js +10 -0
- package/dist/auth/providers/MSALProvider.js.map +1 -1
- package/dist/auth/providers/OktaProvider.d.ts +9 -0
- package/dist/auth/providers/OktaProvider.d.ts.map +1 -1
- package/dist/auth/providers/OktaProvider.js +10 -0
- package/dist/auth/providers/OktaProvider.js.map +1 -1
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +42 -8
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +8 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +26 -4
- package/dist/context.js.map +1 -1
- package/dist/directives/Public.js +2 -0
- package/dist/directives/Public.js.map +1 -1
- package/dist/entitySubclasses/entityPermissions.server.d.ts +7 -2
- package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
- package/dist/entitySubclasses/entityPermissions.server.js +26 -8
- package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
- package/dist/generated/generated.d.ts +539 -2
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +9985 -14951
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/DeleteOptionsInput.d.ts +3 -0
- package/dist/generic/DeleteOptionsInput.d.ts.map +1 -1
- package/dist/generic/DeleteOptionsInput.js +3 -2
- package/dist/generic/DeleteOptionsInput.js.map +1 -1
- package/dist/generic/KeyInputOutputTypes.js +0 -6
- package/dist/generic/KeyInputOutputTypes.js.map +1 -1
- package/dist/generic/KeyValuePairInput.d.ts +4 -0
- package/dist/generic/KeyValuePairInput.d.ts.map +1 -1
- package/dist/generic/KeyValuePairInput.js +4 -2
- package/dist/generic/KeyValuePairInput.js.map +1 -1
- package/dist/generic/PushStatusResolver.js +0 -3
- package/dist/generic/PushStatusResolver.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +58 -0
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +203 -18
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +22 -0
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +42 -108
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -37
- package/dist/index.js.map +1 -1
- package/dist/orm.d.ts.map +1 -1
- package/dist/orm.js +2 -1
- package/dist/orm.js.map +1 -1
- package/dist/resolvers/APIKeyResolver.d.ts +74 -0
- package/dist/resolvers/APIKeyResolver.d.ts.map +1 -1
- package/dist/resolvers/APIKeyResolver.js +49 -10
- package/dist/resolvers/APIKeyResolver.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +189 -0
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +152 -21
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/ColorResolver.js +0 -5
- package/dist/resolvers/ColorResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.d.ts +65 -0
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +118 -40
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts +47 -0
- package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/CreateQueryResolver.js +92 -116
- package/dist/resolvers/CreateQueryResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.js +2 -14
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts +40 -0
- package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.js +2 -36
- package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +0 -7
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts +1 -1
- package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +15 -3
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.d.ts +16 -0
- package/dist/resolvers/FileResolver.d.ts.map +1 -1
- package/dist/resolvers/FileResolver.js +59 -74
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +18 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +17 -9
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts +19 -0
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +35 -35
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/InfoResolver.d.ts +2 -2
- package/dist/resolvers/InfoResolver.d.ts.map +1 -1
- package/dist/resolvers/InfoResolver.js +17 -20
- package/dist/resolvers/InfoResolver.js.map +1 -1
- package/dist/resolvers/MCPResolver.d.ts +325 -1
- package/dist/resolvers/MCPResolver.d.ts.map +1 -1
- package/dist/resolvers/MCPResolver.js +931 -24
- package/dist/resolvers/MCPResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +3 -29
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js +0 -3
- package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts +20 -0
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +44 -36
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/ReportResolver.d.ts +3 -0
- package/dist/resolvers/ReportResolver.d.ts.map +1 -1
- package/dist/resolvers/ReportResolver.js +9 -10
- package/dist/resolvers/ReportResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +54 -0
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +116 -40
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts +42 -0
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +95 -22
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +9 -6
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/RunTestResolver.d.ts +12 -0
- package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTestResolver.js +35 -21
- package/dist/resolvers/RunTestResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts +312 -0
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +295 -45
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts +21 -0
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +36 -22
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +14 -0
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +54 -21
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/TaskResolver.d.ts +13 -0
- package/dist/resolvers/TaskResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskResolver.js +22 -7
- package/dist/resolvers/TaskResolver.js.map +1 -1
- package/dist/resolvers/TelemetryResolver.d.ts +22 -0
- package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
- package/dist/resolvers/TelemetryResolver.js +45 -79
- package/dist/resolvers/TelemetryResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +11 -13
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.js +3 -12
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
- package/dist/resolvers/UserResolver.js +10 -0
- package/dist/resolvers/UserResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.js +4 -0
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/resolvers/VersionHistoryResolver.d.ts +39 -0
- package/dist/resolvers/VersionHistoryResolver.d.ts.map +1 -0
- package/dist/resolvers/VersionHistoryResolver.js +208 -0
- package/dist/resolvers/VersionHistoryResolver.js.map +1 -0
- package/dist/rest/EntityCRUDHandler.d.ts +19 -0
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +55 -0
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/OAuthCallbackHandler.d.ts +143 -0
- package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -0
- package/dist/rest/OAuthCallbackHandler.js +634 -0
- package/dist/rest/OAuthCallbackHandler.js.map +1 -0
- package/dist/rest/RESTEndpointHandler.d.ts +120 -0
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +213 -24
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +19 -0
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +39 -0
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/rest/index.d.ts +1 -0
- package/dist/rest/index.d.ts.map +1 -1
- package/dist/rest/index.js +1 -0
- package/dist/rest/index.js.map +1 -1
- package/dist/rest/setupRESTEndpoints.d.ts +35 -0
- package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
- package/dist/rest/setupRESTEndpoints.js +15 -1
- package/dist/rest/setupRESTEndpoints.js.map +1 -1
- package/dist/services/ScheduledJobsService.d.ts +31 -0
- package/dist/services/ScheduledJobsService.d.ts.map +1 -1
- package/dist/services/ScheduledJobsService.js +38 -4
- package/dist/services/ScheduledJobsService.js.map +1 -1
- package/dist/services/TaskOrchestrator.d.ts +73 -0
- package/dist/services/TaskOrchestrator.d.ts.map +1 -1
- package/dist/services/TaskOrchestrator.js +137 -15
- package/dist/services/TaskOrchestrator.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -13
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +37 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +55 -8
- package/dist/util.js.map +1 -1
- package/package.json +83 -78
- package/src/auth/exampleNewUserSubClass.ts +1 -5
- package/src/auth/newUsers.ts +4 -2
- package/src/entitySubclasses/entityPermissions.server.ts +1 -3
- package/src/generated/generated.ts +4707 -2664
- package/src/index.ts +73 -62
- package/src/resolvers/FileCategoryResolver.ts +1 -1
- package/src/resolvers/InfoResolver.ts +10 -6
- package/src/resolvers/MCPResolver.ts +910 -10
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +0 -4
- package/src/resolvers/VersionHistoryResolver.ts +177 -0
- package/src/rest/OAuthCallbackHandler.ts +766 -0
- package/src/rest/RESTEndpointHandler.ts +58 -35
- package/src/rest/index.ts +2 -1
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
479
|
-
|
|
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
|
}
|