@suveren/gateway 0.11.2 → 0.13.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.
Files changed (127) hide show
  1. package/README.md +35 -1
  2. package/bin/suveren-gateway.js +137 -12
  3. package/content/integrations/calendar.json +1 -0
  4. package/content/integrations/crm.json +1 -67
  5. package/content/integrations/deploy-github.json +3 -5
  6. package/content/integrations/erp.json +1 -80
  7. package/content/integrations/gmail.json +1 -0
  8. package/content/integrations/linkedin.json +1 -0
  9. package/content/integrations/mail.json +1 -48
  10. package/content/integrations/mollie.json +1 -0
  11. package/content/integrations/records.json +1 -0
  12. package/dist/control-plane/index.mjs +555 -122
  13. package/dist/mcp-server/http.mjs +643 -168
  14. package/dist/ui/assets/{index-BzcgeHlq.js → index-CmLqzgqA.js} +21 -21
  15. package/dist/ui/assets/{index-BFOprnFZ.css → index-y1OFeYnS.css} +1 -1
  16. package/dist/ui/index.html +2 -2
  17. package/lib/autostart-templates.d.mts +6 -0
  18. package/lib/autostart-templates.mjs +21 -4
  19. package/lib/config.d.mts +10 -0
  20. package/lib/config.mjs +79 -18
  21. package/lib/node-version.d.mts +7 -0
  22. package/lib/node-version.mjs +28 -0
  23. package/lib/policy.d.mts +34 -0
  24. package/lib/policy.mjs +369 -0
  25. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts.map +1 -1
  26. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js +9 -7
  27. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js.map +1 -1
  28. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts +13 -0
  29. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts.map +1 -1
  30. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.js +17 -4
  31. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.js.map +1 -1
  32. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.d.ts +13 -0
  33. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.d.ts.map +1 -1
  34. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js +23 -10
  35. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js.map +1 -1
  36. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationFormExample.js +63 -20
  37. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationFormExample.js.map +1 -1
  38. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationUrlExample.js +71 -29
  39. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationUrlExample.js.map +1 -1
  40. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/jsonResponseStreamableHttp.js +45 -6
  41. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/jsonResponseStreamableHttp.js.map +1 -1
  42. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleStreamableHttp.js +71 -31
  43. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleStreamableHttp.js.map +1 -1
  44. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleTaskInteractive.js +61 -18
  45. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleTaskInteractive.js.map +1 -1
  46. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js +57 -19
  47. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
  48. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/ssePollingExample.js +64 -17
  49. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/ssePollingExample.js.map +1 -1
  50. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js +61 -13
  51. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
  52. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.d.ts +4 -0
  53. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.d.ts.map +1 -1
  54. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.js.map +1 -1
  55. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.d.ts +17 -8
  56. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.d.ts.map +1 -1
  57. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.js +31 -14
  58. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.js.map +1 -1
  59. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.d.ts +9 -1
  60. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.d.ts.map +1 -1
  61. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.js +17 -1
  62. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.js.map +1 -1
  63. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.d.ts +12 -1
  64. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.d.ts.map +1 -1
  65. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js +46 -2
  66. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js.map +1 -1
  67. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.d.ts +1 -0
  68. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.d.ts.map +1 -1
  69. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.js +39 -9
  70. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.js.map +1 -1
  71. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.d.ts +10 -0
  72. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.d.ts.map +1 -1
  73. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.js +77 -0
  74. package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.js.map +1 -1
  75. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.d.ts.map +1 -1
  76. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js +9 -7
  77. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js.map +1 -1
  78. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.d.ts +13 -0
  79. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.d.ts.map +1 -1
  80. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.js +18 -5
  81. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.js.map +1 -1
  82. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.d.ts +13 -0
  83. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.d.ts.map +1 -1
  84. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js +24 -11
  85. package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js.map +1 -1
  86. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationFormExample.js +63 -20
  87. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationFormExample.js.map +1 -1
  88. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationUrlExample.js +71 -29
  89. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationUrlExample.js.map +1 -1
  90. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/jsonResponseStreamableHttp.js +45 -6
  91. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/jsonResponseStreamableHttp.js.map +1 -1
  92. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleStreamableHttp.js +71 -31
  93. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleStreamableHttp.js.map +1 -1
  94. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleTaskInteractive.js +61 -18
  95. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleTaskInteractive.js.map +1 -1
  96. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js +57 -19
  97. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
  98. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/ssePollingExample.js +64 -17
  99. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/ssePollingExample.js.map +1 -1
  100. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js +61 -13
  101. package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
  102. package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.d.ts +4 -0
  103. package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.d.ts.map +1 -1
  104. package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js.map +1 -1
  105. package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.d.ts +17 -8
  106. package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.d.ts.map +1 -1
  107. package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.js +31 -14
  108. package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.js.map +1 -1
  109. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.d.ts +9 -1
  110. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.d.ts.map +1 -1
  111. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.js +17 -1
  112. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.js.map +1 -1
  113. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts +12 -1
  114. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts.map +1 -1
  115. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js +46 -2
  116. package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js.map +1 -1
  117. package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.d.ts +1 -0
  118. package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.d.ts.map +1 -1
  119. package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js +39 -9
  120. package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js.map +1 -1
  121. package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts +10 -0
  122. package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts.map +1 -1
  123. package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js +72 -0
  124. package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js.map +1 -1
  125. package/node_modules/@modelcontextprotocol/sdk/package.json +1 -1
  126. package/package.json +2 -2
  127. package/server.js +60 -8
package/README.md CHANGED
@@ -106,7 +106,7 @@ Open `http://localhost:7400`. The MCP server is at `http://localhost:7430`.
106
106
 
107
107
  ### Option B — npm
108
108
 
109
- Requires [Node.js 20.18.1+](https://nodejs.org/).
109
+ Requires [Node.js 22+](https://nodejs.org/).
110
110
 
111
111
  ```bash
112
112
  npm install -g @suveren/gateway
@@ -185,6 +185,40 @@ See `suveren-gateway config help` for the full command reference.
185
185
 
186
186
  ---
187
187
 
188
+ ## Behind a company proxy
189
+
190
+ On a company laptop that routes all internet traffic through an HTTP(S) proxy, the gateway needs two things: the proxy address, and — if that proxy does TLS inspection — the company's root certificate.
191
+
192
+ **The proxy.** Set the standard environment variables (upper or lower case both work) before starting the gateway, or save one with the CLI:
193
+
194
+ ```bash
195
+ export HTTPS_PROXY=http://proxy.corp.example:8080
196
+ export HTTP_PROXY=http://proxy.corp.example:8080 # usually the same value
197
+ export NO_PROXY=internal.example.com # optional: hosts to reach directly
198
+ suveren-gateway start
199
+
200
+ # or, to save it so every future start picks it up without re-exporting:
201
+ suveren-gateway config set proxy http://proxy.corp.example:8080
202
+ suveren-gateway restart
203
+ ```
204
+
205
+ Every outbound call this applies to — the Authority Server, the update checker, and a remote AI assistant endpoint — honours the proxy. A loopback target (the control plane and MCP server talking to each other, or a local AI assistant such as Ollama) is **never** proxied, no matter what is set — a corporate proxy has no route back to the machine it's running on.
206
+
207
+ **TLS inspection.** Many corporate proxies intercept HTTPS by re-signing every certificate with their own root — that includes the Authority Server's. If you see a certificate error mentioning an untrusted issuer, trust the company's root the same way you would for a self-hosted Authority Server:
208
+
209
+ ```bash
210
+ suveren-gateway config set ca-file /path/to/company-root-ca.pem
211
+ suveren-gateway restart
212
+ ```
213
+
214
+ This works through the proxy exactly like it works directly — `--ca-file` applies to the certificate the gateway actually sees, proxied or not.
215
+
216
+ **The one combination that still refuses, on purpose:** [TLS pinning](#pinning-the-authority-servers-tls-certificate) (opt-in, off by default) together with a TLS-inspecting proxy. Pinning checks the certificate the gateway sees *through* the proxy — if that's the proxy's own re-signed one rather than the real Authority Server's, the pin will not match, and the gateway refuses the connection and says so, even with the company root trusted via `--ca-file`. That refusal is correct: ask network/IT to exempt the Authority Server's host from TLS inspection, the same request you'd make for any pinned certificate on that network.
217
+
218
+ **On a company-managed machine, IT can set the proxy centrally** instead of relying on `HTTPS_PROXY` being exported in every shell — see [Managed settings](docs/managed-settings.md). A policy-set `Proxy` (Windows registry or a JSON policy file) **overrides** an already-set `HTTP_PROXY`/`HTTPS_PROXY` rather than merely filling it in, and `config set proxy` / `--proxy` refuse with "set by your IT" when it's locked. A loopback target is still never proxied, regardless.
219
+
220
+ ---
221
+
188
222
  ## Simulation mode — block every real system
189
223
 
190
224
  A mandate is bound to a *profile* (e.g. "sales"), not to a specific connector. If a real
@@ -17,9 +17,19 @@ import { homedir, platform, userInfo } from 'node:os';
17
17
  import { dirname, join, resolve } from 'node:path';
18
18
  import { fileURLToPath } from 'node:url';
19
19
  import { buildLaunchAgentPlist, buildMacLauncher, buildSystemdUnit, buildWindowsTaskXml } from '../lib/autostart-templates.mjs';
20
- import { DEFAULT_AS_URL, readConfig, writeConfig, validateAsUrl, validateCaFile, validatePinTls, resolveAsUrl, resolvePinTls, resolvePinTlsExpectedFingerprint, resolveSimulation } from '../lib/config.mjs';
20
+ import { DEFAULT_AS_URL, readConfig, writeConfig, validateAsUrl, validateCaFile, validateProxyUrl, validatePinTls, resolveAsUrl, resolvePinTls, resolvePinTlsExpectedFingerprint, resolveSimulation } from '../lib/config.mjs';
21
21
  import { createInterface } from 'node:readline/promises';
22
22
  import { readPairing as readAsPairing, recordTlsPin, formatFingerprint, normalizeFingerprint } from '../lib/as-pairing.mjs';
23
+ import { unsupportedNodeReason } from '../lib/node-version.mjs';
24
+ import { readPolicy, isPolicyLocked } from '../lib/policy.mjs';
25
+
26
+ // Before any command: on an unsupported Node the connectors cannot run (see
27
+ // lib/node-version.mjs), so refuse with the reason instead of starting.
28
+ const nodeReason = unsupportedNodeReason(process.versions.node);
29
+ if (nodeReason) {
30
+ console.error(nodeReason);
31
+ process.exit(1);
32
+ }
23
33
 
24
34
  const __dirname = dirname(fileURLToPath(import.meta.url));
25
35
  const PKG_ROOT = resolve(__dirname, '..');
@@ -190,6 +200,18 @@ async function start(args) {
190
200
  caFileToSave = v.path;
191
201
  }
192
202
 
203
+ const proxyFlag = flagValue(args, '--proxy');
204
+ let proxyToSave = null;
205
+ if (proxyFlag) {
206
+ refuseIfLockedByPolicy('proxy', 'proxy');
207
+ const v = validateProxyUrl(proxyFlag);
208
+ if (!v.ok) {
209
+ console.error(`Invalid --proxy: ${v.error}`);
210
+ process.exit(1);
211
+ }
212
+ proxyToSave = v.url;
213
+ }
214
+
193
215
  // --pin-tls: a boolean flag (turns TLS certificate pinning ON — there is
194
216
  // no `start --pin-tls off`; use `config set pin-tls off` for that).
195
217
  // Validated against the EFFECTIVE as-url for this very start (the flag
@@ -266,6 +288,12 @@ async function start(args) {
266
288
  writeConfig(DATA_DIR, { caFile: caFileToSave });
267
289
  console.log(`CA file: ${caFileToSave} (saved — applied to every process this gateway starts)`);
268
290
  }
291
+ if (proxyToSave) {
292
+ writeConfig(DATA_DIR, { proxyUrl: proxyToSave });
293
+ process.env.HTTP_PROXY ??= proxyToSave;
294
+ process.env.HTTPS_PROXY ??= proxyToSave;
295
+ console.log(`Proxy: ${proxyToSave} (saved — applied to every process this gateway starts, unless HTTP_PROXY/HTTPS_PROXY is already set)`);
296
+ }
269
297
  if (pinTlsToSave) {
270
298
  const effectiveAsUrl = asUrlToSave ?? resolveAsUrl(DATA_DIR);
271
299
  const committed = commitConfirmedFingerprint(DATA_DIR, effectiveAsUrl, confirmedFingerprintHex);
@@ -510,16 +538,36 @@ function printConfigHelp() {
510
538
  console.log(`suveren-gateway config — read or save gateway settings
511
539
 
512
540
  Usage:
513
- suveren-gateway config get [as-url|ca-file|pin-tls] Print the resolved value(s)
541
+ suveren-gateway config get [as-url|ca-file|pin-tls|proxy] Print the resolved value(s)
514
542
  suveren-gateway config set as-url <url> Save the Authority Server URL
515
543
  suveren-gateway config set ca-file <path> Save a CA bundle for internal TLS
516
544
  suveren-gateway config set pin-tls on --expect-fingerprint <sha256-hex>
517
545
  Pin the Authority Server's TLS certificate
518
546
  suveren-gateway config set pin-tls off Stop enforcing the TLS certificate pin
547
+ suveren-gateway config set proxy <url> Save an HTTP(S) proxy (http(s)://host:port)
519
548
 
520
549
  Saved in ${join(DATA_DIR, 'config.json')}.
521
- Precedence at start: --as-url flag > env SUVEREN_AS_URL > saved as-url >
522
- default (${DEFAULT_AS_URL}).
550
+ Precedence at start: IT policy > --as-url flag > env SUVEREN_AS_URL > saved
551
+ as-url > default (${DEFAULT_AS_URL}).
552
+
553
+ A setting your organization's IT has set centrally (Windows registry policy,
554
+ or a policy file — see docs/managed-settings.md) is LOCKED: \`config get\`
555
+ shows "(set by your IT)" next to it, and \`config set\` refuses to change it.
556
+
557
+ proxy is a convenience for operators without shell access to set
558
+ HTTP_PROXY/HTTPS_PROXY themselves — those environment variables remain the
559
+ actual requirement (every process this gateway runs honours them directly)
560
+ and always win if already set; a saved proxy only fills in whichever of the
561
+ two is still unset. NO_PROXY is honoured too, read directly from the
562
+ environment (there is no saved equivalent). A loopback target (the control
563
+ plane and the MCP server talking to each other, or a local AI assistant) is
564
+ never proxied, independent of any of this.
565
+
566
+ An IT-set Proxy policy is the ONE locked setting that OVERRIDES an
567
+ already-set HTTP_PROXY/HTTPS_PROXY rather than merely out-ranking a saved
568
+ value — the Windows-company-laptop case, where the proxy lives in system
569
+ settings, not something an employee's shell exports. A NoProxy policy
570
+ likewise overrides NO_PROXY. See docs/managed-settings.md.
523
571
 
524
572
  pin-tls (default off, self-hosted Authority Servers with a stable signing
525
573
  key) requires https:// and pins the Authority Server's TLS certificate
@@ -555,21 +603,48 @@ running gateway keeps using what it already resolved at its own startup.
555
603
  `);
556
604
  }
557
605
 
606
+ /** Appended to `config get` output when IT policy has set this key — see
607
+ * lib/policy.mjs. Purely informational here; the refusal itself lives in
608
+ * `refuseIfLockedByPolicy`, called from `config set` / `simulation`. */
609
+ function policySuffix(key) {
610
+ return isPolicyLocked(key) ? ' (set by your IT)' : '';
611
+ }
612
+
613
+ /**
614
+ * Refuse a `config set` / `simulation on|off` for a key IT policy already
615
+ * controls. A policy value is the top precedence tier (see policy.mjs) —
616
+ * writing a saved config.json value anyway would be actively misleading
617
+ * (`config get` would show something that is NOT what's actually running),
618
+ * so this exits before any validation or write happens, naming the policy
619
+ * value so the person doesn't have to go find it themselves.
620
+ */
621
+ function refuseIfLockedByPolicy(key, humanName, display) {
622
+ if (!isPolicyLocked(key)) return;
623
+ const value = readPolicy().policy[key];
624
+ console.error(`${humanName} is set by your IT policy (${display ? display(value) : value}) and cannot be changed here.`);
625
+ console.error('Contact your IT administrator to change it.');
626
+ process.exit(1);
627
+ }
628
+
558
629
  async function config(args) {
559
630
  const sub = args[0];
560
631
 
561
632
  if (sub === 'get') {
562
633
  const key = args[1];
563
634
  const saved = readConfig(DATA_DIR);
635
+ const policyCaFile = readPolicy().policy.caFile;
636
+ const policyProxy = readPolicy().policy.proxy;
564
637
  if (!key) {
565
- console.log(`as-url: ${resolveAsUrl(DATA_DIR)}`);
566
- console.log(`ca-file: ${saved.caFile ?? '(not set)'}`);
567
- console.log(`pin-tls: ${resolvePinTls(DATA_DIR) ? 'on' : 'off'}`);
638
+ console.log(`as-url: ${resolveAsUrl(DATA_DIR)}${policySuffix('asUrl')}`);
639
+ console.log(`ca-file: ${(policyCaFile ?? saved.caFile) ?? '(not set)'}${policySuffix('caFile')}`);
640
+ console.log(`pin-tls: ${resolvePinTls(DATA_DIR) ? 'on' : 'off'}${policySuffix('pinTls')}`);
641
+ console.log(`proxy: ${(policyProxy ?? saved.proxyUrl) ?? '(not set)'}${policySuffix('proxy')}`);
568
642
  return;
569
643
  }
570
- if (key === 'as-url') { console.log(resolveAsUrl(DATA_DIR)); return; }
571
- if (key === 'ca-file') { console.log(saved.caFile ?? ''); return; }
572
- if (key === 'pin-tls') { console.log(resolvePinTls(DATA_DIR) ? 'on' : 'off'); return; }
644
+ if (key === 'as-url') { console.log(`${resolveAsUrl(DATA_DIR)}${policySuffix('asUrl')}`); return; }
645
+ if (key === 'ca-file') { console.log(`${(policyCaFile ?? saved.caFile) ?? ''}${policySuffix('caFile')}`); return; }
646
+ if (key === 'pin-tls') { console.log(`${resolvePinTls(DATA_DIR) ? 'on' : 'off'}${policySuffix('pinTls')}`); return; }
647
+ if (key === 'proxy') { console.log(`${(policyProxy ?? saved.proxyUrl) ?? ''}${policySuffix('proxy')}`); return; }
573
648
  console.error(`Unknown config key: ${key}\n`);
574
649
  printConfigHelp();
575
650
  process.exit(2);
@@ -579,6 +654,7 @@ async function config(args) {
579
654
  const key = args[1];
580
655
  const value = args[2];
581
656
  if (key === 'as-url') {
657
+ refuseIfLockedByPolicy('asUrl', 'as-url');
582
658
  if (!value) {
583
659
  console.error('Usage: suveren-gateway config set as-url <url>');
584
660
  process.exit(2);
@@ -594,6 +670,7 @@ async function config(args) {
594
670
  return;
595
671
  }
596
672
  if (key === 'ca-file') {
673
+ refuseIfLockedByPolicy('caFile', 'ca-file');
597
674
  if (!value) {
598
675
  console.error('Usage: suveren-gateway config set ca-file <path>');
599
676
  process.exit(2);
@@ -608,7 +685,25 @@ async function config(args) {
608
685
  console.log('Restart to pick it up: suveren-gateway restart');
609
686
  return;
610
687
  }
688
+ if (key === 'proxy') {
689
+ refuseIfLockedByPolicy('proxy', 'proxy');
690
+ if (!value) {
691
+ console.error('Usage: suveren-gateway config set proxy <url>');
692
+ process.exit(2);
693
+ }
694
+ const v = validateProxyUrl(value);
695
+ if (!v.ok) {
696
+ console.error(`Invalid proxy: ${v.error}`);
697
+ process.exit(1);
698
+ }
699
+ writeConfig(DATA_DIR, { proxyUrl: v.url });
700
+ console.log(`Saved proxy: ${v.url}`);
701
+ console.log('Sets HTTP_PROXY/HTTPS_PROXY for every process this gateway starts, unless already set in the environment.');
702
+ console.log('Restart to pick it up: suveren-gateway restart');
703
+ return;
704
+ }
611
705
  if (key === 'pin-tls') {
706
+ refuseIfLockedByPolicy('pinTls', 'pin-tls', (v) => (v ? 'on' : 'off'));
612
707
  if (value !== 'on' && value !== 'off') {
613
708
  console.error('Usage: suveren-gateway config set pin-tls on|off [--expect-fingerprint <sha256-hex>]');
614
709
  process.exit(2);
@@ -670,12 +765,17 @@ Usage:
670
765
  suveren-gateway simulation status Show the saved setting and (if running) the live one
671
766
 
672
767
  Changes take effect on the next \`suveren-gateway start\` / \`restart\`.
768
+
769
+ If your organization's IT has set Simulation centrally (Windows registry
770
+ policy, or a policy file — see docs/managed-settings.md), it is LOCKED: both
771
+ \`on\` and \`off\` refuse, naming the policy value, and this cannot be
772
+ overridden locally — not even by setting SUVEREN_SIMULATION directly.
673
773
  `);
674
774
  }
675
775
 
676
776
  async function printSimulationStatus() {
677
777
  const saved = resolveSimulation(DATA_DIR);
678
- console.log(`Saved: ${saved ? 'on' : 'off'}`);
778
+ console.log(`Saved: ${saved ? 'on' : 'off'}${policySuffix('simulation')}`);
679
779
  try {
680
780
  const res = await fetch(`http://localhost:${SUVEREN_PORT}/health`, { signal: AbortSignal.timeout(3000) });
681
781
  if (res.ok) {
@@ -711,6 +811,7 @@ async function simulation(args) {
711
811
  }
712
812
 
713
813
  if (sub === 'on') {
814
+ refuseIfLockedByPolicy('simulation', 'Simulation mode', (v) => (v ? 'on' : 'off'));
714
815
  // The safe direction — blocking real systems needs no confirmation.
715
816
  writeConfig(DATA_DIR, { simulation: true });
716
817
  console.log('Simulation mode: ON (saved) — every connector without a manifest "simulation" marker will be blocked.');
@@ -719,6 +820,7 @@ async function simulation(args) {
719
820
  }
720
821
 
721
822
  if (sub === 'off') {
823
+ refuseIfLockedByPolicy('simulation', 'Simulation mode', (v) => (v ? 'on' : 'off'));
722
824
  console.error('Turning simulation mode OFF makes every real system reachable again: any connector');
723
825
  console.error('without a manifest "simulation" marker (e.g. a live Gmail or ERP account) will spawn');
724
826
  console.error('and execute for real the next time this gateway starts.');
@@ -986,6 +1088,19 @@ async function serviceInstallWindows() {
986
1088
  // Deliberately does NOT stop the running gateway — see the macOS note.
987
1089
  // Registering is enough; Task Scheduler starts it at the next logon.
988
1090
 
1091
+ // SUVEREN_LAUNCHER (set by the Windows installer's launcher.cmd, never by
1092
+ // a plain npm/dev install) means this `service install` was itself invoked
1093
+ // THROUGH that launcher — route the scheduled task through it too, instead
1094
+ // of straight at node.exe/server.js. The launcher sets SUVEREN_INSTALL_
1095
+ // METHOD/SUVEREN_OFFLINE/SUVEREN_INTEGRATIONS_DIR/PATH itself, scoped to
1096
+ // just the gateway process it starts; writing those into HKCU\Environment
1097
+ // (an earlier version of the installer did this) is ACCOUNT-WIDE — it
1098
+ // would shadow a developer's own Node for every other program on that
1099
+ // account and force every other gateway install under the same user into
1100
+ // offline+managed mode too. Absent (every non-managed install), this is a
1101
+ // no-op: buildWindowsTaskXml renders identically to before.
1102
+ const launcherPath = process.env.SUVEREN_LAUNCHER;
1103
+
989
1104
  // schtasks reads the XML from a file and requires UTF-16 LE with a BOM —
990
1105
  // it rejects UTF-8 with an unhelpful "The task XML is malformed".
991
1106
  const xml = buildWindowsTaskXml({
@@ -994,6 +1109,7 @@ async function serviceInstallWindows() {
994
1109
  author: 'Suveren',
995
1110
  dataDir: process.env.SUVEREN_DATA_DIR ?? '',
996
1111
  userId: windowsUserId(),
1112
+ ...(launcherPath ? { command: launcherPath, args: 'run' } : {}),
997
1113
  });
998
1114
  const xmlPath = join(DATA_DIR, 'suveren-task.xml');
999
1115
  writeFileSync(xmlPath, '\ufeff' + xml, { encoding: 'utf16le' });
@@ -1248,6 +1364,7 @@ Usage:
1248
1364
  suveren-gateway start [--detach] Run the gateway (foreground by default)
1249
1365
  [--as-url <url>] Authority Server URL (saved for next time)
1250
1366
  [--ca-file <path>] Internal CA bundle (saved for next time)
1367
+ [--proxy <url>] HTTP(S) proxy (saved for next time — see below)
1251
1368
  [--pin-tls --expect-fingerprint <hex>] Pin the AS TLS certificate (saved; https:// only — see below)
1252
1369
  [--simulation] Block every real system (saved — see \`simulation help\`)
1253
1370
  suveren-gateway stop Stop a detached gateway
@@ -1256,9 +1373,10 @@ Usage:
1256
1373
  suveren-gateway logs [--tail] Print or tail ~/.suveren/gateway.log
1257
1374
  suveren-gateway service <cmd> Run as a login service that survives reboot
1258
1375
  (install | uninstall | status)
1259
- suveren-gateway config get [as-url|ca-file|pin-tls] Print the resolved value(s)
1376
+ suveren-gateway config get [as-url|ca-file|pin-tls|proxy] Print the resolved value(s)
1260
1377
  suveren-gateway config set as-url <url> Save the Authority Server URL
1261
1378
  suveren-gateway config set ca-file <path> Save a CA bundle for internal TLS
1379
+ suveren-gateway config set proxy <url> Save an HTTP(S) proxy (see below)
1262
1380
  suveren-gateway config set pin-tls on --expect-fingerprint <hex>
1263
1381
  Pin the Authority Server's TLS certificate
1264
1382
  (see \`suveren-gateway config help\` for the fingerprint check)
@@ -1273,6 +1391,13 @@ Environment:
1273
1391
  SUVEREN_DATA_DIR Data directory (default ~/.suveren)
1274
1392
  SUVEREN_AS_URL Authority Server URL — overrides the saved as-url
1275
1393
  SUVEREN_SIMULATION 1 to force simulation mode for this run — overrides the saved setting
1394
+ HTTP_PROXY / HTTPS_PROXY / NO_PROXY (upper or lower case)
1395
+ Corporate proxy — honoured for every outbound call to the Authority
1396
+ Server, the update checker, and a remote AI assistant endpoint. A
1397
+ loopback target (127.0.0.1/localhost, incl. a local AI assistant) is
1398
+ never proxied. These win over a saved \`config set proxy\` — UNLESS
1399
+ your organization's IT has set a Proxy/NoProxy policy, which wins
1400
+ over both (see docs/managed-settings.md).
1276
1401
 
1277
1402
  Authority Server resolution order: --as-url flag > SUVEREN_AS_URL env >
1278
1403
  saved as-url (\`config set as-url\`) > default (${DEFAULT_AS_URL}).
@@ -9,6 +9,7 @@
9
9
  "defaultAgeDays": 30
10
10
  },
11
11
  "npmPackage": "@humanagencyp/google-calendar-mcp",
12
+ "npmVersion": "0.1.2",
12
13
  "mcp": {
13
14
  "command": "google-calendar-mcp",
14
15
  "args": []
@@ -6,6 +6,7 @@
6
6
  "icon": "users",
7
7
  "profile": "customers",
8
8
  "npmPackage": "@humanagencyp/crm-mcp",
9
+ "npmVersion": "1.1.2",
9
10
  "mcp": {
10
11
  "command": "crm-mcp",
11
12
  "args": []
@@ -142,73 +143,6 @@
142
143
  }
143
144
  }
144
145
  },
145
- "templates": [
146
- {
147
- "name": "Browse & Log",
148
- "description": "Search contacts, view pipeline and timeline, log activities. No creates or deletes.",
149
- "risk": "low",
150
- "mode": "automatic",
151
- "bounds": {
152
- "read_access": "unlimited",
153
- "write_daily_max": "50",
154
- "delete_daily_max": "0",
155
- "setup_daily_max": "0"
156
- },
157
- "context": {
158
- "contact_type": "customer,lead,partner,vendor"
159
- },
160
- "intent": "Allow the agent to browse CRM data and log activities. Read and log only — no creating, editing, or deleting records.",
161
- "ttl": 2592000,
162
- "tags": [
163
- "read + log",
164
- "no deletes"
165
- ]
166
- },
167
- {
168
- "name": "Full CRM with Review",
169
- "description": "Create contacts, deals, and tasks. Update and delete records. Every write requires approval.",
170
- "risk": "high",
171
- "mode": "review",
172
- "bounds": {
173
- "read_access": "unlimited",
174
- "write_daily_max": "50",
175
- "delete_daily_max": "10",
176
- "setup_daily_max": "0"
177
- },
178
- "context": {
179
- "contact_type": "customer,lead,partner,vendor"
180
- },
181
- "intent": "Full CRM management — create, update, and delete contacts, deals, and tasks. Every write action requires my review before execution.",
182
- "ttl": 2592000,
183
- "tags": [
184
- "50 writes/day",
185
- "10 deletes/day",
186
- "review mode"
187
- ]
188
- },
189
- {
190
- "name": "Load test data (with approval)",
191
- "description": "Load a simulation package (customers and optional contacts) into the simulated CRM, for setting up a three-week test. No browsing, writing, or deleting — this template grants nothing but the one-time test-data load, and it still requires your approval.",
192
- "risk": "high",
193
- "mode": "review",
194
- "bounds": {
195
- "read_access": "none",
196
- "write_daily_max": "0",
197
- "delete_daily_max": "0",
198
- "setup_daily_max": "1"
199
- },
200
- "context": {
201
- "contact_type": "customer"
202
- },
203
- "intent": "Allow the agent to load one simulation package of test data into the simulated CRM, for my review before it happens. No other CRM action is granted by this mandate — set up test data separately from the mandate the agent uses for its actual work.",
204
- "ttl": 2592000,
205
- "tags": [
206
- "setup only",
207
- "review mode",
208
- "1 load"
209
- ]
210
- }
211
- ],
212
146
  "setupHint": "No configuration needed — works out of the box.",
213
147
  "setupGuide": [
214
148
  {
@@ -6,12 +6,10 @@
6
6
  "icon": "wrench",
7
7
  "profile": "deploy",
8
8
  "npmPackage": "@humanagencyp/deploy-mcp",
9
+ "npmVersion": "0.4.2",
9
10
  "mcp": {
10
- "command": "npx",
11
- "args": [
12
- "-y",
13
- "@humanagencyp/deploy-mcp@latest"
14
- ]
11
+ "command": "deploy-mcp",
12
+ "args": []
15
13
  },
16
14
  "credentials": {
17
15
  "fields": [
@@ -6,6 +6,7 @@
6
6
  "icon": "briefcase",
7
7
  "profile": "sales",
8
8
  "npmPackage": "@humanagencyp/erp-mcp",
9
+ "npmVersion": "0.3.3",
9
10
  "mcp": {
10
11
  "command": "erp-mcp",
11
12
  "args": []
@@ -138,86 +139,6 @@
138
139
  }
139
140
  }
140
141
  },
141
- "templates": [
142
- {
143
- "name": "Draft quotes",
144
- "description": "Look up items and customers, create and edit draft quotes. Nothing is sent to a customer or converted to an order.",
145
- "risk": "low",
146
- "mode": "automatic",
147
- "bounds": {
148
- "read_access": "unlimited",
149
- "value_max": "5000",
150
- "discount_max": "15",
151
- "order_value_daily_max": "0",
152
- "quote_daily_max": "20",
153
- "send_daily_max": "0",
154
- "order_daily_max": "0",
155
- "setup_daily_max": "0"
156
- },
157
- "context": {
158
- "currency": "EUR"
159
- },
160
- "intent": "Allow the agent to browse items and customers and prepare draft quotes for my review. It cannot send a quote to a customer or convert one into an order.",
161
- "ttl": 2592000,
162
- "tags": [
163
- "drafts only",
164
- "no sends",
165
- "no orders"
166
- ]
167
- },
168
- {
169
- "name": "Send and confirm with review above limit",
170
- "description": "Draft, send, and convert quotes into orders. Every send and every order requires your approval.",
171
- "risk": "high",
172
- "mode": "review",
173
- "bounds": {
174
- "read_access": "unlimited",
175
- "value_max": "20000",
176
- "discount_max": "20",
177
- "order_value_daily_max": "50000",
178
- "quote_daily_max": "50",
179
- "send_daily_max": "20",
180
- "order_daily_max": "10",
181
- "setup_daily_max": "0"
182
- },
183
- "context": {
184
- "currency": "EUR"
185
- },
186
- "intent": "Allow the agent to draft, send, and convert quotes into orders. Every quote sent to a customer and every order requires my review before it happens.",
187
- "ttl": 2592000,
188
- "tags": [
189
- "20k EUR/document",
190
- "review mode",
191
- "10 orders/day"
192
- ]
193
- },
194
- {
195
- "name": "Load test data (with approval)",
196
- "description": "Load a simulation package (customers and products) into the simulated ERP, for setting up a three-week test. No quoting, sending, or ordering — this template grants nothing but the one-time test-data load, and it still requires your approval.",
197
- "risk": "high",
198
- "mode": "review",
199
- "bounds": {
200
- "read_access": "none",
201
- "value_max": "0",
202
- "discount_max": "0",
203
- "order_value_daily_max": "0",
204
- "quote_daily_max": "0",
205
- "send_daily_max": "0",
206
- "order_daily_max": "0",
207
- "setup_daily_max": "1"
208
- },
209
- "context": {
210
- "currency": "EUR"
211
- },
212
- "intent": "Allow the agent to load one simulation package of test data into the simulated ERP, for my review before it happens. No other ERP action is granted by this mandate — set up test data separately from the mandate the agent uses for its actual work.",
213
- "ttl": 2592000,
214
- "tags": [
215
- "setup only",
216
- "review mode",
217
- "1 load"
218
- ]
219
- }
220
- ],
221
142
  "setupHint": "No configuration needed — works out of the box.",
222
143
  "setupGuide": [
223
144
  {
@@ -6,6 +6,7 @@
6
6
  "icon": "mail",
7
7
  "profile": "email",
8
8
  "npmPackage": "@shinzolabs/gmail-mcp",
9
+ "npmVersion": "1.7.4",
9
10
  "readPolicy": {
10
11
  "defaultAgeDays": 30
11
12
  },
@@ -6,6 +6,7 @@
6
6
  "icon": "share",
7
7
  "profile": "publish",
8
8
  "npmPackage": "@humanagencyp/linkedin-mcp",
9
+ "npmVersion": "1.0.7",
9
10
  "mcp": {
10
11
  "command": "linkedin-mcp",
11
12
  "args": []
@@ -6,6 +6,7 @@
6
6
  "icon": "mail",
7
7
  "profile": "email",
8
8
  "npmPackage": "@humanagencyp/email-mcp",
9
+ "npmVersion": "0.1.3",
9
10
  "mcp": {
10
11
  "command": "email-mcp",
11
12
  "args": []
@@ -86,54 +87,6 @@
86
87
  }
87
88
  }
88
89
  },
89
- "templates": [
90
- {
91
- "name": "Work the inbox",
92
- "description": "Read the simulated inbox and send replies. Cannot load or replace test data.",
93
- "risk": "low",
94
- "mode": "automatic",
95
- "bounds": {
96
- "read_access": "unlimited",
97
- "recipient_max": "1",
98
- "send_daily_max": "50",
99
- "setup_daily_max": "0"
100
- },
101
- "context": {
102
- "allowed_recipients": "",
103
- "allowed_domains": ""
104
- },
105
- "intent": "Allow the agent to read the simulated inbox and reply to messages on its own. It cannot load or replace the test data the inbox was seeded with.",
106
- "ttl": 2592000,
107
- "tags": [
108
- "read + reply",
109
- "automatic",
110
- "no setup"
111
- ]
112
- },
113
- {
114
- "name": "Load test data (with approval)",
115
- "description": "Load a simulation package into an empty inbox. No reading or sending — a separate, one-time setup mandate.",
116
- "risk": "high",
117
- "mode": "review",
118
- "bounds": {
119
- "read_access": "none",
120
- "recipient_max": "0",
121
- "send_daily_max": "0",
122
- "setup_daily_max": "1"
123
- },
124
- "context": {
125
- "allowed_recipients": "",
126
- "allowed_domains": ""
127
- },
128
- "intent": "Allow the agent to load one simulation package into an empty inbox, with my review before it happens. No reading or sending under this mandate — grant it separately from the mandate the agent uses for its daily work.",
129
- "ttl": 2592000,
130
- "tags": [
131
- "setup only",
132
- "review mode",
133
- "1 load/day"
134
- ]
135
- }
136
- ],
137
90
  "setupHint": "No configuration needed — works out of the box, in simulation mode.",
138
91
  "setupGuide": [
139
92
  {
@@ -6,6 +6,7 @@
6
6
  "icon": "card",
7
7
  "profile": "charge",
8
8
  "npmPackage": "mcp-remote",
9
+ "npmVersion": "0.14.3",
9
10
  "mcp": {
10
11
  "command": "mcp-remote",
11
12
  "args": [
@@ -6,6 +6,7 @@
6
6
  "icon": "file-text",
7
7
  "profile": "records",
8
8
  "npmPackage": "@humanagencyp/records-mcp",
9
+ "npmVersion": "1.0.4",
9
10
  "personalDefault": true,
10
11
  "mcp": {
11
12
  "command": "records-mcp",