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.
@@ -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