@bryan-thompson/inspector-assessment 1.43.0 → 1.43.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.
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bryan-thompson/inspector-assessment-cli",
3
- "version": "1.43.0",
3
+ "version": "1.43.1",
4
4
  "description": "CLI for the Enhanced MCP Inspector with assessment capabilities",
5
5
  "license": "MIT",
6
6
  "author": "Bryan Thompson <bryan@triepod.ai>",
@@ -1,4 +1,4 @@
1
- import { u as useToast, r as reactExports, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-DEeUx8Bb.js";
1
+ import { u as useToast, r as reactExports, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-97IA_LWd.js";
2
2
  const OAuthCallback = ({ onConnect }) => {
3
3
  const { toast } = useToast();
4
4
  const hasProcessedRef = reactExports.useRef(false);
@@ -1,4 +1,4 @@
1
- import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-DEeUx8Bb.js";
1
+ import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-97IA_LWd.js";
2
2
  const OAuthDebugCallback = ({ onConnect }) => {
3
3
  reactExports.useEffect(() => {
4
4
  let isProcessed = false;
@@ -16373,7 +16373,7 @@ object({
16373
16373
  token_type_hint: string().optional()
16374
16374
  }).strip();
16375
16375
  const name = "@bryan-thompson/inspector-assessment-client";
16376
- const version$1 = "1.43.0";
16376
+ const version$1 = "1.43.1";
16377
16377
  const packageJson = {
16378
16378
  name,
16379
16379
  version: version$1
@@ -49456,7 +49456,7 @@ const useTheme = () => {
49456
49456
  [theme, setThemeWithSideEffect]
49457
49457
  );
49458
49458
  };
49459
- const version = "1.43.0";
49459
+ const version = "1.43.1";
49460
49460
  var [createTooltipContext] = createContextScope("Tooltip", [
49461
49461
  createPopperScope
49462
49462
  ]);
@@ -52799,13 +52799,13 @@ const App = () => {
52799
52799
  };
52800
52800
  if (window.location.pathname === "/oauth/callback") {
52801
52801
  const OAuthCallback = React.lazy(
52802
- () => __vitePreload(() => import("./OAuthCallback-BEnTdTGR.js"), true ? [] : void 0)
52802
+ () => __vitePreload(() => import("./OAuthCallback-ngu_aFUO.js"), true ? [] : void 0)
52803
52803
  );
52804
52804
  return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthCallback, { onConnect: onOAuthConnect }) });
52805
52805
  }
52806
52806
  if (window.location.pathname === "/oauth/callback/debug") {
52807
52807
  const OAuthDebugCallback = React.lazy(
52808
- () => __vitePreload(() => import("./OAuthDebugCallback-CTNSrDLW.js"), true ? [] : void 0)
52808
+ () => __vitePreload(() => import("./OAuthDebugCallback-CsGYu8op.js"), true ? [] : void 0)
52809
52809
  );
52810
52810
  return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthDebugCallback, { onConnect: onOAuthDebugConnect }) });
52811
52811
  }
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/mcp.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>MCP Inspector</title>
8
- <script type="module" crossorigin src="/assets/index-DEeUx8Bb.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-97IA_LWd.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-BoUA5OL1.css">
10
10
  </head>
11
11
  <body>
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Static Annotation Scanner
3
+ *
4
+ * Scans source code files for tool annotations using AST parsing.
5
+ * Detects annotations nested inside tool definition objects/arrays
6
+ * in ES module syntax that regex-based scanning would miss.
7
+ *
8
+ * Fixes Issue #192: Static annotation scanner misses nested annotations
9
+ * in ES module syntax like:
10
+ * const TOOLS = [{ name: 'x', annotations: { readOnlyHint: true } }];
11
+ *
12
+ * @module helpers/StaticAnnotationScanner
13
+ */
14
+ /**
15
+ * Evidence from static annotation scanning
16
+ */
17
+ export interface StaticAnnotationEvidence {
18
+ /** File path where annotation was found */
19
+ filePath: string;
20
+ /** Tool name associated with the annotation */
21
+ toolName: string;
22
+ /** Confidence level */
23
+ confidence: "high" | "medium" | "low";
24
+ /** Description of how the annotation was found */
25
+ detail: string;
26
+ /** Line number in source file */
27
+ lineNumber?: number;
28
+ }
29
+ /**
30
+ * Extracted annotation from source code
31
+ */
32
+ export interface StaticAnnotation {
33
+ toolName: string;
34
+ readOnlyHint?: boolean;
35
+ destructiveHint?: boolean;
36
+ idempotentHint?: boolean;
37
+ openWorldHint?: boolean;
38
+ }
39
+ /**
40
+ * Result of static annotation scanning
41
+ */
42
+ export interface StaticAnnotationScanResult {
43
+ /** Map of tool name to extracted annotations */
44
+ annotations: Map<string, StaticAnnotation>;
45
+ /** Overall confidence of the scan */
46
+ confidence: "high" | "medium" | "low";
47
+ /** Evidence collected during scanning */
48
+ evidence: StaticAnnotationEvidence[];
49
+ /** Whether source code was scanned */
50
+ sourceCodeScanned: boolean;
51
+ /** Count of tools with annotations found */
52
+ annotatedToolCount: number;
53
+ /** Files that were scanned */
54
+ scannedFiles: string[];
55
+ /** Errors encountered during parsing */
56
+ parseErrors: Array<{
57
+ file: string;
58
+ error: string;
59
+ }>;
60
+ }
61
+ /**
62
+ * Scans source code for tool annotations using AST parsing.
63
+ *
64
+ * Detection approach:
65
+ * 1. Parse JS/TS files with acorn (ecmaVersion 2022, module syntax)
66
+ * 2. Walk AST looking for Property nodes with key 'annotations'
67
+ * 3. Extract annotation values (readOnlyHint, destructiveHint, etc.)
68
+ * 4. Find associated tool name from sibling 'name' property in parent object
69
+ *
70
+ * @public
71
+ */
72
+ export declare class StaticAnnotationScanner {
73
+ /**
74
+ * File patterns to skip during source code scanning
75
+ * (same patterns as StdioTransportDetector for consistency)
76
+ */
77
+ private readonly SKIP_FILE_PATTERNS;
78
+ /** Maximum file size for source scanning (500KB) */
79
+ private readonly MAX_FILE_SIZE;
80
+ /** File extensions to scan */
81
+ private readonly SCANNABLE_EXTENSIONS;
82
+ /**
83
+ * Scan source files for tool annotations.
84
+ *
85
+ * @param sourceCodeFiles - Map of file paths to content
86
+ * @returns Static annotation scan results
87
+ */
88
+ scan(sourceCodeFiles?: Map<string, string>): StaticAnnotationScanResult;
89
+ /**
90
+ * Parse a single file for tool annotations.
91
+ *
92
+ * @param filePath - File path for error reporting
93
+ * @param content - File content to parse
94
+ * @returns Array of extracted annotations with line numbers
95
+ */
96
+ private parseFile;
97
+ /**
98
+ * Check if a property node is an 'annotations' property.
99
+ */
100
+ private isAnnotationsProperty;
101
+ /**
102
+ * Extract annotation values from an ObjectExpression node.
103
+ */
104
+ private extractAnnotationValues;
105
+ /**
106
+ * Get the string name of a property key.
107
+ */
108
+ private getPropertyKeyName;
109
+ /**
110
+ * Find the tool name from ancestor context.
111
+ * Looks for a sibling 'name' property in the parent ObjectExpression.
112
+ */
113
+ private findToolNameFromContext;
114
+ /**
115
+ * Strip common TypeScript syntax for basic parsing.
116
+ * This is a simple approach - just removes type annotations to allow JS parsing.
117
+ */
118
+ private stripTypeScript;
119
+ /**
120
+ * Check if file should be skipped during scanning.
121
+ */
122
+ private shouldSkipFile;
123
+ /**
124
+ * Check if file has a scannable extension.
125
+ */
126
+ private isScannableFile;
127
+ /**
128
+ * Compute overall confidence from collected evidence.
129
+ *
130
+ * Confidence rules:
131
+ * - High: 2+ annotations found with explicit 'annotations' objects
132
+ * - Medium: 1 annotation found
133
+ * - Low: No annotations found
134
+ */
135
+ private computeConfidence;
136
+ }
137
+ //# sourceMappingURL=StaticAnnotationScanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StaticAnnotationScanner.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/helpers/StaticAnnotationScanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,gDAAgD;IAChD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC3C,qCAAqC;IACrC,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IACtC,yCAAyC;IACzC,QAAQ,EAAE,wBAAwB,EAAE,CAAC;IACrC,sCAAsC;IACtC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,4CAA4C;IAC5C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,8BAA8B;IAC9B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,wCAAwC;IACxC,WAAW,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrD;AA2CD;;;;;;;;;;GAUG;AACH,qBAAa,uBAAuB;IAClC;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAejC;IAEF,oDAAoD;IACpD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAW;IAEzC,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAOnC;IAEF;;;;;OAKG;IACH,IAAI,CAAC,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,0BAA0B;IAmEvE;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAuEjB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAY7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA8C/B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAU1B;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IA4B/B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAkBvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,OAAO,CAAC,eAAe;IAMvB;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;CAsB1B"}
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Static Annotation Scanner
3
+ *
4
+ * Scans source code files for tool annotations using AST parsing.
5
+ * Detects annotations nested inside tool definition objects/arrays
6
+ * in ES module syntax that regex-based scanning would miss.
7
+ *
8
+ * Fixes Issue #192: Static annotation scanner misses nested annotations
9
+ * in ES module syntax like:
10
+ * const TOOLS = [{ name: 'x', annotations: { readOnlyHint: true } }];
11
+ *
12
+ * @module helpers/StaticAnnotationScanner
13
+ */
14
+ import * as acorn from "acorn";
15
+ import * as walk from "acorn-walk";
16
+ /**
17
+ * Scans source code for tool annotations using AST parsing.
18
+ *
19
+ * Detection approach:
20
+ * 1. Parse JS/TS files with acorn (ecmaVersion 2022, module syntax)
21
+ * 2. Walk AST looking for Property nodes with key 'annotations'
22
+ * 3. Extract annotation values (readOnlyHint, destructiveHint, etc.)
23
+ * 4. Find associated tool name from sibling 'name' property in parent object
24
+ *
25
+ * @public
26
+ */
27
+ export class StaticAnnotationScanner {
28
+ /**
29
+ * File patterns to skip during source code scanning
30
+ * (same patterns as StdioTransportDetector for consistency)
31
+ */
32
+ SKIP_FILE_PATTERNS = [
33
+ /node_modules/i,
34
+ /\.test\.(ts|js|tsx|jsx|py)$/i,
35
+ /\.spec\.(ts|js|tsx|jsx|py)$/i,
36
+ /\.d\.ts$/i,
37
+ /package-lock\.json$/i,
38
+ /yarn\.lock$/i,
39
+ /\.map$/i,
40
+ /\.git\//i,
41
+ /dist\//i,
42
+ /build\//i,
43
+ /__tests__\//i,
44
+ /__mocks__\//i,
45
+ /__pycache__\//i,
46
+ /\.pytest_cache\//i,
47
+ ];
48
+ /** Maximum file size for source scanning (500KB) */
49
+ MAX_FILE_SIZE = 500_000;
50
+ /** File extensions to scan */
51
+ SCANNABLE_EXTENSIONS = [
52
+ ".js",
53
+ ".ts",
54
+ ".mjs",
55
+ ".cjs",
56
+ ".tsx",
57
+ ".jsx",
58
+ ];
59
+ /**
60
+ * Scan source files for tool annotations.
61
+ *
62
+ * @param sourceCodeFiles - Map of file paths to content
63
+ * @returns Static annotation scan results
64
+ */
65
+ scan(sourceCodeFiles) {
66
+ const annotations = new Map();
67
+ const evidence = [];
68
+ const scannedFiles = [];
69
+ const parseErrors = [];
70
+ if (!sourceCodeFiles || sourceCodeFiles.size === 0) {
71
+ return {
72
+ annotations,
73
+ confidence: "low",
74
+ evidence,
75
+ sourceCodeScanned: false,
76
+ annotatedToolCount: 0,
77
+ scannedFiles,
78
+ parseErrors,
79
+ };
80
+ }
81
+ sourceCodeFiles.forEach((content, filePath) => {
82
+ // Skip files that shouldn't be scanned
83
+ if (this.shouldSkipFile(filePath))
84
+ return;
85
+ // Skip oversized files
86
+ if (content.length > this.MAX_FILE_SIZE)
87
+ return;
88
+ // Only scan JS/TS files
89
+ if (!this.isScannableFile(filePath))
90
+ return;
91
+ scannedFiles.push(filePath);
92
+ try {
93
+ const fileAnnotations = this.parseFile(filePath, content);
94
+ for (const ann of fileAnnotations) {
95
+ // Store annotation (later occurrences override earlier)
96
+ annotations.set(ann.toolName, ann);
97
+ // Record evidence
98
+ evidence.push({
99
+ filePath,
100
+ toolName: ann.toolName,
101
+ confidence: "high",
102
+ detail: `Found annotations object in tool definition`,
103
+ lineNumber: ann.lineNumber,
104
+ });
105
+ }
106
+ }
107
+ catch (error) {
108
+ parseErrors.push({
109
+ file: filePath,
110
+ error: error instanceof Error ? error.message : String(error),
111
+ });
112
+ }
113
+ });
114
+ const confidence = this.computeConfidence(evidence);
115
+ return {
116
+ annotations,
117
+ confidence,
118
+ evidence,
119
+ sourceCodeScanned: scannedFiles.length > 0,
120
+ annotatedToolCount: annotations.size,
121
+ scannedFiles,
122
+ parseErrors,
123
+ };
124
+ }
125
+ /**
126
+ * Parse a single file for tool annotations.
127
+ *
128
+ * @param filePath - File path for error reporting
129
+ * @param content - File content to parse
130
+ * @returns Array of extracted annotations with line numbers
131
+ */
132
+ parseFile(filePath, content) {
133
+ const results = [];
134
+ // Try to parse as ES module first, fall back to script
135
+ let ast;
136
+ try {
137
+ ast = acorn.parse(content, {
138
+ ecmaVersion: 2022,
139
+ sourceType: "module",
140
+ locations: true,
141
+ });
142
+ }
143
+ catch {
144
+ // Try as script (CommonJS)
145
+ try {
146
+ ast = acorn.parse(content, {
147
+ ecmaVersion: 2022,
148
+ sourceType: "script",
149
+ locations: true,
150
+ });
151
+ }
152
+ catch (scriptError) {
153
+ // For TypeScript files, try stripping type annotations
154
+ // (basic approach - strip common TS patterns)
155
+ const strippedContent = this.stripTypeScript(content);
156
+ try {
157
+ ast = acorn.parse(strippedContent, {
158
+ ecmaVersion: 2022,
159
+ sourceType: "module",
160
+ locations: true,
161
+ });
162
+ }
163
+ catch {
164
+ throw new Error(`Failed to parse ${filePath}: ${scriptError instanceof Error ? scriptError.message : String(scriptError)}`);
165
+ }
166
+ }
167
+ }
168
+ // Walk the AST with ancestor tracking
169
+ walk.ancestor(ast, {
170
+ Property: (node, ancestors) => {
171
+ const prop = node;
172
+ // Check if this is an 'annotations' property
173
+ if (!this.isAnnotationsProperty(prop))
174
+ return;
175
+ // Value must be an object expression
176
+ if (prop.value.type !== "ObjectExpression")
177
+ return;
178
+ const annotationValues = this.extractAnnotationValues(prop.value);
179
+ if (!annotationValues)
180
+ return;
181
+ // Find the tool name from parent context
182
+ const toolName = this.findToolNameFromContext(ancestors);
183
+ if (!toolName)
184
+ return;
185
+ results.push({
186
+ toolName,
187
+ ...annotationValues,
188
+ lineNumber: prop.loc?.start.line,
189
+ });
190
+ },
191
+ });
192
+ return results;
193
+ }
194
+ /**
195
+ * Check if a property node is an 'annotations' property.
196
+ */
197
+ isAnnotationsProperty(prop) {
198
+ // Handle Identifier key: annotations: {...}
199
+ if (prop.key.type === "Identifier") {
200
+ return prop.key.name === "annotations";
201
+ }
202
+ // Handle Literal key: 'annotations': {...} or "annotations": {...}
203
+ if (prop.key.type === "Literal") {
204
+ return prop.key.value === "annotations";
205
+ }
206
+ return false;
207
+ }
208
+ /**
209
+ * Extract annotation values from an ObjectExpression node.
210
+ */
211
+ extractAnnotationValues(obj) {
212
+ const result = {};
213
+ let hasAnyAnnotation = false;
214
+ for (const prop of obj.properties) {
215
+ if (prop.type !== "Property")
216
+ continue;
217
+ const propNode = prop;
218
+ const keyName = this.getPropertyKeyName(propNode);
219
+ if (!keyName)
220
+ continue;
221
+ // Only extract boolean values
222
+ if (propNode.value.type !== "Literal")
223
+ continue;
224
+ const literalValue = propNode.value.value;
225
+ if (typeof literalValue !== "boolean")
226
+ continue;
227
+ // Map property names (handle both *Hint and non-suffixed)
228
+ switch (keyName) {
229
+ case "readOnlyHint":
230
+ case "readOnly":
231
+ result.readOnlyHint = literalValue;
232
+ hasAnyAnnotation = true;
233
+ break;
234
+ case "destructiveHint":
235
+ case "destructive":
236
+ result.destructiveHint = literalValue;
237
+ hasAnyAnnotation = true;
238
+ break;
239
+ case "idempotentHint":
240
+ case "idempotent":
241
+ result.idempotentHint = literalValue;
242
+ hasAnyAnnotation = true;
243
+ break;
244
+ case "openWorldHint":
245
+ case "openWorld":
246
+ result.openWorldHint = literalValue;
247
+ hasAnyAnnotation = true;
248
+ break;
249
+ }
250
+ }
251
+ return hasAnyAnnotation ? result : null;
252
+ }
253
+ /**
254
+ * Get the string name of a property key.
255
+ */
256
+ getPropertyKeyName(prop) {
257
+ if (prop.key.type === "Identifier") {
258
+ return prop.key.name;
259
+ }
260
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string") {
261
+ return prop.key.value;
262
+ }
263
+ return null;
264
+ }
265
+ /**
266
+ * Find the tool name from ancestor context.
267
+ * Looks for a sibling 'name' property in the parent ObjectExpression.
268
+ */
269
+ findToolNameFromContext(ancestors) {
270
+ // Walk up ancestors looking for ObjectExpression (the tool definition)
271
+ for (let i = ancestors.length - 1; i >= 0; i--) {
272
+ const ancestor = ancestors[i];
273
+ if (ancestor.type === "ObjectExpression") {
274
+ const objNode = ancestor;
275
+ // Look for sibling 'name' property
276
+ for (const prop of objNode.properties) {
277
+ if (prop.type !== "Property")
278
+ continue;
279
+ const propNode = prop;
280
+ const keyName = this.getPropertyKeyName(propNode);
281
+ if (keyName === "name" && propNode.value.type === "Literal") {
282
+ const nameValue = propNode.value.value;
283
+ if (typeof nameValue === "string") {
284
+ return nameValue;
285
+ }
286
+ }
287
+ }
288
+ }
289
+ }
290
+ return null;
291
+ }
292
+ /**
293
+ * Strip common TypeScript syntax for basic parsing.
294
+ * This is a simple approach - just removes type annotations to allow JS parsing.
295
+ */
296
+ stripTypeScript(content) {
297
+ return (content
298
+ // Remove type imports: import type { X } from 'y'
299
+ .replace(/import\s+type\s+\{[^}]*\}\s+from\s+['"][^'"]+['"];?/g, "")
300
+ // Remove type annotations: : Type
301
+ .replace(/:\s*[A-Z][a-zA-Z0-9<>[\],\s|&]*(?=[\s,;)=\]}])/g, "")
302
+ // Remove interface declarations
303
+ .replace(/interface\s+\w+\s*(\{[^}]*\}|\{[\s\S]*?\n\})/g, "")
304
+ // Remove type declarations
305
+ .replace(/type\s+\w+\s*=\s*[^;]+;/g, "")
306
+ // Remove as Type assertions
307
+ .replace(/\s+as\s+[A-Z][a-zA-Z0-9<>[\],\s|&]*/g, "")
308
+ // Remove generic parameters on function calls
309
+ .replace(/<[A-Z][a-zA-Z0-9<>[\],\s|&]*>(?=\()/g, ""));
310
+ }
311
+ /**
312
+ * Check if file should be skipped during scanning.
313
+ */
314
+ shouldSkipFile(filePath) {
315
+ return this.SKIP_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
316
+ }
317
+ /**
318
+ * Check if file has a scannable extension.
319
+ */
320
+ isScannableFile(filePath) {
321
+ return this.SCANNABLE_EXTENSIONS.some((ext) => filePath.toLowerCase().endsWith(ext));
322
+ }
323
+ /**
324
+ * Compute overall confidence from collected evidence.
325
+ *
326
+ * Confidence rules:
327
+ * - High: 2+ annotations found with explicit 'annotations' objects
328
+ * - Medium: 1 annotation found
329
+ * - Low: No annotations found
330
+ */
331
+ computeConfidence(evidence) {
332
+ if (evidence.length === 0) {
333
+ return "low";
334
+ }
335
+ // Count high-confidence evidence
336
+ const highConfCount = evidence.filter((e) => e.confidence === "high").length;
337
+ if (highConfCount >= 2) {
338
+ return "high";
339
+ }
340
+ if (highConfCount >= 1) {
341
+ return "medium";
342
+ }
343
+ return "low";
344
+ }
345
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"ToolAnnotationAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/ToolAnnotationAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAG9B,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAiBL,KAAK,4BAA4B,EAClC,MAAM,eAAe,CAAC;AAKvB;;;GAGG;AACH,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAExC;;GAEG;AACH,MAAM,WAAW,gCAAiC,SAAQ,wBAAwB;IAChF,WAAW,EAAE,4BAA4B,EAAE,CAAC;IAC5C,cAAc,EAAE,OAAO,CAAC;IACxB,2BAA2B,EAAE,4BAA4B,EAAE,CAAC;CAC7D;AAED,qBAAa,sBAAuB,SAAQ,YAAY;IACtD,OAAO,CAAC,YAAY,CAAC,CAAmB;IACxC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,kBAAkB,CAAC,CAA2B;gBAE1C,MAAM,EAAE,uBAAuB;IAK3C;;OAEG;IACH,qBAAqB,IAAI,wBAAwB,GAAG,SAAS;IAI7D;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAK7C;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAK/C;;OAEG;IACH,eAAe,IAAI,OAAO;IAO1B;;OAEG;IACG,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,wBAAwB,GAAG,gCAAgC,CAAC;CA8SxE"}
1
+ {"version":3,"file":"ToolAnnotationAssessor.d.ts","sourceRoot":"","sources":["../../../../src/services/assessment/modules/ToolAnnotationAssessor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,wBAAwB,EAG9B,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAiBL,KAAK,4BAA4B,EAClC,MAAM,eAAe,CAAC;AAQvB;;;GAGG;AACH,OAAO,EAAE,4BAA4B,EAAE,CAAC;AAExC;;GAEG;AACH,MAAM,WAAW,gCAAiC,SAAQ,wBAAwB;IAChF,WAAW,EAAE,4BAA4B,EAAE,CAAC;IAC5C,cAAc,EAAE,OAAO,CAAC;IACxB,2BAA2B,EAAE,4BAA4B,EAAE,CAAC;CAC7D;AAED,qBAAa,sBAAuB,SAAQ,YAAY;IACtD,OAAO,CAAC,YAAY,CAAC,CAAmB;IACxC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,kBAAkB,CAAC,CAA2B;gBAE1C,MAAM,EAAE,uBAAuB;IAK3C;;OAEG;IACH,qBAAqB,IAAI,wBAAwB,GAAG,SAAS;IAI7D;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI;IAK7C;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAK/C;;OAEG;IACH,eAAe,IAAI,OAAO;IAO1B;;OAEG;IACG,MAAM,CACV,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,wBAAwB,GAAG,gCAAgC,CAAC;CA8TxE"}
@@ -36,6 +36,8 @@ emitAnnotationEvents,
36
36
  enhanceWithClaudeInference, createPatternBasedInference, } from "./annotations/index.js";
37
37
  // Issue #207: Runtime annotation verification
38
38
  import { verifyRuntimeAnnotations } from "../helpers/RuntimeAnnotationVerifier.js";
39
+ // Issue #192: Static source code annotation scanning
40
+ import { StaticAnnotationScanner } from "../helpers/StaticAnnotationScanner.js";
39
41
  export class ToolAnnotationAssessor extends BaseAssessor {
40
42
  claudeBridge;
41
43
  compiledPatterns;
@@ -108,6 +110,15 @@ export class ToolAnnotationAssessor extends BaseAssessor {
108
110
  this.logger.debug(`Tool ${detail.toolName}: annotations found at ${detail.location} (${detail.foundHints.join(", ")})`);
109
111
  }
110
112
  }
113
+ // Issue #192: Scan source code for static annotations
114
+ const staticScanner = new StaticAnnotationScanner();
115
+ const staticScanResult = staticScanner.scan(context.sourceCodeFiles);
116
+ if (staticScanResult.sourceCodeScanned) {
117
+ this.logger.info(`Static annotation scan: ${staticScanResult.annotatedToolCount}/${context.tools.length} tools have annotations in source code (${staticScanResult.scannedFiles.length} files scanned)`);
118
+ if (staticScanResult.parseErrors.length > 0) {
119
+ this.logger.debug(`Static scan parse errors: ${staticScanResult.parseErrors.length} files failed to parse`);
120
+ }
121
+ }
111
122
  // Issue #57: Detect server architecture
112
123
  const architectureContext = {
113
124
  tools: context.tools.map((t) => ({
@@ -138,7 +149,8 @@ export class ToolAnnotationAssessor extends BaseAssessor {
138
149
  for (const tool of context.tools) {
139
150
  this.testCount++;
140
151
  // Use extracted assessSingleTool function
141
- const result = assessSingleTool(tool, this.compiledPatterns, this.persistenceContext);
152
+ // Issue #192: Pass static annotations for fallback when MCP annotations missing
153
+ const result = assessSingleTool(tool, this.compiledPatterns, this.persistenceContext, staticScanResult.annotations);
142
154
  // Enhance with Claude inference if available
143
155
  if (useClaudeInference) {
144
156
  const enhancedResult = await enhanceWithClaudeInference(tool, result, this.claudeBridge, this.logger);
@@ -8,6 +8,7 @@ import type { Tool } from "@modelcontextprotocol/sdk/types.js";
8
8
  import type { ToolAnnotationResult, AssessmentStatus, ToolParamProgress, AnnotationSource } from "../../../../lib/assessmentTypes.js";
9
9
  import type { CompiledPatterns, ServerPersistenceContext } from "../../config/annotationPatterns.js";
10
10
  import { type PoisoningScanResult } from "./DescriptionPoisoningDetector.js";
11
+ import type { StaticAnnotation } from "../../helpers/StaticAnnotationScanner.js";
11
12
  /**
12
13
  * Extracted annotation structure from a tool
13
14
  */
@@ -66,8 +67,13 @@ export declare function extractToolParams(schema: unknown): ToolParamProgress[];
66
67
  export declare function scanInputSchemaDescriptions(tool: Tool): PoisoningScanResult;
67
68
  /**
68
69
  * Assess a single tool's annotations
70
+ *
71
+ * @param tool - The tool to assess
72
+ * @param compiledPatterns - Compiled name patterns for behavior inference
73
+ * @param persistenceContext - Server persistence context
74
+ * @param staticAnnotations - Optional map of static annotations from source code (Issue #192)
69
75
  */
70
- export declare function assessSingleTool(tool: Tool, compiledPatterns: CompiledPatterns, persistenceContext?: ServerPersistenceContext): ToolAnnotationResult;
76
+ export declare function assessSingleTool(tool: Tool, compiledPatterns: CompiledPatterns, persistenceContext?: ServerPersistenceContext, staticAnnotations?: Map<string, StaticAnnotation>): ToolAnnotationResult;
71
77
  /**
72
78
  * Determine overall status based on tool results
73
79
  */
@@ -1 +1 @@
1
- {"version":3,"file":"AlignmentChecker.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/annotations/AlignmentChecker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAC/D,OAAO,KAAK,EACV,oBAAoB,EACpB,gBAAgB,EAEhB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EACV,gBAAgB,EAChB,wBAAwB,EACzB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,gCAAgC,CAAC;AAuFxC;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,kBAAkB,EAAE;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AA0CD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE7D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,oBAAoB,CAkNnE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,IAAI,GACT,oBAAoB,CAAC,kBAAkB,CAAC,CA6D1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,iBAAiB,EAAE,CAqBtE;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,IAAI,GAAG,mBAAmB,CAmD3E;AAqCD;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,gBAAgB,EAAE,gBAAgB,EAClC,kBAAkB,CAAC,EAAE,wBAAwB,GAC5C,oBAAoB,CA0JtB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,oBAAoB,EAAE,EAC/B,UAAU,EAAE,MAAM,GACjB,gBAAgB,CAoClB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,oBAAoB,EAAE,EAC/B,UAAU,EAAE,MAAM,GACjB,sBAAsB,CA2BxB"}
1
+ {"version":3,"file":"AlignmentChecker.d.ts","sourceRoot":"","sources":["../../../../../src/services/assessment/modules/annotations/AlignmentChecker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAC/D,OAAO,KAAK,EACV,oBAAoB,EACpB,gBAAgB,EAEhB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EACV,gBAAgB,EAChB,wBAAwB,EACzB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAuF9E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,kBAAkB,EAAE;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AA0CD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE7D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,oBAAoB,CAkNnE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,IAAI,GACT,oBAAoB,CAAC,kBAAkB,CAAC,CA6D1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,iBAAiB,EAAE,CAqBtE;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,IAAI,GAAG,mBAAmB,CAmD3E;AAqCD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,gBAAgB,EAAE,gBAAgB,EAClC,kBAAkB,CAAC,EAAE,wBAAwB,EAC7C,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAChD,oBAAoB,CAgLtB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,oBAAoB,EAAE,EAC/B,UAAU,EAAE,MAAM,GACjB,gBAAgB,CAoClB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,oBAAoB,EAAE,EAC/B,UAAU,EAAE,MAAM,GACjB,sBAAsB,CA2BxB"}
@@ -324,13 +324,37 @@ function mergePoisoningScanResults(primary, secondary) {
324
324
  }
325
325
  /**
326
326
  * Assess a single tool's annotations
327
+ *
328
+ * @param tool - The tool to assess
329
+ * @param compiledPatterns - Compiled name patterns for behavior inference
330
+ * @param persistenceContext - Server persistence context
331
+ * @param staticAnnotations - Optional map of static annotations from source code (Issue #192)
327
332
  */
328
- export function assessSingleTool(tool, compiledPatterns, persistenceContext) {
333
+ export function assessSingleTool(tool, compiledPatterns, persistenceContext, staticAnnotations) {
329
334
  const issues = [];
330
335
  const recommendations = [];
331
- const annotations = extractAnnotations(tool);
332
- const hasAnnotations = annotations.readOnlyHint !== undefined ||
336
+ // First try runtime annotation extraction
337
+ let annotations = extractAnnotations(tool);
338
+ let hasAnnotations = annotations.readOnlyHint !== undefined ||
333
339
  annotations.destructiveHint !== undefined;
340
+ // Issue #192: Fall back to static source code annotations if no runtime annotations
341
+ if (!hasAnnotations && staticAnnotations?.has(tool.name)) {
342
+ const staticAnn = staticAnnotations.get(tool.name);
343
+ const hasStaticAnnotations = staticAnn.readOnlyHint !== undefined ||
344
+ staticAnn.destructiveHint !== undefined;
345
+ if (hasStaticAnnotations) {
346
+ annotations = {
347
+ readOnlyHint: staticAnn.readOnlyHint,
348
+ destructiveHint: staticAnn.destructiveHint,
349
+ idempotentHint: staticAnn.idempotentHint,
350
+ openWorldHint: staticAnn.openWorldHint,
351
+ title: annotations.title,
352
+ description: annotations.description,
353
+ source: "source-code",
354
+ };
355
+ hasAnnotations = true;
356
+ }
357
+ }
334
358
  const inferredBehavior = inferBehavior(tool.name, tool.description, compiledPatterns, persistenceContext);
335
359
  let alignmentStatus = "ALIGNED";
336
360
  if (!hasAnnotations) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bryan-thompson/inspector-assessment-client",
3
- "version": "1.43.0",
3
+ "version": "1.43.1",
4
4
  "description": "Client-side application for the Enhanced MCP Inspector with assessment capabilities",
5
5
  "license": "MIT",
6
6
  "author": "Bryan Thompson <bryan@triepod.ai>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bryan-thompson/inspector-assessment",
3
- "version": "1.43.0",
3
+ "version": "1.43.1",
4
4
  "description": "Enhanced MCP Inspector with comprehensive assessment capabilities for server validation",
5
5
  "license": "MIT",
6
6
  "author": "Bryan Thompson <bryan@triepod.ai>",
@@ -132,6 +132,8 @@
132
132
  "dependencies": {
133
133
  "@modelcontextprotocol/conformance": "^0.1.9",
134
134
  "@modelcontextprotocol/sdk": "^1.25.2",
135
+ "acorn": "^8.14.0",
136
+ "acorn-walk": "^8.3.4",
135
137
  "commander": "^14.0.2",
136
138
  "node-fetch": "^3.3.2",
137
139
  "open": "^10.2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bryan-thompson/inspector-assessment-server",
3
- "version": "1.43.0",
3
+ "version": "1.43.1",
4
4
  "description": "Server-side application for the Enhanced MCP Inspector with assessment capabilities",
5
5
  "license": "MIT",
6
6
  "author": "Bryan Thompson <bryan@triepod.ai>",