Sign inSign up

mekayelanik/semgrep-mcp-server

By mekayelanik

Updated 10 days ago

Image
0

7.0K

mekayelanik/semgrep-mcp-server repository overview

Semgrep Logo

Docker Pulls Platforms License: GPL v3 Stars Issues Last Commit

Semgrep MCP Server

Unofficial Multi-Architecture Docker Image for Semgrep's MCP Server

⚠️ Unofficial image — community-maintained, packages the official Semgrep CLI (LGPL-2.1). Not affiliated with / endorsed by Semgrep Inc. Official: semgrep.dev. Semgrep® is a trademark of Semgrep Inc.; nominative use only.

Runs official semgrep mcp (built into Semgrep ≥ 1.146.0) wrapped with mcp-proxy for stdio→StreamableHTTP/SSE bridging, fronted by HAProxy L7 with TLS, HTTP/2, HTTP/3 (QUIC), CORS, rate limit, IP ACL, Bearer auth.

Table of Contents

😎 Buy Me a Coffee ☕︎

Your support encourages me to keep creating/supporting my open-source projects. If you found value in this project, you can buy me a coffee to keep me inspired.

Buy Me A Coffee

Overview

Semgrep MCP exposes Semgrep's security scanner as Model Context Protocol tools any AI coding assistant (Claude Code, Cursor, Windsurf, VS Code, Claude Desktop, ChatGPT) can call directly. This image packages the official upstream server in a production-ready container — OSS works with no cloud dependencies.

Key Features
  • Official upstream — uses semgrep mcp built into Semgrep CLI (≥ 1.146.0)
  • 9 MCP Tools + 2 Prompts + 2 Resources (see Tool Reference)
  • Universal OSS + Pro — works without a token; auto-unlocks Pro when SEMGREP_APP_TOKEN is set
  • Multi-arch — native linux/amd64 + linux/arm64
  • Multiple transports — SHTTP (streamable HTTP) + SSE via mcp-proxy (exposed simultaneously)
  • Secure defaults — Alpine base, HAProxy TLS (auto self-signed), Bearer auth, CORS, rate limit, IP ACL
  • HTTP/2 + HTTP/3 (QUIC) — auto-negotiate or explicit version
  • PUID/PGID — non-root with configurable UID/GID
  • Health checks/healthz answered locally by HAProxy (sub-millisecond)
  • Persistent cache — rule registry cached across restarts
Tool Reference
ToolOSSPro
semgrep_scan
semgrep_scan_with_custom_rule
get_abstract_syntax_tree
semgrep_rule_schema
get_supported_languages
semgrep_whoami✅ (returns anonymous)✅ (returns user)
semgrep_findings❌ (auto-disabled)
semgrep_scan_remote❌ (auto-disabled)
semgrep_scan_supply_chain❌ (auto-disabled)

Prompts: write_custom_semgrep_rule, setup_semgrep_mcp

Resources: semgrep://rule/schema, semgrep://rule/{rule_id}/yaml

Supported Architectures

ArchitectureTag PrefixStatus
x86-64amd64-<version>Stable
ARM64arm64v8-<version>Stable

Multi-arch images automatically select the correct architecture for your system.

Available Tags

TagDescription
latestTracks upstream semgrep latest release
stableLast known-good release
1.159.0Specific version
amd64-1.159.0Arch-pinned version
arm64v8-1.159.0Arch-pinned version
System Requirements
  • Docker ≥ 24.0 · Linux kernel ≥ 5.4 (QUIC/HTTP3 UDP)
  • RAM: ≥ 512 MB small codebases; ≥ 2 GB large repos
  • Outbound HTTPS to semgrep.dev + raw.githubusercontent.com for rule registry

Quick Start

services:
  semgrep-mcp:
    image: mekayelanik/semgrep-mcp-server:latest
    container_name: semgrep-mcp
    restart: unless-stopped
    environment:
      PORT: 7055
      PUID: 1000
      PGID: 1000
      TZ: UTC
      PROTOCOL: SHTTP
      ENABLE_HTTPS: "false"
      SEMGREP_RULES: "p/default"
      # Unlock Pro tools:
      # SEMGREP_APP_TOKEN: "${SEMGREP_APP_TOKEN}"
      # Protect endpoint:
      # API_KEY: "your-long-random-token"
    ports:
      - "7055:7055/tcp"
      - "7055:7055/udp"
    volumes:
      - ./code:/code:ro
      - ./custom-rules:/opt/custom-rules:ro
      - semgrep-cache:/home/semgrep/.semgrep
      - semgrep-registry:/home/semgrep/.cache/semgrep

volumes:
  semgrep-cache:
  semgrep-registry:
Docker CLI
docker run -d --name semgrep-mcp --restart unless-stopped \
  -p 7055:7055/tcp -p 7055:7055/udp \
  -e PUID=1000 -e PGID=1000 -e TZ=UTC -e PROTOCOL=SHTTP \
  -v "$PWD/code:/code:ro" -v "$PWD/custom-rules:/opt/custom-rules:ro" \
  -v semgrep-cache:/home/semgrep/.semgrep -v semgrep-registry:/home/semgrep/.cache/semgrep \
  mekayelanik/semgrep-mcp-server:latest
Access Endpoints
TransportEndpoint
SHTTP (streamable HTTP)http://host-ip:7055/mcp
SSEhttp://host-ip:7055/sse
Healthhttp://host-ip:7055/healthz (HAProxy-local)

WebSocket transport was dropped in the migration to mcp-proxy. Setting PROTOCOL=WS will now fail at startup with a clear message. Use SHTTP or SSE instead.

Configuration

Environment Variables
Container Configuration
VariableDefaultDescription
PORT7055External HAProxy port
INTERNAL_PORT37055Internal mcp-proxy port (loopback)
PUID1000User ID for file permissions
PGID1000Group ID for file permissions
TZUTCContainer timezone (TZ database)
PROTOCOLSHTTPTransport: SHTTP / SSE (both exposed simultaneously)
MCP_PROXY_STATELESSfalseKeep false — semgrep requires stateful sessions for reverse-RPC
SEMGREP_MAX_MEM_MB0Virtual memory cap on semgrep child (0 disables)
HAPROXY_FRONTEND_MAXCONN(unset)Cap concurrent connections at HAProxy frontend
HAPROXY_SERVER_MAXCONN(unset)Cap concurrent connections to mcp-proxy backend
Semgrep Configuration
VariableDefaultDescription
SEMGREP_APP_TOKEN(unset)AppSec Platform token — enables Pro tools
REQUIRE_PROfalseIf true, exits 1 when token is missing
SEMGREP_RULESp/defaultDefault ruleset(s) — space-separated list (see Ruleset Selection)
SEMGREP_METRICSoffTelemetry (on / off)
SEMGREP_SEND_METRICSoffTelemetry (on / off)
USE_SEMGREP_RPC(unset)Set true to use RPC backend over pysemgrep CLI
Per-Tool Overrides

Set any of these to true to disable the corresponding MCP tool. In OSS mode, the last three are auto-enabled-as-disabled when SEMGREP_APP_TOKEN is unset.

VariableDefault
SEMGREP_RULE_SCHEMA_DISABLEDfalse
GET_SUPPORTED_LANGUAGES_DISABLEDfalse
SEMGREP_SCAN_WITH_CUSTOM_RULE_DISABLEDfalse
SEMGREP_SCAN_DISABLEDfalse
GET_ABSTRACT_SYNTAX_TREE_DISABLEDfalse
SEMGREP_FINDINGS_DISABLEDauto
SEMGREP_SCAN_REMOTE_DISABLEDauto
SEMGREP_SCAN_SUPPLY_CHAIN_DISABLEDauto
TLS / HTTPS Configuration
VariableDefaultDescription
ENABLE_HTTPSfalseEnable TLS at HAProxy frontend
TLS_CERT_PATH/etc/haproxy/certs/server.crtCustom cert path
TLS_KEY_PATH/etc/haproxy/certs/server.keyCustom key path
TLS_CNlocalhostCommon Name for auto-generated cert
TLS_SANDNS:<TLS_CN>Subject Alternative Name
TLS_DAYS365Self-signed cert validity (days)
TLS_MIN_VERSIONTLSv1.3TLSv1.2 or TLSv1.3
HTTP_VERSION_MODEautoauto / h1 / h2 / h3 / h1+h2 / all
Security Configuration
VariableDefaultDescription
API_KEY(unset)Bearer-token required on all requests except /healthz
CORS(unset)Allowed origins: * / all / comma-separated list
RATE_LIMIT0Max requests per period (0 = disabled)
RATE_LIMIT_PERIOD10sPeriod: 10s / 1m / 1h / 1d
MAX_CONNECTIONS_PER_IP0Concurrent connections cap per source IP
IP_ALLOWLIST(unset)Comma-separated CIDRs (only these may connect)
IP_BLOCKLIST(unset)Comma-separated CIDRs (denied)
HTTPS and HTTP Version Notes
  • ENABLE_HTTPS=false → always HTTP/1.1 (TLS required for HTTP/2 and HTTP/3)
  • HTTP_VERSION_MODE=auto with TLS → negotiates HTTP/1.1 + HTTP/2 + HTTP/3 (QUIC build required)
  • HTTP/3 requires UDP mapping (-p 7055:7055/udp)
API Key Authentication Notes

Clients send Authorization: Bearer <API_KEY>. /healthz bypasses auth. Constant-time validation in HAProxy. Length 5-256 chars; no whitespace/control chars.

User & Group IDs

Find yours: id $USER

Timezone Examples

UTC, America/New_York, Europe/Berlin, Asia/Dhaka, Asia/Tokyo, Australia/Sydney

MCP Client Configuration

Transport Support
TransportURL Format
SHTTPhttp[s]://host:7055/mcp
SSEhttp[s]://host:7055/sse

WebSocket transport was dropped in the migration to mcp-proxy.

CLI-based clients
# Claude Code
claude mcp add --transport http semgrep http://host-ip:7055/mcp
# with auth: append --header "Authorization: Bearer your-token"

# Codex CLI
codex mcp add semgrep --url http://host-ip:7055/mcp --transport http
JSON-config clients (Claude Desktop / Cursor / Windsurf / VS Code)

Config paths: Claude Desktop → ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows) · Cursor → ~/.cursor/mcp.json · Windsurf → ~/.codeium/windsurf/mcp_config.json.

{
  "mcpServers": {
    "semgrep": {
      "transport": { "type": "http", "url": "http://host-ip:7055/mcp" }
    }
  }
}

Key varies per client: Claude Desktop uses transport.url, Cursor uses url, Windsurf uses serverUrl. VS Code (Cline / Roo-Cline): Settings → MCP Servers → Add → Name=semgrep, Transport=http, URL=http://host-ip:7055/mcp.

Testing
npx -y @modelcontextprotocol/inspector http://host-ip:7055/mcp

Pro Engine Mode

Semgrep Pro (AppSec Platform) unlocks 3 additional tools. To enable:

  1. Get a token from semgrep.dev/orgs/-/settings/tokens
  2. Set SEMGREP_APP_TOKEN=<your-token> in compose or via -e
  3. Restart container — entrypoint forwards token to semgrep mcp subprocess (read directly from env every tool call; no semgrep login), auto-downloads Pro Engine binary (~376 MB) if missing, and unlocks semgrep_findings (AppSec platform findings), semgrep_scan_remote (hosted repo scan), semgrep_scan_supply_chain (SCA).

Pro binary auto-install: INSTALL_PRO_ON_START (default true). On 401/403/network fail → OSS fallback with three Pro tools auto-disabled. Binary lives in site-packages, not in /home/semgrep/.semgrep volume — intentional, keeps binary version matched to Semgrep CLI on image upgrade.

Strict mode: REQUIRE_PRO=true — exits 1 if token missing OR Pro install fails. For CI / prod.

OSS mode works without any setup. Default ruleset is p/default (curated by Semgrep); change via SEMGREP_RULES env.

Ruleset Selection

SEMGREP_RULES stacks any number of registry packs, local YAMLs, or HTTPS URLs in a single scan. Wired to upstream -f/--config (Click multiple=True). Rules auto-dedup by ID.

Syntax — space-separated (NOT comma)
SEMGREP_RULES="p/default p/python"
SEMGREP_RULES="p/default p/python p/owasp-top-ten /opt/custom-rules/local_rules.yaml"

Equivalent CLI: semgrep scan --config p/default --config p/python --config /opt/custom-rules/local_rules.yaml

Value types
FormExampleMeaning
p/<pack>p/pythonRegistry ruleset
r/<rule-id>r/python.lang.security.audit.eval-detectedSingle registry rule
Absolute path/opt/custom-rules/local.yamlLocal YAML inside container
HTTPS URLhttps://example.com/rules.yamlRemote YAML
autoautoCloud auto-config (logs project URL)
supply-chainsupply-chainSCA scan (Pro)

Local paths must be absolute + valid inside the container. Mount host rules at /opt/custom-rules → reference as /opt/custom-rules/<file>.yaml. No spaces, no ~ expansion.

Usage
# docker-compose
environment:
  SEMGREP_RULES: "p/default p/python p/django /opt/custom-rules/local_rules.yaml"
volumes:
  - ./my-rules:/opt/custom-rules:ro
# docker CLI
docker run -d -e SEMGREP_RULES="p/default p/python p/owasp-top-ten" \
  -v "$PWD/my-rules:/opt/custom-rules:ro" -p 7055:7055/tcp -p 7055:7055/udp \
  mekayelanik/semgrep-mcp-server:latest

# verify
docker exec semgrep-mcp sh -c 'echo $SEMGREP_RULES'
docker exec semgrep-mcp semgrep scan --dry-run /code 2>&1 | head -20
Curated registry packs (official, by Semgrep)

Top-level: p/default (2852) · p/owasp-top-ten (2283) · p/cwe-top-25 (1452) · p/r2c-security-audit (225, alias p/security-audit) · p/r2c-best-practices (125) · p/secure-defaults (62) · p/r2c-bug-scan (44) · p/comment (1556, noisy)

Languages: p/python (1069) · p/javascript (316) · p/typescript (316) · p/nodejs (248) · p/java (239) · p/csharp (178) · p/golang (113) · p/kotlin (71) · p/ruby (66) · p/swift (64) · p/rust (60) · p/c (53, also C++) · p/php (53) · p/apex (22) · p/ocaml (27) · p/elixir (14) · p/scala (12)

Frameworks: p/expressjs (279) · p/flask (220) · p/django (183) · p/fastapi (152) · p/koa (145) · p/hapi (141) · p/nestjs (31) · p/play (23) · p/brakeman (19, Rails) · p/php-laravel (15) · p/react-best-practices (13) · p/nextjs (6) · p/react (5)

Vulnerability categories: p/sql-injection (320) · p/secrets (269) · p/gitleaks (175) · p/shadow-ai (140) · p/command-injection (100) · p/xss (83) · p/agent-skills (63) · p/security-headers (37) · p/ai-best-practices (27) · p/jwt (25) · p/mcp (19)

IaC / config: p/terraform (63) · p/kubernetes (11) · p/docker / p/dockerfile (7, Hadolint port) · p/docker-compose (6)

Meta / misc: p/c-audit-banned-functions (100, MS banned-fn list) · p/phpcs-security-audit (9) · p/cpp-audit (6) · p/semgrep-rule-lints (6) · p/semgrep-misconfigurations (1)

High-quality community / third-party packs
PackRulesAuthorFocus
p/gitlab544GitLabMulti-language security
p/findsecbugs286Semgrep+GitLabJava security (FindSecBugs port)
p/trailofbits120Trail of BitsPython/Go/Rust audit rules (repo)
p/bandit / p/gitlab-bandit90GitLab+SemgrepPython security (Bandit port)
p/semgrep-go-correctness66Damian GryskiGo correctness (repo)
p/flawfinder64GitLabC security (Flawfinder port)
p/insecure-transport53Colleen DaiCross-language HTTP leaks
p/smart-contracts50DecuritySolidity / Vyper
p/mobsfscan43MobSFAndroid / iOS

p/elttam is NOT a registry shortcut (only hidden ben-elttam.* internal packs exist). Closest equivalent: p/trailofbits + p/r2c-security-audit. Live index: semgrep.dev/api/registry/rulesets · browse: semgrep.dev/explore

Starter combinations
# Python web (Django/Flask/FastAPI)
SEMGREP_RULES="p/default p/python p/django p/flask p/fastapi p/owasp-top-ten p/secrets"
# Node.js / TypeScript
SEMGREP_RULES="p/default p/typescript p/javascript p/nodejs p/expressjs p/owasp-top-ten p/secrets"
# Go
SEMGREP_RULES="p/default p/golang p/semgrep-go-correctness p/owasp-top-ten p/secrets"
# Java / Spring
SEMGREP_RULES="p/default p/java p/findsecbugs p/owasp-top-ten p/secrets"
# Rust
SEMGREP_RULES="p/default p/rust p/trailofbits p/secrets"
# Audit-grade Python
SEMGREP_RULES="p/default p/python p/r2c-security-audit p/trailofbits p/owasp-top-ten p/cwe-top-25"
# IaC (Terraform + K8s + Docker)
SEMGREP_RULES="p/terraform p/kubernetes p/dockerfile p/docker-compose p/secrets"
# Smart contracts
SEMGREP_RULES="p/default p/smart-contracts p/secrets"
# AI / LLM apps
SEMGREP_RULES="p/default p/ai-best-practices p/shadow-ai p/agent-skills p/mcp p/secrets"
Precedence
  • SEMGREP_RULES drives every semgrep_scan / semgrep_scan_remote via env-inherited -f.
  • semgrep_scan_with_custom_rule(code_files, rule) — explicit rule YAML overrides (not appends) SEMGREP_RULES. Stack rules in one rules: list.
  • semgrep_scan_supply_chain — hardcoded --config supply-chain; SEMGREP_RULES ignored.
  • Unset → default p/default. auto → cloud fetch (leaks project URL).

Multi-value note: upstream osemgrep does not split SEMGREP_RULES on whitespace; this image bakes a build-time patch (build_data/patches/fix_mcp_multirule.py, anchor-checked) so multi-value works transparently for all MCP tools.

Code & Rules Directories

Scan target + custom-rules dirs are env-configurable via .env or shell env. Compose auto-loads .env next to docker-compose.yml.

VariableDefaultDescription
CODE_DIR./codeHost path mounted read-only at /code
CUSTOM_RULES_DIR./custom-rulesHost path mounted read-only at /opt/custom-rules
cp .env.example .env
# edit to:
#   CODE_DIR=/home/user/projects/myapp
#   CUSTOM_RULES_DIR=/home/user/semgrep-rules
docker compose up -d
# client: semgrep_scan(path="/code/src/main.py")

# no .env — shell override:
CODE_DIR=/path/to/code docker compose up -d
Custom rules

Clients invoke semgrep_scan_with_custom_rule with inline YAML, OR reference paths under /opt/custom-rules/. See semgrep.dev/docs/writing-rules for syntax.

Network Configuration

Comparison
NetworkIsolationExposed
Bridge (default)Host:7055 → Container:7055
Host (Linux)Direct on container port
MACVLANDedicated IP on LAN
Bridge Network (Default)
services:
  semgrep-mcp:
    image: mekayelanik/semgrep-mcp-server:latest
    ports:
      - "7055:7055/tcp"
      - "7055:7055/udp"

Access: http://localhost:7055/mcp

Host Network (Linux Only)
services:
  semgrep-mcp:
    image: mekayelanik/semgrep-mcp-server:latest
    network_mode: host
MACVLAN Network (Advanced)

See the Docker MACVLAN docs for setup. Give the container its own LAN IP to avoid port collisions.

Updating

Docker Compose
docker compose pull && docker compose up -d
Docker CLI
docker pull mekayelanik/semgrep-mcp-server:latest
docker stop semgrep-mcp && docker rm semgrep-mcp
# re-run with same args
One-Time Update with Watchtower
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower --run-once semgrep-mcp

Troubleshooting

Pre-Flight Checklist
  1. docker ps shows healthy
  2. curl http://host-ip:7055/healthz returns 200
  3. docker logs semgrep-mcp — banner shown, no errors
  4. Outbound: container reaches semgrep.dev + raw.githubusercontent.com
Common Issues
Container Won't Start
  • Port 7055 already bound on host
  • PUID/PGID not positive integers
  • API_KEY wrong length (must be 5–256, no whitespace)
Permission Errors
  • Volume owned by wrong UID: chown -R 1000:1000 ./custom-rules
  • Or match host: PUID=$(id -u) PGID=$(id -g)
Client Cannot Connect
  • Firewall blocking 7055/tcp or 7055/udp
  • TLS on but client uses http:// — switch to https://
  • API_KEY set but client missing Authorization: Bearer ...
  • Wrong endpoint: SHTTP=/mcp, SSE=/sse
Memory & Concurrency Tuning

mcp-proxy runs the Semgrep backend as a single long-lived stdio child and multiplexes all client sessions through it via JSON-RPC ids. This caps the expected memory footprint; the knobs below cap the worst case:

  • MCP_PROXY_STATELESS=false (default) — share one backend child across all sessions. Required for semgrep because semgrep mcp issues server-initiated roots/list reverse-RPC on most tool calls. Stateless mode would respawn the child per POST and break every reverse-RPC tool.
  • SEMGREP_MAX_MEM_MB=4096 — caps the virtual-memory size of the semgrep child via prlimit --as. A runaway scan gets OOM-killed by the kernel before it exhausts the host.
  • HAPROXY_FRONTEND_MAXCONN=64 + HAPROXY_SERVER_MAXCONN=16 — bound concurrent connections at the HAProxy layer so a burst cannot saturate the upstream stdio bridge.
  • /healthz is answered directly by HAProxy with a local 200 — Docker's container healthcheck no longer depends on upstream MCP readiness, so a slow Pro Engine install will not mark the container unhealthy.
Slow First Scan

First scan downloads + caches rule registry. Mount persistent volume (see compose).

Pro Tools Disabled
  • Token unset → three Pro tools auto-disabled
  • Token invalid (401/403) → check logs for Pro Engine install failed; regenerate at semgrep.dev/orgs/-/settings/tokens. Container does NOT run semgrep login — token consumed from env per-call; no login-failed line exists
  • Pro binary install fail (egress blocked / deployment lacks Pro) → OSS fallback. REQUIRE_PRO=true to exit 1
  • INSTALL_PRO_ON_START=false skips the 376 MB download; install manually via docker exec semgrep-mcp semgrep install-semgrep-pro
Debug Information
docker exec semgrep-mcp semgrep --version
docker exec semgrep-mcp semgrep mcp --help
docker logs --tail 200 semgrep-mcp

Additional Resources

Docs: Semgrep · Semgrep MCP upstream · MCP protocol · mcp-proxy

Project: DockerfileModifier · compose example · GitHub repo

Monitoring: /healthz returns 200 (answered locally by HAProxy) — wire into Prometheus blackbox, Uptime Kuma, etc.

😎 Buy Me a Coffee ☕︎

Image saved you time? [Buy me a coffee](https://buymeaco

Tag summary

Content type

Image

Digest

sha256:ca938d6d3

Size

202.2 MB

Last updated

10 days ago

docker pull mekayelanik/semgrep-mcp-server