@modelcontextprotocol/inspector 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/clients/cli/build/index.js +1485 -78
- package/clients/tui/build/index.js +2539 -838
- package/clients/web/build/index.js +14 -3
- package/clients/web/dist/assets/index-30pI79Cf.js +1595 -0
- package/clients/web/dist/assets/{prism-light-_bvDBTQE.js → prism-light-9SaQe5zv.js} +1 -1
- package/clients/web/dist/index.html +1 -1
- package/package.json +3 -2
- package/clients/web/dist/assets/index-ZYqA4f7P.js +0 -1486
|
@@ -616,7 +616,11 @@ function isOfferable(branch, merged) {
|
|
|
616
616
|
return false;
|
|
617
617
|
}
|
|
618
618
|
if (!admitsObject(branch)) return false;
|
|
619
|
-
const
|
|
619
|
+
const mergedProperties = propertiesOf(merged);
|
|
620
|
+
if (requiredOf(merged).some((name) => !Object.hasOwn(mergedProperties, name))) {
|
|
621
|
+
return false;
|
|
622
|
+
}
|
|
623
|
+
const properties = Object.values(mergedProperties);
|
|
620
624
|
return properties.length > 0 && // Every value has to be something a renderer can read AND something a
|
|
621
625
|
// caller can satisfy. A `null` or an array is not a schema at all, and the
|
|
622
626
|
// web form dereferences one on the way to choosing a widget. JSON Schema's
|
|
@@ -4238,6 +4242,7 @@ import {
|
|
|
4238
4242
|
OAuthTokensSchema as OAuthTokensSchema2
|
|
4239
4243
|
} from "@modelcontextprotocol/core";
|
|
4240
4244
|
var DEFAULT_REVOCATION_TIMEOUT_MS = 5e3;
|
|
4245
|
+
var MIN_REVOCATION_REQUEST_BUDGET_MS = 5;
|
|
4241
4246
|
function selectRevocableToken(tokens) {
|
|
4242
4247
|
if (!tokens) return null;
|
|
4243
4248
|
if (tokens.refresh_token) {
|
|
@@ -4285,7 +4290,10 @@ function base64Encode(value) {
|
|
|
4285
4290
|
return Buffer.from(value, "utf8").toString("base64");
|
|
4286
4291
|
}
|
|
4287
4292
|
async function revokeToken(params) {
|
|
4288
|
-
const timeoutMs =
|
|
4293
|
+
const timeoutMs = Math.max(
|
|
4294
|
+
0,
|
|
4295
|
+
Math.round(params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS)
|
|
4296
|
+
);
|
|
4289
4297
|
try {
|
|
4290
4298
|
const { url, init } = buildRevocationRequest(params);
|
|
4291
4299
|
const response = await withDeadline(
|
|
@@ -4473,7 +4481,7 @@ async function runPlan(plan, params) {
|
|
|
4473
4481
|
if (!endpoint) return { status: "skipped", reason: "no_endpoint" };
|
|
4474
4482
|
const outcomes = [...plan.failures];
|
|
4475
4483
|
const timeoutMs = params.timeoutMs ?? DEFAULT_REVOCATION_TIMEOUT_MS;
|
|
4476
|
-
const deadlineAt =
|
|
4484
|
+
const deadlineAt = performance.now() + timeoutMs;
|
|
4477
4485
|
for (const grant of plan.grants) {
|
|
4478
4486
|
if (grant.issuer === void 0) {
|
|
4479
4487
|
outcomes.push({
|
|
@@ -4489,8 +4497,8 @@ async function runPlan(plan, params) {
|
|
|
4489
4497
|
});
|
|
4490
4498
|
continue;
|
|
4491
4499
|
}
|
|
4492
|
-
const remainingMs = deadlineAt -
|
|
4493
|
-
if (remainingMs <=
|
|
4500
|
+
const remainingMs = deadlineAt - performance.now();
|
|
4501
|
+
if (remainingMs <= MIN_REVOCATION_REQUEST_BUDGET_MS) {
|
|
4494
4502
|
outcomes.push({
|
|
4495
4503
|
status: "failed",
|
|
4496
4504
|
endpoint,
|
|
@@ -4542,13 +4550,13 @@ async function clearStoredAuthForRelogin(serverUrl, options) {
|
|
|
4542
4550
|
}
|
|
4543
4551
|
async function sendPlans(plans, budgetMs) {
|
|
4544
4552
|
const fetchFn = createProxyFetch() ?? fetch;
|
|
4545
|
-
const deadlineAt =
|
|
4553
|
+
const deadlineAt = performance.now() + budgetMs;
|
|
4546
4554
|
let reported;
|
|
4547
4555
|
let lastSkip;
|
|
4548
4556
|
for (const plan of plans) {
|
|
4549
|
-
const remainingMs = deadlineAt -
|
|
4557
|
+
const remainingMs = deadlineAt - performance.now();
|
|
4550
4558
|
const needsNetwork = plan.outcome === void 0;
|
|
4551
|
-
if (needsNetwork && remainingMs <=
|
|
4559
|
+
if (needsNetwork && remainingMs <= MIN_REVOCATION_REQUEST_BUDGET_MS) {
|
|
4552
4560
|
const exhausted = {
|
|
4553
4561
|
status: "failed",
|
|
4554
4562
|
detail: `the ${budgetMs}ms revocation budget was exhausted before "${plan.serverUrl}" was attempted`
|
|
@@ -4978,6 +4986,653 @@ function buildClientExtensions(input) {
|
|
|
4978
4986
|
return map;
|
|
4979
4987
|
}
|
|
4980
4988
|
|
|
4989
|
+
// ../../core/mcp/skillsSchemas.ts
|
|
4990
|
+
import { z as z2 } from "zod/v4";
|
|
4991
|
+
import { ResourceSchema } from "@modelcontextprotocol/core";
|
|
4992
|
+
var SKILLS_EXTENSION_KEY = "io.modelcontextprotocol/skills";
|
|
4993
|
+
var SKILLS_LIST_METHOD = "skills/list";
|
|
4994
|
+
var SKILLS_GET_METHOD = "skills/get";
|
|
4995
|
+
var RESOURCES_DIRECTORY_READ_METHOD = "resources/directory/read";
|
|
4996
|
+
var DYNAMIC_RESOURCES = "dynamic";
|
|
4997
|
+
var SkillFrontmatterSchema = z2.looseObject({
|
|
4998
|
+
name: z2.string().optional(),
|
|
4999
|
+
description: z2.string().optional()
|
|
5000
|
+
});
|
|
5001
|
+
var SkillResourceSchema = z2.looseObject({
|
|
5002
|
+
uri: z2.string(),
|
|
5003
|
+
digest: z2.string().optional(),
|
|
5004
|
+
size: z2.number().optional()
|
|
5005
|
+
});
|
|
5006
|
+
var SkillEntrySchema = z2.looseObject({
|
|
5007
|
+
uri: z2.string(),
|
|
5008
|
+
frontmatter: SkillFrontmatterSchema,
|
|
5009
|
+
resources: z2.union([
|
|
5010
|
+
z2.literal(DYNAMIC_RESOURCES),
|
|
5011
|
+
z2.array(SkillResourceSchema)
|
|
5012
|
+
])
|
|
5013
|
+
});
|
|
5014
|
+
var ListSkillsResultSchema = z2.looseObject({
|
|
5015
|
+
skills: z2.array(SkillEntrySchema),
|
|
5016
|
+
nextCursor: z2.string().optional()
|
|
5017
|
+
});
|
|
5018
|
+
var ModernListSkillsResultSchema = ListSkillsResultSchema.extend({
|
|
5019
|
+
resultType: z2.literal("complete"),
|
|
5020
|
+
// A non-negative INTEGER, matching the codec: `ttlMs: -1` or `0.5` is an
|
|
5021
|
+
// envelope violation, and accepting it here would hide exactly the kind of
|
|
5022
|
+
// defect this schema exists to surface.
|
|
5023
|
+
ttlMs: z2.int().min(0),
|
|
5024
|
+
cacheScope: z2.enum(["public", "private"])
|
|
5025
|
+
});
|
|
5026
|
+
var GetSkillEnvelopeSchema = z2.looseObject({
|
|
5027
|
+
skill: SkillEntrySchema
|
|
5028
|
+
});
|
|
5029
|
+
var ModernGetSkillEnvelopeSchema = GetSkillEnvelopeSchema.extend({
|
|
5030
|
+
resultType: z2.literal("complete")
|
|
5031
|
+
});
|
|
5032
|
+
var GetSkillResultSchema = GetSkillEnvelopeSchema.transform(
|
|
5033
|
+
(result) => result.skill
|
|
5034
|
+
);
|
|
5035
|
+
var DirectoryChildSchema = ResourceSchema;
|
|
5036
|
+
var DirectoryReadResultSchema = z2.looseObject({
|
|
5037
|
+
resources: z2.array(DirectoryChildSchema),
|
|
5038
|
+
nextCursor: z2.string().optional()
|
|
5039
|
+
});
|
|
5040
|
+
var ModernDirectoryReadResultSchema = DirectoryReadResultSchema.extend(
|
|
5041
|
+
{ resultType: z2.literal("complete") }
|
|
5042
|
+
);
|
|
5043
|
+
|
|
5044
|
+
// ../../core/mcp/sha256.ts
|
|
5045
|
+
var K = new Uint32Array([
|
|
5046
|
+
1116352408,
|
|
5047
|
+
1899447441,
|
|
5048
|
+
3049323471,
|
|
5049
|
+
3921009573,
|
|
5050
|
+
961987163,
|
|
5051
|
+
1508970993,
|
|
5052
|
+
2453635748,
|
|
5053
|
+
2870763221,
|
|
5054
|
+
3624381080,
|
|
5055
|
+
310598401,
|
|
5056
|
+
607225278,
|
|
5057
|
+
1426881987,
|
|
5058
|
+
1925078388,
|
|
5059
|
+
2162078206,
|
|
5060
|
+
2614888103,
|
|
5061
|
+
3248222580,
|
|
5062
|
+
3835390401,
|
|
5063
|
+
4022224774,
|
|
5064
|
+
264347078,
|
|
5065
|
+
604807628,
|
|
5066
|
+
770255983,
|
|
5067
|
+
1249150122,
|
|
5068
|
+
1555081692,
|
|
5069
|
+
1996064986,
|
|
5070
|
+
2554220882,
|
|
5071
|
+
2821834349,
|
|
5072
|
+
2952996808,
|
|
5073
|
+
3210313671,
|
|
5074
|
+
3336571891,
|
|
5075
|
+
3584528711,
|
|
5076
|
+
113926993,
|
|
5077
|
+
338241895,
|
|
5078
|
+
666307205,
|
|
5079
|
+
773529912,
|
|
5080
|
+
1294757372,
|
|
5081
|
+
1396182291,
|
|
5082
|
+
1695183700,
|
|
5083
|
+
1986661051,
|
|
5084
|
+
2177026350,
|
|
5085
|
+
2456956037,
|
|
5086
|
+
2730485921,
|
|
5087
|
+
2820302411,
|
|
5088
|
+
3259730800,
|
|
5089
|
+
3345764771,
|
|
5090
|
+
3516065817,
|
|
5091
|
+
3600352804,
|
|
5092
|
+
4094571909,
|
|
5093
|
+
275423344,
|
|
5094
|
+
430227734,
|
|
5095
|
+
506948616,
|
|
5096
|
+
659060556,
|
|
5097
|
+
883997877,
|
|
5098
|
+
958139571,
|
|
5099
|
+
1322822218,
|
|
5100
|
+
1537002063,
|
|
5101
|
+
1747873779,
|
|
5102
|
+
1955562222,
|
|
5103
|
+
2024104815,
|
|
5104
|
+
2227730452,
|
|
5105
|
+
2361852424,
|
|
5106
|
+
2428436474,
|
|
5107
|
+
2756734187,
|
|
5108
|
+
3204031479,
|
|
5109
|
+
3329325298
|
|
5110
|
+
]);
|
|
5111
|
+
var H0 = new Uint32Array([
|
|
5112
|
+
1779033703,
|
|
5113
|
+
3144134277,
|
|
5114
|
+
1013904242,
|
|
5115
|
+
2773480762,
|
|
5116
|
+
1359893119,
|
|
5117
|
+
2600822924,
|
|
5118
|
+
528734635,
|
|
5119
|
+
1541459225
|
|
5120
|
+
]);
|
|
5121
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
5122
|
+
function sha256Bytes(bytes) {
|
|
5123
|
+
const message = new Uint8Array(bytes);
|
|
5124
|
+
const bitLength = message.length * 8;
|
|
5125
|
+
const withLength = message.length + 9;
|
|
5126
|
+
const padded = new Uint8Array(Math.ceil(withLength / 64) * 64);
|
|
5127
|
+
padded.set(message);
|
|
5128
|
+
padded[message.length] = 128;
|
|
5129
|
+
const view = new DataView(padded.buffer);
|
|
5130
|
+
view.setUint32(padded.length - 8, Math.floor(bitLength / 4294967296));
|
|
5131
|
+
view.setUint32(padded.length - 4, bitLength >>> 0);
|
|
5132
|
+
const h = new Uint32Array(H0);
|
|
5133
|
+
const w = new Uint32Array(64);
|
|
5134
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
5135
|
+
for (let i = 0; i < 16; i += 1) w[i] = view.getUint32(offset + i * 4);
|
|
5136
|
+
for (let i = 16; i < 64; i += 1) {
|
|
5137
|
+
const s0 = rotr(w[i - 15], 7) ^ rotr(w[i - 15], 18) ^ w[i - 15] >>> 3;
|
|
5138
|
+
const s1 = rotr(w[i - 2], 17) ^ rotr(w[i - 2], 19) ^ w[i - 2] >>> 10;
|
|
5139
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
5140
|
+
}
|
|
5141
|
+
let [a, b, c, d, e, f, g, hh] = h;
|
|
5142
|
+
for (let i = 0; i < 64; i += 1) {
|
|
5143
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
5144
|
+
const ch = e & f ^ ~e & g;
|
|
5145
|
+
const temp1 = hh + S1 + ch + K[i] + w[i] >>> 0;
|
|
5146
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
5147
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
5148
|
+
const temp2 = S0 + maj >>> 0;
|
|
5149
|
+
hh = g;
|
|
5150
|
+
g = f;
|
|
5151
|
+
f = e;
|
|
5152
|
+
e = d + temp1 >>> 0;
|
|
5153
|
+
d = c;
|
|
5154
|
+
c = b;
|
|
5155
|
+
b = a;
|
|
5156
|
+
a = temp1 + temp2 >>> 0;
|
|
5157
|
+
}
|
|
5158
|
+
h[0] = h[0] + a >>> 0;
|
|
5159
|
+
h[1] = h[1] + b >>> 0;
|
|
5160
|
+
h[2] = h[2] + c >>> 0;
|
|
5161
|
+
h[3] = h[3] + d >>> 0;
|
|
5162
|
+
h[4] = h[4] + e >>> 0;
|
|
5163
|
+
h[5] = h[5] + f >>> 0;
|
|
5164
|
+
h[6] = h[6] + g >>> 0;
|
|
5165
|
+
h[7] = h[7] + hh >>> 0;
|
|
5166
|
+
}
|
|
5167
|
+
const digest = new Uint8Array(32);
|
|
5168
|
+
const out = new DataView(digest.buffer);
|
|
5169
|
+
for (let i = 0; i < 8; i += 1) out.setUint32(i * 4, h[i]);
|
|
5170
|
+
return digest;
|
|
5171
|
+
}
|
|
5172
|
+
|
|
5173
|
+
// ../../core/mcp/skillFile.ts
|
|
5174
|
+
import { parse as parseYaml } from "yaml";
|
|
5175
|
+
function splitSkillFile(text) {
|
|
5176
|
+
if (!/^---[ \t]*\r?\n/.test(text)) return { body: text };
|
|
5177
|
+
const rest = text.slice(text.indexOf("\n") + 1);
|
|
5178
|
+
const close = rest.search(/^---[ \t]*\r?$/m);
|
|
5179
|
+
if (close === -1) return { body: text };
|
|
5180
|
+
const frontmatter = rest.slice(0, close).replace(/\r?\n$/, "");
|
|
5181
|
+
const after = rest.slice(close);
|
|
5182
|
+
const newline = after.indexOf("\n");
|
|
5183
|
+
if (newline === -1) return { frontmatter, body: "" };
|
|
5184
|
+
return { frontmatter, body: after.slice(newline + 1).replace(/^\r?\n/, "") };
|
|
5185
|
+
}
|
|
5186
|
+
function hasContent(yamlText) {
|
|
5187
|
+
return yamlText.split("\n").some((line) => line.trim() !== "" && !line.trimStart().startsWith("#"));
|
|
5188
|
+
}
|
|
5189
|
+
var MAX_FRONTMATTER_DEPTH = 64;
|
|
5190
|
+
function jsonGraphError(value, seen = /* @__PURE__ */ new Set(), depth = 0) {
|
|
5191
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
5192
|
+
if (depth > MAX_FRONTMATTER_DEPTH) {
|
|
5193
|
+
return `Frontmatter nests deeper than ${MAX_FRONTMATTER_DEPTH} levels, which cannot be compared field by field.`;
|
|
5194
|
+
}
|
|
5195
|
+
const node = value;
|
|
5196
|
+
if (seen.has(node)) {
|
|
5197
|
+
return "Frontmatter contains a cyclic YAML alias, which has no JSON equivalent and cannot be compared against the listing.";
|
|
5198
|
+
}
|
|
5199
|
+
seen.add(node);
|
|
5200
|
+
const members = Array.isArray(node) ? node : Object.values(node);
|
|
5201
|
+
for (const member of members) {
|
|
5202
|
+
const error = jsonGraphError(member, seen, depth + 1);
|
|
5203
|
+
if (error) return error;
|
|
5204
|
+
}
|
|
5205
|
+
seen.delete(node);
|
|
5206
|
+
return void 0;
|
|
5207
|
+
}
|
|
5208
|
+
function parseSkillFrontmatter(yamlText) {
|
|
5209
|
+
let parsed;
|
|
5210
|
+
try {
|
|
5211
|
+
parsed = parseYaml(yamlText);
|
|
5212
|
+
} catch (err) {
|
|
5213
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
5214
|
+
}
|
|
5215
|
+
if (parsed === null || parsed === void 0) {
|
|
5216
|
+
return hasContent(yamlText) ? { error: "Frontmatter is not a YAML mapping of fields." } : { fields: {} };
|
|
5217
|
+
}
|
|
5218
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
5219
|
+
return {
|
|
5220
|
+
error: "Frontmatter is not a YAML mapping of fields."
|
|
5221
|
+
};
|
|
5222
|
+
}
|
|
5223
|
+
const graphError = jsonGraphError(parsed, /* @__PURE__ */ new Set(), 0);
|
|
5224
|
+
if (graphError) return { error: graphError };
|
|
5225
|
+
return { fields: parsed };
|
|
5226
|
+
}
|
|
5227
|
+
|
|
5228
|
+
// ../../core/mcp/skills.ts
|
|
5229
|
+
var SKILL_MAX_RESOURCE_ENTRIES = 512;
|
|
5230
|
+
var SKILL_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
|
|
5231
|
+
var SKILL_MAX_CATALOG_SKILLS = 256;
|
|
5232
|
+
var SKILL_MAX_CATALOG_BYTES = 64 * 1024 * 1024;
|
|
5233
|
+
var SKILL_FILE_SUFFIX = "/SKILL.md";
|
|
5234
|
+
var DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
5235
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
5236
|
+
var SKILL_NAME_MAX_LENGTH = 64;
|
|
5237
|
+
var SKILL_DESCRIPTION_MAX_LENGTH = 1024;
|
|
5238
|
+
function codePointLength(value) {
|
|
5239
|
+
return [...value].length;
|
|
5240
|
+
}
|
|
5241
|
+
function getSkillsExtension(capabilities) {
|
|
5242
|
+
const declared = capabilities?.extensions?.[SKILLS_EXTENSION_KEY];
|
|
5243
|
+
if (typeof declared !== "object" || declared === null) return void 0;
|
|
5244
|
+
const directoryRead = declared.directoryRead === true;
|
|
5245
|
+
return { directoryRead };
|
|
5246
|
+
}
|
|
5247
|
+
function normalizeSkillUri(uri) {
|
|
5248
|
+
let parsed;
|
|
5249
|
+
try {
|
|
5250
|
+
parsed = new URL(uri);
|
|
5251
|
+
} catch {
|
|
5252
|
+
return void 0;
|
|
5253
|
+
}
|
|
5254
|
+
if (!parsed.pathname.startsWith("/")) return void 0;
|
|
5255
|
+
return canonicalizePercentEncoding(parsed.href);
|
|
5256
|
+
}
|
|
5257
|
+
function canonicalizePercentEncoding(value) {
|
|
5258
|
+
return value.replace(/%[0-9a-fA-F]{2}/g, (escape) => {
|
|
5259
|
+
const char = String.fromCharCode(Number.parseInt(escape.slice(1), 16));
|
|
5260
|
+
return /[A-Za-z0-9\-._~]/.test(char) ? char : escape.toUpperCase();
|
|
5261
|
+
});
|
|
5262
|
+
}
|
|
5263
|
+
function skillNameFromUri(uri) {
|
|
5264
|
+
const normalized = normalizeSkillUri(uri);
|
|
5265
|
+
if (normalized === void 0 || !normalized.endsWith(SKILL_FILE_SUFFIX)) {
|
|
5266
|
+
return void 0;
|
|
5267
|
+
}
|
|
5268
|
+
const path5 = normalized.slice(0, -SKILL_FILE_SUFFIX.length);
|
|
5269
|
+
const segment = path5.slice(path5.lastIndexOf("/") + 1);
|
|
5270
|
+
return segment.length > 0 ? segment : void 0;
|
|
5271
|
+
}
|
|
5272
|
+
function skillUriIdentity(uri) {
|
|
5273
|
+
return normalizeSkillUri(uri) ?? uri;
|
|
5274
|
+
}
|
|
5275
|
+
function skillDisplayName(entry) {
|
|
5276
|
+
const declared = entry.frontmatter.name?.trim();
|
|
5277
|
+
if (declared) return declared;
|
|
5278
|
+
return skillNameFromUri(entry.uri) ?? entry.uri;
|
|
5279
|
+
}
|
|
5280
|
+
function checkSkillConformance(entry) {
|
|
5281
|
+
const issues = [];
|
|
5282
|
+
const rawName = entry.frontmatter.name;
|
|
5283
|
+
const declaredName = rawName?.trim() ? rawName : void 0;
|
|
5284
|
+
const uriName = skillNameFromUri(entry.uri);
|
|
5285
|
+
if (!declaredName) {
|
|
5286
|
+
issues.push({
|
|
5287
|
+
code: "missing-name",
|
|
5288
|
+
severity: "error",
|
|
5289
|
+
message: "frontmatter.name is required but missing or empty."
|
|
5290
|
+
});
|
|
5291
|
+
} else if (codePointLength(declaredName) > SKILL_NAME_MAX_LENGTH || !SKILL_NAME_PATTERN.test(declaredName)) {
|
|
5292
|
+
issues.push({
|
|
5293
|
+
code: "malformed-name",
|
|
5294
|
+
severity: "error",
|
|
5295
|
+
message: `frontmatter.name "${declaredName}" is not a valid Agent Skills name: 1\u2013${SKILL_NAME_MAX_LENGTH} lowercase alphanumerics and hyphens, with no leading, trailing or consecutive hyphen.`
|
|
5296
|
+
});
|
|
5297
|
+
}
|
|
5298
|
+
const rawDescription = entry.frontmatter.description;
|
|
5299
|
+
if (!rawDescription?.trim()) {
|
|
5300
|
+
issues.push({
|
|
5301
|
+
code: "missing-description",
|
|
5302
|
+
severity: "error",
|
|
5303
|
+
message: "frontmatter.description is required but missing or empty."
|
|
5304
|
+
});
|
|
5305
|
+
} else if (codePointLength(rawDescription) > SKILL_DESCRIPTION_MAX_LENGTH) {
|
|
5306
|
+
issues.push({
|
|
5307
|
+
code: "malformed-description",
|
|
5308
|
+
severity: "error",
|
|
5309
|
+
message: `frontmatter.description is ${codePointLength(rawDescription)} characters, above the ${SKILL_DESCRIPTION_MAX_LENGTH}-character limit.`
|
|
5310
|
+
});
|
|
5311
|
+
}
|
|
5312
|
+
if (uriName === void 0) {
|
|
5313
|
+
issues.push({
|
|
5314
|
+
code: "malformed-uri",
|
|
5315
|
+
severity: "error",
|
|
5316
|
+
message: `Skill URI must be a hierarchical URI ending with "${SKILL_FILE_SUFFIX}" and carrying a non-empty path segment before it.`
|
|
5317
|
+
});
|
|
5318
|
+
} else if (declaredName && uriName !== declaredName) {
|
|
5319
|
+
issues.push({
|
|
5320
|
+
code: "name-path-mismatch",
|
|
5321
|
+
severity: "error",
|
|
5322
|
+
message: `URI path segment "${uriName}" does not match frontmatter.name "${declaredName}".`
|
|
5323
|
+
});
|
|
5324
|
+
}
|
|
5325
|
+
if (entry.resources === DYNAMIC_RESOURCES) {
|
|
5326
|
+
issues.push({
|
|
5327
|
+
code: "dynamic-resources",
|
|
5328
|
+
severity: "warning",
|
|
5329
|
+
message: 'resources is "dynamic": the file set is generated, so no digest is advertised and integrity cannot be verified.'
|
|
5330
|
+
});
|
|
5331
|
+
return issues;
|
|
5332
|
+
}
|
|
5333
|
+
if (entry.resources.length > SKILL_MAX_RESOURCE_ENTRIES) {
|
|
5334
|
+
issues.push({
|
|
5335
|
+
code: "resource-limit-exceeded",
|
|
5336
|
+
severity: "warning",
|
|
5337
|
+
message: `Skill declares ${entry.resources.length} resource entries, above the ${SKILL_MAX_RESOURCE_ENTRIES}-entry interoperability limit; a host is only required to support up to it.`
|
|
5338
|
+
});
|
|
5339
|
+
}
|
|
5340
|
+
const totalBytes = totalSkillBytes(entry.resources);
|
|
5341
|
+
if (totalBytes > SKILL_MAX_TOTAL_BYTES) {
|
|
5342
|
+
issues.push({
|
|
5343
|
+
code: "size-limit-exceeded",
|
|
5344
|
+
severity: "warning",
|
|
5345
|
+
message: `Skill resources total ${totalBytes} bytes, above the ${SKILL_MAX_TOTAL_BYTES}-byte (16 MiB) interoperability limit; a host is only required to support up to it.`
|
|
5346
|
+
});
|
|
5347
|
+
}
|
|
5348
|
+
const entryIdentity = skillUriIdentity(entry.uri);
|
|
5349
|
+
if (!entry.resources.some(
|
|
5350
|
+
(resource) => skillUriIdentity(resource.uri) === entryIdentity
|
|
5351
|
+
)) {
|
|
5352
|
+
issues.push({
|
|
5353
|
+
code: "manifest-missing-self",
|
|
5354
|
+
severity: "error",
|
|
5355
|
+
message: `Manifest does not list the skill's own entry file (${entry.uri}); a manifest must be the complete file set.`
|
|
5356
|
+
});
|
|
5357
|
+
}
|
|
5358
|
+
const seenUris = /* @__PURE__ */ new Set();
|
|
5359
|
+
const normalizedEntryUri = normalizeSkillUri(entry.uri);
|
|
5360
|
+
const root = normalizedEntryUri !== void 0 && normalizedEntryUri.endsWith(SKILL_FILE_SUFFIX) ? `${normalizedEntryUri.slice(0, -SKILL_FILE_SUFFIX.length)}/` : void 0;
|
|
5361
|
+
for (const resource of entry.resources) {
|
|
5362
|
+
const identity2 = skillUriIdentity(resource.uri);
|
|
5363
|
+
if (seenUris.has(identity2)) {
|
|
5364
|
+
issues.push({
|
|
5365
|
+
code: "duplicate-resource",
|
|
5366
|
+
severity: "error",
|
|
5367
|
+
message: "Manifest lists this URI more than once; entries must be unique.",
|
|
5368
|
+
resourceUri: resource.uri
|
|
5369
|
+
});
|
|
5370
|
+
}
|
|
5371
|
+
seenUris.add(identity2);
|
|
5372
|
+
if (root !== void 0) {
|
|
5373
|
+
const normalized = normalizeSkillUri(resource.uri);
|
|
5374
|
+
if (normalized === void 0 || !normalized.startsWith(root)) {
|
|
5375
|
+
issues.push({
|
|
5376
|
+
code: "resource-outside-skill-root",
|
|
5377
|
+
severity: "error",
|
|
5378
|
+
message: `Manifest entry does not resolve inside the skill root "${root}".`,
|
|
5379
|
+
resourceUri: resource.uri
|
|
5380
|
+
});
|
|
5381
|
+
}
|
|
5382
|
+
}
|
|
5383
|
+
if (resource.digest === void 0) {
|
|
5384
|
+
issues.push({
|
|
5385
|
+
code: "missing-digest",
|
|
5386
|
+
severity: "error",
|
|
5387
|
+
message: "Manifest entry declares no digest, which is required \u2014 and without it the file cannot be verified.",
|
|
5388
|
+
resourceUri: resource.uri
|
|
5389
|
+
});
|
|
5390
|
+
} else if (!DIGEST_PATTERN.test(resource.digest)) {
|
|
5391
|
+
issues.push({
|
|
5392
|
+
code: "malformed-digest",
|
|
5393
|
+
severity: "error",
|
|
5394
|
+
message: `Digest "${resource.digest}" is not "sha256:" followed by 64 lowercase hex characters.`,
|
|
5395
|
+
resourceUri: resource.uri
|
|
5396
|
+
});
|
|
5397
|
+
}
|
|
5398
|
+
if (resource.size === void 0) {
|
|
5399
|
+
issues.push({
|
|
5400
|
+
code: "missing-size",
|
|
5401
|
+
severity: "error",
|
|
5402
|
+
message: "Manifest entry declares no size, which is required \u2014 and without it the entry is excluded from the 16 MiB total and its length cannot be cross-checked.",
|
|
5403
|
+
resourceUri: resource.uri
|
|
5404
|
+
});
|
|
5405
|
+
} else if (!isUsableSize(resource.size)) {
|
|
5406
|
+
issues.push({
|
|
5407
|
+
code: "malformed-size",
|
|
5408
|
+
severity: "error",
|
|
5409
|
+
message: `Size ${resource.size} is not a non-negative integer byte length.`,
|
|
5410
|
+
resourceUri: resource.uri
|
|
5411
|
+
});
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
return issues;
|
|
5415
|
+
}
|
|
5416
|
+
function isUsableSize(size) {
|
|
5417
|
+
return size !== void 0 && Number.isSafeInteger(size) && size >= 0;
|
|
5418
|
+
}
|
|
5419
|
+
function totalSkillBytes(resources) {
|
|
5420
|
+
return resources.reduce(
|
|
5421
|
+
(sum, r) => sum + (isUsableSize(r.size) ? r.size : 0),
|
|
5422
|
+
0
|
|
5423
|
+
);
|
|
5424
|
+
}
|
|
5425
|
+
function canonicalize(value) {
|
|
5426
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
5427
|
+
if (value === null || typeof value !== "object") return value;
|
|
5428
|
+
return sortKeys(value);
|
|
5429
|
+
}
|
|
5430
|
+
function sortKeys(value) {
|
|
5431
|
+
return Object.fromEntries(
|
|
5432
|
+
Object.entries(value).map(([key, member]) => [key, canonicalize(member)]).sort(([x], [y]) => x.localeCompare(y))
|
|
5433
|
+
);
|
|
5434
|
+
}
|
|
5435
|
+
function toHex(bytes) {
|
|
5436
|
+
let out = "";
|
|
5437
|
+
for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
|
|
5438
|
+
return out;
|
|
5439
|
+
}
|
|
5440
|
+
async function sha256Digest(bytes) {
|
|
5441
|
+
const subtle = globalThis.crypto?.subtle;
|
|
5442
|
+
if (!subtle) return `sha256:${toHex(sha256Bytes(bytes))}`;
|
|
5443
|
+
const copy = new Uint8Array(bytes);
|
|
5444
|
+
const hash = await subtle.digest("SHA-256", copy.buffer);
|
|
5445
|
+
return `sha256:${toHex(new Uint8Array(hash))}`;
|
|
5446
|
+
}
|
|
5447
|
+
function textToBytes(text) {
|
|
5448
|
+
return new TextEncoder().encode(text);
|
|
5449
|
+
}
|
|
5450
|
+
function bytesToText(bytes) {
|
|
5451
|
+
return new TextDecoder().decode(bytes);
|
|
5452
|
+
}
|
|
5453
|
+
function base64ToBytes(blob) {
|
|
5454
|
+
const binary = atob(blob);
|
|
5455
|
+
const bytes = new Uint8Array(binary.length);
|
|
5456
|
+
for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
|
|
5457
|
+
return bytes;
|
|
5458
|
+
}
|
|
5459
|
+
function skillFileBytes(contents) {
|
|
5460
|
+
if (typeof contents.text === "string") return textToBytes(contents.text);
|
|
5461
|
+
if (typeof contents.blob === "string") return base64ToBytes(contents.blob);
|
|
5462
|
+
throw new Error("resources/read returned neither text nor blob content.");
|
|
5463
|
+
}
|
|
5464
|
+
async function verifySkillResource(resource, bytes) {
|
|
5465
|
+
const expectedSize = resource.size;
|
|
5466
|
+
if (expectedSize !== void 0 && expectedSize !== bytes.byteLength) {
|
|
5467
|
+
return {
|
|
5468
|
+
status: "mismatch",
|
|
5469
|
+
expectedSize,
|
|
5470
|
+
actualSize: bytes.byteLength,
|
|
5471
|
+
...resource.digest !== void 0 ? { expectedDigest: resource.digest } : {},
|
|
5472
|
+
reason: `Manifest declares ${expectedSize} bytes but the fetched file is ${bytes.byteLength}.`
|
|
5473
|
+
};
|
|
5474
|
+
}
|
|
5475
|
+
const expectedDigest = resource.digest;
|
|
5476
|
+
if (expectedDigest === void 0) {
|
|
5477
|
+
return {
|
|
5478
|
+
status: "unverifiable",
|
|
5479
|
+
reason: "The manifest entry advertises no digest."
|
|
5480
|
+
};
|
|
5481
|
+
}
|
|
5482
|
+
if (!DIGEST_PATTERN.test(expectedDigest)) {
|
|
5483
|
+
return {
|
|
5484
|
+
status: "unverifiable",
|
|
5485
|
+
expectedDigest,
|
|
5486
|
+
reason: 'The advertised digest is not "sha256:" followed by 64 lowercase hex characters.'
|
|
5487
|
+
};
|
|
5488
|
+
}
|
|
5489
|
+
const actualDigest = await sha256Digest(bytes);
|
|
5490
|
+
return {
|
|
5491
|
+
status: actualDigest === expectedDigest ? "verified" : "mismatch",
|
|
5492
|
+
actualDigest,
|
|
5493
|
+
expectedDigest,
|
|
5494
|
+
...expectedSize !== void 0 ? { expectedSize, actualSize: bytes.byteLength } : {}
|
|
5495
|
+
};
|
|
5496
|
+
}
|
|
5497
|
+
function jsonLikeEqual(a, b) {
|
|
5498
|
+
if (typeof a === "number" || typeof b === "number") {
|
|
5499
|
+
if (typeof a === "number" && typeof b === "number") {
|
|
5500
|
+
return Number.isFinite(a) && Number.isFinite(b) ? a === b : Object.is(a, b);
|
|
5501
|
+
}
|
|
5502
|
+
return false;
|
|
5503
|
+
}
|
|
5504
|
+
if (a === null || b === null) return a === b;
|
|
5505
|
+
if (typeof a !== "object" || typeof b !== "object") return Object.is(a, b);
|
|
5506
|
+
const aArray = Array.isArray(a);
|
|
5507
|
+
if (aArray !== Array.isArray(b)) return false;
|
|
5508
|
+
if (aArray) {
|
|
5509
|
+
const x2 = a;
|
|
5510
|
+
const y2 = b;
|
|
5511
|
+
return x2.length === y2.length && x2.every((v, i) => jsonLikeEqual(v, y2[i]));
|
|
5512
|
+
}
|
|
5513
|
+
const x = a;
|
|
5514
|
+
const y = b;
|
|
5515
|
+
const keys = Object.keys(x);
|
|
5516
|
+
return keys.length === Object.keys(y).length && keys.every((k) => Object.hasOwn(y, k) && jsonLikeEqual(x[k], y[k]));
|
|
5517
|
+
}
|
|
5518
|
+
function displayValue(value) {
|
|
5519
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
5520
|
+
return String(value);
|
|
5521
|
+
}
|
|
5522
|
+
return JSON.stringify(
|
|
5523
|
+
canonicalize(value),
|
|
5524
|
+
(_key, member) => typeof member === "number" && !Number.isFinite(member) ? `<${String(member)}>` : member
|
|
5525
|
+
);
|
|
5526
|
+
}
|
|
5527
|
+
function checkSkillFrontmatterMatch(entry, skillFileText) {
|
|
5528
|
+
const { frontmatter } = splitSkillFile(skillFileText);
|
|
5529
|
+
if (frontmatter === void 0) {
|
|
5530
|
+
return [
|
|
5531
|
+
{
|
|
5532
|
+
code: "frontmatter-absent",
|
|
5533
|
+
severity: "error",
|
|
5534
|
+
message: "The served SKILL.md carries no YAML frontmatter block, so the listing's frontmatter cannot be the file's.",
|
|
5535
|
+
resourceUri: entry.uri
|
|
5536
|
+
}
|
|
5537
|
+
];
|
|
5538
|
+
}
|
|
5539
|
+
const listedError = jsonGraphError(entry.frontmatter);
|
|
5540
|
+
if (listedError) {
|
|
5541
|
+
return [
|
|
5542
|
+
{
|
|
5543
|
+
code: "frontmatter-unparsable",
|
|
5544
|
+
severity: "error",
|
|
5545
|
+
message: `The listing's own frontmatter cannot be compared: ${listedError}`,
|
|
5546
|
+
resourceUri: entry.uri
|
|
5547
|
+
}
|
|
5548
|
+
];
|
|
5549
|
+
}
|
|
5550
|
+
const parsed = parseSkillFrontmatter(frontmatter);
|
|
5551
|
+
if ("error" in parsed) {
|
|
5552
|
+
return [
|
|
5553
|
+
{
|
|
5554
|
+
code: "frontmatter-unparsable",
|
|
5555
|
+
severity: "error",
|
|
5556
|
+
message: `The served SKILL.md's frontmatter is not valid YAML: ${parsed.error}`,
|
|
5557
|
+
resourceUri: entry.uri
|
|
5558
|
+
}
|
|
5559
|
+
];
|
|
5560
|
+
}
|
|
5561
|
+
const issues = [];
|
|
5562
|
+
const fields = [
|
|
5563
|
+
.../* @__PURE__ */ new Set([
|
|
5564
|
+
...Object.keys(entry.frontmatter),
|
|
5565
|
+
...Object.keys(parsed.fields)
|
|
5566
|
+
])
|
|
5567
|
+
].sort();
|
|
5568
|
+
for (const field of fields) {
|
|
5569
|
+
const listed = entry.frontmatter[field];
|
|
5570
|
+
const served = parsed.fields[field];
|
|
5571
|
+
if (listed === void 0) {
|
|
5572
|
+
issues.push({
|
|
5573
|
+
code: "frontmatter-mismatch",
|
|
5574
|
+
severity: "error",
|
|
5575
|
+
message: `The served SKILL.md declares "${field}" but the listing's frontmatter omits it.`,
|
|
5576
|
+
resourceUri: entry.uri
|
|
5577
|
+
});
|
|
5578
|
+
continue;
|
|
5579
|
+
}
|
|
5580
|
+
if (served === void 0) {
|
|
5581
|
+
issues.push({
|
|
5582
|
+
code: "frontmatter-mismatch",
|
|
5583
|
+
severity: "error",
|
|
5584
|
+
message: `The listing declares "${field}" but the served SKILL.md omits it.`,
|
|
5585
|
+
resourceUri: entry.uri
|
|
5586
|
+
});
|
|
5587
|
+
continue;
|
|
5588
|
+
}
|
|
5589
|
+
if (!jsonLikeEqual(listed, served)) {
|
|
5590
|
+
issues.push({
|
|
5591
|
+
code: "frontmatter-mismatch",
|
|
5592
|
+
severity: "error",
|
|
5593
|
+
message: `Field "${field}" differs: the listing says ${displayValue(listed)} but the served SKILL.md says ${displayValue(served)}.`,
|
|
5594
|
+
resourceUri: entry.uri
|
|
5595
|
+
});
|
|
5596
|
+
}
|
|
5597
|
+
}
|
|
5598
|
+
return issues;
|
|
5599
|
+
}
|
|
5600
|
+
var COLLISION_SAMPLE = 3;
|
|
5601
|
+
function checkSkillNameCollisions(entries) {
|
|
5602
|
+
const byName = /* @__PURE__ */ new Map();
|
|
5603
|
+
for (const entry of entries) {
|
|
5604
|
+
const name = entry.frontmatter.name;
|
|
5605
|
+
if (typeof name !== "string" || name.trim() === "") continue;
|
|
5606
|
+
const group = byName.get(name);
|
|
5607
|
+
if (group) group.push(entry);
|
|
5608
|
+
else byName.set(name, [entry]);
|
|
5609
|
+
}
|
|
5610
|
+
const issues = /* @__PURE__ */ new Map();
|
|
5611
|
+
for (const [name, group] of byName) {
|
|
5612
|
+
const identities = new Set(group.map((e) => skillUriIdentity(e.uri)));
|
|
5613
|
+
if (identities.size < 2) continue;
|
|
5614
|
+
const uris = [...identities].sort();
|
|
5615
|
+
for (const entry of group) {
|
|
5616
|
+
const self = skillUriIdentity(entry.uri);
|
|
5617
|
+
const sample = [];
|
|
5618
|
+
for (const uri of uris) {
|
|
5619
|
+
if (uri === self) continue;
|
|
5620
|
+
sample.push(uri);
|
|
5621
|
+
if (sample.length === COLLISION_SAMPLE) break;
|
|
5622
|
+
}
|
|
5623
|
+
const unshown = identities.size - 1 - sample.length;
|
|
5624
|
+
const others = unshown > 0 ? `${sample.join(", ")}, and ${unshown} more` : sample.join(", ");
|
|
5625
|
+
const subject = identities.size === 2 ? "Another skill in this listing also declares" : `${identities.size - 1} other skills in this listing also declare`;
|
|
5626
|
+
issues.set(self, {
|
|
5627
|
+
code: "duplicate-name",
|
|
5628
|
+
severity: "warning",
|
|
5629
|
+
message: `${subject} the name "${name}" (${others}). This is legal \u2014 a consumer must tell them apart by their URIs rather than collapsing or preferring one.`
|
|
5630
|
+
});
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
return issues;
|
|
5634
|
+
}
|
|
5635
|
+
|
|
4981
5636
|
// ../../core/mcp/appElicitation.ts
|
|
4982
5637
|
import { ElicitResultSchema } from "@modelcontextprotocol/core";
|
|
4983
5638
|
function uiExtension(capabilities) {
|
|
@@ -5065,7 +5720,7 @@ import {
|
|
|
5065
5720
|
ListResourcesResultSchema,
|
|
5066
5721
|
ListResourceTemplatesResultSchema,
|
|
5067
5722
|
ListPromptsResultSchema,
|
|
5068
|
-
ResourceSchema,
|
|
5723
|
+
ResourceSchema as ResourceSchema2,
|
|
5069
5724
|
ResourceTemplateSchema,
|
|
5070
5725
|
PromptSchema
|
|
5071
5726
|
} from "@modelcontextprotocol/core";
|
|
@@ -5088,7 +5743,7 @@ function validateToolOutput(provider, tool, result) {
|
|
|
5088
5743
|
}
|
|
5089
5744
|
|
|
5090
5745
|
// ../../core/mcp/listSalvage.ts
|
|
5091
|
-
import { z as
|
|
5746
|
+
import { z as z3 } from "zod/v4";
|
|
5092
5747
|
import { SdkError as SdkError2, SdkErrorCode as SdkErrorCode2 } from "@modelcontextprotocol/client";
|
|
5093
5748
|
import { ToolSchema } from "@modelcontextprotocol/core";
|
|
5094
5749
|
var LIST_MAX_PAGES = 64;
|
|
@@ -5104,20 +5759,20 @@ function isSalvageableRejection(err) {
|
|
|
5104
5759
|
return SdkError2.isInstance(err) && err.code === SdkErrorCode2.InvalidResult;
|
|
5105
5760
|
}
|
|
5106
5761
|
function lenientListPageSchema(resultSchema, itemsKey) {
|
|
5107
|
-
return resultSchema.extend({ [itemsKey]:
|
|
5762
|
+
return resultSchema.extend({ [itemsKey]: z3.array(z3.unknown()) });
|
|
5108
5763
|
}
|
|
5109
5764
|
function nextCursorOf(page) {
|
|
5110
5765
|
if (typeof page !== "object" || page === null) return void 0;
|
|
5111
5766
|
const value = page.nextCursor;
|
|
5112
5767
|
return typeof value === "string" ? value : void 0;
|
|
5113
5768
|
}
|
|
5114
|
-
var ModernResultEnvelopeSchema =
|
|
5115
|
-
resultType:
|
|
5769
|
+
var ModernResultEnvelopeSchema = z3.looseObject({
|
|
5770
|
+
resultType: z3.literal("complete"),
|
|
5116
5771
|
// A non-negative INTEGER, matching the codec: `ttlMs: -1` or `0.5` is an
|
|
5117
5772
|
// envelope violation the strict path rejects, and accepting it here on the
|
|
5118
5773
|
// strength of an unrelated bad entry would hide it.
|
|
5119
|
-
ttlMs:
|
|
5120
|
-
cacheScope:
|
|
5774
|
+
ttlMs: z3.int().min(0),
|
|
5775
|
+
cacheScope: z3.enum(["public", "private"])
|
|
5121
5776
|
});
|
|
5122
5777
|
function rawItemsOf(page, itemsKey) {
|
|
5123
5778
|
if (typeof page !== "object" || page === null) return void 0;
|
|
@@ -5186,9 +5841,9 @@ var LegacyToolWireSchema = ToolSchema.extend({
|
|
|
5186
5841
|
outputSchema: ToolSchema.shape.inputSchema.optional()
|
|
5187
5842
|
});
|
|
5188
5843
|
var ModernToolWireSchema = ToolSchema.extend({
|
|
5189
|
-
inputSchema:
|
|
5190
|
-
type:
|
|
5191
|
-
$schema:
|
|
5844
|
+
inputSchema: z3.looseObject({
|
|
5845
|
+
type: z3.literal("object"),
|
|
5846
|
+
$schema: z3.string().optional()
|
|
5192
5847
|
})
|
|
5193
5848
|
});
|
|
5194
5849
|
function stripExecution(tool) {
|
|
@@ -5202,10 +5857,10 @@ function eraToolItemSchema(wire, normalize) {
|
|
|
5202
5857
|
|
|
5203
5858
|
// ../../core/mcp/taskNotificationSchemas.ts
|
|
5204
5859
|
import { NotificationSchema } from "@modelcontextprotocol/core";
|
|
5205
|
-
import { z as
|
|
5860
|
+
import { z as z4 } from "zod/v4";
|
|
5206
5861
|
var TasksListChangedNotificationSchema = NotificationSchema.extend({
|
|
5207
|
-
method:
|
|
5208
|
-
params:
|
|
5862
|
+
method: z4.literal("notifications/tasks/list_changed"),
|
|
5863
|
+
params: z4.record(z4.string(), z4.unknown()).optional()
|
|
5209
5864
|
});
|
|
5210
5865
|
|
|
5211
5866
|
// ../../core/mcp/uriTemplate.ts
|
|
@@ -6186,8 +6841,8 @@ var BaseOAuthClientProvider = class {
|
|
|
6186
6841
|
);
|
|
6187
6842
|
}
|
|
6188
6843
|
async saveClientInformation(clientInformation, options) {
|
|
6189
|
-
const registrationKind = options && "registrationKind" in options ? options.registrationKind : "dcr";
|
|
6190
6844
|
const issuer = options && "issuer" in options ? options.issuer : void 0;
|
|
6845
|
+
const registrationKind = options && "registrationKind" in options ? options.registrationKind : await this.resolveSdkRegistrationKind(clientInformation, issuer);
|
|
6191
6846
|
await this.storage.saveClientInformation(
|
|
6192
6847
|
this.serverUrl,
|
|
6193
6848
|
clientInformation,
|
|
@@ -6197,6 +6852,70 @@ var BaseOAuthClientProvider = class {
|
|
|
6197
6852
|
}
|
|
6198
6853
|
);
|
|
6199
6854
|
}
|
|
6855
|
+
/**
|
|
6856
|
+
* Resolve the registration kind for a save that carries no explicit one — that
|
|
6857
|
+
* is, one the SDK made. SDK v2's `saveClientInformation` contract passes only
|
|
6858
|
+
* `{ issuer }`, so the mechanism cannot be handed to us; treating every such
|
|
6859
|
+
* save as DCR is what relabeled a CIMD registration `Dynamic (DCR)` in
|
|
6860
|
+
* Connection Info the moment the SDK bound it to an issuer (#2242).
|
|
6861
|
+
*
|
|
6862
|
+
* Two cases reach here, and they are told apart by whether a registration
|
|
6863
|
+
* already exists for this issuer:
|
|
6864
|
+
*
|
|
6865
|
+
* - **A back-stamp.** A registration is already stored for this issuer under
|
|
6866
|
+
* this `client_id`, and the SDK is only adding the `issuer` to it. Its
|
|
6867
|
+
* recorded kind is the answer — kind and credential are written and cleared
|
|
6868
|
+
* together, so a stored registration always has one.
|
|
6869
|
+
* - **A new registration.** Nothing is stored for this issuer, so this save
|
|
6870
|
+
* creates it. SDK v2 `auth()` reaches its URL-based-client-ID branch — rather
|
|
6871
|
+
* than `registerClient` — exactly when the AS advertises
|
|
6872
|
+
* `client_id_metadata_document_supported` and a `clientMetadataUrl` is
|
|
6873
|
+
* configured, and it persists the AS metadata via `saveDiscoveryState`
|
|
6874
|
+
* *before* it reads or writes client information. So the branch it took is
|
|
6875
|
+
* not inferred here, it is read back from the state it just wrote.
|
|
6876
|
+
*
|
|
6877
|
+
* This is why the check is not "the `client_id` looks like our metadata URL".
|
|
6878
|
+
* RFC 7591 §3.2 makes a dynamically issued `client_id` opaque, so an AS may
|
|
6879
|
+
* mint that very URL from `POST /register`; the URL comparison only decides
|
|
6880
|
+
* whether CIMD is *in play* for this connection, and the two cases above decide
|
|
6881
|
+
* what actually happened (#2242, Copilot).
|
|
6882
|
+
*
|
|
6883
|
+
* Consequences worth stating, since each was a defect on the way here:
|
|
6884
|
+
*
|
|
6885
|
+
* - An existing DCR whose `client_id` happens to be the metadata URL stays
|
|
6886
|
+
* `dcr` — it takes the back-stamp path and its recorded kind says so.
|
|
6887
|
+
* - `invalidateCredentials("client")`, which SDK `auth()` calls on
|
|
6888
|
+
* `invalid_client` before retrying, clears the registration and its kind. The
|
|
6889
|
+
* retry therefore takes the new-registration path and is answered from
|
|
6890
|
+
* discovery state, which that clear does not touch.
|
|
6891
|
+
* - A second AS behind one resource gets its own answer, since discovery state
|
|
6892
|
+
* describes the issuer the SDK actually resolved. One that does not advertise
|
|
6893
|
+
* CIMD is `dcr` even when it mints the metadata URL as its `client_id`.
|
|
6894
|
+
* - A transient failure in our own CIMD preflight costs nothing: the SDK's own
|
|
6895
|
+
* discovery is what this reads.
|
|
6896
|
+
*/
|
|
6897
|
+
async resolveSdkRegistrationKind(clientInformation, issuer) {
|
|
6898
|
+
const clientMetadataUrl = this.clientMetadataUrl?.trim();
|
|
6899
|
+
if (!clientMetadataUrl || clientInformation.client_id !== clientMetadataUrl) {
|
|
6900
|
+
return "dcr";
|
|
6901
|
+
}
|
|
6902
|
+
const stored = await this.storage.getClientInformation(
|
|
6903
|
+
this.serverUrl,
|
|
6904
|
+
false,
|
|
6905
|
+
issuer
|
|
6906
|
+
);
|
|
6907
|
+
if (stored?.client_id === clientMetadataUrl) {
|
|
6908
|
+
const storedKind = await this.storage.getClientRegistrationKind(
|
|
6909
|
+
this.serverUrl,
|
|
6910
|
+
issuer
|
|
6911
|
+
);
|
|
6912
|
+
return storedKind === "cimd" ? "cimd" : "dcr";
|
|
6913
|
+
}
|
|
6914
|
+
const discovery = await this.storage.getDiscoveryState(this.serverUrl);
|
|
6915
|
+
const metadata = discovery?.authorizationServerMetadata;
|
|
6916
|
+
if (issuer !== void 0 && metadata?.issuer !== issuer) return "dcr";
|
|
6917
|
+
return metadata?.client_id_metadata_document_supported === true ? "cimd" : "dcr";
|
|
6918
|
+
}
|
|
6200
6919
|
async saveScope(scope) {
|
|
6201
6920
|
await this.storage.saveScope(this.serverUrl, scope);
|
|
6202
6921
|
this.cachedScope = scope;
|
|
@@ -7075,35 +7794,49 @@ import { discoverOAuthProtectedResourceMetadata as discoverOAuthProtectedResourc
|
|
|
7075
7794
|
async function ensureCimdClientRegistration(params) {
|
|
7076
7795
|
const clientMetadataUrl = params.provider.clientMetadataUrl?.trim();
|
|
7077
7796
|
if (!clientMetadataUrl) return;
|
|
7078
|
-
|
|
7079
|
-
if (
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7797
|
+
let metadata = (await params.provider.discoveryState())?.authorizationServerMetadata;
|
|
7798
|
+
if (!metadata) {
|
|
7799
|
+
let resourceMetadata;
|
|
7800
|
+
try {
|
|
7801
|
+
resourceMetadata = await discoverOAuthProtectedResourceMetadata2(
|
|
7802
|
+
params.serverUrl,
|
|
7803
|
+
{ resourceMetadataUrl: params.resourceMetadataUrl },
|
|
7804
|
+
// The same fetch the AS-metadata leg below uses. On web that is
|
|
7805
|
+
// `createRemoteFetch`, which proxies through the backend to sidestep
|
|
7806
|
+
// CORS — on the global `fetch` this leg would fail in the browser, be
|
|
7807
|
+
// swallowed by the catch, and leave CIMD probing the wrong
|
|
7808
|
+
// authorization server (Copilot).
|
|
7809
|
+
params.fetchFn
|
|
7810
|
+
);
|
|
7811
|
+
} catch {
|
|
7812
|
+
resourceMetadata = void 0;
|
|
7813
|
+
}
|
|
7814
|
+
try {
|
|
7815
|
+
metadata = await discoverAuthorizationServerMetadataForServer(
|
|
7816
|
+
params.serverUrl,
|
|
7817
|
+
resourceMetadata,
|
|
7818
|
+
params.fetchFn
|
|
7819
|
+
);
|
|
7820
|
+
} catch {
|
|
7821
|
+
return;
|
|
7822
|
+
}
|
|
7094
7823
|
}
|
|
7095
|
-
const
|
|
7096
|
-
params.serverUrl,
|
|
7097
|
-
resourceMetadata,
|
|
7098
|
-
params.fetchFn
|
|
7099
|
-
);
|
|
7824
|
+
const issuer = metadata?.issuer;
|
|
7100
7825
|
if (!metadata?.client_id_metadata_document_supported) return;
|
|
7101
|
-
const
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7826
|
+
const existing = await params.provider.clientInformation(
|
|
7827
|
+
issuer ? { issuer } : void 0
|
|
7828
|
+
);
|
|
7829
|
+
if (existing?.client_id) return;
|
|
7830
|
+
await params.provider.saveClientInformation(
|
|
7831
|
+
{ client_id: clientMetadataUrl },
|
|
7832
|
+
{
|
|
7833
|
+
registrationKind: "cimd",
|
|
7834
|
+
// An AS metadata document without an `issuer` is malformed (RFC 8414 §2),
|
|
7835
|
+
// but the type allows it; fall back to the unkeyed slot rather than
|
|
7836
|
+
// inventing a key.
|
|
7837
|
+
...issuer && { issuer }
|
|
7838
|
+
}
|
|
7839
|
+
);
|
|
7107
7840
|
}
|
|
7108
7841
|
|
|
7109
7842
|
// ../../core/auth/ema/transportProvider.ts
|
|
@@ -7130,11 +7863,26 @@ var EmaTransportOAuthProvider = class {
|
|
|
7130
7863
|
state() {
|
|
7131
7864
|
return this.inner.state();
|
|
7132
7865
|
}
|
|
7133
|
-
|
|
7134
|
-
|
|
7866
|
+
// SEP-2352: `ctx` carries the authorization-server `issuer` the SDK resolved,
|
|
7867
|
+
// and the inner provider keys registrations by it. Dropping it here made every
|
|
7868
|
+
// EMA read and write land on the unkeyed slot — and, since #2242, left
|
|
7869
|
+
// `resolveSdkRegistrationKind` with no issuer to check, so a CIMD registration
|
|
7870
|
+
// made over an EMA connection was recorded as DCR (Copilot).
|
|
7871
|
+
clientInformation(ctx) {
|
|
7872
|
+
return this.inner.clientInformation(ctx);
|
|
7873
|
+
}
|
|
7874
|
+
saveClientInformation(clientInformation, ctx) {
|
|
7875
|
+
return this.inner.saveClientInformation(clientInformation, ctx);
|
|
7135
7876
|
}
|
|
7136
|
-
|
|
7137
|
-
|
|
7877
|
+
// Without these the SDK persists no discovery state for an EMA connection, so
|
|
7878
|
+
// it re-discovers on every call, cannot perform its SEP-2352 callback-leg
|
|
7879
|
+
// authorization-server binding check (it warns as much), and — since #2242 —
|
|
7880
|
+
// leaves the registration-kind resolver nothing to read back.
|
|
7881
|
+
saveDiscoveryState(state) {
|
|
7882
|
+
return this.inner.saveDiscoveryState(state);
|
|
7883
|
+
}
|
|
7884
|
+
discoveryState() {
|
|
7885
|
+
return this.inner.discoveryState();
|
|
7138
7886
|
}
|
|
7139
7887
|
async tokens() {
|
|
7140
7888
|
const stored = await this.inner.tokens();
|
|
@@ -7203,8 +7951,11 @@ function issuerMismatchTitle() {
|
|
|
7203
7951
|
return "Authorization server mismatch";
|
|
7204
7952
|
}
|
|
7205
7953
|
var MAX_DISPLAYED_ISSUER_LENGTH = 120;
|
|
7954
|
+
function truncateUrlForDisplay(url) {
|
|
7955
|
+
return url.length > MAX_DISPLAYED_ISSUER_LENGTH ? `${url.slice(0, MAX_DISPLAYED_ISSUER_LENGTH)}\u2026` : url;
|
|
7956
|
+
}
|
|
7206
7957
|
function truncateIssuerForDisplay(issuer) {
|
|
7207
|
-
return issuer
|
|
7958
|
+
return truncateUrlForDisplay(issuer);
|
|
7208
7959
|
}
|
|
7209
7960
|
function issuerMismatchMessage(options) {
|
|
7210
7961
|
const target = options.serverName ? `"${options.serverName}"` : "this server";
|
|
@@ -10418,7 +11169,13 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
10418
11169
|
throw new Error("Client is not connected");
|
|
10419
11170
|
}
|
|
10420
11171
|
const result = await this.client.request(
|
|
10421
|
-
{
|
|
11172
|
+
{
|
|
11173
|
+
method: "tasks/list",
|
|
11174
|
+
// `!== undefined`, not truthiness: a cursor is opaque and `""` is a
|
|
11175
|
+
// legal value a server may hand back. Dropping it asks for page one
|
|
11176
|
+
// again, so a caller walking pages would loop on the first page.
|
|
11177
|
+
params: cursor !== void 0 ? { cursor } : {}
|
|
11178
|
+
},
|
|
10422
11179
|
ListTasksResultSchema,
|
|
10423
11180
|
this.getRequestOptions()
|
|
10424
11181
|
);
|
|
@@ -12012,7 +12769,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12012
12769
|
const effectiveMeta = this.mergeMeta(metadata);
|
|
12013
12770
|
const params = {
|
|
12014
12771
|
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
12015
|
-
|
|
12772
|
+
// `!== undefined`, not truthiness: a cursor is opaque and `""` is a
|
|
12773
|
+
// legal value a server may hand back. Dropping it asks for page one
|
|
12774
|
+
// again, so a caller walking pages would loop on the first page.
|
|
12775
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
12016
12776
|
};
|
|
12017
12777
|
const response = await this.invokeMcpClient(
|
|
12018
12778
|
() => this.client.request(
|
|
@@ -12040,7 +12800,7 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12040
12800
|
method: "resources/list",
|
|
12041
12801
|
itemsKey: "resources",
|
|
12042
12802
|
resultSchema: ListResourcesResultSchema,
|
|
12043
|
-
itemSchema:
|
|
12803
|
+
itemSchema: ResourceSchema2,
|
|
12044
12804
|
metadata: options?.metadata,
|
|
12045
12805
|
aggregate: async () => [
|
|
12046
12806
|
...(await this.client.listResources(
|
|
@@ -12142,7 +12902,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12142
12902
|
const effectiveMeta = this.mergeMeta(metadata);
|
|
12143
12903
|
const params = {
|
|
12144
12904
|
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
12145
|
-
|
|
12905
|
+
// `!== undefined`, not truthiness: a cursor is opaque and `""` is a
|
|
12906
|
+
// legal value a server may hand back. Dropping it asks for page one
|
|
12907
|
+
// again, so a caller walking pages would loop on the first page.
|
|
12908
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
12146
12909
|
};
|
|
12147
12910
|
const response = await this.invokeMcpClient(
|
|
12148
12911
|
() => this.client.request(
|
|
@@ -12195,7 +12958,10 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12195
12958
|
const effectiveMeta = this.mergeMeta(metadata);
|
|
12196
12959
|
const params = {
|
|
12197
12960
|
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
12198
|
-
|
|
12961
|
+
// `!== undefined`, not truthiness: a cursor is opaque and `""` is a
|
|
12962
|
+
// legal value a server may hand back. Dropping it asks for page one
|
|
12963
|
+
// again, so a caller walking pages would loop on the first page.
|
|
12964
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
12199
12965
|
};
|
|
12200
12966
|
const response = await this.invokeMcpClient(
|
|
12201
12967
|
() => this.client.request(
|
|
@@ -12234,6 +13000,155 @@ var InspectorClient = class _InspectorClient extends InspectorClientEventTarget
|
|
|
12234
13000
|
});
|
|
12235
13001
|
return { prompts };
|
|
12236
13002
|
}
|
|
13003
|
+
/**
|
|
13004
|
+
* The Skills extension (SEP-2640) the server declared, or `undefined` when it
|
|
13005
|
+
* declared none. Gates the Skills tab and the skills store the same way
|
|
13006
|
+
* {@link isTasksExtensionNegotiated} gates Tasks — but deliberately without an
|
|
13007
|
+
* era check: `skills/*` are not spec method names in either codec, so a
|
|
13008
|
+
* legacy-era server that declares the extension is serving it (#2234).
|
|
13009
|
+
*/
|
|
13010
|
+
getSkillsExtension() {
|
|
13011
|
+
return getSkillsExtension(this.capabilities);
|
|
13012
|
+
}
|
|
13013
|
+
/**
|
|
13014
|
+
* One page of `skills/list` (SEP-2640).
|
|
13015
|
+
*
|
|
13016
|
+
* An ordinary `client.request` with an explicit result schema — the SDK's era
|
|
13017
|
+
* gate skips methods neither codec defines, and `assertCapabilityForMethod`
|
|
13018
|
+
* falls through to a no-op for them, so this is all the extension needs. The
|
|
13019
|
+
* raw-wire path modern `tasks/*` uses would be wrong here: it exists for spec
|
|
13020
|
+
* names the 2026 codec deleted, and taking it would bypass the SDK's response
|
|
13021
|
+
* correlation for nothing (#2234).
|
|
13022
|
+
*/
|
|
13023
|
+
async listSkills(cursor, metadata) {
|
|
13024
|
+
if (!this.client) {
|
|
13025
|
+
throw new Error("Client is not connected");
|
|
13026
|
+
}
|
|
13027
|
+
const effectiveMeta = this.mergeMeta(metadata);
|
|
13028
|
+
const params = {
|
|
13029
|
+
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
13030
|
+
// `!== undefined`, not truthiness: a cursor is opaque and the empty
|
|
13031
|
+
// string is a legal value. Dropping `""` would silently re-request page
|
|
13032
|
+
// one, which the store then reports as a repeated-cursor failure — a
|
|
13033
|
+
// conforming server made to look broken.
|
|
13034
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
13035
|
+
};
|
|
13036
|
+
const resultSchema = this.isModernEra() ? ModernListSkillsResultSchema : ListSkillsResultSchema;
|
|
13037
|
+
const response = await this.invokeMcpClient(
|
|
13038
|
+
() => this.client.request(
|
|
13039
|
+
{ method: SKILLS_LIST_METHOD, params },
|
|
13040
|
+
resultSchema,
|
|
13041
|
+
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
13042
|
+
),
|
|
13043
|
+
{ method: SKILLS_LIST_METHOD }
|
|
13044
|
+
);
|
|
13045
|
+
return {
|
|
13046
|
+
skills: response.skills,
|
|
13047
|
+
nextCursor: response.nextCursor
|
|
13048
|
+
};
|
|
13049
|
+
}
|
|
13050
|
+
/**
|
|
13051
|
+
* One skill entry by URI (`skills/get`, SEP-2640). The result envelope is
|
|
13052
|
+
* required — `GetSkillEnvelopeSchema` requires `{ skill }` and rejects an entry
|
|
13053
|
+
* returned inline, so a non-conforming shape fails here rather than being
|
|
13054
|
+
* silently normalized past the conformance checks.
|
|
13055
|
+
*/
|
|
13056
|
+
async getSkill(uri, metadata) {
|
|
13057
|
+
return (await this.getSkillResult(uri, metadata)).skill;
|
|
13058
|
+
}
|
|
13059
|
+
/**
|
|
13060
|
+
* `skills/get` as the server sent it — the `{ skill }` envelope **and any
|
|
13061
|
+
* other members it carried**.
|
|
13062
|
+
*
|
|
13063
|
+
* Separate from {@link getSkill} because the callers differ: the UIs want the
|
|
13064
|
+
* entry, while the CLI prints the result and must not reshape it. SEP-2640
|
|
13065
|
+
* explicitly leaves open whether this result carries `ttlMs` / `cacheScope`,
|
|
13066
|
+
* so a server may send them — and unwrapping to the entry discards exactly
|
|
13067
|
+
* those (Copilot).
|
|
13068
|
+
*/
|
|
13069
|
+
async getSkillResult(uri, metadata) {
|
|
13070
|
+
if (!this.client) {
|
|
13071
|
+
throw new Error("Client is not connected");
|
|
13072
|
+
}
|
|
13073
|
+
const effectiveMeta = this.mergeMeta(metadata);
|
|
13074
|
+
const params = {
|
|
13075
|
+
uri,
|
|
13076
|
+
...effectiveMeta ? { _meta: effectiveMeta } : {}
|
|
13077
|
+
};
|
|
13078
|
+
const resultSchema = this.isModernEra() ? ModernGetSkillEnvelopeSchema : GetSkillEnvelopeSchema;
|
|
13079
|
+
try {
|
|
13080
|
+
return await this.invokeMcpClient(
|
|
13081
|
+
() => this.client.request(
|
|
13082
|
+
{ method: SKILLS_GET_METHOD, params },
|
|
13083
|
+
resultSchema,
|
|
13084
|
+
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
13085
|
+
),
|
|
13086
|
+
{ method: SKILLS_GET_METHOD }
|
|
13087
|
+
);
|
|
13088
|
+
} catch (err) {
|
|
13089
|
+
if (isClientDecodeRejection(err)) {
|
|
13090
|
+
this.markResponseRejected(
|
|
13091
|
+
SKILLS_GET_METHOD,
|
|
13092
|
+
err instanceof Error ? err.message : String(err)
|
|
13093
|
+
);
|
|
13094
|
+
}
|
|
13095
|
+
throw err;
|
|
13096
|
+
}
|
|
13097
|
+
}
|
|
13098
|
+
/**
|
|
13099
|
+
* One page of `resources/directory/read` (SEP-2640): the direct children of
|
|
13100
|
+
* a directory resource.
|
|
13101
|
+
*
|
|
13102
|
+
* **Gated on the server's own declaration, and the gate throws rather than
|
|
13103
|
+
* asks.** SEP-2640 is explicit that *"clients MUST NOT call
|
|
13104
|
+
* `resources/directory/read` against a server that has not declared
|
|
13105
|
+
* `directoryRead: true`"*, so this refuses locally instead of sending a call
|
|
13106
|
+
* the spec forbids and letting the server answer `-32601`. Refusing here also
|
|
13107
|
+
* keeps the Protocol tab honest: a request we were never allowed to make
|
|
13108
|
+
* should not appear in the exchange log as a server-side failure.
|
|
13109
|
+
*
|
|
13110
|
+
* Not recursive — the SEP says clients descend by calling the method again on
|
|
13111
|
+
* a child directory, so the walking is the caller's, not this method's.
|
|
13112
|
+
*/
|
|
13113
|
+
async readResourceDirectory(uri, cursor, metadata) {
|
|
13114
|
+
if (!this.client) {
|
|
13115
|
+
throw new Error("Client is not connected");
|
|
13116
|
+
}
|
|
13117
|
+
const extension = this.getSkillsExtension();
|
|
13118
|
+
if (!extension?.directoryRead) {
|
|
13119
|
+
throw new Error(
|
|
13120
|
+
`Server did not declare directoryRead in ${SKILLS_EXTENSION_KEY}; ${RESOURCES_DIRECTORY_READ_METHOD} must not be called.`
|
|
13121
|
+
);
|
|
13122
|
+
}
|
|
13123
|
+
const effectiveMeta = this.mergeMeta(metadata);
|
|
13124
|
+
const params = {
|
|
13125
|
+
uri,
|
|
13126
|
+
...effectiveMeta ? { _meta: effectiveMeta } : {},
|
|
13127
|
+
// `!== undefined` for the same reason `listSkills` uses it: a cursor is
|
|
13128
|
+
// opaque and `""` is a legal value, so truthiness would silently re-ask
|
|
13129
|
+
// for page one.
|
|
13130
|
+
...cursor !== void 0 ? { cursor } : {}
|
|
13131
|
+
};
|
|
13132
|
+
const resultSchema = this.isModernEra() ? ModernDirectoryReadResultSchema : DirectoryReadResultSchema;
|
|
13133
|
+
try {
|
|
13134
|
+
return await this.invokeMcpClient(
|
|
13135
|
+
() => this.client.request(
|
|
13136
|
+
{ method: RESOURCES_DIRECTORY_READ_METHOD, params },
|
|
13137
|
+
resultSchema,
|
|
13138
|
+
this.getRequestOptions(this.progressTokenOf(metadata))
|
|
13139
|
+
),
|
|
13140
|
+
{ method: RESOURCES_DIRECTORY_READ_METHOD }
|
|
13141
|
+
);
|
|
13142
|
+
} catch (err) {
|
|
13143
|
+
if (isClientDecodeRejection(err)) {
|
|
13144
|
+
this.markResponseRejected(
|
|
13145
|
+
RESOURCES_DIRECTORY_READ_METHOD,
|
|
13146
|
+
err instanceof Error ? err.message : String(err)
|
|
13147
|
+
);
|
|
13148
|
+
}
|
|
13149
|
+
throw err;
|
|
13150
|
+
}
|
|
13151
|
+
}
|
|
12237
13152
|
/**
|
|
12238
13153
|
* Get a prompt by name
|
|
12239
13154
|
* @param name Prompt name
|
|
@@ -13259,7 +14174,30 @@ var EXIT_CODES = {
|
|
|
13259
14174
|
AUTH_REQUIRED: 3,
|
|
13260
14175
|
UNREACHABLE: 4,
|
|
13261
14176
|
TOOL_ERROR: 5,
|
|
13262
|
-
SCHEMA_UNPORTABLE: 6
|
|
14177
|
+
SCHEMA_UNPORTABLE: 6,
|
|
14178
|
+
/**
|
|
14179
|
+
* `--verify` found a SEP-2640 violation: a conformance error, a digest or
|
|
14180
|
+
* size mismatch, or a manifest file that could not be read (#2248).
|
|
14181
|
+
*
|
|
14182
|
+
* Its own code rather than reusing `SCHEMA_UNPORTABLE`, for the reason that
|
|
14183
|
+
* one exists at all: a CI job that fails on an unportable tool schema and a
|
|
14184
|
+
* CI job that fails on a tampered skill digest are different jobs, and
|
|
14185
|
+
* collapsing them would make `if [ $? -eq 6 ]` ambiguous.
|
|
14186
|
+
*/
|
|
14187
|
+
SKILL_NONCONFORMANT: 7,
|
|
14188
|
+
/**
|
|
14189
|
+
* `--verify` could not check the whole catalog: the read bounds stopped the
|
|
14190
|
+
* walk before it finished (#2248).
|
|
14191
|
+
*
|
|
14192
|
+
* Distinct from `SKILL_NONCONFORMANT` because the server has broken no
|
|
14193
|
+
* **MUST** — SEP-2640 states the 512-entry and 16 MiB limits as SHOULD NOT,
|
|
14194
|
+
* with hosts free to support more — so exiting 7 would call a conforming
|
|
14195
|
+
* server nonconformant. It is still non-zero, because reporting success for
|
|
14196
|
+
* a manifest whose unread entries were never fetched is a false pass. A CI
|
|
14197
|
+
* job that wants to tolerate oversized catalogs can allow 8 and still fail
|
|
14198
|
+
* on 7.
|
|
14199
|
+
*/
|
|
14200
|
+
SKILL_INCOMPLETE: 8
|
|
13263
14201
|
};
|
|
13264
14202
|
var CliExitCodeError = class extends Error {
|
|
13265
14203
|
constructor(exitCode, message, envelope) {
|
|
@@ -13767,6 +14705,15 @@ async function consumeMethodOutcome(outcome, args) {
|
|
|
13767
14705
|
for (const line of outcome.lines) {
|
|
13768
14706
|
await awaitableLog(JSON.stringify(line) + "\n");
|
|
13769
14707
|
}
|
|
14708
|
+
if (outcome.summary) await awaitableError(`${outcome.summary}
|
|
14709
|
+
`);
|
|
14710
|
+
if (outcome.exitCode) {
|
|
14711
|
+
throw new CliExitCodeError(outcome.exitCode, outcome.summary ?? "", {
|
|
14712
|
+
// The envelope's `code` follows the exit code, so a caller reading one
|
|
14713
|
+
// never has to reconcile it against the other.
|
|
14714
|
+
code: outcome.exitCode === EXIT_CODES.SKILL_INCOMPLETE ? "skills_incomplete" : "skills_nonconformant"
|
|
14715
|
+
});
|
|
14716
|
+
}
|
|
13770
14717
|
return;
|
|
13771
14718
|
}
|
|
13772
14719
|
await new Promise((resolve7) => {
|
|
@@ -14332,7 +15279,7 @@ var ManagedRequestorTasksState = class extends TypedEventTarget {
|
|
|
14332
15279
|
const page = result.tasks.filter((t) => !this.dismissedTaskIds.has(t.taskId)).map(
|
|
14333
15280
|
(t) => this.cancelledTaskIds.has(t.taskId) ? { ...t, status: "cancelled" } : t
|
|
14334
15281
|
);
|
|
14335
|
-
this.tasks = cursor ? [...this.tasks, ...page]
|
|
15282
|
+
this.tasks = cursor === void 0 ? page : [...this.tasks, ...page];
|
|
14336
15283
|
cursor = result.nextCursor;
|
|
14337
15284
|
pageCount++;
|
|
14338
15285
|
if (pageCount >= MAX_PAGES) {
|
|
@@ -14340,7 +15287,7 @@ var ManagedRequestorTasksState = class extends TypedEventTarget {
|
|
|
14340
15287
|
`Maximum pagination limit (${MAX_PAGES} pages) reached while listing requestor tasks`
|
|
14341
15288
|
);
|
|
14342
15289
|
}
|
|
14343
|
-
} while (cursor);
|
|
15290
|
+
} while (cursor !== void 0);
|
|
14344
15291
|
this.dispatchTypedEvent("tasksChange", this.tasks);
|
|
14345
15292
|
return this.getTasks();
|
|
14346
15293
|
}
|
|
@@ -14393,6 +15340,146 @@ var ManagedRequestorTasksState = class extends TypedEventTarget {
|
|
|
14393
15340
|
}
|
|
14394
15341
|
};
|
|
14395
15342
|
|
|
15343
|
+
// ../../core/mcp/state/managedSkillsState.ts
|
|
15344
|
+
var REPEATED_CURSOR_MESSAGE = "Server repeated a pagination cursor in skills/list; stopped to avoid an infinite walk.";
|
|
15345
|
+
var SKILLS_MAX_PAGES = LIST_MAX_PAGES;
|
|
15346
|
+
var SKILLS_PAGE_LIMIT_MESSAGE = `skills/list exceeded ${SKILLS_MAX_PAGES} pages without the server's pagination converging`;
|
|
15347
|
+
var ManagedSkillsState = class extends TypedEventTarget {
|
|
15348
|
+
skills = [];
|
|
15349
|
+
pageCount = 0;
|
|
15350
|
+
error = null;
|
|
15351
|
+
client = null;
|
|
15352
|
+
unsubscribe = null;
|
|
15353
|
+
// Overlap guard, held as the generation whose walk is in flight (or `null`).
|
|
15354
|
+
// See `refresh` for why this is not a boolean.
|
|
15355
|
+
runningGeneration = null;
|
|
15356
|
+
// Session counter, advanced by `reset` (disconnect) and by `destroy`. A walk
|
|
15357
|
+
// captures it and abandons its writes when it no longer matches.
|
|
15358
|
+
generation = 0;
|
|
15359
|
+
constructor(client) {
|
|
15360
|
+
super();
|
|
15361
|
+
this.client = client;
|
|
15362
|
+
const onConnect = () => {
|
|
15363
|
+
void this.refresh().catch(() => {
|
|
15364
|
+
});
|
|
15365
|
+
};
|
|
15366
|
+
const onStatusChange = () => {
|
|
15367
|
+
if (isTerminalStatus(this.client?.getStatus())) {
|
|
15368
|
+
this.reset();
|
|
15369
|
+
}
|
|
15370
|
+
};
|
|
15371
|
+
this.client.addEventListener("connect", onConnect);
|
|
15372
|
+
this.client.addEventListener("statusChange", onStatusChange);
|
|
15373
|
+
this.unsubscribe = () => {
|
|
15374
|
+
if (this.client) {
|
|
15375
|
+
this.client.removeEventListener("connect", onConnect);
|
|
15376
|
+
this.client.removeEventListener("statusChange", onStatusChange);
|
|
15377
|
+
}
|
|
15378
|
+
this.client = null;
|
|
15379
|
+
};
|
|
15380
|
+
}
|
|
15381
|
+
/** Defensive copy of the current list. */
|
|
15382
|
+
getSkills() {
|
|
15383
|
+
return [...this.skills];
|
|
15384
|
+
}
|
|
15385
|
+
getPagination() {
|
|
15386
|
+
return { pageCount: this.pageCount };
|
|
15387
|
+
}
|
|
15388
|
+
/** The last walk's failure, or `null` when it succeeded. */
|
|
15389
|
+
getError() {
|
|
15390
|
+
return this.error;
|
|
15391
|
+
}
|
|
15392
|
+
// Compared by identity rather than message: two distinct failures with the
|
|
15393
|
+
// same text are still two events, and a re-render on a repeat failure is
|
|
15394
|
+
// cheap next to silently coalescing them.
|
|
15395
|
+
setError(value) {
|
|
15396
|
+
if (this.error === value) return;
|
|
15397
|
+
this.error = value;
|
|
15398
|
+
this.dispatchTypedEvent("errorChange", value);
|
|
15399
|
+
}
|
|
15400
|
+
reset() {
|
|
15401
|
+
this.generation += 1;
|
|
15402
|
+
this.skills = [];
|
|
15403
|
+
this.pageCount = 0;
|
|
15404
|
+
this.dispatchTypedEvent("skillsChange", this.getSkills());
|
|
15405
|
+
this.dispatchTypedEvent("paginationChange", this.getPagination());
|
|
15406
|
+
this.setError(null);
|
|
15407
|
+
}
|
|
15408
|
+
/**
|
|
15409
|
+
* Walk every page of `skills/list` and commit the result.
|
|
15410
|
+
*
|
|
15411
|
+
* A failure is recorded as observable state (`getError`) AND re-thrown: the
|
|
15412
|
+
* state drives the panel's error rendering, while the rejection is what a
|
|
15413
|
+
* caller's auth-recovery wrapper keys off to detect a 401 and start a
|
|
15414
|
+
* re-authorization. The connect-time load, which has no such caller, catches
|
|
15415
|
+
* it above.
|
|
15416
|
+
*/
|
|
15417
|
+
async refresh(metadata) {
|
|
15418
|
+
const client = this.client;
|
|
15419
|
+
if (!client || client.getStatus() !== "connected") return this.getSkills();
|
|
15420
|
+
if (!client.getSkillsExtension()) {
|
|
15421
|
+
this.applyPages([], 0);
|
|
15422
|
+
return this.getSkills();
|
|
15423
|
+
}
|
|
15424
|
+
const generation = this.generation;
|
|
15425
|
+
const isCurrent = () => this.generation === generation;
|
|
15426
|
+
if (this.runningGeneration === generation) return this.getSkills();
|
|
15427
|
+
this.runningGeneration = generation;
|
|
15428
|
+
try {
|
|
15429
|
+
const collected = [];
|
|
15430
|
+
const seen = /* @__PURE__ */ new Set();
|
|
15431
|
+
let cursor;
|
|
15432
|
+
let pages = 0;
|
|
15433
|
+
for (; ; ) {
|
|
15434
|
+
const page = await client.listSkills(cursor, metadata);
|
|
15435
|
+
if (!isCurrent()) return this.getSkills();
|
|
15436
|
+
collected.push(...page.skills);
|
|
15437
|
+
pages += 1;
|
|
15438
|
+
if (page.nextCursor === void 0) break;
|
|
15439
|
+
if (pages >= SKILLS_MAX_PAGES) {
|
|
15440
|
+
throw new Error(SKILLS_PAGE_LIMIT_MESSAGE);
|
|
15441
|
+
}
|
|
15442
|
+
if (seen.has(page.nextCursor)) {
|
|
15443
|
+
throw new Error(REPEATED_CURSOR_MESSAGE);
|
|
15444
|
+
}
|
|
15445
|
+
seen.add(page.nextCursor);
|
|
15446
|
+
cursor = page.nextCursor;
|
|
15447
|
+
}
|
|
15448
|
+
this.setError(null);
|
|
15449
|
+
this.applyPages(collected, pages);
|
|
15450
|
+
return this.getSkills();
|
|
15451
|
+
} catch (err) {
|
|
15452
|
+
if (isCurrent()) {
|
|
15453
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
15454
|
+
this.setError(error);
|
|
15455
|
+
if (isClientDecodeRejection(err)) {
|
|
15456
|
+
client.markResponseRejected?.(SKILLS_LIST_METHOD, error.message);
|
|
15457
|
+
}
|
|
15458
|
+
}
|
|
15459
|
+
throw err;
|
|
15460
|
+
} finally {
|
|
15461
|
+
if (this.runningGeneration === generation) {
|
|
15462
|
+
this.runningGeneration = null;
|
|
15463
|
+
}
|
|
15464
|
+
}
|
|
15465
|
+
}
|
|
15466
|
+
applyPages(skills, pageCount) {
|
|
15467
|
+
this.skills = skills;
|
|
15468
|
+
this.pageCount = pageCount;
|
|
15469
|
+
this.dispatchTypedEvent("skillsChange", this.getSkills());
|
|
15470
|
+
this.dispatchTypedEvent("paginationChange", this.getPagination());
|
|
15471
|
+
}
|
|
15472
|
+
/** Unsubscribe from the client and drop the list; idempotent. */
|
|
15473
|
+
destroy() {
|
|
15474
|
+
this.generation += 1;
|
|
15475
|
+
this.unsubscribe?.();
|
|
15476
|
+
this.unsubscribe = null;
|
|
15477
|
+
this.skills = [];
|
|
15478
|
+
this.pageCount = 0;
|
|
15479
|
+
this.error = null;
|
|
15480
|
+
}
|
|
15481
|
+
};
|
|
15482
|
+
|
|
14396
15483
|
// ../../core/mcp/apps.ts
|
|
14397
15484
|
import { getToolUiResourceUri } from "@modelcontextprotocol/ext-apps/app-bridge";
|
|
14398
15485
|
function getAppResourceUri(tool) {
|
|
@@ -14463,13 +15550,241 @@ async function collectAppInfo(client, tool, metadata) {
|
|
|
14463
15550
|
}
|
|
14464
15551
|
}
|
|
14465
15552
|
|
|
15553
|
+
// src/handlers/skills-verify.ts
|
|
15554
|
+
function summarizeSkillVerification(reports) {
|
|
15555
|
+
const failed = reports.filter((report) => report.outcome === "failed").length;
|
|
15556
|
+
const incomplete = reports.filter(
|
|
15557
|
+
(report) => report.outcome === "incomplete"
|
|
15558
|
+
).length;
|
|
15559
|
+
const files = reports.reduce((sum, report) => sum + report.files.length, 0);
|
|
15560
|
+
const mismatched = reports.reduce(
|
|
15561
|
+
(sum, report) => sum + report.files.filter((file) => file.status === "mismatch").length,
|
|
15562
|
+
0
|
|
15563
|
+
);
|
|
15564
|
+
const skillWord = reports.length === 1 ? "skill" : "skills";
|
|
15565
|
+
const fileWord = files === 1 ? "file" : "files";
|
|
15566
|
+
const incompleteClause = incomplete === 0 ? "" : ` ${incomplete} of ${reports.length} ${skillWord} could not be fully checked: the read bounds stopped the walk.`;
|
|
15567
|
+
const headline = failed === 0 ? incomplete === 0 ? `Verified ${reports.length} ${skillWord} and ${files} ${fileWord}: no conformance errors.` : `Checked ${reports.length} ${skillWord} and ${files} ${fileWord}: no conformance errors in what was read.` : `${failed} of ${reports.length} ${skillWord} failed verification (${mismatched} digest/size mismatch across ${files} ${fileWord}).`;
|
|
15568
|
+
return `${headline}${incompleteClause}`;
|
|
15569
|
+
}
|
|
15570
|
+
|
|
15571
|
+
// ../../core/mcp/skillsVerification.ts
|
|
15572
|
+
function reasonOf(err) {
|
|
15573
|
+
return err instanceof Error ? err.message : String(err);
|
|
15574
|
+
}
|
|
15575
|
+
function boundedManifest(declared) {
|
|
15576
|
+
const kept = [];
|
|
15577
|
+
let bytes = 0;
|
|
15578
|
+
for (const resource of declared) {
|
|
15579
|
+
if (kept.length >= SKILL_MAX_RESOURCE_ENTRIES) break;
|
|
15580
|
+
const size = typeof resource.size === "number" && Number.isSafeInteger(resource.size) && resource.size >= 0 ? resource.size : 0;
|
|
15581
|
+
if (bytes + size > SKILL_MAX_TOTAL_BYTES) break;
|
|
15582
|
+
bytes += size;
|
|
15583
|
+
kept.push(resource);
|
|
15584
|
+
}
|
|
15585
|
+
return kept;
|
|
15586
|
+
}
|
|
15587
|
+
function contentsFor(result, uri) {
|
|
15588
|
+
const contents = result?.contents;
|
|
15589
|
+
if (!Array.isArray(contents)) return void 0;
|
|
15590
|
+
const wanted = skillUriIdentity(uri);
|
|
15591
|
+
for (const block of contents) {
|
|
15592
|
+
if (typeof block !== "object" || block === null) continue;
|
|
15593
|
+
const got = block.uri;
|
|
15594
|
+
if (typeof got !== "string") continue;
|
|
15595
|
+
if (skillUriIdentity(got) === wanted) return block;
|
|
15596
|
+
}
|
|
15597
|
+
return void 0;
|
|
15598
|
+
}
|
|
15599
|
+
function utf8Length(value) {
|
|
15600
|
+
let total = 0;
|
|
15601
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
15602
|
+
const code = value.charCodeAt(i);
|
|
15603
|
+
if (code < 128) {
|
|
15604
|
+
total += 1;
|
|
15605
|
+
} else if (code < 2048) {
|
|
15606
|
+
total += 2;
|
|
15607
|
+
} else if (code >= 55296 && code <= 56319) {
|
|
15608
|
+
const next = i + 1 < value.length ? value.charCodeAt(i + 1) : 0;
|
|
15609
|
+
if (next >= 56320 && next <= 57343) {
|
|
15610
|
+
total += 4;
|
|
15611
|
+
i += 1;
|
|
15612
|
+
} else {
|
|
15613
|
+
total += 3;
|
|
15614
|
+
}
|
|
15615
|
+
} else {
|
|
15616
|
+
total += 3;
|
|
15617
|
+
}
|
|
15618
|
+
}
|
|
15619
|
+
return total;
|
|
15620
|
+
}
|
|
15621
|
+
function responseBytes(result) {
|
|
15622
|
+
const contents = result?.contents;
|
|
15623
|
+
if (!Array.isArray(contents)) return 0;
|
|
15624
|
+
let total = 0;
|
|
15625
|
+
for (const block of contents) {
|
|
15626
|
+
if (typeof block !== "object" || block === null) continue;
|
|
15627
|
+
const { text, blob } = block;
|
|
15628
|
+
if (typeof text === "string") total += utf8Length(text);
|
|
15629
|
+
if (typeof blob === "string") total += blob.length;
|
|
15630
|
+
}
|
|
15631
|
+
return total;
|
|
15632
|
+
}
|
|
15633
|
+
async function verifySkills(client, entries, metadata) {
|
|
15634
|
+
const collisions = checkSkillNameCollisions(entries);
|
|
15635
|
+
const reports = [];
|
|
15636
|
+
let walkedSkills = 0;
|
|
15637
|
+
let catalogBytes = 0;
|
|
15638
|
+
for (const entry of entries) {
|
|
15639
|
+
const withinBudget = walkedSkills < SKILL_MAX_CATALOG_SKILLS && catalogBytes <= SKILL_MAX_CATALOG_BYTES;
|
|
15640
|
+
let entryBytes;
|
|
15641
|
+
const files = [];
|
|
15642
|
+
const declared = entry.resources === DYNAMIC_RESOURCES ? [] : entry.resources;
|
|
15643
|
+
const manifest = withinBudget ? boundedManifest(declared) : [];
|
|
15644
|
+
let incomplete = !withinBudget ? `Not read: this run already reached its catalog budget of ${SKILL_MAX_CATALOG_SKILLS} skills / ${SKILL_MAX_CATALOG_BYTES} bytes. Nothing about this skill's files has been checked \u2014 verify it on its own with \`--method skills/get --uri\` to get a verdict.` : manifest.length < declared.length ? `Only ${manifest.length} of ${declared.length} manifest entries were read: the skill exceeds the ${SKILL_MAX_RESOURCE_ENTRIES}-entry / ${SKILL_MAX_TOTAL_BYTES}-byte interoperability limits, so the rest were not fetched and cannot be reported on.` : void 0;
|
|
15645
|
+
let receivedBytes = 0;
|
|
15646
|
+
const entryIdentity = skillUriIdentity(entry.uri);
|
|
15647
|
+
let selfAttempted = false;
|
|
15648
|
+
for (const [index, resource] of manifest.entries()) {
|
|
15649
|
+
if (skillUriIdentity(resource.uri) === entryIdentity) {
|
|
15650
|
+
selfAttempted = true;
|
|
15651
|
+
}
|
|
15652
|
+
let charged = 0;
|
|
15653
|
+
try {
|
|
15654
|
+
const invocation = await client.readResource(resource.uri, metadata);
|
|
15655
|
+
charged = responseBytes(invocation.result);
|
|
15656
|
+
const contents = contentsFor(invocation.result, resource.uri);
|
|
15657
|
+
if (!contents) {
|
|
15658
|
+
files.push({
|
|
15659
|
+
uri: resource.uri,
|
|
15660
|
+
status: "read-error",
|
|
15661
|
+
reason: "resources/read returned no content block for this URI, so there are no bytes that can be checked against its digest."
|
|
15662
|
+
});
|
|
15663
|
+
} else {
|
|
15664
|
+
let bytes;
|
|
15665
|
+
try {
|
|
15666
|
+
bytes = skillFileBytes(contents);
|
|
15667
|
+
} catch (err) {
|
|
15668
|
+
files.push({
|
|
15669
|
+
uri: resource.uri,
|
|
15670
|
+
status: "read-error",
|
|
15671
|
+
reason: reasonOf(err)
|
|
15672
|
+
});
|
|
15673
|
+
}
|
|
15674
|
+
if (bytes) {
|
|
15675
|
+
charged = Math.max(charged, bytes.byteLength);
|
|
15676
|
+
if (skillUriIdentity(resource.uri) === entryIdentity)
|
|
15677
|
+
entryBytes = bytes;
|
|
15678
|
+
const verification = await verifySkillResource(resource, bytes);
|
|
15679
|
+
files.push({ uri: resource.uri, ...verification });
|
|
15680
|
+
}
|
|
15681
|
+
}
|
|
15682
|
+
} catch (err) {
|
|
15683
|
+
if (err instanceof AuthRecoveryRequiredError) throw err;
|
|
15684
|
+
files.push({
|
|
15685
|
+
uri: resource.uri,
|
|
15686
|
+
status: "read-error",
|
|
15687
|
+
reason: reasonOf(err)
|
|
15688
|
+
});
|
|
15689
|
+
}
|
|
15690
|
+
receivedBytes += charged;
|
|
15691
|
+
if (receivedBytes > SKILL_MAX_TOTAL_BYTES) {
|
|
15692
|
+
if (index < manifest.length - 1) {
|
|
15693
|
+
incomplete = `Stopped after ${index + 1} of ${declared.length} manifest entries: the files actually served exceed the ${SKILL_MAX_TOTAL_BYTES}-byte interoperability limit, whatever sizes the manifest declared.`;
|
|
15694
|
+
}
|
|
15695
|
+
break;
|
|
15696
|
+
}
|
|
15697
|
+
}
|
|
15698
|
+
if (withinBudget && !selfAttempted) {
|
|
15699
|
+
const fail = (reason) => files.push({ uri: entry.uri, status: "read-error", reason });
|
|
15700
|
+
try {
|
|
15701
|
+
const invocation = await client.readResource(entry.uri, metadata);
|
|
15702
|
+
const contents = contentsFor(invocation.result, entry.uri);
|
|
15703
|
+
if (!contents) {
|
|
15704
|
+
fail(
|
|
15705
|
+
"resources/read returned no content block for this skill's own SKILL.md, so its frontmatter cannot be checked against the listing."
|
|
15706
|
+
);
|
|
15707
|
+
} else {
|
|
15708
|
+
try {
|
|
15709
|
+
entryBytes = skillFileBytes(contents);
|
|
15710
|
+
} catch (err) {
|
|
15711
|
+
fail(reasonOf(err));
|
|
15712
|
+
}
|
|
15713
|
+
}
|
|
15714
|
+
const declaredSelf = declared.find(
|
|
15715
|
+
(resource) => skillUriIdentity(resource.uri) === entryIdentity
|
|
15716
|
+
);
|
|
15717
|
+
if (declaredSelf && entryBytes !== void 0) {
|
|
15718
|
+
files.push({
|
|
15719
|
+
// The DECLARED spelling, not the entry's. They can differ — a
|
|
15720
|
+
// manifest may write its self-entry in a normalized-equivalent
|
|
15721
|
+
// form — and a consumer matching rows against the manifest then
|
|
15722
|
+
// finds nothing, while a normalized "extra files" filter suppresses
|
|
15723
|
+
// it as already covered. The result was a verdict that existed in
|
|
15724
|
+
// the report and appeared nowhere on screen (Copilot).
|
|
15725
|
+
uri: declaredSelf.uri,
|
|
15726
|
+
...await verifySkillResource(declaredSelf, entryBytes)
|
|
15727
|
+
});
|
|
15728
|
+
}
|
|
15729
|
+
} catch (err) {
|
|
15730
|
+
if (err instanceof AuthRecoveryRequiredError) throw err;
|
|
15731
|
+
fail(reasonOf(err));
|
|
15732
|
+
}
|
|
15733
|
+
}
|
|
15734
|
+
if (withinBudget) {
|
|
15735
|
+
walkedSkills += 1;
|
|
15736
|
+
catalogBytes += receivedBytes;
|
|
15737
|
+
}
|
|
15738
|
+
const entryText = entryBytes === void 0 ? void 0 : bytesToText(entryBytes);
|
|
15739
|
+
const collision = collisions.get(skillUriIdentity(entry.uri));
|
|
15740
|
+
const conformance = [
|
|
15741
|
+
...checkSkillConformance(entry),
|
|
15742
|
+
...collision ? [collision] : []
|
|
15743
|
+
];
|
|
15744
|
+
const frontmatter = entryText === void 0 ? [] : checkSkillFrontmatterMatch(entry, entryText);
|
|
15745
|
+
const hasError = [...conformance, ...frontmatter].some(
|
|
15746
|
+
(issue) => issue.severity === "error"
|
|
15747
|
+
);
|
|
15748
|
+
const fileFailed = files.some(
|
|
15749
|
+
(file) => file.status === "mismatch" || file.status === "read-error"
|
|
15750
|
+
);
|
|
15751
|
+
reports.push({
|
|
15752
|
+
uri: entry.uri,
|
|
15753
|
+
name: skillDisplayName(entry),
|
|
15754
|
+
conformance,
|
|
15755
|
+
frontmatter,
|
|
15756
|
+
files,
|
|
15757
|
+
...incomplete ? { incomplete } : {},
|
|
15758
|
+
ok: !hasError && !fileFailed,
|
|
15759
|
+
outcome: hasError || fileFailed ? "failed" : incomplete !== void 0 ? "incomplete" : "verified"
|
|
15760
|
+
});
|
|
15761
|
+
}
|
|
15762
|
+
return reports;
|
|
15763
|
+
}
|
|
15764
|
+
function allSkillsVerified(reports) {
|
|
15765
|
+
return reports.every((report) => report.outcome === "verified");
|
|
15766
|
+
}
|
|
15767
|
+
function anySkillFailed(reports) {
|
|
15768
|
+
return reports.some((report) => report.outcome === "failed");
|
|
15769
|
+
}
|
|
15770
|
+
|
|
14466
15771
|
// src/handlers/run-method.ts
|
|
15772
|
+
function assertSkillsSupported(inspectorClient, method) {
|
|
15773
|
+
if (!inspectorClient.getSkillsExtension()) {
|
|
15774
|
+
throw new CliExitCodeError(
|
|
15775
|
+
EXIT_CODES.USAGE,
|
|
15776
|
+
`Server does not declare the ${SKILLS_EXTENSION_KEY} extension, so ${method} is not available.`,
|
|
15777
|
+
{ code: "skills_unsupported" }
|
|
15778
|
+
);
|
|
15779
|
+
}
|
|
15780
|
+
}
|
|
14467
15781
|
async function runMethod(inspectorClient, args) {
|
|
14468
15782
|
let managedToolsState = null;
|
|
14469
15783
|
let managedResourcesState = null;
|
|
14470
15784
|
let managedResourceTemplatesState = null;
|
|
14471
15785
|
let managedPromptsState = null;
|
|
14472
15786
|
let managedTasksState = null;
|
|
15787
|
+
let managedSkillsState = null;
|
|
14473
15788
|
try {
|
|
14474
15789
|
let result;
|
|
14475
15790
|
let appInfo;
|
|
@@ -14688,6 +16003,73 @@ async function runMethod(inspectorClient, args) {
|
|
|
14688
16003
|
result = await inspectorClient.getRequestorTaskResult(
|
|
14689
16004
|
args.taskId
|
|
14690
16005
|
);
|
|
16006
|
+
} else if (args.method === "skills/list") {
|
|
16007
|
+
assertSkillsSupported(inspectorClient, args.method);
|
|
16008
|
+
managedSkillsState = new ManagedSkillsState(inspectorClient);
|
|
16009
|
+
const skills = await managedSkillsState.refresh(args.metadata);
|
|
16010
|
+
if (args.verify) {
|
|
16011
|
+
const reports = await verifySkills(
|
|
16012
|
+
inspectorClient,
|
|
16013
|
+
skills,
|
|
16014
|
+
args.metadata
|
|
16015
|
+
);
|
|
16016
|
+
return {
|
|
16017
|
+
kind: "ndjson",
|
|
16018
|
+
lines: reports,
|
|
16019
|
+
summary: summarizeSkillVerification(reports),
|
|
16020
|
+
// Three outcomes, three exit codes: a broken MUST is 7, a walk the
|
|
16021
|
+
// read bounds cut short is 8, and everything checked and passing is
|
|
16022
|
+
// 0. Collapsing the middle case into either of the others reports
|
|
16023
|
+
// something untrue about the server (Copilot).
|
|
16024
|
+
...allSkillsVerified(reports) ? {} : {
|
|
16025
|
+
exitCode: anySkillFailed(reports) ? EXIT_CODES.SKILL_NONCONFORMANT : EXIT_CODES.SKILL_INCOMPLETE
|
|
16026
|
+
}
|
|
16027
|
+
};
|
|
16028
|
+
}
|
|
16029
|
+
result = { skills };
|
|
16030
|
+
} else if (args.method === "skills/get") {
|
|
16031
|
+
if (!args.uri) {
|
|
16032
|
+
throw new Error(
|
|
16033
|
+
"URI is required for skills/get method. Use --uri to specify the skill URI."
|
|
16034
|
+
);
|
|
16035
|
+
}
|
|
16036
|
+
assertSkillsSupported(inspectorClient, args.method);
|
|
16037
|
+
const envelope = await inspectorClient.getSkillResult(
|
|
16038
|
+
args.uri,
|
|
16039
|
+
args.metadata
|
|
16040
|
+
);
|
|
16041
|
+
const skill = envelope.skill;
|
|
16042
|
+
if (args.verify) {
|
|
16043
|
+
const reports = await verifySkills(
|
|
16044
|
+
inspectorClient,
|
|
16045
|
+
[skill],
|
|
16046
|
+
args.metadata
|
|
16047
|
+
);
|
|
16048
|
+
return {
|
|
16049
|
+
kind: "ndjson",
|
|
16050
|
+
lines: reports,
|
|
16051
|
+
summary: summarizeSkillVerification(reports),
|
|
16052
|
+
// Three outcomes, three exit codes: a broken MUST is 7, a walk the
|
|
16053
|
+
// read bounds cut short is 8, and everything checked and passing is
|
|
16054
|
+
// 0. Collapsing the middle case into either of the others reports
|
|
16055
|
+
// something untrue about the server (Copilot).
|
|
16056
|
+
...allSkillsVerified(reports) ? {} : {
|
|
16057
|
+
exitCode: anySkillFailed(reports) ? EXIT_CODES.SKILL_NONCONFORMANT : EXIT_CODES.SKILL_INCOMPLETE
|
|
16058
|
+
}
|
|
16059
|
+
};
|
|
16060
|
+
}
|
|
16061
|
+
result = envelope;
|
|
16062
|
+
} else if (args.method === "resources/directory/read") {
|
|
16063
|
+
if (!args.uri) {
|
|
16064
|
+
throw new Error(
|
|
16065
|
+
"URI is required for resources/directory/read. Use --uri to specify the directory URI."
|
|
16066
|
+
);
|
|
16067
|
+
}
|
|
16068
|
+
result = await inspectorClient.readResourceDirectory(
|
|
16069
|
+
args.uri,
|
|
16070
|
+
args.cursor,
|
|
16071
|
+
args.metadata
|
|
16072
|
+
);
|
|
14691
16073
|
} else if (args.method === "roots/list") {
|
|
14692
16074
|
result = { roots: inspectorClient.getRoots() };
|
|
14693
16075
|
} else if (args.method === "roots/set") {
|
|
@@ -14722,6 +16104,7 @@ async function runMethod(inspectorClient, args) {
|
|
|
14722
16104
|
managedResourcesState?.destroy();
|
|
14723
16105
|
managedResourceTemplatesState?.destroy();
|
|
14724
16106
|
managedPromptsState?.destroy();
|
|
16107
|
+
managedSkillsState?.destroy();
|
|
14725
16108
|
managedTasksState?.destroy();
|
|
14726
16109
|
}
|
|
14727
16110
|
}
|
|
@@ -14736,7 +16119,13 @@ var ONE_SHOT_METHODS = [
|
|
|
14736
16119
|
"resources/templates/list",
|
|
14737
16120
|
"prompts/list",
|
|
14738
16121
|
"prompts/get",
|
|
14739
|
-
"logging/setLevel"
|
|
16122
|
+
"logging/setLevel",
|
|
16123
|
+
// SEP-2640. All three are ordinary one-shot request/response calls — no
|
|
16124
|
+
// stream, no long-lived subscription — so they belong here alongside the
|
|
16125
|
+
// other list verbs rather than being reachable only from the session CLI.
|
|
16126
|
+
"skills/list",
|
|
16127
|
+
"skills/get",
|
|
16128
|
+
"resources/directory/read"
|
|
14740
16129
|
];
|
|
14741
16130
|
function isOneShotMethod(method) {
|
|
14742
16131
|
return ONE_SHOT_METHODS.includes(method);
|
|
@@ -15181,7 +16570,7 @@ function createCliOAuthNavigation(options = {}) {
|
|
|
15181
16570
|
}
|
|
15182
16571
|
|
|
15183
16572
|
// ../../core/client/config-parse.ts
|
|
15184
|
-
import { z as
|
|
16573
|
+
import { z as z5 } from "zod";
|
|
15185
16574
|
function isAbsoluteHttpUrl(value) {
|
|
15186
16575
|
let url;
|
|
15187
16576
|
try {
|
|
@@ -15198,11 +16587,11 @@ function isRealHost(hostname) {
|
|
|
15198
16587
|
const labels = hostname.split(".");
|
|
15199
16588
|
return labels.length >= 2 && labels.every((label) => label !== "");
|
|
15200
16589
|
}
|
|
15201
|
-
var HttpUrlStringSchema =
|
|
16590
|
+
var HttpUrlStringSchema = z5.string().min(1).superRefine((val, ctx) => {
|
|
15202
16591
|
const trimmed = val.trim();
|
|
15203
16592
|
if (!isAbsoluteHttpUrl(trimmed)) {
|
|
15204
16593
|
ctx.addIssue({
|
|
15205
|
-
code:
|
|
16594
|
+
code: z5.ZodIssueCode.custom,
|
|
15206
16595
|
message: `Invalid URL: "${trimmed}" \u2014 must be an http(s) URL (e.g. https://idp.example.com)`
|
|
15207
16596
|
});
|
|
15208
16597
|
}
|
|
@@ -15234,7 +16623,7 @@ function refineCimdMetadataUrl(val, ctx, required) {
|
|
|
15234
16623
|
if (trimmed === "") {
|
|
15235
16624
|
if (required) {
|
|
15236
16625
|
ctx.addIssue({
|
|
15237
|
-
code:
|
|
16626
|
+
code: z5.ZodIssueCode.custom,
|
|
15238
16627
|
message: "CIMD client metadata URL is required when CIMD is enabled"
|
|
15239
16628
|
});
|
|
15240
16629
|
}
|
|
@@ -15243,25 +16632,25 @@ function refineCimdMetadataUrl(val, ctx, required) {
|
|
|
15243
16632
|
const error = getCimdClientMetadataUrlError(trimmed);
|
|
15244
16633
|
if (error) {
|
|
15245
16634
|
ctx.addIssue({
|
|
15246
|
-
code:
|
|
16635
|
+
code: z5.ZodIssueCode.custom,
|
|
15247
16636
|
message: error
|
|
15248
16637
|
});
|
|
15249
16638
|
}
|
|
15250
16639
|
}
|
|
15251
|
-
var CimdConfigSchema =
|
|
15252
|
-
enabled:
|
|
15253
|
-
clientMetadataUrl:
|
|
16640
|
+
var CimdConfigSchema = z5.object({
|
|
16641
|
+
enabled: z5.boolean(),
|
|
16642
|
+
clientMetadataUrl: z5.string()
|
|
15254
16643
|
}).superRefine((data, ctx) => {
|
|
15255
16644
|
refineCimdMetadataUrl(data.clientMetadataUrl, ctx, data.enabled === true);
|
|
15256
16645
|
});
|
|
15257
|
-
var EnterpriseManagedAuthIdpConfigSchema =
|
|
16646
|
+
var EnterpriseManagedAuthIdpConfigSchema = z5.object({
|
|
15258
16647
|
issuer: HttpUrlStringSchema,
|
|
15259
|
-
clientId:
|
|
15260
|
-
clientSecret:
|
|
16648
|
+
clientId: z5.string().min(1),
|
|
16649
|
+
clientSecret: z5.string().optional()
|
|
15261
16650
|
});
|
|
15262
|
-
var ClientConfigSchema =
|
|
15263
|
-
enterpriseManagedAuth:
|
|
15264
|
-
enabled:
|
|
16651
|
+
var ClientConfigSchema = z5.object({
|
|
16652
|
+
enterpriseManagedAuth: z5.object({
|
|
16653
|
+
enabled: z5.boolean().optional(),
|
|
15265
16654
|
idp: EnterpriseManagedAuthIdpConfigSchema
|
|
15266
16655
|
}).optional(),
|
|
15267
16656
|
cimd: CimdConfigSchema.optional()
|
|
@@ -16040,7 +17429,13 @@ async function parseArgs(argv) {
|
|
|
16040
17429
|
"Tool argument as key=value pair",
|
|
16041
17430
|
parseKeyValuePair2,
|
|
16042
17431
|
{}
|
|
16043
|
-
).option(
|
|
17432
|
+
).option(
|
|
17433
|
+
"--uri <uri>",
|
|
17434
|
+
"URI of the resource (resources/read, resources/directory/read) or of the skill (skills/get)"
|
|
17435
|
+
).option(
|
|
17436
|
+
"--cursor <cursor>",
|
|
17437
|
+
"Opaque pagination cursor (for resources/directory/read; pass back the nextCursor from the previous page)."
|
|
17438
|
+
).option(
|
|
16044
17439
|
"--prompt-name <promptName>",
|
|
16045
17440
|
"Name of the prompt (for prompts/get method)"
|
|
16046
17441
|
).option(
|
|
@@ -16092,6 +17487,9 @@ async function parseArgs(argv) {
|
|
|
16092
17487
|
).option(
|
|
16093
17488
|
"--strict",
|
|
16094
17489
|
"Report tool-schema portability problems in full (path, issue, suggested fix) on stderr, and exit 6 if any is error-severity. Use with --method tools/list. Without it, a one-line count is printed instead."
|
|
17490
|
+
).option(
|
|
17491
|
+
"--verify",
|
|
17492
|
+
"Run the SEP-2640 conformance and digest checks over the skills returned, emit one JSON report per skill on stdout, and exit 7 if any fails or 8 if any could not be fully checked within the read bounds. Use with --method skills/list or --method skills/get."
|
|
16095
17493
|
).option(
|
|
16096
17494
|
"--connect-timeout <ms>",
|
|
16097
17495
|
`Connection timeout in ms (default ${DEFAULT_CONNECT_TIMEOUT_MS} for ad-hoc --server-url / target invocations; 0 = no timeout).`,
|
|
@@ -16195,6 +17593,13 @@ async function parseArgs(argv) {
|
|
|
16195
17593
|
);
|
|
16196
17594
|
}
|
|
16197
17595
|
}
|
|
17596
|
+
if (options.verify) {
|
|
17597
|
+
if (options.method !== "skills/list" && options.method !== "skills/get") {
|
|
17598
|
+
throw new Error(
|
|
17599
|
+
"--verify requires --method skills/list or --method skills/get."
|
|
17600
|
+
);
|
|
17601
|
+
}
|
|
17602
|
+
}
|
|
16198
17603
|
const oauthStatePath = getStateFilePath();
|
|
16199
17604
|
if (options.listStoredAuth) {
|
|
16200
17605
|
const servers = await readOAuthServers(oauthStatePath);
|
|
@@ -16358,6 +17763,8 @@ async function parseArgs(argv) {
|
|
|
16358
17763
|
toolMeta: options.toolMetadata,
|
|
16359
17764
|
appInfo: options.appInfo === true,
|
|
16360
17765
|
strict: options.strict === true,
|
|
17766
|
+
verify: options.verify === true,
|
|
17767
|
+
cursor: options.cursor,
|
|
16361
17768
|
format: options.format
|
|
16362
17769
|
};
|
|
16363
17770
|
return {
|