MCP Verify Low-Cost Hybrid Read-Only Plan
Date: 2026-07-23
Purpose
MCP Verify should keep the public product experience while cutting Fly.io spend from the previous roughly $130/month operating model to a single-digit or low-teens monthly target.
The preferred direction is not a fully static site and not a full 45 GB SQLite database in the cloud. The preferred direction is a small dynamic Fly web app backed by a richer curated SQLite snapshot generated daily on the on-prem Linux server.
Current Findings
- The restored on-prem
cloud_mcp_verifyPostgres database is about 22 GB. - A full SQLite export of all 39 public tables is 45,915,111,424 bytes, or 42.76 GiB.
- The full SQLite export contains 10,345,626 rows.
- Major full-export row counts:
servers: 54,005validation_runs: 4,476,783jobs: 5,180,070analytics_events: 592,675materialized_payloads: 4- The current curated public snapshot is about 31 MB and includes 54,005 Verify server rows.
- The current Fly Verify app is running a lightweight
shared-cpu-1x512 MB read-only web container. - The current lightweight Verify page is too stripped down because the snapshot/runtime only exposes a minimal server table and a simple home/search page.
Cost Assumptions
Fly cost drivers to avoid:
- Managed Postgres for always-on cloud state.
- Always-on worker processes.
- Fly-hosted Prometheus, Loki, Grafana, and log shippers.
- Large Fly volumes. Fly volumes bill even when attached machines are stopped.
- Serving a 42.76 GiB SQLite database through the web tier.
Cheap components to keep:
- One or two small Fly shared CPU Machines with
min_machines_running = 0. - Tigris object storage for small curated snapshots.
- On-prem Linux server for all heavy processing and mutable state.
Relevant public pricing references checked on 2026-07-23:
- Fly volumes: https://fly.io/docs/about/pricing/
- Fly cost management examples: https://fly.io/docs/about/cost-management/
- Tigris storage: https://www.tigrisdata.com/pricing/
Target Monthly Cost
Target: approximately $5-$15/month.
Expected cloud resources:
verify.sentinelsignal.io: oneshared-cpu-1x, 512 MB Fly Machine, auto-start/auto-stop.- Optional
sentinelsignal.ioAPI/public web app: oneshared-cpu-1x, 512 MB Fly Machine, auto-start/auto-stop. - Tigris bucket for daily curated snapshots, likely under 5 GB retained.
- No normal-operation Fly Postgres.
- No normal-operation Fly monitoring volumes or monitoring apps.
Architecture
On-Prem Linux Server
The on-prem server is the source of truth and does all expensive work:
- Runs Postgres database
cloud_mcp_verify. - Runs MCP Verify ingestion and validation workers.
- Runs ranking, shortlist, compare, badge, and profile materialization.
- Stores full historical data, including
jobs,analytics_events, and completevalidation_runs. - Builds a daily curated SQLite snapshot for cloud serving.
- Uploads snapshot artifacts to Tigris.
- Optionally signals/restarts Fly read-only web Machines after successful publish.
Tigris Object Storage
Tigris stores immutable and latest pointers:
prod/latest/manifest.json
prod/latest/snapshot.sqlite
prod/snapshots/YYYY-MM-DD/manifest.json
prod/snapshots/YYYY-MM-DD/snapshot.sqlite
The snapshot should be a curated serving artifact, not a faithful dump of the full operational database.
Fly Read-Only Web Tier
Fly runs a tiny dynamic FastAPI app:
- Downloads the latest snapshot at startup.
- Verifies manifest schema version and SHA-256.
- Opens SQLite read-only with immutable mode.
- Serves public HTML and JSON routes dynamically from local cached SQLite.
- Refreshes or reloads when a newer snapshot is published.
- Keeps serving the previous good snapshot if the latest artifact fails validation.
The Fly tier should not:
- Run workers.
- Store durable state.
- Accept expensive writes.
- Hold a database volume.
- Depend on live Fly Postgres.
Functional Goal
Keep most public MCP Verify functionality:
- Home/search page.
- Server detail pages.
- Server search and suggestions.
- Server JSON detail endpoints.
- Badge SVG endpoints.
- Ranking and shortlist pages.
- Ranking and shortlist JSON endpoints.
- Compare page for selected servers.
- Compare JSON endpoints for precomputed/selected server summaries.
- Trust index/latest feed where it can be precomputed.
- Static docs/product pages.
- Discovery metadata such as server card and well-known endpoints.
Disable, redirect, or defer workflows that require mutable state:
- Claim creation and verification.
- Owner console writes.
- Revalidation requests.
- Authenticated validation credential storage.
- Billing checkout and Stripe webhooks.
- Admin dashboards.
- Analytics event writes.
- Worker/job mutation endpoints.
For disabled write paths, the public UI should show clear product-appropriate states instead of raw 503 pages.
Curated Snapshot Design
Core Tables
The snapshot should include:
publication_metadataworkflow_catalogverify_serversverify_server_versionsverify_recent_validation_runsverify_materialized_payloadsverify_server_profilesverify_badgesverify_search_indexverify_compare_cardsverify_route_redirects
verify_servers
Include the full public catalog fields required for list, search, profile cards, badges, and JSON detail:
server_idnamespacenameidentifiertitledescriptionhomepage_urldocs_urlicon_urlsupport_urlregistry_sourceregistry_identifierremote_urlserver_card_urllatest_versioncurrent_statuscurrent_scoretransport_typehas_oauthhas_dcrhas_promptstool_countcurrent_score_components_jsoncurrent_validation_schema_versionlast_validated_atcreated_atupdated_at
Indexes:
(namespace, name)uniquecurrent_score DESClast_validated_at DESClower(title)lower(name)- optional FTS5 index for search
verify_recent_validation_runs
Do not publish all 4.48M validation rows.
Publish only:
- latest validation run per server
- optionally last 3-10 validation runs per server
- optionally more history for top/claimed/high-risk servers
Fields:
validation_idserver_idserver_version_idvalidation_profilestatussummary_statustransport_typelatency_msfailures_jsonchecks_jsonscore_components_jsonvalidation_schema_versionstarted_atcompleted_at
verify_materialized_payloads
Publish existing precomputed payloads from Postgres:
shortlists:8rankings:8global_digest:daily:20global_digest:weekly:20
Store:
payload_namecache_keyschema_versionpayload_jsongenerated_atupdated_at
verify_server_profiles
Precompute one compact public profile JSON per server.
This avoids reimplementing every full ORM-derived detail calculation in the Fly runtime.
Fields:
server_idnamespacenameprofile_jsongenerated_at
The profile JSON should contain all data needed by:
/servers/{namespace}/{name}/v1/servers/{namespace}/{name}/v1/servers/{namespace}/{name}/trust-summary/v1/servers/{namespace}/{name}/evidence
verify_badges
Precompute common badge states:
- score badge
- freshness badge
- OAuth badge
- write-safe badge
- enterprise-ready badge, if supported
The Fly runtime can render SVG dynamically from these rows or serve pre-rendered SVG text.
verify_search_index
Store normalized search fields:
server_ididentifiertitledescriptionregistry_sourcetransport_typecurrent_scorecurrent_statuslast_validated_atsearch_text
Use either:
- SQLite FTS5 if available in the runtime image, or
- deterministic
LIKEqueries over normalized text for the first iteration.
Route Plan
Serve From Snapshot
GET /GET /servers/{namespace}/{name}GET /v1/serversGET /v1/searchGET /v1/search/suggestionsGET /v1/servers/{namespace}/{name}GET /v1/servers/{namespace}/{name}/validationsGET /v1/servers/{namespace}/{name}/trust-summaryGET /v1/servers/{namespace}/{name}/evidenceGET /badge/{namespace}/{name}.svgGET /v1/servers/{namespace}/{name}/badgeGET /v1/badges/{namespace}/{name}/metadataGET /shortlistsGET /rankingsGET /v1/shortlistsGET /v1/rankingsGET /v1/exports/catalogGET /v1/exports/shortlistsGET /v1/exports/rankingsGET /compareGET /compare.jsonGET /v1/compareGET /trust-indexGET /v1/trust-index/latestGET /server-card.jsonGET /.well-known/server-card.json
Static Product Pages
Serve as static HTML or lightweight rendered templates:
/pricing/license/commercial/badges/demo/github-action/ecosystem/verify-intelligence-api/mcp-server-security-scanner/best-mcp-servers-for-chatgpt/compare-mcp-servers/healthcare-mcp-servers/oauth-mcp-server-directory
Disabled Or Redirected
Return product-aware disabled pages or JSON:
POST /claim/startPOST /claim/intentPOST /v1/servers/{namespace}/{name}/claimsPOST /v1/claims/{claim_id}/verifyPOST /v1/servers/{namespace}/{name}/revalidatePOST /v1/servers/{namespace}/{name}/credentialsPOST /v1/servers/{namespace}/{name}/billingPOST /webhooks/stripePOST /api/analytics/events- admin routes
Future option: proxy a small set of write routes to on-prem over a secure tunnel if needed.
Implementation Phases
Phase 1: Snapshot Expansion
- Extend
app_publication.export_sqlitewith richer MCP Verify public tables. - Add row-count and schema validation for new tables.
- Keep secrets and private fields excluded.
- Keep
jobs, rawanalytics_events, and full historicalvalidation_runsexcluded. - Add unit tests for snapshot schema and redaction boundaries.
Exit criteria:
- Snapshot contains enough data for search, detail, badges, rankings, and compare.
- Snapshot remains reasonably sized.
- Snapshot validates locally.
Phase 2: Read-Only Repository
- Extend
app_public_web.repository.PublicSnapshotRepository. - Add methods for:
- server search/list
- server detail/profile lookup
- validation history lookup
- materialized payload lookup
- badge metadata lookup
- compare card lookup
Exit criteria:
- Repository tests cover expected route data.
- Missing server behavior is deterministic.
Phase 3: Public Verify Routes
- Replace the stripped
/and/verifypage with a richer Verify home/search page. - Add server detail pages.
- Add JSON list/detail/search endpoints.
- Add badge SVG endpoint.
- Add rankings/shortlists pages and APIs.
- Add compare page/API backed by snapshot data.
- Preserve read-only runtime headers.
Exit criteria:
- Existing public links return useful pages or JSON.
- Mutating endpoints are intentionally disabled with appropriate responses.
Phase 4: Publishing And Rollback
- Update daily on-prem publisher to produce the richer snapshot.
- Upload latest and versioned artifacts.
- Keep at least seven published snapshots for rollback.
- Add a rollback script or documented command to repoint
prod/latest/manifest.json. - Restart or signal Fly web apps after publish.
Exit criteria:
- Daily timer publishes and validates the richer snapshot.
- Fly can boot from the latest snapshot.
- Fly keeps serving the previous good snapshot if latest is bad.
Phase 5: Cost Cleanup
After the richer read-only runtime is stable:
- Delete Fly Postgres volume if no longer needed.
- Delete old monitoring volumes if no longer needed.
- Keep monitoring on-prem or use lightweight external uptime checks.
- Confirm all unnecessary apps remain suspended or deleted.
Exit criteria:
- No cloud database dependency in normal operation.
- No unexpected Fly volumes remain.
- Monthly bill is trending toward the target range.
Validation Plan
Local:
- Build snapshot from on-prem Postgres.
- Run snapshot validator.
- Run route tests against local FastAPI TestClient.
- Smoke-test representative pages:
//v1/servers/servers/{namespace}/{name}/badge/{namespace}/{name}.svg/rankings/shortlists/compare
On Fly:
- Deploy one read-only Machine.
- Verify
/readyzsnapshot ID and row counts. - Verify core pages return 200.
- Verify disabled write paths do not mutate state.
- Verify memory use stays within 512 MB.
- Verify startup succeeds after cold auto-start.
Rollback Plan
If the richer runtime fails:
- Repoint Tigris
prod/latest/manifest.jsonto the previous known-good snapshot. - Restart the Fly read-only Machine.
- If needed, redeploy the current stripped read-only runtime.
- Keep the on-prem Postgres database unchanged.
Risks
- Reimplementing every original dynamic page exactly may take longer than expected.
- Some original pages depend on ORM-derived calculations that need to be precomputed.
- Search quality may be weaker until FTS5 or a proper search index is added.
- Mutating product flows need a separate decision: disabled, on-prem tunnel, or tiny write service.
- Snapshot size could grow if too much validation history is included.
Decision Record
Chosen:
- Rich curated SQLite snapshot.
- Small dynamic read-only Fly runtime.
- On-prem source of truth and workers.
Rejected:
- Full 42.76 GiB SQLite database on Fly.
- Object-storage-mounted SQLite as the primary serving database.
- Fully static site as the primary product surface.
- Always-on Fly Postgres and monitoring stack for normal operation.
Next Implementation Step
Do not start implementation until this plan is accepted.
The first implementation PR should only expand the snapshot schema and validator, with tests and a measured artifact size. The web route expansion should be a separate follow-up PR.