#
tokens: 2669/50000 6/6 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   └── workflows
│       └── npm-publish.yml
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
.idea/
node_modules/
dist/

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Keycloak MCP Server

[![smithery badge](https://smithery.ai/badge/keycloak-model-context-protocol)](https://smithery.ai/server/keycloak-model-context-protocol)

A Model Context Protocol server for Keycloak administration, providing tools to manage users and realms.

## Features

- Create new users in specific realms
- Delete users from realms
- List available realms
- List users in specific realms

## Installation

### Installing via Smithery

To install Keycloak for Claude Desktop automatically via [Smithery](https://smithery.ai/server/keycloak-model-context-protocol):

```bash
npx -y @smithery/cli install keycloak-model-context-protocol --client claude
```

### Via NPM (Recommended)

The server is available as an NPM package:
```bash
# Direct usage with npx
npx -y keycloak-model-context-protocol

# Or global installation
npm install -g keycloak-model-context-protocol
```

### Local Development Setup

If you want to develop or modify the server:

```bash
git clone <repository-url>
cd keycloak-model-context-protocol
npm install
npm run build
```

## Configuration

### Using NPM Package (Recommended)
Configure the server in your Claude Desktop configuration file:

```json
{
  "mcpServers": {
    "keycloak": {
      "command": "npx",
      "args": ["-y", "keycloak-model-context-protocol"],
      "env": {
        "KEYCLOAK_URL": "http://localhost:8080",
        "KEYCLOAK_ADMIN": "admin",
        "KEYCLOAK_ADMIN_PASSWORD": "admin"
      }
    }
  }
}
```

### For Local Development
```json
{
  "mcpServers": {
    "keycloak": {
      "command": "node",
      "args": ["path/to/dist/index.js"],
      "env": {
        "KEYCLOAK_URL": "http://localhost:8080",
        "KEYCLOAK_ADMIN": "admin",
        "KEYCLOAK_ADMIN_PASSWORD": "admin"
      }
    }
  }
}
```

## Available Tools

### create-user
Creates a new user in a specified realm.

**Inputs**:
- `realm`: The realm name
- `username`: Username for the new user
- `email`: Email address for the user
- `firstName`: User's first name
- `lastName`: User's last name

### delete-user
Deletes a user from a specified realm.

**Inputs**:
- `realm`: The realm name
- `userId`: The ID of the user to delete

### list-realms
Lists all available realms.

### list-users
Lists all users in a specified realm.

**Inputs**:
- `realm`: The realm name

## Development

```bash
npm run watch
```

## Testing

To test the server using MCP Inspector:

```bash
npx -y @modelcontextprotocol/inspector npx -y keycloak-model-context-protocol
```

## Deployment

### NPM Package

This project is automatically published to [NPM](https://www.npmjs.com/package/keycloak-model-context-protocol) via GitHub Actions when a new release is published on GitHub.

#### Setup Requirements for Deployment

1. Create NPM account and get access token
2. Add NPM_TOKEN secret to GitHub repository
   - Go to repository Settings > Secrets
   - Add new secret named `NPM_TOKEN`
   - Paste your NPM access token as the value

## Prerequisites

- Node.js 18 or higher
- Running Keycloak instance

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "keycloak-model-context-protocol",
  "version": "0.0.2",
  "description": "MCP server for Keycloak administration",
  "license": "MIT",
  "author": "Christoph Englisch",
  "type": "module",
  "bin": {
    "keycloak-model-context-protocol": "./dist/index.js"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc && shx chmod +x dist/*.js",
    "prepare": "npm run build",
    "watch": "tsc --watch"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "0.5.0",
    "@keycloak/keycloak-admin-client": "^22.0.5",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/node": "^22",
    "shx": "^0.3.4",
    "typescript": "^5.3.3"
  }
}
```

--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------

```yaml
name: NPM Publish
on:
  push:
    branches:
      - main
  pull_request:
  release:
    types: [published]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - name: Install dependencies
        run: npm ci
      - name: Build package
        run: npm run build
  publish:
    runs-on: ubuntu-latest
    needs: [build]
    if: github.event_name == 'release'
    environment: release
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
          registry-url: "https://registry.npmjs.org"
      - name: Install dependencies
        run: npm ci
      - name: Publish package
        run: npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import KcAdminClient from '@keycloak/keycloak-admin-client';
import { z } from 'zod';

const server = new Server(
  {
    name: "keycloak-admin",
    version: "0.0.1",
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// Initialize Keycloak client
const kcAdminClient = new KcAdminClient({
  baseUrl: process.env.KEYCLOAK_URL || 'http://localhost:8080',
  realmName: 'master'
});

// Tool schemas
const CreateUserSchema = z.object({
  realm: z.string(),
  username: z.string(),
  email: z.string().email(),
  firstName: z.string(),
  lastName: z.string()
});

const DeleteUserSchema = z.object({
  realm: z.string(),
  userId: z.string()
});

const ListUsersSchema = z.object({
  realm: z.string()
});

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "create-user",
        description: "Create a new user in a specific realm",
        inputSchema: {
          type: "object",
          properties: {
            realm: { type: "string" },
            username: { type: "string" },
            email: { type: "string", format: "email" },
            firstName: { type: "string" },
            lastName: { type: "string" }
          },
          required: ["realm", "username", "email", "firstName", "lastName"]
        }
      },
      {
        name: "delete-user",
        description: "Delete a user from a specific realm",
        inputSchema: {
          type: "object",
          properties: {
            realm: { type: "string" },
            userId: { type: "string" }
          },
          required: ["realm", "userId"]
        }
      },
      {
        name: "list-realms",
        description: "List all available realms",
        inputSchema: {
          type: "object",
          properties: {},
          required: []
        }
      },
      {
        name: "list-users",
        description: "List users in a specific realm",
        inputSchema: {
          type: "object",
          properties: {
            realm: { type: "string" }
          },
          required: ["realm"]
        }
      }
    ]
  };
});

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  // Authenticate before each request
  await kcAdminClient.auth({
    username: process.env.KEYCLOAK_ADMIN || 'admin',
    password: process.env.KEYCLOAK_ADMIN_PASSWORD || 'admin',
    grantType: 'password',
    clientId: 'admin-cli',
  });

  const { name, arguments: args } = request.params;

  try {
    switch (name) {
      case "create-user": {
        const { realm, username, email, firstName, lastName } = CreateUserSchema.parse(args);
        
        kcAdminClient.setConfig({
          realmName: realm
        });

        const user = await kcAdminClient.users.create({
          realm,
          username,
          email,
          firstName,
          lastName,
          enabled: true
        });

        return {
          content: [{
            type: "text",
            text: `User created successfully. User ID: ${user.id}`
          }]
        };
      }

      case "delete-user": {
        const { realm, userId } = DeleteUserSchema.parse(args);
        
        kcAdminClient.setConfig({
          realmName: realm
        });

        await kcAdminClient.users.del({
          id: userId,
          realm
        });

        return {
          content: [{
            type: "text",
            text: `User ${userId} deleted successfully from realm ${realm}`
          }]
        };
      }

      case "list-realms": {
        const realms = await kcAdminClient.realms.find();
        
        return {
          content: [{
            type: "text",
            text: `Available realms:\n${realms.map(r => `- ${r.realm}`).join('\n')}`
          }]
        };
      }

      case "list-users": {
        const { realm } = ListUsersSchema.parse(args);
        
        kcAdminClient.setConfig({
          realmName: realm
        });

        const users = await kcAdminClient.users.find();
        
        return {
          content: [{
            type: "text",
            text: `Users in realm ${realm}:\n${users.map(u => `- ${u.username} (${u.id})`).join('\n')}`
          }]
        };
      }

      default:
        throw new Error(`Unknown tool: ${name}`);
    }
  } catch (error) {
    if (error instanceof z.ZodError) {
      return {
        isError: true,
        content: [{
          type: "text",
          text: `Invalid arguments: ${error.errors.map(e => `${e.path.join(".")}: ${e.message}`).join(", ")}`
        }]
      };
    }
    throw error;
  }
});

// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Keycloak MCP Server running on stdio");
```