airgap-db-bridge 1.0.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.
- airgap_db_bridge-1.0.3/.github/workflows/ci.yml +201 -0
- airgap_db_bridge-1.0.3/.gitignore +74 -0
- airgap_db_bridge-1.0.3/CHANGELOG.md +51 -0
- airgap_db_bridge-1.0.3/Dockerfile +65 -0
- airgap_db_bridge-1.0.3/PKG-INFO +302 -0
- airgap_db_bridge-1.0.3/README.md +273 -0
- airgap_db_bridge-1.0.3/TROUBLESHOOTING.md +692 -0
- airgap_db_bridge-1.0.3/docker-compose.yml +72 -0
- airgap_db_bridge-1.0.3/dxt/icon.png +0 -0
- airgap_db_bridge-1.0.3/dxt/manifest.json +77 -0
- airgap_db_bridge-1.0.3/pyproject.toml +76 -0
- airgap_db_bridge-1.0.3/smithery.yaml +45 -0
- airgap_db_bridge-1.0.3/src/postgresql_mcp/__init__.py +6 -0
- airgap_db_bridge-1.0.3/src/postgresql_mcp/core.py +309 -0
- airgap_db_bridge-1.0.3/src/postgresql_mcp/models.py +199 -0
- airgap_db_bridge-1.0.3/src/postgresql_mcp/py.typed +0 -0
- airgap_db_bridge-1.0.3/src/postgresql_mcp/server.py +212 -0
- airgap_db_bridge-1.0.3/tests/conftest.py +133 -0
- airgap_db_bridge-1.0.3/tests/conftest.py.backup +138 -0
- airgap_db_bridge-1.0.3/tests/test_core.py +340 -0
- airgap_db_bridge-1.0.3/tests/test_models.py +328 -0
- airgap_db_bridge-1.0.3/tests/test_tools.py +157 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main, develop]
|
|
6
|
+
tags: ['v*']
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [master, main, develop]
|
|
9
|
+
release:
|
|
10
|
+
types: [published]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint:
|
|
14
|
+
name: Lint (ruff)
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
- name: Set up Python
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
cache: "pip"
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: |
|
|
28
|
+
pip install -U pip
|
|
29
|
+
pip install ruff==0.6.9
|
|
30
|
+
|
|
31
|
+
- name: Run ruff check
|
|
32
|
+
run: ruff check src/postgresql_mcp
|
|
33
|
+
|
|
34
|
+
- name: Run ruff format check
|
|
35
|
+
run: ruff format --check src/postgresql_mcp
|
|
36
|
+
|
|
37
|
+
typecheck:
|
|
38
|
+
name: Type Check (mypy)
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
steps:
|
|
41
|
+
- name: Checkout
|
|
42
|
+
uses: actions/checkout@v4
|
|
43
|
+
|
|
44
|
+
- name: Set up Python
|
|
45
|
+
uses: actions/setup-python@v5
|
|
46
|
+
with:
|
|
47
|
+
python-version: "3.12"
|
|
48
|
+
cache: "pip"
|
|
49
|
+
|
|
50
|
+
- name: Install dependencies
|
|
51
|
+
run: |
|
|
52
|
+
pip install -U pip
|
|
53
|
+
pip install -e .[dev]
|
|
54
|
+
|
|
55
|
+
- name: Run mypy
|
|
56
|
+
run: mypy src/postgresql_mcp
|
|
57
|
+
|
|
58
|
+
test:
|
|
59
|
+
name: Test (pytest)
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
services:
|
|
62
|
+
postgres:
|
|
63
|
+
image: postgres:16-alpine
|
|
64
|
+
env:
|
|
65
|
+
POSTGRES_USER: postgres
|
|
66
|
+
POSTGRES_PASSWORD: postgres
|
|
67
|
+
POSTGRES_DB: postgres
|
|
68
|
+
ports:
|
|
69
|
+
- 5432:5432
|
|
70
|
+
options: >-
|
|
71
|
+
--health-cmd "pg_isready -U postgres"
|
|
72
|
+
--health-interval 5s
|
|
73
|
+
--health-timeout 5s
|
|
74
|
+
--health-retries 10
|
|
75
|
+
env:
|
|
76
|
+
DB_BRIDGE_TEST_DSN: postgresql://postgres:***@localhost:5432/postgres
|
|
77
|
+
steps:
|
|
78
|
+
- name: Checkout
|
|
79
|
+
uses: actions/checkout@v4
|
|
80
|
+
|
|
81
|
+
- name: Set up Python
|
|
82
|
+
uses: actions/setup-python@v5
|
|
83
|
+
with:
|
|
84
|
+
python-version: "3.12"
|
|
85
|
+
cache: "pip"
|
|
86
|
+
|
|
87
|
+
- name: Install dependencies
|
|
88
|
+
run: |
|
|
89
|
+
pip install -U pip
|
|
90
|
+
pip install -e .[dev]
|
|
91
|
+
|
|
92
|
+
- name: Wait for PostgreSQL
|
|
93
|
+
run: |
|
|
94
|
+
for i in {1..30}; do
|
|
95
|
+
if pg_isready -h localhost -p 5432 -U postgres; then
|
|
96
|
+
echo "PostgreSQL is ready"
|
|
97
|
+
break
|
|
98
|
+
fi
|
|
99
|
+
sleep 1
|
|
100
|
+
done
|
|
101
|
+
|
|
102
|
+
- name: Run tests
|
|
103
|
+
run: pytest tests --cov=postgresql_mcp --cov-report=xml --cov-report=term-missing
|
|
104
|
+
|
|
105
|
+
- name: Upload coverage
|
|
106
|
+
uses: codecov/codecov-action@v4
|
|
107
|
+
with:
|
|
108
|
+
files: ./coverage.xml
|
|
109
|
+
fail_ci_if_error: false
|
|
110
|
+
name: db-bridge-coverage
|
|
111
|
+
|
|
112
|
+
build:
|
|
113
|
+
name: Build Package
|
|
114
|
+
runs-on: ubuntu-latest
|
|
115
|
+
needs: [lint, typecheck, test]
|
|
116
|
+
steps:
|
|
117
|
+
- name: Checkout
|
|
118
|
+
uses: actions/checkout@v4
|
|
119
|
+
|
|
120
|
+
- name: Set up Python
|
|
121
|
+
uses: actions/setup-python@v5
|
|
122
|
+
with:
|
|
123
|
+
python-version: "3.12"
|
|
124
|
+
cache: "pip"
|
|
125
|
+
|
|
126
|
+
- name: Install build dependencies
|
|
127
|
+
run: |
|
|
128
|
+
pip install -U pip build hatch
|
|
129
|
+
|
|
130
|
+
- name: Build package
|
|
131
|
+
run: python -m build
|
|
132
|
+
|
|
133
|
+
- name: Verify package
|
|
134
|
+
run: |
|
|
135
|
+
pip install dist/*.whl
|
|
136
|
+
airgap-db-bridge --help
|
|
137
|
+
|
|
138
|
+
- name: Upload artifacts
|
|
139
|
+
uses: actions/upload-artifact@v4
|
|
140
|
+
with:
|
|
141
|
+
name: dist
|
|
142
|
+
path: dist/
|
|
143
|
+
retention-days: 7
|
|
144
|
+
|
|
145
|
+
publish:
|
|
146
|
+
name: Publish to PyPI
|
|
147
|
+
runs-on: ubuntu-latest
|
|
148
|
+
needs: [build]
|
|
149
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
150
|
+
permissions:
|
|
151
|
+
id-token: write
|
|
152
|
+
contents: read
|
|
153
|
+
environment: pypi
|
|
154
|
+
steps:
|
|
155
|
+
- name: Download artifacts
|
|
156
|
+
uses: actions/download-artifact@v4
|
|
157
|
+
with:
|
|
158
|
+
name: dist
|
|
159
|
+
path: dist/
|
|
160
|
+
|
|
161
|
+
- name: Publish to PyPI
|
|
162
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
163
|
+
|
|
164
|
+
docker:
|
|
165
|
+
name: Build Docker Image
|
|
166
|
+
runs-on: ubuntu-latest
|
|
167
|
+
needs: [build]
|
|
168
|
+
if: github.event_name == 'release' && github.event.action == 'published'
|
|
169
|
+
steps:
|
|
170
|
+
- name: Checkout
|
|
171
|
+
uses: actions/checkout@v4
|
|
172
|
+
|
|
173
|
+
- name: Set up Docker Buildx
|
|
174
|
+
uses: docker/setup-buildx-action@v3
|
|
175
|
+
|
|
176
|
+
- name: Log in to Docker Hub
|
|
177
|
+
uses: docker/login-action@v3
|
|
178
|
+
with:
|
|
179
|
+
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
180
|
+
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
181
|
+
|
|
182
|
+
- name: Extract metadata
|
|
183
|
+
id: meta
|
|
184
|
+
uses: docker/metadata-action@v5
|
|
185
|
+
with:
|
|
186
|
+
images: ghcr.io/airgap-fleet/db-bridge
|
|
187
|
+
tags: |
|
|
188
|
+
type=semver,pattern={{version}}
|
|
189
|
+
type=semver,pattern={{major}}.{{minor}}
|
|
190
|
+
type=sha
|
|
191
|
+
type=raw,value=latest,enable={{is_default_branch}}
|
|
192
|
+
|
|
193
|
+
- name: Build and push
|
|
194
|
+
uses: docker/build-push-action@v5
|
|
195
|
+
with:
|
|
196
|
+
context: .
|
|
197
|
+
push: true
|
|
198
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
199
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
200
|
+
cache-from: type=gha
|
|
201
|
+
cache-to: type=gha,mode=max
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Virtual environments
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
env/
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
dist/
|
|
13
|
+
build/
|
|
14
|
+
*.egg-info/
|
|
15
|
+
|
|
16
|
+
# Test artifacts
|
|
17
|
+
.coverage
|
|
18
|
+
coverage.xml
|
|
19
|
+
htmlcov/
|
|
20
|
+
.pytest_cache/
|
|
21
|
+
|
|
22
|
+
# Type checking
|
|
23
|
+
.mypy_cache/
|
|
24
|
+
|
|
25
|
+
# Ruff
|
|
26
|
+
.ruff_cache/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.idea/
|
|
30
|
+
.vscode/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
|
|
34
|
+
# OS
|
|
35
|
+
.DS_Store
|
|
36
|
+
Thumbs.db
|
|
37
|
+
|
|
38
|
+
# Build artifacts
|
|
39
|
+
*.dxt
|
|
40
|
+
*.lock
|
|
41
|
+
|
|
42
|
+
# Environment
|
|
43
|
+
.env
|
|
44
|
+
.env.*
|
|
45
|
+
|
|
46
|
+
# Obsidian Vault - NEVER COMMIT PERSONAL DATA
|
|
47
|
+
.obsidian/
|
|
48
|
+
00_Master/
|
|
49
|
+
01_Obi-Wan/
|
|
50
|
+
02_Sub-Agents/
|
|
51
|
+
03_Context/
|
|
52
|
+
04_Daily_Logs/
|
|
53
|
+
05_Skills/
|
|
54
|
+
05_Studies/
|
|
55
|
+
Anakin/
|
|
56
|
+
July 2026/
|
|
57
|
+
AFaaS_MASTER_PACKAGE.md
|
|
58
|
+
BUSINESS_MODEL.md
|
|
59
|
+
AgentComms.md
|
|
60
|
+
INDEX.md
|
|
61
|
+
Skill Map.md
|
|
62
|
+
Chat Logs/
|
|
63
|
+
Decisions Log.md
|
|
64
|
+
"C:\Users\brook\reindex.py"
|
|
65
|
+
*.sqlite
|
|
66
|
+
*.db
|
|
67
|
+
check_db.py
|
|
68
|
+
fix_*.py
|
|
69
|
+
reindex_*.py
|
|
70
|
+
run_*.py
|
|
71
|
+
seed_*.py
|
|
72
|
+
temp.txt
|
|
73
|
+
test_verification.py
|
|
74
|
+
to-patch.md
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-08-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- FastMCP 3.x transport layer with stdio, SSE, and HTTP transports
|
|
12
|
+
- Bearer token authentication via TokenVerifier (compatible with Obsidian/Filesystem MCPs)
|
|
13
|
+
- Health check endpoint (`/health`) and server info endpoint (`/info`)
|
|
14
|
+
- Structured logging with structlog (JSON output, log levels)
|
|
15
|
+
- Pydantic v2 configuration via pydantic-settings (environment variable support)
|
|
16
|
+
- Lifespan management for connection pool initialization/cleanup
|
|
17
|
+
- Synchronous core module (no FastMCP dependencies) with async API
|
|
18
|
+
- 6 database tools: query, execute, list_tables, describe_table, run_migration, explain_analyze
|
|
19
|
+
- Parameterized query support for SQL injection prevention
|
|
20
|
+
- Read-only mode for secure deployments
|
|
21
|
+
- Connection pooling with configurable size
|
|
22
|
+
- Comprehensive unit test suite (39 tests, 78% coverage)
|
|
23
|
+
- Integration test suite (marked for manual execution with PostgreSQL)
|
|
24
|
+
- Multi-stage Dockerfile with non-root user
|
|
25
|
+
- Docker Compose for local development
|
|
26
|
+
- GitHub Actions CI/CD pipeline
|
|
27
|
+
- Full type safety with mypy strict mode
|
|
28
|
+
- 75%+ test coverage requirement
|
|
29
|
+
- **Stateless protocol (2026-07-28 / SEP-2575): no global session state, explicit config per request**
|
|
30
|
+
- **`create_core(config)` factory — fresh instance per request**
|
|
31
|
+
- **DXT bundle for one-click installation in Claude Desktop**
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- **BREAKING**: Core rewritten as synchronous module with async facade
|
|
35
|
+
- **BREAKING**: Server uses FastMCP 3.x (was 2.x)
|
|
36
|
+
- **BREAKING**: Authentication changed to TokenVerifier (was none)
|
|
37
|
+
- **BREAKING**: Configuration via pydantic-settings/env vars (was python-dotenv)
|
|
38
|
+
- Updated dependencies: asyncpg 0.29+, pydantic 2.10+, pydantic-settings 2.6+, structlog 25.1+
|
|
39
|
+
- Removed global `_core` singleton — stateless per-request instantiation
|
|
40
|
+
|
|
41
|
+
### Security
|
|
42
|
+
- Parameterized queries only (no string interpolation)
|
|
43
|
+
- Read-only mode blocks write operations
|
|
44
|
+
- Connection pooling with least privilege
|
|
45
|
+
- Bearer token authentication for all transports
|
|
46
|
+
- Audit logging for all operations
|
|
47
|
+
|
|
48
|
+
## [0.1.0] - 2026-08-07
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
- Initial release
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Multi-stage Dockerfile for DB Bridge
|
|
2
|
+
# ==============================================================================
|
|
3
|
+
# Stage 1: Builder - Install dependencies and build package
|
|
4
|
+
# ==============================================================================
|
|
5
|
+
FROM python:3.12-slim AS builder
|
|
6
|
+
|
|
7
|
+
WORKDIR /app
|
|
8
|
+
|
|
9
|
+
# Install build dependencies
|
|
10
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
11
|
+
gcc \
|
|
12
|
+
libpq-dev \
|
|
13
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
14
|
+
|
|
15
|
+
# Install uv for fast dependency management
|
|
16
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
|
17
|
+
|
|
18
|
+
# Copy project files
|
|
19
|
+
COPY pyproject.toml ./
|
|
20
|
+
COPY src/ ./src/
|
|
21
|
+
|
|
22
|
+
# Install dependencies and build package
|
|
23
|
+
RUN uv pip install --system --no-cache-dir -e .
|
|
24
|
+
|
|
25
|
+
# ==============================================================================
|
|
26
|
+
# Stage 2: Runtime - Minimal runtime image
|
|
27
|
+
# ==============================================================================
|
|
28
|
+
FROM python:3.12-slim AS runtime
|
|
29
|
+
|
|
30
|
+
WORKDIR /app
|
|
31
|
+
|
|
32
|
+
# Install runtime dependencies only
|
|
33
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
34
|
+
libpq5 \
|
|
35
|
+
ca-certificates \
|
|
36
|
+
&& rm -rf /var/lib/apt/lists/* \
|
|
37
|
+
&& groupadd -r mcp && useradd -r -g mcp -u 10001 -s /sbin/nologin mcp
|
|
38
|
+
|
|
39
|
+
# Copy installed package from builder
|
|
40
|
+
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
|
|
41
|
+
COPY --from=builder /usr/local/bin/db-bridge /usr/local/bin/db-bridge
|
|
42
|
+
|
|
43
|
+
# Create non-root user and set ownership
|
|
44
|
+
RUN chown -R mcp:mcp /app
|
|
45
|
+
|
|
46
|
+
USER mcp
|
|
47
|
+
|
|
48
|
+
# Environment variables
|
|
49
|
+
ENV DB_BRIDGE_TRANSPORT=stdio \
|
|
50
|
+
DB_BRIDGE_LOG_LEVEL=INFO \
|
|
51
|
+
DB_BRIDGE_POOL_MIN_SIZE=1 \
|
|
52
|
+
DB_BRIDGE_POOL_MAX_SIZE=10 \
|
|
53
|
+
DB_BRIDGE_QUERY_TIMEOUT=30 \
|
|
54
|
+
PYTHONUNBUFFERED=1 \
|
|
55
|
+
PYTHONDONTWRITEBYTECODE=1
|
|
56
|
+
|
|
57
|
+
# Health check
|
|
58
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
59
|
+
CMD python -c "import postgresql_mcp; print('healthy')" || exit 1
|
|
60
|
+
|
|
61
|
+
# Expose ports for SSE/HTTP transports
|
|
62
|
+
EXPOSE 8000
|
|
63
|
+
|
|
64
|
+
# Entry point
|
|
65
|
+
ENTRYPOINT ["db-bridge"]
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: airgap-db-bridge
|
|
3
|
+
Version: 1.0.3
|
|
4
|
+
Summary: Bridge your AI assistant to PostgreSQL databases — query, execute, migrate, and analyze without cloud dependencies
|
|
5
|
+
Author-email: AFaaS Team <team@afaas.co.uk>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: bridge,database,fastmcp,postgresql
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Database
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Requires-Python: >=3.11
|
|
16
|
+
Requires-Dist: asyncpg>=0.29
|
|
17
|
+
Requires-Dist: fastmcp<4.0,>=3.4
|
|
18
|
+
Requires-Dist: pydantic-settings>=2.6
|
|
19
|
+
Requires-Dist: pydantic>=2.10
|
|
20
|
+
Requires-Dist: structlog>=25.1
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
27
|
+
Requires-Dist: uv>=0.5; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# Airgap DB Bridge
|
|
31
|
+
|
|
32
|
+
Bridge your AI assistant to PostgreSQL databases — query, execute, migrate, and analyze without cloud dependencies.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- **6 Database Tools**: Query, Execute, List Tables, Describe Table, Run Migration, Explain Analyze
|
|
37
|
+
- **Security First**: Parameterized queries only, read-only mode, connection pooling, audit logging
|
|
38
|
+
- **Production Ready**: Async connection pooling, configurable timeouts, structured logging
|
|
39
|
+
- **Developer Experience**: Type-safe Pydantic models, comprehensive tests, MCP Inspector compatible
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
### From PyPI (when published)
|
|
44
|
+
```bash
|
|
45
|
+
pip install airgap-db-bridge
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### From Source
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/airgap-fleet/db-bridge.git
|
|
51
|
+
cd db-bridge
|
|
52
|
+
pip install -e .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Docker
|
|
56
|
+
```bash
|
|
57
|
+
docker pull ghcr.io/airgap-fleet/db-bridge:latest
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
### 1. Configure Environment
|
|
63
|
+
```bash
|
|
64
|
+
cp .env.example .env
|
|
65
|
+
# Edit .env with your PostgreSQL connection details
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Run Server
|
|
69
|
+
```bash
|
|
70
|
+
# Direct execution
|
|
71
|
+
airgap-db-bridge
|
|
72
|
+
|
|
73
|
+
# Or with Docker Compose (includes PostgreSQL)
|
|
74
|
+
docker-compose up -d
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Configure MCP Client
|
|
78
|
+
Add to your MCP client configuration (Claude Desktop, Cursor, VS Code, etc.):
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"postgresql": {
|
|
83
|
+
"command": "airgap-db-bridge",
|
|
84
|
+
"env": {
|
|
85
|
+
"DB_BRIDGE_DSN": "postgresql://user:***@localhost:5432/db"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
| Environment Variable | Default | Description |
|
|
95
|
+
|---------------------|---------|-------------|
|
|
96
|
+
| `DB_BRIDGE_DSN` | `postgresql://postgres:***@localhost:5432/postgres` | PostgreSQL connection string |
|
|
97
|
+
| `DB_BRIDGE_POOL_SIZE` | `10` | Connection pool size (1-100) |
|
|
98
|
+
| `DB_BRIDGE_READ_ONLY` | `false` | Enable read-only mode (blocks write operations) |
|
|
99
|
+
| `DB_BRIDGE_QUERY_TIMEOUT` | `30.0` | Query timeout in seconds (0-300) |
|
|
100
|
+
| `DB_BRIDGE_LOG_LEVEL` | `INFO` | Structured logging level |
|
|
101
|
+
|
|
102
|
+
## Tools Reference
|
|
103
|
+
|
|
104
|
+
| Tool | Description | Parameters | Read-Only Safe |
|
|
105
|
+
|------|-------------|------------|----------------|
|
|
106
|
+
| `query` | Execute parameterized SELECT query | `sql` (string), `params` (array, optional) | ✅ |
|
|
107
|
+
| `execute` | Execute INSERT/UPDATE/DELETE | `sql` (string), `params` (array, optional) | ❌ |
|
|
108
|
+
| `list_tables` | List tables in a schema | `schema` (string, default: "public") | ✅ |
|
|
109
|
+
| `describe_table` | Get table structure (columns, indexes, constraints) | `table` (string), `schema` (string, default: "public") | ✅ |
|
|
110
|
+
| `run_migration` | Run DDL statements in transaction | `sql` (string) | ❌ |
|
|
111
|
+
| `explain_analyze` | Get query execution plan with costs | `sql` (string), `params` (array, optional) | ✅ |
|
|
112
|
+
|
|
113
|
+
## Usage Examples
|
|
114
|
+
|
|
115
|
+
### Query Data
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"tool": "query",
|
|
119
|
+
"arguments": {
|
|
120
|
+
"sql": "SELECT * FROM users WHERE age > $1 AND active = $2",
|
|
121
|
+
"params": [18, true]
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Insert Data
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"tool": "execute",
|
|
130
|
+
"arguments": {
|
|
131
|
+
"sql": "INSERT INTO users (name, email, age) VALUES ($1, $2, $3)",
|
|
132
|
+
"params": ["John Doe", "john@example.com", 30]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### List Tables
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"tool": "list_tables",
|
|
141
|
+
"arguments": {
|
|
142
|
+
"schema": "public"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Describe Table Structure
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"tool": "describe_table",
|
|
151
|
+
"arguments": {
|
|
152
|
+
"table": "users",
|
|
153
|
+
"schema": "public"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Run Migration
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"tool": "run_migration",
|
|
162
|
+
"arguments": {
|
|
163
|
+
"sql": "CREATE TABLE products (id SERIAL PRIMARY KEY, name TEXT NOT NULL, price DECIMAL(10,2)); CREATE INDEX idx_products_name ON products(name);"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Analyze Query Plan
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"tool": "explain_analyze",
|
|
172
|
+
"arguments": {
|
|
173
|
+
"sql": "SELECT * FROM users JOIN orders ON users.id = orders.user_id WHERE users.id = $1",
|
|
174
|
+
"params": [1]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Security Model
|
|
180
|
+
|
|
181
|
+
### Parameterized Queries Only
|
|
182
|
+
All SQL execution uses parameterized queries (`$1`, `$2`, etc.). String concatenation or interpolation is **not supported** — this prevents SQL injection by design.
|
|
183
|
+
|
|
184
|
+
### Read-Only Mode
|
|
185
|
+
Set `DB_BRIDGE_READ_ONLY=true` to disable:
|
|
186
|
+
- `execute` (INSERT/UPDATE/DELETE)
|
|
187
|
+
- `run_migration` (DDL)
|
|
188
|
+
|
|
189
|
+
Read operations (`query`, `list_tables`, `describe_table`, `explain_analyze`) remain available.
|
|
190
|
+
|
|
191
|
+
### Connection Pooling
|
|
192
|
+
- Configurable pool size (1-100 connections)
|
|
193
|
+
- Automatic connection lifecycle management
|
|
194
|
+
- Query timeout enforcement
|
|
195
|
+
|
|
196
|
+
### Audit Logging
|
|
197
|
+
All operations are logged with structured JSON including:
|
|
198
|
+
- Operation type and parameters (sanitized)
|
|
199
|
+
- Execution time
|
|
200
|
+
- Row counts affected
|
|
201
|
+
- Error details (if any)
|
|
202
|
+
|
|
203
|
+
## Development
|
|
204
|
+
|
|
205
|
+
### Prerequisites
|
|
206
|
+
- Python 3.11+
|
|
207
|
+
- PostgreSQL 14+ (for local development)
|
|
208
|
+
- uv (recommended) or pip
|
|
209
|
+
|
|
210
|
+
### Setup
|
|
211
|
+
```bash
|
|
212
|
+
# Install uv if not present
|
|
213
|
+
pip install uv
|
|
214
|
+
|
|
215
|
+
# Create virtual environment and install dependencies
|
|
216
|
+
uv sync --dev
|
|
217
|
+
|
|
218
|
+
# Run tests
|
|
219
|
+
uv run pytest
|
|
220
|
+
|
|
221
|
+
# Type check
|
|
222
|
+
uv run mypy src/postgresql_mcp
|
|
223
|
+
|
|
224
|
+
# Lint and format
|
|
225
|
+
uv run ruff check .
|
|
226
|
+
uv run ruff format .
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Running Tests with Local PostgreSQL
|
|
230
|
+
```bash
|
|
231
|
+
# Start PostgreSQL (Docker)
|
|
232
|
+
docker run -d --name pg-test -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:16
|
|
233
|
+
|
|
234
|
+
# Run tests
|
|
235
|
+
DB_BRIDGE_TEST_DSN=postgresql://postgres:***@localhost:5432/postgres uv run pytest
|
|
236
|
+
|
|
237
|
+
# Cleanup
|
|
238
|
+
docker rm -f pg-test
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### MCP Inspector
|
|
242
|
+
```bash
|
|
243
|
+
npx @modelcontextprotocol/inspector uv run airgap-db-bridge
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Architecture
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
db-bridge/
|
|
250
|
+
├── src/postgresql_mcp/
|
|
251
|
+
│ ├── __init__.py # Package exports
|
|
252
|
+
│ ├── models.py # Pydantic models (requests/responses/config)
|
|
253
|
+
│ ├── core.py # Business logic (asyncpg, zero FastMCP imports)
|
|
254
|
+
│ └── server.py # FastMCP app, tool registration, lifespan
|
|
255
|
+
├── tests/
|
|
256
|
+
│ ├── conftest.py # Test fixtures and setup
|
|
257
|
+
│ ├── test_models.py # Model validation tests
|
|
258
|
+
│ ├── test_core.py # Core business logic tests
|
|
259
|
+
│ └── test_tools.py # MCP tool integration tests
|
|
260
|
+
├── .github/workflows/ci.yml # CI/CD pipeline
|
|
261
|
+
├── Dockerfile # Multi-stage container build
|
|
262
|
+
├── docker-compose.yml # Local development stack
|
|
263
|
+
├── pyproject.toml # Project configuration (hatch)
|
|
264
|
+
└── README.md # This file
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Design Principles
|
|
268
|
+
|
|
269
|
+
1. **Separation of Concerns**: `core.py` contains zero FastMCP imports — fully testable in isolation
|
|
270
|
+
2. **Type Safety**: Pydantic v2 for all boundaries, mypy strict mode
|
|
271
|
+
3. **Async First**: asyncpg for non-blocking database operations
|
|
272
|
+
4. **Security by Default**: Parameterized queries, read-only mode, least privilege
|
|
273
|
+
5. **Observability**: Structured JSON logging, execution timing, audit trails
|
|
274
|
+
|
|
275
|
+
## CI/CD Pipeline
|
|
276
|
+
|
|
277
|
+
The GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every push/PR:
|
|
278
|
+
|
|
279
|
+
1. **Lint** — ruff check + format
|
|
280
|
+
2. **Type Check** — mypy strict
|
|
281
|
+
3. **Test** — pytest with PostgreSQL service, coverage ≥90%
|
|
282
|
+
4. **Build** — hatch build + twine verify
|
|
283
|
+
5. **Publish** — PyPI on release (trusted publishing)
|
|
284
|
+
6. **Docker** — Multi-platform image on release
|
|
285
|
+
|
|
286
|
+
## License
|
|
287
|
+
|
|
288
|
+
MIT License — see LICENSE file for details.
|
|
289
|
+
|
|
290
|
+
## Contributing
|
|
291
|
+
|
|
292
|
+
1. Fork the repository
|
|
293
|
+
2. Create a feature branch
|
|
294
|
+
3. Make changes with tests
|
|
295
|
+
4. Ensure CI passes (lint, typecheck, test, coverage)
|
|
296
|
+
5. Submit a pull request
|
|
297
|
+
|
|
298
|
+
## Support
|
|
299
|
+
|
|
300
|
+
- Issues: GitHub Issues
|
|
301
|
+
- Documentation: This README + inline docstrings
|
|
302
|
+
- MCP Specification: https://modelcontextprotocol.io
|