pqc-memory-mcp 1.0.0

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.
Files changed (155) hide show
  1. package/.env.example +47 -0
  2. package/AGENTS.md +13 -0
  3. package/README.md +403 -0
  4. package/dist/auth/tenant-verifier.d.ts +18 -0
  5. package/dist/auth/tenant-verifier.d.ts.map +1 -0
  6. package/dist/auth/tenant-verifier.js +255 -0
  7. package/dist/auth/tenant-verifier.js.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +34 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/client.d.ts +244 -0
  13. package/dist/client.d.ts.map +1 -0
  14. package/dist/client.js +543 -0
  15. package/dist/client.js.map +1 -0
  16. package/dist/debug.d.ts +6 -0
  17. package/dist/debug.d.ts.map +1 -0
  18. package/dist/debug.js +21 -0
  19. package/dist/debug.js.map +1 -0
  20. package/dist/http.d.ts +21 -0
  21. package/dist/http.d.ts.map +1 -0
  22. package/dist/http.js +189 -0
  23. package/dist/http.js.map +1 -0
  24. package/dist/index.d.ts +3 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +28 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/init-wizard.d.ts +3 -0
  29. package/dist/init-wizard.d.ts.map +1 -0
  30. package/dist/init-wizard.js +138 -0
  31. package/dist/init-wizard.js.map +1 -0
  32. package/dist/mcp-exchange.d.ts +38 -0
  33. package/dist/mcp-exchange.d.ts.map +1 -0
  34. package/dist/mcp-exchange.js +126 -0
  35. package/dist/mcp-exchange.js.map +1 -0
  36. package/dist/server-factory.d.ts +8 -0
  37. package/dist/server-factory.d.ts.map +1 -0
  38. package/dist/server-factory.js +29 -0
  39. package/dist/server-factory.js.map +1 -0
  40. package/dist/session.d.ts +27 -0
  41. package/dist/session.d.ts.map +1 -0
  42. package/dist/session.js +121 -0
  43. package/dist/session.js.map +1 -0
  44. package/dist/tenant-cache.d.ts +20 -0
  45. package/dist/tenant-cache.d.ts.map +1 -0
  46. package/dist/tenant-cache.js +61 -0
  47. package/dist/tenant-cache.js.map +1 -0
  48. package/dist/tenant-fetch.d.ts +20 -0
  49. package/dist/tenant-fetch.d.ts.map +1 -0
  50. package/dist/tenant-fetch.js +91 -0
  51. package/dist/tenant-fetch.js.map +1 -0
  52. package/dist/tenant-runtime.d.ts +16 -0
  53. package/dist/tenant-runtime.d.ts.map +1 -0
  54. package/dist/tenant-runtime.js +31 -0
  55. package/dist/tenant-runtime.js.map +1 -0
  56. package/dist/tenant.d.ts +20 -0
  57. package/dist/tenant.d.ts.map +1 -0
  58. package/dist/tenant.js +26 -0
  59. package/dist/tenant.js.map +1 -0
  60. package/dist/tool-definitions.d.ts +9 -0
  61. package/dist/tool-definitions.d.ts.map +1 -0
  62. package/dist/tool-definitions.js +81 -0
  63. package/dist/tool-definitions.js.map +1 -0
  64. package/dist/tool-dispatch.d.ts +9 -0
  65. package/dist/tool-dispatch.d.ts.map +1 -0
  66. package/dist/tool-dispatch.js +194 -0
  67. package/dist/tool-dispatch.js.map +1 -0
  68. package/dist/tools/codegraph.d.ts +73 -0
  69. package/dist/tools/codegraph.d.ts.map +1 -0
  70. package/dist/tools/codegraph.js +203 -0
  71. package/dist/tools/codegraph.js.map +1 -0
  72. package/dist/tools/delete.d.ts +18 -0
  73. package/dist/tools/delete.d.ts.map +1 -0
  74. package/dist/tools/delete.js +23 -0
  75. package/dist/tools/delete.js.map +1 -0
  76. package/dist/tools/evolution.d.ts +90 -0
  77. package/dist/tools/evolution.d.ts.map +1 -0
  78. package/dist/tools/evolution.js +255 -0
  79. package/dist/tools/evolution.js.map +1 -0
  80. package/dist/tools/graph.d.ts +43 -0
  81. package/dist/tools/graph.d.ts.map +1 -0
  82. package/dist/tools/graph.js +94 -0
  83. package/dist/tools/graph.js.map +1 -0
  84. package/dist/tools/models.d.ts +57 -0
  85. package/dist/tools/models.d.ts.map +1 -0
  86. package/dist/tools/models.js +105 -0
  87. package/dist/tools/models.js.map +1 -0
  88. package/dist/tools/narratives.d.ts +82 -0
  89. package/dist/tools/narratives.d.ts.map +1 -0
  90. package/dist/tools/narratives.js +218 -0
  91. package/dist/tools/narratives.js.map +1 -0
  92. package/dist/tools/objects.d.ts +65 -0
  93. package/dist/tools/objects.d.ts.map +1 -0
  94. package/dist/tools/objects.js +157 -0
  95. package/dist/tools/objects.js.map +1 -0
  96. package/dist/tools/obsidian.d.ts +31 -0
  97. package/dist/tools/obsidian.d.ts.map +1 -0
  98. package/dist/tools/obsidian.js +167 -0
  99. package/dist/tools/obsidian.js.map +1 -0
  100. package/dist/tools/outcome.d.ts +21 -0
  101. package/dist/tools/outcome.d.ts.map +1 -0
  102. package/dist/tools/outcome.js +35 -0
  103. package/dist/tools/outcome.js.map +1 -0
  104. package/dist/tools/promote.d.ts +21 -0
  105. package/dist/tools/promote.d.ts.map +1 -0
  106. package/dist/tools/promote.js +31 -0
  107. package/dist/tools/promote.js.map +1 -0
  108. package/dist/tools/reflect.d.ts +27 -0
  109. package/dist/tools/reflect.d.ts.map +1 -0
  110. package/dist/tools/reflect.js +85 -0
  111. package/dist/tools/reflect.js.map +1 -0
  112. package/dist/tools/search.d.ts +27 -0
  113. package/dist/tools/search.d.ts.map +1 -0
  114. package/dist/tools/search.js +42 -0
  115. package/dist/tools/search.js.map +1 -0
  116. package/dist/tools/strategies.d.ts +43 -0
  117. package/dist/tools/strategies.d.ts.map +1 -0
  118. package/dist/tools/strategies.js +126 -0
  119. package/dist/tools/strategies.js.map +1 -0
  120. package/dist/tools/summary.d.ts +61 -0
  121. package/dist/tools/summary.d.ts.map +1 -0
  122. package/dist/tools/summary.js +124 -0
  123. package/dist/tools/summary.js.map +1 -0
  124. package/dist/tools/swarm.d.ts +71 -0
  125. package/dist/tools/swarm.d.ts.map +1 -0
  126. package/dist/tools/swarm.js +145 -0
  127. package/dist/tools/swarm.js.map +1 -0
  128. package/dist/tools/v4.d.ts +152 -0
  129. package/dist/tools/v4.d.ts.map +1 -0
  130. package/dist/tools/v4.js +348 -0
  131. package/dist/tools/v4.js.map +1 -0
  132. package/dist/tools/write.d.ts +30 -0
  133. package/dist/tools/write.d.ts.map +1 -0
  134. package/dist/tools/write.js +52 -0
  135. package/dist/tools/write.js.map +1 -0
  136. package/dist/types.d.ts +359 -0
  137. package/dist/types.d.ts.map +1 -0
  138. package/dist/types.js +2 -0
  139. package/dist/types.js.map +1 -0
  140. package/dist/zod-json-schema.d.ts +3 -0
  141. package/dist/zod-json-schema.d.ts.map +1 -0
  142. package/dist/zod-json-schema.js +52 -0
  143. package/dist/zod-json-schema.js.map +1 -0
  144. package/docs/claude-remote-oauth.md +26 -0
  145. package/docs/crypticpqc-auth-architecture.md +107 -0
  146. package/docs/crypticpqc-cross-domain-auth.md +194 -0
  147. package/docs/mcp-auth0-exchange-contract.md +75 -0
  148. package/docs/mcp-authorization-spec-alignment.md +29 -0
  149. package/docs/mcp-org-object-storage.md +55 -0
  150. package/docs/pqc-api-tenant-context-go.md +238 -0
  151. package/docs/pqc-db-mcp-tenant-context.md +15 -0
  152. package/docs/pqc-db-tenant-context-implementation.md +32 -0
  153. package/docs/standalone-distribution.md +92 -0
  154. package/package.json +52 -0
  155. package/scripts/start.mjs +13 -0
@@ -0,0 +1,194 @@
1
+ # Cryptic PQC — shared auth across domains
2
+
3
+ This document maps how **one identity story** should span the four public surfaces. It is the coordination layer between teams/repos; **pqc-api** (`crypticpqc.com`) remains the **authoritative** place for org/bucket/API-key resolution unless explicitly delegated.
4
+
5
+ | Origin | Role | Typical credentials |
6
+ |--------|------|---------------------|
7
+ | **`https://crypticpqc.com`** | pqc-db / pqc-api — data, memory API, `GET /api/v1/mcp/tenant-context`, portal JSON if served here | **`JWTService.ValidateAccessToken`** — **HS256 pqc-db access JWT** on protected routes; optional **`X-API-Key`** / **`MCP_TENANT_SERVICE_API_KEY`** on tenant-context |
8
+ | **`https://app.crypticpqc.com`** | Vael-client — React SPA (Auth0 + Google connection) | Auth0 only at **login**; after callback, pqc-db **mints HS256 access JWTs** (`portal_token` cookie, etc.). **API calls must use that pqc-db JWT**, not a raw Auth0 API access token (see below). |
9
+ | **`https://mcp.crypticpqc.com`** | pqc-memory-mcp — Streamable HTTP MCP for Claude / tools | No end-user login UI; **`Authorization: Bearer`** from client; **`PQC_TENANT_RESOLUTION_URL`** → crypticpqc.com; optional RFC 9728 metadata |
10
+ | **`https://crypto.crypticpqc.com`** | Key manager — keys, policies, crypto operations | **Only pqc-db calls this service** (browser/MCP do not). User identity stops at pqc-db; pqc-db uses **service-to-service** credentials to crypto. |
11
+
12
+ **Naming note:** Production API hostname may be `crypticpqc.com` or `api.crypticpqc.com`; pick **one canonical public API origin** and use it everywhere env vars say “pqc-api origin.”
13
+
14
+ ---
15
+
16
+ ## Ground truth: pqc-db (CrypticPQDB) today
17
+
18
+ This matches the current **portal + JWT** implementation:
19
+
20
+ 1. **Auth0 is used only on the login / OAuth callback path**
21
+ pqc-db verifies the **Auth0 ID token** (`VerifyIDToken`), then **mints pqc-db tokens** (`GenerateTokenPair`) and sets e.g. **`portal_token`** (access JWT in an HTTP-only cookie).
22
+
23
+ 2. **Every normal protected route (including portal middleware) expects a pqc-db access JWT**
24
+ `JWTService.ValidateAccessToken` parses **HMAC-SHA256** tokens signed with the app secret and checks custom claims (`uid`, `oid`, `type: access`, etc.). Anything that is not that format (including a raw **Auth0-issued API access token** in `Authorization: Bearer`) **will not validate**.
25
+
26
+ 3. **Implication**
27
+ “Shared auth” across SPA and API means: **same login story (Auth0 once)**, **same ongoing credential (pqc-db JWT)**. It does **not** mean “send Auth0’s access token to pqc-db on every request” in the current code.
28
+
29
+ ---
30
+
31
+ ## Why Auth0 for login, pqc-db JWT after
32
+
33
+ Auth0 is an **identity provider** (OIDC): it proves a human completed login and gives a stable **`sub`** (and profile). It is not your **application session layer**. pqc-db was built around the latter first.
34
+
35
+ - **One credential shape on the hot path** — Portal, REST, `tenant-context`, and shared middleware expect the same **claims** (`uid`, `oid`, role, `type: access`). Treating Auth0 access tokens as `Bearer` on every route would add a **parallel** stack (JWKS, issuer/audience rules, claim mapping, opaque-token edge cases) across handlers.
36
+ - **Tenancy lives in the database** — Resolve “this IdP user → this `users` / org row” **once** at callback, then mint a JWT that already carries **org and role** the API trusts, instead of re-deriving IdP claims on every request.
37
+ - **Operations** — **HS256** with a local secret is cheap, deterministic, and does not tie routine API traffic to Auth0 availability or JWKS cache behavior.
38
+ - **Evolution** — Add or change IdPs (Auth0, another OIDC, password) at login without rewriting downstream auth: services still only accept **pqc-db JWT** (and API keys).
39
+ - **Claude MCP** — The connector sends **Auth0’s** access token into paths that expect **pqc-db** JWT; the A/B/C-style fixes close that **boundary** without a wholesale “Auth0 for all APIs” rewrite.
40
+
41
+ ---
42
+
43
+ ## Design principles (updated)
44
+
45
+ 1. **One Auth0 tenant for human login; multiple Auth0 applications if needed**
46
+ SPA: PKCE. Claude MCP connector (if used): separate app + secret, callback `https://claude.ai/api/mcp/auth_callback`. **`PQC_MCP_AUTHORIZATION_SERVERS`** = Auth0 issuer URL so Claude can complete OAuth — **but** see the MCP gap below.
47
+
48
+ 2. **One token family pqc-db validates on API routes today: HS256 pqc-db access JWT**
49
+ To support Claude without changing validation, you need **either** a **new code path** on `tenant-context` (or a small **exchange** endpoint) that turns an Auth0 access token into trust **or** mints a pqc-db JWT for MCP. **Do not** assume Claude’s Auth0 access token works against `ValidateAccessToken` as deployed.
50
+
51
+ 3. **MCP never stores long-lived user secrets**
52
+ MCP resolves **Bearer →** `tenant-context` **→** memory **`api_key`** + `bucket_id` (see [pqc-api-tenant-context-go.md](./pqc-api-tenant-context-go.md)). That Bearer must be something **tenant-context** accepts.
53
+
54
+ 4. **Key manager: backend-only from pqc-db**
55
+ End users authenticate to **pqc-db** (via Auth0 bootstrap + pqc-db JWT). **Only pqc-db** calls **`crypto.crypticpqc.com`**; lock that edge with mTLS, private network, or a service API key.
56
+
57
+ ---
58
+
59
+ ## High-level flows
60
+
61
+ ### A. Human in the portal (SPA)
62
+
63
+ ```mermaid
64
+ sequenceDiagram
65
+ participant U as User
66
+ participant App as app.crypticpqc.com
67
+ participant A0 as Auth0
68
+ participant API as crypticpqc.com
69
+
70
+ U->>App: Open app
71
+ App->>A0: OIDC authorize (PKCE)
72
+ A0->>U: Google / login
73
+ A0->>App: Auth0 tokens (callback to pqc-db)
74
+ App->>API: Login callback / session establishment
75
+ API->>A0: Verify ID token mint pqc-db JWT
76
+ API-->>App: Set-Cookie portal_token (pqc-db access JWT)
77
+ App->>API: API calls Cookie and/or Authorization Bearer pqc-db JWT
78
+ API->>API: JWTService.ValidateAccessToken (HS256 only)
79
+ ```
80
+
81
+ ### B. Claude remote MCP (**gap vs current pqc-db**)
82
+
83
+ ```mermaid
84
+ sequenceDiagram
85
+ participant C as Claude.ai
86
+ participant A0 as Auth0
87
+ participant MCP as mcp.crypticpqc.com
88
+ participant API as crypticpqc.com
89
+
90
+ C->>MCP: GET /.well-known/oauth-protected-resource/mcp
91
+ MCP-->>C: authorization_servers, resource
92
+ C->>A0: OAuth (connector client + secret)
93
+ A0-->>C: Auth0 API access_token
94
+ C->>MCP: POST /mcp Authorization Bearer (Auth0 token)
95
+ MCP->>API: GET /api/v1/mcp/tenant-context Bearer (same)
96
+ Note over API: Today: ValidateAccessToken expects pqc-db HS256 JWT — Auth0 token fails
97
+ ```
98
+
99
+ **Ways to close the gap (pick one):**
100
+
101
+ | Approach | Idea |
102
+ |----------|------|
103
+ | **A. Dual validation on `tenant-context` only** | If Bearer looks like Auth0 (e.g. `iss` + RS256), validate via JWKS and map `sub` → user/org; else existing `ValidateAccessToken`. |
104
+ | **B. Token exchange endpoint** | e.g. `POST /api/v1/auth/mcp-exchange`: body or `Authorization: Bearer <auth0>`, response = pqc-db access JWT; MCP calls exchange first (requires product decision). |
105
+ | **C. Claude-only mint** | Highly constrained endpoint that issues short-lived pqc-db JWT after Auth0 proof (similar to B). |
106
+
107
+ Until one of these exists, **OAuth metadata + Auth0 on Claude** alone is not enough for a working tenant-context.
108
+
109
+ ### C. Key manager (only pqc-db calls crypto)
110
+
111
+ ```mermaid
112
+ flowchart LR
113
+ subgraph users [Users]
114
+ U[Browser / Claude]
115
+ end
116
+ subgraph api [pqc-db]
117
+ PQC[crypticpqc.com]
118
+ end
119
+ subgraph km [Key manager]
120
+ CRY[crypto.crypticpqc.com]
121
+ end
122
+
123
+ U -->|Auth0 then pqc-db JWT| PQC
124
+ PQC -->|service auth only| CRY
125
+ ```
126
+
127
+ **Implementation checklist for crypto:**
128
+
129
+ - [ ] No public “log in to crypto” required if UI never talks to crypto directly.
130
+ - [ ] **mTLS**, private network, or **service API key** on pqc-db → crypto.
131
+ - [ ] Authorize key operations inside **pqc-db** (org, role, policy) before calling crypto.
132
+
133
+ ---
134
+
135
+ ## Planned implementation: pqc-db (CrypticPQDB)
136
+
137
+ The **Claude Auth0 application** (client ID + secret) lives in **Auth0** and **Claude’s connector Advanced settings** only. For a standard **JWT + JWKS** verify path, **pqc-db does not need the Claude client secret**; it verifies **user access tokens** with **JWKS**, using **`iss`** (Auth0 domain / issuer URL) and **`aud`** (the **Auth0 API identifier** those tokens are minted for—not the OAuth client ID).
138
+
139
+ **Core change (choose one):**
140
+
141
+ 1. **`POST /api/v1/auth/mcp-exchange` (recommended)** — Verify Auth0 access JWT (JWKS + `iss` / `aud`), resolve user, **`GenerateTokenPair`**, return pqc-db **`access_token`** (and optional refresh) like portal login. **pqc-mcp-server** can then call exchange once, then **`GET /api/v1/mcp/tenant-context`** with the **pqc-db** Bearer.
142
+
143
+ 2. **JWKS branch on `GET /api/v1/mcp/tenant-context` only** — Same verify + user resolve, then existing tenant resolver; MCP keeps a single hop to `tenant-context` with Auth0 Bearer.
144
+
145
+ **Supporting work in pqc-db:**
146
+
147
+ - **Config / env:** e.g. `AUTH0_DOMAIN` (or issuer URL), **`AUTH0_AUDIENCE`** (or `AUTH0_MCP_AUDIENCE` if MCP uses a different Auth0 API than the portal). Document in `env.example`.
148
+ - **User mapping:** Prefer **`auth0_sub`** (or equivalent) on **`users`**, set from the **portal** Auth0 callback on login; exchange / tenant-context resolves by **`sub`**. Avoid email-only mapping.
149
+ - **Hardening:** Optional **`MCP_TENANT_SERVICE_API_KEY`** on **`mcp-exchange`** (mirror `tenant-context`) so the mint endpoint is not a public oracle.
150
+ - **Auth0 API / audience:** Claude’s app must receive tokens whose **`aud`** matches what pqc-db validates (same or dedicated Auth0 API—env must match).
151
+ - **Tests and docs** for the new path.
152
+
153
+ **Follow-up in pqc-mcp-server (if exchange):** teach HTTP MCP to detect Auth0 Bearer vs pqc-db JWT, call **`mcp-exchange`**, cache **pqc-db** JWT for TTL, then call **`tenant-context`** (or call **`tenant-context`** only with the minted token).
154
+
155
+ **Implementation plan (working copy):** `local/mcp_auth0_exchange_8d491625.plan.md` (under `local/`, gitignored — copy or sync important deltas into committed docs). **Committed API contract** for MCP consumers: [mcp-auth0-exchange-contract.md](./mcp-auth0-exchange-contract.md).
156
+
157
+ ---
158
+
159
+ ## Environment alignment (MCP server)
160
+
161
+ | Variable | Points to |
162
+ |----------|-----------|
163
+ | `PQC_TENANT_RESOLUTION_URL` | **crypticpqc.com** (pqc-api **origin** only) |
164
+ | `PQC_MCP_PUBLIC_URL` | **https://mcp.crypticpqc.com** |
165
+ | `PQC_MCP_AUTHORIZATION_SERVERS` | **Auth0 issuer** URL(s), comma-separated if ever multiple |
166
+ | `PQC_MCP_ALLOWED_HOSTS` | `mcp.crypticpqc.com` (if you enable host allowlisting) |
167
+
168
+ See also: [claude-remote-oauth.md](./claude-remote-oauth.md), [pqc-db-mcp-tenant-context.md](./pqc-db-mcp-tenant-context.md).
169
+
170
+ ---
171
+
172
+ ## CORS and cookies
173
+
174
+ - **SPA** on `app.crypticpqc.com` calling **API** on `crypticpqc.com`: allow origin in API CORS; prefer **Authorization header** over cross-site cookies for API calls unless you intentionally use **SameSite=None; Secure** first-party cookie patterns.
175
+ - **MCP** is server-to-server from Claude’s infrastructure: CORS is irrelevant for `POST /mcp`; focus on **TLS**, **Host** allowlist, and **token validation**.
176
+
177
+ ---
178
+
179
+ ## Checklist — “shared auth layer done”
180
+
181
+ 1. [ ] **Canonical API base URL** documented and used in SPA, MCP (`PQC_TENANT_RESOLUTION_URL`), and runbooks.
182
+ 2. [ ] **SPA:** After Auth0 login, all `/api/v1/...` calls use **pqc-db access JWT** (cookie and/or `Authorization`), not raw Auth0 access tokens.
183
+ 3. [ ] **Claude MCP:** Chosen strategy **A / B / C** implemented on pqc-db so `tenant-context` succeeds with the Bearer Claude sends.
184
+ 4. [ ] **MCP:** `/.well-known/oauth-protected-resource/mcp` returns **200 JSON** in production (still needed for Claude OAuth discovery).
185
+ 5. [ ] **crypto.crypticpqc.com:** reachable only from pqc-db with service auth; policies enforced in pqc-db before calling crypto.
186
+
187
+ ---
188
+
189
+ ## Related repos
190
+
191
+ - **pqc-mcp-server** (this repo) — HTTP MCP + tenant fetch.
192
+ - **pqc-db / CrypticPQDB** — `tenant-context`, JWT validation, buckets.
193
+ - **vael-client** — SPA on `app.crypticpqc.com`.
194
+ - **Key manager** — implement and link OpenAPI / auth doc here when available.
@@ -0,0 +1,75 @@
1
+ # pqc-db: `POST /api/v1/auth/mcp-exchange` (contract)
2
+
3
+ This describes the **pqc-db** endpoint planned for Claude remote MCP. Source plan (local, may be gitignored): `local/mcp_auth0_exchange_8d491625.plan.md`. **pqc-mcp-server** should call this, then `GET /api/v1/mcp/tenant-context` with the minted **pqc-db access JWT**.
4
+
5
+ ## Request
6
+
7
+ - **Method / path:** `POST /api/v1/auth/mcp-exchange`
8
+ - **Headers:**
9
+ - `Authorization: Bearer <Auth0 access JWT>` (required)
10
+ - `X-API-Key: <shared secret>` — when pqc-db **`MCP_TENANT_SERVICE_API_KEY`** (or equivalent) is set; same semantics as `GET /api/v1/mcp/tenant-context` and MCP **`PQC_MCP_SERVICE_API_KEY`**.
11
+
12
+ ## Response (success)
13
+
14
+ - **200** `Content-Type: application/json`
15
+ - Body shape (aligned with login / `TokenPair`): recommended
16
+
17
+ ```json
18
+ {
19
+ "tokens": {
20
+ "access_token": "<pqc-db HS256 JWT>",
21
+ "refresh_token": "<optional>",
22
+ "expires_at": "2026-03-21T12:00:00Z",
23
+ "token_type": "Bearer"
24
+ }
25
+ }
26
+ ```
27
+
28
+ `expires_at` may be an **RFC3339 string** or a numeric epoch (seconds or ms); **pqc-mcp-server** accepts both. Exact field names should match pqc-db **`AuthResponse`** / OpenAPI.
29
+
30
+ ## Errors (intended mapping)
31
+
32
+ | HTTP | When |
33
+ |------|------|
34
+ | **400** | Token malformed / not a JWT (e.g. opaque token) |
35
+ | **401** | Invalid or expired Auth0 JWT after JWKS verify |
36
+ | **403** | User inactive |
37
+ | **404** | No `users` row for `auth0_sub` (`not_linked` style — user must complete portal Auth0 login once) |
38
+ | **503** / **501** | Exchange not configured (`AUTH0_DOMAIN` / `AUTH0_MCP_AUDIENCE` unset) — if implemented |
39
+
40
+ ## pqc-db prerequisites
41
+
42
+ - **`users.auth0_sub`** populated from portal Auth0 callback (`userInfo.Sub`) on login / invitation complete.
43
+ - **JWKS verify** with **`iss`** (Auth0 issuer) and **`aud`** = Auth0 **API identifier** (not OAuth client id). Env e.g. `AUTH0_DOMAIN`, `AUTH0_MCP_AUDIENCE`.
44
+ - Access token must be a **JWT** (three dot-separated segments); opaque tokens need a different design.
45
+
46
+ ### Opaque token from Claude (`not_jwt_opaque_unsupported`)
47
+
48
+ **pqc-db `AUTH0_MCP_AUDIENCE` only affects validation after you already have a JWT.** Opaque access tokens usually mean Auth0 did **not** mint an API JWT—often because the **authorize/token request never targeted your API**.
49
+
50
+ Common fix: **align PRM `resource` with Auth0’s API Identifier** (the `audience` value). This server defaults PRM `resource` to `{PQC_MCP_PUBLIC_URL}/mcp`. If your Auth0 API Identifier is **`https://mcp.example.com`** (no `/mcp`) and the MCP client passes that string as `audience`, Auth0 won’t match an API registered as **`https://mcp.example.com/mcp`**, and you can get an opaque token.
51
+
52
+ **Options:** (1) Set **`PQC_MCP_PRM_RESOURCE`** on MCP to exactly match **`AUTH0_MCP_AUDIENCE`** / Auth0 API Identifier, **or** (2) Change the Auth0 API Identifier to match PRM’s `resource`, **or** (3) Confirm in **Auth0 → Monitoring → Logs** that successful token exchanges include `audience=` for your API.
53
+
54
+ ## pqc-mcp-server behavior (implemented)
55
+
56
+ Set **`PQC_MCP_AUTH0_EXCHANGE`**:
57
+
58
+ | Value | Behavior |
59
+ |-------|----------|
60
+ | *(unset)* / `0` / `false` / `off` | Bearer sent only to **`tenant-context`** (pqc-db access JWT only). |
61
+ | **`1`**, **`retry`**, **`true`** | Try **`tenant-context`** first; on **401**, if Bearer looks like a JWT, **POST mcp-exchange**, cache minted JWT until **`expires_at`**, retry **`tenant-context`**. |
62
+ | **`always`** | Always **POST mcp-exchange** first (JWT-shaped Bearer only), then **`tenant-context`** with minted JWT. |
63
+
64
+ **Claude + Auth0:** use **`always`** or **`retry`** in production. **`PQC_MCP_SERVICE_API_KEY`** is sent on both **mcp-exchange** and **tenant-context** when set.
65
+
66
+ Cached **`access_token`** is keyed by the **inbound** Auth0 token hash; **`TenantRuntimeCache`** still keys runtimes by the same inbound Bearer so sessions stay stable.
67
+
68
+ ## Security notes
69
+
70
+ - Token-minting endpoint: protect with **service key**, HTTPS, and document rate-limit intent for ops (see plan §7).
71
+
72
+ ## See also
73
+
74
+ - [crypticpqc-cross-domain-auth.md](./crypticpqc-cross-domain-auth.md) — full cross-domain narrative and planned implementation.
75
+ - [pqc-api-tenant-context-go.md](./pqc-api-tenant-context-go.md) — `tenant-context` wire format.
@@ -0,0 +1,29 @@
1
+ # MCP authorization spec vs pqc-memory-mcp (alignment notes)
2
+
3
+ References: [Claude — Building custom connectors via remote MCP servers](https://support.claude.com/en/articles/11503834-building-custom-connectors-via-remote-mcp-servers), [MCP authorization (2025-11-25)](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization), [TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk).
4
+
5
+ ## On track
6
+
7
+ | Area | Spec / product | This stack |
8
+ |------|----------------|------------|
9
+ | Transport | Streamable HTTP (SSE still mentioned but may deprecate) | `StreamableHTTPServerTransport` + `POST /mcp` |
10
+ | OAuth | Supported; DCR or static client id/secret | Auth0 + Claude Advanced client; external IdP |
11
+ | Callback | `https://claude.ai/api/mcp/auth_callback` (allowlist `https://claude.com/...` too if hardening) | Documented in connector setup |
12
+ | Token to MCP | `Authorization: Bearer` on MCP requests | `requireBearerAuth` + tenant verifier |
13
+ | No token passthrough to pqc-db memory API | Resource server must not forward client tokens to upstream as-if native | **Planned:** Auth0 token → **`mcp-exchange`** → pqc-db JWT → `tenant-context` / memory API (separate credential) |
14
+ | Resource metadata | RFC 9728; `authorization_servers` | `/.well-known/oauth-protected-resource/mcp` when env set |
15
+ | `resource` in OAuth (RFC 8707) | Clients MUST send `resource` for token binding | **Auth0** maps **`audience`** to the API Identifier string, not always the PRM `resource` path. If PRM says `https://host/mcp` but Auth0 API Identifier is `https://host`, some clients may request the wrong audience → **opaque** access tokens. Align via **`PQC_MCP_PRM_RESOURCE`** or change Auth0 API Identifier (see [mcp-auth0-exchange-contract.md](mcp-auth0-exchange-contract.md) opaque-token note). |
16
+
17
+ ## Gaps / follow-ups
18
+
19
+ 1. **PRM always on in prod** — Spec treats protected-resource metadata as part of the OAuth story. Missing `PQC_MCP_PUBLIC_URL` / `PQC_MCP_AUTHORIZATION_SERVERS` yields **404** on well-known; fix with env on Zeabur.
20
+
21
+ 2. **`401` + `WWW-Authenticate`** — Spec expects clients to handle `resource_metadata` (and optional `scope`) on unauthorized responses. Behavior depends on **`@modelcontextprotocol/sdk`** middleware; verify on upgrade that Claude’s discovery path still works.
22
+
23
+ 3. **Refresh tokens** — Claude docs say servers should support expiry/refresh. After **mcp-exchange**, align token TTL/refresh with portal `TokenPair` behavior so long sessions do not silently die.
24
+
25
+ 4. **SDK versioning** — Upstream [typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk): **v1.x** is current production line; **v2** is pre-alpha on `main`. This repo pins a **v1.x** `@modelcontextprotocol/sdk`; track release notes for auth/transport changes.
26
+
27
+ ## TypeScript SDK
28
+
29
+ Official implementation and examples: [github.com/modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk). Runnable Streamable HTTP examples live under `examples/`; auth-heavy flows are documented in SDK `docs/` and MCP spec.
@@ -0,0 +1,55 @@
1
+ # Org object storage — MCP ↔ pqc-db API
2
+
3
+ The MCP server talks to **pqc-db tenant API** only (`{apiUrl}/api/v1/...`). There is **no** direct MinIO/SDK path in this repo.
4
+
5
+ ## Authentication
6
+
7
+ Same as other non-session routes (e.g. code graph): **`X-API-Key`** from tenant context.
8
+
9
+ **Streamable HTTP:** the JWT that successfully resolved **`GET /api/v1/mcp/tenant-context`** is also sent as **`Authorization: Bearer`** on tenant API calls (`TenantContext.apiBearerToken` → `PqcClient`).
10
+
11
+ Memory **`X-Memory-Session`** is **not** used for these routes.
12
+
13
+ ## Endpoints
14
+
15
+ | Action | Method | Path |
16
+ |--------|--------|------|
17
+ | Upload (encrypted) | `POST` | `/api/v1/keys/managed/{key_id}/objects` |
18
+ | Download (decrypted) | `GET` | `/api/v1/keys/managed/{key_id}/objects/{object_id}` |
19
+ | List | `GET` | `/api/v1/objects` |
20
+ | Metadata | `GET` | `/api/v1/objects/{id}` |
21
+
22
+ ### Upload (`multipart/form-data`)
23
+
24
+ - **`file`** (required) — binary body
25
+ - **`bucket_id`** (required) — org **data** bucket UUID
26
+ - **`filename`** (optional) — display name override
27
+
28
+ Success is typically **201** with JSON including **`object_id`**.
29
+
30
+ ### Download
31
+
32
+ Response body: decrypted bytes (e.g. `application/octet-stream`). Use the **`key_id`** from upload or from list/metadata.
33
+
34
+ ### List
35
+
36
+ Optional query: **`bucket=<uuid>`**. Rows for encrypted objects may include **`key_id`**.
37
+
38
+ ### Metadata
39
+
40
+ May include **`download_hint`** for encrypted objects.
41
+
42
+ ## Not supported here
43
+
44
+ Legacy or portal-only paths such as **`/api/v1/objects/upload`**, **`/api/v1/objects/{id}/download`**, or dashboard upload URLs — MCP implements **only** the managed-key pair above.
45
+
46
+ ## MCP tools
47
+
48
+ | Tool | Maps to |
49
+ |------|---------|
50
+ | `object_upload` | multipart POST |
51
+ | `object_download` | GET bytes → base64 in tool result |
52
+ | `object_list` | GET `/objects` |
53
+ | `object_get` | GET `/objects/{id}` |
54
+
55
+ `object_upload` accepts **`file_base64`** (MCP-friendly); `object_download` returns **`file_base64`** plus size and content type. Large downloads are capped by **`max_bytes`** (default 10 MiB, max 50 MiB) to avoid accidental huge tool payloads.
@@ -0,0 +1,238 @@
1
+ # pqc-api: `GET /api/v1/mcp/tenant-context` (Go)
2
+
3
+ This is what **pqc-mcp-server** calls when `PQC_TENANT_RESOLUTION_URL` is set (HTTP MCP mode). Implement this on **pqc-api** so each user’s **Bearer** token resolves to memory credentials.
4
+
5
+ ## Aligned with pqc-api (deployed behavior)
6
+
7
+ | Topic | Detail |
8
+ |--------|--------|
9
+ | **MCP env** | `PQC_TENANT_RESOLUTION_URL` = **public API origin only** (no path), e.g. `https://crypticpqc.com` or `http://localhost:8081`. MCP appends `/api/v1/mcp/tenant-context`. |
10
+ | **Bearer** | **pqc-api access JWT** (e.g. from `POST /api/v1/auth/login` or portal), **HS256**, must match **`JWT_SECRET`** / **`JWT_ISSUER`** on pqc-api. |
11
+ | **After 200** | MCP uses **`api_url`**, **`api_key`**, **`bucket_id`** from JSON for all memory calls — not separate hard-coded `PQC_API_URL` in HTTP mode. |
12
+ | **Service key** | Optional pair: MCP **`PQC_MCP_SERVICE_API_KEY`** ↔ pqc-api **`MCP_TENANT_SERVICE_API_KEY`** (same value). Sent as **`X-API-Key`** on tenant-context **only**. If pqc-api enforces it, both must be set; otherwise leave both unset. |
13
+ | **TTL** | **`session_ttl_minutes`** in JSON (pqc-api often uses **1440**); MCP falls back to **`PQC_SESSION_TTL`** if omitted. |
14
+ | **Provisioning** | Org needs a **`data_buckets`** row with **`bucket_mode = memory`**, or tenant-context returns **404** with `not_provisioned` (or similar). |
15
+ | **pqc-api config** | **`PUBLIC_API_URL`** / **`BASE_URL`** (or equivalent) so returned **`api_url`** is correct for clients. |
16
+
17
+ **Auth0 vs this Bearer:** In the current CrypticPQDB stack, **Auth0 participates only in the OAuth callback** (ID token verification); ongoing API auth is **pqc-db HS256 access JWTs** via `JWTService.ValidateAccessToken`. If the MCP client sends an **Auth0-issued access token**, it will **not** pass that validator unless you add a **separate** code path on this handler (JWKS) or an **exchange** step. See [crypticpqc-cross-domain-auth.md](./crypticpqc-cross-domain-auth.md).
18
+
19
+ ## Wire contract (must match MCP client)
20
+
21
+ **Method / path:** `GET /api/v1/mcp/tenant-context`
22
+ **Base URL:** MCP sets `PQC_TENANT_RESOLUTION_URL` to the **origin** of pqc-api (scheme + host + port).
23
+
24
+ ### Request headers
25
+
26
+ | Header | Required | Notes |
27
+ |--------|----------|--------|
28
+ | `Authorization` | Yes | `Bearer <access_token>` — **pqc-api JWT** (HS256), same as other `/api/v1/...` authenticated calls |
29
+ | `X-API-Key` | No | Sent only if MCP has `PQC_MCP_SERVICE_API_KEY`; must match pqc-api **`MCP_TENANT_SERVICE_API_KEY`** when that env is set |
30
+
31
+ ### Success: `200 OK`
32
+
33
+ `Content-Type: application/json`
34
+
35
+ ```json
36
+ {
37
+ "api_url": "https://api.yourcompany.com",
38
+ "api_key": "pqc_xxxxxxxx",
39
+ "bucket_id": "550e8400-e29b-41d4-a716-446655440000",
40
+ "session_ttl_minutes": 1440
41
+ }
42
+ ```
43
+
44
+ | Field | Type | Required | Meaning |
45
+ |-------|------|----------|---------|
46
+ | `api_url` | string | Yes | Base URL the MCP will use for **all** memory calls (typically your public API root; **no** trailing slash) |
47
+ | `api_key` | string | Yes | Value MCP sends as `X-API-Key` on subsequent `POST /api/v1/memory/session/start`, etc. |
48
+ | `bucket_id` | string | Yes | UUID passed to `startSession` as `bucket_id` |
49
+ | `session_ttl_minutes` | number | No | Memory session TTL; MCP falls back to `PQC_SESSION_TTL` or 1440 |
50
+
51
+ ### Errors
52
+
53
+ Return JSON when possible so operators see a message in logs (`error` string is what the Node client prefers on non-2xx):
54
+
55
+ ```json
56
+ { "error": "invalid_token" }
57
+ ```
58
+
59
+ | HTTP | When |
60
+ |------|------|
61
+ | `401` | Missing `Authorization`, not `Bearer`, invalid/expired JWT, wrong **`MCP_TENANT_SERVICE_API_KEY`** (if enforced) |
62
+ | `403` | Token valid but user/org not allowed to use MCP / no bucket |
63
+ | `404` | Not provisioned — e.g. no **`data_buckets`** with **`bucket_mode = memory`**; body may include `"error":"not_provisioned"` |
64
+ | `500` | Database or internal failure (avoid leaking internals in `error` string to untrusted clients if this URL is internet-facing) |
65
+
66
+ ## Example: `curl` (local testing)
67
+
68
+ ```bash
69
+ # After you mint a real access token from your IdP:
70
+ curl -sS -D - \
71
+ -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
72
+ -H "X-API-Key: pqc_service_optional" \
73
+ "https://localhost:8081/api/v1/mcp/tenant-context"
74
+ ```
75
+
76
+ **Example success body:**
77
+
78
+ ```json
79
+ {
80
+ "api_url": "http://localhost:8081",
81
+ "api_key": "pqc_user_or_connector_key_abc",
82
+ "bucket_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
83
+ "session_ttl_minutes": 1440
84
+ }
85
+ ```
86
+
87
+ **Example error:**
88
+
89
+ ```http
90
+ HTTP/1.1 401 Unauthorized
91
+ Content-Type: application/json
92
+
93
+ {"error":"invalid or expired token"}
94
+ ```
95
+
96
+ ## Go types
97
+
98
+ ```go
99
+ // internal/mcp/types.go (or your layout)
100
+
101
+ type TenantContextResponse struct {
102
+ APIURL string `json:"api_url"`
103
+ APIKey string `json:"api_key"`
104
+ BucketID string `json:"bucket_id"`
105
+ SessionTTLMinutes *int `json:"session_ttl_minutes,omitempty"`
106
+ }
107
+
108
+ type ErrorResponse struct {
109
+ Error string `json:"error"`
110
+ }
111
+ ```
112
+
113
+ ## Go handler sketch (Gin)
114
+
115
+ ```go
116
+ package mcp
117
+
118
+ import (
119
+ "net/http"
120
+ "strings"
121
+
122
+ "github.com/gin-gonic/gin"
123
+ )
124
+
125
+ // Dependencies you inject: JWT validator, DB, config (public API base, optional service key).
126
+ func RegisterTenantContextRoute(r *gin.Engine, h *TenantContextHandler) {
127
+ // Adjust group prefix to match your existing /api/v1 router.
128
+ v1 := r.Group("/api/v1")
129
+ v1.GET("/mcp/tenant-context", h.Handle)
130
+ }
131
+
132
+ type TenantContextHandler struct {
133
+ // ExpectedBaseURL is what you return as api_url (e.g. cfg.PublicAPIURL).
134
+ ExpectedBaseURL string
135
+ // Optional: if non-empty, require X-API-Key to match (shared secret with MCP).
136
+ ServiceAPIKey string
137
+ // Resolver loads api_key + bucket_id for the authenticated subject.
138
+ Resolver TenantResolver
139
+ // ValidateAndParseBearer validates JWT (JWKS/introspection) and returns claims (sub, org, email).
140
+ Auth BearerAuth
141
+ }
142
+
143
+ type TenantResolver interface {
144
+ Resolve(ctx interface{}, subject string, orgID *string) (apiKey, bucketID string, ttl *int, err error)
145
+ }
146
+
147
+ type BearerAuth interface {
148
+ ValidateBearer(c *gin.Context, rawToken string) (subject string, orgID *string, err error)
149
+ }
150
+
151
+ func (h *TenantContextHandler) Handle(c *gin.Context) {
152
+ if h.ServiceAPIKey != "" && c.GetHeader("X-API-Key") != h.ServiceAPIKey {
153
+ c.JSON(http.StatusUnauthorized, ErrorResponse{Error: "unauthorized"})
154
+ return
155
+ }
156
+
157
+ authz := c.GetHeader("Authorization")
158
+ if !strings.HasPrefix(strings.ToLower(authz), "bearer ") {
159
+ c.JSON(http.StatusUnauthorized, ErrorResponse{Error: "missing bearer token"})
160
+ return
161
+ }
162
+ raw := strings.TrimSpace(authz[7:])
163
+
164
+ subject, orgID, err := h.Auth.ValidateBearer(c, raw)
165
+ if err != nil {
166
+ c.JSON(http.StatusUnauthorized, ErrorResponse{Error: "invalid or expired token"})
167
+ return
168
+ }
169
+
170
+ apiKey, bucketID, ttl, err := h.Resolver.Resolve(c, subject, orgID)
171
+ if err != nil {
172
+ // map ErrNotProvisioned -> 404, ErrForbidden -> 403, etc.
173
+ c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "tenant lookup failed"})
174
+ return
175
+ }
176
+
177
+ resp := TenantContextResponse{
178
+ APIURL: strings.TrimRight(h.ExpectedBaseURL, "/"),
179
+ APIKey: apiKey,
180
+ BucketID: bucketID,
181
+ }
182
+ if ttl != nil {
183
+ resp.SessionTTLMinutes = ttl
184
+ }
185
+ c.JSON(http.StatusOK, resp)
186
+ }
187
+ ```
188
+
189
+ You would replace the generic `Resolve` / `ValidateBearer` with your real packages (existing user table, org RLS, API key issuance).
190
+
191
+ ## JWT validation (what to implement inside `ValidateBearer`)
192
+
193
+ Typical steps:
194
+
195
+ 1. Parse `Authorization` bearer string.
196
+ 2. Verify signature using your IdP’s **JWKS** (issuer from token `iss`, audience `aud` must match what your OAuth client uses).
197
+ 3. Reject if `exp` in the past.
198
+ 4. Return stable **`sub`** (and optional `org_id` from a custom claim you control).
199
+
200
+ Libraries commonly used:
201
+
202
+ - `github.com/golang-jwt/jwt/v5` + fetch JWKS from `https://<issuer>/.well-known/jwks.json`, or
203
+ - `github.com/lestrrat-go/jwx/v2/jwk` + `jwt.Parse`
204
+
205
+ **Do not** log the raw bearer or the full JWT in production.
206
+
207
+ ## Resolver (what to implement inside `Resolve`)
208
+
209
+ 1. Look up user by `sub` (or email claim) in your DB.
210
+ 2. Ensure they belong to an **org** with a **memory bucket** (your existing `bucket` model).
211
+ 3. Return an **API key** that:
212
+ - is allowed to call `POST /api/v1/memory/session/start` with that `bucket_id`, and
213
+ - is scoped so it cannot access other orgs’ buckets (your existing RLS / middleware).
214
+
215
+ If the user exists but has no bucket yet, return **404** and a JSON `error` like `"not_provisioned"` so you can distinguish from bad tokens.
216
+
217
+ ## Alignment with MCP env
218
+
219
+ | MCP env | Role |
220
+ |---------|------|
221
+ | `PQC_TENANT_RESOLUTION_URL` | Must be the base URL where this handler lives (`GET .../api/v1/mcp/tenant-context`) |
222
+ | `PQC_MCP_SERVICE_API_KEY` | Optional; if set, MCP sends it as `X-API-Key` on every tenant-context request |
223
+
224
+ Returned `api_url` should match what memory routes already use (same host as `PQC_API_URL` in stdio mode), so the MCP’s `PqcClient` hits the correct deployment.
225
+
226
+ ## Testing checklist
227
+
228
+ 1. **401:** no header, malformed bearer, expired JWT, wrong `aud`.
229
+ 2. **403:** valid JWT but user not entitled for MCP.
230
+ 3. **404:** valid JWT, user exists, no bucket / mapping.
231
+ 4. **200:** returns all three required fields; MCP can call `session/start` with returned `api_key` + `bucket_id`.
232
+ 5. **Service key:** with `PQC_MCP_SERVICE_API_KEY` set on MCP, handler rejects missing/wrong `X-API-Key`.
233
+
234
+ ## Security reminders
235
+
236
+ - Use **HTTPS** in production for this endpoint (bearer + optional service key).
237
+ - Rate-limit by IP and/or by `sub` after JWT parse to reduce brute force.
238
+ - Return **generic** `error` strings to anonymous clients; log detailed reasons server-side only.
@@ -0,0 +1,15 @@
1
+ # pqc-db / pqc-api: MCP tenant context API
2
+
3
+ The **HTTP** entrypoint in **pqc-mcp-server** resolves tenants by calling pqc-api:
4
+
5
+ `GET {PQC_TENANT_RESOLUTION_URL}/api/v1/mcp/tenant-context`
6
+
7
+ Use **public API origin only** for `PQC_TENANT_RESOLUTION_URL` (e.g. `https://crypticpqc.com`), no path.
8
+
9
+ ## Request / response
10
+
11
+ See **[pqc-api-tenant-context-go.md](pqc-api-tenant-context-go.md)** for headers, JSON body, errors, Go examples, and alignment with **JWT_SECRET** / **JWT_ISSUER**, **`MCP_TENANT_SERVICE_API_KEY`**, **`not_provisioned`**, and **`data_buckets` (`bucket_mode = memory`)**.
12
+
13
+ ## Legacy note
14
+
15
+ Older text referred generically to “OAuth / JWKS / IdP”. **As pqc-api is today**, the Bearer is typically the **pqc-api access JWT** (HS256). External IdPs must either issue compatible tokens or you add an exchange step on pqc-api before this handler runs.
@@ -0,0 +1,32 @@
1
+ # pqc-db: implementing `GET /api/v1/mcp/tenant-context`
2
+
3
+ This endpoint is **not** implemented in the `pqc-mcp-server` repo; it belongs in **pqc-db** (Go). The MCP HTTP server calls it on every Bearer-authenticated session to map the OAuth access token to memory tenant credentials.
4
+
5
+ ## Behavior
6
+
7
+ 1. Read `Authorization: Bearer <access_token>`.
8
+ 2. Validate the token (JWKS for your IdP, introspection, or shared secret per your security model).
9
+ 3. Resolve `sub` / org / email to an internal user and **bucket_id**.
10
+ 4. Load or mint an **API key** constrained to that bucket (or return an existing org-scoped key policy your team accepts).
11
+ 5. Return JSON:
12
+
13
+ ```json
14
+ {
15
+ "api_url": "https://api.yourdomain.com",
16
+ "api_key": "pqc_…",
17
+ "bucket_id": "uuid",
18
+ "session_ttl_minutes": 1440
19
+ }
20
+ ```
21
+
22
+ ## Suggested Go shape
23
+
24
+ - **Router**: Gin (or your existing router), e.g. `memory.GET("/mcp/tenant-context", handler)`.
25
+ - **Middleware**: Reuse existing API auth if this route is only called from MCP with end-user tokens; otherwise restrict via `X-API-Key` (`PQC_MCP_SERVICE_API_KEY`) plus Bearer.
26
+ - **RLS**: Ensure the resolved `bucket_id` belongs to the same org as the token claims.
27
+ - **Errors**: `401` invalid token, `403` no entitlement, `404` user not provisioned.
28
+
29
+ ## Tests
30
+
31
+ - Valid token → 200 and consistent bucket.
32
+ - Token for user A must never return user B’s `bucket_id` or `api_key`.