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,,
|