Skip to main content
Glama

IT Operations Hub

เกตเวย์ MCP รวมศูนย์สำหรับงาน IT Operations และบัญชี Express — เอเจนต์ AI คุยกับ Zabbix 7, MeshCentral, และ Express Accounting (express.co.th) ผ่าน SSE และ Streamable HTTP หลัง Cloudflare Tunnel และ Nginx RBAC

A production Docker Compose stack:

Cloudflare Tunnel → Nginx (Bearer RBAC + OAuth DCR, SSE/WebSocket) → mcp-hub-it | mcp-hub-admin | mcp-hub-accounting → sub-mcp-zabbix | sub-mcp-meshcentral | sub-mcp-express → Zabbix / MeshCentral / Express books

All services share a single bridge network, infra-net. MCP hubs and databases are not published on the host. Only LAN/VPN ports for the gateway, Zabbix, and MeshCentral agents are bound.

Architecture

AI agents (Claude Desktop, Cursor, ChatGPT, Grok)
        |  HTTPS  (+ OAuth DCR token page, or Authorization: Bearer)
        v
Cloudflare Zero Trust  ──tunnel──► cloudflared
        |
        v
Nginx :80 (internal) / MCP_LAN_PORT on the host
  OAuth  /authorize /token /register /.well-known/*  → mcp-oauth (token-paste page)
  Bearer IT_TOKEN          → role it          → /mcp/it/*          (it + admin)
  Bearer ADMIN_TOKEN       → role admin       → /mcp/admin/*       (admin only)
  Bearer ACCOUNTING_TOKEN  → role accounting  → /mcp/accounting/*  (accounting only)
        |
        +--> mcp-hub-it:3000           Zabbix + MeshCentral inventory
        +--> mcp-hub-admin:3000        same + meshcentral_run_shell
        +--> mcp-hub-accounting:3000   Express Accounting read-only
                    |
                    +--> sub-mcp-zabbix / sub-mcp-meshcentral
                    +--> sub-mcp-express   fixture | http adapter | DBF
                              |
                              +--> zabbix-web / zabbix-server / zabbix-db
                              +--> meshcentral
                              +--> Express books (sample, sidecar REST, or .DBF)

Tools

Tool

IT hub

Admin hub

Accounting hub

zabbix_get_active_problems(severity_min?)

yes

yes

no

zabbix_get_device_status(group_name?)

yes

yes

no

zabbix_get_metrics(host_name, item_keys)

yes

yes

no

meshcentral_get_inventory()

yes

yes

no

meshcentral_run_shell(node_id, command)

no

yes

no

express_get_status()

no

no

yes

express_list_customers(query?, limit?)

no

no

yes

express_list_vendors(query?, limit?)

no

no

yes

express_list_items(query?, limit?)

no

no

yes

express_list_ar_invoices(status?, query?, limit?)

no

no

yes

express_list_gl_accounts(query?, limit?)

no

no

yes

Nginx rejects an IT token on /mcp/admin/ and /mcp/accounting/ with HTTP 403. An accounting token cannot call IT or admin paths. The IT hub process does not register the shell tool. Express tools are read-only; see docs/EXPRESS.md.

Related MCP server: Enterprise MCP Server

Requirements

  • Docker Engine 24+ with Compose v2

  • A host that can reach ESXi / switches / NVR / Windows agents on the LAN (Zabbix server port 10051, MeshCentral 443/4433)

  • Optional: Cloudflare account for the public MCP hostname

Quick start

git clone <this-repo>
cd itops-mcp-hub
cp .env.example .env

Edit .env:

  1. Generate unique RBAC tokens (URL-safe, no spaces or quotes):

    openssl rand -hex 32   # IT_TOKEN
    openssl rand -hex 32   # ADMIN_TOKEN
    openssl rand -hex 32   # ACCOUNTING_TOKEN
  2. Set ZABBIX_DB_PASSWORD and MeshCentral MESHCENTRAL_PASSWORD / MESHCENTRAL_API_KEY.

  3. Leave CLOUDFLARE_TUNNEL_TOKEN empty until Zero Trust is configured.

Bring the stack up (LAN/VPN mode, no tunnel):

docker compose up -d

First boot of Zabbix Postgres schema can take a couple of minutes. Watch:

docker compose ps
docker compose logs -f zabbix-server zabbix-web nginx mcp-hub-it

Gateway status page (LAN): http://<compose-host>:9080/

Service

Default LAN port

Notes

MCP + status page

9080

Nginx. MCP paths require a Bearer token

Zabbix UI

9443

First login Admin / zabbix — change immediately

Zabbix server

10051

Agents, ESXi, SNMP traps path into the server

MeshCentral HTTPS

9444

Create the first admin when ALLOW_NEW_ACCOUNTS=true

MeshCentral agent

4433

Agent/MPS

Cloudflare Tunnel (public MCP URL)

docker compose --profile tunnel up -d

cloudflared is distroless and expects a real CLOUDFLARE_TUNNEL_TOKEN. Do not start this profile until the token is set.

First-run: Zabbix API token

The MCP Zabbix server authenticates with a Zabbix API token (Bearer), not the UI password.

  1. Open http://<host>:9443 and log in as Admin.

  2. Change the Admin password; set ZABBIX_WEB_PASSWORD in .env to match.

  3. User menu → API tokens → Create token mcp-gateway.

  4. Put the secret into ZABBIX_API_TOKEN and recreate the Zabbix MCP container:

    docker compose up -d --force-recreate sub-mcp-zabbix

Alternatively, after the UI is up:

node scripts/create-zabbix-api-token.mjs
# paste the printed value into ZABBIX_API_TOKEN

Create host groups that match how you filter devices (ESXi, Switches, CCTV, Windows servers, …). zabbix_get_device_status searches group names.

First-run: MeshCentral

  1. Open https://<host>:9444 (self-signed certificate on a fresh volume).

  2. Create the account that matches MESHCENTRAL_USER / MESHCENTRAL_PASSWORD.

  3. Set MESHCENTRAL_ALLOW_NEW_ACCOUNTS=false and recreate meshcentral after the first admin exists.

  4. Install MeshAgents on Windows / Linux / ESXi management jump hosts as needed.

  5. meshcentral_get_inventory reads the cached node list over /control.ashx (action: "nodes"). CPU/RAM are returned when MeshCentral already stored them on the node object; they are not probed live.

  6. meshcentral_run_shell uses runcommands (PowerShell on Windows, POSIX shell otherwise) and is exposed only on the admin hub.

MESHCENTRAL_API_KEY is used as the control-channel password when MESHCENTRAL_PASSWORD is empty. Prefer a dedicated MeshCentral service user. Authentication is the MeshCentral x-meshauth header (base64(user),base64(pass)).

Internal TLS to https://meshcentral:443 uses a self-signed cert. Keep MESHCENTRAL_TLS_INSECURE=true unless you mounted a real certificate into meshcentral-data.

Cloudflare Zero Trust

This is the public path for AI clients. MeshCentral agents and Zabbix pollers stay on the LAN; only the MCP gateway is published.

1. Tunnel

  1. Zero Trust → NetworksTunnels → Create a locally managed tunnel.

  2. Copy the token into CLOUDFLARE_TUNNEL_TOKEN.

  3. Public hostname, for example mcp.example.com:

    • Type: HTTP

    • URL: http://nginx:80

    • The connector runs inside infra-net, so it must use the Compose service name nginx, not a host port.

  4. docker compose --profile tunnel up -d

Set PUBLIC_MCP_ORIGIN=https://mcp.example.com (the same public hostname) so ChatGPT/Grok OAuth discovery advertises the real URL. Then recreate mcp-oauth and nginx.

Optional origin settings in the hostname:

  • HTTP Host Header: mcp.example.com

  • Disable chunked encoding: off (SSE needs chunked transfer)

  • No extra origin TLS (nginx listens HTTP on 80)

2. Access application (service tokens)

  1. Zero Trust → AccessApplications → Add Self-hosted.

  2. Application domain: mcp.example.com (include /mcp* if you split policies).

  3. Identity: Service Auth (and optionally your IdP for humans).

  4. Create a Service Token (Client ID + Client Secret).

  5. Policy: Service Token is valid, then allow.

Cloudflare consumes the service token headers at the edge. Configure the application to forward these headers to origin (Access → Application → Settings / Overview, depending on the dashboard version):

Header

Purpose

CF-Access-Client-Id

Service token id (validated by Cloudflare, then forwarded)

CF-Access-Client-Secret

Service token secret

Authorization

Must pass through unmodified — Nginx maps this to it / admin

CF-Access-Jwt-Assertion

Set by Cloudflare after a successful Access login

If Access strips Authorization, Nginx will 401 every MCP call. Add Authorization to the allowed/forwarded header list, or put the MCP Bearer token in a second Access-approved header and change Nginx — this repo expects Authorization: Bearer <IT_TOKEN|ADMIN_TOKEN|ACCOUNTING_TOKEN>.

Create two Access service tokens if you want to rotate IT and Admin Cloudflare identities independently of the MCP RBAC tokens.

3. SSE through Cloudflare

Nginx already sets proxy_buffering off, gzip off, X-Accel-Buffering: no, and 1-hour proxy timeouts. In the tunnel hostname, do not enable extra buffering or “HTTP/2 to origin” if SSE stalls; HTTP/1.1 to nginx is the safe origin protocol.

Team trial: ChatGPT / Grok (read-only)

ขณะรอ ai-tools-mcp (ชั้นอนุมัติคำสั่ง privileged) ทีมทดลองบน ChatGPT / Grok ได้ เฉพาะเส้น IT

  • URL: https://<tunnel-host>/mcp/it/mcp — เลือก OAuth (อย่าเลือก Token ใน ChatGPT ถ้าต้องการหน้าเว็บ)

  • ครั้งแรกเบราว์เซอร์เปิด https://<tunnel-host>/authorize ให้วาง IT_TOKEN เหมือน ai-collaboration-mcp

  • ตั้ง PUBLIC_MCP_ORIGIN=https://<tunnel-host> ใน .env แล้ว recreate mcp-oauth + nginx

  • ห้าม ADMIN_TOKEN และห้าม /mcp/admin/ สำหรับทีมทดลอง

  • ทีมบัญชีใช้เส้นแยก https://<tunnel-host>/mcp/accounting/mcp + ACCOUNTING_TOKEN + scope mcp:accounting — ดู docs/EXPRESS.md

  • ขั้นตอนละเอียดอยู่ที่ docs/TEAM-CONNECT.md

meshcentral_run_shell ยังปิดสำหรับทีมทดลองจนกว่าจะมี payload-hash approval + human queue + audit

MCP client configuration

Replace host, tokens, and Cloudflare service-token values. Claude Desktop still uses the legacy SSE transport (/sse). Newer clients can use Streamable HTTP (/mcp).

Claude Desktop / SSE (IT role)

claude_desktop_config.json:

{
  "mcpServers": {
    "itops-it": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://mcp.example.com/mcp/it/sse",
        "--header",
        "Authorization: Bearer ${IT_TOKEN}",
        "--header",
        "CF-Access-Client-Id: ${CF_ACCESS_CLIENT_ID}",
        "--header",
        "CF-Access-Client-Secret: ${CF_ACCESS_CLIENT_SECRET}"
      ]
    }
  }
}

If your client supports URL + headers natively:

{
  "mcpServers": {
    "itops-it": {
      "url": "https://mcp.example.com/mcp/it/sse",
      "transport": "sse",
      "headers": {
        "Authorization": "Bearer IT_TOKEN_HERE",
        "CF-Access-Client-Id": "CLIENT_ID.access",
        "CF-Access-Client-Secret": "CLIENT_SECRET"
      }
    },
    "itops-admin": {
      "url": "https://mcp.example.com/mcp/admin/sse",
      "transport": "sse",
      "headers": {
        "Authorization": "Bearer ADMIN_TOKEN_HERE",
        "CF-Access-Client-Id": "CLIENT_ID.access",
        "CF-Access-Client-Secret": "CLIENT_SECRET"
      }
    }
  }
}

Streamable HTTP

{
  "mcpServers": {
    "itops-accounting": {
      "url": "https://mcp.example.com/mcp/accounting/mcp",
      "headers": {
        "Authorization": "Bearer ACCOUNTING_TOKEN_HERE"
      }
    }
  }
}

LAN test without Cloudflare

curl -sS http://127.0.0.1:9080/healthz
curl -sS -D- -o /dev/null \
  -H "Authorization: Bearer $IT_TOKEN" \
  http://127.0.0.1:9080/mcp/it/
curl -sS -D- -o /dev/null \
  -H "Authorization: Bearer $IT_TOKEN" \
  http://127.0.0.1:9080/mcp/admin/
# expect 403 on the admin path
curl -sS -D- -o /dev/null \
  -H "Authorization: Bearer $IT_TOKEN" \
  http://127.0.0.1:9080/mcp/accounting/
# expect 403 — books are not an IT role
curl -sS -D- -o /dev/null \
  -H "Authorization: Bearer $ACCOUNTING_TOKEN" \
  http://127.0.0.1:9080/mcp/accounting/

Repository layout

docker-compose.yml
nginx/                    # RBAC reverse proxy + status page
packages/
  Dockerfile              # shared Node 22 image, ARG SERVICE=
  mcp-common/             # Streamable HTTP + SSE helper
  mcp-zabbix/             # Zabbix JSON-RPC tools
  mcp-meshcentral/        # MeshCentral control.ashx tools
  mcp-express/            # Express Accounting (fixture / HTTP / DBF)
  mcp-hub/                # aggregator; HUB_ROLE=it|admin|accounting
  mcp-oauth/              # OAuth 2.1 + DCR; /authorize asks for site token
scripts/create-zabbix-api-token.mjs
docs/EXPRESS.md           # Express Accounting backends and RBAC

Operations notes

  • Rotate IT_TOKEN / ADMIN_TOKEN / ACCOUNTING_TOKEN by changing .env and docker compose up -d --force-recreate nginx mcp-oauth.

  • Do not publish mcp-hub-*, sub-mcp-*, or zabbix-db to the internet.

  • meshcentral_run_shell runs as SYSTEM/root (runAsUser: 0) on the agent. Treat ADMIN_TOKEN like production break-glass.

  • After changing MeshCentral hostname or published HTTPS port, update config.json in the meshcentral-data volume (aliasPort / cert) so agent download URLs stay correct.

  • Logs are JSON lines from the Node services and json-file rotated at 10 MB × 3.

Nested Docker / CI hosts

If containers start but Nginx cannot reach mcp-hub-* (SSE hangs after a 200 auth, ping between containers fails), the kernel is filtering bridged traffic:

sudo sysctl -w net.bridge.bridge-nf-call-iptables=0
sudo sysctl -w net.bridge.bridge-nf-call-ip6tables=0

This is a host setting, not a Compose service setting. Normal bare-metal or VM Docker installs already have working inter-container connectivity.

License

Internal operations tooling. Review Zabbix and MeshCentral licenses for the upstream images.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides a secure MCP gateway for AI agents to access APIs without exposing raw credentials, with scoped access, audit logging, and OAuth support.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Secure MCP server that exposes WAFpass REST endpoints as tools for AI assistants, with role-based filtering and token validation.
    Apache 2.0