spark-mcp 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. spark_mcp-0.4.1/LICENSE +21 -0
  2. spark_mcp-0.4.1/MANIFEST.in +3 -0
  3. spark_mcp-0.4.1/PKG-INFO +329 -0
  4. spark_mcp-0.4.1/README.md +297 -0
  5. spark_mcp-0.4.1/pyproject.toml +77 -0
  6. spark_mcp-0.4.1/requirements.txt +2 -0
  7. spark_mcp-0.4.1/scripts/security_sanitizer.py +318 -0
  8. spark_mcp-0.4.1/scripts/verify_api_integration.py +279 -0
  9. spark_mcp-0.4.1/setup.cfg +4 -0
  10. spark_mcp-0.4.1/spark_mcp/__init__.py +3 -0
  11. spark_mcp-0.4.1/spark_mcp/__main__.py +4 -0
  12. spark_mcp-0.4.1/spark_mcp/api/__init__.py +1 -0
  13. spark_mcp-0.4.1/spark_mcp/api/api_client_base.py +51 -0
  14. spark_mcp-0.4.1/spark_mcp/api/api_client_rest.py +371 -0
  15. spark_mcp-0.4.1/spark_mcp/api/api_client_spark.py +676 -0
  16. spark_mcp-0.4.1/spark_mcp/api_client.py +13 -0
  17. spark_mcp-0.4.1/spark_mcp/auth.py +194 -0
  18. spark_mcp-0.4.1/spark_mcp/connectors/__init__.py +1 -0
  19. spark_mcp-0.4.1/spark_mcp/kg_ingest.py +286 -0
  20. spark_mcp-0.4.1/spark_mcp/mcp_config.json +1 -0
  21. spark_mcp-0.4.1/spark_mcp/mcp_server.py +67 -0
  22. spark_mcp-0.4.1/spark_mcp/models.py +84 -0
  23. spark_mcp-0.4.1/spark_mcp/ontology/__init__.py +7 -0
  24. spark_mcp-0.4.1/spark_mcp/ontology/certification.json +34 -0
  25. spark_mcp-0.4.1/spark_mcp/ontology/fixtures/records.json +5 -0
  26. spark_mcp-0.4.1/spark_mcp/ontology/mappings/source.yaml +9 -0
  27. spark_mcp-0.4.1/spark_mcp/ontology/migrations/manifest.json +6 -0
  28. spark_mcp-0.4.1/spark_mcp/ontology/shapes/connector.shacl.ttl +104 -0
  29. spark_mcp-0.4.1/spark_mcp/ontology/spark.ttl +81 -0
  30. spark_mcp-0.4.1/spark_mcp/prompts/__init__.py +1 -0
  31. spark_mcp-0.4.1/spark_mcp/skills/__init__.py +1 -0
  32. spark_mcp-0.4.1/spark_mcp/skills/spark-kg-ingestion/SKILL.md +59 -0
  33. spark_mcp-0.4.1/spark_mcp/skills/spark-transform-operations/SKILL.md +80 -0
  34. spark_mcp-0.4.1/spark_mcp.egg-info/PKG-INFO +329 -0
  35. spark_mcp-0.4.1/spark_mcp.egg-info/SOURCES.txt +44 -0
  36. spark_mcp-0.4.1/spark_mcp.egg-info/dependency_links.txt +1 -0
  37. spark_mcp-0.4.1/spark_mcp.egg-info/entry_points.txt +11 -0
  38. spark_mcp-0.4.1/spark_mcp.egg-info/requires.txt +19 -0
  39. spark_mcp-0.4.1/spark_mcp.egg-info/top_level.txt +9 -0
  40. spark_mcp-0.4.1/tests/conftest.py +0 -0
  41. spark_mcp-0.4.1/tests/test_api_client.py +180 -0
  42. spark_mcp-0.4.1/tests/test_auth_env.py +115 -0
  43. spark_mcp-0.4.1/tests/test_kg_ingest.py +172 -0
  44. spark_mcp-0.4.1/tests/test_ownership.py +97 -0
  45. spark_mcp-0.4.1/tests/test_rest_operations.py +96 -0
  46. spark_mcp-0.4.1/tests/test_startup.py +9 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Knuckles-Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ include README.md
2
+ include requirements.txt
3
+ recursive-include spark_mcp *.py *.json *.md *.yaml *.yml
@@ -0,0 +1,329 @@
1
+ Metadata-Version: 2.4
2
+ Name: spark-mcp
3
+ Version: 0.4.1
4
+ Summary: Spark Connect (gRPC) API + MCP Server for Agentic AI!
5
+ Author-email: Project Maintainers <maintainers@users.noreply.github.com>
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Environment :: Console
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: <3.15,>=3.12
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: agent-connector-sdk[mcp]<1.0.0,>=0.3.6
16
+ Requires-Dist: httpx>=0.28.1
17
+ Requires-Dist: numpy>=2.5.4
18
+ Requires-Dist: pandas>=3.0.6
19
+ Requires-Dist: pyarrow>=26.0.0
20
+ Requires-Dist: pyspark-client<4.2.0,>=4.1.3
21
+ Requires-Dist: requests>=2.34.2
22
+ Provides-Extra: mcp
23
+ Requires-Dist: agent-connector-sdk[mcp]<1.0.0,>=0.3.6; extra == "mcp"
24
+ Provides-Extra: all
25
+ Requires-Dist: agent-connector-sdk[mcp]<1.0.0,>=0.3.6; extra == "all"
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest-xdist>=3.8.0; extra == "test"
28
+ Requires-Dist: pytest>=9.1.1; extra == "test"
29
+ Requires-Dist: pytest-asyncio>=1.4.0; extra == "test"
30
+ Requires-Dist: pytest-cov>=7.1.0; extra == "test"
31
+ Dynamic: license-file
32
+
33
+ # Spark Mcp
34
+ ## CLI or API | MCP
35
+
36
+ ![PyPI - Version](https://img.shields.io/pypi/v/spark-mcp)
37
+ ![MCP Server](https://badge.mcpx.dev?type=server 'MCP Server')
38
+ ![PyPI - Downloads](https://img.shields.io/pypi/dd/spark-mcp)
39
+ ![GitHub Repo stars](https://img.shields.io/github/stars/Knuckles-Team/spark-mcp)
40
+ ![GitHub forks](https://img.shields.io/github/forks/Knuckles-Team/spark-mcp)
41
+ ![GitHub contributors](https://img.shields.io/github/contributors/Knuckles-Team/spark-mcp)
42
+ ![PyPI - License](https://img.shields.io/pypi/l/spark-mcp)
43
+ ![GitHub](https://img.shields.io/github/license/Knuckles-Team/spark-mcp)
44
+ ![GitHub last commit (by committer)](https://img.shields.io/github/last-commit/Knuckles-Team/spark-mcp)
45
+ ![GitHub pull requests](https://img.shields.io/github/issues-pr/Knuckles-Team/spark-mcp)
46
+ ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/Knuckles-Team/spark-mcp)
47
+ ![GitHub issues](https://img.shields.io/github/issues/Knuckles-Team/spark-mcp)
48
+ ![GitHub top language](https://img.shields.io/github/languages/top/Knuckles-Team/spark-mcp)
49
+ ![GitHub language count](https://img.shields.io/github/languages/count/Knuckles-Team/spark-mcp)
50
+ ![GitHub repo size](https://img.shields.io/github/repo-size/Knuckles-Team/spark-mcp)
51
+ ![GitHub repo file count (file type)](https://img.shields.io/github/directory-file-count/Knuckles-Team/spark-mcp)
52
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/spark-mcp)
53
+ ![PyPI - Implementation](https://img.shields.io/pypi/implementation/spark-mcp)
54
+
55
+ *Version: 0.4.1*
56
+
57
+ > **Documentation** — Installation, deployment, and usage across the API, CLI, MCP,
58
+ > and A2A agent interfaces are maintained in the
59
+ > [official documentation](https://knuckles-team.github.io/spark-mcp/).
60
+
61
+ ---
62
+
63
+ ## Table of Contents
64
+ - [Overview](#overview)
65
+ - [Installation](#installation)
66
+ - [Usage](#usage)
67
+ - [Architecture](#architecture)
68
+ - [Environment Variables](#environment-variables)
69
+ - [MCP Tools](#mcp-tools)
70
+ - [Documentation](#documentation)
71
+
72
+ ## Overview
73
+ `spark-mcp` exposes a standardized interface to the live Spark Connect deployment
74
+ via the Model Context Protocol — SQL/session execution, versioned Transform
75
+ submission/tracking/rerun against the shared Iceberg `lakehouse` catalog
76
+ (`scope=lakekeeper`, explicit — the same landmine Trino/Lakekeeper hit if dropped),
77
+ namespace/table catalog reads, OpenLineage config/status reporting, and a
78
+ Wire-First `spark_ingest_run` tool that pushes the
79
+ `SparkApplication -> SparkJob -> Transform -> TransformRun -> DatasetVersion` chain
80
+ into the epistemic-graph Knowledge Graph.
81
+
82
+ **Spark Connect is live**, confirmed 2026-08-26 against
83
+ `spark-connect.apps.svc:15002` with a real `pyspark.sql.connect` session — a query
84
+ against `lakehouse.analytics.trino_verify` returned the same 3 rows Trino's sibling
85
+ lane already proved reading/writing (cross-engine Iceberg interop). See
86
+ [docs/architecture.md](docs/architecture.md) for the full evidence and this
87
+ package's `AGENTS.md` for known gaps (identity-token attach, `ActionSpec`
88
+ typed-Actions deferral).
89
+
90
+ Talks directly to Spark over `pyspark-client` (the thin Spark Connect client) for SQL, DataFrame and catalog operations, and over the Spark monitoring REST API (`/api/v1` on the driver UI and the History Server) for applications, jobs, stages, executors, storage, environment and SQL executions, never
91
+ `bin/spark-shell --remote` (does not work on the vanilla Spark image) and never a
92
+ `kubectl exec spark-sql` fallback.
93
+
94
+ ## Installation
95
+
96
+ Pick the extra that matches what the operator want to run:
97
+
98
+ | Extra | Installs | Use when |
99
+ |-------|----------|----------|
100
+ | `spark-mcp[mcp]` | Connector-focused MCP server (`agent-connector-sdk` — FastMCP/FastAPI + `epistemic-graph[full]`) | The operator only run the **MCP server** (smallest install / image) |
101
+ | `spark-mcp[all]` | Everything (`mcp` + `agent` + `logfire`) | Development / both surfaces |
102
+
103
+ ```bash
104
+ uv pip install "spark-mcp[mcp]"
105
+ ```
106
+
107
+ ### Container images (`:mcp`)
108
+
109
+ One multi-stage `docker/Dockerfile` builds the MCP-only image (`--target mcp`):
110
+
111
+ ```bash
112
+ docker build --target mcp -t spark-mcp:mcp .
113
+ ```
114
+
115
+ Both images install a headless JRE at runtime — `pyspark[connect]`'s gRPC client has
116
+ a JVM-side dependency, unlike the fleet's pure-Python REST connectors.
117
+
118
+ ## Usage
119
+ Run the MCP server directly:
120
+ ```bash
121
+ python -m spark_mcp
122
+ ```
123
+
124
+ ### MCP Configuration Example (stdio)
125
+
126
+ ```json
127
+ {
128
+ "mcpServers": {
129
+ "spark-mcp": {
130
+ "command": "uv",
131
+ "args": ["run", "spark-mcp"],
132
+ "env": {
133
+ "MCP_TOOL_MODE": "intent",
134
+ "SPARK_CONNECT_URL": "sc://spark-connect.apps.svc:15002",
135
+ "SPARK_LAKEHOUSE_CATALOG": "lakehouse",
136
+ "SPARK_LAKEHOUSE_SCOPE": "lakekeeper",
137
+ "SPARK_ENABLE_TRANSFORMS": "False",
138
+ "SPARKTOOL": "True"
139
+ }
140
+ }
141
+ }
142
+ }
143
+ ```
144
+
145
+ ## Architecture
146
+
147
+ `api_client.py` wraps `api/api_client_spark.py`'s `SparkApi`, a thin gRPC client
148
+ over `pyspark.sql.connect.session.SparkSession`, behind `api/api_client_base.py`'s
149
+ typed-error layer (`SparkApiError` with a `kind`: `unreachable` / `job_failed` /
150
+ `session_evicted` / `disabled` — never a silent empty result). `auth.py` mints
151
+ Keycloak client-credentials identity tokens and can attach them as real gRPC bearer
152
+ credentials, but the deployed Spark Connect endpoint is plaintext gRPC today, so
153
+ attach is disabled by default (see `docs/architecture.md`).
154
+
155
+ `mcp_server.py` serves one typed `spark_<operation>` tool per `SparkApi` method
156
+ (`spark_sql`, `spark_describe`, `spark_cancel`, `spark_submit_transform`,
157
+ `spark_rerun_transform`, `spark_list_transform_runs`, `spark_list_lakekeeper_tables`,
158
+ `spark_lineage_config`, `spark_lineage_events`, `spark_application_status`) through one
159
+ `register_tool_surface(...)` call; GET-like reads are read-only and `spark_cancel` is
160
+ destructive. `kg_ingest.py` exposes
161
+ `ingest_entities`/`ingest_run` plus record-mapping helpers around the required
162
+ `native_ingest` (Wire-First) authority.
163
+
164
+ ## Python version
165
+
166
+ Python 3.14 (floor 3.12) against a Spark 4.1 server. `pyspark-client` 4.1 is the
167
+ thin Spark Connect client; it runs on numpy 2, pandas 3 and pyarrow wheels.
168
+ `pyspark-client` ships only a pure-Python sdist, so it is the one package built
169
+ from source (`[tool.pipelines] allow-source-build = ["pyspark-client"]`).
170
+
171
+ ## Environment Variables
172
+
173
+ <!-- ENV-VARS-TABLE:START -->
174
+
175
+ #### Package environment variables
176
+
177
+ | Variable | Example | Description |
178
+ |----------|---------|-------------|
179
+ | `SPARK_CONNECT_URL` | `sc://spark-connect.apps.svc:15002` | |
180
+ | `SPARK_UI_URL` | `http://spark-runner.apps.svc:4040` | driver monitoring REST |
181
+ | `SPARK_HISTORY_URL` | `http://spark-history.apps.svc:18080` | History Server REST |
182
+ | `SPARK_REST_TIMEOUT_S` | `30` | |
183
+ | `SPARK_SUBMIT_TIMEOUT_S` | `300` | |
184
+ | `SPARK_LAKEHOUSE_CATALOG` | `lakehouse` | document/verify the invariant client-side — same invalid_scope landmine as |
185
+ | `SPARK_LAKEHOUSE_SCOPE` | `lakekeeper` | |
186
+ | `SPARK_ENABLE_TRANSFORMS` | `False` | egeria-mcp's EGERIA_ENABLE_WRITE) — spark_submit_transform/spark_rerun_transform |
187
+ | `SPARK_CONNECT_ATTACH_IDENTITY_TOKEN` | secret-injected | Service is plaintext gRPC; attaching a bearer token forces a TLS channel the |
188
+ | `SPARK_CONNECT_USE_SSL` | `False` | |
189
+ | `SPARK_SERVICE_CLIENT_ID` | `spark-service` | |
190
+ | `SPARK_SERVICE_CLIENT_SECRET` | secret-injected | |
191
+ | `SPARK_OAUTH_SCOPE` | `lakekeeper` | |
192
+ | `SPARK_OAUTH_TOKEN_URL` | secret-injected | |
193
+ | `SPARK_KEYCLOAK_URL` | `http://localhost:8080` | |
194
+ | `SPARK_KEYCLOAK_REALM` | `homelab` | |
195
+ | `SPARKTOOL` | `True` | |
196
+ | `INGESTTOOL` | `True` | |
197
+
198
+ #### Inherited agent-connector-sdk variables (apply to every connector)
199
+
200
+ | Variable | Example | Description |
201
+ |----------|---------|-------------|
202
+ | `TRANSPORT` | `stdio` | MCP transport: `stdio` \| `streamable-http` \| `sse` |
203
+ | `HOST` | `127.0.0.1` | Loopback bind host (set an authenticated ingress explicitly) |
204
+ | `PORT` | `8000` | Bind port (HTTP transports) |
205
+ | `MCP_TOOL_MODE` | `intent` | Tool surface: `intent` \| `condensed` \| `verbose` \| `both` |
206
+ | `MCP_ENABLED_TOOLS` | — | Comma-separated tool allow-list |
207
+ | `MCP_DISABLED_TOOLS` | — | Comma-separated tool deny-list |
208
+ | `MCP_ENABLED_TAGS` | — | Comma-separated tag allow-list |
209
+ | `MCP_DISABLED_TAGS` | — | Comma-separated tag deny-list |
210
+ | `EUNOMIA_TYPE` | `none` | Authorization mode: `none` \| `embedded` \| `remote` |
211
+ | `EUNOMIA_POLICY_FILE` | `mcp_policies.json` | Embedded Eunomia policy file |
212
+ | `EUNOMIA_REMOTE_URL` | — | Remote Eunomia authorization server URL |
213
+ | `ENABLE_OTEL` | `False` | Enable OpenTelemetry export |
214
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | — | OTLP collector endpoint |
215
+ | `MCP_CLIENT_AUTH` | — | Outbound MCP child auth: `oidc-client-credentials` \| `basic` \| `none` |
216
+ | `OIDC_CLIENT_ID` | — | OIDC client id (service-account auth) |
217
+ | `OIDC_CLIENT_SECRET_REF` | `secret://identity/oidc-client-secret` | Runtime secret reference for the OIDC service account |
218
+ | `MCP_BASIC_AUTH_USERNAME` | — | HTTP Basic username (`MCP_CLIENT_AUTH=basic`) |
219
+ | `MCP_BASIC_AUTH_PASSWORD_REF` | `secret://identity/mcp-basic-password` | Runtime secret reference for HTTP Basic auth (`MCP_CLIENT_AUTH=basic`) |
220
+ | `DEBUG` | `False` | Verbose logging |
221
+ | `PYTHONUNBUFFERED` | `1` | Unbuffered stdout (recommended in containers) |
222
+ | `MCP_URL` | `http://localhost:8000/mcp` | URL of the MCP server the agent connects to |
223
+ | `PROVIDER` | `openai` | LLM provider for the agent |
224
+ | `MODEL_ID` | `gpt-4o` | Model id for the agent |
225
+ | `ENABLE_WEB_UI` | `True` | Serve the AG-UI web interface |
226
+
227
+ _15 package + 24 inherited variable(s). Auto-generated from `.env.example` + the shared agent-connector-sdk set — do not edit._
228
+ <!-- ENV-VARS-TABLE:END -->
229
+
230
+
231
+ | Variable | Required | Notes |
232
+ |----------|----------|-------|
233
+ | `SPARK_CONNECT_URL` | recommended | Spark Connect gRPC endpoint. Defaults to `sc://spark-connect.apps.svc:15002`. |
234
+ | `SPARK_SUBMIT_TIMEOUT_S` | optional | Submission timeout budget. Defaults to `300`. |
235
+ | `SPARK_LAKEHOUSE_CATALOG` / `SPARK_LAKEHOUSE_SCOPE` | optional | Defaults to `lakehouse` / `lakekeeper` — never leave scope at the shared-client default `catalog`. |
236
+ | `SPARK_ENABLE_TRANSFORMS` | ✅ for writes | `spark_submit_transform`/`spark_rerun_transform` refuse unless `true`. |
237
+ | `SPARK_CONNECT_ATTACH_IDENTITY_TOKEN` / `SPARK_CONNECT_USE_SSL` | optional | Both must be `true` together — see `docs/architecture.md`. |
238
+ | `SPARK_SERVICE_CLIENT_ID` / `SPARK_SERVICE_CLIENT_SECRET` | for identity attach | Keycloak client-credentials. |
239
+ | `SPARK_OAUTH_SCOPE` / `SPARK_OAUTH_TOKEN_URL` / `SPARK_KEYCLOAK_URL` / `SPARK_KEYCLOAK_REALM` | optional | Token-mint configuration. |
240
+
241
+ See [.env.example](.env.example) for the full template.
242
+
243
+ ## MCP Tools
244
+
245
+ | Group | Tools |
246
+ |-------|-------|
247
+ | session | `spark_sql`, `spark_describe`, `spark_cancel` |
248
+ | transforms | `spark_submit_transform`, `spark_list_transform_runs`, `spark_rerun_transform` |
249
+ | catalog | `spark_list_lakekeeper_tables` |
250
+ | lineage | `spark_lineage_config`, `spark_lineage_events` |
251
+ | ops | `spark_application_status` |
252
+ | ingest | `spark_ingest_run` |
253
+
254
+ ## Documentation
255
+ - [Architecture](docs/architecture.md)
256
+ - [Configuration](docs/configuration.md)
257
+ - [AGENTS.md](AGENTS.md) — canonical agent-facing context, live-proof evidence, known gaps
258
+
259
+ ## Available MCP Tools
260
+
261
+ <!-- MCP-TOOLS-TABLE:START -->
262
+
263
+ #### Condensed action-routed tools (`MCP_TOOL_MODE=condensed`)
264
+
265
+ | MCP Tool | Toggle Env Var | Description |
266
+ |----------|----------------|-------------|
267
+ | `spark_ingest_run` | `INGESTTOOL` | Push one TransformRun's SparkApplication->SparkJob->Transform->TransformRun |
268
+
269
+ #### Verbose 1:1 API-mapped tools (`MCP_TOOL_MODE=verbose` or `both`)
270
+
271
+ <details>
272
+ <summary>12 per-operation tools — one per public API method (click to expand)</summary>
273
+
274
+ | MCP Tool | Toggle Env Var | Description |
275
+ |----------|----------------|-------------|
276
+ | `spark_application_status` | `SPARKTOOL` | Best-effort application/session status. |
277
+ | `spark_cancel` | `SPARKTOOL` | Interrupt running operations on this session (all, or one tagged operation). |
278
+ | `spark_close` | `SPARK_APITOOL` | Invoke the close operation. |
279
+ | `spark_describe` | `SPARKTOOL` | Schema + column info for one fully-qualified table. |
280
+ | `spark_get_session` | `SPARK_APITOOL` | Return the cached Spark Connect session, building it on first use. |
281
+ | `spark_lineage_config` | `SPARKTOOL` | Report OpenLineage listener config — already set server-side at |
282
+ | `spark_lineage_events` | `SPARKTOOL` | Named per this lane's tool-group contract; NOT a Kafka consumer. |
283
+ | `spark_list_lakekeeper_tables` | `SPARKTOOL` | Invoke the list_lakekeeper_tables operation. |
284
+ | `spark_list_transform_runs` | `SPARKTOOL` | Invoke the list_transform_runs operation. |
285
+ | `spark_rerun_transform` | `SPARKTOOL` | Deterministically re-execute a prior TransformRun's manifest. |
286
+ | `spark_sql` | `SPARKTOOL` | Execute one SQL statement and return rows as plain dicts. |
287
+ | `spark_submit_transform` | `SPARKTOOL` | Execute one Transform manifest, recording a TransformRun (success or typed failure). |
288
+
289
+ </details>
290
+
291
+ _1 action-routed tool(s) · 12 verbose 1:1 tool(s). Each is enabled unless its `<DOMAIN>TOOL` toggle is set false; `MCP_TOOL_MODE` selects the surface (**`intent` default** — the six verb-tools, granular set loaded on demand · `condensed` action-routed · `verbose` 1:1 · `both`). Auto-generated — do not edit._
292
+ <!-- MCP-TOOLS-TABLE:END -->
293
+
294
+ ---
295
+
296
+ ## Repository Owners
297
+
298
+ <img width="100%" height="180em" src="https://github-readme-stats.vercel.app/api?username=example&show_icons=true&hide_border=true&&count_private=true&include_all_commits=true" />
299
+
300
+ ![GitHub followers](https://img.shields.io/github/followers/example)
301
+ ![GitHub User's stars](https://img.shields.io/github/stars/example)
302
+
303
+ ---
304
+
305
+ ## Contribute
306
+
307
+ Contributions are welcome! Please ensure code quality by running local checks before submitting pull requests:
308
+ - Format code using `ruff format .`
309
+ - Lint code using `ruff check .`
310
+ - Validate type-safety with `mypy .`
311
+ - Run test suites using `pytest`
312
+
313
+
314
+
315
+ <!-- GOVERNED-CAPABILITY:START -->
316
+ ## Governed capability contract
317
+
318
+ This package ships a compact canonical skill surface with specialist procedures
319
+ kept as referenced workflows. The current MCP tools, skill metadata,
320
+ `connector_manifest.yml`, ontology, mappings, shapes, fixtures, migrations,
321
+ tool-schema fingerprints, and certification metadata form one versioned
322
+ capability contract. Validate them together; do not rely on stale tool names or
323
+ historical per-task skill wrappers.
324
+
325
+ Runtime endpoints, credentials, certificate trust, tenant identity, retention,
326
+ and observability policy are deployment inputs and are never packaged values.
327
+ See [Configuration, trust, and privacy](docs/configuration.md) before enabling a
328
+ network transport, connector ingestion, Arbora delegation, or trace export.
329
+ <!-- GOVERNED-CAPABILITY:END -->
@@ -0,0 +1,297 @@
1
+ # Spark Mcp
2
+ ## CLI or API | MCP
3
+
4
+ ![PyPI - Version](https://img.shields.io/pypi/v/spark-mcp)
5
+ ![MCP Server](https://badge.mcpx.dev?type=server 'MCP Server')
6
+ ![PyPI - Downloads](https://img.shields.io/pypi/dd/spark-mcp)
7
+ ![GitHub Repo stars](https://img.shields.io/github/stars/Knuckles-Team/spark-mcp)
8
+ ![GitHub forks](https://img.shields.io/github/forks/Knuckles-Team/spark-mcp)
9
+ ![GitHub contributors](https://img.shields.io/github/contributors/Knuckles-Team/spark-mcp)
10
+ ![PyPI - License](https://img.shields.io/pypi/l/spark-mcp)
11
+ ![GitHub](https://img.shields.io/github/license/Knuckles-Team/spark-mcp)
12
+ ![GitHub last commit (by committer)](https://img.shields.io/github/last-commit/Knuckles-Team/spark-mcp)
13
+ ![GitHub pull requests](https://img.shields.io/github/issues-pr/Knuckles-Team/spark-mcp)
14
+ ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/Knuckles-Team/spark-mcp)
15
+ ![GitHub issues](https://img.shields.io/github/issues/Knuckles-Team/spark-mcp)
16
+ ![GitHub top language](https://img.shields.io/github/languages/top/Knuckles-Team/spark-mcp)
17
+ ![GitHub language count](https://img.shields.io/github/languages/count/Knuckles-Team/spark-mcp)
18
+ ![GitHub repo size](https://img.shields.io/github/repo-size/Knuckles-Team/spark-mcp)
19
+ ![GitHub repo file count (file type)](https://img.shields.io/github/directory-file-count/Knuckles-Team/spark-mcp)
20
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/spark-mcp)
21
+ ![PyPI - Implementation](https://img.shields.io/pypi/implementation/spark-mcp)
22
+
23
+ *Version: 0.4.1*
24
+
25
+ > **Documentation** — Installation, deployment, and usage across the API, CLI, MCP,
26
+ > and A2A agent interfaces are maintained in the
27
+ > [official documentation](https://knuckles-team.github.io/spark-mcp/).
28
+
29
+ ---
30
+
31
+ ## Table of Contents
32
+ - [Overview](#overview)
33
+ - [Installation](#installation)
34
+ - [Usage](#usage)
35
+ - [Architecture](#architecture)
36
+ - [Environment Variables](#environment-variables)
37
+ - [MCP Tools](#mcp-tools)
38
+ - [Documentation](#documentation)
39
+
40
+ ## Overview
41
+ `spark-mcp` exposes a standardized interface to the live Spark Connect deployment
42
+ via the Model Context Protocol — SQL/session execution, versioned Transform
43
+ submission/tracking/rerun against the shared Iceberg `lakehouse` catalog
44
+ (`scope=lakekeeper`, explicit — the same landmine Trino/Lakekeeper hit if dropped),
45
+ namespace/table catalog reads, OpenLineage config/status reporting, and a
46
+ Wire-First `spark_ingest_run` tool that pushes the
47
+ `SparkApplication -> SparkJob -> Transform -> TransformRun -> DatasetVersion` chain
48
+ into the epistemic-graph Knowledge Graph.
49
+
50
+ **Spark Connect is live**, confirmed 2026-08-26 against
51
+ `spark-connect.apps.svc:15002` with a real `pyspark.sql.connect` session — a query
52
+ against `lakehouse.analytics.trino_verify` returned the same 3 rows Trino's sibling
53
+ lane already proved reading/writing (cross-engine Iceberg interop). See
54
+ [docs/architecture.md](docs/architecture.md) for the full evidence and this
55
+ package's `AGENTS.md` for known gaps (identity-token attach, `ActionSpec`
56
+ typed-Actions deferral).
57
+
58
+ Talks directly to Spark over `pyspark-client` (the thin Spark Connect client) for SQL, DataFrame and catalog operations, and over the Spark monitoring REST API (`/api/v1` on the driver UI and the History Server) for applications, jobs, stages, executors, storage, environment and SQL executions, never
59
+ `bin/spark-shell --remote` (does not work on the vanilla Spark image) and never a
60
+ `kubectl exec spark-sql` fallback.
61
+
62
+ ## Installation
63
+
64
+ Pick the extra that matches what the operator want to run:
65
+
66
+ | Extra | Installs | Use when |
67
+ |-------|----------|----------|
68
+ | `spark-mcp[mcp]` | Connector-focused MCP server (`agent-connector-sdk` — FastMCP/FastAPI + `epistemic-graph[full]`) | The operator only run the **MCP server** (smallest install / image) |
69
+ | `spark-mcp[all]` | Everything (`mcp` + `agent` + `logfire`) | Development / both surfaces |
70
+
71
+ ```bash
72
+ uv pip install "spark-mcp[mcp]"
73
+ ```
74
+
75
+ ### Container images (`:mcp`)
76
+
77
+ One multi-stage `docker/Dockerfile` builds the MCP-only image (`--target mcp`):
78
+
79
+ ```bash
80
+ docker build --target mcp -t spark-mcp:mcp .
81
+ ```
82
+
83
+ Both images install a headless JRE at runtime — `pyspark[connect]`'s gRPC client has
84
+ a JVM-side dependency, unlike the fleet's pure-Python REST connectors.
85
+
86
+ ## Usage
87
+ Run the MCP server directly:
88
+ ```bash
89
+ python -m spark_mcp
90
+ ```
91
+
92
+ ### MCP Configuration Example (stdio)
93
+
94
+ ```json
95
+ {
96
+ "mcpServers": {
97
+ "spark-mcp": {
98
+ "command": "uv",
99
+ "args": ["run", "spark-mcp"],
100
+ "env": {
101
+ "MCP_TOOL_MODE": "intent",
102
+ "SPARK_CONNECT_URL": "sc://spark-connect.apps.svc:15002",
103
+ "SPARK_LAKEHOUSE_CATALOG": "lakehouse",
104
+ "SPARK_LAKEHOUSE_SCOPE": "lakekeeper",
105
+ "SPARK_ENABLE_TRANSFORMS": "False",
106
+ "SPARKTOOL": "True"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ ## Architecture
114
+
115
+ `api_client.py` wraps `api/api_client_spark.py`'s `SparkApi`, a thin gRPC client
116
+ over `pyspark.sql.connect.session.SparkSession`, behind `api/api_client_base.py`'s
117
+ typed-error layer (`SparkApiError` with a `kind`: `unreachable` / `job_failed` /
118
+ `session_evicted` / `disabled` — never a silent empty result). `auth.py` mints
119
+ Keycloak client-credentials identity tokens and can attach them as real gRPC bearer
120
+ credentials, but the deployed Spark Connect endpoint is plaintext gRPC today, so
121
+ attach is disabled by default (see `docs/architecture.md`).
122
+
123
+ `mcp_server.py` serves one typed `spark_<operation>` tool per `SparkApi` method
124
+ (`spark_sql`, `spark_describe`, `spark_cancel`, `spark_submit_transform`,
125
+ `spark_rerun_transform`, `spark_list_transform_runs`, `spark_list_lakekeeper_tables`,
126
+ `spark_lineage_config`, `spark_lineage_events`, `spark_application_status`) through one
127
+ `register_tool_surface(...)` call; GET-like reads are read-only and `spark_cancel` is
128
+ destructive. `kg_ingest.py` exposes
129
+ `ingest_entities`/`ingest_run` plus record-mapping helpers around the required
130
+ `native_ingest` (Wire-First) authority.
131
+
132
+ ## Python version
133
+
134
+ Python 3.14 (floor 3.12) against a Spark 4.1 server. `pyspark-client` 4.1 is the
135
+ thin Spark Connect client; it runs on numpy 2, pandas 3 and pyarrow wheels.
136
+ `pyspark-client` ships only a pure-Python sdist, so it is the one package built
137
+ from source (`[tool.pipelines] allow-source-build = ["pyspark-client"]`).
138
+
139
+ ## Environment Variables
140
+
141
+ <!-- ENV-VARS-TABLE:START -->
142
+
143
+ #### Package environment variables
144
+
145
+ | Variable | Example | Description |
146
+ |----------|---------|-------------|
147
+ | `SPARK_CONNECT_URL` | `sc://spark-connect.apps.svc:15002` | |
148
+ | `SPARK_UI_URL` | `http://spark-runner.apps.svc:4040` | driver monitoring REST |
149
+ | `SPARK_HISTORY_URL` | `http://spark-history.apps.svc:18080` | History Server REST |
150
+ | `SPARK_REST_TIMEOUT_S` | `30` | |
151
+ | `SPARK_SUBMIT_TIMEOUT_S` | `300` | |
152
+ | `SPARK_LAKEHOUSE_CATALOG` | `lakehouse` | document/verify the invariant client-side — same invalid_scope landmine as |
153
+ | `SPARK_LAKEHOUSE_SCOPE` | `lakekeeper` | |
154
+ | `SPARK_ENABLE_TRANSFORMS` | `False` | egeria-mcp's EGERIA_ENABLE_WRITE) — spark_submit_transform/spark_rerun_transform |
155
+ | `SPARK_CONNECT_ATTACH_IDENTITY_TOKEN` | secret-injected | Service is plaintext gRPC; attaching a bearer token forces a TLS channel the |
156
+ | `SPARK_CONNECT_USE_SSL` | `False` | |
157
+ | `SPARK_SERVICE_CLIENT_ID` | `spark-service` | |
158
+ | `SPARK_SERVICE_CLIENT_SECRET` | secret-injected | |
159
+ | `SPARK_OAUTH_SCOPE` | `lakekeeper` | |
160
+ | `SPARK_OAUTH_TOKEN_URL` | secret-injected | |
161
+ | `SPARK_KEYCLOAK_URL` | `http://localhost:8080` | |
162
+ | `SPARK_KEYCLOAK_REALM` | `homelab` | |
163
+ | `SPARKTOOL` | `True` | |
164
+ | `INGESTTOOL` | `True` | |
165
+
166
+ #### Inherited agent-connector-sdk variables (apply to every connector)
167
+
168
+ | Variable | Example | Description |
169
+ |----------|---------|-------------|
170
+ | `TRANSPORT` | `stdio` | MCP transport: `stdio` \| `streamable-http` \| `sse` |
171
+ | `HOST` | `127.0.0.1` | Loopback bind host (set an authenticated ingress explicitly) |
172
+ | `PORT` | `8000` | Bind port (HTTP transports) |
173
+ | `MCP_TOOL_MODE` | `intent` | Tool surface: `intent` \| `condensed` \| `verbose` \| `both` |
174
+ | `MCP_ENABLED_TOOLS` | — | Comma-separated tool allow-list |
175
+ | `MCP_DISABLED_TOOLS` | — | Comma-separated tool deny-list |
176
+ | `MCP_ENABLED_TAGS` | — | Comma-separated tag allow-list |
177
+ | `MCP_DISABLED_TAGS` | — | Comma-separated tag deny-list |
178
+ | `EUNOMIA_TYPE` | `none` | Authorization mode: `none` \| `embedded` \| `remote` |
179
+ | `EUNOMIA_POLICY_FILE` | `mcp_policies.json` | Embedded Eunomia policy file |
180
+ | `EUNOMIA_REMOTE_URL` | — | Remote Eunomia authorization server URL |
181
+ | `ENABLE_OTEL` | `False` | Enable OpenTelemetry export |
182
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | — | OTLP collector endpoint |
183
+ | `MCP_CLIENT_AUTH` | — | Outbound MCP child auth: `oidc-client-credentials` \| `basic` \| `none` |
184
+ | `OIDC_CLIENT_ID` | — | OIDC client id (service-account auth) |
185
+ | `OIDC_CLIENT_SECRET_REF` | `secret://identity/oidc-client-secret` | Runtime secret reference for the OIDC service account |
186
+ | `MCP_BASIC_AUTH_USERNAME` | — | HTTP Basic username (`MCP_CLIENT_AUTH=basic`) |
187
+ | `MCP_BASIC_AUTH_PASSWORD_REF` | `secret://identity/mcp-basic-password` | Runtime secret reference for HTTP Basic auth (`MCP_CLIENT_AUTH=basic`) |
188
+ | `DEBUG` | `False` | Verbose logging |
189
+ | `PYTHONUNBUFFERED` | `1` | Unbuffered stdout (recommended in containers) |
190
+ | `MCP_URL` | `http://localhost:8000/mcp` | URL of the MCP server the agent connects to |
191
+ | `PROVIDER` | `openai` | LLM provider for the agent |
192
+ | `MODEL_ID` | `gpt-4o` | Model id for the agent |
193
+ | `ENABLE_WEB_UI` | `True` | Serve the AG-UI web interface |
194
+
195
+ _15 package + 24 inherited variable(s). Auto-generated from `.env.example` + the shared agent-connector-sdk set — do not edit._
196
+ <!-- ENV-VARS-TABLE:END -->
197
+
198
+
199
+ | Variable | Required | Notes |
200
+ |----------|----------|-------|
201
+ | `SPARK_CONNECT_URL` | recommended | Spark Connect gRPC endpoint. Defaults to `sc://spark-connect.apps.svc:15002`. |
202
+ | `SPARK_SUBMIT_TIMEOUT_S` | optional | Submission timeout budget. Defaults to `300`. |
203
+ | `SPARK_LAKEHOUSE_CATALOG` / `SPARK_LAKEHOUSE_SCOPE` | optional | Defaults to `lakehouse` / `lakekeeper` — never leave scope at the shared-client default `catalog`. |
204
+ | `SPARK_ENABLE_TRANSFORMS` | ✅ for writes | `spark_submit_transform`/`spark_rerun_transform` refuse unless `true`. |
205
+ | `SPARK_CONNECT_ATTACH_IDENTITY_TOKEN` / `SPARK_CONNECT_USE_SSL` | optional | Both must be `true` together — see `docs/architecture.md`. |
206
+ | `SPARK_SERVICE_CLIENT_ID` / `SPARK_SERVICE_CLIENT_SECRET` | for identity attach | Keycloak client-credentials. |
207
+ | `SPARK_OAUTH_SCOPE` / `SPARK_OAUTH_TOKEN_URL` / `SPARK_KEYCLOAK_URL` / `SPARK_KEYCLOAK_REALM` | optional | Token-mint configuration. |
208
+
209
+ See [.env.example](.env.example) for the full template.
210
+
211
+ ## MCP Tools
212
+
213
+ | Group | Tools |
214
+ |-------|-------|
215
+ | session | `spark_sql`, `spark_describe`, `spark_cancel` |
216
+ | transforms | `spark_submit_transform`, `spark_list_transform_runs`, `spark_rerun_transform` |
217
+ | catalog | `spark_list_lakekeeper_tables` |
218
+ | lineage | `spark_lineage_config`, `spark_lineage_events` |
219
+ | ops | `spark_application_status` |
220
+ | ingest | `spark_ingest_run` |
221
+
222
+ ## Documentation
223
+ - [Architecture](docs/architecture.md)
224
+ - [Configuration](docs/configuration.md)
225
+ - [AGENTS.md](AGENTS.md) — canonical agent-facing context, live-proof evidence, known gaps
226
+
227
+ ## Available MCP Tools
228
+
229
+ <!-- MCP-TOOLS-TABLE:START -->
230
+
231
+ #### Condensed action-routed tools (`MCP_TOOL_MODE=condensed`)
232
+
233
+ | MCP Tool | Toggle Env Var | Description |
234
+ |----------|----------------|-------------|
235
+ | `spark_ingest_run` | `INGESTTOOL` | Push one TransformRun's SparkApplication->SparkJob->Transform->TransformRun |
236
+
237
+ #### Verbose 1:1 API-mapped tools (`MCP_TOOL_MODE=verbose` or `both`)
238
+
239
+ <details>
240
+ <summary>12 per-operation tools — one per public API method (click to expand)</summary>
241
+
242
+ | MCP Tool | Toggle Env Var | Description |
243
+ |----------|----------------|-------------|
244
+ | `spark_application_status` | `SPARKTOOL` | Best-effort application/session status. |
245
+ | `spark_cancel` | `SPARKTOOL` | Interrupt running operations on this session (all, or one tagged operation). |
246
+ | `spark_close` | `SPARK_APITOOL` | Invoke the close operation. |
247
+ | `spark_describe` | `SPARKTOOL` | Schema + column info for one fully-qualified table. |
248
+ | `spark_get_session` | `SPARK_APITOOL` | Return the cached Spark Connect session, building it on first use. |
249
+ | `spark_lineage_config` | `SPARKTOOL` | Report OpenLineage listener config — already set server-side at |
250
+ | `spark_lineage_events` | `SPARKTOOL` | Named per this lane's tool-group contract; NOT a Kafka consumer. |
251
+ | `spark_list_lakekeeper_tables` | `SPARKTOOL` | Invoke the list_lakekeeper_tables operation. |
252
+ | `spark_list_transform_runs` | `SPARKTOOL` | Invoke the list_transform_runs operation. |
253
+ | `spark_rerun_transform` | `SPARKTOOL` | Deterministically re-execute a prior TransformRun's manifest. |
254
+ | `spark_sql` | `SPARKTOOL` | Execute one SQL statement and return rows as plain dicts. |
255
+ | `spark_submit_transform` | `SPARKTOOL` | Execute one Transform manifest, recording a TransformRun (success or typed failure). |
256
+
257
+ </details>
258
+
259
+ _1 action-routed tool(s) · 12 verbose 1:1 tool(s). Each is enabled unless its `<DOMAIN>TOOL` toggle is set false; `MCP_TOOL_MODE` selects the surface (**`intent` default** — the six verb-tools, granular set loaded on demand · `condensed` action-routed · `verbose` 1:1 · `both`). Auto-generated — do not edit._
260
+ <!-- MCP-TOOLS-TABLE:END -->
261
+
262
+ ---
263
+
264
+ ## Repository Owners
265
+
266
+ <img width="100%" height="180em" src="https://github-readme-stats.vercel.app/api?username=example&show_icons=true&hide_border=true&&count_private=true&include_all_commits=true" />
267
+
268
+ ![GitHub followers](https://img.shields.io/github/followers/example)
269
+ ![GitHub User's stars](https://img.shields.io/github/stars/example)
270
+
271
+ ---
272
+
273
+ ## Contribute
274
+
275
+ Contributions are welcome! Please ensure code quality by running local checks before submitting pull requests:
276
+ - Format code using `ruff format .`
277
+ - Lint code using `ruff check .`
278
+ - Validate type-safety with `mypy .`
279
+ - Run test suites using `pytest`
280
+
281
+
282
+
283
+ <!-- GOVERNED-CAPABILITY:START -->
284
+ ## Governed capability contract
285
+
286
+ This package ships a compact canonical skill surface with specialist procedures
287
+ kept as referenced workflows. The current MCP tools, skill metadata,
288
+ `connector_manifest.yml`, ontology, mappings, shapes, fixtures, migrations,
289
+ tool-schema fingerprints, and certification metadata form one versioned
290
+ capability contract. Validate them together; do not rely on stale tool names or
291
+ historical per-task skill wrappers.
292
+
293
+ Runtime endpoints, credentials, certificate trust, tenant identity, retention,
294
+ and observability policy are deployment inputs and are never packaged values.
295
+ See [Configuration, trust, and privacy](docs/configuration.md) before enabling a
296
+ network transport, connector ingestion, Arbora delegation, or trace export.
297
+ <!-- GOVERNED-CAPABILITY:END -->