df9b01272feba8abd9be712f21dc7d5c9a81e57c
Add /admin/uploads HTML page and JSON API backed by the upload_files DB index. The page lists file_id, name, size (KB), uploaded_at, and sha256 with client-side search by name, column sort, and per-row delete. DELETE /admin/uploads/:id removes the DB index row and the on-disk data file plus .meta.json sidecar. Admin deletes are written to the audit log. Wire the new dependencies through admin.Mount and main.go, and run the backfill step on startup so pre-existing sidecars are indexed. Tests cover auth, HTML rendering, JSON search, delete removing all artifacts, and audit entry creation. 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%