sqlsaber 0.3.0__py3-none-any.whl → 0.4.1__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 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/cli/commands.py CHANGED
@@ -117,6 +117,9 @@ def query(
117
117
  if query_text:
118
118
  # Single query mode with streaming
119
119
  streaming_handler = StreamingQueryHandler(console)
120
+ console.print(
121
+ f"[bold blue]Connected to:[/bold blue] {db_name} {agent._get_database_type_name()}\n"
122
+ )
120
123
  await streaming_handler.execute_streaming_query(query_text, agent)
121
124
  else:
122
125
  # Interactive mode
@@ -20,21 +20,24 @@ class InteractiveSession:
20
20
 
21
21
  def show_welcome_message(self):
22
22
  """Display welcome message for interactive mode."""
23
+ # Show database information
24
+ db_name = getattr(self.agent, "database_name", None) or "Unknown"
25
+ db_type = self.agent._get_database_type_name()
26
+
23
27
  self.console.print(
24
28
  Panel.fit(
25
29
  "[bold green]SQLSaber - Use the agent Luke![/bold green]\n\n"
26
- "Type your queries in natural language.\n\n"
27
- "Press Esc-Enter or Meta-Enter to submit your query.\n\n"
28
- "Type 'exit' or 'quit' to leave.",
30
+ "[bold]Your agentic SQL assistant.[/bold]\n\n\n"
31
+ "[dim]Use 'clear' to reset conversation, 'exit' or 'quit' to leave.[/dim]\n\n"
32
+ "[dim]Start a message with '#' to add something to agent's memory for this database.[/dim]",
29
33
  border_style="green",
30
34
  )
31
35
  )
32
-
33
36
  self.console.print(
34
- "[dim]Commands: 'clear' to reset conversation, 'exit' or 'quit' to leave[/dim]"
37
+ f"[bold blue]Connected to:[/bold blue] {db_name} ({db_type})\n"
35
38
  )
36
39
  self.console.print(
37
- "[dim]Memory: Start a message with '#' to add it as a memory for this database[/dim]\n"
40
+ "[dim]Press Esc-Enter or Meta-Enter to submit your query.[/dim]\n"
38
41
  )
39
42
 
40
43
  async def run(self):
@@ -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;
@@ -0,0 +1,5 @@
1
+ """MCP (Model Context Protocol) server implementation for SQLSaber."""
2
+
3
+ from .mcp import mcp
4
+
5
+ __all__ = ["mcp"]
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.0
3
+ Version: 0.4.1
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
@@ -23,7 +24,28 @@ Description-Content-Type: text/markdown
23
24
 
24
25
  SQLSaber is an agentic SQL assistant. Think Claude Code but for SQL.
25
26
 
26
- Ask your questions in natural language and it will gather the right context and answer your query by writing SQL and analyzing the results.
27
+ Ask your questions in natural language and it will gather the right context automatically and answer your query by writing SQL and analyzing the results.
28
+
29
+ ## Table of Contents
30
+
31
+ - [Features](#features)
32
+ - [Installation](#installation)
33
+ - [Configuration](#configuration)
34
+ - [Database Connection](#database-connection)
35
+ - [AI Model Configuration](#ai-model-configuration)
36
+ - [Memory Management](#memory-management)
37
+ - [Usage](#usage)
38
+ - [Interactive Mode](#interactive-mode)
39
+ - [Single Query](#single-query)
40
+ - [Database Selection](#database-selection)
41
+ - [Examples](#examples)
42
+ - [MCP Server Integration](#mcp-server-integration)
43
+ - [Starting the MCP Server](#starting-the-mcp-server)
44
+ - [Configuring MCP Clients](#configuring-mcp-clients)
45
+ - [Available MCP Tools](#available-mcp-tools)
46
+ - [How It Works](#how-it-works)
47
+ - [Contributing](#contributing)
48
+ - [License](#license)
27
49
 
28
50
  ## Features
29
51
 
@@ -34,19 +56,29 @@ Ask your questions in natural language and it will gather the right context and
34
56
  - 💬 Interactive REPL mode
35
57
  - 🎨 Beautiful formatted output with syntax highlighting
36
58
  - 🗄️ Support for PostgreSQL, SQLite, and MySQL
59
+ - 🔌 MCP (Model Context Protocol) server support
37
60
 
38
61
  ## Installation
39
62
 
63
+ ### `uv`
64
+
40
65
  ```bash
41
66
  uv tool install sqlsaber
42
67
  ```
43
68
 
44
- or
69
+ ### `pipx`
45
70
 
46
71
  ```bash
47
72
  pipx install sqlsaber
48
73
  ```
49
74
 
75
+ ### `brew`
76
+
77
+ ```bash
78
+ brew install uv
79
+ uv tool install sqlsaber
80
+ ```
81
+
50
82
  ## Configuration
51
83
 
52
84
  ### Database Connection
@@ -140,6 +172,43 @@ saber query "show me the distribution of customer ages"
140
172
  saber query "which products had the highest sales growth last quarter?"
141
173
  ```
142
174
 
175
+ ## MCP Server Integration
176
+
177
+ SQLSaber includes an MCP (Model Context Protocol) server that allows AI agents like Claude Code to directly leverage tools available in SQLSaber.
178
+
179
+ ### Starting the MCP Server
180
+
181
+ Run the MCP server using uvx:
182
+
183
+ ```bash
184
+ uvx --from sqlsaber saber-mcp
185
+ ```
186
+
187
+ ### Configuring MCP Clients
188
+
189
+ #### Claude Code
190
+
191
+ Add SQLSaber as an MCP server in Claude Code:
192
+
193
+ ```bash
194
+ claude mcp add -- uvx --from sqlsaber saber-mcp
195
+ ```
196
+
197
+ #### Other MCP Clients
198
+
199
+ For other MCP clients, configure them to run the command: `uvx --from sqlsaber saber-mcp`
200
+
201
+ ### Available MCP Tools
202
+
203
+ Once connected, the MCP client will have access to these tools:
204
+
205
+ - `get_databases()` - Lists all configured databases
206
+ - `list_tables(database)` - Get all tables in a database with row counts
207
+ - `introspect_schema(database, table_pattern?)` - Get detailed schema information
208
+ - `execute_sql(database, query, limit?)` - Execute SQL queries (read-only)
209
+
210
+ The MCP server uses your existing SQLSaber database configurations, so make sure to set up your databases using `saber db add` first.
211
+
143
212
  ## How It Works
144
213
 
145
214
  SQLSaber uses an intelligent three-step process optimized for minimal token usage:
@@ -3,12 +3,13 @@ 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
- sqlsaber/cli/commands.py,sha256=h418lgh_Xp7XEQ1xvjcDyplC2JON0-y98QMaDm6o29k,4919
9
+ sqlsaber/cli/commands.py,sha256=Dw24W0jij-8t1lpk99C4PBTgzFSag6vU-FZcjAYGG54,5074
9
10
  sqlsaber/cli/database.py,sha256=DUfyvNBDp47oFM_VAC_hXHQy_qyE7JbXtowflJpwwH8,12643
10
11
  sqlsaber/cli/display.py,sha256=5J4AgJADmMwKi9Aq5u6_MKRO1TA6unS4F4RUfml_sfU,7651
11
- sqlsaber/cli/interactive.py,sha256=y92rdoM49SOSwEctm9ZcrEN220fhJ_DMHPSd_7KsORg,3701
12
+ sqlsaber/cli/interactive.py,sha256=Kqe7kN9mhUiY_5z1Ki6apZ9ahs8uzhHp3xMZGiyTXpY,3912
12
13
  sqlsaber/cli/memory.py,sha256=LW4ZF2V6Gw6hviUFGZ4ym9ostFCwucgBTIMZ3EANO-I,7671
13
14
  sqlsaber/cli/models.py,sha256=3IcXeeU15IQvemSv-V-RQzVytJ3wuQ4YmWk89nTDcSE,7813
14
15
  sqlsaber/cli/streaming.py,sha256=5QGAYTAvg9mzQLxDEVtdDH-TIbGfYYzMOLoOYPrHPu0,3788
@@ -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=S3uPSXcrU3swoOPPjbXSxo1_fZ8vV3ELuPoV2GRy1ZI,27953
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.3.0.dist-info/METADATA,sha256=YKw06cBqX0bNZK98kJfYAES_c8VPXOhvOtIKONt1xFk,3982
29
- sqlsaber-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
30
- sqlsaber-0.3.0.dist-info/entry_points.txt,sha256=POwcsEskUp7xQQWabrAi6Eawz4qc5eBlB3KzAiBq-Y0,124
31
- sqlsaber-0.3.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
32
- sqlsaber-0.3.0.dist-info/RECORD,,
31
+ sqlsaber-0.4.1.dist-info/METADATA,sha256=Jf17BrqK-2LdClq-VdYzhQ5jCsIN44rqsAsQ9kU4ClM,5938
32
+ sqlsaber-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
+ sqlsaber-0.4.1.dist-info/entry_points.txt,sha256=jmFo96Ylm0zIKXJBwhv_P5wQ7SXP9qdaBcnTp8iCEe8,195
34
+ sqlsaber-0.4.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
35
+ sqlsaber-0.4.1.dist-info/RECORD,,
@@ -1,4 +1,6 @@
1
1
  [console_scripts]
2
2
  saber = sqlsaber.cli.commands:main
3
+ saber-mcp = sqlsaber.mcp.mcp:main
3
4
  sql = sqlsaber.cli.commands:main
4
5
  sqlsaber = sqlsaber.cli.commands:main
6
+ sqlsaber-mcp = sqlsaber.mcp.mcp:main