Health by Dr Brad

io.github.DrBradStanfield由 GitHub 帳號 DrBradStanfield 發布v1.0.2更新於 Oct 27, 2026

Read and add to your own health record, kept in your own cloud, and get a health plan from it.

已驗證Streamable HTTP可網頁執行位置與生活檔案與儲存知識與記憶

概覽

AI 產生的概覽

託管型連接器,讀取存放在你自己雲端硬碟的個人健康紀錄,讓助理檢視紀錄、新增測量與檢驗結果,並產生篩檢計畫。

可以做什麼
  • 讀取存放在 Dropbox 或 Google Drive 的健康紀錄檔案,包括資料夾中尚未寫入紀錄的檔案
  • 新增測量值、血液檢驗結果與篩檢答案,更正時以附加新列方式處理而非覆寫舊資料
  • 從 Dropbox 應用程式資料夾匯入檢驗文件,並登錄助理從聊天中檔案讀取到的結果
  • 產生健康計畫,列出到期應做的篩檢項目,附上實證建議與引用
  • 透過連接器送出意見回饋
適用情境
適合已使用 Dr Brad 健康路線圖紀錄、希望讓助理讀取紀錄、補充新的測量或檢驗數值並提示哪些篩檢到期的人。主要供透過 Claude 或 ChatGPT 操作該紀錄的使用者。
執行需求
遠端 MCP 端點 mcp.drstanfield.com,以 OAuth 連線。需要已連結的 Dropbox 或 Google Drive 帳號,其中存放 health-roadmap.json 紀錄檔;匯入文件需使用 Dropbox 路徑。僅使用網站本身則無需帳號。
安裝前請注意
涉及健康資料。託管連接器會解開你的雲端憑證,並在每次請求期間將紀錄保存在伺服器記憶體中,但聲稱不儲存。在商店頁面連結雲端硬碟會預設替你訂閱電子郵件提醒,並送出雲端帳號電子郵件、篩檢名稱與到期日期。助理可以寫入你的紀錄;更正會附加新列,不會刪除內容。檢驗匯入會將文件內容送至 Anthropic 的 API 進行擷取。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Health by Dr Brad,將其新增到工作區。
  2. 連接 所需的 憑證。它們會被私密儲存,絕不會在公開頁面上展示。
  3. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/io.github.DrBradStanfield%2Fhealth-by-dr-brad/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。 客戶端會在首次連接時完成 OAuth 登入。

{
  "mcpServers": {
    "health-by-dr-brad": {
      "type": "http",
      "url": "https://mcp.drstanfield.com/mcp"
    }
  }
}

README

Health by Dr Brad

A free health roadmap. Enter your measurements and blood test results, and it shows which screening tests are due, with evidence-based suggestions and their citations. Use it at https://drstanfield.com/pages/roadmap, no account needed. Connect it to Claude or ChatGPT with the connector URL https://mcp.drstanfield.com/mcp. Your record is one file, health-roadmap.json, in your own Dropbox or Google Drive or your browser; our server never keeps it.

Health-metric tracking with personalized suggestions, delivered as a Shopify storefront theme extension and as a self-hosted page.

Local-first. A user's health data lives in their own cloud (Google Drive, Dropbox, GitHub, WebDAV) or in localStorage, as a single health-roadmap.json file. The website and the local tools never send that file to our server; the hosted connector at mcp.drstanfield.com holds it in server memory for the length of one request and stores none of it. "Logged in" means a cloud provider is connected. The Fly backend is thin: chatbot, lab-import extraction, A/B and product events, email reminders, Klaviyo capture, and the hosted MCP server. Supabase holds operational rows only, never health values. Email reminders are the one place a screening name reaches us: that row holds the enrolled email address, the screening labels and the dates they are due, and nothing else (app/lib/reminder-v2.server.ts).

The three agent surfaces

The same file, the same write path, three ways in.

  1. Widget: the React app in the storefront or on the self-hosted page. Reads and writes through RoadmapStore.
  2. CLI and stdio MCP: tools/get-plan.ts, tools/edit-record.ts and tools/mcp-server.ts, running on the user's own machine against their own file. See docs/guides/getting-started.md ("An open-source health record you can connect to any AI", the hub guide), docs/guides/command-line.md and docs/guides/connect-claude-desktop.md.
  3. Hosted MCP: https://mcp.drstanfield.com/mcp, live since 2026-09-02 on the Fly app health-tool-edu, over Dropbox and Google Drive. Nine tools as of 2026-09-07: the hosted-only import_documents (US-35) reads lab files from the Dropbox app folder (Drive's drive.file scope cannot see dropped files), and file_results (US-36) takes the rows the assistant read from a file dropped into the chat, so the file never reaches our server. On Dropbox a read also names folder files not yet in the record (US-37).

To answer one hosted call, the server unseals the cloud credential the assistant holds, opens the user's folder with it, and holds the record in server memory for the length of that request. It stores none of it. It does write one value-free activity row per call: which tool, which assistant, the outcome and the time. (One exception, for an invented account: while Anthropic's connector review is pending, the server holds the reviewer account's Dropbox credential itself; US-32 AC38.) Anyone who does not want that runs surface 2 instead: tools/mcp-server.ts is the same tool layer over the same file with no server of ours in it at all (docs/guides/getting-started.md, the hub guide, and docs/guides/connect-claude-desktop.md).

Every non-browser writer goes through the same SyncManager (packages/health-core/src/sync-manager.ts), which merges on conflict and verifies after writing. The CLI and stdio MCP run it over file-adapter.ts, which takes a lock file and keeps backups. The hosted MCP runs it over the REST adapters (dropbox-rest.ts, drive-rest.ts), where the provider's own conditional write does the same job. See packages/health-core/README.md.

Rows are never mutated. A correction appends a new row and marks the old one entered-in-error.

Two builds, one source

BuildCommandWhere
Shopify storefrontnpm run build:shopify-prodboth stores, theme extension
Self-hostnpm run build:pagesGitHub Pages, almost no backend, bring your own keys

Flags: VITE_LOCAL_FIRST for all v2 behavior, VITE_SHOPIFY_SURFACE to gate the features that need our server.

"Almost no backend" is the honest version of the Pages build. The record and the chat never touch our server there: the record is written straight to the user's own cloud, and chat runs on the user's own key. Two optional calls do reach us, and only if the user makes them. Signing in with Google posts the OAuth code to /api/google-token, which forwards it to Google and stores nothing (widget-src/standalone/google-config.ts). Enrolling in email reminders posts the address and the due dates to /api/reminders-v2 (widget-src/standalone/reminders.ts). Sentry error reporting also runs on this build (widget-src/standalone/app.tsx calls initSentry), scrubbed by widget-src/src/lib/sentry.ts before anything leaves the browser: on drbradstanfield.github.io it is enabled, under the environment name standalone, so the page does talk to sentry.io. The scrub is the same one the storefront build runs, the shared scrubEventText over every free-text field of an event, so a Pages error is reported under the same rules as any other.

The rest of what the page loads is its own. widget-src/standalone/index.html carries one module script and loads no third-party script at load time; Sentry is bundled into it, and the hero image ships with the build (widget-src/standalone/hero-dr-brad.webp, 26 KB) rather than being fetched from cdn.shopify.com, so loading the page tells Shopify nothing. Connecting Google Drive loads Google's sign-in script from accounts.google.com. Cloud credentials and the optional Anthropic key live in localStorage for that origin (hr_anthropic_key, health_roadmap_dropbox_tokens, health_roadmap_gdrive and health_roadmap_gdrive_tokens, health_roadmap_github, and health_roadmap_selfhost, which holds a WebDAV username and password), where any script running on the origin can read them. /roadmap/ and /roadmap/dev/ are the same origin, so the staging preview shares that storage with the live page.

Getting started

bash
git clone <repo-url>cd roadmapnpm installcp .env.example .env   # fill in what you neednpm run test:all

Node 22 or newer. Never run shopify app dev: the dev preview overrides production.

Tests

bash
npm test          # symlink guard + health-core + servernpm run test:all  # everything, and what CI runsnpx vitest run <path>

Deploy

CI is the deploy path: .github/workflows/deploy.yml, triggered from Actions → Deploy → Run workflow. All credentials live in the GitHub production environment. Manual and emergency steps, plus the two-app Fly split, are in docs/deploy-runbook.md.

Verifying this yourself

You do not have to take the code's word for it. Open the site with the DevTools network tab recording and use the widget.

  • Saving a metric produces no request to our server. The record file is written only by the adapters in widget-src/src/storage/ (dropbox.ts, drive.ts, github.ts, webdav.ts, local-storage-adapter.ts), so the write goes to your own Dropbox, Google Drive, GitHub, WebDAV server, or localStorage. No endpoint on our server accepts the record for storage. The hosted connector is the one place a record reaches our server at all, and only if you connect one: it reads the file in server memory for the length of one request and stores none of it.
  • Two requests carry health content, and you trigger both. A lab-document upload POSTs the document's page content to /api/lab-import-v2, which sends it to Anthropic's API for extraction, returns the values, and stores none of them. Sending a chat message from the storefront widget POSTs the message, that conversation's earlier turns, and the health context needed to answer it to /api/chat. On that surface the server stores one row per question: the first 500 characters of the text you typed, with every number value masked to # (some test names that contain a digit, such as HbA1c, stay as written), which may still hold health details you wrote in words; the articles it matched; its classification; the router's raw output and any router error; the name of the surface; and a pseudonymous session and conversation id. The earlier turns and the reply are not stored. A daily job blanks the question text, the router's raw output and the error text once a row is 30 days old; the match record stays: the articles, the classification, the ids. A guest session row holds a hashed address, not an IP.
  • The chat bubble on blog pages and the chatbot embed send less than the widget's chat, and keep more. Both read the widget's copy of your saved record out of localStorage and send it as chat context on every message (loadGuestInputs() in widget-src/src/lib/storage.ts, called from widget-src/src/site-chat.tsx and widget-src/src/chatbot-embed.tsx). That context is your saved profile (sex, height, birth year and month, unit system); the latest saved value of each measurement, with its date; the medications on record, with drug names and doses; and every screening answer on record: methods, results, dates and follow-ups, your smoking history and pack-years, your PSA value, your answers on discussing prostate and endometrial screening, and your answer about abnormal bleeding. Nothing you typed without saving is in it. The widget's own chat sends all of that, plus up to 24 dated values per measurement and whatever is typed on the page but not yet saved. Neither bundle sets VITE_LOCAL_FIRST, so the server takes them for a stored surface: it writes your question and the reply into chat_messages, and the reply can quote the values you were sent with. It also writes a chat_match_events row per question, holding the question unmasked and your earlier questions (router_context). When you are signed in to the store, these rows carry your customer account's user_id. Deleting a conversation in the bubble deletes its messages and title and blanks the question text of its match rows. Those cached values are still sent as context; Brad chose on 2026-09-10 to keep that, because the answer is worse without them. The transcripts now join the same 30-day purge (app/lib/chat-purge-cron.server.ts): once a row is 30 days old the job blanks the message text and the conversation title, which is the first words of your question. What stays is the shape of the thread, not the words: row ids, the role, the timestamps, the model, the token counts, and the fallback flags. A blanked turn reads back as [removed after 30 days]. The Discord bot's transcripts are blanked on the same clock, decided 2026-09-10: a question typed at the bot is as private as one typed into the bubble; the author's Discord id stays in external_id. The YouTube transcripts stay, because those turns are public comments on a video: the comments and replies in chat_messages and youtube_bot_log have no deletion date. Their chat_match_events text is blanked at 30 days, as on every platform.
  • Once the question archive is switched on (it ships off; steps in docs/chat-question-archive/README.md), just before the purge blanks a health question from the widget's chat, the bubble or the embed, it keeps a copy of that one question with no end date, to improve the chatbot (US-15 AC44). An automatic step tries to remove names, places, dates (the year stays), exact ages (the decade stays), contact details, account and order numbers, and the names of employers and schools; it can miss some. The copy keeps the health details, and we do not link it to your account. Making it sends the question to Anthropic once more (their 30-day API retention applies). Store questions, replies, Discord, YouTube and bubble or embed conversations deleted within 30 days are never copied (an earlier copy stays until you ask; a tool-page delete never reaches our server, so it does not stop the copy). "Delete all my data" cannot reach it; to have a copy deleted, email us the wording and we will search for it.
  • Two devices editing apart converge without a conversation. If both record the same measurement for the same day while apart, the newer entry becomes that day's value; the other stays in your history marked entered-in-error. Nothing is deleted. The record file is the trust root: anything that can write it can already delete it outright, and a file carrying a later erase wins on every device. Recovery is your provider's version history.
  • Telemetry is a closed allow-list of event names in packages/health-core/src/product-events.ts. An event is a name plus an anonymous visitor UUID, and the server rejects any name off the list.
  • Nothing else we call carries a value. /api/google-token forwards an OAuth code or refresh token to Google and stores nothing. /api/reminders-v2 sends your reminder schedule, which is a label such as "Colonoscopy" and a due date. On the storefront you do not have to enrol: connecting a cloud provider enrols you, because that connection IS the consent (autoEnrolReminders in widget-src/standalone/reminders.ts, the default-on decision of 2026-08-11). Your cloud account's email address, the labels and the dates reach us on that connect. The plan-ready email that follows says reminders are coming and carries the one-click off switch, as does every reminder after it. On the GitHub Pages build nothing enrols you: autoEnrolReminders returns immediately off the Shopify surface, and only the toggle, which asks you for an address, can enrol you. Either way what we hold is that address, the schedule and the token that turns reminders off: no health values, no account, nothing else. The optional plan email sends the address you type and that same schedule.
  • Feedback you type into the widget's feedback box is stored and emailed. The route takes your email address and up to 2000 characters of free text, adds your Shopify customer id when you are logged in, writes the row to feedback_submissions and sends the same thing to Brad through Resend (app/routes/api.feedback.ts, recordFeedbackSubmission in app/lib/product-events.server.ts). Nothing scrubs that text and nothing deletes the row, so health details you write there stay written.
  • Errors go to Sentry, scrubbed in the browser before they leave it (widget-src/src/lib/sentry.ts): console breadcrumb text is replaced, fetch breadcrumb URLs are cut back to their origin, and record-operation errors are reduced to a fixed message and a closed set of tags. sendDefaultPii is never set. Read the text scrub for what it is (packages/health-core/src/sentry-scrub.ts): it removes email addresses, numbers carrying a unit, and numbers written within 20 characters of about thirty metric and lab words, and server-side it drops any extra field longer than 200 characters. It does not recognise a diagnosis or a name written in prose, and it has no phone-number rule (that guard sits in the connector's report_feedback tool). The control that carries the weight is upstream: the code throws errors with fixed messages instead of echoing the text it was reading. The lab-extraction parse path was changed on 2026-09-10 to do the same, because a JSON or schema error quotes the document back.
  • Health files cannot ride into the server image. Every health-data pattern in .gitignore is also excluded in .dockerignore, and a test asserts the two stay in step (tests/dockerignore.test.ts), so adding an ignore rule without the matching exclusion fails the suite.
  • The plan footer links to the privacy notice on both builds (widget-src/src/components/ResultsPanel.tsx), so what we do with your data is one click from your plan, not buried in a consent screen.

Where to read next

來源:README.md,提交 154a2fc

工具

0
該伺服器需要登入,因此 SourceWeft 無法在沒有帳號的情況下讀取其工具,也未收錄。已連接帳號所能看到的內容可能因帳號及其權限而異。

版本歷史

1
  1. v1.0.2最新Oct 20, 2026

評分

已滿足適用檢查項的 85%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分