> ## Documentation Index
> Fetch the complete documentation index at: https://docs.endorlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Endor Labs MCP server in Gemini CLI

> <Badge color="green">Beta</Badge> <br /> Learn how to deploy and run the Endor Labs MCP server in Gemini CLI.

export const BadgeTabs = ({children}) => {
  const DEFAULT_BADGE_COLOR = 'gray';
  const FALLBACK_TAB_PREFIX = 'Tab ';
  const ARIA_ID_PREFIX = 'bt-tab-';
  const BADGE_COLOR_CLASS = {
    green: 'bt-badge--green',
    blue: 'bt-badge--blue',
    orange: 'bt-badge--orange',
    red: 'bt-badge--red',
    purple: 'bt-badge--purple',
    yellow: 'bt-badge--yellow',
    gray: 'bt-badge--gray'
  };
  const readDataAttr = (props, kebabName) => {
    if (!props || typeof props !== 'object') {
      return undefined;
    }
    const direct = props[kebabName];
    if (direct !== undefined && direct !== null) {
      return String(direct);
    }
    return undefined;
  };
  const normalizeBadgeColor = raw => {
    if (!raw) {
      return DEFAULT_BADGE_COLOR;
    }
    const c = String(raw).toLowerCase().trim();
    return BADGE_COLOR_CLASS[c] ? c : DEFAULT_BADGE_COLOR;
  };
  const flattenChildren = (node, out) => {
    if (node === null || node === undefined) {
      return;
    }
    if (Array.isArray(node)) {
      for (const item of node) {
        flattenChildren(item, out);
      }
      return;
    }
    out.push(node);
  };
  const childToTabItem = (child, index, itemCount) => {
    if (child === null || child === undefined) return null;
    if (typeof child === 'string' || typeof child === 'number') {
      const text = String(child).trim();
      if (!text) return null;
      return {
        key: `text-${index}`,
        title: `${FALLBACK_TAB_PREFIX}${itemCount + 1}`,
        badgeText: '',
        badgeColor: DEFAULT_BADGE_COLOR,
        node: <span>{child}</span>
      };
    }
    if (typeof child !== 'object' || !child.props) return null;
    const p = child.props;
    const title = readDataAttr(p, 'data-title')?.trim() || `${FALLBACK_TAB_PREFIX}${itemCount + 1}`;
    const badgeText = readDataAttr(p, 'data-badge')?.trim() || '';
    const badgeColor = normalizeBadgeColor(readDataAttr(p, 'data-badge-color'));
    return {
      key: child.key == null ? `tab-${index}` : String(child.key),
      title,
      badgeText,
      badgeColor,
      node: child
    };
  };
  const tabItems = useMemo(() => {
    const arr = [];
    flattenChildren(children, arr);
    const items = [];
    for (const [i, child] of arr.entries()) {
      const item = childToTabItem(child, i, items.length);
      if (item) items.push(item);
    }
    return items;
  }, [children]);
  const [activeIndex, setActiveIndex] = useState(0);
  const [isDark, setIsDark] = useState(false);
  const [baseId] = useState(() => `${ARIA_ID_PREFIX}${Math.random().toString(36).slice(2, 11)}`);
  useEffect(() => {
    const check = () => {
      const r = document.documentElement;
      setIsDark(r.dataset.theme === 'dark' || r.classList.contains('dark'));
    };
    check();
    const obs = new MutationObserver(check);
    obs.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['data-theme', 'class']
    });
    return () => obs.disconnect();
  }, []);
  useEffect(() => {
    if (activeIndex >= tabItems.length && tabItems.length > 0) {
      setActiveIndex(0);
    }
  }, [activeIndex, tabItems.length]);
  const safeIndex = tabItems.length === 0 ? 0 : Math.min(activeIndex, tabItems.length - 1);
  const focusTabAt = nextIndex => {
    if (tabItems.length === 0) {
      return;
    }
    const wrapped = (nextIndex % tabItems.length + tabItems.length) % tabItems.length;
    setActiveIndex(wrapped);
    const btn = document.getElementById(`${baseId}-tab-${wrapped}`);
    if (btn && typeof btn.focus === 'function') {
      btn.focus();
    }
  };
  const onKeyDownTabList = event => {
    if (tabItems.length === 0) {
      return;
    }
    const key = event.key;
    if (key === 'ArrowRight' || key === 'ArrowDown') {
      event.preventDefault();
      focusTabAt(safeIndex + 1);
    } else if (key === 'ArrowLeft' || key === 'ArrowUp') {
      event.preventDefault();
      focusTabAt(safeIndex - 1);
    } else if (key === 'Home') {
      event.preventDefault();
      focusTabAt(0);
    } else if (key === 'End') {
      event.preventDefault();
      focusTabAt(tabItems.length - 1);
    }
  };
  if (tabItems.length === 0) {
    return null;
  }
  const navStyle = {
    display: 'flex',
    flexWrap: 'wrap',
    gap: '0.35rem',
    marginBottom: '0.75rem',
    borderBottom: isDark ? '1px solid rgba(255,255,255,0.12)' : '1px solid rgba(0,0,0,0.08)',
    paddingBottom: '0.25rem'
  };
  const btnBase = selected => ({
    display: 'inline-flex',
    alignItems: 'center',
    gap: '0.35rem',
    padding: '0.45rem 0.65rem',
    border: 'none',
    borderBottom: selected ? '2px solid #26D07C' : '2px solid transparent',
    background: 'transparent',
    color: isDark ? '#e6edf3' : '#1f2937',
    cursor: 'pointer',
    fontSize: '0.9rem',
    fontWeight: selected ? 600 : 500,
    borderRadius: '6px 6px 0 0'
  });
  return <div className="not-prose bt-root" style={{
    margin: '1rem 0'
  }}>
      <div className="bt-tablist" role="tablist" aria-orientation="horizontal" tabIndex={-1} onKeyDown={onKeyDownTabList} style={navStyle}>
        {tabItems.map((tab, index) => {
    const selected = index === safeIndex;
    const tabId = `${baseId}-tab-${index}`;
    const panelId = `${baseId}-panel-${index}`;
    const badgeClass = BADGE_COLOR_CLASS[tab.badgeColor] || BADGE_COLOR_CLASS.gray;
    return <button key={tab.key} type="button" id={tabId} role="tab" aria-selected={selected} aria-controls={panelId} tabIndex={selected ? 0 : -1} className={`bt-tab-btn${selected ? ' bt-tab-btn--active' : ''}`} style={btnBase(selected)} onClick={() => setActiveIndex(index)}>
              <span className="bt-tab-title">{tab.title}</span>
              {tab.badgeText ? <span className={`bt-badge ${badgeClass}`}>{tab.badgeText}</span> : null}
            </button>;
  })}
      </div>
      {tabItems.map((tab, index) => {
    const panelId = `${baseId}-panel-${index}`;
    const tabId = `${baseId}-tab-${index}`;
    const hidden = index !== safeIndex;
    return <div key={`${tab.key}-panel`} id={panelId} role="tabpanel" aria-labelledby={tabId} hidden={hidden} className="bt-tabpanel" style={{
      display: hidden ? 'none' : 'block'
    }}>
            {tab.node}
          </div>;
  })}
    </div>;
};

export const McpInstallGenerator = ({variant = 'json', platform, configKey = 'servers', edition = 'enterprise'}) => {
  const AUTH_OPTIONS = [{
    value: '',
    label: 'Select...',
    disabled: true
  }, {
    value: 'google',
    label: 'Google'
  }, {
    value: 'github',
    label: 'GitHub'
  }, {
    value: 'gitlab',
    label: 'GitLab'
  }, {
    value: 'sso',
    label: 'SSO'
  }];
  const PLATFORM_NAMES = {
    'claude-code': 'Claude Code',
    'codex': 'OpenAI Codex',
    'gemini': 'Gemini CLI'
  };
  const MCP_SERVER_NAME = 'endor-cli-tools';
  const ARG_SEPARATOR = '--';
  const TERMINAL_CMD_NPX = ['npx', '-y', 'endorctl', 'ai-tools', 'mcp-server'];
  const TERMINAL_CMD_DIRECT = ['endorctl', 'ai-tools', 'mcp-server'];
  const TERMINAL_CONFIGS = {
    'claude-code': {
      cli: 'claude',
      configLabel: 'MCP server configuration for .mcp.json',
      configFormat: 'json',
      prefix: ['mcp', 'add'],
      envFlag: '--env',
      extraFlags: [],
      enterpriseNameBeforeEnv: true,
      useArgSeparator: true
    },
    'codex': {
      cli: 'codex',
      configLabel: 'MCP server configuration for config.toml',
      configFormat: 'toml',
      prefix: ['mcp', 'add'],
      envFlag: '--env',
      extraFlags: [],
      enterpriseNameBeforeEnv: false,
      useArgSeparator: true
    },
    'gemini': {
      cli: 'gemini',
      configLabel: 'MCP server configuration for settings.json',
      configFormat: 'json',
      prefix: ['mcp', 'add'],
      envFlag: '-e',
      extraFlags: [],
      enterpriseNameBeforeEnv: true,
      useArgSeparator: false
    }
  };
  const DEV_SERVER_CONFIG = {
    type: 'stdio',
    command: 'npx',
    args: ['-y', 'endorctl', 'ai-tools', 'mcp-server']
  };
  const PROTOCOL_MAP = {
    cursor: 'cursor:',
    vscode: 'vscode:'
  };
  const IDE_LABELS = {
    cursor: 'Install in Cursor',
    vscode: 'Install in VS Code'
  };
  const ENTERPRISE_SUBTITLES = {
    terminal: p => 'Select your authentication method and copy the command to add the MCP server to ' + (PLATFORM_NAMES[p] || p) + '.',
    cursor: () => 'Select your authentication method and generate the configuration for Cursor.',
    vscode: () => 'Select your authentication method and generate the configuration for Visual Studio Code.',
    json: () => 'Select your authentication method and generate JSON for your MCP configuration.'
  };
  const DEV_SUBTITLES = {
    terminal: p => 'Copy the command to add the Endor Labs MCP server to ' + (PLATFORM_NAMES[p] || p) + '.',
    cursor: () => 'Install the Endor Labs MCP server in Cursor with one click.',
    vscode: () => 'Install the Endor Labs MCP server in Visual Studio Code with one click.',
    json: () => 'Copy the JSON configuration for your MCP configuration file.'
  };
  const sanitize = (input, type) => {
    if (!input) {
      return '';
    }
    const s = String(input).trim();
    if (type === 'authMode') {
      const allowed = ['', 'google', 'github', 'gitlab', 'sso'];
      return allowed.includes(s) ? s : '';
    }
    return s.replaceAll(/[^a-zA-Z0-9._-]/g, '').substring(0, 100);
  };
  const buildEnv = o => {
    if (o.useExisting) {
      return undefined;
    }
    if (o.isEnterprise) {
      const env = {
        ENDOR_NAMESPACE: o.namespace,
        ENDOR_MCP_SERVER_AUTH_MODE: o.authMode
      };
      if (o.authMode === 'sso' && o.tenant) {
        env.ENDOR_MCP_SERVER_AUTH_TENANT = o.tenant;
      }
      return env;
    }
    return undefined;
  };
  const buildTerminalCommand = (cfg, o) => {
    const rawCmd = o.usingNpx ? TERMINAL_CMD_NPX : TERMINAL_CMD_DIRECT;
    const cmdPart = cfg.useArgSeparator ? [ARG_SEPARATOR, ...rawCmd] : rawCmd;
    const base = [cfg.cli, ...cfg.prefix, ...cfg.extraFlags];
    if (o.useExisting) {
      return [...base, MCP_SERVER_NAME, ...cmdPart].join(' ');
    }
    if (o.isEnterprise) {
      const envTokens = [cfg.envFlag, 'ENDOR_NAMESPACE=' + o.namespace, cfg.envFlag, 'ENDOR_MCP_SERVER_AUTH_MODE=' + o.authMode];
      if (o.authMode === 'sso' && o.tenant) {
        envTokens.push(cfg.envFlag, 'ENDOR_MCP_SERVER_AUTH_TENANT=' + o.tenant);
      }
      if (cfg.enterpriseNameBeforeEnv) {
        return [...base, MCP_SERVER_NAME, ...envTokens, ...cmdPart].join(' ');
      }
      return [...base, ...envTokens, MCP_SERVER_NAME, ...cmdPart].join(' ');
    }
    return [...base, MCP_SERVER_NAME, ...cmdPart].join(' ');
  };
  const buildManualConfig = o => {
    const command = o.usingNpx ? 'npx' : 'endorctl';
    const args = o.usingNpx ? ['-y', 'endorctl', 'ai-tools', 'mcp-server'] : ['ai-tools', 'mcp-server'];
    const env = buildEnv(o);
    const tcfg = TERMINAL_CONFIGS[platform];
    if (tcfg?.configFormat === 'toml') {
      const lines = ['[mcp_servers.endor-cli-tools]'];
      lines.push('command = "' + command + '"', 'args = [' + args.map(a => '"' + a + '"').join(', ') + ']');
      if (env) {
        lines.push('', '[mcp_servers.endor-cli-tools.env]');
        Object.keys(env).forEach(k => lines.push(k + ' = "' + env[k] + '"'));
      }
      return lines.join('\n');
    }
    const mc = {
      command,
      args
    };
    if (env) {
      mc.env = env;
    }
    return JSON.stringify({
      mcpServers: {
        'endor-cli-tools': mc
      }
    }, null, 2);
  };
  const buildJsonConfig = o => {
    const command = o.usingNpx ? 'npx' : 'endorctl';
    const args = o.usingNpx ? ['-y', 'endorctl', 'ai-tools', 'mcp-server'] : ['ai-tools', 'mcp-server'];
    const env = buildEnv(o);
    const mc = {
      type: 'stdio',
      command,
      args
    };
    if (env) {
      mc.env = env;
    }
    const wrapper = {};
    wrapper[configKey] = {
      'endor-cli-tools': mc
    };
    return JSON.stringify(wrapper, null, 2);
  };
  const buildDeeplinkConfig = o => {
    const command = o.usingNpx ? 'npx' : 'endorctl';
    const args = o.usingNpx ? ['-y', 'endorctl', 'ai-tools', 'mcp-server'] : ['ai-tools', 'mcp-server'];
    const env = buildEnv(o);
    const mc = {
      type: 'stdio',
      command,
      args
    };
    if (env) {
      mc.env = env;
    }
    return mc;
  };
  const isValidProtocol = (uri, proto) => {
    try {
      const parsed = new URL(uri);
      return parsed.protocol === proto;
    } catch (_urlError) {
      console.debug(_urlError);
      return false;
    }
  };
  const validateOpts = o => {
    if (o.useExisting) {
      return null;
    }
    if (!o.authMode) {
      return 'Select an authentication method';
    }
    if (o.authMode === 'sso' && !o.tenant) {
      return 'Tenant is required for SSO authentication';
    }
    if (!o.namespace) {
      return 'Namespace is required for Enterprise Edition';
    }
    return null;
  };
  const buildDeeplinkUri = cfg => {
    if (variant === 'cursor') {
      const b64 = btoa(JSON.stringify(cfg));
      return 'cursor://anysphere.cursor-deeplink/mcp/install?name=' + encodeURIComponent('endor-cli-tools') + '&config=' + encodeURIComponent(b64);
    }
    const ic = {
      name: 'Endor Labs MCP Server (endor-cli-tools)',
      command: cfg.command,
      args: cfg.args
    };
    if (cfg.env) {
      ic.env = cfg.env;
    }
    return 'vscode:mcp/install?' + encodeURIComponent(JSON.stringify(ic));
  };
  const devJsonConfigFor = () => {
    if (variant === 'json') {
      const mc = {
        type: 'stdio',
        ...DEV_SERVER_CONFIG
      };
      const w = {};
      w[configKey] = {
        'endor-cli-tools': mc
      };
      return JSON.stringify(w, null, 2);
    }
    if (variant === 'vscode') {
      return JSON.stringify({
        servers: {
          'endor-cli-tools': {
            type: 'stdio',
            ...DEV_SERVER_CONFIG
          }
        }
      }, null, 2);
    }
    return JSON.stringify({
      mcpServers: {
        'endor-cli-tools': {
          type: 'stdio',
          ...DEV_SERVER_CONFIG
        }
      }
    }, null, 2);
  };
  const devManualConfigFor = () => {
    if (!TERMINAL_CONFIGS[platform]) {
      return '';
    }
    return TERMINAL_CONFIGS[platform].configFormat === 'toml' ? '[mcp_servers.endor-cli-tools]\ncommand = "npx"\nargs = ["-y", "endorctl", "ai-tools", "mcp-server"]' : JSON.stringify({
      mcpServers: {
        'endor-cli-tools': DEV_SERVER_CONFIG
      }
    }, null, 2);
  };
  const S = {
    formGroup: bg => ({
      background: bg,
      borderRadius: '12px',
      padding: '0.75rem',
      border: '1px solid rgba(38, 208, 124, 0.3)'
    }),
    label: c => ({
      display: 'block',
      fontWeight: 500,
      marginBottom: '0.2rem',
      color: c,
      fontSize: '0.8rem'
    }),
    helpText: c => ({
      fontSize: '0.7rem',
      color: c,
      lineHeight: 1.3,
      marginTop: '0.25rem'
    }),
    select: (bg, c) => ({
      padding: '0.5rem 0.75rem',
      border: '1px solid rgba(38, 208, 124, 0.45)',
      borderRadius: '8px',
      fontSize: '0.85rem',
      backgroundColor: bg,
      color: c,
      width: '100%',
      appearance: 'none',
      backgroundImage: "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2326D07C' d='M6 9L1 4h10z'/%3E%3C/svg%3E\")",
      backgroundRepeat: 'no-repeat',
      backgroundPosition: 'right 0.75rem center',
      paddingRight: '2.5rem',
      cursor: 'pointer',
      outline: 'none'
    }),
    input: (bg, c) => ({
      padding: '0.5rem 0.75rem',
      border: '1px solid rgba(38, 208, 124, 0.45)',
      borderRadius: '8px',
      fontSize: '0.85rem',
      backgroundColor: bg,
      color: c,
      width: '100%',
      outline: 'none',
      boxSizing: 'border-box'
    }),
    codeBox: bg => ({
      background: bg,
      border: '1px solid rgba(38, 208, 124, 0.2)',
      borderRadius: '8px',
      padding: '0.75rem',
      marginBottom: '0.75rem'
    }),
    codeHeader: c => ({
      display: 'flex',
      alignItems: 'center',
      color: c,
      fontWeight: 600,
      margin: '0 0 0.5rem 0',
      fontSize: '0.9rem'
    }),
    pre: (bg, c) => ({
      background: bg,
      color: c,
      padding: '0.75rem',
      borderRadius: '6px',
      fontFamily: "'Monaco', 'Menlo', 'Ubuntu Mono', monospace",
      fontSize: '0.8rem',
      lineHeight: 1.4,
      overflowX: 'auto',
      margin: 0,
      whiteSpace: 'pre-wrap',
      wordWrap: 'break-word',
      border: '1px solid rgba(38, 208, 124, 0.1)'
    }),
    copyBtn: (copied, dk) => ({
      marginLeft: 'auto',
      padding: '0.25rem 0.5rem',
      background: copied ? '#28a745' : 'linear-gradient(to bottom, rgba(50,225,140,0.92), rgba(30,190,110,0.92))',
      color: dk ? '#000' : '#fff',
      border: '1px solid rgba(38,208,124,0.5)',
      borderRadius: '6px',
      fontSize: '0.75rem',
      cursor: 'pointer',
      fontWeight: 500,
      whiteSpace: 'nowrap'
    }),
    primaryBtn: (dk, lg) => ({
      padding: lg ? '0.75rem 1.5rem' : '0.4rem 0.75rem',
      background: 'linear-gradient(to bottom, rgba(50,225,140,0.92), rgba(30,190,110,0.92))',
      color: dk ? '#000' : '#fff',
      border: '1.5px solid rgba(38,208,124,0.6)',
      borderRadius: '10px',
      cursor: 'pointer',
      fontSize: lg ? '0.9rem' : '0.8rem',
      display: 'inline-flex',
      alignItems: 'center',
      fontWeight: 500
    }),
    secondaryBtn: (dk, c) => ({
      padding: '0.4rem 0.75rem',
      background: dk ? 'linear-gradient(to bottom, rgba(255,255,255,0.12), rgba(255,255,255,0.06))' : 'linear-gradient(to bottom, rgba(255,255,255,0.3), rgba(240,240,240,0.2))',
      color: c,
      border: '1.5px solid rgba(38,208,124,0.35)',
      borderRadius: '10px',
      cursor: 'pointer',
      fontSize: '0.8rem',
      display: 'inline-flex',
      alignItems: 'center',
      fontWeight: 500
    }),
    ic: bg => ({
      background: bg,
      padding: '0.15rem 0.35rem',
      borderRadius: '4px',
      fontSize: '0.8em'
    }),
    toggleBtn: (active, bgLight, textColor, dk, hasLeftBorder) => {
      const activeGradient = 'linear-gradient(to bottom, rgba(50,225,140,0.92), rgba(30,190,110,0.92))';
      const activeColor = dk ? '#000' : '#fff';
      const base = {
        flex: 1,
        padding: '0.5rem 0.75rem',
        border: 'none',
        cursor: 'pointer',
        fontSize: '0.85rem',
        fontWeight: 600
      };
      if (hasLeftBorder) {
        base.borderLeft = '1px solid rgba(38,208,124,0.45)';
      }
      base.background = active ? activeGradient : bgLight;
      base.color = active ? activeColor : textColor;
      return base;
    },
    errorBox: dk => ({
      marginTop: '0.5rem',
      padding: '0.5rem 0.75rem',
      background: dk ? '#2d0f0f' : '#fde8e8',
      border: '1px solid ' + (dk ? '#5c1e1e' : '#f1c0c0'),
      borderRadius: '8px',
      color: dk ? '#ff6659' : '#9a0007',
      fontSize: '0.8rem'
    })
  };
  const CURSOR_SVG = <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" style={{
    marginRight: '0.5rem',
    flexShrink: 0
  }}><path d="M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23" /></svg>;
  const VSCODE_SVG = <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" style={{
    marginRight: '0.5rem',
    flexShrink: 0
  }}><path d="M17.583.063a1.5 1.5 0 0 0-1.032.392 1.5 1.5 0 0 0-.001 0L7.05 9.6 2.856 6.295a1 1 0 0 0-1.272.09l-1.2 1.143a1 1 0 0 0 0 1.44L4.2 12l-3.816 3.03a1 1 0 0 0 0 1.442l1.2 1.143a1 1 0 0 0 1.272.09L7.05 14.4l9.5 9.145a1.5 1.5 0 0 0 1.033.392c.205 0 .405-.042.59-.124l4.2-1.89a1.5 1.5 0 0 0 .877-1.364V2.44a1.5 1.5 0 0 0-.877-1.362l-4.2-1.891a1.5 1.5 0 0 0-.59-.124M18 7.39v9.22L10.97 12z" /></svg>;
  const IDE_SVGS = {
    cursor: CURSOR_SVG,
    vscode: VSCODE_SVG
  };
  const [authMode, setAuthMode] = useState('');
  const [tenant, setTenant] = useState('');
  const [ns, setNs] = useState('');
  const [useExisting, setUseExisting] = useState(true);
  const [usingNpx, setUsingNpx] = useState(true);
  const [isDark, setIsDark] = useState(false);
  const [showResult, setShowResult] = useState(variant !== 'json');
  const [error, setError] = useState('');
  const [copyStates, setCopyStates] = useState({});
  useEffect(() => {
    const check = () => {
      const r = document.documentElement;
      setIsDark(r.dataset.theme === 'dark' || r.classList.contains('dark'));
    };
    check();
    const obs = new MutationObserver(check);
    obs.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['data-theme', 'class']
    });
    return () => obs.disconnect();
  }, []);
  const isTerminal = variant === 'terminal';
  const isCursor = variant === 'cursor';
  const isVscode = variant === 'vscode';
  const isJson = variant === 'json';
  const isDeeplink = isCursor || isVscode;
  const isEnterprise = authMode !== '';
  const isSSO = authMode === 'sso';
  const opts = useMemo(() => ({
    authMode: sanitize(authMode, 'authMode'),
    tenant: sanitize(tenant, 'tenant'),
    namespace: sanitize(ns, 'namespace'),
    useExisting,
    usingNpx,
    isEnterprise: isEnterprise && !useExisting
  }), [authMode, tenant, ns, useExisting, usingNpx, isEnterprise]);
  const validationError = useMemo(() => validateOpts(opts), [opts]);
  const commandText = useMemo(() => {
    if (!isTerminal || validationError) {
      return isTerminal ? '# Select an authentication method to see the command' : '';
    }
    const cfg = TERMINAL_CONFIGS[platform];
    return cfg ? buildTerminalCommand(cfg, opts) : '';
  }, [isTerminal, platform, opts, validationError]);
  const manualConfigText = useMemo(() => {
    if (!isTerminal || validationError) {
      return isTerminal ? '// Select an authentication method to generate the configuration' : '';
    }
    return buildManualConfig(opts);
  }, [isTerminal, opts, validationError]);
  const jsonConfigText = useMemo(() => {
    if (!isJson && !isDeeplink) {
      return '';
    }
    if (validationError) {
      return '// Select an authentication method to generate the configuration';
    }
    if (isJson) {
      return buildJsonConfig(opts);
    }
    const cfg = buildDeeplinkConfig(opts);
    const wrapper = isVscode ? {
      servers: {
        'endor-cli-tools': cfg
      }
    } : {
      mcpServers: {
        'endor-cli-tools': cfg
      }
    };
    return JSON.stringify(wrapper, null, 2);
  }, [isJson, isDeeplink, isVscode, opts, validationError]);
  const markCopied = useCallback(key => {
    setCopyStates(prev => ({
      ...prev,
      [key]: true
    }));
    setTimeout(() => setCopyStates(prev => ({
      ...prev,
      [key]: false
    })), 2000);
  }, []);
  const handleCopy = useCallback((text, key) => {
    navigator.clipboard.writeText(text).then(() => markCopied(key)).catch(() => {
      alert('Unable to copy. Please select and copy the text manually.');
    });
  }, [markCopied]);
  const handleReset = useCallback(() => {
    setAuthMode('');
    setTenant('');
    setNs('');
    setUseExisting(true);
    setUsingNpx(true);
    setError('');
    if (isJson) {
      setShowResult(false);
    }
  }, [isJson]);
  const handleDeeplinkInstall = useCallback(e => {
    if (e) {
      e.preventDefault();
    }
    setError('');
    const err = validateOpts(opts);
    if (err) {
      setError(err);
      return;
    }
    const cfg = buildDeeplinkConfig(opts);
    const uri = buildDeeplinkUri(cfg);
    if (!isValidProtocol(uri, PROTOCOL_MAP[variant])) {
      setError('Unable to generate a valid install link');
      return;
    }
    globalThis.location.href = uri;
  }, [opts, variant]);
  const handleGenerate = useCallback(e => {
    if (e) {
      e.preventDefault();
    }
    setError('');
    const err = validateOpts(opts);
    if (err) {
      setError(err);
      return;
    }
    setShowResult(true);
  }, [opts]);
  const showAuthMethod = !useExisting;
  const showNamespace = isEnterprise && !useExisting;
  const showTenant = isSSO && !useExisting;
  const subtitleText = (ENTERPRISE_SUBTITLES[variant] || ENTERPRISE_SUBTITLES.json)(platform);
  const manualConfigLabel = isTerminal ? TERMINAL_CONFIGS[platform]?.configLabel ?? 'Manual configuration' : 'MCP server configuration for mcp.json';
  const bgColor = isDark ? '#0d1117' : '#ffffff';
  const bgLight = isDark ? '#161b22' : '#f6f8fa';
  const textColor = isDark ? '#e6edf3' : '#1f2937';
  const mutedText = isDark ? 'rgba(230,237,243,0.7)' : 'rgba(31,41,55,0.7)';
  const cardBorder = '2px solid rgba(38,208,124,' + (isDark ? '0.4' : '0.5') + ')';
  const cardShadow = '0 2px 8px rgba(38,208,124,' + (isDark ? '0.05' : '0.1') + ')';
  const renderCodeBlock = (label, code, copyKey) => <div style={S.codeBox(bgLight)}>
      <div style={S.codeHeader(textColor)}>
        <span>{label}</span>
        <button type="button" onClick={() => handleCopy(code, copyKey)} style={S.copyBtn(copyStates[copyKey], isDark)}>
          {copyStates[copyKey] ? '✓ Copied!' : '📋 Copy'}
        </button>
      </div>
      <pre style={S.pre(bgColor, textColor)}>{code}</pre>
    </div>;
  const formSubmitHandlers = {
    json: handleGenerate,
    cursor: handleDeeplinkInstall,
    vscode: handleDeeplinkInstall
  };
  const onSubmit = formSubmitHandlers[variant] || (e => e.preventDefault());
  if (edition === 'developer') {
    const devJsonConfig = devJsonConfigFor();
    const devTerminalCfg = TERMINAL_CONFIGS[platform];
    const devTerminalSeparator = devTerminalCfg?.useArgSeparator ? ' -- ' : ' ';
    const devTerminalCommand = isTerminal && devTerminalCfg ? devTerminalCfg.cli + ' mcp add endor-cli-tools' + devTerminalSeparator + 'npx -y endorctl ai-tools mcp-server' : '';
    const devManualConfig = isTerminal ? devManualConfigFor() : '';
    const devSubtitle = (DEV_SUBTITLES[variant] || DEV_SUBTITLES.json)(platform);
    const handleDevDeeplink = () => {
      const uri = buildDeeplinkUri(DEV_SERVER_CONFIG);
      if (isValidProtocol(uri, PROTOCOL_MAP[variant])) {
        globalThis.location.href = uri;
      }
    };
    return <div className="not-prose mcp-gen" style={{
      margin: '1rem 0',
      fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif'
    }}>
        <div className="mcp-gen-card" style={{
      background: bgColor,
      border: cardBorder,
      borderRadius: '16px',
      padding: '0.75rem 1rem',
      color: textColor,
      boxShadow: cardShadow,
      width: 'fit-content',
      maxWidth: '100%'
    }}>
          <div style={{
      marginBottom: '0.5rem'
    }}>
              <h3 style={{
      color: textColor,
      marginBottom: '0.25rem',
      fontWeight: 600,
      fontSize: '1.1rem',
      marginTop: 0
    }}>Developer Edition Configuration</h3>
              <p style={{
      color: textColor,
      opacity: 0.8,
      margin: 0,
      fontSize: '0.85rem',
      lineHeight: 1.4
    }}>{devSubtitle}</p>
          </div>

          {isDeeplink && <div style={{
      display: 'flex',
      gap: '0.75rem',
      justifyContent: 'flex-start',
      marginBottom: '0.75rem'
    }}>
              <button type="button" onClick={handleDevDeeplink} style={S.primaryBtn(isDark, true)}>
                {IDE_SVGS[variant]}
                {IDE_LABELS[variant]}
              </button>
            </div>}

          {isTerminal && renderCodeBlock('Run this command in your terminal', devTerminalCommand, 'dev-terminal-cmd')}

          {isCursor && <p style={{
      fontSize: '0.85rem',
      color: textColor,
      lineHeight: 1.45,
      margin: '0.75rem 0 0.5rem 0',
      opacity: 0.9
    }}>
              For manual configuration, copy and paste the following JSON directly into a{' '}
              <code style={S.ic(bgLight)}>.cursor/mcp.json</code> file in the root of your repository.
            </p>}
          {isVscode && <p style={{
      fontSize: '0.85rem',
      color: textColor,
      lineHeight: 1.45,
      margin: '0.75rem 0 0.5rem 0',
      opacity: 0.9
    }}>
              For manual configuration, copy and paste the following JSON directly into a{' '}
              <code style={S.ic(bgLight)}>.vscode/mcp.json</code> file in the root of your repository.
            </p>}
          {isJson && <p style={{
      fontSize: '0.85rem',
      color: textColor,
      lineHeight: 1.45,
      margin: '0.75rem 0 0.5rem 0',
      opacity: 0.9
    }}>
              For manual configuration, copy and paste the following JSON directly into your MCP configuration file.
            </p>}
          {isTerminal && <p style={{
      fontSize: '0.85rem',
      color: textColor,
      lineHeight: 1.45,
      margin: '0.75rem 0 0.5rem 0',
      opacity: 0.9
    }}>
              For manual configuration, copy and paste the following configuration directly into your MCP configuration file.
            </p>}

          {(isJson || isDeeplink) && <details style={{
      marginTop: '0'
    }}>
              <summary style={{
      cursor: 'pointer',
      fontSize: '0.85rem',
      color: textColor,
      opacity: 0.8,
      padding: '0.5rem 0',
      userSelect: 'none'
    }}>📄 View JSON configuration</summary>
              {renderCodeBlock('MCP server configuration', devJsonConfig, 'dev-json-config')}
            </details>}

          {isTerminal && <details style={{
      marginTop: '0'
    }}>
              <summary style={{
      cursor: 'pointer',
      fontSize: '0.85rem',
      color: textColor,
      opacity: 0.8,
      padding: '0.5rem 0',
      userSelect: 'none'
    }}>📄 View manual configuration</summary>
              {renderCodeBlock(manualConfigLabel, devManualConfig, 'dev-terminal-config')}
            </details>}
        </div>
      </div>;
  }
  return <div className="not-prose mcp-gen" style={{
    margin: '1rem 0',
    fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif'
  }}>
      <div className="mcp-gen-card" style={{
    background: bgColor,
    border: cardBorder,
    borderRadius: '16px',
    padding: '0.75rem 1rem',
    color: textColor,
    boxShadow: cardShadow,
    width: 'fit-content',
    maxWidth: '100%'
  }}>
        <div style={{
    marginBottom: '0.5rem'
  }}>
            <h3 style={{
    color: textColor,
    marginBottom: '0.25rem',
    fontWeight: 600,
    fontSize: '1.1rem',
    marginTop: 0
  }}>Enterprise Edition Configuration</h3>
            <p style={{
    color: textColor,
    opacity: 0.8,
    margin: 0,
    fontSize: '0.85rem',
    lineHeight: 1.4
  }}>{subtitleText}</p>
        </div>

        <form onSubmit={onSubmit}>
          <div className="mcp-gen-grid" style={{
    display: 'grid',
    gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
    gap: '0.75rem',
    marginBottom: '0.75rem'
  }}>
            <div style={S.formGroup(bgLight)}>
              <div style={S.label(textColor)} id="mcp-preexisting-label">Pre-existing Endor Labs configuration?</div>
              <fieldset aria-labelledby="mcp-preexisting-label" style={{
    margin: 0,
    padding: 0,
    display: 'flex',
    gap: '0',
    borderRadius: '8px',
    overflow: 'hidden',
    border: '1px solid rgba(38,208,124,0.45)'
  }}>
                <button type="button" aria-pressed={!useExisting} onClick={() => {
    setUseExisting(false);
    setError('');
  }} style={S.toggleBtn(!useExisting, bgLight, textColor, isDark, false)}>No</button>
                <button type="button" aria-pressed={useExisting} onClick={() => {
    setUseExisting(true);
    setError('');
  }} style={S.toggleBtn(useExisting, bgLight, textColor, isDark, true)}>Yes</button>
              </fieldset>
              <div style={S.helpText(mutedText)}>
                Select <strong>Yes</strong> if you already have Endor Labs configured locally from a previous <code style={S.ic(bgLight)}>endorctl init</code>. Your existing configuration is used without additional setup.
              </div>
            </div>

            <div style={S.formGroup(bgLight)}>
              <div style={S.label(textColor)} id="mcp-using-npx-label">Using npx?</div>
              <fieldset aria-labelledby="mcp-using-npx-label" style={{
    margin: 0,
    padding: 0,
    display: 'flex',
    gap: '0',
    borderRadius: '8px',
    overflow: 'hidden',
    border: '1px solid rgba(38,208,124,0.45)'
  }}>
                <button type="button" aria-pressed={usingNpx} onClick={() => setUsingNpx(true)} style={S.toggleBtn(usingNpx, bgLight, textColor, isDark, false)}>Yes</button>
                <button type="button" aria-pressed={!usingNpx} onClick={() => setUsingNpx(false)} style={S.toggleBtn(!usingNpx, bgLight, textColor, isDark, true)}>No</button>
              </fieldset>
              <div style={S.helpText(mutedText)}>
                Select <strong>Yes</strong> to use <code style={S.ic(bgLight)}>npx</code> to download and run endorctl from npm. Select <strong>No</strong> if you have endorctl installed directly (e.g. via Homebrew or direct download).
              </div>
              <div className="mcp-endorctl-update-note" style={{
    marginTop: '0.5rem',
    padding: '0.5rem 0.75rem',
    background: isDark ? '#0d2818' : '#eafaf1',
    border: '1px solid rgba(38,208,124,0.35)',
    borderRadius: '8px',
    fontSize: '0.78rem',
    color: textColor,
    lineHeight: 1.4,
    visibility: usingNpx ? 'hidden' : 'visible'
  }}>
                <strong>Keep endorctl updated.</strong> When using a directly installed endorctl, make sure to keep it updated with your package manager (e.g. <code style={S.ic(bgLight)}>brew upgrade endorctl</code>) for the best experience. See the <a href="/setup-deployment/cli/" style={{
    color: '#26D07C'
  }}>endorctl installation docs</a> for details.
              </div>
            </div>

            {showAuthMethod && <div style={S.formGroup(bgLight)}>
                <div style={S.label(textColor)}>Authentication method <span style={{
    color: '#d32f2f'
  }}>*</span></div>
                <select value={authMode} onChange={e => {
    setAuthMode(e.target.value);
    setError('');
  }} style={S.select(bgColor, textColor)} aria-label="Authentication method">
                  {AUTH_OPTIONS.map(ed => <option key={ed.value || 'placeholder'} value={ed.value} disabled={ed.disabled}>{ed.label}</option>)}
                </select>
                <div style={S.helpText(mutedText)}>Choose how your organization authenticates with Endor Labs.</div>
              </div>}

            {showNamespace && <div style={S.formGroup(bgLight)}>
                <div style={S.label(textColor)}>Namespace <span style={{
    color: '#d32f2f'
  }}>*</span></div>
                <input type="text" maxLength="100" value={ns} onChange={e => {
    setNs(e.target.value);
    setError('');
  }} placeholder="your-namespace" style={S.input(bgColor, textColor)} />
                <div style={S.helpText(mutedText)}>Your Endor Labs namespace. Only alphanumeric characters, dots, hyphens, and underscores are allowed.</div>
              </div>}

            {showTenant && <div style={S.formGroup(bgLight)}>
                <div style={S.label(textColor)}>Tenant <span style={{
    color: '#d32f2f'
  }}>*</span></div>
                <input type="text" maxLength="100" value={tenant} onChange={e => {
    setTenant(e.target.value);
    setError('');
  }} placeholder="your-tenant" style={S.input(bgColor, textColor)} />
                <div style={S.helpText(mutedText)}>Your organization's SSO tenant name.</div>
              </div>}
          </div>

          {isTerminal && renderCodeBlock('Run this command in your terminal', commandText, 'terminal-cmd')}

          <div style={{
    display: 'flex',
    gap: '0.75rem',
    justifyContent: 'flex-start',
    alignItems: 'center',
    flexWrap: 'wrap'
  }}>
            <button type="button" onClick={handleReset} style={S.secondaryBtn(isDark, textColor)}>↺ Reset</button>
            {isJson && <button type="submit" style={S.primaryBtn(isDark, false)}>✦ Generate</button>}
            {isCursor && <button type="button" onClick={handleDeeplinkInstall} style={S.primaryBtn(isDark, true)}>
                {CURSOR_SVG}
                Install in Cursor
              </button>}
            {isVscode && <button type="button" onClick={handleDeeplinkInstall} style={S.primaryBtn(isDark, true)}>
                {VSCODE_SVG}
                Install in VS Code
              </button>}
          </div>

          {error && <div style={S.errorBox(isDark)}>{error}</div>}

          {isJson && !showResult && <div style={{
    marginTop: '0.75rem',
    padding: '0.5rem 0.75rem',
    background: bgLight,
    border: '1px solid rgba(38,208,124,0.35)',
    borderRadius: '8px',
    color: textColor,
    fontSize: '0.8rem'
  }}>
              ℹ Fill in your configuration above and click <strong>Generate</strong> to create a JSON configuration.
            </div>}

          {isJson && showResult && <div style={{
    marginTop: '1rem'
  }}>{renderCodeBlock('MCP server configuration for mcp.json', jsonConfigText, 'json-config')}</div>}

          {isCursor && <p style={{
    fontSize: '0.85rem',
    color: textColor,
    lineHeight: 1.45,
    margin: '0.75rem 0 0.5rem 0',
    opacity: 0.9
  }}>
              For manual configuration, copy and paste the following JSON directly into a{' '}
              <code style={{
    background: bgLight,
    padding: '0.15rem 0.35rem',
    borderRadius: '4px',
    fontSize: '0.8em'
  }}>.cursor/mcp.json</code>{' '}
              file in the root of your repository.
            </p>}

          {isDeeplink && <details style={{
    marginTop: isCursor ? '0' : '0.75rem'
  }}>
              <summary style={{
    cursor: 'pointer',
    fontSize: '0.85rem',
    color: textColor,
    opacity: 0.8,
    padding: '0.5rem 0',
    userSelect: 'none'
  }}>
                {isCursor ? '📄 View JSON configuration' : '📄 View manual JSON configuration'}
              </summary>
              {renderCodeBlock('MCP server configuration for mcp.json', jsonConfigText, 'deeplink-config')}
            </details>}

          {isTerminal && <details style={{
    marginTop: '0.75rem'
  }}>
              <summary style={{
    cursor: 'pointer',
    fontSize: '0.85rem',
    color: textColor,
    opacity: 0.8,
    padding: '0.5rem 0',
    userSelect: 'none'
  }}>📄 View manual configuration</summary>
              {renderCodeBlock(manualConfigLabel, manualConfigText, 'terminal-config')}
            </details>}
        </form>
      </div>
    </div>;
};

Scan dependencies, detect vulnerabilities, find leaked secrets, and review code for security issues directly inside Gemini CLI, powered by your AI agent.

You can install the MCP server with [Gemini CLI](#install-the-mcp-server) or as a [Gemini extension](#install-as-a-gemini-extension).

<Tip>
  You can also connect the [Endor Labs documentation MCP server](/introduction/docs-mcp-server) to get accurate, real-time answers about Endor Labs directly in your AI tools.
</Tip>

## What you can do

With the Endor Labs MCP server, you can:

* **Check dependency safety** before adding a new package
* **Scan for vulnerabilities and malware** in your open source dependencies
* **Find leaked secrets** accidentally committed in your Git history
* **Run AI security reviews** on your code changes (Enterprise Edition)

<Note>
  **Recommended: Node.js 24 LTS or later**

  The Endor Labs MCP server runs `endorctl` through `npx`. On Node.js versions older than 24, you may see a harmless warning printed before scan output.

  ```text theme={null}
  (node:NNNNN) ExperimentalWarning: CommonJS module .../debug/src/node.js is loading ES Module .../supports-color/index.js using require().
  ```

  This is a Node.js runtime notice, not an error from `endorctl` or the MCP server, and it does not affect scan results. To silence it, upgrade Node.js to 24 LTS or later, then restart your IDE or CLI. Verify with `node --version`.
</Note>

## Install the MCP server

<BadgeTabs>
  <div data-title="Developer Edition" data-badge="FREE" data-badge-color="green">
    The Developer Edition is free and uses default security policies from Endor Labs. You do not need an Endor Labs account. When you use the MCP server for the first time, a browser window opens for authentication through GitHub, GitLab, or Google.

    <br />

    Run the following command in your terminal to install the MCP server.

    ```shell theme={null}

    gemini mcp add endor-cli-tools npx -y endorctl ai-tools mcp-server

    ```

    For manual configuration, copy and paste the following configuration directly into your MCP configuration file.

    ```json theme={null}
        {
          "mcpServers": {
            "endor-cli-tools": {
              "type": "stdio",
              "command": "npx",
              "args": [
                "-y",
                "endorctl",
                "ai-tools",
                "mcp-server"
              ]
            }
          }
        }
    ```

    <Note>
      Have questions? Email us at [community-support@endor.ai](mailto:community-support@endor.ai).
    </Note>
  </div>

  <div data-title="Enterprise Edition" data-badge="PAID" data-badge-color="blue">
    The Enterprise Edition enforces your organization's specific security policies. You need your Endor Labs namespace and an authentication method. Ensure that your developers have Read-Only permissions to Endor Labs. See [Authorization policies](/platform-administration/rbac/authorization-policies) for more details.

    Generate the Enterprise Edition configuration for your organization.

    <McpInstallGenerator variant="terminal" platform="gemini" />

    The following parameters are used to configure the MCP server. All parameters are optional.

    * `ENDOR_MCP_SERVER_AUTH_MODE`: The authentication mode to use for the MCP server. You can use the following authentication modes: `github`, `gitlab`, `google`, `sso`. If you choose `sso`, you must add `ENDOR_MCP_SERVER_AUTH_TENANT` as an additional parameter. If not specified, the MCP server defaults to browser authentication for the Developer Edition.
    * `ENDOR_NAMESPACE`: The namespace to use for the MCP server. Required for Enterprise Edition to access your organization's specific policies. Not needed for Developer Edition.
    * `ENDOR_MCP_SERVER_AUTH_TENANT`: The tenant name for SSO authentication. Required when `ENDOR_MCP_SERVER_AUTH_MODE` is set to `sso` for Enterprise Edition access.
  </div>
</BadgeTabs>

## Verify the installation

```text theme={null}
/mcp list
```

Confirm that **endor-cli-tools** appears in the list. You can also use `/mcp` in Gemini CLI to view active MCP servers.

### Try a test prompt

After installing the MCP server, try the following prompt in your AI chat or CLI to verify that the tools are working.

```text theme={null}
Check if the npm package lodash version 4.17.20 has any vulnerabilities
```

The MCP server uses the `check_dependency_for_vulnerabilities` tool to check for known vulnerabilities and return the results. If you see a response with vulnerability details, the MCP server is working correctly.

## Scope options

Gemini CLI supports two MCP configuration scopes:

* `-s project`: Shared with everyone in the project via `.gemini/settings.json` file (default).
* `-s user`: Available to you across all projects via `~/.gemini/settings.json`.

## Manage MCP servers

```text theme={null}
/mcp list
```

In Gemini CLI, use `/mcp` to view and manage your active MCP servers.

## How to use the Endor Labs MCP server

The Endor Labs MCP server provides the following tools:

* `check_dependency_for_vulnerabilities`: Check if a dependency in your project is vulnerable.
* `check_dependency_for_risks`: Check a dependency for security risks including vulnerabilities and malware.
* `get_endor_vulnerability`: Get the details of a specific vulnerability from the Endor Labs vulnerability database.
* `get_resource`: Retrieve additional context from commonly used Endor Labs resources about your software, such as findings, vulnerabilities, and projects.
* `scan`: Run an Endor Labs security scan to detect risks in your open source dependencies, find common security issues, and spot any credentials accidentally exposed in your Git repository.
* `security_review`: Perform security review analysis on code diffs. Analyzes local uncommitted changes (both staged and unstaged) compared to HEAD, or diffs between the main branch and the last commit. Requires the Enterprise Edition. You must specify your namespace in the MCP server configuration. You must also enable AI security code review for your namespace in the Endor Labs platform. See [AI security code review](/secure-ai-coding/ai-security-review/) for setup instructions.

After you set up the MCP server, you can choose to disable the tools that you do not want to use.

## Configure GEMINI.md

To enhance the MCP server integration, you can add instructions in `GEMINI.md` at the root of your repository. Gemini CLI reads `GEMINI.md` files to guide AI development with your project-specific instructions.

1. Navigate to the root of your repository.

2. Create or edit the `GEMINI.md` file in the root of your repository.

3. Add appropriate rules for your project. For example, you can add a rule to check if the code is free from vulnerabilities.

### Example GEMINI.md instructions

You can use the following `GEMINI.md` instructions as a quick start for the Endor Labs MCP server. Modify the instructions to meet your specific organization's needs. For more information, refer to the [Gemini CLI documentation](https://github.com/google-gemini/gemini-cli).

<Tabs>
  <Tab title="SCA Rule Example">
    ```markdown theme={null}
    # Software Composition Analysis (SCA) Rule (Endor Labs via MCP)

    This project uses Endor Labs for automated dependency (SCA) scanning, integrated through the MCP server.

    ## Workflow

    Every time a manifest or lockfile (package.json, requirements.txt, go.mod, pom.xml, etc.) is created or modified in any way, immediately do the following prior to performing your next task.

    **Important**: Do not proceed after creating or modifying a manifest file without running this first.

    - Run `endor-cli-tools` using the `check_dependency_for_vulnerabilities` tool via the MCP server.
    - Provide the **ecosystem**, **dependency name**, and **version** always when making this tool call.
    - If a vulnerability or error is identified:
      - Upgrade to the suggested safe version, or
      - Replace the dependency with a non-vulnerable alternative.
    - Re-run the check using `endor-cli-tools` to confirm the issue is resolved.

    ## Notes
    - All scans must be performed using the MCP server integration (`endor-cli-tools`). Do not invoke `endorctl` directly.
    ```
  </Tab>

  <Tab title="Secrets Rule Example">
    ```markdown theme={null}
    # Leaked Secrets Detection Rule (Endor Labs via MCP)

    This project uses [Endor Labs](https://docs.endorlabs.com/) for automated security scanning, integrated through the MCP server.

    ## Workflow

    Whenever a file is modified in the repository, and before the end of an agent session:

    - Run `endor-cli-tools` using the `scan` tool via the MCP server to check for leaked secrets.
    - If any secrets or errors are detected:
      - Remove the exposed secret or correct the error immediately.
      - Re-run the scan to verify the secret has been properly removed.
    - Save scan results and remediation steps in a security log or as comments for audit purposes.

    ## Notes
    - All scans must be performed using the MCP server integration (`endor-cli-tools`). Do not invoke `endorctl` directly.
    - This scan must use the path of the directory from which the changed files are in. Use absolute paths.
    ```
  </Tab>

  <Tab title="SAST Rule Example">
    ```markdown theme={null}
    # Static Application Security Testing (SAST) Rule (Endor Labs via MCP)

    This project uses [Endor Labs](https://docs.endorlabs.com/) for automated SAST, integrated through the MCP server.

    ## Workflow

    Whenever a file is modified in the repository, and before the end of an agent session:

    - Run `endor-cli-tools` using the `scan` tool via the MCP server to perform SAST scans.
    - If any vulnerabilities or errors are found:
      - Present the issues to the user.
      - Recommend and apply appropriate fixes (e.g., input sanitization, validation, escaping, secure APIs).
    - Save scan results and remediation steps in a security log or as comments for audit purposes.

    ## Notes
    - All scans must be performed using the MCP server integration (`endor-cli-tools`). Do not invoke `endorctl` directly.
    - Do not invoke Opengrep directly.
    - This scan must use the path of the directory from which the changed files are in. Use absolute paths.
    ```
  </Tab>
</Tabs>

## Install as a Gemini extension

As an alternative to the MCP server configuration, you can install the Endor Labs MCP server as a Gemini extension. You can find the extension [on GitHub](https://github.com/endorlabs/gemini-extension).

```bash theme={null}
gemini extensions install https://github.com/endorlabs/gemini-extension.git
```

No additional configuration is required for Developer Edition. You do not need an Endor Labs account. When you first use a tool, a browser window opens allowing you to authenticate with GitHub, GitLab, or Google.

For Enterprise Edition, use a natural language command in Gemini CLI to initiate an authentication flow with your organization's namespace after installing the extension. For example:

```text theme={null}
Initialize Endor Labs with Google authentication using the command endorctl init --auth-mode=google
```

## Troubleshooting

Use the following troubleshooting steps to resolve common issues with the Endor Labs MCP server.

<AccordionGroup>
  <Accordion title="MCP server shows disconnected">
    Run `npx --version` in your terminal. If the command fails, install [Node.js](https://nodejs.org/) version 18 or later. After installing, restart your IDE or CLI to reload the MCP server configuration.
  </Accordion>

  <Accordion title="Browser auth window does not open">
    Ensure your IDE or CLI can open a browser. Check firewall or security software that might block browser launch. For Enterprise Edition with SSO, verify that `ENDOR_MCP_SERVER_AUTH_MODE` and `ENDOR_MCP_SERVER_AUTH_TENANT` are set correctly in your MCP configuration.
  </Accordion>

  <Accordion title="npx times out behind a corporate proxy">
    Install endorctl using your preferred method and configure the MCP server to call it directly instead of using npx. In the Enterprise Edition install wizard, select **No** under **Using npx?** to generate the correct configuration. Alternatively, replace the `command` and `args` entries in your MCP configuration manually:

    ```json theme={null}
    "command": "endorctl",
    "args": ["ai-tools", "mcp-server"]
    ```

    For installation options, see [Install endorctl](/setup-deployment/cli/). For more details on how npx and a system-installed endorctl differ, see the FAQ entry below.
  </Accordion>

  <Accordion title="Understanding npx vs. a system-installed endorctl">
    The default MCP server configuration uses `npx -y endorctl` to run endorctl. This command downloads endorctl from the npm registry into a temporary cache (`~/.npm/_npx/`) and runs it from there. It does **not** install endorctl globally and does **not** interact with any existing endorctl binary on your system.

    If you have endorctl installed separately (for example, through Homebrew or a direct download), the `npx` command runs its own copy and ignores the system-installed version. These two copies are completely independent.

    To use your existing endorctl installation instead of npx, select **No** under **Using npx?** in the Enterprise Edition install wizard. This generates a configuration that calls `endorctl` directly:

    ```json theme={null}
    "command": "endorctl",
    "args": ["ai-tools", "mcp-server"]
    ```

    With this approach, updates are managed by your existing package manager (for example, `brew upgrade endorctl`).
  </Accordion>

  <Accordion title="Tools return errors (Enterprise)">
    Verify your namespace is correct and your user has `Read-Only` permissions in Endor Labs. See [Authorization policies](/platform-administration/rbac/authorization-policies/) for details. Also ensure endorctl is on your PATH if you installed it globally instead of using npx.
  </Accordion>

  <Accordion title="MCP server fails to start on Windows">
    On Windows, ensure the following prerequisites are met:

    * Node.js is installed
    * npm global bin directory is in your PATH

    #### Install Node.js

    If Node.js is not installed, download and install the **LTS version** from [nodejs.org](https://nodejs.org/). During installation, ensure the option to add Node.js to PATH is selected.

    #### Configure the PATH environment variable

    After installing Node.js, verify that the npm global bin directory is in your PATH:

    1. Run the following command in the command line.

       ```powershell theme={null}
       npm config get prefix
       ```

       This returns the npm global directory path, typically `C:\Users\<YourUsername>\AppData\Roaming\npm`.

    2. Add the npm global directory path to the **Path** variable under **User variables** in your system's environment variables settings.

    3. Restart for the PATH changes to take effect.

    #### Verify the setup

    Run the following command in your terminal.

    ```powershell theme={null}
    npx --version
    ```

    If this returns a version number, your Windows setup is complete and the MCP server can use `npx` to run endorctl.
  </Accordion>
</AccordionGroup>
