@boldsec/mcp 0.1.0

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/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @boldsec/mcp
2
+
3
+ The **BoLD MCP server** — connect, wire, and verify [BoLD](https://boldsec.io) live BOLA/IDOR
4
+ monitoring from your AI editor (Claude Desktop, Cursor, Codex). Your editor's AI assistant reads your
5
+ repo and wires BoLD for you; you review every change.
6
+
7
+ - **Metadata only.** BoLD receives only request metadata, never your code or your users' data. This
8
+ server reads nothing and uploads nothing; it just talks to the BoLD API on your behalf.
9
+ - **Never reaches a verdict.** The MCP only connects / lists / shows coverage / explains wiring. BoLD's
10
+ engine owns every verdict; no tool here decides "vulnerable" or "safe".
11
+ - **You stay in control.** The assistant proposes diffs; you approve them. Nothing is committed for you.
12
+
13
+ ## Setup
14
+
15
+ 1. In BoLD, under **Settings**, mint a **personal access token** (starts with `blt_`).
16
+ 2. Add the server to your editor's MCP config. For Claude Desktop (`claude_desktop_config.json`):
17
+
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "bold": {
22
+ "command": "npx",
23
+ "args": ["-y", "@boldsec/mcp"],
24
+ "env": { "BOLD_TOKEN": "blt_your_token_here" }
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ `BOLD_API_URL` is optional (defaults to `https://api.boldsec.io`). The token is a secret: it is sent
31
+ only in the Authorization header and is never logged or echoed.
32
+
33
+ ## Tools
34
+
35
+ - **bold_connect_app(name, base_url)** — connect an app; returns the two env vars to set (the ingest
36
+ key is a secret shown once).
37
+ - **bold_list_monitors()** — your connected apps and whether each is watching or waiting.
38
+ - **bold_coverage(monitor_id)** — the routes BoLD has actually seen traffic on. Reports only observed
39
+ routes; never implies coverage of an unwired route (not an all-clear).
40
+ - **bold_wiring_guide(stack?)** — step-by-step wiring instructions for the current repo, including how
41
+ to write `resolveCallerId` for your auth, and the rule to leave the `TODO` if unsure.
42
+
43
+ ## What a session looks like
44
+
45
+ > "Connect this app to BoLD and wire it up."
46
+
47
+ The assistant calls `bold_connect_app`, then `bold_wiring_guide`, then wraps your routes with
48
+ `@boldsec/next`, writes `resolveCallerId` from your repo's auth (or leaves the `TODO` and asks you if
49
+ it cannot tell), sets the env vars, and finally calls `bold_coverage` so you can watch the routes light
50
+ up. You review and approve every diff.
@@ -0,0 +1,64 @@
1
+ /**
2
+ * The BoLD REST client the MCP tools call. A THIN wrapper over the existing /api/live + /api/tokens
3
+ * endpoints, authenticated with the user's personal access token (Bearer). No detection logic lives
4
+ * here; the MCP never reaches a verdict.
5
+ *
6
+ * TRUST: the token is a secret. It is sent only in the Authorization header, NEVER logged, NEVER
7
+ * echoed into a tool result. Errors are turned into a typed, honest BoldApiError (status + message),
8
+ * never a raw stack trace, and never a fabricated success (a failed call must read as a failure, not
9
+ * a silent empty "all clear").
10
+ */
11
+ export declare class BoldApiError extends Error {
12
+ readonly status: number;
13
+ constructor(status: number, message: string);
14
+ }
15
+ export interface Monitor {
16
+ id: string;
17
+ label: string;
18
+ base_url: string;
19
+ active: boolean;
20
+ events_seen: number;
21
+ last_event_at: string | null;
22
+ }
23
+ export interface MonitorCreated extends Monitor {
24
+ ingest_key: string;
25
+ }
26
+ export interface CoverageEndpoint {
27
+ endpoint: string;
28
+ hits: number;
29
+ first_seen_at: string;
30
+ last_seen_at: string;
31
+ }
32
+ export interface Coverage {
33
+ monitor_id: string;
34
+ endpoints: CoverageEndpoint[];
35
+ endpoints_seen: number;
36
+ truncated: boolean;
37
+ }
38
+ /** A minimal fetch signature so tests can inject a stub without a network. */
39
+ export type FetchLike = (url: string, init?: {
40
+ method?: string;
41
+ headers?: Record<string, string>;
42
+ body?: string;
43
+ }) => Promise<{
44
+ status: number;
45
+ ok: boolean;
46
+ json: () => Promise<unknown>;
47
+ text: () => Promise<string>;
48
+ }>;
49
+ export interface BoldClientConfig {
50
+ apiUrl: string;
51
+ token: string;
52
+ fetchImpl?: FetchLike;
53
+ }
54
+ export declare class BoldClient {
55
+ private readonly apiUrl;
56
+ private readonly token;
57
+ private readonly fetchImpl;
58
+ constructor(cfg: BoldClientConfig);
59
+ private request;
60
+ connectApp(name: string, baseUrl: string): Promise<MonitorCreated>;
61
+ listMonitors(): Promise<Monitor[]>;
62
+ coverage(monitorId: string): Promise<Coverage>;
63
+ }
64
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK5C;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,cAAe,SAAQ,OAAO;IAC7C,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,8EAA8E;AAC9E,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,KACxE,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AAEzG,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAE1B,GAAG,EAAE,gBAAgB;YAMnB,OAAO;IAkCrB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAOlE,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAIlC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAM/C"}
package/dist/client.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The BoLD REST client the MCP tools call. A THIN wrapper over the existing /api/live + /api/tokens
3
+ * endpoints, authenticated with the user's personal access token (Bearer). No detection logic lives
4
+ * here; the MCP never reaches a verdict.
5
+ *
6
+ * TRUST: the token is a secret. It is sent only in the Authorization header, NEVER logged, NEVER
7
+ * echoed into a tool result. Errors are turned into a typed, honest BoldApiError (status + message),
8
+ * never a raw stack trace, and never a fabricated success (a failed call must read as a failure, not
9
+ * a silent empty "all clear").
10
+ */
11
+ export class BoldApiError extends Error {
12
+ status;
13
+ constructor(status, message) {
14
+ super(message);
15
+ this.status = status;
16
+ this.name = "BoldApiError";
17
+ }
18
+ }
19
+ export class BoldClient {
20
+ apiUrl;
21
+ token;
22
+ fetchImpl;
23
+ constructor(cfg) {
24
+ this.apiUrl = cfg.apiUrl.replace(/\/+$/, "");
25
+ this.token = cfg.token;
26
+ this.fetchImpl = cfg.fetchImpl ?? globalThis.fetch;
27
+ }
28
+ async request(method, path, body) {
29
+ let res;
30
+ try {
31
+ res = await this.fetchImpl(`${this.apiUrl}${path}`, {
32
+ method,
33
+ headers: {
34
+ authorization: `Bearer ${this.token}`,
35
+ ...(body !== undefined ? { "content-type": "application/json" } : {}),
36
+ },
37
+ ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
38
+ });
39
+ }
40
+ catch (e) {
41
+ // A network/DNS failure is honest, not a verdict: surface it, never pretend success.
42
+ throw new BoldApiError(0, `Could not reach BoLD at ${this.apiUrl}: ${e.message}`);
43
+ }
44
+ if (res.status === 401) {
45
+ throw new BoldApiError(401, "BoLD rejected the token (invalid or revoked). Mint a new personal access token in BoLD under Settings and update BOLD_TOKEN.");
46
+ }
47
+ if (!res.ok) {
48
+ let detail = `HTTP ${res.status}`;
49
+ try {
50
+ const j = (await res.json());
51
+ if (j?.detail)
52
+ detail = j.detail;
53
+ }
54
+ catch {
55
+ // keep the status-only message
56
+ }
57
+ throw new BoldApiError(res.status, detail);
58
+ }
59
+ return (await res.json());
60
+ }
61
+ connectApp(name, baseUrl) {
62
+ return this.request("POST", "/api/live/monitors", {
63
+ label: name,
64
+ base_url: baseUrl,
65
+ });
66
+ }
67
+ listMonitors() {
68
+ return this.request("GET", "/api/live/monitors");
69
+ }
70
+ coverage(monitorId) {
71
+ return this.request("GET", `/api/live/monitors/${encodeURIComponent(monitorId)}/coverage`);
72
+ }
73
+ }
74
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The wiring guide text the MCP hands the user's AI agent. PURE TEXT, no code is written here. It
3
+ * teaches the agent how to wire BoLD into THIS repo (auth detection, owner-field discovery, the exact
4
+ * resolveCallerId patterns), with the anthem baked in as hard rules. Above all: if the caller-id
5
+ * namespace can't be confidently determined, LEAVE THE TODO (a wrong resolveCallerId causes a false
6
+ * verdict, which is worse than none).
7
+ *
8
+ * M3 makes this repo-aware and precise enough that the agent can wire a real app end to end, with the
9
+ * human approving every diff. The MCP never edits the user's files; the agent does, in the editor.
10
+ */
11
+ export declare function wiringGuide(stack?: string): string;
12
+ //# sourceMappingURL=guide.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide.d.ts","sourceRoot":"","sources":["../src/guide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAmBH,wBAAgB,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAkDlD"}
package/dist/guide.js ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The wiring guide text the MCP hands the user's AI agent. PURE TEXT, no code is written here. It
3
+ * teaches the agent how to wire BoLD into THIS repo (auth detection, owner-field discovery, the exact
4
+ * resolveCallerId patterns), with the anthem baked in as hard rules. Above all: if the caller-id
5
+ * namespace can't be confidently determined, LEAVE THE TODO (a wrong resolveCallerId causes a false
6
+ * verdict, which is worse than none).
7
+ *
8
+ * M3 makes this repo-aware and precise enough that the agent can wire a real app end to end, with the
9
+ * human approving every diff. The MCP never edits the user's files; the agent does, in the editor.
10
+ */
11
+ const RESOLVER_PATTERNS = [
12
+ " - NextAuth / Auth.js:",
13
+ " import { auth } from \"@/auth\";",
14
+ " const resolveCallerId = async () => (await auth())?.user?.id ?? null;",
15
+ " - Supabase Auth (server):",
16
+ " const { data } = await supabase.auth.getUser();",
17
+ " const resolveCallerId = async () => data.user?.id ?? null;",
18
+ " - JWT in the Authorization header:",
19
+ " const resolveCallerId = async (req) => {",
20
+ " const t = req.headers.get(\"authorization\")?.replace(\"Bearer \", \"\");",
21
+ " return t ? (jwtVerify(t, KEY)).payload.sub ?? null : null;",
22
+ " };",
23
+ " - Session id in a cookie:",
24
+ " const resolveCallerId = async () =>",
25
+ " (await lookupUserIdForSession((await cookies()).get(\"session\")?.value)) ?? null;",
26
+ ];
27
+ export function wiringGuide(stack) {
28
+ const stackNote = stack && stack.toLowerCase() === "cloudflare"
29
+ ? "This app is on Cloudflare, so the one-header edge path is an option (one global header covers every route). Otherwise use the SDK wrapper below."
30
+ : "Use the SDK wrapper (works on any host). If the app sits behind an edge/proxy you control, the one-header path can cover every route at once, but the wrapper is the default.";
31
+ return [
32
+ "# Wiring BoLD into this app (instructions for the AI assistant)",
33
+ "",
34
+ "You are wiring BoLD, a runtime BOLA/IDOR alarm, into the user's Next.js App Router app. Work in",
35
+ "the user's editor and PRESENT EVERY CHANGE AS A DIFF THE USER APPROVES. Never commit silently.",
36
+ "BoLD only ever receives request metadata; never send the user's code or data anywhere.",
37
+ "",
38
+ "## Steps",
39
+ "1. Install the SDK: npm install @boldsec/next",
40
+ "2. Wrap every route handler: npx @boldsec/next init (dry run first; --apply writes; review the diff)",
41
+ `3. ${stackNote}`,
42
+ "4. Write resolveCallerId for EACH wrapped route (the codemod leaves a TODO(BoLD) marker). See below.",
43
+ "5. Set the two env vars from bold_connect_app (BOLD_INGEST_URL, BOLD_INGEST_KEY), then redeploy.",
44
+ "6. Verify with bold_coverage once a request has been made.",
45
+ "",
46
+ "## Step 4 in detail -- write resolveCallerId correctly from THIS repo",
47
+ "resolveCallerId(request) must return the SIGNED-IN user's id in the SAME FORM as the owner field",
48
+ "(ownerId / owner_id / user_id / ...) that this app's object responses expose. Determine BOTH ends",
49
+ "from the actual code, do not assume:",
50
+ "",
51
+ "a) Find how the app authenticates a request (read the repo):",
52
+ " look for next-auth/@auth, @supabase/ssr or supabase-js, a jwt/jose verify, or a session cookie",
53
+ " that maps to a user. Identify the call that yields the current user's id.",
54
+ "b) Find the owner field's VALUE SHAPE: open an example object route's response (or its DB model)",
55
+ " and note what the owner id looks like (e.g. usr_101, a bare number, a uuid).",
56
+ "c) Write a resolver returning the caller id in THAT shape. Common patterns:",
57
+ ...RESOLVER_PATTERNS,
58
+ "",
59
+ "d) SANITY-CHECK the shape with bold_check_resolver (pass a sample caller id + a sample owner",
60
+ " value). It flags a gross mismatch (e.g. you return a number but owners are usr_*). A shape",
61
+ " match does NOT prove correctness, you must still be sure the value is the caller's OWN id.",
62
+ "",
63
+ "## THE ANTHEM RULE (do not break this)",
64
+ "If you CANNOT confidently determine that the id your resolver returns is the signed-in caller's",
65
+ "own id, in the owner field's namespace, DO NOT GUESS. Leave the TODO(BoLD) in place and tell the",
66
+ "user you were unsure and why. A wrong resolveCallerId makes BoLD compare the wrong identities,",
67
+ "which can cause a FALSE alarm or hide a real one. A left-in TODO is safe (BoLD holds cross-reads",
68
+ "for review); a wrong resolver is not. When unsure: stop and ask, never ship a confident guess.",
69
+ "",
70
+ "## After wiring",
71
+ "Tell the user to make one authenticated request, then call bold_coverage to confirm BoLD has",
72
+ "started seeing routes. bold_coverage reports ONLY routes BoLD has actually observed; it never",
73
+ "implies coverage of an unwired route.",
74
+ ].join("\n");
75
+ }
76
+ //# sourceMappingURL=guide.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guide.js","sourceRoot":"","sources":["../src/guide.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,iBAAiB,GAAG;IACxB,yBAAyB;IACzB,wCAAwC;IACxC,6EAA6E;IAC7E,6BAA6B;IAC7B,uDAAuD;IACvD,kEAAkE;IAClE,sCAAsC;IACtC,gDAAgD;IAChD,mFAAmF;IACnF,oEAAoE;IACpE,UAAU;IACV,6BAA6B;IAC7B,2CAA2C;IAC3C,4FAA4F;CAC7F,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,MAAM,SAAS,GACb,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,KAAK,YAAY;QAC3C,CAAC,CAAC,kJAAkJ;QACpJ,CAAC,CAAC,+KAA+K,CAAC;IAEtL,OAAO;QACL,iEAAiE;QACjE,EAAE;QACF,iGAAiG;QACjG,gGAAgG;QAChG,wFAAwF;QACxF,EAAE;QACF,UAAU;QACV,gDAAgD;QAChD,yGAAyG;QACzG,MAAM,SAAS,EAAE;QACjB,sGAAsG;QACtG,kGAAkG;QAClG,4DAA4D;QAC5D,EAAE;QACF,uEAAuE;QACvE,kGAAkG;QAClG,mGAAmG;QACnG,sCAAsC;QACtC,EAAE;QACF,8DAA8D;QAC9D,mGAAmG;QACnG,8EAA8E;QAC9E,kGAAkG;QAClG,iFAAiF;QACjF,6EAA6E;QAC7E,GAAG,iBAAiB;QACpB,EAAE;QACF,8FAA8F;QAC9F,+FAA+F;QAC/F,+FAA+F;QAC/F,EAAE;QACF,wCAAwC;QACxC,iGAAiG;QACjG,kGAAkG;QAClG,gGAAgG;QAChG,kGAAkG;QAClG,gGAAgG;QAChG,EAAE;QACF,iBAAiB;QACjB,8FAA8F;QAC9F,+FAA+F;QAC/F,uCAAuC;KACxC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @boldsec/mcp entry point. An MCP server (stdio) that the user's AI editor (Claude Desktop, Cursor,
4
+ * Codex) spawns. It exposes BoLD's connect / list / coverage / wiring-guide tools so the user can
5
+ * wire BoLD without leaving their editor, with the agent proposing diffs the human approves.
6
+ *
7
+ * Config via environment (set in the editor's MCP config, never hardcoded):
8
+ * BOLD_TOKEN the personal access token (blt_...) minted in BoLD under Settings. REQUIRED.
9
+ * BOLD_API_URL the BoLD API base. Optional; defaults to https://api.boldsec.io.
10
+ *
11
+ * The token is a secret: it is sent only in the Authorization header and is NEVER logged or echoed.
12
+ * On a missing token we fail fast with a clear message (to stderr, never stdout, which is the MCP
13
+ * protocol channel).
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG"}
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @boldsec/mcp entry point. An MCP server (stdio) that the user's AI editor (Claude Desktop, Cursor,
4
+ * Codex) spawns. It exposes BoLD's connect / list / coverage / wiring-guide tools so the user can
5
+ * wire BoLD without leaving their editor, with the agent proposing diffs the human approves.
6
+ *
7
+ * Config via environment (set in the editor's MCP config, never hardcoded):
8
+ * BOLD_TOKEN the personal access token (blt_...) minted in BoLD under Settings. REQUIRED.
9
+ * BOLD_API_URL the BoLD API base. Optional; defaults to https://api.boldsec.io.
10
+ *
11
+ * The token is a secret: it is sent only in the Authorization header and is NEVER logged or echoed.
12
+ * On a missing token we fail fast with a clear message (to stderr, never stdout, which is the MCP
13
+ * protocol channel).
14
+ */
15
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16
+ import { BoldClient } from "./client.js";
17
+ import { buildServer } from "./server.js";
18
+ const DEFAULT_API_URL = "https://api.boldsec.io";
19
+ async function main() {
20
+ const token = process.env.BOLD_TOKEN;
21
+ if (!token) {
22
+ // stderr only: stdout is the JSON-RPC channel and must not carry human messages.
23
+ process.stderr.write("BOLD_TOKEN is not set. Mint a personal access token in BoLD (Settings) and set it as " +
24
+ "BOLD_TOKEN in your editor's MCP config for @boldsec/mcp.\n");
25
+ process.exit(1);
26
+ }
27
+ const apiUrl = process.env.BOLD_API_URL ?? DEFAULT_API_URL;
28
+ const client = new BoldClient({ apiUrl, token });
29
+ const server = buildServer(client);
30
+ const transport = new StdioServerTransport();
31
+ await server.connect(transport);
32
+ // The server now runs until the editor closes the transport. Nothing is written to stdout except
33
+ // the MCP protocol; the token never appears anywhere.
34
+ }
35
+ main().catch((e) => {
36
+ process.stderr.write(`@boldsec/mcp failed to start: ${e.message}\n`);
37
+ process.exit(1);
38
+ });
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,eAAe,GAAG,wBAAwB,CAAC;AAEjD,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,iFAAiF;QACjF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,uFAAuF;YACrF,4DAA4D,CAC/D,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,eAAe,CAAC;IAE3D,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,iGAAiG;IACjG,sDAAsD;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAkC,CAAW,CAAC,OAAO,IAAI,CAAC,CAAC;IAChF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The BoLD MCP server wiring. Registers the four SAFE tools (connect / list / coverage / wiring
3
+ * guide) over a BoldClient. Building the server is separated from process startup so tests can drive
4
+ * a real server with an injected (stubbed) client over an in-memory transport, no editor or network.
5
+ *
6
+ * Scope (M2): connect + read + guide only. There is deliberately NO tool that reaches a verdict or
7
+ * writes the user's code. The MCP relays; classify() owns every verdict, untouched.
8
+ */
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import type { BoldClient } from "./client.ts";
11
+ export declare function buildServer(client: BoldClient): McpServer;
12
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAmB9C,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAkFzD"}
package/dist/server.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The BoLD MCP server wiring. Registers the four SAFE tools (connect / list / coverage / wiring
3
+ * guide) over a BoldClient. Building the server is separated from process startup so tests can drive
4
+ * a real server with an injected (stubbed) client over an in-memory transport, no editor or network.
5
+ *
6
+ * Scope (M2): connect + read + guide only. There is deliberately NO tool that reaches a verdict or
7
+ * writes the user's code. The MCP relays; classify() owns every verdict, untouched.
8
+ */
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import { z } from "zod";
11
+ import { toolCheckResolver, toolConnectApp, toolCoverage, toolListMonitors, toolWiringGuide, } from "./tools.js";
12
+ const VERSION = "0.1.0";
13
+ function content(r) {
14
+ return {
15
+ content: [{ type: "text", text: r.text }],
16
+ isError: r.isError,
17
+ };
18
+ }
19
+ export function buildServer(client) {
20
+ const server = new McpServer({ name: "boldsec-mcp", version: VERSION });
21
+ server.registerTool("bold_connect_app", {
22
+ title: "Connect an app to BoLD",
23
+ description: "Connect an app to BoLD live monitoring. Returns the ingest env vars (the ingest key is a " +
24
+ "secret shown once). After this, wire the routes (bold_wiring_guide) and confirm (bold_coverage).",
25
+ inputSchema: {
26
+ name: z.string().min(1).describe("A label for the app, e.g. 'My SaaS'."),
27
+ base_url: z.string().min(1).describe("The app's base URL, e.g. https://myapp.com"),
28
+ },
29
+ }, async (args) => content(await toolConnectApp(client, args)));
30
+ server.registerTool("bold_list_monitors", {
31
+ title: "List connected apps",
32
+ description: "List the apps connected to BoLD and whether each is watching or waiting.",
33
+ inputSchema: {},
34
+ }, async () => content(await toolListMonitors(client)));
35
+ server.registerTool("bold_coverage", {
36
+ title: "Routes BoLD has seen",
37
+ description: "Show the routes BoLD has actually observed traffic on for an app. Reports ONLY observed " +
38
+ "routes; never implies coverage of an unwired route (not an all-clear).",
39
+ inputSchema: {
40
+ monitor_id: z.string().min(1).describe("The monitor id from bold_connect_app/bold_list_monitors."),
41
+ },
42
+ }, async (args) => content(await toolCoverage(client, args)));
43
+ server.registerTool("bold_wiring_guide", {
44
+ title: "How to wire BoLD into this app",
45
+ description: "Return step-by-step instructions for wiring BoLD into the current repo, including how to " +
46
+ "write resolveCallerId for this app's auth, and the rule to leave the TODO if unsure.",
47
+ inputSchema: {
48
+ stack: z
49
+ .string()
50
+ .optional()
51
+ .describe("Optional hosting stack hint (e.g. 'cloudflare', 'vercel')."),
52
+ },
53
+ }, async (args) => content(toolWiringGuide(args)));
54
+ server.registerTool("bold_check_resolver", {
55
+ title: "Sanity-check a resolveCallerId shape",
56
+ description: "Given a sample caller id (what resolveCallerId returns) and a sample owner-field value, " +
57
+ "flag a gross shape mismatch. A shape match does NOT prove correctness; the human must still " +
58
+ "confirm the value is the caller's own id. Never asserts a resolver is right.",
59
+ inputSchema: {
60
+ caller_id_sample: z
61
+ .string()
62
+ .min(1)
63
+ .describe("An example id resolveCallerId returns, e.g. 'usr_101'."),
64
+ owner_value_sample: z
65
+ .string()
66
+ .min(1)
67
+ .describe("An example value of the response owner field, e.g. 'usr_777'."),
68
+ },
69
+ }, async (args) => content(toolCheckResolver(args)));
70
+ return server;
71
+ }
72
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Id-shape classification for the resolveCallerId sanity check (M3). Mirrors how the BoLD SDK reads
3
+ * object ids: numeric (42), uuid/long-hex, or a prefixed id (usr_101). Used by bold_check_resolver to
4
+ * catch a GROSS shape mismatch between what a resolver returns and the owner-field value.
5
+ *
6
+ * IMPORTANT (the anthem): a shape MATCH is necessary but NOT sufficient. usr_101 and usr_999 share a
7
+ * shape yet are different users. This classifier can only flag an obvious mismatch (e.g. resolver
8
+ * returns a numeric id while owners are usr_*); it can NEVER assert a resolver is correct. The caller
9
+ * (the agent + the human) must still confirm the actual value is the caller's own id.
10
+ */
11
+ export type IdShape = "numeric" | "uuid-or-hex" | "prefixed" | "other";
12
+ export declare function classifyShape(value: string): IdShape;
13
+ export interface ShapeCheck {
14
+ match: boolean;
15
+ callerShape: IdShape;
16
+ ownerShape: IdShape;
17
+ message: string;
18
+ }
19
+ /** Compare the shape a resolver returns against a sample owner value. Honest by construction: it
20
+ * reports a plausible match or a likely mismatch, and ALWAYS reminds the caller that a shape match
21
+ * does not prove the value is correct (only the human/agent can confirm that). */
22
+ export declare function checkResolverShape(callerIdSample: string, ownerValueSample: string): ShapeCheck;
23
+ //# sourceMappingURL=shapes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shapes.d.ts","sourceRoot":"","sources":["../src/shapes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,OAAO,GAAG,SAAS,GAAG,aAAa,GAAG,UAAU,GAAG,OAAO,CAAC;AAMvE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOpD;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;mFAEmF;AACnF,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,UAAU,CAa/F"}
package/dist/shapes.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Id-shape classification for the resolveCallerId sanity check (M3). Mirrors how the BoLD SDK reads
3
+ * object ids: numeric (42), uuid/long-hex, or a prefixed id (usr_101). Used by bold_check_resolver to
4
+ * catch a GROSS shape mismatch between what a resolver returns and the owner-field value.
5
+ *
6
+ * IMPORTANT (the anthem): a shape MATCH is necessary but NOT sufficient. usr_101 and usr_999 share a
7
+ * shape yet are different users. This classifier can only flag an obvious mismatch (e.g. resolver
8
+ * returns a numeric id while owners are usr_*); it can NEVER assert a resolver is correct. The caller
9
+ * (the agent + the human) must still confirm the actual value is the caller's own id.
10
+ */
11
+ const NUMERIC = /^[0-9]+$/;
12
+ const UUID_OR_HEX = /^[0-9a-fA-F-]{6,}$/;
13
+ const PREFIXED = /^[a-zA-Z][a-zA-Z0-9]*(?:_[a-zA-Z0-9]+)+$/;
14
+ export function classifyShape(value) {
15
+ const v = value.trim();
16
+ if (NUMERIC.test(v))
17
+ return "numeric";
18
+ // A prefixed id (usr_101) must be checked before uuid/hex so it isn't mis-read as hex.
19
+ if (PREFIXED.test(v))
20
+ return "prefixed";
21
+ if (UUID_OR_HEX.test(v))
22
+ return "uuid-or-hex";
23
+ return "other";
24
+ }
25
+ /** Compare the shape a resolver returns against a sample owner value. Honest by construction: it
26
+ * reports a plausible match or a likely mismatch, and ALWAYS reminds the caller that a shape match
27
+ * does not prove the value is correct (only the human/agent can confirm that). */
28
+ export function checkResolverShape(callerIdSample, ownerValueSample) {
29
+ const callerShape = classifyShape(callerIdSample);
30
+ const ownerShape = classifyShape(ownerValueSample);
31
+ const match = callerShape === ownerShape && callerShape !== "other";
32
+ const base = `Caller id looks like "${callerShape}"; owner field looks like "${ownerShape}".`;
33
+ const message = match
34
+ ? `${base} The shapes are consistent. This does NOT prove the resolver: confirm the value it ` +
35
+ `returns is the SIGNED-IN caller's own id (usr_101 and usr_999 share a shape but are different ` +
36
+ `users). If you cannot confirm, leave the TODO.`
37
+ : `${base} The shapes do NOT match, so the resolver is very likely wrong (BoLD would compare ` +
38
+ `mismatched identities). Re-derive resolveCallerId so it returns the caller id in the owner ` +
39
+ `field's form, or leave the TODO and ask the user.`;
40
+ return { match, callerShape, ownerShape, message };
41
+ }
42
+ //# sourceMappingURL=shapes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shapes.js","sourceRoot":"","sources":["../src/shapes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,OAAO,GAAG,UAAU,CAAC;AAC3B,MAAM,WAAW,GAAG,oBAAoB,CAAC;AACzC,MAAM,QAAQ,GAAG,0CAA0C,CAAC;AAE5D,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IACvB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACtC,uFAAuF;IACvF,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,UAAU,CAAC;IACxC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,aAAa,CAAC;IAC9C,OAAO,OAAO,CAAC;AACjB,CAAC;AASD;;mFAEmF;AACnF,MAAM,UAAU,kBAAkB,CAAC,cAAsB,EAAE,gBAAwB;IACjF,MAAM,WAAW,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,WAAW,KAAK,UAAU,IAAI,WAAW,KAAK,OAAO,CAAC;IACpE,MAAM,IAAI,GAAG,yBAAyB,WAAW,8BAA8B,UAAU,IAAI,CAAC;IAC9F,MAAM,OAAO,GAAG,KAAK;QACnB,CAAC,CAAC,GAAG,IAAI,qFAAqF;YAC5F,gGAAgG;YAChG,gDAAgD;QAClD,CAAC,CAAC,GAAG,IAAI,qFAAqF;YAC5F,6FAA6F;YAC7F,mDAAmD,CAAC;IACxD,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AACrD,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The MCP tool handlers. Each is a PURE async function over a BoldClient, returning the text the
3
+ * agent sees. Kept separate from the server wiring so every tool is unit-testable against a stubbed
4
+ * client (success + 401 + network error + the anthem-honesty of the coverage text), with no editor
5
+ * and no network.
6
+ *
7
+ * THE ANTHEM IN THE MCP: none of these reach a verdict. bold_coverage in particular states plainly
8
+ * that it reports ONLY routes BoLD has observed and never implies coverage of an unwired route, so
9
+ * the tool output can never read as a false "all clear".
10
+ */
11
+ import { type BoldClient } from "./client.ts";
12
+ export interface ToolResult {
13
+ isError: boolean;
14
+ text: string;
15
+ }
16
+ export declare function toolConnectApp(client: BoldClient, args: {
17
+ name: string;
18
+ base_url: string;
19
+ }): Promise<ToolResult>;
20
+ export declare function toolListMonitors(client: BoldClient): Promise<ToolResult>;
21
+ export declare function toolCoverage(client: BoldClient, args: {
22
+ monitor_id: string;
23
+ }): Promise<ToolResult>;
24
+ export declare function toolWiringGuide(args: {
25
+ stack?: string;
26
+ }): ToolResult;
27
+ export declare function toolCheckResolver(args: {
28
+ caller_id_sample: string;
29
+ owner_value_sample: string;
30
+ }): ToolResult;
31
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAI5D,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAaD,wBAAsB,cAAc,CAClC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACvC,OAAO,CAAC,UAAU,CAAC,CAkBrB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAkB9E;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3B,OAAO,CAAC,UAAU,CAAC,CA8BrB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAGpE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,UAAU,CAKb"}
package/dist/tools.js ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * The MCP tool handlers. Each is a PURE async function over a BoldClient, returning the text the
3
+ * agent sees. Kept separate from the server wiring so every tool is unit-testable against a stubbed
4
+ * client (success + 401 + network error + the anthem-honesty of the coverage text), with no editor
5
+ * and no network.
6
+ *
7
+ * THE ANTHEM IN THE MCP: none of these reach a verdict. bold_coverage in particular states plainly
8
+ * that it reports ONLY routes BoLD has observed and never implies coverage of an unwired route, so
9
+ * the tool output can never read as a false "all clear".
10
+ */
11
+ import { BoldApiError } from "./client.js";
12
+ import { wiringGuide } from "./guide.js";
13
+ import { checkResolverShape } from "./shapes.js";
14
+ function ok(text) {
15
+ return { isError: false, text };
16
+ }
17
+ /** Turn any thrown error into an honest, non-leaking tool error. Never prints the token; a
18
+ * BoldApiError carries a safe message, an unexpected error is reported without internals. */
19
+ function fail(e) {
20
+ if (e instanceof BoldApiError)
21
+ return { isError: true, text: e.message };
22
+ return { isError: true, text: `Unexpected error talking to BoLD: ${e.message}` };
23
+ }
24
+ export async function toolConnectApp(client, args) {
25
+ try {
26
+ const m = await client.connectApp(args.name, args.base_url);
27
+ return ok([
28
+ `Connected "${m.label}" to BoLD (monitor id: ${m.id}).`,
29
+ "",
30
+ "Set these two environment variables on the app, then redeploy:",
31
+ ` BOLD_INGEST_URL=https://api.boldsec.io/api/live/ingest`,
32
+ ` BOLD_INGEST_KEY=${m.ingest_key}`,
33
+ "",
34
+ "This ingest key is shown ONCE and is a secret: put it in the app's env (not in committed",
35
+ "code). Next, wire the routes (call bold_wiring_guide) and confirm with bold_coverage.",
36
+ ].join("\n"));
37
+ }
38
+ catch (e) {
39
+ return fail(e);
40
+ }
41
+ }
42
+ export async function toolListMonitors(client) {
43
+ try {
44
+ const monitors = await client.listMonitors();
45
+ if (monitors.length === 0) {
46
+ return ok("You have no connected apps yet. Use bold_connect_app to connect one.");
47
+ }
48
+ const lines = monitors.map((m) => {
49
+ const state = !m.active
50
+ ? "disconnected"
51
+ : m.events_seen > 0
52
+ ? `watching (${m.events_seen} events seen)`
53
+ : "waiting for first event";
54
+ return `- ${m.label} [${m.id}] ${m.base_url} -- ${state}`;
55
+ });
56
+ return ok(["Your connected apps:", ...lines].join("\n"));
57
+ }
58
+ catch (e) {
59
+ return fail(e);
60
+ }
61
+ }
62
+ export async function toolCoverage(client, args) {
63
+ try {
64
+ const c = await client.coverage(args.monitor_id);
65
+ if (c.endpoints_seen === 0) {
66
+ return ok([
67
+ "No routes seen yet for this app.",
68
+ "BoLD can only watch routes it has received a request on, so nothing is covered until",
69
+ "traffic arrives. THIS IS NOT AN ALL CLEAR: make one authenticated request and check again.",
70
+ ].join("\n"));
71
+ }
72
+ const lines = c.endpoints.map((e) => `- ${e.endpoint} (${e.hits} hit${e.hits === 1 ? "" : "s"})`);
73
+ return ok([
74
+ `BoLD is watching the ${c.endpoints_seen} route${c.endpoints_seen === 1 ? "" : "s"} it has seen traffic on:`,
75
+ ...lines,
76
+ "",
77
+ "It can ONLY speak to these routes. A route BoLD has not observed is NOT covered; never",
78
+ "assume a route is watched until it appears here.",
79
+ ...(c.truncated
80
+ ? ["", "(The per-app record limit was reached; more routes may be in use than listed.)"]
81
+ : []),
82
+ ].join("\n"));
83
+ }
84
+ catch (e) {
85
+ return fail(e);
86
+ }
87
+ }
88
+ export function toolWiringGuide(args) {
89
+ // Pure text; no network. The anthem rule (leave the TODO if unsure) lives in the guide itself.
90
+ return ok(wiringGuide(args.stack));
91
+ }
92
+ export function toolCheckResolver(args) {
93
+ // Pure shape check. NEVER asserts the resolver is correct (a shape match is necessary, not
94
+ // sufficient); it only catches a gross mismatch and always defers final confirmation to the human.
95
+ const r = checkResolverShape(args.caller_id_sample, args.owner_value_sample);
96
+ return ok(r.message);
97
+ }
98
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@boldsec/mcp",
3
+ "version": "0.1.0",
4
+ "description": "BoLD MCP server: connect, wire, and verify BoLD live BOLA/IDOR monitoring from your AI editor (Claude, Cursor, Codex). Metadata only; never reaches a verdict.",
5
+ "type": "module",
6
+ "bin": {
7
+ "boldsec-mcp": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "homepage": "https://boldsec.io",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/Sahith59/BoLD.git",
20
+ "directory": "web/sdk/bold-mcp"
21
+ },
22
+ "scripts": {
23
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
24
+ "typecheck": "tsc -p tsconfig.json",
25
+ "test": "node --test src/client.test.ts src/tools.test.ts src/server.test.ts src/shapes.test.ts",
26
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
27
+ },
28
+ "keywords": [
29
+ "mcp",
30
+ "model-context-protocol",
31
+ "bola",
32
+ "idor",
33
+ "security",
34
+ "nextjs",
35
+ "claude",
36
+ "cursor"
37
+ ],
38
+ "license": "UNLICENSED",
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "dependencies": {
43
+ "@modelcontextprotocol/sdk": "^1.29.0",
44
+ "zod": "^3.23.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^20",
48
+ "typescript": "^5"
49
+ }
50
+ }