Skip to main content
Glama
MetaTwin Physiology MCP

MetaTwin Physiology MCP

by zeyang01
README.md
# MetaTwin Physiology MCP

English | [简体中文](README.zh-CN.md)

A read-only [Model Context Protocol](https://modelcontextprotocol.io) server for personal physiology and health data: **blood glucose, blood ketones, device heart rate, weight, image-based body-fat percentage and BMI**.

It is a stateless JSON **Streamable HTTP** MCP server (path `/mcp`) protected by **OAuth 2.1 Authorization Code + PKCE** with Dynamic Client Registration (RFC 7591). End users connect an MCP-compatible client to the hosted service, sign in once in a browser, and grant the `health:read:self` scope. The server is **read-only** — there is no tool that creates, modifies or deletes data.

> The public hosted service and the organization behind it: **replace this paragraph with the official service URL and operator name before publishing.**

## Capabilities

| Tool | What it does |
|---|---|
| `my_physiology_catalog` | (Optional) Metric catalog: fixed metric IDs, units, data sources, quality-rule versions and availability. |
| `my_physiology_devices` | Lists the user's glucose/ketone devices (1=CGM, 2=CKM, 4=GK5 dual-metric) with first/last measurement dates and the latest device window per metric — used to pick a range when the user gives no date ("latest glucose"). |
| `my_physiology_query` | Creates a query snapshot for a date window (start/end date, IANA time zone, metric set), or polls an existing one with `queryContext`. Status is `building` / `ready` / `failed`; snapshots live 24 hours and are usually ready immediately. |
| `my_physiology_records` | Pages through raw records of a ready snapshot (value, unit, device, quality status and exclusion reason) using an HMAC-signed cursor; `pageSize` 1–5000. One CGM wear (~14 days) is roughly 4,000 points and returns in 1–2 calls. |
| `my_physiology_stats` | Per-metric count, mean, population/sample variance, median, P5/P25/P75/P95, min/max and exclusion counts by reason; optional half-open sub-interval. |

A parallel set of `admin_user_physiology_*` tools exists for approved administrators (`health:read:all` scope plus a server-side double allow-list); they are invisible to ordinary users.

### Fixed metric IDs

`blood_glucose`, `blood_ketone`, `heart_rate_device`, `weight_record`, `weight_body_fat`, `body_bmi`

Every raw record carries a quality flag (`valid` / `excluded`) with a reason, and statistics report raw counts, valid counts and per-reason exclusions.

## Connecting as a user

Point any MCP client supporting Streamable HTTP + OAuth at the hosted endpoint (the URL always ends with `/mcp`):

```json
{
  "mcpServers": {
    "metatwin-physiology": {
      "url": "https://mcp.metatwin.top/mcp",
      "transport": "streamable-http",
      "oauth": { "enabled": true, "scopes": ["health:read:self"] }
    }
  }
}
```

The client discovers the authorization server via standard `.well-known` metadata, performs DCR + PKCE, and the user completes one browser login (mobile/email verification code or password) and a consent screen. RFC 9068 JWT access tokens are short-lived; rotating refresh tokens enable silent renewal.

## Security model

This server is the **edge** only; it holds no database and forwards no OAuth tokens to the data backend.

- **Least privilege.** Ordinary tokens carry only `health:read:self`; the accessible account is forced to the token subject — a caller can never name another account. Cross-account access requires the separate `health:read:all` scope and a double server-side allow-list (client ID and user ID).
- **Tokens stay at the edge.** Calls to the private data backend are authenticated with one-shot, HMAC-signed assertions (30–60 s lifetime, unique `jti` with Redis-backed replay protection) bound to method, path and the SHA-256 hash of the exact request body.
- **Every request re-verified.** JWT signature, issuer, audience and claims are checked, then RFC 7662 introspection confirms the token is still active with matching scopes — revocation is immediate. The backend independently re-checks identity, role, scope and live account state on every call. Any verification failure is rejected (fail-closed).
- **Safe outputs.** Error codes come from a fixed whitelist; backend responses are validated against JSON Schemas and malformed data yields a generic 502. Page cursors are HMAC-signed and cannot be forged.
- **Minimal audit logs.** Only actor type/ID, client ID, operation, target account and timestamps — never health values or token contents.

## Self-hosting

The server requires a compatible OAuth 2.1 authorization server (issuing RS256 `at+jwt` tokens with RFC 9068 claims and an RFC 7662 introspection endpoint) and a private backend implementing the internal physiology HTTP contract. It is published primarily as the reference implementation behind the hosted service.

### Configuration

All configuration is via environment variables; the server refuses to start on invalid or insecure values.

| Variable | Description |
|---|---|
| `MCP_RESOURCE_URL` | Public HTTPS URL, must end with `/mcp` |
| `MCP_BACKEND_URL` | Private backend **origin** (no path); HTTP allowed only for `localhost` |
| `MCP_OAUTH_ISSUER` | Authorization server issuer URL (HTTPS) |
| `MCP_OAUTH_JWKS_URL` | JWKS endpoint (HTTPS) |
| `MCP_OAUTH_INTROSPECTION_URL` | RFC 7662 introspection endpoint (HTTPS) |
| `MCP_OAUTH_INTROSPECTION_CLIENT_ID` / `_SECRET` | Credentials for introspection (client_secret_basic) |
| `MCP_ALLOWED_CLIENT_IDS` | Comma-separated allow-list of OAuth client IDs |
| `METATWIN_MCP_ADMIN_CLIENT_IDS` | Subset allowed to use administrator tools |
| `METATWIN_MCP_ASSERTION_SECRET` | Base64 of at least 32 random bytes; shared only with the private backend |
| `MCP_TOOL_LANGUAGE` | `en` (default) or `zh` for tool descriptions |

Terminate public HTTPS at a reverse proxy routing `/mcp` and `/.well-known/oauth-protected-resource/mcp`, and keep the backend URL private. Never commit real `.env` files.

### Run locally

Requires Python 3.11+.

```sh
python3 -m venv .venv
. .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install -e '.[test]'
cp .env.example .env          # then edit .env
set -a && . ./.env && set +a  # bash only
python -m metatwin_mcp
```

### Container

```sh
docker build -t metatwin-physiology-mcp .
docker run --env-file .env -p 8000:8000 metatwin-physiology-mcp
```

### Tests

```sh
pytest -q
```

## License

[Apache License 2.0](LICENSE)