Skip to main content
Glama
larksuite

Feishu/Lark OpenAPI MCP

Official
by larksuite
store.ts7.7 kB
import { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'; import { OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js'; import { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js'; import fs from 'fs'; import { storageManager } from './utils/storage-manager'; import { StorageData } from './types'; import { logger } from '../utils/logger'; export class AuthStore implements OAuthRegisteredClientsStore { private storageDataCache: StorageData = { tokens: {}, clients: {} }; private codeVerifiers: Map<string, string> = new Map(); private initializePromise: Promise<void> | undefined; private fileWatcher: fs.FSWatcher | undefined; private isReloading = false; private isInitializedStorageSuccess = false; constructor() { this.initialize(); } private async initialize(): Promise<void> { if (this.initializePromise) { return this.initializePromise; } this.initializePromise = this.performInitialization(); await this.initializePromise; } private async performInitialization(): Promise<void> { try { await this.loadFromStorage(); logger.info( `[AuthStore] Initialized storage successfully with ${Object.keys(this.storageDataCache.tokens).length} tokens`, ); await this.clearExpiredTokens(); this.setupFileWatcher(); this.isInitializedStorageSuccess = true; } catch (error) { logger.error(`[AuthStore] Failed to initialize: ${error}`); this.isInitializedStorageSuccess = false; } } private setupFileWatcher(): void { try { if (fs.existsSync(storageManager.storageFile)) { logger.info(`[AuthStore] Setup file watcher for ${storageManager.storageFile}`); this.fileWatcher = fs.watch(storageManager.storageFile, () => { this.handleFileChange(); }); } } catch (error) { logger.error(`[AuthStore] Failed to setup file watcher: ${error}`); } } private async handleFileChange(): Promise<void> { if (this.isReloading) { return; } this.isReloading = true; try { logger.info(`[AuthStore] Reloading storage from ${storageManager.storageFile}`); await new Promise((resolve) => setTimeout(resolve, 100)); await this.loadFromStorage(); } catch (error) { logger.error(`[AuthStore] Failed to reload storage: ${error}`); } finally { this.isReloading = false; } } private async loadFromStorage(): Promise<void> { const storageData = await storageManager.loadStorageData(); this.storageDataCache = storageData; } private async saveToStorage(): Promise<void> { if (!this.isInitializedStorageSuccess) { return; } await storageManager.saveStorageData(this.storageDataCache); } private async clearExpiredTokens(): Promise<void> { if (!this.storageDataCache || !this.storageDataCache.tokens) { return; } const now = Date.now() / 1000; let hasExpiredTokens = false; const expiredTokenKeys: string[] = []; for (const [tokenKey, token] of Object.entries(this.storageDataCache.tokens)) { // 7 days after expires clear the token if (token.expiresAt && token.expiresAt + 7 * 24 * 60 * 60 < now) { expiredTokenKeys.push(tokenKey); } } if (expiredTokenKeys.length > 0) { for (const tokenKey of expiredTokenKeys) { delete this.storageDataCache.tokens[tokenKey]; } hasExpiredTokens = true; } if (this.storageDataCache.localTokens) { const orphanedLocalTokenKeys: string[] = []; for (const [appId, tokenKey] of Object.entries(this.storageDataCache.localTokens)) { if (!this.storageDataCache.tokens[tokenKey]) { orphanedLocalTokenKeys.push(appId); } } if (orphanedLocalTokenKeys.length > 0) { for (const appId of orphanedLocalTokenKeys) { delete this.storageDataCache.localTokens[appId]; } hasExpiredTokens = true; } } if (hasExpiredTokens) { logger.info(`[AuthStore] Cleared expired tokens`); await this.saveToStorage(); } } async storeToken(token: AuthInfo): Promise<AuthInfo> { await this.initialize(); this.storageDataCache.tokens[token.token] = token; await this.saveToStorage(); return token; } async removeToken(accessToken: string): Promise<void> { await this.initialize(); delete this.storageDataCache.tokens[accessToken]; await this.saveToStorage(); } async getToken(accessToken: string): Promise<AuthInfo | undefined> { await this.initialize(); return this.storageDataCache.tokens[accessToken]; } async getTokenByRefreshToken(refreshToken: string): Promise<AuthInfo | undefined> { await this.initialize(); return Object.values(this.storageDataCache.tokens).find((token) => token.extra?.refreshToken === refreshToken); } async getLocalAccessToken(appId: string): Promise<string | undefined> { await this.initialize(); return this.storageDataCache.localTokens?.[appId]; } async storeLocalAccessToken(accessToken: string, appId: string): Promise<string> { await this.initialize(); if (!this.storageDataCache.localTokens) { this.storageDataCache.localTokens = {}; } this.storageDataCache.localTokens[appId] = accessToken; await this.saveToStorage(); return accessToken; } async removeLocalAccessToken(appId: string): Promise<void> { await this.initialize(); if (this.storageDataCache.localTokens?.[appId]) { logger.info(`[AuthStore] Removing local access token for app: ${appId}`); const tokenToRemove = this.storageDataCache.localTokens[appId]; delete this.storageDataCache.tokens[tokenToRemove]; delete this.storageDataCache.localTokens[appId]; await this.saveToStorage(); } } async removeAllLocalAccessTokens(): Promise<void> { await this.initialize(); logger.info('[AuthStore] Removing all local access tokens'); if (this.storageDataCache.localTokens) { const tokens = Object.values(this.storageDataCache.localTokens); for (const token of tokens) { if (this.storageDataCache.tokens[token]) { delete this.storageDataCache.tokens[token]; } } } this.storageDataCache.localTokens = {}; await this.saveToStorage(); } async getAllLocalAccessTokens(): Promise<{ [appId: string]: string }> { await this.initialize(); return this.storageDataCache.localTokens || {}; } async registerClient(client: OAuthClientInformationFull): Promise<OAuthClientInformationFull> { await this.initialize(); this.storageDataCache.clients[client.client_id] = client; await this.saveToStorage(); return client; } async getClient(id: string): Promise<OAuthClientInformationFull | undefined> { await this.initialize(); return this.storageDataCache.clients[id]; } async removeClient(clientId: string): Promise<void> { await this.initialize(); delete this.storageDataCache.clients[clientId]; await this.saveToStorage(); } storeCodeVerifier(key: string, codeVerifier: string): void { this.codeVerifiers.set(key, codeVerifier); } getCodeVerifier(key: string): string | undefined { return this.codeVerifiers.get(key); } removeCodeVerifier(key: string): void { this.codeVerifiers.delete(key); } clearExpiredCodeVerifiers(): void { this.codeVerifiers.clear(); } destroy(): void { if (this.fileWatcher) { this.fileWatcher.close(); this.fileWatcher = undefined; } } } export const authStore = new AuthStore();

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/larksuite/lark-openapi-mcp'

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