@techsologic/unolock-agent-mcp 0.1.11

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,390 @@
1
+ # UnoLock Agent MCP
2
+
3
+ This repository is the dedicated home for UnoLock's Python agent/MCP client.
4
+
5
+ UnoLock Agent MCP is currently in alpha. It is available for evaluation and early testing, but it is not ready for broad production rollout yet.
6
+
7
+ ## Security Requirement
8
+
9
+ UnoLock Agent MCP is built for customers who want the strongest practical protection for AI-accessed secrets.
10
+
11
+ For normal customer use, the strongest deployment uses a production-ready:
12
+
13
+ * TPM
14
+ * vTPM
15
+ * Secure Enclave
16
+ * or equivalent platform-backed non-exportable key store
17
+
18
+ If the host cannot provide one of those, the MCP can still fall back to a lower-assurance software provider. When that happens, the MCP reports the reduced assurance clearly, requires an explicit acknowledgment before use, and makes the reduced-assurance tradeoff visible instead of pretending it met UnoLock's preferred key-storage requirements.
19
+
20
+ That tradeoff is intentional. Agentic Safe Access exists to keep AI access as close as possible to UnoLock's normal device-bound security model without pretending every host can satisfy the same storage guarantees.
21
+
22
+ ## Intended Environment
23
+
24
+ UnoLock Agent MCP is designed to work across a wide range of agent environments.
25
+
26
+ The strongest deployments are environments that can provide device-bound, non-exportable key storage in a normal user-controlled session. That includes:
27
+
28
+ * desktop AI assistants
29
+ * local MCP hosts such as Claude Desktop or Cursor
30
+ * user-controlled workstations, laptops, and VMs with TPM/vTPM access
31
+ * macOS hosts that can use either Secure Enclave or a non-exportable Keychain-backed key
32
+ * Windows or WSL hosts that can use either TPM-backed keys or the non-exportable Windows CNG fallback
33
+
34
+ Other environments may still work, but they may only be able to provide lower assurance. That commonly includes:
35
+
36
+ * fully headless background agents
37
+ * remote sandboxes
38
+ * plain containers without hardware-backed key access
39
+
40
+ These environments are harder to support because they often cannot satisfy UnoLock's preferred requirement for device-bound, non-exportable key storage in a normal user-controlled session.
41
+
42
+ Official GitHub repository:
43
+
44
+ * `https://github.com/TechSologic/unolock-agent-mcp`
45
+ * Releases: `https://github.com/TechSologic/unolock-agent-mcp/releases`
46
+
47
+ Recommended customer install source:
48
+
49
+ * GitHub Releases binaries
50
+ * `npx @techsologic/unolock-agent-mcp` as the Node/npm wrapper path
51
+ * `pipx install` as the fallback source install path when no release binary is available yet
52
+
53
+ If you are new to UnoLock itself, start with these docs first:
54
+
55
+ * UnoLock Knowledge Base: `https://docs.unolock.com/index.html`
56
+ * Agentic Safe Access: `https://docs.unolock.com/features/agentic-safe-access.html`
57
+ * Access Keys & Safe Access: `https://docs.unolock.com/features/multi-device-access.html`
58
+ * Spaces: `https://docs.unolock.com/features/spaces.html`
59
+ * Connect an AI Agent to a Safe: `https://docs.unolock.com/howto/connecting-an-ai-agent.html`
60
+
61
+ The current MCP proves the hardest integration seam first:
62
+
63
+ * live local `/start` flow compatibility
64
+ * ML-DSA signature verification
65
+ * ML-KEM encapsulation
66
+ * AES-GCM callback decryption
67
+
68
+ The agent client does not create Safes.
69
+
70
+ Safe creation remains a human/browser responsibility, matching the product model:
71
+
72
+ * human admin creates a Safe
73
+ * human admin creates an agent access key for that Safe
74
+ * MCP registers to the existing Safe
75
+ * MCP later authenticates and uses the shared Safe API surface
76
+
77
+ ## Quick start
78
+
79
+ Run this from the repo root after the local server is up on `http://127.0.0.1:3000`:
80
+
81
+ ```bash
82
+ ./scripts/bootstrap.sh
83
+ ./scripts/run_local_probe.sh
84
+ ./scripts/run_stdio_mcp.sh
85
+ ./scripts/run_local_e2e_readonly.sh
86
+ ```
87
+
88
+ For real MCP hosts, see:
89
+
90
+ * [Install Guide](docs/install.md)
91
+ * [macOS Quick Start](docs/macos.md)
92
+ * [Supported Environments](docs/supported-environments.md)
93
+ * [MCP Host Config](docs/host-config.md)
94
+ * [Support Matrix](docs/support-matrix.md)
95
+ * [Tool Catalog](docs/tool-catalog.md)
96
+ * [Claude Desktop example](examples/claude-desktop-config.json)
97
+ * [Cursor example](examples/cursor-mcp.json)
98
+ * [Config file example](examples/unolock-agent-config.json)
99
+
100
+ If you prefer manual install from source:
101
+
102
+ ```bash
103
+ git clone https://github.com/TechSologic/unolock-agent-mcp.git
104
+ cd unolock-agent-mcp
105
+ python3 -m pip install --user -e .
106
+ unolock-agent-probe probe
107
+ unolock-agent-mcp mcp
108
+ python3 -m unolock_mcp bootstrap --connection-url '<unoLock connection url>' --pin 0123 --list-records
109
+ python3 -m unolock_mcp tpm-diagnose
110
+ unolock-agent-tpm-check
111
+ unolock-agent-self-test
112
+ python3 -m unolock_mcp config-check
113
+ ```
114
+
115
+ macOS support is still alpha. The MCP now prefers Secure Enclave when it works cleanly and otherwise falls back to a non-exportable macOS Keychain key for broader compatibility. If you are evaluating it on Apple Silicon, start with:
116
+
117
+ * [macOS Quick Start](docs/macos.md)
118
+
119
+ The first `liboqs-python` run may build or locate `liboqs` under your home directory. That can take a few minutes.
120
+
121
+ For the best customer experience, prefer GitHub Release binaries over source installs. Source installs still depend on the local `liboqs-python` / `liboqs` environment.
122
+
123
+ ## Preferred Customer Install
124
+
125
+ When available, prefer the standalone GitHub Release binaries instead of installing from Git.
126
+
127
+ That avoids most of the Python packaging and source-build overhead for customers.
128
+
129
+ If your host environment is already Node/npm-oriented, you can also use the npm wrapper:
130
+
131
+ ```bash
132
+ npx @techsologic/unolock-agent-mcp --version
133
+ ```
134
+
135
+ The wrapper downloads the correct GitHub Release binary for the current platform on first use and then reuses the cached copy.
136
+
137
+ ## Standalone config
138
+
139
+ When the MCP runs outside the main UnoLock monorepo, it can usually derive its UnoLock runtime config from the UnoLock agent key connection URL. Environment variables and config files are primarily for overrides and custom deployments.
140
+
141
+ Default config file location:
142
+
143
+ ```text
144
+ ~/.config/unolock-agent-mcp/config.json
145
+ ```
146
+
147
+ Override example:
148
+
149
+ ```json
150
+ {
151
+ "base_url": "https://api.unolock.example",
152
+ "transparency_origin": "https://safe.unolock.example",
153
+ "app_version": "1.2.3",
154
+ "signing_public_key_b64": "BASE64_SERVER_PQ_SIGNING_PUBLIC_KEY"
155
+ }
156
+ ```
157
+
158
+ For the standard hosted UnoLock deployment, the MCP can derive the API origin, UnoLock app version, and PQ validation key from the user-provided agent key connection URL automatically. If you want to force the same hosted deployment without waiting for a connection URL, this also works:
159
+
160
+ ```json
161
+ {
162
+ "base_url": "https://api.safe.unolock.com"
163
+ }
164
+ ```
165
+
166
+ the MCP will derive `https://safe.unolock.com`, fetch `/unolock-client.json`, and read the published app version and `serverPQValidationKey`. If that hosted file is unavailable, it falls back to the transparency bundle.
167
+
168
+ Use this command to verify what the MCP resolved:
169
+
170
+ ```bash
171
+ python3 -m unolock_mcp config-check
172
+ ```
173
+
174
+ Versioning is intentionally split:
175
+
176
+ * MCP package version: the version of `unolock-agent-mcp` itself
177
+ * UnoLock app version: the Safe client/server compatibility version sent as `x-app-version`
178
+
179
+ For the standard hosted UnoLock deployment, the MCP resolves the UnoLock app version from the hosted client metadata rather than reusing the MCP package version.
180
+
181
+ TPM provider selection:
182
+
183
+ * default: `UNOLOCK_TPM_PROVIDER=auto`
184
+ * force software provider: `UNOLOCK_TPM_PROVIDER=software`
185
+ * force Linux TPM/vTPM provider: `UNOLOCK_TPM_PROVIDER=linux`
186
+ * force best macOS provider: `UNOLOCK_TPM_PROVIDER=mac`
187
+ * force best Windows provider: `UNOLOCK_TPM_PROVIDER=windows`
188
+
189
+ On WSL2, `auto` now prefers the Windows TPM helper provider when `powershell.exe` can create TPM-backed keys on the Windows host, and falls back to a non-exportable Windows CNG key when TPM-backed creation is unavailable. This has been validated locally with live registration and authentication. If neither Windows path works, `auto` falls back to the software provider with loud reduced-assurance warnings.
190
+
191
+ On macOS, `auto` now tries the Secure Enclave provider first and then falls back to a non-exportable Keychain-backed provider. Secure Enclave remains the higher-assurance path, but the Keychain path is there to reduce launch-context friction on real Macs.
192
+
193
+ ## Current capabilities
194
+
195
+ The working path today is the local probe:
196
+
197
+ * GET `/start?type=access`
198
+ * verify `PQ_KEY_EXCHANGE`
199
+ * verify the server ML-DSA signature
200
+ * encapsulate to the server ML-KEM public key
201
+ * POST the PQ callback response
202
+ * decrypt the next encrypted callback
203
+
204
+ On the current local stack this is already returning the next callback successfully.
205
+
206
+ The package now also exposes a real stdio MCP server with:
207
+
208
+ * in-memory UnoLock auth-flow session store
209
+ * generic `/start` flow bootstrap after PQ negotiation
210
+ * generic flow continuation
211
+ * generic authenticated `/api` action calls
212
+ * convenience wrappers for `GetSpaces` and `GetArchives`
213
+ * note and checklist projection from UnoLock `Records` archives
214
+ * write-support MVP for notes and checklists with version-aware conflict handling
215
+
216
+ Installed commands:
217
+
218
+ * `unolock-agent-probe`
219
+ * run the packaged local probe
220
+ * `unolock-agent-mcp`
221
+ * run the stdio MCP server
222
+ * `unolock-agent-tpm-check`
223
+ * run the fail-fast production-readiness TPM check
224
+
225
+ Current MCP tools:
226
+
227
+ * `unolock_probe_local_server`
228
+ * `unolock_get_registration_status`
229
+ * `unolock_get_tpm_diagnostics`
230
+ * `unolock_set_agent_pin`
231
+ * `unolock_clear_agent_pin`
232
+ * `unolock_submit_connection_url`
233
+ * `unolock_clear_connection_url`
234
+ * `unolock_start_registration_from_connection_url`
235
+ * `unolock_continue_agent_session`
236
+ * `unolock_authenticate_agent`
237
+ * `unolock_bootstrap_agent`
238
+ * `unolock_start_flow`
239
+ * `unolock_continue_flow`
240
+ * `unolock_get_session`
241
+ * `unolock_list_sessions`
242
+ * `unolock_delete_session`
243
+ * `unolock_call_api`
244
+ * `unolock_get_spaces`
245
+ * `unolock_get_archives`
246
+ * `unolock_list_spaces`
247
+ * `unolock_list_records`
248
+ * `unolock_list_notes`
249
+ * `unolock_list_checklists`
250
+ * `unolock_get_record`
251
+ * `unolock_create_note`
252
+ * `unolock_update_note`
253
+ * `unolock_rename_record`
254
+ * `unolock_create_checklist`
255
+ * `unolock_set_checklist_item_done`
256
+ * `unolock_add_checklist_item`
257
+ * `unolock_remove_checklist_item`
258
+
259
+ Registration discovery support:
260
+
261
+ * the MCP can report whether it is registered
262
+ * if not registered, it tells the agent to ask the user for the UnoLock agent key connection URL
263
+ * that URL is explicitly treated as one-time-use and enrollment-only
264
+ * in the cold-start path, the MCP now prefers that the agent ask for the connection URL and the optional PIN together
265
+ * the agent key connection URL can be submitted and stored locally
266
+ * `unolock_submit_agent_bootstrap` can submit the connection URL and optional PIN in one step
267
+ * the optional agent PIN is held only in MCP process memory and cleared on restart or via `unolock_clear_agent_pin`
268
+ * the MCP can now auto-drive `agentRegister` and `agentAccess` through known callbacks using the active TPM DAO
269
+ * the Windows TPM helper provider is now usable from WSL2 when `powershell.exe` can reach the Windows Platform Crypto Provider
270
+ * the Windows CNG non-exportable fallback provider is now also usable from Windows/WSL when TPM-backed creation is unavailable
271
+ * the software provider is the final fallback when the host cannot provide a production-grade provider, and the MCP surfaces that reduced assurance clearly
272
+ * once authenticated, the MCP can read UnoLock notes/checklists and project them into plain-text agent-friendly DTOs while keeping the stored Quill/checklist formats unchanged
273
+ * the MCP can now create notes and checklists and perform version-aware note/checklist updates within the agent's allowed Spaces
274
+ * registration status now reports a `recommended_next_action` and `guidance` field so an agent can tell whether it should ask for an agent key URL, ask for a PIN, start registration, or authenticate
275
+ * after the MCP process restarts, the agent stays registered but must ask the user for the PIN again before re-authenticating
276
+ * registration state now remembers which TPM provider created the agent key and will tell the host to re-register or force the old provider if there is a provider mismatch
277
+ * the MCP can diagnose the active TPM/vTPM provider and give host advice when no working TPM/vTPM is detected
278
+
279
+ Read and write support:
280
+
281
+ * `unolock_list_records` accepts `kind`, `space_id`, `pinned`, and `label`
282
+ * `unolock_list_notes` and `unolock_list_checklists` are convenience wrappers
283
+ * `unolock_list_spaces` returns space metadata plus record counts
284
+ * read/list/get responses include `writable`, `allowed_operations`, `version`, `read_only`, and `locked`
285
+ * write tools use cache-first optimistic writes with 5-minute in-memory archive TTLs
286
+ * archive rereads happen only on cache miss, cache expiry, or upload conflict
287
+ * write conflicts return stable structured reasons such as `write_conflict_requires_reread`
288
+
289
+ Current bootstrap limitation:
290
+
291
+ * to finish `DecodeKey` and `ClientDataKey`, the MCP still needs the bootstrap AIDK material for the access
292
+ * if the connection URL does not include that bootstrap secret, the MCP will stop with a clear blocker instead of faking progress
293
+ * that keeps the implementation aligned with UnoLock's current AIDK/CDMK hierarchy instead of bypassing it
294
+
295
+ ## Testing with a local Safe
296
+
297
+ When you need a real Safe for local testing, use the UnoLock browser Playwright harness from a full UnoLock checkout under `client/e2e-playwright`.
298
+
299
+ That harness already covers:
300
+
301
+ * Safe creation
302
+ * virtual WebAuthn registration in Chromium
303
+ * Safe open/lifecycle flows
304
+
305
+ This keeps the boundary clean:
306
+
307
+ * browser tests create and manage test Safes
308
+ * `agent-mcp` only probes or authenticates against an existing Safe
309
+
310
+ For local agent bootstrap, the create-safe harness can now emit a registration artifact:
311
+
312
+ ```bash
313
+ E2E_AGENT_BOOTSTRAP_OUTPUT_FILE=/tmp/unolock-agent-bootstrap.json \
314
+ npm --prefix client/e2e-playwright run test:create-safe
315
+ cat /tmp/unolock-agent-bootstrap.json
316
+ ```
317
+
318
+ That artifact includes:
319
+
320
+ * the generated UnoLock agent key connection URL
321
+ * the access ID used for the agent registration
322
+ * the bootstrap secret encoding needed by the MCP
323
+ * whether the browser had to fall back to the current access because the Safe tier could not create another device access
324
+ * with the default Playwright settings, the local test PIN is `0123`
325
+
326
+ Current local fallback behavior:
327
+
328
+ * if the Safe tier permits another device access, the harness creates a dedicated AI-marked access
329
+ * if the tier blocks new device accesses, the harness issues an agent registration URL for the current authenticated access instead
330
+ * the current-access fallback exists for local/dev testing and is not the preferred long-term product shape
331
+
332
+ For a full local regression run:
333
+
334
+ ```bash
335
+ ./scripts/run_local_e2e_readonly.sh
336
+ ```
337
+
338
+ That script:
339
+
340
+ * creates a fresh local Safe and agent bootstrap artifact with Playwright
341
+ * registers the MCP against that new agent key
342
+ * authenticates and reads spaces/records
343
+ * simulates an MCP restart
344
+ * re-authenticates with the PIN and verifies access again
345
+
346
+ ## Package layout
347
+
348
+ ```text
349
+ unolock-agent-mcp/
350
+ docs/
351
+ scripts/
352
+ src/
353
+ unolock_mcp/
354
+ api/
355
+ auth/
356
+ crypto/
357
+ domain/
358
+ mcp/
359
+ tpm/
360
+ transport/
361
+ tests/
362
+ ```
363
+
364
+ ## Separation of concerns
365
+
366
+ * `src/unolock_mcp/tpm/`
367
+ * TPM DAO and provider implementations
368
+ * `src/unolock_mcp/crypto/`
369
+ * PQ session negotiation
370
+ * callback AES-GCM helpers
371
+ * AWS Encryption SDK helpers
372
+ * Safe keyring management
373
+ * `src/unolock_mcp/transport/`
374
+ * `/start` and `/api` HTTP transport
375
+ * callback DTO handling
376
+ * `src/unolock_mcp/auth/`
377
+ * agent registration and access clients
378
+ * local compatibility probe
379
+ * generic flow session store
380
+ * `src/unolock_mcp/api/`
381
+ * authenticated Safe API client
382
+ * `src/unolock_mcp/domain/`
383
+ * domain objects and DTOs
384
+ * `src/unolock_mcp/mcp/`
385
+ * MCP tool surface only
386
+
387
+ ## Notes
388
+
389
+ * Server-side interop probes can still live under `server/safe-server/scripts/` when they are validating server behavior directly.
390
+ * Production agent auth is intended to use TPM/vTPM or equivalent device-backed storage. The software provider is the lower-assurance fallback when stronger host key protection is not available.
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+ const path = require("path");
7
+ const https = require("https");
8
+ const { spawn } = require("child_process");
9
+
10
+ const PACKAGE_VERSION = "0.1.11";
11
+ const REPO = "TechSologic/unolock-agent-mcp";
12
+
13
+ function platformAssetInfo() {
14
+ const platform = process.platform;
15
+ const arch = process.arch;
16
+ if (platform === "linux" && arch === "x64") {
17
+ return { asset: "unolock-agent-mcp-linux-x86_64", executable: "unolock-agent-mcp-linux-x86_64" };
18
+ }
19
+ if (platform === "darwin" && arch === "arm64") {
20
+ return { asset: "unolock-agent-mcp-macos-arm64", executable: "unolock-agent-mcp-macos-arm64" };
21
+ }
22
+ if (platform === "darwin" && arch === "x64") {
23
+ return { asset: "unolock-agent-mcp-macos-x86_64", executable: "unolock-agent-mcp-macos-x86_64" };
24
+ }
25
+ if (platform === "win32" && arch === "x64") {
26
+ return { asset: "unolock-agent-mcp-windows-amd64.exe", executable: "unolock-agent-mcp-windows-amd64.exe" };
27
+ }
28
+ throw new Error(`Unsupported platform for UnoLock Agent MCP binary: ${platform}/${arch}`);
29
+ }
30
+
31
+ function cacheRoot() {
32
+ if (process.platform === "win32") {
33
+ return process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
34
+ }
35
+ return process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
36
+ }
37
+
38
+ function binaryPath() {
39
+ const { executable } = platformAssetInfo();
40
+ return path.join(cacheRoot(), "unolock-agent-mcp", PACKAGE_VERSION, executable);
41
+ }
42
+
43
+ function binaryUrl() {
44
+ if (process.env.UNOLOCK_AGENT_MCP_BINARY_URL) {
45
+ return process.env.UNOLOCK_AGENT_MCP_BINARY_URL;
46
+ }
47
+ const { asset } = platformAssetInfo();
48
+ return `https://github.com/${REPO}/releases/download/v${PACKAGE_VERSION}/${asset}`;
49
+ }
50
+
51
+ function ensureDir(dir) {
52
+ fs.mkdirSync(dir, { recursive: true, mode: 0o755 });
53
+ }
54
+
55
+ function fetchToFile(url, dest) {
56
+ return new Promise((resolve, reject) => {
57
+ const temp = `${dest}.download`;
58
+ const request = https.get(url, (response) => {
59
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
60
+ response.resume();
61
+ fetchToFile(response.headers.location, dest).then(resolve, reject);
62
+ return;
63
+ }
64
+ if (response.statusCode !== 200) {
65
+ response.resume();
66
+ reject(new Error(`Failed to download UnoLock Agent MCP binary: HTTP ${response.statusCode}`));
67
+ return;
68
+ }
69
+ const file = fs.createWriteStream(temp, { mode: 0o755 });
70
+ response.pipe(file);
71
+ file.on("finish", () => {
72
+ file.close((closeErr) => {
73
+ if (closeErr) {
74
+ reject(closeErr);
75
+ return;
76
+ }
77
+ fs.renameSync(temp, dest);
78
+ if (process.platform !== "win32") {
79
+ fs.chmodSync(dest, 0o755);
80
+ }
81
+ resolve();
82
+ });
83
+ });
84
+ file.on("error", (error) => {
85
+ file.close(() => {
86
+ try {
87
+ fs.unlinkSync(temp);
88
+ } catch {}
89
+ reject(error);
90
+ });
91
+ });
92
+ });
93
+ request.on("error", reject);
94
+ });
95
+ }
96
+
97
+ async function ensureBinary() {
98
+ const dest = binaryPath();
99
+ if (fs.existsSync(dest)) {
100
+ return dest;
101
+ }
102
+ ensureDir(path.dirname(dest));
103
+ process.stderr.write(`Downloading UnoLock Agent MCP ${PACKAGE_VERSION} for ${process.platform}/${process.arch}...\n`);
104
+ await fetchToFile(binaryUrl(), dest);
105
+ return dest;
106
+ }
107
+
108
+ async function main() {
109
+ const dest = await ensureBinary();
110
+ const child = spawn(dest, process.argv.slice(2), {
111
+ stdio: "inherit",
112
+ env: process.env
113
+ });
114
+ child.on("exit", (code, signal) => {
115
+ if (signal) {
116
+ process.kill(process.pid, signal);
117
+ return;
118
+ }
119
+ process.exit(code ?? 1);
120
+ });
121
+ child.on("error", (error) => {
122
+ process.stderr.write(`${error.message}\n`);
123
+ process.exit(1);
124
+ });
125
+ }
126
+
127
+ main().catch((error) => {
128
+ process.stderr.write(`${error.message}\n`);
129
+ process.exit(1);
130
+ });
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@techsologic/unolock-agent-mcp",
3
+ "version": "0.1.11",
4
+ "description": "npx wrapper for the official UnoLock Agent MCP release binaries",
5
+ "license": "SEE LICENSE IN README.md",
6
+ "homepage": "https://github.com/TechSologic/unolock-agent-mcp",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TechSologic/unolock-agent-mcp.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/TechSologic/unolock-agent-mcp/issues"
13
+ },
14
+ "bin": {
15
+ "unolock-agent-mcp": "./bin/unolock-agent-mcp.js"
16
+ },
17
+ "files": [
18
+ "bin/"
19
+ ],
20
+ "keywords": [
21
+ "unolock",
22
+ "mcp",
23
+ "agent",
24
+ "security",
25
+ "tpm"
26
+ ],
27
+ "engines": {
28
+ "node": ">=18"
29
+ }
30
+ }