mcp-gatekeeper 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_gatekeeper-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +29 -0
- mcp_gatekeeper-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +17 -0
- mcp_gatekeeper-0.1.0/.github/SECURITY.md +20 -0
- mcp_gatekeeper-0.1.0/.github/workflows/ci.yml +35 -0
- mcp_gatekeeper-0.1.0/.github/workflows/release.yml +19 -0
- mcp_gatekeeper-0.1.0/.gitignore +23 -0
- mcp_gatekeeper-0.1.0/.pre-commit-config.yaml +14 -0
- mcp_gatekeeper-0.1.0/CHANGELOG.md +36 -0
- mcp_gatekeeper-0.1.0/CODE_OF_CONDUCT.md +60 -0
- mcp_gatekeeper-0.1.0/CONTRIBUTING.md +62 -0
- mcp_gatekeeper-0.1.0/LICENSE +21 -0
- mcp_gatekeeper-0.1.0/PKG-INFO +163 -0
- mcp_gatekeeper-0.1.0/README.md +130 -0
- mcp_gatekeeper-0.1.0/examples/bad_server.py +29 -0
- mcp_gatekeeper-0.1.0/examples/good_server.py +41 -0
- mcp_gatekeeper-0.1.0/pyproject.toml +90 -0
- mcp_gatekeeper-0.1.0/scripts/release.py +170 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/__init__.py +3 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/cli.py +106 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/client.py +117 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/doctor.py +40 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/engine.py +111 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/model.py +56 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/py.typed +0 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/rules/__init__.py +9 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/rules/base.py +79 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/rules/descriptions.py +84 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/rules/schemas.py +79 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/rules/security.py +62 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/rules/tokens.py +33 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/testing/__init__.py +1 -0
- mcp_gatekeeper-0.1.0/src/mcpgate/testing/plugin.py +119 -0
- mcp_gatekeeper-0.1.0/tests/conftest.py +47 -0
- mcp_gatekeeper-0.1.0/tests/fixtures/bad_server.py +41 -0
- mcp_gatekeeper-0.1.0/tests/fixtures/stdio_server.py +18 -0
- mcp_gatekeeper-0.1.0/tests/test_cli.py +105 -0
- mcp_gatekeeper-0.1.0/tests/test_client.py +70 -0
- mcp_gatekeeper-0.1.0/tests/test_package.py +5 -0
- mcp_gatekeeper-0.1.0/tests/test_plugin.py +47 -0
- mcp_gatekeeper-0.1.0/tests/test_rules.py +187 -0
- mcp_gatekeeper-0.1.0/uv.lock +1688 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Report a problem with mcpgate
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: what-happened
|
|
7
|
+
attributes:
|
|
8
|
+
label: What happened?
|
|
9
|
+
description: Include the exact `mcpgate` command and full output.
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: input
|
|
13
|
+
id: version
|
|
14
|
+
attributes:
|
|
15
|
+
label: mcpgate version
|
|
16
|
+
placeholder: "0.1.0"
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: input
|
|
20
|
+
id: python
|
|
21
|
+
attributes:
|
|
22
|
+
label: Python version
|
|
23
|
+
validations:
|
|
24
|
+
required: true
|
|
25
|
+
- type: textarea
|
|
26
|
+
id: server
|
|
27
|
+
attributes:
|
|
28
|
+
label: MCP server being linted
|
|
29
|
+
description: Command or URL. Redact anything private.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Suggest a rule or feature
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: Problem
|
|
9
|
+
description: What quality defect or workflow is not covered?
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: proposal
|
|
14
|
+
attributes:
|
|
15
|
+
label: Proposed solution
|
|
16
|
+
validations:
|
|
17
|
+
required: true
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
|---------|-----------|
|
|
7
|
+
| 0.1.x | ✅ |
|
|
8
|
+
|
|
9
|
+
## Reporting a vulnerability
|
|
10
|
+
|
|
11
|
+
Do **not** open a public issue for security vulnerabilities. Use GitHub's
|
|
12
|
+
"Report a vulnerability" (Security → Advisories → New draft security advisory),
|
|
13
|
+
or open a minimal issue pointing to a draft advisory. You will get a response
|
|
14
|
+
within 7 days.
|
|
15
|
+
|
|
16
|
+
## Scope
|
|
17
|
+
|
|
18
|
+
`mcpgate` connects to MCP servers you point it at. Vulnerabilities of interest
|
|
19
|
+
include: command injection via server command strings, unsafe deserialization of
|
|
20
|
+
server responses, and SARIF/JSON output injection.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
- name: Install
|
|
21
|
+
run: uv sync --all-extras
|
|
22
|
+
- name: Lint
|
|
23
|
+
run: uv run ruff check .
|
|
24
|
+
- name: Format check
|
|
25
|
+
run: uv run ruff format --check .
|
|
26
|
+
- name: Types
|
|
27
|
+
run: uv run mypy src
|
|
28
|
+
- name: Test
|
|
29
|
+
run: |
|
|
30
|
+
uv run coverage run -m pytest
|
|
31
|
+
uv run coverage report
|
|
32
|
+
- name: Build
|
|
33
|
+
run: |
|
|
34
|
+
uv build
|
|
35
|
+
uv run --with twine twine check dist/*
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
environment: pypi
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: astral-sh/setup-uv@v5
|
|
16
|
+
- name: Build
|
|
17
|
+
run: uv build
|
|
18
|
+
- name: Publish to PyPI
|
|
19
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.venv/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.coverage
|
|
11
|
+
coverage.xml
|
|
12
|
+
htmlcov/
|
|
13
|
+
uv.lock.bak
|
|
14
|
+
.DS_Store
|
|
15
|
+
|
|
16
|
+
# AI/planning docs — local only
|
|
17
|
+
PLAN.md
|
|
18
|
+
IMPLEMENTATION_PLAN.md
|
|
19
|
+
AGENTS.md
|
|
20
|
+
CLAUDE.md
|
|
21
|
+
GEMINI.md
|
|
22
|
+
.claude/
|
|
23
|
+
copilot-instructions.md
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.8.4
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff-format
|
|
6
|
+
- id: ruff
|
|
7
|
+
args: [--fix]
|
|
8
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
9
|
+
rev: v1.13.0
|
|
10
|
+
stages: [pre-push]
|
|
11
|
+
hooks:
|
|
12
|
+
- id: mypy
|
|
13
|
+
files: ^src/
|
|
14
|
+
additional_dependencies: [pydantic>=2.0, click>=8.1, types-click]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-09-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- feat: automated release script with semver bump decision
|
|
12
|
+
- feat: pytest plugin with mcp_server fixture and tool_contract
|
|
13
|
+
- feat: lint/doctor CLI with text, json and sarif output
|
|
14
|
+
- feat: 15 lint rules with decorator registry (MCP101-MCP401)
|
|
15
|
+
- feat: protocol client and normalized snapshot models
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- fix: rename PyPI distribution to mcp-gatekeeper
|
|
19
|
+
- fix: rename PyPI package to mcp-gate (mcpgate is taken)
|
|
20
|
+
- fix: snapshot servers that lack resources/prompts capabilities
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- docs: drop AGENTS.md link from README
|
|
24
|
+
- docs: self-contained public CONTRIBUTING guide
|
|
25
|
+
- docs: full README with rule table, comparison and examples; coverage gate at 90%
|
|
26
|
+
|
|
27
|
+
### Other
|
|
28
|
+
- chore: keep AI and planning docs untracked
|
|
29
|
+
- chore: scaffold package, OSS files, CI and release workflows
|
|
30
|
+
|
|
31
|
+
## [0.1.0] - 2026-09-02
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- `mcpgate lint` with 15 rules (MCP101–MCP401), text/json/sarif output, CI-friendly exit codes.
|
|
35
|
+
- `mcpgate doctor` health report.
|
|
36
|
+
- Pytest plugin: `mcp_server` fixture + `tool_contract` helper.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
|
10
|
+
orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment include:
|
|
18
|
+
|
|
19
|
+
* Demonstrating empathy and kindness toward other people
|
|
20
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
21
|
+
* Giving and gracefully accepting constructive feedback
|
|
22
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
23
|
+
and learning from the experience
|
|
24
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
|
25
|
+
community
|
|
26
|
+
|
|
27
|
+
Examples of unacceptable behavior include:
|
|
28
|
+
|
|
29
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
30
|
+
any kind
|
|
31
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
32
|
+
* Public or private harassment
|
|
33
|
+
* Publishing others' private information, such as a physical or email address,
|
|
34
|
+
without their explicit permission
|
|
35
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
36
|
+
professional setting
|
|
37
|
+
|
|
38
|
+
## Enforcement Responsibilities
|
|
39
|
+
|
|
40
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
41
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
42
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
43
|
+
or harmful.
|
|
44
|
+
|
|
45
|
+
## Scope
|
|
46
|
+
|
|
47
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
48
|
+
an individual is officially representing the community in public spaces.
|
|
49
|
+
|
|
50
|
+
## Enforcement
|
|
51
|
+
|
|
52
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
53
|
+
reported to the community leaders responsible for enforcement by opening a
|
|
54
|
+
private GitHub issue marked as a conduct report. All complaints will be reviewed
|
|
55
|
+
and investigated promptly and fairly.
|
|
56
|
+
|
|
57
|
+
## Attribution
|
|
58
|
+
|
|
59
|
+
This Code of Conduct is adapted from the
|
|
60
|
+
[Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Contributing to mcpgate
|
|
2
|
+
|
|
3
|
+
Thanks for helping make MCP servers better for the agents that use them!
|
|
4
|
+
|
|
5
|
+
## Getting started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/ofsazib/mcpgate
|
|
9
|
+
cd mcpgate
|
|
10
|
+
uv sync --all-extras # install deps (dev + test extras)
|
|
11
|
+
|
|
12
|
+
uv run coverage run -m pytest # tests (fully offline)
|
|
13
|
+
uv run coverage report # coverage, gate at 90%
|
|
14
|
+
uv run ruff check . # lint
|
|
15
|
+
uv run ruff format . # format
|
|
16
|
+
uv run mypy src # strict type check
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Python 3.10–3.13 and [uv](https://docs.astral.sh/uv/).
|
|
20
|
+
|
|
21
|
+
## Project layout
|
|
22
|
+
|
|
23
|
+
| Module | Responsibility |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `src/mcpgate/cli.py` | click app: `mcpgate lint \| doctor` |
|
|
26
|
+
| `src/mcpgate/client.py` | connect over in-memory/stdio/HTTP; snapshot definitions |
|
|
27
|
+
| `src/mcpgate/model.py` | pydantic models: `ToolInfo`, `Finding`, `ServerSnapshot`, … |
|
|
28
|
+
| `src/mcpgate/engine.py` | rule runner, text/json/sarif formatters, exit codes |
|
|
29
|
+
| `src/mcpgate/doctor.py` | health report |
|
|
30
|
+
| `src/mcpgate/testing/plugin.py` | pytest plugin (`mcp_server` fixture, `tool_contract`) |
|
|
31
|
+
| `src/mcpgate/rules/` | rule registry + rule modules |
|
|
32
|
+
|
|
33
|
+
## Adding a lint rule
|
|
34
|
+
|
|
35
|
+
Rule codes are grouped by prefix: `MCP1xx` descriptions, `MCP2xx` schemas,
|
|
36
|
+
`MCP3xx` security, `MCP4xx` token cost.
|
|
37
|
+
|
|
38
|
+
1. Write a small pure function over `ServerSnapshot` returning
|
|
39
|
+
`(source, message-kwargs)` tuples, in the right `rules/*.py` module.
|
|
40
|
+
2. Register it with the `@rule("MCPxxx", "severity", "message template")` decorator
|
|
41
|
+
from `rules/base.py`.
|
|
42
|
+
3. Add a positive and a negative test in `tests/test_rules.py` — one of each per
|
|
43
|
+
rule, no exceptions.
|
|
44
|
+
4. If it is user-visible, add a row to the rule table in `README.md`.
|
|
45
|
+
|
|
46
|
+
## Ground rules
|
|
47
|
+
|
|
48
|
+
- **Runtime dependencies** are frozen at `mcp`, `click`, `pydantic` — anything else
|
|
49
|
+
needs prior discussion in an issue.
|
|
50
|
+
- **Tests are offline.** Use the `mcp` SDK's in-memory transport; mark network
|
|
51
|
+
tests with `@pytest.mark.network` (deselected by default).
|
|
52
|
+
- **Typing is strict.** No `# type: ignore` without a comment explaining why.
|
|
53
|
+
- **Commits** follow Conventional Commits (`feat:`, `fix:`, `docs:`, …).
|
|
54
|
+
- Every PR must keep `pytest`, `ruff` and `mypy src` green.
|
|
55
|
+
|
|
56
|
+
## Reporting bugs and security issues
|
|
57
|
+
|
|
58
|
+
Bugs and feature requests: open a GitHub issue using the templates.
|
|
59
|
+
Security vulnerabilities: **never** in a public issue — see
|
|
60
|
+
[SECURITY.md](.github/SECURITY.md).
|
|
61
|
+
|
|
62
|
+
By participating you agree to the [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mcpgate contributors
|
|
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,163 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mcp-gatekeeper
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Automated quality gate for MCP servers — ruff + pytest for MCP.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ofsazib/mcpgate
|
|
6
|
+
Project-URL: Changelog, https://github.com/ofsazib/mcpgate/blob/main/CHANGELOG.md
|
|
7
|
+
Project-URL: Issues, https://github.com/ofsazib/mcpgate/issues
|
|
8
|
+
Author: mcpgate contributors
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-agents,lint,mcp,model-context-protocol,pytest,quality-gate
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
22
|
+
Classifier: Topic :: Software Development :: Testing
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: click>=8.1
|
|
26
|
+
Requires-Dist: mcp<2,>=1.0
|
|
27
|
+
Requires-Dist: pydantic>=2.0
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'test'
|
|
30
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'test'
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == 'test'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# mcpgate
|
|
35
|
+
|
|
36
|
+
> Automated quality gate for MCP servers — **ruff + pytest for MCP**.
|
|
37
|
+
|
|
38
|
+
[](https://github.com/ofsazib/mcpgate/actions/workflows/ci.yml)
|
|
39
|
+
[](https://pypi.org/project/mcp-gatekeeper/)
|
|
40
|
+
[](https://pypi.org/project/mcp-gatekeeper/)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
[](https://codecov.io/gh/ofsazib/mcpgate)
|
|
43
|
+
|
|
44
|
+
## The problem
|
|
45
|
+
|
|
46
|
+
When an MCP tool description is vague, the AI agent misuses the tool — even though
|
|
47
|
+
the code behind it is perfect. This failure is invisible to unit tests because it
|
|
48
|
+
happens at the AI-interpretation layer. Research documents the scale:
|
|
49
|
+
|
|
50
|
+
- An arXiv study of 103 major MCP servers (856 tools) found tool descriptions
|
|
51
|
+
systematically "smelly and costly" ([arXiv:2602.14878](https://arxiv.org/abs/2602.14878)).
|
|
52
|
+
- An AWS-led study found **97.1% of MCP tool descriptions contain at least one
|
|
53
|
+
quality defect**.
|
|
54
|
+
|
|
55
|
+
`mcpgate` catches these defects before agents hit them in production. It speaks the
|
|
56
|
+
real protocol, so it works with any server — FastMCP, the official Python SDK, or
|
|
57
|
+
TypeScript servers — over stdio or HTTP.
|
|
58
|
+
|
|
59
|
+
## Quickstart
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv tool install mcp-gatekeeper # or: pip install mcp-gatekeeper
|
|
63
|
+
|
|
64
|
+
mcpgate lint "uvx mcp-server-fetch"
|
|
65
|
+
mcpgate doctor "uvx mcp-server-fetch"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Exit codes are CI-friendly: `0` clean, `1` findings (fail the build), `2` connection
|
|
69
|
+
or protocol error.
|
|
70
|
+
|
|
71
|
+
## Example output
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
warning MCP101 Tool `delete_record`: description too short (7 words; minimum 8) src: tools/delete_record
|
|
75
|
+
warning MCP105 Tool `delete_record`: description does not state error/return behavior src: tools/delete_record
|
|
76
|
+
warning MCP303 Tool `delete_record`: destructive operation without a confirmation parameter src: tools/delete_record
|
|
77
|
+
error MCP301 Tool `run_command`: suggests command execution without a safety note src: tools/run_command
|
|
78
|
+
warning MCP101 Tool `run_command`: description too short (7 words; minimum 8) src: tools/run_command
|
|
79
|
+
warning MCP105 Tool `run_command`: description does not state error/return behavior src: tools/run_command
|
|
80
|
+
warning MCP101 Tool `search`: description too short (2 words; minimum 8) src: tools/search
|
|
81
|
+
warning MCP105 Tool `search`: description does not state error/return behavior src: tools/search
|
|
82
|
+
info MCP204 Tool `search`: description repeats the tool name (wasted tokens) src: tools/search
|
|
83
|
+
———
|
|
84
|
+
found 9 problems (1 errors, 7 warnings)
|
|
85
|
+
run `mcpgate lint --explain CODE` for details
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Formats: `--format text|json|sarif` (SARIF plugs into GitHub code scanning).
|
|
89
|
+
Filter with `--select MCP3xx --ignore MCP401`; inspect a rule with
|
|
90
|
+
`--explain MCP101`; fail on warnings with `--strict`.
|
|
91
|
+
|
|
92
|
+
## Rules (v0.1)
|
|
93
|
+
|
|
94
|
+
| Code | Severity | What it checks |
|
|
95
|
+
|------|----------|----------------|
|
|
96
|
+
| MCP101 | warning | Tool description missing or shorter than 8 words |
|
|
97
|
+
| MCP102 | warning | Description is vague ("does stuff", "handles things", "various") |
|
|
98
|
+
| MCP103 | warning | Description just restates the tool name |
|
|
99
|
+
| MCP104 | warning | Tools with 2+ parameters where parameters lack descriptions |
|
|
100
|
+
| MCP105 | warning | Description does not state error/return behavior |
|
|
101
|
+
| MCP106 | warning | Description longer than 1024 chars (token bloat) |
|
|
102
|
+
| MCP201 | error | Input schema missing or not a JSON object schema |
|
|
103
|
+
| MCP202 | warning | Required parameter is not described |
|
|
104
|
+
| MCP203 | info | `additionalProperties: false` schema but description states no constraints |
|
|
105
|
+
| MCP204 | info | Description repeats the tool name (wasted tokens) |
|
|
106
|
+
| MCP205 | error | Declared output schema is invalid |
|
|
107
|
+
| MCP301 | error | Command-execution tool without a safety note |
|
|
108
|
+
| MCP302 | warning | Parameter name looks like a secret (`api_key`, `token`, …) |
|
|
109
|
+
| MCP303 | warning | Destructive operation without a confirmation parameter |
|
|
110
|
+
| MCP401 | info | Full definition snapshot costs more than ~10k tokens |
|
|
111
|
+
|
|
112
|
+
## Pytest plugin
|
|
113
|
+
|
|
114
|
+
Install with the `test` extra, then contract-test your tools over the real protocol:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip install "mcp-gatekeeper[test]"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import pytest
|
|
122
|
+
from my_server import mcp # your FastMCP / MCP server object
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@pytest.mark.parametrize("mcp_server", [mcp], indirect=True)
|
|
126
|
+
def test_search(mcp_server):
|
|
127
|
+
result = mcp_server.call_tool("search", {"query": "hello"})
|
|
128
|
+
assert result.is_error is False
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@pytest.mark.parametrize("mcp_server", [mcp], indirect=True)
|
|
132
|
+
def test_contract(mcp_server):
|
|
133
|
+
mcp_server.tool_contract("search")
|
|
134
|
+
# asserts: described, valid object schema, described required params,
|
|
135
|
+
# and the error path returns is_error instead of crashing
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or point a whole suite at one server: `pytest --mcp-server "uvx mcp-server-fetch"`.
|
|
139
|
+
Works in-memory for server objects (fully offline), stdio/HTTP for commands and URLs.
|
|
140
|
+
|
|
141
|
+
## How it compares
|
|
142
|
+
|
|
143
|
+
| | MCP Inspector | mcplint-cli | mcp-doctor | **mcpgate** |
|
|
144
|
+
|---|---|---|---|---|
|
|
145
|
+
| Purpose | interactive debugger | description lint | diagnostics | unified quality gate |
|
|
146
|
+
| CLI-first, CI exit codes | ✗ | ✓ | partial | ✓ (0/1/2) |
|
|
147
|
+
| Definition lint rules | ✗ | ✓ | ✗ | ✓ (15 rules) |
|
|
148
|
+
| Doctor / health check | ✓ | ✗ | ✓ | ✓ |
|
|
149
|
+
| pytest plugin | ✗ | ✗ | ✗ | ✓ |
|
|
150
|
+
| JSON + SARIF output | ✗ | ✗ | ✗ | ✓ |
|
|
151
|
+
| Framework-agnostic (speaks MCP) | ✓ | ✓ | ✓ | ✓ |
|
|
152
|
+
|
|
153
|
+
## Roadmap
|
|
154
|
+
|
|
155
|
+
- **v0.2** — `--fix`: LLM-assisted rewriting of weak descriptions (the engine already
|
|
156
|
+
carries the context needed per finding)
|
|
157
|
+
- Declarative YAML test suites and `--baseline` files
|
|
158
|
+
- Custom user rule plugins and a pre-commit hook package
|
|
159
|
+
- GitHub Action and official MCP servers registry integration
|
|
160
|
+
|
|
161
|
+
## Contributing
|
|
162
|
+
|
|
163
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). MIT licensed — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# mcpgate
|
|
2
|
+
|
|
3
|
+
> Automated quality gate for MCP servers — **ruff + pytest for MCP**.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/ofsazib/mcpgate/actions/workflows/ci.yml)
|
|
6
|
+
[](https://pypi.org/project/mcp-gatekeeper/)
|
|
7
|
+
[](https://pypi.org/project/mcp-gatekeeper/)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://codecov.io/gh/ofsazib/mcpgate)
|
|
10
|
+
|
|
11
|
+
## The problem
|
|
12
|
+
|
|
13
|
+
When an MCP tool description is vague, the AI agent misuses the tool — even though
|
|
14
|
+
the code behind it is perfect. This failure is invisible to unit tests because it
|
|
15
|
+
happens at the AI-interpretation layer. Research documents the scale:
|
|
16
|
+
|
|
17
|
+
- An arXiv study of 103 major MCP servers (856 tools) found tool descriptions
|
|
18
|
+
systematically "smelly and costly" ([arXiv:2602.14878](https://arxiv.org/abs/2602.14878)).
|
|
19
|
+
- An AWS-led study found **97.1% of MCP tool descriptions contain at least one
|
|
20
|
+
quality defect**.
|
|
21
|
+
|
|
22
|
+
`mcpgate` catches these defects before agents hit them in production. It speaks the
|
|
23
|
+
real protocol, so it works with any server — FastMCP, the official Python SDK, or
|
|
24
|
+
TypeScript servers — over stdio or HTTP.
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv tool install mcp-gatekeeper # or: pip install mcp-gatekeeper
|
|
30
|
+
|
|
31
|
+
mcpgate lint "uvx mcp-server-fetch"
|
|
32
|
+
mcpgate doctor "uvx mcp-server-fetch"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Exit codes are CI-friendly: `0` clean, `1` findings (fail the build), `2` connection
|
|
36
|
+
or protocol error.
|
|
37
|
+
|
|
38
|
+
## Example output
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
warning MCP101 Tool `delete_record`: description too short (7 words; minimum 8) src: tools/delete_record
|
|
42
|
+
warning MCP105 Tool `delete_record`: description does not state error/return behavior src: tools/delete_record
|
|
43
|
+
warning MCP303 Tool `delete_record`: destructive operation without a confirmation parameter src: tools/delete_record
|
|
44
|
+
error MCP301 Tool `run_command`: suggests command execution without a safety note src: tools/run_command
|
|
45
|
+
warning MCP101 Tool `run_command`: description too short (7 words; minimum 8) src: tools/run_command
|
|
46
|
+
warning MCP105 Tool `run_command`: description does not state error/return behavior src: tools/run_command
|
|
47
|
+
warning MCP101 Tool `search`: description too short (2 words; minimum 8) src: tools/search
|
|
48
|
+
warning MCP105 Tool `search`: description does not state error/return behavior src: tools/search
|
|
49
|
+
info MCP204 Tool `search`: description repeats the tool name (wasted tokens) src: tools/search
|
|
50
|
+
———
|
|
51
|
+
found 9 problems (1 errors, 7 warnings)
|
|
52
|
+
run `mcpgate lint --explain CODE` for details
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Formats: `--format text|json|sarif` (SARIF plugs into GitHub code scanning).
|
|
56
|
+
Filter with `--select MCP3xx --ignore MCP401`; inspect a rule with
|
|
57
|
+
`--explain MCP101`; fail on warnings with `--strict`.
|
|
58
|
+
|
|
59
|
+
## Rules (v0.1)
|
|
60
|
+
|
|
61
|
+
| Code | Severity | What it checks |
|
|
62
|
+
|------|----------|----------------|
|
|
63
|
+
| MCP101 | warning | Tool description missing or shorter than 8 words |
|
|
64
|
+
| MCP102 | warning | Description is vague ("does stuff", "handles things", "various") |
|
|
65
|
+
| MCP103 | warning | Description just restates the tool name |
|
|
66
|
+
| MCP104 | warning | Tools with 2+ parameters where parameters lack descriptions |
|
|
67
|
+
| MCP105 | warning | Description does not state error/return behavior |
|
|
68
|
+
| MCP106 | warning | Description longer than 1024 chars (token bloat) |
|
|
69
|
+
| MCP201 | error | Input schema missing or not a JSON object schema |
|
|
70
|
+
| MCP202 | warning | Required parameter is not described |
|
|
71
|
+
| MCP203 | info | `additionalProperties: false` schema but description states no constraints |
|
|
72
|
+
| MCP204 | info | Description repeats the tool name (wasted tokens) |
|
|
73
|
+
| MCP205 | error | Declared output schema is invalid |
|
|
74
|
+
| MCP301 | error | Command-execution tool without a safety note |
|
|
75
|
+
| MCP302 | warning | Parameter name looks like a secret (`api_key`, `token`, …) |
|
|
76
|
+
| MCP303 | warning | Destructive operation without a confirmation parameter |
|
|
77
|
+
| MCP401 | info | Full definition snapshot costs more than ~10k tokens |
|
|
78
|
+
|
|
79
|
+
## Pytest plugin
|
|
80
|
+
|
|
81
|
+
Install with the `test` extra, then contract-test your tools over the real protocol:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install "mcp-gatekeeper[test]"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import pytest
|
|
89
|
+
from my_server import mcp # your FastMCP / MCP server object
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@pytest.mark.parametrize("mcp_server", [mcp], indirect=True)
|
|
93
|
+
def test_search(mcp_server):
|
|
94
|
+
result = mcp_server.call_tool("search", {"query": "hello"})
|
|
95
|
+
assert result.is_error is False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.parametrize("mcp_server", [mcp], indirect=True)
|
|
99
|
+
def test_contract(mcp_server):
|
|
100
|
+
mcp_server.tool_contract("search")
|
|
101
|
+
# asserts: described, valid object schema, described required params,
|
|
102
|
+
# and the error path returns is_error instead of crashing
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Or point a whole suite at one server: `pytest --mcp-server "uvx mcp-server-fetch"`.
|
|
106
|
+
Works in-memory for server objects (fully offline), stdio/HTTP for commands and URLs.
|
|
107
|
+
|
|
108
|
+
## How it compares
|
|
109
|
+
|
|
110
|
+
| | MCP Inspector | mcplint-cli | mcp-doctor | **mcpgate** |
|
|
111
|
+
|---|---|---|---|---|
|
|
112
|
+
| Purpose | interactive debugger | description lint | diagnostics | unified quality gate |
|
|
113
|
+
| CLI-first, CI exit codes | ✗ | ✓ | partial | ✓ (0/1/2) |
|
|
114
|
+
| Definition lint rules | ✗ | ✓ | ✗ | ✓ (15 rules) |
|
|
115
|
+
| Doctor / health check | ✓ | ✗ | ✓ | ✓ |
|
|
116
|
+
| pytest plugin | ✗ | ✗ | ✗ | ✓ |
|
|
117
|
+
| JSON + SARIF output | ✗ | ✗ | ✗ | ✓ |
|
|
118
|
+
| Framework-agnostic (speaks MCP) | ✓ | ✓ | ✓ | ✓ |
|
|
119
|
+
|
|
120
|
+
## Roadmap
|
|
121
|
+
|
|
122
|
+
- **v0.2** — `--fix`: LLM-assisted rewriting of weak descriptions (the engine already
|
|
123
|
+
carries the context needed per finding)
|
|
124
|
+
- Declarative YAML test suites and `--baseline` files
|
|
125
|
+
- Custom user rule plugins and a pre-commit hook package
|
|
126
|
+
- GitHub Action and official MCP servers registry integration
|
|
127
|
+
|
|
128
|
+
## Contributing
|
|
129
|
+
|
|
130
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). MIT licensed — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""An example MCP server that violates many mcpgate rules (for docs).
|
|
2
|
+
|
|
3
|
+
Run: uv run mcpgate lint "python3 examples/bad_server.py"
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
from mcp.server.fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
logging.getLogger("mcp").setLevel(logging.WARNING)
|
|
11
|
+
|
|
12
|
+
mcp = FastMCP("example-bad-server")
|
|
13
|
+
|
|
14
|
+
TOOLS = {
|
|
15
|
+
"search": "Search stuff.",
|
|
16
|
+
"run_command": "Runs any shell command you pass it.",
|
|
17
|
+
"delete_record": "Removes a record from the database permanently.",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _register() -> None:
|
|
22
|
+
for name, desc in TOOLS.items():
|
|
23
|
+
mcp.tool(name=name, description=desc)(lambda: None)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
_register()
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
mcp.run()
|