@mcp-ts/sdk 1.3.7 → 1.3.9
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/LICENSE +21 -21
- package/README.md +398 -404
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/bin/mcp-ts.js +0 -0
- package/dist/bin/mcp-ts.js.map +1 -1
- package/dist/bin/mcp-ts.mjs +0 -0
- package/dist/bin/mcp-ts.mjs.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +2 -2
- package/dist/client/react.d.ts +2 -2
- package/dist/client/react.js +25 -2
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +26 -3
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/package.json +185 -185
- package/src/adapters/agui-middleware.ts +382 -382
- package/src/bin/mcp-ts.ts +102 -102
- package/src/client/core/app-host.ts +417 -417
- package/src/client/core/sse-client.ts +371 -371
- package/src/client/core/types.ts +31 -31
- package/src/client/index.ts +27 -27
- package/src/client/react/index.ts +16 -16
- package/src/client/react/use-app-host.ts +73 -73
- package/src/client/react/use-mcp-apps.tsx +247 -214
- package/src/client/react/use-mcp.ts +641 -641
- package/src/client/vue/index.ts +10 -10
- package/src/client/vue/use-mcp.ts +617 -617
- package/src/index.ts +11 -11
- package/src/server/handlers/nextjs-handler.ts +204 -204
- package/src/server/handlers/sse-handler.ts +631 -631
- package/src/server/index.ts +57 -57
- package/src/server/mcp/multi-session-client.ts +228 -228
- package/src/server/mcp/oauth-client.ts +1188 -1188
- package/src/server/mcp/storage-oauth-provider.ts +272 -272
- package/src/server/storage/file-backend.ts +157 -157
- package/src/server/storage/index.ts +176 -176
- package/src/server/storage/memory-backend.ts +123 -123
- package/src/server/storage/redis-backend.ts +276 -276
- package/src/server/storage/redis.ts +160 -160
- package/src/server/storage/sqlite-backend.ts +182 -182
- package/src/server/storage/supabase-backend.ts +228 -228
- package/src/server/storage/types.ts +116 -116
- package/src/shared/constants.ts +29 -29
- package/src/shared/errors.ts +133 -133
- package/src/shared/event-routing.ts +28 -28
- package/src/shared/events.ts +180 -180
- package/src/shared/index.ts +75 -75
- package/src/shared/tool-utils.ts +61 -61
- package/src/shared/types.ts +282 -282
- package/src/shared/utils.ts +38 -38
- package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -84
|
@@ -1,228 +1,228 @@
|
|
|
1
|
-
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
import { StorageBackend, SessionData } from './types.js';
|
|
3
|
-
import { SESSION_TTL_SECONDS } from '../../shared/constants.js';
|
|
4
|
-
import { generateSessionId } from '../../shared/utils.js';
|
|
5
|
-
|
|
6
|
-
export class SupabaseStorageBackend implements StorageBackend {
|
|
7
|
-
private readonly DEFAULT_TTL = SESSION_TTL_SECONDS;
|
|
8
|
-
|
|
9
|
-
constructor(private supabase: SupabaseClient) {}
|
|
10
|
-
|
|
11
|
-
async init(): Promise<void> {
|
|
12
|
-
// Validate that the table exists
|
|
13
|
-
const { error } = await this.supabase
|
|
14
|
-
.from('mcp_sessions')
|
|
15
|
-
.select('session_id')
|
|
16
|
-
.limit(0);
|
|
17
|
-
|
|
18
|
-
if (error) {
|
|
19
|
-
// Postgres error code 42P01 is "relation does not exist"
|
|
20
|
-
if (error.code === '42P01') {
|
|
21
|
-
throw new Error(
|
|
22
|
-
'[SupabaseStorage] Table "mcp_sessions" not found in your database. ' +
|
|
23
|
-
'Please run "npx mcp-ts supabase-init" in your project to set up the required table and RLS policies.'
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
throw new Error(`[SupabaseStorage] Initialization check failed: ${error.message}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
console.log('[mcp-ts][Storage] Supabase: ✓ "mcp_sessions" table verified.');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
generateSessionId(): string {
|
|
33
|
-
return generateSessionId();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
private mapRowToSessionData(row: any): SessionData {
|
|
37
|
-
return {
|
|
38
|
-
sessionId: row.session_id,
|
|
39
|
-
serverId: row.server_id,
|
|
40
|
-
serverName: row.server_name,
|
|
41
|
-
serverUrl: row.server_url,
|
|
42
|
-
transportType: row.transport_type,
|
|
43
|
-
callbackUrl: row.callback_url,
|
|
44
|
-
createdAt: new Date(row.created_at).getTime(),
|
|
45
|
-
identity: row.identity,
|
|
46
|
-
headers: row.headers,
|
|
47
|
-
active: row.active,
|
|
48
|
-
clientInformation: row.client_information,
|
|
49
|
-
tokens: row.tokens,
|
|
50
|
-
codeVerifier: row.code_verifier,
|
|
51
|
-
clientId: row.client_id,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async createSession(session: SessionData, ttl?: number): Promise<void> {
|
|
56
|
-
const { sessionId, identity } = session;
|
|
57
|
-
if (!sessionId || !identity) throw new Error('identity and sessionId required');
|
|
58
|
-
|
|
59
|
-
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
60
|
-
const expiresAt = new Date(Date.now() + effectiveTtl * 1000).toISOString();
|
|
61
|
-
|
|
62
|
-
const { error } = await this.supabase
|
|
63
|
-
.from('mcp_sessions')
|
|
64
|
-
.insert({
|
|
65
|
-
session_id: sessionId,
|
|
66
|
-
user_id: identity, // Maps user_id to identity to support RLS using auth.uid()
|
|
67
|
-
server_id: session.serverId,
|
|
68
|
-
server_name: session.serverName,
|
|
69
|
-
server_url: session.serverUrl,
|
|
70
|
-
transport_type: session.transportType,
|
|
71
|
-
callback_url: session.callbackUrl,
|
|
72
|
-
created_at: new Date(session.createdAt || Date.now()).toISOString(),
|
|
73
|
-
identity: identity,
|
|
74
|
-
headers: session.headers,
|
|
75
|
-
active: session.active ?? false,
|
|
76
|
-
client_information: session.clientInformation,
|
|
77
|
-
tokens: session.tokens,
|
|
78
|
-
code_verifier: session.codeVerifier,
|
|
79
|
-
client_id: session.clientId,
|
|
80
|
-
expires_at: expiresAt
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
if (error) {
|
|
84
|
-
// Postgres error code 23505 is unique violation
|
|
85
|
-
if (error.code === '23505') {
|
|
86
|
-
throw new Error(`Session ${sessionId} already exists`);
|
|
87
|
-
}
|
|
88
|
-
throw new Error(`Failed to create session in Supabase: ${error.message}`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async updateSession(identity: string, sessionId: string, data: Partial<SessionData>, ttl?: number): Promise<void> {
|
|
93
|
-
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
94
|
-
const expiresAt = new Date(Date.now() + effectiveTtl * 1000).toISOString();
|
|
95
|
-
|
|
96
|
-
// Convert the camelCase keys to snake_case for Supabase
|
|
97
|
-
const updateData: any = {
|
|
98
|
-
expires_at: expiresAt,
|
|
99
|
-
updated_at: new Date().toISOString()
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
if ('serverId' in data) updateData.server_id = data.serverId;
|
|
103
|
-
if ('serverName' in data) updateData.server_name = data.serverName;
|
|
104
|
-
if ('serverUrl' in data) updateData.server_url = data.serverUrl;
|
|
105
|
-
if ('transportType' in data) updateData.transport_type = data.transportType;
|
|
106
|
-
if ('callbackUrl' in data) updateData.callback_url = data.callbackUrl;
|
|
107
|
-
if ('active' in data) updateData.active = data.active;
|
|
108
|
-
if ('headers' in data) updateData.headers = data.headers;
|
|
109
|
-
if ('clientInformation' in data) updateData.client_information = data.clientInformation;
|
|
110
|
-
if ('tokens' in data) updateData.tokens = data.tokens;
|
|
111
|
-
if ('codeVerifier' in data) updateData.code_verifier = data.codeVerifier;
|
|
112
|
-
if ('clientId' in data) updateData.client_id = data.clientId;
|
|
113
|
-
|
|
114
|
-
const { data: updatedRows, error } = await this.supabase
|
|
115
|
-
.from('mcp_sessions')
|
|
116
|
-
.update(updateData)
|
|
117
|
-
.eq('identity', identity)
|
|
118
|
-
.eq('session_id', sessionId)
|
|
119
|
-
.select('id');
|
|
120
|
-
|
|
121
|
-
if (error) {
|
|
122
|
-
throw new Error(`Failed to update session: ${error.message}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (!updatedRows || updatedRows.length === 0) {
|
|
126
|
-
throw new Error(`Session ${sessionId} not found for identity ${identity}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async getSession(identity: string, sessionId: string): Promise<SessionData | null> {
|
|
131
|
-
const { data, error } = await this.supabase
|
|
132
|
-
.from('mcp_sessions')
|
|
133
|
-
.select('*')
|
|
134
|
-
.eq('identity', identity)
|
|
135
|
-
.eq('session_id', sessionId)
|
|
136
|
-
.maybeSingle();
|
|
137
|
-
|
|
138
|
-
if (error) {
|
|
139
|
-
console.error('[SupabaseStorage] Failed to get session:', error);
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (!data) return null;
|
|
144
|
-
|
|
145
|
-
return this.mapRowToSessionData(data);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async getIdentitySessionsData(identity: string): Promise<SessionData[]> {
|
|
149
|
-
const { data, error } = await this.supabase
|
|
150
|
-
.from('mcp_sessions')
|
|
151
|
-
.select('*')
|
|
152
|
-
.eq('identity', identity);
|
|
153
|
-
|
|
154
|
-
if (error) {
|
|
155
|
-
console.error(`[SupabaseStorage] Failed to get session data for ${identity}:`, error);
|
|
156
|
-
return [];
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return data.map(row => this.mapRowToSessionData(row));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
async removeSession(identity: string, sessionId: string): Promise<void> {
|
|
163
|
-
const { error } = await this.supabase
|
|
164
|
-
.from('mcp_sessions')
|
|
165
|
-
.delete()
|
|
166
|
-
.eq('identity', identity)
|
|
167
|
-
.eq('session_id', sessionId);
|
|
168
|
-
|
|
169
|
-
if (error) {
|
|
170
|
-
console.error('[SupabaseStorage] Failed to remove session:', error);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async getIdentityMcpSessions(identity: string): Promise<string[]> {
|
|
175
|
-
const { data, error } = await this.supabase
|
|
176
|
-
.from('mcp_sessions')
|
|
177
|
-
.select('session_id')
|
|
178
|
-
.eq('identity', identity);
|
|
179
|
-
|
|
180
|
-
if (error) {
|
|
181
|
-
console.error(`[SupabaseStorage] Failed to get sessions for ${identity}:`, error);
|
|
182
|
-
return [];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return data.map(row => row.session_id);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async getAllSessionIds(): Promise<string[]> {
|
|
189
|
-
const { data, error } = await this.supabase
|
|
190
|
-
.from('mcp_sessions')
|
|
191
|
-
.select('session_id');
|
|
192
|
-
|
|
193
|
-
if (error) {
|
|
194
|
-
console.error('[SupabaseStorage] Failed to get all sessions:', error);
|
|
195
|
-
return [];
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return data.map(row => row.session_id);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async clearAll(): Promise<void> {
|
|
202
|
-
// Warning: This deletes everything. Typically only used in testing.
|
|
203
|
-
const { error } = await this.supabase
|
|
204
|
-
.from('mcp_sessions')
|
|
205
|
-
.delete()
|
|
206
|
-
.neq('session_id', ''); // Delete all rows trick
|
|
207
|
-
|
|
208
|
-
if (error) {
|
|
209
|
-
console.error('[SupabaseStorage] Failed to clear sessions:', error);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
async cleanupExpiredSessions(): Promise<void> {
|
|
214
|
-
const { error } = await this.supabase
|
|
215
|
-
.from('mcp_sessions')
|
|
216
|
-
.delete()
|
|
217
|
-
.lt('expires_at', new Date().toISOString());
|
|
218
|
-
|
|
219
|
-
if (error) {
|
|
220
|
-
console.error('[SupabaseStorage] Failed to cleanup expired sessions:', error);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async disconnect(): Promise<void> {
|
|
225
|
-
// Supabase client handles its own connection pooling over HTTP,
|
|
226
|
-
// there is no explicit disconnect method.
|
|
227
|
-
}
|
|
228
|
-
}
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { StorageBackend, SessionData } from './types.js';
|
|
3
|
+
import { SESSION_TTL_SECONDS } from '../../shared/constants.js';
|
|
4
|
+
import { generateSessionId } from '../../shared/utils.js';
|
|
5
|
+
|
|
6
|
+
export class SupabaseStorageBackend implements StorageBackend {
|
|
7
|
+
private readonly DEFAULT_TTL = SESSION_TTL_SECONDS;
|
|
8
|
+
|
|
9
|
+
constructor(private supabase: SupabaseClient) {}
|
|
10
|
+
|
|
11
|
+
async init(): Promise<void> {
|
|
12
|
+
// Validate that the table exists
|
|
13
|
+
const { error } = await this.supabase
|
|
14
|
+
.from('mcp_sessions')
|
|
15
|
+
.select('session_id')
|
|
16
|
+
.limit(0);
|
|
17
|
+
|
|
18
|
+
if (error) {
|
|
19
|
+
// Postgres error code 42P01 is "relation does not exist"
|
|
20
|
+
if (error.code === '42P01') {
|
|
21
|
+
throw new Error(
|
|
22
|
+
'[SupabaseStorage] Table "mcp_sessions" not found in your database. ' +
|
|
23
|
+
'Please run "npx mcp-ts supabase-init" in your project to set up the required table and RLS policies.'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`[SupabaseStorage] Initialization check failed: ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log('[mcp-ts][Storage] Supabase: ✓ "mcp_sessions" table verified.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
generateSessionId(): string {
|
|
33
|
+
return generateSessionId();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private mapRowToSessionData(row: any): SessionData {
|
|
37
|
+
return {
|
|
38
|
+
sessionId: row.session_id,
|
|
39
|
+
serverId: row.server_id,
|
|
40
|
+
serverName: row.server_name,
|
|
41
|
+
serverUrl: row.server_url,
|
|
42
|
+
transportType: row.transport_type,
|
|
43
|
+
callbackUrl: row.callback_url,
|
|
44
|
+
createdAt: new Date(row.created_at).getTime(),
|
|
45
|
+
identity: row.identity,
|
|
46
|
+
headers: row.headers,
|
|
47
|
+
active: row.active,
|
|
48
|
+
clientInformation: row.client_information,
|
|
49
|
+
tokens: row.tokens,
|
|
50
|
+
codeVerifier: row.code_verifier,
|
|
51
|
+
clientId: row.client_id,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async createSession(session: SessionData, ttl?: number): Promise<void> {
|
|
56
|
+
const { sessionId, identity } = session;
|
|
57
|
+
if (!sessionId || !identity) throw new Error('identity and sessionId required');
|
|
58
|
+
|
|
59
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
60
|
+
const expiresAt = new Date(Date.now() + effectiveTtl * 1000).toISOString();
|
|
61
|
+
|
|
62
|
+
const { error } = await this.supabase
|
|
63
|
+
.from('mcp_sessions')
|
|
64
|
+
.insert({
|
|
65
|
+
session_id: sessionId,
|
|
66
|
+
user_id: identity, // Maps user_id to identity to support RLS using auth.uid()
|
|
67
|
+
server_id: session.serverId,
|
|
68
|
+
server_name: session.serverName,
|
|
69
|
+
server_url: session.serverUrl,
|
|
70
|
+
transport_type: session.transportType,
|
|
71
|
+
callback_url: session.callbackUrl,
|
|
72
|
+
created_at: new Date(session.createdAt || Date.now()).toISOString(),
|
|
73
|
+
identity: identity,
|
|
74
|
+
headers: session.headers,
|
|
75
|
+
active: session.active ?? false,
|
|
76
|
+
client_information: session.clientInformation,
|
|
77
|
+
tokens: session.tokens,
|
|
78
|
+
code_verifier: session.codeVerifier,
|
|
79
|
+
client_id: session.clientId,
|
|
80
|
+
expires_at: expiresAt
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (error) {
|
|
84
|
+
// Postgres error code 23505 is unique violation
|
|
85
|
+
if (error.code === '23505') {
|
|
86
|
+
throw new Error(`Session ${sessionId} already exists`);
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`Failed to create session in Supabase: ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async updateSession(identity: string, sessionId: string, data: Partial<SessionData>, ttl?: number): Promise<void> {
|
|
93
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
94
|
+
const expiresAt = new Date(Date.now() + effectiveTtl * 1000).toISOString();
|
|
95
|
+
|
|
96
|
+
// Convert the camelCase keys to snake_case for Supabase
|
|
97
|
+
const updateData: any = {
|
|
98
|
+
expires_at: expiresAt,
|
|
99
|
+
updated_at: new Date().toISOString()
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if ('serverId' in data) updateData.server_id = data.serverId;
|
|
103
|
+
if ('serverName' in data) updateData.server_name = data.serverName;
|
|
104
|
+
if ('serverUrl' in data) updateData.server_url = data.serverUrl;
|
|
105
|
+
if ('transportType' in data) updateData.transport_type = data.transportType;
|
|
106
|
+
if ('callbackUrl' in data) updateData.callback_url = data.callbackUrl;
|
|
107
|
+
if ('active' in data) updateData.active = data.active;
|
|
108
|
+
if ('headers' in data) updateData.headers = data.headers;
|
|
109
|
+
if ('clientInformation' in data) updateData.client_information = data.clientInformation;
|
|
110
|
+
if ('tokens' in data) updateData.tokens = data.tokens;
|
|
111
|
+
if ('codeVerifier' in data) updateData.code_verifier = data.codeVerifier;
|
|
112
|
+
if ('clientId' in data) updateData.client_id = data.clientId;
|
|
113
|
+
|
|
114
|
+
const { data: updatedRows, error } = await this.supabase
|
|
115
|
+
.from('mcp_sessions')
|
|
116
|
+
.update(updateData)
|
|
117
|
+
.eq('identity', identity)
|
|
118
|
+
.eq('session_id', sessionId)
|
|
119
|
+
.select('id');
|
|
120
|
+
|
|
121
|
+
if (error) {
|
|
122
|
+
throw new Error(`Failed to update session: ${error.message}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (!updatedRows || updatedRows.length === 0) {
|
|
126
|
+
throw new Error(`Session ${sessionId} not found for identity ${identity}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async getSession(identity: string, sessionId: string): Promise<SessionData | null> {
|
|
131
|
+
const { data, error } = await this.supabase
|
|
132
|
+
.from('mcp_sessions')
|
|
133
|
+
.select('*')
|
|
134
|
+
.eq('identity', identity)
|
|
135
|
+
.eq('session_id', sessionId)
|
|
136
|
+
.maybeSingle();
|
|
137
|
+
|
|
138
|
+
if (error) {
|
|
139
|
+
console.error('[SupabaseStorage] Failed to get session:', error);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!data) return null;
|
|
144
|
+
|
|
145
|
+
return this.mapRowToSessionData(data);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async getIdentitySessionsData(identity: string): Promise<SessionData[]> {
|
|
149
|
+
const { data, error } = await this.supabase
|
|
150
|
+
.from('mcp_sessions')
|
|
151
|
+
.select('*')
|
|
152
|
+
.eq('identity', identity);
|
|
153
|
+
|
|
154
|
+
if (error) {
|
|
155
|
+
console.error(`[SupabaseStorage] Failed to get session data for ${identity}:`, error);
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return data.map(row => this.mapRowToSessionData(row));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async removeSession(identity: string, sessionId: string): Promise<void> {
|
|
163
|
+
const { error } = await this.supabase
|
|
164
|
+
.from('mcp_sessions')
|
|
165
|
+
.delete()
|
|
166
|
+
.eq('identity', identity)
|
|
167
|
+
.eq('session_id', sessionId);
|
|
168
|
+
|
|
169
|
+
if (error) {
|
|
170
|
+
console.error('[SupabaseStorage] Failed to remove session:', error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async getIdentityMcpSessions(identity: string): Promise<string[]> {
|
|
175
|
+
const { data, error } = await this.supabase
|
|
176
|
+
.from('mcp_sessions')
|
|
177
|
+
.select('session_id')
|
|
178
|
+
.eq('identity', identity);
|
|
179
|
+
|
|
180
|
+
if (error) {
|
|
181
|
+
console.error(`[SupabaseStorage] Failed to get sessions for ${identity}:`, error);
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return data.map(row => row.session_id);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async getAllSessionIds(): Promise<string[]> {
|
|
189
|
+
const { data, error } = await this.supabase
|
|
190
|
+
.from('mcp_sessions')
|
|
191
|
+
.select('session_id');
|
|
192
|
+
|
|
193
|
+
if (error) {
|
|
194
|
+
console.error('[SupabaseStorage] Failed to get all sessions:', error);
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return data.map(row => row.session_id);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async clearAll(): Promise<void> {
|
|
202
|
+
// Warning: This deletes everything. Typically only used in testing.
|
|
203
|
+
const { error } = await this.supabase
|
|
204
|
+
.from('mcp_sessions')
|
|
205
|
+
.delete()
|
|
206
|
+
.neq('session_id', ''); // Delete all rows trick
|
|
207
|
+
|
|
208
|
+
if (error) {
|
|
209
|
+
console.error('[SupabaseStorage] Failed to clear sessions:', error);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async cleanupExpiredSessions(): Promise<void> {
|
|
214
|
+
const { error } = await this.supabase
|
|
215
|
+
.from('mcp_sessions')
|
|
216
|
+
.delete()
|
|
217
|
+
.lt('expires_at', new Date().toISOString());
|
|
218
|
+
|
|
219
|
+
if (error) {
|
|
220
|
+
console.error('[SupabaseStorage] Failed to cleanup expired sessions:', error);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async disconnect(): Promise<void> {
|
|
225
|
+
// Supabase client handles its own connection pooling over HTTP,
|
|
226
|
+
// there is no explicit disconnect method.
|
|
227
|
+
}
|
|
228
|
+
}
|