spice-mcp 0.1.4__tar.gz → 0.1.6__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.4 → spice_mcp-0.1.6}/.gitignore +2 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/PKG-INFO +18 -6
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/README.md +16 -4
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/docs/codex_cli.md +2 -2
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/docs/discovery.md +11 -4
- spice_mcp-0.1.6/docs/tools.md +118 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/pyproject.toml +2 -2
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/client.py +13 -29
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/spellbook/explorer.py +97 -17
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/mcp/server.py +149 -111
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/mcp/tools/execute_query.py +13 -21
- spice_mcp-0.1.6/src/spice_mcp/service_layer/verification_service.py +185 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/fastmcp/test_server_fastmcp.py +4 -4
- spice_mcp-0.1.6/tests/integration/test_dune_table_names.py +48 -0
- spice_mcp-0.1.6/tests/integration/test_spellbook_discovery.py +289 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/integration/test_user_journeys.py +6 -5
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/mcp/test_tool_contracts.py +5 -4
- spice_mcp-0.1.6/tests/offline/test_show_rewrite.py +37 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/tools/test_additional_mcp_tools.py +14 -10
- spice_mcp-0.1.6/tests/tools/test_dbt_config_verification.py +169 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/tools/test_unified_discover.py +70 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/uv.lock +1 -1
- spice_mcp-0.1.4/docs/tools.md +0 -88
- spice_mcp-0.1.4/scripts/bridgez/make_circle_comparison.py +0 -114
- spice_mcp-0.1.4/scripts/codex_tools_doctor.sh +0 -104
- spice_mcp-0.1.4/test_mcp_cursor/.env.example +0 -9
- spice_mcp-0.1.4/test_mcp_cursor/CURSOR_SETUP.md +0 -159
- spice_mcp-0.1.4/test_mcp_cursor/INSTALL.md +0 -71
- spice_mcp-0.1.4/test_mcp_cursor/QUICK_START.md +0 -96
- spice_mcp-0.1.4/test_mcp_cursor/README.md +0 -38
- spice_mcp-0.1.4/test_mcp_cursor/TEST_RESULTS.md +0 -50
- spice_mcp-0.1.4/test_mcp_cursor/cursor_mcp_config.json +0 -11
- spice_mcp-0.1.4/test_mcp_cursor/setup_cursor_mcp.sh +0 -58
- spice_mcp-0.1.4/test_mcp_cursor/test_issue_8_scenarios.py +0 -335
- spice_mcp-0.1.4/test_mcp_cursor/test_real_api.py +0 -394
- spice_mcp-0.1.4/tests/integration/test_spellbook_discovery.py +0 -214
- spice_mcp-0.1.4/tests/offline/test_show_rewrite.py +0 -24
- spice_mcp-0.1.4/tests/scripts/comprehensive_test_runner.py +0 -164
- spice_mcp-0.1.4/tests/scripts/run_tests.py +0 -25
- spice_mcp-0.1.4/tests/scripts/test_api_health.py +0 -424
- spice_mcp-0.1.4/tests/scripts/test_cache_functionality.py +0 -197
- spice_mcp-0.1.4/tests/scripts/test_data_types.py +0 -727
- spice_mcp-0.1.4/tests/scripts/test_dune_connectivity.py +0 -93
- spice_mcp-0.1.4/tests/scripts/test_dune_query_execution.py +0 -170
- spice_mcp-0.1.4/tests/scripts/test_error_handling.py +0 -200
- spice_mcp-0.1.4/tests/scripts/test_mcp_simulation.py +0 -1166
- spice_mcp-0.1.4/tests/scripts/test_mcp_tools.py +0 -133
- spice_mcp-0.1.4/tests/scripts/test_performance.py +0 -654
- spice_mcp-0.1.4/tests/scripts/test_query_lifecycle.py +0 -534
- spice_mcp-0.1.4/tests/scripts/test_resilience.py +0 -945
- spice_mcp-0.1.4/tests/scripts/test_resource_management.py +0 -376
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/.python-version +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/CONTRIBUTING.md +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/LICENSE +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/docs/architecture.md +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/docs/codex_cli_tools.md +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/docs/config.md +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/docs/development.md +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/docs/dune_api.md +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/docs/index.md +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/docs/installation.md +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/pytest.ini +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/admin.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/cache.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/extract.py +1 -1
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/helpers.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/query_wrapper.py +1 -1
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/transport.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/types.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/typing_utils.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/urls.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/dune/user_agent.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/http_client.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/adapters/spellbook/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/config.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/core/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/core/errors.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/core/models.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/core/ports.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/logging/query_history.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/mcp/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/mcp/tools/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/mcp/tools/base.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/observability/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/observability/logging.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/polars_utils.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/py.typed +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/service_layer/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/service_layer/discovery_service.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/service_layer/query_admin_service.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/src/spice_mcp/service_layer/query_service.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/cassettes/.gitkeep +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/config/environments.yaml +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/config/test_queries.yaml +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/conftest.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/fastmcp/test_dune_query_schema_validation.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/fastmcp/test_resources_and_validation.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/fastmcp/test_server_mcp_extras.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/http_stubbed/test_age.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/http_stubbed/test_errors.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/http_stubbed/test_pagination.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/integration/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/live/test_live_basic.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/live/test_live_sui.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/mcp/conftest.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/offline/test_cache.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/offline/test_cache_consistency.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/offline/test_discovery.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/offline/test_dune_adapter.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/offline/test_edge_cases.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/offline/test_parsing.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/offline/test_query_history.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/offline/test_timeout.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/offline/test_typing_utils.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/offline/test_urls.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/property/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/property/test_property_based.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/style/test_polars_lazy.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/support/api_client.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/support/helpers.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/support/query_factory.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/support/test_data.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/tools/test_execute_query_tool.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/tools/test_health_tool.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/tools/test_query_service.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/tools/test_schemas.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/validation/__init__.py +0 -0
- {spice_mcp-0.1.4 → spice_mcp-0.1.6}/tests/validation/test_error_messages.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spice-mcp
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: mcp server built ontop of dune api endpoint
|
|
5
5
|
Author-email: Evan-Kim2028 <ekcopersonal@gmail.com>
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Classifier: Operating System :: OS Independent
|
|
@@ -28,11 +28,14 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
|
|
29
29
|
An MCP server that provides AI agents with direct access to [Dune Analytics](https://dune.com/) data. Execute queries, discover schemas and tables, and manage saved queries—all through a clean, type-safe interface optimized for AI workflows.
|
|
30
30
|
|
|
31
|
+
**Discover High-Quality Tables**: Leverages [Dune Spellbook](https://github.com/duneanalytics/spellbook), Dune's official GitHub repository of curated dbt models, to surface verified, production-ready tables with rich metadata.
|
|
32
|
+
|
|
31
33
|
## Why spice-mcp?
|
|
32
34
|
|
|
33
35
|
- **Agent-friendly**: Designed for AI agents using the Model Context Protocol (MCP)
|
|
36
|
+
- **High-Quality Discovery**: Leverages Dune Spellbook's GitHub repository to find verified, production-ready tables with rich metadata
|
|
34
37
|
- **Efficient**: Polars-first pipeline keeps data lazy until needed, reducing memory usage
|
|
35
|
-
- **Discovery**: Built-in tools to explore Dune's extensive blockchain datasets
|
|
38
|
+
- **Discovery**: Built-in tools to explore Dune's extensive blockchain datasets from both Dune API and Spellbook
|
|
36
39
|
- **Type-safe**: Fully typed parameters and responses with FastMCP
|
|
37
40
|
- **Reproducible**: Automatic query history logging and SQL artifact storage
|
|
38
41
|
|
|
@@ -73,10 +76,8 @@ An MCP server that provides AI agents with direct access to [Dune Analytics](htt
|
|
|
73
76
|
|------|-------------|----------------|
|
|
74
77
|
| `dune_query` | Execute queries by ID, URL, or raw SQL | `query` (str), `parameters` (object), `limit` (int), `offset` (int), `format` (`preview\|raw\|metadata\|poll`), `refresh` (bool), `timeout_seconds` (float) |
|
|
75
78
|
| `dune_query_info` | Get metadata for a saved query | `query` (str - ID or URL) |
|
|
76
|
-
| `dune_discover` | Unified discovery across Dune API and Spellbook | `keyword` (str\|list), `schema` (str), `limit` (int), `source` (`dune\|spellbook\|both`), `include_columns` (bool) |
|
|
77
|
-
| `dune_find_tables` | Search schemas and list tables | `keyword` (str), `schema` (str), `limit` (int) |
|
|
79
|
+
| `dune_discover` | Unified discovery across Dune API and Spellbook (returns verified tables only). **Leverages Dune Spellbook GitHub repository** for high-quality, curated tables. | `keyword` (str\|list), `schema` (str), `limit` (int), `source` (`dune\|spellbook\|both`), `include_columns` (bool) |
|
|
78
80
|
| `dune_describe_table` | Get column metadata for a table | `schema` (str), `table` (str) |
|
|
79
|
-
| `spellbook_find_models` | Search Spellbook dbt models | `keyword` (str\|list), `schema` (str), `limit` (int), `include_columns` (bool) |
|
|
80
81
|
| `dune_health_check` | Verify API key and configuration | (no parameters) |
|
|
81
82
|
| `dune_query_create` | Create a new saved query | `name` (str), `query_sql` (str), `description` (str), `tags` (list), `parameters` (list) |
|
|
82
83
|
| `dune_query_update` | Update an existing saved query | `query_id` (int), `name` (str), `query_sql` (str), `description` (str), `tags` (list), `parameters` (list) |
|
|
@@ -91,6 +92,17 @@ An MCP server that provides AI agents with direct access to [Dune Analytics](htt
|
|
|
91
92
|
|
|
92
93
|
[Dune](https://dune.com/) is a crypto data platform providing curated blockchain datasets and a public API. It aggregates on-chain data from Ethereum, Solana, Polygon, and other chains into queryable SQL tables. See the [Dune Docs](https://dune.com/docs) for more information.
|
|
93
94
|
|
|
95
|
+
## What is Dune Spellbook?
|
|
96
|
+
|
|
97
|
+
[Dune Spellbook](https://github.com/duneanalytics/spellbook) is Dune's official GitHub repository containing thousands of curated dbt models. These models represent high-quality, production-ready tables that are:
|
|
98
|
+
|
|
99
|
+
- **Verified**: All tables are verified to exist in Dune before being returned
|
|
100
|
+
- **Well-documented**: Rich metadata including column descriptions and types
|
|
101
|
+
- **Maintained**: Regularly updated by the Dune community and team
|
|
102
|
+
- **Production-ready**: Used by analysts and dashboards across the ecosystem
|
|
103
|
+
|
|
104
|
+
spice-mcp automatically clones and parses the Spellbook repository to discover these high-quality tables, parsing dbt config blocks to resolve actual Dune table names and verifying their existence before returning them to you.
|
|
105
|
+
|
|
94
106
|
## Installation
|
|
95
107
|
|
|
96
108
|
**From PyPI** (recommended):
|
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
An MCP server that provides AI agents with direct access to [Dune Analytics](https://dune.com/) data. Execute queries, discover schemas and tables, and manage saved queries—all through a clean, type-safe interface optimized for AI workflows.
|
|
9
9
|
|
|
10
|
+
**Discover High-Quality Tables**: Leverages [Dune Spellbook](https://github.com/duneanalytics/spellbook), Dune's official GitHub repository of curated dbt models, to surface verified, production-ready tables with rich metadata.
|
|
11
|
+
|
|
10
12
|
## Why spice-mcp?
|
|
11
13
|
|
|
12
14
|
- **Agent-friendly**: Designed for AI agents using the Model Context Protocol (MCP)
|
|
15
|
+
- **High-Quality Discovery**: Leverages Dune Spellbook's GitHub repository to find verified, production-ready tables with rich metadata
|
|
13
16
|
- **Efficient**: Polars-first pipeline keeps data lazy until needed, reducing memory usage
|
|
14
|
-
- **Discovery**: Built-in tools to explore Dune's extensive blockchain datasets
|
|
17
|
+
- **Discovery**: Built-in tools to explore Dune's extensive blockchain datasets from both Dune API and Spellbook
|
|
15
18
|
- **Type-safe**: Fully typed parameters and responses with FastMCP
|
|
16
19
|
- **Reproducible**: Automatic query history logging and SQL artifact storage
|
|
17
20
|
|
|
@@ -52,10 +55,8 @@ An MCP server that provides AI agents with direct access to [Dune Analytics](htt
|
|
|
52
55
|
|------|-------------|----------------|
|
|
53
56
|
| `dune_query` | Execute queries by ID, URL, or raw SQL | `query` (str), `parameters` (object), `limit` (int), `offset` (int), `format` (`preview\|raw\|metadata\|poll`), `refresh` (bool), `timeout_seconds` (float) |
|
|
54
57
|
| `dune_query_info` | Get metadata for a saved query | `query` (str - ID or URL) |
|
|
55
|
-
| `dune_discover` | Unified discovery across Dune API and Spellbook | `keyword` (str\|list), `schema` (str), `limit` (int), `source` (`dune\|spellbook\|both`), `include_columns` (bool) |
|
|
56
|
-
| `dune_find_tables` | Search schemas and list tables | `keyword` (str), `schema` (str), `limit` (int) |
|
|
58
|
+
| `dune_discover` | Unified discovery across Dune API and Spellbook (returns verified tables only). **Leverages Dune Spellbook GitHub repository** for high-quality, curated tables. | `keyword` (str\|list), `schema` (str), `limit` (int), `source` (`dune\|spellbook\|both`), `include_columns` (bool) |
|
|
57
59
|
| `dune_describe_table` | Get column metadata for a table | `schema` (str), `table` (str) |
|
|
58
|
-
| `spellbook_find_models` | Search Spellbook dbt models | `keyword` (str\|list), `schema` (str), `limit` (int), `include_columns` (bool) |
|
|
59
60
|
| `dune_health_check` | Verify API key and configuration | (no parameters) |
|
|
60
61
|
| `dune_query_create` | Create a new saved query | `name` (str), `query_sql` (str), `description` (str), `tags` (list), `parameters` (list) |
|
|
61
62
|
| `dune_query_update` | Update an existing saved query | `query_id` (int), `name` (str), `query_sql` (str), `description` (str), `tags` (list), `parameters` (list) |
|
|
@@ -70,6 +71,17 @@ An MCP server that provides AI agents with direct access to [Dune Analytics](htt
|
|
|
70
71
|
|
|
71
72
|
[Dune](https://dune.com/) is a crypto data platform providing curated blockchain datasets and a public API. It aggregates on-chain data from Ethereum, Solana, Polygon, and other chains into queryable SQL tables. See the [Dune Docs](https://dune.com/docs) for more information.
|
|
72
73
|
|
|
74
|
+
## What is Dune Spellbook?
|
|
75
|
+
|
|
76
|
+
[Dune Spellbook](https://github.com/duneanalytics/spellbook) is Dune's official GitHub repository containing thousands of curated dbt models. These models represent high-quality, production-ready tables that are:
|
|
77
|
+
|
|
78
|
+
- **Verified**: All tables are verified to exist in Dune before being returned
|
|
79
|
+
- **Well-documented**: Rich metadata including column descriptions and types
|
|
80
|
+
- **Maintained**: Regularly updated by the Dune community and team
|
|
81
|
+
- **Production-ready**: Used by analysts and dashboards across the ecosystem
|
|
82
|
+
|
|
83
|
+
spice-mcp automatically clones and parses the Spellbook repository to discover these high-quality tables, parsing dbt config blocks to resolve actual Dune table names and verifying their existence before returning them to you.
|
|
84
|
+
|
|
73
85
|
## Installation
|
|
74
86
|
|
|
75
87
|
**From PyPI** (recommended):
|
|
@@ -54,8 +54,8 @@ Verify configuration
|
|
|
54
54
|
- `codex mcp list` should list `spice_mcp_beta` with the python command and no secrets in Env.
|
|
55
55
|
|
|
56
56
|
Try some tools
|
|
57
|
-
- Find schemas
|
|
58
|
-
- `
|
|
57
|
+
- Find schemas and tables
|
|
58
|
+
- `mcp__spice_mcp_beta__dune_discover {"keyword": "dex", "source": "dune"}`
|
|
59
59
|
- Describe a table
|
|
60
60
|
- `mcp__spice_mcp_beta__dune_describe_table {"schema": "dex", "table": "trades"}`
|
|
61
61
|
- Query preview (with metadata/pagination)
|
|
@@ -2,20 +2,24 @@ Catalog Discovery
|
|
|
2
2
|
|
|
3
3
|
Summary
|
|
4
4
|
- There is no public REST endpoint to browse the full catalog. Discovery is best achieved using Dune SQL primitives and fallback probes.
|
|
5
|
+
- **Native SHOW statements are preferred** - they're faster than information_schema queries. See issue #10 for details.
|
|
5
6
|
|
|
6
7
|
Approach
|
|
7
8
|
- Schemas
|
|
8
9
|
- SHOW SCHEMAS
|
|
9
10
|
- SHOW SCHEMAS LIKE '%keyword%'
|
|
11
|
+
- ⚠️ Avoid: information_schema.schemata (slower, causes lag)
|
|
10
12
|
- Tables
|
|
11
13
|
- SHOW TABLES FROM <schema>
|
|
12
14
|
- If SHOW is blocked, probe candidate names via SELECT 1 FROM <schema>.<table> LIMIT 1
|
|
15
|
+
- ⚠️ Avoid: information_schema.tables (slower, causes lag)
|
|
13
16
|
- Columns
|
|
14
17
|
- SHOW COLUMNS FROM <schema>.<table>
|
|
15
18
|
- Fallback: SELECT * FROM <schema>.<table> LIMIT 1, infer columns and Polars dtypes client-side
|
|
16
|
-
- INFORMATION_SCHEMA
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
+
- INFORMATION_SCHEMA (Deprecated)
|
|
20
|
+
- Previously used for portability, but causes performance issues
|
|
21
|
+
- Native SHOW statements are now used directly (faster, no lag)
|
|
22
|
+
- Kept as fallback only if SHOW is blocked
|
|
19
23
|
|
|
20
24
|
|
|
21
25
|
Helpers in this repo
|
|
@@ -23,5 +27,8 @@ Helpers in this repo
|
|
|
23
27
|
- `find_schemas(keyword)`, `list_tables(schema, limit)`, `describe_table(schema, table)`
|
|
24
28
|
|
|
25
29
|
MCP Tools
|
|
26
|
-
-
|
|
30
|
+
- dune_discover: **PRIMARY discovery tool** - unified search across Dune API and Spellbook, returns verified tables only
|
|
31
|
+
- Automatically parses dbt configs from Spellbook models to resolve actual Dune table names
|
|
32
|
+
- Verifies tables exist in Dune before returning (uses persistent cache)
|
|
33
|
+
- Filters out non-existent tables
|
|
27
34
|
- dune_describe_table: describe columns with SHOW + fallback
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Tools Reference
|
|
2
|
+
|
|
3
|
+
All tools are exposed by the MCP server started with `spice-mcp`.
|
|
4
|
+
|
|
5
|
+
1) dune_discover
|
|
6
|
+
- Purpose: **PRIMARY discovery tool** for finding tables in Dune. Searches both Dune API and Spellbook repository. Returns ONLY verified, queryable tables.
|
|
7
|
+
- ⚠️ **IMPORTANT**: Always use this tool instead of querying `information_schema` directly (which is slow and causes lag).
|
|
8
|
+
- Input schema:
|
|
9
|
+
- keyword?: string | string[] — search term(s) to find schemas/tables (e.g., "walrus", ["layerzero", "dex"])
|
|
10
|
+
- schema?: string — schema name to list tables from (e.g., "dex", "sui_walrus")
|
|
11
|
+
- limit?: integer (default 50) — maximum number of tables to return
|
|
12
|
+
- source?: 'dune' | 'spellbook' | 'both' (default 'both') — where to search
|
|
13
|
+
- include_columns?: boolean (default true) — include column details for Spellbook models
|
|
14
|
+
- Output fields:
|
|
15
|
+
- schemas: string[] — matching schema names
|
|
16
|
+
- tables: array of table objects, each with:
|
|
17
|
+
- schema: string — Spellbook subproject name (for Spellbook models) or Dune schema name
|
|
18
|
+
- table: string — Spellbook model name (for Spellbook models) or Dune table name
|
|
19
|
+
- fully_qualified_name: string — schema.table format
|
|
20
|
+
- source: 'dune' | 'spellbook'
|
|
21
|
+
- dune_schema?: string — actual Dune schema name (for Spellbook models, parsed from dbt config)
|
|
22
|
+
- dune_alias?: string — actual Dune table alias (for Spellbook models, parsed from dbt config)
|
|
23
|
+
- dune_table?: string — verified, queryable Dune table name (e.g., "sui_walrus.base_table")
|
|
24
|
+
- verified?: boolean — true (all returned tables are verified to exist)
|
|
25
|
+
- columns?: array — column details (if include_columns=true)
|
|
26
|
+
- source: string — the source parameter used
|
|
27
|
+
- message?: string — helpful message if no tables found
|
|
28
|
+
- Features:
|
|
29
|
+
- Automatically parses dbt configs from Spellbook models to resolve actual Dune table names
|
|
30
|
+
- Verifies tables exist in Dune before returning (uses persistent cache)
|
|
31
|
+
- Filters out non-existent tables
|
|
32
|
+
- Examples:
|
|
33
|
+
- Search for walrus tables (returns verified tables only):
|
|
34
|
+
- `dune_discover {"keyword":"walrus"}`
|
|
35
|
+
- → Returns tables with `dune_table` field like "sui_walrus.base_table"
|
|
36
|
+
- Use discovered table to query:
|
|
37
|
+
- `dune_query {"query":"SELECT * FROM sui_walrus.base_table LIMIT 10"}`
|
|
38
|
+
- Search only Spellbook:
|
|
39
|
+
- `dune_discover {"keyword":["layerzero","bridge"],"source":"spellbook"}`
|
|
40
|
+
- List tables in a schema:
|
|
41
|
+
- `dune_discover {"schema":"dex"}`
|
|
42
|
+
|
|
43
|
+
2) dune_query
|
|
44
|
+
- Purpose: Execute Dune queries (ID, URL, raw SQL) and return a compact preview plus Dune metadata/pagination hints.
|
|
45
|
+
- ⚠️ **IMPORTANT**: Always use `dune_discover` FIRST to find verified table names. Do not guess table names or query `information_schema` directly.
|
|
46
|
+
- Input schema:
|
|
47
|
+
- query: string (required) — Query ID, URL, or raw SQL using tables from `dune_discover`
|
|
48
|
+
- parameters?: object
|
|
49
|
+
- performance?: 'medium' | 'large'
|
|
50
|
+
- limit?: integer, offset?: integer, sort_by?: string, columns?: string[], sample_count?: integer
|
|
51
|
+
- refresh?: boolean, max_age?: number, timeout_seconds?: number
|
|
52
|
+
- format?: 'preview' | 'raw' | 'metadata' | 'poll' (preview by default)
|
|
53
|
+
- extras?: object (e.g., allow_partial_results, ignore_max_datapoints_per_request)
|
|
54
|
+
- Output fields:
|
|
55
|
+
- type: 'preview' | 'metadata' | 'raw' | 'execution'
|
|
56
|
+
- rowcount: number, columns: string[]
|
|
57
|
+
- data_preview: object[] (first rows)
|
|
58
|
+
- execution_id: string, duration_ms: number
|
|
59
|
+
- metadata?: structured Dune metadata / execution state / error hints
|
|
60
|
+
- next_uri?: string, next_offset?: number
|
|
61
|
+
- Errors: `{ "ok": false, "error": { code, message, data: { suggestions }, context? } }`
|
|
62
|
+
- Examples:
|
|
63
|
+
- Workflow: discover → query:
|
|
64
|
+
- `dune_discover {"keyword":"walrus"}` → get `dune_table="sui_walrus.base_table"`
|
|
65
|
+
- `dune_query {"query":"SELECT * FROM sui_walrus.base_table LIMIT 10"}`
|
|
66
|
+
- Preview latest metadata without rows:
|
|
67
|
+
- `dune_query {"query":"4388","format":"metadata"}`
|
|
68
|
+
- Preview first rows and metadata:
|
|
69
|
+
- `dune_query {"query":"4388","limit":10}`
|
|
70
|
+
|
|
71
|
+
Logging & Artifacts
|
|
72
|
+
- Successful calls are written to a JSONL audit log (see `docs/config.md` for path configuration via `QueryHistory`).
|
|
73
|
+
- The canonical SQL is stored as a deduplicated artefact keyed by SHA‑256 (for raw SQL and query IDs/URLs), enabling reproducibility and offline review.
|
|
74
|
+
- Result caching is handled by `adapters.dune.cache` (parquet files) and can be tuned via `SPICE_CACHE_*` environment variables.
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
3) dune_describe_table
|
|
78
|
+
- Purpose: Describe columns for a schema.table (SHOW + fallback to 1-row SELECT inference).
|
|
79
|
+
- Input schema:
|
|
80
|
+
- schema: string
|
|
81
|
+
- table: string
|
|
82
|
+
- Output fields:
|
|
83
|
+
- columns: [{ name, dune_type?, polars_dtype?, extra?, comment? }]
|
|
84
|
+
- Errors follow the standard MCP envelope.
|
|
85
|
+
|
|
86
|
+
3) dune_health_check
|
|
87
|
+
- Purpose: Basic environment and logging readiness check.
|
|
88
|
+
- Output fields: ok, api_key_present, status
|
|
89
|
+
|
|
90
|
+
4) dune_query_info
|
|
91
|
+
- Purpose: Fetch Dune query object metadata (name, description, tags, parameter schema, SQL).
|
|
92
|
+
- Input schema:
|
|
93
|
+
- query: string — ID or URL
|
|
94
|
+
- Output fields:
|
|
95
|
+
- ok: boolean, status: number, query_id: number
|
|
96
|
+
- name?: string, description?: string, tags?: string[], parameters?: object[], version?: number, query_sql?: string
|
|
97
|
+
|
|
98
|
+
5) dune_query_create
|
|
99
|
+
- Purpose: Create a saved Dune query.
|
|
100
|
+
- Input schema:
|
|
101
|
+
- name: string (required)
|
|
102
|
+
- query_sql: string (required)
|
|
103
|
+
- description?: string, tags?: string[], parameters?: object[]
|
|
104
|
+
- Output: Dune query object
|
|
105
|
+
|
|
106
|
+
6) dune_query_update
|
|
107
|
+
- Purpose: Update a saved Dune query.
|
|
108
|
+
- Input schema:
|
|
109
|
+
- query_id: integer (required)
|
|
110
|
+
- name?: string, query_sql?: string, description?: string, tags?: string[], parameters?: object[]
|
|
111
|
+
- Output: Dune query object
|
|
112
|
+
|
|
113
|
+
7) dune_query_fork
|
|
114
|
+
- Purpose: Fork an existing saved Dune query.
|
|
115
|
+
- Input schema:
|
|
116
|
+
- source_query_id: integer (required)
|
|
117
|
+
- name?: string (new name)
|
|
118
|
+
- Output: Dune query object
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "spice-mcp"
|
|
3
|
-
version = "0.1.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.1.6"
|
|
4
|
+
description = "mcp server built ontop of dune api endpoint"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Evan-Kim2028", email = "ekcopersonal@gmail.com" }
|
|
7
7
|
]
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
import re
|
|
5
4
|
import time
|
|
6
5
|
from collections.abc import Mapping, Sequence
|
|
7
6
|
from typing import Any
|
|
@@ -47,10 +46,9 @@ class DuneAdapter(QueryExecutor, CatalogExplorer):
|
|
|
47
46
|
self._ensure_api_key()
|
|
48
47
|
start = time.time()
|
|
49
48
|
q = request.query
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
q = q_rewritten
|
|
49
|
+
# Use native SHOW statements directly - they're faster than information_schema queries
|
|
50
|
+
# See issue #10: https://github.com/Evan-Kim2028/spice-mcp/issues/10
|
|
51
|
+
# Removed rewrite to avoid performance issues with information_schema queries
|
|
54
52
|
result = _execute_dune_query(
|
|
55
53
|
query_or_execution=q,
|
|
56
54
|
verbose=False,
|
|
@@ -200,7 +198,9 @@ class DuneAdapter(QueryExecutor, CatalogExplorer):
|
|
|
200
198
|
# Internal helpers --------------------------------------------------------------
|
|
201
199
|
def _run_sql(self, sql: str, *, limit: int | None = None) -> pl.DataFrame:
|
|
202
200
|
self._ensure_api_key()
|
|
203
|
-
|
|
201
|
+
# Use native SHOW statements directly - they're faster than information_schema queries
|
|
202
|
+
# See issue #10: https://github.com/Evan-Kim2028/spice-mcp/issues/10
|
|
203
|
+
sql_eff = sql
|
|
204
204
|
df = _execute_dune_query(
|
|
205
205
|
query_or_execution=sql_eff,
|
|
206
206
|
verbose=False,
|
|
@@ -233,28 +233,12 @@ def _build_preview(lf: pl.LazyFrame, columns: list[str], rowcount: int) -> Resul
|
|
|
233
233
|
|
|
234
234
|
|
|
235
235
|
def _maybe_rewrite_show_sql(sql: str) -> str | None:
|
|
236
|
-
"""
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
236
|
+
"""DEPRECATED: This function is no longer used.
|
|
237
|
+
|
|
238
|
+
Native SHOW statements are now used directly as they're faster than
|
|
239
|
+
information_schema queries in Dune. See issue #10 for details.
|
|
240
|
+
|
|
241
|
+
This function is kept for backward compatibility but is not called.
|
|
240
242
|
"""
|
|
241
|
-
|
|
242
|
-
m = re.match(r"^SHOW\s+SCHEMAS\s+LIKE\s+'([^']+)'\s*;?$", s, flags=re.IGNORECASE)
|
|
243
|
-
if m:
|
|
244
|
-
pat = m.group(1)
|
|
245
|
-
return (
|
|
246
|
-
"SELECT schema_name AS Schema FROM information_schema.schemata "
|
|
247
|
-
f"WHERE schema_name LIKE '{pat}'"
|
|
248
|
-
)
|
|
249
|
-
if re.match(r"^SHOW\s+SCHEMAS\s*;?$", s, flags=re.IGNORECASE):
|
|
250
|
-
return "SELECT schema_name AS Schema FROM information_schema.schemata"
|
|
251
|
-
|
|
252
|
-
m = re.match(r"^SHOW\s+TABLES\s+FROM\s+([A-Za-z0-9_\.]+)\s*;?$", s, flags=re.IGNORECASE)
|
|
253
|
-
if m:
|
|
254
|
-
schema = m.group(1)
|
|
255
|
-
return (
|
|
256
|
-
"SELECT table_name AS Table FROM information_schema.tables "
|
|
257
|
-
f"WHERE table_schema = '{schema}'"
|
|
258
|
-
)
|
|
259
|
-
|
|
243
|
+
# Function body kept for reference but not executed
|
|
260
244
|
return None
|
|
@@ -133,11 +133,43 @@ class SpellbookExplorer(CatalogExplorer):
|
|
|
133
133
|
if not schema_yml.exists():
|
|
134
134
|
schema_yml = sql_file.parent.parent / "schema.yml"
|
|
135
135
|
|
|
136
|
+
# Parse dbt config to get actual Dune table name
|
|
137
|
+
config = self._parse_dbt_config(sql_file)
|
|
138
|
+
|
|
139
|
+
# Ignore templated dbt config values like "{{ target.schema }}"
|
|
140
|
+
def _is_templated(val: Any) -> bool:
|
|
141
|
+
try:
|
|
142
|
+
s = str(val)
|
|
143
|
+
except Exception:
|
|
144
|
+
return False
|
|
145
|
+
return "{{" in s and "}}" in s
|
|
146
|
+
|
|
147
|
+
raw_schema = config.get("schema")
|
|
148
|
+
raw_alias = config.get("alias")
|
|
149
|
+
|
|
150
|
+
dune_schema = (
|
|
151
|
+
raw_schema.strip() if isinstance(raw_schema, str) else raw_schema
|
|
152
|
+
)
|
|
153
|
+
dune_alias = (
|
|
154
|
+
raw_alias.strip() if isinstance(raw_alias, str) else raw_alias
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Fall back to original names when values are templated or empty
|
|
158
|
+
if not dune_schema or _is_templated(dune_schema):
|
|
159
|
+
dune_schema = schema_name
|
|
160
|
+
if not dune_alias or _is_templated(dune_alias):
|
|
161
|
+
dune_alias = model_name
|
|
162
|
+
|
|
163
|
+
dune_table = f"{dune_schema}.{dune_alias}"
|
|
164
|
+
|
|
136
165
|
models[schema_name].append({
|
|
137
166
|
"name": model_name,
|
|
138
167
|
"file": sql_file,
|
|
139
168
|
"schema_yml": schema_yml if schema_yml.exists() else None,
|
|
140
169
|
"schema": schema_name,
|
|
170
|
+
"dune_schema": dune_schema,
|
|
171
|
+
"dune_alias": dune_alias,
|
|
172
|
+
"dune_table": dune_table,
|
|
141
173
|
})
|
|
142
174
|
|
|
143
175
|
self._models_cache = models
|
|
@@ -264,14 +296,70 @@ class SpellbookExplorer(CatalogExplorer):
|
|
|
264
296
|
|
|
265
297
|
return []
|
|
266
298
|
|
|
299
|
+
def _parse_dbt_config(self, sql_file: Path) -> dict[str, str]:
|
|
300
|
+
"""
|
|
301
|
+
Parse dbt config block from SQL file to extract schema and alias.
|
|
302
|
+
|
|
303
|
+
Looks for patterns like:
|
|
304
|
+
{{ config(schema='sui_walrus', alias='base_table') }}
|
|
305
|
+
{{ config(schema="sui_walrus", alias="base_table") }}
|
|
306
|
+
|
|
307
|
+
Returns dict with 'schema' and 'alias' keys, or empty dict if not found.
|
|
308
|
+
"""
|
|
309
|
+
try:
|
|
310
|
+
with open(sql_file, encoding="utf-8") as f:
|
|
311
|
+
sql = f.read()
|
|
312
|
+
|
|
313
|
+
# Match dbt config block: {{ config(...) }}
|
|
314
|
+
# Use non-greedy match to get first config block
|
|
315
|
+
config_match = re.search(
|
|
316
|
+
r"{{\s*config\s*\((.*?)\)\s*}}",
|
|
317
|
+
sql,
|
|
318
|
+
re.IGNORECASE | re.DOTALL,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
if not config_match:
|
|
322
|
+
return {}
|
|
323
|
+
|
|
324
|
+
config_content = config_match.group(1)
|
|
325
|
+
result: dict[str, str] = {}
|
|
326
|
+
|
|
327
|
+
# Extract schema parameter (supports single and double quotes)
|
|
328
|
+
schema_match = re.search(
|
|
329
|
+
r"schema\s*=\s*['\"]([^'\"]+)['\"]",
|
|
330
|
+
config_content,
|
|
331
|
+
re.IGNORECASE,
|
|
332
|
+
)
|
|
333
|
+
if schema_match:
|
|
334
|
+
result["schema"] = schema_match.group(1)
|
|
335
|
+
|
|
336
|
+
# Extract alias parameter (supports single and double quotes)
|
|
337
|
+
alias_match = re.search(
|
|
338
|
+
r"alias\s*=\s*['\"]([^'\"]+)['\"]",
|
|
339
|
+
config_content,
|
|
340
|
+
re.IGNORECASE,
|
|
341
|
+
)
|
|
342
|
+
if alias_match:
|
|
343
|
+
result["alias"] = alias_match.group(1)
|
|
344
|
+
|
|
345
|
+
return result
|
|
346
|
+
except Exception:
|
|
347
|
+
# On any error (file read, parsing, etc.), return empty dict
|
|
348
|
+
# This allows fallback to using schema_name and model_name
|
|
349
|
+
return {}
|
|
350
|
+
|
|
267
351
|
def _parse_sql_columns(self, sql_file: Path) -> list[TableColumn]:
|
|
268
|
-
"""
|
|
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
|
+
"""
|
|
269
358
|
try:
|
|
270
359
|
with open(sql_file, encoding="utf-8") as f:
|
|
271
360
|
sql = f.read()
|
|
272
361
|
|
|
273
|
-
# Look for SELECT ... FROM patterns
|
|
274
|
-
# Match: SELECT col1, col2, col3 FROM ...
|
|
362
|
+
# Look for SELECT ... FROM patterns (simple heuristic)
|
|
275
363
|
select_match = re.search(
|
|
276
364
|
r"SELECT\s+(.+?)\s+FROM",
|
|
277
365
|
sql,
|
|
@@ -280,27 +368,20 @@ class SpellbookExplorer(CatalogExplorer):
|
|
|
280
368
|
|
|
281
369
|
if select_match:
|
|
282
370
|
cols_str = select_match.group(1)
|
|
283
|
-
#
|
|
371
|
+
# Simple split - may not handle all nested cases perfectly
|
|
372
|
+
# This is OK since column info is optional and best-effort
|
|
284
373
|
cols = []
|
|
285
374
|
for col in cols_str.split(","):
|
|
286
375
|
col = col.strip()
|
|
287
|
-
#
|
|
288
|
-
|
|
289
|
-
col = col.split(" AS ", 1)[0].strip()
|
|
290
|
-
elif " " in col and not col.startswith("("):
|
|
291
|
-
# Might be alias without AS
|
|
292
|
-
parts = col.split()
|
|
293
|
-
col = parts[0].strip()
|
|
294
|
-
|
|
295
|
-
# Clean up function calls: function(col) -> col
|
|
296
|
-
col = re.sub(r"^\w+\((.+)\)", r"\1", col)
|
|
376
|
+
# Basic cleanup - remove obvious SQL noise
|
|
377
|
+
col = col.split()[-1] if col else ""
|
|
297
378
|
col = col.strip().strip('"').strip("'")
|
|
298
379
|
|
|
299
|
-
if col and col not in ["*", "DISTINCT"]:
|
|
380
|
+
if col and col not in ["*", "DISTINCT", "FROM"]:
|
|
300
381
|
cols.append(
|
|
301
382
|
TableColumn(
|
|
302
383
|
name=col,
|
|
303
|
-
dune_type="VARCHAR",
|
|
384
|
+
dune_type="VARCHAR",
|
|
304
385
|
polars_dtype="Utf8",
|
|
305
386
|
)
|
|
306
387
|
)
|
|
@@ -310,4 +391,3 @@ class SpellbookExplorer(CatalogExplorer):
|
|
|
310
391
|
pass
|
|
311
392
|
|
|
312
393
|
return []
|
|
313
|
-
|