# MRS MCP Protocol

Surface: contract-only MCP protocol document for the Metarecord Service (MRS). Host: <a href="https://mcp.g3nretailstack.com/mrs/PROTOCOL.md" target="_blank" rel="noopener noreferrer">https://mcp.g3nretailstack.com/mrs/PROTOCOL.md</a> (static) with MCP server at `https://api.mcp.g3nretailstack.com/mcp`. Status: deployed.


## 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/mrs`
- Health check: `GET /mrs/stat` — requires `requireSession` (any authenticated user).
- MCP protocol: <a href="https://mcp.g3nretailstack.com/mrs/PROTOCOL.md" target="_blank" rel="noopener noreferrer">https://mcp.g3nretailstack.com/mrs/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).

## About g3nretailstack
- Headless retail/wholesale stack with microservices including UAS (identity), USM (sessions), OFM (org/facility), and MRS (opaque object storage). All public surfaces are contract-only on custom domains.

## Tenancy, auth, pagination
- Auth placement: header auth is canonical for org-scoped APIs; body auth is accepted for compatibility where documented. See [/common/headers-identity.html](https://doc.g3nretailstack.com/common/headers-identity.html).
- Tenant-facing MRS routes require a valid `session_guid` and are exposed via API Gateway. Operator-only maintenance ops are **direct Lambda** (IAM-gated) and are not exposed via API Gateway.
- **Role enforcement**: All API Gateway routes require `mrs_reader` or `mrs_writer` roles from OFM member/resolve. Read routes (`GET /record`, `GET /record/meta`, `GET /list`, `GET /head`) require `mrs_reader` or `mrs_writer`. Write routes (`POST /record`, `POST /record/complete`, `POST /tag/add`, `POST /tag/remove`, `POST /doom`, `POST /ttl/set`) require `mrs_writer`. Missing role returns `403 mrs.role_required`.
- Required: `orgcode` on all operations (accepted as request body field or `x-orgcode` header); `cccode` optional. `cccode` can be provided as a request field and/or header `x-cccode`. Format `^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$`, canonicalized to uppercase. Invalid values (or body/header mismatch) return HTTP 400. Caller must belong to the requested org (owner or active member); non-associated callers receive `404 not-found` (anti-enumeration).
- Pagination: default `limit` 8, clamped 1–256. `next_token` is an opaque cursor from the prior page.
- Tags: regex `[0-9A-Za-z]{1,128}`, no spaces, cap 20 per record, canonical uppercase storage with case-insensitive match.
- Status FSM: `pending_upload` → `active` → `doomed` (terminal). Presigned uploads start in `pending_upload` and transition to `active` on successful `record/complete`. Inline JSON creates go directly to `active`. TTL `doom_at` auto-dooms; explicit doom call available. Doomed objects retained/versioned 365d; default queries only return active unless explicitly requested. Records in `pending_upload` are not readable via `GET /mrs/record` (returns `invalid-state`).
- Optimistic concurrency (revision): state-changing operations on an existing record require `expected_revision` matching the current record `revision`. Missing → `428 expected-revision-required`; mismatch → `409 conflict` with `error.details.{provided_revision,current_revision,current_record}`.

## Size tiers and content rules
- Inline JSON path: `application/json`, payload ≤256 KB, no gzip accepted.
- Presigned path: required for payloads >256 KB or non-JSON; gzip mandatory; hard cap 128 MB (enforced in presign policy and completion). Store `size_bytes` (original) and `size_gzip_bytes` (compressed). `content_md5` values are the **hex** MD5 of the gzip payload and are required on presign + complete.
- Downloads: inline JSON can be returned directly; all other content via presigned GET. Events never include content.

## Presign error handling (size/gzip/MD5)
Common HTTP 400 tags on presign/complete:
- `inline-too-large`, `gzip-required`, `missing-content-md5`, `missing-size`
- `size-mismatch`, `md5-mismatch`, `etag-mismatch`
- `invalid-token`, `upload-expired`, `missing-object`
- `type-mismatch`, `encoding-mismatch`

## Endpoints (API Gateway, POST unless noted)
- Base: `https://api.g3nretailstack.com/mrs/...`

### Create/put (inline or presign)
- `POST /mrs/record`
  - Inline request: `{ container, orgcode, cccode?, record_id?, caption?, tags?, doom_at?, content_type: "application/json", payload, idempotency_key?, expected_revision? }` (`expected_revision` required when updating an existing `record_id`; payload ≤256 KB JSON)
  - Presign request: `{ container, orgcode, cccode?, record_id?, caption?, tags?, doom_at?, content_type, content_encoding: "gzip", size_bytes, size_gzip_bytes, content_md5 (hex of gzip body), idempotency_key?, expected_revision? }` (`expected_revision` required when updating an existing `record_id`)
  - Inline response `data`: `{ record_id, status, s3: { bucket, key, version_id, etag }, size_bytes, size_gzip_bytes?, content_type, content_encoding?, content_md5?, orgcode, cccode?, container, caption?, tags?, doom_at?, revision, created_at, updated_at }`
  - Presign response `data`: `{ record_id, presign: { upload_url, method, headers, expires_at }, content_token, max_size_bytes: 134217728, orgcode, cccode?, container, caption?, tags?, doom_at?, revision }`

### Complete presigned upload
- `POST /mrs/record/complete`
  - Request: `{ container, record_id, orgcode, cccode?, expected_revision, content_token, reported: { size_bytes, size_gzip_bytes, etag, version_id, content_type, content_encoding, content_md5 } }` (`expected_revision` must match the presign response `revision`; `content_md5` = **hex** MD5 of the gzip payload; size/etag/md5 mismatches → 400)
  - Response: same metadata shape as inline create with `status=active` (transitions from `pending_upload` to `active`).

### Metadata and content handles
- `GET /mrs/record/meta`
  - Request: `{ container, record_id, orgcode, cccode?, include_doomed? }`
  - Response: metadata (no URLs).
- `GET /mrs/record`
  - Request: `{ container, record_id, orgcode, cccode?, inline_ok? }`
  - Response: inline JSON payload when stored inline; otherwise `{ presign: { download_url, method, headers, expires_at }, ...metadata }`.

### List
- `GET /mrs/list`
  - Request: `{ orgcode, cccode?, container?, tag?, status?, include_doomed?, caption_prefix?, record_prefix?, limit?, next_token? }` (status default `active`)
  - Response: `{ items: [metadata...], next_token? }`
  - `include_doomed=true` or `status=all` returns active + doomed records.

### Tags
- `POST /mrs/tag/add` / `POST /mrs/tag/remove`
  - Request: `{ container, record_id, orgcode, cccode?, tags[], expected_revision }`
  - Response: updated metadata.

### Doom and TTL
- `POST /mrs/doom`
  - Request: `{ container, record_id, orgcode, cccode?, reason?, expected_revision }`
  - Response: metadata with `status=doomed`.
- `POST /mrs/ttl/set`
  - Request: `{ container, record_id, orgcode, cccode?, doom_at, expected_revision }`
  - Response: updated metadata.

### Head/exists
- `GET /mrs/head`
  - Request: `{ container, record_id, orgcode, cccode? }`
  - Response `200`: `{ exists: true, status, size_bytes, size_gzip_bytes?, content_md5?, doom_at? }`. Returns `404 not-found` when the record does not exist (does **not** return `exists: false`).

### Operator-only ops (direct Lambda; not API Gateway)
Calling these paths on the API Gateway base returns `404 not-found`.
- `mrs_reconcile` (Lambda): `{ orgcode, container, mode? ("report-only"|"repair"), max_items? (default 25), next_token? }` → `{ mode, checked, drifts, next_token? }`
- `mrs_tag_scrub` (Lambda): `{ orgcode, container, limit? (default 25), next_token? }` → `{ scrubbed, processed, next_token? }`
- `mrs_cleanup` (Lambda): `{ orgcode, container, limit? (default 50), mode? ("dry-run"|"execute") }` → `{ mode, processed, tags_scrubbed, rollups_deleted, next_token?, cutoff }`

### Scheduled jobs (EventBridge; not API Gateway)
These jobs run via EventBridge-scheduled Lambda invocations on the main MRS handler. The handler dispatches on `{ "source": "aws.events", "job": "<name>" }`. They are not exposed via API Gateway or the operator Lambda entry points above.

- `sweep-doomed` (Lambda): Walks all buckets (hex 00-ff) via the GSI4 due-date index. Transitions records whose `doom_at` has elapsed from `active`/`pending_upload` to `doomed`. Removes the due-date GSI projection. Revision-protected; conflicts are counted. Resumes from a persisted job-state cursor. Max items per invocation: `MRS_JOB_MAX_ITEMS` (default 200). Response: `{ success, scanned, updated, revision_conflicts, bucket_queries }`.
- `tag-scrub` (Lambda, scheduled): Currently disabled (`disabled_for_scale_p0`). When enabled, would scrub orphaned tag fan-out items from doomed records globally. Response: `{ success, disabled, reason }`.
- `compact-sizes` (Lambda, scheduled): Currently disabled (`disabled_for_scale_p0`). When enabled, would recalculate and compact size rollups. Response: `{ success, disabled, reason }`.
- `cleanup-stale` (Lambda, scheduled): Runs `cleanupStale` in `execute` mode globally. Walks doomed records older than `MRS_CLEANUP_RETENTION_DAYS` (default 90), strips their tags, and deletes tag fan-out items. Response: `{ success, orgcode, container, mode, cutoff, processed, eligible, tags_scrubbed, rollups_deleted, revision_conflicts, next_token }`.

## Envelope (all responses)
```json
{
  "success": boolean,
  "data": any,
  "error": { "major": {"tag": string, "message": {"en_US": string}}, "minor"?: {"tag": string, "message": {"en_US": string}}, "details"?: any },
  "stats": {
    "call": string,
    "service": "mrs",
    "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,
    "session_guid"?: string,
    "user_guid"?: string,
    "orgcode"?: string,
    "cccode"?: string
  }
}
```

## Notes
- Content is never included in responses/events; only metadata and presigned URLs/headers. Inline path only for JSON ≤256 KB with `application/json`.
- Gzip mandatory for uploads >256 KB; store both original and gzip sizes; hard cap 128 MB.
- Default queries exclude doomed; include via `status` or `include_doomed`.
- Idempotency: `idempotency_key` (ASCII ≤128) on create/put. Scope: container + record_id (if provided) or container + idempotency_key (auto-id). Window: 24h; replay returns prior response (errors replay too).

## Roles
- Read: `mrs_reader`, `mrs_writer` (owner implied).
- Write: `mrs_writer`.
- Read operations: record/get, record/meta, list, head.
- Write operations: record/put, record/complete, tag/add, tag/remove, doom, ttl/set.

## Error tags
Service-specific tags: `invalid-session`, `invalid-tag`, `inline-too-large`, `missing-size`, `size-mismatch`, `too-large`, `unsupported-content-type`, `gzip-required`, `missing-content-md5`, `invalid-content-md5`, `md5-missing`, `md5-mismatch`, `doomed`, `invalid-status`, `idempotency-conflict`, `missing-scope`.
Common tags (see [/common/error-tags.html](https://doc.g3nretailstack.com/common/error-tags.html)): `not-found`, `validation-error`, `unauthorized`, `throttled`, `internal-error`, `expected-revision-required`, `conflict`.

## Example envelopes
Success:
```json
{
  "success": true,
  "data": { "metadata": { "record_id": "r-1", "container": "c1", "status": "active" } },
  "stats": { "service": "mrs", "call": "mrs_record_get", "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": "mrs.validation_failed",
    "http_status": 400,
    "retryable": false,
    "major": { "tag": "missing-scope", "message": { "en_US": "container required" } }
  },
  "stats": { "service": "mrs", "call": "mrs_record_put", "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/mrs/openapi.yaml" target="_blank" rel="noopener noreferrer">https://doc.g3nretailstack.com/mrs/openapi.yaml</a>


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