lifesaver-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@lifesaver-mcpPull the work order list report for last month."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
lifesaver-mcp
Sales and customer-retention tooling for picture-framing stores on Lifesaver Software: one website for every store, fed by two kinds of data source.
Dir | What |
The website (React + FastAPI, Cloud Run): all-stores overview, a dashboard and a report builder / SQL console per store, and for on-prem stores an in-store detail section. Also the MCP server for Claude over every store ( | |
Every store: id, name, tier ( | |
Cloud tier. Everything that talks to Lifesaver Cloud ( | |
On-prem tier. Turns a store's own LifeSaver data (uploaded to Google Drive by the owner) into | |
Store-independent: visits, the five retention KPIs, the SQLite tables every store writes, the store registry ( | |
The query layer behind the Reports tab and the MCP tools: named dimensions/metrics over each store's data, plus the cloud and pooled report DBs ( | |
| Offline suite, split as |
Dependencies only point inward: cloud and sqlite_extract import core;
core imports neither. Background: cloud/spec.md (phases 1-2)
and docs/kpi-design.md (the KPI design; its renderer has
since moved into webapp/).
How it fits together
CLOUD STORES (one lsscloud account each) ON-PREM STORES (LifeSaver on their own machine)
lsscloud.com ── Work Order List report owner drags LifeSaver folder / zip
│ into a shared Google Drive folder
│ refresh.yml (daily 09:17 UTC, one job │
│ per cloud store in stores.toml) │ mason-ingest.yml (every 6 h)
└─▶ cloud.warehouse.job sync └─▶ sqlite_extract/ingest.sh
gs://frame-shop-511616-kpi-warehouse/ gs://frame-shop-511616-lifesaver-data/
stores/<id>/warehouse.db lifesaver.sqlite
│ │
└───────────────┬──────────────────────────────┘
▼ (re-read every 10 min; on-prem KPIs derived on arrival)
webapp (Cloud Run "frame-shop")
/ all stores: side by side + pooled
/stores/<id> one store + its Reports (+ in-store detail if on-prem)
/mcp/<token> the MCP server for Claude: every store, or all pooledGitHub Actions drive everything; all authenticate to GCP as gha-deployer
via Workload Identity Federation (no keys):
Workflow | When | Does | Setup |
push to | test (incl. frontend build) → website image to GHCR → redeploy the website (and with it the MCP server) | ||
daily 09:17 UTC | every cloud store: pull new report rows → recompute KPIs → save its | DEPLOY.md §2 | |
every 6 h (:41) | on-prem store: newest Drive upload → validated | DEPLOY.md §3 |
GCS buckets (project frame-shop-511616):
Bucket | Holds | Written by |
| each cloud store's |
|
| the on-prem store's live |
|
Related MCP server: connectwise-command-mcp
Layout
Path | What |
| the 3-step scrape as a class; one session, re-login + retry on expiry |
| CSV bytes → typed rows (BOM, |
| per-report config (page path + date-field control IDs); WorkOrderList only |
| pydantic row/response schemas |
| FastAPI app (local dev; not deployed) |
| main store's ingest: raw landing → |
| original standalone reference script (still runnable); also has |
| bulk-pull invoices as PDFs ( |
| pull the full customer list as CSV (every customer, not scoped to any date range) |
| one-off recon of the other SSRS report pages (parked — see its README); also has |
| CI/CD, the GitHub Actions identity, and the two data workflows' setup |
| finds the newest LifeSaver upload (zip or plain folder) in the shared Drive folder and downloads only what's needed |
| LifeSaver |
| Drive → build → sanity-check against the live copy → publish to GCS; what |
|
|
|
|
| the website: |
| the store registry |
| the report query layer ( |
| on push to |
| daily: every cloud store → sync → recompute KPIs → save its warehouse |
| every 6 h: Mason Drive upload → validated |
Setup
python3 -m venv .venv
.venv/bin/pip install -r requirements-dev.txt # runtime + pytest
cp .env.example .env # fill in LIFESAVER_USERNAME / LIFESAVER_PASSWORD(The website's container installs webapp/requirements.txt.)
Run the API
.venv/bin/uvicorn cloud.lifesaver.api:app --port 8000GET /reports/work-order-list?start=2025-08-01&end=2025-08-31→ JSONadd
&format=csvfor the raw exportinteractive docs at
http://localhost:8000/docs
Claude connector (MCP)
The website serves it at /mcp/<token>: every store, or all of them pooled, from the same data the
site shows. Tools, tiers and the connector URL: webapp/README.md.
Retention warehouse + website (Phase 3)
A cloud store (its login in LIFESAVER_USERNAME / LIFESAVER_PASSWORD; WAREHOUSE_DB_PATH
picks the file, default warehouse.db):
export LIFESAVER_USERNAME=... LIFESAVER_PASSWORD=...
.venv/bin/python -m cloud.warehouse.job backfill # full history, month by month, resumable
.venv/bin/python -m cloud.warehouse.job kpis # eyeball against the baselinesAn on-prem store. In production this is hands-off: the owner uploads to Drive and
mason-ingest.yml does the rest. By hand:
# LifeSaver folder or zip -> lifesaver.sqlite (or: gcloud storage cp gs://frame-shop-511616-lifesaver-data/lifesaver.sqlite mason.sqlite)
.venv/bin/python -m sqlite_extract.extract ~/Downloads/LifeSaver.zip mason.sqliteThen see it in the website, locally (webapp/README.md has the full recipe):
(cd webapp/frontend && npm install && npm run build)
STORE_DANS_DATA=warehouse.db STORE_WHEATONS_DATA=mason.sqlite AUTH_DISABLED=1 \
.venv/bin/uvicorn webapp.server:app --port 8765How the Drive ingest works, the lifesaver.sqlite data model, and the
runbook (rejected uploads, force, rollback): sqlite_extract/README.md.
Full CLI and layer model: cloud/warehouse/README.md. Design,
KPI definitions, and open questions: docs/kpi-design.md.
warehouse.db and warehouse_raw/ are gitignored — each cloud store's system of record is the
GCS warehouse bucket.
Standalone script (no server)
export LIFESAVER_USERNAME=... LIFESAVER_PASSWORD=...
.venv/bin/python cloud/lifesaver_report_pull.py --start 8/1/2025 --end 8/31/2025 --out aug.csvPulling invoices as PDFs
A single invoice (lifesaver_report_pull.pull_invoice(session, invoice_number))
hits a different endpoint than WorkOrderList — /Reports/Invoice/ needs no
postback, but the human-readable "Invoice #" must first be resolved to an
internal id via a separate POST (resolve_invoice_id()) before it works;
using the human number directly renders a silently-empty PDF ($0.00
everywhere, #Error in place of the invoice number). See
cloud/spec.md → "Invoices — a separate, simpler flow" for how both of
these were confirmed.
Bulk-pulling, from a WorkOrderList CSV export's invoiceNumber column or a
plain list:
export LIFESAVER_USERNAME=... LIFESAVER_PASSWORD=...
.venv/bin/python cloud/scripts/pull_invoices.py --csv aug.csv --out-dir invoices
.venv/bin/python cloud/scripts/pull_invoices.py --ids 584,591,602 --out-dir invoicesWrites invoices/invoice_<id>.pdf per invoice, one login/session for the
whole batch, a delay between requests (--delay, default 1.5s), and one
invoice failing doesn't stop the rest.
Pulling the customer list
Don't scrape it from the invoice PDFs (name + cell phone only, no
email/address, one request per invoice) — the report catalog has a report
built for exactly this. Filter: "No filter, show all customers." returns
every customer in the database with real columns (name, email, phone,
address, first/last purchase, total spend, ...), not scoped to any date
range. See cloud/spec.md → "Customer list — a report built for exactly
this, once found" for how that was confirmed.
export LIFESAVER_USERNAME=... LIFESAVER_PASSWORD=...
.venv/bin/python cloud/scripts/pull_customers.py --out customers.csvOne session per user
LifeSaver only allows one active session per user (license-limited). If a run
doesn't log out, the next login is blocked with UserAlreadyLoggedIn. The client
handles this automatically: it terminates its own stale session (never another
user's) and retries. To clear one manually:
LIFESAVER_USERNAME=... LIFESAVER_PASSWORD=... .venv/bin/python cloud/scripts/terminate_my_sessions.pySet LIFESAVER_TERMINATE_OWN_SESSION=false to disable the auto-clear.
Tests
.venv/bin/python -m pytest -qOffline only — no network, no credentials, no GCP. The fixtures in
tests/fixtures/ approximate the live responses; export_sample.csv is a real
capture. After any live run, refresh the HTML fixtures from real responses to
make the suite true regression coverage.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query selected Google and Bing analytics, search, and advertising reports through MCP.
Provide seamless access to Appfolio Property Manager Reporting API through a standardized MCP serv…
LegiScan MCP — wraps the LegiScan API (api.legiscan.com)
CareerOneStop MCP — wraps the CareerOneStop Web API (U.S. Dept. of Labor) (api.careeronestop.org).…
Related MCP Servers
- FlicenseNot gradedqualityDmaintenancePulls golf data from Arccos, GHIN, and 18Birdies, and generates interactive HTML and PDF round reports with shot maps and stats via local MCP tools.-
- AlicenseNot gradedqualityBmaintenanceExposes the ConnectWise Command (Continuum) ITSupport247 Reporting API as MCP tools, enabling querying of sites, devices, patches, antivirus, performance, and more.Apache 2.0
- FlicenseNot gradedqualityCmaintenanceMCP server for Bvoip / 1Stream that exposes call-reporting, phone-status, and CRM-extension-mapping endpoints as MCP tools.-
- AlicenseNot gradedqualityDmaintenanceMCP server for read-only queries and reports from the Astrea (Aurum) legal software. Enables retrieving case information by client, new cases per month, tags, financials (fees/expenses), schedule (hearings, meetings, deadlines, tasks), and case updates for client reports.10MIT