How to install
You need your access token: a name and a secret, which you receive when you buy. Then:
Requirements
- Python 3.11 or newer, and a terminal.
- For servers reached by a web address: the
connectextra (installed below). Local servers are never started; their tool list is read from a file you save (see the guide). - For the AI review only: an Anthropic API key in the environment variable
ANTHROPIC_API_KEY(a Claude subscription is not an API key; create one at platform.claude.com, billed separately). Without it every other part works and the reports say "AI review: not run".
Install
From the private package index, with the access token you were sent: a name and a secret. The token is yours alone; keep it in your password manager or your pipeline's secret store, and tell us at once if it leaks — you get a new one. Read both into the shell, the secret without it being shown or kept in your history:
read -r FAINTPRINT_USER && export FAINTPRINT_USER
read -rs FAINTPRINT_TOKEN && export FAINTPRINT_TOKEN
Install it once with pipx, a standard tool that puts a Python command where your terminal finds it, in any folder. pipx is needed once per machine: on a Mac with Homebrew, brew install pipx, then pipx ensurepath and a new terminal window; elsewhere, see pipx's install page.
PIP_INDEX_URL="https://$FAINTPRINT_USER:$FAINTPRINT_TOKEN@gitlab.com/api/v4/projects/faintprint%2Ffaintprint/packages/pypi/simple" pipx install --force "faintprint[connect]==0.5.0"
The index also passes on requests for the other libraries Faintprint needs to the public Python index, so this one address is all you give. The token goes to this one command only; nothing on your machine keeps it. To update, run the same line with the new version number from the changelog.
Without pipx, in a Python environment of its own in the current folder. Faintprint needs Python 3.11 or newer, and the python3 Apple ships with macOS is older: use one from Homebrew or python.org. pip shows the secret as **** in its output.
python3 -m venv .venv && .venv/bin/pip install --index-url "https://$FAINTPRINT_USER:$FAINTPRINT_TOKEN@gitlab.com/api/v4/projects/faintprint%2Ffaintprint/packages/pypi/simple" "faintprint[connect]==0.5.0"
source .venv/bin/activate
As a container image (built for Intel/AMD Linux; on an Apple-silicon Mac, Docker runs it under emulation), with the same token:
printf '%s' "$FAINTPRINT_TOKEN" | docker login registry.gitlab.com --username "$FAINTPRINT_USER" --password-stdin
docker run --rm -v "$PWD:/work" registry.gitlab.com/faintprint/faintprint:0.5.0 scan /work/mcp.json
The folder you run it from is /work inside the container, and the reports are written to scan-reports/ in it. On Linux, add --user "$(id -u):$(id -g)" after --rm so the reports belong to you.
Installed with pipx, the command is then faintprint, in any folder (in a .venv environment, after source .venv/bin/activate):
faintprint --version
faintprint scan --help
First run
What your AI tools on this machine are connected to:
faintprint scan
With nothing named, it reads the config files of your AI tools on this machine (read only) and scans the servers they list — the same as --discover. It prints every place it looked.
One server by its web address:
faintprint scan https://example.com/mcp
Write the address in full, with https://. A name without it is read as a file name; if it looks like an address, the command says so and shows the full form.
A saved tool list, or a config file of Claude Desktop, Claude Code, Cursor, Windsurf or VS Code:
faintprint scan path/to/mcp.json
Reports go to scan-reports/<date and time>/ unless you pass --out DIR: index.html for the run, one page and one JSON file per server, and the fetched tool list beside each report so the same report can be rebuilt later with no network. Typed in a terminal, the run's page opens in your browser when the scan ends; --no-open skips that. In a pipeline or a script it never opens.
Add --review to have the borderline passages read by the AI reviewer. The command prints the passages, the number of calls, the estimated and worst-case price, the model, and exactly what is sent; checks your key with a free token count that sends the key and one fixed word (a refused key is said there, and nothing is spent); then asks. In a pipeline, --yes answers in advance and the review runs only within --max-cost (default $1.00).
The build check
The exit code is the answer:
| code | meaning |
|---|---|
| 0 | pass: everything checked, nothing at or above the level |
| 1 | fail: a server shows signs of an attack at or above the level |
| 2 | could not run: wrong arguments, an unreadable file, or nothing to scan |
| 3 | not finished: something was not checked (a review not run, a server not reachable, a local server without a tool list) |
The level is "Suspicious or worse" unless you pass --fail-on minor | suspicious | likely-attack | never. --allow-unfinished lets an unfinished run pass, with a warning.
The full guide, including the one command that saves a local server's tool list, is the guide.
GitHub's Security tab
--sarif FILE also writes the findings in SARIF, the standard format code scanners hand their findings over in. Uploaded to GitHub, each sign of an attack becomes an alert in the repository's Security tab, at the line that holds the passage, and a borderline passage the AI review has not read becomes a note:
faintprint scan .vscode/mcp.json --out faintprint-reports --sarif faintprint-reports/faintprint.sarif
In a pipeline the step below writes it for you and uploads it with upload-sarif: true. Code scanning is free on public repositories; on a private one it needs GitHub Code Security. The guide has the details.
In a pipeline
A ready-made step installs Faintprint with your token, scans what you name, keeps the reports as the job's artefact (also when the job fails) and ends the job with the exit code above. Store the token's name and secret as pipeline secrets, FAINTPRINT_USER and FAINTPRINT_TOKEN; the step hands them to the install only. The AI review stays off unless you turn it on with your own ANTHROPIC_API_KEY, and then runs only within max-cost.
GitHub Actions:
- uses: faintprint/scan-action@714a55b7270c66d97276c687557f4284b0aeb448 # v1.3.0
with:
scan: .vscode/mcp.json
deploy-user: ${{ secrets.FAINTPRINT_USER }}
deploy-token: ${{ secrets.FAINTPRINT_TOKEN }}
GitLab CI (on gitlab.com; the guide has the line for a GitLab you run yourself):
include:
- component: $CI_SERVER_FQDN/faintprint/pipeline/scan@7f17722aff486b22446effa85649453ba179a6de # 1.3.0
inputs:
scan: .vscode/mcp.json
Both lines are pinned to a commit, so your check changes only when you change the line; faintprint/scan-action@v1 and scan@1.3.0 are the shorter forms. Every input — scan, tools, fail-on, review, max-cost, allow-unfinished, version, reports, and on GitHub upload-sarif and sarif-category — is in the guide.