CLI

Commands, flags and exit codes.

shipped

Generated from the CLI's own description of itself (methodology 0.3-draft, CLI 0.3.0). Only flags the CLI accepts today are listed.

Commands

sealmark init

Write sealmark.yaml (and sealmark.local.yaml for secrets) from an MCP client's configuration, a one-line command, or a template. With --probes, start the server like doctor does and write one valid call per tool as a probes: block (never asks a question).

init [--ci <gitlab|github>] [--from "<command line>" | --from-url <url>] [--client <name>] [--server <name>] [--yes] [--force] [--config <path>] | init --probes [--write] [--json] [--yes] [--url <url>] [--start "<command line>"] [--config <path>]

sealmark run

Grade the configured server(s) and print the report.

run [--url <url>] [--start "<command line>"] [--header "Name: value"]... [--bearer <token>] [--start-timeout <sec>] [--name <name>] [--upstream] [--oauth] [--no-oauth-tls] [--scope-tool <tool=param>]... [--scope-allow <id>]... [--scope-deny <id>]... [--identity-a <token>] [--identity-b <token>] [--soft-fail] [--config <path>] [--profile <name>] [--pack <id>]... [--server <name|all>] [--fail-under A-F] [--format <fmt>] [--out <file>] [--active] [--no-sandbox] [--replay <report.json>]

sealmark doctor

Check that sealmark can start your server: tools on PATH, the launcher, the sandbox, initialize and tools/list, the classification and why each pack would run or not.

doctor [--url <url>] [--start "<command line>"] [--header "Name: value"]... [--bearer <token>] [--start-timeout <sec>] [--name <name>] [--upstream] [--oauth] [--no-oauth-tls] [--scope-tool <tool=param>]... [--scope-allow <id>]... [--scope-deny <id>]... [--identity-a <token>] [--identity-b <token>] [--config <path>] [--profile <name>] [--server <name>] [--json]

sealmark describe

Print the machine-readable description of this build: methodology and CLI versions, the check registry, configuration keys, commands, flags and exit codes.

describe --json

sealmark explain

Explain one check: what it measures, its probes, verdict rules, known false positives and negatives, the settings that affect it, and what to fix when it fails.

explain <check-id> [--json]

sealmark list-checks

List every check with its pack, severity, scope and whether its settings are locked by the standard profile.

list-checks [--pack <id>] [--json]

sealmark diff

Compare two JSON reports without running anything: verdict regressions and improvements, and tool-surface drift. Exit 1 when something regressed or drifted.

diff <before.json> <after.json> [--json]

sealmark config show

Print the merged configuration, with the origin of every value on request.

config show [--origins] [--json] [--config <path>] [--profile <name>] [--url <url>] [--start "<command line>"] [--header "Name: value"]... [--bearer <token>] [--start-timeout <sec>] [--name <name>] [--soft-fail] [--format <fmt>]...

sealmark config profiles

List shipped and user profiles with their difference from the standard profile.

config profiles [--json]

sealmark config migrate

Rewrite a configuration file from an older schema_version to the current one and print the diff.

config migrate [--config <path>]

Flags

FlagValueConfig keyCommandsMeaning
--config<file>flag onlyrun, doctor, init, config show, config migrateProject config file (default sealmark.yaml).
--profile<value>profilerun, doctor, config showProfile to start from.
--url<url>server.urlrun, doctor, init, config showURL of the MCP server to test (Streamable HTTP). With --start, Sealmark starts the server and waits for this URL to answer.
--start<command line>server.commandrun, doctor, init, config showCommand line that starts the server, for example --start "my-server --port 8000". With --url it is launched, awaited, tested and stopped; alone it is a stdio server. It is not run through a shell: no &&, |, ;, > or & and no VAR=value prefix (wrap it: --start "sh -c '...'").
--header"Name: value"server.headersrun, doctor, init, config showHTTP header for --url as "Name: value"; repeat for several. Put a CI secret in single quotes, --header 'Authorization: Bearer ${MCP_TOKEN}', and Sealmark reads it from the environment, so it never shows in the process list.
--bearer<token>server.headersrun, doctor, init, config showBearer token for --url: sets the header Authorization: Bearer <token>. Put a CI secret in single quotes, --bearer '${MCP_TOKEN}', and Sealmark reads it from the environment. Easier than --header in a CI file, because "Authorization: Bearer x" contains ": " and breaks a plain YAML value.
--start-timeout<sec>server.start_timeout_secrun, doctor, init, config showSeconds to wait for --start's server to answer on --url (default 60).
--name<name>namerun, doctor, init, config showDisplay name of the server in the report.
--soft-failswitchoutput.exit_policy.soft_failrun, config showA grade below the gate prints a warning and exits 0. Config errors, nothing graded and server failures still fail.
--pack<id>packsrun, list-checks, config showTest suite (pack) to run; repeat for several. --suite is the same flag.
--suite<id>packsrun, list-checks, config showTest suite to run; repeat for several. Same as --pack.
--timeout-probe<sec>timeout_probe_secrun, doctor, config showSeconds the cap and timeout probes wait.
--connect-timeout<sec>timeouts.connect_msrun, doctor, init, config showSeconds to wait for the server to start.
--fail-under<A-F>output.exit_policy.fail_underrun, config showExit 1 when a graded pack is below this letter. Default A: anything below an A fails. Use B, C, D or F to relax it, or none to turn the gate off.
--format<value>output.formatsrun, config showReport format; repeat the flag or comma-separate for several (text, json, markdown, junit, sarif, summary, badge, shields).
--out<file>output.outrun, config showWrite the report to this file.
--out-dir<dir>output.out_dirrun, config showDirectory for multi-format output.
--template<file>output.templaterun, config showReport template path.
--baseline<file>output.baselinerun, config showPrior JSON report to compare against.
--write-baseline<file>output.write_baselinerun, config showWrite the current JSON report for later --baseline runs.
--fail-on<value>output.fail_onrun, config showWith a baseline, what decides the exit code: any (the default), regressions or drift; repeat the flag or comma-separate for several.
--verbosity<value>output.verbosityrun, config showquiet, normal or verbose.
--redact
or --no-redact
switchoutput.redactrun, config show--no-redact prints secret-looking values (local debugging only).
--embedded
or --no-embedded
switchdb.embeddedrun, config show--no-embedded disables the embedded Postgres.
--pg<value>db.pgrun, config showSuperuser DSN of an existing Postgres (needs --yes).
--yesswitchyesrun, init, config showrun: confirm that --pg may recreate the seed database. init: take the defaults and never ask questions. (safety flag: only the command line sets it, never a file or an environment variable)
--activeswitchactiverun, config showAllow the exec pack and active custom probes. (safety flag: only the command line sets it, never a file or an environment variable)
--sandbox
or --no-sandbox
switchsandbox.enabledrun, doctor, init, config show--no-sandbox disables the sandbox (the run becomes CUSTOM).
--sandbox-dir<dir>sandbox.dirrun, doctor, init, config showParent directory for the sandbox.
--keep-sandboxswitchsandbox.keeprun, doctor, config showKeep the sandbox after the run.
--pack-timeout<sec>sandbox.pack_timeout_secrun, doctor, config showSeconds before one pack is abandoned.
--upstreamswitchupstream.kindrun, doctor, init, config showStart the harness's upstream stub (upstream.kind: http) and fill {upstream} in the server config with its URL, so the scope, sql-guard and upstream packs can see what the server forwards.
--scope-tool<value>scope.toolsrun, doctor, init, config showA tool that takes an identifier the server restricts, as tool=param (for example describe_table=table); repeat for several. Needs --scope-allow.
--scope-allow<value>scope.allowrun, doctor, init, config showAn identifier the server may serve; an entry ending with the separator is a prefix (sales.public.). Repeat for several.
--scope-deny<value>scope.denyrun, doctor, init, config showAn identifier the server must refuse even inside the allow list. Repeat for several.
--identity-a<value>identities.arun, doctor, init, config showToken for caller A ({bearer}, {bearer:a}) when the server validates tokens it did not issue. Put a CI secret in single quotes, --identity-a '${TOKEN_A}', and Sealmark reads it from the environment.
--identity-b<value>identities.brun, doctor, init, config showToken for caller B ({bearer:b}); must differ from --identity-a. Single-quote a CI secret as for --identity-a.
--oauthswitchoauth.issuerrun, doctor, init, config showStart the harness's stub OAuth issuer (oauth.issuer: stub): {issuer}, {jwks} and {audience} name it for the server's own settings, and the harness sends tokens it signed as Authorization: Bearer. The oauth pack then checks that forged, expired, wrong-audience and wrong-issuer tokens are refused.
--oauth-tls
or --no-oauth-tls
switchoauth.tlsrun, doctor, init, config show--no-oauth-tls serves the stub issuer over plain http instead of https (the default, with a throwaway CA in {ca_file}). Only for a server that cannot be given a CA file; a hardened server refuses an http trust root.
--server<value>run.serverrun, doctor, init, config showEntry of `servers` to use (run also accepts `all`). init: the imported servers to write, as comma separated names, client/name, or all.
--helpswitchflag onlyinit, run, doctor, describe, explain, list-checks, diff, config show, config profiles, config migratePrint usage and exit.
--versionswitchflag onlyPrint the sealmark version and exit.
--forceswitchflag onlyinit, config migrateOverwrite files that already exist.
--jsonswitchflag onlydoctor, describe, diff, explain, list-checks, config show, config profiles, initMachine-readable output.
--probesswitchflag onlyinitinit: start the server like doctor does and write one valid call per tool as a probes: block (stdout, or the config file with --write). Never prompts.
--writeswitchflag onlyinitinit --probes: append the block to the config file; exit 2 when it already has a probes: key.
--from<command line>flag onlyinitWrite a config from a one-liner, for example --from "npx -y @modelcontextprotocol/server-filesystem {sandbox}".
--ci<gitlab|github>flag onlyinitPrint a ready-to-paste CI job for this provider and write nothing.
--from-url<url>flag onlyinitWrite a config for an already running Streamable HTTP server.
--client<name>flag onlyinitOnly read this MCP client's configuration (claude-desktop, claude-code, cursor, vscode, copilot-cli).
--effectiveswitchflag onlyconfig showAccepted for compatibility; config show always prints the effective configuration.
--originsswitchflag onlyconfig showAnnotate every value with where it came from.
--replay<report.json>flag onlyrunRe-run with the locked settings of a previous JSON report, after verifying its config hash.
--fixture<fs|git|sqlite>flag onlyrunWrite a fixture the sandbox generates, for hand runs: the fs layout, the git repositories or the SQLite database (DuckDB is planned).
--into<dir>flag onlyrunDirectory for --fixture.

Exit codes

  1. 0ok
  2. 1a graded pack is below --fail-under (a provisional A* is below an A gate), an inconclusive pack under output.exit_policy.treat_inconclusive_as: fail, or a baseline comparison found a regression
  3. 2usage or configuration error
  4. 3no pack was graded
  5. 4harness or server failure (the server would not start, the database was unavailable, or doctor found a failing step)
  6. 129interrupted by SIGHUP (the terminal closed)
  7. 130interrupted by SIGINT (Ctrl-C)
  8. 143terminated by SIGTERM

What one run does#

sealmark run does the following, in order.

  1. Reads the flags and the configuration. A bad flag or an invalid config stops here with exit code 2.
  2. Starts the server once, inside a disposable sandbox (after writing any server.files into it), to read its tools and run the core pack. A server Sealmark does not launch (--url alone) or launches without a sandbox (--start with --url) is not sandboxed.
  3. Classifies each tool by what its arguments take (a path, a URL, a command, SQL, a repository, plain text). A tool it cannot classify gets the core checks only and is listed as not covered.
  4. Runs every other pack that applies, each against a target the harness owns, with fresh server sessions. The db and sqlite packs use a seeded synthetic database.
  5. Grades each pack, prints the report, and sets the exit code.

The exact checks, probes and thresholds are on the checks pages. The cleanup that follows every run is described under Safety.

Which packs run#

There are twelve built-in packs, plus your own probes:. Eight are in the standard profile. exec runs only with --active. scope, sql-guard and upstream are candidate packs, added when their declaration exists (guide). Each pack runs only when the harness can give the server a target it owns; otherwise it is reported as not applicable, with the reason in the second column.

PackNot applicable when
corenever; it runs on any server
dbthe config has no {dsn} (or {host}, {port}...) placeholder, or no tool takes SQL
sqliteno tool takes a SQL string, or the config has no {sqlite_path}
fsno tool is classified as taking a file path with medium confidence or higher
netno tool is classified as taking a URL with medium confidence or higher (a url parameter on a tool whose main class is path counts), or the server cannot reach the harness's loopback listener
gitno tool is classified as taking a git repository path with medium confidence or higher, or the config has no {sandbox} (a server given a real repository would reach every other repository legitimately, so the scope check could not be configured; pass {sandbox}/repo as the repository argument)
httpserver.url is not set: the server is started over stdio
effectsno tool both takes a harness-owned target and declares annotations
execthe --active flag is missing, or no tool is classified as running a command with high confidence
customno probes: with a check: are declared in the config, or the pack list was chosen with --pack or packs: and leaves custom out (the probes are skipped with a warning)

A server that is already running over HTTP cannot be started inside the sandbox, so fs, git, exec, effects and sqlite are not applicable for it. --no-sandbox has the same effect and makes the run CUSTOM. Naming fewer than the standard packs with --pack also makes the run CUSTOM.

Where output goes#

Progress lines and warnings go to standard error. When the console does not already show the grades (the report goes to a file with --out or --out-dir, including --format summary --out <file>, or the format on standard output is a machine one such as --format json), the grades are also printed to standard error, one line per server. The line names a critical fail whether or not it capped the grade, and a critical check at part as critical part, for example db F critical fail: mutating-sql. The formats and the files they produce are on Output.

The gate and the exit code#

The default gate is A in every profile. sealmark run exits 1 when any graded pack is below the gate. --fail-under takes a letter from A to F, in either case, or none to turn the gate off (output.exit_policy.fail_under: null in a file). A provisional A* is below an A gate, but a provisional B meets --fail-under B. The gate judges only packs that received a letter; a warning on standard error names an inconclusive pack it does not cover. --soft-fail keeps the gate but turns exit 1 into exit 0, with a soft-fail: warning. Before 0.2.0 there was no gate unless you asked for one.

With --baseline, --fail-on chooses what decides exit 1: any (the default: the gate and regressions), regressions (only regressions, so a server that is already below the gate can be held where it is) or drift (a breaking or notable change in the tool surface; see Catch tool-surface drift). They combine, for example --fail-on regressions,drift. regressions and drift need --baseline; without one the run exits 2.

CI is detected#

When CI=true (GitLab, GitHub Actions and most CI systems set it) and no profile is chosen, Sealmark uses the ci profile and says so in the log. The ci profile prints the summary format in the job log and writes JUnit and JSON files into ./sealmark-report/. The opt-outs are asking for something else: a profile (--profile standard, or profile: in a file or SEALMARK_PROFILE), or any of --out, --format or --out-dir. --fail-under and --soft-fail are not opt-outs, because they change the exit code and not the output.

sealmark init --ci gitlab (or github) prints a ready-to-paste CI job and writes nothing. With --start "<command>" and --url <url> Sealmark launches your server, waits until the URL answers, grades it and stops it, so a CI job needs no config file. See the CI guides.

Two flag notes#

  • --timeout-probe sets how long the cap and timeout probes wait (10 seconds by default). If your server's own timeout is longer than this value plus 8 seconds, set the flag to your timeout, or the db-timeout check will say "still running".
  • --fixture writes what the sandbox generates into a directory, for exploring by hand: sealmark run --fixture fs --into ./sealmark-fixtures writes the file layout the fs pack uses (with a SEALMARK-FIXTURE.txt that explains each path), git adds the three repositories (and needs git on the path), and sqlite writes the seeded database file. It refuses to write over an earlier fixture. See Sandbox.

Why an exit code happens#

The table above lists what each code means. These are the situations behind them, taken from the CLI's source.

Exit 0#

The run finished, at least one pack was graded, and none is below the gate. sealmark init that wrote its file, --help, and a run below the gate under --soft-fail also exit 0.

Exit 1#

A graded pack is below the gate, or an inconclusive pack counts as failing (output.exit_policy.treat_inconclusive_as: fail, which the ci profile sets), or a --baseline comparison found a regression, or, with --fail-on drift, the tool surface drifted. Standard error names each one, for example pack core graded B, below --fail-under A. The report is still written. With --fail-under none the exit code is not tied to the grades, and under treat_inconclusive_as: fail a note says that the policy does not affect the exit code while the gate is off. With --soft-fail this becomes exit 0; exits 2, 3 and 4 still fail.

Exit 2#

Something is wrong with how you invoked Sealmark or with its config. The message on standard error says which, with the file and line and a did-you-mean when a key is misspelled. Causes on the command line:

  • an unknown flag, an unknown command, no command, or an extra argument;
  • --format other than the eight formats, a --fail-on value other than any, regressions or drift, --fail-on regressions or drift without --baseline, --format badge or shields with several servers, --fixture duckdb or an unknown fixture, --fail-under other than A to F or none, an unknown --pack, or --timeout-probe that is not a positive number;
  • --pg without --yes, --pack exec without --active, or a --pg value that cannot be read as a URL;
  • init or config migrate when the file already exists and --force is missing;
  • --start and --url that come from different places.

A config the checks in How the file is checked reject exits 2 too, and so do server.files that writes outside the sandbox, uses a database placeholder, or is set for a server Sealmark does not start in the sandbox, and sql_tools that names a tool the server does not declare or an argument the tool does not have as a string.

Exit 3#

No pack was graded. Every pack was either not applicable or inconclusive, so there is no letter to judge. This code is decided before the gate, so a run that grades nothing can never pass it by accident. The reasons are in the report. See Troubleshooting.

Exit 4#

The harness or the server failed. Examples: the server command was not found, would not complete the handshake within the connect timeout, or its stderr shows a crash; a server started with --start exited or never answered on --url within --start-timeout; the embedded Postgres could not be loaded or started; the seed database could not be provisioned on the Postgres you named; doctor found a failing step. The message includes the last lines of the server's standard error when there are any.

Exit 129, 130 and 143#

On SIGHUP (129, a closed terminal), SIGINT (130, Ctrl-C) or SIGTERM (143) the harness cleans up and exits with 128 plus the signal number. It starts no further server after the signal, removes the sandbox, the databases and the embedded Postgres (also one that was still starting), and if a directory is still there it prints cleanup did not finish: <path> (remove it by hand) on standard error. These codes are set by the signal handler, not by the result.

Examples#

Each command below uses only flags the CLI accepts today.

bash
# write a starter sealmark.yaml, then edit "server" to point at your MCP server
npx sealmark init

# run every pack that applies; any pack below an A exits 1 (the default gate)
npx sealmark run

# relax the gate to a B, or switch it off and only read the report
npx sealmark run --fail-under B
npx sealmark run --fail-under none

# warn about a grade below the gate but exit 0 (errors still fail)
npx sealmark run --soft-fail

# one pack, JSON record to a file
npx sealmark run --pack core --format json --out sealmark.json

# no config file: launch a server, test it over HTTP, stop it
npx sealmark run --start "my-server --port 8000" --url http://localhost:8000/mcp

# a server that is already running, with a token read from the environment
npx sealmark run --url https://example.test/mcp --bearer '${MCP_TOKEN}'

# print a ready-to-paste CI job
npx sealmark init --ci gitlab

# keep a baseline, and fail on a regression or a changed tool surface
npx sealmark run --write-baseline sealmark-baseline.json
npx sealmark run --baseline sealmark-baseline.json --fail-on regressions,drift

# compare two reports without starting a server
npx sealmark diff before.json after.json

# a README badge for one server, and the layouts the sandbox generates
npx sealmark run --format badge --out-dir badge
npx sealmark run --fixture fs --into ./sealmark-fixtures

# your own throwaway Postgres instead of the embedded one
npx sealmark run --pg postgres://postgres@localhost:5432/postgres --yes