2571ce69c045e544bf4aae0b98e80fb09771da34
上一轮改用 git clone 手写 checkout 是因为 actions/checkout@v4 是 Node 实现, alpine golang-1.25 镜像没装 Node. 但后来发现镜像连 bash 也没有 (alpine 极简镜像, 只剩 sh), run: | 块也会崩. gitea/action-checkout 是 Gitea 官方 Go 实现的 checkout action, 不依赖 Node 也不依赖 bash, 跟 Gitea runner 原生集成. 替换两个 job 的 checkout step. 注意: gitea/action-checkout 只解决 checkout. 后续 run: | 块 (Set GOPROXY / Build / Test / Upload release asset / Notify) 仍 然用 bash, alpine 没 bash 会继续崩. 这是已知遗留, 跑起来再看 是否真崩. 完整修法是给所有 run: | 块加 shell: sh, 但本 commit 先按用户要求只换 checkout, 观察跑结果再决定. Co-Authored-By: Claude <noreply@anthropic.com>
…
…
…
…
spark-mcp-go
MCP (Model Context Protocol) server for Apache Spark on YARN. Lets an LLM agent
discover Spark/YARN endpoints, submit jobs, fetch logs, and analyze them via
11 typed Tools. Streamable HTTP transport, SQLite-backed configuration, and
log/slog structured logging with per-Tool call files.
What it does
- Exposes 11 MCP Tools over Streamable HTTP at
/mcp - Admin API at
/admin/*for cluster and audit configuration - HTTP Basic and YARN SimpleAuth for Spark/YARN endpoints
- SSRF protection with cluster URL allowlist plus DNS rebinding guard
- Per-Tool independent audit log (file mode
0600) spark-submitvia localexec.Command(no shell, no injection)
Quick start (3 steps)
-
Copy and edit the environment file:
cp .env.example .env # Edit ADMIN_TOKENS and AGENT_TOKEN -
Build and start the server:
go build ./... ./spark-mcp-go # Or use the helper: # ./scripts/dev.sh -
Initialize an MCP session, then call
list_clustersto discover endpoints.
11 Tools
| Tool | Type | Purpose |
|---|---|---|
list_clusters |
discovery | Return all active clusters (LLM entry point) |
spark_submit |
exec | Local spark-submit process, extracts app_id |
list_applications |
RM | GET /ws/v1/cluster/apps[?state=&user=] |
get_application_status |
RM | GET /ws/v1/cluster/apps/{id} |
get_application_logs |
RM | Fallback chain: amContainerLogs -> aggregated-logs -> logs |
kill_application |
RM | PUT state KILLED |
fetch_spark_metrics |
SHS | Executor metrics + summary mode triggers analyzer |
fetch_cluster_env |
RM | Aggregate /cluster/info and /cluster/metrics |
analyze_spark_log |
analyzer | RM logs + heuristic rules + LLM-ready prompt |
fetch_url |
HTTP | Generic primitive, uses cluster allowlist and auth |
upload_file |
FS | Write to ./data/uploads/, reference from spark_submit |
Environment variables
| Variable | Default | Required | Description |
|---|---|---|---|
LISTEN_ADDR |
:8080 |
no | HTTP listen address |
DATA_DIR |
./data |
no | SQLite, uploads, and log root |
ADMIN_TOKENS |
— | yes | Comma-separated tokens for /admin/* |
AGENT_TOKEN |
— | yes | Token for /mcp requests |
HTTP_CLIENT_TIMEOUT |
30s |
no | Timeout for fetch_url/RM Tool HTTP calls |
MAX_RESPONSE_BYTES |
1048576 (1 MiB) |
no | HTTP response truncation limit |
SPARK_SUBMIT_TIMEOUT |
60s |
no | spark-submit process timeout |
LOG_DIR |
./data/logs |
no | Log root directory |
LOG_LEVEL |
info |
no | debug, info, warn, or error |
LOG_FORMAT |
text |
no | text for terminal, json for log files |
ANALYZER_DATA_SKEW_RATIO |
3.0 |
no | Data-skew rule threshold (max/min ratio) |
ANALYZER_GC_PRESSURE_RATIO |
0.1 |
no | GC-pressure rule threshold (GC/CPU ratio) |
ANALYZER_BOTTLENECK_SHUFFLE_GB |
50 |
no | Bottleneck rule threshold (shuffle GB) |
Admin API
All endpoints require Authorization: Bearer <admin_token>.
| Method | Path | Description |
|---|---|---|
GET |
/admin/clusters |
List all clusters |
POST |
/admin/clusters |
Create a cluster |
GET |
/admin/clusters/:id |
Get one cluster |
PUT |
/admin/clusters/:id |
Update a cluster |
DELETE |
/admin/clusters/:id |
Delete a cluster |
GET |
/admin/audit?limit=100 |
Query audit log |
Create a cluster:
curl -sS -X POST \\
-H "Authorization: Bearer ${ADMIN_TOKEN}" \\
-H "Content-Type: application/json" \\
-d '{
"id": "prod",
"name": "prod",
"rm_url": "http://rm.example.com:8088",
"shs_url": "http://shs.example.com:18080",
"spark_submit_execute_bin": "/opt/spark/bin/spark-submit",
"is_active": true,
"auth_type": "simple",
"auth_username": "yarn",
"rate_limit_per_min": 10,
"url_allowlist": ["rm.example.com:8088", "shs.example.com:18080"]
}' \\
"http://127.0.0.1:${LISTEN_ADDR:-:8080}/admin/clusters"
Note: auth_password is not accepted via JSON (json:"-"). Set it through a
dedicated password endpoint or seed the database directly.
Security model
Three lines of defense:
- Token auth -
ADMIN_TOKENSprotects/admin/*;AGENT_TOKENprotects/mcp. - SSRF guard - Every outbound URL must match the cluster's
url_allowlist, with private-IP CIDR blacklist (incl.169.254.0.0/16AWS/GCP metadata). - Path allowlist -
upload_filewrites only to./data/uploads/, rejects absolute paths,.., and non-[a-zA-Z0-9._-]filenames.
Additional guarantees:
spark_submitruns the cluster's binary viaexec.Command(name, args...)— slice form, neversh -c. No shell metacharacter interpretation.auth_passwordis taggedjson:"-"; never serialized in responses, never accepted from JSON request bodies.- Cross-host redirects (RM → NM 307) preserve the
Authorizationheader throughDoWithRedirectonly when the destination host is inurl_allowlist. - Per-Tool log files are created with mode
0600and live under./data/logs/tools/.
Development
go build ./...
go test ./...
go vet ./...
More docs
- ARCHITECTURE.md - Design and package layout
- docs/runbook.md - Deployment, upgrade, and troubleshooting
Releases
2
Release v1.0.0
Latest
Languages
Go
86.5%
HTML
12.6%
Shell
0.9%