@mcp-ts/sdk 1.3.1 → 1.3.3
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 +371 -290
- package/dist/adapters/agui-adapter.d.mts +3 -3
- package/dist/adapters/agui-adapter.d.ts +3 -3
- 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 +3 -3
- package/dist/adapters/ai-adapter.d.ts +3 -3
- package/dist/adapters/langchain-adapter.d.mts +3 -3
- package/dist/adapters/langchain-adapter.d.ts +3 -3
- package/dist/adapters/mastra-adapter.d.mts +3 -3
- package/dist/adapters/mastra-adapter.d.ts +3 -3
- package/dist/client/index.d.mts +10 -66
- package/dist/client/index.d.ts +10 -66
- package/dist/client/index.js +91 -173
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +91 -173
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +15 -5
- package/dist/client/react.d.ts +15 -5
- package/dist/client/react.js +130 -182
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +130 -182
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +27 -7
- package/dist/client/vue.d.ts +27 -7
- package/dist/client/vue.js +131 -182
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +131 -182
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{events-BgeztGYZ.d.mts → events-CK3N--3g.d.mts} +2 -0
- package/dist/{events-BgeztGYZ.d.ts → events-CK3N--3g.d.ts} +2 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +224 -258
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +224 -258
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-CxogNckF.d.mts → multi-session-client-DzjmT7FX.d.mts} +4 -10
- package/dist/{multi-session-client-cox_WXUj.d.ts → multi-session-client-FAFpUzZ4.d.ts} +4 -10
- package/dist/server/index.d.mts +18 -23
- package/dist/server/index.d.ts +18 -23
- package/dist/server/index.js +133 -85
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +133 -85
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +3 -3
- package/dist/shared/index.d.ts +3 -3
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{types-CLccx9wW.d.mts → types-CW6lghof.d.mts} +6 -0
- package/dist/{types-CLccx9wW.d.ts → types-CW6lghof.d.ts} +6 -0
- package/package.json +1 -1
- package/src/client/core/sse-client.ts +354 -493
- package/src/client/react/index.ts +16 -16
- package/src/client/react/use-mcp-apps.tsx +214 -214
- package/src/client/react/use-mcp.ts +84 -19
- package/src/client/vue/use-mcp.ts +119 -44
- package/src/server/handlers/nextjs-handler.ts +207 -217
- package/src/server/handlers/sse-handler.ts +14 -0
- package/src/server/mcp/oauth-client.ts +48 -46
- package/src/server/storage/types.ts +12 -5
- package/src/shared/events.ts +2 -0
- package/src/shared/types.ts +6 -0
|
@@ -1,217 +1,207 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Next.js App Router Handler for MCP
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
clientDefaults
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return Response.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
message: error instanceof Error ? error.message : 'Unknown error',
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
{ status: 500 }
|
|
212
|
-
);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return { GET, POST };
|
|
217
|
-
}
|
|
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
|
+
}
|
|
@@ -225,6 +225,8 @@ export class SSEConnectionManager {
|
|
|
225
225
|
serverName: s.serverName,
|
|
226
226
|
serverUrl: s.serverUrl,
|
|
227
227
|
transport: s.transportType,
|
|
228
|
+
createdAt: s.createdAt,
|
|
229
|
+
active: s.active !== false,
|
|
228
230
|
})),
|
|
229
231
|
};
|
|
230
232
|
}
|
|
@@ -248,6 +250,15 @@ export class SSEConnectionManager {
|
|
|
248
250
|
);
|
|
249
251
|
|
|
250
252
|
if (duplicate) {
|
|
253
|
+
// If the existing session is still pending OAuth, treat connect as "resume auth"
|
|
254
|
+
// instead of failing with duplicate connection error.
|
|
255
|
+
if (duplicate.active === false) {
|
|
256
|
+
await this.restoreSession({ sessionId: duplicate.sessionId });
|
|
257
|
+
return {
|
|
258
|
+
sessionId: duplicate.sessionId,
|
|
259
|
+
success: true,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
251
262
|
throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
|
|
252
263
|
}
|
|
253
264
|
|
|
@@ -260,6 +271,7 @@ export class SSEConnectionManager {
|
|
|
260
271
|
sessionId,
|
|
261
272
|
serverId,
|
|
262
273
|
serverName,
|
|
274
|
+
serverUrl,
|
|
263
275
|
state: 'CONNECTING',
|
|
264
276
|
previousState: 'DISCONNECTED',
|
|
265
277
|
timestamp: Date.now(),
|
|
@@ -433,6 +445,7 @@ export class SSEConnectionManager {
|
|
|
433
445
|
sessionId,
|
|
434
446
|
serverId: session.serverId ?? 'unknown',
|
|
435
447
|
serverName: session.serverName ?? 'Unknown',
|
|
448
|
+
serverUrl: session.serverUrl,
|
|
436
449
|
state: 'VALIDATING',
|
|
437
450
|
previousState: 'DISCONNECTED',
|
|
438
451
|
timestamp: Date.now(),
|
|
@@ -495,6 +508,7 @@ export class SSEConnectionManager {
|
|
|
495
508
|
sessionId,
|
|
496
509
|
serverId: session.serverId ?? 'unknown',
|
|
497
510
|
serverName: session.serverName ?? 'Unknown',
|
|
511
|
+
serverUrl: session.serverUrl,
|
|
498
512
|
state: 'AUTHENTICATING',
|
|
499
513
|
previousState: 'DISCONNECTED',
|
|
500
514
|
timestamp: Date.now(),
|