Introducing actlint

MCP tool annotations are self-declared. actlint makes them checkable.

·
Share
On this page

Before an AI agent runs a tool, one small label decides whether a human gets asked first — and the tool writes that label about itself.

So the tools most worth stopping are exactly the ones free to call themselves harmless. Nothing in the protocol checks the claim: the client trusts it, the registry republishes it, and the person wiring the server in has no tool for the job.

Today we're releasing the missing tool. actlint is a static action-risk linter for MCP servers, and the first open-source project from Formael: github.com/formael/actlint.

In one line: actlint reads the tools a server advertises, works out for itself what each one actually does, checks that against what the tool claims about itself, and reports where the two disagree — as a scorecard, a JSON report, and a CI pass/fail signal.

Point it at any server you can launch or reach — no install, no account, about sixty seconds:

check a server before you wire it into an agent
sh
# a server you're about to add to an agent — check it first
npx actlint --http https://your-mcp-server.example.com
 
# or a tool manifest you captured earlier
npx actlint --manifest manifest.json --fail-on critical

Or wire it into CI with the GitHub Action:

.github/workflows/actlint.yml
yaml
- uses: formael/actlint/packages/github-action@<commit-sha>
  with:
    args: --http https://your-mcp-server.example.com --fail-on medium

The problem, precisely

Every MCP tool describes itself: a name, a description, an inputSchema, and — optionally — a few advisory annotations. readOnlyHint. destructiveHint. idempotentHint. openWorldHint. A well-behaved client reads those to decide when to pause and ask a human before it calls the tool. Declare a tool read-only, and the client reasonably stops asking.

That makes the hints load-bearing. A tool that writes files can still declare itself read-only. A tool that deletes records can omit its destructive hint entirely. Nothing in the protocol verifies either claim — and when the label is wrong, the cost isn't a wrong sentence in a chat. It's the tool quietly running without the one confirmation that would have caught it.

actlint is the checker. A linter in the lineage of eslint, tflint, and hadolint — it reads what a server declares and reports the problems. It never calls a tool, never blocks a request, never sits in the path of a live action.

The label that decides whether a human is asked is written by the very thing the human might need to stop.

Why it matters

If you ship an MCP server, actlint in CI keeps your declared annotations honest as your tools evolve — the way a schema linter keeps an API spec honest. Over-declaring risk is cheap to fix; under-declaring is the one your users find for you.

If you connect servers you didn't write, it's a sixty-second pre-flight check and a paper trail for security review. Attach the captured manifest to the report, and anyone can re-run the exact same numbers.

If you care about the ecosystem, checkable labels compound. Client prompts, registry listings, and procurement reviews all lean on the same annotations. A shared, repeatable way to verify them raises the floor for everyone building on top.

What it does

actlint runs a four-step pipeline, and the order is the whole point — it grades a claim only after forming its own opinion, so it can never be talked into agreeing with a tool.

  1. Capture

    Point actlint at a live server once. It reads the tool list and normalizes it into a manifest. This is the only step that touches a network or launches a process.

  2. Derive, blind

    The scoring engine works out each tool's risk profile from its name, schema, and description alone — without ever seeing the tool's own annotations.

  3. Compare

    The declared annotations enter only now, checked against the profile actlint formed independently.

  4. Report

    Every gap comes with a plain-English reason and a mapping to the standards it relates to — OWASP ASI, the OWASP MCP Top 10, CoSAI/OASIS, the EU AI Act, and NIST — through a published crosswalk, not a private taxonomy.

The result is a scorecard with a single honesty grade at the top and every gap listed below it:

a clean server earns its grade
txt
actlint  ▸  stdio server                         honesty grade: A
────────────────────────────────────────────────────────────────
14 tools scanned · 0 under-declared · 0 undeclared · 0 over-declared

The three kinds of gap are named in plain order of concern. Under-declared is the dangerous one — the tool claims less risk than it has, which is exactly the case that suppresses a confirmation prompt. Undeclared means the tool said nothing at all. Over-declared is harmless excess caution. A grade of A isn't leniency; it means the tool's declarations matched what actlint derived on its own, not that actlint failed to look.

Why you can trust the output

The derivation step is blind on purpose — it can't grade a label it has already seen. By construction, the tool's own claims never reach the scoring engine until the independent read is finished.

Only one part of actlint ever touches the network — the initial fetch you asked for. Everything after that is sealed off from the outside world by design. That's what lets a second run reproduce the first exactly: same server in, same findings out, character for character. Hand someone the captured manifest and they can re-derive your grade themselves.

When the evidence is thin, actlint says uncertain. It lowers the severity, never hides the finding, and never fails a build on a guess. Unknown inputs resolve toward caution — never to "safe."

3

report formats

human scorecard · JSON · SARIF for code scanning

0

actions taken

it reads and reports — it never calls a tool

1

network call

the fetch you asked for, and nothing else

Using the CLI

actlint can point at a live stdio server, a remote HTTP server, or a manifest captured earlier — and report as a human scorecard, JSON, or SARIF. A few patterns come up in real use.

Capture once, analyze many. Talking to a live server is the only step that touches the network. Capture the manifest once, then run every analysis offline — reproducible, and safe in a CI job with no network access at all:

capture, then analyze offline
sh
actlint --capture fs.json npx -y @modelcontextprotocol/server-filesystem ./sandbox
actlint --manifest fs.json                        # human scorecard
actlint --manifest fs.json --json  -o report.json # versioned JSON report
actlint --manifest fs.json --sarif -o fs.sarif    # GitHub code scanning

Gate a build. The exit code is the gate: 1 on a finding at or above --fail-on, 2 on a usage error, 3 if the server couldn't be reached.

fail the build only above medium severity
sh
actlint --manifest fs.json --fail-on medium

The GitHub Action is a thin wrapper around the same CLI — pin it to a commit SHA and the same exit code fails the job:

.github/workflows/actlint.yml
yaml
- uses: formael/actlint/packages/github-action@<commit-sha>
  with:
    args: --http https://your-mcp-server.example.com --fail-on medium

Adopt on a noisy server. Record today's findings as an accepted baseline, then gate only on what's new — no red build on day one:

baseline, then gate only new findings
sh
actlint --manifest fs.json --write-baseline fs.baseline.json
actlint --manifest fs.json --baseline fs.baseline.json --fail-on low

Understand a finding. explain is offline and takes a rule id straight from any report — the reasoning, an example, the standards mapping, and the fix:

see why a rule fired
sh
actlint explain write-as-readonly

Where it's heading

actlint is v0.1, and the version number will move only when the work behind it does. The direction is steady rather than fast. The risk vocabulary grows more discerning as it meets more real servers. The mapping to OWASP ASI, the MCP Top 10, and the other standards follows their 2026 revisions. The judgment keeps settling until its shape is stable enough to depend on. A 1.0 will mark the point where the report schema, rule ids, and exit codes stop moving — reached, not scheduled. No calendar sets the pace; the work does.

Scope, honestly

actlint reads declarations. It doesn't watch runtime behavior, catch prompt injection, or analyze privileges — those belong to other tools, and a server that lies artfully in its own descriptions can still fool a static read. It measures and reports; it never enforces.

It's v0.1: the vocabulary will grow, and some findings will be wrong. When one is, explain shows exactly which judgment fired and why — file an issue and it sharpens the vocabulary for everyone downstream. We're not taking outside code yet while the v0.x foundations settle, but a disputed finding is the contribution we want most right now.

One line on why Formael is publishing this: we build the layer that governs agent actions at the moment they run, and actlint is deliberately not that layer. It measures whether labels are honest, and it stays free, open source, and standalone whether or not you ever talk to us. An ecosystem where the labels can be checked is one we want to exist.

A tool that grades other software's self-descriptions should expect the same scrutiny, so actlint ships with its own: signed builds, a published bill of materials, and verifiable provenance — Apache-2.0, on npm, no account, no telemetry.

Key takeaways

  • MCP tool annotations are self-declared and unverified by the protocol — actlint is the static checker that reports where a tool's declared and actual behavior disagree.
  • It forms its own opinion before it reads the label it's grading, runs the same twice, and resolves thin evidence to caution — never to "safe."
  • Start with npx actlint <target> — stdio, HTTP, or a captured manifest; then wire in the GitHub Action and gate CI with --fail-on and a baseline.

The repo is github.com/formael/actlint. Point it at a server you run or depend on, and tell us what it gets wrong.

Field notes on the agentic enterprise.

No noise. A few times a month, when there's something worth saying.

Early Access

Build the AI agents
you actually want to ship.

Join the Formael early access program. Get the boundary your agent fleet needs - governed execution, an append-only audit trail, and a neutral control plane you own - before you have to build it yourself.

Guided onboarding included
Custom policy configuration
Dedicated integration support