@mcp-ts/sdk 1.3.4 → 1.3.6

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 (70) hide show
  1. package/README.md +404 -400
  2. package/dist/adapters/agui-adapter.d.mts +1 -1
  3. package/dist/adapters/agui-adapter.d.ts +1 -1
  4. package/dist/adapters/agui-middleware.d.mts +1 -1
  5. package/dist/adapters/agui-middleware.d.ts +1 -1
  6. package/dist/adapters/ai-adapter.d.mts +1 -1
  7. package/dist/adapters/ai-adapter.d.ts +1 -1
  8. package/dist/adapters/langchain-adapter.d.mts +1 -1
  9. package/dist/adapters/langchain-adapter.d.ts +1 -1
  10. package/dist/adapters/mastra-adapter.d.mts +1 -1
  11. package/dist/adapters/mastra-adapter.d.ts +1 -1
  12. package/dist/bin/mcp-ts.d.mts +1 -0
  13. package/dist/bin/mcp-ts.d.ts +1 -0
  14. package/dist/bin/mcp-ts.js +105 -0
  15. package/dist/bin/mcp-ts.js.map +1 -0
  16. package/dist/bin/mcp-ts.mjs +82 -0
  17. package/dist/bin/mcp-ts.mjs.map +1 -0
  18. package/dist/client/index.d.mts +1 -0
  19. package/dist/client/index.d.ts +1 -0
  20. package/dist/client/index.js +14 -5
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/client/index.mjs +14 -5
  23. package/dist/client/index.mjs.map +1 -1
  24. package/dist/client/react.js +15 -6
  25. package/dist/client/react.js.map +1 -1
  26. package/dist/client/react.mjs +15 -6
  27. package/dist/client/react.mjs.map +1 -1
  28. package/dist/client/vue.js +15 -6
  29. package/dist/client/vue.js.map +1 -1
  30. package/dist/client/vue.mjs +15 -6
  31. package/dist/client/vue.mjs.map +1 -1
  32. package/dist/index.d.mts +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +480 -179
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +418 -179
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/{multi-session-client-FAFpUzZ4.d.ts → multi-session-client-BYLarghq.d.ts} +29 -19
  39. package/dist/{multi-session-client-DzjmT7FX.d.mts → multi-session-client-CzhMkE0k.d.mts} +29 -19
  40. package/dist/server/index.d.mts +1 -1
  41. package/dist/server/index.d.ts +1 -1
  42. package/dist/server/index.js +455 -172
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +410 -172
  45. package/dist/server/index.mjs.map +1 -1
  46. package/dist/shared/index.d.mts +2 -2
  47. package/dist/shared/index.d.ts +2 -2
  48. package/dist/shared/index.js +2 -2
  49. package/dist/shared/index.js.map +1 -1
  50. package/dist/shared/index.mjs +2 -2
  51. package/dist/shared/index.mjs.map +1 -1
  52. package/package.json +19 -6
  53. package/src/bin/mcp-ts.ts +102 -0
  54. package/src/client/core/sse-client.ts +371 -354
  55. package/src/client/react/use-mcp.ts +31 -31
  56. package/src/client/vue/use-mcp.ts +77 -77
  57. package/src/server/handlers/nextjs-handler.ts +204 -207
  58. package/src/server/handlers/sse-handler.ts +14 -63
  59. package/src/server/mcp/oauth-client.ts +67 -79
  60. package/src/server/mcp/storage-oauth-provider.ts +71 -38
  61. package/src/server/storage/file-backend.ts +1 -0
  62. package/src/server/storage/index.ts +82 -38
  63. package/src/server/storage/memory-backend.ts +4 -0
  64. package/src/server/storage/redis-backend.ts +102 -23
  65. package/src/server/storage/sqlite-backend.ts +1 -0
  66. package/src/server/storage/supabase-backend.ts +227 -0
  67. package/src/server/storage/types.ts +12 -12
  68. package/src/shared/constants.ts +2 -2
  69. package/src/shared/event-routing.ts +28 -0
  70. package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -0
@@ -1,207 +1,204 @@
1
- /**
2
- * Next.js App Router Handler for MCP
3
- * Stateless transport for serverless environments:
4
- * - POST + `Accept: text/event-stream` streams progress + rpc-response
5
- * - POST + JSON accepts direct RPC result response
6
- */
7
-
8
- import { SSEConnectionManager, type ClientMetadata } from './sse-handler.js';
9
- import type { McpConnectionEvent, McpObservabilityEvent } from '../../shared/events.js';
10
- import type { McpRpcResponse } from '../../shared/types.js';
11
-
12
- function isRpcResponseEvent(event: McpConnectionEvent | McpObservabilityEvent | McpRpcResponse): event is McpRpcResponse {
13
- return 'id' in event && ('result' in event || 'error' in event);
14
- }
15
-
16
- export interface NextMcpHandlerOptions {
17
- /**
18
- * Extract identity from request (default: from 'identity' query param)
19
- */
20
- getIdentity?: (request: Request) => string | null;
21
-
22
- /**
23
- * Extract auth token from request (default: from 'token' query param or Authorization header)
24
- */
25
- getAuthToken?: (request: Request) => string | null;
26
-
27
- /**
28
- * Authenticate user and verify access (optional)
29
- * Return true if user is authenticated, false otherwise
30
- */
31
- authenticate?: (identity: string, token: string | null) => Promise<boolean> | boolean;
32
-
33
- /**
34
- * Heartbeat interval in milliseconds (default: 30000)
35
- */
36
- heartbeatInterval?: number;
37
-
38
- /**
39
- * Static OAuth client metadata defaults (for all connections)
40
- */
41
- clientDefaults?: ClientMetadata;
42
-
43
- /**
44
- * Dynamic OAuth client metadata getter (per-request)
45
- */
46
- getClientMetadata?: (request: Request) => ClientMetadata | Promise<ClientMetadata>;
47
- }
48
-
49
- export function createNextMcpHandler(options: NextMcpHandlerOptions = {}) {
50
- const {
51
- getIdentity = (request: Request) => new URL(request.url).searchParams.get('identity'),
52
- getAuthToken = (request: Request) => {
53
- const url = new URL(request.url);
54
- return url.searchParams.get('token') || request.headers.get('authorization');
55
- },
56
- authenticate = () => true,
57
- heartbeatInterval = 30000,
58
- clientDefaults,
59
- getClientMetadata,
60
- } = options;
61
-
62
- const toManagerOptions = (identity: string, resolvedClientMetadata?: ClientMetadata) => ({
63
- identity,
64
- heartbeatInterval,
65
- clientDefaults: resolvedClientMetadata,
66
- });
67
-
68
- async function resolveClientMetadata(request: Request): Promise<ClientMetadata | undefined> {
69
- return getClientMetadata ? await getClientMetadata(request) : clientDefaults;
70
- }
71
-
72
- async function GET(): Promise<Response> {
73
- return Response.json(
74
- {
75
- error: {
76
- code: 'METHOD_NOT_ALLOWED',
77
- message: 'Use POST /api/mcp. For streaming use Accept: text/event-stream.',
78
- },
79
- },
80
- { status: 405 }
81
- );
82
- }
83
-
84
- async function POST(request: Request): Promise<Response> {
85
- const identity = getIdentity(request);
86
- const authToken = getAuthToken(request);
87
- const acceptsEventStream = (request.headers.get('accept') || '').toLowerCase().includes('text/event-stream');
88
-
89
- if (!identity) {
90
- return Response.json({ error: { code: 'MISSING_IDENTITY', message: 'Missing identity' } }, { status: 400 });
91
- }
92
-
93
- const isAuthorized = await authenticate(identity, authToken);
94
- if (!isAuthorized) {
95
- return Response.json({ error: { code: 'UNAUTHORIZED', message: 'Unauthorized' } }, { status: 401 });
96
- }
97
-
98
- let rawBody = '';
99
- try {
100
- rawBody = await request.text();
101
- const body = rawBody ? JSON.parse(rawBody) : null;
102
-
103
- if (!body || typeof body !== 'object') {
104
- return Response.json(
105
- {
106
- error: {
107
- code: 'INVALID_REQUEST',
108
- message: 'Invalid JSON-RPC request body',
109
- },
110
- },
111
- { status: 400 }
112
- );
113
- }
114
-
115
- const resolvedClientMetadata = await resolveClientMetadata(request);
116
-
117
- if (!acceptsEventStream) {
118
- const manager = new SSEConnectionManager(
119
- toManagerOptions(identity, resolvedClientMetadata),
120
- () => { }
121
- );
122
- try {
123
- const response = await manager.handleRequest(body as any);
124
- return Response.json(response);
125
- } finally {
126
- manager.dispose();
127
- }
128
- }
129
-
130
- const stream = new TransformStream();
131
- const writer = stream.writable.getWriter();
132
- const encoder = new TextEncoder();
133
- let streamWritable = true;
134
-
135
- const sendSSE = (event: string, data: unknown) => {
136
- if (!streamWritable) return;
137
- const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
138
- writer.write(encoder.encode(message)).catch(() => {
139
- streamWritable = false;
140
- });
141
- };
142
-
143
- const manager = new SSEConnectionManager(
144
- toManagerOptions(identity, resolvedClientMetadata),
145
- (event: McpConnectionEvent | McpObservabilityEvent | McpRpcResponse) => {
146
- if (isRpcResponseEvent(event)) {
147
- sendSSE('rpc-response', event);
148
- } else if ('type' in event && 'sessionId' in event) {
149
- sendSSE('connection', event);
150
- } else {
151
- sendSSE('observability', event);
152
- }
153
- }
154
- );
155
-
156
- sendSSE('connected', { timestamp: Date.now() });
157
-
158
- void (async () => {
159
- try {
160
- await manager.handleRequest(body as any);
161
- } catch (error) {
162
- const err = error instanceof Error ? error : new Error('Unknown error');
163
- sendSSE('rpc-response', {
164
- id: (body as any).id || 'unknown',
165
- error: {
166
- code: 'EXECUTION_ERROR',
167
- message: err.message,
168
- },
169
- } satisfies McpRpcResponse);
170
- } finally {
171
- streamWritable = false;
172
- manager.dispose();
173
- writer.close().catch(() => { });
174
- }
175
- })();
176
-
177
- return new Response(stream.readable, {
178
- status: 200,
179
- headers: {
180
- 'Content-Type': 'text/event-stream',
181
- 'Cache-Control': 'no-cache, no-transform',
182
- 'Connection': 'keep-alive',
183
- 'X-Accel-Buffering': 'no',
184
- },
185
- });
186
- } catch (error) {
187
- const err = error instanceof Error ? error : new Error('Unknown error');
188
- console.error('[MCP Next Handler] Failed to handle RPC', {
189
- identity,
190
- message: err.message,
191
- stack: err.stack,
192
- rawBody: rawBody.slice(0, 500),
193
- });
194
- return Response.json(
195
- {
196
- error: {
197
- code: 'EXECUTION_ERROR',
198
- message: err.message,
199
- },
200
- },
201
- { status: 500 }
202
- );
203
- }
204
- }
205
-
206
- return { GET, POST };
207
- }
1
+ /**
2
+ * Next.js App Router Handler for MCP
3
+ * Stateless transport for serverless environments:
4
+ * - POST + `Accept: text/event-stream` streams progress + rpc-response
5
+ * - POST + JSON accepts direct RPC result response
6
+ */
7
+
8
+ import { SSEConnectionManager, type ClientMetadata } from './sse-handler.js';
9
+ import type { McpConnectionEvent, McpObservabilityEvent } from '../../shared/events.js';
10
+ import { isConnectionEvent, isRpcResponseEvent } from '../../shared/event-routing.js';
11
+ import type { McpRpcResponse } from '../../shared/types.js';
12
+
13
+ export interface NextMcpHandlerOptions {
14
+ /**
15
+ * Extract identity from request (default: from 'identity' query param)
16
+ */
17
+ getIdentity?: (request: Request) => string | null;
18
+
19
+ /**
20
+ * Extract auth token from request (default: from 'token' query param or Authorization header)
21
+ */
22
+ getAuthToken?: (request: Request) => string | null;
23
+
24
+ /**
25
+ * Authenticate user and verify access (optional)
26
+ * Return true if user is authenticated, false otherwise
27
+ */
28
+ authenticate?: (identity: string, token: string | null) => Promise<boolean> | boolean;
29
+
30
+ /**
31
+ * Heartbeat interval in milliseconds (default: 30000)
32
+ */
33
+ heartbeatInterval?: number;
34
+
35
+ /**
36
+ * Static OAuth client metadata defaults (for all connections)
37
+ */
38
+ clientDefaults?: ClientMetadata;
39
+
40
+ /**
41
+ * Dynamic OAuth client metadata getter (per-request)
42
+ */
43
+ getClientMetadata?: (request: Request) => ClientMetadata | Promise<ClientMetadata>;
44
+ }
45
+
46
+ export function createNextMcpHandler(options: NextMcpHandlerOptions = {}) {
47
+ const {
48
+ getIdentity = (request: Request) => new URL(request.url).searchParams.get('identity'),
49
+ getAuthToken = (request: Request) => {
50
+ const url = new URL(request.url);
51
+ return url.searchParams.get('token') || request.headers.get('authorization');
52
+ },
53
+ authenticate = () => true,
54
+ heartbeatInterval = 30000,
55
+ clientDefaults,
56
+ getClientMetadata,
57
+ } = options;
58
+
59
+ const toManagerOptions = (identity: string, resolvedClientMetadata?: ClientMetadata) => ({
60
+ identity,
61
+ heartbeatInterval,
62
+ clientDefaults: resolvedClientMetadata,
63
+ });
64
+
65
+ async function resolveClientMetadata(request: Request): Promise<ClientMetadata | undefined> {
66
+ return getClientMetadata ? await getClientMetadata(request) : clientDefaults;
67
+ }
68
+
69
+ async function GET(): Promise<Response> {
70
+ return Response.json(
71
+ {
72
+ error: {
73
+ code: 'METHOD_NOT_ALLOWED',
74
+ message: 'Use POST /api/mcp. For streaming use Accept: text/event-stream.',
75
+ },
76
+ },
77
+ { status: 405 }
78
+ );
79
+ }
80
+
81
+ async function POST(request: Request): Promise<Response> {
82
+ const identity = getIdentity(request);
83
+ const authToken = getAuthToken(request);
84
+ const acceptsEventStream = (request.headers.get('accept') || '').toLowerCase().includes('text/event-stream');
85
+
86
+ if (!identity) {
87
+ return Response.json({ error: { code: 'MISSING_IDENTITY', message: 'Missing identity' } }, { status: 400 });
88
+ }
89
+
90
+ const isAuthorized = await authenticate(identity, authToken);
91
+ if (!isAuthorized) {
92
+ return Response.json({ error: { code: 'UNAUTHORIZED', message: 'Unauthorized' } }, { status: 401 });
93
+ }
94
+
95
+ let rawBody = '';
96
+ try {
97
+ rawBody = await request.text();
98
+ const body = rawBody ? JSON.parse(rawBody) : null;
99
+
100
+ if (!body || typeof body !== 'object') {
101
+ return Response.json(
102
+ {
103
+ error: {
104
+ code: 'INVALID_REQUEST',
105
+ message: 'Invalid JSON-RPC request body',
106
+ },
107
+ },
108
+ { status: 400 }
109
+ );
110
+ }
111
+
112
+ const resolvedClientMetadata = await resolveClientMetadata(request);
113
+
114
+ if (!acceptsEventStream) {
115
+ const manager = new SSEConnectionManager(
116
+ toManagerOptions(identity, resolvedClientMetadata),
117
+ () => { }
118
+ );
119
+ try {
120
+ const response = await manager.handleRequest(body as any);
121
+ return Response.json(response);
122
+ } finally {
123
+ manager.dispose();
124
+ }
125
+ }
126
+
127
+ const stream = new TransformStream();
128
+ const writer = stream.writable.getWriter();
129
+ const encoder = new TextEncoder();
130
+ let streamWritable = true;
131
+
132
+ const sendSSE = (event: string, data: unknown) => {
133
+ if (!streamWritable) return;
134
+ const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
135
+ writer.write(encoder.encode(message)).catch(() => {
136
+ streamWritable = false;
137
+ });
138
+ };
139
+
140
+ const manager = new SSEConnectionManager(
141
+ toManagerOptions(identity, resolvedClientMetadata),
142
+ (event: McpConnectionEvent | McpObservabilityEvent | McpRpcResponse) => {
143
+ if (isRpcResponseEvent(event)) {
144
+ sendSSE('rpc-response', event);
145
+ } else if (isConnectionEvent(event)) {
146
+ sendSSE('connection', event);
147
+ } else {
148
+ sendSSE('observability', event);
149
+ }
150
+ }
151
+ );
152
+
153
+ sendSSE('connected', { timestamp: Date.now() });
154
+
155
+ void (async () => {
156
+ try {
157
+ await manager.handleRequest(body as any);
158
+ } catch (error) {
159
+ const err = error instanceof Error ? error : new Error('Unknown error');
160
+ sendSSE('rpc-response', {
161
+ id: (body as any).id || 'unknown',
162
+ error: {
163
+ code: 'EXECUTION_ERROR',
164
+ message: err.message,
165
+ },
166
+ } satisfies McpRpcResponse);
167
+ } finally {
168
+ streamWritable = false;
169
+ manager.dispose();
170
+ writer.close().catch(() => { });
171
+ }
172
+ })();
173
+
174
+ return new Response(stream.readable, {
175
+ status: 200,
176
+ headers: {
177
+ 'Content-Type': 'text/event-stream',
178
+ 'Cache-Control': 'no-cache, no-transform',
179
+ 'Connection': 'keep-alive',
180
+ 'X-Accel-Buffering': 'no',
181
+ },
182
+ });
183
+ } catch (error) {
184
+ const err = error instanceof Error ? error : new Error('Unknown error');
185
+ console.error('[MCP Next Handler] Failed to handle RPC', {
186
+ identity,
187
+ message: err.message,
188
+ stack: err.stack,
189
+ rawBody: rawBody.slice(0, 500),
190
+ });
191
+ return Response.json(
192
+ {
193
+ error: {
194
+ code: 'EXECUTION_ERROR',
195
+ message: err.message,
196
+ },
197
+ },
198
+ { status: 500 }
199
+ );
200
+ }
201
+ }
202
+
203
+ return { GET, POST };
204
+ }
@@ -34,6 +34,8 @@ import type {
34
34
  CallToolResult,
35
35
  } from '../../shared/types.js';
36
36
  import { RpcErrorCodes } from '../../shared/errors.js';
37
+ import { UnauthorizedError } from '../../shared/errors.js';
38
+ import { isConnectionEvent, isRpcResponseEvent } from '../../shared/event-routing.js';
37
39
  import { MCPClient } from '../mcp/oauth-client.js';
38
40
  import { storage } from '../storage/index.js';
39
41
 
@@ -265,18 +267,6 @@ export class SSEConnectionManager {
265
267
  // Generate session ID
266
268
  const sessionId = await storage.generateSessionId();
267
269
 
268
- // Emit connecting state
269
- this.emitConnectionEvent({
270
- type: 'state_changed',
271
- sessionId,
272
- serverId,
273
- serverName,
274
- serverUrl,
275
- state: 'CONNECTING',
276
- previousState: 'DISCONNECTED',
277
- timestamp: Date.now(),
278
- });
279
-
280
270
  try {
281
271
  // Get resolved client metadata
282
272
  const clientMetadata = await this.getResolvedClientMetadata();
@@ -291,16 +281,6 @@ export class SSEConnectionManager {
291
281
  callbackUrl,
292
282
  transportType,
293
283
  ...clientMetadata, // Spread client metadata (clientName, clientUri, logoUri, policyUri)
294
- onRedirect: (authUrl) => {
295
- // Emit auth required event
296
- this.emitConnectionEvent({
297
- type: 'auth_required',
298
- sessionId,
299
- serverId,
300
- authUrl,
301
- timestamp: Date.now(),
302
- });
303
- },
304
284
  });
305
285
 
306
286
  // Note: Session will be created by MCPClient after successful connection
@@ -322,22 +302,22 @@ export class SSEConnectionManager {
322
302
  await client.connect();
323
303
 
324
304
  // Fetch tools
325
- const tools = await client.listTools();
326
-
327
- this.emitConnectionEvent({
328
- type: 'tools_discovered',
329
- sessionId,
330
- serverId,
331
- toolCount: tools.tools.length,
332
- tools: tools.tools,
333
- timestamp: Date.now(),
334
- });
305
+ await client.listTools();
335
306
 
336
307
  return {
337
308
  sessionId,
338
309
  success: true,
339
310
  };
340
311
  } catch (error) {
312
+ if (error instanceof UnauthorizedError) {
313
+ // OAuth-required is a pending-auth state, not a failed connection.
314
+ this.clients.delete(sessionId);
315
+ return {
316
+ sessionId,
317
+ success: true,
318
+ };
319
+ }
320
+
341
321
  this.emitConnectionEvent({
342
322
  type: 'error',
343
323
  sessionId,
@@ -468,15 +448,6 @@ export class SSEConnectionManager {
468
448
 
469
449
  const tools = await client.listTools();
470
450
 
471
- this.emitConnectionEvent({
472
- type: 'tools_discovered',
473
- sessionId,
474
- serverId: session.serverId ?? 'unknown',
475
- toolCount: tools.tools.length,
476
- tools: tools.tools,
477
- timestamp: Date.now(),
478
- });
479
-
480
451
  return { success: true, toolCount: tools.tools.length };
481
452
  } catch (error) {
482
453
  this.emitConnectionEvent({
@@ -503,17 +474,6 @@ export class SSEConnectionManager {
503
474
  throw new Error('Session not found');
504
475
  }
505
476
 
506
- this.emitConnectionEvent({
507
- type: 'state_changed',
508
- sessionId,
509
- serverId: session.serverId ?? 'unknown',
510
- serverName: session.serverName ?? 'Unknown',
511
- serverUrl: session.serverUrl,
512
- state: 'AUTHENTICATING',
513
- previousState: 'DISCONNECTED',
514
- timestamp: Date.now(),
515
- });
516
-
517
477
  try {
518
478
  const client = new MCPClient({
519
479
  identity: this.identity,
@@ -527,15 +487,6 @@ export class SSEConnectionManager {
527
487
 
528
488
  const tools = await client.listTools();
529
489
 
530
- this.emitConnectionEvent({
531
- type: 'tools_discovered',
532
- sessionId,
533
- serverId: session.serverId ?? 'unknown',
534
- toolCount: tools.tools.length,
535
- tools: tools.tools,
536
- timestamp: Date.now(),
537
- });
538
-
539
490
  return { success: true, toolCount: tools.tools.length };
540
491
  } catch (error) {
541
492
  this.emitConnectionEvent({
@@ -637,9 +588,9 @@ export function createSSEHandler(options: SSEHandlerOptions) {
637
588
 
638
589
  // Create connection manager with event routing
639
590
  const manager = new SSEConnectionManager(options, (event) => {
640
- if ('id' in event) {
591
+ if (isRpcResponseEvent(event)) {
641
592
  writeSSEEvent(res, 'rpc-response', event);
642
- } else if ('type' in event && 'sessionId' in event) {
593
+ } else if (isConnectionEvent(event)) {
643
594
  writeSSEEvent(res, 'connection', event);
644
595
  } else {
645
596
  writeSSEEvent(res, 'observability', event);