Skip to main content
Glama

faf_auto

Scans package manifests to auto-detect project stack, then generates or updates a .faf file with language, framework, database, and build tools extracted from actual dependencies.

Instructions

Auto-detect project stack and generate/update a .faf file. Scans for package.json, pyproject.toml, Cargo.toml, go.mod, and other manifest files. Extracts language, framework, database, API type, and build tools from actual dependencies — no hardcoded defaults. Creates a new .faf if none exists, or fills empty slots in an existing one.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
directoryNo.
pathNoproject.faf

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • server.py:554-554 (registration)
    Tool registration via @mcp.tool() decorator on the faf_auto function.
    @mcp.tool()
  • Handler function for the faf_auto tool: auto-detects project stack from manifest files (pyproject.toml, package.json, Cargo.toml, etc.), generates/updates a .faf file, and returns detected metadata with Mk4 score.
    def faf_auto(directory: str = ".", path: str = "project.faf") -> dict:
        """Auto-detect project stack and generate/update a .faf file.
        Scans for package.json, pyproject.toml, Cargo.toml, go.mod, and other
        manifest files. Extracts language, framework, database, API type, and
        build tools from actual dependencies — no hardcoded defaults.
        Creates a new .faf if none exists, or fills empty slots in an existing one."""
        try:
            dir_path = Path(directory).resolve()
            if not dir_path.is_dir():
                return {"success": False, "error": f"Directory not found: {directory}"}
    
            detected = _detect_stack(directory)
    
            # Resolve path relative to directory
            faf_path = Path(path)
            if not faf_path.is_absolute():
                faf_path = dir_path / faf_path
            faf_path = faf_path.resolve()
    
            created = not faf_path.exists()
    
            if created:
                # Generate new .faf from detections
                name = detected.get("name") or dir_path.name or "my-project"
                lang = detected.get("main_language", "unknown")
                goal = detected.get("goal") or "Describe your project goal"
                version = detected.get("version") or "0.1.0"
                content = f"""faf_version: '2.5.0'
    project:
      name: {name}
      goal: {goal}
      main_language: {lang}
    stack:
      frontend: {detected.get('framework') if detected.get('framework') in ('React', 'Vue', 'Svelte', 'Next.js') else 'null'}
      backend: {detected.get('framework') if detected.get('framework') in ('FastAPI', 'Flask', 'Django', 'Express', 'FastMCP', 'Axum', 'Actix', 'Gin', 'Echo') else 'null'}
      database: {detected.get('database', 'null')}
      testing: {detected.get('testing', 'null')}
    human_context:
      who: Developers
      what: {goal}
      why: Why does this project exist?
    ai_instructions:
      priority: Read project.faf first
      usage: Code-first, minimal explanations
    preferences:
      quality_bar: zero_errors
      commit_style: conventional
    state:
      phase: development
      version: {version}
      status: active
    """
                faf_path.parent.mkdir(parents=True, exist_ok=True)
                faf_path.write_text(content)
            else:
                # Update existing: fill only empty/null slots
                existing = faf_path.read_text()
                updated = existing
                # Fill null main_language
                if detected.get("main_language") and ("main_language: null" in updated or "main_language: unknown" in updated):
                    updated = updated.replace("main_language: null", f"main_language: {detected['main_language']}")
                    updated = updated.replace("main_language: unknown", f"main_language: {detected['main_language']}")
                
                # Fill metadata if missing
                for field, key in [("name", "name"), ("goal", "goal"), ("version", "version")]:
                    val = detected.get(key)
                    if val and f"{field}: null" in updated:
                        updated = updated.replace(f"{field}: null", f"{field}: {val}")
                    if val and field == "goal" and "Describe your project goal" in updated:
                        updated = updated.replace("Describe your project goal", val)
    
                # Fill null stack fields
                for field, key in [("frontend", "framework"), ("backend", "framework"), ("database", "database"), ("testing", "testing")]:
                    val = detected.get(key)
                    if val and f"  {field}: null" in updated:
                        # Only set frontend for frontend frameworks, backend for backend frameworks
                        if field == "frontend" and val not in ("React", "Vue", "Svelte", "Next.js"):
                            continue
                        if field == "backend" and val not in ("FastAPI", "Flask", "Django", "Express", "FastMCP", "Axum", "Actix", "Gin", "Echo"):
                            continue
                        updated = updated.replace(f"  {field}: null", f"  {field}: {val}")
                if updated != existing:
                    faf_path.write_text(updated)
    
            # Score with Mk4 engine
            try:
                mk4 = _mk4_score_file(str(faf_path))
                score = mk4.score
                tier = mk4.tier
            except Exception:
                score = 0
                tier = "RED"
    
            lang = detected.get("main_language", "unknown")
            fw = detected.get("framework")
            action = "Created" if created else "Updated"
            msg_parts = [f"Detected {lang}"]
            if fw:
                msg_parts[0] += f"/{fw}"
            msg_parts.append(f"{action} {faf_path.name} at {score}%.")
    
            return {
                "success": True,
                "path": str(faf_path),
                "created": created,
                "detected": {
                    "main_language": detected.get("main_language"),
                    "package_manager": detected.get("package_manager"),
                    "build_tool": detected.get("build_tool"),
                    "framework": detected.get("framework"),
                    "api_type": detected.get("api_type"),
                    "database": detected.get("database"),
                },
                "score": score,
                "tier": tier,
                "message": " ".join(msg_parts),
            }
        except Exception as e:
            return {"success": False, "error": str(e)}
  • Helper function that scans a directory for manifest files (pyproject.toml, package.json, Cargo.toml, go.mod, etc.) and detects the project's language, framework, database, testing frameworks, package manager, and metadata (name, version, goal).
    def _detect_stack(directory: str) -> dict:
        """Scan directory for manifest files and detect project stack.
        Only sets values that are actually detected — never hardcodes defaults."""
        detected = {}
        dir_path = Path(directory).resolve()
    
        # Check which manifest files exist
        has_pyproject = (dir_path / "pyproject.toml").is_file()
        has_package_json = (dir_path / "package.json").is_file()
        has_cargo = (dir_path / "Cargo.toml").is_file()
        has_go_mod = (dir_path / "go.mod").is_file()
        has_requirements = (dir_path / "requirements.txt").is_file()
        has_gemfile = (dir_path / "Gemfile").is_file()
        has_composer = (dir_path / "composer.json").is_file()
        has_tsconfig = (dir_path / "tsconfig.json").is_file()
    
        # Priority: pyproject.toml / Cargo.toml / go.mod > package.json
        if has_pyproject:
            detected["main_language"] = "Python"
            try:
                content = (dir_path / "pyproject.toml").read_text()
                # Detect build system
                if "setuptools" in content:
                    detected["build_tool"] = "setuptools"
                elif "hatchling" in content or "hatch" in content:
                    detected["build_tool"] = "hatch"
                elif "flit" in content:
                    detected["build_tool"] = "flit"
                elif "pdm" in content:
                    detected["build_tool"] = "pdm"
                elif "poetry" in content:
                    detected["build_tool"] = "poetry"
                detected["package_manager"] = "pip"
    
                # Detect frameworks from dependencies
                content_lower = content.lower()
                if "fastmcp" in content_lower:
                    detected["framework"] = "FastMCP"
                    detected["api_type"] = "MCP"
                elif "fastapi" in content_lower:
                    detected["framework"] = "FastAPI"
                    detected["api_type"] = "REST"
                elif "flask" in content_lower:
                    detected["framework"] = "Flask"
                    detected["api_type"] = "REST"
                elif "django" in content_lower:
                    detected["framework"] = "Django"
                    detected["api_type"] = "REST"
    
                # Detect databases
                if "bigquery" in content_lower or "google-cloud-bigquery" in content_lower:
                    detected["database"] = "BigQuery"
                elif "psycopg" in content_lower or "asyncpg" in content_lower or "postgresql" in content_lower:
                    detected["database"] = "PostgreSQL"
                elif "pymongo" in content_lower or "motor" in content_lower:
                    detected["database"] = "MongoDB"
                elif "redis" in content_lower:
                    detected["database"] = "Redis"
                elif "sqlalchemy" in content_lower:
                    detected["database"] = "SQLAlchemy"
    
                # Detect testing
                if "pytest" in content_lower:
                    detected["testing"] = "pytest"
            except Exception:
                pass
    
        elif has_cargo:
            detected["main_language"] = "Rust"
            detected["package_manager"] = "cargo"
            try:
                content = (dir_path / "Cargo.toml").read_text()
                content_lower = content.lower()
                if "tokio" in content_lower:
                    detected["framework"] = "Tokio"
                if "axum" in content_lower:
                    detected["framework"] = "Axum"
                    detected["api_type"] = "REST"
                elif "actix" in content_lower:
                    detected["framework"] = "Actix"
                    detected["api_type"] = "REST"
            except Exception:
                pass
    
        elif has_go_mod:
            detected["main_language"] = "Go"
            detected["package_manager"] = "go modules"
            try:
                content = (dir_path / "go.mod").read_text()
                if "gin-gonic" in content:
                    detected["framework"] = "Gin"
                    detected["api_type"] = "REST"
                elif "echo" in content:
                    detected["framework"] = "Echo"
                    detected["api_type"] = "REST"
            except Exception:
                pass
    
        elif has_package_json:
            detected["main_language"] = "TypeScript" if has_tsconfig else "JavaScript"
            detected["package_manager"] = "npm"
            try:
                import json as _json
                pkg = _json.loads((dir_path / "package.json").read_text())
                all_deps = {}
                all_deps.update(pkg.get("dependencies", {}))
                all_deps.update(pkg.get("devDependencies", {}))
                dep_keys = " ".join(all_deps.keys()).lower()
    
                if "next" in all_deps:
                    detected["framework"] = "Next.js"
                elif "react" in all_deps:
                    detected["framework"] = "React"
                elif "vue" in all_deps:
                    detected["framework"] = "Vue"
                elif "svelte" in all_deps or "@sveltejs/kit" in all_deps:
                    detected["framework"] = "Svelte"
                elif "express" in all_deps:
                    detected["framework"] = "Express"
                    detected["api_type"] = "REST"
    
                if "jest" in dep_keys:
                    detected["testing"] = "Jest"
                elif "vitest" in dep_keys:
                    detected["testing"] = "Vitest"
                elif "mocha" in dep_keys:
                    detected["testing"] = "Mocha"
    
                if "yarn.lock" in [f.name for f in dir_path.iterdir() if f.is_file()]:
                    detected["package_manager"] = "yarn"
                elif "pnpm-lock.yaml" in [f.name for f in dir_path.iterdir() if f.is_file()]:
                    detected["package_manager"] = "pnpm"
            except Exception:
                pass
    
        elif has_requirements:
            detected["main_language"] = "Python"
            detected["package_manager"] = "pip"
    
        elif has_gemfile:
            detected["main_language"] = "Ruby"
            detected["package_manager"] = "bundler"
    
        elif has_composer:
            detected["main_language"] = "PHP"
            detected["package_manager"] = "composer"
    
        # TypeScript upgrade if tsconfig exists alongside non-TS detection
        if has_tsconfig and detected.get("main_language") == "JavaScript":
            detected["main_language"] = "TypeScript"
    
        # --- Metadata Extraction (Name, Version, Goal) ---
        try:
            if has_pyproject:
                import tomllib
                data = tomllib.loads((dir_path / "pyproject.toml").read_text())
                if "project" in data:
                    detected["name"] = data["project"].get("name")
                    detected["version"] = data["project"].get("version")
                    detected["goal"] = data["project"].get("description")
            elif has_package_json:
                import json as _json
                data = _json.loads((dir_path / "package.json").read_text())
                detected["name"] = data.get("name")
                detected["version"] = data.get("version")
                detected["goal"] = data.get("description")
            elif has_cargo:
                content = (dir_path / "Cargo.toml").read_text()
                import re
                name_match = re.search(r'^name\s*=\s*"(.*)"', content, re.MULTILINE)
                version_match = re.search(r'^version\s*=\s*"(.*)"', content, re.MULTILINE)
                if name_match: detected["name"] = name_match.group(1)
                if version_match: detected["version"] = version_match.group(1)
        except Exception:
            pass
    
        return detected
  • Helper function used by faf_auto to score the generated/updated .faf file using the Mk4 Championship Scoring Engine.
    def _mk4_score_file(path: str):
        """Read a .faf file and return Mk4Result."""
        content = Path(path).read_text()
        return score_faf(content)
Behavior4/5

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

With no annotations, the description carries full burden. It discloses that the tool scans for manifests, extracts dependencies, and creates/updates .faf, including that it only fills empty slots. Missing details like permissions or side effects, but overall adequate.

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?

Three concise sentences with no wasted words. First sentence states purpose, second details scanning behavior, third clarifies update behavior. Front-loaded and efficient.

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

Completeness3/5

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

Covers key actions and scan targets but lacks details on error handling, what happens if .faf exists with non-empty slots, or return value. Output schema exists but is not described. Adequate for a straightforward tool but not complete.

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

Parameters2/5

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

Schema description coverage is 0% and the tool description does not explain the two parameters (directory, path). Their meaning is only implied by context, which is insufficient for precise agent use.

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 auto-detects project stack and generates/updates a .faf file. It specifies what manifest files it scans and what it extracts, distinguishing it from siblings like faf_init which likely does not auto-detect.

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

Usage Guidelines3/5

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

The description implies usage for auto-detecting project stack but does not explicitly state when to use it versus alternatives like faf_init or faf_validate. No when-not-to-use guidance is provided.

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/Wolfe-Jam/gemini-faf-mcp'

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