@rekog/mcp-nest 1.7.0-alpha.0 → 1.7.0-alpha.2

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 (104) hide show
  1. package/dist/authz/guards/jwt-auth.guard.d.ts +13 -0
  2. package/dist/authz/guards/jwt-auth.guard.d.ts.map +1 -0
  3. package/dist/authz/guards/jwt-auth.guard.js +46 -0
  4. package/dist/authz/guards/jwt-auth.guard.js.map +1 -0
  5. package/dist/authz/index.d.ts +8 -1
  6. package/dist/authz/index.d.ts.map +1 -1
  7. package/dist/authz/index.js +22 -0
  8. package/dist/authz/index.js.map +1 -1
  9. package/dist/authz/interfaces/index.d.ts +3 -0
  10. package/dist/authz/interfaces/index.d.ts.map +1 -0
  11. package/dist/authz/interfaces/index.js +19 -0
  12. package/dist/authz/interfaces/index.js.map +1 -0
  13. package/dist/authz/mcp-oauth.controller.d.ts +53 -0
  14. package/dist/authz/mcp-oauth.controller.d.ts.map +1 -0
  15. package/dist/authz/mcp-oauth.controller.js +305 -0
  16. package/dist/authz/mcp-oauth.controller.js.map +1 -0
  17. package/dist/authz/mcp-oauth.module.d.ts +11 -0
  18. package/dist/authz/mcp-oauth.module.d.ts.map +1 -0
  19. package/dist/authz/mcp-oauth.module.js +173 -0
  20. package/dist/authz/mcp-oauth.module.js.map +1 -0
  21. package/dist/authz/providers/github.provider.d.ts +3 -0
  22. package/dist/authz/providers/github.provider.d.ts.map +1 -0
  23. package/dist/authz/providers/github.provider.js +23 -0
  24. package/dist/authz/providers/github.provider.js.map +1 -0
  25. package/dist/authz/providers/google.provider.d.ts +3 -0
  26. package/dist/authz/providers/google.provider.d.ts.map +1 -0
  27. package/dist/authz/providers/google.provider.js +24 -0
  28. package/dist/authz/providers/google.provider.js.map +1 -0
  29. package/dist/authz/providers/oauth-provider.interface.d.ts +75 -0
  30. package/dist/authz/providers/oauth-provider.interface.d.ts.map +1 -0
  31. package/dist/authz/providers/oauth-provider.interface.js +3 -0
  32. package/dist/authz/providers/oauth-provider.interface.js.map +1 -0
  33. package/dist/authz/services/client.service.d.ts +10 -0
  34. package/dist/authz/services/client.service.d.ts.map +1 -0
  35. package/dist/authz/services/client.service.js +84 -0
  36. package/dist/authz/services/client.service.js.map +1 -0
  37. package/dist/authz/services/jwt-token.service.d.ts +29 -0
  38. package/dist/authz/services/jwt-token.service.d.ts.map +1 -0
  39. package/dist/authz/services/jwt-token.service.js +103 -0
  40. package/dist/authz/services/jwt-token.service.js.map +1 -0
  41. package/dist/authz/services/oauth-strategy.service.d.ts +11 -0
  42. package/dist/authz/services/oauth-strategy.service.d.ts.map +1 -0
  43. package/dist/authz/services/oauth-strategy.service.js +64 -0
  44. package/dist/authz/services/oauth-strategy.service.js.map +1 -0
  45. package/dist/authz/stores/memory-store.service.d.ts +19 -0
  46. package/dist/authz/stores/memory-store.service.d.ts.map +1 -0
  47. package/dist/authz/stores/memory-store.service.js +84 -0
  48. package/dist/authz/stores/memory-store.service.js.map +1 -0
  49. package/dist/authz/stores/memory-store.service.spec.d.ts +2 -0
  50. package/dist/authz/stores/memory-store.service.spec.d.ts.map +1 -0
  51. package/dist/authz/stores/memory-store.service.spec.js +195 -0
  52. package/dist/authz/stores/memory-store.service.spec.js.map +1 -0
  53. package/dist/authz/stores/oauth-store.interface.d.ts +52 -0
  54. package/dist/authz/stores/oauth-store.interface.d.ts.map +1 -0
  55. package/dist/authz/stores/oauth-store.interface.js +3 -0
  56. package/dist/authz/stores/oauth-store.interface.js.map +1 -0
  57. package/dist/authz/stores/sqlite/sqlite-store.service.d.ts +18 -0
  58. package/dist/authz/stores/sqlite/sqlite-store.service.d.ts.map +1 -0
  59. package/dist/authz/stores/sqlite/sqlite-store.service.js +206 -0
  60. package/dist/authz/stores/sqlite/sqlite-store.service.js.map +1 -0
  61. package/dist/authz/stores/typeorm/entities/authorization-code.entity.d.ts +13 -0
  62. package/dist/authz/stores/typeorm/entities/authorization-code.entity.d.ts.map +1 -0
  63. package/dist/authz/stores/typeorm/entities/authorization-code.entity.js +60 -0
  64. package/dist/authz/stores/typeorm/entities/authorization-code.entity.js.map +1 -0
  65. package/dist/authz/stores/typeorm/entities/index.d.ts +4 -0
  66. package/dist/authz/stores/typeorm/entities/index.d.ts.map +1 -0
  67. package/dist/authz/stores/typeorm/entities/index.js +10 -0
  68. package/dist/authz/stores/typeorm/entities/index.js.map +1 -0
  69. package/dist/authz/stores/typeorm/entities/oauth-client.entity.d.ts +16 -0
  70. package/dist/authz/stores/typeorm/entities/oauth-client.entity.d.ts.map +1 -0
  71. package/dist/authz/stores/typeorm/entities/oauth-client.entity.js +72 -0
  72. package/dist/authz/stores/typeorm/entities/oauth-client.entity.js.map +1 -0
  73. package/dist/authz/stores/typeorm/entities/oauth-session.entity.d.ts +13 -0
  74. package/dist/authz/stores/typeorm/entities/oauth-session.entity.d.ts.map +1 -0
  75. package/dist/authz/stores/typeorm/entities/oauth-session.entity.js +60 -0
  76. package/dist/authz/stores/typeorm/entities/oauth-session.entity.js.map +1 -0
  77. package/dist/authz/stores/typeorm/typeorm-store.service.d.ts +21 -0
  78. package/dist/authz/stores/typeorm/typeorm-store.service.d.ts.map +1 -0
  79. package/dist/authz/stores/typeorm/typeorm-store.service.js +87 -0
  80. package/dist/authz/stores/typeorm/typeorm-store.service.js.map +1 -0
  81. package/dist/mcp/services/handlers/mcp-tools.handler.d.ts.map +1 -1
  82. package/dist/mcp/services/handlers/mcp-tools.handler.js.map +1 -1
  83. package/package.json +17 -2
  84. package/src/authz/guards/jwt-auth.guard.ts +45 -0
  85. package/src/authz/index.ts +8 -1
  86. package/src/authz/mcp-oauth.controller.ts +394 -0
  87. package/src/authz/mcp-oauth.module.ts +235 -0
  88. package/src/authz/providers/github.provider.ts +24 -0
  89. package/src/authz/providers/google.provider.ts +22 -0
  90. package/src/authz/providers/oauth-provider.interface.ts +102 -0
  91. package/src/authz/services/client.service.ts +110 -0
  92. package/src/authz/services/jwt-token.service.ts +127 -0
  93. package/src/authz/services/oauth-strategy.service.ts +55 -0
  94. package/src/authz/stores/memory-store.service.spec.ts +240 -0
  95. package/src/authz/stores/memory-store.service.ts +100 -0
  96. package/src/authz/stores/oauth-store.interface.ts +105 -0
  97. package/src/authz/stores/sqlite/sqlite-store.service.ts +279 -0
  98. package/src/authz/stores/typeorm/README.md +133 -0
  99. package/src/authz/stores/typeorm/entities/authorization-code.entity.ts +34 -0
  100. package/src/authz/stores/typeorm/entities/index.ts +3 -0
  101. package/src/authz/stores/typeorm/entities/oauth-client.entity.ts +49 -0
  102. package/src/authz/stores/typeorm/entities/oauth-session.entity.ts +34 -0
  103. package/src/authz/stores/typeorm/typeorm-store.service.ts +98 -0
  104. package/src/mcp/services/handlers/mcp-tools.handler.ts +5 -2
@@ -0,0 +1,279 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import Database from 'better-sqlite3';
3
+ import { randomBytes } from 'crypto';
4
+ import { OAuthSession } from '../../providers/oauth-provider.interface';
5
+ import {
6
+ AuthorizationCode,
7
+ IOAuthStore,
8
+ OAuthClient,
9
+ } from '../oauth-store.interface';
10
+
11
+ @Injectable()
12
+ export class SQLiteStore implements IOAuthStore {
13
+ private sqlite: any;
14
+
15
+ constructor(databasePath: string = './oauth-sqlite.db') {
16
+ this.sqlite = new Database(databasePath);
17
+ this.initializeTables();
18
+ }
19
+
20
+ private initializeTables() {
21
+ // Enable foreign keys
22
+ this.sqlite.pragma('foreign_keys = ON');
23
+
24
+ // Create tables if they don't exist
25
+ this.sqlite.exec(`
26
+ CREATE TABLE IF NOT EXISTS oauth_clients (
27
+ client_id TEXT PRIMARY KEY,
28
+ client_name TEXT NOT NULL,
29
+ client_description TEXT,
30
+ logo_uri TEXT,
31
+ client_uri TEXT,
32
+ developer_name TEXT,
33
+ developer_email TEXT,
34
+ redirect_uris TEXT NOT NULL,
35
+ grant_types TEXT NOT NULL,
36
+ response_types TEXT NOT NULL,
37
+ token_endpoint_auth_method TEXT NOT NULL,
38
+ created_at INTEGER NOT NULL,
39
+ updated_at INTEGER NOT NULL
40
+ )
41
+ `);
42
+
43
+ this.sqlite.exec(`
44
+ CREATE TABLE IF NOT EXISTS authorization_codes (
45
+ code TEXT PRIMARY KEY,
46
+ user_id TEXT NOT NULL,
47
+ client_id TEXT NOT NULL,
48
+ redirect_uri TEXT NOT NULL,
49
+ code_challenge TEXT NOT NULL,
50
+ code_challenge_method TEXT NOT NULL,
51
+ expires_at INTEGER NOT NULL,
52
+ used_at INTEGER,
53
+ github_access_token TEXT NOT NULL
54
+ )
55
+ `);
56
+
57
+ this.sqlite.exec(`
58
+ CREATE TABLE IF NOT EXISTS oauth_sessions (
59
+ session_id TEXT PRIMARY KEY,
60
+ client_id TEXT NOT NULL,
61
+ redirect_uri TEXT NOT NULL,
62
+ code_challenge TEXT NOT NULL,
63
+ code_challenge_method TEXT NOT NULL,
64
+ state TEXT,
65
+ resource TEXT,
66
+ expires_at INTEGER NOT NULL
67
+ )
68
+ `);
69
+ }
70
+
71
+ // Client management
72
+ async storeClient(client: OAuthClient): Promise<OAuthClient> {
73
+ const stmt = this.sqlite.prepare(`
74
+ INSERT INTO oauth_clients (
75
+ client_id, client_name, client_description, logo_uri, client_uri,
76
+ developer_name, developer_email, redirect_uris, grant_types,
77
+ response_types, token_endpoint_auth_method, created_at, updated_at
78
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
79
+ `);
80
+
81
+ stmt.run(
82
+ client.client_id,
83
+ client.client_name,
84
+ client.client_description || null,
85
+ client.logo_uri || null,
86
+ client.client_uri || null,
87
+ client.developer_name || null,
88
+ client.developer_email || null,
89
+ JSON.stringify(client.redirect_uris),
90
+ JSON.stringify(client.grant_types),
91
+ JSON.stringify(client.response_types),
92
+ client.token_endpoint_auth_method,
93
+ client.created_at.getTime(),
94
+ client.updated_at.getTime(),
95
+ );
96
+
97
+ return client;
98
+ }
99
+
100
+ async getClient(client_id: string): Promise<OAuthClient | undefined> {
101
+ const stmt = this.sqlite.prepare(
102
+ 'SELECT * FROM oauth_clients WHERE client_id = ?',
103
+ );
104
+ const result = stmt.get(client_id) as any;
105
+
106
+ if (!result) {
107
+ return undefined;
108
+ }
109
+
110
+ return {
111
+ client_id: result.client_id,
112
+ client_name: result.client_name,
113
+ client_description: result.client_description || undefined,
114
+ logo_uri: result.logo_uri || undefined,
115
+ client_uri: result.client_uri || undefined,
116
+ developer_name: result.developer_name || undefined,
117
+ developer_email: result.developer_email || undefined,
118
+ redirect_uris: JSON.parse(result.redirect_uris),
119
+ grant_types: JSON.parse(result.grant_types),
120
+ response_types: JSON.parse(result.response_types),
121
+ token_endpoint_auth_method: result.token_endpoint_auth_method,
122
+ created_at: new Date(result.created_at),
123
+ updated_at: new Date(result.updated_at),
124
+ };
125
+ }
126
+
127
+ async findClient(client_name: string): Promise<OAuthClient | undefined> {
128
+ const stmt = this.sqlite.prepare(
129
+ 'SELECT * FROM oauth_clients WHERE client_name = ?',
130
+ );
131
+ const result = stmt.get(client_name) as any;
132
+
133
+ if (!result) {
134
+ return undefined;
135
+ }
136
+
137
+ return {
138
+ client_id: result.client_id,
139
+ client_name: result.client_name,
140
+ client_description: result.client_description || undefined,
141
+ logo_uri: result.logo_uri || undefined,
142
+ client_uri: result.client_uri || undefined,
143
+ developer_name: result.developer_name || undefined,
144
+ developer_email: result.developer_email || undefined,
145
+ redirect_uris: JSON.parse(result.redirect_uris),
146
+ grant_types: JSON.parse(result.grant_types),
147
+ response_types: JSON.parse(result.response_types),
148
+ token_endpoint_auth_method: result.token_endpoint_auth_method,
149
+ created_at: new Date(result.created_at),
150
+ updated_at: new Date(result.updated_at),
151
+ };
152
+ }
153
+
154
+ // Authorization code management
155
+ async storeAuthCode(code: AuthorizationCode): Promise<void> {
156
+ const stmt = this.sqlite.prepare(`
157
+ INSERT INTO authorization_codes (
158
+ code, user_id, client_id, redirect_uri, code_challenge,
159
+ code_challenge_method, expires_at, used_at, github_access_token
160
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
161
+ `);
162
+
163
+ stmt.run(
164
+ code.code,
165
+ code.user_id,
166
+ code.client_id,
167
+ code.redirect_uri,
168
+ code.code_challenge,
169
+ code.code_challenge_method,
170
+ code.expires_at,
171
+ code.used_at ? code.used_at.getTime() : null,
172
+ code.github_access_token,
173
+ );
174
+ }
175
+
176
+ async getAuthCode(code: string): Promise<AuthorizationCode | undefined> {
177
+ const stmt = this.sqlite.prepare(
178
+ 'SELECT * FROM authorization_codes WHERE code = ?',
179
+ );
180
+ const result = stmt.get(code) as any;
181
+
182
+ if (!result) {
183
+ return undefined;
184
+ }
185
+
186
+ // Check if expired
187
+ if (result.expires_at < Date.now()) {
188
+ await this.removeAuthCode(code);
189
+ return undefined;
190
+ }
191
+
192
+ return {
193
+ code: result.code,
194
+ user_id: result.user_id,
195
+ client_id: result.client_id,
196
+ redirect_uri: result.redirect_uri,
197
+ code_challenge: result.code_challenge,
198
+ code_challenge_method: result.code_challenge_method,
199
+ expires_at: result.expires_at,
200
+ used_at: result.used_at ? new Date(result.used_at) : undefined,
201
+ github_access_token: result.github_access_token,
202
+ };
203
+ }
204
+
205
+ async removeAuthCode(code: string): Promise<void> {
206
+ const stmt = this.sqlite.prepare(
207
+ 'DELETE FROM authorization_codes WHERE code = ?',
208
+ );
209
+ stmt.run(code);
210
+ }
211
+
212
+ // OAuth session management
213
+ async storeOAuthSession(
214
+ sessionId: string,
215
+ session: OAuthSession,
216
+ ): Promise<void> {
217
+ const stmt = this.sqlite.prepare(`
218
+ INSERT INTO oauth_sessions (
219
+ session_id, client_id, redirect_uri, code_challenge,
220
+ code_challenge_method, state, resource, expires_at
221
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
222
+ `);
223
+
224
+ stmt.run(
225
+ sessionId,
226
+ session.clientId,
227
+ session.redirectUri,
228
+ session.codeChallenge,
229
+ session.codeChallengeMethod,
230
+ session.state || null,
231
+ session.resource || null,
232
+ session.expiresAt,
233
+ );
234
+ }
235
+
236
+ async getOAuthSession(sessionId: string): Promise<OAuthSession | undefined> {
237
+ const stmt = this.sqlite.prepare(
238
+ 'SELECT * FROM oauth_sessions WHERE session_id = ?',
239
+ );
240
+ const result = stmt.get(sessionId) as any;
241
+
242
+ if (!result) {
243
+ return undefined;
244
+ }
245
+
246
+ // Check if expired
247
+ if (result.expires_at < Date.now()) {
248
+ await this.removeOAuthSession(sessionId);
249
+ return undefined;
250
+ }
251
+
252
+ return {
253
+ sessionId: result.session_id,
254
+ clientId: result.client_id,
255
+ redirectUri: result.redirect_uri,
256
+ codeChallenge: result.code_challenge,
257
+ codeChallengeMethod: result.code_challenge_method,
258
+ state: result.state || undefined,
259
+ resource: result.resource || undefined,
260
+ expiresAt: result.expires_at,
261
+ };
262
+ }
263
+
264
+ async removeOAuthSession(sessionId: string): Promise<void> {
265
+ const stmt = this.sqlite.prepare(
266
+ 'DELETE FROM oauth_sessions WHERE session_id = ?',
267
+ );
268
+ stmt.run(sessionId);
269
+ }
270
+
271
+ generateClientId(client: OAuthClient): string {
272
+ // Create deterministic client ID based on name + random salt
273
+ const normalizedName = client.client_name
274
+ .toLowerCase()
275
+ .replace(/[^a-z0-9]/g, '');
276
+ const salt = randomBytes(4).toString('hex');
277
+ return `${normalizedName}_${salt}`;
278
+ }
279
+ }
@@ -0,0 +1,133 @@
1
+ # TypeORM OAuth Store Implementation
2
+
3
+ This directory contains a TypeORM implementation of the `IOAuthStore` interface for persistent OAuth data storage.
4
+
5
+ ## Features
6
+
7
+ - **Persistent Storage**: Unlike the in-memory store, data survives server restarts
8
+ - **Database Support**: Works with any database supported by TypeORM (PostgreSQL, MySQL, SQLite, etc.)
9
+ - **Automatic Schema Management**: Entities automatically create the required database tables
10
+ - **Expiration Handling**: Automatic cleanup of expired sessions and authorization codes
11
+
12
+ ## Entities
13
+
14
+ ### OAuthClientEntity
15
+ Stores OAuth client information including:
16
+ - Client credentials and metadata
17
+ - Redirect URIs and grant types
18
+ - Creation and update timestamps
19
+
20
+ ### AuthorizationCodeEntity
21
+ Stores authorization codes with:
22
+ - Code challenge and method for PKCE
23
+ - Expiration and usage tracking
24
+ - Associated user and client information
25
+
26
+ ### OAuthSessionEntity
27
+ Stores OAuth session data including:
28
+ - Session state and parameters
29
+ - Expiration handling
30
+ - Client association
31
+
32
+ ## Usage
33
+
34
+ ### Basic SQLite Configuration
35
+
36
+ ```typescript
37
+ McpOAuthModule.forRoot({
38
+ provider: GoogleOAuthProvider,
39
+ clientId: process.env.GOOGLE_CLIENT_ID!,
40
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
41
+ jwtSecret: process.env.JWT_SECRET!,
42
+ typeormOptions: {
43
+ type: 'sqlite',
44
+ database: 'oauth.db',
45
+ synchronize: true,
46
+ logging: false,
47
+ },
48
+ })
49
+ ```
50
+
51
+ ### PostgreSQL Configuration
52
+
53
+ ```typescript
54
+ McpOAuthModule.forRoot({
55
+ provider: GoogleOAuthProvider,
56
+ clientId: process.env.GOOGLE_CLIENT_ID!,
57
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
58
+ jwtSecret: process.env.JWT_SECRET!,
59
+ typeormOptions: {
60
+ type: 'postgres',
61
+ host: process.env.DB_HOST || 'localhost',
62
+ port: parseInt(process.env.DB_PORT) || 5432,
63
+ username: process.env.DB_USERNAME || 'postgres',
64
+ password: process.env.DB_PASSWORD || 'password',
65
+ database: process.env.DB_NAME || 'oauth_db',
66
+ synchronize: process.env.NODE_ENV !== 'production',
67
+ logging: process.env.NODE_ENV === 'development',
68
+ },
69
+ })
70
+ ```
71
+
72
+ ### MySQL Configuration
73
+
74
+ ```typescript
75
+ McpOAuthModule.forRoot({
76
+ provider: GoogleOAuthProvider,
77
+ clientId: process.env.GOOGLE_CLIENT_ID!,
78
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
79
+ jwtSecret: process.env.JWT_SECRET!,
80
+ typeormOptions: {
81
+ type: 'mysql',
82
+ host: process.env.DB_HOST || 'localhost',
83
+ port: parseInt(process.env.DB_PORT) || 3306,
84
+ username: process.env.DB_USERNAME || 'root',
85
+ password: process.env.DB_PASSWORD || 'password',
86
+ database: process.env.DB_NAME || 'oauth_db',
87
+ synchronize: process.env.NODE_ENV !== 'production',
88
+ logging: process.env.NODE_ENV === 'development',
89
+ },
90
+ })
91
+ ```
92
+
93
+ ## Configuration Options
94
+
95
+ All standard TypeORM options are supported:
96
+
97
+ - `type`: Database type (sqlite, postgres, mysql, etc.)
98
+ - `host`: Database host
99
+ - `port`: Database port
100
+ - `username`: Database username
101
+ - `password`: Database password
102
+ - `database`: Database name or file path (for SQLite)
103
+ - `synchronize`: Auto-create tables (disable in production)
104
+ - `logging`: Enable query logging
105
+ - `entities`: Additional entities (OAuth entities are automatically included)
106
+ - `migrations`: Database migrations
107
+ - `ssl`: SSL configuration for production databases
108
+
109
+ ## Production Considerations
110
+
111
+ 1. **Disable synchronize**: Use migrations instead of auto-sync in production
112
+ 2. **Enable SSL**: Configure SSL for production database connections
113
+ 3. **Connection Pooling**: Configure appropriate connection pool settings
114
+ 4. **Logging**: Disable query logging in production for performance
115
+ 5. **Backup Strategy**: Implement regular database backups
116
+
117
+ ## Migrations
118
+
119
+ For production use, create migrations instead of using `synchronize: true`:
120
+
121
+ ```bash
122
+ npx typeorm migration:generate -n CreateOAuthTables
123
+ npx typeorm migration:run
124
+ ```
125
+
126
+ ## Interface Compatibility
127
+
128
+ The TypeORM implementation provides both synchronous and asynchronous methods:
129
+
130
+ - Synchronous methods (for IOAuthStore compatibility): Log warnings and use fire-and-forget async operations
131
+ - Asynchronous methods (recommended): `*Async` versions that return promises
132
+
133
+ Consider updating your application to use the async methods for better error handling and performance.
@@ -0,0 +1,34 @@
1
+ import { Entity, Column, PrimaryColumn, CreateDateColumn } from 'typeorm';
2
+
3
+ @Entity('authorization_codes')
4
+ export class AuthorizationCodeEntity {
5
+ @PrimaryColumn()
6
+ code: string;
7
+
8
+ @Column()
9
+ user_id: string;
10
+
11
+ @Column()
12
+ client_id: string;
13
+
14
+ @Column()
15
+ redirect_uri: string;
16
+
17
+ @Column()
18
+ code_challenge: string;
19
+
20
+ @Column()
21
+ code_challenge_method: string;
22
+
23
+ @Column('bigint')
24
+ expires_at: number;
25
+
26
+ @Column({ nullable: true })
27
+ used_at?: Date;
28
+
29
+ @Column()
30
+ github_access_token: string;
31
+
32
+ @CreateDateColumn()
33
+ created_at: Date;
34
+ }
@@ -0,0 +1,3 @@
1
+ export { OAuthClientEntity } from './oauth-client.entity';
2
+ export { AuthorizationCodeEntity } from './authorization-code.entity';
3
+ export { OAuthSessionEntity } from './oauth-session.entity';
@@ -0,0 +1,49 @@
1
+ import {
2
+ Entity,
3
+ Column,
4
+ PrimaryColumn,
5
+ CreateDateColumn,
6
+ UpdateDateColumn,
7
+ } from 'typeorm';
8
+
9
+ @Entity('oauth_clients')
10
+ export class OAuthClientEntity {
11
+ @PrimaryColumn()
12
+ client_id: string;
13
+
14
+ @Column()
15
+ client_name: string;
16
+
17
+ @Column({ nullable: true })
18
+ client_description?: string;
19
+
20
+ @Column({ nullable: true })
21
+ logo_uri?: string;
22
+
23
+ @Column({ nullable: true })
24
+ client_uri?: string;
25
+
26
+ @Column({ nullable: true })
27
+ developer_name?: string;
28
+
29
+ @Column({ nullable: true })
30
+ developer_email?: string;
31
+
32
+ @Column('simple-array')
33
+ redirect_uris: string[];
34
+
35
+ @Column('simple-array')
36
+ grant_types: string[];
37
+
38
+ @Column('simple-array')
39
+ response_types: string[];
40
+
41
+ @Column()
42
+ token_endpoint_auth_method: string;
43
+
44
+ @CreateDateColumn()
45
+ created_at: Date;
46
+
47
+ @UpdateDateColumn()
48
+ updated_at: Date;
49
+ }
@@ -0,0 +1,34 @@
1
+ import { Entity, Column, PrimaryColumn, CreateDateColumn } from 'typeorm';
2
+
3
+ @Entity('oauth_sessions')
4
+ export class OAuthSessionEntity {
5
+ @PrimaryColumn()
6
+ sessionId: string;
7
+
8
+ @Column()
9
+ state: string;
10
+
11
+ @Column({ nullable: true })
12
+ clientId?: string;
13
+
14
+ @Column({ nullable: true })
15
+ redirectUri?: string;
16
+
17
+ @Column({ nullable: true })
18
+ codeChallenge?: string;
19
+
20
+ @Column({ nullable: true })
21
+ codeChallengeMethod?: string;
22
+
23
+ @Column({ nullable: true })
24
+ oauthState?: string;
25
+
26
+ @Column({ nullable: true })
27
+ resource?: string;
28
+
29
+ @Column('bigint')
30
+ expiresAt: number;
31
+
32
+ @CreateDateColumn()
33
+ created_at: Date;
34
+ }
@@ -0,0 +1,98 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { InjectRepository } from '@nestjs/typeorm';
3
+ import { Repository } from 'typeorm';
4
+ import { randomBytes } from 'crypto';
5
+ import {
6
+ OAuthClientEntity,
7
+ AuthorizationCodeEntity,
8
+ OAuthSessionEntity,
9
+ } from './entities';
10
+ import { OAuthSession } from '../../providers/oauth-provider.interface';
11
+ import {
12
+ AuthorizationCode,
13
+ IOAuthStore,
14
+ OAuthClient,
15
+ } from '../oauth-store.interface';
16
+
17
+ @Injectable()
18
+ export class TypeOrmStore implements IOAuthStore {
19
+ constructor(
20
+ @InjectRepository(OAuthClientEntity)
21
+ private readonly clientRepository: Repository<OAuthClientEntity>,
22
+ @InjectRepository(AuthorizationCodeEntity)
23
+ private readonly authCodeRepository: Repository<AuthorizationCodeEntity>,
24
+ @InjectRepository(OAuthSessionEntity)
25
+ private readonly sessionRepository: Repository<OAuthSessionEntity>,
26
+ ) {}
27
+
28
+ // Client management
29
+ async storeClient(client: OAuthClient): Promise<OAuthClient> {
30
+ const savedClient = await this.clientRepository.save(client);
31
+ return savedClient;
32
+ }
33
+
34
+ async getClient(client_id: string): Promise<OAuthClient | undefined> {
35
+ return (
36
+ (await this.clientRepository.findOne({ where: { client_id } })) ??
37
+ undefined
38
+ );
39
+ }
40
+
41
+ async findClient(client_name: string): Promise<OAuthClient | undefined> {
42
+ return (
43
+ (await this.clientRepository.findOne({ where: { client_name } })) ??
44
+ undefined
45
+ );
46
+ }
47
+
48
+ // Authorization code management
49
+ async storeAuthCode(code: AuthorizationCode): Promise<void> {
50
+ await this.authCodeRepository.save(code);
51
+ }
52
+
53
+ async getAuthCode(code: string): Promise<AuthorizationCode | undefined> {
54
+ const authCode = await this.authCodeRepository.findOne({ where: { code } });
55
+ // Check if expired
56
+ if (authCode && authCode.expires_at < Date.now()) {
57
+ await this.authCodeRepository.delete({ code });
58
+ return undefined;
59
+ }
60
+ return authCode ?? undefined;
61
+ }
62
+
63
+ async removeAuthCode(code: string): Promise<void> {
64
+ await this.authCodeRepository.delete({ code });
65
+ }
66
+
67
+ // OAuth session management
68
+ async storeOAuthSession(
69
+ sessionId: string,
70
+ session: OAuthSession,
71
+ ): Promise<void> {
72
+ await this.sessionRepository.save({ ...session, sessionId });
73
+ }
74
+
75
+ async getOAuthSession(sessionId: string): Promise<OAuthSession | undefined> {
76
+ const session = await this.sessionRepository.findOne({
77
+ where: { sessionId },
78
+ });
79
+ if (session && session.expiresAt < Date.now()) {
80
+ await this.sessionRepository.delete({ sessionId });
81
+ return undefined;
82
+ }
83
+ return session ?? undefined;
84
+ }
85
+
86
+ async removeOAuthSession(sessionId: string): Promise<void> {
87
+ await this.sessionRepository.delete({ sessionId });
88
+ }
89
+
90
+ generateClientId(client: OAuthClient): string {
91
+ // Create deterministic client ID based on name + random salt
92
+ const normalizedName = client.client_name
93
+ .toLowerCase()
94
+ .replace(/[^a-z0-9]/g, '');
95
+ const salt = randomBytes(4).toString('hex');
96
+ return `${normalizedName}_${salt}`;
97
+ }
98
+ }
@@ -42,7 +42,7 @@ export class McpToolsHandler extends McpHandlerBase {
42
42
  if (!validation.success) {
43
43
  throw new McpError(
44
44
  ErrorCode.InternalError,
45
- `Tool result does not match outputSchema: ${validation.error.message}`
45
+ `Tool result does not match outputSchema: ${validation.error.message}`,
46
46
  );
47
47
  }
48
48
  return {
@@ -140,7 +140,10 @@ export class McpToolsHandler extends McpHandlerBase {
140
140
  httpRequest,
141
141
  );
142
142
 
143
- const transformedResult = this.formatToolResult(result, toolInfo.metadata.outputSchema);
143
+ const transformedResult = this.formatToolResult(
144
+ result,
145
+ toolInfo.metadata.outputSchema,
146
+ );
144
147
 
145
148
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
146
149
  this.logger.debug(transformedResult, 'CallToolRequestSchema result');