Governed MCP for the software you already run · EU-hosted · Every call logged
Your people are already putting company data into AI. You can’t see it, can’t filter it, and can’t prove it didn’t happen. A ban doesn’t stop that — it just moves it somewhere you can’t look. Sallyport puts one gate in front of the systems you already run: every call checked against that person’s rights, filtered against your DPO’s classification, and written to a log nobody can quietly edit — including us.
The exposure you already carry
Somewhere in your company this week, customer data will be pasted into a free chatbot — unlogged, unfiltered, outside the EU. Not out of malice; because it works. A policy doesn’t stop that, and a ban only moves it somewhere you can’t look. Sallyport doesn’t fight the habit. It gives it a home you can see into.
The safe path has to be the easier path. That’s the whole trick.
The gate, live
The demo worth watching is the one that says no. Access follows the person, personal data follows the DPO’s classification, and the log follows every call — including the refused ones. Compliance you can demonstrate, not declare. Pick who’s asking.
Both men hold the same role. David asks for a field the DPO classified as personal data and is refused; Thomas asks for business data his rights cover and gets it. Both calls are logged identically — the refusal is the product, and the record of it is what you will actually be asked for.
What a filtered answer looks like
On the left, the record as it sits in Gripp. On the right, what Sales actually receives through the gate — same request, same moment.
name: Nova BV
email: finance@nova.nl
iban: NL91 ABNA 0417 6789
cost_price: 84.00
status: inactive
name: Nova BV
email: finance@nova.nl
iban: •••• •••• 6789 masked
cost_price: (hidden) hidden
status: active inactive rows withheld
Masked, hidden and withheld are three different things, and the difference matters to a DPO: a masked field proves it exists, a hidden one doesn’t, and a withheld row was never in the answer at all.
Before the rules, the roles
Everyone carries two independent sets of rights, and conflating them is how least-privilege quietly dies.
Connector roles
Sales, Support, Finance. Scoped per connector, and the thing every rule on this page is written against.
Sallyport roles
Admin, Auditor, DPO. Configuration, read-only audit access, or signing off on requests for personal data.
Someone can have either, both, or neither — and an Auditor with no connector role can read the log without ever reading a customer record.
Governed MCP
Three checks stand between an assistant and your data, and none of them is a promise in a policy document. Everything a role can reach is set on one screen, where you can see the whole of it at once.
Identity comes from your own IdP over SSO, with SCIM keeping the roster in step. Rights are resolved fresh on every single request — never carried around inside a token that outlives the decision. Revoke someone in your IdP at 14:00 and the call they make at 14:01 is already refused.
A declared allowlist: per role, per table, per field, per operation — plus the row filter that decides which rows, which read and write state separately. Not code somewhere that a well-meaning change could widen. A field is reachable only because somebody chose to grant it. Absence is denial.
Every call — allowed, filtered or refused — is appended to a log that nobody can quietly edit, us included. It ships out as NDJSON to Splunk, Datadog, Sentinel, Elastic or S3, so the record lives in your estate and not only in ours.
| Entity | Access | Rules |
|---|---|---|
| Contacts | No accessViewEditManage | date of birth masked |
| Companies | No accessViewEditManage | own accounts only |
| Notes | No accessViewEditManage | + rule |
| Entity | Access | Rules |
|---|---|---|
| Invoices | No accessViewEditManage | owner = the person asking |
| Payments | No accessViewEditManage | — |
Every entity, every role, one screen — and a newly connected entity arrives at No access until somebody chooses otherwise. When you are asked who can reach personal data in which system, this page is the answer — not a spreadsheet somebody maintained until they left.
scroll the tables →
How deep it goes
Handing a role an entity is one decision out of five. The three below it are the ones that decide whether an assistant comes back with one customer record or forty thousand — and they are the three a plain MCP server does not have.
Which system this role can reach at all. Sales gets Gripp; Exact is not on the list, so there is nothing to misconfigure.
Which tables inside it, at which level — No access, View, Edit, Manage. A newly connected entity arrives at No access and stays there until somebody chooses otherwise.
Read and write are separate grants with separate scopes. A role can read every open invoice in the company and be able to change none of them — including its own row filter, which is allowed to differ between the two.
Which records come back at all. Conditions are evaluated per request, so they can be written relative to whoever is asking — and they can reach across a relation, into a field on the linked record.
Which columns survive: returned, masked, or dropped before the response is assembled. Per role, on top of the classification your DPO gave the field.
The three marked in green are the ones a plain MCP server has no way to express — it has no notion of who is asking, so it cannot scope a row or drop a column on their behalf.
Return rows where all conditions match. The rest are withheld on the way out — and pushed down into the vendor’s own query wherever the vendor can express them, so they are never fetched in the first place.
Conditions the vendor’s own API can express are compiled into the call it receives. The rest are applied here, against the response, before it is assembled.
pushed into the call to GrippGET /contacts?status=active&owner=8412®ion[in]=EU
applied here, on the way out
company.credit_hold = false
Every field arrives carrying the classification your DPO gave it and the default that follows from it. A role can be narrowed below that default freely; widening past it takes a signed exception with an expiry date.
The difference
MCP is the standard that lets an assistant call your systems. Most MCP servers implement the calling half and stop there. That half is the easy half.
MCP is a public protocol, not ours — any assistant speaks it, and you can point yours back at the raw API whenever you like. What we add is the half that decides.
Evidence
This is the request that arrives from an auditor, a customer’s security team, or your own board after something goes wrong. Today it takes a week and ends in a best guess. Filter the log.
| Time | Who | Role | Call | Fields | Verdict |
|---|---|---|---|---|---|
| 09:02:11 | melanie | Admin | moneybird.invoices.list | total, due_at | allowed |
| 10:42:07 | thomas | Sales | moneybird.invoices.list | total, state owner=thomas | allowed |
| 10:44:31 | david | Sales | crm.contacts.list | date_of_birth | refused |
| 11:15:58 | hannah | DPO | crm.contacts.get | date_of_birth, email | allowed |
| 13:07:20 | anna | Finance | exact.journals.list | 7 fields | allowed |
| 15:31:02 | david | Sales | crm.contacts.export | bulk export | refused |
| 16:48:44 | thomas | Sales | moneybird.mail.bulk | 142 recipients | held |
scroll the table →
Robert de Wit is an Auditor: read-only, across every connector — the reason that role exists. Note that the two refusals are in the log at all. A gate that only records its successes is a gate that cannot answer the question you bought it for.
Classification
Hannah is the DPO. She marks ‘date of birth’ as personal data once, in one place, and the gate withholds it from every connector, on every call, from that moment on — without anyone touching Moneybird, Gripp, Exact or the CRM.
Classification lives at the gate, not in each system’s own settings. That means it applies to the connector you add next month too, before anyone has thought to configure it.
Someone genuinely needs a classified field for a fortnight. Grant it time-bound; it lapses without anyone having to remember. Standing exceptions are how least-privilege quietly dies.
Every request the classification blocked shows up in the DPO’s own view — which turns “is our policy working?” from an opinion into a count.
| Field | Classification | Netherlands (AVG) | Germany (BDSG) | France (loi I&L) |
|---|---|---|---|---|
| employee.socialsecuritynumber | Special | blocked BSN · Steuer-ID · NIR — never reaches AI | ||
| employee.dateofbirth | Personal | filtered | masked | masked |
| contact.email | Personal | masked | masked | masked |
| employmentcontract.internal_price_per_hour | Financial | Finance only | ||
| company.companyname | Public | allowed | ||
Sallyport reads a tool’s shape before AI touches it and flags every field that falls under privacy law — Dutch AVG, German BDSG, French loi Informatique et Libertés. New fields arrive unclassified and nothing reaches AI until the DPO signs off.
The half hour she spends on this is the half hour the rest of the year rests on.
Exceptions
A “no” that can never become a “yes” is a rule people route around. An exception is a temporary override that supersedes the standing rules — it can grant or revoke, on one entity or one field, for a window you set. Then it reverts on its own.
| Who | Change | Scope | Until | State |
|---|---|---|---|---|
| David · Sales | grant read-only | gripp · contacts.birthdate | 21 Jul · 7 days | pending DPO |
| Anna · Finance | grant View | exact · invoices | 30 Jul | active |
| Contractors | revoke all access | gripp · contacts | 31 Jul | active |
| Support | unmask iban | moneybird · contacts | expired | reverted |
Nobody has to remember to take it away. Standing exceptions are how least-privilege quietly dies — so here they can’t stand.
Residency & custody
These are the questions that come before every pilot, in the order they always get asked. Here they are without the discovery call.
In the EU. Hosted in Germany. Nothing leaves it, and we never train on any of it.
Metadata and which rows a rule held back — not full record payloads. You set how long the audit log and the result cache live, and you can purge on demand without opening a ticket.
Yes, and a transparent one. You stay the controller; we’re the processor. There’s a signed DPA with EU SCCs and a published list of everyone we rely on.
That’s the whole product. Every request is recorded — who asked, what they got, which rule held what back — in an append-only log nobody can quietly edit, including us.
The objection
“You’re one more place our client data can leak.”
Fair. So the vendor keys you give us are sealed with a key you also hold — drop ours and you revoke our access for good. Each secret gets its own one-time data key, and that key is itself encrypted. Plaintext exists only in memory, for the single request that needs it, inside a hardware boundary. No operator, no database dump and no log ever sees it. We are a sub-processor that cannot read your secrets.
Additive, not a project
The usual answer to “we need AI on our data” is a platform you migrate into — a year of work, a new data store to secure, and a copy of everything you own living somewhere it didn’t before. This is the other answer.
Systems replaced. Your accounting, projects and CRM stay exactly where they are, with the vendors you already assessed and the contracts you already signed.
Copies of your data created. Nothing is exported, converted or re-entered. There is no export to lose, no warehouse to breach, and nothing to delete if you leave.
To the first governed call. Connect a system, review the roles it proposes, classify what’s personal, and watch the log start filling. Not a quarter — an afternoon.
Disconnect and nothing moved, so there is nothing to move back. That is a small decision, and small decisions are the only kind worth making about something this new.
And then it does the work
Governance that only ever says no gets switched off. The reason this one survives contact with the business is that the same gate is what lets the business have the thing it actually wants.
An automation can never reach further than the person who set it running. There is no service account quietly holding more than anyone should.
Sign-off is part of the definition, not a convention people follow when they remember. No sign-off configured, no pause; one configured, it waits.
Automated work is audited exactly like a person’s. When something runs wrong at 03:00 you can still say what happened and who approved it.
More cases
Same company, same stack, seven different answers — because seven different people asked.
Export the whole customer database to a spreadsheet.
Refused · bulk exportpolicy: data minimisation · logged & visible to Hannah (DPO)
Reclassify ‘date of birth’ as personal data on every connector.
Applied · gate-wideone classification → enforced on every future call, all tools
Show every AI call that touched personal data this month.
Allowed · audit scopeaudit.log.query · read-only role · the reason the Auditor role exists
Email all my clients the new-year discount offer.
Routed · needs sign-offbulk send → approval from Melanie (manager) · drafts held at the gate
What did De Groot Media order from us last quarter?
Allowed · read-onlygripp.projects.list · her accounts only · no financial fields
Which payments from last week are still unmatched in Exact?
Allowed · finance scopeexact.journals.list · 7 rows · full ledger access, her role’s job
Onboard Lisa de Boer, starting next Monday.
Running · 4 of 7gripp + moneybird + mail · sign-off by Julia · rest runs Mon 08:00
Bureau Noord · IT
“I spent a year trying to ban it, and lost. Now every call our people make is one I can show the auditor.”
Free trial · EU-hosted · Every call logged
Connect one system this afternoon and watch the log start filling. If it’s not for you, disconnect — nothing moved, so there’s nothing to move back.