@bryan-thompson/inspector-assessment-cli 1.36.1 → 1.36.2
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.
|
@@ -142,6 +142,71 @@ describe("JSONL Event Emission", () => {
|
|
|
142
142
|
emitToolDiscovered(tool);
|
|
143
143
|
expect(emittedEvents[0].annotations).toBeNull();
|
|
144
144
|
});
|
|
145
|
+
// Issue #155: Test that direct properties are detected
|
|
146
|
+
it("should detect annotations from direct properties (Issue #155)", () => {
|
|
147
|
+
// Simulate a tool with readOnlyHint as a direct property (not in annotations object)
|
|
148
|
+
// This is how some MCP SDKs serialize annotations
|
|
149
|
+
const tool = {
|
|
150
|
+
name: "browse_subreddit",
|
|
151
|
+
description: "Browse a subreddit",
|
|
152
|
+
inputSchema: { type: "object" },
|
|
153
|
+
readOnlyHint: true, // Direct property, not in annotations object
|
|
154
|
+
};
|
|
155
|
+
emitToolDiscovered(tool);
|
|
156
|
+
const annotations = emittedEvents[0].annotations;
|
|
157
|
+
expect(annotations).not.toBeNull();
|
|
158
|
+
expect(annotations?.readOnlyHint).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
it("should detect annotations from metadata object (Issue #155)", () => {
|
|
161
|
+
// Simulate a tool with annotations in metadata object
|
|
162
|
+
const tool = {
|
|
163
|
+
name: "get_data",
|
|
164
|
+
description: "Get data",
|
|
165
|
+
inputSchema: { type: "object" },
|
|
166
|
+
metadata: {
|
|
167
|
+
readOnlyHint: true,
|
|
168
|
+
destructiveHint: false,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
emitToolDiscovered(tool);
|
|
172
|
+
const annotations = emittedEvents[0].annotations;
|
|
173
|
+
expect(annotations).not.toBeNull();
|
|
174
|
+
expect(annotations?.readOnlyHint).toBe(true);
|
|
175
|
+
expect(annotations?.destructiveHint).toBe(false);
|
|
176
|
+
});
|
|
177
|
+
it("should detect annotations from _meta object (Issue #155)", () => {
|
|
178
|
+
// Simulate a tool with annotations in _meta object
|
|
179
|
+
const tool = {
|
|
180
|
+
name: "search_docs",
|
|
181
|
+
description: "Search documents",
|
|
182
|
+
inputSchema: { type: "object" },
|
|
183
|
+
_meta: {
|
|
184
|
+
idempotentHint: true,
|
|
185
|
+
openWorldHint: false,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
emitToolDiscovered(tool);
|
|
189
|
+
const annotations = emittedEvents[0].annotations;
|
|
190
|
+
expect(annotations).not.toBeNull();
|
|
191
|
+
expect(annotations?.idempotentHint).toBe(true);
|
|
192
|
+
expect(annotations?.openWorldHint).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
it("should prioritize tool.annotations over direct properties (Issue #155)", () => {
|
|
195
|
+
// When both exist, tool.annotations should take precedence
|
|
196
|
+
const tool = {
|
|
197
|
+
name: "conflicting_tool",
|
|
198
|
+
description: "Test priority",
|
|
199
|
+
inputSchema: { type: "object" },
|
|
200
|
+
annotations: {
|
|
201
|
+
readOnlyHint: false, // In annotations object
|
|
202
|
+
},
|
|
203
|
+
readOnlyHint: true, // Direct property (should be ignored)
|
|
204
|
+
};
|
|
205
|
+
emitToolDiscovered(tool);
|
|
206
|
+
const annotations = emittedEvents[0].annotations;
|
|
207
|
+
expect(annotations).not.toBeNull();
|
|
208
|
+
expect(annotations?.readOnlyHint).toBe(false); // Should use annotations object value
|
|
209
|
+
});
|
|
145
210
|
});
|
|
146
211
|
describe("emitToolsDiscoveryComplete", () => {
|
|
147
212
|
it("should emit tools_discovery_complete with count", () => {
|
|
@@ -60,16 +60,99 @@ export function extractToolParams(schema) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Emit tool_discovered event for each tool found.
|
|
62
62
|
* Includes annotations if the server provides them.
|
|
63
|
+
*
|
|
64
|
+
* Issue #155: Check multiple locations for annotations:
|
|
65
|
+
* 1. tool.annotations object (MCP 2024-11 spec)
|
|
66
|
+
* 2. Direct properties (tool.readOnlyHint, etc.)
|
|
67
|
+
* 3. tool.metadata object
|
|
68
|
+
* 4. tool._meta object
|
|
63
69
|
*/
|
|
64
70
|
export function emitToolDiscovered(tool) {
|
|
65
71
|
const params = extractToolParams(tool.inputSchema);
|
|
66
|
-
// Extract annotations
|
|
67
|
-
|
|
72
|
+
// Issue #155: Extract annotations from multiple sources (priority order)
|
|
73
|
+
// NOTE: This is a simplified version of AlignmentChecker.extractAnnotations()
|
|
74
|
+
// that only checks *Hint-suffixed properties (readOnlyHint, destructiveHint, etc.),
|
|
75
|
+
// not non-suffixed variants like readOnly, destructive, idempotent, openWorld.
|
|
76
|
+
// See AlignmentChecker.resolveAnnotationValue() for full implementation with fallbacks.
|
|
77
|
+
const toolAny = tool;
|
|
78
|
+
// Priority 1: Check tool.annotations object (MCP spec)
|
|
79
|
+
let readOnlyHint;
|
|
80
|
+
let destructiveHint;
|
|
81
|
+
let idempotentHint;
|
|
82
|
+
let openWorldHint;
|
|
83
|
+
if (tool.annotations) {
|
|
84
|
+
readOnlyHint = tool.annotations.readOnlyHint;
|
|
85
|
+
destructiveHint = tool.annotations.destructiveHint;
|
|
86
|
+
idempotentHint = tool.annotations.idempotentHint;
|
|
87
|
+
openWorldHint = tool.annotations.openWorldHint;
|
|
88
|
+
}
|
|
89
|
+
// Priority 2: Check direct properties on tool object
|
|
90
|
+
// Only use if not already found in annotations
|
|
91
|
+
if (readOnlyHint === undefined && typeof toolAny.readOnlyHint === "boolean") {
|
|
92
|
+
readOnlyHint = toolAny.readOnlyHint;
|
|
93
|
+
}
|
|
94
|
+
if (destructiveHint === undefined &&
|
|
95
|
+
typeof toolAny.destructiveHint === "boolean") {
|
|
96
|
+
destructiveHint = toolAny.destructiveHint;
|
|
97
|
+
}
|
|
98
|
+
if (idempotentHint === undefined &&
|
|
99
|
+
typeof toolAny.idempotentHint === "boolean") {
|
|
100
|
+
idempotentHint = toolAny.idempotentHint;
|
|
101
|
+
}
|
|
102
|
+
if (openWorldHint === undefined &&
|
|
103
|
+
typeof toolAny.openWorldHint === "boolean") {
|
|
104
|
+
openWorldHint = toolAny.openWorldHint;
|
|
105
|
+
}
|
|
106
|
+
// Priority 3: Check tool.metadata object
|
|
107
|
+
const metadata = toolAny.metadata;
|
|
108
|
+
if (metadata) {
|
|
109
|
+
if (readOnlyHint === undefined &&
|
|
110
|
+
typeof metadata.readOnlyHint === "boolean") {
|
|
111
|
+
readOnlyHint = metadata.readOnlyHint;
|
|
112
|
+
}
|
|
113
|
+
if (destructiveHint === undefined &&
|
|
114
|
+
typeof metadata.destructiveHint === "boolean") {
|
|
115
|
+
destructiveHint = metadata.destructiveHint;
|
|
116
|
+
}
|
|
117
|
+
if (idempotentHint === undefined &&
|
|
118
|
+
typeof metadata.idempotentHint === "boolean") {
|
|
119
|
+
idempotentHint = metadata.idempotentHint;
|
|
120
|
+
}
|
|
121
|
+
if (openWorldHint === undefined &&
|
|
122
|
+
typeof metadata.openWorldHint === "boolean") {
|
|
123
|
+
openWorldHint = metadata.openWorldHint;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Priority 4: Check tool._meta object
|
|
127
|
+
const _meta = toolAny._meta;
|
|
128
|
+
if (_meta) {
|
|
129
|
+
if (readOnlyHint === undefined && typeof _meta.readOnlyHint === "boolean") {
|
|
130
|
+
readOnlyHint = _meta.readOnlyHint;
|
|
131
|
+
}
|
|
132
|
+
if (destructiveHint === undefined &&
|
|
133
|
+
typeof _meta.destructiveHint === "boolean") {
|
|
134
|
+
destructiveHint = _meta.destructiveHint;
|
|
135
|
+
}
|
|
136
|
+
if (idempotentHint === undefined &&
|
|
137
|
+
typeof _meta.idempotentHint === "boolean") {
|
|
138
|
+
idempotentHint = _meta.idempotentHint;
|
|
139
|
+
}
|
|
140
|
+
if (openWorldHint === undefined &&
|
|
141
|
+
typeof _meta.openWorldHint === "boolean") {
|
|
142
|
+
openWorldHint = _meta.openWorldHint;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Build annotations object if any hints were found
|
|
146
|
+
const hasAnnotations = readOnlyHint !== undefined ||
|
|
147
|
+
destructiveHint !== undefined ||
|
|
148
|
+
idempotentHint !== undefined ||
|
|
149
|
+
openWorldHint !== undefined;
|
|
150
|
+
const annotations = hasAnnotations
|
|
68
151
|
? {
|
|
69
|
-
readOnlyHint
|
|
70
|
-
destructiveHint
|
|
71
|
-
idempotentHint
|
|
72
|
-
openWorldHint
|
|
152
|
+
readOnlyHint,
|
|
153
|
+
destructiveHint,
|
|
154
|
+
idempotentHint,
|
|
155
|
+
openWorldHint,
|
|
73
156
|
}
|
|
74
157
|
: null;
|
|
75
158
|
emitJSONL({
|
package/package.json
CHANGED