@mcp-index/sdk 0.3.2 → 0.5.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
@@ -3,6 +3,42 @@
3
3
  All notable changes to the TypeScript SDK. Versioning is semver (0.x: minor =
4
4
  additive feature, patch = fix). Backward-compatible additions only in 0.2.0.
5
5
 
6
+ ## [Unreleased]
7
+
8
+ Staged for the next publish — `npm publish` packs the current `README.md`, so these
9
+ ride out automatically with the next version bump (no ad-hoc republish needed).
10
+
11
+ ## 0.5.0 — 2026-06-09
12
+
13
+ ### Added
14
+ - **Drift telemetry (M1, opt-in, OFF by default).** On a tool pin or a contract drift the
15
+ gate can emit one one-way signal — salted (HMAC) fingerprints of the server/tool id, the
16
+ contract hashes, the change type, a safety flag, an hour-rounded time, and a random install
17
+ id. Never a schema, argument, description, URL, or server/tool name. Enable with
18
+ `MCPINDEX_DRIFT_TELEMETRY=detection`; fail-open (never blocks or changes a tool call). The
19
+ consent string is exported as `DRIFT_TELEMETRY_NOTICE`.
20
+
21
+ ### Docs
22
+ - README "What it does" now documents the default-on blast-radius grade (Tier-0a action
23
+ classification) shipped in 0.4.0, with the `MCPINDEX_ACTION_CLASSIFICATION_ENABLED=0`
24
+ opt-out. (0.4.0 published before the README caught up.)
25
+
26
+ ## 0.4.0 — 2026-06-08
27
+
28
+ ### Added
29
+ - **Local Tier-0a action classification (blast radius).** The gate now COMPUTES the
30
+ advisory `actionClassification` block locally from the live tool definition — action
31
+ type, resource, side-effect, reversibility, egress, and a static autonomy ceiling —
32
+ instead of leaving it `null` until a hosted verdict supplied one. So the blast-radius
33
+ grade is on by default in the SDK, at parity with the Python `mcpindex-preflight`
34
+ client. Default-on; opt out with `MCPINDEX_ACTION_CLASSIFICATION_ENABLED=0`. Pure,
35
+ deterministic, total, and a byte-for-byte port of the Python `cse.action_class.classify`
36
+ (12-case cross-language golden parity test). **Advisory only** — it rides alongside the
37
+ decision and never moves `PROCEED`/`HOLD` (AD-1). New exports: `classifyAction`,
38
+ `classifyToolDef`, `actionClassificationEnabled`, and the `ActionType` /
39
+ `SideEffectClass` / `Reversibility` / `Egress` / `ScopeHint` / `PatternShape` /
40
+ `AutonomyCeiling` / `NoteClass` / `Severity` / `EvidenceRefType` enums.
41
+
6
42
  ## 0.3.2 — 2026-06-08
7
43
 
8
44
  ### Fixed
package/README.md CHANGED
@@ -36,6 +36,11 @@ const guarded = wrap(session); // pins each tool's contract on first use (TOFU
36
36
  on a benign change (no false alarm).
37
37
  - Postures: `Monitor` (notify + proceed) / `Guard` (default; hold dangerous) /
38
38
  `Strict` (hold any drift).
39
+ - Grades each call's **blast radius** (Tier-0a action classification) on the verdict:
40
+ action type (read / write / delete / send / …), reversibility, egress, and a static
41
+ autonomy ceiling, computed locally from the live contract. **Advisory** — it rides
42
+ alongside the decision and never moves HOLD/PROCEED. On by default; opt out with
43
+ `MCPINDEX_ACTION_CLASSIFICATION_ENABLED=0`.
39
44
 
40
45
  ## What it is NOT
41
46
 
@@ -43,6 +48,32 @@ The verdict is a **contract-diff, not a safety oracle** — it tells you the con
43
48
  *changed* versus your pin, not that the new contract is safe. It does not "block attacks"
44
49
  or "guarantee safe." You decide what a HOLD means for your agent.
45
50
 
51
+ ## Drift telemetry (opt-in, OFF by default)
52
+
53
+ The SDK can report **that** a tool's contract drifted — so mcpindex can track drift on servers
54
+ it can't crawl itself (private / auth-gated). It is **off by default and sends nothing** unless
55
+ you turn it on:
56
+
57
+ ```sh
58
+ export MCPINDEX_DRIFT_TELEMETRY=detection # off (default) | detection | contribute
59
+ ```
60
+
61
+ When enabled, each tool you pin and each contract drift sends **one one-way signal**:
62
+
63
+ - **What it sends:** salted (HMAC) fingerprints of the server/tool id, the contract hashes, the
64
+ change type (the fixed ChangeKind vocabulary), a safety flag, an **hour-rounded** timestamp,
65
+ and a random install id (a token that links one machine's signals so distinct installs can be
66
+ counted — not derived from you).
67
+ - **What it NEVER sends:** tool schemas, arguments, descriptions, URLs, server/tool **names**, or
68
+ any of your data. The payload has no free-text field by construction, and the ingest rejects
69
+ anything that isn't the closed shape.
70
+ - **Fail-open:** telemetry never blocks, slows, or changes a tool call. Losing a signal is always
71
+ preferred to perturbing the gate.
72
+
73
+ `detection` enables the closed signal above; `contribute` is reserved for a future, separately
74
+ opt-in richer tier (it behaves identically today). The full notice is exported as
75
+ `DRIFT_TELEMETRY_NOTICE`. Unset the variable any time to stop.
76
+
46
77
  ## License
47
78
 
48
79
  PolyForm Noncommercial 1.0.0 — © Bhartis LLC. Free for noncommercial use; commercial use
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Tier 0a — the static action classifier (TS port of `cse.action_class`).
3
+ *
4
+ * Grades the *action* a tool would take (its blast radius), not just whether the
5
+ * tool is trustworthy. Pure, deterministic, model-free, total (never throws on
6
+ * hostile input). Reuses the structural risk leg (`risk.assess`) + the name-token
7
+ * lexicon (`lexicon.nameTokens`) and maps them into the spec's action-classification
8
+ * schema (action_type / resource / side-effect / reversibility / egress + a STATIC,
9
+ * fail-closed autonomy_ceiling).
10
+ *
11
+ * ADVISORY only: it rides ALONGSIDE the gate decision and NEVER moves PROCEED/HOLD.
12
+ * The `autonomy_ceiling` here is the STATIC cold-start default; the calibration-backed
13
+ * ceiling lives in the private moat and is structurally unreachable from the SDK.
14
+ *
15
+ * Cross-language parity: the value tables and derivation ladder mirror
16
+ * `corpus_eval/tooling/cse/action_class.py` byte-for-byte. Keep them in lockstep.
17
+ */
18
+ import type { ActionClassification, ToolDef } from "./preflight.js";
19
+ export declare enum ActionType {
20
+ READ = "read",
21
+ LIST = "list",
22
+ SEARCH = "search",
23
+ WRITE = "write",
24
+ UPDATE = "update",
25
+ DELETE = "delete",
26
+ SEND = "send",
27
+ EXECUTE = "execute",
28
+ AUTH = "auth",
29
+ ADMIN = "admin",
30
+ UNKNOWN = "unknown"
31
+ }
32
+ export declare enum SideEffectClass {
33
+ NONE = "none",
34
+ LOCAL_WRITE = "local-write",
35
+ OUTBOUND = "outbound",
36
+ DESTRUCTIVE = "destructive"
37
+ }
38
+ export declare enum Reversibility {
39
+ REVERSIBLE = "reversible",
40
+ HARD_TO_REVERSE = "hard-to-reverse",
41
+ IRREVERSIBLE = "irreversible"
42
+ }
43
+ export declare enum Egress {
44
+ NONE = "none",
45
+ INTERNAL = "internal",
46
+ EXTERNAL = "external"
47
+ }
48
+ export declare enum ScopeHint {
49
+ NARROW = "narrow",
50
+ BROAD = "broad",
51
+ UNBOUNDED = "unbounded"
52
+ }
53
+ export declare enum PatternShape {
54
+ LITERAL = "literal",
55
+ SINGLE_ARG = "single-arg",
56
+ GLOB = "glob",
57
+ WILDCARD = "wildcard",
58
+ TEMPLATED = "templated"
59
+ }
60
+ export declare enum AutonomyCeiling {
61
+ DISCOVERY = "discovery",
62
+ REVERSIBLE = "reversible",
63
+ NEEDS_APPROVAL = "needs-approval",
64
+ NEVER_UNATTENDED = "never-unattended"
65
+ }
66
+ export declare enum NoteClass {
67
+ DESTRUCTIVE_NO_UNDO_PATH = "destructive_no_undo_path",
68
+ ANNOTATION_CONTRADICTS_PROBE = "annotation_contradicts_probe",
69
+ KNOWN_CVE = "known_cve",
70
+ OBSERVED_EXFIL = "observed_exfil",
71
+ SCOPE_ESCAPE = "scope_escape"
72
+ }
73
+ export declare enum Severity {
74
+ LOW = "low",
75
+ MEDIUM = "medium",
76
+ HIGH = "high"
77
+ }
78
+ export declare enum EvidenceRefType {
79
+ PROBE = "probe",
80
+ SCHEMA_FLAG = "schema_flag",
81
+ CORPUS_CELL = "corpus_cell",
82
+ CVE = "cve"
83
+ }
84
+ /** Precedence ladder, deterministic legs only (annotations advisory-UP + verb-keyword
85
+ * over name). A self-declared readOnlyHint can NEVER lower a class the verb raises;
86
+ * destructiveHint:true floors the effective type at DELETE. No verb match -> UNKNOWN. */
87
+ export declare function deriveActionTypes(name: string, annotations?: unknown): [ActionType[], ActionType];
88
+ /** Static action classification (Tier 0a). Pure, deterministic, total. The
89
+ * autonomy_ceiling is the STATIC cold-start default (basis="static"). */
90
+ export declare function classify(name: string, description: string, inputSchema: unknown, annotations?: unknown): ActionClassification;
91
+ /** Tier 0a feature flag — default ON; opt out by setting the env var to a falsey
92
+ * string. Read at call time (not import) so a toggle takes effect without reimport. */
93
+ export declare function actionClassificationEnabled(env?: Record<string, string | undefined>): boolean;
94
+ /** Convenience wrapper for the gate: classify a live tool definition. Returns null
95
+ * when the flag is off or the definition is unusable (mirrors Python
96
+ * `build_action_classification`). Never throws. */
97
+ export declare function classifyToolDef(name: string, defn: ToolDef | null, env?: Record<string, string | undefined>): ActionClassification | null;
98
+ //# sourceMappingURL=actionClass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actionClass.d.ts","sourceRoot":"","sources":["../../src/actionClass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAIpE,oBAAY,UAAU;IACpB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,OAAO,YAAY;CACpB;AAED,oBAAY,eAAe;IACzB,IAAI,SAAS;IACb,WAAW,gBAAgB;IAC3B,QAAQ,aAAa;IACrB,WAAW,gBAAgB;CAC5B;AAED,oBAAY,aAAa;IACvB,UAAU,eAAe;IACzB,eAAe,oBAAoB;IACnC,YAAY,iBAAiB;CAC9B;AAED,oBAAY,MAAM;IAChB,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,QAAQ,aAAa;CACtB;AAED,oBAAY,SAAS;IACnB,MAAM,WAAW;IACjB,KAAK,UAAU;IACf,SAAS,cAAc;CACxB;AAED,oBAAY,YAAY;IACtB,OAAO,YAAY;IACnB,UAAU,eAAe;IACzB,IAAI,SAAS;IACb,QAAQ,aAAa;IACrB,SAAS,cAAc;CACxB;AAED,oBAAY,eAAe;IACzB,SAAS,cAAc;IACvB,UAAU,eAAe;IACzB,cAAc,mBAAmB;IACjC,gBAAgB,qBAAqB;CACtC;AAED,oBAAY,SAAS;IACnB,wBAAwB,6BAA6B;IACrD,4BAA4B,iCAAiC;IAC7D,SAAS,cAAc;IACvB,cAAc,mBAAmB;IACjC,YAAY,iBAAiB;CAC9B;AAED,oBAAY,QAAQ;IAClB,GAAG,QAAQ;IACX,MAAM,WAAW;IACjB,IAAI,SAAS;CACd;AAED,oBAAY,eAAe;IACzB,KAAK,UAAU;IACf,WAAW,gBAAgB;IAC3B,WAAW,gBAAgB;IAC3B,GAAG,QAAQ;CACZ;AAkED;;yFAEyF;AACzF,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,WAAW,GAAE,OAAc,GAC1B,CAAC,UAAU,EAAE,EAAE,UAAU,CAAC,CAc5B;AA8ID;yEACyE;AACzE,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,OAAO,EACpB,WAAW,GAAE,OAAc,GAC1B,oBAAoB,CAqBtB;AAKD;uFACuF;AACvF,wBAAgB,2BAA2B,CAAC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,OAAO,CAG1G;AAED;;mDAEmD;AACnD,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACvC,oBAAoB,GAAG,IAAI,CAmB7B"}
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Tier 0a — the static action classifier (TS port of `cse.action_class`).
3
+ *
4
+ * Grades the *action* a tool would take (its blast radius), not just whether the
5
+ * tool is trustworthy. Pure, deterministic, model-free, total (never throws on
6
+ * hostile input). Reuses the structural risk leg (`risk.assess`) + the name-token
7
+ * lexicon (`lexicon.nameTokens`) and maps them into the spec's action-classification
8
+ * schema (action_type / resource / side-effect / reversibility / egress + a STATIC,
9
+ * fail-closed autonomy_ceiling).
10
+ *
11
+ * ADVISORY only: it rides ALONGSIDE the gate decision and NEVER moves PROCEED/HOLD.
12
+ * The `autonomy_ceiling` here is the STATIC cold-start default; the calibration-backed
13
+ * ceiling lives in the private moat and is structurally unreachable from the SDK.
14
+ *
15
+ * Cross-language parity: the value tables and derivation ladder mirror
16
+ * `corpus_eval/tooling/cse/action_class.py` byte-for-byte. Keep them in lockstep.
17
+ */
18
+ import { assess, blastRank, BlastRadius, DomainClass } from "./risk.js";
19
+ import { nameTokens } from "./lexicon.js";
20
+ // ----------------------------------------------------------------------- enums
21
+ export var ActionType;
22
+ (function (ActionType) {
23
+ ActionType["READ"] = "read";
24
+ ActionType["LIST"] = "list";
25
+ ActionType["SEARCH"] = "search";
26
+ ActionType["WRITE"] = "write";
27
+ ActionType["UPDATE"] = "update";
28
+ ActionType["DELETE"] = "delete";
29
+ ActionType["SEND"] = "send";
30
+ ActionType["EXECUTE"] = "execute";
31
+ ActionType["AUTH"] = "auth";
32
+ ActionType["ADMIN"] = "admin";
33
+ ActionType["UNKNOWN"] = "unknown";
34
+ })(ActionType || (ActionType = {}));
35
+ export var SideEffectClass;
36
+ (function (SideEffectClass) {
37
+ SideEffectClass["NONE"] = "none";
38
+ SideEffectClass["LOCAL_WRITE"] = "local-write";
39
+ SideEffectClass["OUTBOUND"] = "outbound";
40
+ SideEffectClass["DESTRUCTIVE"] = "destructive";
41
+ })(SideEffectClass || (SideEffectClass = {}));
42
+ export var Reversibility;
43
+ (function (Reversibility) {
44
+ Reversibility["REVERSIBLE"] = "reversible";
45
+ Reversibility["HARD_TO_REVERSE"] = "hard-to-reverse";
46
+ Reversibility["IRREVERSIBLE"] = "irreversible";
47
+ })(Reversibility || (Reversibility = {}));
48
+ export var Egress;
49
+ (function (Egress) {
50
+ Egress["NONE"] = "none";
51
+ Egress["INTERNAL"] = "internal";
52
+ Egress["EXTERNAL"] = "external";
53
+ })(Egress || (Egress = {}));
54
+ export var ScopeHint;
55
+ (function (ScopeHint) {
56
+ ScopeHint["NARROW"] = "narrow";
57
+ ScopeHint["BROAD"] = "broad";
58
+ ScopeHint["UNBOUNDED"] = "unbounded";
59
+ })(ScopeHint || (ScopeHint = {}));
60
+ export var PatternShape;
61
+ (function (PatternShape) {
62
+ PatternShape["LITERAL"] = "literal";
63
+ PatternShape["SINGLE_ARG"] = "single-arg";
64
+ PatternShape["GLOB"] = "glob";
65
+ PatternShape["WILDCARD"] = "wildcard";
66
+ PatternShape["TEMPLATED"] = "templated";
67
+ })(PatternShape || (PatternShape = {}));
68
+ export var AutonomyCeiling;
69
+ (function (AutonomyCeiling) {
70
+ AutonomyCeiling["DISCOVERY"] = "discovery";
71
+ AutonomyCeiling["REVERSIBLE"] = "reversible";
72
+ AutonomyCeiling["NEEDS_APPROVAL"] = "needs-approval";
73
+ AutonomyCeiling["NEVER_UNATTENDED"] = "never-unattended";
74
+ })(AutonomyCeiling || (AutonomyCeiling = {}));
75
+ export var NoteClass;
76
+ (function (NoteClass) {
77
+ NoteClass["DESTRUCTIVE_NO_UNDO_PATH"] = "destructive_no_undo_path";
78
+ NoteClass["ANNOTATION_CONTRADICTS_PROBE"] = "annotation_contradicts_probe";
79
+ NoteClass["KNOWN_CVE"] = "known_cve";
80
+ NoteClass["OBSERVED_EXFIL"] = "observed_exfil";
81
+ NoteClass["SCOPE_ESCAPE"] = "scope_escape";
82
+ })(NoteClass || (NoteClass = {}));
83
+ export var Severity;
84
+ (function (Severity) {
85
+ Severity["LOW"] = "low";
86
+ Severity["MEDIUM"] = "medium";
87
+ Severity["HIGH"] = "high";
88
+ })(Severity || (Severity = {}));
89
+ export var EvidenceRefType;
90
+ (function (EvidenceRefType) {
91
+ EvidenceRefType["PROBE"] = "probe";
92
+ EvidenceRefType["SCHEMA_FLAG"] = "schema_flag";
93
+ EvidenceRefType["CORPUS_CELL"] = "corpus_cell";
94
+ EvidenceRefType["CVE"] = "cve";
95
+ })(EvidenceRefType || (EvidenceRefType = {}));
96
+ // ------------------------------------------------------- derivation primitives
97
+ // Verb token -> action_type. Most-destructive wins on multi-match (see DESTRUCT_RANK).
98
+ const VERB_TO_TYPE = [
99
+ [ActionType.DELETE, new Set("delete remove drop destroy purge wipe truncate prune".split(" "))],
100
+ [ActionType.EXECUTE, new Set("exec execute run eval".split(" "))],
101
+ [ActionType.ADMIN, new Set("provision deploy terminate reboot scale kill".split(" "))],
102
+ [ActionType.AUTH, new Set("authorize grant revoke issue".split(" "))],
103
+ [ActionType.SEND, new Set("send post publish notify dispatch email broadcast tweet sms message".split(" "))],
104
+ [ActionType.WRITE, new Set("write create insert upload put".split(" "))],
105
+ [ActionType.UPDATE, new Set("update modify patch merge set".split(" "))],
106
+ [ActionType.SEARCH, new Set("search query lookup find".split(" "))],
107
+ [ActionType.LIST, new Set("list".split(" "))],
108
+ [ActionType.READ, new Set("get read fetch describe show view".split(" "))],
109
+ ];
110
+ // Descending destructiveness — tiebreak for the "effective" action_type and the
111
+ // fail-closed ordering. UNKNOWN is most severe (we could not classify it).
112
+ const DESTRUCT_ORDER = [
113
+ ActionType.UNKNOWN,
114
+ ActionType.DELETE,
115
+ ActionType.EXECUTE,
116
+ ActionType.ADMIN,
117
+ ActionType.AUTH,
118
+ ActionType.SEND,
119
+ ActionType.WRITE,
120
+ ActionType.UPDATE,
121
+ ActionType.SEARCH,
122
+ ActionType.LIST,
123
+ ActionType.READ,
124
+ ];
125
+ const DESTRUCT_RANK = new Map(DESTRUCT_ORDER.map((t, i) => [t, i]));
126
+ const READ_TYPES = new Set([
127
+ ActionType.READ,
128
+ ActionType.LIST,
129
+ ActionType.SEARCH,
130
+ ]);
131
+ // Defense-in-depth caps on the attacker-controlled tool name + schema.
132
+ const MAX_NAME = 8192; // chars of name tokenized (mirrors risk MAX_TEXT)
133
+ const MAX_PROPS = 512; // top-level schema property keys scanned before fail-closed
134
+ function isPlainObject(x) {
135
+ return typeof x === "object" && x !== null && !Array.isArray(x);
136
+ }
137
+ function intersects(a, b) {
138
+ for (const x of a)
139
+ if (b.has(x))
140
+ return true;
141
+ return false;
142
+ }
143
+ /** Read a boolean MCP annotation hint; total, fail-safe (non-object / missing /
144
+ * non-bool -> false). */
145
+ function annotationBool(annotations, key) {
146
+ if (isPlainObject(annotations)) {
147
+ const v = annotations[key];
148
+ if (typeof v === "boolean")
149
+ return v;
150
+ }
151
+ return false;
152
+ }
153
+ /** Precedence ladder, deterministic legs only (annotations advisory-UP + verb-keyword
154
+ * over name). A self-declared readOnlyHint can NEVER lower a class the verb raises;
155
+ * destructiveHint:true floors the effective type at DELETE. No verb match -> UNKNOWN. */
156
+ export function deriveActionTypes(name, annotations = null) {
157
+ const tokens = nameTokens(name.slice(0, MAX_NAME));
158
+ const matched = new Set();
159
+ for (const [t, verbs] of VERB_TO_TYPE) {
160
+ if (intersects(tokens, verbs))
161
+ matched.add(t);
162
+ }
163
+ if (annotationBool(annotations, "destructiveHint"))
164
+ matched.add(ActionType.DELETE);
165
+ if (matched.size === 0)
166
+ return [[ActionType.UNKNOWN], ActionType.UNKNOWN];
167
+ const ordered = [...matched].sort((a, b) => (DESTRUCT_RANK.get(a) ?? 0) - (DESTRUCT_RANK.get(b) ?? 0));
168
+ return [ordered, ordered[0]];
169
+ }
170
+ /** Coarse static scope/pattern shape from the schema's top-level params. Conservative:
171
+ * no arg values, no deep walk. Fail-closed for non-trivial / pathological schemas. */
172
+ function scopePattern(inputSchema) {
173
+ if (!isPlainObject(inputSchema))
174
+ return [ScopeHint.BROAD, PatternShape.TEMPLATED];
175
+ const props = inputSchema["properties"];
176
+ if (!isPlainObject(props) || Object.keys(props).length === 0) {
177
+ return [ScopeHint.NARROW, PatternShape.LITERAL];
178
+ }
179
+ const keys = Object.keys(props);
180
+ if (keys.length > MAX_PROPS)
181
+ return [ScopeHint.UNBOUNDED, PatternShape.WILDCARD];
182
+ const names = new Set(keys.map((k) => k.toLowerCase()));
183
+ const globIsh = new Set(["glob", "pattern", "wildcard", "regex"]);
184
+ const multiIsh = new Set(["ids", "keys", "paths", "targets", "filters", "query"]);
185
+ if (intersects(names, globIsh))
186
+ return [ScopeHint.UNBOUNDED, PatternShape.GLOB];
187
+ for (const nm of names) {
188
+ const spec = props[nm];
189
+ if (isPlainObject(spec) && spec["type"] === "array") {
190
+ return [ScopeHint.UNBOUNDED, PatternShape.WILDCARD];
191
+ }
192
+ }
193
+ if (intersects(names, multiIsh))
194
+ return [ScopeHint.BROAD, PatternShape.WILDCARD];
195
+ return [ScopeHint.NARROW, PatternShape.SINGLE_ARG];
196
+ }
197
+ function sideEffectClass(eff, ra) {
198
+ if (READ_TYPES.has(eff) && ra.looksReadOnly)
199
+ return SideEffectClass.NONE;
200
+ if (eff === ActionType.DELETE || eff === ActionType.EXECUTE || eff === ActionType.ADMIN) {
201
+ return SideEffectClass.DESTRUCTIVE;
202
+ }
203
+ if (ra.domainClass === DomainClass.PAYMENTS)
204
+ return SideEffectClass.DESTRUCTIVE;
205
+ if (eff === ActionType.SEND || ra.domainClass === DomainClass.COMMS) {
206
+ return SideEffectClass.OUTBOUND;
207
+ }
208
+ if (eff === ActionType.WRITE || eff === ActionType.UPDATE || eff === ActionType.AUTH) {
209
+ return SideEffectClass.LOCAL_WRITE;
210
+ }
211
+ // Fallthrough: a read-NAMED tool the risk leg refused to confirm read-only, or an
212
+ // UNKNOWN type. Fail closed to local-write (UNKNOWN is floored to never-unattended
213
+ // downstream by the ceiling).
214
+ return SideEffectClass.LOCAL_WRITE;
215
+ }
216
+ function reversibility(eff, sec) {
217
+ if (eff === ActionType.DELETE ||
218
+ eff === ActionType.EXECUTE ||
219
+ eff === ActionType.ADMIN ||
220
+ eff === ActionType.AUTH) {
221
+ return Reversibility.IRREVERSIBLE;
222
+ }
223
+ if (sec === SideEffectClass.DESTRUCTIVE)
224
+ return Reversibility.IRREVERSIBLE;
225
+ if (sec === SideEffectClass.OUTBOUND || eff === ActionType.WRITE || eff === ActionType.UPDATE) {
226
+ return Reversibility.HARD_TO_REVERSE;
227
+ }
228
+ return Reversibility.REVERSIBLE;
229
+ }
230
+ function egressOf(eff, sec) {
231
+ if (sec === SideEffectClass.OUTBOUND)
232
+ return Egress.EXTERNAL;
233
+ if (READ_TYPES.has(eff) && sec === SideEffectClass.NONE)
234
+ return Egress.NONE;
235
+ return Egress.INTERNAL;
236
+ }
237
+ /** Cold-start fail-closed lattice. UNKNOWN / HIGH-blast-irreversible / execute ->
238
+ * never-unattended; then by side-effect/reversibility, fail-closed. */
239
+ function staticCeiling(eff, sec, rev, blast) {
240
+ if (eff === ActionType.UNKNOWN)
241
+ return AutonomyCeiling.NEVER_UNATTENDED;
242
+ if (rev === Reversibility.IRREVERSIBLE && blastRank(blast) >= blastRank(BlastRadius.HIGH)) {
243
+ return AutonomyCeiling.NEVER_UNATTENDED;
244
+ }
245
+ if (eff === ActionType.EXECUTE)
246
+ return AutonomyCeiling.NEVER_UNATTENDED;
247
+ if (rev === Reversibility.IRREVERSIBLE || sec === SideEffectClass.OUTBOUND) {
248
+ return AutonomyCeiling.NEEDS_APPROVAL;
249
+ }
250
+ if (sec === SideEffectClass.LOCAL_WRITE || rev === Reversibility.HARD_TO_REVERSE) {
251
+ return AutonomyCeiling.REVERSIBLE;
252
+ }
253
+ return AutonomyCeiling.DISCOVERY;
254
+ }
255
+ function resourceKind(ra) {
256
+ return ra.domainClass !== DomainClass.UNKNOWN ? ra.domainClass : "unknown";
257
+ }
258
+ function evidence(ra, eff) {
259
+ const refs = [];
260
+ if (ra.nameWriteish)
261
+ refs.push({ ref_type: EvidenceRefType.SCHEMA_FLAG, ref_id: "name_writeish" });
262
+ if (ra.hasWritePayloadParam) {
263
+ refs.push({ ref_type: EvidenceRefType.SCHEMA_FLAG, ref_id: "write_payload_param" });
264
+ }
265
+ if (ra.requiresCredential) {
266
+ refs.push({ ref_type: EvidenceRefType.SCHEMA_FLAG, ref_id: "requires_credential" });
267
+ }
268
+ if (ra.domainClass !== DomainClass.UNKNOWN) {
269
+ refs.push({ ref_type: EvidenceRefType.SCHEMA_FLAG, ref_id: `domain.${ra.domainClass}` });
270
+ }
271
+ refs.push({ ref_type: EvidenceRefType.SCHEMA_FLAG, ref_id: `action.${eff}` });
272
+ return refs.slice(0, 16);
273
+ }
274
+ function riskNotes(eff, sec, annotations) {
275
+ const notes = [];
276
+ if (annotationBool(annotations, "readOnlyHint") && sec !== SideEffectClass.NONE) {
277
+ notes.push({
278
+ note_class: NoteClass.ANNOTATION_CONTRADICTS_PROBE,
279
+ severity: Severity.HIGH,
280
+ source: { ref_type: EvidenceRefType.SCHEMA_FLAG, ref_id: "readonly_hint_vs_writeish" },
281
+ provenance: "derived",
282
+ });
283
+ }
284
+ if (sec === SideEffectClass.DESTRUCTIVE) {
285
+ notes.push({
286
+ note_class: NoteClass.DESTRUCTIVE_NO_UNDO_PATH,
287
+ severity: Severity.MEDIUM,
288
+ source: { ref_type: EvidenceRefType.SCHEMA_FLAG, ref_id: `action.${eff}` },
289
+ provenance: "derived",
290
+ });
291
+ }
292
+ return notes;
293
+ }
294
+ /** Static action classification (Tier 0a). Pure, deterministic, total. The
295
+ * autonomy_ceiling is the STATIC cold-start default (basis="static"). */
296
+ export function classify(name, description, inputSchema, annotations = null) {
297
+ const ra = assess(name, description, inputSchema);
298
+ const [types, eff] = deriveActionTypes(name, annotations);
299
+ const [scope, pattern] = scopePattern(inputSchema);
300
+ const sec = sideEffectClass(eff, ra);
301
+ const rev = reversibility(eff, sec);
302
+ const egress = egressOf(eff, sec);
303
+ const ceiling = staticCeiling(eff, sec, rev, ra.blastRadius);
304
+ return {
305
+ action_types: types,
306
+ effective_action_type: eff,
307
+ resource: { kind: resourceKind(ra), pattern, scope_hint: scope },
308
+ side_effect_class: sec,
309
+ reversibility: rev,
310
+ egress,
311
+ autonomy_ceiling: ceiling,
312
+ autonomy_ceiling_basis: "static",
313
+ known_risk_notes: riskNotes(eff, sec, annotations),
314
+ evidence: evidence(ra, eff),
315
+ };
316
+ }
317
+ const ACTION_CLASS_ENV = "MCPINDEX_ACTION_CLASSIFICATION_ENABLED";
318
+ const FALSEY = new Set(["0", "false", "no", "off"]);
319
+ /** Tier 0a feature flag — default ON; opt out by setting the env var to a falsey
320
+ * string. Read at call time (not import) so a toggle takes effect without reimport. */
321
+ export function actionClassificationEnabled(env = process.env) {
322
+ const raw = (env[ACTION_CLASS_ENV] ?? "").trim().toLowerCase();
323
+ return !FALSEY.has(raw);
324
+ }
325
+ /** Convenience wrapper for the gate: classify a live tool definition. Returns null
326
+ * when the flag is off or the definition is unusable (mirrors Python
327
+ * `build_action_classification`). Never throws. */
328
+ export function classifyToolDef(name, defn, env) {
329
+ if (!actionClassificationEnabled(env))
330
+ return null;
331
+ if (defn === null || !name)
332
+ return null;
333
+ try {
334
+ const d = defn;
335
+ const rawDesc = d["description"];
336
+ // Mirror Python `str(defn.get("description", ""))`: identical for the normal string
337
+ // case; a non-string description is malformed/hostile, so stringify its CONTENT (never
338
+ // "[object Object]") so embedded text is still scanned — the grade can't be suppressed by
339
+ // sending a non-string. Security-conservative; never fail-open to a lower blast radius.
340
+ const description = typeof rawDesc === "string" ? rawDesc : rawDesc == null ? "" : JSON.stringify(rawDesc);
341
+ // Mirror Python `schema if isinstance(schema, dict) else {}`: a tool with no (or a
342
+ // non-object) inputSchema grades identically here and on the server.
343
+ const inputSchema = isPlainObject(d["inputSchema"]) ? d["inputSchema"] : {};
344
+ return classify(name, description, inputSchema, d["annotations"]);
345
+ }
346
+ catch {
347
+ return null;
348
+ }
349
+ }
350
+ //# sourceMappingURL=actionClass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actionClass.js","sourceRoot":"","sources":["../../src/actionClass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Drift telemetry — the detection-tier emit (M1 of the usage-log drift flywheel).
3
+ *
4
+ * Opt-in (`MCPINDEX_DRIFT_TELEMETRY` ∈ {off,detection,contribute}, default off),
5
+ * fire-and-forget, fail-OPEN. On a TOFU pin (coverage) or a classified drift (outcome),
6
+ * emit a CLOSED, privacy-by-construction signal: salted fingerprints + change enums +
7
+ * content hashes ONLY — NEVER a schema, an argument, a URL, or a plaintext server/tool
8
+ * name. Mirrors the `outcomes.ts` discipline: the only thing that ever leaves the device
9
+ * is a safe projection built by construction, not a redaction of a richer record.
10
+ *
11
+ * PRIVACY INVARIANT (enforced by construction + the leak test): the DriftSignal has no
12
+ * field that can hold a raw string from the tool's schema/args/description.
13
+ * - server_fp / tool_fp are HMAC-SHA256 fingerprints under a GLOBAL salt. Global salt =>
14
+ * a public server_id is de-anonymizable by anyone who can guess it (ACCEPTED — public
15
+ * servers are public); a private/internal server_id is unreversible (PROTECTED).
16
+ * - prev_hash / new_hash are the gate's own `definition_hash` — already a SHA over the
17
+ * canonical contract, public-by-design (it is what the verdict is keyed on).
18
+ * - change_kinds is the closed `ChangeKind` enum vocabulary; safety_relevant is a bool.
19
+ * - at_hour is coarsened to the hour (no sub-hour timing fingerprint).
20
+ *
21
+ * The emit path NEVER blocks on the network and NEVER throws: it appends to a bounded ring
22
+ * buffer and returns; a single unref'd timer flushes batches in the background. A flush
23
+ * failure DROPS the batch (no retry) — losing telemetry is always preferable to perturbing
24
+ * a tool call. Telemetry is a SINGLETON across every Gate (one buffer, one timer).
25
+ *
26
+ * Byte-identical fingerprints to the Python port `tooling/cse/drift_telemetry.py` — the
27
+ * cross-language parity test pins this.
28
+ */
29
+ import type { Change } from "./schemaDiff.js";
30
+ export type DriftMode = "off" | "detection" | "contribute";
31
+ /** The canonical human-readable consent notice — the single source of truth for the README
32
+ * and the future first-run prompt (M5). Byte-identical to the Python `DRIFT_TELEMETRY_NOTICE`. */
33
+ export declare const DRIFT_TELEMETRY_NOTICE: string;
34
+ /** The CLOSED wire payload. Every field is a fingerprint, a content hash, a closed enum,
35
+ * a bool, an hour-stamp, or the opaque install id — never a raw tool string. */
36
+ export interface DriftSignal {
37
+ readonly v: 1;
38
+ readonly event: "pin" | "drift";
39
+ readonly server_fp: string;
40
+ readonly tool_fp: string;
41
+ readonly prev_hash: string | null;
42
+ readonly new_hash: string;
43
+ readonly change_kinds: string[] | null;
44
+ readonly safety_relevant: boolean;
45
+ readonly at_hour: string;
46
+ readonly sdk: string;
47
+ readonly install_id: string;
48
+ }
49
+ export type DriftTransport = (batch: DriftSignal[]) => Promise<void>;
50
+ /** HMAC-SHA256(GLOBAL_SALT, msg) hex, truncated to 16 bytes (32 hex chars). Byte-identical
51
+ * to the Python `_fingerprint`. */
52
+ export declare function fingerprint(msg: string): string;
53
+ export declare function serverFp(serverId: string): string;
54
+ export declare function toolFp(serverId: string, toolName: string): string;
55
+ /** Coarsen an ISO-8601 stamp to the hour: "2026-06-09T05:09:46.123Z" → "2026-06-09T05:00:00Z".
56
+ * Returns "" on any unexpected shape (never leaks sub-hour precision; never throws). Mirrors
57
+ * the Python `_at_hour`. */
58
+ export declare function atHour(nowIso: string): string;
59
+ /** Build the coverage (pin) signal. Pure — exported for the leak/parity tests. */
60
+ export declare function buildPinSignal(serverId: string, toolName: string, definitionHash: string, nowIso: string, installId: string): DriftSignal;
61
+ /** Build the outcome (drift) signal. Pure — exported for the leak/parity tests. The change
62
+ * details (path/detail strings, which CAN carry tool text) are DROPPED here by construction;
63
+ * only the closed `kind` enum and the `safetyRelevant` bool survive. */
64
+ export declare function buildDriftSignal(serverId: string, toolName: string, prevHash: string, newHash: string, changes: readonly Change[], nowIso: string, installId: string): DriftSignal;
65
+ /** The singleton emit engine. One per process; every Gate shares it. */
66
+ declare class DriftTelemetry {
67
+ private buffer;
68
+ private timer;
69
+ private flushing;
70
+ private mode;
71
+ private cachedInstallId;
72
+ private transport;
73
+ private resolveMode;
74
+ enabled(): boolean;
75
+ private installId;
76
+ private enqueue;
77
+ /** Drain the buffer and POST it. Best-effort: a transport failure drops the batch with no
78
+ * retry (fail-open). Never throws. */
79
+ flush(): Promise<void>;
80
+ recordPin(serverId: string, toolName: string, definitionHash: string, nowIso: string): void;
81
+ recordDrift(serverId: string, toolName: string, prevHash: string, newHash: string, changes: readonly Change[], nowIso: string): void;
82
+ _setTransport(t: DriftTransport): void;
83
+ _reset(): void;
84
+ _drainForTest(): DriftSignal[];
85
+ }
86
+ /** Process-wide singleton. The Gate calls `driftTelemetry.recordPin/recordDrift`. */
87
+ export declare const driftTelemetry: DriftTelemetry;
88
+ export {};
89
+ //# sourceMappingURL=driftTelemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"driftTelemetry.d.ts","sourceRoot":"","sources":["../../src/driftTelemetry.ts"],"names":[],"mappings":"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"}