mcp-server-motherduck 0.6.3__py3-none-any.whl → 0.7.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 mcp-server-motherduck might be problematic. Click here for more details.
- mcp_server_motherduck/configs.py +1 -1
- mcp_server_motherduck/database.py +70 -2
- {mcp_server_motherduck-0.6.3.dist-info → mcp_server_motherduck-0.7.0.dist-info}/METADATA +75 -3
- mcp_server_motherduck-0.7.0.dist-info/RECORD +10 -0
- mcp_server_motherduck-0.6.3.dist-info/RECORD +0 -10
- {mcp_server_motherduck-0.6.3.dist-info → mcp_server_motherduck-0.7.0.dist-info}/WHEEL +0 -0
- {mcp_server_motherduck-0.6.3.dist-info → mcp_server_motherduck-0.7.0.dist-info}/entry_points.txt +0 -0
- {mcp_server_motherduck-0.6.3.dist-info → mcp_server_motherduck-0.7.0.dist-info}/licenses/LICENSE +0 -0
mcp_server_motherduck/configs.py
CHANGED
|
@@ -36,6 +36,10 @@ class DatabaseClient:
|
|
|
36
36
|
|
|
37
37
|
logger.info(f"🔌 Connecting to {self.db_type} database")
|
|
38
38
|
|
|
39
|
+
# S3 databases don't support read-only mode
|
|
40
|
+
if self.db_type == "s3" and self._read_only:
|
|
41
|
+
raise ValueError("Read-only mode is not supported for S3 databases")
|
|
42
|
+
|
|
39
43
|
if self.db_type == "duckdb" and self._read_only:
|
|
40
44
|
# check that we can connect, issue a `select 1` and then close + return None
|
|
41
45
|
try:
|
|
@@ -53,6 +57,66 @@ class DatabaseClient:
|
|
|
53
57
|
logger.error(f"❌ Read-only check failed: {e}")
|
|
54
58
|
raise
|
|
55
59
|
|
|
60
|
+
# Check if this is an S3 path
|
|
61
|
+
if self.db_type == "s3":
|
|
62
|
+
# For S3, we need to create an in-memory connection and attach the S3 database
|
|
63
|
+
conn = duckdb.connect(':memory:')
|
|
64
|
+
|
|
65
|
+
# Install and load the httpfs extension for S3 support
|
|
66
|
+
import io
|
|
67
|
+
from contextlib import redirect_stdout, redirect_stderr
|
|
68
|
+
|
|
69
|
+
null_file = io.StringIO()
|
|
70
|
+
with redirect_stdout(null_file), redirect_stderr(null_file):
|
|
71
|
+
try:
|
|
72
|
+
conn.execute("INSTALL httpfs;")
|
|
73
|
+
except:
|
|
74
|
+
pass # Extension might already be installed
|
|
75
|
+
conn.execute("LOAD httpfs;")
|
|
76
|
+
|
|
77
|
+
# Configure S3 credentials from environment variables using CREATE SECRET
|
|
78
|
+
aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID')
|
|
79
|
+
aws_secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
|
|
80
|
+
aws_region = os.environ.get('AWS_DEFAULT_REGION', 'us-east-1')
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if aws_access_key and aws_secret_key:
|
|
84
|
+
# Use CREATE SECRET for better credential management
|
|
85
|
+
conn.execute(f"""
|
|
86
|
+
CREATE SECRET IF NOT EXISTS s3_secret (
|
|
87
|
+
TYPE S3,
|
|
88
|
+
KEY_ID '{aws_access_key}',
|
|
89
|
+
SECRET '{aws_secret_key}',
|
|
90
|
+
REGION '{aws_region}'
|
|
91
|
+
);
|
|
92
|
+
""")
|
|
93
|
+
|
|
94
|
+
# Attach the S3 database
|
|
95
|
+
try:
|
|
96
|
+
# For S3, we always attach as READ_ONLY since S3 storage is typically read-only
|
|
97
|
+
# Even when not in read_only mode, we attach as READ_ONLY for S3
|
|
98
|
+
conn.execute(f"ATTACH '{self.db_path}' AS s3db (READ_ONLY);")
|
|
99
|
+
# Use the attached database
|
|
100
|
+
conn.execute("USE s3db;")
|
|
101
|
+
logger.info(f"✅ Successfully connected to {self.db_type} database (attached as read-only)")
|
|
102
|
+
except Exception as e:
|
|
103
|
+
logger.error(f"Failed to attach S3 database: {e}")
|
|
104
|
+
# If the database doesn't exist and we're not in read-only mode, try to create it
|
|
105
|
+
if "database does not exist" in str(e) and not self._read_only:
|
|
106
|
+
logger.info("S3 database doesn't exist, attempting to create it...")
|
|
107
|
+
try:
|
|
108
|
+
# Create a new database at the S3 location
|
|
109
|
+
conn.execute(f"ATTACH '{self.db_path}' AS s3db;")
|
|
110
|
+
conn.execute("USE s3db;")
|
|
111
|
+
logger.info(f"✅ Created new S3 database at {self.db_path}")
|
|
112
|
+
except Exception as create_error:
|
|
113
|
+
logger.error(f"Failed to create S3 database: {create_error}")
|
|
114
|
+
raise
|
|
115
|
+
else:
|
|
116
|
+
raise
|
|
117
|
+
|
|
118
|
+
return conn
|
|
119
|
+
|
|
56
120
|
conn = duckdb.connect(
|
|
57
121
|
self.db_path,
|
|
58
122
|
config={"custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"},
|
|
@@ -65,8 +129,12 @@ class DatabaseClient:
|
|
|
65
129
|
|
|
66
130
|
def _resolve_db_path_type(
|
|
67
131
|
self, db_path: str, motherduck_token: str | None = None, saas_mode: bool = False
|
|
68
|
-
) -> tuple[str, Literal["duckdb", "motherduck"]]:
|
|
132
|
+
) -> tuple[str, Literal["duckdb", "motherduck", "s3"]]:
|
|
69
133
|
"""Resolve and validate the database path"""
|
|
134
|
+
# Handle S3 paths
|
|
135
|
+
if db_path.startswith("s3://"):
|
|
136
|
+
return db_path, "s3"
|
|
137
|
+
|
|
70
138
|
# Handle MotherDuck paths
|
|
71
139
|
if db_path.startswith("md:"):
|
|
72
140
|
if motherduck_token:
|
|
@@ -102,7 +170,7 @@ class DatabaseClient:
|
|
|
102
170
|
|
|
103
171
|
def _execute(self, query: str) -> str:
|
|
104
172
|
if self.conn is None:
|
|
105
|
-
# open short lived readonly connection, run query, close connection, return result
|
|
173
|
+
# open short lived readonly connection for local DuckDB, run query, close connection, return result
|
|
106
174
|
conn = duckdb.connect(
|
|
107
175
|
self.db_path,
|
|
108
176
|
config={"custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-server-motherduck
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A MCP server for MotherDuck and local DuckDB
|
|
5
5
|
Author-email: tdoehmen <till@motherduck.com>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -19,7 +19,7 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
|
|
20
20
|
An MCP server implementation that interacts with DuckDB and MotherDuck databases, providing SQL analytics capabilities to AI Assistants and IDEs.
|
|
21
21
|
|
|
22
|
-
[
|
|
22
|
+
[<img src="https://cursor.com/deeplink/mcp-install-dark.svg" alt="Install in Cursor">](https://cursor.com/en/install-mcp?name=DuckDB&config=eyJjb21tYW5kIjoidXZ4IG1jcC1zZXJ2ZXItbW90aGVyZHVjayAtLWRiLXBhdGggOm1lbW9yeToiLCJlbnYiOnsibW90aGVyZHVja190b2tlbiI6IiJ9fQ%3D%3D)
|
|
23
23
|
|
|
24
24
|
## Resources
|
|
25
25
|
- [Close the Loop: Faster Data Pipelines with MCP, DuckDB & AI (Blogpost)](https://motherduck.com/blog/faster-data-pipelines-with-mcp-duckdb-ai/)
|
|
@@ -59,7 +59,7 @@ The MCP server supports the following parameters:
|
|
|
59
59
|
|-----------|------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
60
60
|
| `--transport` | Choice | `stdio` | Transport type. Options: `stdio`, `sse`, `stream` |
|
|
61
61
|
| `--port` | Integer | `8000` | Port to listen on for sse and stream transport mode |
|
|
62
|
-
| `--db-path` | String | `md:` | Path to local DuckDB database file or
|
|
62
|
+
| `--db-path` | String | `md:` | Path to local DuckDB database file, MotherDuck database, or S3 URL (e.g., `s3://bucket/path/to/db.duckdb`) |
|
|
63
63
|
| `--motherduck-token` | String | `None` | Access token to use for MotherDuck database connections (uses `motherduck_token` env var by default) |
|
|
64
64
|
| `--read-only` | Flag | `False` | Flag for connecting to DuckDB or MotherDuck in read-only mode. For DuckDB it uses short-lived connections to enable concurrent access |
|
|
65
65
|
| `--home-dir` | String | `None` | Home directory for DuckDB (uses `HOME` env var by default) |
|
|
@@ -223,6 +223,44 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
|
|
|
223
223
|
|
|
224
224
|
- Replace `YOUR_MOTHERDUCK_TOKEN_HERE` with your actual MotherDuck token
|
|
225
225
|
|
|
226
|
+
### Usage with Claude Code
|
|
227
|
+
|
|
228
|
+
Claude Code supports MCP servers through CLI commands or JSON configuration. Here are two ways to set it up:
|
|
229
|
+
|
|
230
|
+
#### Option 1: Using CLI Commands
|
|
231
|
+
|
|
232
|
+
Add the MotherDuck MCP server directly using the Claude Code CLI:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
claude mcp add mcp-server-motherduck uvx mcp-server-motherduck -- --db-path md: --motherduck-token <YOUR_MOTHERDUCK_TOKEN_HERE>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
#### Option 2: Using JSON Configuration
|
|
239
|
+
|
|
240
|
+
Add the server using a JSON configuration:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
claude mcp add-json mcp-server-motherduck '{
|
|
244
|
+
"command": "uvx",
|
|
245
|
+
"args": [
|
|
246
|
+
"mcp-server-motherduck",
|
|
247
|
+
"--db-path",
|
|
248
|
+
"md:",
|
|
249
|
+
"--motherduck-token",
|
|
250
|
+
"<YOUR_MOTHERDUCK_TOKEN_HERE>"
|
|
251
|
+
]
|
|
252
|
+
}'
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Scoping Options**:
|
|
256
|
+
- Use `--local` (default) for project-specific configuration
|
|
257
|
+
- Use `--project` to share the configuration with your team via `.mcp.json`
|
|
258
|
+
- Use `--user` to make the server available across all your projects
|
|
259
|
+
|
|
260
|
+
**Important Notes**:
|
|
261
|
+
- Replace `YOUR_MOTHERDUCK_TOKEN_HERE` with your actual MotherDuck token
|
|
262
|
+
- Claude Code also supports environment variable expansion, so you can use `${MOTHERDUCK_TOKEN}` if you've set the environment variable
|
|
263
|
+
|
|
226
264
|
## Securing your MCP Server when querying MotherDuck
|
|
227
265
|
|
|
228
266
|
If the MCP server is exposed to third parties and should only have read access to data, we recommend using a read scaling token and running the MCP server in SaaS mode.
|
|
@@ -316,6 +354,37 @@ modeling data within duckdb and then an MCP client (Windsurf/Cline/Claude/Cursor
|
|
|
316
354
|
was used for exploring the database. The short lived connections allow each tool
|
|
317
355
|
to run and then release their connection, allowing the next tool to connect.
|
|
318
356
|
|
|
357
|
+
## Connect to DuckDB on S3
|
|
358
|
+
|
|
359
|
+
You can connect to DuckDB databases stored on Amazon S3 by providing an S3 URL as the database path. The server will automatically configure the necessary S3 credentials from your environment variables.
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
{
|
|
363
|
+
"mcpServers": {
|
|
364
|
+
"mcp-server-motherduck": {
|
|
365
|
+
"command": "uvx",
|
|
366
|
+
"args": [
|
|
367
|
+
"mcp-server-motherduck",
|
|
368
|
+
"--db-path",
|
|
369
|
+
"s3://your-bucket/path/to/database.duckdb"
|
|
370
|
+
],
|
|
371
|
+
"env": {
|
|
372
|
+
"AWS_ACCESS_KEY_ID": "<your_key>",
|
|
373
|
+
"AWS_SECRET_ACCESS_KEY": "<your_secret>",
|
|
374
|
+
"AWS_DEFAULT_REGION": "<your_region>"
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
**Note**: For S3 connections:
|
|
383
|
+
- AWS credentials must be provided via environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and optionally `AWS_DEFAULT_REGION`)
|
|
384
|
+
- The S3 database is attached to an in-memory DuckDB instance
|
|
385
|
+
- The httpfs extension is automatically installed and configured for S3 access
|
|
386
|
+
- Both read and write operations are supported
|
|
387
|
+
|
|
319
388
|
## Example Queries
|
|
320
389
|
|
|
321
390
|
Once configured, you can e.g. ask Claude to run queries like:
|
|
@@ -384,3 +453,6 @@ To run the server from a local development environment, use the following config
|
|
|
384
453
|
## License
|
|
385
454
|
|
|
386
455
|
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
|
|
456
|
+
|
|
457
|
+
##
|
|
458
|
+
mcp-name: io.github.motherduckdb/mcp-server-motherduck
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
mcp_server_motherduck/__init__.py,sha256=vogjO-bwHYZFut85eeiVmRepamcWHk8bImri_kBKZMU,6026
|
|
2
|
+
mcp_server_motherduck/configs.py,sha256=KZ3y9AuLhq7q_kW99Qz44b4t1_3PbciHb1Eqcy5pc8g,771
|
|
3
|
+
mcp_server_motherduck/database.py,sha256=-n1Z0UXsi5Y3nO1_df1CeHiSEe644gJNkJ7r_pdWytw,7761
|
|
4
|
+
mcp_server_motherduck/prompt.py,sha256=P7BrmhVXwDkPeSHQ3f25WMP6lpBpN2BxDzYPOQ3fxX8,56699
|
|
5
|
+
mcp_server_motherduck/server.py,sha256=U1LM2oQ36gO_pAZuez9HV_u8YDWdER8tQIdDbiXfzx0,5232
|
|
6
|
+
mcp_server_motherduck-0.7.0.dist-info/METADATA,sha256=HaTJoYMP0xjrqejg4jy2LF0wLS4GEChlkIGJ_J6a2Lc,17820
|
|
7
|
+
mcp_server_motherduck-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
+
mcp_server_motherduck-0.7.0.dist-info/entry_points.txt,sha256=dRTgcvWJn40bz0PVuKPylK6w92cFN32lwunZOgo5j4s,69
|
|
9
|
+
mcp_server_motherduck-0.7.0.dist-info/licenses/LICENSE,sha256=Tj68w9jCiceFKTvZ3jET-008NjhozcQMXpm-fyL9WUI,1067
|
|
10
|
+
mcp_server_motherduck-0.7.0.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
mcp_server_motherduck/__init__.py,sha256=vogjO-bwHYZFut85eeiVmRepamcWHk8bImri_kBKZMU,6026
|
|
2
|
-
mcp_server_motherduck/configs.py,sha256=JP8izAKamXdeU6Rk0N5XjPXvN71WG3K0WdYD9_ChN-M,771
|
|
3
|
-
mcp_server_motherduck/database.py,sha256=3qkeBIpf08WV6pz9rf52b_MipzaHALurFugMx-XARI8,4513
|
|
4
|
-
mcp_server_motherduck/prompt.py,sha256=P7BrmhVXwDkPeSHQ3f25WMP6lpBpN2BxDzYPOQ3fxX8,56699
|
|
5
|
-
mcp_server_motherduck/server.py,sha256=U1LM2oQ36gO_pAZuez9HV_u8YDWdER8tQIdDbiXfzx0,5232
|
|
6
|
-
mcp_server_motherduck-0.6.3.dist-info/METADATA,sha256=jh9FiBLMG2fQ7GcS9vg8aBTn-4WShnVHXcd1yTFpWmw,15606
|
|
7
|
-
mcp_server_motherduck-0.6.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
-
mcp_server_motherduck-0.6.3.dist-info/entry_points.txt,sha256=dRTgcvWJn40bz0PVuKPylK6w92cFN32lwunZOgo5j4s,69
|
|
9
|
-
mcp_server_motherduck-0.6.3.dist-info/licenses/LICENSE,sha256=Tj68w9jCiceFKTvZ3jET-008NjhozcQMXpm-fyL9WUI,1067
|
|
10
|
-
mcp_server_motherduck-0.6.3.dist-info/RECORD,,
|
|
File without changes
|
{mcp_server_motherduck-0.6.3.dist-info → mcp_server_motherduck-0.7.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{mcp_server_motherduck-0.6.3.dist-info → mcp_server_motherduck-0.7.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|