Skip to main content
Glama

login

Authenticate to access PingCode project management data by opening a browser for login with third-party support. Credentials are automatically saved after successful authentication.

Instructions

打开浏览器进行 PingCode 登录(支持飞书等第三方登录)。登录成功后凭证会自动保存。

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • Core handler function for the 'login' tool. Launches a non-headless Chrome browser using persistent context, navigates to PingCode signin page, waits up to 5 minutes for user to login (detects successful redirect), extracts PingCode cookies, optionally user info, saves credentials to disk, and returns success/message.
    export async function login(): Promise<{ success: boolean; message: string }> {
      let context: BrowserContext | null = null;
    
      try {
        console.error('正在启动浏览器...');
        
        // 使用独立的配置文件目录,首次登录后会保存登录状态
        // 下次登录时会复用已保存的状态(包括飞书授权)
        context = await chromium.launchPersistentContext(CHROME_USER_DATA_DIR, {
          headless: false,
          channel: 'chrome', // 使用系统 Chrome
          viewport: { width: 1280, height: 800 },
          args: ['--disable-blink-features=AutomationControlled'],
        });
    
        const page = await context.newPage();
    
        console.error(`正在打开登录页面: ${LOGIN_URL}`);
        await page.goto(LOGIN_URL, { waitUntil: 'networkidle' });
    
        console.error('请在浏览器中完成登录(支持飞书等第三方登录)...');
        console.error(`登录超时时间: ${LOGIN_TIMEOUT / 1000 / 60} 分钟`);
    
        // 等待用户完成登录(检测 URL 变化)
        try {
          await page.waitForURL(SUCCESS_URL_PATTERN, {
            timeout: LOGIN_TIMEOUT,
          });
        } catch {
          // 如果超时,检查当前 URL 是否已经是登录成功的页面
          const currentUrl = page.url();
          if (!SUCCESS_URL_PATTERN.test(currentUrl)) {
            return {
              success: false,
              message: '登录超时或被取消',
            };
          }
        }
    
        console.error('登录成功,正在保存凭证...');
    
        // 从页面提取当前用户信息
        let currentUser: { id: string; name: string } | undefined;
        try {
          // 等待页面加载完成
          await page.waitForLoadState('networkidle');
          
          // 从 localStorage 或页面提取用户信息
          const userInfo = await page.evaluate(() => {
            // 尝试从 localStorage 获取
            const stored = localStorage.getItem('user') || localStorage.getItem('currentUser');
            if (stored) {
              try {
                const user = JSON.parse(stored);
                return { id: user._id || user.id, name: user.display_name || user.name };
              } catch {}
            }
            // 尝试从 window 对象获取
            const w = window as any;
            if (w.__INITIAL_STATE__?.user) {
              const user = w.__INITIAL_STATE__.user;
              return { id: user._id || user.id, name: user.display_name || user.name };
            }
            if (w.currentUser) {
              return { id: w.currentUser._id || w.currentUser.id, name: w.currentUser.display_name || w.currentUser.name };
            }
            return null;
          });
          
          if (userInfo && userInfo.id) {
            currentUser = userInfo;
            console.error(`当前用户: ${currentUser.name} (${currentUser.id})`);
          }
        } catch {
          console.error('未能获取用户信息,继续保存凭证...');
        }
    
        // 获取所有 cookies
        const cookies = await context.cookies();
        const pingcodeCookies: CookieData[] = cookies
          .filter((c) => c.domain.includes('pingcode.com'))
          .map((c) => ({
            name: c.name,
            value: c.value,
            domain: c.domain,
            path: c.path,
            expires: c.expires,
            httpOnly: c.httpOnly,
            secure: c.secure,
            sameSite: c.sameSite as 'Strict' | 'Lax' | 'None',
          }));
    
        if (pingcodeCookies.length === 0) {
          return {
            success: false,
            message: '未能获取有效的登录凭证',
          };
        }
    
        // 计算过期时间(取最早过期的 cookie,或默认 7 天)
        const expiresAt = pingcodeCookies.reduce((min, c) => {
          if (c.expires && c.expires > 0) {
            const expMs = c.expires * 1000;
            return expMs < min ? expMs : min;
          }
          return min;
        }, Date.now() + 7 * 24 * 60 * 60 * 1000);
    
        // 保存凭证
        const credentials: Credentials = {
          cookies: pingcodeCookies,
          domain: PINGCODE_DOMAIN,
          saved_at: Date.now(),
          expires_at: expiresAt,
          user: currentUser,
        };
    
        saveCredentials(credentials);
    
        return {
          success: true,
          message: `登录成功!凭证已保存到 ${getCredentialsPath()}`,
        };
      } catch (error: any) {
        return {
          success: false,
          message: `登录失败: ${error.message}`,
        };
      } finally {
        // 关闭浏览器
        if (context) await context.close().catch(() => {});
      }
    }
  • Input schema and metadata for the 'login' tool, defined in the ListTools response. No input parameters required; includes Chinese description.
      name: 'login',
      description:
        '打开浏览器进行 PingCode 登录(支持飞书等第三方登录)。登录成功后凭证会自动保存。',
      inputSchema: {
        type: 'object',
        properties: {},
        required: [],
      },
    },
  • src/index.ts:166-177 (registration)
    Tool dispatch/registration in the MCP server's CallToolRequestSchema handler. Matches tool name 'login', invokes the login() function, and returns formatted MCP content response with error flag based on result.
    case 'login': {
      const result = await login();
      return {
        content: [
          {
            type: 'text',
            text: result.message,
          },
        ],
        isError: !result.success,
      };
    }
  • src/index.ts:9-9 (registration)
    Import statement registering the login handler function into the main server file.
    import { login, logout, checkAuth } from './tools/login.js';

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/ratatatat1/pingcode-mcp'

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