@suveren/gateway 0.15.0 → 0.16.0
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/mcp-server/http.mjs +259 -89
- package/package.json +1 -1
- package/profiles/delegation/0.1.profile.json +117 -0
- package/profiles/index.json +2 -1
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -2270,14 +2270,26 @@ function formatConsumptionFull(entries) {
|
|
|
2270
2270
|
}
|
|
2271
2271
|
|
|
2272
2272
|
// src/lib/context-loader.ts
|
|
2273
|
-
import { readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
|
|
2273
|
+
import { readFileSync as readFileSync12, existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, renameSync as renameSync3 } from "fs";
|
|
2274
2274
|
import { homedir as homedir7 } from "os";
|
|
2275
|
-
import { join as join11 } from "path";
|
|
2275
|
+
import { join as join11, dirname as dirname9 } from "path";
|
|
2276
2276
|
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join11(homedir7(), ".suveren");
|
|
2277
|
+
var CONTEXT_MAX_BYTES = 16 * 1024;
|
|
2278
|
+
function contextFilePath(dataDir2) {
|
|
2279
|
+
return join11(dataDir2 ?? DEFAULT_DIR7, "context.md");
|
|
2280
|
+
}
|
|
2281
|
+
function writeContextFile(content, dataDir2) {
|
|
2282
|
+
const bytes = Buffer.byteLength(content, "utf-8");
|
|
2283
|
+
if (bytes > CONTEXT_MAX_BYTES) throw new Error(`the brief is ${bytes} bytes; the limit is ${CONTEXT_MAX_BYTES}.`);
|
|
2284
|
+
const filePath = contextFilePath(dataDir2);
|
|
2285
|
+
mkdirSync9(dirname9(filePath), { recursive: true });
|
|
2286
|
+
const tmpPath = `${filePath}.tmp`;
|
|
2287
|
+
writeFileSync9(tmpPath, content, "utf-8");
|
|
2288
|
+
renameSync3(tmpPath, filePath);
|
|
2289
|
+
}
|
|
2277
2290
|
var BRIEF_MAX_CHARS = 16 * 1024;
|
|
2278
2291
|
function readContextFile(dataDir2) {
|
|
2279
|
-
const
|
|
2280
|
-
const filePath = join11(dir, "context.md");
|
|
2292
|
+
const filePath = contextFilePath(dataDir2);
|
|
2281
2293
|
if (!existsSync11(filePath)) return null;
|
|
2282
2294
|
try {
|
|
2283
2295
|
const content = readFileSync12(filePath, "utf-8").trim();
|
|
@@ -2843,68 +2855,6 @@ import { getProfile as getProfile2, ContentBindingError, boundActionTypes as bou
|
|
|
2843
2855
|
import { readFile } from "fs/promises";
|
|
2844
2856
|
import { extname } from "path";
|
|
2845
2857
|
import { randomUUID } from "crypto";
|
|
2846
|
-
|
|
2847
|
-
// src/lib/manifest-loader.ts
|
|
2848
|
-
import { readFileSync as readFileSync13, existsSync as existsSync12 } from "fs";
|
|
2849
|
-
import { join as join12, resolve } from "path";
|
|
2850
|
-
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
2851
|
-
function isExactSemver(value) {
|
|
2852
|
-
return typeof value === "string" && EXACT_SEMVER_RE.test(value);
|
|
2853
|
-
}
|
|
2854
|
-
function invalidNpmPinReason(manifest) {
|
|
2855
|
-
if (!manifest.npmPackage) return null;
|
|
2856
|
-
if (manifest.npmVersion === void 0) {
|
|
2857
|
-
return `npmPackage "${manifest.npmPackage}" is set but npmVersion is missing \u2014 every connector manifest must pin an exact tested version`;
|
|
2858
|
-
}
|
|
2859
|
-
if (!isExactSemver(manifest.npmVersion)) {
|
|
2860
|
-
return `npmVersion ${JSON.stringify(manifest.npmVersion)} for "${manifest.npmPackage}" is not an exact semver (e.g. "1.2.3") \u2014 ranges, dist-tags like "latest", git specs, URLs, and file paths are not allowed`;
|
|
2861
|
-
}
|
|
2862
|
-
return null;
|
|
2863
|
-
}
|
|
2864
|
-
var manifests = /* @__PURE__ */ new Map();
|
|
2865
|
-
function loadManifests(integrationsDir) {
|
|
2866
|
-
const dir = resolve(
|
|
2867
|
-
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join12(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
2868
|
-
);
|
|
2869
|
-
const indexPath = join12(dir, "index.json");
|
|
2870
|
-
if (!existsSync12(indexPath)) {
|
|
2871
|
-
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
2872
|
-
return 0;
|
|
2873
|
-
}
|
|
2874
|
-
let index;
|
|
2875
|
-
try {
|
|
2876
|
-
index = JSON.parse(readFileSync13(indexPath, "utf-8"));
|
|
2877
|
-
} catch (err) {
|
|
2878
|
-
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
2879
|
-
return 0;
|
|
2880
|
-
}
|
|
2881
|
-
let loaded = 0;
|
|
2882
|
-
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
2883
|
-
const manifestPath = join12(dir, relativePath);
|
|
2884
|
-
try {
|
|
2885
|
-
const manifest = JSON.parse(readFileSync13(manifestPath, "utf-8"));
|
|
2886
|
-
const pinReason = invalidNpmPinReason(manifest);
|
|
2887
|
-
if (pinReason) {
|
|
2888
|
-
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
|
|
2889
|
-
continue;
|
|
2890
|
-
}
|
|
2891
|
-
manifests.set(id, manifest);
|
|
2892
|
-
loaded++;
|
|
2893
|
-
} catch (err) {
|
|
2894
|
-
console.error(`[ManifestLoader] Failed to load manifest ${id} from ${manifestPath}:`, err);
|
|
2895
|
-
}
|
|
2896
|
-
}
|
|
2897
|
-
console.error(`[ManifestLoader] Loaded ${loaded} integration manifest(s) from ${dir}`);
|
|
2898
|
-
return loaded;
|
|
2899
|
-
}
|
|
2900
|
-
function getManifest(id) {
|
|
2901
|
-
return manifests.get(id);
|
|
2902
|
-
}
|
|
2903
|
-
function getAllManifests() {
|
|
2904
|
-
return Array.from(manifests.values());
|
|
2905
|
-
}
|
|
2906
|
-
|
|
2907
|
-
// src/lib/tool-proxy.ts
|
|
2908
2858
|
var IMAGE_MIME = {
|
|
2909
2859
|
".jpg": "image/jpeg",
|
|
2910
2860
|
".jpeg": "image/jpeg",
|
|
@@ -3021,7 +2971,7 @@ function denyRead(state2, tool, reason, detail, target) {
|
|
|
3021
2971
|
return { content: [{ type: "text", text: `Read blocked by Gatekeeper: ${detail}` }], isError: true };
|
|
3022
2972
|
}
|
|
3023
2973
|
function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
3024
|
-
if (isSimulationMode() && !
|
|
2974
|
+
if (isSimulationMode() && !integrationManager2.isSimulationSafe(tool.integrationId)) {
|
|
3025
2975
|
return async () => ({
|
|
3026
2976
|
content: [{
|
|
3027
2977
|
type: "text",
|
|
@@ -3031,7 +2981,13 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
|
3031
2981
|
});
|
|
3032
2982
|
}
|
|
3033
2983
|
const gated = createGatedToolHandlerInner(tool, integrationManager2, state2);
|
|
3034
|
-
const
|
|
2984
|
+
const gateable = !!tool.gating?.profile && tool.gating.category !== "disabled";
|
|
2985
|
+
const inner = async (args) => {
|
|
2986
|
+
const normalized = normalizeIncomingArgs(tool, args);
|
|
2987
|
+
const refusal = gateable ? integrationManager2.precheckBuiltin?.(tool, normalized) : null;
|
|
2988
|
+
if (refusal) return { content: [{ type: "text", text: refusal }], isError: true };
|
|
2989
|
+
return gated(normalized);
|
|
2990
|
+
};
|
|
3035
2991
|
const blocked = tool.gating?.blockedArgs ?? [];
|
|
3036
2992
|
if (blocked.length === 0) return inner;
|
|
3037
2993
|
return async (args) => {
|
|
@@ -4555,16 +4511,16 @@ function verifyGateContentHashes(content, auth) {
|
|
|
4555
4511
|
}
|
|
4556
4512
|
|
|
4557
4513
|
// src/lib/integration-registry.ts
|
|
4558
|
-
import { mkdirSync as
|
|
4514
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync12 } from "fs";
|
|
4559
4515
|
import { homedir as homedir8 } from "os";
|
|
4560
|
-
import { dirname as
|
|
4561
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
4516
|
+
import { dirname as dirname10, join as join12 } from "path";
|
|
4517
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join12(homedir8(), ".suveren");
|
|
4562
4518
|
var IntegrationRegistry = class {
|
|
4563
4519
|
integrations = /* @__PURE__ */ new Map();
|
|
4564
4520
|
filePath;
|
|
4565
4521
|
constructor(dataDir2) {
|
|
4566
4522
|
const dir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
4567
|
-
this.filePath =
|
|
4523
|
+
this.filePath = join12(dir, "integrations.json");
|
|
4568
4524
|
this.load();
|
|
4569
4525
|
}
|
|
4570
4526
|
// ─── CRUD ───────────────────────────────────────────────────────────────
|
|
@@ -4595,12 +4551,12 @@ var IntegrationRegistry = class {
|
|
|
4595
4551
|
}
|
|
4596
4552
|
// ─── Load / Save ────────────────────────────────────────────────────────
|
|
4597
4553
|
load() {
|
|
4598
|
-
if (!
|
|
4599
|
-
|
|
4554
|
+
if (!existsSync12(this.filePath)) {
|
|
4555
|
+
mkdirSync10(dirname10(this.filePath), { recursive: true });
|
|
4600
4556
|
return;
|
|
4601
4557
|
}
|
|
4602
4558
|
try {
|
|
4603
|
-
const raw =
|
|
4559
|
+
const raw = readFileSync13(this.filePath, "utf-8");
|
|
4604
4560
|
const data = JSON.parse(raw);
|
|
4605
4561
|
const migrated = data.integrations.map((i) => {
|
|
4606
4562
|
if ("toolGating" in i && !("profile" in i)) {
|
|
@@ -4629,20 +4585,80 @@ var IntegrationRegistry = class {
|
|
|
4629
4585
|
version: 1,
|
|
4630
4586
|
integrations: Array.from(this.integrations.values())
|
|
4631
4587
|
};
|
|
4632
|
-
|
|
4633
|
-
|
|
4588
|
+
mkdirSync10(dirname10(this.filePath), { recursive: true });
|
|
4589
|
+
writeFileSync10(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
4634
4590
|
}
|
|
4635
4591
|
};
|
|
4636
4592
|
|
|
4637
4593
|
// src/lib/integration-manager.ts
|
|
4638
4594
|
import { homedir as homedir9 } from "os";
|
|
4639
4595
|
import { join as join14, delimiter } from "path";
|
|
4640
|
-
import { existsSync as existsSync14, writeFileSync as
|
|
4596
|
+
import { existsSync as existsSync14, writeFileSync as writeFileSync11, mkdirSync as mkdirSync11, readFileSync as readFileSync15, rmSync } from "fs";
|
|
4641
4597
|
import { execFile } from "child_process";
|
|
4642
4598
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
4643
4599
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4644
4600
|
import { getProfile as getProfile5 } from "@hap/core";
|
|
4645
4601
|
|
|
4602
|
+
// src/lib/manifest-loader.ts
|
|
4603
|
+
import { readFileSync as readFileSync14, existsSync as existsSync13 } from "fs";
|
|
4604
|
+
import { join as join13, resolve } from "path";
|
|
4605
|
+
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
4606
|
+
function isExactSemver(value) {
|
|
4607
|
+
return typeof value === "string" && EXACT_SEMVER_RE.test(value);
|
|
4608
|
+
}
|
|
4609
|
+
function invalidNpmPinReason(manifest) {
|
|
4610
|
+
if (!manifest.npmPackage) return null;
|
|
4611
|
+
if (manifest.npmVersion === void 0) {
|
|
4612
|
+
return `npmPackage "${manifest.npmPackage}" is set but npmVersion is missing \u2014 every connector manifest must pin an exact tested version`;
|
|
4613
|
+
}
|
|
4614
|
+
if (!isExactSemver(manifest.npmVersion)) {
|
|
4615
|
+
return `npmVersion ${JSON.stringify(manifest.npmVersion)} for "${manifest.npmPackage}" is not an exact semver (e.g. "1.2.3") \u2014 ranges, dist-tags like "latest", git specs, URLs, and file paths are not allowed`;
|
|
4616
|
+
}
|
|
4617
|
+
return null;
|
|
4618
|
+
}
|
|
4619
|
+
var manifests = /* @__PURE__ */ new Map();
|
|
4620
|
+
function loadManifests(integrationsDir) {
|
|
4621
|
+
const dir = resolve(
|
|
4622
|
+
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join13(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
4623
|
+
);
|
|
4624
|
+
const indexPath = join13(dir, "index.json");
|
|
4625
|
+
if (!existsSync13(indexPath)) {
|
|
4626
|
+
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
4627
|
+
return 0;
|
|
4628
|
+
}
|
|
4629
|
+
let index;
|
|
4630
|
+
try {
|
|
4631
|
+
index = JSON.parse(readFileSync14(indexPath, "utf-8"));
|
|
4632
|
+
} catch (err) {
|
|
4633
|
+
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
4634
|
+
return 0;
|
|
4635
|
+
}
|
|
4636
|
+
let loaded = 0;
|
|
4637
|
+
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
4638
|
+
const manifestPath = join13(dir, relativePath);
|
|
4639
|
+
try {
|
|
4640
|
+
const manifest = JSON.parse(readFileSync14(manifestPath, "utf-8"));
|
|
4641
|
+
const pinReason = invalidNpmPinReason(manifest);
|
|
4642
|
+
if (pinReason) {
|
|
4643
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
|
|
4644
|
+
continue;
|
|
4645
|
+
}
|
|
4646
|
+
manifests.set(id, manifest);
|
|
4647
|
+
loaded++;
|
|
4648
|
+
} catch (err) {
|
|
4649
|
+
console.error(`[ManifestLoader] Failed to load manifest ${id} from ${manifestPath}:`, err);
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
console.error(`[ManifestLoader] Loaded ${loaded} integration manifest(s) from ${dir}`);
|
|
4653
|
+
return loaded;
|
|
4654
|
+
}
|
|
4655
|
+
function getManifest(id) {
|
|
4656
|
+
return manifests.get(id);
|
|
4657
|
+
}
|
|
4658
|
+
function getAllManifests() {
|
|
4659
|
+
return Array.from(manifests.values());
|
|
4660
|
+
}
|
|
4661
|
+
|
|
4646
4662
|
// src/lib/remote-auth-preflight.ts
|
|
4647
4663
|
var AUTH_HEADER_RE = /^authorization:\s*(.+)$/i;
|
|
4648
4664
|
function remotePreflightTarget(command, args) {
|
|
@@ -4730,11 +4746,11 @@ function buildPath() {
|
|
|
4730
4746
|
}
|
|
4731
4747
|
function ensureIntegrationsDir() {
|
|
4732
4748
|
if (!existsSync14(INTEGRATIONS_DIR)) {
|
|
4733
|
-
|
|
4749
|
+
mkdirSync11(INTEGRATIONS_DIR, { recursive: true });
|
|
4734
4750
|
}
|
|
4735
4751
|
const pkgPath = join14(INTEGRATIONS_DIR, "package.json");
|
|
4736
4752
|
if (!existsSync14(pkgPath)) {
|
|
4737
|
-
|
|
4753
|
+
writeFileSync11(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
4738
4754
|
}
|
|
4739
4755
|
}
|
|
4740
4756
|
function readAgeOf(config) {
|
|
@@ -4762,6 +4778,8 @@ var IntegrationManager = class {
|
|
|
4762
4778
|
this.serviceCredentials = serviceCredentials2;
|
|
4763
4779
|
}
|
|
4764
4780
|
running = /* @__PURE__ */ new Map();
|
|
4781
|
+
/** In-process tool groups (see builtin-integration.ts) — kept apart from `running` on purpose. */
|
|
4782
|
+
builtins = /* @__PURE__ */ new Map();
|
|
4765
4783
|
onToolsChanged = null;
|
|
4766
4784
|
/**
|
|
4767
4785
|
* Called when a persisted config was migrated to the manifest's command/args
|
|
@@ -5190,9 +5208,82 @@ var IntegrationManager = class {
|
|
|
5190
5208
|
this.onToolsChanged?.();
|
|
5191
5209
|
}
|
|
5192
5210
|
/**
|
|
5193
|
-
*
|
|
5211
|
+
* Register an in-process tool group (see builtin-integration.ts). Its tools are
|
|
5212
|
+
* listed, gated and executed through the same path as a connector's; only the
|
|
5213
|
+
* final call runs `handler` instead of a child MCP client. Refused when the id
|
|
5214
|
+
* is unusable or already taken, so a built-in can never shadow a connector.
|
|
5215
|
+
*/
|
|
5216
|
+
registerBuiltin(def) {
|
|
5217
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(def.id) || def.id.includes("__")) {
|
|
5218
|
+
throw new Error(`Built-in integration id "${def.id}" must be lowercase letters, digits and '-'.`);
|
|
5219
|
+
}
|
|
5220
|
+
if (this.builtins.has(def.id) || this.running.has(def.id) || getManifest(def.id)) {
|
|
5221
|
+
throw new Error(`Built-in integration id "${def.id}" is already in use.`);
|
|
5222
|
+
}
|
|
5223
|
+
const names = /* @__PURE__ */ new Set();
|
|
5224
|
+
const tools = def.tools.map((tool) => {
|
|
5225
|
+
if (names.has(tool.name)) throw new Error(`Built-in "${def.id}": tool "${tool.name}" is declared twice.`);
|
|
5226
|
+
names.add(tool.name);
|
|
5227
|
+
const gating = this.resolveToolGating(def.profile, def.toolGating, tool.name);
|
|
5228
|
+
return {
|
|
5229
|
+
originalName: tool.name,
|
|
5230
|
+
namespacedName: `${def.id}__${tool.name}`,
|
|
5231
|
+
integrationId: def.id,
|
|
5232
|
+
description: tool.description,
|
|
5233
|
+
inputSchema: withoutBlockedArgs(tool.inputSchema, gating?.blockedArgs),
|
|
5234
|
+
gating
|
|
5235
|
+
};
|
|
5236
|
+
});
|
|
5237
|
+
this.builtins.set(def.id, { def, tools });
|
|
5238
|
+
console.error(`[IntegrationManager] Built-in ${def.id}: ${tools.length} tool(s) under profile ${def.profile}`);
|
|
5239
|
+
this.onToolsChanged?.();
|
|
5240
|
+
}
|
|
5241
|
+
/** Whether `id` names a built-in (in-process) integration. */
|
|
5242
|
+
isBuiltin(id) {
|
|
5243
|
+
return this.builtins.has(id);
|
|
5244
|
+
}
|
|
5245
|
+
/**
|
|
5246
|
+
* Whether `id` may run while simulation mode is on: a connector whose manifest
|
|
5247
|
+
* carries the `simulation` marker, or a built-in that declares `simulation: true`.
|
|
5248
|
+
*/
|
|
5249
|
+
isSimulationSafe(id) {
|
|
5250
|
+
const builtin = this.builtins.get(id);
|
|
5251
|
+
if (builtin) return builtin.def.simulation === true;
|
|
5252
|
+
return manifestIsSimulated(getManifest(id));
|
|
5253
|
+
}
|
|
5254
|
+
/**
|
|
5255
|
+
* The built-in's own refusal for this call, checked before the gate (no proposal,
|
|
5256
|
+
* no ticket): `simulationOnly` outside simulation mode, then the tool's `validate`.
|
|
5257
|
+
* Null for connector tools and for calls that may proceed.
|
|
5258
|
+
*/
|
|
5259
|
+
precheckBuiltin(tool, args) {
|
|
5260
|
+
const builtin = this.builtins.get(tool.integrationId);
|
|
5261
|
+
if (!builtin) return null;
|
|
5262
|
+
if (builtin.def.simulationOnly && !isSimulationMode()) {
|
|
5263
|
+
return `Refused: "${tool.namespacedName}" is not available. No ticket was requested.`;
|
|
5264
|
+
}
|
|
5265
|
+
const def = builtin.def.tools.find((t) => t.name === tool.originalName);
|
|
5266
|
+
const refusal = def?.validate?.(args);
|
|
5267
|
+
return refusal ? `Refused: ${refusal} No ticket was requested.` : null;
|
|
5268
|
+
}
|
|
5269
|
+
/**
|
|
5270
|
+
* Proxy a tool call to a downstream MCP server (or a built-in's handler).
|
|
5194
5271
|
*/
|
|
5195
5272
|
async callTool(integrationId, toolName, args) {
|
|
5273
|
+
const builtin = this.builtins.get(integrationId);
|
|
5274
|
+
if (builtin) {
|
|
5275
|
+
const tool = builtin.def.tools.find((t) => t.name === toolName);
|
|
5276
|
+
if (!tool) {
|
|
5277
|
+
return { content: [{ type: "text", text: `Tool "${integrationId}__${toolName}" does not exist.` }], isError: true };
|
|
5278
|
+
}
|
|
5279
|
+
try {
|
|
5280
|
+
const result = await tool.handler(args);
|
|
5281
|
+
return { content: result.content.map((c) => ({ type: c.type, text: c.text })), isError: result.isError };
|
|
5282
|
+
} catch (err) {
|
|
5283
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5284
|
+
return { content: [{ type: "text", text: `Tool call failed: ${message}` }], isError: true };
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5196
5287
|
const entry = this.running.get(integrationId);
|
|
5197
5288
|
if (!entry) {
|
|
5198
5289
|
return {
|
|
@@ -5223,6 +5314,9 @@ var IntegrationManager = class {
|
|
|
5223
5314
|
for (const entry of this.running.values()) {
|
|
5224
5315
|
tools.push(...entry.tools);
|
|
5225
5316
|
}
|
|
5317
|
+
for (const entry of this.builtins.values()) {
|
|
5318
|
+
tools.push(...entry.tools);
|
|
5319
|
+
}
|
|
5226
5320
|
return tools;
|
|
5227
5321
|
}
|
|
5228
5322
|
/**
|
|
@@ -5497,12 +5591,87 @@ function warnNoProfiles(reason) {
|
|
|
5497
5591
|
);
|
|
5498
5592
|
}
|
|
5499
5593
|
|
|
5594
|
+
// src/lib/builtin-integration.ts
|
|
5595
|
+
function builtinText(text) {
|
|
5596
|
+
return { content: [{ type: "text", text }] };
|
|
5597
|
+
}
|
|
5598
|
+
|
|
5599
|
+
// src/lib/builtins/setup.ts
|
|
5600
|
+
var DELEGATION_PROFILE = "github.com/humanagencyprotocol/hap-profiles/delegation@0.1";
|
|
5601
|
+
function briefRefusal(args) {
|
|
5602
|
+
if (typeof args.content !== "string" || args.content.trim() === "") return "`content` must be the complete new brief (markdown text).";
|
|
5603
|
+
const bytes = Buffer.byteLength(args.content, "utf-8");
|
|
5604
|
+
if (bytes > CONTEXT_MAX_BYTES) return `the brief is ${bytes} bytes; the limit is ${CONTEXT_MAX_BYTES}. Shorten it.`;
|
|
5605
|
+
return void 0;
|
|
5606
|
+
}
|
|
5607
|
+
function setupBuiltin(_deps) {
|
|
5608
|
+
return {
|
|
5609
|
+
id: "setup",
|
|
5610
|
+
name: "Test setup",
|
|
5611
|
+
profile: DELEGATION_PROFILE,
|
|
5612
|
+
simulation: true,
|
|
5613
|
+
simulationOnly: true,
|
|
5614
|
+
toolGating: {
|
|
5615
|
+
overrides: {
|
|
5616
|
+
set_agent_brief: {
|
|
5617
|
+
executionMapping: {},
|
|
5618
|
+
staticExecution: { action_type: "brief" },
|
|
5619
|
+
hideUnlessAuthorized: true
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
},
|
|
5623
|
+
tools: [
|
|
5624
|
+
{
|
|
5625
|
+
name: "set_agent_brief",
|
|
5626
|
+
description: "Simulation mode only: propose a new agent brief \u2014 the standing instructions every agent session starts with. `content` replaces the whole brief (markdown, at most 16 KB). A person approves or rejects the proposal; only after approval is the brief replaced, and it applies from the next session. Write the complete brief, not a change to the current one.",
|
|
5627
|
+
inputSchema: {
|
|
5628
|
+
type: "object",
|
|
5629
|
+
properties: {
|
|
5630
|
+
content: { type: "string", description: "The complete new agent brief, in markdown (at most 16 KB)." },
|
|
5631
|
+
receipt_id: {
|
|
5632
|
+
type: "string",
|
|
5633
|
+
description: "Authorization reference for this call, set by the governing gateway \u2014 agents do not set this."
|
|
5634
|
+
}
|
|
5635
|
+
},
|
|
5636
|
+
required: ["content"]
|
|
5637
|
+
},
|
|
5638
|
+
validate: briefRefusal,
|
|
5639
|
+
handler: async (args) => {
|
|
5640
|
+
if (!isSimulationMode()) return { ...builtinText("Refused: not available outside simulation mode."), isError: true };
|
|
5641
|
+
const refusal = briefRefusal(args);
|
|
5642
|
+
if (refusal) return { ...builtinText(`Refused: ${refusal}`), isError: true };
|
|
5643
|
+
writeContextFile(args.content);
|
|
5644
|
+
return builtinText(
|
|
5645
|
+
`Agent brief replaced (${Buffer.byteLength(args.content, "utf-8")} bytes). It applies from the next agent session.`
|
|
5646
|
+
);
|
|
5647
|
+
}
|
|
5648
|
+
}
|
|
5649
|
+
]
|
|
5650
|
+
};
|
|
5651
|
+
}
|
|
5652
|
+
|
|
5653
|
+
// src/lib/builtins/index.ts
|
|
5654
|
+
var BUILTIN_FACTORIES = [setupBuiltin];
|
|
5655
|
+
function registerBuiltins(deps, factories = BUILTIN_FACTORIES) {
|
|
5656
|
+
const registered = [];
|
|
5657
|
+
for (const factory of factories) {
|
|
5658
|
+
try {
|
|
5659
|
+
const def = factory(deps);
|
|
5660
|
+
deps.integrationManager.registerBuiltin(def);
|
|
5661
|
+
registered.push(def.id);
|
|
5662
|
+
} catch (err) {
|
|
5663
|
+
console.error(`[Suveren MCP] Built-in tool group not registered: ${err instanceof Error ? err.message : String(err)}`);
|
|
5664
|
+
}
|
|
5665
|
+
}
|
|
5666
|
+
return registered;
|
|
5667
|
+
}
|
|
5668
|
+
|
|
5500
5669
|
// bin/http.ts
|
|
5501
5670
|
import { decodeAttestationBlob as decodeAttestationBlob3 } from "@hap/core";
|
|
5502
5671
|
|
|
5503
5672
|
// src/lib/committed-executor.ts
|
|
5504
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
5505
|
-
import { dirname as
|
|
5673
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync12, readFileSync as readFileSync17, unlinkSync as unlinkSync6, writeFileSync as writeFileSync12 } from "fs";
|
|
5674
|
+
import { dirname as dirname11, join as join16 } from "path";
|
|
5506
5675
|
var CommittedExecutor = class {
|
|
5507
5676
|
constructor(run) {
|
|
5508
5677
|
this.run = run;
|
|
@@ -5538,9 +5707,9 @@ var ExecutorLock = class {
|
|
|
5538
5707
|
if (current && current.pid !== process.pid && isAlive(current.pid)) {
|
|
5539
5708
|
return { held: false, holderPid: current.pid };
|
|
5540
5709
|
}
|
|
5541
|
-
|
|
5710
|
+
mkdirSync12(dirname11(this.path), { recursive: true, mode: 448 });
|
|
5542
5711
|
const lock = { pid: process.pid, startedAt: Math.floor(Date.now() / 1e3) };
|
|
5543
|
-
|
|
5712
|
+
writeFileSync12(this.path, JSON.stringify(lock), { mode: 384 });
|
|
5544
5713
|
this.held = true;
|
|
5545
5714
|
return { held: true };
|
|
5546
5715
|
}
|
|
@@ -5585,8 +5754,8 @@ import { homedir as homedir10 } from "os";
|
|
|
5585
5754
|
import { join as join18 } from "path";
|
|
5586
5755
|
|
|
5587
5756
|
// src/lib/mcp-as-tracker.ts
|
|
5588
|
-
import { existsSync as existsSync17, mkdirSync as
|
|
5589
|
-
import { dirname as
|
|
5757
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync13, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "fs";
|
|
5758
|
+
import { dirname as dirname12, join as join17 } from "path";
|
|
5590
5759
|
function trackerPath(dataDir2) {
|
|
5591
5760
|
return join17(dataDir2, "mcp-as-pairing.json");
|
|
5592
5761
|
}
|
|
@@ -5602,8 +5771,8 @@ function readMcpPairedAsUrl(dataDir2) {
|
|
|
5602
5771
|
}
|
|
5603
5772
|
function writeMcpPairedAsUrl(dataDir2, asUrl) {
|
|
5604
5773
|
const path = trackerPath(dataDir2);
|
|
5605
|
-
|
|
5606
|
-
|
|
5774
|
+
mkdirSync13(dirname12(path), { recursive: true, mode: 448 });
|
|
5775
|
+
writeFileSync13(path, JSON.stringify({ asUrl }, null, 2), { encoding: "utf-8", mode: 384 });
|
|
5607
5776
|
}
|
|
5608
5777
|
|
|
5609
5778
|
// bin/http.ts
|
|
@@ -6092,6 +6261,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
6092
6261
|
console.error(`[Suveren MCP] SP server: ${spUrl}`);
|
|
6093
6262
|
profilesLoaded = loadProfiles();
|
|
6094
6263
|
loadManifests();
|
|
6264
|
+
registerBuiltins({ state, integrationManager });
|
|
6095
6265
|
if (process.env.SUVEREN_DISABLE_AUTO_INTEGRATIONS !== "1" && integrationRegistry.getEnabled().length === 0) {
|
|
6096
6266
|
const personalManifests = getAllManifests().filter((m) => m.personalDefault);
|
|
6097
6267
|
for (const manifest of personalManifests) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suveren/gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Suveren gateway — local agent gateway built in compliance with the Human Agency Protocol (HAP). Runs the UI, control plane, and MCP server in one Node process.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "server.js",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "github.com/humanagencyprotocol/hap-profiles/delegation@0.1",
|
|
3
|
+
"name": "Delegation",
|
|
4
|
+
"version": "0.1",
|
|
5
|
+
"description": "Delegation authority — lets an agent propose changes to its own setup: the agent brief and new mandates. Every proposal waits for a person's approval; nothing takes effect before it. Also lets the agent read the setup guides.",
|
|
6
|
+
"commitment_modes": [
|
|
7
|
+
"review"
|
|
8
|
+
],
|
|
9
|
+
"boundsSchema": {
|
|
10
|
+
"actionTypes": [
|
|
11
|
+
"brief",
|
|
12
|
+
"mandate"
|
|
13
|
+
],
|
|
14
|
+
"keyOrder": [
|
|
15
|
+
"profile",
|
|
16
|
+
"read_access",
|
|
17
|
+
"brief_daily_max",
|
|
18
|
+
"mandate_daily_max"
|
|
19
|
+
],
|
|
20
|
+
"fields": {
|
|
21
|
+
"profile": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"required": true
|
|
24
|
+
},
|
|
25
|
+
"read_access": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"required": true,
|
|
28
|
+
"displayName": "Read setup guides",
|
|
29
|
+
"description": "Allow the agent to read the guides for setting up a test: interview, test data, mandates, agent brief, the agent's own process, risks.",
|
|
30
|
+
"boundType": {
|
|
31
|
+
"kind": "enum",
|
|
32
|
+
"values": [
|
|
33
|
+
"unlimited",
|
|
34
|
+
"none"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"toolsDescription": "setup guides",
|
|
38
|
+
"default": "unlimited"
|
|
39
|
+
},
|
|
40
|
+
"brief_daily_max": {
|
|
41
|
+
"type": "number",
|
|
42
|
+
"required": true,
|
|
43
|
+
"displayName": "Brief proposals per day",
|
|
44
|
+
"description": "How many times per day the agent may propose a new agent brief. Each proposal waits for your approval.",
|
|
45
|
+
"unit": "count",
|
|
46
|
+
"boundType": {
|
|
47
|
+
"kind": "cumulative_count",
|
|
48
|
+
"window": "daily"
|
|
49
|
+
},
|
|
50
|
+
"appliesTo": [
|
|
51
|
+
"brief"
|
|
52
|
+
],
|
|
53
|
+
"default": 0
|
|
54
|
+
},
|
|
55
|
+
"mandate_daily_max": {
|
|
56
|
+
"type": "number",
|
|
57
|
+
"required": true,
|
|
58
|
+
"displayName": "Mandate proposals per day",
|
|
59
|
+
"description": "How many new mandates per day the agent may propose. Each one waits for your approval and is created only then.",
|
|
60
|
+
"unit": "count",
|
|
61
|
+
"boundType": {
|
|
62
|
+
"kind": "cumulative_count",
|
|
63
|
+
"window": "daily"
|
|
64
|
+
},
|
|
65
|
+
"appliesTo": [
|
|
66
|
+
"mandate"
|
|
67
|
+
],
|
|
68
|
+
"default": 0
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"executionContextSchema": {
|
|
73
|
+
"fields": {
|
|
74
|
+
"brief_count_daily": {
|
|
75
|
+
"source": "cumulative",
|
|
76
|
+
"cumulativeField": "_count",
|
|
77
|
+
"window": "daily",
|
|
78
|
+
"description": "Running daily count of brief proposals",
|
|
79
|
+
"required": true,
|
|
80
|
+
"constraint": {
|
|
81
|
+
"type": "number",
|
|
82
|
+
"enforceable": [
|
|
83
|
+
"max"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
"mandate_count_daily": {
|
|
88
|
+
"source": "cumulative",
|
|
89
|
+
"cumulativeField": "_count",
|
|
90
|
+
"window": "daily",
|
|
91
|
+
"description": "Running daily count of mandate proposals",
|
|
92
|
+
"required": true,
|
|
93
|
+
"constraint": {
|
|
94
|
+
"type": "number",
|
|
95
|
+
"enforceable": [
|
|
96
|
+
"max"
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"requiredGates": [
|
|
103
|
+
"bounds",
|
|
104
|
+
"intent",
|
|
105
|
+
"commitment",
|
|
106
|
+
"decision_owner"
|
|
107
|
+
],
|
|
108
|
+
"ttl": {
|
|
109
|
+
"default": 86400,
|
|
110
|
+
"max": 2592000
|
|
111
|
+
},
|
|
112
|
+
"retention_minimum": 7776000,
|
|
113
|
+
"content_binding": {
|
|
114
|
+
"version": "1",
|
|
115
|
+
"kind": "jcs"
|
|
116
|
+
}
|
|
117
|
+
}
|
package/profiles/index.json
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"github.com/humanagencyprotocol/hap-profiles/email@0.7": "email/0.7.profile.json",
|
|
30
30
|
"github.com/humanagencyprotocol/hap-profiles/sales@0.3": "sales/0.3.profile.json",
|
|
31
31
|
"github.com/humanagencyprotocol/hap-profiles/purchase@0.6": "purchase/0.6.profile.json",
|
|
32
|
-
"github.com/humanagencyprotocol/hap-profiles/reporting@0.1": "reporting/0.1.profile.json"
|
|
32
|
+
"github.com/humanagencyprotocol/hap-profiles/reporting@0.1": "reporting/0.1.profile.json",
|
|
33
|
+
"github.com/humanagencyprotocol/hap-profiles/delegation@0.1": "delegation/0.1.profile.json"
|
|
33
34
|
}
|
|
34
35
|
}
|