connectpak 0.1.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.
@@ -0,0 +1,94 @@
1
+ """One `connectpak` command for every engine.
2
+
3
+ Give it a connection string and it picks the engine from it, then hands off
4
+ to that engine's own CLI (`connectpak-sql-postgres`, ...), so the read-only
5
+ guard, timeout, and row cap are exactly the engine's.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib
11
+ import os
12
+ from urllib.parse import urlsplit
13
+
14
+ from connectpak.launcher.engines import BY_KEY, ENGINES, SQLITE_SUFFIXES, Engine
15
+
16
+ CONNECTION_VAR = "CONNECTPAK_CONNECTION"
17
+ ENGINE_VAR = "CONNECTPAK_ENGINE"
18
+
19
+
20
+ class LaunchError(ValueError):
21
+ """The connection or engine setting can't be resolved to one engine."""
22
+
23
+
24
+ def detect_engine(connection: str) -> Engine | None:
25
+ """Return the engine a connection string unambiguously names, else None."""
26
+ value = connection.strip()
27
+ scheme = urlsplit(value).scheme.lower() if "://" in value else ""
28
+ if scheme:
29
+ for engine in ENGINES:
30
+ if scheme in engine.schemes:
31
+ return engine
32
+ if scheme in ("ws", "wss") and ".neptune.amazonaws.com" in value.lower():
33
+ return BY_KEY["neptune"]
34
+ return None
35
+ if value.lower().endswith(SQLITE_SUFFIXES) or value == ":memory:":
36
+ return BY_KEY["sqlite"]
37
+ return None
38
+
39
+
40
+ def resolve(
41
+ connection: str | None, engine_key: str | None, environ: dict[str, str]
42
+ ) -> tuple[Engine, str]:
43
+ """Pick the engine and the connection value to hand to its CLI."""
44
+ engine_key = (engine_key or "").strip().lower()
45
+ connection = (connection or "").strip()
46
+
47
+ if engine_key and engine_key not in BY_KEY:
48
+ raise LaunchError(
49
+ f"unknown engine {engine_key!r}. Choose one of: {', '.join(BY_KEY)}"
50
+ )
51
+
52
+ if not connection:
53
+ # No unified setting: fall back to an engine-specific variable, so
54
+ # existing CONNECTPAK_<ENGINE>_* setups keep working.
55
+ candidates = [BY_KEY[engine_key]] if engine_key else list(ENGINES)
56
+ found = [e for e in candidates if environ.get(e.env_var, "").strip()]
57
+ if len(found) == 1:
58
+ return found[0], environ[found[0].env_var].strip()
59
+ if len(found) > 1:
60
+ names = ", ".join(f"${e.env_var}" for e in found)
61
+ raise LaunchError(
62
+ f"several engine variables are set ({names}). "
63
+ f"Set ${ENGINE_VAR} to choose one."
64
+ )
65
+ raise LaunchError(
66
+ f"no connection configured. Set ${CONNECTION_VAR} (and ${ENGINE_VAR} "
67
+ "if the engine can't be read from it)."
68
+ )
69
+
70
+ engine = BY_KEY[engine_key] if engine_key else detect_engine(connection)
71
+ if engine is None:
72
+ raise LaunchError(
73
+ "can't tell which database this connection is for. Set "
74
+ f"${ENGINE_VAR} to one of: {', '.join(BY_KEY)}"
75
+ )
76
+ if engine.key == "sqlite" and connection.lower().startswith("sqlite://"):
77
+ # SQLAlchemy form: sqlite:///rel.db -> rel.db, sqlite:////abs.db -> /abs.db
78
+ connection = connection.split("://", 1)[1]
79
+ if connection.startswith("/"):
80
+ connection = connection[1:]
81
+ return engine, connection
82
+
83
+
84
+ def run(engine: Engine, connection: str, argv: list[str]) -> int:
85
+ """Hand off to the engine's CLI with its connection env var set."""
86
+ import sys
87
+
88
+ os.environ[engine.env_var] = connection
89
+ module = importlib.import_module(engine.module)
90
+ sys.argv = [engine.cli, *argv]
91
+ return module.main()
92
+
93
+
94
+ __all__ = ["CONNECTION_VAR", "ENGINE_VAR", "LaunchError", "detect_engine", "resolve", "run"]
@@ -0,0 +1,44 @@
1
+ """CLI entry point: `connectpak` — one command for every engine."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import os
7
+ import sys
8
+
9
+ from connectpak.launcher import CONNECTION_VAR, ENGINE_VAR, LaunchError, resolve, run
10
+ from connectpak.launcher.engines import ENGINES
11
+
12
+
13
+ def main() -> int:
14
+ parser = argparse.ArgumentParser(
15
+ prog="connectpak",
16
+ description="Serve any connectpak engine over MCP, read-only. The engine is "
17
+ "read from the connection string, or set with --engine.",
18
+ epilog="Other options (--max-rows, --statement-timeout-ms, ...) are passed "
19
+ "through to the engine's own CLI.",
20
+ )
21
+ parser.add_argument("--connection", default=os.environ.get(CONNECTION_VAR),
22
+ help=f"Connection string (default: ${CONNECTION_VAR}).")
23
+ parser.add_argument("--engine", default=os.environ.get(ENGINE_VAR),
24
+ help=f"Engine to use if it can't be detected (default: ${ENGINE_VAR}).")
25
+ parser.add_argument("--list-engines", action="store_true",
26
+ help="Print the supported engines and exit.")
27
+ args, passthrough = parser.parse_known_args()
28
+
29
+ if args.list_engines:
30
+ for e in ENGINES:
31
+ print(f"{e.key:<14} {e.display:<16} e.g. {e.example}")
32
+ return 0
33
+
34
+ try:
35
+ engine, connection = resolve(args.connection, args.engine, dict(os.environ))
36
+ except LaunchError as exc:
37
+ print(f"connectpak: error: {exc}", file=sys.stderr)
38
+ return 2
39
+ print(f"connectpak: serving {engine.display} (read-only)", file=sys.stderr)
40
+ return run(engine, connection, passthrough)
41
+
42
+
43
+ if __name__ == "__main__":
44
+ raise SystemExit(main())
@@ -0,0 +1,115 @@
1
+ """Registry of every connectpak engine.
2
+
3
+ This is the single source of truth for the `connectpak` launcher (which
4
+ engine handles a connection string) and for `scripts/gen_plugins.py`
5
+ (what the plugin documents and forwards). It must stay stdlib-only so the
6
+ generator can load it without installing anything.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, field
12
+
13
+ AWS_ENV = (
14
+ "AWS_PROFILE",
15
+ "AWS_REGION",
16
+ "AWS_DEFAULT_REGION",
17
+ "AWS_ACCESS_KEY_ID",
18
+ "AWS_SECRET_ACCESS_KEY",
19
+ "AWS_SESSION_TOKEN",
20
+ "AWS_CONFIG_FILE",
21
+ "AWS_SHARED_CREDENTIALS_FILE",
22
+ )
23
+ GCP_ENV = ("GOOGLE_APPLICATION_CREDENTIALS", "GOOGLE_CLOUD_PROJECT", "CLOUDSDK_CONFIG")
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class Engine:
28
+ key: str # what users type for CONNECTPAK_ENGINE, e.g. "postgres"
29
+ package: str # directory under packages/, e.g. "sql-postgres"
30
+ display: str
31
+ family: str # "sql" | "document" | "graph" | "vector"
32
+ env_var: str # the engine CLI's connection env var
33
+ example: str
34
+ # URL schemes that identify this engine unambiguously.
35
+ schemes: tuple[str, ...] = ()
36
+ # Cloud credential env vars the engine's SDK reads (forwarded by Codex).
37
+ passthrough_env: tuple[str, ...] = field(default=())
38
+
39
+ @property
40
+ def cli(self) -> str:
41
+ return f"connectpak-{self.package}"
42
+
43
+ @property
44
+ def module(self) -> str:
45
+ track, name = self.package.split("-", 1)
46
+ return f"connectpak.{track}.{name}.__main__"
47
+
48
+
49
+ ENGINES: tuple[Engine, ...] = (
50
+ # SQL
51
+ Engine("postgres", "sql-postgres", "Postgres", "sql", "CONNECTPAK_POSTGRES_DSN",
52
+ "postgresql://reader:pw@db.example.com:5432/app", ("postgresql", "postgres")),
53
+ Engine("sqlite", "sql-sqlite", "SQLite", "sql", "CONNECTPAK_SQLITE_PATH",
54
+ "/Users/me/data/analytics.sqlite", ("sqlite",)),
55
+ Engine("mysql", "sql-mysql", "MySQL", "sql", "CONNECTPAK_MYSQL_DSN",
56
+ "mysql://reader:pw@db.example.com:3306/app", ("mysql", "mariadb")),
57
+ Engine("clickhouse", "sql-clickhouse", "ClickHouse", "sql", "CONNECTPAK_CLICKHOUSE_DSN",
58
+ "clickhouse://reader:pw@localhost:8123/default", ("clickhouse",)),
59
+ Engine("sqlserver", "sql-sqlserver", "SQL Server", "sql", "CONNECTPAK_SQLSERVER_DSN",
60
+ "mssql://reader:pw@db.example.com:1433/app", ("mssql", "sqlserver")),
61
+ Engine("oracle", "sql-oracle", "Oracle", "sql", "CONNECTPAK_ORACLE_DSN",
62
+ "reader/pw@db.example.com:1521/ORCLPDB1"),
63
+ Engine("bigquery", "sql-bigquery", "BigQuery", "sql", "CONNECTPAK_BIGQUERY_PROJECT",
64
+ "my-gcp-project", passthrough_env=GCP_ENV),
65
+ Engine("snowflake", "sql-snowflake", "Snowflake", "sql", "CONNECTPAK_SNOWFLAKE_DSN",
66
+ "snowflake://user:pw@account/db/schema?warehouse=WH&role=READONLY", ("snowflake",)),
67
+ Engine("redshift", "sql-redshift", "Redshift", "sql", "CONNECTPAK_REDSHIFT_DSN",
68
+ "redshift://user:pw@host:5439/db", ("redshift",)),
69
+ Engine("databricks", "sql-databricks", "Databricks", "sql", "CONNECTPAK_DATABRICKS_DSN",
70
+ "databricks://token:dapi-xxxxx@host.cloud.databricks.com?http_path=/sql/1.0/...",
71
+ ("databricks",)),
72
+ # Document / key-value / search
73
+ Engine("mongodb", "nosql-mongodb", "MongoDB", "document", "CONNECTPAK_MONGODB_URI",
74
+ "mongodb://user:pw@host:27017/dbname", ("mongodb", "mongodb+srv")),
75
+ Engine("dynamodb", "nosql-dynamodb", "DynamoDB", "document", "CONNECTPAK_DYNAMODB_REGION",
76
+ "us-east-1", passthrough_env=AWS_ENV),
77
+ Engine("elasticsearch", "nosql-elasticsearch", "Elasticsearch", "document",
78
+ "CONNECTPAK_ELASTICSEARCH_URL", "http://elastic:pw@localhost:9200"),
79
+ Engine("cassandra", "nosql-cassandra", "Cassandra", "document",
80
+ "CONNECTPAK_CASSANDRA_HOSTS", "user:pw@127.0.0.1:9042"),
81
+ # Graph
82
+ Engine("neo4j", "graph-neo4j", "Neo4j", "graph", "CONNECTPAK_NEO4J_URI",
83
+ "neo4j+s://neo4j:pw@cluster.example.com",
84
+ ("neo4j", "neo4j+s", "neo4j+ssc", "bolt", "bolt+s", "bolt+ssc")),
85
+ Engine("memgraph", "graph-memgraph", "Memgraph", "graph", "CONNECTPAK_MEMGRAPH_URI",
86
+ "bolt://user:pw@localhost:7687"),
87
+ Engine("arangodb", "graph-arangodb", "ArangoDB", "graph", "CONNECTPAK_ARANGODB_URL",
88
+ "http://root:pw@localhost:8529/_system"),
89
+ Engine("neptune", "graph-neptune", "Amazon Neptune", "graph", "CONNECTPAK_NEPTUNE_URL",
90
+ "wss://my-cluster.cluster-xxxx.us-east-1.neptune.amazonaws.com:8182/gremlin"),
91
+ # Vector
92
+ Engine("pgvector", "vector-pgvector", "pgvector", "vector", "CONNECTPAK_PGVECTOR_DSN",
93
+ "postgresql://reader:pw@db.example.com:5432/app"),
94
+ Engine("pinecone", "vector-pinecone", "Pinecone", "vector", "CONNECTPAK_PINECONE_API_KEY",
95
+ "pcsk_xxxxx"),
96
+ Engine("weaviate", "vector-weaviate", "Weaviate", "vector", "CONNECTPAK_WEAVIATE_URL",
97
+ "https://my-cluster.weaviate.network"),
98
+ Engine("qdrant", "vector-qdrant", "Qdrant", "vector", "CONNECTPAK_QDRANT_URL",
99
+ "https://api-key@my-cluster.qdrant.io"),
100
+ Engine("chroma", "vector-chroma", "Chroma", "vector", "CONNECTPAK_CHROMA_URL",
101
+ "http://localhost:8000"),
102
+ Engine("milvus", "vector-milvus", "Milvus", "vector", "CONNECTPAK_MILVUS_URI",
103
+ "http://localhost:19530"),
104
+ )
105
+
106
+ BY_KEY: dict[str, Engine] = {e.key: e for e in ENGINES}
107
+
108
+ FAMILIES: dict[str, str] = {
109
+ "sql": "SQL databases and warehouses",
110
+ "document": "document, key-value, and search stores",
111
+ "graph": "graph databases",
112
+ "vector": "vector databases",
113
+ }
114
+
115
+ SQLITE_SUFFIXES = (".db", ".sqlite", ".sqlite3", ".db3")
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.5
2
+ Name: connectpak
3
+ Version: 0.1.0
4
+ Summary: Read-only MCP database connectors for LLMs: one command for 24 engines (SQL, NoSQL, graph, vector).
5
+ Project-URL: Homepage, https://github.com/arunvenkatadri/connectpak
6
+ Project-URL: Repository, https://github.com/arunvenkatadri/connectpak
7
+ License: MIT
8
+ Keywords: claude-code,codex,database,llm,mcp,read-only
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: connectpak-core==0.1.0
11
+ Requires-Dist: connectpak-graph-arangodb==0.1.0
12
+ Requires-Dist: connectpak-graph-memgraph==0.1.0
13
+ Requires-Dist: connectpak-graph-neo4j==0.1.0
14
+ Requires-Dist: connectpak-graph-neptune==0.1.0
15
+ Requires-Dist: connectpak-nosql-cassandra==0.1.0
16
+ Requires-Dist: connectpak-nosql-dynamodb==0.1.0
17
+ Requires-Dist: connectpak-nosql-elasticsearch==0.1.0
18
+ Requires-Dist: connectpak-nosql-mongodb==0.1.0
19
+ Requires-Dist: connectpak-sql-bigquery==0.1.0
20
+ Requires-Dist: connectpak-sql-clickhouse==0.1.0
21
+ Requires-Dist: connectpak-sql-databricks==0.1.0
22
+ Requires-Dist: connectpak-sql-mysql==0.1.0
23
+ Requires-Dist: connectpak-sql-oracle==0.1.0
24
+ Requires-Dist: connectpak-sql-postgres==0.1.0
25
+ Requires-Dist: connectpak-sql-redshift==0.1.0
26
+ Requires-Dist: connectpak-sql-snowflake==0.1.0
27
+ Requires-Dist: connectpak-sql-sqlite==0.1.0
28
+ Requires-Dist: connectpak-sql-sqlserver==0.1.0
29
+ Requires-Dist: connectpak-vector-chroma==0.1.0
30
+ Requires-Dist: connectpak-vector-milvus==0.1.0
31
+ Requires-Dist: connectpak-vector-pgvector==0.1.0
32
+ Requires-Dist: connectpak-vector-pinecone==0.1.0
33
+ Requires-Dist: connectpak-vector-qdrant==0.1.0
34
+ Requires-Dist: connectpak-vector-weaviate==0.1.0
35
+ Description-Content-Type: text/markdown
36
+
37
+ # connectpak
38
+
39
+ One command that serves any of connectpak's 24 read-only database connectors
40
+ over MCP. It installs every engine package and picks the engine from the
41
+ connection string.
42
+
43
+ ```bash
44
+ CONNECTPAK_CONNECTION="postgresql://reader:pw@localhost:5432/app" uvx connectpak
45
+ CONNECTPAK_ENGINE=bigquery CONNECTPAK_CONNECTION=my-gcp-project uvx connectpak
46
+ uvx connectpak --list-engines
47
+ ```
48
+
49
+ Engines detected from the connection string: Postgres, MySQL/MariaDB,
50
+ ClickHouse, SQL Server, Snowflake, Redshift, Databricks, MongoDB, Neo4j,
51
+ SQLite (a `.db`/`.sqlite` path, or a SQLAlchemy-style `sqlite:///relative.db` / `sqlite:////absolute.db` URL), and Neptune. For the others
52
+ (Oracle, BigQuery, DynamoDB, Elasticsearch, Cassandra, Memgraph, ArangoDB,
53
+ pgvector, Pinecone, Weaviate, Qdrant, Chroma, Milvus), set
54
+ `CONNECTPAK_ENGINE`. If `CONNECTPAK_CONNECTION` isn't set, the engine's own
55
+ variable (such as `CONNECTPAK_POSTGRES_DSN`) is used.
56
+
57
+ To install only one engine, use its own package instead, such as
58
+ `connectpak-sql-postgres`. See the
59
+ [main README](https://github.com/arunvenkatadri/connectpak) for the safety
60
+ model and query formats.
@@ -0,0 +1,7 @@
1
+ connectpak/launcher/__init__.py,sha256=GLp_8hqZr4HH0qM6eN_bP23ERzIHq4zDGnV6MorpxoA,3505
2
+ connectpak/launcher/__main__.py,sha256=0JjA9hHwe0b2_YUVk8_f51POeDtG4dqXLBSbqN1vM0w,1710
3
+ connectpak/launcher/engines.py,sha256=5m36-yJhuXnf5WOU2ZGav5fdsITeCNQoHMeBnNNCCxM,5592
4
+ connectpak-0.1.0.dist-info/METADATA,sha256=uNpQWr4Po5Acz5JLsi6GN6rXiE-wuIqAatRUGmwAqn8,2738
5
+ connectpak-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
6
+ connectpak-0.1.0.dist-info/entry_points.txt,sha256=XuwhvK6cTdRMaGNyTQWi_oveFSVjx1VZ1GaDxsSEF08,65
7
+ connectpak-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ connectpak = connectpak.launcher.__main__:main