Skip to main content
Glama
x0base

mcp-security-toolkit

by x0base

wordlist_gen

Produce targeted wordlists for security assessments by combining brand, names, and keywords in password, username, or subdomain modes.

Instructions

Generate a wordlist tailored to the target surface.

Modes:

  • passwords: combine brand / keyword seeds with leet substitution, capitalization variants, common suffixes and year suffixes.

  • usernames: combine person names into common patterns (first, last, first.last, flast, firstl, …).

  • subdomains: combine brand + keywords with a curated list of common environment / service subdomain labels.

Pure function. No network.

Args: mode: One of passwords, usernames, subdomains. brand: Target organization brand (used in all modes). names: List of person names ("Jane Doe") for usernames mode. keywords: Additional seed words for passwords / subdomains. years: Year strings to append (passwords mode). max_size: Hard cap on returned entries.

Returns: GenReport with sample (the wordlist itself, up to max_size).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
modeYes
brandNo
namesNo
keywordsNo
yearsNo
max_sizeNo

Implementation Reference

  • Main tool function `wordlist_gen` that generates password/usernames/subdomain wordlists based on OSINT surface info. Orchestrates calls to _passwords, _usernames, _subdomains helpers and returns a GenReport.
    def wordlist_gen(
        mode: Literal["passwords", "usernames", "subdomains"],
        brand: str | None = None,
        names: list[str] | None = None,
        keywords: list[str] | None = None,
        years: list[str] | None = None,
        max_size: int = 5000,
    ) -> dict:
        """Generate a wordlist tailored to the target surface.
    
        Modes:
          - `passwords`: combine brand / keyword seeds with leet substitution,
            capitalization variants, common suffixes and year suffixes.
          - `usernames`: combine person names into common patterns
            (`first`, `last`, `first.last`, `flast`, `firstl`, …).
          - `subdomains`: combine brand + keywords with a curated list of
            common environment / service subdomain labels.
    
        Pure function. No network.
    
        Args:
            mode: One of `passwords`, `usernames`, `subdomains`.
            brand: Target organization brand (used in all modes).
            names: List of person names ("Jane Doe") for `usernames` mode.
            keywords: Additional seed words for `passwords` / `subdomains`.
            years: Year strings to append (passwords mode).
            max_size: Hard cap on returned entries.
    
        Returns:
            GenReport with `sample` (the wordlist itself, up to `max_size`).
        """
        if mode not in ("passwords", "usernames", "subdomains"):
            return {"error": f"mode must be passwords|usernames|subdomains, got {mode!r}"}
    
        seeds: list[str] = []
        if brand:
            seeds.append(brand)
        if keywords:
            seeds.extend(keywords)
    
        if mode == "passwords":
            out = _passwords(seeds, years or [])
        elif mode == "usernames":
            if not names:
                return {"error": "usernames mode requires `names`"}
            out = _usernames(names, brand)
        else:
            if not brand:
                return {"error": "subdomains mode requires `brand`"}
            out = _subdomains(brand, keywords or [])
    
        sorted_out = sorted({w for w in out if w})
        truncated = len(sorted_out) > max_size
        return GenReport(
            mode=mode,
            total=len(sorted_out),
            sample=sorted_out[:max_size],
            truncated=truncated,
        ).model_dump()
  • Tool registration via `mcp.tool()(wordlist_gen.wordlist_gen)` in the FastMCP server.
    mcp.tool()(wordlist_gen.wordlist_gen)
  • GenReport Pydantic model defining the return schema (mode, total, sample, truncated).
    class GenReport(BaseModel):
        mode: str
        total: int
        sample: list[str]
        truncated: bool
  • _passwords helper: generates password candidates from seeds with leet, capitalization, suffixes and years.
    def _passwords(seeds: list[str], years: list[str]) -> set[str]:
        out: set[str] = set()
        for s in seeds:
            if not s:
                continue
            base = s.lower()
            out.update(_capitalize_variants(base))
            out.add(_leet(base))
            out.add(_leet(base).capitalize())
            for suf in SUFFIXES + years:
                if not suf:
                    continue
                out.add(base + suf)
                out.add(base.capitalize() + suf)
                out.add(_leet(base) + suf)
        return out
  • _usernames helper: generates username patterns from person names (first.last, flast, etc.).
    def _usernames(names: list[str], brand: str | None) -> set[str]:
        out: set[str] = set()
        for n in names:
            n = n.strip().lower()
            if not n:
                continue
            parts = n.split()
            if len(parts) == 1:
                out.add(parts[0])
                continue
            first, *_, last = parts
            out.add(f"{first}")
            out.add(f"{last}")
            out.add(f"{first}.{last}")
            out.add(f"{first[0]}{last}")
            out.add(f"{first}{last[0]}")
            out.add(f"{first}_{last}")
            out.add(f"{last}.{first}")
            out.add(f"{last}{first[0]}")
            if brand:
                out.add(f"{first}.{last}@{brand.lower()}")
        return out
  • _subdomains helper: combines brand + keywords with common subdomain labels (dev, staging, api, etc.).
    def _subdomains(brand: str, keywords: list[str]) -> set[str]:
        out: set[str] = set()
        base = brand.lower()
        common = [
            "dev", "stage", "staging", "test", "qa", "uat", "preprod", "beta",
            "internal", "intranet", "vpn", "mail", "smtp", "imap", "owa", "exchange",
            "api", "admin", "portal", "auth", "sso", "git", "gitlab", "github",
            "jenkins", "ci", "cd", "k8s", "prom", "grafana", "kibana", "splunk",
            "old", "new", "backup", "bak", "legacy", "demo", "sandbox", "lab",
        ]
        out.update(common)
        for kw in keywords:
            kw = kw.strip().lower()
            if not kw:
                continue
            out.add(kw)
            for sep, suf in product(SEPARATORS, common):
                out.add(f"{kw}{sep}{suf}")
        for suf in common:
            out.add(f"{base}-{suf}")
            out.add(f"{suf}-{base}")
        return out

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observedv0.3.1

TDQS

A4.6/5.0
Behavior4/5

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

Without annotations, the description carries the full burden. It explicitly states 'Pure function. No network.' which is a key behavioral trait. It also describes the return type (GenReport with sample) and the hard cap via max_size. However, it doesn't mention if there are any limits on input sizes or potential errors.

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?

The description is highly concise and well-structured: a one-line summary, then bulleted mode descriptions with clear transformations, followed by a parameter list. Every sentence adds value, and the format is easy to parse for an AI agent.

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

Completeness5/5

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

Given 6 parameters, no output schema, and no annotations, the description covers all essential aspects: purpose, mode details, parameter roles, behavioral traits (pure, no network), and return type. There are no obvious gaps that would hinder correct invocation.

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

Parameters5/5

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

The input schema has no descriptions (0% coverage), but the tool description fully compensates by explaining each parameter: mode enum, brand used across modes, names for usernames, keywords for passwords/subdomains, years for passwords, and max_size as a cap. This provides all necessary semantic context beyond the raw schema.

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 states the tool's purpose: 'Generate a wordlist tailored to the target surface.' It lists three distinct modes (passwords, usernames, subdomains) with specific transformations, making it easy to understand the output. The tool's function is well-differentiated from sibling tools which focus on audits, creds lookup, etc.

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 explicit usage scenarios by mode (e.g., combine brand/keywords with leet substitution for passwords). It doesn't state when to avoid using the tool, but the mode descriptions implicitly guide appropriate contexts. No explicit alternatives are given, but the sibling tools are clearly different.

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