@mcp-ts/sdk 2.2.0 → 2.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-ts/sdk",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -6,7 +6,7 @@
6
6
  /** Core MCP client and session management */
7
7
  export { MCPClient } from './mcp/oauth-client.js';
8
8
  export { UnauthorizedError } from '../shared/errors.js';
9
- export { sessions, type SessionStore } from './storage/index.js';
9
+ export { sessions, onSessionMutation, type SessionStore } from './storage/index.js';
10
10
  export { StorageOAuthClientProvider } from './mcp/storage-oauth-provider.js';
11
11
  export { MultiSessionClient } from './mcp/multi-session-client.js';
12
12
 
@@ -44,6 +44,13 @@ export type {
44
44
  CallToolResponse,
45
45
  } from '../shared/types';
46
46
 
47
+ export type {
48
+ Session,
49
+ SessionMutationEvent,
50
+ SessionMutationListener,
51
+ SessionMutationType,
52
+ } from './storage/types.js';
53
+
47
54
  /** Re-export MCP SDK types for convenience */
48
55
  export type {
49
56
  OAuthClientMetadata,
@@ -5,7 +5,7 @@ import { FileStorageBackend } from './file-backend';
5
5
  import { SqliteStorage } from './sqlite-backend.js';
6
6
  import { SupabaseStorageBackend } from './supabase-backend.js';
7
7
  import { NeonStorageBackend, type NeonStorageOptions } from './neon-backend.js';
8
- import type { SessionStore } from './types.js';
8
+ import type { SessionStore, Session, SessionMutationEvent, SessionMutationListener } from './types.js';
9
9
 
10
10
  // Re-export types
11
11
  export * from './types.js';
@@ -42,6 +42,65 @@ function warnIfNeonConnectionStringIsInsecure(connectionString: string): void {
42
42
 
43
43
  let storageInstance: SessionStore | null = null;
44
44
  let storagePromise: Promise<SessionStore> | null = null;
45
+ const sessionMutationListeners = new Set<SessionMutationListener>();
46
+
47
+ function emitSessionMutation(event: SessionMutationEvent): void {
48
+ for (const listener of sessionMutationListeners) {
49
+ try {
50
+ const result = listener(event);
51
+ if (result && typeof (result as Promise<void>).catch === 'function') {
52
+ void (result as Promise<void>).catch((error) => {
53
+ console.error('[mcp-ts][Storage] Session mutation listener failed:', error);
54
+ });
55
+ }
56
+ } catch (error) {
57
+ console.error('[mcp-ts][Storage] Session mutation listener failed:', error);
58
+ }
59
+ }
60
+ }
61
+
62
+ function createSessionMutationEvent(prop: PropertyKey, args: any[]): SessionMutationEvent | null {
63
+ const timestamp = Date.now();
64
+
65
+ if (prop === 'create') {
66
+ const [session, ttl] = args as [Session, number | undefined];
67
+ if (!session?.userId || !session?.sessionId) return null;
68
+ return {
69
+ type: 'create',
70
+ userId: session.userId,
71
+ sessionId: session.sessionId,
72
+ session,
73
+ ttl,
74
+ timestamp,
75
+ };
76
+ }
77
+
78
+ if (prop === 'update') {
79
+ const [userId, sessionId, patch, ttl] = args as [string, string, Partial<Session>, number | undefined];
80
+ if (!userId || !sessionId) return null;
81
+ return {
82
+ type: 'update',
83
+ userId,
84
+ sessionId,
85
+ patch,
86
+ ttl,
87
+ timestamp,
88
+ };
89
+ }
90
+
91
+ if (prop === 'delete') {
92
+ const [userId, sessionId] = args as [string, string];
93
+ if (!userId || !sessionId) return null;
94
+ return {
95
+ type: 'delete',
96
+ userId,
97
+ sessionId,
98
+ timestamp,
99
+ };
100
+ }
101
+
102
+ return null;
103
+ }
45
104
 
46
105
  async function initializeStorage<T extends SessionStore>(store: T): Promise<T> {
47
106
  if (typeof store.init === 'function') {
@@ -215,6 +274,17 @@ export function _setStorageInstanceForTesting(instance: SessionStore | null): vo
215
274
  }
216
275
  }
217
276
 
277
+ export function onSessionMutation(listener: SessionMutationListener): () => void {
278
+ sessionMutationListeners.add(listener);
279
+ return () => {
280
+ sessionMutationListeners.delete(listener);
281
+ };
282
+ }
283
+
284
+ export function _resetSessionMutationListenersForTesting(): void {
285
+ sessionMutationListeners.clear();
286
+ }
287
+
218
288
  /**
219
289
  * Global session store instance
220
290
  * Uses lazy initialization with a Proxy to handle async setup transparently
@@ -225,7 +295,12 @@ export const sessions: SessionStore = new Proxy({} as SessionStore, {
225
295
  const instance = await getStorage();
226
296
  const value = (instance as any)[prop];
227
297
  if (typeof value === 'function') {
228
- return value.apply(instance, args);
298
+ const result = await value.apply(instance, args);
299
+ const event = createSessionMutationEvent(prop, args);
300
+ if (event) {
301
+ emitSessionMutation(event);
302
+ }
303
+ return result;
229
304
  }
230
305
  return value;
231
306
  };
@@ -29,6 +29,20 @@ export interface Session {
29
29
  clientId?: string;
30
30
  }
31
31
 
32
+ export type SessionMutationType = 'create' | 'update' | 'delete';
33
+
34
+ export interface SessionMutationEvent {
35
+ type: SessionMutationType;
36
+ userId: string;
37
+ sessionId: string;
38
+ timestamp: number;
39
+ session?: Session;
40
+ patch?: Partial<Session>;
41
+ ttl?: number;
42
+ }
43
+
44
+ export type SessionMutationListener = (event: SessionMutationEvent) => void | Promise<void>;
45
+
32
46
  export interface SetClientOptions {
33
47
  sessionId: string;
34
48
  serverId?: string; // Database server ID