| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- /**
- * MCP 服务器配置
- */
- export interface McpServerConfig {
- id: string;
- name: string;
- url: string;
- authType: 'none' | 'jwt';
- loginApi?: string;
- baseUrl?: string;
- description?: string;
- }
- /**
- * MCP 服务器配置列表
- */
- export const MCP_SERVERS: Record<string, McpServerConfig> = {
- 'novel-translator': {
- id: 'novel-translator',
- name: 'Novel Translator MCP',
- url: 'https://d8d-ai-vscode-8080-223-236-template-6-group.dev.d8d.fun/mcp',
- authType: 'none',
- },
- 'novel-platform-user': {
- id: 'novel-platform-user',
- name: 'Novel Platform User MCP',
- url: 'https://d8d-ai-vscode-8080-223-238-template-6-group.dev.d8d.fun/mcp/',
- authType: 'jwt',
- loginApi: '/api/v1/auth/login',
- baseUrl: 'https://d8d-ai-vscode-8080-223-238-template-6-group.dev.d8d.fun',
- },
- 'novel-platform-admin': {
- id: 'novel-platform-admin',
- name: 'Novel Platform Admin MCP',
- url: 'https://d8d-ai-vscode-8080-223-238-template-6-group.dev.d8d.fun/admin-mcp/',
- authType: 'jwt',
- loginApi: '/api/v1/auth/admin-login',
- baseUrl: 'https://d8d-ai-vscode-8080-223-238-template-6-group.dev.d8d.fun',
- },
- 'template-241-mcp-app': {
- id: 'template-241-mcp-app',
- name: 'Template 241 MCP App',
- url: 'https://d8d-ai-vscode-8080-223-241-template-6-group.dev.d8d.fun/mcp',
- authType: 'none',
- description: 'MCP App 架构 - 36 个 shadcn/ui 组件',
- },
- };
- /**
- * MCP 登录响应
- */
- export interface McpLoginResponse {
- success: boolean;
- token?: string;
- username?: string;
- error?: string;
- detail?: string;
- }
- /**
- * MCP 健康状态
- */
- export interface McpHealthStatus {
- healthy: boolean;
- latency?: number; // 响应延迟(毫秒)
- error?: string;
- }
- /**
- * MCP Token 管理器
- *
- * 核心概念:
- * - Web UI 是 MCP 测试工具,不需要全局登录
- * - 每个 MCP 有独立的认证状态
- * - 可以同时管理多个 MCP 的登录
- *
- * 状态区分:
- * - isHealthy: MCP 服务器是否正常运行(health check)
- * - isLoggedIn: 是否有有效的认证 token
- */
- export class McpTokenManager {
- private static instance: McpTokenManager;
- private healthCache: Map<string, { healthy: boolean; timestamp: number }> = new Map();
- private readonly HEALTH_CACHE_TTL = 10000; // 健康检查缓存 10 秒
- private constructor() {}
- static getInstance(): McpTokenManager {
- if (!McpTokenManager.instance) {
- McpTokenManager.instance = new McpTokenManager();
- }
- return McpTokenManager.instance;
- }
- /**
- * 保存 MCP Token
- */
- saveToken(mcpType: string, token: string, username: string): void {
- if (typeof window === 'undefined') return;
- const key = `mcp_token_${mcpType}`;
- localStorage.setItem(key, token);
- localStorage.setItem(`mcp_token_${mcpType}_time`, Date.now().toString());
- localStorage.setItem(`mcp_username_${mcpType}`, username);
- // 调试:验证保存
- console.log(`[McpTokenManager] Saved token for ${mcpType}:`, {
- key,
- tokenLength: token?.length,
- username,
- verified: localStorage.getItem(key) === token
- });
- }
- /**
- * 获取 MCP Token
- */
- getToken(mcpType: string): string | null {
- if (typeof window === 'undefined') return null;
- return localStorage.getItem(`mcp_token_${mcpType}`);
- }
- /**
- * 获取 MCP 用户名
- */
- getUsername(mcpType: string): string | null {
- if (typeof window === 'undefined') return null;
- return localStorage.getItem(`mcp_username_${mcpType}`);
- }
- /**
- * 检查 MCP 是否已登录
- */
- isLoggedIn(mcpType: string): boolean {
- return !!this.getToken(mcpType);
- }
- /**
- * 获取所有已登录的 MCP Token
- */
- getAllTokens(): Record<string, string> {
- const tokens: Record<string, string> = {};
- for (const mcpType of Object.keys(MCP_SERVERS)) {
- const token = this.getToken(mcpType);
- if (token) {
- tokens[mcpType] = token;
- }
- }
- return tokens;
- }
- /**
- * 获取所有已登录的 MCP 列表
- */
- getLoggedInMcpList(): string[] {
- return Object.keys(MCP_SERVERS).filter(mcpType => this.isLoggedIn(mcpType));
- }
- /**
- * 清除 MCP Token
- */
- clearToken(mcpType: string): void {
- if (typeof window === 'undefined') return;
- localStorage.removeItem(`mcp_token_${mcpType}`);
- localStorage.removeItem(`mcp_token_${mcpType}_time`);
- localStorage.removeItem(`mcp_username_${mcpType}`);
- }
- /**
- * 清除所有 MCP Token
- */
- clearAllTokens(): void {
- for (const mcpType of Object.keys(MCP_SERVERS)) {
- this.clearToken(mcpType);
- }
- }
- /**
- * 获取 Token 存储时长(毫秒)
- */
- getTokenAge(mcpType: string): number {
- if (typeof window === 'undefined') return 0;
- const time = localStorage.getItem(`mcp_token_${mcpType}_time`);
- return time ? Date.now() - parseInt(time) : 0;
- }
- /**
- * 获取 Token 剩余有效时间(假设 24 小时有效)
- */
- getTokenRemainingTime(mcpType: string): number {
- const TOKEN_VALIDITY = 24 * 60 * 60 * 1000; // 24 小时
- const age = this.getTokenAge(mcpType);
- return Math.max(0, TOKEN_VALIDITY - age);
- }
- /**
- * 登录 MCP 服务器
- */
- async loginMcp(mcpType: string, email: string, password: string): Promise<McpLoginResponse> {
- const config = MCP_SERVERS[mcpType];
- if (!config || config.authType !== 'jwt') {
- return { success: false, error: '该 MCP 不需要登录' };
- }
- // 使用本地后端代理端点
- const isAdmin = mcpType === 'novel-platform-admin';
- const proxyUrl = isAdmin ? '/api/auth/admin-login' : '/api/auth/login';
- try {
- const response = await fetch(proxyUrl, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ email, password }),
- });
- const data = await response.json();
- console.log(`[McpTokenManager.loginMcp] Response:`, {
- ok: response.ok,
- status: response.status,
- success: data.success,
- hasToken: !!data.token,
- username: data.username
- });
- if (response.ok && data.success && data.token) {
- this.saveToken(mcpType, data.token, data.username || email);
- return { success: true, token: data.token, username: data.username };
- } else {
- return {
- success: false,
- error: data.detail || data.error || '登录失败',
- };
- }
- } catch (e) {
- console.error(`[McpTokenManager.loginMcp] Error:`, e);
- return {
- success: false,
- error: e instanceof Error ? e.message : '网络错误',
- };
- }
- }
- /**
- * 登出 MCP 服务器
- */
- logoutMcp(mcpType: string): void {
- this.clearToken(mcpType);
- this.healthCache.delete(mcpType);
- }
- /**
- * 检查 MCP 服务器健康状态
- * 通过后端代理端点 /api/mcp/health/:mcpType 进行健康检查
- */
- async checkHealth(mcpType: string): Promise<McpHealthStatus> {
- // 检查缓存
- const cached = this.healthCache.get(mcpType);
- if (cached && Date.now() - cached.timestamp < this.HEALTH_CACHE_TTL) {
- return { healthy: cached.healthy };
- }
- const config = MCP_SERVERS[mcpType];
- if (!config) {
- return { healthy: false, error: '未知的 MCP 类型' };
- }
- try {
- const startTime = Date.now();
- // 使用后端代理端点进行健康检查
- const response = await fetch(`/api/mcp/health/${mcpType}`, {
- method: 'GET',
- headers: { 'Content-Type': 'application/json' },
- });
- const latency = Date.now() - startTime;
- if (response.ok) {
- const data = await response.json();
- const healthy = data.status === 'healthy' || data.healthy === true;
- // 更新缓存
- this.healthCache.set(mcpType, {
- healthy,
- timestamp: Date.now(),
- });
- return { healthy, latency };
- } else {
- const error = await response.text().catch(() => '未知错误');
- this.healthCache.set(mcpType, { healthy: false, timestamp: Date.now() });
- return { healthy: false, error: `HTTP ${response.status}: ${error}` };
- }
- } catch (e) {
- const errorMsg = e instanceof Error ? e.message : '网络错误';
- this.healthCache.set(mcpType, { healthy: false, timestamp: Date.now() });
- return { healthy: false, error: errorMsg };
- }
- }
- /**
- * 检查 MCP 服务器是否健康(使用缓存,不发起网络请求)
- */
- isHealthy(mcpType: string): boolean {
- const cached = this.healthCache.get(mcpType);
- if (!cached) return false; // 尚未检查过,默认不健康
- // 如果缓存过期,返回 false 以触发新的检查
- if (Date.now() - cached.timestamp > this.HEALTH_CACHE_TTL) return false;
- return cached.healthy;
- }
- /**
- * 清除健康检查缓存
- */
- clearHealthCache(mcpType?: string): void {
- if (mcpType) {
- this.healthCache.delete(mcpType);
- } else {
- this.healthCache.clear();
- }
- }
- // ==================== 启用/禁用状态管理 ====================
- /**
- * 设置 MCP 服务器启用状态
- */
- setEnabled(mcpType: string, enabled: boolean): void {
- console.log(`[McpTokenManager.setEnabled] Called with mcpType="${mcpType}", enabled=${enabled}`);
- if (typeof window === 'undefined') {
- console.log(`[McpTokenManager.setEnabled] SSR mode, skipping`);
- return;
- }
- const key = `mcp_enabled_${mcpType}`;
- const value = enabled.toString();
- localStorage.setItem(key, value);
- console.log(`[McpTokenManager.setEnabled] Saved: ${key}="${value}"`);
- // 验证保存是否成功
- const saved = localStorage.getItem(key);
- console.log(`[McpTokenManager.setEnabled] Verified: ${key}="${saved}"`);
- }
- /**
- * 获取 MCP 服务器启用状态
- * SSR 时返回 false(避免 hydration 不匹配)
- * 客户端默认返回 true(启用)
- */
- isEnabled(mcpType: string): boolean {
- if (typeof window === 'undefined') return false; // SSR 时返回 false
- const value = localStorage.getItem(`mcp_enabled_${mcpType}`);
- const result = value === null ? true : value === 'true';
- console.log(`[McpTokenManager.isEnabled] ${mcpType}: value="${value}", result=${result}`);
- return result;
- }
- /**
- * 切换 MCP 服务器启用状态
- */
- toggleEnabled(mcpType: string): boolean {
- console.log(`[McpTokenManager.toggleEnabled] Called with mcpType="${mcpType}"`);
- const currentState = this.isEnabled(mcpType);
- console.log(`[McpTokenManager.toggleEnabled] Current state: ${currentState}`);
- const newState = !currentState;
- console.log(`[McpTokenManager.toggleEnabled] New state: ${newState}`);
- this.setEnabled(mcpType, newState);
- return newState;
- }
- /**
- * 获取所有已启用的 MCP 列表
- * SSR 时返回空数组(避免 hydration 不匹配)
- */
- getEnabledMcpList(): string[] {
- if (typeof window === 'undefined') return []; // SSR 时返回空数组
- const result: string[] = [];
- console.log('[McpTokenManager.getEnabledMcpList] Checking all MCPs...');
- for (const mcpType of Object.keys(MCP_SERVERS)) {
- const value = localStorage.getItem(`mcp_enabled_${mcpType}`);
- const enabled = value === null ? true : value === 'true';
- console.log(` ${mcpType}: localStorage="${value}", enabled=${enabled}`);
- if (enabled) {
- result.push(mcpType);
- }
- }
- console.log(`[McpTokenManager.getEnabledMcpList] Result: [${result.join(', ')}]`);
- return result;
- }
- /**
- * 获取已启用且已登录的 MCP 数量
- */
- getEnabledLoggedInCount(): number {
- return Object.keys(MCP_SERVERS).filter(mcpType =>
- this.isEnabled(mcpType) && this.isLoggedIn(mcpType)
- ).length;
- }
- /**
- * 获取已启用的 MCP 数量
- */
- getEnabledCount(): number {
- return Object.keys(MCP_SERVERS).filter(mcpType => this.isEnabled(mcpType)).length;
- }
- }
- // 导出单例实例
- export const mcpTokenManager = McpTokenManager.getInstance();
|