beszel-mcp
A read-only MCP server for Beszel monitoring hub, providing tools to query server metrics, containers, services, disk health, and alerts.
README
beszel-mcp
A read-only MCP server for a Beszel monitoring hub. Twelve tools let an agent answer questions about your servers — load, containers, services, disk health, alerts — without knowing anything about Beszel's API.
An independent project, not affiliated with or endorsed by the Beszel maintainers.
Quick start
docker run -p 3000:3000 \
-e BESZEL_URL=https://beszel.example.com \
-e BESZEL_EMAIL=you@example.com \
-e BESZEL_PASSWORD=your-password \
-e MCP_AUTH_TOKEN=some-long-random-string \
ghcr.io/maxbth/beszel-mcp
The MCP endpoint is POST http://localhost:3000/mcp (Streamable HTTP).
stdio
For local clients that speak stdio:
docker run -i --rm \
-e BESZEL_URL=https://beszel.example.com \
-e BESZEL_EMAIL=you@example.com \
-e BESZEL_PASSWORD=your-password \
ghcr.io/maxbth/beszel-mcp --stdio
Wired into an MCP client — Claude Desktop, Claude Code, or anything else that launches a
subprocess. -i is required (the client talks to the container's stdin) and --rm keeps a
container from being left behind on every restart:
{
"mcpServers": {
"beszel": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "BESZEL_URL",
"-e", "BESZEL_EMAIL",
"-e", "BESZEL_PASSWORD",
"ghcr.io/maxbth/beszel-mcp", "--stdio"
],
"env": {
"BESZEL_URL": "https://beszel.example.com",
"BESZEL_EMAIL": "you@example.com",
"BESZEL_PASSWORD": "your-password"
}
}
}
}
MCP_AUTH_TOKEN has no role here: there is no port to reach, so the only thing that can talk
to the server is the process that spawned it.
Image tags
| Tag | Points at |
|---|---|
latest, 1.2.3, 1.2, 1 |
the most recent tagged release |
dev |
the current tip of main, rebuilt on every push |
dev moves. It is the right tag for trying unreleased changes and the wrong one
for anything you depend on, since the digest behind it changes without warning.
Pull a version tag for that.
Tools
| Tool | What it answers |
|---|---|
get_hub_info |
Can I reach the hub, and what is it running? |
list_systems |
What machines exist and how are they doing right now? |
get_system |
Full hardware and current state for one machine |
get_system_metrics |
How has this machine behaved over the last hour/day/week? |
list_containers |
What containers are running, and are any unhealthy? |
get_container_metrics |
Which container is eating the CPU? |
get_container_logs |
What is this container saying? |
list_services |
Which systemd units are failed? |
get_service_details |
Why is this unit unhappy? |
list_smart_devices |
Is any disk dying? |
list_alerts |
What alerts are configured, and which are firing? |
get_alert_history |
Is this problem recurring? |
Every tool is read-only. This server never writes to your hub.
Configuration
| Variable | Default | Notes |
|---|---|---|
BESZEL_URL |
— | required |
BESZEL_EMAIL / BESZEL_PASSWORD |
— | required unless BESZEL_TOKEN |
BESZEL_TOKEN |
— | skips login |
BESZEL_SUPERUSER |
false |
authenticate against _superusers |
BESZEL_TIMEOUT_MS |
15000 |
per-request timeout |
MCP_TRANSPORT |
http |
http or stdio; --stdio overrides |
MCP_HOST |
127.0.0.1 |
the image sets 0.0.0.0 |
MCP_PORT |
3000 |
|
MCP_AUTH_TOKEN |
unset | when set, a matching bearer token is required |
MCP_ALLOWED_ORIGINS |
empty | comma-separated hostnames (not full origins), added to the localhost defaults on a localhost bind |
LOG_LEVEL |
info |
Securing the endpoint with MCP_AUTH_TOKEN
Set MCP_AUTH_TOKEN and every request to /mcp must carry a matching
Authorization: Bearer header. Leave it unset and the endpoint is open to anything that can
reach the port. There is no default value and no built-in fallback — an unset token means no
authentication, not a weak one.
1. Generate one. Any long random string works; these produce 32 bytes of base64:
openssl rand -base64 32
2. Give it to the server. Prefer a file over an inline -e, which lands in your shell
history and in docker inspect output:
echo "MCP_AUTH_TOKEN=$(openssl rand -base64 32)" >> .env
docker run -p 3000:3000 --env-file .env ghcr.io/maxbth/beszel-mcp
3. Give it to the client. In an MCP client's server config:
{
"mcpServers": {
"beszel": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": {"Authorization": "Bearer PASTE_THE_TOKEN_HERE"}
}
}
}
To check it by hand — the first should return 401, the second should not:
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://mcp.example.com/mcp
curl -s -o /dev/null -w '%{http_code}\n' -X POST https://mcp.example.com/mcp -H "Authorization: Bearer $MCP_AUTH_TOKEN"
Comparison is constant-time, and the token is never written to a log line or an error message. To rotate it, change the value and restart: there is no session state, so nothing survives the restart and every client simply presents the new token on its next request.
MCP_AUTH_TOKEN protects /mcp only — see below for why /health is deliberately open.
Health checks
GET /health returns 200 {"status":"ok"} and is reachable without a bearer token, from
outside the container, through a reverse proxy. It is exempt from the bearer check and from
the Host/Origin validation that guards /mcp, so an uptime monitor or tunnel — Pangolin,
Traefik, a Kubernetes liveness probe, the image's own HEALTHCHECK — can reach it without
being configured as a browser origin or preserving a particular Host header.
That exemption is safe because the route has nothing to protect: it returns no monitoring
data, no configuration and no credential, and it changes nothing. Host/Origin validation
exists to stop a malicious web page using a victim's browser to read data from a server on
their network — and there is no data here to read. Anyone who reaches /health learns only
that something is listening, which the TCP handshake already told them.
/mcp keeps every protection. A valid bearer token still does not buy past Origin
validation, and vice versa.
For Pangolin specifically, point the health check at /health on the same port you expose:
Health check path: /health
Expected status: 200
A note on exposure
The image binds 0.0.0.0 so the container is reachable from outside itself. A wildcard
bind gets no automatic Host-header validation, so set MCP_AUTH_TOKEN — otherwise
anything that can reach the port can read every monitored machine, container and disk, and
fetch container logs. The server warns on stderr when you do not. Origin validation is
always on: on a wildcard bind with MCP_ALLOWED_ORIGINS empty, browser requests are rejected
outright and only non-browser clients (which send no Origin) get through.
On a localhost bind (127.0.0.1, localhost, ::1 — the default outside the image) the
localhost hostnames localhost, 127.0.0.1 and [::1] are allowed on top of whatever
MCP_ALLOWED_ORIGINS lists, so browser-hosted clients such as the MCP Inspector work with no
configuration. Non-localhost origins are still rejected.
A note on visibility
Beszel scopes systems per user. If tools report no systems, the configured
account is probably not a member of them — either add it to each system, or run
the hub with SHARE_ALL_SYSTEMS=true.
get_container_logs additionally requires the hub to have container details
enabled (it is, unless the hub sets CONTAINER_DETAILS=false).
Hub compatibility
Built and tested against Beszel 0.19. An older hub still works: 0.19 moved hostname, kernel,
CPU model, core/thread counts, OS and podman out of systems.info into a system_details
collection, so list_systems joins it and falls back to the legacy blob when the collection
answers 404. Decoding likewise accepts both the current byte fields and the deprecated MB/s pairs.
Nothing needs configuring either way.
Development
Requires Bun 1.3+.
bun install
bun test
bun run typecheck # Bun does not typecheck; this is what does
bun run lint:check
bun run dev
Issues and pull requests are welcome. Run those three commands before opening a PR — CI runs
exactly the same gate, plus a multi-arch Docker build, and fails on any lint warning. Tests are
colocated with what they test (src/config.ts / src/config.test.ts) and every tool is
read-only by contract, so a change that writes to the hub will not be accepted.
License
MIT © Maxime Bertheau
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。