---
title: Autonomous Agent Access | Keycard
description: Let agents call API-key-backed MCP servers through a Unified Access Gateway as themselves, with no user in the loop
---

Some agents do work of their own, with no person in the loop. Those agents can call MCP tools through a [Unified Access Gateway](/admin/unified-access-gateway/index.md) by authenticating as themselves: [Access Policies](/admin/access-policies/index.md) decide what an agent can reach, and each call is recorded in the audit log under the agent’s identity. For why an agent should have its own identity, see [Give an agent its own identity](/use-cases/agent-with-its-own-identity/index.md).

Not just agents

The same steps work for any headless workload, such as a CI job or a backend service.

API-key upstreams only

An agent acting as itself can only reach upstreams that Keycard holds a [vaulted credential](/concepts/resources/#vaulted-static-credentials/index.md) for, such as an MCP server that takes an API key. Upstreams that use per-user OAuth stay user-only: an agent’s request for one fails with `invalid_target`.

## How it works

The agent gets an access token from Keycard using its own identity (behind the scenes, the SDK uses the `client_credentials` grant) and sends it to the gateway’s MCP endpoint.

To keep credentials fully separated, there are two actors in the flow: the agent communicating with the gateway, and the gateway acting on behalf of the agent toward the upstream. On each request, the gateway exchanges the agent’s token with Keycard, which runs a policy check for each actor before the request is allowed through:

| Check            | Principal   | Context                      |
| ---------------- | ----------- | ---------------------------- |
| Subject check    | The agent   | `context.on_behalf == false` |
| Delegation check | The gateway | `context.on_behalf == true`  |

## Prerequisites

- **Manager** access to the Zone (see [Roles & Permissions](/admin/roles-and-permissions/index.md))
- The agent registered as an Application with a client credential (see [Applications](/concepts/applications/#credentials/index.md))
- The upstream MCP server registered as a Resource with a vaulted credential (see [Vaulted Static Credentials](/concepts/resources/#vaulted-static-credentials/index.md))
- A Unified Access Gateway with the API-key MCP server attached as a dependency (see [Unified Access Gateway](/admin/unified-access-gateway/index.md))

## Give an agent access

Access is configured entirely through [Access Policies](/admin/access-policies/index.md): a Policy permits the agent for the gateway it connects through and for each upstream it may reach.

1. **Author the Policy**

   Permit the agent for the gateway and each API-key upstream, scoped to the agent acting as itself (`context.on_behalf` and `context.impersonate` both `false`):

   ```
   @id("nightly-report-agent-access")
   @description("Permit the reporting agent the gateway and its API-key upstream")
   permit (
     principal is Keycard::Application,
     action,
     resource
   ) when {
     principal.identifier == "<agent-app-identifier>" &&
     ["<gateway-mcp-url>", "<upstream-mcp-url>"].contains(resource.identifier) &&
     context.on_behalf == false &&
     context.impersonate == false
   };
   ```

   An agent permitted for the gateway but not for an upstream connects successfully and is denied that upstream’s tools.

2. **Add it to your policy set**

   Add the new Policy to your policy set and activate the set. See [Access Policies](/admin/access-policies/index.md) for authoring and activating policy sets.

   Note

   If you don’t have a custom policy set yet, create one containing the [managed default policies](/admin/access-policies/#managed-policies/index.md) plus the new Policy, then activate it. The defaults keep existing user access working and cover the gateway’s own checks.

## Connect the agent

Copy the gateway’s **MCP Access URL** from its **Application settings** page and your Zone’s **Issuer URL** from **Settings** → **Connection**. The SDK handles authentication for the agent and brokers a token for the gateway. The agent then calls the gateway like any MCP server. This example uses the Keycard LangChain SDK:

```
import os


import httpx
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client


from keycardai.langchain import Access, KeycardGrantMiddleware


GATEWAY_URL = "<gateway-mcp-url>"


keycard = KeycardGrantMiddleware(
    zone_url=os.environ["KEYCARD_ISSUER"],
    resources=[GATEWAY_URL],
    client_id=os.environ["KEYCARD_CLIENT_ID"],
    client_secret=os.environ["KEYCARD_CLIENT_SECRET"],
)




async def list_gateway_tools():
    with keycard.grant(Access.as_self(), resources=[GATEWAY_URL]) as access:
        token = access.access(GATEWAY_URL).access_token


    http_client = httpx.AsyncClient(headers={"Authorization": f"Bearer {token}"})
    async with http_client, streamable_http_client(GATEWAY_URL, http_client=http_client) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            return (await session.list_tools()).tools
```

There is no sign-in and no authorization prompt: your Policies already authorized the access.

## Verify

In Keycard Console, open the agent Application’s page and select the **Activity** tab: each run shows the credential issuance events for the gateway and the upstream, with no user attached. See [Read an Activity Feed](/admin/activities/index.md) for how to read the feed.

## Revoke access

Remove the agent’s Policy from your policy set, or remove the upstream from that Policy if the agent should keep its other access. The gateway checks policy on every request, so the agent’s very next request is rejected. There is nothing to revoke. The denial shows up in the Audit Log as a denied exchange.

Revoking a user’s access works through grants instead. See [Revoke a Grant](/admin/revoke-a-grant/index.md).

## Troubleshooting

- **The token exchange fails with `invalid_target`.** Either the upstream uses per-user OAuth (which stays on the user path), or the Resource has no vaulted credential yet. For the latter, add one on the Resource with **Add credential**.
- **The agent gets a token but an upstream’s tools are missing or denied.** No Policy permits the agent for that upstream. Being permitted for the gateway is not enough. Add the upstream to the agent’s Policy, then retry. See [Diagnosing policy-blocked actions](/admin/access-policies/#diagnosing-policy-blocked-actions/index.md).
- **After activating a custom policy set, an upstream is unreachable through the gateway despite an agent permit.** The set is missing the managed [`default-app-direct-access`](/admin/access-policies/#managed-policies/index.md) policy, which the gateway needs for its own access to the upstream. The denial appears in the audit log with the gateway as principal. Add the managed policy back to the set.
