Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,56 @@ All notable changes to this SDK are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
[SemVer](https://semver.org/).

## [2.8.0] - 2026-08-13

Adds a setup command. The SDK surface is unchanged.

### Added

- **`lenz init` — wire Lenz into an MCP client.** Writes the Lenz MCP server
block into Claude Code (`./.mcp.json`), Cursor (`./.cursor/mcp.json`) or
Codex (`./.codex/config.toml`), then verifies the key with one authenticated
request. `--print` emits the config without touching anything, which is also
the answer for clients this doesn't know about.

Codex is TOML, so its table is APPENDED as text rather than parsed and
re-serialized — every TOML library drops comments and reflows formatting, and
handing someone back a file that is equivalent but visibly not theirs is the
same failure as clobbering it. A config already declaring
`[mcp_servers.lenz]` is refused: TOML rejects duplicate tables, so a second
copy would stop the whole file parsing. Codex also takes
`bearer_token_env_var`, so no key is written there at all.

**Claude Desktop writes nothing and prints its connector steps instead.**
`claude_desktop_config.json` is documented for local stdio servers only; a
remote streamable-HTTP server is added through Settings → Connectors → Add
custom connector. Writing that file put a live key somewhere nothing reads.

The odd one out among the verbs: every other command calls the API, this one
configures. Existing MCP servers in the target file are preserved — only the
`lenz` key is written — and a file that exists but does not parse as JSON is
refused rather than overwritten, since guessing there could silently discard
servers configured by hand. Writes are atomic (temp file + replace) and the
file is created `0600`.

**The key is not written into the project configs.** `.mcp.json` is a file
Claude Code's documentation tells teams to check into version control, so
writing a live credential there by default would be handing the user a leak.
Claude Code and Cursor get an environment-variable reference instead — in
each client's own syntax, `${LENZ_API_KEY}` and `${env:LENZ_API_KEY}`, which
are not interchangeable — and the command prints the `export` line to run.
`--write-key` opts out, for a private checkout. Claude Desktop still gets the
key itself: its config is global and the app never sees an exported variable.
`--json` reports which happened as `key_in_config` / `key_env_var`.

A failed key check is reported as a key problem, not a failed init: the
config is already written and correct at that point, and saying which of the
two broke stops people re-running `init` at something it cannot fix.

Parity: `npx lenz-io init` in the Node SDK is the same command, writing the
same block to the same locations. A developer who set one machine up with
each must not get two different results — change them together.

## [2.7.0] - 2026-08-10

Quota errors are now a first-class, typed condition instead of an
Expand Down
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pip install "lenz-io[cli]" # or into your current environment

```bash
lenz login # paste an API key (free — get one at lenz.io/api-integration)
lenz init # wire Lenz into Claude Code / Claude Desktop / Cursor
lenz extract "Einstein won the 1921 Nobel for relativity" # free, 1000/day
lenz assess "The Great Wall is visible from space" # fast verdict
lenz verify "Water boils at 90C at sea level" # full pipeline (~90s)
Expand Down Expand Up @@ -67,6 +68,60 @@ them; resolve it non-interactively by index (spawns one verification per pick):
lenz verify --resume "$tid" --claim 1,3 --detach --json # → spawned task_ids
```

### `lenz init` — wire Lenz into an MCP client

The one command here that **configures rather than calls**. It writes the Lenz
MCP server into an AI client's config and checks the key with one authenticated
request, so Claude Code, Claude Desktop or Cursor can fact-check inside a
conversation.

```bash
lenz init # writes ./.mcp.json (Claude Code)
lenz init --client cursor # writes ./.cursor/mcp.json
lenz init --client codex # appends to ./.codex/config.toml
lenz init --client claude-desktop # prints the connector steps
lenz init --print # print the config, write nothing
```

**Codex is TOML.** The Lenz table is appended to your existing
`.codex/config.toml` as text rather than parsed and rewritten, so comments and
formatting survive untouched, and a config already declaring
`[mcp_servers.lenz]` is refused rather than given a second one — TOML rejects
duplicate tables, and a second copy would stop the whole file parsing. Codex
takes `bearer_token_env_var`, so no key is written there at all.

**Claude Desktop takes no config file.** `claude_desktop_config.json` is for
local stdio servers; a remote server like Lenz is added through Settings →
Connectors → "Add custom connector", or in one click from
[the directory](https://claude.ai/directory/connectors/lenz). `lenz init
--client claude-desktop` prints those steps rather than writing anything.

**Your key does not go into the project configs.** `.mcp.json` and
`.cursor/mcp.json` live in your repo, and Claude Code's documentation says to
check `.mcp.json` into version control so your team shares the same servers. So
those two get an environment-variable reference, and you export the key:

```bash
export LENZ_API_KEY=lenz_... # add to your shell profile to make it stick
```

Each client spells that reference differently — `${LENZ_API_KEY}` for Claude
Code, `${env:LENZ_API_KEY}` for Cursor — and `init` writes the right one. Pass
`--write-key` to put the key in the file instead, for a private checkout.
Claude Desktop always gets the key itself: its config is global, and the app is
launched from the desktop rather than a shell, so it never sees an exported
variable.

Existing MCP servers in that file are preserved — only the `lenz` key is
written. A file that exists but isn't valid JSON is refused rather than
overwritten, because guessing there could silently discard servers you
configured by hand. Writes are atomic (temp file + replace) and the file is
created `0600`.

`npx lenz-io init` in the [Node SDK](https://github.com/lenzhq/lenz-io-node)
is the same command, writing the same block to the same places — use whichever
runtime you already have.

## Quickstart — the canonical integration

```python
Expand Down
6 changes: 5 additions & 1 deletion src/lenz_io/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from lenz_io import __version__
from lenz_io.client import DEFAULT_BASE_URL

from . import commands
from . import commands, init_cmd
from . import verify as verify_mod
from .config import ENV_BASE_URL, ConfigError, resolve_all
from .context import CLIState
Expand Down Expand Up @@ -72,6 +72,10 @@ def _main(
app.command("status")(commands.status)
app.command("show")(commands.show)
app.command("ask")(commands.ask)
# `init` configures rather than calls — it wires Lenz into an MCP client.
# Listed right after the verbs so it is visible to someone who has just
# installed the CLI and is looking for what to do next.
app.command("init")(init_cmd.init)
app.command("login")(commands.login)
app.command("logout")(commands.logout)
app.command("config")(commands.config_status)
Expand Down
Loading
Loading