A local-only, single-binary execution layer for AI agents. Point it at your databases and infrastructure; agents get CLI-shaped access over MCP, with native Go execution, local guardrails, a full audit log, and a context store that makes every session start warmer than the last.
$ access serve
access v0.1.0 — listening on http://127.0.0.1:4820
UI: http://127.0.0.1:4820/?_token=…
MCP: http://127.0.0.1:4820/mcp
Execute against: postgres, mysql, sqlite, redis, kubernetes — plus warehouses clickhouse, snowflake, bigquery over their official SDKs. Learn from (never execute): a docs folder, a git repo, a dbt project — context resources that feed your agents knowledge and attach it to the databases it describes.
Every resource you hand an agent today is another MCP server: another process to run (stdio here, docker there, remote http for the third), another copy of credentials sitting in a client config, and another 5–8 bespoke tools your agent has to learn at runtime — none of which appear in its training data. And each server binds one instance — a second Kubernetes cluster isn't more config, it's a whole second server.
access collapses all four axes. Kinds become drivers inside one binary,
instances become rows in a catalog, deployment becomes one process, and the
tool surface stays at five:
One connection, five tools. Agents connect to one server exposing five
stable tools; the catalog is data returned by discover, not tool
definitions, so your tenth resource costs the prompt nothing. Smarter clients
now lazy-load tool schemas — that fixes where the schema lives, not what it
costs: a bespoke tool is still an API the model has to learn from its
description, every session. access doesn't shrink the textbook; it removes
it.
The interface agents already know. AI labs have spent billions teaching
models the terminal — psql, redis-cli, kubectl, Kibana's Dev Tools
console: the flags, the idioms, the error messages. Wrapper tools throw that
training away. The reference Kubernetes MCP server exposes 19 tools; not one
shapes output (every call returns full objects into your context), and pods
can't be filtered by CrashLoopBackOff (its own schema says so). The agent
still gets there — it just pays in hops and tokens. And real debugging rarely
stays in one system: the cluster tells you the pod was OOMKilled, but why it
grew is in the app logs — a second resource. That crossing is where the
architectures split:
With access the agent writes the command it was trained on and narrows
output at the source (--tail, -o jsonpath, LIMIT 10), and reaching the
next system is just another execute against a resource already in the
catalog — no new server, no new tool vocabulary. Guardrails subtract dangerous
verbs locally instead of a wrapper deciding the surface upfront. API-shaped
resources will speak the same five tools through http-style verbs
(calendar.event.create <params>) — soon.
No sandbox, because no code. Agents send argv
(["psql","-c","select 1"]); access parses it into typed native driver
calls (pgx, mysql, go-redis) or a fenced kubectl. Nothing an agent sends is
evaluated or shelled on your machine. That makes it as safe as — usually
safer than — a sandboxed shell: there is no shell to escape.
It learns, so agents stop re-learning. Without memory, every session
burns tokens rediscovering the same schemas and conventions. Adding a
resource builds ~/.access/context/system/<name>/*.md from live
introspection; agents search it before probing, and you add your own files
and Agent Skills (<name>/SKILL.md) alongside.
Knowledge lives in more than databases. Point access at a docs folder,
a git repo, or a dbt project and it becomes a context resource: agents
read it, never execute it. A dbt project's facts (enum values, join edges)
attach to the warehouse it describes and are cross-checked against the live
schema; a runbook folder teaches the why introspection can't see. Every
piece of knowledge carries a trust tier — introspected (from the live
system) > declared (metadata a human wrote) > external-prose (free text)
agent-learned— and all of it is DATA to the agent, never instructions.
Secrets never reach the agent. Write-only vault (AES-256-GCM), decrypted only inside a connection handshake. No API returns a secret, ever.
Read-only by default, audited always. Statement classification (SQL), command allowlists (Redis), verb allowlists (kubectl) — enforced locally, before your credential is used. Every attempt, including denials, lands in the run log.
You ask one question in plain language; the agent does the rest. Each
example is one exchange: your line (You:), then the agent's calls to
access — discover / search / execute, with each ↳ showing what
access returns — and finally the agent's plain-language reply (Agent:),
written from that returned data. The pattern never changes: look things up
first, run a command last. The lookups (discover, search) only read what
access already knows; they never touch your database or cluster.
Query by business context. You don't tell the agent where the data is —
it already knows, because access learned your infra when the resource was
added.
You: how did each product category do on revenue?
discover()
↳ dev-postgres · postgres · read-only · 6 knowledge files
search({ resource: "dev-postgres", query: "revenue" })
↳ schema.demo.md — "v_revenue_by_category: revenue rolled up per category"
↳ note.finance.md — "revenue excludes internal test accounts (email like '%@internal.test')"
execute({ resource: "dev-postgres",
argv: ["psql", "-c", "select * from demo.v_revenue_by_category"] })
↳ category | revenue
----------+-----------
Standard | 1,284,300
Premium | 902,150
Pool | 318,470
(5 rows)
Agent: Standard rides brought in the most revenue ($1.28M), then Premium ($902K) and Pool ($318K) — full breakdown above. These already exclude internal test accounts, per your finance note.
The agent never guessed at the schema or ran a throwaway query. discover and
search only read what access learned when the database was added — the
introspected schema docs plus the finance note you dropped in — so the single
query it ran was the right one, with the test-account rule already applied.
Triage a cluster without handing over the cluster.
You: checkout is slow in staging — take a look
discover({ kind: "kubernetes" })
↳ staging-k8s · read-only
search({ resource: "staging-k8s", query: "checkout" })
↳ workloads.checkout.md — checkout-api (deployment), checkout-web (service)
execute({ resource: "staging-k8s", argv: ["kubectl", "get", "pods", "-n", "checkout"] })
↳ NAME READY STATUS RESTARTS
checkout-api-7d4b9-x2vq8 0/1 CrashLoopBackOff 6
checkout-web-5c8f6-p2m9k 1/1 Running 0
execute({ resource: "staging-k8s", argv: ["kubectl", "logs", "deploy/checkout-api", "-n", "checkout", "--tail", "200"] })
↳ … Killed: memory cgroup limit exceeded (OOMKilled, limit 128Mi)
Agent:
checkout-apiis crash-looping in staging — it's being OOMKilled against a 128Mi memory limit (6 restarts so far);checkout-webis healthy. Raising the limit should stop the restarts. I stayed read-only, so nothing was changed.
The agent can get, describe, logs, top — but exec, delete,
port-forward, and credential-swapping flags are fenced off locally, and
read-only mode enforces the verb allowlist before your kubeconfig is ever
used.
It teaches itself, then you teach it more. Adding a resource makes
access introspect it and write the knowledge docs the agent reads with
search — so it starts warm, before anyone curates anything. On top of that, drop a
note or a skill ("when asked about churn, use mrr_facts, not the raw events
table") and every agent, in every future session, finds it with search
before it starts guessing. Deterministic sync keeps the self-learned docs
fresh; your curation compounds on top.
Install the latest release — one binary, no dependencies:
curl -fsSL https://raw.githubusercontent.com/dumbmachine/access-mcp/main/scripts/install.sh | sh
This drops access into /usr/local/bin (or ~/.local/bin if that isn't
writable). Pin a version by passing the env var to sh:
curl -fsSL https://raw.githubusercontent.com/dumbmachine/access-mcp/main/scripts/install.sh | ACCESS_VERSION=v0.2.0-alpha.8 sh
Or export ACCESS_VERSION=… before piping. Pick a location with
ACCESS_INSTALL_DIR=~/bin. Prefer to grab a tarball yourself? They're on the
releases page for macOS
and Linux (arm64 + amd64), each with a checksums.txt.
Run:
access serve
Build from source instead (Go 1.25+, pnpm)
git clone https://github.com/dumbmachine/access-mcp
cd access-mcp
make # SPA + binary → bin/access
./bin/access serve
Connect your agent — one command configures every MCP client on your machine (add-mcp detects Claude Code, Cursor, Codex, VS Code, and friends):
npx add-mcp http://127.0.0.1:4820/mcp \
--name access \
--header "Authorization: Bearer <your-token>"
The token is printed by access serve and shown on the UI's Connect
page, alongside the raw endpoint for wiring a client by hand.
Prefer stdio? Clients can spawn the binary directly — no URL or token:
npx add-mcp "access mcp" --name access # or: claude mcp add access -- access mcp
access ships with a demo database already connected. Try:
Using the access MCP tools, explore the demo-sqlite resource and answer: which three artists sold the most tracks? Show the SQL you used.
Then add your own resources in the UI (Add resource) — credentials go straight into the vault and a context sync builds the resource's knowledge base from live introspection.
| tool | what |
|---|---|
discover |
lean catalog of your resources (+ how much context each has) |
describe |
argv grammar & recipes per kind; per-resource manifest |
execute |
run argv against a resource (dry_run supported; not_executable on a context resource) |
search |
search / read the context store (schema docs, notes, dbt facts, folder docs) |
help |
the invariants doc |
Two transports, same data dir:
- HTTP —
http://127.0.0.1:4820/mcp, bearer token, loopback only. - stdio —
access mcp, spawned by the client; no token needed.
Some resources are things agents should learn from, not run commands
against: a folder of runbooks, a git docs repo, a dbt project. Add one and
discover shows it as class: context with mode learn-only; execute
against it returns a not_executable error that points the agent at
search instead. Three kinds today:
| kind | what it ingests | trust tier |
|---|---|---|
folder |
a local dir of markdown/text — runbooks, ADRs, an Obsidian vault, anything that syncs to disk (Drive/Dropbox) | external-prose |
git |
the same, fetched read-only (depth-1) from a repo | external-prose |
dbt |
a dbt project's model/column descriptions + tests (accepted_values→enums, relationships→joins) |
declared |
Knowledge attaches to what it describes. Give a context resource
maps_to: ["prod-pg"] and its docs surface under search(resource="prod-pg")
alongside the introspected schema, and count toward prod-pg's
knowledge_files. A dbt project mapped to a warehouse is cross-checked
against that warehouse's live schema — any table or column the project names
that doesn't exist gets a visible ⚠ unverified marker, so a stale doc never
routes an agent to a column that's gone.
Safety holds: folder ingestion refuses symlinks that escape the root; git
uses pure-Go go-git (no git subprocess) into a scratch dir wiped after
sync and never executes repo contents; all ingested text is sanitized
(control/zero-width/bidi runes stripped) and marked with its trust tier;
sync is deterministic (no LLM) and never on the MCP wire (a prompt-injected
agent can't trigger or poison ingestion). Deleting a context resource evicts
every doc it contributed.
Try it end to end (needs a dev-postgres first, see below):
make bootstrap postgres # the warehouse the dbt project describes
make bootstrap docs # Glide engineering handbook → glide-handbook (folder)
make bootstrap dbt # Glide dbt project, mapped → glide-dbt (dbt)
Then ask your agent:
Using the access tools, what are the valid values of a trip's status, and which columns join trips to drivers? Then show the top drivers by earnings.
It finds the dbt-declared enum + join facts under search(resource="dev-postgres"),
learns from the handbook that earnings must include tips (use
v_driver_earnings, not raw fare_cents), and answers correctly — knowledge
introspection alone could never give it.
make # full build: SPA + binary with SPA embedded
make build # Go binary only → bin/access
make run # build + serve
make test # go test ./...
make vet
make web-dev # Vite dev server proxying /api + /mcp to a running serve
With a server running (make run), stand up seeded docker databases and
register them through the public API — exactly like the UI would:
The seeds model one fictional ride-hailing org (Glide) across every resource — same city codes, same driver/trip id ranges — so agents can learn a coherent company, not five disconnected demos:
make bootstrap postgres # Glide rides OLTP: riders, drivers, trips, payments → dev-postgres
make bootstrap mysql # Glide Eats OLTP: restaurants, couriers, orders → dev-mysql
make bootstrap redis # live state: driver geo, surge, dispatch queue → dev-redis
make bootstrap clickhouse # telemetry: trip_events stream + daily rollups → dev-clickhouse
make bootstrap kubernetes # the services on a kind cluster → dev-k8s
make bootstrap docs # Glide engineering handbook (folder) → glide-handbook
make bootstrap dbt # Glide dbt project, mapped to dev-postgres → glide-dbt
The database/cluster kinds are created read-write and context-synced
immediately, so search works on the first query; the docs/dbt kinds
are learn-only context resources (run make bootstrap postgres before dbt
so its facts have a warehouse to attach to and be cross-checked against).
Every bootstrap also seeds the curated layer — notes with Glide's tribal
knowledge (fares exclude tips; soft-deleted drivers; telemetry vs truth) and
a cross-resource incident runbook — which is the part schema introspection
can never learn. The postgres container preloads pg_stat_statements so sync
also captures how the team actually queries the DB; the kind cluster (needs
the kind CLI) includes a deliberately crash-looping surge-pricing
deployment whose failure mode is exactly what the seeded runbook walks
through; and the docs handbook ships two deliberate traps (a doc naming a
table that no longer exists, and a prompt-injection line) so you can see the
stale-doc and DATA-not-instructions handling for yourself. Tear everything
down when you're done:
make bootstrap down # remove containers/clusters + resources + secrets
make bootstrap down postgres # just one kind
Ports/dirs are overridable via ACCESS_PORT, ACCESS_DATA_DIR, PG_PORT,
MYSQL_PORT, REDIS_PORT, CH_PORT (see scripts/bootstrap.sh).
See PLAN.md for the full design.