spice-mcp 0.1.2__tar.gz → 0.1.4__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.2 → spice_mcp-0.1.4}/.gitignore +3 -0
- spice_mcp-0.1.4/PKG-INFO +121 -0
- spice_mcp-0.1.4/README.md +100 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/docs/architecture.md +3 -4
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/docs/codex_cli.md +7 -13
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/docs/config.md +1 -1
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/docs/development.md +3 -3
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/docs/discovery.md +0 -4
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/docs/index.md +1 -1
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/docs/tools.md +1 -14
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/pyproject.toml +1 -1
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/dune/__init__.py +4 -2
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/dune/cache.py +2 -34
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/dune/client.py +9 -4
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/dune/extract.py +79 -633
- spice_mcp-0.1.4/src/spice_mcp/adapters/dune/query_wrapper.py +86 -0
- spice_mcp-0.1.4/src/spice_mcp/adapters/dune/user_agent.py +9 -0
- spice_mcp-0.1.4/src/spice_mcp/adapters/spellbook/__init__.py +6 -0
- spice_mcp-0.1.4/src/spice_mcp/adapters/spellbook/explorer.py +313 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/config.py +1 -1
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/core/models.py +0 -8
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/core/ports.py +0 -15
- spice_mcp-0.1.4/src/spice_mcp/mcp/server.py +797 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/mcp/tools/base.py +1 -1
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/mcp/tools/execute_query.py +32 -63
- spice_mcp-0.1.4/test_mcp_cursor/.env.example +9 -0
- spice_mcp-0.1.4/test_mcp_cursor/CURSOR_SETUP.md +159 -0
- spice_mcp-0.1.4/test_mcp_cursor/INSTALL.md +71 -0
- spice_mcp-0.1.4/test_mcp_cursor/QUICK_START.md +96 -0
- spice_mcp-0.1.4/test_mcp_cursor/README.md +38 -0
- spice_mcp-0.1.4/test_mcp_cursor/TEST_RESULTS.md +50 -0
- spice_mcp-0.1.4/test_mcp_cursor/cursor_mcp_config.json +11 -0
- spice_mcp-0.1.4/test_mcp_cursor/setup_cursor_mcp.sh +58 -0
- spice_mcp-0.1.4/test_mcp_cursor/test_issue_8_scenarios.py +335 -0
- spice_mcp-0.1.4/test_mcp_cursor/test_real_api.py +394 -0
- spice_mcp-0.1.4/tests/fastmcp/test_dune_query_schema_validation.py +139 -0
- spice_mcp-0.1.4/tests/fastmcp/test_resources_and_validation.py +58 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/fastmcp/test_server_fastmcp.py +34 -27
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/fastmcp/test_server_mcp_extras.py +1 -3
- spice_mcp-0.1.4/tests/integration/__init__.py +2 -0
- spice_mcp-0.1.4/tests/integration/test_spellbook_discovery.py +214 -0
- spice_mcp-0.1.4/tests/integration/test_user_journeys.py +327 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/mcp/conftest.py +2 -21
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/mcp/test_tool_contracts.py +0 -16
- spice_mcp-0.1.4/tests/offline/test_cache_consistency.py +253 -0
- spice_mcp-0.1.4/tests/offline/test_edge_cases.py +256 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/offline/test_typing_utils.py +4 -6
- spice_mcp-0.1.4/tests/property/__init__.py +2 -0
- spice_mcp-0.1.4/tests/property/test_property_based.py +195 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_mcp_simulation.py +0 -1
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_mcp_tools.py +4 -5
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/support/helpers.py +2 -7
- spice_mcp-0.1.4/tests/tools/test_additional_mcp_tools.py +117 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/tools/test_execute_query_tool.py +6 -15
- spice_mcp-0.1.4/tests/tools/test_unified_discover.py +183 -0
- spice_mcp-0.1.4/tests/validation/__init__.py +2 -0
- spice_mcp-0.1.4/tests/validation/test_error_messages.py +206 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/uv.lock +1 -1
- spice_mcp-0.1.2/PKG-INFO +0 -193
- spice_mcp-0.1.2/README.md +0 -172
- spice_mcp-0.1.2/debug_mcp_tool.py +0 -147
- spice_mcp-0.1.2/debug_template_query.py +0 -147
- spice_mcp-0.1.2/docs/commands.md +0 -41
- spice_mcp-0.1.2/issue_diagnostics.py +0 -76
- spice_mcp-0.1.2/src/spice_mcp/mcp/server.py +0 -527
- spice_mcp-0.1.2/src/spice_mcp/mcp/tools/sui_package_overview.py +0 -56
- spice_mcp-0.1.2/src/spice_mcp/service_layer/sui_service.py +0 -131
- spice_mcp-0.1.2/test_semaphore_fix.py +0 -101
- spice_mcp-0.1.2/test_sync_fix.py +0 -55
- spice_mcp-0.1.2/tests/fastmcp/test_resources_and_validation.py +0 -49
- spice_mcp-0.1.2/tests/mcp/test_resources.py +0 -53
- spice_mcp-0.1.2/tests/tools/test_additional_mcp_tools.py +0 -82
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/.python-version +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/CONTRIBUTING.md +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/LICENSE +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/docs/codex_cli_tools.md +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/docs/dune_api.md +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/docs/installation.md +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/pytest.ini +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/scripts/bridgez/make_circle_comparison.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/scripts/codex_tools_doctor.sh +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/__init__.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/__init__.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/dune/admin.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/dune/helpers.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/dune/transport.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/dune/types.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/dune/typing_utils.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/dune/urls.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/adapters/http_client.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/core/__init__.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/core/errors.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/logging/query_history.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/mcp/__init__.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/mcp/tools/__init__.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/observability/__init__.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/observability/logging.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/polars_utils.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/py.typed +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/service_layer/__init__.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/service_layer/discovery_service.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/service_layer/query_admin_service.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/src/spice_mcp/service_layer/query_service.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/cassettes/.gitkeep +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/config/environments.yaml +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/config/test_queries.yaml +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/conftest.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/http_stubbed/test_age.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/http_stubbed/test_errors.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/http_stubbed/test_pagination.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/live/test_live_basic.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/live/test_live_sui.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/offline/test_cache.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/offline/test_discovery.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/offline/test_dune_adapter.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/offline/test_parsing.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/offline/test_query_history.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/offline/test_show_rewrite.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/offline/test_timeout.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/offline/test_urls.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/comprehensive_test_runner.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/run_tests.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_api_health.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_cache_functionality.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_data_types.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_dune_connectivity.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_dune_query_execution.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_error_handling.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_performance.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_query_lifecycle.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_resilience.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/scripts/test_resource_management.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/style/test_polars_lazy.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/support/api_client.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/support/query_factory.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/support/test_data.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/tools/test_health_tool.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/tools/test_query_service.py +0 -0
- {spice_mcp-0.1.2 → spice_mcp-0.1.4}/tests/tools/test_schemas.py +0 -0
spice_mcp-0.1.4/PKG-INFO
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spice-mcp
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: MCP server for Dune Analytics data access
|
|
5
|
+
Author-email: Evan-Kim2028 <ekcopersonal@gmail.com>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Operating System :: OS Independent
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
|
+
Classifier: Typing :: Typed
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Requires-Dist: aiohttp>=3.9.5
|
|
14
|
+
Requires-Dist: fastmcp>=0.3.0
|
|
15
|
+
Requires-Dist: mcp>=0.9.0
|
|
16
|
+
Requires-Dist: polars>=1.35.1
|
|
17
|
+
Requires-Dist: requests>=2.31.0
|
|
18
|
+
Requires-Dist: rich-argparse>=1.5.2
|
|
19
|
+
Requires-Dist: rich>=13.3.3
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# spice-mcp
|
|
23
|
+
|
|
24
|
+
[](https://pypi.org/project/spice-mcp/)
|
|
25
|
+
<a href="https://glama.ai/mcp/servers/@Evan-Kim2028/spice-mcp">
|
|
26
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/@Evan-Kim2028/spice-mcp/badge" alt="Spice MCP server" />
|
|
27
|
+
</a>
|
|
28
|
+
|
|
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
|
+
|
|
31
|
+
## Why spice-mcp?
|
|
32
|
+
|
|
33
|
+
- **Agent-friendly**: Designed for AI agents using the Model Context Protocol (MCP)
|
|
34
|
+
- **Efficient**: Polars-first pipeline keeps data lazy until needed, reducing memory usage
|
|
35
|
+
- **Discovery**: Built-in tools to explore Dune's extensive blockchain datasets
|
|
36
|
+
- **Type-safe**: Fully typed parameters and responses with FastMCP
|
|
37
|
+
- **Reproducible**: Automatic query history logging and SQL artifact storage
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
1. **Install**:
|
|
42
|
+
```bash
|
|
43
|
+
uv pip install spice-mcp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
2. **Set API key** (choose one method):
|
|
47
|
+
- **Option A**: Create a `.env` file in your project root:
|
|
48
|
+
```bash
|
|
49
|
+
echo "DUNE_API_KEY=your-api-key-here" > .env
|
|
50
|
+
```
|
|
51
|
+
- **Option B**: Export in your shell:
|
|
52
|
+
```bash
|
|
53
|
+
export DUNE_API_KEY=your-api-key-here
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
3. **Use with Cursor IDE**:
|
|
57
|
+
Add to Cursor Settings → MCP Servers:
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"name": "spice-mcp",
|
|
61
|
+
"command": "spice-mcp",
|
|
62
|
+
"env": {
|
|
63
|
+
"DUNE_API_KEY": "your-dune-api-key-here"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Note**: Query history logging is enabled by default. Logs are saved to `logs/queries.jsonl` (or `~/.spice_mcp/logs/queries.jsonl` if not in a project directory). To customize paths, set `SPICE_QUERY_HISTORY` and `SPICE_ARTIFACT_ROOT` environment variables.
|
|
69
|
+
|
|
70
|
+
## Core Tools
|
|
71
|
+
|
|
72
|
+
| Tool | Description | Key Parameters |
|
|
73
|
+
|------|-------------|----------------|
|
|
74
|
+
| `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
|
+
| `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) |
|
|
78
|
+
| `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
|
+
| `dune_health_check` | Verify API key and configuration | (no parameters) |
|
|
81
|
+
| `dune_query_create` | Create a new saved query | `name` (str), `query_sql` (str), `description` (str), `tags` (list), `parameters` (list) |
|
|
82
|
+
| `dune_query_update` | Update an existing saved query | `query_id` (int), `name` (str), `query_sql` (str), `description` (str), `tags` (list), `parameters` (list) |
|
|
83
|
+
| `dune_query_fork` | Fork an existing saved query | `source_query_id` (int), `name` (str) |
|
|
84
|
+
|
|
85
|
+
## Resources
|
|
86
|
+
|
|
87
|
+
- `spice:history/tail/{n}` — View last N lines of query history (1-1000)
|
|
88
|
+
- `spice:artifact/{sha}` — Retrieve stored SQL by SHA-256 hash
|
|
89
|
+
|
|
90
|
+
## What is Dune?
|
|
91
|
+
|
|
92
|
+
[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
|
+
## Installation
|
|
95
|
+
|
|
96
|
+
**From PyPI** (recommended):
|
|
97
|
+
```bash
|
|
98
|
+
uv pip install spice-mcp
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**From source**:
|
|
102
|
+
```bash
|
|
103
|
+
git clone https://github.com/Evan-Kim2028/spice-mcp.git
|
|
104
|
+
cd spice-mcp
|
|
105
|
+
uv sync
|
|
106
|
+
uv pip install -e .
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Requirements**: Python 3.13+
|
|
110
|
+
|
|
111
|
+
## Documentation
|
|
112
|
+
|
|
113
|
+
- [Tool Reference](docs/tools.md) — Complete tool documentation with parameters
|
|
114
|
+
- [Architecture](docs/architecture.md) — Code structure and design patterns
|
|
115
|
+
- [Discovery Guide](docs/discovery.md) — How to explore Dune schemas and tables
|
|
116
|
+
- [Dune API Guide](docs/dune_api.md) — Understanding Dune's data structure
|
|
117
|
+
- [Configuration](docs/config.md) — Environment variables and settings
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
See [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# spice-mcp
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/spice-mcp/)
|
|
4
|
+
<a href="https://glama.ai/mcp/servers/@Evan-Kim2028/spice-mcp">
|
|
5
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/@Evan-Kim2028/spice-mcp/badge" alt="Spice MCP server" />
|
|
6
|
+
</a>
|
|
7
|
+
|
|
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
|
+
|
|
10
|
+
## Why spice-mcp?
|
|
11
|
+
|
|
12
|
+
- **Agent-friendly**: Designed for AI agents using the Model Context Protocol (MCP)
|
|
13
|
+
- **Efficient**: Polars-first pipeline keeps data lazy until needed, reducing memory usage
|
|
14
|
+
- **Discovery**: Built-in tools to explore Dune's extensive blockchain datasets
|
|
15
|
+
- **Type-safe**: Fully typed parameters and responses with FastMCP
|
|
16
|
+
- **Reproducible**: Automatic query history logging and SQL artifact storage
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
1. **Install**:
|
|
21
|
+
```bash
|
|
22
|
+
uv pip install spice-mcp
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
2. **Set API key** (choose one method):
|
|
26
|
+
- **Option A**: Create a `.env` file in your project root:
|
|
27
|
+
```bash
|
|
28
|
+
echo "DUNE_API_KEY=your-api-key-here" > .env
|
|
29
|
+
```
|
|
30
|
+
- **Option B**: Export in your shell:
|
|
31
|
+
```bash
|
|
32
|
+
export DUNE_API_KEY=your-api-key-here
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. **Use with Cursor IDE**:
|
|
36
|
+
Add to Cursor Settings → MCP Servers:
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"name": "spice-mcp",
|
|
40
|
+
"command": "spice-mcp",
|
|
41
|
+
"env": {
|
|
42
|
+
"DUNE_API_KEY": "your-dune-api-key-here"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Note**: Query history logging is enabled by default. Logs are saved to `logs/queries.jsonl` (or `~/.spice_mcp/logs/queries.jsonl` if not in a project directory). To customize paths, set `SPICE_QUERY_HISTORY` and `SPICE_ARTIFACT_ROOT` environment variables.
|
|
48
|
+
|
|
49
|
+
## Core Tools
|
|
50
|
+
|
|
51
|
+
| Tool | Description | Key Parameters |
|
|
52
|
+
|------|-------------|----------------|
|
|
53
|
+
| `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
|
+
| `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) |
|
|
57
|
+
| `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
|
+
| `dune_health_check` | Verify API key and configuration | (no parameters) |
|
|
60
|
+
| `dune_query_create` | Create a new saved query | `name` (str), `query_sql` (str), `description` (str), `tags` (list), `parameters` (list) |
|
|
61
|
+
| `dune_query_update` | Update an existing saved query | `query_id` (int), `name` (str), `query_sql` (str), `description` (str), `tags` (list), `parameters` (list) |
|
|
62
|
+
| `dune_query_fork` | Fork an existing saved query | `source_query_id` (int), `name` (str) |
|
|
63
|
+
|
|
64
|
+
## Resources
|
|
65
|
+
|
|
66
|
+
- `spice:history/tail/{n}` — View last N lines of query history (1-1000)
|
|
67
|
+
- `spice:artifact/{sha}` — Retrieve stored SQL by SHA-256 hash
|
|
68
|
+
|
|
69
|
+
## What is Dune?
|
|
70
|
+
|
|
71
|
+
[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
|
+
## Installation
|
|
74
|
+
|
|
75
|
+
**From PyPI** (recommended):
|
|
76
|
+
```bash
|
|
77
|
+
uv pip install spice-mcp
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**From source**:
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/Evan-Kim2028/spice-mcp.git
|
|
83
|
+
cd spice-mcp
|
|
84
|
+
uv sync
|
|
85
|
+
uv pip install -e .
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Requirements**: Python 3.13+
|
|
89
|
+
|
|
90
|
+
## Documentation
|
|
91
|
+
|
|
92
|
+
- [Tool Reference](docs/tools.md) — Complete tool documentation with parameters
|
|
93
|
+
- [Architecture](docs/architecture.md) — Code structure and design patterns
|
|
94
|
+
- [Discovery Guide](docs/discovery.md) — How to explore Dune schemas and tables
|
|
95
|
+
- [Dune API Guide](docs/dune_api.md) — Understanding Dune's data structure
|
|
96
|
+
- [Configuration](docs/config.md) — Environment variables and settings
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
See [LICENSE](LICENSE) file for details.
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
## Layers
|
|
25
25
|
|
|
26
26
|
### Core (`src/spice_mcp/core`)
|
|
27
|
-
- `models.py` – dataclass representations for query requests, previews, metadata, schema descriptions
|
|
28
|
-
- `ports.py` – protocols defining the boundaries (`QueryExecutor`, `CatalogExplorer
|
|
27
|
+
- `models.py` – dataclass representations for query requests, previews, metadata, schema descriptions.
|
|
28
|
+
- `ports.py` – protocols defining the boundaries (`QueryExecutor`, `CatalogExplorer`).
|
|
29
29
|
- `errors.py` – MCP-oriented error categorisation and envelope helpers (`error_response`).
|
|
30
30
|
|
|
31
31
|
These modules contain zero infrastructure concerns; they define the shapes that higher layers orchestrate.
|
|
@@ -39,13 +39,12 @@ These modules contain zero infrastructure concerns; they define the shapes that
|
|
|
39
39
|
### Service Layer (`src/spice_mcp/service_layer`)
|
|
40
40
|
- `query_service.py` – orchestrates `QueryExecutor` calls and shapes the dictionaries returned to MCP tools. Handles `performance` passthrough, metadata merging, and preview formatting.
|
|
41
41
|
- `discovery_service.py` – thin façade around `CatalogExplorer`, returning friendlier lists for schema/table introspection.
|
|
42
|
-
- `sui_service.py` – composes `QueryService` to deliver Sui-specific behaviours (events preview, package overview defaults).
|
|
43
42
|
|
|
44
43
|
Services are pure Python and easily testable with stubbed ports (see `tests/tools/test_query_service.py`).
|
|
45
44
|
|
|
46
45
|
### MCP Integration (`src/spice_mcp/mcp`)
|
|
47
46
|
- `server.py` – FastMCP stdio bridge. Lazily initialises configuration, adapters, services, tools, and resources. Provides resource URIs and tools while keeping stdout clean.
|
|
48
|
-
- `tools/execute_query.py
|
|
47
|
+
- `tools/execute_query.py` – thin glue calling into services, handling logging/audit trails, and shaping agent-friendly responses.
|
|
49
48
|
- Uses `QueryHistory` (`src/spice_mcp/logging/query_history.py`) for JSONL audit trails and SQL artefact storage.
|
|
50
49
|
|
|
51
50
|
### Observability (`src/spice_mcp/observability`)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Codex CLI MCP Setup (spice_mcp_beta)
|
|
2
2
|
|
|
3
3
|
Goal
|
|
4
|
-
- Register the spice-mcp server as an MCP provider named `spice_mcp_beta` for Codex CLI, so you can call tools like `dune_query
|
|
4
|
+
- Register the spice-mcp server as an MCP provider named `spice_mcp_beta` for Codex CLI, so you can call tools like `dune_query`.
|
|
5
5
|
|
|
6
6
|
Prereqs
|
|
7
7
|
- Dune API key in your shell (e.g., export `DUNE_API_KEY=...`)
|
|
@@ -54,23 +54,17 @@ 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
|
|
58
|
-
- `mcp__spice_mcp_beta__dune_find_tables {"keyword": "
|
|
59
|
-
- Describe
|
|
60
|
-
- `mcp__spice_mcp_beta__dune_describe_table {"schema": "
|
|
57
|
+
- Find schemas
|
|
58
|
+
- `mcp__spice_mcp_beta__dune_find_tables {"keyword": "dex"}`
|
|
59
|
+
- Describe a table
|
|
60
|
+
- `mcp__spice_mcp_beta__dune_describe_table {"schema": "dex", "table": "trades"}`
|
|
61
61
|
- Query preview (with metadata/pagination)
|
|
62
62
|
- `mcp__spice_mcp_beta__dune_query {"query": "4388", "limit": 5}`
|
|
63
|
-
- `mcp__spice_mcp_beta__dune_query {"query": "SELECT * FROM
|
|
64
|
-
MCP command URIs (resources)
|
|
65
|
-
- You can read command-style resources for safe defaults (3-day windows, small LIMITs):
|
|
66
|
-
- Events preview: `spice:sui/events_preview/72/50/0xcaf6...,0x2c8d...`
|
|
67
|
-
- Package overview (cmd): `spice:sui/package_overview/72/30/0xcaf6...,0x2c8d...`
|
|
68
|
-
- Global preview (no package filter): `spice:sui/events_preview/72/50/_`
|
|
69
|
-
- See `docs/commands.md` for details. (Your client must support reading MCP resources.)
|
|
63
|
+
- `mcp__spice_mcp_beta__dune_query {"query": "SELECT * FROM dex.trades LIMIT 5"}`
|
|
70
64
|
|
|
71
65
|
Notes & troubleshooting
|
|
72
66
|
- Secret safety: Never store `DUNE_API_KEY` in Codex config; use `shell_environment_policy.inherit=["DUNE_API_KEY"]` or set it in your shell.
|
|
73
67
|
- Missing key error: If you see `DUNE_API_KEY required`, export it in your shell and relaunch. The server also attempts to load `.env` from the project or home directory as a fallback.
|
|
74
68
|
- FastMCP stdout: This server disables FastMCP banners/logging to keep stdio clean; if handshakes fail, ensure you used the exact `python -m spice_mcp.mcp.server` and PYTHONPATH as shown.
|
|
75
|
-
- Heavy scans: Start with `format="metadata"` on `dune_query`, use `performance="large"`, and small `limit` with a recent time window
|
|
69
|
+
- Heavy scans: Start with `format="metadata"` on `dune_query`, use `performance="large"`, and small `limit` with a recent time window.
|
|
76
70
|
- Tests/CI: set `SPICE_MCP_SKIP_DOTENV=1` to stop `_ensure_initialized` from reading local `.env` files when the key is intentionally absent.
|
|
@@ -15,7 +15,7 @@ Optional
|
|
|
15
15
|
- `SPICE_LOGGING_ENABLED`: true/false (default: true)
|
|
16
16
|
- Timeouts
|
|
17
17
|
- `SPICE_TIMEOUT_SECONDS`: default polling timeout (seconds)
|
|
18
|
-
- `SPICE_MAX_CONCURRENT_QUERIES`:
|
|
18
|
+
- `SPICE_MAX_CONCURRENT_QUERIES`: reserved for future concurrency control (default: 5, not currently enforced)
|
|
19
19
|
- Raw SQL
|
|
20
20
|
- `SPICE_RAW_SQL_QUERY_ID`: ID of the template query used to execute raw SQL (default: 4060379). Health is reported by `dune_health_check` when set.
|
|
21
21
|
|
|
@@ -4,7 +4,7 @@ This document summarizes local development workflows for spice-mcp.
|
|
|
4
4
|
|
|
5
5
|
## Tooling
|
|
6
6
|
|
|
7
|
-
- Tests: `pytest` (+ `
|
|
7
|
+
- Tests: `pytest` (+ `responses`, `vcrpy`, `hypothesis`)
|
|
8
8
|
- Lint/Format: `ruff` (lint + import sort + formatting)
|
|
9
9
|
- Types: `mypy`
|
|
10
10
|
|
|
@@ -27,7 +27,7 @@ Enable live tests explicitly:
|
|
|
27
27
|
- `tests/offline/`: pure unit tests; no network.
|
|
28
28
|
- `tests/http_stubbed/`: HTTP boundary via `responses`.
|
|
29
29
|
- `tests/tools/`: service + MCP tool unit tests.
|
|
30
|
-
- `tests/mcp/`: FastMCP contract tests with stubbed services (preview/raw/metadata
|
|
30
|
+
- `tests/mcp/`: FastMCP contract tests with stubbed services (preview/raw/metadata).
|
|
31
31
|
- `tests/fastmcp/`: registration/metadata smoke tests for FastMCP wiring.
|
|
32
32
|
- `tests/style/`: static safety checks (lazyframe enforcement, etc.).
|
|
33
33
|
- `tests/live/`: opt-in integrations; require `SPICE_TEST_LIVE=1` and `DUNE_API_KEY`.
|
|
@@ -36,7 +36,7 @@ Most unit/contract tests construct stub implementations of the ports in `src/spi
|
|
|
36
36
|
|
|
37
37
|
## Style
|
|
38
38
|
|
|
39
|
-
- Follow Ruff + ruff-format defaults (88 cols,
|
|
39
|
+
- Follow Ruff + ruff-format defaults (88 cols, py313).
|
|
40
40
|
- Prefer small, focused functions and explicit typing in `src/`.
|
|
41
41
|
- Tests prioritize readability over strict typing.
|
|
42
42
|
- Export `SPICE_MCP_SKIP_DOTENV=1` when running tests that expect the API key to be unset.
|
|
@@ -17,10 +17,6 @@ Approach
|
|
|
17
17
|
- Some deployments allow: information_schema.schemata/tables/columns
|
|
18
18
|
- If blocked, use SHOW + probes
|
|
19
19
|
|
|
20
|
-
Sui Examples
|
|
21
|
-
- Find Sui schemas: SHOW SCHEMAS LIKE '%sui%'
|
|
22
|
-
- Confirm core tables: SELECT * FROM sui.events/transactions/checkpoints/objects LIMIT 1
|
|
23
|
-
- SHOW TABLES works on some Sui-related schemas (e.g., sui_base), but not all
|
|
24
20
|
|
|
25
21
|
Helpers in this repo
|
|
26
22
|
- `src/spice_mcp/service_layer/discovery_service.py` provides service wrappers around the Dune adapter:
|
|
@@ -7,7 +7,7 @@ spice-mcp Documentation
|
|
|
7
7
|
- Architecture Overview: `docs/architecture.md`
|
|
8
8
|
- Dune API Overview: `docs/dune_api.md`
|
|
9
9
|
- Catalog Discovery Guide: `docs/discovery.md`
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
- Codex CLI MCP Setup: `docs/codex_cli.md`
|
|
12
12
|
- Development & Testing: `docs/development.md`
|
|
13
13
|
|
|
@@ -53,20 +53,7 @@ Logging & Artifacts
|
|
|
53
53
|
- columns: [{ name, dune_type?, polars_dtype?, extra?, comment? }]
|
|
54
54
|
- Errors follow the standard MCP envelope.
|
|
55
55
|
|
|
56
|
-
4)
|
|
57
|
-
- Purpose: Preview Sui package activity (events, transactions, objects) over a time window; timeouts supported.
|
|
58
|
-
- Input schema:
|
|
59
|
-
- packages: string[] (required)
|
|
60
|
-
- hours?: integer (default 72)
|
|
61
|
-
- timeout_seconds?: number (default 30)
|
|
62
|
-
- Output fields (best-effort):
|
|
63
|
-
- ok: boolean
|
|
64
|
-
- events_count?, events_preview?, events_timeout?, events_error?
|
|
65
|
-
- transactions_count?, transactions_preview?, transactions_timeout?, transactions_error?
|
|
66
|
-
- objects_count?, objects_preview?, objects_timeout?, objects_error?
|
|
67
|
-
- On error: { ok: false, error: { … } }
|
|
68
|
-
|
|
69
|
-
5) dune_health_check
|
|
56
|
+
4) dune_health_check
|
|
70
57
|
- Purpose: Basic environment and logging readiness check.
|
|
71
58
|
- Output fields: ok, api_key_present, status
|
|
72
59
|
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
This module provides a thin façade used by the new service layer while
|
|
4
4
|
keeping the battle-tested logic that the original spice client offered.
|
|
5
|
+
|
|
6
|
+
Only synchronous interfaces are exposed.
|
|
5
7
|
"""
|
|
6
8
|
|
|
7
9
|
from . import urls # re-export for callers needing low-level helpers
|
|
8
|
-
from .extract import
|
|
10
|
+
from .extract import query # noqa: F401
|
|
9
11
|
|
|
10
|
-
__all__ = ["query", "
|
|
12
|
+
__all__ = ["query", "urls"]
|
|
@@ -51,39 +51,7 @@ def load_from_cache(
|
|
|
51
51
|
return None, execution
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
async
|
|
55
|
-
execute_kwargs: _extract.ExecuteKwargs,
|
|
56
|
-
result_kwargs: _extract.RetrievalKwargs,
|
|
57
|
-
output_kwargs: _extract.OutputKwargs,
|
|
58
|
-
) -> tuple[
|
|
59
|
-
pl.DataFrame | tuple[pl.DataFrame, Execution] | None, Execution | None
|
|
60
|
-
]:
|
|
61
|
-
# get latest execution
|
|
62
|
-
execution = await _extract.async_get_latest_execution(execute_kwargs)
|
|
63
|
-
if execution is None:
|
|
64
|
-
return None, None
|
|
65
|
-
|
|
66
|
-
# build cache path
|
|
67
|
-
cache_path = _build_cache_path(
|
|
68
|
-
execution=execution,
|
|
69
|
-
execute_kwargs=execute_kwargs,
|
|
70
|
-
result_kwargs=result_kwargs,
|
|
71
|
-
cache_dir=output_kwargs['cache_dir'],
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
# load cache result
|
|
75
|
-
if os.path.exists(cache_path):
|
|
76
|
-
import polars as pl
|
|
77
|
-
|
|
78
|
-
if result_kwargs['verbose']:
|
|
79
|
-
print('loading dune query result from cache')
|
|
80
|
-
df = await pl.scan_parquet(cache_path).collect_async()
|
|
81
|
-
if output_kwargs['include_execution']:
|
|
82
|
-
return (df, execution), execution
|
|
83
|
-
else:
|
|
84
|
-
return df, execution
|
|
85
|
-
else:
|
|
86
|
-
return None, execution
|
|
54
|
+
## Removed async cache loader; synchronous load_from_cache should be used exclusively.
|
|
87
55
|
|
|
88
56
|
|
|
89
57
|
def save_to_cache(
|
|
@@ -113,7 +81,7 @@ def save_to_cache(
|
|
|
113
81
|
# save to cache
|
|
114
82
|
tmp_path = (
|
|
115
83
|
cache_path + '_tmp_' + secrets.token_hex(8)
|
|
116
|
-
) # add
|
|
84
|
+
) # add tmp suffix to avoid conflicts during writes
|
|
117
85
|
df.write_parquet(tmp_path)
|
|
118
86
|
shutil.move(tmp_path, cache_path)
|
|
119
87
|
|
|
@@ -24,6 +24,10 @@ from ...polars_utils import collect_preview
|
|
|
24
24
|
from ..http_client import HttpClient, HttpClientConfig
|
|
25
25
|
from . import extract, urls
|
|
26
26
|
|
|
27
|
+
# Use wrapper to avoid FastMCP detecting overloads in extract.query()
|
|
28
|
+
# Note: We still import extract for _determine_input_type and other non-overloaded functions
|
|
29
|
+
from .query_wrapper import execute_query as _execute_dune_query
|
|
30
|
+
|
|
27
31
|
|
|
28
32
|
class DuneAdapter(QueryExecutor, CatalogExplorer):
|
|
29
33
|
"""Thin façade around the vendored extract module."""
|
|
@@ -47,7 +51,7 @@ class DuneAdapter(QueryExecutor, CatalogExplorer):
|
|
|
47
51
|
q_rewritten = _maybe_rewrite_show_sql(q)
|
|
48
52
|
if q_rewritten is not None:
|
|
49
53
|
q = q_rewritten
|
|
50
|
-
result =
|
|
54
|
+
result = _execute_dune_query(
|
|
51
55
|
query_or_execution=q,
|
|
52
56
|
verbose=False,
|
|
53
57
|
refresh=request.refresh,
|
|
@@ -123,9 +127,10 @@ class DuneAdapter(QueryExecutor, CatalogExplorer):
|
|
|
123
127
|
pass
|
|
124
128
|
|
|
125
129
|
url = urls.get_query_results_url(query_id, parameters=params, csv=False)
|
|
130
|
+
from .user_agent import get_user_agent
|
|
126
131
|
headers = {
|
|
127
132
|
"X-Dune-API-Key": self._api_key(),
|
|
128
|
-
"User-Agent":
|
|
133
|
+
"User-Agent": get_user_agent(),
|
|
129
134
|
}
|
|
130
135
|
try:
|
|
131
136
|
resp = self._http.request("GET", url, headers=headers)
|
|
@@ -196,8 +201,8 @@ class DuneAdapter(QueryExecutor, CatalogExplorer):
|
|
|
196
201
|
def _run_sql(self, sql: str, *, limit: int | None = None) -> pl.DataFrame:
|
|
197
202
|
self._ensure_api_key()
|
|
198
203
|
sql_eff = _maybe_rewrite_show_sql(sql) or sql
|
|
199
|
-
df =
|
|
200
|
-
sql_eff,
|
|
204
|
+
df = _execute_dune_query(
|
|
205
|
+
query_or_execution=sql_eff,
|
|
201
206
|
verbose=False,
|
|
202
207
|
performance="medium",
|
|
203
208
|
timeout_seconds=self.config.default_timeout_seconds,
|