Developers & Agents Updated 2026-08-22 View as Markdown

nLight.fit MCP server

nLight.fit runs a remote Model Context Protocol 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

Eighteen tools, in three groups.

Curated tools are the eleven 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.

Account tools are get_profile, which returns what you have said about yourself rather than anything measured: your Mission Statement and the injuries, allergies and exclusions any recommendation has to respect. Every other tool reports what happened to you; this is the one that reports what you are trying to do and what would harm you, so a client that plans training or nutrition should read it first. Your name and your free-form notes are never exposed — see what is not exposed.

The tool reference lists all eighteen with full input schemas.

What is not exposed

Connecting a client does not hand over your whole profile. Two fields are withheld from MCP on purpose.

Your name stays out because nothing an external model does with a health record needs it, and it is the single field that turns a de-identified record into an identified one. Clients are told this so they neither ask for it nor guess at it.

Your free-form notes — the open "anything else" field — stay out because their contents cannot be known in advance, which means they cannot be cleared for sharing in advance either.

Coaching-style settings are also withheld, for a different reason: they configure how the in-app coach talks to you, and an external client is there to analyse your record, not to imitate a persona.

Everything else in the profile is shared when a client calls get_profile, including injuries and allergies. That is deliberate. This endpoint already returns your bloodwork, your genotypes and every metric you track, so withholding the two fields whose whole purpose is preventing a harmful suggestion would protect nothing and cost you safety.

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.

Try it without an account

A sandbox at POST https://nlight.fit/api/sandbox speaks this exact protocol with these exact tool schemas, answered from a synthetic member record, and needs no credential at all. Point a client at it to learn the surface before anyone signs up, then change the URL. See sandbox.

Transport

Streamable HTTP. 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 for the supported list.

Three consequences worth knowing:

  • GET returns 405, with Allow: POST, OPTIONS. That is what the transport prescribes for a server offering no server-to-client event stream, and this one is stateless with nothing to push.
  • JSON-RPC batching is rejected. Send one message per request.
  • Send Accept: application/json, text/event-stream, as the transport requires. You will get JSON. A client that accepts only text/event-stream gets the same envelope as a single SSE message event instead.

Every response carries MCP-Protocol-Version naming the revision that answered, RateLimit-Policy describing your quota, and Api-Version. A response that spends quota also carries RateLimit with what is left of it. Send a credential and every request is counted. Send none and an anonymous tools/list is still counted, while the negotiation methods (initialize, server/discover, ping) are answered without touching the limiter and carry the policy alone. A machine-readable description of the server — endpoint, transport, authorization, protocol revisions and tool list — is published at /.well-known/mcp.json.

Supported methods

Method Purpose
server/discover Stateless capability discovery. Returns protocol version, capabilities, server info and the orientation instructions.
initialize The legacy handshake. Still answered for older clients; negotiates the highest revision you both speak. Also carries the instructions.
tools/list The tool catalog, built for the authenticated account. Carries a one-hour cache hint scoped private.
tools/call Invoke a tool.
prompts/list The published workflows.
prompts/get Expand one workflow into a message, with arguments filled in.
ping Health check. Returns an empty object.

There are no MCP resources today.

Reading a tool result

Every tool returns the same envelope. The payload is a JSON document, pretty-printed into a single text block:

{
  "content": [{ "type": "text", "text": "{\n  \"found\": true,\n  …\n}" }],
  "isError": false
}

Two things about that shape are worth designing around.

A tool that fails still returns 200. A failure is reported as isError: true with the reason inside the payload, not as a JSON-RPC error. That is deliberate: a model can read what went wrong and adjust, whereas a transport-level error tells the client the whole call is broken. Only protocol problems — malformed JSON, an unknown method, bad arguments — come back as JSON-RPC errors. See errors and limits.

The text block is capped. Past the cap the JSON is cut and an explicit [TRUNCATED: …] marker is appended, so a clipped series can never read as a complete one. The cap and the exact marker are on the errors page.

How current is this result?

Different tools answer as of different moments, and the difference is not cosmetic. get_recovery_context reads the live ledger and answers as of today. get_user_facts and get_insights reflect the last time the background analysis pipeline ran. Quoting the two side by side as though they described the same moment is the specific error this next field exists to prevent.

So a tool result carries an as-of date:

{
  "found": true,
  "coverage": { "lastDay": "2026-08-09" },
  "computedAt": "2026-08-15T12:20:17.721Z",
  "dataThrough": "2026-08-09"
}

dataThrough is always a plain YYYY-MM-DD calendar date. It means this result describes your record up to and including that day — not when the result was computed. Where a tool has both, the data horizon wins: the example above was computed on the 15th from a record that ends on the 9th, and dataThrough reports the 9th, because that is the date the numbers are actually about.

Three rules for using it:

  • Do not compare two results as contemporaneous unless their dataThrough values agree. If they differ, say so in your answer rather than resolving the gap silently.
  • Absence is not a freshness claim. A tool with no dated horizon — list_metrics returns a catalog, not a series — carries no dataThrough, and neither does one whose payload does not expose a date the server can read. Treat a missing field as "unknown", never as "current".
  • It is a horizon, not a guarantee of completeness. A day inside the range can still be unlogged. Coverage fields on the individual tools tell you which days actually hold data.

Orientation

An external client starts colder than the in-app coach. The coach reaches these same handlers through a system prompt built from your profile, your columns, your labs and your correlations. A client connecting over MCP gets the protocol and nothing else, and a model with the right tools and no framing produces confident wrong answers rather than merely inefficient ones.

Three things close that gap.

Instructions. initialize and server/discover both return an instructions string, which clients fold into the model's system prompt. It describes the four layers of the data — ledger, feature matrix, precomputed statistics, narrative — and states the things that are not guessable from a schema: that r means a confounder-controlled partial correlation in get_correlation_edges and a raw Pearson in the leadLag insights section, which confidence tiers exist and what each one survived, how stale each layer can be, and the window and size budgets. It is identical for every account, so it costs nothing to cache.

A catalog built for you. tools/list stamps every tool description with your account's real context: how many days you have logged and over what span, the actual names of your custom columns, whether WHOOP is connected, and whether you have enough history for the correlation and insights engines to have run at all. Without this a model learns your thresholds by calling tools until one refuses. This is why the catalog is marked cacheScope: private — it now definitely carries one account's vocabulary, so a shared cache serving it to another user would be a real leak rather than a theoretical one.

Prompts. Four published workflows — a morning readiness check, a weekly review, a hypothesis test over any two metrics, and a single-metric explainer. Clients that render prompts, Claude Desktop among them, show these as pickable entries, so you can run a multi-tool analysis without knowing the tool graph exists. The hypothesis test is the one worth knowing about: it directs the model to check the precomputed verdict before running its own analysis, because an ad-hoc query over a short window will happily rediscover a confound the nightly scan already rejected and present it as a finding.

A minimal call

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 endpoint returns a complete medical record, so a request that looks like it came from a browser is refused. Genuine MCP clients are servers and send no Origin at all, so refusing every request that carries one costs nothing and closes the DNS-rebinding case the transport specification warns about.

Connecting over OAuth

The server is also an OAuth 2.1 authorization server, which is what makes Claude's Add custom connector work: paste the endpoint URL, approve a consent screen, done. No token, no config file.

A client with no credential POSTs to the endpoint and gets a 401 whose WWW-Authenticate header carries a resource_metadata pointer. From there the flow is entirely standard:

Endpoint Purpose
/.well-known/oauth-protected-resource Names the resource and its authorization server (RFC 9728)
/.well-known/oauth-authorization-server Endpoint locations and supported parameters (RFC 8414)
POST /api/oauth/register Dynamic client registration (RFC 7591)
GET /api/oauth/authorize Consent screen
POST /api/oauth/token Code exchange and refresh

Four constraints are worth stating because they are enforced rather than recommended:

  • Public clients only. No client secret is issued. A desktop application cannot keep one, so issuing them would create a credential that looks confidential and is not. PKCE carries the proof instead.
  • S256 only. The plain challenge method is advertised nowhere and accepted nowhere.
  • read only. The authorization server cannot issue a write scope at all. A connector asking for write is granted read, so no consent screen can be the thing that lets an outside application modify a medical record.
  • Authorization codes are single-use, and a replay is treated as theft. Presenting a code twice means one of the two holders stole it and the server cannot tell which, so it revokes the entire grant — an attacker who wins the race still ends up with nothing.

The access token a connector receives is an ordinary API token. It appears in your token list under the name of the application that asked, it carries the same read scope, and revoking it disconnects the client immediately.

Getting connected

If your client speaks OAuth, paste https://nlight.fit/api/mcp into its connector dialog and approve the screen.

Otherwise:

  1. Mint an API token in Settings → MCP / CLI Access — see token management.
  2. Add the server to your client — see client setup.
  3. Ask your client to list tools, and confirm you get eighteen back.