A small FastMCP notes server prepared for deployment on Prefect Horizon, with a production Supabase Postgres database managed by Pulumi.
Horizon owns the public deployment boundary: GitHub builds, TLS, OAuth, access control, scaling, preview environments, and request observability. The repository only owns the MCP tools and their application data.
MCP client -> Horizon OAuth gateway -> FastMCP server -> Supabase Postgres
The five tools create, list, read, update, and delete notes in one shared workspace. Horizon decides who can reach that workspace. The server deliberately does not trust undocumented identity headers or pretend that gateway users map to application-level note owners.
-
Push this repository to GitHub.
-
Sign in at horizon.prefect.io and connect the GitHub repository.
-
Create a server with these values:
- Server name:
horizon-notesor another available name - Entrypoint:
server.py:mcp - Authentication: enabled
- Environment variable:
FASTMCP_STATELESS_HTTP=true
- Server name:
-
Provision Supabase by following
infra/README.md, then add the secret PulumidatabaseUrloutput to Horizon asDATABASE_URL. -
Deploy the server. Horizon detects
pyproject.toml, installs the Python dependencies, and publishes an endpoint such ashttps://horizon-notes.fastmcp.app/mcp. -
Test every tool in Horizon Inspector or ChatMCP before connecting another client.
Every push to the configured production branch triggers a deployment. Pull requests receive preview deployments, so this branch can be pushed and opened as a PR before it is merged.
Use the connection snippet produced by Horizon. For the included Claude Code plugin:
export SQLITE_NOTES_MCP_URL=https://your-server-name.fastmcp.app/mcp
claude plugin marketplace add "$PWD" --scope project
claude plugin install sqlite-notes@mcp-auth-local --scope projectOpen /mcp, choose sqlite-notes, and complete the Horizon sign-in flow.
Local HTTP mode intentionally has no authentication. It is for development on loopback only.
uv sync
uv run horizon-notesThe MCP endpoint is http://127.0.0.1:8000/mcp. Without DATABASE_URL, local development falls
back to SQLite at MCP_DATABASE_PATH. Optional variables are documented in .env.example.
Verify the exact object Horizon imports:
uv run fastmcp inspect server.py:mcpuv run pytestDATABASE_URL: secret Supabase transaction-pooler URL used by the deployed MCP.FASTMCP_STATELESS_HTTP=true: enables stateless HTTP for Horizon autoscaling.MCP_DATABASE_PATH: optional SQLite path for local development only.MCP_HOSTandMCP_PORT: optional local server bind settings.
Pulumi uses SUPABASE_ACCESS_TOKEN to provision the database. The infrastructure project uses an
ignored local filesystem state backend, so it does not require a Pulumi account, Pulumi Cloud, or
PULUMI_ACCESS_TOKEN. None of the provisioning credentials belong in Horizon. SUPABASE_URL, anon
keys, and service-role keys are not needed because the service uses a direct Postgres connection.
Production notes live in the private horizon_notes Postgres schema, outside Supabase's default
Data API schemas. The MCP creates the schema and table idempotently at startup. Local SQLite remains
available only as a zero-configuration development fallback.
Horizon authentication protects the MCP endpoint. It does not by itself make rows private to each user, so this server correctly describes the notes as shared.