@mcp-ts/sdk 2.5.0 → 2.5.2
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/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 +2 -2
- package/dist/adapters/mastra-adapter.d.ts +2 -2
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +3 -0
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +4 -4
- package/dist/client/react.d.ts +4 -4
- package/dist/client/react.js +23 -38
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +23 -38
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +5 -4
- package/dist/client/vue.d.ts +5 -4
- package/dist/client/vue.js +4 -1
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +4 -1
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-CTURVnom.d.mts → index-B3GPsPsi.d.mts} +2 -1
- package/dist/{index-sVcqrhf7.d.ts → index-gNdSQTSz.d.ts} +2 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +214 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +214 -13
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-CSPSHHla.d.ts → multi-session-client-CB4oDrQX.d.ts} +36 -10
- package/dist/{multi-session-client-DMZGVABI.d.mts → multi-session-client-xj3iQIv6.d.mts} +36 -10
- package/dist/server/index.d.mts +39 -7
- package/dist/server/index.d.ts +39 -7
- package/dist/server/index.js +211 -13
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +211 -13
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +4 -4
- package/dist/shared/index.d.ts +4 -4
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-CS9l0w4a.d.ts → tool-router-C-Mw1_BQ.d.ts} +1 -1
- package/dist/{tool-router-CVLBaCwH.d.mts → tool-router-CsKVXbQB.d.mts} +1 -1
- package/dist/{types-DK_NGWd4.d.mts → types-6SmXege4.d.mts} +4 -2
- package/dist/{types-DK_NGWd4.d.ts → types-6SmXege4.d.ts} +4 -2
- package/package.json +2 -2
- package/src/client/core/sse-client.ts +4 -0
- package/src/client/react/oauth-popup.tsx +17 -24
- package/src/client/react/use-mcp.ts +7 -16
- package/src/client/vue/use-mcp.ts +2 -1
- package/src/server/handlers/sse-handler.ts +121 -3
- package/src/server/mcp/oauth-client.ts +44 -20
- package/src/server/mcp/tool-policy-gateway.ts +98 -2
- package/src/server/storage/tool-policy.ts +102 -0
- package/src/shared/events.ts +1 -0
- package/src/shared/types.ts +4 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
-
import { A as ToolClientProvider, T as ToolClient } from './types-
|
|
2
|
+
import { A as ToolClientProvider, T as ToolClient } from './types-6SmXege4.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 { A as ToolClientProvider, T as ToolClient } from './types-
|
|
2
|
+
import { A as ToolClientProvider, T as ToolClient } from './types-6SmXege4.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* ToolIndex — Lightweight in-memory search index for MCP tool discovery.
|
|
@@ -57,6 +57,7 @@ type McpConnectionEvent = {
|
|
|
57
57
|
serverId: string;
|
|
58
58
|
toolCount: number;
|
|
59
59
|
tools: any[];
|
|
60
|
+
allTools?: any[];
|
|
60
61
|
timestamp: number;
|
|
61
62
|
} | {
|
|
62
63
|
type: 'auth_required';
|
|
@@ -210,7 +211,7 @@ interface ToolPolicy {
|
|
|
210
211
|
toolIds: string[];
|
|
211
212
|
updatedAt: number;
|
|
212
213
|
}
|
|
213
|
-
type McpRpcMethod = 'connect' | 'disconnect' | 'listTools' | 'callTool' | 'listSessions' | 'getSession' | 'finishAuth' | 'listPrompts' | 'getPrompt' | 'listResources' | 'readResource' | 'setToolPolicy' | 'getToolPolicy';
|
|
214
|
+
type McpRpcMethod = 'connect' | 'disconnect' | 'reconnect' | 'listTools' | 'callTool' | 'listSessions' | 'getSession' | 'finishAuth' | 'listPrompts' | 'getPrompt' | 'listResources' | 'readResource' | 'setToolPolicy' | 'getToolPolicy';
|
|
214
215
|
interface McpRpcRequest {
|
|
215
216
|
id: string;
|
|
216
217
|
method: McpRpcMethod;
|
|
@@ -235,6 +236,7 @@ interface ConnectParams {
|
|
|
235
236
|
interface DisconnectParams {
|
|
236
237
|
sessionId: string;
|
|
237
238
|
}
|
|
239
|
+
type ReconnectParams = ConnectParams;
|
|
238
240
|
interface SessionParams {
|
|
239
241
|
sessionId: string;
|
|
240
242
|
}
|
|
@@ -266,7 +268,7 @@ interface SetToolPolicyParams {
|
|
|
266
268
|
interface GetToolPolicyParams {
|
|
267
269
|
sessionId: string;
|
|
268
270
|
}
|
|
269
|
-
type McpRpcParams = ConnectParams | DisconnectParams | SessionParams | CallToolParams | GetPromptParams | ReadResourceParams | FinishAuthParams | SetToolPolicyParams | GetToolPolicyParams | undefined;
|
|
271
|
+
type McpRpcParams = ConnectParams | DisconnectParams | ReconnectParams | SessionParams | CallToolParams | GetPromptParams | ReadResourceParams | FinishAuthParams | SetToolPolicyParams | GetToolPolicyParams | undefined;
|
|
270
272
|
interface SessionInfo {
|
|
271
273
|
sessionId: string;
|
|
272
274
|
serverId?: string;
|
|
@@ -57,6 +57,7 @@ type McpConnectionEvent = {
|
|
|
57
57
|
serverId: string;
|
|
58
58
|
toolCount: number;
|
|
59
59
|
tools: any[];
|
|
60
|
+
allTools?: any[];
|
|
60
61
|
timestamp: number;
|
|
61
62
|
} | {
|
|
62
63
|
type: 'auth_required';
|
|
@@ -210,7 +211,7 @@ interface ToolPolicy {
|
|
|
210
211
|
toolIds: string[];
|
|
211
212
|
updatedAt: number;
|
|
212
213
|
}
|
|
213
|
-
type McpRpcMethod = 'connect' | 'disconnect' | 'listTools' | 'callTool' | 'listSessions' | 'getSession' | 'finishAuth' | 'listPrompts' | 'getPrompt' | 'listResources' | 'readResource' | 'setToolPolicy' | 'getToolPolicy';
|
|
214
|
+
type McpRpcMethod = 'connect' | 'disconnect' | 'reconnect' | 'listTools' | 'callTool' | 'listSessions' | 'getSession' | 'finishAuth' | 'listPrompts' | 'getPrompt' | 'listResources' | 'readResource' | 'setToolPolicy' | 'getToolPolicy';
|
|
214
215
|
interface McpRpcRequest {
|
|
215
216
|
id: string;
|
|
216
217
|
method: McpRpcMethod;
|
|
@@ -235,6 +236,7 @@ interface ConnectParams {
|
|
|
235
236
|
interface DisconnectParams {
|
|
236
237
|
sessionId: string;
|
|
237
238
|
}
|
|
239
|
+
type ReconnectParams = ConnectParams;
|
|
238
240
|
interface SessionParams {
|
|
239
241
|
sessionId: string;
|
|
240
242
|
}
|
|
@@ -266,7 +268,7 @@ interface SetToolPolicyParams {
|
|
|
266
268
|
interface GetToolPolicyParams {
|
|
267
269
|
sessionId: string;
|
|
268
270
|
}
|
|
269
|
-
type McpRpcParams = ConnectParams | DisconnectParams | SessionParams | CallToolParams | GetPromptParams | ReadResourceParams | FinishAuthParams | SetToolPolicyParams | GetToolPolicyParams | undefined;
|
|
271
|
+
type McpRpcParams = ConnectParams | DisconnectParams | ReconnectParams | SessionParams | CallToolParams | GetPromptParams | ReadResourceParams | FinishAuthParams | SetToolPolicyParams | GetToolPolicyParams | undefined;
|
|
270
272
|
interface SessionInfo {
|
|
271
273
|
sessionId: string;
|
|
272
274
|
serverId?: string;
|
package/package.json
CHANGED
|
@@ -102,6 +102,10 @@ export class SSEClient {
|
|
|
102
102
|
return this.sendRequest<DisconnectResult>('disconnect', { sessionId });
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
async reconnectToServer(params: ConnectParams): Promise<ConnectResult> {
|
|
106
|
+
return this.sendRequest<ConnectResult>('reconnect', params);
|
|
107
|
+
}
|
|
108
|
+
|
|
105
109
|
async setToolPolicy(
|
|
106
110
|
sessionId: string,
|
|
107
111
|
toolPolicy: Pick<ToolPolicy, 'mode'> & { toolIds?: string[] }
|
|
@@ -135,7 +135,6 @@ export function createOAuthPopupRedirectHandler(
|
|
|
135
135
|
openCenteredPopup(url, {
|
|
136
136
|
...options,
|
|
137
137
|
onBlocked: options.onBlocked ?? ((blockedUrl) => {
|
|
138
|
-
window.alert('Popup blocked! Allow popups for this site to complete authentication.');
|
|
139
138
|
window.location.href = blockedUrl;
|
|
140
139
|
}),
|
|
141
140
|
});
|
|
@@ -179,30 +178,26 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
|
|
|
179
178
|
: '';
|
|
180
179
|
const targetSessionId = rawState ? parseOAuthState(rawState)?.sessionId || rawState : '';
|
|
181
180
|
|
|
182
|
-
if (
|
|
181
|
+
if (targetSessionId) {
|
|
183
182
|
pendingPopupsRef.current.set(targetSessionId, { popupWindow, state: rawState });
|
|
184
183
|
}
|
|
185
184
|
|
|
186
185
|
if (!targetSessionId) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
});
|
|
192
|
-
}
|
|
186
|
+
postPopupResult(popupWindow, {
|
|
187
|
+
success: false,
|
|
188
|
+
error: 'Missing OAuth session identifier',
|
|
189
|
+
});
|
|
193
190
|
return;
|
|
194
191
|
}
|
|
195
192
|
|
|
196
193
|
const targetSession = connections.find((connection) => connection.sessionId === targetSessionId);
|
|
197
194
|
if (!targetSession) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
});
|
|
205
|
-
}
|
|
195
|
+
postPopupResult(popupWindow, {
|
|
196
|
+
sessionId: targetSessionId,
|
|
197
|
+
state: rawState,
|
|
198
|
+
success: false,
|
|
199
|
+
error: 'OAuth session not found in the current client state',
|
|
200
|
+
});
|
|
206
201
|
return;
|
|
207
202
|
}
|
|
208
203
|
|
|
@@ -217,14 +212,12 @@ export function useMcpOAuthPopup<TConnection extends OAuthPopupConnectionLike>(
|
|
|
217
212
|
} catch (error) {
|
|
218
213
|
processingCodesRef.current.delete(codeKey);
|
|
219
214
|
pendingPopupsRef.current.delete(targetSession.sessionId);
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
});
|
|
227
|
-
}
|
|
215
|
+
postPopupResult(popupWindow, {
|
|
216
|
+
sessionId: rawState,
|
|
217
|
+
state: rawState,
|
|
218
|
+
success: false,
|
|
219
|
+
error: error instanceof Error ? error.message : 'Failed to finish auth',
|
|
220
|
+
});
|
|
228
221
|
}
|
|
229
222
|
};
|
|
230
223
|
|
|
@@ -371,7 +371,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
return prev.map((c: McpConnection) =>
|
|
374
|
-
c.sessionId === event.sessionId ? { ...c, tools: event.tools, state: 'READY', updatedAt: new Date() } : c
|
|
374
|
+
c.sessionId === event.sessionId ? { ...c, tools: event.tools, allTools: (event as any).allTools, state: 'READY', updatedAt: new Date() } : c
|
|
375
375
|
);
|
|
376
376
|
}
|
|
377
377
|
|
|
@@ -516,24 +516,15 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
516
516
|
throw new Error('SSE client not initialized');
|
|
517
517
|
}
|
|
518
518
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
await clientRef.current.disconnectFromServer(existing.sessionId);
|
|
525
|
-
if (isMountedRef.current) {
|
|
526
|
-
setConnections((prev: McpConnection[]) =>
|
|
527
|
-
prev.filter((c: McpConnection) => c.sessionId !== existing.sessionId)
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
519
|
+
const result = await clientRef.current.reconnectToServer(params);
|
|
520
|
+
|
|
521
|
+
// The server emits connection events for the new session via SSE,
|
|
522
|
+
// so local state is kept in sync automatically. No manual removal
|
|
523
|
+
// of the old session or insertion of the new one needed here.
|
|
531
524
|
|
|
532
|
-
// Connect fresh
|
|
533
|
-
const result = await clientRef.current.connectToServer(params);
|
|
534
525
|
return result.sessionId;
|
|
535
526
|
},
|
|
536
|
-
[
|
|
527
|
+
[]
|
|
537
528
|
);
|
|
538
529
|
|
|
539
530
|
/**
|
|
@@ -84,6 +84,7 @@ export interface McpConnection {
|
|
|
84
84
|
transport?: string;
|
|
85
85
|
state: McpConnectionState;
|
|
86
86
|
tools: ToolInfo[];
|
|
87
|
+
allTools?: ToolInfo[];
|
|
87
88
|
authUrl?: string;
|
|
88
89
|
error?: string;
|
|
89
90
|
createdAt?: Date;
|
|
@@ -294,7 +295,7 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
294
295
|
case 'tools_discovered': {
|
|
295
296
|
const index = connections.value.findIndex((c) => c.sessionId === event.sessionId);
|
|
296
297
|
if (index !== -1) {
|
|
297
|
-
connections.value[index] = { ...connections.value[index], tools: event.tools, state: 'READY', updatedAt: new Date() };
|
|
298
|
+
connections.value[index] = { ...connections.value[index], tools: event.tools, allTools: (event as any).allTools, state: 'READY', updatedAt: new Date() };
|
|
298
299
|
}
|
|
299
300
|
break;
|
|
300
301
|
}
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
McpRpcResponse,
|
|
19
19
|
ConnectParams,
|
|
20
20
|
DisconnectParams,
|
|
21
|
+
ReconnectParams,
|
|
21
22
|
SessionParams,
|
|
22
23
|
CallToolParams,
|
|
23
24
|
GetPromptParams,
|
|
@@ -166,6 +167,10 @@ export class SSEConnectionManager {
|
|
|
166
167
|
result = await this.connect(request.params as ConnectParams);
|
|
167
168
|
break;
|
|
168
169
|
|
|
170
|
+
case 'reconnect':
|
|
171
|
+
result = await this.reconnect(request.params as ReconnectParams);
|
|
172
|
+
break;
|
|
173
|
+
|
|
169
174
|
case 'disconnect':
|
|
170
175
|
result = await this.disconnect(request.params as DisconnectParams);
|
|
171
176
|
break;
|
|
@@ -363,6 +368,87 @@ export class SSEConnectionManager {
|
|
|
363
368
|
}
|
|
364
369
|
}
|
|
365
370
|
|
|
371
|
+
/**
|
|
372
|
+
* Reconnect to an MCP server — tears down the active client transport/connection
|
|
373
|
+
* and creates a fresh connection while reusing the existing session credentials in a single RPC call.
|
|
374
|
+
*/
|
|
375
|
+
private async reconnect(params: ReconnectParams): Promise<ConnectResult> {
|
|
376
|
+
const { serverId: rawServerId, serverName, serverUrl, callbackUrl, transportType } = params;
|
|
377
|
+
const headers = normalizeHeaders(params.headers);
|
|
378
|
+
|
|
379
|
+
// Normalize serverId the same way connect() does
|
|
380
|
+
const serverId = rawServerId && rawServerId.length <= 12
|
|
381
|
+
? rawServerId
|
|
382
|
+
: generateServerId();
|
|
383
|
+
|
|
384
|
+
// Find existing session for the same server to reuse its session ID
|
|
385
|
+
const existingSessions = await sessions.list(this.userId);
|
|
386
|
+
const duplicate = existingSessions.find(s =>
|
|
387
|
+
s.serverId === serverId || s.serverUrl === serverUrl
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
// Reuse the duplicate sessionId if present, otherwise generate a new one
|
|
391
|
+
const sessionId = duplicate ? duplicate.sessionId : await sessions.generateSessionId();
|
|
392
|
+
|
|
393
|
+
if (duplicate) {
|
|
394
|
+
// Disconnect any active in-memory client transport without deleting the database session
|
|
395
|
+
const existingClient = this.clients.get(duplicate.sessionId);
|
|
396
|
+
if (existingClient) {
|
|
397
|
+
await existingClient.disconnect();
|
|
398
|
+
this.clients.delete(duplicate.sessionId);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
const clientMetadata = await this.getResolvedClientMetadata();
|
|
404
|
+
|
|
405
|
+
// Create a new client instantiating the reused session ID (which preserves DCR credentials and tokens)
|
|
406
|
+
const client = new MCPClient({
|
|
407
|
+
userId: this.userId,
|
|
408
|
+
sessionId,
|
|
409
|
+
serverId,
|
|
410
|
+
serverName,
|
|
411
|
+
serverUrl,
|
|
412
|
+
callbackUrl,
|
|
413
|
+
transportType,
|
|
414
|
+
headers,
|
|
415
|
+
...clientMetadata,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
this.clients.set(sessionId, client);
|
|
419
|
+
|
|
420
|
+
client.onConnectionEvent((event) => {
|
|
421
|
+
this.emitConnectionEvent(event);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
client.onObservabilityEvent((event) => {
|
|
425
|
+
this.sendEvent(event);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
await client.connect();
|
|
429
|
+
await this.listPolicyFilteredTools(sessionId);
|
|
430
|
+
|
|
431
|
+
return { sessionId, success: true };
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (error instanceof UnauthorizedError) {
|
|
434
|
+
this.clients.delete(sessionId);
|
|
435
|
+
return { sessionId, success: true };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
this.emitConnectionEvent({
|
|
439
|
+
type: 'error',
|
|
440
|
+
sessionId,
|
|
441
|
+
serverId,
|
|
442
|
+
error: error instanceof Error ? error.message : 'Connection failed',
|
|
443
|
+
errorType: 'connection',
|
|
444
|
+
timestamp: Date.now(),
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
this.clients.delete(sessionId);
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
366
452
|
/**
|
|
367
453
|
* Disconnect from an MCP server
|
|
368
454
|
*/
|
|
@@ -422,6 +508,22 @@ export class SSEConnectionManager {
|
|
|
422
508
|
return client;
|
|
423
509
|
}
|
|
424
510
|
|
|
511
|
+
/**
|
|
512
|
+
* Fetches all tools from the remote MCP server and emits a `tools_discovered` event.
|
|
513
|
+
*
|
|
514
|
+
* Two lists are always published together:
|
|
515
|
+
* - `tools` — policy-filtered list that agents are allowed to call.
|
|
516
|
+
* - `allTools` — the complete, unfiltered list used by the management UI so
|
|
517
|
+
* that blocked tools still appear as checkboxes in the dialog.
|
|
518
|
+
*
|
|
519
|
+
* `fetchTools()` is called first (populates the in-memory cache), then
|
|
520
|
+
* `gateway.listTools()` re-uses that cache internally — so only one remote
|
|
521
|
+
* network round-trip is made regardless of how many callers follow.
|
|
522
|
+
*
|
|
523
|
+
* @param sessionId - The session whose tools should be discovered.
|
|
524
|
+
* @returns The session record and the policy-filtered tool list.
|
|
525
|
+
* @throws {Error} When the session does not exist in the store.
|
|
526
|
+
*/
|
|
425
527
|
private async listPolicyFilteredTools(sessionId: string): Promise<{ session: Session; result: ListToolsRpcResult }> {
|
|
426
528
|
const session = await sessions.get(this.userId, sessionId);
|
|
427
529
|
if (!session) {
|
|
@@ -429,6 +531,7 @@ export class SSEConnectionManager {
|
|
|
429
531
|
}
|
|
430
532
|
|
|
431
533
|
const client = await this.getOrCreateClient(sessionId);
|
|
534
|
+
const allTools = await client.fetchTools().catch(() => ({ tools: [] }));
|
|
432
535
|
const gateway = createToolPolicyGateway(this.userId, sessionId, client);
|
|
433
536
|
const result = await gateway.listTools();
|
|
434
537
|
|
|
@@ -438,13 +541,17 @@ export class SSEConnectionManager {
|
|
|
438
541
|
serverId: session.serverId ?? 'unknown',
|
|
439
542
|
toolCount: result.tools.length,
|
|
440
543
|
tools: result.tools,
|
|
544
|
+
allTools: allTools.tools,
|
|
441
545
|
timestamp: Date.now(),
|
|
442
546
|
});
|
|
443
547
|
|
|
444
548
|
return { session, result };
|
|
445
549
|
}
|
|
550
|
+
|
|
446
551
|
/**
|
|
447
|
-
*
|
|
552
|
+
* Returns the policy-filtered tool list for a session (agent-facing).
|
|
553
|
+
* Internally re-uses `listPolicyFilteredTools` which also emits a
|
|
554
|
+
* `tools_discovered` SSE event to keep client state up to date.
|
|
448
555
|
*/
|
|
449
556
|
private async listTools(params: SessionParams): Promise<ListToolsRpcResult> {
|
|
450
557
|
const { sessionId } = params;
|
|
@@ -485,7 +592,15 @@ export class SSEConnectionManager {
|
|
|
485
592
|
}
|
|
486
593
|
|
|
487
594
|
/**
|
|
488
|
-
*
|
|
595
|
+
* Persists a new tool access policy for a session and broadcasts the updated
|
|
596
|
+
* filtered tool list to all connected browser clients via a `tools_discovered` event.
|
|
597
|
+
*
|
|
598
|
+
* Both `tools` (policy-filtered) and `allTools` (complete list) are emitted
|
|
599
|
+
* so the management UI can immediately reflect the new checkbox states without
|
|
600
|
+
* an additional round-trip to the server.
|
|
601
|
+
*
|
|
602
|
+
* @param params - Session ID and the new `{ mode, toolIds }` policy to apply.
|
|
603
|
+
* @throws {Error} When the session does not exist or the policy references unknown tool IDs.
|
|
489
604
|
*/
|
|
490
605
|
private async setToolPolicy(params: SetToolPolicyParams): Promise<SetToolPolicyResult> {
|
|
491
606
|
const { sessionId } = params;
|
|
@@ -508,6 +623,7 @@ export class SSEConnectionManager {
|
|
|
508
623
|
serverId: session.serverId ?? 'unknown',
|
|
509
624
|
toolCount: filteredTools.length,
|
|
510
625
|
tools: filteredTools,
|
|
626
|
+
allTools: allTools.tools,
|
|
511
627
|
timestamp: Date.now(),
|
|
512
628
|
});
|
|
513
629
|
|
|
@@ -518,8 +634,10 @@ export class SSEConnectionManager {
|
|
|
518
634
|
toolCount: filteredTools.length,
|
|
519
635
|
};
|
|
520
636
|
}
|
|
637
|
+
|
|
521
638
|
/**
|
|
522
|
-
*
|
|
639
|
+
* Proxies a tool invocation to the remote MCP server.
|
|
640
|
+
* Resolves the client for the given session and delegates to the tool router.
|
|
523
641
|
*/
|
|
524
642
|
private async callTool(params: CallToolParams): Promise<CallToolResult> {
|
|
525
643
|
const { sessionId, toolName, toolArgs } = params;
|
|
@@ -509,13 +509,20 @@ export class MCPClient {
|
|
|
509
509
|
}
|
|
510
510
|
|
|
511
511
|
/**
|
|
512
|
-
* Connects to the MCP server
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
512
|
+
* Connects to the MCP server.
|
|
513
|
+
*
|
|
514
|
+
* Automatically validates and refreshes OAuth tokens if needed.
|
|
515
|
+
* Saves the session to Redis on first successful connection.
|
|
516
|
+
*
|
|
517
|
+
* The in-memory tools cache (`cachedTools`) is cleared at the start of every
|
|
518
|
+
* call so that a reconnection always fetches a fresh tool list from the remote
|
|
519
|
+
* server — even if the same `MCPClient` instance is reused.
|
|
520
|
+
*
|
|
521
|
+
* @throws {UnauthorizedError} When OAuth authorization is required.
|
|
522
|
+
* @throws {Error} When connection fails for other reasons.
|
|
517
523
|
*/
|
|
518
524
|
async connect(): Promise<void> {
|
|
525
|
+
this.cachedTools = null;
|
|
519
526
|
// Close any existing transport so we can negotiate a fresh session.
|
|
520
527
|
// The SDK Client throws if asked to connect() while a transport is
|
|
521
528
|
// already attached; close() detaches it cleanly so the same Client
|
|
@@ -782,18 +789,41 @@ export class MCPClient {
|
|
|
782
789
|
}
|
|
783
790
|
|
|
784
791
|
/**
|
|
785
|
-
*
|
|
786
|
-
*
|
|
792
|
+
* In-memory cache for the remote server's full tools list.
|
|
793
|
+
*
|
|
794
|
+
* Populated on the first `fetchTools()` call and reused for the lifetime of
|
|
795
|
+
* the connection. Cleared to `null` at the start of `connect()` so that a
|
|
796
|
+
* reconnect always retrieves a fresh list, and also in `dispose()` to release
|
|
797
|
+
* the memory when the client is no longer needed.
|
|
798
|
+
*/
|
|
799
|
+
private cachedTools: ListToolsResult | null = null;
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Lists all available tools from the connected MCP server without emitting
|
|
803
|
+
* discovery events. The result is cached in memory for the lifetime of the
|
|
804
|
+
* connection — subsequent callers (e.g. `gateway.listTools()` running right
|
|
805
|
+
* after `fetchTools()`) pay zero extra network cost.
|
|
806
|
+
*
|
|
807
|
+
* Gateways use this to apply a tool-access policy before publishing the
|
|
808
|
+
* filtered list to agents or UI state.
|
|
809
|
+
*
|
|
810
|
+
* @returns The full `ListToolsResult` from the remote server.
|
|
811
|
+
* @throws {Error} When the client is not connected or the request times out.
|
|
787
812
|
*/
|
|
788
813
|
async fetchTools(): Promise<ListToolsResult> {
|
|
814
|
+
if (this.cachedTools) {
|
|
815
|
+
return this.cachedTools;
|
|
816
|
+
}
|
|
789
817
|
const request: ListToolsRequest = {
|
|
790
818
|
method: 'tools/list',
|
|
791
819
|
params: {},
|
|
792
820
|
};
|
|
793
821
|
|
|
794
|
-
|
|
822
|
+
const result = await this.withRetry(() =>
|
|
795
823
|
this.client!.request(request, ListToolsResultSchema)
|
|
796
824
|
);
|
|
825
|
+
this.cachedTools = result;
|
|
826
|
+
return result;
|
|
797
827
|
}
|
|
798
828
|
|
|
799
829
|
/**
|
|
@@ -1119,10 +1149,14 @@ export class MCPClient {
|
|
|
1119
1149
|
}
|
|
1120
1150
|
|
|
1121
1151
|
/**
|
|
1122
|
-
*
|
|
1123
|
-
*
|
|
1152
|
+
* Disposes all event emitters and releases cached state.
|
|
1153
|
+
*
|
|
1154
|
+
* Clears `cachedTools` to free memory, and disposes the connection and
|
|
1155
|
+
* observability event emitters so downstream listeners are unsubscribed.
|
|
1156
|
+
* Call this when the client is permanently shut down (not just disconnected).
|
|
1124
1157
|
*/
|
|
1125
1158
|
dispose(): void {
|
|
1159
|
+
this.cachedTools = null;
|
|
1126
1160
|
this._onConnectionEvent.dispose();
|
|
1127
1161
|
this._onObservabilityEvent.dispose();
|
|
1128
1162
|
}
|
|
@@ -1177,14 +1211,4 @@ export class MCPClient {
|
|
|
1177
1211
|
getSessionId(): string {
|
|
1178
1212
|
return this.sessionId;
|
|
1179
1213
|
}
|
|
1180
|
-
|
|
1181
1214
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|