# Mcts Scoring

> Security audit of an MCP (Model Context Protocol) server via the MCTS scanner (Model Context Threat Scanner, `mcp-mcts`). Produces a report with legacy + v2 scoring (8 risk dimensions, per-finding contributions, attack chains, baseline for regression detection). Distinguishes heuristic false positives from real risks and proposes pragmatic mitigations (confirmation tokens for destructive tools, local-path validation, read/egress isolation). Triggers (EN): "MCTS audit", "MCP security scan", "audit my MCP server", "MCTS score", "MCP threat scan", "check MCP risks", "run MCTS on", "mcp-mcts", "MCTS findings", "MCTS baseline", before publishing an MCP server (PyPI/npm). Triggers (FR): "audit MCTS", "scan sécurité MCP", "auditer mon serveur MCP", "score MCTS", "vérifier les risques de mon MCP", "passer MCTS sur", "mcts baseline", avant publication d'un serveur MCP.

- Skill: `onyxyno/mcts-scoring` (Agent Skill)
- Install (CLI): `npx skillmds@latest add onyxyno/mcts-scoring`
- Raw SKILL.md: https://api.skillmd.com/api/skills/onyxyno/mcts-scoring/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: OnyxynO (https://skillmd.com/u/onyxyno)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/onyxyno/mcts-scoring

---


# Skill — MCP security audit via MCTS

## When to use

- Before publishing an MCP server (PyPI / npm / Anthropic registry)
- As a CI gate on any repo that exposes MCP tools
- After adding a new MCP tool — detect regressions via baseline
- When the user asks "is my MCP server safe?"
- Before installing a third-party MCP (`mcts vet pypi:<package>`)

## What MCTS scans

MCTS is a **static heuristic analyzer** on the MCP server side. It models the following threats:

1. **Destructive tools without safeguards** (`delete`, `drop`, `rm`, `exec`…)
2. **Prompt injection** via tool descriptions (tool poisoning)
3. **Attack chains**: combinations of innocent tools → exfiltration or execution
4. **Excessive Agency** (OWASP LLM06): overly broad permissions
5. **Path traversal, command injection, plain-text secrets** in tool code

> ⚠️ The scanner does **not** read function logic — it flags on names, imports, and structure. Many findings are structural false positives to interpret with care.

## Installation and invocation

```bash
# Recommended isolated install (does not touch the project venv)
uvx --from mcp-mcts mcts scan <server_path.py>

# Or global install
pipx install mcp-mcts
uv tool install mcp-mcts
```

PyPI distribution: **`mcp-mcts`** (the short name `mcts` is taken).
Executables provided: `mcts` and `mcts-mcp`.

## Standard workflow

### 1. Locate the MCP entrypoint
```bash
# Python — look for FastMCP, @mcp.tool, mcp.server
grep -rn "FastMCP\|@mcp.tool\|mcp.server" --include="*.py" <project>

# TypeScript/JS — look for McpServer or createServer
grep -rn "McpServer\|@modelcontextprotocol/sdk/server" --include="*.ts" <project>
```

### 2. First scan
```bash
cd <project> && uvx --from mcp-mcts mcts scan <entrypoint> \
  --format json --output /tmp/mcts-<project>.json
```

HTML/SARIF reports are written to `<project>/mcts_analysis/` (add to `.gitignore`).

### 3. Read the score
```bash
jq '{summary, score, score_v2: {absolute_risk: .score_v2.absolute_risk, security_score: .score_v2.security_score, risk_percentile: .score_v2.risk_percentile, dimension_scores: .score_v2.dimension_scores}}' /tmp/mcts-<project>.json
```

### 4. Detail critical and high findings
```bash
jq '.findings[] | select(.severity == "critical")' /tmp/mcts-<project>.json
jq '.findings[] | select(.severity == "high")'     /tmp/mcts-<project>.json
```

### 5. Capture baseline (for future regression detection)
```bash
uvx --from mcp-mcts mcts scan <entrypoint> --save-baseline mcts_baseline.json
```

Commit the baseline. Future scans use `--baseline mcts_baseline.json` to report only **new** findings.

## Interpreting the two scores

| Metric | Reading |
|---|---|
| **Overall (legacy)** /100 | School-grade score weighted by severity × count |
| **Risk Index (legacy)** /100 | Inverse — higher is worse |
| **Absolute Risk (v2)** | Absolute risk summed and weighted across 8 dimensions |
| **Security Score (v2)** /100 | Benchmark vs public corpus (`corpus-YYYY-MM`) |
| **Risk percentile** | "Riskier than X% of MCP servers in the corpus" |

### The 8 dimensions (score_v2)

| Dimension | Meaning |
|---|---|
| **reachability** | How accessible the tool is from the agent |
| **blast_radius** | Damage scope if exploited |
| **business_impact** | Importance of data/systems touched |
| **exploitability** | Ease of exploitation (trivial names, no guard) |
| **asset_value** | Value of what is protected |
| **attack_preconditions** | Weak preconditions = high risk |
| **exposure** | Exposure surface (local-only vs networked) |
| **threat_maturity** | Maturity of the MCP attack ecosystem |

For a **local-only single-user** MCP, `reachability=98` and `blast_radius=100` are structurally high — what matters is **exploitability** and specific findings.

## Common findings and pragmatic mitigations

| Finding | Pattern | Mitigation |
|---|---|---|
| **MCTS-T-1006** Destructive tool | Tool named `delete`/`drop`/`reset` | Mandatory `confirm_id` parameter that must equal the id to delete |
| **MCTS-T-1005** Read→exfil chain | `search/get/read` tools + `import/send/upload` tools | Whitelist paths (`urlparse.scheme not in URL_SCHEMES`), require absolute local paths |
| **MCTS-T-1002** Path traversal | File-access tool with `filepath` param | Validate and canonicalize paths; restrict to an allowlisted root |
| **MCTS-T-1001** Excessive description length | Very long docstring on a tool | Shorten the `@mcp.tool()` docstring (move details elsewhere) |
| **MCTS-T-1001** Security-sensitive ops | Tool performs disk or network I/O | Often false positive if I/O is strictly local and necessary — document as accepted |
| **behavioral-sink-*-urllib-open-fs** | `urllib`/`pathlib`/`open` imports in the server module | Frequent false positive — isolate I/O utilities into a separate module to avoid contaminating tools |
| **meta-excessive-desc-tool:** | Tool with description over threshold | Same as T-1001, shorten |

## Recommended helper (Python — local path validation)

```python
from pathlib import Path
from urllib.parse import urlparse

def _validate_local_path(path: str) -> str | None:
    """Return an error message if path is not safe, otherwise None."""
    parsed = urlparse(path)
    if parsed.scheme and parsed.scheme not in ("", "file"):
        return f"scheme '{parsed.scheme}' not allowed"
    p = Path(path).expanduser()
    if not p.is_absolute():
        return f"relative path rejected: {path}"
    if not p.exists():
        return f"path not found: {p}"
    return None
```

Apply at the top of every tool that accepts a `path: str` parameter (import, upload, file read).

## Recommended helper (delete with confirmation)

```python
@mcp.tool()
def delete(id: int, confirm_id: int) -> dict:
    """Delete a record. `confirm_id` must equal `id` to confirm."""
    if confirm_id != id:
        return {"error": f"confirmation required: confirm_id ({confirm_id}) != id ({id})"}
    return _service.delete(id)
```

## Known scanner limitations

- **Does not read function logic**: `delete(id, confirm_id)` still flags as `MCTS-T-1006` even after adding the guard — heuristic purely based on tool name
- **Attack chain false positives**: `search` + `import_local_file` is flagged as `read→exfil` even when the import is strictly local
- **`behavioral-sink-*-urllib-open-fs` appears on all tools** if the server module imports `urllib`/`pathlib`/`open`, by structural contamination

Consequence: the **raw score** does not improve with fixes — the baseline is the relevant tool for catching **regressions** (new tools, new attack chains).

## Suggested CI gate

```yaml
# .github/workflows/mcts.yml
- name: MCTS audit
  run: |
    uvx --from mcp-mcts mcts scan src/<package>/mcp/server.py \
      --baseline mcts_baseline.json --max-absolute-risk 700
```

`--max-absolute-risk` fails the build if the risk exceeds the threshold. Tune to project profile (personal local-only: 700 OK; multi-user enterprise: aim for 200).

## Report template

```
## MCTS audit — <project> (<date>)

**Score**: overall <X>/100 · absolute_risk <Y> (<level>) · security <Z>/100 · <percentile>th percentile

**Critical (<n>)**
- <finding_id>: <title> — <pragmatic mitigation>

**Significant medium (<n>)**
- ... (filter out structural false positives)

**Identified false positives**
- behavioral-sink-* × <n>: contamination by urllib/pathlib imports — accepted
- ...

**Concrete actions**
1. <code fix> — file:line
2. <code fix>
3. Capture baseline `mcts_baseline.json`

**Accepted residual risk**
- <finding_id>: why we accept it (local-only context, single-user…)
```

## See also

- MCTS repo: https://github.com/MCP-Audit/MCTS
- OWASP LLM Top 10 (mapping included in reports)
- OWASP MCP Top 10 (mapping included)
- Complementary skill: `audit-securite` (general web app security, not MCP-specific)

