mcp-server-motherduck 0.5.1__py3-none-any.whl → 0.6.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 mcp-server-motherduck might be problematic. Click here for more details.

@@ -1,66 +1,193 @@
1
- from . import server
2
- import asyncio
3
- import argparse
1
+ import anyio
4
2
  import logging
3
+ import click
4
+ from .server import build_application
5
+ from .configs import SERVER_VERSION, SERVER_LOCALHOST, UVICORN_LOGGING_CONFIG
6
+
7
+ __version__ = SERVER_VERSION
5
8
 
6
9
  logger = logging.getLogger("mcp_server_motherduck")
7
- logging.basicConfig(level=logging.INFO, format="[%(name)s] %(levelname)s - %(message)s")
10
+ logging.basicConfig(
11
+ level=logging.INFO, format="[motherduck] %(levelname)s - %(message)s"
12
+ )
8
13
 
9
14
 
10
- def main():
15
+ @click.command()
16
+ @click.option("--port", default=8000, help="Port to listen on for SSE")
17
+ @click.option(
18
+ "--transport",
19
+ type=click.Choice(["stdio", "sse", "stream"]),
20
+ default="stdio",
21
+ help="(Default: `stdio`) Transport type",
22
+ )
23
+ @click.option(
24
+ "--db-path",
25
+ default="md:",
26
+ help="(Default: `md:`) Path to local DuckDB database file or MotherDuck database",
27
+ )
28
+ @click.option(
29
+ "--motherduck-token",
30
+ default=None,
31
+ help="(Default: env var `motherduck_token`) Access token to use for MotherDuck database connections",
32
+ )
33
+ @click.option(
34
+ "--home-dir",
35
+ default=None,
36
+ help="(Default: env var `HOME`) Home directory for DuckDB",
37
+ )
38
+ @click.option(
39
+ "--saas-mode",
40
+ is_flag=True,
41
+ help="Flag for connecting to MotherDuck in SaaS mode",
42
+ )
43
+ @click.option(
44
+ "--read-only",
45
+ is_flag=True,
46
+ 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).",
47
+ )
48
+ @click.option(
49
+ "--json-response",
50
+ is_flag=True,
51
+ default=False,
52
+ help="(Default: `False`) Enable JSON responses instead of SSE streams. Only supported for `stream` transport.",
53
+ )
54
+ def main(
55
+ port,
56
+ transport,
57
+ db_path,
58
+ motherduck_token,
59
+ home_dir,
60
+ saas_mode,
61
+ read_only,
62
+ json_response,
63
+ ):
11
64
  """Main entry point for the package."""
12
65
 
13
- parser = argparse.ArgumentParser(description="MotherDuck MCP Server")
14
- parser.add_argument(
15
- "--db-path",
16
- default="md:",
17
- help="(Default: `md:`) Path to local DuckDB database file or MotherDuck database",
18
- )
19
- parser.add_argument(
20
- "--motherduck-token",
21
- default=None,
22
- help="(Default: env var `motherduck_token`) Access token to use for MotherDuck database connections",
23
- )
24
- parser.add_argument(
25
- "--home-dir",
26
- default=None,
27
- help="(Default: env var `HOME`) Home directory for DuckDB",
28
- )
29
- parser.add_argument(
30
- "--saas-mode",
31
- action="store_true",
32
- help="Flag for connecting to MotherDuck in SaaS mode",
33
- )
34
- # This is experimental and will change in the future
35
- parser.add_argument(
36
- "--result-format",
37
- help="(Default: `markdown`) Format of the query result",
38
- default="markdown",
39
- choices=["markdown", "duckbox", "text"],
40
- )
66
+ logger.info("🦆 MotherDuck MCP Server v" + SERVER_VERSION)
67
+ logger.info("Ready to execute SQL queries via DuckDB/MotherDuck")
41
68
 
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).",
69
+ app, init_opts = build_application(
70
+ db_path=db_path,
71
+ motherduck_token=motherduck_token,
72
+ home_dir=home_dir,
73
+ saas_mode=saas_mode,
74
+ read_only=read_only,
46
75
  )
47
76
 
48
- args = parser.parse_args()
49
- logger.info("🦆 MotherDuck MCP Server v" + server.SERVER_VERSION)
50
- logger.info("Ready to execute SQL queries via DuckDB/MotherDuck")
51
- logger.info("Waiting for client connection...\n")
52
-
53
- asyncio.run(
54
- server.main(
55
- db_path=args.db_path,
56
- motherduck_token=args.motherduck_token,
57
- result_format=args.result_format,
58
- home_dir=args.home_dir,
59
- saas_mode=args.saas_mode,
60
- read_only=args.read_only,
77
+ if transport == "sse":
78
+ from mcp.server.sse import SseServerTransport
79
+ from starlette.applications import Starlette
80
+ from starlette.responses import Response
81
+ from starlette.routing import Mount, Route
82
+
83
+ logger.info("MCP server initialized in \033[32msse\033[0m mode")
84
+
85
+ sse = SseServerTransport("/messages/")
86
+
87
+ async def handle_sse(request):
88
+ async with sse.connect_sse(
89
+ request.scope, request.receive, request._send
90
+ ) as (read_stream, write_stream):
91
+ await app.run(read_stream, write_stream, init_opts)
92
+ return Response()
93
+
94
+ logger.info(
95
+ f"🦆 Connect to MotherDuck MCP Server at \033[1m\033[36mhttp://{SERVER_LOCALHOST}:{port}/sse\033[0m"
96
+ )
97
+
98
+ starlette_app = Starlette(
99
+ debug=True,
100
+ routes=[
101
+ Route("/sse", endpoint=handle_sse, methods=["GET"]),
102
+ Mount("/messages/", app=sse.handle_post_message),
103
+ ],
104
+ )
105
+
106
+ import uvicorn
107
+
108
+ uvicorn.run(
109
+ starlette_app,
110
+ host=SERVER_LOCALHOST,
111
+ port=port,
112
+ log_config=UVICORN_LOGGING_CONFIG,
113
+ )
114
+
115
+ elif transport == "stream":
116
+ from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
117
+ from collections.abc import AsyncIterator
118
+ from starlette.applications import Starlette
119
+ from starlette.routing import Mount
120
+ from starlette.types import Receive, Scope, Send
121
+ import contextlib
122
+
123
+ logger.info("MCP server initialized in \033[32mhttp-streamable\033[0m mode")
124
+
125
+ # Create the session manager with true stateless mode
126
+ session_manager = StreamableHTTPSessionManager(
127
+ app=app,
128
+ event_store=None,
129
+ json_response=json_response,
130
+ stateless=True,
131
+ )
132
+
133
+ async def handle_streamable_http(
134
+ scope: Scope, receive: Receive, send: Send
135
+ ) -> None:
136
+ await session_manager.handle_request(scope, receive, send)
137
+
138
+ @contextlib.asynccontextmanager
139
+ async def lifespan(app: Starlette) -> AsyncIterator[None]:
140
+ """Context manager for session manager."""
141
+ async with session_manager.run():
142
+ logger.info("MCP server started with StreamableHTTP session manager")
143
+ try:
144
+ yield
145
+ finally:
146
+ logger.info(
147
+ "🦆 MotherDuck MCP Server in \033[32mhttp-streamable\033[0m mode shutting down"
148
+ )
149
+
150
+ logger.info(
151
+ f"🦆 Connect to MotherDuck MCP Server at \033[1m\033[36mhttp://{SERVER_LOCALHOST}:{port}/mcp\033[0m"
152
+ )
153
+
154
+ # Create an ASGI application using the transport
155
+ starlette_app = Starlette(
156
+ debug=True,
157
+ routes=[
158
+ Mount("/mcp", app=handle_streamable_http),
159
+ ],
160
+ lifespan=lifespan,
161
+ )
162
+
163
+ import uvicorn
164
+
165
+ uvicorn.run(
166
+ starlette_app,
167
+ host=SERVER_LOCALHOST,
168
+ port=port,
169
+ log_config=UVICORN_LOGGING_CONFIG,
170
+ )
171
+
172
+ else:
173
+ from mcp.server.stdio import stdio_server
174
+
175
+ logger.info("MCP server initialized in \033[32mstdio\033[0m mode")
176
+ logger.info("Waiting for client connection")
177
+
178
+ async def arun():
179
+ async with stdio_server() as (read_stream, write_stream):
180
+ await app.run(read_stream, write_stream, init_opts)
181
+
182
+ anyio.run(arun)
183
+ # This will only be reached when the server is shutting down
184
+ logger.info(
185
+ "🦆 MotherDuck MCP Server in \033[32mstdio\033[0m mode shutting down"
61
186
  )
62
- )
63
187
 
64
188
 
65
189
  # Optionally expose other important items at package level
66
- __all__ = ["main", "server"]
190
+ __all__ = ["main"]
191
+
192
+ if __name__ == "__main__":
193
+ main()
@@ -0,0 +1,32 @@
1
+ from typing import Any
2
+
3
+ SERVER_VERSION = "0.6.1"
4
+
5
+ SERVER_LOCALHOST = "127.0.0.1"
6
+
7
+ UVICORN_LOGGING_CONFIG: dict[str, Any] = {
8
+ "version": 1,
9
+ "disable_existing_loggers": False,
10
+ "formatters": {
11
+ "default": {
12
+ "()": "uvicorn.logging.DefaultFormatter",
13
+ "fmt": "[uvicorn] %(levelname)s - %(message)s",
14
+ "use_colors": None,
15
+ },
16
+ },
17
+ "handlers": {
18
+ "default": {
19
+ "formatter": "default",
20
+ "class": "logging.StreamHandler",
21
+ "stream": "ext://sys.stderr",
22
+ },
23
+ },
24
+ "loggers": {
25
+ "uvicorn": {
26
+ "handlers": ["default"],
27
+ "level": "INFO",
28
+ "propagate": False,
29
+ },
30
+ "uvicorn.error": {"level": "INFO"},
31
+ },
32
+ }
@@ -0,0 +1,134 @@
1
+ import os
2
+ import duckdb
3
+ from typing import Literal, Optional
4
+ import io
5
+ from contextlib import redirect_stdout
6
+ from tabulate import tabulate
7
+ import logging
8
+ from .configs import SERVER_VERSION
9
+
10
+ logger = logging.getLogger("mcp_server_motherduck")
11
+
12
+
13
+ class DatabaseClient:
14
+ def __init__(
15
+ self,
16
+ db_path: str | None = None,
17
+ motherduck_token: str | None = None,
18
+ home_dir: str | None = None,
19
+ saas_mode: bool = False,
20
+ read_only: bool = False,
21
+ ):
22
+ self._read_only = read_only
23
+ self.db_path, self.db_type = self._resolve_db_path_type(
24
+ db_path, motherduck_token, saas_mode
25
+ )
26
+ logger.info(f"Database client initialized in `{self.db_type}` mode")
27
+
28
+ # Set the home directory for DuckDB
29
+ if home_dir:
30
+ os.environ["HOME"] = home_dir
31
+
32
+ self.conn = self._initialize_connection()
33
+
34
+ def _initialize_connection(self) -> Optional[duckdb.DuckDBPyConnection]:
35
+ """Initialize connection to the MotherDuck or DuckDB database"""
36
+
37
+ logger.info(f"🔌 Connecting to {self.db_type} database")
38
+
39
+ if self.db_type == "duckdb" and self._read_only:
40
+ # check that we can connect, issue a `select 1` and then close + return None
41
+ try:
42
+ conn = duckdb.connect(
43
+ self.db_path,
44
+ config={
45
+ "custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"
46
+ },
47
+ read_only=self._read_only,
48
+ )
49
+ conn.execute("SELECT 1")
50
+ conn.close()
51
+ return None
52
+ except Exception as e:
53
+ logger.error(f"❌ Read-only check failed: {e}")
54
+ raise
55
+
56
+ if self._read_only:
57
+ raise ValueError(
58
+ "Read-only mode is only supported for local DuckDB databases. See `saas_mode` for similar functionality with MotherDuck."
59
+ )
60
+ conn = duckdb.connect(
61
+ self.db_path,
62
+ config={"custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"},
63
+ )
64
+
65
+ logger.info(f"✅ Successfully connected to {self.db_type} database")
66
+
67
+ return conn
68
+
69
+ def _resolve_db_path_type(
70
+ self, db_path: str, motherduck_token: str | None = None, saas_mode: bool = False
71
+ ) -> tuple[str, Literal["duckdb", "motherduck"]]:
72
+ """Resolve and validate the database path"""
73
+ # Handle MotherDuck paths
74
+ if db_path.startswith("md:"):
75
+ if motherduck_token:
76
+ logger.info("Using MotherDuck token to connect to database `md:`")
77
+ if saas_mode:
78
+ logger.info("Connecting to MotherDuck in SaaS mode")
79
+ return (
80
+ f"{db_path}?motherduck_token={motherduck_token}&saas_mode=true",
81
+ "motherduck",
82
+ )
83
+ else:
84
+ return (
85
+ f"{db_path}?motherduck_token={motherduck_token}",
86
+ "motherduck",
87
+ )
88
+ elif os.getenv("motherduck_token"):
89
+ logger.info(
90
+ "Using MotherDuck token from env to connect to database `md:`"
91
+ )
92
+ return (
93
+ f"{db_path}?motherduck_token={os.getenv('motherduck_token')}",
94
+ "motherduck",
95
+ )
96
+ else:
97
+ raise ValueError(
98
+ "Please set the `motherduck_token` as an environment variable or pass it as an argument with `--motherduck-token` when using `md:` as db_path."
99
+ )
100
+
101
+ if db_path == ":memory:":
102
+ return db_path, "duckdb"
103
+
104
+ return db_path, "duckdb"
105
+
106
+ def _execute(self, query: str) -> str:
107
+ if self.conn is None:
108
+ # open short lived readonly connection, run query, close connection, return result
109
+ conn = duckdb.connect(
110
+ self.db_path,
111
+ config={"custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"},
112
+ read_only=self._read_only,
113
+ )
114
+ q = conn.execute(query)
115
+ else:
116
+ q = self.conn.execute(query)
117
+
118
+ out = tabulate(
119
+ q.fetchall(),
120
+ headers=[d[0] + "\n" + d[1] for d in q.description],
121
+ tablefmt="pretty",
122
+ )
123
+
124
+ if self.conn is None:
125
+ conn.close()
126
+
127
+ return out
128
+
129
+ def query(self, query: str) -> str:
130
+ try:
131
+ return self._execute(query)
132
+
133
+ except Exception as e:
134
+ raise ValueError(f"❌ Error executing query: {e}")
@@ -1,163 +1,20 @@
1
- import os
2
1
  import logging
3
- import duckdb
4
2
  from pydantic import AnyUrl
5
- from typing import Literal, Optional
6
- import io
7
- from contextlib import redirect_stdout
8
- from tabulate import tabulate
9
- import mcp.server.stdio
3
+ from typing import Literal
10
4
  import mcp.types as types
11
5
  from mcp.server import NotificationOptions, Server
12
6
  from mcp.server.models import InitializationOptions
7
+ from .configs import SERVER_VERSION
8
+ from .database import DatabaseClient
13
9
  from .prompt import PROMPT_TEMPLATE
14
10
 
15
11
 
16
- SERVER_VERSION = "0.5.1"
17
-
18
12
  logger = logging.getLogger("mcp_server_motherduck")
19
13
 
20
14
 
21
- class DatabaseClient:
22
- def __init__(
23
- self,
24
- db_path: str | None = None,
25
- motherduck_token: str | None = None,
26
- result_format: Literal["markdown", "duckbox", "text"] = "markdown",
27
- home_dir: str | None = None,
28
- saas_mode: bool = False,
29
- read_only: bool = False,
30
- ):
31
- self._read_only = read_only
32
- self.db_path, self.db_type = self._resolve_db_path_type(
33
- db_path, motherduck_token, saas_mode
34
- )
35
- logger.info(f"Database client initialized in `{self.db_type}` mode")
36
-
37
- # Set the home directory for DuckDB
38
- if home_dir:
39
- os.environ["HOME"] = home_dir
40
-
41
- self.conn = self._initialize_connection()
42
- self.result_format = result_format
43
-
44
- def _initialize_connection(self) -> Optional[duckdb.DuckDBPyConnection]:
45
- """Initialize connection to the MotherDuck or DuckDB database"""
46
-
47
- logger.info(f"🔌 Connecting to {self.db_type} database")
48
-
49
- if self.db_type == "duckdb" and self._read_only:
50
- # check that we can connect, issue a `select 1` and then close + return None
51
- try:
52
- conn = duckdb.connect(
53
- self.db_path,
54
- config={
55
- "custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"
56
- },
57
- read_only=self._read_only,
58
- )
59
- conn.execute("SELECT 1")
60
- conn.close()
61
- return None
62
- except Exception as e:
63
- logger.error(f"❌ Read-only check failed: {e}")
64
- raise
65
-
66
- if self._read_only:
67
- raise ValueError(
68
- "Read-only mode is only supported for local DuckDB databases. See `saas_mode` for similar functionality with MotherDuck."
69
- )
70
- conn = duckdb.connect(
71
- self.db_path,
72
- config={"custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"},
73
- )
74
-
75
- logger.info(f"✅ Successfully connected to {self.db_type} database")
76
-
77
- return conn
78
-
79
- def _resolve_db_path_type(
80
- self, db_path: str, motherduck_token: str | None = None, saas_mode: bool = False
81
- ) -> tuple[str, Literal["duckdb", "motherduck"]]:
82
- """Resolve and validate the database path"""
83
- # Handle MotherDuck paths
84
- if db_path.startswith("md:"):
85
- if motherduck_token:
86
- logger.info("Using MotherDuck token to connect to database `md:`")
87
- if saas_mode:
88
- logger.info("Connecting to MotherDuck in SaaS mode")
89
- return (
90
- f"{db_path}?motherduck_token={motherduck_token}&saas_mode=true",
91
- "motherduck",
92
- )
93
- else:
94
- return (
95
- f"{db_path}?motherduck_token={motherduck_token}",
96
- "motherduck",
97
- )
98
- elif os.getenv("motherduck_token"):
99
- logger.info(
100
- "Using MotherDuck token from env to connect to database `md:`"
101
- )
102
- return (
103
- f"{db_path}?motherduck_token={os.getenv('motherduck_token')}",
104
- "motherduck",
105
- )
106
- else:
107
- raise ValueError(
108
- "Please set the `motherduck_token` as an environment variable or pass it as an argument with `--motherduck-token` when using `md:` as db_path."
109
- )
110
-
111
- if db_path == ":memory:":
112
- return db_path, "duckdb"
113
-
114
- # Handle local database paths as the last check
115
- if not os.path.exists(db_path):
116
- raise FileNotFoundError(
117
- f"The local database path `{db_path}` does not exist."
118
- )
119
- return db_path, "duckdb"
120
-
121
- def _execute(self, query: str) -> str:
122
- if self.conn is None:
123
- # open short lived readonly connection, run query, close connection, return result
124
- conn = duckdb.connect(
125
- self.db_path,
126
- config={"custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"},
127
- read_only=self._read_only,
128
- )
129
- q = conn.execute(query)
130
- else:
131
- q = self.conn.execute(query)
132
-
133
- if self.result_format == "markdown":
134
- out = tabulate(q.fetchall(), headers=[d[0]+'\n'+d[1] for d in q.description], tablefmt="pretty")
135
- elif self.result_format == "duckbox":
136
- # Duckbox version of the output
137
- buffer = io.StringIO()
138
- with redirect_stdout(buffer):
139
- q.show(max_rows=100, max_col_width=20)
140
- out = buffer.getvalue()
141
- else:
142
- out = str(q.fetchall())
143
-
144
- if self.conn is None:
145
- conn.close()
146
-
147
- return out
148
-
149
- def query(self, query: str) -> str:
150
- try:
151
- return self._execute(query)
152
-
153
- except Exception as e:
154
- raise ValueError(f"❌ Error executing query: {e}")
155
-
156
-
157
- async def main(
15
+ def build_application(
158
16
  db_path: str,
159
17
  motherduck_token: str | None = None,
160
- result_format: Literal["markdown", "duckbox", "text"] = "markdown",
161
18
  home_dir: str | None = None,
162
19
  saas_mode: bool = False,
163
20
  read_only: bool = False,
@@ -166,7 +23,6 @@ async def main(
166
23
  server = Server("mcp-server-motherduck")
167
24
  db_client = DatabaseClient(
168
25
  db_path=db_path,
169
- result_format=result_format,
170
26
  motherduck_token=motherduck_token,
171
27
  home_dir=home_dir,
172
28
  saas_mode=saas_mode,
@@ -281,20 +137,13 @@ async def main(
281
137
  logger.error(f"Error executing tool {name}: {e}")
282
138
  raise ValueError(f"Error executing tool {name}: {str(e)}")
283
139
 
284
- # Run the server using stdin/stdout streams
285
- async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
286
- await server.run(
287
- read_stream,
288
- write_stream,
289
- InitializationOptions(
290
- server_name="motherduck",
291
- server_version=SERVER_VERSION,
292
- capabilities=server.get_capabilities(
293
- notification_options=NotificationOptions(),
294
- experimental_capabilities={},
295
- ),
296
- ),
297
- )
140
+ initialization_options = InitializationOptions(
141
+ server_name="motherduck",
142
+ server_version=SERVER_VERSION,
143
+ capabilities=server.get_capabilities(
144
+ notification_options=NotificationOptions(),
145
+ experimental_capabilities={},
146
+ ),
147
+ )
298
148
 
299
- # This will only be reached when the server is shutting down
300
- logger.info("\n🦆 MotherDuck MCP Server shutting down...")
149
+ return server, initialization_options
@@ -1,19 +1,25 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-server-motherduck
3
- Version: 0.5.1
3
+ Version: 0.6.1
4
4
  Summary: A MCP server for MotherDuck and local DuckDB
5
5
  Author-email: tdoehmen <till@motherduck.com>
6
6
  License-File: LICENSE
7
7
  Requires-Python: >=3.10
8
- Requires-Dist: duckdb==1.3.0
9
- Requires-Dist: mcp>=1.3.0
8
+ Requires-Dist: anyio>=4.8.0
9
+ Requires-Dist: click>=8.1.8
10
+ Requires-Dist: duckdb==1.3.1
11
+ Requires-Dist: mcp>=1.9.4
12
+ Requires-Dist: starlette>=0.46.1
10
13
  Requires-Dist: tabulate>=0.9.0
14
+ Requires-Dist: uvicorn>=0.34.0
11
15
  Description-Content-Type: text/markdown
12
16
 
13
17
  # MotherDuck's DuckDB MCP Server
14
18
 
15
19
  An MCP server implementation that interacts with DuckDB and MotherDuck databases, providing SQL analytics capabilities to AI Assistants and IDEs.
16
20
 
21
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=DuckDB&config=eyJjb21tYW5kIjoidXZ4IG1jcC1zZXJ2ZXItbW90aGVyZHVjayAtLWRiLXBhdGggbWQ6IiwiZW52Ijp7Im1vdGhlcmR1Y2tfdG9rZW4iOiIifX0%3D)
22
+
17
23
  ## Resources
18
24
  - [Close the Loop: Faster Data Pipelines with MCP, DuckDB & AI (Blogpost)](https://motherduck.com/blog/faster-data-pipelines-with-mcp-duckdb-ai/)
19
25
  - [Faster Data Pipelines development with MCP and DuckDB (YouTube)](https://www.youtube.com/watch?v=yG1mv8ZRxcU)
@@ -44,6 +50,37 @@ The server offers one tool:
44
50
 
45
51
  All interactions with both DuckDB and MotherDuck are done through writing SQL queries.
46
52
 
53
+ ## Command Line Parameters
54
+
55
+ The MCP server supports the following parameters:
56
+
57
+ | Parameter | Type | Default | Description |
58
+ |-----------|------|---------|-------------|
59
+ | `--transport` | Choice | `stdio` | Transport type. Options: `stdio`, `sse`, `stream` |
60
+ | `--port` | Integer | `8000` | Port to listen on for sse and stream transport mode |
61
+ | `--db-path` | String | `md:` | Path to local DuckDB database file or MotherDuck database |
62
+ | `--motherduck-token` | String | `None` | Access token to use for MotherDuck database connections (uses `motherduck_token` env var by default) |
63
+ | `--read-only` | Flag | `False` | Flag for connecting to DuckDB in read-only mode. Only supported for local DuckDB databases. Uses short-lived connections for concurrent access |
64
+ | `--home-dir` | String | `None` | Home directory for DuckDB (uses `HOME` env var by default) |
65
+ | `--saas-mode` | Flag | `False` | Flag for connecting to MotherDuck in SaaS mode |
66
+ | `--json-response` | Flag | `False` | Enable JSON responses for HTTP stream. Only supported for `stream` transport |
67
+
68
+ ### Quick Usage Examples
69
+
70
+ ```bash
71
+ # Connect to local DuckDB file in read-only mode with stream transport mode
72
+ uvx mcp-server-motherduck --transport stream --db-path /path/to/local.db --read-only
73
+
74
+ # Connect to MotherDuck with token with stream transport mode
75
+ uvx mcp-server-motherduck --transport stream --db-path md: --motherduck-token YOUR_TOKEN
76
+
77
+ # Connect to local DuckDB file in read-only mode with stream transport mode
78
+ uvx mcp-server-motherduck --transport stream --db-path /path/to/local.db --read-only
79
+
80
+ # Connect to MotherDuck in SaaS mode for enhanced security with stream transport mode
81
+ uvx mcp-server-motherduck --transport stream --db-path md: --motherduck-token YOUR_TOKEN --saas-mode
82
+ ```
83
+
47
84
  ## Getting Started
48
85
 
49
86
  ### General Prerequisites
@@ -93,11 +130,11 @@ See [Connect to local DuckDB](#connect-to-local-duckdb).
93
130
 
94
131
  ### Usage with VS Code
95
132
 
96
- [![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)
133
+ [![Install with UV in VS Code](https://img.shields.io/badge/VS_Code-Install_with_UV-0098FF?style=plastic)](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-Install_with_UV-24bfa5?style=plastic&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)
97
134
 
98
- 1. For the quickest installation, click one of the "Install with UV" buttons at the top of this README.
135
+ For the quickest installation, click one of the "Install with UV" buttons at the top.
99
136
 
100
- ### Manual Installation
137
+ #### Manual Installation
101
138
 
102
139
  Add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`.
103
140
 
@@ -288,47 +325,29 @@ Once configured, you can e.g. ask Claude to run queries like:
288
325
  - "Join data from my local DuckDB database with a table in MotherDuck"
289
326
  - "Analyze data stored in Amazon S3"
290
327
 
291
- ## Testing
292
-
293
- The server is designed to be run by tools like Claude Desktop and Cursor, but you can start it manually for testing purposes. When testing the server manually, you can specify which database to connect to using the `--db-path` parameter:
294
-
295
- 1. **Default MotherDuck database**:
296
-
297
- - To connect to the default MotherDuck database, you will need to pass the auth token using the `--motherduck-token` parameter.
298
-
299
- ```bash
300
- uvx mcp-server-motherduck --db-path md: --motherduck-token <your_motherduck_token>
301
- ```
302
-
303
- 2. **Specific MotherDuck database**:
304
-
305
- ```bash
306
- uvx mcp-server-motherduck --db-path md:your_database_name --motherduck-token <your_motherduck_token>
307
- ```
328
+ ## Running in SSE mode
308
329
 
309
- 3. **Local DuckDB database**:
330
+ The server can run in SSE mode in two ways:
310
331
 
311
- ```bash
312
- uvx mcp-server-motherduck --db-path /path/to/your/local.db
313
- ```
332
+ ### Direct SSE mode
314
333
 
315
- 4. **In-memory database**:
334
+ Run the server directly in SSE mode using the `--transport sse` flag:
316
335
 
317
- ```bash
318
- uvx mcp-server-motherduck --db-path :memory:
319
- ```
336
+ ```bash
337
+ uvx mcp-server-motherduck --transport sse --port 8000 --db-path md: --motherduck-token <your_motherduck_token>
338
+ ```
320
339
 
321
- If you don't specify a database path but have set the `motherduck_token` environment variable, the server will automatically connect to the default MotherDuck database (`md:`).
340
+ This will start the server listening on the specified port (default 8000) and you can point your clients directly to this endpoint.
322
341
 
323
- ## Running in SSE mode
342
+ ### Using supergateway
324
343
 
325
- The server could also be running SSE mode using `supergateway` by running the following command:
344
+ Alternatively, you can run SSE mode using `supergateway`:
326
345
 
327
346
  ```bash
328
347
  npx -y supergateway --stdio "uvx mcp-server-motherduck --db-path md: --motherduck-token <your_motherduck_token>"
329
348
  ```
330
349
 
331
- And you can point your clients such as Claude Desktop, Cursor to this endpoint.
350
+ Both methods allow you to point your clients such as Claude Desktop, Cursor to the SSE endpoint.
332
351
 
333
352
  ## Development configuration
334
353
 
@@ -0,0 +1,10 @@
1
+ mcp_server_motherduck/__init__.py,sha256=vogjO-bwHYZFut85eeiVmRepamcWHk8bImri_kBKZMU,6026
2
+ mcp_server_motherduck/configs.py,sha256=751VWhE8qm71zb10gPEMYmuN5C2HCH_MKaOAR6y4x3g,771
3
+ mcp_server_motherduck/database.py,sha256=yx07lz_vpCzuYflkQ1FXJMA9gRjDkgg0hFbdmZx2s_s,4684
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.1.dist-info/METADATA,sha256=fljNXrkiegDXZKW1H7OK9rnIzwQ3h1jRS4vjim-O8ZI,13718
7
+ mcp_server_motherduck-0.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ mcp_server_motherduck-0.6.1.dist-info/entry_points.txt,sha256=dRTgcvWJn40bz0PVuKPylK6w92cFN32lwunZOgo5j4s,69
9
+ mcp_server_motherduck-0.6.1.dist-info/licenses/LICENSE,sha256=Tj68w9jCiceFKTvZ3jET-008NjhozcQMXpm-fyL9WUI,1067
10
+ mcp_server_motherduck-0.6.1.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- mcp_server_motherduck/__init__.py,sha256=Yd_ron4Fy7mV_p48z0IFNKNbqC7fYfjNnPv4pJ6dRCg,2208
2
- mcp_server_motherduck/prompt.py,sha256=P7BrmhVXwDkPeSHQ3f25WMP6lpBpN2BxDzYPOQ3fxX8,56699
3
- mcp_server_motherduck/server.py,sha256=jkMN5gxs7NW5Nk5Drt6OxJ3wY7TRTkUNuAd-hNf0oXI,10857
4
- mcp_server_motherduck-0.5.1.dist-info/METADATA,sha256=a3lpQCcqKON01lFpH4d7zvsM-cPASlXE6rIzwVL7vH8,12327
5
- mcp_server_motherduck-0.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
- mcp_server_motherduck-0.5.1.dist-info/entry_points.txt,sha256=dRTgcvWJn40bz0PVuKPylK6w92cFN32lwunZOgo5j4s,69
7
- mcp_server_motherduck-0.5.1.dist-info/licenses/LICENSE,sha256=Tj68w9jCiceFKTvZ3jET-008NjhozcQMXpm-fyL9WUI,1067
8
- mcp_server_motherduck-0.5.1.dist-info/RECORD,,