Skip to main content
Glama
TylerFlar

claude-fidelity-mcp

by TylerFlar

fidelity_login

Login to Fidelity using credentials directly or via environment variables. Supports automatic TOTP 2FA or SMS 2FA with a follow-up step for verification.

Instructions

Log in to Fidelity. Supports TOTP 2FA (automatic) and SMS 2FA (requires follow-up with fidelity_submit_2fa). Credentials can be passed directly or via env vars FIDELITY_USERNAME, FIDELITY_PASSWORD, FIDELITY_TOTP_SECRET.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
usernameNoFidelity username. Falls back to FIDELITY_USERNAME env var.
passwordNoFidelity password. Falls back to FIDELITY_PASSWORD env var.
totp_secretNoTOTP secret for authenticator app 2FA. Falls back to FIDELITY_TOTP_SECRET env var.

Implementation Reference

  • src/index.ts:34-97 (registration)
    Registration of the fidelity_login tool on the MCP server, defining its schema (username, password, totp_secret) and delegating to the login() function from ./auth.js.
    server.tool(
      "fidelity_login",
      "Log in to Fidelity. Supports TOTP 2FA (automatic) and SMS 2FA (requires follow-up with fidelity_submit_2fa). Credentials can be passed directly or via env vars FIDELITY_USERNAME, FIDELITY_PASSWORD, FIDELITY_TOTP_SECRET.",
      {
        username: z
          .string()
          .optional()
          .describe(
            "Fidelity username. Falls back to FIDELITY_USERNAME env var."
          ),
        password: z
          .string()
          .optional()
          .describe(
            "Fidelity password. Falls back to FIDELITY_PASSWORD env var."
          ),
        totp_secret: z
          .string()
          .optional()
          .describe(
            "TOTP secret for authenticator app 2FA. Falls back to FIDELITY_TOTP_SECRET env var."
          ),
      },
      async ({ username, password, totp_secret }) => {
        const user = username ?? process.env.FIDELITY_USERNAME;
        const pass = password ?? process.env.FIDELITY_PASSWORD;
        const totp = totp_secret ?? process.env.FIDELITY_TOTP_SECRET;
    
        if (!user || !pass) {
          return {
            content: [
              {
                type: "text",
                text: "Error: Username and password are required. Provide them as arguments or set FIDELITY_USERNAME and FIDELITY_PASSWORD environment variables.",
              },
            ],
            isError: true,
          };
        }
    
        try {
          const result = await login(getConfig(), user, pass, totp);
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(result, null, 2),
              },
            ],
            isError: !result.success,
          };
        } catch (e) {
          return {
            content: [
              {
                type: "text",
                text: `Login failed: ${e instanceof Error ? e.message : String(e)}`,
              },
            ],
            isError: true,
          };
        }
      }
    );
  • Zod schema defining the input parameters for fidelity_login: username, password (both optional, fallback to env vars), and totp_secret (optional, fallback to FIDELITY_TOTP_SECRET env var).
    {
      username: z
        .string()
        .optional()
        .describe(
          "Fidelity username. Falls back to FIDELITY_USERNAME env var."
        ),
      password: z
        .string()
        .optional()
        .describe(
          "Fidelity password. Falls back to FIDELITY_PASSWORD env var."
        ),
      totp_secret: z
        .string()
        .optional()
        .describe(
          "TOTP secret for authenticator app 2FA. Falls back to FIDELITY_TOTP_SECRET env var."
        ),
    },
  • Main login handler function. Initializes the browser via Playwright, navigates to Fidelity login page, fills credentials, clicks login, checks for 2FA requirement, and returns a LoginResult indicating success or need for SMS 2FA.
    export async function login(
      config: FidelityConfig,
      username: string,
      password: string,
      totpSecret?: string
    ): Promise<LoginResult> {
      const page = await initBrowser(config);
    
      // Navigate to login page (double navigation to handle redirects)
      await page.goto(LOGIN_URLS[0], { waitUntil: "domcontentloaded" });
      await page.waitForTimeout(3000);
    
      // If redirected to new signin URL, use that; otherwise retry original
      if (!isOnLoginPage(page.url())) {
        await page.goto(LOGIN_URLS[0], { waitUntil: "domcontentloaded" });
      }
      await page.waitForTimeout(2000);
    
      // Fill credentials
      const usernameField = page.getByLabel("Username", { exact: true });
      await usernameField.waitFor({ state: "visible", timeout: config.timeout });
      await usernameField.fill(username);
    
      const passwordField = page.getByLabel("Password", { exact: true });
      await passwordField.fill(password);
    
      // Click login
      await page.getByRole("button", { name: "Log in" }).click();
    
      // Wait for loading
      await waitForLoadingCompleteDouble(page, config.timeout);
    
      // Check if we landed on summary (no 2FA needed)
      if (page.url().includes("summary")) {
        await saveSession();
        return {
          success: true,
          needsSms2FA: false,
          message: "Login successful. No 2FA required.",
        };
      }
    
      // Still on login/signin page - 2FA required
      if (isOnLoginPage(page.url())) {
        return await handle2FA(page, config, totpSecret);
      }
    
      return {
        success: false,
        needsSms2FA: false,
        message: `Unexpected URL after login: ${page.url()}`,
      };
    }
  • handle2FA helper function that processes 2FA after login: either automatic TOTP code generation/submission (if totpSecret provided) or fallback to SMS code request via 'Text me the code' workflow.
    async function handle2FA(
      page: Page,
      config: FidelityConfig,
      totpSecret?: string
    ): Promise<LoginResult> {
      // Wait for 2FA widget
      try {
        await page.waitForSelector("#dom-widget div", {
          state: "visible",
          timeout: 15000,
        });
      } catch {
        return {
          success: false,
          needsSms2FA: false,
          message: "2FA widget did not appear. Login may have failed.",
        };
      }
    
      // Check if TOTP authenticator code is requested
      const totpHeading = page.getByRole("heading", {
        name: "Enter the code from your",
      });
    
      if (totpSecret) {
        try {
          await totpHeading.waitFor({ state: "visible", timeout: 5000 });
    
          // Generate TOTP code
          const totp = new OTPAuth.TOTP({
            secret: OTPAuth.Secret.fromBase32(totpSecret),
            digits: 6,
            period: 30,
          });
          const code = totp.generate();
    
          // Fill code
          const codeInput = page.getByPlaceholder("XXXXXX");
          await codeInput.fill(code);
    
          // Check "Don't ask me again"
          try {
            const rememberLabel = page
              .locator("label")
              .filter({ hasText: "Don't ask me again on this" });
            if (await rememberLabel.isVisible()) {
              await rememberLabel.click();
            }
          } catch {
            // Optional checkbox
          }
    
          // Submit
          await page.getByRole("button", { name: "Continue" }).click();
    
          // Wait for redirect to summary
          await page.waitForURL("**/portfolio/summary", {
            timeout: config.timeout,
          });
          await waitForLoadingComplete(page);
          await saveSession();
    
          return {
            success: true,
            needsSms2FA: false,
            message: "Login successful with TOTP authentication.",
          };
        } catch (e) {
          return {
            success: false,
            needsSms2FA: false,
            message: `TOTP authentication failed: ${e instanceof Error ? e.message : String(e)}`,
          };
        }
      }
    
      // No TOTP secret - try to fall back to SMS
      try {
        // Check for "Try another way" link (push notification page)
        const tryAnotherWay = page.getByRole("link", { name: "Try another way" });
        if (await tryAnotherWay.isVisible({ timeout: 3000 })) {
          // Check "Don't ask me again" first
          try {
            const rememberLabel = page
              .locator("label")
              .filter({ hasText: "Don't ask me again on this" });
            if (await rememberLabel.isVisible()) {
              await rememberLabel.click();
            }
          } catch {
            // Optional
          }
          await tryAnotherWay.click();
          await page.waitForTimeout(2000);
        }
      } catch {
        // Not on push notification page
      }
    
      // Try to click "Text me the code"
      try {
        const textMeBtn = page.getByRole("button", { name: "Text me the code" });
        await textMeBtn.waitFor({ state: "visible", timeout: 5000 });
        await textMeBtn.click();
        await page.waitForTimeout(2000);
    
        // Click the code input to focus it
        const codeInput = page.getByPlaceholder("XXXXXX");
        await codeInput.click();
    
        return {
          success: true,
          needsSms2FA: true,
          message:
            "SMS code sent. Use fidelity_submit_2fa tool to enter the code.",
        };
      } catch {
        // Check if TOTP is required but no secret was provided
        try {
          if (await totpHeading.isVisible()) {
            return {
              success: false,
              needsSms2FA: false,
              message:
                "Authenticator app code required but no TOTP secret provided. Set FIDELITY_TOTP_SECRET environment variable.",
            };
          }
        } catch {
          // Ignore
        }
    
        return {
          success: false,
          needsSms2FA: false,
          message: "Could not initiate 2FA. Check your Fidelity security settings.",
        };
      }
    }
  • initBrowser helper - initializes Playwright Firefox browser, loads saved session state, and returns a Page instance. Called by the login() handler.
    export async function initBrowser(config: FidelityConfig): Promise<Page> {
      if (page && !page.isClosed()) {
        return page;
      }
    
      const sessionPath = getSessionPath(config);
      currentConfig = config;
    
      // Ensure session directory exists
      fs.mkdirSync(config.sessionDir, { recursive: true });
    
      // Initialize session file if it doesn't exist
      if (!fs.existsSync(sessionPath)) {
        fs.writeFileSync(sessionPath, JSON.stringify({ cookies: [], origins: [] }));
      }
    
      browser = await firefox.launch({
        headless: config.headless,
        args: ["--disable-webgl", "--disable-software-rasterizer"],
      });
    
      // Load saved session state
      let storageState: string | undefined;
      try {
        const data = fs.readFileSync(sessionPath, "utf-8");
        const parsed = JSON.parse(data);
        if (parsed.cookies || parsed.origins) {
          storageState = sessionPath;
        }
      } catch {
        // No valid session, start fresh
      }
    
      context = await browser.newContext({
        storageState,
        userAgent:
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0",
        viewport: { width: 1920, height: 1080 },
        locale: "en-US",
      });
    
      page = await context.newPage();
    
      // Basic stealth: override navigator properties
      await page.addInitScript(() => {
        Object.defineProperty(navigator, "webdriver", { get: () => false });
        Object.defineProperty(navigator, "plugins", {
          get: () => [1, 2, 3, 4, 5],
        });
        Object.defineProperty(navigator, "languages", {
          get: () => ["en-US", "en"],
        });
      });
    
      return page;
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries full behavioral burden. It discloses TOTP (automatic) vs SMS (requires follow-up) behaviors and credential fallback to env vars. However, it omits what the tool returns upon success/failure, rate limits, or session implications, making it adequate but not thorough.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences front-load the purpose and key behaviors. No redundant words, every sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, so the description should explain return values and error behavior, which it does not. It also does not mention that a successful login is required for sibling tools. While the 2FA flow is well-covered, critical invocation details are missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with each parameter already including fallback details. The description repeats this info without adding new context, so baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool as logging into Fidelity, mentions specific 2FA methods (TOTP, SMS), and explicitly names a sibling tool (fidelity_submit_2fa) for follow-up, distinguishing it from other tools on the server.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context: use for login. It explains that SMS 2FA requires a follow-up call to fidelity_submit_2fa, guiding the agent on next steps. It does not explicitly mention when not to use or alternative tools, but the context is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/TylerFlar/claude-fidelity-mcp'

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