@mcp-ts/sdk 1.4.0 → 1.5.1
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 +20 -27
- package/dist/adapters/agui-adapter.d.mts +16 -0
- package/dist/adapters/agui-adapter.d.ts +16 -0
- package/dist/adapters/agui-adapter.js +185 -0
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +185 -0
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +2 -0
- package/dist/adapters/agui-middleware.d.ts +2 -0
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/adapters/ai-adapter.d.mts +21 -0
- package/dist/adapters/ai-adapter.d.ts +21 -0
- package/dist/adapters/ai-adapter.js +175 -0
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs +175 -0
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +16 -0
- package/dist/adapters/langchain-adapter.d.ts +16 -0
- package/dist/adapters/langchain-adapter.js +179 -0
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +179 -0
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/client/react.d.mts +94 -8
- package/dist/client/react.d.ts +94 -8
- package/dist/client/react.js +364 -26
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +358 -27
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.d.mts +4 -4
- package/dist/client/vue.d.ts +4 -4
- package/dist/client/vue.js +11 -2
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs +11 -2
- package/dist/client/vue.mjs.map +1 -1
- package/dist/{index-CQr9q0bF.d.mts → index-DcYfpY3H.d.mts} +1 -1
- package/dist/{index-nE_7Io0I.d.ts → index-GfC_eNEv.d.ts} +1 -1
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +938 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +923 -13
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +58 -12
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +58 -12
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +86 -4
- package/dist/shared/index.d.ts +86 -4
- package/dist/shared/index.js +874 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +865 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/tool-router-Bo8qZbsD.d.ts +325 -0
- package/dist/tool-router-XnWVxPzv.d.mts +325 -0
- package/dist/{types-CW6lghof.d.mts → types-CfCoIsWI.d.mts} +27 -1
- package/dist/{types-CW6lghof.d.ts → types-CfCoIsWI.d.ts} +27 -1
- package/package.json +3 -2
- package/src/adapters/agui-adapter.ts +79 -0
- package/src/adapters/ai-adapter.ts +75 -0
- package/src/adapters/langchain-adapter.ts +74 -0
- package/src/client/react/index.ts +16 -0
- package/src/client/react/oauth-popup.tsx +446 -0
- package/src/client/react/use-mcp-apps.tsx +50 -32
- package/src/client/react/use-mcp.ts +36 -3
- package/src/client/vue/use-mcp.ts +38 -3
- package/src/server/handlers/sse-handler.ts +39 -0
- package/src/server/index.ts +2 -0
- package/src/server/mcp/oauth-client.ts +35 -15
- package/src/shared/index.ts +36 -0
- package/src/shared/meta-tools.ts +387 -0
- package/src/shared/schema-compressor.ts +124 -0
- package/src/shared/tool-index.ts +499 -0
- package/src/shared/tool-router.ts +469 -0
- package/src/shared/types.ts +30 -0
- package/supabase/migrations/20260421010000_add_session_cleanup_cron.sql +32 -0
|
@@ -15,7 +15,9 @@ import React, {
|
|
|
15
15
|
useImperativeHandle,
|
|
16
16
|
type MutableRefObject,
|
|
17
17
|
} from 'react';
|
|
18
|
-
import {
|
|
18
|
+
import type { UseAppHostOptions } from './use-app-host.js';
|
|
19
|
+
import { useAppHost } from './use-app-host.js';
|
|
20
|
+
import { resolveMetaToolProxy } from '../../shared/meta-tools.js';
|
|
19
21
|
import type { SSEClient } from '../core/sse-client.js';
|
|
20
22
|
import { APP_HOST_DEFAULTS } from '../core/constants.js';
|
|
21
23
|
import type { SandboxConfig } from '../core/app-host.js';
|
|
@@ -56,9 +58,10 @@ export interface McpAppRendererHandle {
|
|
|
56
58
|
/** Props for {@link useMcpApps}'s `McpAppRenderer` (client is supplied via the hook). */
|
|
57
59
|
export interface McpAppRendererProps extends Pick<UseAppHostOptions, 'sandbox' | 'hostContext' | 'onCallTool' | 'onReadResource' | 'onFallbackRequest' | 'onMessage' | 'onOpenLink' | 'onLoggingMessage' | 'onSizeChanged' | 'onError'> {
|
|
58
60
|
name: string;
|
|
61
|
+
client?: McpClient | null;
|
|
59
62
|
toolResourceUri?: string;
|
|
60
63
|
html?: string;
|
|
61
|
-
input?: Record<string, unknown
|
|
64
|
+
input?: Record<string, unknown> | null;
|
|
62
65
|
result?: unknown;
|
|
63
66
|
status?: 'executing' | 'inProgress' | 'complete' | 'idle';
|
|
64
67
|
toolInputPartial?: any;
|
|
@@ -102,8 +105,10 @@ const McpAppViewInner = forwardRef<McpAppRendererHandle, McpAppViewProps>(functi
|
|
|
102
105
|
},
|
|
103
106
|
ref,
|
|
104
107
|
) {
|
|
108
|
+
|
|
105
109
|
const mcpClient = clientRef.current;
|
|
106
|
-
const
|
|
110
|
+
const { toolName: resolvedToolName, args: resolvedInput } = resolveMetaToolProxy(name, input);
|
|
111
|
+
const metadata = getMcpAppMetadata(mcpClient, resolvedToolName, resolvedInput);
|
|
107
112
|
const sseClient = mcpClient?.sseClient ?? null;
|
|
108
113
|
const resourceUri = toolResourceUri || metadata?.resourceUri;
|
|
109
114
|
const appSessionId = metadata?.sessionId;
|
|
@@ -194,7 +199,7 @@ const McpAppViewInner = forwardRef<McpAppRendererHandle, McpAppViewProps>(functi
|
|
|
194
199
|
|
|
195
200
|
const sentInputRef = useRef(false);
|
|
196
201
|
const sentResultRef = useRef(false);
|
|
197
|
-
const lastInputRef = useRef(
|
|
202
|
+
const lastInputRef = useRef(resolvedInput);
|
|
198
203
|
const lastResultRef = useRef(result);
|
|
199
204
|
const lastStatusRef = useRef(status);
|
|
200
205
|
|
|
@@ -236,15 +241,16 @@ const McpAppViewInner = forwardRef<McpAppRendererHandle, McpAppViewProps>(functi
|
|
|
236
241
|
.catch((err) => setError(err instanceof Error ? err : new Error(String(err))));
|
|
237
242
|
}, [host, resourceUri, html, appSessionId]);
|
|
238
243
|
|
|
244
|
+
// Send tool inputs
|
|
239
245
|
useEffect(() => {
|
|
240
|
-
if (!host || !isLaunched || !resourceUri || !appSessionId || !
|
|
246
|
+
if (!host || !isLaunched || !resourceUri || !appSessionId || !resolvedInput) return;
|
|
241
247
|
|
|
242
|
-
if (!sentInputRef.current || JSON.stringify(
|
|
248
|
+
if (!sentInputRef.current || JSON.stringify(resolvedInput) !== JSON.stringify(lastInputRef.current)) {
|
|
243
249
|
sentInputRef.current = true;
|
|
244
|
-
lastInputRef.current =
|
|
245
|
-
host.sendToolInput(
|
|
250
|
+
lastInputRef.current = resolvedInput;
|
|
251
|
+
host.sendToolInput(resolvedInput);
|
|
246
252
|
}
|
|
247
|
-
}, [host, isLaunched,
|
|
253
|
+
}, [host, isLaunched, resolvedInput, resourceUri, appSessionId, resolvedToolName]);
|
|
248
254
|
|
|
249
255
|
useEffect(() => {
|
|
250
256
|
if (!host || !isLaunched || !resourceUri || !appSessionId || result === undefined) return;
|
|
@@ -259,7 +265,7 @@ const McpAppViewInner = forwardRef<McpAppRendererHandle, McpAppViewProps>(functi
|
|
|
259
265
|
: result;
|
|
260
266
|
host.sendToolResult(formattedResult);
|
|
261
267
|
}
|
|
262
|
-
}, [host, isLaunched, result, status, resourceUri, appSessionId,
|
|
268
|
+
}, [host, isLaunched, result, status, resourceUri, appSessionId, resolvedToolName]);
|
|
263
269
|
|
|
264
270
|
useEffect(() => {
|
|
265
271
|
if (status === 'executing' && lastStatusRef.current !== 'executing') {
|
|
@@ -354,34 +360,44 @@ const McpAppViewInner = forwardRef<McpAppRendererHandle, McpAppViewProps>(functi
|
|
|
354
360
|
const McpAppView = memo(McpAppViewInner);
|
|
355
361
|
McpAppView.displayName = 'McpAppView';
|
|
356
362
|
|
|
363
|
+
/**
|
|
364
|
+
* Renders an interactive MCP application inside a sandboxed iframe.
|
|
365
|
+
*/
|
|
366
|
+
export const McpAppRenderer = memo(
|
|
367
|
+
forwardRef<McpAppRendererHandle, McpAppRendererProps>(function McpAppRenderer(
|
|
368
|
+
{ client, ...props },
|
|
369
|
+
ref
|
|
370
|
+
) {
|
|
371
|
+
const clientRef = useRef(client || null);
|
|
372
|
+
clientRef.current = client || null;
|
|
373
|
+
|
|
374
|
+
return <McpAppView ref={ref} clientRef={clientRef} {...props} />;
|
|
375
|
+
})
|
|
376
|
+
);
|
|
377
|
+
|
|
357
378
|
/**
|
|
358
379
|
* Helpers scoped to one `mcpClient`. Pass the client here once; `McpAppRenderer` only needs per-tool props (`name`, `input`, `result`, `status`).
|
|
359
380
|
*
|
|
360
381
|
* @param mcpClient - From `useMcp()` or context (for example `useMcpContext()`).
|
|
382
|
+
* @deprecated Use the standalone `<McpAppRenderer>` component and `getMcpAppMetadata` utility directly.
|
|
361
383
|
*/
|
|
362
384
|
export function useMcpApps(mcpClient: McpClient | null) {
|
|
363
|
-
// Stable `McpAppRenderer` type: parent re-renders and `connections` updates must not remount the iframe.
|
|
364
|
-
const clientRef = useRef(mcpClient);
|
|
365
|
-
clientRef.current = mcpClient;
|
|
366
|
-
|
|
367
385
|
const getAppMetadata = useCallback(
|
|
368
|
-
(toolName: string) => getMcpAppMetadata(
|
|
369
|
-
[]
|
|
386
|
+
(toolName: string) => getMcpAppMetadata(mcpClient, toolName),
|
|
387
|
+
[mcpClient]
|
|
370
388
|
);
|
|
371
389
|
|
|
372
|
-
const
|
|
373
|
-
const
|
|
374
|
-
props,
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return { getAppMetadata, McpAppRenderer };
|
|
390
|
+
const BoundMcpAppRenderer = useMemo(() => {
|
|
391
|
+
const Renderer = forwardRef<McpAppRendererHandle, Omit<McpAppRendererProps, 'client'>>(
|
|
392
|
+
function BoundMcpAppRenderer(props, ref) {
|
|
393
|
+
return <McpAppRenderer ref={ref} client={mcpClient} {...props} />;
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
Renderer.displayName = 'BoundMcpAppRenderer';
|
|
397
|
+
return memo(Renderer);
|
|
398
|
+
}, [mcpClient]);
|
|
399
|
+
|
|
400
|
+
return { getAppMetadata, McpAppRenderer: BoundMcpAppRenderer };
|
|
385
401
|
}
|
|
386
402
|
|
|
387
403
|
function extractToolName(fullName: string): string {
|
|
@@ -389,13 +405,15 @@ function extractToolName(fullName: string): string {
|
|
|
389
405
|
return match?.[1] || fullName;
|
|
390
406
|
}
|
|
391
407
|
|
|
392
|
-
function getMcpAppMetadata(
|
|
408
|
+
export function getMcpAppMetadata(
|
|
393
409
|
mcpClient: McpClient | null,
|
|
394
|
-
toolName: string
|
|
410
|
+
toolName: string,
|
|
411
|
+
input?: Record<string, unknown> | null
|
|
395
412
|
): McpAppMetadata | undefined {
|
|
396
413
|
if (!mcpClient) return undefined;
|
|
397
414
|
|
|
398
|
-
const
|
|
415
|
+
const { toolName: proxyToolName } = resolveMetaToolProxy(toolName, input);
|
|
416
|
+
const extractedName = extractToolName(proxyToolName);
|
|
399
417
|
|
|
400
418
|
for (const conn of mcpClient.connections) {
|
|
401
419
|
for (const tool of conn.tools) {
|
|
@@ -290,17 +290,48 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
290
290
|
state === 'CONNECTED' ||
|
|
291
291
|
state === 'DISCOVERING';
|
|
292
292
|
|
|
293
|
+
const getVisibleState = (
|
|
294
|
+
incomingState: McpConnectionState,
|
|
295
|
+
existingState?: McpConnectionState,
|
|
296
|
+
previousState?: McpConnectionState
|
|
297
|
+
): McpConnectionState => {
|
|
298
|
+
// `INITIALIZING` has two meanings in practice:
|
|
299
|
+
// 1. genuine cold start / reconnect work
|
|
300
|
+
// 2. an internal setup step that happens mid-OAuth completion
|
|
301
|
+
//
|
|
302
|
+
// For case (2), showing raw `INITIALIZING` creates a confusing user-facing
|
|
303
|
+
// sequence like AUTHENTICATING -> INITIALIZING -> AUTHENTICATED.
|
|
304
|
+
// We keep the raw event stream intact for observability, but collapse the
|
|
305
|
+
// visible state back into the current auth phase in the UI.
|
|
306
|
+
if (
|
|
307
|
+
incomingState === 'INITIALIZING' &&
|
|
308
|
+
(existingState === 'AUTHENTICATING' ||
|
|
309
|
+
existingState === 'AUTHENTICATED' ||
|
|
310
|
+
previousState === 'AUTHENTICATING' ||
|
|
311
|
+
previousState === 'AUTHENTICATED')
|
|
312
|
+
) {
|
|
313
|
+
return existingState === 'AUTHENTICATED' || previousState === 'AUTHENTICATED'
|
|
314
|
+
? 'AUTHENTICATED'
|
|
315
|
+
: 'AUTHENTICATING';
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return incomingState;
|
|
319
|
+
};
|
|
320
|
+
|
|
293
321
|
setConnections((prev: McpConnection[]) => {
|
|
294
322
|
switch (event.type) {
|
|
295
323
|
case 'state_changed': {
|
|
296
324
|
const existing = prev.find((c: McpConnection) => c.sessionId === event.sessionId);
|
|
297
325
|
if (existing) {
|
|
326
|
+
// Normalize the incoming backend state into the smoother user-facing
|
|
327
|
+
// state we want to render for this existing connection.
|
|
328
|
+
const normalizedState = getVisibleState(event.state, existing.state, event.previousState);
|
|
298
329
|
// In stateless per-request transport, tool calls can emit transient reconnect states.
|
|
299
330
|
// Keep READY sticky to avoid UI flicker from READY -> CONNECTING -> CONNECTED.
|
|
300
331
|
const nextState =
|
|
301
|
-
existing.state === 'READY' && isTransientReconnectState(
|
|
332
|
+
existing.state === 'READY' && isTransientReconnectState(normalizedState)
|
|
302
333
|
? existing.state
|
|
303
|
-
:
|
|
334
|
+
: normalizedState;
|
|
304
335
|
|
|
305
336
|
return prev.map((c: McpConnection) =>
|
|
306
337
|
c.sessionId === event.sessionId ? {
|
|
@@ -323,7 +354,9 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
323
354
|
serverId: event.serverId,
|
|
324
355
|
serverName: event.serverName,
|
|
325
356
|
serverUrl: event.serverUrl,
|
|
326
|
-
|
|
357
|
+
// New connections do not have prior local state, so we normalize
|
|
358
|
+
// only against the server-reported previous state.
|
|
359
|
+
state: getVisibleState(event.state, undefined, event.previousState),
|
|
327
360
|
createdAt: event.createdAt ? new Date(event.createdAt) : undefined,
|
|
328
361
|
tools: [],
|
|
329
362
|
},
|
|
@@ -240,16 +240,49 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
240
240
|
state === 'CONNECTED' ||
|
|
241
241
|
state === 'DISCOVERING';
|
|
242
242
|
|
|
243
|
+
const getVisibleState = (
|
|
244
|
+
incomingState: McpConnectionState,
|
|
245
|
+
existingState?: McpConnectionState,
|
|
246
|
+
previousState?: McpConnectionState
|
|
247
|
+
): McpConnectionState => {
|
|
248
|
+
// `INITIALIZING` has two meanings in practice:
|
|
249
|
+
// 1. genuine cold start / reconnect work
|
|
250
|
+
// 2. an internal setup step that happens mid-OAuth completion
|
|
251
|
+
//
|
|
252
|
+
// For case (2), showing raw `INITIALIZING` creates a confusing user-facing
|
|
253
|
+
// sequence like AUTHENTICATING -> INITIALIZING -> AUTHENTICATED.
|
|
254
|
+
// We keep the raw event stream intact for observability, but collapse the
|
|
255
|
+
// visible state back into the current auth phase in the UI.
|
|
256
|
+
if (
|
|
257
|
+
incomingState === 'INITIALIZING' &&
|
|
258
|
+
(
|
|
259
|
+
existingState === 'AUTHENTICATING' ||
|
|
260
|
+
existingState === 'AUTHENTICATED' ||
|
|
261
|
+
previousState === 'AUTHENTICATING' ||
|
|
262
|
+
previousState === 'AUTHENTICATED'
|
|
263
|
+
)
|
|
264
|
+
) {
|
|
265
|
+
return existingState === 'AUTHENTICATED' || previousState === 'AUTHENTICATED'
|
|
266
|
+
? 'AUTHENTICATED'
|
|
267
|
+
: 'AUTHENTICATING';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return incomingState;
|
|
271
|
+
};
|
|
272
|
+
|
|
243
273
|
switch (event.type) {
|
|
244
274
|
case 'state_changed': {
|
|
245
275
|
const existing = connections.value.find((c) => c.sessionId === event.sessionId);
|
|
246
276
|
if (existing) {
|
|
277
|
+
// Normalize the incoming backend state into the smoother user-facing
|
|
278
|
+
// state we want to render for this existing connection.
|
|
279
|
+
const normalizedState = getVisibleState(event.state, existing.state, event.previousState);
|
|
247
280
|
// In stateless per-request transport, tool calls can emit transient reconnect states.
|
|
248
281
|
// Keep READY sticky to avoid UI flicker from READY -> CONNECTING -> CONNECTED.
|
|
249
282
|
const nextState =
|
|
250
|
-
existing.state === 'READY' && isTransientReconnectState(
|
|
283
|
+
existing.state === 'READY' && isTransientReconnectState(normalizedState)
|
|
251
284
|
? existing.state
|
|
252
|
-
:
|
|
285
|
+
: normalizedState;
|
|
253
286
|
|
|
254
287
|
const index = connections.value.indexOf(existing);
|
|
255
288
|
connections.value[index] = {
|
|
@@ -268,7 +301,9 @@ export function useMcp(options: UseMcpOptions): McpClient {
|
|
|
268
301
|
sessionId: event.sessionId,
|
|
269
302
|
serverId: event.serverId,
|
|
270
303
|
serverName: event.serverName,
|
|
271
|
-
|
|
304
|
+
// New connections do not have prior local state, so we normalize
|
|
305
|
+
// only against the server-reported previous state.
|
|
306
|
+
state: getVisibleState(event.state, undefined, event.previousState),
|
|
272
307
|
createdAt: event.createdAt ? new Date(event.createdAt) : undefined,
|
|
273
308
|
tools: [],
|
|
274
309
|
}];
|
|
@@ -363,9 +363,24 @@ export class SSEConnectionManager {
|
|
|
363
363
|
return existing;
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
const session = await storage.getSession(this.identity, sessionId);
|
|
367
|
+
if (!session) {
|
|
368
|
+
throw new Error('Session not found');
|
|
369
|
+
}
|
|
370
|
+
|
|
366
371
|
const client = new MCPClient({
|
|
367
372
|
identity: this.identity,
|
|
368
373
|
sessionId,
|
|
374
|
+
// These fields are optional in MCPClient, but when rehydrating a known
|
|
375
|
+
// stored session on the server we pass them explicitly to preserve the
|
|
376
|
+
// original transport/connection metadata instead of relying on lazy
|
|
377
|
+
// reloading during initialize().
|
|
378
|
+
serverId: session.serverId,
|
|
379
|
+
serverName: session.serverName,
|
|
380
|
+
serverUrl: session.serverUrl,
|
|
381
|
+
callbackUrl: session.callbackUrl,
|
|
382
|
+
transportType: session.transportType,
|
|
383
|
+
headers: session.headers,
|
|
369
384
|
});
|
|
370
385
|
|
|
371
386
|
// Subscribe to events before connecting
|
|
@@ -437,6 +452,16 @@ export class SSEConnectionManager {
|
|
|
437
452
|
const client = new MCPClient({
|
|
438
453
|
identity: this.identity,
|
|
439
454
|
sessionId,
|
|
455
|
+
// These fields are optional in MCPClient, but when rehydrating a known
|
|
456
|
+
// stored session on the server we pass them explicitly to preserve the
|
|
457
|
+
// original transport/connection metadata instead of relying on lazy
|
|
458
|
+
// reloading during initialize().
|
|
459
|
+
serverId: session.serverId,
|
|
460
|
+
serverName: session.serverName,
|
|
461
|
+
serverUrl: session.serverUrl,
|
|
462
|
+
callbackUrl: session.callbackUrl,
|
|
463
|
+
transportType: session.transportType,
|
|
464
|
+
headers: session.headers,
|
|
440
465
|
...clientMetadata,
|
|
441
466
|
});
|
|
442
467
|
|
|
@@ -478,6 +503,20 @@ export class SSEConnectionManager {
|
|
|
478
503
|
const client = new MCPClient({
|
|
479
504
|
identity: this.identity,
|
|
480
505
|
sessionId,
|
|
506
|
+
// These fields are optional in MCPClient, but when rehydrating a known
|
|
507
|
+
// stored session on the server we pass them explicitly to preserve the
|
|
508
|
+
// original connection metadata instead of relying on lazy
|
|
509
|
+
// reloading during initialize().
|
|
510
|
+
serverId: session.serverId,
|
|
511
|
+
serverName: session.serverName,
|
|
512
|
+
serverUrl: session.serverUrl,
|
|
513
|
+
callbackUrl: session.callbackUrl,
|
|
514
|
+
// NOTE: transportType is intentionally omitted here.
|
|
515
|
+
// The session's stored transportType is a placeholder ('streamable_http')
|
|
516
|
+
// set before transport negotiation. Omitting it lets MCPClient auto-negotiate
|
|
517
|
+
// (try streamable_http → SSE fallback), which is critical for servers like
|
|
518
|
+
// Neon that only support SSE transport.
|
|
519
|
+
headers: session.headers,
|
|
481
520
|
});
|
|
482
521
|
|
|
483
522
|
client.onConnectionEvent((event) => this.emitConnectionEvent(event));
|
package/src/server/index.ts
CHANGED
|
@@ -500,16 +500,10 @@ export class MCPClient {
|
|
|
500
500
|
this.emitStateChange('CONNECTED');
|
|
501
501
|
this.emitProgress('Connected successfully');
|
|
502
502
|
|
|
503
|
-
//
|
|
504
|
-
//
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const needsTtlPromotion = !existingSession || existingSession.active !== true;
|
|
508
|
-
|
|
509
|
-
if (needsTransportUpdate || needsTtlPromotion) {
|
|
510
|
-
console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
|
|
511
|
-
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
512
|
-
}
|
|
503
|
+
// Refresh session metadata on every successful connect so active sessions
|
|
504
|
+
// record ongoing usage and don't look dormant to storage cleanup jobs.
|
|
505
|
+
console.log(`[MCPClient] Saving session ${this.sessionId} with 12hr TTL (connect success)`);
|
|
506
|
+
await this.saveSession(SESSION_TTL_SECONDS, true);
|
|
513
507
|
} catch (error) {
|
|
514
508
|
/** Handle Authentication Errors */
|
|
515
509
|
if (
|
|
@@ -537,11 +531,17 @@ export class MCPClient {
|
|
|
537
531
|
: `OAuth authorization URL not available: ${detail}`;
|
|
538
532
|
this.emitError(message, 'auth');
|
|
539
533
|
this.emitStateChange('FAILED');
|
|
534
|
+
|
|
535
|
+
// Proactive Cleanup: This session has reached a terminal failure state.
|
|
536
|
+
// We remove it now to ensure the database remains lean, bypassing the
|
|
537
|
+
// automated lifecycle sweep.
|
|
540
538
|
try {
|
|
541
539
|
await storage.removeSession(this.identity, this.sessionId);
|
|
542
540
|
} catch {
|
|
543
|
-
//
|
|
541
|
+
// Non-blocking: Proactive cleanup failures are suppressed to prioritize
|
|
542
|
+
// the original error context.
|
|
544
543
|
}
|
|
544
|
+
|
|
545
545
|
throw new Error(message);
|
|
546
546
|
}
|
|
547
547
|
|
|
@@ -571,6 +571,19 @@ export class MCPClient {
|
|
|
571
571
|
const errorMessage = error instanceof Error ? error.message : 'Connection failed';
|
|
572
572
|
this.emitError(errorMessage, 'connection');
|
|
573
573
|
this.emitStateChange('FAILED');
|
|
574
|
+
|
|
575
|
+
// Terminal Handshake Failure: only purge transient sessions. Active
|
|
576
|
+
// sessions may still hold valid credentials for a later reconnect.
|
|
577
|
+
try {
|
|
578
|
+
const existingSession = await storage.getSession(this.identity, this.sessionId);
|
|
579
|
+
if (!existingSession || existingSession.active !== true) {
|
|
580
|
+
await storage.removeSession(this.identity, this.sessionId);
|
|
581
|
+
}
|
|
582
|
+
} catch {
|
|
583
|
+
// Non-blocking: Cleanup is performed on a best-effort basis and should
|
|
584
|
+
// not interfere with the primary error propagation.
|
|
585
|
+
}
|
|
586
|
+
|
|
574
587
|
throw error;
|
|
575
588
|
}
|
|
576
589
|
}
|
|
@@ -606,6 +619,7 @@ export class MCPClient {
|
|
|
606
619
|
|
|
607
620
|
let lastError: unknown;
|
|
608
621
|
let tokensExchanged = false;
|
|
622
|
+
let authenticatedStateEmitted = false;
|
|
609
623
|
|
|
610
624
|
for (const currentType of transportsToTry) {
|
|
611
625
|
const isLastAttempt = currentType === transportsToTry[transportsToTry.length - 1];
|
|
@@ -623,10 +637,11 @@ export class MCPClient {
|
|
|
623
637
|
this.emitProgress(`Tokens already exchanged, skipping auth step for ${currentType}...`);
|
|
624
638
|
}
|
|
625
639
|
|
|
626
|
-
|
|
627
|
-
|
|
640
|
+
if (!authenticatedStateEmitted) {
|
|
641
|
+
this.emitStateChange('AUTHENTICATED');
|
|
642
|
+
authenticatedStateEmitted = true;
|
|
643
|
+
}
|
|
628
644
|
|
|
629
|
-
this.emitStateChange('AUTHENTICATED');
|
|
630
645
|
this.emitProgress('Creating authenticated client...');
|
|
631
646
|
|
|
632
647
|
this.client = new Client(
|
|
@@ -650,6 +665,9 @@ export class MCPClient {
|
|
|
650
665
|
/** We explicitly try to connect with the transport we just auth'd with first */
|
|
651
666
|
await this.client.connect(this.transport);
|
|
652
667
|
|
|
668
|
+
/** Connection succeeded — lock in the transport type */
|
|
669
|
+
this.transportType = currentType;
|
|
670
|
+
|
|
653
671
|
this.emitStateChange('CONNECTED');
|
|
654
672
|
// Update session with 12hr TTL after successful OAuth
|
|
655
673
|
console.log(`[MCPClient] Updating session ${this.sessionId} to 12hr TTL (OAuth complete)`);
|
|
@@ -1108,7 +1126,9 @@ export class MCPClient {
|
|
|
1108
1126
|
* @returns Server name or undefined
|
|
1109
1127
|
*/
|
|
1110
1128
|
getServerName(): string | undefined {
|
|
1111
|
-
|
|
1129
|
+
const info = (this.client as any)?.getServerVersion();
|
|
1130
|
+
console.log('server info ->', info);
|
|
1131
|
+
return info?.title ?? info?.name ?? this.serverName;
|
|
1112
1132
|
}
|
|
1113
1133
|
|
|
1114
1134
|
/**
|
package/src/shared/index.ts
CHANGED
|
@@ -22,6 +22,8 @@ export * from './errors';
|
|
|
22
22
|
|
|
23
23
|
// Types
|
|
24
24
|
export type {
|
|
25
|
+
ToolClient,
|
|
26
|
+
ToolClientProvider,
|
|
25
27
|
ToolInfo,
|
|
26
28
|
McpRpcRequest,
|
|
27
29
|
McpRpcResponse,
|
|
@@ -73,3 +75,37 @@ export {
|
|
|
73
75
|
type ToolUiConfig,
|
|
74
76
|
} from './tool-utils.js';
|
|
75
77
|
|
|
78
|
+
// Tool Router — Context window optimization
|
|
79
|
+
export {
|
|
80
|
+
ToolRouter,
|
|
81
|
+
type ToolRouterOptions,
|
|
82
|
+
type ToolRouterStrategy,
|
|
83
|
+
type ToolRouterClientInput,
|
|
84
|
+
type ToolGroupInfo,
|
|
85
|
+
} from './tool-router.js';
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
ToolIndex,
|
|
89
|
+
type ToolSummary,
|
|
90
|
+
type IndexedTool,
|
|
91
|
+
type ToolIndexOptions,
|
|
92
|
+
type EmbedFn,
|
|
93
|
+
} from './tool-index.js';
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
SchemaCompressor,
|
|
97
|
+
type CompactTool,
|
|
98
|
+
type CompressionStats,
|
|
99
|
+
} from './schema-compressor.js';
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
createSearchToolDefinition,
|
|
103
|
+
createRegexSearchToolDefinition,
|
|
104
|
+
createGetSchemaToolDefinition,
|
|
105
|
+
createExecuteToolDefinition,
|
|
106
|
+
executeMetaTool,
|
|
107
|
+
isMetaTool,
|
|
108
|
+
resolveMetaToolProxy,
|
|
109
|
+
type CallToolFn,
|
|
110
|
+
} from './meta-tools.js';
|
|
111
|
+
|