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.
Built-in Admin MCP Data tools
Data tools appear under Inventory → MCP Servers → Registration → Built-in Admin MCP endpoint, separately from Registered upstream servers and their counts. Connect administrators to https://YOUR_ORG.aicontrols.dev/mcp/admin to use its four read-only tools alongside the management tools. Applications and service accounts continue to use /mcp with their existing reporting grants. Data has no upstream URL, command, upstream credentials, connection scan, or editable transport. See the Admin MCP reference for deployment availability and authentication requirements.
The Data tool group and its Details drawer show an Administrator connection guide with the endpoint, credential requirements, and Data grant distinction. Use the administrator SQL steps below to discover the business schema and run a query.
The Details drawer covers half of the window. Use the arrow buttons in its header, or the ← and → keys, to move to the previous or next server on the Registration tab without closing the drawer. The page URL contains the server while the drawer is open, for example ?server=<name>, so you can copy it to share the server. Back closes the drawer.
Its four Data tools provide authorized reporting and coaching:
| Tool | Purpose |
|---|---|
data__catalog | Discover authorized logical reporting metadata, or the paged business SQL schema for an administrator with SQL authority. |
data__query | Run a prepared report, semantic query, or administrator read-only SQL query. |
data__explain_query | Validate a semantic query and inspect its plan without reading source rows. |
data__coaching_context | Return prepared session, usage, and delivery evidence; accepts only an optional limit. |
Prepared reports include recent_sessions, recent_pull_requests, coaching_context, and team_directory. They default to 50 rows and accept a limit from 1 through 100. Prepared results are bounded samples, not complete exports. Administrators with an explicit SQL grant can also submit read-only SQLite queries as described below. The provider allows one report execution at a time; additional concurrent calls fail until capacity is available. Reports also fail when a source exceeds its fixed read bound, instead of returning a silently incomplete aggregate.
Source cells are limited to 64 KiB, and encoded result rows to 64 KiB. A prepared-report response cannot exceed 8 MiB. Oversized values or results fail the request; fields are never silently truncated to fit.
Semantic metric aliases must be single identifiers of at most 512 bytes, using ASCII letters, digits, and underscores, with a letter or underscore first. Output names must be unique, and order_by must name a field in the query output, such as a selected field or metric alias. SQL expressions are not accepted in either position.
For fields cataloged as int64, filter values must be decimal JSON strings, including each value in an in filter. For example, use {"field":"input_tokens","operator":"gt","value":"100"}. Bare numbers such as "value":100 are rejected; strings preserve exact values above JavaScript's safe integer range. Timestamp filters use UTC RFC3339 strings, such as "2026-10-01T00:00:00Z". Floating-point fields accept JSON numbers.
Catalog discovery with no arguments includes only authorized logical metadata. Administrator SQL discovery separately exposes reviewed physical business table names and explains excluded sources and columns. Credential values are never queryable. A revoked grant remains revoked when the demo is activated again.
Calls use the same AIControls gateway policies, access bindings, approvals, rate limits, response controls, and audit path as other MCP tools. They also require a current Data Access grant for the verified caller. Seeing the tool group does not grant access to reporting data. Its built-in provider cannot be renamed, replaced, refreshed, scanned, or deleted.
Policies, access bindings, schema pinning, and audit history retain the AIControls Data identity and reserved data tool prefix. The group is part of Admin MCP; it does not require a separate administrator connection. Before upgrading a deployment that already has an MCP server named data, an AIControls Data server, or a custom data tool prefix, rename that server or prefix. AIControls refuses to start with an ambiguous reserved identity rather than route calls to the wrong provider.
Query business data with administrator SQL
For field definitions, typed values, limits, and grant responses, see the AIControls Data reference.
Sign in as an active administrator and connect your MCP client to
https://YOUR_ORG.aicontrols.dev/mcp/admin. Use your current AIControls-issued local session JWT or a personal PAT bound to your active administrator account's immutable ID. Send the credential in theAuthorization: Bearer YOUR_ADMIN_TOKENheader on every request; earlier MCP session credentials and URL query tokens do not establish Data authority. For example:{"mcpServers": {"aicontrols": {"url": "https://YOUR_ORG.aicontrols.dev/mcp/admin","headers": {"Authorization": "Bearer YOUR_ADMIN_TOKEN"}}}}For a PAT, allow the exact qualified tools you need, such as
data__cataloganddata__query. A valid empty tool list ([]) permits all Data tools. Malformed or null lists, wildcard names, bare names, and legacy email-only PATs do not authorize Data access. Raw external OIDC tokens do not qualify for administrator SQL.Data access also requires an explicit Data Access grant for the caller. Normal startup does not create grants. A fresh demo grants its seeded administrator the
admin_sqlcapability. Existing grants remain unchanged after an upgrade or restart.Use your current local session JWT for both grant-management requests below, even when your MCP client uses a PAT. PATs cannot read or change these grants. Read
GET /api/v1/data-access/admin-sql/granton the admin endpoint. If it returns404 grant_not_found, explicitly create your grant with:{"expected_revision":"0","enabled":true}Send this body to
PUT /api/v1/data-access/admin-sql/grant. The new grant lasts 365 days and includesdiscoverandadmin_sqlonly; it does not grant prepared or semantic dataset access.For an existing active grant, explicitly enable SQL with
PUT /api/v1/data-access/admin-sql/grantand the returned revision:{"expected_revision":"1","enabled":true}Use
enabled:falseto remove SQL authority. A stale revision returns409. This operation preserves dataset permissions, expiry, and revocation; it does not renew an expired grant or restore a revoked one. SQL authority covers the entire reviewed business schema and all of its rows across the bound deployment, independently of your logical dataset field and row grants. This preview supports deployment administrators; it does not isolate customers sharing a source database.Call
data__catalogwith these arguments:{"schema":"admin_sql","limit":25,"offset":0}Follow
next_offsetuntil it is absent. Usetablesto select exact table names,include_columns:falseto list table summaries, orsection:"relationships"to inspect documented joins. Usesection:"exclusions"without a table filter to page through credential, secret, and internal-state exclusions. The maximum page size is 50. Unknown columns are not automatically published.Pass a read-only SQLite statement to
data__query. For example:{"sql":"WITH counts AS (SELECT developer_id, count(*) AS sessions FROM developer_sessions GROUP BY developer_id) SELECT developer_id, sessions, dense_rank() OVER (ORDER BY sessions DESC) AS rank FROM counts WHERE sessions >= :minimum ORDER BY sessions DESC, developer_id","parameters":{"minimum":{"type":"integer","value":"1"}},"limit":10}SQL supports joins, CTEs, recursive queries, subqueries, grouping, windows, unions, and reviewed built-in scalar functions. Named parameters use
:namein SQL. Values usetypeandvalue:integeruses a decimal string,reala JSON number,texta string,bloba base64 string, andnullJSON null. Result cells use the same lossless representation; an expression's type can differ by row.Only reviewed business tables are available. Writes, schema changes, multiple statements, system catalogs, PRAGMA, ATTACH, extension loading, virtual tables, implicit
rowidaliases, and filesystem or network functions are rejected. Use the primary-key columns returned by schema discovery. SQL runs against an isolated snapshot of the referenced business tables.Inspect
complete,truncated,source_tables,source_rows, andsource_bytesalongside the result. A result beyond your row limit returns an explicit truncated page; source overflow fails the entire query, including aggregates. A complete SQL result describes the retained source snapshot, not all historical activity.SQL queries use the
interactiveprofile by default: a five-second total deadline and at most 100,000 cumulative source rows. For larger reports, verify that catalog discovery advertisesreportinexecution_profiles, then add"profile":"report"to thedata__queryarguments. This profile allows up to 1,000,000 cumulative source rows and 60 seconds; a shorter caller deadline still applies. Upgrade the server if it does not advertise the report profile. Prepared and semantic queries do not accept SQL execution profiles.Both SQL profiles allow at most 100 result rows, an 8 MiB response, 64 KiB cells and encoded rows, and 256 MiB of accounted source data. SQL text is limited to 64 KiB, parameters to 64, result columns to 256, expression depth to 100, and compound SELECT terms to 64. These are demo safety limits, not production capacity or memory guarantees. Errors distinguish
invalid_request,access_denied,limit_exceeded,deadline_exceeded, andunavailablewithout exposing backend details.
The gateway rechecks the administrator's current role and SQL grant before
execution and delivery. Normal policy, enabled-state, and response controls
apply. SQL audit records retain request-size/count metadata and event correlation
without SQL literals, parameter values, or a content-derived request fingerprint.
Stored SQL approval justifications omit reflected request values. Prepared and
semantic query forms remain available;
data__explain_query continues to accept only semantic requests.
Register the server
A public server that needs no sign-in, such as DeepWiki at https://mcp.deepwiki.com/mcp, is enough to see tool governance work. See Try AIControls with free models and a public MCP server.
- Go to Inventory → MCP Servers → Registration tab → Add MCP Server.
Fill in the server fields:
Field Description Name Used as a tool prefix in agent calls, e.g. prometheus→ toolprometheus__queryType http(remote server) orstdio(local command)Transport For httpservers: SSE (legacy, and what most MCP servers speak) or Streamable HTTP (FastMCP 3.x and any server built against the 2025-03-26 MCP spec). Use Detect settings from URL if you aren't sure.URL / Command MCP server endpoint or executable command Authentication See Choose an authentication method below
Pick Streamable HTTP for servers with slow tools, and for any tool that isn't safe to run twice.
SSE holds one long-lived connection, so AIControls probes it with a short timeout to tell a stalled connection apart from a working one. A tool that takes longer than 15 seconds to reply is treated as a possibly-dead connection: the call is abandoned and retried on a fresh connection. If the server had in fact received the first call, the tool runs twice — which matters for anything that isn't idempotent, such as creating a ticket, sending a message, or committing a change.
Streamable HTTP sends each tool call as an independent request. AIControls checks an idle MCP session with a lightweight ping before sending the next tool call. If the session has expired, AIControls creates a new session first. The tool call itself is still sent exactly once and may run until the calling client's request deadline.
Developers don't configure MCP servers themselves — they're provisioned centrally and available to all agents automatically based on group membership policies.
Server names must be unique. Adding a server with a name that's already registered fails with an error instead of replacing the existing one — edit the existing server instead.
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 |
|---|---|
| Callback URL | Read-only. The redirect URI to register in your OAuth app, in the form <public URL>/auth/connect/<server name>/callback. It appears once you enter a server name. Click Copy to copy it |
| 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.
Editing an OAuth server: the drawer shows the saved client ID, scopes, and custom endpoint URLs. The client secret is never shown. If you keep the same provider, any field you leave blank keeps its saved value, so you can change other settings, such as connection instructions, without entering the credentials again. To change a value, enter the new one. To remove all scopes, clear the Scopes field. If you switch providers, enter the new provider's client ID, secret, and scopes.
Callback URL. AIControls builds the callback URL from ui.publicURL, the externally reachable address of your deployment, and sends the same URL as the redirect_uri when a developer signs in. Register it exactly as shown; the provider rejects a sign-in whose redirect URI doesn't match. If ui.publicURL isn't set, the field uses the address you opened the console on and asks you to set a public URL, so that the URL matches the address developers use.
Tools appear after a developer connects. Without a discovery token, AIControls can't list the server's tools when you add it, because the server requires each developer to sign in. Test connection reports Test connection: sign-in required instead of an error, and the server registers with 0 tools. To list them, connect your own account in Connected Accounts, then click Refresh on the server card. Refresh lists the tools with the connection of the administrator who clicks it. To list tools when you add the server, enter a discovery token.
The server card shows your own connection: Connected, or Not connected with a Connect button. When you haven't connected, the tool count reads Connect your account to discover tools. Otherwise it shows when the tools were last synced, for example From last sync, 2 hours ago. If Refresh can't reach the server with your connection, it keeps the last synced tools and shows Live refresh unavailable with an Open Connected Accounts button.
Connections belong to the server name. Each developer's OAuth connection is stored for a specific server name. If you rename the server, or remove it and add it again under another name, each developer must connect again.
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. The link points to Connected Accounts at ui.publicURL. When no public URL is set, or it names this machine, the message has no link and asks developers to connect from Settings → Connected Accounts instead.
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.
Discovery — whether run from Test connection, on save, or by a later Refresh — is bounded by a 30-second deadline. An upstream that accepts the connection but never responds fails with a clear scan timed out error instead of leaving the request hanging.
The same 30-second bound applies to the proxy's own connection handshake when a developer's first tool call opens a session to the upstream — the call fails with a clear connection-timeout error rather than hanging. Only the handshake is bounded: a healthy server's tool call may still run as long as it needs (streaming responses are never capped).
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 Inventory → MCP Servers → Registration tab 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 Inventory → MCP Servers → Registration tab 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]
See also
A stdio connection test starts a local process on the proxy. It requires an
administrator session; builder credentials and developer PATs cannot run it.
Only configure commands you trust with the proxy's operating-system privileges.