
mifit
io.github.Jenwein由 GitHub 帳號 Jenwein 發布v0.4.0更新於 Oct 29, 2026
Read-only access to your own Mi Fitness (Xiaomi) data: sleep, heart rate, steps, workouts
概覽
唯讀的 MCP 伺服器,讓助理查詢你自己的小米運動健康(Mi Fitness)雲端資料:步數、心率、睡眠、運動等。
- 可以做什麼
- 讀取每日彙總:步數與距離、卡路里、活動、心率、血氧、壓力、體重以及個人目標
- 讀取每日睡眠,含睡眠時段,可依需求回傳各階段時間軸
- 列出運動紀錄,並取得戶外跑步與騎乘的逐秒取樣,僅在開啟時才包含 GPS 軌跡
- 查看登入狀態、時區與 GPS 設定,並可從助理發起 QR code 登入
- 每個工具只讀取一類資料,且有時間範圍上限(例如 92 天,原始取樣為 7 天)
- 適用情境
- 適合希望助理回答自己從 Mi Fitness 應用程式同步的健康與運動歷史問題,例如上週睡得如何、某次運動表現怎樣,而不必手動匯出檔案。
- 執行需求
- 以 stdio 本機程序執行,透過 uvx 啟動(需要 Python 3.12 或更新版本,uv 可代為安裝)。需要一個小米 / Mi Fitness 帳號,以 QR code 登入一次,工作階段儲存在使用者家目錄下。需開啟雲端同步才能取得資料。未宣告任何 API 金鑰或環境變數。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 mifit,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
mifit
Read your own Mi Fitness (小米运动健康) data from the Xiaomi cloud: a small Python library, a command-line tool and an MCP server for AI assistants.
The Mi Fitness app syncs your steps, heart rate, sleep and more to the Xiaomi cloud, but Xiaomi offers no public API for individuals. mifit signs in to your own account by QR code, reads that synced data and returns it as JSON or as Python objects, so you can build on it.
Unofficial. Not affiliated with, endorsed by or supported by Xiaomi. See Disclaimer.
What it reads today
"Checked against the app" means the values were compared with what the Mi
Fitness app shows, on one real account (China region, UTC+8). More is
planned. Meanwhile, mifit raw (in Python raw_window and daily_report)
returns the rows of any other key of the fitness-data or (with --daily)
daily-report endpoint, uninterpreted.
What changed in each version, breaking changes included, is in CHANGELOG.md.
Install
Requires Python 3.12 or later; uv installs one for you if needed.
Upgrade: uv tool upgrade mifit (or pipx upgrade mifit, pip install -U mifit).
Log in
A QR code is drawn in the terminal, with the login URL below it. Scan it with the Xiaomi / Mi Fitness app on your phone (or open the URL) and approve the sign-in. Your password never passes through mifit.
The session is saved to ~/.mifit/session.json (on Windows
%USERPROFILE%\.mifit\session.json). It grants access to your Mi Fitness
account's health data (not only reading). It also holds your account's
passToken, which mifit uses to renew the session when it expires. The passToken
is account-level: it can obtain tokens for any Xiaomi service, so the file
grants access to your whole Xiaomi account, not just your Mi Fitness health
data, and it stays useful long after the session token itself has expired.
Treat it like a password, and do not share it or commit it. On Linux and macOS
the file is readable by you only; on Windows it relies on your user profile's
permissions.
If Xiaomi asks for an extra security confirmation, mifit login prints a URL:
open it, confirm, then run mifit login again.
Use with an AI assistant (MCP)
mifit mcp is a Model Context Protocol
server: AI assistants such as Claude Desktop, Claude Code, Cursor and VS Code
start it on your computer and read your Mi Fitness data when you ask them about
it ("how did I sleep last week?").
What leaves your computer. The results of the tools the assistant calls are
sent to the assistant's model provider, like the rest of the conversation. The
login stays in ~/.mifit on this computer: the server runs locally on
stdin/stdout, opens no network port, never puts a credential in a result, and
only reads your health data (the login tools save the new session to
~/.mifit). GPS tracks are off: the server gives a workout's route only when it
was started with --allow-gps (in the Claude Desktop extension: when its
Allow GPS tracks switch is on) and the assistant asks for it, and a route
shows where you were, including where you live. (To serve assistants on other
machines instead, see Remote access (HTTP):
that server does open a port.)
Log in once, in a terminal:
(or mifit login after uv tool install mifit). Without a terminal, ask the
assistant to log you in: it calls login_start, which returns the QR code to
scan with the Xiaomi or Mi Fitness app.
Add the server to your assistant. uvx (part of
uv) downloads and runs mifit when needed:
After uv tool install mifit, the command can be mifit with the argument
mcp instead. To allow GPS tracks, add --allow-gps after mcp
(["mifit", "mcp", "--allow-gps"]).
mifit is also listed in the official
MCP Registry as
io.github.Jenwein/mifit, for clients that install servers from it.
Claude Desktop, in one click. Download mifit-<version>.mcpb from the
latest release and open it
(or install it from Claude Desktop's Settings → Extensions): Claude Desktop
(Windows, macOS) offers to install the extension. Its settings have an
Allow GPS tracks switch, off by default. The extension runs the mifit
release it came with: to update, install the .mcpb of the new release. Use
either the extension or the JSON config above, not both.
Tools. Each reads one kind of data; dates are local calendar days.
Results are the records of the Python API with local times
(2026-10-07T23:12:00+08:00) instead of Unix seconds, never raw. Dense data
is cut down to at most 500 rows and the notes say how. A result is kept for
five minutes, so asking again sends no new requests.
Ask in your own words; the assistant picks the tools. For example: "Did my resting heart rate go up this month?"
Remote access (HTTP)
mifit mcp --transport http runs the same server as a long-running service on
streamable HTTP,
so assistants on other machines can use it. Run it on the machine that keeps
the login, such as a home server on your Tailscale or Headscale tailnet:
--host ADDRESS: the address to listen on. The default,127.0.0.1, serves this machine only; give the server's tailnet address to serve the tailnet.0.0.0.0listens on every interface.--port PORT: default8766.--path PATH: the endpoint, default/mcp.--allowed-host HOST[:PORT]: aHostheader to accept. The server answers only requests whoseHostheader it knows, and refuses the others with HTTP 421 (the MCP SDK's DNS rebinding protection, always on). It knows127.0.0.1,localhostand[::1](any port) when it listens on loopback or on every interface, and its--hostaddress with the port otherwise. Add each name the clients connect by, as they send it:mifit.ts.internal:8766, ormifit.ts.internal:*for any port. Repeat the option for more names.--allow-gpsworks as it does on stdio.
Dates and local times are counted in the server's time zone. If it is not
yours, start the server with yours, such as TZ=Asia/Shanghai (in the systemd
unit below, its Environment= line).
At startup the server prints its URL and the Host headers it accepts on
standard error. Then add it to each assistant by its URL:
Other clients take the URL as a streamable HTTP (http) server. To log in
without a terminal on the server, ask the assistant: login_start returns the
QR code in the tool result, nothing opens on the server's screen, and the
session is saved on the server. If Xiaomi asks for a security confirmation,
run mifit login in a terminal on the server (over SSH, say) and follow it.
Security. The HTTP server has no authentication. Anyone who can reach its port can read your Mi Fitness data through it, and can start a login that replaces yours. The session file on the server grants access to your whole Xiaomi account, not just Mi Fitness (see Log in). So keep the server on a private network: listen on the tailnet address only, never forward the port to the internet, and use your tailnet's access rules to limit which machines reach it. The server prints a warning on standard error when it listens beyond loopback. Requests are plain HTTP: on a tailnet, WireGuard encrypts them between machines.
To keep it running, use a systemd user service, here with the mifit of
uv tool install:
Export
<domain>:activity_daily,consumption,heart_rate,heart_rate_daily,sleep,spo2,spo2_daily,steps,stress_daily,weight,workoutsorworkout_detail(per-second samples and GPS track of outdoor runs and rides).--from/--to: first and last day, inclusive, as local calendar days.--json PATH: write to a file instead of standard output.--full:heart_rateonly; page backwards through the history instead of querying one time window (see Known limitations). This is a single read: it ignores--from, so it needs no--longhowever long the range, and it cannot be combined with--chunk,--longor--resume.--no-track:workout_detailonly; skip the GPS file, so no track is requested or written.--chunk day|week|month: the chunk size (1, 7 or 31 days) for a range longer than one chunk; the default depends on the domain.--long: allow a range over 92 days.--resume: continue an interrupted export fromPATH.partial(needs--json).
The output is one JSON object with three lists:
Long ranges
A range longer than one chunk (7 days for raw heart rate, SpO2 and workout
detail, 31 days for everything else, counted from --from) is read chunk by
chunk, one request at a time, with progress on standard error. A shorter range
is a single read, as before. The chunks are merged into one output of the same
shape; with more than one chunk, each note starts with the chunk it came
from. Ranges over 92 days need --long: they take many requests and can run
for many minutes. The one exception is heart_rate --full: it ignores
--from and is a single read, never chunked, so it needs no --long.
With --json PATH, progress is kept in PATH.partial: your exported data
plus the export's arguments (domain, dates, chunk size), no credentials.
Treat it like the output itself. If the export stops (a network error, an
expired session, Ctrl+C), the finished chunks stay in that file; rerun the
same command with --resume and only the missing chunks are read. The output
is the same as an uninterrupted run's, and the partial file is deleted once
it is written. An export to standard output is chunked the same way but
cannot be resumed.
A failed chunk stops the export; nothing is retried automatically. If one chunk keeps failing, you can skip it by exporting the ranges before and after it separately.
Any key: mifit raw
Reads any key of the fitness-data or (with --daily) daily-report endpoint as
the service sends it, whether or not mifit also has a domain for it
(heart_rate has one):
- It writes
{records: [], raw, notes}.rawholds the service's rows, with their own fields andvaluedecoded from its JSON string;recordsis always empty. - For a raw key it asks the service a day early and keeps the rows inside your window, so the first hours of the window are complete.
--dailyreads the key's daily report instead, one row per local day that has data;--tag TAGpicks another aggregated tag (defaultdaily_report).- It is one read, paged: a range over 92 days needs
--long, and there is no--resume. - The keys known so far are listed in notes/domain-catalog.md.
Python API
raw_window(key, ...) is raw made complete: it asks a day early and keeps
the rows of your window, with value decoded. daily_report(key, ...) returns
the key's daily rows, one per local day that has data, with tag= to pick a
tag other than daily_report. Both give records=[] and the rows in raw.
Which keys exist and what is verified: notes/domain-catalog.md.
workout_detail gives one WorkoutDetail per workout in records: its
workout summary (the Workout of workouts), its samples (WorkoutSample,
one per recorded second) and its track (TrackPoint); track=False asks
for no GPS file.
Dates are "YYYY-MM-DD" strings (local calendar days, inclusive) or ints of
Unix seconds. Each domain method returns a result with records, raw and
notes; raw(key, ...) returns the service's rows as they came, for any key
of the fitness-data endpoint (data/get_fitness_data_by_time), not the daily
reports (see raw_window for a complete window). Errors specific to mifit
derive from mifit.MiFitError: AuthError (no usable session, or HTTP 401),
its subclass RenewalError (an HTTP 401 the automatic renewal could not fix;
log in again), ApiError (the service refused the request or sent something
unreadable) and NotificationRequiredError (raised by the login when Xiaomi
asks for a security confirmation). Network failures surface as requests
exceptions, and a bad date argument raises ValueError or TypeError.
For a long range, read_chunks reads one chunk at a time (the CLI's chunk
sizes; size="day", "week" or "month" overrides them) and merge_chunks
joins the parts. Its dates are "YYYY-MM-DD" strings only.
read_chunks(..., skip=labels) passes over the chunks whose labels you
already have, to resume, and options= gives keyword arguments to every
chunk's call, such as {"track": False} for workout_detail.
options={"full": True} is refused with ValueError: a full read pages back
from each chunk's end, so every chunk would read the same samples again; call
heart_rate(..., full=True) once instead. The library has no 92-day limit: a
long range is many requests, so ask for what you need.
When a request is refused with HTTP 401, the client renews the session once
from its passToken and sends the request again. A client that uses the
session saved by mifit login saves the renewed session back to
~/.mifit. Explicit sessions (MiFit(session)) are renewed in memory; read
client.session to keep the renewed one. MiFit(auto_renew=False) turns
renewal off, so a 401 raises AuthError straight away.
MiFit(http=...) injects the HTTP client, to keep a client offline. With
renewal on, the renewal runs on that client too, so its get must accept the
cookies= and allow_redirects= keywords as well as the usual params=,
headers= and timeout= (a requests.Session does). Without an injected
client, the renewal uses a fresh requests.Session of its own.
Two layers: raw and records
rawis the service's decoded payloads (the innervalueof each row, or for consumption the response'sresultobject; forworkout_detail, each workout's decoded summary and files as stored values), with Xiaomi's own field names. It is not a complete dump of every row the service sent. The generic reads are the exception: forraw_window,daily_reportandmifit raw,rawholds each whole row the service sent for your window, its outer fields included and itsvaluedecoded.recordsare typed dataclasses with neutral names (asleep_min,avg_hr_bpm, ...). A field's meaning is established from what the app displays or from the app's own code before it enters this layer. One, the REM stage, rests on the app's code alone, because the author's device reports no REM. The sleep score is not in this layer: the app showed only a rating, so the number could not be checked; it is inrawassleep_score. A value the service did not report isNone, never a made-up0.notesare caveats about that particular result: skipped rows, data that may be incomplete.
Known limitations
- Cloud only, and the cloud lags. mifit reads what the app has synced to the Xiaomi cloud. If cloud sync is off or has not run yet, data is missing or late; the latest day may be partial.
- The calorie ring is synthesized by the app. The app combines cloud
activity with phone-side activity, so
consumptionfor the latest day can read lower than the ring.step_caloriesinstepsis the step report's walking-calorie figure, not the ring. - Raw windows are corrected by the SDK. The service drops the samples at
the start of a raw-data window (the first bucket); every dense raw key
checked does this:
heart_rate,spo2,stressandcalories(notes/08, notes/11, notes/domain-catalog.md). Theheart_rate,spo2andweightreads,raw_windowandmifit rawask the service for a day before the start and keep only the requested window.raw(key, ...)returns the service's answer as it came, for any key, without this correction: useraw_windowfor a complete window.--full(full=True,heart_rateonly) pages backwards from the end date instead; it ignores the start date and stops after 10 pages, saying so innoteswhen older data remains. - NaN and Infinity become
null. JSON has no such numbers, so where the service sends one, the CLI writesnulland a note says how many it replaced. - A read the service breaks off says so. When the service reports more
pages but sends no usable cursor to fetch them, the read stops there and
notessays that rows in the range may be missing. - Workout detail reads outdoor runs and rides only. It covers the file
formats seen so far (record file versions 8 and 5, GPS file version 2).
Walks, hikes, swims, indoor runs and other sports, and other file versions,
get a note and no samples: each needs a real file and a hand-written layout
table first. Altitude is in
rawonly. One call reads the detail of at most 10 workouts (long ranges are read a week per chunk). A GPS track shows where you were, including where you live:--no-track(track=False) leaves it out. The download links and keys never appear in the output. - Workout files outside the China region are untested. Files download
only from hosts under
.fds.api.xiaomi.com, the host seen on a China-region account. A link to another host is refused with a note, and that workout keeps its summary without samples or track. - Read-only. mifit never writes, changes or deletes anything in your account.
- Sessions expire. On an HTTP 401, mifit renews the session once from the
stored passToken and retries the request. When that fails, or the session
was saved before renewal existed (it has no passToken), run
mifit loginagain. Renewal was checked live on 2026-10-15. The lifetime without renewal is still being observed and is at least 22 hours so far: logins, 401s and renewals are logged (event, time and ages only) to~/.mifit/session-log.jsonl. (An HTTP 401 can also mean the request signature was refused; a 401 right after a renewal is taken to mean that and is not retried.) - Narrowly tested. One account in the China region. Other regions are routed but unverified, and Xiaomi can change the protocol at any time.
- Be gentle with the service. Requests are sent one at a time with about a
second's pause plus random jitter. Long ranges are read in chunks, and a
range over 92 days needs
--long. This lowers the risk of being rate-limited, but does not remove it.
Development
The tests are offline: a fake transport stands in for the Xiaomi cloud, and
any attempt to open a network connection fails the test. The test extra
includes tzdata, so the daylight-saving tests also run on Windows, which has
no time-zone database of its own. CI runs the suite on Linux with Python 3.12,
3.13 and 3.14.
Disclaimer
mifit is an independent, unofficial project. It is not affiliated with, endorsed by or supported by Xiaomi. "Xiaomi" and "Mi Fitness" are trademarks of their owners and are used here only to say what the software works with.
It is meant for reading your own data with your own account. Automated access may conflict with Xiaomi's terms of service and could get your account rate-limited or restricted; use it at your own risk. Nothing it outputs is medical advice. The software is provided "as is", without warranty of any kind (see LICENSE).
Credits
Request signing and encryption, the transport, the region table and the STS token exchange are adapted from Mi-Fitness-Sync by kevinkwee (MIT License). See THIRD_PARTY_NOTICES.
License
MIT. See LICENSE.
來源:README.md,提交 00b8e15
工具
0版本歷史
2- v0.4.0最新Oct 29, 2026
- v0.3.2Oct 16, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分
