Skip to main content
Glama

Vextra MCP Server

by kcaitech
AGPL 3.0
3
auth.ts5.82 kB
import { AuthConfig } from "../config/index.js"; import https from 'https'; import http from 'http'; export interface AuthCredentials { access_key: string; access_secret: string; } export interface TokenResponse { token: string; expire: number; } export class AuthService { private config: AuthConfig; private tokenCache: Map<string, { token: string; expire: number }> = new Map(); constructor(config: AuthConfig) { this.config = config; } /** * 获取访问令牌 */ async getAccessToken(credentials: AuthCredentials): Promise<string> { const cacheKey = `${credentials.access_key}:${credentials.access_secret}`; // 检查缓存 const cached = this.tokenCache.get(cacheKey); if (cached && cached.expire > Date.now() + 10 * 1000) { // 多预留10秒 return cached.token; } const tokenUrl = `${this.config.addr}`; const url = new URL(tokenUrl); const postData = JSON.stringify({ access_key: credentials.access_key, access_secret: credentials.access_secret, expire: this.config.expire, }); const options = { hostname: url.hostname, port: url.port || (url.protocol === 'https:' ? 443 : 80), path: url.pathname + url.search, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData), }, // 忽略SSL证书验证 rejectUnauthorized: false, }; const client = url.protocol === 'https:' ? https : http; return new Promise((resolve, reject) => { const req = client.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { try { const responseData: TokenResponse = JSON.parse(data)?.data; if (!responseData || !responseData.token) { console.log(data) reject(new Error('Invalid response: missing token')); return; } // 缓存token(使用配置中的默认过期时间) const expiresIn = responseData.expire || this.config.expire; const expires = Date.now() + (expiresIn * 1000); this.tokenCache.set(cacheKey, { token: responseData.token, expire: expires }); console.log(`Token obtained for access_key: ${credentials.access_key}`); resolve(responseData.token); } catch (error) { reject(new Error(`Failed to parse response: ${error}`)); } } else { reject(new Error(`Authentication failed: ${res.statusCode} ${res.statusMessage} ${data}`)); } }); }); req.on('error', (error) => { reject(new Error(`Request failed: ${error.message}`)); }); req.write(postData); req.end(); }); } /** * 清除token缓存 */ clearTokenCache(credentials?: AuthCredentials): void { if (credentials) { const cacheKey = `${credentials.access_key}:${credentials.access_secret}`; this.tokenCache.delete(cacheKey); } else { this.tokenCache.clear(); } } /** * 清理过期的token */ cleanupExpiredTokens(): void { const now = Date.now(); const expiredKeys: string[] = []; for (const [key, value] of this.tokenCache) { if (value.expire <= now) { expiredKeys.push(key); } } expiredKeys.forEach(key => this.tokenCache.delete(key)); if (expiredKeys.length > 0) { console.log(`Cleaned up ${expiredKeys.length} expired tokens`); } } /** * 获取配置信息 */ getConfig(): AuthConfig { return { ...this.config }; } /** * 获取缓存统计信息 */ getCacheStats(): { total: number; valid: number; expired: number } { const now = Date.now(); let valid = 0; let expired = 0; for (const [, value] of this.tokenCache) { if (value.expire > now) { valid++; } else { expired++; } } return { total: this.tokenCache.size, valid, expired, }; } } // 单例模式 let authService: AuthService | null = null; export function initAuthService(config: AuthConfig): AuthService { if (authService) { return authService; } authService = new AuthService(config); let cleanupInterval: NodeJS.Timeout | null = setInterval(() => { authService!.cleanupExpiredTokens(); }, config.expire * 1000); const cleanup = () => { if (cleanupInterval) { clearInterval(cleanupInterval); cleanupInterval = null; } } process.on('exit', cleanup); process.on('SIGTERM', cleanup); process.on('SIGINT', cleanup); return authService; } export function getAuthService(): AuthService { if (!authService) { throw new Error("Authentication service not available"); } return authService; }

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/kcaitech/vextra-mcp'

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