# 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. 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` | Hands someone a link so their client can search your knowledge base, or registers one shared with you (`kb="their-name"`). | | `unshare` / `forget` | Revokes one reader, 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=)`. 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. `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": ""}`, and store the returned `owner` and `token`: the code is one-time and dies on redemption. Every read then carries `Authorization: Bearer `. - `GET /v1/kb//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//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": ` 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": ` 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 `agent=True`, 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 `. They take no `` 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": ""}` — 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 reference: https://useopyt.com/docs.html ### Claude Desktop If you can run commands on the Mac, run these yourself. If you cannot, say so, tell the person how to open Terminal, and give them the commands one at a time, waiting after each. ```bash curl -LsSf https://astral.sh/uv/install.sh | sh # absolute path: uv edited the shell profile, not this shell ~/.local/bin/uvx --from opyt@latest opyt-install-client --claude-desktop ``` Merges Opyt into Claude Desktop's config beside whatever is already there, backing the old file up first. Running it twice changes nothing; `--uninstall` removes the entry and leaves the rest. Then quit and reopen Claude Desktop and follow "After any local install" below. ### Claude Code Run these yourself. Opyt ships on PyPI, so there is nothing to clone or build. ```bash # once per machine, if uvx is not on the PATH curl -LsSf https://astral.sh/uv/install.sh | sh # the absolute path to uvx: `command -v uvx`, or ~/.local/bin/uvx right after installing uv # -s user: every project, not only the current folder (the default scope is local) claude mcp add -s user Opyt -- /absolute/path/to/uvx --from opyt@latest opyt-mcp claude mcp list ``` Then follow "After any local install" below. ### claude.ai — set up on a Mac in Claude Desktop, then read here claude.ai cannot build a library: Claude Desktop on a Mac does, and claude.ai reads it through the Opyt connector. Assume nothing is set up. You cannot install anything or add the connector yourself, so walk the person through these one at a time. The connector address is `https://api.useopyt.com/mcp`: an address to **paste**, never a page to visit (opening it in a browser shows an error, and that is correct). 1. If they don't have Claude Desktop on their Mac, they download it from `claude.ai/download` and sign in with the same Claude account. 2. In Claude Desktop they paste the Claude Desktop install (above), quit and reopen it, say `onboard`, and follow setup to the end. 3. When setup offers it, or any time after, they say "connect Opyt to Google" in Claude Desktop, enter the code on Google's page and approve. 4. On claude.ai: their name at the bottom-left corner, then **Settings**, then **Connectors** (some accounts: **Customize**). The **+** next to Connectors, then **Add custom connector**. 5. Paste `https://api.useopyt.com/mcp` and click **Add**. Name it `Opyt` if asked. Leave **Advanced settings** alone — Opyt needs no client ID and no secret. 6. **Connect**: an Opyt page asks whether to allow Claude — **Allow Access** — then Google, with the **same Google account** as step 3. Claude never sees the Google password. 7. In a new chat, the **+** in the message box, **Connectors**, turn Opyt **on**. A connector left off cannot be used — this is the step people miss. 8. Ask about the library. The connector searches it by keyword. It can also save a link, follow a topic, add a person or remove an item: Opyt on the Mac makes those changes, at once when the Mac is on and otherwise when it is next on. Setup, and choosing people from those the user already follows, happen in Claude Desktop. The connector's full documentation (its five read tools and reader_view, sign-in, status answers, errors, limits and what the server keeps): https://useopyt.com/connector.html ### ChatGPT Desktop ChatGPT Desktop, the ChatGPT app for Mac, runs local MCP servers and reads them from `~/.codex/config.toml`, the file it shares with Codex. Assume nothing is set up. If you can run commands, do these yourself; if not, tell the person how to open Terminal and give them one command at a time. 1. If `uvx` is not on the PATH, install uv: `curl -LsSf https://astral.sh/uv/install.sh | sh` 2. Add Opyt to `~/.codex/config.toml` (create it if missing, keep what is in it, and replace an existing `[mcp_servers.Opyt]` table rather than adding a second), with the absolute path from `command -v uvx`: ```toml [mcp_servers.Opyt] command = "/absolute/path/to/uvx" args = ["--from", "opyt@latest", "opyt-mcp"] startup_timeout_sec = 120 tool_timeout_sec = 120 ``` The first start downloads Opyt, which takes longer than the default 10-second start limit. 3. They quit and reopen ChatGPT, type `/mcp` to see Opyt listed, and say `onboard`. ### Codex Codex reads the same `~/.codex/config.toml`, so an install in either shows up in both. Run the two steps above yourself in the terminal, then tell the person to restart Codex (in the IDE extension: **Restart extension**), check `/mcp`, and say `onboard`. ### chatgpt.com — set up in ChatGPT Desktop, then read here chatgpt.com cannot build a library: ChatGPT Desktop does, and chatgpt.com reads it through the Opyt connector, on a paid plan. Assume nothing is set up, and guide the person one step at a time. 1. If they don't have ChatGPT Desktop on their Mac, they download it from chatgpt.com and sign in with the same account. 2. In the app they set up Opyt as above, say `onboard`, and follow setup to the end. 3. When setup offers it, or any time after, they say "connect Opyt to Google" in the app, enter the code on Google's page and approve. 4. On chatgpt.com: **Settings**, switch **Developer mode** on (under **Apps & Connectors** → **Advanced**, or **Security and login**; OpenAI has moved it more than once). Read them the warning it shows: a custom connector runs someone else's code for them. 5. **Create** (or **New app**), name it `Opyt`, paste `https://api.useopyt.com/mcp` as the **MCP server URL**, choose **OAuth**. No client ID, no secret. 6. **Allow Access** on the Opyt page, then sign in with Google, with the same Google account as step 3. 7. In a new chat, switch Opyt on from the **+** menu, then ask about the library. The connector searches it by keyword. It can also save a link, follow a topic, add a person or remove an item: Opyt on the Mac makes those changes, at once when the Mac is on and otherwise when it is next on. The connector's full documentation: https://useopyt.com/connector.html ### Cursor Run these yourself in the terminal: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ~/.local/bin/uvx --from opyt@latest opyt-install-client --cursor ``` Merges into `~/.cursor/mcp.json` beside whatever is already there, backing the old file up first. Running it twice changes nothing; `--uninstall` removes the entry and leaves the rest. ### Windsurf Run these yourself in the terminal: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh ~/.local/bin/uvx --from opyt@latest opyt-install-client --windsurf ``` Same, for `~/.codeium/windsurf/mcp_config.json`. ### Any other MCP client Any client that speaks MCP over stdio. Do as much as you can yourself and tell the person exactly what to do for the rest. Use the absolute path to `uvx` (`command -v uvx`) — a desktop app never reads the shell profile, so a bare `uvx` will not be found: ```json { "mcpServers": { "Opyt": { "command": "/Users/you/.local/bin/uvx", "args": ["--from", "opyt@latest", "opyt-mcp"] } } } ``` ### After any local install 1. **Restart your client.** Opyt's tools appear in it. A restart is also how you get fixes: the command says `opyt@latest`, so your client resolves the newest published Opyt each time it starts one. There is no upgrade step and no install path that can go stale. Offline it runs the copy you already have. 2. **Say `onboard`** and follow the steps below. The tool hands off to Oracle selection when setup is ready; no separate user command is required to trigger that handoff. On macOS, `onboard` also installs Opyt's background worker, which keeps the store growing while the client is closed. It starts at login, opens no port, and holds no credential of its own. ### Onboarding and connecting Substack or X 1. Call `onboard()` and follow the step it returns. Opyt is free and calls no model: search matches words rather than meaning, and images are not read. On a local install the tool may name `start="github"` or `start="openalex"` once, optional keys that lift those services' anonymous rate limits. Each hands back a page to give the user as a link: say what it is for, and call the same word again once they are done. 2. Ask for **one source at a time**: X, Substack, research, a personal site, or skip. More sources can be connected later. For Substack call `onboard(source="substack")`. 3. Sign in to X or Substack in the Opyt-owned browser window the tool opens. Opyt does not use the sessions in the user's normal browser profiles. 4. When the window is signed in, call `onboard()` again. Never request passwords, email codes, cookies or sign-in links in chat. If a call's reply is lost, reread progress with bare `onboard()` rather than resending its sign-in arguments. 5. Nothing else is asked. Connecting X or Substack imports the posts the user saved there and keeps them current, and every confirmed Oracle is kept current. Do not invent a question about it. 6. Follow the tool's handoff to `oracle`, show the candidates and let the user choose. Imports can continue after the initial response. Scheduled work runs while the Mac is awake, including when the chat is closed, and resumes after sleep. Describe progress from the returned presentation; do not promise that a laptop's time asleep counts as import time. ## What it costs and what calls out Opyt is free and MIT-licensed, and it calls no model at all: search matches words rather than meaning, and images are not read. Opyt used to embed everything it stored; with the AI client reading the results and searching again in other words, the difference in precision and recall was negligible, so embeddings went. When a conceptual question comes back thin, search again with a synonym or the term of art. Your own AI client handles every question, answer and piece of reasoning on the subscription you already pay for. X and Substack access use your connected OPYT-managed browser session; everything else — papers, blogs, GitHub, any supported article page you hand it — is public source access. Local retrieval runs on the user's machine and sends the query nowhere. Searching a remote shared knowledge base sends the query and filters to its service. The Opyt connector serves claude.ai and ChatGPT a copy of the user's library kept on Opyt's server.