adolf: bearer-authenticate the agap MCP server, fix audio config schema
openclaw.json now sends `Authorization: Bearer ${AGAP_MCP_TOKEN}` to the agap
MCP server, which requires it as of kb#180. The token is injected from
openai/.env via docker-compose.yml and only substituted here, never inlined.
It maps to agent id `adolf`, which is also what the kb#147 vault gate reads.
Fixes tools.media.audio, which had been added but never restart-validated:
the per-entry `apiKey: "not-needed"` is rejected by the schema
("tools.media.audio.models.0: Invalid input"), and an invalid config makes the
gateway refuse to start outright -- adolf crash-looped on the first restart
after the block landed. The old comment claimed the schema requires a
non-empty apiKey; it is the opposite, apiKey is not a valid per-entry key at
all. Isolated with `openclaw config validate` against the running image
(2026.6.11): {provider, model} and {provider, model, baseUrl} validate, and
adding apiKey alone reproduces the failure. baseUrl is kept -- that is the
per-entry override pointing the openai-shaped provider at the local
faster-whisper server. Provider auth follows the normal model auth order per
docs/nodes/audio.md, and faster-whisper-server has no auth to satisfy anyway.
Two lessons encoded in the comments: `enabled: false` does NOT exempt an entry
from schema validation, and a config edit is not done until a restart boots
healthy -- this sat invalid but latent because the running gateway still held
an older loaded config. The block stays enabled: false; turning STT on is
still a kb#175/#191 decision (GTX 1070 co-residency).
Also adds the proactive-prioritization and todoist-capture design notes and
the vw-mcp prototype.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
100
adolf/vw-mcp/vaultwarden.js
Normal file
100
adolf/vw-mcp/vaultwarden.js
Normal file
@@ -0,0 +1,100 @@
|
||||
// Trimmed, read-only copy of agap-mcp/src/vaultwarden.js (kb task #64).
|
||||
//
|
||||
// Deliberate differences from the master copy:
|
||||
// - Only the 3 read tools are implemented: get password, get item, list
|
||||
// items. vwCreateLogin / vwUpdatePassword (and AI_COLLECTION, which only
|
||||
// those write paths needed) are NOT here — this server must never be able
|
||||
// to write to the vault, even in principle.
|
||||
// - Authenticates as a DEDICATED bot user (BW_EMAIL=adolf-vault@auth.local),
|
||||
// never the master allogn@gmail.com account. No default email/password —
|
||||
// both must be explicit in .env so this can never silently fall back to
|
||||
// the master identity.
|
||||
// - BITWARDENCLI_APPDATA_DIR (see docker-compose.yml) points at this
|
||||
// service's OWN volume, separate from the agap-mcp/marketplace-mcp host
|
||||
// bind mount (`/home/alvis/.config/Bitwarden CLI`) — the bot's bw
|
||||
// login/session state must never share a directory with the master's.
|
||||
//
|
||||
// The real fence is server-side: the bot user is granted read-only access to
|
||||
// a narrow "Adolf" collection only (not the whole "AI" collection). This
|
||||
// client code does not filter by collection — Vaultwarden itself only
|
||||
// returns items the bot user's permissions allow, whatever org-wide ORG_ID
|
||||
// is passed.
|
||||
import { execFileSync } from 'child_process';
|
||||
|
||||
const BW = 'bw';
|
||||
const ORG_ID = '4bd75130-b4d3-48d4-a4cb-e52b70295a51';
|
||||
|
||||
let _session = null;
|
||||
|
||||
function bwEnv() {
|
||||
const env = { ...process.env };
|
||||
for (const k of ['HTTPS_PROXY', 'HTTP_PROXY', 'ALL_PROXY', 'https_proxy', 'http_proxy', 'all_proxy'])
|
||||
delete env[k];
|
||||
env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
return env;
|
||||
}
|
||||
|
||||
function run(args, input) {
|
||||
return execFileSync(BW, args, {
|
||||
env: bwEnv(),
|
||||
encoding: 'utf8',
|
||||
input,
|
||||
stdio: input ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
}
|
||||
|
||||
export async function initVaultwarden() {
|
||||
const email = process.env.BW_EMAIL;
|
||||
const password = process.env.BW_PASSWORD;
|
||||
const server = process.env.VW_URL;
|
||||
if (!email || !password) {
|
||||
throw new Error('BW_EMAIL and BW_PASSWORD env vars are required — the dedicated adolf-vault@auth.local bot credentials, never the master account (see .env.example)');
|
||||
}
|
||||
if (!server) {
|
||||
throw new Error('VW_URL env var is required — this service owns a FRESH BITWARDENCLI_APPDATA_DIR volume (unlike agap-mcp/marketplace-mcp, which reuse the host dir that already has `bw config server` set), so it must configure the server itself on every boot (see .env.example)');
|
||||
}
|
||||
|
||||
// Idempotent — safe to call on every boot, including against an
|
||||
// already-configured appdata dir.
|
||||
run(['config', 'server', server]);
|
||||
|
||||
let status = 'unauthenticated';
|
||||
try {
|
||||
status = JSON.parse(run(['status'])).status;
|
||||
} catch {}
|
||||
|
||||
if (status === 'unauthenticated') {
|
||||
run(['login', email, password, '--raw']);
|
||||
}
|
||||
|
||||
_session = run(['unlock', password, '--raw']);
|
||||
run(['sync', '--session', _session]);
|
||||
console.log('Vaultwarden: ready (adolf-vault bot identity)');
|
||||
}
|
||||
|
||||
function session() {
|
||||
if (!_session) throw new Error('Vaultwarden not initialized');
|
||||
return _session;
|
||||
}
|
||||
|
||||
export function vwGetPassword(name) {
|
||||
return run(['get', 'password', name, '--session', session()]);
|
||||
}
|
||||
|
||||
export function vwGetItem(name) {
|
||||
return JSON.parse(run(['get', 'item', name, '--session', session()]));
|
||||
}
|
||||
|
||||
export function vwListItems(search) {
|
||||
const args = ['list', 'items', '--session', session()];
|
||||
if (search) args.push('--search', search);
|
||||
return JSON.parse(run(args));
|
||||
}
|
||||
|
||||
export function vwListOrgItems(search) {
|
||||
// Server-side collection permissions (not this code) decide what actually
|
||||
// comes back — the bot user only sees the narrow "Adolf" collection.
|
||||
const args = ['list', 'items', '--organizationid', ORG_ID, '--session', session()];
|
||||
if (search) args.push('--search', search);
|
||||
return JSON.parse(run(args));
|
||||
}
|
||||
Reference in New Issue
Block a user