@checkbox-site/mcp 0.2.3 → 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 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.3";
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.3';
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' },
@@ -729,7 +731,7 @@ const getSetupStatus = {
729
731
  const pl = sitePlansNote(res.setup_session?.site_plans);
730
732
  if (pl)
731
733
  notes.push(pl);
732
- return { headline: `Re-checked setup session ${id}.`, ...(notes.length ? { notes } : {}), data: withoutLegacyScore(res), next: nextActionOf(res) ?? nextActionOf(res.session) };
734
+ return { headline: `Re-checked setup session ${id}.`, ...(notes.length ? { notes } : {}), data: withoutLegacyScore(res), next: nextActionOf(res) ?? nextActionOf(res.setup_session) ?? nextActionOf(res.session) };
733
735
  }
734
736
  const session = await api.request('GET', `/v1/setup_sessions/${enc(id)}`);
735
737
  const open = (session.steps ?? []).filter((s) => s.status && !['done', 'skipped', 'not_applicable'].includes(s.status));
@@ -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. Then work through get_compliance_plan.',
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, not just the checkout: in the billing platform\'s TEST mode Checkbox subscribes a test customer (card 4242…) whose email is a Checkbox inbox, waits for your acknowledgment email, fast-forwards a Stripe test clock through every legal notice window before trial end and renewal (collecting reminder emails), then cancels online and waits for the cancellation confirmation. Nothing is charged.',
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
- 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)', except: r.template.remaining_checks ?? [] } : undefined);
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 compliance: concrete ways to make the checkout page, Stripe Checkout, lifecycle emails and the cancellation flow convert or retain better WITHOUT breaking automatic-renewal laws. Deterministic library; every after-example was run through Checkbox\'s own checks for the merchant\'s states before it is offered.',
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: nextActionOf(fix) ?? { type: 'apply_fix', message: 'Apply fix (or the matching task from get_compliance_plan), then call verify_site.', operation_id: 'verifySite' },
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,18 +1,26 @@
1
1
  {
2
2
  "name": "@checkbox-site/mcp",
3
- "version": "0.2.3",
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
+ "checkbox",
7
+ "mcp",
8
+ "mcp-server",
9
+ "model-context-protocol",
10
+ "ai-agent",
11
+ "stripe",
12
+ "stripe-checkout",
6
13
  "subscription",
7
14
  "subscription-compliance",
8
- "subscription compliance",
9
15
  "auto-renewal",
10
- "auto renewal",
11
- "autorenewal",
12
16
  "automatic-renewal",
13
17
  "compliance",
14
18
  "rosca",
15
19
  "click-to-cancel",
20
+ "ci",
21
+ "subscription compliance",
22
+ "auto renewal",
23
+ "autorenewal",
16
24
  "california-arl",
17
25
  "auto-renewal-law",
18
26
  "auto-renewal-compliance",
@@ -21,14 +29,8 @@
21
29
  "stripe-subscriptions",
22
30
  "ftc",
23
31
  "negative-option",
24
- "stripe",
25
- "stripe-checkout",
26
32
  "checkout",
27
- "cancellation",
28
- "mcp",
29
- "model-context-protocol",
30
- "mcp-server",
31
- "ai-agent"
33
+ "cancellation"
32
34
  ],
33
35
  "mcpName": "site.checkbox/checkbox",
34
36
  "license": "MIT",
@@ -75,7 +77,7 @@
75
77
  "prepack": "npm run build"
76
78
  },
77
79
  "dependencies": {
78
- "@checkbox-site/node": "^0.2.3",
80
+ "@checkbox-site/node": "^0.2.5",
79
81
  "@modelcontextprotocol/sdk": "^1.32.2",
80
82
  "zod": "^4.6.7"
81
83
  }