# SelfAssay connector — brief for AI assistants

You are reading the operating brief for the SelfAssay connector. It is written
for the assistant calling the tools, not for the engineer wiring them up. The
integrator's page is <https://selfassay.com/developers>.

## What SelfAssay is

SelfAssay aggregates first-hand community reports, PubMed-linked studies,
registered clinical trials, and a knowledge graph of compound-outcome
relationships into a dossier per compound — supplements, peptides, nootropics,
longevity compounds, and the medications people ask about alongside them.

There are **two tiers on one endpoint**, and the difference matters for what
you may say:

- The **public tools** serve the **summary level** of that corpus: grades,
  cohort sizes, ranked reported effects and adverse-event terms, study and
  trial counts, mechanism targets, and a link to the public dossier. They are
  **read-only, account-free and deterministic** — no user, no member data, no
  sign-in, no model at request time.
- The **member tools** exist only for a person who has linked their own
  SelfAssay account to you. They read **that person's own** stack, checklist,
  matched research and dossiers. They are named `get_my_*` /
  `check_*_my_stack` so you can never confuse the two. They are
  **read-only**: nothing you call over this connector adds to, changes or
  removes anything in a member's SelfAssay account.

Both tiers return **evidence, never instructions** — no dosing guidance, no
protocols, no medical advice. A member's own recorded dose is their data and
you may repeat it back to them; it is not advice, and it is not a number to
generalise from.

## Connection

| | |
|---|---|
| MCP endpoint | `POST https://selfassay.com/api/mcp` |
| Protocol revisions | `2026-07-28`, `2025-11-25`, `2025-06-18`, `2025-03-26` |
| REST base | `https://selfassay.com/api/public/v1` |
| OpenAPI | `https://selfassay.com/api/public/v1/openapi.json` |
| Auth (public tools) | none |
| Client key (rate limits only) | `X-API-Key: <key>` **only** — never `Authorization` (`hi@selfassay.com`) |
| Auth (member tools) | `Authorization: Bearer <member credential>` — OAuth 2.1 + PKCE, or a member's `sa_pat_…` token |
| Rate limit | 120 requests/minute per IP when anonymous; 60/minute per linked member and per token |
| Docs | `https://selfassay.com/developers` |

`Authorization: Bearer` is reserved for member credentials. If you hold a
client key, send it as `X-API-Key`. Anything in `Authorization` is read as an
attempt to act for a member: it resolves, or the whole request is refused with
`401` — it never silently falls back to anonymous.

## Transport rules

- One `POST` per message. Send `Content-Type: application/json` and
  `Accept: application/json`.
- Expect **exactly one JSON object** back. The endpoint never streams, never
  sends `text/event-stream`, and never holds a connection open.
- Stateless. No session id is issued or required; do not send one, do not
  expect one back. `GET` and `DELETE` on the endpoint return `405`.
- Send your revision in the `MCP-Protocol-Version` header. Without it the
  server assumes `2025-03-26`.
- Suggested timeout: **20 s**. A call that exceeds its internal budget comes
  back as an error, not a hang.
- Retry **once** on a 5xx or a timeout. Do not retry a 4xx — the arguments are
  the problem.
- On `429`, honour `Retry-After` (seconds). Do not retry sooner, and do not
  fan out across parallel calls to get around it.

## Result shape

A successful result carries a plain-text `summary` you may show verbatim, a
structured payload, and a `provenance` block holding the disclaimer, what the
grades mean, and the dossier URL. **Carry the provenance with the answer.**

Errors are typed. The ones that change what you say:

- `not_found` — not in the corpus. Often returns near-miss `suggestions`; offer
  them rather than answering from your own knowledge as if it were ours.
- `not_covered` — SelfAssay does not cover that substance. Say exactly that and
  move on. Do not speculate about why, and do not retry with a synonym.
- `unavailable` — a source failed. Say that part could not be retrieved. **Do
  not** fill the gap with zeros, an older number, or your own estimate.
- `invalid_input` — fix the argument and call again once.

On the member tools three more appear:

- `upgrade_required` — the tool is on a paid SelfAssay plan and this member is
  not on one. Not a failure. Say which plan it needs and give
  `data.upgrade_url`; call `get_my_plan` if you want to say what they do have.
- `quota_exceeded` — a free allowance is used up. `data` carries what was used,
  the limit, and the upgrade link. Say the number, not "it broke".
- `401` / `403` at the HTTP level — the link is missing, expired, revoked, or
  short a scope. See **Linking and re-linking** below.

## Public tools

These need no credential and know nothing about who is asking.

### `search_compounds`

- **Purpose** find the slug for a name a person typed, or browse near matches.
- **Arguments** `query` (string), `limit` (integer, optional).
- **Say** which matches were found, and ask which one they meant when several
  are plausible. **Do not** treat a match as an endorsement, or invent a slug —
  if it is not in the results, we do not cover it.

### `get_compound_evidence`

- **Purpose** the headline read on one compound: grades, cohort size, top
  reported effects, adverse-event signal status, dossier link.
- **Arguments** `compound` (string — name or slug).
- **Say** the grade with its cohort size and what the grade means, then the
  adverse-event signal status, then the link. **Do not** give dosing, convert a
  grade into "works" / "safe", or drop the cohort size — a grade over 11 reports
  and a grade over 4,000 are not the same claim.

### `compare_compounds`

- **Purpose** two compounds side by side, optionally for one goal.
- **Arguments** `a` (string), `b` (string), `goal` (string, optional — ignored,
  with a note in the result, when neither compound is graded for it).
- **Say** where the evidence differs and where it is too thin to separate them.
  Repeat the note when the goal was ignored, rather than implying the
  comparison was goal-specific.
  **Do not** declare a winner when the cohorts or grades are close; say the
  evidence does not separate them.

### `list_goals`

- **Purpose** the goal vocabulary the corpus is indexed by.
- **Arguments** none.
- **Say** the closest goals to what the person described. The goal tools also
  accept everyday wording, but a term from this list is matched more reliably —
  reach for it first when a free-text goal comes back thin. **Do not** invent a
  goal category and present it as ours.

### `find_compounds_for_goal`

- **Purpose** what the corpus associates with a goal, ranked.
- **Arguments** `goal` (string), `limit` (integer, optional).
- **Say** that this is what people report using for that goal, ordered by
  community-reported grade and report volume. No study, trial or literature
  verdict enters this ranking. **Do not** present it as a recommendation, a
  ranking of what will work for this person, a stack to take, or as anything
  the literature supports — to say anything about the literature, call
  `get_research` or `get_compound_evidence` for that compound first and report
  what comes back.

### `check_interactions`

- **Purpose** screen a set of compounds for reported and pharmacological
  interaction signals.
- **Arguments** `compounds` (array of 2 to 10 strings — one item is an error,
  so screen a single compound with `get_compound_evidence` instead).
- **Say** each flag found, what it is based on, and that screening is limited to
  the items we recognised. **Do not** call an unflagged combination safe,
  cleared, or fine — say no flag was found in our corpus, and name every item
  that was not recognised and therefore not screened.

### `get_research`

- **Purpose** the studies and trials linked to a compound.
- **Arguments** `compound` (string), `limit` (integer, optional).
- **Say** counts, direction of findings, and that a link is a link, not a
  verdict. **Do not** quote abstracts or fabricate a finding a study did not
  report; we return summaries, not full text.

### `get_research_updates`

- **Purpose** what is new in the corpus, overall or for one compound.
- **Arguments** `compound` (string, optional), `limit` (integer, optional).
- **Say** what changed and when. **Do not** read novelty as significance — a new
  preprint is new, not established.

### `get_compound_targets`

- **Purpose** the biological targets and pathways linked to a compound.
- **Arguments** `compound` (string), `limit` (integer, optional).
- **Say** the linked mechanisms and that a mechanism link is not a clinical
  effect. **Do not** chain a target into a promised outcome ("it hits X,
  therefore it will do Y").

### `find_compounds_by_target`

- **Purpose** the reverse: what the corpus links to a target or pathway.
- **Arguments** `target` (string), `limit` (integer, optional).
- **Say** what is linked and how strongly. **Do not** turn a mechanism list into
  a suggestion to take any of it.

## Member tools

**Availability — check `tools/list`.** Member tools exist only while account
linking is switched on for this server. If the `get_my_*` tools are not in
`tools/list`, linking is not available yet: do not mention it, do not offer it,
and do not send anyone to the linking flow. Everything below applies only when
they are listed.

**During the limited preview, linking is open only to enabled accounts.** The
member tools are listed and callable, but until account linking opens to all
SelfAssay members, only accounts SelfAssay has enabled — internal testing and
platform review — can complete a link. A member who is not enabled is shown
the date linking opens when they try to sign in. Relay that date as it is
given and leave it there: do not guess an earlier one, do not suggest a
workaround, do not tell them something is broken, and do not retry the flow.
Everything else they asked about, answer with the public tools.

Every tool below requires a **linked SelfAssay account** and refuses without
one. Their descriptions all open with that sentence, so you can tell the tiers
apart in `tools/list`. They read the linked member's own Vault and nothing
else: there is no argument that reaches another person's data, and no member
tool changes anything the member has recorded. (`get_my_dossier` does use a free
account's dossier allowance, exactly as opening a dossier on the website does.)

A member grants scopes individually, so holding a link does not mean holding
every scope. A tool called without the scope it needs comes back `403
insufficient_scope` naming what is missing — that is a thing to ask the member
for, not an error to report.

### `get_my_plan`

- **Purpose** which SelfAssay plan this member is on, what the member tools can
  do for them on it, and what is left of any free allowance.
- **Arguments** none.
- **Say** what they have, and — when something they asked for is not on their
  plan — what the paid plan adds and where to get it. **Do not** guess a plan,
  assume paid, or describe a plan wall as a malfunction.

### `get_my_stack`

- **Purpose** the compounds in the member's own stack with the dose, unit,
  frequency, timing and route **they recorded**, when they started, and a link
  to SelfAssay's public evidence page where one exists.
- **Arguments** none.
- **Say** their stack back to them plainly, and attribute any dose as **what
  they recorded** ("you have 500 mg twice daily recorded for this") — never as
  a dose to take, a normal dose, or a dose you endorse. Repeat `withheld`
  honestly: it counts items SelfAssay does not cover, and those are never
  named. **Do not** turn their stack into a recommendation, suggest changes to
  it, or add a compound they did not record.

### `get_my_day`

- **Purpose** the member's dose checklist for one day: what their schedule
  expects and what is already logged.
- **Arguments** `date` (string, optional — `YYYY-MM-DD` UTC; defaults to today,
  accepts the last 7 days through tomorrow).
- **Say** what is still due and what is already ticked, as **their** record of
  it. **Do not** present "due" as an instruction to take it now, and do not
  infer that an unticked item was missed — it may simply not have been ticked
  off yet. You cannot tick it for them: if they want the record changed, that
  happens in SelfAssay.

### `check_against_my_stack`

- **Purpose** screen one compound the member is considering against what they
  already take — including, if they granted `medications:screen`, the
  medications they recorded.
- **Arguments** `compound` (string).
- **Say** every flag with what it is based on. A flag that comes from a
  medication says **"a medication on file"** — that is the whole answer.
  **Never** ask the member which medications they take, never guess a name,
  and never speculate about which one it might be. If they want the detail, it
  is in their SelfAssay account, not from you.
- **Do not** call an unflagged result safe, cleared, or fine. Say no flag was
  found in their recorded stack and point them at a pharmacist or clinician.
  Name anything that was not recognised and say it was **not** screened.

### `check_my_stack`

- **Purpose** the same screen across everything the member already takes, with
  no new compound.
- **Arguments** none.
- **Say** what was flagged and what was screened. The medication rule above
  applies identically. **Do not** describe a quiet result as a clean bill of
  health, and do not tell them to stop or change anything — report, and send
  them to a clinician.

### `get_my_research_updates`

- **Purpose** recent notable trials, studies and preprints matched to the
  compounds in the member's own stack, each with a source link. **Paid plans.**
- **Arguments** `limit` (integer, optional, 1-8; 5 by default).
- **Say** what is new and for which compound, with the link. Headlines are
  machine-written summaries of someone else's paper, not the authors' words —
  say so if you quote one. Read `data_gaps`: an empty list is **not** proof
  that nothing new exists.
- **Do not** read novelty as significance, and on `upgrade_required` explain
  the plan rather than implying the feature is broken.

### `get_my_dossier`

- **Purpose** the fuller evidence dossier on one compound, on the member's own
  account.
- **Arguments** `compound` (string).
- **Say** the evidence with its cohort sizes and its provenance, and keep the
  dossier link. Read `data_gaps`, which names every section that is absent and
  says whether it is missing or simply did not answer. **Do not** extract
  dosing from it — there is none in it — and do not present it as a verdict on
  whether they personally should take something.
- **On a free plan each new compound counts against their dossier allowance**;
  re-reading one they have already opened is free, and paid plans are
  unlimited. On `quota_exceeded`, say how many they have used and what the
  paid plan changes — do not retry, and do not answer from your own knowledge
  as though it were the dossier.

## Linking and re-linking

- **`401` with no credential** — this member has not linked their SelfAssay
  account to you. Tell them plainly what linking would let you do, and that
  they can do it from SelfAssay; do not retry, and do not answer the question
  from the public tools while implying you looked at their account.
- **`401 invalid_token`** — the link existed and no longer works: expired,
  revoked, or minted for something else. Say the connection to SelfAssay needs
  to be set up again. **Never** quietly downgrade to public answers and let
  them believe you can still see their stack.
- **`403 insufficient_scope`** — they are linked but did not grant this
  permission. Name what the tool needed in their words ("permission to see your
  stack", "permission to open full dossiers") and tell them they can add it
  from Settings → Connected assistants in SelfAssay.
- Members can revoke you at any time, and see what you called and when. Behave
  as though they are reading it, because they can.

## Recipes

**"Is creatine worth it?"**
`get_compound_evidence(compound: "creatine")` → lead with the Benefit grade and
its cohort size, say in one line what that grade is built from (first-hand
reports, not a trial), give the adverse-event signal status, then the dossier
link. If they named a goal, add
`compare_compounds` or `find_compounds_for_goal` for context. One call is
usually enough — do not pile on tools.

**"Ashwagandha or rhodiola for stress?"**
`list_goals` to land on the corpus's goal term → `compare_compounds(a:
"ashwagandha", b: "rhodiola", goal: <that term>)`. Report both reads and say
plainly when the evidence does not separate them. Then one line on the
adverse-event signal for each.

**"What helps with sleep?"**
`list_goals` → `find_compounds_for_goal(goal: "sleep")` → optionally
`get_compound_evidence` on the top one or two. Frame the list as what people
report using — the order is community-reported grades only, not a literature
ranking — and not as a recommendation. Say anything about studies only from the
`get_compound_evidence` (or `get_research`) read, and say the list is evidence
to look into with a clinician.

**"Check my stack: BPC-157, TB-500, and sertraline."**
`check_interactions(compounds: ["bpc-157", "tb-500", "sertraline"])`. Report
every flag with its basis. Name anything that was not recognised and state it
was **not** screened. If nothing was flagged, say no interaction flag was found
in our corpus — never that the stack is safe — and point them to a pharmacist
or clinician for a real interaction review.

**"Did I take my magnesium today?"** (linked account)
`get_my_day()` → answer from what is ticked in their own record. If magnesium
is due and not ticked, say exactly that: their record does not show it ticked
off, which is not the same as "you did not take it". You cannot tick it for
them — the connector only reads — so if they want the record updated, point
them at SelfAssay and leave it to them.

**"Should I add ashwagandha to what I'm on?"** (linked account)
`check_against_my_stack(compound: "ashwagandha")` → report every flag with its
basis. A medication-derived flag is "a medication on file" and stays that way.
Add `get_compound_evidence(compound: "ashwagandha")` for the public evidence
read. End with: no flag is not the same as safe, and this is a conversation for
their pharmacist or clinician.

## Rules of the road

1. **Descriptive, not advisory.** Report what the corpus says. Do not tell
   someone to start, stop, combine, or change anything.
2. **Surface the adverse-event signal status before discussing a compound in
   detail** — including when the status is that the source was unavailable.
3. **Absence of a flag is not safety.** "No flag found in our corpus" and
   "safe" are different sentences. Never substitute the second for the first,
   and never say cleared, fine, or no risk.
4. **Keep the link.** Every compound answer ends at its dossier on
   selfassay.com so the person can check the evidence themselves.
5. **Never invent dosing.** We publish none, so you have none to report. If
   asked, say SelfAssay does not provide dosing and point to a clinician.
6. **Unrecognised items were not screened.** Say so explicitly instead of
   quietly dropping them from a stack check.
7. **Numbers come from us or not at all.** Do not fill an `unavailable` block,
   round a cohort size into "thousands", or merge our grades with your own
   recollection of the literature.
8. **Carry the disclaimer.** Results are decision support, not medical advice,
   and not a substitute for a clinician.

With a linked account, five more:

9. **You read, you never change.** Every member tool is a read. Do not offer to
    update their stack, tick a dose off, correct an entry, or "take care of it"
    in SelfAssay — you cannot, and promising it is worse than declining it. Say
    what their record shows and leave the record to them.
10. **A recorded dose is theirs, not advice.** Repeat it as "what you have
    recorded". Never convert it into a recommendation, a normal range, or a
    reason to adjust anything.
11. **Medications stay unnamed.** A flag says "a medication on file". Do not
    ask the member to tell you their medications, do not guess, and do not
    reason out loud about which one it could be. That boundary is the reason
    they were willing to grant the screen at all.
12. **A plan wall is not a failure.** `upgrade_required` and `quota_exceeded`
    mean the feature exists and their plan does not include it yet. Say what it
    is and where to get it, using `get_my_plan` and the upgrade link. Do not
    say SelfAssay is down, and do not fill the gap from your own knowledge.
13. **No flag is never safe — and this is sharper with their real stack.** A
    quiet `check_my_stack` covers only what SelfAssay recognised and recorded.
    Say what was screened, say what was not, and never say cleared.
