Rucio: Per-Site Setup¶
rucio-mcp fronts more than one Rucio VO/instance (ATLAS, ESCAPE, ...), and
each site is its own OAuth 2.1 authorization surface — rucio-mcp acts as an
OAuth 2.1 AS proxy per site, not one shared AS for all of them. This page is
the operator procedure for wiring up one additional site, using the
oauth21-direct identity-provider type (see below).
Each site is both an Adding a Service case (a new
aggregator.services entry) and an identity-provider case (a new
broker.identityProviders entry) — this page is the concrete, two-site
worked example; read those two pages first for what each field means in
general.
The shape: oauth21-direct, not keycloak-brokered¶
rucio-mcp is itself an OAuth 2.1 authorization server, so it's wired up the
same way any oauth21-direct backend is (see
auth.md's identity provider types and
architecture.md's CIMD section):
the broker acts as a direct OAuth 2.1 client to each site's authorize/token
endpoints, identifying itself via its own CIMD document
(GET /.well-known/cimd) instead of registering per site through Dynamic
Client Registration. No Keycloak Identity Provider is created for rucio-mcp
at all — an earlier design (#62)
proposed one per site, but it was never built; oauth21-direct is what
shipped.
This is a different shape from the atlas-oidc keycloak-brokered provider
that services PanDA and AMI: that one is a Keycloak Identity Provider (the
user links their CERN account, Keycloak stores the resulting token, and the
broker retrieves it via GET /realms/<realm>/broker/atlas-oidc/token). Rucio
sites don't use that path or its prerequisites — no "Store Tokens" IdP
config, no read-token client role (see
auth.md's read-token role section)
— because there is no Keycloak IdP in the loop for them.
Per-site endpoint pattern¶
rucio-mcp serves each site's OAuth 2.1 surface under /site/{site}, not a
shared path:
| Purpose | URL |
|---|---|
| Authorize | https://rucio-mcp.example.org/site/<site>/authorize |
| Token | https://rucio-mcp.example.org/site/<site>/token |
| Issuer | https://rucio-mcp.example.org/site/<site> |
MCP endpoint (aggregator backend url) |
https://rucio-mcp.example.org/site/<site> |
<site> is the Rucio VO/instance name (atlas, escape, ...). Everything
below is per-site: adding a third site means repeating both config blocks
with a new <site> value, not editing the existing entries.
What to configure per site¶
Two config additions, both in the deploying HelmRelease's values, no chart
template edits:
1. broker.identityProviders — an oauth21-direct entry.
broker:
publicOrigin: "https://mcp-portal.example.org" # see below — must be the portal's origin
identityProviders:
- type: oauth21-direct
alias: rucio-mcp-<site>
targets: ["rucio-mcp-<site>"]
authorizationEndpoint: "https://rucio-mcp.example.org/site/<site>/authorize"
tokenEndpoint: "https://rucio-mcp.example.org/site/<site>/token"
issuer: "https://rucio-mcp.example.org/site/<site>"
displayName: "Rucio MCP for <Site>"
enables: "Access to rucio-mcp for <site> on your behalf"
alias and the single entry in targets both follow rucio-mcp-<site> —
this is convention, not a requirement, but it's what the deployed config
uses and keeps the identity-provider alias, its target, and the matching
aggregator.services entry (below) trivially traceable to each other.
2. aggregator.services — a matching service entry.
aggregator:
backends:
- name: rucio-mcp-<site>
prefix: rucio-<site>
url: "http://rucio-mcp.<namespace>.svc.cluster.local:80/site/<site>"
transport: http
required_permission: read_data
auth_type: bearer
name must match the targets entry above — that's how
CredentialRegistry (see adding-a-service.md) knows
which identity provider services this backend's credential. prefix is
distinct per site (rucio-atlas, rucio-escape, ...) so the aggregator can
tell the sites' tools apart; url is the in-cluster Service address rather
than the public hostname used for the authorize/token endpoints above, since
tool calls stay inside the cluster.
required_permission: read_data is the same permission across every site —
reuse it (see adding-a-service.md's Step
2),
no new policy.yaml entry needed as long as the groups you want to grant
Rucio access already map to read_data (the built-in default does, for
atlas, cms, dune, escape — see
auth.md's Group-to-Permission Mapping Example).
Why broker.publicOrigin must be the portal's origin¶
broker.publicOrigin is what every redirect_uri the broker constructs for
any oauth21-direct provider is built from — not just rucio-mcp's, but
every site's. It must be the exact origin the portal SPA is served from
because the linking flow's nonce cookie is host-only: the portal's own
same-origin fetch to /v1/oauth/authorize/{alias} sets the cookie on
whichever host received that call, and the authorization server's redirect
back to <publicOrigin>/v1/oauth/callback/{alias} only carries that cookie
if it lands on the same host. This is a broker-wide setting, not a per-site
one — see
architecture.md's CIMD section
for the full mechanics, and set it once regardless of how many rucio-mcp
sites (or other oauth21-direct backends) you configure.
The CIMD client-id mechanism, concretely for rucio-mcp¶
The broker's GET /.well-known/cimd document (served on the broker's own
host, e.g. https://mcp.example.org/.well-known/cimd) is a
self-describing client registration: its client_id is the literal URL used
to fetch it, and its redirect_uris list one entry per configured
oauth21-direct provider — so adding a second rucio-mcp site adds a second
redirect_uris entry automatically, no separate registration step.
This is a two-sided fit, not just the broker being economical about
registration: rucio-mcp itself (rucio_mcp/server.py) deliberately disables
Dynamic Client Registration and authenticates clients solely via CIMD,
advertising client_id_metadata_document_supported: true in its OAuth 2.1
authorization server metadata for every site. That's why OAUTH21_CLIENT_ID
(the chart's broker.publicOrigin-derived CIMD URL) is what identifies the
broker to rucio-mcp, rather than a client id issued through a registration
step — rucio-mcp has no /register endpoint or per-client registry to
register against in the first place.
The linking flow the user experiences¶
- On the portal's Identities page, the user clicks Link next to "Rucio
MCP for ATLAS" (or whichever site's
displayName). - The portal SPA calls
GET /v1/oauth/authorize/rucio-mcp-atlaswith its own bearer token andAccept: application/json, gets back anauthorize_url, and navigates the browser there. The broker sets an HttpOnly nonce cookie on this call. - The browser lands on rucio-mcp's
/site/atlas/authorize— rucio-mcp handles whatever authentication that site requires on its own; the broker is not involved in that step. - rucio-mcp redirects the browser back to the broker's
<publicOrigin>/v1/oauth/callback/rucio-mcp-atlaswith an authorization code (or an error, per OAuth 2.1 §4.1.2.1 — the broker surfaces either case, seebroker/src/af_mcp_broker/api/oauth21.py'scallbackroute). - The broker validates the returned
stateand nonce cookie, exchanges the code for a token at rucio-mcp's/site/atlas/tokenendpoint, and stores the result in its own OAuth 2.1TokenStore— Vault/OpenBao-backed in production (broker.oauth21.tokenStore.backend: vault), keyed by(subject, alias). This token lives in the broker's own store, not Keycloak — unlike theatlas-oidcpath, there's no Keycloak stored-broker-token involved anywhere in this flow. - The portal redirects back to the Identities page with a confirmation that
rucio-mcp-atlasis now linked.
From here, a tool call against a rucio-mcp-backed tool the caller's
permissions allow (read_data) has the broker fetch (and refresh, near
expiry) this stored token and forward it as the tool call's bearer
credential — the same auth_type: bearer path any other backend uses.
Naming consequence: double-prefixed tool names¶
rucio-mcp's tools are already self-prefixed at the source (rucio_whoami,
rucio_list_dids, ...). With two sites configured, each as its own
aggregator.services entry, apply_namespace is left at its default
(true) rather than set to false — a single un-namespaced rucio_* mount
would be ambiguous once a second site can advertise the same tool names (see
adding-a-service.md's apply_namespace
section for the full
tradeoff and why false only stays safe for exactly one rucio-mcp backend).
The result callers actually see is double-prefixed tool names —
rucio-atlas_rucio_whoami, rucio-escape_rucio_whoami — ugly, but
unambiguous. This isn't a per-site config knob; it falls directly out of
apply_namespace's default once more than one self-prefixed backend is
configured, and applies equally to a third or fourth site.
See also¶
- Adding a Service — the general, service-agnostic procedure this page is a worked example of.
- Authentication —
keycloak-brokeredvsoauth21-directin general, and whyatlas-oidc(PanDA, AMI) uses the former while rucio-mcp uses the latter. - Architecture — the CIMD mechanism in full.