@suveren/gateway 0.2.12 → 0.3.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/README.md +21 -7
- package/dist/control-plane/index.mjs +146 -2
- package/dist/mcp-server/http.mjs +80 -38
- package/dist/ui/assets/index-RkZBhIxU.js +105 -0
- package/dist/ui/index.html +1 -1
- package/node_modules/@hap/core/dist/index.d.mts +147 -3
- package/node_modules/@hap/core/dist/index.d.ts +147 -3
- package/node_modules/@hap/core/dist/index.js +74 -10
- package/node_modules/@hap/core/dist/index.mjs +69 -10
- package/node_modules/@hap/core/package.json +1 -1
- package/node_modules/@hap/core/src/gatekeeper.ts +4 -4
- package/node_modules/@hap/core/src/identity.ts +77 -0
- package/node_modules/@hap/core/src/index.ts +2 -0
- package/node_modules/@hap/core/src/intent-disclosure.ts +69 -0
- package/node_modules/@hap/core/src/types.ts +62 -2
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/README.md +16 -1
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/index.js +4 -20
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/read.js +17 -17
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/json.js +60 -32
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/raw.js +3 -3
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/text.js +3 -3
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/urlencoded.js +16 -20
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/utils.js +18 -16
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/LICENSE +22 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/README.md +69 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +26 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.js +170 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/package.json +52 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/package.json +23 -10
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/assert/strict.d.ts +10 -1
- package/node_modules/@types/node/assert.d.ts +144 -17
- package/node_modules/@types/node/async_hooks.d.ts +9 -9
- package/node_modules/@types/node/buffer.d.ts +0 -5
- package/node_modules/@types/node/child_process.d.ts +17 -21
- package/node_modules/@types/node/cluster.d.ts +13 -13
- package/node_modules/@types/node/console.d.ts +1 -1
- package/node_modules/@types/node/crypto.d.ts +454 -571
- package/node_modules/@types/node/diagnostics_channel.d.ts +37 -29
- package/node_modules/@types/node/dns.d.ts +14 -14
- package/node_modules/@types/node/events.d.ts +3 -3
- package/node_modules/@types/node/fs/promises.d.ts +4 -4
- package/node_modules/@types/node/fs.d.ts +3 -12
- package/node_modules/@types/node/globals.d.ts +4 -4
- package/node_modules/@types/node/http.d.ts +5 -5
- package/node_modules/@types/node/http2.d.ts +5 -1
- package/node_modules/@types/node/index.d.ts +0 -3
- package/node_modules/@types/node/inspector.d.ts +1 -1
- package/node_modules/@types/node/module.d.ts +16 -45
- package/node_modules/@types/node/net.d.ts +1 -1
- package/node_modules/@types/node/os.d.ts +2 -2
- package/node_modules/@types/node/package.json +4 -4
- package/node_modules/@types/node/perf_hooks.d.ts +1 -9
- package/node_modules/@types/node/process.d.ts +11 -11
- package/node_modules/@types/node/readline.d.ts +12 -11
- package/node_modules/@types/node/repl.d.ts +10 -10
- package/node_modules/@types/node/sqlite.d.ts +1 -1
- package/node_modules/@types/node/stream/promises.d.ts +2 -2
- package/node_modules/@types/node/stream/web.d.ts +1 -1
- package/node_modules/@types/node/stream.d.ts +5 -5
- package/node_modules/@types/node/test/reporters.d.ts +1 -1
- package/node_modules/@types/node/test.d.ts +8 -8
- package/node_modules/@types/node/tls.d.ts +1 -1
- package/node_modules/@types/node/ts5.6/index.d.ts +1 -4
- package/node_modules/@types/node/ts5.7/index.d.ts +0 -3
- package/node_modules/@types/node/url.d.ts +3 -3
- package/node_modules/@types/node/util/types.d.ts +1 -1
- package/node_modules/@types/node/util.d.ts +3 -20
- package/node_modules/@types/node/v8.d.ts +1 -1
- package/node_modules/@types/node/vm.d.ts +9 -9
- package/node_modules/@types/node/web-globals/domexception.d.ts +15 -0
- package/node_modules/@types/node/worker_threads.d.ts +2 -5
- package/node_modules/@types/node/zlib.d.ts +3 -3
- package/node_modules/fast-uri/index.js +1 -1
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/security.test.js +28 -0
- package/node_modules/hono/dist/adapter/aws-lambda/handler.js +14 -20
- package/node_modules/hono/dist/adapter/lambda-edge/handler.js +8 -2
- package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +14 -20
- package/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +8 -2
- package/node_modules/hono/dist/cjs/helper/css/common.js +3 -1
- package/node_modules/hono/dist/cjs/helper/css/index.js +9 -1
- package/node_modules/hono/dist/cjs/jsx/base.js +8 -14
- package/node_modules/hono/dist/cjs/jsx/components.js +41 -21
- package/node_modules/hono/dist/cjs/jsx/context.js +131 -5
- package/node_modules/hono/dist/cjs/jsx/streaming.js +9 -7
- package/node_modules/hono/dist/cjs/middleware/bearer-auth/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -5
- package/node_modules/hono/dist/cjs/middleware/language/language.js +10 -32
- package/node_modules/hono/dist/cjs/middleware/serve-static/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/timing/timing.js +3 -1
- package/node_modules/hono/dist/cjs/utils/ipaddr.js +6 -1
- package/node_modules/hono/dist/helper/css/common.js +3 -1
- package/node_modules/hono/dist/helper/css/index.js +9 -1
- package/node_modules/hono/dist/jsx/base.js +15 -15
- package/node_modules/hono/dist/jsx/components.js +42 -22
- package/node_modules/hono/dist/jsx/context.js +129 -5
- package/node_modules/hono/dist/jsx/streaming.js +10 -8
- package/node_modules/hono/dist/middleware/bearer-auth/index.js +1 -1
- package/node_modules/hono/dist/middleware/cors/index.js +2 -5
- package/node_modules/hono/dist/middleware/language/language.js +10 -32
- package/node_modules/hono/dist/middleware/serve-static/index.js +1 -1
- package/node_modules/hono/dist/middleware/timing/timing.js +3 -1
- package/node_modules/hono/dist/tsconfig.build.tsbuildinfo +1 -1
- package/node_modules/hono/dist/types/adapter/aws-lambda/handler.d.ts +1 -1
- package/node_modules/hono/dist/types/jsx/base.d.ts +1 -3
- package/node_modules/hono/dist/types/jsx/context.d.ts +39 -0
- package/node_modules/hono/dist/utils/ipaddr.js +6 -1
- package/node_modules/hono/package.json +19 -13
- package/node_modules/http-proxy-middleware/README.md +3 -10
- package/node_modules/http-proxy-middleware/dist/errors.d.ts +4 -0
- package/node_modules/http-proxy-middleware/dist/errors.js +16 -1
- package/node_modules/http-proxy-middleware/dist/factory.d.ts +2 -2
- package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body-utils/stringify-form-data.d.ts +7 -0
- package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body-utils/stringify-form-data.js +45 -0
- package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body.js +23 -23
- package/node_modules/http-proxy-middleware/dist/handlers/response-interceptor.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/handlers/response-interceptor.js +1 -1
- package/node_modules/http-proxy-middleware/dist/http-proxy-middleware.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/http-proxy-middleware.js +2 -2
- package/node_modules/http-proxy-middleware/dist/legacy/create-proxy-middleware.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/legacy/create-proxy-middleware.js +1 -1
- package/node_modules/http-proxy-middleware/dist/legacy/options-adapter.js +1 -1
- package/node_modules/http-proxy-middleware/dist/legacy/types.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/path-filter.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/path-filter.js +1 -1
- package/node_modules/http-proxy-middleware/dist/path-rewriter.js +1 -1
- package/node_modules/http-proxy-middleware/dist/plugins/default/error-response-plugin.js +2 -1
- package/node_modules/http-proxy-middleware/dist/plugins/default/logger-plugin.js +3 -3
- package/node_modules/http-proxy-middleware/dist/router.js +26 -8
- package/node_modules/http-proxy-middleware/dist/status-code.js +15 -12
- package/node_modules/http-proxy-middleware/dist/types.d.ts +3 -3
- package/node_modules/http-proxy-middleware/dist/types.js +0 -4
- package/node_modules/http-proxy-middleware/dist/utils/sanitize.d.ts +1 -0
- package/node_modules/http-proxy-middleware/dist/utils/sanitize.js +6 -0
- package/node_modules/http-proxy-middleware/package.json +30 -28
- package/node_modules/qs/CHANGELOG.md +11 -0
- package/node_modules/qs/dist/qs.js +17 -17
- package/node_modules/qs/lib/parse.js +19 -8
- package/node_modules/qs/lib/utils.js +47 -8
- package/node_modules/qs/package.json +6 -5
- package/node_modules/qs/test/parse.js +230 -0
- package/node_modules/qs/test/utils.js +194 -0
- package/node_modules/side-channel/CHANGELOG.md +10 -0
- package/node_modules/side-channel/README.md +1 -1
- package/node_modules/side-channel/index.js +5 -2
- package/node_modules/side-channel/package.json +10 -10
- package/node_modules/side-channel/test/index.js +16 -0
- package/node_modules/undici-types/agent.d.ts +0 -2
- package/node_modules/undici-types/client.d.ts +25 -19
- package/node_modules/undici-types/dispatcher.d.ts +7 -33
- package/node_modules/undici-types/dispatcher1-wrapper.d.ts +7 -0
- package/node_modules/undici-types/fetch.d.ts +24 -4
- package/node_modules/undici-types/h2c-client.d.ts +6 -6
- package/node_modules/undici-types/handlers.d.ts +1 -2
- package/node_modules/undici-types/header.d.ts +5 -0
- package/node_modules/undici-types/index.d.ts +3 -1
- package/node_modules/undici-types/interceptors.d.ts +1 -1
- package/node_modules/undici-types/package.json +1 -1
- package/node_modules/undici-types/pool.d.ts +0 -2
- package/node_modules/undici-types/proxy-agent.d.ts +2 -2
- package/node_modules/undici-types/round-robin-pool.d.ts +0 -2
- package/node_modules/undici-types/snapshot-agent.d.ts +4 -0
- package/node_modules/undici-types/socks5-proxy-agent.d.ts +2 -2
- package/node_modules/undici-types/webidl.d.ts +0 -1
- package/package.json +3 -3
- package/dist/ui/assets/index-CS16uhqb.js +0 -102
- package/node_modules/@types/node/compatibility/iterators.d.ts +0 -21
package/README.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# Suveren Gateway
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
> **HAP is the protocol. Suveren is an implementation of it.**
|
|
4
|
+
>
|
|
5
|
+
> The Human Agency Protocol (HAP) is the open standard for bounded AI-agent
|
|
6
|
+
> authority — it defines the roles (**Authority Server**, **Gatekeeper**,
|
|
7
|
+
> **Executor**) and the concepts (profiles, gates, attestations, bounds,
|
|
8
|
+
> context, receipts). **Suveren** implements them: this repo is the **Gateway**,
|
|
9
|
+
> and [`suveren-as`](https://www.suveren.ai) is the **Authority Server**. The
|
|
10
|
+
> protocol is open — anyone can build their own compliant gateway.
|
|
11
|
+
|
|
12
|
+
This repository is the **Suveren Gateway** — Suveren's implementation of the HAP
|
|
13
|
+
**Gatekeeper + Executor** roles. It runs locally and verifies every tool call
|
|
14
|
+
against its authorization before the call reaches an external service. (The
|
|
15
|
+
`@hap/core` library inside this repo keeps its HAP name because it re-exports the
|
|
16
|
+
open protocol library — "HAP-compliant" and spec references describe the open
|
|
17
|
+
standard, not the Suveren brand.)
|
|
4
18
|
|
|
5
19
|
Part of [suveren.ai](https://www.suveren.ai).
|
|
6
20
|
|
|
@@ -16,11 +30,11 @@ Works with any MCP-compatible agent. Define and authorize what they're allowed t
|
|
|
16
30
|
|
|
17
31
|
Set the threshold. Routine actions execute automatically within the bounds you defined. High-stakes actions pause for your review before the agent acts.
|
|
18
32
|
|
|
19
|
-
**Automatic** — You commit to specific bounds upfront: max amounts, allowed actions, time windows. The agent executes autonomously within those bounds.
|
|
33
|
+
**Automatic** — You commit to specific bounds upfront: max amounts, allowed actions, time windows. The agent executes autonomously within those bounds. For each tool call the gateway verifies your authorization and requests a receipt from the Authority Server, which issues the signed receipt before the call runs — no receipt, no execution.
|
|
20
34
|
|
|
21
35
|
**Review each action** — You define bounds but defer full commitment. When the agent proposes an action, you review it in the gateway UI — seeing exactly which tool, which arguments, which context. You approve or reject. Execution only proceeds after your decision.
|
|
22
36
|
|
|
23
|
-
Both modes are bounded.
|
|
37
|
+
Both modes are bounded. In both, the Authority Server issues a signed receipt before the action runs — no receipt, no execution — and that signed history is a full audit trail. The difference is whether you trust the bounds enough for autonomous execution, or want to review each action individually.
|
|
24
38
|
|
|
25
39
|
---
|
|
26
40
|
|
|
@@ -33,7 +47,7 @@ Human AI Agent
|
|
|
33
47
|
| articulate direction, |
|
|
34
48
|
| commit (or defer) |
|
|
35
49
|
v |
|
|
36
|
-
|
|
50
|
+
Authority Server |
|
|
37
51
|
| 2. Sign attestation (Ed25519) |
|
|
38
52
|
v |
|
|
39
53
|
Gateway |
|
|
@@ -41,14 +55,14 @@ Gateway |
|
|
|
41
55
|
| 4. Tool call <-----------|
|
|
42
56
|
| |
|
|
43
57
|
| 5. Fully committed: |
|
|
44
|
-
| Gatekeeper
|
|
45
|
-
| -> receipt
|
|
58
|
+
| Gatekeeper checks bounds, asks AS |
|
|
59
|
+
| -> AS issues receipt, execute |
|
|
46
60
|
| |
|
|
47
61
|
| Deferred commitment: |
|
|
48
62
|
| -> proposal created |
|
|
49
63
|
| -> human reviews in UI |
|
|
50
64
|
| -> commit or reject |
|
|
51
|
-
| ->
|
|
65
|
+
| -> on commit: AS receipt, execute |
|
|
52
66
|
```
|
|
53
67
|
|
|
54
68
|
The agent never holds credentials or signing authority. It acts within the bounds you set — high autonomy without losing accountability.
|
|
@@ -362,6 +362,13 @@ async function getGateContent(path) {
|
|
|
362
362
|
if (!res.ok) throw new Error("Failed to fetch gate content");
|
|
363
363
|
return res.json();
|
|
364
364
|
}
|
|
365
|
+
async function getEnrichedAuthorizations() {
|
|
366
|
+
const res = await fetch(`${MCP_BASE}/internal/authorizations`, {
|
|
367
|
+
headers: internalHeaders()
|
|
368
|
+
});
|
|
369
|
+
if (!res.ok) throw new Error("Failed to fetch authorizations");
|
|
370
|
+
return res.json();
|
|
371
|
+
}
|
|
365
372
|
async function getMcpHealth() {
|
|
366
373
|
const res = await fetch(`${MCP_BASE}/health`);
|
|
367
374
|
if (!res.ok) throw new Error("MCP server unreachable");
|
|
@@ -375,6 +382,7 @@ import {
|
|
|
375
382
|
HkdfSha256,
|
|
376
383
|
Aes256Gcm
|
|
377
384
|
} from "@hpke/core";
|
|
385
|
+
import { canonicalizeText } from "@hap/core";
|
|
378
386
|
function getSuite() {
|
|
379
387
|
return new CipherSuite({
|
|
380
388
|
kem: new DhkemX25519HkdfSha256(),
|
|
@@ -975,6 +983,84 @@ ${request.currentText}
|
|
|
975
983
|
};
|
|
976
984
|
}
|
|
977
985
|
}
|
|
986
|
+
async function callModel(config, messages, opts) {
|
|
987
|
+
if (config.provider === "ollama") {
|
|
988
|
+
const response2 = await fetch(`${config.endpoint}/api/chat`, {
|
|
989
|
+
method: "POST",
|
|
990
|
+
headers: { "Content-Type": "application/json" },
|
|
991
|
+
body: JSON.stringify({
|
|
992
|
+
model: config.model,
|
|
993
|
+
messages,
|
|
994
|
+
stream: false,
|
|
995
|
+
options: { temperature: opts.temperature, num_predict: opts.maxTokens }
|
|
996
|
+
}),
|
|
997
|
+
signal: AbortSignal.timeout(opts.timeoutMs)
|
|
998
|
+
});
|
|
999
|
+
if (!response2.ok) throw new Error(`Ollama: ${response2.status}`);
|
|
1000
|
+
const data2 = await response2.json();
|
|
1001
|
+
return data2.message?.content?.trim() || "No response generated.";
|
|
1002
|
+
}
|
|
1003
|
+
const headers = { "Content-Type": "application/json" };
|
|
1004
|
+
if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1005
|
+
const response = await fetch(`${config.endpoint}/chat/completions`, {
|
|
1006
|
+
method: "POST",
|
|
1007
|
+
headers,
|
|
1008
|
+
body: JSON.stringify({ model: config.model, messages, temperature: opts.temperature, max_tokens: opts.maxTokens }),
|
|
1009
|
+
signal: AbortSignal.timeout(opts.timeoutMs)
|
|
1010
|
+
});
|
|
1011
|
+
if (!response.ok) {
|
|
1012
|
+
const errorText = await response.text();
|
|
1013
|
+
throw new Error(`AI provider: ${response.status} - ${errorText}`);
|
|
1014
|
+
}
|
|
1015
|
+
const data = await response.json();
|
|
1016
|
+
return data.choices?.[0]?.message?.content?.trim() || "No response generated.";
|
|
1017
|
+
}
|
|
1018
|
+
var INTENT_REVIEW_PROMPT = `You are a reviewing assistant for Suveren. A human is creating a NEW agent authorization. Compare the MEANING of its Intent against the Intent of their EXISTING authorizations on the same profile.
|
|
1019
|
+
|
|
1020
|
+
Focus ONLY on the intent prose \u2014 the stated situation, goal, and soft rules / watch-outs. Surface:
|
|
1021
|
+
- Contradictions: a soft rule or goal in one intent that conflicts with another (e.g. one says "never email on weekends", the new one implies weekend sends; one says "formal tone only", another "keep it casual").
|
|
1022
|
+
- Tension: intent that works against, or ignores a caution stated in, another authorization.
|
|
1023
|
+
- Redundant purpose: the new intent describes essentially the same purpose as an existing one.
|
|
1024
|
+
|
|
1025
|
+
Do NOT report:
|
|
1026
|
+
- That scopes/recipients overlap, or that numeric limits differ \u2014 those are detected separately and already shown to the user. Ignore them entirely.
|
|
1027
|
+
- Anything not grounded in the actual intent text.
|
|
1028
|
+
|
|
1029
|
+
Use each authorization's scope ONLY to judge whether two intents' rules would actually collide in practice (overlapping targets) versus being independent \u2014 never report the scope overlap itself.
|
|
1030
|
+
|
|
1031
|
+
Rules:
|
|
1032
|
+
- Be concise \u2014 a few short bullets at most.
|
|
1033
|
+
- If the new intent has no real content yet (just placeholder prompts like "Why \u2014 \u2026", "Goal \u2014 \u2026", "Watch out \u2014 \u2026"), reply EXACTLY: "Not enough intent written yet to review."
|
|
1034
|
+
- If the intents are compatible, reply EXACTLY: "No intent conflicts found with your existing authorizations."
|
|
1035
|
+
- Refer to each existing authorization by its number in square brackets \u2014 e.g. [1], [2] \u2014 matching the numbered list provided, and you may also state its scope. Never invent names. Do NOT rewrite intent or make the decision \u2014 you surface; the human decides.`;
|
|
1036
|
+
async function getIntentReview(config, request) {
|
|
1037
|
+
const disclaimer = "AI surfaces reality. You decide.";
|
|
1038
|
+
const lines = [];
|
|
1039
|
+
lines.push(`NEW authorization on profile "${request.profileName}":`);
|
|
1040
|
+
if (request.scope) lines.push(` Scope (for context only): ${request.scope}`);
|
|
1041
|
+
lines.push(` Intent: ${request.newIntent.trim() || "(empty)"}`);
|
|
1042
|
+
lines.push("");
|
|
1043
|
+
lines.push("EXISTING authorizations on the same profile:");
|
|
1044
|
+
request.existingGrants.forEach((g, i) => {
|
|
1045
|
+
lines.push(` [${i + 1}]`);
|
|
1046
|
+
if (g.scope) lines.push(` Scope (for context only): ${g.scope}`);
|
|
1047
|
+
lines.push(` Intent: ${g.intent.trim() || "(empty)"}`);
|
|
1048
|
+
});
|
|
1049
|
+
const messages = [
|
|
1050
|
+
{ role: "system", content: INTENT_REVIEW_PROMPT },
|
|
1051
|
+
{ role: "user", content: lines.join("\n") }
|
|
1052
|
+
];
|
|
1053
|
+
try {
|
|
1054
|
+
const review = await callModel(config, messages, { temperature: 0.2, maxTokens: 400, timeoutMs: 45e3 });
|
|
1055
|
+
return { success: true, review, disclaimer };
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
return {
|
|
1058
|
+
success: false,
|
|
1059
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
1060
|
+
disclaimer
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
978
1064
|
async function testAIConnectivity(config) {
|
|
979
1065
|
try {
|
|
980
1066
|
if (config.provider === "ollama") {
|
|
@@ -999,6 +1085,10 @@ async function testAIConnectivity(config) {
|
|
|
999
1085
|
}
|
|
1000
1086
|
|
|
1001
1087
|
// src/routes/ai.ts
|
|
1088
|
+
var describeScope = (c = {}) => {
|
|
1089
|
+
const vals = Object.values(c).map((v) => String(v).trim()).filter(Boolean);
|
|
1090
|
+
return vals.length > 0 ? vals.join(", ") : "all (unscoped)";
|
|
1091
|
+
};
|
|
1002
1092
|
function createAIRouter(vault2) {
|
|
1003
1093
|
const router = Router3();
|
|
1004
1094
|
function loadAIConfig() {
|
|
@@ -1043,6 +1133,46 @@ function createAIRouter(vault2) {
|
|
|
1043
1133
|
const result = await getAIChatResponse(config, request);
|
|
1044
1134
|
res.json(result);
|
|
1045
1135
|
});
|
|
1136
|
+
router.post("/intent-review", async (req, res) => {
|
|
1137
|
+
const config = loadAIConfig();
|
|
1138
|
+
if (!config) {
|
|
1139
|
+
res.status(400).json({ error: "AI not configured. Save AI settings in Settings > General." });
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const { profileId, newIntent, context } = req.body;
|
|
1143
|
+
if (!profileId) {
|
|
1144
|
+
res.status(400).json({ error: "Missing profileId" });
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
let existing = [];
|
|
1148
|
+
try {
|
|
1149
|
+
const data = await getEnrichedAuthorizations();
|
|
1150
|
+
existing = (data.authorizations ?? []).filter((a) => a.profileId === profileId && !!a.intent?.trim());
|
|
1151
|
+
} catch {
|
|
1152
|
+
existing = [];
|
|
1153
|
+
}
|
|
1154
|
+
if (existing.length === 0) {
|
|
1155
|
+
res.json({ success: true, review: null, note: "No other authorizations on this profile to compare against." });
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
const result = await getIntentReview(config, {
|
|
1159
|
+
profileName: profileId.split("/").pop() ?? profileId,
|
|
1160
|
+
newIntent: newIntent ?? "",
|
|
1161
|
+
scope: describeScope(context),
|
|
1162
|
+
existingGrants: existing.map((a) => ({
|
|
1163
|
+
intent: a.intent ?? "",
|
|
1164
|
+
scope: describeScope(a.context)
|
|
1165
|
+
}))
|
|
1166
|
+
});
|
|
1167
|
+
res.json({
|
|
1168
|
+
...result,
|
|
1169
|
+
grants: existing.map((a) => ({
|
|
1170
|
+
scope: describeScope(a.context),
|
|
1171
|
+
intent: a.intent,
|
|
1172
|
+
bounds: a.bounds
|
|
1173
|
+
}))
|
|
1174
|
+
});
|
|
1175
|
+
});
|
|
1046
1176
|
router.post("/test", async (req, res) => {
|
|
1047
1177
|
const body = req.body;
|
|
1048
1178
|
let config;
|
|
@@ -1234,6 +1364,7 @@ function createMCPRouter() {
|
|
|
1234
1364
|
|
|
1235
1365
|
// src/routes/encrypt-intent.ts
|
|
1236
1366
|
import { Router as Router6 } from "express";
|
|
1367
|
+
import { computeIntentDisclosureHash } from "@hap/core";
|
|
1237
1368
|
function createEncryptIntentRouter() {
|
|
1238
1369
|
const router = Router6();
|
|
1239
1370
|
router.post("/", async (req, res) => {
|
|
@@ -1279,10 +1410,14 @@ function createEncryptIntentRouter() {
|
|
|
1279
1410
|
enc: Buffer.from(wrap.enc).toString("base64")
|
|
1280
1411
|
};
|
|
1281
1412
|
}
|
|
1413
|
+
const intentCiphertextB64 = Buffer.from(encrypted.intentCiphertext).toString("base64");
|
|
1414
|
+
const approversFrozen = parsed.map((r) => r.userId);
|
|
1415
|
+
const intentDisclosureHash = computeIntentDisclosureHash(intentCiphertextB64, approversFrozen);
|
|
1282
1416
|
res.json({
|
|
1283
|
-
intentCiphertext:
|
|
1417
|
+
intentCiphertext: intentCiphertextB64,
|
|
1284
1418
|
encryptedKeys,
|
|
1285
|
-
approversFrozen
|
|
1419
|
+
approversFrozen,
|
|
1420
|
+
intentDisclosureHash
|
|
1286
1421
|
});
|
|
1287
1422
|
} catch (err) {
|
|
1288
1423
|
console.error("[Control Plane] encrypt-intent error:", err);
|
|
@@ -1863,6 +1998,15 @@ app.get("/gate-content", authGuard, async (req, res) => {
|
|
|
1863
1998
|
res.status(500).json({ error: "Failed to fetch gate content from MCP server" });
|
|
1864
1999
|
}
|
|
1865
2000
|
});
|
|
2001
|
+
app.get("/active-authorizations", authGuard, async (_req, res) => {
|
|
2002
|
+
try {
|
|
2003
|
+
const data = await getEnrichedAuthorizations();
|
|
2004
|
+
res.json(data);
|
|
2005
|
+
} catch (err) {
|
|
2006
|
+
console.error("[Control Plane] Authorizations retrieval error:", err);
|
|
2007
|
+
res.status(500).json({ error: "Failed to fetch authorizations from MCP server" });
|
|
2008
|
+
}
|
|
2009
|
+
});
|
|
1866
2010
|
app.post("/gate-content", jsonParser, authGuard, async (req, res) => {
|
|
1867
2011
|
try {
|
|
1868
2012
|
const { frameHash, boundsHash, contextHash, context, path, gateContent } = req.body;
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -182,6 +182,11 @@ var SPClient = class {
|
|
|
182
182
|
};
|
|
183
183
|
|
|
184
184
|
// src/lib/attestation-cache.ts
|
|
185
|
+
import { decodeAttestationBlob } from "@hap/core";
|
|
186
|
+
function isCommitmentDowngrade(auth) {
|
|
187
|
+
const requiresReview = auth.signedCommitmentMode === "review" || auth.signedCommitmentMode === "review_above_cap";
|
|
188
|
+
return requiresReview && (auth.deferredCommitmentDomains ?? []).length === 0;
|
|
189
|
+
}
|
|
185
190
|
var AttestationCache = class {
|
|
186
191
|
constructor(spClient) {
|
|
187
192
|
this.spClient = spClient;
|
|
@@ -228,6 +233,18 @@ var AttestationCache = class {
|
|
|
228
233
|
if (!result.profile_id || !result.frame) return null;
|
|
229
234
|
const storageHash = result.frame_hash ?? result.bounds_hash;
|
|
230
235
|
const bounds = result.bounds ?? result.frame;
|
|
236
|
+
let signedCommitmentMode;
|
|
237
|
+
let subjects;
|
|
238
|
+
const firstBlob = result.attestations[0]?.blob;
|
|
239
|
+
if (firstBlob) {
|
|
240
|
+
try {
|
|
241
|
+
const payload = decodeAttestationBlob(firstBlob).payload;
|
|
242
|
+
signedCommitmentMode = payload.commitment_mode;
|
|
243
|
+
subjects = payload.subjects;
|
|
244
|
+
} catch {
|
|
245
|
+
signedCommitmentMode = void 0;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
231
248
|
const auth = {
|
|
232
249
|
frameHash: storageHash,
|
|
233
250
|
boundsHash: result.bounds_hash,
|
|
@@ -247,9 +264,11 @@ var AttestationCache = class {
|
|
|
247
264
|
requiredDomains: result.required_domains ?? [],
|
|
248
265
|
attestedDomains: result.attested_domains ?? [],
|
|
249
266
|
deferredCommitmentDomains: result.deferred_commitment_domains ?? [],
|
|
267
|
+
signedCommitmentMode,
|
|
268
|
+
subjects,
|
|
250
269
|
complete: result.complete
|
|
251
270
|
};
|
|
252
|
-
this.authorizations.set(auth.path, auth);
|
|
271
|
+
this.authorizations.set(auth.frameHash ?? auth.path, auth);
|
|
253
272
|
return auth;
|
|
254
273
|
}
|
|
255
274
|
/**
|
|
@@ -278,7 +297,7 @@ var AttestationCache = class {
|
|
|
278
297
|
* Cache an authorization directly (e.g., from SP response after creation).
|
|
279
298
|
*/
|
|
280
299
|
cacheAuthorization(auth) {
|
|
281
|
-
this.authorizations.set(auth.path, auth);
|
|
300
|
+
this.authorizations.set(auth.frameHash ?? auth.path, auth);
|
|
282
301
|
}
|
|
283
302
|
/**
|
|
284
303
|
* Remove a cached authorization by path. Called when the SP reports the
|
|
@@ -715,7 +734,7 @@ var SharedState = class {
|
|
|
715
734
|
getEnrichedAuthorizations() {
|
|
716
735
|
const authorizations = this.cache.getAllAuthorizations();
|
|
717
736
|
return authorizations.map((auth) => {
|
|
718
|
-
const gateEntry =
|
|
737
|
+
const gateEntry = (auth.frameHash ? this.gateStore.get(auth.frameHash) : null) ?? (auth.boundsHash ? this.gateStore.get(auth.boundsHash) : null) ?? this.gateStore.get(auth.path) ?? null;
|
|
719
738
|
return {
|
|
720
739
|
...auth,
|
|
721
740
|
gateContent: gateEntry?.gateContent ?? null,
|
|
@@ -905,20 +924,23 @@ function buildMandateBrief(opts) {
|
|
|
905
924
|
import { getProfile as getProfile3 } from "@hap/core";
|
|
906
925
|
|
|
907
926
|
// src/lib/receipt-footer.ts
|
|
927
|
+
import { deriveIdentityLine } from "@hap/core";
|
|
908
928
|
var AS_BASE = (process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai").replace(/\/$/, "");
|
|
929
|
+
var OPERATOR_NAME = process.env.SUVEREN_OPERATOR_NAME ?? "Suveren";
|
|
909
930
|
var FOOTER_PROFILES = /* @__PURE__ */ new Set(["email", "calendar", "publish"]);
|
|
910
931
|
var CONTENT_FIELD_CANDIDATES = ["body", "text", "description", "content"];
|
|
911
|
-
var FOOTER_MARKER = "\u2014 Sent by an AI agent
|
|
932
|
+
var FOOTER_MARKER = "\u2014 Sent by an AI agent";
|
|
912
933
|
function shouldAttachFooter() {
|
|
913
934
|
return true;
|
|
914
935
|
}
|
|
915
936
|
function verifyUrl(receiptId) {
|
|
916
937
|
return `${AS_BASE}/r/${receiptId}`;
|
|
917
938
|
}
|
|
918
|
-
function footerText(receiptId) {
|
|
939
|
+
function footerText(receiptId, subject) {
|
|
940
|
+
const line = deriveIdentityLine(subject, { operatorName: OPERATOR_NAME });
|
|
919
941
|
return `
|
|
920
942
|
|
|
921
|
-
${
|
|
943
|
+
\u2014 ${line}. Verify: ${verifyUrl(receiptId)}`;
|
|
922
944
|
}
|
|
923
945
|
function isStringType(t) {
|
|
924
946
|
return t === "string" || Array.isArray(t) && t.includes("string");
|
|
@@ -937,45 +959,34 @@ function stripFooter(value) {
|
|
|
937
959
|
if (i === -1) return value;
|
|
938
960
|
return value.slice(0, i).replace(/\n+$/, "");
|
|
939
961
|
}
|
|
940
|
-
function appendVerificationFooter(tool, args, receiptId) {
|
|
962
|
+
function appendVerificationFooter(tool, args, receiptId, subject) {
|
|
941
963
|
const profile = tool.gating?.profile;
|
|
942
964
|
if (!profile || !FOOTER_PROFILES.has(profile)) return args;
|
|
943
965
|
const field = detectContentField(tool);
|
|
944
966
|
if (!field) return args;
|
|
945
967
|
const current = typeof args[field] === "string" ? args[field] : "";
|
|
946
|
-
return { ...args, [field]: stripFooter(current) + footerText(receiptId) };
|
|
968
|
+
return { ...args, [field]: stripFooter(current) + footerText(receiptId, subject) };
|
|
947
969
|
}
|
|
948
970
|
|
|
949
971
|
// src/lib/content-binding.ts
|
|
950
|
-
import {
|
|
951
|
-
import { canonicalize, getProfile as getProfile2 } from "@hap/core";
|
|
952
|
-
function canonicalizeText(input) {
|
|
953
|
-
const nfc = input.normalize("NFC");
|
|
954
|
-
const lf = nfc.replace(/\r\n?/g, "\n");
|
|
955
|
-
const lines = lf.split("\n").map((line) => line.replace(/[ \t]+$/, ""));
|
|
956
|
-
return lines.join("\n").replace(/\n+$/, "");
|
|
957
|
-
}
|
|
958
|
-
function sha256Hex(bytes) {
|
|
959
|
-
return createHash("sha256").update(bytes, "utf8").digest("hex");
|
|
960
|
-
}
|
|
972
|
+
import { computeContentHash, getProfile as getProfile2 } from "@hap/core";
|
|
961
973
|
function getContentBinding(profileId) {
|
|
962
|
-
|
|
963
|
-
return profile?.content_binding;
|
|
974
|
+
return getProfile2(profileId)?.content_binding;
|
|
964
975
|
}
|
|
965
976
|
function computeContentBinding(profileId, tool, toolArgs) {
|
|
966
977
|
const binding = getContentBinding(profileId);
|
|
967
978
|
if (!binding) return void 0;
|
|
968
|
-
let
|
|
979
|
+
let contentHash;
|
|
969
980
|
if (binding.kind === "jcs") {
|
|
970
|
-
|
|
981
|
+
contentHash = computeContentHash(binding, toolArgs);
|
|
971
982
|
} else {
|
|
972
983
|
const field = tool ? detectContentField(tool) : null;
|
|
973
984
|
if (!field) return void 0;
|
|
974
985
|
const raw = typeof toolArgs[field] === "string" ? toolArgs[field] : "";
|
|
975
|
-
|
|
986
|
+
contentHash = computeContentHash(binding, raw);
|
|
976
987
|
}
|
|
977
988
|
return {
|
|
978
|
-
contentHash
|
|
989
|
+
contentHash,
|
|
979
990
|
contentBinding: { version: binding.version, kind: binding.kind }
|
|
980
991
|
};
|
|
981
992
|
}
|
|
@@ -1111,7 +1122,7 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
|
1111
1122
|
}
|
|
1112
1123
|
const errors = [];
|
|
1113
1124
|
for (const auth of matchingAuths) {
|
|
1114
|
-
const { result } = await state2.gatekeeper.verifyExecution(auth.path, execution, {
|
|
1125
|
+
const { result } = await state2.gatekeeper.verifyExecution(auth.frameHash ?? auth.path, execution, {
|
|
1115
1126
|
bounds: auth.bounds,
|
|
1116
1127
|
context: auth.context
|
|
1117
1128
|
});
|
|
@@ -1119,6 +1130,15 @@ function createGatedToolHandler(tool, integrationManager2, state2) {
|
|
|
1119
1130
|
}
|
|
1120
1131
|
if (result.approved) {
|
|
1121
1132
|
const authHash = auth.frameHash ?? auth.boundsHash;
|
|
1133
|
+
if (isCommitmentDowngrade(auth)) {
|
|
1134
|
+
return {
|
|
1135
|
+
content: [{
|
|
1136
|
+
type: "text",
|
|
1137
|
+
text: `Refusing to execute ${tool.originalName}: the signed authorization requires review (commitment_mode="${auth.signedCommitmentMode}") but the Authority Server returned no pending approvers. This inconsistency (a possible commitment-mode downgrade) is rejected fail-closed. Re-fetch the authorization or contact the Authority Server operator.`
|
|
1138
|
+
}],
|
|
1139
|
+
isError: true
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1122
1142
|
if ((auth.deferredCommitmentDomains ?? []).length > 0) {
|
|
1123
1143
|
try {
|
|
1124
1144
|
const enrichedArgs = await attachImagePreview(args);
|
|
@@ -1227,7 +1247,7 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
|
|
|
1227
1247
|
}
|
|
1228
1248
|
if (err instanceof SPReceiptError && err.statusCode === 403) {
|
|
1229
1249
|
if (/revoked/i.test(err.message)) {
|
|
1230
|
-
state2.cache.invalidate(auth.path);
|
|
1250
|
+
state2.cache.invalidate(auth.frameHash ?? auth.path);
|
|
1231
1251
|
}
|
|
1232
1252
|
return {
|
|
1233
1253
|
content: [{ type: "text", text: `Blocked by SP: ${err.message}` }],
|
|
@@ -1245,7 +1265,7 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.`
|
|
|
1245
1265
|
execution: { ...execution },
|
|
1246
1266
|
timestamp: Math.floor(Date.now() / 1e3)
|
|
1247
1267
|
});
|
|
1248
|
-
let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId) : args;
|
|
1268
|
+
let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId, auth.subjects?.[0]) : args;
|
|
1249
1269
|
if (receiptId) outgoingArgs = attachReceiptId(tool, outgoingArgs, receiptId);
|
|
1250
1270
|
return integrationManager2.callTool(tool.integrationId, tool.originalName, outgoingArgs);
|
|
1251
1271
|
}
|
|
@@ -1390,6 +1410,15 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
1390
1410
|
output2.push(`[${auth.path}] ${auth.profileId} (${remainingMin} min remaining)${statusLabel}`);
|
|
1391
1411
|
output2.push("");
|
|
1392
1412
|
output2.push(` Bounds: ${boundsDesc}`);
|
|
1413
|
+
const reviewDomains = auth.deferredCommitmentDomains ?? [];
|
|
1414
|
+
output2.push(reviewDomains.length > 0 ? " Mode: review \u2014 each action requires your approval before it runs (a proposal is created, not executed)" : " Mode: automatic \u2014 actions run immediately within bounds");
|
|
1415
|
+
try {
|
|
1416
|
+
const meta = await state2.spClient.getFrameMetadata(auth.frameHash ?? auth.boundsHash ?? auth.path);
|
|
1417
|
+
if (meta?.aboveCap) {
|
|
1418
|
+
output2.push(" \u26A0 Above team cap \u2014 actions here require approval even within these bounds.");
|
|
1419
|
+
}
|
|
1420
|
+
} catch {
|
|
1421
|
+
}
|
|
1393
1422
|
const shortName = shortProfileName2(auth.profileId);
|
|
1394
1423
|
const profile = getProfile3(auth.profileId) ?? getProfile3(shortName);
|
|
1395
1424
|
if (profile) {
|
|
@@ -1443,6 +1472,9 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
1443
1472
|
if (auth.complete) {
|
|
1444
1473
|
const lines = [` [${auth.path}] ${auth.profileId} \u2014 ${remainingMin} min remaining`];
|
|
1445
1474
|
lines.push(` Bounds: ${boundsDesc}`);
|
|
1475
|
+
if ((auth.deferredCommitmentDomains ?? []).length > 0) {
|
|
1476
|
+
lines.push(" Mode: review (each action requires your approval)");
|
|
1477
|
+
}
|
|
1446
1478
|
const shortName = shortProfileName2(auth.profileId);
|
|
1447
1479
|
const profile = getProfile3(auth.profileId) ?? getProfile3(shortName);
|
|
1448
1480
|
if (profile) {
|
|
@@ -1866,18 +1898,17 @@ function createMcpServer(state2, integrationManager2) {
|
|
|
1866
1898
|
}
|
|
1867
1899
|
|
|
1868
1900
|
// src/lib/gate-content.ts
|
|
1869
|
-
import {
|
|
1870
|
-
import { decodeAttestationBlob } from "@hap/core";
|
|
1901
|
+
import { computeIntentHash } from "@hap/core";
|
|
1902
|
+
import { decodeAttestationBlob as decodeAttestationBlob2 } from "@hap/core";
|
|
1871
1903
|
function hashGateContent(text) {
|
|
1872
|
-
|
|
1873
|
-
return `sha256:${hex}`;
|
|
1904
|
+
return computeIntentHash(text);
|
|
1874
1905
|
}
|
|
1875
1906
|
function verifyGateContentHashes(content, auth) {
|
|
1876
1907
|
const errors = [];
|
|
1877
1908
|
if (auth.attestations.length === 0) {
|
|
1878
1909
|
return { valid: false, errors: ["No attestations available to verify against"] };
|
|
1879
1910
|
}
|
|
1880
|
-
const attestation =
|
|
1911
|
+
const attestation = decodeAttestationBlob2(auth.attestations[0].blob);
|
|
1881
1912
|
const expectedHashes = attestation.payload.gate_content_hashes;
|
|
1882
1913
|
if (!expectedHashes?.intent) {
|
|
1883
1914
|
return { valid: false, errors: ["Attestation does not contain intent hash"] };
|
|
@@ -2477,7 +2508,7 @@ app.post("/internal/gate-content", internalOnly, async (req, res) => {
|
|
|
2477
2508
|
res.status(400).json({ error: "Gate content hash mismatch", details: verification.errors });
|
|
2478
2509
|
return;
|
|
2479
2510
|
}
|
|
2480
|
-
const path = rawPath ||
|
|
2511
|
+
const path = rawPath || storageHash;
|
|
2481
2512
|
state.setGateContent(path, storageHash, auth.profileId, gateContent, {
|
|
2482
2513
|
boundsHash,
|
|
2483
2514
|
contextHash,
|
|
@@ -2537,7 +2568,7 @@ app.post("/internal/resync-gates", internalOnly, async (_req, res) => {
|
|
|
2537
2568
|
const syncHash = gate.frameHash ?? gate.boundsHash;
|
|
2538
2569
|
const auth = await state.cache.syncAuthorization(syncHash);
|
|
2539
2570
|
if (auth) {
|
|
2540
|
-
state.setGateContent(
|
|
2571
|
+
state.setGateContent(syncHash, syncHash, auth.profileId, gate.gateContent, {
|
|
2541
2572
|
boundsHash: gate.boundsHash,
|
|
2542
2573
|
contextHash: gate.contextHash,
|
|
2543
2574
|
context: gate.context
|
|
@@ -2545,8 +2576,8 @@ app.post("/internal/resync-gates", internalOnly, async (_req, res) => {
|
|
|
2545
2576
|
synced++;
|
|
2546
2577
|
console.error(`[Suveren MCP] Re-synced gate: ${gate.path}`);
|
|
2547
2578
|
} else {
|
|
2548
|
-
state.cache.invalidate(
|
|
2549
|
-
state.gateStore.delete(
|
|
2579
|
+
state.cache.invalidate(syncHash);
|
|
2580
|
+
state.gateStore.delete(syncHash);
|
|
2550
2581
|
orphaned++;
|
|
2551
2582
|
console.error(`[Suveren MCP] Orphan gate purged (SP attestation deleted): ${gate.path}`);
|
|
2552
2583
|
}
|
|
@@ -2709,6 +2740,17 @@ app.get("/internal/gate-content", internalOnly, (req, res) => {
|
|
|
2709
2740
|
res.json({ entries: gates });
|
|
2710
2741
|
}
|
|
2711
2742
|
});
|
|
2743
|
+
app.get("/internal/authorizations", internalOnly, (_req, res) => {
|
|
2744
|
+
const authorizations = state.getEnrichedAuthorizations().map((a) => ({
|
|
2745
|
+
profileId: a.profileId,
|
|
2746
|
+
frameHash: a.frameHash,
|
|
2747
|
+
bounds: a.frame,
|
|
2748
|
+
context: a.context ?? {},
|
|
2749
|
+
intent: a.gateContent?.intent ?? null,
|
|
2750
|
+
deferredCommitmentDomains: a.deferredCommitmentDomains ?? []
|
|
2751
|
+
}));
|
|
2752
|
+
res.json({ authorizations });
|
|
2753
|
+
});
|
|
2712
2754
|
app.get("/internal/manifests", internalOnly, (_req, res) => {
|
|
2713
2755
|
res.json({ manifests: getAllManifests() });
|
|
2714
2756
|
});
|
|
@@ -2782,7 +2824,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
2782
2824
|
console.error(`[Suveren MCP] SP server: ${spUrl}`);
|
|
2783
2825
|
profilesLoaded = loadProfiles();
|
|
2784
2826
|
loadManifests();
|
|
2785
|
-
if (integrationRegistry.getEnabled().length === 0) {
|
|
2827
|
+
if (process.env.SUVEREN_DISABLE_AUTO_INTEGRATIONS !== "1" && integrationRegistry.getEnabled().length === 0) {
|
|
2786
2828
|
const personalManifests = getAllManifests().filter((m) => m.personalDefault);
|
|
2787
2829
|
for (const manifest of personalManifests) {
|
|
2788
2830
|
const optionalKeys = new Set(
|