Skip to main content
Glama

Feishu MCP Server

callbackService.ts4.12 kB
import { Request, Response } from 'express'; import { AuthService } from './feishuAuthService.js'; import { Config } from '../utils/config.js'; import { renderFeishuAuthResultHtml } from '../utils/document.js'; import { AuthUtils,TokenCacheManager } from '../utils/auth/index.js'; // 通用响应码 const CODE = { SUCCESS: 0, PARAM_ERROR: 400, CUSTOM: 500, }; // 封装响应方法 function sendSuccess(res: Response, data: any) { const html = renderFeishuAuthResultHtml(data); res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.status(200).send(html); } function sendFail(res: Response, msg: string, code: number = CODE.CUSTOM) { const html = renderFeishuAuthResultHtml({ error: msg, code }); res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.status(200).send(html); } const authService = new AuthService(); const config = Config.getInstance(); export async function callback(req: Request, res: Response) { const code = req.query.code as string; const state = req.query.state as string; console.log(`[callback] query:`, req.query); if (!code) { console.log('[callback] 缺少code参数'); return sendFail(res, '缺少code参数', CODE.PARAM_ERROR); } if (!state) { console.log('[callback] 缺少state参数'); return sendFail(res, '缺少state参数', CODE.PARAM_ERROR); } // 解析state参数 const stateData = AuthUtils.decodeState(state); if (!stateData) { console.log('[callback] state参数解析失败'); return sendFail(res, 'state参数格式错误', CODE.PARAM_ERROR); } const { appId, appSecret, clientKey, redirectUri } = stateData; console.log(`[callback] 解析state成功:`, { appId, clientKey, redirectUri }); // 验证state中的appId和appSecret是否与配置匹配 const configAppId = config.feishu.appId; const configAppSecret = config.feishu.appSecret; if (appId !== configAppId || appSecret !== configAppSecret) { console.log('[callback] state中的appId或appSecret与配置不匹配'); return sendFail(res, 'state参数验证失败', CODE.PARAM_ERROR); } // 使用从state中解析的redirect_uri,如果没有则使用默认值 const redirect_uri = redirectUri || `http://localhost:${config.server.port}/callback`; const session = (req as any).session; const code_verifier = session?.code_verifier || undefined; try { // 获取 user_access_token const tokenResp = await authService.getUserTokenByCode({ client_id: appId, client_secret: appSecret, code, redirect_uri, code_verifier }); const data = (tokenResp && typeof tokenResp === 'object') ? tokenResp : undefined; console.log('[callback] feishu response:', data); if (!data || data.code !== 0 || !data.access_token) { return sendFail(res, `获取 access_token 失败,飞书返回: ${JSON.stringify(tokenResp)}`, CODE.CUSTOM); } // 使用TokenCacheManager缓存token信息 const tokenCacheManager = TokenCacheManager.getInstance(); if (data.access_token && data.expires_in) { // 计算过期时间戳 data.expires_at = Math.floor(Date.now() / 1000) + data.expires_in; if (data.refresh_token_expires_in) { data.refresh_token_expires_at = Math.floor(Date.now() / 1000) + data.refresh_token_expires_in; } // 缓存token信息 const refreshTtl = data.refresh_token_expires_in || 3600 * 24 * 365; // 默认1年 tokenCacheManager.cacheUserToken(clientKey, data, refreshTtl); console.log(`[callback] token已缓存到clientKey: ${clientKey}`); } // 获取用户信息 const access_token = data.access_token; let userInfo = null; if (access_token) { userInfo = await authService.getUserInfo(access_token); console.log('[callback] feishu userInfo:', userInfo); } return sendSuccess(res, { ...data, userInfo, clientKey }); } catch (e) { console.error('[callback] 请求飞书token或用户信息失败:', e); return sendFail(res, `请求飞书token或用户信息失败: ${e}`, CODE.CUSTOM); } }

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/cso1z/Feishu-MCP'

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