NEWPromptev MCP Server — One URL. Every tool. Your team’s knowledge. Connect Claude Code, Cursor, or any MCP client.

Permission-aware retrieval

Retrieval that knows who is asking.

Every point above is a chunk of somebody’s corpus, clustered by the team that owns it. Ask a question and the index returns the nearest ones — then your ACL deletes most of what came back. Nothing in the stack reports the loss. The answer just arrives thinner.

See the measurement

Python 3.12+  ·  Node 22+  ·  one Postgres schema, either client

Recall · permissions assigned at random
0.980
Recall · permissions clustered like real teams
0.473
Same corpus · same index · same query set
−52%

The mechanism

The index stops searching before it reaches you.

An approximate index walks its neighbours in distance order, discards the ones your filter rejects as it meets them, and stops when its budget is spent. When a team's documents sit together in the embedding space — as they always do — that budget goes almost entirely on rows the caller may not read. Below: one query, its search radius, and who is inside it.

  • Query
  • Returned
  • Permitted, withheld
  • Other principals
Measured recall @ 10% visible
0.980
Result slots filled — asked for 20

19 of 20 permitted rows returned · 1 withheld Measured recall 0.980.

The easy case. Permissions assigned at random scatter every group evenly through the vector space, so the neighbourhood around any query already contains rows this caller can read. Almost nothing is lost — and almost no real corpus looks like this.

Measured on 200,000 documents from BEIR nq at 1536 dimensions, scored against an exact brute-force oracle over the eligible rows. Drop visibility to 1% and clustered permissions return nothing at all for 75% of queries — not a degraded answer, an empty one.

Don't take our number

Measure your own corpus, offline.

A benchmark on someone else's data proves nothing about yours — the loss depends entirely on the shape of your permissions. So the measurement ships as a command. It samples your real ACL values, replays query vectors against your own index, and never sends a byte anywhere.

check-acl-exposure
$ context-engine check-acl-exposure --database-url $DATABASE_URL --samples 200

  Corpus: 214,880 chunks with embeddings   top_k=10

  principals                  visible         plan    plain   engine   empty      p50
  -----------------------------------------------------------------------------------
  group:legal                    3.4%         hnsw    0.312    0.941    31.0%    18.7ms
  group:finance                  8.1%         hnsw    0.508    0.933     9.5%    16.2ms
  group:hr                      11.9%  bitmap+sort    0.964    0.971     0.0%    22.4ms

  plain  = a bare scoped query, which is what most stacks issue
  engine = this library's shipped retrieval path on the same rows
  Both are scored against an exact scan over the eligible rows.

  VERDICT: exposed — the worst scope (group:legal) returns only 31.2% of what an
  exact search would, and 31% of queries came back empty.

  Callers in that scope are silently getting fewer results than exist.
  No error is raised and nothing is logged, so this does not show up in
  monitoring.

Sample output — your numbers depend on the shape of your permissions

What it is

A library over the Postgres you already run.

Full-text, trigram, vector and graph legs fuse through Reciprocal Rank Fusion — and all four compile through one scope predicate. There is no permissive default and no way to call retrieval without saying who is asking.

Enforced inside the plan

The ACL is a SQL predicate every retrieval leg shares, so a document you may not see is never fetched, never ranked, and never reaches a reranker.

Redaction before embedding

A pure transform — no model, no network. Mask at ingest and the secret never enters the index; mask at output and it never leaves. ACL decides who, redaction decides what.

Your Postgres, your keys

BYO database, embedding provider and LLM. The package never calls a hosted service you didn't configure — no control plane, no egress, no vendor holding your corpus.

Python and TypeScript

Two clients, one Postgres schema — a corpus ingested by either is searchable by the other. Serve it on FastAPI, Flask, Django, Hono, Express, Fastify, or over MCP.

Requirements are a database, not a stack: PostgreSQL 14+ with vector (pgvector 0.8+) for the semantic leg, pg_trgm for fuzzy and partial matching, and unaccent so café and cafe match. All three are stock PostgreSQL and the engine creates them itself on the first migration — you install nothing by hand.

Start here

Find the loss before an auditor does.

Install it, point it at the database you already have, and read the verdict it prints. If the loss isn't there, you've spent four minutes.

Read the write-up

Apache-2.0  ·  Python 3.12+  ·  Node 22+  ·  PostgreSQL 14+ with vector (pgvector ≥ 0.8), pg_trgm and unaccent

2,500 free credits · No card required · No subscription