okta-mcp-server
Version:
Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching
352 lines • 11 kB
JavaScript
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