teppi-check 0.1.0 → 0.2.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 +24 -0
- package/npm/cli.js +377 -53
- package/npm/index.d.ts +4 -3
- package/npm/index.d.ts.map +1 -1
- package/npm/index.js +341 -21
- package/npm/mcp-probe/frame.d.ts +18 -0
- package/npm/mcp-probe/index.d.ts +2 -0
- package/npm/mcp-probe/probe.d.ts +44 -0
- package/npm/mcp.d.ts +13 -0
- package/npm/mcp.d.ts.map +1 -0
- package/npm/probe.d.ts +1 -1
- package/npm/report.d.ts +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@ Send one unpaid request to any paid endpoint and see what it actually advertises
|
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx teppi-check https://some-seller.example/v1/extract
|
|
7
|
+
npx teppi-check --mcp https://mcp.some-seller.example/mcp
|
|
7
8
|
```
|
|
8
9
|
|
|
9
10
|
No install and no dependencies: the published package is a single bundled file, so the validator
|
|
@@ -70,9 +71,32 @@ takes a purchase, which the full prober does on a schedule.
|
|
|
70
71
|
Terms are read from the `payment-required` header (base64 or plain JSON) or from the response
|
|
71
72
|
body, because deployed servers use both.
|
|
72
73
|
|
|
74
|
+
## Remote MCP servers
|
|
75
|
+
|
|
76
|
+
`--mcp` runs the handshake instead: `initialize`, the initialized notification, then `tools/list`
|
|
77
|
+
over Streamable HTTP.
|
|
78
|
+
|
|
79
|
+
| Check | Fails when |
|
|
80
|
+
|---|---|
|
|
81
|
+
| server answered | connection refused, timeout, or a server that never replies |
|
|
82
|
+
| named a protocol version | it never got that far |
|
|
83
|
+
| named itself | it never got that far |
|
|
84
|
+
| listed its tools | it never got that far |
|
|
85
|
+
| every tool declared an input schema | a tool an agent would have to guess the arguments for |
|
|
86
|
+
| every tool said what it does | a tool with no description |
|
|
87
|
+
| tool list digest | never, it prints the digest so you can compare it against a record |
|
|
88
|
+
|
|
89
|
+
A server asking for credentials or for payment is reported as such and does **not** fail: that is
|
|
90
|
+
what a private or paid server is entitled to say. Only a server that did not answer fails.
|
|
91
|
+
|
|
92
|
+
The digest is a sha256 over each tool's name, description and whether it declared an input
|
|
93
|
+
schema, sorted by name. Reordering the list does not change it and rewording a description does,
|
|
94
|
+
which is what makes it useful for noticing that a server's instructions changed under you.
|
|
95
|
+
|
|
73
96
|
## Options
|
|
74
97
|
|
|
75
98
|
```
|
|
99
|
+
--mcp handshake a remote mcp server instead of an x402 endpoint
|
|
76
100
|
--method <verb> default POST
|
|
77
101
|
--body <json|@file> default {}
|
|
78
102
|
--json machine readable output
|
package/npm/cli.js
CHANGED
|
@@ -4609,7 +4609,7 @@ var require_core = __commonJS({
|
|
|
4609
4609
|
errorsText(errors = this.errors, { separator = ", ", dataVar = "data" } = {}) {
|
|
4610
4610
|
if (!errors || errors.length === 0)
|
|
4611
4611
|
return "No errors";
|
|
4612
|
-
return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((
|
|
4612
|
+
return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text2, msg) => text2 + separator + msg);
|
|
4613
4613
|
}
|
|
4614
4614
|
$dataMetaSchema(metaSchema, keywordsJsonPointers) {
|
|
4615
4615
|
const rules = this.RULES.all;
|
|
@@ -6871,32 +6871,349 @@ import { readFileSync } from "node:fs";
|
|
|
6871
6871
|
import { argv, env, exit, stdout } from "node:process";
|
|
6872
6872
|
import { parseArgs } from "node:util";
|
|
6873
6873
|
|
|
6874
|
-
// src/
|
|
6874
|
+
// src/mcp.ts
|
|
6875
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
6876
|
+
|
|
6877
|
+
// ../mcp-probe/src/frame.ts
|
|
6878
|
+
var JSON_RPC = "2.0";
|
|
6879
|
+
var PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26"];
|
|
6880
|
+
var PREFERRED_VERSION = PROTOCOL_VERSIONS[0];
|
|
6881
|
+
function request(id, method, params) {
|
|
6882
|
+
return JSON.stringify({ jsonrpc: JSON_RPC, id, method, ...params ? { params } : {} });
|
|
6883
|
+
}
|
|
6884
|
+
function notification(method) {
|
|
6885
|
+
return JSON.stringify({ jsonrpc: JSON_RPC, method });
|
|
6886
|
+
}
|
|
6887
|
+
function lastEventData(body) {
|
|
6888
|
+
const payloads = body.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).filter((line) => line.length > 0);
|
|
6889
|
+
return payloads.length === 0 ? null : payloads[payloads.length - 1];
|
|
6890
|
+
}
|
|
6891
|
+
function readOne(text2) {
|
|
6892
|
+
let parsed;
|
|
6893
|
+
try {
|
|
6894
|
+
parsed = JSON.parse(text2);
|
|
6895
|
+
} catch {
|
|
6896
|
+
return { kind: "unreadable", why: "the answer is not json" };
|
|
6897
|
+
}
|
|
6898
|
+
const one = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
6899
|
+
if (typeof one !== "object" || one === null) {
|
|
6900
|
+
return { kind: "unreadable", why: "the answer is not a json rpc object" };
|
|
6901
|
+
}
|
|
6902
|
+
const message = one;
|
|
6903
|
+
if (message.error !== void 0) {
|
|
6904
|
+
const error = message.error;
|
|
6905
|
+
return {
|
|
6906
|
+
kind: "error",
|
|
6907
|
+
code: typeof error.code === "number" ? error.code : 0,
|
|
6908
|
+
message: typeof error.message === "string" ? error.message : "no message"
|
|
6909
|
+
};
|
|
6910
|
+
}
|
|
6911
|
+
if (typeof message.result !== "object" || message.result === null) {
|
|
6912
|
+
return { kind: "unreadable", why: "the answer carries neither a result nor an error" };
|
|
6913
|
+
}
|
|
6914
|
+
return { kind: "result", value: message.result };
|
|
6915
|
+
}
|
|
6916
|
+
function readAnswer(contentType, body) {
|
|
6917
|
+
const streamed = (contentType ?? "").toLowerCase().includes("text/event-stream");
|
|
6918
|
+
if (!streamed) return readOne(body);
|
|
6919
|
+
const data = lastEventData(body);
|
|
6920
|
+
return data === null ? { kind: "unreadable", why: "the stream carried no data line" } : readOne(data);
|
|
6921
|
+
}
|
|
6922
|
+
|
|
6923
|
+
// ../mcp-probe/src/probe.ts
|
|
6875
6924
|
import { createHash } from "node:crypto";
|
|
6925
|
+
var HANDSHAKE_TIMEOUT_MS = 2e4;
|
|
6926
|
+
var CLIENT = { name: "teppi-probe", version: "0.1.0" };
|
|
6927
|
+
function initFor(body, sessionId, userAgent) {
|
|
6928
|
+
return {
|
|
6929
|
+
method: "POST",
|
|
6930
|
+
headers: {
|
|
6931
|
+
"content-type": "application/json",
|
|
6932
|
+
accept: "application/json, text/event-stream",
|
|
6933
|
+
"mcp-protocol-version": PREFERRED_VERSION,
|
|
6934
|
+
"user-agent": userAgent,
|
|
6935
|
+
...sessionId === null ? {} : { "mcp-session-id": sessionId }
|
|
6936
|
+
},
|
|
6937
|
+
body
|
|
6938
|
+
};
|
|
6939
|
+
}
|
|
6940
|
+
var INITIALIZE = {
|
|
6941
|
+
protocolVersion: PREFERRED_VERSION,
|
|
6942
|
+
capabilities: {},
|
|
6943
|
+
clientInfo: CLIENT
|
|
6944
|
+
};
|
|
6945
|
+
var text = (value) => typeof value === "string" ? value : null;
|
|
6946
|
+
function toolsFrom(result) {
|
|
6947
|
+
const raw = result.tools;
|
|
6948
|
+
if (!Array.isArray(raw)) return [];
|
|
6949
|
+
return raw.flatMap((value) => {
|
|
6950
|
+
if (typeof value !== "object" || value === null) return [];
|
|
6951
|
+
const tool = value;
|
|
6952
|
+
const name = text(tool.name);
|
|
6953
|
+
if (name === null) return [];
|
|
6954
|
+
return [
|
|
6955
|
+
{
|
|
6956
|
+
name,
|
|
6957
|
+
description: text(tool.description),
|
|
6958
|
+
hasInputSchema: typeof tool.inputSchema === "object" && tool.inputSchema !== null
|
|
6959
|
+
}
|
|
6960
|
+
];
|
|
6961
|
+
});
|
|
6962
|
+
}
|
|
6963
|
+
function reachFor(status) {
|
|
6964
|
+
if (status === 401 || status === 403) return "auth_required";
|
|
6965
|
+
if (status === 402) return "payment_required";
|
|
6966
|
+
if (status >= 500 || status === 404 || status === 410) return "dead";
|
|
6967
|
+
if (status >= 400) return "error";
|
|
6968
|
+
return null;
|
|
6969
|
+
}
|
|
6970
|
+
async function turn(response, method) {
|
|
6971
|
+
const refused = reachFor(response.status);
|
|
6972
|
+
if (refused !== null) return { kind: "refused", reach: refused, status: response.status };
|
|
6973
|
+
const answer = readAnswer(response.headers.get("content-type"), await response.text());
|
|
6974
|
+
if (answer.kind === "result") {
|
|
6975
|
+
return { kind: "read", status: response.status, value: answer.value };
|
|
6976
|
+
}
|
|
6977
|
+
return {
|
|
6978
|
+
kind: "broken",
|
|
6979
|
+
status: response.status,
|
|
6980
|
+
note: answer.kind === "error" ? `${method}: ${answer.message}` : answer.why
|
|
6981
|
+
};
|
|
6982
|
+
}
|
|
6983
|
+
var DEADLINE_MS = 45e3;
|
|
6984
|
+
async function probeMcp(url, options = {}) {
|
|
6985
|
+
const clockFor = options.now ?? Date.now;
|
|
6986
|
+
const startedFor = clockFor();
|
|
6987
|
+
let timer;
|
|
6988
|
+
const gaveUp = new Promise((resolve) => {
|
|
6989
|
+
timer = setTimeout(
|
|
6990
|
+
() => resolve({
|
|
6991
|
+
url,
|
|
6992
|
+
observedAt: new Date(startedFor).toISOString(),
|
|
6993
|
+
reach: "dead",
|
|
6994
|
+
httpStatus: null,
|
|
6995
|
+
handshakeMs: options.deadlineMs ?? DEADLINE_MS,
|
|
6996
|
+
protocolVersion: null,
|
|
6997
|
+
serverName: null,
|
|
6998
|
+
serverVersion: null,
|
|
6999
|
+
tools: null,
|
|
7000
|
+
note: "never answered and never gave up"
|
|
7001
|
+
}),
|
|
7002
|
+
options.deadlineMs ?? DEADLINE_MS
|
|
7003
|
+
);
|
|
7004
|
+
});
|
|
7005
|
+
try {
|
|
7006
|
+
return await Promise.race([handshake(url, options), gaveUp]);
|
|
7007
|
+
} finally {
|
|
7008
|
+
clearTimeout(timer);
|
|
7009
|
+
}
|
|
7010
|
+
}
|
|
7011
|
+
async function handshake(url, options) {
|
|
7012
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
7013
|
+
const clock = options.now ?? Date.now;
|
|
7014
|
+
const userAgent = options.userAgent ?? `${CLIENT.name}/${CLIENT.version}`;
|
|
7015
|
+
const startedAt = clock();
|
|
7016
|
+
const base = {
|
|
7017
|
+
url,
|
|
7018
|
+
observedAt: new Date(startedAt).toISOString(),
|
|
7019
|
+
protocolVersion: null,
|
|
7020
|
+
serverName: null,
|
|
7021
|
+
serverVersion: null,
|
|
7022
|
+
tools: null
|
|
7023
|
+
};
|
|
7024
|
+
const send = (body, sessionId) => doFetch(url, {
|
|
7025
|
+
...initFor(body, sessionId, userAgent),
|
|
7026
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? HANDSHAKE_TIMEOUT_MS)
|
|
7027
|
+
});
|
|
7028
|
+
const took = () => Math.max(0, clock() - startedAt);
|
|
7029
|
+
try {
|
|
7030
|
+
const opened = await send(request(1, "initialize", INITIALIZE), null);
|
|
7031
|
+
const hello = await turn(opened, "initialize");
|
|
7032
|
+
if (hello.kind === "refused") {
|
|
7033
|
+
return {
|
|
7034
|
+
...base,
|
|
7035
|
+
reach: hello.reach,
|
|
7036
|
+
httpStatus: hello.status,
|
|
7037
|
+
handshakeMs: took(),
|
|
7038
|
+
note: null
|
|
7039
|
+
};
|
|
7040
|
+
}
|
|
7041
|
+
if (hello.kind === "broken") {
|
|
7042
|
+
return {
|
|
7043
|
+
...base,
|
|
7044
|
+
reach: "error",
|
|
7045
|
+
httpStatus: hello.status,
|
|
7046
|
+
handshakeMs: took(),
|
|
7047
|
+
note: hello.note
|
|
7048
|
+
};
|
|
7049
|
+
}
|
|
7050
|
+
const info = hello.value.serverInfo ?? {};
|
|
7051
|
+
const session = opened.headers.get("mcp-session-id");
|
|
7052
|
+
const said = {
|
|
7053
|
+
...base,
|
|
7054
|
+
protocolVersion: text(hello.value.protocolVersion),
|
|
7055
|
+
serverName: text(info.name),
|
|
7056
|
+
serverVersion: text(info.version)
|
|
7057
|
+
};
|
|
7058
|
+
await send(notification("notifications/initialized"), session).catch(() => void 0);
|
|
7059
|
+
const listed = await turn(await send(request(2, "tools/list"), session), "tools/list");
|
|
7060
|
+
if (listed.kind === "refused") {
|
|
7061
|
+
return {
|
|
7062
|
+
...said,
|
|
7063
|
+
reach: listed.reach,
|
|
7064
|
+
httpStatus: listed.status,
|
|
7065
|
+
handshakeMs: took(),
|
|
7066
|
+
note: "it opened but would not list"
|
|
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
|
|
7076
|
+
};
|
|
7077
|
+
}
|
|
7078
|
+
return {
|
|
7079
|
+
...said,
|
|
7080
|
+
reach: "readable",
|
|
7081
|
+
httpStatus: listed.status,
|
|
7082
|
+
handshakeMs: took(),
|
|
7083
|
+
tools: toolsFrom(listed.value),
|
|
7084
|
+
note: null
|
|
7085
|
+
};
|
|
7086
|
+
} catch (err) {
|
|
7087
|
+
const name = err.name;
|
|
7088
|
+
return {
|
|
7089
|
+
...base,
|
|
7090
|
+
reach: "dead",
|
|
7091
|
+
httpStatus: null,
|
|
7092
|
+
handshakeMs: took(),
|
|
7093
|
+
note: name === "TimeoutError" || name === "AbortError" ? "timed out" : "unreachable"
|
|
7094
|
+
};
|
|
7095
|
+
}
|
|
7096
|
+
}
|
|
7097
|
+
function toolsDigest(tools) {
|
|
7098
|
+
const canonical = JSON.stringify(
|
|
7099
|
+
[...tools].map((t) => [t.name, t.description ?? "", t.hasInputSchema]).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
|
|
7100
|
+
);
|
|
7101
|
+
return createHash("sha256").update(canonical).digest("hex");
|
|
7102
|
+
}
|
|
7103
|
+
|
|
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";
|
|
6876
7193
|
|
|
6877
7194
|
// ../x402/src/handshake.ts
|
|
6878
|
-
var
|
|
7195
|
+
var HANDSHAKE_TIMEOUT_MS2 = 3e4;
|
|
6879
7196
|
var sendsBody = (method) => method !== "GET" && method !== "HEAD";
|
|
6880
|
-
function handshakeInit(
|
|
6881
|
-
const carries = sendsBody(
|
|
7197
|
+
function handshakeInit(request2) {
|
|
7198
|
+
const carries = sendsBody(request2.method);
|
|
6882
7199
|
const headers = { accept: "application/json" };
|
|
6883
7200
|
if (carries) headers["content-type"] = "application/json";
|
|
6884
|
-
if (
|
|
6885
|
-
if (
|
|
7201
|
+
if (request2.userAgent !== void 0) headers["user-agent"] = request2.userAgent;
|
|
7202
|
+
if (request2.payment) headers[request2.payment.header] = request2.payment.value;
|
|
6886
7203
|
return {
|
|
6887
|
-
method:
|
|
7204
|
+
method: request2.method,
|
|
6888
7205
|
headers,
|
|
6889
|
-
...carries ? { body: JSON.stringify(
|
|
6890
|
-
signal: AbortSignal.timeout(
|
|
7206
|
+
...carries ? { body: JSON.stringify(request2.input ?? {}) } : {},
|
|
7207
|
+
signal: AbortSignal.timeout(request2.timeoutMs ?? HANDSHAKE_TIMEOUT_MS2)
|
|
6891
7208
|
};
|
|
6892
7209
|
}
|
|
6893
|
-
async function
|
|
7210
|
+
async function handshake2(request2, deps = {}) {
|
|
6894
7211
|
const doFetch = deps.fetchImpl ?? fetch;
|
|
6895
7212
|
const now = deps.now ?? Date.now;
|
|
6896
7213
|
const started = now();
|
|
6897
7214
|
const since = () => Math.max(0, now() - started);
|
|
6898
7215
|
try {
|
|
6899
|
-
const response = await doFetch(
|
|
7216
|
+
const response = await doFetch(request2.url, handshakeInit(request2));
|
|
6900
7217
|
return { ok: true, response, body: await response.text(), latencyMs: since() };
|
|
6901
7218
|
} catch (err) {
|
|
6902
7219
|
return { ok: false, reason: err.message, latencyMs: since() };
|
|
@@ -6974,24 +7291,24 @@ var ajv = new import_ajv.Ajv({
|
|
|
6974
7291
|
validateSchema: true,
|
|
6975
7292
|
validateFormats: false
|
|
6976
7293
|
});
|
|
6977
|
-
function
|
|
7294
|
+
function pass2(id, label, detail) {
|
|
6978
7295
|
return detail === void 0 ? { id, label, status: "pass" } : { id, label, status: "pass", detail };
|
|
6979
7296
|
}
|
|
6980
|
-
function
|
|
7297
|
+
function fail2(id, label, detail) {
|
|
6981
7298
|
return { id, label, status: "fail", detail };
|
|
6982
7299
|
}
|
|
6983
7300
|
function schemaCheck(id, label, schema) {
|
|
6984
7301
|
if (schema === null || schema === void 0) {
|
|
6985
|
-
return
|
|
7302
|
+
return fail2(id, label, "not declared, so no response can ever be checked against it");
|
|
6986
7303
|
}
|
|
6987
7304
|
if (typeof schema !== "object") {
|
|
6988
|
-
return
|
|
7305
|
+
return fail2(id, label, `declared as ${typeof schema}, not an object`);
|
|
6989
7306
|
}
|
|
6990
7307
|
try {
|
|
6991
7308
|
ajv.compile(schema);
|
|
6992
|
-
return
|
|
7309
|
+
return pass2(id, label);
|
|
6993
7310
|
} catch (err) {
|
|
6994
|
-
return
|
|
7311
|
+
return fail2(id, label, `declared but invalid: ${err.message.split("\n")[0]}`);
|
|
6995
7312
|
}
|
|
6996
7313
|
}
|
|
6997
7314
|
function requirementChecks(terms) {
|
|
@@ -6999,7 +7316,7 @@ function requirementChecks(terms) {
|
|
|
6999
7316
|
const report = (id, label, has) => {
|
|
7000
7317
|
const gaps = terms.accepts.map((r, i) => has(r) ? -1 : i).filter((i) => i >= 0);
|
|
7001
7318
|
out.push(
|
|
7002
|
-
gaps.length === 0 ?
|
|
7319
|
+
gaps.length === 0 ? pass2(id, label) : fail2(id, label, `absent from accepts[${gaps.join("], accepts[")}]`)
|
|
7003
7320
|
);
|
|
7004
7321
|
};
|
|
7005
7322
|
report("price_declared", "price is stated", (r) => priceOf(r) !== null);
|
|
@@ -7016,29 +7333,29 @@ function requirementChecks(terms) {
|
|
|
7016
7333
|
return out;
|
|
7017
7334
|
}
|
|
7018
7335
|
function runHandshakeChecks(input) {
|
|
7019
|
-
const checks = [
|
|
7336
|
+
const checks = [pass2("reachable", "endpoint answered")];
|
|
7020
7337
|
checks.push(
|
|
7021
|
-
input.status === 402 ?
|
|
7338
|
+
input.status === 402 ? pass2("status_402", "asks for payment") : fail2("status_402", "asks for payment", `returned ${input.status}, not 402`)
|
|
7022
7339
|
);
|
|
7023
7340
|
if (input.terms === null) {
|
|
7024
7341
|
checks.push(
|
|
7025
|
-
|
|
7342
|
+
fail2("terms_parseable", "payment terms parse", input.parseError ?? "no terms found")
|
|
7026
7343
|
);
|
|
7027
7344
|
return checks;
|
|
7028
7345
|
}
|
|
7029
|
-
checks.push(
|
|
7346
|
+
checks.push(pass2("terms_parseable", "payment terms parse", `read from the ${input.source}`));
|
|
7030
7347
|
checks.push(
|
|
7031
|
-
input.terms.accepts.length > 0 ?
|
|
7348
|
+
input.terms.accepts.length > 0 ? pass2(
|
|
7032
7349
|
"accepts_present",
|
|
7033
7350
|
"offers at least one way to pay",
|
|
7034
7351
|
`${input.terms.accepts.length} option(s)`
|
|
7035
|
-
) :
|
|
7352
|
+
) : fail2("accepts_present", "offers at least one way to pay", "accepts is empty")
|
|
7036
7353
|
);
|
|
7037
7354
|
if (input.terms.accepts.length === 0) return checks;
|
|
7038
7355
|
checks.push(...requirementChecks(input.terms));
|
|
7039
7356
|
const cache = input.headers.get("cache-control") ?? "";
|
|
7040
7357
|
checks.push(
|
|
7041
|
-
/no-store|no-cache/i.test(cache) ?
|
|
7358
|
+
/no-store|no-cache/i.test(cache) ? pass2("not_cacheable", "payment terms are not cached") : fail2(
|
|
7042
7359
|
"not_cacheable",
|
|
7043
7360
|
"payment terms are not cached",
|
|
7044
7361
|
cache === "" ? "no cache-control, so a proxy may serve stale terms" : `cache-control: ${cache}`
|
|
@@ -7047,15 +7364,15 @@ function runHandshakeChecks(input) {
|
|
|
7047
7364
|
return checks;
|
|
7048
7365
|
}
|
|
7049
7366
|
function unreachable(reason) {
|
|
7050
|
-
return [
|
|
7367
|
+
return [fail2("reachable", "endpoint answered", reason)];
|
|
7051
7368
|
}
|
|
7052
7369
|
function verdictOf(checks) {
|
|
7053
7370
|
return checks.some((c) => c.status === "fail") ? "fail" : "pass";
|
|
7054
7371
|
}
|
|
7055
7372
|
|
|
7056
7373
|
// src/probe.ts
|
|
7057
|
-
function
|
|
7058
|
-
return `sha256:${
|
|
7374
|
+
function sha2562(input) {
|
|
7375
|
+
return `sha256:${createHash3("sha256").update(input).digest("hex")}`;
|
|
7059
7376
|
}
|
|
7060
7377
|
function advertisedFrom(terms) {
|
|
7061
7378
|
if (!terms) return [];
|
|
@@ -7071,7 +7388,7 @@ async function probe(options) {
|
|
|
7071
7388
|
const body = options.body ?? "{}";
|
|
7072
7389
|
const doFetch = options.fetchImpl ?? fetch;
|
|
7073
7390
|
const observedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7074
|
-
const requestHash =
|
|
7391
|
+
const requestHash = sha2562(`${method} ${options.url}
|
|
7075
7392
|
${body}`);
|
|
7076
7393
|
const base = {
|
|
7077
7394
|
url: options.url,
|
|
@@ -7081,7 +7398,7 @@ ${body}`);
|
|
|
7081
7398
|
termsSource: "none",
|
|
7082
7399
|
advertised: []
|
|
7083
7400
|
};
|
|
7084
|
-
const answer = await
|
|
7401
|
+
const answer = await handshake2(
|
|
7085
7402
|
{
|
|
7086
7403
|
url: options.url,
|
|
7087
7404
|
method,
|
|
@@ -7102,9 +7419,9 @@ ${body}`);
|
|
|
7102
7419
|
verdict: "fail"
|
|
7103
7420
|
};
|
|
7104
7421
|
}
|
|
7105
|
-
const { response, body:
|
|
7422
|
+
const { response, body: text2 } = answer;
|
|
7106
7423
|
const handshakeMs = answer.latencyMs;
|
|
7107
|
-
const parsed = parseTerms(response.headers,
|
|
7424
|
+
const parsed = parseTerms(response.headers, text2);
|
|
7108
7425
|
const checks = runHandshakeChecks({
|
|
7109
7426
|
status: response.status,
|
|
7110
7427
|
headers: response.headers,
|
|
@@ -7119,7 +7436,7 @@ ${body}`);
|
|
|
7119
7436
|
handshakeMs,
|
|
7120
7437
|
termsSource: parsed.source,
|
|
7121
7438
|
advertised: advertisedFrom(parsed.terms),
|
|
7122
|
-
responseHash:
|
|
7439
|
+
responseHash: sha2562(text2),
|
|
7123
7440
|
checks,
|
|
7124
7441
|
verdict: verdictOf(checks)
|
|
7125
7442
|
};
|
|
@@ -7139,8 +7456,8 @@ function shouldColour(env2, isTty) {
|
|
|
7139
7456
|
if (env2.FORCE_COLOR !== void 0 && env2.FORCE_COLOR !== "0") return true;
|
|
7140
7457
|
return isTty;
|
|
7141
7458
|
}
|
|
7142
|
-
function paint(colour,
|
|
7143
|
-
return on ? `${ANSI[colour]}${
|
|
7459
|
+
function paint(colour, text2, on) {
|
|
7460
|
+
return on ? `${ANSI[colour]}${text2}${ANSI.reset}` : text2;
|
|
7144
7461
|
}
|
|
7145
7462
|
var MARK = { pass: "ok", fail: "fail", skip: "skip" };
|
|
7146
7463
|
var COLOUR = { pass: "green", fail: "red", skip: "yellow" };
|
|
@@ -7192,6 +7509,7 @@ var USAGE = `
|
|
|
7192
7509
|
Sends one unpaid request and reports what the endpoint advertises, in the
|
|
7193
7510
|
same form the public record uses.
|
|
7194
7511
|
|
|
7512
|
+
--mcp handshake a remote mcp server instead of an x402 endpoint
|
|
7195
7513
|
--method <verb> default POST
|
|
7196
7514
|
--body <json|@file> default {}
|
|
7197
7515
|
--json machine readable output
|
|
@@ -7200,11 +7518,26 @@ var USAGE = `
|
|
|
7200
7518
|
|
|
7201
7519
|
Exit codes: 0 every check passed, 1 a check failed, 2 could not be reached.
|
|
7202
7520
|
`;
|
|
7521
|
+
function refuseUnlessUsable(url) {
|
|
7522
|
+
let parsed;
|
|
7523
|
+
try {
|
|
7524
|
+
parsed = new URL(url);
|
|
7525
|
+
} catch {
|
|
7526
|
+
stdout.write(`not a url: ${url}
|
|
7527
|
+
`);
|
|
7528
|
+
exit(2);
|
|
7529
|
+
}
|
|
7530
|
+
if (parsed.protocol !== "https:" && parsed.hostname !== "localhost") {
|
|
7531
|
+
stdout.write("refusing to send a request over plain http\n");
|
|
7532
|
+
exit(2);
|
|
7533
|
+
}
|
|
7534
|
+
}
|
|
7203
7535
|
async function main() {
|
|
7204
7536
|
const { values, positionals } = parseArgs({
|
|
7205
7537
|
args: argv.slice(2),
|
|
7206
7538
|
allowPositionals: true,
|
|
7207
7539
|
options: {
|
|
7540
|
+
mcp: { type: "boolean", default: false },
|
|
7208
7541
|
method: { type: "string" },
|
|
7209
7542
|
body: { type: "string" },
|
|
7210
7543
|
json: { type: "boolean", default: false },
|
|
@@ -7219,26 +7552,17 @@ async function main() {
|
|
|
7219
7552
|
`);
|
|
7220
7553
|
exit(values.help ? 0 : 2);
|
|
7221
7554
|
}
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
parsedUrl = new URL(url);
|
|
7225
|
-
} catch {
|
|
7226
|
-
stdout.write(`not a url: ${url}
|
|
7227
|
-
`);
|
|
7228
|
-
exit(2);
|
|
7229
|
-
}
|
|
7230
|
-
if (parsedUrl.protocol !== "https:" && parsedUrl.hostname !== "localhost") {
|
|
7231
|
-
stdout.write("refusing to send a request over plain http\n");
|
|
7232
|
-
exit(2);
|
|
7233
|
-
}
|
|
7234
|
-
const rawBody = values.body ?? "{}";
|
|
7235
|
-
const body = rawBody.startsWith("@") ? readFileSync(rawBody.slice(1), "utf8") : rawBody;
|
|
7236
|
-
const result = await probe({
|
|
7555
|
+
refuseUnlessUsable(url);
|
|
7556
|
+
const shared = {
|
|
7237
7557
|
url,
|
|
7238
|
-
method: values.method?.toUpperCase() ?? "POST",
|
|
7239
|
-
body,
|
|
7240
7558
|
...values.timeout ? { timeoutMs: Number(values.timeout) } : {},
|
|
7241
7559
|
...values["user-agent"] ? { userAgent: values["user-agent"] } : {}
|
|
7560
|
+
};
|
|
7561
|
+
const rawBody = values.body ?? "{}";
|
|
7562
|
+
const result = values.mcp ? await probeMcpEndpoint(shared) : await probe({
|
|
7563
|
+
...shared,
|
|
7564
|
+
method: values.method?.toUpperCase() ?? "POST",
|
|
7565
|
+
body: rawBody.startsWith("@") ? readFileSync(rawBody.slice(1), "utf8") : rawBody
|
|
7242
7566
|
});
|
|
7243
7567
|
stdout.write(
|
|
7244
7568
|
values.json ? `${formatJson(result)}
|
package/npm/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { type PaymentRequirement, type PaymentTerms, parseTerms, priceOf, schemasOf, } from './terms.js';
|
|
2
|
-
export { type Check, type CheckStatus, runHandshakeChecks, schemaCheck, verdictOf, } from './checks.
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
2
|
+
export { type Check, type CheckStatus, runHandshakeChecks, schemaCheck, verdictOf, } from './checks.js';
|
|
3
|
+
export { checksFor, type McpProbeOptions, probeMcpEndpoint, REACH_LABEL, } from './mcp.js';
|
|
4
|
+
export { DEFAULT_USER_AGENT, type ProbeOptions, type ProbeResult, probe } from './probe.js';
|
|
5
|
+
export { formatJson, formatText, shouldColour } from './report.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
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,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,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
|
@@ -4608,7 +4608,7 @@ var require_core = __commonJS({
|
|
|
4608
4608
|
errorsText(errors = this.errors, { separator = ", ", dataVar = "data" } = {}) {
|
|
4609
4609
|
if (!errors || errors.length === 0)
|
|
4610
4610
|
return "No errors";
|
|
4611
|
-
return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((
|
|
4611
|
+
return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text2, msg) => text2 + separator + msg);
|
|
4612
4612
|
}
|
|
4613
4613
|
$dataMetaSchema(metaSchema, keywordsJsonPointers) {
|
|
4614
4614
|
const rules = this.RULES.all;
|
|
@@ -7015,32 +7015,349 @@ function verdictOf(checks) {
|
|
|
7015
7015
|
return checks.some((c) => c.status === "fail") ? "fail" : "pass";
|
|
7016
7016
|
}
|
|
7017
7017
|
|
|
7018
|
-
// src/
|
|
7018
|
+
// src/mcp.ts
|
|
7019
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
7020
|
+
|
|
7021
|
+
// ../mcp-probe/src/frame.ts
|
|
7022
|
+
var JSON_RPC = "2.0";
|
|
7023
|
+
var PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26"];
|
|
7024
|
+
var PREFERRED_VERSION = PROTOCOL_VERSIONS[0];
|
|
7025
|
+
function request(id, method, params) {
|
|
7026
|
+
return JSON.stringify({ jsonrpc: JSON_RPC, id, method, ...params ? { params } : {} });
|
|
7027
|
+
}
|
|
7028
|
+
function notification(method) {
|
|
7029
|
+
return JSON.stringify({ jsonrpc: JSON_RPC, method });
|
|
7030
|
+
}
|
|
7031
|
+
function lastEventData(body) {
|
|
7032
|
+
const payloads = body.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).filter((line) => line.length > 0);
|
|
7033
|
+
return payloads.length === 0 ? null : payloads[payloads.length - 1];
|
|
7034
|
+
}
|
|
7035
|
+
function readOne(text2) {
|
|
7036
|
+
let parsed;
|
|
7037
|
+
try {
|
|
7038
|
+
parsed = JSON.parse(text2);
|
|
7039
|
+
} catch {
|
|
7040
|
+
return { kind: "unreadable", why: "the answer is not json" };
|
|
7041
|
+
}
|
|
7042
|
+
const one = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
7043
|
+
if (typeof one !== "object" || one === null) {
|
|
7044
|
+
return { kind: "unreadable", why: "the answer is not a json rpc object" };
|
|
7045
|
+
}
|
|
7046
|
+
const message = one;
|
|
7047
|
+
if (message.error !== void 0) {
|
|
7048
|
+
const error = message.error;
|
|
7049
|
+
return {
|
|
7050
|
+
kind: "error",
|
|
7051
|
+
code: typeof error.code === "number" ? error.code : 0,
|
|
7052
|
+
message: typeof error.message === "string" ? error.message : "no message"
|
|
7053
|
+
};
|
|
7054
|
+
}
|
|
7055
|
+
if (typeof message.result !== "object" || message.result === null) {
|
|
7056
|
+
return { kind: "unreadable", why: "the answer carries neither a result nor an error" };
|
|
7057
|
+
}
|
|
7058
|
+
return { kind: "result", value: message.result };
|
|
7059
|
+
}
|
|
7060
|
+
function readAnswer(contentType, body) {
|
|
7061
|
+
const streamed = (contentType ?? "").toLowerCase().includes("text/event-stream");
|
|
7062
|
+
if (!streamed) return readOne(body);
|
|
7063
|
+
const data = lastEventData(body);
|
|
7064
|
+
return data === null ? { kind: "unreadable", why: "the stream carried no data line" } : readOne(data);
|
|
7065
|
+
}
|
|
7066
|
+
|
|
7067
|
+
// ../mcp-probe/src/probe.ts
|
|
7019
7068
|
import { createHash } from "node:crypto";
|
|
7069
|
+
var HANDSHAKE_TIMEOUT_MS = 2e4;
|
|
7070
|
+
var CLIENT = { name: "teppi-probe", version: "0.1.0" };
|
|
7071
|
+
function initFor(body, sessionId, userAgent) {
|
|
7072
|
+
return {
|
|
7073
|
+
method: "POST",
|
|
7074
|
+
headers: {
|
|
7075
|
+
"content-type": "application/json",
|
|
7076
|
+
accept: "application/json, text/event-stream",
|
|
7077
|
+
"mcp-protocol-version": PREFERRED_VERSION,
|
|
7078
|
+
"user-agent": userAgent,
|
|
7079
|
+
...sessionId === null ? {} : { "mcp-session-id": sessionId }
|
|
7080
|
+
},
|
|
7081
|
+
body
|
|
7082
|
+
};
|
|
7083
|
+
}
|
|
7084
|
+
var INITIALIZE = {
|
|
7085
|
+
protocolVersion: PREFERRED_VERSION,
|
|
7086
|
+
capabilities: {},
|
|
7087
|
+
clientInfo: CLIENT
|
|
7088
|
+
};
|
|
7089
|
+
var text = (value) => typeof value === "string" ? value : null;
|
|
7090
|
+
function toolsFrom(result) {
|
|
7091
|
+
const raw = result.tools;
|
|
7092
|
+
if (!Array.isArray(raw)) return [];
|
|
7093
|
+
return raw.flatMap((value) => {
|
|
7094
|
+
if (typeof value !== "object" || value === null) return [];
|
|
7095
|
+
const tool = value;
|
|
7096
|
+
const name = text(tool.name);
|
|
7097
|
+
if (name === null) return [];
|
|
7098
|
+
return [
|
|
7099
|
+
{
|
|
7100
|
+
name,
|
|
7101
|
+
description: text(tool.description),
|
|
7102
|
+
hasInputSchema: typeof tool.inputSchema === "object" && tool.inputSchema !== null
|
|
7103
|
+
}
|
|
7104
|
+
];
|
|
7105
|
+
});
|
|
7106
|
+
}
|
|
7107
|
+
function reachFor(status) {
|
|
7108
|
+
if (status === 401 || status === 403) return "auth_required";
|
|
7109
|
+
if (status === 402) return "payment_required";
|
|
7110
|
+
if (status >= 500 || status === 404 || status === 410) return "dead";
|
|
7111
|
+
if (status >= 400) return "error";
|
|
7112
|
+
return null;
|
|
7113
|
+
}
|
|
7114
|
+
async function turn(response, method) {
|
|
7115
|
+
const refused = reachFor(response.status);
|
|
7116
|
+
if (refused !== null) return { kind: "refused", reach: refused, status: response.status };
|
|
7117
|
+
const answer = readAnswer(response.headers.get("content-type"), await response.text());
|
|
7118
|
+
if (answer.kind === "result") {
|
|
7119
|
+
return { kind: "read", status: response.status, value: answer.value };
|
|
7120
|
+
}
|
|
7121
|
+
return {
|
|
7122
|
+
kind: "broken",
|
|
7123
|
+
status: response.status,
|
|
7124
|
+
note: answer.kind === "error" ? `${method}: ${answer.message}` : answer.why
|
|
7125
|
+
};
|
|
7126
|
+
}
|
|
7127
|
+
var DEADLINE_MS = 45e3;
|
|
7128
|
+
async function probeMcp(url, options = {}) {
|
|
7129
|
+
const clockFor = options.now ?? Date.now;
|
|
7130
|
+
const startedFor = clockFor();
|
|
7131
|
+
let timer;
|
|
7132
|
+
const gaveUp = new Promise((resolve) => {
|
|
7133
|
+
timer = setTimeout(
|
|
7134
|
+
() => resolve({
|
|
7135
|
+
url,
|
|
7136
|
+
observedAt: new Date(startedFor).toISOString(),
|
|
7137
|
+
reach: "dead",
|
|
7138
|
+
httpStatus: null,
|
|
7139
|
+
handshakeMs: options.deadlineMs ?? DEADLINE_MS,
|
|
7140
|
+
protocolVersion: null,
|
|
7141
|
+
serverName: null,
|
|
7142
|
+
serverVersion: null,
|
|
7143
|
+
tools: null,
|
|
7144
|
+
note: "never answered and never gave up"
|
|
7145
|
+
}),
|
|
7146
|
+
options.deadlineMs ?? DEADLINE_MS
|
|
7147
|
+
);
|
|
7148
|
+
});
|
|
7149
|
+
try {
|
|
7150
|
+
return await Promise.race([handshake(url, options), gaveUp]);
|
|
7151
|
+
} finally {
|
|
7152
|
+
clearTimeout(timer);
|
|
7153
|
+
}
|
|
7154
|
+
}
|
|
7155
|
+
async function handshake(url, options) {
|
|
7156
|
+
const doFetch = options.fetchImpl ?? fetch;
|
|
7157
|
+
const clock = options.now ?? Date.now;
|
|
7158
|
+
const userAgent = options.userAgent ?? `${CLIENT.name}/${CLIENT.version}`;
|
|
7159
|
+
const startedAt = clock();
|
|
7160
|
+
const base = {
|
|
7161
|
+
url,
|
|
7162
|
+
observedAt: new Date(startedAt).toISOString(),
|
|
7163
|
+
protocolVersion: null,
|
|
7164
|
+
serverName: null,
|
|
7165
|
+
serverVersion: null,
|
|
7166
|
+
tools: null
|
|
7167
|
+
};
|
|
7168
|
+
const send = (body, sessionId) => doFetch(url, {
|
|
7169
|
+
...initFor(body, sessionId, userAgent),
|
|
7170
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? HANDSHAKE_TIMEOUT_MS)
|
|
7171
|
+
});
|
|
7172
|
+
const took = () => Math.max(0, clock() - startedAt);
|
|
7173
|
+
try {
|
|
7174
|
+
const opened = await send(request(1, "initialize", INITIALIZE), null);
|
|
7175
|
+
const hello = await turn(opened, "initialize");
|
|
7176
|
+
if (hello.kind === "refused") {
|
|
7177
|
+
return {
|
|
7178
|
+
...base,
|
|
7179
|
+
reach: hello.reach,
|
|
7180
|
+
httpStatus: hello.status,
|
|
7181
|
+
handshakeMs: took(),
|
|
7182
|
+
note: null
|
|
7183
|
+
};
|
|
7184
|
+
}
|
|
7185
|
+
if (hello.kind === "broken") {
|
|
7186
|
+
return {
|
|
7187
|
+
...base,
|
|
7188
|
+
reach: "error",
|
|
7189
|
+
httpStatus: hello.status,
|
|
7190
|
+
handshakeMs: took(),
|
|
7191
|
+
note: hello.note
|
|
7192
|
+
};
|
|
7193
|
+
}
|
|
7194
|
+
const info = hello.value.serverInfo ?? {};
|
|
7195
|
+
const session = opened.headers.get("mcp-session-id");
|
|
7196
|
+
const said = {
|
|
7197
|
+
...base,
|
|
7198
|
+
protocolVersion: text(hello.value.protocolVersion),
|
|
7199
|
+
serverName: text(info.name),
|
|
7200
|
+
serverVersion: text(info.version)
|
|
7201
|
+
};
|
|
7202
|
+
await send(notification("notifications/initialized"), session).catch(() => void 0);
|
|
7203
|
+
const listed = await turn(await send(request(2, "tools/list"), session), "tools/list");
|
|
7204
|
+
if (listed.kind === "refused") {
|
|
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") {
|
|
7214
|
+
return {
|
|
7215
|
+
...said,
|
|
7216
|
+
reach: "error",
|
|
7217
|
+
httpStatus: listed.status,
|
|
7218
|
+
handshakeMs: took(),
|
|
7219
|
+
note: listed.note
|
|
7220
|
+
};
|
|
7221
|
+
}
|
|
7222
|
+
return {
|
|
7223
|
+
...said,
|
|
7224
|
+
reach: "readable",
|
|
7225
|
+
httpStatus: listed.status,
|
|
7226
|
+
handshakeMs: took(),
|
|
7227
|
+
tools: toolsFrom(listed.value),
|
|
7228
|
+
note: null
|
|
7229
|
+
};
|
|
7230
|
+
} catch (err) {
|
|
7231
|
+
const name = err.name;
|
|
7232
|
+
return {
|
|
7233
|
+
...base,
|
|
7234
|
+
reach: "dead",
|
|
7235
|
+
httpStatus: null,
|
|
7236
|
+
handshakeMs: took(),
|
|
7237
|
+
note: name === "TimeoutError" || name === "AbortError" ? "timed out" : "unreachable"
|
|
7238
|
+
};
|
|
7239
|
+
}
|
|
7240
|
+
}
|
|
7241
|
+
function toolsDigest(tools) {
|
|
7242
|
+
const canonical = JSON.stringify(
|
|
7243
|
+
[...tools].map((t) => [t.name, t.description ?? "", t.hasInputSchema]).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
|
|
7244
|
+
);
|
|
7245
|
+
return createHash("sha256").update(canonical).digest("hex");
|
|
7246
|
+
}
|
|
7247
|
+
|
|
7248
|
+
// src/mcp.ts
|
|
7249
|
+
function sha256(input) {
|
|
7250
|
+
return `sha256:${createHash2("sha256").update(input).digest("hex")}`;
|
|
7251
|
+
}
|
|
7252
|
+
var pass2 = (id, label, detail) => detail === void 0 ? { id, label, status: "pass" } : { id, label, status: "pass", detail };
|
|
7253
|
+
var fail2 = (id, label, detail) => ({
|
|
7254
|
+
id,
|
|
7255
|
+
label,
|
|
7256
|
+
status: "fail",
|
|
7257
|
+
detail
|
|
7258
|
+
});
|
|
7259
|
+
var skip = (id, label, detail) => ({
|
|
7260
|
+
id,
|
|
7261
|
+
label,
|
|
7262
|
+
status: "skip",
|
|
7263
|
+
detail
|
|
7264
|
+
});
|
|
7265
|
+
var REACH_LABEL = {
|
|
7266
|
+
readable: "listed its tools",
|
|
7267
|
+
auth_required: "asked for credentials",
|
|
7268
|
+
payment_required: "asked for payment",
|
|
7269
|
+
dead: "did not answer",
|
|
7270
|
+
error: "answered with something else"
|
|
7271
|
+
};
|
|
7272
|
+
function checksFor(seen) {
|
|
7273
|
+
const checks = [];
|
|
7274
|
+
checks.push(
|
|
7275
|
+
seen.reach === "dead" ? fail2("reachable", "server answered", seen.note ?? "no answer") : pass2("reachable", "server answered", REACH_LABEL[seen.reach])
|
|
7276
|
+
);
|
|
7277
|
+
if (seen.reach === "dead") return checks;
|
|
7278
|
+
checks.push(
|
|
7279
|
+
seen.protocolVersion === null ? skip("protocol", "named a protocol version", "it never got as far as saying") : pass2("protocol", "named a protocol version", seen.protocolVersion)
|
|
7280
|
+
);
|
|
7281
|
+
checks.push(
|
|
7282
|
+
seen.serverName === null ? skip("identity", "named itself", "it never got as far as saying") : pass2(
|
|
7283
|
+
"identity",
|
|
7284
|
+
"named itself",
|
|
7285
|
+
`${seen.serverName}${seen.serverVersion === null ? "" : ` ${seen.serverVersion}`}`
|
|
7286
|
+
)
|
|
7287
|
+
);
|
|
7288
|
+
if (seen.tools === null) {
|
|
7289
|
+
checks.push(skip("tools", "listed its tools", REACH_LABEL[seen.reach]));
|
|
7290
|
+
return checks;
|
|
7291
|
+
}
|
|
7292
|
+
checks.push(pass2("tools", "listed its tools", `${seen.tools.length} tool(s)`));
|
|
7293
|
+
const bare = seen.tools.filter((t) => !t.hasInputSchema).map((t) => t.name);
|
|
7294
|
+
checks.push(
|
|
7295
|
+
bare.length === 0 ? pass2("input-schemas", "every tool declared an input schema") : fail2(
|
|
7296
|
+
"input-schemas",
|
|
7297
|
+
"every tool declared an input schema",
|
|
7298
|
+
`${bare.length} without one: ${bare.slice(0, 5).join(", ")}`
|
|
7299
|
+
)
|
|
7300
|
+
);
|
|
7301
|
+
const unnamed = seen.tools.filter((t) => t.description === null).map((t) => t.name);
|
|
7302
|
+
checks.push(
|
|
7303
|
+
unnamed.length === 0 ? pass2("descriptions", "every tool said what it does") : fail2(
|
|
7304
|
+
"descriptions",
|
|
7305
|
+
"every tool said what it does",
|
|
7306
|
+
`${unnamed.length} without one: ${unnamed.slice(0, 5).join(", ")}`
|
|
7307
|
+
)
|
|
7308
|
+
);
|
|
7309
|
+
checks.push(pass2("digest", "tool list digest", toolsDigest(seen.tools)));
|
|
7310
|
+
return checks;
|
|
7311
|
+
}
|
|
7312
|
+
async function probeMcpEndpoint(options) {
|
|
7313
|
+
const seen = await probeMcp(options.url, {
|
|
7314
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
7315
|
+
...options.fetchImpl === void 0 ? {} : { fetchImpl: options.fetchImpl },
|
|
7316
|
+
...options.userAgent === void 0 ? {} : { userAgent: options.userAgent }
|
|
7317
|
+
});
|
|
7318
|
+
const checks = checksFor(seen);
|
|
7319
|
+
return {
|
|
7320
|
+
url: options.url,
|
|
7321
|
+
method: "MCP",
|
|
7322
|
+
observedAt: seen.observedAt,
|
|
7323
|
+
reachable: seen.reach !== "dead",
|
|
7324
|
+
httpStatus: seen.httpStatus,
|
|
7325
|
+
handshakeMs: seen.handshakeMs,
|
|
7326
|
+
termsSource: seen.reach === "payment_required" ? "mcp 402" : "none",
|
|
7327
|
+
advertised: [],
|
|
7328
|
+
requestHash: sha256(`MCP ${options.url}`),
|
|
7329
|
+
responseHash: seen.tools === null ? null : `sha256:${toolsDigest(seen.tools)}`,
|
|
7330
|
+
checks,
|
|
7331
|
+
verdict: checks.some((c) => c.status === "fail") ? "fail" : "pass"
|
|
7332
|
+
};
|
|
7333
|
+
}
|
|
7334
|
+
|
|
7335
|
+
// src/probe.ts
|
|
7336
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
7020
7337
|
|
|
7021
7338
|
// ../x402/src/handshake.ts
|
|
7022
|
-
var
|
|
7339
|
+
var HANDSHAKE_TIMEOUT_MS2 = 3e4;
|
|
7023
7340
|
var sendsBody = (method) => method !== "GET" && method !== "HEAD";
|
|
7024
|
-
function handshakeInit(
|
|
7025
|
-
const carries = sendsBody(
|
|
7341
|
+
function handshakeInit(request2) {
|
|
7342
|
+
const carries = sendsBody(request2.method);
|
|
7026
7343
|
const headers = { accept: "application/json" };
|
|
7027
7344
|
if (carries) headers["content-type"] = "application/json";
|
|
7028
|
-
if (
|
|
7029
|
-
if (
|
|
7345
|
+
if (request2.userAgent !== void 0) headers["user-agent"] = request2.userAgent;
|
|
7346
|
+
if (request2.payment) headers[request2.payment.header] = request2.payment.value;
|
|
7030
7347
|
return {
|
|
7031
|
-
method:
|
|
7348
|
+
method: request2.method,
|
|
7032
7349
|
headers,
|
|
7033
|
-
...carries ? { body: JSON.stringify(
|
|
7034
|
-
signal: AbortSignal.timeout(
|
|
7350
|
+
...carries ? { body: JSON.stringify(request2.input ?? {}) } : {},
|
|
7351
|
+
signal: AbortSignal.timeout(request2.timeoutMs ?? HANDSHAKE_TIMEOUT_MS2)
|
|
7035
7352
|
};
|
|
7036
7353
|
}
|
|
7037
|
-
async function
|
|
7354
|
+
async function handshake2(request2, deps = {}) {
|
|
7038
7355
|
const doFetch = deps.fetchImpl ?? fetch;
|
|
7039
7356
|
const now = deps.now ?? Date.now;
|
|
7040
7357
|
const started = now();
|
|
7041
7358
|
const since = () => Math.max(0, now() - started);
|
|
7042
7359
|
try {
|
|
7043
|
-
const response = await doFetch(
|
|
7360
|
+
const response = await doFetch(request2.url, handshakeInit(request2));
|
|
7044
7361
|
return { ok: true, response, body: await response.text(), latencyMs: since() };
|
|
7045
7362
|
} catch (err) {
|
|
7046
7363
|
return { ok: false, reason: err.message, latencyMs: since() };
|
|
@@ -7049,8 +7366,8 @@ async function handshake(request, deps = {}) {
|
|
|
7049
7366
|
|
|
7050
7367
|
// src/probe.ts
|
|
7051
7368
|
var DEFAULT_USER_AGENT = "node";
|
|
7052
|
-
function
|
|
7053
|
-
return `sha256:${
|
|
7369
|
+
function sha2562(input) {
|
|
7370
|
+
return `sha256:${createHash3("sha256").update(input).digest("hex")}`;
|
|
7054
7371
|
}
|
|
7055
7372
|
function advertisedFrom(terms) {
|
|
7056
7373
|
if (!terms) return [];
|
|
@@ -7066,7 +7383,7 @@ async function probe(options) {
|
|
|
7066
7383
|
const body = options.body ?? "{}";
|
|
7067
7384
|
const doFetch = options.fetchImpl ?? fetch;
|
|
7068
7385
|
const observedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7069
|
-
const requestHash =
|
|
7386
|
+
const requestHash = sha2562(`${method} ${options.url}
|
|
7070
7387
|
${body}`);
|
|
7071
7388
|
const base = {
|
|
7072
7389
|
url: options.url,
|
|
@@ -7076,7 +7393,7 @@ ${body}`);
|
|
|
7076
7393
|
termsSource: "none",
|
|
7077
7394
|
advertised: []
|
|
7078
7395
|
};
|
|
7079
|
-
const answer = await
|
|
7396
|
+
const answer = await handshake2(
|
|
7080
7397
|
{
|
|
7081
7398
|
url: options.url,
|
|
7082
7399
|
method,
|
|
@@ -7097,9 +7414,9 @@ ${body}`);
|
|
|
7097
7414
|
verdict: "fail"
|
|
7098
7415
|
};
|
|
7099
7416
|
}
|
|
7100
|
-
const { response, body:
|
|
7417
|
+
const { response, body: text2 } = answer;
|
|
7101
7418
|
const handshakeMs = answer.latencyMs;
|
|
7102
|
-
const parsed = parseTerms(response.headers,
|
|
7419
|
+
const parsed = parseTerms(response.headers, text2);
|
|
7103
7420
|
const checks = runHandshakeChecks({
|
|
7104
7421
|
status: response.status,
|
|
7105
7422
|
headers: response.headers,
|
|
@@ -7114,7 +7431,7 @@ ${body}`);
|
|
|
7114
7431
|
handshakeMs,
|
|
7115
7432
|
termsSource: parsed.source,
|
|
7116
7433
|
advertised: advertisedFrom(parsed.terms),
|
|
7117
|
-
responseHash:
|
|
7434
|
+
responseHash: sha2562(text2),
|
|
7118
7435
|
checks,
|
|
7119
7436
|
verdict: verdictOf(checks)
|
|
7120
7437
|
};
|
|
@@ -7134,8 +7451,8 @@ function shouldColour(env, isTty) {
|
|
|
7134
7451
|
if (env.FORCE_COLOR !== void 0 && env.FORCE_COLOR !== "0") return true;
|
|
7135
7452
|
return isTty;
|
|
7136
7453
|
}
|
|
7137
|
-
function paint(colour,
|
|
7138
|
-
return on ? `${ANSI[colour]}${
|
|
7454
|
+
function paint(colour, text2, on) {
|
|
7455
|
+
return on ? `${ANSI[colour]}${text2}${ANSI.reset}` : text2;
|
|
7139
7456
|
}
|
|
7140
7457
|
var MARK = { pass: "ok", fail: "fail", skip: "skip" };
|
|
7141
7458
|
var COLOUR = { pass: "green", fail: "red", skip: "yellow" };
|
|
@@ -7181,11 +7498,14 @@ function formatJson(result) {
|
|
|
7181
7498
|
}
|
|
7182
7499
|
export {
|
|
7183
7500
|
DEFAULT_USER_AGENT,
|
|
7501
|
+
REACH_LABEL,
|
|
7502
|
+
checksFor,
|
|
7184
7503
|
formatJson,
|
|
7185
7504
|
formatText,
|
|
7186
7505
|
parseTerms,
|
|
7187
7506
|
priceOf,
|
|
7188
7507
|
probe,
|
|
7508
|
+
probeMcpEndpoint,
|
|
7189
7509
|
runHandshakeChecks,
|
|
7190
7510
|
schemaCheck,
|
|
7191
7511
|
schemasOf,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const JSON_RPC = "2.0";
|
|
2
|
+
export declare const PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26"];
|
|
3
|
+
export declare const PREFERRED_VERSION: "2025-06-18";
|
|
4
|
+
export type RpcResult = {
|
|
5
|
+
readonly kind: 'result';
|
|
6
|
+
readonly value: Record<string, unknown>;
|
|
7
|
+
} | {
|
|
8
|
+
readonly kind: 'error';
|
|
9
|
+
readonly code: number;
|
|
10
|
+
readonly message: string;
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: 'unreadable';
|
|
13
|
+
readonly why: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function request(id: number, method: string, params?: Record<string, unknown>): string;
|
|
16
|
+
export declare function notification(method: string): string;
|
|
17
|
+
export declare function lastEventData(body: string): string | null;
|
|
18
|
+
export declare function readAnswer(contentType: string | null, body: string): RpcResult;
|
|
@@ -0,0 +1,2 @@
|
|
|
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';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export declare const HANDSHAKE_TIMEOUT_MS = 20000;
|
|
2
|
+
export declare const CLIENT: {
|
|
3
|
+
readonly name: "teppi-probe";
|
|
4
|
+
readonly version: "0.1.0";
|
|
5
|
+
};
|
|
6
|
+
export type Reach = 'readable' | 'auth_required' | 'payment_required' | 'dead' | 'error';
|
|
7
|
+
export type Tool = {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly description: string | null;
|
|
10
|
+
readonly hasInputSchema: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type McpObservation = {
|
|
13
|
+
readonly url: string;
|
|
14
|
+
readonly observedAt: string;
|
|
15
|
+
readonly reach: Reach;
|
|
16
|
+
readonly httpStatus: number | null;
|
|
17
|
+
readonly handshakeMs: number;
|
|
18
|
+
readonly protocolVersion: string | null;
|
|
19
|
+
readonly serverName: string | null;
|
|
20
|
+
readonly serverVersion: string | null;
|
|
21
|
+
readonly tools: readonly Tool[] | null;
|
|
22
|
+
readonly note: string | null;
|
|
23
|
+
};
|
|
24
|
+
export type ProbeOptions = {
|
|
25
|
+
readonly timeoutMs?: number;
|
|
26
|
+
readonly deadlineMs?: number;
|
|
27
|
+
readonly fetchImpl?: typeof fetch;
|
|
28
|
+
readonly now?: () => number;
|
|
29
|
+
readonly userAgent?: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function initFor(body: string, sessionId: string | null, userAgent: string): RequestInit;
|
|
32
|
+
export declare const INITIALIZE: {
|
|
33
|
+
protocolVersion: "2025-06-18";
|
|
34
|
+
capabilities: {};
|
|
35
|
+
clientInfo: {
|
|
36
|
+
readonly name: "teppi-probe";
|
|
37
|
+
readonly version: "0.1.0";
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
export declare function toolsFrom(result: Record<string, unknown>): Tool[];
|
|
41
|
+
export declare function reachFor(status: number): Reach | null;
|
|
42
|
+
export declare const DEADLINE_MS = 45000;
|
|
43
|
+
export declare function probeMcp(url: string, options?: ProbeOptions): Promise<McpObservation>;
|
|
44
|
+
export declare function toolsDigest(tools: readonly Tool[]): string;
|
package/npm/mcp.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type McpObservation } from './mcp-probe/index.js';
|
|
2
|
+
import type { Check } from './checks.js';
|
|
3
|
+
import type { ProbeResult } from './probe.js';
|
|
4
|
+
export type McpProbeOptions = {
|
|
5
|
+
readonly url: string;
|
|
6
|
+
readonly userAgent?: string;
|
|
7
|
+
readonly timeoutMs?: number;
|
|
8
|
+
readonly fetchImpl?: typeof fetch;
|
|
9
|
+
};
|
|
10
|
+
export declare const REACH_LABEL: Readonly<Record<McpObservation['reach'], string>>;
|
|
11
|
+
export declare function checksFor(seen: McpObservation): Check[];
|
|
12
|
+
export declare function probeMcpEndpoint(options: McpProbeOptions): Promise<ProbeResult>;
|
|
13
|
+
//# sourceMappingURL=mcp.d.ts.map
|
package/npm/mcp.d.ts.map
ADDED
|
@@ -0,0 +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,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACzC,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,CAsBrF"}
|
package/npm/probe.d.ts
CHANGED
package/npm/report.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProbeResult } from './probe.
|
|
1
|
+
import type { ProbeResult } from './probe.js';
|
|
2
2
|
export declare function shouldColour(env: Record<string, string | undefined>, isTty: boolean): boolean;
|
|
3
3
|
export declare function formatText(result: ProbeResult, colour: boolean): string;
|
|
4
4
|
export declare function formatJson(result: ProbeResult): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teppi-check",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Probe any paid endpoint and print what it actually returns for the money. Reproduces any entry in the Teppi record.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"x402",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
".": "./npm/index.js"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
+
"@teppi/mcp-probe": "0.0.0",
|
|
23
24
|
"@teppi/x402": "0.0.0",
|
|
24
25
|
"ajv": "^8.17.1",
|
|
25
26
|
"esbuild": "^0.28.2",
|