db-mcp 1.0.2 → 1.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.
Files changed (241) hide show
  1. package/.gitattributes +2 -0
  2. package/LICENSE +1 -1
  3. package/README.md +244 -518
  4. package/dist/chunk-4IA3DB5C.js +135 -0
  5. package/dist/chunk-AOUL5SHS.js +930 -0
  6. package/dist/chunk-DWJXQEFA.js +13759 -0
  7. package/dist/chunk-DZQLDEQS.js +879 -0
  8. package/dist/chunk-S5IDDSSB.js +74 -0
  9. package/dist/chunk-Z2GFQU3G.js +363 -0
  10. package/dist/cli.d.ts +0 -7
  11. package/dist/cli.js +227 -183
  12. package/dist/http-VSB7DBJR.js +1541 -0
  13. package/dist/index.d.ts +842 -10
  14. package/dist/index.js +5 -17
  15. package/dist/sqlite-VHBA4ABV.js +600 -0
  16. package/dist/sqlite-native-D2LH5ZT7.js +2498 -0
  17. package/dist/worker-script.d.ts +2 -0
  18. package/dist/worker-script.js +126 -0
  19. package/package.json +26 -16
  20. package/playwright.config.ts +101 -0
  21. package/scripts/generate-server-instructions.ts +111 -0
  22. package/server.json +5 -6
  23. package/test-server/README.md +118 -0
  24. package/test-server/code-map.md +409 -0
  25. package/test-server/fixtures/sample.csv +6 -0
  26. package/test-server/reset-database.ps1 +373 -0
  27. package/test-server/sample.csv +11 -0
  28. package/test-server/test-agent-experience.md +243 -0
  29. package/test-server/test-database.sql +388 -0
  30. package/test-server/test-group-tools.md +861 -0
  31. package/test-server/test-help-resources.mjs +238 -0
  32. package/test-server/test-preflight.md +53 -0
  33. package/test-server/test-prompts.md +354 -0
  34. package/test-server/test-resources.md +245 -0
  35. package/test-server/test-tool-annotations.mjs +157 -0
  36. package/test-server/test-tools-advanced-1.md +517 -0
  37. package/test-server/test-tools-advanced-2.md +487 -0
  38. package/test-server/test-tools-codemode.md +629 -0
  39. package/test-server/test-tools.md +176 -0
  40. package/test-server/tool-reference.md +236 -0
  41. package/tsconfig.test.json +9 -0
  42. package/dist/adapters/DatabaseAdapter.d.ts +0 -141
  43. package/dist/adapters/DatabaseAdapter.d.ts.map +0 -1
  44. package/dist/adapters/DatabaseAdapter.js +0 -131
  45. package/dist/adapters/DatabaseAdapter.js.map +0 -1
  46. package/dist/adapters/sqlite/SchemaManager.d.ts +0 -58
  47. package/dist/adapters/sqlite/SchemaManager.d.ts.map +0 -1
  48. package/dist/adapters/sqlite/SchemaManager.js +0 -187
  49. package/dist/adapters/sqlite/SchemaManager.js.map +0 -1
  50. package/dist/adapters/sqlite/SqliteAdapter.d.ts +0 -161
  51. package/dist/adapters/sqlite/SqliteAdapter.d.ts.map +0 -1
  52. package/dist/adapters/sqlite/SqliteAdapter.js +0 -741
  53. package/dist/adapters/sqlite/SqliteAdapter.js.map +0 -1
  54. package/dist/adapters/sqlite/index.d.ts +0 -9
  55. package/dist/adapters/sqlite/index.d.ts.map +0 -1
  56. package/dist/adapters/sqlite/index.js +0 -8
  57. package/dist/adapters/sqlite/index.js.map +0 -1
  58. package/dist/adapters/sqlite/json-utils.d.ts +0 -100
  59. package/dist/adapters/sqlite/json-utils.d.ts.map +0 -1
  60. package/dist/adapters/sqlite/json-utils.js +0 -274
  61. package/dist/adapters/sqlite/json-utils.js.map +0 -1
  62. package/dist/adapters/sqlite/output-schemas.d.ts +0 -1187
  63. package/dist/adapters/sqlite/output-schemas.d.ts.map +0 -1
  64. package/dist/adapters/sqlite/output-schemas.js +0 -1337
  65. package/dist/adapters/sqlite/output-schemas.js.map +0 -1
  66. package/dist/adapters/sqlite/prompts.d.ts +0 -13
  67. package/dist/adapters/sqlite/prompts.d.ts.map +0 -1
  68. package/dist/adapters/sqlite/prompts.js +0 -605
  69. package/dist/adapters/sqlite/prompts.js.map +0 -1
  70. package/dist/adapters/sqlite/resources.d.ts +0 -13
  71. package/dist/adapters/sqlite/resources.d.ts.map +0 -1
  72. package/dist/adapters/sqlite/resources.js +0 -251
  73. package/dist/adapters/sqlite/resources.js.map +0 -1
  74. package/dist/adapters/sqlite/tools/admin.d.ts +0 -14
  75. package/dist/adapters/sqlite/tools/admin.d.ts.map +0 -1
  76. package/dist/adapters/sqlite/tools/admin.js +0 -788
  77. package/dist/adapters/sqlite/tools/admin.js.map +0 -1
  78. package/dist/adapters/sqlite/tools/core.d.ts +0 -25
  79. package/dist/adapters/sqlite/tools/core.d.ts.map +0 -1
  80. package/dist/adapters/sqlite/tools/core.js +0 -359
  81. package/dist/adapters/sqlite/tools/core.js.map +0 -1
  82. package/dist/adapters/sqlite/tools/fts.d.ts +0 -13
  83. package/dist/adapters/sqlite/tools/fts.d.ts.map +0 -1
  84. package/dist/adapters/sqlite/tools/fts.js +0 -347
  85. package/dist/adapters/sqlite/tools/fts.js.map +0 -1
  86. package/dist/adapters/sqlite/tools/geo.d.ts +0 -14
  87. package/dist/adapters/sqlite/tools/geo.d.ts.map +0 -1
  88. package/dist/adapters/sqlite/tools/geo.js +0 -252
  89. package/dist/adapters/sqlite/tools/geo.js.map +0 -1
  90. package/dist/adapters/sqlite/tools/index.d.ts +0 -30
  91. package/dist/adapters/sqlite/tools/index.d.ts.map +0 -1
  92. package/dist/adapters/sqlite/tools/index.js +0 -61
  93. package/dist/adapters/sqlite/tools/index.js.map +0 -1
  94. package/dist/adapters/sqlite/tools/json-helpers.d.ts +0 -14
  95. package/dist/adapters/sqlite/tools/json-helpers.d.ts.map +0 -1
  96. package/dist/adapters/sqlite/tools/json-helpers.js +0 -477
  97. package/dist/adapters/sqlite/tools/json-helpers.js.map +0 -1
  98. package/dist/adapters/sqlite/tools/json-operations.d.ts +0 -14
  99. package/dist/adapters/sqlite/tools/json-operations.d.ts.map +0 -1
  100. package/dist/adapters/sqlite/tools/json-operations.js +0 -839
  101. package/dist/adapters/sqlite/tools/json-operations.js.map +0 -1
  102. package/dist/adapters/sqlite/tools/stats.d.ts +0 -15
  103. package/dist/adapters/sqlite/tools/stats.d.ts.map +0 -1
  104. package/dist/adapters/sqlite/tools/stats.js +0 -1219
  105. package/dist/adapters/sqlite/tools/stats.js.map +0 -1
  106. package/dist/adapters/sqlite/tools/text.d.ts +0 -14
  107. package/dist/adapters/sqlite/tools/text.d.ts.map +0 -1
  108. package/dist/adapters/sqlite/tools/text.js +0 -1141
  109. package/dist/adapters/sqlite/tools/text.js.map +0 -1
  110. package/dist/adapters/sqlite/tools/vector.d.ts +0 -14
  111. package/dist/adapters/sqlite/tools/vector.d.ts.map +0 -1
  112. package/dist/adapters/sqlite/tools/vector.js +0 -613
  113. package/dist/adapters/sqlite/tools/vector.js.map +0 -1
  114. package/dist/adapters/sqlite/tools/virtual.d.ts +0 -13
  115. package/dist/adapters/sqlite/tools/virtual.d.ts.map +0 -1
  116. package/dist/adapters/sqlite/tools/virtual.js +0 -930
  117. package/dist/adapters/sqlite/tools/virtual.js.map +0 -1
  118. package/dist/adapters/sqlite/types.d.ts +0 -207
  119. package/dist/adapters/sqlite/types.d.ts.map +0 -1
  120. package/dist/adapters/sqlite/types.js +0 -186
  121. package/dist/adapters/sqlite/types.js.map +0 -1
  122. package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts +0 -163
  123. package/dist/adapters/sqlite-native/NativeSqliteAdapter.d.ts.map +0 -1
  124. package/dist/adapters/sqlite-native/NativeSqliteAdapter.js +0 -748
  125. package/dist/adapters/sqlite-native/NativeSqliteAdapter.js.map +0 -1
  126. package/dist/adapters/sqlite-native/index.d.ts +0 -11
  127. package/dist/adapters/sqlite-native/index.d.ts.map +0 -1
  128. package/dist/adapters/sqlite-native/index.js +0 -11
  129. package/dist/adapters/sqlite-native/index.js.map +0 -1
  130. package/dist/adapters/sqlite-native/tools/spatialite.d.ts +0 -19
  131. package/dist/adapters/sqlite-native/tools/spatialite.d.ts.map +0 -1
  132. package/dist/adapters/sqlite-native/tools/spatialite.js +0 -628
  133. package/dist/adapters/sqlite-native/tools/spatialite.js.map +0 -1
  134. package/dist/adapters/sqlite-native/tools/transactions.d.ts +0 -12
  135. package/dist/adapters/sqlite-native/tools/transactions.d.ts.map +0 -1
  136. package/dist/adapters/sqlite-native/tools/transactions.js +0 -255
  137. package/dist/adapters/sqlite-native/tools/transactions.js.map +0 -1
  138. package/dist/adapters/sqlite-native/tools/window.d.ts +0 -12
  139. package/dist/adapters/sqlite-native/tools/window.d.ts.map +0 -1
  140. package/dist/adapters/sqlite-native/tools/window.js +0 -370
  141. package/dist/adapters/sqlite-native/tools/window.js.map +0 -1
  142. package/dist/auth/AuthorizationServerDiscovery.d.ts +0 -90
  143. package/dist/auth/AuthorizationServerDiscovery.d.ts.map +0 -1
  144. package/dist/auth/AuthorizationServerDiscovery.js +0 -204
  145. package/dist/auth/AuthorizationServerDiscovery.js.map +0 -1
  146. package/dist/auth/OAuthResourceServer.d.ts +0 -65
  147. package/dist/auth/OAuthResourceServer.d.ts.map +0 -1
  148. package/dist/auth/OAuthResourceServer.js +0 -121
  149. package/dist/auth/OAuthResourceServer.js.map +0 -1
  150. package/dist/auth/TokenValidator.d.ts +0 -60
  151. package/dist/auth/TokenValidator.d.ts.map +0 -1
  152. package/dist/auth/TokenValidator.js +0 -235
  153. package/dist/auth/TokenValidator.js.map +0 -1
  154. package/dist/auth/errors.d.ts +0 -74
  155. package/dist/auth/errors.d.ts.map +0 -1
  156. package/dist/auth/errors.js +0 -133
  157. package/dist/auth/errors.js.map +0 -1
  158. package/dist/auth/index.d.ts +0 -13
  159. package/dist/auth/index.d.ts.map +0 -1
  160. package/dist/auth/index.js +0 -15
  161. package/dist/auth/index.js.map +0 -1
  162. package/dist/auth/middleware.d.ts +0 -81
  163. package/dist/auth/middleware.d.ts.map +0 -1
  164. package/dist/auth/middleware.js +0 -291
  165. package/dist/auth/middleware.js.map +0 -1
  166. package/dist/auth/scopes.d.ts +0 -136
  167. package/dist/auth/scopes.d.ts.map +0 -1
  168. package/dist/auth/scopes.js +0 -349
  169. package/dist/auth/scopes.js.map +0 -1
  170. package/dist/auth/types.d.ts +0 -257
  171. package/dist/auth/types.d.ts.map +0 -1
  172. package/dist/auth/types.js +0 -8
  173. package/dist/auth/types.js.map +0 -1
  174. package/dist/cli.d.ts.map +0 -1
  175. package/dist/cli.js.map +0 -1
  176. package/dist/constants/ServerInstructions.d.ts +0 -45
  177. package/dist/constants/ServerInstructions.d.ts.map +0 -1
  178. package/dist/constants/ServerInstructions.js +0 -356
  179. package/dist/constants/ServerInstructions.js.map +0 -1
  180. package/dist/filtering/ToolConstants.d.ts +0 -34
  181. package/dist/filtering/ToolConstants.d.ts.map +0 -1
  182. package/dist/filtering/ToolConstants.js +0 -174
  183. package/dist/filtering/ToolConstants.js.map +0 -1
  184. package/dist/filtering/ToolFilter.d.ts +0 -82
  185. package/dist/filtering/ToolFilter.d.ts.map +0 -1
  186. package/dist/filtering/ToolFilter.js +0 -296
  187. package/dist/filtering/ToolFilter.js.map +0 -1
  188. package/dist/index.d.ts.map +0 -1
  189. package/dist/index.js.map +0 -1
  190. package/dist/server/McpServer.d.ts +0 -61
  191. package/dist/server/McpServer.d.ts.map +0 -1
  192. package/dist/server/McpServer.js +0 -270
  193. package/dist/server/McpServer.js.map +0 -1
  194. package/dist/transports/http.d.ts +0 -134
  195. package/dist/transports/http.d.ts.map +0 -1
  196. package/dist/transports/http.js +0 -516
  197. package/dist/transports/http.js.map +0 -1
  198. package/dist/transports/index.d.ts +0 -5
  199. package/dist/transports/index.d.ts.map +0 -1
  200. package/dist/transports/index.js +0 -5
  201. package/dist/transports/index.js.map +0 -1
  202. package/dist/types/index.d.ts +0 -380
  203. package/dist/types/index.d.ts.map +0 -1
  204. package/dist/types/index.js +0 -68
  205. package/dist/types/index.js.map +0 -1
  206. package/dist/utils/annotations.d.ts +0 -44
  207. package/dist/utils/annotations.d.ts.map +0 -1
  208. package/dist/utils/annotations.js +0 -77
  209. package/dist/utils/annotations.js.map +0 -1
  210. package/dist/utils/errors.d.ts +0 -155
  211. package/dist/utils/errors.d.ts.map +0 -1
  212. package/dist/utils/errors.js +0 -329
  213. package/dist/utils/errors.js.map +0 -1
  214. package/dist/utils/identifiers.d.ts +0 -121
  215. package/dist/utils/identifiers.d.ts.map +0 -1
  216. package/dist/utils/identifiers.js +0 -319
  217. package/dist/utils/identifiers.js.map +0 -1
  218. package/dist/utils/index.d.ts +0 -7
  219. package/dist/utils/index.d.ts.map +0 -1
  220. package/dist/utils/index.js +0 -7
  221. package/dist/utils/index.js.map +0 -1
  222. package/dist/utils/insightsManager.d.ts +0 -39
  223. package/dist/utils/insightsManager.d.ts.map +0 -1
  224. package/dist/utils/insightsManager.js +0 -63
  225. package/dist/utils/insightsManager.js.map +0 -1
  226. package/dist/utils/logger.d.ts +0 -189
  227. package/dist/utils/logger.d.ts.map +0 -1
  228. package/dist/utils/logger.js +0 -394
  229. package/dist/utils/logger.js.map +0 -1
  230. package/dist/utils/progress-utils.d.ts +0 -54
  231. package/dist/utils/progress-utils.d.ts.map +0 -1
  232. package/dist/utils/progress-utils.js +0 -74
  233. package/dist/utils/progress-utils.js.map +0 -1
  234. package/dist/utils/resourceAnnotations.d.ts +0 -36
  235. package/dist/utils/resourceAnnotations.d.ts.map +0 -1
  236. package/dist/utils/resourceAnnotations.js +0 -57
  237. package/dist/utils/resourceAnnotations.js.map +0 -1
  238. package/dist/utils/where-clause.d.ts +0 -41
  239. package/dist/utils/where-clause.d.ts.map +0 -1
  240. package/dist/utils/where-clause.js +0 -116
  241. package/dist/utils/where-clause.js.map +0 -1
@@ -0,0 +1,1541 @@
1
+ import './chunk-4IA3DB5C.js';
2
+ import { createModuleLogger, DbMcpError, ERROR_CODES } from './chunk-AOUL5SHS.js';
3
+ import express from 'express';
4
+ import { localhostHostValidation } from '@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js';
5
+ import cors from 'cors';
6
+ import { randomUUID } from 'crypto';
7
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
9
+ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
10
+ import * as jose from 'jose';
11
+
12
+ // src/auth/middleware/extraction.ts
13
+ function extractBearerToken(authHeader) {
14
+ if (!authHeader) {
15
+ return null;
16
+ }
17
+ const parts = authHeader.split(" ");
18
+ const scheme = parts[0];
19
+ const tokenPart = parts[1];
20
+ if (parts.length !== 2 || scheme?.toLowerCase() !== "bearer") {
21
+ return null;
22
+ }
23
+ if (tokenPart === void 0) {
24
+ return null;
25
+ }
26
+ const token = tokenPart.trim();
27
+ return token.length > 0 ? token : null;
28
+ }
29
+
30
+ // src/auth/errors.ts
31
+ var OAuthError = class extends DbMcpError {
32
+ /** HTTP status code for this error */
33
+ httpStatus;
34
+ /** WWW-Authenticate header value */
35
+ wwwAuthenticate;
36
+ constructor(message, code, httpStatus, details, wwwAuthenticate) {
37
+ super(message, code, "authentication" /* AUTHENTICATION */, { details });
38
+ this.name = "OAuthError";
39
+ this.httpStatus = httpStatus;
40
+ this.wwwAuthenticate = wwwAuthenticate;
41
+ }
42
+ };
43
+ var TokenMissingError = class extends OAuthError {
44
+ constructor(realm = "db-mcp") {
45
+ super(
46
+ "No access token provided",
47
+ ERROR_CODES.AUTH.TOKEN_MISSING.full,
48
+ 401,
49
+ void 0,
50
+ `Bearer realm="${realm}"`
51
+ );
52
+ this.name = "TokenMissingError";
53
+ }
54
+ };
55
+ var InvalidTokenError = class extends OAuthError {
56
+ constructor(message = "Invalid access token", details) {
57
+ super(
58
+ message,
59
+ ERROR_CODES.AUTH.TOKEN_INVALID.full,
60
+ 401,
61
+ details,
62
+ 'Bearer error="invalid_token"'
63
+ );
64
+ this.name = "InvalidTokenError";
65
+ }
66
+ };
67
+ var TokenExpiredError = class extends OAuthError {
68
+ constructor(expiredAt) {
69
+ super(
70
+ "Access token has expired",
71
+ ERROR_CODES.AUTH.TOKEN_EXPIRED.full,
72
+ 401,
73
+ expiredAt ? { expiredAt: expiredAt.toISOString() } : void 0,
74
+ 'Bearer error="invalid_token", error_description="Token has expired"'
75
+ );
76
+ this.name = "TokenExpiredError";
77
+ }
78
+ };
79
+ var InvalidSignatureError = class extends OAuthError {
80
+ constructor(message = "Token signature verification failed") {
81
+ super(
82
+ message,
83
+ ERROR_CODES.AUTH.SIGNATURE_INVALID.full,
84
+ 401,
85
+ void 0,
86
+ 'Bearer error="invalid_token", error_description="Signature verification failed"'
87
+ );
88
+ this.name = "InvalidSignatureError";
89
+ }
90
+ };
91
+ var AuthServerDiscoveryError = class extends OAuthError {
92
+ constructor(serverUrl, cause) {
93
+ super(
94
+ `Failed to discover authorization server metadata: ${serverUrl}`,
95
+ ERROR_CODES.AUTH.DISCOVERY_FAILED.full,
96
+ 500,
97
+ {
98
+ serverUrl,
99
+ cause: cause?.message
100
+ }
101
+ );
102
+ this.name = "AuthServerDiscoveryError";
103
+ }
104
+ };
105
+ var JwksFetchError = class extends OAuthError {
106
+ constructor(jwksUri, cause) {
107
+ super(
108
+ `Failed to fetch JWKS: ${jwksUri}`,
109
+ ERROR_CODES.AUTH.JWKS_FETCH_FAILED.full,
110
+ 500,
111
+ {
112
+ jwksUri,
113
+ cause: cause?.message
114
+ }
115
+ );
116
+ this.name = "JwksFetchError";
117
+ }
118
+ };
119
+ function isOAuthError(error) {
120
+ return error instanceof OAuthError;
121
+ }
122
+
123
+ // src/auth/scopes/constants.ts
124
+ var SCOPES = {
125
+ /** Read-only access to all databases */
126
+ READ: "read",
127
+ /** Read and write access to all databases */
128
+ WRITE: "write",
129
+ /** Administrative access */
130
+ ADMIN: "admin"};
131
+ var BASE_SCOPES = ["read", "write", "admin", "full"];
132
+ var SCOPE_PATTERNS = {
133
+ /** Database-specific access pattern */
134
+ DATABASE: /^db:([a-zA-Z0-9_-]+)$/,
135
+ /** Table-specific access pattern */
136
+ TABLE: /^table:([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)$/
137
+ };
138
+ var SUPPORTED_SCOPES = [
139
+ "read",
140
+ "write",
141
+ "admin",
142
+ "full",
143
+ "db:{database}",
144
+ "table:{database}:{table}"
145
+ ];
146
+ function parseScopes(scopeString) {
147
+ return scopeString.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0);
148
+ }
149
+
150
+ // src/auth/scopes/mapping.ts
151
+ var TOOL_GROUP_SCOPES = {
152
+ core: SCOPES.READ,
153
+ json: SCOPES.READ,
154
+ text: SCOPES.READ,
155
+ stats: SCOPES.READ,
156
+ vector: SCOPES.READ,
157
+ geo: SCOPES.READ,
158
+ introspection: SCOPES.READ,
159
+ migration: SCOPES.WRITE,
160
+ admin: SCOPES.ADMIN,
161
+ codemode: SCOPES.ADMIN
162
+ };
163
+ var groupsForScope = (maxScope) => {
164
+ const hierarchy = {
165
+ read: 0,
166
+ write: 1,
167
+ admin: 2,
168
+ full: 3
169
+ };
170
+ const maxLevel = hierarchy[maxScope];
171
+ return Object.entries(TOOL_GROUP_SCOPES).filter(([, scope]) => hierarchy[scope] <= maxLevel).map(([group]) => group);
172
+ };
173
+ groupsForScope(SCOPES.READ);
174
+ groupsForScope(SCOPES.WRITE);
175
+ groupsForScope(SCOPES.ADMIN);
176
+
177
+ // src/auth/middleware/express-auth.ts
178
+ var logger = createModuleLogger("AUTH");
179
+ function isPublicPath(path, publicPaths) {
180
+ if (path.startsWith("/.well-known/")) {
181
+ return true;
182
+ }
183
+ for (const pattern of publicPaths) {
184
+ if (pattern === path) {
185
+ return true;
186
+ }
187
+ if (pattern.endsWith("/*")) {
188
+ const prefix = pattern.slice(0, -2);
189
+ if (path === prefix || path.startsWith(prefix + "/")) {
190
+ return true;
191
+ }
192
+ }
193
+ }
194
+ return false;
195
+ }
196
+ function createAuthMiddleware(config) {
197
+ const { tokenValidator, resourceServer, publicPaths = [] } = config;
198
+ return async (req, res, next) => {
199
+ const requestId = crypto.randomUUID();
200
+ req.requestId = requestId;
201
+ if (isPublicPath(req.path, publicPaths)) {
202
+ logger.info(`Public path accessed: ${req.path}`, {
203
+ code: "AUTH_PUBLIC_PATH",
204
+ requestId,
205
+ path: req.path
206
+ });
207
+ next();
208
+ return;
209
+ }
210
+ const token = extractBearerToken(req.headers.authorization);
211
+ if (!token) {
212
+ const error = new TokenMissingError(resourceServer.getResourceUri());
213
+ logger.warning("No access token provided", {
214
+ code: ERROR_CODES.AUTH.TOKEN_MISSING.full,
215
+ requestId,
216
+ path: req.path
217
+ });
218
+ res.status(error.httpStatus);
219
+ res.setHeader("WWW-Authenticate", error.wwwAuthenticate ?? "");
220
+ res.json({
221
+ error: "unauthorized",
222
+ error_description: error.message
223
+ });
224
+ return;
225
+ }
226
+ const result = await tokenValidator.validate(token);
227
+ if (!result.valid) {
228
+ logger.warning(
229
+ `Token validation failed: ${result.error ?? "Unknown error"}`,
230
+ {
231
+ code: result.errorCode ?? ERROR_CODES.AUTH.TOKEN_INVALID.full,
232
+ requestId,
233
+ path: req.path
234
+ }
235
+ );
236
+ res.status(401);
237
+ res.setHeader(
238
+ "WWW-Authenticate",
239
+ resourceServer.getWWWAuthenticateHeader("invalid_token", result.error)
240
+ );
241
+ res.json({
242
+ error: "invalid_token",
243
+ error_description: result.error
244
+ });
245
+ return;
246
+ }
247
+ const claims = result.claims;
248
+ if (!claims) {
249
+ res.status(500).json({ error: "internal_error" });
250
+ return;
251
+ }
252
+ req.auth = claims;
253
+ req.accessToken = token;
254
+ logger.info(`Request authenticated: ${claims.sub}`, {
255
+ code: "AUTH_SUCCESS",
256
+ requestId,
257
+ sub: claims.sub,
258
+ scopes: claims.scopes.length,
259
+ path: req.path
260
+ });
261
+ next();
262
+ };
263
+ }
264
+
265
+ // src/auth/middleware/express-scopes.ts
266
+ createModuleLogger("AUTH");
267
+ function oauthErrorHandler(error, _req, res, next) {
268
+ if (isOAuthError(error)) {
269
+ res.status(error.httpStatus);
270
+ if (error.wwwAuthenticate) {
271
+ res.setHeader("WWW-Authenticate", error.wwwAuthenticate);
272
+ }
273
+ res.json({
274
+ error: error.code,
275
+ error_description: error.message
276
+ });
277
+ return;
278
+ }
279
+ next(error);
280
+ }
281
+
282
+ // src/transports/http/types.ts
283
+ var DEFAULT_RATE_LIMIT_WINDOW_MS = 6e4;
284
+ var DEFAULT_RATE_LIMIT_MAX = 100;
285
+ var DEFAULT_MAX_BODY_BYTES = 1048576;
286
+ var DEFAULT_HSTS_MAX_AGE = 31536e3;
287
+ var JSONRPC_SERVER_ERROR = -32e3;
288
+ var JSONRPC_INTERNAL_ERROR = -32603;
289
+ var HTTP_REQUEST_TIMEOUT_MS = 12e4;
290
+ var HTTP_KEEP_ALIVE_TIMEOUT_MS = 65e3;
291
+ var HTTP_HEADERS_TIMEOUT_MS = 66e3;
292
+ function getClientIp(req, trustProxy) {
293
+ if (trustProxy) {
294
+ const forwarded = req.headers["x-forwarded-for"];
295
+ if (typeof forwarded === "string") {
296
+ const firstIp = forwarded.split(",")[0]?.trim();
297
+ if (firstIp) return firstIp;
298
+ }
299
+ }
300
+ return req.ip ?? req.socket.remoteAddress ?? "unknown";
301
+ }
302
+ function setupSecurityHeaders(state) {
303
+ if (!state.app) return;
304
+ state.app.use((_req, res, next) => {
305
+ res.setHeader("X-Content-Type-Options", "nosniff");
306
+ res.setHeader("X-Frame-Options", "DENY");
307
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
308
+ res.setHeader(
309
+ "Content-Security-Policy",
310
+ "default-src 'none'; frame-ancestors 'none'"
311
+ );
312
+ res.setHeader(
313
+ "Permissions-Policy",
314
+ "camera=(), microphone=(), geolocation=()"
315
+ );
316
+ res.setHeader("Referrer-Policy", "no-referrer");
317
+ if (state.config.enableHSTS) {
318
+ const maxAge = state.config.hstsMaxAge ?? DEFAULT_HSTS_MAX_AGE;
319
+ res.setHeader(
320
+ "Strict-Transport-Security",
321
+ `max-age=${String(maxAge)}; includeSubDomains`
322
+ );
323
+ }
324
+ next();
325
+ });
326
+ }
327
+ function matchesCorsOrigin(origin, pattern) {
328
+ if (pattern === "*") return true;
329
+ if (pattern.startsWith("*.")) {
330
+ const domain = pattern.slice(1);
331
+ return origin.endsWith(domain) && origin.length > domain.length;
332
+ }
333
+ return origin === pattern;
334
+ }
335
+ function setupCors(state) {
336
+ if (!state.app) return;
337
+ const corsOrigins = state.config.corsOrigins ?? ["*"];
338
+ const isWildcard = corsOrigins.includes("*");
339
+ state.app.use((req, res, next) => {
340
+ const origin = req.headers.origin;
341
+ if (isWildcard) {
342
+ res.setHeader("Access-Control-Allow-Origin", "*");
343
+ } else if (origin && corsOrigins.some((pattern) => matchesCorsOrigin(origin, pattern))) {
344
+ res.setHeader("Access-Control-Allow-Origin", origin);
345
+ res.setHeader("Vary", "Origin");
346
+ res.setHeader("Access-Control-Allow-Credentials", "true");
347
+ }
348
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
349
+ res.setHeader(
350
+ "Access-Control-Allow-Headers",
351
+ "Content-Type, Accept, Authorization, mcp-session-id, Last-Event-ID, mcp-protocol-version"
352
+ );
353
+ res.setHeader("Access-Control-Expose-Headers", "mcp-session-id");
354
+ if (req.method === "OPTIONS") {
355
+ res.setHeader("Access-Control-Max-Age", "86400");
356
+ res.status(204).end();
357
+ return;
358
+ }
359
+ next();
360
+ });
361
+ if (state.config.cors) {
362
+ state.app.use(cors(state.config.cors));
363
+ }
364
+ }
365
+ function setupRateLimiting(state) {
366
+ if (!state.app) return;
367
+ const windowMs = DEFAULT_RATE_LIMIT_WINDOW_MS;
368
+ const maxRequests = process.env["MCP_RATE_LIMIT_MAX"] ? parseInt(process.env["MCP_RATE_LIMIT_MAX"], 10) : DEFAULT_RATE_LIMIT_MAX;
369
+ const trustProxy = state.config.trustProxy ?? false;
370
+ state.rateLimitCleanupTimer = setInterval(() => {
371
+ const now = Date.now();
372
+ for (const [ip, entry] of state.rateLimitMap) {
373
+ if (now >= entry.resetAt) {
374
+ state.rateLimitMap.delete(ip);
375
+ }
376
+ }
377
+ }, windowMs);
378
+ state.rateLimitCleanupTimer.unref();
379
+ state.app.use((req, res, next) => {
380
+ if (req.path === "/health") {
381
+ next();
382
+ return;
383
+ }
384
+ const ip = getClientIp(req, trustProxy);
385
+ const now = Date.now();
386
+ let entry = state.rateLimitMap.get(ip);
387
+ if (!entry || now >= entry.resetAt) {
388
+ entry = { count: 0, resetAt: now + windowMs };
389
+ state.rateLimitMap.set(ip, entry);
390
+ }
391
+ entry.count++;
392
+ if (entry.count > maxRequests) {
393
+ const retryAfter = Math.ceil((entry.resetAt - now) / 1e3);
394
+ res.setHeader("Retry-After", String(retryAfter));
395
+ res.status(429).json({
396
+ error: "Too Many Requests",
397
+ retryAfter
398
+ });
399
+ return;
400
+ }
401
+ next();
402
+ });
403
+ }
404
+
405
+ // src/transports/http/type-adapters.ts
406
+ function asIncoming(req) {
407
+ return req;
408
+ }
409
+ function asServerResponse(res) {
410
+ return res;
411
+ }
412
+
413
+ // src/transports/http/session.ts
414
+ var logger3 = createModuleLogger("HTTP");
415
+ async function setupStatelessEndpoints(state) {
416
+ if (!state.app || !state.mcpServer) {
417
+ throw new DbMcpError(
418
+ "Transport or server not initialized",
419
+ ERROR_CODES.SERVER.TRANSPORT_ERROR.full,
420
+ "internal" /* INTERNAL */
421
+ );
422
+ }
423
+ state.statelessTransport = new StreamableHTTPServerTransport({
424
+ enableJsonResponse: true
425
+ });
426
+ state.statelessTransport.onclose ??= () => {
427
+ logger3.debug("Stateless transport closed", {
428
+ code: "HTTP_STATELESS_CLOSE"
429
+ });
430
+ };
431
+ await state.mcpServer.connect(
432
+ state.statelessTransport
433
+ );
434
+ logger3.info("Stateless transport connected", { code: "HTTP_STATELESS" });
435
+ state.app.post("/mcp", (req, res) => {
436
+ if (!state.statelessTransport) {
437
+ res.status(500).json({ error: "Transport not initialized" });
438
+ return;
439
+ }
440
+ void state.statelessTransport.handleRequest(
441
+ asIncoming(req),
442
+ asServerResponse(res),
443
+ req.body
444
+ );
445
+ });
446
+ state.app.get("/mcp", (_req, res) => {
447
+ res.status(405).json({
448
+ jsonrpc: "2.0",
449
+ error: {
450
+ code: JSONRPC_SERVER_ERROR,
451
+ message: "SSE streaming not available in stateless mode"
452
+ },
453
+ id: null
454
+ });
455
+ });
456
+ state.app.delete("/mcp", (_req, res) => {
457
+ res.status(204).end();
458
+ });
459
+ }
460
+ function setupStatefulEndpoints(state) {
461
+ if (!state.app || !state.mcpServer) {
462
+ throw new DbMcpError(
463
+ "Transport or server not initialized",
464
+ ERROR_CODES.SERVER.TRANSPORT_ERROR.full,
465
+ "internal" /* INTERNAL */
466
+ );
467
+ }
468
+ const server = state.mcpServer;
469
+ state.app.post("/mcp", (req, res) => {
470
+ const sessionId = req.headers["mcp-session-id"];
471
+ if (sessionId && state.sseTransports.has(sessionId)) {
472
+ res.status(400).json({
473
+ jsonrpc: "2.0",
474
+ error: {
475
+ code: JSONRPC_SERVER_ERROR,
476
+ message: "Bad Request: Session exists but uses a different transport protocol"
477
+ },
478
+ id: null
479
+ });
480
+ return;
481
+ }
482
+ const isNewSessionRequest = (body) => {
483
+ if (isInitializeRequest(body)) {
484
+ return true;
485
+ }
486
+ if (Array.isArray(body) && body.length > 0) {
487
+ return isInitializeRequest(body[0]);
488
+ }
489
+ return false;
490
+ };
491
+ void (async () => {
492
+ try {
493
+ let httpTransport;
494
+ if (sessionId && state.transports.has(sessionId)) {
495
+ httpTransport = state.transports.get(sessionId);
496
+ } else if (sessionId === void 0 && isNewSessionRequest(req.body)) {
497
+ const newTransport = new StreamableHTTPServerTransport({
498
+ sessionIdGenerator: () => randomUUID(),
499
+ onsessioninitialized: (sid) => {
500
+ logger3.info("HTTP session initialized", {
501
+ code: "HTTP_SESSION_INIT",
502
+ sessionId: sid
503
+ });
504
+ state.transports.set(sid, newTransport);
505
+ }
506
+ });
507
+ newTransport.onclose = () => {
508
+ const sid = newTransport.sessionId;
509
+ if (sid !== void 0 && state.transports.has(sid)) {
510
+ logger3.info("HTTP transport closed", {
511
+ code: "HTTP_SESSION_CLOSE",
512
+ sessionId: sid
513
+ });
514
+ state.transports.delete(sid);
515
+ }
516
+ };
517
+ try {
518
+ await server.connect(
519
+ newTransport
520
+ );
521
+ } catch {
522
+ await server.close();
523
+ await server.connect(
524
+ newTransport
525
+ );
526
+ }
527
+ await newTransport.handleRequest(
528
+ asIncoming(req),
529
+ asServerResponse(res),
530
+ req.body
531
+ );
532
+ return;
533
+ } else {
534
+ res.status(400).json({
535
+ jsonrpc: "2.0",
536
+ error: {
537
+ code: JSONRPC_SERVER_ERROR,
538
+ message: "Bad Request: No valid session ID provided"
539
+ },
540
+ id: null
541
+ });
542
+ return;
543
+ }
544
+ if (httpTransport !== void 0) {
545
+ await httpTransport.handleRequest(
546
+ asIncoming(req),
547
+ asServerResponse(res),
548
+ req.body
549
+ );
550
+ }
551
+ } catch (error) {
552
+ logger3.error("Error handling MCP request", {
553
+ code: ERROR_CODES.SERVER.TRANSPORT_ERROR.full,
554
+ error: error instanceof Error ? error : void 0
555
+ });
556
+ if (!res.headersSent) {
557
+ res.status(500).json({
558
+ jsonrpc: "2.0",
559
+ error: {
560
+ code: JSONRPC_INTERNAL_ERROR,
561
+ message: "Internal server error"
562
+ },
563
+ id: null
564
+ });
565
+ }
566
+ }
567
+ })();
568
+ });
569
+ state.app.get("/mcp", (req, res) => {
570
+ const sessionId = req.headers["mcp-session-id"];
571
+ if (sessionId === void 0 || !state.transports.has(sessionId)) {
572
+ res.status(400).send("Invalid or missing session ID");
573
+ return;
574
+ }
575
+ const lastEventId = req.headers["last-event-id"];
576
+ if (lastEventId !== void 0) {
577
+ logger3.debug("Client reconnecting with Last-Event-ID", {
578
+ code: "HTTP_SSE_RECONNECT",
579
+ sessionId,
580
+ lastEventId
581
+ });
582
+ }
583
+ const httpTransport = state.transports.get(sessionId);
584
+ if (httpTransport !== void 0) {
585
+ void httpTransport.handleRequest(asIncoming(req), asServerResponse(res));
586
+ }
587
+ });
588
+ state.app.delete("/mcp", (req, res) => {
589
+ const sessionId = req.headers["mcp-session-id"];
590
+ if (sessionId === void 0 || !state.transports.has(sessionId)) {
591
+ res.status(400).send("Invalid or missing session ID");
592
+ return;
593
+ }
594
+ logger3.info("Session termination requested", {
595
+ code: "HTTP_SESSION_DELETE",
596
+ sessionId
597
+ });
598
+ const httpTransport = state.transports.get(sessionId);
599
+ if (httpTransport !== void 0) {
600
+ void httpTransport.handleRequest(asIncoming(req), asServerResponse(res));
601
+ }
602
+ });
603
+ }
604
+ function setupLegacySSEEndpoints(state) {
605
+ if (!state.app || !state.mcpServer) return;
606
+ const server = state.mcpServer;
607
+ state.app.get("/sse", (req, res) => {
608
+ logger3.info("Legacy SSE connection requested", {
609
+ code: "SSE_CONNECT"
610
+ });
611
+ const sseTransport = new SSEServerTransport(
612
+ "/messages",
613
+ asServerResponse(res)
614
+ );
615
+ void (async () => {
616
+ try {
617
+ state.sseTransports.set(sseTransport.sessionId, sseTransport);
618
+ sseTransport.onclose = () => {
619
+ logger3.info("Legacy SSE transport closed", {
620
+ code: "SSE_CLOSE",
621
+ sessionId: sseTransport.sessionId
622
+ });
623
+ state.sseTransports.delete(sseTransport.sessionId);
624
+ };
625
+ try {
626
+ await server.connect(
627
+ sseTransport
628
+ );
629
+ } catch {
630
+ await server.close();
631
+ await server.connect(
632
+ sseTransport
633
+ );
634
+ }
635
+ } catch (error) {
636
+ logger3.error("Error starting SSE transport", {
637
+ code: ERROR_CODES.SERVER.TRANSPORT_ERROR.full,
638
+ error: error instanceof Error ? error : void 0
639
+ });
640
+ if (!res.headersSent) {
641
+ res.status(500).end();
642
+ }
643
+ }
644
+ })();
645
+ req.on("close", () => {
646
+ state.sseTransports.delete(sseTransport.sessionId);
647
+ });
648
+ });
649
+ state.app.post("/messages", (req, res) => {
650
+ const sessionId = typeof req.query["sessionId"] === "string" ? req.query["sessionId"] : void 0;
651
+ if (!sessionId) {
652
+ res.status(400).json({
653
+ jsonrpc: "2.0",
654
+ error: {
655
+ code: JSONRPC_SERVER_ERROR,
656
+ message: "Missing sessionId parameter"
657
+ },
658
+ id: null
659
+ });
660
+ return;
661
+ }
662
+ const sseTransport = state.sseTransports.get(sessionId);
663
+ if (!sseTransport) {
664
+ res.status(404).json({
665
+ jsonrpc: "2.0",
666
+ error: {
667
+ code: JSONRPC_SERVER_ERROR,
668
+ message: "No transport found for sessionId"
669
+ },
670
+ id: null
671
+ });
672
+ return;
673
+ }
674
+ void sseTransport.handlePostMessage(
675
+ asIncoming(req),
676
+ asServerResponse(res),
677
+ req.body
678
+ );
679
+ });
680
+ }
681
+
682
+ // src/auth/oauth-resource-server.ts
683
+ var logger4 = createModuleLogger("AUTH");
684
+ var OAuthResourceServer = class {
685
+ config;
686
+ metadata = null;
687
+ constructor(config) {
688
+ this.config = {
689
+ resource: config.resource,
690
+ authorizationServers: config.authorizationServers,
691
+ scopesSupported: config.scopesSupported.length > 0 ? config.scopesSupported : [...SUPPORTED_SCOPES],
692
+ bearerMethodsSupported: config.bearerMethodsSupported ?? ["header"]
693
+ };
694
+ logger4.info(
695
+ `OAuth Resource Server initialized for: ${this.config.resource}`,
696
+ { code: "AUTH_INIT" }
697
+ );
698
+ }
699
+ /**
700
+ * Get the Protected Resource Metadata document
701
+ *
702
+ * @returns RFC 9728 compliant metadata
703
+ */
704
+ getMetadata() {
705
+ this.metadata ??= this.buildMetadata();
706
+ return this.metadata;
707
+ }
708
+ /**
709
+ * Build the Protected Resource Metadata document
710
+ */
711
+ buildMetadata() {
712
+ return {
713
+ resource: this.config.resource,
714
+ authorization_servers: this.config.authorizationServers,
715
+ scopes_supported: this.config.scopesSupported,
716
+ bearer_methods_supported: this.config.bearerMethodsSupported,
717
+ resource_documentation: `${this.config.resource}/docs`,
718
+ resource_signing_alg_values_supported: ["RS256", "ES256"]
719
+ };
720
+ }
721
+ /**
722
+ * Get Express request handler for the metadata endpoint
723
+ *
724
+ * Serves: GET /.well-known/oauth-protected-resource
725
+ */
726
+ getMetadataHandler() {
727
+ return (_req, res) => {
728
+ const metadata = this.getMetadata();
729
+ res.setHeader("Content-Type", "application/json");
730
+ res.setHeader("Cache-Control", "public, max-age=3600");
731
+ res.json(metadata);
732
+ logger4.info("Protected Resource Metadata served", {
733
+ code: "AUTH_METADATA_SERVED"
734
+ });
735
+ };
736
+ }
737
+ /**
738
+ * Generate WWW-Authenticate header for 401 responses
739
+ *
740
+ * @param error - Error type for the header
741
+ * @param errorDescription - Human-readable error description
742
+ * @returns WWW-Authenticate header value
743
+ */
744
+ getWWWAuthenticateHeader(error, errorDescription) {
745
+ const parts = [`Bearer realm="${this.config.resource}"`];
746
+ if (error) {
747
+ parts.push(`error="${error}"`);
748
+ }
749
+ if (errorDescription) {
750
+ parts.push(`error_description="${errorDescription}"`);
751
+ }
752
+ return parts.join(", ");
753
+ }
754
+ /**
755
+ * Get the resource URI
756
+ */
757
+ getResourceUri() {
758
+ return this.config.resource;
759
+ }
760
+ /**
761
+ * Get the authorization servers
762
+ */
763
+ getAuthorizationServers() {
764
+ return [...this.config.authorizationServers];
765
+ }
766
+ /**
767
+ * Get supported scopes
768
+ */
769
+ getSupportedScopes() {
770
+ return [...this.config.scopesSupported];
771
+ }
772
+ /**
773
+ * Check if a scope is supported by this resource server.
774
+ * Validates standard scopes and dynamic patterns (db:*, table:*:*).
775
+ */
776
+ isScopeSupported(scope) {
777
+ if (BASE_SCOPES.includes(scope)) {
778
+ return true;
779
+ }
780
+ if (this.config.scopesSupported.includes(scope)) {
781
+ return true;
782
+ }
783
+ if (SCOPE_PATTERNS.DATABASE.test(scope)) {
784
+ return true;
785
+ }
786
+ if (SCOPE_PATTERNS.TABLE.test(scope)) {
787
+ return true;
788
+ }
789
+ return false;
790
+ }
791
+ /**
792
+ * Get the well-known metadata endpoint path
793
+ */
794
+ getWellKnownPath() {
795
+ return "/.well-known/oauth-protected-resource";
796
+ }
797
+ /**
798
+ * Clear cached metadata (useful when configuration changes)
799
+ */
800
+ clearCache() {
801
+ this.metadata = null;
802
+ }
803
+ };
804
+
805
+ // src/auth/authorization-server-discovery.ts
806
+ var logger5 = createModuleLogger("AUTH");
807
+ var AuthorizationServerDiscovery = class {
808
+ authServerUrl;
809
+ cacheTtl;
810
+ timeout;
811
+ cachedMetadata = null;
812
+ cacheExpiry = 0;
813
+ constructor(config) {
814
+ this.authServerUrl = config.authServerUrl.replace(/\/+$/, "");
815
+ this.cacheTtl = config.cacheTtl ?? 3600;
816
+ this.timeout = config.timeout ?? 5e3;
817
+ logger5.info(
818
+ `Authorization Server Discovery initialized for: ${this.authServerUrl}`,
819
+ { code: "AUTH_INIT" }
820
+ );
821
+ }
822
+ /**
823
+ * Discover authorization server metadata
824
+ *
825
+ * Fetches from /.well-known/oauth-authorization-server
826
+ * Results are cached for cacheTtl seconds.
827
+ *
828
+ * @returns Authorization server metadata
829
+ * @throws AuthServerDiscoveryError if discovery fails
830
+ */
831
+ async discover() {
832
+ if (this.cachedMetadata && Date.now() < this.cacheExpiry) {
833
+ logger5.info("Using cached authorization server metadata", {
834
+ code: "AUTH_CACHE_HIT"
835
+ });
836
+ return this.cachedMetadata;
837
+ }
838
+ const metadataUrl = `${this.authServerUrl}/.well-known/oauth-authorization-server`;
839
+ logger5.info(`Fetching authorization server metadata from: ${metadataUrl}`, {
840
+ code: "AUTH_DISCOVERY"
841
+ });
842
+ try {
843
+ const controller = new AbortController();
844
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
845
+ const response = await fetch(metadataUrl, {
846
+ method: "GET",
847
+ headers: {
848
+ Accept: "application/json"
849
+ },
850
+ signal: controller.signal
851
+ });
852
+ clearTimeout(timeoutId);
853
+ if (!response.ok) {
854
+ throw new DbMcpError(
855
+ `HTTP ${String(response.status)}: ${response.statusText}`,
856
+ ERROR_CODES.AUTH.DISCOVERY_FAILED.full,
857
+ "connection" /* CONNECTION */
858
+ );
859
+ }
860
+ const metadata = await response.json();
861
+ this.validateMetadata(metadata);
862
+ this.cachedMetadata = metadata;
863
+ this.cacheExpiry = Date.now() + this.cacheTtl * 1e3;
864
+ logger5.info(
865
+ `Authorization server metadata cached for ${String(this.cacheTtl)}s`,
866
+ { code: "AUTH_DISCOVERY_SUCCESS" }
867
+ );
868
+ return metadata;
869
+ } catch (error) {
870
+ const cause = error instanceof Error ? error : new Error(String(error));
871
+ logger5.error(
872
+ `Failed to discover authorization server: ${this.authServerUrl}`,
873
+ {
874
+ code: ERROR_CODES.AUTH.DISCOVERY_FAILED.full,
875
+ operation: "discover",
876
+ entityId: this.authServerUrl,
877
+ error: cause
878
+ }
879
+ );
880
+ throw new AuthServerDiscoveryError(this.authServerUrl, cause);
881
+ }
882
+ }
883
+ /**
884
+ * Validate required metadata fields per RFC 8414
885
+ */
886
+ validateMetadata(metadata) {
887
+ if (!metadata.issuer) {
888
+ throw new DbMcpError(
889
+ "Missing required field: issuer",
890
+ "AUTH_DISCOVERY_INVALID",
891
+ "validation" /* VALIDATION */
892
+ );
893
+ }
894
+ if (!metadata.token_endpoint) {
895
+ throw new DbMcpError(
896
+ "Missing required field: token_endpoint",
897
+ "AUTH_DISCOVERY_INVALID",
898
+ "validation" /* VALIDATION */
899
+ );
900
+ }
901
+ const expectedIssuer = this.authServerUrl;
902
+ if (metadata.issuer !== expectedIssuer) {
903
+ logger5.warning(
904
+ `Issuer mismatch: expected ${expectedIssuer}, got ${metadata.issuer}`,
905
+ { code: "AUTH_ISSUER_MISMATCH" }
906
+ );
907
+ }
908
+ }
909
+ /**
910
+ * Get cached metadata (throws if not discovered)
911
+ */
912
+ getMetadata() {
913
+ if (!this.cachedMetadata) {
914
+ throw new DbMcpError(
915
+ "Authorization server metadata not yet discovered. Call discover() first.",
916
+ "AUTH_DISCOVERY_REQUIRED",
917
+ "internal" /* INTERNAL */
918
+ );
919
+ }
920
+ return this.cachedMetadata;
921
+ }
922
+ /**
923
+ * Get JWKS URI from metadata
924
+ *
925
+ * @throws Error if metadata not discovered or jwks_uri not present
926
+ */
927
+ getJwksUri() {
928
+ const metadata = this.getMetadata();
929
+ if (!metadata.jwks_uri) {
930
+ throw new DbMcpError(
931
+ "Authorization server does not provide jwks_uri",
932
+ "AUTH_DISCOVERY_INVALID",
933
+ "validation" /* VALIDATION */
934
+ );
935
+ }
936
+ return metadata.jwks_uri;
937
+ }
938
+ /**
939
+ * Get token endpoint from metadata
940
+ */
941
+ getTokenEndpoint() {
942
+ return this.getMetadata().token_endpoint;
943
+ }
944
+ /**
945
+ * Get issuer from metadata
946
+ */
947
+ getIssuer() {
948
+ return this.getMetadata().issuer;
949
+ }
950
+ /**
951
+ * Get registration endpoint from metadata (RFC 7591)
952
+ *
953
+ * @returns Registration endpoint or null if not supported
954
+ */
955
+ getRegistrationEndpoint() {
956
+ return this.getMetadata().registration_endpoint ?? null;
957
+ }
958
+ /**
959
+ * Check if dynamic client registration is supported
960
+ */
961
+ supportsClientRegistration() {
962
+ return this.getRegistrationEndpoint() !== null;
963
+ }
964
+ /**
965
+ * Get supported scopes from metadata
966
+ */
967
+ getSupportedScopes() {
968
+ return this.getMetadata().scopes_supported ?? [];
969
+ }
970
+ /**
971
+ * Check if a specific scope is supported
972
+ */
973
+ isScopeSupported(scope) {
974
+ const supportedScopes = this.getSupportedScopes();
975
+ return supportedScopes.length === 0 || supportedScopes.includes(scope);
976
+ }
977
+ /**
978
+ * Clear cached metadata
979
+ */
980
+ clearCache() {
981
+ this.cachedMetadata = null;
982
+ this.cacheExpiry = 0;
983
+ logger5.info("Authorization server metadata cache cleared", {
984
+ code: "AUTH_CACHE_CLEARED"
985
+ });
986
+ }
987
+ /**
988
+ * Check if cache is valid
989
+ */
990
+ isCacheValid() {
991
+ return this.cachedMetadata !== null && Date.now() < this.cacheExpiry;
992
+ }
993
+ /**
994
+ * Get the authorization server URL
995
+ */
996
+ getAuthServerUrl() {
997
+ return this.authServerUrl;
998
+ }
999
+ };
1000
+ var logger6 = createModuleLogger("AUTH");
1001
+ var TokenValidator = class {
1002
+ /** Resolved configuration with all defaults applied */
1003
+ jwksUri;
1004
+ issuer;
1005
+ audience;
1006
+ clockTolerance;
1007
+ jwksCacheTtl;
1008
+ jwks = null;
1009
+ jwksExpiry = 0;
1010
+ constructor(config) {
1011
+ this.jwksUri = config.jwksUri;
1012
+ this.issuer = config.issuer;
1013
+ this.audience = config.audience;
1014
+ this.clockTolerance = config.clockTolerance ?? 60;
1015
+ this.jwksCacheTtl = config.jwksCacheTtl ?? 3600;
1016
+ logger6.info(`Token Validator initialized for issuer: ${this.issuer}`, {
1017
+ code: "AUTH_INIT"
1018
+ });
1019
+ }
1020
+ /**
1021
+ * Validate an access token
1022
+ *
1023
+ * @param token - The JWT access token
1024
+ * @returns Validation result with claims or error
1025
+ */
1026
+ async validate(token) {
1027
+ try {
1028
+ const jwks = this.getJwks();
1029
+ const { payload } = await jose.jwtVerify(token, jwks, {
1030
+ issuer: this.issuer,
1031
+ audience: this.audience,
1032
+ clockTolerance: this.clockTolerance
1033
+ });
1034
+ const claims = this.extractClaims(payload);
1035
+ logger6.info(`Token validated for subject: ${claims.sub}`, {
1036
+ code: "AUTH_TOKEN_VALID",
1037
+ sub: claims.sub,
1038
+ scopes: claims.scopes.length,
1039
+ exp: new Date(claims.exp * 1e3).toISOString()
1040
+ });
1041
+ return {
1042
+ valid: true,
1043
+ claims
1044
+ };
1045
+ } catch (error) {
1046
+ return this.handleValidationError(error);
1047
+ }
1048
+ }
1049
+ /**
1050
+ * Get or refresh the JWKS
1051
+ */
1052
+ getJwks() {
1053
+ if (this.jwks && Date.now() < this.jwksExpiry) {
1054
+ return this.jwks;
1055
+ }
1056
+ logger6.info(`Fetching JWKS from: ${this.jwksUri}`, {
1057
+ code: "AUTH_JWKS_FETCH"
1058
+ });
1059
+ try {
1060
+ this.jwks = jose.createRemoteJWKSet(new URL(this.jwksUri), {
1061
+ cooldownDuration: 3e4,
1062
+ // 30 seconds between retries
1063
+ cacheMaxAge: this.jwksCacheTtl * 1e3
1064
+ });
1065
+ this.jwksExpiry = Date.now() + this.jwksCacheTtl * 1e3;
1066
+ logger6.info(`JWKS cached for ${String(this.jwksCacheTtl)}s`, {
1067
+ code: "AUTH_JWKS_CACHED"
1068
+ });
1069
+ return this.jwks;
1070
+ } catch (error) {
1071
+ const cause = error instanceof Error ? error : new Error(String(error));
1072
+ logger6.error(`Failed to fetch JWKS: ${this.jwksUri}`, {
1073
+ code: ERROR_CODES.AUTH.JWKS_FETCH_FAILED.full,
1074
+ error: cause
1075
+ });
1076
+ throw new JwksFetchError(this.jwksUri, cause);
1077
+ }
1078
+ }
1079
+ /**
1080
+ * Extract and normalize token claims
1081
+ */
1082
+ extractClaims(payload) {
1083
+ let scopes = [];
1084
+ if (typeof payload["scope"] === "string") {
1085
+ scopes = parseScopes(payload["scope"]);
1086
+ } else if (Array.isArray(payload["scopes"])) {
1087
+ scopes = payload["scopes"].filter(
1088
+ (s) => typeof s === "string"
1089
+ );
1090
+ } else if (Array.isArray(payload["scope"])) {
1091
+ scopes = payload["scope"].filter(
1092
+ (s) => typeof s === "string"
1093
+ );
1094
+ }
1095
+ return {
1096
+ sub: payload.sub ?? "unknown",
1097
+ scopes,
1098
+ exp: payload.exp ?? 0,
1099
+ iat: payload.iat ?? 0,
1100
+ iss: payload.iss,
1101
+ aud: payload.aud,
1102
+ nbf: payload.nbf ?? void 0,
1103
+ jti: payload.jti,
1104
+ client_id: payload["client_id"],
1105
+ // Include all other claims
1106
+ ...payload
1107
+ };
1108
+ }
1109
+ /**
1110
+ * Handle validation errors and convert to TokenValidationResult
1111
+ */
1112
+ handleValidationError(error) {
1113
+ if (error instanceof jose.errors.JWTExpired) {
1114
+ logger6.warning("Token has expired", {
1115
+ code: ERROR_CODES.AUTH.TOKEN_EXPIRED.full,
1116
+ error
1117
+ });
1118
+ return {
1119
+ valid: false,
1120
+ error: "Token has expired",
1121
+ errorCode: ERROR_CODES.AUTH.TOKEN_EXPIRED.full
1122
+ };
1123
+ }
1124
+ if (error instanceof jose.errors.JWTClaimValidationFailed) {
1125
+ logger6.warning(`Token claim validation failed: ${error.message}`, {
1126
+ code: ERROR_CODES.AUTH.TOKEN_INVALID.full,
1127
+ error
1128
+ });
1129
+ return {
1130
+ valid: false,
1131
+ error: `Token claim validation failed: ${error.message}`,
1132
+ errorCode: ERROR_CODES.AUTH.TOKEN_INVALID.full
1133
+ };
1134
+ }
1135
+ if (error instanceof jose.errors.JWSSignatureVerificationFailed) {
1136
+ logger6.warning("Token signature verification failed", {
1137
+ code: ERROR_CODES.AUTH.SIGNATURE_INVALID.full,
1138
+ error
1139
+ });
1140
+ return {
1141
+ valid: false,
1142
+ error: "Token signature verification failed",
1143
+ errorCode: ERROR_CODES.AUTH.SIGNATURE_INVALID.full
1144
+ };
1145
+ }
1146
+ if (error instanceof jose.errors.JWKSNoMatchingKey) {
1147
+ logger6.warning("No matching key found in JWKS", {
1148
+ code: ERROR_CODES.AUTH.TOKEN_INVALID.full,
1149
+ error
1150
+ });
1151
+ return {
1152
+ valid: false,
1153
+ error: "No matching key found in JWKS",
1154
+ errorCode: ERROR_CODES.AUTH.TOKEN_INVALID.full
1155
+ };
1156
+ }
1157
+ const message = error instanceof Error ? error.message : String(error);
1158
+ logger6.error(`Token validation failed: ${message}`, {
1159
+ code: ERROR_CODES.AUTH.TOKEN_INVALID.full,
1160
+ error: error instanceof Error ? error : void 0
1161
+ });
1162
+ return {
1163
+ valid: false,
1164
+ error: `Token validation failed: ${message}`,
1165
+ errorCode: ERROR_CODES.AUTH.TOKEN_INVALID.full
1166
+ };
1167
+ }
1168
+ /**
1169
+ * Refresh the JWKS cache
1170
+ */
1171
+ refreshJwks() {
1172
+ this.jwks = null;
1173
+ this.jwksExpiry = 0;
1174
+ this.getJwks();
1175
+ logger6.info("JWKS cache refreshed", { code: "AUTH_JWKS_REFRESHED" });
1176
+ }
1177
+ /**
1178
+ * Clear the JWKS cache
1179
+ */
1180
+ clearCache() {
1181
+ this.jwks = null;
1182
+ this.jwksExpiry = 0;
1183
+ logger6.info("Token validator cache cleared", {
1184
+ code: "AUTH_CACHE_CLEARED"
1185
+ });
1186
+ }
1187
+ /**
1188
+ * Convert a validation error to the appropriate OAuth error class
1189
+ */
1190
+ static toOAuthError(result) {
1191
+ if (result.errorCode === ERROR_CODES.AUTH.TOKEN_EXPIRED.full) {
1192
+ return new TokenExpiredError();
1193
+ }
1194
+ if (result.errorCode === ERROR_CODES.AUTH.SIGNATURE_INVALID.full) {
1195
+ return new InvalidSignatureError();
1196
+ }
1197
+ return new InvalidTokenError(result.error);
1198
+ }
1199
+ };
1200
+
1201
+ // src/transports/http/oauth.ts
1202
+ var logger7 = createModuleLogger("HTTP");
1203
+ function applyAuthMiddleware(state) {
1204
+ if (state.config.oauth.enabled && state.tokenValidator && state.resourceServer && state.app) {
1205
+ const authMiddleware = createAuthMiddleware({
1206
+ tokenValidator: state.tokenValidator,
1207
+ resourceServer: state.resourceServer,
1208
+ publicPaths: ["/health", ...state.config.oauth.publicPaths ?? []]
1209
+ });
1210
+ state.app.use(authMiddleware);
1211
+ } else if (state.config.authToken && state.app) {
1212
+ state.app.use(createSimpleBearerAuth(state.config.authToken));
1213
+ logger7.info("Simple bearer token authentication enabled", {
1214
+ code: "AUTH_BEARER_ENABLED"
1215
+ });
1216
+ } else if (state.app) {
1217
+ logger7.warning(
1218
+ "No authentication configured for HTTP transport. Set --auth-token or --oauth-enabled for production deployments.",
1219
+ { code: "AUTH_NONE" }
1220
+ );
1221
+ }
1222
+ }
1223
+ function createSimpleBearerAuth(expectedToken) {
1224
+ const publicPaths = ["/health", "/", "/.well-known"];
1225
+ return (req, res, next) => {
1226
+ if (publicPaths.some((p) => req.path === p || req.path.startsWith(`${p}/`))) {
1227
+ next();
1228
+ return;
1229
+ }
1230
+ const authHeader = req.headers.authorization;
1231
+ if (!authHeader?.startsWith("Bearer ")) {
1232
+ res.status(401);
1233
+ res.setHeader("WWW-Authenticate", 'Bearer realm="db-mcp"');
1234
+ res.json({
1235
+ error: "unauthorized",
1236
+ error_description: "Bearer token required"
1237
+ });
1238
+ return;
1239
+ }
1240
+ const token = authHeader.slice(7);
1241
+ if (token !== expectedToken) {
1242
+ logger7.warning("Invalid bearer token rejected", {
1243
+ code: "AUTH_BEARER_REJECTED"
1244
+ });
1245
+ res.status(401);
1246
+ res.setHeader(
1247
+ "WWW-Authenticate",
1248
+ 'Bearer realm="db-mcp", error="invalid_token"'
1249
+ );
1250
+ res.json({
1251
+ error: "unauthorized",
1252
+ error_description: "Invalid bearer token"
1253
+ });
1254
+ return;
1255
+ }
1256
+ next();
1257
+ };
1258
+ }
1259
+ async function setupOAuth(state, resourceUri) {
1260
+ logger7.info("Setting up OAuth 2.1...", { code: "HTTP_OAUTH_SETUP" });
1261
+ state.resourceServer = new OAuthResourceServer({
1262
+ resource: resourceUri,
1263
+ authorizationServers: [state.config.oauth.authorizationServerUrl],
1264
+ scopesSupported: [...SUPPORTED_SCOPES]
1265
+ });
1266
+ state.app?.get(
1267
+ "/.well-known/oauth-protected-resource",
1268
+ state.resourceServer.getMetadataHandler()
1269
+ );
1270
+ state.authServerDiscovery = new AuthorizationServerDiscovery({
1271
+ authServerUrl: state.config.oauth.authorizationServerUrl
1272
+ });
1273
+ try {
1274
+ const metadata = await state.authServerDiscovery.discover();
1275
+ state.tokenValidator = new TokenValidator({
1276
+ jwksUri: state.config.oauth.jwksUri ?? metadata.jwks_uri ?? "",
1277
+ issuer: state.config.oauth.issuer ?? metadata.issuer,
1278
+ audience: state.config.oauth.audience,
1279
+ clockTolerance: state.config.oauth.clockTolerance
1280
+ });
1281
+ logger7.info("OAuth 2.1 setup complete", { code: "HTTP_OAUTH_READY" });
1282
+ } catch (error) {
1283
+ logger7.warning(
1284
+ "Authorization server discovery failed. OAuth validation disabled.",
1285
+ {
1286
+ code: ERROR_CODES.AUTH.DISCOVERY_FAILED.full,
1287
+ error: error instanceof Error ? error : void 0
1288
+ }
1289
+ );
1290
+ if (!state.config.oauth.jwksUri) {
1291
+ logger7.error(
1292
+ "No JWKS URI available. Please provide oauth.jwksUri in config.",
1293
+ { code: ERROR_CODES.AUTH.DISCOVERY_FAILED.full }
1294
+ );
1295
+ throw error;
1296
+ }
1297
+ state.tokenValidator = new TokenValidator({
1298
+ jwksUri: state.config.oauth.jwksUri,
1299
+ issuer: state.config.oauth.issuer ?? state.config.oauth.authorizationServerUrl,
1300
+ audience: state.config.oauth.audience,
1301
+ clockTolerance: state.config.oauth.clockTolerance
1302
+ });
1303
+ }
1304
+ }
1305
+
1306
+ // src/transports/http/transport.ts
1307
+ var logger8 = createModuleLogger("HTTP");
1308
+ var HttpTransport = class {
1309
+ /** Internal state shared with session/middleware/oauth modules */
1310
+ state;
1311
+ constructor(config) {
1312
+ this.state = {
1313
+ config: {
1314
+ ...config,
1315
+ host: config.host ?? "0.0.0.0",
1316
+ stateless: config.stateless ?? false,
1317
+ enableHSTS: config.enableHSTS ?? process.env["MCP_ENABLE_HSTS"] === "true"
1318
+ },
1319
+ app: null,
1320
+ httpServer: null,
1321
+ transports: /* @__PURE__ */ new Map(),
1322
+ sseTransports: /* @__PURE__ */ new Map(),
1323
+ statelessTransport: null,
1324
+ resourceServer: null,
1325
+ authServerDiscovery: null,
1326
+ tokenValidator: null,
1327
+ mcpServer: null,
1328
+ rateLimitMap: /* @__PURE__ */ new Map(),
1329
+ rateLimitCleanupTimer: null
1330
+ };
1331
+ }
1332
+ /**
1333
+ * Initialize the transport with the MCP server.
1334
+ *
1335
+ * For stateful mode, the server reference is stored to connect new sessions.
1336
+ * For stateless mode, a single transport is created and connected.
1337
+ *
1338
+ * @param server - The MCP server instance
1339
+ */
1340
+ async initialize(server) {
1341
+ logger8.info("Initializing HTTP transport...", {
1342
+ code: "HTTP_INIT",
1343
+ stateless: this.state.config.stateless
1344
+ });
1345
+ this.state.mcpServer = server;
1346
+ this.state.app = express();
1347
+ this.state.app.use(localhostHostValidation());
1348
+ setupSecurityHeaders(this.state);
1349
+ setupCors(this.state);
1350
+ setupRateLimiting(this.state);
1351
+ const maxBodyBytes = this.state.config.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
1352
+ this.state.app.use(
1353
+ express.json({ limit: maxBodyBytes })
1354
+ );
1355
+ const resourceUri = this.state.config.resourceUri ?? `http://localhost:${String(this.state.config.port)}`;
1356
+ if (this.state.config.oauth.enabled) {
1357
+ await setupOAuth(this.state, resourceUri);
1358
+ }
1359
+ this.state.app.get("/health", (_req, res) => {
1360
+ res.json({
1361
+ status: "healthy",
1362
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1363
+ oauth: this.state.config.oauth.enabled,
1364
+ mode: this.state.config.stateless ? "stateless" : "stateful",
1365
+ activeSessions: this.state.transports.size
1366
+ });
1367
+ });
1368
+ this.state.app.get("/", (_req, res) => {
1369
+ res.json({
1370
+ name: "db-mcp",
1371
+ description: "SQLite MCP Server with dual HTTP transport",
1372
+ mode: this.state.config.stateless ? "stateless" : "stateful",
1373
+ endpoints: {
1374
+ "POST /mcp": "JSON-RPC requests (Streamable HTTP, MCP 2025-03-26)",
1375
+ "GET /mcp": this.state.config.stateless === true ? "Not available in stateless mode" : "SSE stream for server-to-client notifications",
1376
+ "DELETE /mcp": "Session termination",
1377
+ "GET /sse": "Legacy SSE connection (MCP 2024-11-05)",
1378
+ "POST /messages": "Legacy SSE message endpoint",
1379
+ "GET /health": "Health check"
1380
+ },
1381
+ documentation: "https://github.com/neverinfamous/db-mcp"
1382
+ });
1383
+ });
1384
+ applyAuthMiddleware(this.state);
1385
+ if (this.state.config.stateless) {
1386
+ await setupStatelessEndpoints(this.state);
1387
+ } else {
1388
+ setupStatefulEndpoints(this.state);
1389
+ }
1390
+ if (!this.state.config.stateless) {
1391
+ setupLegacySSEEndpoints(this.state);
1392
+ }
1393
+ this.state.app.use(oauthErrorHandler);
1394
+ this.state.app.use((_req, res) => {
1395
+ res.status(404).json({ error: "Not found" });
1396
+ });
1397
+ logger8.info("HTTP transport initialized", {
1398
+ code: "HTTP_INIT_COMPLETE",
1399
+ port: this.state.config.port,
1400
+ oauth: this.state.config.oauth.enabled,
1401
+ mode: this.state.config.stateless ? "stateless" : "stateful",
1402
+ resourceUri
1403
+ });
1404
+ }
1405
+ /**
1406
+ * Start listening on the configured port
1407
+ */
1408
+ async start() {
1409
+ if (!this.state.app) {
1410
+ throw new DbMcpError(
1411
+ "Transport not initialized. Call initialize() first.",
1412
+ ERROR_CODES.SERVER.START_FAILED.full,
1413
+ "internal" /* INTERNAL */
1414
+ );
1415
+ }
1416
+ return new Promise((resolve, reject) => {
1417
+ try {
1418
+ this.state.httpServer = this.state.app?.listen(
1419
+ this.state.config.port,
1420
+ this.state.config.host ?? "0.0.0.0",
1421
+ () => {
1422
+ if (this.state.httpServer) {
1423
+ this.state.httpServer.setTimeout(HTTP_REQUEST_TIMEOUT_MS);
1424
+ this.state.httpServer.keepAliveTimeout = HTTP_KEEP_ALIVE_TIMEOUT_MS;
1425
+ this.state.httpServer.headersTimeout = HTTP_HEADERS_TIMEOUT_MS;
1426
+ }
1427
+ logger8.info(
1428
+ `HTTP server listening on ${this.state.config.host ?? "0.0.0.0"}:${String(this.state.config.port)}`,
1429
+ {
1430
+ code: "HTTP_SERVER_STARTED",
1431
+ mode: this.state.config.stateless ? "stateless" : "stateful"
1432
+ }
1433
+ );
1434
+ resolve();
1435
+ }
1436
+ ) ?? null;
1437
+ this.state.httpServer?.on("error", (error) => {
1438
+ logger8.error("Failed to start HTTP server", {
1439
+ code: ERROR_CODES.SERVER.START_FAILED.full,
1440
+ error
1441
+ });
1442
+ reject(error);
1443
+ });
1444
+ } catch (error) {
1445
+ reject(error instanceof Error ? error : new Error(String(error)));
1446
+ }
1447
+ });
1448
+ }
1449
+ /**
1450
+ * Stop the server and close all active sessions
1451
+ */
1452
+ async stop() {
1453
+ if (this.state.rateLimitCleanupTimer) {
1454
+ clearInterval(this.state.rateLimitCleanupTimer);
1455
+ this.state.rateLimitCleanupTimer = null;
1456
+ }
1457
+ for (const [sessionId, transport] of this.state.transports) {
1458
+ try {
1459
+ logger8.debug("Closing transport", {
1460
+ code: "HTTP_SESSION_CLEANUP",
1461
+ sessionId
1462
+ });
1463
+ await transport.close();
1464
+ } catch (error) {
1465
+ logger8.error("Error closing transport", {
1466
+ code: ERROR_CODES.SERVER.SHUTDOWN_FAILED.full,
1467
+ sessionId,
1468
+ error: error instanceof Error ? error : void 0
1469
+ });
1470
+ }
1471
+ }
1472
+ this.state.transports.clear();
1473
+ for (const [sessionId, transport] of this.state.sseTransports) {
1474
+ try {
1475
+ logger8.debug("Closing SSE transport", {
1476
+ code: "SSE_SESSION_CLEANUP",
1477
+ sessionId
1478
+ });
1479
+ await transport.close();
1480
+ } catch (error) {
1481
+ logger8.error("Error closing SSE transport", {
1482
+ code: ERROR_CODES.SERVER.SHUTDOWN_FAILED.full,
1483
+ sessionId,
1484
+ error: error instanceof Error ? error : void 0
1485
+ });
1486
+ }
1487
+ }
1488
+ this.state.sseTransports.clear();
1489
+ if (this.state.statelessTransport) {
1490
+ try {
1491
+ await this.state.statelessTransport.close();
1492
+ } catch (error) {
1493
+ logger8.error("Error closing stateless transport", {
1494
+ code: ERROR_CODES.SERVER.SHUTDOWN_FAILED.full,
1495
+ error: error instanceof Error ? error : void 0
1496
+ });
1497
+ }
1498
+ }
1499
+ return new Promise((resolve, reject) => {
1500
+ if (!this.state.httpServer) {
1501
+ resolve();
1502
+ return;
1503
+ }
1504
+ this.state.httpServer.close((error) => {
1505
+ if (error) {
1506
+ logger8.error("Error stopping HTTP server", {
1507
+ code: ERROR_CODES.SERVER.SHUTDOWN_FAILED.full,
1508
+ error
1509
+ });
1510
+ reject(error);
1511
+ } else {
1512
+ logger8.info("HTTP server stopped", { code: "HTTP_SERVER_STOPPED" });
1513
+ resolve();
1514
+ }
1515
+ });
1516
+ });
1517
+ }
1518
+ /**
1519
+ * Get the underlying Express app
1520
+ */
1521
+ getApp() {
1522
+ return this.state.app;
1523
+ }
1524
+ /**
1525
+ * Get the number of active sessions (stateful mode only)
1526
+ */
1527
+ getActiveSessionCount() {
1528
+ return this.state.transports.size;
1529
+ }
1530
+ /**
1531
+ * Get the OAuth Resource Server
1532
+ */
1533
+ getResourceServer() {
1534
+ return this.state.resourceServer;
1535
+ }
1536
+ };
1537
+ function createHttpTransport(config) {
1538
+ return new HttpTransport(config);
1539
+ }
1540
+
1541
+ export { HttpTransport, createHttpTransport };