UNPKG

okta-mcp-server

Version:

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

126 lines 4.73 kB
import { logger } from '../../utils/logger.js'; import { buildQueryParams } from './utils.js'; /** * Parses the URI to extract query parameters */ function parseUsersUri(uri) { const url = new URL(uri, 'okta://'); const params = url.searchParams; return { limit: params.get('limit') ? parseInt(params.get('limit'), 10) : 200, filter: params.get('filter') || undefined, search: params.get('search') || undefined, status: params.get('status') || undefined, lastUpdated: params.get('lastUpdated') || undefined, sortBy: params.get('sortBy') || undefined, sortOrder: params.get('sortOrder') || undefined, }; } /** * Async generator for streaming users efficiently */ async function* streamUsers(okta, options) { const pageSize = Math.min(options.limit || 200, 200); // Okta max is 200 per page let after; let totalFetched = 0; const maxToFetch = options.limit || Infinity; try { while (totalFetched < maxToFetch) { const currentPageSize = Math.min(pageSize, maxToFetch - totalFetched); logger.debug(`Fetching users page with size ${currentPageSize}, after: ${after || 'start'}`); const response = await okta.listUsers(buildQueryParams({ limit: currentPageSize, after, filter: options.filter, search: options.search, status: options.status, lastUpdated: options.lastUpdated, sortBy: options.sortBy, sortOrder: options.sortOrder, })); // Yield the current page of users yield response.data; totalFetched += response.data.length; // Check if there are more pages if (!response.headers?.link || response.data.length < currentPageSize) { break; } // Parse the 'after' cursor from the Link header const linkHeader = response.headers.link; const afterMatch = linkHeader.match(/after=([^&>]+)/); if (!afterMatch) { break; } after = afterMatch[1]; } } catch (error) { logger.error('Error streaming users:', error); throw error; } } /** * Handle users resource with efficient streaming and pagination */ export default async function handleUsersResource(uri, container) { const okta = container.resolve('okta'); const options = parseUsersUri(uri); logger.info(`Handling users resource with options:`, options); try { // For the initial request, we'll fetch the first page and provide links const firstPageSize = Math.min(options.limit || 200, 200); const firstPageResponse = await okta.listUsers(buildQueryParams({ limit: firstPageSize, filter: options.filter, search: options.search, status: options.status, lastUpdated: options.lastUpdated, sortBy: options.sortBy, sortOrder: options.sortOrder, })); const users = firstPageResponse.data; const totalCount = firstPageResponse.headers?.['x-total-count'] ? parseInt(firstPageResponse.headers['x-total-count'], 10) : undefined; // Build the response content const content = { uri, mimeType: 'application/json', text: JSON.stringify({ users, metadata: { count: users.length, totalCount, hasMore: firstPageResponse.headers?.link?.includes('rel="next"') || false, filters: { filter: options.filter, search: options.search, status: options.status, lastUpdated: options.lastUpdated, sortBy: options.sortBy, sortOrder: options.sortOrder, }, }, }, null, 2), }; // TODO: Add support for pagination links when MCP supports it return content; } catch (error) { logger.error('Failed to handle users resource:', error); throw error; } } /** * Stream all users efficiently for bulk operations * This is an additional export for programmatic use */ export async function* streamAllUsers(container, options = {}) { const okta = container.resolve('okta'); for await (const userBatch of streamUsers(okta, options)) { for (const user of userBatch) { yield user; } } } //# sourceMappingURL=users.js.map