Realm, Client, User 조회 Tool을 TypeScript로 구현하기
이번 글에서는 Keycloak Admin API를 MCP Tool로 감싸는 TypeScript 서버 구조를 구현합니다. 목표는 Keycloak을 조작하는 것이 아니라, AI Agent가 Realm, Client, User 설정을 안전하게 조회하도록 만드는 것입니다.
Keycloak MCP 서버를 TypeScript로 구현하는 방법을 설명합니다. MCP TypeScript SDK의 McpServer, StdioServerTransport, Keycloak Admin API 토큰 발급, Realm 조회, Client 조회, Redirect URI 점검, User 제한 조회, 관리자성 Role 점검 Tool 구현 흐름을 GitHub 예제 저장소 keycloak-mcp-readonly-server 기준으로 정리합니다.
이번 글에서 구현할 GitHub 예제 구조
1편에서는 Keycloak MCP가 왜 필요한지와 읽기 전용 구조가 왜 중요한지 설명했습니다. 2편에서는 실제 예제 저장소의 코드를 기준으로 MCP 서버를 구성합니다. 전체 코드는 GitHub keycloak-mcp-readonly-server에 정리되어 있습니다.
keycloak-mcp-readonly-server/
├── src/
│ ├── index.ts
│ ├── config.ts
│ ├── keycloak-client.ts
│ ├── guardrails.ts
│ ├── audit.ts
│ └── tools/
│ ├── list-realms.ts
│ ├── list-clients.ts
│ ├── check-redirect-uris.ts
│ ├── list-users.ts
│ └── check-admin-roles.ts
├── docs/
├── examples/
├── package.json
└── .env.example이 구조에서 src/index.ts는 MCP 서버 엔트리포인트입니다. src/keycloak-client.ts는 Keycloak Admin API 호출을 담당합니다. src/tools/ 아래 파일들은 각각 하나의 읽기 전용 점검 Tool을 담당합니다.
핵심 원칙은 단순합니다. Keycloak Admin API 전체를 AI에게 열지 않고, 운영자가 허용한 작은 Tool만 노출합니다. 이 예제는 상태를 바꾸는 기능을 포함하지 않습니다.
개발 환경 준비
예제는 Node.js와 TypeScript 기반입니다. MCP TypeScript SDK 공식 문서 기준으로 TypeScript 서버를 만들 때 @modelcontextprotocol/sdk 패키지를 사용합니다. Keycloak Admin API 호출에는 별도 Keycloak 전용 SDK 대신 표준 fetch를 사용해 흐름을 단순하게 보여줍니다.
| 항목 | 용도 | 비고 |
|---|---|---|
| Node.js 20+ | TypeScript MCP 서버 실행 | 예제 package.json의 engines 기준 |
| TypeScript | 타입 안정성과 빌드 | tsc로 빌드 |
| MCP TypeScript SDK | MCP 서버와 Tool 등록 | McpServer, StdioServerTransport 사용 |
| Keycloak 테스트 Realm | Admin API 조회 대상 | 운영 Realm 연결 전 개발 Realm 권장 |
| Service Account | Client Credentials 토큰 발급 | 최소 권한 부여 필요 |
처음부터 운영 Keycloak에 연결하지 않는 것이 중요합니다. 개발 Realm이나 로컬 Keycloak에서 먼저 Tool 호출과 응답 형태를 확인한 뒤, 운영 Realm 연결 여부를 별도로 검토해야 합니다.
환경 변수 구성하기
예제 저장소는 .env.example을 제공합니다. 실제 실행할 때는 이 파일을 .env로 복사하고 환경에 맞는 값을 넣습니다. 단, 실제 Secret 값은 절대 GitHub에 커밋하면 안 됩니다.
KEYCLOAK_BASE_URL=https://keycloak.example.com
KEYCLOAK_ADMIN_REALM=master
KEYCLOAK_TARGET_REALM=sample
KEYCLOAK_CLIENT_ID=mcp-readonly
KEYCLOAK_CLIENT_SECRET=replace-me
KEYCLOAK_ALLOW_WRITE_TOOLS=falseKEYCLOAK_ADMIN_REALM은 토큰을 발급받는 Realm입니다. 일반적으로 master Realm 또는 별도 관리 Realm을 사용할 수 있습니다. KEYCLOAK_TARGET_REALM은 실제 조회할 기본 Realm입니다.
예제의 src/config.ts는 필수 환경 변수가 없으면 실행을 중단합니다. 또한 KEYCLOAK_ALLOW_WRITE_TOOLS=true가 들어오면 예제 서버 자체가 실행되지 않도록 막았습니다. 이 저장소의 목적이 읽기 전용 서버라는 점을 코드 레벨에서도 표현한 것입니다.
환경 변수에 들어가는 Client Secret은 로컬 개발에서는 .env, 운영에서는 Secret Manager 또는 안전한 런타임 Secret 주입 방식으로 관리해야 합니다.
Keycloak Access Token 발급 로직 만들기
Keycloak Admin API를 호출하려면 Access Token이 필요합니다. 예제는 Client Credentials 방식을 사용합니다. 전용 confidential client를 만들고, service account를 활성화한 뒤 필요한 realm-management role만 부여하는 구조입니다.
const tokenUrl = baseUrl + "/realms/" + adminRealm + "/protocol/openid-connect/token";
const body = new URLSearchParams({
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
});
const response = await fetch(tokenUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body,
});예제 저장소의 src/keycloak-client.ts는 토큰을 매번 새로 받지 않고 만료 시간을 기준으로 간단히 캐싱합니다. 토큰 만료 직전에는 새 토큰을 발급받도록 여유 시간을 둡니다.
운영 환경에서는 토큰 발급 실패, 네트워크 오류, 인증서 문제, 권한 부족을 구분해 로그로 남기는 것이 좋습니다. 특히 401과 403은 원인이 다릅니다. 401은 토큰 발급 또는 인증 자체의 문제이고, 403은 토큰은 유효하지만 Admin API 권한이 부족한 경우가 많습니다.
MCP Server 기본 엔트리포인트 만들기
MCP 서버의 엔트리포인트는 src/index.ts입니다. 공식 MCP TypeScript SDK 문서 기준으로 McpServer를 만들고, stdio 기반 transport인 StdioServerTransport에 연결합니다.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({
name: "keycloak-mcp-readonly-server",
version: "0.1.0",
});
const transport = new StdioServerTransport();
await server.connect(transport);stdio transport를 사용할 때 주의할 점이 있습니다. MCP 프로토콜 메시지는 stdout을 사용합니다. 따라서 운영 로그를 console.log로 stdout에 찍으면 MCP 통신을 깨뜨릴 수 있습니다. 예제 저장소는 감사 로그를 console.error로 stderr에 출력하도록 src/audit.ts를 분리했습니다.
이 작은 차이가 중요합니다. MCP 서버는 일반 CLI 앱처럼 아무 로그나 stdout에 출력하면 안 됩니다. 프로토콜 채널과 운영 로그 채널을 분리해야 합니다.
list_realms Tool 구현
list_realms는 service account가 볼 수 있는 Realm 목록을 조회하는 Tool입니다. 가장 기본적인 점검 도구이지만, Realm 이름도 내부 운영 정보이므로 공개 환경에 그대로 노출하면 안 됩니다.
export const ListRealmsArgs = z.object({});
export async function listRealms(api: KeycloakClientApi) {
const realms = await api.listRealms();
return realms.map((realm) => ({
realm: realm.realm,
enabled: realm.enabled ?? true,
}));
}입력값이 필요 없기 때문에 Zod 스키마는 빈 객체입니다. 반환값도 Realm 전체 설정이 아니라 Realm 이름과 enabled 상태 정도로 제한합니다. 운영에서 더 많은 필드가 필요하다면 추가할 수 있지만, 기본값은 작게 시작하는 편이 안전합니다.
list_clients Tool 구현
list_clients는 특정 Realm의 Client 목록과 주요 메타데이터를 조회합니다. Keycloak에서 Client 설정은 로그인 흐름과 Redirect URI, Web Origin, Public Client 여부와 연결되므로 운영 점검에서 자주 확인합니다.
export const ListClientsArgs = z.object({
realm: z.string().optional(),
});
export async function listClients(api: KeycloakClientApi, args) {
const clients = await api.listClients(args.realm);
return clients.map((client) => ({
clientId: client.clientId,
enabled: client.enabled ?? true,
publicClient: client.publicClient ?? false,
protocol: client.protocol,
redirectUriCount: client.redirectUris?.length ?? 0,
webOriginCount: client.webOrigins?.length ?? 0,
}));
}중요한 점은 Client Secret을 반환하지 않는다는 것입니다. Client Secret은 인증 정보입니다. AI Agent에게 보여줄 이유가 없습니다. 예제는 Client 목록과 점검에 필요한 수량 정보만 반환합니다.
운영에서는 Public Client가 필요한지, Redirect URI가 너무 넓지 않은지, 사용하지 않는 Client가 enabled 상태로 남아 있지 않은지 확인하는 데 사용할 수 있습니다.
check_redirect_uris Tool 구현
check_redirect_uris는 Client 설정 중 검토가 필요한 Redirect URI를 찾아주는 Tool입니다. 예제에서는 wildcard, localhost, 127.0.0.1 패턴을 위험 후보로 분류합니다.
export function isRiskyRedirectUri(uri: string): boolean {
const normalized = uri.toLowerCase();
return (
normalized.includes("*") ||
normalized.startsWith("http://localhost") ||
normalized.startsWith("http://127.0.0.1") ||
normalized === "*"
);
}이 검사는 취약점 확정이 아닙니다. 운영자가 검토해야 할 후보를 줄여주는 휴리스틱입니다. 예를 들어 개발 환경에서는 localhost Redirect URI가 정상일 수 있습니다. 하지만 운영 Realm에 남아 있다면 별도 확인이 필요합니다.
AI가 “위험하다”고 말해도 곧바로 설정을 바꾸면 안 됩니다. 해당 Client의 사용 목적, 환경, 배포 단계, OAuth 흐름을 확인한 뒤 변경해야 합니다.
list_users Tool 구현 시 개인정보 줄이기
User 조회는 특히 조심해야 합니다. 읽기 전용이라고 해도 사용자 목록에는 개인정보가 포함될 수 있습니다. 따라서 예제의 list_users Tool은 기본적으로 최대 50개만 조회하고, 이메일은 마스킹합니다.
export function maskEmail(email?: string): string | undefined {
if (!email) return undefined;
const [name, domain] = email.split("@");
if (!name || !domain) return "***";
return name.slice(0, 2) + "***@" + domain;
}예제 Tool은 username, enabled, emailVerified, maskedEmail 정도만 반환합니다. 전체 프로필, attribute, credential 정보, federation 관련 민감 정보는 반환하지 않습니다.
운영에서는 검색 조건을 강제하는 것도 좋습니다. “전체 사용자 덤프”를 AI에게 넘기는 구조는 피해야 합니다. 특정 키워드, 특정 상태, 특정 점검 목적이 있을 때만 제한적으로 조회하는 방향이 안전합니다.
check_admin_roles Tool 구현
check_admin_roles는 관리자성 Role 후보를 찾아주는 Tool입니다. 예제에서는 role 이름에 admin, manage, realm-management 같은 패턴이 들어가면 검토 대상으로 반환합니다.
const adminRolePatterns = [/admin/i, /manage-/i, /realm-management/i];
export async function checkAdminRoles(api, args) {
const roles = await api.listRealmRoles(args.realm);
return roles
.filter((role) => adminRolePatterns.some((pattern) => pattern.test(role.name)))
.map((role) => ({
name: role.name,
description: role.description,
note: "Review this role and its assignments before granting.",
}));
}이 Tool은 완전한 권한 분석기가 아닙니다. Keycloak의 권한 구조는 Realm Role, Client Role, Composite Role, Group Mapping, User Mapping이 섞일 수 있습니다. 따라서 이 예제는 관리자성 Role 후보를 찾는 시작점으로 봐야 합니다.
3편에서는 이런 Role 점검 결과를 운영에서 어떻게 해석하고, AI 답변을 어디까지 신뢰해야 하는지 별도로 다룹니다.
Claude Desktop 또는 MCP Client 설정
MCP 서버를 빌드한 뒤에는 MCP Client 설정에 서버 실행 명령과 환경 변수를 넣어야 합니다. 예제 저장소는 examples/claude-desktop-config.json 파일을 제공합니다.
{
"mcpServers": {
"keycloak-readonly": {
"command": "node",
"args": ["C:/path/to/keycloak-mcp-readonly-server/dist/index.js"],
"env": {
"KEYCLOAK_BASE_URL": "https://keycloak.example.com",
"KEYCLOAK_ADMIN_REALM": "master",
"KEYCLOAK_TARGET_REALM": "sample",
"KEYCLOAK_CLIENT_ID": "mcp-readonly",
"KEYCLOAK_CLIENT_SECRET": "replace-me"
}
}
}
}실제 사용 시에는 args 경로를 본인의 빌드 결과 위치로 바꿔야 합니다. 또한 Client Secret은 예시처럼 문서에 남겨두지 말고, 개인 환경에서만 관리해야 합니다.
MCP Client에 연결한 뒤에는 예제 저장소의 examples/prompts.md에 있는 질문을 사용해 Tool 호출을 테스트할 수 있습니다.
실행 결과와 자주 만나는 오류
Keycloak Admin API를 연결할 때 자주 만나는 오류는 401, 403, Realm 경로 오류, HTTPS 인증서 문제입니다. 오류 메시지를 정확히 나눠 봐야 빠르게 해결할 수 있습니다.
| 오류 | 가능한 원인 | 확인할 것 |
|---|---|---|
| 401 Unauthorized | Client ID, Client Secret, token endpoint 오류 | Service Account 활성화, Secret 값, admin realm 경로 |
| 403 Forbidden | 토큰은 있지만 Admin API 권한 부족 | realm-management role, view-users, view-clients 권한 |
| 404 Not Found | Realm 이름 또는 Admin API 경로 오류 | KEYCLOAK_TARGET_REALM, base URL |
| 인증서 오류 | 사설 인증서 또는 내부 CA 미신뢰 | 개발 환경 CA 설정, HTTPS 구성 |
| MCP 응답 깨짐 | stdout에 일반 로그 출력 | 운영 로그는 stderr로 출력 |
예제 저장소는 scripts/validate-repo.mjs를 제공합니다. 이 스크립트는 JSON 파싱, 필수 파일 존재 여부, 명백한 Secret 커밋 여부, 금지된 쓰기 Tool 등록 여부를 검사합니다.
GitHub 예제 저장소로 따라 해보기
전체 예제는 GitHub에 있습니다. 블로그는 구조와 판단 기준을 설명하고, 저장소는 실제 파일을 제공합니다.
git clone https://github.com/yechan-cloudlab/cloud-public.git
cd cloud-public/keycloak-mcp-readonly-server
npm install
Copy-Item .env.example .env
npm run validate
npm run typecheck
npm run build
npm startmacOS나 Linux에서는 환경 파일 복사를 아래처럼 하면 됩니다.
cp .env.example .env실제 실행 전에는 .env 값을 본인의 Keycloak 테스트 환경에 맞게 수정해야 합니다. 운영 Realm에 연결하기 전에는 반드시 개발 Realm에서 Tool 호출 결과를 검증해야 합니다.
3편에서는 이 MCP 서버를 운영 환경에 연결할 때 AI Agent에게 인증 시스템을 어디까지 보여줄 것인지, Service Account 권한과 감사 로그, 네트워크 접근 제어 기준을 정리합니다.
핵심 요약
Keycloak MCP 서버 구현에서 중요한 것은 많은 기능을 빠르게 붙이는 것이 아닙니다. Admin API를 작은 읽기 전용 Tool로 나누고, AI Agent가 호출할 수 있는 범위를 명확히 제한하는 것입니다.
이번 글에서는 TypeScript 기반 MCP 서버 구조, Keycloak Access Token 발급, Realm·Client·User 조회 Tool, Redirect URI 점검 Tool의 구현 흐름을 다뤘습니다. 이 정도만으로도 운영자가 반복적으로 하던 설정 확인 작업을 상당 부분 줄일 수 있습니다.
하지만 운영 환경에서는 조회만으로도 민감한 정보가 노출될 수 있습니다. 다음 글에서는 Service Account 권한, Secret 관리, 감사 로그, 네트워크 접근 제어, 운영 Realm 연결 기준을 다루겠습니다.
A useful Keycloak MCP server is not the one with the most permissions. It is the one with the clearest boundary.
'Keycloak' 카테고리의 다른 글
| Claude한테 “키클락 유저 100명 생성해줘” 했더니 10초 만에 끝났다: Keycloak MCP 자동화 실전 (0) | 2026.05.21 |
|---|---|
| Keycloak MCP 보안 운영 가이드: AI Agent에게 인증 시스템을 어디까지 보여줄 것인가 (0) | 2026.05.21 |
| Keycloak MCP란 무엇인가: AI Agent로 인증 설정을 점검하는 읽기 전용 서버 만들기 (0) | 2026.05.21 |
| Keycloak 이중화 구성 방법: EKS에서 Pod, AZ, RDS까지 어디를 이중화해야 하는가 (0) | 2026.05.18 |
| Keycloak 운영 방법: 인증서 갱신, 백업, 업그레이드, 장애 대응 체크리스트 (0) | 2026.05.17 |