sqlsaber 0.3.0__py3-none-any.whl → 0.4.0__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.
Potentially problematic release.
This version of sqlsaber might be problematic. Click here for more details.
- sqlsaber/agents/mcp.py +21 -0
- sqlsaber/database/schema.py +2 -2
- sqlsaber/mcp/__init__.py +5 -0
- sqlsaber/mcp/mcp.py +138 -0
- {sqlsaber-0.3.0.dist-info → sqlsaber-0.4.0.dist-info}/METADATA +40 -1
- {sqlsaber-0.3.0.dist-info → sqlsaber-0.4.0.dist-info}/RECORD +9 -6
- {sqlsaber-0.3.0.dist-info → sqlsaber-0.4.0.dist-info}/entry_points.txt +2 -0
- {sqlsaber-0.3.0.dist-info → sqlsaber-0.4.0.dist-info}/WHEEL +0 -0
- {sqlsaber-0.3.0.dist-info → sqlsaber-0.4.0.dist-info}/licenses/LICENSE +0 -0
sqlsaber/agents/mcp.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Generic SQL agent implementation for MCP tools."""
|
|
2
|
+
|
|
3
|
+
from typing import AsyncIterator
|
|
4
|
+
from sqlsaber.agents.base import BaseSQLAgent
|
|
5
|
+
from sqlsaber.database.connection import BaseDatabaseConnection
|
|
6
|
+
from sqlsaber.models.events import StreamEvent
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MCPSQLAgent(BaseSQLAgent):
|
|
10
|
+
"""MCP SQL Agent for MCP tool operations without LLM-specific logic."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, db_connection: BaseDatabaseConnection):
|
|
13
|
+
super().__init__(db_connection)
|
|
14
|
+
|
|
15
|
+
async def query_stream(
|
|
16
|
+
self, user_query: str, use_history: bool = True
|
|
17
|
+
) -> AsyncIterator[StreamEvent]:
|
|
18
|
+
"""Not implemented for generic agent as it's only used for tool operations."""
|
|
19
|
+
raise NotImplementedError(
|
|
20
|
+
"MCPSQLAgent does not support query streaming. Use specific agent implementations for conversation."
|
|
21
|
+
)
|
sqlsaber/database/schema.py
CHANGED
|
@@ -201,8 +201,8 @@ class PostgreSQLSchemaIntrospector(BaseSchemaIntrospector):
|
|
|
201
201
|
t.table_type,
|
|
202
202
|
COALESCE(ts.approximate_row_count, 0) as row_count
|
|
203
203
|
FROM information_schema.tables t
|
|
204
|
-
LEFT JOIN table_stats ts
|
|
205
|
-
ON t.table_schema = ts.schemaname
|
|
204
|
+
LEFT JOIN table_stats ts
|
|
205
|
+
ON t.table_schema = ts.schemaname
|
|
206
206
|
AND t.table_name = ts.tablename
|
|
207
207
|
WHERE t.table_schema NOT IN ('pg_catalog', 'information_schema')
|
|
208
208
|
ORDER BY t.table_schema, t.table_name;
|
sqlsaber/mcp/__init__.py
ADDED
sqlsaber/mcp/mcp.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""FastMCP server implementation for SQLSaber."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from fastmcp import FastMCP
|
|
7
|
+
|
|
8
|
+
from sqlsaber.agents.mcp import MCPSQLAgent
|
|
9
|
+
from sqlsaber.config.database import DatabaseConfigManager
|
|
10
|
+
from sqlsaber.database.connection import DatabaseConnection
|
|
11
|
+
|
|
12
|
+
INSTRUCTIONS = """
|
|
13
|
+
This server provides helpful resources and tools that will help you address users queries on their database.
|
|
14
|
+
|
|
15
|
+
- Get all databases using `get_databases()`
|
|
16
|
+
- Call `list_tables()` to get a list of all tables in the database with row counts. Use this first to discover available tables.
|
|
17
|
+
- Call `introspect_schema()` to introspect database schema to understand table structures.
|
|
18
|
+
- Call `execute_sql()` to execute SQL queries against the database and retrieve results.
|
|
19
|
+
|
|
20
|
+
Guidelines:
|
|
21
|
+
- Use list_tables first, then introspect_schema for specific tables only
|
|
22
|
+
- Use table patterns like 'sample%' or '%experiment%' to filter related tables
|
|
23
|
+
- Use proper JOIN syntax and avoid cartesian products
|
|
24
|
+
- Include appropriate WHERE clauses to limit results
|
|
25
|
+
- Handle errors gracefully and suggest fixes
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Create the FastMCP server instance
|
|
29
|
+
mcp = FastMCP(name="SQL Assistant", instructions=INSTRUCTIONS)
|
|
30
|
+
|
|
31
|
+
# Initialize the database config manager
|
|
32
|
+
config_manager = DatabaseConfigManager()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
async def _create_agent_for_database(database_name: str) -> Optional[MCPSQLAgent]:
|
|
36
|
+
"""Create a MCPSQLAgent for the specified database."""
|
|
37
|
+
try:
|
|
38
|
+
# Look up configured database connection
|
|
39
|
+
db_config = config_manager.get_database(database_name)
|
|
40
|
+
if not db_config:
|
|
41
|
+
return None
|
|
42
|
+
connection_string = db_config.to_connection_string()
|
|
43
|
+
|
|
44
|
+
# Create database connection
|
|
45
|
+
db_conn = DatabaseConnection(connection_string)
|
|
46
|
+
|
|
47
|
+
# Create and return the agent
|
|
48
|
+
agent = MCPSQLAgent(db_conn)
|
|
49
|
+
return agent
|
|
50
|
+
|
|
51
|
+
except Exception:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@mcp.tool
|
|
56
|
+
def get_databases() -> dict:
|
|
57
|
+
"""List all configured databases with their types."""
|
|
58
|
+
databases = []
|
|
59
|
+
for db_config in config_manager.list_databases():
|
|
60
|
+
databases.append(
|
|
61
|
+
{
|
|
62
|
+
"name": db_config.name,
|
|
63
|
+
"type": db_config.type,
|
|
64
|
+
"database": db_config.database,
|
|
65
|
+
"host": db_config.host,
|
|
66
|
+
"port": db_config.port,
|
|
67
|
+
"is_default": db_config.name == config_manager.get_default_name(),
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return {"databases": databases, "count": len(databases)}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@mcp.tool
|
|
75
|
+
async def list_tables(database: str) -> str:
|
|
76
|
+
"""
|
|
77
|
+
Get a list of all tables in the database with row counts. Use this first to discover available tables.
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
agent = await _create_agent_for_database(database)
|
|
81
|
+
if not agent:
|
|
82
|
+
return json.dumps(
|
|
83
|
+
{"error": f"Database '{database}' not found or could not connect"}
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
result = await agent.list_tables()
|
|
87
|
+
await agent.db.close()
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
except Exception as e:
|
|
91
|
+
return json.dumps({"error": f"Error listing tables: {str(e)}"})
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@mcp.tool
|
|
95
|
+
async def introspect_schema(database: str, table_pattern: Optional[str] = None) -> str:
|
|
96
|
+
"""
|
|
97
|
+
Introspect database schema to understand table structures. Use optional pattern to filter tables (e.g., 'public.users', 'user%', '%order%').
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
agent = await _create_agent_for_database(database)
|
|
101
|
+
if not agent:
|
|
102
|
+
return json.dumps(
|
|
103
|
+
{"error": f"Database '{database}' not found or could not connect"}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
result = await agent.introspect_schema(table_pattern)
|
|
107
|
+
await agent.db.close()
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
return json.dumps({"error": f"Error introspecting schema: {str(e)}"})
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@mcp.tool
|
|
115
|
+
async def execute_sql(database: str, query: str, limit: Optional[int] = 100) -> str:
|
|
116
|
+
"""Execute a SQL query against the specified database."""
|
|
117
|
+
try:
|
|
118
|
+
agent = await _create_agent_for_database(database)
|
|
119
|
+
if not agent:
|
|
120
|
+
return json.dumps(
|
|
121
|
+
{"error": f"Database '{database}' not found or could not connect"}
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
result = await agent.execute_sql(query, limit)
|
|
125
|
+
await agent.db.close()
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
return json.dumps({"error": f"Error executing SQL: {str(e)}"})
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main():
|
|
133
|
+
"""Entry point for the MCP server console script."""
|
|
134
|
+
mcp.run()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlsaber
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: SQLSaber - Agentic SQL assistant like Claude Code
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -8,6 +8,7 @@ Requires-Dist: aiomysql>=0.2.0
|
|
|
8
8
|
Requires-Dist: aiosqlite>=0.21.0
|
|
9
9
|
Requires-Dist: anthropic>=0.54.0
|
|
10
10
|
Requires-Dist: asyncpg>=0.30.0
|
|
11
|
+
Requires-Dist: fastmcp>=2.9.0
|
|
11
12
|
Requires-Dist: httpx>=0.28.1
|
|
12
13
|
Requires-Dist: keyring>=25.6.0
|
|
13
14
|
Requires-Dist: pandas>=2.0.0
|
|
@@ -34,6 +35,7 @@ Ask your questions in natural language and it will gather the right context and
|
|
|
34
35
|
- 💬 Interactive REPL mode
|
|
35
36
|
- 🎨 Beautiful formatted output with syntax highlighting
|
|
36
37
|
- 🗄️ Support for PostgreSQL, SQLite, and MySQL
|
|
38
|
+
- 🔌 MCP (Model Context Protocol) server support
|
|
37
39
|
|
|
38
40
|
## Installation
|
|
39
41
|
|
|
@@ -140,6 +142,43 @@ saber query "show me the distribution of customer ages"
|
|
|
140
142
|
saber query "which products had the highest sales growth last quarter?"
|
|
141
143
|
```
|
|
142
144
|
|
|
145
|
+
## MCP Server Integration
|
|
146
|
+
|
|
147
|
+
SQLSaber includes an MCP (Model Context Protocol) server that allows AI agents like Claude Code to directly leverage tools available in SQLSaber.
|
|
148
|
+
|
|
149
|
+
### Starting the MCP Server
|
|
150
|
+
|
|
151
|
+
Run the MCP server using uvx:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
uvx saber-mcp
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Configuring MCP Clients
|
|
158
|
+
|
|
159
|
+
#### Claude Code
|
|
160
|
+
|
|
161
|
+
Add SQLSaber as an MCP server in Claude Code:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
claude mcp add -- uvx saber-mcp
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### Other MCP Clients
|
|
168
|
+
|
|
169
|
+
For other MCP clients, configure them to run the command: `uvx saber-mcp`
|
|
170
|
+
|
|
171
|
+
### Available MCP Tools
|
|
172
|
+
|
|
173
|
+
Once connected, the MCP client will have access to these tools:
|
|
174
|
+
|
|
175
|
+
- `get_databases()` - Lists all configured databases
|
|
176
|
+
- `list_tables(database)` - Get all tables in a database with row counts
|
|
177
|
+
- `introspect_schema(database, table_pattern?)` - Get detailed schema information
|
|
178
|
+
- `execute_sql(database, query, limit?)` - Execute SQL queries (read-only)
|
|
179
|
+
|
|
180
|
+
The MCP server uses your existing SQLSaber database configurations, so make sure to set up your databases using `saber db add` first.
|
|
181
|
+
|
|
143
182
|
## How It Works
|
|
144
183
|
|
|
145
184
|
SQLSaber uses an intelligent three-step process optimized for minimal token usage:
|
|
@@ -3,6 +3,7 @@ sqlsaber/__main__.py,sha256=RIHxWeWh2QvLfah-2OkhI5IJxojWfy4fXpMnVEJYvxw,78
|
|
|
3
3
|
sqlsaber/agents/__init__.py,sha256=LWeSeEUE4BhkyAYFF3TE-fx8TtLud3oyEtyB8ojFJgo,167
|
|
4
4
|
sqlsaber/agents/anthropic.py,sha256=xAjKeQSnaut-P5VBeBISbQeqdP41epDjX6MJb2ZUXWg,14060
|
|
5
5
|
sqlsaber/agents/base.py,sha256=IuVyCaA7VsA92odfQS2_lYNzwIZwPxK55mL_xRewgwQ,6943
|
|
6
|
+
sqlsaber/agents/mcp.py,sha256=FKtXgDrPZ2-xqUYCw2baI5JzrWekXaC5fjkYW1_Mg50,827
|
|
6
7
|
sqlsaber/agents/streaming.py,sha256=_EO390-FHUrL1fRCNfibtE9QuJz3LGQygbwG3CB2ViY,533
|
|
7
8
|
sqlsaber/cli/__init__.py,sha256=qVSLVJLLJYzoC6aj6y9MFrzZvAwc4_OgxU9DlkQnZ4M,86
|
|
8
9
|
sqlsaber/cli/commands.py,sha256=h418lgh_Xp7XEQ1xvjcDyplC2JON0-y98QMaDm6o29k,4919
|
|
@@ -18,15 +19,17 @@ sqlsaber/config/database.py,sha256=vKFOxPjVakjQhj1uoLcfzhS9ZFr6Z2F5b4MmYALQZoA,1
|
|
|
18
19
|
sqlsaber/config/settings.py,sha256=zjQ7nS3ybcCb88Ea0tmwJox5-q0ettChZw89ZqRVpX8,3975
|
|
19
20
|
sqlsaber/database/__init__.py,sha256=a_gtKRJnZVO8-fEZI7g3Z8YnGa6Nio-5Y50PgVp07ss,176
|
|
20
21
|
sqlsaber/database/connection.py,sha256=s8GSFZebB8be8sVUr-N0x88-20YfkfljJFRyfoB1gH0,15154
|
|
21
|
-
sqlsaber/database/schema.py,sha256=
|
|
22
|
+
sqlsaber/database/schema.py,sha256=9QoH-gADzWlepq-tGz3nPU3miSUU0koWmpDaoWvz8Q0,27951
|
|
23
|
+
sqlsaber/mcp/__init__.py,sha256=COdWq7wauPBp5Ew8tfZItFzbcLDSEkHBJSMhxzy8C9c,112
|
|
24
|
+
sqlsaber/mcp/mcp.py,sha256=ACm1P1TnicjOptQgeLNhXg5xgZf4MYq2kqdfVdj6wh0,4477
|
|
22
25
|
sqlsaber/memory/__init__.py,sha256=GiWkU6f6YYVV0EvvXDmFWe_CxarmDCql05t70MkTEWs,63
|
|
23
26
|
sqlsaber/memory/manager.py,sha256=ML2NEO5Z4Aw36sEI9eOvWVnjl-qT2VOTojViJAj7Seo,2777
|
|
24
27
|
sqlsaber/memory/storage.py,sha256=DvZBsSPaAfk_DqrNEn86uMD-TQsWUI6rQLfNw6PSCB8,5788
|
|
25
28
|
sqlsaber/models/__init__.py,sha256=RJ7p3WtuSwwpFQ1Iw4_DHV2zzCtHqIzsjJzxv8kUjUE,287
|
|
26
29
|
sqlsaber/models/events.py,sha256=55m41tDwMsFxnKKA5_VLJz8iV-V4Sq3LDfta4VoutJI,737
|
|
27
30
|
sqlsaber/models/types.py,sha256=3U_30n91EB3IglBTHipwiW4MqmmaA2qfshfraMZyPps,896
|
|
28
|
-
sqlsaber-0.
|
|
29
|
-
sqlsaber-0.
|
|
30
|
-
sqlsaber-0.
|
|
31
|
-
sqlsaber-0.
|
|
32
|
-
sqlsaber-0.
|
|
31
|
+
sqlsaber-0.4.0.dist-info/METADATA,sha256=CL1mNjOLrc6VDJqE2dSrCXO5OJz9gTMxYNoYq6jtzYE,5071
|
|
32
|
+
sqlsaber-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
33
|
+
sqlsaber-0.4.0.dist-info/entry_points.txt,sha256=jmFo96Ylm0zIKXJBwhv_P5wQ7SXP9qdaBcnTp8iCEe8,195
|
|
34
|
+
sqlsaber-0.4.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
35
|
+
sqlsaber-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|