teppi-check 0.2.0 → 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 +263 -171
- 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 +107 -26
- package/npm/mcp-probe/index.d.ts +1 -1
- package/npm/mcp-probe/probe.d.ts +9 -1
- package/npm/mcp.d.ts +1 -1
- package/npm/mcp.d.ts.map +1 -1
- package/npm/probe.d.ts +4 -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
|
@@ -6955,7 +6955,8 @@ function toolsFrom(result) {
|
|
|
6955
6955
|
{
|
|
6956
6956
|
name,
|
|
6957
6957
|
description: text(tool.description),
|
|
6958
|
-
|
|
6958
|
+
inputSchema: tool.inputSchema ?? null,
|
|
6959
|
+
outputSchema: tool.outputSchema ?? null
|
|
6959
6960
|
}
|
|
6960
6961
|
];
|
|
6961
6962
|
});
|
|
@@ -6981,6 +6982,8 @@ async function turn(response, method) {
|
|
|
6981
6982
|
};
|
|
6982
6983
|
}
|
|
6983
6984
|
var DEADLINE_MS = 45e3;
|
|
6985
|
+
var MAX_PAGES = 20;
|
|
6986
|
+
var MAX_TOOLS = 2e3;
|
|
6984
6987
|
async function probeMcp(url, options = {}) {
|
|
6985
6988
|
const clockFor = options.now ?? Date.now;
|
|
6986
6989
|
const startedFor = clockFor();
|
|
@@ -6996,7 +6999,10 @@ async function probeMcp(url, options = {}) {
|
|
|
6996
6999
|
protocolVersion: null,
|
|
6997
7000
|
serverName: null,
|
|
6998
7001
|
serverVersion: null,
|
|
7002
|
+
instructions: null,
|
|
6999
7003
|
tools: null,
|
|
7004
|
+
pages: null,
|
|
7005
|
+
truncated: false,
|
|
7000
7006
|
note: "never answered and never gave up"
|
|
7001
7007
|
}),
|
|
7002
7008
|
options.deadlineMs ?? DEADLINE_MS
|
|
@@ -7008,6 +7014,36 @@ async function probeMcp(url, options = {}) {
|
|
|
7008
7014
|
clearTimeout(timer);
|
|
7009
7015
|
}
|
|
7010
7016
|
}
|
|
7017
|
+
async function walkTools(send, session) {
|
|
7018
|
+
const tools = [];
|
|
7019
|
+
let cursor = null;
|
|
7020
|
+
let pages = 0;
|
|
7021
|
+
let status = 200;
|
|
7022
|
+
for (; ; ) {
|
|
7023
|
+
const asked = request(2 + pages, "tools/list", cursor === null ? void 0 : { cursor });
|
|
7024
|
+
const listed = await turn(await send(asked, session), "tools/list");
|
|
7025
|
+
if (listed.kind === "refused") {
|
|
7026
|
+
return {
|
|
7027
|
+
kind: "refused",
|
|
7028
|
+
reach: listed.reach,
|
|
7029
|
+
status: listed.status,
|
|
7030
|
+
note: "it opened but would not list"
|
|
7031
|
+
};
|
|
7032
|
+
}
|
|
7033
|
+
if (listed.kind === "broken")
|
|
7034
|
+
return { kind: "broken", status: listed.status, note: listed.note };
|
|
7035
|
+
tools.push(...toolsFrom(listed.value));
|
|
7036
|
+
status = listed.status;
|
|
7037
|
+
pages += 1;
|
|
7038
|
+
const next = text(listed.value.nextCursor);
|
|
7039
|
+
if (next === null || next === cursor)
|
|
7040
|
+
return { kind: "listed", status, tools, pages, truncated: false };
|
|
7041
|
+
if (pages >= MAX_PAGES || tools.length >= MAX_TOOLS) {
|
|
7042
|
+
return { kind: "listed", status, tools, pages, truncated: true };
|
|
7043
|
+
}
|
|
7044
|
+
cursor = next;
|
|
7045
|
+
}
|
|
7046
|
+
}
|
|
7011
7047
|
async function handshake(url, options) {
|
|
7012
7048
|
const doFetch = options.fetchImpl ?? fetch;
|
|
7013
7049
|
const clock = options.now ?? Date.now;
|
|
@@ -7019,7 +7055,10 @@ async function handshake(url, options) {
|
|
|
7019
7055
|
protocolVersion: null,
|
|
7020
7056
|
serverName: null,
|
|
7021
7057
|
serverVersion: null,
|
|
7022
|
-
|
|
7058
|
+
instructions: null,
|
|
7059
|
+
tools: null,
|
|
7060
|
+
pages: null,
|
|
7061
|
+
truncated: false
|
|
7023
7062
|
};
|
|
7024
7063
|
const send = (body, sessionId) => doFetch(url, {
|
|
7025
7064
|
...initFor(body, sessionId, userAgent),
|
|
@@ -7053,34 +7092,28 @@ async function handshake(url, options) {
|
|
|
7053
7092
|
...base,
|
|
7054
7093
|
protocolVersion: text(hello.value.protocolVersion),
|
|
7055
7094
|
serverName: text(info.name),
|
|
7056
|
-
serverVersion: text(info.version)
|
|
7095
|
+
serverVersion: text(info.version),
|
|
7096
|
+
instructions: text(hello.value.instructions)
|
|
7057
7097
|
};
|
|
7058
7098
|
await send(notification("notifications/initialized"), session).catch(() => void 0);
|
|
7059
|
-
const
|
|
7060
|
-
if (
|
|
7099
|
+
const walked = await walkTools(send, session);
|
|
7100
|
+
if (walked.kind !== "listed") {
|
|
7061
7101
|
return {
|
|
7062
7102
|
...said,
|
|
7063
|
-
reach:
|
|
7064
|
-
httpStatus:
|
|
7103
|
+
reach: walked.kind === "refused" ? walked.reach : "error",
|
|
7104
|
+
httpStatus: walked.status,
|
|
7065
7105
|
handshakeMs: took(),
|
|
7066
|
-
note:
|
|
7067
|
-
};
|
|
7068
|
-
}
|
|
7069
|
-
if (listed.kind === "broken") {
|
|
7070
|
-
return {
|
|
7071
|
-
...said,
|
|
7072
|
-
reach: "error",
|
|
7073
|
-
httpStatus: listed.status,
|
|
7074
|
-
handshakeMs: took(),
|
|
7075
|
-
note: listed.note
|
|
7106
|
+
note: walked.note
|
|
7076
7107
|
};
|
|
7077
7108
|
}
|
|
7078
7109
|
return {
|
|
7079
7110
|
...said,
|
|
7080
7111
|
reach: "readable",
|
|
7081
|
-
httpStatus:
|
|
7112
|
+
httpStatus: walked.status,
|
|
7082
7113
|
handshakeMs: took(),
|
|
7083
|
-
tools:
|
|
7114
|
+
tools: walked.tools,
|
|
7115
|
+
pages: walked.pages,
|
|
7116
|
+
truncated: walked.truncated,
|
|
7084
7117
|
note: null
|
|
7085
7118
|
};
|
|
7086
7119
|
} catch (err) {
|
|
@@ -7096,130 +7129,11 @@ async function handshake(url, options) {
|
|
|
7096
7129
|
}
|
|
7097
7130
|
function toolsDigest(tools) {
|
|
7098
7131
|
const canonical = JSON.stringify(
|
|
7099
|
-
[...tools].map((t) => [t.name, t.description ?? "", t.
|
|
7132
|
+
[...tools].map((t) => [t.name, t.description ?? "", t.inputSchema !== null]).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
|
|
7100
7133
|
);
|
|
7101
7134
|
return createHash("sha256").update(canonical).digest("hex");
|
|
7102
7135
|
}
|
|
7103
7136
|
|
|
7104
|
-
// src/mcp.ts
|
|
7105
|
-
function sha256(input) {
|
|
7106
|
-
return `sha256:${createHash2("sha256").update(input).digest("hex")}`;
|
|
7107
|
-
}
|
|
7108
|
-
var pass = (id, label, detail) => detail === void 0 ? { id, label, status: "pass" } : { id, label, status: "pass", detail };
|
|
7109
|
-
var fail = (id, label, detail) => ({
|
|
7110
|
-
id,
|
|
7111
|
-
label,
|
|
7112
|
-
status: "fail",
|
|
7113
|
-
detail
|
|
7114
|
-
});
|
|
7115
|
-
var skip = (id, label, detail) => ({
|
|
7116
|
-
id,
|
|
7117
|
-
label,
|
|
7118
|
-
status: "skip",
|
|
7119
|
-
detail
|
|
7120
|
-
});
|
|
7121
|
-
var REACH_LABEL = {
|
|
7122
|
-
readable: "listed its tools",
|
|
7123
|
-
auth_required: "asked for credentials",
|
|
7124
|
-
payment_required: "asked for payment",
|
|
7125
|
-
dead: "did not answer",
|
|
7126
|
-
error: "answered with something else"
|
|
7127
|
-
};
|
|
7128
|
-
function checksFor(seen) {
|
|
7129
|
-
const checks = [];
|
|
7130
|
-
checks.push(
|
|
7131
|
-
seen.reach === "dead" ? fail("reachable", "server answered", seen.note ?? "no answer") : pass("reachable", "server answered", REACH_LABEL[seen.reach])
|
|
7132
|
-
);
|
|
7133
|
-
if (seen.reach === "dead") return checks;
|
|
7134
|
-
checks.push(
|
|
7135
|
-
seen.protocolVersion === null ? skip("protocol", "named a protocol version", "it never got as far as saying") : pass("protocol", "named a protocol version", seen.protocolVersion)
|
|
7136
|
-
);
|
|
7137
|
-
checks.push(
|
|
7138
|
-
seen.serverName === null ? skip("identity", "named itself", "it never got as far as saying") : pass(
|
|
7139
|
-
"identity",
|
|
7140
|
-
"named itself",
|
|
7141
|
-
`${seen.serverName}${seen.serverVersion === null ? "" : ` ${seen.serverVersion}`}`
|
|
7142
|
-
)
|
|
7143
|
-
);
|
|
7144
|
-
if (seen.tools === null) {
|
|
7145
|
-
checks.push(skip("tools", "listed its tools", REACH_LABEL[seen.reach]));
|
|
7146
|
-
return checks;
|
|
7147
|
-
}
|
|
7148
|
-
checks.push(pass("tools", "listed its tools", `${seen.tools.length} tool(s)`));
|
|
7149
|
-
const bare = seen.tools.filter((t) => !t.hasInputSchema).map((t) => t.name);
|
|
7150
|
-
checks.push(
|
|
7151
|
-
bare.length === 0 ? pass("input-schemas", "every tool declared an input schema") : fail(
|
|
7152
|
-
"input-schemas",
|
|
7153
|
-
"every tool declared an input schema",
|
|
7154
|
-
`${bare.length} without one: ${bare.slice(0, 5).join(", ")}`
|
|
7155
|
-
)
|
|
7156
|
-
);
|
|
7157
|
-
const unnamed = seen.tools.filter((t) => t.description === null).map((t) => t.name);
|
|
7158
|
-
checks.push(
|
|
7159
|
-
unnamed.length === 0 ? pass("descriptions", "every tool said what it does") : fail(
|
|
7160
|
-
"descriptions",
|
|
7161
|
-
"every tool said what it does",
|
|
7162
|
-
`${unnamed.length} without one: ${unnamed.slice(0, 5).join(", ")}`
|
|
7163
|
-
)
|
|
7164
|
-
);
|
|
7165
|
-
checks.push(pass("digest", "tool list digest", toolsDigest(seen.tools)));
|
|
7166
|
-
return checks;
|
|
7167
|
-
}
|
|
7168
|
-
async function probeMcpEndpoint(options) {
|
|
7169
|
-
const seen = await probeMcp(options.url, {
|
|
7170
|
-
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
7171
|
-
...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
|
|
7172
|
-
...options.userAgent === void 0 ? {} : { userAgent: options.userAgent }
|
|
7173
|
-
});
|
|
7174
|
-
const checks = checksFor(seen);
|
|
7175
|
-
return {
|
|
7176
|
-
url: options.url,
|
|
7177
|
-
method: "MCP",
|
|
7178
|
-
observedAt: seen.observedAt,
|
|
7179
|
-
reachable: seen.reach !== "dead",
|
|
7180
|
-
httpStatus: seen.httpStatus,
|
|
7181
|
-
handshakeMs: seen.handshakeMs,
|
|
7182
|
-
termsSource: seen.reach === "payment_required" ? "mcp 402" : "none",
|
|
7183
|
-
advertised: [],
|
|
7184
|
-
requestHash: sha256(`MCP ${options.url}`),
|
|
7185
|
-
responseHash: seen.tools === null ? null : `sha256:${toolsDigest(seen.tools)}`,
|
|
7186
|
-
checks,
|
|
7187
|
-
verdict: checks.some((c) => c.status === "fail") ? "fail" : "pass"
|
|
7188
|
-
};
|
|
7189
|
-
}
|
|
7190
|
-
|
|
7191
|
-
// src/probe.ts
|
|
7192
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
7193
|
-
|
|
7194
|
-
// ../x402/src/handshake.ts
|
|
7195
|
-
var HANDSHAKE_TIMEOUT_MS2 = 3e4;
|
|
7196
|
-
var sendsBody = (method) => method !== "GET" && method !== "HEAD";
|
|
7197
|
-
function handshakeInit(request2) {
|
|
7198
|
-
const carries = sendsBody(request2.method);
|
|
7199
|
-
const headers = { accept: "application/json" };
|
|
7200
|
-
if (carries) headers["content-type"] = "application/json";
|
|
7201
|
-
if (request2.userAgent !== void 0) headers["user-agent"] = request2.userAgent;
|
|
7202
|
-
if (request2.payment) headers[request2.payment.header] = request2.payment.value;
|
|
7203
|
-
return {
|
|
7204
|
-
method: request2.method,
|
|
7205
|
-
headers,
|
|
7206
|
-
...carries ? { body: JSON.stringify(request2.input ?? {}) } : {},
|
|
7207
|
-
signal: AbortSignal.timeout(request2.timeoutMs ?? HANDSHAKE_TIMEOUT_MS2)
|
|
7208
|
-
};
|
|
7209
|
-
}
|
|
7210
|
-
async function handshake2(request2, deps = {}) {
|
|
7211
|
-
const doFetch = deps.fetchImpl ?? fetch;
|
|
7212
|
-
const now = deps.now ?? Date.now;
|
|
7213
|
-
const started = now();
|
|
7214
|
-
const since = () => Math.max(0, now() - started);
|
|
7215
|
-
try {
|
|
7216
|
-
const response = await doFetch(request2.url, handshakeInit(request2));
|
|
7217
|
-
return { ok: true, response, body: await response.text(), latencyMs: since() };
|
|
7218
|
-
} catch (err) {
|
|
7219
|
-
return { ok: false, reason: err.message, latencyMs: since() };
|
|
7220
|
-
}
|
|
7221
|
-
}
|
|
7222
|
-
|
|
7223
7137
|
// ../x402/src/terms.ts
|
|
7224
7138
|
var HEADER_NAMES = ["payment-required", "x-payment-required", "www-authenticate-payment"];
|
|
7225
7139
|
function asTerms(value) {
|
|
@@ -7291,24 +7205,24 @@ var ajv = new import_ajv.Ajv({
|
|
|
7291
7205
|
validateSchema: true,
|
|
7292
7206
|
validateFormats: false
|
|
7293
7207
|
});
|
|
7294
|
-
function
|
|
7208
|
+
function pass(id, label, detail) {
|
|
7295
7209
|
return detail === void 0 ? { id, label, status: "pass" } : { id, label, status: "pass", detail };
|
|
7296
7210
|
}
|
|
7297
|
-
function
|
|
7211
|
+
function fail(id, label, detail) {
|
|
7298
7212
|
return { id, label, status: "fail", detail };
|
|
7299
7213
|
}
|
|
7300
7214
|
function schemaCheck(id, label, schema) {
|
|
7301
7215
|
if (schema === null || schema === void 0) {
|
|
7302
|
-
return
|
|
7216
|
+
return fail(id, label, "not declared, so no response can ever be checked against it");
|
|
7303
7217
|
}
|
|
7304
7218
|
if (typeof schema !== "object") {
|
|
7305
|
-
return
|
|
7219
|
+
return fail(id, label, `declared as ${typeof schema}, not an object`);
|
|
7306
7220
|
}
|
|
7307
7221
|
try {
|
|
7308
7222
|
ajv.compile(schema);
|
|
7309
|
-
return
|
|
7223
|
+
return pass(id, label);
|
|
7310
7224
|
} catch (err) {
|
|
7311
|
-
return
|
|
7225
|
+
return fail(id, label, `declared but invalid: ${err.message.split("\n")[0]}`);
|
|
7312
7226
|
}
|
|
7313
7227
|
}
|
|
7314
7228
|
function requirementChecks(terms) {
|
|
@@ -7316,7 +7230,7 @@ function requirementChecks(terms) {
|
|
|
7316
7230
|
const report = (id, label, has) => {
|
|
7317
7231
|
const gaps = terms.accepts.map((r, i) => has(r) ? -1 : i).filter((i) => i >= 0);
|
|
7318
7232
|
out.push(
|
|
7319
|
-
gaps.length === 0 ?
|
|
7233
|
+
gaps.length === 0 ? pass(id, label) : fail(id, label, `absent from accepts[${gaps.join("], accepts[")}]`)
|
|
7320
7234
|
);
|
|
7321
7235
|
};
|
|
7322
7236
|
report("price_declared", "price is stated", (r) => priceOf(r) !== null);
|
|
@@ -7333,29 +7247,29 @@ function requirementChecks(terms) {
|
|
|
7333
7247
|
return out;
|
|
7334
7248
|
}
|
|
7335
7249
|
function runHandshakeChecks(input) {
|
|
7336
|
-
const checks = [
|
|
7250
|
+
const checks = [pass("reachable", "endpoint answered")];
|
|
7337
7251
|
checks.push(
|
|
7338
|
-
input.status === 402 ?
|
|
7252
|
+
input.status === 402 ? pass("status_402", "asks for payment") : fail("status_402", "asks for payment", `returned ${input.status}, not 402`)
|
|
7339
7253
|
);
|
|
7340
7254
|
if (input.terms === null) {
|
|
7341
7255
|
checks.push(
|
|
7342
|
-
|
|
7256
|
+
fail("terms_parseable", "payment terms parse", input.parseError ?? "no terms found")
|
|
7343
7257
|
);
|
|
7344
7258
|
return checks;
|
|
7345
7259
|
}
|
|
7346
|
-
checks.push(
|
|
7260
|
+
checks.push(pass("terms_parseable", "payment terms parse", `read from the ${input.source}`));
|
|
7347
7261
|
checks.push(
|
|
7348
|
-
input.terms.accepts.length > 0 ?
|
|
7262
|
+
input.terms.accepts.length > 0 ? pass(
|
|
7349
7263
|
"accepts_present",
|
|
7350
7264
|
"offers at least one way to pay",
|
|
7351
7265
|
`${input.terms.accepts.length} option(s)`
|
|
7352
|
-
) :
|
|
7266
|
+
) : fail("accepts_present", "offers at least one way to pay", "accepts is empty")
|
|
7353
7267
|
);
|
|
7354
7268
|
if (input.terms.accepts.length === 0) return checks;
|
|
7355
7269
|
checks.push(...requirementChecks(input.terms));
|
|
7356
7270
|
const cache = input.headers.get("cache-control") ?? "";
|
|
7357
7271
|
checks.push(
|
|
7358
|
-
/no-store|no-cache/i.test(cache) ?
|
|
7272
|
+
/no-store|no-cache/i.test(cache) ? pass("not_cacheable", "payment terms are not cached") : fail(
|
|
7359
7273
|
"not_cacheable",
|
|
7360
7274
|
"payment terms are not cached",
|
|
7361
7275
|
cache === "" ? "no cache-control, so a proxy may serve stale terms" : `cache-control: ${cache}`
|
|
@@ -7364,12 +7278,165 @@ function runHandshakeChecks(input) {
|
|
|
7364
7278
|
return checks;
|
|
7365
7279
|
}
|
|
7366
7280
|
function unreachable(reason) {
|
|
7367
|
-
return [
|
|
7281
|
+
return [fail("reachable", "endpoint answered", reason)];
|
|
7368
7282
|
}
|
|
7369
|
-
function verdictOf(checks) {
|
|
7283
|
+
function verdictOf(checks, defects) {
|
|
7284
|
+
if (defects.length > 0) return "fail";
|
|
7370
7285
|
return checks.some((c) => c.status === "fail") ? "fail" : "pass";
|
|
7371
7286
|
}
|
|
7372
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
|
+
|
|
7373
7440
|
// src/probe.ts
|
|
7374
7441
|
function sha2562(input) {
|
|
7375
7442
|
return `sha256:${createHash3("sha256").update(input).digest("hex")}`;
|
|
@@ -7416,6 +7483,7 @@ ${body}`);
|
|
|
7416
7483
|
handshakeMs: answer.latencyMs,
|
|
7417
7484
|
responseHash: null,
|
|
7418
7485
|
checks: unreachable(answer.reason),
|
|
7486
|
+
defects: defectsOf(options.url, null),
|
|
7419
7487
|
verdict: "fail"
|
|
7420
7488
|
};
|
|
7421
7489
|
}
|
|
@@ -7429,6 +7497,7 @@ ${body}`);
|
|
|
7429
7497
|
parseError: parsed.parseError,
|
|
7430
7498
|
source: parsed.source
|
|
7431
7499
|
});
|
|
7500
|
+
const defects = defectsOf(options.url, response.status);
|
|
7432
7501
|
return {
|
|
7433
7502
|
...base,
|
|
7434
7503
|
reachable: true,
|
|
@@ -7438,9 +7507,23 @@ ${body}`);
|
|
|
7438
7507
|
advertised: advertisedFrom(parsed.terms),
|
|
7439
7508
|
responseHash: sha2562(text2),
|
|
7440
7509
|
checks,
|
|
7441
|
-
|
|
7510
|
+
defects,
|
|
7511
|
+
verdict: verdictOf(checks, defects)
|
|
7442
7512
|
};
|
|
7443
7513
|
}
|
|
7514
|
+
function askedForPayment(result) {
|
|
7515
|
+
return result.checks.some((c) => c.id === "status_402" && c.status === "pass");
|
|
7516
|
+
}
|
|
7517
|
+
async function probeEitherVerb(run, explicit) {
|
|
7518
|
+
const verbs = explicit === void 0 ? ["GET", "POST"] : [explicit.toUpperCase()];
|
|
7519
|
+
let first;
|
|
7520
|
+
for (const method of verbs) {
|
|
7521
|
+
const attempt = await run(method);
|
|
7522
|
+
if (askedForPayment(attempt)) return attempt;
|
|
7523
|
+
first ??= attempt;
|
|
7524
|
+
}
|
|
7525
|
+
return first;
|
|
7526
|
+
}
|
|
7444
7527
|
|
|
7445
7528
|
// src/report.ts
|
|
7446
7529
|
var ANSI = {
|
|
@@ -7478,6 +7561,13 @@ function formatText(result, colour) {
|
|
|
7478
7561
|
lines.push(` ${check.label.padEnd(width)}${mark}`);
|
|
7479
7562
|
if (check.detail) lines.push(` ${paint("dim", check.detail, colour)}`);
|
|
7480
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
|
+
}
|
|
7481
7571
|
if (result.advertised.length > 0) {
|
|
7482
7572
|
lines.push("");
|
|
7483
7573
|
lines.push(` ${paint("dim", "advertised", colour)}`);
|
|
@@ -7490,9 +7580,9 @@ function formatText(result, colour) {
|
|
|
7490
7580
|
if (result.responseHash)
|
|
7491
7581
|
lines.push(` ${paint("dim", "response ", colour)}${result.responseHash}`);
|
|
7492
7582
|
lines.push("");
|
|
7493
|
-
const failed = result.checks.filter((c) => c.status === "fail").length;
|
|
7583
|
+
const failed = result.checks.filter((c) => c.status === "fail").length + result.defects.length;
|
|
7494
7584
|
lines.push(
|
|
7495
|
-
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)}`
|
|
7496
7586
|
);
|
|
7497
7587
|
lines.push(` ${paint("dim", "this is the unpaid half. correctness needs a purchase.", colour)}`);
|
|
7498
7588
|
lines.push("");
|
|
@@ -7506,17 +7596,18 @@ function formatJson(result) {
|
|
|
7506
7596
|
var USAGE = `
|
|
7507
7597
|
teppi-check <url> [options]
|
|
7508
7598
|
|
|
7509
|
-
Sends one unpaid request and reports what the endpoint advertises,
|
|
7510
|
-
|
|
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.
|
|
7511
7601
|
|
|
7512
7602
|
--mcp handshake a remote mcp server instead of an x402 endpoint
|
|
7513
|
-
--method <verb>
|
|
7603
|
+
--method <verb> tries get then post when not given
|
|
7514
7604
|
--body <json|@file> default {}
|
|
7515
7605
|
--json machine readable output
|
|
7516
7606
|
--timeout <ms> default 15000
|
|
7517
7607
|
--user-agent <ua> default node
|
|
7518
7608
|
|
|
7519
|
-
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.
|
|
7520
7611
|
`;
|
|
7521
7612
|
function refuseUnlessUsable(url) {
|
|
7522
7613
|
let parsed;
|
|
@@ -7559,17 +7650,18 @@ async function main() {
|
|
|
7559
7650
|
...values["user-agent"] ? { userAgent: values["user-agent"] } : {}
|
|
7560
7651
|
};
|
|
7561
7652
|
const rawBody = values.body ?? "{}";
|
|
7562
|
-
const
|
|
7563
|
-
|
|
7564
|
-
method
|
|
7565
|
-
|
|
7566
|
-
|
|
7653
|
+
const body = rawBody.startsWith("@") ? readFileSync(rawBody.slice(1), "utf8") : rawBody;
|
|
7654
|
+
const found = await probeEitherVerb(
|
|
7655
|
+
(method) => probe({ ...shared, method, body }),
|
|
7656
|
+
values.method
|
|
7657
|
+
);
|
|
7658
|
+
const out = values.mcp ? await probeMcpEndpoint(shared) : found;
|
|
7567
7659
|
stdout.write(
|
|
7568
|
-
values.json ? `${formatJson(
|
|
7569
|
-
` : formatText(
|
|
7660
|
+
values.json ? `${formatJson(out)}
|
|
7661
|
+
` : formatText(out, shouldColour(env, stdout.isTTY === true))
|
|
7570
7662
|
);
|
|
7571
|
-
if (!
|
|
7572
|
-
exit(
|
|
7663
|
+
if (!out.reachable) exit(2);
|
|
7664
|
+
exit(out.verdict === "pass" ? 0 : 1);
|
|
7573
7665
|
}
|
|
7574
7666
|
main().catch((err) => {
|
|
7575
7667
|
stdout.write(`${err.message}
|
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
|
|
|
@@ -7099,7 +7131,8 @@ function toolsFrom(result) {
|
|
|
7099
7131
|
{
|
|
7100
7132
|
name,
|
|
7101
7133
|
description: text(tool.description),
|
|
7102
|
-
|
|
7134
|
+
inputSchema: tool.inputSchema ?? null,
|
|
7135
|
+
outputSchema: tool.outputSchema ?? null
|
|
7103
7136
|
}
|
|
7104
7137
|
];
|
|
7105
7138
|
});
|
|
@@ -7125,6 +7158,8 @@ async function turn(response, method) {
|
|
|
7125
7158
|
};
|
|
7126
7159
|
}
|
|
7127
7160
|
var DEADLINE_MS = 45e3;
|
|
7161
|
+
var MAX_PAGES = 20;
|
|
7162
|
+
var MAX_TOOLS = 2e3;
|
|
7128
7163
|
async function probeMcp(url, options = {}) {
|
|
7129
7164
|
const clockFor = options.now ?? Date.now;
|
|
7130
7165
|
const startedFor = clockFor();
|
|
@@ -7140,7 +7175,10 @@ async function probeMcp(url, options = {}) {
|
|
|
7140
7175
|
protocolVersion: null,
|
|
7141
7176
|
serverName: null,
|
|
7142
7177
|
serverVersion: null,
|
|
7178
|
+
instructions: null,
|
|
7143
7179
|
tools: null,
|
|
7180
|
+
pages: null,
|
|
7181
|
+
truncated: false,
|
|
7144
7182
|
note: "never answered and never gave up"
|
|
7145
7183
|
}),
|
|
7146
7184
|
options.deadlineMs ?? DEADLINE_MS
|
|
@@ -7152,6 +7190,36 @@ async function probeMcp(url, options = {}) {
|
|
|
7152
7190
|
clearTimeout(timer);
|
|
7153
7191
|
}
|
|
7154
7192
|
}
|
|
7193
|
+
async function walkTools(send, session) {
|
|
7194
|
+
const tools = [];
|
|
7195
|
+
let cursor = null;
|
|
7196
|
+
let pages = 0;
|
|
7197
|
+
let status = 200;
|
|
7198
|
+
for (; ; ) {
|
|
7199
|
+
const asked = request(2 + pages, "tools/list", cursor === null ? void 0 : { cursor });
|
|
7200
|
+
const listed = await turn(await send(asked, session), "tools/list");
|
|
7201
|
+
if (listed.kind === "refused") {
|
|
7202
|
+
return {
|
|
7203
|
+
kind: "refused",
|
|
7204
|
+
reach: listed.reach,
|
|
7205
|
+
status: listed.status,
|
|
7206
|
+
note: "it opened but would not list"
|
|
7207
|
+
};
|
|
7208
|
+
}
|
|
7209
|
+
if (listed.kind === "broken")
|
|
7210
|
+
return { kind: "broken", status: listed.status, note: listed.note };
|
|
7211
|
+
tools.push(...toolsFrom(listed.value));
|
|
7212
|
+
status = listed.status;
|
|
7213
|
+
pages += 1;
|
|
7214
|
+
const next = text(listed.value.nextCursor);
|
|
7215
|
+
if (next === null || next === cursor)
|
|
7216
|
+
return { kind: "listed", status, tools, pages, truncated: false };
|
|
7217
|
+
if (pages >= MAX_PAGES || tools.length >= MAX_TOOLS) {
|
|
7218
|
+
return { kind: "listed", status, tools, pages, truncated: true };
|
|
7219
|
+
}
|
|
7220
|
+
cursor = next;
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7155
7223
|
async function handshake(url, options) {
|
|
7156
7224
|
const doFetch = options.fetchImpl ?? fetch;
|
|
7157
7225
|
const clock = options.now ?? Date.now;
|
|
@@ -7163,7 +7231,10 @@ async function handshake(url, options) {
|
|
|
7163
7231
|
protocolVersion: null,
|
|
7164
7232
|
serverName: null,
|
|
7165
7233
|
serverVersion: null,
|
|
7166
|
-
|
|
7234
|
+
instructions: null,
|
|
7235
|
+
tools: null,
|
|
7236
|
+
pages: null,
|
|
7237
|
+
truncated: false
|
|
7167
7238
|
};
|
|
7168
7239
|
const send = (body, sessionId) => doFetch(url, {
|
|
7169
7240
|
...initFor(body, sessionId, userAgent),
|
|
@@ -7197,34 +7268,28 @@ async function handshake(url, options) {
|
|
|
7197
7268
|
...base,
|
|
7198
7269
|
protocolVersion: text(hello.value.protocolVersion),
|
|
7199
7270
|
serverName: text(info.name),
|
|
7200
|
-
serverVersion: text(info.version)
|
|
7271
|
+
serverVersion: text(info.version),
|
|
7272
|
+
instructions: text(hello.value.instructions)
|
|
7201
7273
|
};
|
|
7202
7274
|
await send(notification("notifications/initialized"), session).catch(() => void 0);
|
|
7203
|
-
const
|
|
7204
|
-
if (
|
|
7205
|
-
return {
|
|
7206
|
-
...said,
|
|
7207
|
-
reach: listed.reach,
|
|
7208
|
-
httpStatus: listed.status,
|
|
7209
|
-
handshakeMs: took(),
|
|
7210
|
-
note: "it opened but would not list"
|
|
7211
|
-
};
|
|
7212
|
-
}
|
|
7213
|
-
if (listed.kind === "broken") {
|
|
7275
|
+
const walked = await walkTools(send, session);
|
|
7276
|
+
if (walked.kind !== "listed") {
|
|
7214
7277
|
return {
|
|
7215
7278
|
...said,
|
|
7216
|
-
reach: "error",
|
|
7217
|
-
httpStatus:
|
|
7279
|
+
reach: walked.kind === "refused" ? walked.reach : "error",
|
|
7280
|
+
httpStatus: walked.status,
|
|
7218
7281
|
handshakeMs: took(),
|
|
7219
|
-
note:
|
|
7282
|
+
note: walked.note
|
|
7220
7283
|
};
|
|
7221
7284
|
}
|
|
7222
7285
|
return {
|
|
7223
7286
|
...said,
|
|
7224
7287
|
reach: "readable",
|
|
7225
|
-
httpStatus:
|
|
7288
|
+
httpStatus: walked.status,
|
|
7226
7289
|
handshakeMs: took(),
|
|
7227
|
-
tools:
|
|
7290
|
+
tools: walked.tools,
|
|
7291
|
+
pages: walked.pages,
|
|
7292
|
+
truncated: walked.truncated,
|
|
7228
7293
|
note: null
|
|
7229
7294
|
};
|
|
7230
7295
|
} catch (err) {
|
|
@@ -7240,7 +7305,7 @@ async function handshake(url, options) {
|
|
|
7240
7305
|
}
|
|
7241
7306
|
function toolsDigest(tools) {
|
|
7242
7307
|
const canonical = JSON.stringify(
|
|
7243
|
-
[...tools].map((t) => [t.name, t.description ?? "", t.
|
|
7308
|
+
[...tools].map((t) => [t.name, t.description ?? "", t.inputSchema !== null]).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
|
|
7244
7309
|
);
|
|
7245
7310
|
return createHash("sha256").update(canonical).digest("hex");
|
|
7246
7311
|
}
|
|
@@ -7290,7 +7355,7 @@ function checksFor(seen) {
|
|
|
7290
7355
|
return checks;
|
|
7291
7356
|
}
|
|
7292
7357
|
checks.push(pass2("tools", "listed its tools", `${seen.tools.length} tool(s)`));
|
|
7293
|
-
const bare = seen.tools.filter((t) =>
|
|
7358
|
+
const bare = seen.tools.filter((t) => t.inputSchema === null).map((t) => t.name);
|
|
7294
7359
|
checks.push(
|
|
7295
7360
|
bare.length === 0 ? pass2("input-schemas", "every tool declared an input schema") : fail2(
|
|
7296
7361
|
"input-schemas",
|
|
@@ -7316,6 +7381,7 @@ async function probeMcpEndpoint(options) {
|
|
|
7316
7381
|
...options.userAgent === void 0 ? {} : { userAgent: options.userAgent }
|
|
7317
7382
|
});
|
|
7318
7383
|
const checks = checksFor(seen);
|
|
7384
|
+
const defects = defectsOf(options.url, seen.httpStatus);
|
|
7319
7385
|
return {
|
|
7320
7386
|
url: options.url,
|
|
7321
7387
|
method: "MCP",
|
|
@@ -7328,7 +7394,8 @@ async function probeMcpEndpoint(options) {
|
|
|
7328
7394
|
requestHash: sha256(`MCP ${options.url}`),
|
|
7329
7395
|
responseHash: seen.tools === null ? null : `sha256:${toolsDigest(seen.tools)}`,
|
|
7330
7396
|
checks,
|
|
7331
|
-
|
|
7397
|
+
defects,
|
|
7398
|
+
verdict: verdictOf(checks, defects)
|
|
7332
7399
|
};
|
|
7333
7400
|
}
|
|
7334
7401
|
|
|
@@ -7411,6 +7478,7 @@ ${body}`);
|
|
|
7411
7478
|
handshakeMs: answer.latencyMs,
|
|
7412
7479
|
responseHash: null,
|
|
7413
7480
|
checks: unreachable(answer.reason),
|
|
7481
|
+
defects: defectsOf(options.url, null),
|
|
7414
7482
|
verdict: "fail"
|
|
7415
7483
|
};
|
|
7416
7484
|
}
|
|
@@ -7424,6 +7492,7 @@ ${body}`);
|
|
|
7424
7492
|
parseError: parsed.parseError,
|
|
7425
7493
|
source: parsed.source
|
|
7426
7494
|
});
|
|
7495
|
+
const defects = defectsOf(options.url, response.status);
|
|
7427
7496
|
return {
|
|
7428
7497
|
...base,
|
|
7429
7498
|
reachable: true,
|
|
@@ -7433,7 +7502,8 @@ ${body}`);
|
|
|
7433
7502
|
advertised: advertisedFrom(parsed.terms),
|
|
7434
7503
|
responseHash: sha2562(text2),
|
|
7435
7504
|
checks,
|
|
7436
|
-
|
|
7505
|
+
defects,
|
|
7506
|
+
verdict: verdictOf(checks, defects)
|
|
7437
7507
|
};
|
|
7438
7508
|
}
|
|
7439
7509
|
|
|
@@ -7473,6 +7543,13 @@ function formatText(result, colour) {
|
|
|
7473
7543
|
lines.push(` ${check.label.padEnd(width)}${mark}`);
|
|
7474
7544
|
if (check.detail) lines.push(` ${paint("dim", check.detail, colour)}`);
|
|
7475
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
|
+
}
|
|
7476
7553
|
if (result.advertised.length > 0) {
|
|
7477
7554
|
lines.push("");
|
|
7478
7555
|
lines.push(` ${paint("dim", "advertised", colour)}`);
|
|
@@ -7485,9 +7562,9 @@ function formatText(result, colour) {
|
|
|
7485
7562
|
if (result.responseHash)
|
|
7486
7563
|
lines.push(` ${paint("dim", "response ", colour)}${result.responseHash}`);
|
|
7487
7564
|
lines.push("");
|
|
7488
|
-
const failed = result.checks.filter((c) => c.status === "fail").length;
|
|
7565
|
+
const failed = result.checks.filter((c) => c.status === "fail").length + result.defects.length;
|
|
7489
7566
|
lines.push(
|
|
7490
|
-
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)}`
|
|
7491
7568
|
);
|
|
7492
7569
|
lines.push(` ${paint("dim", "this is the unpaid half. correctness needs a purchase.", colour)}`);
|
|
7493
7570
|
lines.push("");
|
|
@@ -7498,11 +7575,14 @@ function formatJson(result) {
|
|
|
7498
7575
|
}
|
|
7499
7576
|
export {
|
|
7500
7577
|
DEFAULT_USER_AGENT,
|
|
7578
|
+
PROSE_SAYS,
|
|
7501
7579
|
REACH_LABEL,
|
|
7502
7580
|
checksFor,
|
|
7581
|
+
defectsOf,
|
|
7503
7582
|
formatJson,
|
|
7504
7583
|
formatText,
|
|
7505
7584
|
parseTerms,
|
|
7585
|
+
pathNotFoundSays,
|
|
7506
7586
|
priceOf,
|
|
7507
7587
|
probe,
|
|
7508
7588
|
probeMcpEndpoint,
|
|
@@ -7510,5 +7590,6 @@ export {
|
|
|
7510
7590
|
schemaCheck,
|
|
7511
7591
|
schemasOf,
|
|
7512
7592
|
shouldColour,
|
|
7593
|
+
urlHasPlaceholder,
|
|
7513
7594
|
verdictOf
|
|
7514
7595
|
};
|
package/npm/mcp-probe/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { JSON_RPC, lastEventData, notification, PREFERRED_VERSION, PROTOCOL_VERSIONS, type RpcResult, readAnswer, request, } from './frame.js';
|
|
2
|
-
export { CLIENT, HANDSHAKE_TIMEOUT_MS, INITIALIZE, initFor, type McpObservation, type ProbeOptions, probeMcp, type Reach, reachFor, type Tool, toolsDigest, toolsFrom, } from './probe.js';
|
|
2
|
+
export { CLIENT, definitionDigest, definitionOf, HANDSHAKE_TIMEOUT_MS, INITIALIZE, initFor, MAX_PAGES, MAX_TOOLS, type McpObservation, type ProbeOptions, probeMcp, type Reach, reachFor, type Tool, toolsDigest, toolsFrom, } from './probe.js';
|
package/npm/mcp-probe/probe.d.ts
CHANGED
|
@@ -7,7 +7,8 @@ export type Reach = 'readable' | 'auth_required' | 'payment_required' | 'dead' |
|
|
|
7
7
|
export type Tool = {
|
|
8
8
|
readonly name: string;
|
|
9
9
|
readonly description: string | null;
|
|
10
|
-
readonly
|
|
10
|
+
readonly inputSchema: unknown;
|
|
11
|
+
readonly outputSchema: unknown;
|
|
11
12
|
};
|
|
12
13
|
export type McpObservation = {
|
|
13
14
|
readonly url: string;
|
|
@@ -18,7 +19,10 @@ export type McpObservation = {
|
|
|
18
19
|
readonly protocolVersion: string | null;
|
|
19
20
|
readonly serverName: string | null;
|
|
20
21
|
readonly serverVersion: string | null;
|
|
22
|
+
readonly instructions: string | null;
|
|
21
23
|
readonly tools: readonly Tool[] | null;
|
|
24
|
+
readonly pages: number | null;
|
|
25
|
+
readonly truncated: boolean;
|
|
22
26
|
readonly note: string | null;
|
|
23
27
|
};
|
|
24
28
|
export type ProbeOptions = {
|
|
@@ -40,5 +44,9 @@ export declare const INITIALIZE: {
|
|
|
40
44
|
export declare function toolsFrom(result: Record<string, unknown>): Tool[];
|
|
41
45
|
export declare function reachFor(status: number): Reach | null;
|
|
42
46
|
export declare const DEADLINE_MS = 45000;
|
|
47
|
+
export declare const MAX_PAGES = 20;
|
|
48
|
+
export declare const MAX_TOOLS = 2000;
|
|
43
49
|
export declare function probeMcp(url: string, options?: ProbeOptions): Promise<McpObservation>;
|
|
44
50
|
export declare function toolsDigest(tools: readonly Tool[]): string;
|
|
51
|
+
export declare function definitionOf(seen: McpObservation): string;
|
|
52
|
+
export declare function definitionDigest(seen: McpObservation): string;
|
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,7 +26,10 @@ 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>;
|
|
33
|
+
export declare function askedForPayment(result: ProbeResult): boolean;
|
|
34
|
+
export declare function probeEitherVerb(run: (method: string) => Promise<ProbeResult>, explicit?: string): Promise<ProbeResult>;
|
|
31
35
|
//# sourceMappingURL=probe.d.ts.map
|
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"}
|