@lenne.tech/nest-server 11.42.16 → 11.43.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/.claude/rules/better-auth.md +22 -0
- package/.claude/rules/configurable-features.md +3 -3
- package/.claude/rules/testing.md +4 -4
- package/FRAMEWORK-API.md +2 -2
- package/dist/core/common/interfaces/server-options.interface.d.ts +3 -0
- package/dist/core/modules/ai/core-ai-mcp-oauth.initializer.d.ts +19 -0
- package/dist/core/modules/ai/core-ai-mcp-oauth.initializer.js +145 -0
- package/dist/core/modules/ai/core-ai-mcp-oauth.initializer.js.map +1 -0
- package/dist/core/modules/ai/core-ai-mcp.controller.js +6 -5
- package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -1
- package/dist/core/modules/ai/core-ai.module.d.ts +2 -0
- package/dist/core/modules/ai/core-ai.module.js +6 -1
- package/dist/core/modules/ai/core-ai.module.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.d.ts +33 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.js +282 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.js.map +1 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.d.ts +15 -2
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js +73 -19
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.helper.js.map +1 -1
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.registry.d.ts +5 -0
- package/dist/core/modules/ai/helpers/ai-mcp-oauth.registry.js.map +1 -1
- package/dist/core/modules/ai/index.d.ts +1 -0
- package/dist/core/modules/ai/index.js +1 -0
- package/dist/core/modules/ai/index.js.map +1 -1
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.d.ts +30 -2
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js +222 -15
- package/dist/core/modules/ai/services/core-ai-mcp-oauth.service.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.config.d.ts +4 -0
- package/dist/core/modules/better-auth/better-auth.config.js +29 -2
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +2 -0
- package/dist/core/modules/better-auth/core-better-auth.controller.js +36 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/REQUEST-LIFECYCLE.md +1 -1
- package/migration-guides/11.42.x-to-11.43.x.md +195 -0
- package/package.json +4 -4
- package/src/core/common/helpers/swagger.helper.ts +4 -4
- package/src/core/common/interfaces/server-options.interface.ts +37 -9
- package/src/core/modules/ai/INTEGRATION-CHECKLIST.md +50 -24
- package/src/core/modules/ai/README.md +98 -13
- package/src/core/modules/ai/core-ai-mcp-oauth.initializer.ts +199 -0
- package/src/core/modules/ai/core-ai-mcp.controller.ts +13 -6
- package/src/core/modules/ai/core-ai.module.ts +14 -2
- package/src/core/modules/ai/helpers/ai-mcp-oauth-consent.helper.ts +369 -0
- package/src/core/modules/ai/helpers/ai-mcp-oauth.helper.ts +149 -43
- package/src/core/modules/ai/helpers/ai-mcp-oauth.registry.ts +18 -5
- package/src/core/modules/ai/index.ts +1 -0
- package/src/core/modules/ai/services/core-ai-mcp-oauth.service.ts +385 -28
- package/src/core/modules/better-auth/better-auth.config.ts +49 -5
- package/src/core/modules/better-auth/core-better-auth.controller.ts +75 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Injectable,
|
|
3
|
+
Logger,
|
|
4
|
+
OnApplicationBootstrap,
|
|
5
|
+
OnApplicationShutdown,
|
|
6
|
+
OnModuleInit,
|
|
7
|
+
Optional,
|
|
8
|
+
RequestMethod,
|
|
9
|
+
} from '@nestjs/common';
|
|
10
|
+
import { MODULE_PATH, PATH_METADATA, VERSION_METADATA } from '@nestjs/common/constants';
|
|
11
|
+
import { addLeadingSlash } from '@nestjs/common/utils/shared.utils';
|
|
12
|
+
import { ApplicationConfig, HttpAdapterHost, ModuleRef, ModulesContainer } from '@nestjs/core';
|
|
13
|
+
import { RoutePathFactory } from '@nestjs/core/router/route-path-factory';
|
|
14
|
+
|
|
15
|
+
import { ConfigService } from '../../common/services/config.service';
|
|
16
|
+
import { getBetterAuthTokenService } from '../better-auth/core-better-auth.registry';
|
|
17
|
+
import { CoreAiMcpController } from './core-ai-mcp.controller';
|
|
18
|
+
import { mountAiMcpOAuthRouter } from './helpers/ai-mcp-oauth.helper';
|
|
19
|
+
import { getMountedMcpOAuth, IMountedMcpOAuth, setMountedMcpOAuth } from './helpers/ai-mcp-oauth.registry';
|
|
20
|
+
import { CoreAiMcpOAuthService } from './services/core-ai-mcp-oauth.service';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Mounts the MCP OAuth 2.1 endpoints (`/.well-known/oauth-*`, `/authorize`, `/token`, `/register`)
|
|
24
|
+
* when `ai.mcp.oauth` is enabled — no `main.ts` code, and the same under `Test.createTestingModule()`.
|
|
25
|
+
*
|
|
26
|
+
* WHERE in the lifecycle is the whole point. The endpoints are an Express router, not Nest routes,
|
|
27
|
+
* and Nest registers its not-found handler inside `app.init()`, after `onModuleInit`. A router added
|
|
28
|
+
* after `app.init()` — what the documentation used to prescribe — sits behind that handler and is
|
|
29
|
+
* never reached: every OAuth endpoint answered 404. Mounted here, it sits behind every Nest
|
|
30
|
+
* middleware (security headers, request context, CORS) and the Nest routes, and ahead of the
|
|
31
|
+
* not-found handler.
|
|
32
|
+
*
|
|
33
|
+
* The MCP resource path is computed the way Nest computes the controller's route (global prefix and
|
|
34
|
+
* its exclusions, URI versioning, a `RouterModule` path), so the protected resource metadata names
|
|
35
|
+
* the URL the MCP endpoint actually answers on. The issuer is `ai.mcp.oauthBaseUrl`, else the
|
|
36
|
+
* server's `baseUrl`; a deployed environment without either fails the boot rather than advertising
|
|
37
|
+
* a guessed URL.
|
|
38
|
+
*
|
|
39
|
+
* Registered by `CoreAiModule` whenever the MCP endpoint is; consumers never interact with it.
|
|
40
|
+
*/
|
|
41
|
+
@Injectable()
|
|
42
|
+
export class CoreAiMcpOAuthInitializer implements OnApplicationBootstrap, OnApplicationShutdown, OnModuleInit {
|
|
43
|
+
protected readonly logger = new Logger(CoreAiMcpOAuthInitializer.name);
|
|
44
|
+
|
|
45
|
+
/** What this instance mounted, so shutdown clears only its own record. */
|
|
46
|
+
private mounted?: IMountedMcpOAuth;
|
|
47
|
+
|
|
48
|
+
constructor(
|
|
49
|
+
protected readonly oauthService: CoreAiMcpOAuthService,
|
|
50
|
+
@Optional() protected readonly httpAdapterHost?: HttpAdapterHost,
|
|
51
|
+
@Optional() protected readonly moduleRef?: ModuleRef,
|
|
52
|
+
@Optional() protected readonly modulesContainer?: ModulesContainer,
|
|
53
|
+
) {}
|
|
54
|
+
|
|
55
|
+
async onModuleInit(): Promise<void> {
|
|
56
|
+
if (!this.oauthService.isOAuthEnabled()) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const adapter = this.httpAdapterHost?.httpAdapter;
|
|
60
|
+
if (!adapter) {
|
|
61
|
+
// An application context without an HTTP server (CLI, migration script): nothing to serve.
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Repeated here, in framework code, because a subclass may override onModuleInit() without super.
|
|
65
|
+
this.oauthService.assertProductionSafe();
|
|
66
|
+
if (adapter.getType() !== 'express') {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`ai.mcp.oauth requires the Express adapter, but the application runs on "${adapter.getType()}": ` +
|
|
69
|
+
'the MCP OAuth endpoints are an Express router.',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.mounted = await mountAiMcpOAuthRouter(
|
|
74
|
+
adapter.getInstance(),
|
|
75
|
+
(router) => adapter.use(router),
|
|
76
|
+
this.oauthService,
|
|
77
|
+
{
|
|
78
|
+
baseUrl: ConfigService.get<unknown>('ai.mcp.oauthBaseUrl'),
|
|
79
|
+
authBasePath: this.resolveAuthBasePath(),
|
|
80
|
+
baseUrlSource: 'ai.mcp.oauthBaseUrl',
|
|
81
|
+
mcpPath: this.resolveMcpPath(),
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The built-in consent page signs users in through Better-Auth. Checked at bootstrap, when every
|
|
88
|
+
* module — Better-Auth's included — has initialized.
|
|
89
|
+
*/
|
|
90
|
+
onApplicationBootstrap(): void {
|
|
91
|
+
if (!this.mounted || !this.oauthService.usesBuiltInConsent() || getBetterAuthTokenService()) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.logger.warn(
|
|
95
|
+
'ai.mcp.oauth is enabled, but Better-Auth is not active: the built-in consent page has no way to sign a user ' +
|
|
96
|
+
'in, so no MCP client can be authorized. Enable betterAuth, or register a CoreAiMcpOAuthService subclass ' +
|
|
97
|
+
'with an authorizeConsent() of your own via CoreModule.forRoot(env, { ai: { mcpOAuthService } }).',
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
onApplicationShutdown(): void {
|
|
102
|
+
if (this.mounted && getMountedMcpOAuth() === this.mounted) {
|
|
103
|
+
setMountedMcpOAuth(undefined);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The path the MCP endpoint (`POST` on `CoreAiMcpController`) is registered at, computed with
|
|
109
|
+
* Nest's own `RoutePathFactory` from the inputs Nest passes it (`NestApplication.registerRouter`,
|
|
110
|
+
* `RoutesResolver`). Re-implementing the rules instead would drift silently the first time Nest
|
|
111
|
+
* changes one; this breaks loudly, and `tests/unit/ai-mcp-oauth-lifecycle.spec.ts` pins the result
|
|
112
|
+
* against the route the controller actually answers on.
|
|
113
|
+
*/
|
|
114
|
+
protected resolveMcpPath(): string {
|
|
115
|
+
const container = (this.moduleRef as unknown as { container?: { applicationConfig?: ApplicationConfig } })
|
|
116
|
+
?.container;
|
|
117
|
+
const applicationConfig = container?.applicationConfig;
|
|
118
|
+
const ctrlPath = this.controllerPath();
|
|
119
|
+
if (!applicationConfig) {
|
|
120
|
+
return ctrlPath;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const versioningOptions = applicationConfig.getVersioning();
|
|
124
|
+
const controllerVersion = versioningOptions
|
|
125
|
+
? (Reflect.getMetadata(VERSION_METADATA, CoreAiMcpController) ?? versioningOptions.defaultVersion)
|
|
126
|
+
: undefined;
|
|
127
|
+
const paths = new RoutePathFactory(applicationConfig).create(
|
|
128
|
+
{
|
|
129
|
+
controllerVersion,
|
|
130
|
+
ctrlPath,
|
|
131
|
+
globalPrefix: addLeadingSlash(applicationConfig.getGlobalPrefix()),
|
|
132
|
+
methodPath: '/',
|
|
133
|
+
modulePath: this.modulePath(),
|
|
134
|
+
versioningOptions,
|
|
135
|
+
},
|
|
136
|
+
RequestMethod.POST,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (paths.length > 1) {
|
|
140
|
+
this.logger.warn(
|
|
141
|
+
`The MCP endpoint is registered at ${paths.join(', ')}; the OAuth metadata describes the first one only.`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return paths[0];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Where the IAM endpoints the consent page signs in through answer: Better-Auth's `basePath` below
|
|
149
|
+
* the global prefix (and URI version) Nest registers the IAM controller under. The OAuth router
|
|
150
|
+
* itself sits at the root, so a page served there must not assume `/iam`.
|
|
151
|
+
*/
|
|
152
|
+
protected resolveAuthBasePath(): string {
|
|
153
|
+
const basePath = `/${String(ConfigService.get<string>('betterAuth.basePath') || 'iam').replace(/^\//, '')}`;
|
|
154
|
+
const applicationConfig = (this.moduleRef as unknown as { container?: { applicationConfig?: ApplicationConfig } })
|
|
155
|
+
?.container?.applicationConfig;
|
|
156
|
+
if (!applicationConfig) {
|
|
157
|
+
return basePath;
|
|
158
|
+
}
|
|
159
|
+
const versioningOptions = applicationConfig.getVersioning();
|
|
160
|
+
const [signIn] = new RoutePathFactory(applicationConfig).create(
|
|
161
|
+
{
|
|
162
|
+
controllerVersion: versioningOptions?.defaultVersion,
|
|
163
|
+
ctrlPath: basePath,
|
|
164
|
+
globalPrefix: addLeadingSlash(applicationConfig.getGlobalPrefix()),
|
|
165
|
+
methodPath: '/sign-in/email',
|
|
166
|
+
versioningOptions,
|
|
167
|
+
},
|
|
168
|
+
RequestMethod.POST,
|
|
169
|
+
);
|
|
170
|
+
return signIn.replace(/\/sign-in\/email$/, '');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The controller's declared route, with a leading slash (`/ai/mcp`). */
|
|
174
|
+
private controllerPath(): string {
|
|
175
|
+
const declared = Reflect.getMetadata(PATH_METADATA, CoreAiMcpController);
|
|
176
|
+
const path = Array.isArray(declared) ? declared[0] : declared;
|
|
177
|
+
return `/${String(path ?? 'ai/mcp').replace(/^\//, '')}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A `RouterModule` path in front of the module that registered the MCP controller, read the way
|
|
182
|
+
* `RoutesResolver` reads it. The module is looked up rather than imported: importing `CoreAiModule`
|
|
183
|
+
* here would put this file and the module on an import cycle.
|
|
184
|
+
*/
|
|
185
|
+
private modulePath(): string | undefined {
|
|
186
|
+
if (!this.modulesContainer) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
for (const module of this.modulesContainer.values()) {
|
|
190
|
+
if (module.controllers.has(CoreAiMcpController)) {
|
|
191
|
+
return (
|
|
192
|
+
Reflect.getMetadata(MODULE_PATH + this.modulesContainer.applicationId, module.metatype) ??
|
|
193
|
+
Reflect.getMetadata(MODULE_PATH, module.metatype)
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -9,6 +9,7 @@ import { ConfigService } from '../../common/services/config.service';
|
|
|
9
9
|
import { CoreRedisService } from '../../common/services/core-redis.service';
|
|
10
10
|
import { CoreBetterAuthModule } from '../better-auth/core-better-auth.module';
|
|
11
11
|
import { ErrorCode } from '../error-code/error-codes';
|
|
12
|
+
import { getMountedMcpOAuth } from './helpers/ai-mcp-oauth.registry';
|
|
12
13
|
import { CoreAiMcpOAuthService } from './services/core-ai-mcp-oauth.service';
|
|
13
14
|
import { CoreAiMcpService } from './services/core-ai-mcp.service';
|
|
14
15
|
import { AiToolUser } from './tools/ai-tool.registry';
|
|
@@ -34,7 +35,8 @@ import { AiToolUser } from './tools/ai-tool.registry';
|
|
|
34
35
|
* `WWW-Authenticate` header as the protocol expects.
|
|
35
36
|
*
|
|
36
37
|
* When `ai.mcp.oauth` is enabled, the handler additionally accepts OAuth 2.1 access
|
|
37
|
-
* tokens (
|
|
38
|
+
* tokens (endpoints mounted by `CoreAiMcpOAuthInitializer`); otherwise it authenticates via
|
|
39
|
+
* Bearer/session token.
|
|
38
40
|
*
|
|
39
41
|
* ## Multi-replica
|
|
40
42
|
*
|
|
@@ -121,7 +123,7 @@ export class CoreAiMcpController implements OnModuleDestroy {
|
|
|
121
123
|
async handlePost(@Req() req: Request, @Res() res: Response): Promise<void> {
|
|
122
124
|
const user = await this.resolveUser(req);
|
|
123
125
|
if (!user?.id) {
|
|
124
|
-
this.unauthorized(
|
|
126
|
+
this.unauthorized(res);
|
|
125
127
|
return;
|
|
126
128
|
}
|
|
127
129
|
|
|
@@ -298,7 +300,7 @@ export class CoreAiMcpController implements OnModuleDestroy {
|
|
|
298
300
|
private async handleSessionRequest(req: Request, res: Response): Promise<void> {
|
|
299
301
|
const user = await this.resolveUser(req);
|
|
300
302
|
if (!user?.id) {
|
|
301
|
-
this.unauthorized(
|
|
303
|
+
this.unauthorized(res);
|
|
302
304
|
return;
|
|
303
305
|
}
|
|
304
306
|
const sessionId = req.headers['mcp-session-id'] as string | undefined;
|
|
@@ -470,12 +472,17 @@ export class CoreAiMcpController implements OnModuleDestroy {
|
|
|
470
472
|
|
|
471
473
|
/**
|
|
472
474
|
* MCP-style 401 with a `WWW-Authenticate` header for OAuth discovery.
|
|
475
|
+
*
|
|
476
|
+
* `resource_metadata` is the URL of the protected resource METADATA (RFC 9728 §5.1), exactly as
|
|
477
|
+
* the mounted OAuth router serves it — not the endpoint itself, and not a URL built from the
|
|
478
|
+
* request's `Host` header, which the caller controls. Without mounted OAuth endpoints there is
|
|
479
|
+
* no metadata to point at, so the challenge is a plain `Bearer`.
|
|
473
480
|
*/
|
|
474
|
-
private unauthorized(
|
|
475
|
-
const
|
|
481
|
+
private unauthorized(res: Response): void {
|
|
482
|
+
const oauth = getMountedMcpOAuth();
|
|
476
483
|
res
|
|
477
484
|
.status(401)
|
|
478
|
-
.set({ 'WWW-Authenticate': `Bearer resource_metadata="${
|
|
485
|
+
.set({ 'WWW-Authenticate': oauth ? `Bearer resource_metadata="${oauth.resourceMetadataUrl}"` : 'Bearer' })
|
|
479
486
|
.json({ error: 'Unauthorized: a valid Bearer token is required' });
|
|
480
487
|
}
|
|
481
488
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DynamicModule, Global, Module, Type } from '@nestjs/common';
|
|
2
2
|
import { MongooseModule } from '@nestjs/mongoose';
|
|
3
3
|
|
|
4
|
+
import { CoreAiMcpOAuthInitializer } from './core-ai-mcp-oauth.initializer';
|
|
4
5
|
import { CoreAiMcpController } from './core-ai-mcp.controller';
|
|
5
6
|
import { CoreAiController } from './core-ai.controller';
|
|
6
7
|
import { CoreAiResolver } from './core-ai.resolver';
|
|
@@ -95,6 +96,12 @@ export interface CoreAiModuleOptions {
|
|
|
95
96
|
/** Whether to register the MCP server controller at /ai/mcp. */
|
|
96
97
|
mcpEnabled?: boolean;
|
|
97
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Custom MCP OAuth service (extends CoreAiMcpOAuthService) — where `authorizeConsent()` wires the
|
|
101
|
+
* login/consent UI. Bound to the base token, so the MCP controller and the OAuth endpoints use it.
|
|
102
|
+
*/
|
|
103
|
+
mcpOAuthService?: Type<CoreAiMcpOAuthService>;
|
|
104
|
+
|
|
98
105
|
/** Custom connection-preference service (extends CoreAiConnectionPreferenceService). */
|
|
99
106
|
preferenceService?: Type<CoreAiConnectionPreferenceService>;
|
|
100
107
|
|
|
@@ -174,11 +181,15 @@ export class CoreAiModule {
|
|
|
174
181
|
const ToolPolicyServiceClass = options.toolPolicyService || CoreAiToolPolicyService;
|
|
175
182
|
const ModeServiceClass = options.modeService || CoreAiModeService;
|
|
176
183
|
const McpClientServiceClass = options.mcpClientService || CoreAiMcpClientService;
|
|
184
|
+
const McpOAuthServiceClass = options.mcpOAuthService || CoreAiMcpOAuthService;
|
|
177
185
|
|
|
178
|
-
// Register the MCP controller only when enabled (it lazy-loads the MCP SDK)
|
|
186
|
+
// Register the MCP controller only when enabled (it lazy-loads the MCP SDK), together with the
|
|
187
|
+
// initializer that mounts its OAuth endpoints when `ai.mcp.oauth` is enabled.
|
|
179
188
|
const controllers: Type<any>[] = [ControllerClass];
|
|
189
|
+
const mcpProviders: Type<any>[] = [];
|
|
180
190
|
if (options.mcpEnabled) {
|
|
181
191
|
controllers.push(CoreAiMcpController);
|
|
192
|
+
mcpProviders.push(CoreAiMcpOAuthInitializer);
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
return {
|
|
@@ -246,7 +257,7 @@ export class CoreAiModule {
|
|
|
246
257
|
{ provide: CoreAiConversationService, useClass: ConversationServiceClass },
|
|
247
258
|
{ provide: CoreAiInteractionService, useClass: InteractionServiceClass },
|
|
248
259
|
CoreAiMcpService,
|
|
249
|
-
|
|
260
|
+
...mcpProviders,
|
|
250
261
|
// Bind base-class tokens to the (possibly overridden) implementations so
|
|
251
262
|
// injections by base type resolve to the project's subclass.
|
|
252
263
|
{ provide: CoreAiConnectionPreferenceService, useClass: PreferenceServiceClass },
|
|
@@ -262,6 +273,7 @@ export class CoreAiModule {
|
|
|
262
273
|
{ provide: CoreAiToolPolicyService, useClass: ToolPolicyServiceClass },
|
|
263
274
|
{ provide: CoreAiModeService, useClass: ModeServiceClass },
|
|
264
275
|
{ provide: CoreAiMcpClientService, useClass: McpClientServiceClass },
|
|
276
|
+
{ provide: CoreAiMcpOAuthService, useClass: McpOAuthServiceClass },
|
|
265
277
|
ResolverClass,
|
|
266
278
|
],
|
|
267
279
|
};
|