---
name: fluent-mcp-tools
description: Fluent Commerce MCP tools reference. Use when working with the Fluent MCP extension server for event dispatch, event flow forensics, GraphQL operations, batch ingestion, or webhook validation. Triggers on "mcp tools", "send fluent event", "event flow inspect", "trace event payloads", "fluent graphql", "batch ingestion".
user-invocable: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep
argument-hint: <operation> [entity-type] [entity-ref]
cursor-tier: manual
---

# Fluent MCP Tools

Quick reference for Fluent Commerce MCP extension server tools.

## When to Use

- Sending events to Fluent Commerce (order lifecycle, fulfilment transitions)
- Retrieving UI/user-action transitions from workflow trigger contexts
- Running GraphQL queries or mutations
- Bulk data operations via batch ingestion
- Validating inbound webhook payloads
- Creating, updating, and retrieving entities (Order, Fulfilment, Location, etc.)
- Deploying, diffing, and simulating workflows
- Managing settings (upsert, bulk upsert)
- Discovering and validating environment configuration
- Asserting entity state in E2E test sequences
- Monitoring event processing health via Prometheus metrics
- Inspecting or clearing local MCP caches after external workflow/settings changes

This skill is the canonical MCP tool contract reference. Other skills should link here for payload syntax and limits instead of redefining tool schemas.

**Tool count: 45** — Diagnostics (3), Events (5), GraphQL (8), Batch (5), Orchestration (2), Metrics (5), Entity (3), Workflow (5), Settings (3), Environment (2), Test (1), Webhook (1), Cache (2).

## Diagnostics

### `config.validate`

No parameters. Validates auth/base URL/retailer configuration and reports readiness for API tools. Run this first.

### `health.ping`

No parameters. Quick diagnostics: confirms SDK adapter connection and config readiness when calls fail.

### `connection.test`

No parameters. Comprehensive connectivity test — authenticates, executes a `me` query, and returns:
- **User:** id, username, email, type, status, roles, permissions
- **Retailer:** id, ref, tradingName, primaryEmail
- **Location:** id, ref, name, type

More thorough than `health.ping` — verifies the GraphQL endpoint end-to-end. Use when first connecting, debugging auth issues, or verifying retailer/location context.

## Cache Management

### `cache.status`

Optional parameter:
- `verbose` (boolean, default `false`) — include per-entry details

No API call. Shows local cache statistics: enabled/disabled state, cache directory, entry count, total bytes, per-category breakdown, and session hit/miss rate.

### `cache.clear`

Optional parameter:
- `pattern` (string) — glob-style cache key filter. Omit to clear everything.

No API call. Clears cached entries and returns `cleared` plus remaining entry count. Common patterns:
- `workflow:*` — clear workflow list/get cache
- `setting:get:fc.mystique.*` — clear manifest/settings cache
- `introspect:*` — clear GraphQL schema cache

## Event API Semantics

For event data model, types, categories, statuses, execution model, event contracts, and query patterns — see `/fluent-event-api`. This skill focuses on MCP tool syntax and payload contracts only.

## Event Operations

### Recommended Flow

1. `config.validate` - Verify connection
2. `event.build` - Preview payload
3. `event.send` (dryRun: true) - Validate without sending
4. `event.send` (dryRun: false) - Actually send
5. `event.list` / `event.get` - Verify delivery
6. `event.flowInspect` - One-call runtime forensics for root entity

### Common Events

| Event | Entity Type | Description |
|-------|-------------|-------------|
| `ConfirmValidation` | ORDER | Confirm order passed validation |
| `ConfirmAllocation` | FULFILMENT | Confirm stock allocation |
| `CreateInvoice` | FULFILMENT | Invoice created |
| `ConfirmPick` | FULFILMENT | Items picked (attrs: pickedAt, pickedItems) |
| `ConfirmShipment` | FULFILMENT | Shipment dispatched (attrs: trackingNumber, carrierRef) |
| `ConfirmDelivery` | FULFILMENT | Delivery completed (attrs: deliveredAt) |
| `CancelOrder` | ORDER | Cancel an order |

### Runtime Forensics (`event.flowInspect`)

Use `event.flowInspect` when you need the full runtime evidence bundle in one call for any entity type.

> **MANDATORY: Always use compact mode first.** Call with default parameters (no `compact: false`). The ~2-3k token compact summary is sufficient for 90% of use cases. Only add targeted detail flags (`includeRuleDetails`, `includeCustomLogs`, `includeSnapshots`, `includeCrossEntity`) one at a time when compact findings reveal specific issues. Setting `compact: false` returns ~24-30k tokens and WILL fill context — use only as a last resort after identifying exactly what raw data you need.

**Compact mode (default `compact: true`):** Returns a pre-analyzed summary (~2-3k tokens) with an `analysis` section containing anomaly findings, status flow, failed webhook endpoints, and slowest rulesets. Audit arrays are stripped to essentials.

Collects ORCHESTRATION events (plus ORCHESTRATION_AUDIT when `includeAudit=true`, and SCHEDULED when `includeScheduled=true`) for a root entity, then extracts:

**Always included (both modes):**
- **Orchestration timeline** — status/entity type counts, top event names
- **Auto-recommendations** — contextual findings based on status counts, webhook failures, exceptions
- **Totals** — raw event counts for all categories

**Compact mode additions (`compact: true`):**
- `analysis.statusFlow` — ordered unique statuses seen (e.g., `["CREATED", "BOOKED", "SHIPPED"]`)
- `analysis.findings[]` — anomaly detection: NO_MATCH (CRITICAL), FAILED (HIGH), webhook errors (HIGH), exceptions (HIGH), PENDING (MEDIUM), slow rulesets (MEDIUM)
- `analysis.failedWebhookEndpoints` — URLs that returned >= 400
- `analysis.slowestRulesets` — top 3 slowest
- `analysis.timespan` — first/last event timestamps with durationMs
- `audit.webhookActions` — **only failures** (responseCode >= 400)
- `audit.mutationActions` — **top 5 by queryName** (name + count only, no payloads)
- `audit.sendEventActions` — count + scheduledCount only
- `diagnostics.inspectedEvents` — summary (id, name, status, closeMatchCount)

**Full mode additions (`compact: false`):**
- **Mutation payloads** — full GraphQL mutations with `input`, `response`, `durationMs`
- **Webhook diagnostics** — all webhooks with `Request Endpoint`, `Response code`, `Response Body`
- **Scheduled dispatches** — future-dated SendEvent evidence with full event payload
- **Ruleset durations** — all rulesets ranked by processing time
- **Cross-entity events** — full events array per child entity type

**Default-on flags:**
- `compact: true` — pre-analyzed summary response
- `includeAudit: true` — parse ORCHESTRATION_AUDIT action evidence
- `includeExceptions: true` — rule exceptions with class, message, ruleset context
- `includeNoMatchDetails: true` — enhanced NO_MATCH diagnostics from ruleSet audit events
- `includeEventDetails: true` — compact orchestration events in response (full mode only)
- `includeScheduled: true` — separate SCHEDULED event fetch

**Opt-in flags (default false):**
- `includeRuleDetails: true` — per-rule execution trace (full mode only)
- `includeCustomLogs: true` — custom plugin log messages (full mode only)
- `includeSnapshots: true` — entity state snapshots (full mode only)
- `includeCrossEntity: true` — fetches child entity events

**Drilldown control:**
- `inspectStatuses: ["NO_MATCH", "PENDING", "FAILED"]` — auto-fetches `event.get` for these statuses
- `maxDrilldowns: 50` — cap on individual event.get calls
- `actionSampleLimit: 100` — max rows per extraction section

**Response sections (compact mode):**

| Section | Key Fields | Condition |
|---------|------------|-----------|
| `totals` | orchestrationCount, auditCount, all category counts | Always |
| `analysis` | statusFlow, entityTypes, timespan, failedWebhookEndpoints, slowestRulesets, findings[] | Always (compact only) |
| `orchestration` | statusCounts, entityTypeCounts, topNames (top 10) | Always |
| `audit.webhookActions` | Only failures (responseCode >= 400) | When includeAudit and failures exist |
| `audit.mutationActions` | Top 5 queryName + count | When includeAudit and mutations exist |
| `audit.sendEventActions` | { count, scheduledCount } | When includeAudit |
| `audit.exceptions` | id, ruleset, rule, exceptionClass, message | When includeExceptions |
| `diagnostics.inspectedEvents` | eventId, name, eventStatus, closeMatchCount | When inspectStatuses has entries |

**Response sections (full mode, compact: false):**

| Section | Key Fields | Condition |
|---------|------------|-----------|
| `totals` | orchestrationCount, auditCount, all category counts | Always |
| `orchestration` | statusCounts, entityTypeCounts, topNames (top 30), events? | Always |
| `scheduled` | SCHEDULED events with compact view | When includeScheduled and events exist |
| `audit.mutationActions` | queryName, queryType, input, response, durationMs | When includeAudit |
| `audit.webhookActions` | endpoint, responseCode, responseBody | When includeAudit |
| `audit.sendEventActions` | eventName, futureDated, scheduledOn, event payload | When includeAudit |
| `audit.rulesetDurations` | name, durationMs (sorted slowest first) | When includeAudit |
| `audit.exceptions` | id, ruleset, rule, exceptionClass, message | When includeExceptions |
| `audit.snapshots` | status, items, attributes, customer, toAddress, deliveryType | When includeSnapshots |
| `audit.customLogs` | source, logs[] (from LogCollection) | When includeCustomLogs |
| `audit.ruleEvents` | name, ruleSet, props, durationMs | When includeRuleDetails |
| `diagnostics.inspectedEvents` | closeMatches, entityStatus, message, full event | When inspectStatuses has entries |
| `diagnostics.noMatchAuditDetails` | rulesetName, entityStatus, message, closeMatches | When includeNoMatchDetails |
| `crossEntity` | entityType, orchestrationCount, statusCounts, events | When includeCrossEntity |

Example (compact — the default, recommended first call):

```json
{
  "rootEntityRef": "E2E_MULTI_202602221343",
  "rootEntityType": "ORDER"
}
```

Example (entity-agnostic by ref only):

```json
{
  "rootEntityRef": "STORE-001",
  "includeScheduled": false
}
```

Example (deep analysis with all sections):

```json
{
  "rootEntityRef": "ORD-001",
  "rootEntityType": "ORDER",
  "includeRuleDetails": true,
  "includeCustomLogs": true,
  "includeSnapshots": true,
  "includeCrossEntity": true
}
```

Example (lightweight — minimal output):

```json
{
  "rootEntityRef": "ORD-001",
  "rootEntityType": "ORDER",
  "includeEventDetails": false,
  "includeScheduled": false,
  "includeExceptions": false,
  "includeNoMatchDetails": false,
  "maxDrilldowns": 0
}
```

## GraphQL Operations

### Single Query
Use `graphql.query` for one-off queries with manual pagination. Mutations are not retried; queries are retried on transient failures.

```json
{
  "query": "{ orders(first: 10, status: \"BOOKED\") { edges { cursor node { id ref status totalPrice } } pageInfo { hasNextPage } } }"
}
```

With variables:
```json
{
  "query": "query($id: ID!) { orderById(id: $id) { id ref status fulfilments { edges { node { id ref status } } } } }",
  "variables": { "id": "12345" }
}
```

### Auto-Paginated Query
Use `graphql.queryAll` when you need ALL records. Automatically follows Relay cursors across pages. Defaults: 100 max pages, 10000 max records, 5 min timeout.

```json
{
  "query": "{ orders(first: 100, status: \"BOOKED\") { edges { cursor node { id ref } } pageInfo { hasNextPage } } }",
  "maxRecords": 5000,
  "summarize": true
}
```

With `summarize: true`, large result sets return field distributions and samples instead of full data — keeps response within LLM context budget.

### Batch Mutations
Use `graphql.batchMutate` for bulk updates (up to 50 mutations per request). Generates aliased mutation queries automatically.

```json
{
  "mutation": "updateOrder",
  "inputs": [
    { "id": "1", "status": "SHIPPED" },
    { "id": "2", "status": "SHIPPED" }
  ],
  "returnFields": ["id", "ref", "status"]
}
```

Use `dryRun: true` to preview the generated mutation query and variables without executing:
```json
{
  "mutation": "updateOrder",
  "inputs": [{ "id": "1", "status": "SHIPPED" }],
  "dryRun": true
}
```

### Schema Discovery
Use `graphql.introspect` to discover mutations, input types, and field requirements:

```json
{ "listMutations": true }
```
```json
{ "mutation": "updateOrder" }
```
```json
{ "type": "UpdateOrderInput" }
```

### Schema Tools Response Contract

`graphql.buildQuery`, `graphql.validate`, and `graphql.generateFull` return a consistent envelope:

| Field | Type | Description |
|-------|------|-------------|
| `ok` | boolean | Always `true` — indicates the tool executed successfully |
| `valid` | boolean | `true` if the document is schema-valid, `false` if errors were found |
| `document` | string | The generated/validated GraphQL document (`buildQuery`/`generateFull`) |
| `errors` / `validationErrors` | string[] | Validation error messages (when `valid: false`) |
| `errorCount` | number | Number of errors (when `valid: false`) |
| `parseError` | string | Parse-level error (malformed GraphQL syntax) |

**IMPORTANT:** Check the `valid` field to determine query validity, NOT `ok`. The `ok` field indicates tool execution success (the tool ran without crashing), while `valid` indicates whether the GraphQL document passed schema validation. A response with `{ ok: true, valid: false }` means the tool worked correctly but found schema errors in the document.

### Schema Tool Annotations

All 45 MCP extension tools now advertise MCP `ToolAnnotations` (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`). MCP clients that respect annotations can automatically distinguish read-only operations (queries, introspection, cache status) from write operations (event.send, entity.create, workflow.upload).

## Batch Ingestion

### Flow
1. `batch.create` - Create job (name, entityType, action)
2. `batch.send` - Send records (jobId, payload)
3. `batch.status` - Poll until terminal state
4. `batch.batchStatus` - Check a specific batch inside a job (jobId + batchId). Use for troubleshooting partial failures within multi-batch jobs.
5. `batch.results` - Get per-record outcomes

## Event Analytics

### Metrics Tools

#### Health Check (Single Call)

Use `metrics.healthCheck` for a one-call health assessment. Runs multiple Prometheus queries locally, applies anomaly thresholds, and returns findings + recommendations. Falls back to Event API if Prometheus is unavailable.

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `window` | string | no | `"1h"` | Prometheus time window (e.g., `1h`, `6h`, `24h`, `7d`) |
| `includeTopEvents` | boolean | no | `true` | Include top-N event breakdown |
| `topN` | integer (1-100) | no | `10` | Number of top events to return |
| `thresholds` | object | no | see below | Custom anomaly detection thresholds |
| `thresholds.failureRate` | number | no | `5` | Failure rate threshold % (>5% = HIGH) |
| `thresholds.pendingRate` | number | no | `10` | Pending queue ratio % (>10% = MEDIUM) |
| `thresholds.dominanceRate` | number | no | `50` | Single-event dominance % (>50% = MEDIUM, possible runaway loop) |

```json
{ "window": "1h", "includeTopEvents": true, "thresholds": { "failureRate": 3 } }
```

For custom PromQL queries, use `metrics.query`. For pre-aggregated Event API rankings, use `metrics.topEvents`.

#### SLO Report (Managed Services)

Use `metrics.sloReport` when you need an operational KPI snapshot in one call:
- failure/no-match/pending rates
- p95 runtime + p95 inflight latency
- threshold findings + recommendations
- optional top failing events

**Parameters:**

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `window` | string | no | `"1h"` | Window for KPI aggregation (e.g., `30m`, `1h`, `24h`, `7d`) |
| `includeTopFailingEvents` | boolean | no | `true` | Include top failing events from Event API |
| `topN` | integer (1-100) | no | `10` | Top N failing events to include |
| `maxPages` | integer (1-50) | no | `10` | Event API pagination cap for fallback/top failing events |
| `thresholds` | object | no | see below | Custom SLO thresholds |
| `thresholds.failureRate` | number | no | `5` | Failure rate threshold % |
| `thresholds.noMatchRate` | number | no | `0` | NO_MATCH rate threshold % |
| `thresholds.pendingRate` | number | no | `10` | Pending rate threshold % |
| `thresholds.runtimeP95Seconds` | number | no | `5` | Runtime p95 threshold in seconds |
| `thresholds.inflightP95Seconds` | number | no | `60` | Inflight latency p95 threshold in seconds |

```json
{
  "window": "1h",
  "includeTopFailingEvents": true,
  "topN": 10,
  "thresholds": { "failureRate": 2, "runtimeP95Seconds": 3 }
}
```

`metrics.sloReport` is preferred for release checks and incident triage handoffs where you need explicit SLI fields, not just raw PromQL output.

#### Label Discovery (Metric -> Labels)

Use `metrics.labelCatalog` before writing grouped PromQL to discover valid labels for a metric:
- samples live series via `last_over_time(metric[window])`
- extracts label keys + cardinality + sample values
- merges known Fluent label hints for common metrics

```json
{
  "metric": "core_event_received_total",
  "window": "1h",
  "maxValuesPerLabel": 10
}
```

Recommended workflow:
1. `metrics.labelCatalog` (discover labels)
2. `metrics.query` (write correct `sum by (...)` / `histogram_quantile(...)`)
3. `metrics.sloReport` (operational KPI snapshot)

#### Prometheus Metrics
Use `metrics.query` to execute PromQL queries via the Fluent GraphQL `metricInstant` / `metricRange` proxy:
- **Instant**: Evaluate at a single point in time — `{ query: "sum(rubix_event_runtime_seconds_count)", type: "instant" }`
- **Range**: Evaluate over a time window — `{ query: "...", type: "range", start: "ISO", end: "ISO", step: "1h" }`

The MCP tool routes PromQL through the GraphQL API (not raw Prometheus REST endpoints, which are not exposed by the platform). The `time`, `start`, and `end` parameters accept ISO-8601 timestamps (mapped to GraphQL `DateTime` type).

Access is permission-gated. If metrics calls fail for auth/authorization, verify the user/token has `METRICS_VIEW`.

#### Counter Delta Pattern (`last_over_time`)

For accurate daily/hourly counter deltas over wider windows, use:

```promql
(last_over_time(metric[window]) - metric offset <period>)
or
last_over_time(metric[window])
```

Use this when offset samples can be missing (scrape gaps, resets). For short-window trend estimates, `increase()`/`rate()` is usually sufficient.

#### Label Hygiene (Critical)

Do not assume labels are shared across all metrics. Common mappings:

| Metric | Key Labels |
|---|---|
| `core_event_received_total` | `account_id`, `retailer_id`, `event_name`, `entity_type`, `source` |
| `rubix_event_runtime_seconds_count` | `account_id`, `retailer_id`, `event_name`, `entity_type`, `source`, `status` |
| `rubix_event_inflight_latency_seconds_count` | `account_id`, `retailer_id`, `event_name`, `entity_type`, `source` |
| `rubix_event_runtime_seconds_bucket` | `account_id`, `retailer_id`, `event_name`, `entity_type`, `source`, `status`, `le` |

Example pitfall: `status` is **not** a label on `core_event_received_total`; including it in `sum by (...)` will produce misleading/null label outputs.

#### Event Volume Analytics
Use `metrics.topEvents` for aggregate event analytics within a time window:
- Paginates through `event.list` results, groups by (name + entityType + status)
- Returns top-N rankings, failure rates, and status breakdown
- Supports `eventStatus` filter for server-side pre-filtering (e.g., `"FAILED"`, `"NO_MATCH"`)
- Example: `{ from: "2026-02-22T07:00:00Z", topN: 20 }`
- Example (failures only): `{ from: "2026-02-22T07:00:00Z", eventStatus: "FAILED", topN: 10 }`

For event model/filter semantics → `/fluent-event-api`.
For diagnostic decision trees → `/fluent-trace`.

### Event Analysis via Filters
Use `event.list` with targeted filters (`eventStatus`, `category`, `eventType`, `from`/`to`) to find patterns. Combine with `graphql.query` to correlate entity state with event history.

### Status/Category Alias Normalization

When translating user input into `event.list` filters, normalize common aliases:

| User/doc term | Canonical filter value |
|---|---|
| `ERROR` | `FAILED` |
| `PROCESSING` | `PENDING` (or `SCHEDULED` depending on lifecycle stage) |
| `ORDER_WORKFLOW` | `ORDER WORKFLOW` |

Prefer canonical values in requests; keep aliases only for interpretation.

## JOB/BATCH Entities vs Batch Ingestion

The MCP `batch.*` tools and JOB/BATCH workflow entities are **different concepts** that share terminology:

| Concept | What It Is | MCP Tools | Event API |
|---------|-----------|-----------|-----------|
| **Batch Ingestion** | Data loading mechanism (bulk create/update entities) | `batch.create`, `batch.send`, `batch.status`, `batch.results` | Not event-driven; uses dedicated ingestion API |
| **JOB/BATCH Entities** | Workflow-driven processing entities (orchestrated execution) | Query via `graphql.query`, trace via `event.list` | `entityType=JOB` or `entityType=BATCH`; linked by `rootEntityType=JOB` |

### Diagnosing JOB/BATCH Workflow Issues

JOB and BATCH are orchestratable entities with their own workflows. To trace them:

```
event.list({ "context.entityType": "JOB", "context.entityRef": "<JOB_REF>", "count": 100 })
event.list({ "context.entityType": "BATCH", "context.rootEntityRef": "<JOB_REF>", "count": 100 })
```

BATCH entities link to their parent JOB via `rootEntityType=JOB` / `rootEntityRef`. The `BATCH_COMPLETE` event signals batch processing completion.

For event model semantics and query patterns → `/fluent-event-api`.
For diagnostic decision trees → `/fluent-trace`.

## Orchestration

### Rule Registry

Use `plugin.list` to fetch all registered orchestration rules (standard + custom) with metadata from `GET /orchestration/rest/v1/plugin`. Returns rule name, description, accepted entity types, produced events, and parameters.

- List all rules: `{}` (no params)
- Filter by name: `{ "name": "SendEvent" }` (case-insensitive substring match)

### Workflow Transitions

Use `workflow.transitions` to discover available user actions at any entity state
(backed by `POST /api/v4.1/transition`).

**flexType auto-derive:** When `flexType` is omitted but `type` and `subtype` are present, the tool auto-derives `flexType` as `{type}::{subtype}` (e.g., `type: "CREDIT_MEMO"` + `subtype: "APPEASEMENT"` → `flexType: "CREDIT_MEMO::APPEASEMENT"`). This avoids the silent empty-response problem. You can still pass `flexType` explicitly to override.

**When to use:**
- Discover what events can be fired at a given status without reading workflow JSON
- Build dynamic E2E test sequences that adapt to workflow changes
- Validate that expected user actions exist after deployment
- Get required attributes for each action (avoids missing-attribute errors on `event.send`)

**Example — ORDER at a specific status (all three mandatory params included):**

```json
{
  "triggers": [
    {
      "type": "ORDER",
      "subtype": "HD",
      "status": "CREATED",
      "retailerId": "5",
      "module": "adminconsole",
      "flexType": "ORDER::HD",
      "flexVersion": 12
    }
  ]
}
```

**Example — Module-scoped trigger:**

```json
{
  "triggers": [
    {
      "type": "MANIFEST",
      "subtype": "DEFAULT",
      "status": "PENDING",
      "module": "servicepoint",
      "flexType": "CARRIER::DEFAULT",
      "retailerId": "2"
    }
  ]
}
```

**Integration with event.send:** The `eventName` from each `userAction` maps directly to
`event.send`'s `name` parameter. The `attributes[]` tell you what to include in `event.send`'s
`attributes` parameter.

Notes:
- `retailerId` is required per trigger (falls back to `FLUENT_RETAILER_ID`)
- **Transition API gotcha** — `module` is required, and `module: "all"` returns NOTHING. `flexType` is required by the API, but the MCP tool auto-derives it from `type` + `subtype` when omitted. `flexVersion` is strongly recommended and required for some entity types (for example `CREDIT_MEMO`). `status` is an optional filter.
- Response includes `userActions` with `eventName`, `context`, and `attributes`
- All rules treated equally regardless of plugin origin — NO "OOTB vs custom" distinction

### Strict User Action Contract (Anti-Hallucination)

Use these guardrails whenever interpreting `workflow.transitions`:
1. Treat `userActions[].eventName` as the only valid event to send (never infer from labels).
2. Treat `userActions[].attributes` as the runtime contract for payload shape (do not invent keys).
3. If an expected action is missing, verify `module` first (`"all"` returns NOTHING), then confirm `flexType` is present or auto-derived from `type` + `subtype`, and finally confirm `flexVersion` for entity types that require it.
4. If actions appear under one module but not another, fix `context[].modules` (module name comes from the app's `orchestrationAlias`, NOT the manifest setting suffix — e.g., Store App manifest is `fc.mystique.manifest.store` but module is `"servicepoint"`).
5. Validate trigger scope alignment: `type`, `subtype`, and `flexType` must match the workflow branch.
6. Do not invent undocumented user-action schema fields; keep to `eventName`, `context`, `attributes`.

For canonical schema authoring and diagnostics, cross-check `/fluent-workflow-builder` and `/fluent-transition-api`.

### Workflow Analysis Flow

For end-to-end workflow understanding:
1. Download workflow JSON (via Fluent CLI, official MCP `workflow.download`, or extension `workflow.get`)
2. `plugin.list` — look up rules referenced in rulesets to understand what each does
3. `workflow.transitions` — query user actions for each entity state to see what UI buttons appear

## Agentic Operations

Tools for agent-driven workflows: type-safe entity CRUD, workflow deployment, settings management, environment introspection, and state assertions.

### Entity Lifecycle

#### `entity.create`

Type-safe entity creation with built-in validation and gotcha knowledge.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityType` | string | yes | — | Entity type. Supported: ORDER, FULFILMENT, FULFILMENT_CHOICE, LOCATION, PRODUCT, INVENTORY_POSITION, INVENTORY_QUANTITY, NETWORK, VIRTUAL_CATALOGUE, WAVE, ARTICLE, CUSTOMER |
| `data` | object | yes | — | Entity creation input fields (matches GraphQL create input type) |
| `returnFields` | string[] | no | type defaults | Fields to return after creation |
| `dryRun` | boolean | no | false | If true, build and validate mutation without executing |

**Key behaviors:**
- Validates required fields BEFORE sending (e.g., Location needs `openingSchedule`)
- Encodes compound key rules (ProductKey needs `ref` + `catalogue.ref`)
- Auto-resolves `retailerId` from config for retailer-scoped entities
- Returns the executed mutation string for audit trail

**Gotchas encoded:**
- No create inputs have a `status` field (auto-set to CREATED)
- Customer has NO `ref` field — `username` is the identifier
- Network uses `name` not `ref` in create; `retailers` is a plural array
- Location requires `openingSchedule` (even for 24/7, use `allHours: true`)
- Product `gtin` has 20-char max
- Settings `context` is plain String, `contextId` is separate Int

```json
{
  "entityType": "ORDER",
  "data": {
    "ref": "TEST-ORDER-001",
    "type": "HD",
    "retailer": { "id": 5 }
  },
  "dryRun": true
}
```

#### `entity.update`

Status-aware entity updates with optional transition validation.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityType` | string | yes | — | Entity type |
| `id` | string | yes | — | Entity ID |
| `fields` | object | yes | — | Fields to update |
| `returnFields` | string[] | no | type defaults | Fields to return after update |
| `validateTransition` | boolean | no | false | If true and status is changing, validates via `workflow.transitions` first |

```json
{
  "entityType": "ORDER",
  "id": "36",
  "fields": { "status": "BOOKED" },
  "validateTransition": true
}
```

#### `entity.get`

Unified entity lookup by ID or ref with optional edge inclusion.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityType` | string | yes | — | Entity type |
| `id` | string | no | — | Entity ID (preferred lookup) |
| `ref` | string | no | — | Entity ref (fallback; not available for CUSTOMER) |
| `fields` | string[] | no | type defaults | Fields to return |
| `includeEdges` | string[] | no | — | Related entity edges to include (e.g., `fulfilments`, `items`, `attributes`) |

Either `id` or `ref` must be provided.

```json
{
  "entityType": "ORDER",
  "ref": "HD-001",
  "includeEdges": ["fulfilments", "items"]
}
```

### Workflow Management

#### `workflow.get`

Fetch a specific workflow definition by entity type and subtype from the REST API.
Calls `GET /api/v4.1/workflow/{retailerId}/{entityType}::{entitySubtype}/`.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityType` | string | yes | — | Workflow entity type (e.g., ORDER, FULFILMENT, INVENTORY_CATALOGUE) |
| `entitySubtype` | string | yes | — | Workflow entity subtype (e.g., HD, DEFAULT, MASTER) |
| `retailerId` | string | no | from config | Target retailer ID |
| `version` | string | no | latest | Specific workflow version to fetch |
| `outputFile` | string | no | — | File path to save the full workflow JSON. Returns only summary (name, version, status/ruleset counts) — keeps large workflows out of the LLM context |

**Known entity types:** ORDER, FULFILMENT, INVENTORY_CATALOGUE, RETURN_ORDER, BILLING_ACCOUNT, VIRTUAL_CATALOGUE, PRODUCT_CATALOGUE, CONTROL_GROUP, PAYMENT, WAVE, CONSIGNMENT, ARTICLE, LOCATION, NETWORK, CUSTOMER, CREDIT_MEMO

**Common subtypes:** HD, CC, DEFAULT, MASTER, BASE, CUSTOMER, REFUND, PAYMENT, HD_WH, HD_MP

> This fetches a specific workflow by name, which works even when the list endpoint returns 401 (insufficient permissions for listing). Without `outputFile`, returns the full workflow JSON plus a summary. With `outputFile`, writes JSON to disk and returns only the summary.

```json
{
  "entityType": "ORDER",
  "entitySubtype": "HD",
  "outputFile": "./workflows/ORDER-HD.json"
}
```

#### `workflow.list`

List all workflows for a retailer from the REST API.
Calls `GET /api/v4.1/workflow?retailerId={retailerId}`.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `retailerId` | string | no | from config | Target retailer ID |
| `outputDir` | string | no | — | Directory path to download ALL workflows as individual JSON files (`<TYPE>-<SUBTYPE>.json`). Returns only summary metadata. Reads from live server, NOT workflowlog cache |

Returns deduplicated list (latest version per workflow name) plus total version count.

With `outputDir`, downloads every workflow's full JSON to individual files and returns only summary metadata (names, versions, file sizes). This is the recommended way to bulk-download workflows for local analysis without bloating LLM context.

> The list endpoint may return 401 on some accounts where the user lacks listing permissions. In that case, use `workflow.get` to fetch a specific workflow by entity type and subtype.

```json
{
  "outputDir": "./accounts/<PROFILE>/workflows/<RETAILER_REF>"
}
```

#### `workflow.upload`

Deploy a workflow JSON definition to the Fluent environment via REST API.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `workflow` | string \| object | yes | — | Workflow JSON (as object or JSON string) |
| `retailerId` | string | no | from config | Target retailer ID |
| `validate` | boolean | no | true | Validate structure before uploading |
| `dryRun` | boolean | no | false | Validate only, do not deploy |

**Validation checks:** `name` present, at least one status defined, all rulesets have name/triggers/rules.

> For production deployments, prefer `fluent module install` via CLI which bundles workflows with settings, rules, and data in a versioned module. Use this tool for interactive editing, hotfixes, or when CLI is unavailable.

#### `workflow.diff`

Compare two workflow JSON definitions. Pure local computation — no API calls.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `base` | string \| object | yes | — | Base workflow (before) |
| `target` | string \| object | yes | — | Target workflow (after) |
| `format` | `"summary"` \| `"detailed"` \| `"mermaid"` | no | `"summary"` | Output format |

**Risk assessment:** Removing rulesets = HIGH, adding = LOW, modifying props = MEDIUM.

Formats:
- `summary` — change counts and risk level
- `detailed` — per-ruleset change breakdown
- `mermaid` — `stateDiagram-v2` with color-coded added/removed/modified states

#### `workflow.simulate`

Static prediction of workflow event outcomes. Pure local computation — no API calls.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `workflow` | string \| object | yes | — | Workflow JSON |
| `currentStatus` | string | yes | — | Current entity status to simulate from |
| `eventName` | string | no | — | Event name filter (if omitted, returns all matching rulesets) |
| `entityType` | string | no | — | Entity type filter |
| `entitySubtype` | string | no | — | Entity subtype filter |

**What it does:**
- Finds rulesets whose triggers match `currentStatus` (+ optional `eventName`)
- Extracts SetState rules → predicted next status
- Extracts SendEvent rules → predicted follow-on events
- Extracts SendWebhook rules → webhook side effects
- Identifies custom rules that cannot be statically predicted

**Important limitations (always disclosed in response):**
- STATIC ONLY: Cannot evaluate runtime conditions, entity attributes, or settings values
- CUSTOM RULES OPAQUE: Java plugin rules may conditionally execute
- NO CROSS-ENTITY: Does not follow SendEvent chains into child entity workflows
- Use `workflow.transitions` for AUTHORITATIVE live validation

```json
{
  "workflow": { "name": "ORDER::HD", "rulesets": [...] },
  "currentStatus": "CREATED",
  "eventName": "ConfirmValidation"
}
```

### Settings Management

#### `setting.get`

Fetch settings by name (supports `%` wildcards for pattern matching). Optionally saves content to a local file to keep large manifest JSON out of the LLM context.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `name` | string | yes | — | Setting name or pattern. Supports `%` wildcards (e.g., `fc.mystique.manifest.%`) |
| `context` | string | no | — | Filter by context: `ACCOUNT`, `RETAILER`, `LOCATION`, etc. |
| `contextId` | integer | no | — | Filter by context ID |
| `outputFile` | string | no | — | File path to save the setting value. When set, writes content to disk and returns only metadata (name, context, valueType, size). For multiple matches, creates a directory and saves each setting as a separate file |
| `first` | integer | no | 10 | Max results (1–100) |

**Key behaviors:**
- Single match + `outputFile`: writes pretty-printed JSON to file, returns metadata only
- Multiple matches + `outputFile`: creates directory at path, saves each as `<safeName>.json`
- No `outputFile`: returns full settings inline (including `lobValue`)
- Wildcard `%` matches any characters (SQL LIKE syntax)

**Use cases:**
- Download manifest settings locally before editing: `{ "name": "fc.mystique.manifest.oms.%", "outputFile": "./manifests/oms" }`
- Inspect a specific setting inline: `{ "name": "WEBHOOK_ORDER_URL" }`
- Backup all manifest fragments: `{ "name": "fc.mystique.manifest.%", "outputFile": "./backups/manifests" }`

```json
{
  "name": "fc.mystique.manifest.oms.fragment.%",
  "outputFile": "./manifests/fragments"
}
```

#### `setting.upsert`

Create or update a Fluent Commerce setting. Upsert semantics — queries by name + context + contextId first, creates if missing, updates if exists.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `name` | string | yes | — | Setting key/name |
| `value` | string | no | — | Setting value (for small values). Optional when `lobValue` is provided |
| `lobValue` | string/object/array | no | — | Large object value (for JSON payloads > 4KB). Accepts raw JSON objects/arrays or strings |
| `valueType` | string | no | auto | Explicit value type: `"STRING"`, `"LOB"`, or `"JSON"`. **Auto-enforced to `"JSON"` for `fc.mystique.*` settings** regardless of what you pass — the UX Framework requires `valueType: "JSON"` for manifest settings. For non-manifest settings: defaults to `"LOB"` when `lobValue` is provided, `"STRING"` when `value` is provided. |
| `context` | string | yes | — | Scope: `RETAILER`, `ACCOUNT`, `LOCATION`, `NETWORK`, `AGENT`, `CUSTOMER` |
| `contextId` | integer | no | from config | Context ID (e.g., retailer ID). Falls back to `FLUENT_RETAILER_ID` for RETAILER context |

**Key gotchas:**
- `context` is a plain String (`"RETAILER"`), NOT an object
- `contextId` is a separate Int field. For `ACCOUNT` context, `contextId` auto-resolves to `0` — you can omit it.
- Provide either `value` or `lobValue` (`lobValue` wins when both are sent)
- For large JSON values (>4KB), use `lobValue` instead of `value`
- `lobValue` auto-normalizes pre-stringified JSON: if you pass `lobValue: '{"plugins":[...]}'` (a string), it is automatically parsed back to an object before sending to the API, preventing double-encoding
- **Mystique manifest settings (`fc.mystique.*`) auto-enforce `valueType: "JSON"`** — you no longer need to set this manually. The tool resolves it automatically because the UX Framework requires the `Json` scalar, and `"LOB"` causes double-encoding that breaks manifest parsing.
- Returns `created: true/false` and `previousValue` for audit trail

```json
{
  "name": "WEBHOOK_ORDER_URL",
  "value": "https://example.com/webhook",
  "context": "RETAILER"
}
```

#### `setting.bulkUpsert`

Batch create or update multiple settings in one call.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `settings` | array | yes | — | Array of setting objects (min 1, max 50). Each item must include `name`, `context`, and at least one of `value` or `lobValue`; `contextId` and `valueType` are optional |

Processes sequentially with individual error handling. Returns `created`, `updated`, `failed` counts plus per-setting results.

```json
{
  "settings": [
    { "name": "WEBHOOK_ORDER_URL", "value": "https://...", "context": "RETAILER" },
    { "name": "WEBHOOK_FULFILMENT_URL", "value": "https://...", "context": "RETAILER" }
  ]
}
```

### Environment Introspection

#### `environment.discover`

Full environment snapshot in one call.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `include` | string[] | no | `["retailer", "locations", "networks", "catalogues"]` | Sections to include. Valid: `retailer`, `locations`, `networks`, `catalogues`, `workflows`, `settings`, `modules`, `users` |

Returns everything an agent needs: retailer details, locations with type/status, networks with associated locations, catalogues (inventory + product + virtual), deployed workflows, settings, modules, and authenticated user info.

**Limitations:**
- Locations and settings return first 100 items only (use `graphql.queryAll` for complete list)
- Workflows section uses transition API probe — use `fluent workflow list` via CLI for definitive workflow listing

```json
{
  "include": ["retailer", "locations", "networks", "catalogues", "settings", "workflows"]
}
```

#### `environment.validate`

Pre-flight environment validation before E2E tests or deployments.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `checks` | string[] | no | `["auth", "retailer", "locations"]` | Checks to run. Valid: `auth`, `retailer`, `locations`, `inventory`, `workflows`, `settings`, `modules` |

Returns pass/fail per check with severity (`info`, `warning`, `error`) and actionable messages.

**Checks performed:**
- `auth` — token valid, permissions sufficient
- `retailer` — exists, active
- `locations` — at least one warehouse/store exists
- `inventory` — at least one product with stock > 0
- `workflows` — key workflows deployed (ORDER, FULFILMENT)
- `settings` — settings exist in environment
- `modules` — modules deployed (via plugin registry)

```json
{
  "checks": ["auth", "retailer", "locations", "inventory", "workflows"]
}
```

### Testing

#### `test.assert`

Assert entity state matches expectations with optional polling. Deep assertion on entity fields, attributes, and edge counts/statuses.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `entityType` | string | yes | — | Entity type (ORDER, FULFILMENT, etc.) |
| `id` | string | no | — | Entity ID (preferred) |
| `ref` | string | no | — | Entity ref (fallback) |
| `assertions` | object | yes | — | Assertion conditions (see below) |
| `poll` | boolean | no | false | Enable polling mode (retry until pass or timeout) |
| `timeoutMs` | integer | no | 60000 | Polling timeout in ms (min 1000, max 300000) |
| `intervalMs` | integer | no | 5000 | Polling interval in ms (min 1000, max 30000) |

**Assertion fields:**
- `status` — exact match on entity status
- `type` — exact match on entity type
- `subtype` — exact match on entity subtype
- `attributes` — key-value pairs that must be present
- `edges` — per-edge assertions with `minCount`, `maxCount`, `status`

**Polling mode:** Set `poll: true` to retry until assertions pass or timeout. Useful after sending events when state changes are async.

```json
{
  "entityType": "ORDER",
  "ref": "HD-001",
  "assertions": {
    "status": "BOOKED",
    "edges": {
      "fulfilments": { "minCount": 1, "status": "CREATED" }
    }
  },
  "poll": true,
  "timeoutMs": 30000
}
```

## Webhook Validation

### `webhook.validate`

Validate a Fluent Commerce webhook payload and optionally verify its signature.

| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `payload` | object | yes | — | Webhook payload. Must include `name`, `id`, `retailerId` for basic validation |
| `rawBody` | string | no | — | Exact raw HTTP request body for signature verification (avoids false negatives from JSON re-serialization) |
| `signature` | string | no | — | X-Fluent-Signature header value (base64). Required for signature validation |
| `publicKey` | string | no | — | Fluent public key (PEM format). Required when `signature` is provided |
| `algorithm` | `"SHA512withRSA"` \| `"MD5withRSA"` | no | `"SHA512withRSA"` | Signature algorithm |

**Two modes:**
1. **Basic validation** (no signature): Checks required fields present
2. **Signature validation**: Verifies X-Fluent-Signature against the body using the public key

```json
{
  "payload": { "name": "OrderCreated", "id": "123", "retailerId": "1" },
  "signature": "<base64-sig>",
  "publicKey": "<PEM-key>"
}
```

## Response Optimization

High-volume tools support response shaping to reduce token consumption:

### event.list — Field Projection

Pass `fields` (array of strings) to return only selected fields per event. Reduces token usage by 7-8x for typical queries.

```json
{
  "context.entityRef": "ORDER_REF",
  "context.entityType": "ORDER",
  "count": 50,
  "fields": ["id", "name", "eventStatus", "category", "generatedOn", "context.entityRef", "context.entityType"]
}
```

Common field sets by use case:
- **Trace/debug:** `["id", "name", "eventStatus", "category", "context.entityRef", "context.entityType", "generatedOn", "source", "context.sourceEvents"]`
- **E2E status check:** `["id", "name", "eventStatus", "context.entityRef"]`
- **Exception analysis:** `["id", "name", "eventStatus", "category", "attributes", "context.entityRef", "generatedOn"]`

Omit `fields` for full event data (when you need attributes, source, timing, etc.).

### plugin.list — Compact Mode

Pass `compact: true` to return only `ruleInfo` (name, description, entity types) per rule, stripping `parameters` and `eventAttributes`. Reduces token usage by 40-60x for unfiltered queries.

```json
{ "compact": true }
{ "name": "SendEvent", "compact": true }
```

Use full mode (default) when you need rule parameters or event attribute contracts.

### plugin.list — Keys Only Mode

Pass `keysOnly: true` to return just the full list of rule keys plus a `byPrefix` breakdown. No rule details — avoids response budget truncation entirely. Ideal for counting custom vs OOTB rules in `/fluent-account-snapshot`.

```json
{ "keysOnly": true }
```

Returns:
```json
{
  "ok": true,
  "count": 602,
  "keys": ["ACME.order.RuleName", "FLUENTRETAIL.base.RuleName", ...],
  "byPrefix": { "ACME": 402, "FLUENTRETAIL": 200 }
}
```

### environment.discover — modules.byPrefix

The `modules` section now includes `byPrefix` — a count of registered rules per account prefix:
```json
{ "modules": { "pluginCount": 602, "contexts": [...], "byPrefix": { "ACME": 402, "FLUENTRETAIL": 200 } } }
```

### Tool Selection Matrix — Large Payloads

MCP Extension has a **50K char response budget** (`FLUENT_RESPONSE_BUDGET_CHARS`). Manifests (10-120KB) and workflows (50KB+) will be silently truncated if fetched inline. Use `outputFile`/`outputDir` to keep large payloads out of LLM context:

| Scenario | Use this | Not this | Why |
|----------|----------|----------|-----|
| Read one large setting/manifest | `setting.get` + `outputFile` | `graphql.query` inline | Returns metadata only (~200 bytes); full content on disk |
| Bulk-read all manifests | `setting.get` wildcard + `outputFile` | Multiple `graphql.query` calls | Saves each match to individual file |
| Read one workflow | `workflow.get` + `outputFile` | `fluent workflow download` (CLI) | Live server, not stale workflowlog cache |
| Bulk-download all workflows | `workflow.list` + `outputDir` | CLI `fluent workflow download -w all` | One call, live server, files on disk |
| Compare CDN vs custom manifest | `/fluent-mystique-diff` (local script) | Manual MCP queries | Zero API after first CDN download |
| Schema introspection | `graphql.introspect` | `graphql.query` with __type | Cached 10min, structured output |
| List query/mutation roots | `graphql.listRoots` | `graphql.introspect` + parse | Typed schema, cached |
| Build query from fields | `graphql.buildQuery` | Manual string construction | Auto-validates against schema |
| Validate query before exec | `graphql.validate` | N/A | Catches field/type errors early |
| Generate full query | `graphql.generateFull` | Manual introspection | Depth-limited, maximal selection |

**Rule of thumb:** If the response might exceed 4KB, use `outputFile`/`outputDir`. Then `Read` the local file only when you need to inspect it.

## GraphQL Validation Gate (MANDATORY)

See `knowledge/platform/graphql-preflight.md` for the full protocol.

**Every LLM-constructed query or mutation MUST be validated before execution.** Use `graphql.validate` (check `valid` field, NOT `ok`) or `graphql.buildQuery` (returns schema-validated query). One invalid field silently nullifies the entire response in Fluent Commerce — no partial success.

## Entity Query Guidelines (MANDATORY)

**Clarify before querying — never assume:**
1. **Ambiguous ordering** ("last", "latest", "recent", "first", "oldest") — ask the user: most recent (newest) or earliest (oldest)?
2. **Partial date ranges** ("in February", "last month", "in Q1") — ask: which year? Don't assume current year.
3. **Relative terms** ("recently", "a while ago") — ask for a specific date range.
4. Keep asking until query parameters are fully unambiguous. Applies to ALL entity types.

**Always query dynamically:**
- Use `first: N` (most recent) or `last: N` (oldest) on the root query. Fluent default sort is descending by ID.
- **NEVER use `entityById` with an ID from a previous query.** Always use the root query with appropriate filters — new records may exist since the last query.
- For date-filtered queries, use `createdOn: { from: "...", to: "..." }`.

## Pagination Pattern

Fluent uses Relay-style connections. Cursors live on each **edge**, not on pageInfo:

```graphql
{
  orders(first: 50, after: $cursor) {
    edges {
      cursor
      node { id ref status }
    }
    pageInfo { hasNextPage }
  }
}
```

Take cursor from the LAST edge, pass as `after` in next call.

## HTTP Streamable Transport

The MCP extension server supports an HTTP transport mode as an alternative to stdio. This enables remote MCP clients, multi-session usage, and integration with HTTP-based MCP client libraries.

### Configuration

| Env Variable | Default | Description |
|---|---|---|
| `FLUENT_MCP_TRANSPORT` | `stdio` | Set to `http` to start an HTTP server instead of stdio |
| `FLUENT_MCP_HTTP_PORT` | `3100` | HTTP server port |
| `FLUENT_MCP_HTTP_HOST` | `127.0.0.1` | HTTP server bind address |
| `FLUENT_MCP_AUTH_TOKEN` | *(none)* | Bearer token for HTTP auth — when set, requires `Authorization: Bearer <token>` header |
| `FLUENT_MCP_STATELESS` | `false` | Stateless mode — creates a fresh MCP server per request (no session tracking) |

### `.mcp.json` for HTTP mode

When using HTTP transport, the MCP server runs as a standalone process. Start it separately and point your MCP client to the HTTP endpoint:

```bash
# Start the server
FLUENT_MCP_TRANSPORT=http FLUENT_PROFILE=MY_PROFILE npx @fluentcommerce/fluent-mcp-extn
```

MCP clients that support HTTP Streamable transport connect to `http://127.0.0.1:3100/mcp` (or your configured host:port).

**When to use HTTP vs stdio:**

| Scenario | Transport |
|---|---|
| Claude Code / Copilot / local IDE | `stdio` (default) |
| Remote MCP client or shared server | `http` |
| CI/CD pipeline MCP integration | `http` + auth token |
| Multiple concurrent sessions | `http` (stdio is single-session) |
