@mcp-ts/sdk 2.3.4 → 2.4.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 (89) hide show
  1. package/README.md +161 -39
  2. package/dist/adapters/agui-adapter.d.mts +4 -4
  3. package/dist/adapters/agui-adapter.d.ts +4 -4
  4. package/dist/adapters/agui-middleware.d.mts +4 -4
  5. package/dist/adapters/agui-middleware.d.ts +4 -4
  6. package/dist/adapters/ai-adapter.d.mts +4 -4
  7. package/dist/adapters/ai-adapter.d.ts +4 -4
  8. package/dist/adapters/langchain-adapter.d.mts +4 -4
  9. package/dist/adapters/langchain-adapter.d.ts +4 -4
  10. package/dist/adapters/mastra-adapter.d.mts +2 -2
  11. package/dist/adapters/mastra-adapter.d.ts +2 -2
  12. package/dist/client/index.d.mts +3 -3
  13. package/dist/client/index.d.ts +3 -3
  14. package/dist/client/index.js +2 -2
  15. package/dist/client/index.js.map +1 -1
  16. package/dist/client/index.mjs +2 -2
  17. package/dist/client/index.mjs.map +1 -1
  18. package/dist/client/react.d.mts +11 -10
  19. package/dist/client/react.d.ts +11 -10
  20. package/dist/client/react.js +66 -26
  21. package/dist/client/react.js.map +1 -1
  22. package/dist/client/react.mjs +67 -27
  23. package/dist/client/react.mjs.map +1 -1
  24. package/dist/client/vue.d.mts +8 -7
  25. package/dist/client/vue.d.ts +8 -7
  26. package/dist/client/vue.js +27 -17
  27. package/dist/client/vue.js.map +1 -1
  28. package/dist/client/vue.mjs +27 -17
  29. package/dist/client/vue.mjs.map +1 -1
  30. package/dist/{events-CK3N--3g.d.mts → events-C4m7tK1U.d.mts} +0 -1
  31. package/dist/{events-CK3N--3g.d.ts → events-C4m7tK1U.d.ts} +0 -1
  32. package/dist/{index-CmjMd2ac.d.ts → index-Ch7ouNSa.d.ts} +3 -3
  33. package/dist/{index-Cfjsme-a.d.mts → index-L5XoXgsb.d.mts} +3 -3
  34. package/dist/index.d.mts +6 -6
  35. package/dist/index.d.ts +6 -6
  36. package/dist/index.js +799 -326
  37. package/dist/index.js.map +1 -1
  38. package/dist/index.mjs +793 -324
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/{multi-session-client-C_kPHpD5.d.ts → multi-session-client-Bwm-efqg.d.ts} +43 -38
  41. package/dist/{multi-session-client-C7hGqzgM.d.mts → multi-session-client-k-9RvWvi.d.mts} +43 -38
  42. package/dist/server/index.d.mts +48 -20
  43. package/dist/server/index.d.ts +48 -20
  44. package/dist/server/index.js +791 -320
  45. package/dist/server/index.js.map +1 -1
  46. package/dist/server/index.mjs +787 -319
  47. package/dist/server/index.mjs.map +1 -1
  48. package/dist/shared/index.d.mts +10 -9
  49. package/dist/shared/index.d.ts +10 -9
  50. package/dist/shared/index.js +7 -5
  51. package/dist/shared/index.js.map +1 -1
  52. package/dist/shared/index.mjs +5 -4
  53. package/dist/shared/index.mjs.map +1 -1
  54. package/dist/{tool-router-BMzhoNYt.d.ts → tool-router-CZMrOG8J.d.ts} +1 -1
  55. package/dist/{tool-router-BhHsvBfP.d.mts → tool-router-CuApsDiV.d.mts} +1 -1
  56. package/dist/{types-CxFaaZrM.d.mts → types-DCk_IF4L.d.mts} +5 -3
  57. package/dist/{types-CxFaaZrM.d.ts → types-DCk_IF4L.d.ts} +5 -3
  58. package/migrations/neon/20260513010000_install_mcp_sessions.sql +40 -3
  59. package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +4 -4
  60. package/migrations/supabase/20260330195700_install_mcp_sessions.sql +67 -3
  61. package/migrations/supabase/20260421010000_add_session_cleanup_cron.sql +6 -6
  62. package/migrations/v2.3.4/neon/20260513010000_install_mcp_sessions.sql +69 -0
  63. package/migrations/v2.3.4/neon/20260513020000_add_session_cleanup_cron.sql +35 -0
  64. package/migrations/v2.3.4/supabase/20260330195700_install_mcp_sessions.sql +82 -0
  65. package/migrations/v2.3.4/supabase/20260421010000_add_session_cleanup_cron.sql +32 -0
  66. package/package.json +13 -3
  67. package/src/client/core/sse-client.ts +2 -2
  68. package/src/client/react/index.ts +1 -1
  69. package/src/client/react/oauth-popup.tsx +34 -13
  70. package/src/client/react/use-mcp.ts +19 -45
  71. package/src/client/utils/session-state.ts +43 -0
  72. package/src/client/vue/use-mcp.ts +18 -47
  73. package/src/server/handlers/sse-handler.ts +16 -9
  74. package/src/server/mcp/multi-session-client.ts +48 -11
  75. package/src/server/mcp/oauth-client.ts +133 -111
  76. package/src/server/mcp/storage-oauth-provider.ts +79 -50
  77. package/src/server/storage/file-backend.ts +74 -18
  78. package/src/server/storage/index.ts +2 -4
  79. package/src/server/storage/memory-backend.ts +49 -13
  80. package/src/server/storage/neon-backend.ts +201 -68
  81. package/src/server/storage/redis-backend.ts +81 -23
  82. package/src/server/storage/session-lifecycle.ts +78 -0
  83. package/src/server/storage/sqlite-backend.ts +89 -15
  84. package/src/server/storage/supabase-backend.ts +188 -63
  85. package/src/server/storage/types.ts +49 -16
  86. package/src/shared/constants.ts +5 -6
  87. package/src/shared/events.ts +0 -1
  88. package/src/shared/types.ts +5 -3
  89. package/src/shared/utils.ts +26 -0
@@ -1,7 +1,13 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import * as path from 'path';
3
- import type { SessionStore, Session, SetClientOptions } from './types.js';
3
+ import type { SessionStore, Session, SessionCredentials } from './types.js';
4
4
  import { generateSessionId } from '../../shared/utils.js';
5
+ import {
6
+ mergeSessionUpdate,
7
+ normalizeNewSession,
8
+ normalizeStoredSession,
9
+ isSessionExpired,
10
+ } from './session-lifecycle.js';
5
11
 
6
12
  /**
7
13
  * File system implementation of SessionStore
@@ -10,6 +16,7 @@ import { generateSessionId } from '../../shared/utils.js';
10
16
  export class FileStorageBackend implements SessionStore {
11
17
  private filePath: string;
12
18
  private memoryCache: Map<string, Session> | null = null;
19
+ private credentialsCache: Map<string, SessionCredentials> | null = null;
13
20
  private initialized = false;
14
21
 
15
22
  /**
@@ -35,15 +42,23 @@ export class FileStorageBackend implements SessionStore {
35
42
  const json = JSON.parse(data);
36
43
 
37
44
  this.memoryCache = new Map();
38
- if (Array.isArray(json)) {
39
- json.forEach((s: Session) => {
40
- this.memoryCache!.set(this.getSessionKey(s.userId || 'unknown', s.sessionId), s);
45
+ this.credentialsCache = new Map();
46
+ if (Array.isArray(json.sessions)) {
47
+ json.sessions.forEach((s: Session) => {
48
+ const session = normalizeStoredSession(s);
49
+ this.memoryCache!.set(this.getSessionKey(session.userId || 'unknown', session.sessionId), session);
50
+ });
51
+ }
52
+ if (Array.isArray(json.credentials)) {
53
+ json.credentials.forEach((c: SessionCredentials) => {
54
+ this.credentialsCache!.set(this.getSessionKey(c.userId, c.sessionId), c);
41
55
  });
42
56
  }
43
57
  } catch (error: any) {
44
58
  if (error.code === 'ENOENT') {
45
59
  // File does not exist, initialize empty
46
60
  this.memoryCache = new Map();
61
+ this.credentialsCache = new Map();
47
62
  await this.flush();
48
63
  } else {
49
64
  console.error('[FileStorage] Failed to load sessions:', error);
@@ -60,9 +75,11 @@ export class FileStorageBackend implements SessionStore {
60
75
  }
61
76
 
62
77
  private async flush(): Promise<void> {
63
- if (!this.memoryCache) return;
64
- const sessions = Array.from(this.memoryCache.values());
65
- await fs.writeFile(this.filePath, JSON.stringify(sessions, null, 2), 'utf-8');
78
+ if (!this.memoryCache || !this.credentialsCache) return;
79
+ await fs.writeFile(this.filePath, JSON.stringify({
80
+ sessions: Array.from(this.memoryCache.values()),
81
+ credentials: Array.from(this.credentialsCache.values()),
82
+ }, null, 2), 'utf-8');
66
83
  }
67
84
 
68
85
  private getSessionKey(userId: string, sessionId: string): string {
@@ -73,7 +90,7 @@ export class FileStorageBackend implements SessionStore {
73
90
  return generateSessionId();
74
91
  }
75
92
 
76
- async create(session: Session, ttl?: number): Promise<void> {
93
+ async create(session: Session): Promise<void> {
77
94
  await this.ensureInitialized();
78
95
  const { sessionId, userId } = session;
79
96
  if (!sessionId || !userId) throw new Error('userId and sessionId required');
@@ -83,12 +100,11 @@ export class FileStorageBackend implements SessionStore {
83
100
  throw new Error(`Session ${sessionId} already exists`);
84
101
  }
85
102
 
86
- this.memoryCache!.set(sessionKey, session);
103
+ this.memoryCache!.set(sessionKey, normalizeNewSession(session));
87
104
  await this.flush();
88
- // Note: TTL is ignored in file backend - sessions don't auto-expire
89
105
  }
90
106
 
91
- async update(userId: string, sessionId: string, data: Partial<Session>, ttl?: number): Promise<void> {
107
+ async update(userId: string, sessionId: string, data: Partial<Session>): Promise<void> {
92
108
  await this.ensureInitialized();
93
109
  if (!userId || !sessionId) throw new Error('userId and sessionId required');
94
110
 
@@ -99,14 +115,22 @@ export class FileStorageBackend implements SessionStore {
99
115
  throw new Error(`Session ${sessionId} not found`);
100
116
  }
101
117
 
102
- const updated = {
103
- ...current,
104
- ...data
105
- };
118
+ const updated = mergeSessionUpdate(current, data);
106
119
 
107
120
  this.memoryCache!.set(sessionKey, updated);
108
121
  await this.flush();
109
- // Note: TTL is ignored in file backend - sessions don't auto-expire
122
+ }
123
+
124
+ async patchCredentials(userId: string, sessionId: string, data: Partial<SessionCredentials>): Promise<void> {
125
+ await this.ensureInitialized();
126
+ const sessionKey = this.getSessionKey(userId, sessionId);
127
+ if (!this.memoryCache!.has(sessionKey)) {
128
+ throw new Error(`Session ${sessionId} not found`);
129
+ }
130
+
131
+ const current = this.credentialsCache!.get(sessionKey) ?? { sessionId, userId };
132
+ this.credentialsCache!.set(sessionKey, { ...current, ...data, sessionId, userId });
133
+ await this.flush();
110
134
  }
111
135
 
112
136
  async get(userId: string, sessionId: string): Promise<Session | null> {
@@ -115,6 +139,23 @@ export class FileStorageBackend implements SessionStore {
115
139
  return this.memoryCache!.get(sessionKey) || null;
116
140
  }
117
141
 
142
+ async getCredentials(userId: string, sessionId: string): Promise<SessionCredentials | null> {
143
+ await this.ensureInitialized();
144
+ const sessionKey = this.getSessionKey(userId, sessionId);
145
+ if (!this.memoryCache!.has(sessionKey)) return null;
146
+ return this.credentialsCache!.get(sessionKey) ?? { sessionId, userId };
147
+ }
148
+
149
+ async clearCredentials(userId: string, sessionId: string): Promise<void> {
150
+ await this.patchCredentials(userId, sessionId, {
151
+ clientInformation: null,
152
+ tokens: null,
153
+ codeVerifier: null,
154
+ clientId: null,
155
+ oauthState: null,
156
+ });
157
+ }
158
+
118
159
  async list(userId: string): Promise<Session[]> {
119
160
  await this.ensureInitialized();
120
161
  return Array.from(this.memoryCache!.values()).filter(s => s.userId === userId);
@@ -130,7 +171,9 @@ export class FileStorageBackend implements SessionStore {
130
171
  async delete(userId: string, sessionId: string): Promise<void> {
131
172
  await this.ensureInitialized();
132
173
  const sessionKey = this.getSessionKey(userId, sessionId);
133
- if (this.memoryCache!.delete(sessionKey)) {
174
+ const deleted = this.memoryCache!.delete(sessionKey);
175
+ this.credentialsCache!.delete(sessionKey);
176
+ if (deleted) {
134
177
  await this.flush();
135
178
  }
136
179
  }
@@ -143,12 +186,25 @@ export class FileStorageBackend implements SessionStore {
143
186
  async clearAll(): Promise<void> {
144
187
  await this.ensureInitialized();
145
188
  this.memoryCache!.clear();
189
+ this.credentialsCache!.clear();
146
190
  await this.flush();
147
191
  }
148
192
 
149
193
  async cleanupExpired(): Promise<void> {
150
- // Could implement TTL check here using createdAt
151
194
  await this.ensureInitialized();
195
+ let changed = false;
196
+
197
+ for (const [key, session] of this.memoryCache!.entries()) {
198
+ if (!isSessionExpired(session)) continue;
199
+
200
+ this.memoryCache!.delete(key);
201
+ this.credentialsCache!.delete(key);
202
+ changed = true;
203
+ }
204
+
205
+ if (changed) {
206
+ await this.flush();
207
+ }
152
208
  }
153
209
 
154
210
  async disconnect(): Promise<void> {
@@ -63,27 +63,25 @@ function createSessionMutationEvent(prop: PropertyKey, args: any[]): SessionMuta
63
63
  const timestamp = Date.now();
64
64
 
65
65
  if (prop === 'create') {
66
- const [session, ttl] = args as [Session, number | undefined];
66
+ const [session] = args as [Session];
67
67
  if (!session?.userId || !session?.sessionId) return null;
68
68
  return {
69
69
  type: 'create',
70
70
  userId: session.userId,
71
71
  sessionId: session.sessionId,
72
72
  session,
73
- ttl,
74
73
  timestamp,
75
74
  };
76
75
  }
77
76
 
78
77
  if (prop === 'update') {
79
- const [userId, sessionId, patch, ttl] = args as [string, string, Partial<Session>, number | undefined];
78
+ const [userId, sessionId, patch] = args as [string, string, Partial<Session>];
80
79
  if (!userId || !sessionId) return null;
81
80
  return {
82
81
  type: 'update',
83
82
  userId,
84
83
  sessionId,
85
84
  patch,
86
- ttl,
87
85
  timestamp,
88
86
  };
89
87
  }
@@ -1,5 +1,6 @@
1
- import type { SessionStore, Session, SetClientOptions } from './types.js';
1
+ import type { SessionStore, Session, SessionCredentials } from './types.js';
2
2
  import { generateSessionId } from '../../shared/utils.js';
3
+ import { isSessionExpired, mergeSessionUpdate, normalizeNewSession } from './session-lifecycle.js';
3
4
 
4
5
  /**
5
6
  * In-memory implementation of SessionStore
@@ -8,6 +9,7 @@ import { generateSessionId } from '../../shared/utils.js';
8
9
  export class MemoryStorageBackend implements SessionStore {
9
10
  // Map<userId:sessionId, Session>
10
11
  private sessions = new Map<string, Session>();
12
+ private credentials = new Map<string, SessionCredentials>();
11
13
 
12
14
  // Map<userId, Set<sessionId>>
13
15
  private userIdSessions = new Map<string, Set<string>>();
@@ -26,7 +28,7 @@ export class MemoryStorageBackend implements SessionStore {
26
28
  return generateSessionId();
27
29
  }
28
30
 
29
- async create(session: Session, ttl?: number): Promise<void> {
31
+ async create(session: Session): Promise<void> {
30
32
  const { sessionId, userId } = session;
31
33
  if (!sessionId || !userId) throw new Error('userId and sessionId required');
32
34
 
@@ -35,17 +37,16 @@ export class MemoryStorageBackend implements SessionStore {
35
37
  throw new Error(`Session ${sessionId} already exists`);
36
38
  }
37
39
 
38
- this.sessions.set(sessionKey, session);
40
+ this.sessions.set(sessionKey, normalizeNewSession(session));
39
41
 
40
42
  // Update index
41
43
  if (!this.userIdSessions.has(userId)) {
42
44
  this.userIdSessions.set(userId, new Set());
43
45
  }
44
46
  this.userIdSessions.get(userId)!.add(sessionId);
45
- // Note: TTL is ignored in memory backend - sessions don't auto-expire
46
47
  }
47
48
 
48
- async update(userId: string, sessionId: string, data: Partial<Session>, ttl?: number): Promise<void> {
49
+ async update(userId: string, sessionId: string, data: Partial<Session>): Promise<void> {
49
50
  if (!userId || !sessionId) throw new Error('userId and sessionId required');
50
51
 
51
52
  const sessionKey = this.getSessionKey(userId, sessionId);
@@ -55,13 +56,19 @@ export class MemoryStorageBackend implements SessionStore {
55
56
  throw new Error(`Session ${sessionId} not found`);
56
57
  }
57
58
 
58
- const updated = {
59
- ...current,
60
- ...data
61
- };
59
+ const updated = mergeSessionUpdate(current, data);
62
60
 
63
61
  this.sessions.set(sessionKey, updated);
64
- // Note: TTL is ignored in memory backend - sessions don't auto-expire
62
+ }
63
+
64
+ async patchCredentials(userId: string, sessionId: string, data: Partial<SessionCredentials>): Promise<void> {
65
+ const sessionKey = this.getSessionKey(userId, sessionId);
66
+ if (!this.sessions.has(sessionKey)) {
67
+ throw new Error(`Session ${sessionId} not found`);
68
+ }
69
+
70
+ const current = this.credentials.get(sessionKey) ?? { sessionId, userId };
71
+ this.credentials.set(sessionKey, { ...current, ...data, sessionId, userId });
65
72
  }
66
73
 
67
74
 
@@ -70,6 +77,22 @@ export class MemoryStorageBackend implements SessionStore {
70
77
  return this.sessions.get(sessionKey) || null;
71
78
  }
72
79
 
80
+ async getCredentials(userId: string, sessionId: string): Promise<SessionCredentials | null> {
81
+ const sessionKey = this.getSessionKey(userId, sessionId);
82
+ if (!this.sessions.has(sessionKey)) return null;
83
+ return this.credentials.get(sessionKey) ?? { sessionId, userId };
84
+ }
85
+
86
+ async clearCredentials(userId: string, sessionId: string): Promise<void> {
87
+ await this.patchCredentials(userId, sessionId, {
88
+ clientInformation: null,
89
+ tokens: null,
90
+ codeVerifier: null,
91
+ clientId: null,
92
+ oauthState: null,
93
+ });
94
+ }
95
+
73
96
  async listIds(userId: string): Promise<string[]> {
74
97
  const set = this.userIdSessions.get(userId);
75
98
  return set ? Array.from(set) : [];
@@ -92,6 +115,7 @@ export class MemoryStorageBackend implements SessionStore {
92
115
  async delete(userId: string, sessionId: string): Promise<void> {
93
116
  const sessionKey = this.getSessionKey(userId, sessionId);
94
117
  this.sessions.delete(sessionKey);
118
+ this.credentials.delete(sessionKey);
95
119
 
96
120
  const set = this.userIdSessions.get(userId);
97
121
  if (set) {
@@ -108,13 +132,25 @@ export class MemoryStorageBackend implements SessionStore {
108
132
 
109
133
  async clearAll(): Promise<void> {
110
134
  this.sessions.clear();
135
+ this.credentials.clear();
111
136
  this.userIdSessions.clear();
112
137
  }
113
138
 
114
139
  async cleanupExpired(): Promise<void> {
115
- // In-memory doesn't implement TTL automatically,
116
- // but we could check createdAt + TTL here if needed.
117
- // For now, no-op.
140
+ for (const [key, session] of this.sessions.entries()) {
141
+ if (!isSessionExpired(session)) continue;
142
+
143
+ this.sessions.delete(key);
144
+ this.credentials.delete(key);
145
+
146
+ const set = this.userIdSessions.get(session.userId);
147
+ if (set) {
148
+ set.delete(session.sessionId);
149
+ if (set.size === 0) {
150
+ this.userIdSessions.delete(session.userId);
151
+ }
152
+ }
153
+ }
118
154
  }
119
155
 
120
156
  async disconnect(): Promise<void> {