skgate

io.github.helv-iov0.10.0更新於 Oct 4, 2026

MCP gateway: OAuth-protected endpoints for remote and hosted MCP servers, plus a Grok API.

已驗證STDIO僅桌面AI & MLCloud & Infrastructure

概覽

AI 產生的概覽

自架的 OAuth 保護閘道,可代理遠端與本機託管的 MCP 伺服器,並提供以 Grok 為後端的 OpenAI 相容 API。

功能
skgate 以單一容器執行,在 OAuth 2.1 之後託管 MCP 伺服器,可為每個上游提供獨立路徑,也可把多個上游彙整到單一端點。它能管理來自 npm、PyPI 或 git 儲存庫的 stdio 伺服器,依需求啟動並在閒置時停止,還能將 Grok 訂閱代理成帶虛擬金鑰與模型別名的 OpenAI 相容 API。管理員登入與設定透過網頁介面完成。
適用情境
當你想為多個 MCP 伺服器提供單一認證端點,或想透過 OpenAI 相容 API 使用 Grok 訂閱時,可以採用它。它適合家庭實驗室與小型部署,尤其是希望閒置的 MCP 程序不佔用記憶體的情境。
執行需求
需要本機容器執行環境(例如 Docker),以及一個帶機密用戶端的 OIDC 提供者。必要的環境變數:PUBLIC_URL、OIDC_ISSUER、OIDC_CLIENT_ID 與 OIDC_CLIENT_SECRET。完整映像還需要 Node.js、Python、uv、.NET、Go 與 git 來執行託管伺服器;slim 映像僅做代理。建議但不強制需要 Grok 訂閱。
安裝前請注意
OIDC_CLIENT_SECRET 與上游憑證會被儲存,並以 SECRETS_KEY 加密,因此請備份資料目錄。除非以 OIDC_ALLOWED_EMAILS 或 OIDC_ALLOWED_GROUPS 限制,否則 OIDC 提供者放行的每位使用者都會成為管理員。託管上游會執行管理員提供的指令,可用 slim 映像停用。虛擬金鑰可被允許放在 URL 中,這會使其洩漏到日誌與瀏覽紀錄。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 skgate,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

[skgate logo]

skgate

Use your Grok subscription as an OpenAI-compatible API, and serve your MCP servers from one OAuth-protected gateway.

Yes, all MCP servers: everyone's welcome. skgate can run them for you too, so no more stacks. It just works.

[skgate demo]

Name Origin

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

"sk" is what most AI API keys start with, or so I perceive it, and "gate" is for gateway. Bit rubbish as names go, 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.

Quick start

Before you start: an OIDC provider with a confidential client for skgate (admin login is OIDC only). Just trying it on one machine? docs/quickstart.md runs skgate with a bundled provider and no accounts.

A Grok subscription is recommended but not required.

yaml
# docker-compose.ymlservices:  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. status > Grok > Sign in: open the shown address, enter the code, approve.
  4. keys > enter a name > Create key. Copy the sk-... key; it is shown once.
  5. Use it: base URL https://skgate.example.com/v1, API key sk-... (see Examples).
    • The base URL is forgiving: /v1, /api, /api/v1 and the bare host all reach the same API, so use whichever form your client expects.

Image tags:

  • latest: proxy + managed MCP servers (Node.js, Python, uv, .NET, Go, git)
  • slim: proxy only

Upgrade: back up ./data, then docker compose pull && docker compose up -d.

Examples

curl: models and a chat completion
sh
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
sh
export OPENAI_BASE_URL=https://skgate.example.com/v1 OPENAI_API_KEY=sk-...
python
from openai import OpenAI
client = OpenAI()  # reads the two variables abover = client.chat.completions.create(model="grok-latest", messages=[{"role": "user", "content": "Say hi"}])print(r.choices[0].message.content)
Model alias: one name that always points at the newest model

Map grok-latest to the latest available model. Change the target in this one place and every app using grok-latest is upgraded at once, with no client config changes.

Grok > Details > Model aliases: alias grok-latest, target the newest model in the list (for example grok-4.7), Save.

text
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 Grok signed in. 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 (package or repository):

  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: Access token)
  2. Suggest configuration. skgate fetches the README and manifests (package.json, pyproject.toml, server.json), the MCP helper model proposes command, args, install step and env names (marked secret or not, required or optional), and the Manual configuration fields are filled in with a confidence and any warnings. Nothing is saved yet. Point it at the repo, fill in the variables it needs, and it just works.

  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: sign in to Grok on status, or use Pick MCP helper model beside it.

MCP client: one upstream or all of them
URLServes
https://skgate.example.com/mcp/<alias>One upstream; tool names unchanged
https://skgate.example.com/mcpEvery upstream marked In /mcp; tools prefixed <alias>-

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

json
{"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, 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.

text
before   3 MCP servers = 3 containers, always runningafter    1 skgate container; 0 server processes while idle, 1 per server in use
text
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 answers initialize (up to 60 s).
  • A server with a request in flight is never stopped. Stopping is SIGTERM, then SIGKILL after 5 s.
  • Idle time is idleTimeoutSeconds in import JSON (default 600; not in the form):
json
{"mcpServers": {"time": {"command": "uvx", "args": ["mcp-server-time"], "skgate": {"idleTimeoutSeconds": 120}}}}
  • The aggregated /mcp includes remote 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 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.

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

Served at https://skgate.example.com/mcp/everything. For Python servers use "command": "uvx", "args": ["<package>"].

Managed MCP server: git repository

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

json
{"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 (URL), or import:

json
{"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
Model aliasesgrok-latest maps to the newest model; 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)

Comparison

ProviderOwn subscription sign-in in third-party toolsIn skgate
xAI Grok✅ announced for OpenCode, more planned [1]✅ (independent, not an xAI product)
OpenAI✅ "Sign in with ChatGPT" since 2026-09-29; hosted apps need approval [2]❌
Anthropic Claude❌ not offered to third parties [3]❌ use the API
Google Gemini❌ CLI login not for reuse [4]❌ use an API key
GitHub Copilot⚠️ OpenCode partnership only [5]❌

As of 2026-10-02; check each provider's terms.

Sources
  1. xAI, Use Grok in OpenCode
  2. OpenAI, Sign in with ChatGPT
  3. Anthropic, Legal and compliance
  4. Google, Gemini CLI terms
  5. GitHub, Copilot now supports OpenCode

Configuration

Set under environment: (or env_file); placeholders 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 or a passphrase.
GITHUB_TOKENemptyOptional GitHub token for Suggest when it reads a repository. An upstream's own access token takes precedence. Raises GitHub's rate limit.
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.
MANAGED_DIR/data/managedWork dirs and clones of managed upstreams.
MANAGED_MAX_PROCS0Concurrent managed processes; 0 is unlimited.

Grok needs no variables; its base URL and aliases are in its Details dialog.

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

Reverse proxy

Set PUBLIC_URL to the public https origin. No forward-auth on /v1, /mcp, /authorize, /token, /register, /.well-known. Only Traefik is tested by the author; open an issue with feedback.

Traefik
yaml
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, /mcp, /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
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
caddyfile
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
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
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_* the admin answers 503. 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.
  • 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: operations.

Built with AI assistance

skgate is built with AI assistance: coding agents write much of the code, tests and docs. The author reviews the changes and runs skgate.

Development

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

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

License

MIT, see LICENSE.

來源:README.md,提交 9fcab95

工具

0
工具後設資料尚未被收錄。

版本歷史

1
  1. v0.10.0最新Oct 4, 2026