Skip to main content
Glama
README.md
# PIM Me

Azure PIM role activation library and MCP server. Activate roles programmatically or through natural language with your AI assistant. Save your frequently-used roles as favorites and activate them all with a single command.

## Quick Start

### Prerequisites

- Node.js 18+
- Azure CLI installed and logged in (`az login`)
- Azure account with PIM-eligible roles

### As an MCP Server

Add to your MCP client:

**VS Code** (`.vscode/mcp.json`):
```json
{
  "servers": {
    "pim-me": {
      "command": "npx",
      "args": ["-y", "pim-me-mcp"]
    }
  }
}
```

**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
```json
{
  "mcpServers": {
    "pim-me": {
      "command": "npx",
      "args": ["-y", "pim-me-mcp"]
    }
  }
}
```

### As a Library

```bash
npm install pim-me-mcp
```

```typescript
import { 
  listEligibleRoles, 
  listActiveRoles, 
  activateRoles,
  activateQuickRoles 
} from 'pim-me-mcp';

// List all roles you can activate
const eligible = await listEligibleRoles();
console.log(eligible.roles);

// Check what's currently elevated
const active = await listActiveRoles();
active.roles.forEach(role => {
  console.log(`${role.roleName} expires at ${role.endDateTime}`);
});

// Activate specific roles
const result = await activateRoles(
  [{ name: 'Contributor', scope: 'my-subscription' }],
  'Development work',
  8 // hours
);

// Or activate your saved favorites
const quickResult = await activateQuickRoles('Development work');
```

## MCP Server Usage

### Setting Up Quick Roles

The easiest way to use this tool is to set up your frequently-used roles once:

1. **Ask**: "Show me my PIM roles" or "Help me set up my quick roles"
2. **Pick roles** from the numbered list: "Save roles 20, 21, 22 as my quick roles"
3. **Set a default justification** (optional): Include `defaultJustification: "Development work"` when saving

Your configuration is saved to `~/.pim-me-mcp.json`:

```json
{
  "quickRoles": {
    "roles": [
      { "name": "Owner", "scope": "my-resource-group" },
      { "name": "Contributor", "scope": "my-subscription" }
    ],
    "description": "My daily development roles",
    "defaultJustification": "Development work"
  }
}
```

### Daily Usage

Once configured, just say:

- **"Activate my quick roles"** — uses your default justification
- **"Activate my quick roles for debugging production issue"** — custom justification
- **"List my eligible roles"** — see all roles you can activate
- **"List my active roles"** — see currently elevated roles with expiration times
- **"Activate the Contributor role for my-subscription"** — activate specific roles

## Available Tools (MCP)

| Tool | Description |
|------|-------------|
| `list_eligible_roles` | Lists all PIM roles you can activate |
| `list_active_roles` | Lists currently elevated roles with expiration times |
| `list_quick_roles` | Shows eligible roles with indices + your saved quick roles |
| `save_quick_roles` | Saves selected roles (by index) as quick roles |
| `activate_quick_roles` | Activates your saved quick roles |
| `activate_pim_roles` | Activates specific roles by name |

## Library API

### Core Functions

| Function | Description |
|----------|-------------|
| `listEligibleRoles()` | Returns all PIM roles you can activate |
| `listActiveRoles()` | Returns currently elevated roles with expiration times |
| `activateRoles(roles, justification, hours)` | Activates specific roles |
| `activateQuickRoles(justification?, hours?)` | Activates your saved favorites |

### Configuration Functions

| Function | Description |
|----------|-------------|
| `loadQuickRolesConfig()` | Loads quick roles from config file or env |
| `saveQuickRolesConfig(roles, desc?, justification?)` | Saves quick roles to config |
| `getConfigPath()` | Returns path to `~/.pim-me-mcp.json` |

### Types

```typescript
interface RoleConfig {
  name: string;   // e.g., "Contributor"
  scope: string;  // e.g., "my-subscription"
}

interface QuickRolesConfig {
  roles: RoleConfig[];
  description?: string;
  defaultJustification?: string;
}
```

---

## How It Works

This MCP server uses the Azure CLI to interact with the Azure PIM REST API:

1. **Lists eligible roles** via `roleEligibilityScheduleInstances` API
2. **Lists active roles** via `roleAssignmentScheduleInstances` API (filtered to `assignmentType=Activated`)
3. **Activates roles** via `roleAssignmentScheduleRequests` API with `SelfActivate` request type

**API Version**: `2020-10-01`

---

## Tricky Implementation Details

### 🔑 Group-Based Role Assignments

The trickiest part of Azure PIM automation is handling **group-based role assignments**. When a role is assigned to a group (rather than directly to a user), activation requires special handling.

#### Problem 1: Wrong Principal ID

**Symptom**: `"InsufficientPermissions"` or `"The assignee cannot be found"`

**Cause**: The `roleEligibilityScheduleInstances` API returns the *group's* principal ID, but the activation API needs the *user's* principal ID.

**Solution**: Extract the user's OID from the Azure access token JWT:

```typescript
async function getCurrentUserPrincipalId(): Promise<string> {
  const tokenResult = await azCommand(
    "account get-access-token --resource https://management.azure.com"
  );
  const tokenData = JSON.parse(tokenResult);
  
  // Decode JWT payload (base64)
  const payload = JSON.parse(
    Buffer.from(tokenData.accessToken.split('.')[1], 'base64').toString()
  );
  
  return payload.oid;  // The user's Azure AD Object ID
}
```

#### Problem 2: Missing Linked Schedule ID

**Symptom**: Activation fails for group-based roles even with correct principal ID

**Solution**: For group-based assignments, include `linkedRoleEligibilityScheduleId` in the request body. This links the activation back to the group's eligibility schedule.

### 📦 Activation Request Body

```json
{
  "properties": {
    "principalId": "<user-oid>",
    "roleDefinitionId": "<role-definition-id>",
    "requestType": "SelfActivate",
    "justification": "<business-justification>",
    "scheduleInfo": {
      "expiration": {
        "type": "AfterDuration",
        "duration": "PT8H"
      }
    },
    "linkedRoleEligibilityScheduleId": "<eligibility-schedule-id>"
  }
}
```

> **Note**: `linkedRoleEligibilityScheduleId` is required for group-based assignments, optional for direct assignments.

### ⚠️ Error Handling

| Error Code | Meaning | Solution |
|------------|---------|----------|
| `RoleAssignmentExists` | Role already activated | Treat as success ✅ |
| `InsufficientPermissions` | Wrong principal ID | Use user's OID, not group's |
| `The assignee cannot be found` | Principal ID mismatch | Extract OID from access token |

### 📋 API Reference

| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?$filter=asTarget()` | GET | List eligible roles |
| `/providers/Microsoft.Authorization/roleAssignmentScheduleInstances?$filter=asTarget()` | GET | List active roles |
| `/{scope}/providers/Microsoft.Authorization/roleAssignmentScheduleRequests/{guid}` | PUT | Activate a role |

---

## Troubleshooting

| Issue | Solution |
|-------|----------|
| "Command 'az' not found" | [Install Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) |
| "Please run 'az login'" | Run `az login` to authenticate |
| Role not found | Use `list_eligible_roles` to see exact role names and scopes |

## License

MIT

TDQS

A3.6/5.0

Scored across 6 tools

Disambiguation3/5

list_eligible_roles and list_quick_roles overlap significantly by both listing eligible roles, though list_quick_roles adds indices and saved quick-role info. activate_quick_roles and activate_pim_roles are distinct in purpose but could still be confused at a glance.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern, starting with list, activate, or save. The naming clearly communicates the action and target object without mixing conventions.

Tool Count5/5

With 6 tools, the server is well-scoped for a focused PIM role activation workflow. Each tool serves a recognizable function within the quick-role and direct-activation flows.

Completeness3/5

The set covers listing eligible roles, activating roles, quick-role management, and listing active roles, but lacks a deactivate or end-role operation. This creates a dead end for users who want to manually end an elevation early.

Maintenance

ActivityInactive
ResponsivenessNo issues