This release is a pre-release and may not be stable for production use.
A knowledge base that grows from your attention across the platforms you already use, served to your AI client over MCP.
Every bookmark, follow and subscription was you deciding whose thinking is worth your time. Opyt pulls the full public archive of those people from X, Substack, GitHub, their own blogs and arXiv, and turns it into one SQLite file your assistant can search, read and count over. There is no new app and no chat UI. Your client calls the tools; your client's model does the reasoning, on the subscription you already pay for.
If it is useful to you, star it. That is how other people find it.
Why Opyt
- Nobody you did not choose. Candidates are ranked off your own follows, Lists, subscriptions, bookmarks and likes. There is no recommendation model and no trending list. Zero hits is an answer: nobody you trust has touched the claim.
- Your backlog is searchable on the first run. Everything you saved before today comes in during setup. You are not starting from an empty store.
- It grows on the days you never open it. Reading a topic end to end emits standing questions. Those questions keep running against arXiv, GitHub and OpenAlex, and stage what they find for you to review when you feel like it.
- Full archives, not the three posts you bookmarked. Confirm one person and Opyt finds their other platforms, verifies them, and pulls years of posts, repos and essays in full text.
- Free of new subscriptions. Reading and reasoning run on the AI client you already have. One metered key covers classification and embeddings, on cheap open models.
- One local SQLite file. Everything lives in
~/.opyt/opyt.db. No vault of markdown, no dashboard, no daemon, no account. - Any MCP client. Claude Code, Claude Desktop, Cursor, Windsurf, Codex, or anything else that speaks MCP over stdio.
- MIT licensed. Read the source.
Quick start
1. Install
Claude Code
# once per machine, if you do not already have uv
curl -LsSf https://astral.sh/uv/install.sh | sh
claude mcp add Opyt -- uvx --from opyt==0.1.0a5 opyt-mcp
Claude Desktop
Without a terminal: download opyt-0.1.0a5.mcpb, double-click it, and review the install screen Desktop shows you. The bundle carries its own Python and every package it needs, so it wants no uv, makes no network call at launch, and starts in about eleven seconds. That is why it is a 198 MB download.
Or from a terminal:
curl -LsSf https://astral.sh/uv/install.sh | sh
# absolute path: uv edited your shell profile, not this shell
~/.local/bin/uvx --from opyt==0.1.0a5 opyt-install-client --claude-desktop
Cursor
curl -LsSf https://astral.sh/uv/install.sh | sh
~/.local/bin/uvx --from opyt==0.1.0a5 opyt-install-client --cursor
This merges Opyt into ~/.cursor/mcp.json beside whatever servers are already there, and copies the old file aside first. Running it twice changes nothing, and --uninstall removes the entry and leaves the rest.
Windsurf
curl -LsSf https://astral.sh/uv/install.sh | sh
~/.local/bin/uvx --from opyt==0.1.0a5 opyt-install-client --windsurf
Merges into ~/.codeium/windsurf/mcp_config.json, backing up the old file first.
Anything else that speaks MCP
Install uv, run which uvx to get its absolute path, and add this to whatever the client calls its MCP config:
{
"mcpServers": {
"Opyt": {
"command": "/Users/you/.local/bin/uvx",
"args": ["--from", "opyt==0.1.0a5", "opyt-mcp"]
}
}
}
Use the absolute path, not a bare uvx. A desktop app spawns its servers with a minimal PATH that never sources your shell profile, so the directory uv's installer added to that profile does not exist as far as the process is concerned.
Let an agent do it
Paste this into the client you want Opyt in:
Install the Opyt MCP server for this client, from useopyt.com/docs.html.
0.1.0a5is a pre-release, so the version is pinned explicitly. Drop the pin once a stable release exists.On macOS, also run
~/.local/bin/uvx --from opyt==0.1.0a5 opyt-install-workeronce. That installsopyt-workeras a LaunchAgent, which is the only thing that runs the background rails. Skip it and every tool still works, but nothing refreshes on its own. It opens no port and starts at login.Opyt does not run at claude.ai. Your client starts Opyt as a process on your own machine and pipes to it, and a browser tab cannot start a process on your machine. Claude Desktop is the same account and the same models, and takes one double-click.
2. Say onboard
Restart your client. Opyt appears as twelve tools. Then:
you › onboard
⚙ onboard()
x bookmarks 691 saved posts, full text
x likes 99 liked authors
x follows 492 accounts
x lists 6 lists
substack 19 subscriptions · 12 saved posts, full text
read from your logged-in browser · no API key, no password
claude › Everything you saved is in. 703 posts are now searchable,
and Opyt knows the 1,193 accounts they came from.
A browser tab opens for the one key it needs and you click Approve. Nothing is ever pasted into chat. It then looks for a browser already signed into x.com, which is how Opyt reads X, so there is no X key to get.
3. Say oracle
you › oracle
⚙ oracle()
1 @jasonfurman you follow · you subscribe · saved 12×
2 @drvolts you follow · saved 8×
3 @karpathy liked 4×
Confirm the ones you want and Opyt pulls each person's whole archive: their X posts, their GitHub repos and READMEs, their essays on their own site, going back years.
The tools
Twelve tools. Every argument, its type and default, what each call returns and what it costs are in the full reference. The docstring on each tool in mcp_server/ is the authoritative version of the same thing.
| Tool | Effect | What it does |
|---|---|---|
onboard |
writes · re-entrant | Acquires the one key, reads your browser session, takes consent, runs the first import. |
oracle |
writes · spends | Screens the people your own signals already point at, confirms them, ingests them. |
add_oracle |
two-phase · spends | Adds one person by handle or URL, finds everywhere they publish, pulls the archive. |
search |
read-only | Ranked retrieval over the store, filterable by tag, kind, source, author and date. |
open |
read-only · free | The full stored text of one atom, plus its live source URL. |
aggregate |
read-only · free | Counts across the whole store rather than a sample: by topic, author, kind, date. |
sitting |
writes · spends | Reads every atom on one topic end to end, in publication order, through a lens. |
frontier |
writes · free | The ranked review queue of what your standing questions found. |
hopper |
two-phase · spends | Takes any URL, works out what it is, and saves it as an atom. |
share |
two-phase · publishes | Shows you what is in your store, then hands you a link that lets someone search it. |
accept |
single-phase · writes | Registers a knowledge base somebody shared with you, from the link they sent. |
unshare |
two-phase · deletes | Cuts off one reader, or every reader plus the served copy. |
The server owns only the last segment of each name. A client that namespaces adds its own prefix, so search reaches Claude Code as mcp__Opyt__search. All twelve return a JSON object.
search, open and aggregate are the LLM-free retrieval core: they return data and your host model does the reasoning. They are also the only three that take kb=, which reads a knowledge base someone shared with you.
Examples
Find something
you › Are tariff price effects one-off or recurring?
⚙ search("are tariff price effects one-off or recurring")
claude › Three of your own sources speak to this. The closest is a post
you bookmarked yourself in June:
0.81 @jasonfurman x post 2026-06-11 you bookmarked this
0.74 @m_c_klein essay 2026-05-04 from an author you confirmed
0.71 @nicktimiraos x post 2026-04-22
search is a router, not an answer. Each hit is a card plus a pointer, and open(atom_id) returns the full stored text before you assert what a source says. Ranking is pure relevance: a BM25 arm and a semantic arm fused by rank, with no trust or popularity re-rank on top.
Read a topic end to end
you › Read everything I've got on GLP-1 drugs and muscle loss,
and lay out what it actually claims.
⚙ sitting(action="read", lens="claims")
claude › Eleven claims came back, each paired with what would break it
and each citing the documents it came from. The first three:
claim what would disprove it
The headline number hides what came Body-composition scans published
off: fat and muscle go together. beside the headline figure.
A 28% loss is not comparable to a 15% One measurement standard adopted
one unless both measured lean mass across the trials.
the same way.
At population scale the binding Oral dosing flattening the
constraint is people staying on the discontinuation curve.
drug, not how well it works.
A sitting is the complement to a search. Search returns the best few; a sitting walks every document on the topic in publication order, so a position that changed over time reads as a change instead of a contradiction. Five lenses read the same material differently:
| Lens | What it returns |
|---|---|
claims |
Every claim, each paired with what would disprove it. |
briefing |
What holds, what is still open, and where your authors converge. |
trajectory |
The phases the thinking moved through, in publication order. |
disconfirmation |
The case against a claim you name, from your own sources only. |
gaps |
Whether your material answers a question, and the nearest misses if not. |
Every sitting also emits standing questions, which is what feeds the next section.
Catch up on what arrived
Tuesday
you › Read everything I've saved on data-center grid costs.
codex › Three questions came out of that read and they are still
running. You typed none of them.
Friday · you ran nothing in between
you › Anything new?
⚙ frontier()
codex › Six things came in since Tuesday, ranked by how many of your
own questions reached each one:
1 paper Who Pays for the Load? Data-Center Demand and Retail Rates
all 3 questions · 2 of your topics
2 repo PyPSA/pypsa-eur 1 question · 1.2k stars
3 paper Interconnection Queue Times by Region, 2019 to 2026
1 question
+ 3 more staged, ranked the same way
The queue sits on your machine and waits. Opyt says at the start of a session that something is there, and never pushes the list itself into context uninvited.
Count what you actually read
you › What have I been reading most since March?
⚙ aggregate(date_from="2026-03")
cursor › Tariffs and inflation is your biggest topic since March,
with grid and data centers close behind:
tariffs & inflation 96
grid & data centers 84
interconnection queues 53
GLP-1 trials 41
This is a count over everything you saved, in one pass, not an impression from what you remember saving.
Keep something
you › Save this: arxiv.org/abs/2608.01144
⚙ hopper("arxiv.org/abs/2608.01144")
routed → paper ingester
preview: "Marginal Cost Allocation for Large Flexible Loads" · 22 pages · confirm?
you › yes
claude › Saved in full text, searchable beside everything else.
One sentence. No folders, no tags, no deciding where it belongs.
What it reads
| Source | What comes in | Access |
|---|---|---|
| X | bookmarks · likes · follows · Lists | your browser session |
| Substack | subscriptions · saved posts, full text | your browser session |
| GitHub | a tracked person's repos and READMEs | public |
| Blogs | a tracked person's whole archive, found by feed or sitemap | public |
| arXiv · OpenAlex | papers matching your standing questions | public |
| Any URL | a paper, post, article or repo you hand it, saved in full | you |
| posts and articles from the people you track | coming soon | |
| Aggregators | Hacker News · Hugging Face · npm · crates.io · Stack Overflow · Product Hunt · Homebrew | coming soon |
| Funding | company and round data, asked by name | coming soon |
X and Substack are read from the browser session you are already signed into, on this machine only. There is no API key and no password.
How it works
Content enters the store as atoms, Opyt's unit of stored content, one per post, repo or paper. Two independent paths put them there.
Track a person. add_oracle (or oracle to browse candidates first) takes a handle or URL. Opyt auto-detects which of X, Substack, a personal blog and GitHub that person actually publishes to, then pulls their full archive from each. Candidate ranking is lexicographic: person-level acts such as a follow, a subscription or a List outrank content-level ones such as a bookmark or a like. A background rail keeps every tracked person's sources current without you asking again.
Let Frontier watch your standing topics. Frontier is a passive discovery rail. It runs the standing questions your sittings emitted against arXiv, GitHub and OpenAlex on a schedule, judges what it finds against your topics, and stages the good ones. You read the queue with frontier when you want to.
Everything lands in one SQLite database at ~/.opyt/opyt.db: atoms, their chunks, a full-text index, and the entity and trust edges between tracked people. The MCP server is the only interface, and it creates its own store on first use.
Eight background rails do the unattended work — refreshing tracked people's sources, running Frontier's stages, pulling new X bookmarks. A resident process, opyt-worker, is the only thing that launches them; run opyt-install-worker once and macOS starts it at login. Each rail is independently gated and its exit code is recorded, so a stalled or failing rail never blocks the others or the server itself. Without the worker installed, the MCP tools still work and nothing runs unattended.
How calls run
Opyt is free and MIT-licensed. Configure one OpenRouter key for the model calls that classify, extract, embed, and read incoming material.
| For | Access | |
|---|---|---|
| Your AI client | every question, every answer, all the reasoning | your configured client |
| OpenRouter | sorting, extracting, embedding, and reading incoming material | configured API key |
| X | posts, timelines, profiles, threads | your OPYT-managed browser session |
| Everything else | papers, blogs, GitHub, Substack, any URL you hand it | public source access |
opyt-keys --list shows which credentials are set, never their values. .env.example documents each one.
Share a knowledge base
search, open and aggregate take a kb= argument that reads someone else's store instead of your own.
you › I need to pick an eval setup for retrieval.
Has anyone already been through this?
⚙ search("evaluating retrieval quality", kb="ana")
⚙ search("evaluating retrieval quality", kb="ravi")
claude › Two of them have. Ana read the framework and its repo in March,
and Ravi has the benchmark it is usually compared against.
Nothing on this in yours.
Getting there is two sentences and no terminal. On your side:
you › share my knowledge base with Leo.
⚙ share()
claude › 2,876 atoms, mostly papers and X posts, from March 2024 to yesterday.
The top authors are @karpathy, @hypersoren and @swyx. Sharing sends
all of it, it lasts until you revoke it, and it stays current on its
own. Go ahead?
you › yes
⚙ share(confirm=True, as_name="David", for_whom="Leo")
claude › Send Leo this: https://useopyt.com/invite#Kq7f...
On theirs:
you › someone sent me this: https://useopyt.com/invite#Kq7f...
⚙ accept("https://useopyt.com/invite#Kq7f...")
claude › Registered. Ask me anything and I can search David's reading too,
with kb="David".
Publishing is self-service: no invitation and no account. The served copy refreshes itself when somebody has read it since your last push and your store has changed since then, so an unchanged store never re-uploads and a knowledge base nobody reads costs nothing. unshare(reader="Leo") cuts off one person and leaves the copy serving; unshare() cuts off everyone and deletes it.
Under it, a push is a full replace: an export is a projection of a store, not a log of changes to one, so the newest upload wins. Both acts run from your assistant — share publishes and mints the invite, accept redeems one — and neither has a terminal command any more. service/DEPLOY.md has the operator procedure for a machine with no assistant on it.
Design principles
Three constraints hold across the whole codebase, enforced by an AST-based guard (scripts/guard.py) that runs in pre-commit.
- Distributable. Nothing may assume a specific machine. Paths are derived at runtime from
sys.executableandPath(__file__), never hardcoded. - Client-agnostic. The core and every knowledge tool run on any MCP client, not just Claude Code. Claude-Code-specific behavior is opt-in and never load-bearing.
- Fail-safe. A missing optional input degrades to an empty result, not a crash. A failed external call skips cleanly. It never writes partial state and never marks unfinished work done.
Contributing
The install path above is for using Opyt. To work on it:
git clone https://github.com/maimond123/Opyt.git
cd Opyt
bash scripts/setup.sh # venv, editable install, git hooks
pytest tests/
setup.sh assumes a python3 that already satisfies requires-python >= 3.10 and does not check. A stock macOS python3 is 3.9.6, as is the one xcode-select --install delivers. Read the header of that script before running it.
Issues and pull requests are welcome at github.com/maimond123/Opyt.
License
MIT. See LICENSE.
Built with ❤️ in New Jersey
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file opyt-0.1.0a5.tar.gz.
File metadata
- Download URL: opyt-0.1.0a5.tar.gz
- Upload date:
- Size: 741.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d17498653ad421104e065569a7c851bb288d7fe72f6ad713b1952f7c34db1f11
|
|
| MD5 |
3564b70642e00034e4edfbb01f1891c2
|
|
| BLAKE2b-256 |
db02e3f8f99b4c7eb435e4e21e4855c90decac2bb7cb964e7b3d072fd3416b89
|
File details
Details for the file opyt-0.1.0a5-py3-none-any.whl.
File metadata
- Download URL: opyt-0.1.0a5-py3-none-any.whl
- Upload date:
- Size: 774.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
132d1988f4f73e6a5a3310f824a8601435b4454f1aba0187b2306bb92dbbdd36
|
|
| MD5 |
cd7bb6c3df7eec09735b5e7a3549594c
|
|
| BLAKE2b-256 |
ff1c9fa5d9b761ba3a26f4106451fd32d930b2639b73c13b3f137a78377d096f
|