mcpsonar 0.9.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.
Files changed (129) hide show
  1. mcpsonar-0.9.1/.gitignore +19 -0
  2. mcpsonar-0.9.1/CHANGELOG.md +400 -0
  3. mcpsonar-0.9.1/LICENSE +21 -0
  4. mcpsonar-0.9.1/PKG-INFO +639 -0
  5. mcpsonar-0.9.1/README.md +577 -0
  6. mcpsonar-0.9.1/SECURITY.md +65 -0
  7. mcpsonar-0.9.1/config.example.yaml +76 -0
  8. mcpsonar-0.9.1/docs/_config.yml +28 -0
  9. mcpsonar-0.9.1/docs/adr/0001-mcp-sdk-2.md +214 -0
  10. mcpsonar-0.9.1/docs/banner.html +78 -0
  11. mcpsonar-0.9.1/docs/blog-rugpull.md +230 -0
  12. mcpsonar-0.9.1/docs/cli.md +285 -0
  13. mcpsonar-0.9.1/docs/competitive-positioning.md +123 -0
  14. mcpsonar-0.9.1/docs/configuration.md +348 -0
  15. mcpsonar-0.9.1/docs/demo/README.md +61 -0
  16. mcpsonar-0.9.1/docs/demo/scenarios.md +74 -0
  17. mcpsonar-0.9.1/docs/demo/speaker-guide.md +55 -0
  18. mcpsonar-0.9.1/docs/demo/troubleshooting.md +50 -0
  19. mcpsonar-0.9.1/docs/detection-rules.md +143 -0
  20. mcpsonar-0.9.1/docs/engineering-audit.md +299 -0
  21. mcpsonar-0.9.1/docs/faq.md +94 -0
  22. mcpsonar-0.9.1/docs/index.md +70 -0
  23. mcpsonar-0.9.1/docs/product-roadmap.md +269 -0
  24. mcpsonar-0.9.1/docs/quickstart.md +197 -0
  25. mcpsonar-0.9.1/docs/troubleshooting.md +90 -0
  26. mcpsonar-0.9.1/examples/popular-servers.yaml +37 -0
  27. mcpsonar-0.9.1/examples/rugpull-demo/README.md +122 -0
  28. mcpsonar-0.9.1/examples/rugpull-demo/run_demo.sh +91 -0
  29. mcpsonar-0.9.1/examples/rugpull-demo/server.py +83 -0
  30. mcpsonar-0.9.1/pyproject.toml +85 -0
  31. mcpsonar-0.9.1/scripts/check_package.py +111 -0
  32. mcpsonar-0.9.1/scripts/manual_check.py +60 -0
  33. mcpsonar-0.9.1/scripts/mock_mcp_server.py +74 -0
  34. mcpsonar-0.9.1/scripts/smoke_e2e.sh +148 -0
  35. mcpsonar-0.9.1/src/mcpsonar/__init__.py +3 -0
  36. mcpsonar-0.9.1/src/mcpsonar/adapters/__init__.py +64 -0
  37. mcpsonar-0.9.1/src/mcpsonar/adapters/base.py +28 -0
  38. mcpsonar-0.9.1/src/mcpsonar/adapters/chat.py +80 -0
  39. mcpsonar-0.9.1/src/mcpsonar/adapters/email.py +167 -0
  40. mcpsonar-0.9.1/src/mcpsonar/adapters/generic_webhook.py +82 -0
  41. mcpsonar-0.9.1/src/mcpsonar/adapters/prometheus.py +157 -0
  42. mcpsonar-0.9.1/src/mcpsonar/adapters/uptime_kuma.py +39 -0
  43. mcpsonar-0.9.1/src/mcpsonar/anomaly.py +174 -0
  44. mcpsonar-0.9.1/src/mcpsonar/api.py +344 -0
  45. mcpsonar-0.9.1/src/mcpsonar/auth.py +649 -0
  46. mcpsonar-0.9.1/src/mcpsonar/check_engine.py +641 -0
  47. mcpsonar-0.9.1/src/mcpsonar/cli.py +372 -0
  48. mcpsonar-0.9.1/src/mcpsonar/commands/__init__.py +30 -0
  49. mcpsonar-0.9.1/src/mcpsonar/commands/_remote.py +131 -0
  50. mcpsonar-0.9.1/src/mcpsonar/commands/_target.py +72 -0
  51. mcpsonar-0.9.1/src/mcpsonar/commands/completion.py +231 -0
  52. mcpsonar-0.9.1/src/mcpsonar/commands/config_cmd.py +151 -0
  53. mcpsonar-0.9.1/src/mcpsonar/commands/doctor.py +309 -0
  54. mcpsonar-0.9.1/src/mcpsonar/commands/inspect_cmd.py +178 -0
  55. mcpsonar-0.9.1/src/mcpsonar/commands/monitor_cmd.py +469 -0
  56. mcpsonar-0.9.1/src/mcpsonar/commands/report_cmd.py +105 -0
  57. mcpsonar-0.9.1/src/mcpsonar/commands/servers_cmd.py +346 -0
  58. mcpsonar-0.9.1/src/mcpsonar/commands/version.py +43 -0
  59. mcpsonar-0.9.1/src/mcpsonar/dashboard.py +1289 -0
  60. mcpsonar-0.9.1/src/mcpsonar/demo.py +347 -0
  61. mcpsonar-0.9.1/src/mcpsonar/drift.py +121 -0
  62. mcpsonar-0.9.1/src/mcpsonar/incidents.py +41 -0
  63. mcpsonar-0.9.1/src/mcpsonar/lint.py +396 -0
  64. mcpsonar-0.9.1/src/mcpsonar/loader.py +74 -0
  65. mcpsonar-0.9.1/src/mcpsonar/models.py +525 -0
  66. mcpsonar-0.9.1/src/mcpsonar/monitor.py +253 -0
  67. mcpsonar-0.9.1/src/mcpsonar/netpolicy.py +186 -0
  68. mcpsonar-0.9.1/src/mcpsonar/notify.py +148 -0
  69. mcpsonar-0.9.1/src/mcpsonar/output.py +144 -0
  70. mcpsonar-0.9.1/src/mcpsonar/redact.py +198 -0
  71. mcpsonar-0.9.1/src/mcpsonar/report.py +291 -0
  72. mcpsonar-0.9.1/src/mcpsonar/scan.py +235 -0
  73. mcpsonar-0.9.1/src/mcpsonar/scenarios.py +513 -0
  74. mcpsonar-0.9.1/src/mcpsonar/scheduler.py +174 -0
  75. mcpsonar-0.9.1/src/mcpsonar/secretref.py +189 -0
  76. mcpsonar-0.9.1/src/mcpsonar/services.py +109 -0
  77. mcpsonar-0.9.1/src/mcpsonar/state.py +27 -0
  78. mcpsonar-0.9.1/src/mcpsonar/storage.py +899 -0
  79. mcpsonar-0.9.1/src/mcpsonar/tooldefs.py +118 -0
  80. mcpsonar-0.9.1/src/mcpsonar/trust.py +312 -0
  81. mcpsonar-0.9.1/src/mcpsonar/validators/__init__.py +26 -0
  82. mcpsonar-0.9.1/src/mcpsonar/validators/base.py +103 -0
  83. mcpsonar-0.9.1/src/mcpsonar/validators/v2024_11_05.py +32 -0
  84. mcpsonar-0.9.1/src/mcpsonar/validators/v2025_03_26.py +32 -0
  85. mcpsonar-0.9.1/src/mcpsonar/validators/v2025_06_18.py +33 -0
  86. mcpsonar-0.9.1/src/mcpsonar/validators/v2025_11_25.py +33 -0
  87. mcpsonar-0.9.1/src/mcpsonar/validators/v2026_07_28.py +32 -0
  88. mcpsonar-0.9.1/src/mcpsonar/websec.py +149 -0
  89. mcpsonar-0.9.1/tests/corpus/anomaly_scenarios.py +47 -0
  90. mcpsonar-0.9.1/tests/corpus/detection.yaml +161 -0
  91. mcpsonar-0.9.1/tests/fixtures/db/v0.5.0.sql +71 -0
  92. mcpsonar-0.9.1/tests/fixtures/db/v0.6.0.sql +77 -0
  93. mcpsonar-0.9.1/tests/fixtures/db/v0.7.0.sql +87 -0
  94. mcpsonar-0.9.1/tests/fixtures/era_server.py +86 -0
  95. mcpsonar-0.9.1/tests/fixtures/scripted_server.py +123 -0
  96. mcpsonar-0.9.1/tests/test_adapters.py +160 -0
  97. mcpsonar-0.9.1/tests/test_anomaly.py +90 -0
  98. mcpsonar-0.9.1/tests/test_anomaly_scenarios.py +106 -0
  99. mcpsonar-0.9.1/tests/test_api.py +218 -0
  100. mcpsonar-0.9.1/tests/test_app.py +159 -0
  101. mcpsonar-0.9.1/tests/test_auth.py +316 -0
  102. mcpsonar-0.9.1/tests/test_auth_required.py +84 -0
  103. mcpsonar-0.9.1/tests/test_cancellation.py +79 -0
  104. mcpsonar-0.9.1/tests/test_check_engine.py +187 -0
  105. mcpsonar-0.9.1/tests/test_cli.py +427 -0
  106. mcpsonar-0.9.1/tests/test_cli_remote.py +319 -0
  107. mcpsonar-0.9.1/tests/test_demo_scenarios.py +120 -0
  108. mcpsonar-0.9.1/tests/test_detection_corpus.py +79 -0
  109. mcpsonar-0.9.1/tests/test_drift.py +161 -0
  110. mcpsonar-0.9.1/tests/test_email.py +235 -0
  111. mcpsonar-0.9.1/tests/test_mcp2_migration.py +252 -0
  112. mcpsonar-0.9.1/tests/test_monitor.py +174 -0
  113. mcpsonar-0.9.1/tests/test_netpolicy.py +194 -0
  114. mcpsonar-0.9.1/tests/test_notifications.py +169 -0
  115. mcpsonar-0.9.1/tests/test_packaging.py +25 -0
  116. mcpsonar-0.9.1/tests/test_protocol_edges.py +110 -0
  117. mcpsonar-0.9.1/tests/test_redact.py +234 -0
  118. mcpsonar-0.9.1/tests/test_report.py +201 -0
  119. mcpsonar-0.9.1/tests/test_scan.py +90 -0
  120. mcpsonar-0.9.1/tests/test_scheduler.py +135 -0
  121. mcpsonar-0.9.1/tests/test_secretref.py +204 -0
  122. mcpsonar-0.9.1/tests/test_storage.py +126 -0
  123. mcpsonar-0.9.1/tests/test_storage_schema.py +176 -0
  124. mcpsonar-0.9.1/tests/test_teams.py +270 -0
  125. mcpsonar-0.9.1/tests/test_timings.py +98 -0
  126. mcpsonar-0.9.1/tests/test_tokens.py +151 -0
  127. mcpsonar-0.9.1/tests/test_tooldefs_lint.py +120 -0
  128. mcpsonar-0.9.1/tests/test_trust.py +113 -0
  129. mcpsonar-0.9.1/tests/test_websec.py +183 -0
@@ -0,0 +1,19 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+
8
+ # Virtual envs
9
+ .venv/
10
+ venv/
11
+
12
+ # Local config (users copy from config.example.yaml)
13
+ config.yaml
14
+ config.yml
15
+
16
+ # SQLite databases
17
+ *.db
18
+ *.sqlite
19
+ *.sqlite3
@@ -0,0 +1,400 @@
1
+ # Changelog
2
+
3
+ All notable changes to MCPSonar. Versions follow [SemVer](https://semver.org/); each release
4
+ is a Git tag `vX.Y.Z`, which is also how the GitHub Action is pinned.
5
+
6
+ ## [0.9.1] — 2026-11-04
7
+
8
+ No change to the code.
9
+
10
+ ### Added
11
+ - Published on PyPI: `pip install mcpsonar`, `uvx mcpsonar`. The release workflow uploads
12
+ the wheel and sdist it already checked, through trusted publishing (no stored token), from
13
+ a `pypi` environment that needs the maintainer's approval.
14
+ - `examples/rugpull-demo/`: a local MCP server that changes its tool, and a script that
15
+ shows MCPSonar catching it.
16
+
17
+ ### Changed
18
+ - README, banner and repository description lead with rug-pull and tool-poisoning
19
+ detection; uptime and latency monitoring are described after them.
20
+ - Install instructions use PyPI instead of the Git URL.
21
+
22
+ ## [0.9.0] — 2026-10-23
23
+
24
+ ### Added
25
+ - **`mcpsonar demo --scenario NAME`**: deterministic demos told step by step in the
26
+ terminal: `healthy`, `poisoning`, `rug-pull`, `latency-regression`, `ci-gate`, or `all`.
27
+ - **Real engine:** the local demo servers are steered before each step (latency, clean or
28
+ poisoned tools) and the real engine runs on them: the monitor's check pipeline, the
29
+ `risks` view, `scan` with a lockfile and SARIF.
30
+ - **Self-checking:** each scenario asserts MCPSonar's reaction; exit 1 if one does not
31
+ behave, so `--scenario all --auto` is an end-to-end smoke test (run by the test suite).
32
+ - **Pacing:** Enter between steps on a terminal; `--auto` for no pauses; `--json`.
33
+ - **`--dashboard`:** the dashboard follows every step live (checks go through the
34
+ scheduler, like "Check now").
35
+ - **Guides:** `docs/demo/`: overview, scenarios, speaker guide, troubleshooting.
36
+ - **`mcpsonar demo --servers`**: only the three demo MCP servers, with their URLs and
37
+ one-line `curl` commands to rug-pull or slow them down. The control endpoint also accepts
38
+ a query string (`/__demo?tools=poisoned`), so the same line works in every shell.
39
+ - **Onboarding docs:**
40
+ - `docs/quickstart.md`: install, scan and pin, monitor, rug pull, review, CI gate and
41
+ doctor in about ten minutes, all local, every command run while writing it;
42
+ - `docs/faq.md`;
43
+ - `docs/troubleshooting.md`, including a verified procedure for a forgotten admin
44
+ password.
45
+ - **`scan --no-color`**, like every other command.
46
+ - **Slack and Discord notifications** (`type: slack` / `discord`): one message per change,
47
+ not per check.
48
+ - **What is sent:** down or degraded, back up with how long it was out, and new findings.
49
+ Episodes from 0.8 mean a slow period is one message.
50
+ - **Hostile text:** server-controlled text is stripped of invisible characters and
51
+ escaped: no `<!channel>` or `@everyone`, no disguised links. URLs must be https and
52
+ are masked.
53
+ - **Link to the instance:** `MCPSONAR_PUBLIC_URL` adds it to each message.
54
+ - **Email notifications** (`type: email`): the same rules, through your SMTP server.
55
+ - **TLS:** required (STARTTLS that must succeed, or implicit TLS on 465). Plain SMTP only
56
+ for a relay on localhost.
57
+ - **Outbound policy:** checked before connecting; the connection goes only to an allowed
58
+ address, and the certificate is verified against the host name.
59
+ - **Password:** masked in every output and restored on edit. It can be a secret
60
+ reference.
61
+ - **Tested** against a scripted SMTP server with a certificate generated for the test:
62
+ STARTTLS, implicit TLS, a server without STARTTLS, an untrusted certificate and a
63
+ blocked destination.
64
+ - **Dashboard:**
65
+ - **Incidents in History:** each run of non-up checks with its start, worst status,
66
+ duration (or "ongoing") and first reason. It comes from the same function as
67
+ `mcpsonar history`, now shared with the insights API (`"incidents"`).
68
+ - **Filters:** down, degraded, to review, findings, latency, auth required, paused. Only
69
+ the filters that match something are shown, and the choice is remembered in the
70
+ browser.
71
+ - **When the API can't be reached:** a banner says so, keeps the last data, and retries.
72
+ A 401 goes to the login page. Before, the page stayed on "Loading…".
73
+ - **Phones:** no more horizontal scrolling at 375 px (the top bar, tiles and actions
74
+ wrap).
75
+ - **Accessibility:**
76
+ - every text color reaches WCAG AA (4.5:1) in both themes; the light theme's green,
77
+ amber, teal and faint text were below it;
78
+ - per-server buttons have accessible names ("History · calculator");
79
+ - the heartbeat strip has a text equivalent ("Last 40 checks: 37 up, 3 down");
80
+ - the tool count is a real button.
81
+ - **Accounts and roles:** `viewer`, `operator`, `admin`; admins manage accounts
82
+ (`/api/users`). The first account and every account from earlier versions is an admin;
83
+ the last admin can be neither demoted nor deleted. See
84
+ [Accounts and roles](docs/configuration.md#accounts-and-roles).
85
+ - **Your account:** change your password (your other sessions end), list your sessions
86
+ (with their address and browser) and end any of them, keep your API tokens private. An
87
+ admin resetting a password also ends that person's sessions and tokens.
88
+ - **Audit log** (`/api/audit`, admins): logins, accounts, tokens, server changes, imports,
89
+ exports with secrets and every approval of tool changes. No credentials. It is
90
+ append-only in the database itself (triggers refuse UPDATE and DELETE).
91
+ - **Dashboard for teams:**
92
+ - **Per role:** each role sees only the controls it can use. Operators see stdio as
93
+ "admins only"; viewers get read tokens only. The server still enforces every rule.
94
+ - **Account:** change your password, see your sessions (browser, address) and sign them
95
+ out.
96
+ - **Users** (admins): add accounts, change roles, reset passwords, delete accounts.
97
+ - **Audit** (admins): the log, newest first, with what each approval covered.
98
+ - **Trust Report**, for one server, several or the whole instance, over N days (default 30):
99
+ - **Contents:** availability (share of checks answered, incidents, time not up, the
100
+ longest incident, latency p50/p95); the pinned tool definitions with SHA-256
101
+ fingerprints; every tool change in the window with its review status and **who
102
+ approved it, when** (audit log); the lint findings on the current tools, labeled as
103
+ heuristics.
104
+ - **Format:** one self-contained page (no script, no external resource), styled for
105
+ printing to PDF. It gives no score and no verdict: it records observations.
106
+ - **Where:** `GET /api/trust-report` (html or json, any role), the dashboard (top bar,
107
+ History), and `mcpsonar trust-report` (renders the instance's JSON with the same code).
108
+ - **Schema version 4:** user roles, session ids, the audit log. It is migrated
109
+ automatically, with a backup.
110
+ - **Recovery for the generic webhook:** `on_recovery` (default on) sends one POST when a
111
+ server is back up from an `on` status. `CheckEvent` gains `previous_status` and
112
+ `previous_status_since` (live events only).
113
+
114
+ ### Changed
115
+ - **Security: stricter API tokens.**
116
+ - **Lower of scope and role:** a token now acts with the lower of its scope and its
117
+ owner's current role, and never as an admin.
118
+ - **Owner gone:** a token whose owner no longer exists stops working.
119
+ - **No longer possible with a write token:** adding or changing a stdio server's process,
120
+ and exporting with secrets. These are admin actions and need a dashboard session.
121
+ - **Visibility:** tokens are listed and revoked by their owner (admins see all).
122
+ - **The way back after a latency shift is found sooner.** After a reported shift, the next
123
+ shift is searched relative to the new level. Before, a short regression followed by a fix
124
+ could hide the return to normal, because the strongest split of the window was the
125
+ already-reported first change.
126
+ - **Quieter new databases:** creating a new database no longer logs each migration step at
127
+ INFO; upgrading an existing one still does.
128
+
129
+ ## [0.8.1] — 2026-10-22
130
+
131
+ ### Fixed
132
+ - **Dashboard stuck on "Loading…" after a latency shift.** Since 0.8.0 a `latency_shift`
133
+ carries `since`; `/api/status` failed (500) as soon as any server's last anomaly was a
134
+ shift, and the whole dashboard stopped loading. Upgrade if you run 0.8.0.
135
+ - **Heartbeat strip of new servers looked empty:** the padding slots shared a CSS class with
136
+ the empty-state box, so servers with fewer than 40 checks showed no bars.
137
+ - **The "Anomalies" tile counts each episode once:** checks marked `ongoing` no longer add
138
+ to it (they still show in the strip, with "(ongoing)" in the tooltip).
139
+ - History is ordered deterministically when two checks share a timestamp (coarse clocks
140
+ on Windows): insertion order wins. A test depending on it failed intermittently on the
141
+ Windows CI runner.
142
+
143
+ ## [0.8.0] — 2026-10-21
144
+
145
+ ### Observability
146
+ - **Where the time goes.** Every check now records:
147
+ - `timings_ms`: `handshake`, `discovery` (every `tools/list` page), `probes` and `total`
148
+ (connection set-up included);
149
+ - `tool_latency_ms`: the latency of each probed tool call.
150
+ - **Unchanged meaning:** `latency_ms` is still the handshake, so baselines, anomaly detection
151
+ and dashboards behave as before. The new fields are additive in the API, webhooks and the
152
+ database, through an automatic migration.
153
+ - **Prometheus:** new `mcpsonar_check_duration_ms`, `mcpsonar_phase_latency_ms{phase}` and
154
+ `mcpsonar_tool_latency_ms{tool}`. No existing metric was renamed.
155
+ - **CLI:** `mcpsonar history` shows the median of each phase and of each probed tool.
156
+ - **Response drift.** For each probed tool, MCPSonar remembers the *shape* of the answer
157
+ (error or not, content types, structure of `structuredContent`, order of magnitude of the
158
+ size) and reports a `response_drift` anomaly once when it changes.
159
+ - Response content is never stored.
160
+ - Opt-in `drift: hash` (with `drift_ignore`) also catches value changes on deterministic
161
+ probes; `drift: off` disables it per probe.
162
+ - The status is not affected. See [Response drift](docs/configuration.md#response-drift).
163
+
164
+ ### Packaging and developer experience
165
+ - **`mcpsonar completion bash|zsh|fish|powershell`**, generated from the CLI's own parser,
166
+ so it always matches the installed version; no new dependency.
167
+ - **`doctor` checks the remote setup:** whether `MCPSONAR_TOKEN` is accepted (scope,
168
+ expiry), the instance version against the CLI's, and it refuses to send the token over
169
+ plain HTTP to a non-local instance. `/api/auth/status` reports the version only to an
170
+ authenticated caller, and the validity of a bearer token sent with it.
171
+ - **Checked packages.** A CI `package` job builds the wheel and sdist, checks their metadata
172
+ and contents (`scripts/check_package.py`, `twine check --strict`), installs the wheel into
173
+ a clean environment, runs it, and runs it through `uvx`. Releases attach both files.
174
+ - **Metadata:** Documentation, Changelog and Issues URLs; README links are absolute, so they
175
+ work outside GitHub too; the sdist no longer ships the screenshots (7.3 MB to 241 KB).
176
+
177
+ ### Secret references
178
+ - Header and stdio `env` values accept `${env:NAME}` and `${file:PATH}`, resolved at every
179
+ check and never stored, exported or shown resolved. `${file:...}` is re-read on every
180
+ check, so a rotated secret needs no restart.
181
+ - **Allowlisted reads**, because configurations also come from the API and shared files:
182
+ variables named `MCPSONAR_SECRET_*` or listed in `MCPSONAR_SECRET_ENV`; files inside
183
+ `/run/secrets` or `MCPSONAR_SECRETS_DIR` (after resolving symlinks and `..`).
184
+ - **Errors:** a malformed or forbidden reference is refused by the API (400) and by config
185
+ loading; an unresolvable one makes the check `down` with the reason, never a value.
186
+ - **Display:** references are shown as-is in the dashboard, the API and exports, so an
187
+ export with references is complete and safe to share.
188
+ - See [Secret references](docs/configuration.md#secret-references).
189
+
190
+ ### Storage
191
+ - **Versioned schema.** The database records its schema version (`PRAGMA user_version`)
192
+ and is migrated forward on start by numbered steps, each in its own transaction.
193
+ Databases from v0.5, v0.6 and v0.7 are upgraded automatically; tests restore databases
194
+ created by each released tag and check that the migrated schema equals a new one.
195
+ - **Backup before migrating:** `mcpsonar.db.schema<N>.bak` is written next to the database.
196
+ - **No silent downgrade:** a database written by a newer mcpsonar is refused (exit 2).
197
+ - **WAL mode** with a separate read-only connection: reads no longer wait for writes.
198
+ Back up with SQLite's online backup, not by copying the `.db` file alone (see
199
+ [Backups](docs/configuration.md#backups)).
200
+ - **Atomic writes.** Each write runs in one transaction, one writer at a time. Before, a
201
+ multi-statement write (replacing a tool snapshot) could be committed halfway by another
202
+ task sharing the connection.
203
+
204
+ ### Anomaly detection
205
+ - **Episodes.** A run of slow checks is one episode: the first `latency_spike` is notified,
206
+ the following ones carry `ongoing: true` (kept in the history, not notified), and the
207
+ first normal check reports a new **`latency_recovered`** anomaly. Generic webhooks with
208
+ `on_anomaly` now fire once per episode plus the recovery, instead of on every slow check.
209
+ `anomaly.degrade` keeps the server degraded for the whole episode.
210
+ - **Baseline poisoning.** Slow checks are left out of the baseline while they are a
211
+ minority, so repeated bursts no longer inflate it and hide milder spikes.
212
+ - **Sustained regression vs. burst.** A latency shift now needs most of the new segment
213
+ to sit at the new level: a burst shorter than 8 checks was reported as a shift before.
214
+ Once reported, the new level becomes the baseline (with `since`, when it started).
215
+ - **Cold start.** From the 8th check a provisional baseline flags blatant spikes only
216
+ (≥ 2× and 1.5× the sensitivity); the insights API reports `samples` and `provisional`.
217
+ - **Zero MAD.** When over half the latencies are identical, the mean absolute deviation
218
+ stands in for the MAD.
219
+ - A reproducible scenario corpus (`tests/corpus/anomaly_scenarios.py`) pins all of this.
220
+
221
+ ### Reports and SARIF
222
+ - `mcpsonar report` for one server or every server in `--config`, in **Markdown** (for an
223
+ issue or PR comment), **JSON** or **SARIF 2.1.0**. It includes findings with each rule's
224
+ documentation, lockfile drift (`--lock`, read-only) and unreachable servers. Exit 0/1/2
225
+ like `scan`.
226
+ - `mcpsonar scan --sarif FILE` and a `sarif` input on the GitHub Action. Upload the file with
227
+ `github/codeql-action/upload-sarif` and findings become **code scanning alerts**, pointing
228
+ at the tool's line in the lockfile.
229
+ - What the SARIF carries:
230
+ - every rule, with its help text, `security-severity`, `precision` (the rule's confidence)
231
+ and OWASP MCP Top 10 tags;
232
+ - lockfile drift as the rule `tool-definition-changed`;
233
+ - stable `partialFingerprints` so alerts follow the same issue across runs.
234
+ - Checked against the official SARIF 2.1.0 schema.
235
+ - `risks --json` also carries each finding's confidence and OWASP categories (same function
236
+ as the reports).
237
+
238
+ ### Detection quality
239
+ - New adversarial corpus (`tests/corpus/detection.yaml`), run in CI: positive and negative
240
+ cases for every lint rule, plus documented known limitations run as strict expected
241
+ failures.
242
+ - Every rule is documented in [docs/detection-rules.md](docs/detection-rules.md) with its
243
+ severity, **confidence** (precision), OWASP MCP Top 10 mapping, examples and what to
244
+ review. The registry (`lint.RULES`) is the single source of each rule's severity.
245
+ - Tuned against the corpus and against the tools of the 24 watched public servers: 8 false
246
+ positives on 42 live tools became 0, and the corpus has 0 misses and 0 false positives
247
+ (3 documented limitations).
248
+ - `prompt-injection` ignores CLI flags and parameters (`--ignore`, `override=true`).
249
+ - `sensitive-path`: private keys, `.env` files and MCP client configs count only when the
250
+ text asks to read or send them. "Add this server to your mcp.json" is setup, not an
251
+ attack.
252
+ - `exfiltration` needs data (conversation, files, keys, …) heading to the destination, so
253
+ "Send a POST request to https://api…" no longer matches.
254
+ - `coercive-instruction` needs "always / first / also": "You must provide a city name" and
255
+ API ordering notes no longer match.
256
+ - `hidden-unicode` ignores the zero-width joiner inside emoji sequences.
257
+ - `fake-system-tag` also catches `[SYSTEM]` / `[IMPORTANT]`.
258
+ - `encoded-blob` ignores long URLs.
259
+ - `long-description` threshold raised from 1,500 to 4,000 characters (real descriptions
260
+ reach ~3,300).
261
+
262
+ ## [0.7.0] — 2026-10-21
263
+
264
+ ### Added
265
+ - CLI commands `tools`, `risks`, `config validate|show|export`, `doctor` and `version`
266
+ (plus `--version`). They work without a running instance, against a URL, `--stdio` or a
267
+ server name from a YAML file. Every command supports `--format json`, `NO_COLOR` /
268
+ `--no-color`, results on stdout and diagnostics on stderr, and stable exit codes (0/1/2).
269
+ See [docs/cli.md](docs/cli.md).
270
+ - A rule registry (`lint.RULES`): every finding's rule ID now has a title, an explanation
271
+ and a remediation.
272
+ - **API tokens** for the CLI and automation: created and revoked in the dashboard (new **API
273
+ tokens** dialog). They are scoped *read* (GET only) or *write*, can expire, are stored
274
+ hashed and shown once, and can't manage tokens. They are sent as
275
+ `Authorization: Bearer mcps_…`.
276
+ - Remote-mode CLI commands that work against a running instance (`MCPSONAR_URL`,
277
+ `MCPSONAR_TOKEN`), through the same API as the dashboard and never through its database:
278
+ - `status`;
279
+ - `servers list|show`;
280
+ - `check NAME|--all`, run by the instance;
281
+ - `diff NAME`: changes marked pending, SUSPICIOUS or APPROVED; never approves anything;
282
+ - `history NAME --last 24h`: uptime, latency percentiles, baseline, shift, incidents;
283
+ - `servers add|edit|pause|resume|remove`. `--header-from-env` keeps credentials out of
284
+ shell history. `edit` changes only the given options and keeps credentials and probe
285
+ arguments. `remove` asks for confirmation on a terminal and needs `--yes` otherwise.
286
+ - `GET /api/servers/{id}/tool-changes` includes the lint `findings` of each new definition.
287
+
288
+ ### Protocol
289
+ - **MCP Python SDK 2.x (`mcp>=2.3,<3`) and spec revision 2026-07-28.** One client speaks both
290
+ protocol eras: `server/discover` for 2026-07-28 servers, falling back to `initialize` for
291
+ 2024-11-05 … 2025-11-25 servers. Servers that only speak 2026-07-28 used to be reported
292
+ *down*. 5 of the 24 public servers in `examples/popular-servers.yaml` already speak it.
293
+ - New validator for 2026-07-28. `spec_version` reports the negotiated revision.
294
+ - Unchanged:
295
+ - **Tool fingerprints:** identical to mcp 1.x, with golden values tested and 42/42 tools
296
+ checked on live servers. Stored baselines and lockfiles are re-normalized, so annotation
297
+ keys 2.x no longer represents don't count as changes.
298
+ - **401/403/429 handling:** *auth required*, *credentials rejected*, *rate limited* +
299
+ `Retry-After`.
300
+ - **Latency continuity:** HTTP times only the RPC that succeeded; stdio includes process
301
+ startup, as before. A 401/403/429 on the discover probe ends negotiation at once, so
302
+ protected servers cost one request per check, as before.
303
+ - `httpx` is replaced by `httpx2`, the stack mcp 2.x uses, so the outbound policy covers one HTTP
304
+ stack. On Windows and macOS, TLS uses the operating-system trust store, so corporate CAs work
305
+ without extra configuration.
306
+ - "down" details now include the HTTP status when there was one (e.g. `(HTTP 503)`).
307
+
308
+ ### Changed
309
+ - The dashboard's Tools view, `mcpsonar scan` and the new commands share one inspection
310
+ service. As a result `scan` now has an overall time budget too.
311
+ - One-shot commands log only warnings; `run` and `demo` are unchanged.
312
+ - `scan` also accepts `--format json`.
313
+
314
+ ### Fixed
315
+ - `--stdio` with quoted paths on Windows (e.g. `"C:\Program Files\nodejs\npx.cmd"`): the
316
+ quotes were kept as part of the path, so the command could not be found.
317
+
318
+ ## [0.6.0] — 2026-10-21
319
+
320
+ Security hardening release. Details, evidence and tests for each item:
321
+ [docs/engineering-audit.md](docs/engineering-audit.md). Upgrade notes:
322
+ [docs/configuration.md](docs/configuration.md#upgrading-from-v05).
323
+
324
+ ### Security
325
+ - **Critical:** blocked a drive-by remote command execution. A page on any website could POST
326
+ YAML to `/api/import` on an `--no-auth` instance (including `mcpsonar demo` and the watch
327
+ compose) and make MCPSonar launch an arbitrary stdio command. State-changing requests from
328
+ other sites or origins are now refused, and with `--no-auth`, stdio servers can't be added
329
+ over the API unless `--allow-stdio-api` is given.
330
+ - `Host` allowlist (`--allowed-host`; loopback-only by default with `--no-auth`) against
331
+ DNS rebinding.
332
+ - First-admin setup requires a one-time token printed in the log (or
333
+ `MCPSONAR_SETUP_TOKEN`); concurrent setups can no longer create two admins.
334
+ - Server-side sessions, revoked on logout and stored as hashes; `Secure` cookie over HTTPS
335
+ (`--cookie-secure`).
336
+ - Login throttling; PBKDF2 raised to 600k iterations and moved off the event loop (old hashes
337
+ are upgraded at the next login).
338
+ - Credentials (headers, `env`, URL keys and userinfo, webhook and push URLs) are masked in API
339
+ responses, the dashboard and exports, and scrubbed from check details, webhook and Kuma
340
+ messages, logs and `scan` output. Editing a server keeps its stored credentials.
341
+ - Outbound policy enforced at connect time: cloud metadata and link-local addresses blocked;
342
+ `MCPSONAR_BLOCK_PRIVATE_NETWORKS`, `MCPSONAR_OUTBOUND_ALLOW` / `_DENY`. Configured headers are
343
+ no longer forwarded on cross-origin redirects; at most 5 redirects.
344
+ - Security headers (no framing, CSP, nosniff, no-referrer); 1 MiB body limit; bounded history
345
+ queries; optional `MCPSONAR_METRICS_TOKEN` for `/metrics`; http(s)-only URLs.
346
+ - The container runs as an unprivileged user with no capabilities and has a HEALTHCHECK.
347
+ - `pyjwt>=2.15` and `certifi>=2026.10.19` (transitive dependencies with known advisories, GHSA-6q48-6c7w-9gw2 for certifi); CI runs `pip-audit`.
348
+
349
+ ### Fixed
350
+ - `tools/list` pagination is followed. Only the first page used to be checked, pinned and
351
+ linted.
352
+ - A stdio server that never answers can no longer hang its check loop: session read timeouts
353
+ plus an overall check budget.
354
+ - Probes never call a tool the server doesn't list, and skip tools annotated destructive
355
+ unless `allow_destructive: true`; new per-probe `timeout_seconds`.
356
+ - Duplicate tool names are reported; reordering a schema's `required` list is no longer a
357
+ "tool change".
358
+ - Concurrent checks of the same server (Check now + loop) could record tool changes twice.
359
+ - Editing a server in the dashboard dropped its probes' arguments.
360
+
361
+ ### Changed
362
+ - `GET /api/export` is masked by default (`?include_secrets=true` for a full export); import
363
+ refuses a masked export.
364
+ - Everyone signs in once more after upgrading (sessions moved server-side).
365
+ - CI runs on Linux, Windows and macOS and smoke-tests the Docker image; Dependabot enabled.
366
+
367
+ ## [0.5.0] — 2026-10-14
368
+
369
+ First tagged release.
370
+
371
+ ### Added
372
+ - **Rug-pull detection**: every server's tool definitions are pinned on first sight; added,
373
+ removed or changed tools are recorded and (by default) keep the server *degraded* until a
374
+ human reviews the diff in the dashboard.
375
+ - **Tool-poisoning lint**: hidden Unicode, instruction overrides, fake `<IMPORTANT>` blocks,
376
+ "don't tell the user", credential paths, exfiltration, tool shadowing, suspicious
377
+ parameters.
378
+ - **Anomaly detection** with a learned per-server baseline: latency spikes (robust z-score),
379
+ level shifts (change point) and flapping.
380
+ - `mcpsonar scan`: one-shot scan with a tool **lockfile** for CI, plus a GitHub Action.
381
+ - `mcpsonar demo`: dashboard against four scripted demo servers.
382
+ - `examples/popular-servers.yaml` + `docker-compose.watch.yml`: watch 24 hosted MCP servers.
383
+ - Validators for MCP spec 2024-11-05, 2025-03-26 and 2025-06-18 (besides 2025-11-25).
384
+
385
+ ### Changed
386
+ - OAuth-protected servers (401/403 to an anonymous client) are **up · auth required**
387
+ instead of down; rejected credentials are down; 429 is degraded (rate limited).
388
+
389
+ ### Fixed
390
+ - Latency was inflated when several servers were checked at once (a TLS context was rebuilt
391
+ on every check).
392
+ - Pinned `mcp>=1.28,<2`: the 2.x SDK is a breaking rewrite and broke every check on a fresh
393
+ install.
394
+ - Source files contained literal zero-width and bidi-override characters (in the lint's own
395
+ regex and two test fixtures). They are now written as Unicode escape sequences, and CI
396
+ rejects any invisible character in tracked files.
397
+ - ruff pinned to an exact version so CI and local linting agree.
398
+ - Pausing, editing or deleting a server while one of its checks was in flight could hang
399
+ forever: the MCP SDK can turn a cancellation into an ordinary error, which was recorded as
400
+ "down" and the check loop kept running. Cancellation is now always propagated.
mcpsonar-0.9.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alessandro Fraleoni
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.