@blursec/mcp 1.0.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.
@@ -0,0 +1,173 @@
1
+ import { BlursecClientConfig, Blursec } from '@blursec/sdk';
2
+
3
+ /**
4
+ * Env-aware Blursec constructor for the MCP server.
5
+ *
6
+ * Reads the API key and environment from process env so agent hosts
7
+ * (Claude Desktop, VS Code, Cursor, ...) can configure the server via
8
+ * their standard `env` block without ever placing the key in a prompt.
9
+ *
10
+ * @module
11
+ */
12
+
13
+ /**
14
+ * Construct a {@link Blursec} client configured from the environment.
15
+ *
16
+ * Environment read:
17
+ *
18
+ * | env var | purpose |
19
+ * |-----------------------|--------------------------------------------------|
20
+ * | `BLURSEC_API_KEY` | API key (required unless overridden) |
21
+ * | `BLURSEC_ENVIRONMENT` | `production` (default) / `staging` / `local` |
22
+ *
23
+ * Every field of {@link BlursecClientConfig} can be overridden via
24
+ * the `overrides` argument; `defaultHeaders` are merged (overrides
25
+ * win on conflict). Outbound traffic is tagged
26
+ * `X-Blursec-Platform: mcp` so the Blursec ops team can distinguish
27
+ * agent traffic from application traffic.
28
+ *
29
+ * @throws {BlursecValidationError} when neither `overrides.apiKey`
30
+ * nor the `BLURSEC_API_KEY` env var is set.
31
+ */
32
+ declare function createBlursecFromEnv(overrides?: Partial<BlursecClientConfig>): Blursec;
33
+
34
+ /**
35
+ * Minimal, zero-dependency MCP server core.
36
+ *
37
+ * Implements the subset of the Model Context Protocol a tools-only
38
+ * server needs — `initialize`, `ping`, `tools/list`, `tools/call` —
39
+ * as plain JSON-RPC 2.0 message handling, decoupled from any
40
+ * transport. {@link BlursecMcpServer.handleLine} is what the stdio
41
+ * CLI feeds; tests drive {@link BlursecMcpServer.handleMessage}
42
+ * directly.
43
+ *
44
+ * Why not `@modelcontextprotocol/sdk`? This package guards a security
45
+ * product's perimeter: a hand-rolled ~200-line protocol core keeps the
46
+ * supply chain at zero runtime dependencies, matching the root SDK's
47
+ * invariant. The protocol subset used here (stdio framing, version
48
+ * negotiation, tools) has been stable across every spec revision since
49
+ * 2024-11-05.
50
+ *
51
+ * @module
52
+ */
53
+
54
+ /** Protocol revisions this server can speak, newest first. */
55
+ declare const SUPPORTED_PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
56
+ /** JSON-RPC 2.0 error codes used by this server. */
57
+ declare const JSONRPC_ERRORS: {
58
+ readonly PARSE_ERROR: -32700;
59
+ readonly INVALID_REQUEST: -32600;
60
+ readonly METHOD_NOT_FOUND: -32601;
61
+ readonly INVALID_PARAMS: -32602;
62
+ };
63
+ /** Shape of every reply produced by {@link BlursecMcpServer}. */
64
+ interface JsonRpcResponse {
65
+ jsonrpc: "2.0";
66
+ id: string | number | null;
67
+ result?: unknown;
68
+ error?: {
69
+ code: number;
70
+ message: string;
71
+ };
72
+ }
73
+ /** Server identity advertised during `initialize`. */
74
+ interface ServerInfo {
75
+ readonly name: string;
76
+ readonly version: string;
77
+ }
78
+ /**
79
+ * Tools-only MCP server bound to a {@link Blursec} client.
80
+ *
81
+ * Stateless by design: messages are handled independently, so a host
82
+ * that re-initializes or probes out of order never wedges the server.
83
+ *
84
+ * @example Wire to any line-based transport
85
+ * ```ts
86
+ * const server = new BlursecMcpServer(createBlursecFromEnv());
87
+ * rl.on("line", async (line) => {
88
+ * const reply = await server.handleLine(line);
89
+ * if (reply !== undefined) process.stdout.write(reply + "\n");
90
+ * });
91
+ * ```
92
+ */
93
+ declare class BlursecMcpServer {
94
+ private readonly client;
95
+ private readonly info;
96
+ constructor(client: Blursec, info?: ServerInfo);
97
+ /**
98
+ * Handle one newline-delimited JSON-RPC message and return the
99
+ * serialized reply, or `undefined` for notifications (which per
100
+ * JSON-RPC must not be answered).
101
+ */
102
+ handleLine(line: string): Promise<string | undefined>;
103
+ /**
104
+ * Handle one already-parsed JSON-RPC message.
105
+ *
106
+ * Returns `undefined` for notifications; a {@link JsonRpcResponse}
107
+ * for requests (including error responses — this method never throws).
108
+ */
109
+ handleMessage(message: unknown): Promise<JsonRpcResponse | undefined>;
110
+ private initializeResult;
111
+ private toolsCall;
112
+ private ok;
113
+ private err;
114
+ }
115
+
116
+ /**
117
+ * Blursec tool definitions + dispatcher for the MCP server.
118
+ *
119
+ * Five read-mostly tools wrapping the SDK surface. Deliberately
120
+ * excluded: `auth.rotate` (immediately invalidates the live API key —
121
+ * far too destructive to hand to an autonomous agent).
122
+ *
123
+ * Secret-handling rules baked into every tool:
124
+ *
125
+ * - Hashing happens in *this* process via the SDK; only a 10-hex-char
126
+ * k-anonymity prefix ever reaches the Blursec API.
127
+ * - Tool results never echo back the input credential/token, so the
128
+ * secret does not get duplicated into the model context a second time.
129
+ * - Agents that can hash locally should prefer `blursec_check_hash`
130
+ * so the raw secret never enters the conversation at all.
131
+ *
132
+ * @module
133
+ */
134
+
135
+ /**
136
+ * JSON-Schema-shaped tool description, as required by the MCP
137
+ * `tools/list` response.
138
+ */
139
+ interface ToolDefinition {
140
+ readonly name: string;
141
+ readonly description: string;
142
+ readonly inputSchema: Record<string, unknown>;
143
+ }
144
+ /**
145
+ * Result of dispatching one tool call.
146
+ *
147
+ * `ok: false` means the *tool* failed (bad arguments, SDK validation,
148
+ * strict-mode API error) — per the MCP spec this is reported inside
149
+ * the result with `isError: true`, not as a protocol error, so the
150
+ * model can read the message and self-correct.
151
+ */
152
+ interface ToolCallOutcome {
153
+ readonly ok: boolean;
154
+ readonly payload: unknown;
155
+ }
156
+ /** Thrown when `tools/call` names a tool this server does not have. */
157
+ declare class UnknownToolError extends Error {
158
+ constructor(name: string);
159
+ }
160
+ /**
161
+ * The tool manifest returned by `tools/list`.
162
+ */
163
+ declare const BLURSEC_TOOLS: readonly ToolDefinition[];
164
+ /**
165
+ * Dispatch a single tool call against a {@link Blursec} client.
166
+ *
167
+ * Argument errors and SDK errors are folded into `ok: false` outcomes
168
+ * (the model can read them); only {@link UnknownToolError} escapes —
169
+ * that one is the *caller's* protocol bug and becomes a JSON-RPC error.
170
+ */
171
+ declare function callBlursecTool(client: Blursec, name: string, args: unknown): Promise<ToolCallOutcome>;
172
+
173
+ export { BLURSEC_TOOLS, BlursecMcpServer, JSONRPC_ERRORS, type JsonRpcResponse, SUPPORTED_PROTOCOL_VERSIONS, type ServerInfo, type ToolCallOutcome, type ToolDefinition, UnknownToolError, callBlursecTool, createBlursecFromEnv };
package/dist/index.js ADDED
@@ -0,0 +1,368 @@
1
+ import { BlursecValidationError, Blursec } from '@blursec/sdk';
2
+
3
+ // src/env.ts
4
+ var PLATFORM_HEADER = "X-Blursec-Platform";
5
+ function resolveEnvironment(value) {
6
+ switch (value) {
7
+ case "production":
8
+ case "staging":
9
+ case "local":
10
+ return value;
11
+ default:
12
+ return "production";
13
+ }
14
+ }
15
+ function readEnvString(name) {
16
+ const value = process.env[name];
17
+ if (typeof value !== "string") return void 0;
18
+ const trimmed = value.trim();
19
+ return trimmed.length > 0 ? trimmed : void 0;
20
+ }
21
+ function createBlursecFromEnv(overrides) {
22
+ const apiKey = overrides?.apiKey ?? readEnvString("BLURSEC_API_KEY");
23
+ if (typeof apiKey !== "string" || apiKey.trim().length === 0) {
24
+ throw new BlursecValidationError(
25
+ "blursec-mcp: BLURSEC_API_KEY is not set. Add it to the `env` block of your MCP host configuration (never pass API keys as prompt text)."
26
+ );
27
+ }
28
+ const environment = overrides?.environment ?? resolveEnvironment(readEnvString("BLURSEC_ENVIRONMENT"));
29
+ const mergedHeaders = {
30
+ [PLATFORM_HEADER]: "mcp",
31
+ ...overrides?.defaultHeaders ?? {}
32
+ };
33
+ return new Blursec({
34
+ ...overrides,
35
+ apiKey,
36
+ environment,
37
+ defaultHeaders: mergedHeaders
38
+ });
39
+ }
40
+ var UnknownToolError = class extends Error {
41
+ constructor(name) {
42
+ super(`Unknown tool: ${name}`);
43
+ this.name = "UnknownToolError";
44
+ Object.setPrototypeOf(this, new.target.prototype);
45
+ }
46
+ };
47
+ var CONTEXT_SCHEMA = {
48
+ type: "object",
49
+ description: "Optional request context forwarded to Blursec Layer-3 risk scoring.",
50
+ properties: {
51
+ ip: { type: "string", description: "Client IP address." },
52
+ userAgent: { type: "string", description: "Raw User-Agent header." },
53
+ deviceFingerprint: { type: "string", description: "Stable device fingerprint." },
54
+ country: { type: "string", description: "ISO 3166-1 alpha-2 country code." },
55
+ userId: { type: "string", description: "User id, if known at check time." }
56
+ },
57
+ additionalProperties: false
58
+ };
59
+ var BLURSEC_TOOLS = [
60
+ {
61
+ name: "blursec_check_credential",
62
+ description: "Check an email + password pair against the live Blursec stealer-log database using k-anonymity (only a 10-character SHA-256 prefix leaves this machine; the raw password is hashed locally and never transmitted or echoed back). Returns leaked status, severity, and a recommended action (allow / monitor / force_reset / kill_sessions / block). If you already have the SHA-256 digest, prefer blursec_check_hash so the raw secret never enters the conversation.",
63
+ inputSchema: {
64
+ type: "object",
65
+ properties: {
66
+ email: { type: "string", description: "The account email address." },
67
+ password: { type: "string", description: "The password to check." },
68
+ context: CONTEXT_SCHEMA
69
+ },
70
+ required: ["email", "password"],
71
+ additionalProperties: false
72
+ }
73
+ },
74
+ {
75
+ name: "blursec_check_token",
76
+ description: "Check a session token / cookie value against the Blursec stealer-log database. Same k-anonymity model as blursec_check_credential \u2014 the token is hashed locally and only a 10-character prefix is transmitted.",
77
+ inputSchema: {
78
+ type: "object",
79
+ properties: {
80
+ sessionToken: {
81
+ type: "string",
82
+ description: "The raw session token or cookie value."
83
+ },
84
+ context: CONTEXT_SCHEMA
85
+ },
86
+ required: ["sessionToken"],
87
+ additionalProperties: false
88
+ }
89
+ },
90
+ {
91
+ name: "blursec_check_hash",
92
+ description: "Check a pre-computed SHA-256 hex digest (64 characters) against the Blursec stealer-log database. The most privacy-preserving tool: the raw credential never needs to appear anywhere in the conversation. For email+password pairs the digest must be SHA-256(lowercase(trim(email)) + ':' + password).",
93
+ inputSchema: {
94
+ type: "object",
95
+ properties: {
96
+ sha256: {
97
+ type: "string",
98
+ description: "64-character SHA-256 hex digest of the credential.",
99
+ pattern: "^[0-9a-fA-F]{64}$"
100
+ },
101
+ context: CONTEXT_SCHEMA
102
+ },
103
+ required: ["sha256"],
104
+ additionalProperties: false
105
+ }
106
+ },
107
+ {
108
+ name: "blursec_verify_session",
109
+ description: "Verify a live session token against the stealer-log database. Like blursec_check_token but auto-detects the token shape (JWTs are hashed by signature segment only) and additionally returns tokenType and a compromised flag. Use during incident response to triage active sessions.",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {
113
+ sessionToken: {
114
+ type: "string",
115
+ description: "The raw session token (opaque or JWT)."
116
+ }
117
+ },
118
+ required: ["sessionToken"],
119
+ additionalProperties: false
120
+ }
121
+ },
122
+ {
123
+ name: "blursec_whoami",
124
+ description: "Return the principal the configured Blursec API key is authenticated as (id, name, workspace, scopes). Use to verify connectivity and key scope before running checks.",
125
+ inputSchema: {
126
+ type: "object",
127
+ properties: {},
128
+ additionalProperties: false
129
+ }
130
+ }
131
+ ];
132
+ async function callBlursecTool(client, name, args) {
133
+ try {
134
+ switch (name) {
135
+ case "blursec_check_credential": {
136
+ const bag = requireArgsObject(args);
137
+ const email = requireString(bag, "email");
138
+ const password = requireString(bag, "password");
139
+ const result = await client.credentials.check(
140
+ email,
141
+ password,
142
+ checkOptions(bag)
143
+ );
144
+ return { ok: true, payload: result };
145
+ }
146
+ case "blursec_check_token": {
147
+ const bag = requireArgsObject(args);
148
+ const sessionToken = requireString(bag, "sessionToken");
149
+ const result = await client.credentials.checkToken(
150
+ sessionToken,
151
+ checkOptions(bag)
152
+ );
153
+ return { ok: true, payload: result };
154
+ }
155
+ case "blursec_check_hash": {
156
+ const bag = requireArgsObject(args);
157
+ const sha256 = requireString(bag, "sha256");
158
+ const result = await client.credentials.checkHash(
159
+ sha256,
160
+ checkOptions(bag)
161
+ );
162
+ return { ok: true, payload: result };
163
+ }
164
+ case "blursec_verify_session": {
165
+ const bag = requireArgsObject(args);
166
+ const sessionToken = requireString(bag, "sessionToken");
167
+ const result = await client.sessions.verify(sessionToken);
168
+ return { ok: true, payload: result };
169
+ }
170
+ case "blursec_whoami": {
171
+ const result = await client.auth.whoami();
172
+ return { ok: true, payload: result };
173
+ }
174
+ default:
175
+ throw new UnknownToolError(name);
176
+ }
177
+ } catch (err) {
178
+ if (err instanceof UnknownToolError) throw err;
179
+ return {
180
+ ok: false,
181
+ payload: {
182
+ error: err instanceof Error ? err.message : String(err),
183
+ type: err instanceof Error ? err.name : "UnknownError"
184
+ }
185
+ };
186
+ }
187
+ }
188
+ function requireArgsObject(args) {
189
+ if (typeof args !== "object" || args === null || Array.isArray(args)) {
190
+ throw new BlursecValidationError(
191
+ "tool arguments must be a JSON object."
192
+ );
193
+ }
194
+ return args;
195
+ }
196
+ function requireString(bag, key) {
197
+ const value = bag[key];
198
+ if (typeof value !== "string" || value.length === 0) {
199
+ throw new BlursecValidationError(
200
+ `missing or empty required string argument: ${key}`
201
+ );
202
+ }
203
+ return value;
204
+ }
205
+ function checkOptions(bag) {
206
+ const raw = bag["context"];
207
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
208
+ return void 0;
209
+ }
210
+ const source = raw;
211
+ const context = {};
212
+ for (const key of [
213
+ "ip",
214
+ "userAgent",
215
+ "deviceFingerprint",
216
+ "country",
217
+ "userId"
218
+ ]) {
219
+ const value = source[key];
220
+ if (typeof value === "string" && value.length > 0) {
221
+ context[key] = value;
222
+ }
223
+ }
224
+ return Object.keys(context).length > 0 ? { context } : void 0;
225
+ }
226
+
227
+ // src/server.ts
228
+ var SUPPORTED_PROTOCOL_VERSIONS = [
229
+ "2025-06-18",
230
+ "2025-03-26",
231
+ "2024-11-05"
232
+ ];
233
+ var JSONRPC_ERRORS = {
234
+ PARSE_ERROR: -32700,
235
+ INVALID_REQUEST: -32600,
236
+ METHOD_NOT_FOUND: -32601,
237
+ INVALID_PARAMS: -32602
238
+ };
239
+ var DEFAULT_SERVER_INFO = {
240
+ name: "blursec-mcp",
241
+ version: "1.0.0"
242
+ };
243
+ var BlursecMcpServer = class {
244
+ client;
245
+ info;
246
+ constructor(client, info = DEFAULT_SERVER_INFO) {
247
+ this.client = client;
248
+ this.info = info;
249
+ }
250
+ /**
251
+ * Handle one newline-delimited JSON-RPC message and return the
252
+ * serialized reply, or `undefined` for notifications (which per
253
+ * JSON-RPC must not be answered).
254
+ */
255
+ async handleLine(line) {
256
+ let message;
257
+ try {
258
+ message = JSON.parse(line);
259
+ } catch {
260
+ return JSON.stringify({
261
+ jsonrpc: "2.0",
262
+ id: null,
263
+ error: { code: JSONRPC_ERRORS.PARSE_ERROR, message: "Parse error" }
264
+ });
265
+ }
266
+ const response = await this.handleMessage(message);
267
+ return response === void 0 ? void 0 : JSON.stringify(response);
268
+ }
269
+ /**
270
+ * Handle one already-parsed JSON-RPC message.
271
+ *
272
+ * Returns `undefined` for notifications; a {@link JsonRpcResponse}
273
+ * for requests (including error responses — this method never throws).
274
+ */
275
+ async handleMessage(message) {
276
+ if (!isRequestShaped(message)) {
277
+ return {
278
+ jsonrpc: "2.0",
279
+ id: null,
280
+ error: {
281
+ code: JSONRPC_ERRORS.INVALID_REQUEST,
282
+ message: "Invalid Request"
283
+ }
284
+ };
285
+ }
286
+ const { id, method, params } = message;
287
+ const isNotification = id === void 0;
288
+ if (isNotification) return void 0;
289
+ switch (method) {
290
+ case "initialize":
291
+ return this.ok(id, this.initializeResult(params));
292
+ case "ping":
293
+ return this.ok(id, {});
294
+ case "tools/list":
295
+ return this.ok(id, { tools: BLURSEC_TOOLS });
296
+ case "tools/call":
297
+ return this.toolsCall(id, params);
298
+ default:
299
+ return this.err(
300
+ id,
301
+ JSONRPC_ERRORS.METHOD_NOT_FOUND,
302
+ `Method not found: ${method}`
303
+ );
304
+ }
305
+ }
306
+ // ── handlers ────────────────────────────────────────────────────────
307
+ initializeResult(params) {
308
+ const requested = typeof params === "object" && params !== null && typeof params["protocolVersion"] === "string" ? params["protocolVersion"] : void 0;
309
+ const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requested ?? "") ? requested : SUPPORTED_PROTOCOL_VERSIONS[0];
310
+ return {
311
+ protocolVersion,
312
+ capabilities: { tools: {} },
313
+ serverInfo: this.info,
314
+ instructions: "Blursec credential-leak threat intel. All check tools hash locally and transmit only a 10-character k-anonymity prefix. Prefer blursec_check_hash when a SHA-256 digest is available so raw secrets stay out of the conversation. Results with failedOpen=true mean the database was unreachable \u2014 treat as 'unknown', never as 'safe'."
315
+ };
316
+ }
317
+ async toolsCall(id, params) {
318
+ if (typeof params !== "object" || params === null || typeof params["name"] !== "string") {
319
+ return this.err(
320
+ id,
321
+ JSONRPC_ERRORS.INVALID_PARAMS,
322
+ "tools/call requires params.name (string)"
323
+ );
324
+ }
325
+ const name = params["name"];
326
+ const args = params["arguments"] ?? {};
327
+ try {
328
+ const outcome = await callBlursecTool(this.client, name, args);
329
+ return this.ok(id, {
330
+ content: [
331
+ {
332
+ type: "text",
333
+ text: JSON.stringify(outcome.payload, null, 2)
334
+ }
335
+ ],
336
+ isError: !outcome.ok
337
+ });
338
+ } catch (err) {
339
+ if (err instanceof UnknownToolError) {
340
+ return this.err(id, JSONRPC_ERRORS.INVALID_PARAMS, err.message);
341
+ }
342
+ return this.err(
343
+ id,
344
+ JSONRPC_ERRORS.INVALID_REQUEST,
345
+ err instanceof Error ? err.message : String(err)
346
+ );
347
+ }
348
+ }
349
+ // ── reply builders ──────────────────────────────────────────────────
350
+ ok(id, result) {
351
+ return { jsonrpc: "2.0", id, result };
352
+ }
353
+ err(id, code, message) {
354
+ return { jsonrpc: "2.0", id, error: { code, message } };
355
+ }
356
+ };
357
+ function isRequestShaped(message) {
358
+ if (typeof message !== "object" || message === null) return false;
359
+ const m = message;
360
+ if (m["jsonrpc"] !== "2.0") return false;
361
+ if (typeof m["method"] !== "string") return false;
362
+ const id = m["id"];
363
+ return id === void 0 || id === null || typeof id === "string" || typeof id === "number";
364
+ }
365
+
366
+ export { BLURSEC_TOOLS, BlursecMcpServer, JSONRPC_ERRORS, SUPPORTED_PROTOCOL_VERSIONS, UnknownToolError, callBlursecTool, createBlursecFromEnv };
367
+ //# sourceMappingURL=index.js.map
368
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@blursec/mcp",
3
+ "version": "1.0.0",
4
+ "description": "Official Model Context Protocol (MCP) server for @blursec/sdk — lets AI agents check credentials, tokens, and sessions against live stealer-log threat intel.",
5
+ "license": "MIT",
6
+ "author": "Blursec",
7
+ "homepage": "https://github.com/blur-sec/blursec-sdk/tree/main/packages/mcp#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/blur-sec/blursec-sdk.git",
11
+ "directory": "packages/mcp"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/blur-sec/blursec-sdk/issues"
15
+ },
16
+ "type": "module",
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "bin": {
21
+ "blursec-mcp": "./dist/cli.js"
22
+ },
23
+ "exports": {
24
+ ".": {
25
+ "import": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "require": {
30
+ "types": "./dist/index.d.cts",
31
+ "default": "./dist/index.cjs"
32
+ },
33
+ "default": "./dist/index.js"
34
+ },
35
+ "./package.json": "./package.json"
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "!dist/**/*.map",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "sideEffects": false,
44
+ "engines": {
45
+ "node": ">=18"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup",
49
+ "dev": "tsup --watch",
50
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest",
53
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
54
+ },
55
+ "keywords": [
56
+ "blursec",
57
+ "mcp",
58
+ "model-context-protocol",
59
+ "ai-agent",
60
+ "credential-leak",
61
+ "stealer-logs",
62
+ "threat-intel",
63
+ "security"
64
+ ],
65
+ "peerDependencies": {
66
+ "@blursec/sdk": "^1.0.0"
67
+ },
68
+ "devDependencies": {
69
+ "@blursec/sdk": "file:../..",
70
+ "@types/node": "^22.0.0",
71
+ "tsup": "^8.0.0",
72
+ "typescript": "^5.4.0",
73
+ "vitest": "^1.6.0"
74
+ }
75
+ }