@techsologic/unolock-agent-mcp 0.1.13 → 0.1.15
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 +101 -15
- package/bin/unolock-agent-mcp.js +120 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,8 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
This repository is the dedicated home for UnoLock's Python agent/MCP client.
|
|
4
4
|
|
|
5
|
+
UnoLock was built to protect you. Now it can protect both you and your agent.
|
|
6
|
+
|
|
5
7
|
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
8
|
|
|
9
|
+
## Why Use UnoLock For An Agent
|
|
10
|
+
|
|
11
|
+
UnoLock Agent MCP is not only about protecting secrets.
|
|
12
|
+
|
|
13
|
+
It gives an agent a safer place to keep and use:
|
|
14
|
+
|
|
15
|
+
* secrets
|
|
16
|
+
* durable memory
|
|
17
|
+
* structured notes
|
|
18
|
+
* checklists
|
|
19
|
+
* space-scoped working data
|
|
20
|
+
|
|
21
|
+
Compared to local memory files or plaintext secret storage, UnoLock gives the agent:
|
|
22
|
+
|
|
23
|
+
* encrypted storage
|
|
24
|
+
* controlled access to only the Spaces it should use
|
|
25
|
+
* persistence beyond a single local machine or process
|
|
26
|
+
* safer recovery from host loss, reset, or replacement
|
|
27
|
+
* a stronger access model than reusable API keys or plaintext config secrets
|
|
28
|
+
|
|
7
29
|
## Security Requirement
|
|
8
30
|
|
|
9
31
|
UnoLock Agent MCP is built for customers who want the strongest practical protection for AI-accessed secrets.
|
|
@@ -44,10 +66,18 @@ Official GitHub repository:
|
|
|
44
66
|
* `https://github.com/TechSologic/unolock-agent-mcp`
|
|
45
67
|
* Releases: `https://github.com/TechSologic/unolock-agent-mcp/releases`
|
|
46
68
|
|
|
69
|
+
Agent-first onboarding site:
|
|
70
|
+
|
|
71
|
+
* `https://unolock.ai/index.html`
|
|
72
|
+
* `https://unolock.ai/install-mcp.html`
|
|
73
|
+
* `https://unolock.ai/connect-agent.html`
|
|
74
|
+
* `https://unolock.ai/agent-explanation-kit.html`
|
|
75
|
+
|
|
47
76
|
Recommended customer install source:
|
|
48
77
|
|
|
78
|
+
* `mcporter` keep-alive plus `npx @techsologic/unolock-agent-mcp@latest` when available
|
|
49
79
|
* GitHub Releases binaries
|
|
50
|
-
* `npx @techsologic/unolock-agent-mcp` as the Node/npm wrapper path
|
|
80
|
+
* `npx @techsologic/unolock-agent-mcp@latest` as the Node/npm wrapper path
|
|
51
81
|
* `pipx install` as the fallback source install path when no release binary is available yet
|
|
52
82
|
|
|
53
83
|
If you are new to UnoLock itself, start with these docs first:
|
|
@@ -58,6 +88,11 @@ If you are new to UnoLock itself, start with these docs first:
|
|
|
58
88
|
* Spaces: `https://docs.unolock.com/features/spaces.html`
|
|
59
89
|
* Connect an AI Agent to a Safe: `https://docs.unolock.com/howto/connecting-an-ai-agent.html`
|
|
60
90
|
|
|
91
|
+
Prerequisite:
|
|
92
|
+
|
|
93
|
+
* Free and Inheritance can share their single included Safe space with one extra Agent Key.
|
|
94
|
+
* Sovereign and HighRisk are still the right tiers for broader multi-Space and collaboration-heavy agent workflows.
|
|
95
|
+
|
|
61
96
|
The current MCP proves the hardest integration seam first:
|
|
62
97
|
|
|
63
98
|
* live local `/start` flow compatibility
|
|
@@ -72,7 +107,7 @@ Safe creation remains a human/browser responsibility, matching the product model
|
|
|
72
107
|
* human admin creates a Safe
|
|
73
108
|
* human admin creates an agent access key for that Safe
|
|
74
109
|
* MCP registers to the existing Safe
|
|
75
|
-
* MCP later authenticates and uses the shared Safe API surface
|
|
110
|
+
* MCP later authenticates and uses the shared Safe API surface for agent memory, notes, checklists, and secrets
|
|
76
111
|
|
|
77
112
|
## Quick start
|
|
78
113
|
|
|
@@ -91,12 +126,16 @@ For real MCP hosts, see:
|
|
|
91
126
|
* [macOS Quick Start](docs/macos.md)
|
|
92
127
|
* [Supported Environments](docs/supported-environments.md)
|
|
93
128
|
* [MCP Host Config](docs/host-config.md)
|
|
129
|
+
* [mcporter keep-alive setup](docs/mcporter.md)
|
|
94
130
|
* [Support Matrix](docs/support-matrix.md)
|
|
95
131
|
* [Tool Catalog](docs/tool-catalog.md)
|
|
96
132
|
* [Claude Desktop example](examples/claude-desktop-config.json)
|
|
97
133
|
* [Cursor example](examples/cursor-mcp.json)
|
|
134
|
+
* [mcporter example](examples/mcporter.json)
|
|
98
135
|
* [Config file example](examples/unolock-agent-config.json)
|
|
99
136
|
|
|
137
|
+
`mcporter` is the preferred path when it is available. The user PIN is kept only in MCP process memory, so keeping the MCP alive means lower latency, fewer repeat PIN prompts, and less pressure for the agent to store the PIN persistently.
|
|
138
|
+
|
|
100
139
|
If you prefer manual install from source:
|
|
101
140
|
|
|
102
141
|
```bash
|
|
@@ -122,6 +161,12 @@ For the best customer experience, prefer GitHub Release binaries over source ins
|
|
|
122
161
|
|
|
123
162
|
## Preferred Customer Install
|
|
124
163
|
|
|
164
|
+
When available, prefer `mcporter` keep-alive plus the npm wrapper or release binary instead of a cold-start bare MCP process.
|
|
165
|
+
|
|
166
|
+
For an agent-first public onboarding flow, send users or agents to:
|
|
167
|
+
|
|
168
|
+
* `https://unolock.ai/index.html`
|
|
169
|
+
|
|
125
170
|
When available, prefer the standalone GitHub Release binaries instead of installing from Git.
|
|
126
171
|
|
|
127
172
|
That avoids most of the Python packaging and source-build overhead for customers.
|
|
@@ -129,11 +174,13 @@ That avoids most of the Python packaging and source-build overhead for customers
|
|
|
129
174
|
If your host environment is already Node/npm-oriented, you can also use the npm wrapper:
|
|
130
175
|
|
|
131
176
|
```bash
|
|
132
|
-
npx @techsologic/unolock-agent-mcp --version
|
|
177
|
+
npx @techsologic/unolock-agent-mcp@latest --version
|
|
133
178
|
```
|
|
134
179
|
|
|
135
180
|
The wrapper downloads the correct GitHub Release binary for the current platform on first use and then reuses the cached copy.
|
|
136
181
|
|
|
182
|
+
On restart, the npm wrapper now checks GitHub Releases for a newer stable binary and will update its cached binary between tasks when a newer release is available.
|
|
183
|
+
|
|
137
184
|
The npm package is an OpenClaw-friendly install and launch path for the external UnoLock MCP binary.
|
|
138
185
|
|
|
139
186
|
It is **not** an OpenClaw plugin package for `openclaw plugins install ...`.
|
|
@@ -145,15 +192,62 @@ Project home:
|
|
|
145
192
|
Use it as a command that OpenClaw can launch, for example:
|
|
146
193
|
|
|
147
194
|
```bash
|
|
148
|
-
npx @techsologic/unolock-agent-mcp mcp
|
|
195
|
+
npx @techsologic/unolock-agent-mcp@latest mcp
|
|
149
196
|
```
|
|
150
197
|
|
|
151
198
|
With no arguments, the npm wrapper starts the MCP server by default:
|
|
152
199
|
|
|
153
200
|
```bash
|
|
154
|
-
npx @techsologic/unolock-agent-mcp
|
|
201
|
+
npx @techsologic/unolock-agent-mcp@latest
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Preferred keep-alive example with `mcporter`:
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"servers": {
|
|
209
|
+
"unolock-agent": {
|
|
210
|
+
"command": "npx",
|
|
211
|
+
"args": ["@techsologic/unolock-agent-mcp@latest"],
|
|
212
|
+
"lifecycle": "keep-alive"
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Update Policy
|
|
219
|
+
|
|
220
|
+
UnoLock Agent MCP should not replace itself in the middle of an active session or write flow.
|
|
221
|
+
|
|
222
|
+
The intended update model is:
|
|
223
|
+
|
|
224
|
+
* the MCP reports update status
|
|
225
|
+
* the wrapper or runner applies updates
|
|
226
|
+
* the runner restarts between tasks so in-memory PINs and sessions can be re-established cleanly
|
|
227
|
+
|
|
228
|
+
Check update status with:
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
unolock-agent-mcp check-update --json
|
|
155
232
|
```
|
|
156
233
|
|
|
234
|
+
Or, through the MCP itself, call:
|
|
235
|
+
|
|
236
|
+
* `unolock_get_update_status`
|
|
237
|
+
|
|
238
|
+
Preferred channel behavior:
|
|
239
|
+
|
|
240
|
+
* `mcporter` + `npx @techsologic/unolock-agent-mcp@latest`
|
|
241
|
+
* preferred low-friction path
|
|
242
|
+
* on restart, the npm wrapper checks GitHub Releases and can fetch the latest stable binary
|
|
243
|
+
* npm publishing is only needed when the wrapper itself changes
|
|
244
|
+
* direct GitHub Release binary
|
|
245
|
+
* replace the binary manually, then restart the MCP runner
|
|
246
|
+
* Python package install
|
|
247
|
+
* upgrade the package in that environment, then restart the runner
|
|
248
|
+
|
|
249
|
+
For the best user experience, do updates between tasks, not while an enrollment flow, authentication flow, or sensitive write flow is active.
|
|
250
|
+
|
|
157
251
|
## Standalone config
|
|
158
252
|
|
|
159
253
|
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.
|
|
@@ -170,12 +264,11 @@ Override example:
|
|
|
170
264
|
{
|
|
171
265
|
"base_url": "https://api.unolock.example",
|
|
172
266
|
"transparency_origin": "https://safe.unolock.example",
|
|
173
|
-
"app_version": "1.2.3",
|
|
174
267
|
"signing_public_key_b64": "BASE64_SERVER_PQ_SIGNING_PUBLIC_KEY"
|
|
175
268
|
}
|
|
176
269
|
```
|
|
177
270
|
|
|
178
|
-
For the standard hosted UnoLock deployment, the MCP can derive the API origin
|
|
271
|
+
For the standard hosted UnoLock deployment, the MCP can derive the API origin 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:
|
|
179
272
|
|
|
180
273
|
```json
|
|
181
274
|
{
|
|
@@ -183,7 +276,7 @@ For the standard hosted UnoLock deployment, the MCP can derive the API origin, U
|
|
|
183
276
|
}
|
|
184
277
|
```
|
|
185
278
|
|
|
186
|
-
the MCP will derive `https://safe.unolock.com`, fetch `/unolock-client.json`, and read the published
|
|
279
|
+
the MCP will derive `https://safe.unolock.com`, fetch `/unolock-client.json`, and read the published `serverPQValidationKey`. If that hosted file is unavailable, it falls back to the transparency bundle.
|
|
187
280
|
|
|
188
281
|
Use this command to verify what the MCP resolved:
|
|
189
282
|
|
|
@@ -191,13 +284,6 @@ Use this command to verify what the MCP resolved:
|
|
|
191
284
|
python3 -m unolock_mcp config-check
|
|
192
285
|
```
|
|
193
286
|
|
|
194
|
-
Versioning is intentionally split:
|
|
195
|
-
|
|
196
|
-
* MCP package version: the version of `unolock-agent-mcp` itself
|
|
197
|
-
* UnoLock app version: the Safe client/server compatibility version sent as `x-app-version`
|
|
198
|
-
|
|
199
|
-
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.
|
|
200
|
-
|
|
201
287
|
TPM provider selection:
|
|
202
288
|
|
|
203
289
|
* default: `UNOLOCK_TPM_PROVIDER=auto`
|
package/bin/unolock-agent-mcp.js
CHANGED
|
@@ -7,8 +7,8 @@ const path = require("path");
|
|
|
7
7
|
const https = require("https");
|
|
8
8
|
const { spawn } = require("child_process");
|
|
9
9
|
|
|
10
|
-
const PACKAGE_VERSION = "0.1.
|
|
11
|
-
const
|
|
10
|
+
const PACKAGE_VERSION = "0.1.15";
|
|
11
|
+
const FALLBACK_BINARY_VERSION = "0.1.15";
|
|
12
12
|
const REPO = "TechSologic/unolock-agent-mcp";
|
|
13
13
|
|
|
14
14
|
function platformAssetInfo() {
|
|
@@ -36,17 +36,21 @@ function cacheRoot() {
|
|
|
36
36
|
return process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function
|
|
39
|
+
function metadataPath() {
|
|
40
|
+
return path.join(cacheRoot(), "unolock-agent-mcp", "release.json");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function binaryPath(releaseVersion) {
|
|
40
44
|
const { executable } = platformAssetInfo();
|
|
41
|
-
return path.join(cacheRoot(), "unolock-agent-mcp",
|
|
45
|
+
return path.join(cacheRoot(), "unolock-agent-mcp", releaseVersion, executable);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
function binaryUrl() {
|
|
48
|
+
function binaryUrl(releaseVersion) {
|
|
45
49
|
if (process.env.UNOLOCK_AGENT_MCP_BINARY_URL) {
|
|
46
50
|
return process.env.UNOLOCK_AGENT_MCP_BINARY_URL;
|
|
47
51
|
}
|
|
48
52
|
const { asset } = platformAssetInfo();
|
|
49
|
-
return `https://github.com/${REPO}/releases/download/v${
|
|
53
|
+
return `https://github.com/${REPO}/releases/download/v${releaseVersion}/${asset}`;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
function ensureDir(dir) {
|
|
@@ -95,23 +99,126 @@ function fetchToFile(url, dest) {
|
|
|
95
99
|
});
|
|
96
100
|
}
|
|
97
101
|
|
|
102
|
+
function fetchJson(url) {
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
const request = https.get(
|
|
105
|
+
url,
|
|
106
|
+
{
|
|
107
|
+
headers: {
|
|
108
|
+
"Accept": "application/vnd.github+json",
|
|
109
|
+
"User-Agent": "unolock-agent-mcp-npm-wrapper"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
(response) => {
|
|
113
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
114
|
+
response.resume();
|
|
115
|
+
fetchJson(response.headers.location).then(resolve, reject);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (response.statusCode !== 200) {
|
|
119
|
+
response.resume();
|
|
120
|
+
reject(new Error(`Failed to query UnoLock Agent MCP latest release: HTTP ${response.statusCode}`));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
let body = "";
|
|
124
|
+
response.setEncoding("utf8");
|
|
125
|
+
response.on("data", (chunk) => {
|
|
126
|
+
body += chunk;
|
|
127
|
+
});
|
|
128
|
+
response.on("end", () => {
|
|
129
|
+
try {
|
|
130
|
+
resolve(JSON.parse(body));
|
|
131
|
+
} catch (error) {
|
|
132
|
+
reject(error);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
request.on("error", reject);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizeVersion(value) {
|
|
142
|
+
if (!value || typeof value !== "string") {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
const trimmed = value.trim();
|
|
146
|
+
if (!trimmed) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return trimmed.startsWith("v") ? trimmed.slice(1) : trimmed;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readReleaseMetadata() {
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(fs.readFileSync(metadataPath(), "utf8"));
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function writeReleaseMetadata(releaseVersion) {
|
|
161
|
+
ensureDir(path.dirname(metadataPath()));
|
|
162
|
+
fs.writeFileSync(
|
|
163
|
+
metadataPath(),
|
|
164
|
+
JSON.stringify(
|
|
165
|
+
{
|
|
166
|
+
releaseVersion,
|
|
167
|
+
checkedAt: Date.now()
|
|
168
|
+
},
|
|
169
|
+
null,
|
|
170
|
+
2
|
|
171
|
+
),
|
|
172
|
+
"utf8"
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function resolveReleaseVersion() {
|
|
177
|
+
const override = normalizeVersion(process.env.UNOLOCK_AGENT_MCP_BINARY_VERSION);
|
|
178
|
+
if (override) {
|
|
179
|
+
return override;
|
|
180
|
+
}
|
|
181
|
+
const metadata = readReleaseMetadata();
|
|
182
|
+
try {
|
|
183
|
+
const payload = await fetchJson(`https://api.github.com/repos/${REPO}/releases/latest`);
|
|
184
|
+
const latest = normalizeVersion(payload && payload.tag_name);
|
|
185
|
+
if (latest) {
|
|
186
|
+
writeReleaseMetadata(latest);
|
|
187
|
+
return latest;
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (metadata && typeof metadata.releaseVersion === "string" && fs.existsSync(binaryPath(metadata.releaseVersion))) {
|
|
191
|
+
return metadata.releaseVersion;
|
|
192
|
+
}
|
|
193
|
+
process.stderr.write(`Warning: ${error.message}. Falling back to bundled release ${FALLBACK_BINARY_VERSION}.\n`);
|
|
194
|
+
}
|
|
195
|
+
writeReleaseMetadata(FALLBACK_BINARY_VERSION);
|
|
196
|
+
return FALLBACK_BINARY_VERSION;
|
|
197
|
+
}
|
|
198
|
+
|
|
98
199
|
async function ensureBinary() {
|
|
99
|
-
const
|
|
200
|
+
const releaseVersion = await resolveReleaseVersion();
|
|
201
|
+
const dest = binaryPath(releaseVersion);
|
|
100
202
|
if (fs.existsSync(dest)) {
|
|
101
|
-
return dest;
|
|
203
|
+
return { dest, releaseVersion };
|
|
102
204
|
}
|
|
103
205
|
ensureDir(path.dirname(dest));
|
|
104
|
-
process.stderr.write(`Downloading UnoLock Agent MCP ${
|
|
105
|
-
await fetchToFile(binaryUrl(), dest);
|
|
106
|
-
return dest;
|
|
206
|
+
process.stderr.write(`Downloading UnoLock Agent MCP ${releaseVersion} for ${process.platform}/${process.arch}...\n`);
|
|
207
|
+
await fetchToFile(binaryUrl(releaseVersion), dest);
|
|
208
|
+
return { dest, releaseVersion };
|
|
107
209
|
}
|
|
108
210
|
|
|
109
211
|
async function main() {
|
|
110
|
-
const dest = await ensureBinary();
|
|
212
|
+
const { dest, releaseVersion } = await ensureBinary();
|
|
111
213
|
const forwardedArgs = process.argv.length > 2 ? process.argv.slice(2) : ["mcp"];
|
|
112
214
|
const child = spawn(dest, forwardedArgs, {
|
|
113
215
|
stdio: "inherit",
|
|
114
|
-
env:
|
|
216
|
+
env: {
|
|
217
|
+
...process.env,
|
|
218
|
+
UNOLOCK_AGENT_MCP_INSTALL_CHANNEL: "npm-wrapper",
|
|
219
|
+
UNOLOCK_AGENT_MCP_WRAPPER_VERSION: PACKAGE_VERSION,
|
|
220
|
+
UNOLOCK_AGENT_MCP_BINARY_VERSION: releaseVersion
|
|
221
|
+
}
|
|
115
222
|
});
|
|
116
223
|
child.on("exit", (code, signal) => {
|
|
117
224
|
if (signal) {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@techsologic/unolock-agent-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "npx wrapper for the official UnoLock Agent MCP release binaries",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://github.com/TechSologic/unolock-agent-mcp",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|