Security scanner for Model Context Protocol (MCP) servers. Detects prompt injection, tool poisoning, capability escalation, and rug-pull attacks.
Project description
MCP Security Scanner
Security scanner for Model Context Protocol (MCP) servers.
It analyzes server capabilities, detects policy and runtime risks, and exports findings as json, html, or sarif.
Why This Project
- Secure MCP integrations before production rollout.
- Detect static misconfiguration and capability risk early.
- Compare baseline snapshots to catch risky tool mutations.
- Optionally run bounded dynamic probes with
--dynamic.
Architecture
flowchart LR
A["Target Input (server/config/baseline/compare)"] --> B["Connector Discovery (stdio/sse/streamable-http)"]
B --> C["Tool + Resource Snapshot"]
C --> D["Static Analyzer Pipeline"]
D --> E["Report Builder (json/html/sarif)"]
C --> F["Baseline Engine (hash + mutation diff)"]
A --> G["Optional Auth Resolver (config + URL JSON auth/mTLS)"]
G --> B
A --> H["Optional Dynamic Probes (--dynamic)"]
H --> E
F --> E
Capability Snapshot (Sprint 1-9V)
| Area | Status |
|---|---|
| Transports | stdio, sse, streamable-http |
| Commands | server, config, baseline, compare, cache rotate |
| Default analyzers | Static, PromptInjection, Escalation, ToolPoisoning, CrossTool |
| Dynamic mode | Opt-in (--dynamic), bounded and deterministic |
| OAuth auth types | oauth_client_credentials, oauth_device_code, oauth_auth_code_pkce |
| Token endpoint auth methods | client_secret_post, client_secret_basic, private_key_jwt |
| Persistent cache backends | local, aws_secrets_manager, aws_ssm_parameter_store, gcp_secret_manager, azure_key_vault, hashicorp_vault, kubernetes_secrets, oci_vault, doppler_secrets, onepassword_connect, bitwarden_secrets, infisical_secrets, akeyless_secrets, gitlab_variables, gitlab_group_variables, gitlab_instance_variables, github_actions_variables, github_environment_variables, github_organization_variables, consul_kv, redis_kv, cloudflare_kv, etcd_kv, postgres_kv, mysql_kv, mongo_kv, dynamodb_kv, s3_object_kv, sqlite_kv |
| Release pipeline | OIDC publish + Sigstore + idempotent GitHub release + build-wheel CLI smoke + tag/version consistency guard (pyproject/__version__/wheel/CLI) + PyPI visibility verification |
| mTLS | OAuth token-endpoint mTLS + transport discovery mTLS |
| Compare contract | only tool_added, tool_removed, tool_changed mapped to LLM05 |
Current Scope Details
configsupports auth/session flow v1 for network transports (bearer,api_key,session_cookie,oauth_client_credentials,oauth_device_code,oauth_auth_code_pkce)- Optional persistent OAuth cache hardening (strict lock, corruption recovery, metadata key management, multi-key recovery)
- OAuth provider hardening+ (tolerant token parsing and transient retry policy for token endpoints)
- OAuth provider integrations v2 in
configauth:token_endpoint_auth_method=private_key_jwtsupports env/file/AWS KMS signing sources - Release stabilization (Sprint 8D): PyPI distribution name switched to
ogulcanaydogan-mcp-security-scannerto avoid name collision - Release hardening (Sprint 8J): publish workflow uses idempotent
gh releasecreate/upload path and tag-scoped publish concurrency guard - OAuth cache provider expansion (Sprint 8K): added
aws_ssm_parameter_storebackend (pre-provisioned SecureString parameter model) - OAuth cache provider expansion (Sprint 8L): added
kubernetes_secretsbackend (in-cluster auth + kubeconfig fallback, pre-provisioned Secret model) - OAuth cache provider expansion (Sprint 8M): added
oci_vaultbackend (resource principal first, OCI config fallback, pre-provisioned secret model) - Release + contract hardening (Sprint 8N): pre-publish tag/version guard, post-publish PyPI visibility retry check, and shared OAuth cache backend invariant tests
- OAuth cache provider expansion (Sprint 8O): added
doppler_secretsbackend (env-token auth, pre-provisioned secret model) - OAuth cache provider expansion (Sprint 8P): added
onepassword_connectbackend (env-token auth, pre-provisioned item/field model) - OAuth cache provider expansion (Sprint 8Q): added
bitwarden_secretsbackend (env-token auth, pre-provisioned secret model) - OAuth cache provider expansion (Sprint 8R): added
infisical_secretsbackend (env-token auth, pre-provisioned secret model) - OAuth cache provider expansion (Sprint 8S): added
akeyless_secretsbackend (env-token auth, pre-provisioned secret model) - OAuth cache provider expansion (Sprint 8T): added
gitlab_variablesbackend (env-token auth, pre-provisioned project variable model) - OAuth cache provider expansion (Sprint 8U): added
github_actions_variablesbackend (env-token auth, pre-provisioned repository variable model) - OAuth cache provider expansion (Sprint 8V): added
github_environment_variablesbackend (env-token auth, pre-provisioned repository environment variable model) - OAuth cache provider expansion (Sprint 8W): added
github_organization_variablesbackend (env-token auth, pre-provisioned organization variable model) - Release + contract hardening (Sprint 8X): added build-wheel CLI smoke verification, publish-time wheel/tag version guard, and expanded
persistent=falsebackend invariant coverage - OAuth cache provider expansion (Sprint 8Y): added
consul_kvbackend (env-token auth, pre-provisioned KV key model) - OAuth cache provider expansion (Sprint 8Z): added
redis_kvbackend (env-password auth, pre-provisioned key model) - Stabilization hardening (Sprint 8AA): centralized OAuth cache backend dispatch contract + stricter publish-time version consistency checks (
pyproject,__version__, wheel metadata, CLI) - OAuth cache provider expansion (Sprint 8AB): added
cloudflare_kvbackend (env-token auth, pre-provisioned KV key model) - OAuth cache provider expansion (Sprint 8AC): added
gitlab_group_variablesbackend (env-token auth, pre-provisioned group variable model) - v1.0 RC stabilization (Sprint 8AD): feature-freeze with backend contract lock, RC-safe tag/version normalization (
v1.0.0-rcN->1.0.0rcN) in publish guards, and post-1.0 deferred-provider positioning - v1.0.0 GA finalization: RC2 snapshot promoted to stable without runtime/CLI/auth/report contract changes
- Post-1.0 provider v2 expansion (Sprint 9A): GitLab project/group variable backends now support optional
gitlab_environment_scope(default*) - Post-1.0 provider v2 expansion (Sprint 9B): GitHub organization variable backend preserves existing visibility (
all/private/selected) during cache updates - Post-1.0 stabilization hardening (Sprint 9C): OAuth cache dispatch error paths fail closed (
load -> {},persist -> no-op) and publish visibility verification uses explicit PyPI index/no-cache flags - Post-1.0 provider expansion (Sprint 9D): added
etcd_kvbackend (etcd v3 JSON API, env-token auth, pre-provisioned key model) - Post-1.0 stabilization hardening (Sprint 9E): centralized remote backend spec as canonical source for dispatch maps and tightened matrix contract coverage without runtime behavior changes
- Post-1.0 provider expansion (Sprint 9F): added
gitlab_instance_variablesbackend (GitLab admin instance variable API, env-token auth, pre-provisioned variable model without environment scope) - Post-1.0 stabilization hardening (Sprint 9G): centralized GitLab backend capability matrix reused across validation and API path/query/body builders, plus single-script version consistency checks reused in build/publish CI stages
- Post-1.0 provider expansion (Sprint 9H): added
postgres_kvbackend (psycopg3 env-DSN auth, fixed-schema pre-provisioned row model) - Post-1.0 stabilization hardening (Sprint 9I): expanded backend-dispatch contract resolver coverage and consolidated PyPI visibility verification under the shared release-consistency script (runtime behavior unchanged)
- Post-1.0 provider expansion (Sprint 9J): added
mysql_kvbackend (PyMySQL env-DSN auth, fixed-schema pre-provisioned row model) - Post-1.0 stabilization hardening (Sprint 9K): centralized OAuth cache backend-contract checks and tightened deterministic release consistency validation without runtime behavior changes
- Post-1.0 stabilization hardening (Sprint 9L): strengthened backend-contract mismatch detection (including supported-set drift), improved deterministic PyPI visibility retry logging for transient failures, and kept runtime behavior unchanged
- Post-1.0 provider expansion (Sprint 9M): added
mongo_kvbackend (PyMongo env-DSN auth, fixed-schema pre-provisioned document model) - Post-1.0 stabilization hardening (Sprint 9N): centralized OAuth cache contract snapshot checks (including callable-map drift guards) and tightened deterministic PyPI visibility diagnostics with explicit index/timeout inputs in the shared release-consistency script
- Post-1.0 provider expansion (Sprint 9O): added
dynamodb_kvbackend (boto3 DynamoDB client with existing AWS region/endpoint settings, fixed-schema pre-provisioned item model) - Post-1.0 stabilization hardening (Sprint 9P): canonical OAuth cache contract mismatch diagnostics now emit deterministic missing/extra backend details, and PyPI visibility retries log deterministic attempt-scoped diagnostics in the shared release-consistency script
- Post-1.0 provider expansion (Sprint 9Q): added
s3_object_kvbackend (boto3 S3 object client with existing AWS region/endpoint settings, fixed-schema pre-provisioned object model) - Post-1.0 stabilization hardening (Sprint 9R): canonical backend contract expected-map baseline is now initialized from spec-derived sources and PyPI visibility diagnostics are emitted through a deterministic attempt-scoped logger (no runtime behavior change)
- Post-1.0 GitLab v2 finalization (Sprint 9S): GitLab capability contract is explicitly locked so
gitlab_environment_scoperemains supported only for project/group backends while instance backend stays scope-forbidden (auth_config_error) due upstream API limits - Post-1.0 stabilization hardening (Sprint 9T): release-consistency PyPI retry diagnostics now normalize volatile memory-address fragments for deterministic logs while preserving retry semantics and runtime behavior
- Post-1.0 stabilization hardening (Sprint 9U): OAuth cache contract snapshot checks now use detached map snapshots for stricter drift isolation, and release-visibility diagnostics are covered by deterministic retry-path tests (runtime behavior unchanged)
- Post-1.0 provider expansion (Sprint 9V): added
sqlite_kvbackend (sqlite3env-DSN auth, fixed-schema pre-provisioned row model) - Post-1.0 stabilization hardening (Sprint 9W): centralized OAuth cache contract mismatch validation through shared deterministic helpers (
set/source/callabledeltas) and added explicit final-attempt PyPI visibility failure events in release-consistency diagnostics (runtime behavior unchanged) - Post-1.0 stabilization hardening (Sprint 9X): unified contract mismatch evaluation into one deterministic candidate pipeline and extracted deterministic PyPI visibility command/env builders (runtime behavior unchanged)
- Baseline mutation detection (
added/removed/changed) with deterministic hashes - Severity threshold filtering and documented exit-code contract
Installation
From PyPI (after trusted publisher mapping is enabled and first publish succeeds):
pip install ogulcanaydogan-mcp-security-scanner
From source:
git clone https://github.com/ogulcanaydogan/mcp-security-scanner.git
cd mcp-security-scanner
pip install -e .[dev]
PyPI Operations Checklist (Single Owner)
Current owner model is single-account (ogulcan). Keep these controls in place:
- Ensure PyPI 2FA is enabled and recovery codes are stored offline.
- Keep account email access current and verified before release windows.
- In project publishing settings, keep exactly one active trusted publisher for:
- repository
ogulcanaydogan/mcp-security-scanner - workflow
ci.yml - environment
(Any)(empty)
- repository
- Remove stale duplicate/pending publisher records for the same project.
Quick Start
# Version check
mcp-scan --version
# Scan a stdio server command
mcp-scan server "python -m my_mcp_server" --format json
# Scan a URL target (auto-detected: streamable-http, fallback to sse)
mcp-scan server "https://example.com/sse" --format html --output report.html
# Scan a URL target with auth/header/mTLS JSON options
mcp-scan server "https://example.com/mcp" \
--headers-json '{"X-Trace":"run-42"}' \
--auth-json '{"type":"api_key","key_env":"MCP_API_KEY"}' \
--mtls-cert-file /etc/mcp/client.crt \
--mtls-key-file /etc/mcp/client.key \
--format json
# Run dynamic probes in addition to default analyzers (opt-in)
mcp-scan server "python -m my_mcp_server" --dynamic --format json
# Build baseline from live server snapshot
mcp-scan baseline "python -m my_mcp_server" --save baseline.json
# Compare live snapshot with baseline
mcp-scan compare baseline.json "python -m my_mcp_server" --format sarif --output mutations.sarif
# Rotate persistent OAuth cache encryption key
mcp-scan cache rotate
config Command (Claude Desktop Config)
mcp-scan config reads mcpServers entries and scans each server sequentially.
mcp-scan config ~/.claude/claude_desktop_config.json --timeout 30 --format json
Supported entry styles:
{
"mcpServers": {
"local-stdio": {
"transport": "stdio",
"command": "python",
"args": ["-m", "my_mcp_server"],
"env": {"APP_ENV": "prod"}
},
"remote-sse": {
"transport": "sse",
"url": "https://example.com/sse",
"headers": {"X-Trace": "req-42"},
"mtls_cert_file": "/etc/mcp/transport-client.crt",
"mtls_key_file": "/etc/mcp/transport-client.key",
"mtls_ca_bundle_file": "/etc/mcp/transport-ca.pem",
"auth": {"type": "bearer", "token_env": "MCP_BEARER_TOKEN"}
},
"remote-streamable": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {"type": "api_key", "key_env": "MCP_API_KEY", "header": "X-API-Key"}
},
"remote-session": {
"transport": "sse",
"url": "https://example.com/session",
"headers": {"Cookie": "existing=1"},
"auth": {"type": "session_cookie", "cookie_env": "MCP_SESSION_ID", "cookie_name": "session"}
},
"remote-oauth": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"token_endpoint_auth_method": "private_key_jwt",
"client_assertion_kms_key_id": "arn:aws:kms:eu-west-1:111122223333:key/abcd",
"client_assertion_kms_region": "eu-west-1",
"client_assertion_kms_endpoint_url": "https://kms.eu-west-1.amazonaws.com",
"client_assertion_kid": "key-2026-03",
"mtls_cert_file": "/etc/mcp/oauth-client.crt",
"mtls_key_file": "/etc/mcp/oauth-client.key",
"mtls_ca_bundle_file": "/etc/mcp/oauth-ca.pem",
"scope": "mcp.read",
"audience": "mcp-security-scanner",
"cache": {"persistent": true, "namespace": "prod-security", "backend": "local"},
"header": "Authorization",
"scheme": "Bearer"
}
},
"remote-oauth-aws-cache": {
"transport": "sse",
"url": "https://example.com/sse",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "aws_secrets_manager",
"aws_secret_id": "mcp-security-scanner/oauth-cache-prod",
"aws_region": "eu-west-1",
"aws_endpoint_url": "https://secretsmanager.eu-west-1.amazonaws.com"
}
}
},
"remote-oauth-gcp-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "gcp_secret_manager",
"gcp_secret_name": "projects/my-project/secrets/mcp-security-scanner-oauth-cache",
"gcp_endpoint_url": "https://secretmanager.googleapis.com"
}
}
},
"remote-oauth-azure-cache": {
"transport": "sse",
"url": "https://example.com/sse",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "azure_key_vault",
"azure_vault_url": "https://mcp-security.vault.azure.net",
"azure_secret_name": "mcp-security-scanner-oauth-cache",
"azure_secret_version": "latest"
}
}
},
"remote-oauth-vault-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "hashicorp_vault",
"vault_url": "https://vault.example.com",
"vault_secret_path": "kv/mcp-security-scanner/oauth-cache",
"vault_token_env": "VAULT_TOKEN",
"vault_namespace": "platform-security"
}
}
},
"remote-oauth-k8s-cache": {
"transport": "sse",
"url": "https://example.com/sse",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "kubernetes_secrets",
"k8s_secret_namespace": "mcp-security",
"k8s_secret_name": "oauth-cache",
"k8s_secret_key": "oauth_cache"
}
}
},
"remote-oauth-oci-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "oci_vault",
"oci_secret_ocid": "ocid1.secret.oc1.iad.exampleuniqueid1234567890",
"oci_region": "eu-frankfurt-1",
"oci_endpoint_url": "https://vaults.eu-frankfurt-1.oci.oraclecloud.com"
}
}
},
"remote-oauth-doppler-cache": {
"transport": "sse",
"url": "https://example.com/sse",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "doppler_secrets",
"doppler_project": "security-platform",
"doppler_config": "prd",
"doppler_secret_name": "MCP_OAUTH_CACHE",
"doppler_token_env": "DOPPLER_TOKEN",
"doppler_api_url": "https://api.doppler.com"
}
}
},
"remote-oauth-onepassword-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "onepassword_connect",
"op_connect_host": "https://op-connect.example.com",
"op_vault_id": "vault-uuid-or-id",
"op_item_id": "item-uuid",
"op_field_label": "oauth_cache",
"op_connect_token_env": "OP_CONNECT_TOKEN"
}
}
},
"remote-oauth-bitwarden-cache": {
"transport": "sse",
"url": "https://example.com/sse",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "bitwarden_secrets",
"bw_secret_id": "11111111-2222-3333-4444-555555555555",
"bw_access_token_env": "BWS_ACCESS_TOKEN",
"bw_api_url": "https://api.bitwarden.com"
}
}
},
"remote-oauth-infisical-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "infisical_secrets",
"infisical_project_id": "workspace-123",
"infisical_environment": "prod",
"infisical_secret_name": "MCP_OAUTH_CACHE",
"infisical_token_env": "INFISICAL_TOKEN",
"infisical_api_url": "https://app.infisical.com/api"
}
}
},
"remote-oauth-akeyless-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "akeyless_secrets",
"akeyless_secret_name": "/prod/mcp/oauth_cache",
"akeyless_token_env": "AKEYLESS_TOKEN",
"akeyless_api_url": "https://api.akeyless.io"
}
}
},
"remote-oauth-gitlab-cache": {
"transport": "sse",
"url": "https://example.com/sse",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "gitlab_variables",
"gitlab_project_id": "12345",
"gitlab_variable_key": "MCP_OAUTH_CACHE",
"gitlab_token_env": "GITLAB_TOKEN",
"gitlab_api_url": "https://gitlab.example.com/api/v4"
}
}
},
"remote-oauth-gitlab-group-cache": {
"transport": "sse",
"url": "https://example.com/sse",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "gitlab_group_variables",
"gitlab_group_id": "67890",
"gitlab_variable_key": "MCP_OAUTH_CACHE",
"gitlab_token_env": "GITLAB_TOKEN",
"gitlab_api_url": "https://gitlab.example.com/api/v4"
}
}
},
"remote-oauth-github-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "github_actions_variables",
"github_repository": "ogulcanaydogan/mcp-security-scanner",
"github_variable_name": "MCP_OAUTH_CACHE",
"github_token_env": "GITHUB_TOKEN",
"github_api_url": "https://api.github.com"
}
}
},
"remote-oauth-github-environment-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "github_environment_variables",
"github_repository": "ogulcanaydogan/mcp-security-scanner",
"github_environment_name": "production",
"github_variable_name": "MCP_OAUTH_CACHE",
"github_token_env": "GITHUB_TOKEN",
"github_api_url": "https://api.github.com"
}
}
},
"remote-oauth-github-organization-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "github_organization_variables",
"github_organization": "ogulcanaydogan",
"github_variable_name": "MCP_OAUTH_CACHE",
"github_token_env": "GITHUB_TOKEN",
"github_api_url": "https://api.github.com"
}
}
},
"remote-oauth-consul-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "consul_kv",
"consul_key_path": "mcp/security/oauth/cache",
"consul_token_env": "CONSUL_HTTP_TOKEN",
"consul_api_url": "https://consul.example.com"
}
}
},
"remote-oauth-redis-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "redis_kv",
"redis_key": "mcp/security/oauth/cache",
"redis_url": "rediss://redis.example.com:6380/0",
"redis_password_env": "REDIS_PASSWORD"
}
}
},
"remote-oauth-cloudflare-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "cloudflare_kv",
"cf_account_id": "1234567890abcdef1234567890abcdef",
"cf_namespace_id": "fedcba0987654321fedcba0987654321",
"cf_kv_key": "mcp/security/oauth/cache",
"cf_api_token_env": "CLOUDFLARE_API_TOKEN",
"cf_api_url": "https://api.cloudflare.com/client/v4"
}
}
},
"remote-oauth-etcd-cache": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_client_credentials",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_CLIENT_SECRET",
"cache": {
"persistent": true,
"namespace": "prod-security",
"backend": "etcd_kv",
"etcd_key": "mcp/security/oauth/cache",
"etcd_api_url": "https://etcd.example.com:2379",
"etcd_token_env": "ETCD_TOKEN"
}
}
},
"remote-device-oauth": {
"transport": "sse",
"url": "https://example.com/sse",
"auth": {
"type": "oauth_device_code",
"device_authorization_url": "https://auth.example.com/oauth/device/code",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_DEVICE_CLIENT_ID",
"client_secret_env": "MCP_OAUTH_DEVICE_CLIENT_SECRET",
"token_endpoint_auth_method": "client_secret_post",
"scope": "mcp.read",
"audience": "mcp-security-scanner",
"header": "Authorization",
"scheme": "Bearer"
}
},
"remote-auth-code": {
"transport": "streamable-http",
"url": "https://example.com/mcp",
"auth": {
"type": "oauth_auth_code_pkce",
"authorization_url": "https://auth.example.com/oauth/authorize",
"token_url": "https://auth.example.com/oauth/token",
"client_id_env": "MCP_OAUTH_AUTH_CODE_CLIENT_ID",
"scope": "mcp.read",
"audience": "mcp-security-scanner",
"redirect_host": "127.0.0.1",
"redirect_port": 8765,
"callback_path": "/callback"
}
}
}
}
Notes:
stdiovalidation:commandrequired,argsoptional list,envoptional objectssevalidation:urlrequired (http/https),headersoptional objectstreamable-httpvalidation:urlrequired (http/https),headersoptional objecttransport: "streamable_http"alias is accepted and normalized tostreamable-httpauthis optional and only valid forsse/streamable-httpentriesauthvalidation/env resolution errors produceauth_config_errorfindings and scan continues with remaining servers- OAuth token endpoint/network/response failures produce
auth_token_errorfindings and scan continues with remaining servers oauth_client_credentialsandoauth_device_codesupport optionaltoken_endpoint_auth_method:client_secret_post(default)client_secret_basic(oauth_device_coderequiresclient_secret_envwhen used)private_key_jwt(oauth_client_credentials+oauth_device_code;oauth_auth_code_pkceremains unchanged)
private_key_jwtvalidation rules:- exactly one signing source is required:
client_assertion_key_envclient_assertion_key_fileclient_assertion_kms_key_id(AWS KMS signing)
- optional KMS tuning:
client_assertion_kms_region,client_assertion_kms_endpoint_url - optional
client_assertion_kidis propagated into JWT header - v1 signing algorithm is
RS256
- exactly one signing source is required:
- token endpoint mTLS options for OAuth auth entries:
mtls_cert_file+mtls_key_filemust be provided together- optional
mtls_ca_bundle_fileis used as request verify bundle - mTLS is applied only to OAuth token endpoint calls
- transport-level mTLS options for network entries (
sse,streamable-http):- top-level
mtls_cert_file+mtls_key_filemust be provided together - optional top-level
mtls_ca_bundle_fileis used as connection verify bundle - applies to discovery transport HTTP client setup (independent from
auth.mtls_*)
- top-level
- OAuth token cache key is deterministic:
namespace + token_url + client_id + scope + audience auth.cacheis optional and only valid for OAuth auth types:persistent(bool, defaultfalse)namespace(string, default"default")backend(string, default"local"):local,aws_secrets_manager,aws_ssm_parameter_store,gcp_secret_manager,azure_key_vault,hashicorp_vault,kubernetes_secrets,oci_vault,doppler_secrets,onepassword_connect,bitwarden_secrets,infisical_secrets,akeyless_secrets,gitlab_variables,gitlab_group_variables,gitlab_instance_variables,github_actions_variables,github_environment_variables,github_organization_variables,consul_kv,redis_kv,cloudflare_kv,etcd_kv,postgres_kv,mysql_kv,mongo_kv,dynamodb_kv,s3_object_kv, orsqlite_kvaws_secret_id(required whenbackend=aws_secrets_manager)aws_ssm_parameter_name(required whenbackend=aws_ssm_parameter_store)- optional
aws_region,aws_endpoint_urlfor AWS client routing (aws_secrets_manager/aws_ssm_parameter_store/dynamodb_kv/s3_object_kv) gcp_secret_name(required whenbackend=gcp_secret_manager, formatprojects/<project>/secrets/<secret>)- optional
gcp_endpoint_urlfor GCP client endpoint routing (ADC auth) azure_vault_url(required whenbackend=azure_key_vault, formathttps://<name>.vault.azure.net)azure_secret_name(required whenbackend=azure_key_vault, Azure Key Vault secret-name rules)- optional
azure_secret_version(defaultlatest) vault_url(required whenbackend=hashicorp_vault,http/https)vault_secret_path(required whenbackend=hashicorp_vault, KV path)- optional
vault_token_env(Vault token env var name; defaults toVAULT_TOKEN) - optional
vault_namespace k8s_secret_namespace(required whenbackend=kubernetes_secrets, Kubernetes namespace)k8s_secret_name(required whenbackend=kubernetes_secrets, Kubernetes Secret name)- optional
k8s_secret_key(defaultoauth_cache) oci_secret_ocid(required whenbackend=oci_vault, OCI secret OCID)- optional
oci_region - optional
oci_endpoint_url(http/https) doppler_project(required whenbackend=doppler_secrets)doppler_config(required whenbackend=doppler_secrets)doppler_secret_name(required whenbackend=doppler_secrets)- optional
doppler_token_env(defaultDOPPLER_TOKEN) - optional
doppler_api_url(httpsURL; defaults to Doppler API) op_connect_host(required whenbackend=onepassword_connect,httpsURL)op_vault_id(required whenbackend=onepassword_connect)op_item_id(required whenbackend=onepassword_connect)- optional
op_field_label(defaultoauth_cache) - optional
op_connect_token_env(defaultOP_CONNECT_TOKEN) bw_secret_id(required whenbackend=bitwarden_secrets, UUID-style secret ID)- optional
bw_access_token_env(defaultBWS_ACCESS_TOKEN) - optional
bw_api_url(httpsURL; defaults to Bitwarden API) infisical_project_id(required whenbackend=infisical_secrets)infisical_environment(required whenbackend=infisical_secrets)infisical_secret_name(required whenbackend=infisical_secrets)- optional
infisical_token_env(defaultINFISICAL_TOKEN) - optional
infisical_api_url(httpsURL; defaults to Infisical cloud API) akeyless_secret_name(required whenbackend=akeyless_secrets)- optional
akeyless_token_env(defaultAKEYLESS_TOKEN) - optional
akeyless_api_url(httpsURL; defaults to Akeyless API) gitlab_project_id(required whenbackend=gitlab_variables, numeric project ID)gitlab_group_id(required whenbackend=gitlab_group_variables, numeric group ID)gitlab_variable_key(required whenbackend=gitlab_variables,backend=gitlab_group_variables, orbackend=gitlab_instance_variables, env-style key)- optional
gitlab_environment_scope(default*, only forbackend=gitlab_variablesorbackend=gitlab_group_variables) - optional
gitlab_token_env(defaultGITLAB_TOKEN) - optional
gitlab_api_url(httpsURL; defaults tohttps://gitlab.com/api/v4) github_repository(required whenbackend=github_actions_variablesorbackend=github_environment_variables, format<owner>/<repo>)github_organization(required whenbackend=github_organization_variables, organization slug)github_environment_name(required whenbackend=github_environment_variables, non-empty string)github_variable_name(required whenbackend=github_actions_variables,backend=github_environment_variables, orbackend=github_organization_variables, env-style key)- optional
github_token_env(defaultGITHUB_TOKEN) - optional
github_api_url(httpsURL; defaults tohttps://api.github.com) consul_key_path(required whenbackend=consul_kv, Consul KV key path)- optional
consul_token_env(defaultCONSUL_HTTP_TOKEN) - optional
consul_api_url(http/httpsURL; defaults tohttp://127.0.0.1:8500) redis_key(required whenbackend=redis_kv, Redis key path)- optional
redis_url(redis://orrediss://; defaults toredis://127.0.0.1:6379/0) - optional
redis_password_env(defaultREDIS_PASSWORD) cf_account_id(required whenbackend=cloudflare_kv, Cloudflare account identifier)cf_namespace_id(required whenbackend=cloudflare_kv, Cloudflare KV namespace identifier)cf_kv_key(required whenbackend=cloudflare_kv, Cloudflare KV key)- optional
cf_api_token_env(defaultCLOUDFLARE_API_TOKEN) - optional
cf_api_url(httpsURL; defaults tohttps://api.cloudflare.com/client/v4) etcd_key(required whenbackend=etcd_kv, etcd v3 key path)- optional
etcd_api_url(http/httpsURL; defaults tohttp://127.0.0.1:2379) - optional
etcd_token_env(defaultETCD_TOKEN) postgres_cache_key(required whenbackend=postgres_kv, fixed-schema row key)- optional
postgres_dsn_env(defaultPOSTGRES_DSN) mysql_cache_key(required whenbackend=mysql_kv, fixed-schema row key)- optional
mysql_dsn_env(defaultMYSQL_DSN) mongo_cache_key(required whenbackend=mongo_kv, fixed-schema document key)- optional
mongo_dsn_env(defaultMONGODB_URI) dynamodb_cache_key(required whenbackend=dynamodb_kv, fixed-schema item key)s3_bucket(required whenbackend=s3_object_kv, pre-provisioned S3 bucket)s3_object_key(required whenbackend=s3_object_kv, pre-provisioned object key)sqlite_cache_key(required whenbackend=sqlite_kv, fixed-schema row key)- optional
sqlite_dsn_env(defaultSQLITE_DSN)
- cache lookup order for OAuth:
- in-memory
- persistent disk cache (
auth.cache.persistent=true) - refresh grant
- primary grant
- persistent cache details (opt-in):
backend=local:- encrypted file:
~/.cache/mcp-security-scanner/oauth-cache-v1.json.enc - lock file:
~/.cache/mcp-security-scanner/oauth-cache-v1.lock(exclusive lock with retry; timeout falls back to in-memory/live token flow) - encrypted payload envelope:
schema_version,key_id,updated_at,entries(v2) - encryption key lookup: OS keyring (
service="mcp-security-scanner",username="oauth-cache-key-v1") then fallback key file~/.config/mcp-security-scanner/cache.key - key metadata stores
active+historicalkey entries (key_id+fernet_key); legacy raw key format remains readable - decrypt recovery order: payload
key_idmatch when possible, then active key, then historical keys (deterministic order) - historical key retention is bounded (max 3);
cache rotatepromotes current active key into historical set - fallback key file is created with
0600permissions - cache/key file mode hardening uses best-effort
0600 - corrupt or undecryptable cache payloads are quarantined as
oauth-cache-v1.json.enc.corrupt.<timestamp>
- encrypted file:
backend=aws_secrets_manager:- cache payload is stored as a single JSON envelope in the configured AWS secret (
auth.cache.aws_secret_id) - optional
aws_regionandaws_endpoint_urltune client resolution
- cache payload is stored as a single JSON envelope in the configured AWS secret (
backend=aws_ssm_parameter_store:- cache payload is stored as a single JSON envelope in configured SSM SecureString parameter (
auth.cache.aws_ssm_parameter_name) - parameter must be pre-provisioned; missing/provider errors are non-fatal and scanner falls back to live token flow
- optional
aws_regionandaws_endpoint_urltune client resolution
- cache payload is stored as a single JSON envelope in configured SSM SecureString parameter (
backend=gcp_secret_manager:- cache payload is stored as a single JSON envelope in the configured GCP secret (
auth.cache.gcp_secret_name) - writes use new secret versions via
projects/.../secrets/.../versions/latestread +add_secret_versionwrite - secret must be pre-provisioned; missing/provider errors are non-fatal and scanner falls back to live token flow
- optional
gcp_endpoint_urlis supported for custom endpoint routing; auth uses ADC
- cache payload is stored as a single JSON envelope in the configured GCP secret (
backend=azure_key_vault:- cache payload is stored as a single JSON envelope in the configured Azure Key Vault secret (
auth.cache.azure_secret_name) - auth uses Azure SDK default credential chain (
DefaultAzureCredential), no new CLI credential flags - secret must be pre-provisioned; missing/provider errors are non-fatal and scanner falls back to live token flow
- optional
azure_secret_versioncontrols read version (defaultlatest); writes create a new secret version
- cache payload is stored as a single JSON envelope in the configured Azure Key Vault secret (
backend=hashicorp_vault:- cache payload is stored as a single JSON envelope in configured Vault KV v2 secret path (
auth.cache.vault_secret_path) - auth uses configured token env (
vault_token_env) orVAULT_TOKENfallback - optional Vault enterprise namespace is supported via
vault_namespace - secret path must be pre-provisioned; missing/provider errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured Vault KV v2 secret path (
backend=kubernetes_secrets:- cache payload is stored as a single JSON envelope in configured Kubernetes Secret data key
(
auth.cache.k8s_secret_namespace/auth.cache.k8s_secret_name/auth.cache.k8s_secret_key) - Kubernetes auth chain is deterministic: in-cluster config first, then kubeconfig fallback
- Secret must be pre-provisioned; scanner patches existing Secret data and does not auto-create missing Secrets
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured Kubernetes Secret data key
(
backend=oci_vault:- cache payload is stored as a single JSON envelope in configured OCI Vault secret (
auth.cache.oci_secret_ocid) - auth chain is deterministic: Resource Principal signer first, then OCI config/profile fallback
- secret must be pre-provisioned; scanner writes new secret content version and does not auto-create missing secrets
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured OCI Vault secret (
backend=doppler_secrets:- cache payload is stored as a single JSON envelope in configured Doppler secret
(
auth.cache.doppler_project/auth.cache.doppler_config/auth.cache.doppler_secret_name) - auth uses env token only (
auth.cache.doppler_token_env, defaultDOPPLER_TOKEN) - secret must be pre-provisioned; scanner updates existing secret key and does not auto-create missing secrets
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured Doppler secret
(
backend=onepassword_connect:- cache payload is stored as a single JSON envelope in configured 1Password Connect item field
(
auth.cache.op_connect_host/auth.cache.op_vault_id/auth.cache.op_item_id/auth.cache.op_field_label) - auth uses env token only (
auth.cache.op_connect_token_env, defaultOP_CONNECT_TOKEN) - item+field must be pre-provisioned; scanner updates existing field value and does not auto-create missing item/field
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured 1Password Connect item field
(
backend=bitwarden_secrets:- cache payload is stored as a single JSON envelope in configured Bitwarden secret value
(
auth.cache.bw_secret_id) - auth uses env token only (
auth.cache.bw_access_token_env, defaultBWS_ACCESS_TOKEN) - secret must be pre-provisioned; scanner updates existing secret value and does not auto-create missing secrets
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured Bitwarden secret value
(
backend=infisical_secrets:- cache payload is stored as a single JSON envelope in configured Infisical secret value
(
auth.cache.infisical_project_id/auth.cache.infisical_environment/auth.cache.infisical_secret_name) - auth uses env token only (
auth.cache.infisical_token_env, defaultINFISICAL_TOKEN) - secret must be pre-provisioned; scanner updates existing secret value and does not auto-create missing secrets
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured Infisical secret value
(
backend=akeyless_secrets:- cache payload is stored as a single JSON envelope in configured Akeyless secret value
(
auth.cache.akeyless_secret_name) - auth uses env token only (
auth.cache.akeyless_token_env, defaultAKEYLESS_TOKEN) - secret must be pre-provisioned; scanner updates existing secret value and does not auto-create missing secrets
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured Akeyless secret value
(
backend=gitlab_variables:- cache payload is stored as a single JSON envelope in configured GitLab project variable value
(
auth.cache.gitlab_project_id/auth.cache.gitlab_variable_key/auth.cache.gitlab_environment_scope) - auth uses env token only (
auth.cache.gitlab_token_env, defaultGITLAB_TOKEN) auth.cache.gitlab_environment_scopeis optional and defaults to*- variable must be pre-provisioned; scanner updates existing variable value and does not auto-create missing variables
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured GitLab project variable value
(
backend=gitlab_group_variables:- cache payload is stored as a single JSON envelope in configured GitLab group variable value
(
auth.cache.gitlab_group_id/auth.cache.gitlab_variable_key/auth.cache.gitlab_environment_scope) - auth uses env token only (
auth.cache.gitlab_token_env, defaultGITLAB_TOKEN) auth.cache.gitlab_environment_scopeis optional and defaults to*- variable must be pre-provisioned; scanner updates existing variable value and does not auto-create missing variables
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured GitLab group variable value
(
backend=gitlab_instance_variables:- cache payload is stored as a single JSON envelope in configured GitLab instance variable value
(
auth.cache.gitlab_variable_key) - auth uses env token only (
auth.cache.gitlab_token_env, defaultGITLAB_TOKEN) - environment scope is not used in v1 for instance variables
- variable must be pre-provisioned; scanner updates existing variable value and does not auto-create missing variables
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured GitLab instance variable value
(
backend=github_actions_variables:- cache payload is stored as a single JSON envelope in configured GitHub repository Actions variable value
(
auth.cache.github_repository/auth.cache.github_variable_name) - auth uses env token only (
auth.cache.github_token_env, defaultGITHUB_TOKEN) - variable must be pre-provisioned; scanner updates existing variable value and does not auto-create missing variables
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured GitHub repository Actions variable value
(
backend=github_environment_variables:- cache payload is stored as a single JSON envelope in configured GitHub repository environment variable value
(
auth.cache.github_repository/auth.cache.github_environment_name/auth.cache.github_variable_name) - auth uses env token only (
auth.cache.github_token_env, defaultGITHUB_TOKEN) - environment + variable must be pre-provisioned; scanner updates existing variable value and does not auto-create missing variables
- environment/variable path segments are URL-encoded before API calls
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured GitHub repository environment variable value
(
backend=github_organization_variables:- cache payload is stored as a single JSON envelope in configured GitHub organization variable value
(
auth.cache.github_organization/auth.cache.github_variable_name) - auth uses env token only (
auth.cache.github_token_env, defaultGITHUB_TOKEN) - organization variable must be pre-provisioned; scanner updates existing variable value and does not auto-create missing variables
- v2 behavior preserves existing organization variable visibility on write (
all,private,selected) - when visibility is
selected, scanner resolves current selected repository IDs and re-sends them on update - organization/variable path segments are URL-encoded before API calls
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured GitHub organization variable value
(
backend=consul_kv:- cache payload is stored as a single JSON envelope in configured Consul KV key
(
auth.cache.consul_key_path) - auth uses env token only (
auth.cache.consul_token_env, defaultCONSUL_HTTP_TOKEN) - key must be pre-provisioned; scanner updates existing key value and does not auto-create missing keys
- key path segments are URL-encoded (with
/preserved) before API calls - missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured Consul KV key
(
backend=redis_kv:- cache payload is stored as a single JSON envelope in configured Redis key (
auth.cache.redis_key) - auth uses env password only (
auth.cache.redis_password_env, defaultREDIS_PASSWORD) - key must be pre-provisioned; scanner updates existing key value and does not auto-create missing keys
- supports
redis://andrediss://URL forms viaauth.cache.redis_url - missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured Redis key (
backend=cloudflare_kv:- cache payload is stored as a single JSON envelope in configured Cloudflare KV key
(
auth.cache.cf_account_id/auth.cache.cf_namespace_id/auth.cache.cf_kv_key) - auth uses env token only (
auth.cache.cf_api_token_env, defaultCLOUDFLARE_API_TOKEN) - key must be pre-provisioned; scanner updates existing key value and does not auto-create missing keys
- account/namespace/key path segments are URL-encoded before API calls
- missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured Cloudflare KV key
(
backend=etcd_kv:- cache payload is stored as a single JSON envelope in configured etcd v3 key (
auth.cache.etcd_key) - auth uses env token when present (
auth.cache.etcd_token_env, defaultETCD_TOKEN); missing token falls back to auth-less client - key must be pre-provisioned; scanner updates existing key value and does not auto-create missing keys
- read uses
POST /v3/kv/rangewith base64 key, write usesPOST /v3/kv/putwith base64 key/value - missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured etcd v3 key (
backend=postgres_kv:- cache payload is stored as a single JSON envelope in fixed schema row (
mcp_oauth_cache_store.cache_key/payload_json) - DSN is env-only (
auth.cache.postgres_dsn_env, defaultPOSTGRES_DSN) - row must be pre-provisioned; scanner updates existing row value and does not auto-create missing rows
- read uses
SELECT payload_json ... WHERE cache_key=%s, write uses preflightSELECT+UPDATE - missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in fixed schema row (
backend=mysql_kv:- cache payload is stored as a single JSON envelope in fixed schema row (
mcp_oauth_cache_store.cache_key/payload_json) - DSN is env-only (
auth.cache.mysql_dsn_env, defaultMYSQL_DSN) - row must be pre-provisioned; scanner updates existing row value and does not auto-create missing rows
- read uses
SELECT payload_json ... WHERE cache_key=%s, write uses preflightSELECT+UPDATE - missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in fixed schema row (
backend=mongo_kv:- cache payload is stored as a single JSON envelope in fixed schema document (
mcp_security_scanner.oauth_cache_store.cache_key/payload_json) - DSN is env-only (
auth.cache.mongo_dsn_env, defaultMONGODB_URI) - document must be pre-provisioned; scanner updates existing document value and does not auto-create missing documents
- read uses
find_one({"cache_key": ...}), write uses preflightfind_one+update_one - missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in fixed schema document (
backend=dynamodb_kv:- cache payload is stored as a single JSON envelope in fixed schema item (
mcp_oauth_cache_store.cache_key/payload_json) - DynamoDB client uses existing AWS settings (
auth.cache.aws_region,auth.cache.aws_endpoint_url) when provided - item must be pre-provisioned; scanner updates existing item value and does not auto-create missing items
- read uses
GetItem, write uses preflightGetItem+UpdateItemwithattribute_exists(cache_key)guard - missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in fixed schema item (
backend=s3_object_kv:- cache payload is stored as a single JSON envelope in configured S3 object (
auth.cache.s3_bucket/auth.cache.s3_object_key) - S3 client uses existing AWS settings (
auth.cache.aws_region,auth.cache.aws_endpoint_url) when provided - object must be pre-provisioned; scanner updates existing object value and does not auto-create missing objects
- read uses
GetObject, write uses preflightGetObject+PutObjecton the same object key - missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in configured S3 object (
backend=sqlite_kv:- cache payload is stored as a single JSON envelope in fixed schema row (
mcp_oauth_cache_store.cache_key/payload_json) - DSN is env-only (
auth.cache.sqlite_dsn_env, defaultSQLITE_DSN) - row must be pre-provisioned; scanner updates existing row value and does not auto-create missing rows
- read uses
SELECT payload_json ... WHERE cache_key=?, write uses preflightSELECT+UPDATE - missing/provider/read/write/parse errors are non-fatal and scanner falls back to live token flow
- cache payload is stored as a single JSON envelope in fixed schema row (
- backend read/write/decrypt/parse failures are non-fatal; scanner falls back to live token flow
oauth_device_codeuses copy/paste UX (verification_uri+user_code) and supports refresh-token reuse on expiry- in headless/CI environments (no interactive TTY),
oauth_device_codeentries produceauth_token_errorand scan continues oauth_auth_code_pkceuses local callback + PKCE (S256), prints authorization URL, and supports refresh-token reuse on expiryoauth_auth_code_pkcecallback listener tries configured/default port first and falls back to random local port when needed- in headless/CI environments (no interactive TTY),
oauth_auth_code_pkceentries produceauth_token_errorand scan continues - Authorization header scheme precedence for OAuth is:
auth.scheme(if provided)- token response
token_type(if present) - fallback
Bearer
- OAuth token/device/refresh/auth-code endpoint calls use shared transient retry policy:
- retryable statuses:
429,500,502,503,504 - retryable transport errors: timeout/connection/network
- max
2retries (total3attempts), short bounded backoff
- retryable statuses:
- dynamic analyzer v1 is opt-in:
- enable with
--dynamiconserverandconfig - default pipeline remains unchanged when flag is omitted
- bounded runtime policy is enforced from a single control point:
- max tool count, max probe payload count, max payload fields, per-probe timeout
- dynamic findings are returned in deterministic order with stable metadata keys
- benign placeholder/blocked-execution contexts are suppressed to reduce false positives
- enable with
- Refresh fallback behavior:
- if refresh fails with
invalid_grant/invalid_token, scanner drops cached refresh token and retries primary grant once - if retry requires interaction in headless mode,
auth_token_erroris emitted and scan continues
- if refresh fails with
- auth finding evidence never includes secret/token/cookie plaintext
- Unsupported transport entries do not stop the run; they are reported as findings
- Per-server scan failures do not stop the run; they are reported as
scan_failurefindings - URL positional commands (
server,baseline,compare) support:--headers-json(JSON object)--auth-json(JSON object with same shape asconfig.auth)--mtls-cert-file+--mtls-key-file(optional--mtls-ca-bundle-file)
- URL auth/mTLS options are URL-only; when used with stdio targets the command fails with operational error (
exit 2)
cache command:
mcp-scan cache rotaterotates persistent OAuth cache encryption key and re-encrypts cached entries- exit
0on success, exit2on operational failure
Outputs and Severity Filter
--format:json(default),html,sarif--output: write report to file; if omitted, prints to stdout--severity:critical | high | medium | low | info | all
Severity threshold is inclusive (high shows high + critical).
Exit Codes
| Command | Exit 0 |
Exit 1 |
Exit 2 |
|---|---|---|---|
server |
No findings after severity filter | Findings exist after filter | Operational error |
config |
No findings after severity filter | Findings exist after filter | Operational error |
compare |
No findings after severity filter | Findings exist after filter | Operational error |
baseline |
Baseline created successfully | N/A | Operational error |
Baseline v1 Format
baseline writes a baseline-v1 JSON document:
schema_versionscanner_versioncreated_atserver(name,command)tools[](overall_hash, field hashes, metadata)
compare maps mutation severity as:
removed/changed:highadded:medium
All mutation findings map to OWASP: LLM05.
Development
pytest -q
mypy src
Current quality gate:
- tests passing
- coverage
>=80% mypy srcclean
Roadmap (Post v1.0.0 GA)
Current release target:
1.0.24stabilization patch release (no new backend) with single-pipeline OAuth cache contract mismatch evaluation and deterministic PyPI visibility command/env construction in the shared release-consistency script.- Post-1.0 provider onboarding remains available under the same contract baseline.
Deferred (post-1.0):
- additional persistent secret-store providers beyond
local,aws_secrets_manager,aws_ssm_parameter_store,gcp_secret_manager,azure_key_vault,hashicorp_vault,kubernetes_secrets,oci_vault,doppler_secrets,onepassword_connect,bitwarden_secrets,infisical_secrets,akeyless_secrets,gitlab_variables,gitlab_group_variables,gitlab_instance_variables,github_actions_variables,github_environment_variables,github_organization_variables,consul_kv,redis_kv,cloudflare_kv,etcd_kv,postgres_kv,mysql_kv,mongo_kv,dynamodb_kv,s3_object_kv, andsqlite_kv; backend onboarding uses the shared dispatch/contract baseline from Sprint 8AA.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ogulcanaydogan_mcp_security_scanner-1.0.24.tar.gz.
File metadata
- Download URL: ogulcanaydogan_mcp_security_scanner-1.0.24.tar.gz
- Upload date:
- Size: 194.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
884296abf3e9f2be18654e1ef5686d43378be2f50a8901c41486c53257c349b5
|
|
| MD5 |
fab40cb401b88e753404bbb6170aea02
|
|
| BLAKE2b-256 |
5d65052ce99d06948701ddd5b4d5cf8e08d31701b9f4e234a47d46c0cf400834
|
Provenance
The following attestation bundles were made for ogulcanaydogan_mcp_security_scanner-1.0.24.tar.gz:
Publisher:
ci.yml on ogulcanaydogan/mcp-security-scanner
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ogulcanaydogan_mcp_security_scanner-1.0.24.tar.gz -
Subject digest:
884296abf3e9f2be18654e1ef5686d43378be2f50a8901c41486c53257c349b5 - Sigstore transparency entry: 1200716543
- Sigstore integration time:
-
Permalink:
ogulcanaydogan/mcp-security-scanner@9dada6924532002b9d1890ccd889d2ec00dde0d1 -
Branch / Tag:
refs/tags/v1.0.24 - Owner: https://github.com/ogulcanaydogan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9dada6924532002b9d1890ccd889d2ec00dde0d1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ogulcanaydogan_mcp_security_scanner-1.0.24-py3-none-any.whl.
File metadata
- Download URL: ogulcanaydogan_mcp_security_scanner-1.0.24-py3-none-any.whl
- Upload date:
- Size: 107.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb848c8d2a3b066ce7c9647812777cbb1c4072e69ff4b05e73bfae6f1ce7ab42
|
|
| MD5 |
97f6a3d903260538154cfdcf31640588
|
|
| BLAKE2b-256 |
43ebc10f841970ee8ebdf2f29761445e02a6613886ca11730f65221b877a84f0
|
Provenance
The following attestation bundles were made for ogulcanaydogan_mcp_security_scanner-1.0.24-py3-none-any.whl:
Publisher:
ci.yml on ogulcanaydogan/mcp-security-scanner
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ogulcanaydogan_mcp_security_scanner-1.0.24-py3-none-any.whl -
Subject digest:
eb848c8d2a3b066ce7c9647812777cbb1c4072e69ff4b05e73bfae6f1ce7ab42 - Sigstore transparency entry: 1200716547
- Sigstore integration time:
-
Permalink:
ogulcanaydogan/mcp-security-scanner@9dada6924532002b9d1890ccd889d2ec00dde0d1 -
Branch / Tag:
refs/tags/v1.0.24 - Owner: https://github.com/ogulcanaydogan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9dada6924532002b9d1890ccd889d2ec00dde0d1 -
Trigger Event:
push
-
Statement type: