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.
Files changed (28) hide show
  1. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/PKG-INFO +3 -1
  2. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/pyproject.toml +4 -1
  3. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/cli.py +31 -4
  4. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/config.py +7 -0
  5. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/db.py +492 -75
  6. sqlnow_mcp-0.3.0/sqlnow_mcp/map_result.py +432 -0
  7. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/metadata.py +71 -20
  8. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/server.py +438 -6
  9. sqlnow_mcp-0.3.0/sqlnow_mcp/ui/browse.html +153 -0
  10. sqlnow_mcp-0.3.0/sqlnow_mcp/ui/map-mapbox.html +3445 -0
  11. sqlnow_mcp-0.3.0/sqlnow_mcp/ui/map.html +737 -0
  12. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/ui/table.html +70 -70
  13. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/ui.py +12 -0
  14. sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/__init__.py +31 -0
  15. sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/api.py +223 -0
  16. sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/classify.py +266 -0
  17. sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/cli.py +88 -0
  18. sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/extract.py +164 -0
  19. sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/loader.py +124 -0
  20. sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/naming.py +81 -0
  21. sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/reader.py +110 -0
  22. sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/render.py +101 -0
  23. sqlnow_mcp-0.3.0/sqlnow_mcp/xlsx2duckdb/render_office.py +102 -0
  24. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/LICENSE +0 -0
  25. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/README.md +0 -0
  26. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/__init__.py +0 -0
  27. {sqlnow_mcp-0.2.1 → sqlnow_mcp-0.3.0}/sqlnow_mcp/resource_limits.py +0 -0
  28. {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.2.1
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.2.1"
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 Parquet file as a view or table."""
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), name=name, mode=attach_mode.lower() # type: ignore[arg-type]
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