mcp-server-motherduck 0.6.4__py3-none-any.whl → 1.0.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.
- mcp_server_motherduck/__init__.py +198 -133
- mcp_server_motherduck/assets/duck_feet_square.png +0 -0
- mcp_server_motherduck/configs.py +1 -1
- mcp_server_motherduck/database.py +342 -35
- mcp_server_motherduck/instructions.py +187 -0
- mcp_server_motherduck/server.py +208 -115
- mcp_server_motherduck/tools/__init__.py +19 -0
- mcp_server_motherduck/tools/execute_query.py +21 -0
- mcp_server_motherduck/tools/list_columns.py +99 -0
- mcp_server_motherduck/tools/list_databases.py +52 -0
- mcp_server_motherduck/tools/list_tables.py +91 -0
- mcp_server_motherduck/tools/switch_database_connection.py +130 -0
- mcp_server_motherduck-1.0.0.dist-info/METADATA +225 -0
- mcp_server_motherduck-1.0.0.dist-info/RECORD +17 -0
- {mcp_server_motherduck-0.6.4.dist-info → mcp_server_motherduck-1.0.0.dist-info}/WHEEL +1 -1
- mcp_server_motherduck/prompt.py +0 -195
- mcp_server_motherduck-0.6.4.dist-info/METADATA +0 -427
- mcp_server_motherduck-0.6.4.dist-info/RECORD +0 -10
- {mcp_server_motherduck-0.6.4.dist-info → mcp_server_motherduck-1.0.0.dist-info}/entry_points.txt +0 -0
- {mcp_server_motherduck-0.6.4.dist-info → mcp_server_motherduck-1.0.0.dist-info}/licenses/LICENSE +0 -0
mcp_server_motherduck/server.py
CHANGED
|
@@ -1,149 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastMCP Server for MotherDuck and DuckDB.
|
|
3
|
+
|
|
4
|
+
This module creates and configures the FastMCP server with all tools.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
1
8
|
import logging
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from mcp.
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from fastmcp import FastMCP
|
|
12
|
+
from fastmcp.utilities.types import Image
|
|
13
|
+
from mcp.types import Icon
|
|
14
|
+
|
|
7
15
|
from .configs import SERVER_VERSION
|
|
8
16
|
from .database import DatabaseClient
|
|
9
|
-
from .
|
|
10
|
-
|
|
17
|
+
from .instructions import get_instructions
|
|
18
|
+
from .tools.execute_query import execute_query as execute_query_fn
|
|
19
|
+
from .tools.list_columns import list_columns as list_columns_fn
|
|
20
|
+
from .tools.list_databases import list_databases as list_databases_fn
|
|
21
|
+
from .tools.list_tables import list_tables as list_tables_fn
|
|
22
|
+
from .tools.switch_database_connection import (
|
|
23
|
+
switch_database_connection as switch_database_connection_fn,
|
|
24
|
+
)
|
|
11
25
|
|
|
12
26
|
logger = logging.getLogger("mcp_server_motherduck")
|
|
13
27
|
|
|
28
|
+
# Server icon - embedded as data URI from local file
|
|
29
|
+
ASSETS_DIR = Path(__file__).parent / "assets"
|
|
30
|
+
ICON_PATH = ASSETS_DIR / "duck_feet_square.png"
|
|
31
|
+
|
|
14
32
|
|
|
15
|
-
def
|
|
33
|
+
def create_mcp_server(
|
|
16
34
|
db_path: str,
|
|
17
35
|
motherduck_token: str | None = None,
|
|
18
36
|
home_dir: str | None = None,
|
|
19
37
|
saas_mode: bool = False,
|
|
20
38
|
read_only: bool = False,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
ephemeral_connections: bool = True,
|
|
40
|
+
max_rows: int = 1024,
|
|
41
|
+
max_chars: int = 50000,
|
|
42
|
+
query_timeout: int = -1,
|
|
43
|
+
init_sql: str | None = None,
|
|
44
|
+
allow_switch_databases: bool = False,
|
|
45
|
+
) -> FastMCP:
|
|
46
|
+
"""
|
|
47
|
+
Create and configure the FastMCP server.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
db_path: Path to database (local file, :memory:, md:, or s3://)
|
|
51
|
+
motherduck_token: MotherDuck authentication token
|
|
52
|
+
home_dir: Home directory for DuckDB
|
|
53
|
+
saas_mode: Enable MotherDuck SaaS mode
|
|
54
|
+
read_only: Enable read-only mode
|
|
55
|
+
ephemeral_connections: Use temporary connections for read-only local files
|
|
56
|
+
max_rows: Maximum rows to return from queries
|
|
57
|
+
max_chars: Maximum characters in query results
|
|
58
|
+
query_timeout: Query timeout in seconds (-1 to disable)
|
|
59
|
+
init_sql: SQL file path or string to execute on startup
|
|
60
|
+
allow_switch_databases: Enable the switch_database_connection tool
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Configured FastMCP server instance
|
|
64
|
+
"""
|
|
65
|
+
# Create database client
|
|
24
66
|
db_client = DatabaseClient(
|
|
25
67
|
db_path=db_path,
|
|
26
68
|
motherduck_token=motherduck_token,
|
|
27
69
|
home_dir=home_dir,
|
|
28
70
|
saas_mode=saas_mode,
|
|
29
71
|
read_only=read_only,
|
|
72
|
+
ephemeral_connections=ephemeral_connections,
|
|
73
|
+
max_rows=max_rows,
|
|
74
|
+
max_chars=max_chars,
|
|
75
|
+
query_timeout=query_timeout,
|
|
76
|
+
init_sql=init_sql,
|
|
30
77
|
)
|
|
31
78
|
|
|
32
|
-
|
|
79
|
+
# Get instructions with connection context
|
|
80
|
+
instructions = get_instructions(
|
|
81
|
+
read_only=read_only,
|
|
82
|
+
saas_mode=saas_mode,
|
|
83
|
+
db_path=db_path,
|
|
84
|
+
allow_switch_databases=allow_switch_databases,
|
|
85
|
+
)
|
|
33
86
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
87
|
+
# Create server icon from local file
|
|
88
|
+
icons = []
|
|
89
|
+
if ICON_PATH.exists():
|
|
90
|
+
img = Image(path=str(ICON_PATH))
|
|
91
|
+
icons.append(Icon(src=img.to_data_uri(), mimeType="image/png"))
|
|
92
|
+
|
|
93
|
+
# Create FastMCP server with icon
|
|
94
|
+
mcp = FastMCP(
|
|
95
|
+
name="mcp-server-motherduck",
|
|
96
|
+
instructions=instructions,
|
|
97
|
+
version=SERVER_VERSION,
|
|
98
|
+
icons=icons if icons else None,
|
|
99
|
+
)
|
|
42
100
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
""
|
|
46
|
-
|
|
47
|
-
|
|
101
|
+
# Define query tool annotations (dynamic based on read_only flag)
|
|
102
|
+
query_annotations = {
|
|
103
|
+
"readOnlyHint": read_only,
|
|
104
|
+
"destructiveHint": not read_only,
|
|
105
|
+
"openWorldHint": False,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Catalog tool annotations (always read-only)
|
|
109
|
+
catalog_annotations = {
|
|
110
|
+
"readOnlyHint": True,
|
|
111
|
+
"destructiveHint": False,
|
|
112
|
+
"openWorldHint": False,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Switch database annotations (open world - can connect to any database)
|
|
116
|
+
switch_db_annotations = {
|
|
117
|
+
"readOnlyHint": False,
|
|
118
|
+
"destructiveHint": False,
|
|
119
|
+
"openWorldHint": True,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Register query tool
|
|
123
|
+
@mcp.tool(
|
|
124
|
+
name="execute_query",
|
|
125
|
+
title="Execute Query",
|
|
126
|
+
description="Execute a SQL query on the DuckDB or MotherDuck database. Unqualified table names resolve to current_database() and current_schema() automatically. Fully qualified names (database.schema.table) are only needed when multiple DuckDB databases are attached or when connected to MotherDuck.",
|
|
127
|
+
annotations=query_annotations,
|
|
128
|
+
)
|
|
129
|
+
def execute_query(sql: str) -> str:
|
|
48
130
|
"""
|
|
49
|
-
|
|
50
|
-
raise ValueError(f"Unsupported URI scheme: {uri.scheme}")
|
|
131
|
+
Execute a SQL query on the DuckDB or MotherDuck database.
|
|
51
132
|
|
|
52
|
-
|
|
53
|
-
|
|
133
|
+
Args:
|
|
134
|
+
sql: SQL query to execute (DuckDB SQL dialect)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
JSON string with query results
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
ValueError: If the query fails
|
|
54
141
|
"""
|
|
55
|
-
|
|
56
|
-
|
|
142
|
+
result = execute_query_fn(sql, db_client)
|
|
143
|
+
if not result.get("success", True):
|
|
144
|
+
# Raise exception so FastMCP marks as isError=True
|
|
145
|
+
raise ValueError(json.dumps(result, indent=2, default=str))
|
|
146
|
+
return json.dumps(result, indent=2, default=str)
|
|
147
|
+
|
|
148
|
+
# Register list_databases tool
|
|
149
|
+
@mcp.tool(
|
|
150
|
+
name="list_databases",
|
|
151
|
+
title="List Databases",
|
|
152
|
+
description="List all databases available in the connection. Useful when multiple DuckDB databases are attached or when connected to MotherDuck.",
|
|
153
|
+
annotations=catalog_annotations,
|
|
154
|
+
)
|
|
155
|
+
def list_databases_tool() -> str:
|
|
57
156
|
"""
|
|
58
|
-
|
|
59
|
-
# TODO: Check where and how this is used, and how to optimize this.
|
|
60
|
-
# Check postgres and sqlite servers.
|
|
61
|
-
return [
|
|
62
|
-
types.Prompt(
|
|
63
|
-
name="duckdb-motherduck-initial-prompt",
|
|
64
|
-
description="A prompt to initialize a connection to duckdb or motherduck and start working with it",
|
|
65
|
-
)
|
|
66
|
-
]
|
|
157
|
+
List all databases available in the connection.
|
|
67
158
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
name: str, arguments: dict[str, str] | None
|
|
71
|
-
) -> types.GetPromptResult:
|
|
159
|
+
Returns:
|
|
160
|
+
JSON string with database list
|
|
72
161
|
"""
|
|
73
|
-
|
|
74
|
-
|
|
162
|
+
result = list_databases_fn(db_client)
|
|
163
|
+
return json.dumps(result, indent=2, default=str)
|
|
164
|
+
|
|
165
|
+
# Register list_tables tool
|
|
166
|
+
@mcp.tool(
|
|
167
|
+
name="list_tables",
|
|
168
|
+
title="List Tables",
|
|
169
|
+
description="List all tables and views in a database with their comments. If database is not specified, uses the current database.",
|
|
170
|
+
annotations=catalog_annotations,
|
|
171
|
+
)
|
|
172
|
+
def list_tables(database: str | None = None, schema: str | None = None) -> str:
|
|
75
173
|
"""
|
|
76
|
-
|
|
77
|
-
# TODO: Check where and how this is used, and how to optimize this.
|
|
78
|
-
# Check postgres and sqlite servers.
|
|
79
|
-
if name != "duckdb-motherduck-initial-prompt":
|
|
80
|
-
raise ValueError(f"Unknown prompt: {name}")
|
|
81
|
-
|
|
82
|
-
return types.GetPromptResult(
|
|
83
|
-
description="Initial prompt for interacting with DuckDB/MotherDuck",
|
|
84
|
-
messages=[
|
|
85
|
-
types.PromptMessage(
|
|
86
|
-
role="user",
|
|
87
|
-
content=types.TextContent(type="text", text=PROMPT_TEMPLATE),
|
|
88
|
-
)
|
|
89
|
-
],
|
|
90
|
-
)
|
|
174
|
+
List all tables and views in a database.
|
|
91
175
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
176
|
+
Args:
|
|
177
|
+
database: Database name to list tables from (defaults to current database)
|
|
178
|
+
schema: Optional schema name to filter by
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
JSON string with table/view list
|
|
97
182
|
"""
|
|
98
|
-
|
|
99
|
-
return
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
"required": ["query"],
|
|
112
|
-
},
|
|
113
|
-
),
|
|
114
|
-
]
|
|
115
|
-
|
|
116
|
-
@server.call_tool()
|
|
117
|
-
async def handle_tool_call(
|
|
118
|
-
name: str, arguments: dict | None
|
|
119
|
-
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
|
183
|
+
result = list_tables_fn(db_client, database, schema)
|
|
184
|
+
return json.dumps(result, indent=2, default=str)
|
|
185
|
+
|
|
186
|
+
# Register list_columns tool
|
|
187
|
+
@mcp.tool(
|
|
188
|
+
name="list_columns",
|
|
189
|
+
title="List Columns",
|
|
190
|
+
description="List all columns of a table or view with their types and comments. If database/schema are not specified, uses the current database/schema.",
|
|
191
|
+
annotations=catalog_annotations,
|
|
192
|
+
)
|
|
193
|
+
def list_columns(table: str, database: str | None = None, schema: str | None = None) -> str:
|
|
120
194
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
195
|
+
List all columns of a table or view.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
table: Table or view name
|
|
199
|
+
database: Database name (defaults to current database)
|
|
200
|
+
schema: Schema name (defaults to current schema)
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
JSON string with column list
|
|
123
204
|
"""
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
205
|
+
result = list_columns_fn(table, db_client, database, schema)
|
|
206
|
+
return json.dumps(result, indent=2, default=str)
|
|
207
|
+
|
|
208
|
+
# Conditionally register switch_database_connection tool
|
|
209
|
+
if allow_switch_databases:
|
|
210
|
+
# Store server's read_only setting for switch_database_connection
|
|
211
|
+
server_read_only_mode = read_only
|
|
212
|
+
|
|
213
|
+
@mcp.tool(
|
|
214
|
+
name="switch_database_connection",
|
|
215
|
+
title="Switch Database Connection",
|
|
216
|
+
description="Switch to a different database connection. For local files, use absolute paths only. The new connection respects the server's read-only/read-write mode. For local files, the file must exist unless create_if_not_exists=True (requires read-write mode).",
|
|
217
|
+
annotations=switch_db_annotations,
|
|
218
|
+
)
|
|
219
|
+
def switch_database_connection(path: str, create_if_not_exists: bool = False) -> str:
|
|
220
|
+
"""
|
|
221
|
+
Switch to a different primary database.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
path: Database path. For local files, must be an absolute path.
|
|
225
|
+
Also accepts :memory:, md:database_name, or s3:// paths.
|
|
226
|
+
create_if_not_exists: If True, create the database file if it doesn't exist.
|
|
227
|
+
Only works in read-write mode.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
JSON string with result
|
|
231
|
+
"""
|
|
232
|
+
result = switch_database_connection_fn(
|
|
233
|
+
path=path,
|
|
234
|
+
db_client=db_client,
|
|
235
|
+
server_read_only=server_read_only_mode,
|
|
236
|
+
create_if_not_exists=create_if_not_exists,
|
|
237
|
+
)
|
|
238
|
+
return json.dumps(result, indent=2, default=str)
|
|
239
|
+
|
|
240
|
+
logger.info(f"FastMCP server created with {len(mcp._tool_manager._tools)} tools")
|
|
148
241
|
|
|
149
|
-
return
|
|
242
|
+
return mcp
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tools for MotherDuck/DuckDB server.
|
|
3
|
+
|
|
4
|
+
Each tool is defined in its own module and exported here.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .execute_query import execute_query
|
|
8
|
+
from .list_columns import list_columns
|
|
9
|
+
from .list_databases import list_databases
|
|
10
|
+
from .list_tables import list_tables
|
|
11
|
+
from .switch_database_connection import switch_database_connection
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"execute_query",
|
|
15
|
+
"list_databases",
|
|
16
|
+
"list_tables",
|
|
17
|
+
"list_columns",
|
|
18
|
+
"switch_database_connection",
|
|
19
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Execute Query tool - Execute SQL queries against DuckDB/MotherDuck databases.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
DESCRIPTION = "Execute a SQL query on the DuckDB or MotherDuck database."
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def execute_query(sql: str, db_client: Any) -> dict[str, Any]:
|
|
11
|
+
"""
|
|
12
|
+
Execute a SQL query on the DuckDB or MotherDuck database.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
sql: SQL query to execute (DuckDB SQL dialect)
|
|
16
|
+
db_client: DatabaseClient instance (injected by server)
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
JSON-serializable dict with query results or error
|
|
20
|
+
"""
|
|
21
|
+
return db_client.query(sql)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
List columns tool - List all columns of a table or view.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
DESCRIPTION = (
|
|
8
|
+
"List all columns of a table or view with their types and comments. "
|
|
9
|
+
"If database/schema are not specified, uses the current database/schema."
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def list_columns(
|
|
14
|
+
table: str,
|
|
15
|
+
db_client: Any,
|
|
16
|
+
database: str | None = None,
|
|
17
|
+
schema: str | None = None,
|
|
18
|
+
) -> dict[str, Any]:
|
|
19
|
+
"""
|
|
20
|
+
List all columns of a table or view.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
table: Table or view name
|
|
24
|
+
db_client: DatabaseClient instance (injected by server)
|
|
25
|
+
database: Database name (defaults to current database)
|
|
26
|
+
schema: Schema name (defaults to current schema)
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
JSON-serializable dict with column list or error
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
# Get current database if not specified
|
|
33
|
+
if database is None:
|
|
34
|
+
_, _, db_rows = db_client.execute_raw("SELECT current_database()")
|
|
35
|
+
database = db_rows[0][0]
|
|
36
|
+
|
|
37
|
+
# Get current schema if not specified
|
|
38
|
+
if schema is None:
|
|
39
|
+
_, _, schema_rows = db_client.execute_raw("SELECT current_schema()")
|
|
40
|
+
schema = schema_rows[0][0]
|
|
41
|
+
|
|
42
|
+
# Query columns using DuckDB system function
|
|
43
|
+
sql = f"""
|
|
44
|
+
SELECT
|
|
45
|
+
column_name as name,
|
|
46
|
+
data_type as type,
|
|
47
|
+
is_nullable = 'YES' as nullable,
|
|
48
|
+
comment
|
|
49
|
+
FROM duckdb_columns()
|
|
50
|
+
WHERE database_name = '{database}'
|
|
51
|
+
AND schema_name = '{schema}'
|
|
52
|
+
AND table_name = '{table}'
|
|
53
|
+
ORDER BY column_index
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
_, _, rows = db_client.execute_raw(sql)
|
|
57
|
+
|
|
58
|
+
# Transform results
|
|
59
|
+
columns = [
|
|
60
|
+
{
|
|
61
|
+
"name": row[0],
|
|
62
|
+
"type": row[1],
|
|
63
|
+
"nullable": bool(row[2]),
|
|
64
|
+
"comment": row[3] if row[3] else None,
|
|
65
|
+
}
|
|
66
|
+
for row in rows
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
# Determine if it's a view or table
|
|
70
|
+
object_type = "table"
|
|
71
|
+
try:
|
|
72
|
+
_, _, view_rows = db_client.execute_raw(f"""
|
|
73
|
+
SELECT 1 FROM duckdb_views()
|
|
74
|
+
WHERE database_name = '{database}'
|
|
75
|
+
AND schema_name = '{schema}'
|
|
76
|
+
AND view_name = '{table}'
|
|
77
|
+
LIMIT 1
|
|
78
|
+
""")
|
|
79
|
+
if view_rows:
|
|
80
|
+
object_type = "view"
|
|
81
|
+
except Exception:
|
|
82
|
+
pass # Assume table if check fails
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
"success": True,
|
|
86
|
+
"database": database,
|
|
87
|
+
"schema": schema,
|
|
88
|
+
"table": table,
|
|
89
|
+
"objectType": object_type,
|
|
90
|
+
"columns": columns,
|
|
91
|
+
"columnCount": len(columns),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
return {
|
|
96
|
+
"success": False,
|
|
97
|
+
"error": str(e),
|
|
98
|
+
"errorType": type(e).__name__,
|
|
99
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
List databases tool - Show all databases available in the connection.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
DESCRIPTION = "List all databases with their names and types."
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def list_databases(db_client: Any) -> dict[str, Any]:
|
|
11
|
+
"""
|
|
12
|
+
List all databases available in the connection.
|
|
13
|
+
|
|
14
|
+
For MotherDuck: Uses MD_ALL_DATABASES() to list all databases.
|
|
15
|
+
For local DuckDB: Uses duckdb_databases() system function.
|
|
16
|
+
|
|
17
|
+
Excludes internal databases: 'system' and 'temp'.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
db_client: DatabaseClient instance (injected by server)
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
JSON-serializable dict with database list or error
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
# Try MotherDuck function first (works for MotherDuck connections)
|
|
27
|
+
try:
|
|
28
|
+
_, _, rows = db_client.execute_raw(
|
|
29
|
+
"SELECT alias, type FROM MD_ALL_DATABASES() "
|
|
30
|
+
"WHERE alias IS NOT NULL AND alias NOT IN ('system', 'temp')"
|
|
31
|
+
)
|
|
32
|
+
databases = [{"name": row[0], "type": row[1]} for row in rows]
|
|
33
|
+
except Exception:
|
|
34
|
+
# Fall back to DuckDB system function (works for local DuckDB)
|
|
35
|
+
_, _, rows = db_client.execute_raw(
|
|
36
|
+
"SELECT database_name, type FROM duckdb_databases() "
|
|
37
|
+
"WHERE database_name NOT IN ('system', 'temp')"
|
|
38
|
+
)
|
|
39
|
+
databases = [{"name": row[0], "type": row[1]} for row in rows]
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
"success": True,
|
|
43
|
+
"databases": databases,
|
|
44
|
+
"databaseCount": len(databases),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
except Exception as e:
|
|
48
|
+
return {
|
|
49
|
+
"success": False,
|
|
50
|
+
"error": str(e),
|
|
51
|
+
"errorType": type(e).__name__,
|
|
52
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
List tables tool - List all tables and views in a database.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
DESCRIPTION = (
|
|
8
|
+
"List all tables and views in a database with their comments. "
|
|
9
|
+
"If database is not specified, uses the current database."
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def list_tables(
|
|
14
|
+
db_client: Any,
|
|
15
|
+
database: str | None = None,
|
|
16
|
+
schema: str | None = None,
|
|
17
|
+
) -> dict[str, Any]:
|
|
18
|
+
"""
|
|
19
|
+
List all tables and views in a database.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
db_client: DatabaseClient instance (injected by server)
|
|
23
|
+
database: Database name to list tables from (defaults to current database)
|
|
24
|
+
schema: Optional schema name to filter by (defaults to all schemas)
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
JSON-serializable dict with table/view list or error
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
# Get current database if not specified
|
|
31
|
+
if database is None:
|
|
32
|
+
_, _, db_rows = db_client.execute_raw("SELECT current_database()")
|
|
33
|
+
database = db_rows[0][0]
|
|
34
|
+
|
|
35
|
+
# Build schema filter
|
|
36
|
+
schema_filter = f"AND schema_name = '{schema}'" if schema else ""
|
|
37
|
+
|
|
38
|
+
# Query tables and views using DuckDB system functions
|
|
39
|
+
sql = f"""
|
|
40
|
+
SELECT
|
|
41
|
+
schema_name as schema,
|
|
42
|
+
table_name as name,
|
|
43
|
+
'table' as type,
|
|
44
|
+
comment
|
|
45
|
+
FROM duckdb_tables()
|
|
46
|
+
WHERE database_name = '{database}' {schema_filter}
|
|
47
|
+
|
|
48
|
+
UNION ALL
|
|
49
|
+
|
|
50
|
+
SELECT
|
|
51
|
+
schema_name as schema,
|
|
52
|
+
view_name as name,
|
|
53
|
+
'view' as type,
|
|
54
|
+
comment
|
|
55
|
+
FROM duckdb_views()
|
|
56
|
+
WHERE database_name = '{database}' {schema_filter}
|
|
57
|
+
|
|
58
|
+
ORDER BY schema, type, name
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
_, _, rows = db_client.execute_raw(sql)
|
|
62
|
+
|
|
63
|
+
# Transform results
|
|
64
|
+
tables = [
|
|
65
|
+
{
|
|
66
|
+
"schema": row[0],
|
|
67
|
+
"name": row[1],
|
|
68
|
+
"type": row[2],
|
|
69
|
+
"comment": row[3] if row[3] else None,
|
|
70
|
+
}
|
|
71
|
+
for row in rows
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
table_count = sum(1 for t in tables if t["type"] == "table")
|
|
75
|
+
view_count = sum(1 for t in tables if t["type"] == "view")
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
"success": True,
|
|
79
|
+
"database": database,
|
|
80
|
+
"schema": schema or "all",
|
|
81
|
+
"tables": tables,
|
|
82
|
+
"tableCount": table_count,
|
|
83
|
+
"viewCount": view_count,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
return {
|
|
88
|
+
"success": False,
|
|
89
|
+
"error": str(e),
|
|
90
|
+
"errorType": type(e).__name__,
|
|
91
|
+
}
|