@sanctuary-framework/mcp-server 1.0.0-rc.1 → 1.0.0-rc.2
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/dist/cli.cjs +1747 -1716
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1747 -1716
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +208 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +209 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { sha256 } from '@noble/hashes/sha256';
|
|
|
4
4
|
import { hmac } from '@noble/hashes/hmac';
|
|
5
5
|
import { RistrettoPoint, ed25519 } from '@noble/curves/ed25519';
|
|
6
6
|
import { readFile, mkdir, writeFile, stat, unlink, readdir, chmod, access } from 'fs/promises';
|
|
7
|
-
import { join, dirname } from 'path';
|
|
7
|
+
import { join, basename, dirname, resolve } from 'path';
|
|
8
8
|
import { platform, homedir } from 'os';
|
|
9
9
|
import { createRequire } from 'module';
|
|
10
10
|
import { argon2id } from 'hash-wasm';
|
|
@@ -8931,7 +8931,7 @@ var DashboardApprovalChannel = class {
|
|
|
8931
8931
|
server = createServer$2(handler);
|
|
8932
8932
|
}
|
|
8933
8933
|
this.httpServer = server;
|
|
8934
|
-
return new Promise((
|
|
8934
|
+
return new Promise((resolve2, reject) => {
|
|
8935
8935
|
const protocol = this.useTLS ? "https" : "http";
|
|
8936
8936
|
const baseUrl = `${protocol}://${this.config.host}:${this.config.port}`;
|
|
8937
8937
|
server.listen(this.config.port, this.config.host, () => {
|
|
@@ -8956,7 +8956,7 @@ var DashboardApprovalChannel = class {
|
|
|
8956
8956
|
if (shouldAutoOpen) {
|
|
8957
8957
|
this.openInBrowser(sessionUrl);
|
|
8958
8958
|
}
|
|
8959
|
-
|
|
8959
|
+
resolve2();
|
|
8960
8960
|
});
|
|
8961
8961
|
server.on("error", (err) => {
|
|
8962
8962
|
if (err.code === "EADDRINUSE") {
|
|
@@ -9002,8 +9002,8 @@ var DashboardApprovalChannel = class {
|
|
|
9002
9002
|
}
|
|
9003
9003
|
this.rateLimits.clear();
|
|
9004
9004
|
if (this.httpServer) {
|
|
9005
|
-
return new Promise((
|
|
9006
|
-
this.httpServer.close(() =>
|
|
9005
|
+
return new Promise((resolve2) => {
|
|
9006
|
+
this.httpServer.close(() => resolve2());
|
|
9007
9007
|
});
|
|
9008
9008
|
}
|
|
9009
9009
|
}
|
|
@@ -9017,7 +9017,7 @@ var DashboardApprovalChannel = class {
|
|
|
9017
9017
|
`[Sanctuary] Approval required: ${request.operation} (Tier ${request.tier}) \u2014 open dashboard to respond
|
|
9018
9018
|
`
|
|
9019
9019
|
);
|
|
9020
|
-
return new Promise((
|
|
9020
|
+
return new Promise((resolve2) => {
|
|
9021
9021
|
const timer = setTimeout(() => {
|
|
9022
9022
|
this.pending.delete(id);
|
|
9023
9023
|
const response = {
|
|
@@ -9031,12 +9031,12 @@ var DashboardApprovalChannel = class {
|
|
|
9031
9031
|
decision: response.decision,
|
|
9032
9032
|
decided_by: "timeout"
|
|
9033
9033
|
});
|
|
9034
|
-
|
|
9034
|
+
resolve2(response);
|
|
9035
9035
|
}, this.config.timeout_seconds * 1e3);
|
|
9036
9036
|
const pending = {
|
|
9037
9037
|
id,
|
|
9038
9038
|
request,
|
|
9039
|
-
resolve,
|
|
9039
|
+
resolve: resolve2,
|
|
9040
9040
|
timer,
|
|
9041
9041
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9042
9042
|
};
|
|
@@ -9882,7 +9882,7 @@ var WebhookApprovalChannel = class {
|
|
|
9882
9882
|
* Start the callback listener server.
|
|
9883
9883
|
*/
|
|
9884
9884
|
async start() {
|
|
9885
|
-
return new Promise((
|
|
9885
|
+
return new Promise((resolve2, reject) => {
|
|
9886
9886
|
this.callbackServer = createServer$2(
|
|
9887
9887
|
(req, res) => this.handleCallback(req, res)
|
|
9888
9888
|
);
|
|
@@ -9897,7 +9897,7 @@ var WebhookApprovalChannel = class {
|
|
|
9897
9897
|
|
|
9898
9898
|
`
|
|
9899
9899
|
);
|
|
9900
|
-
|
|
9900
|
+
resolve2();
|
|
9901
9901
|
}
|
|
9902
9902
|
);
|
|
9903
9903
|
this.callbackServer.on("error", reject);
|
|
@@ -9917,8 +9917,8 @@ var WebhookApprovalChannel = class {
|
|
|
9917
9917
|
}
|
|
9918
9918
|
this.pending.clear();
|
|
9919
9919
|
if (this.callbackServer) {
|
|
9920
|
-
return new Promise((
|
|
9921
|
-
this.callbackServer.close(() =>
|
|
9920
|
+
return new Promise((resolve2) => {
|
|
9921
|
+
this.callbackServer.close(() => resolve2());
|
|
9922
9922
|
});
|
|
9923
9923
|
}
|
|
9924
9924
|
}
|
|
@@ -9931,7 +9931,7 @@ var WebhookApprovalChannel = class {
|
|
|
9931
9931
|
`[Sanctuary] Webhook approval sent: ${request.operation} (Tier ${request.tier}) \u2014 awaiting callback
|
|
9932
9932
|
`
|
|
9933
9933
|
);
|
|
9934
|
-
return new Promise((
|
|
9934
|
+
return new Promise((resolve2) => {
|
|
9935
9935
|
const timer = setTimeout(() => {
|
|
9936
9936
|
this.pending.delete(id);
|
|
9937
9937
|
const response = {
|
|
@@ -9940,12 +9940,12 @@ var WebhookApprovalChannel = class {
|
|
|
9940
9940
|
decided_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9941
9941
|
decided_by: "timeout"
|
|
9942
9942
|
};
|
|
9943
|
-
|
|
9943
|
+
resolve2(response);
|
|
9944
9944
|
}, this.config.timeout_seconds * 1e3);
|
|
9945
9945
|
const pending = {
|
|
9946
9946
|
id,
|
|
9947
9947
|
request,
|
|
9948
|
-
resolve,
|
|
9948
|
+
resolve: resolve2,
|
|
9949
9949
|
timer,
|
|
9950
9950
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9951
9951
|
};
|
|
@@ -13139,7 +13139,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13139
13139
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13140
13140
|
const canonicalBytes = canonicalize(outcome);
|
|
13141
13141
|
const canonicalString = new TextDecoder().decode(canonicalBytes);
|
|
13142
|
-
const
|
|
13142
|
+
const sha2568 = createCommitment(canonicalString);
|
|
13143
13143
|
let pedersenData;
|
|
13144
13144
|
if (includePedersen && Number.isInteger(outcome.rounds) && outcome.rounds >= 0) {
|
|
13145
13145
|
const pedersen = createPedersenCommitment(outcome.rounds);
|
|
@@ -13151,7 +13151,7 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13151
13151
|
const commitmentPayload = {
|
|
13152
13152
|
bridge_commitment_id: commitmentId,
|
|
13153
13153
|
session_id: outcome.session_id,
|
|
13154
|
-
sha256_commitment:
|
|
13154
|
+
sha256_commitment: sha2568.commitment,
|
|
13155
13155
|
terms_hash: outcome.terms_hash,
|
|
13156
13156
|
committer_did: identity.did,
|
|
13157
13157
|
committed_at: now,
|
|
@@ -13162,8 +13162,8 @@ function createBridgeCommitment(outcome, identity, identityEncryptionKey, includ
|
|
|
13162
13162
|
return {
|
|
13163
13163
|
bridge_commitment_id: commitmentId,
|
|
13164
13164
|
session_id: outcome.session_id,
|
|
13165
|
-
sha256_commitment:
|
|
13166
|
-
blinding_factor:
|
|
13165
|
+
sha256_commitment: sha2568.commitment,
|
|
13166
|
+
blinding_factor: sha2568.blinding_factor,
|
|
13167
13167
|
committer_did: identity.did,
|
|
13168
13168
|
signature: toBase64url(signature),
|
|
13169
13169
|
pedersen_commitment: pedersenData,
|
|
@@ -17330,13 +17330,13 @@ var ProxyRouter = class {
|
|
|
17330
17330
|
* Call an upstream tool with a timeout.
|
|
17331
17331
|
*/
|
|
17332
17332
|
async callWithTimeout(serverName, toolName, args, timeoutMs) {
|
|
17333
|
-
return new Promise((
|
|
17333
|
+
return new Promise((resolve2, reject) => {
|
|
17334
17334
|
const timer = setTimeout(() => {
|
|
17335
17335
|
reject(new Error(`Upstream tool call timed out after ${timeoutMs}ms`));
|
|
17336
17336
|
}, timeoutMs);
|
|
17337
17337
|
this.clientManager.callTool(serverName, toolName, args).then((result) => {
|
|
17338
17338
|
clearTimeout(timer);
|
|
17339
|
-
|
|
17339
|
+
resolve2(result);
|
|
17340
17340
|
}).catch((err) => {
|
|
17341
17341
|
clearTimeout(timer);
|
|
17342
17342
|
reject(err);
|
|
@@ -22385,9 +22385,7 @@ var TEMPLATE_NAMES = [
|
|
|
22385
22385
|
"coding-assistant",
|
|
22386
22386
|
"ops-runner",
|
|
22387
22387
|
"planner",
|
|
22388
|
-
"handoff-coordinator"
|
|
22389
|
-
"x-miner",
|
|
22390
|
-
"github-miner"
|
|
22388
|
+
"handoff-coordinator"
|
|
22391
22389
|
];
|
|
22392
22390
|
function isTemplateName(value) {
|
|
22393
22391
|
return typeof value === "string" && TEMPLATE_NAMES.includes(value);
|
|
@@ -23312,6 +23310,176 @@ function initTemplate(params) {
|
|
|
23312
23310
|
});
|
|
23313
23311
|
return { compiled, signed_event, bundle };
|
|
23314
23312
|
}
|
|
23313
|
+
var DEFAULT_STORAGE_DIR = ".sanctuary";
|
|
23314
|
+
var KEYCHAIN_SERVICE_DEFAULT = "sanctuary-passphrase";
|
|
23315
|
+
function keychainServiceFor(storagePath, home = homedir()) {
|
|
23316
|
+
const defaultPath = join(home, DEFAULT_STORAGE_DIR);
|
|
23317
|
+
if (storagePath === defaultPath) return KEYCHAIN_SERVICE_DEFAULT;
|
|
23318
|
+
const digest = sha256(Buffer.from(storagePath, "utf-8"));
|
|
23319
|
+
const suffix = Buffer.from(digest).toString("hex").slice(0, 12);
|
|
23320
|
+
return `${KEYCHAIN_SERVICE_DEFAULT}-${suffix}`;
|
|
23321
|
+
}
|
|
23322
|
+
var RUNTIME_FILE_NAME = "runtime.json";
|
|
23323
|
+
function runtimePath(storagePath) {
|
|
23324
|
+
return join(storagePath, RUNTIME_FILE_NAME);
|
|
23325
|
+
}
|
|
23326
|
+
async function readTenantRuntime(storagePath) {
|
|
23327
|
+
try {
|
|
23328
|
+
const raw = await readFile(runtimePath(storagePath), "utf-8");
|
|
23329
|
+
const parsed = JSON.parse(raw);
|
|
23330
|
+
if (typeof parsed.dashboard_port !== "number" || typeof parsed.pid !== "number" || typeof parsed.started_at !== "string" || typeof parsed.version !== "string" || typeof parsed.dashboard_host !== "string" || typeof parsed.mode !== "string") {
|
|
23331
|
+
return null;
|
|
23332
|
+
}
|
|
23333
|
+
const state = {
|
|
23334
|
+
version: parsed.version,
|
|
23335
|
+
pid: parsed.pid,
|
|
23336
|
+
started_at: parsed.started_at,
|
|
23337
|
+
dashboard_host: parsed.dashboard_host,
|
|
23338
|
+
dashboard_port: parsed.dashboard_port,
|
|
23339
|
+
mode: parsed.mode
|
|
23340
|
+
};
|
|
23341
|
+
if (typeof parsed.webhook_callback_port === "number") {
|
|
23342
|
+
state.webhook_callback_port = parsed.webhook_callback_port;
|
|
23343
|
+
}
|
|
23344
|
+
if (typeof parsed.webhook_callback_host === "string") {
|
|
23345
|
+
state.webhook_callback_host = parsed.webhook_callback_host;
|
|
23346
|
+
}
|
|
23347
|
+
return state;
|
|
23348
|
+
} catch {
|
|
23349
|
+
return null;
|
|
23350
|
+
}
|
|
23351
|
+
}
|
|
23352
|
+
|
|
23353
|
+
// src/cli/agents/discovery.ts
|
|
23354
|
+
var EXTRAS_FILE_NAME = "agents-extra.json";
|
|
23355
|
+
async function isTenantDir(path) {
|
|
23356
|
+
const [hasState, hasProfile, hasFallback] = await Promise.all([
|
|
23357
|
+
dirExists(join(path, "state")),
|
|
23358
|
+
fileExists2(join(path, "cocoon-profile.json")),
|
|
23359
|
+
fileExists2(join(path, "passphrase.enc"))
|
|
23360
|
+
]);
|
|
23361
|
+
const initialized = hasState;
|
|
23362
|
+
let passphraseStatus;
|
|
23363
|
+
if (hasFallback) passphraseStatus = "fallback-file";
|
|
23364
|
+
else if (hasProfile || hasState) passphraseStatus = "keychain";
|
|
23365
|
+
else passphraseStatus = "not-initialized";
|
|
23366
|
+
return { initialized, hasProfile, passphraseStatus };
|
|
23367
|
+
}
|
|
23368
|
+
async function dirExists(path) {
|
|
23369
|
+
try {
|
|
23370
|
+
const s = await stat(path);
|
|
23371
|
+
return s.isDirectory();
|
|
23372
|
+
} catch {
|
|
23373
|
+
return false;
|
|
23374
|
+
}
|
|
23375
|
+
}
|
|
23376
|
+
async function fileExists2(path) {
|
|
23377
|
+
try {
|
|
23378
|
+
const s = await stat(path);
|
|
23379
|
+
return s.isFile();
|
|
23380
|
+
} catch {
|
|
23381
|
+
return false;
|
|
23382
|
+
}
|
|
23383
|
+
}
|
|
23384
|
+
async function newestAuditMtime(storagePath) {
|
|
23385
|
+
const auditDir = join(storagePath, "state", "_audit");
|
|
23386
|
+
let entries = [];
|
|
23387
|
+
try {
|
|
23388
|
+
entries = await readdir(auditDir);
|
|
23389
|
+
} catch {
|
|
23390
|
+
return null;
|
|
23391
|
+
}
|
|
23392
|
+
let newest = 0;
|
|
23393
|
+
for (const name of entries) {
|
|
23394
|
+
try {
|
|
23395
|
+
const s = await stat(join(auditDir, name));
|
|
23396
|
+
if (s.isFile() && s.mtimeMs > newest) newest = s.mtimeMs;
|
|
23397
|
+
} catch {
|
|
23398
|
+
}
|
|
23399
|
+
}
|
|
23400
|
+
if (newest === 0) return null;
|
|
23401
|
+
return new Date(newest).toISOString();
|
|
23402
|
+
}
|
|
23403
|
+
async function readExtraPaths(root, env) {
|
|
23404
|
+
const out = [];
|
|
23405
|
+
const fromEnv = env.SANCTUARY_AGENTS_EXTRA_PATHS;
|
|
23406
|
+
if (fromEnv && fromEnv.length > 0) {
|
|
23407
|
+
for (const part of fromEnv.split(":")) {
|
|
23408
|
+
const trimmed = part.trim();
|
|
23409
|
+
if (trimmed.length > 0) out.push(resolve(trimmed));
|
|
23410
|
+
}
|
|
23411
|
+
}
|
|
23412
|
+
try {
|
|
23413
|
+
const raw = await readFile(join(root, EXTRAS_FILE_NAME), "utf-8");
|
|
23414
|
+
const parsed = JSON.parse(raw);
|
|
23415
|
+
if (Array.isArray(parsed)) {
|
|
23416
|
+
for (const p of parsed) {
|
|
23417
|
+
if (typeof p === "string" && p.trim().length > 0) out.push(resolve(p));
|
|
23418
|
+
}
|
|
23419
|
+
}
|
|
23420
|
+
} catch {
|
|
23421
|
+
}
|
|
23422
|
+
return Array.from(new Set(out));
|
|
23423
|
+
}
|
|
23424
|
+
async function describeTenant(name, storagePath, home) {
|
|
23425
|
+
const exists = await dirExists(storagePath);
|
|
23426
|
+
if (!exists) return null;
|
|
23427
|
+
const { initialized, hasProfile, passphraseStatus } = await isTenantDir(storagePath);
|
|
23428
|
+
if (!initialized && !hasProfile && passphraseStatus === "not-initialized") {
|
|
23429
|
+
return null;
|
|
23430
|
+
}
|
|
23431
|
+
const last_activity = await newestAuditMtime(storagePath);
|
|
23432
|
+
const runtime = await readTenantRuntime(storagePath);
|
|
23433
|
+
return {
|
|
23434
|
+
name,
|
|
23435
|
+
storage_path: storagePath,
|
|
23436
|
+
exists: true,
|
|
23437
|
+
initialized,
|
|
23438
|
+
has_cocoon_profile: hasProfile,
|
|
23439
|
+
keychain_service: keychainServiceFor(storagePath, home),
|
|
23440
|
+
passphrase_status: passphraseStatus,
|
|
23441
|
+
last_activity,
|
|
23442
|
+
runtime
|
|
23443
|
+
};
|
|
23444
|
+
}
|
|
23445
|
+
async function discoverTenants(options = {}) {
|
|
23446
|
+
const home = options.home ?? homedir();
|
|
23447
|
+
const env = options.env ?? process.env;
|
|
23448
|
+
const root = options.root ?? join(home, DEFAULT_STORAGE_DIR);
|
|
23449
|
+
const tenants = [];
|
|
23450
|
+
const rootTenant = await describeTenant("default", root, home);
|
|
23451
|
+
if (rootTenant) tenants.push(rootTenant);
|
|
23452
|
+
let children = [];
|
|
23453
|
+
try {
|
|
23454
|
+
children = await readdir(root);
|
|
23455
|
+
} catch {
|
|
23456
|
+
}
|
|
23457
|
+
for (const child of children) {
|
|
23458
|
+
const childPath = join(root, child);
|
|
23459
|
+
if (child.startsWith(".")) continue;
|
|
23460
|
+
if (child === "state" || child === "backup" || child === "config") continue;
|
|
23461
|
+
const s = await stat(childPath).catch(() => null);
|
|
23462
|
+
if (!s || !s.isDirectory()) continue;
|
|
23463
|
+
const desc = await describeTenant(child, childPath, home);
|
|
23464
|
+
if (desc) tenants.push(desc);
|
|
23465
|
+
}
|
|
23466
|
+
const extras = await readExtraPaths(root, env);
|
|
23467
|
+
for (const extra of extras) {
|
|
23468
|
+
if (tenants.some((t) => t.storage_path === extra)) continue;
|
|
23469
|
+
const desc = await describeTenant(basename(extra), extra, home);
|
|
23470
|
+
if (desc) tenants.push(desc);
|
|
23471
|
+
}
|
|
23472
|
+
tenants.sort((a, b) => {
|
|
23473
|
+
if (a.name === "default") return -1;
|
|
23474
|
+
if (b.name === "default") return 1;
|
|
23475
|
+
return a.name.localeCompare(b.name);
|
|
23476
|
+
});
|
|
23477
|
+
return tenants;
|
|
23478
|
+
}
|
|
23479
|
+
async function findTenant(name, options = {}) {
|
|
23480
|
+
const tenants = await discoverTenants(options);
|
|
23481
|
+
return tenants.find((t) => t.name === name) ?? null;
|
|
23482
|
+
}
|
|
23315
23483
|
|
|
23316
23484
|
// src/dashboard/api.ts
|
|
23317
23485
|
function constantTimeEquals(a, b) {
|
|
@@ -23465,6 +23633,18 @@ async function handleRequest(deps, req, res) {
|
|
|
23465
23633
|
});
|
|
23466
23634
|
return true;
|
|
23467
23635
|
}
|
|
23636
|
+
const isAgentWrapped = deps.isAgentWrapped ?? (async (agentId) => {
|
|
23637
|
+
const tenant = await findTenant(agentId);
|
|
23638
|
+
if (!tenant) return false;
|
|
23639
|
+
return tenant.initialized || tenant.has_cocoon_profile;
|
|
23640
|
+
});
|
|
23641
|
+
if (!await isAgentWrapped(body.agent_name)) {
|
|
23642
|
+
writeJSON(res, 400, {
|
|
23643
|
+
error: "orphan_agent_id",
|
|
23644
|
+
message: `No wrapped harness found for agent_id "${body.agent_name}". Run \`sanctuary wrap\` to wrap the harness first, then retry template init.`
|
|
23645
|
+
});
|
|
23646
|
+
return true;
|
|
23647
|
+
}
|
|
23468
23648
|
const nodeId = deps.nodeId ?? "dashboard-node";
|
|
23469
23649
|
const nodePrivateKey = deps.nodePrivateKey ?? generateEphemeralKey();
|
|
23470
23650
|
const principalId = deps.principalId ?? "dashboard-principal";
|
|
@@ -23573,11 +23753,11 @@ async function startDashboardServer(options) {
|
|
|
23573
23753
|
}
|
|
23574
23754
|
}
|
|
23575
23755
|
});
|
|
23576
|
-
await new Promise((
|
|
23756
|
+
await new Promise((resolve2, reject) => {
|
|
23577
23757
|
server.once("error", reject);
|
|
23578
23758
|
server.listen(port, host, () => {
|
|
23579
23759
|
server.off("error", reject);
|
|
23580
|
-
|
|
23760
|
+
resolve2();
|
|
23581
23761
|
});
|
|
23582
23762
|
});
|
|
23583
23763
|
const actualPort = (() => {
|
|
@@ -23590,8 +23770,8 @@ async function startDashboardServer(options) {
|
|
|
23590
23770
|
url,
|
|
23591
23771
|
port: actualPort,
|
|
23592
23772
|
host,
|
|
23593
|
-
stop: () => new Promise((
|
|
23594
|
-
server.close((err) => err ? reject(err) :
|
|
23773
|
+
stop: () => new Promise((resolve2, reject) => {
|
|
23774
|
+
server.close((err) => err ? reject(err) : resolve2());
|
|
23595
23775
|
}),
|
|
23596
23776
|
publish,
|
|
23597
23777
|
publishActivity: (entry) => publish({ type: "activity", data: entry }),
|
|
@@ -24192,7 +24372,7 @@ async function createSanctuaryServer(options) {
|
|
|
24192
24372
|
clientManager.configure(enabledServers).catch((err) => {
|
|
24193
24373
|
console.error(`[Sanctuary] Failed to configure upstream servers: ${err instanceof Error ? err.message : "unknown error"}`);
|
|
24194
24374
|
});
|
|
24195
|
-
await new Promise((
|
|
24375
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2e3));
|
|
24196
24376
|
const proxiedTools = proxyRouter.getProxiedTools();
|
|
24197
24377
|
if (proxiedTools.length > 0) {
|
|
24198
24378
|
allTools.push(...proxiedTools);
|