# UAS MCP Protocol

Surface: unauthenticated MCP protocol document for UAS. Hosted at <a href="https://mcp.g3nretailstack.com/uas/PROTOCOL.md" target="_blank" rel="noopener noreferrer">https://mcp.g3nretailstack.com/uas/PROTOCOL.md</a> (static) with MCP server at `https://api.mcp.g3nretailstack.com/mcp`.

Status: live; safe for external read (no secrets). Update as UAS evolves.

Runtime: Node.js 24; argon2 is bundled for password handling. Internal packaging details are intentionally omitted here.


## Usage patterns (headless)
- Stack-wide SOPs & operations catalog: <a href="https://doc.g3nretailstack.com/story/operations.html" target="_blank" rel="noopener noreferrer">/story/operations.html</a>.
- Super-usecase scenarios + QA status: <a href="https://doc.g3nretailstack.com/story/super-usecases.html" target="_blank" rel="noopener noreferrer">/story/super-usecases.html</a>.
- This protocol stays contract-only; use the catalogs for workflow expectations.

## Base URL
- API Gateway: `https://api.g3nretailstack.com/uas` (public `POST /uas/stat` only).
- Health check: `GET /uas/stat` — public, no auth required.
- MCP protocol: <a href="https://mcp.g3nretailstack.com/uas/PROTOCOL.md" target="_blank" rel="noopener noreferrer">https://mcp.g3nretailstack.com/uas/PROTOCOL.md</a>.

## MCP transport & resources
- Transport/auth/options: see <a href="https://doc.g3nretailstack.com/common/mcp.html" target="_blank" rel="noopener noreferrer">/common/mcp.html</a>.
- MCP resources include protocol docs, OpenAPI contracts, and doc pages (surfaces/calls/playbooks).
- Streaming: JSON only today; SSE is not enabled on API Gateway (streaming would use a dedicated endpoint).

## Auth + tenancy
- `/uas/stat` uses `email` + `passcode` (no session required).
- Direct Lambda operations are IAM-gated (CLI uses direct invoke); optional `orgcode`/`cccode` passthrough is allowed.

## About g3nretailstack (G3N headless)
- Headless retail stack with wholesale extensions and backbone for retail/wholesale ops.
- Microservices (implemented): UAS (identity, emails, passcodes, PMs), USM (sessions), OFM (org/facility, membership, teams, orgcode/cccode), MRS (metarecord storage), PVM (product metadata + suppliers/taxonomy), PMC (publish-time stamping), ICS (inventory control), SCM (sales cycle), PCM (procurement), PPM (pricing/promotions), CRM (loyalty), Influencer, Accounting, IPM (integration plane), RBS (retail bus), UTL (offboarding), OPS (operations management), UCP (Universal Commerce Protocol Adapter), SLC (Shopify Legacy Connector), VisualGrid (data exploration).

## Endpoints

- **Public API Gateway**: `POST https://api.g3nretailstack.com/uas/stat`
- Payload: `{ email: string, passcode: string, include_payment_history?: boolean, actor?: string }` (email inputs canonicalized: trim + lowercase; optional passthrough: `orgcode` may be included for correlation; optional cost attribution `cccode` may be provided as a field and/or header `x-cccode` and must match `^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$` (canonical uppercase); invalid values return HTTP 400. Bearer `session_guid` tokens are not emitted in response `stats` or tenant events/audits.)
  - Response: envelope `{ success, data?: UserSnapshot, error?, revision?, stats }`

- **Optimistic concurrency (revision)** (applies to state-changing actions on an existing user):
  - Request field: `expected_revision` (must match the current record `revision`)
  - Missing `expected_revision` → HTTP `428` (`expected-revision-required`)
  - Mismatch → HTTP `409` (`conflict`)
  - Conflict payload includes `error.details.{provided_revision,current_revision,current_record}`
  - Get the current `revision` from `stat` (public) or `userGet`/`userSnapshot` (direct). After a successful mutation, use the response `revision` for your next write.

- **Direct Lambda (CLI/SDK) actions** (invoke with payload JSON; `UAS_ACTION` shown; default path for all non-public operations):
  - `userCreate`: `{ email, passcode, caption?, reason?, actor? }` (optional passthrough accepted: `orgcode`, `cccode`)
  - `userGet` / `userSnapshot`: `{ user_id, include_payment_history?, actor? }` (passthrough optional context accepted)
  - `userStatusSet`: `{ user_id, status, expected_revision, reason?, actor? }` (passthrough optional context accepted; verified requires verified primary email + passcode set). Note: the user status FSM also has an implicit `verified → unverified` transition triggered as a side-effect of `emailSetPrimary` when the new primary email is unverified (emits `userStatusAutoUnverify` event).
  - `userConfigSet`: `{ user_id, max_active_sessions: number|null, expected_revision, reason?, actor? }` (set per-user session cap; must be 32–8192 when set; null clears)
  - Pagination rules (apply to all list-style actions): default `limit` 8, clamped 1–256 inclusive. `next_token` is an opaque base64 cursor from the prior page.
  - Email
    - `emailAdd`: `{ user_id, email, expected_revision, caption?, reason?, actor? }`
    - `emailList`: `{ user_id, limit?, next_token?, actor? }` (paginated; response includes `next_token`)
    - `emailSetPrimary`: `{ user_id, email, expected_revision, reason?, actor? }` — **Side-effect:** when the new primary email is unverified and the user status is `verified`, the user status is automatically downgraded to `unverified`. This side-effect emits a `userStatusAutoUnverify` event.
    - `emailIssueToken`: `{ user_id, email, expected_revision, reason?, actor? }`
    - `emailConfirmToken`: `{ user_id, token, expected_revision, reason?, actor? }`
    - `emailDoom`: `{ user_id, email, expected_revision, reason?, actor? }`
  - Passcode
    - `passcodeSet` / `passcodeReset`: `{ user_id, passcode, expected_revision, reason?, actor? }`
  - Payment methods (FSM: `pending` → `active` | `suspended` | `doomed`; `active` ↔ `suspended`; `active` → `doomed`; `doomed` is terminal)
    - `pmCreate`: `{ user_id, processor, token_type, token, expected_revision, caption?, reason?, actor? }`
    - `pmList`: `{ user_id, include_history?, limit?, next_token?, actor? }` (paginated; response includes `next_token`)
    - `pmSetStatus`: `{ user_id, pm_id, status, expected_revision, reason, actor? }`
    - `pmPromote`: `{ user_id, pm_id, expected_revision, reason?, actor? }` — sets status to `active`
    - `pmDemote`: `{ user_id, pm_id, expected_revision, reason?, actor? }` — sets status to `suspended`
    - `pmDoom`: `{ user_id, pm_id, expected_revision, reason?, actor? }` — terminal; removes token
    - `pmVacateToken`: `{ user_id, pm_id, expected_revision, reason?, actor? }` — nulls stored token for doomed PM
  - Optional passthrough for any action: `orgcode`, `cccode` — forwarded to stats/events if present, never required/validated.

All responses use the envelope:
```json
{
  "success": boolean,
  "data"?: any,
  "error"?: { "major": {"tag": string, "message": {"en_US": string}}, "minor": {"tag": string, "message": {"en_US": string}}, "details"?: any },
  "revision"?: string,
	  "stats"?: {
	    "call": string,
	    "service": "uas",
	    "request_id"?: string,
	    "timestamp_utc": string,
	    "build": { "build_major": string, "build_minor": string, "build_id": string },
	    "latency_ms"?: number,
	    "bandwidth_in_bytes"?: number,
	    "bandwidth_out_bytes"?: number,
	    "actor"?: string,
	    "user_guid"?: string,
	    "orgcode"?: string,
	    "cccode"?: string
	  }
	}
```

Context passthrough
- `actor` is UAS-aware context. If provided, it is included in stats/events.
- `orgcode` is opaque passthrough only. If it appears in a request, it is copied into stats/events; otherwise it is omitted. UAS does not validate, generate, or require it.
- `cccode` is opaque passthrough for direct Lambda actions, but is validated/canonicalized on the public `/uas/stat` API Gateway surface (and can also be provided as header `x-cccode`). Bearer `session_guid` tokens are treated as secrets and are not emitted in response `stats` or tenant events/audits.

## Data shapes (abridged)
- `UserSnapshot`: `{ user_id, account_ref, status, caption, created_at, updated_at, emails: EmailRecord[], passcode: { set: boolean, updated_at?: string }, payment_methods: PaymentMethod[] }`
- `EmailRecord`: `{ email, status, is_primary, created_at, updated_at }`
- `PaymentMethod`: `{ pm_id, processor, token_type, status, priority, created_at, updated_at, status_history?: Array<{status, reason, changed_at}>, default?: boolean, doomed_at?: string }`

Request/response schema (concise)
- `userCreate` → req `{ email, passcode, caption?, reason?, actor?, orgcode?, cccode? }`, resp `{ user_id, account_ref }`
- `userGet` / `userSnapshot` → req `{ user_id, include_payment_history?, actor?, orgcode?, cccode? }`, resp `{ user_snapshot }`
- `userStatusSet` → req `{ user_id, status, expected_revision, reason?, actor?, orgcode?, cccode? }`, resp `{ status }`
- `userConfigSet` → req `{ user_id, max_active_sessions: number|null, expected_revision, reason?, actor?, orgcode?, cccode? }`, resp `{ user_id, max_active_sessions? }`
- `emailAdd` → req `{ user_id, email, expected_revision, caption?, reason?, actor?, orgcode?, cccode? }`, resp `{ email }`
- `emailList` → req `{ user_id, limit?, next_token?, actor?, orgcode?, cccode? }`, resp `{ emails, next_token? }`
- `emailSetPrimary` → req `{ user_id, email, expected_revision, reason?, actor?, orgcode?, cccode? }`, resp `{ primary }` — **Side-effect:** when the new primary email is unverified and the user status is `verified`, the user status is automatically downgraded to `unverified` and a `userStatusAutoUnverify` event is emitted.
- `emailIssueToken` → req `{ user_id, email, expected_revision, reason?, actor?, orgcode?, cccode? }`, resp `{ token }`
- `emailConfirmToken` → req `{ user_id, token, expected_revision, reason?, actor?, orgcode?, cccode? }`, resp `{ email, status: 'verified' }`
- `emailDoom` → req `{ user_id, email, expected_revision, reason?, actor?, orgcode?, cccode? }`, resp `{ email, status: 'doomed' }`
- `passcodeSet`/`passcodeReset` → req `{ user_id, passcode, expected_revision, reason?, actor?, orgcode?, cccode? }`, resp `{ ok: true }`
- `pmCreate` → req `{ user_id, processor, token_type, token, expected_revision, caption?, reason?, actor?, orgcode?, cccode? }`, resp `{ pm_id, status }`
- `pmList` → req `{ user_id, include_history?, limit?, next_token?, actor?, orgcode?, cccode? }`, resp `{ payment_methods, next_token? }`
- `pmSetStatus` → req `{ user_id, pm_id, status, expected_revision, reason, actor?, orgcode?, cccode? }`, resp `{ pm_id, status, default_pm? }`
- `pmPromote`/`pmDemote`/`pmDoom` → req `{ user_id, pm_id, expected_revision, reason?, actor?, orgcode?, cccode? }` (status implied), resp mirrors `pmSetStatus`
- `pmVacateToken` → req `{ user_id, pm_id, expected_revision, reason?, actor?, orgcode?, cccode? }`, resp `{ pm_id, token_vacated: true }`

## Eventing and audit
- Eventing/audit infrastructure exists but is intentionally not described in this public protocol. Stats include actor/org/cc/user context when provided (never bearer session tokens).

## Roles
UAS does not enforce role-based access at the API level. All operations require a valid request context (session or direct invocation). There is no org-scoped role gating; authorization is identity-based (user_id).

## Error tags
Service-specific tags: `duplicate-email`, `invalid-token`, `token-expired`, `invalid-transition`, `passcode-policy-failed`, `passcode-reuse`, `payment-uniqueness-conflict`.
Common tags (see [/common/error-tags.html](https://doc.g3nretailstack.com/common/error-tags.html)): `not-found`, `validation-error`, `unauthorized`, `internal-error`, `expected-revision-required`, `conflict`.

## Example envelopes
Success:
```json
{
  "success": true,
  "data": { "user_id": "u-abc", "status": "verified" },
  "stats": { "service": "uas", "call": "userGet", "timestamp_utc": "2026-01-01T00:00:00Z", "build": { "build_major": "MONDAY", "build_minor": "0000000000", "build_id": "MONDAY-0000000000" } }
}
```
Error:
```json
{
  "success": false,
  "error": {
    "error_code": "uas.validation_failed",
    "http_status": 400,
    "retryable": false,
    "major": { "tag": "validation-error", "message": { "en_US": "email required" } }
  },
  "stats": { "service": "uas", "call": "userCreate", "timestamp_utc": "2026-01-01T00:00:00Z", "build": { "build_major": "MONDAY", "build_minor": "0000000000", "build_id": "MONDAY-0000000000" } }
}
```


## Idempotency & retries
- All **GET / list / resolve / search** calls are safe to retry with identical inputs (read-only, no side effects).
- **POST mutations** that accept `expected_revision` use optimistic concurrency: on `409 conflict` or `428 expected-revision-required`, re-read the record, obtain the current `revision`, and retry with the updated value.
- Creates are generally **not** idempotent. Prefer caller-provided `code` (where supported) and verify existence before retrying a failed create.
- Bulk or scheduled jobs that accept an `idempotency_key` will de-duplicate within the documented time window.

## Known pitfalls
- **Missing `expected_revision`**: most state-changing operations require it; omitting it returns `428` with the current revision in `error.details`.
- **Stale revision**: reading a record, waiting, then writing with an outdated `revision` triggers `409`. Always use the latest revision from the most recent read.
- **Pagination cursors**: `next_token` is opaque JSON. Do not modify, decode, or persist cursors across sessions — they may change format between deploys.
- **Anti-enumeration 404**: some org-scoped reads return `404` even when the record exists, if the caller is not associated with the org. Treat `404` as ambiguous; verify caller association before assuming "not found".

## OpenAPI
- Contract schema: <a href="https://doc.g3nretailstack.com/uas/openapi.yaml" target="_blank" rel="noopener noreferrer">https://doc.g3nretailstack.com/uas/openapi.yaml</a>

## Hosting plan
- Publish this document (and future updates) to `mcp.g3nretailstack.com`. Ensure content remains unauthenticated, contract-focused, and free of internal resource details.


_Build MONDAY-1776194870 • 2026-04-14T19:27:50.000Z • [© 1999 Microhouse Systems Inc. All rights reserved.](https://doc.g3nretailstack.com/common/copyright-license.html)_
