@commercetools/commerce-mcp 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +117 -9
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
7
|
exports.resolveToolsForConfiguration = exports.applyResolvedToolsToConfiguration = exports.ACCEPTED_TOOLS = void 0;
|
|
5
8
|
exports.parseArgs = parseArgs;
|
|
@@ -7,7 +10,9 @@ exports.main = main;
|
|
|
7
10
|
const modelcontextprotocol_1 = require("@commercetools/commerce-agent/modelcontextprotocol");
|
|
8
11
|
const processors_1 = require("@commercetools/processors");
|
|
9
12
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
10
|
-
const colors_1 = require("colors");
|
|
13
|
+
const colors_1 = __importDefault(require("colors"));
|
|
14
|
+
const red = colors_1.default.red;
|
|
15
|
+
const yellow = colors_1.default.yellow;
|
|
11
16
|
const HIDDEN_ARGS = ['customerId', 'isAdmin', 'storeKey', 'businessUnitKey'];
|
|
12
17
|
const PUBLIC_ARGS = [
|
|
13
18
|
'tools',
|
|
@@ -21,6 +26,10 @@ const PUBLIC_ARGS = [
|
|
|
21
26
|
'dynamicToolLoadingThreshold',
|
|
22
27
|
'toolOutputFormat',
|
|
23
28
|
'logging',
|
|
29
|
+
'host',
|
|
30
|
+
'port',
|
|
31
|
+
'allowedHosts',
|
|
32
|
+
'allowedOrigins',
|
|
24
33
|
];
|
|
25
34
|
const ACCEPTED_ARGS = [...PUBLIC_ARGS, ...HIDDEN_ARGS];
|
|
26
35
|
var modelcontextprotocol_2 = require("@commercetools/commerce-agent/modelcontextprotocol");
|
|
@@ -63,6 +72,15 @@ function parseArgs(args) {
|
|
|
63
72
|
else if (key == 'stateless') {
|
|
64
73
|
env.stateless = value == 'true';
|
|
65
74
|
}
|
|
75
|
+
else if (key == 'host') {
|
|
76
|
+
env.host = value;
|
|
77
|
+
}
|
|
78
|
+
else if (key == 'allowedHosts') {
|
|
79
|
+
env.allowedHosts = splitList(value);
|
|
80
|
+
}
|
|
81
|
+
else if (key == 'allowedOrigins') {
|
|
82
|
+
env.allowedOrigins = splitList(value);
|
|
83
|
+
}
|
|
66
84
|
else if (key == 'port') {
|
|
67
85
|
env.port = Number(value);
|
|
68
86
|
}
|
|
@@ -130,6 +148,11 @@ function parseArgs(args) {
|
|
|
130
148
|
env.remote = env.remote || process.env.REMOTE == 'true';
|
|
131
149
|
env.logging = env.logging || process.env.LOGGING == 'true';
|
|
132
150
|
env.stateless = env.stateless || process.env.STATELESS == 'true';
|
|
151
|
+
env.host = env.host || process.env.HOST || modelcontextprotocol_1.DEFAULT_HOST;
|
|
152
|
+
env.allowedHosts =
|
|
153
|
+
env.allowedHosts || splitList(process.env.ALLOWED_HOSTS) || undefined;
|
|
154
|
+
env.allowedOrigins =
|
|
155
|
+
env.allowedOrigins || splitList(process.env.ALLOWED_ORIGINS) || undefined;
|
|
133
156
|
env.port = env.port || Number(process.env.PORT);
|
|
134
157
|
options.businessUnitKey =
|
|
135
158
|
options.businessUnitKey || process.env.BUSINESS_UNIT_KEY;
|
|
@@ -160,7 +183,7 @@ function parseArgs(args) {
|
|
|
160
183
|
}
|
|
161
184
|
break;
|
|
162
185
|
case 'auth_token':
|
|
163
|
-
if (!env.accessToken) {
|
|
186
|
+
if (!env.remote && !env.accessToken) {
|
|
164
187
|
throw new Error('Missing required access token when "authType" is "auth_token". Please make sure to provide the value for "accessToken" or via environment variable (ACCESS_TOKEN).');
|
|
165
188
|
}
|
|
166
189
|
break;
|
|
@@ -260,14 +283,72 @@ function createAuthConfig(env) {
|
|
|
260
283
|
throw new Error(`Unsupported auth type: ${env.authType}`);
|
|
261
284
|
}
|
|
262
285
|
}
|
|
286
|
+
function splitList(value) {
|
|
287
|
+
if (!value)
|
|
288
|
+
return undefined;
|
|
289
|
+
const entries = value
|
|
290
|
+
.split(',')
|
|
291
|
+
.map((entry) => entry.trim())
|
|
292
|
+
.filter(Boolean);
|
|
293
|
+
return entries.length > 0 ? entries : undefined;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Hostnames the server will answer for. Loopback always works; the interface
|
|
297
|
+
* it was told to bind is added so addressing the server by that address works
|
|
298
|
+
* without extra configuration, and `--allowedHosts` covers the rest (a domain
|
|
299
|
+
* in front of a proxy, a container hostname, ...).
|
|
300
|
+
*/
|
|
301
|
+
function resolveAllowedHosts(env) {
|
|
302
|
+
const hosts = new Set([...modelcontextprotocol_1.LOOPBACK_HOSTNAMES, ...(env.allowedHosts ?? [])]);
|
|
303
|
+
const host = env.host;
|
|
304
|
+
if (host && !isWildcardHost(host)) {
|
|
305
|
+
hosts.add(host);
|
|
306
|
+
}
|
|
307
|
+
return [...hosts];
|
|
308
|
+
}
|
|
309
|
+
const LOOPBACK_HOSTS = ['127.0.0.1', 'localhost', '::1', '::ffff:127.0.0.1'];
|
|
310
|
+
/** Hosts that mean "listen on every interface" rather than a single address. */
|
|
311
|
+
const WILDCARD_HOSTS = ['0.0.0.0', '::', '::0', '*', ''];
|
|
312
|
+
function isLoopbackHost(host) {
|
|
313
|
+
return LOOPBACK_HOSTS.includes(host) || host.startsWith('127.');
|
|
314
|
+
}
|
|
315
|
+
function isWildcardHost(host) {
|
|
316
|
+
return WILDCARD_HOSTS.includes(host.trim());
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Binding beyond loopback puts the server on the network, where anything that
|
|
320
|
+
* can reach the port can talk to it. That is a legitimate choice behind a
|
|
321
|
+
* gateway, but it should never happen without the operator noticing — and a
|
|
322
|
+
* wildcard bind deserves a louder warning than a single chosen interface,
|
|
323
|
+
* since it also covers interfaces the operator may not have had in mind.
|
|
324
|
+
*/
|
|
325
|
+
function warnIfPubliclyBound(host) {
|
|
326
|
+
if (isLoopbackHost(host))
|
|
327
|
+
return;
|
|
328
|
+
const exposure = isWildcardHost(host)
|
|
329
|
+
? `bound to ${host || '0.0.0.0'} — every network interface on this machine, including\n` +
|
|
330
|
+
` any that is publicly routable`
|
|
331
|
+
: `bound to ${host}, so it is reachable from outside this machine`;
|
|
332
|
+
console.error(yellow(`\n\u26a0\ufe0f The MCP server is ${exposure}.\n` +
|
|
333
|
+
` Callers still need a valid "Authorization: Bearer <token>" header, but make sure the port\n` +
|
|
334
|
+
` is only exposed to networks you trust. Use --host=127.0.0.1 to keep it local.\n`));
|
|
335
|
+
}
|
|
336
|
+
/** True once the server actually holds an address. */
|
|
337
|
+
function isBound(server) {
|
|
338
|
+
const address = server?.address;
|
|
339
|
+
return typeof address === 'function' ? address.call(server) !== null : true;
|
|
340
|
+
}
|
|
341
|
+
function onServerError(server, listener) {
|
|
342
|
+
const on = server?.on;
|
|
343
|
+
if (typeof on === 'function')
|
|
344
|
+
on.call(server, 'error', listener);
|
|
345
|
+
}
|
|
263
346
|
function handleError(error) {
|
|
264
|
-
console.error(
|
|
265
|
-
console.error(
|
|
347
|
+
console.error(red('\n🚨 Error initializing commercetools MCP server:\n'));
|
|
348
|
+
console.error(yellow(` ${error.message}\n`));
|
|
266
349
|
}
|
|
267
350
|
async function main() {
|
|
268
|
-
require('dotenv').config({
|
|
269
|
-
quiet: true,
|
|
270
|
-
});
|
|
351
|
+
require('dotenv').config({ quiet: true });
|
|
271
352
|
const { options, env } = parseArgs(process.argv.slice(2));
|
|
272
353
|
// Create the CommercetoolsCommerceAgent instance
|
|
273
354
|
const selectedTools = options.tools;
|
|
@@ -302,13 +383,40 @@ async function main() {
|
|
|
302
383
|
authConfig,
|
|
303
384
|
configuration,
|
|
304
385
|
stateless: env.stateless,
|
|
386
|
+
allowedHosts: resolveAllowedHosts(env),
|
|
387
|
+
allowedOrigins: env.allowedOrigins,
|
|
305
388
|
streamableHttpOptions: {
|
|
306
389
|
sessionIdGenerator: undefined,
|
|
307
390
|
},
|
|
308
391
|
});
|
|
309
392
|
const port = env.port || 8080;
|
|
310
|
-
|
|
311
|
-
|
|
393
|
+
// `*` and an empty value mean "every interface"; the OS needs 0.0.0.0.
|
|
394
|
+
const host = (0, modelcontextprotocol_1.normalizeBindHost)(env.host);
|
|
395
|
+
warnIfPubliclyBound(host);
|
|
396
|
+
/**
|
|
397
|
+
* Kept in a holder so the listen callback can read the server without a
|
|
398
|
+
* binding that is still in its temporal dead zone should an app
|
|
399
|
+
* implementation invoke the callback synchronously.
|
|
400
|
+
*/
|
|
401
|
+
const bound = {};
|
|
402
|
+
bound.server = streamServer.listen(port, host, function () {
|
|
403
|
+
/**
|
|
404
|
+
* Express runs this callback even when the bind failed (an unresolvable
|
|
405
|
+
* host leaves `address()` null), so confirm before claiming success —
|
|
406
|
+
* the error handler below reports the failure instead.
|
|
407
|
+
*/
|
|
408
|
+
if (!isBound(bound.server))
|
|
409
|
+
return;
|
|
410
|
+
console.error(`Stream server listening on ${host}:${port}`);
|
|
411
|
+
});
|
|
412
|
+
/**
|
|
413
|
+
* A bind failure — port already taken, address not available, a host that
|
|
414
|
+
* does not resolve — arrives as an 'error' event. Left unhandled it kills
|
|
415
|
+
* the process with no explanation of what went wrong.
|
|
416
|
+
*/
|
|
417
|
+
onServerError(bound.server, (error) => {
|
|
418
|
+
handleError(new Error(`Unable to bind ${host}:${port}${error.code ? ` (${error.code})` : ''}. ${error.message}`));
|
|
419
|
+
process.exitCode = 1;
|
|
312
420
|
});
|
|
313
421
|
}
|
|
314
422
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commercetools/commerce-mcp",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"mcpName": "io.github.commercetools/commerce-mcp",
|
|
5
5
|
"homepage": "https://github.com/commercetools/commerce-mcp",
|
|
6
6
|
"description": "A command line tool for setting up commercetools MCP server",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"colors": "^1.4.0",
|
|
24
24
|
"dotenv": "^17.2.1",
|
|
25
25
|
"mcp-remote": "^0.1.18",
|
|
26
|
-
"@commercetools/commerce-agent": "4.
|
|
26
|
+
"@commercetools/commerce-agent": "4.1.0",
|
|
27
27
|
"@commercetools/processors": "0.0.0"
|
|
28
28
|
},
|
|
29
29
|
"keywords": [
|