@reactionary/mcp 0.0.0-stage → 0.10.31

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/inspect.js ADDED
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { loadProjectRootEnv } from "./lib/load-project-root-env.js";
5
+ loadProjectRootEnv(import.meta.url);
6
+ const isSourceEntrypoint = import.meta.url.endsWith("/inspect.ts");
7
+ const cliPath = fileURLToPath(
8
+ new URL(isSourceEntrypoint ? "./cli.ts" : "./cli.js", import.meta.url)
9
+ );
10
+ const host = process.env["MCP_HOST"] ?? "127.0.0.1";
11
+ const port = process.env["MCP_PORT"] ?? process.env["PORT"] ?? "3000";
12
+ const path = process.env["MCP_PATH"] ?? "/mcp";
13
+ const serverUrl = `http://${host}:${port}${path}`;
14
+ let serverProcess;
15
+ let inspectorProcess;
16
+ async function main() {
17
+ serverProcess = spawn(process.execPath, getServerArgs(), {
18
+ stdio: "inherit",
19
+ env: process.env
20
+ });
21
+ serverProcess.once("exit", (code, signal) => {
22
+ if (!inspectorProcess || inspectorProcess.exitCode !== null) {
23
+ return;
24
+ }
25
+ console.error(
26
+ `Reactionary MCP server exited before inspector closed: ${formatExit(
27
+ code,
28
+ signal
29
+ )}`
30
+ );
31
+ inspectorProcess.kill("SIGTERM");
32
+ });
33
+ await waitForServerStartup(serverProcess, serverUrl);
34
+ inspectorProcess = spawn(getPnpmCommand(), [
35
+ "dlx",
36
+ "@modelcontextprotocol/inspector",
37
+ "--web",
38
+ "--transport",
39
+ "http",
40
+ "--server-url",
41
+ serverUrl
42
+ ], {
43
+ stdio: "inherit",
44
+ env: process.env
45
+ });
46
+ const exitCode = await waitForExit(inspectorProcess);
47
+ await stopServer();
48
+ process.exit(exitCode);
49
+ }
50
+ function getServerArgs() {
51
+ if (!isSourceEntrypoint) {
52
+ return [cliPath];
53
+ }
54
+ return ["--loader", "@swc-node/register/esm", cliPath];
55
+ }
56
+ function getPnpmCommand() {
57
+ return process.platform === "win32" ? "pnpm.cmd" : "pnpm";
58
+ }
59
+ async function waitForServerStartup(child, url) {
60
+ const timeoutMs = process.env["MCP_INSPECTOR_STARTUP_TIMEOUT_MS"] ? Number.parseInt(process.env["MCP_INSPECTOR_STARTUP_TIMEOUT_MS"], 10) : 1e4;
61
+ if (!Number.isInteger(timeoutMs) || timeoutMs < 1) {
62
+ throw new Error(
63
+ `Invalid MCP_INSPECTOR_STARTUP_TIMEOUT_MS: ${process.env["MCP_INSPECTOR_STARTUP_TIMEOUT_MS"]}`
64
+ );
65
+ }
66
+ await Promise.race([
67
+ waitForReadyResponse(url, timeoutMs),
68
+ waitForEarlyExit(child)
69
+ ]);
70
+ }
71
+ async function waitForReadyResponse(url, timeoutMs) {
72
+ const deadline = Date.now() + timeoutMs;
73
+ let lastError;
74
+ while (Date.now() < deadline) {
75
+ try {
76
+ const response = await fetch(url, {
77
+ method: "POST",
78
+ headers: {
79
+ accept: "application/json, text/event-stream",
80
+ "content-type": "application/json"
81
+ },
82
+ body: JSON.stringify({
83
+ jsonrpc: "2.0",
84
+ id: 0,
85
+ method: "initialize",
86
+ params: {
87
+ protocolVersion: "2025-06-18",
88
+ capabilities: {},
89
+ clientInfo: {
90
+ name: "@reactionary/mcp inspector launcher",
91
+ version: "0.0.0"
92
+ }
93
+ }
94
+ })
95
+ });
96
+ if (response.ok) {
97
+ return;
98
+ }
99
+ lastError = new Error(
100
+ `Readiness request failed with HTTP ${response.status}`
101
+ );
102
+ } catch (error) {
103
+ lastError = error;
104
+ }
105
+ await delay(100);
106
+ }
107
+ throw new Error(
108
+ `Timed out waiting for Reactionary MCP server at ${url}: ${formatError(lastError)}`
109
+ );
110
+ }
111
+ function waitForEarlyExit(child) {
112
+ return new Promise((_, reject) => {
113
+ child.once("error", reject);
114
+ child.once("exit", (code, signal) => {
115
+ reject(
116
+ new Error(
117
+ `Reactionary MCP server exited before it was ready: ${formatExit(
118
+ code,
119
+ signal
120
+ )}`
121
+ )
122
+ );
123
+ });
124
+ });
125
+ }
126
+ function delay(delayMs) {
127
+ return new Promise((resolve) => {
128
+ setTimeout(resolve, delayMs);
129
+ });
130
+ }
131
+ function waitForExit(child) {
132
+ return new Promise((resolve) => {
133
+ child.once("exit", (code) => {
134
+ resolve(code ?? 0);
135
+ });
136
+ });
137
+ }
138
+ async function stopServer() {
139
+ if (!serverProcess || serverProcess.exitCode !== null) {
140
+ return;
141
+ }
142
+ serverProcess.kill("SIGTERM");
143
+ await waitForExit(serverProcess);
144
+ }
145
+ function formatExit(code, signal) {
146
+ if (signal) {
147
+ return `signal ${signal}`;
148
+ }
149
+ return `code ${code ?? 0}`;
150
+ }
151
+ function formatError(error) {
152
+ return error instanceof Error ? error.message : String(error);
153
+ }
154
+ main().catch(async (error) => {
155
+ console.error(error);
156
+ await stopServer();
157
+ process.exit(1);
158
+ });
@@ -0,0 +1,283 @@
1
+ import {
2
+ ClientBuilder,
3
+ NoOpCache,
4
+ createInitialRequestContext
5
+ } from "@reactionary/core";
6
+ import { withAlgoliaCapabilities } from "@reactionary/algolia";
7
+ import {
8
+ withCommercetoolsCapabilities
9
+ } from "@reactionary/commercetools";
10
+ import { withFakeCapabilities } from "@reactionary/fake";
11
+ import { withMagentoCapabilities } from "@reactionary/magento";
12
+ import { withMedusaCapabilities } from "@reactionary/medusa";
13
+ import {
14
+ withMeilisearchCapabilities
15
+ } from "@reactionary/meilisearch";
16
+ import { withUnomiCapabilities } from "@reactionary/unomi";
17
+ const providerSystems = [
18
+ "FAKE",
19
+ "MAGENTO",
20
+ "MEDUSA",
21
+ "COMMERCETOOLS",
22
+ "ALGOLIA",
23
+ "MEILISEARCH",
24
+ "UNOMI"
25
+ ];
26
+ function createReactionaryClientFromEnv(options = {}) {
27
+ const env = options.env ?? process.env;
28
+ const enabledSystems = getEnabledReactionaryMCPProviderSystems(env);
29
+ if (enabledSystems.length === 0) {
30
+ throw new Error(getNoEnabledProviderSystemsMessage());
31
+ }
32
+ const context = {
33
+ ...createInitialRequestContext(),
34
+ ...options.contextOverrides
35
+ };
36
+ let builder = new ClientBuilder(context).withCache(
37
+ options.cache ?? new NoOpCache()
38
+ );
39
+ for (const system of enabledSystems) {
40
+ switch (system) {
41
+ case "ALGOLIA":
42
+ builder = builder.withCapability(
43
+ withAlgoliaCapabilities(getAlgoliaConfiguration(env), {
44
+ productSearch: { enabled: true },
45
+ productRecommendations: { enabled: true }
46
+ })
47
+ );
48
+ break;
49
+ case "COMMERCETOOLS":
50
+ builder = builder.withCapability(
51
+ withCommercetoolsCapabilities(getCommercetoolsConfiguration(env), {
52
+ cart: { enabled: true },
53
+ product: { enabled: true },
54
+ category: { enabled: true },
55
+ checkout: { enabled: true },
56
+ identity: { enabled: true },
57
+ inventory: { enabled: true },
58
+ order: { enabled: true },
59
+ price: { enabled: true },
60
+ productSearch: { enabled: true },
61
+ productAssociations: { enabled: true },
62
+ productReviews: { enabled: true },
63
+ productList: { enabled: true },
64
+ orderSearch: { enabled: true },
65
+ companyRegistration: { enabled: true },
66
+ company: { enabled: true },
67
+ employee: { enabled: true },
68
+ employeeInvitation: { enabled: true },
69
+ store: { enabled: true },
70
+ profile: { enabled: true },
71
+ personalizationProfile: { enabled: true }
72
+ })
73
+ );
74
+ break;
75
+ case "FAKE":
76
+ builder = builder.withCapability(
77
+ withFakeCapabilities(getFakeConfiguration(), {
78
+ price: { enabled: true },
79
+ inventory: { enabled: true },
80
+ product: { enabled: true },
81
+ productReviews: { enabled: true },
82
+ productAssociations: { enabled: true },
83
+ featureFlag: { enabled: true }
84
+ })
85
+ );
86
+ break;
87
+ case "MAGENTO":
88
+ builder = builder.withCapability(
89
+ withMagentoCapabilities(getMagentoConfiguration(env), {
90
+ cart: { enabled: true },
91
+ product: { enabled: true },
92
+ category: { enabled: true },
93
+ identity: { enabled: true },
94
+ inventory: { enabled: true },
95
+ price: { enabled: true },
96
+ productSearch: { enabled: true },
97
+ profile: { enabled: true },
98
+ orderSearch: { enabled: true },
99
+ checkout: { enabled: true },
100
+ productAssociations: { enabled: true },
101
+ productRecommendations: { enabled: true },
102
+ productReviews: { enabled: true }
103
+ })
104
+ );
105
+ break;
106
+ case "MEDUSA":
107
+ builder = builder.withCapability(
108
+ withMedusaCapabilities(getMedusaConfiguration(env), {
109
+ cart: { enabled: true },
110
+ product: { enabled: true },
111
+ category: { enabled: true },
112
+ checkout: { enabled: true },
113
+ identity: { enabled: true },
114
+ inventory: { enabled: true },
115
+ order: { enabled: true },
116
+ price: { enabled: true },
117
+ productSearch: { enabled: true },
118
+ productRecommendations: { enabled: true },
119
+ productAssociations: { enabled: true },
120
+ orderSearch: { enabled: true },
121
+ store: { enabled: true },
122
+ profile: { enabled: true },
123
+ personalizationProfile: { enabled: true },
124
+ employee: { enabled: true },
125
+ employeeInvitation: { enabled: true },
126
+ company: { enabled: true },
127
+ companyRegistration: { enabled: true }
128
+ })
129
+ );
130
+ break;
131
+ case "MEILISEARCH":
132
+ builder = builder.withCapability(
133
+ withMeilisearchCapabilities(getMeilisearchConfiguration(env), {
134
+ productSearch: { enabled: true },
135
+ orderSearch: { enabled: true },
136
+ productRecommendations: { enabled: true }
137
+ })
138
+ );
139
+ break;
140
+ case "UNOMI":
141
+ builder = builder.withCapability(
142
+ withUnomiCapabilities(getUnomiConfiguration(env), {
143
+ personalizationProfile: { enabled: true },
144
+ analytics: { enabled: false }
145
+ })
146
+ );
147
+ break;
148
+ }
149
+ }
150
+ return {
151
+ client: builder.build(),
152
+ enabledSystems
153
+ };
154
+ }
155
+ function getEnabledReactionaryMCPProviderSystems(env = process.env) {
156
+ return providerSystems.filter(
157
+ (system) => isEnvEnabled(env[`ENABLED_${system}`])
158
+ );
159
+ }
160
+ function getNoEnabledProviderSystemsMessage() {
161
+ return `No Reactionary provider system is enabled. Set one of ${providerSystems.map((system) => `ENABLED_${system}=true`).join(", ")}.`;
162
+ }
163
+ function isEnvEnabled(value) {
164
+ return value?.toLowerCase() === "true";
165
+ }
166
+ function getAlgoliaConfiguration(env) {
167
+ return {
168
+ apiKey: env["ALGOLIA_API_KEY"] || "",
169
+ appId: env["ALGOLIA_APP_ID"] || "",
170
+ indexName: env["ALGOLIA_INDEX"] || "",
171
+ useRecommendationsForBots: env["ALGOLIA_USE_RECOMMENDATIONS_FOR_BOTS"] === "true",
172
+ useBaseIndexNameForEnglishLocale: env["ALGOLIA_USE_BASE_INDEX_NAME_FOR_ENGLISH_LOCALE"] === "true"
173
+ };
174
+ }
175
+ function getMeilisearchConfiguration(env) {
176
+ return {
177
+ apiKey: env["MEILISEARCH_API_KEY"] || "",
178
+ apiUrl: env["MEILISEARCH_API_URL"] || "",
179
+ indexName: env["MEILISEARCH_INDEX"] || "",
180
+ useAIEmbedding: env["MEILISEARCH_USE_AI_EMBEDDING"] || void 0,
181
+ semanticRatio: env["MEILISEARCH_SEMANTIC_RATIO"] ? parseFloat(env["MEILISEARCH_SEMANTIC_RATIO"]) : 0.5,
182
+ orderIndexName: env["MEILISEARCH_ORDER_INDEX"] || "order",
183
+ useRecommendationsForBots: env["MEILISEARCH_USE_RECOMMENDATIONS_FOR_BOTS"] === "true"
184
+ };
185
+ }
186
+ function getUnomiConfiguration(env) {
187
+ return {
188
+ apiUrl: env["UNOMI_API_URL"] || "",
189
+ username: env["UNOMI_USERNAME"] || "",
190
+ password: env["UNOMI_PASSWORD"] || "",
191
+ scope: env["UNOMI_SCOPE"] || "",
192
+ profilePath: env["UNOMI_PROFILE_PATH"] || "/cxs/profiles"
193
+ };
194
+ }
195
+ function getMedusaConfiguration(env) {
196
+ return {
197
+ publishable_key: env["MEDUSA_PUBLISHABLE_KEY"] || "",
198
+ adminApiKey: env["MEDUSA_ADMIN_KEY"] || "",
199
+ apiUrl: env["MEDUSA_API_URL"] || "",
200
+ defaultCurrency: env["MEDUSA_DEFAULT_CURRENCY"] || "",
201
+ allCurrencies: []
202
+ };
203
+ }
204
+ function getMagentoConfiguration(env) {
205
+ return {
206
+ adminApiKey: env["MAGENTO_ADMIN_API_KEY"] || "",
207
+ baseUrl: env["MAGENTO_BASE_URL"] || "",
208
+ storeCode: env["MAGENTO_STORE_CODE"] || "",
209
+ authStoreCode: env["MAGENTO_AUTH_STORE_CODE"] || "default",
210
+ mediaUrl: env["MAGENTO_MEDIA_URL"] || void 0,
211
+ mediaSource: env["MAGENTO_MEDIA_SOURCE"] === "EXTERNAL" ? "EXTERNAL" : "DEFAULT",
212
+ defaultCurrency: env["MAGENTO_DEFAULT_CURRENCY"] || "",
213
+ rootCategoryId: env["MAGENTO_ROOT_CATEGORY_ID"] || "2",
214
+ graphqlUrl: env["MAGENTO_GRAPHQL_URL"] || void 0,
215
+ reviewRatingCode: env["MAGENTO_REVIEW_RATING_CODE"] || void 0,
216
+ allCurrencies: []
217
+ };
218
+ }
219
+ function getFakeConfiguration() {
220
+ return {
221
+ jitter: {
222
+ mean: 0,
223
+ deviation: 0
224
+ },
225
+ seeds: {
226
+ product: 1,
227
+ search: 1,
228
+ category: 1
229
+ },
230
+ featureFlags: {
231
+ flags: [
232
+ {
233
+ key: "true-flag",
234
+ type: "boolean",
235
+ enabled: true
236
+ },
237
+ {
238
+ key: "string-flag",
239
+ type: "multivariate",
240
+ variants: ["red", "green", "blue"],
241
+ enabledVariant: "blue"
242
+ }
243
+ ]
244
+ }
245
+ };
246
+ }
247
+ function getCommercetoolsConfiguration(env) {
248
+ return {
249
+ apiUrl: env["CTP_API_URL"] || "",
250
+ authUrl: env["CTP_AUTH_URL"] || "",
251
+ clientId: env["CTP_CLIENT_ID"] || "",
252
+ clientSecret: env["CTP_CLIENT_SECRET"] || "",
253
+ projectKey: env["CTP_PROJECT_KEY"] || "",
254
+ scopes: parseCommaSeparatedEnvList(env["CTP_SCOPES"]),
255
+ paymentMethods: [
256
+ {
257
+ identifier: {
258
+ method: "stripe",
259
+ name: "Stripe",
260
+ paymentProcessor: "stripe"
261
+ },
262
+ isPunchOut: false,
263
+ description: "Stripe payment gateway"
264
+ }
265
+ ],
266
+ facetFieldsForSearch: parseCommaSeparatedEnvList(
267
+ env["CTP_FACET_FIELDS_FOR_SEARCH"]
268
+ ),
269
+ adminClientId: env["CTP_ADMIN_CLIENT_ID"] || void 0,
270
+ adminClientSecret: env["CTP_ADMIN_CLIENT_SECRET"] || void 0,
271
+ listPriceChannelKey: env["CTP_LIST_PRICE_CHANNEL_KEY"] || void 0,
272
+ customerPriceChannelKey: env["CTP_CUSTOMER_PRICE_CHANNEL_KEY"] || void 0
273
+ };
274
+ }
275
+ function parseCommaSeparatedEnvList(value) {
276
+ return (value || "").split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
277
+ }
278
+ export {
279
+ createReactionaryClientFromEnv,
280
+ getEnabledReactionaryMCPProviderSystems,
281
+ getNoEnabledProviderSystemsMessage,
282
+ parseCommaSeparatedEnvList
283
+ };
@@ -0,0 +1,40 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { config } from "dotenv";
5
+ const workspaceRootMarkers = ["nx.json", "pnpm-workspace.yaml"];
6
+ function loadProjectRootEnv(from = import.meta.url, env = process.env) {
7
+ const envPath = env["DOTENV_CONFIG_PATH"] ?? resolveProjectRootEnvPath(from);
8
+ if (!envPath) {
9
+ config();
10
+ return;
11
+ }
12
+ const result = config({ path: envPath });
13
+ if (!result.error) {
14
+ return;
15
+ }
16
+ const errorCode = result.error.code;
17
+ if (errorCode === "ENOENT" && env["DOTENV_CONFIG_PATH"] === void 0) {
18
+ return;
19
+ }
20
+ throw result.error;
21
+ }
22
+ function resolveProjectRootEnvPath(from = import.meta.url) {
23
+ let currentDirectory = dirname(fileURLToPath(from));
24
+ while (true) {
25
+ if (workspaceRootMarkers.some(
26
+ (marker) => existsSync(join(currentDirectory, marker))
27
+ )) {
28
+ return join(currentDirectory, ".env");
29
+ }
30
+ const parentDirectory = dirname(currentDirectory);
31
+ if (parentDirectory === currentDirectory) {
32
+ return void 0;
33
+ }
34
+ currentDirectory = parentDirectory;
35
+ }
36
+ }
37
+ export {
38
+ loadProjectRootEnv,
39
+ resolveProjectRootEnvPath
40
+ };