# Connect AI clients to the Directory MCP server

> Connect Claude, Claude Code, VS Code or any MCP client to your organization's directory, devices and audit trail. Read-only, scoped and revocable.

Source: https://docs.trustaige.com/manage/directory-mcp

Replace these placeholders with your organization's own values:

- `<your-client-id>`: App client ID, for example `q3Jx8vN2mZ0bT5sLwK7yHc4dR1fGaE9uPiO6nVjXkMs`. On the app’s page in the hub, under Credentials.
- `<your-client-secret>`: Client secret, a secret. From the hub: on the app’s page under Credentials. Keep it on your server.

You're connecting an AI assistant (Claude, Claude Code, VS Code, or any other
client that speaks the Model Context Protocol) to your organization in
Trustaige, so it can answer questions about your members, groups, devices and
audit trail. If an administrator sent you here, all you need is this page and
your own Trustaige sign-in: there is no token or file to receive. The server
address is the same for every organization, and you choose the organization
when you approve the connection.

Trustaige runs the server itself. It's called the **Trustaige Directory MCP**,
and in the hub it sits under **Apps**, marked **Built-in**.

## What it gives your organization

Your security team can ask an assistant questions in plain language and get
answers from live directory data, instead of exporting reports:

- "Summarize our security posture"
- "Which devices are out of compliance?"
- "Who holds admin roles right now?"
- "What groups is ada@example.com in?"
- "Show me sign-in denials from the last 24 hours"
- "Which members have no device enrolled?"

The hub lists these under **Try asking**. Four rules hold for every connection,
and the hub states them under **How access is enforced**:

- **Read-only, always.** No tool on this server writes. An assistant can answer
  questions; it can never change the directory.
- **Scopes capped by the approver.** A connection only carries what the role of
  the person who approved it already permits.
- **Re-checked on every request.** Each call re-reads the person's live
  membership and role. Access tokens last an hour.
- **Revocation is immediate.** Revoke a connection in the hub, or suspend the
  person who approved it, and its tokens stop working at once.

Every connection is listed in the hub with who approved it and when it was last
used, and approving, revoking, linking and unlinking are recorded in the audit
log.

## How it works

The server follows the MCP authorization specification, so a client needs only
the server's address. It discovers everything else, registers itself, and sends
you to Trustaige to approve.

*Steps 1 to 7 happen once per connection. After that the client refreshes its own tokens; every tool call is checked against your live role.*

1. AI client → Directory MCP: connects to /mcp
2. Directory MCP → AI client: 401, with the metadata address
3. AI client → Trustaige sign-in: discovers and registers itself
4. AI client → You: opens the consent page in your browser
5. You ↔ Trustaige sign-in: passkey, then Allow access
6. Trustaige sign-in → AI client: authorization code
7. AI client → Trustaige sign-in: code + PKCE verifier: access and refresh tokens
8. AI client → Directory MCP: tool calls, with the access token

## Before you begin

| You need | Why |
|---|---|
| The Directory MCP server switched on for your organization | It's in preview and is switched on organization by organization. Until it's on, the hub says **The Directory MCP server is not enabled for this organization.** |
| A role that covers the scopes the client asks for | Approving a connection needs the hub permission behind each scope: **View users**, **Manage devices**, **View audit logs**, or superadmin. A client that doesn't name scopes asks for all three. |
| An MCP client that supports remote servers over HTTP with OAuth | Claude, Claude Code and VS Code all do. |

To see the server page, its connections and the setup steps in the hub, you need
the **Manage applications** permission. Approving a connection doesn't need it.

## The server address

Every organization uses the same address. Copy it from the hub's **Server URL**
field (**Apps → Trustaige Directory MCP**), or use it from here:

```text title="Server URL"
https://id.trustaige.com/mcp
```

Which organization a connection reaches is decided when you approve it: the
consent page names the organization at the top, and the connection is bound to
it from then on.

## Connect Claude

For claude.ai and Claude Desktop.

1. In Claude, open **Settings → Connectors** and choose **Add custom connector**.
2. Paste the server URL, `https://id.trustaige.com/mcp`, and select **Add**.
3. Click **Connect**. Your browser opens the Trustaige consent page. Sign in with
   your passkey if you're asked, check the organization and the scopes, and
   select **Allow access**.
4. Ask Claude "summarize our security posture" to confirm the tools respond.

## Connect Claude Code

Run this in a terminal:

```bash title="Terminal"
claude mcp add --transport http trustaige https://id.trustaige.com/mcp
```

Claude Code adds the server to the project you run it in. To use it in every
project on this computer, add `--scope user`.

1. Start Claude Code and run `/mcp`.
2. Select **trustaige** and choose **Authenticate**. The consent page opens in
   your browser.
3. Sign in with your passkey if you're asked, check the organization and the
   scopes, and select **Allow access**.

## Connect VS Code

Add the server to your `mcp.json`. The hub's **Enterprise-managed
authorization** section links here for this configuration, for an organization
whose people reach the server through a linked AI client (see
[Enterprise-managed authorization](#enterprise-managed-authorization)):

```json title="mcp.json"
{
  "servers": {
    "trustaige": {
      "type": "http",
      "url": "https://id.trustaige.com/mcp",
      "oauth": {
        "enterpriseManaged": true
      }
    }
  }
}
```

Without enterprise-managed authorization, VS Code is one of
[the other MCP clients](#connect-another-mcp-client): give it the server URL and
it discovers the rest, then opens the consent page.

## Connect another MCP client

Any client that follows the MCP authorization specification works with the
server URL alone. The hub lists this under **Other clients**. What the server
offers, for the engineer checking a client:

| Step | What the server does |
|---|---|
| Unauthenticated request | `401` with `WWW-Authenticate: Bearer resource_metadata="https://id.trustaige.com/.well-known/oauth-protected-resource/mcp"` |
| Protected resource metadata ([RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)) | `https://id.trustaige.com/.well-known/oauth-protected-resource/mcp`, naming the authorization server `https://id.trustaige.com/mcp/auth` |
| Authorization server metadata ([RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)) | `https://id.trustaige.com/.well-known/oauth-authorization-server/mcp/auth` (the path-insertion form) |
| Client registration | Dynamic registration ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)) at `https://id.trustaige.com/mcp/auth/register`, or a [client ID metadata document](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-02.html): an `https` URL as the `client_id` |
| Authorization | Authorization code with PKCE (`S256` only) at `https://id.trustaige.com/mcp/auth/authorize` |
| Tokens | `https://id.trustaige.com/mcp/auth/token`: `authorization_code` and `refresh_token` grants |
| Transport | Streamable HTTP at `https://id.trustaige.com/mcp`, `GET` and `POST`, the token in the `Authorization` header |

The details a client can trip on:

- **Redirect URIs.** A registration carries 1 to 10 `redirect_uris`. Plain
  `http` is accepted only for `localhost`, `127.0.0.1` and `::1`, and a loopback
  redirect matches whatever port the client opens at sign-in time. `javascript`,
  `data`, `file`, `blob` and `about` URIs are refused.
- **Registered clients are public.** A dynamic registration gets
  `token_endpoint_auth_method: none`; PKCE protects the code. A client published
  as a metadata document may instead declare `private_key_jwt` with its keys
  (`jwks` or `jwks_uri`), and then proves itself on every token request. A
  published document must not carry a `client_secret`, and its redirect URIs
  must match exactly.
- **Scopes.** Ask for any of `directory:read`, `devices:read` and
  `events:read`. A request with no `scope` asks for all three.
- **Resource.** If the client sends `resource`
  ([RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)), it must be
  `https://id.trustaige.com/mcp`.
- **Lifetimes.** Access tokens last one hour. Refresh tokens last 30 days and
  are replaced, along with the access token, every time they're used. A
  registration nobody has used for 90 days is removed; a client re-registers on
  its own.

## What the person approving sees

The consent page, titled **Connect AI client**, shows:

- **Trustaige ID · MCP access grant**, the organization's name, and
  **Directory MCP server · read-only**.
- The client's name (and the host of its web address, when it gives one), "is
  requesting a scoped connection to answer questions about
  your organization."
- A **Scope manifest**: one line per scope the client asked for.
- **Granted under the authority of** your email address.
- **Deny** and **Allow access**.

**Allow access** stamps the page **Authorized** and returns you to the client.
**Deny** returns you to the client with the request refused, and nothing is
created.

## Scopes and tools

The server has nine read-only tools. Each needs a scope, and each scope needs a
permission from the person who approves the connection:

| Scope | Consent page | Tools | The approver needs |
|---|---|---|---|
| None | | `whoami`: the authenticated member, organization, scopes and token expiry | |
| `directory:read` | **Directory**: Search members and list groups and their memberships | `find_member` (up to 25 matches by name, email or username), `list_groups` (with member counts), `member_groups` (by exact email) | **View users** |
| `devices:read` | **Devices**: View the device fleet and per-member compliance posture | `list_devices` (the first 500 devices), `member_devices` (by exact email) | **Manage devices** |
| `events:read` | **Audit events**: Read recent audit and security events | `security_events` (newest first: 50 by default, at most 100, from the last 30 days unless asked otherwise) | **View audit logs** |
| `directory:read` and `devices:read` | | `security_posture_summary`: members by role, fleet compliance, unassigned devices, group totals | Both of the above |

A superadmin can approve any scope. The approver's role is read again on every
request, so if their role later loses a permission, the connection loses that
scope at once, without being revoked. Answers only ever come from the
organization the connection was approved for.

## Enterprise-managed authorization

Standard connections ask each person to approve their own client. If your
organization runs its own AI client and signs people in to it through
Trustaige, you can link that client instead: the people signed in to it reach
the Directory with no consent screen, and each connection carries only the
scopes that person's role covers. It uses the Identity Assertion Authorization
Grant (ID-JAG), the basis of MCP's enterprise-managed authorization.

The **Enterprise-managed authorization** section appears on the hub's
**Trustaige Directory MCP** page when enterprise-managed authorization is also
switched on for your organization (it's in preview too).

### Link an AI client

The AI client must be an OpenID Connect app in your organization, turned on,
with its own secret or key. Public clients can't be linked: the client proves
itself to the Directory with the credentials it already uses with Trustaige.

1. Open **Apps → Trustaige Directory MCP** and scroll to
   **Enterprise-managed authorization**.
2. Under **Ready to link**, find the app and click **Link**. You'll be asked to
   confirm it's you.
3. The app moves to the list above, marked **Linked**.

To bind every grant to a key the app holds, open the app's **MCP** tab and turn
on **Require DPoP** under **As an AI client**. A copied grant is then useless to
anyone else.

### Get a token for a signed-in person

This is for the engineer who builds the linked client. Two requests, after the
person has signed in to your client through Trustaige:

1. **Exchange the person's ID token for an identity assertion grant**, at
   Trustaige's token endpoint, authenticating as your app the way it's set up
   to (a client secret is shown here; `client_secret_post` and
   `private_key_jwt` work too):

   ```bash title="1. Token exchange"
   curl https://id.trustaige.com/oauth2/token \
     -u "<your-client-id>:<your-client-secret>" \
     -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
     -d requested_token_type=urn:ietf:params:oauth:token-type:id-jag \
     -d subject_token_type=urn:ietf:params:oauth:token-type:id_token \
     -d "subject_token=<id_token>" \
     -d audience=https://id.trustaige.com/mcp/auth \
     -d resource=https://id.trustaige.com/mcp \
     --data-urlencode "scope=directory:read devices:read events:read"
   ```

   The reply's `access_token` is the grant, with `issued_token_type`
   `urn:ietf:params:oauth:token-type:id-jag`. It lasts five minutes.

2. **Redeem the grant at the Directory's token endpoint**, authenticating the
   same way:

   ```bash title="2. Redeem the grant"
   curl https://id.trustaige.com/mcp/auth/token \
     -u "<your-client-id>:<your-client-secret>" \
     -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
     -d "assertion=<id_jag>"
   ```

   The reply is an access token for `https://id.trustaige.com/mcp`, valid for
   one hour, with the scopes the person's role covers. There's no refresh
   token: when it expires, exchange a fresh ID token again, so Trustaige checks
   the person's standing each time.

What Trustaige checks:

- The ID token was issued to the client that's exchanging it, carries
  `auth_time`, and its sign-in session is still live. Once the person signs
  out, their ID tokens can't be exchanged.
- The person's membership is active.
- The client authenticates at the Directory with `client_secret_basic`,
  `client_secret_post` or `private_key_jwt` (an assertion addressed to
  `https://id.trustaige.com/mcp/auth` or its token endpoint).
- If you send a DPoP proof ([RFC 9449](https://www.rfc-editor.org/rfc/rfc9449))
  with the exchange, the grant is bound to that key; redeem it with a proof from
  the same key and the access token comes back as `token_type: DPoP`, to be
  sent with `Authorization: DPoP` and a proof on every call. With **Require
  DPoP** on, the exchange is refused without a proof.
- Each grant can be redeemed once.

In the hub, these connections show **Signed in through Trustaige as** the
person, with an **Enterprise-managed** badge, and **Key-bound** when DPoP is in
use.

### Your apps' own MCP servers

The same grants reach MCP servers other vendors run, when the vendor supports
enterprise-managed authorization. With it switched on, each app in the hub has
an **MCP** tab:

1. Click **Set up MCP governance** and paste the **MCP server URL**: the same
   URL you'd give an AI client.
2. Click **Resolve**. The hub reads the server's published metadata and fills
   in the **Authorization server** and **Resource identifier**.
3. Under **Allowed scopes**, keep only what your organization grants. Scopes
   the vendor adds later stay off until you allow them.
4. Add **Client-ID mappings** only if the MCP server knows your AI client by a
   different ID than Trustaige does.
5. Click **Save**. The tab now shows an **Issuer** under **Trusted at**,
   followed by the app's name. Add it as the identity provider's issuer in the
   vendor's enterprise-managed authorization settings. It belongs to your
   organization alone, so the vendor accepts grants for your people and no one
   else's.

Your AI client then exchanges ID tokens as above, with the server's
authorization server as `audience`. Unlike the Directory, a grant for another
vendor's server also needs the person to be assigned to the app, and passes
your access policies.

## Check that it works

- **In the client:** ask "summarize our security posture", or ask the client to
  run `whoami`. It answers with your email, roles, scopes, organization and when
  the token expires.
- **In Claude Code:** `claude mcp get trustaige` shows the server and whether
  it's connected.
- **In the hub:** **Apps → Trustaige Directory MCP → Connected AI clients**
  lists the connection as **active**, with **Authorized by** your name and
  **Last used**. The **connected** count at the top goes up by one.
- **Discovery, from any terminal:**

  ```bash title="Terminal"
  curl https://id.trustaige.com/.well-known/oauth-protected-resource/mcp
  ```

  ```json title="Response"
  {
    "resource": "https://id.trustaige.com/mcp",
    "authorization_servers": ["https://id.trustaige.com/mcp/auth"],
    "scopes_supported": ["directory:read", "devices:read", "events:read"],
    "bearer_methods_supported": ["header"]
  }
  ```

## Revoke a connection

1. Open **Apps → Trustaige Directory MCP** and find the connection under
   **Connected AI clients**.
2. Click **Revoke**, read what stops, and click **Revoke** again.

The client loses access at once and its tokens stop working. To connect again,
the person has to authorize it again. Suspending the person who approved a
connection ends it the same way.

To end a linked client's access, click **Unlink** beside it under
**Enterprise-managed authorization**, and confirm. Every connection it holds
ends at once, and it can't reach the Directory on anyone's behalf until you link
it again.

Removing the server from the client itself (for example
`claude mcp remove trustaige`) doesn't revoke anything in Trustaige; revoke it in
the hub too.

A connection shows **expired** when its client hasn't refreshed it for 30 days.
Connect again from the client.

## Troubleshooting

**“The Directory MCP server is not enabled for this organization.”**

The server is in preview and isn't switched on for the organization you're signed in to. Ask Trustaige to switch it on. If you belong to several organizations, check the consent page named the one you meant.

**“connecting an AI client requires admin permissions covering every requested scope”**

Your role doesn't cover every scope the client asked for. A client that doesn't name scopes asks for all three, which needs **View users**, **Manage devices** and **View audit logs**. Ask an administrator whose role covers them to approve the connection, or to give your role those permissions.

**The consent page names the wrong organization**

The connection goes to the organization you're signed in to in this browser. Click **Deny**, switch to the right organization in the hub, then start the connection again from your client.

**“This connection request is invalid or has expired.”**

The authorization request was malformed, or the client's registration is gone (registrations nobody uses for 90 days are removed). Start the connection again from the client. In Claude Code, remove the server and add it again.

**A tool says the connection “does not carry the … scope”**

The connection was approved without that scope, or the approver's role has since lost the permission behind it. Reconnect with that scope, or have an administrator whose role covers it approve the connection.

**The client stops working after it had been connected**

The connection was revoked, the person who approved it was suspended or left, or it expired after 30 days without a refresh. Look it up under **Connected AI clients**; if it's not **active**, connect again.

**“Only an AI client with its own secret or key can be linked.”**

The app is a public client. Only an OpenID Connect app with a client secret or keys can be linked. Apps that can be linked are listed under **Ready to link**.

**“That client is already registered with the Directory MCP server.”**

Another registration already uses that client ID. A client linked to another organization can't be linked to yours too.

**The token exchange fails with invalid_target**

The `error_description` says which: “enterprise-managed authorization is not enabled for this organization”, “the Directory MCP server is not enabled for this organization”, or “no enterprise-managed resource matches the requested audience” (for the Directory, `audience` must be `https://id.trustaige.com/mcp/auth`).

**The token exchange fails with invalid_grant: subject_session_ended or subject_token_stale**

The person's Trustaige sign-in has ended, or the ID token is older than their session can be. Sign them in again and exchange the new ID token. `subject_token_auth_time_missing` means the ID token has no `auth_time`.

**Redeeming the grant fails with “this client is not linked to the Directory MCP server”**

Link the app in the hub first, under **Enterprise-managed authorization → Ready to link**. If it says “this client is not linked for the organization that issued the grant”, the app is linked to a different organization than the person's.

**Redeeming the grant fails with “the member's role covers none of the requested scopes”**

The person's role has none of **View users**, **Manage devices** or **View audit logs**. Only people whose role covers at least one scope can reach the Directory.

**“this grant was already redeemed”**

Each grant redeems once. Exchange the ID token again for a new grant.

## Reference

| | |
|---|---|
| MCP server | `https://id.trustaige.com/mcp` |
| Protected resource metadata | `https://id.trustaige.com/.well-known/oauth-protected-resource/mcp` |
| Authorization server (issuer) | `https://id.trustaige.com/mcp/auth` |
| Authorization server metadata | `https://id.trustaige.com/.well-known/oauth-authorization-server/mcp/auth` |
| Registration | `https://id.trustaige.com/mcp/auth/register` |
| Authorization | `https://id.trustaige.com/mcp/auth/authorize` |
| Token | `https://id.trustaige.com/mcp/auth/token` |
| Scopes | `directory:read`, `devices:read`, `events:read` |
| Audit events | `mcp.client_authorized`, `mcp.grant_revoked`, `mcp.client_linked`, `mcp.client_unlinked` |

Standards: [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728),
[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414),
[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591),
[RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636),
[RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707),
[RFC 9449](https://www.rfc-editor.org/rfc/rfc9449),
[Identity Assertion Authorization Grant](https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/),
[Client ID metadata documents](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-02.html).
