@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 +390 -0
- package/bin/unolock-agent-mcp.js +130 -0
- package/package.json +30 -0
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
|
+
}
|