@frontmcp/sdk 0.5.0 → 0.6.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 +3 -3
- package/package.json +8 -19
- package/src/adapter/adapter.instance.js +5 -0
- package/src/adapter/adapter.instance.js.map +1 -1
- package/src/auth/authorization/authorization.class.d.ts +1 -4
- package/src/auth/authorization/authorization.class.js +6 -13
- package/src/auth/authorization/authorization.class.js.map +1 -1
- package/src/auth/flows/session.verify.flow.d.ts +1 -0
- package/src/auth/flows/session.verify.flow.js +11 -1
- package/src/auth/flows/session.verify.flow.js.map +1 -1
- package/src/auth/flows/well-known.jwks.flow.js +2 -2
- package/src/auth/flows/well-known.jwks.flow.js.map +1 -1
- package/src/auth/jwks/dev-key-persistence.d.ts +63 -0
- package/src/auth/jwks/dev-key-persistence.js +219 -0
- package/src/auth/jwks/dev-key-persistence.js.map +1 -0
- package/src/auth/jwks/index.d.ts +1 -0
- package/src/auth/jwks/index.js +1 -0
- package/src/auth/jwks/index.js.map +1 -1
- package/src/auth/jwks/jwks.service.d.ts +7 -4
- package/src/auth/jwks/jwks.service.js +81 -12
- package/src/auth/jwks/jwks.service.js.map +1 -1
- package/src/auth/jwks/jwks.types.d.ts +7 -0
- package/src/auth/jwks/jwks.types.js.map +1 -1
- package/src/auth/machine-id.d.ts +5 -0
- package/src/auth/machine-id.js +32 -0
- package/src/auth/machine-id.js.map +1 -0
- package/src/auth/session/index.d.ts +1 -0
- package/src/auth/session/index.js +3 -1
- package/src/auth/session/index.js.map +1 -1
- package/src/auth/session/record/session.base.js +5 -3
- package/src/auth/session/record/session.base.js.map +1 -1
- package/src/auth/session/record/session.stateless.d.ts +2 -2
- package/src/auth/session/record/session.stateless.js +5 -3
- package/src/auth/session/record/session.stateless.js.map +1 -1
- package/src/auth/session/redis-session.store.d.ts +64 -0
- package/src/auth/session/redis-session.store.js +204 -0
- package/src/auth/session/redis-session.store.js.map +1 -0
- package/src/auth/session/session.service.d.ts +0 -2
- package/src/auth/session/session.service.js +1 -7
- package/src/auth/session/session.service.js.map +1 -1
- package/src/auth/session/transport-session.manager.js +3 -5
- package/src/auth/session/transport-session.manager.js.map +1 -1
- package/src/auth/session/transport-session.types.d.ts +4 -0
- package/src/auth/session/transport-session.types.js +4 -3
- package/src/auth/session/transport-session.types.js.map +1 -1
- package/src/auth/session/utils/session-id.utils.d.ts +12 -1
- package/src/auth/session/utils/session-id.utils.js +48 -9
- package/src/auth/session/utils/session-id.utils.js.map +1 -1
- package/src/auth/ui/base-layout.d.ts +0 -8
- package/src/auth/ui/base-layout.js +1 -14
- package/src/auth/ui/base-layout.js.map +1 -1
- package/src/auth/ui/index.d.ts +3 -4
- package/src/auth/ui/index.js +10 -11
- package/src/auth/ui/index.js.map +1 -1
- package/src/auth/ui/{htmx-templates.d.ts → templates.d.ts} +5 -6
- package/src/auth/ui/{htmx-templates.js → templates.js} +8 -15
- package/src/auth/ui/templates.js.map +1 -0
- package/src/common/decorators/decorator-utils.js.map +1 -1
- package/src/common/decorators/front-mcp.decorator.js +28 -2
- package/src/common/decorators/front-mcp.decorator.js.map +1 -1
- package/src/common/index.d.ts +0 -1
- package/src/common/index.js +0 -1
- package/src/common/index.js.map +1 -1
- package/src/common/interfaces/adapter.interface.d.ts +6 -0
- package/src/common/interfaces/adapter.interface.js.map +1 -1
- package/src/common/interfaces/execution-context.interface.d.ts +52 -3
- package/src/common/interfaces/execution-context.interface.js +88 -3
- package/src/common/interfaces/execution-context.interface.js.map +1 -1
- package/src/common/interfaces/flow.interface.d.ts +13 -0
- package/src/common/interfaces/flow.interface.js +24 -0
- package/src/common/interfaces/flow.interface.js.map +1 -1
- package/src/common/interfaces/server.interface.d.ts +9 -0
- package/src/common/interfaces/server.interface.js.map +1 -1
- package/src/common/metadata/app.metadata.d.ts +108 -0
- package/src/common/metadata/front-mcp.metadata.d.ts +659 -2
- package/src/common/metadata/front-mcp.metadata.js +3 -1
- package/src/common/metadata/front-mcp.metadata.js.map +1 -1
- package/src/common/metadata/provider.metadata.d.ts +14 -0
- package/src/common/metadata/provider.metadata.js +18 -2
- package/src/common/metadata/provider.metadata.js.map +1 -1
- package/src/common/metadata/tool.metadata.d.ts +33 -1
- package/src/common/metadata/tool.metadata.js.map +1 -1
- package/src/common/migrate/auth-transport.migrate.d.ts +62 -0
- package/src/common/migrate/auth-transport.migrate.js +140 -0
- package/src/common/migrate/auth-transport.migrate.js.map +1 -0
- package/src/common/migrate/index.d.ts +1 -0
- package/src/common/migrate/index.js +6 -0
- package/src/common/migrate/index.js.map +1 -0
- package/src/common/schemas/http-output.schema.d.ts +10 -2
- package/src/common/schemas/index.d.ts +1 -0
- package/src/common/schemas/index.js +1 -0
- package/src/common/schemas/index.js.map +1 -1
- package/src/common/schemas/session-header.schema.d.ts +16 -0
- package/src/common/schemas/session-header.schema.js +42 -0
- package/src/common/schemas/session-header.schema.js.map +1 -0
- package/src/common/tokens/front-mcp.tokens.js +3 -1
- package/src/common/tokens/front-mcp.tokens.js.map +1 -1
- package/src/common/types/options/auth.options.d.ts +233 -3
- package/src/common/types/options/auth.options.js +29 -40
- package/src/common/types/options/auth.options.js.map +1 -1
- package/src/common/types/options/index.d.ts +2 -0
- package/src/common/types/options/index.js +2 -0
- package/src/common/types/options/index.js.map +1 -1
- package/src/common/types/options/redis.options.d.ts +22 -0
- package/src/common/types/options/redis.options.js +45 -0
- package/src/common/types/options/redis.options.js.map +1 -0
- package/src/common/types/options/transport.options.d.ts +84 -0
- package/src/common/types/options/transport.options.js +121 -0
- package/src/common/types/options/transport.options.js.map +1 -0
- package/src/completion/flows/complete.flow.d.ts +17 -2
- package/src/context/frontmcp-context-storage.d.ts +94 -0
- package/src/context/frontmcp-context-storage.js +183 -0
- package/src/context/frontmcp-context-storage.js.map +1 -0
- package/src/context/frontmcp-context.d.ts +269 -0
- package/src/context/frontmcp-context.js +360 -0
- package/src/context/frontmcp-context.js.map +1 -0
- package/src/context/frontmcp-context.provider.d.ts +43 -0
- package/src/context/frontmcp-context.provider.js +61 -0
- package/src/context/frontmcp-context.provider.js.map +1 -0
- package/src/context/index.d.ts +34 -0
- package/src/context/index.js +64 -0
- package/src/context/index.js.map +1 -0
- package/src/context/request-context-storage.d.ts +89 -0
- package/src/context/request-context-storage.js +183 -0
- package/src/context/request-context-storage.js.map +1 -0
- package/src/context/request-context.d.ts +184 -0
- package/src/context/request-context.js +209 -0
- package/src/context/request-context.js.map +1 -0
- package/src/context/request-context.provider.d.ts +37 -0
- package/src/context/request-context.provider.js +51 -0
- package/src/context/request-context.provider.js.map +1 -0
- package/src/context/session-key.provider.d.ts +45 -0
- package/src/context/session-key.provider.js +65 -0
- package/src/context/session-key.provider.js.map +1 -0
- package/src/context/trace-context.d.ts +43 -0
- package/src/context/trace-context.js +142 -0
- package/src/context/trace-context.js.map +1 -0
- package/src/errors/index.d.ts +1 -1
- package/src/errors/index.js +3 -1
- package/src/errors/index.js.map +1 -1
- package/src/errors/mcp.error.d.ts +7 -0
- package/src/errors/mcp.error.js +11 -1
- package/src/errors/mcp.error.js.map +1 -1
- package/src/flows/flow.instance.d.ts +16 -0
- package/src/flows/flow.instance.js +166 -80
- package/src/flows/flow.instance.js.map +1 -1
- package/src/flows/flow.registry.d.ts +5 -0
- package/src/flows/flow.registry.js +45 -3
- package/src/flows/flow.registry.js.map +1 -1
- package/src/front-mcp/front-mcp.d.ts +12 -0
- package/src/front-mcp/front-mcp.js +22 -3
- package/src/front-mcp/front-mcp.js.map +1 -1
- package/src/front-mcp/front-mcp.providers.d.ts +266 -1
- package/src/front-mcp/front-mcp.providers.js +2 -1
- package/src/front-mcp/front-mcp.providers.js.map +1 -1
- package/src/front-mcp/serverless-handler.d.ts +28 -0
- package/src/front-mcp/serverless-handler.js +61 -0
- package/src/front-mcp/serverless-handler.js.map +1 -0
- package/src/hooks/hooks.utils.d.ts +1 -1
- package/src/hooks/hooks.utils.js +10 -3
- package/src/hooks/hooks.utils.js.map +1 -1
- package/src/index.d.ts +8 -4
- package/src/index.js +20 -1
- package/src/index.js.map +1 -1
- package/src/logger/instances/instance.logger.js +0 -1
- package/src/logger/instances/instance.logger.js.map +1 -1
- package/src/logging/flows/set-level.flow.d.ts +17 -2
- package/src/notification/notification.service.js +5 -1
- package/src/notification/notification.service.js.map +1 -1
- package/src/prompt/flows/get-prompt.flow.d.ts +97 -2
- package/src/prompt/flows/prompts-list.flow.d.ts +12 -1
- package/src/provider/provider.registry.d.ts +97 -5
- package/src/provider/provider.registry.js +306 -9
- package/src/provider/provider.registry.js.map +1 -1
- package/src/provider/provider.types.d.ts +21 -3
- package/src/provider/provider.types.js.map +1 -1
- package/src/resource/flows/read-resource.flow.d.ts +22 -3
- package/src/resource/flows/resource-templates-list.flow.d.ts +20 -1
- package/src/resource/flows/resources-list.flow.d.ts +20 -1
- package/src/resource/flows/subscribe-resource.flow.d.ts +17 -2
- package/src/resource/flows/unsubscribe-resource.flow.d.ts +17 -2
- package/src/scope/flows/http.request.flow.js +43 -7
- package/src/scope/flows/http.request.flow.js.map +1 -1
- package/src/scope/scope.instance.js +12 -5
- package/src/scope/scope.instance.js.map +1 -1
- package/src/server/adapters/base.host.adapter.d.ts +9 -0
- package/src/server/adapters/base.host.adapter.js.map +1 -1
- package/src/server/adapters/express.host.adapter.d.ts +12 -0
- package/src/server/adapters/express.host.adapter.js +21 -1
- package/src/server/adapters/express.host.adapter.js.map +1 -1
- package/src/server/server.instance.d.ts +3 -0
- package/src/server/server.instance.js +14 -7
- package/src/server/server.instance.js.map +1 -1
- package/src/tool/flows/call-tool.flow.d.ts +118 -13
- package/src/tool/flows/call-tool.flow.js +240 -194
- package/src/tool/flows/call-tool.flow.js.map +1 -1
- package/src/tool/flows/tools-list.flow.d.ts +25 -11
- package/src/tool/flows/tools-list.flow.js +82 -31
- package/src/tool/flows/tools-list.flow.js.map +1 -1
- package/src/tool/tool.instance.d.ts +1 -4
- package/src/transport/adapters/transport.streamable-http.adapter.js +1 -0
- package/src/transport/adapters/transport.streamable-http.adapter.js.map +1 -1
- package/src/transport/flows/handle.sse.flow.js +9 -2
- package/src/transport/flows/handle.sse.flow.js.map +1 -1
- package/src/transport/flows/handle.streamable-http.flow.js +63 -6
- package/src/transport/flows/handle.streamable-http.flow.js.map +1 -1
- package/src/transport/mcp-handlers/complete-request.handler.d.ts +27 -1
- package/src/transport/mcp-handlers/get-prompt-request.handler.d.ts +52 -1
- package/src/transport/mcp-handlers/index.d.ts +413 -7
- package/src/transport/mcp-handlers/initialize-request.handler.js +12 -2
- package/src/transport/mcp-handlers/initialize-request.handler.js.map +1 -1
- package/src/transport/mcp-handlers/list-prompts-request.handler.d.ts +27 -1
- package/src/transport/mcp-handlers/list-resource-templates-request.handler.d.ts +32 -1
- package/src/transport/mcp-handlers/list-resources-request.handler.d.ts +32 -1
- package/src/transport/mcp-handlers/list-tools-request.handler.d.ts +30 -1
- package/src/transport/mcp-handlers/logging-set-level-request.handler.d.ts +20 -0
- package/src/transport/mcp-handlers/read-resource-request.handler.d.ts +27 -1
- package/src/transport/mcp-handlers/subscribe-request.handler.d.ts +20 -0
- package/src/transport/mcp-handlers/unsubscribe-request.handler.d.ts +20 -0
- package/src/transport/transport.registry.d.ts +68 -4
- package/src/transport/transport.registry.js +313 -11
- package/src/transport/transport.registry.js.map +1 -1
- package/src/auth/ui/htmx-templates.js.map +0 -1
- package/src/common/providers/session.provider.d.ts +0 -13
- package/src/common/providers/session.provider.js +0 -27
- package/src/common/providers/session.provider.js.map +0 -1
|
@@ -4,8 +4,15 @@ import { z } from 'zod';
|
|
|
4
4
|
declare const inputSchema: z.ZodObject<{
|
|
5
5
|
request: z.ZodObject<{
|
|
6
6
|
params: z.ZodOptional<z.ZodObject<{
|
|
7
|
+
task: z.ZodOptional<z.ZodObject<{
|
|
8
|
+
ttl: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodNull]>>;
|
|
9
|
+
pollInterval: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
}, z.core.$loose>>;
|
|
7
11
|
_meta: z.ZodOptional<z.ZodObject<{
|
|
8
12
|
progressToken: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
13
|
+
"io.modelcontextprotocol/related-task": z.ZodOptional<z.ZodObject<{
|
|
14
|
+
taskId: z.ZodString;
|
|
15
|
+
}, z.core.$loose>>;
|
|
9
16
|
}, z.core.$loose>>;
|
|
10
17
|
cursor: z.ZodOptional<z.ZodString>;
|
|
11
18
|
}, z.core.$loose>>;
|
|
@@ -14,7 +21,11 @@ declare const inputSchema: z.ZodObject<{
|
|
|
14
21
|
ctx: z.ZodUnknown;
|
|
15
22
|
}, z.core.$strip>;
|
|
16
23
|
declare const outputSchema: z.ZodObject<{
|
|
17
|
-
_meta: z.ZodOptional<z.
|
|
24
|
+
_meta: z.ZodOptional<z.ZodObject<{
|
|
25
|
+
"io.modelcontextprotocol/related-task": z.ZodOptional<z.ZodObject<{
|
|
26
|
+
taskId: z.ZodString;
|
|
27
|
+
}, z.core.$loose>>;
|
|
28
|
+
}, z.core.$loose>>;
|
|
18
29
|
nextCursor: z.ZodOptional<z.ZodString>;
|
|
19
30
|
prompts: z.ZodArray<z.ZodObject<{
|
|
20
31
|
description: z.ZodOptional<z.ZodString>;
|
|
@@ -11,6 +11,18 @@ export default class ProviderRegistry extends RegistryAbstract<ProviderEntry, Pr
|
|
|
11
11
|
/** topo order (deps first) */
|
|
12
12
|
private order;
|
|
13
13
|
private registries;
|
|
14
|
+
/** Session-scoped provider instance cache by sessionKey */
|
|
15
|
+
private sessionStores;
|
|
16
|
+
/** Locks to prevent concurrent session builds (race condition prevention) */
|
|
17
|
+
private sessionBuildLocks;
|
|
18
|
+
/** Maximum number of sessions to cache (LRU eviction) */
|
|
19
|
+
private static readonly MAX_SESSION_CACHE_SIZE;
|
|
20
|
+
/** Session cache TTL in milliseconds (1 hour) */
|
|
21
|
+
private static readonly SESSION_CACHE_TTL_MS;
|
|
22
|
+
/** Cleanup interval in milliseconds (1 minute) */
|
|
23
|
+
private static readonly SESSION_CLEANUP_INTERVAL_MS;
|
|
24
|
+
/** Handle for the session cleanup interval */
|
|
25
|
+
private sessionCleanupInterval;
|
|
14
26
|
constructor(list: ProviderType[], parentProviders?: ProviderRegistry | undefined);
|
|
15
27
|
getProviders(): ProviderEntry[];
|
|
16
28
|
/** Walk up the registry chain to find a def for a token. */
|
|
@@ -32,6 +44,15 @@ export default class ProviderRegistry extends RegistryAbstract<ProviderEntry, Pr
|
|
|
32
44
|
getAllSingletons(): ReadonlyMap<Token, unknown>;
|
|
33
45
|
discoveryDeps(rec: ProviderRecord): Token[];
|
|
34
46
|
invocationTokens(_token: Token, rec: ProviderRecord): Token[];
|
|
47
|
+
/**
|
|
48
|
+
* Normalize deprecated scopes to their modern equivalents.
|
|
49
|
+
*
|
|
50
|
+
* - SESSION → CONTEXT (deprecated)
|
|
51
|
+
* - REQUEST → CONTEXT (deprecated)
|
|
52
|
+
*
|
|
53
|
+
* This enables backwards compatibility while unifying the scope model.
|
|
54
|
+
*/
|
|
55
|
+
private normalizeScope;
|
|
35
56
|
getProviderScope(rec: ProviderRecord): ProviderScope;
|
|
36
57
|
getScope(): ScopeEntry;
|
|
37
58
|
private withTimeout;
|
|
@@ -73,10 +94,81 @@ export default class ProviderRegistry extends RegistryAbstract<ProviderEntry, Pr
|
|
|
73
94
|
getActiveScope(): Scope;
|
|
74
95
|
getActiveServer(): FrontMcpServer;
|
|
75
96
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
97
|
+
* Clean up a specific session's provider cache.
|
|
98
|
+
* Call this when a session is terminated or expired.
|
|
99
|
+
*
|
|
100
|
+
* @param sessionKey - The session identifier to clean up
|
|
101
|
+
*/
|
|
102
|
+
cleanupSession(sessionKey: string): void;
|
|
103
|
+
/**
|
|
104
|
+
* Clean up expired sessions from the cache.
|
|
105
|
+
* Sessions older than SESSION_CACHE_TTL_MS are removed.
|
|
106
|
+
*
|
|
107
|
+
* @returns Number of sessions cleaned up
|
|
108
|
+
*/
|
|
109
|
+
cleanupExpiredSessions(): number;
|
|
110
|
+
/**
|
|
111
|
+
* Start the background session cleanup timer.
|
|
112
|
+
* This periodically removes expired sessions from the cache.
|
|
113
|
+
*/
|
|
114
|
+
startSessionCleanup(): void;
|
|
115
|
+
/**
|
|
116
|
+
* Stop the background session cleanup timer.
|
|
117
|
+
* Call this when shutting down the server gracefully.
|
|
118
|
+
*/
|
|
119
|
+
stopSessionCleanup(): void;
|
|
120
|
+
/**
|
|
121
|
+
* Dispose of the registry, cleaning up all resources.
|
|
122
|
+
* Call this when the registry/scope is being destroyed to prevent:
|
|
123
|
+
* - Memory leaks from retained interval handles
|
|
124
|
+
* - Orphaned session cleanup timers
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* // In scope teardown
|
|
129
|
+
* scope.providers.dispose();
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
dispose(): void;
|
|
133
|
+
/**
|
|
134
|
+
* Get session cache statistics for monitoring.
|
|
135
|
+
*/
|
|
136
|
+
getSessionCacheStats(): {
|
|
137
|
+
size: number;
|
|
138
|
+
maxSize: number;
|
|
139
|
+
ttlMs: number;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Build provider instance views for different scopes.
|
|
143
|
+
*
|
|
144
|
+
* This method creates a complete view of providers across all scopes:
|
|
145
|
+
* - GLOBAL: Returns existing singleton instances (read-only)
|
|
146
|
+
* - CONTEXT: Builds per-context providers (unified session + request)
|
|
147
|
+
*
|
|
148
|
+
* Note: For backwards compatibility, SESSION and REQUEST scopes are normalized
|
|
149
|
+
* to CONTEXT. The returned views include `session` and `request` aliases that
|
|
150
|
+
* both point to the `context` store.
|
|
151
|
+
*
|
|
152
|
+
* @param sessionKey - Unique context/session identifier for CONTEXT-scoped providers
|
|
153
|
+
* @param contextProviders - Optional pre-built CONTEXT-scoped providers (e.g., FrontMcpContext)
|
|
154
|
+
* @returns ProviderViews with global and context provider maps (session/request as aliases)
|
|
155
|
+
*/
|
|
156
|
+
buildViews(sessionKey: string, contextProviders?: Map<Token, unknown>): Promise<ProviderViews>;
|
|
157
|
+
/**
|
|
158
|
+
* Build a provider into a store, with access to context and global views for dependencies.
|
|
159
|
+
*/
|
|
160
|
+
private buildIntoStoreWithViews;
|
|
161
|
+
/**
|
|
162
|
+
* Resolve a dependency from the available views (context → global).
|
|
163
|
+
*/
|
|
164
|
+
private resolveFromViews;
|
|
165
|
+
/**
|
|
166
|
+
* Get a provider from the given views, checking context → global.
|
|
167
|
+
*
|
|
168
|
+
* @param token - The provider token to look up
|
|
169
|
+
* @param views - The provider views to search
|
|
170
|
+
* @returns The provider instance
|
|
171
|
+
* @throws Error if provider not found in any view
|
|
80
172
|
*/
|
|
81
|
-
|
|
173
|
+
getScoped<T>(token: Token<T>, views: ProviderViews): T;
|
|
82
174
|
}
|
|
@@ -7,6 +7,7 @@ const token_utils_1 = require("../utils/token.utils");
|
|
|
7
7
|
const metadata_utils_1 = require("../utils/metadata.utils");
|
|
8
8
|
const regsitry_1 = require("../regsitry");
|
|
9
9
|
const scope_1 = require("../scope");
|
|
10
|
+
const session_key_provider_1 = require("../context/session-key.provider");
|
|
10
11
|
class ProviderRegistry extends regsitry_1.RegistryAbstract {
|
|
11
12
|
parentProviders;
|
|
12
13
|
/** used to track which registry provided which token */
|
|
@@ -16,6 +17,18 @@ class ProviderRegistry extends regsitry_1.RegistryAbstract {
|
|
|
16
17
|
// /** scoped instance stores by key */
|
|
17
18
|
// private scoped = new Map<string, Map<Token, any>>();
|
|
18
19
|
registries = new Map();
|
|
20
|
+
/** Session-scoped provider instance cache by sessionKey */
|
|
21
|
+
sessionStores = new Map();
|
|
22
|
+
/** Locks to prevent concurrent session builds (race condition prevention) */
|
|
23
|
+
sessionBuildLocks = new Map();
|
|
24
|
+
/** Maximum number of sessions to cache (LRU eviction) */
|
|
25
|
+
static MAX_SESSION_CACHE_SIZE = 10000;
|
|
26
|
+
/** Session cache TTL in milliseconds (1 hour) */
|
|
27
|
+
static SESSION_CACHE_TTL_MS = 3600000;
|
|
28
|
+
/** Cleanup interval in milliseconds (1 minute) */
|
|
29
|
+
static SESSION_CLEANUP_INTERVAL_MS = 60000;
|
|
30
|
+
/** Handle for the session cleanup interval */
|
|
31
|
+
sessionCleanupInterval = null;
|
|
19
32
|
constructor(list, parentProviders) {
|
|
20
33
|
super('ProviderRegistry', parentProviders, list, false);
|
|
21
34
|
this.parentProviders = parentProviders;
|
|
@@ -141,6 +154,13 @@ class ProviderRegistry extends regsitry_1.RegistryAbstract {
|
|
|
141
154
|
throw new Error(`Failed constructing ${(0, token_utils_1.tokenName)(token)}: ${msg}`);
|
|
142
155
|
}
|
|
143
156
|
}
|
|
157
|
+
// Start background session cleanup after initialization
|
|
158
|
+
// This ensures expired sessions are periodically cleaned up (TTL enforcement)
|
|
159
|
+
// Only start for scope-level registries (those with parentProviders) since
|
|
160
|
+
// SESSION providers are stored in scope registries, not the global registry
|
|
161
|
+
if (this.parentProviders) {
|
|
162
|
+
this.startSessionCleanup();
|
|
163
|
+
}
|
|
144
164
|
}
|
|
145
165
|
/* -------------------- Views & session stores -------------------- */
|
|
146
166
|
/** Return the live singleton map as a read-only view. No copying. */
|
|
@@ -153,8 +173,26 @@ class ProviderRegistry extends regsitry_1.RegistryAbstract {
|
|
|
153
173
|
invocationTokens(_token, rec) {
|
|
154
174
|
return (0, provider_utils_1.providerInvocationTokens)(rec, (k, phase) => (0, token_utils_1.depsOfClass)(k, phase));
|
|
155
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Normalize deprecated scopes to their modern equivalents.
|
|
178
|
+
*
|
|
179
|
+
* - SESSION → CONTEXT (deprecated)
|
|
180
|
+
* - REQUEST → CONTEXT (deprecated)
|
|
181
|
+
*
|
|
182
|
+
* This enables backwards compatibility while unifying the scope model.
|
|
183
|
+
*/
|
|
184
|
+
normalizeScope(scope) {
|
|
185
|
+
switch (scope) {
|
|
186
|
+
case common_1.ProviderScope.SESSION:
|
|
187
|
+
case common_1.ProviderScope.REQUEST:
|
|
188
|
+
return common_1.ProviderScope.CONTEXT;
|
|
189
|
+
default:
|
|
190
|
+
return scope;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
156
193
|
getProviderScope(rec) {
|
|
157
|
-
|
|
194
|
+
const rawScope = rec.metadata?.scope ?? common_1.ProviderScope.GLOBAL;
|
|
195
|
+
return this.normalizeScope(rawScope);
|
|
158
196
|
}
|
|
159
197
|
getScope() {
|
|
160
198
|
return this.getActiveScope();
|
|
@@ -553,19 +591,278 @@ class ProviderRegistry extends regsitry_1.RegistryAbstract {
|
|
|
553
591
|
getActiveServer() {
|
|
554
592
|
return this.getWithParents(common_1.FrontMcpServer);
|
|
555
593
|
}
|
|
594
|
+
/* -------------------- Session Cache Management -------------------- */
|
|
595
|
+
/**
|
|
596
|
+
* Clean up a specific session's provider cache.
|
|
597
|
+
* Call this when a session is terminated or expired.
|
|
598
|
+
*
|
|
599
|
+
* @param sessionKey - The session identifier to clean up
|
|
600
|
+
*/
|
|
601
|
+
cleanupSession(sessionKey) {
|
|
602
|
+
this.sessionStores.delete(sessionKey);
|
|
603
|
+
// Resolve any waiters before deleting the lock to prevent hung promises
|
|
604
|
+
const lock = this.sessionBuildLocks.get(sessionKey);
|
|
605
|
+
if (lock) {
|
|
606
|
+
lock.resolve();
|
|
607
|
+
this.sessionBuildLocks.delete(sessionKey);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Clean up expired sessions from the cache.
|
|
612
|
+
* Sessions older than SESSION_CACHE_TTL_MS are removed.
|
|
613
|
+
*
|
|
614
|
+
* @returns Number of sessions cleaned up
|
|
615
|
+
*/
|
|
616
|
+
cleanupExpiredSessions() {
|
|
617
|
+
const now = Date.now();
|
|
618
|
+
const cutoff = now - ProviderRegistry.SESSION_CACHE_TTL_MS;
|
|
619
|
+
let cleaned = 0;
|
|
620
|
+
for (const [key, entry] of this.sessionStores) {
|
|
621
|
+
if (entry.lastAccess < cutoff) {
|
|
622
|
+
this.sessionStores.delete(key);
|
|
623
|
+
// Resolve any waiters before deleting the lock to prevent hung promises
|
|
624
|
+
const lock = this.sessionBuildLocks.get(key);
|
|
625
|
+
if (lock) {
|
|
626
|
+
lock.resolve();
|
|
627
|
+
this.sessionBuildLocks.delete(key);
|
|
628
|
+
}
|
|
629
|
+
cleaned++;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return cleaned;
|
|
633
|
+
}
|
|
556
634
|
/**
|
|
557
|
-
*
|
|
558
|
-
*
|
|
559
|
-
* Actual view building logic is planned as part of the session management refactoring.
|
|
560
|
-
* See the ProviderRegistryInterface TODO for fixing the session type.
|
|
635
|
+
* Start the background session cleanup timer.
|
|
636
|
+
* This periodically removes expired sessions from the cache.
|
|
561
637
|
*/
|
|
562
|
-
|
|
638
|
+
startSessionCleanup() {
|
|
639
|
+
// Only start if not already running
|
|
640
|
+
if (this.sessionCleanupInterval) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
this.sessionCleanupInterval = setInterval(() => {
|
|
644
|
+
const cleaned = this.cleanupExpiredSessions();
|
|
645
|
+
if (cleaned > 0) {
|
|
646
|
+
console.debug(`[ProviderRegistry] Cleaned up ${cleaned} expired sessions`);
|
|
647
|
+
}
|
|
648
|
+
}, ProviderRegistry.SESSION_CLEANUP_INTERVAL_MS);
|
|
649
|
+
// Allow the process to exit even if the interval is running
|
|
650
|
+
// This is important for graceful shutdown in serverless environments
|
|
651
|
+
if (this.sessionCleanupInterval.unref) {
|
|
652
|
+
this.sessionCleanupInterval.unref();
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Stop the background session cleanup timer.
|
|
657
|
+
* Call this when shutting down the server gracefully.
|
|
658
|
+
*/
|
|
659
|
+
stopSessionCleanup() {
|
|
660
|
+
if (this.sessionCleanupInterval) {
|
|
661
|
+
clearInterval(this.sessionCleanupInterval);
|
|
662
|
+
this.sessionCleanupInterval = null;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Dispose of the registry, cleaning up all resources.
|
|
667
|
+
* Call this when the registry/scope is being destroyed to prevent:
|
|
668
|
+
* - Memory leaks from retained interval handles
|
|
669
|
+
* - Orphaned session cleanup timers
|
|
670
|
+
*
|
|
671
|
+
* @example
|
|
672
|
+
* ```typescript
|
|
673
|
+
* // In scope teardown
|
|
674
|
+
* scope.providers.dispose();
|
|
675
|
+
* ```
|
|
676
|
+
*/
|
|
677
|
+
dispose() {
|
|
678
|
+
this.stopSessionCleanup();
|
|
679
|
+
// Clear all session stores to help garbage collection
|
|
680
|
+
this.sessionStores.clear();
|
|
681
|
+
// Resolve any pending locks to prevent hung promises
|
|
682
|
+
for (const lock of this.sessionBuildLocks.values()) {
|
|
683
|
+
lock.resolve();
|
|
684
|
+
}
|
|
685
|
+
this.sessionBuildLocks.clear();
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Get session cache statistics for monitoring.
|
|
689
|
+
*/
|
|
690
|
+
getSessionCacheStats() {
|
|
691
|
+
return {
|
|
692
|
+
size: this.sessionStores.size,
|
|
693
|
+
maxSize: ProviderRegistry.MAX_SESSION_CACHE_SIZE,
|
|
694
|
+
ttlMs: ProviderRegistry.SESSION_CACHE_TTL_MS,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
/* -------------------- Scoped Provider Views -------------------- */
|
|
698
|
+
/**
|
|
699
|
+
* Build provider instance views for different scopes.
|
|
700
|
+
*
|
|
701
|
+
* This method creates a complete view of providers across all scopes:
|
|
702
|
+
* - GLOBAL: Returns existing singleton instances (read-only)
|
|
703
|
+
* - CONTEXT: Builds per-context providers (unified session + request)
|
|
704
|
+
*
|
|
705
|
+
* Note: For backwards compatibility, SESSION and REQUEST scopes are normalized
|
|
706
|
+
* to CONTEXT. The returned views include `session` and `request` aliases that
|
|
707
|
+
* both point to the `context` store.
|
|
708
|
+
*
|
|
709
|
+
* @param sessionKey - Unique context/session identifier for CONTEXT-scoped providers
|
|
710
|
+
* @param contextProviders - Optional pre-built CONTEXT-scoped providers (e.g., FrontMcpContext)
|
|
711
|
+
* @returns ProviderViews with global and context provider maps (session/request as aliases)
|
|
712
|
+
*/
|
|
713
|
+
async buildViews(sessionKey, contextProviders) {
|
|
714
|
+
// Early validation BEFORE any cache operations or lock acquisition
|
|
715
|
+
// This prevents cache pollution with invalid session keys
|
|
716
|
+
session_key_provider_1.SessionKey.validate(sessionKey);
|
|
717
|
+
// 1. Global providers - return existing singletons
|
|
718
|
+
const global = this.getAllSingletons();
|
|
719
|
+
// 2. Context providers - build per-context (unified session+request)
|
|
720
|
+
// Note: We don't cache context providers because they're per-request.
|
|
721
|
+
// The unified context model means all non-global providers are fresh per-request.
|
|
722
|
+
const contextStore = new Map(contextProviders);
|
|
723
|
+
// Inject SessionKey for backwards compatibility with providers that depend on it
|
|
724
|
+
// @deprecated - Providers should migrate to using FRONTMCP_CONTEXT instead
|
|
725
|
+
if (!contextStore.has(session_key_provider_1.SessionKey)) {
|
|
726
|
+
contextStore.set(session_key_provider_1.SessionKey, new session_key_provider_1.SessionKey(sessionKey));
|
|
727
|
+
}
|
|
728
|
+
// Build all CONTEXT-scoped providers (including normalized SESSION/REQUEST)
|
|
729
|
+
for (const token of this.order) {
|
|
730
|
+
const rec = this.defs.get(token);
|
|
731
|
+
if (!rec)
|
|
732
|
+
continue;
|
|
733
|
+
// getProviderScope() already normalizes SESSION/REQUEST → CONTEXT
|
|
734
|
+
if (this.getProviderScope(rec) !== common_1.ProviderScope.CONTEXT)
|
|
735
|
+
continue;
|
|
736
|
+
if (contextStore.has(token))
|
|
737
|
+
continue;
|
|
738
|
+
await this.buildIntoStoreWithViews(token, rec, contextStore, sessionKey, contextStore, global);
|
|
739
|
+
}
|
|
740
|
+
// Return views with backwards-compatible aliases
|
|
741
|
+
// All three (context, session, request) point to the same map
|
|
563
742
|
return {
|
|
564
|
-
global
|
|
565
|
-
|
|
566
|
-
|
|
743
|
+
global,
|
|
744
|
+
context: contextStore,
|
|
745
|
+
// Backwards compatibility aliases
|
|
746
|
+
session: contextStore,
|
|
747
|
+
request: contextStore,
|
|
567
748
|
};
|
|
568
749
|
}
|
|
750
|
+
/**
|
|
751
|
+
* Build a provider into a store, with access to context and global views for dependencies.
|
|
752
|
+
*/
|
|
753
|
+
async buildIntoStoreWithViews(token, rec, store, scopeKey, contextStore, globalStore) {
|
|
754
|
+
if (store.has(token))
|
|
755
|
+
return;
|
|
756
|
+
try {
|
|
757
|
+
switch (rec.kind) {
|
|
758
|
+
case common_1.ProviderKind.VALUE: {
|
|
759
|
+
store.set(token, rec.useValue);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
case common_1.ProviderKind.FACTORY: {
|
|
763
|
+
const deps = this.invocationTokens(token, rec);
|
|
764
|
+
const args = [];
|
|
765
|
+
for (const d of deps) {
|
|
766
|
+
args.push(await this.resolveFromViews(d, contextStore, globalStore, scopeKey));
|
|
767
|
+
}
|
|
768
|
+
const out = rec.useFactory(...args);
|
|
769
|
+
const val = (0, token_utils_1.isPromise)(out)
|
|
770
|
+
? await this.withTimeout(out, this.asyncTimeoutMs, `${(0, token_utils_1.tokenName)(token)}.useFactory(...)`)
|
|
771
|
+
: out;
|
|
772
|
+
store.set(token, val);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
case common_1.ProviderKind.CLASS:
|
|
776
|
+
case common_1.ProviderKind.CLASS_TOKEN: {
|
|
777
|
+
const depsTokens = this.invocationTokens(token, rec);
|
|
778
|
+
const deps = [];
|
|
779
|
+
for (const d of depsTokens) {
|
|
780
|
+
deps.push(await this.resolveFromViews(d, contextStore, globalStore, scopeKey));
|
|
781
|
+
}
|
|
782
|
+
const klass = rec.kind === common_1.ProviderKind.CLASS ? rec.useClass : rec.provide;
|
|
783
|
+
if ((0, metadata_utils_1.hasAsyncWith)(klass)) {
|
|
784
|
+
const out = klass.with(...deps);
|
|
785
|
+
const val = (0, token_utils_1.isPromise)(out)
|
|
786
|
+
? await this.withTimeout(out, this.asyncTimeoutMs, `${klass.name}.with(...)`)
|
|
787
|
+
: out;
|
|
788
|
+
store.set(token, val);
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
const instance = new klass(...deps);
|
|
792
|
+
const init = instance?.init;
|
|
793
|
+
if (typeof init === 'function') {
|
|
794
|
+
const ret = init.call(instance);
|
|
795
|
+
if ((0, token_utils_1.isPromise)(ret))
|
|
796
|
+
await this.withTimeout(ret, this.asyncTimeoutMs, `${klass.name}.init()`);
|
|
797
|
+
}
|
|
798
|
+
store.set(token, instance);
|
|
799
|
+
}
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
catch (e) {
|
|
805
|
+
const msg = e?.message ?? e;
|
|
806
|
+
console.error(`Failed constructing (context-scoped):`, msg);
|
|
807
|
+
throw new Error(`Failed constructing (context-scoped) ${(0, token_utils_1.tokenName)(token)}: ${msg}`);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Resolve a dependency from the available views (context → global).
|
|
812
|
+
*/
|
|
813
|
+
async resolveFromViews(token, contextStore, globalStore, scopeKey) {
|
|
814
|
+
// Check stores in order: context -> global -> instances
|
|
815
|
+
if (contextStore.has(token))
|
|
816
|
+
return contextStore.get(token);
|
|
817
|
+
if (globalStore.has(token))
|
|
818
|
+
return globalStore.get(token);
|
|
819
|
+
if (this.instances.has(token))
|
|
820
|
+
return this.instances.get(token);
|
|
821
|
+
// Try to build if it's a registered provider
|
|
822
|
+
const rec = this.defs.get(token);
|
|
823
|
+
if (rec) {
|
|
824
|
+
const scope = this.getProviderScope(rec);
|
|
825
|
+
if (scope === common_1.ProviderScope.GLOBAL) {
|
|
826
|
+
throw new Error(`GLOBAL dependency ${(0, token_utils_1.tokenName)(token)} is not instantiated`);
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
// CONTEXT scope - build into context store
|
|
830
|
+
await this.buildIntoStoreWithViews(token, rec, contextStore, scopeKey, contextStore, globalStore);
|
|
831
|
+
return contextStore.get(token);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
// Check parent hierarchy
|
|
835
|
+
const up = this.lookupDefInHierarchy(token);
|
|
836
|
+
if (up) {
|
|
837
|
+
const sc = up.registry.getProviderScope(up.rec);
|
|
838
|
+
if (sc === common_1.ProviderScope.GLOBAL) {
|
|
839
|
+
const v = up.registry.instances.get(token);
|
|
840
|
+
if (v !== undefined)
|
|
841
|
+
return v;
|
|
842
|
+
throw new Error(`GLOBAL dependency ${(0, token_utils_1.tokenName)(token)} is not instantiated in parent`);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
throw new Error(`Cannot resolve dependency ${(0, token_utils_1.tokenName)(token)} from views`);
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Get a provider from the given views, checking context → global.
|
|
849
|
+
*
|
|
850
|
+
* @param token - The provider token to look up
|
|
851
|
+
* @param views - The provider views to search
|
|
852
|
+
* @returns The provider instance
|
|
853
|
+
* @throws Error if provider not found in any view
|
|
854
|
+
*/
|
|
855
|
+
getScoped(token, views) {
|
|
856
|
+
// Check context first (unified session+request store)
|
|
857
|
+
if (views.context.has(token))
|
|
858
|
+
return views.context.get(token);
|
|
859
|
+
if (views.global.has(token))
|
|
860
|
+
return views.global.get(token);
|
|
861
|
+
// Check instances as fallback for global providers
|
|
862
|
+
if (this.instances.has(token))
|
|
863
|
+
return this.instances.get(token);
|
|
864
|
+
throw new Error(`Provider ${(0, token_utils_1.tokenName)(token)} not found in views. Ensure it was built via buildViews().`);
|
|
865
|
+
}
|
|
569
866
|
}
|
|
570
867
|
exports.default = ProviderRegistry;
|
|
571
868
|
//# sourceMappingURL=provider.registry.js.map
|