mcp-secret-sentinel
Detects Discord webhooks in code to prevent exposure.
Detects GitHub tokens (classic and fine-grained) in code to prevent exposure.
Detects Google API keys in code to prevent exposure.
Detects MongoDB connection strings with embedded credentials in code to prevent exposure.
Detects MySQL connection strings with embedded credentials in code to prevent exposure.
Detects NVIDIA API keys in code to prevent exposure.
Detects OpenAI API keys in code to prevent exposure.
Detects Redis connection strings with embedded credentials in code to prevent exposure.
Detects Slack tokens and incoming webhooks in code to prevent exposure.
Detects Stripe live keys in code to prevent exposure.
Detects Twilio API keys in code to prevent exposure.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-secret-sentinelscan staged changes in this repository"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-secret-sentinel
MCP server and CLI that scans code for exposed secrets — API keys, tokens, private keys and high-entropy strings — with placeholder-aware allowlisting and redacted reports.
Secrets rarely leak through hackers; they leak through commits. An agent (or a human in a hurry) pastes a webhook URL into a config, stages it, pushes — and from that moment the credential is compromised, even if the next commit deletes it, because history keeps every added line. mcp-secret-sentinel gives an agent a pre-commit checkpoint: scan a snippet, a file, a whole tree, the staged diff, the commits you are about to push, or recent history, and get back a severity-ranked, fully redacted report it can act on before anything leaves the machine. The full secret value never appears in the tool output, so it never enters the conversation transcript either.
Tools
Tool | Arguments | Returns |
|
| Findings for a raw snippet (code, config, diff, logs) |
|
| Findings for one file; decodes UTF-8/16/32 by BOM, skips binaries (null-byte heuristic) and files over 5 MB |
|
| Recursive scan; skips |
|
| Scans only the lines added in |
|
| Scans the commits in |
|
| Scans lines added by the last N commits (of HEAD, or of every branch, tag and the stash), tagging each finding with its commit hash |
| — | Active detectors with severity and remediation advice, plus the allowlist rules |
Every tool is annotated read-only (readOnlyHint, no open-world access), so MCP clients can run it without a confirmation prompt.
All scan tools return the same shape:
{
"clean": false,
"findings": [
{
"file": "config/notify.yaml",
"line": 14,
"pattern": "Slack incoming webhook",
"severity": "high",
"redacted": "hook…(77 chars)",
"advice": "Anyone with this URL can post messages to your workspace. Regenerate the webhook in your Slack app settings and load the URL from an environment variable."
}
],
"files_scanned": 6,
"summary": "Found 1 potential secret(s) across 6 scanned file(s): 1 high. Do NOT commit or push until these are removed or rotated."
}suppressed is added when inline comments silenced findings (see below).
Bounded responses. A noisy tree used to return every finding. 3,000 hits made a 1 MB tool result that swamped the agent's context. Every scan tool now lists at most max_findings findings (default 200, most severe first, 0 = no limit). When it has to cut, the report says so and keeps the totals:
{
"clean": false,
"findings": ["…the 200 most severe…"],
"truncated": true,
"total_findings": 3001,
"counts_by_severity": {"critical": 1, "high": 3000},
"counts_by_pattern": {"Generic secret assignment": 3000, "Stripe live key": 1},
"top_files": [{"file": "fixtures/users.py", "findings": 3001}],
"summary": "Found 3001 potential secret(s) … [listing the 200 most severe of 3001; see counts_by_severity, counts_by_pattern and top_files, or raise max_findings]"
}What it detects
Thirty-three regex detectors:
Family | Detectors |
Code hosting & packages | GitHub tokens ( |
AI providers | OpenAI (legacy |
Cloud | AWS access key IDs ( |
SaaS | Stripe live keys, Shopify tokens, SendGrid keys, Twilio keys and account SIDs, Slack bot/user/app/refresh tokens and incoming webhooks, Discord webhooks, Telegram bot tokens |
Keys & tokens | Private key blocks (RSA / EC / DSA / OPENSSH / PGP / encrypted), |
Credentials in URLs | Database and queue connection strings (Postgres, MySQL/MariaDB, MongoDB, AMQP, Redis) and |
Generic |
|
On top of the regexes, a Shannon-entropy detector flags quoted strings of 20+ characters assigned to variables whose empirical entropy reaches 4.5 bits/char — the signature of random credential material — but only when no specific pattern already claimed that span.
For credentials in URLs the reported value is the password, and the allowlist judges the password alone. A templated or look-alike host can no longer hide a literal password: postgres://app:<literal>@${DB_HOST}/app and mysql://root:<literal>@db.examplecorp.net/prod are both reported.
What it deliberately ignores (allowlist)
Each candidate value is checked against these placeholder heuristics before being reported:
Too short — values under 8 characters are too short to be real credentials.
Masked — values that are mostly (≥ 80%)
X,x,*, or dots: already redacted by a human, including vendor prefixes followed by anXXXX…run.example— any value containingexample(any case), such as vendor-documented sample keys like the AWS docs key ending inEXAMPLE.placeholder,changeme(alsochange-me/change_me) — conventional fill-me-in markers.your-…-here — fill-in-the-blank markers.
<angle brackets>— documentation-style placeholders.${TEMPLATE_VARIABLES}— the secret is injected elsewhere, not stored here.Template expressions —
{{ vault_db_password }}(Jinja, Ansible, Helm, Go templates),{% … %},<%= … %>(ERB/EJS) and#{…}(Ruby).Variable references — a value that is only
$UPPER_CASE_VAR,$(command), PowerShell$env:VARor cmd%VAR%.$ecretP4ssis still a password.Format placeholders — a value that is only
%(name)sor{name}.Environment lookups — values referencing
os.environorprocess.env: an environment lookup is the fix, not the leak.Code, for generic assignments only —
API_SECRET = os.getenv("API_SECRET"),DB_PASSWORD = settings.DATABASE_PASSWORD,API_TOKEN = SECRETS[0]or one side of"password='" + pwd + "'". The rule is narrow on purpose ("Xk9(pq!2Lm"is still a password) and never applies to vendor formats such as JWTs, which contain dots.Reserved documentation hosts — a credentialed URL pointing at
example.com/.net/.org,*.exampleor*.invalid.
Inline suppression
A deliberate fixture or a known-safe line can be silenced where it lives, without renaming variables:
TEST_DSN = "postgres://ci:ci-only-password@db:5432/test" # secret-sentinel: ignoredetect-secrets' marker pragma: allowlist secret works too, so files already annotated for that tool need no changes. Markers are case-insensitive and cover only their own line, and they also apply to staged and committed lines. Suppressed findings are not hidden silently: the result carries "suppressed": <count> and says so in the summary.
Encodings and line numbers
Files are decoded by byte-order mark (UTF-8, UTF-16 LE/BE, UTF-32) before the binary check, and BOM-less UTF-16 is recognised too. UTF-16 is what Windows PowerShell 5.1's > and Out-File write, so those files are now scanned instead of being skipped as binaries. Lines are split on \n only (a trailing \r is dropped). Form feeds, vertical tabs, \x85, U+2028 and U+2029 inside a line no longer shift the reported line numbers.
Redaction guarantee
Every finding shows at most the first 4 characters, and never more than a quarter of the value, plus the total length: "hook…(77 chars)", and "Tr…(8 chars)" for an 8-character password (0.1.0 showed half of it). The full value never appears in the output, the transcript, or the logs. This is enforced in code (a single redact() choke point) and in the test suite, which asserts the raw values are absent from serialized results, including over real MCP stdio.
Git scans read the text, not your diff settings
The git tools parse git diff / git log -p output, and local git settings used to be able to change that output: an external diff tool (diff.external, difftastic and friends) replaced the diff entirely, so staged secrets were reported as "no staged changes" while the external program ran, and diff.mnemonicPrefix, diff.noprefix or quoted non-ASCII paths corrupted the reported file names. Every git call now:
passes
--no-ext-diff --no-textconv --src-prefix=a/ --dst-prefix=b/ -U0 --inter-hunk-context=0 --no-color;overrides
core.quotePath,core.fsmonitor,diff.noprefix,diff.mnemonicPrefix,diff.relative,diff.submodule,log.showSignatureandlog.showRootwith-c;drops
GIT_EXTERNAL_DIFF/GIT_DIFF_OPTSfrom the environment, setsGIT_OPTIONAL_LOCKS=0, and closes stdin;rejects revisions that start with
-, so an agent-suppliedbasecan never become a git option.
The diff parser is a state machine driven by the hunk line counts, so an added line that reads +++ something is content, never a file header. Binary files are named in the summary instead of being skipped silently.
Related MCP server: re-leak-scan
How it works
flowchart TD
A[Agent calls a scan tool] --> B{Source}
B -->|scan_text / scan_file| C[Decode by BOM, split on newline only]
B -->|scan_directory| D[Walk tree, skip .git, node_modules,<br/>virtualenvs, caches, binaries,<br/>lockfiles, .gitignore matches] --> C
B -->|scan_git_staged / scan_git_range /<br/>scan_git_history| E[git diff / git log -p, zero context,<br/>diff config overridden, no external programs] --> P[Hunk-counting parser:<br/>added lines, real paths, commit tags] --> C
C --> S{Inline marker?<br/>secret-sentinel: ignore /<br/>pragma: allowlist secret}
S -->|yes| U[Counted in suppressed]
S -->|no| F[33 regex detectors,<br/>specific patterns claim spans first]
S -->|no| G[Shannon entropy >= 4.5 bits/char<br/>on assigned strings of 20+ chars]
F --> H{Allowlist check on the value:<br/>placeholders, templates, variables,<br/>masked values, env lookups}
G --> H
H -->|placeholder| I[Dropped, not reported]
H -->|real candidate| J[Redact: at most 4 chars and<br/>a quarter of the value, + length]
J --> K[Report: file, line, pattern,<br/>severity, redacted, advice]Quickstart
The package is not on PyPI yet (the publish workflow runs when a v* tag is
pushed), so install it straight from GitHub. It runs on Python 3.10+ with
either major of the official MCP SDK (mcp 1.7+ or 2.x).
Claude Desktop (claude_desktop_config.json), with uvx fetching it from git:
{
"mcpServers": {
"secret-sentinel": {
"command": "uvx",
"args": [
"--from",
"git+https://github.com/AleBrito124356/mcp-secret-sentinel",
"mcp-secret-sentinel"
]
}
}
}Claude Code:
claude mcp add secret-sentinel -- uvx --from git+https://github.com/AleBrito124356/mcp-secret-sentinel mcp-secret-sentinelPermanent install with pip (no uv needed):
pip install "git+https://github.com/AleBrito124356/mcp-secret-sentinel"Then use mcp-secret-sentinel as the command in any MCP client config.
Command line, git hook and CI
The same scanner works without an MCP client. mcp-secret-sentinel with no arguments still starts the stdio server, so existing client configs are unchanged. With a command it becomes a CLI:
mcp-secret-sentinel scan # the current directory
mcp-secret-sentinel scan src config/app.yaml # files and directories
git diff | mcp-secret-sentinel scan - # stdin
mcp-secret-sentinel scan --staged # what the next commit would add
mcp-secret-sentinel scan --range # what the next push would publish (@{upstream}..HEAD)
mcp-secret-sentinel scan --range origin/main..HEAD
mcp-secret-sentinel scan --history 100 --all-branches
mcp-secret-sentinel scan --format sarif -o secrets.sarif
mcp-secret-sentinel patterns # detectors, allowlist rules, suppression markers$ mcp-secret-sentinel scan src
src/billing.py:2 [critical] Stripe live key sk_l…(32 chars)
Roll the key in the Stripe Dashboard (Developers -> API keys). Live keys grant access to real payment data.
src/notify.py:3 [high] Slack incoming webhook hook…(69 chars)
Anyone with this URL can post messages to your workspace. Regenerate the webhook in your Slack app settings and load the URL from an environment variable.
Found 2 potential secret(s) across 2 scanned file(s): 1 critical, 1 high. Do NOT commit or push until these are removed or rotated.Option | Meaning |
| Human report (default), the MCP JSON report, or SARIF 2.1.0 |
| Lowest severity that makes the exit status 1 (default |
| List at most N findings, most severe first (default 200; |
| Stop a directory scan after N files (default 5000) |
| With |
| Write the report to a file; the one-line summary goes to stderr |
Exit status: 0 clean (or only findings below --fail-on), 1 findings at or above --fail-on, 2 usage error (bad option, missing path, not a git repository, unknown revision). Paths in the report are relative to the working directory, which is what editors and SARIF viewers expect. With --staged, --range and --history they are relative to the repository root. Output is redacted in every format.
Block commits with a git hook
mcp-secret-sentinel install-hook # in the repository, or: install-hook path/to/repoThis writes .git/hooks/pre-commit, or the directory core.hooksPath points at. The hook runs scan --staged with the Python that installed it, so a commit is refused while its staged lines contain a secret, and git commit --no-verify skips it once. The installer never overwrites a hook written by another tool unless you pass --force, and then it keeps the old hook as pre-commit.bak. --fail-on high makes the hook ignore medium findings.
With the pre-commit framework, add this to .pre-commit-config.yaml:
repos:
- repo: https://github.com/AleBrito124356/mcp-secret-sentinel
rev: main # pin a commit SHA (or a v* tag once one is published)
hooks:
- id: secret-sentinelGitHub code scanning
--format sarif produces a SARIF 2.1.0 log (checked against the official JSON schema). It has one rule per detector with security-severity, a redacted message, a startLine per result and a stable fingerprint, so findings show up as code scanning alerts. A workflow step for your own repository:
- run: pip install "git+https://github.com/AleBrito124356/mcp-secret-sentinel"
- run: mcp-secret-sentinel scan --format sarif -o secret-sentinel.sarif --fail-on critical
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: secret-sentinel.sarifExample session
User: I'm about to push acme-app — make sure I'm not leaking anything.
Claude calls
scan_git_staged(repo_path="C:/dev/acme-app"):{ "clean": false, "findings": [ { "file": "config/notify.yaml", "line": 14, "pattern": "Slack incoming webhook", "severity": "high", "redacted": "hook…(77 chars)", "advice": "Anyone with this URL can post messages to your workspace. Regenerate the webhook in your Slack app settings and load the URL from an environment variable." } ], "files_scanned": 6, "summary": "Found 1 potential secret(s) across 6 scanned file(s): 1 high. Do NOT commit or push until these are removed or rotated. Unstage the affected files and strip the secrets before committing." }Claude: Hold the push —
config/notify.yamlline 14 contains a real Slack webhook URL in the staged diff. Three steps:
Replace the literal URL with an environment lookup, e.g.
SLACK_WEBHOOK_URL.Regenerate the webhook in your Slack app settings — treat the current one as compromised.
Re-stage the file and I'll verify.
User: Done — check again.
Claude calls
scan_git_staged(...)→"clean": true, "summary": "Staged changes are clean — 6 file(s) with additions scanned, no secrets in the added lines."— commits, then callsscan_git_range(repo_path="C:/dev/acme-app")→"clean": true, "commits_scanned": 3, "range": "@{upstream}..HEAD".Claude: Clean — the staged changes and the 3 commits your push would publish contain no detectable secrets. Safe to push.
Limitations
.gitignoresupport is best-effort: plain names,*.extglobs,dir/and/anchoredpatterns from the root.gitignoreonly. No!negations, no**globs, no nested ignore files.Entropy needs diversity: empirical per-string entropy maxes out at log2(distinct characters), so a candidate needs at least 23 distinct characters to clear 4.5 bits/char. Short random strings are covered by the regex detectors instead.
Unquoted generic assignments are only detected in dotenv-style
UPPER_CASE=valuelines — a deliberate trade against false positives in ordinary code.Line-by-line matching: a secret split across lines (a PEM body, a string concatenated over several lines) is only caught by its first line, such as the
BEGIN PRIVATE KEYheader.Inline suppression is same-line only: a marker on the line above does nothing, because in a zero-context diff that line is usually not visible.
Merge commits are not diffed (git's default for
git log -p), so a secret introduced only while resolving a merge conflict is missed byscan_git_rangeandscan_git_history.scan_git_stagedstill sees it before the merge commit is made.Binary files in git are not scanned. That includes files that
.gitattributesmarks as binary or-diff; the summary lists them by name.Not a CI replacement: dedicated scanners (gitleaks, trufflehog) with hundreds of rules belong in your pipeline. This server is the fast local checkpoint an agent can run before the commit exists.
Development
git clone https://github.com/AleBrito124356/mcp-secret-sentinel
cd mcp-secret-sentinel
pip install -e ".[dev]"
python -m pytestFrom the source tree, python -m mcp_secret_sentinel runs the CLI (the MCP server with no arguments), and python -m mcp_secret_sentinel.server always runs the server.
The test suite never contains a realistic secret literal: every positive fixture is assembled at runtime by concatenation. A test scans this repository and requires it to be clean. The suite is organised as follows:
tests/test_core.pyandtests/test_detection.pycover every detector, the allowlist and placeholder syntaxes, entropy, redaction, encodings, line numbering, directory walking, inline suppression and a linear-time bound for long lines.tests/test_git.pyuses real temporary repositories and a local bare remote. It checks diff configuration overrides (a marker file provesdiff.externaland textconv programs never run), path quoting, hunk parsing,--alland range scans.tests/test_cli.pyruns the CLI as a subprocess: exit codes,--fail-on, JSON and SARIF, stdin, legacy console encodings, andinstall-hookblocking a realgit commit.tests/test_server.pyspawns the real server over stdio and drives it with the SDK's ownClientSession, so it proves the wiring on whichever SDK major is installed.
To check the other SDK major too:
python -m venv .venv-mcp1
.venv-mcp1/bin/pip install "mcp<2" -e ".[dev]" # Windows: .venv-mcp1\Scripts\pip
.venv-mcp1/bin/python -m pytestRelated MCP servers
Part of a family of small, dependency-light MCP servers:
mcp-decision-lab — weighted decision matrices with sensitivity analysis
mcp-devils-advocate — stress-test a claim: devil's advocate, premortem, assumption audits
mcp-git-historian — churn hotspots, blame summaries, bus factor
mcp-memory-vault — persistent memory with SQLite FTS5 search
License
MIT
Available Tools
7 toolslist_patternsARead-onlyIdempotent
List the active secret detectors and allowlist rules.
Returns: {"count": int, "patterns": [{"name", "severity", "advice"}], "entropy_detector": {...threshold and advice...}, "allowlist_rules": [{"name", "description"}]} Raw regexes are intentionally not exposed.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so safety is covered by structured data. The description adds genuinely useful behavioral context beyond that: the return shape and the explicit note that raw regexes are intentionally withheld, which prevents an agent from hunting for them.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One sentence of purpose followed by a compact return-shape block; nothing is padded. The purpose is front-loaded and the structured detail follows it logically.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema present, the description carries the return-value burden and does so concretely (count, patterns with name/severity/advice, entropy_detector, allowlist_rules). It also closes the obvious follow-up question about regex exposure, so an agent has everything needed to call and interpret this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, which is the baseline-4 case. There is nothing to disambiguate and the description correctly implies a no-argument listing call.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb and resource ('List the active secret detectors and allowlist rules'). This is clearly distinct from the sibling scan_* tools, which perform scanning rather than config introspection, so an agent can route correctly without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The purpose implies this is for discovering what detectors exist, but the description never states when to prefer it (e.g., before a scan to know coverage) or names any alternative. Usage is inferable but not spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scan_directoryARead-onlyIdempotent
Recursively scan a directory tree for exposed secrets.
Automatically skips .git, node_modules, virtualenvs and conda envs under any name, site-packages, tool caches (.tox, .nox, .mypy_cache, .pytest_cache, .ruff_cache), pycache, dist, build, minified JS bundles, lockfiles, binaries, files over 5 MB, and simple patterns from the root .gitignore (best-effort: no negations, no ** globs).
Args: path: Absolute path to the directory to scan. max_files: Stop after scanning this many files (default 500). max_findings: List at most this many findings, most severe first (default 200, 0 = no limit). When capped, the result adds truncated, total_findings, counts_by_severity, counts_by_pattern and top_files.
Returns: The standard redacted findings report; finding paths are relative to the scanned root, using forward slashes.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| max_files | No | ||
| max_findings | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (readOnly, idempotent, non-destructive, closed-world), yet the description adds substantial behavioral context beyond them: the exact skip list, the 5 MB file cap, best-effort .gitignore handling with its limitations, and truncation semantics when max_findings is hit.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the purpose, then structured Args/Returns sections. The skip list is long but each entry is informative; the only mild bloat is enumerating cache dirs that could be summarized.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, and the description compensates by describing the return shape (redacted findings report, root-relative forward-slash paths) and the truncation metadata. All three parameters and their edge cases are covered.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the burden and does so fully: path is documented as an absolute path, max_files and max_findings carry defaults and precise semantics ('0 = no limit'), plus the derived truncation fields emitted when capped.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource+scope: 'Recursively scan a directory tree for exposed secrets.' An agent can tell it apart from scan_file/scan_text/scan_git_* by inference from 'directory tree', but no sibling is named explicitly, so it stops short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the resource ('scan a directory'), but there is no explicit when-to-use statement, no prerequisites, and no guidance on choosing this over scan_file or scan_text when a whole tree is not needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scan_fileARead-onlyIdempotent
Scan a single file for exposed secrets.
UTF-8, UTF-16 and UTF-32 text is decoded by byte-order mark. Binary files (null-byte heuristic) and files larger than 5 MB are skipped and reported as such in the summary.
Args: path: Absolute path to the file to scan. max_findings: List at most this many findings, most severe first (default 200, 0 = no limit). When capped, the result adds truncated, total_findings, counts_by_severity, counts_by_pattern and top_files.
Returns: The standard redacted findings report (see scan_text). Returns an error result if the file does not exist.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| max_findings | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, the description discloses important runtime behavior: encoding handling via byte-order marks, skipping binary and >5 MB files with reporting, and max_findings truncation metadata. It also states the error result if the file does not exist, which is valuable behavioral context not covered by annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core action, then structured into Args and Returns sections. Each part adds useful information for this two-parameter scanning tool, and there is no redundant filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the lack of an output schema and the tool's scoped complexity, the description is complete: it explains input semantics, file-handling limits, truncation behavior, and the return format by reference to scan_text. An agent has enough information to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must carry the burden, and it does: path is documented as an absolute file path, and max_findings explains the default, the unlimited value, ordering, and truncation-summary fields. This fully compensates for the empty schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Scan a single file for exposed secrets.' It clearly distinguishes file scanning from directory, git, and text-scanning siblings through the 'single file' scope, so an agent can identify the tool's role without inspecting the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The 'single file' phrasing implies the appropriate context, but the description does not explicitly say when to use this tool instead of scan_text, scan_directory, or the git-specific scanners. Usage is therefore present but left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scan_git_historyARead-onlyIdempotent
Scan the lines added by the most recent commits (git log -p).
Each finding is tagged with the short hash of the commit that introduced it. A secret that was later deleted is still reported — history retains it, so the credential must be rotated regardless.
Args: repo_path: Absolute path to a git repository (or any path inside one). max_commits: How many commits back to inspect (default 50). all_branches: Walk every branch, tag and the stash instead of only the history of HEAD (default false). max_findings: List at most this many findings, most severe first (default 200, 0 = no limit). When capped, the result adds truncated, total_findings, counts_by_severity, counts_by_pattern and top_files.
Returns: The standard redacted findings report, with a "commit" field on each finding.
| Name | Required | Description | Default |
|---|---|---|---|
| repo_path | Yes | ||
| max_commits | No | ||
| all_branches | No | ||
| max_findings | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only/idempotent/non-destructive, yet the description adds genuinely new behavior: deleted secrets are still reported because history retains them, and it discloses the truncation contract and the extra fields (truncated, total_findings, counts_by_severity, etc.) returned when capped.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded purpose sentence followed by two high-value behavioral notes and structured Args/Returns. Every line adds meaning; nothing is padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the Returns section (redacted findings report with a commit field plus truncation fields) fully describes the response, and all four parameters are explained. An agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the full burden and does so well: repo_path accepts any path inside a repo, max_commits default 50, all_branches walks every branch/tag/stash vs HEAD only, max_findings default 200 with 0 = no limit.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb+resource+method: 'Scan the lines added by the most recent commits (git log -p)'. It distinguishes itself from scan_git_staged and scan_git_range by scoping to HEAD's recent history, and the commit-tagging rule clarifies what is being read.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied ('most recent commits') and the all_branches flag hints at scope, but with close siblings (scan_git_staged, scan_git_range) the description never states when to choose this tool over them or any exclusions. No explicit routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scan_git_rangeARead-onlyIdempotent
Scan the commits in base..head: by default, what git push would send.
Run this before pushing. With the defaults it scans every commit on the current branch that its upstream does not have yet. If the branch has no upstream, the error says so: pass the branch you will push to as base (for example "origin/main").
Args: repo_path: Absolute path to a git repository (or any path inside one). base: Commits reachable from here are excluded (default "@{upstream}"). head: Last commit to include (default "HEAD"). max_commits: Scan at most this many of the newest commits in the range. max_findings: List at most this many findings, most severe first (default 200, 0 = no limit). When capped, the result adds truncated, total_findings, counts_by_severity, counts_by_pattern and top_files.
Returns: The standard redacted findings report with a "commit" field on each finding, plus "commits_scanned" and "range".
| Name | Required | Description | Default |
|---|---|---|---|
| base | No | @{upstream} | |
| head | No | HEAD | |
| repo_path | Yes | ||
| max_commits | No | ||
| max_findings | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already confirm it is a safe, read-only, idempotent operation, and the description adds substantial context: the default upstream range, the error message when no upstream exists, how to work around it, truncation behavior for max_findings (which adds truncated, total_findings, etc.), and the return shape.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core purpose, then uses labeled sections for guidance, arguments, and returns. Every sentence adds actionable detail without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the Returns section describes the report fields; annotations cover safety; all parameters are documented; and error behavior is explained. Nothing needed to invoke the tool correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must carry the full burden. Its Args section documents all five parameters with defaults, semantics, and edge cases (e.g., max_commits limits newest commits; max_findings 0 means no limit), fully compensating for the schema gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Scan the commits in base..head') and clarifies the default range as 'what git push would send', which implicitly distinguishes it from scan_git_history and scan_git_staged by scope. However, it never names a sibling alternative, so it falls short of the explicit differentiation a 5 requires.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides a clear when-to-use directive ('Run this before pushing') and explains default behavior plus error recovery. It does not state when not to use this tool or compare it to alternatives, so it lacks explicit exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scan_git_stagedARead-onlyIdempotent
Scan ONLY the lines currently staged for commit (git diff --cached).
This is the pre-commit checkpoint: it inspects exactly the content the next commit would publish, and reports the file and post-commit line number of every added secret. Local diff settings (external diff tools, textconv filters, prefixes, path quoting) are overridden, and git runs no configured programs.
Args: repo_path: Absolute path to a git repository (or any path inside one). max_findings: List at most this many findings, most severe first (default 200, 0 = no limit). When capped, the result adds truncated, total_findings, counts_by_severity, counts_by_pattern and top_files.
Returns: The standard redacted findings report. If nothing is staged the result is clean with an explanatory summary.
| Name | Required | Description | Default |
|---|---|---|---|
| repo_path | Yes | ||
| max_findings | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive. The description adds meaningful behavior beyond that: local diff settings are overridden, git runs no configured programs, and reported line numbers are post-commit. It does not mention auth or rate limits, but the extra operational detail is valuable.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the essential scoping sentence, then uses compact Args/Returns structure. Well-sized for a tool with two params and truncation behavior, though the Returns paragraph is slightly padding-adjacent.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description still explains the return shape (standard redacted findings report, clean-with-summary when nothing is staged, truncation fields when capped) plus both parameters. An agent has everything needed to call and interpret it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must carry both params, and it does: repo_path is 'absolute path to a git repository (or any path inside one)' and max_findings documents the default 200, the 0 = no limit sentinel, and the truncation metadata emitted when capped. This fully compensates for the schema gap.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Scan) and resource (lines currently staged for commit / git diff --cached), with an explicit scope constraint ('ONLY the lines currently staged'). The pre-commit framing clearly distinguishes it from scan_git_history and scan_git_range.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Names the precise moment to use it ('the pre-commit checkpoint: it inspects exactly the content the next commit would publish'), giving clear context. It does not explicitly contrast against the sibling git-scanning tools (history/range), so it stops short of full when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scan_textARead-onlyIdempotent
Scan a snippet of text or code for exposed secrets.
33 detectors: GitHub/GitLab/npm/PyPI tokens, OpenAI (incl. sk-proj-), Anthropic, OpenRouter, Groq, NVIDIA and Hugging Face keys, AWS, Azure, Google, DigitalOcean, Stripe, Shopify, SendGrid, Twilio, Slack, Discord and Telegram credentials, JWTs, private and age keys, credentialed connection strings and URLs, generic password/secret/token assignments, plus high-entropy strings. Placeholders ($VAR, ${VAR}, {{ templates }}, %(name)s, changeme, masked values...) are skipped, and a line carrying "secret-sentinel: ignore" or "pragma: allowlist secret" is counted in "suppressed" instead of reported.
Args: text: The raw text to scan (code, config, diff output, logs...). source_name: Label used in each finding's "file" field. max_findings: List at most this many findings, most severe first (default 200, 0 = no limit). When capped, the result adds truncated, total_findings, counts_by_severity, counts_by_pattern and top_files.
Returns: {"clean": bool, "findings": [{"file", "line", "pattern", "severity", "redacted", "advice"}], "files_scanned": int, "summary": str} Secret values are ALWAYS redacted (at most 4 chars, never more than a quarter of the value, + length); the full value is never included.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | ||
| source_name | No | input | |
| max_findings | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare the safe-read profile (readOnly, idempotent, non-destructive); the description adds substantial behavior beyond them: placeholder/variable forms are skipped, 'secret-sentinel: ignore' and 'pragma: allowlist secret' lines land in 'suppressed' rather than findings, and secret values are always redacted to at most 4 chars. It also discloses truncation semantics (truncated flag, total_findings, counts_by_severity).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the one-line purpose, then structured Args/Returns sections; every clause is informative rather than filler. The 33-detector enumeration is long, but it defines detection coverage an agent would otherwise have to guess, so it mostly earns its space.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, and the description compensates by specifying the return shape (clean, findings with file/line/pattern/severity/redacted/advice, files_scanned, summary) plus the redaction guarantee. Suppression, truncation and parameter behavior are all covered, so nothing needed to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the full burden and does: text is 'raw text to scan (code, config, diff output, logs...)', source_name is the label populating each finding's 'file' field, and max_findings explains the default 200, that 0 means no limit, ordering is most-severe-first, and which extra fields appear when capped.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('scan a snippet of text or code for exposed secrets') and immediately scopes the input class. Against siblings scan_file, scan_directory and scan_git_staged, the in-memory text-snippet scope makes the boundary clear without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: the tool takes raw text, so the agent can infer it is for content already in hand rather than a path. It never states when to prefer scan_file/scan_directory over this one, nor any exclusions or prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
7 tool updates
v0.2.0- First observed
list_patterns - First observed
scan_directory - First observed
scan_file - First observed
scan_git_history - First observed
scan_git_range - First observed
scan_git_staged - First observed
scan_text
TDQS
Scored across 7 tools
Each tool has a clearly distinct input target: raw text, single file, directory tree, git staged diff, git history, and a git range. The three git tools (staged/history/range) could superficially overlap, but their descriptions crisply define different scopes (pre-commit checkpoint, past commits, pre-push range), and list_patterns is clearly the only non-scanning tool.
All seven tools follow a strict verb_noun convention (list_patterns, scan_text, scan_file, scan_directory, scan_git_staged, scan_git_history, scan_git_range). The scan_git_* family is a predictable, self-describing sub-pattern that makes the set easy to navigate.
Seven tools is well-scoped for a secret scanner: one introspection tool plus six scan variants covering distinct sources, with no redundant or filler entries. Each tool earns its place by addressing a different context (editor text, file, tree, pre-commit, history, pre-push).
The surface covers the full scanning lifecycle (ad-hoc text, files, directories, staged changes, history, push range) plus detector introspection, which is strong. The only gap is that allowlist/detector configuration is read-only (list_patterns) with no way to add or modify rules via tools, though this is plausibly handled by external config files.
Maintenance
Related MCP Connectors
Security scanner for MCP servers. Detect vulnerabilities, prompt injection, and tool poisoning.
Scan any public GitHub MCP-server repo for security issues. 37 MCP-specific L1 rules, 8 languages.
MCP server for detecting and redacting PII (Personally Identifiable Information) in PDF documents.
Scan any MCP server for tool-poisoning, security, auth & license. Trust score before install.
Related MCP Servers
- AlicenseAqualityNot gradedmaintenanceEnterprise-grade MCP (Model Context Protocol) server for detecting secrets and sensitive information in GitHub repositories. Scans for 35+ types of secrets including API keys, passwords, tokens, and credentials with production-ready reliability features.50-
- AlicenseAqualityCmaintenanceMCP server for detecting publisher telemetry pipeline leaks in binary artifacts by scanning for sensitive strings like Sentry DSNs, Slack tokens, and AWS keys.6MIT
- AlicenseAqualityCmaintenanceMCP server for developer workflow automation — smart commits, secret scanning, PR descriptions, and more.5MIT
- AlicenseAqualityAmaintenanceAn MCP server for autonomous AI agents to scan and detect hardcoded secrets, API keys, and passwords in source code files.1MIT