spice-mcp 0.1.1__tar.gz → 0.1.2__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.1 → spice_mcp-0.1.2}/.gitignore +6 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/PKG-INFO +1 -1
- spice_mcp-0.1.2/debug_mcp_tool.py +147 -0
- spice_mcp-0.1.2/debug_template_query.py +147 -0
- spice_mcp-0.1.2/issue_diagnostics.py +76 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/pyproject.toml +1 -1
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/dune/typing_utils.py +8 -1
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/mcp/server.py +1 -20
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/mcp/tools/execute_query.py +23 -1
- spice_mcp-0.1.2/test_semaphore_fix.py +101 -0
- spice_mcp-0.1.2/test_sync_fix.py +55 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/fastmcp/test_server_fastmcp.py +2 -3
- spice_mcp-0.1.2/tests/mcp/test_tool_contracts.py +55 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/uv.lock +1 -1
- spice_mcp-0.1.1/.factory/droids/agentic-documentation-systems-droid.md +0 -16
- spice_mcp-0.1.1/.factory/droids/development-workflow-optimization-droid.md +0 -16
- spice_mcp-0.1.1/.factory/droids/production-python-development-droid.md +0 -18
- spice_mcp-0.1.1/.factory/droids/temp-smoke-droid.md +0 -8
- spice_mcp-0.1.1/.github/workflows/ci.yml +0 -144
- spice_mcp-0.1.1/bin/bat +0 -18
- spice_mcp-0.1.1/bin/fd +0 -18
- spice_mcp-0.1.1/debug_api.py +0 -97
- spice_mcp-0.1.1/tests/mcp/test_tool_contracts.py +0 -58
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/.python-version +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/CONTRIBUTING.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/LICENSE +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/README.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/architecture.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/codex_cli.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/codex_cli_tools.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/commands.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/config.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/development.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/discovery.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/dune_api.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/index.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/installation.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/docs/tools.md +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/pytest.ini +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/scripts/bridgez/make_circle_comparison.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/scripts/codex_tools_doctor.sh +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/__init__.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/__init__.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/dune/__init__.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/dune/admin.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/dune/cache.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/dune/client.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/dune/extract.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/dune/helpers.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/dune/transport.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/dune/types.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/dune/urls.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/adapters/http_client.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/config.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/core/__init__.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/core/errors.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/core/models.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/core/ports.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/logging/query_history.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/mcp/__init__.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/mcp/tools/__init__.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/mcp/tools/base.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/mcp/tools/sui_package_overview.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/observability/__init__.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/observability/logging.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/polars_utils.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/py.typed +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/service_layer/__init__.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/service_layer/discovery_service.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/service_layer/query_admin_service.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/service_layer/query_service.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/src/spice_mcp/service_layer/sui_service.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/cassettes/.gitkeep +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/config/environments.yaml +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/config/test_queries.yaml +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/conftest.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/fastmcp/test_resources_and_validation.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/fastmcp/test_server_mcp_extras.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/http_stubbed/test_age.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/http_stubbed/test_errors.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/http_stubbed/test_pagination.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/live/test_live_basic.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/live/test_live_sui.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/mcp/conftest.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/mcp/test_resources.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/offline/test_cache.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/offline/test_discovery.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/offline/test_dune_adapter.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/offline/test_parsing.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/offline/test_query_history.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/offline/test_show_rewrite.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/offline/test_timeout.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/offline/test_typing_utils.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/offline/test_urls.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/comprehensive_test_runner.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/run_tests.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_api_health.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_cache_functionality.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_data_types.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_dune_connectivity.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_dune_query_execution.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_error_handling.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_mcp_simulation.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_mcp_tools.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_performance.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_query_lifecycle.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_resilience.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/scripts/test_resource_management.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/style/test_polars_lazy.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/support/api_client.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/support/helpers.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/support/query_factory.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/support/test_data.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/tools/test_additional_mcp_tools.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/tools/test_execute_query_tool.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/tools/test_health_tool.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/tools/test_query_service.py +0 -0
- {spice_mcp-0.1.1 → spice_mcp-0.1.2}/tests/tools/test_schemas.py +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Debug script to test the MCP tool layer directly.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import json
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# Add src to path
|
|
12
|
+
sys.path.insert(0, '/Users/evandekim/Documents/spice_mcp/src')
|
|
13
|
+
|
|
14
|
+
from spice_mcp.config import Config
|
|
15
|
+
from spice_mcp.logging.query_history import QueryHistory
|
|
16
|
+
from spice_mcp.mcp.tools.execute_query import ExecuteQueryTool
|
|
17
|
+
from spice_mcp.service_layer.query_service import QueryService
|
|
18
|
+
from spice_mcp.adapters.dune.client import DuneAdapter
|
|
19
|
+
|
|
20
|
+
def test_mcp_tool_directly():
|
|
21
|
+
"""Test the MCP tool directly"""
|
|
22
|
+
|
|
23
|
+
# Ensure API key is available
|
|
24
|
+
api_key = os.getenv('DUNE_API_KEY')
|
|
25
|
+
if not api_key:
|
|
26
|
+
print("❌ DUNE_API_KEY not set")
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
print("🔍 Testing MCP tool directly...")
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
# Setup the components
|
|
33
|
+
from spice_mcp.config import DuneConfig, CacheConfig, HttpClientConfig
|
|
34
|
+
config = Config(
|
|
35
|
+
dune=DuneConfig(api_key=api_key),
|
|
36
|
+
cache=CacheConfig(),
|
|
37
|
+
http=HttpClientConfig()
|
|
38
|
+
)
|
|
39
|
+
adapter = DuneAdapter(config)
|
|
40
|
+
query_service = QueryService(adapter)
|
|
41
|
+
query_history = QueryHistory(Path("/tmp/query_history.jsonl"), Path("/tmp/sql_artifacts"))
|
|
42
|
+
|
|
43
|
+
# Create the tool
|
|
44
|
+
tool = ExecuteQueryTool(config, query_service, query_history)
|
|
45
|
+
|
|
46
|
+
# Test simple raw SQL
|
|
47
|
+
print("\n📝 Testing MCP tool with 'SELECT 1 as test'")
|
|
48
|
+
|
|
49
|
+
# This should work via the MCP tool interface
|
|
50
|
+
import asyncio
|
|
51
|
+
result = asyncio.run(tool.execute(
|
|
52
|
+
query="SELECT 1 as test",
|
|
53
|
+
refresh=True,
|
|
54
|
+
format="preview"
|
|
55
|
+
))
|
|
56
|
+
|
|
57
|
+
print(f"✅ Tool result type: {type(result)}")
|
|
58
|
+
print(f"✅ Tool result keys: {list(result.keys()) if isinstance(result, dict) else 'not dict'}")
|
|
59
|
+
|
|
60
|
+
if isinstance(result, dict):
|
|
61
|
+
print(f"✅ Response type: {result.get('type')}")
|
|
62
|
+
print(f"✅ Row count: {result.get('rowcount')}")
|
|
63
|
+
print(f"✅ Execution ID: {result.get('execution_id')}")
|
|
64
|
+
if 'error' in result:
|
|
65
|
+
print(f"❌ Error: {result['error']}")
|
|
66
|
+
return False
|
|
67
|
+
else:
|
|
68
|
+
print("✅ Success! MCP tool executed raw SQL correctly")
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
print(f"❌ MCP tool test failed: {e}")
|
|
72
|
+
print(f" Error type: {type(e)}")
|
|
73
|
+
import traceback
|
|
74
|
+
traceback.print_exc()
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
def test_service_layer_directly():
|
|
80
|
+
"""Test the service layer directly"""
|
|
81
|
+
|
|
82
|
+
# Ensure API key is available
|
|
83
|
+
api_key = os.getenv('DUNE_API_KEY')
|
|
84
|
+
if not api_key:
|
|
85
|
+
print("❌ DUNE_API_KEY not set")
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
print("🔍 Testing service layer directly...")
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
# Setup the components
|
|
92
|
+
from spice_mcp.config import DuneConfig, CacheConfig, HttpClientConfig
|
|
93
|
+
config = Config(
|
|
94
|
+
dune=DuneConfig(api_key=api_key),
|
|
95
|
+
cache=CacheConfig(),
|
|
96
|
+
http=HttpClientConfig()
|
|
97
|
+
)
|
|
98
|
+
adapter = DuneAdapter(config)
|
|
99
|
+
query_service = QueryService(adapter)
|
|
100
|
+
|
|
101
|
+
# Test service layer directly
|
|
102
|
+
print("\n📝 Testing service layer with 'SELECT 1 as test'")
|
|
103
|
+
|
|
104
|
+
result = query_service.execute(
|
|
105
|
+
query="SELECT 1 as test",
|
|
106
|
+
refresh=True,
|
|
107
|
+
include_execution=True
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
print(f"✅ Service result type: {type(result)}")
|
|
111
|
+
print(f"✅ Service result keys: {list(result.keys()) if isinstance(result, dict) else 'not dict'}")
|
|
112
|
+
|
|
113
|
+
if isinstance(result, dict):
|
|
114
|
+
print(f"✅ Row count: {result.get('rowcount')}")
|
|
115
|
+
print(f"✅ Execution: {result.get('execution')}")
|
|
116
|
+
if 'execution' in result and result['execution'].get('execution_id'):
|
|
117
|
+
print("✅ Service layer executed successfully!")
|
|
118
|
+
return True
|
|
119
|
+
else:
|
|
120
|
+
print("❌ Service layer failed - no execution ID")
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
print(f"❌ Service layer test failed: {e}")
|
|
125
|
+
import traceback
|
|
126
|
+
traceback.print_exc()
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
if __name__ == "__main__":
|
|
132
|
+
print("🐛 Debugging spice-mcp MCP tool layer...")
|
|
133
|
+
|
|
134
|
+
success = True
|
|
135
|
+
|
|
136
|
+
# Test 1: Service layer
|
|
137
|
+
success &= test_service_layer_directly()
|
|
138
|
+
|
|
139
|
+
# Test 2: MCP tool
|
|
140
|
+
success &= test_mcp_tool_directly()
|
|
141
|
+
|
|
142
|
+
if success:
|
|
143
|
+
print("\n✅ All tests passed!")
|
|
144
|
+
sys.exit(0)
|
|
145
|
+
else:
|
|
146
|
+
print("\n❌ Some tests failed!")
|
|
147
|
+
sys.exit(1)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Debug script to test template query 4060379 execution directly.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
# Add src to path
|
|
11
|
+
sys.path.insert(0, '/Users/evandekim/Documents/spice_mcp/src')
|
|
12
|
+
|
|
13
|
+
from spice_mcp.adapters.dune import extract, urls
|
|
14
|
+
|
|
15
|
+
def test_template_query_directly():
|
|
16
|
+
"""Test the template query directly via extract.query()"""
|
|
17
|
+
|
|
18
|
+
# Ensure API key is available
|
|
19
|
+
api_key = os.getenv('DUNE_API_KEY')
|
|
20
|
+
if not api_key:
|
|
21
|
+
print("❌ DUNE_API_KEY not set")
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
template_id = int(os.getenv("SPICE_RAW_SQL_QUERY_ID", "4060379"))
|
|
25
|
+
print(f"🔍 Testing template query {template_id} directly...")
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
# Test 1: Execute simple raw SQL
|
|
29
|
+
print("\n📝 Test 1: Simple SELECT 1")
|
|
30
|
+
result = extract.query(
|
|
31
|
+
query_or_execution="SELECT 1 as test",
|
|
32
|
+
verbose=True,
|
|
33
|
+
refresh=True,
|
|
34
|
+
poll=True,
|
|
35
|
+
api_key=api_key,
|
|
36
|
+
timeout_seconds=30
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
print(f"✅ Result type: {type(result)}")
|
|
40
|
+
if hasattr(result, 'shape'):
|
|
41
|
+
print(f"✅ Result shape: {result.shape}")
|
|
42
|
+
print(f"✅ Result columns: {list(result.columns) if hasattr(result, 'columns') else 'N/A'}")
|
|
43
|
+
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f"❌ Test 1 failed: {e}")
|
|
46
|
+
print(f" Error type: {type(e)}")
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
# Test 2: Test with parameters explicitly
|
|
51
|
+
print("\n📝 Test 2: Raw SQL with explicit parameters")
|
|
52
|
+
result = extract.query(
|
|
53
|
+
query_or_execution="SELECT 1 as test",
|
|
54
|
+
verbose=True,
|
|
55
|
+
refresh=True,
|
|
56
|
+
poll=True,
|
|
57
|
+
api_key=api_key,
|
|
58
|
+
parameters={'query': 'SELECT 1 as test'},
|
|
59
|
+
timeout_seconds=30
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
print(f"✅ Result type: {type(result)}")
|
|
63
|
+
if hasattr(result, 'shape'):
|
|
64
|
+
print(f"✅ Result shape: {result.shape}")
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"❌ Test 2 failed: {e}")
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
def test_determine_input_type():
|
|
73
|
+
"""Test the determine_input_type function"""
|
|
74
|
+
print("\n🔍 Testing determine_input_type...")
|
|
75
|
+
|
|
76
|
+
query_id, execution, parameters = extract.determine_input_type("SELECT 1 as test")
|
|
77
|
+
|
|
78
|
+
print(f"✅ Query ID: {query_id}")
|
|
79
|
+
print(f"✅ Execution: {execution}")
|
|
80
|
+
print(f"✅ Parameters: {parameters}")
|
|
81
|
+
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
def test_template_query_api_check():
|
|
85
|
+
"""Check if the template query actually exists and is accessible"""
|
|
86
|
+
template_id = int(os.getenv("SPICE_RAW_SQL_QUERY_ID", "4060379"))
|
|
87
|
+
api_key = os.getenv('DUNE_API_KEY')
|
|
88
|
+
|
|
89
|
+
if not api_key:
|
|
90
|
+
print("❌ DUNE_API_KEY not set")
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
print(f"\n🔍 Checking template query {template_id} metadata...")
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
# Check if the query exists by trying to get its info
|
|
97
|
+
url = urls.get_query_execute_url(template_id)
|
|
98
|
+
headers = {'X-Dune-API-Key': api_key, 'User-Agent': extract.get_user_agent()}
|
|
99
|
+
|
|
100
|
+
print(f"🌐 Query execute URL: {url}")
|
|
101
|
+
|
|
102
|
+
# Try to execute with empty parameters first
|
|
103
|
+
import requests
|
|
104
|
+
|
|
105
|
+
response = requests.post(
|
|
106
|
+
url,
|
|
107
|
+
headers=headers,
|
|
108
|
+
json={'query_parameters': {}, 'performance': 'medium'},
|
|
109
|
+
timeout=30
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
print(f"📊 Status code: {response.status_code}")
|
|
113
|
+
|
|
114
|
+
if response.status_code == 200:
|
|
115
|
+
result = response.json()
|
|
116
|
+
print(f"✅ Execution result: {json.dumps(result, indent=2)}")
|
|
117
|
+
else:
|
|
118
|
+
print(f"❌ API Response: {response.text}")
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
except Exception as e:
|
|
122
|
+
print(f"❌ Template query check failed: {e}")
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
return True
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
print("🐛 Debugging spice-mcp raw SQL execution issues...")
|
|
129
|
+
|
|
130
|
+
success = True
|
|
131
|
+
|
|
132
|
+
# Test 1: Determine input type
|
|
133
|
+
success &= test_determine_input_type()
|
|
134
|
+
|
|
135
|
+
# Test 2: Check template query API
|
|
136
|
+
success &= test_template_query_api_check()
|
|
137
|
+
|
|
138
|
+
# Test 3: Direct execution
|
|
139
|
+
print(f"\n🚀 About to test raw SQL execution...")
|
|
140
|
+
success &= test_template_query_directly()
|
|
141
|
+
|
|
142
|
+
if success:
|
|
143
|
+
print("\n✅ All tests passed!")
|
|
144
|
+
sys.exit(0)
|
|
145
|
+
else:
|
|
146
|
+
print("\n❌ Some tests failed!")
|
|
147
|
+
sys.exit(1)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Raw SQL Execution Issue Analysis
|
|
2
|
+
|
|
3
|
+
## ✅ GOOD NEWS: Core Code is Working Correctly!
|
|
4
|
+
|
|
5
|
+
After extensive debugging, I've determined that the core spice-mcp code is **working perfectly**. Both the Dune adapter layer and MCP tool layer successfully execute raw SQL queries when properly configured.
|
|
6
|
+
|
|
7
|
+
## 🐛 Root Cause: Environmental/Configuration Issue
|
|
8
|
+
|
|
9
|
+
The issue reported in #6 is **not in the code itself** but rather in how users are running the MCP server. The symptoms suggest:
|
|
10
|
+
|
|
11
|
+
1. **Environment variable issue**: `SPICE_RAW_SQL_QUERY_ID` may not be set in the MCP server context
|
|
12
|
+
2. **MCP server setup issue**: The FastMCP server may not be properly configured
|
|
13
|
+
3. **Path/import issues**: The MCP server may be running in a different environment
|
|
14
|
+
|
|
15
|
+
## 🔧 Proposed Solutions
|
|
16
|
+
|
|
17
|
+
### Option 1: Hard-code fallback (Recommended)
|
|
18
|
+
Make the template ID resolution more robust by providing a fallback:
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
def resolve_raw_sql_template_id() -> int:
|
|
22
|
+
"""Return a stable template ID used for executing raw SQL text."""
|
|
23
|
+
# Try environment variable first, fallback to hardcoded value
|
|
24
|
+
env_value = os.getenv("SPICE_RAW_SQL_QUERY_ID")
|
|
25
|
+
if env_value and env_value.strip():
|
|
26
|
+
try:
|
|
27
|
+
return int(env_value.strip())
|
|
28
|
+
except ValueError:
|
|
29
|
+
pass
|
|
30
|
+
# Fallback to known working template ID
|
|
31
|
+
return 4060379
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Option 2: Enhanced Diagnostics
|
|
35
|
+
Add better error reporting in the MCP tool to help users debug their setup:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
# Add environment variable check at tool startup
|
|
39
|
+
if not os.getenv("SPICE_RAW_SQL_QUERY_ID"):
|
|
40
|
+
print("Warning: SPICE_RAW_SQL_QUERY_ID not set, using default template ID")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Option 3: Verify Tool
|
|
44
|
+
Create a verification tool that tests the raw SQL path:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
def verify_raw_sql_config(self) -> dict:
|
|
48
|
+
"""Verify that raw SQL configuration is working."""
|
|
49
|
+
try:
|
|
50
|
+
template_id = resolve_raw_sql_template_id()
|
|
51
|
+
return {
|
|
52
|
+
"status": "ok",
|
|
53
|
+
"template_id": template_id,
|
|
54
|
+
"env_var_set": bool(os.getenv("SPICE_RAW_SQL_QUERY_ID"))
|
|
55
|
+
}
|
|
56
|
+
except Exception as e:
|
|
57
|
+
return {"status": "error", "error": str(e)}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 🎯 User Action Required
|
|
61
|
+
|
|
62
|
+
For users experiencing this issue:
|
|
63
|
+
|
|
64
|
+
1. **Set environment variable properly in MCP server context**
|
|
65
|
+
2. **Verify MCP server startup logs** for any authentication or import errors
|
|
66
|
+
3. **Use the enhanced diagnostics** (once implemented)
|
|
67
|
+
|
|
68
|
+
## 📋 Test Results Summary
|
|
69
|
+
|
|
70
|
+
- ✅ Template ID resolution: Working
|
|
71
|
+
- ✅ Dune adapter raw SQL: Working
|
|
72
|
+
- ✅ MCP tool raw SQL: Working
|
|
73
|
+
- ✅ Service layer: Working
|
|
74
|
+
- ❌ User environment: Needs investigation
|
|
75
|
+
|
|
76
|
+
The fix in 0.1.1 addressed the core issue. The remaining problem is likely user environment specific.
|
|
@@ -9,4 +9,11 @@ def resolve_raw_sql_template_id() -> int:
|
|
|
9
9
|
Tests stub HTTP boundaries and only require a consistent integer. This
|
|
10
10
|
placeholder can be adjusted if upstream semantics change.
|
|
11
11
|
"""
|
|
12
|
-
|
|
12
|
+
env_value = os.getenv("SPICE_RAW_SQL_QUERY_ID")
|
|
13
|
+
if env_value:
|
|
14
|
+
try:
|
|
15
|
+
return int(env_value.strip())
|
|
16
|
+
except (ValueError, AttributeError):
|
|
17
|
+
# Invalid environment variable, fallback to default
|
|
18
|
+
pass
|
|
19
|
+
return 4060379
|
|
@@ -229,26 +229,7 @@ async def dune_query(
|
|
|
229
229
|
_ensure_initialized()
|
|
230
230
|
assert EXECUTE_QUERY_TOOL is not None
|
|
231
231
|
try:
|
|
232
|
-
#
|
|
233
|
-
if 'asyncio' not in globals():
|
|
234
|
-
pass # type: ignore
|
|
235
|
-
if SEMAPHORE is not None:
|
|
236
|
-
async with SEMAPHORE: # type: ignore
|
|
237
|
-
return await EXECUTE_QUERY_TOOL.execute(
|
|
238
|
-
query=query,
|
|
239
|
-
parameters=parameters,
|
|
240
|
-
refresh=refresh,
|
|
241
|
-
max_age=max_age,
|
|
242
|
-
limit=limit,
|
|
243
|
-
offset=offset,
|
|
244
|
-
sample_count=sample_count,
|
|
245
|
-
sort_by=sort_by,
|
|
246
|
-
columns=columns,
|
|
247
|
-
format=format,
|
|
248
|
-
extras=extras,
|
|
249
|
-
timeout_seconds=timeout_seconds,
|
|
250
|
-
)
|
|
251
|
-
# Fallback without semaphore
|
|
232
|
+
# Execute query directly without semaphore concurrency control
|
|
252
233
|
return await EXECUTE_QUERY_TOOL.execute(
|
|
253
234
|
query=query,
|
|
254
235
|
parameters=parameters,
|
|
@@ -313,6 +313,15 @@ class ExecuteQueryTool(MCPTool):
|
|
|
313
313
|
if template_id_value is not None:
|
|
314
314
|
extra_fields["template_query_id"] = template_id_value
|
|
315
315
|
|
|
316
|
+
# Compute query SHA256 for better debugging
|
|
317
|
+
q_sha = None
|
|
318
|
+
try:
|
|
319
|
+
q_sha = self._persist_query_sql(q_use, q_type)
|
|
320
|
+
if q_sha:
|
|
321
|
+
extra_fields["query_sha256"] = q_sha
|
|
322
|
+
except Exception:
|
|
323
|
+
pass
|
|
324
|
+
|
|
316
325
|
self.query_history.record(
|
|
317
326
|
execution_id="unknown",
|
|
318
327
|
query_type=_categorize_query(query),
|
|
@@ -324,9 +333,22 @@ class ExecuteQueryTool(MCPTool):
|
|
|
324
333
|
)
|
|
325
334
|
|
|
326
335
|
enriched = self._enrich_error(exc)
|
|
327
|
-
context = {"tool": "dune_query", "query": q_use}
|
|
336
|
+
context = {"tool": "dune_query", "query": q_use, "query_type": _categorize_query(q_use)}
|
|
328
337
|
if enriched:
|
|
329
338
|
context.update(enriched)
|
|
339
|
+
|
|
340
|
+
# Add debugging information for raw SQL failures
|
|
341
|
+
if q_type == "raw_sql" and "could not determine execution" in str(exc):
|
|
342
|
+
context.update({
|
|
343
|
+
"debug_info": "Raw SQL execution failed - this may be related to FastMCP async/concurrency handling",
|
|
344
|
+
"template_query_id": template_id_value,
|
|
345
|
+
"environment_vars": {
|
|
346
|
+
"SPICE_RAW_SQL_QUERY_ID": os.getenv("SPICE_RAW_SQL_QUERY_ID"),
|
|
347
|
+
"DUNE_API_KEY_present": bool(os.getenv("DUNE_API_KEY"))
|
|
348
|
+
},
|
|
349
|
+
"suggested_action": "Retry or check if the template query (4060379) is accessible"
|
|
350
|
+
})
|
|
351
|
+
|
|
330
352
|
return error_response(exc, context=context)
|
|
331
353
|
|
|
332
354
|
def _persist_query_sql(self, query: str, q_type: str) -> str | None:
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test script to verify the semaphore fix works for raw SQL execution.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import asyncio
|
|
9
|
+
sys.path.insert(0, '/Users/evandekim/Documents/spice_mcp/src')
|
|
10
|
+
|
|
11
|
+
from spice_mcp.mcp.tools.execute_query import ExecuteQueryTool
|
|
12
|
+
from spice_mcp.config import Config, DuneConfig, CacheConfig, HttpClientConfig
|
|
13
|
+
from spice_mcp.logging.query_history import QueryHistory
|
|
14
|
+
from spice_mcp.service_layer.query_service import QueryService
|
|
15
|
+
from spice_mcp.adapters.dune.client import DuneAdapter
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
def test_direct_vs_mcp_calls():
|
|
19
|
+
"""Test the difference between direct tool calls and MCP server calls."""
|
|
20
|
+
|
|
21
|
+
# Ensure API key is available
|
|
22
|
+
api_key = os.getenv('DUNE_API_KEY')
|
|
23
|
+
if not api_key:
|
|
24
|
+
print("❌ DUNE_API_KEY not set")
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
print("🔍 Comparing direct tool calls vs MCP server calls...")
|
|
28
|
+
|
|
29
|
+
# Setup all components
|
|
30
|
+
config = Config(
|
|
31
|
+
dune=DuneConfig(api_key=api_key),
|
|
32
|
+
cache=CacheConfig(),
|
|
33
|
+
http=HttpClientConfig()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
adapter = DuneAdapter(config)
|
|
37
|
+
query_service = QueryService(adapter)
|
|
38
|
+
query_history = QueryHistory(Path("/tmp/query_history.jsonl"), Path("/tmp/sql_artifacts"))
|
|
39
|
+
|
|
40
|
+
# Create the tool
|
|
41
|
+
tool = ExecuteQueryTool(config, query_service, query_history)
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
# Test 1: Direct tool call (this should work)
|
|
45
|
+
print("\n📝 Test 1: Direct tool call")
|
|
46
|
+
direct_result = asyncio.run(tool.execute(
|
|
47
|
+
query="SELECT 1 as test",
|
|
48
|
+
refresh=True,
|
|
49
|
+
format="preview"
|
|
50
|
+
))
|
|
51
|
+
print(f"✅ Direct call succeeded: {type(direct_result)}, keys: {list(direct_result.keys()) if isinstance(direct_result, dict) else 'not dict'}")
|
|
52
|
+
|
|
53
|
+
# Test 2: MCP server call (simulate what fails)
|
|
54
|
+
print("\n📝 Test 2: MCP server call simulation")
|
|
55
|
+
|
|
56
|
+
# Import the server module
|
|
57
|
+
from spice_mcp.mcp import server as mcp_server
|
|
58
|
+
|
|
59
|
+
# Initialize the server
|
|
60
|
+
mcp_server._ensure_initialized()
|
|
61
|
+
mcp_server.EXECUTE_QUERY_TOOL = tool # Use our tool instance
|
|
62
|
+
|
|
63
|
+
# Try calling the MCP tool function
|
|
64
|
+
mcp_result = asyncio.run(mcp_server.dune_query(
|
|
65
|
+
query="SELECT 1 as test",
|
|
66
|
+
refresh=True,
|
|
67
|
+
format="preview"
|
|
68
|
+
))
|
|
69
|
+
print(f"✅ MCP call succeeded: {type(mcp_result)}, keys: {list(mcp_result.keys()) if isinstance(mcp_result, dict) else 'not dict'}")
|
|
70
|
+
|
|
71
|
+
# Compare results
|
|
72
|
+
print("\n📊 Comparison:")
|
|
73
|
+
print(f"Direct call execution_id: {direct_result.get('execution_id', 'N/A')}")
|
|
74
|
+
print(f"MCP call execution_id: {mcp_result.get('execution_id', 'N/A')}")
|
|
75
|
+
|
|
76
|
+
# Check if both have execution_ids
|
|
77
|
+
if (direct_result.get('execution_id') and direct_result.get('execution_id') != 'unknown' and
|
|
78
|
+
mcp_result.get('execution_id') and mcp_result.get('execution_id') != 'unknown'):
|
|
79
|
+
print("✅ SUCCESS: Both calls have valid execution IDs!")
|
|
80
|
+
return True
|
|
81
|
+
else:
|
|
82
|
+
print("❌ FAILURE: One or both calls failed to get execution ID")
|
|
83
|
+
if (mcp_result.get('execution_id') == 'unknown'):
|
|
84
|
+
print(" MCP call has execution_id='unknown' - this is the bug!")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f"❌ Test failed: {e}")
|
|
89
|
+
import traceback
|
|
90
|
+
traceback.print_exc()
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
print("🐛 Testing semaphore removal fix...")
|
|
95
|
+
success = test_direct_vs_mcp_calls()
|
|
96
|
+
|
|
97
|
+
if success:
|
|
98
|
+
print("\n✅ Semaphore removal fix works!")
|
|
99
|
+
else:
|
|
100
|
+
print("\n❌ Semaphore removal fix still issues!")
|
|
101
|
+
sys.exit(0 if success else 1)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Test the synchronous fix for raw SQL execution.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
sys.path.insert(0, '/Users/evandekim/Documents/spice_mcp/src')
|
|
9
|
+
|
|
10
|
+
from spice_mcp.mcp.server import dune_query
|
|
11
|
+
|
|
12
|
+
def test_sync_dune_query():
|
|
13
|
+
"""Test the sync version of dune_query tool"""
|
|
14
|
+
print("🐛 Testing synchronous dune_query tool...")
|
|
15
|
+
|
|
16
|
+
# Set up environment if needed
|
|
17
|
+
if not os.getenv('DUNE_API_KEY'):
|
|
18
|
+
print("❌ DUNE_API_KEY not set")
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
result = dune_query(
|
|
23
|
+
query="SELECT 1 as test",
|
|
24
|
+
refresh=True,
|
|
25
|
+
format="preview"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
print(f"✅ Result type: {type(result)}")
|
|
29
|
+
print(f"✅ Result keys: {list(result.keys()) if isinstance(result, dict) else 'not dict'}")
|
|
30
|
+
|
|
31
|
+
if isinstance(result, dict):
|
|
32
|
+
print(f"✅ Response type: {result.get('type')}")
|
|
33
|
+
print(f"✅ Row count: {result.get('rowcount')}")
|
|
34
|
+
print(f"✅ Execution ID: {result.get('execution_id')}")
|
|
35
|
+
if 'error' in result:
|
|
36
|
+
print(f"❌ Error: {result['error']}")
|
|
37
|
+
return False
|
|
38
|
+
else:
|
|
39
|
+
print("✅ Success! Synchronous dune_query executed raw SQL correctly")
|
|
40
|
+
|
|
41
|
+
except Exception as e:
|
|
42
|
+
print(f"❌ Test failed: {e}")
|
|
43
|
+
import traceback
|
|
44
|
+
traceback.print_exc()
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
success = test_sync_dune_query()
|
|
51
|
+
if success:
|
|
52
|
+
print("\n✅ Synchronous fix working!")
|
|
53
|
+
else:
|
|
54
|
+
print("\n❌ Synchronous fix failed!")
|
|
55
|
+
sys.exit(0 if success else 1)
|
|
@@ -36,8 +36,7 @@ async def test_health_tool_executes(monkeypatch, tmp_path):
|
|
|
36
36
|
assert "api_key_present" in result
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
async def test_dune_query_delegates_and_returns_preview(monkeypatch, tmp_path):
|
|
39
|
+
def test_dune_query_delegates_and_returns_preview(monkeypatch, tmp_path):
|
|
41
40
|
monkeypatch.setenv("DUNE_API_KEY", "test-key")
|
|
42
41
|
monkeypatch.setenv("SPICE_QUERY_HISTORY", str(tmp_path / "queries.jsonl"))
|
|
43
42
|
|
|
@@ -66,7 +65,7 @@ async def test_dune_query_delegates_and_returns_preview(monkeypatch, tmp_path):
|
|
|
66
65
|
monkeypatch.setattr(server.EXECUTE_QUERY_TOOL.query_history, "record", lambda **k: None)
|
|
67
66
|
|
|
68
67
|
# Act: call underlying tool directly to avoid FastMCP internals
|
|
69
|
-
res =
|
|
68
|
+
res = server.EXECUTE_QUERY_TOOL.execute(query="select 1", format="preview") # type: ignore[union-attr]
|
|
70
69
|
|
|
71
70
|
# Assert
|
|
72
71
|
assert res["type"] == "preview"
|