# MCP server

nLight.fit runs a remote [Model Context Protocol](https://modelcontextprotocol.io) server at `POST https://nlight.fit/api/mcp`. It lets an AI client you already use — Claude Desktop, Claude Code, Cursor, or anything else that speaks MCP — query your own tracked data, WHOOP metrics, bloodwork and correlations directly.

The tools are the same handlers the in-app coach uses. There is no second implementation, so an answer you get in Cursor is computed exactly the way the answer in the app is.

## What you can reach

Sixteen tools, in two groups.

**Curated tools** are the ten the in-app coach also calls. They return a conclusion the analysis pipeline already reached: a streak, a metric series with statistics, a correlation the nightly engine judged significant. They are small, fast, and hard to misuse.

**Warehouse tools** are six that exist only over MCP. They return the underlying evidence instead of a verdict — the raw daily feature matrix, the correlation graph as structured edges, the precomputed statistical models — so a client can test a hypothesis the engine never considered. They are deliberately not offered to the in-app coach, because a 365-day by 40-metric matrix dropped into a chat context produces a worse answer than the single precomputed edge it already had.

The [tool reference](/docs/mcp-tools) lists all sixteen with full input schemas.

## Read-only, and why

Every exposed tool is a query. Nothing reachable over MCP can change your data.

That is enforced in three places rather than assumed. The endpoint requires the `read` scope. Tools are published from an explicit allowlist, not from "everything the coach can call", so a write tool added for the in-app coach cannot become publicly reachable by merging. And every tool descriptor carries `annotations.readOnlyHint: true`, which lets a client run any of them without prompting you for confirmation.

Write tools are a separate phase. They will require a token carrying the `write` scope, which the auth layer already understands but nothing currently grants over this endpoint.

## Transport

Stateless JSON-RPC 2.0 over HTTP POST. One message per request, no session, no handshake required, no long-lived stream.

This matters for a practical reason: the older session-based MCP transport needed a connection pinned to a single server instance, which a serverless platform cannot promise. Protocol revision `2026-07-28` removed the handshake and made every message self-contained, which is what allows this to be an ordinary Vercel function rather than a separate always-on service.

Older clients that still open with `initialize` are handled too. Which era is in play is decided by the `MCP-Protocol-Version` header. See [errors and limits](/docs/errors) for the supported list.

Two consequences worth knowing:

- `GET` returns `405`. There is no event stream to connect to.
- JSON-RPC batching is rejected. Send one message per request.

## Supported methods

| Method | Purpose |
| --- | --- |
| `server/discover` | Stateless capability discovery. Returns protocol version, capabilities and server info. |
| `initialize` | The legacy handshake. Still answered for older clients; negotiates the highest revision you both speak. |
| `tools/list` | The tool catalog. Carries a one-hour cache hint scoped `private`. |
| `tools/call` | Invoke a tool. |
| `ping` | Health check. Returns an empty object. |

`tools/list` is marked `cacheScope: private` deliberately. The catalog is currently identical for every user, but tool descriptions cite your custom columns by name, so a shared cache must never serve one account's vocabulary to another.

There are no MCP resources or prompts today — only tools.

## A minimal call

```bash
curl -sX POST https://nlight.fit/api/mcp \
  -H "Authorization: Bearer $NLIGHT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "MCP-Protocol-Version: 2026-07-28" \
  -H "Mcp-Method: tools/list" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

From revision `2026-07-28` onward the `Mcp-Method` header is required, and `Mcp-Name` is required for messages that address something. The server checks both against the request body and rejects disagreement, so an intermediary cannot route on one operation while the server performs another.

## Browser requests are refused

Any request carrying an `Origin` header is rejected with `403` unless that origin is explicitly allowlisted by the server operator.

This is real access control rather than defensive tidying. The site applies a permissive `Access-Control-Allow-Origin: *` across `/api/*`, which is fine for endpoints that need a browser but not for one that returns a complete medical record. Genuine MCP clients are servers and send no `Origin` at all, so refusing every request that carries one costs nothing and closes both the CORS gap and the DNS-rebinding case the transport specification warns about.

## Getting connected

1. Mint an API token — see [token management](/docs/token-scripts).
2. Add the server to your client — see [client setup](/docs/clients).
3. Ask your client to list tools, and confirm you get sixteen back.

## Related

- [MCP tool reference](/docs/mcp-tools) — all sixteen tools with input schemas
- [Authentication](/docs/authentication) — how API tokens and scopes work
- [Errors and limits](/docs/errors) — status codes, rate limits, protocol versions
