@icoretech/warden-mcp 0.1.15 → 0.1.17
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 +9 -6
- package/bin/warden-mcp.js +5 -14
- package/dist/bw/bwCli.js +2 -1
- package/dist/bw/bwSession.js +28 -5
- package/dist/bw/resolveBwBin.js +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,12 +62,14 @@ BW_BIN=/absolute/path/to/bw npx -y @icoretech/warden-mcp@latest
|
|
|
62
62
|
`warden-mcp` intentionally bundles a vetted `@bitwarden/cli` version instead of
|
|
63
63
|
blindly following the newest upstream CLI on every release. New `bw` releases
|
|
64
64
|
can change login and unlock behavior in ways that break automation, so `bw`
|
|
65
|
-
upgrades should be smoke-tested against real Vaultwarden
|
|
66
|
-
|
|
65
|
+
upgrades should be smoke-tested against real Vaultwarden flows before bumping
|
|
66
|
+
the bundled version. Official Bitwarden compatibility is intended, but it is
|
|
67
|
+
not continuously proven in CI without a real Bitwarden tenant.
|
|
67
68
|
|
|
68
|
-
This repository's compose smoke now exercises both
|
|
69
|
-
user API-key auth against a
|
|
70
|
-
not rely on unit coverage
|
|
69
|
+
This repository's compose smoke now exercises both direct `bw` auth flows and
|
|
70
|
+
the MCP/SDK layers with username/password auth plus user API-key auth against a
|
|
71
|
+
real local Vaultwarden, so `@bitwarden/cli` bumps do not rely on unit coverage
|
|
72
|
+
alone.
|
|
71
73
|
|
|
72
74
|
## Install And Run
|
|
73
75
|
|
|
@@ -456,7 +458,8 @@ Run integration tests:
|
|
|
456
458
|
make test
|
|
457
459
|
```
|
|
458
460
|
|
|
459
|
-
`make test` now runs both compose-backed auth paths
|
|
461
|
+
`make test` now runs both compose-backed auth paths and verifies them at the
|
|
462
|
+
raw CLI plus MCP/SDK layers:
|
|
460
463
|
|
|
461
464
|
- user/password login from `.env.test`
|
|
462
465
|
- api-key login from `tmp/vaultwarden-bootstrap/apikey.env`, generated by the bootstrap step and kept out of git via `tmp/`
|
package/bin/warden-mcp.js
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
// bin/warden-mcp.js — CLI entry for @icoretech/warden-mcp
|
|
4
4
|
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
-
import {
|
|
7
|
-
import { createRequire } from 'node:module';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
8
7
|
import { dirname, resolve } from 'node:path';
|
|
9
8
|
import { fileURLToPath } from 'node:url';
|
|
10
9
|
|
|
@@ -13,20 +12,12 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
13
12
|
// Resolve bw binary: optional dep → system PATH
|
|
14
13
|
if (!process.env.BW_BIN) {
|
|
15
14
|
try {
|
|
16
|
-
const
|
|
17
|
-
const { resolveBundledBwCandidate } = await import(
|
|
15
|
+
const { resolveBundledBwBin } = await import(
|
|
18
16
|
resolve(__dirname, '../dist/bw/resolveBwBin.js')
|
|
19
17
|
);
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (existsSync(candidate)) {
|
|
24
|
-
try {
|
|
25
|
-
accessSync(candidate, constants.X_OK);
|
|
26
|
-
process.env.BW_BIN = candidate;
|
|
27
|
-
} catch {
|
|
28
|
-
// Not executable — fall through to system bw
|
|
29
|
-
}
|
|
18
|
+
const candidate = resolveBundledBwBin();
|
|
19
|
+
if (candidate) {
|
|
20
|
+
process.env.BW_BIN = candidate;
|
|
30
21
|
}
|
|
31
22
|
} catch {
|
|
32
23
|
// @bitwarden/cli optional dep not installed — fall through to system bw
|
package/dist/bw/bwCli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/bw/bwCli.ts
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
+
import { resolveBundledBwBin } from './resolveBwBin.js';
|
|
3
4
|
export class BwCliError extends Error {
|
|
4
5
|
exitCode;
|
|
5
6
|
stdout;
|
|
@@ -13,7 +14,7 @@ export class BwCliError extends Error {
|
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
export async function runBw(args, opts = {}) {
|
|
16
|
-
const bwBin = process.env.BW_BIN ?? 'bw';
|
|
17
|
+
const bwBin = process.env.BW_BIN ?? resolveBundledBwBin() ?? 'bw';
|
|
17
18
|
// Ensure the CLI never blocks waiting for a prompt (e.g. master password).
|
|
18
19
|
// This is critical for running as an MCP server / in test automation.
|
|
19
20
|
const injectNoInteraction = opts.noInteraction ?? true;
|
package/dist/bw/bwSession.js
CHANGED
|
@@ -3,6 +3,11 @@ import { rm } from 'node:fs/promises';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { runBw } from './bwCli.js';
|
|
5
5
|
import { Mutex } from './mutex.js';
|
|
6
|
+
const POST_LOGIN_UNLOCK_RETRY_ATTEMPTS = 20;
|
|
7
|
+
const POST_LOGIN_UNLOCK_RETRY_DELAY_MS = 2_000;
|
|
8
|
+
function sleep(ms) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
}
|
|
6
11
|
function requiredEnv(name) {
|
|
7
12
|
const v = process.env[name];
|
|
8
13
|
if (!v) {
|
|
@@ -188,7 +193,7 @@ export class BwSessionManager {
|
|
|
188
193
|
timeoutMs: 60_000,
|
|
189
194
|
noInteraction: false,
|
|
190
195
|
});
|
|
191
|
-
return stdout.trim();
|
|
196
|
+
return { completed: true, session: stdout.trim() };
|
|
192
197
|
}
|
|
193
198
|
const { stdout } = await runBw([
|
|
194
199
|
'login',
|
|
@@ -197,17 +202,35 @@ export class BwSessionManager {
|
|
|
197
202
|
'BW_PASSWORD',
|
|
198
203
|
'--raw',
|
|
199
204
|
], { env: unlockEnv, timeoutMs: 60_000, noInteraction: false });
|
|
200
|
-
return stdout.trim();
|
|
205
|
+
return { completed: true, session: stdout.trim() };
|
|
201
206
|
}
|
|
202
207
|
catch {
|
|
203
|
-
return '';
|
|
208
|
+
return { completed: false, session: '' };
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const retryUnlockAfterLogin = async () => {
|
|
212
|
+
for (let attempt = 0; attempt < POST_LOGIN_UNLOCK_RETRY_ATTEMPTS; attempt += 1) {
|
|
213
|
+
const session = await tryUnlock();
|
|
214
|
+
if (session)
|
|
215
|
+
return session;
|
|
216
|
+
if (attempt < POST_LOGIN_UNLOCK_RETRY_ATTEMPTS - 1) {
|
|
217
|
+
await sleep(POST_LOGIN_UNLOCK_RETRY_DELAY_MS);
|
|
218
|
+
}
|
|
204
219
|
}
|
|
220
|
+
return '';
|
|
205
221
|
};
|
|
206
222
|
// Prefer unlocking first (works when already logged in). If it yields an empty
|
|
207
223
|
// stdout on exit=0 (observed in some bw builds), fall back to login --raw.
|
|
208
224
|
let session = await tryUnlock();
|
|
209
|
-
if (!session)
|
|
210
|
-
|
|
225
|
+
if (!session) {
|
|
226
|
+
const login = await tryLoginRaw();
|
|
227
|
+
if (login.session) {
|
|
228
|
+
session = login.session;
|
|
229
|
+
}
|
|
230
|
+
else if (login.completed) {
|
|
231
|
+
session = await retryUnlockAfterLogin();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
211
234
|
if (!session)
|
|
212
235
|
session = await tryUnlock();
|
|
213
236
|
if (!session)
|
package/dist/bw/resolveBwBin.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { accessSync, constants, readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
1
3
|
import { dirname, join } from 'node:path';
|
|
2
4
|
export function resolveBundledBwCandidate(pkgManifestPath, pkgBin) {
|
|
3
5
|
const pkgDir = dirname(pkgManifestPath);
|
|
@@ -8,3 +10,15 @@ export function resolveBundledBwCandidate(pkgManifestPath, pkgBin) {
|
|
|
8
10
|
: 'dist/bw';
|
|
9
11
|
return join(pkgDir, binEntry);
|
|
10
12
|
}
|
|
13
|
+
export function resolveBundledBwBin(resolvePackage = createRequire(import.meta.url).resolve) {
|
|
14
|
+
try {
|
|
15
|
+
const pkgManifestPath = resolvePackage('@bitwarden/cli/package.json');
|
|
16
|
+
const pkgJson = JSON.parse(readFileSync(pkgManifestPath, 'utf8'));
|
|
17
|
+
const candidate = resolveBundledBwCandidate(pkgManifestPath, pkgJson.bin);
|
|
18
|
+
accessSync(candidate, constants.X_OK);
|
|
19
|
+
return candidate;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|