Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion assets/agw-docs/pages/agentgateway/mcp/mcp-auth-about.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@ The MCP OAuth specification extends the standard OAuth 2.0 Authorization Code Fl

## Challenges

Learn about some common challenges with MCP auth.

### Non-compliance to MCP OAuth spec {#non-compliance}

Most IdPs do not comply to the MCP OAuth specification and therefore do not expose the resource and authorization server metadata in a format that an MCP client can understand. Because of that, clients cannot dynamically register with the IdP to obtain their client ID. Instead, they must manually register with each IdP that they want to use. In cases where MCP clients, IdPs, and MCP servers are not in the same environment or are owned by different teams and organizations, pre-registration of clients can become unfeasible.

### Connect-time authentication {#connect-time}

When MCP clients connect directly to remote MCP servers, each server handles its own OAuth independently. A developer working in an IDE like Cursor or Claude Code might have their agent call a tool, only for an OAuth pop-up to interrupt the session. The developer loses context, and faces another pop-up when the agent calls a tool on a different server. Each server might use a different identity provider, and none of them enforce corporate Single Sign-On (SSO).

## Agentgateway to fill in the gaps

Instead of pre-registering MCP clients, you can use agentgateway to register MCP clients dynamically with your IdP. The agentgateway proxy implements the MCP OAuth 2.0 specification, and can therefore facilitate the client registration process on behalf of the MCP client by translating the MCP OAuth information into configuration that the IdP understands.
Expand All @@ -18,7 +26,7 @@ The MCP OAuth flow that is facilitated by the agentgateway proxy includes the fo
1. **Initialization**: In the initialization phase, the MCP client tries to connect to a protected MCP server. This connection fails with a 401 HTTP response.
2. **Discovery**: The MCP client discovers the OAuth authorization server that protects the MCP server and required scopes to access the MCP server by using agentgateway.
3. **Client registration**: The agentgateway proxy registers the client with the IdP and returns the client ID.
4. **Authentication**: The MCP client is redirected to the IdP for login. After successful login, the client receives a JWT access token.
4. **Authentication**: The MCP client is redirected to the IdP for login. After successful login, the client receives a JWT access token. This authentication happens at "connect time" with agentgateway, not each "request time" when the client calls a tool. This type of connection is sometimes called "eager auth."
5. **MCP server access**: The client uses the JWT token to access the MCP server and its tools.

Review the following diagram to learn about the steps that are involved in each phase:
Expand Down Expand Up @@ -71,6 +79,14 @@ sequenceDiagram

For more information, see the [JWT auth docs]({{< link-hextra path="/mcp/mcp-access/">}}).

### Benefits

Agentgateway's approach provides the following benefits.

- **No mid-session interruptions**: Tool calls succeed immediately because the client already authenticated when it connected.
- **Single sign-on**: One login through your enterprise IdP grants access to all MCP servers behind the gateway.
- **Consistent policy enforcement**: Authentication and authorization policies are applied uniformly at the gateway, rather than depending on each MCP server to implement its own security.

## Setup

Try out MCP auth with Keycloak and a sample MCP server. Start with [setting up Keycloak]({{< link-hextra path="/mcp/auth/keycloak/" >}}).
82 changes: 78 additions & 4 deletions assets/agw-docs/pages/agentgateway/mcp/mcp-auth-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ For more information, see the [JWT auth docs]({{< link-hextra path="/mcp/mcp-acc
2. Follow the steps to set up an [MCP server with a fetch tool]({{< link-hextra path="/mcp/static-mcp/" >}}).
3. Follow the steps to [set up Keycloak]({{< link-hextra path="/mcp/auth/keycloak/" >}}).
4. Install the experimental channel Gateway API.
```sh
```sh {paths="mcp-auth-setup"}
kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v{{< reuse "agw-docs/versions/k8s-gw-version.md" >}}/experimental-install.yaml
```

Expand All @@ -23,7 +23,7 @@ For more information, see the [JWT auth docs]({{< link-hextra path="/mcp/mcp-acc
With Keycloak deployed and your MCP backend configured, you can now create an {{< reuse "agw-docs/snippets/trafficpolicy.md" >}} that enforces authentication for the MCP backend.

1. Create an {{< reuse "agw-docs/snippets/trafficpolicy.md" >}} with MCP authentication configuration.
```yaml
```yaml {paths="mcp-auth-setup"}
kubectl apply -f - <<EOF
apiVersion: {{< reuse "agw-docs/snippets/trafficpolicy-apiversion.md" >}}
kind: {{< reuse "agw-docs/snippets/trafficpolicy.md" >}}
Expand Down Expand Up @@ -87,12 +87,32 @@ With Keycloak deployed and your MCP backend configured, you can now create an {{
| `resourceMetadat.bearerMethodsSupported` | Methods to provide the bearer token when authenticating with the server. In this example, the bearer token can be provided as a header, query parameter, or as part of the body. |

2. Verify that the policy was accepted.
```sh
```sh {paths="mcp-auth-setup"}
kubectl get {{< reuse "agw-docs/snippets/trafficpolicy.md" >}} mcp-echo-authn -o yaml
```

{{< doc-test paths="mcp-auth-setup" >}}
YAMLTest -f - <<'EOF'
- name: wait for mcp-echo-authn policy to be accepted
wait:
target:
apiVersion: agentgateway.dev/v1alpha1
kind: AgentgatewayPolicy
metadata:
namespace: default
name: mcp-echo-authn
jsonPath: "$.status.ancestors[0].conditions[?(@.type=='Accepted')].status"
jsonPathExpectation:
comparator: equals
value: "True"
polling:
timeoutSeconds: 60
intervalSeconds: 2
EOF
{{< /doc-test >}}

3. Update the HTTPRoute that routes incoming traffic to the MCP server to include the discovery paths for the MCP resource and authorization server. This way, the agentgateway proxy can retrieve the resource and authorization server metadata during the MCP auth flow.
```yaml
```yaml {paths="mcp-auth-setup"}
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
Expand Down Expand Up @@ -149,6 +169,51 @@ With Keycloak deployed and your MCP backend configured, you can now create an {{
EOF
```

{{< doc-test paths="mcp-auth-setup" >}}
YAMLTest -f - <<'EOF'
- name: wait for mcp HTTPRoute to be accepted
wait:
target:
kind: HTTPRoute
metadata:
namespace: default
name: mcp
jsonPath: "$.status.parents[0].conditions[?(@.type=='Accepted')].status"
jsonPathExpectation:
comparator: equals
value: "True"
polling:
timeoutSeconds: 60
intervalSeconds: 2
- name: unauthenticated MCP request returns 401 (connect-time auth enforced)
http:
url: "http://${INGRESS_GW_ADDRESS}:80/mcp"
method: GET
source:
type: local
expect:
statusCode: 401
headers:
- name: www-authenticate
comparator: contains
value: resource_metadata
retries: 3
- name: resource metadata discovery returns 200
http:
url: "http://${INGRESS_GW_ADDRESS}:80/.well-known/oauth-protected-resource/mcp"
method: GET
source:
type: local
expect:
statusCode: 200
bodyJsonPath:
- path: "$.resource"
comparator: contains
value: "/mcp"
retries: 3
EOF
{{< /doc-test >}}

## Verify MCP auth

1. Open the MCP inspector.
Expand Down Expand Up @@ -197,6 +262,15 @@ With Keycloak deployed and your MCP backend configured, you can now create an {{
{{< reuse-image src="img/oauth-connect-to-server.png" >}}
{{< reuse-image-dark srcDark="img/oauth-connect-to-server-dark.png" >}}

5. Verify that tool calls work without re-authentication. Because the client authenticates at connect time, tool calls succeed immediately without any additional login prompts.
1. From the menu bar, click the **Tools** tab.
2. Click **List Tools** and select the `fetch` tool.
3. In the **url** field, enter a website URL, such as `https://example.com/`.
4. Click **Run Tool**.
5. Verify that the tool call succeeds and returns the fetched content. No additional authentication is required because the token from the initial connection is reused for all tool calls within the session.

{{< reuse-image src="img/mcp-inspector-fetch.png" >}}
{{< reuse-image-dark srcDark="img/mcp-inspector-fetch-dark.png" >}}

## Clean up

Expand Down
51 changes: 42 additions & 9 deletions assets/agw-docs/snippets/keycloak.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,66 @@ The following steps install Keycloak in your cluster, and configure two user cre
Install and configure Keycloak:

1. Create a namespace for your Keycloak deployment.
```shell
```shell {paths="setup-keycloak"}
kubectl create namespace keycloak
```
2. Create the Keycloak deployment.
```shell
```shell {paths="setup-keycloak"}
kubectl -n keycloak apply -f https://raw.githubusercontent.com/solo-io/gloo-mesh-use-cases/main/policy-demo/oidc/keycloak.yaml
```
3. Wait for the Keycloak rollout to finish.
```shell
```shell {paths="setup-keycloak"}
kubectl -n keycloak rollout status deploy/keycloak
```

{{< doc-test paths="setup-keycloak" >}}
YAMLTest -f - <<'EOF'
- name: wait for keycloak deployment to be ready
wait:
target:
kind: Deployment
metadata:
namespace: keycloak
name: keycloak
jsonPath: "$.status.availableReplicas"
jsonPathExpectation:
comparator: greaterThan
value: 0
polling:
timeoutSeconds: 120
intervalSeconds: 5
- name: wait for keycloak service LB address
wait:
target:
kind: Service
metadata:
namespace: keycloak
name: keycloak
jsonPath: "$.status.loadBalancer.ingress[0].ip"
jsonPathExpectation:
comparator: exists
polling:
timeoutSeconds: 300
intervalSeconds: 5
EOF
{{< /doc-test >}}

4. Set the Keycloak endpoint details from the load balancer service. If you are running locally in kind and need a local IP address for the load balancer service, consider using [`cloud-provider-kind`](https://github.com/kubernetes-sigs/cloud-provider-kind).
```shell
```shell {paths="setup-keycloak"}
export ENDPOINT_KEYCLOAK=$(kubectl -n keycloak get service keycloak -o jsonpath='{.status.loadBalancer.ingress[0].ip}{.status.loadBalancer.ingress[0].hostname}'):8080
export HOST_KEYCLOAK=$(echo ${ENDPOINT_KEYCLOAK} | cut -d: -f1)
export PORT_KEYCLOAK=$(echo ${ENDPOINT_KEYCLOAK} | cut -d: -f2)
export KEYCLOAK_URL=http://${ENDPOINT_KEYCLOAK}
echo $KEYCLOAK_URL
```
5. Set the Keycloak admin token. If you see a parsing error, try running the `curl` command by itself. You might notice that your internet provider or network rules are blocking the requests. If so, you can update your security settings or change the network so that the request can be processed.
```shell
```shell {paths="setup-keycloak"}
export KEYCLOAK_TOKEN=$(curl -d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" | jq -r .access_token)
echo $KEYCLOAK_TOKEN
```

6. Use the admin token to configure Keycloak with the two users for testing purposes. If you get a `401 Unauthorized` error, run the previous command and try again.
```shell
```shell {paths="setup-keycloak"}
# Create initial token to register the client
read -r client token <<<$(curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"expiration": 0, "count": 1}' $KEYCLOAK_URL/admin/realms/master/clients-initial-access | jq -r '[.id, .token] | @tsv')
export KEYCLOAK_CLIENT=${client}
Expand Down Expand Up @@ -107,18 +140,18 @@ You might integrate OIDC with your apps. In such cases, you might need particula
The following instructions assume that you are still logged into the **Administration Console** from the previous step.

1. Confirm that you have the following environmental variables set. If not, refer to [Step 1: Install Keycloak](#install) section.
```shell
```shell {paths="setup-keycloak"}
echo $KEYCLOAK_URL
```

2. Get the issuer and JWKS path. The agentgateway proxy uses these values to validate the JWTs.
1. From the sidebar menu options, click **Realm Settings**.
2. From the **General** tab, scroll down to the **Endpoints** section and open the **OpenID Endpoint Configuration** link. In a new tab, your browser opens to a URL similar to `http://$KEYCLOAK_URL:8080/realms/master/.well-known/openid-configuration`.
3. In the OpenID configuration, search for the `issuer` field. Save the value as an environment variable, such as the following example.
```sh
```sh {paths="setup-keycloak"}
export KEYCLOAK_ISSUER=$KEYCLOAK_URL/realms/master
```
4. In the OpenID configuration, search for the `jwks_uri` field, and copy the path without the Keycloak URL that you retrieved earlier. For example, the path might be set to `/realms/master/protocol/openid-connect/certs`.
```shell
```shell {paths="setup-keycloak"}
export KEYCLOAK_JWKS_PATH=/realms/master/protocol/openid-connect/certs
```
12 changes: 12 additions & 0 deletions content/docs/kubernetes/latest/mcp/auth/setup.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
---
title: Set up MCP auth
weight: 40
test:
mcp-auth-setup:
- file: content/docs/kubernetes/latest/install/helm.md
path: experimental
- file: content/docs/kubernetes/latest/setup/gateway.md
path: all
- file: content/docs/kubernetes/latest/mcp/static-mcp.md
path: setup-mcp-server
- file: content/docs/kubernetes/latest/mcp/auth/keycloak.md
path: setup-keycloak
- file: content/docs/kubernetes/latest/mcp/auth/setup.md
path: mcp-auth-setup
---

{{< reuse "agw-docs/pages/agentgateway/mcp/mcp-auth-setup.md" >}}
12 changes: 12 additions & 0 deletions content/docs/kubernetes/main/mcp/auth/setup.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
---
title: Set up MCP auth
weight: 40
test:
mcp-auth-setup:
- file: content/docs/kubernetes/main/install/helm.md
path: experimental
- file: content/docs/kubernetes/main/setup/gateway.md
path: all
- file: content/docs/kubernetes/main/mcp/static-mcp.md
path: setup-mcp-server
- file: content/docs/kubernetes/main/mcp/auth/keycloak.md
path: setup-keycloak
- file: content/docs/kubernetes/main/mcp/auth/setup.md
path: mcp-auth-setup
---

{{< reuse "agw-docs/pages/agentgateway/mcp/mcp-auth-setup.md" >}}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Attach to:

MCP authentication enables OAuth 2.0 protection for MCP servers, helping to implement the [MCP Authorization specification](https://modelcontextprotocol.io/specification/draft/basic/authorization). Agentgateway can act as a resource server, validating JWT tokens and exposing protected resource metadata.

MCP authentication uses a connect-time model: the OAuth flow happens once when the client first connects, not on each tool call. After the initial authentication, the access token is reused for all subsequent requests within the session.

> [!NOTE]
> {{< reuse "agw-docs/snippets/mcp-policy-note.md" >}}

Expand Down
2 changes: 2 additions & 0 deletions content/docs/standalone/latest/mcp/mcp-authn.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ prev: /mcp/connect

MCP authentication enables OAuth 2.0 protection for MCP servers, helping to implement the [MCP Authorization specification](https://modelcontextprotocol.io/specification/draft/basic/authorization). Agentgateway can act as a resource server, validating JWT tokens and exposing protected resource metadata.

MCP authentication uses a connect-time model: the OAuth flow happens once when the client first connects, not on each tool call. This type of connection is sometimes called "eager auth." After the initial authentication, the access token is reused for all subsequent requests within the session. For more information, see [About MCP auth]({{< link-hextra path="/mcp/auth/about/" >}}).

There are three deployment scenarios.

## Authorization Server Proxy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Attach to:

MCP authentication enables OAuth 2.0 protection for MCP servers, helping to implement the [MCP Authorization specification](https://modelcontextprotocol.io/specification/draft/basic/authorization). Agentgateway can act as a resource server, validating JWT tokens and exposing protected resource metadata.

MCP authentication uses a connect-time model: the OAuth flow happens once when the client first connects, not on each tool call. After the initial authentication, the access token is reused for all subsequent requests within the session.

> [!NOTE]
> {{< reuse "agw-docs/snippets/mcp-policy-note.md" >}}

Expand Down
2 changes: 2 additions & 0 deletions content/docs/standalone/main/mcp/mcp-authn.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ prev: /mcp/connect

MCP authentication enables OAuth 2.0 protection for MCP servers, helping to implement the [MCP Authorization specification](https://modelcontextprotocol.io/specification/draft/basic/authorization). Agentgateway can act as a resource server, validating JWT tokens and exposing protected resource metadata.

MCP authentication uses a connect-time model: the OAuth flow happens once when the client first connects, not on each tool call. This type of connection is sometimes called "eager auth." After the initial authentication, the access token is reused for all subsequent requests within the session. For more information, see [About MCP auth]({{< link-hextra path="/mcp/auth/about/" >}}).

There are three deployment scenarios.

## Authorization Server Proxy
Expand Down
Loading