LABSCO SUMMARY
One login in front of every MCP server, and the API keys stay behind it.
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.
What it is
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.
What you get
- 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 yourmcp.jsonrather 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
Requirements
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.
Setup effort
One command plus credentials — npx @mcp-s/secure-mcp-gateway, then supply credentials
USE CASES
Giving a team access without giving out keys
Put the upstream credentials in the gateway's config and let people sign in with the identity provider they already use.
Revoking access when someone leaves
Access follows the OAuth account, so removing them in the provider removes their MCP access too.
Adding auth to a server that has none
Register the existing stdio server in mcp.json — it keeps working unchanged, now behind a login.
Connecting a client to just one server
Append the server name as a query parameter so that client attaches to a single entry rather than the whole set.
Auditing who reached what
Every interaction passes through one authenticated gateway, so the access trail lives in your identity provider's logs.
BEFORE YOU INSTALL
What to know first
Requires
- npx on PATH
- AUTH_SECRET in your environment
Where it runs
All 3 documented setups run the server on your machine.
INSTALLATION
Connect your client
Server setup
npx @mcp-s/secure-mcp-gatewayMCP Clients
Any MCP client — stdio
{
"mcpServers": {
"mcp-gateway": {
"command": "npx",
"args": [
"-y",
"@mcp-s/mcp"
],
"env": {
"BASE_URL": "http://localhost:3000"
}
}
}
}The configuration on this page does not set AUTH_SECRET; the project's instructions put it in a .env file. To supply AUTH_SECRET without depending on where the server looks for that file, use the block's "env" section.
Any MCP client — HTTP
{
"mcpServers": {
"mcp-gateway": {
"url": "http://localhost:3000/mcp"
}
}
}Cursor
{
"mcpServers": {
"okta-mcp-gateway": {
"url": "http://localhost:3000/mcp"
}
}
}