@prmichaelsen/mcp-auth 7.0.4 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +2 -2
- package/dist/types.d.ts +73 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wrapper/config.d.ts +20 -2
- package/dist/wrapper/config.d.ts.map +1 -1
- package/dist/wrapper/index.d.ts +10 -30
- package/dist/wrapper/index.d.ts.map +1 -1
- package/dist/wrapper/index.js +6 -5
- package/dist/wrapper/index.js.map +3 -3
- package/dist/wrapper/instance-pool-manager.d.ts +119 -0
- package/dist/wrapper/instance-pool-manager.d.ts.map +1 -0
- package/dist/wrapper/instance-pool-manager.js +225 -0
- package/dist/wrapper/instance-pool-manager.js.map +7 -0
- package/dist/wrapper/progress-manager.d.ts +101 -0
- package/dist/wrapper/progress-manager.d.ts.map +1 -0
- package/dist/wrapper/progress-manager.js +241 -0
- package/dist/wrapper/progress-manager.js.map +7 -0
- package/dist/wrapper/server-wrapper.d.ts +36 -0
- package/dist/wrapper/server-wrapper.d.ts.map +1 -1
- package/dist/wrapper/server-wrapper.js +196 -5
- package/dist/wrapper/server-wrapper.js.map +2 -2
- package/package.json +1 -1
|
@@ -31,8 +31,12 @@ export declare class AuthenticatedServerWrapper {
|
|
|
31
31
|
private config;
|
|
32
32
|
private logger;
|
|
33
33
|
private serverPool;
|
|
34
|
+
private poolManager?;
|
|
34
35
|
private isRunning;
|
|
35
36
|
private cleanupTimer?;
|
|
37
|
+
private progressContexts;
|
|
38
|
+
private progressManager;
|
|
39
|
+
private cleanupInterval?;
|
|
36
40
|
constructor(config: ServerWrapperConfig);
|
|
37
41
|
/**
|
|
38
42
|
* Validate wrapper configuration
|
|
@@ -50,6 +54,18 @@ export declare class AuthenticatedServerWrapper {
|
|
|
50
54
|
* Stop the wrapped server
|
|
51
55
|
*/
|
|
52
56
|
stop(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Store progress context for a user
|
|
59
|
+
*/
|
|
60
|
+
private storeProgressContext;
|
|
61
|
+
/**
|
|
62
|
+
* Get progress context for a user
|
|
63
|
+
*/
|
|
64
|
+
private getProgressContext;
|
|
65
|
+
/**
|
|
66
|
+
* Clear progress context for a user
|
|
67
|
+
*/
|
|
68
|
+
private clearProgressContext;
|
|
53
69
|
/**
|
|
54
70
|
* Handle SSE request with direct Express req/res access
|
|
55
71
|
* This allows us to use StreamableHTTPServerTransport properly
|
|
@@ -101,4 +117,24 @@ export declare class AuthenticatedServerWrapper {
|
|
|
101
117
|
*/
|
|
102
118
|
isServerRunning(): boolean;
|
|
103
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Convenience function to create and configure an authenticated server wrapper
|
|
122
|
+
*
|
|
123
|
+
* @param config - Server wrapper configuration
|
|
124
|
+
* @returns Configured AuthenticatedServerWrapper instance
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const wrapped = wrapServer({
|
|
129
|
+
* serverFactory: (accessToken, userId) => createMyServer(accessToken, userId),
|
|
130
|
+
* authProvider: new JWTAuthProvider({ jwtSecret: process.env.JWT_SECRET }),
|
|
131
|
+
* tokenResolver: new APITokenResolver({ ... }),
|
|
132
|
+
* resourceType: 'myapi',
|
|
133
|
+
* transport: { type: 'sse', port: 3000 }
|
|
134
|
+
* });
|
|
135
|
+
*
|
|
136
|
+
* await wrapped.start();
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
export declare function wrapServer(config: ServerWrapperConfig): AuthenticatedServerWrapper;
|
|
104
140
|
//# sourceMappingURL=server-wrapper.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-wrapper.d.ts","sourceRoot":"","sources":["../../src/wrapper/server-wrapper.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAiC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"server-wrapper.d.ts","sourceRoot":"","sources":["../../src/wrapper/server-wrapper.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAiC,MAAM,aAAa,CAAC;AA+BtF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,WAAW,CAAC,CAAsB;IAC1C,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,YAAY,CAAC,CAAiB;IACtC,OAAO,CAAC,gBAAgB,CAA2C;IACnE,OAAO,CAAC,eAAe,CAAmB;IAC1C,OAAO,CAAC,eAAe,CAAC,CAAiB;gBAE7B,MAAM,EAAE,mBAAmB;IA2CvC;;OAEG;IACH,OAAO,CAAC,cAAc;IAwDtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAyBvB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAuD3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAK5B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAI1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAK5B;;;OAGG;YACW,gBAAgB;IAsI9B;;OAEG;YACW,iBAAiB;IAW/B;;OAEG;YACW,uBAAuB;IA0DrC;;OAEG;YACW,mBAAmB;IAuBjC;;OAEG;IACH,OAAO,CAAC,eAAe;IAqCvB;;OAEG;YACW,mBAAmB;IAyBjC;;OAEG;YACW,iBAAiB;IA2N/B;;OAEG;YACW,kBAAkB;IAUhC;;OAEG;IACH,YAAY,IAAI;QACd,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,KAAK,CAAC;YACf,MAAM,EAAE,MAAM,CAAC;YACf,SAAS,EAAE,MAAM,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,GAAG,EAAE,MAAM,CAAC;YACZ,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC,CAAC;KACJ;IAgBD;;OAEG;IACH,eAAe,IAAI,OAAO;CAG3B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,mBAAmB,GAAG,0BAA0B,CAElF"}
|
|
@@ -11,19 +11,41 @@ import {
|
|
|
11
11
|
validateResourceType,
|
|
12
12
|
validateUserId,
|
|
13
13
|
validateAccessToken,
|
|
14
|
-
validateTransportConfig
|
|
14
|
+
validateTransportConfig,
|
|
15
|
+
validatePositiveNumber
|
|
15
16
|
} from "../utils/validation.js";
|
|
17
|
+
import { ProgressManager } from "./progress-manager.js";
|
|
18
|
+
import { InstancePoolManager } from "./instance-pool-manager.js";
|
|
16
19
|
class AuthenticatedServerWrapper {
|
|
17
20
|
config;
|
|
18
21
|
logger;
|
|
19
22
|
serverPool;
|
|
23
|
+
poolManager;
|
|
20
24
|
isRunning = false;
|
|
21
25
|
cleanupTimer;
|
|
26
|
+
progressContexts = /* @__PURE__ */ new Map();
|
|
27
|
+
progressManager;
|
|
28
|
+
cleanupInterval;
|
|
22
29
|
constructor(config) {
|
|
23
30
|
this.validateConfig(config);
|
|
24
31
|
this.config = this.normalizeConfig(config);
|
|
25
32
|
this.logger = createLogger(this.config.middleware.logging);
|
|
26
33
|
this.serverPool = /* @__PURE__ */ new Map();
|
|
34
|
+
if (this.config.instanceMode === "pooled" && this.config.instancePool) {
|
|
35
|
+
this.poolManager = new InstancePoolManager(
|
|
36
|
+
this.config.instancePool,
|
|
37
|
+
this.logger
|
|
38
|
+
);
|
|
39
|
+
this.logger.info("Instance pool manager initialized", {
|
|
40
|
+
maxSize: this.config.instancePool.maxSize,
|
|
41
|
+
idleTimeout: this.config.instancePool.idleTimeout,
|
|
42
|
+
maxLifetime: this.config.instancePool.maxLifetime
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
this.progressManager = new ProgressManager(this.logger);
|
|
46
|
+
this.cleanupInterval = setInterval(() => {
|
|
47
|
+
this.progressManager.cleanupStaleStreams();
|
|
48
|
+
}, 6e4);
|
|
27
49
|
this.logger.info("AuthenticatedServerWrapper created", {
|
|
28
50
|
name: this.config.name,
|
|
29
51
|
resourceType: this.config.resourceType,
|
|
@@ -49,6 +71,25 @@ class AuthenticatedServerWrapper {
|
|
|
49
71
|
}
|
|
50
72
|
validateResourceType(config.resourceType);
|
|
51
73
|
validateTransportConfig(config.transport);
|
|
74
|
+
if (config.instanceMode === "pooled") {
|
|
75
|
+
if (!config.instancePool) {
|
|
76
|
+
throw new ConfigurationError(
|
|
77
|
+
'instancePool configuration required when instanceMode is "pooled"'
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
validatePositiveNumber(config.instancePool.maxSize, "instancePool.maxSize");
|
|
81
|
+
validatePositiveNumber(config.instancePool.idleTimeout, "instancePool.idleTimeout");
|
|
82
|
+
validatePositiveNumber(config.instancePool.maxLifetime, "instancePool.maxLifetime");
|
|
83
|
+
if (config.instancePool.maxSize < 1) {
|
|
84
|
+
throw new ConfigurationError("instancePool.maxSize must be at least 1");
|
|
85
|
+
}
|
|
86
|
+
if (config.instancePool.idleTimeout < 1e3) {
|
|
87
|
+
throw new ConfigurationError("instancePool.idleTimeout must be at least 1000ms (1 second)");
|
|
88
|
+
}
|
|
89
|
+
if (config.instancePool.maxLifetime < config.instancePool.idleTimeout) {
|
|
90
|
+
throw new ConfigurationError("instancePool.maxLifetime must be >= idleTimeout");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
52
93
|
if (config.tokenResolver) {
|
|
53
94
|
this.logger?.info("Token resolver configured - dynamic mode", {
|
|
54
95
|
resolverType: config.tokenResolver.constructor.name
|
|
@@ -73,6 +114,8 @@ class AuthenticatedServerWrapper {
|
|
|
73
114
|
name: config.name ?? "mcp-auth-wrapped-server",
|
|
74
115
|
version: config.version ?? "1.0.0",
|
|
75
116
|
instanceMode: config.instanceMode ?? "ephemeral",
|
|
117
|
+
instancePool: config.instancePool ?? null,
|
|
118
|
+
// Convert undefined to null
|
|
76
119
|
middleware: {
|
|
77
120
|
rateLimit: config.middleware?.rateLimit,
|
|
78
121
|
logging: config.middleware?.logging ?? { enabled: true, level: "info" }
|
|
@@ -141,6 +184,13 @@ class AuthenticatedServerWrapper {
|
|
|
141
184
|
clearTimeout(this.cleanupTimer);
|
|
142
185
|
this.cleanupTimer = void 0;
|
|
143
186
|
}
|
|
187
|
+
if (this.cleanupInterval) {
|
|
188
|
+
clearInterval(this.cleanupInterval);
|
|
189
|
+
this.cleanupInterval = void 0;
|
|
190
|
+
}
|
|
191
|
+
if (this.poolManager) {
|
|
192
|
+
await this.poolManager.closeAll();
|
|
193
|
+
}
|
|
144
194
|
if (this.config.instanceMode === "pooled") {
|
|
145
195
|
for (const [userId, instance] of this.serverPool.entries()) {
|
|
146
196
|
try {
|
|
@@ -165,12 +215,34 @@ class AuthenticatedServerWrapper {
|
|
|
165
215
|
this.isRunning = false;
|
|
166
216
|
this.logger.info("Server wrapper stopped");
|
|
167
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Store progress context for a user
|
|
220
|
+
*/
|
|
221
|
+
storeProgressContext(userId, progressToken) {
|
|
222
|
+
this.progressContexts.set(userId, progressToken);
|
|
223
|
+
this.logger.debug("Stored progress context", { userId, progressToken });
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get progress context for a user
|
|
227
|
+
*/
|
|
228
|
+
getProgressContext(userId) {
|
|
229
|
+
return this.progressContexts.get(userId);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Clear progress context for a user
|
|
233
|
+
*/
|
|
234
|
+
clearProgressContext(userId) {
|
|
235
|
+
this.progressContexts.delete(userId);
|
|
236
|
+
this.logger.debug("Cleared progress context", { userId });
|
|
237
|
+
}
|
|
168
238
|
/**
|
|
169
239
|
* Handle SSE request with direct Express req/res access
|
|
170
240
|
* This allows us to use StreamableHTTPServerTransport properly
|
|
171
241
|
*/
|
|
172
242
|
async handleSSERequest(req, res, context) {
|
|
173
243
|
const requestLogger = this.logger.child({ requestId: context.requestId });
|
|
244
|
+
let userId;
|
|
245
|
+
const progressToken = req.body._meta?.progressToken;
|
|
174
246
|
try {
|
|
175
247
|
requestLogger.debug("Authenticating request");
|
|
176
248
|
const authResult = await this.config.authProvider.authenticate(context);
|
|
@@ -178,7 +250,7 @@ class AuthenticatedServerWrapper {
|
|
|
178
250
|
requestLogger.warn("Authentication failed", { error: authResult.error });
|
|
179
251
|
throw new AuthenticationError(authResult.error || "Authentication failed");
|
|
180
252
|
}
|
|
181
|
-
|
|
253
|
+
userId = validateUserId(authResult.userId);
|
|
182
254
|
requestLogger.debug("Authentication successful", { userId });
|
|
183
255
|
let accessToken;
|
|
184
256
|
if (this.config.tokenResolver) {
|
|
@@ -197,8 +269,41 @@ class AuthenticatedServerWrapper {
|
|
|
197
269
|
accessToken = "";
|
|
198
270
|
requestLogger.debug("Static mode - no token resolution", { userId, mode: "static" });
|
|
199
271
|
}
|
|
272
|
+
if (progressToken) {
|
|
273
|
+
this.storeProgressContext(userId, progressToken);
|
|
274
|
+
this.progressManager.registerStream(userId, progressToken, (notification) => {
|
|
275
|
+
requestLogger.debug("Would forward progress notification", {
|
|
276
|
+
userId,
|
|
277
|
+
progressToken: notification.progressToken,
|
|
278
|
+
progress: notification.progress,
|
|
279
|
+
total: notification.total
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
requestLogger.debug("Progress token extracted and stream registered", { userId, progressToken });
|
|
283
|
+
}
|
|
200
284
|
const server = await this.getServerInstance(userId, accessToken);
|
|
201
|
-
|
|
285
|
+
if (progressToken) {
|
|
286
|
+
const originalNotification = server.notification?.bind(server);
|
|
287
|
+
if (originalNotification) {
|
|
288
|
+
server.notification = (params) => {
|
|
289
|
+
if (params.method === "notifications/progress") {
|
|
290
|
+
const handled = this.progressManager.forwardNotification(
|
|
291
|
+
params.params
|
|
292
|
+
);
|
|
293
|
+
if (handled) {
|
|
294
|
+
requestLogger.debug("Progress notification intercepted and forwarded", {
|
|
295
|
+
progressToken: params.params.progressToken
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (originalNotification) {
|
|
301
|
+
originalNotification(params);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
requestLogger.debug("Forwarding request to MCP server", { userId, hasProgressToken: !!progressToken });
|
|
202
307
|
const transport = new StreamableHTTPServerTransport({
|
|
203
308
|
sessionIdGenerator: void 0
|
|
204
309
|
// Stateless mode
|
|
@@ -207,10 +312,21 @@ class AuthenticatedServerWrapper {
|
|
|
207
312
|
await transport.handleRequest(req, res, req.body);
|
|
208
313
|
requestLogger.info("Request handled successfully", {
|
|
209
314
|
userId,
|
|
210
|
-
resourceType: this.config.resourceType
|
|
315
|
+
resourceType: this.config.resourceType,
|
|
316
|
+
hadProgressToken: !!progressToken
|
|
211
317
|
});
|
|
318
|
+
if (progressToken) {
|
|
319
|
+
this.progressManager.unregisterStream(progressToken);
|
|
320
|
+
this.clearProgressContext(userId);
|
|
321
|
+
}
|
|
212
322
|
} catch (error) {
|
|
213
323
|
requestLogger.error("SSE request handling failed", error);
|
|
324
|
+
if (progressToken) {
|
|
325
|
+
this.progressManager.unregisterStream(progressToken);
|
|
326
|
+
if (userId) {
|
|
327
|
+
this.clearProgressContext(userId);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
214
330
|
throw error;
|
|
215
331
|
}
|
|
216
332
|
}
|
|
@@ -228,6 +344,13 @@ class AuthenticatedServerWrapper {
|
|
|
228
344
|
* Get or create pooled server instance
|
|
229
345
|
*/
|
|
230
346
|
async getPooledServerInstance(userId, accessToken) {
|
|
347
|
+
if (this.poolManager) {
|
|
348
|
+
return await this.poolManager.getInstance(
|
|
349
|
+
userId,
|
|
350
|
+
accessToken,
|
|
351
|
+
this.config.serverFactory
|
|
352
|
+
);
|
|
353
|
+
}
|
|
231
354
|
if (this.serverPool.has(userId)) {
|
|
232
355
|
const instance = this.serverPool.get(userId);
|
|
233
356
|
if (instance.accessToken !== accessToken) {
|
|
@@ -415,6 +538,70 @@ class AuthenticatedServerWrapper {
|
|
|
415
538
|
}
|
|
416
539
|
}
|
|
417
540
|
});
|
|
541
|
+
app.get(`${basePath}/progress/stats`, async (req, res) => {
|
|
542
|
+
try {
|
|
543
|
+
const context = {
|
|
544
|
+
headers: req.headers,
|
|
545
|
+
transport: "sse",
|
|
546
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
547
|
+
requestId: req.headers["x-request-id"]
|
|
548
|
+
};
|
|
549
|
+
const authResult = await this.config.authProvider.authenticate(context);
|
|
550
|
+
if (!authResult.authenticated || !authResult.userId) {
|
|
551
|
+
return res.status(401).json({
|
|
552
|
+
error: "Authentication required",
|
|
553
|
+
code: "AUTHENTICATION_ERROR"
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
const userId = authResult.userId;
|
|
557
|
+
const userMetrics = this.progressManager.getUserMetrics(userId);
|
|
558
|
+
const globalStats = this.progressManager.getStats();
|
|
559
|
+
res.json({
|
|
560
|
+
user: {
|
|
561
|
+
userId,
|
|
562
|
+
...userMetrics
|
|
563
|
+
},
|
|
564
|
+
global: globalStats,
|
|
565
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
566
|
+
});
|
|
567
|
+
} catch (error) {
|
|
568
|
+
this.logger.error("Error fetching progress stats", error);
|
|
569
|
+
res.status(500).json({
|
|
570
|
+
error: "Internal server error",
|
|
571
|
+
code: "INTERNAL_ERROR"
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
app.get(`${basePath}/progress/metrics`, async (req, res) => {
|
|
576
|
+
try {
|
|
577
|
+
const context = {
|
|
578
|
+
headers: req.headers,
|
|
579
|
+
transport: "sse",
|
|
580
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
581
|
+
requestId: req.headers["x-request-id"]
|
|
582
|
+
};
|
|
583
|
+
const authResult = await this.config.authProvider.authenticate(context);
|
|
584
|
+
if (!authResult.authenticated) {
|
|
585
|
+
return res.status(401).json({
|
|
586
|
+
error: "Authentication required",
|
|
587
|
+
code: "AUTHENTICATION_ERROR"
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
const allMetrics = this.progressManager.getAllMetrics();
|
|
591
|
+
const health = this.progressManager.checkHealth();
|
|
592
|
+
res.json({
|
|
593
|
+
metrics: allMetrics,
|
|
594
|
+
health,
|
|
595
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
596
|
+
});
|
|
597
|
+
} catch (error) {
|
|
598
|
+
this.logger.error("Error fetching progress metrics", error);
|
|
599
|
+
res.status(500).json({
|
|
600
|
+
error: "Internal server error",
|
|
601
|
+
code: "INTERNAL_ERROR"
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
});
|
|
418
605
|
app.get(`${basePath}/health`, (req, res) => {
|
|
419
606
|
res.json({
|
|
420
607
|
status: "healthy",
|
|
@@ -472,7 +659,11 @@ class AuthenticatedServerWrapper {
|
|
|
472
659
|
return this.isRunning;
|
|
473
660
|
}
|
|
474
661
|
}
|
|
662
|
+
function wrapServer(config) {
|
|
663
|
+
return new AuthenticatedServerWrapper(config);
|
|
664
|
+
}
|
|
475
665
|
export {
|
|
476
|
-
AuthenticatedServerWrapper
|
|
666
|
+
AuthenticatedServerWrapper,
|
|
667
|
+
wrapServer
|
|
477
668
|
};
|
|
478
669
|
//# sourceMappingURL=server-wrapper.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/wrapper/server-wrapper.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Authenticated server wrapper implementation\n *\n * Wraps MCP servers with authentication and multi-tenancy support.\n * Uses ephemeral instances by default for security.\n */\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport type { ServerWrapperConfig, NormalizedServerWrapperConfig } from './config.js';\nimport type { RequestContext } from '../types.js';\nimport { \n AuthenticationError, \n TokenResolutionError,\n ConfigurationError,\n TransportError\n} from '../utils/errors.js';\nimport { createLogger, type Logger } from '../utils/logger.js';\nimport {\n validateRequiredFields,\n validateResourceType,\n validateUserId,\n validateAccessToken,\n validateTransportConfig\n} from '../utils/validation.js';\n\n/**\n * Server instance metadata (for pooled mode)\n */\ninterface ServerInstance {\n server: Server;\n accessToken: string;\n userId: string;\n createdAt: number;\n lastUsed: number;\n}\n\n/**\n * Authenticated server wrapper\n * \n * Wraps an MCP server with authentication, automatically handling:\n * - Request authentication via AuthProvider\n * - Token resolution via ResourceTokenResolver\n * - Per-user server instance creation (ephemeral or pooled)\n * - Transport management (stdio, SSE, HTTP)\n * \n * @example\n * ```typescript\n * const wrapper = new AuthenticatedServerWrapper({\n * serverFactory: (accessToken, userId) => createInstagramServer(accessToken),\n * authProvider: new JWTAuthProvider({ ... }),\n * tokenResolver: new DatabaseTokenResolver({ ... }),\n * resourceType: 'instagram',\n * transport: { type: 'sse', port: 3000 }\n * });\n * \n * await wrapper.start();\n * ```\n */\nexport class AuthenticatedServerWrapper {\n private config: NormalizedServerWrapperConfig;\n private logger: Logger;\n private serverPool: Map<string, ServerInstance>;\n private isRunning: boolean = false;\n private cleanupTimer?: NodeJS.Timeout;\n \n constructor(config: ServerWrapperConfig) {\n // Validate configuration\n this.validateConfig(config);\n \n // Normalize configuration with defaults\n this.config = this.normalizeConfig(config);\n \n // Initialize logger\n this.logger = createLogger(this.config.middleware.logging);\n \n // Initialize server pool (only used in pooled mode)\n this.serverPool = new Map();\n \n this.logger.info('AuthenticatedServerWrapper created', {\n name: this.config.name,\n resourceType: this.config.resourceType,\n transport: this.config.transport.type,\n instanceMode: this.config.instanceMode\n });\n }\n \n /**\n * Validate wrapper configuration\n */\n private validateConfig(config: ServerWrapperConfig): void {\n // Validate required fields manually for better type safety\n if (!config.serverFactory) {\n throw new ConfigurationError('serverFactory is required');\n }\n if (!config.authProvider) {\n throw new ConfigurationError('authProvider is required');\n }\n // tokenResolver is now optional for static servers\n if (!config.resourceType) {\n throw new ConfigurationError('resourceType is required');\n }\n if (!config.transport) {\n throw new ConfigurationError('transport is required');\n }\n \n validateResourceType(config.resourceType);\n validateTransportConfig(config.transport);\n \n // Log mode based on tokenResolver presence\n if (config.tokenResolver) {\n this.logger?.info('Token resolver configured - dynamic mode', {\n resolverType: config.tokenResolver.constructor.name\n });\n } else {\n this.logger?.info('No token resolver - static mode', {\n note: 'Server factory will receive empty string as accessToken'\n });\n }\n }\n \n /**\n * Normalize configuration with defaults\n */\n private normalizeConfig(config: ServerWrapperConfig): NormalizedServerWrapperConfig {\n return {\n serverFactory: config.serverFactory,\n authProvider: config.authProvider,\n tokenResolver: config.tokenResolver ?? null, // Convert undefined to null\n resourceType: config.resourceType,\n transport: config.transport,\n name: config.name ?? 'mcp-auth-wrapped-server',\n version: config.version ?? '1.0.0',\n instanceMode: config.instanceMode ?? 'ephemeral',\n middleware: {\n rateLimit: config.middleware?.rateLimit,\n logging: config.middleware?.logging ?? { enabled: true, level: 'info' }\n },\n pooling: {\n maxServersPerUser: config.pooling?.maxServersPerUser ?? 1,\n idleTimeoutMs: config.pooling?.idleTimeoutMs ?? 300000,\n maxTotalServers: config.pooling?.maxTotalServers ?? 100\n },\n requestTimeoutMs: config.requestTimeoutMs ?? 30000,\n enableTracing: config.enableTracing ?? false\n };\n }\n \n /**\n * Start the wrapped server\n */\n async start(): Promise<void> {\n if (this.isRunning) {\n throw new ConfigurationError('Server is already running');\n }\n \n this.logger.info('Starting authenticated server wrapper', {\n name: this.config.name,\n transport: this.config.transport.type\n });\n \n // Initialize auth provider\n if (this.config.authProvider.initialize) {\n await this.config.authProvider.initialize();\n this.logger.debug('Auth provider initialized');\n }\n \n // Initialize token resolver (if configured)\n if (this.config.tokenResolver) {\n if (this.config.tokenResolver.initialize) {\n await this.config.tokenResolver.initialize();\n this.logger.debug('Token resolver initialized');\n }\n } else {\n this.logger.debug('Static mode - no token resolver to initialize');\n }\n \n // Start appropriate transport\n switch (this.config.transport.type) {\n case 'stdio':\n await this.startStdioTransport();\n break;\n case 'sse':\n await this.startSSETransport();\n break;\n case 'http':\n await this.startHTTPTransport();\n break;\n default:\n throw new TransportError(`Unsupported transport type: ${this.config.transport.type}`);\n }\n \n this.isRunning = true;\n \n this.logger.info('Server wrapper started successfully', {\n name: this.config.name,\n transport: this.config.transport.type,\n port: this.config.transport.port\n });\n }\n \n /**\n * Stop the wrapped server\n */\n async stop(): Promise<void> {\n if (!this.isRunning) {\n return;\n }\n \n this.logger.info('Stopping server wrapper');\n \n // Clear cleanup timer\n if (this.cleanupTimer) {\n clearTimeout(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n \n // Close all pooled servers\n if (this.config.instanceMode === 'pooled') {\n for (const [userId, instance] of this.serverPool.entries()) {\n try {\n await instance.server.close();\n this.logger.debug('Closed pooled server instance', { userId });\n } catch (error) {\n this.logger.error('Error closing server instance', error as Error, { userId });\n }\n }\n this.serverPool.clear();\n }\n \n // Cleanup auth provider\n if (this.config.authProvider.cleanup) {\n await this.config.authProvider.cleanup();\n this.logger.debug('Auth provider cleaned up');\n }\n \n // Cleanup token resolver (if configured)\n if (this.config.tokenResolver) {\n if (this.config.tokenResolver.cleanup) {\n await this.config.tokenResolver.cleanup();\n this.logger.debug('Token resolver cleaned up');\n }\n }\n \n this.isRunning = false;\n \n this.logger.info('Server wrapper stopped');\n }\n \n /**\n * Handle SSE request with direct Express req/res access\n * This allows us to use StreamableHTTPServerTransport properly\n */\n private async handleSSERequest(req: any, res: any, context: RequestContext): Promise<void> {\n const requestLogger = this.logger.child({ requestId: context.requestId });\n \n try {\n // 1. Authenticate\n requestLogger.debug('Authenticating request');\n const authResult = await this.config.authProvider.authenticate(context);\n \n if (!authResult.authenticated || !authResult.userId) {\n requestLogger.warn('Authentication failed', { error: authResult.error });\n throw new AuthenticationError(authResult.error || 'Authentication failed');\n }\n \n const userId = validateUserId(authResult.userId);\n requestLogger.debug('Authentication successful', { userId });\n \n // 2. Resolve resource token (or use empty string for static mode)\n let accessToken: string;\n \n if (this.config.tokenResolver) {\n // Dynamic mode - resolve token from external source\n const resolvedToken = await this.config.tokenResolver.resolveToken(\n userId,\n this.config.resourceType\n );\n \n if (!resolvedToken) {\n requestLogger.warn('Token resolution failed', { userId, resourceType: this.config.resourceType });\n throw new TokenResolutionError(userId, this.config.resourceType);\n }\n \n validateAccessToken(resolvedToken);\n accessToken = resolvedToken;\n requestLogger.debug('Token resolved', { userId, resourceType: this.config.resourceType });\n } else {\n // Static mode - no external token needed\n accessToken = '';\n requestLogger.debug('Static mode - no token resolution', { userId, mode: 'static' });\n }\n \n // 3. Get server instance\n const server = await this.getServerInstance(userId, accessToken);\n \n // 4. Forward request to server via StreamableHTTPServerTransport\n requestLogger.debug('Forwarding request to MCP server', { userId });\n \n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined // Stateless mode\n });\n \n // Connect server to transport\n await server.connect(transport);\n \n // Forward the request through the transport\n // The transport handles JSON-RPC formatting\n // Tool names are passed through unchanged\n await transport.handleRequest(req, res, req.body);\n \n requestLogger.info('Request handled successfully', {\n userId,\n resourceType: this.config.resourceType\n });\n \n } catch (error) {\n requestLogger.error('SSE request handling failed', error as Error);\n throw error;\n }\n }\n \n /**\n * Get server instance (ephemeral or from pool)\n */\n private async getServerInstance(userId: string, accessToken: string): Promise<Server> {\n if (this.config.instanceMode === 'ephemeral') {\n // Create new server instance for each request (recommended)\n this.logger.debug('Creating ephemeral server instance', { userId });\n return await this.config.serverFactory(accessToken, userId);\n }\n \n // Pooled mode\n return await this.getPooledServerInstance(userId, accessToken);\n }\n \n /**\n * Get or create pooled server instance\n */\n private async getPooledServerInstance(userId: string, accessToken: string): Promise<Server> {\n // Check if we have a cached server instance\n if (this.serverPool.has(userId)) {\n const instance = this.serverPool.get(userId)!;\n \n // Check if token changed (user rotated token)\n if (instance.accessToken !== accessToken) {\n this.logger.info('Token changed, recreating server instance', { userId });\n await instance.server.close();\n this.serverPool.delete(userId);\n } else {\n // Reuse existing instance\n instance.lastUsed = Date.now();\n this.logger.debug('Reusing pooled server instance', { userId });\n return instance.server;\n }\n }\n \n // Check pool size limit\n if (this.serverPool.size >= this.config.pooling.maxTotalServers) {\n this.logger.warn('Server pool limit reached, evicting oldest instance', {\n poolSize: this.serverPool.size,\n maxTotal: this.config.pooling.maxTotalServers\n });\n await this.evictOldestInstance();\n }\n \n // Create new server instance\n this.logger.info('Creating new pooled server instance', { userId });\n const server = await this.config.serverFactory(accessToken, userId);\n \n // Add to pool\n this.serverPool.set(userId, {\n server,\n accessToken,\n userId,\n createdAt: Date.now(),\n lastUsed: Date.now()\n });\n \n // Schedule cleanup if not already scheduled\n if (!this.cleanupTimer) {\n this.scheduleCleanup();\n }\n \n return server;\n }\n \n /**\n * Evict oldest server instance from pool\n */\n private async evictOldestInstance(): Promise<void> {\n let oldestUserId: string | null = null;\n let oldestTime = Infinity;\n \n for (const [userId, instance] of this.serverPool.entries()) {\n if (instance.lastUsed < oldestTime) {\n oldestTime = instance.lastUsed;\n oldestUserId = userId;\n }\n }\n \n if (oldestUserId) {\n const instance = this.serverPool.get(oldestUserId)!;\n await instance.server.close();\n this.serverPool.delete(oldestUserId);\n \n this.logger.debug('Evicted oldest server instance', {\n userId: oldestUserId,\n age: Date.now() - instance.createdAt\n });\n }\n }\n \n /**\n * Schedule cleanup of idle server instances\n */\n private scheduleCleanup(): void {\n const timeout = this.config.pooling.idleTimeoutMs;\n \n this.cleanupTimer = setTimeout(async () => {\n const now = Date.now();\n const toRemove: string[] = [];\n \n for (const [userId, instance] of this.serverPool.entries()) {\n if (now - instance.lastUsed > timeout) {\n toRemove.push(userId);\n }\n }\n \n for (const userId of toRemove) {\n const instance = this.serverPool.get(userId)!;\n try {\n await instance.server.close();\n this.serverPool.delete(userId);\n \n this.logger.debug('Cleaned up idle server instance', {\n userId,\n idleTime: now - instance.lastUsed\n });\n } catch (error) {\n this.logger.error('Error cleaning up server instance', error as Error, { userId });\n }\n }\n \n // Reschedule if pool is not empty\n if (this.serverPool.size > 0) {\n this.scheduleCleanup();\n } else {\n this.cleanupTimer = undefined;\n }\n }, timeout);\n }\n \n /**\n * Start stdio transport (single-user mode)\n */\n private async startStdioTransport(): Promise<void> {\n this.logger.info('Starting stdio transport');\n \n // For stdio, we use environment variable for token\n const envVar = `${this.config.resourceType.toUpperCase()}_ACCESS_TOKEN`;\n const accessToken = process.env[envVar];\n \n if (!accessToken) {\n throw new ConfigurationError(\n `${envVar} environment variable required for stdio mode`\n );\n }\n \n const userId = 'stdio-user';\n \n // Create server instance\n const server = await this.config.serverFactory(accessToken, userId);\n \n // Connect to stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n \n this.logger.info('Stdio transport started', { userId });\n }\n \n /**\n * Start SSE transport (multi-user mode)\n */\n private async startSSETransport(): Promise<void> {\n this.logger.info('Starting SSE transport', {\n port: this.config.transport.port,\n basePath: this.config.transport.basePath\n });\n \n // Import express dynamically (optional dependency)\n // @ts-ignore - Dynamic import of optional dependency\n const express = await import('express');\n const app = express.default();\n \n // Enable JSON parsing\n app.use(express.json());\n \n // Enable CORS if configured\n if (this.config.transport.cors) {\n // Validate CORS configuration\n if (!this.config.transport.corsOrigin) {\n throw new ConfigurationError(\n 'CORS origin must be explicitly configured when CORS is enabled. ' +\n 'Set transport.corsOrigin to a specific origin (e.g., \"https://app.example.com\") ' +\n 'or an array of allowed origins.'\n );\n }\n \n // Check for wildcard in production\n if (this.config.transport.corsOrigin === '*') {\n const isProduction = process.env.NODE_ENV === 'production';\n \n if (isProduction) {\n throw new ConfigurationError(\n 'CORS wildcard (*) is not allowed in production environments. ' +\n 'Specify explicit origins to prevent CSRF attacks. ' +\n 'Example: corsOrigin: \"https://app.example.com\"'\n );\n }\n \n this.logger.warn(\n 'CORS wildcard (*) detected in development. ' +\n 'This is insecure and should never be used in production.',\n { corsOrigin: this.config.transport.corsOrigin }\n );\n }\n \n // @ts-ignore - Dynamic import of optional dependency\n const cors = await import('cors');\n app.use(cors.default({\n origin: this.config.transport.corsOrigin,\n credentials: true,\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],\n exposedHeaders: ['X-Request-ID'],\n maxAge: 86400 // 24 hours\n }));\n \n this.logger.info('CORS enabled', {\n origin: this.config.transport.corsOrigin,\n credentials: true\n });\n }\n \n const basePath = this.config.transport.basePath || '/mcp';\n \n // Root endpoint info\n app.get(basePath, (req: any, res: any) => {\n res.json({\n name: this.config.name,\n version: this.config.version,\n resourceType: this.config.resourceType,\n endpoints: {\n message: `POST ${basePath}/message`,\n health: `GET ${basePath}/health`\n },\n documentation: 'https://github.com/prmichaelsen/mcp-auth'\n });\n });\n \n // SSE endpoint for MCP messages\n app.post(`${basePath}/message`, async (req: any, res: any) => {\n try {\n const context: RequestContext = {\n headers: req.headers as Record<string, string>,\n transport: 'sse',\n timestamp: new Date(),\n requestId: req.headers['x-request-id'] as string | undefined\n };\n \n // Handle request and forward to MCP server via transport\n await this.handleSSERequest(req, res, context);\n \n } catch (error) {\n this.logger.error('SSE request failed', error as Error);\n \n if (error instanceof AuthenticationError || error instanceof TokenResolutionError) {\n res.status(error.statusCode).json({\n error: error.message,\n code: error.code\n });\n } else {\n res.status(500).json({\n error: 'Internal server error',\n code: 'INTERNAL_ERROR'\n });\n }\n }\n });\n \n // Health check endpoint\n app.get(`${basePath}/health`, (req: any, res: any) => {\n res.json({\n status: 'healthy',\n name: this.config.name,\n version: this.config.version,\n resourceType: this.config.resourceType,\n instanceMode: this.config.instanceMode,\n poolSize: this.serverPool.size\n });\n });\n \n // Start server\n const port = this.config.transport.port || 3000;\n const host = this.config.transport.host || '0.0.0.0';\n \n await new Promise<void>((resolve) => {\n app.listen(port, host, () => {\n this.logger.info('SSE transport listening', {\n host,\n port,\n basePath,\n url: `http://${host}:${port}${basePath}`\n });\n resolve();\n });\n });\n }\n \n /**\n * Start HTTP transport (multi-user mode)\n */\n private async startHTTPTransport(): Promise<void> {\n this.logger.info('Starting HTTP transport', {\n port: this.config.transport.port\n });\n \n // HTTP transport is similar to SSE but with different endpoint structure\n // For now, delegate to SSE implementation\n await this.startSSETransport();\n }\n \n /**\n * Get server pool statistics\n */\n getPoolStats(): {\n size: number;\n instances: Array<{\n userId: string;\n createdAt: number;\n lastUsed: number;\n age: number;\n idleTime: number;\n }>;\n } {\n const now = Date.now();\n const instances = Array.from(this.serverPool.entries()).map(([userId, instance]) => ({\n userId,\n createdAt: instance.createdAt,\n lastUsed: instance.lastUsed,\n age: now - instance.createdAt,\n idleTime: now - instance.lastUsed\n }));\n \n return {\n size: this.serverPool.size,\n instances\n };\n }\n \n /**\n * Check if server is running\n */\n isServerRunning(): boolean {\n return this.isRunning;\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/**\n * Authenticated server wrapper implementation\n *\n * Wraps MCP servers with authentication and multi-tenancy support.\n * Uses ephemeral instances by default for security.\n */\n\nimport type { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport type { ServerWrapperConfig, NormalizedServerWrapperConfig } from './config.js';\nimport type { RequestContext, ProgressNotification } from '../types.js';\nimport {\n AuthenticationError,\n TokenResolutionError,\n ConfigurationError,\n TransportError\n} from '../utils/errors.js';\nimport { createLogger, type Logger } from '../utils/logger.js';\nimport {\n validateRequiredFields,\n validateResourceType,\n validateUserId,\n validateAccessToken,\n validateTransportConfig,\n validatePositiveNumber\n} from '../utils/validation.js';\nimport { ProgressManager } from './progress-manager.js';\nimport { InstancePoolManager } from './instance-pool-manager.js';\n\n/**\n * Server instance metadata (for pooled mode)\n */\ninterface ServerInstance {\n server: Server;\n accessToken: string;\n userId: string;\n createdAt: number;\n lastUsed: number;\n}\n\n/**\n * Authenticated server wrapper\n * \n * Wraps an MCP server with authentication, automatically handling:\n * - Request authentication via AuthProvider\n * - Token resolution via ResourceTokenResolver\n * - Per-user server instance creation (ephemeral or pooled)\n * - Transport management (stdio, SSE, HTTP)\n * \n * @example\n * ```typescript\n * const wrapper = new AuthenticatedServerWrapper({\n * serverFactory: (accessToken, userId) => createInstagramServer(accessToken),\n * authProvider: new JWTAuthProvider({ ... }),\n * tokenResolver: new DatabaseTokenResolver({ ... }),\n * resourceType: 'instagram',\n * transport: { type: 'sse', port: 3000 }\n * });\n * \n * await wrapper.start();\n * ```\n */\nexport class AuthenticatedServerWrapper {\n private config: NormalizedServerWrapperConfig;\n private logger: Logger;\n private serverPool: Map<string, ServerInstance>;\n private poolManager?: InstancePoolManager;\n private isRunning: boolean = false;\n private cleanupTimer?: NodeJS.Timeout;\n private progressContexts: Map<string, string | number> = new Map();\n private progressManager!: ProgressManager;\n private cleanupInterval?: NodeJS.Timeout;\n \n constructor(config: ServerWrapperConfig) {\n // Validate configuration\n this.validateConfig(config);\n \n // Normalize configuration with defaults\n this.config = this.normalizeConfig(config);\n \n // Initialize logger\n this.logger = createLogger(this.config.middleware.logging);\n \n // Initialize server pool (legacy - only used in old pooled mode)\n this.serverPool = new Map();\n \n // Initialize pool manager if pooled mode with instancePool config\n if (this.config.instanceMode === 'pooled' && this.config.instancePool) {\n this.poolManager = new InstancePoolManager(\n this.config.instancePool,\n this.logger\n );\n \n this.logger.info('Instance pool manager initialized', {\n maxSize: this.config.instancePool.maxSize,\n idleTimeout: this.config.instancePool.idleTimeout,\n maxLifetime: this.config.instancePool.maxLifetime\n });\n }\n \n // Initialize progress manager\n this.progressManager = new ProgressManager(this.logger);\n \n // Schedule periodic cleanup of stale streams (every minute)\n this.cleanupInterval = setInterval(() => {\n this.progressManager.cleanupStaleStreams();\n }, 60000);\n \n this.logger.info('AuthenticatedServerWrapper created', {\n name: this.config.name,\n resourceType: this.config.resourceType,\n transport: this.config.transport.type,\n instanceMode: this.config.instanceMode\n });\n }\n \n /**\n * Validate wrapper configuration\n */\n private validateConfig(config: ServerWrapperConfig): void {\n // Validate required fields manually for better type safety\n if (!config.serverFactory) {\n throw new ConfigurationError('serverFactory is required');\n }\n if (!config.authProvider) {\n throw new ConfigurationError('authProvider is required');\n }\n // tokenResolver is now optional for static servers\n if (!config.resourceType) {\n throw new ConfigurationError('resourceType is required');\n }\n if (!config.transport) {\n throw new ConfigurationError('transport is required');\n }\n \n validateResourceType(config.resourceType);\n validateTransportConfig(config.transport);\n \n // Validate instance pool config if pooled mode\n if (config.instanceMode === 'pooled') {\n if (!config.instancePool) {\n throw new ConfigurationError(\n 'instancePool configuration required when instanceMode is \"pooled\"'\n );\n }\n \n validatePositiveNumber(config.instancePool.maxSize, 'instancePool.maxSize');\n validatePositiveNumber(config.instancePool.idleTimeout, 'instancePool.idleTimeout');\n validatePositiveNumber(config.instancePool.maxLifetime, 'instancePool.maxLifetime');\n \n if (config.instancePool.maxSize < 1) {\n throw new ConfigurationError('instancePool.maxSize must be at least 1');\n }\n \n if (config.instancePool.idleTimeout < 1000) {\n throw new ConfigurationError('instancePool.idleTimeout must be at least 1000ms (1 second)');\n }\n \n if (config.instancePool.maxLifetime < config.instancePool.idleTimeout) {\n throw new ConfigurationError('instancePool.maxLifetime must be >= idleTimeout');\n }\n }\n \n // Log mode based on tokenResolver presence\n if (config.tokenResolver) {\n this.logger?.info('Token resolver configured - dynamic mode', {\n resolverType: config.tokenResolver.constructor.name\n });\n } else {\n this.logger?.info('No token resolver - static mode', {\n note: 'Server factory will receive empty string as accessToken'\n });\n }\n }\n \n /**\n * Normalize configuration with defaults\n */\n private normalizeConfig(config: ServerWrapperConfig): NormalizedServerWrapperConfig {\n return {\n serverFactory: config.serverFactory,\n authProvider: config.authProvider,\n tokenResolver: config.tokenResolver ?? null, // Convert undefined to null\n resourceType: config.resourceType,\n transport: config.transport,\n name: config.name ?? 'mcp-auth-wrapped-server',\n version: config.version ?? '1.0.0',\n instanceMode: config.instanceMode ?? 'ephemeral',\n instancePool: config.instancePool ?? null, // Convert undefined to null\n middleware: {\n rateLimit: config.middleware?.rateLimit,\n logging: config.middleware?.logging ?? { enabled: true, level: 'info' }\n },\n pooling: {\n maxServersPerUser: config.pooling?.maxServersPerUser ?? 1,\n idleTimeoutMs: config.pooling?.idleTimeoutMs ?? 300000,\n maxTotalServers: config.pooling?.maxTotalServers ?? 100\n },\n requestTimeoutMs: config.requestTimeoutMs ?? 30000,\n enableTracing: config.enableTracing ?? false\n };\n }\n \n /**\n * Start the wrapped server\n */\n async start(): Promise<void> {\n if (this.isRunning) {\n throw new ConfigurationError('Server is already running');\n }\n \n this.logger.info('Starting authenticated server wrapper', {\n name: this.config.name,\n transport: this.config.transport.type\n });\n \n // Initialize auth provider\n if (this.config.authProvider.initialize) {\n await this.config.authProvider.initialize();\n this.logger.debug('Auth provider initialized');\n }\n \n // Initialize token resolver (if configured)\n if (this.config.tokenResolver) {\n if (this.config.tokenResolver.initialize) {\n await this.config.tokenResolver.initialize();\n this.logger.debug('Token resolver initialized');\n }\n } else {\n this.logger.debug('Static mode - no token resolver to initialize');\n }\n \n // Start appropriate transport\n switch (this.config.transport.type) {\n case 'stdio':\n await this.startStdioTransport();\n break;\n case 'sse':\n await this.startSSETransport();\n break;\n case 'http':\n await this.startHTTPTransport();\n break;\n default:\n throw new TransportError(`Unsupported transport type: ${this.config.transport.type}`);\n }\n \n this.isRunning = true;\n \n this.logger.info('Server wrapper started successfully', {\n name: this.config.name,\n transport: this.config.transport.type,\n port: this.config.transport.port\n });\n }\n \n /**\n * Stop the wrapped server\n */\n async stop(): Promise<void> {\n if (!this.isRunning) {\n return;\n }\n \n this.logger.info('Stopping server wrapper');\n \n // Clear cleanup timers\n if (this.cleanupTimer) {\n clearTimeout(this.cleanupTimer);\n this.cleanupTimer = undefined;\n }\n \n if (this.cleanupInterval) {\n clearInterval(this.cleanupInterval);\n this.cleanupInterval = undefined;\n }\n \n // Close pool manager if exists\n if (this.poolManager) {\n await this.poolManager.closeAll();\n }\n \n // Close all pooled servers (legacy)\n if (this.config.instanceMode === 'pooled') {\n for (const [userId, instance] of this.serverPool.entries()) {\n try {\n await instance.server.close();\n this.logger.debug('Closed pooled server instance', { userId });\n } catch (error) {\n this.logger.error('Error closing server instance', error as Error, { userId });\n }\n }\n this.serverPool.clear();\n }\n \n // Cleanup auth provider\n if (this.config.authProvider.cleanup) {\n await this.config.authProvider.cleanup();\n this.logger.debug('Auth provider cleaned up');\n }\n \n // Cleanup token resolver (if configured)\n if (this.config.tokenResolver) {\n if (this.config.tokenResolver.cleanup) {\n await this.config.tokenResolver.cleanup();\n this.logger.debug('Token resolver cleaned up');\n }\n }\n \n this.isRunning = false;\n \n this.logger.info('Server wrapper stopped');\n }\n \n /**\n * Store progress context for a user\n */\n private storeProgressContext(userId: string, progressToken: string | number): void {\n this.progressContexts.set(userId, progressToken);\n this.logger.debug('Stored progress context', { userId, progressToken });\n }\n \n /**\n * Get progress context for a user\n */\n private getProgressContext(userId: string): string | number | undefined {\n return this.progressContexts.get(userId);\n }\n \n /**\n * Clear progress context for a user\n */\n private clearProgressContext(userId: string): void {\n this.progressContexts.delete(userId);\n this.logger.debug('Cleared progress context', { userId });\n }\n \n /**\n * Handle SSE request with direct Express req/res access\n * This allows us to use StreamableHTTPServerTransport properly\n */\n private async handleSSERequest(req: any, res: any, context: RequestContext): Promise<void> {\n const requestLogger = this.logger.child({ requestId: context.requestId });\n let userId: string | undefined;\n const progressToken = req.body._meta?.progressToken;\n \n try {\n // 1. Authenticate\n requestLogger.debug('Authenticating request');\n const authResult = await this.config.authProvider.authenticate(context);\n \n if (!authResult.authenticated || !authResult.userId) {\n requestLogger.warn('Authentication failed', { error: authResult.error });\n throw new AuthenticationError(authResult.error || 'Authentication failed');\n }\n \n userId = validateUserId(authResult.userId);\n requestLogger.debug('Authentication successful', { userId });\n \n // 2. Resolve resource token (or use empty string for static mode)\n let accessToken: string;\n \n if (this.config.tokenResolver) {\n // Dynamic mode - resolve token from external source\n const resolvedToken = await this.config.tokenResolver.resolveToken(\n userId,\n this.config.resourceType\n );\n \n if (!resolvedToken) {\n requestLogger.warn('Token resolution failed', { userId, resourceType: this.config.resourceType });\n throw new TokenResolutionError(userId, this.config.resourceType);\n }\n \n validateAccessToken(resolvedToken);\n accessToken = resolvedToken;\n requestLogger.debug('Token resolved', { userId, resourceType: this.config.resourceType });\n } else {\n // Static mode - no external token needed\n accessToken = '';\n requestLogger.debug('Static mode - no token resolution', { userId, mode: 'static' });\n }\n \n // 3. Store progress token and register stream if provided\n if (progressToken) {\n this.storeProgressContext(userId, progressToken);\n \n // Register progress stream with callback to forward notifications\n this.progressManager.registerStream(userId, progressToken, (notification) => {\n // Forward notification to client via response\n // Note: In a real implementation, this would use SSE or WebSocket\n // For now, we log that we would forward it\n requestLogger.debug('Would forward progress notification', {\n userId,\n progressToken: notification.progressToken,\n progress: notification.progress,\n total: notification.total\n });\n });\n \n requestLogger.debug('Progress token extracted and stream registered', { userId, progressToken });\n }\n \n // 4. Get server instance\n const server = await this.getServerInstance(userId, accessToken);\n \n // 5. Intercept server notifications to forward progress\n if (progressToken) {\n const originalNotification = (server as any).notification?.bind(server);\n if (originalNotification) {\n (server as any).notification = (params: any) => {\n // Check if this is a progress notification\n if (params.method === 'notifications/progress') {\n const handled = this.progressManager.forwardNotification(\n params.params as ProgressNotification\n );\n \n if (handled) {\n requestLogger.debug('Progress notification intercepted and forwarded', {\n progressToken: params.params.progressToken\n });\n return; // Don't send through original path\n }\n }\n \n // Forward other notifications normally\n if (originalNotification) {\n originalNotification(params);\n }\n };\n }\n }\n \n // 6. Forward request to server via StreamableHTTPServerTransport\n requestLogger.debug('Forwarding request to MCP server', { userId, hasProgressToken: !!progressToken });\n \n const transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: undefined // Stateless mode\n });\n \n // Connect server to transport\n await server.connect(transport);\n \n // Forward the request through the transport\n // The transport handles JSON-RPC formatting\n // Tool names are passed through unchanged\n await transport.handleRequest(req, res, req.body);\n \n requestLogger.info('Request handled successfully', {\n userId,\n resourceType: this.config.resourceType,\n hadProgressToken: !!progressToken\n });\n \n // Clean up progress context and stream after request completes\n if (progressToken) {\n this.progressManager.unregisterStream(progressToken);\n this.clearProgressContext(userId);\n }\n \n } catch (error) {\n requestLogger.error('SSE request handling failed', error as Error);\n \n // Clean up progress context and stream on error\n if (progressToken) {\n this.progressManager.unregisterStream(progressToken);\n if (userId) {\n this.clearProgressContext(userId);\n }\n }\n \n throw error;\n }\n }\n \n /**\n * Get server instance (ephemeral or from pool)\n */\n private async getServerInstance(userId: string, accessToken: string): Promise<Server> {\n if (this.config.instanceMode === 'ephemeral') {\n // Create new server instance for each request (recommended)\n this.logger.debug('Creating ephemeral server instance', { userId });\n return await this.config.serverFactory(accessToken, userId);\n }\n \n // Pooled mode\n return await this.getPooledServerInstance(userId, accessToken);\n }\n \n /**\n * Get or create pooled server instance\n */\n private async getPooledServerInstance(userId: string, accessToken: string): Promise<Server> {\n // Use new InstancePoolManager if configured\n if (this.poolManager) {\n return await this.poolManager.getInstance(\n userId,\n accessToken,\n this.config.serverFactory\n );\n }\n \n // Legacy pooling logic (deprecated)\n // Check if we have a cached server instance\n if (this.serverPool.has(userId)) {\n const instance = this.serverPool.get(userId)!;\n \n // Check if token changed (user rotated token)\n if (instance.accessToken !== accessToken) {\n this.logger.info('Token changed, recreating server instance', { userId });\n await instance.server.close();\n this.serverPool.delete(userId);\n } else {\n // Reuse existing instance\n instance.lastUsed = Date.now();\n this.logger.debug('Reusing pooled server instance', { userId });\n return instance.server;\n }\n }\n \n // Check pool size limit\n if (this.serverPool.size >= this.config.pooling.maxTotalServers) {\n this.logger.warn('Server pool limit reached, evicting oldest instance', {\n poolSize: this.serverPool.size,\n maxTotal: this.config.pooling.maxTotalServers\n });\n await this.evictOldestInstance();\n }\n \n // Create new server instance\n this.logger.info('Creating new pooled server instance', { userId });\n const server = await this.config.serverFactory(accessToken, userId);\n \n // Add to pool\n this.serverPool.set(userId, {\n server,\n accessToken,\n userId,\n createdAt: Date.now(),\n lastUsed: Date.now()\n });\n \n // Schedule cleanup if not already scheduled\n if (!this.cleanupTimer) {\n this.scheduleCleanup();\n }\n \n return server;\n }\n \n /**\n * Evict oldest server instance from pool\n */\n private async evictOldestInstance(): Promise<void> {\n let oldestUserId: string | null = null;\n let oldestTime = Infinity;\n \n for (const [userId, instance] of this.serverPool.entries()) {\n if (instance.lastUsed < oldestTime) {\n oldestTime = instance.lastUsed;\n oldestUserId = userId;\n }\n }\n \n if (oldestUserId) {\n const instance = this.serverPool.get(oldestUserId)!;\n await instance.server.close();\n this.serverPool.delete(oldestUserId);\n \n this.logger.debug('Evicted oldest server instance', {\n userId: oldestUserId,\n age: Date.now() - instance.createdAt\n });\n }\n }\n \n /**\n * Schedule cleanup of idle server instances\n */\n private scheduleCleanup(): void {\n const timeout = this.config.pooling.idleTimeoutMs;\n \n this.cleanupTimer = setTimeout(async () => {\n const now = Date.now();\n const toRemove: string[] = [];\n \n for (const [userId, instance] of this.serverPool.entries()) {\n if (now - instance.lastUsed > timeout) {\n toRemove.push(userId);\n }\n }\n \n for (const userId of toRemove) {\n const instance = this.serverPool.get(userId)!;\n try {\n await instance.server.close();\n this.serverPool.delete(userId);\n \n this.logger.debug('Cleaned up idle server instance', {\n userId,\n idleTime: now - instance.lastUsed\n });\n } catch (error) {\n this.logger.error('Error cleaning up server instance', error as Error, { userId });\n }\n }\n \n // Reschedule if pool is not empty\n if (this.serverPool.size > 0) {\n this.scheduleCleanup();\n } else {\n this.cleanupTimer = undefined;\n }\n }, timeout);\n }\n \n /**\n * Start stdio transport (single-user mode)\n */\n private async startStdioTransport(): Promise<void> {\n this.logger.info('Starting stdio transport');\n \n // For stdio, we use environment variable for token\n const envVar = `${this.config.resourceType.toUpperCase()}_ACCESS_TOKEN`;\n const accessToken = process.env[envVar];\n \n if (!accessToken) {\n throw new ConfigurationError(\n `${envVar} environment variable required for stdio mode`\n );\n }\n \n const userId = 'stdio-user';\n \n // Create server instance\n const server = await this.config.serverFactory(accessToken, userId);\n \n // Connect to stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n \n this.logger.info('Stdio transport started', { userId });\n }\n \n /**\n * Start SSE transport (multi-user mode)\n */\n private async startSSETransport(): Promise<void> {\n this.logger.info('Starting SSE transport', {\n port: this.config.transport.port,\n basePath: this.config.transport.basePath\n });\n \n // Import express dynamically (optional dependency)\n // @ts-ignore - Dynamic import of optional dependency\n const express = await import('express');\n const app = express.default();\n \n // Enable JSON parsing\n app.use(express.json());\n \n // Enable CORS if configured\n if (this.config.transport.cors) {\n // Validate CORS configuration\n if (!this.config.transport.corsOrigin) {\n throw new ConfigurationError(\n 'CORS origin must be explicitly configured when CORS is enabled. ' +\n 'Set transport.corsOrigin to a specific origin (e.g., \"https://app.example.com\") ' +\n 'or an array of allowed origins.'\n );\n }\n \n // Check for wildcard in production\n if (this.config.transport.corsOrigin === '*') {\n const isProduction = process.env.NODE_ENV === 'production';\n \n if (isProduction) {\n throw new ConfigurationError(\n 'CORS wildcard (*) is not allowed in production environments. ' +\n 'Specify explicit origins to prevent CSRF attacks. ' +\n 'Example: corsOrigin: \"https://app.example.com\"'\n );\n }\n \n this.logger.warn(\n 'CORS wildcard (*) detected in development. ' +\n 'This is insecure and should never be used in production.',\n { corsOrigin: this.config.transport.corsOrigin }\n );\n }\n \n // @ts-ignore - Dynamic import of optional dependency\n const cors = await import('cors');\n app.use(cors.default({\n origin: this.config.transport.corsOrigin,\n credentials: true,\n methods: ['GET', 'POST', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization', 'X-Request-ID'],\n exposedHeaders: ['X-Request-ID'],\n maxAge: 86400 // 24 hours\n }));\n \n this.logger.info('CORS enabled', {\n origin: this.config.transport.corsOrigin,\n credentials: true\n });\n }\n \n const basePath = this.config.transport.basePath || '/mcp';\n \n // Root endpoint info\n app.get(basePath, (req: any, res: any) => {\n res.json({\n name: this.config.name,\n version: this.config.version,\n resourceType: this.config.resourceType,\n endpoints: {\n message: `POST ${basePath}/message`,\n health: `GET ${basePath}/health`\n },\n documentation: 'https://github.com/prmichaelsen/mcp-auth'\n });\n });\n \n // SSE endpoint for MCP messages\n app.post(`${basePath}/message`, async (req: any, res: any) => {\n try {\n const context: RequestContext = {\n headers: req.headers as Record<string, string>,\n transport: 'sse',\n timestamp: new Date(),\n requestId: req.headers['x-request-id'] as string | undefined\n };\n \n // Handle request and forward to MCP server via transport\n await this.handleSSERequest(req, res, context);\n \n } catch (error) {\n this.logger.error('SSE request failed', error as Error);\n \n if (error instanceof AuthenticationError || error instanceof TokenResolutionError) {\n res.status(error.statusCode).json({\n error: error.message,\n code: error.code\n });\n } else {\n res.status(500).json({\n error: 'Internal server error',\n code: 'INTERNAL_ERROR'\n });\n }\n }\n });\n \n // Progress monitoring endpoint (authenticated)\n app.get(`${basePath}/progress/stats`, async (req: any, res: any) => {\n try {\n // Authenticate request\n const context: RequestContext = {\n headers: req.headers as Record<string, string>,\n transport: 'sse',\n timestamp: new Date(),\n requestId: req.headers['x-request-id'] as string | undefined\n };\n \n const authResult = await this.config.authProvider.authenticate(context);\n \n if (!authResult.authenticated || !authResult.userId) {\n return res.status(401).json({\n error: 'Authentication required',\n code: 'AUTHENTICATION_ERROR'\n });\n }\n \n const userId = authResult.userId;\n \n // Get user-specific metrics\n const userMetrics = this.progressManager.getUserMetrics(userId);\n const globalStats = this.progressManager.getStats();\n \n res.json({\n user: {\n userId,\n ...userMetrics\n },\n global: globalStats,\n timestamp: new Date().toISOString()\n });\n \n } catch (error) {\n this.logger.error('Error fetching progress stats', error as Error);\n res.status(500).json({\n error: 'Internal server error',\n code: 'INTERNAL_ERROR'\n });\n }\n });\n \n // Detailed metrics endpoint (authenticated)\n app.get(`${basePath}/progress/metrics`, async (req: any, res: any) => {\n try {\n // Authenticate request\n const context: RequestContext = {\n headers: req.headers as Record<string, string>,\n transport: 'sse',\n timestamp: new Date(),\n requestId: req.headers['x-request-id'] as string | undefined\n };\n \n const authResult = await this.config.authProvider.authenticate(context);\n \n if (!authResult.authenticated) {\n return res.status(401).json({\n error: 'Authentication required',\n code: 'AUTHENTICATION_ERROR'\n });\n }\n \n // Get all metrics (could add admin check here)\n const allMetrics = this.progressManager.getAllMetrics();\n const health = this.progressManager.checkHealth();\n \n res.json({\n metrics: allMetrics,\n health,\n timestamp: new Date().toISOString()\n });\n \n } catch (error) {\n this.logger.error('Error fetching progress metrics', error as Error);\n res.status(500).json({\n error: 'Internal server error',\n code: 'INTERNAL_ERROR'\n });\n }\n });\n \n // Health check endpoint\n app.get(`${basePath}/health`, (req: any, res: any) => {\n res.json({\n status: 'healthy',\n name: this.config.name,\n version: this.config.version,\n resourceType: this.config.resourceType,\n instanceMode: this.config.instanceMode,\n poolSize: this.serverPool.size\n });\n });\n \n // Start server\n const port = this.config.transport.port || 3000;\n const host = this.config.transport.host || '0.0.0.0';\n \n await new Promise<void>((resolve) => {\n app.listen(port, host, () => {\n this.logger.info('SSE transport listening', {\n host,\n port,\n basePath,\n url: `http://${host}:${port}${basePath}`\n });\n resolve();\n });\n });\n }\n \n /**\n * Start HTTP transport (multi-user mode)\n */\n private async startHTTPTransport(): Promise<void> {\n this.logger.info('Starting HTTP transport', {\n port: this.config.transport.port\n });\n \n // HTTP transport is similar to SSE but with different endpoint structure\n // For now, delegate to SSE implementation\n await this.startSSETransport();\n }\n \n /**\n * Get server pool statistics\n */\n getPoolStats(): {\n size: number;\n instances: Array<{\n userId: string;\n createdAt: number;\n lastUsed: number;\n age: number;\n idleTime: number;\n }>;\n } {\n const now = Date.now();\n const instances = Array.from(this.serverPool.entries()).map(([userId, instance]) => ({\n userId,\n createdAt: instance.createdAt,\n lastUsed: instance.lastUsed,\n age: now - instance.createdAt,\n idleTime: now - instance.lastUsed\n }));\n \n return {\n size: this.serverPool.size,\n instances\n };\n }\n \n /**\n * Check if server is running\n */\n isServerRunning(): boolean {\n return this.isRunning;\n }\n}\n\n/**\n * Convenience function to create and configure an authenticated server wrapper\n *\n * @param config - Server wrapper configuration\n * @returns Configured AuthenticatedServerWrapper instance\n *\n * @example\n * ```typescript\n * const wrapped = wrapServer({\n * serverFactory: (accessToken, userId) => createMyServer(accessToken, userId),\n * authProvider: new JWTAuthProvider({ jwtSecret: process.env.JWT_SECRET }),\n * tokenResolver: new APITokenResolver({ ... }),\n * resourceType: 'myapi',\n * transport: { type: 'sse', port: 3000 }\n * });\n *\n * await wrapped.start();\n * ```\n */\nexport function wrapServer(config: ServerWrapperConfig): AuthenticatedServerWrapper {\n return new AuthenticatedServerWrapper(config);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|