@mcp-ts/sdk 2.3.4 → 2.4.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 +113 -30
- package/dist/adapters/agui-adapter.d.mts +3 -3
- package/dist/adapters/agui-adapter.d.ts +3 -3
- package/dist/adapters/agui-middleware.d.mts +3 -3
- package/dist/adapters/agui-middleware.d.ts +3 -3
- package/dist/adapters/ai-adapter.d.mts +3 -3
- package/dist/adapters/ai-adapter.d.ts +3 -3
- package/dist/adapters/langchain-adapter.d.mts +3 -3
- package/dist/adapters/langchain-adapter.d.ts +3 -3
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +2 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +2 -2
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +9 -8
- package/dist/client/react.d.ts +9 -8
- package/dist/client/react.js +66 -26
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +67 -27
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +6 -5
- package/dist/client/vue.d.ts +6 -5
- package/dist/client/vue.js +27 -17
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +27 -17
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-Cfjsme-a.d.mts → index-ByIjEReo.d.mts} +2 -2
- package/dist/{index-CmjMd2ac.d.ts → index-CtXvKl8N.d.ts} +2 -2
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +728 -306
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +722 -304
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-C7hGqzgM.d.mts → multi-session-client-CIMUGF8S.d.mts} +15 -33
- package/dist/{multi-session-client-C_kPHpD5.d.ts → multi-session-client-CnvZEGPY.d.ts} +15 -33
- package/dist/server/index.d.mts +45 -17
- package/dist/server/index.d.ts +45 -17
- package/dist/server/index.js +720 -300
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +716 -299
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +9 -8
- package/dist/shared/index.d.ts +9 -8
- package/dist/shared/index.js +7 -5
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +5 -4
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-BMzhoNYt.d.ts → tool-router-CZMrOG8J.d.ts} +1 -1
- package/dist/{tool-router-BhHsvBfP.d.mts → tool-router-CuApsDiV.d.mts} +1 -1
- package/dist/{types-CxFaaZrM.d.mts → types-DCk_IF4L.d.mts} +5 -3
- package/dist/{types-CxFaaZrM.d.ts → types-DCk_IF4L.d.ts} +5 -3
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +40 -3
- package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +4 -4
- package/migrations/supabase/20260330195700_install_mcp_sessions.sql +67 -3
- package/migrations/supabase/20260421010000_add_session_cleanup_cron.sql +6 -6
- package/package.json +13 -3
- package/src/client/core/sse-client.ts +2 -2
- package/src/client/react/index.ts +1 -1
- package/src/client/react/oauth-popup.tsx +34 -13
- package/src/client/react/use-mcp.ts +19 -45
- package/src/client/utils/session-state.ts +43 -0
- package/src/client/vue/use-mcp.ts +18 -47
- package/src/server/handlers/sse-handler.ts +9 -4
- package/src/server/mcp/multi-session-client.ts +2 -6
- package/src/server/mcp/oauth-client.ts +73 -101
- package/src/server/mcp/storage-oauth-provider.ts +79 -50
- package/src/server/storage/file-backend.ts +74 -18
- package/src/server/storage/index.ts +2 -4
- package/src/server/storage/memory-backend.ts +49 -13
- package/src/server/storage/neon-backend.ts +201 -68
- package/src/server/storage/redis-backend.ts +81 -23
- package/src/server/storage/session-lifecycle.ts +78 -0
- package/src/server/storage/sqlite-backend.ts +89 -15
- package/src/server/storage/supabase-backend.ts +188 -63
- package/src/server/storage/types.ts +49 -16
- package/src/shared/constants.ts +5 -6
- package/src/shared/types.ts +5 -3
- 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,
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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> {
|