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 +160 -0
- package/README.md +574 -16
- package/package.json +3 -3
- package/src/attributes.js +16 -0
- package/src/config.js +31 -2
- package/src/cost/extractor.js +4 -13
- package/src/error-recording/types.d.ts +8 -6
- package/src/fingerprint/normalize/message.js +4 -1
- package/src/fingerprint/normalize/secrets.js +141 -0
- package/src/index.d.ts +159 -17
- package/src/index.js +3 -0
- package/src/instrument.js +158 -28
- package/src/metrics.js +41 -0
- package/src/result-meta.js +34 -0
- package/src/secret-leak.js +153 -0
- package/src/sensitive-tools.js +146 -0
- package/src/staleness.js +305 -0
- package/src/tool-integrity/attributes.js +29 -0
- package/src/tool-integrity/config.js +67 -0
- package/src/tool-integrity/emitter.js +110 -0
- package/src/tool-integrity/hash.js +104 -0
- package/src/tool-integrity/tracker.js +129 -0
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
|