# The MCP gateway

Put your tool servers behind your project's MCP gateway, grant tools to AI agents one by one, and see what the gateway checks on every call.

Your project has a gateway for the Model Context Protocol (MCP), the protocol AI agents use to call tools. It is at `https://acme.authfi.io/mcp`. Agents connect to it instead of to each tool server, and the gateway decides, call by call, whether this agent may call this tool with these arguments.

This guide shows you how to connect a tool server and grant its tools. It then describes what the gateway checks, returns and records. For a first call, start with [Connect agents to tools](/quickstart/mcp).

## Before you begin

You need:

- Administrator access to the AuthFI console.
- A registered agent with an access token for the gateway. See [Give an AI agent an identity](/quickstart/agent).
- The URL of the tool server, and the bearer token it expects if it needs one.

<Steps>

### Connect a tool server

1. In the console, open **Integrations > MCP Gateway** and select the **Servers** tab.
2. Choose **Connect a server**.
3. Fill in the dialog, then choose **Register**.

| Field                            | Enter                                                                       |
| -------------------------------- | --------------------------------------------------------------------------- |
| Namespace                        | One short name: letters, digits and dashes. It prefixes every tool's name.  |
| Server URL                       | Where the tool server is reached, for example `https://mcp.example.com/mcp` |
| Transport                        | "Streamable HTTP", "SSE" or "Agent2Agent"                                   |
| How it authenticates the gateway | "Bearer token" or "No credential"                                           |
| Whose credential                 | "One shared credential" or "Each user's own". Asked for a bearer token.     |
| Credential                       | The token the tool server expects. Asked for one shared credential.         |

The console confirms that the server is registered. The gateway then reads the server's tools and lists each one as `namespace.tool`, for example `github.create_issue`. You see them on the **Tools** tab.

### Grant tools

Nothing is callable until it is granted.

1. On the **Tools** tab, choose **Say who may call a tool**.
2. Choose the kind of caller: "An AI agent", "A person" or "A group".
3. Under "Caller", choose the agent. Only active agents are listed.
4. Under "May call", tick the tools. Use "Filter tools" to find them.
5. Choose **Grant 1 tool**. The button counts the tools you ticked.

The console confirms that the caller may now call the tools. On the **Tools** tab, the "Who may call it" column shows the grant.

A grant is on one tool. Granting `github.create_issue` does not grant `github.delete_repo`. A grant to a [group](/concepts/access) reaches every member of it, now and later.

You can also grant from the agent's own page: open the agent under **Identity > AI Agents** and choose **Give it a tool**.

### Point the agent at the gateway

On the **Overview** tab, find "Where the agents connect" and copy the "Gateway" URL. For the example project it is `https://acme.authfi.io/mcp`. Configure the agent to call that URL, not the tool server.

</Steps>

## Check that it works

List the tools as the agent. The gateway takes JSON-RPC over HTTP `POST`, with the agent's access token as a Bearer credential.

<Endpoint method="POST" path="/mcp" />

```bash
curl https://acme.authfi.io/mcp   -H "Authorization: Bearer $ACCESS_TOKEN"   -H "Content-Type: application/json"   -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

`$ACCESS_TOKEN` is the agent's access token. The result contains the tools of every server you added. Each tool looks like this:

```json
{
  "name": "payments.refund",
  "description": "Refund a payment",
  "inputSchema": { "type": "object" },
  "annotations": { "io.authfi/governance": "may_require_approval" }
}
```

A tool this agent may call is marked `no_policy_restriction`.

After the agent calls a tool, open the **Tape** tab. The call is listed with its "Caller", "Tool" and "Verdict".

## How tools are marked in the list

`tools/list` returns every tool, each marked with what would happen if this agent called it.

| `io.authfi/governance`  | Meaning                                                  |
| ----------------------- | -------------------------------------------------------- |
| `no_policy_restriction` | This agent may call it                                   |
| `may_require_approval`  | A person may have to approve, depending on the arguments |
| `never_allowed`         | This agent's call is denied                              |

An agent can use the mark to avoid tools it cannot call. The decision itself is made on each call, with its arguments.

## What is checked on a call

For every `tools/call`, the gateway checks, in order:

1. The token is a valid agent access token for this project.
2. The agent has not been revoked.
3. The agent is granted this tool.
4. If the agent is acting for a person, that person is granted it too.
5. The arguments pass the project's [rules](/agents/limits#add-a-rule-for-the-whole-project).

Only then does the gateway forward the call, with the tool server's own credential.

## When a call is denied

A denied call is an ordinary tool result with `isError` set, so the model reads it and can change course.

```json
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "isError": true,
    "content": [{ "type": "text", "text": "Denied by policy: agent:agt_7f3a9c21 is not granted payments.refund" }]
  }
}
```

## When a call needs approval

If a rule sends the call to a person, the result says so. It carries an approval ID and tells the model how to continue.

```json
{
  "result": {
    "isError": true,
    "content": [
      {
        "type": "text",
        "text": "Approval required. A human must approve this action in the AuthFI console (approval 3f0c2b6e-…). Retry this call with an argument \"_authfi_approval_id\": \"3f0c2b6e-…\" once it is approved."
      }
    ]
  }
}
```

A person approves or denies the request on the **Approvals** page of the console. The agent then makes the same call again and adds the approval ID:

```json
{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "tools/call",
  "params": {
    "name": "payments.refund",
    "arguments": { "payment": "pay_4821", "amount": 1200, "_authfi_approval_id": "3f0c2b6e-…" }
  }
}
```

The gateway checks the approval, removes `_authfi_approval_id`, and forwards the call. The tool server never sees the approval ID. An approval is good for 15 minutes and for that call.

For approvals outside the gateway, see [Ask before a sensitive action](/agents/approvals).

## How credentials are kept

The tool server's credential is sealed when you enter it and is never shown again. The gateway attaches it to each call it forwards. The agent never holds it, so the agent has nothing to leak, and revoking the agent needs no change at the tool server.

With "Each user's own" credential, each person connects their own account. On the **Servers** tab, the person chooses **Connect my account** on the server, enters "Your token" and chooses **Connect**. The gateway then makes each call with the account of the person the agent is acting for. A call with no person, or for a person who has not connected their account, is denied.

## How an agent signs in to the gateway

An agent sends its access token as a Bearer credential. An agent access token requested with no `resource` is issued for the gateway by default.

A request with no token, or with a token that is expired or revoked, gets a `401`, as [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) describes:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://acme.authfi.io/.well-known/oauth-protected-resource"
```

The document at that URL names the gateway and where to get a token for it:

```json
{ "resource": "https://acme.authfi.io/mcp", "authorization_servers": ["https://acme.authfi.io"] }
```

## Protocol reference

| Property  | Value                                    |
| --------- | ---------------------------------------- |
| Transport | JSON-RPC 2.0 over HTTP `POST` to `/mcp`  |
| Revision  | `2025-06-18`                             |
| Methods   | `initialize`, `tools/list`, `tools/call` |

| JSON-RPC error | Meaning                              |
| -------------- | ------------------------------------ |
| `-32700`       | The body is not valid JSON           |
| `-32601`       | The method is not one of the three   |
| `-32602`       | The parameters are wrong             |
| `-32000`       | The tool server could not be reached |

## What is recorded

Every call, allowed or denied, is an entry in the [audit record](/security/audit). The entry holds the agent, the person it acted for, the tool, the decision and its reason.

The **Tape** tab of **Integrations > MCP Gateway** lists the last 100 calls.

Its columns are "When", "Caller", "Tool", "Verdict", "Why" and "Acted for". Filter the list with "every call", "refused" or "allowed".

## Troubleshooting

| What you see                                                | Cause                                                          | Fix                                               |
| ----------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------- |
| `401` with a `WWW-Authenticate` header                      | The token is missing, expired or revoked                       | Sign in again and send the new access token       |
| A result that says the agent is not granted the tool        | The agent has no grant on this tool                            | Grant it with **Say who may call a tool**         |
| A denied call to a server with "Each user's own" credential | The call has no person, or the person has no account connected | The person chooses **Connect my account**         |
| JSON-RPC error `-32000`                                     | The tool server could not be reached                           | Check that the server answers at its "Server URL" |
| JSON-RPC error `-32601`                                     | The method is not `initialize`, `tools/list` or `tools/call`   | Use one of the three methods                      |

## Next steps

<Cards>
  <Card href="/quickstart/mcp" title="Connect agents to tools">
    Make a first tool call through the gateway.
  </Card>
  <Card href="/agents/limits" title="Limit what an agent may do">
    Write the rules the gateway applies to a call's arguments.
  </Card>
  <Card href="/agents/approvals" title="Ask before a sensitive action">
    How a person approves, and what an approval records.
  </Card>
  <Card href="/agents/revocation" title="Revoke an agent">
    Stop an agent's tool calls at once.
  </Card>
</Cards>
