Multi-Team Isolation¶
Each team has its own place to deploy servers and manage access. Kubernetes namespaces keep those resources separate, and the platform checks team membership before allowing changes. A team can share a server with another team through an explicit grant.
In the resource definitions, MCPServer.spec.teamID records the owning team.
SubjectRef.teamID identifies the team allowed to use a grant or session.
The source-of-truth data plane is:
MCPServerandMCPAccessGrantare namespaced Kubernetes resources. Agent sessions are namespace-qualified platform records stored by platform-api; Kubernetes session objects do not control authorization.MCPServer.spec.teamIDis the stable platform team ID that owns the server.SubjectRefcontainshumanID,agentID, andteamID; the gateway matches every non-empty field by exact string equality.- A server owner may grant a different team access by setting
subject.teamIDto that team's ID. The platform verifies that each named human or agent belongs to that team; it never treats server ownership as subject identity. - Cross-team grants require
expiresAtand are limited byMCP_CROSS_TEAM_GRANT_MAX_TTL(default168h, seven days). Adapter sessions issued from a grant cannot outlive the grant. - A subject with only
teamIDgrants or binds any authenticated principal in that team. MCPAccessGrant.spec.expiresAtcan end a delegation automatically. Any session issued from the grant expires no later than the grant, including sessions requested by an auto-refreshing adapter.- The gateway reads team identity from OAuth
team_id,tenant_id, ortidclaims, or from the session named by a verified adapter certificate.
When to use this¶
To delegate through the dashboard, open Access control, select the resource namespace, create a grant, and choose Delegate to another team. Enter the recipient's exact team slug and select an active agent, a stable member ID, both, or explicitly the entire recipient team. Recipient lookup requires resource namespace management rights and does not expose general foreign-team directory or membership-management access. Choices are paginated and are cleared when the resource or lookup changes. The form defaults to a 24-hour expiry; the API enforces the configured maximum and revalidates recipients when saving.
Use Revoke sessions on a grant to atomically revoke all explicitly linked
platform sessions, including links stored in another namespace. Disable or
expire the grant as well to prevent future issuance. Retrying revocation reports
only newly revoked sessions; revoked history remains inspectable. Administration's
Gateway audit shows actor and resource teams separately. Unknown historical
actor attribution stays unknown; the resource team_id does not prove actor
membership.
The default mcp-servers namespace is fine for a single team, local
development, and simple evaluation clusters. For a deployment that hosts
multiple teams or tenant boundaries, use one namespace per team.
Recommended namespace shape:
| Team | Namespace | Resources in that namespace |
|---|---|---|
| Acme | mcp-team-acme |
Acme MCPServer, MCPAccessGrant, MCPAgentSession, secrets, configmaps |
| Globex | mcp-team-globex |
Globex MCPServer, MCPAccessGrant, MCPAgentSession, secrets, configmaps |
Keep each server's grants and sessions in the same namespace as the server they
govern. When serverRef.namespace is omitted, clients and renderers should
treat the current resource namespace as the intended boundary.
Provisioning a team namespace¶
Admins create a managed team namespace through the platform API with
team create:
mcp-runtime auth login --api-url https://platform.example.com
mcp-runtime team create acme --name "Acme"
mcp-runtime team user create acme \
--username acme-user@example.com \
--role member
mcp-runtime team list
team user create prompts twice for a hidden password. For non-interactive
use, provide the password through stdin with --password-stdin; password
values are not accepted as command-line arguments.
team create records the team in the platform store and asks the API to ensure
the managed namespace, quota, limit range, default deny network policy, default
service account, bundled Traefik watch RBAC, and bundled Traefik namespace
watch entry. Platform API server writes into that namespace default
spec.teamID from the authenticated principal's team. Grant/session writes
default missing subject.teamID to the owning server team. The platform API
accepts an explicit foreign subject.teamID only after verifying the named
human or active agent belongs to that team and the grant has a capped expiry.
Use team user create as a platform admin when you need a new local
password-login user for a team. The command creates the user and team membership
in one transaction as member or owner, and then the user can sign in with:
mcp-runtime auth login \
--api-url https://platform.example.com \
--username acme-user@example.com \
--password '...'
For an existing platform user, find their user ID in the platform UI and run
mcp-runtime team user add <team-slug> <user-id> --role member (or set the role
to owner). This changes only the team membership and does not reset the user's
password. In the UI, open Administration → Teams and choose Add existing user,
then enter that user ID and select a role. Create account in this team is for
new accounts and requires a temporary password of at least eight characters.
Validation failures appear in the form; an existing email offers the membership
form rather than resetting the account password. Platform admins and team owners can manage members in their teams.
The APIs resolve the current account and membership after validating a saved
login token. Membership changes take effect on the next API request; directory
lookup failures deny access rather than trusting a stale token's roles.
Remove membership with mcp-runtime team user remove <team-slug> <user-id>;
removing the only owner requires --confirm-last-owner. This removes access to
that team, but does not delete the account or revoke its account-wide API keys
and sessions.
For a disposable test identity, first create a regular team member with the
hidden password prompt, record its user ID with team user list, sign in as
that user, and enroll the adapter through the normal session/certificate path.
The static administrator service key has no human identity and cannot replace
that user's login for enrollment. After the test, sign back in as an
administrator and run:
mcp-runtime user deactivate <user-id> --yes --json
user deactivate permanently disables a platform account, removes its team
memberships, and revokes its stored API keys, registry credentials, refresh
tokens and MCP sessions. Audit history and the identity tombstone remain;
there is no reactivation or hard-delete command. Repeating the command returns
already_deactivated: true with the cleanup counts, so interrupted cleanup can
be retried. The transaction rolls back if any revocation or audit write fails.
The signed-in administrator, the last active administrator and a team's last
active owner cannot be deactivated; transfer ownership or create another
administrator first. Server resources and team-owned agents remain in place
for the surviving administrators.
Bearer/API-key requests revalidate current account state. MCP session revocations use the existing gateway sync contract (normally a 500ms poll; disconnected caches deny after the documented maximum stale window), rather than an acknowledgement from every gateway. Previously issued registry bearer tokens retain their normal expiry; revoked stored credentials cannot mint new tokens. Account cleanup counts report committed platform state.
team init is deprecated and rejects at runtime. Use team create above
for the normal platform-backed flow. The managed namespace shape that
team create provisions includes:
apiVersion: v1
kind: Namespace
metadata:
name: mcp-team-acme
labels:
mcpruntime.org/scope: team
mcpruntime.org/team-slug: acme
pod-security.kubernetes.io/enforce: restricted
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: mcp-runtime-team-admin
namespace: mcp-team-acme
rules:
- apiGroups: ["mcpruntime.org"]
resources: ["mcpservers", "mcpaccessgrants", "mcpagentsessions"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["secrets", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods", "pods/log", "events", "services"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch"]
kubectl apply uses create, update, and patch; Kubernetes has no
separate apply verb.
Team Fields¶
Server ownership:
apiVersion: mcpruntime.org/v1alpha1
kind: MCPServer
metadata:
name: payments
namespace: mcp-team-acme
spec:
teamID: 7d0a0b8f-7c25-4761-a632-3cf0108e31d6
image: registry.example.com/acme/payments:latest
Team-wide access:
apiVersion: mcpruntime.org/v1alpha1
kind: MCPAccessGrant
metadata:
name: payments-acme-read
namespace: mcp-team-acme
spec:
serverRef: {name: payments}
subject: {teamID: 7d0a0b8f-7c25-4761-a632-3cf0108e31d6}
maxTrust: low
allowedSideEffects: [read]
Specific human or agent inside a team:
subject:
humanID: alice@example.com
agentID: agt_01arz3ndektsv4rrffq69g5fav
teamID: 7d0a0b8f-7c25-4761-a632-3cf0108e31d6
When more than one subject field is set, every field must match the request
identity, so a moved user stops matching the old team's grants as soon
as their trusted teamID claim or certificate-backed session changes.
Gateway Identity¶
The gateway validates the OAuth token and reads team identity from team_id,
tenant_id, or tid, in that order. Adapter-certificate requests take
humanID, agentID, and teamID
from the rendered session that the ingress-verified SPIFFE identity resolves
to, inside the platform trust domain; their OAuth subject must match that
session's humanID. First-party OAuth tokens with multiple
memberships use team_ids; the gateway selects the policy server's team ID
when present, or the sole team ID when there is only one.
Platform API Enforcement¶
The platform API blocks changes outside a user's allowed team namespaces.
When you install MCP Runtime, --platform-mode chooses which catalog users see
by default. In public mode, visitors can browse that catalog without signing
in. This is a setting for your own platform; the public reference deployment
at platform.mcpruntime.org is a separate installation.
| Mode | Default namespace behavior | Non-admin behavior |
|---|---|---|
org (default) |
mcp-servers-org |
Authenticated users publish and browse the org catalog together with their authorized team namespaces. |
public |
mcp-servers-public |
Anonymous users read the public catalog; signed-in users also publish and browse org and authorized team namespaces. |
- In
orgmode, anonymous callers cannot read the MCP server catalog. - In
organdpublicmodes, non-admin listings cover the active catalog namespace (mcp-servers-orgormcp-servers-publicby default, plus anyPLATFORM_CATALOG_NAMESPACESentries) and the team namespaces authorized on the caller's principal. Anonymous public-mode reads are limited to the public catalog namespaces only; org and team namespaces remain authenticated. - Server publish requests may pass
scope: tenant,scope: org, orscope: publicin place of an explicit catalog namespace. The API acceptsorgandtenantin both modes andpubliconly in public mode;tenantresolves to the caller's team namespace unless an authorized team namespace is provided. - Non-admin callers cannot write servers, grants, or sessions into the shared
mcp-serverscatalog namespace unless it is explicitly configured as an active catalog. - Non-admin callers can only read or write resources in namespaces listed on
their authenticated principal, except for the active
org/publicmode catalog namespace. - Server apply defaults
spec.teamIDfrom the principal's team namespace and rejects mismatches. - Grant/session apply defaults missing
subject.teamIDfrom the referenced server or namespace team. An explicit foreignsubject.teamIDis preserved, allowing the server-owning team to grant another team access to that server after the platform API verifies subject membership and bounded expiry. - A grant or session must reference an
MCPServerin the same namespace as the access resource. Cross-namespaceserverRef.namespacevalues are rejected. - Admin callers keep cluster-wide visibility, but same-namespace and team-ID ownership checks still apply to server resources.
Direct kubectl apply still depends on Kubernetes RBAC. Bind team admins only
inside their team namespace. As a defense-in-depth guard, the operator renders
only grants and sessions whose serverRef points at the target server. Missing
subject.teamID values constrain the caller, not server ownership. Direct
Kubernetes writes bypass platform identity and cross-team TTL checks, so use
the platform API for cross-team grants. Explicit foreign subjects are
rendered and enforced by the gateway, which matches every non-empty
humanID, agentID, and teamID exactly.
Ingress controller watch scope¶
The bundled Traefik manifests watch only registry, mcp-platform,
mcp-observability, mcp-log-collector, mcp-servers, mcp-servers-org, and
mcp-servers-public by default so Traefik does not need broad namespace access.
If MCP servers live in team namespaces,
update the ingress controller watch list, bind the Traefik watch role in each
team namespace, and allow ingress-controller traffic through the namespace
NetworkPolicy. The platform API team create flow performs those changes for
the repo-managed traefik/traefik Deployment when
PLATFORM_TEAM_TRAEFIK_WATCH is not disabled.
For the bundled Traefik overlay, extend the argument:
--providers.kubernetesingress.namespaces=registry,mcp-platform,mcp-observability,mcp-log-collector,mcp-servers,mcp-servers-org,mcp-servers-public,mcp-team-acme,mcp-team-globex
External ingress controllers need equivalent namespace watch, RBAC, and
NetworkPolicy configuration. For platform API team creation, set
PLATFORM_TEAM_TRAEFIK_WATCH=disabled when another controller manages that
wiring, or override PLATFORM_TRAEFIK_NAMESPACE,
PLATFORM_TRAEFIK_DEPLOYMENT, and PLATFORM_TRAEFIK_SERVICE_ACCOUNT when the
repo-managed Traefik names differ.
Managed namespace policies allow outbound HTTPS to Traefik pods in the
configured ingress namespace on ports 443 and 8443. This lets standalone OAuth
servers fetch discovery and signing keys from the platform's public issuer.
Both the namespace and Traefik pod labels must match; the rule does not allow
general Internet egress or ingress-controller dashboard access. Namespace
provisioning through team create or server deploy refreshes this policy for
existing namespaces as well.
Identifier Conventions¶
Keep identifiers stable:
| Field | Recommendation |
|---|---|
teamID |
Use the platform store team UUID or another immutable identity-provider tenant/team ID. Do not use a mutable display name. |
humanID |
Use the identity provider's stable subject claim, or email when that is stable in your environment. |
agentID |
Use the immutable agt_<26-character lowercase ULID> returned by the platform agent directory. Do not type or reuse a human-readable name as an ID. |
mcp-runtime access grant init and access session init scaffold local YAML on
the workstation only. access grant apply uses the platform API by default after
mcp-runtime auth login --api-url <platform-url>; access session apply is
admin-only on the platform API (agents use adapter proxy --server …
--agent … --auto-refresh). Add --use-kube only for admin/operator
direct Kubernetes writes. Before applying manifests, the apply commands warn
about obvious humanID shape problems, such as whitespace, malformed
email-like strings, case-sensitive uppercase email identifiers, or values that
appear to encode mcp-team-* namespace names. The warnings never block the
apply.
Grants default to enforcement. Add --mode monitor when initializing a grant,
or choose Monitor in the dashboard, to observe tool, trust and side-effect
denials before enforcing them. Audit events include would_deny and
would_deny_reason. Identity and required-session checks still block, and a
monitor grant cannot override a matching enforcing grant's denial. Disabled
and expired grants cannot shadow denials. After reviewing the observations,
switch the grant to Enforce in the dashboard or set spec.mode: enforce
in the manifest and apply it through the CLI. Deploy gateways supporting policy
schema v5 before enabling monitor mode.
Audit And Reporting¶
Gateway analytics events carry target team_id from MCPServer.spec.teamID
and caller subject_team_id from the request identity, alongside server,
namespace, human ID, agent ID, session, tool, and decision. ClickHouse
materializes team_id, so team-scoped reporting can filter directly on the
server owner's team without joining through namespace names.
Known Limits¶
- Team-wide grants require team identity from a verified adapter session or validated OAuth team claims. Arbitrary client-supplied identity headers are not trusted.
- The evaluator does exact string matching. It does not resolve live group membership from the platform database.
- Direct
kubectl applycan bypass platform API defaulting; Kubernetes RBAC is the guardrail for direct writers. - Cross-team server sharing is a privileged pattern. Prefer a shared namespace and explicit admin-owned grants when a server is intentionally shared.
- The bundled setup manifests create the legacy single-team namespace and the org/public catalog namespaces; per-team tenant namespaces are an explicit operational step. Personal user namespaces are not created for tenant publishing.
Operational Checklist¶
- Use
mcp-runtime team createto provision a namespace per team or tenant boundary. - Bind team admins only to their namespace.
- Put each team's
MCPServer,MCPAccessGrant,MCPAgentSession, analytics secrets, and image pull secrets in that namespace. - Set
spec.teamIDon every team-ownedMCPServer. - Set
subject.teamIDon grants and sessions, or use the platform API so it defaults missing values from the owning server team. Use an explicit foreignsubject.teamIDfor delegated cross-team access. - Configure verified adapter-certificate identity and OAuth when the server enables it.
- Add team namespaces to the ingress controller watch list and RBAC.
Related Docs¶
- Getting Started for self-hosting; Local Kind for contributor setup.
- CLI for
team,access, and namespace-scoped commands. - Runtime for CRD and reconciliation behavior.
- API Reference for access resource fields.
Next: Client Connections connects an authorized agent using its session certificate. Use Server Publishing first if the team's server is not deployed yet.