@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.
Files changed (82) hide show
  1. package/README.md +113 -30
  2. package/dist/adapters/agui-adapter.d.mts +3 -3
  3. package/dist/adapters/agui-adapter.d.ts +3 -3
  4. package/dist/adapters/agui-middleware.d.mts +3 -3
  5. package/dist/adapters/agui-middleware.d.ts +3 -3
  6. package/dist/adapters/ai-adapter.d.mts +3 -3
  7. package/dist/adapters/ai-adapter.d.ts +3 -3
  8. package/dist/adapters/langchain-adapter.d.mts +3 -3
  9. package/dist/adapters/langchain-adapter.d.ts +3 -3
  10. package/dist/adapters/mastra-adapter.d.mts +1 -1
  11. package/dist/adapters/mastra-adapter.d.ts +1 -1
  12. package/dist/client/index.d.mts +2 -2
  13. package/dist/client/index.d.ts +2 -2
  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 +9 -8
  19. package/dist/client/react.d.ts +9 -8
  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 +6 -5
  25. package/dist/client/vue.d.ts +6 -5
  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/{index-Cfjsme-a.d.mts → index-ByIjEReo.d.mts} +2 -2
  31. package/dist/{index-CmjMd2ac.d.ts → index-CtXvKl8N.d.ts} +2 -2
  32. package/dist/index.d.mts +5 -5
  33. package/dist/index.d.ts +5 -5
  34. package/dist/index.js +728 -306
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +722 -304
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/{multi-session-client-C7hGqzgM.d.mts → multi-session-client-CIMUGF8S.d.mts} +15 -33
  39. package/dist/{multi-session-client-C_kPHpD5.d.ts → multi-session-client-CnvZEGPY.d.ts} +15 -33
  40. package/dist/server/index.d.mts +45 -17
  41. package/dist/server/index.d.ts +45 -17
  42. package/dist/server/index.js +720 -300
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +716 -299
  45. package/dist/server/index.mjs.map +1 -1
  46. package/dist/shared/index.d.mts +9 -8
  47. package/dist/shared/index.d.ts +9 -8
  48. package/dist/shared/index.js +7 -5
  49. package/dist/shared/index.js.map +1 -1
  50. package/dist/shared/index.mjs +5 -4
  51. package/dist/shared/index.mjs.map +1 -1
  52. package/dist/{tool-router-BMzhoNYt.d.ts → tool-router-CZMrOG8J.d.ts} +1 -1
  53. package/dist/{tool-router-BhHsvBfP.d.mts → tool-router-CuApsDiV.d.mts} +1 -1
  54. package/dist/{types-CxFaaZrM.d.mts → types-DCk_IF4L.d.mts} +5 -3
  55. package/dist/{types-CxFaaZrM.d.ts → types-DCk_IF4L.d.ts} +5 -3
  56. package/migrations/neon/20260513010000_install_mcp_sessions.sql +40 -3
  57. package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +4 -4
  58. package/migrations/supabase/20260330195700_install_mcp_sessions.sql +67 -3
  59. package/migrations/supabase/20260421010000_add_session_cleanup_cron.sql +6 -6
  60. package/package.json +13 -3
  61. package/src/client/core/sse-client.ts +2 -2
  62. package/src/client/react/index.ts +1 -1
  63. package/src/client/react/oauth-popup.tsx +34 -13
  64. package/src/client/react/use-mcp.ts +19 -45
  65. package/src/client/utils/session-state.ts +43 -0
  66. package/src/client/vue/use-mcp.ts +18 -47
  67. package/src/server/handlers/sse-handler.ts +9 -4
  68. package/src/server/mcp/multi-session-client.ts +2 -6
  69. package/src/server/mcp/oauth-client.ts +73 -101
  70. package/src/server/mcp/storage-oauth-provider.ts +79 -50
  71. package/src/server/storage/file-backend.ts +74 -18
  72. package/src/server/storage/index.ts +2 -4
  73. package/src/server/storage/memory-backend.ts +49 -13
  74. package/src/server/storage/neon-backend.ts +201 -68
  75. package/src/server/storage/redis-backend.ts +81 -23
  76. package/src/server/storage/session-lifecycle.ts +78 -0
  77. package/src/server/storage/sqlite-backend.ts +89 -15
  78. package/src/server/storage/supabase-backend.ts +188 -63
  79. package/src/server/storage/types.ts +49 -16
  80. package/src/shared/constants.ts +5 -6
  81. package/src/shared/types.ts +5 -3
  82. package/src/shared/utils.ts +26 -0
@@ -1,5 +1,5 @@
1
1
  import { Tool } from '@modelcontextprotocol/sdk/types.js';
2
- import { u as ToolClientProvider, T as ToolClient } from './types-CxFaaZrM.js';
2
+ import { u as ToolClientProvider, T as ToolClient } from './types-DCk_IF4L.js';
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-CxFaaZrM.mjs';
2
+ import { u as ToolClientProvider, T as ToolClient } from './types-DCk_IF4L.mjs';
3
3
 
4
4
  /**
5
5
  * ToolIndex — Lightweight in-memory search index for MCP tool discovery.
@@ -83,6 +83,7 @@ type ToolInfo = {
83
83
  outputSchema?: Tool['outputSchema'];
84
84
  };
85
85
  type TransportType = 'sse' | 'streamable-http';
86
+ type SessionStatus = 'pending' | 'active';
86
87
  type McpRpcMethod = 'connect' | 'disconnect' | 'listTools' | 'callTool' | 'listSessions' | 'getSession' | 'finishAuth' | 'listPrompts' | 'getPrompt' | 'listResources' | 'readResource';
87
88
  interface McpRpcRequest {
88
89
  id: string;
@@ -126,7 +127,7 @@ interface ReadResourceParams {
126
127
  uri: string;
127
128
  }
128
129
  interface FinishAuthParams {
129
- sessionId: string;
130
+ state: string;
130
131
  code: string;
131
132
  }
132
133
  type McpRpcParams = ConnectParams | DisconnectParams | SessionParams | CallToolParams | GetPromptParams | ReadResourceParams | FinishAuthParams | undefined;
@@ -137,11 +138,12 @@ interface SessionInfo {
137
138
  serverUrl: string;
138
139
  transport: TransportType;
139
140
  createdAt: number;
141
+ updatedAt?: number;
140
142
  /**
141
143
  * Session readiness for auto-restore.
142
- * false means auth is pending and should be resumed explicitly by user action.
144
+ * `pending` means auth is in progress and should be resumed explicitly by user action.
143
145
  */
144
- active?: boolean;
146
+ status: SessionStatus;
145
147
  }
146
148
  interface SessionListResult {
147
149
  sessions: SessionInfo[];
@@ -83,6 +83,7 @@ type ToolInfo = {
83
83
  outputSchema?: Tool['outputSchema'];
84
84
  };
85
85
  type TransportType = 'sse' | 'streamable-http';
86
+ type SessionStatus = 'pending' | 'active';
86
87
  type McpRpcMethod = 'connect' | 'disconnect' | 'listTools' | 'callTool' | 'listSessions' | 'getSession' | 'finishAuth' | 'listPrompts' | 'getPrompt' | 'listResources' | 'readResource';
87
88
  interface McpRpcRequest {
88
89
  id: string;
@@ -126,7 +127,7 @@ interface ReadResourceParams {
126
127
  uri: string;
127
128
  }
128
129
  interface FinishAuthParams {
129
- sessionId: string;
130
+ state: string;
130
131
  code: string;
131
132
  }
132
133
  type McpRpcParams = ConnectParams | DisconnectParams | SessionParams | CallToolParams | GetPromptParams | ReadResourceParams | FinishAuthParams | undefined;
@@ -137,11 +138,12 @@ interface SessionInfo {
137
138
  serverUrl: string;
138
139
  transport: TransportType;
139
140
  createdAt: number;
141
+ updatedAt?: number;
140
142
  /**
141
143
  * Session readiness for auto-restore.
142
- * false means auth is pending and should be resumed explicitly by user action.
144
+ * `pending` means auth is in progress and should be resumed explicitly by user action.
143
145
  */
144
- active?: boolean;
146
+ status: SessionStatus;
145
147
  }
146
148
  interface SessionListResult {
147
149
  sessions: SessionInfo[];
@@ -12,17 +12,38 @@ CREATE TABLE IF NOT EXISTS public.mcp_sessions (
12
12
  callback_url TEXT NOT NULL,
13
13
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
14
14
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
15
- expires_at TIMESTAMPTZ NOT NULL,
16
- active BOOLEAN DEFAULT false,
15
+ expires_at TIMESTAMPTZ,
16
+ status TEXT NOT NULL DEFAULT 'pending'
17
+ CHECK (status IN ('pending', 'active')),
17
18
  headers JSONB,
19
+ auth_url TEXT,
20
+ CONSTRAINT mcp_sessions_user_session_unique
21
+ UNIQUE (user_id, session_id)
22
+ );
23
+
24
+ CREATE TABLE IF NOT EXISTS public.mcp_credentials (
25
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
26
+ session_id TEXT NOT NULL,
27
+ user_id TEXT NOT NULL,
18
28
  client_information JSONB,
19
29
  tokens JSONB,
20
30
  code_verifier TEXT,
21
- client_id TEXT
31
+ client_id TEXT,
32
+ oauth_state JSONB,
33
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
34
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
35
+ CONSTRAINT mcp_credentials_session_fk
36
+ FOREIGN KEY (user_id, session_id)
37
+ REFERENCES public.mcp_sessions(user_id, session_id)
38
+ ON DELETE CASCADE,
39
+ CONSTRAINT mcp_credentials_user_session_unique
40
+ UNIQUE (user_id, session_id)
22
41
  );
23
42
 
24
43
  CREATE INDEX IF NOT EXISTS idx_mcp_sessions_user_id ON public.mcp_sessions(user_id);
25
44
  CREATE INDEX IF NOT EXISTS idx_mcp_sessions_expires_at ON public.mcp_sessions(expires_at);
45
+ CREATE INDEX IF NOT EXISTS idx_mcp_credentials_user_session
46
+ ON public.mcp_credentials(user_id, session_id);
26
47
 
27
48
  CREATE OR REPLACE FUNCTION public.set_current_timestamp_updated_at()
28
49
  RETURNS TRIGGER AS $$
@@ -39,6 +60,13 @@ BEFORE UPDATE ON public.mcp_sessions
39
60
  FOR EACH ROW
40
61
  EXECUTE FUNCTION public.set_current_timestamp_updated_at();
41
62
 
63
+ DROP TRIGGER IF EXISTS trg_mcp_credentials_updated_at ON public.mcp_credentials;
64
+
65
+ CREATE TRIGGER trg_mcp_credentials_updated_at
66
+ BEFORE UPDATE ON public.mcp_credentials
67
+ FOR EACH ROW
68
+ EXECUTE FUNCTION public.set_current_timestamp_updated_at();
69
+
42
70
  -- Optional production configuration:
43
71
  -- Create a dedicated app role and use its credentials in NEON_DATABASE_URL.
44
72
  -- Replace neondb and the password before running.
@@ -47,6 +75,7 @@ EXECUTE FUNCTION public.set_current_timestamp_updated_at();
47
75
  -- GRANT CONNECT ON DATABASE neondb TO mcp_service_role;
48
76
  -- GRANT USAGE ON SCHEMA public TO mcp_service_role;
49
77
  -- GRANT SELECT, INSERT, UPDATE, DELETE ON public.mcp_sessions TO mcp_service_role;
78
+ -- GRANT SELECT, INSERT, UPDATE, DELETE ON public.mcp_credentials TO mcp_service_role;
50
79
  -- GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO mcp_service_role;
51
80
 
52
81
  -- Optional RLS configuration:
@@ -60,6 +89,7 @@ EXECUTE FUNCTION public.set_current_timestamp_updated_at();
60
89
  -- GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO mcp_service_role;
61
90
  --
62
91
  -- ALTER TABLE public.mcp_sessions ENABLE ROW LEVEL SECURITY;
92
+ -- ALTER TABLE public.mcp_credentials ENABLE ROW LEVEL SECURITY;
63
93
  --
64
94
  -- CREATE POLICY mcp_service_role_full_access
65
95
  -- ON public.mcp_sessions
@@ -67,3 +97,10 @@ EXECUTE FUNCTION public.set_current_timestamp_updated_at();
67
97
  -- TO mcp_service_role
68
98
  -- USING (true)
69
99
  -- WITH CHECK (true);
100
+ --
101
+ -- CREATE POLICY mcp_service_role_oauth_full_access
102
+ -- ON public.mcp_credentials
103
+ -- FOR ALL
104
+ -- TO mcp_service_role
105
+ -- USING (true)
106
+ -- WITH CHECK (true);
@@ -18,12 +18,12 @@ WHERE jobname IN (
18
18
  );
19
19
 
20
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.
21
+ -- Removes abandoned OAuth flows and other transient
22
+ -- sessions whose pending expiration has passed.
23
23
  SELECT cron.schedule(
24
24
  'mcp-cleanup-transient-sessions',
25
25
  '*/5 * * * *',
26
- $$DELETE FROM public.mcp_sessions WHERE expires_at < now() AND active IS NOT TRUE;$$
26
+ $$DELETE FROM public.mcp_sessions WHERE expires_at IS NOT NULL AND expires_at < now() AND status <> 'active';$$
27
27
  );
28
28
 
29
29
  -- Stage 2: Long-term Dormancy Eviction (daily at midnight UTC)
@@ -31,5 +31,5 @@ SELECT cron.schedule(
31
31
  SELECT cron.schedule(
32
32
  'mcp-cleanup-dormant-sessions',
33
33
  '0 0 * * *',
34
- $$DELETE FROM public.mcp_sessions WHERE active = true AND updated_at < now() - interval '30 days';$$
34
+ $$DELETE FROM public.mcp_sessions WHERE status = 'active' AND updated_at < now() - interval '30 days';$$
35
35
  );
@@ -10,19 +10,41 @@ CREATE TABLE IF NOT EXISTS public.mcp_sessions (
10
10
  callback_url TEXT NOT NULL,
11
11
  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
12
12
  updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
13
- expires_at TIMESTAMPTZ NOT NULL,
14
- active BOOLEAN DEFAULT false,
13
+ expires_at TIMESTAMPTZ,
14
+ status TEXT NOT NULL DEFAULT 'pending'
15
+ CHECK (status IN ('pending', 'active')),
15
16
  headers JSONB,
17
+ auth_url TEXT,
18
+ CONSTRAINT mcp_sessions_user_session_unique
19
+ UNIQUE (user_id, session_id)
20
+ );
21
+
22
+ -- Runtime credentials are kept separate from connection metadata.
23
+ CREATE TABLE IF NOT EXISTS public.mcp_credentials (
24
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
25
+ session_id TEXT NOT NULL,
26
+ user_id TEXT NOT NULL,
16
27
  client_information JSONB,
17
28
  tokens JSONB,
18
29
  code_verifier TEXT,
19
- client_id TEXT
30
+ client_id TEXT,
31
+ oauth_state JSONB,
32
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
33
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
34
+ CONSTRAINT mcp_credentials_session_fk
35
+ FOREIGN KEY (user_id, session_id)
36
+ REFERENCES public.mcp_sessions(user_id, session_id)
37
+ ON DELETE CASCADE,
38
+ CONSTRAINT mcp_credentials_user_session_unique
39
+ UNIQUE (user_id, session_id)
20
40
  );
21
41
 
22
42
  -- Add an index on user_id for faster lookups
23
43
  CREATE INDEX IF NOT EXISTS idx_mcp_sessions_user_id ON public.mcp_sessions(user_id);
24
44
  -- Add an index on expires_at to speed up the cleanup job
25
45
  CREATE INDEX IF NOT EXISTS idx_mcp_sessions_expires_at ON public.mcp_sessions(expires_at);
46
+ CREATE INDEX IF NOT EXISTS idx_mcp_credentials_user_session
47
+ ON public.mcp_credentials(user_id, session_id);
26
48
 
27
49
  -- Trigger to automatically update the 'updated_at' column
28
50
  CREATE OR REPLACE FUNCTION public.set_current_timestamp_updated_at()
@@ -39,8 +61,15 @@ BEFORE UPDATE ON public.mcp_sessions
39
61
  FOR EACH ROW
40
62
  EXECUTE FUNCTION public.set_current_timestamp_updated_at();
41
63
 
64
+ DROP TRIGGER IF EXISTS trg_mcp_credentials_updated_at ON public.mcp_credentials;
65
+ CREATE TRIGGER trg_mcp_credentials_updated_at
66
+ BEFORE UPDATE ON public.mcp_credentials
67
+ FOR EACH ROW
68
+ EXECUTE FUNCTION public.set_current_timestamp_updated_at();
69
+
42
70
  -- Enable Row Level Security (RLS)
43
71
  ALTER TABLE public.mcp_sessions ENABLE ROW LEVEL SECURITY;
72
+ ALTER TABLE public.mcp_credentials ENABLE ROW LEVEL SECURITY;
44
73
 
45
74
  -- Policy 1: Users can read their own sessions
46
75
  CREATE POLICY "Users can view their own sessions"
@@ -51,6 +80,14 @@ USING (
51
80
  auth.uid()::text = user_id
52
81
  );
53
82
 
83
+ CREATE POLICY "Users can view their own credentials"
84
+ ON public.mcp_credentials
85
+ FOR SELECT
86
+ TO authenticated
87
+ USING (
88
+ auth.uid()::text = user_id
89
+ );
90
+
54
91
  -- Policy 2: Users can insert their own sessions
55
92
  CREATE POLICY "Users can insert their own sessions"
56
93
  ON public.mcp_sessions
@@ -60,6 +97,14 @@ WITH CHECK (
60
97
  auth.uid()::text = user_id
61
98
  );
62
99
 
100
+ CREATE POLICY "Users can insert their own credentials"
101
+ ON public.mcp_credentials
102
+ FOR INSERT
103
+ TO authenticated
104
+ WITH CHECK (
105
+ auth.uid()::text = user_id
106
+ );
107
+
63
108
  -- Policy 3: Users can update their own sessions
64
109
  CREATE POLICY "Users can update their own sessions"
65
110
  ON public.mcp_sessions
@@ -72,6 +117,17 @@ WITH CHECK (
72
117
  auth.uid()::text = user_id
73
118
  );
74
119
 
120
+ CREATE POLICY "Users can update their own credentials"
121
+ ON public.mcp_credentials
122
+ FOR UPDATE
123
+ TO authenticated
124
+ USING (
125
+ auth.uid()::text = user_id
126
+ )
127
+ WITH CHECK (
128
+ auth.uid()::text = user_id
129
+ );
130
+
75
131
  -- Policy 4: Users can delete their own sessions
76
132
  CREATE POLICY "Users can delete their own sessions"
77
133
  ON public.mcp_sessions
@@ -80,3 +136,11 @@ TO authenticated
80
136
  USING (
81
137
  auth.uid()::text = user_id
82
138
  );
139
+
140
+ CREATE POLICY "Users can delete their own credentials"
141
+ ON public.mcp_credentials
142
+ FOR DELETE
143
+ TO authenticated
144
+ USING (
145
+ auth.uid()::text = user_id
146
+ );
@@ -5,27 +5,27 @@ CREATE EXTENSION IF NOT EXISTS pg_cron;
5
5
  -- ─────────────────────────────────────────────────────────────────────────────
6
6
  -- Stage 1: Short-term Transient Purge (every 5 minutes)
7
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
8
+ -- Targets transient sessions (pending OAuth or setup states) whose
9
+ -- expiration has passed. Active sessions are explicitly
10
10
  -- excluded from this sweep to preserve automation credentials.
11
11
  --
12
12
  -- The idx_mcp_sessions_expires_at index ensures this is a fast indexed scan.
13
13
  SELECT cron.schedule(
14
14
  'cleanup-transient-sessions',
15
15
  '*/5 * * * *',
16
- $$DELETE FROM public.mcp_sessions WHERE expires_at < now() AND active IS NOT TRUE;$$
16
+ $$DELETE FROM public.mcp_sessions WHERE expires_at IS NOT NULL AND expires_at < now() AND status <> 'active';$$
17
17
  );
18
18
 
19
19
  -- ─────────────────────────────────────────────────────────────────────────────
20
20
  -- Stage 2: Long-term Dormancy Eviction (daily at midnight UTC)
21
21
  -- ─────────────────────────────────────────────────────────────────────────────
22
- -- Safety net for sessions that were successfully established (active = true)
23
- -- but have been completely untouched for 30+ days. This prevents "active"
22
+ -- Safety net for sessions that were successfully established (status = 'active')
23
+ -- but have been completely untouched for 30+ days. This prevents active
24
24
  -- records from persisting indefinitely if they are genuinely abandoned.
25
25
  SELECT cron.schedule(
26
26
  'cleanup-dormant-sessions',
27
27
  '0 0 * * *',
28
- $$DELETE FROM public.mcp_sessions WHERE active = true AND updated_at < now() - interval '30 days';$$
28
+ $$DELETE FROM public.mcp_sessions WHERE status = 'active' AND updated_at < now() - interval '30 days';$$
29
29
  );
30
30
 
31
31
  -- Add a comment on the extension for visibility in Supabase Dashboard
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@mcp-ts/sdk",
3
- "version": "2.3.4",
3
+ "version": "2.4.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "description": "A lightweight MCP (Model Context Protocol) client library for JavaScript and cross-runtime environments, supporting MCP Apps in host applications and multiple storage backends (Memory, File, Redis, Supabase, Neon).",
7
+ "description": "A lightweight MCP client library for TypeScript with durable sessions across Redis, Supabase, Neon, and SQLite, cross-runtime agent support, and dynamic tool discovery to reduce LLM context usage.",
8
8
  "main": "./dist/index.js",
9
9
  "module": "./dist/index.mjs",
10
10
  "types": "./dist/index.d.ts",
@@ -102,7 +102,17 @@
102
102
  "react",
103
103
  "hook",
104
104
  "anthropic",
105
- "claude"
105
+ "claude",
106
+ "typescript",
107
+ "agent",
108
+ "agents",
109
+ "durable-sessions",
110
+ "tool-discovery",
111
+ "tool-router",
112
+ "context-window",
113
+ "sqlite",
114
+ "supabase",
115
+ "neon"
106
116
  ],
107
117
  "author": "MCP Assistant Contributors",
108
118
  "license": "MIT",
@@ -117,8 +117,8 @@ export class SSEClient {
117
117
  return this.sendRequest<GetSessionResult>('getSession', { sessionId });
118
118
  }
119
119
 
120
- async finishAuth(sessionId: string, code: string): Promise<FinishAuthResult> {
121
- return this.sendRequest<FinishAuthResult>('finishAuth', { sessionId, code });
120
+ async finishAuth(state: string, code: string): Promise<FinishAuthResult> {
121
+ return this.sendRequest<FinishAuthResult>('finishAuth', { state, code });
122
122
  }
123
123
 
124
124
  async listPrompts(sessionId: string): Promise<ListPromptsResult> {
@@ -8,7 +8,7 @@ export { useMcp, type UseMcpOptions, type McpClient, type McpConnection } from '
8
8
 
9
9
  // Optional OAuth popup conveniences. These are not required for auth:
10
10
  // consumers can still provide their own onRedirect handler, callback page UI,
11
- // or complete `finishAuth(sessionId, code)` from a normal redirect flow.
11
+ // or complete `finishAuth(state, code)` from a normal redirect flow.
12
12
  export {
13
13
  useMcpOAuthPopup,
14
14
  openCenteredPopup,
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react';
2
+ import { parseOAuthState } from '../../shared/utils.js';
2
3
 
3
4
  export interface OAuthPopupConnectionLike {
4
5
  sessionId: string;
@@ -11,7 +12,7 @@ export interface OAuthPopupConnectionLike {
11
12
  *
12
13
  * These utilities sit on top of the core MCP auth primitives:
13
14
  * - `useMcp({ onRedirect })` to decide how auth navigation happens
14
- * - `finishAuth(sessionId, code)` to complete code exchange
15
+ * - `finishAuth(state, code)` to complete code exchange
15
16
  *
16
17
  * Consumers are free to:
17
18
  * - use these helpers as-is for a turnkey popup flow
@@ -60,6 +61,7 @@ function postPopupResult(
60
61
  popupWindow: WindowProxy | null,
61
62
  result: {
62
63
  sessionId?: string;
64
+ state?: string;
63
65
  success: boolean;
64
66
  error?: string;
65
67
  }
@@ -150,9 +152,9 @@ export function createOAuthPopupRedirectHandler(
150
152
  */
151
153
  export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
152
154
  connections: TConnection[],
153
- finishAuth: (sessionId: string, code: string) => Promise<unknown>
155
+ finishAuth: (state: string, code: string) => Promise<unknown>
154
156
  ): void {
155
- const pendingPopupsRef = useRef<Map<string, WindowProxy>>(new Map());
157
+ const pendingPopupsRef = useRef<Map<string, { popupWindow: WindowProxy | null; state: string }>>(new Map());
156
158
  const processingCodesRef = useRef<Set<string>>(new Set());
157
159
 
158
160
  useEffect(() => {
@@ -169,10 +171,16 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
169
171
  const popupWindow = event.source && 'postMessage' in event.source
170
172
  ? event.source as WindowProxy
171
173
  : null;
172
- const targetSessionId = typeof event.data.sessionId === 'string' ? event.data.sessionId : '';
174
+ const rawState =
175
+ typeof event.data.state === 'string'
176
+ ? event.data.state
177
+ : typeof event.data.sessionId === 'string'
178
+ ? event.data.sessionId
179
+ : '';
180
+ const targetSessionId = rawState ? parseOAuthState(rawState)?.sessionId || rawState : '';
173
181
 
174
182
  if (popupWindow && targetSessionId) {
175
- pendingPopupsRef.current.set(targetSessionId, popupWindow);
183
+ pendingPopupsRef.current.set(targetSessionId, { popupWindow, state: rawState });
176
184
  }
177
185
 
178
186
  if (!targetSessionId) {
@@ -190,6 +198,7 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
190
198
  if (popupWindow) {
191
199
  postPopupResult(popupWindow, {
192
200
  sessionId: targetSessionId,
201
+ state: rawState,
193
202
  success: false,
194
203
  error: 'OAuth session not found in the current client state',
195
204
  });
@@ -204,13 +213,14 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
204
213
  processingCodesRef.current.add(codeKey);
205
214
 
206
215
  try {
207
- await finishAuth(targetSession.sessionId, code);
216
+ await finishAuth(rawState, code);
208
217
  } catch (error) {
209
218
  processingCodesRef.current.delete(codeKey);
210
219
  pendingPopupsRef.current.delete(targetSession.sessionId);
211
220
  if (popupWindow) {
212
221
  postPopupResult(popupWindow, {
213
- sessionId: targetSession.sessionId,
222
+ sessionId: rawState,
223
+ state: rawState,
214
224
  success: false,
215
225
  error: error instanceof Error ? error.message : 'Failed to finish auth',
216
226
  });
@@ -237,11 +247,14 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
237
247
 
238
248
  useEffect(() => {
239
249
  for (const connection of connections) {
240
- const popupWindow = pendingPopupsRef.current.get(connection.sessionId) || null;
250
+ const pendingPopup = pendingPopupsRef.current.get(connection.sessionId);
251
+ const popupWindow = pendingPopup?.popupWindow || null;
252
+ const resultState = pendingPopup?.state || connection.sessionId;
241
253
 
242
254
  if (connection.state === 'AUTHENTICATED' || connection.state === 'READY' || connection.state === 'CONNECTED') {
243
255
  postPopupResult(popupWindow, {
244
- sessionId: connection.sessionId,
256
+ sessionId: resultState,
257
+ state: resultState,
245
258
  success: true,
246
259
  });
247
260
  for (const codeKey of processingCodesRef.current) {
@@ -255,7 +268,8 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
255
268
 
256
269
  if (connection.state === 'FAILED') {
257
270
  postPopupResult(popupWindow, {
258
- sessionId: connection.sessionId,
271
+ sessionId: resultState,
272
+ state: resultState,
259
273
  success: false,
260
274
  error: connection.error || 'Failed to complete authorization',
261
275
  });
@@ -277,7 +291,7 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
277
291
  * opener window, waits for success/failure, and closes the popup on success.
278
292
  *
279
293
  * It is intentionally optional: apps can replace it with their own callback
280
- * page UI or skip popup auth entirely and call `finishAuth(sessionId, code)`
294
+ * page UI or skip popup auth entirely and call `finishAuth(state, code)`
281
295
  * from any callback route they control.
282
296
  */
283
297
  export function McpOAuthCallbackContent({
@@ -328,7 +342,14 @@ export function McpOAuthCallbackContent({
328
342
  return;
329
343
  }
330
344
 
331
- if (event.data.sessionId !== sessionId) {
345
+ const resultState =
346
+ typeof event.data.state === 'string'
347
+ ? event.data.state
348
+ : typeof event.data.sessionId === 'string'
349
+ ? event.data.sessionId
350
+ : '';
351
+
352
+ if (resultState !== sessionId) {
332
353
  return;
333
354
  }
334
355
 
@@ -352,7 +373,7 @@ export function McpOAuthCallbackContent({
352
373
  window.addEventListener('message', handleResult);
353
374
  channel?.addEventListener('message', handleResult);
354
375
 
355
- const payload = { type: AUTH_CODE_MESSAGE, code, sessionId };
376
+ const payload = { type: AUTH_CODE_MESSAGE, code, state: sessionId, sessionId };
356
377
 
357
378
  if (window.opener) {
358
379
  try {