mcp-server-motherduck 0.5__py3-none-any.whl → 0.6.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.
@@ -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.0"
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,139 @@
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
+ # Handle local database paths as the last check
105
+ if not os.path.exists(db_path):
106
+ raise FileNotFoundError(
107
+ f"The local database path `{db_path}` does not exist."
108
+ )
109
+ return db_path, "duckdb"
110
+
111
+ def _execute(self, query: str) -> str:
112
+ if self.conn is None:
113
+ # open short lived readonly connection, run query, close connection, return result
114
+ conn = duckdb.connect(
115
+ self.db_path,
116
+ config={"custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"},
117
+ read_only=self._read_only,
118
+ )
119
+ q = conn.execute(query)
120
+ else:
121
+ q = self.conn.execute(query)
122
+
123
+ out = tabulate(
124
+ q.fetchall(),
125
+ headers=[d[0] + "\n" + d[1] for d in q.description],
126
+ tablefmt="pretty",
127
+ )
128
+
129
+ if self.conn is None:
130
+ conn.close()
131
+
132
+ return out
133
+
134
+ def query(self, query: str) -> str:
135
+ try:
136
+ return self._execute(query)
137
+
138
+ except Exception as e:
139
+ raise ValueError(f"❌ Error executing query: {e}")
@@ -1,162 +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
- import mcp.server.stdio
3
+ from typing import Literal
9
4
  import mcp.types as types
10
5
  from mcp.server import NotificationOptions, Server
11
6
  from mcp.server.models import InitializationOptions
7
+ from .configs import SERVER_VERSION
8
+ from .database import DatabaseClient
12
9
  from .prompt import PROMPT_TEMPLATE
13
10
 
14
11
 
15
- SERVER_VERSION = "0.5"
16
-
17
12
  logger = logging.getLogger("mcp_server_motherduck")
18
13
 
19
14
 
20
- class DatabaseClient:
21
- def __init__(
22
- self,
23
- db_path: str | None = None,
24
- motherduck_token: str | None = None,
25
- result_format: Literal["markdown", "duckbox", "text"] = "markdown",
26
- home_dir: str | None = None,
27
- saas_mode: bool = False,
28
- read_only: bool = False,
29
- ):
30
- self._read_only = read_only
31
- self.db_path, self.db_type = self._resolve_db_path_type(
32
- db_path, motherduck_token, saas_mode
33
- )
34
- logger.info(f"Database client initialized in `{self.db_type}` mode")
35
-
36
- # Set the home directory for DuckDB
37
- if home_dir:
38
- os.environ["HOME"] = home_dir
39
-
40
- self.conn = self._initialize_connection()
41
- self.result_format = result_format
42
-
43
- def _initialize_connection(self) -> Optional[duckdb.DuckDBPyConnection]:
44
- """Initialize connection to the MotherDuck or DuckDB database"""
45
-
46
- logger.info(f"🔌 Connecting to {self.db_type} database")
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
- )
69
- conn = duckdb.connect(
70
- self.db_path,
71
- config={"custom_user_agent": f"mcp-server-motherduck/{SERVER_VERSION}"},
72
- )
73
-
74
- logger.info(f"✅ Successfully connected to {self.db_type} database")
75
-
76
- return conn
77
-
78
- def _resolve_db_path_type(
79
- self, db_path: str, motherduck_token: str | None = None, saas_mode: bool = False
80
- ) -> tuple[str, Literal["duckdb", "motherduck"]]:
81
- """Resolve and validate the database path"""
82
- # Handle MotherDuck paths
83
- if db_path.startswith("md:"):
84
- if motherduck_token:
85
- logger.info("Using MotherDuck token to connect to database `md:`")
86
- if saas_mode:
87
- logger.info("Connecting to MotherDuck in SaaS mode")
88
- return (
89
- f"{db_path}?motherduck_token={motherduck_token}&saas_mode=true",
90
- "motherduck",
91
- )
92
- else:
93
- return (
94
- f"{db_path}?motherduck_token={motherduck_token}",
95
- "motherduck",
96
- )
97
- elif os.getenv("motherduck_token"):
98
- logger.info(
99
- "Using MotherDuck token from env to connect to database `md:`"
100
- )
101
- return (
102
- f"{db_path}?motherduck_token={os.getenv('motherduck_token')}",
103
- "motherduck",
104
- )
105
- else:
106
- raise ValueError(
107
- "Please set the `motherduck_token` as an environment variable or pass it as an argument with `--motherduck-token` when using `md:` as db_path."
108
- )
109
-
110
- if db_path == ":memory:":
111
- return db_path, "duckdb"
112
-
113
- # Handle local database paths as the last check
114
- if not os.path.exists(db_path):
115
- raise FileNotFoundError(
116
- f"The local database path `{db_path}` does not exist."
117
- )
118
- return db_path, "duckdb"
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
-
148
- def query(self, query: str) -> str:
149
- try:
150
- return self._execute(query)
151
-
152
- except Exception as e:
153
- raise ValueError(f"❌ Error executing query: {e}")
154
-
155
-
156
- async def main(
15
+ def build_application(
157
16
  db_path: str,
158
17
  motherduck_token: str | None = None,
159
- result_format: Literal["markdown", "duckbox", "text"] = "markdown",
160
18
  home_dir: str | None = None,
161
19
  saas_mode: bool = False,
162
20
  read_only: bool = False,
@@ -165,7 +23,6 @@ async def main(
165
23
  server = Server("mcp-server-motherduck")
166
24
  db_client = DatabaseClient(
167
25
  db_path=db_path,
168
- result_format=result_format,
169
26
  motherduck_token=motherduck_token,
170
27
  home_dir=home_dir,
171
28
  saas_mode=saas_mode,
@@ -280,20 +137,13 @@ async def main(
280
137
  logger.error(f"Error executing tool {name}: {e}")
281
138
  raise ValueError(f"Error executing tool {name}: {str(e)}")
282
139
 
283
- # Run the server using stdin/stdout streams
284
- async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
285
- await server.run(
286
- read_stream,
287
- write_stream,
288
- InitializationOptions(
289
- server_name="motherduck",
290
- server_version=SERVER_VERSION,
291
- capabilities=server.get_capabilities(
292
- notification_options=NotificationOptions(),
293
- experimental_capabilities={},
294
- ),
295
- ),
296
- )
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
+ )
297
148
 
298
- # This will only be reached when the server is shutting down
299
- logger.info("\n🦆 MotherDuck MCP Server shutting down...")
149
+ return server, initialization_options
@@ -1,20 +1,25 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-server-motherduck
3
- Version: 0.5
3
+ Version: 0.6.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
7
7
  Requires-Python: >=3.10
8
- Requires-Dist: duckdb==1.2.2
9
- Requires-Dist: mcp>=1.3.0
10
- Requires-Dist: pandas>=2.0.0
8
+ Requires-Dist: anyio>=4.8.0
9
+ Requires-Dist: click>=8.1.8
10
+ Requires-Dist: duckdb==1.3.0
11
+ Requires-Dist: mcp>=1.9.4
12
+ Requires-Dist: starlette>=0.46.1
11
13
  Requires-Dist: tabulate>=0.9.0
14
+ Requires-Dist: uvicorn>=0.34.0
12
15
  Description-Content-Type: text/markdown
13
16
 
14
17
  # MotherDuck's DuckDB MCP Server
15
18
 
16
19
  An MCP server implementation that interacts with DuckDB and MotherDuck databases, providing SQL analytics capabilities to AI Assistants and IDEs.
17
20
 
21
+ [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=DuckDB&config=eyJjb21tYW5kIjoidXZ4IG1jcC1zZXJ2ZXItbW90aGVyZHVjayAtLWRiLXBhdGggbWQ6IiwiZW52Ijp7Im1vdGhlcmR1Y2tfdG9rZW4iOiIifX0%3D)
22
+
18
23
  ## Resources
19
24
  - [Close the Loop: Faster Data Pipelines with MCP, DuckDB & AI (Blogpost)](https://motherduck.com/blog/faster-data-pipelines-with-mcp-duckdb-ai/)
20
25
  - [Faster Data Pipelines development with MCP and DuckDB (YouTube)](https://www.youtube.com/watch?v=yG1mv8ZRxcU)
@@ -45,6 +50,37 @@ The server offers one tool:
45
50
 
46
51
  All interactions with both DuckDB and MotherDuck are done through writing SQL queries.
47
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
+
48
84
  ## Getting Started
49
85
 
50
86
  ### General Prerequisites
@@ -94,11 +130,11 @@ See [Connect to local DuckDB](#connect-to-local-duckdb).
94
130
 
95
131
  ### Usage with VS Code
96
132
 
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)
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)
98
134
 
99
- 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.
100
136
 
101
- ### Manual Installation
137
+ #### Manual Installation
102
138
 
103
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)`.
104
140
 
@@ -185,7 +221,6 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
185
221
  **Important Notes**:
186
222
 
187
223
  - Replace `YOUR_MOTHERDUCK_TOKEN_HERE` with your actual MotherDuck token
188
- - Replace `YOUR_HOME_FOLDER_PATH` with the path to your home directory (needed by DuckDB for file operations). For example, on macOS, it would be `/Users/your_username`
189
224
  - The `HOME` environment variable is required for DuckDB to function properly.
190
225
 
191
226
  ## Securing your MCP Server when querying MotherDuck
@@ -290,47 +325,29 @@ Once configured, you can e.g. ask Claude to run queries like:
290
325
  - "Join data from my local DuckDB database with a table in MotherDuck"
291
326
  - "Analyze data stored in Amazon S3"
292
327
 
293
- ## Testing
294
-
295
- 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:
296
-
297
- 1. **Default MotherDuck database**:
298
-
299
- - To connect to the default MotherDuck database, you will need to pass the auth token using the `--motherduck-token` parameter.
300
-
301
- ```bash
302
- uvx mcp-server-motherduck --db-path md: --motherduck-token <your_motherduck_token>
303
- ```
304
-
305
- 2. **Specific MotherDuck database**:
306
-
307
- ```bash
308
- uvx mcp-server-motherduck --db-path md:your_database_name --motherduck-token <your_motherduck_token>
309
- ```
328
+ ## Running in SSE mode
310
329
 
311
- 3. **Local DuckDB database**:
330
+ The server can run in SSE mode in two ways:
312
331
 
313
- ```bash
314
- uvx mcp-server-motherduck --db-path /path/to/your/local.db
315
- ```
332
+ ### Direct SSE mode
316
333
 
317
- 4. **In-memory database**:
334
+ Run the server directly in SSE mode using the `--transport sse` flag:
318
335
 
319
- ```bash
320
- uvx mcp-server-motherduck --db-path :memory:
321
- ```
336
+ ```bash
337
+ uvx mcp-server-motherduck --transport sse --port 8000 --db-path md: --motherduck-token <your_motherduck_token>
338
+ ```
322
339
 
323
- 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.
324
341
 
325
- ## Running in SSE mode
342
+ ### Using supergateway
326
343
 
327
- The server could also be running SSE mode using `supergateway` by running the following command:
344
+ Alternatively, you can run SSE mode using `supergateway`:
328
345
 
329
346
  ```bash
330
347
  npx -y supergateway --stdio "uvx mcp-server-motherduck --db-path md: --motherduck-token <your_motherduck_token>"
331
348
  ```
332
349
 
333
- 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.
334
351
 
335
352
  ## Development configuration
336
353
 
@@ -0,0 +1,10 @@
1
+ mcp_server_motherduck/__init__.py,sha256=vogjO-bwHYZFut85eeiVmRepamcWHk8bImri_kBKZMU,6026
2
+ mcp_server_motherduck/configs.py,sha256=0owM37f8Vw0Ieqc4eKP7HNrp_W40PcivmlRaaMR4uB0,771
3
+ mcp_server_motherduck/database.py,sha256=RQeVFSL5Uo5hyuysSeJPkz_fCQHijHJYTnBBdPvemvU,4902
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.0.dist-info/METADATA,sha256=eJkdX5OBln_0ZGYagtyUiqj5AEIHs1Y4tMdN3uDofAc,13718
7
+ mcp_server_motherduck-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
8
+ mcp_server_motherduck-0.6.0.dist-info/entry_points.txt,sha256=dRTgcvWJn40bz0PVuKPylK6w92cFN32lwunZOgo5j4s,69
9
+ mcp_server_motherduck-0.6.0.dist-info/licenses/LICENSE,sha256=Tj68w9jCiceFKTvZ3jET-008NjhozcQMXpm-fyL9WUI,1067
10
+ mcp_server_motherduck-0.6.0.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=hPZkx1WafvNx8LCLRdq0ftQV_C4qz7x18kqY5ACgZDk,10760
4
- mcp_server_motherduck-0.5.dist-info/METADATA,sha256=Rg2d2t94aFpd1ArzkAoWY5su_J7n8SoXz9YBY4hGLws,12523
5
- mcp_server_motherduck-0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
- mcp_server_motherduck-0.5.dist-info/entry_points.txt,sha256=dRTgcvWJn40bz0PVuKPylK6w92cFN32lwunZOgo5j4s,69
7
- mcp_server_motherduck-0.5.dist-info/licenses/LICENSE,sha256=Tj68w9jCiceFKTvZ3jET-008NjhozcQMXpm-fyL9WUI,1067
8
- mcp_server_motherduck-0.5.dist-info/RECORD,,