使用说明
TrustMCP: MCP Security Scanner for JavaScript and TypeScript
TrustMCP is an open-source static security scanner for JavaScript and TypeScript Model Context Protocol (MCP) servers. It detects risky shell execution, secret exposure, filesystem access, internal network access, and exfiltration chains before you run unknown MCP server code or wire it into CI.
Use TrustMCP from the CLI or GitHub Actions to review third-party MCP servers, gate your own repository, and export evidence as JSON, Markdown, or SARIF. It scans source code without executing the target.
Quick start
Requires Node.js 18.18+.
Scan a public MCP server without a global install:
npx trustmcp https://github.com/modelcontextprotocol/servers --format text
Or install the CLI globally and scan a local repository:
npm install -g trustmcp
trustmcp ./path-to-mcp-server --format text
The latest stable release is 0.2.1 on npm. To evaluate the repository tip directly:
git clone https://github.com/Gujiassh/trustmcp.git
cd trustmcp
npm install
npm run build
node dist/cli/main.js gh:modelcontextprotocol/servers --format text
Documentation: rules · installation · demo · GitHub Action examples · release history
Why scan MCP servers
MCP servers are getting easier to discover and easier to wire into local tools. Trust review is still mostly manual, so TrustMCP focuses on a believable first step: scan the code, point to concrete evidence, and explain why it matters.
TrustMCP intentionally stays small:
- one local CLI
- one reusable GitHub Action
- static heuristics only
- public GitHub repo root URLs or local folders
- thirteen evidence-backed rules
It does not claim a target is safe.
Common use cases
- Review a third-party MCP server before local use
- Gate your own MCP server repository in CI
- Export JSON, Markdown, or SARIF for automation, artifacts, and code scanning
What TrustMCP scans
For the short answer plus scope and non-goals, check out What TrustMCP scans, and how it differs from npm audit.
For a rule-by-rule explainer, check out TrustMCP rules explained.
mcp/shell-execmcp/outbound-fetchmcp/broad-filesystemmcp/archive-extractmcp/download-write-execmcp/dynamic-code-execmcp/env-secret-exposuremcp/internal-network-accessmcp/subprocess-network-exfilmcp/tool-metadata-riskmcp/script-runner-execmcp/sensitive-local-datamcp/local-service-binding
For the stable JSON, baseline, and GitHub Action machine-readable contract, see TrustMCP machine-readable output contract. For the recommended single-repository baseline and CI rollout path, see TrustMCP project-level policy adoption.
Some repositories may legitimately trigger both mcp/broad-filesystem and mcp/sensitive-local-data on the same file path. TrustMCP currently keeps both findings because they answer different trust-review questions: broad host-path reach versus direct access to credential/secret-bearing locations.
Some repositories may also trigger mcp/internal-network-access alongside mcp/outbound-fetch when a request targets localhost, private network ranges, link-local metadata services, .local, or .internal hosts. TrustMCP keeps those findings separate because a generic outbound request and a request into local or internal infrastructure answer different trust-review questions.
Some repositories may also trigger mcp/local-service-binding when they open listeners or bind ports from MCP-controlled flows. That rule is intentionally separate from fetch and execution rules because it answers a different trust question: whether the repository expands its reachable surface by starting a local or publicly bound service.
If you want the current IDs from the CLI, run node dist/cli/main.js list-rules.
For automation-friendly rule metadata, run node dist/cli/main.js list-rules --json.
The JSON form now includes each rule's default severity plus its possible confidenceLevels, stable confidenceReasons, and optional confidenceGuidance entries with machine-readable descriptions.
If you want to persist the shipped rule metadata to a file, run:
node dist/cli/main.js list-rules --json --output-file rules.json
If you want copy-paste automation paths, for example in shell scripts:
node dist/cli/main.js doctor gh:modelcontextprotocol/servers --json | jq '.ok'
node dist/cli/main.js list-rules --json | jq -r '.[] | "\(.id)\t\(.severity)"'
node dist/cli/main.js list-rules --json | jq -r '.[] | select(.id == "mcp/outbound-fetch") | .confidenceReasons[]'
Use list-rules --json for shipped rule metadata: rule IDs, default severities, possible confidence levels, stable confidence reason codes, and guidance descriptions. Use scan report JSON (--format json) for per-run findings, target metadata, baseline state, and summary counts.
Every finding includes:
ruleIdseverityconfidenceconfidenceReasontitlefilelinewhen availableevidencewhyItMattersremediation
Every report also includes a coverage block. It distinguishes a complete no-match from a scan where relevant source files were skipped by the documented file policy.
CLI usage
Run against a local folder
node dist/cli/main.js ./fixtures/local-risky
Run against a public GitHub repository
node dist/cli/main.js https://github.com/modelcontextprotocol/servers --format text
Or use explicit GitHub shorthand:
node dist/cli/main.js gh:modelcontextprotocol/servers --format text
If you need a reproducible pinned GitHub scan instead of the current default-branch head, pass an explicit ref:
node dist/cli/main.js gh:modelcontextprotocol/servers@main --format text
node dist/cli/main.js "https://github.com/modelcontextprotocol/servers?ref=main" --format text
GitHub scans accept repository root inputs only: either https://github.com/owner/repo or gh:owner/repo, optionally with an explicit ref selector (?ref= for full URLs or @ref for gh: shorthand). Trailing slashes, .git, and repo-root query fragments are normalized for full URLs. Copied tree/..., blob/..., and other GitHub subpath URLs are rejected with a hint to use the repository root URL instead. Invalid shorthand fails with gh:<owner>/<repo> guidance rather than falling through to local-path handling.
Source files are bounded to 256,000 bytes each. Relevant oversized files, binary-content files, and symbolic links are recorded in report coverage instead of disappearing silently. TrustMCP intentionally ignores .git, dependency/build output, coverage output, and common test/fixture directories. Public GitHub ZIP inputs are also checked before extraction: at most 20,000 entries, 25,000,000 uncompressed bytes per entry, and 250,000,000 total uncompressed bytes.
Output formats
Emit JSON for CI or other tooling:
node dist/cli/main.js ./fixtures/local-risky --format json
Emit compact markdown for a PR comment or job summary:
node dist/cli/main.js gh:modelcontextprotocol/servers --format markdown
Emit SARIF for code-scanning or security workflow ingestion:
node dist/cli/main.js gh:modelcontextprotocol/servers --format sarif --output-file trustmcp.sarif
Write the selected format to a file while still printing it to stdout:
node dist/cli/main.js gh:modelcontextprotocol/servers --format markdown --output-file trustmcp-report.md
Emit only the compact summary for terminal or CI status checks:
node dist/cli/main.js gh:modelcontextprotocol/servers --summary-only --fail-on high
Config file
Reuse stable CLI defaults from an explicit JSON config file:
{
"format": "markdown",
"fail-on": "high",
"summary-only": true,
"output-file": "reports/trustmcp.md",
"baseline-file": "trustmcp.baseline.json"
}
node dist/cli/main.js gh:modelcontextprotocol/servers --config trustmcp.config.json
Generate a starter config file without overwriting an existing one:
node dist/cli/main.js init-config
Validate a target and optional config before your first real scan:
node dist/cli/main.js doctor gh:modelcontextprotocol/servers --config trustmcp.config.json
Emit that preflight result as JSON for automation:
node dist/cli/main.js doctor gh:modelcontextprotocol/servers --json
When you pass --config, doctor also checks config-loaded output-file paths and catches missing parent directories before a real scan starts.
Configuration file options
trustmcp.config.json is a flat JSON object that mirrors every CLI flag that can be set with --config (and the two new helper fields described below). Supported fields are:
format: one oftext,json,markdown, orsarif.fail-on: a severity (low,medium,high) for the CLI exit code.summary-only: a boolean that limits console output to the compact summary block.output-file: a file path where the rendered report is written in addition to stdout.ignore-rules: an array of exact rule IDs (mcp/shell-exec,mcp/outbound-fetch, etc.). Any finding whoseruleIdmatches one of the entries is dropped from the final report, but the heuristic still runs internally, so only use this to silence known noise that you have inspected.ignore-paths: an array of slash-separated relative paths (files or directories) inside the audited target. If a finding’sfilepath starts with one of the configured strings, that finding is omitted from the CLI summary output and structured report. There is no globbing or regex support; entries are matched literally and are case-sensitive.baseline-file: a JSON file containing previously accepted findings. Preferred entries includefingerprint,ruleId,file, and optionalline; older tuple-only entries remain readable for compatibility. When provided, TrustMCP still reports the full finding list for visibility, but--fail-ononly evaluates findings that are not present in the baseline.baseline-output: a file path where TrustMCP writes the current finding identities as reusable baseline JSON. This is the explicit “accept current findings” path for laterbaseline-fileruns.
Both ignore-rules and ignore-paths are best reserved for short-lived noise gating when you already trust the other findings and you are confident that the ignored rows represent accepted risk. They do not change the rule evaluation itself; they only suppress matching findings from the emitted summary and report output.
baseline-file solves a different adoption problem: it lets an existing repository keep historical findings visible while failing CI only on newly introduced findings. Baseline matching is fingerprint-first, with compatibility fallback for older ruleId + file + optional line entries. There is no globbing, regex matching, or automatic baseline update flow in this first slice.
If you want to create that baseline file from a real scan instead of writing it by hand, run:
node dist/cli/main.js ./fixtures/local-risky --baseline-output trustmcp.baseline.json
It also flags invalid config combinations early, such as summary-only: true with format: sarif.
Doctor also verifies that the current Node.js runtime satisfies TrustMCP's supported engine floor.
If something fails before the scan starts, check TrustMCP troubleshooting.
List the currently shipped TrustMCP rules:
node dist/cli/main.js list-rules
Source the shipped shell completions directly for repeated local use:
source completions/trustmcp.sh
source completions/trustmcp.zsh
JSON reports include summary.severityCounts.low, summary.severityCounts.medium, and summary.severityCounts.high so CI consumers can read stable severity totals without re-counting the finding list.
CI exit codes
Fail a CI job when a finding meets or exceeds a severity threshold:
node dist/cli/main.js https://github.com/modelcontextprotocol/servers --format json --fail-on high
--fail-on low fails on any finding, --fail-on medium fails on medium or high findings, and --fail-on high fails on high findings only. Threshold matches exit with code 2; TrustMCP runtime or argument errors exit with code 1.
Use TrustMCP in GitHub Actions
TrustMCP now ships a reusable composite action at the repository root. For copy-pasteable workflows, start from ./.github/examples/trustmcp-gate.yml for the checked-out workspace case, ./.github/examples/trustmcp-public-target.yml for an explicit public GitHub target, ./.github/examples/trustmcp-artifact.yml to retain a rendered markdown report, ./.github/examples/trustmcp-pr-comment.yml to keep a sticky PR comment updated with the current markdown report, ./.github/examples/trustmcp-json-artifact.yml for a retained JSON report, ./.github/examples/trustmcp-sarif-artifact.yml for a retained SARIF file, or ./.github/examples/trustmcp-upload-sarif.yml for the GitHub code-scanning upload path.
If you are choosing between those examples:
- use
trustmcp-gate.ymlwhen you only need CI pass/fail behavior - use
trustmcp-baseline-gate.ymlwhen historical findings should stay visible but only new findings should fail CI - use
trustmcp-artifact.ymlortrustmcp-json-artifact.ymlwhen later workflow steps need a saved report - use
trustmcp-pr-comment.ymlwhen reviewers should see the markdown report directly on the PR - use
trustmcp-sarif-artifact.ymlortrustmcp-upload-sarif.ymlwhen a security workflow or code-scanning consumer expects SARIF
Minimal external usage looks like this:
jobs:
trustmcp:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: trustmcp
uses: Gujiassh/trustmcp@v0.2.1
with:
target: ${{ github.workspace }}
format: json
fail-on: high
- run: |
echo "findings=${{ steps.trustmcp.outputs.finding-count }}"
echo "rules=${{ steps.trustmcp.outputs.rule-count }}"
echo "high=${{ steps.trustmcp.outputs.high-count }}"
echo "new_findings=${{ steps.trustmcp.outputs.new-finding-count }}"
echo "new_rules=${{ steps.trustmcp.outputs.new-rule-count }}"
echo "baseline_applied=${{ steps.trustmcp.outputs.baseline-applied }}"
echo "summary=${{ steps.trustmcp.outputs.summary-message }}"
The action builds TrustMCP from its own source tree on each run and then scans the checked-out target path or public GitHub URL you pass in. Relative local target paths are resolved from ${{ github.workspace }}. It does not rely on a published npm package or marketplace wrapper. The official examples pin the supported @v0.2.1 release; if you need immutable supply-chain pinning, replace the release tag with its commit SHA.
The reusable action exposes finding-count, rule-count, low-count, medium-count, and high-count outputs for total visible findings, plus new-finding-count, new-rule-count, new-low-count, new-medium-count, and new-high-count for the baseline-filtered "new findings" subset when you use baseline gating. It also emits gated-finding-count, gated-rule-count, gated-low-count, gated-medium-count, and gated-high-count for the exact set currently used for policy failure, baseline-applied so later steps can tell whether baseline gating was active, coverage-complete, scanned-file-count, and skipped-file-count so workflows can detect incomplete scans, and summary-message so lightweight jobs can reuse the exact one-line summary without reparsing JSON output.
SARIF output now also carries the stable TrustMCP finding fingerprint in each result's properties.fingerprint and SARIF-native partialFingerprints.primaryLocationLineHash, plus TrustMCP policy-state flags such as properties.baselineApplied, properties.isNewFinding, and properties.isGatedFinding, so code-scanning consumers can align SARIF results with JSON reports and baseline entries more reliably.
When GITHUB_STEP_SUMMARY is available, the reusable action also appends the compact TrustMCP markdown report there automatically for easier job review.
To reuse CLI defaults inside GitHub Actions, pass the new config-file input pointing at your trustmcp.config.json. The action resolves the config file path itself from ${{ github.workspace }} before building, then resolves config-owned relative paths such as baseline-file and baseline-output from that config file's directory, just like the CLI. Dedicated summary-only, baseline-file, and baseline-output inputs still let the workflow override those values explicitly; those explicit action inputs remain workspace-relative. The action enforces the same summary-only + format: sarif restriction as the CLI, so shared configs behave identically between local and CI runs.
If you want the recommended baseline-first CI rollout pattern, start from TrustMCP project-level policy adoption and the example workflow at .github/examples/trustmcp-baseline-gate.yml.
If a later workflow step needs a concrete report file, set output-file, for example output-file: reports/trustmcp.md. Relative paths are resolved from the checked-out workspace, and the parent directory must already exist.
If you want the markdown report to stay visible directly in PR review without downloading artifacts, start from .github/examples/trustmcp-pr-comment.yml. That example writes reports/trustmcp.md and then upserts a sticky pull-request comment instead of creating a new comment on every run.
Real scan examples
At pinned ref eed21856dcf0defa23394909e27125311fed246f, TrustMCP reported the following on microsoft/playwright-mcp:
Target: microsoft/playwright-mcp
Ref: main@eed21856dcf0defa23394909e27125311fed246f
Summary: 4 finding(s) across 1 rule(s). Static heuristics only.
[HIGH][HIGH] Shell execution capability detected
Rule: mcp/shell-exec
Location: packages/playwright-mcp/update-readme.js:149
Evidence: execSync('node cli.js --help > help.txt');
This is a point-in-time heuristic capability match, not a blanket judgment of the project. In this case, TrustMCP matched shell execution in repository scripts and maintenance paths, which is exactly the kind of capability surface this tool is meant to surface before deeper review.
For a balanced no-match example, at pinned ref e874a073288981779e14a489b7f9b10f4b576b3c, TrustMCP reported the following on okooo5km/memory-mcp-server:
Target: okooo5km/memory-mcp-server
Ref: main@e874a073288981779e14a489b7f9b10f4b576b3c
Summary: No matching rules were triggered. Static heuristics only; this does not mean the target is safe.
That is also a point-in-time result, not a blanket safety judgment. It means the current TrustMCP rules did not match shell execution, outbound fetch, or broad filesystem patterns at that pinned commit.
Run the included checks:
npm test
npm run build
npm run smoke
npm run reference:check
npm run reference:scan
npm run release:check
npm run release:check:strict
Example output
TrustMCP v0.2.1
Target: /absolute/path/to/local-risky
Source: local-directory
Summary: 3 finding(s) across 3 rule(s). Static heuristics only.
Coverage: complete (15 file(s) scanned)
[HIGH][HIGH] Shell execution capability detected
Rule: mcp/shell-exec
Location: src/shell.ts:4
Evidence: exec(args.command);
Why it matters: Shell execution can turn tool input into arbitrary host commands.
Remediation: Prefer fixed command allowlists, avoid shell string interpolation, and require explicit operator approval for host command execution.
When no rules match, TrustMCP says:
No matching rules were triggered.
That message is intentionally narrow. It is not a safety verdict.
Limitations and non-goals
TrustMCP is honest about scope:
- static heuristics only
- JavaScript and TypeScript repositories only
- no runtime sandboxing
- no private repo support
- GitHub input is bounded by explicit compressed download and uncompressed archive budgets
- GitHub input supports repository root URLs only; tree/blob/subpath URLs are rejected instead of being scanned ambiguously
- no auth flows
- no web UI, registry, or hosted service
- no guarantee that a target is safe or complete coverage of all MCP risks
The current TrustMCP surface is designed for a fast first pass and practical CI/review use, not a full security review.
Project status
- The latest npm release is
trustmcp@0.2.1. - The matching GitHub release and reusable Action tag are
v0.2.1. - Source installation remains supported for contributors and repository-tip evaluation.
- GitHub Actions CI runs on Node.js 18, 20, 22, and 24.
- Future GitHub releases and npm publications remain manual maintainer actions.
The npm package, GitHub release, reusable Action tag, source, CLI, package metadata, tests, and release notes agree on 0.2.1.
Maintainer release checks
npm run pack:checkvalidates tarball contents.npm run pack:smokeinstalls a local tarball, executes its npm binary, and verifies version and rule-list output.npm run publish:checkruns the packaging-oriented release preflight.npm run release:checkruns the local scanner and packaging gate.npm run release:check:strictalso requires live reference-target scans.
Use the release gate chooser, npm publish checklist, and CONTRIBUTING.md for maintainer release work.
Contribution path
The easiest way to contribute is to add a fixture, add or tighten a rule, and keep the output contract stable. Check out CONTRIBUTING.md for the local workflow.
Security reporting
If you find a bug in TrustMCP itself, follow SECURITY.md.
