UNPKG

okta-mcp-server

Version:

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

536 lines 19.4 kB
// Simplified Okta client implementation - API token only export class OktaClient { baseUrl; domain; apiToken; constructor(config) { if (!config.domain) { throw new Error('Okta domain is required'); } if (!config.apiToken) { throw new Error('Okta API token is required'); } // Remove protocol if included in domain const cleanDomain = config.domain.replace(/^https?:\/\//, ''); this.domain = cleanDomain; this.baseUrl = `https://${cleanDomain}/api/v1`; this.apiToken = config.apiToken; } /** * Get authorization header */ async getAuthHeader() { return `SSWS ${this.apiToken}`; } /** * Make an API request to Okta */ async request(method, path, body, queryString) { const url = `${this.baseUrl}${path}${queryString ? `?${queryString}` : ''}`; const authHeader = await this.getAuthHeader(); const options = { method, headers: { Authorization: authHeader, Accept: 'application/json', 'Content-Type': 'application/json', }, }; if (body) { options.body = JSON.stringify(body); } const response = await fetch(url, options); if (!response.ok) { const errorData = await response.json().catch(() => null); throw new Error(`Okta API error: ${response.status} ${response.statusText}${errorData ? ` - ${JSON.stringify(errorData)}` : ''}`); } // Handle empty responses (204 No Content) let data; const contentType = response.headers.get('content-type'); const contentLength = response.headers.get('content-length'); if (response.status === 204 || contentLength === '0' || !contentType?.includes('application/json')) { // Empty response - return undefined or empty object based on expected type data = undefined; } else { data = (await response.json()); } const headers = {}; const linkHeader = response.headers.get('link'); const totalCount = response.headers.get('x-total-count'); if (linkHeader) { headers.link = linkHeader; } if (totalCount) { headers['x-total-count'] = totalCount; } return { data, headers, }; } /** * List users with pagination and filtering */ async listUsers(params) { const queryParams = new URLSearchParams(); if (params) { if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.after) queryParams.append('after', params.after); if (params.filter) queryParams.append('filter', params.filter); if (params.search) queryParams.append('search', params.search); if (params.status) queryParams.append('status', params.status); if (params.lastUpdated) queryParams.append('lastUpdated', params.lastUpdated); if (params.sortBy) queryParams.append('sortBy', params.sortBy); if (params.sortOrder) queryParams.append('sortOrder', params.sortOrder); } return this.request('GET', '/users', undefined, queryParams.toString()); } /** * Get a single user by ID or login */ async getUser(userIdOrLogin) { const response = await this.request('GET', `/users/${encodeURIComponent(userIdOrLogin)}`); return response.data; } /** * Create a new user */ async createUser(userData, queryParams) { const queryString = queryParams ? new URLSearchParams(Object.entries(queryParams) .filter(([_, v]) => v !== undefined) .reduce((acc, [k, v]) => ({ ...acc, [k]: String(v) }), {})).toString() : undefined; const response = await this.request('POST', '/users', userData, queryString); return response.data; } /** * Update a user */ async updateUser(userId, userData) { const response = await this.request('PUT', `/users/${encodeURIComponent(userId)}`, userData); return response.data; } /** * Deactivate a user */ async deactivateUser(userId, sendEmail = false) { const queryString = sendEmail ? 'sendEmail=true' : undefined; await this.request('POST', `/users/${encodeURIComponent(userId)}/lifecycle/deactivate`, undefined, queryString); } /** * Delete a deactivated user */ async deleteUser(userId) { await this.request('DELETE', `/users/${encodeURIComponent(userId)}`); } /** * Get groups for a user */ async getUserGroups(userId, params) { const queryParams = new URLSearchParams(); if (params) { if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.after) queryParams.append('after', params.after); } return this.request('GET', `/users/${encodeURIComponent(userId)}/groups`, undefined, queryParams.toString()); } /** * List groups with pagination and filtering */ async listGroups(params) { const queryParams = new URLSearchParams(); if (params) { if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.after) queryParams.append('after', params.after); if (params.filter) queryParams.append('filter', params.filter); if (params.search) queryParams.append('search', params.search); if (params.type) queryParams.append('type', params.type); if (params.sortBy) queryParams.append('sortBy', params.sortBy); if (params.sortOrder) queryParams.append('sortOrder', params.sortOrder); } return this.request('GET', '/groups', undefined, queryParams.toString()); } /** * Get a single group by ID */ async getGroup(groupId) { const response = await this.request('GET', `/groups/${encodeURIComponent(groupId)}`); return response.data; } /** * Create a new group */ async createGroup(groupData) { const response = await this.request('POST', '/groups', groupData); return response.data; } /** * Update a group */ async updateGroup(groupId, groupData) { const response = await this.request('PUT', `/groups/${encodeURIComponent(groupId)}`, groupData); return response.data; } /** * Delete a group */ async deleteGroup(groupId) { await this.request('DELETE', `/groups/${encodeURIComponent(groupId)}`); } /** * List members of a group */ async listGroupMembers(groupId, params) { const queryParams = new URLSearchParams(); if (params) { if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.after) queryParams.append('after', params.after); } return this.request('GET', `/groups/${encodeURIComponent(groupId)}/users`, undefined, queryParams.toString()); } /** * Add a user to a group */ async addGroupMember(groupId, userId) { await this.request('PUT', `/groups/${encodeURIComponent(groupId)}/users/${encodeURIComponent(userId)}`); } /** * Remove a user from a group */ async removeGroupMember(groupId, userId) { await this.request('DELETE', `/groups/${encodeURIComponent(groupId)}/users/${encodeURIComponent(userId)}`); } /** * Get system logs with pagination and filtering */ async getLogs(params) { const queryParams = new URLSearchParams(); if (params) { if (params.since) queryParams.append('since', params.since); if (params.until) queryParams.append('until', params.until); if (params.filter) queryParams.append('filter', params.filter); if (params.q) queryParams.append('q', params.q); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.sortOrder) queryParams.append('sortOrder', params.sortOrder); if (params.after) queryParams.append('after', params.after); } return this.request('GET', '/logs', undefined, queryParams.toString()); } /** * Check if we have valid authentication */ async hasValidAuth() { return !!this.apiToken; } /** * List applications with pagination and filtering */ async listApps(params) { const queryParams = new URLSearchParams(); if (params) { if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.after) queryParams.append('after', params.after); if (params.filter) queryParams.append('filter', params.filter); if (params.q) queryParams.append('q', params.q); if (params.expand) queryParams.append('expand', params.expand); } return this.request('GET', '/apps', undefined, queryParams.toString()); } /** * Get a specific application by ID */ async getApp(appId, expand) { const queryParams = new URLSearchParams(); if (expand) queryParams.append('expand', expand); const response = await this.request('GET', `/apps/${encodeURIComponent(appId)}`, undefined, queryParams.toString()); return response.data; } /** * Create a new application */ async createApp(appData) { const response = await this.request('POST', '/apps', appData); return response.data; } /** * Update an application */ async updateApp(appId, appData) { const response = await this.request('PUT', `/apps/${encodeURIComponent(appId)}`, appData); return response.data; } /** * Delete an application */ async deleteApp(appId) { // First check if app needs to be deactivated try { const app = await this.getApp(appId); if (app.status === 'ACTIVE') { await this.deactivateApp(appId); } } catch (error) { // If we can't get the app, try to delete anyway } await this.request('DELETE', `/apps/${encodeURIComponent(appId)}`); } /** * Activate an application */ async activateApp(appId) { await this.request('POST', `/apps/${encodeURIComponent(appId)}/lifecycle/activate`); } /** * Deactivate an application */ async deactivateApp(appId) { await this.request('POST', `/apps/${encodeURIComponent(appId)}/lifecycle/deactivate`); } /** * Assign a user to an application */ async assignUserToApp(appId, userId, assignmentData) { const response = await this.request('PUT', `/apps/${encodeURIComponent(appId)}/users/${encodeURIComponent(userId)}`, assignmentData || {}); return response.data; } /** * Assign a group to an application */ async assignGroupToApp(appId, groupId, assignmentData) { const response = await this.request('PUT', `/apps/${encodeURIComponent(appId)}/groups/${encodeURIComponent(groupId)}`, assignmentData || {}); return response.data; } /** * Remove a user from an application */ async removeUserFromApp(appId, userId, sendEmail) { const queryParams = new URLSearchParams(); if (sendEmail !== undefined) { queryParams.append('sendEmail', sendEmail.toString()); } await this.request('DELETE', `/apps/${encodeURIComponent(appId)}/users/${encodeURIComponent(userId)}`, undefined, queryParams.toString()); } /** * Remove a group from an application */ async removeGroupFromApp(appId, groupId) { await this.request('DELETE', `/apps/${encodeURIComponent(appId)}/groups/${encodeURIComponent(groupId)}`); } /** * List users assigned to an application */ async listAppUsers(appId, params) { const queryParams = new URLSearchParams(); if (params) { if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.after) queryParams.append('after', params.after); if (params.q) queryParams.append('q', params.q); if (params.filter) queryParams.append('filter', params.filter); if (params.expand) queryParams.append('expand', params.expand); } return this.request('GET', `/apps/${encodeURIComponent(appId)}/users`, undefined, queryParams.toString()); } /** * List groups assigned to an application */ async listAppGroups(appId, params) { const queryParams = new URLSearchParams(); if (params) { if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.after) queryParams.append('after', params.after); if (params.q) queryParams.append('q', params.q); if (params.expand) queryParams.append('expand', params.expand); } return this.request('GET', `/apps/${encodeURIComponent(appId)}/groups`, undefined, queryParams.toString()); } /** * Update a user's app-specific profile or credentials */ async updateAppUser(appId, userId, updateData) { const response = await this.request('POST', `/apps/${encodeURIComponent(appId)}/users/${encodeURIComponent(userId)}`, updateData); return response.data; } /** * Close the client and cleanup resources */ /** * List policies by type */ async listPolicies(params) { if (!params?.type) { throw new Error('Policy type is required'); } const queryParams = new URLSearchParams(); queryParams.append('type', params.type); if (params.status) queryParams.append('status', params.status); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.after) queryParams.append('after', params.after); if (params.sortBy) queryParams.append('sortBy', params.sortBy); if (params.sortOrder) queryParams.append('sortOrder', params.sortOrder); if (params.filter) queryParams.append('filter', params.filter); return this.request('GET', '/policies', undefined, queryParams.toString()); } /** * Get a specific policy */ async getPolicy(policyId, expand) { let queryString = ''; if (expand) { queryString = `expand=${expand}`; } const response = await this.request('GET', `/policies/${encodeURIComponent(policyId)}`, undefined, queryString); return response.data; } /** * Create a new policy */ async createPolicy(policyData) { // For policy creation, we need to include the type in the body // The API expects it there, not as a query parameter const bodyData = { ...policyData }; const queryParams = new URLSearchParams(); // If policy status is ACTIVE, use activate parameter if (policyData.status === 'ACTIVE') { queryParams.append('activate', 'true'); // Remove status from body but keep all other fields delete bodyData.status; } // Debug logging console.error('DEBUG: Creating policy with body:', JSON.stringify(bodyData, null, 2)); console.error('DEBUG: Query params:', queryParams.toString()); // Send request with type in body const response = await this.request('POST', '/policies', bodyData, queryParams.toString() || undefined); return response.data; } /** * Update a policy */ async updatePolicy(policyId, policyData) { const response = await this.request('PUT', `/policies/${encodeURIComponent(policyId)}`, policyData); return response.data; } /** * Delete a policy */ async deletePolicy(policyId) { await this.request('DELETE', `/policies/${encodeURIComponent(policyId)}`); } /** * Activate a policy */ async activatePolicy(policyId) { await this.request('POST', `/policies/${encodeURIComponent(policyId)}/lifecycle/activate`); } /** * Deactivate a policy */ async deactivatePolicy(policyId) { await this.request('POST', `/policies/${encodeURIComponent(policyId)}/lifecycle/deactivate`); } /** * List policy rules */ async listPolicyRules(policyId, params) { const queryParams = new URLSearchParams(); if (params?.limit) queryParams.append('limit', params.limit.toString()); if (params?.after) queryParams.append('after', params.after); return this.request('GET', `/policies/${encodeURIComponent(policyId)}/rules`, undefined, queryParams.toString()); } /** * Get a specific policy rule */ async getPolicyRule(policyId, ruleId) { const response = await this.request('GET', `/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`); return response.data; } /** * Create a policy rule */ async createPolicyRule(policyId, ruleData) { const response = await this.request('POST', `/policies/${encodeURIComponent(policyId)}/rules`, ruleData); return response.data; } /** * Update a policy rule */ async updatePolicyRule(policyId, ruleId, ruleData) { const response = await this.request('PUT', `/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`, ruleData); return response.data; } /** * Delete a policy rule */ async deletePolicyRule(policyId, ruleId) { await this.request('DELETE', `/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}`); } /** * Activate a policy rule */ async activatePolicyRule(policyId, ruleId) { await this.request('POST', `/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}/lifecycle/activate`); } /** * Deactivate a policy rule */ async deactivatePolicyRule(policyId, ruleId) { await this.request('POST', `/policies/${encodeURIComponent(policyId)}/rules/${encodeURIComponent(ruleId)}/lifecycle/deactivate`); } async close() { // No cleanup needed for API token auth } } //# sourceMappingURL=okta-client.js.map