Skip to main content
Glama

check_conformance

Score an MCP server manifest against the specification. Runs 18 checks across REQUIRED, RECOMMENDED, and BEST PRACTICE tiers to return a grade from A+ to F.

Instructions

Score an MCP server manifest against the MCP specification.

Runs 18 checks across 3 tiers (REQUIRED, RECOMMENDED, BEST PRACTICE) and returns a grade from A+ to F. A server is conformant when all REQUIRED checks pass. Grade F means at least one REQUIRED check failed.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
url_or_pathYesLocal path to server.json OR base URL of MCP server

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Implementation Reference

  • The check_conformance function is the MCP tool handler that scores an MCP server manifest against the MCP specification. It accepts a URL or local file path, delegates to the bawbel CLI via _run_bawbel(['scan-conformance', url_or_path]), parses the result (score, grade, is_conformant, checks), categorizes checks into failed/passed/skipped, and returns a formatted human-readable conformance report.
    @mcp.tool()
    def check_conformance(url_or_path: str) -> str:
        """
        Score an MCP server manifest against the MCP specification.
    
        Runs 18 checks across 3 tiers (REQUIRED, RECOMMENDED, BEST PRACTICE)
        and returns a grade from A+ to F. A server is conformant when all
        REQUIRED checks pass. Grade F means at least one REQUIRED check failed.
    
        Accepts:
            - A local file path to a server.json manifest
            - A server base URL (fetches .well-known/mcp.json automatically)
    
        Args:
            url_or_path: Local path to server.json OR base URL of MCP server
        """
        result = _run_bawbel(["scan-conformance", url_or_path])
    
        if result.get("error"):
            return f"Error: {result['error']}"
    
        score = result.get("score", 0)
        grade = result.get("grade", "?")
        is_conformant = result.get("is_conformant", False)
        checks = result.get("checks", [])
    
        lines = [
            f"Conformance score: {score:.0f}/100  Grade: {grade}",
            f"Conformant: {'Yes' if is_conformant else 'No (REQUIRED check failed)'}",
            "",
        ]
    
        failed = [c for c in checks if c.get("status") == "FAIL"]
        passed = [c for c in checks if c.get("status") == "PASS"]
        skipped = [c for c in checks if c.get("status") == "SKIP"]
    
        if failed:
            lines.append(f"FAILED ({len(failed)}):")
            for c in failed:
                tier = c.get("tier", "")
                name = c.get("check_id", "")
                msg = c.get("message", "")
                lines.append(f"  [{tier}] {name}: {msg}")
            lines.append("")
    
        lines.append(
            f"Passed: {len(passed)}  Failed: {len(failed)}  Skipped: {len(skipped)}"
        )
    
        return "\n".join(lines)
  • The _run_bawbel helper function is used by check_conformance to execute the 'bawbel scan-conformance' CLI command. It runs the bawbel CLI with arguments, parses JSON output, and handles errors (timeout, parse errors, missing CLI).
    def _run_bawbel(args: list[str], input_file: Optional[str] = None) -> dict:
        """
        Run bawbel CLI and return parsed JSON output.
        Returns error dict on failure.
        """
        cmd = ["bawbel"] + args + ["--format", "json"]
        if input_file:
            cmd.append(input_file)
    
        try:
            result = subprocess.run(  # nosec B603  # noqa: S603
                cmd,
                capture_output=True,
                text=True,
                timeout=60,
            )
            raw = result.stdout.strip()
            if not raw:
                return {
                    "error": result.stderr.strip() or "Scanner produced no output",
                    "findings": [],
                    "toxic_flows": [],
                    "risk_score": 0,
                }
    
            # JSON output is a list of file results
            start = raw.find("[")
            if start < 0:
                return {"error": raw[:300], "findings": [], "toxic_flows": [], "risk_score": 0}
    
            results = json.loads(raw[start:])
            if results:
                return results[0]
            return {"findings": [], "toxic_flows": [], "risk_score": 0}
    
        except subprocess.TimeoutExpired:
            return {"error": "Scan timeout (60s)", "findings": [], "toxic_flows": [], "risk_score": 0}
        except json.JSONDecodeError as e:
            return {"error": f"Parse error: {e}", "findings": [], "toxic_flows": [], "risk_score": 0}
        except FileNotFoundError:
            return {
                "error": (
                    "bawbel CLI not found. "
                    "Install with: pip install bawbel-scanner"
                ),
                "findings": [],
                "toxic_flows": [],
                "risk_score": 0,
            }
  • The FastMCP server instance registers all tools via decorators. The instructions mention check_conformance as a tool to 'verify a server follows the MCP spec.' The actual tool registration happens via the @mcp.tool() decorator on line 288.
    mcp = FastMCP(
        name="Bawbel Scanner",
        instructions=(
            "Security scanner for MCP servers and agentic AI components. "
            "Use scan_server_card before connecting to any MCP server. "
            "Use scan_content to check skill files or system prompts. "
            "Use check_conformance to verify a server follows the MCP spec. "
            "Use lookup_ave or search_ave to query the AVE threat intelligence database."
        ),
    )
  • The function signature and docstring define the input schema: it accepts a single string parameter 'url_or_path' (local path to server.json or base URL of MCP server). The output is a formatted string with score, grade, conformant status, and list of failed/passed/skipped checks.
    def check_conformance(url_or_path: str) -> str:
        """
        Score an MCP server manifest against the MCP specification.
    
        Runs 18 checks across 3 tiers (REQUIRED, RECOMMENDED, BEST PRACTICE)
        and returns a grade from A+ to F. A server is conformant when all
        REQUIRED checks pass. Grade F means at least one REQUIRED check failed.
    
        Accepts:
            - A local file path to a server.json manifest
            - A server base URL (fetches .well-known/mcp.json automatically)
    
        Args:
            url_or_path: Local path to server.json OR base URL of MCP server
        """
Behavior3/5

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

No annotations are provided, so the description bears full responsibility. It discloses that the tool runs 18 checks and returns a grade, but does not mention side effects (e.g., network calls, data mutation) or required permissions. Since it likely reads a manifest, the lack of explicit non-destructive hint is a gap.

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 two sentences, front-loaded with the core action, and contains no fluff. Every sentence adds value.

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

Completeness4/5

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

Given that an output schema exists, the description need not detail return values. It explains grading tiers and conformance condition, which is sufficient. However, more context about the checks themselves (e.g., what they cover) might help but is not required.

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?

The input schema has 100% coverage with a clear description of the single parameter (url_or_path). The description adds minimal extra meaning beyond what the schema provides, only referencing 'MCP server manifest' which is implicit. 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 states the tool scores an MCP server manifest against the specification, with specific checks across three tiers and a grade from A+ to F. This clearly distinguishes it from sibling tools like check_pins or scan_content.

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 explains the tool's function and grading criteria, but does not explicitly state when to use this tool versus alternatives or provide when-not-to-use guidance. The context from sibling names makes it somewhat clear, but explicit exclusions are missing.

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/bawbel/bawbel-mcp'

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