Skip to main content
Glama

call_microsoft_api

Make direct API calls to Microsoft Graph or Azure Resource Management endpoints with full control over HTTP methods, parameters, and request configuration.

Instructions

Make direct calls to any Microsoft Graph or Azure Resource Management API endpoint with full control over HTTP methods and parameters.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
apiTypeYesAPI type: Microsoft Graph or Azure Resource Management
pathYesAPI URL path (e.g., '/users')
methodYesHTTP method
apiVersionNoAzure API version (required for Azure APIs)
subscriptionIdNoAzure Subscription ID (for Azure APIs)
queryParamsNoQuery parameters
bodyNoRequest body (for POST, PUT, PATCH)
graphApiVersionNoMicrosoft Graph API version to use (default: v1.0)v1.0
fetchAllNoSet to true to automatically fetch all pages for list results (e.g., users, groups). Default is false.
consistencyLevelNoGraph API ConsistencyLevel header. ADVISED to be set to 'eventual' for Graph GET requests using advanced query parameters ($filter, $count, $search, $orderby).
maxRetriesNoMaximum number of retries for failed requests (0-5, default: 3)
retryDelayNoBase delay between retries in milliseconds (100-10000, default: 1000)
timeoutNoRequest timeout in milliseconds (5000-300000, default: 30000)
customHeadersNoAdditional custom headers to include in the request
responseFormatNoResponse format: 'json' (full response), 'raw' (as received), 'minimal' (values only)json
selectFieldsNoArray of specific fields to select (applies $select automatically for Graph API)
expandFieldsNoArray of fields to expand (applies $expand automatically for Graph API)
batchSizeNoBatch size for pagination when fetchAll is true (1-1000, default: 100)

Implementation Reference

  • Main handler function that executes the tool logic. Supports calling any Microsoft Graph or Azure Resource Management API endpoint with advanced features like automatic pagination (fetchAll), retries with exponential backoff, custom headers, query parameters, ConsistencyLevel for advanced Graph queries, select/expand fields, timeouts, and detailed error reporting.
    export async function handleCallMicrosoftApi( graphClient: Client, args: CallMicrosoftApiArgs, getAccessToken: (scope: string) => Promise<string>, apiConfigs: any, rateLimiter?: any, tokenCache?: any ): Promise<{ content: { type: string; text: string }[]; isError?: boolean }> { const startTime = Date.now(); // Extract parameters with defaults const { apiType, path, method, apiVersion, subscriptionId, queryParams = {}, body, graphApiVersion = 'v1.0', fetchAll = false, consistencyLevel, maxRetries = 3, retryDelay = 1000, timeout = 30000, customHeaders = {}, responseFormat = 'json', selectFields, expandFields, batchSize = 100 } = args; // Apply rate limiting if available if (rateLimiter) { await rateLimiter.checkLimit(); } let determinedUrl: string | undefined; // Enhanced token caching helper const getTokenWithCache = async (scope: string): Promise<string> => { if (tokenCache) { const cached = tokenCache.get(scope); if (cached) { return cached; } } const token = await getAccessToken(scope); if (tokenCache) { tokenCache.set(scope, token); } return token; }; // Auto-apply selectFields and expandFields for Graph API if (apiType === 'graph') { if (selectFields && selectFields.length > 0) { queryParams['$select'] = selectFields.join(','); } if (expandFields && expandFields.length > 0) { queryParams['$expand'] = expandFields.join(','); } if (fetchAll && batchSize !== 100) { queryParams['$top'] = batchSize.toString(); } } // Retry logic wrapper with exponential backoff const executeWithRetry = async (operation: () => Promise<any>): Promise<any> => { let lastError: any; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { if (attempt > 0) { const delay = retryDelay * Math.pow(2, attempt - 1); // Exponential backoff console.debug(`Retry attempt ${attempt}/${maxRetries}, waiting ${delay}ms`); await new Promise(resolve => setTimeout(resolve, delay)); } return await operation(); } catch (error: any) { lastError = error; // Don't retry on authentication errors or 4xx client errors (except 429) if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 429) { throw error; } if (attempt === maxRetries) { console.error(`All retry attempts exhausted for ${apiType} ${method} ${path}`); throw error; } console.warn(`Attempt ${attempt + 1} failed, will retry:`, error.message); } } throw lastError; }; try { if (apiType === 'azure' && !apiVersion) { throw new McpError(ErrorCode.InvalidParams, "apiVersion is required for apiType 'azure'"); } let responseData: any; // --- Microsoft Graph Logic (Enhanced) --- if (apiType === 'graph') { determinedUrl = `https://graph.microsoft.com/${graphApiVersion}`; responseData = await executeWithRetry(async () => { let request = graphClient.api(path).version(graphApiVersion); // Add query parameters if provided if (Object.keys(queryParams).length > 0) { request = request.query(queryParams); } // Add ConsistencyLevel header if provided if (consistencyLevel) { request = request.header('ConsistencyLevel', consistencyLevel); } // Add custom headers Object.entries(customHeaders).forEach(([key, value]) => { request = request.header(key, value); }); // Note: Graph SDK doesn't support timeout directly, but we can implement AbortController for timeout // Handle different methods switch (method.toLowerCase()) { case 'get': if (fetchAll) { // Initialize with empty array for collecting all items let allItems: any[] = []; let nextLink: string | null | undefined = null; // Get first page const firstPageResponse = await request.get(); // Store context from first page const odataContext = firstPageResponse['@odata.context']; // Add items from first page if (firstPageResponse.value && Array.isArray(firstPageResponse.value)) { allItems = [...firstPageResponse.value]; } // Get nextLink from first page nextLink = firstPageResponse['@odata.nextLink']; // Fetch subsequent pages while (nextLink) { // Create a new request for the next page const nextPageResponse = await graphClient.api(nextLink).get(); // Add items from next page if (nextPageResponse.value && Array.isArray(nextPageResponse.value)) { allItems = [...allItems, ...nextPageResponse.value]; } // Update nextLink nextLink = nextPageResponse['@odata.nextLink']; } // Construct final response return { '@odata.context': odataContext, value: allItems, totalCount: allItems.length, fetchedAt: new Date().toISOString() }; } else { return await request.get(); } case 'post': return await request.post(body ?? {}); case 'put': return await request.put(body ?? {}); case 'patch': return await request.patch(body ?? {}); case 'delete': const deleteResult = await request.delete(); // Handle potential 204 No Content response return deleteResult === undefined || deleteResult === null ? { status: "Success (No Content)", deletedAt: new Date().toISOString() } : deleteResult; default: throw new Error(`Unsupported method: ${method}`); } }); } // --- Azure Resource Management Logic (Enhanced) --- else { // apiType === 'azure' determinedUrl = "https://management.azure.com"; responseData = await executeWithRetry(async () => { const token = await getTokenWithCache("https://management.azure.com/.default"); let url = determinedUrl!; if (subscriptionId) { url += `/subscriptions/${subscriptionId}`; } url += path.startsWith('/') ? path : `/${path}`; const urlParams = new URLSearchParams(); urlParams.append('api-version', apiVersion!); Object.entries(queryParams).forEach(([key, value]) => { urlParams.append(key, value); }); url += `?${urlParams.toString()}`; // Prepare request options const headers: Record<string, string> = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', ...customHeaders }; const requestOptions: RequestInit = { method: method.toUpperCase(), headers: headers, signal: AbortSignal.timeout(timeout) }; if (["POST", "PUT", "PATCH"].includes(method.toUpperCase()) && body !== undefined) { requestOptions.body = typeof body === 'string' ? body : JSON.stringify(body); } // --- Pagination Logic for Azure RM --- if (fetchAll && method.toLowerCase() === 'get') { console.debug(`Fetching all pages for Azure RM starting from: ${url}`); let allValues: any[] = []; let currentUrl: string | null = url; while (currentUrl) { console.debug(`Fetching Azure RM page: ${currentUrl}`); // Re-acquire token for each page (Azure tokens might expire) const currentPageToken = await getTokenWithCache("https://management.azure.com/.default"); const currentPageHeaders = { ...headers, 'Authorization': `Bearer ${currentPageToken}` }; const currentPageRequestOptions: RequestInit = { method: 'GET', headers: currentPageHeaders, signal: AbortSignal.timeout(timeout) }; const pageResponse = await fetch(currentUrl, currentPageRequestOptions); const pageText = await pageResponse.text(); let pageData: any; try { pageData = pageText ? JSON.parse(pageText) : {}; } catch (e) { console.error(`Failed to parse JSON from Azure RM page: ${currentUrl}`, pageText); pageData = { rawResponse: pageText }; } if (!pageResponse.ok) { console.error(`API error on Azure RM page ${currentUrl}:`, pageData); throw new Error(`API error (${pageResponse.status}) during Azure RM pagination on ${currentUrl}: ${JSON.stringify(pageData)}`); } if (pageData.value && Array.isArray(pageData.value)) { allValues = allValues.concat(pageData.value); } else if (currentUrl === url && !pageData.nextLink) { // If this is the first page and there's no nextLink, it might be a single resource allValues.push(pageData); } currentUrl = pageData.nextLink || null; // Azure uses nextLink } return { value: allValues, totalCount: allValues.length, fetchedAt: new Date().toISOString() }; } else { // Single page fetch for Azure RM console.debug(`Fetching single page for Azure RM: ${url}`); const apiResponse = await fetch(url, requestOptions); const responseText = await apiResponse.text(); try { const data = responseText ? JSON.parse(responseText) : {}; if (!apiResponse.ok) { throw new Error(`API error (${apiResponse.status}): ${JSON.stringify(data)}`); } return data; } catch (e) { if (!apiResponse.ok) { throw new Error(`API error (${apiResponse.status}): ${responseText}`); } return { rawResponse: responseText }; } } }); } // --- Enhanced Response Formatting --- const executionTime = Date.now() - startTime; let resultText = ""; switch (responseFormat) { case "minimal": if (responseData && responseData.value && Array.isArray(responseData.value)) { resultText = JSON.stringify(responseData.value, null, 2); } else if (responseData && typeof responseData === 'object') { // Extract just the data, excluding metadata const { '@odata.context': _, '@odata.nextLink': __, ...cleanData } = responseData; resultText = JSON.stringify(cleanData, null, 2); } else { resultText = JSON.stringify(responseData, null, 2); } break; case "raw": resultText = JSON.stringify(responseData); break; default: // "json" resultText = `Result for ${apiType} API (${apiType === 'graph' ? graphApiVersion : apiVersion}) - ${method.toUpperCase()} ${path}:\n`; resultText += `Execution time: ${executionTime}ms\n`; if (fetchAll && responseData.totalCount !== undefined) { resultText += `Total items fetched: ${responseData.totalCount}\n`; } resultText += `\n${JSON.stringify(responseData, null, 2)}`; break; } // Add pagination note for single-page requests if (!fetchAll && method.toLowerCase() === 'get' && responseFormat === 'json') { const nextLinkKey = apiType === 'graph' ? '@odata.nextLink' : 'nextLink'; if (responseData && responseData[nextLinkKey]) { resultText += `\n\nNote: More results are available. To retrieve all pages, add 'fetchAll: true' to your request.`; } } return { content: [{ type: "text", text: resultText }], }; } catch (error) { const executionTime = Date.now() - startTime; console.error(`Error in enhanced Microsoft API call (apiType: ${apiType}, path: ${path}, method: ${method}, executionTime: ${executionTime}ms):`, error); // Try to determine the base URL even in case of error if (!determinedUrl) { determinedUrl = apiType === 'graph' ? `https://graph.microsoft.com/${graphApiVersion}` : "https://management.azure.com"; } // Include error body if available let errorBody = 'N/A'; let statusCode = 'N/A'; // Type guard for error object with body property if (error && typeof error === 'object') { if ('body' in error) { const body = (error as any).body; errorBody = typeof body === 'string' ? body : JSON.stringify(body); } if ('statusCode' in error) { statusCode = String((error as any).statusCode); } } return { content: [{ type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error), statusCode: statusCode, errorBody: errorBody, attemptedBaseUrl: determinedUrl, executionTime: executionTime, retryAttempts: maxRetries, timestamp: new Date().toISOString() }, null, 2), }], isError: true }; } }
  • Zod input schema defining all parameters for the tool, matching exactly the usage in prompts (path/endpoint, etc.). Includes support for Graph-specific features like fetchAll pagination, $select/$expand, and advanced options like retries and timeouts.
    export const callMicrosoftApiSchema = z.object({ // Core API parameters (existing) apiType: z.enum(['graph', 'azure']).describe('API type: Microsoft Graph or Azure Resource Management'), path: z.string().describe('API URL path (e.g., \'/users\')'), method: z.enum(['get', 'post', 'put', 'patch', 'delete']).describe('HTTP method'), apiVersion: z.string().optional().describe('Azure API version (required for Azure APIs)'), subscriptionId: z.string().optional().describe('Azure Subscription ID (for Azure APIs)'), queryParams: z.record(z.string(), z.string()).optional().describe('Query parameters'), body: z.record(z.string(), z.any()).optional().describe('Request body (for POST, PUT, PATCH)'), // Graph API specific parameters (existing + enhanced) graphApiVersion: z.enum(['v1.0', 'beta']).optional().default('v1.0').describe('Microsoft Graph API version to use (default: v1.0)'), fetchAll: z.boolean().optional().default(false).describe('Set to true to automatically fetch all pages for list results (e.g., users, groups). Default is false.'), consistencyLevel: z.string().optional().describe('Graph API ConsistencyLevel header. ADVISED to be set to \'eventual\' for Graph GET requests using advanced query parameters ($filter, $count, $search, $orderby).'), // Enhanced performance and reliability features maxRetries: z.number().min(0).max(5).optional().default(3).describe('Maximum number of retries for failed requests (0-5, default: 3)'), retryDelay: z.number().min(100).max(10000).optional().default(1000).describe('Base delay between retries in milliseconds (100-10000, default: 1000)'), timeout: z.number().min(5000).max(300000).optional().default(30000).describe('Request timeout in milliseconds (5000-300000, default: 30000)'), // Customization features customHeaders: z.record(z.string(), z.string()).optional().describe('Additional custom headers to include in the request'), responseFormat: z.enum(['json', 'raw', 'minimal']).optional().default('json').describe('Response format: \'json\' (full response), \'raw\' (as received), \'minimal\' (values only)'), // Graph API enhancement features selectFields: z.array(z.string()).optional().describe('Array of specific fields to select (applies $select automatically for Graph API)'), expandFields: z.array(z.string()).optional().describe('Array of fields to expand (applies $expand automatically for Graph API)'), batchSize: z.number().min(1).max(1000).optional().default(100).describe('Batch size for pagination when fetchAll is true (1-1000, default: 100)'), });
  • src/server.ts:610-630 (registration)
    MCP tool registration in the main server class setupTools() method, specifying the tool name 'call_microsoft_api', description, Zod schema, annotations, and handler wrapper that calls the main implementation.
    this.server.tool( "call_microsoft_api", "Make direct calls to any Microsoft Graph or Azure Resource Management API endpoint with full control over HTTP methods and parameters.", callMicrosoftApiSchema.shape, {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false}, wrapToolHandler(async (args: CallMicrosoftApiArgs) => { // Validate credentials only when tool is executed (lazy loading) this.validateCredentials(); try { return await handleCallMicrosoftApi(this.getGraphClient(), args, this.getAccessToken.bind(this), apiConfigs); } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `Error executing tool: ${error instanceof Error ? error.message : 'Unknown error'}` ); } }) );

Latest Blog Posts

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/DynamicEndpoints/m365-core-mcp'

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