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
JavaScript
// 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