Your agents get
the answer.
Your data stays put.

Marai is an MCP gateway between your AI agents and your databases and APIs. Every tool call is governed: which tools a team may call, which columns it may read, and which values ever leave as themselves. Your users get the real values back.

Scroll

01 — The gateway

Your tools. Your data. Your teams.

Every tenant registers its own data sources and publishes its own MCP tools over them. An agent connects as a person on a team, and the catalogue it sees is that team’s. Not a shared directory with filtering bolted on — a tenant’s tools do not exist for anyone else.

What the tenant admin declares

A data sourcepostgres · warehouse
A tool over itfind_customers(city) SELECT name, email FROM customers WHERE city = :city
Who may use itSupport · name, email Analytics · city only

What the agent gets

tools/listfind_customers — offered to Support (nothing else in this tenant)
tools/call{ "name": "Person 4471", "email": "u4471@masked.marai" }
The same call, from AnalyticsNo matching result was found.

The column rules are read out of the tool’s own query, not typed a second time, so what a team is granted and what the tool actually reads cannot drift apart.

02 — Governance

One record. Different answers for different teams.

Nothing about the source or the tool changes below — only who is asking. Switch the team and watch the same customer record resolve differently.

namePriya Sharmareal value
emailwjzyx.tvjvfs@uhmp.rqxprotected
phone+9 229 105 4889protected
ssn— not exposed —withheld
cityChennaireal value

Support agent. Support can reach the customer without ever holding their SSN — the field is not exposed to this team at all, so it is never fetched, never returned, and not recoverable.

03 — Protection

Type anything. Watch it leave protected.

Not a mock-up — the real playground, embedded. Type your own text or browse the examples, and watch what the model would receive.

This is the actual playground, running inline — open it full screen.

04 — In use

Same protection, from both directions.

A person in a chat window never sees a masked value. An agent over MCP never receives a real one. Neither did anything to opt in.

A chat session · app.getmarai.com

Your user asksWhat is Priya Sharma’s account status? Confirm on priya.sharma@acme.com
What the model is givenWhat is Rvpxli Tqmzhb’s account status? Confirm on kdvhqm.wbtnlf@zcgrp.qxj
What the model answersRvpxli Tqmzhb’s account is active. Confirmation sent to kdvhqm.wbtnlf@zcgrp.qxj.
What your user readsPriya Sharma’s account is active. Confirmation sent to priya.sharma@acme.com.

An agent over MCP · mcp.getmarai.com

Agent callstools/call find_customer_by_email { "email": "priya.sharma@acme.com" }
Agent receives{ "name": "Rvpxli Tqmzhb", "email": "kdvhqm.wbtnlf@zcgrp.qxj", "phone": "+1 288 301 7734", "city": "Chennai" }
Same call, analytics teamtools/list → find_customer_by_email is not listed (the tool does not exist for this team)

City is real, and that is the point. Protection is per category, not blanket masking — the agent can still answer “which customers are in Chennai” perfectly well, having never been given a value it did not need.

05 — Configuration

Building a tool is deciding what stays hidden.

An admin chains sources into one semantic tool and classifies every published field. Done once — every request after that is governed without anyone opting in.

01
customers.find_by_email(:email)admin picks the source and the operation
··
{ "customer_id": "cus_8823" }used, then discarded — never leaves marai
02
orders.list_for_customer(cus_8823)parameter supplied by step 01, never by the model
find_orders_for(email) → { "orders": [ … ] }the only thing an agent can call, and all it receives

The internal customer id is real, necessary, and never exposed. It is not in the request, not in the response, and not something a prompt can ask for — because the mapping between steps was fixed by an admin, not decided at call time.

Composition

One tool, many sources, one governed answer.

A database tool is a tool. It composes with your REST APIs, your GraphQL services and your other databases into a single call an agent makes once — with the mapping between steps fixed by an admin, not decided by the model at call time.

Step 1 · SQL

customers_by_city

SELECT id, email FROM customers WHERE city = :city

Step 2 · REST

billing_status

GET /v1/accounts/{id}/status

Step 3 · SQL

open_orders

SELECT total FROM orders WHERE customer_id = :id

Every step carries its own grants. A team that cannot read customers.emaildoes not get it from step one, and cannot recover it from step three. Composition widens what a single call can reach — never what a caller is allowed to see.

SQLite — liveTable + column grants — liveRead / filter separation — liveBigQuery — registers today, queries nextComposition across sources — next

See it on your own data

The demo is live.

Sign in, connect a source, publish one tool, and watch the same record change shape as you switch teams — in the console, not a slide.

Deliberately not built, and why.
  • Prompt-injection output scanning — the policy wall already blocks the data-leakage case three ways over. What remains is an integrity risk confined to write-capable tools, and every tool today is read-only.
  • Row-level restriction — column-level first; the nearest existing mechanism was removed after review found it enforced nothing.
  • Document databases — the shared abstraction is the operation, never SQL-to-document query translation.
  • SSO / SAML / SCIM, social sign-in — real procurement gaps, not yet built.