使用 Quarkus MCP 客户端从命令行访问安全的 MCP HTTP 服务器

简介

使用 Quarkus MCP 客户端访问安全的 MCP HTTP 服务器这篇博客文章中,我们解释了用户如何使用 GitHub OAuth2 登录到 Quarkus LangChain4j AI 服务器应用程序,并让 Google AI Gemini 使用Quarkus MCP 客户端来访问安全的Quarkus MCP 服务器用户名提供程序工具,并使用 GitHub 访问令牌。

然而,并非每个 AI 服务应用程序都设计为需要用户登录:例如,它可能作为命令行应用程序或 cron 调度程序运行。此外,并非每个需要用户登录的 AI 服务应用程序都能够使用用户登录令牌来访问安全的 MCP 服务器,因为此类服务器可能只支持不同类型的令牌。

在这篇博文中,我们将解释运行在命令行 Quarkus LangChain4j AI 应用程序中的 Quarkus MCP 客户端本身如何使用 OAuth2 client_credentials 授权获取访问令牌,并使用它来访问安全的 Quarkus MCP 服务器服务帐户名称提供程序工具。

我们将使用 Keycloak,并依靠它来演示如何通过限制访问令牌到特定受众,以及交换它们以获取新的、正确的受众,来保护可能跨越多个安全边界的复杂、分布式 AI 应用程序。

演示架构

Command Line Poem Service Architecture

如上图所示,命令行代理使用 Poem Service AI 服务来创作诗歌。Poem Service 使用 AI Gemini 并请求 MCP Client 完成工具调用,以帮助 AI Gemini 找出服务帐户名称。

MCP 客户端必须使用访问令牌。它使用 OAuth2 client_credential 授权来获取服务帐户令牌,并将其传播到安全的 MCP 服务器。此服务帐户令牌的受众将其限制为仅访问 MCP 服务器。

MCP 服务器工具实现必须访问 REST 服务器才能完成工具操作。但是,它不能使用当前的访问令牌,该令牌被限制为访问此 MCP 服务器,因为 REST 服务器只接受旨在访问此 REST 服务器的令牌。

因此,MCP 服务器在传播当前令牌之前,会交换当前令牌以设置 REST 服务器受众,REST 服务器成功完成安全工具调用,并将响应返回给 MCP 客户端。

我们现在准备好开始处理演示。

您可以在 Quarkus LangChain4j 命令行安全 MCP 客户端服务器示例中找到完整的项目源代码。

步骤 1 - 创建并启动 MCP 服务器

首先,让我们创建一个安全的 Quarkus MCP SSE 服务器,该服务器可以强制对工具进行身份验证访问,验证访问令牌是否具有正确的受众,并通过将当前访问令牌交换为具有 REST 服务器受众的新访问令牌,并将此令牌传播到 REST 服务器以获取所需的服务帐户名称来完成工具操作。

MCP 服务器 Maven 依赖

添加以下依赖项

<dependency>
    <groupId>io.quarkiverse.mcp</groupId>
    <artifactId>quarkus-mcp-server-sse</artifactId> (1)
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc</artifactId> (2)
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest</artifactId> (3)
</dependency>
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client-oidc-token-propagation</artifactId> (4)
</dependency>
1 quarkus-mcp-server-sse 是支持 MCP 可流式 HTTP 和 SSE 传输所必需的。
2 quarkus-oidc 是保护对 MCP SSE 端点的访问所必需的。它的版本在 Quarkus BOM 中定义。
3 quarkus-rest 是支持 MCP 工具必须调用的 REST 服务器所必需的。它的版本在 Quarkus BOM 中定义。
4 quarkus-rest-client-oidc-token-propagation 还引入了 quarkus-rest-client,并且是支持使用令牌交换和传播对 REST 服务器进行 REST 客户端调用所必需的。它的版本在 Quarkus BOM 中定义。

MCP 服务帐户名称工具

让我们创建一个可以返回当前服务帐户名称的工具。

package io.quarkiverse.langchain4j.sample;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import io.quarkiverse.mcp.server.TextContent;
import io.quarkiverse.mcp.server.Tool;
import jakarta.inject.Inject;

public class ServiceAccountNameProvider { (1)

    @RestClient
    @Inject
    ServiceAccountNameRestClient serviceAccountNameRestClient; (2)

    @Tool(name = "sevice-account-name-provider", description = "Provides a name of the current service account") (1)
    TextContent provideServiceAccountName() {
        return new TextContent(serviceAccountNameRestClient.getServiceAccountName()); (2)
    }
}
1 提供一个可以返回当前服务帐户名称的工具。
2 使用注入的 ServiceAccountNameRestClient 来访问 REST 服务器以完成服务帐户名称请求。有关更多详细信息,请参见下面的 服务帐户名称 REST 客户端部分。

只有在当前 MCP 请求通过身份验证后,才能调用 MCP 服务器工具。

在这篇博文中,我们不使用 @PermissionAllowed@Authenticated 等注解来强制执行安全工具访问,而仅使用 HTTP 安全策略配置。

请参阅下面的 MCP 服务器配置部分,了解如何保护主 MCP SSE 和工具端点。

服务帐户名称 REST 客户端

MCP 服务帐户名称工具使用服务帐户名称 REST 客户端来调用 REST 服务器,以完成服务帐户名称请求。

此 REST 客户端如下所示

package io.quarkiverse.langchain4j.sample;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.token.propagation.common.AccessToken;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Produces;

@RegisterRestClient
@AccessToken (2)
public interface ServiceAccountNameRestClient {

    @GET
    @Produces("text/plain")
    String getServiceAccountName(); (1)
}
1 从 REST 服务器获取服务帐户名称。有关更多详细信息,请参见下面的 服务帐户名称 REST 服务器部分。
2 使用 @AccessToken 注解来要求访问令牌交换和传播。这个单独的 @AccessToken 注解,由下面的 MCP 服务器配置部分中的附加配置支持,是支持这种复杂的访问令牌流所需要的全部内容。

服务帐户名称 REST 服务器

MCP 服务帐户名称工具使用服务帐户名称 REST 客户端从服务帐户名称 REST 服务器获取服务帐户名称。

此 REST 服务器如下所示

package io.quarkiverse.langchain4j.sample;

import io.quarkus.security.Authenticated;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

@Path("/service-account-name")
public class ServiceAccountNameRestServer {

    @Inject
    SecurityIdentity securityIdentity;

    @GET
    @Produces("text/plain")
    @Authenticated (1)
    public String getServiceAccountName() {
        return securityIdentity.getPrincipal().getName(); (2)
    }
}
1 提供一个安全的 REST 资源方法,该方法可以返回服务帐户名称
2 使用注入的 SecurityIdentity 来完成该方法的任务,在这种情况下,返回服务帐户身份名称。

在此演示中,REST 服务器与 MCP 服务器位于同一位置,以简化演示。当然,在生产中,此类 REST 服务器很可能位于远程位置。

接下来,让我们在 MCP 服务器配置部分中查看如何将对 MCP 服务帐户名称工具和此服务器的访问限制为仅具有特定受众的令牌。

MCP 服务器配置

让我们配置我们的安全 MCP 服务器

# MCP server
quarkus.mcp.server.server-info.name=Service Account Name Provider (1)
quarkus.mcp.server.traffic-logging.enabled=true
quarkus.mcp.server.traffic-logging.text-limit=1000

# Require an authenticated access to MCP server
quarkus.http.auth.permission.authenticated.paths=/mcp/* (2)
quarkus.http.auth.permission.authenticated.policy=authenticated

# Default Quarkus OIDC tenant that verifies access tokens which reach the MCP server.
quarkus.oidc.client-id=quarkus-mcp-server (3)
quarkus.oidc.token.audience=quarkus-mcp-server (4)

# Request a token exchange before the token propagation
quarkus.rest-client-oidc-token-propagation.exchange-token=true (4)

# OIDC client that performs the current token exchange
quarkus.oidc-client.auth-server-url=${quarkus.oidc.auth-server-url} (5)
quarkus.oidc-client.client-id=${quarkus.oidc.client-id}
quarkus.oidc-client.credentials.secret=${quarkus.oidc.credentials.secret}
quarkus.oidc-client.scopes=quarkus-mcp-service-scope
quarkus.oidc-client.grant.type=exchange
quarkus.oidc-client.grant-options.exchange.subject_token_type=urn:ietf:params:oauth:token-type:access_token (6)

# REST client which accesses a protected REST server, by propagating the exchanged token
io.quarkiverse.langchain4j.sample.ServiceAccountNameRestClient/mp-rest/url=https://:8080/service-account-name  (7)

# OIDC `service-account-name-rest-server` tenant that secures a protected REST server.
quarkus.oidc.service-account-name-rest-server.auth-server-url=${quarkus.oidc.auth-server-url} (8)
quarkus.oidc.service-account-name-rest-server.token.audience=quarkus-mcp-service (9)
quarkus.oidc.service-account-name-rest-server.tenant-paths=/service-account-name

# Keycloak devservice that enables a default OIDC tenant that secures MCP server.
quarkus.keycloak.devservices.image-name=quay.io/keycloak/keycloak:26.3.1 (10)
quarkus.keycloak.devservices.port=8081
# Keycloak may require more memory on some systems
quarkus.keycloak.devservices.container-memory-limit=1250M
1 声明 MCP 服务器并启用流量日志记录。
2 强制对主 MCP SSE 和工具端点进行身份验证访问。配置的模式涵盖初始的 '/mcp/sse' 握手和 '/mcp/messages/' 请求。
3 保护 MCP SSE 端点和工具的默认 OIDC 租户。它由开发模式下的 Keycloak 开发服务支持。在简单情况下,您甚至不必配置默认 OIDC 租户。但是在此演示中,需要默认 OIDC 租户来强制到达 MCP 服务器的令牌包含 quarkus-mcp-server 受众。
4 服务帐户名称 REST 客户端传播访问令牌之前,请求访问令牌交换。
5 配置 OIDC 客户端以执行令牌交换
6 将当前令牌将被交换为的新令牌的类型设置为 access_token。从 Quarkus 3.25 开始,预期的新的令牌类型将默认设置为 access_token,并且当交换令牌时需要访问令牌类型时,用户将不必配置此属性。
7 使用 REST 服务器地址配置 服务帐户名称 REST 客户端。REST 服务器仅与 MCP 服务器位于同一位置以简化演示。
8 仅保护 REST 服务器的 OIDC 租户。
9 保护 REST 服务器的 OIDC 租户要求用于访问它的令牌包含 REST 服务器 quarkus-mcp-service 受众。
10 配置 Keycloak 开发服务以使用最新的已发布 Keycloak 镜像之一,并使其在固定的 8081 端口上运行,以简化 Poem 服务配置,其中也需要访问 Keycloak。

在开发模式下启动 MCP 服务器

现在让我们在开发模式下启动 MCP 服务器

mvn quarkus:dev

然后转到下一节中的 步骤 2 - Keycloak 设置,以完成支持安全 MCP 服务器令牌受众和交换要求所需的 Keycloak 配置。

步骤 2 - Keycloak 设置

当我们在开发模式下启动 MCP 服务器时,Keycloak 开发服务启动了一个 Keycloak 容器,使其在端口 8081 上可用,并创建了一个具有 quarkus-mcp-server 客户端的 quarkus 领域 - 此客户端名称是在 MCP 服务器配置部分中使用 quarkus.oidc.client-id=quarkus-mcp-server 属性配置的。

quarkus-mcp-server 客户端表示一个保护 MCP 服务器的机密 OIDC 客户端。

但是 MCP 服务器和 REST 服务器具有其他令牌受众和交换要求,我们必须完成 Keycloak 设置以支持这些要求。让我们开始吧。

转到 https://:8081 并以 Keycloak 管理员身份登录,用户名为 admin,密码为 admin

选择 quarkus 领域

Quarkus Realm

首先,创建一个 quarkus-mcp-client OIDC 客户端,Quarkus MCP 客户端将使用该客户端来获取 OAuth2 client_credentials 令牌以访问 MCP 服务器。

常规设置 开始

Quarkus MCP Client

并启用 客户端身份验证服务帐户角色 功能

Quarkus MCP Client Service Account

保存 quarkus-mcp-client OIDC 客户端。单击其 凭据 选项卡并复制生成的密钥,以便稍后将其导出为 OIDC 客户端密钥,以便运行命令行 AI Poem Service 应用程序。

为了使 Quarkus MCP 客户端能够使用 quarkus-mcp-client OIDC 客户端将获取的访问令牌访问 MCP 服务器,这些令牌必须包含一个具有 quarkus-mcp-server 受众的受众 (aud) 声明。MCP 服务器在 MCP 服务器配置部分中配置为需要此受众。

Keycloak 支持多种选项,用于将受众 (aud) 声明添加到颁发的令牌。我们将使用一种选项,该选项涉及创建一个带有 受众 映射的自定义 客户端范围

转到 客户端范围 并创建一个 可选 quarkus-mcp-server-scope

Quarkus MCP Server Scope

创建 quarkus-mcp-server-scope 范围后,转到其 映射 选项卡,然后选择 配置新映射器 选项并选择 受众

Quarkus MCP Server Scope Audience

将此映射器命名为 quarkus-mcp-server-as-audience,并选择 quarkus-mcp-server 作为 包含的客户端受众

Quarkus MCP Server Scope Audience Details

创建 quarkus-mcp-server-scope 后,将其作为 可选 范围添加到 quarkus-mcp-client

Add Scope to Client

现在,当 Quarkus MCP 客户端使用 quarkus-mcp-client OIDC 客户端来获取令牌时,它将请求一个 quarkus-mcp-server-scope 令牌范围,从而导致 Keycloak 颁发一个具有包含 quarkus-mcp-server 的受众的令牌 - 这正是 Quarkus MCP 服务器所需要的。

接下来,我们需要支持 Quarkus MCP 服务器将传入的访问令牌与具有 quarkus-mcp-server 受众的令牌交换为将包含 REST 服务器受众的新令牌。

创建一个 quarkus-mcp-service OIDC 客户端,该客户端代表 REST 服务器,类似于您创建 quarkus-mcp-client OIDC 客户端的方式。接下来,创建一个 quarkus-mcp-service-scope 客户端范围,类似于您创建 quarkus-mcp-server-scope 客户端范围的方式,在为此范围创建受众映射时选择 quarkus-mcp-service 作为 包含的客户端受众

创建 quarkus-mcp-service-scope 后,将其作为 可选 客户端范围添加到 quarkus-mcp-server MCP 服务器 OIDC 客户端,类似于您将 quarkus-mcp-server-scope 添加到上面的 quarkus-mcp-client 的方式。

最后,更新 quarkus-mcp-server 功能以支持 标准令牌交换,请参阅 Keycloak 文档中的 如何启用令牌交换示例。

现在,保护 MCP 服务器的 quarkus-mcp-server OIDC 客户端还可以交换传入的令牌,并通过将 quarkus-mcp-service-scope 范围添加到令牌交换授权请求中来请求新的 quarkus-mcp-service 受众,这正是 REST 服务器所需要的。

如果您积极使用另一个 OAuth2 提供程序,该提供程序可以生成具有所需受众的令牌并使用标准令牌交换授权来交换它们,那么您也可以尝试调整此演示以与该提供程序一起使用。

步骤 3 - 从命令行创建并运行 Poem Service

MCP 服务器现在正在运行,并已准备好接受工具调用。让我们创建一个命令行 AI Poem Service,它将与 AI Gemini 一起使用,并使用 Quarkus MCP 客户端来完成工具调用。

Poem Service Maven 依赖

添加以下依赖项

<dependency>
    <groupId>io.quarkiverse.langchain4j</groupId>
    <artifactId>quarkus-langchain4j-ai-gemini</artifactId> (1)
</dependency>
<dependency>
    <groupId>io.quarkiverse.langchain4j</groupId>
    <artifactId>quarkus-langchain4j-mcp</artifactId> (2)
</dependency>
<dependency>
    <groupId>io.quarkiverse.langchain4j</groupId>
    <artifactId>quarkus-langchain4j-oidc-client-mcp-auth-provider</artifactId> (3)
</dependency>
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-picocli</artifactId> (4)
</dependency>
1 quarkus-langchain4j-ai-gemini 提供了对 AI Gemini 的支持。
2 quarkus-langchain4j-mcp 提供了核心 MCP 客户端支持。
3 quarkus-langchain4j-oidc-cient-mcp-auth-provider 提供了一个 McpClientAuthProvider 的实现,该实现可以提供它本身通过 OAuth2 client_credentials 授权(或任何其他不需要用户输入的受支持的授权)获取的访问令牌。请注意,此依赖项与 quarkus-langchain4j-oidc-mcp-auth-provider 不同,后者在授权码流完成后提供已可用的令牌,它在 使用 Quarkus MCP 客户端访问安全的 MCP HTTP 服务器这篇博客文章中演示了如何传播 GitHub 登录访问令牌。
4 quarkus-picocli 支持构建命令行 Quarkus 应用程序。它的版本在 Quarkus BOM 中定义。

AI Gemini API 密钥

Poem Service 依赖于 AI Gemini 来创作诗歌。

获取 AI Gemini API 密钥 并将其导出为 AI_GEMINI_API_KEY 环境变量。

OIDC 客户端密钥

Quarkus MCP 客户端将使用 quarkus-langchain4j-oidc-cient-mcp-auth-provider 依赖项提供的 McpClientAuthProvider 的实现。

McpClientAuthProvider 使用 配置的 OIDC 客户端来使用 OAuth2 client_credentials 授权获取访问令牌,其中必须提供 OIDC 客户端密钥。

导出您在完成 步骤 2 - Keycloak 设置部分时复制的 OIDC quarkus-mcp-client 客户端密钥作为 OIDC_CLIENT_SECRET 环境变量。

Poem Service

Poem Service 是一个简单的 Quarkus LangChain4j AI 服务

package io.quarkiverse.langchain4j.sample;

import dev.langchain4j.service.UserMessage;
import io.quarkiverse.langchain4j.RegisterAiService;
import io.quarkiverse.langchain4j.mcp.runtime.McpToolBox;

@RegisterAiService
public interface PoemService {
    @UserMessage("""
            Write a short 1 paragraph poem in {language} about a Java programming language.
            Provide a translation to English if the original poem language is not English.
            Dedicate the poem to the service account, refer to this account by its name.""") (1)
    @McpToolBox("service-account-name") (2)
    String writePoem(String language); (1)
}
1 请求编写一首关于 Java 的诗歌。
2 使用在 Poem Service 配置部分中配置的 Quarkus MCP service-account-name 客户端来调用一个可以提供服务帐户名称的工具。

此服务从 PoemCommand 调用

package io.quarkiverse.langchain4j.sample;

import java.util.concurrent.Callable;
import jakarta.enterprise.context.control.ActivateRequestContext;
import jakarta.inject.Inject;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

@Command(name = "poem", mixinStandardHelpOptions = true, description = "Create a poem", version = "v1.0")
@ActivateRequestContext
public class PoemCommand implements Callable<Integer> {

    @Option(names = { "-l", "--language" }, description = "Poem language", defaultValue = "English")
    String poemLanguage;

    @Inject
    PoemService poemService;

    @Override
    public Integer call() {
        System.out.println(poemService.writePoem(poemLanguage)); (1)
        return 0;
    }
}
1 调用 PoemService

Poem Service 配置

让我们看看命令行 Poem Service 配置是什么样的

quarkus.langchain4j.mcp.service-account-name.transport-type=http (1)
quarkus.langchain4j.mcp.service-account-name.url=https://:8081/mcp/sse/ (2)

quarkus.oidc-client.auth-server-url=https://:8081/realms/quarkus (3)
quarkus.oidc-client.client-id=quarkus-mcp-client (4)
quarkus.oidc-client.credentials.secret=${oidc_client_secret} (5)
quarkus.oidc-client.scopes=quarkus-mcp-server-scope (6)

quarkus.langchain4j.ai.gemini.api-key=${ai_gemini_api_key} (7)
quarkus.langchain4j.ai.gemini.log-requests=true (8)
quarkus.langchain4j.ai.gemini.log-responses=true
1 启用 MCP 客户端 HTTP 传输。在此演示中,我们使用 SSE,但也支持 Streamable HTTP
2 指向您在 在开发模式下启动 MCP 服务器步骤中启动的 Quarkus MCP 服务器端点。
3 配置 OIDC 客户端以使用 OAuth2 client_credentials 授权获取访问令牌,这是 OIDC 客户端支持的默认授权类型。OIDC 客户端指向 Keycloak quarkus 领域,请注意您在 步骤 2 - Keycloak 设置部分中请求 Keycloak 开发服务用于 Keycloak 的固定 8081 端口。
4 OIDC 客户端 ID,您在 步骤 2 - Keycloak 设置部分中创建了 OIDC quarkus-mcp-client 客户端。
5 您在 OIDC 客户端密钥步骤中导出的 OIDC quarkus-mcp-client 客户端密钥。
6 请求颁发给 quarkus-mcp-client 的令牌必须包含 quarkus-mcp-server MCP 服务器受众。您在 步骤 2 - Keycloak 设置部分中创建了一个带有 quarkus-mcp-server 客户端受众映射的客户端 quarkus-mcp-server-scope 范围。
7 您在 AI Gemini API 密钥步骤中获取并导出的 AI Gemini 密钥。
8 启用 AI Gemini 请求和响应日志记录

请注意,MCP 客户端配置具有 service-account-name 名称。您在 Poem Service 部分中使用 @McpToolBox("service-account-name") 注解引用了此配置。

打包 Poem Service

打包命令行 Poem Service

mvn clean package

运行 Poem Service

运行您在 打包 Poem Service部分中打包的命令行 Poem Service

java -jar target/quarkus-app/quarkus-run.jar

您应该收到如下响应

For service-account-quarkus-mcp-client, this Java ode I write,
A language strong, with classes bright, and objects shining light.
From simple apps to systems grand, its power knows no end,
With threads and streams, a helping hand,  a journey without bend.
Its virtual machine, a sturdy friend,  on which great feats depend.

想尝试另一种语言吗?

java -jar target/quarkus-app/quarkus-run.jar --language Greek

您应该收到如下响应

Here's a short poem in Greek about Java, dedicated to the service account "service-account-quarkus-mcp-client":

**Greek:**

Ω, Java, γλώσσα ισχυρή και γρήγορη,
για προγραμματισμό, εργαλείο ακριβές.
Στον service-account-quarkus-mcp-client αφιερωμένη,
η δύναμή σου, πάντα  αξιοθαύμαστη.

**English Translation:**

O Java, language strong and fast,
For programming, a precise tool.
Dedicated to service-account-quarkus-mcp-client,
Your power, always admirable.

令牌受众有任何影响吗?

为了使命令行 Poem Service 成功运行,Quarkus MCP 客户端必须获取一个具有 quarkus-mcp-server 受众的令牌才能访问 MCP 服务器。

以下是 Keycloak 颁发给 MCP 客户端的令牌的样子

Token with quarkus-mcp-server audience

令牌 aud 声明包含两个受众值,其中一个是必需的 quarkus-mcp-server 受众。

为了使 MCP quarkus-mcp-server 服务器完成 Quarkus MCP 客户端请求,它必须验证令牌是否具有正确的 quarkus-mcp-server 受众,并将其交换为具有 quarkus-mcp-service 受众的新令牌才能访问 REST 服务器。

以下是 Keycloak 颁发给 MCP 服务器的交换令牌的样子

Token with quarkus-mcp-service audience

令牌 aud 声明包含一个必需的 quarkus-mcp-service 受众。

请注意,此令牌仍然保留了获取先前令牌的原始 quarkus-mcp-client 客户端的记录,但也将 quarkus-mcp-server 列为授权方 (azp)。

让我们尝试在没有受众声明的情况下访问 MCP 服务器和 REST 服务器。

确保 MCP 服务器正在运行并且 Keycloak 已配置

在演示中,OIDC quarkus-mcp-client 客户端获取用于访问 MCP 服务器的令牌。

使用以下 curl 命令获取 quarkus-mcp-client 客户端的 client_credentials 令牌,省略 quarkus-mcp-server-scope 授权属性

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=client_credentials&client_id=quarkus-mcp-client&client_secret=keycloak_quarkus_mcp_client_secret" https://:8081/realms/quarkus/protocol/openid-connect/token

并在 jwt.io 上确认返回的 JWT 令牌没有受众声明。

尝试使用此令牌访问 MCP 服务器

curl -H "Authorization: Bearer <token>" https://:8080/mcp/sse

您将收到 HTTP 401。

REST 服务器呢?在演示中,OIDC quarkus-mcp-server 客户端获取用于访问 REST 服务器的令牌。

使用以下 curl 命令获取 quarkus-mcp-server 客户端的 client_credentials 令牌,省略 quarkus-mcp-service-scope 授权属性

curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=client_credentials&client_id=quarkus-mcp-server&client_secret=secret" https://:8081/realms/quarkus/protocol/openid-connect/token

并在 jwt.io 上确认返回的 JWT 令牌没有受众声明。

尝试使用此令牌访问 REST 服务器

curl -H "Authorization: Bearer <token>" https://:8080/service-account-name

您将收到 HTTP 401。

您还可以通过添加以下配置片段到 MCP 服务器配置来强制执行更严格的验证,方法是要求 MCP 和 REST 服务器收到的令牌分别颁发给 quarkus-mcp-clientquarkus-mcp-server

# Tokens that are accepted by MCP server must have been requested by `quarkus-mcp-client`

quarkus.oidc.token.required-claims.azp=quarkus-mcp-client

# Tokens that are accepted by REST server must have been requested by `quarkus-mcp-server`

quarkus.oidc.service-account-name-rest-server.token.required-claims.azp=quarkus-mcp-server

关于资源指示器的说明

OAuth2 资源指示器允许进行细粒度的令牌受众限制,在存在必须使用令牌访问的多个不同的资源服务器的情况下。

对于我们在这篇博文中创建的简单演示,拥有包含受众的令牌就足够了。

如果您的提供程序已经支持 OAuth2 资源指示器并且您需要令牌也包含资源指示器,请配置 OIDC 客户端以请求它。

例如,您可以将 quarkus.oidc-client.grant.client.extra-params.resource=https://:8080/mcp 添加到 Poem Service 配置

在这种情况下,为了使 MCP 服务器验证访问令牌是否包含正确的资源指示器,请将 quarkus.oidc.token.required-claims.resource=https://:8080/mcp 添加到 MCP 服务器配置

安全注意事项

确保分布式 AI 系统中的每个参与者都得到适当的保护,并且只接受旨在访问此参与者的令牌至关重要。

令牌受众限制是支持此目标的关键 OAuth2 机制之一,资源指示器允许实现更细粒度的受众限制。

令牌交换可以在令牌在多跳分布式 AI 应用程序中流动时帮助正确切换 OAuth2 安全上下文。

结论

在这篇博文中,我们演示了 Quarkus MCP 客户端如何通过使用 OAuth2 client_credentials 授权获取访问令牌并将其传播到安全的 Quarkus MCP 服务器来访问安全的 MCP 服务器。

我们还研究了将令牌限制为特定受众,并开始了解重要的 OAuth2 令牌交换授权。

我们为您准备了更多关于 AI 和 MCP 安全的内容,敬请期待!