Dependency Security MCP
# Dependency Security MCP
Dependency Security MCP is a small, production-oriented Model Context Protocol server for OWASP Dependency-Check vulnerability scanning. It exposes three narrow tools:
- `start_dependency_scan`
- `get_scan_status`
- `get_scan_report`
It does not expose shell execution, arbitrary filesystem access, repository mutation, remediation, package manager commands, or unrestricted OWASP CLI arguments.
## Architecture
```mermaid
flowchart TD
LangGraph --> Client[MCP Client]
Client --> Server[Dependency Security MCP]
Server --> ScanService
Server --> ReportService
ScanService --> Scanner[DependencyScanner]
ReportService --> Scanner
Scanner --> OWASP[OWASP Dependency-Check]
```
The MCP layer is intentionally thin. Tool functions validate and serialize request and response data, then call application services. Services depend on repository and scanner abstractions, so a later OSV scanner, dependency resolver, version provider, or remediation service can be added without changing the MCP tool contracts.
## Requirements
- Python 3.12+
- OWASP Dependency-Check CLI available as `dependency-check`
- The official MCP Python SDK v2
Install Python dependencies:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
The project uses the current v2 server style:
```python
from mcp.server import MCPServer
```
## Installing OWASP Dependency-Check
Install Dependency-Check from the OWASP project and ensure the `dependency-check` command is on `PATH`. You can also point the server at a custom executable:
```bash
export OWASP_DEPENDENCY_CHECK_PATH=/opt/dependency-check/bin/dependency-check.sh
```
## Configuration
Copy `.env.example` values into your environment:
```bash
export MCP_SERVER_NAME=dependency-security-mcp
export OWASP_DEPENDENCY_CHECK_PATH=dependency-check
export SCAN_OUTPUT_ROOT=/tmp/dependency-security-mcp
export ALLOWED_REPOSITORY_ROOTS=/workspace/repos
export SCAN_TIMEOUT_SECONDS=1800
export LOG_LEVEL=INFO
```
`ALLOWED_REPOSITORY_ROOTS` is a comma-separated allowlist. Requested repositories are resolved with `Path.resolve()` and must exist, be directories, and live below one configured root.
## Running
Run with stdio for local development:
```bash
uv run mcp dev src/dependency_security_mcp/server.py
```
Run with Streamable HTTP for production-style serving:
```bash
uv run mcp run src/dependency_security_mcp/server.py --transport streamable-http
```
Open the MCP Inspector:
```bash
uv run mcp dev src/dependency_security_mcp/server.py
```
## MCP Tools
### `start_dependency_scan`
Starts an OWASP Dependency-Check scan asynchronously and returns immediately after scheduling.
Request:
```json
{
"repository_path": "/workspace/repos/payment-service",
"project_name": "payment-service"
}
```
Response:
```json
{
"scan_id": "4f8b3dfd-e4f7-40ec-b31f-df3d1992c544",
"scanner": "OWASP_DEPENDENCY_CHECK",
"status": "RUNNING",
"project_name": "payment-service",
"started_at": "2026-10-03T07:00:00Z"
}
```
### `get_scan_status`
Request:
```json
{
"scan_id": "4f8b3dfd-e4f7-40ec-b31f-df3d1992c544"
}
```
Response:
```json
{
"scan_id": "4f8b3dfd-e4f7-40ec-b31f-df3d1992c544",
"status": "COMPLETED",
"scanner": "OWASP_DEPENDENCY_CHECK",
"started_at": "2026-10-03T07:00:00Z",
"completed_at": "2026-10-03T07:03:15Z",
"error": null
}
```
### `get_scan_report`
Running response:
```json
{
"scan_id": "4f8b3dfd-e4f7-40ec-b31f-df3d1992c544",
"status": "RUNNING",
"message": "Scan has not completed yet."
}
```
Completed response:
```json
{
"scan_id": "4f8b3dfd-e4f7-40ec-b31f-df3d1992c544",
"scanner": "OWASP_DEPENDENCY_CHECK",
"project_name": "payment-service",
"repository_path": "/workspace/repos/payment-service",
"status": "COMPLETED",
"dependency_count": 147,
"vulnerable_dependency_count": 3,
"vulnerability_count": 5,
"findings": []
}
```
## Example Client Flow
```python
from mcp import Client
async with Client("http://localhost:8000/mcp") as client:
start = await client.call_tool(
"start_dependency_scan",
{
"repository_path": "/workspace/repos/payment-service",
"project_name": "payment-service",
},
)
scan_id = start.structured_content["scan_id"]
while True:
status = await client.call_tool("get_scan_status", {"scan_id": scan_id})
if status.structured_content["status"] in {"COMPLETED", "FAILED", "CANCELLED"}:
break
report = await client.call_tool("get_scan_report", {"scan_id": scan_id})
print(report.structured_content)
```
## Docker
Build:
```bash
docker build -t dependency-security-mcp .
```
Run with a read-only repository mount:
```bash
docker run --rm \
-v /repos:/workspace/repos:ro \
-e ALLOWED_REPOSITORY_ROOTS=/workspace/repos \
dependency-security-mcp
```
The image runs as a non-root user and writes scan output under `SCAN_OUTPUT_ROOT`.
## Security Model
- Never executes arbitrary user commands.
- Never uses `shell=True`.
- Validates repository paths by resolving them before allowlist checks.
- Restricts scans to configured roots.
- Server controls output directories.
- Enforces scan timeouts and process termination.
- Validates scanner exit codes.
- Returns structured client-safe errors.
- Does not expose environment variables, raw shell access, arbitrary filesystem APIs, or arbitrary OWASP arguments.
The caller decides when and which allowed repository to scan. The MCP server decides whether the path is allowed, which executable and arguments are used, where reports are stored, how long the scan may run, and how reports are normalized.
## Tests
Run unit tests:
```bash
pytest
```
Run lint:
```bash
ruff check .
```
The real OWASP integration test is opt-in:
```bash
RUN_OWASP_INTEGRATION_TESTS=true pytest tests/integration
```
## Roadmap
The architecture is ready for additional narrow capabilities later:
- Maven, Gradle, and npm dependency resolution
- OSV scanning
- Dependency version lookup
- Suggested dependency fixes
- Controlled dependency updates
- Build/test verification
Those capabilities are intentionally not implemented in this first version.
TDQS
Scored across 3 tools
Each tool targets a distinct phase of the scan lifecycle: start a scan, poll its status, and retrieve the completed report. There is no functional overlap, and the descriptions clearly differentiate status polling from report retrieval.
All tool names follow a consistent snake_case verb_noun pattern (start_dependency_scan, get_scan_status, get_scan_report). The shared 'get_scan_' prefix for the two retrieval tools reinforces predictability.
Three tools are well-scoped for a focused dependency scanning service: initiate, monitor, and fetch results. The set contains no redundant or filler tools, and the count sits comfortably in the typical 3-15 range.
The core lifecycle (start → status → report) is fully covered, but there is no way to list existing scans or cancel a running scan. These are minor gaps that agents can work around by retaining scan IDs, but they limit multi-scan management.