@mcp-ts/sdk 1.6.2 → 2.0.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 +12 -6
- package/dist/adapters/agui-adapter.d.mts +3 -3
- package/dist/adapters/agui-adapter.d.ts +3 -3
- package/dist/adapters/agui-adapter.js +4 -5
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +4 -5
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- 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 +9 -3
- package/dist/adapters/ai-adapter.d.ts +9 -3
- package/dist/adapters/ai-adapter.js +20 -6
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs +20 -6
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +3 -3
- package/dist/adapters/langchain-adapter.d.ts +3 -3
- package/dist/adapters/langchain-adapter.js +9 -6
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +9 -6
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/adapters/mastra-adapter.js +5 -1
- package/dist/adapters/mastra-adapter.js.map +1 -1
- package/dist/adapters/mastra-adapter.mjs +5 -1
- package/dist/adapters/mastra-adapter.mjs.map +1 -1
- package/dist/bin/mcp-ts.js +7 -1
- package/dist/bin/mcp-ts.js.map +1 -1
- package/dist/bin/mcp-ts.mjs +7 -1
- package/dist/bin/mcp-ts.mjs.map +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +9 -13
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +9 -13
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +7 -7
- package/dist/client/react.d.ts +7 -7
- package/dist/client/react.js +15 -19
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +15 -19
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +7 -7
- package/dist/client/vue.d.ts +7 -7
- package/dist/client/vue.js +14 -18
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +14 -18
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-DhA-OEAe.d.ts → index-C9gvpxy5.d.ts} +5 -5
- package/dist/{index-bFL4ZF2N.d.mts → index-eaH14_5u.d.mts} +5 -5
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +616 -370
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +615 -370
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-CHE8QpVE.d.ts → multi-session-client-BYtguGJm.d.ts} +22 -22
- package/dist/{multi-session-client-CQsRbxYI.d.mts → multi-session-client-DYNe6az3.d.mts} +22 -22
- package/dist/server/index.d.mts +31 -34
- package/dist/server/index.d.ts +31 -34
- package/dist/server/index.js +531 -256
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +530 -256
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +5 -5
- package/dist/shared/index.d.ts +5 -5
- package/dist/shared/index.js +76 -101
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +76 -101
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-Dh2804tM.d.ts → tool-router-Ddtybmr0.d.ts} +71 -73
- package/dist/{tool-router-BVaV1udm.d.mts → tool-router-Dnd6IOKC.d.mts} +71 -73
- package/dist/{types-rIuN1CQi.d.mts → types-BCAG20P6.d.mts} +4 -4
- package/dist/{types-rIuN1CQi.d.ts → types-BCAG20P6.d.ts} +4 -4
- package/dist/{utils-0qmYrqoa.d.mts → utils-DELRKQPU.d.mts} +1 -1
- package/dist/{utils-0qmYrqoa.d.ts → utils-DELRKQPU.d.ts} +1 -1
- package/migrations/neon/20260513010000_install_mcp_sessions.sql +69 -0
- package/migrations/neon/20260513020000_add_session_cleanup_cron.sql +35 -0
- package/{supabase/migrations → migrations/supabase}/20260330195700_install_mcp_sessions.sql +7 -9
- package/package.json +14 -5
- package/src/adapters/ai-adapter.ts +30 -1
- package/src/adapters/langchain-adapter.ts +6 -2
- package/src/adapters/mastra-adapter.ts +6 -2
- package/src/bin/mcp-ts.ts +8 -1
- package/src/client/core/app-host.ts +1 -1
- package/src/client/core/sse-client.ts +12 -14
- package/src/client/core/types.ts +1 -1
- package/src/client/react/use-mcp-apps.tsx +1 -1
- package/src/client/react/use-mcp.ts +11 -11
- package/src/client/vue/use-mcp.ts +10 -10
- package/src/server/handlers/nextjs-handler.ts +18 -15
- package/src/server/handlers/sse-handler.ts +29 -29
- package/src/server/index.ts +1 -1
- package/src/server/mcp/multi-session-client.ts +17 -17
- package/src/server/mcp/oauth-client.ts +37 -37
- package/src/server/mcp/storage-oauth-provider.ts +17 -17
- package/src/server/storage/file-backend.ts +25 -25
- package/src/server/storage/index.ts +67 -10
- package/src/server/storage/memory-backend.ts +34 -34
- package/src/server/storage/neon-backend.ts +281 -0
- package/src/server/storage/redis-backend.ts +64 -64
- package/src/server/storage/sqlite-backend.ts +33 -33
- package/src/server/storage/supabase-backend.ts +23 -24
- package/src/server/storage/types.ts +18 -21
- package/src/shared/errors.ts +1 -1
- package/src/shared/index.ts +1 -2
- package/src/shared/meta-tools.ts +4 -6
- package/src/shared/schema-compressor.ts +2 -42
- package/src/shared/tool-index.ts +89 -84
- package/src/shared/tool-router.ts +0 -24
- package/src/shared/types.ts +4 -4
- /package/{supabase/migrations → migrations/supabase}/20260421010000_add_session_cleanup_cron.sql +0 -0
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-ts/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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).",
|
|
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).",
|
|
8
8
|
"main": "./dist/index.js",
|
|
9
9
|
"module": "./dist/index.mjs",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"files": [
|
|
72
72
|
"dist",
|
|
73
73
|
"src",
|
|
74
|
-
"
|
|
74
|
+
"migrations",
|
|
75
75
|
"README.md",
|
|
76
76
|
"LICENSE"
|
|
77
77
|
],
|
|
@@ -117,13 +117,15 @@
|
|
|
117
117
|
"peerDependencies": {
|
|
118
118
|
"@ag-ui/client": ">=0.0.40",
|
|
119
119
|
"@langchain/core": "^1.1.39",
|
|
120
|
+
"@neondatabase/serverless": "^1.1.0",
|
|
120
121
|
"@supabase/supabase-js": "^2.0.0",
|
|
121
122
|
"ai": "^6.0.0",
|
|
122
123
|
"better-sqlite3": "^12.0.0",
|
|
123
124
|
"ioredis": "^5.0.0",
|
|
124
125
|
"react": ">=18.0.0",
|
|
125
126
|
"rxjs": ">=7.0.0",
|
|
126
|
-
"zod": "^3.23.0"
|
|
127
|
+
"zod": "^3.23.0",
|
|
128
|
+
"json-schema-to-zod": "^2.7.0"
|
|
127
129
|
},
|
|
128
130
|
"peerDependenciesMeta": {
|
|
129
131
|
"react": {
|
|
@@ -135,6 +137,9 @@
|
|
|
135
137
|
"@langchain/core": {
|
|
136
138
|
"optional": true
|
|
137
139
|
},
|
|
140
|
+
"@neondatabase/serverless": {
|
|
141
|
+
"optional": true
|
|
142
|
+
},
|
|
138
143
|
"zod": {
|
|
139
144
|
"optional": true
|
|
140
145
|
},
|
|
@@ -152,18 +157,21 @@
|
|
|
152
157
|
},
|
|
153
158
|
"@supabase/supabase-js": {
|
|
154
159
|
"optional": true
|
|
160
|
+
},
|
|
161
|
+
"json-schema-to-zod": {
|
|
162
|
+
"optional": true
|
|
155
163
|
}
|
|
156
164
|
},
|
|
157
165
|
"dependencies": {
|
|
158
166
|
"@modelcontextprotocol/ext-apps": "^1.5.0",
|
|
159
167
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
160
168
|
"json-schema": "^0.4.0",
|
|
161
|
-
"json-schema-to-zod": "^2.7.0",
|
|
162
169
|
"nanoid": "^5.1.6"
|
|
163
170
|
},
|
|
164
171
|
"devDependencies": {
|
|
165
172
|
"@ag-ui/client": "^0.0.52",
|
|
166
173
|
"@langchain/core": "^1.1.39",
|
|
174
|
+
"@neondatabase/serverless": "^1.1.0",
|
|
167
175
|
"@playwright/test": "^1.58.0",
|
|
168
176
|
"@supabase/supabase-js": "^2.48.0",
|
|
169
177
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -175,6 +183,7 @@
|
|
|
175
183
|
"better-sqlite3": "^12.6.2",
|
|
176
184
|
"ioredis": "^5.9.2",
|
|
177
185
|
"ioredis-mock": "^8.13.1",
|
|
186
|
+
"json-schema-to-zod": "^2.7.0",
|
|
178
187
|
"playwright": "^1.58.0",
|
|
179
188
|
"react": "^18.3.1",
|
|
180
189
|
"react-dom": "^18.3.1",
|
|
@@ -22,6 +22,13 @@ export interface AIAdapterOptions {
|
|
|
22
22
|
* When not provided, all tools are returned as before (backward-compatible).
|
|
23
23
|
*/
|
|
24
24
|
toolRouter?: ToolRouter;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Optional custom callback to determine if a tool requires user approval.
|
|
28
|
+
* Can return a boolean or a Promise<boolean>.
|
|
29
|
+
* If not provided, defaults to checking the tool's `destructiveHint` annotation.
|
|
30
|
+
*/
|
|
31
|
+
needsApproval?: (tool: any, args: any) => boolean | Promise<boolean>;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
/**
|
|
@@ -80,7 +87,12 @@ export class AIAdapter {
|
|
|
80
87
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
81
88
|
throw new Error(`Tool execution failed: ${errorMessage}`);
|
|
82
89
|
}
|
|
83
|
-
}
|
|
90
|
+
},
|
|
91
|
+
needsApproval: this.options.needsApproval
|
|
92
|
+
? (args: any) => this.options.needsApproval!(tool, args)
|
|
93
|
+
: (tool.annotations as any)?.destructiveHint === true
|
|
94
|
+
? () => true
|
|
95
|
+
: undefined
|
|
84
96
|
}
|
|
85
97
|
];
|
|
86
98
|
})
|
|
@@ -166,6 +178,23 @@ export class AIAdapter {
|
|
|
166
178
|
// route directly to the correct MCP client
|
|
167
179
|
return await router.callTool(tool.name, args, namespace);
|
|
168
180
|
},
|
|
181
|
+
needsApproval: this.options.needsApproval
|
|
182
|
+
? (args: any) => this.options.needsApproval!(tool, args)
|
|
183
|
+
: (args: any) => {
|
|
184
|
+
// Default HITL logic using annotations
|
|
185
|
+
if (tool.name === 'mcp_execute_tool') {
|
|
186
|
+
const targetToolName = String(args?.toolName ?? "");
|
|
187
|
+
const targetNamespace = String(args?.serverId ?? "") || undefined;
|
|
188
|
+
if (!targetToolName) return false;
|
|
189
|
+
try {
|
|
190
|
+
const targetTool = router.getToolSchema(targetToolName, targetNamespace);
|
|
191
|
+
return (targetTool as any)?.annotations?.destructiveHint === true;
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return (tool.annotations as any)?.destructiveHint === true;
|
|
197
|
+
}
|
|
169
198
|
},
|
|
170
199
|
];
|
|
171
200
|
})
|
|
@@ -96,9 +96,13 @@ export class LangChainAdapter {
|
|
|
96
96
|
const zodSchemaString = parseSchema(schema);
|
|
97
97
|
// eslint-disable-next-line
|
|
98
98
|
return new Function('z', 'return ' + zodSchemaString)(this.z);
|
|
99
|
-
} catch (error) {
|
|
99
|
+
} catch (error: any) {
|
|
100
100
|
// Fallback: Accept any object if conversion fails
|
|
101
|
-
|
|
101
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
102
|
+
console.warn('[LangChainAdapter] json-schema-to-zod is not installed. To improve type checking, install it with: npm install json-schema-to-zod');
|
|
103
|
+
} else {
|
|
104
|
+
console.warn('[LangChainAdapter] Failed to convert JSON Schema to Zod, using fallback:', error);
|
|
105
|
+
}
|
|
102
106
|
return this.z!.record(this.z!.any()).optional().describe("Dynamic Input");
|
|
103
107
|
}
|
|
104
108
|
}
|
|
@@ -87,9 +87,13 @@ export class MastraAdapter {
|
|
|
87
87
|
const zodSchemaString = parseSchema(schema);
|
|
88
88
|
// eslint-disable-next-line
|
|
89
89
|
return new Function('z', 'return ' + zodSchemaString)(this.z);
|
|
90
|
-
} catch (error) {
|
|
90
|
+
} catch (error: any) {
|
|
91
91
|
// Fallback: Accept any object if conversion fails
|
|
92
|
-
|
|
92
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
93
|
+
console.warn('[MastraAdapter] json-schema-to-zod is not installed. To improve type checking, install it with: npm install json-schema-to-zod');
|
|
94
|
+
} else {
|
|
95
|
+
console.warn('[MastraAdapter] Failed to convert JSON Schema to Zod, using fallback:', error);
|
|
96
|
+
}
|
|
93
97
|
return this.z!.record(this.z!.any()).optional().describe("Dynamic Input");
|
|
94
98
|
}
|
|
95
99
|
}
|
package/src/bin/mcp-ts.ts
CHANGED
|
@@ -35,7 +35,10 @@ async function initSupabase() {
|
|
|
35
35
|
// The supabase/ migrations are at the root of the package.
|
|
36
36
|
// We need to look up two levels to find 'supabase' folder in the package.
|
|
37
37
|
const pkgRoot = path.resolve(__dirname, '../..');
|
|
38
|
-
const sourceDir =
|
|
38
|
+
const sourceDir = resolveFirstExistingPath([
|
|
39
|
+
path.join(pkgRoot, 'migrations', 'supabase'),
|
|
40
|
+
path.join(pkgRoot, 'supabase', 'migrations'),
|
|
41
|
+
]);
|
|
39
42
|
|
|
40
43
|
if (!fs.existsSync(sourceDir)) {
|
|
41
44
|
console.error(`❌ Error: Could not find migration files in package at: ${sourceDir}`);
|
|
@@ -96,6 +99,10 @@ async function initSupabase() {
|
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
|
|
102
|
+
function resolveFirstExistingPath(paths: string[]): string {
|
|
103
|
+
return paths.find(candidate => fs.existsSync(candidate)) || paths[0];
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
run().catch(err => {
|
|
100
107
|
console.error(err);
|
|
101
108
|
process.exit(1);
|
|
@@ -559,7 +559,7 @@ export class AppHost {
|
|
|
559
559
|
private async getSessionId(): Promise<string | undefined> {
|
|
560
560
|
if (this.sessionId) return this.sessionId;
|
|
561
561
|
if (!this.client) return undefined;
|
|
562
|
-
const result = await this.client.
|
|
562
|
+
const result = await this.client.listSessions();
|
|
563
563
|
return result.sessions?.[0]?.sessionId;
|
|
564
564
|
}
|
|
565
565
|
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
SessionListResult,
|
|
21
21
|
ConnectResult,
|
|
22
22
|
DisconnectResult,
|
|
23
|
-
|
|
23
|
+
GetSessionResult,
|
|
24
24
|
FinishAuthResult,
|
|
25
25
|
ListToolsRpcResult,
|
|
26
26
|
ListPromptsResult,
|
|
@@ -32,7 +32,7 @@ export interface SSEClientOptions {
|
|
|
32
32
|
url: string;
|
|
33
33
|
|
|
34
34
|
/** User/Client identifier */
|
|
35
|
-
|
|
35
|
+
userId: string;
|
|
36
36
|
|
|
37
37
|
/** Optional auth token for authenticated requests */
|
|
38
38
|
authToken?: string;
|
|
@@ -87,8 +87,8 @@ export class SSEClient {
|
|
|
87
87
|
return this.connected;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
async
|
|
91
|
-
return this.sendRequest<SessionListResult>('
|
|
90
|
+
async listSessions(): Promise<SessionListResult> {
|
|
91
|
+
return this.sendRequest<SessionListResult>('listSessions');
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
async connectToServer(params: ConnectParams): Promise<ConnectResult> {
|
|
@@ -113,8 +113,8 @@ export class SSEClient {
|
|
|
113
113
|
return result;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
async
|
|
117
|
-
return this.sendRequest<
|
|
116
|
+
async getSession(sessionId: string): Promise<GetSessionResult> {
|
|
117
|
+
return this.sendRequest<GetSessionResult>('getSession', { sessionId });
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
async finishAuth(sessionId: string, code: string): Promise<FinishAuthResult> {
|
|
@@ -198,7 +198,7 @@ export class SSEClient {
|
|
|
198
198
|
const data = await this.readRpcResponseFromStream(response, {
|
|
199
199
|
delayConnectionEvents:
|
|
200
200
|
method === 'connect' ||
|
|
201
|
-
method === '
|
|
201
|
+
method === 'getSession' ||
|
|
202
202
|
method === 'finishAuth',
|
|
203
203
|
});
|
|
204
204
|
return this.parseRpcResponse<T>(data);
|
|
@@ -311,22 +311,20 @@ export class SSEClient {
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
private buildUrl(): string {
|
|
314
|
-
|
|
315
|
-
url.searchParams.set('identity', this.options.identity);
|
|
316
|
-
if (this.options.authToken) {
|
|
317
|
-
url.searchParams.set('token', this.options.authToken);
|
|
318
|
-
}
|
|
319
|
-
return url.toString();
|
|
314
|
+
return new URL(this.options.url, globalThis.location?.origin).toString();
|
|
320
315
|
}
|
|
321
316
|
|
|
322
317
|
private buildHeaders(): HeadersInit {
|
|
323
|
-
const headers:
|
|
318
|
+
const headers: Record<string, string> = {
|
|
324
319
|
'Content-Type': 'application/json',
|
|
325
320
|
'Accept': 'text/event-stream',
|
|
321
|
+
'x-mcp-user-id': this.options.userId,
|
|
326
322
|
};
|
|
323
|
+
|
|
327
324
|
if (this.options.authToken) {
|
|
328
325
|
headers['Authorization'] = `Bearer ${this.options.authToken}`;
|
|
329
326
|
}
|
|
327
|
+
|
|
330
328
|
return headers;
|
|
331
329
|
}
|
|
332
330
|
|
package/src/client/core/types.ts
CHANGED
|
@@ -72,7 +72,7 @@ export interface McpAppRendererProps extends Pick<UseAppHostOptions, 'sandbox' |
|
|
|
72
72
|
|
|
73
73
|
type McpAppViewProps = McpAppRendererProps & {
|
|
74
74
|
/**
|
|
75
|
-
* Ref avoids tying `McpAppRenderer`
|
|
75
|
+
* Ref avoids tying `McpAppRenderer` userId to `mcpClient`: when `connections` updates, `useMcp()` still
|
|
76
76
|
* returns a new object (correct for `useEffect` deps), but the iframe must not remount.
|
|
77
77
|
*/
|
|
78
78
|
clientRef: MutableRefObject<McpClient | null>;
|
|
@@ -25,7 +25,7 @@ export interface UseMcpOptions {
|
|
|
25
25
|
/**
|
|
26
26
|
* User/Client identifier
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
userId: string;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Optional auth token
|
|
@@ -110,7 +110,7 @@ export interface McpClient {
|
|
|
110
110
|
serverName: string;
|
|
111
111
|
serverUrl: string;
|
|
112
112
|
callbackUrl: string;
|
|
113
|
-
transportType?: 'sse' | '
|
|
113
|
+
transportType?: 'sse' | 'streamable-http';
|
|
114
114
|
}) => Promise<string>;
|
|
115
115
|
|
|
116
116
|
/**
|
|
@@ -126,7 +126,7 @@ export interface McpClient {
|
|
|
126
126
|
serverName: string;
|
|
127
127
|
serverUrl: string;
|
|
128
128
|
callbackUrl: string;
|
|
129
|
-
transportType?: 'sse' | '
|
|
129
|
+
transportType?: 'sse' | 'streamable-http';
|
|
130
130
|
}) => Promise<string>;
|
|
131
131
|
|
|
132
132
|
/**
|
|
@@ -220,7 +220,7 @@ export interface McpClient {
|
|
|
220
220
|
export function useMcp(options: UseMcpOptions): McpClient {
|
|
221
221
|
const {
|
|
222
222
|
url,
|
|
223
|
-
|
|
223
|
+
userId,
|
|
224
224
|
authToken,
|
|
225
225
|
autoConnect = true,
|
|
226
226
|
autoInitialize = true,
|
|
@@ -249,7 +249,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
249
249
|
|
|
250
250
|
const clientOptions: SSEClientOptions = {
|
|
251
251
|
url,
|
|
252
|
-
|
|
252
|
+
userId,
|
|
253
253
|
authToken,
|
|
254
254
|
onConnectionEvent: (event) => {
|
|
255
255
|
// Update local state based on event
|
|
@@ -285,7 +285,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
285
285
|
isMountedRef.current = false;
|
|
286
286
|
client.disconnect();
|
|
287
287
|
};
|
|
288
|
-
}, [url,
|
|
288
|
+
}, [url, userId, authToken, autoConnect, autoInitialize]);
|
|
289
289
|
|
|
290
290
|
/**
|
|
291
291
|
* Update connections based on event
|
|
@@ -441,7 +441,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
441
441
|
try {
|
|
442
442
|
setIsInitializing(true);
|
|
443
443
|
|
|
444
|
-
const result = await clientRef.current.
|
|
444
|
+
const result = await clientRef.current.listSessions();
|
|
445
445
|
const sessions = result.sessions || [];
|
|
446
446
|
|
|
447
447
|
// Initialize connections
|
|
@@ -470,7 +470,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
470
470
|
return;
|
|
471
471
|
}
|
|
472
472
|
suppressAuthRedirectSessionsRef.current.add(session.sessionId);
|
|
473
|
-
await clientRef.current.
|
|
473
|
+
await clientRef.current.getSession(session.sessionId);
|
|
474
474
|
} catch (error) {
|
|
475
475
|
console.error(`[useMcp] Failed to validate session ${session.sessionId}:`, error);
|
|
476
476
|
} finally {
|
|
@@ -498,7 +498,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
498
498
|
serverName: string;
|
|
499
499
|
serverUrl: string;
|
|
500
500
|
callbackUrl: string;
|
|
501
|
-
transportType?: 'sse' | '
|
|
501
|
+
transportType?: 'sse' | 'streamable-http';
|
|
502
502
|
}): Promise<string> => {
|
|
503
503
|
if (!clientRef.current) {
|
|
504
504
|
throw new Error('SSE client not initialized');
|
|
@@ -519,7 +519,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
519
519
|
serverName: string;
|
|
520
520
|
serverUrl: string;
|
|
521
521
|
callbackUrl: string;
|
|
522
|
-
transportType?: 'sse' | '
|
|
522
|
+
transportType?: 'sse' | 'streamable-http';
|
|
523
523
|
}): Promise<string> => {
|
|
524
524
|
if (!clientRef.current) {
|
|
525
525
|
throw new Error('SSE client not initialized');
|
|
@@ -602,7 +602,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
602
602
|
}
|
|
603
603
|
// Ensure this attempt is not suppressed as background restore.
|
|
604
604
|
suppressAuthRedirectSessionsRef.current.delete(sessionId);
|
|
605
|
-
await clientRef.current.
|
|
605
|
+
await clientRef.current.getSession(sessionId);
|
|
606
606
|
}, []);
|
|
607
607
|
|
|
608
608
|
/**
|
|
@@ -25,7 +25,7 @@ export interface UseMcpOptions {
|
|
|
25
25
|
/**
|
|
26
26
|
* User/Client identifier
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
userId: string;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Optional auth token
|
|
@@ -109,7 +109,7 @@ export interface McpClient {
|
|
|
109
109
|
serverName: string;
|
|
110
110
|
serverUrl: string;
|
|
111
111
|
callbackUrl: string;
|
|
112
|
-
transportType?: 'sse' | '
|
|
112
|
+
transportType?: 'sse' | 'streamable-http';
|
|
113
113
|
}) => Promise<string>;
|
|
114
114
|
|
|
115
115
|
/**
|
|
@@ -125,7 +125,7 @@ export interface McpClient {
|
|
|
125
125
|
serverName: string;
|
|
126
126
|
serverUrl: string;
|
|
127
127
|
callbackUrl: string;
|
|
128
|
-
transportType?: 'sse' | '
|
|
128
|
+
transportType?: 'sse' | 'streamable-http';
|
|
129
129
|
}) => Promise<string>;
|
|
130
130
|
|
|
131
131
|
/**
|
|
@@ -219,7 +219,7 @@ export interface McpClient {
|
|
|
219
219
|
export function useMcp(options: UseMcpOptions): McpClient {
|
|
220
220
|
const {
|
|
221
221
|
url,
|
|
222
|
-
|
|
222
|
+
userId,
|
|
223
223
|
authToken,
|
|
224
224
|
autoConnect = true,
|
|
225
225
|
autoInitialize = true,
|
|
@@ -386,7 +386,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
386
386
|
try {
|
|
387
387
|
isInitializing.value = true;
|
|
388
388
|
|
|
389
|
-
const result = await clientRef.value.
|
|
389
|
+
const result = await clientRef.value.listSessions();
|
|
390
390
|
const sessions = result.sessions || [];
|
|
391
391
|
|
|
392
392
|
// Initialize connections
|
|
@@ -413,7 +413,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
413
413
|
return;
|
|
414
414
|
}
|
|
415
415
|
suppressAuthRedirectSessions.value.add(session.sessionId);
|
|
416
|
-
await clientRef.value.
|
|
416
|
+
await clientRef.value.getSession(session.sessionId);
|
|
417
417
|
} catch (error) {
|
|
418
418
|
console.error(`[useMcp] Failed to validate session ${session.sessionId}:`, error);
|
|
419
419
|
} finally {
|
|
@@ -443,7 +443,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
443
443
|
|
|
444
444
|
const clientOptions: SSEClientOptions = {
|
|
445
445
|
url,
|
|
446
|
-
|
|
446
|
+
userId,
|
|
447
447
|
authToken,
|
|
448
448
|
onConnectionEvent: (event) => {
|
|
449
449
|
// Update local state based on event
|
|
@@ -493,7 +493,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
493
493
|
serverName: string;
|
|
494
494
|
serverUrl: string;
|
|
495
495
|
callbackUrl: string;
|
|
496
|
-
transportType?: 'sse' | '
|
|
496
|
+
transportType?: 'sse' | 'streamable-http';
|
|
497
497
|
}): Promise<string> => {
|
|
498
498
|
if (!clientRef.value) {
|
|
499
499
|
throw new Error('SSE client not initialized');
|
|
@@ -511,7 +511,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
511
511
|
serverName: string;
|
|
512
512
|
serverUrl: string;
|
|
513
513
|
callbackUrl: string;
|
|
514
|
-
transportType?: 'sse' | '
|
|
514
|
+
transportType?: 'sse' | 'streamable-http';
|
|
515
515
|
}): Promise<string> => {
|
|
516
516
|
if (!clientRef.value) {
|
|
517
517
|
throw new Error('SSE client not initialized');
|
|
@@ -589,7 +589,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
589
589
|
throw new Error('SSE client not initialized');
|
|
590
590
|
}
|
|
591
591
|
suppressAuthRedirectSessions.value.delete(sessionId);
|
|
592
|
-
await clientRef.value.
|
|
592
|
+
await clientRef.value.getSession(sessionId);
|
|
593
593
|
};
|
|
594
594
|
|
|
595
595
|
/**
|
|
@@ -12,9 +12,9 @@ import type { McpRpcResponse } from '../../shared/types.js';
|
|
|
12
12
|
|
|
13
13
|
export interface NextMcpHandlerOptions {
|
|
14
14
|
/**
|
|
15
|
-
* Extract
|
|
15
|
+
* Extract userId from request (default: from 'userId' query param)
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
getUserId?: (request: Request) => string | null;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Extract auth token from request (default: from 'token' query param or Authorization header)
|
|
@@ -25,7 +25,7 @@ export interface NextMcpHandlerOptions {
|
|
|
25
25
|
* Authenticate user and verify access (optional)
|
|
26
26
|
* Return true if user is authenticated, false otherwise
|
|
27
27
|
*/
|
|
28
|
-
authenticate?: (
|
|
28
|
+
authenticate?: (userId: string, token: string | null) => Promise<boolean> | boolean;
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Heartbeat interval in milliseconds (default: 30000)
|
|
@@ -45,10 +45,13 @@ export interface NextMcpHandlerOptions {
|
|
|
45
45
|
|
|
46
46
|
export function createNextMcpHandler(options: NextMcpHandlerOptions = {}) {
|
|
47
47
|
const {
|
|
48
|
-
|
|
48
|
+
getUserId = (request: Request) => request.headers.get('x-mcp-user-id'),
|
|
49
49
|
getAuthToken = (request: Request) => {
|
|
50
|
-
const
|
|
51
|
-
|
|
50
|
+
const authHeader = request.headers.get('authorization');
|
|
51
|
+
if (authHeader?.toLowerCase().startsWith('bearer ')) {
|
|
52
|
+
return authHeader.slice(7);
|
|
53
|
+
}
|
|
54
|
+
return authHeader;
|
|
52
55
|
},
|
|
53
56
|
authenticate = () => true,
|
|
54
57
|
heartbeatInterval = 30000,
|
|
@@ -56,8 +59,8 @@ export function createNextMcpHandler(options: NextMcpHandlerOptions = {}) {
|
|
|
56
59
|
getClientMetadata,
|
|
57
60
|
} = options;
|
|
58
61
|
|
|
59
|
-
const toManagerOptions = (
|
|
60
|
-
|
|
62
|
+
const toManagerOptions = (userId: string, resolvedClientMetadata?: ClientMetadata) => ({
|
|
63
|
+
userId,
|
|
61
64
|
heartbeatInterval,
|
|
62
65
|
clientDefaults: resolvedClientMetadata,
|
|
63
66
|
});
|
|
@@ -79,15 +82,15 @@ export function createNextMcpHandler(options: NextMcpHandlerOptions = {}) {
|
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
async function POST(request: Request): Promise<Response> {
|
|
82
|
-
const
|
|
85
|
+
const userId = getUserId(request);
|
|
83
86
|
const authToken = getAuthToken(request);
|
|
84
87
|
const acceptsEventStream = (request.headers.get('accept') || '').toLowerCase().includes('text/event-stream');
|
|
85
88
|
|
|
86
|
-
if (!
|
|
87
|
-
return Response.json({ error: { code: '
|
|
89
|
+
if (!userId) {
|
|
90
|
+
return Response.json({ error: { code: 'MISSING_userId', message: 'Missing userId' } }, { status: 400 });
|
|
88
91
|
}
|
|
89
92
|
|
|
90
|
-
const isAuthorized = await authenticate(
|
|
93
|
+
const isAuthorized = await authenticate(userId, authToken);
|
|
91
94
|
if (!isAuthorized) {
|
|
92
95
|
return Response.json({ error: { code: 'UNAUTHORIZED', message: 'Unauthorized' } }, { status: 401 });
|
|
93
96
|
}
|
|
@@ -113,7 +116,7 @@ export function createNextMcpHandler(options: NextMcpHandlerOptions = {}) {
|
|
|
113
116
|
|
|
114
117
|
if (!acceptsEventStream) {
|
|
115
118
|
const manager = new SSEConnectionManager(
|
|
116
|
-
toManagerOptions(
|
|
119
|
+
toManagerOptions(userId, resolvedClientMetadata),
|
|
117
120
|
() => { }
|
|
118
121
|
);
|
|
119
122
|
try {
|
|
@@ -138,7 +141,7 @@ export function createNextMcpHandler(options: NextMcpHandlerOptions = {}) {
|
|
|
138
141
|
};
|
|
139
142
|
|
|
140
143
|
const manager = new SSEConnectionManager(
|
|
141
|
-
toManagerOptions(
|
|
144
|
+
toManagerOptions(userId, resolvedClientMetadata),
|
|
142
145
|
(event: McpConnectionEvent | McpObservabilityEvent | McpRpcResponse) => {
|
|
143
146
|
if (isRpcResponseEvent(event)) {
|
|
144
147
|
sendSSE('rpc-response', event);
|
|
@@ -183,7 +186,7 @@ export function createNextMcpHandler(options: NextMcpHandlerOptions = {}) {
|
|
|
183
186
|
} catch (error) {
|
|
184
187
|
const err = error instanceof Error ? error : new Error('Unknown error');
|
|
185
188
|
console.error('[MCP Next Handler] Failed to handle RPC', {
|
|
186
|
-
|
|
189
|
+
userId,
|
|
187
190
|
message: err.message,
|
|
188
191
|
stack: err.stack,
|
|
189
192
|
rawBody: rawBody.slice(0, 500),
|