mcp-server-motherduck 0.4.2__tar.gz → 0.4.3__tar.gz

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.
Files changed (17) hide show
  1. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/.idea/misc.xml +0 -1
  2. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/.idea/workspace.xml +27 -21
  3. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/PKG-INFO +44 -9
  4. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/README.md +43 -8
  5. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/pyproject.toml +1 -1
  6. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/src/mcp_server_motherduck/__init__.py +7 -0
  7. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/src/mcp_server_motherduck/server.py +65 -22
  8. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/uv.lock +1 -1
  9. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/.github/workflows/python-publish.yml +0 -0
  10. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/.gitignore +0 -0
  11. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/.idea/.gitignore +0 -0
  12. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/.idea/mcp-server-motherduck.iml +0 -0
  13. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/.idea/modules.xml +0 -0
  14. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/.idea/vcs.xml +0 -0
  15. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/LICENSE +0 -0
  16. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/makefile +0 -0
  17. {mcp_server_motherduck-0.4.2 → mcp_server_motherduck-0.4.3}/src/mcp_server_motherduck/prompt.py +0 -0
@@ -1,4 +1,3 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
1
  <project version="4">
3
2
  <component name="ProjectRootManager" version="2" languageLevel="JDK_20" project-jdk-name="Python 3.11" project-jdk-type="Python SDK">
4
3
  <output url="file://$PROJECT_DIR$/out" />
@@ -4,7 +4,10 @@
4
4
  <option name="autoReloadType" value="SELECTIVE" />
5
5
  </component>
6
6
  <component name="ChangeListManager">
7
- <list default="true" id="8bdee1d4-886c-4093-b4cf-95b120034c9e" name="Changes" comment="" />
7
+ <list default="true" id="8bdee1d4-886c-4093-b4cf-95b120034c9e" name="Changes" comment="">
8
+ <change beforePath="$PROJECT_DIR$/pyproject.toml" beforeDir="false" afterPath="$PROJECT_DIR$/pyproject.toml" afterDir="false" />
9
+ <change beforePath="$PROJECT_DIR$/src/mcp_server_motherduck/server.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/mcp_server_motherduck/server.py" afterDir="false" />
10
+ </list>
8
11
  <option name="SHOW_DIALOG" value="false" />
9
12
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
10
13
  <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -16,32 +19,32 @@
16
19
  <component name="MarkdownSettingsMigration">
17
20
  <option name="stateVersion" value="1" />
18
21
  </component>
19
- <component name="ProjectColorInfo"><![CDATA[{
20
- "associatedIndex": 3
21
- }]]></component>
22
+ <component name="ProjectColorInfo">{
23
+ &quot;associatedIndex&quot;: 3
24
+ }</component>
22
25
  <component name="ProjectId" id="2vcb2orYlzlw5ZHOvoXgpTtCdVi" />
23
26
  <component name="ProjectViewState">
24
27
  <option name="hideEmptyMiddlePackages" value="true" />
25
28
  <option name="showLibraryContents" value="true" />
26
29
  </component>
27
- <component name="PropertiesComponent"><![CDATA[{
28
- "keyToString": {
29
- "RunOnceActivity.OpenProjectViewOnStart": "true",
30
- "RunOnceActivity.ShowReadmeOnStart": "true",
31
- "WebServerToolWindowFactoryState": "false",
32
- "git-widget-placeholder": "till/add__saas__mode",
33
- "last_opened_file_path": "/Users/doehmen/Documents/motherduck/mcp-server-motherduck",
34
- "node.js.detected.package.eslint": "true",
35
- "node.js.detected.package.tslint": "true",
36
- "node.js.selected.package.eslint": "(autodetect)",
37
- "node.js.selected.package.tslint": "(autodetect)",
38
- "nodejs_package_manager_path": "npm",
39
- "project.structure.last.edited": "Project",
40
- "project.structure.proportion": "0.0",
41
- "project.structure.side.proportion": "0.0",
42
- "vue.rearranger.settings.migration": "true"
30
+ <component name="PropertiesComponent">{
31
+ &quot;keyToString&quot;: {
32
+ &quot;RunOnceActivity.OpenProjectViewOnStart&quot;: &quot;true&quot;,
33
+ &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
34
+ &quot;WebServerToolWindowFactoryState&quot;: &quot;false&quot;,
35
+ &quot;git-widget-placeholder&quot;: &quot;main&quot;,
36
+ &quot;last_opened_file_path&quot;: &quot;/Users/doehmen/Documents/motherduck/mcp-server-motherduck&quot;,
37
+ &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
38
+ &quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
39
+ &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
40
+ &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
41
+ &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
42
+ &quot;project.structure.last.edited&quot;: &quot;Project&quot;,
43
+ &quot;project.structure.proportion&quot;: &quot;0.0&quot;,
44
+ &quot;project.structure.side.proportion&quot;: &quot;0.0&quot;,
45
+ &quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
43
46
  }
44
- }]]></component>
47
+ }</component>
45
48
  <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
46
49
  <component name="TaskManager">
47
50
  <task active="true" id="Default" summary="Default task">
@@ -51,6 +54,9 @@
51
54
  <option name="presentableId" value="Default" />
52
55
  <updated>1744447105618</updated>
53
56
  <workItem from="1744447107447" duration="7470000" />
57
+ <workItem from="1744787944049" duration="599000" />
58
+ <workItem from="1744896732489" duration="6070000" />
59
+ <workItem from="1747725447463" duration="540000" />
54
60
  </task>
55
61
  <servers />
56
62
  </component>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-server-motherduck
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: A MCP server for MotherDuck and local DuckDB
5
5
  Author-email: tdoehmen <till@motherduck.com>
6
6
  License-File: LICENSE
@@ -15,6 +15,10 @@ Description-Content-Type: text/markdown
15
15
 
16
16
  An MCP server implementation that interacts with DuckDB and MotherDuck databases, providing SQL analytics capabilities to AI Assistants and IDEs.
17
17
 
18
+ ## Resources
19
+ - [Close the Loop: Faster Data Pipelines with MCP, DuckDB & AI (Blogpost)](https://motherduck.com/blog/faster-data-pipelines-with-mcp-duckdb-ai/)
20
+ - [Faster Data Pipelines development with MCP and DuckDB (YouTube)](https://www.youtube.com/watch?v=yG1mv8ZRxcU)
21
+
18
22
  ## Features
19
23
 
20
24
  - **Hybrid execution**: query data from local DuckDB or/and cloud-based MotherDuck databases
@@ -44,13 +48,14 @@ All interactions with both DuckDB and MotherDuck are done through writing SQL qu
44
48
  ## Getting Started
45
49
 
46
50
  ### General Prerequisites
51
+
47
52
  - `uv` installed, you can install it using `pip install uv` or `brew install uv`
48
53
 
49
- If you plan to use the MCP with Claude Desktop or any other MCP comptabile client, the client need to be installed.
54
+ If you plan to use the MCP with Claude Desktop or any other MCP comptabile client, the client need to be installed.
50
55
 
51
56
  ### Prerequisites for DuckDB
52
57
 
53
- - No prerequisites. The MCP server can create an in-memory database on-the-fly
58
+ - No prerequisites. The MCP server can create an in-memory database on-the-fly
54
59
  - Or connect to an existing local DuckDB database file , or one stored on remote object storage (e.g., AWS S3).
55
60
 
56
61
  See [Connect to local DuckDB](#connect-to-local-duckdb).
@@ -67,7 +72,7 @@ See [Connect to local DuckDB](#connect-to-local-duckdb).
67
72
 
68
73
  2. Open Cursor:
69
74
 
70
- - To set it up globally for the first time, go to Settings->MCP and click on "+ Add new global MCP server".
75
+ - To set it up globally for the first time, go to Settings->MCP and click on "+ Add new global MCP server".
71
76
  - This will open a `mcp.json` file to which you add the following configuration:
72
77
 
73
78
  ```json
@@ -90,6 +95,7 @@ See [Connect to local DuckDB](#connect-to-local-duckdb).
90
95
  ### Usage with VS Code
91
96
 
92
97
  [![Install with UV in VS Code](https://img.shields.io/badge/VS_Code-UV-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=mcp-server-motherduck&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22mcp-server-motherduck%22%2C%22--db-path%22%2C%22md%3A%22%2C%22--motherduck-token%22%2C%22%24%7Binput%3Amotherduck_token%7D%22%5D%7D&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22motherduck_token%22%2C%22description%22%3A%22MotherDuck+Token%22%2C%22password%22%3Atrue%7D%5D) [![Install with UV in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-UV-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=mcp-server-motherduck&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22mcp-server-motherduck%22%2C%22--db-path%22%2C%22md%3A%22%2C%22--motherduck-token%22%2C%22%24%7Binput%3Amotherduck_token%7D%22%5D%7D&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22motherduck_token%22%2C%22description%22%3A%22MotherDuck+Token%22%2C%22password%22%3Atrue%7D%5D&quality=insiders)
98
+
93
99
  1. For the quickest installation, click one of the "Install with UV" buttons at the top of this README.
94
100
 
95
101
  ### Manual Installation
@@ -186,12 +192,13 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
186
192
 
187
193
  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.
188
194
 
189
- **Read Scaling Tokens** are special access tokens that enable scalable read operations by allowing up to 4 concurrent read replicas, improving performance for multiple end users while *restricting write capabilities*.
195
+ **Read Scaling Tokens** are special access tokens that enable scalable read operations by allowing up to 4 concurrent read replicas, improving performance for multiple end users while *restricting write capabilities*.
190
196
  Refer to the [Read Scaling documentation](https://motherduck.com/docs/key-tasks/authenticating-and-connecting-to-motherduck/read-scaling/#creating-a-read-scaling-token) to learn how to create a read-scaling token.
191
197
 
192
198
  **SaaS Mode** in MotherDuck enhances security by restricting it's access to local files, databases, extensions, and configurations, making it ideal for third-party tools that require stricter environment protection. Learn more about it in the [SaaS Mode documentation](https://motherduck.com/docs/key-tasks/authenticating-and-connecting-to-motherduck/authenticating-to-motherduck/#authentication-using-saas-mode).
193
199
 
194
200
  **Secure Configuration**
201
+
195
202
  ```json
196
203
  {
197
204
  "mcpServers": {
@@ -215,6 +222,7 @@ Refer to the [Read Scaling documentation](https://motherduck.com/docs/key-tasks/
215
222
  To connect to a local DuckDB, instead of using the MotherDuck token, specify the path to your local DuckDB database file or use `:memory:` for an in-memory database.
216
223
 
217
224
  In-memory database:
225
+
218
226
  ```json
219
227
  {
220
228
  "mcpServers": {
@@ -231,6 +239,7 @@ In-memory database:
231
239
  ```
232
240
 
233
241
  Local DuckDB file:
242
+
234
243
  ```json
235
244
  {
236
245
  "mcpServers": {
@@ -246,6 +255,32 @@ Local DuckDB file:
246
255
  }
247
256
  ```
248
257
 
258
+ Local DuckDB file in [readonly mode](https://duckdb.org/docs/stable/connect/concurrency.html):
259
+
260
+ ```json
261
+ {
262
+ "mcpServers": {
263
+ "mcp-server-motherduck": {
264
+ "command": "uvx",
265
+ "args": [
266
+ "mcp-server-motherduck",
267
+ "--db-path",
268
+ "/path/to/your/local.db",
269
+ "--read-only"
270
+ ]
271
+ }
272
+ }
273
+ }
274
+ ```
275
+
276
+ **Note**: readonly mode for local file-backed DuckDB connections also makes use of
277
+ short lived connections. Each time the query MCP tool is used a temporary,
278
+ reaodnly connection is created + query is executed + connection is closed. This
279
+ feature was motivated by a workflow where [DBT](https://www.getdbt.com) was for
280
+ modeling data within duckdb and then an MCP client (Windsurf/Cline/Claude/Cursor)
281
+ was used for exploring the database. The short lived connections allow each tool
282
+ to run and then release their connection, allowing the next tool to connect.
283
+
249
284
  ## Example Queries
250
285
 
251
286
  Once configured, you can e.g. ask Claude to run queries like:
@@ -307,10 +342,10 @@ To run the server from a local development environment, use the following config
307
342
  "mcp-server-motherduck": {
308
343
  "command": "uv",
309
344
  "args": [
310
- "--directory",
311
- "/path/to/your/local/mcp-server-motherduck",
312
- "run",
313
- "mcp-server-motherduck",
345
+ "--directory",
346
+ "/path/to/your/local/mcp-server-motherduck",
347
+ "run",
348
+ "mcp-server-motherduck",
314
349
  "--db-path",
315
350
  "md:",
316
351
  "--motherduck-token",
@@ -2,6 +2,10 @@
2
2
 
3
3
  An MCP server implementation that interacts with DuckDB and MotherDuck databases, providing SQL analytics capabilities to AI Assistants and IDEs.
4
4
 
5
+ ## Resources
6
+ - [Close the Loop: Faster Data Pipelines with MCP, DuckDB & AI (Blogpost)](https://motherduck.com/blog/faster-data-pipelines-with-mcp-duckdb-ai/)
7
+ - [Faster Data Pipelines development with MCP and DuckDB (YouTube)](https://www.youtube.com/watch?v=yG1mv8ZRxcU)
8
+
5
9
  ## Features
6
10
 
7
11
  - **Hybrid execution**: query data from local DuckDB or/and cloud-based MotherDuck databases
@@ -31,13 +35,14 @@ All interactions with both DuckDB and MotherDuck are done through writing SQL qu
31
35
  ## Getting Started
32
36
 
33
37
  ### General Prerequisites
38
+
34
39
  - `uv` installed, you can install it using `pip install uv` or `brew install uv`
35
40
 
36
- If you plan to use the MCP with Claude Desktop or any other MCP comptabile client, the client need to be installed.
41
+ If you plan to use the MCP with Claude Desktop or any other MCP comptabile client, the client need to be installed.
37
42
 
38
43
  ### Prerequisites for DuckDB
39
44
 
40
- - No prerequisites. The MCP server can create an in-memory database on-the-fly
45
+ - No prerequisites. The MCP server can create an in-memory database on-the-fly
41
46
  - Or connect to an existing local DuckDB database file , or one stored on remote object storage (e.g., AWS S3).
42
47
 
43
48
  See [Connect to local DuckDB](#connect-to-local-duckdb).
@@ -54,7 +59,7 @@ See [Connect to local DuckDB](#connect-to-local-duckdb).
54
59
 
55
60
  2. Open Cursor:
56
61
 
57
- - To set it up globally for the first time, go to Settings->MCP and click on "+ Add new global MCP server".
62
+ - To set it up globally for the first time, go to Settings->MCP and click on "+ Add new global MCP server".
58
63
  - This will open a `mcp.json` file to which you add the following configuration:
59
64
 
60
65
  ```json
@@ -77,6 +82,7 @@ See [Connect to local DuckDB](#connect-to-local-duckdb).
77
82
  ### Usage with VS Code
78
83
 
79
84
  [![Install with UV in VS Code](https://img.shields.io/badge/VS_Code-UV-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=mcp-server-motherduck&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22mcp-server-motherduck%22%2C%22--db-path%22%2C%22md%3A%22%2C%22--motherduck-token%22%2C%22%24%7Binput%3Amotherduck_token%7D%22%5D%7D&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22motherduck_token%22%2C%22description%22%3A%22MotherDuck+Token%22%2C%22password%22%3Atrue%7D%5D) [![Install with UV in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-UV-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=mcp-server-motherduck&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22mcp-server-motherduck%22%2C%22--db-path%22%2C%22md%3A%22%2C%22--motherduck-token%22%2C%22%24%7Binput%3Amotherduck_token%7D%22%5D%7D&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22motherduck_token%22%2C%22description%22%3A%22MotherDuck+Token%22%2C%22password%22%3Atrue%7D%5D&quality=insiders)
85
+
80
86
  1. For the quickest installation, click one of the "Install with UV" buttons at the top of this README.
81
87
 
82
88
  ### Manual Installation
@@ -173,12 +179,13 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
173
179
 
174
180
  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.
175
181
 
176
- **Read Scaling Tokens** are special access tokens that enable scalable read operations by allowing up to 4 concurrent read replicas, improving performance for multiple end users while *restricting write capabilities*.
182
+ **Read Scaling Tokens** are special access tokens that enable scalable read operations by allowing up to 4 concurrent read replicas, improving performance for multiple end users while *restricting write capabilities*.
177
183
  Refer to the [Read Scaling documentation](https://motherduck.com/docs/key-tasks/authenticating-and-connecting-to-motherduck/read-scaling/#creating-a-read-scaling-token) to learn how to create a read-scaling token.
178
184
 
179
185
  **SaaS Mode** in MotherDuck enhances security by restricting it's access to local files, databases, extensions, and configurations, making it ideal for third-party tools that require stricter environment protection. Learn more about it in the [SaaS Mode documentation](https://motherduck.com/docs/key-tasks/authenticating-and-connecting-to-motherduck/authenticating-to-motherduck/#authentication-using-saas-mode).
180
186
 
181
187
  **Secure Configuration**
188
+
182
189
  ```json
183
190
  {
184
191
  "mcpServers": {
@@ -202,6 +209,7 @@ Refer to the [Read Scaling documentation](https://motherduck.com/docs/key-tasks/
202
209
  To connect to a local DuckDB, instead of using the MotherDuck token, specify the path to your local DuckDB database file or use `:memory:` for an in-memory database.
203
210
 
204
211
  In-memory database:
212
+
205
213
  ```json
206
214
  {
207
215
  "mcpServers": {
@@ -218,6 +226,7 @@ In-memory database:
218
226
  ```
219
227
 
220
228
  Local DuckDB file:
229
+
221
230
  ```json
222
231
  {
223
232
  "mcpServers": {
@@ -233,6 +242,32 @@ Local DuckDB file:
233
242
  }
234
243
  ```
235
244
 
245
+ Local DuckDB file in [readonly mode](https://duckdb.org/docs/stable/connect/concurrency.html):
246
+
247
+ ```json
248
+ {
249
+ "mcpServers": {
250
+ "mcp-server-motherduck": {
251
+ "command": "uvx",
252
+ "args": [
253
+ "mcp-server-motherduck",
254
+ "--db-path",
255
+ "/path/to/your/local.db",
256
+ "--read-only"
257
+ ]
258
+ }
259
+ }
260
+ }
261
+ ```
262
+
263
+ **Note**: readonly mode for local file-backed DuckDB connections also makes use of
264
+ short lived connections. Each time the query MCP tool is used a temporary,
265
+ reaodnly connection is created + query is executed + connection is closed. This
266
+ feature was motivated by a workflow where [DBT](https://www.getdbt.com) was for
267
+ modeling data within duckdb and then an MCP client (Windsurf/Cline/Claude/Cursor)
268
+ was used for exploring the database. The short lived connections allow each tool
269
+ to run and then release their connection, allowing the next tool to connect.
270
+
236
271
  ## Example Queries
237
272
 
238
273
  Once configured, you can e.g. ask Claude to run queries like:
@@ -294,10 +329,10 @@ To run the server from a local development environment, use the following config
294
329
  "mcp-server-motherduck": {
295
330
  "command": "uv",
296
331
  "args": [
297
- "--directory",
298
- "/path/to/your/local/mcp-server-motherduck",
299
- "run",
300
- "mcp-server-motherduck",
332
+ "--directory",
333
+ "/path/to/your/local/mcp-server-motherduck",
334
+ "run",
335
+ "mcp-server-motherduck",
301
336
  "--db-path",
302
337
  "md:",
303
338
  "--motherduck-token",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcp-server-motherduck"
3
- version = "0.4.2"
3
+ version = "0.4.3"
4
4
  description = "A MCP server for MotherDuck and local DuckDB"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -39,6 +39,12 @@ def main():
39
39
  choices=["markdown", "duckbox", "text"],
40
40
  )
41
41
 
42
+ parser.add_argument(
43
+ "--read-only",
44
+ action="store_true",
45
+ help="Flag for connecting to DuckDB in read-only mode. Only supported for local DuckDB databases. Also makes use of short lived connections so multiple MCP clients or other systems can remain active (though each operation must be done sequentially).",
46
+ )
47
+
42
48
  args = parser.parse_args()
43
49
  logger.info("🦆 MotherDuck MCP Server v" + server.SERVER_VERSION)
44
50
  logger.info("Ready to execute SQL queries via DuckDB/MotherDuck")
@@ -51,6 +57,7 @@ def main():
51
57
  result_format=args.result_format,
52
58
  home_dir=args.home_dir,
53
59
  saas_mode=args.saas_mode,
60
+ read_only=args.read_only,
54
61
  )
55
62
  )
56
63
 
@@ -2,7 +2,7 @@ import os
2
2
  import logging
3
3
  import duckdb
4
4
  from pydantic import AnyUrl
5
- from typing import Literal
5
+ from typing import Literal, Optional
6
6
  import io
7
7
  from contextlib import redirect_stdout
8
8
  import mcp.server.stdio
@@ -12,7 +12,7 @@ from mcp.server.models import InitializationOptions
12
12
  from .prompt import PROMPT_TEMPLATE
13
13
 
14
14
 
15
- SERVER_VERSION = "0.4.2"
15
+ SERVER_VERSION = "0.4.3"
16
16
 
17
17
  logger = logging.getLogger("mcp_server_motherduck")
18
18
 
@@ -25,7 +25,9 @@ class DatabaseClient:
25
25
  result_format: Literal["markdown", "duckbox", "text"] = "markdown",
26
26
  home_dir: str | None = None,
27
27
  saas_mode: bool = False,
28
+ read_only: bool = False,
28
29
  ):
30
+ self._read_only = read_only
29
31
  self.db_path, self.db_type = self._resolve_db_path_type(
30
32
  db_path, motherduck_token, saas_mode
31
33
  )
@@ -38,11 +40,32 @@ class DatabaseClient:
38
40
  self.conn = self._initialize_connection()
39
41
  self.result_format = result_format
40
42
 
41
- def _initialize_connection(self) -> duckdb.DuckDBPyConnection:
43
+ def _initialize_connection(self) -> Optional[duckdb.DuckDBPyConnection]:
42
44
  """Initialize connection to the MotherDuck or DuckDB database"""
43
45
 
44
46
  logger.info(f"🔌 Connecting to {self.db_type} database")
45
47
 
48
+ if self.db_type == "duckdb" and self._read_only:
49
+ # check that we can connect, issue a `select 1` and then close + return None
50
+ try:
51
+ conn = duckdb.connect(
52
+ self.db_path,
53
+ config={
54
+ "custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"
55
+ },
56
+ read_only=self._read_only,
57
+ )
58
+ conn.execute("SELECT 1")
59
+ conn.close()
60
+ return None
61
+ except Exception as e:
62
+ logger.error(f"❌ Read-only check failed: {e}")
63
+ raise
64
+
65
+ if self._read_only:
66
+ raise ValueError(
67
+ "Read-only mode is only supported for local DuckDB databases. See `saas_mode` for similar functionality with MotherDuck."
68
+ )
46
69
  conn = duckdb.connect(
47
70
  self.db_path,
48
71
  config={"custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"},
@@ -62,9 +85,15 @@ class DatabaseClient:
62
85
  logger.info("Using MotherDuck token to connect to database `md:`")
63
86
  if saas_mode:
64
87
  logger.info("Connecting to MotherDuck in SaaS mode")
65
- return f"{db_path}?motherduck_token={motherduck_token}&saas_mode=true", "motherduck"
88
+ return (
89
+ f"{db_path}?motherduck_token={motherduck_token}&saas_mode=true",
90
+ "motherduck",
91
+ )
66
92
  else:
67
- return f"{db_path}?motherduck_token={motherduck_token}", "motherduck"
93
+ return (
94
+ f"{db_path}?motherduck_token={motherduck_token}",
95
+ "motherduck",
96
+ )
68
97
  elif os.getenv("motherduck_token"):
69
98
  logger.info(
70
99
  "Using MotherDuck token from env to connect to database `md:`"
@@ -88,25 +117,37 @@ class DatabaseClient:
88
117
  )
89
118
  return db_path, "duckdb"
90
119
 
120
+ def _execute(self, query: str) -> str:
121
+ if self.conn is None:
122
+ # open short lived readonly connection, run query, close connection, return result
123
+ conn = duckdb.connect(
124
+ self.db_path,
125
+ config={"custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"},
126
+ read_only=self._read_only,
127
+ )
128
+ q = conn.execute(query)
129
+ else:
130
+ q = self.conn.execute(query)
131
+
132
+ if self.result_format == "markdown":
133
+ out = q.fetchdf().to_markdown()
134
+ elif self.result_format == "duckbox":
135
+ # Duckbox version of the output
136
+ buffer = io.StringIO()
137
+ with redirect_stdout(buffer):
138
+ q.show(max_rows=100, max_col_width=20)
139
+ out = buffer.getvalue()
140
+ else:
141
+ out = str(q.fetchall())
142
+
143
+ if self.conn is None:
144
+ conn.close()
145
+
146
+ return out
147
+
91
148
  def query(self, query: str) -> str:
92
149
  try:
93
- if self.result_format == "markdown":
94
- # Markdown version of the output
95
- logger.info(
96
- f"🔍 Executing query: {query[:60]}{'...' if len(query) > 60 else ''}"
97
- )
98
- result = self.conn.execute(query).fetchdf().to_markdown()
99
- logger.info("✅ Query executed successfully")
100
- return result
101
- elif self.result_format == "duckbox":
102
- # Duckbox version of the output
103
- buffer = io.StringIO()
104
- with redirect_stdout(buffer):
105
- self.conn.sql(query).show(max_rows=100, max_col_width=20)
106
- return buffer.getvalue()
107
- else:
108
- # Text version of the output
109
- return str(self.conn.execute(query).fetchall())
150
+ return self._execute(query)
110
151
 
111
152
  except Exception as e:
112
153
  raise ValueError(f"❌ Error executing query: {e}")
@@ -118,6 +159,7 @@ async def main(
118
159
  result_format: Literal["markdown", "duckbox", "text"] = "markdown",
119
160
  home_dir: str | None = None,
120
161
  saas_mode: bool = False,
162
+ read_only: bool = False,
121
163
  ):
122
164
  logger.info("Starting MotherDuck MCP Server")
123
165
  server = Server("mcp-server-motherduck")
@@ -127,6 +169,7 @@ async def main(
127
169
  motherduck_token=motherduck_token,
128
170
  home_dir=home_dir,
129
171
  saas_mode=saas_mode,
172
+ read_only=read_only,
130
173
  )
131
174
 
132
175
  logger.info("Registering handlers")
@@ -185,7 +185,7 @@ wheels = [
185
185
 
186
186
  [[package]]
187
187
  name = "mcp-server-motherduck"
188
- version = "0.4.2"
188
+ version = "0.4.3"
189
189
  source = { editable = "." }
190
190
  dependencies = [
191
191
  { name = "duckdb" },