MCP Servers

A collection of Model Context Protocol servers, templates, tools and more.

A stdio to streamable-http proxy, with OAuth handling

Created 5/31/2026
Updated about 3 hours ago
Repository documentation and setup instructions

hyper-mcp-remote

CI Crates.io License: Apache-2.0

A small, fast stdio → Streamable-HTTP MCP proxy with OAuth 2.1, written in Rust.

hyper-mcp-remote lets any local Model Context Protocol client that only speaks stdio — Claude Desktop, Cursor, Zed, Continue, Windsurf, … — connect to a remote MCP server that speaks Streamable HTTP and requires OAuth.

It is a drop-in Rust alternative to the mcp-remote npm package — no Node.js runtime, single static binary, OS-native secret storage.


Why?

The MCP specification defines two transports:

  • stdio — what almost every desktop client implements.
  • Streamable HTTP — what hosted/remote servers (GitLab, Linear, Atlassian, Cloudflare, GitHub, …) use, often gated by OAuth 2.1.

If your client only speaks stdio, you need a bridge. hyper-mcp-remote is that bridge:

┌────────────────┐  stdio  ┌─────────────────┐   HTTPS + OAuth  ┌────────────────┐
│  MCP client    │ ──────▶ │ hyper-mcp-remote │ ───────────────▶ │ remote MCP svr │
│ (Claude/Zed/…) │ ◀────── │   (this crate)   │ ◀─────────────── │ (GitLab/etc.)  │
└────────────────┘         └─────────────────┘                  └────────────────┘

On first run it performs the full MCP OAuth dance (RFC 9728 discovery → RFC 8414 metadata → dynamic client registration → OAuth 2.1 authorize + PKCE → token exchange), pops the user's browser open for consent, and stores the resulting refresh token in the OS-native secret store. Subsequent launches start with no user interaction.

Features

  • 🦀 Single static binary — no Node, no Python, no runtime to manage.
  • 🔐 Full MCP OAuth 2.1 — discovery (RFC 9728 / RFC 8414), dynamic client registration (RFC 7591), PKCE, refresh, RFC 8707 resource binding.
  • 🗝️ OS-native credential storage — macOS Keychain, Windows Credential Manager, freedesktop Secret Service. Falls back to a 0600 JSON file when no keyring backend is available (headless Linux, CI).
  • 🔁 Bidirectional proxy — forwards sampling, elicitation, list_roots, log notifications, progress, cancellation, and resource update streams in both directions.
  • 🧩 Custom headers with ${ENV} interpolation — pass API keys or extra tenant routing headers from your MCP client config.
  • 🪵 Safe logging — writes to a daily-rolling file (stderr is unusable on a stdio transport); install location is overridable via HYPER_MCP_REMOTE_LOG_PATH.
  • 🚦 Refuses cleartext — non-loopback http:// URLs are rejected unless you explicitly pass --allow-http.
  • 💓 Keepalive pings — periodic MCP ping requests keep the remote session warm across idle load-balancers, NATs, and server-side timeouts, with an early log-visible signal when the upstream becomes unreachable.

Installation

From crates.io

cargo install hyper-mcp-remote --locked

From source

git clone https://github.com/hyper-mcp-rs/hyper-mcp-remote
cd hyper-mcp-remote
cargo install --path . --locked

Docker

docker pull ghcr.io/hyper-mcp-rs/hyper-mcp-remote:latest

The image's entrypoint is the binary itself, so usage is identical:

docker run --rm -i ghcr.io/hyper-mcp-rs/hyper-mcp-remote:latest https://example.com/mcp

Quick start

Claude Desktop / Cursor / Windsurf

Add an entry to your MCP client's server config. The shape is the same everywhere; this is the Claude Desktop variant (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):

{
  "mcpServers": {
    "gitlab": {
      "command": "hyper-mcp-remote",
      "args": ["https://gitlab.com/api/v4/mcp"]
    }
  }
}

Zed

In your Zed settings.json:

{
  "context_servers": {
    "gitlab": {
      "command": {
        "path": "hyper-mcp-remote",
        "args": ["https://gitlab.com/api/v4/mcp"]
      }
    }
  }
}

The first time Zed (or any client) launches the proxy, your browser will open to complete the OAuth consent flow. After that, tokens are cached and launches are silent.

Usage

hyper-mcp-remote [OPTIONS] <SERVER_URL>

Arguments

| Argument | Description | | -------------- | ------------------------------------------------------------- | | <SERVER_URL> | URL of the remote MCP server, e.g. https://example.com/mcp. |

Options

| Flag | Description | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | --header <HEADER> | Extra HTTP header to send on every request. Format Name: value. Supports ${ENV} interpolation. Repeatable. | | --resource <URL> | OAuth resource identifier (RFC 8707). Use to isolate sessions when proxying multiple tenants of the same server. | | --client-name <NAME> | OAuth client name advertised during dynamic client registration. Default: hyper-mcp-remote. | | --scope <SCOPES> | Comma-separated OAuth scopes, overriding any scopes discovered from server metadata. | | --callback-host <HOST> | Bind address for the local OAuth callback server (loopback only). Default: 127.0.0.1. | | --callback-port <PORT> | Fixed port for the local OAuth callback server. Defaults to an OS-selected ephemeral port. Set this when the auth server requires a fixed redirect URI. | | --auth-timeout-secs <SECS> | Max time to wait for the user to complete the browser flow. Default: 300. | | --reset-auth | Forget any cached tokens for this server and force a fresh OAuth flow. | | --allow-http | Allow non-loopback http:// server URLs (cleartext). Disabled by default. | | --ping-interval-secs <SECS> | Interval between MCP ping requests sent to the remote to keep its session alive. Set to 0 to disable. Default: 60. | | --ping-timeout-secs <SECS> | Per-ping timeout. A timed-out ping is logged but does not tear the session down — the transport remains the authority on liveness. Default: 10. | | -h, --help | Print help. | | -V, --version | Print version. |

Passing secrets via headers

Values inside --header may contain ${VAR} placeholders that are interpolated from the process environment at startup. This lets you keep secrets in your MCP client's env: block instead of embedding them in args:

{
  "mcpServers": {
    "internal": {
      "command": "hyper-mcp-remote",
      "args": [
        "https://internal.example.com/mcp",
        "--header", "Authorization: Bearer ${INTERNAL_API_TOKEN}",
        "--header", "X-Tenant: ${TENANT_ID}"
      ],
      "env": {
        "INTERNAL_API_TOKEN": "…",
        "TENANT_ID": "acme"
      }
    }
  }
}

Unknown env vars expand to an empty string and are logged as a warning.

Anonymous (no-OAuth) servers

If the server accepts unauthenticated requests, the proxy detects that on the first probe and skips OAuth entirely. There's nothing extra to configure.

Keeping the session alive

Many hosted MCP deployments sit behind load balancers, NAT devices, or have server-side idle timeouts that silently drop an otherwise-healthy session after a few minutes of inactivity. Without a keepalive, the next tool call your client makes would be the thing that discovers the session is gone — surfacing a confusing error mid-task.

The proxy sends an MCP ping request every --ping-interval-secs (default 30) to keep the upstream session warm. Each ping is bounded by --ping-timeout-secs (default 10); timeouts and failures are logged at warn but the session is not torn down on a single failed ping — the underlying transport remains the authority on whether the connection is actually dead.

Tune or disable as needed:

{
  "mcpServers": {
    "chatty": {
      "command": "hyper-mcp-remote",
      "args": [
        "https://example.com/mcp",
        "--ping-interval-secs", "60"   // every 60s instead of 30s
      ]
    },
    "already-keepalived": {
      "command": "hyper-mcp-remote",
      "args": [
        "https://example.com/mcp",
        "--ping-interval-secs", "0"    // disable; the server is fine on its own
      ]
    }
  }
}

Where things live

| Item | Location | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Cached OAuth tokens | OS keyring under service io.github.hyper-mcp-rs.hyper-mcp-remote. Fallback file: <data_local_dir>/hyper-mcp-remote/credentials/<hash>.json (mode 0600). | | Rolling log files | <config_dir>/hyper-mcp-remote/logs/mcp-server.log (daily rotation). Override with HYPER_MCP_REMOTE_LOG_PATH=/some/dir. Verbosity controlled by RUST_LOG (e.g. RUST_LOG=hyper_mcp_remote=debug). |

<config_dir> and <data_local_dir> follow the OS conventions used by the directories crate (XDG on Linux, Application Support on macOS, %APPDATA% on Windows).

Troubleshooting

The browser didn't open. Some headless or remote contexts (SSH sessions, Docker, locked-down desktops) can't spawn a browser. The authorization URL is also printed to the rolling log file — open it manually on a machine with a browser, log in, and let the proxy receive the callback. Use --callback-host / --callback-port if you need to tunnel the redirect.

OAuth keeps re-prompting. Run with --reset-auth once to clear stale tokens, then try again. If you proxy multiple tenants of the same server, give each its own --resource value so their tokens don't collide.

The server uses self-signed certificates. Not currently supported — reqwest is built with rustls and the system trust store. Open an issue if you need a flag for this.

Where are my logs? See Where things live above. The proxy never logs to stderr because that would corrupt the stdio MCP framing.

How it works

sequenceDiagram
    participant C as MCP client (stdio)
    participant P as hyper-mcp-remote
    participant B as Browser
    participant A as Authorization server
    participant S as Remote MCP server

    C->>P: spawn (stdio)
    P->>S: probe (no auth)
    S-->>P: 401 + WWW-Authenticate
    P->>S: GET .well-known/oauth-protected-resource
    S-->>P: PRM metadata (RFC 9728)
    P->>A: GET .well-known/oauth-authorization-server
    A-->>P: AS metadata (RFC 8414)
    P->>A: dynamic client registration (RFC 7591)
    P->>B: open authorize URL (PKCE)
    B->>A: user consents
    A-->>P: code (loopback redirect)
    P->>A: token exchange
    A-->>P: access + refresh tokens
    P->>P: persist to OS keyring
    C->>P: initialize / tools/list / …
    P->>S: same, with Bearer token
    S-->>P: responses + server-initiated requests
    P-->>C: forwarded

On every later launch, the keyring lookup short-circuits everything from "probe" through "token exchange".

Building & testing

cargo build --release
cargo test                            # unit + offline integration tests
cargo test --test e2e_gitlab -- --ignored --nocapture   # live OAuth against gitlab.com

The e2e test spawns the compiled binary and drives it through a child-process MCP client, exactly the way Claude Desktop or Zed do. It is #[ignore]d because it requires network access and (on first run) human interaction in a browser.

Project layout

src/
├── main.rs        # binary entrypoint, signal handling, wiring
├── cli.rs         # clap argument definitions and validation
├── headers.rs     # --header parsing + ${ENV} interpolation
├── logging.rs     # rolling-file tracing setup (installed via #[ctor])
├── proxy.rs       # bidirectional stdio ⇄ HTTP MCP forwarder
├── session.rs     # session/credential keying
├── transport.rs   # Streamable-HTTP transport construction
└── auth/
    ├── mod.rs        # OAuth state-machine orchestration
    ├── discovery.rs  # RFC 9728 + RFC 8414 discovery
    ├── callback.rs   # loopback callback HTTP server
    └── storage.rs    # OS keyring + file fallback credential store

Contributing

Issues and PRs welcome. Before sending a patch:

cargo fmt --all
cargo clippy --all-targets --all-features -- -D warnings
cargo test

Pre-commit hooks are wired through lefthook; run lefthook install once after cloning.

License

Apache-2.0. See LICENSE.

Acknowledgements

  • mcp-remote by Glen Maddern — the original Node implementation this project is functionally compatible with.
  • rmcp — the Rust MCP SDK that powers the transport and OAuth machinery.
Quick Setup
Installation guide for this server

Installation Command (package not published)

git clone https://github.com/hyper-mcp-rs/hyper-mcp-remote
Manual Installation: Please check the README for detailed setup instructions and any additional dependencies required.

Cursor configuration (mcp.json)

{ "mcpServers": { "hyper-mcp-rs-hyper-mcp-remote": { "command": "git", "args": [ "clone", "https://github.com/hyper-mcp-rs/hyper-mcp-remote" ] } } }