@fluentcommerce/fluent-mcp-extn 0.7.2 → 0.7.4
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 +13 -5
- package/dist/event-tools.js +19 -2
- package/dist/graphql-query-tools.js +98 -3
- package/dist/metrics-tools.js +47 -0
- package/dist/settings-tools.js +53 -17
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -50,7 +50,7 @@ The official `fluent mcp server` (bundled with Fluent CLI) covers core GraphQL a
|
|
|
50
50
|
| Auto-paginated GraphQL (`graphql_queryAll`) | | Yes |
|
|
51
51
|
| GraphQL planning helpers (`graphql_planOperation` / `graphql_buildQuery` / `graphql_generateFull`) | | Yes |
|
|
52
52
|
| Batch mutations (`graphql_batchMutate`) | | Yes |
|
|
53
|
-
| Batch ingestion (`
|
|
53
|
+
| Batch ingestion (`batch_*`) | | Yes |
|
|
54
54
|
| Batch payload planning (`batch_describePayload`) | | Yes |
|
|
55
55
|
| Prometheus metrics (`metrics_query`) | | Yes |
|
|
56
56
|
| Metrics health assessment (`metrics_healthCheck`) | | Yes |
|
|
@@ -295,6 +295,14 @@ npx @fluentcommerce/fluent-mcp-extn
|
|
|
295
295
|
|
|
296
296
|
This starts the MCP stdio server process. In a plain terminal it appears idle because it is waiting for MCP JSON-RPC input from your IDE/agent.
|
|
297
297
|
|
|
298
|
+
To run with **Streamable HTTP** transport instead of stdio:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
FLUENT_MCP_TRANSPORT=http npx @fluentcommerce/fluent-mcp-extn
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
This starts an HTTP server (default port 3100, override with `FLUENT_MCP_HTTP_PORT`). Set `FLUENT_MCP_AUTH_TOKEN` to enable bearer token authentication. Useful for remote MCP clients or multi-agent setups where stdio is not practical.
|
|
305
|
+
|
|
298
306
|
Or install as a project dependency:
|
|
299
307
|
|
|
300
308
|
```bash
|
|
@@ -419,7 +427,7 @@ Example:
|
|
|
419
427
|
}
|
|
420
428
|
```
|
|
421
429
|
|
|
422
|
-
Typical category values: `config`, `health`, `connection`, `event`, `metrics`, `workflow`, `plugin`, `graphql`, `batch`, `webhook`, `entity`, `setting`, `environment`, `test`, `cache`.
|
|
430
|
+
Typical category values: `config`, `health`, `time`, `connection`, `event`, `metrics`, `workflow`, `plugin`, `graphql`, `batch`, `webhook`, `entity`, `setting`, `environment`, `test`, `cache`.
|
|
423
431
|
|
|
424
432
|
This is a prompt-budget optimization, not an access-control boundary. It reduces what the MCP client discovers, but it does not block an explicit low-level `tools/call` if a caller already knows a hidden tool name.
|
|
425
433
|
|
|
@@ -538,7 +546,7 @@ Compare workflow versions across retailers:
|
|
|
538
546
|
```
|
|
539
547
|
1. workflow_get { entityType: "ORDER", entitySubtype: "HD", profile: "STAGING/UK" }
|
|
540
548
|
2. workflow_get { entityType: "ORDER", entitySubtype: "HD", profile: "STAGING/US" }
|
|
541
|
-
3. workflow_diff {
|
|
549
|
+
3. workflow_diff { base: <uk-workflow>, target: <us-workflow> }
|
|
542
550
|
```
|
|
543
551
|
|
|
544
552
|
### Cache
|
|
@@ -988,7 +996,7 @@ With `outputDir`, each workflow is saved as `{TYPE}-{SUBTYPE}.json` (e.g., `ORDE
|
|
|
988
996
|
|
|
989
997
|
| Tool | Description |
|
|
990
998
|
|---|---|
|
|
991
|
-
| `setting_get` | Fetch settings by name (`%` wildcards supported)
|
|
999
|
+
| `setting_get` | Fetch settings by name (`%` wildcards supported). Auto-paginates wildcard queries via edge cursors (up to 500 records). Warns when results are truncated. Optionally save to local file to keep large JSON out of LLM context. |
|
|
992
1000
|
| `setting_upsert` | Create or update a setting with upsert semantics — queries existing by name + context + contextId first |
|
|
993
1001
|
| `setting_bulkUpsert` | Batch create/update up to 50 settings with per-setting error handling |
|
|
994
1002
|
|
|
@@ -1198,7 +1206,7 @@ See the [Getting Started](https://www.npmjs.com/package/@fluentcommerce/ai-skill
|
|
|
1198
1206
|
```bash
|
|
1199
1207
|
git clone https://bitbucket.org/fluentcommerce/fluent-mcp-extn.git
|
|
1200
1208
|
cd fluent-mcp-extn
|
|
1201
|
-
npm install && npm run build && npm test #
|
|
1209
|
+
npm install && npm run build && npm test # 601 unit tests across 25 test files
|
|
1202
1210
|
npm run dev # hot-reload via tsx
|
|
1203
1211
|
```
|
|
1204
1212
|
|
package/dist/event-tools.js
CHANGED
|
@@ -473,7 +473,15 @@ export async function handleEventList(args, ctx) {
|
|
|
473
473
|
const analysis = analyzeEvents(events.results, events.hasMore ?? false);
|
|
474
474
|
return { ok: true, analyze: true, ...analysis };
|
|
475
475
|
}
|
|
476
|
-
|
|
476
|
+
const hasMore = events.hasMore ?? false;
|
|
477
|
+
return {
|
|
478
|
+
ok: true,
|
|
479
|
+
events,
|
|
480
|
+
...(hasMore ? {
|
|
481
|
+
_truncated: true,
|
|
482
|
+
_truncationWarning: `More events exist beyond this page. Use 'start' parameter to fetch the next page, or narrow filters (entityType, from/to, eventStatus) to reduce results.`,
|
|
483
|
+
} : {}),
|
|
484
|
+
};
|
|
477
485
|
}
|
|
478
486
|
/**
|
|
479
487
|
* Handle event_flowInspect tool call.
|
|
@@ -493,6 +501,7 @@ export async function handleEventFlowInspect(args, ctx) {
|
|
|
493
501
|
if (parsed.rootEntityId !== undefined) {
|
|
494
502
|
baseParams["context.rootEntityId"] = parsed.rootEntityId;
|
|
495
503
|
}
|
|
504
|
+
let _paginationTruncated = false;
|
|
496
505
|
const fetchPaged = async (extraParams) => {
|
|
497
506
|
const all = [];
|
|
498
507
|
let page = 1;
|
|
@@ -505,8 +514,10 @@ export async function handleEventFlowInspect(args, ctx) {
|
|
|
505
514
|
};
|
|
506
515
|
const result = await client.getEvents(reqParams);
|
|
507
516
|
const rows = Array.isArray(result?.results) ? result.results : [];
|
|
508
|
-
if (rows.length === 0)
|
|
517
|
+
if (rows.length === 0) {
|
|
518
|
+
hasMore = false;
|
|
509
519
|
break;
|
|
520
|
+
}
|
|
510
521
|
for (const row of rows) {
|
|
511
522
|
const rec = asRecord(row);
|
|
512
523
|
if (rec)
|
|
@@ -515,6 +526,8 @@ export async function handleEventFlowInspect(args, ctx) {
|
|
|
515
526
|
hasMore = result.hasMore ?? rows.length >= 500;
|
|
516
527
|
page += 1;
|
|
517
528
|
}
|
|
529
|
+
if (hasMore)
|
|
530
|
+
_paginationTruncated = true;
|
|
518
531
|
return all;
|
|
519
532
|
};
|
|
520
533
|
const orchestrationEvents = await fetchPaged({
|
|
@@ -920,6 +933,10 @@ export async function handleEventFlowInspect(args, ctx) {
|
|
|
920
933
|
window: { from: parsed.from ?? null, to: parsed.to ?? null },
|
|
921
934
|
paging: { maxPages: parsed.maxPages, pageSize: 500 },
|
|
922
935
|
compact: parsed.compact,
|
|
936
|
+
...(_paginationTruncated ? {
|
|
937
|
+
_truncated: true,
|
|
938
|
+
_truncationWarning: `Results truncated at ${parsed.maxPages} pages (${parsed.maxPages * 500} events max). Increase maxPages to fetch more, or narrow the time window.`,
|
|
939
|
+
} : {}),
|
|
923
940
|
};
|
|
924
941
|
// ========= COMPACT MODE =========
|
|
925
942
|
if (parsed.compact) {
|
|
@@ -147,6 +147,55 @@ function requireClient(ctx) {
|
|
|
147
147
|
}
|
|
148
148
|
return ctx.client;
|
|
149
149
|
}
|
|
150
|
+
function analyzePaginationLimits(pagination, limits) {
|
|
151
|
+
if (!pagination)
|
|
152
|
+
return { truncated: false };
|
|
153
|
+
const totalPages = typeof pagination.totalPages === "number" ? pagination.totalPages : null;
|
|
154
|
+
const totalRecords = typeof pagination.totalRecords === "number" ? pagination.totalRecords : null;
|
|
155
|
+
const pagesReached = typeof pagination.pagesFetched === "number"
|
|
156
|
+
? pagination.pagesFetched >= limits.maxPages
|
|
157
|
+
: totalPages !== null
|
|
158
|
+
? totalPages >= limits.maxPages
|
|
159
|
+
: false;
|
|
160
|
+
const recordsReached = typeof pagination.recordsFetched === "number"
|
|
161
|
+
? pagination.recordsFetched >= limits.maxRecords
|
|
162
|
+
: totalRecords !== null
|
|
163
|
+
? totalRecords >= limits.maxRecords
|
|
164
|
+
: false;
|
|
165
|
+
if (!pagesReached && !recordsReached) {
|
|
166
|
+
return { truncated: false };
|
|
167
|
+
}
|
|
168
|
+
const reasons = [];
|
|
169
|
+
if (pagesReached)
|
|
170
|
+
reasons.push(`page cap ${limits.maxPages}`);
|
|
171
|
+
if (recordsReached)
|
|
172
|
+
reasons.push(`record cap ${limits.maxRecords}`);
|
|
173
|
+
return {
|
|
174
|
+
truncated: true,
|
|
175
|
+
warning: `Auto-pagination may be truncated by the ${reasons.join(" and ")}. Narrow the query or raise the limit if you need a complete result.`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Count actual edges in the merged response data.
|
|
180
|
+
* Guards against SDK metadata bugs where totalRecords is computed
|
|
181
|
+
* as totalPages × pageSize instead of the true accumulated count.
|
|
182
|
+
*/
|
|
183
|
+
function countConnectionEdges(result) {
|
|
184
|
+
if (!result || typeof result !== "object")
|
|
185
|
+
return null;
|
|
186
|
+
const data = result.data;
|
|
187
|
+
if (!data || typeof data !== "object")
|
|
188
|
+
return null;
|
|
189
|
+
for (const val of Object.values(data)) {
|
|
190
|
+
if (!val || typeof val !== "object")
|
|
191
|
+
continue;
|
|
192
|
+
const connection = val;
|
|
193
|
+
if (Array.isArray(connection.edges)) {
|
|
194
|
+
return connection.edges.length;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
150
199
|
// ---------------------------------------------------------------------------
|
|
151
200
|
// Handlers
|
|
152
201
|
// ---------------------------------------------------------------------------
|
|
@@ -189,14 +238,60 @@ export async function handleGraphQLQueryAll(args, ctx) {
|
|
|
189
238
|
errorHandling: parsed.errorHandling,
|
|
190
239
|
};
|
|
191
240
|
const result = await client.graphqlPaginated(payload);
|
|
192
|
-
const
|
|
241
|
+
const rawPagination = result.extensions?.autoPagination ?? null;
|
|
242
|
+
// Reconcile SDK metadata: the SDK may report totalRecords as
|
|
243
|
+
// totalPages × pageSize instead of the true accumulated count.
|
|
244
|
+
// Count the actual edges in the merged response to detect and fix this.
|
|
245
|
+
const actualEdgeCount = countConnectionEdges(result);
|
|
246
|
+
let pagination = rawPagination;
|
|
247
|
+
if (rawPagination &&
|
|
248
|
+
typeof rawPagination === "object" &&
|
|
249
|
+
actualEdgeCount !== null) {
|
|
250
|
+
const sdkTotal = rawPagination.totalRecords;
|
|
251
|
+
if (typeof sdkTotal === "number" && sdkTotal !== actualEdgeCount) {
|
|
252
|
+
const corrected = {
|
|
253
|
+
...rawPagination,
|
|
254
|
+
totalRecords: actualEdgeCount,
|
|
255
|
+
_sdkReportedRecords: sdkTotal,
|
|
256
|
+
};
|
|
257
|
+
pagination = corrected;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const paginationInfo = pagination && typeof pagination === "object"
|
|
261
|
+
? pagination
|
|
262
|
+
: null;
|
|
263
|
+
const paginationAnalysis = analyzePaginationLimits(paginationInfo, {
|
|
264
|
+
maxPages: parsed.maxPages,
|
|
265
|
+
maxRecords: parsed.maxRecords,
|
|
266
|
+
});
|
|
193
267
|
if (parsed.summarize) {
|
|
194
268
|
const summary = summarizeConnection(result);
|
|
195
269
|
if (summary) {
|
|
196
|
-
return {
|
|
270
|
+
return {
|
|
271
|
+
ok: true,
|
|
272
|
+
summarized: true,
|
|
273
|
+
...summary,
|
|
274
|
+
pagination,
|
|
275
|
+
...(paginationAnalysis.truncated
|
|
276
|
+
? {
|
|
277
|
+
_truncated: true,
|
|
278
|
+
_truncationWarning: paginationAnalysis.warning,
|
|
279
|
+
}
|
|
280
|
+
: {}),
|
|
281
|
+
};
|
|
197
282
|
}
|
|
198
283
|
}
|
|
199
|
-
return {
|
|
284
|
+
return {
|
|
285
|
+
ok: true,
|
|
286
|
+
response: result,
|
|
287
|
+
pagination,
|
|
288
|
+
...(paginationAnalysis.truncated
|
|
289
|
+
? {
|
|
290
|
+
_truncated: true,
|
|
291
|
+
_truncationWarning: paginationAnalysis.warning,
|
|
292
|
+
}
|
|
293
|
+
: {}),
|
|
294
|
+
};
|
|
200
295
|
}
|
|
201
296
|
/**
|
|
202
297
|
* Handle graphql_batchMutate tool call.
|
package/dist/metrics-tools.js
CHANGED
|
@@ -10,6 +10,7 @@ import { GraphQLIntrospectionService, } from "@fluentcommerce/fc-connect-sdk";
|
|
|
10
10
|
import { ToolError } from "./errors.js";
|
|
11
11
|
import { classifyMutationOperations, listMutationCatalog, } from "./mutation-taxonomy.js";
|
|
12
12
|
import { parseWindowMs, recommendStep } from "./time-window.js";
|
|
13
|
+
const MAX_METRICS_RANGE_POINTS = 5_000;
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
// Input schemas
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
@@ -204,6 +205,13 @@ export const METRICS_TOOL_DEFINITIONS = [
|
|
|
204
205
|
// ---------------------------------------------------------------------------
|
|
205
206
|
// Math helpers
|
|
206
207
|
// ---------------------------------------------------------------------------
|
|
208
|
+
function parseIsoTimestamp(value, field) {
|
|
209
|
+
const parsed = new Date(value);
|
|
210
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
211
|
+
throw new ToolError("VALIDATION_ERROR", `metrics_query range ${field} must be a valid ISO-8601 timestamp.`);
|
|
212
|
+
}
|
|
213
|
+
return parsed;
|
|
214
|
+
}
|
|
207
215
|
function round1(value) {
|
|
208
216
|
return Math.round(value * 10) / 10;
|
|
209
217
|
}
|
|
@@ -1022,6 +1030,7 @@ async function aggregateEventsFromApi(client, params) {
|
|
|
1022
1030
|
return {
|
|
1023
1031
|
totalEvents: allEvents.length,
|
|
1024
1032
|
totalPages: page - 1,
|
|
1033
|
+
truncated: hasMore,
|
|
1025
1034
|
statusBreakdown: Object.fromEntries(statusCounts),
|
|
1026
1035
|
groups,
|
|
1027
1036
|
uniqueNames: new Set(allEvents.map((e) => e.name)),
|
|
@@ -1064,6 +1073,28 @@ export async function handleMetricsQuery(args, ctx) {
|
|
|
1064
1073
|
if (parsed.type === "range" && (!parsed.start || !parsed.end || !parsed.step)) {
|
|
1065
1074
|
throw new ToolError("VALIDATION_ERROR", "Range queries require start, end, and step.");
|
|
1066
1075
|
}
|
|
1076
|
+
if (parsed.type === "range") {
|
|
1077
|
+
const start = parseIsoTimestamp(parsed.start, "start");
|
|
1078
|
+
const end = parseIsoTimestamp(parsed.end, "end");
|
|
1079
|
+
const durationMs = end.getTime() - start.getTime();
|
|
1080
|
+
if (durationMs <= 0) {
|
|
1081
|
+
throw new ToolError("VALIDATION_ERROR", "metrics_query range end must be after start.");
|
|
1082
|
+
}
|
|
1083
|
+
const stepMs = parseWindowMs(parsed.step);
|
|
1084
|
+
const estimatedPoints = Math.ceil(durationMs / stepMs);
|
|
1085
|
+
if (estimatedPoints > MAX_METRICS_RANGE_POINTS) {
|
|
1086
|
+
const hint = recommendStep(durationMs, Math.min(500, MAX_METRICS_RANGE_POINTS));
|
|
1087
|
+
throw new ToolError("VALIDATION_ERROR", `Range query would request about ${estimatedPoints} points, which exceeds the safety limit of ${MAX_METRICS_RANGE_POINTS}. Increase step to at least ${hint.step}.`, {
|
|
1088
|
+
details: {
|
|
1089
|
+
estimatedPoints,
|
|
1090
|
+
maxPoints: MAX_METRICS_RANGE_POINTS,
|
|
1091
|
+
recommendedStep: hint.step,
|
|
1092
|
+
recommendedStepMs: hint.stepMs,
|
|
1093
|
+
recommendedEstimatedPoints: hint.estimatedPoints,
|
|
1094
|
+
},
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1067
1098
|
const client = requireClient(ctx);
|
|
1068
1099
|
const response = await client.queryPrometheus(parsed);
|
|
1069
1100
|
const vectors = extractPrometheusVectors(response);
|
|
@@ -1092,6 +1123,7 @@ export async function handleMetricsHealthCheck(args, ctx) {
|
|
|
1092
1123
|
let statusBreakdown = {};
|
|
1093
1124
|
let totalEvents = 0;
|
|
1094
1125
|
let topEvents = [];
|
|
1126
|
+
let eventApiTruncated = false;
|
|
1095
1127
|
try {
|
|
1096
1128
|
// Query 1: status breakdown
|
|
1097
1129
|
const statusResponse = await client.queryPrometheus({
|
|
@@ -1154,6 +1186,7 @@ export async function handleMetricsHealthCheck(args, ctx) {
|
|
|
1154
1186
|
});
|
|
1155
1187
|
totalEvents = agg.totalEvents;
|
|
1156
1188
|
statusBreakdown = agg.statusBreakdown;
|
|
1189
|
+
eventApiTruncated = agg.truncated;
|
|
1157
1190
|
if (parsed.includeTopEvents) {
|
|
1158
1191
|
topEvents = rankTopEvents(agg.groups, agg.totalEvents, parsed.topN);
|
|
1159
1192
|
}
|
|
@@ -1230,6 +1263,10 @@ export async function handleMetricsHealthCheck(args, ctx) {
|
|
|
1230
1263
|
ok: true,
|
|
1231
1264
|
healthy,
|
|
1232
1265
|
source,
|
|
1266
|
+
...(eventApiTruncated ? {
|
|
1267
|
+
_truncated: true,
|
|
1268
|
+
_truncationWarning: "Event API results truncated — counts may be approximate. Narrow the time window for accuracy.",
|
|
1269
|
+
} : {}),
|
|
1233
1270
|
summary: {
|
|
1234
1271
|
window,
|
|
1235
1272
|
totalEvents,
|
|
@@ -1268,6 +1305,7 @@ export async function handleMetricsSloReport(args, ctx) {
|
|
|
1268
1305
|
let pendingEvents = 0;
|
|
1269
1306
|
let runtimeP95Seconds = null;
|
|
1270
1307
|
let inflightP95Seconds = null;
|
|
1308
|
+
let eventApiTruncated = false;
|
|
1271
1309
|
try {
|
|
1272
1310
|
const [totalResponse, failedResponse, noMatchResponse, pendingResponse, runtimeP95Response, inflightP95Response,] = await Promise.all([
|
|
1273
1311
|
client.queryPrometheus({
|
|
@@ -1316,6 +1354,7 @@ export async function handleMetricsSloReport(args, ctx) {
|
|
|
1316
1354
|
pendingEvents = agg.statusBreakdown["PENDING"] ?? 0;
|
|
1317
1355
|
runtimeP95Seconds = null;
|
|
1318
1356
|
inflightP95Seconds = null;
|
|
1357
|
+
eventApiTruncated = agg.truncated;
|
|
1319
1358
|
}
|
|
1320
1359
|
let topFailingEvents;
|
|
1321
1360
|
if (parsed.includeTopFailingEvents) {
|
|
@@ -1401,6 +1440,10 @@ export async function handleMetricsSloReport(args, ctx) {
|
|
|
1401
1440
|
ok: true,
|
|
1402
1441
|
source,
|
|
1403
1442
|
healthy: findings.length === 0,
|
|
1443
|
+
...(eventApiTruncated ? {
|
|
1444
|
+
_truncated: true,
|
|
1445
|
+
_truncationWarning: "Event API results truncated — counts may be approximate. Narrow the time window or increase maxPages for accuracy.",
|
|
1446
|
+
} : {}),
|
|
1404
1447
|
summary: {
|
|
1405
1448
|
window,
|
|
1406
1449
|
timeWindow: { from, to },
|
|
@@ -1837,6 +1880,10 @@ export async function handleMetricsTopEvents(args, ctx) {
|
|
|
1837
1880
|
const failedCount = agg.statusBreakdown["FAILED"] ?? 0;
|
|
1838
1881
|
return {
|
|
1839
1882
|
ok: true,
|
|
1883
|
+
...(agg.truncated ? {
|
|
1884
|
+
_truncated: true,
|
|
1885
|
+
_truncationWarning: `Event API results truncated at ${agg.totalPages} pages (${agg.totalEvents} events fetched). Counts may be approximate. Narrow the time window or increase maxPages for accuracy.`,
|
|
1886
|
+
} : {}),
|
|
1840
1887
|
analytics: {
|
|
1841
1888
|
timeWindow: { from: parsed.from, to: toTime },
|
|
1842
1889
|
totalEvents: agg.totalEvents,
|
package/dist/settings-tools.js
CHANGED
|
@@ -63,9 +63,9 @@ export const SettingGetInputSchema = z.object({
|
|
|
63
63
|
.number()
|
|
64
64
|
.int()
|
|
65
65
|
.min(1)
|
|
66
|
-
.max(
|
|
67
|
-
.default(
|
|
68
|
-
.describe("Max results (default:
|
|
66
|
+
.max(500)
|
|
67
|
+
.default(25)
|
|
68
|
+
.describe("Max results per page (default: 25, max: 500). Auto-paginates wildcard queries using edge cursors."),
|
|
69
69
|
digest: z
|
|
70
70
|
.boolean()
|
|
71
71
|
.default(false)
|
|
@@ -320,7 +320,10 @@ function digestSetting(setting) {
|
|
|
320
320
|
digest,
|
|
321
321
|
};
|
|
322
322
|
}
|
|
323
|
-
function buildSettingGetResponse(parsed, settings, cacheMeta) {
|
|
323
|
+
function buildSettingGetResponse(parsed, settings, cacheMeta, truncated = false) {
|
|
324
|
+
const truncationWarning = truncated
|
|
325
|
+
? `Results truncated at ${settings.length} settings. More exist on the server. Use graphql_queryAll with a settings query to fetch all, or narrow your filter.`
|
|
326
|
+
: undefined;
|
|
324
327
|
if (settings.length === 0) {
|
|
325
328
|
return {
|
|
326
329
|
ok: true,
|
|
@@ -370,6 +373,7 @@ function buildSettingGetResponse(parsed, settings, cacheMeta) {
|
|
|
370
373
|
count: settings.length,
|
|
371
374
|
savedTo: outputDir,
|
|
372
375
|
files: savedFiles,
|
|
376
|
+
...(truncationWarning ? { warning: truncationWarning } : {}),
|
|
373
377
|
...(cacheMeta ? { _cache: cacheMeta } : {}),
|
|
374
378
|
settings: settings.map((s) => ({
|
|
375
379
|
id: s.id,
|
|
@@ -387,6 +391,7 @@ function buildSettingGetResponse(parsed, settings, cacheMeta) {
|
|
|
387
391
|
ok: true,
|
|
388
392
|
count: settings.length,
|
|
389
393
|
digested: true,
|
|
394
|
+
...(truncationWarning ? { warning: truncationWarning } : {}),
|
|
390
395
|
...(cacheMeta ? { _cache: cacheMeta } : {}),
|
|
391
396
|
settings: settings.map((s) => digestSetting(s)),
|
|
392
397
|
};
|
|
@@ -394,6 +399,7 @@ function buildSettingGetResponse(parsed, settings, cacheMeta) {
|
|
|
394
399
|
return {
|
|
395
400
|
ok: true,
|
|
396
401
|
count: settings.length,
|
|
402
|
+
...(truncationWarning ? { warning: truncationWarning } : {}),
|
|
397
403
|
...(cacheMeta ? { _cache: cacheMeta } : {}),
|
|
398
404
|
settings: settings.map((s) => ({
|
|
399
405
|
id: s.id,
|
|
@@ -565,7 +571,7 @@ export async function handleSettingGet(args, ctx) {
|
|
|
565
571
|
if (cacheable && ctx.cache) {
|
|
566
572
|
const cached = await ctx.cache.get(cacheKey);
|
|
567
573
|
if (cached.hit && Array.isArray(cached.data)) {
|
|
568
|
-
return buildSettingGetResponse(parsed, cached.data, { hit: true, ageMs: cached.ageMs ?? 0 });
|
|
574
|
+
return buildSettingGetResponse(parsed, cached.data, { hit: true, ageMs: cached.ageMs ?? 0 }, false);
|
|
569
575
|
}
|
|
570
576
|
}
|
|
571
577
|
// Build variables — only include context/contextId if provided
|
|
@@ -585,10 +591,11 @@ export async function handleSettingGet(args, ctx) {
|
|
|
585
591
|
else if (parsed.context === "ACCOUNT") {
|
|
586
592
|
variables.contextId = [0];
|
|
587
593
|
}
|
|
588
|
-
// Build query —
|
|
589
|
-
const query = `query GetSettings($name: [String!], $context: [String!], $contextId: [Int!], $first: Int) {
|
|
590
|
-
settings(name: $name, context: $context, contextId: $contextId, first: $first) {
|
|
594
|
+
// Build query — edge cursors for Fluent-style pagination
|
|
595
|
+
const query = `query GetSettings($name: [String!], $context: [String!], $contextId: [Int!], $first: Int, $after: String) {
|
|
596
|
+
settings(name: $name, context: $context, contextId: $contextId, first: $first, after: $after) {
|
|
591
597
|
edges {
|
|
598
|
+
cursor
|
|
592
599
|
node {
|
|
593
600
|
id
|
|
594
601
|
name
|
|
@@ -599,20 +606,49 @@ export async function handleSettingGet(args, ctx) {
|
|
|
599
606
|
valueType
|
|
600
607
|
}
|
|
601
608
|
}
|
|
609
|
+
pageInfo {
|
|
610
|
+
hasNextPage
|
|
611
|
+
}
|
|
602
612
|
}
|
|
603
613
|
}`;
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
614
|
+
// Auto-paginate wildcard queries using edge cursors; single-page for exact names
|
|
615
|
+
const MAX_TOTAL = 500;
|
|
616
|
+
const isWildcard = isWildcardSettingName(parsed.name);
|
|
617
|
+
const allSettings = [];
|
|
618
|
+
let cursor = null;
|
|
619
|
+
let hasNextPage = true;
|
|
620
|
+
while (hasNextPage && allSettings.length < MAX_TOTAL) {
|
|
621
|
+
const pageVars = { ...variables };
|
|
622
|
+
if (cursor)
|
|
623
|
+
pageVars.after = cursor;
|
|
624
|
+
const response = await client.graphql({
|
|
625
|
+
query,
|
|
626
|
+
variables: pageVars,
|
|
627
|
+
});
|
|
628
|
+
const data = response?.data;
|
|
629
|
+
const connection = data?.settings;
|
|
630
|
+
const edges = (connection?.edges ?? []);
|
|
631
|
+
const pageInfo = connection?.pageInfo;
|
|
632
|
+
for (const edge of edges) {
|
|
633
|
+
if (allSettings.length >= MAX_TOTAL)
|
|
634
|
+
break;
|
|
635
|
+
allSettings.push(edge.node);
|
|
636
|
+
}
|
|
637
|
+
hasNextPage = Boolean(pageInfo?.hasNextPage) && edges.length > 0;
|
|
638
|
+
// Fluent pagination: cursor lives on the edge, not in pageInfo
|
|
639
|
+
if (hasNextPage && edges.length > 0) {
|
|
640
|
+
cursor = edges[edges.length - 1].cursor ?? null;
|
|
641
|
+
}
|
|
642
|
+
// Non-wildcard: one page is enough
|
|
643
|
+
if (!isWildcard)
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
const settings = allSettings;
|
|
647
|
+
const truncated = hasNextPage && allSettings.length >= MAX_TOTAL;
|
|
612
648
|
if (cacheable && ctx.cache) {
|
|
613
649
|
await ctx.cache.set(cacheKey, "setting", settings);
|
|
614
650
|
}
|
|
615
|
-
return buildSettingGetResponse(parsed, settings, cacheable && ctx.cache ? { hit: false, stored: true } : undefined);
|
|
651
|
+
return buildSettingGetResponse(parsed, settings, cacheable && ctx.cache ? { hit: false, stored: true } : undefined, truncated);
|
|
616
652
|
}
|
|
617
653
|
/**
|
|
618
654
|
* Handle setting_upsert tool call.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluentcommerce/fluent-mcp-extn",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "[Experimental] MCP (Model Context Protocol) extension server for Fluent Commerce. Exposes event dispatch, transition actions, GraphQL execution, Prometheus metrics, batch ingestion, and webhook validation as MCP tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"node": ">=20.0.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@fluentcommerce/fc-connect-sdk": "^0.1.
|
|
55
|
+
"@fluentcommerce/fc-connect-sdk": "^0.1.55",
|
|
56
56
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
57
57
|
"graphql": "^16.13.1",
|
|
58
58
|
"zod": "^4.3.6"
|