Skip to main content
Glama
oauth-handler.ts7.39 kB
/** * OAuth Authentication Handler * Handles OAuth 2.0 authorization code flow for user delegation */ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { OAuthAuthorizationArgs, OAuthTokenResponse } from '../types/document-generation-types.js'; import { randomUUID } from 'crypto'; // OAuth token storage (in-memory, consider Redis/database for production) const tokenStore = new Map<string, OAuthTokenResponse>(); /** * Handle OAuth authorization operations */ export async function handleOAuthAuthorization( args: OAuthAuthorizationArgs, clientId: string, clientSecret: string, tenantId: string, redirectUri: string ): Promise<string> { try { switch (args.action) { case 'get-auth-url': return getAuthorizationUrl(args, clientId, tenantId, redirectUri); case 'exchange-code': return await exchangeCodeForToken(args, clientId, clientSecret, tenantId, redirectUri); case 'refresh-token': return await refreshAccessToken(args, clientId, clientSecret, tenantId); case 'revoke': return revokeToken(args); default: throw new McpError( ErrorCode.InvalidRequest, `Unknown action: ${args.action}` ); } } catch (error) { if (error instanceof McpError) throw error; throw new McpError( ErrorCode.InternalError, `OAuth operation failed: ${error instanceof Error ? error.message : 'Unknown error'}` ); } } /** * Generate OAuth authorization URL for user consent */ function getAuthorizationUrl( args: OAuthAuthorizationArgs, clientId: string, tenantId: string, redirectUri: string ): string { // Default scopes for document generation const defaultScopes = [ 'Files.ReadWrite', 'Sites.ReadWrite.All', 'User.Read', 'offline_access' ]; const scopes = args.scopes && args.scopes.length > 0 ? args.scopes : defaultScopes; const state = args.state || randomUUID(); const authUrl = new URL(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`); authUrl.searchParams.append('client_id', clientId); authUrl.searchParams.append('response_type', 'code'); authUrl.searchParams.append('redirect_uri', redirectUri); authUrl.searchParams.append('scope', scopes.join(' ')); authUrl.searchParams.append('state', state); authUrl.searchParams.append('response_mode', 'query'); return JSON.stringify({ success: true, authorizationUrl: authUrl.toString(), state: state, scopes: scopes, instructions: [ '1. Open the authorization URL in a browser', '2. Sign in with your Microsoft 365 account', '3. Grant the requested permissions', '4. Copy the authorization code from the redirect URL', '5. Use the exchange-code action to get access token' ], redirectUri: redirectUri, expiresIn: '10 minutes' }, null, 2); } /** * Exchange authorization code for access token */ async function exchangeCodeForToken( args: OAuthAuthorizationArgs, clientId: string, clientSecret: string, tenantId: string, redirectUri: string ): Promise<string> { if (!args.code) { throw new McpError(ErrorCode.InvalidRequest, 'code is required for exchange-code action'); } const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; const params = new URLSearchParams(); params.append('client_id', clientId); params.append('client_secret', clientSecret); params.append('code', args.code); params.append('redirect_uri', redirectUri); params.append('grant_type', 'authorization_code'); params.append('scope', 'Files.ReadWrite Sites.ReadWrite.All User.Read offline_access'); const response = await fetch(tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params, }); if (!response.ok) { const errorData = await response.text(); throw new McpError( ErrorCode.InternalError, `Token exchange failed: ${response.status} ${response.statusText}. Details: ${errorData}` ); } const tokenData: OAuthTokenResponse = await response.json(); // Store token (use session ID in production) const sessionId = randomUUID(); tokenStore.set(sessionId, tokenData); return JSON.stringify({ success: true, sessionId: sessionId, accessToken: tokenData.access_token, refreshToken: tokenData.refresh_token, expiresIn: tokenData.expires_in, tokenType: tokenData.token_type, scope: tokenData.scope, message: 'Authorization successful! Use this session ID for subsequent document operations.', instructions: [ 'Store the sessionId securely', 'Use the accessToken for Graph API calls', 'Token expires in ' + tokenData.expires_in + ' seconds', 'Use refresh-token action before expiration' ] }, null, 2); } /** * Refresh access token using refresh token */ async function refreshAccessToken( args: OAuthAuthorizationArgs, clientId: string, clientSecret: string, tenantId: string ): Promise<string> { if (!args.refreshToken) { throw new McpError(ErrorCode.InvalidRequest, 'refreshToken is required for refresh-token action'); } const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; const params = new URLSearchParams(); params.append('client_id', clientId); params.append('client_secret', clientSecret); params.append('refresh_token', args.refreshToken); params.append('grant_type', 'refresh_token'); params.append('scope', 'Files.ReadWrite Sites.ReadWrite.All User.Read offline_access'); const response = await fetch(tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params, }); if (!response.ok) { const errorData = await response.text(); throw new McpError( ErrorCode.InternalError, `Token refresh failed: ${response.status} ${response.statusText}. Details: ${errorData}` ); } const tokenData: OAuthTokenResponse = await response.json(); // Store new token const sessionId = randomUUID(); tokenStore.set(sessionId, tokenData); return JSON.stringify({ success: true, sessionId: sessionId, accessToken: tokenData.access_token, refreshToken: tokenData.refresh_token, expiresIn: tokenData.expires_in, tokenType: tokenData.token_type, scope: tokenData.scope, message: 'Token refreshed successfully!' }, null, 2); } /** * Revoke OAuth token */ function revokeToken(args: OAuthAuthorizationArgs): string { // In production, call Microsoft's revocation endpoint // For now, just clear from memory return JSON.stringify({ success: true, message: 'Token revoked successfully. User will need to re-authorize.', note: 'In production, this would call Microsoft\'s token revocation endpoint' }, null, 2); } /** * Get stored token by session ID (helper function) */ export function getStoredToken(sessionId: string): OAuthTokenResponse | undefined { return tokenStore.get(sessionId); } /** * Validate if token is expired (helper function) */ export function isTokenExpired(token: OAuthTokenResponse, acquiredTime: number): boolean { const now = Date.now(); const expirationTime = acquiredTime + (token.expires_in * 1000); return now >= expirationTime; }

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