llms.txt
# Opyt
> Opyt turns the bookmarks, follows and subscriptions you already have into a private
> knowledge base that keeps growing, served to your AI client over MCP. Free, MIT-licensed.
> Source: https://github.com/maimond123/Opyt · Site: https://useopyt.com
This file is the plain-text brief for AI assistants. It is complete on its own: everything
below describes the current code on main, in one flat document with nothing behind tabs.
## What it is
Opyt is an **MCP server**, not a chat app and not a website you log into. It exposes tools to
whatever AI client you already use, and that client does the reasoning on the subscription you
already pay for. There is no Opyt chat UI.
Where it runs:
- **Local install, on a Mac** — data under `~/.opyt`: knowledge-base records, each item's
Markdown snapshot and the search index in `opyt.db`, and the worker queue in `rail_jobs.db`. Works with Claude Code, Claude Desktop, ChatGPT Desktop, Codex, Cursor,
Windsurf, and any client that speaks MCP over stdio. This is where a library is built.
- **The Opyt connector** — `https://api.useopyt.com/mcp`, used by **claude.ai** and **chatgpt.com**. It reads a
published copy of the library a local install builds, once that install is connected to the
same Google account. It searches the library by keyword and puts an answer's sources on one
page, deleted 90 days after it was last opened (`reader_view`). It makes and revokes invites (`share`, `unshare`). It also
hands four changes to the install that built the library: save a link (`hopper`), remove an
item or stop following a person (`forget`), follow a topic (`watch`), add a person
(`add_oracle`). Opyt on the Mac makes those: while the assistant waits when the Mac is awake,
otherwise when it is next on. It also reads a library someone else shared: the person who
was sent an invite pastes it in a chat with the connector added (`accept`), and needs no
install and no library of their own. There is no hosted Opyt that builds a library of its own.
## What it reads
| Source | What it takes | Access |
| --- | --- | --- |
| X | bookmarks, likes, follows, Lists | your connected OPYT-managed session |
| Substack | follows, subscriptions, saved posts and their available full text | your connected OPYT-managed session |
| GitHub | a tracked person's repos and READMEs | public |
| Blogs | a tracked person's whole archive, found by feed or sitemap | public |
| arXiv, GitHub, OpenAlex | new papers and repos that name what a topic you follow watches, added once your AI keeps them; tracked OpenAlex authors and research venues | public |
| X search | new posts that name what a topic you follow watches, added once your AI keeps them, and what `look` finds | your connected OPYT-managed session, local install only |
| Substack search | new newsletter posts that name what a topic you follow watches, added once your AI keeps them, and what `look` finds | public |
| A link you hand it | a supported paper, repo, Substack post, article or X post | you |
Seven signal collectors read connected accounts: four for X and three for Substack. These
signals are separate from importing the saved posts' bodies. Full text is not guaranteed:
paywalls, unavailable pages and incomplete sources can limit what is stored. Check `body_state`.
Hopper does not ingest video, audio, LinkedIn feeds or whole sites. A supported written
transcript or article can be saved instead; tracking a writer belongs to `oracle` / `add_oracle`.
LinkedIn, aggregators (Hacker News, Hugging Face, npm, crates.io, Stack Overflow, Product Hunt,
Homebrew) and company funding data are listed on the site as coming soon, not built.
## What you would use it for
You talk to your AI normally and it calls these tools for you. The general shapes of use:
**Check what you already know before spending time on something.** Before building a thing or
committing to a view, ask whether your own sources have been here already. Repos, papers and
posts rank in one list, so "someone has built this" and "someone credible thinks it fails"
arrive together rather than in two separate searches.
**Stay current on a topic.** Name a subject and Opyt follows it: new papers, repos, X posts and
Substack posts that name the things you watch, and your own people's posts on it. Your AI checks
what arrived before it shows you anything, and can tell you what your people have said about each
find. To see what is being said about one named thing right now, without following it, your AI
uses `look`.
**See how thinking moved.** Search scopes by date, so what your sources argued a year ago is set
against what they argue now.
**Pressure-test a decision, a claim, or a draft.** Argue against a position using only your own
material; find the questions your material cannot answer and why the nearest things fall short;
or write something cited entirely from what you have read, traceable back to the source.
**Catch up after time away.** Ask what's new: per topic you follow, the finds and posts worth your
time, checked by your AI first, then what your people posted outside any topic.
**Notice who you actually rely on.** Rank the people you keep engaging with out of your own
follows, Lists, subscriptions, bookmarks and likes, then track their whole output rather than
the one post you happened to save.
**Keep things without filing them.** Hand it a supported paper, repo, post or article and say
save this. No folders, no tags, no
deciding where it belongs.
**Count what you have been doing.** Distributional questions — what you read most of, when your
interests moved, how much of the store comes from where — answered across the whole store rather
than a sample.
**Borrow someone else's reading.** Ask your question against a knowledge base a colleague shared
with you, so their months of reading answer it before you start from nothing.
**The unusual part — topics.** Say what you want to stay up to date on and your AI writes the
topic: watches that each name one specific thing (a model, a drug, a benchmark), searched every
day on arXiv, GitHub, OpenAlex, X and Substack; words that pick out your own people's posts on
it; and the people who mostly write about it. A preview shows what it would have found last month
before anything is saved. New finds wait outside your library until your AI checks them in
`latest`: the ones worth your time are shown and kept, the rest never come back. Opyt runs no
model for this; the searches run in the background and your AI does the checking.
**Which tool answers which shape of question:** `search` → "where is the relevant material?" ·
`open` → "show me the actual source" · `aggregate` → "how much, and when?" · `latest` → "what is
new since I last looked?" · `watch` → "keep me up to date on this." · `look` → "what are people
saying about this thing right now?"
## The tools
| Tool | What it does |
| --- | --- |
| `onboard` | Sets up this Opyt home. Idempotent, human in the loop; recomputes progress from disk, so calling it twice never repeats a finished step. |
| `oracle` | Screens candidates from your activity, saved papers, tracked researchers' coauthors and tracked Substack publications' recommendations; confirms picks and records what each is owed, which the background process pulls. |
| `add_oracle` | Adds a person or supported research venue. Verifies personal profiles or pulls papers through OpenAlex. Preview first; confirming writes. |
| `search` | Ranked keyword retrieval over the whole store: optional tag/kind/source/author filter, then BM25 ranking. Read-only. |
| `open` | Returns stored source text and its source link. Reading a shared knowledge base uses its service. Check `body_state` before treating the text as complete. |
| `aggregate` | Exact counts by source, kind and author. Optional `sample` supplies descriptions from your own corpus to help identify subjects; counts still cover the whole scope. |
| `latest` | What is new since you last looked: per topic you follow, the finds waiting for your AI's check and your people's new posts, then what arrived outside every topic. |
| `watch` | Follows topics: previews, creates, edits and drops them, and records your AI's check of the finds `latest` handed it. |
| `look` | Searches X, Substack, arXiv, OpenAlex and GitHub right now for one to three named things, over the last 1 to 30 days, keeping only results that name them. Read-only: each result carries its link and the `hopper` arguments to save it. |
| `hopper` | Previews a supported paper, repo, Substack post, article or X post, then saves it after confirmation. |
| `share` / `accept` | The one tool for reaching your library from outside your Mac: `to="person"` hands someone a link so their client can search it, `to="agent"` makes a link for an agent you run, and `to="connector"` connects your own claude.ai or ChatGPT ("connect Opyt to Google"). `accept` registers a library shared with you (`kb="their-name"`). |
| `unshare` / `forget` | Revokes one reader, disconnects your own claude.ai and ChatGPT (`reader="google"`), or deletes everything Opyt's server holds for the library (copy, Google link, share links, read counts) while the library on the Mac stays; removes one item or stops tracking one person. |
`search` answers "where is the relevant material?"; `aggregate` answers "how much, and when?";
`latest` answers "what is new?".
### Returning sources and describing the corpus
Every search result you mention must carry its clickable source link. Use the ready-made
Markdown link in `cite`, including for `frontier_atoms`, and make the item's title the link
text. `source_url` is the bare URL. `open` also returns `cite`. If `cite` is null, say the
source URL is missing; do not invent one. Open the stored text before asserting a source's
claims, and qualify incomplete text using `body_state`.
Opening a paper reads its full text from its open PDF into that response; nothing is added to the
knowledge base. A paper with no reachable open PDF stays at its abstract, and says so.
For "what topics does my knowledge base cover?", call `aggregate(sample=200)` on the user's
own store. Read `corpus_sample`, propose recurring subjects, then verify each with
`search(query=<subject>)`. Report only supported subjects with cited examples and the number
sampled out of `total`. Do not present the raw sample as the answer or save your inferred
subject names. `sample` defaults to 0 and is capped at 500. It is currently not forwarded
when `kb` selects a remote shared knowledge base.
There is no `top_topics` field or stored topic taxonomy in the current aggregate response.
The `tags` filter uses authors' source tags, which cover only part of the corpus; those tags
are not a reliable inventory of its subjects.
### Choosing sources and following an import
- `oracle(action="screen", limit=10, source=None)` returns candidates in `groups`, one per
source (top people on X, on Substack, from papers), then one for organisations, media and
projects. Each group lists its top `limit` people in rank order, with `more` counting the
rest; a screen returns at most 80 people. `source="substack"`, `"x"` or `"papers"` lists
everyone on one source without changing rank.
- A person on several sources is listed once, under the stronger, with a one-line `reason`.
Person-level endorsements rank first. Kind labels only move a card into the organisations
group. Nothing is selected by default: put the `ask` question to the user and let them pick (about 5 to start).
- Call `oracle(action="confirm", canonical_ids=[...])` with those picks. Handles and
supported URLs can also be passed in `add_handles`. Read any `refused` reasons back:
multi-author sites may be refused as personal roots. Supported research venues use OpenAlex.
- Before confirming, agree the X lookback when X is connected: a separate question,
`lookback_options.x`, offering 6 months, 1 year or 2 years (the cap). Paper and web windows
are separate. Research-venue previews report publication counts; a paper pull is capped at 2,000
works, so none of a venue's papers are pulled until `scholar_topics` are chosen. The topic
filter persists.
- `confirm` and `oracle(action="ingest", ...)` record what each person is owed and return at
once; nothing is pulled inside the call. `ingest` asks again: a wider window, subjects, or
`x_lookback="since_last"` for their newest posts. `oracle(action="progress")` says where each
person stands (`newest_posts`, `archives`, `reach`, and `stuck` with the step that clears it).
It waits up to about 40 seconds for a change. There is no `complete`, and nothing waits on
`progress`: what lands while the user is away is announced once, on their next call.
Hopper previews can read an X post or inspect an unrecognized article for scholarly metadata.
Confirming an item a topic's watch already brought in can mark it as user-saved without duplicating it;
`already_present` does not always mean no writes.
Sharing and removal use previews: `share(confirm=True)`, `unshare(confirm=True)` and
`forget(..., confirm=True)` perform the approved action. To revoke one reader, include
`reader="their-label"`. Omitting it from `unshare` deletes everything Opyt's server holds for
this library: the served copy, the Google link the Opyt connector reads through, every share
link and the read counts. The library on the Mac stays, and nothing is uploaded again unless the
user shares or connects to Google again. Someone who no longer has their Mac emails
hello@useopyt.com for the same deletion.
An invite is redeemed in Opyt on the recipient's Mac (`accept`), by pasting it into claude.ai or
chatgpt.com with the Opyt connector added (the connector's `accept`), or over plain HTTPS.
`accept` is single-phase. Its invite fragment is absent from the initial browser page request,
but `accept` sends the code to the service to redeem it. A failed operation can retain earlier
completed work, such as items saved before an import error or registration before an invite error.
### Reading a shared knowledge base over plain HTTPS (no MCP)
An invite link's code — the part after `#` — also works without any MCP client. Redeem it once,
`POST https://api.useopyt.com/v1/redeem` with body `{"code": "<the code>"}`, and store the
returned `owner` and `token`: the code is one-time and dies on redemption. Every read then
carries `Authorization: Bearer <token>`.
- `GET /v1/kb/<owner>/feed` — the newest arrivals plus a `next` cursor. Pass it back as
`?since=…&since_id=…` — or pass any timestamp as `since` — and each poll returns only what
arrived after it; empty `items` means nothing new, and the cursor echoes back so a poller
never loses its place. Arrival time, not publication date: an old essay the owner saved
yesterday is new here. Add `date_from`/`date_to` (publication date, search's exact
semantics) to keep backfill of old material out of a news answer — the rule across the
API is that `since` means "entered this knowledge base" and `date_from`/`date_to` mean
"published".
- `POST /v1/kb/<owner>/search` — body `{"query": "…"}` plus the same optional filters the
search tool takes. One answer holds at most 25 results
(`k`, default 8). To read further, add `"offset": 0`: the answer then carries `next_offset`
while more results remain, and the same search with `"offset": <next_offset>` returns the
next ones, up to the first 250. A null or missing `next_offset` means there are no more.
Without `offset` the answer is the first page and never carries `next_offset`.
- `POST …/open` with `{"atom_id": "…"}` returns an item's stored text, at most 60,000
characters at a time. A longer text comes with `raw_chars` (the whole text's length) and
`next_offset`: add `"offset": <next_offset>` for the next part, until `next_offset` is null.
A part has `body_state` `partial`, so it is never quoted as the whole source.
- `POST …/aggregate` returns counts and authors.
The served copy is updated every hour while the owner's Mac is awake, whenever their
library changed, and at once when they call `share(refresh=True)`. A 401 means access was
revoked; a 404 means the copy has not landed on the service yet — retry shortly. The service
counts reads per day per reader and records no query text and no IP addresses.
**An agent key, for the owner's own agent.** An ordinary invite gives a reader key: it reads
and can change nothing. An owner can instead make an invite for an agent they run themselves
(`share` with `to="agent"`, in Opyt on their Mac or through the connector). It is redeemed
the same way, once, at `POST /v1/redeem`, and the answer also carries `"role": "agent"`. An
agent key reads the library through the routes above, as a reader key does. It also acts as
the owner: it can save links, remove items, add people to follow, and invite or cut off
readers. The owner sees it listed with their readers and cuts it off the same way, with
`unshare` and its name; every call it makes after that answers 401.
An agent key acts through these routes, each with `Authorization: Bearer <token>`. They take no
`<owner>` in the path: the key names the library. Each has the name, the arguments (as a JSON
body) and the answer of the connector's tool of the same name
(https://useopyt.com/connector.html):
- `GET /v1/library/status` — what the library holds, when it was last published, and
`changes`: what was asked to change in it lately and how each ended.
- `POST /v1/library/hopper` `{"reference": "<url>"}` — save one link: a paper, a repository, a
Substack post, an article or one X post.
- `POST /v1/library/forget` `{"atom_id": "…"}` (or `atom_ids`, or `oracle`) — a preview of what
would be removed; the same body with `"confirm": true` removes it. Nothing asks before a
confirmed call, so show the user the preview and get their yes first.
- `POST /v1/library/watch` `{"action": "create", "name": "…", "watches": […]}` — follow a
topic; `action` is `preview`, `create`, `edit`, `drop` or `list`.
- `POST /v1/library/add_oracle` `{"reference": "@handle"}` — add one person whose writing the
library collects. Confirm with the user who it is first.
- `POST /v1/library/reader_view` `{"atom_ids": […], "title": "…"}` — one page showing those
items in full, for anyone with its link to read and comment on. It is deleted 90 days
after it was last opened.
- `POST /v1/library/share` `{"for_whom": "…"}` — a preview; with `"confirm": true`, a one-time
invite for one person, who only reads. An agent cannot make an invite for another agent.
- `POST /v1/library/unshare` `{"reader": "…"}` — a preview; with `"confirm": true`, that reader
or agent is cut off.
A change to the library answers with what happened. Its `status` is the tool's own (`saved`,
`forgotten`, `added`, …) when it is done, `working` when it is being done now, and `later` when
it will be done once the owner's computer is next on; `message` says which in one sentence to
pass on. An answer can carry `earlier`: one line for each change that finished after its call
had answered. Pass those on too.
## Install
**You are the installer.** The person pastes one message; you do the rest. In a client where you
have a shell (Claude Code, Cursor, Windsurf, any agent that runs commands) run the install
yourself and do not hand the person commands. In a client where you cannot touch settings
(claude.ai, ChatGPT, Claude Desktop without a shell) guide them one step at a time: give one
step, wait until they say done, give the next, and if their screen does not match ask what they
see. Lead with the client the person is talking to you in, and assume nothing is set up yet.
claude.ai and chatgpt.com in a browser reach only a library a Mac install builds, so a person who
starts there sets up on a Mac first: claude.ai users in Claude Desktop, chatgpt.com users in
ChatGPT Desktop.
The page, with the same paste per client: https://useopyt.com/#install
Tool referenc