@mcp-ts/sdk 1.3.2 → 1.3.4
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 +400 -406
- package/dist/adapters/agui-adapter.d.mts +1 -1
- package/dist/adapters/agui-adapter.d.ts +1 -1
- package/dist/adapters/agui-middleware.d.mts +1 -1
- package/dist/adapters/agui-middleware.d.ts +1 -1
- package/dist/adapters/ai-adapter.d.mts +1 -1
- package/dist/adapters/ai-adapter.d.ts +1 -1
- package/dist/adapters/langchain-adapter.d.mts +1 -1
- package/dist/adapters/langchain-adapter.d.ts +1 -1
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/client/index.d.mts +8 -64
- package/dist/client/index.d.ts +8 -64
- 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 +12 -2
- package/dist/client/react.d.ts +12 -2
- package/dist/client/react.js +119 -182
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +119 -182
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +24 -4
- package/dist/client/vue.d.ts +24 -4
- package/dist/client/vue.js +121 -182
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +121 -182
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +215 -250
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +215 -250
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-B1DBx5yR.d.mts → multi-session-client-DzjmT7FX.d.mts} +1 -0
- package/dist/{multi-session-client-DyFzyJUx.d.ts → multi-session-client-FAFpUzZ4.d.ts} +1 -0
- package/dist/server/index.d.mts +16 -21
- package/dist/server/index.d.ts +16 -21
- package/dist/server/index.js +124 -77
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +124 -77
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +2 -2
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{types-PjM1W07s.d.mts → types-CW6lghof.d.mts} +5 -0
- package/dist/{types-PjM1W07s.d.ts → types-CW6lghof.d.ts} +5 -0
- package/package.json +1 -1
- package/src/client/core/sse-client.ts +354 -493
- package/src/client/react/use-mcp.ts +75 -23
- package/src/client/vue/use-mcp.ts +111 -48
- package/src/server/handlers/nextjs-handler.ts +207 -217
- package/src/server/handlers/sse-handler.ts +10 -0
- package/src/server/mcp/oauth-client.ts +41 -32
- package/src/server/storage/types.ts +12 -5
- package/src/shared/types.ts +5 -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
|
+
}
|
|
@@ -226,6 +226,7 @@ export class SSEConnectionManager {
|
|
|
226
226
|
serverUrl: s.serverUrl,
|
|
227
227
|
transport: s.transportType,
|
|
228
228
|
createdAt: s.createdAt,
|
|
229
|
+
active: s.active !== false,
|
|
229
230
|
})),
|
|
230
231
|
};
|
|
231
232
|
}
|
|
@@ -249,6 +250,15 @@ export class SSEConnectionManager {
|
|
|
249
250
|
);
|
|
250
251
|
|
|
251
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
|
+
}
|
|
252
262
|
throw new Error(`Connection already exists for server: ${duplicate.serverUrl || duplicate.serverId} (${duplicate.serverName})`);
|
|
253
263
|
}
|
|
254
264
|
|
|
@@ -363,29 +363,34 @@ export class MCPClient {
|
|
|
363
363
|
if (!existingSession && this.serverId && this.serverUrl && this.callbackUrl) {
|
|
364
364
|
this.createdAt = Date.now();
|
|
365
365
|
console.log(`[MCPClient] Creating initial session ${this.sessionId} for OAuth flow`);
|
|
366
|
-
await storage.createSession({
|
|
367
|
-
sessionId: this.sessionId,
|
|
368
|
-
identity: this.identity,
|
|
369
|
-
serverId: this.serverId,
|
|
370
|
-
serverName: this.serverName,
|
|
371
|
-
serverUrl: this.serverUrl,
|
|
372
|
-
callbackUrl: this.callbackUrl,
|
|
373
|
-
transportType: this.transportType || 'streamable_http',
|
|
374
|
-
createdAt: this.createdAt,
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
366
|
+
await storage.createSession({
|
|
367
|
+
sessionId: this.sessionId,
|
|
368
|
+
identity: this.identity,
|
|
369
|
+
serverId: this.serverId,
|
|
370
|
+
serverName: this.serverName,
|
|
371
|
+
serverUrl: this.serverUrl,
|
|
372
|
+
callbackUrl: this.callbackUrl,
|
|
373
|
+
transportType: this.transportType || 'streamable_http',
|
|
374
|
+
createdAt: this.createdAt,
|
|
375
|
+
active: false,
|
|
376
|
+
}, Math.floor(STATE_EXPIRATION_MS / 1000)); // Short TTL until connection succeeds
|
|
377
|
+
}
|
|
378
|
+
}
|
|
378
379
|
|
|
379
380
|
/**
|
|
380
381
|
* Saves current session state to storage
|
|
381
382
|
* Creates new session if it doesn't exist, updates if it does
|
|
382
|
-
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
383
|
-
* @
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
383
|
+
* @param ttl - Time-to-live in seconds (defaults to 12hr for connected sessions)
|
|
384
|
+
* @param active - Session status marker used to avoid unnecessary TTL rewrites
|
|
385
|
+
* @private
|
|
386
|
+
*/
|
|
387
|
+
private async saveSession(
|
|
388
|
+
ttl: number = SESSION_TTL_SECONDS,
|
|
389
|
+
active: boolean = true
|
|
390
|
+
): Promise<void> {
|
|
391
|
+
if (!this.sessionId || !this.serverId || !this.serverUrl || !this.callbackUrl) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
389
394
|
|
|
390
395
|
const sessionData = {
|
|
391
396
|
sessionId: this.sessionId,
|
|
@@ -393,10 +398,11 @@ export class MCPClient {
|
|
|
393
398
|
serverId: this.serverId,
|
|
394
399
|
serverName: this.serverName,
|
|
395
400
|
serverUrl: this.serverUrl,
|
|
396
|
-
callbackUrl: this.callbackUrl,
|
|
397
|
-
transportType: (this.transportType || 'streamable_http') as TransportType,
|
|
398
|
-
createdAt: this.createdAt || Date.now(),
|
|
399
|
-
|
|
401
|
+
callbackUrl: this.callbackUrl,
|
|
402
|
+
transportType: (this.transportType || 'streamable_http') as TransportType,
|
|
403
|
+
createdAt: this.createdAt || Date.now(),
|
|
404
|
+
active,
|
|
405
|
+
};
|
|
400
406
|
|
|
401
407
|
// Try to update first, create if doesn't exist
|
|
402
408
|
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
@@ -506,13 +512,16 @@ export class MCPClient {
|
|
|
506
512
|
this.emitStateChange('CONNECTED');
|
|
507
513
|
this.emitProgress('Connected successfully');
|
|
508
514
|
|
|
509
|
-
//
|
|
510
|
-
//
|
|
511
|
-
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
515
|
+
// Promote short-lived OAuth-pending session TTL to long-lived active TTL once.
|
|
516
|
+
// Also persist when transport negotiation changed the effective transport.
|
|
517
|
+
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
518
|
+
const needsTransportUpdate = !existingSession || existingSession.transportType !== this.transportType;
|
|
519
|
+
const needsTtlPromotion = !existingSession || existingSession.active !== true;
|
|
520
|
+
|
|
521
|
+
if (needsTransportUpdate || needsTtlPromotion) {
|
|
522
|
+
console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
|
|
523
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
524
|
+
}
|
|
516
525
|
} catch (error) {
|
|
517
526
|
/** Handle Authentication Errors */
|
|
518
527
|
if (
|
|
@@ -522,7 +531,7 @@ export class MCPClient {
|
|
|
522
531
|
this.emitStateChange('AUTHENTICATING');
|
|
523
532
|
// Save session with 10min TTL for OAuth pending state
|
|
524
533
|
console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
|
|
525
|
-
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1000));
|
|
534
|
+
await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1000), false);
|
|
526
535
|
|
|
527
536
|
/** Get OAuth authorization URL if available */
|
|
528
537
|
let authUrl = '';
|
|
@@ -633,7 +642,7 @@ export class MCPClient {
|
|
|
633
642
|
this.emitStateChange('CONNECTED');
|
|
634
643
|
// Update session with 12hr TTL after successful OAuth
|
|
635
644
|
console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
|
|
636
|
-
await this.saveSession(SESSION_TTL_SECONDS);
|
|
645
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
637
646
|
|
|
638
647
|
return; // Success, exit function
|
|
639
648
|
|
|
@@ -5,8 +5,8 @@ import type {
|
|
|
5
5
|
OAuthClientInformationMixed,
|
|
6
6
|
} from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
7
7
|
|
|
8
|
-
export interface SessionData {
|
|
9
|
-
sessionId: string;
|
|
8
|
+
export interface SessionData {
|
|
9
|
+
sessionId: string;
|
|
10
10
|
serverId?: string; // Database server ID for mapping
|
|
11
11
|
serverName?: string;
|
|
12
12
|
serverUrl: string;
|
|
@@ -14,9 +14,16 @@ export interface SessionData {
|
|
|
14
14
|
callbackUrl: string;
|
|
15
15
|
createdAt: number;
|
|
16
16
|
identity: string;
|
|
17
|
-
headers?: Record<string, string>;
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
/**
|
|
19
|
+
* Session status marker used for TTL transitions:
|
|
20
|
+
* - false: short-lived intermediate/error/auth-pending session state
|
|
21
|
+
* (keep this value when connection/auth is incomplete or failed)
|
|
22
|
+
* - true: active long-lived session state after successful connection/auth completion
|
|
23
|
+
*/
|
|
24
|
+
active?: boolean;
|
|
25
|
+
// OAuth data (consolidated)
|
|
26
|
+
clientInformation?: OAuthClientInformationMixed;
|
|
20
27
|
tokens?: OAuthTokens;
|
|
21
28
|
codeVerifier?: string;
|
|
22
29
|
clientId?: string;
|
package/src/shared/types.ts
CHANGED
|
@@ -224,6 +224,11 @@ export interface SessionInfo {
|
|
|
224
224
|
serverUrl: string;
|
|
225
225
|
transport: TransportType;
|
|
226
226
|
createdAt: number;
|
|
227
|
+
/**
|
|
228
|
+
* Session readiness for auto-restore.
|
|
229
|
+
* false means auth is pending and should be resumed explicitly by user action.
|
|
230
|
+
*/
|
|
231
|
+
active?: boolean;
|
|
227
232
|
}
|
|
228
233
|
|
|
229
234
|
export interface SessionListResult {
|