Trailmark MCP Server
# Trailmark MCP Server
Trailmark MCP Server is a standalone MCP wrapper around [railofbits/trailmark][trail].
While I do understand the ToB's usage with [Claude skills][skill], my usecase
requires an MCP server that can analyze and server multiple graphs. The server
can scan multiple repositories and the LLM can request information from each
separately.
[trail]: https://github.com/trailofbits/skills/tree/main/plugins/trailmark
[skill]: https://github.com/trailofbits/skills/tree/main/plugins/trailmark
Mostly created with OpenAI GPT-5.5 via Github Copilot in VS Code. Point your LLM
to the [ai-docs](/ai-docs/) directory for documentation and development support.
## Requirements
* Python 3.12+
* `uv`
Project metadata:
* package name: `trailmark-mcp`
* CLI command: `trailmark-mcp`
## Install
Install runtime and development dependencies:
```bash
uv sync --group dev
```
## Quick Start
Start server over stdio:
```bash
uv run trailmark-mcp serve --transport stdio
```
Smoke-test direct scan path without an MCP client:
```bash
uv run trailmark-mcp scan /path/to/repo
```
Skip preanalysis during scan when needed:
```bash
uv run trailmark-mcp scan /path/to/repo --skip-preanalysis
```
## How The Server Works
Primary lifecycle entrypoint is `open_repository(...)`.
Behavior summary:
* if no snapshot exists, the server scans source, optionally runs preanalysis, and saves the first snapshot
* if a snapshot exists and `rescan=False`, the server reloads the latest snapshot into a live session
* if `rescan=True`, the server rebuilds from source and saves a fresh snapshot
This means the common flow is:
1. call `open_repository`
2. use graph tools against returned session
3. call `save_snapshot` after meaningful in-memory mutations when you want persistence
## Session Model
`session_id` is MCP wrapper state, not Trailmark core state.
Current semantics:
* each `open_repository(...)` call creates a new session id
* multiple live sessions can coexist
* tools accept `session_id` to target a specific graph
* omitted `session_id` uses the most recently opened still-open session
* closing the default session promotes the most recently opened remaining session
Use `current_repository(session_id=...)` to verify which repository a session points to.
## Public MCP Tools
Lifecycle:
* `open_repository`
* `current_repository`
* `close_repository`
* `save_snapshot`
Navigation:
* `graph_summary`
* `diff_graphs`
* `search_nodes`
* `callers_of`
* `callees_of`
* `ancestors_of`
* `reachable_from`
* `paths_between`
* `entrypoint_paths_to`
* `attack_surface`
* `complexity_hotspots`
* `functions_that_raise`
Context and mutation:
* `subgraph`
* `annotations_of`
* `findings`
* `nodes_with_annotation`
* `run_preanalysis`
* `annotate_node`
* `clear_annotations`
* `augment_findings`
Notes:
* `diff_graphs(before_session_id, after_session_id)` treats `after` as the new state
* `search_nodes` supports `contains`, `exact`, and `suffix`
* removed helper surfaces like `scan_repository` and `tool_manifest` are intentionally not part of the public runtime anymore
## Snapshot Behavior
Snapshots are written under the analyzed repository, not under this server repository:
```text
<target-repo>/.trailmark/snapshots/<timestamp>/
```
Current snapshot artifacts include:
* `graph.json`
* `summary.json`
* `entrypoints.json`
* `hotspots.json`
* `subgraphs.json`
* `scan-metadata.json`
Snapshots support reload into a live session. Use `rescan=True` when you explicitly need a fresh rebuild from source.
## Repository Layout
Key files:
* `src/trailmark_mcp/cli.py`: CLI entrypoint for `scan` and `serve`
* `src/trailmark_mcp/mcp_app.py`: MCP tool registration
* `src/trailmark_mcp/tool_catalog.py`: declarative metadata for exposed tools
* `src/trailmark_mcp/services/registry.py`: session tracking
* `src/trailmark_mcp/services/runtime.py`: main Trailmark-backed runtime behavior
## Development
Run focused test suite:
```bash
uv run --group dev pytest tests/test_tool_catalog.py tests/test_registry.py tests/test_stdio_server.py
```
Current CI runs that same focused suite on Python 3.12.
Extension rule:
1. add or change runtime behavior
2. register tool in `mcp_app.py`
3. update metadata in `tool_catalog.py`
4. update tests
5. update docs if public behavior changed
## Use In VS Code
VS Code can launch this server directly through MCP using a workspace-level `mcp.json` file.
Typical setup:
1. open this repository in VS Code
2. make sure dependencies are installed with `uv sync --group dev`
3. keep the server definition in `.vscode/mcp.json`
4. let the MCP client start the server over `stdio`
This repository already includes `.vscode/mcp.json` for local use.
Example `mcp.json`:
```json
{
"servers": {
"trailmark-mcp": {
"type": "stdio",
"command": "uv",
"args": [
"run",
"trailmark-mcp",
"serve",
"--transport",
"stdio"
]
}
}
}
```
If you use this server from a larger multi-project workspace, copy the same definition into that workspace root's `.vscode/mcp.json` and make sure the command runs in an environment where `uv` and this project are available.
TDQS
Scored across 24 tools
Each tool targets a distinct operation: graph traversal (ancestors_of, callees_of, callers_of, reachable_from), annotation management (annotate_node, annotations_of, clear_annotations, nodes_with_annotation), repository lifecycle (open_repository, close_repository, current_repository, save_snapshot), and analysis (attack_surface, complexity_hotspots, findings, etc.). No two tools have overlapping purposes.
Tool names follow two predictable patterns: query-like operations use noun_of (e.g., ancestors_of, callees_of, callers_of) and action operations use verb_noun (e.g., annotate_node, open_repository, save_snapshot). This pattern is consistent throughout the set, with no mixing of casing styles or ambiguous verbs.
With 24 tools, the count is slightly above the typical well-scoped range (3-15) but still appropriate for a comprehensive code analysis server covering graph traversal, annotations, repository management, and analysis features. The tools are logically grouped and each serves a distinct purpose, so the number does not feel excessive.
The tool surface covers core workflows: opening/closing repositories, scanning, traversing the call graph, managing annotations, and querying findings. Minor gaps exist, such as no tool to list all repositories (only current_repository) or to delete nodes/annotations, but these do not critically hinder typical use cases for a static analysis tool.