@commercetools/commerce-mcp 3.0.1 → 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/README.md +2 -2
- package/dist/index.js +119 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -13,10 +13,10 @@ pnpm run build
|
|
|
13
13
|
pnpm add link:../typescript
|
|
14
14
|
|
|
15
15
|
# run the server in terminal
|
|
16
|
-
npx ts-node src/index.ts --tools=
|
|
16
|
+
npx ts-node src/index.ts --tools=read_products --clientId=CLIENT_ID --clientSecret=CLIENT_SECRET --authUrl=AUTH_URL --projectKey=PROJECT_KEY --apiUrl=API_URL
|
|
17
17
|
|
|
18
18
|
# test using mcptools : Install mcptools from https://github.com/f/mcptools
|
|
19
|
-
mcp call
|
|
19
|
+
mcp call read_products --params '{"limit": 2}' npx ts-node /<absolute-path>/commerce-agent/modelcontextprotocol/src/index.ts --tools=all \
|
|
20
20
|
--projectKey="PROJECT_KEY" \
|
|
21
21
|
--clientSecret="CLIENT_SECRET" \
|
|
22
22
|
--clientId="CLIENT_ID" \
|
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',
|
|
@@ -20,6 +25,11 @@ const PUBLIC_ARGS = [
|
|
|
20
25
|
'apiUrl',
|
|
21
26
|
'dynamicToolLoadingThreshold',
|
|
22
27
|
'toolOutputFormat',
|
|
28
|
+
'logging',
|
|
29
|
+
'host',
|
|
30
|
+
'port',
|
|
31
|
+
'allowedHosts',
|
|
32
|
+
'allowedOrigins',
|
|
23
33
|
];
|
|
24
34
|
const ACCEPTED_ARGS = [...PUBLIC_ARGS, ...HIDDEN_ARGS];
|
|
25
35
|
var modelcontextprotocol_2 = require("@commercetools/commerce-agent/modelcontextprotocol");
|
|
@@ -62,6 +72,15 @@ function parseArgs(args) {
|
|
|
62
72
|
else if (key == 'stateless') {
|
|
63
73
|
env.stateless = value == 'true';
|
|
64
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
|
+
}
|
|
65
84
|
else if (key == 'port') {
|
|
66
85
|
env.port = Number(value);
|
|
67
86
|
}
|
|
@@ -108,7 +127,7 @@ function parseArgs(args) {
|
|
|
108
127
|
}
|
|
109
128
|
// Validate tools against accepted enum values
|
|
110
129
|
options.tools.forEach((tool) => {
|
|
111
|
-
if (tool == 'all' || tool == '
|
|
130
|
+
if (tool == 'all' || tool == 'read_all') {
|
|
112
131
|
return;
|
|
113
132
|
}
|
|
114
133
|
if (!modelcontextprotocol_1.ACCEPTED_TOOLS.includes(tool.trim())) {
|
|
@@ -129,6 +148,11 @@ function parseArgs(args) {
|
|
|
129
148
|
env.remote = env.remote || process.env.REMOTE == 'true';
|
|
130
149
|
env.logging = env.logging || process.env.LOGGING == 'true';
|
|
131
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;
|
|
132
156
|
env.port = env.port || Number(process.env.PORT);
|
|
133
157
|
options.businessUnitKey =
|
|
134
158
|
options.businessUnitKey || process.env.BUSINESS_UNIT_KEY;
|
|
@@ -159,7 +183,7 @@ function parseArgs(args) {
|
|
|
159
183
|
}
|
|
160
184
|
break;
|
|
161
185
|
case 'auth_token':
|
|
162
|
-
if (!env.accessToken) {
|
|
186
|
+
if (!env.remote && !env.accessToken) {
|
|
163
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).');
|
|
164
188
|
}
|
|
165
189
|
break;
|
|
@@ -259,14 +283,72 @@ function createAuthConfig(env) {
|
|
|
259
283
|
throw new Error(`Unsupported auth type: ${env.authType}`);
|
|
260
284
|
}
|
|
261
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
|
+
}
|
|
262
346
|
function handleError(error) {
|
|
263
|
-
console.error(
|
|
264
|
-
console.error(
|
|
347
|
+
console.error(red('\n🚨 Error initializing commercetools MCP server:\n'));
|
|
348
|
+
console.error(yellow(` ${error.message}\n`));
|
|
265
349
|
}
|
|
266
350
|
async function main() {
|
|
267
|
-
require('dotenv').config({
|
|
268
|
-
quiet: true,
|
|
269
|
-
});
|
|
351
|
+
require('dotenv').config({ quiet: true });
|
|
270
352
|
const { options, env } = parseArgs(process.argv.slice(2));
|
|
271
353
|
// Create the CommercetoolsCommerceAgent instance
|
|
272
354
|
const selectedTools = options.tools;
|
|
@@ -301,13 +383,40 @@ async function main() {
|
|
|
301
383
|
authConfig,
|
|
302
384
|
configuration,
|
|
303
385
|
stateless: env.stateless,
|
|
386
|
+
allowedHosts: resolveAllowedHosts(env),
|
|
387
|
+
allowedOrigins: env.allowedOrigins,
|
|
304
388
|
streamableHttpOptions: {
|
|
305
389
|
sessionIdGenerator: undefined,
|
|
306
390
|
},
|
|
307
391
|
});
|
|
308
392
|
const port = env.port || 8080;
|
|
309
|
-
|
|
310
|
-
|
|
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;
|
|
311
420
|
});
|
|
312
421
|
}
|
|
313
422
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commercetools/commerce-mcp",
|
|
3
|
-
"version": "
|
|
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": "
|
|
26
|
+
"@commercetools/commerce-agent": "4.1.0",
|
|
27
27
|
"@commercetools/processors": "0.0.0"
|
|
28
28
|
},
|
|
29
29
|
"keywords": [
|