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