The move that matters is where the secrets live: the Supabase token, the Notion header, the Linear key all sit in the gateway's config, and the person connecting authenticates as themselves — so access is granted and revoked in your identity provider rather than in everyone's editor settings.
A self-hosted gateway that sits between your AI clients and your MCP servers. You list the servers in an `mcp.json`, the gateway launches them and holds their credentials, and clients authenticate to the gateway through your OAuth provider instead of holding those secrets themselves.
- OAuth sign-in through any provider supported by Auth.js, so the same login your organisation already uses covers MCP access
- Both connection types on the far side: stdio servers launched by the gateway, and Streamable HTTP at `/mcp`
- Server selection with a `?server_name=` query parameter, so a client can attach to one entry from your `mcp.json` rather than all of them
- Upstream API tokens kept in the gateway's own config and never exposed to the people using it
- Deployment inside your own infrastructure, with sessions persisted to SQLite
Node.js version 22 or higher — below that it fails with `No such built-in module: node:sqlite`. Run it with `npx @mcp-s/secure-mcp-gateway`, which reads `./mcp.json` and `./.env` by default, or point at other paths with `--mcp-config` and `--envfile`. `AUTH_SECRET` is required and generated with `openssl rand -base64 33`; `AUTH_PROVIDER` names the provider, defaulting to `google`, with `AUTH_[Provider]_ID` and `AUTH_[Provider]_SECRET` alongside it, plus whatever else that provider needs — the Okta walkthrough in the docs shows the issuer and redirect URI it wants. `PORT` defaults to 3000, `BASE_URL` to `http://localhost:3000`, `TOKEN_EXPIRATION_TIME` to 86400000 milliseconds, and `DB_PATH` to `./mcp.sqlite`. On the client side, connect through `@mcp-s/mcp` over stdio or point straight at the HTTP endpoint.
One command plus a key — npx @mcp-s/secure-mcp-gateway, then supply credentials
