Connect an MCP Server
Register an upstream MCP server and choose how AIControls authenticates to it. For how MCP tool-call proxying and authorization work conceptually, see MCP Governance.
Register the server
- Go to Settings → MCP Servers → Add server.
Fill in the server fields:
Field Description Name Used as a tool prefix in agent calls, e.g. prometheus→ toolprometheus__queryType http(SSE) orstdio(local command)URL / Command MCP server endpoint or shell command Authentication See Choose an authentication method below
Developers don't configure MCP servers themselves — they're provisioned centrally and available to all agents automatically based on group membership policies.
Choose an authentication method
Each MCP server can be configured with one of seven authentication methods. The right choice depends on whether the upstream server has shared credentials, per-developer credentials, or an OAuth-capable identity layer.
- None
- Bearer Token
- Basic Auth
- Passthrough (per-developer)
- Per-developer (credential store)
- OAuth 2.0 (per-developer)
- Cross-App Access
No credentials are forwarded.
When to use: Internal servers on a private network that trust all proxy traffic implicitly.
A static token stored in the workspace config is injected as an Authorization: Bearer header on every request.
When to use: Servers with a shared service account; simple API key authentication.
A username and password stored in the workspace config are forwarded as an Authorization: Basic header.
When to use: Legacy servers that require HTTP basic authentication.
The developer's own AIControls identity token is forwarded directly to the upstream server. The upstream must accept AIControls tokens.
| Field | Description |
|---|---|
| Header name | The request header the token is forwarded in — also the header developers add to their Claude Code MCP config. Cannot be Authorization (reserved for the AIControls PAT); use a custom header like X-Github-Token or X-Jira-Token |
When to use: Internal servers that trust AIControls as an identity provider and want to enforce per-developer access controls themselves.
Each developer stores their own credential for this server in AIControls. The proxy injects the correct credential at call time based on the calling developer's identity. Admins never see individual credentials.
When to use: Servers where each developer has their own account — for example a database or internal API where per-developer audit trails matter.
Each developer completes a one-time OAuth authorization flow (Authorization Code + PKCE). AIControls acts as the OAuth client, stores and refreshes tokens automatically. Subsequent tool calls are transparent to the developer.
| Field | Description |
|---|---|
| Provider | GitHub, Google, Atlassian, or Custom |
| Client ID | OAuth app client ID registered with the provider |
| Client secret | OAuth app client secret |
| Scopes | Optional. Space- or comma-separated, e.g. read:user repo |
| Authorization URL | Required when Provider is Custom — the provider's OAuth authorization endpoint |
| Token URL | Required when Provider is Custom — the provider's OAuth token endpoint |
When to use: Third-party SaaS MCP servers (GitHub, Jira, Salesforce, etc.) that use standard OAuth 2.0 for access control.
For OAuth-enabled servers, developers authorize once by visiting a link shown in their AI tool. AIControls handles token refresh silently — developers are never prompted again unless their access is revoked.
AIControls exchanges the developer's own inbound identity token for a resource-scoped access token at the upstream's Authorization Server, using the Cross-App Access / ID-JAG token exchange (RFC 8693 + RFC 7523). No per-developer credential is stored — the exchange happens statelessly on every call using the identity already established by the client (e.g. Claude, Claude Code).
You'll need, from the upstream's Authorization Server:
| Field | Description |
|---|---|
| IdP token endpoint | Token endpoint of the enterprise IdP that issues the ID-JAG |
| Resource token endpoint | Token endpoint of the upstream's Resource Authorization Server, where the ID-JAG is exchanged for an access token |
| Client ID | Client identifier registered with the enterprise IdP for the exchange |
| Client authentication method | How AIControls authenticates itself to the IdP token endpoint: Client secret (POST body) (default), Client secret (HTTP Basic auth), or Private key JWT (RFC 7523 client assertion) |
| Client secret | Required when the client authentication method is one of the client secret options |
| Client private key (PEM) | Required when the client authentication method is Private key JWT; the RSA private key used to sign the client assertion |
| Client assertion audience | Optional; overrides the client assertion's aud claim. Defaults to the IdP token endpoint |
| Resource audience | The Resource Authorization Server's own identifier (its issuer) — the AS that accepts the ID-JAG and issues the access token. This is not the MCP server's own URL — that's the separate Resource field below. Sent as the RFC 8693 audience parameter |
| Token exchange resource parameter | Which RFC 8693 parameter name carries the resource audience in the exchange request: audience (default) or resource — Authorization Servers vary on which they expect. Ignored when a Resource URI is set below |
| Resource | Optional. The RFC 8693 resource parameter — the specific downstream resource URI (e.g. the MCP server URL). Sent as a distinct parameter alongside the resource audience; some Authorization Servers require both |
| Scope | Optional. Space-delimited scope string requested in both the token-exchange request and the JWT-bearer grant |
| Resource client ID / secret | Optional. Some Resource Authorization Servers register a distinct client identity for the JWT-bearer grant, separate from the Client ID above, authenticated via HTTP Basic auth. Leave blank if the grant needs no client authentication |
When to use: Enterprise MCP servers whose Authorization Server implements Enterprise-Managed Authorization (SEP-990) and where the IdP — not AIControls — should decide which developers may connect at all.
Because the exchange requires the calling developer's own inbound identity token, AIControls has no developer identity to list tools with during registration. Optionally provide a one-time bearer token in the Discovery token field to populate the tool list immediately — otherwise the server registers with zero tools, and the count populates once a developer makes a real call.
Developers using PAT-authenticated tools (like Claude Code) need to link their enterprise identity once before Cross-App Access datasources will work for them — a PAT alone carries no inbound identity token for the exchange.
Discover endpoints automatically
Instead of hand-typing the resource token endpoint, resource audience, and resource, click Discover endpoints from server URL (shown once you enter the server's URL above and select Cross-App Access). AIControls fetches the upstream's RFC 9728 Protected Resource Metadata (/.well-known/oauth-protected-resource) to find its declared Authorization Server, then that server's RFC 8414 metadata (/.well-known/oauth-authorization-server, falling back to /.well-known/openid-configuration) to find its token endpoint — and fills in Resource token endpoint, Resource audience (from the AS's RFC 8414 issuer), and Resource (from the resource server's own RFC 9728 self-identifier) for you to review before saving.
IdP token endpoint isn't auto-filled — it's your own enterprise IdP's token endpoint (leg 1 of the exchange trades your inbound identity token for an ID-JAG there), which has nothing to do with the resource server and can't be discovered from it. Enter that one manually; ask your identity team for the token endpoint of the IdP client registered for this exchange.
If the upstream doesn't publish this metadata, discovery fails with an inline error and you can fill in the fields manually as before.
Two more fields appear on the form for several of the methods above, regardless of which is selected:
| Field | Applies to | Description |
|---|---|---|
| Discovery token | Passthrough, Per-developer (credential store), OAuth 2.0, Cross-App Access | Optional. A one-time bearer token used only to list tools from the upstream during registration — not stored afterward. Leave blank to register with zero tools discovered; the count populates once a developer makes a real call |
| Connection instructions | All per-developer methods | Optional guidance shown to developers in Connected Accounts before they connect their own credential or OAuth account. Most useful for Per-developer (credential store), where developers need to know how to generate the credential you expect |
Test the connection while adding a server
Before saving, click Test connection in the Add/Edit MCP Server drawer to run the standard MCP discovery sequence (initialize, then tools/list, prompts/list, resources/list) against the form's current values, without registering or persisting anything. The result shows the tool/prompt/resource counts, or an error per category.
For a Cross-App Access server, Test connection uses your own linked enterprise identity (see Link your enterprise identity) to run the real two-leg ID-JAG exchange, if you've linked one — the same exchange a developer's tool call would trigger — rather than substituting a discovery token. The result tells you which happened:
| Result | Meaning |
|---|---|
| ✓ Real Cross-App Access exchange validated | You have a linked identity; the test ran the actual leg-1 + leg-2 exchange against the IdP and Resource Authorization Server and reached the upstream with a real access token |
| Discovery only — link your identity to validate the real exchange | You haven't linked an enterprise identity yet, so the test fell back to the Discovery token (or an unauthenticated connection) instead of a real exchange |
Test your connection as a developer
For any per-developer authentication method (Passthrough, Per-developer credential store, OAuth 2.0, or Cross-App Access), once you've connected your own credential in Connected Accounts, click Scan next to the server to run a live check with that credential. AIControls calls the standard MCP discovery sequence — initialize, then tools/list, prompts/list, and resources/list — and shows you exactly what your credential can currently reach: the tools, prompts, and resources the upstream server returns, or an error for any category the credential can't access.
This is useful for confirming a new OAuth connection or stored credential actually grants the access you expect, without needing to make a real tool call from an agent first.
The Scan button only appears once you've connected a credential for that server — it reflects your access, not the server's discovery-token connection used at registration time.
Resolve a quarantined server
When AIControls sees tool-call traffic to a server that isn't configured as a datasource, it automatically creates a quarantined catalog entry and sends a one-time admin alert — see Shadow MCP traffic & quarantine for why this happens. Resolving it is one of two actions:
- Go to Settings → MCP Servers and find the entry marked Quarantined — it shows the endpoint AIControls observed traffic to and when it was first seen.
Choose how to resolve it:
Action Effect Promote Runs the same flow as registering a server manually — confirm the endpoint, transport, and authentication. The entry then becomes a normal governed server: ownable, policy-able, and eligible for Access Bindings. Deny The entry is kept for audit history with quarantine_state: denied; further tool calls to that endpoint are blocked by policy.- Save. There's no third option — an unresolved quarantined entry stays audit-only (logged, but ungoverned) until an admin picks one.
Quarantine is a one-time alert per newly discovered server, not a repeating notification — if you miss it, find outstanding entries by filtering Settings → MCP Servers for the Quarantined state.
Write a tool-authorization policy
Use object.mcp.tool to match tool calls. Arguments are available as object.mcp.arguments:
Block file writes outside the workspace
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: restrict-write-file
spec:
matchConditions:
- expression: 'object.mcp.tool == "write_file"'
validations:
- expression: |
object.mcp.arguments.path.startsWith("/home/") ||
object.mcp.arguments.path.startsWith("/workspace/")
message: "write_file is only permitted inside /home/ or /workspace/"
validationActions: [Deny]
Audit all bash commands
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: audit-bash
annotations:
proxy.nirmata.io/enforcement-mode: audit
spec:
matchConditions:
- expression: 'object.mcp.tool == "bash"'
validations:
- expression: 'true'
message: "bash call recorded"
validationActions: [Audit]
Block destructive commands
apiVersion: policies.kyverno.io/v1
kind: ValidatingPolicy
metadata:
name: block-destructive-bash
spec:
matchConditions:
- expression: 'object.mcp.tool == "bash"'
validations:
- expression: |
!object.mcp.command.matches("(rm -rf|DROP TABLE|truncate)")
message: "Destructive command blocked by policy"
validationActions: [Deny]