#!/usr/bin/env node
import {
  JSONRPCMessageSchema,
  NodeOAuthClientProvider,
  connectToRemoteServer,
  createLazyAuthCoordinator,
  debugLog,
  discoverOAuthServerInfo,
  forgetRejectedAuthorization,
  hasUsableTokens,
  log,
  mcpProxy,
  parseCommandLineArgs,
  serverIssuesAuthChallenge,
  setupSignalHandlers
} from "./chunk-MSHFUYEB.js";

// src/proxy.ts
import { EventEmitter } from "events";

// node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_hono@4.11.7_zod@4.1.13/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
import process2 from "node:process";

// node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_hono@4.11.7_zod@4.1.13/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js
var ReadBuffer = class {
  append(chunk) {
    this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
  }
  readMessage() {
    if (!this._buffer) {
      return null;
    }
    const index = this._buffer.indexOf("\n");
    if (index === -1) {
      return null;
    }
    const line = this._buffer.toString("utf8", 0, index).replace(/\r$/, "");
    this._buffer = this._buffer.subarray(index + 1);
    return deserializeMessage(line);
  }
  clear() {
    this._buffer = void 0;
  }
};
function deserializeMessage(line) {
  return JSONRPCMessageSchema.parse(JSON.parse(line));
}
function serializeMessage(message) {
  return JSON.stringify(message) + "\n";
}

// node_modules/.pnpm/@modelcontextprotocol+sdk@1.25.3_hono@4.11.7_zod@4.1.13/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
var StdioServerTransport = class {
  constructor(_stdin = process2.stdin, _stdout = process2.stdout) {
    this._stdin = _stdin;
    this._stdout = _stdout;
    this._readBuffer = new ReadBuffer();
    this._started = false;
    this._ondata = (chunk) => {
      this._readBuffer.append(chunk);
      this.processReadBuffer();
    };
    this._onerror = (error) => {
      this.onerror?.(error);
    };
  }
  /**
   * Starts listening for messages on stdin.
   */
  async start() {
    if (this._started) {
      throw new Error("StdioServerTransport already started! If using Server class, note that connect() calls start() automatically.");
    }
    this._started = true;
    this._stdin.on("data", this._ondata);
    this._stdin.on("error", this._onerror);
  }
  processReadBuffer() {
    while (true) {
      try {
        const message = this._readBuffer.readMessage();
        if (message === null) {
          break;
        }
        this.onmessage?.(message);
      } catch (error) {
        this.onerror?.(error);
      }
    }
  }
  async close() {
    this._stdin.off("data", this._ondata);
    this._stdin.off("error", this._onerror);
    const remainingDataListeners = this._stdin.listenerCount("data");
    if (remainingDataListeners === 0) {
      this._stdin.pause();
    }
    this._readBuffer.clear();
    this.onclose?.();
  }
  send(message) {
    return new Promise((resolve) => {
      const json = serializeMessage(message);
      if (this._stdout.write(json)) {
        resolve();
      } else {
        this._stdout.once("drain", resolve);
      }
    });
  }
};

// src/proxy.ts
var canFinishAuth = (transport) => typeof transport.finishAuth === "function";
async function runProxy(serverUrl, callbackPath, callbackPort, specifiedPort, headers, transportStrategy = "http-first", host, staticOAuthClientMetadata, staticOAuthClientInfo, clientMetadataUrl, useIdToken, useDeviceCode, authorizeResource, skipResourceParameter, authorizeParams, ignoredTools, authTimeoutMs, serverUrlHash, keepAlive) {
  const events = new EventEmitter();
  const strictPort = !!specifiedPort || !!staticOAuthClientInfo || !!clientMetadataUrl;
  const authCoordinator = createLazyAuthCoordinator(serverUrlHash, callbackPath, callbackPort, events, authTimeoutMs, strictPort);
  log("Discovering OAuth server configuration...");
  const discoveryResult = await discoverOAuthServerInfo(serverUrl, headers);
  if (discoveryResult.protectedResourceMetadata) {
    log(`Discovered authorization server: ${discoveryResult.authorizationServerUrl}`);
    if (discoveryResult.protectedResourceMetadata.scopes_supported) {
      debugLog("Protected Resource Metadata scopes", {
        scopes_supported: discoveryResult.protectedResourceMetadata.scopes_supported
      });
    }
  } else {
    debugLog("No Protected Resource Metadata found, using server URL as authorization server");
  }
  const authProvider = new NodeOAuthClientProvider({
    serverUrl: discoveryResult.authorizationServerUrl,
    callbackPath,
    callbackPort,
    host,
    clientName: "MCP CLI Proxy",
    staticOAuthClientMetadata,
    staticOAuthClientInfo,
    clientMetadataUrl,
    useIdToken,
    useDeviceCode,
    authorizeResource,
    skipResourceParameter,
    authorizeParams,
    serverUrlHash,
    authorizationServerMetadata: discoveryResult.authorizationServerMetadata,
    protectedResourceMetadata: discoveryResult.protectedResourceMetadata,
    wwwAuthenticateScope: discoveryResult.wwwAuthenticateScope
  });
  const localTransport = new StdioServerTransport();
  let server = null;
  const authInitializer = async () => {
    const authState = await authCoordinator.initializeAuth();
    server = authState.server;
    if (authState.actualPort !== callbackPort) {
      log(`Using callback port ${authState.actualPort}`);
      authProvider.setCallbackPort(authState.actualPort);
    }
    return {
      waitForAuthCode: authState.waitForAuthCode,
      skipBrowserAuth: authState.skipBrowserAuth
    };
  };
  if (!useDeviceCode && !await hasUsableTokens(serverUrlHash) && await serverIssuesAuthChallenge(serverUrl, headers)) {
    await authInitializer();
  }
  try {
    const remoteTransport = await connectToRemoteServer(null, serverUrl, authProvider, headers, authInitializer, transportStrategy);
    mcpProxy({
      transportToClient: localTransport,
      transportToServer: remoteTransport,
      ignoredTools,
      keepAlive,
      /**
       * Discards a token the server refused straight after issuing it.
       *
       * Clearing it is what turns the next attempt back into an ordinary 401, which `reauthorize`
       * below can actually answer. Left alone, the SDK keeps presenting the same dead credential
       * and refuses to ask for another, so the failure repeats on every run.
       */
      forgetRejectedAuthorization: () => forgetRejectedAuthorization(authProvider, remoteTransport),
      /**
       * Finishes a sign-in the remote server asked for mid-session.
       *
       * Startup is not the only time a server can demand one: tokens get revoked, refresh tokens
       * lapse, and a server may leave `initialize` public while protecting a tool. Until now those
       * all ended the same way - the SDK opened a browser, nothing was listening on the callback
       * port, and the code was dropped on the floor.
       */
      reauthorize: async () => {
        if (useDeviceCode) {
          log("Device authorization completed; retrying with the tokens it produced");
          return;
        }
        const { waitForAuthCode, skipBrowserAuth } = await authInitializer();
        if (skipBrowserAuth) {
          log("Another instance is completing the sign-in; retrying with the tokens it writes");
          return;
        }
        const { code, state } = await waitForAuthCode();
        if (state) authProvider.useAuthorizationState(state);
        if (!canFinishAuth(remoteTransport)) {
          throw new Error(`${remoteTransport.constructor.name} cannot complete an authorization`);
        }
        await remoteTransport.finishAuth(code);
        log("Re-authorized with the remote server");
      }
    });
    await localTransport.start();
    log("Local STDIO server running");
    log(`Proxy established successfully between local STDIO and remote ${remoteTransport.constructor.name}`);
    log("Press Ctrl+C to exit");
    const cleanup = async () => {
      await remoteTransport.close();
      await localTransport.close();
      if (server) {
        server.close();
      }
    };
    setupSignalHandlers(cleanup);
  } catch (error) {
    log("Fatal error:", error);
    if (error instanceof Error && error.message.includes("self-signed certificate in certificate chain")) {
      log(`You may be behind a VPN!

If you are behind a VPN, you can try setting the NODE_EXTRA_CA_CERTS environment variable to point
to the CA certificate file. If using claude_desktop_config.json, this might look like:

{
  "mcpServers": {
    "\${mcpServerName}": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://remote.mcp.server/sse"
      ],
      "env": {
        "NODE_EXTRA_CA_CERTS": "\${your CA certificate file path}.pem"
      }
    }
  }
}
        `);
    }
    if (server) {
      server.close();
    }
    process.exit(1);
  }
}
parseCommandLineArgs(process.argv.slice(2), "Usage: mcp-remote <https://server-url> [callback-port] [--debug]").then(
  ({
    serverUrl,
    callbackPath,
    callbackPort,
    specifiedPort,
    headers,
    transportStrategy,
    host,
    staticOAuthClientMetadata,
    staticOAuthClientInfo,
    clientMetadataUrl,
    useIdToken,
    useDeviceCode,
    authorizeResource,
    skipResourceParameter,
    authorizeParams,
    ignoredTools,
    authTimeoutMs,
    serverUrlHash,
    keepAlive
  }) => {
    return runProxy(
      serverUrl,
      callbackPath,
      callbackPort,
      specifiedPort,
      headers,
      transportStrategy,
      host,
      staticOAuthClientMetadata,
      staticOAuthClientInfo,
      clientMetadataUrl,
      useIdToken,
      useDeviceCode,
      authorizeResource,
      skipResourceParameter,
      authorizeParams,
      ignoredTools,
      authTimeoutMs,
      serverUrlHash,
      keepAlive
    );
  }
).catch((error) => {
  log("Fatal error:", error);
  process.exit(1);
});
