spice-mcp 0.1.5__tar.gz → 0.1.7__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.
Potentially problematic release.
This version of spice-mcp might be problematic. Click here for more details.
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/PKG-INFO +1 -1
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/pyproject.toml +1 -1
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/spellbook/explorer.py +13 -16
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/mcp/server.py +8 -5
- spice_mcp-0.1.7/tests/integration/test_dune_table_names.py +48 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/uv.lock +1 -1
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/.gitignore +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/.python-version +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/CONTRIBUTING.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/LICENSE +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/README.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/docs/architecture.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/docs/codex_cli.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/docs/codex_cli_tools.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/docs/config.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/docs/development.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/docs/discovery.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/docs/dune_api.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/docs/index.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/docs/installation.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/docs/tools.md +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/pytest.ini +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/admin.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/cache.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/client.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/extract.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/helpers.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/query_wrapper.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/transport.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/types.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/typing_utils.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/urls.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/dune/user_agent.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/http_client.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/adapters/spellbook/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/config.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/core/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/core/errors.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/core/models.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/core/ports.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/logging/query_history.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/mcp/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/mcp/tools/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/mcp/tools/base.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/mcp/tools/execute_query.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/observability/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/observability/logging.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/polars_utils.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/py.typed +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/service_layer/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/service_layer/discovery_service.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/service_layer/query_admin_service.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/service_layer/query_service.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/src/spice_mcp/service_layer/verification_service.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/cassettes/.gitkeep +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/config/environments.yaml +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/config/test_queries.yaml +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/conftest.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/fastmcp/test_dune_query_schema_validation.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/fastmcp/test_resources_and_validation.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/fastmcp/test_server_fastmcp.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/fastmcp/test_server_mcp_extras.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/http_stubbed/test_age.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/http_stubbed/test_errors.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/http_stubbed/test_pagination.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/integration/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/integration/test_spellbook_discovery.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/integration/test_user_journeys.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/live/test_live_basic.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/live/test_live_sui.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/mcp/conftest.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/mcp/test_tool_contracts.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_cache.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_cache_consistency.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_discovery.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_dune_adapter.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_edge_cases.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_parsing.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_query_history.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_show_rewrite.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_timeout.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_typing_utils.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/offline/test_urls.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/property/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/property/test_property_based.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/style/test_polars_lazy.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/support/api_client.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/support/helpers.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/support/query_factory.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/support/test_data.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/tools/test_additional_mcp_tools.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/tools/test_dbt_config_verification.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/tools/test_execute_query_tool.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/tools/test_health_tool.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/tools/test_query_service.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/tools/test_schemas.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/tools/test_unified_discover.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/validation/__init__.py +0 -0
- {spice_mcp-0.1.5 → spice_mcp-0.1.7}/tests/validation/test_error_messages.py +0 -0
|
@@ -349,13 +349,17 @@ class SpellbookExplorer(CatalogExplorer):
|
|
|
349
349
|
return {}
|
|
350
350
|
|
|
351
351
|
def _parse_sql_columns(self, sql_file: Path) -> list[TableColumn]:
|
|
352
|
-
"""
|
|
352
|
+
"""
|
|
353
|
+
Parse SQL file to extract column names from SELECT statements.
|
|
354
|
+
|
|
355
|
+
Note: This is a best-effort heuristic and may not be perfect for complex SQL.
|
|
356
|
+
For accurate column information, use Dune's DESCRIBE TABLE or query the actual table.
|
|
357
|
+
"""
|
|
353
358
|
try:
|
|
354
359
|
with open(sql_file, encoding="utf-8") as f:
|
|
355
360
|
sql = f.read()
|
|
356
361
|
|
|
357
|
-
# Look for SELECT ... FROM patterns
|
|
358
|
-
# Match: SELECT col1, col2, col3 FROM ...
|
|
362
|
+
# Look for SELECT ... FROM patterns (simple heuristic)
|
|
359
363
|
select_match = re.search(
|
|
360
364
|
r"SELECT\s+(.+?)\s+FROM",
|
|
361
365
|
sql,
|
|
@@ -364,27 +368,20 @@ class SpellbookExplorer(CatalogExplorer):
|
|
|
364
368
|
|
|
365
369
|
if select_match:
|
|
366
370
|
cols_str = select_match.group(1)
|
|
367
|
-
#
|
|
371
|
+
# Simple split - may not handle all nested cases perfectly
|
|
372
|
+
# This is OK since column info is optional and best-effort
|
|
368
373
|
cols = []
|
|
369
374
|
for col in cols_str.split(","):
|
|
370
375
|
col = col.strip()
|
|
371
|
-
#
|
|
372
|
-
|
|
373
|
-
col = col.split(" AS ", 1)[0].strip()
|
|
374
|
-
elif " " in col and not col.startswith("("):
|
|
375
|
-
# Might be alias without AS
|
|
376
|
-
parts = col.split()
|
|
377
|
-
col = parts[0].strip()
|
|
378
|
-
|
|
379
|
-
# Clean up function calls: function(col) -> col
|
|
380
|
-
col = re.sub(r"^\w+\((.+)\)", r"\1", col)
|
|
376
|
+
# Basic cleanup - remove obvious SQL noise
|
|
377
|
+
col = col.split()[-1] if col else ""
|
|
381
378
|
col = col.strip().strip('"').strip("'")
|
|
382
379
|
|
|
383
|
-
if col and col not in ["*", "DISTINCT"]:
|
|
380
|
+
if col and col not in ["*", "DISTINCT", "FROM"]:
|
|
384
381
|
cols.append(
|
|
385
382
|
TableColumn(
|
|
386
383
|
name=col,
|
|
387
|
-
dune_type="VARCHAR",
|
|
384
|
+
dune_type="VARCHAR",
|
|
388
385
|
polars_dtype="Utf8",
|
|
389
386
|
)
|
|
390
387
|
)
|
|
@@ -347,7 +347,7 @@ def _unified_discover_impl(
|
|
|
347
347
|
schema: str | None = None,
|
|
348
348
|
limit: int = 50,
|
|
349
349
|
source: Literal["dune", "spellbook", "both"] = "both",
|
|
350
|
-
include_columns: bool =
|
|
350
|
+
include_columns: bool = False,
|
|
351
351
|
) -> dict[str, Any]:
|
|
352
352
|
"""
|
|
353
353
|
Unified discovery implementation that can search Dune API, Spellbook repo, or both.
|
|
@@ -496,7 +496,7 @@ def dune_discover(
|
|
|
496
496
|
schema: str | None = None,
|
|
497
497
|
limit: int = 50,
|
|
498
498
|
source: Literal["dune", "spellbook", "both"] = "both",
|
|
499
|
-
include_columns: bool =
|
|
499
|
+
include_columns: bool = False,
|
|
500
500
|
) -> dict[str, Any]:
|
|
501
501
|
"""
|
|
502
502
|
PRIMARY discovery tool for finding tables in Dune.
|
|
@@ -520,7 +520,9 @@ def dune_discover(
|
|
|
520
520
|
limit: Maximum number of tables to return
|
|
521
521
|
source: Where to search - "dune" (Dune API only), "spellbook" (GitHub repo only),
|
|
522
522
|
or "both" (default: searches both and merges results)
|
|
523
|
-
include_columns: Whether to include column details
|
|
523
|
+
include_columns: Whether to include column details (default: False).
|
|
524
|
+
Note: Column info from Spellbook SQL is unreliable.
|
|
525
|
+
Use dune_describe_table on the actual Dune table for accurate columns.
|
|
524
526
|
|
|
525
527
|
Returns:
|
|
526
528
|
Dictionary with:
|
|
@@ -534,9 +536,10 @@ def dune_discover(
|
|
|
534
536
|
- dune_alias: Actual Dune table alias (for Spellbook models)
|
|
535
537
|
- dune_table: Verified, queryable Dune table name (e.g., "sui_walrus.base_table")
|
|
536
538
|
- verified: True (all returned tables are verified to exist)
|
|
537
|
-
- columns: Column details (for Spellbook models, if include_columns=True)
|
|
538
539
|
- 'source': The source parameter used
|
|
539
540
|
- 'message': Helpful message if no tables found
|
|
541
|
+
|
|
542
|
+
Note: To get accurate column information, use dune_describe_table on the dune_table value.
|
|
540
543
|
|
|
541
544
|
Examples:
|
|
542
545
|
# Search both sources for walrus - returns verified tables only
|
|
@@ -611,7 +614,7 @@ def _spellbook_find_models_impl(
|
|
|
611
614
|
keyword: str | list[str] | None = None,
|
|
612
615
|
schema: str | None = None,
|
|
613
616
|
limit: int = 50,
|
|
614
|
-
include_columns: bool =
|
|
617
|
+
include_columns: bool = False,
|
|
615
618
|
) -> dict[str, Any]:
|
|
616
619
|
"""
|
|
617
620
|
Implementation for spellbook model discovery.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Test that Spellbook discovery returns correct queryable Dune table names."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_walrus_table_discovery_returns_dune_table_names():
|
|
7
|
+
"""Test that discovering Walrus tables returns correct dune_table field."""
|
|
8
|
+
from spice_mcp.mcp.server import _spellbook_find_models_impl
|
|
9
|
+
|
|
10
|
+
# Discover walrus models
|
|
11
|
+
result = _spellbook_find_models_impl(
|
|
12
|
+
keyword="walrus",
|
|
13
|
+
schema=None,
|
|
14
|
+
limit=10,
|
|
15
|
+
include_columns=False
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
assert "models" in result
|
|
19
|
+
walrus_models = result["models"]
|
|
20
|
+
|
|
21
|
+
# Should find at least the base_table and payments models
|
|
22
|
+
assert len(walrus_models) >= 2
|
|
23
|
+
|
|
24
|
+
# Check each model has the required fields
|
|
25
|
+
for model in walrus_models:
|
|
26
|
+
assert "dune_schema" in model
|
|
27
|
+
assert "dune_alias" in model
|
|
28
|
+
assert "dune_table" in model
|
|
29
|
+
|
|
30
|
+
# Verify format is schema.alias (not daily_spellbook.model_name)
|
|
31
|
+
dune_table = model["dune_table"]
|
|
32
|
+
assert "." in dune_table
|
|
33
|
+
assert not dune_table.startswith("daily_spellbook.")
|
|
34
|
+
|
|
35
|
+
# Verify specific known tables
|
|
36
|
+
if model["table"] == "sui_walrus_base_table":
|
|
37
|
+
assert model["dune_schema"] == "sui_walrus"
|
|
38
|
+
assert model["dune_alias"] == "base_table"
|
|
39
|
+
assert model["dune_table"] == "sui_walrus.base_table"
|
|
40
|
+
elif model["table"] == "sui_walrus_payments":
|
|
41
|
+
assert model["dune_schema"] == "sui_walrus"
|
|
42
|
+
assert model["dune_alias"] == "payments"
|
|
43
|
+
assert model["dune_table"] == "sui_walrus.payments"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
pytest.main([__file__, "-v"])
|
|
48
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|