okta-mcp-server
Version:
Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching
971 lines • 32.6 kB
JavaScript
import { z } from 'zod';
import { logger } from '../../utils/logger.js';
import { ListPoliciesSchema, GetPolicySchema, CreatePasswordPolicySchema, CreateSignOnPolicySchema, CreateMfaPolicySchema, UpdatePolicySchema, DeletePolicySchema, ActivatePolicySchema, DeactivatePolicySchema, ListPolicyRulesSchema, GetPolicyRuleSchema, CreatePolicyRuleSchema, UpdatePolicyRuleSchema, DeletePolicyRuleSchema, ActivatePolicyRuleSchema, DeactivatePolicyRuleSchema, PolicyResponseSchema, PolicyRuleResponseSchema, } from './schemas.js';
/**
* Extract pagination cursor from Okta Link header
*/
function extractCursor(linkHeader) {
if (!linkHeader)
return undefined;
const match = linkHeader.match(/<[^>]+after=([^&>]+)[^>]*>;\s*rel="next"/);
return match ? match[1] : undefined;
}
/**
* List Okta policies with pagination and filtering
*/
export async function handleListPolicies(args, client) {
try {
const params = ListPoliciesSchema.parse(args);
// Make API call
const requestParams = {
type: params.type,
limit: params.limit,
sortOrder: params.sortOrder,
};
// Only add optional params if they have values
if (params.after)
requestParams.after = params.after;
if (params.status)
requestParams.status = params.status;
if (params.sortBy)
requestParams.sortBy = params.sortBy;
if (params.filter)
requestParams.filter = params.filter;
const response = await client.listPolicies(requestParams);
logger.debug('Raw Okta response:', {
dataLength: response.data.length,
headers: response.headers,
});
// Extract next cursor from Link header if present
const nextCursor = extractCursor(response.headers?.link);
// Validate and return response
// Handle null conditions from Okta API
const policies = response.data.map((policy) => {
// Convert null values to undefined for optional fields
if (policy.conditions === null) {
policy.conditions = undefined;
}
if (policy.settings === null) {
policy.settings = undefined;
}
if (policy.description === null) {
policy.description = undefined;
}
return PolicyResponseSchema.parse(policy);
});
return {
content: [
{
type: 'text',
text: JSON.stringify({
policies,
_links: nextCursor ? { next: { href: `cursor:${nextCursor}` } } : undefined,
}, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error listing policies:', error);
return {
content: [
{
type: 'text',
text: `Error listing policies: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Get a single Okta policy by ID
*/
export async function handleGetPolicy(args, client) {
try {
const params = GetPolicySchema.parse(args);
const policy = await client.getPolicy(params.policyId, params.expand);
// Validate response
const validatedPolicy = PolicyResponseSchema.parse(policy);
return {
content: [
{
type: 'text',
text: JSON.stringify(validatedPolicy, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error getting policy:', error);
return {
content: [
{
type: 'text',
text: `Error getting policy: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Create a new password policy
*/
export async function handleCreatePasswordPolicy(args, client) {
try {
const params = CreatePasswordPolicySchema.parse(args);
const policyData = {
name: params.name,
type: 'PASSWORD',
description: params.description,
priority: params.priority,
status: params.status,
conditions: params.conditions,
settings: params.settings,
};
const policy = await client.createPolicy(policyData);
// Validate response
const validatedPolicy = PolicyResponseSchema.parse(policy);
// Log audit event
logger.info('Password policy created', {
policyId: validatedPolicy.id,
policyName: validatedPolicy.name,
status: validatedPolicy.status,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(validatedPolicy, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error creating password policy:', error);
return {
content: [
{
type: 'text',
text: `Error creating password policy: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Create a new sign-on policy
*/
export async function handleCreateSignOnPolicy(args, client) {
try {
const params = CreateSignOnPolicySchema.parse(args);
// Ensure we have default settings for sign-on policy
const defaultSettings = {
signon: {
access: 'ALLOW',
requireFactor: false,
factorMode: '1FA',
factorPromptMode: 'ALWAYS',
rememberDeviceByDefault: false,
factorLifetime: 0,
session: {
usePersistentCookie: false,
maxSessionIdleMinutes: 120,
maxSessionLifetimeMinutes: 720,
},
},
};
// Ensure we have minimal default conditions
const defaultConditions = {
people: {
groups: {
include: ['EVERYONE'],
},
},
};
const policyData = {
name: params.name,
type: 'OKTA_SIGN_ON',
description: params.description,
priority: params.priority || 1,
status: params.status || 'ACTIVE',
conditions: params.conditions || defaultConditions,
settings: params.settings || defaultSettings,
};
const policy = await client.createPolicy(policyData);
// Validate response
const validatedPolicy = PolicyResponseSchema.parse(policy);
// Log audit event
logger.info('Sign-on policy created', {
policyId: validatedPolicy.id,
policyName: validatedPolicy.name,
status: validatedPolicy.status,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(validatedPolicy, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error creating sign-on policy:', error);
return {
content: [
{
type: 'text',
text: `Error creating sign-on policy: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Create a new MFA policy
*/
export async function handleCreateMfaPolicy(args, client) {
try {
const params = CreateMfaPolicySchema.parse(args);
// Ensure we have default settings for MFA policy
// MFA_ENROLL policies use authenticators array structure
const defaultSettings = {
authenticators: [
{
key: 'okta_email',
enroll: {
self: 'OPTIONAL',
},
},
{
key: 'okta_sms',
enroll: {
self: 'OPTIONAL',
},
},
],
};
// Ensure we have minimal default conditions
const defaultConditions = {
people: {
groups: {
include: ['EVERYONE'],
},
},
};
const policyData = {
name: params.name,
type: 'MFA_ENROLL',
description: params.description,
priority: params.priority || 1,
status: params.status || 'ACTIVE',
conditions: params.conditions || defaultConditions,
settings: params.settings || defaultSettings,
};
const policy = await client.createPolicy(policyData);
// Validate response
const validatedPolicy = PolicyResponseSchema.parse(policy);
// Log audit event
logger.info('MFA policy created', {
policyId: validatedPolicy.id,
policyName: validatedPolicy.name,
status: validatedPolicy.status,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(validatedPolicy, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error creating MFA policy:', error);
return {
content: [
{
type: 'text',
text: `Error creating MFA policy: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Update an existing Okta policy
*/
export async function handleUpdatePolicy(args, client) {
try {
const params = UpdatePolicySchema.parse(args);
// First, get the policy to check if it's a system policy
const existingPolicy = await client.getPolicy(params.policyId);
// Check if this is a system policy
if (existingPolicy.system === true) {
logger.info('Attempted to update system policy', {
policyId: params.policyId,
policyName: existingPolicy.name,
isSystem: existingPolicy.system,
});
return {
content: [
{
type: 'text',
text: `Cannot update policy ${params.policyId}: System policies (like "Default Policy") cannot be updated through the API. Only custom policies can be modified.`,
},
],
isError: true,
};
}
const updateData = {};
// Only include fields that are being updated
if (params.name !== undefined)
updateData.name = params.name;
if (params.description !== undefined)
updateData.description = params.description;
if (params.priority !== undefined)
updateData.priority = params.priority;
if (params.status !== undefined)
updateData.status = params.status;
if (params.conditions !== undefined)
updateData.conditions = params.conditions;
if (params.settings !== undefined)
updateData.settings = params.settings;
const policy = await client.updatePolicy(params.policyId, updateData);
// Validate response
const validatedPolicy = PolicyResponseSchema.parse(policy);
// Log audit event
logger.info('Policy updated', {
policyId: validatedPolicy.id,
policyName: validatedPolicy.name,
updates: Object.keys(updateData),
});
return {
content: [
{
type: 'text',
text: JSON.stringify(validatedPolicy, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error updating policy:', error);
return {
content: [
{
type: 'text',
text: `Error updating policy: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Delete an Okta policy
*/
export async function handleDeletePolicy(args, client) {
try {
const params = DeletePolicySchema.parse(args);
await client.deletePolicy(params.policyId);
// Log audit event
logger.info('Policy deleted', {
policyId: params.policyId,
});
return {
content: [
{
type: 'text',
text: `Policy ${params.policyId} deleted successfully`,
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error deleting policy:', error);
return {
content: [
{
type: 'text',
text: `Error deleting policy: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Activate an Okta policy
*/
export async function handleActivatePolicy(args, client) {
try {
const params = ActivatePolicySchema.parse(args);
await client.activatePolicy(params.policyId);
// Log audit event
logger.info('Policy activated', {
policyId: params.policyId,
});
return {
content: [
{
type: 'text',
text: `Policy ${params.policyId} activated successfully`,
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error activating policy:', error);
return {
content: [
{
type: 'text',
text: `Error activating policy: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Deactivate an Okta policy
*/
export async function handleDeactivatePolicy(args, client) {
try {
const params = DeactivatePolicySchema.parse(args);
await client.deactivatePolicy(params.policyId);
// Log audit event
logger.info('Policy deactivated', {
policyId: params.policyId,
});
return {
content: [
{
type: 'text',
text: `Policy ${params.policyId} deactivated successfully`,
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error deactivating policy:', error);
return {
content: [
{
type: 'text',
text: `Error deactivating policy: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* List rules for a specific policy
*/
export async function handleListPolicyRules(args, client) {
try {
const params = ListPolicyRulesSchema.parse(args);
const requestParams = {
limit: params.limit,
};
if (params.after)
requestParams.after = params.after;
const response = await client.listPolicyRules(params.policyId, requestParams);
// Extract next cursor from Link header if present
const nextCursor = extractCursor(response.headers?.link);
// Validate and return response
const rules = response.data.map((rule) => PolicyRuleResponseSchema.parse(rule));
return {
content: [
{
type: 'text',
text: JSON.stringify({
rules,
_links: nextCursor ? { next: { href: `cursor:${nextCursor}` } } : undefined,
}, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error listing policy rules:', error);
return {
content: [
{
type: 'text',
text: `Error listing policy rules: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Get a specific policy rule
*/
export async function handleGetPolicyRule(args, client) {
try {
const params = GetPolicyRuleSchema.parse(args);
const rule = await client.getPolicyRule(params.policyId, params.ruleId);
// Validate response
const validatedRule = PolicyRuleResponseSchema.parse(rule);
return {
content: [
{
type: 'text',
text: JSON.stringify(validatedRule, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error getting policy rule:', error);
return {
content: [
{
type: 'text',
text: `Error getting policy rule: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Create a new policy rule
*/
export async function handleCreatePolicyRule(args, client) {
try {
const params = CreatePolicyRuleSchema.parse(args);
// Get the parent policy to determine the rule type
const parentPolicy = await client.getPolicy(params.policyId);
// Map policy type to rule type
const getRuleTypeFromPolicyType = (policyType) => {
switch (policyType) {
case 'PASSWORD':
return 'PASSWORD';
case 'OKTA_SIGN_ON':
return 'SIGN_ON';
case 'MFA_ENROLL':
return 'MFA_ENROLL';
case 'IDP_DISCOVERY':
return 'IDP_DISCOVERY';
case 'PROFILE_ENROLLMENT':
return 'PROFILE_ENROLLMENT';
default:
return policyType; // Fallback to policy type
}
};
const ruleData = {
name: params.name,
priority: params.priority,
status: params.status,
type: getRuleTypeFromPolicyType(parentPolicy.type),
conditions: params.conditions,
actions: params.actions,
};
const rule = await client.createPolicyRule(params.policyId, ruleData);
// Validate response
const validatedRule = PolicyRuleResponseSchema.parse(rule);
// Log audit event
logger.info('Policy rule created', {
policyId: params.policyId,
ruleId: validatedRule.id,
ruleName: validatedRule.name,
status: validatedRule.status,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(validatedRule, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error creating policy rule:', error);
return {
content: [
{
type: 'text',
text: `Error creating policy rule: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Update an existing policy rule
*/
export async function handleUpdatePolicyRule(args, client) {
try {
const params = UpdatePolicyRuleSchema.parse(args);
const updateData = {};
// Only include fields that are being updated
if (params.name !== undefined)
updateData.name = params.name;
if (params.priority !== undefined)
updateData.priority = params.priority;
if (params.status !== undefined)
updateData.status = params.status;
if (params.conditions !== undefined)
updateData.conditions = params.conditions;
if (params.actions !== undefined)
updateData.actions = params.actions;
const rule = await client.updatePolicyRule(params.policyId, params.ruleId, updateData);
// Validate response
const validatedRule = PolicyRuleResponseSchema.parse(rule);
// Log audit event
logger.info('Policy rule updated', {
policyId: params.policyId,
ruleId: validatedRule.id,
ruleName: validatedRule.name,
updates: Object.keys(updateData),
});
return {
content: [
{
type: 'text',
text: JSON.stringify(validatedRule, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error updating policy rule:', error);
return {
content: [
{
type: 'text',
text: `Error updating policy rule: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Delete a policy rule
*/
export async function handleDeletePolicyRule(args, client) {
try {
const params = DeletePolicyRuleSchema.parse(args);
// First, get the rule to check if it's a system rule
const existingRule = await client.getPolicyRule(params.policyId, params.ruleId);
// Check if this is a system rule
if (existingRule.system === true) {
logger.info('Attempted to delete system policy rule', {
policyId: params.policyId,
ruleId: params.ruleId,
ruleName: existingRule.name,
isSystem: existingRule.system,
});
return {
content: [
{
type: 'text',
text: `Cannot delete policy rule ${params.ruleId}: System rules (like "Default Rule") cannot be deleted through the API. Only custom rules can be deleted.`,
},
],
isError: true,
};
}
await client.deletePolicyRule(params.policyId, params.ruleId);
// Log audit event
logger.info('Policy rule deleted', {
policyId: params.policyId,
ruleId: params.ruleId,
ruleName: existingRule.name,
});
return {
content: [
{
type: 'text',
text: `Policy rule ${params.ruleId} deleted successfully from policy ${params.policyId}`,
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error deleting policy rule:', error);
return {
content: [
{
type: 'text',
text: `Error deleting policy rule: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Activate a policy rule
*/
export async function handleActivatePolicyRule(args, client) {
try {
const params = ActivatePolicyRuleSchema.parse(args);
await client.activatePolicyRule(params.policyId, params.ruleId);
// Log audit event
logger.info('Policy rule activated', {
policyId: params.policyId,
ruleId: params.ruleId,
});
return {
content: [
{
type: 'text',
text: `Policy rule ${params.ruleId} activated successfully in policy ${params.policyId}`,
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error activating policy rule:', error);
return {
content: [
{
type: 'text',
text: `Error activating policy rule: ${errorMessage}`,
},
],
isError: true,
};
}
}
/**
* Deactivate a policy rule
*/
export async function handleDeactivatePolicyRule(args, client) {
try {
const params = DeactivatePolicyRuleSchema.parse(args);
await client.deactivatePolicyRule(params.policyId, params.ruleId);
// Log audit event
logger.info('Policy rule deactivated', {
policyId: params.policyId,
ruleId: params.ruleId,
});
return {
content: [
{
type: 'text',
text: `Policy rule ${params.ruleId} deactivated successfully in policy ${params.policyId}`,
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`,
},
],
isError: true,
};
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
logger.error('Error deactivating policy rule:', error);
return {
content: [
{
type: 'text',
text: `Error deactivating policy rule: ${errorMessage}`,
},
],
isError: true,
};
}
}
//# sourceMappingURL=handlers.js.map