teppi-check 0.2.1 → 0.3.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 +30 -5
- package/npm/checks.d.ts +2 -1
- package/npm/checks.d.ts.map +1 -1
- package/npm/cli.js +186 -141
- package/npm/defects.d.ts +9 -0
- package/npm/defects.d.ts.map +1 -0
- package/npm/index.d.ts +1 -0
- package/npm/index.d.ts.map +1 -1
- package/npm/index.js +53 -5
- package/npm/mcp.d.ts +1 -1
- package/npm/mcp.d.ts.map +1 -1
- package/npm/probe.d.ts +2 -0
- package/npm/probe.d.ts.map +1 -1
- package/npm/report.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# teppi-check
|
|
2
2
|
|
|
3
|
-
Send one unpaid request to any paid endpoint and see what it actually advertises
|
|
3
|
+
Send one unpaid request to any paid endpoint and see what it actually advertises, and what is
|
|
4
|
+
already broken about the listing before you pay anything.
|
|
4
5
|
|
|
5
6
|
```bash
|
|
6
7
|
npx teppi-check https://some-seller.example/v1/extract
|
|
@@ -36,13 +37,16 @@ in the record something you can check rather than something you are asked to acc
|
|
|
36
37
|
payment terms are not cached fail
|
|
37
38
|
no cache-control, so a proxy may serve stale terms
|
|
38
39
|
|
|
40
|
+
prose_in_the_url defect
|
|
41
|
+
a query value spells out what to put there instead of putting it
|
|
42
|
+
|
|
39
43
|
advertised
|
|
40
44
|
10000 0x2222...2222 on eip155:8453 via exact
|
|
41
45
|
|
|
42
46
|
request sha256:ba6f7ff0...0878
|
|
43
47
|
response sha256:0a83a213...2496
|
|
44
48
|
|
|
45
|
-
|
|
49
|
+
3 problems found
|
|
46
50
|
```
|
|
47
51
|
|
|
48
52
|
## Why this exists
|
|
@@ -71,6 +75,23 @@ takes a purchase, which the full prober does on a schedule.
|
|
|
71
75
|
Terms are read from the `payment-required` header (base64 or plain JSON) or from the response
|
|
72
76
|
body, because deployed servers use both.
|
|
73
77
|
|
|
78
|
+
## What it finds wrong with the listing
|
|
79
|
+
|
|
80
|
+
A check reads the answer. A defect reads the listing itself, and an endpoint can answer perfectly
|
|
81
|
+
while being impossible to buy from.
|
|
82
|
+
|
|
83
|
+
| Defect | Means |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `prose_in_the_url` | a query value describes the parameter instead of filling it in, or a path segment is still `:id` or `{id}`, or an address is forty repeated characters |
|
|
86
|
+
| `path_not_found` | the listed path answered 404 or 410, so there is nothing there to buy |
|
|
87
|
+
|
|
88
|
+
These are the names the public record uses, with the same sentence beside them. A test runs this
|
|
89
|
+
rule and the one the record is built from over the same list of urls and fails if they ever give
|
|
90
|
+
different answers, so the id you read in a Teppi entry is the id you get here.
|
|
91
|
+
|
|
92
|
+
Nothing our own bad request causes is a defect. A 400, a 422 or a 429 says something about the
|
|
93
|
+
caller, not the seller, and none of them appear.
|
|
94
|
+
|
|
74
95
|
## Remote MCP servers
|
|
75
96
|
|
|
76
97
|
`--mcp` runs the handshake instead: `initialize`, the initialized notification, then `tools/list`
|
|
@@ -97,14 +118,15 @@ which is what makes it useful for noticing that a server's instructions changed
|
|
|
97
118
|
|
|
98
119
|
```
|
|
99
120
|
--mcp handshake a remote mcp server instead of an x402 endpoint
|
|
100
|
-
--method <verb>
|
|
121
|
+
--method <verb> tries GET then POST when not given
|
|
101
122
|
--body <json|@file> default {}
|
|
102
123
|
--json machine readable output
|
|
103
124
|
--timeout <ms> default 15000
|
|
104
125
|
--user-agent <ua> default node
|
|
105
126
|
```
|
|
106
127
|
|
|
107
|
-
Exit codes: `0`
|
|
128
|
+
Exit codes: `0` nothing to report, `1` a check failed or a defect was found, `2` the endpoint
|
|
129
|
+
could not be reached.
|
|
108
130
|
|
|
109
131
|
Requests go over HTTPS only, apart from localhost.
|
|
110
132
|
|
|
@@ -123,7 +145,10 @@ the failure mode nobody notices until buyers quietly stop calling.
|
|
|
123
145
|
import { probe } from 'teppi-check';
|
|
124
146
|
|
|
125
147
|
const result = await probe({ url: 'https://some-seller.example/v1/extract' });
|
|
126
|
-
console.log(result.verdict, result.checks);
|
|
148
|
+
console.log(result.verdict, result.checks, result.defects);
|
|
127
149
|
```
|
|
128
150
|
|
|
151
|
+
`urlHasPlaceholder(url)` and `defectsOf(url, httpStatus)` are exported on their own, for reading a
|
|
152
|
+
list of urls without sending anything.
|
|
153
|
+
|
|
129
154
|
MIT.
|
package/npm/checks.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type PaymentTerms } from './terms.js';
|
|
2
|
+
import type { Defect } from './defects.js';
|
|
2
3
|
export type CheckStatus = 'pass' | 'fail' | 'skip';
|
|
3
4
|
export type Check = {
|
|
4
5
|
readonly id: string;
|
|
@@ -16,5 +17,5 @@ export type HandshakeInput = {
|
|
|
16
17
|
};
|
|
17
18
|
export declare function runHandshakeChecks(input: HandshakeInput): Check[];
|
|
18
19
|
export declare function unreachable(reason: string): Check[];
|
|
19
|
-
export declare function verdictOf(checks: readonly Check[]): 'pass' | 'fail';
|
|
20
|
+
export declare function verdictOf(checks: readonly Check[], defects: readonly Defect[]): 'pass' | 'fail';
|
|
20
21
|
//# sourceMappingURL=checks.d.ts.map
|
package/npm/checks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,YAAY,EAAsB,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"checks.d.ts","sourceRoot":"","sources":["../src/checks.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,YAAY,EAAsB,MAAM,mBAAmB,CAAC;AAEnG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,MAAM,MAAM,KAAK,GAAG;IACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAqBF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,KAAK,CAa7E;AAED,MAAM,MAAM,cAAc,GAAG;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,CAAC;AA+BF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,KAAK,EAAE,CA4CjE;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,CAEnD;AAGD,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,CAG/F"}
|
package/npm/cli.js
CHANGED
|
@@ -7134,125 +7134,6 @@ function toolsDigest(tools) {
|
|
|
7134
7134
|
return createHash("sha256").update(canonical).digest("hex");
|
|
7135
7135
|
}
|
|
7136
7136
|
|
|
7137
|
-
// src/mcp.ts
|
|
7138
|
-
function sha256(input) {
|
|
7139
|
-
return `sha256:${createHash2("sha256").update(input).digest("hex")}`;
|
|
7140
|
-
}
|
|
7141
|
-
var pass = (id, label, detail) => detail === void 0 ? { id, label, status: "pass" } : { id, label, status: "pass", detail };
|
|
7142
|
-
var fail = (id, label, detail) => ({
|
|
7143
|
-
id,
|
|
7144
|
-
label,
|
|
7145
|
-
status: "fail",
|
|
7146
|
-
detail
|
|
7147
|
-
});
|
|
7148
|
-
var skip = (id, label, detail) => ({
|
|
7149
|
-
id,
|
|
7150
|
-
label,
|
|
7151
|
-
status: "skip",
|
|
7152
|
-
detail
|
|
7153
|
-
});
|
|
7154
|
-
var REACH_LABEL = {
|
|
7155
|
-
readable: "listed its tools",
|
|
7156
|
-
auth_required: "asked for credentials",
|
|
7157
|
-
payment_required: "asked for payment",
|
|
7158
|
-
dead: "did not answer",
|
|
7159
|
-
error: "answered with something else"
|
|
7160
|
-
};
|
|
7161
|
-
function checksFor(seen) {
|
|
7162
|
-
const checks = [];
|
|
7163
|
-
checks.push(
|
|
7164
|
-
seen.reach === "dead" ? fail("reachable", "server answered", seen.note ?? "no answer") : pass("reachable", "server answered", REACH_LABEL[seen.reach])
|
|
7165
|
-
);
|
|
7166
|
-
if (seen.reach === "dead") return checks;
|
|
7167
|
-
checks.push(
|
|
7168
|
-
seen.protocolVersion === null ? skip("protocol", "named a protocol version", "it never got as far as saying") : pass("protocol", "named a protocol version", seen.protocolVersion)
|
|
7169
|
-
);
|
|
7170
|
-
checks.push(
|
|
7171
|
-
seen.serverName === null ? skip("identity", "named itself", "it never got as far as saying") : pass(
|
|
7172
|
-
"identity",
|
|
7173
|
-
"named itself",
|
|
7174
|
-
`${seen.serverName}${seen.serverVersion === null ? "" : ` ${seen.serverVersion}`}`
|
|
7175
|
-
)
|
|
7176
|
-
);
|
|
7177
|
-
if (seen.tools === null) {
|
|
7178
|
-
checks.push(skip("tools", "listed its tools", REACH_LABEL[seen.reach]));
|
|
7179
|
-
return checks;
|
|
7180
|
-
}
|
|
7181
|
-
checks.push(pass("tools", "listed its tools", `${seen.tools.length} tool(s)`));
|
|
7182
|
-
const bare = seen.tools.filter((t) => t.inputSchema === null).map((t) => t.name);
|
|
7183
|
-
checks.push(
|
|
7184
|
-
bare.length === 0 ? pass("input-schemas", "every tool declared an input schema") : fail(
|
|
7185
|
-
"input-schemas",
|
|
7186
|
-
"every tool declared an input schema",
|
|
7187
|
-
`${bare.length} without one: ${bare.slice(0, 5).join(", ")}`
|
|
7188
|
-
)
|
|
7189
|
-
);
|
|
7190
|
-
const unnamed = seen.tools.filter((t) => t.description === null).map((t) => t.name);
|
|
7191
|
-
checks.push(
|
|
7192
|
-
unnamed.length === 0 ? pass("descriptions", "every tool said what it does") : fail(
|
|
7193
|
-
"descriptions",
|
|
7194
|
-
"every tool said what it does",
|
|
7195
|
-
`${unnamed.length} without one: ${unnamed.slice(0, 5).join(", ")}`
|
|
7196
|
-
)
|
|
7197
|
-
);
|
|
7198
|
-
checks.push(pass("digest", "tool list digest", toolsDigest(seen.tools)));
|
|
7199
|
-
return checks;
|
|
7200
|
-
}
|
|
7201
|
-
async function probeMcpEndpoint(options) {
|
|
7202
|
-
const seen = await probeMcp(options.url, {
|
|
7203
|
-
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
7204
|
-
...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
|
|
7205
|
-
...options.userAgent === void 0 ? {} : { userAgent: options.userAgent }
|
|
7206
|
-
});
|
|
7207
|
-
const checks = checksFor(seen);
|
|
7208
|
-
return {
|
|
7209
|
-
url: options.url,
|
|
7210
|
-
method: "MCP",
|
|
7211
|
-
observedAt: seen.observedAt,
|
|
7212
|
-
reachable: seen.reach !== "dead",
|
|
7213
|
-
httpStatus: seen.httpStatus,
|
|
7214
|
-
handshakeMs: seen.handshakeMs,
|
|
7215
|
-
termsSource: seen.reach === "payment_required" ? "mcp 402" : "none",
|
|
7216
|
-
advertised: [],
|
|
7217
|
-
requestHash: sha256(`MCP ${options.url}`),
|
|
7218
|
-
responseHash: seen.tools === null ? null : `sha256:${toolsDigest(seen.tools)}`,
|
|
7219
|
-
checks,
|
|
7220
|
-
verdict: checks.some((c) => c.status === "fail") ? "fail" : "pass"
|
|
7221
|
-
};
|
|
7222
|
-
}
|
|
7223
|
-
|
|
7224
|
-
// src/probe.ts
|
|
7225
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
7226
|
-
|
|
7227
|
-
// ../x402/src/handshake.ts
|
|
7228
|
-
var HANDSHAKE_TIMEOUT_MS2 = 3e4;
|
|
7229
|
-
var sendsBody = (method) => method !== "GET" && method !== "HEAD";
|
|
7230
|
-
function handshakeInit(request2) {
|
|
7231
|
-
const carries = sendsBody(request2.method);
|
|
7232
|
-
const headers = { accept: "application/json" };
|
|
7233
|
-
if (carries) headers["content-type"] = "application/json";
|
|
7234
|
-
if (request2.userAgent !== void 0) headers["user-agent"] = request2.userAgent;
|
|
7235
|
-
if (request2.payment) headers[request2.payment.header] = request2.payment.value;
|
|
7236
|
-
return {
|
|
7237
|
-
method: request2.method,
|
|
7238
|
-
headers,
|
|
7239
|
-
...carries ? { body: JSON.stringify(request2.input ?? {}) } : {},
|
|
7240
|
-
signal: AbortSignal.timeout(request2.timeoutMs ?? HANDSHAKE_TIMEOUT_MS2)
|
|
7241
|
-
};
|
|
7242
|
-
}
|
|
7243
|
-
async function handshake2(request2, deps = {}) {
|
|
7244
|
-
const doFetch = deps.fetchImpl ?? fetch;
|
|
7245
|
-
const now = deps.now ?? Date.now;
|
|
7246
|
-
const started = now();
|
|
7247
|
-
const since = () => Math.max(0, now() - started);
|
|
7248
|
-
try {
|
|
7249
|
-
const response = await doFetch(request2.url, handshakeInit(request2));
|
|
7250
|
-
return { ok: true, response, body: await response.text(), latencyMs: since() };
|
|
7251
|
-
} catch (err) {
|
|
7252
|
-
return { ok: false, reason: err.message, latencyMs: since() };
|
|
7253
|
-
}
|
|
7254
|
-
}
|
|
7255
|
-
|
|
7256
7137
|
// ../x402/src/terms.ts
|
|
7257
7138
|
var HEADER_NAMES = ["payment-required", "x-payment-required", "www-authenticate-payment"];
|
|
7258
7139
|
function asTerms(value) {
|
|
@@ -7324,24 +7205,24 @@ var ajv = new import_ajv.Ajv({
|
|
|
7324
7205
|
validateSchema: true,
|
|
7325
7206
|
validateFormats: false
|
|
7326
7207
|
});
|
|
7327
|
-
function
|
|
7208
|
+
function pass(id, label, detail) {
|
|
7328
7209
|
return detail === void 0 ? { id, label, status: "pass" } : { id, label, status: "pass", detail };
|
|
7329
7210
|
}
|
|
7330
|
-
function
|
|
7211
|
+
function fail(id, label, detail) {
|
|
7331
7212
|
return { id, label, status: "fail", detail };
|
|
7332
7213
|
}
|
|
7333
7214
|
function schemaCheck(id, label, schema) {
|
|
7334
7215
|
if (schema === null || schema === void 0) {
|
|
7335
|
-
return
|
|
7216
|
+
return fail(id, label, "not declared, so no response can ever be checked against it");
|
|
7336
7217
|
}
|
|
7337
7218
|
if (typeof schema !== "object") {
|
|
7338
|
-
return
|
|
7219
|
+
return fail(id, label, `declared as ${typeof schema}, not an object`);
|
|
7339
7220
|
}
|
|
7340
7221
|
try {
|
|
7341
7222
|
ajv.compile(schema);
|
|
7342
|
-
return
|
|
7223
|
+
return pass(id, label);
|
|
7343
7224
|
} catch (err) {
|
|
7344
|
-
return
|
|
7225
|
+
return fail(id, label, `declared but invalid: ${err.message.split("\n")[0]}`);
|
|
7345
7226
|
}
|
|
7346
7227
|
}
|
|
7347
7228
|
function requirementChecks(terms) {
|
|
@@ -7349,7 +7230,7 @@ function requirementChecks(terms) {
|
|
|
7349
7230
|
const report = (id, label, has) => {
|
|
7350
7231
|
const gaps = terms.accepts.map((r, i) => has(r) ? -1 : i).filter((i) => i >= 0);
|
|
7351
7232
|
out.push(
|
|
7352
|
-
gaps.length === 0 ?
|
|
7233
|
+
gaps.length === 0 ? pass(id, label) : fail(id, label, `absent from accepts[${gaps.join("], accepts[")}]`)
|
|
7353
7234
|
);
|
|
7354
7235
|
};
|
|
7355
7236
|
report("price_declared", "price is stated", (r) => priceOf(r) !== null);
|
|
@@ -7366,29 +7247,29 @@ function requirementChecks(terms) {
|
|
|
7366
7247
|
return out;
|
|
7367
7248
|
}
|
|
7368
7249
|
function runHandshakeChecks(input) {
|
|
7369
|
-
const checks = [
|
|
7250
|
+
const checks = [pass("reachable", "endpoint answered")];
|
|
7370
7251
|
checks.push(
|
|
7371
|
-
input.status === 402 ?
|
|
7252
|
+
input.status === 402 ? pass("status_402", "asks for payment") : fail("status_402", "asks for payment", `returned ${input.status}, not 402`)
|
|
7372
7253
|
);
|
|
7373
7254
|
if (input.terms === null) {
|
|
7374
7255
|
checks.push(
|
|
7375
|
-
|
|
7256
|
+
fail("terms_parseable", "payment terms parse", input.parseError ?? "no terms found")
|
|
7376
7257
|
);
|
|
7377
7258
|
return checks;
|
|
7378
7259
|
}
|
|
7379
|
-
checks.push(
|
|
7260
|
+
checks.push(pass("terms_parseable", "payment terms parse", `read from the ${input.source}`));
|
|
7380
7261
|
checks.push(
|
|
7381
|
-
input.terms.accepts.length > 0 ?
|
|
7262
|
+
input.terms.accepts.length > 0 ? pass(
|
|
7382
7263
|
"accepts_present",
|
|
7383
7264
|
"offers at least one way to pay",
|
|
7384
7265
|
`${input.terms.accepts.length} option(s)`
|
|
7385
|
-
) :
|
|
7266
|
+
) : fail("accepts_present", "offers at least one way to pay", "accepts is empty")
|
|
7386
7267
|
);
|
|
7387
7268
|
if (input.terms.accepts.length === 0) return checks;
|
|
7388
7269
|
checks.push(...requirementChecks(input.terms));
|
|
7389
7270
|
const cache = input.headers.get("cache-control") ?? "";
|
|
7390
7271
|
checks.push(
|
|
7391
|
-
/no-store|no-cache/i.test(cache) ?
|
|
7272
|
+
/no-store|no-cache/i.test(cache) ? pass("not_cacheable", "payment terms are not cached") : fail(
|
|
7392
7273
|
"not_cacheable",
|
|
7393
7274
|
"payment terms are not cached",
|
|
7394
7275
|
cache === "" ? "no cache-control, so a proxy may serve stale terms" : `cache-control: ${cache}`
|
|
@@ -7397,12 +7278,165 @@ function runHandshakeChecks(input) {
|
|
|
7397
7278
|
return checks;
|
|
7398
7279
|
}
|
|
7399
7280
|
function unreachable(reason) {
|
|
7400
|
-
return [
|
|
7281
|
+
return [fail("reachable", "endpoint answered", reason)];
|
|
7401
7282
|
}
|
|
7402
|
-
function verdictOf(checks) {
|
|
7283
|
+
function verdictOf(checks, defects) {
|
|
7284
|
+
if (defects.length > 0) return "fail";
|
|
7403
7285
|
return checks.some((c) => c.status === "fail") ? "fail" : "pass";
|
|
7404
7286
|
}
|
|
7405
7287
|
|
|
7288
|
+
// src/defects.ts
|
|
7289
|
+
var PLACEHOLDER = [
|
|
7290
|
+
/[?&][^=&]+=(?:string|integer|number|boolean|array|object)(?:&|$)/,
|
|
7291
|
+
/=0x([0-9a-fA-F])\1{39}(?:&|$)/,
|
|
7292
|
+
/\/:[A-Za-z_][A-Za-z0-9_]*(?:\/|[?]|$)/,
|
|
7293
|
+
/\/[{][A-Za-z_][A-Za-z0-9_]*[}](?:\/|[?]|$)/,
|
|
7294
|
+
// An em dash and an e.g. belong to a sentence, and a value is not one.
|
|
7295
|
+
/[?&][^=&]+=[^&]*(?:%E2%80%94|—)/i,
|
|
7296
|
+
/[?&][^=&]+=[^&]*e\.g\./i,
|
|
7297
|
+
// Anchored at the start of the value, since a real one can mention these words later.
|
|
7298
|
+
/[?&][^=&]+=(?:required|optional)(?:[^a-z]|$)/i,
|
|
7299
|
+
// A value that spells out its own parameter name is describing it, not answering it.
|
|
7300
|
+
/[?&]([a-z_]{3,})=[^&]*\1[^&]*(?:\+|%20)/i
|
|
7301
|
+
];
|
|
7302
|
+
function urlHasPlaceholder(url) {
|
|
7303
|
+
return PLACEHOLDER.some((marker) => marker.test(url));
|
|
7304
|
+
}
|
|
7305
|
+
var PROSE_SAYS = "a query value spells out what to put there instead of putting it";
|
|
7306
|
+
var GONE = /* @__PURE__ */ new Set([404, 410]);
|
|
7307
|
+
function pathNotFoundSays(httpStatus) {
|
|
7308
|
+
return `the listed path answered ${httpStatus}`;
|
|
7309
|
+
}
|
|
7310
|
+
function defectsOf(url, httpStatus) {
|
|
7311
|
+
const found = [];
|
|
7312
|
+
if (urlHasPlaceholder(url)) found.push({ defect: "prose_in_the_url", says: PROSE_SAYS });
|
|
7313
|
+
if (httpStatus !== null && GONE.has(httpStatus)) {
|
|
7314
|
+
found.push({ defect: "path_not_found", says: pathNotFoundSays(httpStatus) });
|
|
7315
|
+
}
|
|
7316
|
+
return found;
|
|
7317
|
+
}
|
|
7318
|
+
|
|
7319
|
+
// src/mcp.ts
|
|
7320
|
+
function sha256(input) {
|
|
7321
|
+
return `sha256:${createHash2("sha256").update(input).digest("hex")}`;
|
|
7322
|
+
}
|
|
7323
|
+
var pass2 = (id, label, detail) => detail === void 0 ? { id, label, status: "pass" } : { id, label, status: "pass", detail };
|
|
7324
|
+
var fail2 = (id, label, detail) => ({
|
|
7325
|
+
id,
|
|
7326
|
+
label,
|
|
7327
|
+
status: "fail",
|
|
7328
|
+
detail
|
|
7329
|
+
});
|
|
7330
|
+
var skip = (id, label, detail) => ({
|
|
7331
|
+
id,
|
|
7332
|
+
label,
|
|
7333
|
+
status: "skip",
|
|
7334
|
+
detail
|
|
7335
|
+
});
|
|
7336
|
+
var REACH_LABEL = {
|
|
7337
|
+
readable: "listed its tools",
|
|
7338
|
+
auth_required: "asked for credentials",
|
|
7339
|
+
payment_required: "asked for payment",
|
|
7340
|
+
dead: "did not answer",
|
|
7341
|
+
error: "answered with something else"
|
|
7342
|
+
};
|
|
7343
|
+
function checksFor(seen) {
|
|
7344
|
+
const checks = [];
|
|
7345
|
+
checks.push(
|
|
7346
|
+
seen.reach === "dead" ? fail2("reachable", "server answered", seen.note ?? "no answer") : pass2("reachable", "server answered", REACH_LABEL[seen.reach])
|
|
7347
|
+
);
|
|
7348
|
+
if (seen.reach === "dead") return checks;
|
|
7349
|
+
checks.push(
|
|
7350
|
+
seen.protocolVersion === null ? skip("protocol", "named a protocol version", "it never got as far as saying") : pass2("protocol", "named a protocol version", seen.protocolVersion)
|
|
7351
|
+
);
|
|
7352
|
+
checks.push(
|
|
7353
|
+
seen.serverName === null ? skip("identity", "named itself", "it never got as far as saying") : pass2(
|
|
7354
|
+
"identity",
|
|
7355
|
+
"named itself",
|
|
7356
|
+
`${seen.serverName}${seen.serverVersion === null ? "" : ` ${seen.serverVersion}`}`
|
|
7357
|
+
)
|
|
7358
|
+
);
|
|
7359
|
+
if (seen.tools === null) {
|
|
7360
|
+
checks.push(skip("tools", "listed its tools", REACH_LABEL[seen.reach]));
|
|
7361
|
+
return checks;
|
|
7362
|
+
}
|
|
7363
|
+
checks.push(pass2("tools", "listed its tools", `${seen.tools.length} tool(s)`));
|
|
7364
|
+
const bare = seen.tools.filter((t) => t.inputSchema === null).map((t) => t.name);
|
|
7365
|
+
checks.push(
|
|
7366
|
+
bare.length === 0 ? pass2("input-schemas", "every tool declared an input schema") : fail2(
|
|
7367
|
+
"input-schemas",
|
|
7368
|
+
"every tool declared an input schema",
|
|
7369
|
+
`${bare.length} without one: ${bare.slice(0, 5).join(", ")}`
|
|
7370
|
+
)
|
|
7371
|
+
);
|
|
7372
|
+
const unnamed = seen.tools.filter((t) => t.description === null).map((t) => t.name);
|
|
7373
|
+
checks.push(
|
|
7374
|
+
unnamed.length === 0 ? pass2("descriptions", "every tool said what it does") : fail2(
|
|
7375
|
+
"descriptions",
|
|
7376
|
+
"every tool said what it does",
|
|
7377
|
+
`${unnamed.length} without one: ${unnamed.slice(0, 5).join(", ")}`
|
|
7378
|
+
)
|
|
7379
|
+
);
|
|
7380
|
+
checks.push(pass2("digest", "tool list digest", toolsDigest(seen.tools)));
|
|
7381
|
+
return checks;
|
|
7382
|
+
}
|
|
7383
|
+
async function probeMcpEndpoint(options) {
|
|
7384
|
+
const seen = await probeMcp(options.url, {
|
|
7385
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
7386
|
+
...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
|
|
7387
|
+
...options.userAgent === void 0 ? {} : { userAgent: options.userAgent }
|
|
7388
|
+
});
|
|
7389
|
+
const checks = checksFor(seen);
|
|
7390
|
+
const defects = defectsOf(options.url, seen.httpStatus);
|
|
7391
|
+
return {
|
|
7392
|
+
url: options.url,
|
|
7393
|
+
method: "MCP",
|
|
7394
|
+
observedAt: seen.observedAt,
|
|
7395
|
+
reachable: seen.reach !== "dead",
|
|
7396
|
+
httpStatus: seen.httpStatus,
|
|
7397
|
+
handshakeMs: seen.handshakeMs,
|
|
7398
|
+
termsSource: seen.reach === "payment_required" ? "mcp 402" : "none",
|
|
7399
|
+
advertised: [],
|
|
7400
|
+
requestHash: sha256(`MCP ${options.url}`),
|
|
7401
|
+
responseHash: seen.tools === null ? null : `sha256:${toolsDigest(seen.tools)}`,
|
|
7402
|
+
checks,
|
|
7403
|
+
defects,
|
|
7404
|
+
verdict: verdictOf(checks, defects)
|
|
7405
|
+
};
|
|
7406
|
+
}
|
|
7407
|
+
|
|
7408
|
+
// src/probe.ts
|
|
7409
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
7410
|
+
|
|
7411
|
+
// ../x402/src/handshake.ts
|
|
7412
|
+
var HANDSHAKE_TIMEOUT_MS2 = 3e4;
|
|
7413
|
+
var sendsBody = (method) => method !== "GET" && method !== "HEAD";
|
|
7414
|
+
function handshakeInit(request2) {
|
|
7415
|
+
const carries = sendsBody(request2.method);
|
|
7416
|
+
const headers = { accept: "application/json" };
|
|
7417
|
+
if (carries) headers["content-type"] = "application/json";
|
|
7418
|
+
if (request2.userAgent !== void 0) headers["user-agent"] = request2.userAgent;
|
|
7419
|
+
if (request2.payment) headers[request2.payment.header] = request2.payment.value;
|
|
7420
|
+
return {
|
|
7421
|
+
method: request2.method,
|
|
7422
|
+
headers,
|
|
7423
|
+
...carries ? { body: JSON.stringify(request2.input ?? {}) } : {},
|
|
7424
|
+
signal: AbortSignal.timeout(request2.timeoutMs ?? HANDSHAKE_TIMEOUT_MS2)
|
|
7425
|
+
};
|
|
7426
|
+
}
|
|
7427
|
+
async function handshake2(request2, deps = {}) {
|
|
7428
|
+
const doFetch = deps.fetchImpl ?? fetch;
|
|
7429
|
+
const now = deps.now ?? Date.now;
|
|
7430
|
+
const started = now();
|
|
7431
|
+
const since = () => Math.max(0, now() - started);
|
|
7432
|
+
try {
|
|
7433
|
+
const response = await doFetch(request2.url, handshakeInit(request2));
|
|
7434
|
+
return { ok: true, response, body: await response.text(), latencyMs: since() };
|
|
7435
|
+
} catch (err) {
|
|
7436
|
+
return { ok: false, reason: err.message, latencyMs: since() };
|
|
7437
|
+
}
|
|
7438
|
+
}
|
|
7439
|
+
|
|
7406
7440
|
// src/probe.ts
|
|
7407
7441
|
function sha2562(input) {
|
|
7408
7442
|
return `sha256:${createHash3("sha256").update(input).digest("hex")}`;
|
|
@@ -7449,6 +7483,7 @@ ${body}`);
|
|
|
7449
7483
|
handshakeMs: answer.latencyMs,
|
|
7450
7484
|
responseHash: null,
|
|
7451
7485
|
checks: unreachable(answer.reason),
|
|
7486
|
+
defects: defectsOf(options.url, null),
|
|
7452
7487
|
verdict: "fail"
|
|
7453
7488
|
};
|
|
7454
7489
|
}
|
|
@@ -7462,6 +7497,7 @@ ${body}`);
|
|
|
7462
7497
|
parseError: parsed.parseError,
|
|
7463
7498
|
source: parsed.source
|
|
7464
7499
|
});
|
|
7500
|
+
const defects = defectsOf(options.url, response.status);
|
|
7465
7501
|
return {
|
|
7466
7502
|
...base,
|
|
7467
7503
|
reachable: true,
|
|
@@ -7471,7 +7507,8 @@ ${body}`);
|
|
|
7471
7507
|
advertised: advertisedFrom(parsed.terms),
|
|
7472
7508
|
responseHash: sha2562(text2),
|
|
7473
7509
|
checks,
|
|
7474
|
-
|
|
7510
|
+
defects,
|
|
7511
|
+
verdict: verdictOf(checks, defects)
|
|
7475
7512
|
};
|
|
7476
7513
|
}
|
|
7477
7514
|
function askedForPayment(result) {
|
|
@@ -7524,6 +7561,13 @@ function formatText(result, colour) {
|
|
|
7524
7561
|
lines.push(` ${check.label.padEnd(width)}${mark}`);
|
|
7525
7562
|
if (check.detail) lines.push(` ${paint("dim", check.detail, colour)}`);
|
|
7526
7563
|
}
|
|
7564
|
+
if (result.defects.length > 0) {
|
|
7565
|
+
lines.push("");
|
|
7566
|
+
for (const found of result.defects) {
|
|
7567
|
+
lines.push(` ${found.defect.padEnd(width)}${paint("red", "defect", colour)}`);
|
|
7568
|
+
lines.push(` ${paint("dim", found.says, colour)}`);
|
|
7569
|
+
}
|
|
7570
|
+
}
|
|
7527
7571
|
if (result.advertised.length > 0) {
|
|
7528
7572
|
lines.push("");
|
|
7529
7573
|
lines.push(` ${paint("dim", "advertised", colour)}`);
|
|
@@ -7536,9 +7580,9 @@ function formatText(result, colour) {
|
|
|
7536
7580
|
if (result.responseHash)
|
|
7537
7581
|
lines.push(` ${paint("dim", "response ", colour)}${result.responseHash}`);
|
|
7538
7582
|
lines.push("");
|
|
7539
|
-
const failed = result.checks.filter((c) => c.status === "fail").length;
|
|
7583
|
+
const failed = result.checks.filter((c) => c.status === "fail").length + result.defects.length;
|
|
7540
7584
|
lines.push(
|
|
7541
|
-
result.verdict === "pass" ? ` ${paint("green", "every handshake check passed", colour)}` : ` ${paint("red", `${failed}
|
|
7585
|
+
result.verdict === "pass" ? ` ${paint("green", "every handshake check passed", colour)}` : ` ${paint("red", `${failed} problem${failed === 1 ? "" : "s"} found`, colour)}`
|
|
7542
7586
|
);
|
|
7543
7587
|
lines.push(` ${paint("dim", "this is the unpaid half. correctness needs a purchase.", colour)}`);
|
|
7544
7588
|
lines.push("");
|
|
@@ -7552,8 +7596,8 @@ function formatJson(result) {
|
|
|
7552
7596
|
var USAGE = `
|
|
7553
7597
|
teppi-check <url> [options]
|
|
7554
7598
|
|
|
7555
|
-
Sends one unpaid request and reports what the endpoint advertises,
|
|
7556
|
-
|
|
7599
|
+
Sends one unpaid request and reports what the endpoint advertises, plus
|
|
7600
|
+
anything wrong with the listing itself, in the form the public record uses.
|
|
7557
7601
|
|
|
7558
7602
|
--mcp handshake a remote mcp server instead of an x402 endpoint
|
|
7559
7603
|
--method <verb> tries get then post when not given
|
|
@@ -7562,7 +7606,8 @@ var USAGE = `
|
|
|
7562
7606
|
--timeout <ms> default 15000
|
|
7563
7607
|
--user-agent <ua> default node
|
|
7564
7608
|
|
|
7565
|
-
Exit codes: 0
|
|
7609
|
+
Exit codes: 0 nothing to report, 1 a check failed or a defect was found,
|
|
7610
|
+
2 could not be reached.
|
|
7566
7611
|
`;
|
|
7567
7612
|
function refuseUnlessUsable(url) {
|
|
7568
7613
|
let parsed;
|
package/npm/defects.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type Defect = {
|
|
2
|
+
readonly defect: string;
|
|
3
|
+
readonly says: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function urlHasPlaceholder(url: string): boolean;
|
|
6
|
+
export declare const PROSE_SAYS = "a query value spells out what to put there instead of putting it";
|
|
7
|
+
export declare function pathNotFoundSays(httpStatus: number): string;
|
|
8
|
+
export declare function defectsOf(url: string, httpStatus: number | null): Defect[];
|
|
9
|
+
//# sourceMappingURL=defects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defects.d.ts","sourceRoot":"","sources":["../src/defects.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,MAAM,GAAG;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB,CAAC;AAkBF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,eAAO,MAAM,UAAU,qEAAqE,CAAC;AAK7F,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAO1E"}
|
package/npm/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { type PaymentRequirement, type PaymentTerms, parseTerms, priceOf, schemasOf, } from './terms.js';
|
|
2
2
|
export { type Check, type CheckStatus, runHandshakeChecks, schemaCheck, verdictOf, } from './checks.js';
|
|
3
|
+
export { type Defect, defectsOf, PROSE_SAYS, pathNotFoundSays, urlHasPlaceholder, } from './defects.js';
|
|
3
4
|
export { checksFor, type McpProbeOptions, probeMcpEndpoint, REACH_LABEL, } from './mcp.js';
|
|
4
5
|
export { DEFAULT_USER_AGENT, type ProbeOptions, type ProbeResult, probe } from './probe.js';
|
|
5
6
|
export { formatJson, formatText, shouldColour } from './report.js';
|
package/npm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,UAAU,EACV,OAAO,EACP,SAAS,GACT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,kBAAkB,EAClB,WAAW,EACX,SAAS,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,SAAS,EACT,KAAK,eAAe,EACpB,gBAAgB,EAChB,WAAW,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC5F,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,UAAU,EACV,OAAO,EACP,SAAS,GACT,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,kBAAkB,EAClB,WAAW,EACX,SAAS,GACT,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,KAAK,MAAM,EACX,SAAS,EACT,UAAU,EACV,gBAAgB,EAChB,iBAAiB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,SAAS,EACT,KAAK,eAAe,EACpB,gBAAgB,EAChB,WAAW,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAC5F,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
package/npm/index.js
CHANGED
|
@@ -7011,10 +7011,42 @@ function runHandshakeChecks(input) {
|
|
|
7011
7011
|
function unreachable(reason) {
|
|
7012
7012
|
return [fail("reachable", "endpoint answered", reason)];
|
|
7013
7013
|
}
|
|
7014
|
-
function verdictOf(checks) {
|
|
7014
|
+
function verdictOf(checks, defects) {
|
|
7015
|
+
if (defects.length > 0) return "fail";
|
|
7015
7016
|
return checks.some((c) => c.status === "fail") ? "fail" : "pass";
|
|
7016
7017
|
}
|
|
7017
7018
|
|
|
7019
|
+
// src/defects.ts
|
|
7020
|
+
var PLACEHOLDER = [
|
|
7021
|
+
/[?&][^=&]+=(?:string|integer|number|boolean|array|object)(?:&|$)/,
|
|
7022
|
+
/=0x([0-9a-fA-F])\1{39}(?:&|$)/,
|
|
7023
|
+
/\/:[A-Za-z_][A-Za-z0-9_]*(?:\/|[?]|$)/,
|
|
7024
|
+
/\/[{][A-Za-z_][A-Za-z0-9_]*[}](?:\/|[?]|$)/,
|
|
7025
|
+
// An em dash and an e.g. belong to a sentence, and a value is not one.
|
|
7026
|
+
/[?&][^=&]+=[^&]*(?:%E2%80%94|—)/i,
|
|
7027
|
+
/[?&][^=&]+=[^&]*e\.g\./i,
|
|
7028
|
+
// Anchored at the start of the value, since a real one can mention these words later.
|
|
7029
|
+
/[?&][^=&]+=(?:required|optional)(?:[^a-z]|$)/i,
|
|
7030
|
+
// A value that spells out its own parameter name is describing it, not answering it.
|
|
7031
|
+
/[?&]([a-z_]{3,})=[^&]*\1[^&]*(?:\+|%20)/i
|
|
7032
|
+
];
|
|
7033
|
+
function urlHasPlaceholder(url) {
|
|
7034
|
+
return PLACEHOLDER.some((marker) => marker.test(url));
|
|
7035
|
+
}
|
|
7036
|
+
var PROSE_SAYS = "a query value spells out what to put there instead of putting it";
|
|
7037
|
+
var GONE = /* @__PURE__ */ new Set([404, 410]);
|
|
7038
|
+
function pathNotFoundSays(httpStatus) {
|
|
7039
|
+
return `the listed path answered ${httpStatus}`;
|
|
7040
|
+
}
|
|
7041
|
+
function defectsOf(url, httpStatus) {
|
|
7042
|
+
const found = [];
|
|
7043
|
+
if (urlHasPlaceholder(url)) found.push({ defect: "prose_in_the_url", says: PROSE_SAYS });
|
|
7044
|
+
if (httpStatus !== null && GONE.has(httpStatus)) {
|
|
7045
|
+
found.push({ defect: "path_not_found", says: pathNotFoundSays(httpStatus) });
|
|
7046
|
+
}
|
|
7047
|
+
return found;
|
|
7048
|
+
}
|
|
7049
|
+
|
|
7018
7050
|
// src/mcp.ts
|
|
7019
7051
|
import { createHash as createHash2 } from "node:crypto";
|
|
7020
7052
|
|
|
@@ -7349,6 +7381,7 @@ async function probeMcpEndpoint(options) {
|
|
|
7349
7381
|
...options.userAgent === void 0 ? {} : { userAgent: options.userAgent }
|
|
7350
7382
|
});
|
|
7351
7383
|
const checks = checksFor(seen);
|
|
7384
|
+
const defects = defectsOf(options.url, seen.httpStatus);
|
|
7352
7385
|
return {
|
|
7353
7386
|
url: options.url,
|
|
7354
7387
|
method: "MCP",
|
|
@@ -7361,7 +7394,8 @@ async function probeMcpEndpoint(options) {
|
|
|
7361
7394
|
requestHash: sha256(`MCP ${options.url}`),
|
|
7362
7395
|
responseHash: seen.tools === null ? null : `sha256:${toolsDigest(seen.tools)}`,
|
|
7363
7396
|
checks,
|
|
7364
|
-
|
|
7397
|
+
defects,
|
|
7398
|
+
verdict: verdictOf(checks, defects)
|
|
7365
7399
|
};
|
|
7366
7400
|
}
|
|
7367
7401
|
|
|
@@ -7444,6 +7478,7 @@ ${body}`);
|
|
|
7444
7478
|
handshakeMs: answer.latencyMs,
|
|
7445
7479
|
responseHash: null,
|
|
7446
7480
|
checks: unreachable(answer.reason),
|
|
7481
|
+
defects: defectsOf(options.url, null),
|
|
7447
7482
|
verdict: "fail"
|
|
7448
7483
|
};
|
|
7449
7484
|
}
|
|
@@ -7457,6 +7492,7 @@ ${body}`);
|
|
|
7457
7492
|
parseError: parsed.parseError,
|
|
7458
7493
|
source: parsed.source
|
|
7459
7494
|
});
|
|
7495
|
+
const defects = defectsOf(options.url, response.status);
|
|
7460
7496
|
return {
|
|
7461
7497
|
...base,
|
|
7462
7498
|
reachable: true,
|
|
@@ -7466,7 +7502,8 @@ ${body}`);
|
|
|
7466
7502
|
advertised: advertisedFrom(parsed.terms),
|
|
7467
7503
|
responseHash: sha2562(text2),
|
|
7468
7504
|
checks,
|
|
7469
|
-
|
|
7505
|
+
defects,
|
|
7506
|
+
verdict: verdictOf(checks, defects)
|
|
7470
7507
|
};
|
|
7471
7508
|
}
|
|
7472
7509
|
|
|
@@ -7506,6 +7543,13 @@ function formatText(result, colour) {
|
|
|
7506
7543
|
lines.push(` ${check.label.padEnd(width)}${mark}`);
|
|
7507
7544
|
if (check.detail) lines.push(` ${paint("dim", check.detail, colour)}`);
|
|
7508
7545
|
}
|
|
7546
|
+
if (result.defects.length > 0) {
|
|
7547
|
+
lines.push("");
|
|
7548
|
+
for (const found of result.defects) {
|
|
7549
|
+
lines.push(` ${found.defect.padEnd(width)}${paint("red", "defect", colour)}`);
|
|
7550
|
+
lines.push(` ${paint("dim", found.says, colour)}`);
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7509
7553
|
if (result.advertised.length > 0) {
|
|
7510
7554
|
lines.push("");
|
|
7511
7555
|
lines.push(` ${paint("dim", "advertised", colour)}`);
|
|
@@ -7518,9 +7562,9 @@ function formatText(result, colour) {
|
|
|
7518
7562
|
if (result.responseHash)
|
|
7519
7563
|
lines.push(` ${paint("dim", "response ", colour)}${result.responseHash}`);
|
|
7520
7564
|
lines.push("");
|
|
7521
|
-
const failed = result.checks.filter((c) => c.status === "fail").length;
|
|
7565
|
+
const failed = result.checks.filter((c) => c.status === "fail").length + result.defects.length;
|
|
7522
7566
|
lines.push(
|
|
7523
|
-
result.verdict === "pass" ? ` ${paint("green", "every handshake check passed", colour)}` : ` ${paint("red", `${failed}
|
|
7567
|
+
result.verdict === "pass" ? ` ${paint("green", "every handshake check passed", colour)}` : ` ${paint("red", `${failed} problem${failed === 1 ? "" : "s"} found`, colour)}`
|
|
7524
7568
|
);
|
|
7525
7569
|
lines.push(` ${paint("dim", "this is the unpaid half. correctness needs a purchase.", colour)}`);
|
|
7526
7570
|
lines.push("");
|
|
@@ -7531,11 +7575,14 @@ function formatJson(result) {
|
|
|
7531
7575
|
}
|
|
7532
7576
|
export {
|
|
7533
7577
|
DEFAULT_USER_AGENT,
|
|
7578
|
+
PROSE_SAYS,
|
|
7534
7579
|
REACH_LABEL,
|
|
7535
7580
|
checksFor,
|
|
7581
|
+
defectsOf,
|
|
7536
7582
|
formatJson,
|
|
7537
7583
|
formatText,
|
|
7538
7584
|
parseTerms,
|
|
7585
|
+
pathNotFoundSays,
|
|
7539
7586
|
priceOf,
|
|
7540
7587
|
probe,
|
|
7541
7588
|
probeMcpEndpoint,
|
|
@@ -7543,5 +7590,6 @@ export {
|
|
|
7543
7590
|
schemaCheck,
|
|
7544
7591
|
schemasOf,
|
|
7545
7592
|
shouldColour,
|
|
7593
|
+
urlHasPlaceholder,
|
|
7546
7594
|
verdictOf
|
|
7547
7595
|
};
|
package/npm/mcp.d.ts
CHANGED
package/npm/mcp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAAyB,MAAM,kBAAkB,CAAC;AAC9E,OAAO,
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,cAAc,EAAyB,MAAM,kBAAkB,CAAC;AAC9E,OAAO,EAAE,KAAK,KAAK,EAAa,MAAM,aAAa,CAAC;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAClC,CAAC;AAwBF,eAAO,MAAM,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAMzE,CAAC;AAEF,wBAAgB,SAAS,CAAC,IAAI,EAAE,cAAc,GAAG,KAAK,EAAE,CA2DvD;AAGD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAwBrF"}
|
package/npm/probe.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Check } from './checks.js';
|
|
2
|
+
import { type Defect } from './defects.js';
|
|
2
3
|
export declare const DEFAULT_USER_AGENT = "node";
|
|
3
4
|
export type ProbeOptions = {
|
|
4
5
|
readonly url: string;
|
|
@@ -25,6 +26,7 @@ export type ProbeResult = {
|
|
|
25
26
|
readonly requestHash: string;
|
|
26
27
|
readonly responseHash: string | null;
|
|
27
28
|
readonly checks: readonly Check[];
|
|
29
|
+
readonly defects: readonly Defect[];
|
|
28
30
|
readonly verdict: 'pass' | 'fail';
|
|
29
31
|
};
|
|
30
32
|
export declare function probe(options: ProbeOptions): Promise<ProbeResult>;
|
package/npm/probe.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,KAAK,EAA8C,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,KAAK,EAA8C,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,KAAK,MAAM,EAAa,MAAM,cAAc,CAAC;AAGtD,eAAO,MAAM,kBAAkB,SAAS,CAAC;AAEzC,MAAM,MAAM,YAAY,GAAG;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1F,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CAClC,CAAC;AAgBF,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAmEvE;AAGD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAE5D;AAGD,wBAAsB,eAAe,CACpC,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,EAC7C,QAAQ,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,CAAC,CAStB"}
|
package/npm/report.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAW9C,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAI7F;AASD,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAW9C,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAI7F;AASD,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAsDvE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAEtD"}
|