# Architecture > **Generated from the codebase** by `scripts/gen_architecture.ts` — run `npm run docs:arch`. > Every table below is read out of the source: if a surface is listed, code for it exists > in `src/` or `packages/`. Two sections are authored rather than derived — the diagram > and [Not built](#not-built) — and both say so where they appear, because a shape and an > absence cannot be parsed out of code. > LESSONS R6: eight prior submissions documented routes that were never built. _Generated: 2026-09-28T08:34:23.602Z_ ## Protocol | | | |---|---| | MCP specification | `2025-11-25` — `LATEST_PROTOCOL_VERSION` in the pinned SDK | | Transport | Streamable HTTP (`StreamableHTTPServerTransport`), with `Last-Event-ID` resume | | Hosting | self-hosted; `npm start` runs one process on one origin | | Track requirement | Alexa+ asks for a self-hosted MCP server implementing **2025-11-25** (minimum) over Streamable HTTP | `scripts/e2e.ts` and `scripts/verify.ts` §6 both assert the NEGOTIATED version at runtime and exit non-zero below that floor, so this row cannot drift from the wire. ## The shape _Authored, not derived — the tables below are the machine-checked copy._ ```mermaid flowchart LR physio["Sarah, physio
web/clinician.html"] -- "POST /write
HMAC over raw bytes" --> http subgraph proc["one process · npm start"] http["src/http.ts
Streamable HTTP · OAuth resource"] server["src/server.ts
10 MCP handlers"] store["packages/live-resources
versioned records · hash chain
audience partition"] env["src/envelope.ts
AES-256-GCM · AAD binds the slot"] http --> server server --> store store --> env end store -- "notifications/resources/updated" --> server server -- "SSE, resumable by Last-Event-ID" --> echo["Ray, Echo Show
web/echo.html · ui://unsay/echo"] echo -- "resources/read · re-read under current scope" --> http http -- "GET /verify · public chain replay" --> judge["a judge, no account"] store -. "care-internal:// refused
-32002, never a 403" .-> echo ``` ## MCP surfaces actually registered | Protocol method | Registered in | |---|---| | `tools/call` | `src/server.ts` | | `completion/complete` | `src/server.ts` | | `prompts/get` | `src/server.ts` | | `prompts/list` | `src/server.ts` | | `resources/templates/list` | `src/server.ts` | | `resources/list` | `src/server.ts` | | `tools/list` | `src/server.ts` | | `resources/read` | `src/server.ts` | | `resources/subscribe` | `src/server.ts` | | `resources/unsubscribe` | `src/server.ts` | | `notifications/message` | `src/server.ts` | | `notifications/resources/list_changed` | `packages/live-resources/src/notifier.ts` | | `notifications/resources/updated` | `packages/live-resources/src/notifier.ts` | **10 request handlers + 3 notification sender(s).** Declaring `logging` also makes the SDK serve `logging/setLevel` without a handler of our own, so it is a surface a host can call but is deliberately not listed above — this table only names methods with a sender or handler in the source. It is **honoured**, not merely served: `src/server.ts` passes the transport session id to `sendLoggingMessage()`, which is what the SDK filters the level against, and `npm run e2e` asserts a `notice` is suppressed at level `emergency` while `notifications/resources/updated` still arrives. ## HTTP routes | Route | Methods | Auth | |---|---|---| | `/.well-known/oauth-protected-resource` | GET | public (RFC 9728) | | `/.well-known/oauth-protected-resource/mcp` | GET | public (RFC 9728) | | `/health` | GET | public | | `/mcp` | POST · GET · DELETE | Bearer (care.read.user / care.read.assistant) | | `/verify` | GET | public — a token widens what it lists | | `/write` | POST | HMAC-SHA256 over the raw body, or Bearer care.write | | `/index.html` (and `/`) | GET · HEAD | public — served from `web/` | | `/clinician.html` | GET · HEAD | public — served from `web/` | | `/echo.html` | GET · HEAD | public — served from `web/` | | `/doc/readme` | GET · HEAD | public — `README.md` rendered by `src/docpage.ts` | | `/doc/demo` | GET · HEAD | public — `DEMO.md` rendered by `src/docpage.ts` | | `/doc/architecture` | GET · HEAD | public — `ARCHITECTURE.md` rendered by `src/docpage.ts` | | `/doc/friction` | GET · HEAD | public — `FRICTION.md` rendered by `src/docpage.ts` | | `/doc/spec` | GET · HEAD | public — `docs/SPEC.md` rendered by `src/docpage.ts` | **6 routes + 3 static pages + 5 rendered documents**, all in `src/http.ts`. Plus a second read-only allowlist — the documents and receipts the landing page cites, so every link on it resolves against the server a judge is already running: `/README.md` · `/DEMO.md` · `/ARCHITECTURE.md` · `/FRICTION.md` · `/LICENSE` · `/docs/SPEC.md` · `/docs/proof/bench.txt` · `/docs/proof/bench.remote.txt` · `/docs/proof/verify.json` · `/docs/proof/bench.json` · `/docs/proof/live_run.jsonl` · `/docs/proof/probe_subscribe.json` · `/docs/proof/resume.json` · `/skill/SKILL.md` · `/icon.svg` · `/og.png` · `/docs/assets/readme-hero-animated.svg` · `/docs/assets/icon-animated.svg` · `/packages/live-resources/src/store.ts` · `/src/server.ts` · `/src/http.ts` ## Declared capabilities ```js resources: { subscribe: true, listChanged: true }, completions: {}, prompts: {}, // A second, cheap notification channel. Declaring it also makes the SDK // serve logging/setLevel, so a host can turn the revision log down. logging: {}, tools: {}, ``` ## Modules | File | Exports | |---|---| | `src/audit.ts` | `AuditLog` | | `src/blobs.ts` | `EXERCISE_CLIP`, `GAIT_NOTE`, `CLIPS`, `blobFor` | | `src/docpage.ts` | `DOC_PAGES`, `slugify`, `resolveDocLink`, `renderMarkdown`, `renderDocPage` | | `src/envelope.ts` | `EnvelopeError`, `MasterKeyMissingError`, `KmsUnavailableError`, `DecryptionFailedError`, `EnvelopeKeyMismatchError`, `describeSealed`, `Envelope`, `startupLine`, `announceOnce`, `LocalKeyProvider`, `KmsKeyProvider`, `envelopeFromEnv` | | `src/http.ts` | `DEV_TOKEN_SECRET`, `DEV_WRITE_SECRET`, `SCOPES_SUPPORTED`, `WRITE_SKEW_MS`, `mintToken`, `TokenError`, `verifyToken`, `writeSigningMaterial`, `signWriteBody`, `urisNamedBy`, `replayAllowed`, `MemoryEventStore`, `createHttpServer` | | `src/retraction.ts` | `GLOSSARY`, `spokenAge`, `glossesFor`, `renderRetraction` | | `src/seed.ts` | `RAY`, `DEMO_NOW`, `seed`, `seedDemo`, `STAGED_REVISION` | | `src/server.ts` | `SERVER_INSTRUCTIONS`, `RESOURCE_PAGE_SIZE`, `BRIEF_CARER`, `buildServer` | | `src/store.ts` | `CARE_PARTITION`, `uriFor`, `parseUri`, `LiveResourceStore` | | `src/types.ts` | `SCHEME`, `SCOPE` | | `src/ui_resource.ts` | `UI_ECHO_URI`, `UI_MIME_TYPE`, `UI_TEMPLATE_META`, `UI_FRAME_META`, `uiHtml`, `uiResourceDescriptor` | ## Extracted package The generic half — versioned resources, revision notifications, the hash chain, and the audience/scope partition — lifted out of `src/` so it can be depended on without Unsay. Consumed here by relative import; not published to npm. | File | Exports | |---|---| | `packages/live-resources/src/chain.ts` | `hashVersion`, `AAD_SEPARATOR`, `recordAad` | | `packages/live-resources/src/codec.ts` | — | | `packages/live-resources/src/errors.ts` | `LiveResourceError`, `NotFoundError`, `ReservedSeparatorError`, `PartitionConfigError` | | `packages/live-resources/src/index.ts` | — | | `packages/live-resources/src/notifier.ts` | `ResourceNotifier` | | `packages/live-resources/src/partition.ts` | `AUDIENCES`, `AudiencePartition`, `DEFAULT_PARTITION` | | `packages/live-resources/src/store.ts` | `LiveResourceStore` | | `packages/live-resources/src/types.ts` | — | ## Executable scripts | Command | File | |---|---| | `npm run start` | `scripts/serve.ts` | | `npm run probe` | `scripts/probe_subscribe.ts` | | `npm run probe:resume` | `scripts/probe_resume.ts` | | `npm run test` | `test/**` | | `npm run typecheck` | `tsc --noEmit` | | `npm run verify` | `scripts/verify.ts` | | `npm run e2e` | `scripts/e2e.ts` | | `npm run bench` | `scripts/bench.ts` | | `npm run seed` | `scripts/seed_dump.ts` | | `npm run fixtures` | `node --experimental-strip-types fixtures/gen.ts` | | `npm run keygen` | `node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"` | | `npm run docs:arch` | `scripts/gen_architecture.ts` | ## Runtime dependencies | Package | Version | Used in | |---|---|---| | `@modelcontextprotocol/sdk` | `^1.30.0` | `src/http.ts`, `src/server.ts`, `packages/live-resources/src/notifier.ts`, `scripts/bench.ts`, `scripts/e2e.ts`, `scripts/probe_resume.ts`, `scripts/probe_subscribe.ts`, `scripts/verify.ts` | ## Not built The one authored section in this file — every table above is derived from the source, this list is written by hand in `scripts/gen_architecture.ts` because absence cannot be parsed out of code. Stated so this document cannot imply otherwise: - **No AWS deployment.** `npm start` runs the server locally and nothing is hosted. The KMS provider in `src/envelope.ts` is SigV4-signed and shaped correctly but has never been executed against a live CMK — see FRICTION.md F-004, the payment-verification hold. - **No authorization server.** Unsay is an OAuth 2.1 protected RESOURCE only: no `/authorize`, no `/token`, no refresh, no revocation, no JWKS. Tokens are HS256 under a shared secret, minted by `mintToken()` in the same file that verifies them. - **No durable storage.** The store, the event store and the audit log are in memory; the audit log survives only if `UNSAY_AUDIT_LOG` names a file. - **The MCP Apps binding is shaped, not exercised.** `ui://unsay/echo` is served and read over the protocol by `npm run e2e`, but no host we can reach implements the extension, so the `_meta` template binding on `whats_changed` has never been rendered by one — see FRICTION.md F-013. - No ML model of any kind, by design — the reasoning model belongs to the host. - No blockchain, token, or payment surface.