@checkbox-site/mcp 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 +3 -3
- package/dist/api.d.ts +7 -0
- package/dist/api.js +11 -1
- package/dist/format.d.ts +32 -1
- package/dist/format.js +261 -20
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/permissions.js +2 -2
- package/dist/resources.js +11 -10
- package/dist/server.d.ts +28 -3
- package/dist/server.js +104 -11
- package/dist/stdio.js +2 -1
- package/dist/todo.d.ts +25 -0
- package/dist/todo.js +35 -0
- package/dist/tools.d.ts +196 -1
- package/dist/tools.js +1084 -184
- package/dist/unknown-args.d.ts +22 -0
- package/dist/unknown-args.js +119 -0
- package/dist/version-check.d.ts +22 -0
- package/dist/version-check.js +50 -0
- package/package.json +2 -2
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Levenshtein distance (small inputs: argument names). */
|
|
2
|
+
export declare function editDistance(a: string, b: string): number;
|
|
3
|
+
/** The known argument an unknown one most likely means, if any is close enough. */
|
|
4
|
+
export declare function closestArgument(unknown: string, known: readonly string[]): string | undefined;
|
|
5
|
+
/**
|
|
6
|
+
* Round 21 (pilot 20 item 5): an unknown argument that is an alias or a near spelling of a known one (stripe_params,
|
|
7
|
+
* emial), as opposed to a field this server does not know yet (plan_variant): those are forwarded to the API, which
|
|
8
|
+
* refuses what it does not know either. A name that only contains a known one is not a misspelling here.
|
|
9
|
+
*/
|
|
10
|
+
export declare function likelyMisspelling(unknown: string, known: readonly string[]): string | undefined;
|
|
11
|
+
/** The error an agent reads for unknown arguments: each with a suggestion, then every argument the tool accepts. */
|
|
12
|
+
export declare function unknownArgumentsMessage(tool: string, keys: readonly string[], known: readonly string[]): string;
|
|
13
|
+
/**
|
|
14
|
+
* Pilot round 13: an unknown key inside a nested object (plans[].price_cents, merchant.name) is refused, naming the
|
|
15
|
+
* closest field. `hints` maps keys to a fuller suggestion for this object (e.g. price → amount in minor units).
|
|
16
|
+
*/
|
|
17
|
+
export declare function nestedUnknownMessage(where: string, keys: readonly string[], known: readonly string[], hints?: Readonly<Record<string, string>>): string;
|
|
18
|
+
/**
|
|
19
|
+
* Object params that word the unknown-keys error on both zod majors: the monorepo builds against zod 3 (`errorMap`),
|
|
20
|
+
* the published server ships zod 4 (`error`). Other issues keep zod's own message.
|
|
21
|
+
*/
|
|
22
|
+
export declare function unknownKeysParams(message: (keys: string[]) => string): any;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Round 12 (change-guard): MCP tools registered their input without .strict(), so zod dropped unknown arguments.
|
|
2
|
+
// verify_task with `stripe_params` (a misspelled stripe_checkout_session) found no params, judged the Checkout Sessions
|
|
3
|
+
// read from Stripe and read PASSED. Unknown arguments are now refused with the closest known name.
|
|
4
|
+
/** Names agents use for an argument, mapped to the real one (checked before the edit distance). */
|
|
5
|
+
const ALIASES = Object.freeze({
|
|
6
|
+
stripe_params: 'stripe_checkout_session',
|
|
7
|
+
checkout_params: 'stripe_checkout_session',
|
|
8
|
+
session_params: 'stripe_checkout_session',
|
|
9
|
+
checkout_session: 'stripe_checkout_session',
|
|
10
|
+
stripe_session: 'stripe_checkout_session',
|
|
11
|
+
session: 'stripe_checkout_session',
|
|
12
|
+
sessions: 'stripe_checkout_sessions',
|
|
13
|
+
task_id: 'task',
|
|
14
|
+
site_id: 'site',
|
|
15
|
+
plan_terms: 'terms',
|
|
16
|
+
merchant: 'terms',
|
|
17
|
+
terms: 'merchant',
|
|
18
|
+
body: 'text',
|
|
19
|
+
page_html: 'html',
|
|
20
|
+
price: 'price_id',
|
|
21
|
+
name: 'merchant_name',
|
|
22
|
+
brand: 'merchant_name',
|
|
23
|
+
price_cents: 'amount',
|
|
24
|
+
amount_cents: 'amount',
|
|
25
|
+
unit_amount: 'amount',
|
|
26
|
+
trial_period_days: 'trial_days',
|
|
27
|
+
billing_interval: 'interval',
|
|
28
|
+
});
|
|
29
|
+
/** Levenshtein distance (small inputs: argument names). */
|
|
30
|
+
export function editDistance(a, b) {
|
|
31
|
+
if (a === b)
|
|
32
|
+
return 0;
|
|
33
|
+
const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
34
|
+
for (let i = 1; i <= a.length; i++) {
|
|
35
|
+
let diag = prev[0];
|
|
36
|
+
prev[0] = i;
|
|
37
|
+
for (let j = 1; j <= b.length; j++) {
|
|
38
|
+
const up = prev[j];
|
|
39
|
+
prev[j] = Math.min(up + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
40
|
+
diag = up;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return prev[b.length];
|
|
44
|
+
}
|
|
45
|
+
/** The known argument an unknown one most likely means, if any is close enough. */
|
|
46
|
+
export function closestArgument(unknown, known) {
|
|
47
|
+
const k = unknown.toLowerCase();
|
|
48
|
+
const alias = ALIASES[k];
|
|
49
|
+
if (alias && known.includes(alias))
|
|
50
|
+
return alias;
|
|
51
|
+
let best;
|
|
52
|
+
for (const name of known) {
|
|
53
|
+
const d = editDistance(k, name.toLowerCase());
|
|
54
|
+
if (!best || d < best.d)
|
|
55
|
+
best = { name, d };
|
|
56
|
+
}
|
|
57
|
+
if (best && best.d <= Math.max(2, Math.floor(best.name.length / 3)))
|
|
58
|
+
return best.name;
|
|
59
|
+
// A prefix / containment match ("stripe" → stripe_checkout_session) when exactly one known name fits.
|
|
60
|
+
const contains = known.filter((n) => n.length >= 4 && (n.includes(k) || k.includes(n)));
|
|
61
|
+
return contains.length === 1 ? contains[0] : undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Round 21 (pilot 20 item 5): an unknown argument that is an alias or a near spelling of a known one (stripe_params,
|
|
65
|
+
* emial), as opposed to a field this server does not know yet (plan_variant): those are forwarded to the API, which
|
|
66
|
+
* refuses what it does not know either. A name that only contains a known one is not a misspelling here.
|
|
67
|
+
*/
|
|
68
|
+
export function likelyMisspelling(unknown, known) {
|
|
69
|
+
const k = unknown.toLowerCase();
|
|
70
|
+
const alias = ALIASES[k];
|
|
71
|
+
if (alias && known.includes(alias))
|
|
72
|
+
return alias;
|
|
73
|
+
let best;
|
|
74
|
+
for (const name of known) {
|
|
75
|
+
const d = editDistance(k, name.toLowerCase());
|
|
76
|
+
if (!best || d < best.d)
|
|
77
|
+
best = { name, d };
|
|
78
|
+
}
|
|
79
|
+
return best && best.d <= Math.max(2, Math.floor(best.name.length / 3)) ? best.name : undefined;
|
|
80
|
+
}
|
|
81
|
+
/** The error an agent reads for unknown arguments: each with a suggestion, then every argument the tool accepts. */
|
|
82
|
+
export function unknownArgumentsMessage(tool, keys, known) {
|
|
83
|
+
const parts = keys.map((key) => {
|
|
84
|
+
const hint = closestArgument(key, known);
|
|
85
|
+
return `unknown argument \`${key}\`${hint ? `; did you mean \`${hint}\`?` : ''}`;
|
|
86
|
+
});
|
|
87
|
+
return `${parts.join('. ')}. Nothing was checked. ${tool} accepts: ${known.join(', ')}.`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Pilot round 13: an unknown key inside a nested object (plans[].price_cents, merchant.name) is refused, naming the
|
|
91
|
+
* closest field. `hints` maps keys to a fuller suggestion for this object (e.g. price → amount in minor units).
|
|
92
|
+
*/
|
|
93
|
+
export function nestedUnknownMessage(where, keys, known, hints = {}) {
|
|
94
|
+
const parts = keys.map((key) => {
|
|
95
|
+
const hint = hints[key.toLowerCase()] ?? closestArgument(key, known);
|
|
96
|
+
return `unknown field \`${where}.${key}\`${hint ? `; did you mean \`${where}.${hint}\`?` : ''}`;
|
|
97
|
+
});
|
|
98
|
+
return `${parts.join('. ')}. Nothing was changed. ${where} accepts: ${known.join(', ')}.`;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Object params that word the unknown-keys error on both zod majors: the monorepo builds against zod 3 (`errorMap`),
|
|
102
|
+
* the published server ships zod 4 (`error`). Other issues keep zod's own message.
|
|
103
|
+
*/
|
|
104
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
105
|
+
export function unknownKeysParams(message) {
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
107
|
+
const pick = (issue) => (issue?.code === 'unrecognized_keys' ? message(issue.keys) : undefined);
|
|
108
|
+
return {
|
|
109
|
+
// zod 3
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
errorMap: (issue, ctx) => {
|
|
112
|
+
const m = pick(issue);
|
|
113
|
+
return m !== undefined ? { message: m } : ctx ? { message: ctx.defaultError } : undefined;
|
|
114
|
+
},
|
|
115
|
+
// zod 4 (undefined falls through to the default message)
|
|
116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
117
|
+
error: (issue) => pick(issue),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Response header on every /v1 response: the oldest @checkbox-site/mcp the API is written for. */
|
|
2
|
+
export declare const MCP_MIN_VERSION_HEADER = "checkbox-mcp-minimum-version";
|
|
3
|
+
/**
|
|
4
|
+
* The oldest @checkbox-site/mcp the API is written for. Raise it (with SERVER_VERSION) when the API adds arguments or
|
|
5
|
+
* operations that next_action / task text tells agents to use.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MCP_MINIMUM_VERSION = "0.2.0";
|
|
8
|
+
/** How a person updates the local server (what the README's config runs). */
|
|
9
|
+
export declare const MCP_UPDATE_COMMAND = "npx -y @checkbox-site/mcp@latest";
|
|
10
|
+
/** Compares dotted versions (1.10.0 > 1.9.0; a pre-release suffix is ignored). Negative when a < b. */
|
|
11
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
12
|
+
/** The words an agent reads when this server is older than the API expects; null when it is not (or unknown). */
|
|
13
|
+
export declare function outdatedNotice(current: string, minimum: string | null | undefined): string | null;
|
|
14
|
+
/** Remembers the newest minimum version the API sent (headers of any response). */
|
|
15
|
+
export declare class MinimumVersionTracker {
|
|
16
|
+
#private;
|
|
17
|
+
get minimum(): string | undefined;
|
|
18
|
+
/** Reads the header from a fetch Headers object or a plain lower-case record. */
|
|
19
|
+
observe(headers: {
|
|
20
|
+
get(name: string): string | null;
|
|
21
|
+
} | Record<string, string | undefined> | undefined | null): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Round 21 (pilot 20 item 5: testers ran @checkbox-site/mcp 0.1.1 from npm against an API that named arguments and
|
|
2
|
+
// operations 0.1.1 does not have, and nothing told them to update). Every /v1 response says which MCP server version
|
|
3
|
+
// the API expects (MCP_MIN_VERSION_HEADER); a local server older than that tells the agent, with the update command.
|
|
4
|
+
/** Response header on every /v1 response: the oldest @checkbox-site/mcp the API is written for. */
|
|
5
|
+
export const MCP_MIN_VERSION_HEADER = 'checkbox-mcp-minimum-version';
|
|
6
|
+
/**
|
|
7
|
+
* The oldest @checkbox-site/mcp the API is written for. Raise it (with SERVER_VERSION) when the API adds arguments or
|
|
8
|
+
* operations that next_action / task text tells agents to use.
|
|
9
|
+
*/
|
|
10
|
+
export const MCP_MINIMUM_VERSION = '0.2.0';
|
|
11
|
+
/** How a person updates the local server (what the README's config runs). */
|
|
12
|
+
export const MCP_UPDATE_COMMAND = 'npx -y @checkbox-site/mcp@latest';
|
|
13
|
+
/** Compares dotted versions (1.10.0 > 1.9.0; a pre-release suffix is ignored). Negative when a < b. */
|
|
14
|
+
export function compareVersions(a, b) {
|
|
15
|
+
const parts = (v) => v.trim().replace(/^v/i, '').split(/[-+]/)[0].split('.').map((x) => Number.parseInt(x, 10) || 0);
|
|
16
|
+
const pa = parts(a);
|
|
17
|
+
const pb = parts(b);
|
|
18
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
19
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
20
|
+
if (d)
|
|
21
|
+
return d;
|
|
22
|
+
}
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
const VERSION_RE = /^v?\d+(?:\.\d+){0,3}(?:[-+][\w.]+)?$/;
|
|
26
|
+
/** The words an agent reads when this server is older than the API expects; null when it is not (or unknown). */
|
|
27
|
+
export function outdatedNotice(current, minimum) {
|
|
28
|
+
const min = (minimum ?? '').trim();
|
|
29
|
+
if (!min || !VERSION_RE.test(min) || !VERSION_RE.test(current) || compareVersions(current, min) >= 0)
|
|
30
|
+
return null;
|
|
31
|
+
return `Your Checkbox MCP server is ${current}; the Checkbox API expects ${min} or newer, so arguments and operations its next steps name may be missing here. Ask the human to update it: in the MCP client config run \`${MCP_UPDATE_COMMAND}\` (then restart the client), or connect the remote server https://app.checkbox.site/mcp instead. Until then, call_api reaches any operation.`;
|
|
32
|
+
}
|
|
33
|
+
/** Remembers the newest minimum version the API sent (headers of any response). */
|
|
34
|
+
export class MinimumVersionTracker {
|
|
35
|
+
#minimum;
|
|
36
|
+
get minimum() {
|
|
37
|
+
return this.#minimum;
|
|
38
|
+
}
|
|
39
|
+
/** Reads the header from a fetch Headers object or a plain lower-case record. */
|
|
40
|
+
observe(headers) {
|
|
41
|
+
if (!headers)
|
|
42
|
+
return;
|
|
43
|
+
const raw = typeof headers.get === 'function' ? headers.get(MCP_MIN_VERSION_HEADER) : headers[MCP_MIN_VERSION_HEADER];
|
|
44
|
+
const v = (raw ?? '').trim();
|
|
45
|
+
if (!v || !VERSION_RE.test(v))
|
|
46
|
+
return;
|
|
47
|
+
if (!this.#minimum || compareVersions(v, this.#minimum) > 0)
|
|
48
|
+
this.#minimum = v;
|
|
49
|
+
}
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkbox-site/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Checkbox MCP server: subscription-law compliance tools for AI agents (remote at https://app.checkbox.site/mcp, or local stdio).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"subscription",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"prepack": "npm run build"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@checkbox-site/node": "^0.
|
|
67
|
+
"@checkbox-site/node": "^0.2.0",
|
|
68
68
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
69
69
|
"zod": "^4.6.7"
|
|
70
70
|
}
|