okta-mcp-server
Version:
Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching
433 lines • 13.5 kB
JavaScript
import { z } from 'zod';
import { logger } from '../../utils/logger.js';
import { ListGroupsSchema, GetGroupSchema, CreateGroupSchema, UpdateGroupSchema, DeleteGroupSchema, ListGroupMembersSchema, AddGroupMemberSchema, RemoveGroupMemberSchema, GroupResponseSchema, UserResponseSchema, } 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 groups with pagination and filtering
*/
export async function handleListGroups(args, client) {
try {
const params = ListGroupsSchema.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.type)
requestParams.type = params.type;
if (params.sortBy)
requestParams.sortBy = params.sortBy;
const response = await client.listGroups(requestParams);
logger.debug('Raw Okta response:', {
dataLength: response.data.length,
headers: response.headers,
firstGroup: response.data[0],
});
// Extract next cursor from response headers
const nextCursor = extractCursor(response.headers?.link);
// Format response - with better error handling
const groups = [];
for (const group of response.data) {
try {
const validatedGroup = GroupResponseSchema.parse(group);
groups.push(validatedGroup);
}
catch (parseError) {
logger.error('Failed to parse group:', { group, error: parseError });
// Skip this group but continue processing others
}
}
const result = {
groups,
pagination: {
limit: params.limit,
hasMore: !!nextCursor,
nextCursor,
},
};
logger.debug('Final result:', { groupCount: groups.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,
};
}
logger.error('Error listing groups:', error);
return {
content: [
{
type: 'text',
text: `Error listing groups: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}
/**
* Get a single group by ID
*/
export async function handleGetGroup(args, client) {
try {
const { groupId } = GetGroupSchema.parse(args);
const group = await client.getGroup(groupId);
const validatedGroup = GroupResponseSchema.parse(group);
return {
content: [
{
type: 'text',
text: JSON.stringify(validatedGroup, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`,
},
],
isError: true,
};
}
logger.error('Error getting group:', error);
return {
content: [
{
type: 'text',
text: `Error getting group: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}
/**
* Create a new group
*/
export async function handleCreateGroup(args, client) {
try {
const groupData = CreateGroupSchema.parse(args);
const newGroup = await client.createGroup(groupData);
const validatedGroup = GroupResponseSchema.parse(newGroup);
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'Group created successfully',
group: validatedGroup,
}, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`,
},
],
isError: true,
};
}
logger.error('Error creating group:', error);
return {
content: [
{
type: 'text',
text: `Error creating group: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}
/**
* Update an existing group
*/
export async function handleUpdateGroup(args, client) {
try {
const params = UpdateGroupSchema.parse(args);
const updatedGroup = await client.updateGroup(params.groupId, {
profile: params.profile,
});
const validatedGroup = GroupResponseSchema.parse(updatedGroup);
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'Group updated successfully',
group: validatedGroup,
}, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`,
},
],
isError: true,
};
}
logger.error('Error updating group:', error);
return {
content: [
{
type: 'text',
text: `Error updating group: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}
/**
* Delete a group
*/
export async function handleDeleteGroup(args, client) {
try {
const { groupId } = DeleteGroupSchema.parse(args);
await client.deleteGroup(groupId);
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'Group deleted successfully',
groupId,
}, null, 2),
},
],
};
}
catch (error) {
if (error instanceof z.ZodError) {
return {
content: [
{
type: 'text',
text: `Validation error: ${JSON.stringify(error.errors, null, 2)}`,
},
],
isError: true,
};
}
logger.error('Error deleting group:', error);
return {
content: [
{
type: 'text',
text: `Error deleting group: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}
/**
* List members of a group
*/
export async function handleListGroupMembers(args, client) {
try {
const params = ListGroupMembersSchema.parse(args);
const requestParams = {
limit: params.limit,
};
if (params.after)
requestParams.after = params.after;
const response = await client.listGroupMembers(params.groupId, requestParams);
logger.debug('Raw Okta response:', {
dataLength: response.data.length,
headers: response.headers,
firstMember: response.data[0],
});
// Extract next cursor from response headers
const nextCursor = extractCursor(response.headers?.link);
// Format response - with better error handling
const members = [];
for (const member of response.data) {
try {
const validatedMember = UserResponseSchema.parse(member);
members.push(validatedMember);
}
catch (parseError) {
logger.error('Failed to parse member:', { member, error: parseError });
// Skip this member but continue processing others
}
}
const result = {
groupId: params.groupId,
members,
pagination: {
limit: params.limit,
hasMore: !!nextCursor,
nextCursor,
},
};
logger.debug('Final result:', { memberCount: members.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,
};
}
logger.error('Error listing group members:', error);
return {
content: [
{
type: 'text',
text: `Error listing group members: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}
/**
* Add a user to a group
*/
export async function handleAddGroupMember(args, client) {
try {
const { groupId, userId } = AddGroupMemberSchema.parse(args);
await client.addGroupMember(groupId, userId);
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'User added to group successfully',
groupId,
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,
};
}
logger.error('Error adding group member:', error);
return {
content: [
{
type: 'text',
text: `Error adding group member: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}
/**
* Remove a user from a group
*/
export async function handleRemoveGroupMember(args, client) {
try {
const { groupId, userId } = RemoveGroupMemberSchema.parse(args);
await client.removeGroupMember(groupId, userId);
return {
content: [
{
type: 'text',
text: JSON.stringify({
message: 'User removed from group successfully',
groupId,
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,
};
}
logger.error('Error removing group member:', error);
return {
content: [
{
type: 'text',
text: `Error removing group member: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}
//# sourceMappingURL=handlers.js.map