|
1 | 1 | import type { FetchLike } from '@modelcontextprotocol/sdk/shared/transport.js' |
2 | | -import { createPinnedFetch } from '@/lib/core/security/input-validation.server' |
| 2 | +import { |
| 3 | + createPinnedFetch, |
| 4 | + createPinnedFetchWithDispatcher, |
| 5 | +} from '@/lib/core/security/input-validation.server' |
3 | 6 | import { validateMcpServerSsrf } from '@/lib/mcp/domain-check' |
4 | 7 |
|
| 8 | +/** Pinned fetch for the live MCP transport, plus a handle to release its sockets. */ |
| 9 | +export interface PinnedMcpFetch { |
| 10 | + /** Pinned fetch to hand to the MCP transport's `fetch` option. */ |
| 11 | + fetch: typeof fetch |
| 12 | + /** Tears down the underlying HTTP/2 Agent; call when the MCP client disconnects. */ |
| 13 | + close: () => Promise<void> |
| 14 | +} |
| 15 | + |
5 | 16 | /** |
6 | 17 | * Pinned fetch for the long-lived MCP transport, which reuses one Agent across |
7 | 18 | * a connection's requests. MCP servers are commonly behind HTTP/2 fronts (CDNs, |
8 | 19 | * cloud LBs), and undici's Agent is h1.1-only unless opted into h2 via ALPN, so |
9 | 20 | * the transport enables it. h2 is *not* used for one-shot flows (OAuth discovery, |
10 | 21 | * auth-type probe), where a per-request Agent would leave idle h2 sessions with |
11 | 22 | * no reuse benefit. Pinning is unaffected: the pinned lookup forces the socket to |
12 | | - * `resolvedIP` regardless of negotiated protocol. |
| 23 | + * `resolvedIP` regardless of negotiated protocol. The returned `close` binds the |
| 24 | + * Agent's teardown to the transport lifecycle so h2 sessions don't linger past |
| 25 | + * disconnect. |
13 | 26 | */ |
14 | | -export function createPinnedMcpFetch(resolvedIP: string): typeof fetch { |
15 | | - return createPinnedFetch(resolvedIP, { allowH2: true }) |
| 27 | +export function createPinnedMcpFetch(resolvedIP: string): PinnedMcpFetch { |
| 28 | + const { fetch: pinnedFetch, dispatcher } = createPinnedFetchWithDispatcher(resolvedIP, { |
| 29 | + allowH2: true, |
| 30 | + }) |
| 31 | + return { fetch: pinnedFetch, close: () => dispatcher.destroy() } |
16 | 32 | } |
17 | 33 |
|
18 | 34 | /** |
|
0 commit comments