Skip to content

MCP governance

Govern which MCP tools external clients may call, for whom, and with what declared purpose — and keep an audit trail of every allow/deny decision.

Audience: Organisation administrators.

ProtocolOAuth 2.1 · DCR · PKCE
Default postureDefault-deny
/mcp authBearer only
System policies3 · locked

When to reach for this

  • Connect an external MCP client


    Cursor, Claude Desktop, or a custom app that speaks OAuth 2.1 with Dynamic Client Registration and PKCE.

  • Enforce default-deny exposure


    New clients start with no tools until you attach a Tool Policy — nothing is callable by default.

  • Evidence every call


    Record who called which tool, when, and whether access was allowed or denied.

  • Govern internal HERC too


    The same policy shape governs internal role groups, not just external OAuth clients.

When this isn't the surface you want

  • Revoking an app you personally authorised → that's the end-user Connected apps page under Settings.
  • Deciding who may sign in to Datahub at all → that's role groups, which is separate from what any client's tools can do.

How a governed tool call runs

Both external OAuth clients and internal HERC funnel through one Tool Policy gate. External clients reach it at the Bearer-only /mcp endpoint; internal HERC reaches it through a dedicated internal toolset API. Either way, the gate decides allow or deny, and every decision is written to the audit log.

flowchart LR
    EXT["External MCP client<br/>Bearer token"] --> MCPEP["/mcp<br/>Bearer-only"]
    HERC["Internal HERC<br/>JWT roles"] --> TOOLSET["Internal toolset API"]
    MCPEP --> RESOLVE["Resolve the caller's<br/>Tool Policy"]
    TOOLSET --> RESOLVE
    RESOLVE --> GATE{"Tool-policy gate"}
    GATE -->|allowed| RUN["Tool runs"]
    GATE -->|denied| REFUSE["Call refused"]
    RUN --> AUDIT[("Audit log<br/>allow / deny")]
    REFUSE --> AUDIT
    classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
    class GATE accent

Figure 1 — One gate, two entry points. External clients authenticate to /mcp with a Bearer token; internal HERC loads a filtered tool list through the internal toolset API. Both resolve the same Tool Policy, and every allow/deny is audited.

Why teams reach for this

You get one governance surface for all AI tool access — external and internal — with a single, mergeable policy shape, default-deny by construction, and an audit trail that answers "who called what, and were they allowed?" without bolt-on logging.

Concepts

Concept What it is
OAuth client An external app (Cursor, Claude Desktop, a custom integration) registered to call /mcp. Registered via Dynamic Client Registration; one organisation per client in v1.
Client name The label you read in the client list and pick in the connect dropdown. Datahub keeps it unique, so a second connection wanting a name already in use is saved as Claude (2). You can rename it whenever you like.
Registers as The name the connector itself sends on every reconnect — its actual identity, which Datahub uses to recognise it. Shown under the client name when the two differ. Renaming the label never changes it.
Tool Policy The rule set deciding which tools a caller may use: an allow list (or allow-all), block lists, and a maximum classification ceiling.
Binding Attaches a policy to a principal — an OAuth client, a JWT role-group name (internal HERC), or an Organizational Role persona (role-scoped clients).
Persona (Organizational Role) A role such as Data Steward, Data Owner, or Viewer. On a role-scoped client, each connecting user is automatically bound to the policies attached to their persona.
Role-scoped client An OAuth client with Role-scoped tools on: one shared connection where every user is scoped to their own persona, rather than seeing the client's single uniform toolset.
Access token The Bearer token /mcp accepts. Stored hashed; the plaintext is returned only once, at the token endpoint.
Purpose The declared reason a client wants access. Shown on the consent screen and recorded in every audit row.
Audit log One row per tool decision (allow / deny). Detail rows roll into monthly summaries after the retention window.

Setup — what an admin does once

Step Where Why
Grant admin roles Role groups MCP governance admin (full management) or read-only audit access.
Find the module Administration → MCP governance It lives under Administration, not as a top-level module.
Create a Tool Policy Tool policies List allowed tools (or allow-all with blocks), set a maximum classification ceiling, and add optional blocks (tools, modules, tags, prefixes).
Bind the policy Policy → bindings Attach it to each OAuth client; for internal HERC, add role-group bindings so JWT roles resolve to the same rules.
Expose OAuth discovery Your Datahub base URL External clients read /.well-known/oauth-authorization-server and use /oauth/register, /oauth/authorize, /oauth/token, /oauth/revoke, /oauth/introspect.
Configure the client In the MCP client Complete registration (DCR), run the browser authorise flow, and confirm consent. Until a non-empty policy is bound, tool calls are denied.

Turning MCP off entirely

AI agent connections start switched off. Datahub does not expose an MCP surface until an administrator deliberately allows it, at Administration → Defaults → AI agent data access → Allow AI agents to connect (MCP). If your organisation does not use AI agents, there is nothing to do — the door is already shut.

With it off — which is how every Datahub starts — no external AI connection is possible at all: no app can be authorised, and apps that were already connected stop working. This is stronger than tool policies: a policy decides which tools a connected agent may call, this decides whether an agent can connect at all.

What it does not do

Switching MCP off leaves your tool policies and existing grants untouched. Turning it back on restores exactly the setup you had reviewed, rather than a blank one — so it is safe to use as a temporary stop.

Revoking apps also keeps working while MCP is off, so you can still tidy up connections that were authorised earlier.

Turning it back on is recorded in the audit trail, with who did it and when.

Connecting a client

An external client onboards through the OAuth 2.1 authorization-code flow. The consent screen is the human sign-off: it names the declared purpose, the bound policy, and the scoped tools before the user approves.

flowchart LR
    REG["Register<br/>(Dynamic Client<br/>Registration)"] --> AUTH["Authorize<br/>(browser)"]
    AUTH --> CONSENT["Consent screen<br/>purpose · policy · tools"]
    CONSENT --> CODE["Authorization code<br/>+ PKCE"]
    CODE --> TOKEN["Token endpoint<br/>issues Bearer token"]
    TOKEN --> CALL["Client calls /mcp"]
    classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
    class CONSENT accent

Figure 2 — The OAuth 2.1 onboarding path. Consent shows the declared purpose, the bound policy, and the scoped tools, and lets the user choose allow-once or allow-always. Even after consent, tool calls stay denied until a non-empty policy is bound to the client.

Registers via DCR, runs the browser authorise + consent flow, and calls /mcp with a Bearer access token. Its rules come from the policy bound to that client.

Does not use cookie auth to /mcp. It loads a filtered tool list through a dedicated internal toolset API, using the same policy rules — resolved from the role-group bindings on the policy. Enforcement merges the bindings that match the user's roles.

Where secrets live

Access tokens are stored hashed — only the OAuth token endpoint returns a plaintext token, once, at issuance. The admin UI and customer APIs never expose tokens or secrets beyond presence flags (e.g. whether a client has an active token).

Copy the server URL from Datahub, not from the address bar

Open Administration → MCP governance and copy the MCP server URL shown on the page (the Setup guide panel shows the same value).

Datahub has one configured public address, and the whole OAuth flow is built from it: the sign-in service the AI client discovers, the identity it checks, and the page the user consents on. The URL on screen is that address. It may not match what is in your browser's address bar — if your organisation reaches Datahub through its own domain, for example — and that is expected. Copy what Datahub shows; typing the address bar host instead is the one thing that will not work.

Client names and duplicate rows

Connectors choose their own name, and the hosted ones all choose the same one — every Claude.ai connection registers as plain Claude. Two things follow, and both are handled for you.

Names are made unique. The first connection keeps Claude; the next is saved as Claude (2), then Claude (3). You will see this when you create a client from a preset whose name is taken — the panel tells you the name it actually saved. Rename any of them to something meaningful (Claude — Finance) whenever you like: the label is yours, and the connector's own identity is unaffected. Where the two differ, the client page shows Registers as "…" under the name.

Duplicate rows clean themselves up. Claude.ai and ChatGPT connect as confidential clients, which for security reasons cannot be folded onto an existing registration — their secret is stored hashed and can never be handed out again. So every reconnect creates a row, and estates that ran for a while collected a dozen identical Claude entries. Now a reconnect archives the rows it replaces and the list settles back to one per connector. An archived row says "Replaced by a newer registration" and links to the one that replaced it, so the trail stays readable.

A row is only ever archived this way when it is genuinely spare. Datahub leaves it alone if:

  • it holds a live session — two workspaces of the same product each keep their own connection;
  • it is a connection still being set up — someone part-way through the consent screen;
  • you created it — a client you added yourself is never archived by a reconnect, even before its connector first connects;
  • you already archived it — your reason is not overwritten.

The client list hides archived rows by default. Use the Client status filter above the table to see them.

Tool policies

A Tool Policy is the same shape whether it governs an external OAuth client or an internal role group. When several bindings match a caller, enforcement merges their allowed tools.

Field What it does
Allowed tools The explicit allow list. Empty unless you add tools.
Allow-all Admit every catalogued tool (still subject to blocks and the ceiling).
Blocks Exclude by tool, module, tag, or tool-name prefix.
Maximum classification The ceiling a tool's classification may not exceed.
Bindings The principals the policy applies to — OAuth clients and role-group names.
flowchart LR
    REQ["Requested tool<br/>+ catalog metadata<br/>classification · module · tags"] --> CEIL{"Under classification<br/>ceiling?"}
    CEIL -->|no| DENY["Deny"]
    CEIL -->|yes| BLOCK{"Blocked by tool /<br/>module / tag / prefix?"}
    BLOCK -->|yes| DENY
    BLOCK -->|no| LIST{"On allow list<br/>or allow-all?"}
    LIST -->|no| DENY
    LIST -->|yes| ALLOW["Allow"]
    classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
    class ALLOW accent

Figure 3 — How a Tool Policy decides. A tool must clear the classification ceiling, survive every block list (tool, module, tag, prefix), and be on the allow list — or the policy must allow-all — before it is admitted.

Classification ceiling

Tool classifications rank public → internal → sensitive → restricted; a policy admits only tools at or below its ceiling (default internal).

Three system policies ship and cannot be archived or permanently deleted:

Policy Posture
mcp.empty Denies everything. New OAuth clients start here — this is the default-deny baseline.
mcp.full_internal Broad internal access.
mcp.read_only Read-style access — the catalogue minus its blocked prefixes.

Letting agents draft alerts

Drafting an alert is a write, so it needs a grant. The mcp.alert_author policy carries it and is bound by default to the Data Steward and Data Owner role groups, not to Viewer. It admits the tools that create, edit, test, preview, submit and withdraw an alert draft. There is no tool to activate an alert or send a notification. The read policies list the three tools that change nothing: the channel list and the query and message previews. A person's app role still decides whether those three run, because each needs the alert-authoring role (logicengine.rules.write). The read policies do not include the tools that create, edit, test, submit or withdraw a draft. See Logic Engine.

Role-scoped access

Normally a client exposes one toolset to everyone who connects through it. Role-scoped access turns that into a single shared connection that scopes every user to their own Organizational Role — a Data Steward gets stewarding tools, a Viewer gets a read-only view with a narrow write grant — without registering a separate client per person.

Turn it on with the Role-scoped tools switch on the client. It is off by default, and every existing client is unaffected until you flip it.

flowchart LR
    USER["Connecting user<br/>+ their personas"] --> PA["Persona policies<br/>(union of the user's<br/>Organizational Roles)"]
    CLIENT["Role-scoped client<br/>+ its Tool Policy"] --> CA["Client toolset<br/>(the admin's ceiling)"]
    PA --> INT{"Intersection<br/>least privilege"}
    CA --> INT
    INT --> EFF["What THIS user<br/>can actually call"]
    classDef accent fill:#ff8400,stroke:#e67700,color:#ffffff,font-weight:600;
    class INT accent

Figure 4 — On a role-scoped client the effective toolset is the overlap of the client's policy and the connecting user's persona policies. The tighter of the two always wins, so a persona can only ever narrow the client — never widen it.

Two limits apply, and the tighter one always wins:

Axis What it narrows Rule
Visible tools Which tools the user sees and may call. client policy ∩ the union of the user's persona policies.
Authority What each call may do, re-checked at every endpoint by your roles. the user's roles ∩ the client ceiling ∩ the persona ceiling.

Because access is an intersection, a role-scoped connection is always a subset of the user's normal rights — appropriate for a higher-risk channel where an external AI agent acts on someone's behalf.

Fail-closed by design

If a connecting user's persona has no MCP policy bound, they get zero tools — never the client's broad default. Bind a policy to each persona you expect to connect. The four base personas ship with sensible defaults; the expanded personas start unbound (deny-all) until you bind them.

Binding a persona to a policy. On a Tool policy, open the persona bindings and add an Organizational Role. Each bound persona shows its effective tool count — the tools that persona would resolve to across all its bindings — so you can preview the impact before saving.

Default persona policies (seeded):

Persona Resolves to In practice
Administrator mcp.full_internal Every internal tool.
Data Steward / Data Owner read baseline + catalog/glossary/contract writes Full stewarding: read everything, write assets, glossary terms, and contracts.
Viewer read baseline + complete_task / dismiss_task Mostly read, plus editing tasks.

Read means read

A read persona exposes no mutating tool — not even verbs like trigger_flow or start_workflow that don't start with create/update. The read/write split is decided by each tool's declared capability, not by its name.

Every tool asks the person's role

Each tool asks for the same app role as the screen it stands in for, on every call. The Microsoft Fabric warehouse tools, the Databricks Genie tools and the Databricks alert list ask for it too: Genie needs the Genie role, the Fabric query needs a role that runs warehouse queries (a data reader reaches only the integrations granted to their role group), the Fabric list needs the integrations inventory role, and the alert list needs the role that shows alerts. The read policies therefore list these tools, and a read connection offers them to every person who holds the matching role. Your data exposure settings still apply to every Genie and Fabric answer. A Fabric query must be one read-only statement, and it runs through a shared service principal, not the person's own credentials, so keep that principal's warehouse permissions narrow. To keep one of these tools off a policy altogether, add it to the policy's blocked tools.

What users see

Audience Where What they see
Admins Administration → MCP governance Overview KPIs, Tool policies, OAuth clients, and Audit.
End users Settings → Connected apps The apps they authorised, with revoke. They can also start a new AI-agent connection from the user menu anywhere in Datahub (see Connecting a new AI agent). The OAuth consent screen shows purpose, the bound policy, the scoped tools (summarised), and allow-once vs allow-always.

A connected assistant can also explain its own limits rather than guessing at them. Because a withheld tool is simply absent from its list, an assistant otherwise cannot tell "your role was not granted this" from "Datahub cannot do this" — and will state the second. The get_my_tool_access tool is available on every connection, including one whose policy grants nothing, and reports what is withheld, why, and what an administrator would have to grant.

Audit & compliance

Every tool decision records:

Field Notes
User Who made the call.
OAuth client The client, if any (internal HERC calls have none).
Tool + module Which tool was requested.
Decision allow or deny, with a denial reason on denies.
Purpose The declared purpose, carried from consent.
Timing When the call happened, and how long the decision took.
Question a CISO might ask Where to look
"Who called which tool, and was it allowed?" Audit tab → filter by client / user / date.
"How long do we keep this?" Detail rows are time-bounded; monthly aggregates remain for longer-term reporting.
"Why did this client want access?" Purpose is declared on the client, can be re-asserted on authorize, and appears on consent and in audit.
"Are any tokens or secrets exposed?" No — the UI and customer APIs show only presence flags, never token or secret material.

Limitations

Limitation Note
One organisation per OAuth client (v1). No multi-tenant client.
No federated SSO for OAuth client identities (v1). —
No per-client rate limits. Only platform-wide API limits apply.
No built-in webhook / SIEM fan-out of audit events. Use export and polling where available.
Default policies are locked. mcp.empty, mcp.full_internal, mcp.read_only cannot be archived or permanently deleted.

Troubleshooting

Symptom Likely cause What to try
Every client is refused, including ones that worked yesterday MCP is switched off for the organisation — note this is the default, and an upgrade may have reset it Turn on Administration → Defaults → Allow AI agents to connect (MCP). While off, connecting and authorising are refused regardless of tool policies; your tool policies and existing app authorisations are kept and apply again as soon as you switch it back on.
Client registers but all tool calls denied Client still on the default-empty policy Bind a policy that allows the needed tools (or allow-all with safe blocks).
Consent shows no tools Policy allows nothing or blocks everything Edit the policy's allow list and classification ceiling.
Authorize redirect error redirect_uri not registered Add the exact HTTPS callback URI to the client; avoid javascript: / data:.
Client cannot connect at all — fails at sign-in, or the consent page 404s The URL was typed from the address bar rather than copied from Datahub Copy the MCP server URL shown on the MCP governance page — see Copy the server URL from Datahub.
HERC tools missing after policy change Role-group binding or classification Check policy bindings for herc.read (and related groups); check max_classification.
An assistant reports that a tool does not exist, or that Datahub cannot do something The tool is withheld by policy, not missing. A withheld tool is absent from the client's tool list rather than refused, so the assistant has nothing to tell it apart from a capability that was never built Ask the assistant to call get_my_tool_access: it answers whether the tool exists, whether it is available to that user, and which gate withheld it. Then bind a policy granting it to the user's Organizational Role. App permissions alone do not put a tool on a connection.
Audit empty for external calls No traffic yet, or filters too narrow Generate a test call; clear the date / client filters on the audit tab.

See also

  • HERC


    The internal AI assistant governed by the same Tool Policies.

  • Connected apps


    The end-user view: apps you authorised, and how to revoke them.

  • AI platform


    Where the AI capabilities MCP exposes are configured.

  • Administration


    The admin surfaces MCP governance sits alongside.

  • Connection governance


    Governing the data connections behind the tools.