Skip to main content
Glama

Superjolt MCP Server

by scoritz
auth.service.ts•4.43 kB
import { Injectable } from '@nestjs/common'; import { HttpService } from '@nestjs/axios'; import { ConfigService } from './config.service'; import { StorageService } from './storage.service'; import { LoggerService } from './logger.service'; import { firstValueFrom } from 'rxjs'; const TOKEN_KEY = 'token'; @Injectable() export class AuthService { private tokenCache: string | null = null; constructor( private readonly httpService: HttpService, private readonly configService: ConfigService, private readonly storageService: StorageService, private readonly logger: LoggerService, ) {} async getToken(): Promise<string | null> { // Check cache first if (this.tokenCache) { return this.tokenCache; } // Check environment variable if (process.env.SUPERJOLT_TOKEN) { this.tokenCache = process.env.SUPERJOLT_TOKEN; return this.tokenCache; } // Get from storage (will try keytar first, then file) const token = await this.storageService.get(TOKEN_KEY, { secure: true }); if (token) { this.tokenCache = token; return token; } return null; } async setToken(token: string): Promise<void> { this.tokenCache = token; // Store securely (will try keytar first, then file) await this.storageService.set(TOKEN_KEY, token, { secure: true }); } async deleteToken(): Promise<void> { this.tokenCache = null; // Delete from storage (will handle both keytar and file) await this.storageService.delete(TOKEN_KEY, { secure: true }); } async getTokenSource(): Promise<'env' | 'keychain' | 'file' | null> { // Check environment variable first if (process.env.SUPERJOLT_TOKEN) { return 'env'; } // Check storage const token = await this.storageService.get(TOKEN_KEY, { secure: true }); if (!token) { return null; } // Check if using keytar or file try { const keytar = require('keytar'); const keytarToken = await keytar.getPassword('superjolt-cli', 'token'); if (keytarToken) { return 'keychain'; } } catch { // Keytar not available } return 'file'; } async performOAuthFlow(): Promise<string> { this.logger.log('šŸ” Authenticating with Superjolt...\n'); // Generate state for auth flow using built-in crypto const { randomUUID } = await import('crypto'); const state = randomUUID(); // Get auth URL from API const authEndpoint = this.configService.getVersionedUrl( `auth/github?state=${state}&source=cli`, ); this.logger.log(`Fetching auth URL from: ${authEndpoint}`); const authResponse = await firstValueFrom( this.httpService.get(authEndpoint, { timeout: 10000, // 10 second timeout }), ); this.logger.log('>>', authResponse.data); const authUrl = authResponse.data.url; this.logger.log('Opening browser for GitHub authentication...'); this.logger.log('If the browser does not open, please visit:'); this.logger.log(`\n${authUrl}\n`); // Open browser using dynamic import const open = (await import('open')).default; await open(authUrl); // Poll for completion this.logger.log('ā³ Waiting for authentication...'); const startTime = Date.now(); const timeout = 5 * 60 * 1000; // 5 minutes while (Date.now() - startTime < timeout) { try { const pollResponse = await firstValueFrom( this.httpService.get( this.configService.getVersionedUrl(`auth/poll?state=${state}`), ), ); const { status, token } = pollResponse.data; if (status === 'completed' && token) { // Save token await this.setToken(token); this.logger.log('\nāœ… Authentication successful!\n'); return token; } else if (status === 'failed') { throw new Error('Authentication failed'); } else if (status === 'expired') { throw new Error('Authentication session expired'); } // Wait before polling again await new Promise((resolve) => setTimeout(resolve, 2000)); } catch (error: any) { if (error.response?.status === 404) { // Session might have expired throw new Error('Authentication session expired'); } throw error; } } throw new Error('Authentication timed out'); } }

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/scoritz/superjolt'

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