mcp-bastion-python 1.0.14__tar.gz → 1.0.16__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.
Files changed (53) hide show
  1. mcp_bastion_python-1.0.16/.gitignore +44 -0
  2. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/PKG-INFO +183 -77
  3. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/README.md +182 -76
  4. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/dashboard/README.md +17 -2
  5. mcp_bastion_python-1.0.16/dashboard/app.py +1767 -0
  6. mcp_bastion_python-1.0.16/dashboard/static/chart.umd.js.map +1 -0
  7. mcp_bastion_python-1.0.16/dashboard/static/dashboard-app.js +1101 -0
  8. mcp_bastion_python-1.0.16/dashboard/static/mcp-bastian.png +0 -0
  9. mcp_bastion_python-1.0.16/dashboard/static/mcp-bastian.svg +10 -0
  10. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/pyproject.toml +3 -2
  11. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/__init__.py +1 -1
  12. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/cli.py +135 -6
  13. mcp_bastion_python-1.0.16/src/mcp_bastion/config.py +498 -0
  14. mcp_bastion_python-1.0.16/src/mcp_bastion/demo_dashboard_metrics.py +231 -0
  15. mcp_bastion_python-1.0.16/src/mcp_bastion/demo_live_traffic.py +110 -0
  16. mcp_bastion_python-1.0.16/src/mcp_bastion/doctor.py +97 -0
  17. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/errors.py +49 -0
  18. mcp_bastion_python-1.0.16/src/mcp_bastion/governance_beacon.py +56 -0
  19. mcp_bastion_python-1.0.16/src/mcp_bastion/middleware.py +870 -0
  20. mcp_bastion_python-1.0.16/src/mcp_bastion/otel.py +171 -0
  21. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/__init__.py +4 -0
  22. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/alerts.py +72 -2
  23. mcp_bastion_python-1.0.16/src/mcp_bastion/pillars/audit_hash_chain.py +141 -0
  24. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/audit_log.py +21 -0
  25. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/content_filter.py +23 -0
  26. mcp_bastion_python-1.0.16/src/mcp_bastion/pillars/external_policy.py +144 -0
  27. mcp_bastion_python-1.0.16/src/mcp_bastion/pillars/metrics.py +766 -0
  28. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/pii_redaction.py +15 -4
  29. mcp_bastion_python-1.0.16/src/mcp_bastion/pillars/pricing.py +73 -0
  30. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/semantic_cache.py +13 -7
  31. mcp_bastion_python-1.0.16/src/mcp_bastion/pillars/semantic_firewall.py +71 -0
  32. mcp_bastion_python-1.0.16/src/mcp_bastion/pillars/sensitive_classifier.py +107 -0
  33. mcp_bastion_python-1.0.16/src/mcp_bastion/pillars/telemetry_sinks.py +194 -0
  34. mcp_bastion_python-1.0.16/src/mcp_bastion/policy_simulator.py +171 -0
  35. mcp_bastion_python-1.0.16/src/mcp_bastion/redteam.py +168 -0
  36. mcp_bastion_python-1.0.16/src/mcp_bastion/tenant.py +44 -0
  37. mcp_bastion_python-1.0.14/.gitignore +0 -24
  38. mcp_bastion_python-1.0.14/dashboard/app.py +0 -994
  39. mcp_bastion_python-1.0.14/src/mcp_bastion/config.py +0 -274
  40. mcp_bastion_python-1.0.14/src/mcp_bastion/middleware.py +0 -336
  41. mcp_bastion_python-1.0.14/src/mcp_bastion/otel.py +0 -78
  42. mcp_bastion_python-1.0.14/src/mcp_bastion/pillars/metrics.py +0 -344
  43. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/LICENSE +0 -0
  44. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/NOTICE +0 -0
  45. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/dashboard/static/chart.umd.min.js +0 -0
  46. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/base.py +0 -0
  47. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/circuit_breaker.py +0 -0
  48. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/cost_tracker.py +0 -0
  49. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/prompt_guard.py +0 -0
  50. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/rate_limit.py +0 -0
  51. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/rbac.py +0 -0
  52. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/replay_guard.py +0 -0
  53. {mcp_bastion_python-1.0.14 → mcp_bastion_python-1.0.16}/src/mcp_bastion/pillars/schema_validation.py +0 -0
@@ -0,0 +1,44 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ *.egg
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ node_modules/
9
+ *.log
10
+ .env
11
+ .env.local
12
+ .env.*.local
13
+ .venv/
14
+ venv/
15
+ .DS_Store
16
+ *.tsbuildinfo
17
+ .coverage
18
+ htmlcov/
19
+ coverage.xml
20
+ .pytest_cache/
21
+ .mypy_cache/
22
+
23
+ # Ad-hoc Windows scripts and wrappers (use `git add -f path` to commit one on purpose)
24
+ *.ps1
25
+ !run-dashboard.ps1
26
+ !scripts/validate-ci-local.ps1
27
+ !tests/run_inspector_test.ps1
28
+ *.cmd
29
+ !run-dashboard.cmd
30
+ *.bat
31
+
32
+ # Windows / editors / merge cruft
33
+ Thumbs.db
34
+ ehthumbs.db
35
+ Desktop.ini
36
+ *.stackdump
37
+ *.orig
38
+ *.bak
39
+ *~
40
+ .vs/
41
+
42
+ # Local config (optional: remove to track bastion.yaml)
43
+ # bastion.yaml
44
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-bastion-python
3
- Version: 1.0.14
3
+ Version: 1.0.16
4
4
  Summary: Security middleware for MCP servers protecting LLM agents from prompt injection, resource exhaustion, and PII leakage
5
5
  Project-URL: Homepage, https://github.com/mcp-bastion/mcp-bastion
6
6
  Project-URL: Repository, https://github.com/mcp-bastion/mcp-bastion
@@ -97,22 +97,30 @@ Provides-Extra: policy
97
97
  Requires-Dist: pyyaml>=6.0; extra == 'policy'
98
98
  Description-Content-Type: text/markdown
99
99
 
100
+ <p align="center">
101
+ <img src="images/mcp-bastian.png" alt="MCP-Bastion" width="520" />
102
+ </p>
103
+
100
104
  # MCP-Bastion
101
105
 
102
106
  <!-- mcp-name: io.github.vaquarkhan/mcp-bastion -->
103
107
 
104
- [![Total Downloads](https://img.shields.io/badge/total%20downloads-565-brightgreen)](https://pepy.tech/projects/mcp-bastion-python)
105
- [![PyPI Total Downloads](https://img.shields.io/pypi/dd/mcp-bastion-python?label=daily)](https://pypi.org/project/mcp-bastion-python/)
106
- [![PyPI Monthly Downloads](https://img.shields.io/pypi/dm/mcp-bastion-python?label=monthly)](https://pypi.org/project/mcp-bastion-python/)
108
+ [![Total Downloads](https://img.shields.io/badge/total%20downloads-3.4K-brightgreen)](https://pepy.tech/projects/mcp-bastion-python)
109
+
107
110
  [![PyPI Version](https://img.shields.io/pypi/v/mcp-bastion-python)](https://pypi.org/project/mcp-bastion-python/)
108
- [![npm Version](https://img.shields.io/npm/v/@mcp-bastion/core)](https://www.npmjs.com/package/@mcp-bastion/core)
111
+ [![PyPI downloads](https://img.shields.io/pepy/dt/mcp-bastion-python)](https://pepy.tech/projects/mcp-bastion-python)
112
+ [![Python](https://img.shields.io/pypi/pyversions/mcp-bastion-python)](https://pypi.org/project/mcp-bastion-python/)
113
+ [![CI](https://img.shields.io/github/actions/workflow/status/vaquarkhan/MCP-Bastion/ci.yml?branch=main&label=CI)](https://github.com/vaquarkhan/MCP-Bastion/actions/workflows/ci.yml)
109
114
  [![License: Source Available](https://img.shields.io/badge/license-Source%20Available-orange.svg)](LICENSE)
110
- [![PyPI Downloads](https://static.pepy.tech/personalized-badge/mcp-bastion-python?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/mcp-bastion-python)
111
115
 
112
116
  **Enterprise-Grade Security Middleware for the Model Context Protocol**
113
117
 
114
118
  > Releases are published to npm and PyPI via GitHub Actions on tag push.
115
119
 
120
+ **Documentation:** structured paths for **policy** and **LLM integration** live in [docs/README.md](docs/README.md) and [docs/index.md](docs/index.md). **Community:** open a GitHub **Issue** for bugs or gaps, a **Discussion** for integration questions (if enabled on the repo), or a **PR** for docs and examples—those help every adopter.
121
+
122
+ **Hello world (minimal Bastion on code):** see **[docs/QUICK_START.md](docs/QUICK_START.md)** — FastMCP helper `secure_fastmcp(mcp)` (wires `MCPBastionMiddleware` into tool dispatch), or two-line `build_middleware_from_config()` for full `bastion.yaml` policy, plus a **CI validate** snippet for pipeline-driven installs.
123
+
116
124
  The Model Context Protocol (MCP) has rapidly become the universally accepted standard for connecting AI agents to enterprise databases and APIs. However, this connectivity introduces a massive new attack surface: unpredictable, non-deterministic agentic behavior.
117
125
 
118
126
  MCP-Bastion is a lightweight, drop-in security middleware designed to wrap around any existing Python or TypeScript MCP server. Instead of relying on passive logging, human-in-the-loop approvals, or third-party APIs, MCP-Bastion provides an active, 100% local defense layer. It intercepts standard JSON-RPC traffic to stop threats before they cross the enterprise boundary.
@@ -153,25 +161,64 @@ Drop-in middleware, under 5ms overhead.
153
161
 
154
162
  Hooks into MCP SDKs (TypeScript, Python) and FastMCP via standard middleware. No business logic changes.
155
163
 
156
- ### All Features
164
+ ### Complete feature catalog
157
165
 
158
- | Feature | Description |
159
- |---------|-------------|
160
- | Prompt injection | Block jailbreaks via Meta PromptGuard |
161
- | PII redaction | Mask many entity types via Presidio (e.g. SSN, email, phone, credit card, passport, IBAN, medical license, and more—see Presidio docs) |
162
- | Rate limiting | Max iterations, timeout, token budget |
163
- | Audit logging | Log who, what, when, blocked/allowed |
164
- | Content filter | Block paths/code/URLs, plus allowlist and denylist patterns |
165
- | Circuit breaker | Disable failing tools after N failures |
166
- | RBAC | Tool-level permissions by role |
167
- | Schema validation | Validate tool input types |
168
- | Replay guard | Block duplicate nonces |
169
- | Cost tracker | Per-session cost budget |
170
- | Semantic cache | Cache similar queries |
166
+ **Pillar definitions:** Security controls, `bastion.yaml` sections, and how they relate to dashboard health rows are documented in [docs/PILLARS.md](docs/PILLARS.md) (canonical reference; avoids ambiguous “total pillar” counts). The same page lists **extended** features restored in 1.0.16+ (semantic firewall, sensitive classifier, external policy, edge auth, tool allowlist, session scope, tool metadata guard, multi-tenant, audit hash chain, pricing hooks, telemetry sinks, **red team** and **doctor** CLIs, etc.).
167
+
168
+ > **Deeper context:** [docs/SECURITY_OBSERVABILITY.md](docs/SECURITY_OBSERVABILITY.md) **OWASP MCP Top 10** alignment, attack scenarios, and SIEM/log integrations. **Framework add-ons** (LangChain, OpenAI, Bedrock, …) are listed under [Framework Integrations](#framework-integrations) below.
169
+
170
+ #### Threat prevention & content safety
171
+
172
+ | Feature | What you get |
173
+ |--------|----------------|
174
+ | **Prompt injection defense** | Meta **PromptGuard** scores tool arguments; malicious / jailbreak-style payloads can be blocked before execution (local inference, no third-party API). |
175
+ | **Content filter** | Block **shell/code execution** patterns, **sensitive file paths**, and **URLs**; optional **allowlist** / **denylist** regex or substring rules. |
176
+ | **PII redaction** | **Microsoft Presidio** detects many entity types in outbound tool/resource text (SSN, email, phone, cards, passport, IBAN, licenses, etc.—see Presidio docs). |
177
+
178
+ #### Access control, integrity & abuse
179
+
180
+ | Feature | What you get |
181
+ |--------|----------------|
182
+ | **RBAC** | **Tool-level** allow/deny by **role** (from request metadata); map roles to tool names in `bastion.yaml`. |
183
+ | **Schema validation** | Validate `tools/call` arguments against **JSON Schema** before the tool runs (block malformed or bypass attempts). |
184
+ | **Replay guard** | **Nonce** tracking to reject replayed requests (configurable **require_nonce**). |
185
+ | **Rate limiting** | **Token-bucket** style limits: **max iterations** per session, **timeout**, **token budget**—stops runaway loops and brute-force patterns. |
186
+ | **Circuit breaker** | Stop calling tools that fail repeatedly (limits blast radius of bad upstreams or poisoned tools). |
187
+
188
+ #### FinOps & performance
189
+
190
+ | Feature | What you get |
191
+ |--------|----------------|
192
+ | **Cost tracker** | Per-**session** and optional per-**day** USD caps; blocks when budget is exceeded. |
193
+ | **Semantic cache** | Optional **similarity-based** caching for tool semantics (reduce duplicate expensive calls). |
194
+ | **Low overhead** | Middleware on the hot path targeting **&lt;5 ms** typical overhead (see [docs/METRICS.md](docs/METRICS.md)). |
195
+
196
+ #### Audit, metrics & alerting
197
+
198
+ | Feature | What you get |
199
+ |--------|----------------|
200
+ | **Audit logging** | Structured **allow/deny** decisions with **reason**, **tool**, **tenant_id**, **trace_id**, **request_id**—feed SOC / compliance. |
201
+ | **Alert sinks** | **Slack** incoming webhook; **generic HTTP** webhooks (PagerDuty, Teams, custom APIs); **multiple URLs**; **retry**, **backoff**, **timeout** in `bastion.yaml`. |
202
+ | **In-memory metrics** | **Global MetricsStore**: requests, blocks, PII counts, cost, per-tool stats, latency samples, rolling **time series** buckets. |
203
+ | **Real-time dashboard** | **Web UI** with a top **KPI summary** (totals, block %, top threat, active users/tenants), **traffic & block charts**, **blocked-by-reason/kind** (with readable reasons / tooltips), **PII by entity** (severity-style coloring, e.g. high-risk types emphasized), **top tools**, **cost by user**, **latency P50/P95/P99**, **forensics table** (tenant filter, trace/replay helpers), **recent alerts**, **insights & anomalies** (heuristic signals), **dark/light theme**, **Prometheus** `/metrics`, **JSON** `/api/metrics`, loading/empty guidance instead of a blank first paint. |
204
+ | **OpenTelemetry** | Optional **OTLP** span export — `pip install mcp-bastion-python[otel]` — [docs/OTEL.md](docs/OTEL.md). |
205
+
206
+ #### Policy, packaging & developer experience
207
+
208
+ | Feature | What you get |
209
+ |--------|----------------|
210
+ | **Policy-as-code** | Single **`bastion.yaml`**: toggles for all request-path controls plus audit, alerts, and hot reload ([docs/PILLARS.md](docs/PILLARS.md)); load via `load_config` / `build_middleware_from_config`. |
211
+ | **Hot reload** | Optional **reload `bastion.yaml` on change** without restarting the MCP server ([docs/POLICY_AS_CODE.md](docs/POLICY_AS_CODE.md)). |
212
+ | **Composable middleware** | **`compose_middleware`** ordering; **`MCPBastionMiddleware`** flags for each pillar. |
213
+ | **CLI** | **`mcp-bastion validate`**, **`serve`** (HTTP MCP), **`dashboard`** (optional **`--reload`** / **`--demo`**), **`redteam`**, **`doctor`** — [docs/CLI.md](docs/CLI.md). |
214
+ | **Python + TypeScript** | **`mcp-bastion-python`** on PyPI; **`@mcp-bastion/core`** on npm for TypeScript MCP servers (rate limits in-process; prompt/PII via optional sidecar). |
215
+ | **Containers** | **Dockerfile**, **docker-compose** profiles (proxy + optional dashboard) — [DOCKER.md](DOCKER.md). **Prebuilt images (GHCR):** [`mcp-bastion-proxy`](https://github.com/vaquarkhan/MCP-Bastion/pkgs/container/mcp-bastion-proxy), [`mcp-bastion-dashboard`](https://github.com/vaquarkhan/MCP-Bastion/pkgs/container/mcp-bastion-dashboard) — published on each `v*` tag ([publish-docker.yml](.github/workflows/publish-docker.yml)). |
171
216
 
172
217
  ### Real-Time Dashboard and Alerts
173
218
 
174
- Run the optional dashboard for a live view of requests, blocked count, PII redacted, cost, top tools, and recent alerts:
219
+ **🎥 Demo (screen recording):** [Watch on Vimeo](https://vimeo.com/1186084574) overview of the dashboard and metrics (link opens the player on Vimeo).
220
+
221
+ Run the optional dashboard for a live view of requests, blocked count, PII redacted, cost, top tools, and recent alerts.
175
222
 
176
223
  ```bash
177
224
  mcp-bastion dashboard --port 7000
@@ -180,41 +227,75 @@ mcp-bastion dashboard --port 7000
180
227
 
181
228
  | URL | What it returns |
182
229
  |-----|-----------------|
183
- | [http://localhost:7000/](http://localhost:7000/) | Visual dashboard with charts (requests, blocked by reason, top tools, cost by user, recent alerts) |
184
- | [http://localhost:7000/api/metrics](http://localhost:7000/api/metrics) | JSON: `requests_total`, `blocked_total`, `blocked_pct`, `pii_redacted_total`, `cost_total`, `blocked_by_reason`, `top_tools`, `cost_by_user`, `alerts` |
230
+ | [http://localhost:7000/](http://localhost:7000/) | Charts, KPIs, **forensics**, **insights & anomalies**, alerts, **tool drill-down** (signal vs global block rate), theme toggle |
231
+ | [http://localhost:7000/api/metrics](http://localhost:7000/api/metrics) | JSON: `requests_total`, `blocked_total`, `blocked_pct`, `pii_redacted_total`, `cost_total`, `blocked_by_reason`, `blocked_by_kind`, `top_tools`, `tool_stats`, `cost_by_user`, `time_series`, `latency_ms`, `dashboard_insights`, `blocked_incidents`, `alerts`, … |
185
232
  | [http://localhost:7000/api/health](http://localhost:7000/api/health) | `{"status": "ok"}` |
186
233
  | [http://localhost:7000/metrics](http://localhost:7000/metrics) | Prometheus text format for Grafana/Datadog |
187
234
 
188
- ![MCP-Bastion Dashboard](docs/images/dashboard.png)
189
-
190
- *Dashboard: total requests, blocked count and %, PII redacted, cost; blocked-by-reason bars; top tools; cost by user; recent alerts.*
235
+ *Dashboard: total requests, blocked count and %, PII redacted, cost; blocked-by-reason bars; top tools; cost by user; recent alerts — open [http://localhost:7000/](http://localhost:7000/) while `mcp-bastion dashboard` is running.*
191
236
 
192
237
  - **Alerts:** Slack webhook and cost-threshold alerts. See [dashboard/README.md](dashboard/README.md).
193
238
 
194
239
  ### Documentation: Use Cases, Attacks, Metrics, Tutorials
195
240
 
241
+ **Adoption paths (start-to-finish):**
242
+
243
+ | Goal | Read in order |
244
+ |------|----------------|
245
+ | **Policy-as-code (`bastion.yaml`)** | [docs/PILLARS.md](docs/PILLARS.md) → [docs/POLICY_AS_CODE.md](docs/POLICY_AS_CODE.md) → `bastion.yaml.example` → [docs/CLI.md](docs/CLI.md) (`validate`) |
246
+ | **LLM clients (OpenAI, Claude, Gemini, …)** | [docs/LLM_INTEGRATION.md](docs/LLM_INTEGRATION.md) → [docs/INTEGRATION_MODELS.md](docs/INTEGRATION_MODELS.md) → [examples/](examples/) (`llm_*.py`) |
247
+ | **FastMCP / TypeScript / third-party MCP** | [docs/TUTORIALS.md](docs/TUTORIALS.md) → [docs/DETAILED_TUTORIAL.md](docs/DETAILED_TUTORIAL.md) |
248
+ | **Fleet rollout of `bastion.yaml` + SIEM / SOC audit** | [docs/SECURITY_OBSERVABILITY.md](docs/SECURITY_OBSERVABILITY.md) → [docs/POLICY_AS_CODE.md](docs/POLICY_AS_CODE.md) |
249
+ | **Minimal “hello world” + CI / registries** | [docs/QUICK_START.md](docs/QUICK_START.md) → [examples/ci/README.md](examples/ci/README.md) → [docs/DISCOVERY.md](docs/DISCOVERY.md) |
250
+
251
+ Full index: **[docs/README.md](docs/README.md)** (docs hub) · published site entry: **[docs/index.md](docs/index.md)** · quick wrap: **[docs/QUICK_START.md](docs/QUICK_START.md)** · discovery: **[docs/DISCOVERY.md](docs/DISCOVERY.md)** · contribute: **[CONTRIBUTING.md](CONTRIBUTING.md)**.
252
+
196
253
  | Doc | Description |
197
254
  |-----|-------------|
198
255
  | [docs/index.md](docs/index.md) | GitHub Pages-ready docs home |
256
+ | [docs/POLICY_AS_CODE.md](docs/POLICY_AS_CODE.md) | **`bastion.yaml` reference**: keys, examples, hot reload, alerts |
257
+ | [docs/LLM_INTEGRATION.md](docs/LLM_INTEGRATION.md) | **LLM integration**: OpenAI, Claude, Gemini, Mistral, Grok (stdio + HTTP configs) |
199
258
  | [docs/DETAILED_TUTORIAL.md](docs/DETAILED_TUTORIAL.md) | Step-by-step implementation tutorial for new teams |
200
259
  | [docs/USE_CASES.md](docs/USE_CASES.md) | Real use cases: enterprise gateway, LLM products, internal tools, SaaS, compliance |
201
260
  | [docs/ATTACK_PREVENTION.md](docs/ATTACK_PREVENTION.md) | Examples showing how MCP-Bastion prevents real attacks (injection, PII leak, rate exhaustion, path traversal, RBAC, replay) |
261
+ | [docs/PILLARS.md](docs/PILLARS.md) | Canonical pillar counts: **18** request-path features (10 core + 8 extended), **14** dashboard `pillar_health` rows, **20+** `bastion.yaml` top-level areas — see the doc for scope |
262
+ | [docs/SUPPLY_CHAIN.md](docs/SUPPLY_CHAIN.md) | CI merge gates, releases, npm provenance, PyPI Trusted Publishing |
263
+ | [docs/INTEGRATION_MODELS.md](docs/INTEGRATION_MODELS.md) | Middleware + `bastion.yaml` vs “change base URL”; bridge for Python, TS, Desktop, HTTP, integrations |
264
+ | [examples/ci/README.md](examples/ci/README.md) | Copy-paste GitHub Actions snippet to run `mcp-bastion validate` on your policy file |
265
+ | [docs/REDTEAM.md](docs/REDTEAM.md) | Interpreting harness / red-team scores; which `bastion.yaml` pillars to enable; Node vs Python **scope** (`packages/core/README.md`) |
266
+ | [docs/SECURITY_OBSERVABILITY.md](docs/SECURITY_OBSERVABILITY.md) | **OWASP MCP Top 10**, integration hooks, **fleet-scale `bastion.yaml` rollout**, **SIEM / SOC audit** patterns |
202
267
  | [docs/METRICS.md](docs/METRICS.md) | Performance overhead (&lt;5ms) and effectiveness metrics (dashboard, Prometheus, OTEL) |
203
268
  | [docs/TUTORIALS.md](docs/TUTORIALS.md) | Tutorials: integrating with FastMCP, TypeScript, GitHub MCP, and open-source MCP servers |
204
269
  | [docs/GITHUB_PAGES.md](docs/GITHUB_PAGES.md) | Publish docs as a GitHub Pages website from this same repo |
270
+ | [docs/QUICK_START.md](docs/QUICK_START.md) | Minimal FastMCP / `bastion.yaml` / CI snippets (time-to-value) |
271
+ | [docs/DISCOVERY.md](docs/DISCOVERY.md) | Registry and ecosystem discovery checklist |
272
+ | [docs/ROADMAP.md](docs/ROADMAP.md) | High-level directions; execution tracked in GitHub Issues |
273
+ | [CONTRIBUTING.md](CONTRIBUTING.md) | Contributor guide and **`good first issue`** ideas |
205
274
 
206
275
  ### One-Line Docker
207
276
 
277
+ **Prebuilt images (after the first [publish-docker](.github/workflows/publish-docker.yml) run, usually on a `v*` release tag):**
278
+
279
+ ```bash
280
+ docker pull ghcr.io/vaquarkhan/mcp-bastion-proxy:latest
281
+ docker run -p 8080:8080 ghcr.io/vaquarkhan/mcp-bastion-proxy:latest
282
+ # Dashboard (optional, port 7000):
283
+ # docker pull ghcr.io/vaquarkhan/mcp-bastion-dashboard:latest
284
+ # docker run -p 7000:7000 ghcr.io/vaquarkhan/mcp-bastion-dashboard:latest
285
+ ```
286
+
287
+ **Build locally** (any revision):
288
+
208
289
  ```bash
209
290
  docker build -t mcp-bastion/proxy .
210
291
  docker run -p 8080:8080 mcp-bastion/proxy
211
292
  ```
212
293
 
213
- MCP endpoint: `http://localhost:8080/mcp`. Use `docker-compose up -d` for proxy; add `--profile with-dashboard` for the dashboard. See [DOCKER.md](DOCKER.md).
294
+ MCP endpoint: `http://localhost:8080/mcp`. Use `docker-compose up -d` for proxy; add `--profile with-dashboard` for the dashboard. See [DOCKER.md](DOCKER.md) (includes GHCR pull commands and **package** links for forks: replace `vaquarkhan` with your org or user in image paths).
214
295
 
215
296
  ### Policy-as-Code (bastion.yaml)
216
297
 
217
- Single config file controls all pillars. Copy `bastion.yaml.example` to `bastion.yaml`, then:
298
+ Single config file controls policy (see [docs/PILLARS.md](docs/PILLARS.md) for pillar definitions). Copy `bastion.yaml.example` to `bastion.yaml`, then:
218
299
 
219
300
  ```python
220
301
  from mcp_bastion import build_middleware_from_config
@@ -235,43 +316,46 @@ mcp-bastion dashboard --port 7000 # run metrics dashboard
235
316
 
236
317
  See [docs/CLI.md](docs/CLI.md).
237
318
 
238
- ### OpenTelemetry
239
-
240
- Set `OTEL_EXPORTER_OTLP_ENDPOINT` to export tool-call spans to OTLP. Install optional deps: `pip install mcp-bastion-python[otel]`. See [docs/OTEL.md](docs/OTEL.md).
319
+ ### Continuous integration (this repository)
241
320
 
242
- ### Webhook alerts
321
+ On every pull request and push to `main`, [`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs:
243
322
 
244
- Use Slack webhook, a single generic webhook (`webhook_url` or `BASTION_WEBHOOK_URL`), or multiple URLs (`alerts.webhooks` in bastion.yaml). Each blocked event can POST to your endpoint (e.g. PagerDuty, custom API). Webhook delivery now supports retry/backoff controls in `bastion.yaml` (`retry_attempts`, `retry_backoff_seconds`, `retry_backoff_max_seconds`, `timeout_seconds`).
323
+ 1. `pip install -e ".[dev,policy,dashboard]"` install the Python package with tests, YAML policy loading, and FastAPI for dashboard tests.
324
+ 2. `mcp-bastion validate --config bastion.yaml.example` — ensure the example policy file loads.
325
+ 3. `pytest --cov=mcp_bastion --cov-fail-under=92` — full Python test suite with **≥92%** line coverage on `src/mcp_bastion` (see `[tool.coverage.*]` in `pyproject.toml` for measured paths and gates).
326
+ 4. `npm ci` and `npm test` — TypeScript workspace tests.
245
327
 
246
- ---
328
+ To validate **your** repo’s `bastion.yaml` in CI without cloning MCP-Bastion, see [examples/ci/README.md](examples/ci/README.md).
247
329
 
248
- ## Why MCP-Bastion (Competitive Comparison)
330
+ ### OpenTelemetry
249
331
 
250
- Early security packages (mcp-guardian, mcp-shield) focus on logging or static scanning. MCP-Bastion adds an active defense layer.
332
+ Set `OTEL_EXPORTER_OTLP_ENDPOINT` to export tool-call spans to OTLP. Install optional deps: `pip install mcp-bastion-python[otel]`. See [docs/OTEL.md](docs/OTEL.md).
251
333
 
252
- ### 1. Active Defense vs. Passive Logging
334
+ ### Webhook alerts and external logging
253
335
 
254
- | The Competition | MCP-Bastion |
255
- |-----------------|-------------|
256
- | Tools like mcp-guardian focus on tracing, logging, human-in-the-loop approvals. | Automated interception. MCP-Bastion scrubs PII before it leaves the server. |
336
+ Use **Slack** (`slack_webhook` / `SLACK_WEBHOOK_URL`), a **generic HTTP webhook** (`webhook_url` / `BASTION_WEBHOOK_URL`), or **multiple URLs** (`alerts.webhooks` in `bastion.yaml`). POSTs can drive **PagerDuty**, **Microsoft Teams**, **Datadog Events**, or any HTTP collector your SIEM exposes. Configure **retry/backoff** in `bastion.yaml` (`retry_attempts`, `retry_backoff_seconds`, `retry_backoff_max_seconds`, `timeout_seconds`).
257
337
 
258
- ### 2. Local Inference vs. Third-Party APIs
338
+ **Metrics & traces:** scrape the dashboard **`/metrics`** (Prometheus) or poll **`/api/metrics`** (JSON) for Grafana, Datadog, or custom pollers. Set **`OTEL_EXPORTER_OTLP_ENDPOINT`** for traces to Jaeger, Honeycomb, **AWS ADOT**, etc. (`pip install mcp-bastion-python[otel]`). Route **Python logs** (including `LoggingAlertSink`) through **Fluent Bit**, **Vector**, or the **CloudWatch agent** into Splunk / Elastic / CloudWatch Logs.
259
339
 
260
- | The Competition | MCP-Bastion |
261
- |-----------------|-------------|
262
- | Many guardrail proxies send prompts to external APIs to check for malice. | PromptGuard-86M and Presidio run locally. Data stays on your network. |
340
+ See **[docs/SECURITY_OBSERVABILITY.md](docs/SECURITY_OBSERVABILITY.md)** for the full integration table and **OWASP MCP Top 10** alignment.
263
341
 
264
- ### 3. Stateful Denial of Wallet Protection
342
+ ---
265
343
 
266
- | The Competition | MCP-Bastion |
267
- |-----------------|-------------|
268
- | Most tools focus on static vulns or basic rate limits. | Tracks tool call history per session. Stops runaway loops before they burn API budget. |
344
+ <p align="center">
345
+ <img
346
+ src="images/mcp-bastian-features.png"
347
+ alt="MCP-Bastion features at a glance — pillars, dashboard, and integrations"
348
+ width="920"
349
+ style="max-width:100%; height:auto; border-radius:12px;"
350
+ />
351
+ </p>
269
352
 
270
- ### 4. Drop-in Middleware vs. Standalone Gateway
353
+ ## Why MCP-Bastion
271
354
 
272
- | The Competition | MCP-Bastion |
273
- |-----------------|-------------|
274
- | Some solutions need standalone proxy servers. | Library hooks into `server.setRequestHandler` (TS) or middleware (Python). No extra infra. |
355
+ - **Active enforcement** Intercepts MCP tool traffic so policies (prompt injection checks, PII handling, content rules, RBAC, and more) run before tools execute and before sensitive results propagate.
356
+ - **Local-first classification** — PromptGuard and Presidio run in your environment; you are not required to send prompts to a third-party API for guardrail scoring.
357
+ - **Stateful guardrails** Per-session rate limits, iteration caps, token budgets, and cost tracking to reduce runaway loops and unexpected spend.
358
+ - **Composable integration** — Use `bastion.yaml` with `build_middleware_from_config()` or wire `MCPBastionMiddleware` / `wrapWithMcpBastion` in Python or TypeScript. For a separate process in front of an upstream MCP server, use a wrapper or proxy you control; see [docs/INTEGRATION_MODELS.md](docs/INTEGRATION_MODELS.md).
275
359
 
276
360
  ---
277
361
 
@@ -294,7 +378,7 @@ Early security packages (mcp-guardian, mcp-shield) focus on logging or static sc
294
378
  | File | Purpose |
295
379
  |------|---------|
296
380
  | `examples/python_server_example.py` | Minimal middleware chain |
297
- | `examples/full_demo.py` | All 11 features (rate limit, PII, RBAC, etc.) |
381
+ | `examples/full_demo.py` | Multi-pillar stack (core toggles: rate limit, PII, RBAC, … — see **docs/PILLARS.md**) |
298
382
  | `examples/llm_server.py` | Shared MCP server for LLM clients |
299
383
  | `examples/llm_openai_example.py` | OpenAI |
300
384
  | `examples/llm_claude_example.py` | Claude |
@@ -312,7 +396,7 @@ uv add mcp-bastion-python
312
396
  # or
313
397
  pip install mcp-bastion-python
314
398
  # pinned latest
315
- pip install mcp-bastion-python==1.0.14
399
+ pip install mcp-bastion-python==1.0.16
316
400
  ```
317
401
 
318
402
  **Prerequisites (recommended)**
@@ -337,7 +421,7 @@ npm install @mcp-bastion/core
337
421
 
338
422
  ### Framework Integrations
339
423
 
340
- Drop-in security for your favorite LLM framework. Each package auto-installs `mcp-bastion-python`:
424
+ Drop-in security for your favorite LLM framework. Each package auto-installs `mcp-bastion-python`. **Downloads** for each row point to [pypistats.org](https://pypistats.org/) (trends) and [pepy.tech](https://pepy.tech/) (cumulative) for that PyPI name.
341
425
 
342
426
  ```bash
343
427
  pip install mcp-bastion-langchain # LangChain agents and tools
@@ -361,23 +445,23 @@ pip install mcp-bastion-fastmcp # FastMCP servers
361
445
 
362
446
  | Package | Protects | Version | Downloads |
363
447
  |---------|----------|---------|-----------|
364
- | [mcp-bastion-langchain](https://pypi.org/project/mcp-bastion-langchain/) | LangChain | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-langchain) |
365
- | [mcp-bastion-openai](https://pypi.org/project/mcp-bastion-openai/) | OpenAI GPT | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-openai) |
366
- | [mcp-bastion-anthropic](https://pypi.org/project/mcp-bastion-anthropic/) | Anthropic Claude | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-anthropic) |
367
- | [mcp-bastion-bedrock](https://pypi.org/project/mcp-bastion-bedrock/) | AWS Bedrock | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-bedrock) |
368
- | [mcp-bastion-gemini](https://pypi.org/project/mcp-bastion-gemini/) | Google Gemini | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-gemini) |
369
- | [mcp-bastion-crewai](https://pypi.org/project/mcp-bastion-crewai/) | CrewAI | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-crewai) |
370
- | [mcp-bastion-llamaindex](https://pypi.org/project/mcp-bastion-llamaindex/) | LlamaIndex | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-llamaindex) |
371
- | [mcp-bastion-groq](https://pypi.org/project/mcp-bastion-groq/) | Groq | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-groq) |
372
- | [mcp-bastion-mistral](https://pypi.org/project/mcp-bastion-mistral/) | Mistral AI | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-mistral) |
373
- | [mcp-bastion-cohere](https://pypi.org/project/mcp-bastion-cohere/) | Cohere | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-cohere) |
374
- | [mcp-bastion-azure](https://pypi.org/project/mcp-bastion-azure/) | Azure OpenAI | 0.1.1 | [stats](https://pypistats.org/packages/mcp-bastion-azure) |
375
- | [mcp-bastion-vertexai](https://pypi.org/project/mcp-bastion-vertexai/) | Vertex AI | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-vertexai) |
376
- | [mcp-bastion-huggingface](https://pypi.org/project/mcp-bastion-huggingface/) | Hugging Face | 0.1.1 | [stats](https://pypistats.org/packages/mcp-bastion-huggingface) |
377
- | [mcp-bastion-deepseek](https://pypi.org/project/mcp-bastion-deepseek/) | DeepSeek AI | 0.1.1 | [stats](https://pypistats.org/packages/mcp-bastion-deepseek) |
378
- | [mcp-bastion-together](https://pypi.org/project/mcp-bastion-together/) | Together AI | 0.1.1 | [stats](https://pypistats.org/packages/mcp-bastion-together) |
379
- | [mcp-bastion-fireworks](https://pypi.org/project/mcp-bastion-fireworks/) | Fireworks AI | 0.1.1 | [stats](https://pypistats.org/packages/mcp-bastion-fireworks) |
380
- | [mcp-bastion-fastmcp](https://pypi.org/project/mcp-bastion-fastmcp/) | FastMCP servers | 0.1.0 | [stats](https://pypistats.org/packages/mcp-bastion-fastmcp) |
448
+ | [mcp-bastion-langchain](https://pypi.org/project/mcp-bastion-langchain/) | LangChain | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-langchain) · [pepy](https://pepy.tech/projects/mcp-bastion-langchain) |
449
+ | [mcp-bastion-openai](https://pypi.org/project/mcp-bastion-openai/) | OpenAI GPT | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-openai) · [pepy](https://pepy.tech/projects/mcp-bastion-openai) |
450
+ | [mcp-bastion-anthropic](https://pypi.org/project/mcp-bastion-anthropic/) | Anthropic Claude | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-anthropic) · [pepy](https://pepy.tech/projects/mcp-bastion-anthropic) |
451
+ | [mcp-bastion-bedrock](https://pypi.org/project/mcp-bastion-bedrock/) | AWS Bedrock | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-bedrock) · [pepy](https://pepy.tech/projects/mcp-bastion-bedrock) |
452
+ | [mcp-bastion-gemini](https://pypi.org/project/mcp-bastion-gemini/) | Google Gemini | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-gemini) · [pepy](https://pepy.tech/projects/mcp-bastion-gemini) |
453
+ | [mcp-bastion-crewai](https://pypi.org/project/mcp-bastion-crewai/) | CrewAI | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-crewai) · [pepy](https://pepy.tech/projects/mcp-bastion-crewai) |
454
+ | [mcp-bastion-llamaindex](https://pypi.org/project/mcp-bastion-llamaindex/) | LlamaIndex | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-llamaindex) · [pepy](https://pepy.tech/projects/mcp-bastion-llamaindex) |
455
+ | [mcp-bastion-groq](https://pypi.org/project/mcp-bastion-groq/) | Groq | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-groq) · [pepy](https://pepy.tech/projects/mcp-bastion-groq) |
456
+ | [mcp-bastion-mistral](https://pypi.org/project/mcp-bastion-mistral/) | Mistral AI | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-mistral) · [pepy](https://pepy.tech/projects/mcp-bastion-mistral) |
457
+ | [mcp-bastion-cohere](https://pypi.org/project/mcp-bastion-cohere/) | Cohere | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-cohere) · [pepy](https://pepy.tech/projects/mcp-bastion-cohere) |
458
+ | [mcp-bastion-azure](https://pypi.org/project/mcp-bastion-azure/) | Azure OpenAI | 0.1.3 | [pypistats](https://pypistats.org/packages/mcp-bastion-azure) · [pepy](https://pepy.tech/projects/mcp-bastion-azure) |
459
+ | [mcp-bastion-vertexai](https://pypi.org/project/mcp-bastion-vertexai/) | Vertex AI | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-vertexai) · [pepy](https://pepy.tech/projects/mcp-bastion-vertexai) |
460
+ | [mcp-bastion-huggingface](https://pypi.org/project/mcp-bastion-huggingface/) | Hugging Face | 0.1.3 | [pypistats](https://pypistats.org/packages/mcp-bastion-huggingface) · [pepy](https://pepy.tech/projects/mcp-bastion-huggingface) |
461
+ | [mcp-bastion-deepseek](https://pypi.org/project/mcp-bastion-deepseek/) | DeepSeek AI | 0.1.3 | [pypistats](https://pypistats.org/packages/mcp-bastion-deepseek) · [pepy](https://pepy.tech/projects/mcp-bastion-deepseek) |
462
+ | [mcp-bastion-together](https://pypi.org/project/mcp-bastion-together/) | Together AI | 0.1.3 | [pypistats](https://pypistats.org/packages/mcp-bastion-together) · [pepy](https://pepy.tech/projects/mcp-bastion-together) |
463
+ | [mcp-bastion-fireworks](https://pypi.org/project/mcp-bastion-fireworks/) | Fireworks AI | 0.1.3 | [pypistats](https://pypistats.org/packages/mcp-bastion-fireworks) · [pepy](https://pepy.tech/projects/mcp-bastion-fireworks) |
464
+ | [mcp-bastion-fastmcp](https://pypi.org/project/mcp-bastion-fastmcp/) | FastMCP servers | 0.1.2 | [pypistats](https://pypistats.org/packages/mcp-bastion-fastmcp) · [pepy](https://pepy.tech/projects/mcp-bastion-fastmcp) |
381
465
 
382
466
  ## Publish (PyPI / npm)
383
467
 
@@ -417,7 +501,7 @@ middleware = compose_middleware(bastion)
417
501
  | Example | Description |
418
502
  |---------|-------------|
419
503
  | `examples/python_server_example.py` | Basic middleware chain |
420
- | `examples/full_demo.py` | All features: add, PII, rate limit, prompt injection |
504
+ | `examples/full_demo.py` | Full middleware stack: add, PII, rate limit, prompt injection, etc. |
421
505
  | `examples/llm_openai_example.py` | MCP server for OpenAI |
422
506
  | `examples/llm_claude_example.py` | MCP server for Claude |
423
507
  | `examples/llm_gemini_example.py` | MCP server for Gemini |
@@ -701,6 +785,13 @@ When MCP-Bastion blocks a request, it returns standard MCP/JSON-RPC errors:
701
785
  | -32007 | `SchemaValidationError` | Tool arguments failed schema validation |
702
786
  | -32008 | `ReplayAttackError` | Duplicate nonce / replay detected |
703
787
  | -32009 | `CostBudgetExceededError` | Session cost budget exceeded |
788
+ | -32010 | `SemanticFirewallError` | Tool sequence / argument pattern failed semantic firewall |
789
+ | -32011 | `ExternalPolicyDeniedError` | OPA/Cedar (or other external) policy denied the request |
790
+ | -32012 | `SensitiveContentError` | Sensitive-business classifier above threshold |
791
+ | -32013 | `AuthenticationError` | Edge / gateway authentication (metadata token) failed |
792
+ | -32014 | `ToolNotAllowedError` | Tool not on allowlist |
793
+ | -32015 | `SessionScopeExceededError` | Too many distinct tools per session (scope creep) |
794
+ | -32016 | `ToolMetadataPoisoningError` | Tool list / metadata failed safety checks |
704
795
 
705
796
  ```python
706
797
  # Python: exceptions
@@ -714,6 +805,13 @@ from mcp_bastion.errors import (
714
805
  SchemaValidationError,
715
806
  ReplayAttackError,
716
807
  CostBudgetExceededError,
808
+ SemanticFirewallError,
809
+ ExternalPolicyDeniedError,
810
+ SensitiveContentError,
811
+ AuthenticationError,
812
+ ToolNotAllowedError,
813
+ SessionScopeExceededError,
814
+ ToolMetadataPoisoningError,
717
815
  )
718
816
  import logging
719
817
  logger = logging.getLogger(__name__)
@@ -730,6 +828,13 @@ except (
730
828
  SchemaValidationError,
731
829
  ReplayAttackError,
732
830
  CostBudgetExceededError,
831
+ SemanticFirewallError,
832
+ ExternalPolicyDeniedError,
833
+ SensitiveContentError,
834
+ AuthenticationError,
835
+ ToolNotAllowedError,
836
+ SessionScopeExceededError,
837
+ ToolMetadataPoisoningError,
733
838
  ) as e:
734
839
  logger.warning("blocked: %s", e.to_mcp_error())
735
840
  ```
@@ -787,12 +892,13 @@ See `NOTICE` for licenses. MCP-Bastion uses Meta Llama Prompt Guard 2 (Llama 4 C
787
892
 
788
893
  ## License
789
894
 
790
- MCP-Bastion is distributed under the **MCP-Bastion Community and Commercial License**.
895
+ MCP-Bastion is distributed under the **MCP-Bastion Community and Commercial License** ([LICENSE](LICENSE)).
791
896
 
792
- - Non-commercial use is permitted with required attribution/citation.
793
- - Commercial use requires a separate paid agreement.
897
+ - **Free** for non‑commercial use when you **cite MCP-Bastion** and the **copyright** notice (see [CITATION.cff](CITATION.cff); you can list *your* name, team, or org as authors or as who used the software, while still including the project and repository in the credit).
898
+ - **Copyright** is retained. Do not remove license or copyright text, and do not republish a duplicate of the work as if it were unrelated software without meeting the License terms.
899
+ - **Commercial use** (as defined in the License) may still require a **separate written agreement** — see [COMMERCIAL_LICENSE.md](COMMERCIAL_LICENSE.md).
794
900
 
795
- See:
901
+ See also:
796
902
 
797
903
  - [LICENSE](LICENSE)
798
904
  - [COMMERCIAL_LICENSE.md](COMMERCIAL_LICENSE.md)