@mcp-ts/sdk 2.4.0 → 2.4.2

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 (52) hide show
  1. package/README.md +58 -19
  2. package/dist/adapters/agui-adapter.d.mts +3 -4
  3. package/dist/adapters/agui-adapter.d.ts +3 -4
  4. package/dist/adapters/agui-middleware.d.mts +3 -4
  5. package/dist/adapters/agui-middleware.d.ts +3 -4
  6. package/dist/adapters/ai-adapter.d.mts +3 -4
  7. package/dist/adapters/ai-adapter.d.ts +3 -4
  8. package/dist/adapters/langchain-adapter.d.mts +3 -4
  9. package/dist/adapters/langchain-adapter.d.ts +3 -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 +2 -3
  13. package/dist/client/index.d.ts +2 -3
  14. package/dist/client/react.d.mts +4 -6
  15. package/dist/client/react.d.ts +4 -6
  16. package/dist/client/vue.d.mts +4 -6
  17. package/dist/client/vue.d.ts +4 -6
  18. package/dist/{index-CtXvKl8N.d.ts → index-B8kJSrBJ.d.ts} +1 -2
  19. package/dist/{index-ByIjEReo.d.mts → index-DiJsm_lK.d.mts} +1 -2
  20. package/dist/index.d.mts +5 -6
  21. package/dist/index.d.ts +5 -6
  22. package/dist/index.js +149 -90
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +149 -90
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/{multi-session-client-CnvZEGPY.d.ts → multi-session-client-BluyCPo9.d.ts} +219 -66
  27. package/dist/{multi-session-client-CIMUGF8S.d.mts → multi-session-client-CWs-AE78.d.mts} +219 -66
  28. package/dist/server/index.d.mts +6 -129
  29. package/dist/server/index.d.ts +6 -129
  30. package/dist/server/index.js +149 -90
  31. package/dist/server/index.js.map +1 -1
  32. package/dist/server/index.mjs +149 -90
  33. package/dist/server/index.mjs.map +1 -1
  34. package/dist/shared/index.d.mts +4 -5
  35. package/dist/shared/index.d.ts +4 -5
  36. package/dist/shared/index.js.map +1 -1
  37. package/dist/shared/index.mjs.map +1 -1
  38. package/dist/{tool-router-CuApsDiV.d.mts → tool-router-CbG4Tum6.d.mts} +1 -1
  39. package/dist/{tool-router-CZMrOG8J.d.ts → tool-router-ChIhPwgP.d.ts} +1 -1
  40. package/dist/{types-DCk_IF4L.d.ts → types-CjczQwNX.d.mts} +122 -1
  41. package/dist/{types-DCk_IF4L.d.mts → types-CjczQwNX.d.ts} +122 -1
  42. package/migrations/v2.3.4/neon/20260513010000_install_mcp_sessions.sql +69 -0
  43. package/migrations/v2.3.4/neon/20260513020000_add_session_cleanup_cron.sql +35 -0
  44. package/migrations/v2.3.4/supabase/20260330195700_install_mcp_sessions.sql +82 -0
  45. package/migrations/v2.3.4/supabase/20260421010000_add_session_cleanup_cron.sql +32 -0
  46. package/package.json +1 -1
  47. package/src/server/handlers/sse-handler.ts +7 -5
  48. package/src/server/mcp/multi-session-client.ts +186 -100
  49. package/src/server/mcp/oauth-client.ts +60 -10
  50. package/src/shared/events.ts +0 -1
  51. package/dist/events-CK3N--3g.d.mts +0 -123
  52. package/dist/events-CK3N--3g.d.ts +0 -123
@@ -1,5 +1,5 @@
1
1
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
- import { u as ToolClientProvider, T as ToolClient } from './types-DCk_IF4L.mjs';
2
+ import { A as ToolClientProvider, T as ToolClient } from './types-CjczQwNX.mjs';
3
3
 
4
4
  /**
5
5
  * ToolIndex — Lightweight in-memory search index for MCP tool discovery.
@@ -1,5 +1,5 @@
1
1
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
- import { u as ToolClientProvider, T as ToolClient } from './types-DCk_IF4L.js';
2
+ import { A as ToolClientProvider, T as ToolClient } from './types-CjczQwNX.js';
3
3
 
4
4
  /**
5
5
  * ToolIndex — Lightweight in-memory search index for MCP tool discovery.
@@ -1,5 +1,126 @@
1
1
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
2
 
3
+ /**
4
+ * Simple event emitter pattern for MCP connection events
5
+ * Inspired by Cloudflare's agents pattern but adapted for serverless
6
+ */
7
+ type Disposable = {
8
+ dispose(): void;
9
+ };
10
+ type Event<T> = (listener: (event: T) => void) => Disposable;
11
+ /**
12
+ * Event emitter class for type-safe event handling
13
+ * Similar to Cloudflare's Emitter but simplified for our use case
14
+ */
15
+ declare class Emitter<T> {
16
+ private listeners;
17
+ /**
18
+ * Subscribe to events
19
+ * @param listener - Callback function to handle events
20
+ * @returns Disposable to unsubscribe
21
+ */
22
+ get event(): Event<T>;
23
+ /**
24
+ * Fire an event to all listeners
25
+ * @param event - Event data to emit
26
+ */
27
+ fire(event: T): void;
28
+ /**
29
+ * Clear all listeners
30
+ */
31
+ dispose(): void;
32
+ /**
33
+ * Get number of active listeners
34
+ */
35
+ get listenerCount(): number;
36
+ }
37
+ /**
38
+ * Connection state types matching your existing ConnectionStatus
39
+ * Extended with more granular states for better observability
40
+ */
41
+ type McpConnectionState = 'DISCONNECTED' | 'CONNECTING' | 'AUTHENTICATING' | 'AUTHENTICATED' | 'DISCOVERING' | 'CONNECTED' | 'READY' | 'VALIDATING' | 'RECONNECTING' | 'INITIALIZING' | 'FAILED';
42
+ /**
43
+ * MCP Connection Event Types
44
+ * Discriminated union for type-safe event handling
45
+ */
46
+ type McpConnectionEvent = {
47
+ type: 'state_changed';
48
+ sessionId: string;
49
+ serverId: string;
50
+ serverName: string;
51
+ serverUrl: string;
52
+ createdAt?: number;
53
+ state: McpConnectionState;
54
+ previousState: McpConnectionState;
55
+ timestamp: number;
56
+ } | {
57
+ type: 'tools_discovered';
58
+ sessionId: string;
59
+ serverId: string;
60
+ toolCount: number;
61
+ tools: any[];
62
+ timestamp: number;
63
+ } | {
64
+ type: 'auth_required';
65
+ sessionId: string;
66
+ serverId: string;
67
+ authUrl: string;
68
+ timestamp: number;
69
+ } | {
70
+ type: 'error';
71
+ sessionId: string;
72
+ serverId: string;
73
+ error: string;
74
+ errorType: 'connection' | 'auth' | 'validation' | 'unknown';
75
+ timestamp: number;
76
+ } | {
77
+ type: 'disconnected';
78
+ sessionId: string;
79
+ serverId: string;
80
+ timestamp: number;
81
+ } | {
82
+ type: 'progress';
83
+ sessionId: string;
84
+ serverId: string;
85
+ message: string;
86
+ timestamp: number;
87
+ };
88
+ /**
89
+ * Event fired when a tool execution returns a UI resource URI
90
+ */
91
+ interface McpAppsUIEvent {
92
+ type: 'mcp-apps-ui';
93
+ sessionId: string;
94
+ resourceUri: string;
95
+ toolName: string;
96
+ result: unknown;
97
+ timestamp: number;
98
+ }
99
+ /**
100
+ * Observability event for debugging and monitoring
101
+ */
102
+ interface McpObservabilityEvent {
103
+ type?: string;
104
+ level?: 'debug' | 'info' | 'warn' | 'error';
105
+ message?: string;
106
+ displayMessage?: string;
107
+ sessionId?: string;
108
+ serverId?: string;
109
+ payload?: Record<string, any>;
110
+ metadata?: Record<string, any>;
111
+ timestamp: number;
112
+ id?: string;
113
+ }
114
+ /**
115
+ * DisposableStore for managing multiple disposables
116
+ * Useful for cleanup in React hooks
117
+ */
118
+ declare class DisposableStore {
119
+ private disposables;
120
+ add(disposable: Disposable): void;
121
+ dispose(): void;
122
+ }
123
+
3
124
  /**
4
125
  * Type definitions for MCP operations
5
126
  */
@@ -186,4 +307,4 @@ interface ListResourcesResult {
186
307
  }>;
187
308
  }
188
309
 
189
- export { isConnectSuccess as A, isListToolsSuccess as B, type CallToolParams as C, type DisconnectParams as D, type FinishAuthParams as F, type GetPromptParams as G, type ListPromptsResult as L, type McpRpcMethod as M, type ReadResourceParams as R, type SessionInfo as S, type ToolClient as T, type CallToolRequest as a, type CallToolResponse as b, type ConnectAuthRequiredResponse as c, type ConnectErrorResponse as d, type ConnectParams as e, type ConnectRequest as f, type ConnectResponse as g, type ConnectResult as h, type ConnectSuccessResponse as i, type DisconnectResult as j, type FinishAuthResult as k, type GetSessionResult as l, type ListResourcesResult as m, type ListToolsResponse as n, type ListToolsRpcResult as o, type McpRpcParams as p, type McpRpcRequest as q, type McpRpcResponse as r, type SessionListResult as s, type SessionParams as t, type ToolClientProvider as u, type ToolInfo as v, type TransportType as w, isCallToolSuccess as x, isConnectAuthRequired as y, isConnectError as z };
310
+ export { type ToolClientProvider as A, type ToolInfo as B, type CallToolParams as C, type DisconnectParams as D, Emitter as E, type FinishAuthParams as F, type GetPromptParams as G, type TransportType as H, isCallToolSuccess as I, isConnectAuthRequired as J, isConnectError as K, type ListPromptsResult as L, type McpConnectionEvent as M, isConnectSuccess as N, isListToolsSuccess as O, type McpAppsUIEvent as P, type ReadResourceParams as R, type SessionInfo as S, type ToolClient as T, type CallToolRequest as a, type CallToolResponse as b, type ConnectAuthRequiredResponse as c, type ConnectErrorResponse as d, type ConnectParams as e, type ConnectRequest as f, type ConnectResponse as g, type ConnectResult as h, type ConnectSuccessResponse as i, type DisconnectResult as j, type Disposable as k, DisposableStore as l, type Event as m, type FinishAuthResult as n, type GetSessionResult as o, type ListResourcesResult as p, type ListToolsResponse as q, type ListToolsRpcResult as r, type McpConnectionState as s, type McpObservabilityEvent as t, type McpRpcMethod as u, type McpRpcParams as v, type McpRpcRequest as w, type McpRpcResponse as x, type SessionListResult as y, type SessionParams as z };
@@ -1,5 +1,126 @@
1
1
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
2
 
3
+ /**
4
+ * Simple event emitter pattern for MCP connection events
5
+ * Inspired by Cloudflare's agents pattern but adapted for serverless
6
+ */
7
+ type Disposable = {
8
+ dispose(): void;
9
+ };
10
+ type Event<T> = (listener: (event: T) => void) => Disposable;
11
+ /**
12
+ * Event emitter class for type-safe event handling
13
+ * Similar to Cloudflare's Emitter but simplified for our use case
14
+ */
15
+ declare class Emitter<T> {
16
+ private listeners;
17
+ /**
18
+ * Subscribe to events
19
+ * @param listener - Callback function to handle events
20
+ * @returns Disposable to unsubscribe
21
+ */
22
+ get event(): Event<T>;
23
+ /**
24
+ * Fire an event to all listeners
25
+ * @param event - Event data to emit
26
+ */
27
+ fire(event: T): void;
28
+ /**
29
+ * Clear all listeners
30
+ */
31
+ dispose(): void;
32
+ /**
33
+ * Get number of active listeners
34
+ */
35
+ get listenerCount(): number;
36
+ }
37
+ /**
38
+ * Connection state types matching your existing ConnectionStatus
39
+ * Extended with more granular states for better observability
40
+ */
41
+ type McpConnectionState = 'DISCONNECTED' | 'CONNECTING' | 'AUTHENTICATING' | 'AUTHENTICATED' | 'DISCOVERING' | 'CONNECTED' | 'READY' | 'VALIDATING' | 'RECONNECTING' | 'INITIALIZING' | 'FAILED';
42
+ /**
43
+ * MCP Connection Event Types
44
+ * Discriminated union for type-safe event handling
45
+ */
46
+ type McpConnectionEvent = {
47
+ type: 'state_changed';
48
+ sessionId: string;
49
+ serverId: string;
50
+ serverName: string;
51
+ serverUrl: string;
52
+ createdAt?: number;
53
+ state: McpConnectionState;
54
+ previousState: McpConnectionState;
55
+ timestamp: number;
56
+ } | {
57
+ type: 'tools_discovered';
58
+ sessionId: string;
59
+ serverId: string;
60
+ toolCount: number;
61
+ tools: any[];
62
+ timestamp: number;
63
+ } | {
64
+ type: 'auth_required';
65
+ sessionId: string;
66
+ serverId: string;
67
+ authUrl: string;
68
+ timestamp: number;
69
+ } | {
70
+ type: 'error';
71
+ sessionId: string;
72
+ serverId: string;
73
+ error: string;
74
+ errorType: 'connection' | 'auth' | 'validation' | 'unknown';
75
+ timestamp: number;
76
+ } | {
77
+ type: 'disconnected';
78
+ sessionId: string;
79
+ serverId: string;
80
+ timestamp: number;
81
+ } | {
82
+ type: 'progress';
83
+ sessionId: string;
84
+ serverId: string;
85
+ message: string;
86
+ timestamp: number;
87
+ };
88
+ /**
89
+ * Event fired when a tool execution returns a UI resource URI
90
+ */
91
+ interface McpAppsUIEvent {
92
+ type: 'mcp-apps-ui';
93
+ sessionId: string;
94
+ resourceUri: string;
95
+ toolName: string;
96
+ result: unknown;
97
+ timestamp: number;
98
+ }
99
+ /**
100
+ * Observability event for debugging and monitoring
101
+ */
102
+ interface McpObservabilityEvent {
103
+ type?: string;
104
+ level?: 'debug' | 'info' | 'warn' | 'error';
105
+ message?: string;
106
+ displayMessage?: string;
107
+ sessionId?: string;
108
+ serverId?: string;
109
+ payload?: Record<string, any>;
110
+ metadata?: Record<string, any>;
111
+ timestamp: number;
112
+ id?: string;
113
+ }
114
+ /**
115
+ * DisposableStore for managing multiple disposables
116
+ * Useful for cleanup in React hooks
117
+ */
118
+ declare class DisposableStore {
119
+ private disposables;
120
+ add(disposable: Disposable): void;
121
+ dispose(): void;
122
+ }
123
+
3
124
  /**
4
125
  * Type definitions for MCP operations
5
126
  */
@@ -186,4 +307,4 @@ interface ListResourcesResult {
186
307
  }>;
187
308
  }
188
309
 
189
- export { isConnectSuccess as A, isListToolsSuccess as B, type CallToolParams as C, type DisconnectParams as D, type FinishAuthParams as F, type GetPromptParams as G, type ListPromptsResult as L, type McpRpcMethod as M, type ReadResourceParams as R, type SessionInfo as S, type ToolClient as T, type CallToolRequest as a, type CallToolResponse as b, type ConnectAuthRequiredResponse as c, type ConnectErrorResponse as d, type ConnectParams as e, type ConnectRequest as f, type ConnectResponse as g, type ConnectResult as h, type ConnectSuccessResponse as i, type DisconnectResult as j, type FinishAuthResult as k, type GetSessionResult as l, type ListResourcesResult as m, type ListToolsResponse as n, type ListToolsRpcResult as o, type McpRpcParams as p, type McpRpcRequest as q, type McpRpcResponse as r, type SessionListResult as s, type SessionParams as t, type ToolClientProvider as u, type ToolInfo as v, type TransportType as w, isCallToolSuccess as x, isConnectAuthRequired as y, isConnectError as z };
310
+ export { type ToolClientProvider as A, type ToolInfo as B, type CallToolParams as C, type DisconnectParams as D, Emitter as E, type FinishAuthParams as F, type GetPromptParams as G, type TransportType as H, isCallToolSuccess as I, isConnectAuthRequired as J, isConnectError as K, type ListPromptsResult as L, type McpConnectionEvent as M, isConnectSuccess as N, isListToolsSuccess as O, type McpAppsUIEvent as P, type ReadResourceParams as R, type SessionInfo as S, type ToolClient as T, type CallToolRequest as a, type CallToolResponse as b, type ConnectAuthRequiredResponse as c, type ConnectErrorResponse as d, type ConnectParams as e, type ConnectRequest as f, type ConnectResponse as g, type ConnectResult as h, type ConnectSuccessResponse as i, type DisconnectResult as j, type Disposable as k, DisposableStore as l, type Event as m, type FinishAuthResult as n, type GetSessionResult as o, type ListResourcesResult as p, type ListToolsResponse as q, type ListToolsRpcResult as r, type McpConnectionState as s, type McpObservabilityEvent as t, type McpRpcMethod as u, type McpRpcParams as v, type McpRpcRequest as w, type McpRpcResponse as x, type SessionListResult as y, type SessionParams as z };
@@ -0,0 +1,69 @@
1
+ -- Create the mcp_sessions table for Neon Postgres.
2
+ -- Run this with an owner/admin role, then grant app access with the least-privilege SQL below.
3
+
4
+ CREATE TABLE IF NOT EXISTS public.mcp_sessions (
5
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
6
+ session_id TEXT NOT NULL UNIQUE,
7
+ user_id TEXT NOT NULL,
8
+ server_id TEXT,
9
+ server_name TEXT,
10
+ server_url TEXT NOT NULL,
11
+ transport_type TEXT NOT NULL,
12
+ callback_url TEXT NOT NULL,
13
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
14
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
15
+ expires_at TIMESTAMPTZ NOT NULL,
16
+ active BOOLEAN DEFAULT false,
17
+ headers JSONB,
18
+ client_information JSONB,
19
+ tokens JSONB,
20
+ code_verifier TEXT,
21
+ client_id TEXT
22
+ );
23
+
24
+ CREATE INDEX IF NOT EXISTS idx_mcp_sessions_user_id ON public.mcp_sessions(user_id);
25
+ CREATE INDEX IF NOT EXISTS idx_mcp_sessions_expires_at ON public.mcp_sessions(expires_at);
26
+
27
+ CREATE OR REPLACE FUNCTION public.set_current_timestamp_updated_at()
28
+ RETURNS TRIGGER AS $$
29
+ BEGIN
30
+ NEW.updated_at = now();
31
+ RETURN NEW;
32
+ END;
33
+ $$ LANGUAGE plpgsql;
34
+
35
+ DROP TRIGGER IF EXISTS trg_mcp_sessions_updated_at ON public.mcp_sessions;
36
+
37
+ CREATE TRIGGER trg_mcp_sessions_updated_at
38
+ BEFORE UPDATE ON public.mcp_sessions
39
+ FOR EACH ROW
40
+ EXECUTE FUNCTION public.set_current_timestamp_updated_at();
41
+
42
+ -- Optional production configuration:
43
+ -- Create a dedicated app role and use its credentials in NEON_DATABASE_URL.
44
+ -- Replace neondb and the password before running.
45
+ --
46
+ -- CREATE ROLE mcp_service_role LOGIN PASSWORD 'replace-with-a-strong-password';
47
+ -- GRANT CONNECT ON DATABASE neondb TO mcp_service_role;
48
+ -- GRANT USAGE ON SCHEMA public TO mcp_service_role;
49
+ -- GRANT SELECT, INSERT, UPDATE, DELETE ON public.mcp_sessions TO mcp_service_role;
50
+ -- GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO mcp_service_role;
51
+
52
+ -- Optional RLS configuration:
53
+ -- Uncomment and run this block after creating mcp_service_role if you want
54
+ -- to enforce access through Row Level Security for the dedicated app role.
55
+ --
56
+ -- REVOKE ALL ON public.mcp_sessions FROM PUBLIC;
57
+ -- REVOKE ALL ON ALL SEQUENCES IN SCHEMA public FROM PUBLIC;
58
+ --
59
+ -- GRANT SELECT, INSERT, UPDATE, DELETE ON public.mcp_sessions TO mcp_service_role;
60
+ -- GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO mcp_service_role;
61
+ --
62
+ -- ALTER TABLE public.mcp_sessions ENABLE ROW LEVEL SECURITY;
63
+ --
64
+ -- CREATE POLICY mcp_service_role_full_access
65
+ -- ON public.mcp_sessions
66
+ -- FOR ALL
67
+ -- TO mcp_service_role
68
+ -- USING (true)
69
+ -- WITH CHECK (true);
@@ -0,0 +1,35 @@
1
+ -- Optional Neon pg_cron cleanup jobs.
2
+ --
3
+ -- Neon pg_cron requires endpoint-level setup before this migration can run:
4
+ -- configure cron.database_name for your compute endpoint, restart the compute,
5
+ -- then install/schedule jobs from the target database.
6
+ --
7
+ -- If pg_cron is not enabled for your Neon project, skip this migration and run
8
+ -- storage.cleanupExpiredSessions() from your application scheduler instead.
9
+
10
+ CREATE EXTENSION IF NOT EXISTS pg_cron;
11
+
12
+ -- Keep reruns idempotent without NOTICE noise.
13
+ SELECT cron.unschedule(jobname)
14
+ FROM cron.job
15
+ WHERE jobname IN (
16
+ 'mcp-cleanup-transient-sessions',
17
+ 'mcp-cleanup-dormant-sessions'
18
+ );
19
+
20
+ -- Stage 1: Short-term Transient Purge (every 5 minutes)
21
+ -- Removes failed connections, abandoned OAuth flows, and other inactive
22
+ -- sessions whose TTL has expired.
23
+ SELECT cron.schedule(
24
+ 'mcp-cleanup-transient-sessions',
25
+ '*/5 * * * *',
26
+ $$DELETE FROM public.mcp_sessions WHERE expires_at < now() AND active IS NOT TRUE;$$
27
+ );
28
+
29
+ -- Stage 2: Long-term Dormancy Eviction (daily at midnight UTC)
30
+ -- Removes active sessions that have not been touched for 30+ days.
31
+ SELECT cron.schedule(
32
+ 'mcp-cleanup-dormant-sessions',
33
+ '0 0 * * *',
34
+ $$DELETE FROM public.mcp_sessions WHERE active = true AND updated_at < now() - interval '30 days';$$
35
+ );
@@ -0,0 +1,82 @@
1
+ -- Create the mcp_sessions table
2
+ CREATE TABLE IF NOT EXISTS public.mcp_sessions (
3
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
4
+ session_id TEXT NOT NULL UNIQUE,
5
+ user_id TEXT NOT NULL, -- Will store the application user's ID
6
+ server_id TEXT,
7
+ server_name TEXT,
8
+ server_url TEXT NOT NULL,
9
+ transport_type TEXT NOT NULL,
10
+ callback_url TEXT NOT NULL,
11
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
12
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
13
+ expires_at TIMESTAMPTZ NOT NULL,
14
+ active BOOLEAN DEFAULT false,
15
+ headers JSONB,
16
+ client_information JSONB,
17
+ tokens JSONB,
18
+ code_verifier TEXT,
19
+ client_id TEXT
20
+ );
21
+
22
+ -- Add an index on user_id for faster lookups
23
+ CREATE INDEX IF NOT EXISTS idx_mcp_sessions_user_id ON public.mcp_sessions(user_id);
24
+ -- Add an index on expires_at to speed up the cleanup job
25
+ CREATE INDEX IF NOT EXISTS idx_mcp_sessions_expires_at ON public.mcp_sessions(expires_at);
26
+
27
+ -- Trigger to automatically update the 'updated_at' column
28
+ CREATE OR REPLACE FUNCTION public.set_current_timestamp_updated_at()
29
+ RETURNS TRIGGER AS $$
30
+ BEGIN
31
+ NEW.updated_at = now();
32
+ RETURN NEW;
33
+ END;
34
+ $$ LANGUAGE plpgsql;
35
+
36
+ DROP TRIGGER IF EXISTS trg_mcp_sessions_updated_at ON public.mcp_sessions;
37
+ CREATE TRIGGER trg_mcp_sessions_updated_at
38
+ BEFORE UPDATE ON public.mcp_sessions
39
+ FOR EACH ROW
40
+ EXECUTE FUNCTION public.set_current_timestamp_updated_at();
41
+
42
+ -- Enable Row Level Security (RLS)
43
+ ALTER TABLE public.mcp_sessions ENABLE ROW LEVEL SECURITY;
44
+
45
+ -- Policy 1: Users can read their own sessions
46
+ CREATE POLICY "Users can view their own sessions"
47
+ ON public.mcp_sessions
48
+ FOR SELECT
49
+ TO authenticated
50
+ USING (
51
+ auth.uid()::text = user_id
52
+ );
53
+
54
+ -- Policy 2: Users can insert their own sessions
55
+ CREATE POLICY "Users can insert their own sessions"
56
+ ON public.mcp_sessions
57
+ FOR INSERT
58
+ TO authenticated
59
+ WITH CHECK (
60
+ auth.uid()::text = user_id
61
+ );
62
+
63
+ -- Policy 3: Users can update their own sessions
64
+ CREATE POLICY "Users can update their own sessions"
65
+ ON public.mcp_sessions
66
+ FOR UPDATE
67
+ TO authenticated
68
+ USING (
69
+ auth.uid()::text = user_id
70
+ )
71
+ WITH CHECK (
72
+ auth.uid()::text = user_id
73
+ );
74
+
75
+ -- Policy 4: Users can delete their own sessions
76
+ CREATE POLICY "Users can delete their own sessions"
77
+ ON public.mcp_sessions
78
+ FOR DELETE
79
+ TO authenticated
80
+ USING (
81
+ auth.uid()::text = user_id
82
+ );
@@ -0,0 +1,32 @@
1
+ -- Enable the pg_cron extension (available on all Supabase plans).
2
+ -- This is idempotent and safe to run multiple times.
3
+ CREATE EXTENSION IF NOT EXISTS pg_cron;
4
+
5
+ -- -----------------------------------------------------------------------------
6
+ -- Stage 1: Short-term Transient Purge (every 5 minutes)
7
+ -- -----------------------------------------------------------------------------
8
+ -- Targets sessions that are NOT active (failed connections, abandoned OAuth
9
+ -- flows, mid-flow errors) whose TTL has expired. Active sessions are explicitly
10
+ -- excluded from this sweep to preserve automation credentials.
11
+ --
12
+ -- The idx_mcp_sessions_expires_at index ensures this is a fast indexed scan.
13
+ SELECT cron.schedule(
14
+ 'cleanup-transient-sessions',
15
+ '*/5 * * * *',
16
+ $$DELETE FROM public.mcp_sessions WHERE expires_at < now() AND active IS NOT TRUE;$$
17
+ );
18
+
19
+ -- -----------------------------------------------------------------------------
20
+ -- Stage 2: Long-term Dormancy Eviction (daily at midnight UTC)
21
+ -- -----------------------------------------------------------------------------
22
+ -- Safety net for sessions that were successfully established (active = true)
23
+ -- but have been completely untouched for 30+ days. This prevents "active"
24
+ -- records from persisting indefinitely if they are genuinely abandoned.
25
+ SELECT cron.schedule(
26
+ 'cleanup-dormant-sessions',
27
+ '0 0 * * *',
28
+ $$DELETE FROM public.mcp_sessions WHERE active = true AND updated_at < now() - interval '30 days';$$
29
+ );
30
+
31
+ -- Add a comment on the extension for visibility in Supabase Dashboard
32
+ COMMENT ON EXTENSION pg_cron IS 'Automated Session Lifecycle Management.';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-ts/sdk",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -356,8 +356,8 @@ export class SSEConnectionManager {
356
356
  const client = this.clients.get(sessionId);
357
357
 
358
358
  if (client) {
359
+ // clearSession() handles DELETE + local cleanup internally.
359
360
  await client.clearSession();
360
- client.disconnect();
361
361
  this.clients.delete(sessionId);
362
362
  } else {
363
363
  // Handle orphaned sessions (e.g., OAuth flow failed before client was stored)
@@ -606,16 +606,18 @@ export class SSEConnectionManager {
606
606
  /**
607
607
  * Cleanup and close all connections
608
608
  */
609
- dispose(): void {
609
+ async dispose(): Promise<void> {
610
610
  this.isActive = false;
611
611
 
612
612
  if (this.heartbeatTimer) {
613
613
  clearInterval(this.heartbeatTimer);
614
614
  }
615
615
 
616
- for (const client of this.clients.values()) {
617
- client.disconnect();
618
- }
616
+ // Send HTTP DELETE to each Streamable HTTP server before closing, per spec.
617
+ // Run in parallel so shutdown is not serialised across many sessions.
618
+ await Promise.all(
619
+ Array.from(this.clients.values()).map((client) => client.disconnect())
620
+ );
619
621
 
620
622
  this.clients.clear();
621
623
  }