使用Quarkus构建首个Keycloak MCP服务器工具

本文详细介绍了如何使用Quarkus框架构建基于Model Context Protocol的Keycloak管理服务器,包含用户服务的CRUD操作实现、MCP工具定义以及与AI代理的自然语言交互演示。

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

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

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

什么是模型上下文协议?

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

MCP工作原理

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

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

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

Stdio与SSE

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

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

Keycloak简介

如果你不熟悉Keycloak;它是一个开源身份和访问管理软件。当前版本是26,已经在实际环境中广泛使用。它提供与OAuth/OIDC、AD、LDAP和SAML v2的单点登录功能。

开始实施

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

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

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

1
2
3
4
5
<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中:

1
2
3
4
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-keycloak-admin-rest-client</artifactId>
</dependency>

什么是Realm

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

Keycloak的UserService

让我们开始编写访问Keycloak的服务类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@ApplicationScoped // [1]
public class UserService {
    
    @Inject 
    Keycloak keycloak; // [2]

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

    public String addUser(String realm, String username, String firstName, 
                         String lastName, String email, String password) {
        UserRepresentation user = new UserRepresentation();
        user.setFirstName(firstName);
        user.setLastName(lastName);
        user.setUsername(username);
        user.setEnabled(true);
        user.setEmail(email);

        CredentialRepresentation credential = new CredentialRepresentation();
        credential.setType(CredentialRepresentation.PASSWORD);
        credential.setValue(password);
        credential.setTemporary(false);
        user.setCredentials(List.of(credential));
        
        Response response = keycloak.realm(realm).users().create(user);
        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;
        }
    }
}

创建UserTool

工具是通过使LLM能够执行特定操作并与外部系统交互来增强其能力的组件。这可能是API、数据库、内部系统等。这正是我在这里要做的,为Keycloak构建工具。

我们创建以下流程,其中UserTool将调用UserService,而UserService又调用Keycloak进行操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class UserTool {

    @Inject
    UserService userService;

    @Inject
    ObjectMapper mapper;

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

    @Tool(description = "Create a new user in keycloak realm with the following mandatory fields realm, username, firstName, lastName, email, password")
    String addUser(@ToolArg(description = "A String denoting the name of the realm where the user resides") String realm,
                   @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);
    }
}

打包

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

1
2
3
quarkus.package.jar.type=uber-jar
quarkus.log.file.enable=true
quarkus.log.file.path=kcadmin-quarkus.log

运行:mvn clean package

使用Goose运行

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

使用goose configure添加LLM配置和API密钥。

配置CLI后,我现在可以通过将其添加为扩展来添加打包的MCP服务器:

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

示例交互

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
( O)> can I create a new user in keycloak?
是的,您可以在Keycloak中创建新用户。为此,您需要提供以下用户信息:
- Realm:用户将驻留的Realm名称
- Username:新用户的用户名
- First Name:用户的名字
- Last Name:用户的姓氏
- Email:用户的电子邮件地址
- Password:用户帐户的密码

您可以提供这些详细信息,我可以协助您创建用户。

----
( O)> list all users in all realms
以下是"quarkus" realm中的用户:
1. admin - ID: af134cab-f41c-4675-b141-205f975db679
2. alice - ID: eb4123a3-b722-4798-9af5-8957f823657a
3. jdoe - ID: 1eed6a8e-a853-4597-b4c6-c4c2533546a0

----
( O)> can you delete user sshaaf from realm quarkus

总结

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

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计