mcp-latchpoint 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_latchpoint-0.1.0/.github/workflows/ci.yml +41 -0
- mcp_latchpoint-0.1.0/.github/workflows/publish.yml +68 -0
- mcp_latchpoint-0.1.0/.gitignore +11 -0
- mcp_latchpoint-0.1.0/CHANGELOG.md +8 -0
- mcp_latchpoint-0.1.0/CONTRIBUTING.md +15 -0
- mcp_latchpoint-0.1.0/LICENSE +21 -0
- mcp_latchpoint-0.1.0/PKG-INFO +190 -0
- mcp_latchpoint-0.1.0/README.md +140 -0
- mcp_latchpoint-0.1.0/SECURITY.md +17 -0
- mcp_latchpoint-0.1.0/examples/codex-config.toml +7 -0
- mcp_latchpoint-0.1.0/examples/risky.json +18 -0
- mcp_latchpoint-0.1.0/examples/safe.json +19 -0
- mcp_latchpoint-0.1.0/glama.json +4 -0
- mcp_latchpoint-0.1.0/pyproject.toml +64 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/__init__.py +6 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/cli.py +103 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/discovery.py +67 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/jsonc.py +73 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/mcp_server.py +78 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/model.py +147 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/output.py +163 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/parser.py +289 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/py.typed +1 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/rules.py +501 -0
- mcp_latchpoint-0.1.0/src/mcp_latchpoint/scanner.py +130 -0
- mcp_latchpoint-0.1.0/tests/conftest.py +22 -0
- mcp_latchpoint-0.1.0/tests/test_mcp_server.py +93 -0
- mcp_latchpoint-0.1.0/tests/test_output_cli.py +92 -0
- mcp_latchpoint-0.1.0/tests/test_parser.py +140 -0
- mcp_latchpoint-0.1.0/tests/test_paths.py +75 -0
- mcp_latchpoint-0.1.0/tests/test_rules.py +165 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
strategy:
|
|
13
|
+
fail-fast: false
|
|
14
|
+
matrix:
|
|
15
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
16
|
+
python-version: ["3.11", "3.13"]
|
|
17
|
+
runs-on: ${{ matrix.os }}
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
cache: pip
|
|
24
|
+
- run: python -m pip install --upgrade pip
|
|
25
|
+
- run: python -m pip install -e ".[dev]"
|
|
26
|
+
- run: pytest --cov=mcp_latchpoint --cov-report=term --cov-fail-under=80
|
|
27
|
+
|
|
28
|
+
quality:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.13"
|
|
35
|
+
cache: pip
|
|
36
|
+
- run: python -m pip install --upgrade pip
|
|
37
|
+
- run: python -m pip install -e ".[dev]"
|
|
38
|
+
- run: ruff check .
|
|
39
|
+
- run: mypy
|
|
40
|
+
- run: python -m pip check
|
|
41
|
+
- run: python -m pip wheel . --no-deps --wheel-dir dist
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: pypi-${{ github.event.release.tag_name }}
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
if: ${{ !github.event.release.prerelease }}
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.13"
|
|
25
|
+
- name: Check release tag and branch
|
|
26
|
+
env:
|
|
27
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
28
|
+
run: |
|
|
29
|
+
python - <<'PY'
|
|
30
|
+
import os
|
|
31
|
+
import tomllib
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
with Path("pyproject.toml").open("rb") as source:
|
|
35
|
+
version = tomllib.load(source)["project"]["version"]
|
|
36
|
+
expected = f"v{version}"
|
|
37
|
+
if os.environ["RELEASE_TAG"] != expected:
|
|
38
|
+
raise SystemExit(f"Release tag must be {expected}")
|
|
39
|
+
PY
|
|
40
|
+
git fetch --no-tags origin main
|
|
41
|
+
git merge-base --is-ancestor "$GITHUB_SHA" origin/main
|
|
42
|
+
- name: Test and build distributions
|
|
43
|
+
run: |
|
|
44
|
+
python -m pip install -e ".[dev]" build twine
|
|
45
|
+
python -m pytest -q
|
|
46
|
+
python -m build
|
|
47
|
+
python -m twine check dist/*
|
|
48
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
49
|
+
with:
|
|
50
|
+
name: python-distributions
|
|
51
|
+
path: dist/
|
|
52
|
+
if-no-files-found: error
|
|
53
|
+
|
|
54
|
+
publish:
|
|
55
|
+
needs: build
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
environment:
|
|
58
|
+
name: pypi
|
|
59
|
+
url: https://pypi.org/project/mcp-latchpoint/
|
|
60
|
+
permissions:
|
|
61
|
+
id-token: write
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
64
|
+
with:
|
|
65
|
+
name: python-distributions
|
|
66
|
+
path: dist/
|
|
67
|
+
- name: Publish distributions to PyPI
|
|
68
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Add bounded JSON, JSONC, and Codex TOML parsing for documented MCP client layouts.
|
|
6
|
+
- Add seven deterministic configuration rules with redacted evidence.
|
|
7
|
+
- Add text, JSON, and SARIF output with severity-based CI exits.
|
|
8
|
+
- Add a root-confined, read-only MCP stdio server.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Keep changes small enough to review and add tests for parser, rule, or containment behavior. Rule evidence must be deterministic and must not include credential values.
|
|
4
|
+
|
|
5
|
+
Before opening a pull request, run:
|
|
6
|
+
|
|
7
|
+
```console
|
|
8
|
+
pytest
|
|
9
|
+
ruff check .
|
|
10
|
+
mypy
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
New rules need a stable ID, a concrete risk, a remediation, positive and negative fixtures, and a clear statement of uncertainty. Avoid rules that infer server behavior from a package name alone.
|
|
14
|
+
|
|
15
|
+
Use synthetic configurations in tests and examples. Follow `SECURITY.md` for anything that could expose private data.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robert Vind-Gardoș
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mcp-latchpoint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Offline-first auditor for MCP client configuration files
|
|
5
|
+
Project-URL: Repository, https://github.com/robyroro/mcp-latchpoint
|
|
6
|
+
Project-URL: Issues, https://github.com/robyroro/mcp-latchpoint/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/robyroro/mcp-latchpoint/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Robert Vind-Gardoș
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 Robert Vind-Gardoș
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: audit,configuration,mcp,security
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Environment :: Console
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
40
|
+
Classifier: Topic :: Security
|
|
41
|
+
Requires-Python: >=3.11
|
|
42
|
+
Requires-Dist: mcp<3,>=2.3
|
|
43
|
+
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: jsonschema<5,>=4.23; extra == 'dev'
|
|
45
|
+
Requires-Dist: mypy<2,>=1.15; extra == 'dev'
|
|
46
|
+
Requires-Dist: pytest-cov<8,>=6; extra == 'dev'
|
|
47
|
+
Requires-Dist: pytest<10,>=8.3; extra == 'dev'
|
|
48
|
+
Requires-Dist: ruff<1,>=0.11; extra == 'dev'
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
|
|
51
|
+
# mcp-latchpoint
|
|
52
|
+
|
|
53
|
+
`mcp-latchpoint` audits MCP client configuration files without running the configured servers. It works offline, reads only local files you select, and produces text, JSON, or SARIF results.
|
|
54
|
+
|
|
55
|
+
This is an early defensive tool. Review findings in context before changing a working configuration.
|
|
56
|
+
|
|
57
|
+
## What it checks
|
|
58
|
+
|
|
59
|
+
The v0.1 rules cover:
|
|
60
|
+
|
|
61
|
+
- plain HTTP for non-loopback remote endpoints
|
|
62
|
+
- shell wrappers and inline shell control syntax
|
|
63
|
+
- identifiable `npx` and `uvx` packages without exact versions
|
|
64
|
+
- literal credentials in environment values, headers, arguments, or URLs
|
|
65
|
+
- filesystem roots and home roots passed as recognizable access scopes
|
|
66
|
+
- wildcard hosts and recognizable wildcard scopes
|
|
67
|
+
- conflicting, missing, invalid, or unsupported transport settings
|
|
68
|
+
|
|
69
|
+
Every finding has a stable rule ID, severity, location, remediation, redacted evidence, and a confidence note when interpretation depends on the launched server.
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
Python 3.11 or newer is required.
|
|
74
|
+
|
|
75
|
+
```console
|
|
76
|
+
python -m venv .venv
|
|
77
|
+
# Linux or macOS
|
|
78
|
+
. .venv/bin/activate
|
|
79
|
+
# Windows PowerShell
|
|
80
|
+
.venv\Scripts\Activate.ps1
|
|
81
|
+
python -m pip install .
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
For development:
|
|
85
|
+
|
|
86
|
+
```console
|
|
87
|
+
python -m pip install -e ".[dev]"
|
|
88
|
+
pytest
|
|
89
|
+
ruff check .
|
|
90
|
+
mypy
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The MCP server uses the official Python SDK v2 and the dependency is constrained to `mcp>=2.3,<3`.
|
|
94
|
+
|
|
95
|
+
## CLI
|
|
96
|
+
|
|
97
|
+
Scan one or more explicit files:
|
|
98
|
+
|
|
99
|
+
```console
|
|
100
|
+
mcp-latchpoint scan ~/.config/Code/User/mcp.json
|
|
101
|
+
mcp-latchpoint scan examples/risky.json --format json
|
|
102
|
+
mcp-latchpoint scan examples/risky.json --format sarif --fail-on high > results.sarif
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Restrict every explicit path to an approved directory:
|
|
106
|
+
|
|
107
|
+
```console
|
|
108
|
+
mcp-latchpoint scan ./configs --allowed-root ./configs
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Directories are searched only for recognized MCP config filenames, up to 256 files. A file is limited to 1 MiB by default. Change these bounds with `--max-files` and `--max-bytes`.
|
|
112
|
+
|
|
113
|
+
Exit codes are `0` for a completed scan below the chosen threshold, `1` when a finding meets `--fail-on`, and `2` for input, containment, or parse errors. The default `--fail-on none` reports findings without failing a build.
|
|
114
|
+
|
|
115
|
+
List and explain rules:
|
|
116
|
+
|
|
117
|
+
```console
|
|
118
|
+
mcp-latchpoint rules
|
|
119
|
+
mcp-latchpoint explain MCP004
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Recognized layouts
|
|
123
|
+
|
|
124
|
+
Explicit files may use these structures:
|
|
125
|
+
|
|
126
|
+
| Client layout | Container | Accepted file syntax |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| Claude Desktop, Cursor, portable `.mcp.json`, generic MCP JSON | top-level `mcpServers` object | JSON |
|
|
129
|
+
| Claude Code user/local settings | top-level `mcpServers`, plus `projects.<path>.mcpServers` in `~/.claude.json` | JSON |
|
|
130
|
+
| VS Code | top-level `servers` object | JSONC for `.vscode/mcp.json` |
|
|
131
|
+
| Codex | `[mcp_servers.<name>]` tables | TOML |
|
|
132
|
+
|
|
133
|
+
Files ending in `.jsonc` are also parsed as JSONC. Other `.json` files remain strict JSON so malformed input is not silently accepted.
|
|
134
|
+
|
|
135
|
+
`--discover` checks only the following paths when they exist. It does not search the rest of the home directory.
|
|
136
|
+
|
|
137
|
+
- All systems: `~/.claude.json`, `~/.cursor/mcp.json`, `~/.codex/config.toml`, `$COPILOT_HOME/mcp-config.json` with `~/.copilot/mcp-config.json` as the fallback
|
|
138
|
+
- Current project: `.mcp.json`, `.codex/config.toml`, `.cursor/mcp.json`, `.vscode/mcp.json`
|
|
139
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`, `%APPDATA%\Code\User\mcp.json`
|
|
140
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`, `~/Library/Application Support/Code/User/mcp.json`
|
|
141
|
+
- Linux: `$XDG_CONFIG_HOME/Code/User/mcp.json`, falling back to `~/.config/Code/User/mcp.json`
|
|
142
|
+
|
|
143
|
+
## Read-only MCP server
|
|
144
|
+
|
|
145
|
+
The stdio server exposes two tools: `list_rules` and `scan`. It requires an allowed root at startup. Relative scan paths are resolved below that root; absolute paths, `..` traversal, and symlinks cannot escape it.
|
|
146
|
+
|
|
147
|
+
```console
|
|
148
|
+
mcp-latchpoint-server --root /absolute/path/to/reviewed-configs
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Example client entry:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"mcpServers": {
|
|
156
|
+
"latchpoint": {
|
|
157
|
+
"command": "/absolute/path/to/mcp-latchpoint-server",
|
|
158
|
+
"args": ["--root", "/absolute/path/to/reviewed-configs"]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The server returns findings and scan metadata, never raw configuration content. Stdio is the only server transport exposed by the entry point, and stdout is reserved for MCP protocol messages.
|
|
165
|
+
|
|
166
|
+
### Glama build
|
|
167
|
+
|
|
168
|
+
The [Glama listing](https://glama.ai/mcp/servers/robyroro/mcp-latchpoint) builds a container from the repository. In its Dockerfile configuration, use Python 3.13, these build steps and command arguments:
|
|
169
|
+
|
|
170
|
+
```json
|
|
171
|
+
["uv sync --no-dev"]
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
["mcp-proxy", "--", "/app/.venv/bin/mcp-latchpoint-server", "--root", "/app/examples"]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The root is an existing directory with synthetic sample configurations. It lets Glama start and inspect the tools without giving the server access to a user's files. The command uses the executable inside the virtual environment created by `uv sync`. For this demo, the environment-variable schema can be `{"type":"object","properties":{}}` and placeholder parameters can be `{}`.
|
|
179
|
+
|
|
180
|
+
To scan your own configurations, run the server locally with `--root` pointing to a directory you explicitly trust. Glama's demo root is only for the sample files in `examples/`.
|
|
181
|
+
|
|
182
|
+
## Safety and limitations
|
|
183
|
+
|
|
184
|
+
The scanner never executes commands, installs packages, resolves referenced environment variables, or connects to endpoints. It does not follow configuration includes or inspect an MCP server's code or runtime behavior. Argument-based rules are intentionally limited to recognizable patterns, so custom flags can be missed. A clean report is not proof that a server is safe.
|
|
185
|
+
|
|
186
|
+
Secret detection is designed to emit field names and `<redacted>` markers rather than values. If you find a leak or a path-containment problem, follow [SECURITY.md](SECURITY.md) and do not attach a real configuration to a public issue.
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# mcp-latchpoint
|
|
2
|
+
|
|
3
|
+
`mcp-latchpoint` audits MCP client configuration files without running the configured servers. It works offline, reads only local files you select, and produces text, JSON, or SARIF results.
|
|
4
|
+
|
|
5
|
+
This is an early defensive tool. Review findings in context before changing a working configuration.
|
|
6
|
+
|
|
7
|
+
## What it checks
|
|
8
|
+
|
|
9
|
+
The v0.1 rules cover:
|
|
10
|
+
|
|
11
|
+
- plain HTTP for non-loopback remote endpoints
|
|
12
|
+
- shell wrappers and inline shell control syntax
|
|
13
|
+
- identifiable `npx` and `uvx` packages without exact versions
|
|
14
|
+
- literal credentials in environment values, headers, arguments, or URLs
|
|
15
|
+
- filesystem roots and home roots passed as recognizable access scopes
|
|
16
|
+
- wildcard hosts and recognizable wildcard scopes
|
|
17
|
+
- conflicting, missing, invalid, or unsupported transport settings
|
|
18
|
+
|
|
19
|
+
Every finding has a stable rule ID, severity, location, remediation, redacted evidence, and a confidence note when interpretation depends on the launched server.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
Python 3.11 or newer is required.
|
|
24
|
+
|
|
25
|
+
```console
|
|
26
|
+
python -m venv .venv
|
|
27
|
+
# Linux or macOS
|
|
28
|
+
. .venv/bin/activate
|
|
29
|
+
# Windows PowerShell
|
|
30
|
+
.venv\Scripts\Activate.ps1
|
|
31
|
+
python -m pip install .
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
For development:
|
|
35
|
+
|
|
36
|
+
```console
|
|
37
|
+
python -m pip install -e ".[dev]"
|
|
38
|
+
pytest
|
|
39
|
+
ruff check .
|
|
40
|
+
mypy
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The MCP server uses the official Python SDK v2 and the dependency is constrained to `mcp>=2.3,<3`.
|
|
44
|
+
|
|
45
|
+
## CLI
|
|
46
|
+
|
|
47
|
+
Scan one or more explicit files:
|
|
48
|
+
|
|
49
|
+
```console
|
|
50
|
+
mcp-latchpoint scan ~/.config/Code/User/mcp.json
|
|
51
|
+
mcp-latchpoint scan examples/risky.json --format json
|
|
52
|
+
mcp-latchpoint scan examples/risky.json --format sarif --fail-on high > results.sarif
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Restrict every explicit path to an approved directory:
|
|
56
|
+
|
|
57
|
+
```console
|
|
58
|
+
mcp-latchpoint scan ./configs --allowed-root ./configs
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Directories are searched only for recognized MCP config filenames, up to 256 files. A file is limited to 1 MiB by default. Change these bounds with `--max-files` and `--max-bytes`.
|
|
62
|
+
|
|
63
|
+
Exit codes are `0` for a completed scan below the chosen threshold, `1` when a finding meets `--fail-on`, and `2` for input, containment, or parse errors. The default `--fail-on none` reports findings without failing a build.
|
|
64
|
+
|
|
65
|
+
List and explain rules:
|
|
66
|
+
|
|
67
|
+
```console
|
|
68
|
+
mcp-latchpoint rules
|
|
69
|
+
mcp-latchpoint explain MCP004
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Recognized layouts
|
|
73
|
+
|
|
74
|
+
Explicit files may use these structures:
|
|
75
|
+
|
|
76
|
+
| Client layout | Container | Accepted file syntax |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| Claude Desktop, Cursor, portable `.mcp.json`, generic MCP JSON | top-level `mcpServers` object | JSON |
|
|
79
|
+
| Claude Code user/local settings | top-level `mcpServers`, plus `projects.<path>.mcpServers` in `~/.claude.json` | JSON |
|
|
80
|
+
| VS Code | top-level `servers` object | JSONC for `.vscode/mcp.json` |
|
|
81
|
+
| Codex | `[mcp_servers.<name>]` tables | TOML |
|
|
82
|
+
|
|
83
|
+
Files ending in `.jsonc` are also parsed as JSONC. Other `.json` files remain strict JSON so malformed input is not silently accepted.
|
|
84
|
+
|
|
85
|
+
`--discover` checks only the following paths when they exist. It does not search the rest of the home directory.
|
|
86
|
+
|
|
87
|
+
- All systems: `~/.claude.json`, `~/.cursor/mcp.json`, `~/.codex/config.toml`, `$COPILOT_HOME/mcp-config.json` with `~/.copilot/mcp-config.json` as the fallback
|
|
88
|
+
- Current project: `.mcp.json`, `.codex/config.toml`, `.cursor/mcp.json`, `.vscode/mcp.json`
|
|
89
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`, `%APPDATA%\Code\User\mcp.json`
|
|
90
|
+
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`, `~/Library/Application Support/Code/User/mcp.json`
|
|
91
|
+
- Linux: `$XDG_CONFIG_HOME/Code/User/mcp.json`, falling back to `~/.config/Code/User/mcp.json`
|
|
92
|
+
|
|
93
|
+
## Read-only MCP server
|
|
94
|
+
|
|
95
|
+
The stdio server exposes two tools: `list_rules` and `scan`. It requires an allowed root at startup. Relative scan paths are resolved below that root; absolute paths, `..` traversal, and symlinks cannot escape it.
|
|
96
|
+
|
|
97
|
+
```console
|
|
98
|
+
mcp-latchpoint-server --root /absolute/path/to/reviewed-configs
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Example client entry:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"mcpServers": {
|
|
106
|
+
"latchpoint": {
|
|
107
|
+
"command": "/absolute/path/to/mcp-latchpoint-server",
|
|
108
|
+
"args": ["--root", "/absolute/path/to/reviewed-configs"]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The server returns findings and scan metadata, never raw configuration content. Stdio is the only server transport exposed by the entry point, and stdout is reserved for MCP protocol messages.
|
|
115
|
+
|
|
116
|
+
### Glama build
|
|
117
|
+
|
|
118
|
+
The [Glama listing](https://glama.ai/mcp/servers/robyroro/mcp-latchpoint) builds a container from the repository. In its Dockerfile configuration, use Python 3.13, these build steps and command arguments:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
["uv sync --no-dev"]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
["mcp-proxy", "--", "/app/.venv/bin/mcp-latchpoint-server", "--root", "/app/examples"]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The root is an existing directory with synthetic sample configurations. It lets Glama start and inspect the tools without giving the server access to a user's files. The command uses the executable inside the virtual environment created by `uv sync`. For this demo, the environment-variable schema can be `{"type":"object","properties":{}}` and placeholder parameters can be `{}`.
|
|
129
|
+
|
|
130
|
+
To scan your own configurations, run the server locally with `--root` pointing to a directory you explicitly trust. Glama's demo root is only for the sample files in `examples/`.
|
|
131
|
+
|
|
132
|
+
## Safety and limitations
|
|
133
|
+
|
|
134
|
+
The scanner never executes commands, installs packages, resolves referenced environment variables, or connects to endpoints. It does not follow configuration includes or inspect an MCP server's code or runtime behavior. Argument-based rules are intentionally limited to recognizable patterns, so custom flags can be missed. A clean report is not proof that a server is safe.
|
|
135
|
+
|
|
136
|
+
Secret detection is designed to emit field names and `<redacted>` markers rather than values. If you find a leak or a path-containment problem, follow [SECURITY.md](SECURITY.md) and do not attach a real configuration to a public issue.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Until the first stable release, security fixes are made on the latest `0.x` release and the default branch.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
Do not open a public issue for a vulnerability that could expose configuration data, credentials, or files outside an allowed root. Use GitHub's private vulnerability reporting feature on the eventual public repository. If that feature is not available, contact the repository owner privately before sharing details.
|
|
10
|
+
|
|
11
|
+
Include a minimal reproduction built from synthetic data, the affected version, and the operating system. Do not send real MCP configurations, tokens, home-directory listings, or other private files.
|
|
12
|
+
|
|
13
|
+
Reasonable reports will be acknowledged when the maintainer is available. A public disclosure timeline should be agreed only after the impact and fix have been checked independently.
|
|
14
|
+
|
|
15
|
+
## Security boundaries
|
|
16
|
+
|
|
17
|
+
`mcp-latchpoint` is a static configuration auditor. It must not execute configured commands, install packages, connect to configured endpoints, or return raw configuration through MCP tools. The stdio server's allowed root is a security boundary, including for traversal and symlink resolution.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"shell-launch": {
|
|
4
|
+
"command": "sh",
|
|
5
|
+
"args": ["-c", "npx @example/mcp-server --root /"],
|
|
6
|
+
"env": {
|
|
7
|
+
"API_TOKEN": "synthetic-example-value"
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"remote": {
|
|
11
|
+
"type": "http",
|
|
12
|
+
"url": "http://mcp.example.com/connect?api_key=synthetic-example-value",
|
|
13
|
+
"headers": {
|
|
14
|
+
"Authorization": "Bearer synthetic-example-value"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"reviewed-files": {
|
|
4
|
+
"command": "npx",
|
|
5
|
+
"args": [
|
|
6
|
+
"--yes",
|
|
7
|
+
"@modelcontextprotocol/server-filesystem@1.2.3",
|
|
8
|
+
"/srv/mcp/shared-documents"
|
|
9
|
+
],
|
|
10
|
+
"env": {
|
|
11
|
+
"SERVICE_TOKEN": "${SERVICE_TOKEN}"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"internal-api": {
|
|
15
|
+
"type": "http",
|
|
16
|
+
"url": "https://mcp.example.com/api"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mcp-latchpoint"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Offline-first auditor for MCP client configuration files"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
authors = [{ name = "Robert Vind-Gardoș" }]
|
|
13
|
+
keywords = ["mcp", "security", "configuration", "audit"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Topic :: Security",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"mcp>=2.3,<3",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = [
|
|
31
|
+
"jsonschema>=4.23,<5",
|
|
32
|
+
"mypy>=1.15,<2",
|
|
33
|
+
"pytest>=8.3,<10",
|
|
34
|
+
"pytest-cov>=6,<8",
|
|
35
|
+
"ruff>=0.11,<1",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Repository = "https://github.com/robyroro/mcp-latchpoint"
|
|
40
|
+
Issues = "https://github.com/robyroro/mcp-latchpoint/issues"
|
|
41
|
+
Changelog = "https://github.com/robyroro/mcp-latchpoint/blob/main/CHANGELOG.md"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
mcp-latchpoint = "mcp_latchpoint.cli:main"
|
|
45
|
+
mcp-latchpoint-server = "mcp_latchpoint.mcp_server:main"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["src/mcp_latchpoint"]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
addopts = "-ra --strict-markers --strict-config"
|
|
52
|
+
testpaths = ["tests"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff]
|
|
55
|
+
line-length = 100
|
|
56
|
+
target-version = "py311"
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
60
|
+
|
|
61
|
+
[tool.mypy]
|
|
62
|
+
python_version = "3.11"
|
|
63
|
+
strict = true
|
|
64
|
+
packages = ["mcp_latchpoint"]
|