@prmichaelsen/mcp-auth 7.0.4 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +2 -2
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wrapper/index.d.ts +6 -30
- package/dist/wrapper/index.d.ts.map +1 -1
- package/dist/wrapper/index.js +4 -5
- package/dist/wrapper/index.js.map +3 -3
- 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 +35 -0
- package/dist/wrapper/server-wrapper.d.ts.map +1 -1
- package/dist/wrapper/server-wrapper.js +150 -4
- package/dist/wrapper/server-wrapper.js.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -326,6 +326,155 @@ transport: {
|
|
|
326
326
|
}
|
|
327
327
|
```
|
|
328
328
|
|
|
329
|
+
## Progress Streaming
|
|
330
|
+
|
|
331
|
+
mcp-auth supports MCP progress notifications, allowing wrapped servers to stream progress updates to clients during long-running operations.
|
|
332
|
+
|
|
333
|
+
### How It Works
|
|
334
|
+
|
|
335
|
+
When a client provides a `progressToken` in the request, mcp-auth automatically:
|
|
336
|
+
1. Extracts the progress token from the request
|
|
337
|
+
2. Passes it through to the wrapped MCP server
|
|
338
|
+
3. The wrapped server can send progress notifications back to the client
|
|
339
|
+
4. Progress is isolated per user (multi-tenant safe)
|
|
340
|
+
|
|
341
|
+
### Client-Side Usage
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
345
|
+
|
|
346
|
+
const client = new Client({
|
|
347
|
+
name: 'my-client',
|
|
348
|
+
version: '1.0.0'
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Call tool with progress support
|
|
352
|
+
const result = await client.request({
|
|
353
|
+
method: 'tools/call',
|
|
354
|
+
params: {
|
|
355
|
+
name: 'long_running_operation',
|
|
356
|
+
arguments: { /* ... */ }
|
|
357
|
+
}
|
|
358
|
+
}, {
|
|
359
|
+
progressToken: 'operation-123',
|
|
360
|
+
onprogress: (progress) => {
|
|
361
|
+
console.log(`Progress: ${progress.progress}/${progress.total}`);
|
|
362
|
+
console.log(`Message: ${progress.message}`);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Server-Side Implementation
|
|
368
|
+
|
|
369
|
+
Wrapped MCP servers can send progress notifications:
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
// In your MCP server tool handler
|
|
373
|
+
export async function handleLongOperation(
|
|
374
|
+
args: any,
|
|
375
|
+
extra?: { progressToken?: string | number }
|
|
376
|
+
): Promise<any> {
|
|
377
|
+
const progressToken = extra?.progressToken;
|
|
378
|
+
|
|
379
|
+
if (progressToken) {
|
|
380
|
+
// Send progress notifications
|
|
381
|
+
server.notification({
|
|
382
|
+
method: 'notifications/progress',
|
|
383
|
+
params: {
|
|
384
|
+
progressToken,
|
|
385
|
+
progress: 50,
|
|
386
|
+
total: 100,
|
|
387
|
+
message: 'Processing...'
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ... perform operation
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Features
|
|
397
|
+
|
|
398
|
+
- ✅ **Automatic Pass-Through**: Progress tokens automatically forwarded to wrapped servers
|
|
399
|
+
- ✅ **Multi-Tenant Safe**: Progress notifications isolated per user
|
|
400
|
+
- ✅ **Backward Compatible**: Works with or without progress token (graceful degradation)
|
|
401
|
+
- ✅ **Zero Configuration**: No additional setup required
|
|
402
|
+
|
|
403
|
+
### Monitoring
|
|
404
|
+
|
|
405
|
+
Monitor active progress streams via authenticated endpoints:
|
|
406
|
+
|
|
407
|
+
**User Stats** (shows your own progress streams):
|
|
408
|
+
```bash
|
|
409
|
+
GET /mcp/progress/stats
|
|
410
|
+
Authorization: Bearer <jwt>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Response**:
|
|
414
|
+
```json
|
|
415
|
+
{
|
|
416
|
+
"user": {
|
|
417
|
+
"userId": "user123",
|
|
418
|
+
"activeStreams": 2,
|
|
419
|
+
"totalMessages": 1543,
|
|
420
|
+
"totalBytes": 45231,
|
|
421
|
+
"oldestStreamAge": 120000
|
|
422
|
+
},
|
|
423
|
+
"global": {
|
|
424
|
+
"activeStreams": 15,
|
|
425
|
+
"totalMessages": 8234,
|
|
426
|
+
"userCount": 8
|
|
427
|
+
},
|
|
428
|
+
"timestamp": "2026-02-23T21:00:00.000Z"
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**All Metrics** (detailed stream information):
|
|
433
|
+
```bash
|
|
434
|
+
GET /mcp/progress/metrics
|
|
435
|
+
Authorization: Bearer <jwt>
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Response**:
|
|
439
|
+
```json
|
|
440
|
+
{
|
|
441
|
+
"metrics": [
|
|
442
|
+
{
|
|
443
|
+
"userId": "user123",
|
|
444
|
+
"progressToken": "op-456",
|
|
445
|
+
"startTime": 1708725600000,
|
|
446
|
+
"lastUpdate": 1708725650000,
|
|
447
|
+
"duration": 50000,
|
|
448
|
+
"messageCount": 125,
|
|
449
|
+
"bytesTransferred": 45231,
|
|
450
|
+
"averageMessageSize": 361.8,
|
|
451
|
+
"messagesPerSecond": 2.5
|
|
452
|
+
}
|
|
453
|
+
],
|
|
454
|
+
"health": {
|
|
455
|
+
"healthy": true,
|
|
456
|
+
"issues": [],
|
|
457
|
+
"warnings": []
|
|
458
|
+
},
|
|
459
|
+
"timestamp": "2026-02-23T21:00:00.000Z"
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Performance
|
|
464
|
+
|
|
465
|
+
- **Memory**: ~1KB per active stream
|
|
466
|
+
- **Network**: ~100-500 bytes per progress notification
|
|
467
|
+
- **CPU**: <1% overhead for typical workloads
|
|
468
|
+
- **Cleanup**: Stale streams (>5 minutes idle) automatically removed every minute
|
|
469
|
+
|
|
470
|
+
### Notes
|
|
471
|
+
|
|
472
|
+
- Progress streaming requires SSE or HTTP transport (not available with stdio)
|
|
473
|
+
- Progress tokens are automatically cleaned up after request completion
|
|
474
|
+
- Wrapped servers must implement progress notification support to send updates
|
|
475
|
+
- Health checks detect stale streams and high message rates
|
|
476
|
+
- Monitoring endpoints require authentication
|
|
477
|
+
|
|
329
478
|
## MCP Server Contract
|
|
330
479
|
|
|
331
480
|
To make your MCP server compatible with `wrapServer()`, export a factory function:
|
|
@@ -417,6 +566,24 @@ Working examples coming soon in [`examples/`](./examples/):
|
|
|
417
566
|
- `withTimeout()` - Request timeout
|
|
418
567
|
- `withRetry()` - Automatic retry on failure
|
|
419
568
|
|
|
569
|
+
## Example Projects
|
|
570
|
+
|
|
571
|
+
Real-world projects using `@prmichaelsen/mcp-auth`:
|
|
572
|
+
|
|
573
|
+
### [@prmichaelsen/agentbase-mcp-server](https://github.com/prmichaelsen/agentbase-mcp-server)
|
|
574
|
+
Multi-tenant Instagram MCP server with JWT authentication
|
|
575
|
+
- **Pattern**: Server Wrapping (Dynamic Mode)
|
|
576
|
+
- **Auth**: JWT + API-based token resolution
|
|
577
|
+
- **Use Case**: External API credentials (Instagram)
|
|
578
|
+
- **Transport**: SSE over HTTP
|
|
579
|
+
|
|
580
|
+
### [@prmichaelsen/remember-mcp-server](https://github.com/prmichaelsen/remember-mcp-server)
|
|
581
|
+
Multi-tenant MCP server for remember-mcp with Platform JWT authentication
|
|
582
|
+
- **Pattern**: Server Wrapping (Static Mode)
|
|
583
|
+
- **Auth**: JWT only (no tokenResolver)
|
|
584
|
+
- **Use Case**: User-scoped data in own database
|
|
585
|
+
- **Transport**: SSE over HTTP
|
|
586
|
+
|
|
420
587
|
## License
|
|
421
588
|
|
|
422
589
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @packageDocumentation
|
|
11
11
|
*/
|
|
12
|
-
export { wrapServer, AuthenticatedServerWrapper, type ServerWrapperConfig, type MCPServerFactory, type NormalizedServerWrapperConfig } from './wrapper/index.js';
|
|
12
|
+
export { wrapServer, AuthenticatedServerWrapper, type ServerWrapperConfig, type MCPServerFactory, type NormalizedServerWrapperConfig, ProgressManager, type ProgressCallback, type ProgressStreamMetrics } from './wrapper/index.js';
|
|
13
13
|
export { AuthenticatedMCPServer, type ServerConfig, type NormalizedServerConfig, withAuth, compose, withLogging, withRateLimit, withTimeout, withRetry, type Tool, AuthenticatedTool, createAuthenticatedTool, type AuthenticatedToolHandler } from './server/index.js';
|
|
14
|
-
export type { TransportType, RequestContext, AuthResult, TransportConfig, RateLimitConfig, LoggingConfig, MiddlewareConfig, PoolingConfig, Result, AsyncFunction, ToolHandler, Middleware } from './types.js';
|
|
14
|
+
export type { TransportType, RequestContext, AuthResult, TransportConfig, RateLimitConfig, LoggingConfig, MiddlewareConfig, PoolingConfig, Result, AsyncFunction, ToolHandler, Middleware, ProgressNotification, RequestExtra } from './types.js';
|
|
15
15
|
export type { AuthProvider, ResourceTokenResolver, AuthenticatedContext, AuthProviderConfig, TokenResolverConfig } from './auth/types.js';
|
|
16
16
|
export { BaseAuthProvider } from './auth/base-provider.js';
|
|
17
17
|
export { EnvAuthProvider, type EnvAuthProviderConfig, SimpleTokenResolver, type SimpleTokenResolverConfig, JWTAuthProvider, type JWTAuthProviderConfig, type JWTPayload, JWTTokenResolver, type JWTTokenResolverConfig, APITokenResolver, type APITokenResolverConfig } from './auth/providers/index.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,EACL,UAAU,EACV,0BAA0B,EAC1B,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,6BAA6B,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,EACL,UAAU,EACV,0BAA0B,EAC1B,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACrB,KAAK,6BAA6B,EAClC,eAAe,EACf,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC3B,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,EACL,sBAAsB,EACtB,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAC3B,QAAQ,EACR,OAAO,EACP,WAAW,EACX,aAAa,EACb,WAAW,EACX,SAAS,EACT,KAAK,IAAI,EACT,iBAAiB,EACjB,uBAAuB,EACvB,KAAK,wBAAwB,EAC9B,MAAM,mBAAmB,CAAC;AAM3B,YAAY,EACV,aAAa,EACb,cAAc,EACd,UAAU,EACV,eAAe,EACf,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,EACb,MAAM,EACN,aAAa,EACb,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,YAAY,EACb,MAAM,YAAY,CAAC;AAMpB,YAAY,EACV,YAAY,EACZ,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG3D,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,KAAK,yBAAyB,EAC9B,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,UAAU,EACf,gBAAgB,EAChB,KAAK,sBAAsB,EAC3B,gBAAgB,EAChB,KAAK,sBAAsB,EAC5B,MAAM,2BAA2B,CAAC;AAQnC,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAM3B,OAAO,EAEL,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EAGpB,MAAM,EACN,QAAQ,EACR,aAAa,EACb,YAAY,EACZ,kBAAkB,EAGlB,sBAAsB,EACtB,WAAW,EACX,sBAAsB,EACtB,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAMlD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
wrapServer,
|
|
3
|
-
AuthenticatedServerWrapper
|
|
3
|
+
AuthenticatedServerWrapper,
|
|
4
|
+
ProgressManager
|
|
4
5
|
} from "./wrapper/index.js";
|
|
5
6
|
import {
|
|
6
7
|
AuthenticatedMCPServer,
|
|
@@ -81,6 +82,7 @@ export {
|
|
|
81
82
|
Logger,
|
|
82
83
|
MCPAuthError,
|
|
83
84
|
MissingCredentialsError,
|
|
85
|
+
ProgressManager,
|
|
84
86
|
RateLimitError,
|
|
85
87
|
ServerPoolError,
|
|
86
88
|
SimpleTokenResolver,
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * @prmichaelsen/mcp-auth\n *\n * Authentication and multi-tenancy framework for MCP (Model Context Protocol) servers.\n *\n * Supports two complementary patterns:\n * 1. **Server Wrapping** - Wrap existing MCP servers without modification (MCP-level auth)\n * 2. **Tool-Level Auth** - Build new MCP servers with integrated auth\n *\n * @packageDocumentation\n */\n\n// ============================================================================\n// PATTERN 1: SERVER WRAPPING (MCP-Level Auth)\n// ============================================================================\n// Use this to wrap existing MCP servers without modifying them\n// Ideal for multi-tenant services that host multiple MCP servers\n\nexport {\n wrapServer,\n AuthenticatedServerWrapper,\n type ServerWrapperConfig,\n type MCPServerFactory,\n type NormalizedServerWrapperConfig\n} from './wrapper/index.js';\n\n// ============================================================================\n// PATTERN 2: TOOL-LEVEL AUTH\n// ============================================================================\n// Use this to build new MCP servers with integrated authentication\n// Provides fine-grained control over auth per tool\n\nexport {\n AuthenticatedMCPServer,\n type ServerConfig,\n type NormalizedServerConfig,\n withAuth,\n compose,\n withLogging,\n withRateLimit,\n withTimeout,\n withRetry,\n type Tool,\n AuthenticatedTool,\n createAuthenticatedTool,\n type AuthenticatedToolHandler\n} from './server/index.js';\n\n// ============================================================================\n// SHARED: CORE TYPES\n// ============================================================================\n\nexport type {\n TransportType,\n RequestContext,\n AuthResult,\n TransportConfig,\n RateLimitConfig,\n LoggingConfig,\n MiddlewareConfig,\n PoolingConfig,\n Result,\n AsyncFunction,\n ToolHandler,\n Middleware\n} from './types.js';\n\n// ============================================================================\n// SHARED: AUTHENTICATION\n// ============================================================================\n\nexport type {\n AuthProvider,\n ResourceTokenResolver,\n AuthenticatedContext,\n AuthProviderConfig,\n TokenResolverConfig\n} from './auth/types.js';\n\nexport { BaseAuthProvider } from './auth/base-provider.js';\n\n// Providers\nexport {\n EnvAuthProvider,\n type EnvAuthProviderConfig,\n SimpleTokenResolver,\n type SimpleTokenResolverConfig,\n JWTAuthProvider,\n type JWTAuthProviderConfig,\n type JWTPayload,\n JWTTokenResolver,\n type JWTTokenResolverConfig,\n APITokenResolver,\n type APITokenResolverConfig\n} from './auth/providers/index.js';\n\n// ============================================================================\n// TENANT MANAGER INTEGRATION\n// ============================================================================\n// Standard interfaces for tenant manager APIs\n// Helps tenant platforms provide consistent APIs for MCP servers\n\nexport {\n type TenantAPIErrorResponse,\n type CredentialsAPIResponse,\n type CredentialsAPIHeaders,\n type TenantManagerAPI,\n TenantAPIStatusCode,\n TenantAPIErrorCode,\n createTenantAPIError,\n TenantAPIErrors\n} from './tenant/index.js';\n\n// ============================================================================\n// SHARED: UTILITIES\n// ============================================================================\n\nexport {\n // Errors\n MCPAuthError,\n AuthenticationError,\n TokenResolutionError,\n InvalidTokenError,\n MissingCredentialsError,\n ConfigurationError,\n RateLimitError,\n ServerPoolError,\n TransportError,\n ValidationError,\n isMCPAuthError,\n isAuthenticationError,\n isTokenResolutionError,\n isRateLimitError,\n formatErrorForClient,\n \n // Logger\n Logger,\n LogLevel,\n defaultLogger,\n createLogger,\n sanitizeForLogging,\n \n // Validation\n validateNonEmptyString,\n validateUrl,\n validatePositiveNumber,\n validatePort,\n validateEnum,\n validateObject,\n validateFunction,\n validateRequiredFields,\n validateTransportConfig,\n validateRateLimitConfig,\n validateLoggingConfig,\n validatePoolingConfig,\n sanitizeString,\n validateUserId,\n validateResourceType,\n validateAccessToken\n} from './utils/index.js';\n\n// Re-export types for convenience\nexport type { LogEntry } from './utils/logger.js';\n\n// ============================================================================\n// USAGE EXAMPLES\n// ============================================================================\n\n/**\n * @example Server Wrapping Pattern (MCP-Level Auth)\n * ```typescript\n * import { wrapServer, JWTAuthProvider, DatabaseTokenResolver } from '@prmichaelsen/mcp-auth';\n * import { createServer as createInstagramServer } from '@prmichaelsen/instagram-mcp';\n *\n * const wrapped = wrapServer({\n * serverFactory: (accessToken, userId) => createInstagramServer(accessToken, userId),\n * authProvider: new JWTAuthProvider({ jwtSecret: process.env.JWT_SECRET }),\n * tokenResolver: new DatabaseTokenResolver({ database: {...} }),\n * resourceType: 'instagram',\n * transport: { type: 'sse', port: 3000 }\n * });\n *\n * await wrapped.start();\n * ```\n *\n * @example Tool-Level Auth Pattern\n * ```typescript\n * import { AuthenticatedMCPServer, withAuth, EnvAuthProvider } from '@prmichaelsen/mcp-auth';\n *\n * const server = new AuthenticatedMCPServer({\n * name: 'my-server',\n * authProvider: new EnvAuthProvider(),\n * tokenResolver: new SimpleTokenResolver({ tokenEnvVar: 'API_TOKEN' }),\n * resourceType: 'myapi',\n * transport: { type: 'stdio' }\n * });\n *\n * server.registerTool('get_data', withAuth(async (args, accessToken, userId) => {\n * const client = new MyAPIClient(accessToken);\n * return client.getData(args);\n * }));\n *\n * await server.start();\n * ```\n */\n"],
|
|
5
|
-
"mappings": "AAkBA;AAAA,EACE;AAAA,EACA;AAAA,
|
|
4
|
+
"sourcesContent": ["/**\n * @prmichaelsen/mcp-auth\n *\n * Authentication and multi-tenancy framework for MCP (Model Context Protocol) servers.\n *\n * Supports two complementary patterns:\n * 1. **Server Wrapping** - Wrap existing MCP servers without modification (MCP-level auth)\n * 2. **Tool-Level Auth** - Build new MCP servers with integrated auth\n *\n * @packageDocumentation\n */\n\n// ============================================================================\n// PATTERN 1: SERVER WRAPPING (MCP-Level Auth)\n// ============================================================================\n// Use this to wrap existing MCP servers without modifying them\n// Ideal for multi-tenant services that host multiple MCP servers\n\nexport {\n wrapServer,\n AuthenticatedServerWrapper,\n type ServerWrapperConfig,\n type MCPServerFactory,\n type NormalizedServerWrapperConfig,\n ProgressManager,\n type ProgressCallback,\n type ProgressStreamMetrics\n} from './wrapper/index.js';\n\n// ============================================================================\n// PATTERN 2: TOOL-LEVEL AUTH\n// ============================================================================\n// Use this to build new MCP servers with integrated authentication\n// Provides fine-grained control over auth per tool\n\nexport {\n AuthenticatedMCPServer,\n type ServerConfig,\n type NormalizedServerConfig,\n withAuth,\n compose,\n withLogging,\n withRateLimit,\n withTimeout,\n withRetry,\n type Tool,\n AuthenticatedTool,\n createAuthenticatedTool,\n type AuthenticatedToolHandler\n} from './server/index.js';\n\n// ============================================================================\n// SHARED: CORE TYPES\n// ============================================================================\n\nexport type {\n TransportType,\n RequestContext,\n AuthResult,\n TransportConfig,\n RateLimitConfig,\n LoggingConfig,\n MiddlewareConfig,\n PoolingConfig,\n Result,\n AsyncFunction,\n ToolHandler,\n Middleware,\n ProgressNotification,\n RequestExtra\n} from './types.js';\n\n// ============================================================================\n// SHARED: AUTHENTICATION\n// ============================================================================\n\nexport type {\n AuthProvider,\n ResourceTokenResolver,\n AuthenticatedContext,\n AuthProviderConfig,\n TokenResolverConfig\n} from './auth/types.js';\n\nexport { BaseAuthProvider } from './auth/base-provider.js';\n\n// Providers\nexport {\n EnvAuthProvider,\n type EnvAuthProviderConfig,\n SimpleTokenResolver,\n type SimpleTokenResolverConfig,\n JWTAuthProvider,\n type JWTAuthProviderConfig,\n type JWTPayload,\n JWTTokenResolver,\n type JWTTokenResolverConfig,\n APITokenResolver,\n type APITokenResolverConfig\n} from './auth/providers/index.js';\n\n// ============================================================================\n// TENANT MANAGER INTEGRATION\n// ============================================================================\n// Standard interfaces for tenant manager APIs\n// Helps tenant platforms provide consistent APIs for MCP servers\n\nexport {\n type TenantAPIErrorResponse,\n type CredentialsAPIResponse,\n type CredentialsAPIHeaders,\n type TenantManagerAPI,\n TenantAPIStatusCode,\n TenantAPIErrorCode,\n createTenantAPIError,\n TenantAPIErrors\n} from './tenant/index.js';\n\n// ============================================================================\n// SHARED: UTILITIES\n// ============================================================================\n\nexport {\n // Errors\n MCPAuthError,\n AuthenticationError,\n TokenResolutionError,\n InvalidTokenError,\n MissingCredentialsError,\n ConfigurationError,\n RateLimitError,\n ServerPoolError,\n TransportError,\n ValidationError,\n isMCPAuthError,\n isAuthenticationError,\n isTokenResolutionError,\n isRateLimitError,\n formatErrorForClient,\n \n // Logger\n Logger,\n LogLevel,\n defaultLogger,\n createLogger,\n sanitizeForLogging,\n \n // Validation\n validateNonEmptyString,\n validateUrl,\n validatePositiveNumber,\n validatePort,\n validateEnum,\n validateObject,\n validateFunction,\n validateRequiredFields,\n validateTransportConfig,\n validateRateLimitConfig,\n validateLoggingConfig,\n validatePoolingConfig,\n sanitizeString,\n validateUserId,\n validateResourceType,\n validateAccessToken\n} from './utils/index.js';\n\n// Re-export types for convenience\nexport type { LogEntry } from './utils/logger.js';\n\n// ============================================================================\n// USAGE EXAMPLES\n// ============================================================================\n\n/**\n * @example Server Wrapping Pattern (MCP-Level Auth)\n * ```typescript\n * import { wrapServer, JWTAuthProvider, DatabaseTokenResolver } from '@prmichaelsen/mcp-auth';\n * import { createServer as createInstagramServer } from '@prmichaelsen/instagram-mcp';\n *\n * const wrapped = wrapServer({\n * serverFactory: (accessToken, userId) => createInstagramServer(accessToken, userId),\n * authProvider: new JWTAuthProvider({ jwtSecret: process.env.JWT_SECRET }),\n * tokenResolver: new DatabaseTokenResolver({ database: {...} }),\n * resourceType: 'instagram',\n * transport: { type: 'sse', port: 3000 }\n * });\n *\n * await wrapped.start();\n * ```\n *\n * @example Tool-Level Auth Pattern\n * ```typescript\n * import { AuthenticatedMCPServer, withAuth, EnvAuthProvider } from '@prmichaelsen/mcp-auth';\n *\n * const server = new AuthenticatedMCPServer({\n * name: 'my-server',\n * authProvider: new EnvAuthProvider(),\n * tokenResolver: new SimpleTokenResolver({ tokenEnvVar: 'API_TOKEN' }),\n * resourceType: 'myapi',\n * transport: { type: 'stdio' }\n * });\n *\n * server.registerTool('get_data', withAuth(async (args, accessToken, userId) => {\n * const client = new MyAPIClient(accessToken);\n * return client.getData(args);\n * }));\n *\n * await server.start();\n * ```\n */\n"],
|
|
5
|
+
"mappings": "AAkBA;AAAA,EACE;AAAA,EACA;AAAA,EAIA;AAAA,OAGK;AAQP;AAAA,EACE;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OAEK;AAmCP,SAAS,wBAAwB;AAGjC;AAAA,EACE;AAAA,EAEA;AAAA,EAEA;AAAA,EAGA;AAAA,EAEA;AAAA,OAEK;AAQP;AAAA,EAKE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAMP;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAGA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -8,6 +8,42 @@
|
|
|
8
8
|
* Transport types supported by the framework
|
|
9
9
|
*/
|
|
10
10
|
export type TransportType = 'stdio' | 'sse' | 'http';
|
|
11
|
+
/**
|
|
12
|
+
* Progress notification parameters
|
|
13
|
+
* Used for streaming progress updates during long-running operations
|
|
14
|
+
*/
|
|
15
|
+
export interface ProgressNotification {
|
|
16
|
+
/**
|
|
17
|
+
* Unique token identifying this progress stream
|
|
18
|
+
*/
|
|
19
|
+
progressToken: string | number;
|
|
20
|
+
/**
|
|
21
|
+
* Current progress value (optional)
|
|
22
|
+
*/
|
|
23
|
+
progress?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Total expected value (optional)
|
|
26
|
+
*/
|
|
27
|
+
total?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Progress message/description (optional)
|
|
30
|
+
*/
|
|
31
|
+
message?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Request extra parameters (from MCP SDK)
|
|
35
|
+
* Contains optional metadata passed with requests
|
|
36
|
+
*/
|
|
37
|
+
export interface RequestExtra {
|
|
38
|
+
/**
|
|
39
|
+
* Progress token for streaming progress notifications
|
|
40
|
+
*/
|
|
41
|
+
progressToken?: string | number;
|
|
42
|
+
/**
|
|
43
|
+
* Additional custom parameters
|
|
44
|
+
*/
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
}
|
|
11
47
|
/**
|
|
12
48
|
* Request context passed to authentication providers
|
|
13
49
|
* Contains information about the incoming request
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/wrapper/index.d.ts
CHANGED
|
@@ -1,36 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Server wrapper
|
|
3
|
-
*
|
|
4
|
-
* Provides the main wrapServer() function for wrapping MCP servers with authentication.
|
|
2
|
+
* Server wrapper exports
|
|
5
3
|
*/
|
|
6
|
-
|
|
7
|
-
import type { ServerWrapperConfig } from './config.js';
|
|
8
|
-
export type { ServerWrapperConfig, MCPServerFactory, NormalizedServerWrapperConfig } from './config.js';
|
|
4
|
+
export { wrapServer } from './server-wrapper.js';
|
|
9
5
|
export { AuthenticatedServerWrapper } from './server-wrapper.js';
|
|
6
|
+
export type { ServerWrapperConfig, NormalizedServerWrapperConfig, MCPServerFactory } from './config.js';
|
|
10
7
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* This is the main entry point for the server wrapping pattern.
|
|
14
|
-
* It creates an authenticated wrapper around your MCP server factory function.
|
|
15
|
-
*
|
|
16
|
-
* @param config - Server wrapper configuration
|
|
17
|
-
* @returns Authenticated server wrapper instance
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```typescript
|
|
21
|
-
* import { wrapServer } from '@prmichaelsen/mcp-auth';
|
|
22
|
-
* import { createServer } from '@myorg/my-mcp-server';
|
|
23
|
-
*
|
|
24
|
-
* const wrapped = wrapServer({
|
|
25
|
-
* serverFactory: (accessToken, userId) => createServer(accessToken, userId),
|
|
26
|
-
* authProvider: new JWTAuthProvider({ ... }),
|
|
27
|
-
* tokenResolver: new DatabaseTokenResolver({ ... }),
|
|
28
|
-
* resourceType: 'myapi',
|
|
29
|
-
* transport: { type: 'sse', port: 3000 }
|
|
30
|
-
* });
|
|
31
|
-
*
|
|
32
|
-
* await wrapped.start();
|
|
33
|
-
* ```
|
|
8
|
+
* Progress manager exports
|
|
34
9
|
*/
|
|
35
|
-
export
|
|
10
|
+
export { ProgressManager, type ProgressStreamMetrics } from './progress-manager.js';
|
|
11
|
+
export type { ProgressCallback } from './progress-manager.js';
|
|
36
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/wrapper/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/wrapper/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,YAAY,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAExG;;GAEG;AACH,OAAO,EAAE,eAAe,EAAE,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACpF,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/wrapper/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { wrapServer } from "./server-wrapper.js";
|
|
1
2
|
import { AuthenticatedServerWrapper } from "./server-wrapper.js";
|
|
2
|
-
import {
|
|
3
|
-
function wrapServer(config) {
|
|
4
|
-
return new AuthenticatedServerWrapper(config);
|
|
5
|
-
}
|
|
3
|
+
import { ProgressManager } from "./progress-manager.js";
|
|
6
4
|
export {
|
|
7
|
-
|
|
5
|
+
AuthenticatedServerWrapper,
|
|
6
|
+
ProgressManager,
|
|
8
7
|
wrapServer
|
|
9
8
|
};
|
|
10
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/wrapper/index.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Server wrapper
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": [
|
|
4
|
+
"sourcesContent": ["/**\n * Server wrapper exports\n */\n\nexport { wrapServer } from './server-wrapper.js';\nexport { AuthenticatedServerWrapper } from './server-wrapper.js';\nexport type { ServerWrapperConfig, NormalizedServerWrapperConfig, MCPServerFactory } from './config.js';\n\n/**\n * Progress manager exports\n */\nexport { ProgressManager, type ProgressStreamMetrics } from './progress-manager.js';\nexport type { ProgressCallback } from './progress-manager.js';\n"],
|
|
5
|
+
"mappings": "AAIA,SAAS,kBAAkB;AAC3B,SAAS,kCAAkC;AAM3C,SAAS,uBAAmD;",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress notification manager for multi-tenant progress streaming
|
|
3
|
+
*
|
|
4
|
+
* Manages routing of progress notifications from wrapped MCP servers
|
|
5
|
+
* to the correct clients in multi-tenant deployments.
|
|
6
|
+
*/
|
|
7
|
+
import { Logger } from '../utils/logger.js';
|
|
8
|
+
import type { ProgressNotification } from '../types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Progress stream metadata
|
|
11
|
+
*/
|
|
12
|
+
interface ProgressStream {
|
|
13
|
+
userId: string;
|
|
14
|
+
progressToken: string | number;
|
|
15
|
+
startTime: number;
|
|
16
|
+
lastUpdate: number;
|
|
17
|
+
messageCount: number;
|
|
18
|
+
bytesTransferred?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Progress notification callback
|
|
22
|
+
*/
|
|
23
|
+
export type ProgressCallback = (notification: ProgressNotification) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Manages progress notification routing for multi-tenant deployments
|
|
26
|
+
*/
|
|
27
|
+
export declare class ProgressManager {
|
|
28
|
+
private streams;
|
|
29
|
+
private callbacks;
|
|
30
|
+
private logger;
|
|
31
|
+
constructor(logger: Logger);
|
|
32
|
+
/**
|
|
33
|
+
* Register a progress stream for a user
|
|
34
|
+
*/
|
|
35
|
+
registerStream(userId: string, progressToken: string | number, callback: ProgressCallback): void;
|
|
36
|
+
/**
|
|
37
|
+
* Forward a progress notification to the correct client
|
|
38
|
+
*/
|
|
39
|
+
forwardNotification(notification: ProgressNotification): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Unregister a progress stream
|
|
42
|
+
*/
|
|
43
|
+
unregisterStream(progressToken: string | number): void;
|
|
44
|
+
/**
|
|
45
|
+
* Get active streams for a user
|
|
46
|
+
*/
|
|
47
|
+
getUserStreams(userId: string): ProgressStream[];
|
|
48
|
+
/**
|
|
49
|
+
* Clean up stale streams (no updates for 5 minutes)
|
|
50
|
+
*/
|
|
51
|
+
cleanupStaleStreams(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Get statistics about active streams
|
|
54
|
+
*/
|
|
55
|
+
getStats(): {
|
|
56
|
+
activeStreams: number;
|
|
57
|
+
totalMessages: number;
|
|
58
|
+
userCount: number;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Get detailed metrics for a specific stream
|
|
62
|
+
*/
|
|
63
|
+
getStreamMetrics(progressToken: string | number): ProgressStreamMetrics | null;
|
|
64
|
+
/**
|
|
65
|
+
* Get metrics for all active streams
|
|
66
|
+
*/
|
|
67
|
+
getAllMetrics(): ProgressStreamMetrics[];
|
|
68
|
+
/**
|
|
69
|
+
* Get aggregated metrics by user
|
|
70
|
+
*/
|
|
71
|
+
getUserMetrics(userId: string): {
|
|
72
|
+
activeStreams: number;
|
|
73
|
+
totalMessages: number;
|
|
74
|
+
totalBytes: number;
|
|
75
|
+
oldestStreamAge: number;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Check health of all active streams
|
|
79
|
+
*/
|
|
80
|
+
checkHealth(): {
|
|
81
|
+
healthy: boolean;
|
|
82
|
+
issues: string[];
|
|
83
|
+
warnings: string[];
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Detailed progress stream metrics
|
|
88
|
+
*/
|
|
89
|
+
export interface ProgressStreamMetrics {
|
|
90
|
+
userId: string;
|
|
91
|
+
progressToken: string | number;
|
|
92
|
+
startTime: number;
|
|
93
|
+
lastUpdate: number;
|
|
94
|
+
duration: number;
|
|
95
|
+
messageCount: number;
|
|
96
|
+
bytesTransferred: number;
|
|
97
|
+
averageMessageSize: number;
|
|
98
|
+
messagesPerSecond: number;
|
|
99
|
+
}
|
|
100
|
+
export {};
|
|
101
|
+
//# sourceMappingURL=progress-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress-manager.d.ts","sourceRoot":"","sources":["../../src/wrapper/progress-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD;;GAEG;AACH,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,YAAY,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAE5E;;GAEG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAM1B;;OAEG;IACH,cAAc,CACZ,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GAAG,MAAM,EAC9B,QAAQ,EAAE,gBAAgB,GACzB,IAAI;IAkBP;;OAEG;IACH,mBAAmB,CAAC,YAAY,EAAE,oBAAoB,GAAG,OAAO;IAkEhE;;OAEG;IACH,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAkBtD;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,EAAE;IAYhD;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAiB3B;;OAEG;IACH,QAAQ,IAAI;QACV,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;KACnB;IAgBD;;OAEG;IACH,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,qBAAqB,GAAG,IAAI;IAuB9E;;OAEG;IACH,aAAa,IAAI,qBAAqB,EAAE;IAaxC;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;QAC9B,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,CAAC;QACnB,eAAe,EAAE,MAAM,CAAC;KACzB;IAyBD;;OAEG;IACH,WAAW,IAAI;QACb,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,EAAE,CAAC;QACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB;CAyCF;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;CAC3B"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
class ProgressManager {
|
|
2
|
+
streams;
|
|
3
|
+
callbacks;
|
|
4
|
+
logger;
|
|
5
|
+
constructor(logger) {
|
|
6
|
+
this.streams = /* @__PURE__ */ new Map();
|
|
7
|
+
this.callbacks = /* @__PURE__ */ new Map();
|
|
8
|
+
this.logger = logger;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Register a progress stream for a user
|
|
12
|
+
*/
|
|
13
|
+
registerStream(userId, progressToken, callback) {
|
|
14
|
+
const stream = {
|
|
15
|
+
userId,
|
|
16
|
+
progressToken,
|
|
17
|
+
startTime: Date.now(),
|
|
18
|
+
lastUpdate: Date.now(),
|
|
19
|
+
messageCount: 0
|
|
20
|
+
};
|
|
21
|
+
this.streams.set(progressToken, stream);
|
|
22
|
+
this.callbacks.set(progressToken, callback);
|
|
23
|
+
this.logger.debug("Progress stream registered", {
|
|
24
|
+
userId,
|
|
25
|
+
progressToken
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Forward a progress notification to the correct client
|
|
30
|
+
*/
|
|
31
|
+
forwardNotification(notification) {
|
|
32
|
+
const { progressToken, progress, total, message } = notification;
|
|
33
|
+
const stream = this.streams.get(progressToken);
|
|
34
|
+
const callback = this.callbacks.get(progressToken);
|
|
35
|
+
if (!stream || !callback) {
|
|
36
|
+
this.logger.warn("Progress notification for unknown token", {
|
|
37
|
+
progressToken,
|
|
38
|
+
hasStream: !!stream,
|
|
39
|
+
hasCallback: !!callback
|
|
40
|
+
});
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
stream.lastUpdate = Date.now();
|
|
44
|
+
stream.messageCount++;
|
|
45
|
+
if (message) {
|
|
46
|
+
const bytes = Buffer.byteLength(message, "utf8");
|
|
47
|
+
stream.bytesTransferred = (stream.bytesTransferred || 0) + bytes;
|
|
48
|
+
}
|
|
49
|
+
if (progress !== void 0 && total !== void 0) {
|
|
50
|
+
const percentage = progress / total * 100;
|
|
51
|
+
if ([25, 50, 75, 100].includes(Math.floor(percentage))) {
|
|
52
|
+
this.logger.info("Progress milestone", {
|
|
53
|
+
userId: stream.userId,
|
|
54
|
+
progressToken,
|
|
55
|
+
percentage: Math.floor(percentage),
|
|
56
|
+
progress,
|
|
57
|
+
total
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
callback(notification);
|
|
63
|
+
this.logger.debug("Progress notification forwarded", {
|
|
64
|
+
userId: stream.userId,
|
|
65
|
+
progressToken,
|
|
66
|
+
messageCount: stream.messageCount,
|
|
67
|
+
progress,
|
|
68
|
+
total,
|
|
69
|
+
messageLength: message?.length || 0
|
|
70
|
+
});
|
|
71
|
+
return true;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
this.logger.error("Error forwarding progress notification", error, {
|
|
74
|
+
userId: stream.userId,
|
|
75
|
+
progressToken,
|
|
76
|
+
messageCount: stream.messageCount
|
|
77
|
+
});
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Unregister a progress stream
|
|
83
|
+
*/
|
|
84
|
+
unregisterStream(progressToken) {
|
|
85
|
+
const stream = this.streams.get(progressToken);
|
|
86
|
+
if (stream) {
|
|
87
|
+
const duration = Date.now() - stream.startTime;
|
|
88
|
+
this.logger.debug("Progress stream unregistered", {
|
|
89
|
+
userId: stream.userId,
|
|
90
|
+
progressToken,
|
|
91
|
+
duration,
|
|
92
|
+
messageCount: stream.messageCount
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
this.streams.delete(progressToken);
|
|
96
|
+
this.callbacks.delete(progressToken);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get active streams for a user
|
|
100
|
+
*/
|
|
101
|
+
getUserStreams(userId) {
|
|
102
|
+
const streams = [];
|
|
103
|
+
for (const stream of this.streams.values()) {
|
|
104
|
+
if (stream.userId === userId) {
|
|
105
|
+
streams.push(stream);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return streams;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Clean up stale streams (no updates for 5 minutes)
|
|
112
|
+
*/
|
|
113
|
+
cleanupStaleStreams() {
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
const staleThreshold = 5 * 60 * 1e3;
|
|
116
|
+
const toRemove = [];
|
|
117
|
+
for (const [token, stream] of this.streams.entries()) {
|
|
118
|
+
if (now - stream.lastUpdate > staleThreshold) {
|
|
119
|
+
toRemove.push(token);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const token of toRemove) {
|
|
123
|
+
this.unregisterStream(token);
|
|
124
|
+
this.logger.warn("Cleaned up stale progress stream", { progressToken: token });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get statistics about active streams
|
|
129
|
+
*/
|
|
130
|
+
getStats() {
|
|
131
|
+
const userIds = /* @__PURE__ */ new Set();
|
|
132
|
+
let totalMessages = 0;
|
|
133
|
+
for (const stream of this.streams.values()) {
|
|
134
|
+
userIds.add(stream.userId);
|
|
135
|
+
totalMessages += stream.messageCount;
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
activeStreams: this.streams.size,
|
|
139
|
+
totalMessages,
|
|
140
|
+
userCount: userIds.size
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Get detailed metrics for a specific stream
|
|
145
|
+
*/
|
|
146
|
+
getStreamMetrics(progressToken) {
|
|
147
|
+
const stream = this.streams.get(progressToken);
|
|
148
|
+
if (!stream) return null;
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const duration = now - stream.startTime;
|
|
151
|
+
const messagesPerSecond = duration > 0 ? stream.messageCount / (duration / 1e3) : 0;
|
|
152
|
+
return {
|
|
153
|
+
userId: stream.userId,
|
|
154
|
+
progressToken: stream.progressToken,
|
|
155
|
+
startTime: stream.startTime,
|
|
156
|
+
lastUpdate: stream.lastUpdate,
|
|
157
|
+
duration,
|
|
158
|
+
messageCount: stream.messageCount,
|
|
159
|
+
bytesTransferred: stream.bytesTransferred || 0,
|
|
160
|
+
averageMessageSize: stream.bytesTransferred && stream.messageCount > 0 ? stream.bytesTransferred / stream.messageCount : 0,
|
|
161
|
+
messagesPerSecond
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get metrics for all active streams
|
|
166
|
+
*/
|
|
167
|
+
getAllMetrics() {
|
|
168
|
+
const metrics = [];
|
|
169
|
+
for (const token of this.streams.keys()) {
|
|
170
|
+
const metric = this.getStreamMetrics(token);
|
|
171
|
+
if (metric) {
|
|
172
|
+
metrics.push(metric);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return metrics;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get aggregated metrics by user
|
|
179
|
+
*/
|
|
180
|
+
getUserMetrics(userId) {
|
|
181
|
+
const streams = this.getUserStreams(userId);
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
let totalMessages = 0;
|
|
184
|
+
let totalBytes = 0;
|
|
185
|
+
let oldestStreamAge = 0;
|
|
186
|
+
for (const stream of streams) {
|
|
187
|
+
totalMessages += stream.messageCount;
|
|
188
|
+
totalBytes += stream.bytesTransferred || 0;
|
|
189
|
+
const age = now - stream.startTime;
|
|
190
|
+
if (age > oldestStreamAge) {
|
|
191
|
+
oldestStreamAge = age;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
activeStreams: streams.length,
|
|
196
|
+
totalMessages,
|
|
197
|
+
totalBytes,
|
|
198
|
+
oldestStreamAge
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check health of all active streams
|
|
203
|
+
*/
|
|
204
|
+
checkHealth() {
|
|
205
|
+
const issues = [];
|
|
206
|
+
const warnings = [];
|
|
207
|
+
const now = Date.now();
|
|
208
|
+
for (const [token, stream] of this.streams.entries()) {
|
|
209
|
+
const age = now - stream.startTime;
|
|
210
|
+
const timeSinceUpdate = now - stream.lastUpdate;
|
|
211
|
+
if (age > 36e5) {
|
|
212
|
+
warnings.push(
|
|
213
|
+
`Stream ${token} for user ${stream.userId} is very old (${Math.floor(age / 6e4)} minutes)`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (timeSinceUpdate > 3e5) {
|
|
217
|
+
issues.push(
|
|
218
|
+
`Stream ${token} for user ${stream.userId} is stale (${Math.floor(timeSinceUpdate / 6e4)} minutes since update)`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const duration = age / 1e3;
|
|
222
|
+
if (duration > 0) {
|
|
223
|
+
const rate = stream.messageCount / duration;
|
|
224
|
+
if (rate > 100) {
|
|
225
|
+
warnings.push(
|
|
226
|
+
`Stream ${token} has high message rate (${rate.toFixed(1)}/sec)`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
healthy: issues.length === 0,
|
|
233
|
+
issues,
|
|
234
|
+
warnings
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export {
|
|
239
|
+
ProgressManager
|
|
240
|
+
};
|
|
241
|
+
//# sourceMappingURL=progress-manager.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/wrapper/progress-manager.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Progress notification manager for multi-tenant progress streaming\n * \n * Manages routing of progress notifications from wrapped MCP servers\n * to the correct clients in multi-tenant deployments.\n */\n\nimport { Logger } from '../utils/logger.js';\nimport type { ProgressNotification } from '../types.js';\n\n/**\n * Progress stream metadata\n */\ninterface ProgressStream {\n userId: string;\n progressToken: string | number;\n startTime: number;\n lastUpdate: number;\n messageCount: number;\n bytesTransferred?: number;\n}\n\n/**\n * Progress notification callback\n */\nexport type ProgressCallback = (notification: ProgressNotification) => void;\n\n/**\n * Manages progress notification routing for multi-tenant deployments\n */\nexport class ProgressManager {\n private streams: Map<string | number, ProgressStream>;\n private callbacks: Map<string | number, ProgressCallback>;\n private logger: Logger;\n \n constructor(logger: Logger) {\n this.streams = new Map();\n this.callbacks = new Map();\n this.logger = logger;\n }\n \n /**\n * Register a progress stream for a user\n */\n registerStream(\n userId: string,\n progressToken: string | number,\n callback: ProgressCallback\n ): void {\n const stream: ProgressStream = {\n userId,\n progressToken,\n startTime: Date.now(),\n lastUpdate: Date.now(),\n messageCount: 0\n };\n \n this.streams.set(progressToken, stream);\n this.callbacks.set(progressToken, callback);\n \n this.logger.debug('Progress stream registered', {\n userId,\n progressToken\n });\n }\n \n /**\n * Forward a progress notification to the correct client\n */\n forwardNotification(notification: ProgressNotification): boolean {\n const { progressToken, progress, total, message } = notification;\n \n const stream = this.streams.get(progressToken);\n const callback = this.callbacks.get(progressToken);\n \n if (!stream || !callback) {\n this.logger.warn('Progress notification for unknown token', {\n progressToken,\n hasStream: !!stream,\n hasCallback: !!callback\n });\n return false;\n }\n \n // Update stream metadata\n stream.lastUpdate = Date.now();\n stream.messageCount++;\n \n // Track bytes if message provided\n if (message) {\n const bytes = Buffer.byteLength(message, 'utf8');\n stream.bytesTransferred = (stream.bytesTransferred || 0) + bytes;\n }\n \n // Log progress milestones\n if (progress !== undefined && total !== undefined) {\n const percentage = (progress / total) * 100;\n \n // Log at 25%, 50%, 75%, 100%\n if ([25, 50, 75, 100].includes(Math.floor(percentage))) {\n this.logger.info('Progress milestone', {\n userId: stream.userId,\n progressToken,\n percentage: Math.floor(percentage),\n progress,\n total\n });\n }\n }\n \n // Forward to client\n try {\n callback(notification);\n \n // Debug logging for every notification\n this.logger.debug('Progress notification forwarded', {\n userId: stream.userId,\n progressToken,\n messageCount: stream.messageCount,\n progress,\n total,\n messageLength: message?.length || 0\n });\n \n return true;\n } catch (error) {\n this.logger.error('Error forwarding progress notification', error as Error, {\n userId: stream.userId,\n progressToken,\n messageCount: stream.messageCount\n });\n return false;\n }\n }\n \n /**\n * Unregister a progress stream\n */\n unregisterStream(progressToken: string | number): void {\n const stream = this.streams.get(progressToken);\n \n if (stream) {\n const duration = Date.now() - stream.startTime;\n \n this.logger.debug('Progress stream unregistered', {\n userId: stream.userId,\n progressToken,\n duration,\n messageCount: stream.messageCount\n });\n }\n \n this.streams.delete(progressToken);\n this.callbacks.delete(progressToken);\n }\n \n /**\n * Get active streams for a user\n */\n getUserStreams(userId: string): ProgressStream[] {\n const streams: ProgressStream[] = [];\n \n for (const stream of this.streams.values()) {\n if (stream.userId === userId) {\n streams.push(stream);\n }\n }\n \n return streams;\n }\n \n /**\n * Clean up stale streams (no updates for 5 minutes)\n */\n cleanupStaleStreams(): void {\n const now = Date.now();\n const staleThreshold = 5 * 60 * 1000; // 5 minutes\n const toRemove: (string | number)[] = [];\n \n for (const [token, stream] of this.streams.entries()) {\n if (now - stream.lastUpdate > staleThreshold) {\n toRemove.push(token);\n }\n }\n \n for (const token of toRemove) {\n this.unregisterStream(token);\n this.logger.warn('Cleaned up stale progress stream', { progressToken: token });\n }\n }\n \n /**\n * Get statistics about active streams\n */\n getStats(): {\n activeStreams: number;\n totalMessages: number;\n userCount: number;\n } {\n const userIds = new Set<string>();\n let totalMessages = 0;\n \n for (const stream of this.streams.values()) {\n userIds.add(stream.userId);\n totalMessages += stream.messageCount;\n }\n \n return {\n activeStreams: this.streams.size,\n totalMessages,\n userCount: userIds.size\n };\n }\n \n /**\n * Get detailed metrics for a specific stream\n */\n getStreamMetrics(progressToken: string | number): ProgressStreamMetrics | null {\n const stream = this.streams.get(progressToken);\n if (!stream) return null;\n \n const now = Date.now();\n const duration = now - stream.startTime;\n const messagesPerSecond = duration > 0 ? stream.messageCount / (duration / 1000) : 0;\n \n return {\n userId: stream.userId,\n progressToken: stream.progressToken,\n startTime: stream.startTime,\n lastUpdate: stream.lastUpdate,\n duration,\n messageCount: stream.messageCount,\n bytesTransferred: stream.bytesTransferred || 0,\n averageMessageSize: stream.bytesTransferred && stream.messageCount > 0\n ? stream.bytesTransferred / stream.messageCount\n : 0,\n messagesPerSecond\n };\n }\n \n /**\n * Get metrics for all active streams\n */\n getAllMetrics(): ProgressStreamMetrics[] {\n const metrics: ProgressStreamMetrics[] = [];\n \n for (const token of this.streams.keys()) {\n const metric = this.getStreamMetrics(token);\n if (metric) {\n metrics.push(metric);\n }\n }\n \n return metrics;\n }\n \n /**\n * Get aggregated metrics by user\n */\n getUserMetrics(userId: string): {\n activeStreams: number;\n totalMessages: number;\n totalBytes: number;\n oldestStreamAge: number;\n } {\n const streams = this.getUserStreams(userId);\n const now = Date.now();\n \n let totalMessages = 0;\n let totalBytes = 0;\n let oldestStreamAge = 0;\n \n for (const stream of streams) {\n totalMessages += stream.messageCount;\n totalBytes += stream.bytesTransferred || 0;\n const age = now - stream.startTime;\n if (age > oldestStreamAge) {\n oldestStreamAge = age;\n }\n }\n \n return {\n activeStreams: streams.length,\n totalMessages,\n totalBytes,\n oldestStreamAge\n };\n }\n \n /**\n * Check health of all active streams\n */\n checkHealth(): {\n healthy: boolean;\n issues: string[];\n warnings: string[];\n } {\n const issues: string[] = [];\n const warnings: string[] = [];\n const now = Date.now();\n \n for (const [token, stream] of this.streams.entries()) {\n const age = now - stream.startTime;\n const timeSinceUpdate = now - stream.lastUpdate;\n \n // Check for very old streams (>1 hour)\n if (age > 3600000) {\n warnings.push(\n `Stream ${token} for user ${stream.userId} is very old (${Math.floor(age / 60000)} minutes)`\n );\n }\n \n // Check for stale streams (>5 minutes since update)\n if (timeSinceUpdate > 300000) {\n issues.push(\n `Stream ${token} for user ${stream.userId} is stale (${Math.floor(timeSinceUpdate / 60000)} minutes since update)`\n );\n }\n \n // Check for high message rate (>100/sec)\n const duration = age / 1000;\n if (duration > 0) {\n const rate = stream.messageCount / duration;\n if (rate > 100) {\n warnings.push(\n `Stream ${token} has high message rate (${rate.toFixed(1)}/sec)`\n );\n }\n }\n }\n \n return {\n healthy: issues.length === 0,\n issues,\n warnings\n };\n }\n}\n\n/**\n * Detailed progress stream metrics\n */\nexport interface ProgressStreamMetrics {\n userId: string;\n progressToken: string | number;\n startTime: number;\n lastUpdate: number;\n duration: number;\n messageCount: number;\n bytesTransferred: number;\n averageMessageSize: number;\n messagesPerSecond: number;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -33,6 +33,9 @@ export declare class AuthenticatedServerWrapper {
|
|
|
33
33
|
private serverPool;
|
|
34
34
|
private isRunning;
|
|
35
35
|
private cleanupTimer?;
|
|
36
|
+
private progressContexts;
|
|
37
|
+
private progressManager;
|
|
38
|
+
private cleanupInterval?;
|
|
36
39
|
constructor(config: ServerWrapperConfig);
|
|
37
40
|
/**
|
|
38
41
|
* Validate wrapper configuration
|
|
@@ -50,6 +53,18 @@ export declare class AuthenticatedServerWrapper {
|
|
|
50
53
|
* Stop the wrapped server
|
|
51
54
|
*/
|
|
52
55
|
stop(): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Store progress context for a user
|
|
58
|
+
*/
|
|
59
|
+
private storeProgressContext;
|
|
60
|
+
/**
|
|
61
|
+
* Get progress context for a user
|
|
62
|
+
*/
|
|
63
|
+
private getProgressContext;
|
|
64
|
+
/**
|
|
65
|
+
* Clear progress context for a user
|
|
66
|
+
*/
|
|
67
|
+
private clearProgressContext;
|
|
53
68
|
/**
|
|
54
69
|
* Handle SSE request with direct Express req/res access
|
|
55
70
|
* This allows us to use StreamableHTTPServerTransport properly
|
|
@@ -101,4 +116,24 @@ export declare class AuthenticatedServerWrapper {
|
|
|
101
116
|
*/
|
|
102
117
|
isServerRunning(): boolean;
|
|
103
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Convenience function to create and configure an authenticated server wrapper
|
|
121
|
+
*
|
|
122
|
+
* @param config - Server wrapper configuration
|
|
123
|
+
* @returns Configured AuthenticatedServerWrapper instance
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* const wrapped = wrapServer({
|
|
128
|
+
* serverFactory: (accessToken, userId) => createMyServer(accessToken, userId),
|
|
129
|
+
* authProvider: new JWTAuthProvider({ jwtSecret: process.env.JWT_SECRET }),
|
|
130
|
+
* tokenResolver: new APITokenResolver({ ... }),
|
|
131
|
+
* resourceType: 'myapi',
|
|
132
|
+
* transport: { type: 'sse', port: 3000 }
|
|
133
|
+
* });
|
|
134
|
+
*
|
|
135
|
+
* await wrapped.start();
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export declare function wrapServer(config: ServerWrapperConfig): AuthenticatedServerWrapper;
|
|
104
139
|
//# 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;AA6BtF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAA8B;IAChD,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;IA6BvC;;OAEG;IACH,OAAO,CAAC,cAAc;IA+BtB;;OAEG;IACH,OAAO,CAAC,eAAe;IAwBvB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkD3B;;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;IAgDrC;;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"}
|
|
@@ -13,17 +13,25 @@ import {
|
|
|
13
13
|
validateAccessToken,
|
|
14
14
|
validateTransportConfig
|
|
15
15
|
} from "../utils/validation.js";
|
|
16
|
+
import { ProgressManager } from "./progress-manager.js";
|
|
16
17
|
class AuthenticatedServerWrapper {
|
|
17
18
|
config;
|
|
18
19
|
logger;
|
|
19
20
|
serverPool;
|
|
20
21
|
isRunning = false;
|
|
21
22
|
cleanupTimer;
|
|
23
|
+
progressContexts = /* @__PURE__ */ new Map();
|
|
24
|
+
progressManager;
|
|
25
|
+
cleanupInterval;
|
|
22
26
|
constructor(config) {
|
|
23
27
|
this.validateConfig(config);
|
|
24
28
|
this.config = this.normalizeConfig(config);
|
|
25
29
|
this.logger = createLogger(this.config.middleware.logging);
|
|
26
30
|
this.serverPool = /* @__PURE__ */ new Map();
|
|
31
|
+
this.progressManager = new ProgressManager(this.logger);
|
|
32
|
+
this.cleanupInterval = setInterval(() => {
|
|
33
|
+
this.progressManager.cleanupStaleStreams();
|
|
34
|
+
}, 6e4);
|
|
27
35
|
this.logger.info("AuthenticatedServerWrapper created", {
|
|
28
36
|
name: this.config.name,
|
|
29
37
|
resourceType: this.config.resourceType,
|
|
@@ -141,6 +149,10 @@ class AuthenticatedServerWrapper {
|
|
|
141
149
|
clearTimeout(this.cleanupTimer);
|
|
142
150
|
this.cleanupTimer = void 0;
|
|
143
151
|
}
|
|
152
|
+
if (this.cleanupInterval) {
|
|
153
|
+
clearInterval(this.cleanupInterval);
|
|
154
|
+
this.cleanupInterval = void 0;
|
|
155
|
+
}
|
|
144
156
|
if (this.config.instanceMode === "pooled") {
|
|
145
157
|
for (const [userId, instance] of this.serverPool.entries()) {
|
|
146
158
|
try {
|
|
@@ -165,12 +177,34 @@ class AuthenticatedServerWrapper {
|
|
|
165
177
|
this.isRunning = false;
|
|
166
178
|
this.logger.info("Server wrapper stopped");
|
|
167
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Store progress context for a user
|
|
182
|
+
*/
|
|
183
|
+
storeProgressContext(userId, progressToken) {
|
|
184
|
+
this.progressContexts.set(userId, progressToken);
|
|
185
|
+
this.logger.debug("Stored progress context", { userId, progressToken });
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Get progress context for a user
|
|
189
|
+
*/
|
|
190
|
+
getProgressContext(userId) {
|
|
191
|
+
return this.progressContexts.get(userId);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Clear progress context for a user
|
|
195
|
+
*/
|
|
196
|
+
clearProgressContext(userId) {
|
|
197
|
+
this.progressContexts.delete(userId);
|
|
198
|
+
this.logger.debug("Cleared progress context", { userId });
|
|
199
|
+
}
|
|
168
200
|
/**
|
|
169
201
|
* Handle SSE request with direct Express req/res access
|
|
170
202
|
* This allows us to use StreamableHTTPServerTransport properly
|
|
171
203
|
*/
|
|
172
204
|
async handleSSERequest(req, res, context) {
|
|
173
205
|
const requestLogger = this.logger.child({ requestId: context.requestId });
|
|
206
|
+
let userId;
|
|
207
|
+
const progressToken = req.body._meta?.progressToken;
|
|
174
208
|
try {
|
|
175
209
|
requestLogger.debug("Authenticating request");
|
|
176
210
|
const authResult = await this.config.authProvider.authenticate(context);
|
|
@@ -178,7 +212,7 @@ class AuthenticatedServerWrapper {
|
|
|
178
212
|
requestLogger.warn("Authentication failed", { error: authResult.error });
|
|
179
213
|
throw new AuthenticationError(authResult.error || "Authentication failed");
|
|
180
214
|
}
|
|
181
|
-
|
|
215
|
+
userId = validateUserId(authResult.userId);
|
|
182
216
|
requestLogger.debug("Authentication successful", { userId });
|
|
183
217
|
let accessToken;
|
|
184
218
|
if (this.config.tokenResolver) {
|
|
@@ -197,8 +231,41 @@ class AuthenticatedServerWrapper {
|
|
|
197
231
|
accessToken = "";
|
|
198
232
|
requestLogger.debug("Static mode - no token resolution", { userId, mode: "static" });
|
|
199
233
|
}
|
|
234
|
+
if (progressToken) {
|
|
235
|
+
this.storeProgressContext(userId, progressToken);
|
|
236
|
+
this.progressManager.registerStream(userId, progressToken, (notification) => {
|
|
237
|
+
requestLogger.debug("Would forward progress notification", {
|
|
238
|
+
userId,
|
|
239
|
+
progressToken: notification.progressToken,
|
|
240
|
+
progress: notification.progress,
|
|
241
|
+
total: notification.total
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
requestLogger.debug("Progress token extracted and stream registered", { userId, progressToken });
|
|
245
|
+
}
|
|
200
246
|
const server = await this.getServerInstance(userId, accessToken);
|
|
201
|
-
|
|
247
|
+
if (progressToken) {
|
|
248
|
+
const originalNotification = server.notification?.bind(server);
|
|
249
|
+
if (originalNotification) {
|
|
250
|
+
server.notification = (params) => {
|
|
251
|
+
if (params.method === "notifications/progress") {
|
|
252
|
+
const handled = this.progressManager.forwardNotification(
|
|
253
|
+
params.params
|
|
254
|
+
);
|
|
255
|
+
if (handled) {
|
|
256
|
+
requestLogger.debug("Progress notification intercepted and forwarded", {
|
|
257
|
+
progressToken: params.params.progressToken
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (originalNotification) {
|
|
263
|
+
originalNotification(params);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
requestLogger.debug("Forwarding request to MCP server", { userId, hasProgressToken: !!progressToken });
|
|
202
269
|
const transport = new StreamableHTTPServerTransport({
|
|
203
270
|
sessionIdGenerator: void 0
|
|
204
271
|
// Stateless mode
|
|
@@ -207,10 +274,21 @@ class AuthenticatedServerWrapper {
|
|
|
207
274
|
await transport.handleRequest(req, res, req.body);
|
|
208
275
|
requestLogger.info("Request handled successfully", {
|
|
209
276
|
userId,
|
|
210
|
-
resourceType: this.config.resourceType
|
|
277
|
+
resourceType: this.config.resourceType,
|
|
278
|
+
hadProgressToken: !!progressToken
|
|
211
279
|
});
|
|
280
|
+
if (progressToken) {
|
|
281
|
+
this.progressManager.unregisterStream(progressToken);
|
|
282
|
+
this.clearProgressContext(userId);
|
|
283
|
+
}
|
|
212
284
|
} catch (error) {
|
|
213
285
|
requestLogger.error("SSE request handling failed", error);
|
|
286
|
+
if (progressToken) {
|
|
287
|
+
this.progressManager.unregisterStream(progressToken);
|
|
288
|
+
if (userId) {
|
|
289
|
+
this.clearProgressContext(userId);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
214
292
|
throw error;
|
|
215
293
|
}
|
|
216
294
|
}
|
|
@@ -415,6 +493,70 @@ class AuthenticatedServerWrapper {
|
|
|
415
493
|
}
|
|
416
494
|
}
|
|
417
495
|
});
|
|
496
|
+
app.get(`${basePath}/progress/stats`, async (req, res) => {
|
|
497
|
+
try {
|
|
498
|
+
const context = {
|
|
499
|
+
headers: req.headers,
|
|
500
|
+
transport: "sse",
|
|
501
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
502
|
+
requestId: req.headers["x-request-id"]
|
|
503
|
+
};
|
|
504
|
+
const authResult = await this.config.authProvider.authenticate(context);
|
|
505
|
+
if (!authResult.authenticated || !authResult.userId) {
|
|
506
|
+
return res.status(401).json({
|
|
507
|
+
error: "Authentication required",
|
|
508
|
+
code: "AUTHENTICATION_ERROR"
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
const userId = authResult.userId;
|
|
512
|
+
const userMetrics = this.progressManager.getUserMetrics(userId);
|
|
513
|
+
const globalStats = this.progressManager.getStats();
|
|
514
|
+
res.json({
|
|
515
|
+
user: {
|
|
516
|
+
userId,
|
|
517
|
+
...userMetrics
|
|
518
|
+
},
|
|
519
|
+
global: globalStats,
|
|
520
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
521
|
+
});
|
|
522
|
+
} catch (error) {
|
|
523
|
+
this.logger.error("Error fetching progress stats", error);
|
|
524
|
+
res.status(500).json({
|
|
525
|
+
error: "Internal server error",
|
|
526
|
+
code: "INTERNAL_ERROR"
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
app.get(`${basePath}/progress/metrics`, async (req, res) => {
|
|
531
|
+
try {
|
|
532
|
+
const context = {
|
|
533
|
+
headers: req.headers,
|
|
534
|
+
transport: "sse",
|
|
535
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
536
|
+
requestId: req.headers["x-request-id"]
|
|
537
|
+
};
|
|
538
|
+
const authResult = await this.config.authProvider.authenticate(context);
|
|
539
|
+
if (!authResult.authenticated) {
|
|
540
|
+
return res.status(401).json({
|
|
541
|
+
error: "Authentication required",
|
|
542
|
+
code: "AUTHENTICATION_ERROR"
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
const allMetrics = this.progressManager.getAllMetrics();
|
|
546
|
+
const health = this.progressManager.checkHealth();
|
|
547
|
+
res.json({
|
|
548
|
+
metrics: allMetrics,
|
|
549
|
+
health,
|
|
550
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
551
|
+
});
|
|
552
|
+
} catch (error) {
|
|
553
|
+
this.logger.error("Error fetching progress metrics", error);
|
|
554
|
+
res.status(500).json({
|
|
555
|
+
error: "Internal server error",
|
|
556
|
+
code: "INTERNAL_ERROR"
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
});
|
|
418
560
|
app.get(`${basePath}/health`, (req, res) => {
|
|
419
561
|
res.json({
|
|
420
562
|
status: "healthy",
|
|
@@ -472,7 +614,11 @@ class AuthenticatedServerWrapper {
|
|
|
472
614
|
return this.isRunning;
|
|
473
615
|
}
|
|
474
616
|
}
|
|
617
|
+
function wrapServer(config) {
|
|
618
|
+
return new AuthenticatedServerWrapper(config);
|
|
619
|
+
}
|
|
475
620
|
export {
|
|
476
|
-
AuthenticatedServerWrapper
|
|
621
|
+
AuthenticatedServerWrapper,
|
|
622
|
+
wrapServer
|
|
477
623
|
};
|
|
478
624
|
//# 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} from '../utils/validation.js';\nimport { ProgressManager } from './progress-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 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 (only used in pooled mode)\n this.serverPool = new Map();\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 // 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 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 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 * 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 // 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
|
}
|