# Alerts (https://www.lurq.run/docs/alerts)



lurq watches your repositories and MCP servers and tells you when something
changes. Every alert is also on the dashboard; these channels decide what reaches
you there without looking.

## Email [#email]

* **Urgent alerts** are on by default and fire only for changes that need you
  today: an MCP server rewrote a tool description so it now instructs your agent,
  a tool stopped being read-only, or a breaking release that your repository's
  version range will install on its own. One email per check, at most three a day.
* **The weekly summary** is off until you turn it on. It arrives on Mondays and
  covers everything that changed that week.

Both are set on **Dashboard → Notifications**, and every email has a one-click
unsubscribe link.

## Slack, Discord and Teams [#slack-discord-and-teams]

Paste an incoming webhook URL on **Dashboard → Notifications → Channels** and
choose the lowest severity to send. lurq posts a test message before saving the
channel. Each channel then receives one batched message per check with every
change at or above that severity.

* **Slack:** create an incoming webhook (`https://hooks.slack.com/services/…`).
* **Discord:** Server settings → Integrations → Webhooks (`https://discord.com/api/webhooks/…`).
* **Teams:** use the **Workflows** app's "Send webhook alerts to a channel" template.
  Office 365 connector URLs were retired by Microsoft in May 2026 and are refused.

Channels are part of the Team plan. A channel whose URL stops working (deleted,
revoked) is switched off with the reason shown, and can be resumed.

Webhook URLs are credentials, so lurq stores them encrypted and never shows them
again after you add them.

## Webhooks [#webhooks]

A generic webhook receives a JSON `POST`:

```json
{
  "type": "lurq.alerts",
  "delivery": "channel:12:4f1c…",
  "sentAt": "2026-09-14T13:02:11.000Z",
  "dashboardUrl": "https://lurq.run/dashboard/notifications",
  "more": 0,
  "items": [
    {
      "key": "mcp:381",
      "severity": "critical",
      "source": "mcp",
      "title": "notes: add_note now instructs your agent",
      "detail": "1 tool(s) rewrote their description and now instruct the model: add_note",
      "url": "https://lurq.run/dashboard/mcp/41"
    }
  ]
}
```

`X-Lurq-Delivery` identifies the delivery; a retry repeats it, so use it to
dedupe. `X-Lurq-Signature` is `t=<unix seconds>,v1=<hex>`, an HMAC-SHA256 of
`<t>.<raw body>` with the signing secret shown once when you add the webhook.
Verify it against the raw body and reject old timestamps:

```ts
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(secret: string, header: string, rawBody: string): boolean {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=', 2)));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? '', 'hex');
  return given.length === expected.length && timingSafeEqual(given, expected);
}
```

## GitHub issue [#github-issue]

`lurq mcp-ci` writes a workflow that, besides scanning daily, keeps one pinned
**lurq dashboard** issue in the repository current: your MCP servers, what needs
attention, what changed, and the dependency upgrades waiting. The body is edited
in place, which notifies nobody; lurq comments, which does notify watchers, only
when a scan finds something urgent, and never twice for the same thing.

Close the issue to stop lurq updating it, or generate the workflow with
`lurq mcp-ci --no-issue` to leave it out. Text from MCP servers is escaped in the
issue, so a tool description cannot mention anyone or link anywhere.

## Remote MCP servers you run or pinned [#remote-mcp-servers-you-run-or-pinned]

lurq re-reads every remote MCP server in the official registry on a schedule,
without credentials, the same way a client first meets it. When one of those
servers changes, the accounts that depend on it are told — through the channels
above and through the agent itself, on its next tool call.

You depend on a server, in lurq's terms, when either is true:

* **You run it.** A `lurq mcp-scan` you uploaded included it. Nothing else to do:
  the scan you already ran is the subscription, and you are not asked to rescan.
* **You pinned it.** `lurq mcp-pin <url-or-registry-name>` records its tools and
  its sign-in path as approved. Re-pinning approves a change you have reviewed.

Three kinds of change are urgent, and they are the ones nobody would otherwise
notice until an agent failed:

* **its tools changed** — a tool removed, a parameter that became required, a
  description rewritten into an instruction to the model
* **its sign-in path changed** — a new authorization server, or a registration
  method (Client ID Metadata Documents, Dynamic Client Registration) or PKCE
  support that your client relies on is gone
* **it stopped answering** — calls to it will fail until it is back

Everything else reaches Slack, Discord, Teams and webhooks at its own severity,
and the weekly summary. Acknowledging is per account: one change on a shared
server, and every account that depends on it keeps its own unread state. Each
alert links to `/dashboard/mcp/public/<id>`, which shows the change, which
clients the server now works in, and a button to approve it.


# Keeping a repo current (https://www.lurq.run/docs/autopilot)



The autopilot is a workflow file **you** commit. lurq supplies the evidence — what
you are behind on, and which of the symbols an upgrade removes your code actually
references — and your own runner does the work.

```bash
# The file is rendered per repo, with your package manager and your policy baked in.
# Get it from the dashboard: Repositories → your repo → workflow → Create on GitHub
```

lurq does not commit it for you. Its GitHub app cannot write a byte to your
repository, so the thing granting write access is a commit you made and can
revert.

## Three modes [#three-modes]

| mode      | what it does                                                                                                                                                      | needs                                    |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| `comment` | Plans the upgrades, checks them against your code, writes the brief to the run summary. Changes nothing.                                                          | `LURQ_API_KEY`                           |
| `fix`     | Opens a pull request containing only what the package itself proves: renamed call sites, and the range bump in every manifest declaring the dependency. No model. | `LURQ_API_KEY`                           |
| `pr`      | Everything `fix` does, then an agent migrates what a rule cannot and runs your tests.                                                                             | `LURQ_API_KEY` + an Anthropic credential |

A newly armed repo gets `fix`. It is the mode that cannot fail for want of a
credential, and it still opens pull requests — with the call sites already
migrated, which is more than a version bump.

Change the mode on the repository page. Each run reads the setting when it
starts, so it governs repos that already installed the workflow; only a file
committed before that behaviour existed keeps its own baked-in mode.

## Repository secrets [#repository-secrets]

`LURQ_API_KEY` is required. It lets the workflow ask which upgrades are
outstanding. Create one under **API keys** in the dashboard.

For `pr` mode only, add **one** of these:

```bash
# Already on Claude Pro, Max, Team or Enterprise:
claude setup-token
```

That prints a token to the terminal and stores it nowhere — copy it before you
close the window, and paste it into a repository secret named
`CLAUDE_CODE_OAUTH_TOKEN`. Anthropic documents this as the mechanism for CI,
where an interactive browser login is not available.

<Callout type="warn">
  **That token expires one year after you create it**, with no renewal and no warning. A weekly job
  runs fine until it lapses and then fails. An API key from the Anthropic console does not expire,
  and is what Anthropic recommends for a secret shared across repositories — an OAuth token belongs
  to whoever ran `claude setup-token`, and dies with their subscription.
</Callout>

`ANTHROPIC_API_KEY` from [the Anthropic console](https://platform.claude.com)
works instead, and bills per token. The action also supports Bedrock, Vertex and
Microsoft Foundry through workload identity federation, which stores no secret in
the repository at all; the generated workflow does not wire those up, so that
needs an edit to the file.

Setting both is safe: an unset secret arrives as an empty string, which the
action treats as absent.

## Let an agent do it [#let-an-agent-do-it]

The dashboard's **copy for agent** button on a repository page gives your coding
agent the whole job with that repo's own workflow file already in it. If you would
rather start from here, paste this and fill in the repo name:

```text
Set up lurq's dependency autopilot on <owner/repo>.

1. Ask me for a lurq API key — I create it at https://www.lurq.run/dashboard/keys.
   Never print it back to me, never write it to a file, never commit it.
2. Set it as a repository secret, reading the value from stdin so it stays out of
   my shell history:
      gh secret set LURQ_API_KEY --repo <owner/repo>
3. Get the workflow file: open the repository on https://www.lurq.run/dashboard/repos,
   use "copy file", and write it to .github/workflows/lurq-upgrade.yml exactly as
   given. Do not reformat it, do not change the cron, and do not touch the
   permissions block — that block is the trust boundary and it is deliberately
   minimal.
4. Show me the diff and stop. Do not push: committing this file is what grants
   write access to my repository, and I want to read it first.
5. Only if I say I want pr mode, ask which Anthropic credential I want and set it
   the same way as step 2 — CLAUDE_CODE_OAUTH_TOKEN from `claude setup-token`
   (Pro/Max, expires after one year with no warning) or ANTHROPIC_API_KEY from the
   Anthropic console (does not expire). comment and fix modes need neither.

Then tell me in one line what you set and what is left for me to do.
```

The per-repo version from the dashboard is better where you have the choice: it
carries the rendered file, so the agent does not have to fetch anything, and it
already knows which mode the repo is set to.

## When it runs [#when-it-runs]

Weekly by default, at 06:00 UTC on Monday. A repo whose policy scope is
`security` runs **daily** instead, because a weekly schedule can mean seven days
sitting on a known advisory.

You can also start a run by hand from the Actions tab, and lurq starts one for you
when a dependency you declare ships a new major — so a breaking release reaches
you the day it lands rather than up to six days later.

## What it can and cannot touch [#what-it-can-and-cannot-touch]

Read the `permissions:` block at the top of the file first; it is the whole trust
model.

* lurq's GitHub app is read-only on your code. It can read your manifests and
  **start** this workflow. It cannot write to your repository, change this file,
  or set a repository variable.
* Every commit, branch and pull request is made by the workflow's own
  `GITHUB_TOKEN`, scoped to that one repo, limited to what the file declares.
* The agent's tool allowlist has no `git` and no network tool. It edits files;
  the workflow does version control. A prompt injection in a changelog cannot
  push a branch.
* Nothing lands on your default branch. Auto-merge is off unless you opt in, and
  it defers to your own required checks.

Turning the whole thing off is `git rm .github/workflows/lurq-upgrade.yml`.

## If it runs but never opens anything [#if-it-runs-but-never-opens-anything]

The dashboard flags this on the repositories list as **analysing only**. Two
causes, and the mode tells you which:

* **Set to `pr`** — most likely the Anthropic credential is missing or the OAuth
  token has expired. The run reports its analysis *before* the credential check,
  so you see a completed analysis and a failed job. Check the secret first.
* **Set to `fix`** — no credential is involved, so the workflow file is the
  problem: it predates lurq reading the mode at run time, or pins its own. Re-copy
  it from the repository page.

A repo set to `comment` that only analyses is not flagged. That is what it was
asked to do.


# CLI reference (https://www.lurq.run/docs/cli)



The lurq engine is usable standalone from the terminal, with the same scoring and
retrieval the MCP tools expose. Add `--json` to the query and check commands for
machine-readable output.

<Callout type="info">
  Run `lurq setup` first. The query commands read whichever index is configured: the hosted service
  via your stored API key, or your own Postgres if `DATABASE_URL` is set and no key is. A key wins
  over `DATABASE_URL`: otherwise, running `lurq` inside your own Postgres-backed app would point
  package lookups at that app's database. Operators holding both can force the local index with
  `LURQ_LOCAL=1`. See [Self-hosting](/self-hosting).
</Callout>

## Querying the index [#querying-the-index]

```bash
lurq verify lodahs
lurq evaluate zod
lurq compare drizzle-orm prisma typeorm
```

Not sure which command answers your question? Ask in plain words:

```bash
lurq can "check a major upgrade"
```

`can` searches the catalog that ships with the CLI, so it works offline and
before setup.

## API surfaces [#api-surfaces]

What a version actually exports, and what moved since the version you know:

```bash
lurq usage zod                       # the current export surface
lurq usage zod --known 3.22.4        # …plus the exact delta from the version you know
lurq usage react --target 19.0.0     # pin the target version
lurq versions react                  # the stored version timeline (local index only)
```

`--known` is the one worth remembering: pass the version your model thinks it
knows and get the precise added / removed / renamed / changed list for the
version you're actually installing.

## Whole stacks and projects [#whole-stacks-and-projects]

```bash
lurq compat next react react-dom             # do these install together?
lurq compat next react --pin next=15.5.4     # check exact versions
lurq audit                                   # everything this project depends on
lurq audit --project-only                    # …ignoring user-level agent configs
```

`--pin` requires an exact published version. A range would silently resolve to
`latest` and report the wrong version as pinned, so it's rejected outright.

`audit` reads the project's `package.json`, lockfile, and the MCP servers
configured for it, and reports what is outdated, deprecated, carries an advisory
for the installed version, or has drifted. Anything it could not assess is
counted as such, never reported clean.

## Upgrades [#upgrades]

These run where your code is — a laptop or a CI runner:

```bash
# what's behind, and what each upgrade removes from its API  (needs your lurq key)
lurq upgrade-plan . --json > lurq-plan.json

# do those upgrades remove symbols YOUR code references?      (needs nothing)
lurq check-upgrade . --plan lurq-plan.json --exit-code

# or name the upgrades directly, no plan needed
lurq check-upgrade . --upgrade commander@11.1.0..12.1.0 --upgrade zod@3.23.8..4.0.0

# and write the changes that need no judgement
lurq fix .
```

`check-upgrade` compares both versions' npm tarballs against the symbols your
codebase actually imports, and reports them at `file:line`. It needs no API key,
no network to lurq, and **no test suite** — which is the point: it catches
breakage in code no test covers. `--exit-code` exits 1 when the report is not
safe, for use as a CI gate. `--upgrade` takes `pkg@from..to`, and repeats.

When the package itself proves a rename (the old version exported both names
from the same function), the replacement is printed next to the call site:

```text
BLOCKING  cookie  1.1.1 → 2.0.1
  Removes 1 symbol(s) your code references:
    · cookie.parse → parseCookie    src/session.ts:239
```

It also type-checks the files that import each package, twice: once against the
old version's type definitions and once against the new one's, straight from the
tarballs, with nothing installed. Only errors the upgrade introduces are
reported, so a renamed option or a narrowed parameter shows up at `file:line`
even though the runtime exports never changed. It uses your project's own
`typescript` when it has one, needs a `tsconfig.json`, and skips packages that
ship no type definitions of their own. `--no-types` turns it off, for a faster,
runtime-only check.

What it reports:

| Severity     | What changed                                                                                                                  | Real example                                         |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| **blocking** | a symbol your code uses is gone                                                                                               | `cookie.parse`, removed in cookie 2                  |
| **blocking** | a deep import is no longer offered                                                                                            | `require('uuid/v4')` on uuid 8                       |
| **blocking** | the package went ESM-only, and your code calls what `require()` returned or reads a property it no longer exports             | `fetch(url)` on node-fetch 3, `chalk.red` on chalk 5 |
| **warning**  | a call passes an argument count the new version no longer accepts. Calls that still fit are not reported                      | `src/log.ts:31 passes 1`                             |
| **warning**  | the type check finds a new compiler error                                                                                     | `TS2554 Expected 2-3 arguments` on zod 4             |
| **warning**  | `require()` of a now-ESM package, which throws on Node before 20.19 / 22.12. Skipped when your `engines.node` rules those out | execa 9                                              |
| **warning**  | a new `engines.node` or peer range this project does not meet                                                                 | react-dom 19 with react 18 installed                 |

Downloads are retried on registry errors and checked against the integrity hash
the registry publishes. A version range (`^2.0.0`) is refused rather than
guessed at, and a scan that stops at its file limit says so.

An upgrade it could not establish is reported as `unverified` and is **never**
counted as safe.

`--report` sends the result to your lurq dashboard as well (it needs an API key,
from `--api-key` or `LURQ_API_KEY`). It's opt-in and never changes the outcome of
the check.

### Writing the fix [#writing-the-fix]

`check-upgrade` tells you what breaks. `lurq fix` writes the part of it that
needs no judgement, and hands the rest to your agent with the facts it needs.

```bash
# work out what moved, and show the diff       (one lookup needs your lurq key)
lurq fix .

# name the versions yourself, and it needs nothing
lurq fix . --upgrade cookie@1.1.1..2.0.1

# write the files
lurq fix . --apply

# file every finding as a GitHub code scanning alert
lurq fix . --sarif lurq.sarif
```

With neither `--plan` nor `--upgrade` it works out what moved by itself, so you
never have to look up a version to pass one in. That single lookup is the only
part that talks to lurq, and it applies your repo's selection policy — an
upgrade the policy holds is not attempted. Naming the versions yourself keeps
the entire run offline, exactly like `check-upgrade`.

Nothing is written until `--apply`; the default prints a unified diff, and
`git apply` accepts it:

```text
Would write 2 file(s) (pass --apply):
  rename parse to parseCookie — src/alias.ts, src/app.ts

--- a/src/app.ts
+++ b/src/app.ts
@@ -1,4 +1,4 @@
-import { parse } from 'cookie';
+import { parseCookie } from 'cookie';
```

It bumps the declared range in the same run. Rewriting the imports and leaving
`"cookie": "^1.1.1"` in `package.json` is a tree that does not build: the code
now calls an export the installed version lacks, and the next `npm install`
faithfully reinstalls the old one. Every manifest declaring the package is
bumped, across workspaces, keeping the operator you wrote (`^`, `~`, or an exact
pin). A range that already admits the new version is left alone rather than
narrowed, and one whose intent is a judgement — `>=1.0.0 <2`, `1 || 2`, `1.x` —
is reported instead of guessed at.

Only a rename the package itself proves is ever written — the old version
exported both names from one declaration and the new one still exports the
survivor. That makes the rewrite mechanical rather than a guess. Whether a given
upgrade qualifies depends on the exact versions: cookie 1.1.1 → 2.0.1 does,
because 1.1.x shipped `parseCookie` alongside `parse`; 1.0.2 → 2.0.0 does not.

Everything else becomes a brief instead of an edit, carrying the exports the new
version actually ships with their kinds and arities — the facts an agent doing
the rewrite cannot look up, since it has no network and its training data is the
stale source this is correcting. Two candidates is a choice, not a fix, so it is
briefed too.

Refusals are per file. A file where the imported name is declared twice can't
have its call sites attributed to the import, so that file is briefed while the
rest are still rewritten:

```text
1 change(s) lurq will not make for you:
  parse was renamed to parseCookie, but 1 file(s) need a person:
  src/shadowed.ts (parse is declared more than once)
```

An aliased import is rewritten once, at the import, because every call site
still reads the local name. Type-only references are skipped, subpath imports
keep their own surface, and every edit records the bytes it was computed from —
so if a file changes between the scan and the write, nothing is written at all
rather than a rename landing in the wrong place.

Three kinds of upgrade it will not attempt at all — and it names each one rather
than passing over it in silence:

| Not attempted                                    | Why                                                                                                                                                                |
| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| A multi-major upgrade (`hops` in the plan)       | It has to be walked one major at a time. Jumping straight to the target skips whatever each intermediate release removed.                                          |
| One the plan could not sequence (`sequenceNote`) | That is a migration, not an upgrade, and nothing mechanical should attempt it.                                                                                     |
| Anything past `--max <n>`                        | The blast radius. A deterministic edit is safer than a model's, but a diff touching forty packages is still a review nobody finishes, and the number is your call. |

`check-upgrade` still reports on all three: checking an upgrade is free and tells
you something, so only *performing* one is capped. If every upgrade in a plan
falls into these, `fix` says so and names them — it never reports "nothing to
fix" for work it declined to do.

`--sarif` writes SARIF 2.1.0 for `github/codeql-action/upload-sarif`, which
gives each finding dedup, assignment and closing itself when it stops
reproducing. Proven renames carry the replacement as a SARIF fix, so the
suggested change is visible in the alert; briefs carry the agent instruction
instead. `--exit-code` exits 1 when anything is left for a person or an agent to
do, and `--json` prints the whole result.

## Before you publish [#before-you-publish]

The same diff, pointed the other way: will your change break the people who
depend on you?

```bash
# does the version in package.json match what this release did to the API?
lurq check-release --exit-code
lurq check-release --against 2.3.0           # compare with a specific published version

# does this change break callers of your own HTTP API?
lurq check-api openapi.yaml --against main --exit-code
```

`check-release` compares the package in the directory with its latest published
version (or `--against`), and `--exit-code` exits 1 when the version bump
understates the change — a removal shipped as a minor, say. Use it in
`prepublishOnly` or CI. It needs no key and no network to lurq.

`check-api` compares an OpenAPI document with the same file at a git revision
(`--against`, default `HEAD`), and exits 1 on a breaking change with
`--exit-code`. With no argument it uses the first OpenAPI document it finds;
`-C <dir>` points at another repository. Nothing leaves the machine.

## Environment [#environment]

```bash
lurq check-env .
```

Which variables does this project read that nothing declares — the "works on my
machine" gap. It reads your own source and your `.env*` files, needs no API key
and no network, and never looks at a value: a declared name is the text left of
the first `=`, and the rest is not parsed.

A commented declaration counts. `.env.example` files conventionally document
optional variables as `# REDIS_URL=…`, and that still tells a fresh clone the
variable exists.

Three things are deliberately never reported, because a noisy check gets
switched off and a check nobody runs is worth less than no check:

| Not reported                                                                                                       | Why                                                                     |
| ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| Platform variables — `NODE_ENV`, `CI`, `PORT`, the `GITHUB_*`, `npm_*`, `VERCEL_*` families, XDG and Windows paths | Supplied by the runtime or the runner; they belong in no `.env.example` |
| Reads inside tests, specs and fixtures                                                                             | The harness that runs that code sets them itself                        |
| A read with its fallback beside it — `process.env.CACHE_TTL ?? '60'`                                               | The code already states what happens without it                         |

A variable defaulted at every site is optional; one unguarded read anywhere
makes it required, and only those sites are cited. Reads are found through the
TypeScript AST, so a `process.env.X` inside a comment or a string is not a read,
and `process.env[key]` is skipped rather than guessed at.

`--exit-code` exits 1 when anything is undeclared **or** when the scan stopped
at its file limit — a file nobody opened is not a clean result. `--sarif` files
each variable as a GitHub code scanning alert; `--json` prints every finding
with all of its read sites.

## MCP servers [#mcp-servers]

```bash
lurq mcp-scan                                  # read every configured server's real contract
lurq mcp-ci                                    # a GitHub Actions workflow that rescans daily
lurq mcp-stack                                 # do your configured servers collide?
lurq mcp-surface @modelcontextprotocol/server-filesystem
lurq mcp-drift @modelcontextprotocol/server-filesystem --from <version> --to <version>
lurq connect-check https://mcp.example.com/mcp --client chatgpt
lurq mcp-pin io.github.acme/weather            # tell me when this server changes
lurq mcp-pins                                  # what changed since you pinned it
lurq mcp-unpin io.github.acme/weather
```

`mcp-scan` and `mcp-ci` have their own page: see
[Scanning your MCP servers](/mcp-scan). `mcp-surface` returns a published
server's tool contract as lurq probed it (`--version` for an exact version);
`mcp-drift` needs both `--from` and `--to`, and reports what moved between them,
including silent schema changes and tools that stopped being read-only.

`connect-check` takes an endpoint URL, an official registry name or an npm
package name, and answers for every MCP client lurq has a profile for — or one
client with `--client` — whether the server works there, what setup it needs (a
key to send as a header, an OAuth client to pre-register and the redirect URIs
to allow), or why it is blocked. With `--client` it also prints the config to
paste in that client's own format. A URL lurq has not probed is read live and
not stored.

`mcp-pin` records a remote server as it is now, so lurq tells this account when
its tools, its sign-in path or its availability change; `mcp-pins` lists what
changed since, and re-running `mcp-pin` approves a change you reviewed. Servers
in your uploaded `mcp-scan` results are watched the same way without pinning.
These three need an API key, because a pin belongs to an account.

## Scoring controls [#scoring-controls]

```bash
lurq weights                                  # show & explain the weight model
lurq edit-weights --set composite.lambda=0.5  # override a weight (or --reset)
lurq edit-weights --explain adoption          # what a component measures
```

Both work without an index: the weight model ships with the package. Overrides
are written to your user config, or to `.lurq/weights.json` in the project with
`--project`.

## Selection policy [#selection-policy]

Keep the rules your agents are held to in a file, review changes in a pull
request, and apply them from CI.

```bash
# download the current policy
lurq policy pull lurq.policy.json

# in a PR check: is the file a valid policy? sends nothing
lurq policy push lurq.policy.json --check

# after merge: replace the policy with the file
lurq policy push lurq.policy.json

# who changed the policy, from where, and what changed
lurq policy history

# what agents were refused, or warned about, in the last 30 days
lurq policy log --days 30
```

`push --check` prints the change the file would make when an API key is
available, and every `push` prints the change it made. An invalid file is
rejected with the field that is wrong.

Two fields are worth knowing when you edit the file by hand:

* `"mode": "warn"` reports what the rules would refuse without refusing it.
  Roll a new rule out in warn mode, read `lurq policy log`, then switch to
  `"enforce"`.
* An exception can carry a reason and an expiry:
  `{ "name": "moment", "reason": "legacy date code", "expires": "2027-01-01" }`.
  On the expiry date it stops applying, and stays in the file until you remove it.

`push` replaces the whole policy. It needs an API key with the `policy:write`
scope, created in the dashboard on a Team or Business plan. The key `lurq setup`
stores for your agents can read policy but never change it, so an agent cannot
loosen its own rules. How far back `policy log` reaches depends on the plan (see
[Plans & limits](/plans-and-troubleshooting)).

## Setup and credentials [#setup-and-credentials]

```bash
# interactive: sign in in the browser, then wire up every assistant found
lurq setup

# scriptable: no prompts, no browser
lurq setup --yes --no-open --api-key <key> --agent all

# just one assistant
lurq setup --agent claude-code

# point at your own serve-http instead of api.lurq.run
lurq setup --url https://lurq.internal/mcp

# forget the stored key on this machine (agents keep theirs)
lurq logout

# remove lurq from every agent and this machine
lurq uninstall
```

`--agent` takes `claude-code`, `cursor`, `windsurf`, `copilot`, `codex`,
`gemini-cli`, `antigravity`, `kiro`, or `all`. See
[per-agent configuration](/quickstart#per-agent-configuration) for what each one
gets, and [Uninstall](/quickstart#uninstall) for what `uninstall` removes.

The endpoint is stored with the key, so a later bare `lurq setup` stays on your
server. `LURQ_ENDPOINT` overrides it for a single command without changing what
is stored.

`lurq install` and `lurq login` are aliases for `lurq setup`.

`install-skill` is the older, narrower command: it writes MCP entries and skill
files but does **not** store the key for the CLI. Prefer `setup`.

```bash
# self-host: wire your agent to a local stdio server instead of the hosted API
lurq install-skill --local
```

## Output format [#output-format]

The query, check and scan commands support `--json`:

```bash
lurq evaluate zod --json
```

This emits structured output suitable for piping into other tools, the same
shape the MCP layer serializes.


# How it works (https://www.lurq.run/docs/how-it-works)



lurq's scores are computed from public signals, **never hand-written**. This
page sketches the pipeline from raw data to the answer your agent receives.

## Where the evidence comes from [#where-the-evidence-comes-from]

Three kinds, and the distinction is the whole point.

**Readable** — npm, GitHub, deps.dev, OSV, and bundlephobia, re-synced daily.
Downloads, release cadence, maintenance, advisories, deprecations, license,
bundle cost. This is what scoring runs on.

**Executed** — an isolated sandbox that installs a package version, imports it,
and records what happened. Co-installing a set is how compatibility is
*established*: a successful co-install is positive proof two versions coexist, a
failure is proof they conflict, and the error is kept as evidence.

**Extracted** — the shipped `.d.ts` and the shipped JavaScript of an exact
version, parsed deterministically. No model is involved anywhere in this path.
This is what `usage`, `resolve_surface`, and `diff_surface` answer from, and it's
the only way to know that the function your agent is about to call was removed
two majors ago.

Facts of the second and third kind appear in no changelog and no model's training
data, and they go stale unless someone keeps re-running the experiment.

## The pipeline [#the-pipeline]

```text
public APIs (npm, github, deps.dev, osv, bundlephobia)
  → ingestion
  → scoring (health + quality + field evidence + confidence)
  → summaries / usage guides
  → embeddings
  → postgres + pgvector  (hybrid lexical + semantic search)
  → { stdio MCP server, HTTP MCP service, CLI }

npm tarballs / CDN .d.ts → surface extraction → api surfaces + symbol diffs
sandbox co-install       → compatibility edges
```

Each stage is idempotent and tolerant of a single source being down, so a flaky
upstream API degrades one signal rather than failing the whole index.

## Scoring axes [#scoring-axes]

A package's composite score blends two axes:

* **Health**: signals like maintenance cadence, adoption, reliability, and
  efficiency, drawn from release history, issue activity, dependents, and bundle
  size.
* **Quality**: a complementary axis so a brand-new but well-built package
  isn't penalized purely for being young.

The two are combined with a tunable weight (`composite.lambda`). You can
inspect the model with `lurq weights` and adjust it locally with
`lurq edit-weights`, see the [CLI reference](/cli#scoring-controls).

## Confidence [#confidence]

Confidence labels (`proven`, `emerging`, `promising`, `unproven`) reflect the
**strength of the discovered evidence**, not popularity alone. This matters for
newer dependencies: a package can be young and lightly used yet still be a sound
choice, and lurq surfaces it with an honest confidence label rather than
hiding it.

## Search [#search]

Retrieval is **hybrid**: two independent searches run over the same candidate
set — lexical matching (Postgres full-text) for exact names and keywords, and
semantic vector search via pgvector for intent-based queries like "debounce a
function." This catches both "I know the name" and "I know what I want it to do."

The two are merged with **Reciprocal Rank Fusion**, which combines them using
only each result's *position* in its list. That's deliberate: it means two
incomparable scoring scales never have to be normalised against each other, and
an exact-name hit the vector search ranks poorly still surfaces. The fused
relevance is then blended with the composite score, so results are both relevant
and good.

**No model sits in the ranking path.** That's why answers are fast, cheap, and
the same for the same question.

## Freshness [#freshness]

The index is refreshed on a schedule, and a package nobody has asked about yet
is fetched and scored the first time someone does. Every response includes a `dataAsOf` timestamp so your
agent can reason about how current the evidence is. (Self-hosters run the refresh
themselves, see [Self-hosting](/self-hosting).)


# Introduction (https://www.lurq.run/docs)



**lurq** is the **verification layer for everything your coding agent
installs**. Before an agent adds a package, pins a version, writes an import, or
wires in an MCP server, lurq checks it against live evidence: does it exist, is
it safe, does it install alongside the rest of the stack, and does the version
actually export what the agent is about to call. It reaches your agent as an MCP
server, a CLI, and an installable agent skill.

Think of lurq as a **companion to your coding agent**: your agent writes the
code; lurq checks what it is about to depend on. It's tuned for token cost,
speed, and retrieval quality.

## Why it exists [#why-it-exists]

Coding agents are good at writing code but bad at judging dependencies. They:

* reach for packages frozen at their **training cutoff**, missing anything newer;
* **hallucinate** package names that were never published, which typosquatters
  then register;
* lean on whatever was popular when the model was trained, not what's healthy now.

…and they write code against APIs that moved. A package name can be right,
healthy, and current, and the call your agent just wrote still wrong, because the
symbol it used was removed three majors ago.

lurq fixes this with a live index built from **public signals** (npm, GitHub,
deps.dev, OSV, bundlephobia), plus two things metadata can't tell you: what a
version **actually exports**, extracted from its shipped code, and whether a set
of packages **actually installs together**, established by running it. Scores are
computed, never hand-written, and every response carries a `dataAsOf` timestamp.

## What you get [#what-you-get]

Once connected, your agent can call fifteen MCP tools:

**Before installing**

* [`verify`](/mcp-tools#verify): is a package real, healthy, and safe?
* [`evaluate`](/mcp-tools#evaluate): full evidence read for one package
* [`compare`](/mcp-tools#compare): rank 2–5 packages by health
* [`policy`](/mcp-tools#policy): the rules your account holds agents to

**Whole stacks and projects**

* [`compat`](/mcp-tools#compat): will these packages actually install together?
* [`audit`](/mcp-tools#audit): a whole project's dependencies and MCP servers in one call
* [`diagram`](/mcp-tools#diagram): a reference-architecture diagram for a stack

**Writing the code**

* [`usage`](/mcp-tools#usage): a version's real API, and what changed since the one you know
* [`resolve_surface`](/mcp-tools#resolve_surface): exactly what a version exports at runtime
* [`diff_surface`](/mcp-tools#diff_surface): what a version bump adds, removes, or re-shapes

**MCP servers**

* [`mcp_surface`](/mcp-tools#mcp_surface): a server's real tool contract
* [`mcp_drift`](/mcp-tools#mcp_drift): what a server changed between two versions
* [`mcp_stack`](/mcp-tools#mcp_stack): do these servers collide in one agent?

**Housekeeping**

* [`capabilities`](/mcp-tools#capabilities): which lurq tool answers this situation
* [`report_outcome`](/mcp-tools#report_outcome): tell lurq how a pick worked out

The same engine is available from your terminal — see the
[CLI reference](/cli) — including `lurq check-upgrade`, which tells you
whether an upgrade removes a symbol *your* code references, with no test suite
required, and `lurq mcp-scan`, which reads what every MCP server you have
configured really exposes.

## Scope [#scope]

<Callout type="info">
  v1 covers the **JavaScript / TypeScript web stack (npm) only**, plus MCP servers from any source.
  More package ecosystems are on the roadmap.
</Callout>

Ready to connect your agent? Head to the [Quick start](/quickstart).


# Scanning your MCP servers (https://www.lurq.run/docs/mcp-scan)



`lurq mcp-scan` connects to every MCP server you have configured, with your own
configuration, and reads its whole contract: tools, parameters, annotations,
prompts and server instructions. It works for any server your agent can reach:
npm, PyPI, Docker, remote, or private.

```bash
npx lurqrun mcp-scan
```

It reads the same config files your agents do: `.mcp.json`, `.cursor/mcp.json`,
`.vscode/mcp.json`, `.windsurf/mcp.json`, `~/.claude.json` (including Claude
Code's per-project servers), `~/.cursor/mcp.json`, Windsurf and Gemini.

## What it reports [#what-it-reports]

* **What each tool does.** Capability labels like `shell.exec`,
  `filesystem.write` or `messaging.send`, read from tool names, parameters and
  descriptions, independently of the server's own annotations. A tool that
  declares itself read-only and reads as `delete_file` is flagged.
* **What the server tells your agent.** Every description, parameter doc,
  prompt and instruction is checked for the known attack shapes: hidden Unicode
  (decoded), instructions to ignore other instructions or keep things from you,
  credential paths paired with instructions, capture hosts, and one server
  describing another server's tools.
* **Name collisions.** Two servers exposing the same tool name leave your agent
  unable to say which one it means.
* **What changed since the last scan.** A tool removed, a parameter made
  required, a tool that stopped being read-only, and a description rewritten
  after you approved the server. A rewrite that now instructs the model is
  reported as critical.

## Safety [#safety]

* **Servers committed to a repository are not launched without approval.** A
  `.mcp.json` in a repo you just cloned is someone else's command line. lurq
  honours the approvals you gave in Claude Code, asks before launching anything
  else, and never launches a server you declined. `--trust-project` skips the
  question.
* **Credentials never leave your machine.** Values from `env` and `headers` are
  used to launch the server and are scrubbed from everything stored, printed or
  uploaded.
* **Resource URIs are counted, never kept.** A filesystem server lists your files
  there.

## History [#history]

The last snapshot of each server is kept on your machine, owner-only, so change
detection works with no account. With a key configured (`lurq setup`), each scan
is also recorded to your account, private to you. The dashboard then shows every
server's contract, findings and change history across your machines and CI.

Scans of published npm and PyPI servers are offered as corroboration for the
public index: a version's contract is promoted only when several accounts read
the same one. `--no-contribute` opts out; `--no-upload` keeps a scan local.

## In CI [#in-ci]

```bash
npx lurqrun mcp-ci
```

This writes `.github/workflows/lurq-mcp-scan.yml`, which rescans the servers
committed to the repository every day and on any pull request that changes their
config. It prints the repository secrets to add: your `LURQ_API_KEY` and every
credential the committed servers reference. It fails if a scan could not be
recorded, so a green run always means the dashboard has that day's scan.

The permissions block is the trust model: contents are read-only, and the only
writes are the ones you asked for. By default that is `issues: write`, for the
pinned dashboard issue — `--no-issue` drops it and leaves the workflow entirely
read-only.

```bash
npx lurqrun mcp-ci --sarif
```

That adds `security-events: write` and a step filing every finding as a GitHub
code scanning alert, which brings dedup, assignment and closing-when-fixed with
it — none of which lurq has to build. It uploads even when `--fail-on` failed
the job, because that is exactly the run whose findings matter. It is off by
default for a reason: the upload needs code scanning enabled, and on a private
repository without Advanced Security it fails the job outright.

## Options [#options]

| Flag                   |                                                                                                                                                                                                                                                                                                                                                                                                              |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--fail-on <severity>` | Exit 1 at `critical`, `high`, `moderate` or `low`. Default `none`.                                                                                                                                                                                                                                                                                                                                           |
| `--only <names>`       | Scan only these servers, comma-separated.                                                                                                                                                                                                                                                                                                                                                                    |
| `--project-only`       | Read only config files inside the project.                                                                                                                                                                                                                                                                                                                                                                   |
| `--trust-project`      | Launch repository-committed servers without asking.                                                                                                                                                                                                                                                                                                                                                          |
| `--timeout <seconds>`  | Per-server connect deadline. Default 60; first runs of `npx`/`uvx` download.                                                                                                                                                                                                                                                                                                                                 |
| `--concurrency <n>`    | Servers scanned at once. Default 4.                                                                                                                                                                                                                                                                                                                                                                          |
| `--no-history`         | Do not compare with, or record, the previous scan.                                                                                                                                                                                                                                                                                                                                                           |
| `--no-upload`          | Keep the scan on this machine.                                                                                                                                                                                                                                                                                                                                                                               |
| `--require-upload`     | Exit 1 if the scan could not be recorded to your account. The generated CI workflow sets it.                                                                                                                                                                                                                                                                                                                 |
| `--no-contribute`      | Do not offer public corroboration.                                                                                                                                                                                                                                                                                                                                                                           |
| `--json`               | Machine-readable output.                                                                                                                                                                                                                                                                                                                                                                                     |
| `--sarif <file>`       | Write SARIF 2.1.0 for GitHub code scanning. Upload it with `github/codeql-action/upload-sarif` and each finding becomes an alert with its own dedup, assignment and close-when-fixed lifecycle — one rule per kind of finding, one alert per server and tool. A server stuck on `needs_config` is reported too, as a blocking alert whose instruction is to get the value from you rather than to guess one. |

`lurq mcp-stack` answers just the collision question, live, with the same
config reading.


# MCP tools (https://www.lurq.run/docs/mcp-tools)



Once lurq is installed, your agent can call these tools over MCP. Every response
is compact (to stay cheap in tokens), and package answers carry a `dataAsOf`
timestamp so the agent knows how fresh the underlying evidence is.

| Tool                                  | Inputs                                       | What it answers                                                        |
| ------------------------------------- | -------------------------------------------- | ---------------------------------------------------------------------- |
| [`verify`](#verify)                   | `package`                                    | Is this package real, healthy, and not risky?                          |
| [`evaluate`](#evaluate)               | `package`                                    | Full evidence read for one package                                     |
| [`compare`](#compare)                 | `packages`                                   | 2–5 packages ranked head-to-head                                       |
| [`policy`](#policy)                   | none                                         | The rules your account's selection policy enforces                     |
| [`compat`](#compat)                   | `packages`, `versions`, `node`               | Will these packages actually install together?                         |
| [`audit`](#audit)                     | `packages`, `mcpServers`                     | A whole project's dependencies and MCP servers, in one call            |
| [`diagram`](#diagram)                 | `stack`                                      | A reference-architecture Mermaid diagram for a stack                   |
| [`usage`](#usage)                     | `package`, `version`, `knownVersion`         | A version's *real* public API, and what changed since the one you know |
| [`resolve_surface`](#resolve_surface) | `package`, `version`                         | Exactly what a version exports at runtime                              |
| [`diff_surface`](#diff_surface)       | `package`, `fromVersion`, `toVersion`        | What a version bump adds, removes, or re-shapes                        |
| [`mcp_surface`](#mcp_surface)         | `server`, `version`                          | An MCP server's real tool contract                                     |
| [`mcp_drift`](#mcp_drift)             | `server`, `fromVersion`, `toVersion`         | What an MCP server changed between two versions                        |
| [`mcp_stack`](#mcp_stack)             | `servers`                                    | Do these MCP servers collide in one agent?                             |
| [`connect_check`](#connect_check)     | `server`, `client`                           | Will this MCP server work in my client, and what does it take?         |
| [`capabilities`](#capabilities)       | `query`                                      | Which lurq tool answers this situation                                 |
| [`report_outcome`](#report_outcome)   | `package`, `accepted`, `buildSignal`, `need` | What happened after a pick shipped                                     |

## Before installing a package [#before-installing-a-package]

### `verify` [#verify]

The anti-hallucination guard: is this package real, healthy, and not risky?

* **Input:** `package`, one name (possibly a misspelling or an invention).
* **Output:** whether it exists on the live registry, a supply-chain risk level,
  the individual risk flags, and the **suspected typosquat target** when the name
  closely mimics a popular one.

> `verify lodahs` → not a real package (did you mean `lodash`?)

Use this **before** the agent installs a package or writes an import for one it
"remembers."

### `evaluate` [#evaluate]

A full evidence read for a single package.

* **Input:** `package`, one name.
* **Output:** the complete score breakdown, advisories, a usage guide, the latest
  sandbox verdict, and — if the package has been superseded — the recommended
  migration target. When your account has a selection policy, whether the
  package is allowed.

A package lurq isn't tracking yet is fetched and scored on the spot; the reply
says so, and a retry a few seconds later gets the full read.

Use this when the agent has a candidate and wants the detail before committing.

### `compare` [#compare]

Rank 2–5 packages head-to-head by health.

* **Input:** `packages`, a list of 2–5 names.
* **Output:** a ranked comparison across the scoring axes.

> `compare drizzle-orm prisma typeorm`

Use this when the choice is already narrowed to a shortlist.

### `policy` [#policy]

The rules your account's selection policy enforces, read-only.

* **Input:** none.
* **Output:** `enforced`, and one sentence per active rule: denied packages with
  their reason, the license allowlist, and the advisory, confidence, adoption,
  staleness and bundle-size floors.

`evaluate` already enforces the policy; this lets the agent pick an allowed
package first. There is deliberately no tool that changes policy. Edit it in the
dashboard, or keep it in a file with `lurq policy pull` / `lurq policy push`
(see the [CLI reference](/cli#selection-policy)).

## Checking a whole stack [#checking-a-whole-stack]

### `compat` [#compat]

Will this set of packages actually install together?

* **Input:**
  * `packages`: 2–30 names — **the whole candidate stack in one call**. A
    whole `package.json` dependency list is the intended input. Checking pairs
    separately misses conflicts that only appear across the set.
  * `versions` (optional): exact versions keyed by name, e.g.
    `{ "react": "19.0.0" }`, when you're not checking latest. Ranges are
    refused.
  * `node` (optional): the target Node runtime (`"20"` or `"20.20.2"`), for
    `engines.node` checks.
* **Output:** a verdict, the exact clashing constraints, and the evidence behind
  it.

The verdict is about the set, not its pairs:

| Verdict      | Meaning                                                                                                               |
| ------------ | --------------------------------------------------------------------------------------------------------------------- |
| `conflict`   | Proven broken — a declared peer or engine range, a recorded failed co-install, or npm's own resolver refusing the set |
| `compatible` | npm resolved this exact set of versions                                                                               |
| `unknown`    | lurq could not determine it: a member it has no version for, or a resolve that timed out                              |

lurq never returns `compatible` on the mere absence of a declared conflict, and
`unknown` is never a hedge on a set it did resolve. Read-only: nothing is
installed or executed on your machine.

### `audit` [#audit]

An entire project's dependencies and MCP servers, assessed in one call.

* **Input:** the inventory your agent read locally — names and versions only,
  never source:
  * `packages`: up to 600 entries of `{ name, range, installed }`, from
    `package.json` and the lockfile.
  * `mcpServers`: up to 200 entries of
    `{ alias, kind, packageName, version, endpoint }`, from `.mcp.json` and
    agent configs. `kind` is `npm-stdio`, `remote`, `local`, or
    `other-registry`.
* **Output:** a per-item verdict — outdated, deprecated, advisories for the
  exact installed version, MCP servers that drifted, need credentials, or can't
  be observed — plus an explicit coverage count: how many were answered, how many
  are queued because the index hasn't seen them, and how many were skipped and
  why.

An item lurq could not assess is reported as unassessed, **never as clean**. The
CLI equivalent is `lurq audit`.

### `diagram` [#diagram]

A reference-architecture Mermaid diagram for a stack you've already chosen.

* **Input:** `stack`, the package names. Omit it to get usage guidance.
* **Output:** a Mermaid diagram you can drop into docs or a README.

A labeled starting point, not a validated architecture. Packages lurq can't
classify go into an explicit `Unclassified` bucket and are named in the note —
never quietly filed under a default layer.

## Writing code against a package [#writing-code-against-a-package]

### `usage` [#usage]

A package version's **real** public API — the exported symbols and signatures
extracted from its shipped `.d.ts`, exact to the version.

* **Input:** `package`, an optional target `version` (defaults to latest), and —
  the useful part — a `knownVersion`.
* **Output:** the export surface, the version's declared `engines` (so you don't
  write code against a version the target runtime can't install), and, when you
  passed `knownVersion`, the precise **delta**: what was added, removed, renamed,
  or changed.

Pass the version your model *thinks* it knows and get the delta to the version
you're actually installing. That fact exists in no changelog and no model's
training data.

### `resolve_surface` [#resolve_surface]

What a version **actually exports at runtime**, read from its shipped JavaScript
rather than from documentation or memory.

* **Input:** `package` and an optional exact `version` (defaults to the latest
  extracted).
* **Output:** the runtime symbols, plus the evidence class and extraction tier
  behind the answer.

Runtime existence is what decides whether an import throws: a removed type breaks
`tsc`, a removed runtime symbol breaks the program. A miss returns `unknown&#x60; and
queues extraction — &#x2A;*`unknown` never means the symbol is absent.**

### `diff_surface` [#diff_surface]

What changed between two versions of a package.

* **Input:** `package`, `fromVersion`, `toVersion`.
* **Output:** symbols removed, added, and arity-changed, plus renames the package
  itself proves — with **type-only removals returned separately**, because those
  break `tsc` rather than `node`.

Answers "when did this stop working" from static comparison, with no install
required. Use it before an upgrade, and to explain a break after one.

## MCP servers [#mcp-servers]

The same questions, asked about the MCP servers your agent is wired to. To scan
the servers configured on your own machine, including private and remote ones,
use [`lurq mcp-scan`](/mcp-scan).

### `mcp_surface` [#mcp_surface]

What an MCP server **actually exposes**, read from a live `tools/list` handshake
in a sandbox rather than from a README or memory.

* **Input:** `server`, the npm package name, and an optional exact `version`
  (defaults to the latest probed).
* **Output:** every tool, its required and optional parameters, and its behaviour
  annotations; `requires`, the API keys and settings the server declares it
  needs; and `configRequest`, a ready-made line to put in front of the user when
  one is missing.

A miss returns `unknown` and queues a probe; `unknown` never means the server has
no tools.

### `mcp_drift` [#mcp_drift]

What moved in an MCP server's tool contract between two versions.

* **Input:** `server`, `fromVersion`, `toVersion`.
* **Output:** tools removed, parameters that became required, types narrowed, and
  annotation flips. Two findings have no npm equivalent: **silent drift**, a
  schema that changed while its description stayed byte-identical, and
  **privilege widening**, a tool that stopped being read-only or started being
  destructive.

Use it before upgrading a server an agent depends on.

### `mcp_stack` [#mcp_stack]

Can a set of MCP servers be wired into one agent together?

* **Input:** `servers`, 1–50 entries of `{ server, version, tools }`. Pass
  `tools` (names and annotations are enough) when you already hold a server's
  tool list — any server, remote, PyPI, Docker or private — and it is analysed
  as-is. Otherwise `server` must be an npm package name, and lurq uses its
  probed surface.
* **Output:** tool-name collisions — two servers exposing the same name leave
  the agent unable to say which it means, and nothing errors, one simply shadows
  the other — plus the standing context cost of every tool's schema.

A server that hasn't been probed makes the answer `unknown`, never clean. The CLI
equivalent, reading your own configs live, is `lurq mcp-stack`.

### `connect_check` [#connect_check]

Will an MCP server work in a given client, and what does it take to connect?

* **Input:** `server` — an endpoint URL, an official registry name
  (`io.github.acme/weather`) or an npm package name — and an optional `client`
  (`claude-code`, `claude-ai`, `chatgpt`, `cursor`, `vscode`, `codex`,
  `gemini-cli`, …). Omit `client` to check every client lurq has a profile for.
* **Output:** per client, a verdict — `works`, `needs_setup` (with the steps:
  a key to send as a header, an OAuth client to pre-register and the redirect
  URIs to allow, a URL placeholder to fill), `blocked` (with the reason and which
  side causes it), or `unknown` — plus ready-to-paste config in that client's own
  format. `evidence` carries what the verdict rests on: whether the endpoint
  answered, how it authenticates (OAuth registration methods, PKCE), spec
  deviations strict clients refuse, the tool count, and recent contract or auth
  changes.

Server facts come from a credential-free probe of every remote endpoint in the
official MCP registry, re-read on a schedule. Client facts come from each
client's own documentation and source code, with sources. A URL lurq has never
seen is probed on the spot and **not stored**. `unknown` means a decisive fact
is not established — never that the server will not work.

## Everything else [#everything-else]

### `capabilities` [#capabilities]

Which lurq tool answers the situation in front of the agent, and what to run
next.

* **Input:** `query` (optional), what the agent is trying to do in plain words.
  Omit it for the full menu.
* **Output:** matching capabilities, each with the tool or command to use.

It reads nothing and is not counted as usage. The CLI equivalent is `lurq can`.

### `report_outcome` [#report_outcome]

Opt-in feedback after acting on a package lurq returned.

* **Input:** `package`, `accepted` (did you go with it), an optional
  `buildSignal` (`installed`, `compiled`, `tests_passed`, or `failed`), and an
  optional `need`, the original need it was picked for (up to 500 characters).
* **Output:** an acknowledgement.

No source code — only the decision, whether it built, and the need if you pass
one. It's how lurq learns which packages agents actually succeed with. Safe to
skip.

## Confidence labels [#confidence-labels]

Every scored package carries one of four confidence labels, reflecting the
strength of the **discovered** evidence, not anyone's opinion:

| Label       | Meaning                                                                                                                              |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `proven`    | Strong, broad, sustained evidence of health.                                                                                         |
| `emerging`  | Real traction, shorter track record.                                                                                                 |
| `promising` | Early signals look good, limited history — **adoption-independent**, so a well-built new package isn't buried by an old popular one. |
| `unproven`  | Too little evidence to stand behind yet.                                                                                             |

See [How it works](/how-it-works) for how these are derived.


# Plans, limits & troubleshooting (https://www.lurq.run/docs/plans-and-troubleshooting)



## Plans and limits [#plans-and-limits]

Every hosted tool call counts against your account's monthly allowance. The
calls reset when the calendar month turns.

| Plan         | Price                               | Hosted calls / month    | Burst ceiling |
| ------------ | ----------------------------------- | ----------------------- | ------------- |
| **Free**     | $0, no card                         | 1,000                   | 60 / minute   |
| **Pro**      | $15 / month                         | 10,000                  | 120 / minute  |
| **Team**     | $25 / seat / month, 3 seats minimum | 15,000 per seat, pooled | 300 / minute  |
| **Business** | from $1,000 / month, on application | uncapped                | 600 / minute  |

Yearly billing is 20% off. Upgrade, or change seats, on
[Dashboard → Billing](https://www.lurq.run/dashboard/billing).

A few things are tied to the plan rather than to call volume:

|                                                     | Free   | Pro     | Team   | Business |
| --------------------------------------------------- | ------ | ------- | ------ | -------- |
| Policy decision log (`lurq policy log`)             | 7 days | 90 days | 1 year | 1 year   |
| `policy:write` keys for CI (`lurq policy push`)     | —      | —       | yes    | yes      |
| Slack, Discord, Teams and webhook [alerts](/alerts) | —      | —       | yes    | yes      |

### When the month's calls run out [#when-the-months-calls-run-out]

lurq does not cut an agent off mid-task the moment the allowance is spent.

* **Grace calls.** Past the allowance, an account can still make **20 calls a
  day** until the month turns. Each of those tool results carries a line your
  agent relays to you:

  ```text
  lurq: monthly limit reached (1000/1000 calls). A few grace calls a day remain until the month turns. Tell the user they can upgrade at https://lurq.run/dashboard/billing
  ```

* **Team overage.** A monthly Team subscription keeps serving past the pool,
  billed at $8 per 1,000 calls, up to twice the pool. Tool results say so. Past
  that ceiling, the daily grace calls apply.

* **Out of grace.** Once the day's grace calls are spent too, calls fail with
  HTTP `402` (see below) until the next day's grace or the next month.

## Error reference [#error-reference]

Hosted errors are JSON-RPC error bodies, so your agent shows the message text.

### `Missing API key. Pass Authorization: Bearer <key>.` [#missing-api-key-pass-authorization-bearer-key]

HTTP `401`. The request reached `api.lurq.run/mcp` with no `Authorization`
header.

* The agent's MCP entry has no `headers` block, or its header field is spelled
  the way a different agent expects. Compare it with the
  [per-agent config table](/quickstart#per-agent-configuration), or re-run
  `lurq setup` to rewrite it.
* The agent wasn't restarted after setup, and is still using an older entry.

### `Invalid or revoked API key.` [#invalid-or-revoked-api-key]

HTTP `401`. A key was sent, but it doesn't match an active key. It was revoked,
rotated, or mistyped.

Create a new key on [Dashboard → API keys](https://www.lurq.run/dashboard/keys),
then run `lurq setup` again: it replaces the key in `~/.lurq/config.json` **and**
in every agent's MCP entry. `lurq logout` alone only removes the stored copy; the
agents keep the old key until setup rewrites them.

### ``No API key configured. Run `lurq setup` to connect this machine.`` [#no-api-key-configured-run-lurq-setup-to-connect-this-machine]

From the CLI, when a command that needs the hosted index finds no key in
`--api-key`, `LURQ_API_KEY`, or `~/.lurq/config.json`. In a terminal, lurq offers
to run setup and then retries the command; in CI, pipes, or with `--json`, set
`LURQ_API_KEY`.

### `Monthly limit reached for the <plan> plan (<used>/<limit> calls), and today's 20 grace calls are spent.` [#monthly-limit-reached-for-the-plan-plan-usedlimit-calls-and-todays-20-grace-calls-are-spent]

HTTP `402`. The full message ends with
`It resets when the month turns. Upgrade at https://lurq.run/dashboard/billing`.

You're past the allowance and today's grace calls (see
[above](#when-the-months-calls-run-out)). Wait for tomorrow's grace calls or the
next month, or upgrade. A `402` is not a rate limit: retrying straight away
won't help.

### `Rate limit exceeded.` [#rate-limit-exceeded]

HTTP `429`, JSON-RPC code `-32029`. Too many calls in one minute: more than your
plan's burst ceiling for the key, or a flood of requests from one IP address.
Wait a minute and retry. An agent running a tight loop of calls usually meets
this first; `compat` and `audit` take a whole set in one call and avoid it.

### A package comes back `unknown`, or "retry shortly" [#a-package-comes-back-unknown-or-retry-shortly]

Not an error. lurq never guesses about a package it hasn't read.

* **`evaluate` / `compare`** on a package lurq isn't tracking yet fetches and
  scores it on the spot, and says so: retry in a few seconds for the full read.
* **`compat`** returns `unknown` when it has no version for a member, or the
  resolve timed out.
* **`resolve_surface`, `usage`, `mcp_surface`** return `unknown` when that
  version hasn't been extracted or probed, and queue it. `unknown` **never**
  means the symbol or tool is absent.
* **`audit`** reports such items as queued or unassessed, never as clean.

Retry after a short wait. A name that doesn't exist on npm at all is a different
answer: `verify` reports `not-found-on-registry`, with a suspected typosquat
target when there is one.

### Setup hangs or fails without a terminal [#setup-hangs-or-fails-without-a-terminal]

The setup wizard prompts, so it needs a real terminal. In CI, a container, or a
provisioning script, run it non-interactively:

```bash
lurq setup --yes --no-open --api-key <key> --agent all
```

With `--yes` the key can also come from `LURQ_API_KEY`. Without a key, `--yes`
stops with `No API key. Pass --api-key <key> or set LURQ_API_KEY`.

On an SSH session or headless box without `--yes`, the browser can't hand the key
back: setup prints the sign-in link, then falls back to asking you to paste a key
from the dashboard. Add `--no-open` to skip trying to launch a browser.

### `That key was rejected (401)` / `Couldn't reach … to validate the key` [#that-key-was-rejected-401--couldnt-reach--to-validate-the-key]

Setup checks the key against the endpoint before writing anything. A rejected key
is revoked or mistyped. An unreachable endpoint is usually a proxy, a firewall, or
a typo in `--url`. Answering **no** leaves every config file untouched.

### My agent never calls lurq [#my-agent-never-calls-lurq]

* Restart the agent after setup. MCP servers are read at startup.
* Check the agent's MCP or tool list for `lurq`.
* Cursor and VS Code get no instruction file (see
  [what setup writes](/quickstart#per-agent-configuration)), so they rely on the
  tool descriptions alone. Asking for lurq by name ("check this with lurq")
  always works.


# Quick start (https://www.lurq.run/docs/quickstart)



lurq is a **hosted service**: you don't run a database or a sync to use it. One
command installs the CLI and wires up both your terminal and every coding agent
on the machine.

<Callout type="info">
  The package ships on npm as &#x2A;*`lurqrun`** (the bare name `lurq` is taken). The command you invoke
  is still `lurq` once installed.
</Callout>

## 1. Install and set up [#1-install-and-set-up]

```bash
npx lurqrun
```

That runs the setup wizard. It offers to install `lurqrun` globally first, so
`lurq` works in any terminal without npx, then opens lurq in your browser. Sign
in (or create an account) and the browser **hands the key back to the wizard by
itself**: there is nothing to copy. If that can't work, on an SSH session or a
headless box say, the wizard prints the link and falls back to asking you to
paste a key from [Dashboard → API keys](https://www.lurq.run/dashboard/keys).

From there it does everything in one pass:

1. **Validates the key** against the hosted endpoint.
2. **Stores it** in `~/.lurq/config.json`, owner-readable only (mode `0600`).
3. **Detects your assistants**: Claude Code, Cursor, Windsurf, VS Code /
   Copilot, Codex, Gemini CLI, Antigravity, Kiro.
4. **Writes a keyed remote MCP entry** for each one, in the shape that agent
   expects (see [per-agent configuration](#per-agent-configuration)).
5. **Installs standing instructions** where the agent has a place for them, so
   the model reaches for lurq on its own rather than answering about a package
   from memory.

**No database credentials ever touch your machine.** Restart your agent afterward
so it picks up the new MCP server.

This is a per-machine step, not a per-project one. There is nothing to add to a
repo and nothing to re-run when you start a new project.

<Callout type="info">
  `lurq setup` re-runs the same wizard later, after installing a new editor or to swap in a
  different key. `lurq install` and `lurq login` are aliases for it.
</Callout>

## 2. Use it from the terminal [#2-use-it-from-the-terminal]

The stored key is what makes the CLI work anywhere, with no `LURQ_API_KEY` to
export and no database:

```bash
lurq verify jsonwebtoken
lurq evaluate zod
lurq compare date-fns dayjs moment
lurq usage zod --known 3.22.4
```

See the [CLI reference](/cli) for the full set.

## 3. Verify your agent is connected [#3-verify-your-agent-is-connected]

Ask it to add a dependency, for example:

> "Add zod to this project."

Before it installs anything, you should see it call lurq's `verify` tool, and get
back whether the package exists, its risk level and flags, and a `dataAsOf`
timestamp. To see the same answer without an agent, run `lurq verify zod`.

If the agent doesn't call lurq, confirm it was restarted and that `lurq` shows up
in its MCP tool list. More in
[troubleshooting](/plans-and-troubleshooting#my-agent-never-calls-lurq).

## From a coding agent [#from-a-coding-agent]

An AI agent can set lurq up for you. When `npx lurqrun setup` runs in an agent's
shell, where there is no terminal to prompt in, it prints a one-time sign-in link
and exits. Open the link on the same computer and sign in: lurq stores the key
and connects every detected agent in the background. The link works for 15
minutes. Restart the agent afterwards.

The sign-in has to happen on the machine the agent runs on. For a cloud or SSH
agent, use the non-interactive form below.

## Non-interactive setup [#non-interactive-setup]

For scripted setups (CI, dotfiles, provisioning), skip the prompts and the
browser:

```bash
lurq setup --yes --no-open --api-key <key>
```

Use `--agent all` to configure every detected assistant, or `--agent claude-code`
for one. With `--yes`, the key can also come from `LURQ_API_KEY`. The wizard needs
a terminal, so without one and without a key, setup uses the sign-in link above
instead, except in CI, where this form is the one to use.

## One-click install [#one-click-install]

### Claude Code plugin [#claude-code-plugin]

Inside Claude Code, no terminal needed:

```text
/plugin marketplace add jadenryu/lurq
/plugin install lurq@lurq
```

Claude Code asks for your API key once and keeps it in your system keychain.
The plugin connects the hosted MCP server and adds the lurq skill. Run
`/reload-plugins`, or restart, to load it.

### Cursor and VS Code [#cursor-and-vs-code]

<a href="cursor://anysphere.cursor-deeplink/mcp/install?name=lurq&config=eyJ1cmwiOiJodHRwczovL2FwaS5sdXJxLnJ1bi9tY3AiLCJoZWFkZXJzIjp7IkF1dGhvcml6YXRpb24iOiJCZWFyZXIgPHlvdXItbHVycS1hcGkta2V5PiIsIlgtTHVycS1DbGllbnQiOiJjdXJzb3IifX0%3D">
  Add lurq to Cursor
</a>

·

<a href="vscode:mcp/install?%7B%22name%22%3A%22lurq%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fapi.lurq.run%2Fmcp%22%2C%22headers%22%3A%7B%22Authorization%22%3A%22Bearer%20%3Cyour-lurq-api-key%3E%22%2C%22X-Lurq-Client%22%3A%22copilot%22%7D%7D">
  Install lurq in VS Code
</a>

Each opens the editor with the lurq server filled in. Replace
`<your-lurq-api-key>` in the new entry with a key from
[Dashboard → API keys](https://www.lurq.run/dashboard/keys). `npx lurqrun` does
the same without the paste, and also installs standing instructions.

## Per-agent configuration [#per-agent-configuration]

Setup writes one `lurq` entry into each agent's own MCP config, merged into what
is already there. The shape differs per agent, and the wrong field name is the
most common reason a hand-written entry silently fails. Every entry points at
`https://api.lurq.run/mcp` and sends `Authorization: Bearer <your-key>`.

| Agent             | `--agent`     | MCP config file                       | Entry                                                     |
| ----------------- | ------------- | ------------------------------------- | --------------------------------------------------------- |
| Claude Code       | `claude-code` | `~/.claude.json`                      | `mcpServers.lurq`: `{ "type": "http", "url", "headers" }` |
| Cursor            | `cursor`      | `~/.cursor/mcp.json`                  | `mcpServers.lurq`: `{ "url", "headers" }`                 |
| Windsurf          | `windsurf`    | `~/.codeium/windsurf/mcp_config.json` | `mcpServers.lurq`: `{ "serverUrl", "headers" }`           |
| VS Code / Copilot | `copilot`     | `<VS Code user dir>/mcp.json`         | `servers.lurq`: `{ "type": "http", "url", "headers" }`    |
| Codex CLI         | `codex`       | `~/.codex/config.toml`                | `[mcp_servers.lurq]` with `url` and `http_headers`        |
| Gemini CLI        | `gemini-cli`  | `~/.gemini/settings.json`             | `mcpServers.lurq`: `{ "httpUrl", "headers" }`             |
| Antigravity       | `antigravity` | `~/.gemini/config/mcp_config.json`    | `mcpServers.lurq`: `{ "serverUrl", "headers" }`           |
| Kiro              | `kiro`        | `~/.kiro/settings/mcp.json`           | `mcpServers.lurq`: `{ "url", "headers" }`                 |

The VS Code user directory is `~/Library/Application Support/Code/User` on macOS,
`%APPDATA%\Code\User` on Windows, and `~/.config/Code/User` on Linux (or
`$XDG_CONFIG_HOME/Code/User`).

Standing instructions, written alongside the MCP entry:

| Agent                     | Instructions                                                                                    |
| ------------------------- | ----------------------------------------------------------------------------------------------- |
| Claude Code               | `~/.claude/skills/lurq/SKILL.md`, the full guide as a skill                                     |
| Kiro                      | `~/.kiro/steering/lurq.md`, the full guide as a steering file                                   |
| Windsurf                  | a marked block in `~/.codeium/windsurf/memories/global_rules.md`                                |
| Codex CLI                 | a marked block in `~/.codex/AGENTS.md`                                                          |
| Gemini CLI, Antigravity   | a marked block in `~/.gemini/GEMINI.md`                                                         |
| Cursor, VS Code / Copilot | none: neither has a machine-wide instructions file, so the tool descriptions carry the guidance |

Every install also copies the full guide to `~/.lurq/skill-instructions.md`, as
a readable reference. Shared files are merged, never overwritten: the block sits
between `<!-- lurq:start -->` and `<!-- lurq:end -->`, and re-running setup
replaces that block and leaves everything you wrote alone.

### Installing by hand [#installing-by-hand]

If you'd rather not run the wizard, create a key on
[Dashboard → API keys](https://www.lurq.run/dashboard/keys) and add the entry to
your agent's config file yourself, merged with any servers already there.

For Claude Code, in `~/.claude.json` (and VS Code, under `servers` in its
`mcp.json`):

```json
{
  "mcpServers": {
    "lurq": {
      "type": "http",
      "url": "https://api.lurq.run/mcp",
      "headers": { "Authorization": "Bearer <your-key>" }
    }
  }
}
```

For Cursor or Kiro, drop `type`. For Windsurf or Antigravity, use `serverUrl`
instead of `url`. For Gemini CLI, use `httpUrl`: a plain `url` there means an SSE
server, and the connection never completes.

```json
{
  "mcpServers": {
    "lurq": {
      "httpUrl": "https://api.lurq.run/mcp",
      "headers": { "Authorization": "Bearer <your-key>" }
    }
  }
}
```

For Codex, append to `~/.codex/config.toml`. The headers must be the inline
`http_headers` table, not a `[mcp_servers.lurq.headers]` section, which Codex
rejects:

```toml
[mcp_servers.lurq]
url = "https://api.lurq.run/mcp"
http_headers = { Authorization = "Bearer <your-key>" }
```

For the CLI, `export LURQ_API_KEY=<your-key>` is enough. Restart the agent once
the entry is in place.

## Pointing at your own server [#pointing-at-your-own-server]

The hosted service at `api.lurq.run` is the default, not the only option. If you
run your own `lurq serve-http` (see [Self-hosting](/self-hosting)), point
setup at it:

```bash
lurq setup --url https://lurq.internal/mcp
```

Setup then issues its instructions for your server instead of ours: it won't open
our dashboard, because a key from there is one your server has never issued.
Issue the key from the operator plane of your own deployment:

```bash
npm run operator -- keys create --label my-laptop
```

The endpoint is stored alongside the key, so a later bare `lurq setup` (to wire
up a newly-installed editor, say) stays on your server. Setup always prints which
endpoint it used, so you can see at a glance where a machine points. To move back:

```bash
lurq setup --url https://api.lurq.run/mcp
```

For a one-off against a different endpoint without changing what's stored, set
`LURQ_ENDPOINT` for that command.

If you'd rather your agent talk to a local `lurq serve` over stdio against your
own Postgres, with no HTTP service at all, use `lurq install-skill --local`
instead. That path needs `DATABASE_URL` and no API key.

## What lurq sends and stores [#what-lurq-sends-and-stores]

* **Package names and versions** you ask about, and for `audit`, the dependency
  names, declared ranges and installed versions from your manifest and lockfile.
  Never source code.
* **The `need` text**, if your agent passes one to `report_outcome`, plus whether
  it went with the package and whether it built.
* **MCP scans.** With a key configured, `lurq mcp-scan` records each server's
  contract (tools, parameters, annotations, prompts, instructions) to your
  account. Credentials from `env` and `headers` are scrubbed first, and resource
  URIs are counted, not kept. `--no-upload` keeps a scan on your machine;
  `--no-contribute` stops published servers' contracts being offered to the
  public index. See [Scanning your MCP servers](/mcp-scan).
* **`check-upgrade --report`** sends that report to your dashboard. Without the
  flag, `check-upgrade` sends nothing to lurq.
* **Usage counts.** Each hosted call is counted against your account, by tool
  name and whether it succeeded, never with its arguments.

Details are in the [privacy policy](https://www.lurq.run/privacy).

## Uninstall [#uninstall]

<Callout type="info">
  `lurq uninstall` ships with the next `lurqrun` release. On an older version, use the manual steps
  below.
</Callout>

```bash
lurq uninstall                      # asks, then removes lurq from this machine
lurq uninstall --agent cursor       # just one assistant
lurq uninstall --yes                # no prompt
```

It removes the `lurq` MCP entry from each agent's config, the marked instruction
blocks and skill files setup wrote, and `~/.lurq/config.json`. Everything else in
those files stays. Then `npm uninstall -g lurqrun` removes the command itself.

`lurq logout` is not an uninstall: it only removes the key stored in
`~/.lurq/config.json`. Your agents' MCP entries still hold the key and keep
working until you remove them.

To do it by hand:

1. Delete the `lurq` entry from each agent's MCP config in
   [the table above](#per-agent-configuration): `mcpServers.lurq`,
   `servers.lurq` for VS Code, or the `[mcp_servers.lurq]` table in
   `~/.codex/config.toml`.
2. Delete `~/.claude/skills/lurq/` and `~/.kiro/steering/lurq.md`.
3. Delete the `<!-- lurq:start -->` … `<!-- lurq:end -->` block from
   `~/.gemini/GEMINI.md`, `~/.codex/AGENTS.md` and
   `~/.codeium/windsurf/memories/global_rules.md`.
4. Delete `~/.lurq/`.
5. Revoke the key on [Dashboard → API keys](https://www.lurq.run/dashboard/keys).

## Next steps [#next-steps]

* Learn what each tool does in the [MCP tools reference](/mcp-tools).
* Hit an error? See [Plans, limits & troubleshooting](/plans-and-troubleshooting).
* Curious how scores are derived? Read [How it works](/how-it-works).


# Self-hosting (https://www.lurq.run/docs/self-hosting)



Everything here runs against your **own lurq Postgres index** and needs a
`DATABASE_URL`. It's for operators running the hosted service and for
self-hosters, **not** for end users, who only run `lurq setup`
([Quick start](/quickstart)).

## The two planes [#the-two-planes]

<Callout type="warn">
  The commands that **build** an index are not in the published npm package.

  `lurqrun` ships as a read-only oracle: it can query an index, extract API
  surfaces, scan a codebase, and serve MCP. Everything that ingests, crawls,
  mines, runs a sandbox, issues keys, or migrates the schema lives on a separate
  **operator** binary that is deliberately excluded from the published files, so
  ingestion code and its heavy dependencies never land on a user's machine.

  To run the operator plane, clone the repo:

  ```bash
  git clone https://github.com/jadenryu/lurq && cd lurq
  npm install
  npm run operator -- <command>          # or: npx tsx src/bin/operator.ts <command>
  ```

  A built checkout can also run `node dist-operator/bin/operator.js <command>`,
  which is what the production cron jobs use.
</Callout>

## Configuration [#configuration]

Only `DATABASE_URL` is required; everything else degrades gracefully.

| Variable                                                                       | Required                     | Purpose                                                                                 |
| ------------------------------------------------------------------------------ | ---------------------------- | --------------------------------------------------------------------------------------- |
| `DATABASE_URL`                                                                 | yes                          | Postgres connection (pgvector-enabled)                                                  |
| `GITHUB_TOKEN`                                                                 | recommended                  | GitHub signals (stars, cadence, issues, archived)                                       |
| `EMBEDDING_PROVIDER` / `EMBEDDING_API_KEY` / `EMBEDDING_BASE_URL`              | no                           | Any OpenAI-compatible `/v1/embeddings`; falls back to a local embedder                  |
| `SUMMARY_PROVIDER` / `SUMMARY_API_KEY` / `SUMMARY_BASE_URL`                    | no                           | Any OpenAI-compatible chat endpoint for usage guides; falls back to the npm description |
| `LURQ_SYNC_CONCURRENCY`                                                        | no                           | Parallel ingest workers (default 5)                                                     |
| `LURQ_SYNC_REFRESH_CAP`                                                        | no                           | Stalest non-seed packages refreshed per sync (default 400)                              |
| `PORT`                                                                         | no                           | Port for `serve-http` (defaults to 8080)                                                |
| `LURQ_RATE_LIMIT_MAX` / `LURQ_IP_RATE_LIMIT_MAX` / `LURQ_RATE_LIMIT_WINDOW_MS` | no                           | Per-key / per-IP rate limits for `serve-http`                                           |
| `REDIS_URL`                                                                    | recommended for real traffic | Response cache **and** the shared rate-limit store                                      |
| `LURQ_METRICS_TOKEN`                                                           | no                           | Bearer token guarding `/metrics`; unset disables the endpoint                           |
| `LURQ_ISSUER_SECRET`                                                           | no                           | Shared secret for the dashboard-authenticated routes                                    |
| `E2B_API_KEY` / `E2B_TEMPLATE`                                                 | no                           | VM-isolated sandbox for verifying untrusted packages                                    |

<Callout type="info">
  Without `REDIS_URL` the response cache is a transparent pass-through and every request recomputes
  its search on the database. Fine for one box; set it before serving real traffic, since it also
  backs the rate limiter across instances.
</Callout>

## Set up the index [#set-up-the-index]

```bash
npm run operator -- db migrate   # pgvector extension + schema + curated seed list
npm run operator -- sync         # compute scores from public APIs (~2 min)
```

`sync` is idempotent and tolerant of single-source outages. Run it daily to keep
the index fresh.

Optional, to grow coverage beyond the seed list without curation:

```bash
npm run operator -- discover --cap 25   # crawl, merit-gate, ingest survivors
npm run operator -- worker              # the autonomous loop (Ctrl-C stops cleanly)
npm run operator -- rescore             # re-derive scores after a weight change
```

## Serve [#serve]

These two **are** in the published package:

```bash
lurq serve-http   # HTTP MCP server + API-key auth (helmet, rate limits)
lurq serve        # stdio MCP server against your own DB
```

`serve-http` fronts the central DB, so `DATABASE_URL` lives only on the service
host. Self-hosters can instead wire an agent to a local stdio server with
`lurq install-skill --local`.

Point your own clients at it with `lurq setup --url https://lurq.internal/mcp`;
the endpoint is stored alongside the key, so a later bare `lurq setup` stays on
your server.

## API keys [#api-keys]

For operators issuing keys to end users (operator plane, needs `DATABASE_URL`):

```bash
npm run operator -- keys create --label "acme"      # issue a key (shown once)
npm run operator -- keys list                       # prefix, tier, last-used, status
npm run operator -- keys rotate lurq_live_ab12cd    # replace, then revoke the old one
npm run operator -- keys revoke lurq_live_ab12cd    # revoke by prefix or id
```

<Callout type="warn">
  Keys are shown **once** at creation and stored only as hashes. Interactively, the key is wiped
  from your terminal (screen and scroll-back) after you confirm you've copied it. Capture it when
  it's printed — it can't be recovered, only rotated or revoked and reissued.
</Callout>

`rotate` creates the replacement **before** revoking the original, so a failure
part-way through leaves you with a working key rather than none.
