@checkbox-site/mcp 0.2.4 → 0.2.6
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/resources.js +3 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/tools.d.ts +44 -0
- package/dist/tools.js +85 -23
- package/package.json +2 -2
package/dist/resources.js
CHANGED
|
@@ -62,6 +62,9 @@ what to change. Free. Every feature. No credit card. Fair-use limits.
|
|
|
62
62
|
6. **start_test_run** — a synthetic subscription in Stripe test mode (card 4242, Checkbox inbox, test clock through
|
|
63
63
|
trial end and renewal, online cancellation) observes every lifecycle check; **get_test_run** for results.
|
|
64
64
|
7. Repeat 3-4 until the checks pass (plan status value \`compliant\`, label "Checks pass"), then **complete_setup** (emails the human a summary).
|
|
65
|
+
8. A setup in Stripe test mode is not live yet: the plan and complete_setup carry **go_live** (the steps left before
|
|
66
|
+
real customers, with who takes each: verify the domain, the live Stripe key — a person only —, the live checkout,
|
|
67
|
+
promoting the site). Tell the human "set up in test mode; N steps before real customers", never just "done".
|
|
65
68
|
|
|
66
69
|
Every tool result ends with "Next: …" naming the tool to call. Errors carry a stable \`code\` and often a next_action.
|
|
67
70
|
|
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.6";
|
|
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.6';
|
|
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
|
@@ -133,6 +133,12 @@ export declare function clipWords(v: unknown, n: number): unknown;
|
|
|
133
133
|
export declare function unverifiedScanNote(u: unknown): string | null;
|
|
134
134
|
/** Round 20: one line with the plans Checkbox judges the site against (setup's site_plans), so no second call is needed. */
|
|
135
135
|
export declare function sitePlansNote(plans: unknown): string | null;
|
|
136
|
+
/**
|
|
137
|
+
* Round 33 (pilot 32 item 22, new-app: setup_checkbox answered with about 30 KB on the first call): the first setup
|
|
138
|
+
* response leaves out what the agent does not need yet. The go-live steps become their one-line summary (get_compliance_plan
|
|
139
|
+
* and complete_setup carry them in full), and once the Stripe key is stored its permission checklist is dropped. Pure.
|
|
140
|
+
*/
|
|
141
|
+
export declare function compactSetupDoc<T>(doc: T): T;
|
|
136
142
|
/**
|
|
137
143
|
* Pilot round 9 (nextjs): setup_checkbox still showed `insurable: false` and `score: 26.8` next to the status label.
|
|
138
144
|
* The API keeps both (deprecated) for backward compatibility; agents see only the one status.
|
|
@@ -153,6 +159,17 @@ export declare function summaryEmailLine(status: string | null | undefined, d: {
|
|
|
153
159
|
to?: string | null;
|
|
154
160
|
error?: string | null;
|
|
155
161
|
} | null | undefined): string;
|
|
162
|
+
export interface GoLiveLite {
|
|
163
|
+
summary: string;
|
|
164
|
+
steps: Array<{
|
|
165
|
+
key: string;
|
|
166
|
+
who: 'person' | 'agent';
|
|
167
|
+
text: string;
|
|
168
|
+
url?: string;
|
|
169
|
+
}>;
|
|
170
|
+
}
|
|
171
|
+
/** Round 32 (pilot 31 item 1): the go-live list as notes: what to tell the owner, then each step with who takes it. Pure. */
|
|
172
|
+
export declare function goLiveNotes(g: GoLiveLite | null | undefined): string[];
|
|
156
173
|
export declare function planStatusWords(status: string, label?: string | null): string;
|
|
157
174
|
interface PlanLite {
|
|
158
175
|
status: string;
|
|
@@ -200,6 +217,21 @@ interface PrecheckLite {
|
|
|
200
217
|
}
|
|
201
218
|
/** Audit: the pre-check's per-check results, one compact line each (agents only saw the overall verdict). */
|
|
202
219
|
export declare function precheckLines(pre: PrecheckLite | null | undefined, max?: number): string[];
|
|
220
|
+
interface FinalCheckLite {
|
|
221
|
+
check_id: string;
|
|
222
|
+
observed: string;
|
|
223
|
+
detail?: string;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Round 33 (pilot 32 item 10, new-app: the PASSED_WITH_NOTES text listed the stale pre-check bullets (needs_review)
|
|
227
|
+
* while the final checks lived only in the JSON): a finished verification is summarized from its FINAL checks; the
|
|
228
|
+
* pre-check lines show only while it is pending or when it has no final checks. Pure, exported for tests.
|
|
229
|
+
*/
|
|
230
|
+
export declare function verificationLines(v: {
|
|
231
|
+
status: string;
|
|
232
|
+
precheck?: PrecheckLite | null;
|
|
233
|
+
checks?: FinalCheckLite[] | null;
|
|
234
|
+
}, max?: number): string[];
|
|
203
235
|
/** createTestRun body from the tool's arguments (top-level shortcuts merged into `stripe`). Exported for tests. */
|
|
204
236
|
export declare function startTestRunBody(args: Record<string, unknown>): Record<string, unknown>;
|
|
205
237
|
interface ScheduledNotice {
|
|
@@ -237,6 +269,18 @@ interface SiteShopifyLite {
|
|
|
237
269
|
}
|
|
238
270
|
/** The site's Shopify connection in one line, naming the tool (or the human step) that comes next. */
|
|
239
271
|
export declare function shopifyLines(sh: SiteShopifyLite | null | undefined, site: string): string[];
|
|
272
|
+
/**
|
|
273
|
+
* Round 32 (pilot 31, codex: get_finding on a not_applicable / resolved finding said "apply_fix … verify_site"). Next
|
|
274
|
+
* follows the finding's status: nothing to fix for a finding that passes, does not apply or is resolved. Pure, exported
|
|
275
|
+
* for tests.
|
|
276
|
+
*/
|
|
277
|
+
export declare function findingNext(finding: {
|
|
278
|
+
status: string;
|
|
279
|
+
state?: string;
|
|
280
|
+
compliance?: {
|
|
281
|
+
status?: string;
|
|
282
|
+
} | null;
|
|
283
|
+
}, fromFix: NextAction | null): NextAction;
|
|
240
284
|
/**
|
|
241
285
|
* Agent audit round 2: list_law_updates with limit 3 printed 62 KB (one catalog update carried 195 requirement changes,
|
|
242
286
|
* each with its full summary). Each update keeps its first changes in short form, with the total.
|
package/dist/tools.js
CHANGED
|
@@ -461,10 +461,29 @@ async function setupOverOAuth(api, session, ctx) {
|
|
|
461
461
|
data: (() => {
|
|
462
462
|
const doc = { ...session, ...(fresh ?? {}) };
|
|
463
463
|
const keys = labelledPublicKeys(doc.install?.snippet_key, doc.publishable_key);
|
|
464
|
-
return withoutLegacyScore({ ...doc, ...(keys.length ? { public_keys: keys } : {}), ...(firstCheck ? { first_check: firstCheck } : {}) });
|
|
464
|
+
return compactSetupDoc(withoutLegacyScore({ ...doc, ...(keys.length ? { public_keys: keys } : {}), ...(firstCheck ? { first_check: firstCheck } : {}) }));
|
|
465
465
|
})(),
|
|
466
466
|
};
|
|
467
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* Round 33 (pilot 32 item 22, new-app: setup_checkbox answered with about 30 KB on the first call): the first setup
|
|
470
|
+
* response leaves out what the agent does not need yet. The go-live steps become their one-line summary (get_compliance_plan
|
|
471
|
+
* and complete_setup carry them in full), and once the Stripe key is stored its permission checklist is dropped. Pure.
|
|
472
|
+
*/
|
|
473
|
+
export function compactSetupDoc(doc) {
|
|
474
|
+
if (!doc || typeof doc !== 'object')
|
|
475
|
+
return doc;
|
|
476
|
+
const d = { ...doc };
|
|
477
|
+
const g = d.go_live;
|
|
478
|
+
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 (go_live) or complete_setup' };
|
|
480
|
+
const st = d.stripe;
|
|
481
|
+
if (st && st.test_credential_stored === true && st.restricted_key && Array.isArray(st.restricted_key.permissions)) {
|
|
482
|
+
const { permissions: _p, ...rk } = st.restricted_key;
|
|
483
|
+
d.stripe = { ...st, restricted_key: rk };
|
|
484
|
+
}
|
|
485
|
+
return d;
|
|
486
|
+
}
|
|
468
487
|
/**
|
|
469
488
|
* Pilot round 9 (nextjs): setup_checkbox still showed `insurable: false` and `score: 26.8` next to the status label.
|
|
470
489
|
* The API keeps both (deprecated) for backward compatibility; agents see only the one status.
|
|
@@ -646,6 +665,8 @@ const setupCheckbox = {
|
|
|
646
665
|
headline: `Ask the human to open ${auth.verification_uri_complete} and click Approve (code ${auth.user_code}). It expires in ${Math.round(auth.expires_in / 60)} minutes.`,
|
|
647
666
|
notes: [
|
|
648
667
|
'Tell the human exactly one line, e.g. "Open <link> and click Approve to connect Checkbox (free, no card)." Do not ask them to copy keys.',
|
|
668
|
+
// Round 32 (pilot 31 item 13): the Stripe key box and the restricted key in the same message (one approval).
|
|
669
|
+
`${args.billing_platform === 'stripe' ? 'In the same message' : 'If the site sells through Stripe, in the same message'}, ask them to tick the box “Let your agent store a Stripe test-mode restricted key for this project” on that page and, if the project has no rk_test_ key yet, to create a restricted key in Stripe Test mode (https://dashboard.stripe.com/test/apikeys → Create restricted key, with the permissions the approval page lists) and put it in the project's env file (never in the chat): one approval then covers the key and the test subscription.`,
|
|
649
670
|
`Then call setup_checkbox again with device_code "${auth.device_code}" AND the same facts (site_url, checkout_url, merchant_name, plans, policy URLs): after the approval Checkbox applies them and runs the first checks in that call. Keep the device_code private; it is not shown to the human.`,
|
|
650
671
|
],
|
|
651
672
|
next: { type: 'wait_for_approval', message: `Call setup_checkbox with device_code "${auth.device_code}" to wait for the approval.`, operation_id: 'createDeviceToken' },
|
|
@@ -668,7 +689,7 @@ const setupCheckbox = {
|
|
|
668
689
|
notes,
|
|
669
690
|
data: (() => {
|
|
670
691
|
const keys = labelledPublicKeys(session.install?.snippet_key, session.publishable_key);
|
|
671
|
-
return keys.length ? { ...withoutLegacyScore(session), public_keys: keys } : withoutLegacyScore(session);
|
|
692
|
+
return compactSetupDoc(keys.length ? { ...withoutLegacyScore(session), public_keys: keys } : withoutLegacyScore(session));
|
|
672
693
|
})(),
|
|
673
694
|
};
|
|
674
695
|
},
|
|
@@ -738,7 +759,7 @@ const getSetupStatus = {
|
|
|
738
759
|
return {
|
|
739
760
|
headline: `Setup session ${session.id}: ${session.status}; ${open.length} checklist step(s) open.`,
|
|
740
761
|
// The policies step says what an unverified preview read (or that the pages wait for domain verification).
|
|
741
|
-
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] : [])],
|
|
762
|
+
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) : [])],
|
|
742
763
|
data: withoutLegacyScore(session),
|
|
743
764
|
};
|
|
744
765
|
},
|
|
@@ -792,19 +813,27 @@ export function summaryEmailLine(status, d) {
|
|
|
792
813
|
return 'not sent: this deployment has no email delivery';
|
|
793
814
|
return 'not sent';
|
|
794
815
|
}
|
|
816
|
+
/** Round 32 (pilot 31 item 1): the go-live list as notes: what to tell the owner, then each step with who takes it. Pure. */
|
|
817
|
+
export function goLiveNotes(g) {
|
|
818
|
+
if (!g?.steps?.length)
|
|
819
|
+
return [];
|
|
820
|
+
return [g.summary, ...g.steps.map((s, i) => `Go live ${i + 1} (${s.who === 'person' ? 'owner' : 'you, the agent'}): ${s.text}${s.url ? ` ${s.url}` : ''}`)];
|
|
821
|
+
}
|
|
795
822
|
const completeSetup = {
|
|
796
823
|
name: 'complete_setup',
|
|
797
824
|
title: 'Finish setup and email the human a summary',
|
|
798
|
-
description: 'Call once the setup checklist next_action is "done" (or nothing else can be done without the human). Marks the setup finished and emails the person who approved it "<agent> set up Checkbox for <domain>" with what was created, what is still open and a review link. Idempotent: the email is sent once. Monitoring keeps running.
|
|
825
|
+
description: 'Call once the setup checklist next_action is "done" (or nothing else can be done without the human). Marks the setup finished and emails the person who approved it "<agent> set up Checkbox for <domain>" with what was created, what is still open, the go-live steps and a review link. Idempotent: the email is sent once. Monitoring keeps running. In test mode it returns go_live: tell the human go_live.summary ("set up in test mode; N steps before real customers"), never just "done".',
|
|
799
826
|
input: { setup_session: z.string().max(100).optional().describe('Setup session id (setup_…). Default: the newest one.') },
|
|
800
827
|
scopes: ['setup_sessions:write'],
|
|
801
828
|
annotations: { ...write(true), title: 'Complete setup' },
|
|
802
829
|
async run(args, { api }) {
|
|
803
830
|
const id = await resolveSetupSession(api, args.setup_session);
|
|
804
831
|
const session = await api.request('POST', `/v1/setup_sessions/${enc(id)}/complete`, { body: {} });
|
|
832
|
+
// Round 32 (pilot 31 item 1): a test-mode setup is "set up in test mode; N steps before real customers", never "done".
|
|
833
|
+
const live = goLiveNotes(session.go_live);
|
|
805
834
|
return {
|
|
806
|
-
headline: `Setup ${session.id} completed; summary email: ${summaryEmailLine(session.owner_email, session.owner_email_detail)}.`,
|
|
807
|
-
notes: session.review_url ? [`Human review page: ${session.review_url}`] : [],
|
|
835
|
+
headline: `Setup ${session.id} completed${session.go_live ? ` in Stripe test mode (${session.go_live.steps.length} step${session.go_live.steps.length === 1 ? '' : 's'} left before real customers)` : ''}; summary email: ${summaryEmailLine(session.owner_email, session.owner_email_detail)}.`,
|
|
836
|
+
notes: [...live, ...(session.review_url ? [`Human review page: ${session.review_url}`] : [])],
|
|
808
837
|
data: withoutLegacyScore(session),
|
|
809
838
|
};
|
|
810
839
|
},
|
|
@@ -912,6 +941,7 @@ const getCompliancePlan = {
|
|
|
912
941
|
...(optional.length ? [`Optional suggestions (${plan.protection_level ?? 'balanced'} protection; never block the checks from passing): ${optional.map((t) => t.title).join('; ')}`] : []),
|
|
913
942
|
...(plan.optional_improvements ? ['Optional (never blocking, not part of compliance): conversion suggestions for this site with `suggest_improvements`.'] : []),
|
|
914
943
|
'Each task below is a summary; call get_task with its id for the full change (diff, copy or params) before applying it.',
|
|
944
|
+
...goLiveNotes(plan.go_live),
|
|
915
945
|
...(plan.status === 'compliant' ? await reviewHint(api, site, 'plan_compliant') : []),
|
|
916
946
|
],
|
|
917
947
|
// Normal-round pilot (sveltekit): the full plan (53K characters) was cut mid-JSON by the client. The plan here is a
|
|
@@ -941,11 +971,26 @@ export function precheckLines(pre, max = 12) {
|
|
|
941
971
|
const checks = pre?.checks ?? [];
|
|
942
972
|
if (!checks.length)
|
|
943
973
|
return [];
|
|
944
|
-
const order = { fail: 0, needs_review: 1, missing_data: 1, pass: 2, not_applicable: 3 };
|
|
974
|
+
const order = { fail: 0, needs_review: 1, missing_data: 1, pass: 2, informational: 2, not_applicable: 3 };
|
|
945
975
|
const sorted = [...checks].sort((a, b) => (order[a.status] ?? 1) - (order[b.status] ?? 1));
|
|
946
976
|
const lines = sorted.slice(0, max).map((c) => `${c.check_id}: ${c.status}${c.reason ? ` — ${untrusted(c.reason, 180)}` : ''}${c.fix ? ` Fix: ${untrusted(c.fix, 200)}` : ''}`);
|
|
947
977
|
return [`Pre-check (${pre?.verdict ?? 'n/a'}), per check:`, ...lines, ...(checks.length > max ? [`and ${checks.length - max} more (data.precheck.checks)`] : [])];
|
|
948
978
|
}
|
|
979
|
+
/**
|
|
980
|
+
* Round 33 (pilot 32 item 10, new-app: the PASSED_WITH_NOTES text listed the stale pre-check bullets (needs_review)
|
|
981
|
+
* while the final checks lived only in the JSON): a finished verification is summarized from its FINAL checks; the
|
|
982
|
+
* pre-check lines show only while it is pending or when it has no final checks. Pure, exported for tests.
|
|
983
|
+
*/
|
|
984
|
+
export function verificationLines(v, max = 12) {
|
|
985
|
+
const checks = v.checks ?? [];
|
|
986
|
+
if (v.status === 'pending' || !checks.length)
|
|
987
|
+
return precheckLines(v.precheck, max);
|
|
988
|
+
const order = { fail: 0, needs_review: 1, missing_data: 1, pass: 2, informational: 2, not_applicable: 3 };
|
|
989
|
+
const sorted = [...checks].sort((a, b) => (order[a.observed] ?? 1) - (order[b.observed] ?? 1));
|
|
990
|
+
const lines = sorted.slice(0, max).map((c) => `${c.check_id}: ${c.observed}${c.detail ? ` — ${untrusted(c.detail, 180)}` : ''}`);
|
|
991
|
+
const pre = v.precheck?.verdict && v.precheck.checks?.length ? [`(The pre-check's estimate, ${v.precheck.verdict}, is superseded by these final checks: data.precheck keeps it.)`] : [];
|
|
992
|
+
return ['Final checks:', ...lines, ...(checks.length > max ? [`and ${checks.length - max} more (data.checks)`] : []), ...pre];
|
|
993
|
+
}
|
|
949
994
|
const verifyTask = {
|
|
950
995
|
name: 'verify_task',
|
|
951
996
|
title: 'Verify a task after applying its change',
|
|
@@ -955,7 +1000,7 @@ const verifyTask = {
|
|
|
955
1000
|
'- page tasks: html = the rendered page HTML (before or after deploy; ≤ ~900 KB). Without it Checkbox fetches the live page.',
|
|
956
1001
|
'- Stripe tasks: stripe_checkout_session = exactly the object your code now passes to stripe.checkout.sessions.create.',
|
|
957
1002
|
'- email tasks: email = { subject, text and/or html } of the new template.',
|
|
958
|
-
'Result: verification status passed / failed (with a diff of expected vs observed) / pending (waiting for the confirming scan). Waits up to `wait` seconds. Then follow next_action (usually get_compliance_plan).
|
|
1003
|
+
'Result: verification status passed / needs_review / failed (with a diff of expected vs observed) / pending (waiting for the confirming scan). Waits up to `wait` seconds. Then follow next_action (usually get_compliance_plan). Status needs_review: nothing failed, but a check is still undecided; it is not done: follow next_action. "informational" checks are notes on Stripe\'s own layout: nothing to change.',
|
|
959
1004
|
].join('\n'),
|
|
960
1005
|
input: {
|
|
961
1006
|
task: z.string().max(100).describe('Task id (task_…).'),
|
|
@@ -990,7 +1035,7 @@ const verifyTask = {
|
|
|
990
1035
|
return {
|
|
991
1036
|
headline: `Verification ${v.id} of ${String(args.task)}: ${statusLine(v.status)}${v.plan_status ? `; plan is now ${planStatusWords(v.plan_status, v.plan_status_label)}` : ''}.${v.checked ? ` Checked: ${untrusted(v.checked, 300)}.` : ''}`,
|
|
992
1037
|
notes: [
|
|
993
|
-
...
|
|
1038
|
+
...verificationLines(v),
|
|
994
1039
|
...((v.status === 'failed' || v.status === 'not_checked') && v.next_action?.type === 'verify_domain'
|
|
995
1040
|
? ['The domain is not verified yet. Waiting for the human to publish the DNS record / file / meta tag or install the snippet; don\'t retry until they say it\'s done. Nothing to change in the code.']
|
|
996
1041
|
: v.next_action?.type === 'retry_later'
|
|
@@ -1000,15 +1045,17 @@ const verifyTask = {
|
|
|
1000
1045
|
[v.next_action.description ?? 'Checkbox Notices sends this email: nothing to add to your code. Continue with the next task.']
|
|
1001
1046
|
: v.next_action?.type === 'bcc_then_continue'
|
|
1002
1047
|
? ['The copy is checked; the task closes when the first real email arrives with the BCC. Add the BCC to your sending code and continue with the next task (no need to poll).']
|
|
1003
|
-
: v.status === '
|
|
1004
|
-
? ['
|
|
1005
|
-
: v.status === '
|
|
1006
|
-
? ['
|
|
1007
|
-
: v.status === '
|
|
1008
|
-
? ['
|
|
1009
|
-
: v.status === '
|
|
1010
|
-
? ['
|
|
1011
|
-
:
|
|
1048
|
+
: v.status === 'needs_review'
|
|
1049
|
+
? ['Nothing failed, but a check is still undecided: it is not done. Follow Next (it says what decides it).']
|
|
1050
|
+
: v.status === 'failed'
|
|
1051
|
+
? ['The change did not satisfy the check: read diff (expected vs observed), fix the code, and call verify_task again.']
|
|
1052
|
+
: v.status === 'not_checked'
|
|
1053
|
+
? ['Not checked yet: Checkbox is missing evidence (see next_action), not a code problem. Do not change the code for this; provide what next_action asks for.']
|
|
1054
|
+
: v.status === 'pending_live_check'
|
|
1055
|
+
? ['Live check pending: the email content passes; Checkbox has not seen it sent for a real event yet. Nothing to change in the code: it completes on the first real send (or a proof run).']
|
|
1056
|
+
: v.status === 'pending'
|
|
1057
|
+
? ['Waiting for a confirming observation; call get_verification later (or continue with other tasks).']
|
|
1058
|
+
: []),
|
|
1012
1059
|
...hint,
|
|
1013
1060
|
],
|
|
1014
1061
|
data: v,
|
|
@@ -1065,7 +1112,7 @@ const getVerification = {
|
|
|
1065
1112
|
annotations: { ...ro, title: 'Verification' },
|
|
1066
1113
|
async run(args, { api }) {
|
|
1067
1114
|
const v = await api.request('GET', `/v1/verifications/${enc(String(args.verification))}`, { query: { wait: args.wait ?? 0 } });
|
|
1068
|
-
const notes =
|
|
1115
|
+
const notes = verificationLines(v);
|
|
1069
1116
|
return { headline: `Verification ${v.id}: ${statusLine(v.status)}.`, ...(notes.length ? { notes } : {}), data: v };
|
|
1070
1117
|
},
|
|
1071
1118
|
};
|
|
@@ -1115,7 +1162,7 @@ const startTestRun = {
|
|
|
1115
1162
|
name: 'start_test_run',
|
|
1116
1163
|
title: 'Run a synthetic test subscription (test mode)',
|
|
1117
1164
|
description: [
|
|
1118
|
-
'Observes the WHOLE subscription lifecycle
|
|
1165
|
+
'Observes the WHOLE subscription lifecycle in the billing platform\'s TEST mode: subscribes a test customer (card 4242…) with a Checkbox inbox email, waits for the acknowledgment, fast-forwards a Stripe test clock through each notice window before trial end and renewal (collecting reminders), then cancels online and waits for the confirmation. Nothing is charged.',
|
|
1119
1166
|
'Needs a Stripe TEST-mode restricted key (rk_test_…) stored for the site: store it yourself with store_stripe_test_key (one key for checks, fixes and test runs; live keys stay with the human in the dashboard). Never ask the human to paste a key into the chat. If missing, the error\'s next_action says so. For non-Stripe sites use type "generic".',
|
|
1120
1167
|
'Usually 1-2 minutes (real_checkout: a few): poll get_test_run (its next_action says when, and minutes_left estimates the rest). Each step reports observations, check results and agent-readable fixes; apply them and run again.',
|
|
1121
1168
|
'mode "real_checkout" is the PROOF run: the shopper buys through the merchant\'s own checkout on its test deployment (stripe.checkout_url or stripe.payment_link), so the merchant\'s webhook handlers and email code fire; give stripe.test_account + stripe.cancel_url when customers cancel in the app. A step is "passed" only with evidence; "warning" means needs review.',
|
|
@@ -1315,7 +1362,9 @@ const checkEmail = {
|
|
|
1315
1362
|
annotations: { ...ro, title: 'Check email' },
|
|
1316
1363
|
async run(args, { api }) {
|
|
1317
1364
|
const r = await api.request('POST', '/v1/checks/email', { body: { ...args, kind: checkKind(args.kind) } });
|
|
1318
|
-
|
|
1365
|
+
// Round 32 (pilot 31): a template with values left to fill ({{support_contact}}, {{cancel_url}}) passes once they are filled.
|
|
1366
|
+
const toFill = r.template?.placeholders ?? [];
|
|
1367
|
+
const notes = checkNotes(r, 8, r.template?.verified ? { by: `template (subject + text or html; add your greeting and sign-off around it if you like${toFill.length ? `; replace ${toFill.join(', ')} with your real value${toFill.length === 1 ? '' : 's'} first, or pass them in terms` : ''})`, except: r.template.remaining_checks ?? [] } : undefined);
|
|
1319
1368
|
notes.unshift(r.required_by?.length ? `Legally required for this plan by: ${citationList(r.required_by, r.requirements)}.` : 'Not legally required for this plan (still good practice).');
|
|
1320
1369
|
return { headline: verdictHeadline(`Email (${String(args.kind)}${r.plan ? `, plan ${untrusted(r.plan, 60)}` : ''})`, r), notes, data: slimCheckResult(r) };
|
|
1321
1370
|
},
|
|
@@ -1457,7 +1506,7 @@ const suggestImprovements = {
|
|
|
1457
1506
|
name: 'suggest_improvements',
|
|
1458
1507
|
title: 'Suggest conversion improvements that keep the checks passing',
|
|
1459
1508
|
description: [
|
|
1460
|
-
'OPTIONAL, after
|
|
1509
|
+
'OPTIONAL, after the checks pass: concrete ways to make the checkout, Stripe Checkout, lifecycle emails and cancellation flow convert or retain better WITHOUT breaking automatic-renewal laws. Every after-example passed Checkbox\'s own checks for the merchant\'s states.',
|
|
1461
1510
|
'Each suggestion gives apply.where + apply.content (the exact copy, Stripe JSON patch or flow change to make), apply.instructions, preconditions that must be true (never apply one whose precondition is false), and guardrails with catalog requirement ids (e.g. California / Colorado click-to-cancel next to any save offer, Minnesota ask-first). Never invent figures or add urgency, pre-checked boxes or obstacles.',
|
|
1462
1511
|
'With a site id (or one site in this mode): suggestions from what Checkbox observed. Or send the copy you are writing: terms + any of disclosure, page_html, stripe_checkout_session_params, emails, cancellation_flow (as in the check_* tools).',
|
|
1463
1512
|
'Surfaces whose compliance checks fail are held: fix them first (get_compliance_plan). Suggestions never block and never count toward compliance. After applying one, re-run the check named in verification.operation_id on the real copy.',
|
|
@@ -1812,10 +1861,23 @@ const getFinding = {
|
|
|
1812
1861
|
headline: `${finding.severity.toUpperCase()} ${finding.id} [${finding.compliance?.label ?? finding.status}] ${finding.check_id}: ${finding.title}`,
|
|
1813
1862
|
notes: [complianceBullet(finding.check_id, finding.compliance, finding.status, finding.title)].filter((x) => !!x),
|
|
1814
1863
|
data: { finding, fix },
|
|
1815
|
-
next:
|
|
1864
|
+
next: findingNext(finding, nextActionOf(fix)),
|
|
1816
1865
|
};
|
|
1817
1866
|
},
|
|
1818
1867
|
};
|
|
1868
|
+
/**
|
|
1869
|
+
* Round 32 (pilot 31, codex: get_finding on a not_applicable / resolved finding said "apply_fix … verify_site"). Next
|
|
1870
|
+
* follows the finding's status: nothing to fix for a finding that passes, does not apply or is resolved. Pure, exported
|
|
1871
|
+
* for tests.
|
|
1872
|
+
*/
|
|
1873
|
+
export function findingNext(finding, fromFix) {
|
|
1874
|
+
const settled = finding.status === 'pass' || finding.status === 'not_applicable' || finding.state === 'resolved' || finding.compliance?.status === 'compliant' || finding.compliance?.status === 'not_applicable' || finding.compliance?.status === 'verified_live';
|
|
1875
|
+
if (settled) {
|
|
1876
|
+
const why = finding.state === 'resolved' ? 'resolved' : finding.status === 'not_applicable' || finding.compliance?.status === 'not_applicable' ? 'not applicable to this site' : 'passing';
|
|
1877
|
+
return { type: 'continue', message: `Nothing to fix: this finding is ${why}. Continue with get_compliance_plan for what is left.`, operation_id: 'retrieveCompliancePlan' };
|
|
1878
|
+
}
|
|
1879
|
+
return fromFix ?? { type: 'apply_fix', message: 'Apply fix (or the matching task from get_compliance_plan), then call verify_site.', operation_id: 'verifySite' };
|
|
1880
|
+
}
|
|
1819
1881
|
const publishShopifyTerms = {
|
|
1820
1882
|
name: 'publish_shopify_terms',
|
|
1821
1883
|
title: 'Publish renewal terms to the Shopify app block (write)',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkbox-site/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
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.6",
|
|
81
81
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
82
82
|
"zod": "^4.6.7"
|
|
83
83
|
}
|