CLI
Commands, flags and exit codes.
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
| Flag | Value | Config key | Commands | Meaning |
|---|---|---|---|---|
--config | <file> | flag only | run, doctor, init, config show, config migrate | Project config file (default sealmark.yaml). |
--profile | <value> | profile | run, doctor, config show | Profile to start from. |
--url | <url> | server.url | run, doctor, init, config show | URL 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.command | run, doctor, init, config show | Command 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.headers | run, doctor, init, config show | HTTP 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.headers | run, doctor, init, config show | Bearer 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_sec | run, doctor, init, config show | Seconds to wait for --start's server to answer on --url (default 60). |
--name | <name> | name | run, doctor, init, config show | Display name of the server in the report. |
--soft-fail | switch | output.exit_policy.soft_fail | run, config show | A grade below the gate prints a warning and exits 0. Config errors, nothing graded and server failures still fail. |
--pack | <id> | packs | run, list-checks, config show | Test suite (pack) to run; repeat for several. --suite is the same flag. |
--suite | <id> | packs | run, list-checks, config show | Test suite to run; repeat for several. Same as --pack. |
--timeout-probe | <sec> | timeout_probe_sec | run, doctor, config show | Seconds the cap and timeout probes wait. |
--connect-timeout | <sec> | timeouts.connect_ms | run, doctor, init, config show | Seconds to wait for the server to start. |
--fail-under | <A-F> | output.exit_policy.fail_under | run, config show | Exit 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.formats | run, config show | Report format; repeat the flag or comma-separate for several (text, json, markdown, junit, sarif, summary, badge, shields). |
--out | <file> | output.out | run, config show | Write the report to this file. |
--out-dir | <dir> | output.out_dir | run, config show | Directory for multi-format output. |
--template | <file> | output.template | run, config show | Report template path. |
--baseline | <file> | output.baseline | run, config show | Prior JSON report to compare against. |
--write-baseline | <file> | output.write_baseline | run, config show | Write the current JSON report for later --baseline runs. |
--fail-on | <value> | output.fail_on | run, config show | With a baseline, what decides the exit code: any (the default), regressions or drift; repeat the flag or comma-separate for several. |
--verbosity | <value> | output.verbosity | run, config show | quiet, normal or verbose. |
--redactor --no-redact | switch | output.redact | run, config show | --no-redact prints secret-looking values (local debugging only). |
--embeddedor --no-embedded | switch | db.embedded | run, config show | --no-embedded disables the embedded Postgres. |
--pg | <value> | db.pg | run, config show | Superuser DSN of an existing Postgres (needs --yes). |
--yes | switch | yes | run, init, config show | run: 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) |
--active | switch | active | run, config show | Allow the exec pack and active custom probes. (safety flag: only the command line sets it, never a file or an environment variable) |
--sandboxor --no-sandbox | switch | sandbox.enabled | run, doctor, init, config show | --no-sandbox disables the sandbox (the run becomes CUSTOM). |
--sandbox-dir | <dir> | sandbox.dir | run, doctor, init, config show | Parent directory for the sandbox. |
--keep-sandbox | switch | sandbox.keep | run, doctor, config show | Keep the sandbox after the run. |
--pack-timeout | <sec> | sandbox.pack_timeout_sec | run, doctor, config show | Seconds before one pack is abandoned. |
--upstream | switch | upstream.kind | run, doctor, init, config show | Start 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.tools | run, doctor, init, config show | A 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.allow | run, doctor, init, config show | An identifier the server may serve; an entry ending with the separator is a prefix (sales.public.). Repeat for several. |
--scope-deny | <value> | scope.deny | run, doctor, init, config show | An identifier the server must refuse even inside the allow list. Repeat for several. |
--identity-a | <value> | identities.a | run, doctor, init, config show | Token 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.b | run, doctor, init, config show | Token for caller B ({bearer:b}); must differ from --identity-a. Single-quote a CI secret as for --identity-a. |
--oauth | switch | oauth.issuer | run, doctor, init, config show | Start 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-tlsor --no-oauth-tls | switch | oauth.tls | run, 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.server | run, doctor, init, config show | Entry of `servers` to use (run also accepts `all`). init: the imported servers to write, as comma separated names, client/name, or all. |
--help | switch | flag only | init, run, doctor, describe, explain, list-checks, diff, config show, config profiles, config migrate | Print usage and exit. |
--version | switch | flag only | Print the sealmark version and exit. | |
--force | switch | flag only | init, config migrate | Overwrite files that already exist. |
--json | switch | flag only | doctor, describe, diff, explain, list-checks, config show, config profiles, init | Machine-readable output. |
--probes | switch | flag only | init | init: 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. |
--write | switch | flag only | init | init --probes: append the block to the config file; exit 2 when it already has a probes: key. |
--from | <command line> | flag only | init | Write a config from a one-liner, for example --from "npx -y @modelcontextprotocol/server-filesystem {sandbox}". |
--ci | <gitlab|github> | flag only | init | Print a ready-to-paste CI job for this provider and write nothing. |
--from-url | <url> | flag only | init | Write a config for an already running Streamable HTTP server. |
--client | <name> | flag only | init | Only read this MCP client's configuration (claude-desktop, claude-code, cursor, vscode, copilot-cli). |
--effective | switch | flag only | config show | Accepted for compatibility; config show always prints the effective configuration. |
--origins | switch | flag only | config show | Annotate every value with where it came from. |
--replay | <report.json> | flag only | run | Re-run with the locked settings of a previous JSON report, after verifying its config hash. |
--fixture | <fs|git|sqlite> | flag only | run | Write a fixture the sandbox generates, for hand runs: the fs layout, the git repositories or the SQLite database (DuckDB is planned). |
--into | <dir> | flag only | run | Directory for --fixture. |
Exit codes
- 0ok
- 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
- 2usage or configuration error
- 3no pack was graded
- 4harness or server failure (the server would not start, the database was unavailable, or doctor found a failing step)
- 129interrupted by SIGHUP (the terminal closed)
- 130interrupted by SIGINT (Ctrl-C)
- 143terminated by SIGTERM
What one run does#
sealmark run does the following, in order.
- Reads the flags and the configuration. A bad flag or an invalid config stops here with exit code 2.
- Starts the server once, inside a disposable sandbox (after writing any
server.filesinto it), to read its tools and run thecorepack. A server Sealmark does not launch (--urlalone) or launches without a sandbox (--startwith--url) is not sandboxed. - 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
corechecks only and is listed as not covered. - Runs every other pack that applies, each against a target the harness owns, with fresh server sessions. The
dbandsqlitepacks use a seeded synthetic database. - 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.
| Pack | Not applicable when |
|---|---|
core | never; it runs on any server |
db | the config has no {dsn} (or {host}, {port}...) placeholder, or no tool takes SQL |
sqlite | no tool takes a SQL string, or the config has no {sqlite_path} |
fs | no tool is classified as taking a file path with medium confidence or higher |
net | no 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 |
git | no 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) |
http | server.url is not set: the server is started over stdio |
effects | no tool both takes a harness-owned target and declares annotations |
exec | the --active flag is missing, or no tool is classified as running a command with high confidence |
| custom | no 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-probesets 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 thedb-timeoutcheck will say "still running".--fixturewrites what the sandbox generates into a directory, for exploring by hand:sealmark run --fixture fs --into ./sealmark-fixtureswrites the file layout thefspack uses (with aSEALMARK-FIXTURE.txtthat explains each path),gitadds the three repositories (and needsgiton the path), andsqlitewrites 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;
--formatother than the eight formats, a--fail-onvalue other thanany,regressionsordrift,--fail-on regressionsordriftwithout--baseline,--format badgeorshieldswith several servers,--fixture duckdbor an unknown fixture,--fail-underother than A to F ornone, an unknown--pack, or--timeout-probethat is not a positive number;--pgwithout--yes,--pack execwithout--active, or a--pgvalue that cannot be read as a URL;initorconfig migratewhen the file already exists and--forceis missing;--startand--urlthat 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.
# 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