@modelcontextprotocol/inspector 1.0.3 → 1.0.4
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/build/index.js +7 -1
- package/cli/build/utils/regexFreeSchemaValidator.js +55 -0
- package/cli/build/utils/stripSchemaPatterns.js +467 -0
- package/client/dist/assets/{OAuthCallback-jbB02Bt3.js → OAuthCallback-Cb6VJMaq.js} +1 -1
- package/client/dist/assets/{OAuthDebugCallback-VeJ8bP3s.js → OAuthDebugCallback-CgAV2c_T.js} +1 -1
- package/client/dist/assets/{index-C2CDzlVy.js → index-Bvgm3HOZ.js} +390 -30
- package/client/dist/assets/{index-DXy0twoH.css → index-D-iperlR.css} +3 -0
- package/client/dist/index.html +2 -2
- package/package.json +4 -4
package/cli/build/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { callTool, connect, disconnect, getPrompt, listPrompts, listResources, l
|
|
|
6
6
|
import { handleError } from "./error-handler.js";
|
|
7
7
|
import { createTransport } from "./transport.js";
|
|
8
8
|
import { awaitableLog } from "./utils/awaitable-log.js";
|
|
9
|
+
import { RegexFreeJsonSchemaValidator } from "./utils/regexFreeSchemaValidator.js";
|
|
9
10
|
function createTransportOptions(target, transport, headers) {
|
|
10
11
|
if (target.length === 0) {
|
|
11
12
|
throw new Error("Target is required. Specify a URL or a command to execute.");
|
|
@@ -65,7 +66,12 @@ async function callMethod(args) {
|
|
|
65
66
|
const [, name = packageJson.name] = packageJson.name.split("/");
|
|
66
67
|
const version = packageJson.version;
|
|
67
68
|
const clientIdentity = { name, version };
|
|
68
|
-
|
|
69
|
+
// Tool output schemas come from the server; the SDK validates results
|
|
70
|
+
// against them, so give it a provider that never runs their regexes on the
|
|
71
|
+
// native backtracking RegExp engine (skipped patterns are noted on stderr).
|
|
72
|
+
const client = new Client(clientIdentity, {
|
|
73
|
+
jsonSchemaValidator: new RegexFreeJsonSchemaValidator((note) => console.error(`Warning: ${note}`)),
|
|
74
|
+
});
|
|
69
75
|
try {
|
|
70
76
|
await connect(client, transport);
|
|
71
77
|
let result;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JSON Schema validator provider the CLI hands the SDK `Client`, which
|
|
3
|
+
* uses it to validate every `tools/call` result's `structuredContent` against
|
|
4
|
+
* the tool's server-supplied `outputSchema`.
|
|
5
|
+
*
|
|
6
|
+
* It wraps the SDK's own default provider (`AjvJsonSchemaValidator`) and
|
|
7
|
+
* changes one thing: the schema is passed through {@link stripSchemaPatterns}
|
|
8
|
+
* first, so no server-supplied `pattern` / `patternProperties` regex is ever
|
|
9
|
+
* run on the native backtracking `RegExp` engine, where a pattern such as
|
|
10
|
+
* `^(a+)+$` and a near-match value would hang the CLI. Every other keyword is
|
|
11
|
+
* still validated by the SDK's validator, unchanged.
|
|
12
|
+
*
|
|
13
|
+
* A schema that declares an identifier is not validated at all (see
|
|
14
|
+
* stripSchemaPatterns); every result passes and the note says so.
|
|
15
|
+
*
|
|
16
|
+
* Skipped regexes are not silent: a failing result's message ends with the
|
|
17
|
+
* "not evaluated" note, and a passing one reports it through `onNote` (the
|
|
18
|
+
* CLI writes it to stderr, keeping stdout's JSON clean).
|
|
19
|
+
*/
|
|
20
|
+
import { AjvJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/ajv";
|
|
21
|
+
import { schemaNote, stripSchemaPatterns } from "./stripSchemaPatterns.js";
|
|
22
|
+
export class RegexFreeJsonSchemaValidator {
|
|
23
|
+
inner;
|
|
24
|
+
onNote;
|
|
25
|
+
constructor(onNote, inner = new AjvJsonSchemaValidator()) {
|
|
26
|
+
this.onNote = onNote;
|
|
27
|
+
this.inner = inner;
|
|
28
|
+
}
|
|
29
|
+
getValidator(schema) {
|
|
30
|
+
const result = stripSchemaPatterns(schema);
|
|
31
|
+
const note = schemaNote(result);
|
|
32
|
+
if (!result.validate) {
|
|
33
|
+
// Not validated at all (the schema declares an identifier): every
|
|
34
|
+
// result passes, and the note says the schema was not checked.
|
|
35
|
+
return (input) => {
|
|
36
|
+
this.onNote(note);
|
|
37
|
+
// Unchecked by design; `T` is what the caller asked the schema for.
|
|
38
|
+
return { valid: true, data: input, errorMessage: undefined };
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// `stripSchemaPatterns` returns a structural copy of a JsonSchemaType
|
|
42
|
+
// with keywords removed, which is still a JsonSchemaType.
|
|
43
|
+
const validate = this.inner.getValidator(result.schema);
|
|
44
|
+
if (note === undefined)
|
|
45
|
+
return validate;
|
|
46
|
+
return (input) => {
|
|
47
|
+
const checked = validate(input);
|
|
48
|
+
if (checked.valid) {
|
|
49
|
+
this.onNote(note);
|
|
50
|
+
return checked;
|
|
51
|
+
}
|
|
52
|
+
return { ...checked, errorMessage: `${checked.errorMessage} (${note})` };
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepare a server-supplied JSON Schema for validation without evaluating any
|
|
3
|
+
* of its regular expressions.
|
|
4
|
+
*
|
|
5
|
+
* `pattern` and `patternProperties` regexes come from the server, and Ajv
|
|
6
|
+
* runs them on the native backtracking `RegExp` engine: a pattern such as
|
|
7
|
+
* `^(a+)+$` checked against a near-match string takes exponential time and
|
|
8
|
+
* freezes the thread doing the validation. Rather than evaluate them, this
|
|
9
|
+
* returns a COPY of the schema with every regex removed, plus counts, so the
|
|
10
|
+
* caller can say visibly what was not checked. The original schema object is
|
|
11
|
+
* never mutated (the UI displays it).
|
|
12
|
+
*
|
|
13
|
+
* INVARIANT: the output schema accepts every value the input schema accepts
|
|
14
|
+
* — it may be less strict, and says so, but never stricter — or validation
|
|
15
|
+
* is skipped altogether (`validate: false`) and the note says so. Every rule
|
|
16
|
+
* below exists to keep that; when one cannot be kept simply, give up rather
|
|
17
|
+
* than add cleverness.
|
|
18
|
+
*
|
|
19
|
+
* 0. A schema that declares an identifier anywhere in a schema position
|
|
20
|
+
* (`$id`, draft-04 `id`, `$anchor`, `$dynamicAnchor`, `$recursiveAnchor`)
|
|
21
|
+
* is not validated at all. Identifiers change how references resolve, and
|
|
22
|
+
* moving a subschema (rule 2) would move its identifier scope with it.
|
|
23
|
+
*
|
|
24
|
+
* 1. Only schema positions are rewritten. The walk follows the keywords that
|
|
25
|
+
* hold subschemas, so a property NAMED `pattern`, or a `pattern` key inside
|
|
26
|
+
* a `const` / `enum` / `default` value, is data and is left alone.
|
|
27
|
+
*
|
|
28
|
+
* 2. Every `$ref` is re-pointed at a sanitized copy, or removed. Ajv resolves
|
|
29
|
+
* a JSON pointer to ANY location in the document (`#/default`, say), and
|
|
30
|
+
* compiles whatever it finds there as a schema — so a reference to a place
|
|
31
|
+
* the walk did not visit would hand Ajv an unsanitized regex. A `$ref` is
|
|
32
|
+
* kept only when it is a local `#/...` pointer to a schema position the
|
|
33
|
+
* walk visited; it is then rewritten to that position's place in the
|
|
34
|
+
* output. Any other `$ref` takes its whole node with it: the node becomes
|
|
35
|
+
* `{}`, siblings included, since Ajv 6 ignores a `$ref`'s siblings and
|
|
36
|
+
* activating them could reject what the reference accepted (under Ajv 8
|
|
37
|
+
* dropping them only loosens). Its subschemas are kept resolvable as in
|
|
38
|
+
* rule 2. A `$dynamicRef` / `$recursiveRef` is removed on its own (Ajv 6
|
|
39
|
+
* does not know it, so its siblings were already active). Each counts as
|
|
40
|
+
* skipped.
|
|
41
|
+
*
|
|
42
|
+
* 3. Removing a constraint loosens the schema that holds it, and loosening is
|
|
43
|
+
* only safe in a positive position. Under `not`, inside a `oneOf` branch
|
|
44
|
+
* (where a looser branch can make two branches match), in an `if`
|
|
45
|
+
* condition (which decides whether `then` or `else` applies), in
|
|
46
|
+
* `contains` (bounded by `maxContains`) and beneath `unevaluated*` (whose
|
|
47
|
+
* verdict depends on what sibling keywords evaluated), a looser subschema
|
|
48
|
+
* can turn a pass into a failure. So a subschema that lost anything —
|
|
49
|
+
* directly, or through a child or a `$ref` target — is "tainted", and a
|
|
50
|
+
* keyword of that kind whose subschema is tainted is removed whole and
|
|
51
|
+
* counted as skipped. Independent sibling constraints are kept. `allOf`,
|
|
52
|
+
* `anyOf`, `properties`, `items`, `then` / `else` and the rest only ever
|
|
53
|
+
* loosen, so they stay.
|
|
54
|
+
*
|
|
55
|
+
* `patternProperties` with at least one key is removed, and so is the
|
|
56
|
+
* `additionalProperties` beside it: without the regexes, which keys are
|
|
57
|
+
* "additional" is unknowable, and `additionalProperties: false` would reject
|
|
58
|
+
* keys the server declared as allowed. An EMPTY `patternProperties` holds no
|
|
59
|
+
* regex, so it and its `additionalProperties` are kept as they are.
|
|
60
|
+
*
|
|
61
|
+
* Nothing that a `$ref` could target is deleted outright: a removed subschema
|
|
62
|
+
* is moved, sanitized, into the root's `definitions` under a generated name
|
|
63
|
+
* (where nothing applies it), and references into it are rewritten there. So
|
|
64
|
+
* the transformed schema always still compiles.
|
|
65
|
+
*/
|
|
66
|
+
/** Keywords whose value is a single subschema. */
|
|
67
|
+
const SCHEMA_KEYWORDS = [
|
|
68
|
+
"additionalProperties",
|
|
69
|
+
"additionalItems",
|
|
70
|
+
"contains",
|
|
71
|
+
"propertyNames",
|
|
72
|
+
"if",
|
|
73
|
+
"then",
|
|
74
|
+
"else",
|
|
75
|
+
"not",
|
|
76
|
+
"unevaluatedItems",
|
|
77
|
+
"unevaluatedProperties",
|
|
78
|
+
];
|
|
79
|
+
/** Keywords whose value is an array of subschemas. */
|
|
80
|
+
const SCHEMA_ARRAY_KEYWORDS = ["allOf", "anyOf", "oneOf", "prefixItems"];
|
|
81
|
+
/** Keywords whose value maps names to subschemas. */
|
|
82
|
+
const SCHEMA_MAP_KEYWORDS = [
|
|
83
|
+
"properties",
|
|
84
|
+
"patternProperties",
|
|
85
|
+
"$defs",
|
|
86
|
+
"definitions",
|
|
87
|
+
"dependentSchemas",
|
|
88
|
+
"dependencies",
|
|
89
|
+
];
|
|
90
|
+
/** Prefix of the `definitions` entries removed subschemas are moved to. */
|
|
91
|
+
const HOLDING_PREFIX = "__inspector_unevaluated_";
|
|
92
|
+
/** Keywords that declare an identifier; any one means "do not validate". */
|
|
93
|
+
const IDENTIFIER_KEYWORDS = [
|
|
94
|
+
"$id",
|
|
95
|
+
"id",
|
|
96
|
+
"$anchor",
|
|
97
|
+
"$dynamicAnchor",
|
|
98
|
+
"$recursiveAnchor",
|
|
99
|
+
];
|
|
100
|
+
function isObject(value) {
|
|
101
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
102
|
+
}
|
|
103
|
+
function isSchema(value) {
|
|
104
|
+
return isObject(value) || typeof value === "boolean";
|
|
105
|
+
}
|
|
106
|
+
/** A map key for a `Rel`. Keywords never contain NUL, so this is unique. */
|
|
107
|
+
const relKey = (rel) => rel.length === 1 ? rel[0] : `${rel[0]}\u0000${rel[1]}`;
|
|
108
|
+
/** Every subschema of `node`, as relative locations. */
|
|
109
|
+
function childRels(node) {
|
|
110
|
+
const rels = [];
|
|
111
|
+
for (const key of SCHEMA_KEYWORDS) {
|
|
112
|
+
if (isSchema(node[key]))
|
|
113
|
+
rels.push([key]);
|
|
114
|
+
}
|
|
115
|
+
for (const key of SCHEMA_ARRAY_KEYWORDS) {
|
|
116
|
+
const value = node[key];
|
|
117
|
+
if (Array.isArray(value)) {
|
|
118
|
+
value.forEach((sub, i) => {
|
|
119
|
+
if (isSchema(sub))
|
|
120
|
+
rels.push([key, String(i)]);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (const key of SCHEMA_MAP_KEYWORDS) {
|
|
125
|
+
const value = node[key];
|
|
126
|
+
if (isObject(value)) {
|
|
127
|
+
for (const [name, sub] of Object.entries(value)) {
|
|
128
|
+
// `dependencies` may map to a string array (property dependency).
|
|
129
|
+
if (isSchema(sub))
|
|
130
|
+
rels.push([key, name]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// `items` is either one subschema or (draft-07 tuple form) an array.
|
|
135
|
+
if (Array.isArray(node.items)) {
|
|
136
|
+
node.items.forEach((sub, i) => {
|
|
137
|
+
if (isSchema(sub))
|
|
138
|
+
rels.push(["items", String(i)]);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
else if (isSchema(node.items)) {
|
|
142
|
+
rels.push(["items"]);
|
|
143
|
+
}
|
|
144
|
+
return rels;
|
|
145
|
+
}
|
|
146
|
+
function getAt(node, rel) {
|
|
147
|
+
const value = node[rel[0]];
|
|
148
|
+
return rel.length === 1 ? value : value[rel[1]];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Parse a `$ref` the way Ajv resolves a local JSON pointer: split on `/`,
|
|
152
|
+
* then URI-decode and JSON-pointer-unescape each segment. `null` for any
|
|
153
|
+
* reference that is not a local `#` / `#/...` pointer.
|
|
154
|
+
*/
|
|
155
|
+
function parseLocalRef(ref) {
|
|
156
|
+
if (ref === "#")
|
|
157
|
+
return [];
|
|
158
|
+
if (!ref.startsWith("#/"))
|
|
159
|
+
return null;
|
|
160
|
+
// An encoded `/` is ambiguous: Ajv 6 splits before decoding (one segment,
|
|
161
|
+
// `x/y`), while Ajv 8 decodes first (two segments, `x` then `y`), so the
|
|
162
|
+
// two can resolve different schemas. Give it up rather than guess. A NUL
|
|
163
|
+
// could collide with the internal child keys, so it is given up too.
|
|
164
|
+
if (/%2f|%00/i.test(ref) || ref.includes("\u0000"))
|
|
165
|
+
return null;
|
|
166
|
+
try {
|
|
167
|
+
return ref
|
|
168
|
+
.slice(2)
|
|
169
|
+
.split("/")
|
|
170
|
+
.map((part) => decodeURIComponent(part).replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return null; // malformed percent-encoding
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const formatSegment = (part) => "/" + encodeURIComponent(part.replace(/~/g, "~0").replace(/\//g, "~1"));
|
|
177
|
+
/** References whose target depends on the dynamic scope; always removed. */
|
|
178
|
+
const DYNAMIC_REF_KEYWORDS = ["$dynamicRef", "$recursiveRef"];
|
|
179
|
+
/**
|
|
180
|
+
* Every pass below is linear in the size of the schema: each position is
|
|
181
|
+
* visited once, each container is copied once, taint spreads along reversed
|
|
182
|
+
* edges with a work list, and positions are numbers rather than paths (a path
|
|
183
|
+
* per position would cost O(depth) each). Only the pointers actually written
|
|
184
|
+
* into rewritten `$ref`s are built as strings, memoized per position.
|
|
185
|
+
*/
|
|
186
|
+
export function stripSchemaPatterns(schema) {
|
|
187
|
+
// Pass 1: number every schema position, and record what each depends on.
|
|
188
|
+
const positions = [];
|
|
189
|
+
const rawRefs = [];
|
|
190
|
+
let hasIdentifier = false;
|
|
191
|
+
const analyze = (node, parent) => {
|
|
192
|
+
const id = positions.length;
|
|
193
|
+
const position = {
|
|
194
|
+
node,
|
|
195
|
+
parent,
|
|
196
|
+
children: new Map(),
|
|
197
|
+
direct: false,
|
|
198
|
+
dynamic: false,
|
|
199
|
+
};
|
|
200
|
+
positions.push(position);
|
|
201
|
+
if (!isObject(node))
|
|
202
|
+
return id; // `true` / `false` schemas, or junk
|
|
203
|
+
if (IDENTIFIER_KEYWORDS.some((key) => key in node))
|
|
204
|
+
hasIdentifier = true;
|
|
205
|
+
if (typeof node.pattern === "string")
|
|
206
|
+
position.direct = true;
|
|
207
|
+
if (isObject(node.patternProperties) &&
|
|
208
|
+
Object.keys(node.patternProperties).length > 0) {
|
|
209
|
+
position.direct = true;
|
|
210
|
+
}
|
|
211
|
+
if (typeof node.$ref === "string")
|
|
212
|
+
rawRefs.push([id, node.$ref]);
|
|
213
|
+
if (DYNAMIC_REF_KEYWORDS.some((key) => key in node)) {
|
|
214
|
+
position.dynamic = true;
|
|
215
|
+
}
|
|
216
|
+
for (const rel of childRels(node)) {
|
|
217
|
+
position.children.set(relKey(rel), analyze(getAt(node, rel), id));
|
|
218
|
+
}
|
|
219
|
+
return id;
|
|
220
|
+
};
|
|
221
|
+
analyze(schema, -1);
|
|
222
|
+
/** The position a pointer names, if it names one. */
|
|
223
|
+
const resolve = (segments) => {
|
|
224
|
+
let current = 0;
|
|
225
|
+
for (let i = 0; i < segments.length;) {
|
|
226
|
+
const { children } = positions[current];
|
|
227
|
+
const single = children.get(segments[i]);
|
|
228
|
+
if (single !== undefined) {
|
|
229
|
+
current = single;
|
|
230
|
+
i += 1;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (i + 1 >= segments.length)
|
|
234
|
+
return null;
|
|
235
|
+
const pair = children.get(`${segments[i]}\u0000${segments[i + 1]}`);
|
|
236
|
+
if (pair === undefined)
|
|
237
|
+
return null;
|
|
238
|
+
current = pair;
|
|
239
|
+
i += 2;
|
|
240
|
+
}
|
|
241
|
+
return current;
|
|
242
|
+
};
|
|
243
|
+
const referrers = new Map();
|
|
244
|
+
for (const [id, ref] of rawRefs) {
|
|
245
|
+
const segments = parseLocalRef(ref);
|
|
246
|
+
const target = segments === null ? null : resolve(segments);
|
|
247
|
+
positions[id].ref = target;
|
|
248
|
+
if (target !== null) {
|
|
249
|
+
const list = referrers.get(target) ?? [];
|
|
250
|
+
list.push(id);
|
|
251
|
+
referrers.set(target, list);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Pass 2: a position is tainted if it, a subschema, or a `$ref` target
|
|
255
|
+
// lost anything. Spread from the seeds along reversed edges (to the parent
|
|
256
|
+
// and to every referrer); each position enters the work list at most once.
|
|
257
|
+
const tainted = new Uint8Array(positions.length);
|
|
258
|
+
const work = [];
|
|
259
|
+
const taint = (id) => {
|
|
260
|
+
if (id < 0 || tainted[id])
|
|
261
|
+
return;
|
|
262
|
+
tainted[id] = 1;
|
|
263
|
+
work.push(id);
|
|
264
|
+
};
|
|
265
|
+
positions.forEach((position, id) => {
|
|
266
|
+
if (position.direct || position.dynamic || position.ref === null) {
|
|
267
|
+
taint(id);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
while (work.length > 0) {
|
|
271
|
+
const id = work.pop();
|
|
272
|
+
taint(positions[id].parent);
|
|
273
|
+
for (const referrer of referrers.get(id) ?? [])
|
|
274
|
+
taint(referrer);
|
|
275
|
+
}
|
|
276
|
+
// Pass 3: emit the sanitized copy, recording where each position landed
|
|
277
|
+
// (its output parent and location) so references can be rewritten to it.
|
|
278
|
+
let stripped = 0;
|
|
279
|
+
let skipped = 0;
|
|
280
|
+
const outParent = new Int32Array(positions.length).fill(-2); // -2: unset
|
|
281
|
+
const outRel = new Array(positions.length);
|
|
282
|
+
const refSites = [];
|
|
283
|
+
const holding = [];
|
|
284
|
+
const root = isObject(schema) ? schema : undefined;
|
|
285
|
+
const takenNames = new Set(isObject(root?.definitions) ? Object.keys(root.definitions) : []);
|
|
286
|
+
let nextName = 0;
|
|
287
|
+
const emit = (id) => {
|
|
288
|
+
const { node, children } = positions[id];
|
|
289
|
+
if (!isObject(node))
|
|
290
|
+
return node;
|
|
291
|
+
const childOf = (rel) => children.get(relKey(rel));
|
|
292
|
+
/** Move a subschema somewhere nothing applies it, keeping it resolvable. */
|
|
293
|
+
const relocate = (rel) => holding.push(childOf(rel));
|
|
294
|
+
if (positions[id].ref === null) {
|
|
295
|
+
// A `$ref` that cannot be kept takes its whole node (see rule 2).
|
|
296
|
+
childRels(node).forEach(relocate);
|
|
297
|
+
skipped++;
|
|
298
|
+
return {};
|
|
299
|
+
}
|
|
300
|
+
const copy = { ...node };
|
|
301
|
+
const isTainted = (rel) => tainted[childOf(rel)] === 1;
|
|
302
|
+
if (typeof copy.pattern === "string") {
|
|
303
|
+
delete copy.pattern;
|
|
304
|
+
stripped++;
|
|
305
|
+
}
|
|
306
|
+
if (isObject(copy.patternProperties) &&
|
|
307
|
+
Object.keys(copy.patternProperties).length > 0) {
|
|
308
|
+
for (const name of Object.keys(copy.patternProperties)) {
|
|
309
|
+
if (isSchema(copy.patternProperties[name])) {
|
|
310
|
+
relocate(["patternProperties", name]);
|
|
311
|
+
}
|
|
312
|
+
stripped++;
|
|
313
|
+
}
|
|
314
|
+
delete copy.patternProperties;
|
|
315
|
+
// See the header: without the regexes, which keys are "additional" is
|
|
316
|
+
// unknowable, so this constraint is skipped too.
|
|
317
|
+
if ("additionalProperties" in copy) {
|
|
318
|
+
if (isSchema(copy.additionalProperties)) {
|
|
319
|
+
relocate(["additionalProperties"]);
|
|
320
|
+
}
|
|
321
|
+
delete copy.additionalProperties;
|
|
322
|
+
skipped++;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Keywords where a looser subschema can produce a false failure.
|
|
326
|
+
const dropIfTainted = (keyword, alsoDrop = []) => {
|
|
327
|
+
if (!(keyword in copy))
|
|
328
|
+
return;
|
|
329
|
+
const rels = childRels({ [keyword]: copy[keyword] });
|
|
330
|
+
if (!rels.some(isTainted))
|
|
331
|
+
return;
|
|
332
|
+
rels.forEach(relocate);
|
|
333
|
+
delete copy[keyword];
|
|
334
|
+
for (const extra of alsoDrop)
|
|
335
|
+
delete copy[extra];
|
|
336
|
+
skipped++;
|
|
337
|
+
};
|
|
338
|
+
dropIfTainted("not");
|
|
339
|
+
dropIfTainted("oneOf");
|
|
340
|
+
if (isSchema(copy.if) && isTainted(["if"])) {
|
|
341
|
+
for (const keyword of ["if", "then", "else"]) {
|
|
342
|
+
if (isSchema(copy[keyword]))
|
|
343
|
+
relocate([keyword]);
|
|
344
|
+
delete copy[keyword];
|
|
345
|
+
}
|
|
346
|
+
skipped++;
|
|
347
|
+
}
|
|
348
|
+
dropIfTainted("contains", ["minContains", "maxContains"]);
|
|
349
|
+
if (tainted[id]) {
|
|
350
|
+
// What counts as "evaluated" depends on the sibling keywords, which
|
|
351
|
+
// lost something, so these can no longer be judged.
|
|
352
|
+
for (const keyword of ["unevaluatedProperties", "unevaluatedItems"]) {
|
|
353
|
+
if (keyword in copy) {
|
|
354
|
+
if (isSchema(copy[keyword]))
|
|
355
|
+
relocate([keyword]);
|
|
356
|
+
delete copy[keyword];
|
|
357
|
+
skipped++;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
for (const keyword of DYNAMIC_REF_KEYWORDS) {
|
|
362
|
+
if (keyword in copy) {
|
|
363
|
+
delete copy[keyword];
|
|
364
|
+
skipped++;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const target = positions[id].ref;
|
|
368
|
+
if (typeof copy.$ref === "string" && typeof target === "number") {
|
|
369
|
+
refSites.push({ node: copy, target });
|
|
370
|
+
}
|
|
371
|
+
// Recurse into whatever subschemas are still applied here. Each array or
|
|
372
|
+
// map is copied once, then filled in, so the original is never mutated.
|
|
373
|
+
const cloned = new Set();
|
|
374
|
+
for (const rel of childRels(copy)) {
|
|
375
|
+
const child = childOf(rel);
|
|
376
|
+
outParent[child] = id;
|
|
377
|
+
outRel[child] = rel;
|
|
378
|
+
const sub = emit(child);
|
|
379
|
+
if (rel.length === 1) {
|
|
380
|
+
copy[rel[0]] = sub;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const [keyword, name] = rel;
|
|
384
|
+
if (!cloned.has(keyword)) {
|
|
385
|
+
cloned.add(keyword);
|
|
386
|
+
const container = copy[keyword];
|
|
387
|
+
copy[keyword] = Array.isArray(container)
|
|
388
|
+
? [...container]
|
|
389
|
+
: { ...container };
|
|
390
|
+
}
|
|
391
|
+
copy[keyword][name] = sub;
|
|
392
|
+
}
|
|
393
|
+
return copy;
|
|
394
|
+
};
|
|
395
|
+
outParent[0] = -1;
|
|
396
|
+
outRel[0] = [];
|
|
397
|
+
const output = emit(0);
|
|
398
|
+
if (isObject(output) && holding.length > 0) {
|
|
399
|
+
const definitions = isObject(output.definitions)
|
|
400
|
+
? { ...output.definitions }
|
|
401
|
+
: {};
|
|
402
|
+
// A held subschema may itself hold more; iterating the array as it
|
|
403
|
+
// grows picks those up.
|
|
404
|
+
for (const id of holding) {
|
|
405
|
+
let name;
|
|
406
|
+
do {
|
|
407
|
+
name = `${HOLDING_PREFIX}${nextName++}`;
|
|
408
|
+
} while (takenNames.has(name));
|
|
409
|
+
takenNames.add(name);
|
|
410
|
+
outParent[id] = 0;
|
|
411
|
+
outRel[id] = ["definitions", name];
|
|
412
|
+
definitions[name] = emit(id);
|
|
413
|
+
}
|
|
414
|
+
output.definitions = definitions;
|
|
415
|
+
}
|
|
416
|
+
// Build the pointer for each referenced position once, from its parent's.
|
|
417
|
+
const pointers = new Map();
|
|
418
|
+
const pointerOf = (id) => {
|
|
419
|
+
if (outParent[id] === -2)
|
|
420
|
+
return undefined; // never emitted
|
|
421
|
+
if (id === 0)
|
|
422
|
+
return "#";
|
|
423
|
+
const memo = pointers.get(id);
|
|
424
|
+
if (memo !== undefined)
|
|
425
|
+
return memo;
|
|
426
|
+
// Walk up iteratively to the nearest memoized ancestor (no recursion, so
|
|
427
|
+
// a deep schema cannot overflow the stack here).
|
|
428
|
+
const chain = [];
|
|
429
|
+
let current = id;
|
|
430
|
+
while (current !== 0 && !pointers.has(current)) {
|
|
431
|
+
chain.push(current);
|
|
432
|
+
current = outParent[current];
|
|
433
|
+
}
|
|
434
|
+
let pointer = current === 0 ? "#" : pointers.get(current);
|
|
435
|
+
for (let i = chain.length - 1; i >= 0; i--) {
|
|
436
|
+
const link = chain[i];
|
|
437
|
+
pointer += outRel[link].map(formatSegment).join("");
|
|
438
|
+
pointers.set(link, pointer);
|
|
439
|
+
}
|
|
440
|
+
return pointer;
|
|
441
|
+
};
|
|
442
|
+
for (const site of refSites) {
|
|
443
|
+
// Every position is emitted (in place or held), so this always resolves.
|
|
444
|
+
site.node.$ref = pointerOf(site.target);
|
|
445
|
+
}
|
|
446
|
+
return { schema: output, stripped, skipped, validate: !hasIdentifier };
|
|
447
|
+
}
|
|
448
|
+
/** The user-facing note for a result, or `undefined` if nothing was lost. */
|
|
449
|
+
export function schemaNote(result) {
|
|
450
|
+
if (!result.validate) {
|
|
451
|
+
return "this schema was not validated: it declares an $id or $anchor, which the Inspector does not resolve";
|
|
452
|
+
}
|
|
453
|
+
if (result.stripped === 0 && result.skipped === 0)
|
|
454
|
+
return undefined;
|
|
455
|
+
return patternsNotEvaluatedNote(result.stripped, result.skipped);
|
|
456
|
+
}
|
|
457
|
+
/** The user-facing note for a schema that had checks removed. */
|
|
458
|
+
export function patternsNotEvaluatedNote(stripped, skipped = 0) {
|
|
459
|
+
const parts = [];
|
|
460
|
+
if (stripped > 0) {
|
|
461
|
+
parts.push(`${stripped} regex pattern(s) in this schema were not evaluated`);
|
|
462
|
+
}
|
|
463
|
+
if (skipped > 0) {
|
|
464
|
+
parts.push(`${skipped} check(s) in this schema were skipped because they depend on an unevaluated pattern or an unresolvable $ref`);
|
|
465
|
+
}
|
|
466
|
+
return parts.join("; ");
|
|
467
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as useToast, r as reactExports, c as clearOAuthConnectionSnapshot, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-
|
|
1
|
+
import { u as useToast, r as reactExports, c as clearOAuthConnectionSnapshot, j as jsxRuntimeExports, p as parseOAuthCallbackParams, g as generateOAuthErrorDescription, S as SESSION_KEYS, I as InspectorOAuthClientProvider, a as auth } from "./index-Bvgm3HOZ.js";
|
|
2
2
|
const OAuthCallback = ({ onConnect }) => {
|
|
3
3
|
const { toast } = useToast();
|
|
4
4
|
const hasProcessedRef = reactExports.useRef(false);
|
package/client/dist/assets/{OAuthDebugCallback-VeJ8bP3s.js → OAuthDebugCallback-CgAV2c_T.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-
|
|
1
|
+
import { r as reactExports, S as SESSION_KEYS, p as parseOAuthCallbackParams, j as jsxRuntimeExports, g as generateOAuthErrorDescription } from "./index-Bvgm3HOZ.js";
|
|
2
2
|
const OAuthDebugCallback = ({ onConnect }) => {
|
|
3
3
|
reactExports.useEffect(() => {
|
|
4
4
|
let isProcessed = false;
|
|
@@ -12400,7 +12400,7 @@ function esc(str) {
|
|
|
12400
12400
|
}
|
|
12401
12401
|
const captureStackTrace = Error.captureStackTrace ? Error.captureStackTrace : (..._args) => {
|
|
12402
12402
|
};
|
|
12403
|
-
function isObject(data2) {
|
|
12403
|
+
function isObject$1(data2) {
|
|
12404
12404
|
return typeof data2 === "object" && data2 !== null && !Array.isArray(data2);
|
|
12405
12405
|
}
|
|
12406
12406
|
const allowsEval = cached(() => {
|
|
@@ -12416,13 +12416,13 @@ const allowsEval = cached(() => {
|
|
|
12416
12416
|
}
|
|
12417
12417
|
});
|
|
12418
12418
|
function isPlainObject$2(o2) {
|
|
12419
|
-
if (isObject(o2) === false)
|
|
12419
|
+
if (isObject$1(o2) === false)
|
|
12420
12420
|
return false;
|
|
12421
12421
|
const ctor = o2.constructor;
|
|
12422
12422
|
if (ctor === void 0)
|
|
12423
12423
|
return true;
|
|
12424
12424
|
const prot = ctor.prototype;
|
|
12425
|
-
if (isObject(prot) === false)
|
|
12425
|
+
if (isObject$1(prot) === false)
|
|
12426
12426
|
return false;
|
|
12427
12427
|
if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) {
|
|
12428
12428
|
return false;
|
|
@@ -13877,7 +13877,7 @@ const $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
13877
13877
|
return (payload, ctx) => fn2(shape, payload, ctx);
|
|
13878
13878
|
};
|
|
13879
13879
|
let fastpass;
|
|
13880
|
-
const
|
|
13880
|
+
const isObject2 = isObject$1;
|
|
13881
13881
|
const jit = !globalConfig.jitless;
|
|
13882
13882
|
const allowsEval$1 = allowsEval;
|
|
13883
13883
|
const fastEnabled = jit && allowsEval$1.value;
|
|
@@ -13886,7 +13886,7 @@ const $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
13886
13886
|
inst._zod.parse = (payload, ctx) => {
|
|
13887
13887
|
value ?? (value = _normalized.value);
|
|
13888
13888
|
const input = payload.value;
|
|
13889
|
-
if (!
|
|
13889
|
+
if (!isObject2(input)) {
|
|
13890
13890
|
payload.issues.push({
|
|
13891
13891
|
expected: "object",
|
|
13892
13892
|
code: "invalid_type",
|
|
@@ -14045,7 +14045,7 @@ const $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUn
|
|
|
14045
14045
|
});
|
|
14046
14046
|
inst._zod.parse = (payload, ctx) => {
|
|
14047
14047
|
const input = payload.value;
|
|
14048
|
-
if (!isObject(input)) {
|
|
14048
|
+
if (!isObject$1(input)) {
|
|
14049
14049
|
payload.issues.push({
|
|
14050
14050
|
code: "invalid_type",
|
|
14051
14051
|
expected: "object",
|
|
@@ -17366,7 +17366,7 @@ object({
|
|
|
17366
17366
|
token_type_hint: string().optional()
|
|
17367
17367
|
}).strip();
|
|
17368
17368
|
const name = "@modelcontextprotocol/inspector-client";
|
|
17369
|
-
const version$1 = "1.0.
|
|
17369
|
+
const version$1 = "1.0.4";
|
|
17370
17370
|
const packageJson = {
|
|
17371
17371
|
name,
|
|
17372
17372
|
version: version$1
|
|
@@ -29909,15 +29909,348 @@ function requireAjv$2() {
|
|
|
29909
29909
|
}
|
|
29910
29910
|
var ajvExports$1 = requireAjv$2();
|
|
29911
29911
|
const Ajv$1 = /* @__PURE__ */ getDefaultExportFromCjs(ajvExports$1);
|
|
29912
|
+
const SCHEMA_KEYWORDS = [
|
|
29913
|
+
"additionalProperties",
|
|
29914
|
+
"additionalItems",
|
|
29915
|
+
"contains",
|
|
29916
|
+
"propertyNames",
|
|
29917
|
+
"if",
|
|
29918
|
+
"then",
|
|
29919
|
+
"else",
|
|
29920
|
+
"not",
|
|
29921
|
+
"unevaluatedItems",
|
|
29922
|
+
"unevaluatedProperties"
|
|
29923
|
+
];
|
|
29924
|
+
const SCHEMA_ARRAY_KEYWORDS = ["allOf", "anyOf", "oneOf", "prefixItems"];
|
|
29925
|
+
const SCHEMA_MAP_KEYWORDS = [
|
|
29926
|
+
"properties",
|
|
29927
|
+
"patternProperties",
|
|
29928
|
+
"$defs",
|
|
29929
|
+
"definitions",
|
|
29930
|
+
"dependentSchemas",
|
|
29931
|
+
"dependencies"
|
|
29932
|
+
];
|
|
29933
|
+
const HOLDING_PREFIX = "__inspector_unevaluated_";
|
|
29934
|
+
const IDENTIFIER_KEYWORDS = [
|
|
29935
|
+
"$id",
|
|
29936
|
+
"id",
|
|
29937
|
+
"$anchor",
|
|
29938
|
+
"$dynamicAnchor",
|
|
29939
|
+
"$recursiveAnchor"
|
|
29940
|
+
];
|
|
29941
|
+
function isObject(value) {
|
|
29942
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
29943
|
+
}
|
|
29944
|
+
function isSchema(value) {
|
|
29945
|
+
return isObject(value) || typeof value === "boolean";
|
|
29946
|
+
}
|
|
29947
|
+
const relKey = (rel) => rel.length === 1 ? rel[0] : `${rel[0]}\0${rel[1]}`;
|
|
29948
|
+
function childRels(node) {
|
|
29949
|
+
const rels = [];
|
|
29950
|
+
for (const key of SCHEMA_KEYWORDS) {
|
|
29951
|
+
if (isSchema(node[key])) rels.push([key]);
|
|
29952
|
+
}
|
|
29953
|
+
for (const key of SCHEMA_ARRAY_KEYWORDS) {
|
|
29954
|
+
const value = node[key];
|
|
29955
|
+
if (Array.isArray(value)) {
|
|
29956
|
+
value.forEach((sub, i2) => {
|
|
29957
|
+
if (isSchema(sub)) rels.push([key, String(i2)]);
|
|
29958
|
+
});
|
|
29959
|
+
}
|
|
29960
|
+
}
|
|
29961
|
+
for (const key of SCHEMA_MAP_KEYWORDS) {
|
|
29962
|
+
const value = node[key];
|
|
29963
|
+
if (isObject(value)) {
|
|
29964
|
+
for (const [name2, sub] of Object.entries(value)) {
|
|
29965
|
+
if (isSchema(sub)) rels.push([key, name2]);
|
|
29966
|
+
}
|
|
29967
|
+
}
|
|
29968
|
+
}
|
|
29969
|
+
if (Array.isArray(node.items)) {
|
|
29970
|
+
node.items.forEach((sub, i2) => {
|
|
29971
|
+
if (isSchema(sub)) rels.push(["items", String(i2)]);
|
|
29972
|
+
});
|
|
29973
|
+
} else if (isSchema(node.items)) {
|
|
29974
|
+
rels.push(["items"]);
|
|
29975
|
+
}
|
|
29976
|
+
return rels;
|
|
29977
|
+
}
|
|
29978
|
+
function getAt(node, rel) {
|
|
29979
|
+
const value = node[rel[0]];
|
|
29980
|
+
return rel.length === 1 ? value : value[rel[1]];
|
|
29981
|
+
}
|
|
29982
|
+
function parseLocalRef(ref2) {
|
|
29983
|
+
if (ref2 === "#") return [];
|
|
29984
|
+
if (!ref2.startsWith("#/")) return null;
|
|
29985
|
+
if (/%2f|%00/i.test(ref2) || ref2.includes("\0")) return null;
|
|
29986
|
+
try {
|
|
29987
|
+
return ref2.slice(2).split("/").map(
|
|
29988
|
+
(part) => decodeURIComponent(part).replace(/~1/g, "/").replace(/~0/g, "~")
|
|
29989
|
+
);
|
|
29990
|
+
} catch {
|
|
29991
|
+
return null;
|
|
29992
|
+
}
|
|
29993
|
+
}
|
|
29994
|
+
const formatSegment = (part) => "/" + encodeURIComponent(part.replace(/~/g, "~0").replace(/\//g, "~1"));
|
|
29995
|
+
const DYNAMIC_REF_KEYWORDS = ["$dynamicRef", "$recursiveRef"];
|
|
29996
|
+
function stripSchemaPatterns(schema) {
|
|
29997
|
+
const positions = [];
|
|
29998
|
+
const rawRefs = [];
|
|
29999
|
+
let hasIdentifier = false;
|
|
30000
|
+
const analyze = (node, parent) => {
|
|
30001
|
+
const id2 = positions.length;
|
|
30002
|
+
const position = {
|
|
30003
|
+
node,
|
|
30004
|
+
parent,
|
|
30005
|
+
children: /* @__PURE__ */ new Map(),
|
|
30006
|
+
direct: false,
|
|
30007
|
+
dynamic: false
|
|
30008
|
+
};
|
|
30009
|
+
positions.push(position);
|
|
30010
|
+
if (!isObject(node)) return id2;
|
|
30011
|
+
if (IDENTIFIER_KEYWORDS.some((key) => key in node)) hasIdentifier = true;
|
|
30012
|
+
if (typeof node.pattern === "string") position.direct = true;
|
|
30013
|
+
if (isObject(node.patternProperties) && Object.keys(node.patternProperties).length > 0) {
|
|
30014
|
+
position.direct = true;
|
|
30015
|
+
}
|
|
30016
|
+
if (typeof node.$ref === "string") rawRefs.push([id2, node.$ref]);
|
|
30017
|
+
if (DYNAMIC_REF_KEYWORDS.some((key) => key in node)) {
|
|
30018
|
+
position.dynamic = true;
|
|
30019
|
+
}
|
|
30020
|
+
for (const rel of childRels(node)) {
|
|
30021
|
+
position.children.set(relKey(rel), analyze(getAt(node, rel), id2));
|
|
30022
|
+
}
|
|
30023
|
+
return id2;
|
|
30024
|
+
};
|
|
30025
|
+
analyze(schema, -1);
|
|
30026
|
+
const resolve2 = (segments) => {
|
|
30027
|
+
let current = 0;
|
|
30028
|
+
for (let i2 = 0; i2 < segments.length; ) {
|
|
30029
|
+
const { children } = positions[current];
|
|
30030
|
+
const single = children.get(segments[i2]);
|
|
30031
|
+
if (single !== void 0) {
|
|
30032
|
+
current = single;
|
|
30033
|
+
i2 += 1;
|
|
30034
|
+
continue;
|
|
30035
|
+
}
|
|
30036
|
+
if (i2 + 1 >= segments.length) return null;
|
|
30037
|
+
const pair = children.get(`${segments[i2]}\0${segments[i2 + 1]}`);
|
|
30038
|
+
if (pair === void 0) return null;
|
|
30039
|
+
current = pair;
|
|
30040
|
+
i2 += 2;
|
|
30041
|
+
}
|
|
30042
|
+
return current;
|
|
30043
|
+
};
|
|
30044
|
+
const referrers = /* @__PURE__ */ new Map();
|
|
30045
|
+
for (const [id2, ref2] of rawRefs) {
|
|
30046
|
+
const segments = parseLocalRef(ref2);
|
|
30047
|
+
const target = segments === null ? null : resolve2(segments);
|
|
30048
|
+
positions[id2].ref = target;
|
|
30049
|
+
if (target !== null) {
|
|
30050
|
+
const list = referrers.get(target) ?? [];
|
|
30051
|
+
list.push(id2);
|
|
30052
|
+
referrers.set(target, list);
|
|
30053
|
+
}
|
|
30054
|
+
}
|
|
30055
|
+
const tainted = new Uint8Array(positions.length);
|
|
30056
|
+
const work = [];
|
|
30057
|
+
const taint = (id2) => {
|
|
30058
|
+
if (id2 < 0 || tainted[id2]) return;
|
|
30059
|
+
tainted[id2] = 1;
|
|
30060
|
+
work.push(id2);
|
|
30061
|
+
};
|
|
30062
|
+
positions.forEach((position, id2) => {
|
|
30063
|
+
if (position.direct || position.dynamic || position.ref === null) {
|
|
30064
|
+
taint(id2);
|
|
30065
|
+
}
|
|
30066
|
+
});
|
|
30067
|
+
while (work.length > 0) {
|
|
30068
|
+
const id2 = work.pop();
|
|
30069
|
+
taint(positions[id2].parent);
|
|
30070
|
+
for (const referrer of referrers.get(id2) ?? []) taint(referrer);
|
|
30071
|
+
}
|
|
30072
|
+
let stripped = 0;
|
|
30073
|
+
let skipped = 0;
|
|
30074
|
+
const outParent = new Int32Array(positions.length).fill(-2);
|
|
30075
|
+
const outRel = new Array(positions.length);
|
|
30076
|
+
const refSites = [];
|
|
30077
|
+
const holding = [];
|
|
30078
|
+
const root = isObject(schema) ? schema : void 0;
|
|
30079
|
+
const takenNames = new Set(
|
|
30080
|
+
isObject(root?.definitions) ? Object.keys(root.definitions) : []
|
|
30081
|
+
);
|
|
30082
|
+
let nextName = 0;
|
|
30083
|
+
const emit = (id2) => {
|
|
30084
|
+
const { node, children } = positions[id2];
|
|
30085
|
+
if (!isObject(node)) return node;
|
|
30086
|
+
const childOf = (rel) => children.get(relKey(rel));
|
|
30087
|
+
const relocate = (rel) => holding.push(childOf(rel));
|
|
30088
|
+
if (positions[id2].ref === null) {
|
|
30089
|
+
childRels(node).forEach(relocate);
|
|
30090
|
+
skipped++;
|
|
30091
|
+
return {};
|
|
30092
|
+
}
|
|
30093
|
+
const copy = { ...node };
|
|
30094
|
+
const isTainted = (rel) => tainted[childOf(rel)] === 1;
|
|
30095
|
+
if (typeof copy.pattern === "string") {
|
|
30096
|
+
delete copy.pattern;
|
|
30097
|
+
stripped++;
|
|
30098
|
+
}
|
|
30099
|
+
if (isObject(copy.patternProperties) && Object.keys(copy.patternProperties).length > 0) {
|
|
30100
|
+
for (const name2 of Object.keys(copy.patternProperties)) {
|
|
30101
|
+
if (isSchema(copy.patternProperties[name2])) {
|
|
30102
|
+
relocate(["patternProperties", name2]);
|
|
30103
|
+
}
|
|
30104
|
+
stripped++;
|
|
30105
|
+
}
|
|
30106
|
+
delete copy.patternProperties;
|
|
30107
|
+
if ("additionalProperties" in copy) {
|
|
30108
|
+
if (isSchema(copy.additionalProperties)) {
|
|
30109
|
+
relocate(["additionalProperties"]);
|
|
30110
|
+
}
|
|
30111
|
+
delete copy.additionalProperties;
|
|
30112
|
+
skipped++;
|
|
30113
|
+
}
|
|
30114
|
+
}
|
|
30115
|
+
const dropIfTainted = (keyword2, alsoDrop = []) => {
|
|
30116
|
+
if (!(keyword2 in copy)) return;
|
|
30117
|
+
const rels = childRels({ [keyword2]: copy[keyword2] });
|
|
30118
|
+
if (!rels.some(isTainted)) return;
|
|
30119
|
+
rels.forEach(relocate);
|
|
30120
|
+
delete copy[keyword2];
|
|
30121
|
+
for (const extra of alsoDrop) delete copy[extra];
|
|
30122
|
+
skipped++;
|
|
30123
|
+
};
|
|
30124
|
+
dropIfTainted("not");
|
|
30125
|
+
dropIfTainted("oneOf");
|
|
30126
|
+
if (isSchema(copy.if) && isTainted(["if"])) {
|
|
30127
|
+
for (const keyword2 of ["if", "then", "else"]) {
|
|
30128
|
+
if (isSchema(copy[keyword2])) relocate([keyword2]);
|
|
30129
|
+
delete copy[keyword2];
|
|
30130
|
+
}
|
|
30131
|
+
skipped++;
|
|
30132
|
+
}
|
|
30133
|
+
dropIfTainted("contains", ["minContains", "maxContains"]);
|
|
30134
|
+
if (tainted[id2]) {
|
|
30135
|
+
for (const keyword2 of ["unevaluatedProperties", "unevaluatedItems"]) {
|
|
30136
|
+
if (keyword2 in copy) {
|
|
30137
|
+
if (isSchema(copy[keyword2])) relocate([keyword2]);
|
|
30138
|
+
delete copy[keyword2];
|
|
30139
|
+
skipped++;
|
|
30140
|
+
}
|
|
30141
|
+
}
|
|
30142
|
+
}
|
|
30143
|
+
for (const keyword2 of DYNAMIC_REF_KEYWORDS) {
|
|
30144
|
+
if (keyword2 in copy) {
|
|
30145
|
+
delete copy[keyword2];
|
|
30146
|
+
skipped++;
|
|
30147
|
+
}
|
|
30148
|
+
}
|
|
30149
|
+
const target = positions[id2].ref;
|
|
30150
|
+
if (typeof copy.$ref === "string" && typeof target === "number") {
|
|
30151
|
+
refSites.push({ node: copy, target });
|
|
30152
|
+
}
|
|
30153
|
+
const cloned = /* @__PURE__ */ new Set();
|
|
30154
|
+
for (const rel of childRels(copy)) {
|
|
30155
|
+
const child = childOf(rel);
|
|
30156
|
+
outParent[child] = id2;
|
|
30157
|
+
outRel[child] = rel;
|
|
30158
|
+
const sub = emit(child);
|
|
30159
|
+
if (rel.length === 1) {
|
|
30160
|
+
copy[rel[0]] = sub;
|
|
30161
|
+
continue;
|
|
30162
|
+
}
|
|
30163
|
+
const [keyword2, name2] = rel;
|
|
30164
|
+
if (!cloned.has(keyword2)) {
|
|
30165
|
+
cloned.add(keyword2);
|
|
30166
|
+
const container = copy[keyword2];
|
|
30167
|
+
copy[keyword2] = Array.isArray(container) ? [...container] : { ...container };
|
|
30168
|
+
}
|
|
30169
|
+
copy[keyword2][name2] = sub;
|
|
30170
|
+
}
|
|
30171
|
+
return copy;
|
|
30172
|
+
};
|
|
30173
|
+
outParent[0] = -1;
|
|
30174
|
+
outRel[0] = [];
|
|
30175
|
+
const output = emit(0);
|
|
30176
|
+
if (isObject(output) && holding.length > 0) {
|
|
30177
|
+
const definitions2 = isObject(output.definitions) ? { ...output.definitions } : {};
|
|
30178
|
+
for (const id2 of holding) {
|
|
30179
|
+
let name2;
|
|
30180
|
+
do {
|
|
30181
|
+
name2 = `${HOLDING_PREFIX}${nextName++}`;
|
|
30182
|
+
} while (takenNames.has(name2));
|
|
30183
|
+
takenNames.add(name2);
|
|
30184
|
+
outParent[id2] = 0;
|
|
30185
|
+
outRel[id2] = ["definitions", name2];
|
|
30186
|
+
definitions2[name2] = emit(id2);
|
|
30187
|
+
}
|
|
30188
|
+
output.definitions = definitions2;
|
|
30189
|
+
}
|
|
30190
|
+
const pointers = /* @__PURE__ */ new Map();
|
|
30191
|
+
const pointerOf = (id2) => {
|
|
30192
|
+
if (outParent[id2] === -2) return void 0;
|
|
30193
|
+
if (id2 === 0) return "#";
|
|
30194
|
+
const memo = pointers.get(id2);
|
|
30195
|
+
if (memo !== void 0) return memo;
|
|
30196
|
+
const chain = [];
|
|
30197
|
+
let current = id2;
|
|
30198
|
+
while (current !== 0 && !pointers.has(current)) {
|
|
30199
|
+
chain.push(current);
|
|
30200
|
+
current = outParent[current];
|
|
30201
|
+
}
|
|
30202
|
+
let pointer = current === 0 ? "#" : pointers.get(current);
|
|
30203
|
+
for (let i2 = chain.length - 1; i2 >= 0; i2--) {
|
|
30204
|
+
const link = chain[i2];
|
|
30205
|
+
pointer += outRel[link].map(formatSegment).join("");
|
|
30206
|
+
pointers.set(link, pointer);
|
|
30207
|
+
}
|
|
30208
|
+
return pointer;
|
|
30209
|
+
};
|
|
30210
|
+
for (const site of refSites) {
|
|
30211
|
+
site.node.$ref = pointerOf(site.target);
|
|
30212
|
+
}
|
|
30213
|
+
return { schema: output, stripped, skipped, validate: !hasIdentifier };
|
|
30214
|
+
}
|
|
30215
|
+
function schemaNote(result) {
|
|
30216
|
+
if (!result.validate) {
|
|
30217
|
+
return "this schema was not validated: it declares an $id or $anchor, which the Inspector does not resolve";
|
|
30218
|
+
}
|
|
30219
|
+
if (result.stripped === 0 && result.skipped === 0) return void 0;
|
|
30220
|
+
return patternsNotEvaluatedNote(result.stripped, result.skipped);
|
|
30221
|
+
}
|
|
30222
|
+
function patternsNotEvaluatedNote(stripped, skipped = 0) {
|
|
30223
|
+
const parts = [];
|
|
30224
|
+
if (stripped > 0) {
|
|
30225
|
+
parts.push(
|
|
30226
|
+
`${stripped} regex pattern(s) in this schema were not evaluated`
|
|
30227
|
+
);
|
|
30228
|
+
}
|
|
30229
|
+
if (skipped > 0) {
|
|
30230
|
+
parts.push(
|
|
30231
|
+
`${skipped} check(s) in this schema were skipped because they depend on an unevaluated pattern or an unresolvable $ref`
|
|
30232
|
+
);
|
|
30233
|
+
}
|
|
30234
|
+
return parts.join("; ");
|
|
30235
|
+
}
|
|
29912
30236
|
const ajv$2 = new Ajv$1();
|
|
29913
30237
|
const toolOutputValidators = /* @__PURE__ */ new Map();
|
|
30238
|
+
const toolOutputSchemaNotes = /* @__PURE__ */ new Map();
|
|
29914
30239
|
function cacheToolOutputSchemas(tools) {
|
|
29915
30240
|
toolOutputValidators.clear();
|
|
30241
|
+
toolOutputSchemaNotes.clear();
|
|
29916
30242
|
for (const tool of tools) {
|
|
29917
30243
|
if (tool.outputSchema) {
|
|
29918
30244
|
try {
|
|
29919
|
-
const
|
|
29920
|
-
|
|
30245
|
+
const result = stripSchemaPatterns(tool.outputSchema);
|
|
30246
|
+
const note = schemaNote(result);
|
|
30247
|
+
if (result.validate) {
|
|
30248
|
+
toolOutputValidators.set(
|
|
30249
|
+
tool.name,
|
|
30250
|
+
ajv$2.compile(result.schema)
|
|
30251
|
+
);
|
|
30252
|
+
}
|
|
30253
|
+
if (note !== void 0) toolOutputSchemaNotes.set(tool.name, note);
|
|
29921
30254
|
} catch (error) {
|
|
29922
30255
|
console.warn(
|
|
29923
30256
|
`Failed to compile output schema for tool ${tool.name}:`,
|
|
@@ -29932,20 +30265,23 @@ function getToolOutputValidator(toolName) {
|
|
|
29932
30265
|
}
|
|
29933
30266
|
function validateToolOutput(toolName, structuredContent) {
|
|
29934
30267
|
const validator = getToolOutputValidator(toolName);
|
|
30268
|
+
const toolNote = toolOutputSchemaNotes.get(toolName);
|
|
30269
|
+
const note = toolNote === void 0 ? {} : { note: toolNote };
|
|
29935
30270
|
if (!validator) {
|
|
29936
|
-
return { isValid: true };
|
|
30271
|
+
return toolNote === void 0 ? { isValid: true } : { isValid: true, validated: false, note: toolNote };
|
|
29937
30272
|
}
|
|
29938
30273
|
const isValid2 = validator(structuredContent);
|
|
29939
30274
|
if (!isValid2) {
|
|
29940
30275
|
return {
|
|
29941
30276
|
isValid: false,
|
|
29942
|
-
error: ajv$2.errorsText(validator.errors)
|
|
30277
|
+
error: ajv$2.errorsText(validator.errors),
|
|
30278
|
+
...note
|
|
29943
30279
|
};
|
|
29944
30280
|
}
|
|
29945
|
-
return { isValid: true };
|
|
30281
|
+
return { isValid: true, ...note };
|
|
29946
30282
|
}
|
|
29947
30283
|
function hasOutputSchema(toolName) {
|
|
29948
|
-
return toolOutputValidators.has(toolName);
|
|
30284
|
+
return toolOutputValidators.has(toolName) || toolOutputSchemaNotes.has(toolName);
|
|
29949
30285
|
}
|
|
29950
30286
|
function generateDefaultValue(schema, propertyName, parentSchema) {
|
|
29951
30287
|
if ("default" in schema && schema.default !== void 0) {
|
|
@@ -57143,7 +57479,7 @@ const useTheme = () => {
|
|
|
57143
57479
|
[theme, setThemeWithSideEffect]
|
|
57144
57480
|
);
|
|
57145
57481
|
};
|
|
57146
|
-
const version = "1.0.
|
|
57482
|
+
const version = "1.0.4";
|
|
57147
57483
|
var __defProp$2 = Object.defineProperty;
|
|
57148
57484
|
var __name$2 = (target, value) => __defProp$2(target, "name", { value, configurable: true });
|
|
57149
57485
|
var [createTooltipContext, createTooltipScope] = /* @__PURE__ */ createContextScope("Tooltip", [
|
|
@@ -59409,14 +59745,20 @@ const ToolResults = ({
|
|
|
59409
59745
|
/* @__PURE__ */ jsxRuntimeExports.jsx("h5", { className: "font-semibold mb-2 text-sm", children: "Structured Content:" }),
|
|
59410
59746
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-gray-50 dark:bg-gray-900 p-3 rounded-lg", children: [
|
|
59411
59747
|
/* @__PURE__ */ jsxRuntimeExports.jsx(JsonView, { data: structuredResult.structuredContent }),
|
|
59412
|
-
validationResult && /* @__PURE__ */ jsxRuntimeExports.
|
|
59748
|
+
validationResult && /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
59413
59749
|
"div",
|
|
59414
59750
|
{
|
|
59415
59751
|
className: `mt-2 p-2 rounded text-sm ${validationResult.isValid ? "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200" : "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200"}`,
|
|
59416
|
-
children:
|
|
59417
|
-
"
|
|
59418
|
-
|
|
59419
|
-
|
|
59752
|
+
children: [
|
|
59753
|
+
"validated" in validationResult && validationResult.validated === false ? "Not validated against output schema" : validationResult.isValid ? "✓ Valid according to output schema" : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
59754
|
+
"✗ Validation Error: ",
|
|
59755
|
+
validationResult.error
|
|
59756
|
+
] }),
|
|
59757
|
+
"note" in validationResult && validationResult.note && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 text-xs opacity-80", children: [
|
|
59758
|
+
"⚠ ",
|
|
59759
|
+
validationResult.note
|
|
59760
|
+
] })
|
|
59761
|
+
]
|
|
59420
59762
|
}
|
|
59421
59763
|
)
|
|
59422
59764
|
] })
|
|
@@ -59427,7 +59769,11 @@ const ToolResults = ({
|
|
|
59427
59769
|
] }),
|
|
59428
59770
|
!structuredResult.structuredContent && validationResult && !validationResult.isValid && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 p-2 rounded text-sm", children: [
|
|
59429
59771
|
"✗ Validation Error: ",
|
|
59430
|
-
validationResult.error
|
|
59772
|
+
validationResult.error,
|
|
59773
|
+
"note" in validationResult && validationResult.note && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-1 text-xs opacity-80", children: [
|
|
59774
|
+
"⚠ ",
|
|
59775
|
+
validationResult.note
|
|
59776
|
+
] })
|
|
59431
59777
|
] }) }),
|
|
59432
59778
|
structuredResult.content.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [
|
|
59433
59779
|
structuredResult.structuredContent && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
|
|
@@ -66726,6 +67072,14 @@ const ElicitationRequest = ({
|
|
|
66726
67072
|
const [showUrlConfirm, setShowUrlConfirm] = reactExports.useState(false);
|
|
66727
67073
|
const requestData = request.request;
|
|
66728
67074
|
const isUrlMode = requestData.mode === "url";
|
|
67075
|
+
const sanitized = reactExports.useMemo(
|
|
67076
|
+
() => isUrlMode ? void 0 : stripSchemaPatterns(
|
|
67077
|
+
requestData.requestedSchema
|
|
67078
|
+
),
|
|
67079
|
+
[isUrlMode, requestData]
|
|
67080
|
+
);
|
|
67081
|
+
const regexFreeSchema = sanitized?.schema;
|
|
67082
|
+
const sanitizedNote = sanitized && schemaNote(sanitized);
|
|
66729
67083
|
reactExports.useEffect(() => {
|
|
66730
67084
|
if (isUrlMode) return;
|
|
66731
67085
|
const defaultValue = generateDefaultValue(
|
|
@@ -66885,13 +67239,15 @@ const ElicitationRequest = ({
|
|
|
66885
67239
|
if (!validateFormData(formData, formRequest.requestedSchema)) {
|
|
66886
67240
|
return;
|
|
66887
67241
|
}
|
|
66888
|
-
|
|
66889
|
-
|
|
66890
|
-
|
|
66891
|
-
|
|
66892
|
-
|
|
66893
|
-
|
|
66894
|
-
|
|
67242
|
+
if (sanitized?.validate) {
|
|
67243
|
+
const ajv2 = new Ajv$1();
|
|
67244
|
+
const validate2 = ajv2.compile(regexFreeSchema);
|
|
67245
|
+
const isValid2 = validate2(formData);
|
|
67246
|
+
if (!isValid2) {
|
|
67247
|
+
const errorMessage = ajv2.errorsText(validate2.errors);
|
|
67248
|
+
setValidationError(errorMessage);
|
|
67249
|
+
return;
|
|
67250
|
+
}
|
|
66895
67251
|
}
|
|
66896
67252
|
onResolve(request.id, {
|
|
66897
67253
|
action: "accept",
|
|
@@ -66937,7 +67293,7 @@ const ElicitationRequest = ({
|
|
|
66937
67293
|
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
66938
67294
|
DynamicJsonForm,
|
|
66939
67295
|
{
|
|
66940
|
-
schema:
|
|
67296
|
+
schema: regexFreeSchema,
|
|
66941
67297
|
value: formData,
|
|
66942
67298
|
onChange: (newValue) => {
|
|
66943
67299
|
setFormData(newValue);
|
|
@@ -66945,6 +67301,10 @@ const ElicitationRequest = ({
|
|
|
66945
67301
|
}
|
|
66946
67302
|
}
|
|
66947
67303
|
),
|
|
67304
|
+
sanitizedNote && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 text-xs text-muted-foreground", children: [
|
|
67305
|
+
"⚠ ",
|
|
67306
|
+
sanitizedNote
|
|
67307
|
+
] }),
|
|
66948
67308
|
validationError && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-4 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm text-red-600 dark:text-red-400", children: [
|
|
66949
67309
|
/* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Validation Error:" }),
|
|
66950
67310
|
" ",
|
|
@@ -68053,13 +68413,13 @@ const App = () => {
|
|
|
68053
68413
|
) });
|
|
68054
68414
|
if (window.location.pathname === OAUTH_CALLBACK_PATH) {
|
|
68055
68415
|
const OAuthCallback = React.lazy(
|
|
68056
|
-
() => __vitePreload(() => import("./OAuthCallback-
|
|
68416
|
+
() => __vitePreload(() => import("./OAuthCallback-Cb6VJMaq.js"), true ? [] : void 0)
|
|
68057
68417
|
);
|
|
68058
68418
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthCallback, { onConnect: onOAuthConnect }) });
|
|
68059
68419
|
}
|
|
68060
68420
|
if (window.location.pathname === OAUTH_DEBUG_CALLBACK_PATH) {
|
|
68061
68421
|
const OAuthDebugCallback = React.lazy(
|
|
68062
|
-
() => __vitePreload(() => import("./OAuthDebugCallback-
|
|
68422
|
+
() => __vitePreload(() => import("./OAuthDebugCallback-CgAV2c_T.js"), true ? [] : void 0)
|
|
68063
68423
|
);
|
|
68064
68424
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(reactExports.Suspense, { fallback: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsxRuntimeExports.jsx(OAuthDebugCallback, { onConnect: onOAuthDebugConnect }) });
|
|
68065
68425
|
}
|
package/client/dist/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
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-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-Bvgm3HOZ.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-D-iperlR.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root" class="w-full"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelcontextprotocol/inspector",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Model Context Protocol inspector",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Model Context Protocol a Series of LF Projects, LLC.",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"check-version": "node scripts/check-version-consistency.js"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@modelcontextprotocol/inspector-cli": "^1.0.
|
|
57
|
-
"@modelcontextprotocol/inspector-client": "^1.0.
|
|
58
|
-
"@modelcontextprotocol/inspector-server": "^1.0.
|
|
56
|
+
"@modelcontextprotocol/inspector-cli": "^1.0.4",
|
|
57
|
+
"@modelcontextprotocol/inspector-client": "^1.0.4",
|
|
58
|
+
"@modelcontextprotocol/inspector-server": "^1.0.4",
|
|
59
59
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
60
60
|
"concurrently": "^9.2.0",
|
|
61
61
|
"node-fetch": "^3.3.2",
|