skgate

Registre officiel IA et agents

Self-hosted MCP gateway and LLM proxy: OAuth 2.1, model aliases, Grok subscription as an API.

Installation

Ajoutez ceci à la configuration MCP de votre client (Claude Desktop : claude_desktop_config.json ; Cursor : .cursor/mcp.json ; VS Code et les autres utilisent la même forme « mcpServers »).

OCI package ghcr.io/helv-io/skgate:0.28.0
Claude Code
claude mcp add skgate -e 'PUBLIC_URL=http://localhost:8080' -e 'OIDC_ISSUER=<OIDC_ISSUER>' -e 'OIDC_CLIENT_ID=<OIDC_CLIENT_ID>' -e 'OIDC_CLIENT_SECRET=<your oidc_client_secret>' -- docker run -i --rm ghcr.io/helv-io/skgate:0.28.0
Configuration JSON (Claude Desktop, Cursor, VS Code)
{
    "mcpServers": {
        "skgate": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "ghcr.io/helv-io/skgate:0.28.0"
            ],
            "env": {
                "PUBLIC_URL": "http://localhost:8080",
                "OIDC_ISSUER": "<OIDC_ISSUER>",
                "OIDC_CLIENT_ID": "<OIDC_CLIENT_ID>",
                "OIDC_CLIENT_SECRET": "<your oidc_client_secret>"
            }
        }
    }
}

Variables d'environnement

  • PUBLIC_URL obligatoire Public origin of skgate, no trailing slash.
  • OIDC_ISSUER obligatoire Issuer URL of the OIDC provider used for admin sign-in.
  • OIDC_CLIENT_ID obligatoire OIDC client ID.
  • OIDC_CLIENT_SECRET obligatoire secret OIDC client secret.
OCI package ghcr.io/helv-io/skgate:0.28.0-slim
Claude Code
claude mcp add skgate -e 'PUBLIC_URL=http://localhost:8080' -e 'OIDC_ISSUER=<OIDC_ISSUER>' -e 'OIDC_CLIENT_ID=<OIDC_CLIENT_ID>' -e 'OIDC_CLIENT_SECRET=<your oidc_client_secret>' -- docker run -i --rm ghcr.io/helv-io/skgate:0.28.0-slim
Configuration JSON (Claude Desktop, Cursor, VS Code)
{
    "mcpServers": {
        "skgate": {
            "command": "docker",
            "args": [
                "run",
                "-i",
                "--rm",
                "ghcr.io/helv-io/skgate:0.28.0-slim"
            ],
            "env": {
                "PUBLIC_URL": "http://localhost:8080",
                "OIDC_ISSUER": "<OIDC_ISSUER>",
                "OIDC_CLIENT_ID": "<OIDC_CLIENT_ID>",
                "OIDC_CLIENT_SECRET": "<your oidc_client_secret>"
            }
        }
    }
}

Variables d'environnement

  • PUBLIC_URL obligatoire Public origin of skgate, no trailing slash.
  • OIDC_ISSUER obligatoire Issuer URL of the OIDC provider used for admin sign-in.
  • OIDC_CLIENT_ID obligatoire OIDC client ID.
  • OIDC_CLIENT_SECRET obligatoire secret OIDC client secret.

Documentation

Tirée du README du projet sur GitHub, actualisée chaque semaine. Les liens ouvrent GitHub.

Sommaire
  1. skgate
  2. Name Origin
  3. Quality
  4. Quick start
  5. Proxy your SI providers
  6. Examples
  7. Features
  8. Tested MCP clients
  9. Directory mode
  10. How skgate compares
  11. Comparison
  12. Configuration
  13. Reverse proxy
  14. OIDC setup
  15. Security notes
  16. Built with SI assistance
  17. Development
  18. License
  19. Star History

skgate

Self-hosted MCP gateway and LLM proxy. Serve any MCP server or REST API behind OAuth 2.1 from one address, and proxy your LLM providers (OpenAI, Anthropic, Gemini, Grok, Ollama and more) through one OpenAI-compatible API with model aliases and virtual keys. Your Grok subscription works too, no API key needed.

Any MCP server. skgate can run them for you, so you do not need one container per server.

Name Origin

skgate /ɛsˈkɑːɡeɪt/ (ess-KAH-gate)

"sk" is how a lot of SI API keys start, and "gate" is for gateway. Not a great name, but it's ours.

The plane in the logo is an inside joke. The public wouldn't understand it, and I'm not about to explain it. Sorry.

Quality

Is it good?

Yes

Quick start

You need an OIDC provider with a confidential client for skgate. Admin login is OIDC only. Trying it on one machine? docs/quickstart.md runs skgate with a bundled provider and no accounts.

# docker-compose.yml
services:
  skgate:
    container_name: skgate
    image: ghcr.io/helv-io/skgate:latest
    restart: always
    ports:
      - 8080:8080
    environment:
      - PUBLIC_URL=https://skgate.example.com
      - OIDC_ISSUER=https://auth.example.com
      - OIDC_CLIENT_ID=skgate
      - OIDC_CLIENT_SECRET=change-me
    volumes:
      - ./data:/data
    healthcheck:
      test: ["CMD", "/skgate", "healthcheck"]
      interval: 30s
      timeout: 5s
      retries: 3
  1. docker compose up -d
  2. Open https://skgate.example.com/admin and sign in through your OIDC provider.
  3. mcp upstreams > Add upstream: a remote MCP server, or a GitHub repository, npm package, or PyPI package that skgate runs for you (see Add an MCP server with Suggest configuration).
  4. Point your MCP client at https://skgate.example.com/mcp or the site root for every included upstream, or at https://skgate.example.com/mcp/<alias> for one upstream. Sign in when it asks.

Image tags:

  • latest: proxy + managed MCP servers. Bundles Node.js (npx), pnpm, Bun (bunx), Deno, Python (uv, uvx), Go, .NET and git.
  • slim: proxy only

To upgrade, back up ./data, then docker compose pull && docker compose up -d.

Proxy your SI providers

skgate is also an OpenAI-compatible API in front of your SI providers. Point your apps at one address; switch the provider behind them without changing the apps. User-defined model aliases give your apps a stable model name of your choosing.

Grok can use subscription sign-in with no API key. Other providers use that provider's API key (OpenAI, Anthropic, Gemini, Mistral, DeepSeek, Groq, OpenRouter, Ollama, LM Studio, and any OpenAI-compatible endpoint).

  1. status > Grok > Sign in: open the shown address, enter the code, approve. skgate keeps the sign-in fresh.
  2. keys > enter a name > Create key. Copy the sk-... key; it is shown once.
  3. Use it: base URL https://skgate.example.com/v1, API key sk-... (see Examples).
    • These base URLs all reach the same API: /v1, /api, /api/v1, and the bare host. Use whichever form your client expects.
  4. Other providers: status > Add provider, pick one and enter its API key. Type the provider's address in any form; skgate finds the one that works.

Examples

curl: models and a chat completion

export SKGATE=https://skgate.example.com KEY=sk-...
curl -s $SKGATE/v1/models -H "Authorization: Bearer $KEY"
curl -s $SKGATE/v1/chat/completions -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' \
  -d '{"model":"<id from /v1/models>","messages":[{"role":"user","content":"Say hi"}]}'

OpenAI client

export OPENAI_BASE_URL=https://skgate.example.com/v1 OPENAI_API_KEY=sk-...
from openai import OpenAI

client = OpenAI()  # reads the two variables above
r = client.chat.completions.create(model="<id from /v1/models>", messages=[{"role": "user", "content": "Say hi"}])
print(r.choices[0].message.content)

Model aliases: user-defined names for models

Aliases are user-defined: skgate ships with none. Pick any name and point it at a model. This example uses grok-latest, but the name is up to you.

Change the target in this one place and every app using that name switches at once, with no client config changes.

Grok > Aliases: New alias grok-latest, Target model grok-4.7, Add alias.

client sends   {"model": "grok-latest", ...}
skgate sends   {"model": "grok-4.7", ...}

Aliases are listed first in /v1/models.

Add an MCP server with Suggest configuration

Needs the latest image and an MCP helper model from any ready provider. The first time, Pick MCP helper model next to the button opens the model picker right on the page.

mcp upstreams > Add upstream > Type managed process:

  1. MCP source URL / package, one of:
    SourceRuns as
    @modelcontextprotocol/server-everythingnpm package, npx
    pypi:mcp-server-timePyPI package, uvx
    https://github.com/example-org/notes-mcpgit repo: clone, install, run (private: Suggest asks for a token)
  2. Suggest configuration. skgate fetches the README and manifests (package.json, pyproject.toml, server.json).

    The MCP helper model proposes the command, args, install step, and env names (secret or not, required or optional). The Manual configuration fields fill in, with a confidence and any warnings.

    Nothing is saved yet. Set the variables the server needs.

  3. Set an alias, fill in the variables you need (empty ones are not passed to the server), Save. The server is at https://skgate.example.com/mcp/<alias>.

If the button is greyed out, hover it. Use Pick MCP helper model beside it, or add a provider on status first.

MCP client: one upstream or all of them

URLServes
https://skgate.example.com/mcp/<alias>One upstream
https://skgate.example.com/mcpEvery upstream marked In /mcp
https://skgate.example.com/ (MCP clients)Same aggregate as /mcp when Accept/Content-Type look like MCP; browsers still go to /admin

Hosted connectors use OAuth (leave client ID and secret empty). Scripts and CLIs send a key:

{"mcpServers": {"skgate": {"type": "http", "url": "https://skgate.example.com/mcp/<alias>",
  "headers": {"Authorization": "Bearer sk-..."}}}}

On-demand MCP servers: no RAM while idle

On a RAM-constrained homelab, idle MCP servers should cost nothing.

Managed servers (npx, pnpm dlx, bunx, deno, uvx, git) are child processes of skgate.

By default (Lifecycle on-demand) one starts on its first request and stops after 10 minutes without requests; the next request starts it again.

before   3 MCP servers = 3 containers, always running
after    1 skgate container; 0 server processes while idle, 1 per server in use
stopped  --request-->  starting  -->  running  --10 min idle-->  stopped
  • Default is on-demand; Lifecycle always-on starts the server at boot instead.
  • The first request after a stop waits until the server is up.
  • A server with a request in flight is never stopped.
  • Idle time is idleTimeoutSeconds in import JSON (default 600; not in the form):
{"mcpServers": {"time": {"command": "uvx", "args": ["mcp-server-time"], "skgate": {"idleTimeoutSeconds": 120}}}}
  • The aggregated /mcp includes remote, OpenAPI and always-on upstreams marked In /mcp. On-demand servers are left out, so /mcp never starts them. Point a client at /mcp/<alias> to use one.
  • Remote and OpenAPI upstreams have no process; there is nothing to idle.
  • An admin Stop keeps a server stopped until Start or Restart.

Managed MCP server: npx (stdio)

mcp upstreams > import JSON > paste > Import. Needs the latest image.

{"mcpServers": {"everything": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-everything"]}}}

Served at https://skgate.example.com/mcp/everything. The same package runs with any bundled runner:

Runnercommandargs
npmnpx["-y", "<package>"]
pnpmpnpm["dlx", "<package>"]
Bunbunx["<package>"]
Denodeno["run", "-A", "npm:<package>"]
Pythonuvx["<package>"]

Managed MCP server: git repository

mcp upstreams > import JSON > paste > Import. skgate clones the repo, runs install, then the command.

{"mcpServers": {"notes": {"command": "node", "args": ["server.js"],
  "skgate": {"gitUrl": "https://github.com/example-org/notes-mcp.git", "gitRef": "main", "install": "npm ci"}}}}

Remote MCP server

mcp upstreams > Add upstream > Type remote server, or import:

{"mcpServers": {"docs": {"type": "http", "url": "https://mcp.example.com/mcp",
  "headers": {"Authorization": "Bearer ..."}}}}

Features

FeatureWhat it does
Grok sign-inDevice code or browser paste-back; tokens refresh
Other providersOpenAI, Anthropic, Gemini and more by API key (docs)
Model aliasesUser-defined model names; change the target once
API/v1, /api/v1, /api, no prefix; SSE (docs)
Virtual keysHashed; tokens in/out per key (docs)
MCPRemote, stdio and git servers behind OAuth 2.1 (docs)
Live tool updatesConnected clients hear when an upstream's tools change after an update, a reinstall or a tool switched on or off, and pick up the new tools without reconnecting
REST APIs as toolsGive an OpenAPI description, pick the operations (or let Suggest names and selection name them and pick a core set), and MCP clients get them as tools (docs)

Tested MCP clients

Clients tested by the author against a live skgate. Status is what happened, not a promise.

ClientStatusNotes
ChatGPTOK
ClaudeOK
GrokOK
HermesOK
Open WebUIOKUse OAuth 2.1; register and authorize the client.
GeminiWarningConnects, but never calls tools.
Home AssistantWarningNeeds a manual OAuth client ID and secret. Only one MCP upstream can be used.
ClineUntested
ContinueUntested
CursorOK
GooseUntested
LibreChatUntested
VS CodeUntested
WindsurfUntested
ZedUntested

Directory mode

skgate stdio starts a small read-only MCP server on stdin and stdout. It exists so MCP directories and scanners, which start a server and ask what it offers, can look at skgate. It is not for real use: to run skgate, use the Docker image above.

It needs no settings, opens no port, reads no database and uses no network. It has three tools: about, list_features and config_help. They describe skgate and nothing else. More in docs/mcp.md.

How skgate compares

A rough guide from each project's docs as of 2026-10-13. Check them for current features.

skgateLiteLLMMetaMCP
OpenAI-compatible LLM proxyYesYes, 100+ providersNo
MCP gatewayYesYesYes
Runs stdio MCP serversYes, started on demand, stopped when idleYes, off unless LITELLM_ENABLE_MCP_STDIO=trueYes
OAuth for MCP clientsOAuth 2.1 with dynamic client registrationYes, dynamic client registration at the gatewayYes, MCP spec 2025-06-18 OAuth
DatabaseSQLite, one containerPostgres for virtual keysPostgres (Docker Compose)

Comparison

How each provider signs in elsewhere, and how skgate connects to it:

ProviderSubscription sign-in elsewhereIn skgate
xAI Grok \*OpenCode; more planned [1]Subscription
Anthropic ClaudeNot offered to third parties [2]API key
DeepSeekNoneAPI key
Google Gemini \*CLI login not for reuse [3]API key
Groq \*NoneAPI key
Mistral \*NoneAPI key
OpenAISign in with ChatGPT [4]API key
OpenRouter \*NoneAPI key
LM StudioNoneNo key (local)
OllamaNoneNo key (local)

\* Free tier available.

As of 2026-10-05; check each provider's terms. GitHub Copilot is not a skgate provider.

Sources

  1. xAI, Use Grok in OpenCode
  2. Anthropic, Legal and compliance
  3. Google, Gemini CLI terms
  4. OpenAI, Sign in with ChatGPT

Configuration

Set these under environment: or in an env_file. Placeholders are in .env.example.

VariableDefaultPurpose
PUBLIC_URLhttp://localhost:8080Public origin, no trailing slash.
OIDC_ISSUERIssuer URL, equal to the provider's discovery issuer.
OIDC_CLIENT_ID, OIDC_CLIENT_SECRETConfidential client credentials.
OIDC_SCOPESopenid profile email groupsRequested scopes.
OIDC_REDIRECT_URLPUBLIC_URL/admin/oidc/callbackCallback registered at the provider.
OIDC_ALLOWED_EMAILS, OIDC_ALLOWED_GROUPSemptyComma lists limiting who is admin.
MCP_OAUTH_REQUIRE_CONSENTtrueApprove/Deny page after login at /authorize.
SECRETS_KEYrandom secrets.key fileEncrypts stored upstream secrets. 32-byte base64 (openssl rand -base64 32), or a passphrase, stretched with scrypt.
GITHUB_TOKENemptyOptional GitHub token for Suggest when it reads a repository. An upstream's own access token takes precedence. Raises GitHub's rate limit.
UPDATE_CHECKtrueThe version in the header turns yellow when a newer release exists; false turns the check off.
LISTEN_ADDR:8080Listen address.
DB_PATH/data/skgate.dbSQLite file.
LOG_LEVELinfoinfo or debug.
LOG_LINES1000Lines of output kept per managed process (its current and previous run, at most 512 KB).
TZUTCTime zone for the UI and logs, for example America/New_York.
PUID, PGID1000Run-as ids; never 0, 65534 or 61000-64999 (managed servers use those).
MANAGED_DIR/data/managedWork dirs and clones of managed upstreams.
MANAGED_MAX_PROCS0Concurrent managed processes; 0 is unlimited.

Grok needs no variables. Sign in on the status page. Model aliases are the Aliases control on that card.

Everything lives in /data (skgate.db, secrets.key): back up both.

Reverse proxy

Set PUBLIC_URL to the public https origin.

Do not put forward-auth on /v1, /api, /mcp, /sse, /messages, /authorize, /token, /register, or /.well-known.

Only Traefik is tested by the author. Open an issue with what you tried.

Traefik

services:
  skgate:
    container_name: skgate
    image: ghcr.io/helv-io/skgate:latest
    restart: always
    network_mode: web   # existing Docker network shared with Traefik; no ports needed
    environment:
      - PUBLIC_URL=https://skgate.example.com   # the public https origin
      - OIDC_ISSUER=https://auth.example.com
      - OIDC_CLIENT_ID=skgate
      - OIDC_CLIENT_SECRET=change-me
    volumes:
      - ./data:/data
    healthcheck:
      test: ["CMD", "/skgate", "healthcheck"]
      interval: 30s
      timeout: 5s
      retries: 3
    labels:
      # no forward-auth middleware on /v1, /api, /mcp, /sse, /messages, /authorize, /token, /register, /.well-known
      - traefik.enable=true
      - traefik.http.routers.skgate.rule=Host(`skgate.example.com`)
      - traefik.http.routers.skgate.entryPoints=websecure
      - traefik.http.services.skgate.loadbalancer.server.port=8080

nginx

server {
    listen 443 ssl;
    http2 on;
    server_name skgate.example.com;
    ssl_certificate     /etc/ssl/skgate/fullchain.pem;
    ssl_certificate_key /etc/ssl/skgate/privkey.pem;

    client_max_body_size 32m;                         # skgate accepts up to 32 MB

    location / {
        proxy_pass http://skgate:8080;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;                  # keep Host
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;                          # SSE streaming
        proxy_read_timeout 3600s;                     # long streams
        proxy_send_timeout 3600s;
    }
}

Caddy

skgate.example.com {
    # Host and X-Forwarded-* are set by default; no body limit, no response timeout
    reverse_proxy skgate:8080 {
        flush_interval -1                             # SSE streaming
    }
}

HAProxy

defaults
    mode http
    timeout connect 5s
    timeout client  1h                                # long streams
    timeout server  1h
    timeout tunnel  1h

frontend https
    bind :443 ssl crt /etc/haproxy/certs/skgate.pem alpn h2,http/1.1
    option forwardfor                                 # X-Forwarded-For; Host is kept, responses are not buffered
    http-request set-header X-Forwarded-Proto https
    default_backend skgate

backend skgate
    option httpchk GET /healthz
    server skgate skgate:8080 check

Apache

# a2enmod ssl proxy proxy_http headers
<VirtualHost *:443>
    ServerName skgate.example.com
    SSLEngine on
    SSLCertificateFile    /etc/ssl/skgate/fullchain.pem
    SSLCertificateKeyFile /etc/ssl/skgate/privkey.pem

    # keep Host
    ProxyPreserveHost On
    RequestHeader set X-Forwarded-Proto "https"
    # long streams
    ProxyTimeout 3600
    # flushpackets: no buffering (SSE)
    ProxyPass        / http://skgate:8080/ flushpackets=on
    ProxyPassReverse / http://skgate:8080/
</VirtualHost>

OIDC setup

Client settingValue
TypeConfidential, client_secret_basic or client_secret_post
FlowAuthorization code with PKCE S256
Redirect URIhttps://skgate.example.com/admin/oidc/callback
Scopesopenid profile email groups (if groups is rejected: OIDC_SCOPES=openid profile email)
ID tokenAsymmetric signature (RS, PS, ES, EdDSA); discovery issuer equal to OIDC_ISSUER

Without OIDC_ISSUER, OIDC_CLIENT_ID and OIDC_CLIENT_SECRET, skgate starts but the admin stays closed.

Every user your provider lets in is an admin. Restrict the provider, or set OIDC_ALLOWED_EMAILS / OIDC_ALLOWED_GROUPS.

Hints for Authelia, Authentik, Keycloak, Zitadel and Pocket ID: docs/oidc.md.

Security notes

  • Virtual keys are stored as SHA-256 hashes; upstream credentials are AES-256-GCM encrypted.
  • /authorize needs an admin session.
  • Managed upstreams run admin-supplied commands; use the slim image to disable them.
  • /register, /token, /authorize and admin forms cut off a request body that arrives too slowly. Uploads and streams (/v1, /mcp) have no such limit.
  • A key can be allowed in the URL (?key=) for clients that cannot send headers. It is off per key by default, because URLs leak into logs, history and referrers.

More, including the known trade-offs: operations.

Built with SI assistance

skgate is built with SI assistance: coding agents write much of the code, tests and docs.

Every change is reviewed by the author, and the security paths (OAuth, tokens, secrets) are human reviewed and tested. Reviews by frontier models are a supplement, not a substitute. Issues and pull requests that put it to the test are welcome.

Development

go build ./... && go vet ./... && go test ./...

docs/development.md. Contributors and agents: AGENT.md.

License

MIT, see LICENSE.

Star History

Questions fréquentes

Qu'est-ce que skgate ?

Self-hosted MCP gateway and LLM proxy: OAuth 2.1, model aliases, Grok subscription as an API.

Comment installer skgate ?

Il s'exécute sur votre machine. Dans Claude Code : claude mcp add skgate -e 'PUBLIC_URL=http://localhost:8080' -e 'OIDC_ISSUER=<OIDC_ISSUER>' -e 'OIDC_CLIENT_ID=<OIDC_CLIENT_ID>' -e 'OIDC_CLIENT_SECRET=<your oidc_client_secret>' -- docker run -i --rm ghcr.io/helv-io/skgate:0.28.0. Pour les autres clients, collez la configuration JSON de cette page dans leurs réglages MCP.

Faut-il une clé API pour skgate ?

Oui. Il attend PUBLIC_URL, OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, à renseigner dans sa configuration.

skgate est-il open source ?

Oui. Son code est sur GitHub sous licence MIT.

Qui maintient skgate ?

Il est publié par helv-io. AI.tn le référence sans le maintenir ni le recommander.

Issu du registre MCP officiel. Être listé n'est pas une recommandation : vérifiez le code et les permissions d'un serveur avant de lui donner accès à vos données.