openmaskit 0.1.1__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.
- openmaskit-0.1.1/.claude/CLAUDE.md +35 -0
- openmaskit-0.1.1/.dockerignore +50 -0
- openmaskit-0.1.1/.github/workflows/publish.yml +158 -0
- openmaskit-0.1.1/.github/workflows/test.yml +31 -0
- openmaskit-0.1.1/.gitignore +35 -0
- openmaskit-0.1.1/.python-version +1 -0
- openmaskit-0.1.1/CLAUDE.md +261 -0
- openmaskit-0.1.1/CONTRIBUTING.md +72 -0
- openmaskit-0.1.1/Dockerfile +39 -0
- openmaskit-0.1.1/LICENSE +201 -0
- openmaskit-0.1.1/NOTICE +10 -0
- openmaskit-0.1.1/PKG-INFO +229 -0
- openmaskit-0.1.1/README.md +195 -0
- openmaskit-0.1.1/SECURITY.md +32 -0
- openmaskit-0.1.1/TRADEMARKS.md +70 -0
- openmaskit-0.1.1/assets/icon.png +0 -0
- openmaskit-0.1.1/pyproject.toml +61 -0
- openmaskit-0.1.1/src/openmaskit/__init__.py +19 -0
- openmaskit-0.1.1/src/openmaskit/__main__.py +617 -0
- openmaskit-0.1.1/src/openmaskit/backend_client.py +181 -0
- openmaskit-0.1.1/src/openmaskit/cli.py +112 -0
- openmaskit-0.1.1/src/openmaskit/config.py +126 -0
- openmaskit-0.1.1/src/openmaskit/container.py +285 -0
- openmaskit-0.1.1/src/openmaskit/logging_config.py +74 -0
- openmaskit-0.1.1/src/openmaskit/masking/__init__.py +0 -0
- openmaskit-0.1.1/src/openmaskit/masking/engine.py +536 -0
- openmaskit-0.1.1/src/openmaskit/masking/mappers.py +20 -0
- openmaskit-0.1.1/src/openmaskit/masking/parsing.py +51 -0
- openmaskit-0.1.1/src/openmaskit/masking/rules.py +103 -0
- openmaskit-0.1.1/src/openmaskit/masking/store.py +619 -0
- openmaskit-0.1.1/src/openmaskit/models.py +66 -0
- openmaskit-0.1.1/src/openmaskit/oauth/__init__.py +0 -0
- openmaskit-0.1.1/src/openmaskit/oauth/handler.py +431 -0
- openmaskit-0.1.1/src/openmaskit/proxy/__init__.py +0 -0
- openmaskit-0.1.1/src/openmaskit/proxy/core.py +574 -0
- openmaskit-0.1.1/src/openmaskit/proxy/http_downstream.py +159 -0
- openmaskit-0.1.1/src/openmaskit/proxy/manager.py +260 -0
- openmaskit-0.1.1/src/openmaskit/proxy/upstream.py +321 -0
- openmaskit-0.1.1/src/openmaskit/security.py +145 -0
- openmaskit-0.1.1/src/openmaskit/traffic/__init__.py +0 -0
- openmaskit-0.1.1/src/openmaskit/traffic/buffer.py +44 -0
- openmaskit-0.1.1/src/openmaskit/traffic/store.py +223 -0
- openmaskit-0.1.1/src/openmaskit/web/__init__.py +0 -0
- openmaskit-0.1.1/src/openmaskit/web/app.py +142 -0
- openmaskit-0.1.1/src/openmaskit/web/health.py +109 -0
- openmaskit-0.1.1/src/openmaskit/web/origin.py +110 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/__init__.py +0 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/custom_targets.py +280 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/guardrails.py +160 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/hidden_tools.py +40 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/injections.py +142 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/mappers.py +382 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/marketplace.py +607 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/oauth.py +162 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/oauth_callback.py +191 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/pages.py +158 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/rules.py +124 -0
- openmaskit-0.1.1/src/openmaskit/web/routes/traffic.py +82 -0
- openmaskit-0.1.1/src/openmaskit/web/static/big.png +0 -0
- openmaskit-0.1.1/src/openmaskit/web/static/favicon.png +0 -0
- openmaskit-0.1.1/src/openmaskit/web/static/icon.png +0 -0
- openmaskit-0.1.1/src/openmaskit/web/static/marketplace.html +937 -0
- openmaskit-0.1.1/src/openmaskit/web/static/new_maskit-removebg-preview.png +0 -0
- openmaskit-0.1.1/src/openmaskit/web/static/onboarding.css +386 -0
- openmaskit-0.1.1/src/openmaskit/web/static/original_icon.png +0 -0
- openmaskit-0.1.1/src/openmaskit/web/static/shared.js +322 -0
- openmaskit-0.1.1/src/openmaskit/web/static/style.css +5036 -0
- openmaskit-0.1.1/src/openmaskit/web/static/targets.html +1174 -0
- openmaskit-0.1.1/src/openmaskit/web/static/tool_detail.html +936 -0
- openmaskit-0.1.1/src/openmaskit/web/static/tools.html +661 -0
- openmaskit-0.1.1/src/openmaskit/web/static/tutorial.css +377 -0
- openmaskit-0.1.1/src/openmaskit/web/static/tutorial.js +546 -0
- openmaskit-0.1.1/src/openmaskit/web/static/tutorials/guardrails.json +31 -0
- openmaskit-0.1.1/src/openmaskit/web/static/tutorials/hide-tool.json +16 -0
- openmaskit-0.1.1/src/openmaskit/web/static/tutorials/injections.json +31 -0
- openmaskit-0.1.1/src/openmaskit/web/static/tutorials/masking-with-result.json +31 -0
- openmaskit-0.1.1/src/openmaskit/web/static/tutorials/masking.json +16 -0
- openmaskit-0.1.1/tests/__init__.py +0 -0
- openmaskit-0.1.1/tests/test_backend_client.py +575 -0
- openmaskit-0.1.1/tests/test_cli.py +91 -0
- openmaskit-0.1.1/tests/test_config.py +255 -0
- openmaskit-0.1.1/tests/test_container.py +342 -0
- openmaskit-0.1.1/tests/test_custom_targets.py +343 -0
- openmaskit-0.1.1/tests/test_engine.py +442 -0
- openmaskit-0.1.1/tests/test_guardrails.py +189 -0
- openmaskit-0.1.1/tests/test_guardrails_routes.py +414 -0
- openmaskit-0.1.1/tests/test_health.py +137 -0
- openmaskit-0.1.1/tests/test_http_downstream.py +331 -0
- openmaskit-0.1.1/tests/test_http_downstream_no_leak.py +274 -0
- openmaskit-0.1.1/tests/test_injections.py +202 -0
- openmaskit-0.1.1/tests/test_injections_routes.py +459 -0
- openmaskit-0.1.1/tests/test_logging_config.py +82 -0
- openmaskit-0.1.1/tests/test_main.py +275 -0
- openmaskit-0.1.1/tests/test_mappers.py +319 -0
- openmaskit-0.1.1/tests/test_mappers_routes.py +546 -0
- openmaskit-0.1.1/tests/test_marketplace.py +469 -0
- openmaskit-0.1.1/tests/test_oauth.py +418 -0
- openmaskit-0.1.1/tests/test_oauth_state_cleanup.py +98 -0
- openmaskit-0.1.1/tests/test_origin_middleware.py +475 -0
- openmaskit-0.1.1/tests/test_parsing.py +84 -0
- openmaskit-0.1.1/tests/test_proxy.py +199 -0
- openmaskit-0.1.1/tests/test_proxy_manager.py +409 -0
- openmaskit-0.1.1/tests/test_proxy_upstream.py +689 -0
- openmaskit-0.1.1/tests/test_security.py +163 -0
- openmaskit-0.1.1/tests/test_shutdown.py +167 -0
- openmaskit-0.1.1/tests/test_store.py +400 -0
- openmaskit-0.1.1/tests/test_target_manager.py +362 -0
- openmaskit-0.1.1/tests/test_traffic_buffer.py +95 -0
- openmaskit-0.1.1/tests/test_traffic_routes.py +209 -0
- openmaskit-0.1.1/tests/test_traffic_store.py +179 -0
- openmaskit-0.1.1/tests/test_ui_tutorials.py +87 -0
- openmaskit-0.1.1/tests/test_upstream_container_lifecycle.py +411 -0
- openmaskit-0.1.1/tests/test_web_routes.py +302 -0
- openmaskit-0.1.1/uv.lock +1514 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!-- CODEGRAPH_START -->
|
|
2
|
+
## CodeGraph
|
|
3
|
+
|
|
4
|
+
This project has a CodeGraph MCP server (`codegraph_*` tools) configured. CodeGraph is a tree-sitter-parsed knowledge graph of every symbol, edge, and file. Reads are sub-millisecond and return structural information grep cannot.
|
|
5
|
+
|
|
6
|
+
### When to prefer codegraph over native search
|
|
7
|
+
|
|
8
|
+
Use codegraph for **structural** questions — what calls what, what would break, where is X defined, what is X's signature. Use native grep/read only for **literal text** queries (string contents, comments, log messages) or after you already have a specific file open.
|
|
9
|
+
|
|
10
|
+
| Question | Tool |
|
|
11
|
+
|---|---|
|
|
12
|
+
| "Where is X defined?" / "Find symbol named X" | `codegraph_search` |
|
|
13
|
+
| "What calls function Y?" | `codegraph_callers` |
|
|
14
|
+
| "What does Y call?" | `codegraph_callees` |
|
|
15
|
+
| "How does X reach/become Y? / trace the flow from X to Y" | `codegraph_trace` (one call = the whole path, incl. callback/React/JSX dynamic hops) |
|
|
16
|
+
| "What would break if I changed Z?" | `codegraph_impact` |
|
|
17
|
+
| "Show me Y's signature / source / docstring" | `codegraph_node` |
|
|
18
|
+
| "Give me focused context for a task/area" | `codegraph_context` |
|
|
19
|
+
| "See several related symbols' source at once" | `codegraph_explore` |
|
|
20
|
+
| "What files exist under path/" | `codegraph_files` |
|
|
21
|
+
| "Is the index healthy?" | `codegraph_status` |
|
|
22
|
+
|
|
23
|
+
### Rules of thumb
|
|
24
|
+
|
|
25
|
+
- **Answer directly — don't delegate exploration.** For "how does X work" / architecture questions, answer with 2-3 codegraph calls: `codegraph_context` first, then ONE `codegraph_explore` for the source of the symbols it surfaces. For a specific **flow** ("how does X reach Y") start with `codegraph_trace` from→to — one call returns the whole path with dynamic hops bridged — then ONE `codegraph_explore` for the bodies; don't rebuild the path with `codegraph_search` + `codegraph_callers`. Codegraph IS the pre-built index, so spawning a separate file-reading sub-task/agent — or running a grep + read loop — repeats work codegraph already did and costs more for the same answer.
|
|
26
|
+
- **Trust codegraph results.** They come from a full AST parse. Do NOT re-verify them with grep — that's slower, less accurate, and wastes context.
|
|
27
|
+
- **Don't grep first** when looking up a symbol by name. `codegraph_search` is faster and returns kind + location + signature in one call.
|
|
28
|
+
- **Don't chain `codegraph_search` + `codegraph_node`** when you just want context — `codegraph_context` is one call.
|
|
29
|
+
- **Don't loop `codegraph_node` over many symbols** — one `codegraph_explore` call returns several symbols' source grouped in a single capped call, while each separate node/Read call re-reads the whole context and costs far more.
|
|
30
|
+
- **Index lag — check the staleness banner, don't guess a wait.** When a codegraph response starts with "⚠️ Some files referenced below were edited since the last index sync…", the listed files are pending re-index — Read those specific files for accurate content. Files NOT in that banner are fresh and codegraph is authoritative for them. `codegraph_status` also lists pending files under "Pending sync".
|
|
31
|
+
|
|
32
|
+
### If `.codegraph/` doesn't exist
|
|
33
|
+
|
|
34
|
+
The MCP server returns "not initialized." Ask the user: *"I notice this project doesn't have CodeGraph initialized. Want me to run `codegraph init -i` to build the index?"*
|
|
35
|
+
<!-- CODEGRAPH_END -->
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Version control
|
|
2
|
+
.git
|
|
3
|
+
.gitignore
|
|
4
|
+
|
|
5
|
+
# Python virtual environment
|
|
6
|
+
.venv
|
|
7
|
+
venv
|
|
8
|
+
env
|
|
9
|
+
*.egg-info
|
|
10
|
+
|
|
11
|
+
# Python cache
|
|
12
|
+
__pycache__
|
|
13
|
+
*.pyc
|
|
14
|
+
*.pyo
|
|
15
|
+
*.pyd
|
|
16
|
+
.Python
|
|
17
|
+
|
|
18
|
+
# Testing
|
|
19
|
+
tests
|
|
20
|
+
.pytest_cache
|
|
21
|
+
.coverage
|
|
22
|
+
htmlcov
|
|
23
|
+
.tox
|
|
24
|
+
|
|
25
|
+
# IDE / Editor
|
|
26
|
+
.vscode
|
|
27
|
+
.idea
|
|
28
|
+
*.swp
|
|
29
|
+
*.swo
|
|
30
|
+
*~
|
|
31
|
+
.DS_Store
|
|
32
|
+
|
|
33
|
+
# Claude Code
|
|
34
|
+
.claude
|
|
35
|
+
|
|
36
|
+
# CI/CD
|
|
37
|
+
.github
|
|
38
|
+
|
|
39
|
+
# Documentation (not needed in image)
|
|
40
|
+
CONTRIBUTING.md
|
|
41
|
+
|
|
42
|
+
# Misc
|
|
43
|
+
*.log
|
|
44
|
+
*.db
|
|
45
|
+
*.sqlite
|
|
46
|
+
.env
|
|
47
|
+
.env.*
|
|
48
|
+
|
|
49
|
+
.codegraph
|
|
50
|
+
bench_unmask.py
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Two-step publish: tag pushes → TestPyPI; manual dispatch against the same tag → PyPI.
|
|
2
|
+
#
|
|
3
|
+
# Flow:
|
|
4
|
+
# 1. `git tag v0.1.1 && git push origin v0.1.1`
|
|
5
|
+
# → runs tests, builds wheel, publishes to TestPyPI.
|
|
6
|
+
# 2. Smoke-test the TestPyPI release (`uvx --from openmaskit==0.1.1 ...`).
|
|
7
|
+
# 3. Actions UI → "Publish" → "Run workflow" against ref `v0.1.1`
|
|
8
|
+
# → runs tests, rebuilds wheel, publishes to PyPI.
|
|
9
|
+
#
|
|
10
|
+
# Splitting the PyPI half off the tag trigger gives us the same gate that a
|
|
11
|
+
# "required reviewer on the pypi environment" would on a public repo —
|
|
12
|
+
# without needing GitHub Pro/Team on a private repo.
|
|
13
|
+
#
|
|
14
|
+
# One-time setup before this workflow will succeed (does not need to be done
|
|
15
|
+
# again until you change repo/workflow/env names):
|
|
16
|
+
#
|
|
17
|
+
# 1. TestPyPI Trusted Publisher
|
|
18
|
+
# https://test.pypi.org/manage/account/publishing/ → Add a new pending publisher
|
|
19
|
+
# Owner: MaskitMCP
|
|
20
|
+
# Repository: openmaskit
|
|
21
|
+
# Workflow: publish.yml
|
|
22
|
+
# Environment: testpypi
|
|
23
|
+
#
|
|
24
|
+
# 2. PyPI Trusted Publisher
|
|
25
|
+
# https://pypi.org/manage/account/publishing/ → Add a new pending publisher
|
|
26
|
+
# Owner: MaskitMCP
|
|
27
|
+
# Repository: openmaskit
|
|
28
|
+
# Workflow: publish.yml
|
|
29
|
+
# Environment: pypi
|
|
30
|
+
#
|
|
31
|
+
# 3. GitHub Environments (Settings → Environments)
|
|
32
|
+
# Create two environments named `testpypi` and `pypi`. The `pypi`
|
|
33
|
+
# environment should have a deployment tag policy restricting it to
|
|
34
|
+
# `v*` tags so only tagged commits can deploy.
|
|
35
|
+
#
|
|
36
|
+
# Until step 1-3 are done, this workflow will fail at the publish step with an
|
|
37
|
+
# OIDC error. That's expected and harmless.
|
|
38
|
+
|
|
39
|
+
name: Publish
|
|
40
|
+
|
|
41
|
+
on:
|
|
42
|
+
push:
|
|
43
|
+
tags: ['v*']
|
|
44
|
+
workflow_dispatch:
|
|
45
|
+
|
|
46
|
+
run-name: Publish ${{ github.ref_name }}
|
|
47
|
+
|
|
48
|
+
concurrency:
|
|
49
|
+
group: publish-${{ github.ref }}
|
|
50
|
+
cancel-in-progress: false
|
|
51
|
+
|
|
52
|
+
jobs:
|
|
53
|
+
test:
|
|
54
|
+
name: Run tests
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
steps:
|
|
57
|
+
- uses: actions/checkout@v4
|
|
58
|
+
|
|
59
|
+
- name: Install uv
|
|
60
|
+
uses: astral-sh/setup-uv@v4
|
|
61
|
+
with:
|
|
62
|
+
enable-cache: true
|
|
63
|
+
|
|
64
|
+
- name: Set up Python
|
|
65
|
+
run: uv python install 3.12
|
|
66
|
+
|
|
67
|
+
- name: Install dependencies
|
|
68
|
+
run: uv sync --frozen
|
|
69
|
+
|
|
70
|
+
- name: Run tests
|
|
71
|
+
run: uv run pytest tests/ -v
|
|
72
|
+
|
|
73
|
+
build:
|
|
74
|
+
name: Build sdist + wheel
|
|
75
|
+
needs: test
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
steps:
|
|
78
|
+
- uses: actions/checkout@v4
|
|
79
|
+
|
|
80
|
+
- name: Install uv
|
|
81
|
+
uses: astral-sh/setup-uv@v4
|
|
82
|
+
with:
|
|
83
|
+
enable-cache: true
|
|
84
|
+
|
|
85
|
+
- name: Set up Python
|
|
86
|
+
run: uv python install 3.12
|
|
87
|
+
|
|
88
|
+
- name: Verify tag matches pyproject.toml version
|
|
89
|
+
run: |
|
|
90
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
91
|
+
version=$(uv run python -c "import tomllib, pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")
|
|
92
|
+
if [ "$tag" != "$version" ]; then
|
|
93
|
+
echo "::error::Tag v$tag does not match pyproject.toml version $version. Bump pyproject.toml or retag."
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
echo "Tag and pyproject.toml agree on version: $version"
|
|
97
|
+
|
|
98
|
+
- name: Build
|
|
99
|
+
run: uv build
|
|
100
|
+
|
|
101
|
+
- name: Validate distributions with twine
|
|
102
|
+
run: uvx twine check dist/*
|
|
103
|
+
|
|
104
|
+
- name: Upload dist artifact
|
|
105
|
+
uses: actions/upload-artifact@v4
|
|
106
|
+
with:
|
|
107
|
+
name: dist
|
|
108
|
+
path: dist/
|
|
109
|
+
retention-days: 7
|
|
110
|
+
|
|
111
|
+
publish-testpypi:
|
|
112
|
+
name: Publish to TestPyPI
|
|
113
|
+
needs: build
|
|
114
|
+
if: github.event_name == 'push'
|
|
115
|
+
runs-on: ubuntu-latest
|
|
116
|
+
environment:
|
|
117
|
+
name: testpypi
|
|
118
|
+
url: https://test.pypi.org/project/openmaskit/
|
|
119
|
+
permissions:
|
|
120
|
+
id-token: write # required for OIDC trusted publishing
|
|
121
|
+
steps:
|
|
122
|
+
- name: Download dist artifact
|
|
123
|
+
uses: actions/download-artifact@v4
|
|
124
|
+
with:
|
|
125
|
+
name: dist
|
|
126
|
+
path: dist/
|
|
127
|
+
|
|
128
|
+
- name: Publish
|
|
129
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
130
|
+
with:
|
|
131
|
+
repository-url: https://test.pypi.org/legacy/
|
|
132
|
+
|
|
133
|
+
publish-pypi:
|
|
134
|
+
name: Publish to PyPI
|
|
135
|
+
needs: build
|
|
136
|
+
if: github.event_name == 'workflow_dispatch'
|
|
137
|
+
runs-on: ubuntu-latest
|
|
138
|
+
environment:
|
|
139
|
+
name: pypi
|
|
140
|
+
url: https://pypi.org/project/openmaskit/
|
|
141
|
+
permissions:
|
|
142
|
+
id-token: write
|
|
143
|
+
steps:
|
|
144
|
+
- name: Refuse non-tag refs
|
|
145
|
+
run: |
|
|
146
|
+
if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then
|
|
147
|
+
echo "::error::PyPI publish must be dispatched against a v* tag, got ${GITHUB_REF}."
|
|
148
|
+
exit 1
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
- name: Download dist artifact
|
|
152
|
+
uses: actions/download-artifact@v4
|
|
153
|
+
with:
|
|
154
|
+
name: dist
|
|
155
|
+
path: dist/
|
|
156
|
+
|
|
157
|
+
- name: Publish
|
|
158
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: test-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v4
|
|
21
|
+
with:
|
|
22
|
+
enable-cache: true
|
|
23
|
+
|
|
24
|
+
- name: Set up Python
|
|
25
|
+
run: uv python install 3.12
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --frozen
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: uv run pytest tests/ -v
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
test-venv/
|
|
12
|
+
|
|
13
|
+
# Test / coverage artifacts
|
|
14
|
+
.coverage
|
|
15
|
+
coverage.json
|
|
16
|
+
htmlcov/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
|
|
19
|
+
# Mac related files
|
|
20
|
+
.DS_Store
|
|
21
|
+
*/.DS_Store
|
|
22
|
+
|
|
23
|
+
# Local OpenMaskit config (root-level only)
|
|
24
|
+
/openmaskit*.yaml
|
|
25
|
+
|
|
26
|
+
# Jetbrains related files
|
|
27
|
+
.idea
|
|
28
|
+
.bsp
|
|
29
|
+
|
|
30
|
+
# Local tooling config (not for the public repo)
|
|
31
|
+
.claude/settings.json
|
|
32
|
+
.mcp.json
|
|
33
|
+
|
|
34
|
+
.codegraph
|
|
35
|
+
bench_unmask.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.14
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What is OpenMaskit
|
|
6
|
+
|
|
7
|
+
OpenMaskit is an MCP (Model Context Protocol) server proxy that sits between an AI host (e.g., Claude Code) and a real MCP server. It intercepts tool call responses to mask sensitive field values (replacing `prod-db.internal.net` with `host_1`) and unmasks them when the agent sends those aliases back in tool call arguments.
|
|
8
|
+
|
|
9
|
+
## Deployment model (important for security reasoning)
|
|
10
|
+
|
|
11
|
+
OpenMaskit runs **locally on the user's own machine** — like a CLI dev tool (Docker Desktop, Jupyter, a local DB client). It is **not a hosted service**, the Python backend is **not deployed anywhere**, and the FE↔Python channel is a localhost link on the same machine. So:
|
|
12
|
+
|
|
13
|
+
- **Localhost-only auth on the Web UI / API / MCP endpoint is not required.** The user already owns the machine.
|
|
14
|
+
- **Running arbitrary subprocesses (stdio targets, `docker run ...` from the marketplace) is not RCE in any meaningful sense.** OpenMaskit is a UI for commands the user would otherwise type into their own terminal — it confers no privilege the user doesn't already have.
|
|
15
|
+
- **Multi-user shared-machine threats (other local users reading token files, etc.) are out of scope** unless explicitly raised.
|
|
16
|
+
|
|
17
|
+
The threats that **do** still matter, even for a local tool:
|
|
18
|
+
|
|
19
|
+
- **Browser-based cross-origin attacks against localhost.** A malicious webpage the user visits can `fetch()`/`WebSocket` against `127.0.0.1:9473`/`9474`/`3131` and exfiltrate secrets. This is the canonical "localhost service" attack class (cf. the Docker daemon, ethdev wallets, etc.). CSRF tokens, `Origin` header checks on POST and WS, and not echoing the alias map / unmasked previews to API callers are all still required.
|
|
20
|
+
- **OAuth callback integrity** — the OAuth flow physically goes through the browser, so `state` validation and code-injection defenses still apply.
|
|
21
|
+
- **Malicious upstream MCP server** — OpenMaskit talks to third-party MCP servers; their responses must not be able to crash the proxy, ReDoS the masking engine, or poison persistent state.
|
|
22
|
+
- **Correctness bugs** (mask/unmask collisions, races, leaks) — same as any other software.
|
|
23
|
+
|
|
24
|
+
When reviewing security findings, classify by whether the attacker is (a) the local user themselves [out of scope], (b) a malicious upstream MCP server [in scope], or (c) a webpage in the user's browser / a remote OAuth peer [in scope].
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv sync # Install dependencies
|
|
30
|
+
uv run pytest tests/ -v # Run all tests
|
|
31
|
+
uv run pytest tests/test_engine.py::TestMaskingEngine::test_mask_structured_content -v # Single test
|
|
32
|
+
uv run openmaskit # Run with ./openmaskit.yaml
|
|
33
|
+
uv run openmaskit path/to/config.yaml # Run with custom config
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Naming
|
|
37
|
+
|
|
38
|
+
The UI says "Servers" but the codebase uses "target" everywhere (classes, DB columns, API routes, variables). They mean the same thing — an upstream MCP server that OpenMaskit proxies to. "Server" is the user-facing term; "target" is the internal term. Do not rename internal code.
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
The system has four concurrent components running in one asyncio event loop (via anyio task groups):
|
|
43
|
+
|
|
44
|
+
1. **Proxy Core** (`__main__.py` + `proxy/core.py`) — Bidirectional JSON-RPC relay between downstream clients and upstream MCP server. Operates at the raw `JSONRPCMessage` level for full protocol transparency — all non-tool messages pass through unmodified. Bootstraps the upstream session (initialize + tools/list) at startup.
|
|
45
|
+
|
|
46
|
+
2. **MCP HTTP Endpoint** (`proxy/http_downstream.py`, Starlette on port 9474) — HTTP MCP endpoint that AI agents connect to. Implements the MCP streamable HTTP transport (POST /mcp). Uses `ResponseDispatcher` to correlate requests with responses through the proxy relay.
|
|
47
|
+
|
|
48
|
+
3. **Masking Engine** (`masking/engine.py`) — Synchronous mask/unmask using an in-memory cache. Aliases are created in-memory for speed (`_alias_cache`, `_reverse_cache`) and flushed to SQLite periodically by `_flush_loop`. The engine handles both `structuredContent` dicts (path-based masking) and `TextContent` blocks (JSON/Python-repr-parse-then-mask, fallback to string replacement). Supports two mapper types: `regex_replace` (regex pattern matching on text) and `json_field_mask` (dot-notation path targeting specific fields in parsed JSON/repr).
|
|
49
|
+
|
|
50
|
+
4. **Web UI** (`web/app.py`, Starlette on port 9473) — Dashboard for viewing tool schemas, managing masking rules, trying out tools, and inspecting the traffic audit log (lazy-loaded, paginated).
|
|
51
|
+
|
|
52
|
+
### Key data flow
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
AI Agent HTTP (:9474/mcp) ─┐
|
|
56
|
+
├──► ds_read stream
|
|
57
|
+
AI Host stdin ─────────────┘ ↓
|
|
58
|
+
_intercept_request (unmask aliases in arguments)
|
|
59
|
+
↓
|
|
60
|
+
upstream MCP server
|
|
61
|
+
↓
|
|
62
|
+
_intercept_response (mask values, cache tool schemas)
|
|
63
|
+
↓
|
|
64
|
+
ResponseDispatcher (routes to HTTP waiters)
|
|
65
|
+
↓ (if no waiter)
|
|
66
|
+
ds_write stream → _stdout_writer → AI Host stdout
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Important: stdout is sacred
|
|
70
|
+
|
|
71
|
+
The MCP stdio protocol uses stdout exclusively. All logging goes to stderr. The Web UI must never write to stdout.
|
|
72
|
+
|
|
73
|
+
### ResponseDispatcher pattern
|
|
74
|
+
|
|
75
|
+
HTTP clients (MCP endpoint and web UI "Try it out") register a waiter by request ID before injecting a message into the proxy relay. When the response arrives from upstream, `_relay_upstream_to_downstream` checks the dispatcher first — if a waiter exists, it routes the response directly to the waiter instead of sending it to stdout.
|
|
76
|
+
|
|
77
|
+
### Masking engine's dual-layer caching
|
|
78
|
+
|
|
79
|
+
`MaskingEngine.mask_response()` is synchronous (called from the proxy relay hot path). It writes new aliases to `_pending_writes` which are batch-flushed to SQLite every second by `_flush_loop` in `__main__.py`. This avoids blocking the relay on DB I/O.
|
|
80
|
+
|
|
81
|
+
### Traffic audit log (`traffic/`)
|
|
82
|
+
|
|
83
|
+
Tool-call records are persisted to a **separate** SQLite database (`~/.openmaskit/traffic.db`, configurable via `OPENMASKIT_TRAFFIC_DB_PATH`) so rotation/vacuum is isolated and a corrupt traffic DB can't kill masking config.
|
|
84
|
+
|
|
85
|
+
- **`TrafficStore` (`traffic/store.py`)** — async aiosqlite wrapper. Opens with `PRAGMA journal_mode=WAL` and `PRAGMA synchronous=NORMAL` for low-latency batched writes (WAL is critical here — the flush loop writes every 1s and rotation deletes are concurrent with reads from the GET endpoint). Unmasked args + unmasked response columns are encrypted at rest using the shared Fernet key from `security.TokenEncryption`. Masked args/response columns are plaintext (safe to read without the key).
|
|
86
|
+
- **`TrafficBuffer` (`traffic/buffer.py`)** — process-wide in-memory queue. `_intercept_response` (and the two block paths in `_intercept_request`) call `target.traffic_buffer.append(...)` synchronously on **terminal state only** — there are no pending/in-flight rows. `_traffic_flush_loop` in `__main__.py` drains the buffer to the store every 1s. This mirrors the `MaskingEngine._pending_writes` + `_flush_loop` idiom.
|
|
87
|
+
- **Rotation** — `_traffic_rotation_loop` enforces a global row cap (`OPENMASKIT_TRAFFIC_MAX_ROWS`, default 10000) every 5 minutes by deleting the oldest rows beyond the cap.
|
|
88
|
+
- **Lazy UI** — there is no WebSocket stream. The dashboard fetches pages on demand via `GET /api/targets/{target_name}/traffic?limit=&before=<id>`. The endpoint flushes the buffer before reading so the response reflects the latest writes.
|
|
89
|
+
- **Status values** — `ok`, `error`, `blocked`. Blocked entries (hidden tool or guardrail violation) record the unmasked args (encrypted) and put the block reason into `masked_response`.
|
|
90
|
+
|
|
91
|
+
### Hidden tools
|
|
92
|
+
|
|
93
|
+
Tools can be hidden per-server via the Web UI. Hidden tools are stored in the `hidden_tools` SQLite table and loaded into `TargetState.hidden_tools` at startup. When an agent calls a hidden tool, the proxy returns a `METHOD_NOT_FOUND` error without forwarding to upstream.
|
|
94
|
+
|
|
95
|
+
### Request interception pipeline
|
|
96
|
+
|
|
97
|
+
In `_intercept_request()`, tool calls pass through these stages in order:
|
|
98
|
+
1. **Hidden tool check** — blocks with `METHOD_NOT_FOUND` error
|
|
99
|
+
2. **Unmask arguments** — replaces aliases with real values
|
|
100
|
+
3. **Guardrail check** — validates unmasked args against patterns, blocks with `-32602` error if violated
|
|
101
|
+
4. **Injection application** — injects/overrides argument values before forwarding
|
|
102
|
+
|
|
103
|
+
### Argument guardrails
|
|
104
|
+
|
|
105
|
+
Block tool calls whose arguments match dangerous patterns. Stored in `guardrails` table, loaded into `MaskingEngine._guardrails`. Support three match types: `contains`, `equals`, `regex`. When `argument_name="*"`, scans all string values recursively.
|
|
106
|
+
|
|
107
|
+
### Argument injections
|
|
108
|
+
|
|
109
|
+
Silently inject or override argument values before forwarding. Stored in `injections` table, loaded into `MaskingEngine._injections`. Three modes: `set` (always override), `default` (only if absent), `append` (append to string/list). Values are JSON-encoded strings.
|
|
110
|
+
|
|
111
|
+
### Field stripping
|
|
112
|
+
|
|
113
|
+
Rules with `action="strip"` remove fields entirely from responses (no alias created, field is gone). Only applies to structured data (parsed JSON/repr); plain text blocks skip strip rules.
|
|
114
|
+
|
|
115
|
+
### Text parsing (`masking/parsing.py`)
|
|
116
|
+
|
|
117
|
+
The `try_parse_structured` utility attempts JSON first, then falls back to `ast.literal_eval` for Python repr strings (common in some MCP tool responses). Results are serialized back in their original format after masking.
|
|
118
|
+
|
|
119
|
+
### Persistence
|
|
120
|
+
|
|
121
|
+
Two SQLite databases:
|
|
122
|
+
|
|
123
|
+
**`~/.openmaskit/store.db`** (masking config + state):
|
|
124
|
+
- `mappings` — alias ↔ real_value (persists across restarts so the same real value always gets the same alias)
|
|
125
|
+
- `rules` — masking rules created via Web UI (merged with config-file rules at startup), supports `action` column (`mask` or `strip`)
|
|
126
|
+
- `response_mappers` — output mapper configs (regex or json_field_mask) with optional `config` JSON column
|
|
127
|
+
- `hidden_tools` — tools hidden per server (blocked from agent access)
|
|
128
|
+
- `guardrails` — argument validation rules that block tool calls matching dangerous patterns
|
|
129
|
+
- `injections` — argument injection rules that inject/override values before forwarding
|
|
130
|
+
- `mcp_servers` — marketplace and custom servers (id, name, config JSON, active flag). Used for both marketplace installs and custom targets added via the UI
|
|
131
|
+
|
|
132
|
+
**`~/.openmaskit/traffic.db`** (audit log, separate file by design):
|
|
133
|
+
- `traffic` — one row per terminal-state tool call. Columns: `id`, `ts`, `target_name`, `tool_name`, `request_id`, `status`, `duration_ms`, `args_enc` (BLOB, Fernet), `response_enc` (BLOB, Fernet), `masked_args` (TEXT), `masked_resp` (TEXT). Indexed on `(target_name, id DESC)`.
|
|
134
|
+
|
|
135
|
+
### Marketplace
|
|
136
|
+
|
|
137
|
+
The marketplace catalog is **fetched from a remote backend**, not a local file. There is **no `marketplace.json`** in the repo — entries live on `api.maskitmcp.com` and are paged in over HTTP. Installed servers are persisted in the `mcp_servers` SQLite table and connected at runtime via `TargetManager`.
|
|
138
|
+
|
|
139
|
+
- `backend_client.py` is the HTTP client. It targets `OPENMASKIT_MARKETPLACE_API_URL` (default `https://api.maskitmcp.com`) for catalog reads and `OPENMASKIT_AUTH_BACKEND_URL` (default `https://auth.maskitmcp.com`) for OAuth brokering.
|
|
140
|
+
- Catalog endpoint: `GET {marketplace_url}/api/marketplace/catalog?page=&size=&q=`. Returns `{data: [...], meta: {...}}`. Fail-open: failures return an empty page so the UI still renders.
|
|
141
|
+
- On install, the user provides any required env vars / credentials via a modal; config is saved to `mcp_servers` and the server is hot-connected via `TargetManager`.
|
|
142
|
+
- Servers can be deactivated (disconnected but config retained) and reactivated without re-entering credentials.
|
|
143
|
+
- Active marketplace servers are automatically reconnected on startup (`__main__.py` loads them from DB).
|
|
144
|
+
|
|
145
|
+
#### OAuth install modes
|
|
146
|
+
|
|
147
|
+
Catalog entries carry an `oauth_mode` field that determines the install flow. Three modes:
|
|
148
|
+
|
|
149
|
+
| `oauth_mode` | Flow | Redirect URI |
|
|
150
|
+
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
|
|
151
|
+
| `null` (hosted) | Hosted broker via `auth.maskitmcp.com`. `BackendClient.get_oauth_authorize_url` builds the URL; the broker handles code exchange; tokens land back over HTTPS. | `http://localhost:9473/oauth/callback/{handle}` |
|
|
152
|
+
| `"byo"` | Bring-your-own OAuth client. User pastes `client_id` / `client_secret` in the install modal; OpenMaskit runs the OAuth flow directly against the provider. | `http://localhost:3131/callback` |
|
|
153
|
+
| `"dcr"` | Dynamic Client Registration. OpenMaskit fetches the provider's well-known doc, registers a client at install time, then runs the OAuth flow against the provider. | `http://localhost:3131/callback` |
|
|
154
|
+
|
|
155
|
+
For BYO entries the catalog provides `meta.available_scopes` (`[{scope, label, required, default}]`) which the modal renders as a checklist; required scopes are locked-checked. For DCR entries scopes are discovered live from the well-known doc via `/api/oauth/discover` — catalog doesn't need to ship them. Any catalog entry — BYO, DCR, or a plain env-var stdio install — can ship `meta.setup_guide_url`; the install modal renders a "Setup guide ↗" link inline with the credentials/env-var prompt.
|
|
156
|
+
|
|
157
|
+
BYO and DCR installs both build a `transport: "http"` config with an `oauth` block (same shape as custom targets) and call `manager.add_target`. The existing `oauth/handler.py:create_oauth_provider` already handles both modes — BYO uses its manual branch (pre-seeds `client_info` from the config), DCR uses its discovery + DCR branch. Either way the local `OAuthCallbackServer` on port 3131 (`config.oauth_port`) receives the callback. **No new code paths in `oauth/handler.py` are needed for marketplace BYO/DCR — the install handler just shapes the config dict and the existing OAuth provider does the rest.**
|
|
158
|
+
|
|
159
|
+
Hosted-broker installs are tagged in storage with the placeholder `config.oauth.client_id == "managed-by-backend"`; this is how `marketplace_reauthorize` distinguishes them from BYO/DCR. Hosted entries also preserve `config.backend_id` so reauthorize can ask the broker for a fresh authorize URL.
|
|
160
|
+
|
|
161
|
+
#### Re-authorize
|
|
162
|
+
|
|
163
|
+
`POST /api/marketplace/{target_id}/reauthorize` triggers a fresh OAuth flow for an installed server. The Re-authorize button on each server card (`targets.html`) calls it.
|
|
164
|
+
|
|
165
|
+
- **BYO / DCR**: drops the `tokens` key from the encrypted `{store_dir}/oauth/{handle}.json` (preserves `client_info` so we don't re-prompt for creds or re-run DCR), `remove_target` + `add_target`, the browser-popup OAuth flow runs, returns `{connected: true}` once tokens are written. The token file is updated by `FileTokenStorage` from `oauth/handler.py`.
|
|
166
|
+
- **Hosted broker**: re-runs `BackendClient.get_oauth_authorize_url` and returns `{oauth_url}` for the UI to redirect to. The callback then re-exchanges via `oauth_callback.py` as on first install.
|
|
167
|
+
|
|
168
|
+
### Custom targets (runtime)
|
|
169
|
+
|
|
170
|
+
Users can add arbitrary MCP servers via the dashboard (Servers page → "Add Server"). These are also stored in the `mcp_servers` SQLite table and managed by `TargetManager`. Same hot-add/remove lifecycle as marketplace servers.
|
|
171
|
+
|
|
172
|
+
### TargetManager (`proxy/manager.py`)
|
|
173
|
+
|
|
174
|
+
Handles hot-adding and removing MCP server targets at runtime. Holds references to the shared task group and exit stack so it can spawn proxy loops and connect upstream without restarting. Called by both marketplace and custom target API routes.
|
|
175
|
+
|
|
176
|
+
### OAuth handler (`oauth/handler.py`)
|
|
177
|
+
|
|
178
|
+
Shared OAuth 2.1 callback server running on port 3131. Started once in `__main__.py` and shared across all targets. OAuth tokens are stored per-server at `{store_dir}/oauth/{server_id}.json`, Fernet-encrypted.
|
|
179
|
+
|
|
180
|
+
Two OAuth flow shapes go through this callback:
|
|
181
|
+
|
|
182
|
+
- **Marketplace servers (hosted broker).** OpenMaskit redirects the browser to `auth.maskitmcp.com`, which redirects to the provider, handles the code exchange server-side, and bounces back to the local callback with tokens. The local code never sees the provider's client_secret. See `BackendClient.get_oauth_authorize_url` / `exchange_code` / `refresh_oauth_token`.
|
|
183
|
+
- **Custom HTTP targets (DCR or direct).** The local handler runs the flow against the upstream provider directly using either Dynamic Client Registration or user-supplied credentials.
|
|
184
|
+
|
|
185
|
+
When adding BYO-credential or new DCR paths, the catalog entry signals the flow via an `oauth_mode` field (`"byo" | "dcr" | null`); the absence of the field implies the hosted-broker default.
|
|
186
|
+
|
|
187
|
+
### Bind host
|
|
188
|
+
|
|
189
|
+
All servers (web, MCP, OAuth callback) bind to the address in `OPENMASKIT_HOST` env var (default `127.0.0.1`). The Dockerfile sets this to `0.0.0.0` so the container is accessible from the host.
|
|
190
|
+
|
|
191
|
+
## Configuration
|
|
192
|
+
|
|
193
|
+
`openmaskit.yaml` at project root. Upstream supports `stdio` transport (spawns child process) and `http` transport (connects to remote MCP server with optional OAuth 2.1). If no config file exists, OpenMaskit starts with no pre-configured targets (marketplace/custom targets can still be added via UI).
|
|
194
|
+
|
|
195
|
+
## Web UI
|
|
196
|
+
|
|
197
|
+
### Pages
|
|
198
|
+
|
|
199
|
+
- `/` — Servers page: lists all connected targets (config, marketplace, custom), add/remove custom servers
|
|
200
|
+
- `/marketplace` — Browse and install servers from the catalog
|
|
201
|
+
- `/targets/{name}/tools` — Tool list for a specific server, connect agent button
|
|
202
|
+
- `/targets/{name}/tools/{tool}` — Tool detail: schema, try it out, masking rules, mappers, guardrails, injections
|
|
203
|
+
|
|
204
|
+
### API routes
|
|
205
|
+
|
|
206
|
+
All target-scoped routes: `/api/targets/{target_name}/...`
|
|
207
|
+
|
|
208
|
+
- `GET /api/targets/{target_name}/tools` — cached tool schemas
|
|
209
|
+
- `POST /api/targets/{target_name}/tools/call` — invoke a tool through the proxy (used by "Try it out")
|
|
210
|
+
- `GET/POST/PUT/DELETE /api/targets/{target_name}/rules` — masking rule CRUD (supports `action: "mask"|"strip"`)
|
|
211
|
+
- `GET/POST/PUT/DELETE /api/targets/{target_name}/mappers` — response mapper CRUD
|
|
212
|
+
- `GET/POST/PUT/DELETE /api/targets/{target_name}/guardrails` — argument guardrail CRUD
|
|
213
|
+
- `GET/POST/PUT/DELETE /api/targets/{target_name}/injections` — argument injection CRUD
|
|
214
|
+
- `GET /api/targets/{target_name}/mappings` — current alias mappings
|
|
215
|
+
- `GET/POST /api/targets/{target_name}/hidden_tools` — hide/unhide tools from the agent
|
|
216
|
+
- `GET /api/targets/{target_name}/traffic?limit=&before=<id>` — paginated audit log (cursor pagination; newest first; flushes pending buffer before reading)
|
|
217
|
+
|
|
218
|
+
Marketplace routes:
|
|
219
|
+
|
|
220
|
+
- `GET /api/marketplace` — catalog with install/active status
|
|
221
|
+
- `POST /api/marketplace/install` — install a server from catalog
|
|
222
|
+
- `POST /api/marketplace/activate` — reactivate a previously installed server
|
|
223
|
+
- `POST /api/marketplace/deactivate` — disconnect and deactivate
|
|
224
|
+
- `POST /api/marketplace/{target_id}/reauthorize` — kick off a fresh OAuth flow for an installed server (BYO/DCR clears tokens and runs the flow inline; hosted-broker returns a fresh `oauth_url`)
|
|
225
|
+
|
|
226
|
+
Custom target routes:
|
|
227
|
+
|
|
228
|
+
- `GET /api/custom-targets` — list custom targets
|
|
229
|
+
- `POST /api/custom-targets` — add a new custom target
|
|
230
|
+
- `POST /api/custom-targets/{target_id}/activate` — activate a deactivated custom target
|
|
231
|
+
- `POST /api/custom-targets/{target_id}/deactivate` — deactivate a custom target (keeps config)
|
|
232
|
+
- `POST /api/custom-targets/{target_id}/delete` — permanently remove a custom target
|
|
233
|
+
|
|
234
|
+
Server list routes:
|
|
235
|
+
|
|
236
|
+
- `GET /api/targets` — list all servers (active AND inactive) with runtime state merged from database
|
|
237
|
+
|
|
238
|
+
### Server lifecycle states
|
|
239
|
+
|
|
240
|
+
Servers can be in three states:
|
|
241
|
+
1. **Active** — Connected and running, appears in "Active Servers" section
|
|
242
|
+
2. **Inactive** — Disconnected but config retained in database, appears in "Inactive Servers" section, can be reactivated
|
|
243
|
+
3. **Deleted** — Permanently removed from database (custom servers only)
|
|
244
|
+
|
|
245
|
+
The Servers page (`/`) shows both active and inactive servers in separate sections. Users can:
|
|
246
|
+
- **Deactivate** any server (marketplace or custom) to temporarily disconnect it
|
|
247
|
+
- **Activate** any inactive server to reconnect using stored configuration
|
|
248
|
+
- **Delete** custom servers permanently (marketplace servers can only be deactivated)
|
|
249
|
+
- **View details** of inactive servers to see their configuration
|
|
250
|
+
|
|
251
|
+
### Container runtime compatibility
|
|
252
|
+
|
|
253
|
+
OpenMaskit auto-detects container runtimes (Docker, Podman, nerdctl, Finch) for containerized MCP servers:
|
|
254
|
+
|
|
255
|
+
- Detection happens at startup (`container.py` module)
|
|
256
|
+
- Commands starting with `docker` are automatically substituted with detected runtime
|
|
257
|
+
- Optional override via `container_runtime` config field in `openmaskit.yaml`
|
|
258
|
+
- Example: `docker run mcp-server` → `podman run mcp-server` (if Podman is detected)
|
|
259
|
+
- Logs show detected/configured runtime at startup
|
|
260
|
+
|
|
261
|
+
This makes containerized marketplace servers work across different environments without user intervention.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Contributing to OpenMaskit
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing! OpenMaskit is in early development and contributions of all kinds are welcome — bug reports, fixes, features, docs, and marketplace catalog entries.
|
|
4
|
+
|
|
5
|
+
## Reporting issues
|
|
6
|
+
|
|
7
|
+
1. Check [existing issues](https://github.com/OpenMaskitMCP/openmaskit/issues) to avoid duplicates.
|
|
8
|
+
2. Open a new issue with:
|
|
9
|
+
- A clear title and description
|
|
10
|
+
- Steps to reproduce (for bugs)
|
|
11
|
+
- Expected vs. actual behavior
|
|
12
|
+
- Relevant logs, screenshots, or config
|
|
13
|
+
|
|
14
|
+
## Development setup
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
git clone https://github.com/OpenMaskitMCP/openmaskit.git
|
|
18
|
+
cd openmaskit
|
|
19
|
+
uv sync
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Run OpenMaskit locally:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv run openmaskit # uses ./openmaskit.yaml if present
|
|
26
|
+
uv run openmaskit path/to/config.yaml
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then open the dashboard at `http://127.0.0.1:9473`.
|
|
30
|
+
|
|
31
|
+
## Testing
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv run pytest tests/ -v # all tests
|
|
35
|
+
uv run pytest tests/test_engine.py -v # one module
|
|
36
|
+
uv run pytest tests/test_engine.py::TestMaskingEngine::test_mask_structured_content -v # one test
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
New features and bug fixes should come with tests.
|
|
40
|
+
|
|
41
|
+
## Submitting a change
|
|
42
|
+
|
|
43
|
+
1. **Fork and branch**: `git checkout -b feature/your-feature-name`
|
|
44
|
+
2. **Code** — follow the conventions of the surrounding files. Python 3.12+, type hints where they clarify intent, docstrings on public APIs. Keep functions focused.
|
|
45
|
+
3. **Test** — run the suite above. Add cases for what you changed.
|
|
46
|
+
4. **Commit** with a clear message describing the *why*, not just the *what*.
|
|
47
|
+
5. **Push** and open a pull request. Reference any related issues.
|
|
48
|
+
|
|
49
|
+
### Pull request guidelines
|
|
50
|
+
|
|
51
|
+
- One feature or fix per PR.
|
|
52
|
+
- Update docs (`README.md`, `CLAUDE.md`) if your change affects how OpenMaskit is used or how it's structured.
|
|
53
|
+
- Be responsive to review feedback.
|
|
54
|
+
- For large changes, open an issue first to align on direction before writing code.
|
|
55
|
+
|
|
56
|
+
## Areas where help is wanted
|
|
57
|
+
|
|
58
|
+
- **Test coverage** — integration tests, fuzzing, concurrency stress tests
|
|
59
|
+
- **Documentation** — examples, tutorials, architecture diagrams
|
|
60
|
+
- **Edge cases** — binary data, large payloads, streaming responses
|
|
61
|
+
- **Security review** — threat modeling, timing attack analysis
|
|
62
|
+
- **Observability** — metrics, structured logging improvements
|
|
63
|
+
- **Marketplace** — more pre-configured MCP servers in the catalog
|
|
64
|
+
- **Bug fixes** — see the issue tracker
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
By contributing, you agree that your contributions are licensed under the Apache License, Version 2.0 (see [LICENSE](LICENSE)). This includes the patent grant in §3 of that license.
|
|
69
|
+
|
|
70
|
+
## Questions?
|
|
71
|
+
|
|
72
|
+
Open an issue or start a discussion in the repo — we're happy to help.
|