@frontmcp/sdk 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +1 -0
  2. package/package.json +13 -6
  3. package/src/auth/session/index.d.ts +1 -0
  4. package/src/auth/session/index.js +3 -1
  5. package/src/auth/session/index.js.map +1 -1
  6. package/src/auth/session/vercel-kv-session.store.d.ts +96 -0
  7. package/src/auth/session/vercel-kv-session.store.js +216 -0
  8. package/src/auth/session/vercel-kv-session.store.js.map +1 -0
  9. package/src/common/decorators/front-mcp.decorator.js +14 -17
  10. package/src/common/decorators/front-mcp.decorator.js.map +1 -1
  11. package/src/common/metadata/front-mcp.metadata.d.ts +705 -23
  12. package/src/common/metadata/front-mcp.metadata.js +1 -0
  13. package/src/common/metadata/front-mcp.metadata.js.map +1 -1
  14. package/src/common/metadata/prompt.metadata.d.ts +4 -0
  15. package/src/common/metadata/resource.metadata.d.ts +8 -0
  16. package/src/common/metadata/tool-ui.metadata.d.ts +2 -2
  17. package/src/common/metadata/tool-ui.metadata.js +1 -1
  18. package/src/common/metadata/tool-ui.metadata.js.map +1 -1
  19. package/src/common/metadata/tool.metadata.d.ts +4 -0
  20. package/src/common/schemas/http-output.schema.d.ts +24 -6
  21. package/src/common/tokens/front-mcp.tokens.js +1 -0
  22. package/src/common/tokens/front-mcp.tokens.js.map +1 -1
  23. package/src/common/types/options/redis.options.d.ts +173 -5
  24. package/src/common/types/options/redis.options.js +157 -11
  25. package/src/common/types/options/redis.options.js.map +1 -1
  26. package/src/common/types/options/server-info.options.d.ts +4 -0
  27. package/src/common/types/options/transport.options.d.ts +68 -4
  28. package/src/common/utils/global-config.utils.d.ts +36 -0
  29. package/src/common/utils/global-config.utils.js +44 -0
  30. package/src/common/utils/global-config.utils.js.map +1 -0
  31. package/src/common/utils/index.d.ts +1 -0
  32. package/src/common/utils/index.js +1 -0
  33. package/src/common/utils/index.js.map +1 -1
  34. package/src/completion/flows/complete.flow.d.ts +6 -8
  35. package/src/errors/index.d.ts +1 -1
  36. package/src/errors/index.js +2 -1
  37. package/src/errors/index.js.map +1 -1
  38. package/src/errors/mcp.error.d.ts +9 -0
  39. package/src/errors/mcp.error.js +19 -1
  40. package/src/errors/mcp.error.js.map +1 -1
  41. package/src/front-mcp/front-mcp.providers.d.ts +208 -0
  42. package/src/front-mcp/index.d.ts +1 -0
  43. package/src/front-mcp/index.js +3 -0
  44. package/src/front-mcp/index.js.map +1 -1
  45. package/src/index.d.ts +1 -1
  46. package/src/index.js +2 -1
  47. package/src/index.js.map +1 -1
  48. package/src/logging/flows/set-level.flow.d.ts +6 -8
  49. package/src/prompt/flows/get-prompt.flow.d.ts +14 -8
  50. package/src/prompt/flows/prompts-list.flow.d.ts +8 -7
  51. package/src/resource/flows/read-resource.flow.d.ts +8 -9
  52. package/src/resource/flows/resource-templates-list.flow.d.ts +8 -7
  53. package/src/resource/flows/resources-list.flow.d.ts +8 -7
  54. package/src/resource/flows/subscribe-resource.flow.d.ts +6 -8
  55. package/src/resource/flows/unsubscribe-resource.flow.d.ts +6 -8
  56. package/src/store/adapters/store.vercel-kv.adapter.d.ts +86 -0
  57. package/src/store/adapters/store.vercel-kv.adapter.js +155 -0
  58. package/src/store/adapters/store.vercel-kv.adapter.js.map +1 -0
  59. package/src/store/index.d.ts +2 -0
  60. package/src/store/index.js +2 -0
  61. package/src/store/index.js.map +1 -1
  62. package/src/store/store.factory.d.ts +86 -0
  63. package/src/store/store.factory.js +194 -0
  64. package/src/store/store.factory.js.map +1 -0
  65. package/src/tool/flows/call-tool.flow.d.ts +18 -9
  66. package/src/tool/flows/call-tool.flow.js +2 -2
  67. package/src/tool/flows/call-tool.flow.js.map +1 -1
  68. package/src/tool/flows/tools-list.flow.d.ts +9 -8
  69. package/src/tool/flows/tools-list.flow.js +2 -2
  70. package/src/tool/flows/tools-list.flow.js.map +1 -1
  71. package/src/tool/ui/index.d.ts +4 -4
  72. package/src/tool/ui/index.js +4 -4
  73. package/src/tool/ui/index.js.map +1 -1
  74. package/src/tool/ui/platform-adapters.d.ts +2 -2
  75. package/src/tool/ui/platform-adapters.js +3 -3
  76. package/src/tool/ui/platform-adapters.js.map +1 -1
  77. package/src/tool/ui/template-helpers.d.ts +5 -7
  78. package/src/tool/ui/template-helpers.js +9 -26
  79. package/src/tool/ui/template-helpers.js.map +1 -1
  80. package/src/tool/ui/ui-resource.handler.d.ts +1 -1
  81. package/src/tool/ui/ui-resource.handler.js +5 -5
  82. package/src/tool/ui/ui-resource.handler.js.map +1 -1
  83. package/src/transport/mcp-handlers/complete-request.handler.d.ts +4 -15
  84. package/src/transport/mcp-handlers/get-prompt-request.handler.d.ts +5 -15
  85. package/src/transport/mcp-handlers/index.d.ts +67 -195
  86. package/src/transport/mcp-handlers/list-prompts-request.handler.d.ts +5 -15
  87. package/src/transport/mcp-handlers/list-resource-templates-request.handler.d.ts +5 -15
  88. package/src/transport/mcp-handlers/list-resources-request.handler.d.ts +5 -15
  89. package/src/transport/mcp-handlers/list-tools-request.handler.d.ts +5 -15
  90. package/src/transport/mcp-handlers/logging-set-level-request.handler.d.ts +3 -14
  91. package/src/transport/mcp-handlers/read-resource-request.handler.d.ts +4 -15
  92. package/src/transport/mcp-handlers/subscribe-request.handler.d.ts +3 -14
  93. package/src/transport/mcp-handlers/unsubscribe-request.handler.d.ts +3 -14
  94. package/src/transport/transport.registry.d.ts +5 -1
  95. package/src/transport/transport.registry.js +52 -23
  96. package/src/transport/transport.registry.js.map +1 -1
package/README.md CHANGED
@@ -14,6 +14,7 @@ _Made with ❤️ for TypeScript developers_
14
14
  [![NPM - @frontmcp/sdk](https://img.shields.io/npm/v/@frontmcp/sdk.svg?v=2)](https://www.npmjs.com/package/@frontmcp/sdk)
15
15
  [![Node](https://img.shields.io/badge/node-%3E%3D22-339933?logo=node.js&logoColor=white)](https://nodejs.org)
16
16
  [![License](https://img.shields.io/github/license/agentfront/frontmcp.svg?v=1)](https://github.com/agentfront/frontmcp/blob/main/LICENSE)
17
+ [![Snyk](https://snyk.io/test/github/agentfront/frontmcp/badge.svg)](https://snyk.io/test/github/agentfront/frontmcp)
17
18
 
18
19
  </div>
19
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/sdk",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "FrontMCP SDK",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -40,19 +40,26 @@
40
40
  },
41
41
  "peerDependencies": {
42
42
  "zod": "^4.0.0",
43
- "express": "^4.21.0",
43
+ "express": "^4.18.0 || ^5.0.0",
44
44
  "cors": "^2.8.5",
45
45
  "raw-body": "^3.0.0",
46
- "content-type": "^1.0.5"
46
+ "content-type": "^1.0.5",
47
+ "@vercel/kv": "^2.0.0 || ^3.0.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "@vercel/kv": {
51
+ "optional": true
52
+ }
47
53
  },
48
54
  "dependencies": {
49
- "@frontmcp/ui": "0.6.0",
50
- "@modelcontextprotocol/sdk": "1.24.3",
55
+ "@frontmcp/uipack": "0.6.1",
56
+ "@modelcontextprotocol/sdk": "1.25.1",
51
57
  "ioredis": "^5.8.0",
52
- "jose": "^6.1.0",
58
+ "jose": "^6.1.3",
53
59
  "reflect-metadata": "^0.2.2"
54
60
  },
55
61
  "devDependencies": {
62
+ "@vercel/kv": "^3.0.0",
56
63
  "typescript": "^5.9.3"
57
64
  },
58
65
  "type": "commonjs"
@@ -1,5 +1,6 @@
1
1
  export * from './transport-session.types';
2
2
  export { TransportSessionManager, InMemorySessionStore } from './transport-session.manager';
3
3
  export { RedisSessionStore } from './redis-session.store';
4
+ export { VercelKvSessionStore } from './vercel-kv-session.store';
4
5
  export * from './authorization.store';
5
6
  export * from './authorization-vault';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RedisSessionStore = exports.InMemorySessionStore = exports.TransportSessionManager = void 0;
3
+ exports.VercelKvSessionStore = exports.RedisSessionStore = exports.InMemorySessionStore = exports.TransportSessionManager = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  // Transport session architecture
6
6
  tslib_1.__exportStar(require("./transport-session.types"), exports);
@@ -9,6 +9,8 @@ Object.defineProperty(exports, "TransportSessionManager", { enumerable: true, ge
9
9
  Object.defineProperty(exports, "InMemorySessionStore", { enumerable: true, get: function () { return transport_session_manager_1.InMemorySessionStore; } });
10
10
  var redis_session_store_1 = require("./redis-session.store");
11
11
  Object.defineProperty(exports, "RedisSessionStore", { enumerable: true, get: function () { return redis_session_store_1.RedisSessionStore; } });
12
+ var vercel_kv_session_store_1 = require("./vercel-kv-session.store");
13
+ Object.defineProperty(exports, "VercelKvSessionStore", { enumerable: true, get: function () { return vercel_kv_session_store_1.VercelKvSessionStore; } });
12
14
  // Authorization store for OAuth flows
13
15
  tslib_1.__exportStar(require("./authorization.store"), exports);
14
16
  // Authorization vault for stateful sessions
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/auth/session/index.ts"],"names":[],"mappings":";;;;AAAA,iCAAiC;AACjC,oEAA0C;AAC1C,yEAA4F;AAAnF,oIAAA,uBAAuB,OAAA;AAAE,iIAAA,oBAAoB,OAAA;AACtD,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAE1B,sCAAsC;AACtC,gEAAsC;AAEtC,4CAA4C;AAC5C,gEAAsC","sourcesContent":["// Transport session architecture\nexport * from './transport-session.types';\nexport { TransportSessionManager, InMemorySessionStore } from './transport-session.manager';\nexport { RedisSessionStore } from './redis-session.store';\n\n// Authorization store for OAuth flows\nexport * from './authorization.store';\n\n// Authorization vault for stateful sessions\nexport * from './authorization-vault';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/auth/session/index.ts"],"names":[],"mappings":";;;;AAAA,iCAAiC;AACjC,oEAA0C;AAC1C,yEAA4F;AAAnF,oIAAA,uBAAuB,OAAA;AAAE,iIAAA,oBAAoB,OAAA;AACtD,6DAA0D;AAAjD,wHAAA,iBAAiB,OAAA;AAC1B,qEAAiE;AAAxD,+HAAA,oBAAoB,OAAA;AAE7B,sCAAsC;AACtC,gEAAsC;AAEtC,4CAA4C;AAC5C,gEAAsC","sourcesContent":["// Transport session architecture\nexport * from './transport-session.types';\nexport { TransportSessionManager, InMemorySessionStore } from './transport-session.manager';\nexport { RedisSessionStore } from './redis-session.store';\nexport { VercelKvSessionStore } from './vercel-kv-session.store';\n\n// Authorization store for OAuth flows\nexport * from './authorization.store';\n\n// Authorization vault for stateful sessions\nexport * from './authorization-vault';\n"]}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Vercel KV Session Store
3
+ *
4
+ * Session store implementation using Vercel KV (edge-compatible REST-based key-value store).
5
+ * Uses dynamic import to avoid bundling @vercel/kv for non-Vercel deployments.
6
+ *
7
+ * @see https://vercel.com/docs/storage/vercel-kv
8
+ */
9
+ import { SessionStore, StoredSession } from './transport-session.types';
10
+ import { FrontMcpLogger } from '../../common/interfaces/logger.interface';
11
+ import type { VercelKvProviderOptions } from '../../common/types/options/redis.options';
12
+ export interface VercelKvSessionConfig {
13
+ /**
14
+ * KV REST API URL
15
+ * @default process.env.KV_REST_API_URL
16
+ */
17
+ url?: string;
18
+ /**
19
+ * KV REST API Token
20
+ * @default process.env.KV_REST_API_TOKEN
21
+ */
22
+ token?: string;
23
+ /**
24
+ * Key prefix for session keys
25
+ * @default 'mcp:session:'
26
+ */
27
+ keyPrefix?: string;
28
+ /**
29
+ * Default TTL in milliseconds for session extension on access
30
+ * @default 3600000 (1 hour)
31
+ */
32
+ defaultTtlMs?: number;
33
+ }
34
+ /**
35
+ * Vercel KV-backed session store implementation
36
+ *
37
+ * Provides persistent session storage for edge deployments using Vercel KV.
38
+ * Sessions are stored as JSON with optional TTL.
39
+ */
40
+ export declare class VercelKvSessionStore implements SessionStore {
41
+ private kv;
42
+ private connectPromise;
43
+ private readonly keyPrefix;
44
+ private readonly defaultTtlMs;
45
+ private readonly logger?;
46
+ private readonly config;
47
+ constructor(config: VercelKvSessionConfig | VercelKvProviderOptions, logger?: FrontMcpLogger);
48
+ /**
49
+ * Connect to Vercel KV
50
+ * Uses dynamic import to avoid bundling @vercel/kv when not used.
51
+ * Thread-safe: concurrent calls will share the same connection promise.
52
+ */
53
+ connect(): Promise<void>;
54
+ private doConnect;
55
+ private ensureConnected;
56
+ /**
57
+ * Get the full key for a session ID
58
+ * @throws Error if sessionId is empty
59
+ */
60
+ private key;
61
+ /**
62
+ * Get a stored session by ID
63
+ *
64
+ * Note: Vercel KV doesn't support GETEX, so we use GET + PEXPIRE separately.
65
+ * This is slightly less atomic than Redis GETEX but sufficient for most use cases.
66
+ */
67
+ get(sessionId: string): Promise<StoredSession | null>;
68
+ /**
69
+ * Store a session with optional TTL
70
+ */
71
+ set(sessionId: string, session: StoredSession, ttlMs?: number): Promise<void>;
72
+ /**
73
+ * Delete a session
74
+ */
75
+ delete(sessionId: string): Promise<void>;
76
+ /**
77
+ * Check if a session exists
78
+ */
79
+ exists(sessionId: string): Promise<boolean>;
80
+ /**
81
+ * Allocate a new session ID
82
+ */
83
+ allocId(): string;
84
+ /**
85
+ * Disconnect from Vercel KV
86
+ * Vercel KV uses REST API, so there's no persistent connection to close
87
+ */
88
+ disconnect(): Promise<void>;
89
+ /**
90
+ * Test Vercel KV connection by setting and getting a test key.
91
+ * Useful for validating connection on startup.
92
+ *
93
+ * @returns true if connection is healthy, false otherwise
94
+ */
95
+ ping(): Promise<boolean>;
96
+ }
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+ /**
3
+ * Vercel KV Session Store
4
+ *
5
+ * Session store implementation using Vercel KV (edge-compatible REST-based key-value store).
6
+ * Uses dynamic import to avoid bundling @vercel/kv for non-Vercel deployments.
7
+ *
8
+ * @see https://vercel.com/docs/storage/vercel-kv
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.VercelKvSessionStore = void 0;
12
+ const crypto_1 = require("crypto");
13
+ const transport_session_types_1 = require("./transport-session.types");
14
+ /**
15
+ * Vercel KV-backed session store implementation
16
+ *
17
+ * Provides persistent session storage for edge deployments using Vercel KV.
18
+ * Sessions are stored as JSON with optional TTL.
19
+ */
20
+ class VercelKvSessionStore {
21
+ kv = null;
22
+ connectPromise = null;
23
+ keyPrefix;
24
+ defaultTtlMs;
25
+ logger;
26
+ config;
27
+ constructor(config, logger) {
28
+ this.config = config;
29
+ this.keyPrefix = config.keyPrefix ?? 'mcp:session:';
30
+ this.defaultTtlMs = config.defaultTtlMs ?? 3600000;
31
+ this.logger = logger;
32
+ }
33
+ /**
34
+ * Connect to Vercel KV
35
+ * Uses dynamic import to avoid bundling @vercel/kv when not used.
36
+ * Thread-safe: concurrent calls will share the same connection promise.
37
+ */
38
+ async connect() {
39
+ if (this.kv)
40
+ return;
41
+ // Prevent concurrent connection attempts
42
+ if (this.connectPromise) {
43
+ return this.connectPromise;
44
+ }
45
+ this.connectPromise = this.doConnect();
46
+ try {
47
+ await this.connectPromise;
48
+ }
49
+ catch (error) {
50
+ // Reset promise on failure to allow retry
51
+ this.connectPromise = null;
52
+ throw error;
53
+ }
54
+ }
55
+ async doConnect() {
56
+ const { createClient } = await import('@vercel/kv');
57
+ const url = this.config.url || process.env['KV_REST_API_URL'];
58
+ const token = this.config.token || process.env['KV_REST_API_TOKEN'];
59
+ if (!url || !token) {
60
+ throw new Error('Vercel KV requires url and token. Set KV_REST_API_URL and KV_REST_API_TOKEN environment variables or provide them in config.');
61
+ }
62
+ // Cast to our interface to avoid type compatibility issues
63
+ this.kv = createClient({ url, token });
64
+ }
65
+ async ensureConnected() {
66
+ await this.connect();
67
+ if (!this.kv) {
68
+ throw new Error('[VercelKvSessionStore] Connection failed - client not initialized');
69
+ }
70
+ return this.kv;
71
+ }
72
+ /**
73
+ * Get the full key for a session ID
74
+ * @throws Error if sessionId is empty
75
+ */
76
+ key(sessionId) {
77
+ if (!sessionId || sessionId.trim() === '') {
78
+ throw new Error('[VercelKvSessionStore] sessionId cannot be empty');
79
+ }
80
+ return `${this.keyPrefix}${sessionId}`;
81
+ }
82
+ /**
83
+ * Get a stored session by ID
84
+ *
85
+ * Note: Vercel KV doesn't support GETEX, so we use GET + PEXPIRE separately.
86
+ * This is slightly less atomic than Redis GETEX but sufficient for most use cases.
87
+ */
88
+ async get(sessionId) {
89
+ const kv = await this.ensureConnected();
90
+ const key = this.key(sessionId);
91
+ // Get the session
92
+ const raw = await kv.get(key);
93
+ if (!raw)
94
+ return null;
95
+ // Extend TTL (fire-and-forget, similar to Redis GETEX behavior)
96
+ kv.pexpire(key, this.defaultTtlMs).catch(() => void 0);
97
+ try {
98
+ const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
99
+ const result = transport_session_types_1.storedSessionSchema.safeParse(parsed);
100
+ if (!result.success) {
101
+ this.logger?.warn('[VercelKvSessionStore] Invalid session format', {
102
+ sessionId: sessionId.slice(0, 20),
103
+ errors: result.error.issues.slice(0, 3).map((i) => ({ path: i.path, message: i.message })),
104
+ });
105
+ // Delete invalid session data
106
+ this.delete(sessionId).catch(() => void 0);
107
+ return null;
108
+ }
109
+ const session = result.data;
110
+ // Check application-level expiration (separate from KV TTL)
111
+ if (session.session.expiresAt && session.session.expiresAt < Date.now()) {
112
+ // Session is logically expired - delete it
113
+ await this.delete(sessionId);
114
+ return null;
115
+ }
116
+ // Bound TTL by session.expiresAt to avoid keeping expired sessions
117
+ if (session.session.expiresAt) {
118
+ const ttlMs = Math.min(this.defaultTtlMs, session.session.expiresAt - Date.now());
119
+ if (ttlMs > 0 && ttlMs < this.defaultTtlMs) {
120
+ // Fire-and-forget - we're only optimizing cache eviction timing
121
+ kv.pexpire(key, ttlMs).catch(() => void 0);
122
+ }
123
+ }
124
+ // Update last accessed timestamp (in the returned object)
125
+ const updatedSession = {
126
+ ...session,
127
+ lastAccessedAt: Date.now(),
128
+ };
129
+ return updatedSession;
130
+ }
131
+ catch (error) {
132
+ this.logger?.warn('[VercelKvSessionStore] Failed to parse session', {
133
+ sessionId: sessionId.slice(0, 20),
134
+ error: error.message,
135
+ });
136
+ // Delete corrupted session payloads to prevent repeated failures
137
+ this.delete(sessionId).catch(() => void 0);
138
+ return null;
139
+ }
140
+ }
141
+ /**
142
+ * Store a session with optional TTL
143
+ */
144
+ async set(sessionId, session, ttlMs) {
145
+ const kv = await this.ensureConnected();
146
+ const key = this.key(sessionId);
147
+ const value = JSON.stringify(session);
148
+ if (ttlMs && ttlMs > 0) {
149
+ // Use px for millisecond precision
150
+ await kv.set(key, value, { px: ttlMs });
151
+ }
152
+ else if (session.session.expiresAt) {
153
+ // Use session's expiration if available
154
+ const ttl = session.session.expiresAt - Date.now();
155
+ if (ttl > 0) {
156
+ await kv.set(key, value, { px: ttl });
157
+ }
158
+ else {
159
+ // Already expired, but store anyway (will be cleaned up on next access)
160
+ await kv.set(key, value);
161
+ }
162
+ }
163
+ else {
164
+ // No TTL - session persists until explicitly deleted
165
+ await kv.set(key, value);
166
+ }
167
+ }
168
+ /**
169
+ * Delete a session
170
+ */
171
+ async delete(sessionId) {
172
+ const kv = await this.ensureConnected();
173
+ await kv.del(this.key(sessionId));
174
+ }
175
+ /**
176
+ * Check if a session exists
177
+ */
178
+ async exists(sessionId) {
179
+ const kv = await this.ensureConnected();
180
+ return (await kv.exists(this.key(sessionId))) === 1;
181
+ }
182
+ /**
183
+ * Allocate a new session ID
184
+ */
185
+ allocId() {
186
+ return (0, crypto_1.randomUUID)();
187
+ }
188
+ /**
189
+ * Disconnect from Vercel KV
190
+ * Vercel KV uses REST API, so there's no persistent connection to close
191
+ */
192
+ async disconnect() {
193
+ this.kv = null;
194
+ this.connectPromise = null;
195
+ }
196
+ /**
197
+ * Test Vercel KV connection by setting and getting a test key.
198
+ * Useful for validating connection on startup.
199
+ *
200
+ * @returns true if connection is healthy, false otherwise
201
+ */
202
+ async ping() {
203
+ try {
204
+ const kv = await this.ensureConnected();
205
+ const testKey = `${this.keyPrefix}__ping__`;
206
+ await kv.set(testKey, 'pong', { ex: 1 });
207
+ const result = await kv.get(testKey);
208
+ return result === 'pong';
209
+ }
210
+ catch {
211
+ return false;
212
+ }
213
+ }
214
+ }
215
+ exports.VercelKvSessionStore = VercelKvSessionStore;
216
+ //# sourceMappingURL=vercel-kv-session.store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel-kv-session.store.js","sourceRoot":"","sources":["../../../../src/auth/session/vercel-kv-session.store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Vercel KV Session Store\n *\n * Session store implementation using Vercel KV (edge-compatible REST-based key-value store).\n * Uses dynamic import to avoid bundling @vercel/kv for non-Vercel deployments.\n *\n * @see https://vercel.com/docs/storage/vercel-kv\n */\n\nimport { randomUUID } from 'crypto';\nimport { SessionStore, StoredSession, storedSessionSchema } from './transport-session.types';\nimport { FrontMcpLogger } from '../../common/interfaces/logger.interface';\nimport type { VercelKvProviderOptions } from '../../common/types/options/redis.options';\n\n// Interface for the Vercel KV client (matches @vercel/kv API)\n// Using custom interface to avoid type compatibility issues with optional dependency\ninterface VercelKVClient {\n get<T = unknown>(key: string): Promise<T | null>;\n set(key: string, value: unknown, options?: { ex?: number; px?: number }): Promise<void>;\n del(...keys: string[]): Promise<number>;\n exists(...keys: string[]): Promise<number>;\n pexpire(key: string, milliseconds: number): Promise<number>;\n}\n\nexport interface VercelKvSessionConfig {\n /**\n * KV REST API URL\n * @default process.env.KV_REST_API_URL\n */\n url?: string;\n\n /**\n * KV REST API Token\n * @default process.env.KV_REST_API_TOKEN\n */\n token?: string;\n\n /**\n * Key prefix for session keys\n * @default 'mcp:session:'\n */\n keyPrefix?: string;\n\n /**\n * Default TTL in milliseconds for session extension on access\n * @default 3600000 (1 hour)\n */\n defaultTtlMs?: number;\n}\n\n/**\n * Vercel KV-backed session store implementation\n *\n * Provides persistent session storage for edge deployments using Vercel KV.\n * Sessions are stored as JSON with optional TTL.\n */\nexport class VercelKvSessionStore implements SessionStore {\n private kv: VercelKVClient | null = null;\n private connectPromise: Promise<void> | null = null;\n private readonly keyPrefix: string;\n private readonly defaultTtlMs: number;\n private readonly logger?: FrontMcpLogger;\n private readonly config: VercelKvSessionConfig;\n\n constructor(config: VercelKvSessionConfig | VercelKvProviderOptions, logger?: FrontMcpLogger) {\n this.config = config;\n this.keyPrefix = config.keyPrefix ?? 'mcp:session:';\n this.defaultTtlMs = config.defaultTtlMs ?? 3600000;\n this.logger = logger;\n }\n\n /**\n * Connect to Vercel KV\n * Uses dynamic import to avoid bundling @vercel/kv when not used.\n * Thread-safe: concurrent calls will share the same connection promise.\n */\n async connect(): Promise<void> {\n if (this.kv) return;\n\n // Prevent concurrent connection attempts\n if (this.connectPromise) {\n return this.connectPromise;\n }\n\n this.connectPromise = this.doConnect();\n try {\n await this.connectPromise;\n } catch (error) {\n // Reset promise on failure to allow retry\n this.connectPromise = null;\n throw error;\n }\n }\n\n private async doConnect(): Promise<void> {\n const { createClient } = await import('@vercel/kv');\n\n const url = this.config.url || process.env['KV_REST_API_URL'];\n const token = this.config.token || process.env['KV_REST_API_TOKEN'];\n\n if (!url || !token) {\n throw new Error(\n 'Vercel KV requires url and token. Set KV_REST_API_URL and KV_REST_API_TOKEN environment variables or provide them in config.',\n );\n }\n\n // Cast to our interface to avoid type compatibility issues\n this.kv = createClient({ url, token }) as unknown as VercelKVClient;\n }\n\n private async ensureConnected(): Promise<VercelKVClient> {\n await this.connect();\n if (!this.kv) {\n throw new Error('[VercelKvSessionStore] Connection failed - client not initialized');\n }\n return this.kv;\n }\n\n /**\n * Get the full key for a session ID\n * @throws Error if sessionId is empty\n */\n private key(sessionId: string): string {\n if (!sessionId || sessionId.trim() === '') {\n throw new Error('[VercelKvSessionStore] sessionId cannot be empty');\n }\n return `${this.keyPrefix}${sessionId}`;\n }\n\n /**\n * Get a stored session by ID\n *\n * Note: Vercel KV doesn't support GETEX, so we use GET + PEXPIRE separately.\n * This is slightly less atomic than Redis GETEX but sufficient for most use cases.\n */\n async get(sessionId: string): Promise<StoredSession | null> {\n const kv = await this.ensureConnected();\n const key = this.key(sessionId);\n\n // Get the session\n const raw = await kv.get<string>(key);\n if (!raw) return null;\n\n // Extend TTL (fire-and-forget, similar to Redis GETEX behavior)\n kv.pexpire(key, this.defaultTtlMs).catch(() => void 0);\n\n try {\n const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;\n const result = storedSessionSchema.safeParse(parsed);\n\n if (!result.success) {\n this.logger?.warn('[VercelKvSessionStore] Invalid session format', {\n sessionId: sessionId.slice(0, 20),\n errors: result.error.issues.slice(0, 3).map((i) => ({ path: i.path, message: i.message })),\n });\n // Delete invalid session data\n this.delete(sessionId).catch(() => void 0);\n return null;\n }\n\n const session = result.data;\n\n // Check application-level expiration (separate from KV TTL)\n if (session.session.expiresAt && session.session.expiresAt < Date.now()) {\n // Session is logically expired - delete it\n await this.delete(sessionId);\n return null;\n }\n\n // Bound TTL by session.expiresAt to avoid keeping expired sessions\n if (session.session.expiresAt) {\n const ttlMs = Math.min(this.defaultTtlMs, session.session.expiresAt - Date.now());\n if (ttlMs > 0 && ttlMs < this.defaultTtlMs) {\n // Fire-and-forget - we're only optimizing cache eviction timing\n kv.pexpire(key, ttlMs).catch(() => void 0);\n }\n }\n\n // Update last accessed timestamp (in the returned object)\n const updatedSession: StoredSession = {\n ...session,\n lastAccessedAt: Date.now(),\n };\n\n return updatedSession;\n } catch (error) {\n this.logger?.warn('[VercelKvSessionStore] Failed to parse session', {\n sessionId: sessionId.slice(0, 20),\n error: (error as Error).message,\n });\n // Delete corrupted session payloads to prevent repeated failures\n this.delete(sessionId).catch(() => void 0);\n return null;\n }\n }\n\n /**\n * Store a session with optional TTL\n */\n async set(sessionId: string, session: StoredSession, ttlMs?: number): Promise<void> {\n const kv = await this.ensureConnected();\n const key = this.key(sessionId);\n const value = JSON.stringify(session);\n\n if (ttlMs && ttlMs > 0) {\n // Use px for millisecond precision\n await kv.set(key, value, { px: ttlMs });\n } else if (session.session.expiresAt) {\n // Use session's expiration if available\n const ttl = session.session.expiresAt - Date.now();\n if (ttl > 0) {\n await kv.set(key, value, { px: ttl });\n } else {\n // Already expired, but store anyway (will be cleaned up on next access)\n await kv.set(key, value);\n }\n } else {\n // No TTL - session persists until explicitly deleted\n await kv.set(key, value);\n }\n }\n\n /**\n * Delete a session\n */\n async delete(sessionId: string): Promise<void> {\n const kv = await this.ensureConnected();\n await kv.del(this.key(sessionId));\n }\n\n /**\n * Check if a session exists\n */\n async exists(sessionId: string): Promise<boolean> {\n const kv = await this.ensureConnected();\n return (await kv.exists(this.key(sessionId))) === 1;\n }\n\n /**\n * Allocate a new session ID\n */\n allocId(): string {\n return randomUUID();\n }\n\n /**\n * Disconnect from Vercel KV\n * Vercel KV uses REST API, so there's no persistent connection to close\n */\n async disconnect(): Promise<void> {\n this.kv = null;\n this.connectPromise = null;\n }\n\n /**\n * Test Vercel KV connection by setting and getting a test key.\n * Useful for validating connection on startup.\n *\n * @returns true if connection is healthy, false otherwise\n */\n async ping(): Promise<boolean> {\n try {\n const kv = await this.ensureConnected();\n const testKey = `${this.keyPrefix}__ping__`;\n await kv.set(testKey, 'pong', { ex: 1 });\n const result = await kv.get<string>(testKey);\n return result === 'pong';\n } catch {\n return false;\n }\n }\n}\n"]}
@@ -5,6 +5,7 @@ require("reflect-metadata");
5
5
  const tokens_1 = require("../tokens");
6
6
  const metadata_1 = require("../metadata");
7
7
  const migrate_1 = require("../migrate");
8
+ const mcp_error_1 = require("../../errors/mcp.error");
8
9
  /**
9
10
  * Decorator that marks a class as a FrontMcp Server and provides metadata
10
11
  */
@@ -34,22 +35,18 @@ function FrontMcp(providedMetadata) {
34
35
  const isServerless = typeof process !== 'undefined' && process.env?.['FRONTMCP_SERVERLESS'] === '1';
35
36
  if (isServerless) {
36
37
  // Serverless mode: bootstrap, prepare (no listen), store handler globally
37
- const sdk = '@frontmcp/sdk';
38
- import(sdk)
39
- .then(({ FrontMcpInstance, setServerlessHandler, setServerlessHandlerPromise, setServerlessHandlerError }) => {
40
- if (!FrontMcpInstance) {
41
- throw new Error(`${sdk} version mismatch, make sure you have the same version for all @frontmcp/* packages`);
42
- }
43
- const handlerPromise = FrontMcpInstance.createHandler(metadata);
44
- setServerlessHandlerPromise(handlerPromise);
45
- handlerPromise.then(setServerlessHandler).catch((err) => {
46
- const e = err instanceof Error ? err : new Error(String(err));
47
- setServerlessHandlerError(e);
48
- console.error('[FrontMCP] Serverless initialization failed:', e);
49
- });
50
- })
51
- .catch((err) => {
52
- console.error('[FrontMCP] Failed to import @frontmcp/sdk for serverless init:', err);
38
+ // Use synchronous require for bundler compatibility (rspack/webpack)
39
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
40
+ const { FrontMcpInstance: ServerlessInstance, setServerlessHandler, setServerlessHandlerPromise, setServerlessHandlerError, } = require('@frontmcp/sdk');
41
+ if (!ServerlessInstance) {
42
+ throw new mcp_error_1.InternalMcpError('@frontmcp/sdk version mismatch, make sure you have the same version for all @frontmcp/* packages', 'SDK_VERSION_MISMATCH');
43
+ }
44
+ const handlerPromise = ServerlessInstance.createHandler(metadata);
45
+ setServerlessHandlerPromise(handlerPromise);
46
+ handlerPromise.then(setServerlessHandler).catch((err) => {
47
+ const e = err instanceof Error ? err : new mcp_error_1.InternalMcpError(String(err), 'SERVERLESS_INIT_FAILED');
48
+ setServerlessHandlerError(e);
49
+ console.error('[FrontMCP] Serverless initialization failed:', e);
53
50
  });
54
51
  }
55
52
  else if (metadata.serve) {
@@ -57,7 +54,7 @@ function FrontMcp(providedMetadata) {
57
54
  const sdk = '@frontmcp/sdk';
58
55
  import(sdk).then(({ FrontMcpInstance }) => {
59
56
  if (!FrontMcpInstance) {
60
- throw new Error(`${sdk} version mismatch, make sure you have the same version for all @frontmcp/* packages`);
57
+ throw new mcp_error_1.InternalMcpError(`${sdk} version mismatch, make sure you have the same version for all @frontmcp/* packages`, 'SDK_VERSION_MISMATCH');
61
58
  }
62
59
  FrontMcpInstance.bootstrap(metadata);
63
60
  });
@@ -1 +1 @@
1
- {"version":3,"file":"front-mcp.decorator.js","sourceRoot":"","sources":["../../../../src/common/decorators/front-mcp.decorator.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport { FrontMcpTokens } from '../tokens';\nimport { FrontMcpMetadata, frontMcpMetadataSchema } from '../metadata';\nimport { FrontMcpInstance } from '../../front-mcp';\nimport { applyMigration } from '../migrate';\n\n/**\n * Decorator that marks a class as a FrontMcp Server and provides metadata\n */\nexport function FrontMcp(providedMetadata: FrontMcpMetadata): ClassDecorator {\n return (target: Function) => {\n // Apply migration for deprecated auth.transport and session configs\n const migratedMetadata = applyMigration(providedMetadata);\n const { error, data: metadata } = frontMcpMetadataSchema.safeParse(migratedMetadata);\n if (error) {\n if (error.format().apps) {\n throw new Error(\n `Invalid metadata provided to @FrontMcp { apps: [?] }: \\n${JSON.stringify(error.format().apps, null, 2)}`,\n );\n }\n if (error.format().providers) {\n throw new Error(\n `Invalid metadata provided to @FrontMcp { providers: [?] }: \\n${JSON.stringify(\n error.format().providers,\n null,\n 2,\n )}`,\n );\n }\n const loggingFormat = error.format()['logging'] as Record<string, unknown> | undefined;\n if (loggingFormat?.['transports']) {\n throw new Error(\n `Invalid metadata provided to @FrontMcp { logging: { transports: [?] } }: \\n${JSON.stringify(\n loggingFormat['transports'],\n null,\n 2,\n )}`,\n );\n }\n throw error;\n }\n\n Reflect.defineMetadata(FrontMcpTokens.type, true, target);\n for (const property in metadata) {\n Reflect.defineMetadata(FrontMcpTokens[property] ?? property, metadata[property], target);\n }\n\n // Safe check for serverless mode - process.env may not exist in Cloudflare Workers\n const isServerless = typeof process !== 'undefined' && process.env?.['FRONTMCP_SERVERLESS'] === '1';\n\n if (isServerless) {\n // Serverless mode: bootstrap, prepare (no listen), store handler globally\n const sdk = '@frontmcp/sdk';\n import(sdk)\n .then(({ FrontMcpInstance, setServerlessHandler, setServerlessHandlerPromise, setServerlessHandlerError }) => {\n if (!FrontMcpInstance) {\n throw new Error(\n `${sdk} version mismatch, make sure you have the same version for all @frontmcp/* packages`,\n );\n }\n\n const handlerPromise = FrontMcpInstance.createHandler(metadata);\n setServerlessHandlerPromise(handlerPromise);\n handlerPromise.then(setServerlessHandler).catch((err: unknown) => {\n const e = err instanceof Error ? err : new Error(String(err));\n setServerlessHandlerError(e);\n console.error('[FrontMCP] Serverless initialization failed:', e);\n });\n })\n .catch((err: unknown) => {\n console.error('[FrontMCP] Failed to import @frontmcp/sdk for serverless init:', err);\n });\n } else if (metadata.serve) {\n // Normal mode: bootstrap and start server\n const sdk = '@frontmcp/sdk';\n import(sdk).then(({ FrontMcpInstance }) => {\n if (!FrontMcpInstance) {\n throw new Error(`${sdk} version mismatch, make sure you have the same version for all @frontmcp/* packages`);\n }\n\n FrontMcpInstance.bootstrap(metadata);\n });\n }\n };\n}\n"]}
1
+ {"version":3,"file":"front-mcp.decorator.js","sourceRoot":"","sources":["../../../../src/common/decorators/front-mcp.decorator.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport { FrontMcpTokens } from '../tokens';\nimport { FrontMcpMetadata, frontMcpMetadataSchema } from '../metadata';\nimport { FrontMcpInstance } from '../../front-mcp';\nimport { applyMigration } from '../migrate';\nimport { InternalMcpError } from '../../errors/mcp.error';\n\n/**\n * Decorator that marks a class as a FrontMcp Server and provides metadata\n */\nexport function FrontMcp(providedMetadata: FrontMcpMetadata): ClassDecorator {\n return (target: Function) => {\n // Apply migration for deprecated auth.transport and session configs\n const migratedMetadata = applyMigration(providedMetadata);\n const { error, data: metadata } = frontMcpMetadataSchema.safeParse(migratedMetadata);\n if (error) {\n if (error.format().apps) {\n throw new Error(\n `Invalid metadata provided to @FrontMcp { apps: [?] }: \\n${JSON.stringify(error.format().apps, null, 2)}`,\n );\n }\n if (error.format().providers) {\n throw new Error(\n `Invalid metadata provided to @FrontMcp { providers: [?] }: \\n${JSON.stringify(\n error.format().providers,\n null,\n 2,\n )}`,\n );\n }\n const loggingFormat = error.format()['logging'] as Record<string, unknown> | undefined;\n if (loggingFormat?.['transports']) {\n throw new Error(\n `Invalid metadata provided to @FrontMcp { logging: { transports: [?] } }: \\n${JSON.stringify(\n loggingFormat['transports'],\n null,\n 2,\n )}`,\n );\n }\n throw error;\n }\n\n Reflect.defineMetadata(FrontMcpTokens.type, true, target);\n for (const property in metadata) {\n Reflect.defineMetadata(FrontMcpTokens[property] ?? property, metadata[property], target);\n }\n\n // Safe check for serverless mode - process.env may not exist in Cloudflare Workers\n const isServerless = typeof process !== 'undefined' && process.env?.['FRONTMCP_SERVERLESS'] === '1';\n\n if (isServerless) {\n // Serverless mode: bootstrap, prepare (no listen), store handler globally\n // Use synchronous require for bundler compatibility (rspack/webpack)\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const {\n FrontMcpInstance: ServerlessInstance,\n setServerlessHandler,\n setServerlessHandlerPromise,\n setServerlessHandlerError,\n }: {\n FrontMcpInstance: typeof FrontMcpInstance;\n setServerlessHandler: (handler: unknown) => void;\n setServerlessHandlerPromise: (promise: Promise<unknown>) => void;\n setServerlessHandlerError: (error: Error) => void;\n } = require('@frontmcp/sdk');\n\n if (!ServerlessInstance) {\n throw new InternalMcpError(\n '@frontmcp/sdk version mismatch, make sure you have the same version for all @frontmcp/* packages',\n 'SDK_VERSION_MISMATCH',\n );\n }\n\n const handlerPromise = ServerlessInstance.createHandler(metadata);\n setServerlessHandlerPromise(handlerPromise);\n handlerPromise.then(setServerlessHandler).catch((err: unknown) => {\n const e = err instanceof Error ? err : new InternalMcpError(String(err), 'SERVERLESS_INIT_FAILED');\n setServerlessHandlerError(e);\n console.error('[FrontMCP] Serverless initialization failed:', e);\n });\n } else if (metadata.serve) {\n // Normal mode: bootstrap and start server\n const sdk = '@frontmcp/sdk';\n import(sdk).then(({ FrontMcpInstance }) => {\n if (!FrontMcpInstance) {\n throw new InternalMcpError(\n `${sdk} version mismatch, make sure you have the same version for all @frontmcp/* packages`,\n 'SDK_VERSION_MISMATCH',\n );\n }\n\n FrontMcpInstance.bootstrap(metadata);\n });\n }\n };\n}\n"]}