Skip to content

Commit e254129

Browse files
committed
Add config option for enabling only a specific set of tools
1 parent 2524797 commit e254129

8 files changed

Lines changed: 80 additions & 23 deletions

File tree

config_example.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,9 @@ databases:
3131

3232
# Global MCP server settings
3333
settings:
34-
max_query_timeout: 30 # Maximum time in seconds for any single query
35-
max_rows_per_query: 1000 # Maximum rows returned per query (pagination will apply)
34+
max_query_timeout: 30 # Maximum time in seconds for any single query
35+
max_rows_per_query: 1000 # Maximum rows returned per query, this is a hard limit on top of any LLM-configurable pagination
36+
37+
# Optional: restrict which tools are available (omit to enable all tools)
38+
# Note: show_database_config is always available
39+
available_tools: ["execute_query", "table_summary", "search", "test_connection", "reload_config"]

src/meeseeql/database_manager.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,15 @@ def dialect(self) -> str:
8787
return dialect_map[self.type]
8888

8989

90+
class GlobalSettings(BaseModel):
91+
max_query_timeout: int = 30
92+
max_rows_per_query: int = 500
93+
available_tools: List[str] | None = None
94+
95+
9096
class AppConfig(BaseModel):
9197
databases: Dict[str, DatabaseConfig]
92-
settings: Dict[str, Any]
98+
settings: GlobalSettings = GlobalSettings()
9399
config_path: str | None = None
94100

95101
@model_validator(mode="after")
@@ -165,9 +171,7 @@ def _get_engine(self, db_label: str) -> AsyncEngine | Engine:
165171

166172
# Only add pool_timeout for non-SQLite databases
167173
if not str(url).startswith("sqlite"):
168-
engine_kwargs["pool_timeout"] = self.config.settings.get(
169-
"max_query_timeout", 30
170-
)
174+
engine_kwargs["pool_timeout"] = self.config.settings.max_query_timeout
171175

172176
# Snowflake doesn't have native async support, use sync engine with async wrapper
173177
if db_config.type == "snowflake":
@@ -273,6 +277,9 @@ def get_table_filter_type(self, db_name: str) -> str | None:
273277
else:
274278
return None
275279

280+
def get_available_tools(self) -> List[str] | None:
281+
return self.config.settings.available_tools
282+
276283
def reload_config(self, new_config: AppConfig, changed_db_names: set[str]):
277284
for db_name in changed_db_names:
278285
if db_name in self.engines:

src/meeseeql/main.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ def get_or_init_db_manager():
5858
return db_manager
5959

6060

61-
@mcp.tool()
6261
def show_database_config() -> ToolResult:
6362
"""List all configured databases and their settings.
6463
This tool also gives you the full path to the config file
@@ -72,7 +71,6 @@ def show_database_config() -> ToolResult:
7271
)
7372

7473

75-
@mcp.tool()
7674
async def execute_query(
7775
database: str,
7876
query: str,
@@ -98,7 +96,6 @@ async def execute_query(
9896
)
9997

10098

101-
@mcp.tool()
10299
async def table_summary(
103100
database: str,
104101
table_name: str,
@@ -124,7 +121,6 @@ async def table_summary(
124121
)
125122

126123

127-
@mcp.tool()
128124
async def search(
129125
database: str,
130126
search_term: str,
@@ -147,7 +143,6 @@ async def search(
147143
)
148144

149145

150-
@mcp.tool()
151146
async def test_connection(database: str) -> ToolResult:
152147
"""Test database connection, useful for debugging issues"""
153148
result = await tools.test_connection(get_or_init_db_manager(), database)
@@ -157,7 +152,6 @@ async def test_connection(database: str) -> ToolResult:
157152
)
158153

159154

160-
@mcp.tool()
161155
def reload_config() -> ToolResult:
162156
"""Reload configuration file and report what changed"""
163157
config_path = find_config_file()
@@ -168,8 +162,29 @@ def reload_config() -> ToolResult:
168162
)
169163

170164

165+
def register_tools():
166+
available_tools = get_or_init_db_manager().get_available_tools()
167+
168+
mcp.tool()(show_database_config)
169+
170+
if available_tools is None or "execute_query" in available_tools:
171+
mcp.tool()(execute_query)
172+
173+
if available_tools is None or "table_summary" in available_tools:
174+
mcp.tool()(table_summary)
175+
176+
if available_tools is None or "search" in available_tools:
177+
mcp.tool()(search)
178+
179+
if available_tools is None or "test_connection" in available_tools:
180+
mcp.tool()(test_connection)
181+
182+
if available_tools is None or "reload_config" in available_tools:
183+
mcp.tool()(reload_config)
184+
185+
171186
def main():
172-
get_or_init_db_manager()
187+
register_tools()
173188
mcp.run()
174189

175190

src/meeseeql/tools/execute_query.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Dict, Any, List
33
from pydantic import BaseModel
44
from meeseeql.database_manager import DatabaseManager
5-
from meeseeql.sql_transformer import SqlQueryTransformer, TableAccessError
5+
from meeseeql.sql_transformer import SqlQueryTransformer
66

77

88
class QueryResponse(BaseModel):
@@ -78,9 +78,7 @@ async def execute_query(
7878
if page < 1:
7979
raise ValueError("Page number must be greater than 0")
8080

81-
max_rows = db_manager.config.settings.get("max_rows_per_query", 1000)
82-
if limit > max_rows:
83-
limit = max_rows
81+
limit = min(limit, db_manager.config.settings.max_rows_per_query)
8482

8583
dialect = db_manager.get_dialect_name(database)
8684
transformer = SqlQueryTransformer(query.strip(), dialect)

src/meeseeql/tools/search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ async def search(
9999

100100
sql_template = load_sql_query(dialect, "search")
101101

102-
limit = min(250, db_manager.config.settings.get("max_rows_per_query", 250))
102+
limit = min(250, db_manager.config.settings.max_rows_per_query)
103103

104104
sql_query = sql_template.replace("{{search_term}}", search_term)
105105

src/meeseeql/tools/table_summary.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,7 @@ async def table_summary(
404404
db_manager, database, table_name, schema_value, limit, page
405405
)
406406

407-
max_rows = db_manager.config.settings.get("max_rows_per_query", 1000)
408-
if limit > max_rows:
409-
limit = max_rows
407+
limit = min(limit, db_manager.config.settings.max_rows_per_query)
410408

411409
dialect = db_manager.get_dialect_name(database)
412410

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from meeseeql.database_manager import (
2+
AppConfig,
3+
DatabaseConfig,
4+
DatabaseManager,
5+
GlobalSettings,
6+
)

tests/tools/test_show_database_config.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,41 @@ def test_show_database_config_includes_correct_database_info(db_manager):
4949

5050
def test_show_database_config_with_empty_config():
5151
"""Test show_database_config with empty database config"""
52-
from meeseeql.database_manager import AppConfig
52+
from meeseeql.database_manager import AppConfig, GlobalSettings
5353

54-
empty_config = AppConfig(databases={}, settings={})
54+
empty_config = AppConfig(databases={}, settings=GlobalSettings())
5555
empty_db_manager = DatabaseManager(empty_config)
5656

5757
result = show_database_config(empty_db_manager)
5858

5959
assert isinstance(result, DatabaseList)
6060
assert result.total_count == 0
61+
62+
63+
def test_show_database_config_does_not_include_passwords():
64+
"""Test that passwords are not included in the output"""
65+
from meeseeql.database_manager import AppConfig, DatabaseConfig, GlobalSettings
66+
67+
config_with_password = AppConfig(
68+
databases={
69+
"test_db": DatabaseConfig(
70+
type="postgresql",
71+
description="Test DB with credentials",
72+
host="localhost",
73+
database="test",
74+
username="user",
75+
password="secret_password",
76+
)
77+
},
78+
settings=GlobalSettings(),
79+
)
80+
81+
db_manager = DatabaseManager(config_with_password)
82+
result = show_database_config(db_manager)
83+
84+
output_str = str(result)
85+
assert "secret_password" not in output_str
86+
assert "password:" not in output_str
87+
88+
db_info = result.databases[0]
89+
assert not hasattr(db_info, "password")

0 commit comments

Comments
 (0)