AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Hopai

mcp-alexbojko-hopai · by alexbojko

A knowledge graph in the Postgres you already run — multi-hop traversal, mutation, and vector search via recursive CTEs on two plain tables. No graph database, no extension.

— No reviews yet
0 installs
31 views
0.0% view→install

Install

$ agentstack add mcp-alexbojko-hopai

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ✓ Environment & secrets No
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-alexbojko-hopai)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 1mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Hopai? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

🐘 hopai

A knowledge graph in the Postgres you already run — no graph database required.

[](https://github.com/alexbojko/hopai/actions/workflows/ci.yml)

[](https://github.com/astral-sh/ruff) [](LICENSE)

hopai compiles multi-hop graph traversals into a single recursive CTE against two ordinary PostgreSQL tables (nodes, edges, each with a JSONB properties bag) — no extension, no sidecar service. Traversal, ingestion, updates, real constraints and vector search, through a Python API, a JSON one, and a Cypher subset, so an LLM agent and a human developer can both use it with nothing new to learn.

> Early and moving. hopai is pre-1.0 (0.0.x). Every feat/fix > release may change an interface — a method signature, a JSON key, a > refusal's exact wording — without a deprecation cycle; pin a version if > that would break you. Schema migrations don't exist yet either: > drop_schema() + create_schema() is the upgrade path for now. See the > changelog > before bumping.

⚡ Quick start

pip install hopai
from sqlalchemy import create_engine
from hopai import Graph, Start, Hop

graph = Graph(create_engine("postgresql+psycopg2://user:pass@host/db"))

# "Which companies do Alice's friends work for — counting friends of
#  friends, up to four hops out, and only people who are still active?"
result = graph.traverse(
    Start(where={"name": "Alice"}),
    Hop(via={"kind": "friend"}, hops=(1, 4), where={"active": True}),
    Hop(via={"kind": "works_at"}, where={"type": "company"}),
)

result.nodes            # [{"id": "1", "properties": {...}}, ...]
result.edges            # [{"id": "7", "start_id": "1", "end_id": "2", "properties": {...}}, ...]
result.to_networkx()    # in-memory graph, if you have networkx installed

Read a traversal left to right as a sentence: start here → follow these edges this many times → land on nodes like this → then again. You get back the whole matching subgraph, not just the endpoints. The full walkthrough is [01_quickstart](notebooks/01_quickstart.ipynb).

🔭 Every way to ask

One engine underneath all of it — the same "Alice's friends" question as above, other front ends that ask it, and the other things this engine answers:

# JSON -- for an LLM tool call, an HTTP handler, or config-driven traversal
traverse_json(graph, {"start": {"where": {"name": "Alice"}},
                      "hops": [{"via": {"kind": "friend"}, "hops": [1, 4]}]})

# Cypher -- for a caller, or a model, that already thinks in it
graph.cypher("MATCH (a:person {name: 'Alice'})-[:friend*1..4]->(b) RETURN b")

# Aggregate -- a number instead of a subgraph, computed in the database
graph.aggregate(Start(where={"name": "Alice"}),
                Hop(via={"kind": "friend"}, hops=(1, 4)),
                aggregates={"count": Count()})               # {"count": 12}

# Change and delete -- the same filters, selecting rows to update or remove
graph.update_nodes(where={"name": "Alice"}, set={"active": False})

Similarity and traversal compose — find the nodes closest in meaning to some text, then walk the graph from there. No separate vector database, no gluing two systems together with application code:

# Vector search on its own -- exact cosine similarity, no pgvector, no extension
graph.vector_search(Near("summary", "distributed consensus"), k=10,
                    where={"type": "paper"})

# Start a traversal from similarity instead of a property match: the 5 papers
# most similar to the text, then everything they cite, up to 3 hops out.
graph.traverse(
    Start(near=Near("summary", "distributed consensus"), keep=5),
    Hop(via={"kind": "cites"}, hops=(1, 3)),
)

Hop(near=, keep=) and Hop(via_near=, via_keep=) do the same thing mid-chain — rank what a hop reaches, or beam over each node's edges, by similarity instead of only filtering by property. See Vector search in the table below.

Reranking is the third stage — retrieve wide and cheap with the cosine above, then actually read each candidate against the query before keeping k. It attaches to a flat search and to a traversal step alike:

import cohere
from hopai import Rerank

reader = Rerank(cohere.ClientV2(), model="rerank-v3.5",
                document_from='.properties.title + ": " + .properties.summary',
                candidates=50)                                # pool the reranker sees

graph.vector_search(Near("summary", "distributed consensus"), rerank=reader, k=10)

No provider package imported here either — a Cohere/Voyage client, a sentence-transformers CrossEncoder, or a plain callable all work. See Reranking in the table below.

Every one of these compiles through the same query builder, so the SQL, the semantics and the invariants are identical no matter which front end wrote the call. The ["Learn more"](#-learn-more) table below is where each one's full reference lives.

✨ Highlights

  • 🐘 Plain PostgreSQL — two tables, a recursive CTE, no extension, no

new operational dependency. (One opt-in exception, off by default: vector_backend="pgvector".)

  • 🧭 Real multi-hop traversal — bounded and unbounded hops, per-hop

direction, OPTIONAL, rich JSONB filtering, one round trip.

  • ✏️ Update and delete by filter — SET / REMOVE / DETACH DELETE

semantics, with a filterless call refusing rather than emptying the graph.

  • 🧮 In-database aggregation — count / sum / avg / min / max

computed where the data lives.

  • 🧬 Many graphs, one database — a graph is a string, not a schema;

cross-graph edges are impossible by construction (composite FK).

  • 🤖 Three front ends, one engine — Python, JSON (with a ready-made LLM

tool schema), and a Cypher subset all compile through the same builder.

  • 🔌 An MCP server in one command — hopai-mcp exposes reading, writing,

schema and similarity tools, with permissions deciding which tools exist.

  • 🌐 An HTTP API and a graph explorer — hopai-api serves the same

operations as JSON for a browser, and a self-contained page at / that draws the graph, filters it, and edits it under the same permissions.

  • 🔐 Constraints Neo4j puts behind an enterprise licence — unique,

composite, partial, existence, type and CHECK constraints on JSONB.

  • 🧲 Similarity-seeded traversal — find the nodes closest in meaning to

some text, then walk the graph from there, in one call. Exact cosine similarity on plain real[] columns by default — no pgvector, multivector queries, and a field-level embed= so you can hand it text instead of floats — plus an opt-in vector_backend="pgvector" for when the row counts outgrow exact search.

  • 🎯 Reranking, including inside the walk — `Rerank(client,

document_from='') adds a third retrieval stage to a search *and* to a traversal step, where a candidate is a node plus how it was reached. A model may write the projection: it's validated against a total jq subset in which env` doesn't parse.

  • 🧪 Tested like it matters — an 85% coverage gate and mutation testing

in CI, and real benchmark numbers in benchmarks/.

🗄️ Schema

graph.create_schema()   # idempotent; safe to call on every start-up
CREATE TABLE nodes (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    properties JSONB NOT NULL DEFAULT '{}'
);
CREATE TABLE edges (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    start_id BIGINT NOT NULL REFERENCES nodes(id),
    end_id   BIGINT NOT NULL REFERENCES nodes(id),
    properties JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX ON edges (start_id);
CREATE INDEX ON edges (end_id);
CREATE INDEX ON nodes USING GIN (properties);
CREATE INDEX ON edges USING GIN (properties);

Custom table/column names, and extra real columns alongside the JSONB bag (a foreign key to your own users table, with the collision refusals that keep it distinct from a JSONB property) are covered in full in Schema; multi-graph isolation on one connection pool in [07_many_graphs](notebooks/07manygraphs.ipynb) and Many graphs.

📚 Learn more

Nothing below is summarized away — every section the README used to spell out inline now has a full write-up in one of three places, and each links to the others: a runnable notebook for the topics that have one (executed in CI on every PR, so it can't drift from the API), a guide under Reference explaining the semantics and the gotchas a notebook doesn't narrate, and a generated API reference — hopai.core for Graph itself, and one page per module for everything else — built straight from the library's own docstrings and signatures, so it is the one tier of this table that is structurally unable to go stale:

| Topic | Notebook | Full reference | | --- | --- | --- | | Schema — two tables, extending the model with real columns | [08_under_the_hood](notebooks/08underthehood.ipynb) | Schema | | Many graphs, one database | [07_many_graphs](notebooks/07manygraphs.ipynb) | Many graphs | | Getting data in — add_nodes/add_edges/merge_*/ingest | — | Getting data in | | Changing and deleting — update_*/delete_*/clear/mutate | — | Changing and deleting | | Constraints — unique, composite, partial, existence, type, CHECK | [05_constraints](notebooks/05constraints.ipynb) | Constraints | | Declaring, inferring and enforcing a graph schema | [06_graph_schema](notebooks/06graphschema.ipynb) | Graph schema | | Filters — AND/OR/NOT/GT/BETWEEN, the escape hatch | — | Filters | | Traversal: direction, hop count, OPTIONAL | [02_traversal](notebooks/02traversal.ipynb) | Traversal | | Aggregation | [03_aggregation](notebooks/03aggregation.ipynb) | Aggregation | | Vector search, hybrid ranking, text-to-vector embedding | [09_vector_search](notebooks/09vectorsearch.ipynb) | Vector search | | Reranking — the third retrieval stage, including step-wise | [10_reranking](notebooks/10reranking.ipynb) | Reranking | | The JSON interface | [04_json_and_cypher](notebooks/04jsonandcypher.ipynb) | JSON interface | | Cypher as input syntax | [04_json_and_cypher](notebooks/04jsonandcypher.ipynb) | Cypher | | What this doesn't do (yet), and why each refusal is a refusal | — | Limits | | MCP server — client setup, every tool, every flag | — | Full guide | | HTTP API and the graph explorer | — | Full guide | | Read/write pipelines, multi-graph internals, gotchas | — | architecture.md | | Fixtures, coverage gate, mutation testing | — | testing.md | | release-please, PyPI trusted publishing | — | releasing.md |

See [notebooks/README.md](notebooks/README.md) for how to run the notebooks yourself against a throwaway database.

🔌 MCP server

The same graph as an MCP server, so Claude Desktop, Claude Code, an IDE or an agent framework can use it with nothing to write:

pip install "hopai[mcp]"
hopai-mcp --dsn postgresql+psycopg2://user:pass@localhost/db --read-only

Eleven tools — traverse, aggregate, Cypher, ingest, update/delete, schema inference/declaration, and similarity search. Permissions decide which tools exist: --read-only registers reading only, the default adds writing, --allow-mutations adds deleting, --allow-ddl adds enforce_schema. 📖 Full guide.

🌐 HTTP API and graph explorer

A browser cannot reasonably speak MCP — that is JSON-RPC with session negotiation over a streaming transport — so the same operations are served as ordinary JSON, plus a graph explorer at /:

pip install "hopai[http]"
hopai-api --dsn postgresql+psycopg2://user:pass@localhost/db --allow-mutations
# → explorer at http://127.0.0.1:8080/

Switch graphs, toggle node types, filter by name or by property values, drag and pin nodes, show edge labels, repoint an edge, delete a row. The page reads the permissions the server was started with and hides what it would refuse; every delete takes two clicks and names what will go. The whole page is one file with no CDN, shipped in the wheel. 📖 Full guide.

docker compose up -d brings up Postgres, this API and the MCP server together.

⏱️ Async

AsyncGraph (pip install hopai[asyncio]) covers traversal, aggregation, ingestion, mutation and vector search for an async app — the same query builders Graph runs, reached through SQLAlchemy's sync/async bridge. Schema and constraint declaration stay on the sync Graph — one-time setup calls with no concurrency to gain. See hopai/asyncio.py and the Async section of architecture.md for the bridge design and the benchmark behind it.

🚧 What this doesn't do (yet)

  • No disjoint multi-pattern matching — one linear chain of hops only.
  • OPTIONAL only on the last hop, not mid-chain.
  • Aggregation covers count/sum/avg/min/max over the last step's

matched nodes, numeric properties only — no grouping, no stddev/percentiles.

  • Deletes and updates select rows by their properties, never by where a

traversal arrived.

  • Vector search is exact and unindexed by design — no ANN, no

late-interaction multivectors.

  • Embedding retries transient failures but does not cache or rate-limit —

that's the application's and the client's job, respectively.

  • A cycle-protection path array on every recursive row is measurably

not-cheap past roughly 10 hops on a single-segment traversal.

Each refusal names the rewrite rather than approximating — see the full list for the reasoning behind each one, and architecture.md / hopai/vectors.py/hopai/cypher.py for the implementation.

🛠️ Development

pip install -e ".[dev]"
docker compose up -d      # throwaway PostgreSQL matching the default DSN
pytest tests/ -v
ruff check .

Most of the suite needs no database — query shape, filter compilation and the Cypher translator are tested against compiled SQL. CI enforces an 85% line coverage floor and runs mutation testing on every PR. See testing.md.

📄 License

MIT — see [LICENSE](LICENSE).

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.