@mcp-apps/api-tools-mcp-server
Version:
MCP server for interacting with APIs and web services
82 lines (65 loc) • 3.11 kB
text/typescript
import { InteractiveBrowserCredential, useIdentityPlugin } from "@azure/identity";
import { nativeBrokerPlugin } from "@azure/identity-broker";
import * as dotenv from "dotenv";
// Initialize the broker plugin for WAM support
useIdentityPlugin(nativeBrokerPlugin);
dotenv.config();
interface TokenCacheEntry {
token: string;
expiresAt: number;
}
// Map to store tokens by clientId + tenantId combination
const tokenCache: Map<string, TokenCacheEntry> = new Map();
// Helper function to create a cache key
function createCacheKey(clientId: string, tenantId: string): string {
return `${clientId}|${tenantId}`;
}
export async function getAccessToken(clientId: string | undefined, tenantId: string | undefined, scopes: string[] | undefined, useBroker: boolean = true): Promise<string> {
const now = Date.now();
// Resolve clientId and tenantId, using environment variables as fallback
const resolvedClientId = clientId || process.env.AZURE_CLIENT_ID || '';
const resolvedTenantId = tenantId || process.env.AZURE_TENANT_ID || 'common';
// Create cache key for this client+tenant combination
const cacheKey = createCacheKey(resolvedClientId, resolvedTenantId);
// Check if we have a valid cached token for this combination
const cachedEntry = tokenCache.get(cacheKey);
if (cachedEntry && cachedEntry.expiresAt > now) {
return cachedEntry.token;
}
try {
const credentialOptions: any = {
clientId: resolvedClientId,
tenantId: resolvedTenantId,
loginStyle: "popup"
};
// Add broker authentication if requested
if (useBroker) {
console.log("Using Windows broker authentication (WAM) for interactive login");
credentialOptions.brokerOptions = {
enabled: true,
parentWindowHandle: new Uint8Array(0), // Empty Uint8Array will use the active window
useDefaultBrokerAccount: false, // Try default account before falling back to interactive
legacyEnableMsaPassthrough: true // Set to true if MSA account passthrough is needed
};
} else {
console.log("Using standard interactive browser authentication (WAM disabled)");
}
const credential = new InteractiveBrowserCredential(credentialOptions);
const tokenResponse = await credential.getToken([`${resolvedClientId}/.default`].concat(scopes ? scopes : []));
if (!tokenResponse || !tokenResponse.token) {
throw new Error("Failed to acquire Azure DevOps token");
}
// Set expiration time (expiresOn is in seconds from epoch)
const expirationTime = tokenResponse.expiresOnTimestamp;
const expiresAt = expirationTime - (5 * 60 * 1000); // Token lifetime minus 5 minute safety buffer
// Store the token in cache using the client+tenant key
tokenCache.set(cacheKey, {
token: tokenResponse.token,
expiresAt: expiresAt
});
return tokenResponse.token;
} catch (error) {
console.error("Error acquiring token:", error);
throw new Error("Failed to acquire Azure DevOps access token");
}
}