@checkbox-site/mcp 0.2.4 → 0.2.5
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 +23 -0
- package/dist/tools.js +36 -10
- 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.5";
|
|
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.5';
|
|
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
|
@@ -153,6 +153,17 @@ export declare function summaryEmailLine(status: string | null | undefined, d: {
|
|
|
153
153
|
to?: string | null;
|
|
154
154
|
error?: string | null;
|
|
155
155
|
} | null | undefined): string;
|
|
156
|
+
export interface GoLiveLite {
|
|
157
|
+
summary: string;
|
|
158
|
+
steps: Array<{
|
|
159
|
+
key: string;
|
|
160
|
+
who: 'person' | 'agent';
|
|
161
|
+
text: string;
|
|
162
|
+
url?: string;
|
|
163
|
+
}>;
|
|
164
|
+
}
|
|
165
|
+
/** 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. */
|
|
166
|
+
export declare function goLiveNotes(g: GoLiveLite | null | undefined): string[];
|
|
156
167
|
export declare function planStatusWords(status: string, label?: string | null): string;
|
|
157
168
|
interface PlanLite {
|
|
158
169
|
status: string;
|
|
@@ -237,6 +248,18 @@ interface SiteShopifyLite {
|
|
|
237
248
|
}
|
|
238
249
|
/** The site's Shopify connection in one line, naming the tool (or the human step) that comes next. */
|
|
239
250
|
export declare function shopifyLines(sh: SiteShopifyLite | null | undefined, site: string): string[];
|
|
251
|
+
/**
|
|
252
|
+
* Round 32 (pilot 31, codex: get_finding on a not_applicable / resolved finding said "apply_fix … verify_site"). Next
|
|
253
|
+
* follows the finding's status: nothing to fix for a finding that passes, does not apply or is resolved. Pure, exported
|
|
254
|
+
* for tests.
|
|
255
|
+
*/
|
|
256
|
+
export declare function findingNext(finding: {
|
|
257
|
+
status: string;
|
|
258
|
+
state?: string;
|
|
259
|
+
compliance?: {
|
|
260
|
+
status?: string;
|
|
261
|
+
} | null;
|
|
262
|
+
}, fromFix: NextAction | null): NextAction;
|
|
240
263
|
/**
|
|
241
264
|
* Agent audit round 2: list_law_updates with limit 3 printed 62 KB (one catalog update carried 195 requirement changes,
|
|
242
265
|
* each with its full summary). Each update keeps its first changes in short form, with the total.
|
package/dist/tools.js
CHANGED
|
@@ -646,6 +646,8 @@ const setupCheckbox = {
|
|
|
646
646
|
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
647
|
notes: [
|
|
648
648
|
'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.',
|
|
649
|
+
// Round 32 (pilot 31 item 13): the Stripe key box and the restricted key in the same message (one approval).
|
|
650
|
+
`${args.billing_platform === 'stripe' ? 'In the same message' : 'If the site sells through Stripe, in the same message'}, ask them to tick “Let your agent store a Stripe test-mode key” 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
651
|
`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
652
|
],
|
|
651
653
|
next: { type: 'wait_for_approval', message: `Call setup_checkbox with device_code "${auth.device_code}" to wait for the approval.`, operation_id: 'createDeviceToken' },
|
|
@@ -738,7 +740,7 @@ const getSetupStatus = {
|
|
|
738
740
|
return {
|
|
739
741
|
headline: `Setup session ${session.id}: ${session.status}; ${open.length} checklist step(s) open.`,
|
|
740
742
|
// 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] : [])],
|
|
743
|
+
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
744
|
data: withoutLegacyScore(session),
|
|
743
745
|
};
|
|
744
746
|
},
|
|
@@ -792,19 +794,27 @@ export function summaryEmailLine(status, d) {
|
|
|
792
794
|
return 'not sent: this deployment has no email delivery';
|
|
793
795
|
return 'not sent';
|
|
794
796
|
}
|
|
797
|
+
/** 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. */
|
|
798
|
+
export function goLiveNotes(g) {
|
|
799
|
+
if (!g?.steps?.length)
|
|
800
|
+
return [];
|
|
801
|
+
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}` : ''}`)];
|
|
802
|
+
}
|
|
795
803
|
const completeSetup = {
|
|
796
804
|
name: 'complete_setup',
|
|
797
805
|
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.
|
|
806
|
+
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
807
|
input: { setup_session: z.string().max(100).optional().describe('Setup session id (setup_…). Default: the newest one.') },
|
|
800
808
|
scopes: ['setup_sessions:write'],
|
|
801
809
|
annotations: { ...write(true), title: 'Complete setup' },
|
|
802
810
|
async run(args, { api }) {
|
|
803
811
|
const id = await resolveSetupSession(api, args.setup_session);
|
|
804
812
|
const session = await api.request('POST', `/v1/setup_sessions/${enc(id)}/complete`, { body: {} });
|
|
813
|
+
// Round 32 (pilot 31 item 1): a test-mode setup is "set up in test mode; N steps before real customers", never "done".
|
|
814
|
+
const live = goLiveNotes(session.go_live);
|
|
805
815
|
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}`] : [],
|
|
816
|
+
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)}.`,
|
|
817
|
+
notes: [...live, ...(session.review_url ? [`Human review page: ${session.review_url}`] : [])],
|
|
808
818
|
data: withoutLegacyScore(session),
|
|
809
819
|
};
|
|
810
820
|
},
|
|
@@ -912,6 +922,7 @@ const getCompliancePlan = {
|
|
|
912
922
|
...(optional.length ? [`Optional suggestions (${plan.protection_level ?? 'balanced'} protection; never block the checks from passing): ${optional.map((t) => t.title).join('; ')}`] : []),
|
|
913
923
|
...(plan.optional_improvements ? ['Optional (never blocking, not part of compliance): conversion suggestions for this site with `suggest_improvements`.'] : []),
|
|
914
924
|
'Each task below is a summary; call get_task with its id for the full change (diff, copy or params) before applying it.',
|
|
925
|
+
...goLiveNotes(plan.go_live),
|
|
915
926
|
...(plan.status === 'compliant' ? await reviewHint(api, site, 'plan_compliant') : []),
|
|
916
927
|
],
|
|
917
928
|
// Normal-round pilot (sveltekit): the full plan (53K characters) was cut mid-JSON by the client. The plan here is a
|
|
@@ -941,7 +952,7 @@ export function precheckLines(pre, max = 12) {
|
|
|
941
952
|
const checks = pre?.checks ?? [];
|
|
942
953
|
if (!checks.length)
|
|
943
954
|
return [];
|
|
944
|
-
const order = { fail: 0, needs_review: 1, missing_data: 1, pass: 2, not_applicable: 3 };
|
|
955
|
+
const order = { fail: 0, needs_review: 1, missing_data: 1, pass: 2, informational: 2, not_applicable: 3 };
|
|
945
956
|
const sorted = [...checks].sort((a, b) => (order[a.status] ?? 1) - (order[b.status] ?? 1));
|
|
946
957
|
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
958
|
return [`Pre-check (${pre?.verdict ?? 'n/a'}), per check:`, ...lines, ...(checks.length > max ? [`and ${checks.length - max} more (data.precheck.checks)`] : [])];
|
|
@@ -955,7 +966,7 @@ const verifyTask = {
|
|
|
955
966
|
'- page tasks: html = the rendered page HTML (before or after deploy; ≤ ~900 KB). Without it Checkbox fetches the live page.',
|
|
956
967
|
'- Stripe tasks: stripe_checkout_session = exactly the object your code now passes to stripe.checkout.sessions.create.',
|
|
957
968
|
'- 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). A verification with checks still needs_review is reported as failed with next_action type needs_review: it is not done.',
|
|
969
|
+
'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). A verification with checks still needs_review is reported as failed with next_action type needs_review: it is not done. "informational" checks are notes on Stripe\'s own layout: nothing to change.',
|
|
959
970
|
].join('\n'),
|
|
960
971
|
input: {
|
|
961
972
|
task: z.string().max(100).describe('Task id (task_…).'),
|
|
@@ -1115,7 +1126,7 @@ const startTestRun = {
|
|
|
1115
1126
|
name: 'start_test_run',
|
|
1116
1127
|
title: 'Run a synthetic test subscription (test mode)',
|
|
1117
1128
|
description: [
|
|
1118
|
-
'Observes the WHOLE subscription lifecycle
|
|
1129
|
+
'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
1130
|
'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
1131
|
'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
1132
|
'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 +1326,9 @@ const checkEmail = {
|
|
|
1315
1326
|
annotations: { ...ro, title: 'Check email' },
|
|
1316
1327
|
async run(args, { api }) {
|
|
1317
1328
|
const r = await api.request('POST', '/v1/checks/email', { body: { ...args, kind: checkKind(args.kind) } });
|
|
1318
|
-
|
|
1329
|
+
// Round 32 (pilot 31): a template with values left to fill ({{support_contact}}, {{cancel_url}}) passes once they are filled.
|
|
1330
|
+
const toFill = r.template?.placeholders ?? [];
|
|
1331
|
+
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
1332
|
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
1333
|
return { headline: verdictHeadline(`Email (${String(args.kind)}${r.plan ? `, plan ${untrusted(r.plan, 60)}` : ''})`, r), notes, data: slimCheckResult(r) };
|
|
1321
1334
|
},
|
|
@@ -1457,7 +1470,7 @@ const suggestImprovements = {
|
|
|
1457
1470
|
name: 'suggest_improvements',
|
|
1458
1471
|
title: 'Suggest conversion improvements that keep the checks passing',
|
|
1459
1472
|
description: [
|
|
1460
|
-
'OPTIONAL, after
|
|
1473
|
+
'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
1474
|
'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
1475
|
'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
1476
|
'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 +1825,23 @@ const getFinding = {
|
|
|
1812
1825
|
headline: `${finding.severity.toUpperCase()} ${finding.id} [${finding.compliance?.label ?? finding.status}] ${finding.check_id}: ${finding.title}`,
|
|
1813
1826
|
notes: [complianceBullet(finding.check_id, finding.compliance, finding.status, finding.title)].filter((x) => !!x),
|
|
1814
1827
|
data: { finding, fix },
|
|
1815
|
-
next:
|
|
1828
|
+
next: findingNext(finding, nextActionOf(fix)),
|
|
1816
1829
|
};
|
|
1817
1830
|
},
|
|
1818
1831
|
};
|
|
1832
|
+
/**
|
|
1833
|
+
* Round 32 (pilot 31, codex: get_finding on a not_applicable / resolved finding said "apply_fix … verify_site"). Next
|
|
1834
|
+
* follows the finding's status: nothing to fix for a finding that passes, does not apply or is resolved. Pure, exported
|
|
1835
|
+
* for tests.
|
|
1836
|
+
*/
|
|
1837
|
+
export function findingNext(finding, fromFix) {
|
|
1838
|
+
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';
|
|
1839
|
+
if (settled) {
|
|
1840
|
+
const why = finding.state === 'resolved' ? 'resolved' : finding.status === 'not_applicable' || finding.compliance?.status === 'not_applicable' ? 'not applicable to this site' : 'passing';
|
|
1841
|
+
return { type: 'continue', message: `Nothing to fix: this finding is ${why}. Continue with get_compliance_plan for what is left.`, operation_id: 'retrieveCompliancePlan' };
|
|
1842
|
+
}
|
|
1843
|
+
return fromFix ?? { type: 'apply_fix', message: 'Apply fix (or the matching task from get_compliance_plan), then call verify_site.', operation_id: 'verifySite' };
|
|
1844
|
+
}
|
|
1819
1845
|
const publishShopifyTerms = {
|
|
1820
1846
|
name: 'publish_shopify_terms',
|
|
1821
1847
|
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.5",
|
|
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.5",
|
|
81
81
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
82
82
|
"zod": "^4.6.7"
|
|
83
83
|
}
|