@tideorg/mcp 1.4.1 → 1.9.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 (91) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/GAP_REGISTER.md +19 -9
  3. package/PRIVACY.md +41 -0
  4. package/README.md +204 -120
  5. package/adapters/AGENTS.md +3 -2
  6. package/adapters/CLAUDE.md +1 -1
  7. package/adapters/replit.md +0 -0
  8. package/canon/anti-patterns.md +55 -62
  9. package/canon/concepts.md +46 -34
  10. package/canon/custom-contracts.md +12 -8
  11. package/canon/feature-mapping.md +27 -75
  12. package/canon/framework-matrix.md +69 -22
  13. package/canon/hosting-options.md +111 -0
  14. package/canon/iga-change-requests-api.md +211 -0
  15. package/canon/invariants.md +33 -35
  16. package/canon/redirect-handler.md +0 -0
  17. package/canon/security-gap-mapping.md +506 -0
  18. package/canon/security-runtime-probes.md +177 -0
  19. package/canon/tidecloak-bootstrap.md +84 -15
  20. package/canon/tidecloak-endpoints.md +60 -98
  21. package/canon/troubleshooting.md +201 -53
  22. package/canon/version-policy.md +8 -12
  23. package/mcp-server/dist/http.d.ts +2 -0
  24. package/mcp-server/dist/http.js +46 -0
  25. package/mcp-server/dist/index.d.ts +0 -0
  26. package/mcp-server/dist/index.js +2 -601
  27. package/mcp-server/dist/server.d.ts +2 -0
  28. package/mcp-server/dist/server.js +617 -0
  29. package/package.json +48 -45
  30. package/playbooks/add-auth-nextjs-existing.md +33 -17
  31. package/playbooks/add-auth-nextjs-fresh.md +1 -1
  32. package/playbooks/add-rbac-nextjs.md +7 -2
  33. package/playbooks/bootstrap-realm-from-template.md +33 -18
  34. package/playbooks/configure-e2ee-roles-and-policies.md +0 -0
  35. package/playbooks/deploy-tidecloak-docker.md +31 -28
  36. package/playbooks/diagnose-broken-login.md +0 -0
  37. package/playbooks/diagnose-missing-roles-or-claims.md +2 -2
  38. package/playbooks/initialize-admin-and-link-account.md +22 -19
  39. package/playbooks/migrate-from-existing-auth.md +0 -0
  40. package/playbooks/protect-api-nextjs.md +40 -1
  41. package/playbooks/protect-aspnet-core-asgard.md +313 -0
  42. package/playbooks/protect-routes-nextjs.md +24 -10
  43. package/playbooks/provision-tidecloak-skycloak.md +213 -0
  44. package/playbooks/setup-forseti-e2ee.md +32 -50
  45. package/playbooks/setup-iga-admin-panel.md +113 -171
  46. package/playbooks/start-tidecloak-dev.md +0 -0
  47. package/playbooks/verify-jwt-server-side.md +20 -1
  48. package/prompts/add-admin-approval-flow.md +0 -0
  49. package/prompts/build-private-customer-portal.md +1 -1
  50. package/prompts/migrate-generic-auth-to-tide.md +0 -0
  51. package/prompts/secure-existing-app.md +0 -0
  52. package/prompts/security-gap-analysis.md +55 -0
  53. package/reference-apps/INDEX.md +0 -0
  54. package/reference-apps/encrypted-communication/anti-patterns.md +3 -3
  55. package/reference-apps/encrypted-communication/bootstrap-sequence.md +2 -2
  56. package/reference-apps/encrypted-communication/manifest.yaml +0 -0
  57. package/reference-apps/encrypted-communication/role-policy-matrix.md +0 -0
  58. package/reference-apps/encrypted-communication/scenario.md +2 -2
  59. package/reference-apps/git-pr-signing-service/anti-patterns.md +0 -0
  60. package/reference-apps/git-pr-signing-service/bootstrap-sequence.md +8 -8
  61. package/reference-apps/git-pr-signing-service/manifest.yaml +0 -0
  62. package/reference-apps/git-pr-signing-service/role-policy-matrix.md +0 -0
  63. package/reference-apps/git-pr-signing-service/scenario.md +0 -0
  64. package/reference-apps/iga-admin-governance/anti-patterns.md +18 -16
  65. package/reference-apps/iga-admin-governance/bootstrap-sequence.md +10 -5
  66. package/reference-apps/iga-admin-governance/manifest.yaml +0 -0
  67. package/reference-apps/iga-admin-governance/role-policy-matrix.md +12 -13
  68. package/reference-apps/iga-admin-governance/scenario.md +15 -13
  69. package/reference-apps/organisation-password-manager/anti-patterns.md +0 -0
  70. package/reference-apps/organisation-password-manager/bootstrap-sequence.md +5 -6
  71. package/reference-apps/organisation-password-manager/manifest.yaml +0 -0
  72. package/reference-apps/organisation-password-manager/role-policy-matrix.md +0 -0
  73. package/reference-apps/organisation-password-manager/scenario.md +0 -0
  74. package/reference-apps/policy-governed-signing/anti-patterns.md +0 -0
  75. package/reference-apps/policy-governed-signing/bootstrap-sequence.md +9 -9
  76. package/reference-apps/policy-governed-signing/manifest.yaml +0 -0
  77. package/reference-apps/policy-governed-signing/role-policy-matrix.md +0 -0
  78. package/reference-apps/policy-governed-signing/scenario.md +0 -0
  79. package/skills/tide-diagnostics/SKILL.md +1 -1
  80. package/skills/tide-integration/SKILL.md +9 -18
  81. package/skills/tide-learning-capture/SKILL.md +0 -0
  82. package/skills/tide-mcp-qa/SKILL.md +139 -0
  83. package/skills/tide-rbac-and-e2ee/SKILL.md +5 -5
  84. package/skills/tide-reviewer/SKILL.md +1 -1
  85. package/skills/tide-route-and-api-protection/SKILL.md +0 -0
  86. package/skills/tide-scenario-resolver/SKILL.md +0 -0
  87. package/skills/tide-security-analyst/SKILL.md +182 -0
  88. package/skills/tide-setup/SKILL.md +1 -1
  89. package/skills/tide-solutions-architect/SKILL.md +0 -0
  90. package/canon/delegation.md +0 -195
  91. package/playbooks/setup-server-delegation.md +0 -142
@@ -147,7 +147,7 @@ For missing Tide adapter (Cause 5):
147
147
  ```bash
148
148
  # Re-export adapter with Tide extensions
149
149
  curl -X GET \
150
- "${TIDECLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_ID}/vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json" \
150
+ "${TIDECLOAK_URL}/admin/realms/${REALM}/vendorResources/get-installations-provider?clientId=${CLIENT_ID}&providerId=keycloak-oidc-keycloak-json" \
151
151
  -H "Authorization: Bearer ${ADMIN_TOKEN}" \
152
152
  > data/tidecloak.json
153
153
 
@@ -176,7 +176,7 @@ For missing `jwk` field (Cause 1):
176
176
  ```bash
177
177
  # Re-export adapter with Tide extensions (IGA must be enabled)
178
178
  curl -X GET \
179
- "${TIDECLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_ID}/vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json" \
179
+ "${TIDECLOAK_URL}/admin/realms/${REALM}/vendorResources/get-installations-provider?clientId=${CLIENT_ID}&providerId=keycloak-oidc-keycloak-json" \
180
180
  -H "Authorization: Bearer ${ADMIN_TOKEN}" \
181
181
  > data/tidecloak.json
182
182
 
@@ -279,10 +279,10 @@ if (!navigator.onLine) {
279
279
  ```javascript
280
280
  // Test encrypt/decrypt round-trip
281
281
  const plaintext = 'test data';
282
- const ciphertext = await iam.doEncrypt('test', plaintext);
282
+ const [ciphertext] = await iam.doEncrypt([{ data: plaintext, tags: ['test'] }]);
283
283
  console.log('Encrypted:', ciphertext); // Should complete within 2-5 seconds
284
284
 
285
- const decrypted = await iam.doDecrypt('test', ciphertext);
285
+ const [decrypted] = await iam.doDecrypt([{ encrypted: ciphertext, tags: ['test'] }]);
286
286
  console.log('Decrypted:', decrypted); // Should match plaintext
287
287
  assert(decrypted === plaintext);
288
288
  ```
@@ -474,15 +474,10 @@ curl -H "Authorization: DPoP $TOKEN" -H "DPoP: $DPOP" https://app.example.com/ap
474
474
  curl "${TIDECLOAK_URL}/admin/realms/${REALM}" | jq '.igaEnabled // "not enabled"'
475
475
  # Should output: true
476
476
 
477
- # 2. Check pending change-sets
478
- curl "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/change-set/all/requests" \
477
+ # 2. Check pending change requests (current /iga/ surface — see canon/iga-change-requests-api.md)
478
+ curl "${TIDECLOAK_URL}/admin/realms/${REALM}/iga/change-requests?status=PENDING" \
479
479
  -H "Authorization: Bearer ${ADMIN_TOKEN}" | jq '.'
480
- # Look for change-sets in 'PENDING' or 'APPROVED' status
481
-
482
- # 3. Check counts (lightweight)
483
- curl "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/change-set/counts" \
484
- -H "Authorization: Bearer ${ADMIN_TOKEN}"
485
- # Returns: {"users":N,"roles":N,"clients":N,"groups":N,"total":N}
480
+ # Objects keyed by .id, with .status, .readyToCommit
486
481
  ```
487
482
 
488
483
  **Fix**:
@@ -494,25 +489,20 @@ curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/toggle-iga" \
494
489
  -H "Authorization: Bearer ${ADMIN_TOKEN}"
495
490
  ```
496
491
 
497
- For change-set not approved (Cause 2):
492
+ For a change request not approved (Cause 2):
498
493
  ```bash
499
- # Sign (approve) the change-set
500
- curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/change-set/sign/batch" \
501
- -H "Authorization: Bearer ${ADMIN_TOKEN}" \
502
- -H "Content-Type: application/json" \
503
- -d '{"changeSets":[{"changeSetId":"DRAFT_RECORD_ID","changeSetType":"USER_ROLE","actionType":"CREATE"}]}'
504
-
505
- # If MultiAdmin: response has requiresApprovalPopup=true — complete enclave signing
506
- # Repeat with different admin tokens until quorum reached
494
+ # Authorize (approve) — CR_ID is the .id from the list above; body {} optional
495
+ curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/iga/change-requests/${CR_ID}/authorize" \
496
+ -H "Authorization: Bearer ${ADMIN_TOKEN}" -H "Content-Type: application/json" -d '{}'
497
+ # 409 = you already signed (four-eyes). Tide MultiAdmin: use the {id}/approval-model enclave exchange.
498
+ # Repeat with different admin tokens until threshold reached.
507
499
  ```
508
500
 
509
- For change-set not committed (Cause 3):
501
+ For a change request not committed (Cause 3):
510
502
  ```bash
511
- # Commit after quorum approval
512
- curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/tide-admin/change-set/commit/batch" \
513
- -H "Authorization: Bearer ${ADMIN_TOKEN}" \
514
- -H "Content-Type: application/json" \
515
- -d '{"changeSets":[{"changeSetId":"DRAFT_RECORD_ID","changeSetType":"USER_ROLE","actionType":"CREATE"}]}'
503
+ # Commit after threshold reached (readyToCommit==true); 412 if still short
504
+ curl -X POST "${TIDECLOAK_URL}/admin/realms/${REALM}/iga/change-requests/${CR_ID}/commit" \
505
+ -H "Authorization: Bearer ${ADMIN_TOKEN}"
516
506
  ```
517
507
 
518
508
  For token not refreshed (Cause 5):
@@ -873,10 +863,10 @@ curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/users?username=admin" \
873
863
  -H "Authorization: Bearer $TOKEN" | jq '.[0].id'
874
864
  # If null: user change request was not approved
875
865
 
876
- # Check pending user change requests
877
- curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/tide-admin/change-set/users/requests" \
866
+ # Check pending change requests (current /iga/ surface)
867
+ curl -s "$TIDECLOAK_URL/admin/realms/$REALM_NAME/iga/change-requests?status=PENDING" \
878
868
  -H "Authorization: Bearer $TOKEN" | jq length
879
- # If > 0: approve them first
869
+ # If > 0: approve them first (authorize + commit — see canon/iga-change-requests-api.md)
880
870
  ```
881
871
 
882
872
  **Fix**: Approve user creation change request between user creation and role assignment:
@@ -925,7 +915,7 @@ jq 'has("jwk") and has("vendorId") and has("homeOrkUrl")' data/tidecloak.json
925
915
 
926
916
  # 2. Check export endpoint used
927
917
  # Correct endpoint:
928
- # GET /admin/realms/{realm}/clients/{client-id}/vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json
918
+ # GET /admin/realms/{realm}/vendorResources/get-installations-provider?clientId={clientId}&providerId=keycloak-oidc-keycloak-json (realm-level; client is a query param)
929
919
  ```
930
920
 
931
921
  **Fix**:
@@ -933,7 +923,7 @@ jq 'has("jwk") and has("vendorId") and has("homeOrkUrl")' data/tidecloak.json
933
923
  ```bash
934
924
  # Use correct Tide adapter export endpoint
935
925
  curl -X GET \
936
- "${TIDECLOAK_URL}/admin/realms/${REALM}/clients/${CLIENT_ID}/vendorResources/get-installations-provider?providerId=keycloak-oidc-keycloak-json" \
926
+ "${TIDECLOAK_URL}/admin/realms/${REALM}/vendorResources/get-installations-provider?clientId=${CLIENT_ID}&providerId=keycloak-oidc-keycloak-json" \
937
927
  -H "Authorization: Bearer ${ADMIN_TOKEN}" \
938
928
  -o data/tidecloak.json
939
929
 
@@ -1217,8 +1207,8 @@ await tc.executeSignRequest(request, true);
1217
1207
  | `requiresApprovalPopup: true` | Backend cannot auto-approve, must open approval enclave in admin's browser |
1218
1208
  | `Cannot destructure property 'Policy' of 'Models' as it is undefined` | `Models` imported from `@tidecloak/nextjs` (wrong package). Import from `@tideorg/js` instead. See T-15. |
1219
1209
  | `IAMService.createTideRequest is not a function` | `createTideRequest` (and `requestTideOperatorApproval`, `executeSignRequest`) live on `IAMService._tc` (TideCloak), not `IAMService`. See T-16. |
1220
- | `Cannot destructure 'verifyTideCloakToken'` | `@tidecloak/verify` is CJS, use defensive interop pattern |
1221
- | "Could not create tide realm" (email is null) | `setUpTideRealm` expects `Content-Type: application/x-www-form-urlencoded` with `email`, not JSON |
1210
+ | `Cannot destructure 'verifyTideCloakToken'` | Very old `@tidecloak/verify` (single-export build); upgrade — current versions ship dual CJS/ESM exports and a plain named import works. Note arg order is `(config, token, allowedRoles?)`. |
1211
+ | "Tide realm setup failed" / "Could not set up the Tide realm" | `setUpTideRealm` expects `Content-Type: application/x-www-form-urlencoded` with `email` (`@FormParam`), not JSON. (There is no explicit email-null guard; a missing/blank email surfaces as a downstream setup failure.) |
1222
1212
  | "Could not find configuration for Required Action" | Missing Tide env vars (`SYSTEM_HOME_ORK`, `USER_HOME_ORK`, `THRESHOLD_T`, `THRESHOLD_N`, `PAYER_PUBLIC`) from Docker |
1223
1213
  | AccessDeniedException on H2 | Data directory mounted as project root or wrong permissions; mount `./data` subdirectory and `chown 1000:1000` |
1224
1214
  | `"User has not been given any access to '${tag}'"` | User lacks `_tide_{tag}.selfencrypt` or `.selfdecrypt` role for self-encryption. Fatal — assign role, then re-login. |
@@ -1250,9 +1240,9 @@ curl -s "$TIDECLOAK_URL/admin/realms/$REALM/roles/default-roles-$REALM" \
1250
1240
  -H "Authorization: Bearer $TOKEN" | jq '.composites.realm'
1251
1241
 
1252
1242
  # 3. Check IGA change requests are approved
1253
- curl -s "$TIDECLOAK_URL/admin/realms/$REALM/tide-admin/change-set/roles/requests" \
1243
+ curl -s "$TIDECLOAK_URL/admin/realms/$REALM/iga/change-requests?status=PENDING" \
1254
1244
  -H "Authorization: Bearer $TOKEN" | jq length
1255
- # Should be 0. If > 0, approve and commit.
1245
+ # Should be 0. If > 0, authorize and commit (canon/iga-change-requests-api.md).
1256
1246
 
1257
1247
  # 4. Check user's JWT has the roles
1258
1248
  # Decode token and check realm_access.roles
@@ -1279,12 +1269,15 @@ curl -s "$TIDECLOAK_URL/admin/realms/$REALM/tide-admin/change-set/roles/requests
1279
1269
 
1280
1270
  **Diagnostics**:
1281
1271
  ```bash
1282
- # 1. Check admin-policy endpoint is reachable
1283
- curl -s "${TIDECLOAK_URL}/realms/${REALM}/tide-policy-resources/admin-policy"
1284
- # Should return base64-encoded bytes, not HTML or error JSON
1272
+ # 1. Check the role-policies endpoint is reachable (admin bearer required)
1273
+ curl -s -H "Authorization: Bearer ${ADMIN_TOKEN}" \
1274
+ "${TIDECLOAK_URL}/admin/realms/${REALM}/iga/role-policies"
1275
+ # Should return a JSON array of records; each carries the signed bytes in its `policy` field.
1276
+ # NOTE: the old public GET /realms/{realm}/tide-policy-resources/admin-policy endpoint
1277
+ # is NOT present on current main; do not rely on it.
1285
1278
 
1286
1279
  # 2. Check URL construction (common: double slash from trailing slash in auth-server-url)
1287
- echo "${TIDECLOAK_URL}/realms/${REALM}/tide-policy-resources/admin-policy"
1280
+ echo "${TIDECLOAK_URL}/admin/realms/${REALM}/iga/role-policies"
1288
1281
  # Must not contain // between host and path (except after protocol)
1289
1282
 
1290
1283
  # 3. Check if addPolicy was called AFTER approval, not before
@@ -1294,15 +1287,18 @@ grep -B 5 "addPolicy" src/
1294
1287
 
1295
1288
  **Fix**:
1296
1289
 
1297
- 1. **Fetch admin policy from the correct endpoint** (no auth required):
1290
+ 1. **Fetch the signed admin policy from the correct endpoint** (server-side, admin bearer token):
1298
1291
  ```
1299
- GET /realms/{realm}/tide-policy-resources/admin-policy
1292
+ GET /admin/realms/{realm}/iga/role-policies
1300
1293
  ```
1301
- Not: `GET /admin/realms/{realm}/tide-admin/realm-policy` (returns `{ status: "none" }`).
1294
+ The signed policy bytes live in each record's `policy` field. Not: the public
1295
+ `GET /realms/{realm}/tide-policy-resources/admin-policy` endpoint (not on current main),
1296
+ nor `GET /admin/realms/{realm}/tide-admin/realm-policy` (returns `{ status: "none" }`).
1302
1297
 
1303
- 2. **Decode base64 response**:
1298
+ 2. **Decode the base64 `policy` field**:
1304
1299
  ```js
1305
- const b64 = await res.text();
1300
+ const records = await res.json();
1301
+ const b64 = records[0].policy; // signed bytes, base64-encoded
1306
1302
  const raw = Buffer.from(b64, "base64");
1307
1303
  const bytes = new Uint8Array(raw);
1308
1304
  ```
@@ -1318,7 +1314,7 @@ grep -B 5 "addPolicy" src/
1318
1314
  4. **URL normalization**: Strip trailing slashes from `auth-server-url` before constructing endpoint URLs:
1319
1315
  ```js
1320
1316
  const baseUrl = config["auth-server-url"].replace(/\/+$/, "");
1321
- const adminPolicyUrl = `${baseUrl}/realms/${config.realm}/tide-policy-resources/admin-policy`;
1317
+ const rolePoliciesUrl = `${baseUrl}/admin/realms/${config.realm}/iga/role-policies`;
1322
1318
  ```
1323
1319
 
1324
1320
  **This is a bootstrap/policy-flow failure, not a role issue.** Do not attempt to fix by renaming roles or switching encryption models.
@@ -1453,17 +1449,17 @@ VERIFIED (TIDE_LEARNINGS-001 L-05).
1453
1449
 
1454
1450
  ---
1455
1451
 
1456
- ## T-18: IGA Change-Set Approval May Require Admin Console UI
1452
+ ## T-18: IGA Change-Request Approval May Require Admin Console UI (Tide MultiAdmin)
1457
1453
 
1458
- **Symptom**: REST-based `POST /tide-admin/change-set/sign` succeeds but `POST /tide-admin/change-set/commit` fails with "Could not find authorization signature."
1454
+ **Symptom**: REST-based `POST /iga/change-requests/{id}/authorize` succeeds but `commit` fails (or the CR never reaches `readyToCommit`) with "Could not find authorization signature."
1459
1455
 
1460
- **Root cause**: IGA approval is a cryptographic operation requiring the admin's Tide doken and ORK network interaction. In some configurations, the REST-based sign endpoint auto-signs but the commit requires the browser-based SDK (`approveTideRequests()` via the admin console UI).
1456
+ **Root cause**: In Tide MultiAdmin mode, approval is a cryptographic operation requiring the admin's Tide doken and ORK interaction a bare `authorize` is not enough; the admin must complete the two-phase `{id}/approval-model` enclave exchange. FirstAdmin/Tideless mode signs server-side on `authorize`, so bootstrap works headless.
1461
1457
 
1462
- **When this happens**: Typically when the admin has a linked Tide identity and IGA requires the cryptographic round-trip. The bootstrap script's REST-based `approve_and_commit` may work for the initial bootstrap (master admin token) but fail for subsequent operations.
1458
+ **When this happens**: Typically once the realm is in Tide MultiAdmin mode (a `tide-realm-admin` exists and IGA requires the cryptographic round-trip). The bootstrap script's `approve_all_pending` works during initial FirstAdmin bootstrap but a headless `authorize` fails for subsequent MultiAdmin operations.
1463
1459
 
1464
- **Fix**: If REST commit fails, approve and commit change requests from the TideCloak admin console UI: `http://localhost:8080/admin/master/console/#/{realm}`. Navigate to the change request section and approve from the UI.
1460
+ **Fix**: Complete the enclave step `GET`/`POST /iga/change-requests/{id}/approval-model` with the SDK/enclave, or approve from the TideCloak admin console UI (`http://localhost:8080/admin/master/console/#/{realm}` Change Requests).
1465
1461
 
1466
- **Verification**: After approval, `GET /tide-admin/change-set/counts` returns `total: 0`.
1462
+ **Verification**: After approval, `GET /iga/change-requests?status=PENDING` returns `[]`.
1467
1463
 
1468
1464
  VERIFIED (TIDE_LEARNINGS-001 L-11).
1469
1465
 
@@ -1483,7 +1479,7 @@ VERIFIED (TIDE_LEARNINGS-001 L-11).
1483
1479
  5. Check TideCloak container logs: `docker logs <container>` for the real error
1484
1480
 
1485
1481
  **Common 500 causes**:
1486
- - ORK PreSign failure (`Provided TideAuthData OR PreviousTokenAuthorization`) stale realm, recreate it
1482
+ - ORK PreSign failure — on a stale realm, recreate it. Actual PreSign error strings on current ORK include: `"Tide user authentication data provided but no valid authentication method found within"` and `"DPoP thumbprint in requested token does not match with what the tide user approved"` (`ork: TidecloakSessionStartTokenSignRequest.cs`). (The older `Provided TideAuthData OR PreviousTokenAuthorization` string is no longer emitted.)
1487
1483
  - Database connection failure
1488
1484
  - Realm misconfiguration after incomplete bootstrap
1489
1485
 
@@ -1492,3 +1488,155 @@ VERIFIED (TIDE_LEARNINGS-001 L-11).
1492
1488
  **Verification**: After fixing the server error, the request returns 200 with proper CORS headers.
1493
1489
 
1494
1490
  VERIFIED (LEARNINGS-ratidefy-batch-001 L-15).
1491
+
1492
+ ---
1493
+
1494
+ ## T-20: Admin API Returns 401 Despite Valid Roles (Missing JWT kid)
1495
+
1496
+ **Symptom**: Token has all admin roles (`realm-admin`, `manage-users`, `view-users`, etc.), token is not expired, but admin REST API returns `{"error":"HTTP 401 Unauthorized"}`. TideCloak logs show `kid is null, cant find public key: realm={0}`.
1497
+
1498
+ **Possible Causes**:
1499
+ 1. JWT header has no `kid` field — realm's EdDSA signing key configuration is broken
1500
+ 2. Token signed with ORK threshold EdDSA but no `kid` reference for admin API to look up the verification key
1501
+
1502
+ **Diagnostics**:
1503
+
1504
+ ```bash
1505
+ # Decode the JWT header
1506
+ echo '<token>' | cut -d. -f1 | base64 -d 2>/dev/null | jq .
1507
+ # If output shows {"alg":"EdDSA","typ":"JWT"} with NO "kid" field — that's the problem
1508
+ ```
1509
+
1510
+ **Fix**: Recreate the TideCloak realm. The `kid` issue is a realm-level configuration problem that cannot be fixed on existing tokens. A new realm will issue tokens with proper `kid` in the JWT header.
1511
+
1512
+ **Verification**: After recreating the realm, decode a new token's header — it should include `"kid": "some-key-id"`.
1513
+
1514
+ VERIFIED (Ratidefy SaaS — ratidefy-sign realm had missing kid, recreated as ratidefy-sign-2).
1515
+
1516
+ ---
1517
+
1518
+ ## T-21: Enclave Error "Client origin could not be verified"
1519
+
1520
+ **Symptom**: Tide enclave popup appears and immediately closes. Browser console shows `[HEIMDALL] Recieved enclave error: Client origin could not be verified`.
1521
+
1522
+ **Possible Causes**:
1523
+ 1. `CustomAdminUIDomain` not set on Tide IDP
1524
+ 2. IDP settings not signed after setting `CustomAdminUIDomain`
1525
+ 3. `client-origin-auth` key in adapter config is malformed or missing the origin URL
1526
+
1527
+ **Diagnostics**:
1528
+
1529
+ ```bash
1530
+ # Check CustomAdminUIDomain
1531
+ TOKEN=$(curl -s -X POST "$TC_URL/realms/master/protocol/openid-connect/token" \
1532
+ -d "username=admin&password=$PASS&grant_type=password&client_id=admin-cli" | jq -r .access_token)
1533
+ curl -s "$TC_URL/admin/realms/$REALM/identity-provider/instances/tide" \
1534
+ -H "Authorization: Bearer $TOKEN" | jq '.config.CustomAdminUIDomain'
1535
+
1536
+ # Check adapter config for client-origin-auth key
1537
+ # The key MUST include the origin URL: "client-origin-auth-https://myapp.com"
1538
+ # NOT a hash/signature as the key suffix
1539
+ ```
1540
+
1541
+ **Fix**:
1542
+ 1. Set `CustomAdminUIDomain` to the app origin (e.g., `https://myapp.com`)
1543
+ 2. Sign the IDP settings: `POST /admin/realms/{realm}/vendorResources/sign-idp-settings`
1544
+ 3. Ensure adapter config `client-origin-auth` key includes the origin URL, not just the signature value
1545
+
1546
+ **Verification**: Enclave popup appears and stays open for user interaction.
1547
+
1548
+ VERIFIED (Ratidefy SaaS — IDP settings unsigned, client-origin-auth key had signature instead of origin URL).
1549
+
1550
+ ---
1551
+
1552
+ ## T-22: IGA Role Assignment Stuck in DRAFT (Unsignable Change Request)
1553
+
1554
+ **Symptom**: Role assigned to user via admin API, change request created, but sign/commit returns 200 without actually signing. Role remains in DRAFT state permanently.
1555
+
1556
+ **Possible Causes**:
1557
+ 1. Role assigned before the user linked their Tide account — IGA requires a Tide doken for change request signing
1558
+ 2. The master admin token can create change requests but cannot sign them without a Tide identity
1559
+
1560
+ **Diagnostics**:
1561
+ 1. Check TideCloak admin → realm → Users → target user → Role Mapping tab
1562
+ 2. If role shows "DRAFT" badge, the change request was never signed
1563
+ 3. Check if user has a federated identity (Identity Provider Links tab)
1564
+
1565
+ **Fix**:
1566
+ 1. Delete the DRAFT change request
1567
+ 2. Ensure the user links their Tide account first (via invite link)
1568
+ 3. Then assign the role — the change request can now be signed
1569
+
1570
+ **Prevention**: In automated provisioning, split into two phases:
1571
+ - Phase A: Create realm, user, generate invite link (no role assignment)
1572
+ - Phase B: After user links Tide account, assign role and approve change request
1573
+
1574
+ **Verification**: Role shows "ACTIVE" status in TideCloak admin after sign+commit.
1575
+
1576
+ VERIFIED (Ratidefy SaaS — provisioner assigned tide-realm-admin before account linking, creating permanent DRAFT).
1577
+
1578
+ ---
1579
+
1580
+ ## T-23: ASP.NET Core JWT Validation Fails on Tidecloak EdDSA Tokens
1581
+
1582
+ **Symptoms:**
1583
+ - `IDX10503: Signature validation failed`
1584
+ - `IDX10500: Signature validation failed. No security keys were provided`
1585
+ - 401 on every request despite a valid `Authorization: Bearer <token>`
1586
+
1587
+ **Cause:** `Microsoft.IdentityModel.Tokens` does not ship EdDSA. The `Tide.Asgard.AspNetCore.Authentication` SDK ships an EdDSA `SignatureProvider` in `Tide.Asgard.Core`, but you have to wire it in by setting `IssuerSigningKey = Utils.GetEd25519IssuerKey(builder.Configuration)`.
1588
+
1589
+ The asgard `Example/Program.cs` has that exact line **commented out** at line 17. Copying the Example verbatim leaves you with no EdDSA support; the README form at lines 117-139 is the correct wiring.
1590
+
1591
+ **Fix:**
1592
+ ```csharp
1593
+ builder.Services.AddKeycloakWebApiAuthentication(builder.Configuration, options =>
1594
+ {
1595
+ options.RequireHttpsMetadata = false;
1596
+ options.TokenValidationParameters.IssuerSigningKey =
1597
+ Utils.GetEd25519IssuerKey(builder.Configuration);
1598
+ });
1599
+ ```
1600
+
1601
+ **Verification:** Tokens with `alg=EdDSA` validate. Logs at Debug for `Keycloak.AuthServices` show the validation chain completing.
1602
+
1603
+ VERIFIED (asgard `README.md:117-139` vs `Example/Program.cs:17`).
1604
+
1605
+ See [playbooks/protect-aspnet-core-asgard.md](../playbooks/protect-aspnet-core-asgard.md).
1606
+
1607
+ ---
1608
+
1609
+ ## T-24: ASP.NET Core Token Exchange Throws NotImplementedException
1610
+
1611
+ **Symptoms:**
1612
+ - `ITokenExchangeService.ExchangeToken(...)` throws `NotImplementedException` from the asgard SDK.
1613
+ - Worked before; broke after enabling DPoP on the browser SPA.
1614
+
1615
+ **Cause:** `TokenExchangeService.ExchangeToken` inspects the inbound `Authorization` header. Bearer tokens are routed to a fully-implemented path. DPoP-tagged requests are routed to `ExchangeDPoPToken`, which is a stub (`throw new NotImplementedException()` at the top of the method). The same applies to the "Doken" auth scheme (`ExchangeTideDoken` stub). The internal `ValidateExchangeProof` helper is also a stub.
1616
+
1617
+ The most common trigger is uncommenting `useDPoP: { mode: "strict", alg: "EdDSA" }` in the SPA's `kc.init({...})` call. The moment the SPA starts sending `Authorization: DPoP <token>` + `DPoP: <proof>` headers, every controller that calls `ExchangeToken` 500s.
1618
+
1619
+ **Fix:** Disable browser DPoP until the SDK ships a DPoP-aware exchange path. The DPoP proof-validation service (`DPoPProofValidationService`) is fully implemented for **inbound** DPoP, but its registration helper `.WithDPoP(...)` is commented out — so DPoP today requires manual service wiring on inbound requests AND no DPoP on token exchange.
1620
+
1621
+ **Verification:** With `useDPoP` disabled, exchange succeeds. With DPoP on, the 500 is reproducible.
1622
+
1623
+ VERIFIED (asgard `TokenExchangeService.cs:28-35` routes to stub at line 93).
1624
+
1625
+ ---
1626
+
1627
+ ## T-25: ASP.NET Core 401 with Audience Mismatch
1628
+
1629
+ **Symptoms:**
1630
+ - 401 on every browser-issued request despite a valid token.
1631
+ - Token decodes correctly but `aud` claim does not include the backend client ID.
1632
+
1633
+ **Cause:** The browser's public client (`browser-login-page`) issues tokens with itself as the audience. The .NET backend's confidential client (`backend`) rejects tokens that do not list `backend` in `aud`.
1634
+
1635
+ **Fix:** Add an audience mapper to the browser client's dedicated client scope.
1636
+
1637
+ - Realm -> Clients -> `browser-login-page` -> Client scopes -> `browser-login-page-dedicated` -> Add mapper -> By configuration -> Audience.
1638
+ - Name: `backend-mapper`. Included Client Audience: `backend`. Save.
1639
+
1640
+ **Verification:** Decode an SPA-issued token (e.g. at jwt.io); confirm `aud` array contains `backend`.
1641
+
1642
+ VERIFIED (asgard `README.md:55-62`).
@@ -8,22 +8,20 @@ All other pack files point here. Do not duplicate version numbers in playbooks o
8
8
 
9
9
  ## Tide SDK Packages
10
10
 
11
- Resolved from npm registry on 2026-03-30. No 0.99.x versions are used.
11
+ Resolved from npm registry on 2026-07-07. npm is the release source of truth. Repo `package.json` version fields lag npm (e.g. `tidecloak-js` source on `main` reads 0.12.15); always confirm against npm, not the checked-out source tree.
12
12
 
13
13
  | Package | Stable version | Range for templates | Purpose |
14
14
  |---------|---------------|--------------------| --------|
15
- | `@tidecloak/js` | 0.13.26 | `0.13.26` | Core SDK (vanilla JS, IAMService) |
16
- | `@tidecloak/react` | 0.13.26 | `0.13.26` | React hooks and guard components |
17
- | `@tidecloak/nextjs` | 0.13.26 | `0.13.26` | Next.js provider and hooks |
18
- | `@tidecloak/verify` | 0.13.26 | `0.13.26` | Server-side JWT verification (CJS) |
19
- | `heimdall-tide` | 0.13.26 | `0.13.26` | Policy signing, BasicCustomRequest |
20
- | `@tideorg/js` | 0.13.26 | `0.13.26` | Models, Contracts (Forseti) |
21
- | `asgard-tide` | 0.13.26 | `0.13.26` | Signing request builders (`BasicCustomRequest`, `DynamicPayloadCustomRequest`) |
15
+ | `@tidecloak/js` | 0.13.33 | `0.13.33` | Core SDK (vanilla JS, IAMService) |
16
+ | `@tidecloak/react` | 0.13.33 | `0.13.33` | React hooks and guard components |
17
+ | `@tidecloak/nextjs` | 0.13.33 | `0.13.33` | Next.js provider and hooks |
18
+ | `@tidecloak/verify` | 0.13.33 | `0.13.33` | Server-side JWT verification (CJS) |
19
+ | `heimdall-tide` | 0.13.33 | `0.13.33` | Policy signing, BasicCustomRequest |
20
+ | `@tideorg/js` | 0.13.33 | `0.13.33` | Models, Contracts (Forseti). `tide-js` `main` package.json reads 0.13.32; npm latest is 0.13.33. |
21
+ | `asgard-tide` | 0.13.33 | `0.13.33` | Signing request builders (`BasicCustomRequest`, `DynamicPayloadCustomRequest`) |
22
22
 
23
23
  **Pin to exact version in templates.** Tide packages are pre-1.0. Minor bumps can break. Run `npm view <package> version` before installing to confirm the latest stable.
24
24
 
25
- **Exclude all 0.99.x versions.** If any `@tidecloak/*` package publishes a 0.99.x release, do not adopt it in pack guidance until it is confirmed stable.
26
-
27
25
  ---
28
26
 
29
27
  ## Framework Packages
@@ -75,7 +73,6 @@ npm view jose version
75
73
 
76
74
  - **Do not use `latest` tag in package.json.** Pin Tide packages to exact versions. Use caret ranges only for framework packages. This applies to all Tide packages: `@tidecloak/*`, `@tideorg/js`, `heimdall-tide`, `asgard-tide`.
77
75
  - **Do not use `-staging` npm tags.** Tags like `0.13.24-staging` are pre-release builds. Always use the stable version listed above. "Use staging" refers to the Docker image (`tideorg/tidecloak-stg-dev`), never to npm package tags. VERIFIED (LEARNINGS-batch-005 L-01, L-02).
78
- - **Do not use 0.99.x versions** of any `@tidecloak/*` package in templates or guidance.
79
76
  - **Do not assume 1.0.0.** All Tide packages are pre-1.0. Do not write `"@tidecloak/nextjs": "^1.0.0"`.
80
77
  - **Do not hardcode versions in playbook install snippets.** Playbooks should say `npm install @tidecloak/nextjs` (installs latest) and then instruct the builder to verify with `npm view`. Templates pin the version explicitly.
81
78
  - **Docker image and npm versions are independent.** Switching to the staging Docker image (`tidecloak-stg-dev`) does NOT mean switching to staging npm packages. npm packages stay at stable versions.
@@ -86,4 +83,3 @@ npm view jose version
86
83
 
87
84
  - When a new stable Tide SDK release is published.
88
85
  - When a framework major version ships that affects template compatibility.
89
- - When a 0.99.x Tide package appears and a decision is needed.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import express from "express";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { createServer } from "./server.js";
5
+ const app = express();
6
+ app.use(express.json());
7
+ // Optional Bearer token auth
8
+ const API_TOKEN = process.env.API_TOKEN;
9
+ if (API_TOKEN) {
10
+ app.use("/mcp", (req, res, next) => {
11
+ const auth = req.headers.authorization;
12
+ if (!auth || auth !== `Bearer ${API_TOKEN}`) {
13
+ res.status(401).json({ error: "Unauthorized" });
14
+ return;
15
+ }
16
+ next();
17
+ });
18
+ }
19
+ // MCP endpoint — stateless (new server + transport per request)
20
+ app.post("/mcp", async (req, res) => {
21
+ const server = createServer();
22
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
23
+ res.on("close", () => {
24
+ transport.close();
25
+ server.close();
26
+ });
27
+ await server.connect(transport);
28
+ await transport.handleRequest(req, res, req.body);
29
+ });
30
+ // Handle GET and DELETE for SSE streams (required by protocol)
31
+ app.get("/mcp", async (req, res) => {
32
+ res.status(405).json({ error: "Method not allowed. Use POST for stateless MCP." });
33
+ });
34
+ app.delete("/mcp", async (req, res) => {
35
+ res.status(405).json({ error: "Method not allowed. Stateless server — no sessions to delete." });
36
+ });
37
+ // Health check for Azure / load balancers
38
+ app.get("/health", (_, res) => {
39
+ res.status(200).json({ status: "ok", name: "@tideorg/mcp" });
40
+ });
41
+ const port = parseInt(process.env.PORT || "3000", 10);
42
+ app.listen(port, "0.0.0.0", () => {
43
+ console.log(`@tideorg/mcp HTTP server listening on port ${port}`);
44
+ console.log(`MCP endpoint: http://0.0.0.0:${port}/mcp`);
45
+ console.log(`Health check: http://0.0.0.0:${port}/health`);
46
+ });
File without changes