sqlnow-mcp 0.2.1__tar.gz → 0.3.0__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.
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/PKG-INFO +3 -1
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/pyproject.toml +4 -1
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/cli.py +31 -4
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/config.py +7 -0
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/db.py +492 -75
- sqlnow_mcp-0.3.0/sqlnow_mcp/map_result.py +432 -0
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/metadata.py +71 -20
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/server.py +438 -6
- sqlnow_mcp-0.3.0/sqlnow_mcp/ui/browse.html +153 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/ui/map-mapbox.html +3445 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/ui/map.html +737 -0
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/ui/table.html +70 -70
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/ui.py +12 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/__init__.py +31 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/api.py +223 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/classify.py +266 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/cli.py +88 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/extract.py +164 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/loader.py +124 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/naming.py +81 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/reader.py +110 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/render.py +101 -0
- sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/render_office.py +102 -0
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/LICENSE +0 -0
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/README.md +0 -0
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/__init__.py +0 -0
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/resource_limits.py +0 -0
- {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/table_result.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlnow-mcp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: MCP server for DuckDB with interactive table viewer
|
|
5
5
|
Keywords: mcp,duckdb,sql,llm,claude,cursor
|
|
6
6
|
Author: David Raznick
|
|
@@ -19,6 +19,8 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
19
19
|
Requires-Dist: click
|
|
20
20
|
Requires-Dist: duckdb
|
|
21
21
|
Requires-Dist: fastmcp
|
|
22
|
+
Requires-Dist: flatterer>=0.24.1
|
|
23
|
+
Requires-Dist: openpyxl
|
|
22
24
|
Requires-Dist: pyarrow
|
|
23
25
|
Requires-Dist: pytz
|
|
24
26
|
Requires-Dist: pyyaml
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlnow-mcp"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "MCP server for DuckDB with interactive table viewer"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -25,6 +25,8 @@ dependencies = [
|
|
|
25
25
|
"click",
|
|
26
26
|
"duckdb",
|
|
27
27
|
"fastmcp",
|
|
28
|
+
"flatterer>=0.24.1",
|
|
29
|
+
"openpyxl",
|
|
28
30
|
"pyarrow",
|
|
29
31
|
"pytz",
|
|
30
32
|
"pyyaml",
|
|
@@ -38,6 +40,7 @@ Issues = "https://github.com/kindly/sqlnow-mcp/issues"
|
|
|
38
40
|
|
|
39
41
|
[project.scripts]
|
|
40
42
|
sqlnow-mcp = "sqlnow_mcp:main"
|
|
43
|
+
xlsx2duckdb = "sqlnow_mcp.xlsx2duckdb.cli:main"
|
|
41
44
|
|
|
42
45
|
[dependency-groups]
|
|
43
46
|
dev = [
|
|
@@ -98,10 +98,19 @@ def _make_session(
|
|
|
98
98
|
show_default=True,
|
|
99
99
|
help="MCP transport for local and publish (stdio spawns process; others listen on --host/--port).",
|
|
100
100
|
)
|
|
101
|
+
@click.option(
|
|
102
|
+
"--read-only/--read-write",
|
|
103
|
+
"read_only",
|
|
104
|
+
default=False,
|
|
105
|
+
show_default=True,
|
|
106
|
+
help="Local mode: strict read-only. Forbids writes/attach/detach (the shared "
|
|
107
|
+
"connection is never reopened). Without it, local mode is "
|
|
108
|
+
"read-only-with-temporary-writes (mutations briefly swap the connection).",
|
|
109
|
+
)
|
|
101
110
|
@click.option(
|
|
102
111
|
"--database",
|
|
103
112
|
"-d",
|
|
104
|
-
help="Active database name (without .db extension).",
|
|
113
|
+
help="Active database name (with or without the .db/.duckdb extension).",
|
|
105
114
|
)
|
|
106
115
|
@click.option(
|
|
107
116
|
"--db",
|
|
@@ -162,6 +171,7 @@ def cli(
|
|
|
162
171
|
host: str,
|
|
163
172
|
port: int,
|
|
164
173
|
transport: str,
|
|
174
|
+
read_only: bool,
|
|
165
175
|
database: str | None,
|
|
166
176
|
publish_db: Path | None,
|
|
167
177
|
metadata_path: Path | None,
|
|
@@ -211,6 +221,8 @@ def cli(
|
|
|
211
221
|
host=host,
|
|
212
222
|
port=port,
|
|
213
223
|
transport=transport.lower(), # type: ignore[arg-type]
|
|
224
|
+
read_only=read_only,
|
|
225
|
+
database=database,
|
|
214
226
|
)
|
|
215
227
|
run_local_server(cfg)
|
|
216
228
|
return
|
|
@@ -238,7 +250,7 @@ def create_database_cmd(ctx: click.Context, name: str) -> None:
|
|
|
238
250
|
@cli.command("list-databases")
|
|
239
251
|
@click.pass_context
|
|
240
252
|
def list_databases(ctx: click.Context) -> None:
|
|
241
|
-
"""List .db files in data-dir."""
|
|
253
|
+
"""List .db and .duckdb files in data-dir."""
|
|
242
254
|
session = _session_from_ctx(ctx)
|
|
243
255
|
dbs = session.list_databases()
|
|
244
256
|
if not dbs:
|
|
@@ -278,20 +290,35 @@ def current(ctx: click.Context) -> None:
|
|
|
278
290
|
default="view",
|
|
279
291
|
show_default=True,
|
|
280
292
|
)
|
|
293
|
+
@click.option(
|
|
294
|
+
"--max-cells",
|
|
295
|
+
type=int,
|
|
296
|
+
default=10_000,
|
|
297
|
+
show_default=True,
|
|
298
|
+
help="XLSX: cell budget above which a non-tabular sheet is skipped.",
|
|
299
|
+
)
|
|
281
300
|
@click.pass_context
|
|
282
301
|
def attach_file_cmd(
|
|
283
302
|
ctx: click.Context,
|
|
284
303
|
path: Path,
|
|
285
304
|
name: str | None,
|
|
286
305
|
attach_mode: str,
|
|
306
|
+
max_cells: int,
|
|
287
307
|
) -> None:
|
|
288
|
-
"""Attach a CSV or
|
|
308
|
+
"""Attach a CSV, Parquet, JSON, or XLSX file as a view or table.
|
|
309
|
+
|
|
310
|
+
XLSX files import every sheet (clean sheets become typed tables; non-tabular
|
|
311
|
+
sheets become html/text content tables).
|
|
312
|
+
"""
|
|
289
313
|
session = _session_from_ctx(ctx)
|
|
290
314
|
db = _require_database(ctx)
|
|
291
315
|
_open_database(session, db)
|
|
292
316
|
try:
|
|
293
317
|
result = session.attach_file(
|
|
294
|
-
str(path),
|
|
318
|
+
str(path),
|
|
319
|
+
name=name,
|
|
320
|
+
mode=attach_mode.lower(), # type: ignore[arg-type]
|
|
321
|
+
max_cells=max_cells,
|
|
295
322
|
)
|
|
296
323
|
except DuckDBSessionError as exc:
|
|
297
324
|
raise click.ClickException(str(exc)) from exc
|
|
@@ -40,6 +40,12 @@ class LocalConfig:
|
|
|
40
40
|
host: str = "127.0.0.1"
|
|
41
41
|
port: int = 8000
|
|
42
42
|
transport: Transport = "stdio"
|
|
43
|
+
# Strict read-only: forbid the write-swap so the shared connection is never
|
|
44
|
+
# reopened (rejects writes/attach/detach instead of swapping to read-write).
|
|
45
|
+
read_only: bool = False
|
|
46
|
+
# Database to open at startup. Lets the server reopen the same database after
|
|
47
|
+
# a restart (e.g. under an auto-reload watcher) without a fresh use_database.
|
|
48
|
+
database: str | None = None
|
|
43
49
|
|
|
44
50
|
|
|
45
51
|
@dataclass(frozen=True)
|
|
@@ -64,6 +70,7 @@ def create_session(cfg: LocalConfig) -> DuckDBSession:
|
|
|
64
70
|
data_dir=cfg.data_dir,
|
|
65
71
|
allow_paths=effective,
|
|
66
72
|
allow_external=cfg.allow_external,
|
|
73
|
+
allow_temp_writes=not cfg.read_only,
|
|
67
74
|
)
|
|
68
75
|
|
|
69
76
|
|