@checkbox-site/mcp 0.2.0 → 0.2.1
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 +3 -0
- package/dist/format.js +14 -4
- package/dist/key-file.d.ts +21 -0
- package/dist/key-file.js +118 -0
- package/dist/resources.js +5 -3
- package/dist/server.d.ts +4 -2
- package/dist/server.js +2 -1
- package/dist/stdio.d.ts +2 -0
- package/dist/stdio.js +11 -1
- package/dist/tools.d.ts +13 -0
- package/dist/tools.js +75 -16
- package/package.json +10 -2
package/dist/format.d.ts
CHANGED
|
@@ -57,6 +57,9 @@ export declare function fitJson(data: unknown, max?: number): {
|
|
|
57
57
|
json: string;
|
|
58
58
|
page?: string;
|
|
59
59
|
};
|
|
60
|
+
/** The "Next:" line of a result whose API object names no next step. */
|
|
61
|
+
export declare const FALLBACK_NEXT = "Next: nothing more is needed for this result; get_compliance_plan names the site's next step.";
|
|
62
|
+
export declare const FALLBACK_NEXT_ERROR = "Next: fix what the error names (see the hint above, if any) and call the tool again.";
|
|
60
63
|
export declare function renderOutput(out: ToolOutput): CallToolResult;
|
|
61
64
|
/** An API error as a tool result: stable code, message, param and the API's own next_action. */
|
|
62
65
|
export declare function renderError(err: unknown): CallToolResult;
|
package/dist/format.js
CHANGED
|
@@ -15,6 +15,7 @@ export const OPERATION_TOOLS = {
|
|
|
15
15
|
answerTask: 'answer_task',
|
|
16
16
|
verifySite: 'verify_site',
|
|
17
17
|
retrieveVerification: 'get_verification',
|
|
18
|
+
createTestCredential: 'store_stripe_test_key',
|
|
18
19
|
createTestRun: 'start_test_run',
|
|
19
20
|
retrieveTestRun: 'get_test_run',
|
|
20
21
|
listTestRuns: 'get_test_run',
|
|
@@ -128,7 +129,10 @@ const OP_RE = new RegExp(`\\b(${[...Object.keys(OPERATION_TOOLS), ...Object.keys
|
|
|
128
129
|
* the CLI is unpublished.
|
|
129
130
|
*/
|
|
130
131
|
export function mcpText(text) {
|
|
131
|
-
|
|
132
|
+
// Round 23 (pilot 22 item 9): the REST body `{ type: "stripe_test", secret_key }` is said as the MCP tool's own
|
|
133
|
+
// arguments (store_stripe_test_key takes { secret_key }; `type` is accepted but not needed).
|
|
134
|
+
let s = text.replace(/\{ ?type: ?"stripe_test", ?secret_key\b/g, '{ secret_key');
|
|
135
|
+
s = s.replace(/(?:POST )?\/v1\/checks\/(disclosure|stripe_checkout_session|stripe_payment_link|email|page|notice_schedule)\b/g, (_m, k) => CHECK_PATH_TOOLS[k]);
|
|
132
136
|
s = s.replace(OP_RE, (op) => {
|
|
133
137
|
// Round 16 (pilot 15, sveltekit): "verifySetupSession" became plain get_setup_status, which only reads the checklist,
|
|
134
138
|
// so the agent looped on "Snippet installed: pending". The re-check is get_setup_status with verify=true.
|
|
@@ -191,6 +195,9 @@ export function describeNextAction(next, ctx = {}) {
|
|
|
191
195
|
const text = mcpText((next.message ?? next.description ?? '').trim());
|
|
192
196
|
if (next.type === 'done' || next.type === 'none')
|
|
193
197
|
return `Next: nothing left to do${text ? ` — ${text}` : '.'}`;
|
|
198
|
+
// Round 23 (pilot 22, headless-agent-pr): everything left waits for a deploy or the owner.
|
|
199
|
+
if (next.type === 'done_for_now')
|
|
200
|
+
return `Next: done for this change — ${text || 'the rest waits for a deploy or the owner.'}`;
|
|
194
201
|
const op = next.operation_id ?? undefined;
|
|
195
202
|
// Prefer the API's own mapping (next_action.mcp_tool); `call_api` means "no dedicated tool".
|
|
196
203
|
const named = next.mcp_tool && next.mcp_tool !== 'call_api' ? next.mcp_tool : undefined;
|
|
@@ -330,14 +337,17 @@ export function fitJson(data, max = MAX_JSON_CHARS) {
|
|
|
330
337
|
page: `Output too large (${compact.length} characters): narrow the request (a smaller limit, or one item at a time).`,
|
|
331
338
|
};
|
|
332
339
|
}
|
|
340
|
+
/** The "Next:" line of a result whose API object names no next step. */
|
|
341
|
+
export const FALLBACK_NEXT = 'Next: nothing more is needed for this result; get_compliance_plan names the site\'s next step.';
|
|
342
|
+
export const FALLBACK_NEXT_ERROR = 'Next: fix what the error names (see the hint above, if any) and call the tool again.';
|
|
333
343
|
export function renderOutput(out) {
|
|
334
344
|
const lines = [mcpText(out.headline.trim())];
|
|
335
345
|
for (const n of out.notes ?? [])
|
|
336
346
|
if (n)
|
|
337
347
|
lines.push(`- ${mcpText(n)}`);
|
|
338
348
|
const next = describeNextAction(out.next === undefined ? nextActionOf(out.data) : out.next, { data: out.data });
|
|
339
|
-
|
|
340
|
-
|
|
349
|
+
// Round 23 (pilot 22 item 27, agent-discovery: a result with no "Next:" line): every result names what comes next.
|
|
350
|
+
lines.push(next ?? (out.isError ? FALLBACK_NEXT_ERROR : FALLBACK_NEXT));
|
|
341
351
|
if (out.data !== undefined) {
|
|
342
352
|
const fitted = fitJson(forMcp(out.data));
|
|
343
353
|
if (fitted.page)
|
|
@@ -397,7 +407,7 @@ function missingNext(message) {
|
|
|
397
407
|
return { type: 'find_id', message: 'Call get_compliance_plan for the current task ids (each task carries its last_verification id).', operation_id: 'retrieveCompliancePlan' };
|
|
398
408
|
}
|
|
399
409
|
const ERROR_HINTS = {
|
|
400
|
-
insufficient_scope: 'This connection lacks that permission (a read-only connection or a restricted key). Ask the human to reconnect Checkbox
|
|
410
|
+
insufficient_scope: 'This connection lacks that permission (a read-only connection or a restricted key). Ask the human to reconnect Checkbox and choose Full access on the approval page: Codex: `codex mcp logout checkbox`, then `codex mcp login checkbox`; Claude Code: `/mcp` → checkbox → Clear authentication → Authenticate; claude.ai / Claude Desktop: Settings → Connectors → Checkbox → Disconnect, then Connect. For an API key, change its permissions in the Checkbox dashboard → Agent & API. Never ask them to paste a key. Read-only tools still work.',
|
|
401
411
|
insufficient_role: 'The key acts as a viewer; writes need a member or admin key.',
|
|
402
412
|
api_key_missing: 'No Checkbox API key is configured: call setup_checkbox (one browser approval by the human) to get one.',
|
|
403
413
|
api_key_invalid: 'The configured Checkbox API key is unknown, revoked or expired: reconnect with setup_checkbox (one browser approval by the human), or ask the human to fix the key in the Checkbox dashboard → Developers. Never ask them to paste a key into the chat.',
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface KeysToSave {
|
|
2
|
+
secret_key: string;
|
|
3
|
+
publishable_key?: string | null;
|
|
4
|
+
live_secret_key?: string | null;
|
|
5
|
+
live_publishable_key?: string | null;
|
|
6
|
+
}
|
|
7
|
+
export type SavedKeys = {
|
|
8
|
+
ok: true;
|
|
9
|
+
file: string;
|
|
10
|
+
gitignored: boolean;
|
|
11
|
+
note?: string;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
error: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const ENV_FILE = ".env.local";
|
|
17
|
+
export declare const FALLBACK_FILE = ".checkbox/keys.env";
|
|
18
|
+
/** Writes the issued keys to the project's git-ignored env file. Never throws. */
|
|
19
|
+
export declare function saveKeysToProject(root: string, keys: KeysToSave): SavedKeys;
|
|
20
|
+
/** The CHECKBOX_SECRET_KEY saved by an earlier device login in this folder (.env.local, then .checkbox/keys.env). */
|
|
21
|
+
export declare function readSavedKey(root: string): string | undefined;
|
package/dist/key-file.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Round 25 (pilot 24 item 12, security-reviewer): the stdio device login returned the full ck_test_ secret key in the
|
|
2
|
+
// tool result, so it landed in the LLM transcript (and in whatever logs the client keeps). The local stdio server now
|
|
3
|
+
// writes the issued keys to a git-ignored file in the project itself and returns only a hint ("ck_test_…ab12" and the
|
|
4
|
+
// file name). The next start of `checkbox-mcp` in the same folder reads the key back from that file.
|
|
5
|
+
//
|
|
6
|
+
// Where: .env.local in the project folder (what `checkbox init` writes, and what most frameworks load), unless git
|
|
7
|
+
// tracks .env.local (a secret written there would go into the next commit even with .gitignore): then
|
|
8
|
+
// .checkbox/keys.env, in a folder whose own .gitignore ignores everything.
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
export const ENV_FILE = '.env.local';
|
|
13
|
+
export const FALLBACK_FILE = '.checkbox/keys.env';
|
|
14
|
+
function read(path) {
|
|
15
|
+
try {
|
|
16
|
+
return readFileSync(path, 'utf8');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function gitTracked(root, file) {
|
|
23
|
+
try {
|
|
24
|
+
execFileSync('git', ['ls-files', '--error-unmatch', '--', file], { cwd: root, stdio: 'ignore', timeout: 5000 });
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function gitIgnores(root, file) {
|
|
32
|
+
try {
|
|
33
|
+
execFileSync('git', ['check-ignore', '-q', '--', file], { cwd: root, stdio: 'ignore', timeout: 5000 });
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
// Exit 1: not ignored. Anything else (no git, not a repository): unknown.
|
|
38
|
+
return err.status === 1 ? false : null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function upsertLines(existing, entries, header) {
|
|
42
|
+
let text = existing ?? '';
|
|
43
|
+
const missing = [];
|
|
44
|
+
for (const [k, v] of entries) {
|
|
45
|
+
const re = new RegExp(`^${k}=.*$`, 'm');
|
|
46
|
+
if (re.test(text))
|
|
47
|
+
text = text.replace(re, `${k}=${v}`);
|
|
48
|
+
else
|
|
49
|
+
missing.push([k, v]);
|
|
50
|
+
}
|
|
51
|
+
if (missing.length) {
|
|
52
|
+
const block = [header, ...missing.map(([k, v]) => `${k}=${v}`)];
|
|
53
|
+
text = `${text}${text && !text.endsWith('\n') ? '\n' : ''}${text ? '\n' : ''}${block.join('\n')}\n`;
|
|
54
|
+
}
|
|
55
|
+
return text;
|
|
56
|
+
}
|
|
57
|
+
/** Makes sure .gitignore covers .env.local (only in a git project, or when a .gitignore already exists). */
|
|
58
|
+
function ensureIgnored(root, file) {
|
|
59
|
+
const ignored = gitIgnores(root, file);
|
|
60
|
+
if (ignored === true)
|
|
61
|
+
return true;
|
|
62
|
+
const path = join(root, '.gitignore');
|
|
63
|
+
const existing = read(path);
|
|
64
|
+
if (existing !== null) {
|
|
65
|
+
const lines = existing.split(/\r?\n/).map((l) => l.trim());
|
|
66
|
+
if (lines.some((l) => l === file || l === `/${file}` || l === '.env*' || l === '.env*.local' || l === '*.local' || l === '.env.local*'))
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
if (existing === null && !existsSync(join(root, '.git')))
|
|
70
|
+
return false;
|
|
71
|
+
writeFileSync(path, `${existing ?? ''}${existing && !existing.endsWith('\n') ? '\n' : ''}# Checkbox keys (checkbox-mcp)\n${file}\n`);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
/** Writes the issued keys to the project's git-ignored env file. Never throws. */
|
|
75
|
+
export function saveKeysToProject(root, keys) {
|
|
76
|
+
const entries = [['CHECKBOX_SECRET_KEY', keys.secret_key]];
|
|
77
|
+
if (keys.publishable_key)
|
|
78
|
+
entries.push(['CHECKBOX_PUBLISHABLE_KEY', keys.publishable_key]);
|
|
79
|
+
if (keys.live_secret_key)
|
|
80
|
+
entries.push(['CHECKBOX_LIVE_SECRET_KEY', keys.live_secret_key]);
|
|
81
|
+
if (keys.live_publishable_key)
|
|
82
|
+
entries.push(['CHECKBOX_LIVE_PUBLISHABLE_KEY', keys.live_publishable_key]);
|
|
83
|
+
const header = '# Checkbox (subscription compliance), written by checkbox-mcp. Secret: never commit this file.';
|
|
84
|
+
try {
|
|
85
|
+
if (!gitTracked(root, ENV_FILE)) {
|
|
86
|
+
const path = join(root, ENV_FILE);
|
|
87
|
+
writeFileSync(path, upsertLines(read(path), entries, header), { mode: 0o600 });
|
|
88
|
+
try {
|
|
89
|
+
chmodSync(path, 0o600);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// best effort (some file systems ignore modes)
|
|
93
|
+
}
|
|
94
|
+
const gitignored = ensureIgnored(root, ENV_FILE);
|
|
95
|
+
return { ok: true, file: ENV_FILE, gitignored };
|
|
96
|
+
}
|
|
97
|
+
const dir = join(root, '.checkbox');
|
|
98
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
99
|
+
if (read(join(dir, '.gitignore')) === null)
|
|
100
|
+
writeFileSync(join(dir, '.gitignore'), '# Checkbox local secrets: never committed\n*\n');
|
|
101
|
+
const path = join(root, FALLBACK_FILE);
|
|
102
|
+
writeFileSync(path, upsertLines(read(path), entries, header), { mode: 0o600 });
|
|
103
|
+
return { ok: true, file: FALLBACK_FILE, gitignored: true, note: `${ENV_FILE} is tracked by git, so the key was not written there.` };
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** The CHECKBOX_SECRET_KEY saved by an earlier device login in this folder (.env.local, then .checkbox/keys.env). */
|
|
110
|
+
export function readSavedKey(root) {
|
|
111
|
+
for (const file of [ENV_FILE, FALLBACK_FILE]) {
|
|
112
|
+
const text = read(join(root, file));
|
|
113
|
+
const m = text ? /^CHECKBOX_SECRET_KEY=["']?([A-Za-z0-9_]+)["']?\s*$/m.exec(text) : null;
|
|
114
|
+
if (m?.[1])
|
|
115
|
+
return m[1];
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
package/dist/resources.js
CHANGED
|
@@ -48,10 +48,11 @@ what to change. Free. Every feature. No credit card. Fair-use limits.
|
|
|
48
48
|
1. **setup_checkbox** — connected through the Checkbox connector (OAuth: claude.ai, Claude Desktop, Claude Code, Cursor)
|
|
49
49
|
the human already approved: it registers the site and runs the first checks at once, no key involved. Without any
|
|
50
50
|
credential (device login) it returns ONE link for the human ("Open … and click Approve"); call it again with the
|
|
51
|
-
device_code
|
|
51
|
+
device_code (the local stdio server saves the key to the project's git-ignored .env.local itself and shows only a
|
|
52
|
+
hint; prefer OAuth wherever the client supports it). With an API key it starts a setup session. Pass what you know:
|
|
52
53
|
site_url, checkout_url, terms_url / refund_url / cancellation_url, billing_platform, plans, project.layout_file.
|
|
53
54
|
(The \`checkbox init\` CLI, \`npx @checkbox-site/cli init\`, does the same and patches the layout file.)
|
|
54
|
-
2. Follow the setup **next_action** (install the snippet in the root layout, store
|
|
55
|
+
2. Follow the setup **next_action** (install the snippet in the root layout, store the one Stripe TEST restricted key yourself with store_stripe_test_key,
|
|
55
56
|
register checkout params). After each change: **update_setup**, then **get_setup_status** with verify=true.
|
|
56
57
|
3. **get_compliance_plan** — ordered tasks: requirement + citation, where (file / selector / Stripe object), the exact
|
|
57
58
|
change (diff template, copy, or Stripe params) and how to verify.
|
|
@@ -90,7 +91,8 @@ Every tool result ends with "Next: …" naming the tool to call. Errors carry a
|
|
|
90
91
|
\`Authorization: Bearer <key>\`. Agents without a browser: \`${opts.mcpUrl}?login=device\` offers setup_checkbox's
|
|
91
92
|
device login.
|
|
92
93
|
- Local (stdio): the \`checkbox-mcp\` binary (packages/mcp) with CHECKBOX_API_KEY set; without a key it starts with
|
|
93
|
-
setup_checkbox only and switches to the issued key after the human approves
|
|
94
|
+
setup_checkbox only and switches to the issued key after the human approves (it saves the key to the project's
|
|
95
|
+
git-ignored .env.local itself; the key never appears in the conversation).
|
|
94
96
|
- REST: ${opts.apiBase}/v1 — OpenAPI at ${opts.apiBase}/v1/openapi.json.
|
|
95
97
|
|
|
96
98
|
Automated compliance checks, not legal advice.
|
package/dist/server.d.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type { CheckboxApi } from './api.ts';
|
|
4
4
|
import type { KeyContext } from './permissions.ts';
|
|
5
|
-
import { type IssuedKeys, type ToolDef } from './tools.ts';
|
|
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.1";
|
|
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";
|
|
@@ -22,6 +22,8 @@ export interface CheckboxMcpOptions {
|
|
|
22
22
|
api: CheckboxApi;
|
|
23
23
|
key: KeyContext;
|
|
24
24
|
}>;
|
|
25
|
+
/** stdio: writes device-login keys to a git-ignored file in the project, so the tool result carries only a hint. */
|
|
26
|
+
saveKeys?: ToolContext['saveKeys'];
|
|
25
27
|
/** Called for every tool call (metrics / logs). Never receives arguments (they may hold page HTML or keys). */
|
|
26
28
|
onToolCall?: (event: {
|
|
27
29
|
tool: string;
|
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.1';
|
|
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';
|
|
@@ -114,6 +114,7 @@ export function createCheckboxMcpServer(opts) {
|
|
|
114
114
|
serverVersion: SERVER_VERSION,
|
|
115
115
|
...(opts.devicePoll ? { devicePoll: opts.devicePoll } : {}),
|
|
116
116
|
...(opts.apiForKey ? { apiForKey: opts.apiForKey } : {}),
|
|
117
|
+
...(opts.saveKeys ? { saveKeys: opts.saveKeys } : {}),
|
|
117
118
|
...(client?.name ? { clientName: client.name } : {}),
|
|
118
119
|
...(opts.adoptKeys
|
|
119
120
|
? {
|
package/dist/stdio.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface StdioOptions {
|
|
|
6
6
|
fetch?: typeof fetch;
|
|
7
7
|
/** Diagnostics go to stderr (stdout is the protocol). */
|
|
8
8
|
log?: (line: string) => void;
|
|
9
|
+
/** The project folder the device login saves its keys in (default: the process's working folder). */
|
|
10
|
+
cwd?: string;
|
|
9
11
|
}
|
|
10
12
|
export interface ResolvedConfig {
|
|
11
13
|
apiKey?: string;
|
package/dist/stdio.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// Local stdio MCP server (`checkbox-mcp`) for clients without remote MCP support, or for teams that prefer a local
|
|
2
|
-
// process. Reads CHECKBOX_API_KEY (or CHECKBOX_SECRET_KEY,
|
|
2
|
+
// process. Reads CHECKBOX_API_KEY (or CHECKBOX_SECRET_KEY, from the environment or the project's .env.local, which
|
|
3
|
+
// `checkbox init` and this server's device login write) and calls
|
|
3
4
|
// the API over HTTPS — through @checkbox-site/node when it provides a client, else with the built-in fetch client.
|
|
4
5
|
// Without a key it starts with setup_checkbox only and switches to the issued key once the human approves.
|
|
5
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
7
|
import { CheckboxApiError, createHttpApi, DEFAULT_API_BASE, fromSdkClient } from "./api.js";
|
|
7
8
|
import { keyContextFromPrefix } from "./permissions.js";
|
|
8
9
|
import { createCheckboxMcpServer, DEFAULT_MCP_URL, SERVER_VERSION } from "./server.js";
|
|
10
|
+
import { readSavedKey, saveKeysToProject } from "./key-file.js";
|
|
9
11
|
export function resolveConfig(env, argv) {
|
|
10
12
|
const flag = (name) => {
|
|
11
13
|
const i = argv.indexOf(name);
|
|
@@ -75,6 +77,13 @@ export async function runStdio(opts = {}) {
|
|
|
75
77
|
return;
|
|
76
78
|
}
|
|
77
79
|
const cfg = resolveConfig(env, argv);
|
|
80
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
81
|
+
// Round 25 (pilot 24 item 12): a key an earlier device login saved in this folder (never shown in the transcript).
|
|
82
|
+
if (!cfg.apiKey) {
|
|
83
|
+
const saved = readSavedKey(cwd);
|
|
84
|
+
if (saved)
|
|
85
|
+
cfg.apiKey = saved;
|
|
86
|
+
}
|
|
78
87
|
const api = await buildApi(cfg, opts.fetch);
|
|
79
88
|
let key;
|
|
80
89
|
try {
|
|
@@ -93,6 +102,7 @@ export async function runStdio(opts = {}) {
|
|
|
93
102
|
const nextApi = await buildApi({ apiKey: keys.secret_key, apiBase: cfg.apiBase }, opts.fetch);
|
|
94
103
|
return { api: nextApi, key: await describeKey(nextApi, keys.secret_key, cfg.readOnly) };
|
|
95
104
|
},
|
|
105
|
+
saveKeys: async (keys) => saveKeysToProject(cwd, keys),
|
|
96
106
|
});
|
|
97
107
|
await mcp.server.connect(new StdioServerTransport());
|
|
98
108
|
log(`checkbox-mcp ${SERVER_VERSION}: ${key.authenticated ? `${key.mode} mode, ${mcp.visibleTools().length} tools` : 'no API key — setup_checkbox only'} (${cfg.apiBase})`);
|
package/dist/tools.d.ts
CHANGED
|
@@ -28,6 +28,19 @@ export interface ToolContext {
|
|
|
28
28
|
};
|
|
29
29
|
/** An API client for a key issued during this call (remote: the rest of setup_checkbox runs with it). */
|
|
30
30
|
apiForKey?: (secretKey: string) => CheckboxApi;
|
|
31
|
+
/**
|
|
32
|
+
* stdio (round 25, pilot 24 item 12): writes the issued keys to a git-ignored file in the project, so the result
|
|
33
|
+
* carries only a hint and the secret key never enters the LLM transcript.
|
|
34
|
+
*/
|
|
35
|
+
saveKeys?: (keys: IssuedKeys) => Promise<{
|
|
36
|
+
ok: true;
|
|
37
|
+
file: string;
|
|
38
|
+
gitignored: boolean;
|
|
39
|
+
note?: string;
|
|
40
|
+
} | {
|
|
41
|
+
ok: false;
|
|
42
|
+
error: string;
|
|
43
|
+
}>;
|
|
31
44
|
}
|
|
32
45
|
export interface ToolDef {
|
|
33
46
|
name: string;
|
package/dist/tools.js
CHANGED
|
@@ -105,12 +105,12 @@ const setupFactsShape = {
|
|
|
105
105
|
privacy_url: z.string().max(2048).nullable().optional().describe('Privacy policy URL.'),
|
|
106
106
|
refund_url: z.string().max(2048).nullable().optional().describe('Refund policy URL.'),
|
|
107
107
|
cancellation_url: z.string().max(2048).nullable().optional().describe('Cancellation policy URL, if separate from the Terms.'),
|
|
108
|
-
support_email: z.string().max(320).optional().describe('Support address subscribers
|
|
108
|
+
support_email: z.string().max(320).optional().describe('Support address for subscribers.'),
|
|
109
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 (also accepted as "brand"). Default: the host name, which the plan then asks you to replace.'),
|
|
110
110
|
billing_platform: z
|
|
111
|
-
.enum(['stripe', 'shopify', 'recharge', 'chargebee', 'recurly', 'paddle', 'woocommerce', 'custom'])
|
|
111
|
+
.enum(['stripe', 'shopify', 'recharge', 'chargebee', 'recurly', 'paddle', 'lemon_squeezy', 'woocommerce', 'custom'])
|
|
112
112
|
.optional()
|
|
113
|
-
.describe('Billing platform
|
|
113
|
+
.describe('Billing platform.'),
|
|
114
114
|
plans: z
|
|
115
115
|
.array(strictObject({
|
|
116
116
|
name: z.string().max(200).optional(),
|
|
@@ -406,7 +406,7 @@ export function sitePlansNote(plans) {
|
|
|
406
406
|
if (!Array.isArray(plans))
|
|
407
407
|
return null;
|
|
408
408
|
if (!plans.length)
|
|
409
|
-
return 'Plans: none known yet. Pass plans[] (name,
|
|
409
|
+
return 'Plans: none known yet. Pass plans[] ({ name, amount, currency, interval, trial_days, price_id }) with update_setup, or ask the human to connect Stripe.';
|
|
410
410
|
const lines = plans.slice(0, 6).map((p) => {
|
|
411
411
|
const every = (p.interval_count ?? 1) > 1 ? `every ${p.interval_count} ${p.interval}s` : `/${p.interval ?? '?'}`;
|
|
412
412
|
const trial = p.trial_days ? `, ${p.trial_days}-day trial` : ', no trial';
|
|
@@ -515,7 +515,7 @@ const setupCheckbox = {
|
|
|
515
515
|
'Connected through the Checkbox connector (OAuth: claude.ai, Claude Desktop, Claude Code /mcp, Cursor, VS Code): the human already approved in the browser; there is NO key to ask for, paste or save. Call it with site_url (and what else you know): it registers the site and runs the first checks in the same call.',
|
|
516
516
|
'Pass everything you already know from the codebase: site_url, checkout_url, terms/privacy/refund/cancellation URLs, billing_platform, plans, project.layout_file. Checkbox fetches and checks the Terms itself.',
|
|
517
517
|
'',
|
|
518
|
-
'Only for clients without OAuth (no API key configured on this server): it returns a link for the HUMAN to open and click Approve (free, no card). Show them the link in one line, then call setup_checkbox again with the returned device_code — it waits up to ~50 s; call again while it says pending. On approval
|
|
518
|
+
'Only for clients without OAuth (no API key configured on this server): it returns a link for the HUMAN to open and click Approve (free, no card). Show them the link in one line, then call setup_checkbox again with the returned device_code — it waits up to ~50 s; call again while it says pending. On approval the stdio server saves the key to the git-ignored .env.local, showing a hint; the remote one returns it once: save it to .env.local, never commit it or ask for a pasted key.',
|
|
519
519
|
'',
|
|
520
520
|
'After this: follow next_action (usually verify the domain and install the snippet, then get_setup_status with verify=true), then get_compliance_plan.',
|
|
521
521
|
].join('\n'),
|
|
@@ -542,16 +542,28 @@ const setupCheckbox = {
|
|
|
542
542
|
};
|
|
543
543
|
}
|
|
544
544
|
const notes = [];
|
|
545
|
+
const saved = ctx.saveKeys ? await ctx.saveKeys(res).catch((err) => ({ ok: false, error: err instanceof Error ? err.message : String(err) })) : null;
|
|
545
546
|
if (ctx.adoptKeys) {
|
|
546
547
|
await ctx.adoptKeys(res);
|
|
547
548
|
notes.push('This MCP server now uses the new test-mode key for the rest of the session (more tools are now available).');
|
|
548
|
-
|
|
549
|
+
if (saved?.ok) {
|
|
550
|
+
notes.push(`The secret key was saved to ${saved.file} in this project${saved.gitignored ? ' (git-ignored)' : ' (add it to .gitignore: this folder is not a git repository)'} and is not shown here: never print, copy or commit it. ${saved.note ?? ''}This server reads it from there on its next start.`.replace(/ +/g, ' '));
|
|
551
|
+
}
|
|
552
|
+
else if (saved) {
|
|
553
|
+
notes.push(`The key could not be saved in this project (${saved.error.slice(0, 160)}), and it is not shown here. This server uses it until it stops; for a lasting key, the human creates one in the Checkbox dashboard (Agent & API) and sets CHECKBOX_API_KEY in the MCP server config.`);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
notes.push('Save CHECKBOX_SECRET_KEY to .env.local (and set CHECKBOX_API_KEY in the MCP server config) so future sessions keep working.');
|
|
557
|
+
}
|
|
549
558
|
}
|
|
550
559
|
else {
|
|
551
560
|
notes.push('This remote MCP connection is stateless and keeps no key: save CHECKBOX_SECRET_KEY to .env.local and pass it on the connection (Authorization: Bearer) for the next calls:');
|
|
552
561
|
notes.push(`claude mcp add --transport http checkbox ${ctx.mcpUrl} --header "Authorization: Bearer $CHECKBOX_SECRET_KEY" (Cursor / VS Code: add the same header to the server config). Until then, the plan below is all you need; the HTTP API works with the key right away.`);
|
|
553
562
|
}
|
|
554
|
-
|
|
563
|
+
if (!saved)
|
|
564
|
+
notes.push('The secret key is shown only this once. Never commit it; the publishable key (pk_) is safe in browser code (e.g. NEXT_PUBLIC_CHECKBOX_PUBLISHABLE_KEY).');
|
|
565
|
+
else
|
|
566
|
+
notes.push('The publishable key (pk_) is safe in browser code (e.g. NEXT_PUBLIC_CHECKBOX_PUBLISHABLE_KEY).');
|
|
555
567
|
// Round 16 (pilot 15, sveltekit / change-guard): two public keys in one response, unexplained.
|
|
556
568
|
notes.push("Two public keys: the snippet tag's data-key is the site's snippet key (install.snippet_key; use the script_tag exactly as given); publishable_key is the account key for Checkbox Elements and consent receipts only.");
|
|
557
569
|
// In the same call, with the new key: apply the facts given (they are not lost between the two calls), run the
|
|
@@ -600,10 +612,11 @@ const setupCheckbox = {
|
|
|
600
612
|
notes,
|
|
601
613
|
...(next ? { next } : {}),
|
|
602
614
|
data: {
|
|
603
|
-
...res,
|
|
615
|
+
...(saved ? withoutSecrets(res) : res),
|
|
604
616
|
site,
|
|
605
617
|
...(next ? { next_action: next } : {}),
|
|
606
|
-
|
|
618
|
+
...(saved?.ok ? { key_file: saved.file } : {}),
|
|
619
|
+
env: { CHECKBOX_SECRET_KEY: saved ? `${hintOf(res.secret_key)} (${saved.ok ? `saved in ${saved.file}` : 'not shown'})` : res.secret_key, ...(res.publishable_key ? { CHECKBOX_PUBLISHABLE_KEY: res.publishable_key } : {}) },
|
|
607
620
|
public_keys: labelledPublicKeys(snippetKey ?? res.install?.snippet_key, res.publishable_key),
|
|
608
621
|
...(firstCheck ? { first_check: firstCheck } : {}),
|
|
609
622
|
...(inputWarnings.length ? { warnings: inputWarnings } : {}),
|
|
@@ -659,6 +672,17 @@ const setupCheckbox = {
|
|
|
659
672
|
};
|
|
660
673
|
},
|
|
661
674
|
};
|
|
675
|
+
/** "ck_test_…ab12": recognisable, never usable. */
|
|
676
|
+
function hintOf(key) {
|
|
677
|
+
if (!key)
|
|
678
|
+
return null;
|
|
679
|
+
const m = /^([a-z]{2}_(?:live|test)_)/.exec(key);
|
|
680
|
+
return `${m?.[1] ?? key.slice(0, 3)}…${key.slice(-4)}`;
|
|
681
|
+
}
|
|
682
|
+
/** The device token without its secret keys (hints only): what the stdio server returns once it saved them. */
|
|
683
|
+
function withoutSecrets(res) {
|
|
684
|
+
return { ...res, secret_key: hintOf(res.secret_key), ...(res.live_secret_key ? { live_secret_key: hintOf(res.live_secret_key) } : {}) };
|
|
685
|
+
}
|
|
662
686
|
/**
|
|
663
687
|
* Round 20 (pilot 19 item 16, sveltekit / change-guard: a pk_ site key and a pk_test_ key side by side, unlabelled): each
|
|
664
688
|
* public key says what it is for.
|
|
@@ -1040,7 +1064,7 @@ const getVerification = {
|
|
|
1040
1064
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
1041
1065
|
/** createTestRun body from the tool's arguments (top-level shortcuts merged into `stripe`). Exported for tests. */
|
|
1042
1066
|
export function startTestRunBody(args) {
|
|
1043
|
-
const body = pick(args, ['mode', 'type', 'advance_time', 'cancel', 'email_wait_seconds', 'region', 'stripe', 'generic']);
|
|
1067
|
+
const body = pick(args, ['mode', 'type', 'advance_time', 'cancel', 'email_wait_seconds', 'region', 'plan', 'stripe', 'generic']);
|
|
1044
1068
|
const shortcuts = pick(args, ['checkout_url', 'test_account', 'checkout_steps', 'cancel_url', 'cancel_steps']);
|
|
1045
1069
|
if (Object.keys(shortcuts).length && body.type !== 'generic') {
|
|
1046
1070
|
body.stripe = { ...shortcuts, ...(body.stripe ?? {}) };
|
|
@@ -1049,12 +1073,40 @@ export function startTestRunBody(args) {
|
|
|
1049
1073
|
}
|
|
1050
1074
|
return body;
|
|
1051
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Round 22 (founder decision 2): the agent stores the site's ONE Stripe TEST-mode restricted key itself
|
|
1078
|
+
* (createTestCredential). Live keys and sk_test_ secret keys are refused by the API with what to do instead.
|
|
1079
|
+
*/
|
|
1080
|
+
const storeStripeTestKey = {
|
|
1081
|
+
name: 'store_stripe_test_key',
|
|
1082
|
+
title: 'Store the Stripe TEST restricted key (rk_test_)',
|
|
1083
|
+
description: 'Stores the site\'s ONE Stripe key in TEST mode: a restricted key (rk_test_…) from the project\'s env. Checked with Stripe; on a test-mode site it also connects Stripe (checks, fixes after the human approves, test runs). The human sees it on the dashboard and can revoke it. Live and sk_test_ keys are refused (the error says what to do). Never ask the human to paste a key into the chat.',
|
|
1084
|
+
input: {
|
|
1085
|
+
site: siteId.optional(),
|
|
1086
|
+
secret_key: z.string().min(10).max(255).describe('rk_test_… key. Stored encrypted; never returned.'),
|
|
1087
|
+
confirm_account_mismatch: z.boolean().optional().describe('Human said yes'),
|
|
1088
|
+
type: z.enum(['stripe_test', 'stripe']).optional().describe('Optional: "stripe_test" (the REST body\'s type; "stripe" is accepted too). Always stored as a Stripe TEST key.'),
|
|
1089
|
+
},
|
|
1090
|
+
scopes: ['test_runs:write'],
|
|
1091
|
+
annotations: { ...write(true, true), title: 'Store Stripe test key' },
|
|
1092
|
+
async run(args, { api }) {
|
|
1093
|
+
const site = await resolveSite(api, args.site);
|
|
1094
|
+
const cred = await api.request('POST', `/v1/sites/${enc(site)}/test_credentials`, {
|
|
1095
|
+
body: { type: 'stripe_test', secret_key: String(args.secret_key ?? ''), ...(args.confirm_account_mismatch === true ? { confirm_account_mismatch: true } : {}) },
|
|
1096
|
+
});
|
|
1097
|
+
return {
|
|
1098
|
+
headline: `Stripe test key ${cred.key_hint} stored for ${site} (checked with Stripe). The human sees it on the dashboard and can revoke it.`,
|
|
1099
|
+
data: cred,
|
|
1100
|
+
next: cred.next_action ?? { type: 'continue_setup', message: 'Call get_setup_status for the next step (start_test_run once a checkout config is registered).', operation_id: 'retrieveSetupSession' },
|
|
1101
|
+
};
|
|
1102
|
+
},
|
|
1103
|
+
};
|
|
1052
1104
|
const startTestRun = {
|
|
1053
1105
|
name: 'start_test_run',
|
|
1054
1106
|
title: 'Run a synthetic test subscription (test mode)',
|
|
1055
1107
|
description: [
|
|
1056
1108
|
'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.',
|
|
1057
|
-
'Needs a Stripe TEST key stored for the site
|
|
1109
|
+
'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".',
|
|
1058
1110
|
'Usually 2-4 minutes: poll get_test_run (its next_action says when). Each step reports observations, check results and agent-readable fixes; apply them and run again.',
|
|
1059
1111
|
'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.',
|
|
1060
1112
|
].join('\n'),
|
|
@@ -1066,6 +1118,7 @@ const startTestRun = {
|
|
|
1066
1118
|
cancel: z.boolean().optional().describe('Cancel online at the end (default true).'),
|
|
1067
1119
|
email_wait_seconds: z.number().int().min(15).max(900).optional().describe('How long to wait for each email (default 120).'),
|
|
1068
1120
|
region: z.string().max(10).optional().describe('State the synthetic shopper visits from, e.g. "US-CA".'),
|
|
1121
|
+
plan: z.string().max(200).optional().describe('Plan to run: a registered checkout config name or a price_… its line_items use.'),
|
|
1069
1122
|
stripe: z
|
|
1070
1123
|
.object({
|
|
1071
1124
|
checkout_config: z.string().max(100).optional().describe('Registered checkout config (scc_…) to mirror; default the newest, else the latest live Checkout Session; "none" skips registered configs.'),
|
|
@@ -1153,9 +1206,11 @@ const checkDisclosure = {
|
|
|
1153
1206
|
input: {
|
|
1154
1207
|
text: z.string().min(1).max(5000).describe('The disclosure shown next to the purchase button, exactly as rendered.'),
|
|
1155
1208
|
...planTermsShape,
|
|
1156
|
-
interval: intervalStr,
|
|
1209
|
+
interval: intervalStr.optional().describe('Billing interval: day, week, month or year (optional with site).'),
|
|
1157
1210
|
button_text: z.string().max(200).optional().describe('Purchase button label, e.g. "Start free trial".'),
|
|
1158
1211
|
order_summary: z.string().max(1000).optional().describe('Other text next to the button (order summary), if any.'),
|
|
1212
|
+
site: z.string().max(100).optional().describe('site_… whose plans judge it (as verify_task)'),
|
|
1213
|
+
plan: z.string().max(200).optional().describe('With site: plan name or price_…'),
|
|
1159
1214
|
placement: strictObject({
|
|
1160
1215
|
above_button: z.boolean().optional(),
|
|
1161
1216
|
same_page_as_payment: z.boolean().optional(),
|
|
@@ -1642,7 +1697,8 @@ const getSiteStatus = {
|
|
|
1642
1697
|
const ro = unverifiedScanNote(site.unverified_scan);
|
|
1643
1698
|
const notes = [
|
|
1644
1699
|
...(todo.length ? [`To do (${todo.length}, from the compliance plan, in order):`, ...todo.map((t, i) => `${i + 1}. ${t.id} ${todoLine(t)}`)] : []),
|
|
1645
|
-
|
|
1700
|
+
// Round 25 (pilot 24 item 22: "4 live checks pending" listed 3): every pending live check the label counts.
|
|
1701
|
+
...(evaluated && label ? pendingLive.map((p) => `LIVE CHECK PENDING ${p.check_id}: ${p.what} — ${p.completes}`) : []),
|
|
1646
1702
|
...(ro ? [ro] : []),
|
|
1647
1703
|
...shopifyLines(site.shopify, site.id),
|
|
1648
1704
|
];
|
|
@@ -1970,7 +2026,8 @@ const getCompliantDisclosure = {
|
|
|
1970
2026
|
return {
|
|
1971
2027
|
headline: `Suggested disclosure generated (${r.replacement.verified ? 'passes the checks, verified by the rules engine' : 'not fully verified by the rules engine'}).`,
|
|
1972
2028
|
notes: [
|
|
1973
|
-
|
|
2029
|
+
// Round 23 (pilot 22 item 30): the short line first when the engine passes it; the full text as the fuller version.
|
|
2030
|
+
...(r.replacement.short_disclosure_text ? [`Disclosure: ${r.replacement.short_disclosure_text}`, `Fuller version (also passes): ${r.replacement.disclosure_text}`] : [`Disclosure: ${r.replacement.disclosure_text}`]),
|
|
1974
2031
|
`Consent label: ${r.replacement.consent_label}`,
|
|
1975
2032
|
...(r.replacement.placeholders?.length ? [`Fill these placeholders: ${r.replacement.placeholders.join(', ')}.`] : []),
|
|
1976
2033
|
'Place html directly above the purchase button; never pre-check the box. For consent records as evidence, pass site to get the Elements snippet instead.',
|
|
@@ -2176,7 +2233,7 @@ const callApi = {
|
|
|
2176
2233
|
description: [
|
|
2177
2234
|
'For a next_action whose operation has no dedicated tool (e.g. createStripeCheckoutConfig, updateTask, updateNoticeSettings, listConsents). Calls the Checkbox /v1 API with this server\'s connection; paths and bodies are in https://app.checkbox.site/v1/openapi.json.',
|
|
2178
2235
|
'Examples: {"method":"GET","path":"/v1/account"} ; {"method":"POST","path":"/v1/sites/site_123/domain_verification/check"}.',
|
|
2179
|
-
'Never send API keys or Stripe keys through it
|
|
2236
|
+
'Never send API keys or Stripe keys through it, except a Stripe TEST restricted key via store_stripe_test_key (not call_api). Live keys are added by the human in the Checkbox dashboard, never pasted into the chat.',
|
|
2180
2237
|
'GET is read-only. POST/DELETE change data: only do what the user or a next_action asked for. DELETE archives or removes objects — confirm with the user first.',
|
|
2181
2238
|
].join('\n'),
|
|
2182
2239
|
input: {
|
|
@@ -2207,7 +2264,7 @@ const callApi = {
|
|
|
2207
2264
|
* brand are read as merchant_name; any other unknown field is listed back as ignored.
|
|
2208
2265
|
*/
|
|
2209
2266
|
export const SETUP_FIELD_ALIASES = Object.freeze({ brand: 'merchant_name', brand_name: 'merchant_name', business_name: 'merchant_name', company_name: 'merchant_name', platform: 'billing_platform' });
|
|
2210
|
-
const BILLING_PLATFORMS = new Set(['stripe', 'shopify', 'recharge', 'chargebee', 'recurly', 'paddle', 'woocommerce', 'custom']);
|
|
2267
|
+
const BILLING_PLATFORMS = new Set(['stripe', 'shopify', 'recharge', 'chargebee', 'recurly', 'paddle', 'lemon_squeezy', 'woocommerce', 'custom']);
|
|
2211
2268
|
export function normalizeSetupArgs(raw, known) {
|
|
2212
2269
|
const args = {};
|
|
2213
2270
|
const aliased = [];
|
|
@@ -2276,6 +2333,7 @@ export const TOOLS = [
|
|
|
2276
2333
|
answerTask,
|
|
2277
2334
|
verifySite,
|
|
2278
2335
|
getVerification,
|
|
2336
|
+
storeStripeTestKey,
|
|
2279
2337
|
startTestRun,
|
|
2280
2338
|
getTestRun,
|
|
2281
2339
|
checkDisclosure,
|
|
@@ -2317,6 +2375,7 @@ export const TOOL_OPERATIONS = {
|
|
|
2317
2375
|
answer_task: ['answerTask'],
|
|
2318
2376
|
verify_site: ['verifySite'],
|
|
2319
2377
|
get_verification: ['retrieveVerification'],
|
|
2378
|
+
store_stripe_test_key: ['createTestCredential'],
|
|
2320
2379
|
start_test_run: ['createTestRun'],
|
|
2321
2380
|
get_test_run: ['listTestRuns', 'retrieveTestRun'],
|
|
2322
2381
|
check_disclosure: ['checkDisclosure'],
|
package/package.json
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkbox-site/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Checkbox MCP server: subscription-law compliance tools for AI agents (remote at https://app.checkbox.site/mcp, or local stdio).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"subscription",
|
|
7
|
+
"subscription-compliance",
|
|
7
8
|
"auto-renewal",
|
|
8
9
|
"automatic-renewal",
|
|
9
10
|
"compliance",
|
|
10
11
|
"rosca",
|
|
11
12
|
"click-to-cancel",
|
|
12
13
|
"california-arl",
|
|
14
|
+
"auto-renewal-law",
|
|
15
|
+
"auto-renewal-compliance",
|
|
16
|
+
"subscription-cancellation",
|
|
17
|
+
"renewal-reminder",
|
|
18
|
+
"stripe-subscriptions",
|
|
19
|
+
"ftc",
|
|
20
|
+
"negative-option",
|
|
13
21
|
"stripe",
|
|
14
22
|
"stripe-checkout",
|
|
15
23
|
"checkout",
|
|
@@ -64,7 +72,7 @@
|
|
|
64
72
|
"prepack": "npm run build"
|
|
65
73
|
},
|
|
66
74
|
"dependencies": {
|
|
67
|
-
"@checkbox-site/node": "^0.2.
|
|
75
|
+
"@checkbox-site/node": "^0.2.1",
|
|
68
76
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
69
77
|
"zod": "^4.6.7"
|
|
70
78
|
}
|