inspector/core/mcp/remote/createRemoteTransport.ts
cliffhall d57801d7b3 feat(core): report connection state on a request timeout (#2318)
A request timeout used to surface as a bare `Request timed out`. Every
fact needed to explain it was already in-process: what the client was
still waiting on, when it last heard back, and the state of the
standalone GET notification stream. The SDK's timeout now rejects with
all three, and the same snapshot is shown live in Connection Info and
on the Network entry for the stream.

- core/mcp/connectionDiagnostics.ts: the snapshot type, its one-line
  description, and `annotateRequestTimeout`, which rebuilds the SDK's
  per-request timeout with the state at that moment.
- InspectorClient decorates the SDK Client's `request` (the one funnel
  every verb goes through) so the annotation covers the whole surface;
  tracks outstanding requests with their send time, the last response,
  and the most recent long-lived transport stream; exposes
  `getConnectionDiagnostics()` and `connectionDiagnosticsChange`.
- fetchTracking watches a long-lived stream through a tee'd clone,
  counting SSE events and reporting the close, via a new
  `updateStream` callback wired through the node transport, the remote
  session (`fetch_stream_update`), and the browser transport.
- FetchRequestLogState patches `entry.stream`; the Network entry's
  placeholder reports the event count and open/closed state.
- Connection Info gains a Connection Activity section, ticking while
  open (`useTickingClock`).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APKSYHGxXBUpnR7ErFiLJn
Signed-off-by: cliffhall <cliff@futurescale.com>
2026-09-13 21:25:31 -04:00

69 lines
2.3 KiB
TypeScript

/**
* Factory for createRemoteTransport - returns a CreateTransport that uses the remote server.
*/
import type {
MCPServerConfig,
CreateTransport,
CreateTransportOptions,
CreateTransportResult,
} from "../types.js";
import { RemoteClientTransport } from "./remoteClientTransport.js";
export interface RemoteTransportFactoryOptions {
/** Base URL of the remote server (e.g. http://localhost:3000) */
baseUrl: string;
/** Optional auth token for x-mcp-remote-auth header */
authToken?: string;
/** Optional fetch implementation (for proxy or testing) */
fetchFn?: typeof fetch;
}
/**
* Creates a CreateTransport that produces RemoteClientTransport instances
* connecting to the given remote server.
*
* @example
* import { API_SERVER_ENV_VARS } from '@inspector/core/mcp/remote/constants.js';
* const createTransport = createRemoteTransport({
* baseUrl: 'http://localhost:3000',
* authToken: process.env[API_SERVER_ENV_VARS.AUTH_TOKEN],
* });
* const inspector = new InspectorClient(config, {
* environment: {
* transport: createTransport,
* },
* ...
* });
*/
export function createRemoteTransport(
options: RemoteTransportFactoryOptions,
): CreateTransport {
return (
config: MCPServerConfig,
transportOptions: CreateTransportOptions = {},
): CreateTransportResult => {
// Use only the factory's fetchFn, not InspectorClient's. The transport's HTTP
// (connect, GET events, send, disconnect) must support streaming (GET /api/mcp/events
// is SSE). A remoted fetch (e.g. createRemoteFetch) buffers responses and cannot
// stream. So we ignore transportOptions.fetchFn here; auth can still use a
// remoted fetch via InspectorClient's fetchFn (effectiveAuthFetch).
const transport = new RemoteClientTransport(
{
baseUrl: options.baseUrl,
authToken: options.authToken,
fetchFn: options.fetchFn,
onStderr: transportOptions.onStderr,
onFetchRequest: transportOptions.onFetchRequest,
onFetchResponseBody: transportOptions.onFetchResponseBody,
onFetchStreamUpdate: transportOptions.onFetchStreamUpdate,
authProvider: transportOptions.authProvider,
settings: transportOptions.settings,
},
config,
);
return { transport };
};
}