@@ -545,6 +545,62 @@ class AnthropicErrorResponse(BaseModel):
545545 error : str = Field (..., description = "Error message" )
546546
547547
548+ # Management API Models (IAM/User Management)
549+
550+
551+ class M2MAccountRequest (BaseModel ):
552+ """Request model for creating M2M service account."""
553+
554+ name : str = Field (..., min_length = 1 , description = "Service account name/client ID" )
555+ groups : List [str ] = Field (..., min_length = 1 , description = "List of group names" )
556+ description : Optional [str ] = Field (None , description = "Account description" )
557+
558+
559+ class HumanUserRequest (BaseModel ):
560+ """Request model for creating human user account."""
561+
562+ username : str = Field (..., min_length = 1 , description = "Username" )
563+ email : str = Field (..., description = "Email address" )
564+ first_name : str = Field (..., min_length = 1 , description = "First name" )
565+ last_name : str = Field (..., min_length = 1 , description = "Last name" )
566+ groups : List [str ] = Field (..., min_length = 1 , description = "List of group names" )
567+ password : Optional [str ] = Field (None , description = "Initial password" )
568+
569+
570+ class KeycloakUserSummary (BaseModel ):
571+ """Keycloak user summary model."""
572+
573+ id : str = Field (..., description = "User ID" )
574+ username : str = Field (..., description = "Username" )
575+ email : Optional [str ] = Field (None , description = "Email address" )
576+ firstName : Optional [str ] = Field (None , description = "First name" )
577+ lastName : Optional [str ] = Field (None , description = "Last name" )
578+ enabled : bool = Field (True , description = "Whether user is enabled" )
579+ groups : List [str ] = Field (default_factory = list , description = "User groups" )
580+
581+
582+ class UserListResponse (BaseModel ):
583+ """Response model for list users endpoint."""
584+
585+ users : List [KeycloakUserSummary ] = Field (default_factory = list , description = "List of users" )
586+ total : int = Field (..., description = "Total number of users" )
587+
588+
589+ class UserDeleteResponse (BaseModel ):
590+ """Response model for delete user endpoint."""
591+
592+ username : str = Field (..., description = "Deleted username" )
593+ deleted : bool = Field (True , description = "Deletion status" )
594+
595+
596+ class M2MAccountResponse (BaseModel ):
597+ """Response model for M2M account creation."""
598+
599+ client_id : str = Field (..., description = "Client ID" )
600+ client_secret : str = Field (..., description = "Client secret" )
601+ groups : List [str ] = Field (default_factory = list , description = "Assigned groups" )
602+
603+
548604class RegistryClient :
549605 """
550606 MCP Gateway Registry API client.
@@ -553,6 +609,7 @@ class RegistryClient:
553609 - Server Management: registration, removal, toggling, health checks
554610 - Group Management: create, delete, list groups
555611 - Agent Management: register, update, delete, discover agents (A2A)
612+ - Management API: IAM/user management, M2M accounts, user CRUD operations
556613
557614 Authentication is handled via JWT tokens passed to the constructor.
558615 """
@@ -1374,3 +1431,185 @@ def anthropic_get_server_version(
13741431 result = AnthropicServerResponse (** response .json ())
13751432 logger .info (f"Retrieved server details for { server_name } v{ version } " )
13761433 return result
1434+
1435+
1436+ # Management API Methods (IAM/User Management)
1437+
1438+
1439+ def list_users (
1440+ self ,
1441+ search : Optional [str ] = None ,
1442+ limit : int = 500
1443+ ) -> UserListResponse :
1444+ """
1445+ List Keycloak users (admin only).
1446+
1447+ Args:
1448+ search: Optional search string to filter users
1449+ limit: Maximum number of results (default: 500)
1450+
1451+ Returns:
1452+ UserListResponse with list of users
1453+
1454+ Raises:
1455+ requests.HTTPError: If not authorized (403) or request fails
1456+ """
1457+ logger .info ("Listing Keycloak users" )
1458+
1459+ params = {}
1460+ if search :
1461+ params ["search" ] = search
1462+ if limit != 500 :
1463+ params ["limit" ] = limit
1464+
1465+ response = self ._make_request (
1466+ method = "GET" ,
1467+ endpoint = "/management/iam/users" ,
1468+ params = params
1469+ )
1470+
1471+ result = UserListResponse (** response .json ())
1472+ logger .info (f"Retrieved { result .total } users" )
1473+ return result
1474+
1475+
1476+ def create_m2m_account (
1477+ self ,
1478+ name : str ,
1479+ groups : List [str ],
1480+ description : Optional [str ] = None
1481+ ) -> M2MAccountResponse :
1482+ """
1483+ Create a machine-to-machine service account.
1484+
1485+ Args:
1486+ name: Service account name/client ID
1487+ groups: List of group names for access control
1488+ description: Optional account description
1489+
1490+ Returns:
1491+ M2MAccountResponse with client credentials
1492+
1493+ Raises:
1494+ requests.HTTPError: If not authorized (403), already exists (400), or request fails
1495+ """
1496+ logger .info (f"Creating M2M service account: { name } " )
1497+
1498+ data = {
1499+ "name" : name ,
1500+ "groups" : groups
1501+ }
1502+ if description :
1503+ data ["description" ] = description
1504+
1505+ response = self ._make_request (
1506+ method = "POST" ,
1507+ endpoint = "/management/iam/users/m2m" ,
1508+ data = data
1509+ )
1510+
1511+ result = M2MAccountResponse (** response .json ())
1512+ logger .info (f"M2M account created successfully: { name } " )
1513+ return result
1514+
1515+
1516+ def create_human_user (
1517+ self ,
1518+ username : str ,
1519+ email : str ,
1520+ first_name : str ,
1521+ last_name : str ,
1522+ groups : List [str ],
1523+ password : Optional [str ] = None
1524+ ) -> KeycloakUserSummary :
1525+ """
1526+ Create a human user account in Keycloak.
1527+
1528+ Args:
1529+ username: Username
1530+ email: Email address
1531+ first_name: First name
1532+ last_name: Last name
1533+ groups: List of group names
1534+ password: Optional initial password
1535+
1536+ Returns:
1537+ KeycloakUserSummary with created user details
1538+
1539+ Raises:
1540+ requests.HTTPError: If not authorized (403), already exists (400), or request fails
1541+ """
1542+ logger .info (f"Creating human user: { username } " )
1543+
1544+ data = {
1545+ "username" : username ,
1546+ "email" : email ,
1547+ "firstname" : first_name ,
1548+ "lastname" : last_name ,
1549+ "groups" : groups
1550+ }
1551+ if password :
1552+ data ["password" ] = password
1553+
1554+ response = self ._make_request (
1555+ method = "POST" ,
1556+ endpoint = "/management/iam/users/human" ,
1557+ data = data
1558+ )
1559+
1560+ result = KeycloakUserSummary (** response .json ())
1561+ logger .info (f"User created successfully: { username } " )
1562+ return result
1563+
1564+
1565+ def delete_user (
1566+ self ,
1567+ username : str
1568+ ) -> UserDeleteResponse :
1569+ """
1570+ Delete a user by username.
1571+
1572+ Args:
1573+ username: Username to delete
1574+
1575+ Returns:
1576+ UserDeleteResponse confirming deletion
1577+
1578+ Raises:
1579+ requests.HTTPError: If not authorized (403), not found (400/404), or request fails
1580+ """
1581+ logger .info (f"Deleting user: { username } " )
1582+
1583+ response = self ._make_request (
1584+ method = "DELETE" ,
1585+ endpoint = f"/management/iam/users/{ username } "
1586+ )
1587+
1588+ result = UserDeleteResponse (** response .json ())
1589+ logger .info (f"User deleted successfully: { username } " )
1590+ return result
1591+
1592+
1593+ def list_keycloak_iam_groups (self ) -> List [Dict [str , Any ]]:
1594+ """
1595+ List Keycloak IAM groups (raw Keycloak data).
1596+
1597+ This is different from list_groups() which returns groups with server associations.
1598+ This method returns raw Keycloak group data without scopes.
1599+
1600+ Returns:
1601+ List of Keycloak group dictionaries
1602+
1603+ Raises:
1604+ requests.HTTPError: If not authorized (403) or request fails
1605+ """
1606+ logger .info ("Listing Keycloak IAM groups" )
1607+
1608+ response = self ._make_request (
1609+ method = "GET" ,
1610+ endpoint = "/management/iam/groups"
1611+ )
1612+
1613+ result = response .json ()
1614+ logger .info (f"Retrieved { len (result )} Keycloak groups" )
1615+ return result
0 commit comments