写点什么

使用 Quarkus 构建首个 Keycloak MCP 服务器实战指南

作者:qife122
  • 2025-10-09
    福建
  • 本文字数:6039 字

    阅读完需:约 20 分钟

A Keycloak 示例 - 使用 Quarkus 构建我的第一个 MCP 服务器工具

最近我写了一篇关于"在 Java 生态系统中采用模型上下文协议"的文章。现在也是时候开始尝试自己编写 MCP 服务器了(也许这不是第一次)。


我当然不想错过社区展示的所有酷炫功能。我的目标是学习,并可能创建一个更实用的示例。在这篇文章中,我将选择 Keycloak,并为其编写一个实验性的 MCP 服务器实现。这篇文章也是为了激发对这个主题的兴趣。为 Keycloak 开发 MCP 服务器会有用吗?

什么是模型上下文协议?

模型上下文协议是 Anthropic 于 2024 年 11 月引入的标准。MCP 的目的是建立一个标准,帮助社区编写和使用工具、提示和资源。想象一下,你开始为 Slack 这样的工具编写工具,我也开始为 Slack 编写工具。看啊,我们都有自己的实现,但随后 Slack 也推出了自己的工具。这时我们就有个小问题:一是没有标准的方式与这些工具通信;二是如果 Slack 或 GitHub 负责为其服务创建和暴露工具,会使你我的生活更轻松。这正是我认为 MCP 非常有用的用例。


MCPServerClientLLMUserMCPServerClientLLMUser您的提问(包含可用工具信息)1分析问题并决定使用哪些工具2指示使用选定的工具3执行选定的工具4工具结果5转发工具结果6使用工具结果制定答案7最终响应!8
复制代码


解释:用户向 LLM 发送查询/问题。LLM 分析问题并决定是否需要调用工具。LLM 然后指示客户端执行工具。客户端在 MCP 服务器上执行工具。客户端将结果返回给 LLM。LLM 为用户制定结果。


虽然这只是一个基本示例。现实是 MCP 还支持提示和资源。同样重要的是要说明,MCP 通常并不真正带来新功能,而是专注于标准。自其出现以来,我们已经有多个 MCP 服务器和框架实现可供使用,如 Quarkus、Spring AI、MCP SDK 等。


MCP 使我们能够通过提供例如预构建集成选择和在不同 LLM 之间切换的灵活性来开发代理和复杂工作流。

Stdio 与 SSE

在使用标准 IO 进行本地开发(服务器和客户端在同一台机器上)或构建 CLI 应用程序与使用 HTTP 上的服务器发送事件进行远程开发之间应该做出关键区分。后者应该被强调为在实际多应用程序使用中最实用的。另一个需要注意的重要事项是 MCP 服务器通过 JSON-RPC 进行通信。


JSON-RPC 是一种无状态、轻量级的远程过程调用协议。该规范主要定义了几种数据结构及其处理规则。它在传输方面是不可知的,因为这些概念可以在同一进程内、通过套接字、通过 HTTP 或在许多不同的消息传递环境中使用。


逻辑分解:


StdioSSEJava AppMCP ClientMCP ServerKeycloak
复制代码

Keycloak

如果您不熟悉 Keycloak;它是一个开源的身份和访问管理软件。当前版本是 26,已经在实际环境中广泛使用。它通过 OAuth/OIDC、AD、LDAP 和 SAML v2 提供单点登录功能。如果您不太熟悉 Keycloak,我还编写了一个小型自定进度的 Keycloak 教程,涵盖了所有基础知识和一些高级配置。

开始吧

从 quarkus cli 或通过 code.quarkus.io 创建项目。


在我的示例中,我使用 stdio。这意味着基于 CLI 的标准输入输出扩展。


在 pom.xml 中添加 stdio Quarkus 扩展:


<dependency>    <groupId>io.quarkiverse.mcp</groupId>    <artifactId>quarkus-mcp-server-stdio</artifactId>    <version>1.0.0.Alpha5</version></dependency>
复制代码


很棒。一个有趣的事实是 Keycloak 也是使用 Quarkus 构建的。最初它基于 Wildfly,但大约 2 年前团队将整个项目迁移到了 Quarkus。Keycloak Admin CLI,顾名思义,是用于管理 Keycloak 的强大工具,使用 REST API。我将在这个项目中使用它。让我们也将其添加到 pom.xml 中:


<dependency>    <groupId>io.quarkus</groupId>    <artifactId>quarkus-keycloak-admin-rest-client</artifactId></dependency>
复制代码


好的,这应该为开始设置好了基础。


由于我是从头开始。是时候编写第一个服务了。我将编写一个 UserService,它将通过 Keycloak Admin 客户端调用领域内用户的 CRUD 操作。

什么是领域

领域是给定组或应用程序或服务的所有配置、选项的逻辑命名空间。领域保护和管理一组用户、应用程序和注册的身份代理、客户端等的安全元数据。用户可以在管理控制台内的特定领域中创建。角色(权限类型)可以在领域级别定义,您还可以设置用户角色映射以将这些权限分配给特定用户。用户属于一个领域并登录到该领域。领域彼此隔离,只能管理和验证它们控制的用户。


RealmUsersGroupsRolesClients
复制代码

Keycloak 的 UserService

让我们开始编写一个服务类来访问 Keycloak。


@ApplicationScoped // [1]public class UserService {        @Inject     Keycloak keycloak; // [2]
}
复制代码


服务在应用程序启动时启动 @ApplicationScoped。我还注入了 import org.keycloak.admin.client.Keycloak 客户端来调用 Keycloak 上的管理 API。


以下 getUsers 方法将领域作为输入。这意味着我强制用户指定一个领域。因为在 keycloak 安装中可能有多个领域。一旦接收到 realm 参数,我就可以调用 user().list()来获取领域中的所有用户。


public List<UserRepresentation> getUsers(String realm) {    return keycloak.realm(realm).users().list();}
复制代码


对于 addUser 方法,我需要用户创建参数和领域。这样,当进行工具调用时,我想确保上下文是领域。例如,用户可能要求从两个领域获取用户,然后将用户添加到其中一个。


public String addUser(String realm, String username, String firstName, String lastName, String email, String password) {    UserRepresentation user = new UserRepresentation(); // 设置用户字段 [1]    user.setFirstName(firstName);    user.setLastName(lastName);    user.setUsername(username);    user.setEnabled(true);    user.setEmail(email);
CredentialRepresentation credential = new CredentialRepresentation(); // 添加密码 [2] credential.setType(CredentialRepresentation.PASSWORD); credential.setValue(password); credential.setTemporary(false); user.setCredentials(List.of(credential)); Response response = keycloak.realm(realm).users().create(user); // 发送创建请求 [3] if (response.getStatus() == Response.Status.CREATED.getStatusCode()) { return "Successfully created user: " + username; } else { Log.error("Failed to create user. Status: " + response.getStatus()); response.close(); return "Error creating user: "+" "+username; }}
复制代码


1 - UserRepresentation 类是 Admin REST API 的一部分,用于在 Keycloak 领域内管理用户的上下文中表示用户。该类封装了与用户相关的各种属性和属性,允许管理员以编程方式创建、更新和检索用户信息。


2 - CredentialRepresentation 类用于表示与用户帐户关联的凭据。该类是 Keycloak Admin REST API 的一部分,对于管理用户身份验证方法(如密码、OTP 和其他凭据类型)至关重要。


3 - 最后,我向 Keycloak 发送创建用户请求。此部分下面还有一些错误处理,以确保调用工具时,我可以返回正确的状态。


类似地,我还实现了以下两个函数,用于删除用户和按用户名获取用户。


public String deleteUser(String realm, String username)public UserRepresentation getUserByUsername(String realm, String username)
复制代码


UserService 的完整代码列表可在 github 上找到。


为了使上述内容成功运行,我还需要做两个操作性的工作。


在属性文件中添加以下行。这意味着我在端口 8081 上运行我们的本地 keycloak 实例。


quarkus.keycloak.admin-client.server-url=http://localhost:8081
复制代码

通过 docker-compose 的 Keycloak 开发模式

还有一个用于 keycloak 的 docker-compose.yaml 文件,将在本地启动它。它所做的只是以开发模式启动 keycloak,并暴露端口 8081。目前我正在使用 podman 运行此文件。


services:  keycloak:    image: quay.io/keycloak/keycloak:latest    container_name: keycloak    environment:      - KEYCLOAK_ADMIN=admin      - KEYCLOAK_ADMIN_PASSWORD=admin    ports:      - "8081:8080"    command: >      start-dev    volumes:      - keycloak_data:/opt/keycloak/data    restart: unless-stopped
volumes: keycloak_data:
复制代码


要运行上述文件:docker-compose up

创建 UserTool

工具是一个组件,通过使 LLM 能够执行特定操作并与外部系统交互来增强其能力。这可能是 API、数据库、内部系统等。这正是我在这里要做的,为 keycloak 构建工具。我们创建以下流程,其中 UserTool 将调用 UserService,后者又调用 Keycloak 进行操作。我已经创建了 UserService。现在是创建 UserTool 的时候了。


KeycloakUserServiceUserToolKeycloakUserServiceUserTool调用UserService(例如,用户/领域/客户端)1调用Keycloak(获取用户/领域/客户端)2返回结果(UserRepresentation, RealmRepresentation, ClientRepresentation)3从UserService返回转换结果为String4
复制代码


在以下 UserTool 类中,我注入 UserService 和 ObjectMapper。我使用 com.fasterxml.jackson.databind.ObjectMapper 将一些结果转换为 String,例如 List。


public class UserTool {
@Inject UserService userService;
@Inject ObjectMapper mapper;}
复制代码


接下来,让我们创建一个 MCP 服务器可以暴露的工具。


@Tool(description = "Get all users from a keycloak realm") // [1]String getUsers(@ToolArg(description = "A String denoting the name of the realm where the users reside") String realm) { // [2]    try {        return mapper.writeValueAsString(userService.getUsers(realm)); // [3]    } catch (Exception e) {        throw new ToolCallException("Failed to get users from realm");    }}
复制代码


1 - @Tool(description = "Get all users from a keycloak realm")@Tool:表示 getUsers 方法被 LLM 识别为可调用函数或工具。description 描述此工具的作用,例如获取所有用户。当请求从 keycloak 获取所有用户时,LLM 可以调用此工具。


由于 LLM 可以理解自然语言,所有导致从 keycloak 获取用户上下文的提问应该*理论上调用此工具。还要注意,如果我添加太多与其他工具描述混淆的细节,LLM 可能不会调用所需的工具并最终产生幻觉。应注意如何编写描述。我建议简洁直接,避免歧义和重叠。


2 - ToolArg 向 LLM 指定此工具需要一个参数。在我的情况下,它必须是一个领域。因此,如果用户只说获取所有用户,LLM 应该回来询问哪个领域。如上所述,请注意 description 参数。


3 - 最后,一旦结果从 UserService 返回,在这种情况下是一个 List。我使用 ObjectMapper 将其转换为 String,以便 LLM 可以理解响应。我也尝试过使用 Jsonb,效果也不错。


@Tool(description = "Create a new user in keycloak realm with the following mandatory fields realm, username, firstName, lastName, email, password")  // [1]String addUser(@ToolArg(description = "A String denoting the name of the realm where the user resides") String realm,  // [2]               @ToolArg(description = "A String denoting the username of the user to be created") String username,               @ToolArg(description = "A String denoting the first name of the user to be created") String firstName,               @ToolArg(description = "A String denoting the last name of the user to be created") String lastName,               @ToolArg(description = "A String denoting the email of the user to be created") String email,               @ToolArg(description = "A String denoting the password of the user to be created") String password) {    return userService.addUser(realm, username, firstName, lastName, email, password);  // [3]}
复制代码


1 - 由于这是创建新用户的方法。我指定了需要的确切内容。2 - 成功执行此工具所需的所有不同参数。3 - 最后,一旦 UserService 返回,我将结果传递给 LLM 进行进一步处理。

打包

我们应该将以下属性添加到 application.properties 中。


quarkus.package.jar.type=uber-jar # [1]quarkus.log.file.enable=true # [2]quarkus.log.file.path=kcadmin-quarkus.log # [3]
复制代码


1 - 告诉 Quarkus 创建一个 uber jar。Uberjar,也称为"fat jar"或"shadow jar",是一种 Java 存档文件,包含运行 Java 应用程序所需的所有依赖项和资源。2 - 我还想要一个日志文件,以便我可以了解发生了什么。3 - 日志文件的地址和名称。


mvn clean package
复制代码


应用程序的完整源代码可以在这里找到。如果您只想运行它的话 :)

使用 Goose 运行

一个本地的、可扩展的、开源的 AI 代理,可自动化工程任务。


Goose by block 提供了一个 CLI 工具,具有添加 MCP 作为扩展的能力。使用 goose configure 添加 LLM 配置和 API 密钥。


一旦 CLI 配置完成,我现在可以通过将其添加为扩展来添加打包的 MCP 服务器。


goose session --with-extension="java -jar target/keycloak-mcp-server-1.0.0-SNAPSHOT-runner.jar" 
复制代码


以下是一些可以提问的示例:


( O)> can I create a new user in keycloak?是的,您可以在Keycloak中创建新用户。为此,您需要提供以下用户信息:
- **领域**:用户将驻留的领域名称。- **用户名**:新用户的用户名。- **名字**:用户的名字。- **姓氏**:用户的姓氏。- **电子邮件**:用户的电子邮件地址。- **密码**:用户帐户的密码。
您可以提供这些详细信息,我可以协助您创建用户。
-( O)> can you delete user sshaaf from realm quarkus
复制代码


好的,总结一下。希望您喜欢它,并准备好编写您的第一个 MCP 服务器实现。

总结

本文探讨了为 Keycloak 创建一个实用的模型上下文协议服务器,旨在学习并展示其在 AI 驱动管理方面的潜力。MCP 标准化了 LLM 与外部工具、提示和资源的交互方式,解决了分散的自定义集成问题。文章详细介绍了使用 Quarkus 和 Keycloak Admin REST 客户端构建这个实验性 Keycloak MCP 服务器的过程,重点关注指定领域内的用户管理操作。它提供了 UserService 和 MCP UserTool 的代码片段,解释了如何为 LLM 使用定义工具及其参数。最后,文章展示了如何打包 Quarkus 应用程序并使用"Goose"(一个 AI 代理 CLI)运行它,以使用自然语言查询与 Keycloak 交互。示例的源代码可以在这里找到。

资源

  • Goose - https://github.com/block/goose

  • MCP - https://modelcontextprotocol.io/docs/concepts/tools

  • Keycloak MCP Server - 示例 https://github.com/sshaaf/keycloak-mcp-server

  • MCP 和调用您的 REST API - https://github.com/learnj-ai/llm-jakarta/tree/workshop/step-09-mcp

  • Quarkus - https://quarkus.io/blog/mcp-server/

  • 使用 Quarkus 创建 MCP 服务器 - https://iocanel.com/2025/03/creating-an-mcp-server-with-quarkus-and-backstage/更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)


公众号二维码


办公AI智能小助手


公众号二维码


网络安全技术点滴分享


用户头像

qife122

关注

还未添加个人签名 2021-05-19 加入

还未添加个人简介

评论

发布
暂无评论
使用Quarkus构建首个Keycloak MCP服务器实战指南_Quarkus_qife122_InfoQ写作社区