Skip to content

Architecture

A run has three stages: collect a snapshot, lint it, and report the result. Each stage is a plain function, so the CLI, the GitHub Action, and the library API share the same code.

flowchart LR
    subgraph Collect
        stdio["stdio server<br/>(-- command)"]
        http["Streamable HTTP<br/>or HTTP+SSE (--url)"]
        file["Saved snapshot<br/>(--file)"]
        client["MCP client<br/>collect.ts"]
        parse["parseSnapshot<br/>snapshot.ts"]
        stdio --> client
        http --> client
        file --> parse
    end

    snap[("Snapshot<br/>tools, prompts,<br/>resources, templates")]
    client --> snap
    parse --> snap

    subgraph Lint
        rules["21 rules<br/>schema, description,<br/>injection, capability, naming"]
        config[".mcp-lint.json<br/>severities, ignores"]
        score["computeScore<br/>score.ts"]
        config --> rules
        rules -->|findings| score
    end

    snap --> rules

    subgraph Report
        text[text]
        json[JSON]
        md[Markdown]
        sarif[SARIF 2.1.0]
    end

    score --> text
    score --> json
    score --> md
    score --> sarif
    score --> exit{{"exit 0 / 1 / 2"}}

Collect

collect.ts connects with the official TypeScript SDK and pages through tools/list, prompts/list, resources/list, and resources/templates/list, following nextCursor. It sends raw requests instead of the SDK's typed helpers, because those helpers reject exactly the malformed tools a linter needs to report (see ADR 0003).

snapshot.ts accepts a bare tools/list result, a full JSON-RPC response, or a snapshot written by --save, and remembers the line where each tool starts so SARIF results point at it.

Lint

Every rule has an ID of the form category/name, a default severity, and a check function that receives the snapshot and a report callback. lint.ts runs the enabled rules, applies severity overrides from the config, and sorts findings by entity and severity.

Score

Each tool, prompt, resource, and resource template starts at 100 and loses 25 per error, 8 per warning, and 2 per info finding. The server score is the mean of the entity scores, minus server-level findings, and any injection/* error caps it at 50 (see ADR 0002).

Report

The reporters in src/report/ turn one LintResult into text, JSON, Markdown, or SARIF. The CLI maps the result to an exit code: 0 passed, 1 below the minimum score, 2 usage or connection error.

Distribution

The same source ships four ways: the npm package (tsc output), standalone executables compiled with bun build --compile, a container image that runs a single bundled JavaScript file on Node.js, and a composite GitHub Action that builds the CLI from the tagged source.