@checkbox-site/mcp 0.2.7 → 0.2.9
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/dist/format.d.ts +6 -1
- package/dist/format.js +7 -2
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/tools.d.ts +14 -0
- package/dist/tools.js +100 -26
- package/package.json +2 -2
package/dist/format.d.ts
CHANGED
|
@@ -47,13 +47,18 @@ export interface ToolOutput {
|
|
|
47
47
|
next?: NextAction | null;
|
|
48
48
|
/** Mark the result as an error the model should act on (still a normal result). */
|
|
49
49
|
isError?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Round 36 (pilot 35 item 18: setup and plan responses ran to 20-44 KB): print the JSON without indentation. The tools
|
|
52
|
+
* with a `verbose` argument use it by default.
|
|
53
|
+
*/
|
|
54
|
+
compactJson?: boolean;
|
|
50
55
|
}
|
|
51
56
|
/**
|
|
52
57
|
* Round-3 pilot (nextjs, sveltekit): output was cut at 60 KB in the middle of the JSON, which agents then failed to
|
|
53
58
|
* parse. Output is always valid JSON: an oversized list is paged (the first items that fit, plus `_page` naming the
|
|
54
59
|
* ids of the rest to fetch one by one), never cut mid-value.
|
|
55
60
|
*/
|
|
56
|
-
export declare function fitJson(data: unknown, max?: number): {
|
|
61
|
+
export declare function fitJson(data: unknown, max?: number, flatFirst?: boolean): {
|
|
57
62
|
json: string;
|
|
58
63
|
page?: string;
|
|
59
64
|
};
|
package/dist/format.js
CHANGED
|
@@ -296,7 +296,12 @@ function fitNestedLists(data, arr, max) {
|
|
|
296
296
|
* parse. Output is always valid JSON: an oversized list is paged (the first items that fit, plus `_page` naming the
|
|
297
297
|
* ids of the rest to fetch one by one), never cut mid-value.
|
|
298
298
|
*/
|
|
299
|
-
export function fitJson(data, max = MAX_JSON_CHARS) {
|
|
299
|
+
export function fitJson(data, max = MAX_JSON_CHARS, flatFirst = false) {
|
|
300
|
+
if (flatFirst) {
|
|
301
|
+
const flat = JSON.stringify(data);
|
|
302
|
+
if (flat.length <= max)
|
|
303
|
+
return { json: flat };
|
|
304
|
+
}
|
|
300
305
|
const pretty = JSON.stringify(data, null, 2);
|
|
301
306
|
if (pretty.length <= max)
|
|
302
307
|
return { json: pretty };
|
|
@@ -349,7 +354,7 @@ export function renderOutput(out) {
|
|
|
349
354
|
// Round 23 (pilot 22 item 27, agent-discovery: a result with no "Next:" line): every result names what comes next.
|
|
350
355
|
lines.push(next ?? (out.isError ? FALLBACK_NEXT_ERROR : FALLBACK_NEXT));
|
|
351
356
|
if (out.data !== undefined) {
|
|
352
|
-
const fitted = fitJson(forMcp(out.data));
|
|
357
|
+
const fitted = fitJson(forMcp(out.data), MAX_JSON_CHARS, out.compactJson === true);
|
|
353
358
|
if (fitted.page)
|
|
354
359
|
lines.push(`- ${fitted.page}`);
|
|
355
360
|
lines.push('', '```json', fitted.json, '```');
|
package/dist/server.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { KeyContext } from './permissions.ts';
|
|
|
5
5
|
import { type IssuedKeys, type ToolContext, type ToolDef } from './tools.ts';
|
|
6
6
|
export declare const SERVER_NAME = "checkbox";
|
|
7
7
|
/** Kept equal to packages/mcp/package.json "version" (@checkbox-site/mcp); mcp.test.ts checks it. */
|
|
8
|
-
export declare const SERVER_VERSION = "0.2.
|
|
8
|
+
export declare const SERVER_VERSION = "0.2.9";
|
|
9
9
|
export declare const DEFAULT_MCP_URL = "https://app.checkbox.site/mcp";
|
|
10
10
|
/** The coverage phrase used across Checkbox (the API docs tests check the count against the rules catalog). */
|
|
11
11
|
export declare const COVERAGE_PHRASE = "federal law, 27 states, DC and New York City";
|
package/dist/server.js
CHANGED
|
@@ -9,7 +9,7 @@ import { likelyMisspelling, unknownArgumentsMessage, unknownKeysParams } from ".
|
|
|
9
9
|
import { outdatedNotice } from "./version-check.js";
|
|
10
10
|
export const SERVER_NAME = 'checkbox';
|
|
11
11
|
/** Kept equal to packages/mcp/package.json "version" (@checkbox-site/mcp); mcp.test.ts checks it. */
|
|
12
|
-
export const SERVER_VERSION = '0.2.
|
|
12
|
+
export const SERVER_VERSION = '0.2.9';
|
|
13
13
|
export const DEFAULT_MCP_URL = 'https://app.checkbox.site/mcp';
|
|
14
14
|
/** The coverage phrase used across Checkbox (the API docs tests check the count against the rules catalog). */
|
|
15
15
|
export const COVERAGE_PHRASE = 'federal law, 27 states, DC and New York City';
|
package/dist/tools.d.ts
CHANGED
|
@@ -139,6 +139,20 @@ export declare function sitePlansNote(plans: unknown): string | null;
|
|
|
139
139
|
* and complete_setup carry them in full), and once the Stripe key is stored its permission checklist is dropped. Pure.
|
|
140
140
|
*/
|
|
141
141
|
export declare function compactSetupDoc<T>(doc: T): T;
|
|
142
|
+
/**
|
|
143
|
+
* Round 36 (pilot 35 item 18, new-app / dev-approver: setup responses of 20-40 KB): the default setup response. On top of
|
|
144
|
+
* compactSetupDoc, the latest check's top findings become a count (get_compliance_plan and list_findings have them) and
|
|
145
|
+
* finished checklist steps keep only key, title and status. Every open step, id, status and next_action stays. Pure.
|
|
146
|
+
*/
|
|
147
|
+
export declare function compactSetupResponse<T>(doc: T): T;
|
|
148
|
+
/** A task's next_action without what an MCP agent does not use: the CLI command, and the endpoint when a tool performs it. Pure. */
|
|
149
|
+
export declare function slimNextAction(na: unknown, taskId?: string): unknown;
|
|
150
|
+
/**
|
|
151
|
+
* Round 36 (pilot 35 item 18: get_compliance_plan answered with 28-44 KB): the default plan. Open tasks as slimPlan,
|
|
152
|
+
* with `why` shortened and next_action without the CLI command; closed tasks as id, key, title, status, tier and why
|
|
153
|
+
* they closed; go_live as its summary (the notes list each step). verbose=true returns the API's full plan. Pure.
|
|
154
|
+
*/
|
|
155
|
+
export declare function compactPlan(plan: PlanLite): PlanLite;
|
|
142
156
|
/**
|
|
143
157
|
* Pilot round 9 (nextjs): setup_checkbox still showed `insurable: false` and `score: 26.8` next to the status label.
|
|
144
158
|
* The API keeps both (deprecated) for backward compatibility; agents see only the one status.
|
package/dist/tools.js
CHANGED
|
@@ -87,7 +87,6 @@ const listLimit = z.number().int().min(1).max(100).optional().describe('How many
|
|
|
87
87
|
const startingAfter = z.string().max(100).optional().describe('Pagination cursor: the last id of the previous page (when has_more was true).');
|
|
88
88
|
/** Plan fields agents reach for, mapped to the setup field (plans[] amounts are minor units). */
|
|
89
89
|
const PLAN_FIELD_HINTS = Object.freeze({
|
|
90
|
-
price: 'amount (minor units: 800 = $8.00)',
|
|
91
90
|
price_cents: 'amount (minor units: 800 = $8.00)',
|
|
92
91
|
amount_cents: 'amount',
|
|
93
92
|
unit_amount: 'amount',
|
|
@@ -99,15 +98,15 @@ const PLAN_FIELD_HINTS = Object.freeze({
|
|
|
99
98
|
cancel_url: 'account_url (a site field, not a plan field)',
|
|
100
99
|
});
|
|
101
100
|
const setupFactsShape = {
|
|
102
|
-
site_url: z.string().max(2048).optional().describe('Public URL of the site (production
|
|
103
|
-
checkout_url: z.string().max(2048).optional().describe('
|
|
101
|
+
site_url: z.string().max(2048).optional().describe('Public URL of the site (production or a preview), e.g. "https://acme.example".'),
|
|
102
|
+
checkout_url: z.string().max(2048).optional().describe('Pricing or checkout page.'),
|
|
104
103
|
account_url: z.string().max(2048).nullable().optional().describe('Account page where subscribers manage / cancel (null clears a URL).'),
|
|
105
104
|
terms_url: z.string().max(2048).nullable().optional().describe('Terms of service URL. Checkbox fetches and checks it.'),
|
|
106
105
|
privacy_url: z.string().max(2048).nullable().optional().describe('Privacy policy URL.'),
|
|
107
106
|
refund_url: z.string().max(2048).nullable().optional().describe('Refund policy URL.'),
|
|
108
107
|
cancellation_url: z.string().max(2048).nullable().optional().describe('Cancellation policy URL, if separate from the Terms.'),
|
|
109
108
|
support_email: z.string().max(320).optional().describe('Support address for subscribers.'),
|
|
110
|
-
merchant_name: z.string().max(100).optional().describe('The brand subscribers know (names the seller in generated disclosures), e.g. from the <title> or package.json
|
|
109
|
+
merchant_name: z.string().max(100).optional().describe('The brand subscribers know (names the seller in generated disclosures), e.g. from the <title> or package.json Default: the host name, which the plan asks you to replace.'),
|
|
111
110
|
billing_platform: z
|
|
112
111
|
.enum(['stripe', 'shopify', 'recharge', 'chargebee', 'recurly', 'paddle', 'lemon_squeezy', 'woocommerce', 'custom'])
|
|
113
112
|
.optional()
|
|
@@ -126,10 +125,13 @@ const setupFactsShape = {
|
|
|
126
125
|
intro_amount_off: z.number().int().positive().optional(),
|
|
127
126
|
intro_periods: z.number().int().min(1).max(36).optional(),
|
|
128
127
|
price_id: z.string().max(200).optional().describe('Stripe Price id.'),
|
|
128
|
+
// Round 36 (pilot 35 item 21, codex / dev-approver: the first call sent plans[].price in dollars and was refused).
|
|
129
|
+
// The API takes it as amount in MAJOR units and refuses a price that disagrees with amount.
|
|
130
|
+
price: z.number().positive().optional().describe('Or major units (19 = $19).'),
|
|
129
131
|
}, 'plans[]', PLAN_FIELD_HINTS))
|
|
130
132
|
.max(50)
|
|
131
133
|
.optional()
|
|
132
|
-
.describe('Plans and prices found in the code, e.g. [{"name":"Pro","amount":1900,"currency":"usd","interval":"month","trial_days":14}]; a promo adds intro_percent_off and intro_periods. They decide which laws apply
|
|
134
|
+
.describe('Plans and prices found in the code, e.g. [{"name":"Pro","amount":1900,"currency":"usd","interval":"month","trial_days":14}]; a promo adds intro_percent_off and intro_periods. They decide which laws apply.'),
|
|
133
135
|
project: z
|
|
134
136
|
.object({
|
|
135
137
|
name: z.string().max(200).optional().describe('package.json name.'),
|
|
@@ -422,7 +424,7 @@ export function sitePlansNote(plans) {
|
|
|
422
424
|
* (site_url) and the first checks run in the same call (homepage + snippet, policies fetched and checked, evaluation,
|
|
423
425
|
* first scan), exactly like the end of the device-login path.
|
|
424
426
|
*/
|
|
425
|
-
async function setupOverOAuth(api, session, ctx) {
|
|
427
|
+
async function setupOverOAuth(api, session, ctx, verbose = false) {
|
|
426
428
|
const notes = [
|
|
427
429
|
`Connected to Checkbox through ${ctx.key.appName ?? ctx.clientName ?? 'this app'} with the human's approval (${ctx.key.mode} mode${ctx.key.kind === 'restricted' ? ', read-only' : ''}). No API key is needed: do not ask the human for one.`,
|
|
428
430
|
];
|
|
@@ -457,11 +459,13 @@ async function setupOverOAuth(api, session, ctx) {
|
|
|
457
459
|
return {
|
|
458
460
|
headline: `Setup session ${session.id} started${session.business?.site_url ? ` for ${untrusted(session.business.site_url, 100)}` : ''}${session.site ? ` (test-mode site ${session.site})` : ''}.`,
|
|
459
461
|
notes,
|
|
462
|
+
compactJson: !verbose,
|
|
460
463
|
...(next ? { next } : {}),
|
|
461
464
|
data: (() => {
|
|
462
465
|
const doc = { ...session, ...(fresh ?? {}) };
|
|
463
466
|
const keys = labelledPublicKeys(doc.install?.snippet_key, doc.publishable_key);
|
|
464
|
-
|
|
467
|
+
const out = withoutLegacyScore({ ...doc, ...(keys.length ? { public_keys: keys } : {}), ...(firstCheck ? { first_check: firstCheck } : {}) });
|
|
468
|
+
return verbose ? out : compactSetupResponse(out);
|
|
465
469
|
})(),
|
|
466
470
|
};
|
|
467
471
|
}
|
|
@@ -476,7 +480,7 @@ export function compactSetupDoc(doc) {
|
|
|
476
480
|
const d = { ...doc };
|
|
477
481
|
const g = d.go_live;
|
|
478
482
|
if (g && typeof g === 'object' && Array.isArray(g.steps))
|
|
479
|
-
d.go_live = { summary: g.summary ?? null, steps: g.steps.length, full: 'get_compliance_plan
|
|
483
|
+
d.go_live = { summary: g.summary ?? null, steps: g.steps.length, full: 'the go-live notes of get_compliance_plan, complete_setup, or verbose=true' };
|
|
480
484
|
const st = d.stripe;
|
|
481
485
|
if (st && st.test_credential_stored === true && st.restricted_key && Array.isArray(st.restricted_key.permissions)) {
|
|
482
486
|
const { permissions: _p, ...rk } = st.restricted_key;
|
|
@@ -484,6 +488,67 @@ export function compactSetupDoc(doc) {
|
|
|
484
488
|
}
|
|
485
489
|
return d;
|
|
486
490
|
}
|
|
491
|
+
/** `verbose` on the setup and plan tools (round 36, pilot 35 item 18). */
|
|
492
|
+
const verboseArg = z.boolean().optional().describe('true: the full JSON object.');
|
|
493
|
+
const CLOSED_STEPS = new Set(['done', 'skipped', 'not_applicable']);
|
|
494
|
+
/**
|
|
495
|
+
* Round 36 (pilot 35 item 18, new-app / dev-approver: setup responses of 20-40 KB): the default setup response. On top of
|
|
496
|
+
* compactSetupDoc, the latest check's top findings become a count (get_compliance_plan and list_findings have them) and
|
|
497
|
+
* finished checklist steps keep only key, title and status. Every open step, id, status and next_action stays. Pure.
|
|
498
|
+
*/
|
|
499
|
+
export function compactSetupResponse(doc) {
|
|
500
|
+
if (!doc || typeof doc !== 'object')
|
|
501
|
+
return doc;
|
|
502
|
+
const d = compactSetupDoc({ ...doc });
|
|
503
|
+
const c = d.compliance;
|
|
504
|
+
if (c && typeof c === 'object' && Array.isArray(c.top_findings)) {
|
|
505
|
+
const { top_findings: top, ...rest } = c;
|
|
506
|
+
d.compliance = { ...rest, top_findings_count: top.length };
|
|
507
|
+
}
|
|
508
|
+
if (Array.isArray(d.steps)) {
|
|
509
|
+
d.steps = d.steps.map((st) => {
|
|
510
|
+
const x = st;
|
|
511
|
+
return x && typeof x === 'object' && typeof x.status === 'string' && CLOSED_STEPS.has(x.status) ? { key: x.key, title: x.title, status: x.status } : st;
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
for (const k of ['setup_session', 'session'])
|
|
515
|
+
if (d[k] && typeof d[k] === 'object')
|
|
516
|
+
d[k] = compactSetupResponse(d[k]);
|
|
517
|
+
return d;
|
|
518
|
+
}
|
|
519
|
+
/** A task's next_action without what an MCP agent does not use: the CLI command, and the endpoint when a tool performs it. Pure. */
|
|
520
|
+
export function slimNextAction(na, taskId) {
|
|
521
|
+
if (!na || typeof na !== 'object' || Array.isArray(na))
|
|
522
|
+
return na;
|
|
523
|
+
const { command: _command, ...rest } = na;
|
|
524
|
+
if (taskId && rest.task === taskId)
|
|
525
|
+
delete rest.task;
|
|
526
|
+
if (typeof rest.mcp_tool === 'string' && rest.mcp_tool !== 'call_api')
|
|
527
|
+
delete rest.endpoint;
|
|
528
|
+
return rest;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Round 36 (pilot 35 item 18: get_compliance_plan answered with 28-44 KB): the default plan. Open tasks as slimPlan,
|
|
532
|
+
* with `why` shortened and next_action without the CLI command; closed tasks as id, key, title, status, tier and why
|
|
533
|
+
* they closed; go_live as its summary (the notes list each step). verbose=true returns the API's full plan. Pure.
|
|
534
|
+
*/
|
|
535
|
+
export function compactPlan(plan) {
|
|
536
|
+
const slim = slimPlan(plan);
|
|
537
|
+
const full = new Map((plan.tasks ?? []).map((t) => [t.id, t]));
|
|
538
|
+
const tasks = (slim.tasks ?? []).map((t) => {
|
|
539
|
+
const x = t;
|
|
540
|
+
if (t.status === 'done' || t.status === 'wont_fix') {
|
|
541
|
+
const f = full.get(t.id) ?? {};
|
|
542
|
+
return { id: t.id, key: x.key, title: t.title, status: t.status, tier: t.tier, compliance_status: x.compliance_status, closed_reason: f.closed_reason ?? null };
|
|
543
|
+
}
|
|
544
|
+
return { ...x, why: clipWords(x.why, 200), next_action: slimNextAction(x.next_action, t.id) };
|
|
545
|
+
});
|
|
546
|
+
const out = { ...slim, tasks };
|
|
547
|
+
const g = out.go_live;
|
|
548
|
+
if (g && typeof g === 'object' && Array.isArray(g.steps))
|
|
549
|
+
out.go_live = { summary: g.summary ?? null, steps: g.steps.length, full: 'the notes above, or verbose=true' };
|
|
550
|
+
return out;
|
|
551
|
+
}
|
|
487
552
|
/**
|
|
488
553
|
* Pilot round 9 (nextjs): setup_checkbox still showed `insurable: false` and `score: 26.8` next to the status label.
|
|
489
554
|
* The API keeps both (deprecated) for backward compatibility; agents see only the one status.
|
|
@@ -544,6 +609,7 @@ const setupCheckbox = {
|
|
|
544
609
|
device_code: z.string().max(200).optional().describe('From an earlier setup_checkbox call: poll that browser approval instead of starting a new one.'),
|
|
545
610
|
live: z.boolean().optional().describe('Also request live-mode keys (live mode activates after domain verification). Default false: test mode only.'),
|
|
546
611
|
agent: z.string().max(100).optional().describe('Which AI agent is doing this, shown to the human on the approval page, e.g. "Claude Code".'),
|
|
612
|
+
verbose: verboseArg,
|
|
547
613
|
},
|
|
548
614
|
scopes: ['setup_sessions:write'],
|
|
549
615
|
public: true,
|
|
@@ -630,6 +696,7 @@ const setupCheckbox = {
|
|
|
630
696
|
return {
|
|
631
697
|
headline: `Approved. Checkbox account ready${site ? ` with test-mode site ${site}` : ''}; setup session ${res.setup_session ?? '(none)'}.`,
|
|
632
698
|
notes,
|
|
699
|
+
compactJson: !args.verbose,
|
|
633
700
|
...(next ? { next } : {}),
|
|
634
701
|
data: {
|
|
635
702
|
...(saved ? withoutSecrets(res) : res),
|
|
@@ -677,7 +744,7 @@ const setupCheckbox = {
|
|
|
677
744
|
body.client = { name: 'checkbox-mcp', version: ctx.serverVersion, ...(args.agent || ctx.key.appName || ctx.clientName ? { agent: String(args.agent ?? ctx.key.appName ?? ctx.clientName).slice(0, 100) } : {}) };
|
|
678
745
|
const session = await api.request('POST', '/v1/setup_sessions', { body });
|
|
679
746
|
if (ctx.key.auth === 'oauth')
|
|
680
|
-
return setupOverOAuth(api, session, ctx);
|
|
747
|
+
return setupOverOAuth(api, session, ctx, args.verbose === true);
|
|
681
748
|
const notes = session.review_url ? [`Human review page: ${session.review_url}`] : [];
|
|
682
749
|
if (session.next_action?.type === 'verify_domain')
|
|
683
750
|
notes.push(firstCheckNote(session.next_action));
|
|
@@ -687,9 +754,11 @@ const setupCheckbox = {
|
|
|
687
754
|
return {
|
|
688
755
|
headline: `Setup session ${session.id} started${session.business?.site_url ? ` for ${untrusted(session.business.site_url, 100)}` : ''}${session.site ? ` (test-mode site ${session.site})` : ''}.`,
|
|
689
756
|
notes,
|
|
757
|
+
compactJson: !args.verbose,
|
|
690
758
|
data: (() => {
|
|
691
759
|
const keys = labelledPublicKeys(session.install?.snippet_key, session.publishable_key);
|
|
692
|
-
|
|
760
|
+
const out = keys.length ? { ...withoutLegacyScore(session), public_keys: keys } : withoutLegacyScore(session);
|
|
761
|
+
return args.verbose ? out : compactSetupResponse(out);
|
|
693
762
|
})(),
|
|
694
763
|
};
|
|
695
764
|
},
|
|
@@ -750,7 +819,7 @@ const getSetupStatus = {
|
|
|
750
819
|
const pl = sitePlansNote(res.setup_session?.site_plans);
|
|
751
820
|
if (pl)
|
|
752
821
|
notes.push(pl);
|
|
753
|
-
return { headline: `Re-checked setup session ${id}.`, ...(notes.length ? { notes } : {}), data: withoutLegacyScore(res), next: nextActionOf(res) ?? nextActionOf(res.setup_session) ?? nextActionOf(res.session) };
|
|
822
|
+
return { headline: `Re-checked setup session ${id}.`, ...(notes.length ? { notes } : {}), compactJson: true, data: compactSetupResponse(withoutLegacyScore(res)), next: nextActionOf(res) ?? nextActionOf(res.setup_session) ?? nextActionOf(res.session) };
|
|
754
823
|
}
|
|
755
824
|
const session = await api.request('GET', `/v1/setup_sessions/${enc(id)}`);
|
|
756
825
|
const open = (session.steps ?? []).filter((s) => s.status && !['done', 'skipped', 'not_applicable'].includes(s.status));
|
|
@@ -760,7 +829,8 @@ const getSetupStatus = {
|
|
|
760
829
|
headline: `Setup session ${session.id}: ${session.status}; ${open.length} checklist step(s) open.`,
|
|
761
830
|
// The policies step says what an unverified preview read (or that the pages wait for domain verification).
|
|
762
831
|
notes: [...open.slice(0, 8).map((s) => `${s.status}: ${s.title ?? s.key}${s.key === 'policies' && s.summary ? ` (${s.summary.slice(0, 240)})` : ''}`), ...(ro ? [ro] : []), ...(plansLine ? [plansLine] : []), ...(session.next_action?.type === 'done' ? goLiveNotes(session.go_live) : [])],
|
|
763
|
-
|
|
832
|
+
compactJson: true,
|
|
833
|
+
data: compactSetupResponse(withoutLegacyScore(session)),
|
|
764
834
|
};
|
|
765
835
|
},
|
|
766
836
|
};
|
|
@@ -799,7 +869,7 @@ const updateSetup = {
|
|
|
799
869
|
const notes = Array.isArray(args.plans) && args.plans.length
|
|
800
870
|
? ['Plans saved: Checkbox re-evaluates the site with them now, so get_compliance_plan lists each plan\'s tasks (e.g. the trial reminder of a plan with a trial). If one seems missing, call verify_site to re-check at once.']
|
|
801
871
|
: [];
|
|
802
|
-
return { headline: `Updated setup session ${session.id}.`, notes, data: withoutLegacyScore(session) };
|
|
872
|
+
return { headline: `Updated setup session ${session.id}.`, notes, compactJson: true, data: compactSetupResponse(withoutLegacyScore(session)) };
|
|
803
873
|
},
|
|
804
874
|
};
|
|
805
875
|
/** Round 13 (pilot 12, codex: "sent" while nothing new arrived): the summary email's real delivery status, with when. */
|
|
@@ -899,12 +969,12 @@ const getCompliancePlan = {
|
|
|
899
969
|
title: 'Get the compliance plan (ordered tasks)',
|
|
900
970
|
description: [
|
|
901
971
|
'The main tool after setup. Returns the ordered tasks that make the site pass the automatic-renewal checks, recomputed from the latest observations.',
|
|
902
|
-
'Each task
|
|
972
|
+
'Each task here is a summary (id, status, why, where, change type, next_action); get_task has the full change (diff, copy or Stripe params) and its legal citation.',
|
|
903
973
|
'Plan status: action_required (apply open tasks), verifying (waiting for confirming observations), compliant (done: the checks pass — "pending_live_check" tasks count as passing: they complete on their own or with a proof run), blocked (only human questions — "Your call" — left).',
|
|
904
|
-
'
|
|
974
|
+
'Only tier "required" tasks count; "recommended" and "extra" tasks are optional suggestions (mention them once, do not push them).',
|
|
905
975
|
'Loop: apply the first open required task in the code → verify_task → get_compliance_plan → … until the checks pass (status compliant). Tasks with status "manual" carry a question with a recommended answer: ask the HUMAN, then answer_task. Never invent answers.',
|
|
906
976
|
].join('\n'),
|
|
907
|
-
input: { site: siteId.optional() },
|
|
977
|
+
input: { site: siteId.optional(), verbose: verboseArg },
|
|
908
978
|
scopes: ['compliance_plans:read'],
|
|
909
979
|
annotations: { ...ro, title: 'Compliance plan' },
|
|
910
980
|
async run(args, { api }) {
|
|
@@ -945,15 +1015,17 @@ const getCompliancePlan = {
|
|
|
945
1015
|
...(plan.status === 'compliant' ? await reviewHint(api, site, 'plan_compliant') : []),
|
|
946
1016
|
],
|
|
947
1017
|
// Normal-round pilot (sveltekit): the full plan (53K characters) was cut mid-JSON by the client. The plan here is a
|
|
948
|
-
// compact summary of every task; get_task returns one task in full.
|
|
949
|
-
|
|
1018
|
+
// compact summary of every task; get_task returns one task in full. Round 36 (pilot 35 item 18): compact JSON by
|
|
1019
|
+
// default, the API's full plan with verbose=true.
|
|
1020
|
+
compactJson: !args.verbose,
|
|
1021
|
+
data: args.verbose ? shown : compactPlan(shown),
|
|
950
1022
|
};
|
|
951
1023
|
},
|
|
952
1024
|
};
|
|
953
1025
|
const getTask = {
|
|
954
1026
|
name: 'get_task',
|
|
955
1027
|
title: 'Get one compliance task',
|
|
956
|
-
description: 'One task
|
|
1028
|
+
description: 'One plan task in full: the change to apply (unified-diff template with machine-applicable edits, copy or Stripe params), where, the legal requirement, its last verification and next_action. Call it before applying a task.',
|
|
957
1029
|
input: { task: z.string().max(100).describe('Task id (task_…) from get_compliance_plan.') },
|
|
958
1030
|
scopes: ['compliance_plans:read'],
|
|
959
1031
|
annotations: { ...ro, title: 'Task' },
|
|
@@ -1263,7 +1335,7 @@ const getTestRun = {
|
|
|
1263
1335
|
const progress = w ? [w.step && w.steps_total ? `step ${w.step} of ${w.steps_total}` : '', w.minutes_left ? `about ${w.minutes_left} min left` : ''].filter(Boolean).join(', ') : '';
|
|
1264
1336
|
const waiting = w ? `Waiting for: ${w.what}${w.until ? ` (until ${new Date(w.until * 1000).toISOString().slice(0, 16).replace('T', ' ')} UTC at the latest)` : ''}${progress ? ` [${progress}]` : ''}` : null;
|
|
1265
1337
|
return {
|
|
1266
|
-
headline: `Test run ${run.id}: ${run.status_label ?? statusLine(run.status)}${run.phase && run.status !== 'succeeded' && run.status !== 'failed' ? ` (phase ${run.phase})` : ''}
|
|
1338
|
+
headline: `Test run ${run.id}: ${run.status_label ?? statusLine(run.status)}${run.phase && run.status !== 'succeeded' && run.status !== 'failed' ? ` (phase ${run.phase})` : ''}.${run.plan_status_label ? ` Plan after this run: ${run.plan_status_label}.` : ''}`,
|
|
1267
1339
|
notes: [...(waiting ? [waiting] : []), ...(run.steps ?? []).map((s) => `${s.status ?? '?'}: ${s.title ?? s.key}${s.code ? ` (${s.code})` : ''}`)].slice(0, 13),
|
|
1268
1340
|
data: run,
|
|
1269
1341
|
...(next ? { next } : {}),
|
|
@@ -1393,12 +1465,12 @@ const checkPageHtml = {
|
|
|
1393
1465
|
title: 'Check a checkout / pricing page (HTML or URL)',
|
|
1394
1466
|
description: [
|
|
1395
1467
|
'Checks a whole checkout or pricing page: finds the purchase button, the renewal disclosure, consent checkboxes and terms/cancel links, and evaluates them. Send html = the RENDERED page (after JavaScript; for React/Next pages, render it or paste the server output) — this is how to check a page before deploying.',
|
|
1396
|
-
'Or send url to fetch a
|
|
1468
|
+
'Or send url to fetch a public page (live keys; test keys: your registered site\'s origin).',
|
|
1397
1469
|
'Static HTML cannot measure styling, so placement/styling passes come back as needs_review until a scan confirms; failures are real. When it fails, replacement holds verified drop-in markup.',
|
|
1398
1470
|
].join('\n'),
|
|
1399
1471
|
input: {
|
|
1400
1472
|
html: z.string().max(1_000_000).optional().describe('Rendered checkout page HTML. Give html or url.'),
|
|
1401
|
-
url: z.string().max(2048).optional().describe('
|
|
1473
|
+
url: z.string().max(2048).optional().describe('Public page URL to fetch instead.'),
|
|
1402
1474
|
page_kind: z.enum(['checkout', 'pricing']).optional().describe('Default checkout.'),
|
|
1403
1475
|
terms: planTerms.optional().describe('The plan sold on the page. merchant_name / cancel_url alone are fine (no price compared).'),
|
|
1404
1476
|
plans: z.array(strictObject(planTermsShape, 'plans[]')).max(20).optional().describe('Multi-plan pricing page: EVERY plan it sells (price, interval, trial_days each). Each plan card is judged against its own plan; plan_replacements gives each plan its own markup for its own button.'),
|
|
@@ -1698,7 +1770,7 @@ const updateSite = {
|
|
|
1698
1770
|
name: 'update_site',
|
|
1699
1771
|
title: 'Update a site\'s settings (write)',
|
|
1700
1772
|
description: [
|
|
1701
|
-
'WRITE: updates a site — the fields plan tasks of change.type "config" ask for (their change.content is this tool\'s input): checkout_url, checkout_steps, test_account { login_url, email, password } (stored encrypted; the response echoes it without the password), name (the merchant name in generated legal copy), platform, jurisdictions, monitoring_interval_minutes, protection_level
|
|
1773
|
+
'WRITE: updates a site — the fields plan tasks of change.type "config" ask for (their change.content is this tool\'s input): checkout_url, checkout_steps, checkout_pages, test_account { login_url, email, password } (stored encrypted; the response echoes it without the password), name (the merchant name in generated legal copy), platform, jurisdictions, monitoring_interval_minutes, protection_level.',
|
|
1702
1774
|
'The response is the site; check checkout.url / checkout.test_account to confirm. Then verify_task the task that asked for it.',
|
|
1703
1775
|
].join('\n'),
|
|
1704
1776
|
input: {
|
|
@@ -1706,6 +1778,7 @@ const updateSite = {
|
|
|
1706
1778
|
name: z.string().max(200).optional().describe('Merchant / brand name (used in generated disclosures).'),
|
|
1707
1779
|
checkout_url: z.string().max(2048).optional().describe('Page with the Subscribe / Buy button.'),
|
|
1708
1780
|
checkout_steps: z.array(z.record(z.string(), z.unknown())).max(20).optional().describe('Steps to reach the purchase button (clicks / fills), as in the API.'),
|
|
1781
|
+
checkout_pages: z.array(z.record(z.string(), z.string())).max(4).optional().describe('[{url,plan}] per plan'),
|
|
1709
1782
|
test_account: strictObject({
|
|
1710
1783
|
login_url: z.string().max(2048),
|
|
1711
1784
|
email: z.string().max(320),
|
|
@@ -1723,7 +1796,7 @@ const updateSite = {
|
|
|
1723
1796
|
annotations: { ...write(false, true), title: 'Update site' },
|
|
1724
1797
|
async run(args, { api }) {
|
|
1725
1798
|
const site = await resolveSite(api, args.site);
|
|
1726
|
-
const body = pick(args, ['name', 'checkout_url', 'checkout_steps', 'test_account', 'platform', 'jurisdictions', 'monitoring_interval_minutes', 'protection_level']);
|
|
1799
|
+
const body = pick(args, ['name', 'checkout_url', 'checkout_steps', 'checkout_pages', 'test_account', 'platform', 'jurisdictions', 'monitoring_interval_minutes', 'protection_level']);
|
|
1727
1800
|
if (!Object.keys(body).length)
|
|
1728
1801
|
throw new ToolError('Pass at least one field to change (e.g. checkout_url, test_account, name).');
|
|
1729
1802
|
const updated = await api.request('POST', `/v1/sites/${enc(site)}`, { body });
|
|
@@ -1856,7 +1929,8 @@ const listFindings = {
|
|
|
1856
1929
|
const failNote = args.status === 'fail' && !list.data.length ? ' status=fail is the engine verdict only: a needs_review finding can still be counted as a fix (its label says "Needs a fix"). The fixes to make are the tasks of get_compliance_plan.' : '';
|
|
1857
1930
|
return {
|
|
1858
1931
|
headline: `${list.data.length}${list.has_more ? '+' : ''} finding(s).${failNote}`,
|
|
1859
|
-
|
|
1932
|
+
// Round 35 (pilot 34 item 7): each finding with what the plan says about it (the plan decides what is left to do).
|
|
1933
|
+
notes: list.data.map((f) => `${f.severity.toUpperCase()} ${f.id} [${f.compliance?.label ?? f.status}] ${f.check_id}: ${f.title}${f.requirements?.[0] ? ` [${f.requirements[0].citation}]` : ''}${f.plan_task ? ` — plan: ${f.plan_task.label} (${f.plan_task.id})` : ''}`),
|
|
1860
1934
|
data: list,
|
|
1861
1935
|
};
|
|
1862
1936
|
},
|
|
@@ -1880,7 +1954,7 @@ const getFinding = {
|
|
|
1880
1954
|
? await optional(api.request('GET', `/v1/findings/${enc(id)}/fix`, { query: pick(args, ['target', 'cancel_url', 'plan']) }))
|
|
1881
1955
|
: { unavailable: 'This key has no fixes:read permission.' };
|
|
1882
1956
|
return {
|
|
1883
|
-
headline: `${finding.severity.toUpperCase()} ${finding.id} [${finding.compliance?.label ?? finding.status}] ${finding.check_id}: ${finding.title}`,
|
|
1957
|
+
headline: `${finding.severity.toUpperCase()} ${finding.id} [${finding.compliance?.label ?? finding.status}] ${finding.check_id}: ${finding.title}${finding.plan_task ? ` — plan: ${finding.plan_task.label} (${finding.plan_task.id})` : ''}`,
|
|
1884
1958
|
notes: [complianceBullet(finding.check_id, finding.compliance, finding.status, finding.title)].filter((x) => !!x),
|
|
1885
1959
|
data: { finding, fix },
|
|
1886
1960
|
next: findingNext(finding, nextActionOf(fix)),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkbox-site/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Checkbox MCP server: auto-renewal law and subscription compliance checks for AI agents (FTC ROSCA, California ARL, New York and other states) on Stripe Checkout params, pricing pages, disclosures and emails. Remote at https://app.checkbox.site/mcp, or local stdio.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"checkbox",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"prepack": "npm run build"
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"@checkbox-site/node": "^0.2.
|
|
80
|
+
"@checkbox-site/node": "^0.2.9",
|
|
81
81
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
82
82
|
"zod": "^4.6.7"
|
|
83
83
|
}
|