mcpg 0.8.2__tar.gz → 0.8.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 (142) hide show
  1. mcpg-0.8.3/.oss-scanner/README.md +43 -0
  2. {mcpg-0.8.2 → mcpg-0.8.3}/CHANGELOG.md +64 -0
  3. {mcpg-0.8.2 → mcpg-0.8.3}/PKG-INFO +19 -11
  4. {mcpg-0.8.2 → mcpg-0.8.3}/README.md +16 -8
  5. {mcpg-0.8.2 → mcpg-0.8.3}/packaging/mcpb/pyproject.toml +2 -2
  6. {mcpg-0.8.2 → mcpg-0.8.3}/pyproject.toml +5 -3
  7. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/__init__.py +1 -1
  8. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/about.py +32 -1
  9. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/config.py +27 -1
  10. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/diesel.py +6 -1
  11. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/drizzle.py +6 -1
  12. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/ecto.py +6 -1
  13. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/ent.py +6 -1
  14. mcpg-0.8.3/src/mcpg/guc.py +658 -0
  15. mcpg-0.8.3/src/mcpg/guc_tools.py +174 -0
  16. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/jooq.py +6 -1
  17. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/prisma.py +6 -1
  18. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/sql/allowlist.py +31 -0
  19. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/sqlalchemy_export.py +6 -1
  20. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/sqlc.py +6 -1
  21. mcpg-0.8.3/src/mcpg/timescaledb.py +648 -0
  22. mcpg-0.8.3/src/mcpg/timescaledb_admin.py +707 -0
  23. mcpg-0.8.3/src/mcpg/timescaledb_tools.py +532 -0
  24. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/tools.py +65 -2
  25. mcpg-0.8.2/src/mcpg/timescaledb.py +0 -280
  26. {mcpg-0.8.2 → mcpg-0.8.3}/.gitignore +0 -0
  27. {mcpg-0.8.2 → mcpg-0.8.3}/LICENSE +0 -0
  28. {mcpg-0.8.2 → mcpg-0.8.3}/NOTICE +0 -0
  29. {mcpg-0.8.2 → mcpg-0.8.3}/SECURITY.md +0 -0
  30. {mcpg-0.8.2 → mcpg-0.8.3}/benchmarks/README.md +0 -0
  31. {mcpg-0.8.2 → mcpg-0.8.3}/benchmarks/results/README.md +0 -0
  32. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/.github/workflows/README.md +0 -0
  33. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/LICENSE +0 -0
  34. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/README.md +0 -0
  35. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/benchmarks/rag/README.md +0 -0
  36. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/docs/README.md +0 -0
  37. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/pyproject.toml +0 -0
  38. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/scripts/README.md +0 -0
  39. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/sql/README.md +0 -0
  40. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/src/README.md +0 -0
  41. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/t/README.md +0 -0
  42. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/test/expected/README.md +0 -0
  43. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/test/sql/README.md +0 -0
  44. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/tests/unit/README.md +0 -0
  45. {mcpg-0.8.2 → mcpg-0.8.3}/scratch/pg_turboquant/third_party/README.md +0 -0
  46. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/__main__.py +0 -0
  47. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/advisors.py +0 -0
  48. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/aio.py +0 -0
  49. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/analytical.py +0 -0
  50. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/audit.py +0 -0
  51. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/audit_integrity.py +0 -0
  52. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/audit_nl2sql.py +0 -0
  53. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/audit_trail.py +0 -0
  54. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/autovacuum.py +0 -0
  55. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/cache.py +0 -0
  56. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/composite.py +0 -0
  57. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/config_advisor.py +0 -0
  58. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/context.py +0 -0
  59. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/cron.py +0 -0
  60. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/cursors.py +0 -0
  61. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/cypher.py +0 -0
  62. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/data_movement.py +0 -0
  63. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/database.py +0 -0
  64. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/ddl_dryrun.py +0 -0
  65. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/demo.py +0 -0
  66. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/diagrams.py +0 -0
  67. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/dynamic_session_intent.py +0 -0
  68. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/errors.py +0 -0
  69. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/extensions.py +0 -0
  70. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/graph.py +0 -0
  71. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/graph_diagram.py +0 -0
  72. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/graph_mgmt.py +0 -0
  73. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/graph_projection.py +0 -0
  74. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/headline_curator.py +0 -0
  75. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/health.py +0 -0
  76. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/http_runtime.py +0 -0
  77. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/identifiers.py +0 -0
  78. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/indexing.py +0 -0
  79. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/introspection.py +0 -0
  80. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/io_stats.py +0 -0
  81. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/listen.py +0 -0
  82. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/liveops.py +0 -0
  83. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/locks.py +0 -0
  84. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/logical_replication.py +0 -0
  85. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/maintenance.py +0 -0
  86. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/middleware/rate_limit.py +0 -0
  87. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/migration_history.py +0 -0
  88. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/migration_ingestion.py +0 -0
  89. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/migrations.py +0 -0
  90. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/multidb.py +0 -0
  91. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/naming.py +0 -0
  92. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/nl2sql.py +0 -0
  93. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/obs_logging.py +0 -0
  94. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/observability.py +0 -0
  95. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/oidc.py +0 -0
  96. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/otel_tracing.py +0 -0
  97. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/partman.py +0 -0
  98. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/pg19_ddl.py +0 -0
  99. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/pg19_partitions.py +0 -0
  100. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/pg19_runtime.py +0 -0
  101. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/pg19_skip_scan.py +0 -0
  102. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/pg19_stats.py +0 -0
  103. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/pg_prewarm.py +0 -0
  104. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/pg_search.py +0 -0
  105. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/pgq.py +0 -0
  106. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/pitr.py +0 -0
  107. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/policy.py +0 -0
  108. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/prompts.py +0 -0
  109. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/py.typed +0 -0
  110. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/query.py +0 -0
  111. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/rag_efficiency.py +0 -0
  112. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/rag_telemetry.py +0 -0
  113. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/redis_fdw.py +0 -0
  114. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/repack.py +0 -0
  115. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/replicas.py +0 -0
  116. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/resources.py +0 -0
  117. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/rls.py +0 -0
  118. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/schema_diff.py +0 -0
  119. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/schema_docs.py +0 -0
  120. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/secrets.py +0 -0
  121. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/server.py +0 -0
  122. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/session_advisor.py +0 -0
  123. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/session_intent.py +0 -0
  124. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/shell.py +0 -0
  125. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/sql/__init__.py +0 -0
  126. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/sql/driver.py +0 -0
  127. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/sql/safety.py +0 -0
  128. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/tenancy.py +0 -0
  129. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/test_data.py +0 -0
  130. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/test_row_factory.py +0 -0
  131. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/textsearch.py +0 -0
  132. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/tool_introspection.py +0 -0
  133. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/turboquant.py +0 -0
  134. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/vector_ops.py +0 -0
  135. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/vector_tuner_advanced.py +0 -0
  136. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/vector_tuning.py +0 -0
  137. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/wait_for_lsn.py +0 -0
  138. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/wal_archive.py +0 -0
  139. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/walinspect.py +0 -0
  140. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/warehousepg.py +0 -0
  141. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/workload.py +0 -0
  142. {mcpg-0.8.2 → mcpg-0.8.3}/src/mcpg/write.py +0 -0
@@ -0,0 +1,43 @@
1
+ # OSS Scanner enrollment
2
+
3
+ Artifacts for enrolling MCPg in [Anthropic's OSS
4
+ Scanner](https://github.com/anthropics/oss-scanner), a service that builds a
5
+ project in an isolated, no-network VM and runs a Claude-powered security scan,
6
+ emailing findings (with reproduction steps and proposed patches) privately to
7
+ the `primary_contact`.
8
+
9
+ Files here:
10
+
11
+ - **`Dockerfile`** — the build image the scanner uses. MCPg is pure Python, so
12
+ it just installs the full source + dependency environment (runtime + dev,
13
+ for the adversarial test suites) at build time; the checkout lands at `/src`
14
+ per the scanner contract.
15
+ - **`threat_model.md`** — steers the scan at MCPg's real surface: the SQL-safety
16
+ allowlist, access-mode / tenancy gates, OIDC auth, and the shell tools, with
17
+ app-security severity tiers.
18
+ - **`project.yaml`** — the enrollment manifest (reference copy; see below).
19
+
20
+ ## How to enroll (maintainer, one-time)
21
+
22
+ The scanner is enrolled by a pull request **to the `anthropics/oss-scanner`
23
+ repo** — which is separate from this one, so it must be opened by a maintainer
24
+ (fork + PR):
25
+
26
+ 1. Fork `anthropics/oss-scanner`.
27
+ 2. Add `projects/mcpg/project.yaml` with the contents of
28
+ [`project.yaml`](project.yaml) in this directory (it already points
29
+ `dockerfile` / `threat_model` at the paths here in `devopam/MCPg`, so the
30
+ scanner pulls them from this repo — nothing else to copy).
31
+ 3. Optionally run their local checks first: `tools/validate.py` then
32
+ `tools/check mcpg` (needs Docker + Python + PyYAML).
33
+ 4. Open the PR. After Anthropic merges it, scans run and findings are emailed
34
+ to `primary_contact`.
35
+
36
+ ## Notes
37
+
38
+ - Enrollment is reviewed/merged by Anthropic and targets "critical" OSS —
39
+ acceptance is their call.
40
+ - Reports are model-generated and not human-reviewed (no public disclosure);
41
+ treat findings as leads to verify, per the project's adversarial-test
42
+ discipline.
43
+ - To pin the scanned revision, add `#<tag>` to `repo` in `project.yaml`.
@@ -6,6 +6,70 @@ adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.8.3] - 2026-10-14
10
+
11
+ A feature release by content (new tools and opt-in flags, nothing removed or renamed), shipped as a patch per the maintainer's call: the default tool surface only gains five read-only tools, and every new write or admin tool needs an explicit `MCPG_ALLOW_*` flag. See [release notes](docs/release-notes-0.8.3.md).
12
+
13
+ ### Added
14
+
15
+ - **`ALTER SYSTEM` GUC tools.** `inspect_guc` (read), plus `alter_system`, `alter_system_reset_all` and
16
+ `reload_config` behind unrestricted mode + `MCPG_ALLOW_ALTER_SYSTEM`. Runs on an autocommit connection,
17
+ requires `confirm=true` plus a per-statement `confirmation_token`, and blocks connectivity/preload GUCs and
18
+ settings that run OS commands or hold credentials (`archive_command`, `restore_command`, `primary_conninfo`, …) by
19
+ default (`MCPG_ALTER_SYSTEM_ALLOWLIST` / `MCPG_ALTER_SYSTEM_DENYLIST`). `RESET ALL` enforces that policy on every
20
+ current override, `inspect_guc` redacts secret-bearing values, and the write tools refuse to run under
21
+ `MCPG_DEFAULT_ROLE` / `MCPG_ALLOWED_ROLES`. See `docs/alter-system.md`. (PR #352)
22
+ - **TimescaleDB read-only functions are now allowed in the SQL-safety kernel** (#353): `time_bucket`, `time_bucket_gapfill`,
23
+ `first`/`last`, `locf`, `interpolate`, `histogram`, the `hypertable_*_size` / `*_compression_stats` / `*_columnstore_stats`
24
+ family, `show_chunks`, `by_range`/`by_hash`, the uuidv7 helpers and `timescaledb_experimental.show_policies`. Mutating
25
+ functions, procedures, `timescaledb_pre/post_restore` and `get_telemetry_report` stay blocked.
26
+ - **TimescaleDB tool family** (design: `docs/plans/timescaledb-tools.md`, reference: `docs/timescaledb.md`): reads
27
+ `get_hypertable_stats`, `list_timescale_jobs`, `list_continuous_aggregates`, `get_timescaledb_telemetry_report`;
28
+ additive DDL `add_dimension`, `set_chunk_time_interval`, `add_continuous_aggregate_policy`, `add_reorder_policy` and four
29
+ `remove_*_policy` tools; and, behind the new `MCPG_ALLOW_TIMESCALEDB_ADMIN` flag with a `dry_run` / `confirm` +
30
+ `confirmation_token` flow, `drop_chunks`, `compress_chunk`, `decompress_chunk`, `reorder_chunk`, `merge_chunks`,
31
+ `refresh_continuous_aggregate`, `run_job`, `alter_job`, `delete_job`. Behaviour was verified on TimescaleDB 2.30.2, and a
32
+ new experimental `TimescaleDB` CI job runs the integration tests.
33
+
34
+ ### Security
35
+
36
+ - **Bumped `pyjwt` to ≥ 2.15.0 (2.15.1 resolved)** to clear the October 2026
37
+ advisory cascade against 2.13.0 — thirteen `PYSEC-2026-414x` findings plus
38
+ `CVE-2026-102275`, all flagged by `pip-audit --strict` and fixed in
39
+ 2.14.0 / 2.15.0. 2.15's `PyJWKClient` hardened its JWKS fetch against SSRF
40
+ (own `build_opener`/no-redirect handler instead of the module-level
41
+ `urllib.request.urlopen`); the OIDC verifier uses the stable public API so
42
+ no source change was needed, but the `test_oidc.py` JWKS mock now patches
43
+ pyjwt's `PyJWKClient.fetch_data` boundary rather than urllib internals.
44
+
45
+ ### Changed
46
+
47
+ - **`pglast` 8.4 → 8.5** (PR #350; `uv.lock` updated). The SQL-safety kernel's adversarial and fuzz suites pass on 8.5.
48
+ - **The contract fixtures now enable `MCPG_ALLOW_ALTER_SYSTEM` and `MCPG_ALLOW_TIMESCALEDB_ADMIN`**, so the tool-surface
49
+ snapshot (now 281 tools), annotations and return-shape contracts cover the ALTER SYSTEM and TimescaleDB admin tools.
50
+ - **ORM exporters explain why they reject delimited names.** `generate_prisma_schema` and the Drizzle,
51
+ SQLAlchemy, sqlc, Diesel, jOOQ, Ent and Ecto exporters still accept only plain `[A-Za-z_][A-Za-z0-9_]*`
52
+ names (the name becomes an identifier in generated source), but their errors and tool descriptions now
53
+ say so and point at the SQL tools that accept delimited names.
54
+
55
+ ### Fixed
56
+
57
+ - `docs/tools.md` tool-index heading and the hand-written tool counts (README, tour, user guide) were stale; regenerated.
58
+ - **SQL-safety fuzz harness now actually explores the validator.** `sql_safety_fuzzer` decoded its input
59
+ with `ConsumeUnicodeNoSurrogates`, which turns ASCII SQL into garbage code points, so `pglast` rejected
60
+ nearly every input at the first token and coverage never grew. It now decodes bytes directly and ships a
61
+ seed corpus and SQL dictionary (`.clusterfuzzlite/`); a 60 s run goes from `cov: 4` to `cov: 277`. Roadmap 23.1.
62
+
63
+ ### Documentation
64
+
65
+ - New [`docs/identifier-policy.md`](docs/identifier-policy.md): which names SQL tools accept
66
+ (delimited identifiers via `quote_identifier`) versus the plain-identifier-only sinks and why.
67
+ - README "Safe by default" no longer claims identifiers go through a strict regex; `adding-tools.md`
68
+ now recommends `quote_identifier`; the `subscribe_channel` description and a `config.py` comment no
69
+ longer claim plain-identifier-only names.
70
+ - `CODE_OF_CONDUCT.md` (Contributor Covenant 2.1) is now linked from the README, `CONTRIBUTING.md` and
71
+ the docs index.
72
+
9
73
  ## [0.8.2] - 2026-09-04
10
74
 
11
75
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mcpg
3
- Version: 0.8.2
3
+ Version: 0.8.3
4
4
  Summary: A production-grade PostgreSQL Model Context Protocol (MCP) server.
5
5
  Project-URL: Homepage, https://github.com/devopam/MCPg
6
6
  Project-URL: Documentation, https://github.com/devopam/MCPg#documentation
@@ -35,10 +35,10 @@ Requires-Dist: circuitbreaker>=2.1.3
35
35
  Requires-Dist: httpx>=0.27
36
36
  Requires-Dist: mcp-types>=2.0.0
37
37
  Requires-Dist: mcp[cli]>=2.0.0
38
- Requires-Dist: pglast==8.4
38
+ Requires-Dist: pglast==8.5
39
39
  Requires-Dist: psycopg-pool>=3.3.0
40
40
  Requires-Dist: psycopg[binary]>=3.3.2
41
- Requires-Dist: pyjwt[crypto]>=2.8
41
+ Requires-Dist: pyjwt[crypto]>=2.15.0
42
42
  Requires-Dist: tenacity>=9.1.4
43
43
  Requires-Dist: typing-extensions>=4.12
44
44
  Provides-Extra: aws
@@ -61,7 +61,7 @@ Description-Content-Type: text/markdown
61
61
 
62
62
  **A production-grade [Model Context Protocol](https://modelcontextprotocol.io)
63
63
  server for PostgreSQL.** It lets AI agents safely inspect, query, operate, and
64
- tune a Postgres database — 254 tools spanning catalog introspection,
64
+ tune a Postgres database — 279 tools spanning catalog introspection,
65
65
  query intelligence, natural-language SQL, structural diffs, hybrid search,
66
66
  graph queries, data movement, live ops, and more.
67
67
 
@@ -77,6 +77,7 @@ graph queries, data movement, live ops, and more.
77
77
  [![MCPg MCP server](https://glama.ai/mcp/servers/devopam/MCPg/badges/score.svg)](https://glama.ai/mcp/servers/devopam/MCPg)
78
78
  [![AllMCPs Verified](https://allmcps.com/api/badge/devopam-mcpg)](https://allmcps.com/mcp/devopam-mcpg?verify=63d9d537-be6b-4e07-a720-77dd0c41411b)
79
79
  [![MCPVault: claimed](https://mcpvault.io/badge/mcpg.svg?theme=dark)](https://mcpvault.io/servers/mcpg/health?utm_source=external_badge&utm_medium=referral&utm_campaign=mcp_health_report)
80
+ [![Listed on MCP ServerList](https://mcp-serverlist.com/api/badge/mcpg)](https://mcp-serverlist.com/mcp/mcpg)
80
81
 
81
82
  > **Try it live:** point an MCP client — or the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) — at the hosted, read-only demo endpoint `https://devopam-mcpg-demo.hf.space/mcp`. It serves read tools against throwaway demo data; for real use, run MCPg next to your own database (see [Quick start](#quick-start)).
82
83
 
@@ -87,6 +88,7 @@ graph queries, data movement, live ops, and more.
87
88
  - **[mcpservers.org](https://mcpservers.org/servers/devopam/mcpg)**
88
89
  - **[Smithery](https://smithery.ai/servers/devopam/mcpg)**
89
90
  - **[Glama](https://glama.ai/mcp/servers/devopam/MCPg)**
91
+ - **[MCP ServerList](https://mcp-serverlist.com/mcp/mcpg)**
90
92
 
91
93
  ---
92
94
 
@@ -102,9 +104,9 @@ graph queries, data movement, live ops, and more.
102
104
 
103
105
  - **Safe by default.** Read-only access mode. Every user-supplied SQL
104
106
  statement parses through a validated AST allowlist before execution.
105
- Identifier interpolation flows through a strict
106
- `[A-Za-z_][A-Za-z0-9_]*` regex — a design constraint that means
107
- user input never reaches the database through string concatenation.
107
+ Names that reach PostgreSQL as SQL identifiers are encoded with
108
+ delimited-identifier quoting (see [Identifier policy](docs/identifier-policy.md)),
109
+ not rejected merely for containing hyphens or other legal characters.
108
110
  Capabilities like DDL, shell, and `LISTEN/NOTIFY` are off until you
109
111
  opt in. Every tool publishes MCP `ToolAnnotations` (`readOnlyHint`,
110
112
  `openWorldHint`) derived from those same gates, so clients can
@@ -313,6 +315,8 @@ are one-shot commands, not configuration). The only required one is
313
315
  |---|---|---|
314
316
  | `MCPG_ALLOW_DDL` | `false` | Expose DDL tools (`run_ddl`, `create_graph`, `drop_graph`, hypertable tools, migration tools). Requires `MCPG_ACCESS_MODE=unrestricted`. |
315
317
  | `MCPG_ALLOW_SHELL` | `false` | Expose subprocess-backed tools (`dump_database`, `restore_database`, `run_pg_binary`). Required PG client binaries must be on `PATH`. |
318
+ | `MCPG_ALLOW_TIMESCALEDB_ADMIN` | `false` | With unrestricted mode + `MCPG_ALLOW_DDL`, expose the destructive / heavy TimescaleDB tools (`drop_chunks`, `compress_chunk`, `decompress_chunk`, `reorder_chunk`, `merge_chunks`, `refresh_continuous_aggregate`, `run_job`, `alter_job`, `delete_job`); each needs `dry_run` then `confirm=true` + `confirmation_token`. See [docs/timescaledb.md](docs/timescaledb.md). |
319
+ | `MCPG_ALLOW_ALTER_SYSTEM` | `false` | Expose `alter_system`, `alter_system_reset_all`, `reload_config` (unrestricted mode only; two-step `confirm=true` flow — see [docs/alter-system.md](docs/alter-system.md)). `MCPG_ALTER_SYSTEM_ALLOWLIST` / `MCPG_ALTER_SYSTEM_DENYLIST` (comma-separated GUC names) narrow or extend the built-in denylist. |
316
320
  | `MCPG_ALLOW_LISTEN` | `false` | Expose `LISTEN/NOTIFY` tools (`subscribe_channel`, `poll_notifications`, `unsubscribe_channel`, `list_notification_subscriptions`). |
317
321
 
318
322
  #### Authentication (HTTP transports only)
@@ -597,9 +601,11 @@ Compact category list. For the full, current tool reference see
597
601
  - **Server-side cursors** — `open_cursor`, `fetch_cursor`,
598
602
  `close_cursor`, `list_cursors` for pageable reads over millions
599
603
  of rows.
600
- - **TimescaleDB** — `list_hypertables`, `list_chunks`,
601
- `create_hypertable`, `add_compression_policy`,
602
- `add_retention_policy`.
604
+ - **TimescaleDB** — hypertable / chunk / job / continuous-aggregate
605
+ introspection and stats, policy and dimension management, and gated
606
+ chunk / job administration (`drop_chunks`, `compress_chunk`,
607
+ `refresh_continuous_aggregate`, `run_job`, …). See
608
+ [docs/timescaledb.md](docs/timescaledb.md).
603
609
  - **ORM schema exporters** — Prisma, Drizzle, SQLAlchemy, sqlc,
604
610
  Diesel, jOOQ, Ent, Ecto.
605
611
  - **Event streams** — `subscribe_channel`, `poll_notifications`,
@@ -661,7 +667,9 @@ page for downloadable artifacts.
661
667
 
662
668
  Pull requests welcome — see [`CONTRIBUTING.md`](https://github.com/devopam/MCPg/blob/main/CONTRIBUTING.md) for
663
669
  the dev-loop setup, test conventions, and the per-PR review
664
- checklist.
670
+ checklist. Participation is governed by the
671
+ [Code of Conduct](https://github.com/devopam/MCPg/blob/main/CODE_OF_CONDUCT.md)
672
+ (Contributor Covenant 2.1).
665
673
 
666
674
  ---
667
675
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  **A production-grade [Model Context Protocol](https://modelcontextprotocol.io)
8
8
  server for PostgreSQL.** It lets AI agents safely inspect, query, operate, and
9
- tune a Postgres database — 254 tools spanning catalog introspection,
9
+ tune a Postgres database — 279 tools spanning catalog introspection,
10
10
  query intelligence, natural-language SQL, structural diffs, hybrid search,
11
11
  graph queries, data movement, live ops, and more.
12
12
 
@@ -22,6 +22,7 @@ graph queries, data movement, live ops, and more.
22
22
  [![MCPg MCP server](https://glama.ai/mcp/servers/devopam/MCPg/badges/score.svg)](https://glama.ai/mcp/servers/devopam/MCPg)
23
23
  [![AllMCPs Verified](https://allmcps.com/api/badge/devopam-mcpg)](https://allmcps.com/mcp/devopam-mcpg?verify=63d9d537-be6b-4e07-a720-77dd0c41411b)
24
24
  [![MCPVault: claimed](https://mcpvault.io/badge/mcpg.svg?theme=dark)](https://mcpvault.io/servers/mcpg/health?utm_source=external_badge&utm_medium=referral&utm_campaign=mcp_health_report)
25
+ [![Listed on MCP ServerList](https://mcp-serverlist.com/api/badge/mcpg)](https://mcp-serverlist.com/mcp/mcpg)
25
26
 
26
27
  > **Try it live:** point an MCP client — or the [MCP Inspector](https://github.com/modelcontextprotocol/inspector) — at the hosted, read-only demo endpoint `https://devopam-mcpg-demo.hf.space/mcp`. It serves read tools against throwaway demo data; for real use, run MCPg next to your own database (see [Quick start](#quick-start)).
27
28
 
@@ -32,6 +33,7 @@ graph queries, data movement, live ops, and more.
32
33
  - **[mcpservers.org](https://mcpservers.org/servers/devopam/mcpg)**
33
34
  - **[Smithery](https://smithery.ai/servers/devopam/mcpg)**
34
35
  - **[Glama](https://glama.ai/mcp/servers/devopam/MCPg)**
36
+ - **[MCP ServerList](https://mcp-serverlist.com/mcp/mcpg)**
35
37
 
36
38
  ---
37
39
 
@@ -47,9 +49,9 @@ graph queries, data movement, live ops, and more.
47
49
 
48
50
  - **Safe by default.** Read-only access mode. Every user-supplied SQL
49
51
  statement parses through a validated AST allowlist before execution.
50
- Identifier interpolation flows through a strict
51
- `[A-Za-z_][A-Za-z0-9_]*` regex — a design constraint that means
52
- user input never reaches the database through string concatenation.
52
+ Names that reach PostgreSQL as SQL identifiers are encoded with
53
+ delimited-identifier quoting (see [Identifier policy](docs/identifier-policy.md)),
54
+ not rejected merely for containing hyphens or other legal characters.
53
55
  Capabilities like DDL, shell, and `LISTEN/NOTIFY` are off until you
54
56
  opt in. Every tool publishes MCP `ToolAnnotations` (`readOnlyHint`,
55
57
  `openWorldHint`) derived from those same gates, so clients can
@@ -258,6 +260,8 @@ are one-shot commands, not configuration). The only required one is
258
260
  |---|---|---|
259
261
  | `MCPG_ALLOW_DDL` | `false` | Expose DDL tools (`run_ddl`, `create_graph`, `drop_graph`, hypertable tools, migration tools). Requires `MCPG_ACCESS_MODE=unrestricted`. |
260
262
  | `MCPG_ALLOW_SHELL` | `false` | Expose subprocess-backed tools (`dump_database`, `restore_database`, `run_pg_binary`). Required PG client binaries must be on `PATH`. |
263
+ | `MCPG_ALLOW_TIMESCALEDB_ADMIN` | `false` | With unrestricted mode + `MCPG_ALLOW_DDL`, expose the destructive / heavy TimescaleDB tools (`drop_chunks`, `compress_chunk`, `decompress_chunk`, `reorder_chunk`, `merge_chunks`, `refresh_continuous_aggregate`, `run_job`, `alter_job`, `delete_job`); each needs `dry_run` then `confirm=true` + `confirmation_token`. See [docs/timescaledb.md](docs/timescaledb.md). |
264
+ | `MCPG_ALLOW_ALTER_SYSTEM` | `false` | Expose `alter_system`, `alter_system_reset_all`, `reload_config` (unrestricted mode only; two-step `confirm=true` flow — see [docs/alter-system.md](docs/alter-system.md)). `MCPG_ALTER_SYSTEM_ALLOWLIST` / `MCPG_ALTER_SYSTEM_DENYLIST` (comma-separated GUC names) narrow or extend the built-in denylist. |
261
265
  | `MCPG_ALLOW_LISTEN` | `false` | Expose `LISTEN/NOTIFY` tools (`subscribe_channel`, `poll_notifications`, `unsubscribe_channel`, `list_notification_subscriptions`). |
262
266
 
263
267
  #### Authentication (HTTP transports only)
@@ -542,9 +546,11 @@ Compact category list. For the full, current tool reference see
542
546
  - **Server-side cursors** — `open_cursor`, `fetch_cursor`,
543
547
  `close_cursor`, `list_cursors` for pageable reads over millions
544
548
  of rows.
545
- - **TimescaleDB** — `list_hypertables`, `list_chunks`,
546
- `create_hypertable`, `add_compression_policy`,
547
- `add_retention_policy`.
549
+ - **TimescaleDB** — hypertable / chunk / job / continuous-aggregate
550
+ introspection and stats, policy and dimension management, and gated
551
+ chunk / job administration (`drop_chunks`, `compress_chunk`,
552
+ `refresh_continuous_aggregate`, `run_job`, …). See
553
+ [docs/timescaledb.md](docs/timescaledb.md).
548
554
  - **ORM schema exporters** — Prisma, Drizzle, SQLAlchemy, sqlc,
549
555
  Diesel, jOOQ, Ent, Ecto.
550
556
  - **Event streams** — `subscribe_channel`, `poll_notifications`,
@@ -606,7 +612,9 @@ page for downloadable artifacts.
606
612
 
607
613
  Pull requests welcome — see [`CONTRIBUTING.md`](https://github.com/devopam/MCPg/blob/main/CONTRIBUTING.md) for
608
614
  the dev-loop setup, test conventions, and the per-PR review
609
- checklist.
615
+ checklist. Participation is governed by the
616
+ [Code of Conduct](https://github.com/devopam/MCPg/blob/main/CODE_OF_CONDUCT.md)
617
+ (Contributor Covenant 2.1).
610
618
 
611
619
  ---
612
620
 
@@ -5,9 +5,9 @@
5
5
  # to the release tag by the publish workflow, exactly like server.json.
6
6
  [project]
7
7
  name = "mcpg-desktop-extension"
8
- version = "0.8.2"
8
+ version = "0.8.3"
9
9
  description = "MCPB launcher shim for the MCPg PostgreSQL MCP server."
10
10
  requires-python = ">=3.12"
11
11
  dependencies = [
12
- "mcpg==0.8.2",
12
+ "mcpg==0.8.3",
13
13
  ]
@@ -53,7 +53,7 @@ dependencies = [
53
53
  "mcp[cli]>=2.0.0",
54
54
  "psycopg[binary]>=3.3.2",
55
55
  "psycopg-pool>=3.3.0",
56
- "pglast==8.4",
56
+ "pglast==8.5",
57
57
  "typing-extensions>=4.12",
58
58
  # httpx ships transitively via mcp; declare it explicitly so the
59
59
  # NL→SQL providers in mcpg.nl2sql have a guaranteed surface.
@@ -62,8 +62,10 @@ dependencies = [
62
62
  # mcpg.dynamic_session_intent has a guaranteed surface.
63
63
  "mcp-types>=2.0.0",
64
64
  # JWT validation for the OIDC auth path. ``[crypto]`` pulls in
65
- # ``cryptography`` so RS256 / ES256 signature checks work.
66
- "pyjwt[crypto]>=2.8",
65
+ # ``cryptography`` so RS256 / ES256 signature checks work. Floor raised
66
+ # to 2.15.0 to clear the 2026 pyjwt advisories (PYSEC-2026-414x +
67
+ # CVE-2026-102275), all fixed in 2.14.0 / 2.15.0.
68
+ "pyjwt[crypto]>=2.15.0",
67
69
  "circuitbreaker>=2.1.3",
68
70
  "tenacity>=9.1.4",
69
71
  ]
@@ -2,7 +2,7 @@
2
2
 
3
3
  import warnings
4
4
 
5
- __version__ = "0.8.2"
5
+ __version__ = "0.8.3"
6
6
 
7
7
  # ``schema`` is the natural field name for "which Postgres schema" across
8
8
  # ~180 tool-return dataclasses. The ``mcp`` SDK dynamically builds a
@@ -253,7 +253,11 @@ CAPABILITIES: tuple[Capability, ...] = (
253
253
  detail=(
254
254
  "If TimescaleDB is installed: `create_hypertable`, "
255
255
  "`add_compression_policy`, `add_retention_policy`, `list_hypertables`, "
256
- "`list_chunks`. For PostgreSQL native partitioning managed by "
256
+ "`list_chunks`, `get_hypertable_stats`, `list_timescale_jobs`, "
257
+ "`list_continuous_aggregates`, dimension / policy management, and "
258
+ "(with `MCPG_ALLOW_TIMESCALEDB_ADMIN`) `drop_chunks`, chunk compression / "
259
+ "merge / reorder, continuous-aggregate refresh and job control. "
260
+ "For PostgreSQL native partitioning managed by "
257
261
  "pg_partman: `partman_create_parent`, `partman_run_maintenance`, "
258
262
  "`partman_drop_partition`, `list_partitions`. Both backends are "
259
263
  "first-class for the audit / RAG telemetry tables (see PR-4 / PR-5 "
@@ -540,6 +544,28 @@ _TOOL_TO_BUCKET_OVERRIDES: dict[str, str] = {
540
544
  # list_hypertables / list_chunks / list_partitions are time-series.
541
545
  "list_hypertables": "timeseries_partitioning",
542
546
  "list_chunks": "timeseries_partitioning",
547
+ # TimescaleDB stats / jobs / policies / chunk + job admin (docs/plans/timescaledb-tools.md).
548
+ "get_hypertable_stats": "timeseries_partitioning",
549
+ "list_timescale_jobs": "timeseries_partitioning",
550
+ "list_continuous_aggregates": "timeseries_partitioning",
551
+ "get_timescaledb_telemetry_report": "timeseries_partitioning",
552
+ "add_dimension": "timeseries_partitioning",
553
+ "set_chunk_time_interval": "timeseries_partitioning",
554
+ "add_continuous_aggregate_policy": "timeseries_partitioning",
555
+ "add_reorder_policy": "timeseries_partitioning",
556
+ "remove_retention_policy": "timeseries_partitioning",
557
+ "remove_compression_policy": "timeseries_partitioning",
558
+ "remove_continuous_aggregate_policy": "timeseries_partitioning",
559
+ "remove_reorder_policy": "timeseries_partitioning",
560
+ "drop_chunks": "timeseries_partitioning",
561
+ "compress_chunk": "timeseries_partitioning",
562
+ "decompress_chunk": "timeseries_partitioning",
563
+ "reorder_chunk": "timeseries_partitioning",
564
+ "merge_chunks": "timeseries_partitioning",
565
+ "refresh_continuous_aggregate": "timeseries_partitioning",
566
+ "run_job": "timeseries_partitioning",
567
+ "alter_job": "timeseries_partitioning",
568
+ "delete_job": "timeseries_partitioning",
543
569
  "list_partitions": "timeseries_partitioning",
544
570
  # list_turboquant_indexes / list_pg_search_indexes are search-bucket.
545
571
  "list_turboquant_indexes": "vector_search",
@@ -711,6 +737,11 @@ _TOOL_TO_BUCKET_OVERRIDES: dict[str, str] = {
711
737
  "drop_property_graph": "property_graph_queries",
712
738
  # PG 19 in-server REPACK — operational maintenance, sits next to
713
739
  # run_maintenance / check_database_health.
740
+ # inspect_guc / alter_system family (ALTER SYSTEM tools, docs/alter-system.md).
741
+ "inspect_guc": "operations_and_health",
742
+ "alter_system": "operations_and_health",
743
+ "alter_system_reset_all": "operations_and_health",
744
+ "reload_config": "operations_and_health",
714
745
  "get_repack_status": "operations_and_health",
715
746
  "repack_table": "operations_and_health",
716
747
  # PG 19 async-I/O subsystem — observability + advisor; sits in the
@@ -66,6 +66,10 @@ class Settings:
66
66
  allow_ddl: bool = False
67
67
  allow_shell: bool = False
68
68
  allow_listen: bool = False
69
+ allow_timescaledb_admin: bool = False
70
+ allow_alter_system: bool = False
71
+ alter_system_allowlist: tuple[str, ...] = ()
72
+ alter_system_denylist: tuple[str, ...] = ()
69
73
  elicit_confirm_writes: bool = False
70
74
  shell_timeout_sec: int = 60
71
75
  shell_max_output_bytes: int = 64 * 1024 * 1024
@@ -131,7 +135,8 @@ class Settings:
131
135
  # query when set. HTTP requests can override per-request by sending
132
136
  # ``X-MCPG-Role: <role>``; when ``allowed_roles`` is set the header
133
137
  # value must appear in it (otherwise a 403 is returned). Role names
134
- # are validated against ``[A-Za-z_][A-Za-z0-9_]*`` regardless.
138
+ # must be addressable identifiers (non-empty, no NUL, <= 63 bytes);
139
+ # delimited names (hyphens, spaces, mixed case) are quoted safely.
135
140
  default_role: str | None = None
136
141
  allowed_roles: tuple[str, ...] = ()
137
142
  # Read-replica routing. When ``replica_urls`` is non-empty, every
@@ -314,6 +319,8 @@ class Settings:
314
319
  f"allow_ddl={self.allow_ddl}, "
315
320
  f"allow_shell={self.allow_shell}, "
316
321
  f"allow_listen={self.allow_listen}, "
322
+ f"allow_timescaledb_admin={self.allow_timescaledb_admin}, "
323
+ f"allow_alter_system={self.allow_alter_system}, "
317
324
  f"elicit_confirm_writes={self.elicit_confirm_writes}, "
318
325
  f"shell_timeout_sec={self.shell_timeout_sec}, "
319
326
  f"shell_max_output_bytes={self.shell_max_output_bytes}, "
@@ -553,6 +560,21 @@ def load_settings(env: Mapping[str, str] | None = None) -> Settings: # noqa: C9
553
560
  if (raw := env.get("MCPG_ALLOW_DDL")) is not None:
554
561
  allow_ddl = _parse_bool("MCPG_ALLOW_DDL", raw)
555
562
 
563
+ allow_timescaledb_admin = False
564
+ if (raw := env.get("MCPG_ALLOW_TIMESCALEDB_ADMIN")) is not None:
565
+ allow_timescaledb_admin = _parse_bool("MCPG_ALLOW_TIMESCALEDB_ADMIN", raw)
566
+ allow_alter_system = False
567
+ if (raw := env.get("MCPG_ALLOW_ALTER_SYSTEM")) is not None:
568
+ allow_alter_system = _parse_bool("MCPG_ALLOW_ALTER_SYSTEM", raw)
569
+
570
+ alter_system_allowlist: tuple[str, ...] = ()
571
+ if (raw := env.get("MCPG_ALTER_SYSTEM_ALLOWLIST")) is not None:
572
+ alter_system_allowlist = tuple(n.strip().lower() for n in raw.split(",") if n.strip())
573
+
574
+ alter_system_denylist: tuple[str, ...] = ()
575
+ if (raw := env.get("MCPG_ALTER_SYSTEM_DENYLIST")) is not None:
576
+ alter_system_denylist = tuple(n.strip().lower() for n in raw.split(",") if n.strip())
577
+
556
578
  allow_shell = False
557
579
  if (raw := env.get("MCPG_ALLOW_SHELL")) is not None:
558
580
  allow_shell = _parse_bool("MCPG_ALLOW_SHELL", raw)
@@ -1279,6 +1301,10 @@ def load_settings(env: Mapping[str, str] | None = None) -> Settings: # noqa: C9
1279
1301
  allow_ddl=allow_ddl,
1280
1302
  allow_shell=allow_shell,
1281
1303
  allow_listen=allow_listen,
1304
+ allow_timescaledb_admin=allow_timescaledb_admin,
1305
+ allow_alter_system=allow_alter_system,
1306
+ alter_system_allowlist=alter_system_allowlist,
1307
+ alter_system_denylist=alter_system_denylist,
1282
1308
  elicit_confirm_writes=elicit_confirm_writes,
1283
1309
  shell_timeout_sec=shell_timeout_sec,
1284
1310
  shell_max_output_bytes=shell_max_output_bytes,
@@ -43,7 +43,12 @@ class DieselExportError(MCPgError):
43
43
 
44
44
  def _check_identifier(name: str, kind: str) -> None:
45
45
  if not _IDENTIFIER.match(name):
46
- raise DieselExportError(f"invalid {kind} name: {name!r}")
46
+ raise DieselExportError(
47
+ f"invalid {kind} name {name!r}; this exporter only accepts plain "
48
+ f"identifiers [A-Za-z_][A-Za-z0-9_]* because the name becomes a source "
49
+ f"identifier in generated code. SQL tools (export_table, dump_database, …) "
50
+ f"accept delimited names via quoting — see docs/identifier-policy.md."
51
+ )
47
52
 
48
53
 
49
54
  def _strip_type_params(data_type: str) -> str:
@@ -40,7 +40,12 @@ class DrizzleError(MCPgError):
40
40
 
41
41
  def _check_identifier(name: str, kind: str) -> None:
42
42
  if not _IDENTIFIER.match(name):
43
- raise DrizzleError(f"invalid {kind} name: {name!r}")
43
+ raise DrizzleError(
44
+ f"invalid {kind} name {name!r}; this exporter only accepts plain "
45
+ f"identifiers [A-Za-z_][A-Za-z0-9_]* because the name becomes a source "
46
+ f"identifier in generated code. SQL tools (export_table, dump_database, …) "
47
+ f"accept delimited names via quoting — see docs/identifier-policy.md."
48
+ )
44
49
 
45
50
 
46
51
  _SNAKE_BOUNDARY = re.compile(r"_+([a-z0-9])")
@@ -41,7 +41,12 @@ class EctoExportError(MCPgError):
41
41
 
42
42
  def _check_identifier(name: str, kind: str) -> None:
43
43
  if not _IDENTIFIER.match(name):
44
- raise EctoExportError(f"invalid {kind} name: {name!r}")
44
+ raise EctoExportError(
45
+ f"invalid {kind} name {name!r}; this exporter only accepts plain "
46
+ f"identifiers [A-Za-z_][A-Za-z0-9_]* because the name becomes a source "
47
+ f"identifier in generated code. SQL tools (export_table, dump_database, …) "
48
+ f"accept delimited names via quoting — see docs/identifier-policy.md."
49
+ )
45
50
 
46
51
 
47
52
  def _strip_type_params(data_type: str) -> str:
@@ -37,7 +37,12 @@ class EntExportError(MCPgError):
37
37
 
38
38
  def _check_identifier(name: str, kind: str) -> None:
39
39
  if not _IDENTIFIER.match(name):
40
- raise EntExportError(f"invalid {kind} name: {name!r}")
40
+ raise EntExportError(
41
+ f"invalid {kind} name {name!r}; this exporter only accepts plain "
42
+ f"identifiers [A-Za-z_][A-Za-z0-9_]* because the name becomes a source "
43
+ f"identifier in generated code. SQL tools (export_table, dump_database, …) "
44
+ f"accept delimited names via quoting — see docs/identifier-policy.md."
45
+ )
41
46
 
42
47
 
43
48
  def _pascal(snake: str) -> str: