Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Alembic DSN format handling** — `alembic/env.py` now converts psycopg DSN format (`host=X dbname=Y user=Z password=W`) to SQLAlchemy URL format via `dsn_to_sqlalchemy_url()` helper. Delegates DSN parsing to `psycopg.conninfo.conninfo_to_dict()` for correctness; forwards extra params (sslmode, connect_timeout, etc.) as URL query string. Fixes migration/backfill failures on production where `AWARENESS_DATABASE_URL` uses DSN format.
- **Deploy script** — `scripts/holodeck/deploy.sh` maintenance mode no longer passes `upgrade head` positional args to `mcp-awareness-migrate` (which uses `--flags`, not positional args).
- **README** — fix documented `mcp-awareness-migrate upgrade head` syntax to match actual CLI interface (`mcp-awareness-migrate` with no positional args).
- **Docs** — document that `AWARENESS_DATABASE_URL` accepts both URL and DSN formats, and that DSN values must be quoted in env files to prevent shell space-splitting. Updated in README, data dictionary, `migrate.py` error message, and `alembic/env.py` error message.

## [0.16.2] - 2026-04-09

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ The server is running on port 8420. Point any MCP client at `http://localhost:84
| `AWARENESS_TRANSPORT` | `stdio` | Transport: `stdio` or `streamable-http` |
| `AWARENESS_HOST` | `0.0.0.0` | Bind address (HTTP mode) |
| `AWARENESS_PORT` | `8420` | Port (HTTP mode) |
| `AWARENESS_DATABASE_URL` | _(required)_ | PostgreSQL connection string. Example: `postgresql://user:pass@localhost:5432/awareness` |
| `AWARENESS_DATABASE_URL` | _(required)_ | PostgreSQL connection string. Accepts URL format (`postgresql://user:pass@host:5432/db`) or psycopg DSN format (`host=X dbname=Y user=Z password=W`). **If using DSN format in an env file, quote the value** — unquoted spaces cause the shell to split it into separate assignments. Example: `AWARENESS_DATABASE_URL="host=db dbname=awareness user=awareness password=secret"` |
| `AWARENESS_MOUNT_PATH` | _(none)_ | Secret path prefix for access control (e.g., `/my-secret`). When set, only `/<secret>/mcp` is served; all other paths return 404. Use with a Cloudflare WAF rule. |

#### Embedding (optional)
Expand Down
4 changes: 3 additions & 1 deletion alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
if not database_url:
raise ValueError(
"AWARENESS_DATABASE_URL environment variable is required. "
"Example: postgresql+psycopg://awareness:awareness-dev@localhost:5432/awareness"
'Example (URL): postgresql+psycopg://user:pass@localhost:5432/awareness '
'Example (DSN): "host=localhost dbname=awareness user=user password=pass" '
"Note: DSN values with spaces must be quoted in env files."
)

# Normalise to a SQLAlchemy-compatible URL. Production deployments often
Expand Down
2 changes: 1 addition & 1 deletion docs/data-dictionary.md
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ Use cases: decision → context, intention → action, note → note ("see also"
- **WAL level:** `wal_level=logical` configured for Debezium CDC readiness and logical replication
- **Replication slots:** `max_replication_slots=4` for future replication/CDC
- **Background cleanup:** Daemon thread with its own psycopg connection, debounced (10s), with alive-check guard to prevent thread accumulation
- **Connection string:** Configured via `AWARENESS_DATABASE_URL` (e.g., `postgresql://user:pass@localhost:5432/awareness`)
- **Connection string:** Configured via `AWARENESS_DATABASE_URL`. Accepts URL format (`postgresql://user:pass@host:5432/db`) or psycopg DSN format (`host=X dbname=Y user=Z password=W`). If using DSN format in an env file sourced by the shell, **the value must be quoted** to prevent space-splitting (e.g., `AWARENESS_DATABASE_URL="host=db dbname=awareness user=u password=p"`)
- **Docker image:** `pgvector/pgvector:pg17` (PostgreSQL 17 with pgvector pre-installed)
- **Schema migrations:** Managed by Alembic (raw SQL, no ORM). Migration files in `alembic/versions/`. Run `mcp-awareness-migrate` or `alembic upgrade head`. Version tracked in `alembic_version` table.

Expand Down
10 changes: 9 additions & 1 deletion src/mcp_awareness/migrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,15 @@ def main() -> None:
if not database_url:
print("Error: AWARENESS_DATABASE_URL is required.", file=sys.stderr)
print(
"Example: AWARENESS_DATABASE_URL=postgresql://user:pass@localhost:5432/awareness",
"Example (URL): AWARENESS_DATABASE_URL=postgresql://user:pass@localhost:5432/awareness",
file=sys.stderr,
)
print(
'Example (DSN): AWARENESS_DATABASE_URL="host=localhost dbname=db user=u password=p"',
file=sys.stderr,
)
print(
"Note: DSN values with spaces must be quoted in env files.",
file=sys.stderr,
)
sys.exit(1)
Expand Down
29 changes: 29 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,3 +784,32 @@ def test_set_password_via_main(
assert row is not None
assert row["password_hash"] is not None
cur.execute("DELETE FROM users WHERE id = %s", ("pw-main-user",))


class TestMigrateMain:
"""Tests for mcp-awareness-migrate CLI entry point."""

def test_missing_database_url_exits(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""Missing AWARENESS_DATABASE_URL prints usage and exits 1."""
monkeypatch.delenv("AWARENESS_DATABASE_URL", raising=False)
monkeypatch.setattr("sys.argv", ["mcp-awareness-migrate"])
from mcp_awareness.migrate import main

with pytest.raises(SystemExit) as exc_info:
main()
assert exc_info.value.code == 1

def test_missing_database_url_shows_both_formats(
self, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
) -> None:
"""Error message shows both URL and DSN examples with quoting note."""
monkeypatch.delenv("AWARENESS_DATABASE_URL", raising=False)
monkeypatch.setattr("sys.argv", ["mcp-awareness-migrate"])
from mcp_awareness.migrate import main

with pytest.raises(SystemExit):
main()
captured = capsys.readouterr()
assert "Example (URL):" in captured.err
assert "Example (DSN):" in captured.err
assert "quoted" in captured.err.lower()