spice-mcp 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl
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.
- spice_mcp/adapters/dune/client.py +22 -33
- spice_mcp/adapters/dune/extract.py +49 -5
- spice_mcp/adapters/dune/query_wrapper.py +86 -0
- spice_mcp/adapters/dune/user_agent.py +9 -0
- spice_mcp/adapters/spellbook/explorer.py +84 -1
- spice_mcp/mcp/server.py +199 -99
- spice_mcp/mcp/tools/execute_query.py +19 -25
- spice_mcp/service_layer/verification_service.py +185 -0
- spice_mcp-0.1.5.dist-info/METADATA +133 -0
- {spice_mcp-0.1.3.dist-info → spice_mcp-0.1.5.dist-info}/RECORD +13 -10
- spice_mcp-0.1.3.dist-info/METADATA +0 -198
- {spice_mcp-0.1.3.dist-info → spice_mcp-0.1.5.dist-info}/WHEEL +0 -0
- {spice_mcp-0.1.3.dist-info → spice_mcp-0.1.5.dist-info}/entry_points.txt +0 -0
- {spice_mcp-0.1.3.dist-info → spice_mcp-0.1.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Verification Service - Verifies tables exist in Dune with persistent caching.
|
|
3
|
+
|
|
4
|
+
This service provides lazy verification of table existence, caching results
|
|
5
|
+
to avoid repeated queries. Cache persists across server restarts.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from ..adapters.dune.client import DuneAdapter
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
# Cache entry expires after 1 week (604800 seconds)
|
|
20
|
+
CACHE_TTL_SECONDS = 604800
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class VerificationService:
|
|
24
|
+
"""
|
|
25
|
+
Service for verifying table existence in Dune with persistent caching.
|
|
26
|
+
|
|
27
|
+
Verifies tables exist before returning them to users, ensuring only
|
|
28
|
+
queryable tables are surfaced. Uses persistent cache to avoid repeated
|
|
29
|
+
verification queries.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, cache_path: Path, dune_adapter: DuneAdapter):
|
|
33
|
+
"""
|
|
34
|
+
Initialize verification service.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
cache_path: Path to JSON file for persistent cache storage
|
|
38
|
+
dune_adapter: DuneAdapter instance for querying table existence
|
|
39
|
+
"""
|
|
40
|
+
self.cache_path = cache_path
|
|
41
|
+
self.dune_adapter = dune_adapter
|
|
42
|
+
self._cache: dict[str, dict[str, Any]] = self._load_cache()
|
|
43
|
+
|
|
44
|
+
def verify_tables_batch(
|
|
45
|
+
self, tables: list[tuple[str, str]]
|
|
46
|
+
) -> dict[str, bool]:
|
|
47
|
+
"""
|
|
48
|
+
Verify multiple tables exist in Dune.
|
|
49
|
+
|
|
50
|
+
Uses cache for fast lookups, queries Dune only for uncached tables.
|
|
51
|
+
Results are cached for future use.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
tables: List of (schema, table) tuples to verify
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Dict mapping "schema.table" -> bool (exists or not)
|
|
58
|
+
"""
|
|
59
|
+
results: dict[str, bool] = {}
|
|
60
|
+
to_check: list[tuple[str, str]] = []
|
|
61
|
+
|
|
62
|
+
# Check cache first
|
|
63
|
+
for schema, table in tables:
|
|
64
|
+
fqn = f"{schema}.{table}"
|
|
65
|
+
cached = self._get_cached(fqn)
|
|
66
|
+
if cached is not None:
|
|
67
|
+
results[fqn] = cached
|
|
68
|
+
else:
|
|
69
|
+
to_check.append((schema, table))
|
|
70
|
+
|
|
71
|
+
# Verify uncached tables
|
|
72
|
+
if to_check:
|
|
73
|
+
logger.info(f"Verifying {len(to_check)} uncached tables")
|
|
74
|
+
for schema, table in to_check:
|
|
75
|
+
try:
|
|
76
|
+
exists = self._verify_single(schema, table)
|
|
77
|
+
fqn = f"{schema}.{table}"
|
|
78
|
+
results[fqn] = exists
|
|
79
|
+
self._cache_result(fqn, exists)
|
|
80
|
+
except Exception as e:
|
|
81
|
+
# Do not hard-cache transient failures as negative results.
|
|
82
|
+
# Leave the table unverified so callers can choose to keep it.
|
|
83
|
+
logger.warning(
|
|
84
|
+
f"Failed to verify {schema}.{table}: {e}. Skipping cache and leaving unverified."
|
|
85
|
+
)
|
|
86
|
+
# Intentionally omit from results and cache on failure
|
|
87
|
+
|
|
88
|
+
return results
|
|
89
|
+
|
|
90
|
+
def _verify_single(self, schema: str, table: str) -> bool:
|
|
91
|
+
"""
|
|
92
|
+
Verify a single table exists using lightweight DESCRIBE query.
|
|
93
|
+
|
|
94
|
+
Uses SHOW COLUMNS which is fast and doesn't require full table scan.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
schema: Schema name
|
|
98
|
+
table: Table name
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
True if table exists, False otherwise
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
# Use describe_table which internally uses SHOW COLUMNS
|
|
105
|
+
# This is lightweight and fast
|
|
106
|
+
self.dune_adapter.describe_table(schema, table)
|
|
107
|
+
return True
|
|
108
|
+
except Exception:
|
|
109
|
+
# If describe fails, table doesn't exist
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def _get_cached(self, table: str) -> bool | None:
|
|
113
|
+
"""
|
|
114
|
+
Get verification result from cache if fresh.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
table: Fully qualified table name (schema.table)
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
bool if cached and fresh, None if cache miss or stale
|
|
121
|
+
"""
|
|
122
|
+
if table not in self._cache:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
entry = self._cache[table]
|
|
126
|
+
timestamp = entry.get("timestamp", 0)
|
|
127
|
+
age = time.time() - timestamp
|
|
128
|
+
|
|
129
|
+
if age < CACHE_TTL_SECONDS:
|
|
130
|
+
return entry.get("exists", False)
|
|
131
|
+
else:
|
|
132
|
+
# Cache entry is stale, remove it
|
|
133
|
+
del self._cache[table]
|
|
134
|
+
self._save_cache()
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
def _cache_result(self, table: str, exists: bool) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Cache verification result with current timestamp.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
table: Fully qualified table name
|
|
143
|
+
exists: Whether table exists
|
|
144
|
+
"""
|
|
145
|
+
self._cache[table] = {
|
|
146
|
+
"exists": exists,
|
|
147
|
+
"timestamp": time.time(),
|
|
148
|
+
}
|
|
149
|
+
self._save_cache()
|
|
150
|
+
|
|
151
|
+
def _load_cache(self) -> dict[str, dict[str, Any]]:
|
|
152
|
+
"""
|
|
153
|
+
Load verification cache from disk.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Dict mapping table -> cache entry
|
|
157
|
+
"""
|
|
158
|
+
if not self.cache_path.exists():
|
|
159
|
+
return {}
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
with open(self.cache_path, encoding="utf-8") as f:
|
|
163
|
+
cache = json.load(f)
|
|
164
|
+
# Validate cache structure
|
|
165
|
+
if isinstance(cache, dict):
|
|
166
|
+
return cache
|
|
167
|
+
return {}
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.warning(f"Failed to load verification cache: {e}")
|
|
170
|
+
return {}
|
|
171
|
+
|
|
172
|
+
def _save_cache(self) -> None:
|
|
173
|
+
"""Persist verification cache to disk."""
|
|
174
|
+
try:
|
|
175
|
+
self.cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
176
|
+
with open(self.cache_path, "w", encoding="utf-8") as f:
|
|
177
|
+
json.dump(self._cache, f, indent=2)
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.warning(f"Failed to save verification cache: {e}")
|
|
180
|
+
|
|
181
|
+
def clear_cache(self) -> None:
|
|
182
|
+
"""Clear verification cache (useful for testing or forced refresh)."""
|
|
183
|
+
self._cache = {}
|
|
184
|
+
if self.cache_path.exists():
|
|
185
|
+
self.cache_path.unlink()
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spice-mcp
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: mcp server built ontop of dune api endpoint
|
|
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
|
+
**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
|
+
|
|
33
|
+
## Why spice-mcp?
|
|
34
|
+
|
|
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
|
|
37
|
+
- **Efficient**: Polars-first pipeline keeps data lazy until needed, reducing memory usage
|
|
38
|
+
- **Discovery**: Built-in tools to explore Dune's extensive blockchain datasets from both Dune API and Spellbook
|
|
39
|
+
- **Type-safe**: Fully typed parameters and responses with FastMCP
|
|
40
|
+
- **Reproducible**: Automatic query history logging and SQL artifact storage
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
1. **Install**:
|
|
45
|
+
```bash
|
|
46
|
+
uv pip install spice-mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
2. **Set API key** (choose one method):
|
|
50
|
+
- **Option A**: Create a `.env` file in your project root:
|
|
51
|
+
```bash
|
|
52
|
+
echo "DUNE_API_KEY=your-api-key-here" > .env
|
|
53
|
+
```
|
|
54
|
+
- **Option B**: Export in your shell:
|
|
55
|
+
```bash
|
|
56
|
+
export DUNE_API_KEY=your-api-key-here
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
3. **Use with Cursor IDE**:
|
|
60
|
+
Add to Cursor Settings → MCP Servers:
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"name": "spice-mcp",
|
|
64
|
+
"command": "spice-mcp",
|
|
65
|
+
"env": {
|
|
66
|
+
"DUNE_API_KEY": "your-dune-api-key-here"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**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.
|
|
72
|
+
|
|
73
|
+
## Core Tools
|
|
74
|
+
|
|
75
|
+
| Tool | Description | Key Parameters |
|
|
76
|
+
|------|-------------|----------------|
|
|
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) |
|
|
78
|
+
| `dune_query_info` | Get metadata for a saved query | `query` (str - ID or URL) |
|
|
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) |
|
|
80
|
+
| `dune_describe_table` | Get column metadata for a table | `schema` (str), `table` (str) |
|
|
81
|
+
| `dune_health_check` | Verify API key and configuration | (no parameters) |
|
|
82
|
+
| `dune_query_create` | Create a new saved query | `name` (str), `query_sql` (str), `description` (str), `tags` (list), `parameters` (list) |
|
|
83
|
+
| `dune_query_update` | Update an existing saved query | `query_id` (int), `name` (str), `query_sql` (str), `description` (str), `tags` (list), `parameters` (list) |
|
|
84
|
+
| `dune_query_fork` | Fork an existing saved query | `source_query_id` (int), `name` (str) |
|
|
85
|
+
|
|
86
|
+
## Resources
|
|
87
|
+
|
|
88
|
+
- `spice:history/tail/{n}` — View last N lines of query history (1-1000)
|
|
89
|
+
- `spice:artifact/{sha}` — Retrieve stored SQL by SHA-256 hash
|
|
90
|
+
|
|
91
|
+
## What is Dune?
|
|
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.
|
|
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
|
+
|
|
106
|
+
## Installation
|
|
107
|
+
|
|
108
|
+
**From PyPI** (recommended):
|
|
109
|
+
```bash
|
|
110
|
+
uv pip install spice-mcp
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**From source**:
|
|
114
|
+
```bash
|
|
115
|
+
git clone https://github.com/Evan-Kim2028/spice-mcp.git
|
|
116
|
+
cd spice-mcp
|
|
117
|
+
uv sync
|
|
118
|
+
uv pip install -e .
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Requirements**: Python 3.13+
|
|
122
|
+
|
|
123
|
+
## Documentation
|
|
124
|
+
|
|
125
|
+
- [Tool Reference](docs/tools.md) — Complete tool documentation with parameters
|
|
126
|
+
- [Architecture](docs/architecture.md) — Code structure and design patterns
|
|
127
|
+
- [Discovery Guide](docs/discovery.md) — How to explore Dune schemas and tables
|
|
128
|
+
- [Dune API Guide](docs/dune_api.md) — Understanding Dune's data structure
|
|
129
|
+
- [Configuration](docs/config.md) — Environment variables and settings
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
See [LICENSE](LICENSE) file for details.
|
|
@@ -7,33 +7,36 @@ spice_mcp/adapters/http_client.py,sha256=CYgSKAsx-5c-uxaNIBCBTgQdaoBe5J3dJvnw8iq
|
|
|
7
7
|
spice_mcp/adapters/dune/__init__.py,sha256=nspEuDpVOktAxm8B066s-d0LwouCYGpvNEexi0mRMN8,386
|
|
8
8
|
spice_mcp/adapters/dune/admin.py,sha256=yxOueVz-rmgC-ZFbT06k59G24yRgYjiEkZlall5hXNQ,3157
|
|
9
9
|
spice_mcp/adapters/dune/cache.py,sha256=7ykT58WN1yHGIN2uV3t7fWOqGb1VJdCvf3I-xZwsv74,4304
|
|
10
|
-
spice_mcp/adapters/dune/client.py,sha256=
|
|
11
|
-
spice_mcp/adapters/dune/extract.py,sha256=
|
|
10
|
+
spice_mcp/adapters/dune/client.py,sha256=zle19bU-I3AzpOF1cp_hEaZUFNQV1RWhtl1LAxObk0g,9052
|
|
11
|
+
spice_mcp/adapters/dune/extract.py,sha256=67x-WCaP13vbMsTKnqNSOVbMs6Dsf0QHi2fLHduYTBI,30405
|
|
12
12
|
spice_mcp/adapters/dune/helpers.py,sha256=BgDKr_g-UqmU2hoMb0ejQZHta_NbKwR1eDJp33sJYNk,227
|
|
13
|
+
spice_mcp/adapters/dune/query_wrapper.py,sha256=4dk8D8KJKWoBhuMLzDGupRrXGlG-N0cbM6HCs8wMFvE,2925
|
|
13
14
|
spice_mcp/adapters/dune/transport.py,sha256=eRP-jPY2ZXxvTX9HSjIFqFUlbIzXspgH95jBFoTlpaQ,1436
|
|
14
15
|
spice_mcp/adapters/dune/types.py,sha256=57TMX07u-Gq4BYwRAuZV0xI81nVXgtpp7KBID9YbKyQ,1195
|
|
15
16
|
spice_mcp/adapters/dune/typing_utils.py,sha256=EpWneGDn-eQdo6lkLuESR09KXkDj9OqGz8bEF3JaFkM,574
|
|
16
17
|
spice_mcp/adapters/dune/urls.py,sha256=bcuPERkFQduRTT2BrgzVhoFrMn-Lkvw9NmktcBZYEig,3902
|
|
18
|
+
spice_mcp/adapters/dune/user_agent.py,sha256=c6Kt4zczbuT9mapDoh8-3sgm268MUtvyIRxDF9yJwXQ,218
|
|
17
19
|
spice_mcp/adapters/spellbook/__init__.py,sha256=D2cdVtSUbmAJdbPRvAyKxYS4-wUQ3unXyX4ZFYxenuk,150
|
|
18
|
-
spice_mcp/adapters/spellbook/explorer.py,sha256=
|
|
20
|
+
spice_mcp/adapters/spellbook/explorer.py,sha256=nfecYztzxfBXp9b9IKcP4dVICCnPzhHvPfNJKFhaKxA,14856
|
|
19
21
|
spice_mcp/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
22
|
spice_mcp/core/errors.py,sha256=jlfTuyRaAaA_oU07KUk-1pDAAa43KG0BbZc5CINXtoE,3256
|
|
21
23
|
spice_mcp/core/models.py,sha256=i0C_-UE16OWyyZo_liooEJeYvbChE5lpK80aN2OF4lk,1795
|
|
22
24
|
spice_mcp/core/ports.py,sha256=nEdeA3UH7v0kB_hbguMrpDljb9EhSxUAO0SdhjpoijQ,1618
|
|
23
25
|
spice_mcp/logging/query_history.py,sha256=doE9lod64uzJxlA2XzHH2-VAmC6WstYAkQ0taEAxiIM,4315
|
|
24
26
|
spice_mcp/mcp/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
25
|
-
spice_mcp/mcp/server.py,sha256=
|
|
27
|
+
spice_mcp/mcp/server.py,sha256=oi0RRaSithCgUt0Qt6Tb3gks32LN0dOOwN7kX-BrN7s,32263
|
|
26
28
|
spice_mcp/mcp/tools/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
27
29
|
spice_mcp/mcp/tools/base.py,sha256=zJkVxLgXR48iZcJeng8cZ2rXvbyicagoGlMN7BK7Img,1041
|
|
28
|
-
spice_mcp/mcp/tools/execute_query.py,sha256=
|
|
30
|
+
spice_mcp/mcp/tools/execute_query.py,sha256=K1YpuQGwvVM20A4_h9zNlkeG37J7jbY7BPzLm6vPAsY,16033
|
|
29
31
|
spice_mcp/observability/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
32
|
spice_mcp/observability/logging.py,sha256=ceJUEpKGpf5PAgPBmpB49zjqhdGCAESfLemFUhDSmI8,529
|
|
31
33
|
spice_mcp/service_layer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
34
|
spice_mcp/service_layer/discovery_service.py,sha256=202O0SzCZGQukd9kb2JYfarLygZHgiXlHqp_nTAdrWA,730
|
|
33
35
|
spice_mcp/service_layer/query_admin_service.py,sha256=4q1NAAuTui7cm83Aq2rFDLIzKTHX17yzbSoSJyCmLbI,1356
|
|
34
36
|
spice_mcp/service_layer/query_service.py,sha256=q0eAVW5I3sUxm29DgzPN_cH3rZEzmKwmdE3Xj4qP9lI,3878
|
|
35
|
-
spice_mcp
|
|
36
|
-
spice_mcp-0.1.
|
|
37
|
-
spice_mcp-0.1.
|
|
38
|
-
spice_mcp-0.1.
|
|
39
|
-
spice_mcp-0.1.
|
|
37
|
+
spice_mcp/service_layer/verification_service.py,sha256=dPA88p9zKqg62bNjN_4c5QFEUBHCWjZph8pn2a5zrUI,6057
|
|
38
|
+
spice_mcp-0.1.5.dist-info/METADATA,sha256=ag4hjEMz-qxvImxJPxpsQN_0F9BOUdbk6TF8zAXW5I4,6093
|
|
39
|
+
spice_mcp-0.1.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
40
|
+
spice_mcp-0.1.5.dist-info/entry_points.txt,sha256=4XiXX13Vy-oiUJwlcO_82OltBaxFnEnkJ-76sZGm5os,56
|
|
41
|
+
spice_mcp-0.1.5.dist-info/licenses/LICENSE,sha256=r0GNDnDY1RSkVQp7kEEf6MQU21OrNGJkxUHIsv6eyLk,1079
|
|
42
|
+
spice_mcp-0.1.5.dist-info/RECORD,,
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: spice-mcp
|
|
3
|
-
Version: 0.1.3
|
|
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
|
-
spice-mcp is an MCP server for [Dune](https://dune.com/) Analytics. It wraps a curated subset of the original Spice client inside a clean architecture (`core` models/ports → `adapters.dune` → service layer → FastMCP tools) and adds agent-friendly workflows for discovery. Results are Polars-first in Python and compact, token-efficient in MCP responses.
|
|
25
|
-
|
|
26
|
-
[](https://pypi.org/project/spice-mcp/)
|
|
27
|
-
<a href="https://glama.ai/mcp/servers/@Evan-Kim2028/spice-mcp">
|
|
28
|
-
<img width="380" height="200" src="https://glama.ai/mcp/servers/@Evan-Kim2028/spice-mcp/badge" alt="Spice MCP server" />
|
|
29
|
-
</a>
|
|
30
|
-
|
|
31
|
-
Requirements: Python 3.13+
|
|
32
|
-
|
|
33
|
-
This project uses FastMCP for typed, decorator-registered tools and resources.
|
|
34
|
-
|
|
35
|
-
## Highlights
|
|
36
|
-
- Polars LazyFrame-first pipeline: results stay lazy until explicitly materialized
|
|
37
|
-
- Ports/adapters layering for maintainable integrations ([docs/architecture.md](docs/architecture.md))
|
|
38
|
-
- Discovery utilities (find schemas/tables, describe columns)
|
|
39
|
-
- JSONL query history + SQL artifacts (SHA-256) for reproducibility
|
|
40
|
-
- Rich MCP surface: query info/run, discovery, health, and Dune admin (create/update/fork)
|
|
41
|
-
|
|
42
|
-
## What is Dune?
|
|
43
|
-
[Dune](https://dune.com/) is a crypto data platform providing curated blockchain datasets and a public API to run and fetch query results. See the [Dune Docs](https://dune.com/docs) and [Dune API](https://dune.com/docs/api/) for full details.
|
|
44
|
-
|
|
45
|
-
## Quick Start
|
|
46
|
-
- Export `DUNE_API_KEY` in your shell (the server can also load a local `.env`; set `SPICE_MCP_SKIP_DOTENV=1` to skip during tests).
|
|
47
|
-
- Install from PyPI: `uv pip install spice-mcp`
|
|
48
|
-
- Or install from source:
|
|
49
|
-
- `uv sync` then `uv pip install -e .`
|
|
50
|
-
- Start the FastMCP stdio server:
|
|
51
|
-
- `python -m spice_mcp.mcp.server --env PYTHONPATH=$(pwd)/src`
|
|
52
|
-
- or install the console script via `uv tool install .` and run `spice-mcp`.
|
|
53
|
-
|
|
54
|
-
## Cursor IDE Setup
|
|
55
|
-
|
|
56
|
-
To use spice-mcp with Cursor IDE:
|
|
57
|
-
|
|
58
|
-
1. **Install the MCP Server**:
|
|
59
|
-
```bash
|
|
60
|
-
# Install from PyPI (recommended)
|
|
61
|
-
uv pip install spice-mcp
|
|
62
|
-
|
|
63
|
-
# Or install from source
|
|
64
|
-
uv sync
|
|
65
|
-
uv pip install -e .
|
|
66
|
-
|
|
67
|
-
# Or install via uv tool (creates console script)
|
|
68
|
-
uv tool install .
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
2. **Configure Cursor**:
|
|
72
|
-
- Open Cursor Settings → MCP Servers
|
|
73
|
-
- Add new MCP server configuration:
|
|
74
|
-
```json
|
|
75
|
-
{
|
|
76
|
-
"name": "spice-mcp",
|
|
77
|
-
"command": "spice-mcp",
|
|
78
|
-
"env": {
|
|
79
|
-
"DUNE_API_KEY": "your-dune-api-key-here"
|
|
80
|
-
},
|
|
81
|
-
"disabled": false
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
Alternatively, if you prefer running from source:
|
|
85
|
-
```json
|
|
86
|
-
{
|
|
87
|
-
"name": "spice-mcp",
|
|
88
|
-
"command": "python",
|
|
89
|
-
"args": ["-m", "spice_mcp.mcp.server"],
|
|
90
|
-
"env": {
|
|
91
|
-
"PYTHONPATH": "/path/to/your/spice-mcp/src",
|
|
92
|
-
"DUNE_API_KEY": "your-dune-api-key-here"
|
|
93
|
-
},
|
|
94
|
-
"disabled": false
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
3. **Restart Cursor** to load the MCP server
|
|
99
|
-
|
|
100
|
-
4. **Verify Connection**:
|
|
101
|
-
- Open Cursor and use the command palette (Cmd/Ctrl + Shift + P)
|
|
102
|
-
- Search for "MCP" or "spice" commands
|
|
103
|
-
- Test with `dune_health_check` to verify the connection
|
|
104
|
-
|
|
105
|
-
5. **Available Tools in Cursor**:
|
|
106
|
-
- `dune_query`: Run Dune queries by ID, URL, or raw SQL
|
|
107
|
-
- `dune_find_tables`: Search schemas and list tables
|
|
108
|
-
- `dune_describe_table`: Get column metadata
|
|
109
|
-
- `dune_health_check`: Verify API connection
|
|
110
|
-
|
|
111
|
-
**Tip**: Create a `.env` file in your project root with `DUNE_API_KEY=your-key-here` for easier configuration.
|
|
112
|
-
|
|
113
|
-
## MCP Tools and Features
|
|
114
|
-
|
|
115
|
-
All tools expose typed parameters, titles, and tags; failures return a consistent error envelope.
|
|
116
|
-
|
|
117
|
-
- `dune_query_info` (Query Info, tags: dune, query)
|
|
118
|
-
- Fetch saved-query metadata by ID/URL (name, parameters, tags, SQL, version).
|
|
119
|
-
|
|
120
|
-
- `dune_query` (Run Dune Query, tags: dune, query)
|
|
121
|
-
- Execute by ID/URL/raw SQL with parameters. Supports `refresh`, `max_age`, `limit/offset`, `sample_count`, `sort_by`, `columns`, and `format` = `preview|raw|metadata|poll`; accepts `timeout_seconds`.
|
|
122
|
-
|
|
123
|
-
- `dune_health_check` (Health Check, tag: health)
|
|
124
|
-
- Checks API key presence, query-history path, logging enabled; best-effort template check when configured.
|
|
125
|
-
|
|
126
|
-
- `dune_find_tables` (Find Tables, tags: dune, schema)
|
|
127
|
-
- Search schemas by keyword and/or list tables in a schema (`limit`).
|
|
128
|
-
|
|
129
|
-
- `dune_describe_table` (Describe Table, tags: dune, schema)
|
|
130
|
-
- Column metadata for `schema.table` (Dune types + Polars inferred dtypes when available).
|
|
131
|
-
|
|
132
|
-
- Dune Admin tools (tags: dune, admin)
|
|
133
|
-
- `dune_query_create(name, query_sql, description?, tags?, parameters?)`
|
|
134
|
-
- `dune_query_update(query_id, name?, query_sql?, description?, tags?, parameters?)`
|
|
135
|
-
- `dune_query_fork(source_query_id, name?)`
|
|
136
|
-
|
|
137
|
-
### Resources
|
|
138
|
-
- `spice:history/tail/{n}` — tail last N lines of query history (1..1000)
|
|
139
|
-
- `spice:artifact/{sha}` — fetch stored SQL by 64-hex SHA-256
|
|
140
|
-
- `spice:sui/events_preview/{hours}/{limit}/{packages}` — Sui events preview (JSON)
|
|
141
|
-
- `spice:sui/package_overview/{hours}/{timeout_seconds}/{packages}` — Sui overview (JSON)
|
|
142
|
-
|
|
143
|
-
## Resources
|
|
144
|
-
|
|
145
|
-
- `spice:history/tail/{n}`
|
|
146
|
-
- Last `n` lines from the query-history JSONL, clamped to [1, 1000]
|
|
147
|
-
|
|
148
|
-
- `spice:artifact/{sha}`
|
|
149
|
-
- Returns stored SQL for the SHA-256 (validated as 64 lowercase hex)
|
|
150
|
-
|
|
151
|
-
Tests
|
|
152
|
-
- Offline/unit tests (no network) live under `tests/offline/` and `tests/http_stubbed/`.
|
|
153
|
-
- Live tests under `tests/live/` are skipped by default; enable with `SPICE_TEST_LIVE=1` and a valid `DUNE_API_KEY`.
|
|
154
|
-
- Comprehensive scripted runner (tiered):
|
|
155
|
-
- Run all tiers: `python tests/scripts/comprehensive_test_runner.py`
|
|
156
|
-
- Select tiers: `python tests/scripts/comprehensive_test_runner.py -t 1 -t 3`
|
|
157
|
-
- Stop on first failure: `python tests/scripts/comprehensive_test_runner.py --stop`
|
|
158
|
-
- Optional JUnit export: `python tests/scripts/comprehensive_test_runner.py --junit tests/scripts/report.xml`
|
|
159
|
-
- Pytest directly (offline/default): `uv run pytest -q -m "not live" --cov=src/spice_mcp --cov-report=term-missing`
|
|
160
|
-
|
|
161
|
-
Core Tools (with parameters)
|
|
162
|
-
- `dune_query`
|
|
163
|
-
- Use: Preview/query results by ID, URL, or raw SQL (Polars preview + Dune metadata/pagination).
|
|
164
|
-
- Params: `query` (string), `parameters?` (object), `performance?` ('medium'|'large'), `limit?` (int), `offset?` (int), `sort_by?` (string), `columns?` (string[]), `sample_count?` (int), `refresh?` (bool), `max_age?` (number), `timeout_seconds?` (number), `format?` ('preview'|'raw'|'metadata').
|
|
165
|
-
- Output: `type`, `rowcount`, `columns`, `data_preview`, `execution_id`, `duration_ms`, `metadata?`, `next_uri?`, `next_offset?`.
|
|
166
|
-
- `dune_find_tables`
|
|
167
|
-
- Use: Search schemas by keyword and/or list tables for a schema.
|
|
168
|
-
- Params: `keyword?` (string), `schema?` (string), `limit?` (int)
|
|
169
|
-
- Output: `schemas?` (string[]), `tables?` (string[])
|
|
170
|
-
- `dune_describe_table`
|
|
171
|
-
- Use: Column descriptions for `schema.table` via SHOW + fallback to 1-row sample inference.
|
|
172
|
-
- Params: `schema` (string), `table` (string)
|
|
173
|
-
- Output: `columns` ([{ name, dune_type?, polars_dtype?, extra?, comment? }])
|
|
174
|
-
- `sui_package_overview`
|
|
175
|
-
- Use: Small-window overview for Sui packages (events/transactions/objects) with timeout handling.
|
|
176
|
-
- Params: `packages` (string[]), `hours?` (int, default 72), `timeout_seconds?` (number, default 30)
|
|
177
|
-
- Output: best-effort counts and previews; may include `*_timeout`/`*_error`
|
|
178
|
-
- `dune_health_check`
|
|
179
|
-
- Use: Verify API key presence and logging paths
|
|
180
|
-
- Output: `api_key_present`, `query_history_path`, `logging_enabled`, `status`
|
|
181
|
-
|
|
182
|
-
## Docs
|
|
183
|
-
- See [docs/index.md](docs/index.md) for full documentation:
|
|
184
|
-
- Dune API structure and capabilities: [docs/dune_api.md](docs/dune_api.md)
|
|
185
|
-
- Discovery patterns and examples: [docs/discovery.md](docs/discovery.md)
|
|
186
|
-
|
|
187
|
-
- Tool reference and schemas: [docs/tools.md](docs/tools.md)
|
|
188
|
-
- Codex CLI + tooling integration: [docs/codex_cli.md](docs/codex_cli.md), [docs/codex_cli_tools.md](docs/codex_cli_tools.md)
|
|
189
|
-
- Architecture overview: [docs/architecture.md](docs/architecture.md)
|
|
190
|
-
- Installation and configuration: [docs/installation.md](docs/installation.md), [docs/config.md](docs/config.md)
|
|
191
|
-
- Development and linting: [docs/development.md](docs/development.md)
|
|
192
|
-
|
|
193
|
-
Notes
|
|
194
|
-
- Legacy Spice code now lives under `src/spice_mcp/adapters/dune` (extract, cache, urls, types).
|
|
195
|
-
- Ports and models live in `src/spice_mcp/core`; services consume ports and are exercised by FastMCP tools.
|
|
196
|
-
- Query history and SQL artefacts are always-on (see `src/spice_mcp/logging/query_history.py`).
|
|
197
|
-
- To bypass dot-env loading during tests/CI, export `SPICE_MCP_SKIP_DOTENV=1`.
|
|
198
|
-
- LazyFrames everywhere: eager `.collect()` or `pl.DataFrame` usage outside dedicated helpers is blocked by `tests/style/test_polars_lazy.py`; materialization helpers live in `src/spice_mcp/polars_utils.py`.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|