Your agent doesn't need your database. It needs an answer.
AccessFlow gives an AI agent a governed shape of database access: it can ask, and the
query goes through the same parse, AI risk analysis, human approval, and audit trail as one
a person submitted. Point an agent at a database over MCP without that and it inherits
whatever the connection string can do — every table, every row, every DELETE.
What a direct database connection actually hands over
The usual way to give an agent data access is a database MCP server holding a connection string. That server is not a permission boundary — it is a pipe. Whatever the credential can do, the agent can do, and three properties follow from that:
- The blast radius is the credential, not the task. An agent asked to summarise last week's signups holds the same access as one asked to drop a table. Scope lives in the prompt, which is not a security control.
- Prompt injection becomes database access. Untrusted text in a support ticket or a web page the agent reads can redirect what it decides to run. The pipe faithfully executes the redirected intent.
- The audit trail loses the human. The database sees one service account. Which person's agent ran what, and why, is not recoverable from the logs afterwards.
The agent joins the queue, like everyone else
AccessFlow exposes a Model Context Protocol server. An agent connects with an API key and sees twelve tools; a service account's key can be limited to the subset an admin allow-lists. Submitting a query does not execute it — it enters the same state machine a query from the web editor enters.
Discover, then draft
The agent calls get_datasource_schema to see what exists, and validate_sql to parse-check a draft. Validation never executes and never calls the AI — the agent can iterate for free.
Submit for review
submit_query returns PENDING_AI. Nothing has touched the database yet. Permission and parse failures come back as structured errors, not executions.
AI risk analysis
The query is scored 0–100, checked for missing indexes and anti-patterns, and given a pre-flight cost estimate — the same analysis a human submission gets.
Human approval
The datasource's review plan decides who signs off. Reviewers see the agent's SQL, its risk score, and its justification, and approve or reject from the web UI, Slack, or a mobile push.
Execute, under the same guards
On approval the proxy runs it with the schema allow-list, column masking, row-level security, and row caps applied — then get_query_result returns the rows to the agent.
What holds, specifically
Governance claims are worth only as much as their enforcement point. All but the last are enforced in the service layer, not in the tool wrapper; the tool allow-list is the one check that runs in the wrapper itself, ahead of the service:
Keys carry their owner's access, nothing more
An API key carries exactly the role and datasource permissions of the identity it belongs to — a person for a personal key, or the service account an admin set up for the agent. A reviewer's key can review; an analyst's key cannot. An agent never gets more access than the identity its key belongs to.
Masking survives sampling
get_column_samples runs the governed read path — row-level security predicates and column masks apply, so a masked column returns the masked value to the agent, never the raw one.
Audit is caller-scoped
get_audit_log forces the actor to the calling user and their organisation. An agent cannot read another user's activity — not even with an admin key.
No self-approval
review_query blocks approving your own submission at the service layer, and list_pending_reviews excludes the caller's own queries. An agent cannot approve what it submitted.
Every action is attributed
MCP submissions and decisions write the same audit rows as the web UI, recording the user id, the API key used and the action — the tool layer does not capture the caller's IP or user-agent. There is no separate agent audit path.
Keys are hashed and revocable
The plaintext key is shown exactly once; only a SHA-256 hash is stored, so neither admins nor the system can recover it. Revocation takes effect on the next request.
Scoped to the tools it needs
An admin can restrict a service account to an explicit tool allow-list — a checkbox list on the account's MCP tools tab under Service accounts. Every tool is still listed to every caller, but a call outside the list returns a structured permission_denied before the service runs — the list can only narrow what the account's role already permits, never widen it.
Bounded by a per-identity rate limit
An agent in a runaway loop is capped, not just logged. Every call an API key makes counts against the identity behind it — 120 requests a minute by default, with an optional daily ceiling, and a per-account override an admin can set on the account's Limits tab. Over the cap the server answers HTTP 429 with a Retry-After header so a well-behaved client backs off; a person signed in through the browser is never limited.
Acting for a named person
An agent can say whom it is working for by adding an X-AccessFlow-On-Behalf-Of header naming that person, once they have consented (through their own delegation endpoint, or an admin on the account's On-behalf-of principals tab). Naming someone changes nothing about what the agent may do — its permissions stay its own. The person is recorded on the submission and on every audit entry written while that call is handled, inside the tamper-evident chain, and can no longer approve that submission themselves, or sign off the review of an emergency run made for them. A name the agent may not use, or the header on a review action, gets HTTP 403 from the same filter that rate-limits, before anything runs — never a quiet ignore.
Connecting an agent
Give the agent a key, then point the client at your AccessFlow instance. For an agent that runs on its own — a chat bot, a scheduled assistant, a shared team agent — ask an admin for a service account with a narrow role and only the tools it needs, and use a key issued on its API keys tab. For trying an agent on your own machine, a personal key from Profile → API keys works too, with exactly your own permissions. The server speaks stateless Streamable HTTP at /mcp.
{
"mcpServers": {
"accessflow": {
"type": "http",
"url": "https://accessflow.example.com/mcp",
"headers": {
"X-API-Key": "af_kQ7…"
}
}
}
}
claude mcp add accessflow --transport http \
--url https://accessflow.example.com/mcp \
--header "X-API-Key: af_kQ7…"
Any MCP-compatible client works the same way — Claude Desktop, Claude Code, or a custom pipeline. Full tool reference and error codes are in the MCP documentation →
What this does not solve
A governance layer that claims to solve everything is not one worth trusting. The boundaries are worth stating plainly:
- It does not stop an agent submitting a bad query. A compromised or confused agent can still submit anything its key permits. What it cannot do is get that query executed without passing the review plan. The control is on execution, not on intent.
- Auto-approval is a policy you choose. Routing policies can auto-approve low-risk reads, and a review plan can be configured not to require human approval. Those are deliberate settings — if you grant an agent a path that skips review, it skips review. The default is that it does not.
- It governs AccessFlow, not your agent. This is a control on one path — the database. An agent with other tools, other credentials, or a shell is outside its scope entirely.
Run it yourself
AccessFlow is Apache 2.0 and self-hosted — it runs inside your own infrastructure, and no query text or row data leaves it. One docker compose up gets a working instance; the install guide covers Helm and from-source too.