Skip to main content
Glama

Okta MCP Server

import { z } from "zod"; import pkg from "@okta/okta-sdk-nodejs"; const { Client: OktaClient } = pkg; // Schemas for input validation const userSchemas = { getUser: z.object({ userId: z.string().min(1, "User ID is required"), }), listUsers: z.object({ limit: z.number().min(1).max(200).optional().default(50), filter: z.string().optional(), search: z.string().optional(), after: z.string().optional(), sortBy: z.string().optional(), sortOrder: z.enum(["asc", "desc"]).optional().default("asc"), }), createUser: z.object({ firstName: z.string().min(1, "First name is required"), lastName: z.string().min(1, "Last name is required"), email: z.string().email("Valid email is required"), login: z.string().optional(), activate: z.boolean().optional().default(false), }), activateUser: z.object({ userId: z.string().min(1, "User ID is required"), sendEmail: z.boolean().optional().default(true), }), suspendUser: z.object({ userId: z.string().min(1, "User ID is required"), }), unsuspendUser: z.object({ userId: z.string().min(1, "User ID is required"), }), deactivateUser: z.object({ userId: z.string().min(1, "User ID is required"), }), deleteUser: z.object({ userId: z.string().min(1, "User ID is required"), }), getUserLastLocation: z.object({ userId: z.string().min(1, "User ID is required"), }), }; // Utility function to get Okta client (can be moved to a shared utility file) function getOktaClient() { const oktaDomain = process.env.OKTA_ORG_URL; const apiToken = process.env.OKTA_API_TOKEN; if (!oktaDomain) { throw new Error( "OKTA_ORG_URL environment variable is not set. Please set it to your Okta domain." ); } if (!apiToken) { throw new Error( "OKTA_API_TOKEN environment variable is not set. Please generate an API token in the Okta Admin Console." ); } return new OktaClient({ orgUrl: oktaDomain, token: apiToken, }); } // Utility function to format profile values function getProfileValue(value: string | undefined | null): string { return value ?? "N/A"; } // Utility function to format dates function formatDate(dateString: Date | string | undefined | null): string { if (!dateString) return "N/A"; try { return new Date(dateString).toLocaleString(); } catch (e) { return dateString instanceof Date ? dateString.toISOString() : dateString || "N/A"; } } // Tool definitions for users export const userTools = [ { name: "get_user", description: "Retrieve detailed user information from Okta by user ID", inputSchema: { type: "object", properties: { userId: { type: "string", description: "The unique identifier of the Okta user", }, }, required: ["userId"], }, }, { name: "list_users", description: "List users from Okta with optional filtering and pagination", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Maximum number of users to return (default: 50, max: 200)", }, filter: { type: "string", description: "SCIM filter expression to filter users", }, search: { type: "string", description: "Free-form text search across multiple fields", }, after: { type: "string", description: "Cursor for pagination, obtained from previous response", }, sortBy: { type: "string", description: "Field to sort results by", }, sortOrder: { type: "string", description: "Sort order (asc or desc, default: asc)", enum: ["asc", "desc"], }, }, }, }, { name: "create_user", description: "Create a new user in Okta", inputSchema: { type: "object", properties: { firstName: { type: "string", description: "User's first name", }, lastName: { type: "string", description: "User's last name", }, email: { type: "string", description: "User's email address", }, login: { type: "string", description: "User's login (defaults to email if not provided)", }, activate: { type: "boolean", description: "Whether to activate the user immediately (default: false)", }, }, required: ["firstName", "lastName", "email"], }, }, { name: "activate_user", description: "Activate a user in Okta", inputSchema: { type: "object", properties: { userId: { type: "string", description: "The unique identifier of the Okta user", }, sendEmail: { type: "boolean", description: "Whether to send an activation email (default: true)", }, }, required: ["userId"], }, }, { name: "suspend_user", description: "Suspend a user in Okta", inputSchema: { type: "object", properties: { userId: { type: "string", description: "The unique identifier of the Okta user", }, }, required: ["userId"], }, }, { name: "unsuspend_user", description: "Unsuspend a user in Okta", inputSchema: { type: "object", properties: { userId: { type: "string", description: "The unique identifier of the Okta user", }, }, required: ["userId"], }, }, { name: "deactivate_user", description: "Deactivate a user in Okta", inputSchema: { type: "object", properties: { userId: { type: "string", description: "The unique identifier of the Okta user", }, }, required: ["userId"], }, }, { name: "delete_user", description: "Delete a user from Okta (must be deactivated first)", inputSchema: { type: "object", properties: { userId: { type: "string", description: "The unique identifier of the Okta user", }, }, required: ["userId"], }, }, { name: "get_user_last_location", description: "Retrieve the last known location and login information for a user from Okta system logs", inputSchema: { type: "object", properties: { userId: { type: "string", description: "The unique identifier of the Okta user", }, }, required: ["userId"], }, }, ]; // Handlers for user-related tools export const userHandlers = { get_user: async (request: { parameters: unknown }) => { const { userId } = userSchemas.getUser.parse(request.parameters); try { const oktaClient = getOktaClient(); const user = await oktaClient.userApi.getUser({ userId }); if (!user.profile) { throw new Error("User profile is undefined"); } const formattedUser = `• User Details: ID: ${user.id} Status: ${user.status} - Account Dates: Created: ${formatDate(user.created)} Activated: ${formatDate(user.activated)} Last Login: ${formatDate(user.lastLogin)} Last Updated: ${formatDate(user.lastUpdated)} Status Changed: ${formatDate(user.statusChanged)} Password Changed: ${formatDate(user.passwordChanged)} - Personal Information: Login: ${user.profile.login} Email: ${user.profile.email} Secondary Email: ${getProfileValue(user.profile.secondEmail)} First Name: ${user.profile.firstName} Last Name: ${user.profile.lastName} Display Name: ${user.profile.displayName} Nickname: ${getProfileValue(user.profile.nickName)} - Employment Details: Organization: ${user.profile.organization} Title: ${user.profile.title} Division: ${user.profile.division} Department: ${user.profile.department} Employee Number: ${user.profile.employeeNumber} User Type: ${user.profile.userType} Cost Center: ${user.profile.costCenter} Manager: ${getProfileValue(user.profile.manager)} ManagerId ${getProfileValue(user.profile.managerId)} - Contact Information: Mobile Phone: ${getProfileValue(user.profile.mobilePhone)} Primary Phone: ${getProfileValue(user.profile.primaryPhone)} - Address: Street: ${user.profile.streetAddress} City: ${user.profile.city} State: ${user.profile.state} Zip Code: ${user.profile.zipCode} Country: ${user.profile.countryCode} - Preferences: Preferred Language: ${user.profile.preferredLanguage} Profile URL: ${getProfileValue(user.profile.profileUrl)}`; return { content: [ { type: "text", text: formattedUser, }, ], }; } catch (error) { console.error("Error fetching user:", error); return { content: [ { type: "text", text: `Failed to get user: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }, list_users: async (request: { parameters: unknown }) => { const params = userSchemas.listUsers.parse(request.parameters); try { // Build query parameters const queryParams: Record<string, any> = {}; if (params.limit) queryParams.limit = params.limit; if (params.after) queryParams.after = params.after; if (params.filter) queryParams.filter = params.filter; if (params.search) queryParams.search = params.search; if (params.sortBy) queryParams.sortBy = params.sortBy; if (params.sortOrder) queryParams.sortOrder = params.sortOrder; const oktaClient = getOktaClient(); // Get users list const users = await oktaClient.userApi.listUsers(queryParams); if (!users) { return { content: [ { type: "text", text: "No users data was returned from Okta.", }, ], }; } // Format the response let formattedResponse = "Users:\n"; let count = 0; // Track pagination info let after: string | undefined; // Process the users collection for await (const user of users) { // Check if user is valid if (!user || !user.id) { continue; } count++; // Remember the last user ID for pagination after = user.id; formattedResponse += ` ${count}. ${user.profile?.firstName || ""} ${user.profile?.lastName || ""} (${user.profile?.email || "No email"}) - ID: ${user.id} - Status: ${user.status || "Unknown"} - Created: ${formatDate(user.created)} - Last Updated: ${formatDate(user.lastUpdated)} `; } if (count === 0) { return { content: [ { type: "text", text: "No users found matching your criteria.", }, ], }; } // Add pagination information if (after && count >= (params.limit || 50)) { formattedResponse += `\nPagination:\n- Total users shown: ${count}\n`; formattedResponse += `- For next page, use 'after' parameter with value: ${after}\n`; } else { formattedResponse += `\nTotal users: ${count}\n`; } return { content: [ { type: "text", text: formattedResponse, }, ], }; } catch (error) { console.error("Error listing users:", error); return { content: [ { type: "text", text: `Failed to list users: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }, create_user: async (request: { parameters: unknown }) => { const params = userSchemas.createUser.parse(request.parameters); try { const oktaClient = getOktaClient(); const newUser = { profile: { firstName: params.firstName, lastName: params.lastName, email: params.email, login: params.login || params.email, }, }; const user = await oktaClient.userApi.createUser({ body: newUser, activate: params.activate, }); return { content: [ { type: "text", text: `User created successfully: ID: ${user.id} Login: ${user.profile?.login} Status: ${user.status} Created: ${formatDate(user.created)}`, }, ], }; } catch (error) { console.error("Error creating user:", error); return { content: [ { type: "text", text: `Failed to create user: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }, activate_user: async (request: { parameters: unknown }) => { const { userId, sendEmail } = userSchemas.activateUser.parse( request.parameters ); try { const oktaClient = getOktaClient(); await oktaClient.userApi.activateUser({ userId, sendEmail, }); return { content: [ { type: "text", text: `User with ID ${userId} has been activated successfully.${ sendEmail ? " An activation email has been sent." : "" }`, }, ], }; } catch (error) { console.error("Error activating user:", error); return { content: [ { type: "text", text: `Failed to activate user: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }, suspend_user: async (request: { parameters: unknown }) => { const { userId } = userSchemas.suspendUser.parse(request.parameters); try { const oktaClient = getOktaClient(); await oktaClient.userApi.suspendUser({ userId, }); return { content: [ { type: "text", text: `User with ID ${userId} has been suspended.`, }, ], }; } catch (error) { console.error("Error suspending user:", error); return { content: [ { type: "text", text: `Failed to suspend user: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }, unsuspend_user: async (request: { parameters: unknown }) => { const { userId } = userSchemas.unsuspendUser.parse(request.parameters); try { const oktaClient = getOktaClient(); await oktaClient.userApi.unsuspendUser({ userId, }); return { content: [ { type: "text", text: `User with ID ${userId} has been unsuspended and is now active.`, }, ], }; } catch (error) { console.error("Error unsuspending user:", error); return { content: [ { type: "text", text: `Failed to unsuspend user: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }, deactivate_user: async (request: { parameters: unknown }) => { const { userId } = userSchemas.deactivateUser.parse(request.parameters); try { const oktaClient = getOktaClient(); await oktaClient.userApi.deactivateUser({ userId, }); return { content: [ { type: "text", text: `User with ID ${userId} has been deactivated.`, }, ], }; } catch (error) { console.error("Error deactivating user:", error); return { content: [ { type: "text", text: `Failed to deactivate user: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }, delete_user: async (request: { parameters: unknown }) => { const { userId } = userSchemas.deleteUser.parse(request.parameters); try { const oktaClient = getOktaClient(); await oktaClient.userApi.deleteUser({ userId, }); return { content: [ { type: "text", text: `User with ID ${userId} has been permanently deleted.`, }, ], }; } catch (error) { console.error("Error deleting user:", error); return { content: [ { type: "text", text: `Failed to delete user: ${error instanceof Error ? error.message : String(error)}. Note: Users must be deactivated before they can be deleted.`, }, ], isError: true, }; } }, get_user_last_location: async (request: { parameters: unknown }) => { const { userId } = userSchemas.getUserLastLocation.parse( request.parameters ); try { const oktaClient = getOktaClient(); // First get the user to ensure they exist and get their login const user = await oktaClient.userApi.getUser({ userId }); if (!user || !user.profile) { return { content: [ { type: "text", text: `User with ID ${userId} not found.`, }, ], }; } // Get the last 90 days of system logs for this user's login events const ninetyDaysAgo = new Date(); ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90); // Use the system log API to get login events const logs = await oktaClient.systemLogApi.listLogEvents({ since: ninetyDaysAgo.toISOString(), filter: `target.id eq "${userId}" and (eventType eq "user.session.start" or eventType eq "user.authentication.auth_via_mfa" or eventType eq "user.authentication.sso")`, limit: 1, }); // Get the first (most recent) log entry const lastLogin = await logs.next(); if (!lastLogin || !lastLogin.value) { return { content: [ { type: "text", text: `No login events found for user ${user.profile.login} in the last 90 days. This might mean the user hasn't logged in recently or the events are not being captured in the system logs.`, }, ], }; } const event = lastLogin.value; const clientData = event.client || {}; const geographicalContext = event.client?.geographicalContext || {}; const formattedLocation = `• Last Login Information for User ${user.profile.login}: Time: ${formatDate(event.published)} Event Type: ${event.eventType || "N/A"} IP Address: ${clientData.ipAddress || "N/A"} City: ${geographicalContext.city || "N/A"} State: ${geographicalContext.state || "N/A"} Country: ${geographicalContext.country || "N/A"} Device: ${clientData.device || "N/A"} User Agent: ${clientData.userAgent || "N/A"}`; return { content: [ { type: "text", text: formattedLocation, }, ], }; } catch (error) { console.error("Error fetching user location:", error); return { content: [ { type: "text", text: `Failed to fetch user location: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }, };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kapilduraphe/okta-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server