opentel-mcp 0.16.0 → 0.18.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,165 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.0
4
+
5
+ Security observability: five small signals that help you notice some attacks
6
+ and mistakes on an MCP server. This is **observability, not enforcement**:
7
+ nothing here blocks a call, alters a response or authenticates anyone, and none
8
+ of it records tool arguments, result content, tool descriptions or matched
9
+ secrets (derived signals only: booleans, counts, type names, hashes, tool
10
+ names). Every new feature is off by default except the auth failure counter,
11
+ which adds no new data. No new metric label keys. Python (0.1.0) is not
12
+ affected; these signals are JS-only (ADR 034). This is the first release made
13
+ by the signed release workflow (npm provenance, SBOM).
14
+
15
+ ### Security
16
+
17
+ - **Tool definition integrity (ADR 029).** New opt-in `toolIntegrity: {
18
+ enabled, pins }`. Each tool definition seen in `tools/list` (name, title,
19
+ description, inputSchema, outputSchema, annotations) is hashed with SHA-256
20
+ over a canonical JSON form (`computeToolDefinitionHash()`, new export).
21
+ Span events on the `tools/list` span report `mcp.tool.integrity.changed`
22
+ (differs from the first hash seen), `.pin_mismatch` (differs from `pins`;
23
+ high severity), `.added` and `.removed`, once per distinct new state, with
24
+ hashes and tool names only. New counter `mcp.tool.integrity_violations`
25
+ (label: `gen_ai.tool.name`). `server.getToolIntegrityHashes()` returns the
26
+ current hashes, and in dev mode (`setupNodeSdk: true`) a copy-pasteable
27
+ `pins` snippet is printed once to stderr. Works with `schemaDrift` disabled
28
+ and does not change the ADR 010 drift hash. Detection only: the first hash
29
+ seen is trusted unless pinned.
30
+ - **Secret leak detection in tool output (ADR 030).** New opt-in
31
+ `secretLeakDetection: { enabled, maxScanBytes }` (default 65536). Text
32
+ content of successful and `isError` results is checked in-process with the
33
+ same detectors that scrub error text. Span attributes
34
+ `mcp.tool.secret_leak.detected`, `.types` (a closed set of six names),
35
+ `.count` and `.scan_truncated`; counter `mcp.tool.secret_leaks` (label:
36
+ `gen_ai.tool.name`). Never the matched text, its position or its
37
+ surroundings; the type is span-only; span status is unchanged. Pattern
38
+ based: it misses formats and can fire on look-alikes. The detectors gained
39
+ internal type names without any change to scrubbing, fingerprints or
40
+ `conformance/patterns.json`.
41
+ - **Auth failure counter (ADR 031).** New always-on counter
42
+ `mcp.tool.auth_failures` (label: `gen_ai.tool.name`), +1 per failed call,
43
+ silent or thrown, classified into the existing `auth` category. Stays at zero
44
+ with `fingerprinting: false`. Burst detection is not in the library.
45
+ - **Sensitive tool auditing (ADR 032).** New opt-in `sensitiveTools` (exact
46
+ names and `*` wildcards, matched in linear time). A matching call gets the
47
+ span attribute `mcp.tool.sensitive` and an `mcp.audit.sensitive_call` event
48
+ with outcome, duration and failure category. Never arguments or results. It
49
+ is telemetry, not a tamper-evident audit log.
50
+ - **Clock-skew visibility (ADR 027 amendment).** A producer whose `asOf` is
51
+ ahead of this process's clock is now recorded as the span attribute
52
+ `mcp.tool.freshness.clock_skew_ms`, including inside the tolerance (an
53
+ `asOf` up to 5 s ahead used to be silently treated as "now"). New option
54
+ `staleness.clockSkewToleranceMs` (default `5000`, today's behavior; invalid
55
+ values are warned about and ignored). Verdicts are unchanged. Credit: the
56
+ r/mcp community.
57
+
58
+ ### Internal
59
+
60
+ - **Test reliability.** Timing-sensitive tests were made deterministic: the
61
+ flush-on-exit child-process tests (the shutdown flush is now told apart from
62
+ the 5 s periodic metric print by a stderr readiness marker, a longer hang
63
+ guard, a monotonic clock for the cap check), the thrash benchmark (no spy on
64
+ the timed loop, a relative bound instead of an absolute throughput floor)
65
+ and a staleness test that assumed the wall clock never steps backwards. The
66
+ global `testTimeout` raise was reverted; two tests carry their own timeouts.
67
+ - **Tooling.** Test tooling moved from vitest 5 (which requires Node 22.12+)
68
+ to vitest 4.1.12 so the suites run on every Node version in `engines`
69
+ (`>=20`). The `engines` field is unchanged. Runtime dependencies are
70
+ unchanged.
71
+
72
+ ## 0.17.0
73
+
74
+ Staleness detection: a read tool that returns data older than its own
75
+ freshness window now shows up, even though the call succeeded. Suggested by
76
+ the r/mcp community. Opt-in per tool; tools that don't report freshness
77
+ are untouched. No new metric label keys.
78
+
79
+ ### Added
80
+
81
+ - **Staleness detection (ADR 027, `docs/adr/027-staleness-detection.md`).**
82
+ A tool reports freshness in its result's `_meta` under
83
+ `FRESHNESS_META_KEY` (`'io.github.thirumalaiboobathi.opentel-mcp/freshness'`,
84
+ a reverse-DNS key per the MCP spec): `{ asOf, maxAgeMs }` or
85
+ `{ asOf, freshUntil }` (both allowed; the earlier deadline wins).
86
+ `asOf`/`freshUntil` are epoch milliseconds or ISO 8601 strings with a
87
+ timezone. Every successful `tools/call` carrying it gets:
88
+ - span attributes `mcp.tool.freshness.verdict` (`fresh` | `stale` |
89
+ `unknown`), `.data_age_ms`, `.max_age_ms`, `.limit_source`
90
+ (`max_age_ms` | `fresh_until` | `config`) and, for `unknown`,
91
+ `.unknown_reason` (`invalid_as_of` | `future_as_of` | `missing_window`
92
+ | `invalid_window`);
93
+ - `+1` on the new `mcp.tool.stale_responses` counter (label:
94
+ `gen_ai.tool.name` only) when stale.
95
+ - **Stale is not an error:** span status stays `OK`, no `error.type`,
96
+ never a silent failure, ToolOutcome stays `SUCCESS`.
97
+ - Responses without the key get no verdict and no attributes.
98
+ `isError: true` responses are skipped.
99
+ - Malformed input (epoch seconds, timezone-less ISO strings, negative
100
+ windows, `freshUntil` before `asOf`, `asOf` more than 5 s in the
101
+ future, throwing getters…) never throws: it yields `unknown` with a
102
+ reason and at most one `diag.warn` per tool.
103
+ - Only `asOf`, `maxAgeMs` and `freshUntil` under that one key are read;
104
+ content and other `_meta` fields are never read for this, and raw
105
+ values never reach spans, metrics or logs (canary-tested).
106
+ - **`staleness` option.** `staleness.tools[toolName].maxAgeMs` overrides a
107
+ tool's own window (config can't supply `asOf`); `staleness.enabled:
108
+ false` turns the feature off. On by default, which is a no-op for tools
109
+ that don't report freshness.
110
+ - **Types:** `FRESHNESS_META_KEY`, `FreshnessMeta` (autocomplete for the
111
+ `_meta` value), `FreshnessTimestamp`, `FreshnessVerdict`, and
112
+ `InstrumentOptions.staleness`.
113
+
114
+ ### Security
115
+
116
+ - **Credentials are scrubbed from recorded exceptions, in every mode.**
117
+ Bearer and Basic tokens, AWS access key ids, `sk-`/`sk-proj-`/`sk-ant-`,
118
+ Stripe, `ghp_`/`github_pat_`, Slack `xox…` and Google `AIza…` keys, JWTs,
119
+ passwords in URLs (`https://user:<password>@host`), and the values of keys like
120
+ `token=`, `api_key=`, `access_token=`, `password=`, `client_secret` are
121
+ replaced with `<SECRET>`. Ordinary words and ids (`token expired`, UUIDs,
122
+ commit hashes) are left alone.
123
+ - **Behavior change: the default `errorRecording.mode: 'full'` is no
124
+ longer raw and uncapped.** It still records the error's type, message and
125
+ stack, keeping ids, paths, URLs and line numbers, but with credentials
126
+ scrubbed as above, the message capped at 4096 characters and the stack at
127
+ 16384 (a ` …[truncated by opentel-mcp]` marker shows where).
128
+ `exception.type` is capped at 128 characters, as in `'normalized'` mode.
129
+ Ordinary errors are recorded exactly as before. There is no switch back
130
+ to unscrubbed output. To record *less*, use `'normalized'`, `'none'`, or
131
+ a `redactor`.
132
+ - **Fingerprints change only for errors that contain a credential.** The
133
+ same patterns now run first in `'normalized'` mode and in fingerprinting,
134
+ so two errors that differ only by a leaked token now share one
135
+ fingerprint (before, each token value made its own). Expect a one-time
136
+ regrouping of those errors only.
137
+ - A thrown value whose properties throw when read (e.g. a throwing
138
+ `message` getter) now still marks the span `ERROR`; before, the span was
139
+ left with no status.
140
+ - New test: one canary test runs every feature at once (fingerprinting,
141
+ cost and budget, thrash, schema drift, unactionable errors,
142
+ resources/prompts coverage, staleness) on SDK v1 and v2, `Server` and
143
+ `McpServer`, and checks that no tool argument, result content, `_meta`
144
+ value, resource URI, prompt argument or request `_meta` reaches any span,
145
+ metric or diag log. Nothing leaked; no code change was needed.
146
+ - New `SECURITY.md` at the repository root: private vulnerability reporting
147
+ and what the library does and doesn't record.
148
+
149
+ ### Changed
150
+
151
+ - The cost extractor and staleness detection now read `result._meta`
152
+ through one shared accessor (`src/result-meta.js`). Extraction behavior is
153
+ unchanged.
154
+ - Dev-mode metrics (`setupNodeSdk: true`) print 13 instruments, up from 12.
155
+ - README examples and `examples/hello-mcpserver` use `registerTool()`
156
+ instead of the deprecated `server.tool()`.
157
+ - README's configuration table now lists `unactionableErrors`, `coverage`
158
+ and `staleness`; the misplaced `unactionableErrors` doc comment in
159
+ `index.d.ts` is back on its own option.
160
+ - Test: the flush-on-exit "capped at 1000 ms" check now allows up to 5 s
161
+ (half the uncapped 10 s), so a loaded CI runner can't fail it.
162
+
3
163
  ## 0.16.0
4
164
 
5
165
  Finishes what 0.15 started. Telemetry now survives the way MCP clients