@scopewatch/capability-inference 0.0.1

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.
@@ -0,0 +1,3 @@
1
+ export { inferCapabilities } from './infer.js';
2
+ export type { McpTool, InferredCapability, InferenceResult } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { inferCapabilities } from './infer.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpTool, InferenceResult } from './types.js';
2
+ export declare function inferCapabilities(server_id: string, tools: McpTool[]): InferenceResult;
3
+ //# sourceMappingURL=infer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infer.d.ts","sourceRoot":"","sources":["../src/infer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAsB,eAAe,EAAE,MAAM,YAAY,CAAC;AAsO/E,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,eAAe,CAmBtF"}
package/dist/infer.js ADDED
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Real (not real-time-negotiated) capability inference for MCP tools that
3
+ * lack a declared manifest. Every result is tagged provenance: 'inferred' -
4
+ * this is a best-effort heuristic, not ground truth, and callers must render
5
+ * it with the hedged "appears to..." language Phase B already built for
6
+ * exactly this case.
7
+ *
8
+ * Design validated against 14 real tools from the actual, live
9
+ * @modelcontextprotocol/server-filesystem (see test/fixtures/real-filesystem-server-tools.json,
10
+ * captured directly from a real tools/list response, not invented).
11
+ *
12
+ * TWO STRUCTURAL DEFENSES against the noun/verb ambiguity inherent to
13
+ * scanning natural-language tool descriptions (a word like "modified" or
14
+ * "messages" can be a verb or a noun/adjective depending on context a
15
+ * keyword regex can't see):
16
+ *
17
+ * 1. Verb inference checks the tool's NAME first for the "which flavor"
18
+ * decision (e.g. delete vs execute vs write within the destructive
19
+ * pool) - terse, verb-led names carry none of the incidental nouns/
20
+ * adjectives flowing description prose does.
21
+ *
22
+ * 2. The severity-override rule (see inferVerb, matchDangerousKeyword)
23
+ * scans the FULL name+description text regardless of what the name
24
+ * alone found - its purpose is catching a genuinely more dangerous
25
+ * signal that a calm-sounding NAME might otherwise suppress (a tool
26
+ * named "benign_reader" whose description says it "permanently
27
+ * deletes" a file) - but it is deliberately narrowed to ONLY the
28
+ * delete/execute patterns, not all six categories. The override's job
29
+ * is specifically "is this secretly destructive," not "does the full
30
+ * text contain any keyword at all" - scanning against all six was
31
+ * strictly broader than that job required, and that excess breadth was
32
+ * the exact path a stray 'send' match ("detailed error messages")
33
+ * reappeared through even with name-first in place and no other bug
34
+ * present.
35
+ *
36
+ * Confirmed directly, not assumed: with both defenses in place, temporarily
37
+ * reverting every word-specific exclusion in KEYWORD_PATTERNS below (the
38
+ * 'messag'/'creat'/'modif' exclusions and the 'sent'/'found' additions) and
39
+ * re-running the full fixture matrix - all 12 tests, including the ones
40
+ * that broke earlier iterations - passed with none of those patches in
41
+ * place. That's the actual signal that this closes the class of bug
42
+ * structurally rather than requiring an ever-growing patch list: the
43
+ * patches are kept anyway as defense-in-depth for the one path they still
44
+ * matter on (a generically-named tool with no name-first match still falls
45
+ * back to a full six-category scan for its default classification), but
46
+ * they are no longer load-bearing for the two cases that originally broke.
47
+ *
48
+ * This is not claimed as a complete solution to keyword-heuristic ambiguity
49
+ * over natural language - it structurally closed the specific interaction
50
+ * discovered so far. A future real server could still surface a new
51
+ * instance through the remaining fallback path; treat that as an
52
+ * interesting new fixture to add, not evidence of a regression.
53
+ */
54
+ const SEVERITY = { read: 0, fetch: 1, send: 1, write: 2, delete: 3, execute: 3 };
55
+ // Checked most-severe-first, so if a description contains multiple signal
56
+ // words, the more alarming one is the one that gets detected.
57
+ // NOTE: 'move'/'rename' are deliberately NOT in the write list - a relocation
58
+ // both creates at the destination and vacates the source, which is closer to
59
+ // a delete-shaped effect at the origin than a confident write signal. Left
60
+ // unmatched, it correctly falls through to the destructive-pool default.
61
+ //
62
+ // Patterns are left-anchored word STEMS (\b before, no \b after), not whole
63
+ // words - natural-language tool descriptions use conjugated forms
64
+ // ("deletes", "deleting", "created") far more often than bare infinitives,
65
+ // and \bdelete\b does not match "deletes" (no word boundary between "delete"
66
+ // and the trailing "s"). Left-anchoring on the stem catches all common
67
+ // inflections without needing to enumerate every conjugation.
68
+ // Longer, distinctive words use a left-anchored stem (catches conjugations
69
+ // like "deletes"/"deleting" without enumerating every form, since \bdelete\b
70
+ // does not match "deletes" - no word boundary between "delete" and "s").
71
+ // Short or common words (run, call, get) use exact whole-word matches
72
+ // instead, since a left-anchored stem on a short word risks false positives
73
+ // on unrelated words that merely start with it (e.g. "runtime", "callback",
74
+ // "getter").
75
+ // Audited for the conjugation gap that broke read_text_file's classification
76
+ // (\bdelete\b does not match "deletes"): every stem below is left-anchored
77
+ // (no trailing \b) unless the bare stem itself risks colliding with an
78
+ // unrelated word (run->runtime, call->callback, get->getter, sent->sentence/
79
+ // sentiment, found->foundation), in which case exact whole-word forms are
80
+ // enumerated instead - including irregular past tenses (sent, found) that a
81
+ // simple stem would miss entirely. Known residual gap, documented rather
82
+ // than chased further: rarer irregulars (ran, got, gotten, ate) are not
83
+ // exhaustively enumerated - see the module-level doc comment.
84
+ const KEYWORD_PATTERNS = [
85
+ ['delete', /\b(delete|remov|destroy|purg|drop)/],
86
+ ['execute', /\b(execut|invok|spawn)|\b(run|runs|running|call|calls|calling|called)\b/],
87
+ // 'messag' deliberately excluded: "error messages", "log messages" etc. are
88
+ // extremely common boilerplate in tool descriptions that have nothing to do
89
+ // with the tool sending anything - a noun/verb ambiguity that produced a
90
+ // real false positive (read_text_file's "detailed error messages" phrase)
91
+ // during validation against real tool data. 'sent' added as an exact
92
+ // whole-word form since "send" the bare stem doesn't cover this irregular
93
+ // past tense, and a bare 'sent' stem would collide with "sentence"/
94
+ // "sentiment".
95
+ ['send', /\b(send|post|publish|notif|email)|\bsent\b/],
96
+ ['fetch', /\b(fetch|download|pull|scrap)/],
97
+ // 'creat' and 'modif' as bare stems are deliberately avoided: "creation
98
+ // time"/"last modified time" are extremely common metadata-field phrases
99
+ // (nouns/adjectives describing timestamps, as in get_file_info's own
100
+ // description) with nothing to do with the tool creating or modifying
101
+ // anything - the same noun/verb ambiguity as 'messag' above. Bare
102
+ // "modified" is excluded too (it's the specific form that appears in
103
+ // "last modified" boilerplate); "modify"/"modifies"/"modifying" - active
104
+ // verb forms far more likely to describe a real action - are kept.
105
+ ['write', /\b(writ|updat|edit|sav|upload)|\b(create|creates|created|creating|modify|modifies|modifying)\b/],
106
+ // 'found' added as an exact whole-word form (irregular past tense of
107
+ // "find" - the bare 'find' stem doesn't cover it), guarded against
108
+ // colliding with "foundation".
109
+ ['read', /\b(read|list|search|view|inspect|quer|find)|\b(get|gets|getting|found)\b/],
110
+ ];
111
+ function normalize(text) {
112
+ return text.replace(/[_-]/g, ' ').toLowerCase();
113
+ }
114
+ function matchKeyword(text) {
115
+ for (const [verb, pattern] of KEYWORD_PATTERNS) {
116
+ if (pattern.test(text))
117
+ return verb;
118
+ }
119
+ return null;
120
+ }
121
+ // The severity-override's actual job is narrow: "does the full text contain
122
+ // a delete or execute signal the name alone missed" - not "does the full
123
+ // text contain ANY keyword at all." A name-first match to read/write/send/
124
+ // fetch that isn't contradicted by an actual delete/execute signal has
125
+ // nothing for the override to correct; scanning against all six categories
126
+ // was strictly broader than that job requires, and that excess breadth was
127
+ // exactly the path "detailed error messages" used to reappear as a false
128
+ // 'send' override even with name-first in place and no other bug present.
129
+ // Checking only the two categories the override actually exists to catch
130
+ // removes that path entirely, rather than patching around it per word.
131
+ const DANGEROUS_PATTERNS = KEYWORD_PATTERNS.filter(([verb]) => verb === 'delete' || verb === 'execute');
132
+ function matchDangerousKeyword(text) {
133
+ for (const [verb, pattern] of DANGEROUS_PATTERNS) {
134
+ if (pattern.test(text))
135
+ return verb;
136
+ }
137
+ return null;
138
+ }
139
+ function inferVerb(tool) {
140
+ // Name-first: a tool's NAME is terse and verb-led by convention
141
+ // (read_file, write_file, delete_record) with none of the incidental
142
+ // nouns/adjectives that flowing description prose carries (a description
143
+ // saying "creation time" or "detailed error messages" is never mirrored
144
+ // in a tool's own name). Checking the name alone first for the "which
145
+ // flavor" decision (e.g. within the destructive pool: delete vs execute
146
+ // vs write), and only falling through to the noisier description text
147
+ // when the name itself gives no signal, structurally avoids the whole
148
+ // CLASS of noun/verb ambiguity that produced two real false positives
149
+ // (read_text_file, get_file_info) during validation - not just those two
150
+ // specific words.
151
+ //
152
+ // This alone is NOT sufficient, though: it must not let a calm-sounding
153
+ // NAME suppress a genuinely more dangerous signal sitting in the
154
+ // description (e.g. a tool named "benign_reader" whose description says
155
+ // it "permanently deletes" something). So dangerousKeywordVerb below
156
+ // ALWAYS scans the full name+description text, independent of what the
157
+ // name-first check found - but ONLY against the delete/execute patterns
158
+ // specifically (see matchDangerousKeyword), not all six categories. The
159
+ // override's actual job is narrow ("is this secretly destructive"), and
160
+ // scanning against all six was strictly broader than that job needed -
161
+ // that excess breadth was the exact path a stray 'send' match ("detailed
162
+ // error messages") used to reappear even with name-first in place.
163
+ const nameOnlyVerb = matchKeyword(normalize(tool.name));
164
+ const fullText = normalize(`${tool.name} ${tool.description}`);
165
+ const dangerousKeywordVerb = matchDangerousKeyword(fullText);
166
+ const keywordVerb = nameOnlyVerb ?? matchKeyword(fullText);
167
+ const ann = tool.annotations;
168
+ const warnings = [];
169
+ if (ann?.destructiveHint === true && ann?.readOnlyHint === true) {
170
+ warnings.push(`Tool '${tool.name}' declares contradictory annotations (destructiveHint and readOnlyHint both true); resolving to the more cautious (destructive) classification.`);
171
+ }
172
+ let hintVerb;
173
+ if (ann?.destructiveHint === true) {
174
+ hintVerb = keywordVerb === 'delete' || keywordVerb === 'execute' || keywordVerb === 'write' ? keywordVerb : 'delete';
175
+ }
176
+ else if (ann?.readOnlyHint === true) {
177
+ hintVerb = keywordVerb === 'fetch' ? 'fetch' : 'read';
178
+ }
179
+ else if (ann && ann.destructiveHint === false && ann.readOnlyHint === false) {
180
+ hintVerb = keywordVerb ?? 'write';
181
+ }
182
+ else {
183
+ hintVerb = keywordVerb ?? 'execute';
184
+ }
185
+ let finalVerb = hintVerb;
186
+ if (dangerousKeywordVerb && SEVERITY[dangerousKeywordVerb] > SEVERITY[hintVerb]) {
187
+ warnings.push(`Tool '${tool.name}': keyword evidence ('${dangerousKeywordVerb}') found in its name or description is more severe than the annotation/name-based classification ('${hintVerb}'); using the more cautious classification.`);
188
+ finalVerb = dangerousKeywordVerb;
189
+ }
190
+ return { verb: finalVerb, warnings };
191
+ }
192
+ const RESOURCE_PATTERNS = [
193
+ ['filesystem:*', /\b(path|paths|file|filepath|directory|dir)\b/],
194
+ ['repo:*', /\b(repo|repository)\b/],
195
+ ['http:*', /\b(url|uri|endpoint)\b/],
196
+ ];
197
+ function inferResource(tool) {
198
+ // Deliberately scans ONLY the input schema's parameter names/descriptions,
199
+ // not the tool's free-text description - a description can casually
200
+ // mention path-shaped words ("...their nested paths...") without the tool
201
+ // actually taking any such parameter, which would otherwise produce a
202
+ // falsely specific resource guess instead of the honest wildcard fallback.
203
+ const props = tool.inputSchema?.properties ?? {};
204
+ const paramText = Object.entries(props)
205
+ .map(([key, val]) => `${key} ${val.description ?? ''}`)
206
+ .join(' ');
207
+ const text = normalize(paramText);
208
+ for (const [resource, pattern] of RESOURCE_PATTERNS) {
209
+ if (pattern.test(text))
210
+ return resource;
211
+ }
212
+ return '*';
213
+ }
214
+ export function inferCapabilities(server_id, tools) {
215
+ const capabilities = [];
216
+ const warnings = [];
217
+ for (const tool of tools) {
218
+ const { verb, warnings: toolWarnings } = inferVerb(tool);
219
+ const resource = inferResource(tool);
220
+ capabilities.push({
221
+ tool_id: tool.name,
222
+ verb,
223
+ resource,
224
+ provenance: 'inferred',
225
+ description: `inferred from tool name/description/schema (server: ${server_id})`,
226
+ });
227
+ warnings.push(...toolWarnings);
228
+ }
229
+ return { capabilities, warnings };
230
+ }
231
+ //# sourceMappingURL=infer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"infer.js","sourceRoot":"","sources":["../src/infer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /** A tool as returned by a real MCP server's tools/list response. */
2
+ export type McpTool = {
3
+ name: string;
4
+ description: string;
5
+ inputSchema?: {
6
+ properties?: Record<string, {
7
+ type?: string;
8
+ description?: string;
9
+ }>;
10
+ };
11
+ /** Optional per the MCP spec - self-reported by the server author, not independently verified. */
12
+ annotations?: {
13
+ readOnlyHint?: boolean;
14
+ destructiveHint?: boolean;
15
+ idempotentHint?: boolean;
16
+ openWorldHint?: boolean;
17
+ };
18
+ };
19
+ export type InferredCapability = {
20
+ tool_id: string;
21
+ verb: 'read' | 'fetch' | 'send' | 'write' | 'delete' | 'execute';
22
+ resource: string;
23
+ provenance: 'inferred';
24
+ description: string;
25
+ };
26
+ export type InferenceResult = {
27
+ capabilities: InferredCapability[];
28
+ /** Non-fatal signals worth surfacing in diagnostics - contradictory self-reported
29
+ * annotations, or cases where keyword evidence overrode a declared hint. */
30
+ warnings: string[];
31
+ };
32
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KACtE,CAAC;IACF,kGAAkG;IAClG,WAAW,CAAC,EAAE;QACZ,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,aAAa,CAAC,EAAE,OAAO,CAAC;KACzB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,UAAU,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC;gFAC4E;IAC5E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@scopewatch/capability-inference",
3
+ "version": "0.0.1",
4
+ "description": "Heuristic capability inference from real MCP tools/list data, for servers without declared capability metadata",
5
+ "license": "MIT",
6
+ "author": "Sathvik Putta <puttasathvik16@gmail.com>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/PuttaSathvik16/scopewatch.git",
10
+ "directory": "packages/capability-inference"
11
+ },
12
+ "type": "module",
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": "./dist/index.js",
18
+ "types": "./dist/index.d.ts"
19
+ }
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc",
26
+ "test": "node --import tsx --test 'test/**/*.test.ts'",
27
+ "clean": "rm -rf dist"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.0.0",
31
+ "typescript": "^5.7.0",
32
+ "tsx": "^4.19.0"
33
+ }
34
+ }