# MCP protocol requirements
Every Life Hub module is an MCP server reachable over HTTP. Life Hub acts as an MCP client during admin probes, user connection tests, and sync.
For per-module URLs, scopes, and required tools, see [Modules](./modules.md). For tool argument and response shapes, see [Standard tools](./standard-tools.md).
## Connecting an MCP service
### Life Hub setup (local development)
Before connecting any module, run the app and apply the database schema:
```bash
bun install
bun run db:push # apply Supabase migrations
bun run dev # http://localhost:3000 (Vite); MCP adapters default to http://localhost:8080
```
Set these in `.env` when probing or syncing **built-in adapters** (paths like `/hevy/v1`) from server-side code:
| Variable | Purpose |
| --- | --- |
| `LIFEHUB_PUBLIC_URL` | Origin used to resolve relative MCP URLs (default `http://localhost:8080`) |
| `LIFEHUB_SECRETS_ENCRYPTION_KEY` | Encrypts user API keys and OAuth tokens at rest |
| `SUPABASE_URL`, `SUPABASE_SECRET_KEY` | Server-side database and auth |
OAuth modules also need provider credentials — see [Modules](./modules.md) for per-module `LIFEHUB_OAUTH_*` variables. Redirect URI for all OAuth modules:
```
{APP_ORIGIN}/connections/oauth/callback
```
Example (local): `http://localhost:8080/connections/oauth/callback`
### Register a module (admin)
External MCP servers must be added to the Life Hub catalog before users can connect:
1. Sign in as admin and open **Admin → Modules**.
2. Click **Add module** and fill in:
- **Key** — stable identifier (e.g. `moneysprout`)
- **MCP server URL** — `POST` JSON-RPC endpoint (absolute URL or path relative to app origin)
- **Health check URL** — `GET` liveness endpoint (recommended)
- **Protocol version** — usually `2025-06`
- **Auth type** — `none`, `api_key`, `oauth2`, or `custom`
3. Click **Probe & sync** on the module row. Life Hub runs the same checks as [Passing the connection test](#passing-the-connection-test) and overwrites **capabilities** and **scopes** from the live server.
4. Set module status to **Active**.
Built-in adapters (`hevy`, `strava`, `ticktick`, `health`, `fitness`) ship in `packages/*-module` and are pre-registered in the catalog.
### Connect as a user
Open **Modules** (or **Connections**) and click **Connect** on the module.
| Auth type | User steps |
| --- | --- |
| `api_key` | Paste the upstream API key → **Test connection** → **Save connection**. Key is encrypted server-side and sent as `Authorization: Bearer <key>` on MCP calls. |
| `oauth2` | Click **Connect with …** → authorize on the provider → redirected back to Life Hub. Tokens are encrypted server-side. |
| `none` | Click **Enable module** — no credentials required. |
| `custom` | Admin-managed; contact your administrator. |
**Test connection** (user) and **Probe & sync** (admin) both execute:
1. `GET` health check URL (skipped if empty)
2. `GET` `/.well-known/lifehub-module.json` (admin probe only; scopes merged into catalog)
3. MCP `initialize` → `notifications/initialized` → `tools/list`
A successful test requires at least one tool in `tools/list`.
### Sync after connecting
Once connected, Life Hub calls `tools/call` for each declared **read** capability during sync:
| Tool | When invoked |
| --- | --- |
| `get_daily_summary` | Populates the daily brief |
| `get_alerts` | Populates attention items |
| `get_metrics` | Populates insights |
| `get_tasks` | Populates tasks |
Write tools (`create_task`, `mark_task_done`) are invoked on demand from the UI, not during background sync. See [Standard tools](./standard-tools.md) for contracts.
Trigger sync from **Today** (refresh) or wait for the scheduled sync cycle. Failures are recorded on `sync_runs` and may mark the connection `degraded` or `error`.
## Manual verification commands
Use these `curl` commands to debug an MCP server before or after registering it in Life Hub. Replace placeholders:
- `MCP_URL` — MCP server URL (e.g. `https://mcp.example.com/v1` or `http://localhost:8080/hevy/v1`)
- `HEALTH_URL` — health check URL (e.g. `https://mcp.example.com/health`)
- `TOKEN` — API key or OAuth access token (omit `-H 'Authorization: …'` for `authType: none`)
### 1. Health check
```bash
curl -s -o /dev/null -w "%{http_code}\n" "$HEALTH_URL"
```
Expect `2xx`. Skip if your module has no health endpoint.
### 2. Module manifest
```bash
curl -s "$(echo "$MCP_URL" | sed -E 's|/v1/?$||')/.well-known/lifehub-module.json" | jq .
```
For adapters at the app root (e.g. Hevy), the manifest is at `{origin}/.well-known/lifehub-module.json`. Module-specific adapters may use a path prefix (e.g. `/ticktick/.well-known/lifehub-module.json`).
### 3. MCP `initialize`
```bash
curl -si "$MCP_URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'
```
Save `Mcp-Session-Id` from the response headers if present:
```bash
SESSION_ID=$(curl -si ... | awk -F': ' '/^[Mm]cp-[Ss]ession-[Ii]d:/{print $2}' | tr -d '\r')
```
### 4. MCP `notifications/initialized`
```bash
curl -s "$MCP_URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "Authorization: Bearer $TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'
```
Some servers accept this implicitly; failures here are non-fatal for Life Hub.
### 5. MCP `tools/list`
```bash
curl -s "$MCP_URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "Authorization: Bearer $TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | jq .
```
Expect a `tools` array with at least one entry. Tool names must match [standard tool names](./standard-tools.md).
### 6. MCP `tools/call` (sync runtime)
```bash
curl -s "$MCP_URL" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H "Authorization: Bearer $TOKEN" \
-H "Mcp-Session-Id: $SESSION_ID" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_daily_summary","arguments":{}}}' | jq .
```
Life Hub opens a fresh MCP session per `tools/call` during sync, so you can also run `initialize` → `tools/call` in one script without reusing `SESSION_ID`.
### Example: built-in Hevy adapter
```bash
export MCP_URL=http://localhost:8080/hevy/v1
export HEALTH_URL=http://localhost:8080/hevy/health
export TOKEN=your_hevy_api_key
curl -s "$HEALTH_URL"
curl -s "$MCP_URL" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'
curl -s "$MCP_URL" -H 'Content-Type: application/json' -H "Authorization: Bearer $TOKEN" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_daily_summary","arguments":{}}}'
```
## Endpoints
| Endpoint | Method | Required | Purpose |
| --- | --- | --- | --- |
| MCP server URL | `POST` | Yes | JSON-RPC transport for MCP methods |
| Health check URL | `GET` | Recommended | Liveness probe; must return `2xx` |
| `/.well-known/lifehub-module.json` | `GET` | Recommended | Module manifest (scopes, OAuth) |
The manifest URL is derived from the MCP server origin:
```
{origin}/.well-known/lifehub-module.json
```
Example: MCP server `https://mcp.lifehub.app/health/v1` → manifest at `https://mcp.lifehub.app/.well-known/lifehub-module.json`.
## MCP JSON-RPC methods
These methods must be supported on the MCP server URL.
### `initialize`
Negotiates protocol version and returns server info.
**Request params:**
```json
{
"protocolVersion": "2025-06",
"capabilities": {},
"clientInfo": { "name": "lifehub-planner", "version": "1.0.0" }
}
```
**Expected result fields:**
- `protocolVersion` — negotiated version string
- `serverInfo.name` — human-readable server name (optional but recommended)
Life Hub tries protocol versions in order: the module's configured version, then `2025-06`, `2025-03-26`, `2024-11-05`.
### `notifications/initialized`
Sent after a successful `initialize`. Some servers may ignore this notification; Life Hub tolerates failures here.
### `tools/list`
Returns the tool catalog. Tool **names** must match the [standard tool names](./standard-tools.md) your module implements.
**Expected result:**
```json
{
"tools": [
{ "name": "get_daily_summary", "description": "…" },
{ "name": "get_alerts", "description": "…" }
]
}
```
### `tools/call` (runtime sync)
Used during sync (not during probe). Life Hub invokes each declared capability by name.
**Request params:**
```json
{
"name": "get_alerts",
"arguments": { "since": "2026-06-19T00:00:00Z" }
}
```
## Transport headers
| Header | When | Value |
| --- | --- | --- |
| `Content-Type` | All MCP requests | `application/json` |
| `Accept` | All MCP requests | `application/json, text/event-stream` |
| `Authorization` | When auth is configured | `Bearer <token>` |
| `Mcp-Session-Id` | After `initialize` | Session id returned by the server |
Responses may be plain JSON or SSE (`data: {…}` lines). Request timeout: **15 seconds**.
## Authentication
Configured per module (`authType`):
| Auth type | How Life Hub authenticates MCP calls |
| --- | --- |
| `none` | No `Authorization` header |
| `api_key` | User-provided key as `Authorization: Bearer <key>` |
| `oauth2` | User OAuth access token as `Authorization: Bearer <token>` |
| `custom` | Same as API key; module-specific secret format |
OAuth modules publish endpoints in the module manifest or standard OAuth discovery documents (`/.well-known/oauth-authorization-server` or `/.well-known/openid-configuration`).
When a module exposes a `registration_endpoint`, Life Hub **automatically registers** an OAuth client on first connect (RFC 7591 dynamic client registration). No per-module environment variables or redeploys are required. Optionally set `LIFEHUB_OAUTH_<MODULE_KEY>_CLIENT_ID` to override the auto-registered client.
## Module manifest
`GET /.well-known/lifehub-module.json`
```json
{
"requiredScopes": ["accounts.read"],
"scopes": ["transactions.read"],
"oauth": {
"authorizationUrl": "https://…/authorize",
"tokenUrl": "https://…/token",
"clientId": "…",
"clientIdEnv": "LIFEHUB_OAUTH_MONEYSPROUT_CLIENT_ID",
"clientSecretEnv": "LIFEHUB_OAUTH_MONEYSPROUT_CLIENT_SECRET",
"scopes": ["accounts.read", "transactions.read"],
"usePkce": true
}
}
```
| Field | Description |
| --- | --- |
| `requiredScopes` | Scopes the user must grant |
| `scopes` | Optional additional scopes |
| `oauth` | OAuth2 client configuration for Life Hub's server-side flow |
Life Hub merges `requiredScopes` and `scopes` when syncing the admin module catalog.
## Health check
`GET` the configured health check URL. An empty or missing URL skips the check. Otherwise the response status must be `2xx`.
## Passing the connection test
Admin **Probe & sync** and user **Test connection** succeed when:
1. Health check passes (if configured)
2. `initialize` negotiates a protocol version
3. `tools/list` returns at least one tool
4. No MCP errors are recorded
Capabilities and scopes in the admin UI are overwritten from `tools/list` and the manifest on a successful probe.