UNPKG

okta-mcp-server

Version:

Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching

352 lines 11 kB
import { z } from 'zod'; import { logger } from '../../utils/logger.js'; import { ListUsersSchema, GetUserSchema, CreateUserSchema, UpdateUserSchema, DeleteUserSchema, GetUserGroupsSchema, UserResponseSchema, } from './schemas.js'; import { GroupResponseSchema } from '../groups/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 users with pagination and filtering */ export async function handleListUsers(args, client) { try { const params = ListUsersSchema.parse(args); // Make API call const requestParams = { limit: params.limit, sortOrder: params.sortOrder, }; // Only add optional params if they have values if (params.after) requestParams.after = params.after; if (params.filter) requestParams.filter = params.filter; if (params.search) requestParams.search = params.search; if (params.sortBy) requestParams.sortBy = params.sortBy; const response = await client.listUsers(requestParams); logger.debug('Raw Okta response:', { dataLength: response.data.length, headers: response.headers, firstUser: response.data[0], }); // Extract next cursor from response headers const nextCursor = extractCursor(response.headers?.link); // Format response - with better error handling const users = []; for (const user of response.data) { try { const validatedUser = UserResponseSchema.parse(user); users.push(validatedUser); } catch (parseError) { logger.error('Failed to parse user:', { user, error: parseError }); // Skip this user but continue processing others } } const result = { users, pagination: { limit: params.limit, hasMore: !!nextCursor, nextCursor, }, }; logger.debug('Final result:', { userCount: users.length, hasMore: !!nextCursor }); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { if (error instanceof z.ZodError) { return { content: [ { type: 'text', text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`, }, ], isError: true, }; } return { content: [ { type: 'text', text: `Error listing users: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } /** * Get a single user by ID or login */ export async function handleGetUser(args, client) { try { const { userIdOrLogin } = GetUserSchema.parse(args); const user = await client.getUser(userIdOrLogin); const validatedUser = UserResponseSchema.parse(user); return { content: [ { type: 'text', text: JSON.stringify(validatedUser, null, 2), }, ], }; } catch (error) { if (error instanceof z.ZodError) { return { content: [ { type: 'text', text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`, }, ], isError: true, }; } return { content: [ { type: 'text', text: `Error getting user: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } /** * Create a new user */ export async function handleCreateUser(args, client) { try { const params = CreateUserSchema.parse(args); // Ensure login is set (default to email if not provided) if (!params.profile.login) { params.profile.login = params.profile.email; } // Create user - only include defined properties const userData = { profile: params.profile, }; if (params.credentials) { userData.credentials = params.credentials; } if (params.groupIds) { userData.groupIds = params.groupIds; } const queryParams = { activate: params.activate, provider: params.provider, }; if (params.nextLogin) { queryParams.nextLogin = params.nextLogin; } const user = await client.createUser(userData, queryParams); const validatedUser = UserResponseSchema.parse(user); return { content: [ { type: 'text', text: JSON.stringify({ message: 'User created successfully', user: validatedUser, }, null, 2), }, ], }; } catch (error) { if (error instanceof z.ZodError) { return { content: [ { type: 'text', text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`, }, ], isError: true, }; } return { content: [ { type: 'text', text: `Error creating user: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } /** * Update an existing user */ export async function handleUpdateUser(args, client) { try { const params = UpdateUserSchema.parse(args); // Update user - only include defined properties const updateData = {}; if (params.profile && Object.keys(params.profile).length > 0) { updateData.profile = params.profile; } if (params.credentials) { updateData.credentials = params.credentials; } const user = await client.updateUser(params.userId, updateData); const validatedUser = UserResponseSchema.parse(user); return { content: [ { type: 'text', text: JSON.stringify({ message: 'User updated successfully', user: validatedUser, }, null, 2), }, ], }; } catch (error) { if (error instanceof z.ZodError) { return { content: [ { type: 'text', text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`, }, ], isError: true, }; } return { content: [ { type: 'text', text: `Error updating user: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } /** * Delete (deactivate) a user */ export async function handleDeleteUser(args, client) { try { const params = DeleteUserSchema.parse(args); // First deactivate the user await client.deactivateUser(params.userId, params.sendEmail); // Then delete the deactivated user await client.deleteUser(params.userId); return { content: [ { type: 'text', text: JSON.stringify({ message: 'User deleted successfully', userId: params.userId, }, null, 2), }, ], }; } catch (error) { if (error instanceof z.ZodError) { return { content: [ { type: 'text', text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`, }, ], isError: true, }; } return { content: [ { type: 'text', text: `Error deleting user: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } /** * Get groups for a user */ export async function handleGetUserGroups(args, client) { try { const params = GetUserGroupsSchema.parse(args); // Get user groups const requestParams = { limit: params.limit, }; // Only add optional params if they have values if (params.after) requestParams.after = params.after; const response = await client.getUserGroups(params.userId, requestParams); // Extract next cursor from response headers const nextCursor = extractCursor(response.headers?.link); // Format response const result = { groups: response.data.map((group) => GroupResponseSchema.parse(group)), pagination: { limit: params.limit, hasMore: !!nextCursor, nextCursor, }, }; return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { if (error instanceof z.ZodError) { return { content: [ { type: 'text', text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`, }, ], isError: true, }; } return { content: [ { type: 'text', text: `Error getting user groups: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } } //# sourceMappingURL=handlers.js.map