Drop-in MCP proxy that keeps your secrets out of the context window
Project description
OpenMaskit™
Secure MCP proxy that keeps your secrets out of AI context windows
⚠️ Early stage — expect breaking changes.
What it does
AI coding assistants see everything your MCP tools return — production hostnames, API keys, customer emails. OpenMaskit sits between your AI and your MCP servers and replaces sensitive values with stable aliases (host_1, email_2, api_key_1) so the model never sees the real data. When the agent passes an alias back in a tool call, OpenMaskit swaps in the real value before forwarding.
It also lets you block dangerous tool calls (guardrails), force safe defaults (injections), hide tools from agents, and install pre-configured servers from a marketplace.
Your browser doesn't support inline video. Watch the masking demo.
Mask sensitive fields before they reach the model.
Your browser doesn't support inline video. Watch the guardrail demo.
Block dangerous tool calls with guardrails.
AI Agent (Claude, Cursor, …)
│ HTTP :9474/{server}/mcp
▼
OpenMaskit ── Dashboard :9473
│ stdio / HTTP
▼
Real MCP Server
Quick start
OpenMaskit ships as a Python CLI. Requires Python 3.10+ — if you don't have it, the recommended installer (uvx) will fetch a compatible one for you.
With uv (recommended):
# Install uv if you don't have it (one line, no Python prereq):
curl -LsSf https://astral.sh/uv/install.sh | sh
# Then run OpenMaskit:
uvx openmaskit
uvx downloads a compatible Python (if needed), installs OpenMaskit in an isolated environment, and runs it — one command, no venv to manage.
With pipx (alternative):
pipx install openmaskit
openmaskit
pipx doesn't auto-fetch Pythons, so you'll need a 3.10+ interpreter available first.
Then open the dashboard at http://127.0.0.1:9473 — add servers from the marketplace, connect your AI agent with one click, and configure masking from the UI.
Configuration
OpenMaskit runs with no config at all — add servers from the dashboard.
If you'd rather pre-declare servers, drop a openmaskit.yaml next to where you run it:
targets:
time:
upstream:
transport: stdio
command: uvx
args: ["mcp-server-time"]
rules:
- tool_name: get_time
field_path: timezone
postgres:
upstream:
transport: stdio
command: docker
args: ["run", "-i", "--rm", "-e", "DATABASE_URI", "crystaldba/postgres-mcp"]
env:
DATABASE_URI: postgresql://user:pass@localhost:5432/mydb
guardrails:
- pattern: "DROP TABLE"
message: "Destructive SQL blocked"
datadog:
upstream:
transport: http
url: https://mcp.datadoghq.eu/api/unstable/mcp-server/mcp
headers:
DD-API-KEY: replace-with-your-key
DD-APPLICATION-KEY: replace-with-your-key
# Optional overrides (defaults shown)
web_port: 9473
mcp_port: 9474
# container_runtime: podman # auto-detected from docker/podman/nerdctl/finch
CLI
openmaskit # use ./openmaskit.yaml (or start empty)
openmaskit path/to/config.yaml # custom config
openmaskit -c path/to/config.yaml # same, via flag
openmaskit -w 9473 -m 9474 # override ports
openmaskit -s ~/.openmaskit/store.db # override SQLite path
openmaskit --version
Environment variables
| Variable | Purpose |
|---|---|
OPENMASKIT_HOST |
Bind address (default 127.0.0.1; Docker image uses 0.0.0.0) |
OPENMASKIT_ENCRYPTION_KEY |
Override the at-rest encryption key (otherwise read from ~/.openmaskit/.key) |
OPENMASKIT_LOG_FORMAT |
text (default) or json |
OPENMASKIT_SHUTDOWN_TIMEOUT |
Graceful shutdown deadline in seconds (default 30) |
OPENMASKIT_TRAFFIC_DB_PATH |
Path to the traffic audit database (default ~/.openmaskit/traffic.db) |
OPENMASKIT_TRAFFIC_MAX_ROWS |
Cap on stored audit rows (default 10000, oldest evicted first) |
OPENMASKIT_ALLOWED_ORIGINS |
Comma-separated extra origins allowed to call /api/* |
OPENMASKIT_MAX_REQUEST_BYTES |
Max HTTP request body size in bytes for the dashboard and MCP endpoints (default 1 MiB). Oversized requests get a 413. |
OPENMASKIT_MAX_PARSE_BYTES |
Max length of an upstream text block (in chars) handed to the JSON / Python-repr parser (default 1 MiB). Oversized blocks skip parsing and fall through as plain text. |
OPENMASKIT_DISABLE_MARKETPLACE |
Set to 1 to opt out of all calls to api.maskitmcp.com (catalog, server detail, version check). Custom servers continue to work. |
Dashboard
Everything is configurable from the UI at http://127.0.0.1:9473:
- Marketplace — one-click install of pre-configured MCP servers (with OAuth where needed).
- Custom servers — add stdio or HTTP servers at runtime; deactivate or delete without losing config.
- Tools — browse schemas, try calls, hide tools from agents, set per-tool masking rules, regex output mappers, guardrails, and argument injections.
- Traffic — encrypted, paginated audit log of recent calls.
Connect an AI agent to a server with the "Connect Agent" button on its page — it generates the snippet for Claude Code, Cursor, VS Code, Windsurf, JetBrains, Codex, or OpenCode.
💡 Built-in tutorials. Each configuration panel in the dashboard has a small help icon next to its title. Click it for a guided, step-by-step walkthrough of input masking, output mappers, guardrails, injections, and hiding tools — no docs to dig through.
Highlights
A few things worth knowing about:
- Container runtime auto-detection — Marketplace servers shipped as
docker run …automatically run on Podman, nerdctl, or Finch if that's what you have. No flag needed; override withcontainer_runtimeinopenmaskit.yamlif you want to pin a specific one. - Container lifecycle management — When you deactivate, delete, or stop OpenMaskit, any containers it spawned are stopped with it. No orphaned containers sitting around using ports.
- Stable aliases across restarts —
prod-db.internal.netalways becomeshost_1, the same alias your agent saw last week. Aliases are persisted, so multi-turn conversations stay coherent. - Encrypted traffic audit log — Every tool call is recorded with its unmasked args and response, Fernet-encrypted at rest. Lazy-loaded from the UI on demand and capped at 10k rows by default.
- Two OAuth install paths today — Marketplace servers can ship as BYO (paste your own
client_id/client_secret, OpenMaskit runs the flow locally) or DCR (OpenMaskit registers a client with the provider automatically). DCR discovery follows the MCP authorization spec — it probes the MCP URL, reads theWWW-Authenticatechallenge for the protected-resource metadata link, and follows it to the authorization server, which can live at a different host than the MCP endpoint (Supabase, Atlassian). The "Re-authorize" button on each server card runs a fresh flow when tokens expire. Setup guides for the BYO ones live at openmaskit.com/connect. A third hosted-broker path (zero-setup, with the OAuth code exchange handled byauth.maskitmcp.com) is fully implemented but currently disabled while we work through every security aspect of it — we'd rather ship it later and right than early and questionable. - Multi-tenant servers in the marketplace — Catalog entries that need a user-supplied identifier in the upstream URL (Supabase
project_ref, similar patterns for project- or workspace-scoped servers) declare ameta.paramslist; the install modal collects them and the install handler URL-encodes them onto the upstream URL. Discovery and DCR then run against the resolved URL so the authorization server sees the right resource identifier. - API-key auth for non-OAuth HTTP servers — Marketplace and custom HTTP servers can authenticate with static headers (Datadog
DD-API-KEY, StripeAuthorization: Bearer, etc.). Header values are stored Fernet-encrypted alongside everything else. - Hot add/remove servers — Marketplace installs, custom server adds, deactivations, and deletes all happen live. No restart needed.
- Argument guardrails and injections — Block
DROP TABLEbefore it leaves your machine; silently injectread_only: trueon every database call (if supported by the underlying MCP server). - Field stripping — Some fields shouldn't be aliased, they should just be gone. SSNs, credit cards — strip them from responses entirely.
- Localhost-safe by default —
Originallow-listing, CSRF protection, and OAuthstatevalidation are on out of the box, so a malicious webpage can't reach into your local OpenMaskit.
Docker
docker build -t openmaskit .
docker run -p 9473:9473 -p 9474:9474 openmaskit
The container supports HTTP-based MCP servers. For stdio servers (uvx, npx), run OpenMaskit natively.
Data safety
Two files matter:
~/.openmaskit/.key— encrypts OAuth tokens, the traffic audit log, and stored server configs (env vars, HTTP headers, OAuth secrets). Worth backing up — without it, the stored credentials can't be decrypted and servers will need to be re-installed.~/.openmaskit/store.db— masking rules, aliases, server configs.
~/.openmaskit/traffic.db is the audit log; safe to drop.
For production-style setups, hold the key in OPENMASKIT_ENCRYPTION_KEY instead of on disk.
Telemetry
OpenMaskit generates a random 25-character anonymous installation ID on first run, stored at ~/.openmaskit/.installation_id. The ID, along with the running version (in User-Agent), is sent to api.maskitmcp.com on three endpoints: catalog browse, server detail, and version check. The backend uses it to validate that requests come from a real OpenMaskit install and to count active installations.
That's it. No tool calls, masking rules, MCP server contents, response bodies, OAuth tokens, or usage events are sent. There's no account, no email, no IP-based linking. The backend is only contacted when the marketplace is used.
To opt out, set OPENMASKIT_DISABLE_MARKETPLACE=1. Catalog browse and server detail return empty; custom HTTP/stdio servers continue to work normally.
See CHANGELOG.md for upgrade notes between versions.
Contributing
Bug reports, feature requests, and pull requests are welcome. See CONTRIBUTING.md for development setup, testing, and PR guidelines.
To run from source instead of a published wheel:
git clone https://github.com/MaskitMCP/openmaskit.git
cd openmaskit
uv sync
uv run openmaskit
License
Apache 2.0 — see LICENSE and NOTICE.
Trademark
"OpenMaskit"™ and the OpenMaskit logo are trademarks of Amin Malekloo. The Apache 2.0 license does not grant trademark rights — see TRADEMARKS.md for permitted uses (forks must use a different name; "compatible with OpenMaskit" is fine).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openmaskit-0.5.0.tar.gz.
File metadata
- Download URL: openmaskit-0.5.0.tar.gz
- Upload date:
- Size: 798.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
93cc5786436f18370cb2939a87adcd3ba9a2d4a19f379e3857d960ddabcacd85
|
|
| MD5 |
ffd0e9b62a30ed6a547f42d16263b1ea
|
|
| BLAKE2b-256 |
854e6246144868c3d757b1ead29040de30f92c65e7078ff3b9db59ffd2ebd477
|
Provenance
The following attestation bundles were made for openmaskit-0.5.0.tar.gz:
Publisher:
publish.yml on MaskitMCP/openmaskit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openmaskit-0.5.0.tar.gz -
Subject digest:
93cc5786436f18370cb2939a87adcd3ba9a2d4a19f379e3857d960ddabcacd85 - Sigstore transparency entry: 1744526337
- Sigstore integration time:
-
Permalink:
MaskitMCP/openmaskit@bb04b3a6c8fa4351f67ebc148d026ce1833549aa -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/MaskitMCP
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bb04b3a6c8fa4351f67ebc148d026ce1833549aa -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file openmaskit-0.5.0-py3-none-any.whl.
File metadata
- Download URL: openmaskit-0.5.0-py3-none-any.whl
- Upload date:
- Size: 395.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9c8341a518f2cbdcd5255748d4ec4a010cbe820c71502b52beb2b9d1a1ae2ca
|
|
| MD5 |
a6fb3a513b13c6c934f1cbf86efcb7e7
|
|
| BLAKE2b-256 |
a7a0cbf485ad21eb312f875d666c13d80de186132e1012289d8583bf60af5f41
|
Provenance
The following attestation bundles were made for openmaskit-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on MaskitMCP/openmaskit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openmaskit-0.5.0-py3-none-any.whl -
Subject digest:
f9c8341a518f2cbdcd5255748d4ec4a010cbe820c71502b52beb2b9d1a1ae2ca - Sigstore transparency entry: 1744526431
- Sigstore integration time:
-
Permalink:
MaskitMCP/openmaskit@bb04b3a6c8fa4351f67ebc148d026ce1833549aa -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/MaskitMCP
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bb04b3a6c8fa4351f67ebc148d026ce1833549aa -
Trigger Event:
workflow_dispatch
-
Statement type: