Deploy the MCP server¶
This guide describes how to extend a DataHub deployment with the DataHub MCP server, which exposes the catalog to LLM agents over the Model Context Protocol. Agents can then search datasets, walk lineage, read schemas and glossary terms, and inspect the queries that run against a dataset, grounded in the catalog rather than in the model’s training data. See MCP server for the full tool set.
The MCP server is an optional companion to DataHub, deployed as its own application and related to datahub-k8s. Nothing else in the deployment changes when it is added or removed.
Prerequisites¶
A running DataHub deployment (see the tutorial)
Juju 3.4 or later
To authenticate callers: an ingress provider and an identity provider, as for the DataHub frontend. See Expose DataHub with ingress and Enable single sign-on.
Deploy and relate to DataHub¶
juju deploy datahub-mcp-k8s --channel latest/edge
juju integrate datahub-mcp-k8s:datahub-client datahub-k8s:datahub-client
The MCP server stays blocked until DataHub publishes the access token, then goes active.
On this relation, the DataHub charm creates a service account dedicated to the relation, mints a non-expiring access token for it, and passes the token in a Juju secret together with the GMS URL. The token never appears in relation data or in the charm’s configuration, and removing the relation deletes the service account, which invalidates the token. See Integrations for the naming convention of the charm-managed resources.
The service account holds no privileges of its own; it inherits the DataHub default all-users policies, which grant metadata read. If your deployment has narrowed those policies, grant the service account a read-only metadata policy in DataHub, otherwise the tools return no results.
Expose the server¶
Important
Without an oauth relation the server does not authenticate its callers: anyone who can reach the port can call the tools. Only publish it that way on a network where that is acceptable.
The MCP server requests ingress over the standard ingress interface, so any provider works, for example Traefik or the Nginx Ingress Integrator.
Give it a hostname of its own and serve it at the root of that hostname. Clients discover where to authenticate through documents the server publishes under /.well-known/, which live at the root of the host: behind a path prefix, a client is told to authenticate at a URL it cannot fetch. This guide uses the Nginx Ingress Integrator, which serves at the root by default:
juju deploy nginx-ingress-integrator --channel latest/stable --trust nginx-ingress-integrator-mcp
juju config nginx-ingress-integrator-mcp \
service-hostname=mcp.example.com path-routes=/ rewrite-enabled=false
juju integrate datahub-mcp-k8s:ingress nginx-ingress-integrator-mcp
The ingress must terminate TLS. An OAuth issuer identifier cannot be an http:// URL, so the charm blocks rather than start the workload on a plain HTTP URL. Integrate the ingress with a certificates provider, for example Lego for an ACME-issued certificate, or self-signed-certificates in a test environment.
Point DNS for the chosen hostname at the ingress load balancer address.
Authenticate callers¶
Relate an identity provider to close the endpoint:
juju integrate datahub-mcp-k8s:oauth <identity provider>
The server stays blocked, with the workload stopped, until the provider has registered the client. It does not serve a public endpoint that it cannot yet authenticate.
How callers obtain credentials depends on the provider.
Option A: Providers that register clients themselves¶
Against a provider that publishes a registration_endpoint (Hydra, and therefore the Canonical Identity Platform) the MCP server is a plain OAuth 2.1 resource server. Clients register themselves, and the server only verifies the tokens they arrive with:
juju integrate datahub-mcp-k8s:oauth hydra:oauth
Nothing further is needed. Users pass their client the URL and it configures itself.
Option B: Google¶
Google publishes no registration endpoint, so a client pointed at it has no way to obtain credentials of its own. The charm therefore runs an OAuth proxy in front of Google: an authorization server of its own that registers callers on demand and holds the single Google client this deployment owns. Callers still pass their client nothing but the URL, and Google only ever redirects to the server’s own fixed callback rather than to whichever loopback port a client happens to listen on.
The deployment needs one Google client of its own, separate from the one DataHub’s SSO uses, because a client’s redirect URIs are its own:
In the Google Cloud console, create an OAuth client of type Web application. A desktop client is not needed: the secret stays on the server and never reaches users.
Under Authorized redirect URIs, add
https://<your-hostname>/auth/callback, for examplehttps://mcp.example.com/auth/callback. Add the redirect URI of any client configured by hand alongside it.Deploy a second integrator carrying that client, with the same endpoints as DataHub’s (see Enable single sign-on for the configuration file), and relate it:
juju deploy oauth-external-idp-integrator --config mcp-idp-config.yaml oauth-external-idp-integrator-mcp
juju integrate datahub-mcp-k8s:oauth oauth-external-idp-integrator-mcp:oauth
Two consequences of the proxy are worth knowing:
The workload needs egress to
oauth2.googleapis.com, because it exchanges the authorization code and validates tokens server-side. Behind a filtering proxy, allow that host. The charm forwards the model’sjuju-http-proxy,juju-https-proxy, andjuju-no-proxysettings to the workload; see Configure model proxies.Run a single unit. The proxy is the authorization server, and it holds its client registrations and the tokens it issues in the unit, so a second unit does not recognize the first one’s tokens and a restarted unit does not recognize its own. Callers that discovered the proxy sign in again when that happens. Providers that register clients themselves keep no such state and scale normally.
Not every client can use the proxy. One that reads /.well-known/oauth-protected-resource finds it and configures itself; one an administrator fills into a form never looks, and is pointed at Google directly. Such a client arrives holding a token Google issued rather than one the proxy minted, and the server accepts it when Google confirms it was issued to the client this deployment owns.
Limit the endpoint to callers you registered¶
By default a caller obtains an OAuth client of its own on first connection, which is what lets a user point a client at the URL and nothing else. It also means that anyone who can reach the endpoint and sign in at the identity provider can call the tools with any client they like.
To leave only the callers set up in advance:
juju config datahub-mcp-k8s enable-client-registration=false
Those callers are configured with this deployment’s own client ID and secret, the same pair that is on the oauth relation, and need nothing added to the charm. The server recognizes that client without a registration.
The charm enforces this wherever the registrar happens to be. Fronting Google it is the registrar itself, so it withdraws /register, stops advertising it, and serves no client but its own. Against a provider that registers clients itself it cannot stop the registering, so it refuses tokens that were not issued to this deployment’s client.
A client configured against Google directly, such as Gemini Enterprise, is unaffected either way: it already presents a token naming this deployment’s client, which is what the rule asks for.
Enable the mutation tools¶
By default, the server exposes the read-only tool set. To also expose the tools that write tags, glossary terms, owners, domains, and descriptions:
juju config datahub-mcp-k8s enable-mutation-tools=true
Every write is attributed to the shared service account rather than to the user whose agent made the call, and the service account holds no write privileges by default, so this also requires granting it a write policy in DataHub. See MCP server for the full tool set.
Verify¶
Check that the application is active and note the URL the ingress published:
juju status --relations
Connect a client¶
Unless client registration has been turned off, the URL is all a client needs, including when the endpoint authenticates its callers. It discovers where to authenticate and obtains its own credentials on its own; no client ID, secret, or callback port belongs in a client’s configuration:
{
"mcpServers": {
"datahub": {
"type": "http",
"url": "https://mcp.example.com/mcp"
}
}
}
The first call opens a browser for the user to log in with the identity provider. What a caller sees in the catalog does not depend on who they are: every call reaches DataHub as the one service account from the datahub-client relation. The identity decides whether a caller may call the server at all, not what it will show them.
Where registration is off, the client is instead configured with the endpoint’s /authorize and /token URLs and this deployment’s client ID and secret. Users still sign in as themselves; only the client is provisioned in advance.
Remove the MCP server¶
juju remove-application datahub-mcp-k8s
Removing the relation or the application deletes the DataHub service account and invalidates its token. The catalog and everything else in the deployment are unaffected.