@suveren/gateway 0.11.2 → 0.12.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 +1 -1
- package/bin/suveren-gateway.js +9 -0
- package/content/integrations/calendar.json +1 -0
- package/content/integrations/crm.json +1 -67
- package/content/integrations/deploy-github.json +3 -5
- package/content/integrations/erp.json +1 -80
- package/content/integrations/gmail.json +1 -0
- package/content/integrations/linkedin.json +1 -0
- package/content/integrations/mail.json +1 -48
- package/content/integrations/mollie.json +1 -0
- package/content/integrations/records.json +1 -0
- package/dist/mcp-server/http.mjs +159 -14
- package/lib/node-version.d.mts +7 -0
- package/lib/node-version.mjs +28 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js +9 -7
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.js +17 -4
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/sse.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js +23 -10
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/streamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationFormExample.js +63 -20
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationFormExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationUrlExample.js +71 -29
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/elicitationUrlExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/jsonResponseStreamableHttp.js +45 -6
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/jsonResponseStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleStreamableHttp.js +71 -31
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleTaskInteractive.js +61 -18
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/simpleTaskInteractive.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js +57 -19
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/ssePollingExample.js +64 -17
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/ssePollingExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js +61 -13
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.d.ts +4 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/interfaces.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.d.ts +17 -8
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.js +31 -14
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/experimental/tasks/stores/in-memory.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.d.ts +9 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.js +17 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/auth/middleware/bearerAuth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.d.ts +12 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js +46 -2
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/mcp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.d.ts +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.js +39 -9
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/protocol.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.d.ts +10 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.js +77 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/shared/transport.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js +9 -7
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.js +18 -5
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/sse.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.d.ts +13 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js +24 -11
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/streamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationFormExample.js +63 -20
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationFormExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationUrlExample.js +71 -29
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/elicitationUrlExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/jsonResponseStreamableHttp.js +45 -6
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/jsonResponseStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleStreamableHttp.js +71 -31
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleTaskInteractive.js +61 -18
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/simpleTaskInteractive.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js +57 -19
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/ssePollingExample.js +64 -17
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/ssePollingExample.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js +61 -13
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.d.ts +4 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.d.ts +17 -8
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.js +31 -14
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/stores/in-memory.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.d.ts +9 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.js +17 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/auth/middleware/bearerAuth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts +12 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js +46 -2
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.d.ts +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js +39 -9
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts +10 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js +72 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/transport.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/package.json +1 -1
- package/package.json +2 -2
- package/server.js +9 -0
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
|
|
109
|
+
Requires [Node.js 22+](https://nodejs.org/).
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
112
|
npm install -g @suveren/gateway
|
package/bin/suveren-gateway.js
CHANGED
|
@@ -20,6 +20,15 @@ import { buildLaunchAgentPlist, buildMacLauncher, buildSystemdUnit, buildWindows
|
|
|
20
20
|
import { DEFAULT_AS_URL, readConfig, writeConfig, validateAsUrl, validateCaFile, 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
|
+
|
|
25
|
+
// Before any command: on an unsupported Node the connectors cannot run (see
|
|
26
|
+
// lib/node-version.mjs), so refuse with the reason instead of starting.
|
|
27
|
+
const nodeReason = unsupportedNodeReason(process.versions.node);
|
|
28
|
+
if (nodeReason) {
|
|
29
|
+
console.error(nodeReason);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
23
32
|
|
|
24
33
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
34
|
const PKG_ROOT = resolve(__dirname, '..');
|
|
@@ -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": "
|
|
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": "@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
|
{
|
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -2607,6 +2607,20 @@ import { randomUUID } from "crypto";
|
|
|
2607
2607
|
// src/lib/manifest-loader.ts
|
|
2608
2608
|
import { readFileSync as readFileSync12, existsSync as existsSync11 } from "fs";
|
|
2609
2609
|
import { join as join11, resolve } from "path";
|
|
2610
|
+
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
2611
|
+
function isExactSemver(value) {
|
|
2612
|
+
return typeof value === "string" && EXACT_SEMVER_RE.test(value);
|
|
2613
|
+
}
|
|
2614
|
+
function invalidNpmPinReason(manifest) {
|
|
2615
|
+
if (!manifest.npmPackage) return null;
|
|
2616
|
+
if (manifest.npmVersion === void 0) {
|
|
2617
|
+
return `npmPackage "${manifest.npmPackage}" is set but npmVersion is missing \u2014 every connector manifest must pin an exact tested version`;
|
|
2618
|
+
}
|
|
2619
|
+
if (!isExactSemver(manifest.npmVersion)) {
|
|
2620
|
+
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`;
|
|
2621
|
+
}
|
|
2622
|
+
return null;
|
|
2623
|
+
}
|
|
2610
2624
|
var manifests = /* @__PURE__ */ new Map();
|
|
2611
2625
|
function loadManifests(integrationsDir) {
|
|
2612
2626
|
const dir = resolve(
|
|
@@ -2629,6 +2643,11 @@ function loadManifests(integrationsDir) {
|
|
|
2629
2643
|
const manifestPath = join11(dir, relativePath);
|
|
2630
2644
|
try {
|
|
2631
2645
|
const manifest = JSON.parse(readFileSync12(manifestPath, "utf-8"));
|
|
2646
|
+
const pinReason = invalidNpmPinReason(manifest);
|
|
2647
|
+
if (pinReason) {
|
|
2648
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
|
|
2649
|
+
continue;
|
|
2650
|
+
}
|
|
2632
2651
|
manifests.set(id, manifest);
|
|
2633
2652
|
loaded++;
|
|
2634
2653
|
} catch (err) {
|
|
@@ -4383,6 +4402,17 @@ var IntegrationManager = class {
|
|
|
4383
4402
|
}
|
|
4384
4403
|
running = /* @__PURE__ */ new Map();
|
|
4385
4404
|
onToolsChanged = null;
|
|
4405
|
+
/**
|
|
4406
|
+
* Called when a persisted config was migrated to the manifest's command/args
|
|
4407
|
+
* (see `migrateNpxConfig`) so the caller can write the fix back to
|
|
4408
|
+
* `integrations.json` — the manager itself has no registry/persistence
|
|
4409
|
+
* handle, by design (mirrors `onToolsChanged`). Without a subscriber the
|
|
4410
|
+
* migration still takes effect for THIS start (the in-memory config is
|
|
4411
|
+
* still corrected before spawn); only the one-time persistence is skipped,
|
|
4412
|
+
* so every later start keeps re-migrating in memory, which is safe, if
|
|
4413
|
+
* redundant.
|
|
4414
|
+
*/
|
|
4415
|
+
onConfigMigrated = null;
|
|
4386
4416
|
/**
|
|
4387
4417
|
* Per-integration operation queue. Start/stop for one id are serialized
|
|
4388
4418
|
* through here; without it, a second start issued while the first is still
|
|
@@ -4408,6 +4438,16 @@ var IntegrationManager = class {
|
|
|
4408
4438
|
setOnToolsChanged(cb) {
|
|
4409
4439
|
this.onToolsChanged = cb;
|
|
4410
4440
|
}
|
|
4441
|
+
/**
|
|
4442
|
+
* Register a callback invoked when a persisted config is migrated off a
|
|
4443
|
+
* stale `npx` command to the current manifest's command/args (see
|
|
4444
|
+
* `migrateNpxConfig`). The caller is expected to persist `updates` against
|
|
4445
|
+
* `id` in `integrations.json` so the fix survives the next boot instead of
|
|
4446
|
+
* silently reapplying on every start.
|
|
4447
|
+
*/
|
|
4448
|
+
setOnConfigMigrated(cb) {
|
|
4449
|
+
this.onConfigMigrated = cb;
|
|
4450
|
+
}
|
|
4411
4451
|
/**
|
|
4412
4452
|
* Is `npmPackage` present AND usable?
|
|
4413
4453
|
*
|
|
@@ -4438,6 +4478,23 @@ var IntegrationManager = class {
|
|
|
4438
4478
|
return false;
|
|
4439
4479
|
}
|
|
4440
4480
|
}
|
|
4481
|
+
/**
|
|
4482
|
+
* The installed version of `npmPackage`, or null if it isn't usably
|
|
4483
|
+
* installed at all (see `isUsableInstall`) or its package.json has no
|
|
4484
|
+
* parseable `version`. Never throws — an unreadable version is treated the
|
|
4485
|
+
* same as "not installed", which is the safe direction: it drives a
|
|
4486
|
+
* (re)install rather than a false "already pinned".
|
|
4487
|
+
*/
|
|
4488
|
+
readInstalledVersion(npmPackage) {
|
|
4489
|
+
if (!this.isUsableInstall(npmPackage)) return null;
|
|
4490
|
+
const pkgJson = join13(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"), "package.json");
|
|
4491
|
+
try {
|
|
4492
|
+
const pkg = JSON.parse(readFileSync14(pkgJson, "utf8"));
|
|
4493
|
+
return typeof pkg.version === "string" ? pkg.version : null;
|
|
4494
|
+
} catch {
|
|
4495
|
+
return null;
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4441
4498
|
/**
|
|
4442
4499
|
* Does the node_modules/.bin shim for `binName` exist? On Windows npm writes
|
|
4443
4500
|
* `<name>`, `<name>.cmd`, and `<name>.ps1`; the .cmd variant is what actually
|
|
@@ -4448,9 +4505,32 @@ var IntegrationManager = class {
|
|
|
4448
4505
|
const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
|
|
4449
4506
|
return candidates.some((c) => existsSync13(join13(INTEGRATIONS_BIN, c)));
|
|
4450
4507
|
}
|
|
4508
|
+
/**
|
|
4509
|
+
* The exact version THIS gateway's CURRENTLY LOADED manifest pins
|
|
4510
|
+
* `config.npmPackage` to — or undefined when there's no npmPackage, no
|
|
4511
|
+
* manifest for this id, or the manifest's `npmPackage` no longer names the
|
|
4512
|
+
* same package as `config` (a manually re-pointed integration: nothing in
|
|
4513
|
+
* the manifest vouches for a version of config's actual package).
|
|
4514
|
+
*
|
|
4515
|
+
* Read fresh from `getManifest(config.id)` on every call, never from
|
|
4516
|
+
* anything persisted on `config` itself — manifests are reloaded fresh on
|
|
4517
|
+
* every boot, `integrations.json` is not, so this is what keeps the pin
|
|
4518
|
+
* from going stale the day after an integration was first activated. Used
|
|
4519
|
+
* both to decide the npm install/update (`ensureInstalled`) and to decide
|
|
4520
|
+
* whether a persisted `npx …` command must be refused/migrated
|
|
4521
|
+
* (`migrateNpxConfig`) — one definition of "pinned", so the two can't
|
|
4522
|
+
* disagree about which connectors it applies to.
|
|
4523
|
+
*/
|
|
4524
|
+
pinnedVersionFor(config) {
|
|
4525
|
+
if (!config.npmPackage) return void 0;
|
|
4526
|
+
const manifest = getManifest(config.id);
|
|
4527
|
+
return manifest?.npmPackage === config.npmPackage ? manifest.npmVersion : void 0;
|
|
4528
|
+
}
|
|
4451
4529
|
/**
|
|
4452
4530
|
* Install an npm package into the managed integrations directory if not
|
|
4453
|
-
* already present
|
|
4531
|
+
* already present, and — when `config`'s manifest pins an exact
|
|
4532
|
+
* `npmVersion` for this same package — bring the installed copy to EXACTLY
|
|
4533
|
+
* that version, older or newer. Called automatically before spawning when
|
|
4454
4534
|
* config.npmPackage is set.
|
|
4455
4535
|
*
|
|
4456
4536
|
* Asynchronous ON PURPOSE. This used to be execSync, which blocked the MCP
|
|
@@ -4460,19 +4540,49 @@ var IntegrationManager = class {
|
|
|
4460
4540
|
* CI runners: ~3.6s (Linux), ~5.4s (macOS), ~14s (Windows), and far worse
|
|
4461
4541
|
* on machines with real-time antivirus.
|
|
4462
4542
|
*/
|
|
4463
|
-
async ensureInstalled(
|
|
4543
|
+
async ensureInstalled(config) {
|
|
4544
|
+
const npmPackage = config.npmPackage;
|
|
4545
|
+
if (!npmPackage) return;
|
|
4464
4546
|
ensureIntegrationsDir();
|
|
4547
|
+
const pinnedVersion = this.pinnedVersionFor(config);
|
|
4548
|
+
if (pinnedVersion) {
|
|
4549
|
+
if (this.readInstalledVersion(npmPackage) === pinnedVersion) return;
|
|
4550
|
+
return withInstallLock(async () => {
|
|
4551
|
+
const current = this.readInstalledVersion(npmPackage);
|
|
4552
|
+
if (current === pinnedVersion) return;
|
|
4553
|
+
console.error(
|
|
4554
|
+
`[IntegrationManager] ${npmPackage} ${current ?? "(not installed)"} \u2192 ${pinnedVersion} (pinned by manifest)`
|
|
4555
|
+
);
|
|
4556
|
+
const cleanFirst = !this.isUsableInstall(npmPackage);
|
|
4557
|
+
await this.installNow(npmPackage, pinnedVersion, cleanFirst);
|
|
4558
|
+
});
|
|
4559
|
+
}
|
|
4465
4560
|
if (this.isUsableInstall(npmPackage)) return;
|
|
4466
4561
|
return withInstallLock(async () => {
|
|
4467
4562
|
if (this.isUsableInstall(npmPackage)) return;
|
|
4468
4563
|
await this.installNow(npmPackage);
|
|
4469
4564
|
});
|
|
4470
4565
|
}
|
|
4471
|
-
/**
|
|
4472
|
-
|
|
4566
|
+
/**
|
|
4567
|
+
* The actual install. Callers MUST hold the install lock.
|
|
4568
|
+
*
|
|
4569
|
+
* `exactVersion`, when given, is a pin already validated as an exact semver
|
|
4570
|
+
* by the manifest loader (`isExactSemver`) — re-validated here anyway
|
|
4571
|
+
* because this method does not trust its caller's caller, and because the
|
|
4572
|
+
* version reaches a shell on Windows (see the package-name check below).
|
|
4573
|
+
*
|
|
4574
|
+
* `cleanFirst` (default true, i.e. today's behaviour for every caller
|
|
4575
|
+
* except a version-only pin correction): wipe any existing package
|
|
4576
|
+
* directory before installing. Needed for a genuinely broken/partial
|
|
4577
|
+
* install, where stale files can make the reinstall itself fail; skipped
|
|
4578
|
+
* for a USABLE install being corrected to a different pinned version, so a
|
|
4579
|
+
* failed update leaves the previously-working version on disk instead of
|
|
4580
|
+
* nothing at all.
|
|
4581
|
+
*/
|
|
4582
|
+
async installNow(npmPackage, exactVersion, cleanFirst = true) {
|
|
4473
4583
|
const pkgDir = join13(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
4474
|
-
if (existsSync13(pkgDir)) {
|
|
4475
|
-
console.error(`[IntegrationManager] Removing unusable install of ${npmPackage}`);
|
|
4584
|
+
if (cleanFirst && existsSync13(pkgDir)) {
|
|
4585
|
+
console.error(`[IntegrationManager] Removing ${exactVersion ? "outdated" : "unusable"} install of ${npmPackage}`);
|
|
4476
4586
|
try {
|
|
4477
4587
|
rmSync(pkgDir, { recursive: true, force: true });
|
|
4478
4588
|
} catch (err) {
|
|
@@ -4482,18 +4592,22 @@ var IntegrationManager = class {
|
|
|
4482
4592
|
if (!/^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*$/i.test(npmPackage)) {
|
|
4483
4593
|
throw new Error(`Refusing to install unsafe package name: ${JSON.stringify(npmPackage)}`);
|
|
4484
4594
|
}
|
|
4485
|
-
|
|
4595
|
+
if (exactVersion !== void 0 && !isExactSemver(exactVersion)) {
|
|
4596
|
+
throw new Error(`Refusing to install ${npmPackage} at unsafe/non-exact version spec: ${JSON.stringify(exactVersion)}`);
|
|
4597
|
+
}
|
|
4598
|
+
const spec = exactVersion ? `${npmPackage}@${exactVersion}` : npmPackage;
|
|
4599
|
+
console.error(`[IntegrationManager] Installing ${spec}...`);
|
|
4486
4600
|
const startedAt = Date.now();
|
|
4487
4601
|
const isWin = process.platform === "win32";
|
|
4488
4602
|
await new Promise((resolve3, reject) => {
|
|
4489
4603
|
const child = execFile(
|
|
4490
4604
|
"npm",
|
|
4491
|
-
["install", "--no-fund", "--no-audit",
|
|
4605
|
+
["install", "--no-fund", "--no-audit", spec],
|
|
4492
4606
|
{
|
|
4493
4607
|
cwd: INTEGRATIONS_DIR,
|
|
4494
4608
|
// Windows: a shell so cmd.exe finds npm.cmd (see EINVAL note above).
|
|
4495
4609
|
// POSIX: no shell — npm is a normal executable and keeping shell off
|
|
4496
|
-
// means the validated package
|
|
4610
|
+
// means the validated package spec is still passed as a bare argv.
|
|
4497
4611
|
shell: isWin,
|
|
4498
4612
|
// No timeout: a slow install on a machine with antivirus is not an
|
|
4499
4613
|
// error, and killing it midway is what created broken installs in
|
|
@@ -4503,20 +4617,28 @@ var IntegrationManager = class {
|
|
|
4503
4617
|
},
|
|
4504
4618
|
(err, _stdout, stderr) => {
|
|
4505
4619
|
if (err) {
|
|
4506
|
-
reject(new Error(`Failed to install ${
|
|
4620
|
+
reject(new Error(`Failed to install ${spec}: ${err.message}${stderr ? ` \u2014 ${stderr.trim()}` : ""}`));
|
|
4507
4621
|
return;
|
|
4508
4622
|
}
|
|
4509
4623
|
resolve3();
|
|
4510
4624
|
}
|
|
4511
4625
|
);
|
|
4512
|
-
child.on("error", (err) => reject(new Error(`Failed to run npm for ${
|
|
4626
|
+
child.on("error", (err) => reject(new Error(`Failed to run npm for ${spec}: ${err.message}`)));
|
|
4513
4627
|
});
|
|
4514
4628
|
if (!this.isUsableInstall(npmPackage)) {
|
|
4515
4629
|
throw new Error(
|
|
4516
|
-
`Installed ${
|
|
4630
|
+
`Installed ${spec} but it is not usable \u2014 package.json or its bin target is missing. Check ${INTEGRATIONS_DIR} for a partial install.`
|
|
4517
4631
|
);
|
|
4518
4632
|
}
|
|
4519
|
-
|
|
4633
|
+
if (exactVersion !== void 0) {
|
|
4634
|
+
const installed = this.readInstalledVersion(npmPackage);
|
|
4635
|
+
if (installed !== exactVersion) {
|
|
4636
|
+
throw new Error(
|
|
4637
|
+
`Installed ${npmPackage} but got version ${installed ?? "unknown"}, expected the pinned ${exactVersion}.`
|
|
4638
|
+
);
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4641
|
+
console.error(`[IntegrationManager] Installed ${spec} (${((Date.now() - startedAt) / 1e3).toFixed(1)}s)`);
|
|
4520
4642
|
}
|
|
4521
4643
|
/**
|
|
4522
4644
|
* Connect a client to its transport, but never wait indefinitely. Resolves
|
|
@@ -4575,7 +4697,26 @@ var IntegrationManager = class {
|
|
|
4575
4697
|
throw new Error(`${config.id}: ${SIMULATION_BLOCK_REASON}`);
|
|
4576
4698
|
}
|
|
4577
4699
|
if (config.npmPackage) {
|
|
4578
|
-
await this.ensureInstalled(config
|
|
4700
|
+
await this.ensureInstalled(config);
|
|
4701
|
+
}
|
|
4702
|
+
const pinnedVersion = this.pinnedVersionFor(config);
|
|
4703
|
+
if (pinnedVersion && config.command === "npx") {
|
|
4704
|
+
const manifest = getManifest(config.id);
|
|
4705
|
+
const migrated = {
|
|
4706
|
+
command: manifest.mcp.command,
|
|
4707
|
+
args: manifest.mcp.args,
|
|
4708
|
+
env: manifest.mcp.env ?? config.env
|
|
4709
|
+
};
|
|
4710
|
+
console.error(
|
|
4711
|
+
`[IntegrationManager] ${config.id}: command migrated to manifest (was npx ${config.args.join(" ")})`
|
|
4712
|
+
);
|
|
4713
|
+
config = { ...config, ...migrated };
|
|
4714
|
+
this.onConfigMigrated?.(config.id, migrated);
|
|
4715
|
+
}
|
|
4716
|
+
if (pinnedVersion && config.command === "npx") {
|
|
4717
|
+
throw new Error(
|
|
4718
|
+
`${config.id}: refusing to spawn a pinned connector ("${config.npmPackage}"@${pinnedVersion}) via npx \u2014 this would ignore the pinned local install and fetch the package fresh on every start. The manifest's mcp.command must name the installed binary, not "npx".`
|
|
4719
|
+
);
|
|
4579
4720
|
}
|
|
4580
4721
|
const env = this.resolveEnvKeys(config);
|
|
4581
4722
|
if (isSimulationMode()) {
|
|
@@ -5129,6 +5270,10 @@ function refreshAllSessions() {
|
|
|
5129
5270
|
integrationManager.setOnToolsChanged(() => {
|
|
5130
5271
|
refreshAllSessions();
|
|
5131
5272
|
});
|
|
5273
|
+
integrationManager.setOnConfigMigrated((id, updates) => {
|
|
5274
|
+
integrationRegistry.update(id, updates);
|
|
5275
|
+
console.error(`[Suveren MCP] ${id}: persisted config migrated (${Object.keys(updates).join(", ")})`);
|
|
5276
|
+
});
|
|
5132
5277
|
var app = express();
|
|
5133
5278
|
app.use(express.json());
|
|
5134
5279
|
app.use((_req, res, next) => {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimum Node.js version for the gateway — checked at start by
|
|
3
|
+
* bundle/bin/suveren-gateway.js and bundle/server.js.
|
|
4
|
+
*
|
|
5
|
+
* The connectors the gateway installs (crm, erp, email, records — all built on
|
|
6
|
+
* better-sqlite3 13) need Node 22. `engines` in package.json is not enough:
|
|
7
|
+
* npm only WARNS on a mismatch, and for an unpinned install it silently picks
|
|
8
|
+
* an older connector that still runs on the old Node. With pinned connector
|
|
9
|
+
* versions the pinned connector is installed anyway and crashes on its first
|
|
10
|
+
* database call. Refusing at start, with the reason, is the only visible
|
|
11
|
+
* failure. Keep MIN_NODE_MAJOR in step with `engines` in package.json.tpl.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const MIN_NODE_MAJOR = 22;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Returns null when `version` (e.g. process.versions.node, "22.11.0") is
|
|
18
|
+
* supported, otherwise a message naming both versions. An unparseable version
|
|
19
|
+
* is refused — the safe direction.
|
|
20
|
+
*/
|
|
21
|
+
export function unsupportedNodeReason(version) {
|
|
22
|
+
const major = Number.parseInt(String(version).split('.')[0], 10);
|
|
23
|
+
if (Number.isInteger(major) && major >= MIN_NODE_MAJOR) return null;
|
|
24
|
+
return (
|
|
25
|
+
`Suveren gateway needs Node.js ${MIN_NODE_MAJOR} or newer — this is Node.js ${version}. ` +
|
|
26
|
+
`Install a current Node.js (https://nodejs.org) and start the gateway again.`
|
|
27
|
+
);
|
|
28
|
+
}
|