@try-works/dsh-model-as-tool 0.1.3 → 0.4.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 (260) hide show
  1. package/bin/dsh-tool-mode-credentials.mjs +20 -0
  2. package/bin/dsh-tool-mode.mjs +202 -0
  3. package/catalog/desert-ant-models.json +693 -693
  4. package/client/client.model-tools.js +614 -0
  5. package/client/index.js +1717 -0
  6. package/lib/adapters/index.d.ts.map +1 -1
  7. package/lib/adapters/index.js +4 -4
  8. package/lib/bindings.d.ts.map +1 -1
  9. package/lib/bindings.js +26 -25
  10. package/lib/capability.d.ts.map +1 -1
  11. package/lib/capability.js +17 -0
  12. package/lib/capture-path.d.ts +97 -0
  13. package/lib/capture-path.d.ts.map +1 -0
  14. package/lib/capture-path.js +152 -0
  15. package/lib/diagnostics.d.ts +166 -0
  16. package/lib/diagnostics.d.ts.map +1 -0
  17. package/lib/diagnostics.js +135 -0
  18. package/lib/extension-dependencies.d.ts +325 -0
  19. package/lib/extension-dependencies.d.ts.map +1 -0
  20. package/lib/extension-dependencies.js +858 -0
  21. package/lib/extension-dispatch.d.ts +117 -0
  22. package/lib/extension-dispatch.d.ts.map +1 -0
  23. package/lib/extension-dispatch.js +124 -0
  24. package/lib/extension-registrar.d.ts +100 -0
  25. package/lib/extension-registrar.d.ts.map +1 -0
  26. package/lib/extension-registrar.js +134 -0
  27. package/lib/extensions-api.d.ts +221 -0
  28. package/lib/extensions-api.d.ts.map +1 -0
  29. package/lib/extensions-api.js +17 -0
  30. package/lib/extensions-remote.d.ts +54 -0
  31. package/lib/extensions-remote.d.ts.map +1 -0
  32. package/lib/extensions-remote.js +66 -0
  33. package/lib/extensions-service.d.ts +367 -0
  34. package/lib/extensions-service.d.ts.map +1 -0
  35. package/lib/extensions-service.js +1527 -0
  36. package/lib/harness-llm.d.ts +138 -0
  37. package/lib/harness-llm.d.ts.map +1 -0
  38. package/lib/harness-llm.js +218 -0
  39. package/lib/harness-serving.d.ts +95 -0
  40. package/lib/harness-serving.d.ts.map +1 -0
  41. package/lib/harness-serving.js +157 -0
  42. package/lib/headless.d.ts +97 -0
  43. package/lib/headless.d.ts.map +1 -0
  44. package/lib/headless.js +349 -0
  45. package/lib/http-acquisition.d.ts +27 -0
  46. package/lib/http-acquisition.d.ts.map +1 -0
  47. package/lib/http-acquisition.js +136 -0
  48. package/lib/index.d.ts +271 -7
  49. package/lib/index.d.ts.map +1 -1
  50. package/lib/index.js +2644 -70
  51. package/lib/invocation-accounting.d.ts +169 -0
  52. package/lib/invocation-accounting.d.ts.map +1 -0
  53. package/lib/invocation-accounting.js +148 -0
  54. package/lib/legacy-migration.d.ts +95 -0
  55. package/lib/legacy-migration.d.ts.map +1 -0
  56. package/lib/legacy-migration.js +429 -0
  57. package/lib/managed-workers.d.ts +151 -0
  58. package/lib/managed-workers.d.ts.map +1 -0
  59. package/lib/managed-workers.js +249 -0
  60. package/lib/mcp/client-grant.d.ts +74 -0
  61. package/lib/mcp/client-grant.d.ts.map +1 -0
  62. package/lib/mcp/client-grant.js +60 -0
  63. package/lib/mcp/credential-cli.d.ts +38 -0
  64. package/lib/mcp/credential-cli.d.ts.map +1 -0
  65. package/lib/mcp/credential-cli.js +148 -0
  66. package/lib/mcp/credential-store.d.ts +143 -0
  67. package/lib/mcp/credential-store.d.ts.map +1 -0
  68. package/lib/mcp/credential-store.js +454 -0
  69. package/lib/mcp/host-binding.d.ts +25 -0
  70. package/lib/mcp/host-binding.d.ts.map +1 -1
  71. package/lib/mcp/host-binding.js +107 -14
  72. package/lib/mcp/http.d.ts +68 -18
  73. package/lib/mcp/http.d.ts.map +1 -1
  74. package/lib/mcp/http.js +390 -62
  75. package/lib/mcp/oauth.d.ts +149 -0
  76. package/lib/mcp/oauth.d.ts.map +1 -0
  77. package/lib/mcp/oauth.js +396 -0
  78. package/lib/mcp/protocol.d.ts +46 -7
  79. package/lib/mcp/protocol.d.ts.map +1 -1
  80. package/lib/mcp/protocol.js +63 -16
  81. package/lib/mcp/rate-limit.d.ts +68 -0
  82. package/lib/mcp/rate-limit.d.ts.map +1 -0
  83. package/lib/mcp/rate-limit.js +114 -0
  84. package/lib/mcp/sdk-v2.d.ts +17 -0
  85. package/lib/mcp/sdk-v2.d.ts.map +1 -0
  86. package/lib/mcp/sdk-v2.js +181 -0
  87. package/lib/mcp/server.d.ts +11 -1
  88. package/lib/mcp/server.d.ts.map +1 -1
  89. package/lib/mcp/server.js +80 -13
  90. package/lib/mcp/stdio.d.ts.map +1 -1
  91. package/lib/mcp/stdio.js +9 -1
  92. package/lib/mcp/subscriptions.d.ts +43 -1
  93. package/lib/mcp/subscriptions.d.ts.map +1 -1
  94. package/lib/mcp/subscriptions.js +90 -9
  95. package/lib/model-qualification.d.ts +192 -0
  96. package/lib/model-qualification.d.ts.map +1 -0
  97. package/lib/model-qualification.js +178 -0
  98. package/lib/planning.d.ts +246 -0
  99. package/lib/planning.d.ts.map +1 -0
  100. package/lib/planning.js +457 -0
  101. package/lib/prepared-assets.d.ts +178 -0
  102. package/lib/prepared-assets.d.ts.map +1 -0
  103. package/lib/prepared-assets.js +286 -0
  104. package/lib/provider-bridge.d.ts +37 -0
  105. package/lib/provider-bridge.d.ts.map +1 -1
  106. package/lib/provider-bridge.js +189 -12
  107. package/lib/ptc-authority.d.ts +140 -0
  108. package/lib/ptc-authority.d.ts.map +1 -0
  109. package/lib/ptc-authority.js +216 -0
  110. package/lib/ptc-runtime.d.ts +156 -0
  111. package/lib/ptc-runtime.d.ts.map +1 -0
  112. package/lib/ptc-runtime.js +191 -0
  113. package/lib/publication.d.ts +2 -1
  114. package/lib/publication.d.ts.map +1 -1
  115. package/lib/publication.js +13 -5
  116. package/lib/receipt.d.ts.map +1 -1
  117. package/lib/receipt.js +8 -0
  118. package/lib/title-proposal.d.ts +194 -0
  119. package/lib/title-proposal.d.ts.map +1 -0
  120. package/lib/title-proposal.js +244 -0
  121. package/lib/tool-mode-run.d.ts +235 -0
  122. package/lib/tool-mode-run.d.ts.map +1 -0
  123. package/lib/tool-mode-run.js +283 -0
  124. package/lib/types.d.ts +3 -1
  125. package/lib/types.d.ts.map +1 -1
  126. package/lib/update-surface.d.ts +79 -0
  127. package/lib/update-surface.d.ts.map +1 -0
  128. package/lib/update-surface.js +224 -0
  129. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts +38 -0
  130. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.d.ts.map +1 -0
  131. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/standard.js +445 -0
  132. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts +14 -0
  133. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.d.ts.map +1 -0
  134. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-embed.js +63 -0
  135. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts +14 -0
  136. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.d.ts.map +1 -0
  137. package/node_modules/@try-works/tool-mode-contracts/lib/contracts/text-rerank.js +76 -0
  138. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.d.ts.map +1 -1
  139. package/node_modules/@try-works/tool-mode-contracts/lib/dsh.js +69 -55
  140. package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts +4 -1
  141. package/node_modules/@try-works/tool-mode-contracts/lib/index.d.ts.map +1 -1
  142. package/node_modules/@try-works/tool-mode-contracts/lib/index.js +26 -2
  143. package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts +28 -2
  144. package/node_modules/@try-works/tool-mode-contracts/lib/registry.d.ts.map +1 -1
  145. package/node_modules/@try-works/tool-mode-contracts/lib/registry.js +93 -10
  146. package/node_modules/@try-works/tool-mode-contracts/package.json +0 -1
  147. package/node_modules/@try-works/tool-mode-extension-sdk/bin/dsh-tool-mode.mjs +20 -0
  148. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts +80 -0
  149. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.d.ts.map +1 -0
  150. package/node_modules/@try-works/tool-mode-extension-sdk/lib/cli.js +533 -0
  151. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts +83 -0
  152. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.d.ts.map +1 -0
  153. package/node_modules/@try-works/tool-mode-extension-sdk/lib/declarative-recipes.js +125 -0
  154. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts +70 -0
  155. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.d.ts.map +1 -0
  156. package/node_modules/@try-works/tool-mode-extension-sdk/lib/discovery.js +201 -0
  157. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts +57 -0
  158. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.d.ts.map +1 -0
  159. package/node_modules/@try-works/tool-mode-extension-sdk/lib/errors.js +83 -0
  160. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts +32 -0
  161. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.d.ts.map +1 -0
  162. package/node_modules/@try-works/tool-mode-extension-sdk/lib/index.js +32 -0
  163. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts +86 -0
  164. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.d.ts.map +1 -0
  165. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/archive.js +191 -0
  166. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts +126 -0
  167. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.d.ts.map +1 -0
  168. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/dependencies.js +297 -0
  169. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts +216 -0
  170. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.d.ts.map +1 -0
  171. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/lifecycle.js +800 -0
  172. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts +210 -0
  173. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.d.ts.map +1 -0
  174. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/migration.js +368 -0
  175. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts +63 -0
  176. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.d.ts.map +1 -0
  177. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/runtime-link.js +149 -0
  178. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts +35 -0
  179. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.d.ts.map +1 -0
  180. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/source-directory.js +179 -0
  181. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts +179 -0
  182. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.d.ts.map +1 -0
  183. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/staged.js +235 -0
  184. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts +367 -0
  185. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.d.ts.map +1 -0
  186. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/store.js +807 -0
  187. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.d.ts +44 -0
  188. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.d.ts.map +1 -0
  189. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/tar-gzip-source.js +243 -0
  190. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts +185 -0
  191. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.d.ts.map +1 -0
  192. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/update-flow.js +504 -0
  193. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts +17 -0
  194. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.d.ts.map +1 -0
  195. package/node_modules/@try-works/tool-mode-extension-sdk/lib/install/zip-source.js +80 -0
  196. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts +52 -0
  197. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.d.ts.map +1 -0
  198. package/node_modules/@try-works/tool-mode-extension-sdk/lib/load.js +102 -0
  199. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts +171 -0
  200. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.d.ts.map +1 -0
  201. package/node_modules/@try-works/tool-mode-extension-sdk/lib/manifest.js +271 -0
  202. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts +242 -0
  203. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.d.ts.map +1 -0
  204. package/node_modules/@try-works/tool-mode-extension-sdk/lib/runtime.js +58 -0
  205. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts +118 -0
  206. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.d.ts.map +1 -0
  207. package/node_modules/@try-works/tool-mode-extension-sdk/lib/state.js +113 -0
  208. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts +2 -0
  209. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.d.ts.map +1 -0
  210. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker/worker-child.js +351 -0
  211. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts +150 -0
  212. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.d.ts.map +1 -0
  213. package/node_modules/@try-works/tool-mode-extension-sdk/lib/worker.js +670 -0
  214. package/node_modules/@try-works/tool-mode-extension-sdk/package.json +53 -0
  215. package/node_modules/@try-works/tool-mode-extension-sdk/schema/extension-manifest-1.1.0.schema.json +253 -0
  216. package/node_modules/@try-works/tool-mode-recipes/bin/tool-mode-recipe.mjs +15 -0
  217. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts +427 -0
  218. package/node_modules/@try-works/tool-mode-recipes/lib/broker.d.ts.map +1 -0
  219. package/node_modules/@try-works/tool-mode-recipes/lib/broker.js +592 -0
  220. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts +60 -0
  221. package/node_modules/@try-works/tool-mode-recipes/lib/cli.d.ts.map +1 -0
  222. package/node_modules/@try-works/tool-mode-recipes/lib/cli.js +936 -0
  223. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts +237 -0
  224. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.d.ts.map +1 -0
  225. package/node_modules/@try-works/tool-mode-recipes/lib/dsh-adapter.js +200 -0
  226. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts +85 -0
  227. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.d.ts.map +1 -0
  228. package/node_modules/@try-works/tool-mode-recipes/lib/fixture-provider.js +176 -0
  229. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts +105 -0
  230. package/node_modules/@try-works/tool-mode-recipes/lib/images.d.ts.map +1 -0
  231. package/node_modules/@try-works/tool-mode-recipes/lib/images.js +129 -0
  232. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts +25 -0
  233. package/node_modules/@try-works/tool-mode-recipes/lib/index.d.ts.map +1 -0
  234. package/node_modules/@try-works/tool-mode-recipes/lib/index.js +25 -0
  235. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts +325 -0
  236. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.d.ts.map +1 -0
  237. package/node_modules/@try-works/tool-mode-recipes/lib/recipe-format.js +1310 -0
  238. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts +51 -0
  239. package/node_modules/@try-works/tool-mode-recipes/lib/templates.d.ts.map +1 -0
  240. package/node_modules/@try-works/tool-mode-recipes/lib/templates.js +310 -0
  241. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts +227 -0
  242. package/node_modules/@try-works/tool-mode-recipes/lib/titles.d.ts.map +1 -0
  243. package/node_modules/@try-works/tool-mode-recipes/lib/titles.js +407 -0
  244. package/node_modules/@try-works/tool-mode-recipes/package.json +56 -0
  245. package/node_modules/@try-works/tool-mode-recipes/recipes/describe-image.json +38 -0
  246. package/node_modules/@try-works/tool-mode-recipes/recipes/text-title.json +45 -0
  247. package/node_modules/@try-works/tool-mode-recipes/schema/harness-model-recipe.schema.json +273 -0
  248. package/package.json +43 -15
  249. package/remote/spec.mjs +462 -0
  250. package/remote/typert.host.mjs +14 -0
  251. package/remote/typert.remote-client.mjs +16 -0
  252. package/lib/adapters/tongue.d.ts +0 -31
  253. package/lib/adapters/tongue.d.ts.map +0 -1
  254. package/lib/adapters/tongue.js +0 -93
  255. package/lib/adapters/voz.d.ts +0 -28
  256. package/lib/adapters/voz.d.ts.map +0 -1
  257. package/lib/adapters/voz.js +0 -102
  258. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts +0 -0
  259. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.d.ts.map +0 -0
  260. /package/{lib → node_modules/@try-works/tool-mode-extension-sdk/lib}/licensing.js +0 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * The embedded LOCAL OAuth 2.1 authorization server + resource-server verifier
3
+ * (P26-15).
4
+ *
5
+ * P26-15 permits exactly two implementations: a tested authorization-server
6
+ * integration, or a properly implemented embedded/local server — and forbids
7
+ * inventing token cryptography. This module is the second: the signing and
8
+ * verification primitives are Node's OWN WebCrypto (`crypto.subtle` HMAC-SHA256),
9
+ * and only the JWT envelope around them is assembled here. No cipher, mode or
10
+ * hash is hand-rolled.
11
+ *
12
+ * The shape follows the parts of RFC 6749/6750/8414/9728 a resource server and a
13
+ * local issuer need:
14
+ *
15
+ * - HS256-signed JWTs with `iss`, `sub` (client id), `aud`, `scope`, `iat`, `exp`
16
+ * and a unique `jti`;
17
+ * - RFC 6750 challenges: `WWW-Authenticate: Bearer error="invalid_token"|...`
18
+ * with `scope="..."` on insufficient_scope;
19
+ * - per-client credentials with INDEPENDENT revocation (a revoked client's tokens
20
+ * fail while every other client keeps working);
21
+ * - RFC 8414 authorization-server metadata and RFC 9728 protected-resource
22
+ * metadata, so a client can discover issuer/audience/token endpoint.
23
+ *
24
+ * The static-token mode remains available as CUSTOM authentication and still
25
+ * claims nothing; this module is what a deployment turns on when it wants the
26
+ * OAuth profile, and `authProfile()` reports which mode is live.
27
+ *
28
+ * @module @try-works/dsh-model-as-tool/mcp/oauth
29
+ */
30
+ import type { ClientCredentialStore } from './credential-store.ts';
31
+ /** One registered client of the local authorization server. */
32
+ export interface OAuthClient {
33
+ /** The client's identity, recorded in the token's `sub` and in receipts. */
34
+ readonly clientId: string;
35
+ /** The client's secret, the shared symmetric key for HS256. */
36
+ readonly secret: string;
37
+ /** The scope this client may be granted. */
38
+ readonly scope: readonly string[];
39
+ }
40
+ /** Configuration for the embedded authorization server. */
41
+ export interface LocalOAuthServerConfig {
42
+ /** The issuer URI, stamped into every token's `iss`. */
43
+ readonly issuer: string;
44
+ /** The resource the tokens target, stamped into `aud`. */
45
+ readonly audience: string;
46
+ /** Token lifetime in seconds. */
47
+ readonly tokenLifetimeSeconds?: number;
48
+ /** Clock skew tolerated on verification, in seconds. */
49
+ readonly leewaySeconds?: number;
50
+ /**
51
+ * The durable credential store (SEC-01).
52
+ *
53
+ * When present, generated and operator-provisioned clients are PERSISTED
54
+ * owner-only, revocation survives restarts, and the server hydrates its
55
+ * registry from the store at construction. A generation that cannot be
56
+ * persisted registers nothing (fail-closed). Absent keeps the earlier
57
+ * in-memory behavior.
58
+ */
59
+ readonly store?: ClientCredentialStore;
60
+ }
61
+ /** A generated credential, with the secret returned exactly once. */
62
+ export interface GeneratedClientCredential {
63
+ readonly clientId: string;
64
+ /** Crypto-random, shown once here and never re-echoed (SEC-01). */
65
+ readonly secret: string;
66
+ }
67
+ /** Why verification failed, in RFC 6750 error-code vocabulary. */
68
+ export type OAuthTokenError = {
69
+ readonly code: 'invalid_request';
70
+ readonly description: string;
71
+ } | {
72
+ readonly code: 'invalid_token';
73
+ readonly description: string;
74
+ } | {
75
+ readonly code: 'insufficient_scope';
76
+ readonly description: string;
77
+ readonly scope: string;
78
+ };
79
+ /** A verified token's claims. */
80
+ export interface VerifiedToken {
81
+ readonly clientId: string;
82
+ readonly scope: readonly string[];
83
+ readonly audience: string;
84
+ readonly jti: string;
85
+ }
86
+ /** The embedded local authorization server. */
87
+ export interface LocalOAuthServer {
88
+ /** Register or replace a client credential. */
89
+ upsertClient(client: OAuthClient): void;
90
+ /**
91
+ * GENERATE a fresh client credential with a crypto-random secret (SEC-01).
92
+ *
93
+ * The secret is returned EXACTLY ONCE, in the returned object; every later surface
94
+ * names only the clientId. The generation is fail-closed: when the durable store is
95
+ * present and the credential cannot be persisted, nothing is registered and the
96
+ * call throws — a credential that a restart would forget must never appear usable.
97
+ */
98
+ generateClient(options: {
99
+ readonly clientId: string;
100
+ readonly scope?: readonly string[];
101
+ }): GeneratedClientCredential;
102
+ /** Revoke ONE client; its tokens fail, every other client is untouched. */
103
+ revokeClient(clientId: string): void;
104
+ /** Issue an access token for a client, or undefined for an unknown/revoked client. */
105
+ issueAccessToken(clientId: string, scope?: readonly string[]): Promise<string>;
106
+ /**
107
+ * Verify a presented bearer token: signature, issuer, audience, expiry and
108
+ * scope, against the live client registry and revocation set.
109
+ */
110
+ verify(token: string, requiredScope?: readonly string[]): Promise<{
111
+ readonly ok: true;
112
+ readonly token: VerifiedToken;
113
+ } | {
114
+ readonly ok: false;
115
+ readonly error: OAuthTokenError;
116
+ }>;
117
+ /** RFC 8414 authorization-server metadata. */
118
+ authorizationServerMetadata(): Record<string, unknown>;
119
+ /** RFC 9728 protected-resource metadata. */
120
+ protectedResourceMetadata(): Record<string, unknown>;
121
+ }
122
+ /**
123
+ * Create the embedded local authorization server.
124
+ *
125
+ * The signing key is DERIVED per client from its secret (SHA-256), so each
126
+ * client's tokens are signed with a key only that client's secret can produce —
127
+ * and revoking a client is removing its secret from the registry, which is the
128
+ * one-sided operation a resource server can perform without consulting the
129
+ * authorization server.
130
+ * @param config - issuer, audience and lifetimes.
131
+ * @returns the server.
132
+ */
133
+ export declare function createLocalOAuthServer(config: LocalOAuthServerConfig): LocalOAuthServer;
134
+ /** Build the RFC 6750 challenge header for a verification failure. */
135
+ export declare function oauthChallenge(error: OAuthTokenError, realm?: string): string;
136
+ /**
137
+ * The HTTP status RFC 6750 §3.1 assigns to each token error.
138
+ *
139
+ * All three are 401-shaped failures, but the RFC distinguishes them so a client
140
+ * can tell "malformed request, fix it" (400) from "credential bad, re-auth"
141
+ * (401) from "credential fine, grant too small" (403). Flattening every
142
+ * challenge to 401 was the F4 gap: a client receiving 400 for a revoked token
143
+ * would re-send the same request, and one receiving 401 for insufficient scope
144
+ * would re-authenticate pointlessly instead of asking for the scope.
145
+ */
146
+ export declare function oauthErrorStatus(error: OAuthTokenError): 400 | 401 | 403;
147
+ /** Extract the error code from a challenge, for tests and diagnostics. */
148
+ export declare function challengeErrorCode(challenge: string): string | undefined;
149
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/mcp/oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAElE,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAClC;AAED,2DAA2D;AAC3D,MAAM,WAAW,sBAAsB;IACrC,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,iCAAiC;IACjC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IACtC,wDAAwD;IACxD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B;;;;;;;;OAQG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,qBAAqB,CAAA;CACvC;AAED,qEAAqE;AACrE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,kEAAkE;AAClE,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAClE;IAAE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAChE;IAAE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAEjG,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAA;IACvC;;;;;;;OAOG;IACH,cAAc,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,yBAAyB,CAAA;IACrH,2EAA2E;IAC3E,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,sFAAsF;IACtF,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC9E;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAC7D;QAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAA;KAAE,GACpD;QAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAA;KAAE,CAC1D,CAAA;IACD,8CAA8C;IAC9C,2BAA2B,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtD,4CAA4C;IAC5C,yBAAyB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrD;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,sBAAsB,GAAG,gBAAgB,CA8TvF;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,SAAc,GAAG,MAAM,CAQlF;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAMxE;AAED,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExE"}
@@ -0,0 +1,396 @@
1
+ /**
2
+ * The embedded LOCAL OAuth 2.1 authorization server + resource-server verifier
3
+ * (P26-15).
4
+ *
5
+ * P26-15 permits exactly two implementations: a tested authorization-server
6
+ * integration, or a properly implemented embedded/local server — and forbids
7
+ * inventing token cryptography. This module is the second: the signing and
8
+ * verification primitives are Node's OWN WebCrypto (`crypto.subtle` HMAC-SHA256),
9
+ * and only the JWT envelope around them is assembled here. No cipher, mode or
10
+ * hash is hand-rolled.
11
+ *
12
+ * The shape follows the parts of RFC 6749/6750/8414/9728 a resource server and a
13
+ * local issuer need:
14
+ *
15
+ * - HS256-signed JWTs with `iss`, `sub` (client id), `aud`, `scope`, `iat`, `exp`
16
+ * and a unique `jti`;
17
+ * - RFC 6750 challenges: `WWW-Authenticate: Bearer error="invalid_token"|...`
18
+ * with `scope="..."` on insufficient_scope;
19
+ * - per-client credentials with INDEPENDENT revocation (a revoked client's tokens
20
+ * fail while every other client keeps working);
21
+ * - RFC 8414 authorization-server metadata and RFC 9728 protected-resource
22
+ * metadata, so a client can discover issuer/audience/token endpoint.
23
+ *
24
+ * The static-token mode remains available as CUSTOM authentication and still
25
+ * claims nothing; this module is what a deployment turns on when it wants the
26
+ * OAuth profile, and `authProfile()` reports which mode is live.
27
+ *
28
+ * @module @try-works/dsh-model-as-tool/mcp/oauth
29
+ */
30
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
31
+ const ENCODER = new TextEncoder();
32
+ /** Base64url without padding, the JWT alphabet. */
33
+ function b64url(bytes) {
34
+ return Buffer.from(bytes).toString('base64url');
35
+ }
36
+ /** Decode one base64url JWT part to its JSON value. */
37
+ function decodePart(part) {
38
+ if (part === undefined || part.length === 0)
39
+ return undefined;
40
+ try {
41
+ const parsed = JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
42
+ return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)
43
+ ? parsed
44
+ : undefined;
45
+ }
46
+ catch {
47
+ return undefined;
48
+ }
49
+ }
50
+ /**
51
+ * Create the embedded local authorization server.
52
+ *
53
+ * The signing key is DERIVED per client from its secret (SHA-256), so each
54
+ * client's tokens are signed with a key only that client's secret can produce —
55
+ * and revoking a client is removing its secret from the registry, which is the
56
+ * one-sided operation a resource server can perform without consulting the
57
+ * authorization server.
58
+ * @param config - issuer, audience and lifetimes.
59
+ * @returns the server.
60
+ */
61
+ export function createLocalOAuthServer(config) {
62
+ const lifetime = config.tokenLifetimeSeconds ?? 900;
63
+ const leeway = config.leewaySeconds ?? 30;
64
+ const clients = new Map();
65
+ const revoked = new Map(); // clientId -> secret hash, so a re-registration restores access
66
+ // SEC-01: hydrate the registry from the durable store, so generated credentials
67
+ // survive a restart. Revoked clients are ABSENT from the store (revocation deletes),
68
+ // so everything listed is live.
69
+ for (const stored of config.store?.list() ?? []) {
70
+ if (stored.scheme !== 'oauth-resource-server')
71
+ continue;
72
+ clients.set(stored.clientId, { clientId: stored.clientId, secret: stored.secret, scope: stored.scope });
73
+ }
74
+ // The key type is inferred: naming `CryptoKey` here collides with the DOM value
75
+ // of the same name in the Node type surface.
76
+ const keyFor = (secret) => {
77
+ const digest = createHash('sha256').update(secret).digest();
78
+ return crypto.subtle.importKey('raw', digest, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']);
79
+ };
80
+ /**
81
+ * The LIVE registry entry for one client id.
82
+ *
83
+ * When the durable store exists it is the SOLE authority: the store row is
84
+ * read fresh on every issue and verify, and there is NO fallback to the
85
+ * construction-time hydration —
86
+ *
87
+ * - a row ABSENT from the store is an unknown/revoked client, even if this
88
+ * process hydrated the id earlier (delete-then-recreate invalidates old
89
+ * tokens; deletion through any surface takes effect on the next call);
90
+ * - a row with the WRONG SCHEME (static-token where an OAuth row was
91
+ * hydrated) is REJECTED as an OAuth client — scheme confusion must not
92
+ * resurrect a hydrated entry;
93
+ * - a credential generated after this server was constructed ISSUES
94
+ * immediately (the earlier design answered "unknown client" until
95
+ * restart).
96
+ *
97
+ * The in-memory map serves ONLY the store-less mode.
98
+ */
99
+ const liveClient = (clientId) => {
100
+ if (config.store !== undefined) {
101
+ const stored = config.store.get(clientId);
102
+ if (stored === undefined)
103
+ return undefined;
104
+ if (stored.scheme !== 'oauth-resource-server')
105
+ return undefined;
106
+ return { clientId: stored.clientId, secret: stored.secret, scope: stored.scope };
107
+ }
108
+ return clients.get(clientId);
109
+ };
110
+ /** Validate scope names once, so a bad name is refused at registration. */
111
+ const assertScopeNames = (scope) => {
112
+ // Scope names are RFC 6749 scope-tokens: no whitespace. A name containing a
113
+ // space can never be satisfied (issue joins with ' ', verify splits), so
114
+ // registering one is a configuration error and is refused here rather than
115
+ // silently issuing credentials that can never authorize (review F-g).
116
+ for (const name of scope) {
117
+ if (!/^[A-Za-z0-9._:\/-]+$/.test(name)) {
118
+ throw new Error(`scope name ${JSON.stringify(name)} is not an RFC 6749 scope-token`);
119
+ }
120
+ }
121
+ };
122
+ const register = (client) => {
123
+ assertScopeNames(client.scope);
124
+ // SEC-01 FAIL-CLOSED: persist FIRST, register only when the credential is durable —
125
+ // a generation that a restart would forget must never appear usable.
126
+ if (config.store !== undefined) {
127
+ config.store.upsert({
128
+ clientId: client.clientId,
129
+ secret: client.secret,
130
+ scheme: 'oauth-resource-server',
131
+ scope: client.scope,
132
+ createdAt: new Date().toISOString(),
133
+ });
134
+ }
135
+ clients.set(client.clientId, client);
136
+ revoked.delete(client.clientId);
137
+ };
138
+ return {
139
+ upsertClient(client) {
140
+ register(client);
141
+ },
142
+ generateClient(options) {
143
+ if (typeof options.clientId !== 'string' || options.clientId.length === 0 || options.clientId.length > 64) {
144
+ throw new Error('clientId must be a non-empty string of at most 64 characters');
145
+ }
146
+ if (clients.has(options.clientId) || config.store?.get(options.clientId) !== undefined) {
147
+ throw new Error(`a credential for ${JSON.stringify(options.clientId)} already exists; revoke it first`);
148
+ }
149
+ // SEC-01: high entropy — 32 crypto-random bytes, base64url-encoded. The secret
150
+ // is generated SERVER-SIDE and returned exactly once from this function.
151
+ const secret = randomBytes(32).toString('base64url');
152
+ register({ clientId: options.clientId, secret, scope: options.scope ?? ['mcp:call'] });
153
+ return { clientId: options.clientId, secret };
154
+ },
155
+ revokeClient(clientId) {
156
+ /**
157
+ * Revocation is decided by the LIVE authority, never by the hydrated map:
158
+ * a client generated AFTER this server was constructed (a "late client")
159
+ * exists only in the durable store, and revoking it must delete the
160
+ * durable record just like any other. The revocation FINGERPRINT is the
161
+ * hash of the client's own SECRET — scoped to the credential, not the id —
162
+ * so a legitimate re-generation of the same id with a fresh secret is
163
+ * never blocked by an old revocation.
164
+ */
165
+ const client = liveClient(clientId);
166
+ if (client !== undefined) {
167
+ revoked.set(clientId, createHash('sha256').update(client.secret).digest('hex'));
168
+ // Durable revocation: the store entry is DELETED, so the credential does not
169
+ // resurrect on the next boot. A persistence failure surfaces to the caller;
170
+ // the in-memory revocation still stands for this process.
171
+ config.store?.delete(clientId);
172
+ }
173
+ },
174
+ async issueAccessToken(clientId, scope) {
175
+ const client = liveClient(clientId);
176
+ if (client === undefined)
177
+ return undefined;
178
+ // SECRET-SCOPED revocation: the fingerprint is the hash of the revoked
179
+ // secret itself, so re-generating the same id with a FRESH secret issues
180
+ // again, while a revoked secret never does.
181
+ const revokedHash = revoked.get(clientId);
182
+ if (revokedHash !== undefined && revokedHash === createHash('sha256').update(client.secret).digest('hex')) {
183
+ return undefined;
184
+ }
185
+ const grantedScope = scope === undefined || scope.length === 0
186
+ ? [...client.scope]
187
+ : [...new Set(scope.filter(name => client.scope.includes(name)))];
188
+ const now = Math.floor(Date.now() / 1000);
189
+ const header = b64url(ENCODER.encode(JSON.stringify({ alg: 'HS256', typ: 'JWT' })));
190
+ const payload = b64url(ENCODER.encode(JSON.stringify({
191
+ iss: config.issuer,
192
+ sub: client.clientId,
193
+ aud: config.audience,
194
+ scope: grantedScope.join(' '),
195
+ iat: now,
196
+ exp: now + lifetime,
197
+ jti: `jti_${now.toString(36)}_${Math.random().toString(36).slice(2, 10)}`,
198
+ })));
199
+ const signingInput = `${header}.${payload}`;
200
+ const signature = await crypto.subtle.sign('HMAC', await keyFor(client.secret), ENCODER.encode(signingInput));
201
+ return `${signingInput}.${b64url(new Uint8Array(signature))}`;
202
+ },
203
+ async verify(token, requiredScope) {
204
+ const parts = token.split('.');
205
+ if (parts.length !== 3) {
206
+ return {
207
+ ok: false,
208
+ error: { code: 'invalid_request', description: 'the credential is not a JWT with three parts' },
209
+ };
210
+ }
211
+ const header = decodePart(parts[0]);
212
+ const payload = decodePart(parts[1]);
213
+ if (header?.alg !== 'HS256' || payload === undefined) {
214
+ return {
215
+ ok: false,
216
+ error: { code: 'invalid_request', description: 'the credential is not an HS256 JWT with a JSON payload' },
217
+ };
218
+ }
219
+ const clientId = payload.sub;
220
+ if (typeof clientId !== 'string') {
221
+ return {
222
+ ok: false,
223
+ error: { code: 'invalid_request', description: 'the credential carries no subject' },
224
+ };
225
+ }
226
+ // The LIVE registry entry: the durable store first, so a rotated or
227
+ // deleted credential is judged by the CURRENT secret and scope, never by
228
+ // the construction-time snapshot.
229
+ const client = liveClient(clientId);
230
+ if (client === undefined) {
231
+ return {
232
+ ok: false,
233
+ error: { code: 'invalid_token', description: 'the credential is from an unknown client' },
234
+ };
235
+ }
236
+ // SIGNATURE FIRST: only a credential signed with the client's own key may
237
+ // present claims about that client, so nothing below reads an unsigned claim
238
+ // — including the revocation state, which a forged credential must not be
239
+ // able to probe (F2: the revoked check sat here before and answered an
240
+ // unsigned token claiming sub=X, revealing whether X is revoked).
241
+ const signingInput = `${parts[0]}.${parts[1]}`;
242
+ const signaturePart = parts[2];
243
+ const presented = Buffer.from(signaturePart, 'base64url');
244
+ const expected = new Uint8Array(await crypto.subtle.sign('HMAC', await keyFor(client.secret), ENCODER.encode(signingInput)));
245
+ const matches = presented.length === expected.length && timingSafeEqual(presented, expected);
246
+ if (!matches) {
247
+ return {
248
+ ok: false,
249
+ error: { code: 'invalid_token', description: 'the credential signature does not verify' },
250
+ };
251
+ }
252
+ const revokedHash = revoked.get(clientId);
253
+ if (revokedHash !== undefined && revokedHash === createHash('sha256').update(client.secret).digest('hex')) {
254
+ return {
255
+ ok: false,
256
+ error: { code: 'invalid_token', description: 'the credential is revoked' },
257
+ };
258
+ }
259
+ // SEC-01: a client deleted from the durable store by ANY surface — the local
260
+ // revokeClient, the host's management action, or another process — is revoked
261
+ // for this process too, on the very next verification. The live lookup above
262
+ // consults the store; this is the in-memory fallback check.
263
+ if (config.store !== undefined && config.store.get(clientId) === undefined) {
264
+ return {
265
+ ok: false,
266
+ error: { code: 'invalid_token', description: 'the credential is revoked' },
267
+ };
268
+ }
269
+ // Issuer and audience are validated AFTER the signature: only then are they
270
+ // facts the issuer asserted rather than text an attacker wrote.
271
+ if (payload.iss !== config.issuer) {
272
+ return {
273
+ ok: false,
274
+ error: { code: 'invalid_token', description: 'the credential was issued by a different authorization server' },
275
+ };
276
+ }
277
+ const audience = payload.aud;
278
+ const audiences = Array.isArray(audience) ? audience : [audience];
279
+ if (!audiences.includes(config.audience)) {
280
+ return {
281
+ ok: false,
282
+ error: { code: 'invalid_token', description: `the credential audience does not include ${config.audience}` },
283
+ };
284
+ }
285
+ const exp = payload.exp;
286
+ const now = Math.floor(Date.now() / 1000);
287
+ if (typeof exp !== 'number' || now > exp + leeway) {
288
+ return {
289
+ ok: false,
290
+ error: { code: 'invalid_token', description: 'the credential has expired' },
291
+ };
292
+ }
293
+ const iat = payload.iat;
294
+ if (typeof iat === 'number' && iat > now + leeway) {
295
+ return {
296
+ ok: false,
297
+ error: { code: 'invalid_token', description: 'the credential is not yet valid' },
298
+ };
299
+ }
300
+ const scope = typeof payload.scope === 'string'
301
+ ? payload.scope.split(' ').filter(Boolean)
302
+ : [];
303
+ /**
304
+ * F1 FIX (review): the presented scope is CAPPED at the client's registered
305
+ * grant. Issuance already caps it, but with symmetric keys a client can
306
+ * self-mint a token claiming ANY scope — verification is therefore the only
307
+ * place the per-client grant can be enforced, and it was not. A claim the
308
+ * registry does not admit is refused here, never trusted.
309
+ */
310
+ const overGrant = scope.filter(name => !client.scope.includes(name));
311
+ if (overGrant.length > 0) {
312
+ return {
313
+ ok: false,
314
+ error: {
315
+ code: 'invalid_token',
316
+ description: `the credential claims scope beyond this client's grant: ${overGrant.join(', ')}`,
317
+ },
318
+ };
319
+ }
320
+ const required = requiredScope ?? [];
321
+ const missing = required.filter(name => !scope.includes(name));
322
+ if (missing.length > 0) {
323
+ return {
324
+ ok: false,
325
+ error: {
326
+ code: 'insufficient_scope',
327
+ description: `the credential lacks required scope: ${missing.join(', ')}`,
328
+ scope: required.join(' '),
329
+ },
330
+ };
331
+ }
332
+ return {
333
+ ok: true,
334
+ token: {
335
+ clientId,
336
+ scope,
337
+ audience: config.audience,
338
+ jti: typeof payload.jti === 'string' ? payload.jti : '',
339
+ },
340
+ };
341
+ },
342
+ authorizationServerMetadata() {
343
+ return {
344
+ issuer: config.issuer,
345
+ // NO token_endpoint (F3, review): issuance is programmatic-only here, so
346
+ // advertising an endpoint the transport never serves would be an RFC 8414
347
+ // lie. A client that finds this issuer knows credentials come from the
348
+ // operator's configuration, not from a token request.
349
+ response_types_supported: ['token'],
350
+ token_endpoint_auth_methods_supported: ['client_secret_post'],
351
+ grant_types_supported: ['client_credentials'],
352
+ };
353
+ },
354
+ protectedResourceMetadata() {
355
+ return {
356
+ resource: config.audience,
357
+ authorization_servers: [config.issuer],
358
+ bearer_methods_supported: ['header'],
359
+ scopes_supported: [...new Set([...clients.values()].flatMap(client => client.scope))],
360
+ };
361
+ },
362
+ };
363
+ }
364
+ /** Build the RFC 6750 challenge header for a verification failure. */
365
+ export function oauthChallenge(error, realm = 'tool-mode') {
366
+ const params = [
367
+ `realm="${realm}"`,
368
+ `error="${error.code}"`,
369
+ `error_description="${error.description.replaceAll('"', "'")}"`,
370
+ ];
371
+ if (error.code === 'insufficient_scope')
372
+ params.push(`scope="${error.scope}"`);
373
+ return `Bearer ${params.join(', ')}`;
374
+ }
375
+ /**
376
+ * The HTTP status RFC 6750 §3.1 assigns to each token error.
377
+ *
378
+ * All three are 401-shaped failures, but the RFC distinguishes them so a client
379
+ * can tell "malformed request, fix it" (400) from "credential bad, re-auth"
380
+ * (401) from "credential fine, grant too small" (403). Flattening every
381
+ * challenge to 401 was the F4 gap: a client receiving 400 for a revoked token
382
+ * would re-send the same request, and one receiving 401 for insufficient scope
383
+ * would re-authenticate pointlessly instead of asking for the scope.
384
+ */
385
+ export function oauthErrorStatus(error) {
386
+ switch (error.code) {
387
+ case 'invalid_request': return 400;
388
+ case 'insufficient_scope': return 403;
389
+ case 'invalid_token': return 401;
390
+ }
391
+ }
392
+ /** Extract the error code from a challenge, for tests and diagnostics. */
393
+ export function challengeErrorCode(challenge) {
394
+ return /error="([^"]+)"/.exec(challenge)?.[1];
395
+ }
396
+ //# sourceMappingURL=oauth.js.map
@@ -46,8 +46,15 @@ export declare const META_KEYS: {
46
46
  * Error codes.
47
47
  *
48
48
  * The reserved range belongs to the protocol. P26-03 is explicit that custom
49
- * errors must not be allocated there, so this module declares only codes the
50
- * revision defines and refuses to mint new ones in that band.
49
+ * errors must not be allocated there, and the pinned revision partitions the
50
+ * bands: `-32768..-32000` is JSON-RPC's own predefined-error allocation,
51
+ * `-32020..-32099` is reserved for MCP-defined codes (allocated sequentially,
52
+ * and this module declares exactly those the revision defines), while
53
+ * `-32000..-32019` is the IMPLEMENTATION-DEFINED sub-range — the specification
54
+ * will never define codes there, and receivers must not assume cross-
55
+ * implementation semantics. `rateLimited` lives in that sub-range deliberately:
56
+ * it is a documented implementation allocation, never a minted code in the
57
+ * reserved band.
51
58
  */
52
59
  export declare const ERROR_CODES: {
53
60
  /** Standard JSON-RPC. */
@@ -60,6 +67,16 @@ export declare const ERROR_CODES: {
60
67
  readonly headerMismatch: -32020;
61
68
  readonly unsupportedClientCapability: -32021;
62
69
  readonly unsupportedProtocolVersion: -32022;
70
+ /**
71
+ * Implementation-defined (-32000..-32019, never allocated by the specification).
72
+ *
73
+ * The revision defines NO rate-limit error, so a refusal uses this documented
74
+ * implementation code rather than pretending the request was invalid (-32600) or
75
+ * minting a code in the MCP-reserved band. The HTTP 429 status carries the
76
+ * semantics; receivers that do not know this code still get a conformant
77
+ * `JSONRPCErrorResponse` envelope and a `Retry-After` header.
78
+ */
79
+ readonly rateLimited: -32001;
63
80
  };
64
81
  /** The convention for a tool that ran and returned a domain failure (P26-04). */
65
82
  export interface ToolErrorPayload {
@@ -136,21 +153,43 @@ export interface FeatureDeclaration {
136
153
  * cannot advertise a feature it does not have by accident.
137
154
  */
138
155
  export declare const ADVERTISED_FEATURES: readonly FeatureDeclaration[];
156
+ /**
157
+ * What the server may advertise, given what is ACTUALLY WIRED (P26-12).
158
+ *
159
+ * The static matrix declared `subscriptions/listen` as implemented unconditionally — while
160
+ * the only production transport never received a registry and therefore answered `-32601`
161
+ * for it. An advertised feature that no client can obtain is worse than an absent one: a
162
+ * client plans around it and fails later.
163
+ *
164
+ * The feature is now conditional on the wiring, and the DEFAULT is `false` because that is
165
+ * the production reality. A caller that has genuinely composed a subscription registry says
166
+ * so; nothing else can make the claim true by declaring it.
167
+ *
168
+ * @param wiring - which optional surfaces are composed.
169
+ * @returns the advertised-feature matrix for this deployment.
170
+ */
171
+ export declare function advertisedFeatures(wiring?: {
172
+ readonly subscriptions?: boolean;
173
+ }): readonly FeatureDeclaration[];
139
174
  /**
140
175
  * The capabilities object this server may advertise.
141
176
  *
142
- * Derived from {@link ADVERTISED_FEATURES} rather than written twice, so the
143
- * matrix and the advertised capabilities cannot disagree.
144
- * @returns the capability names that are genuinely implemented.
177
+ * Derived from the matrix rather than written twice, so the two cannot disagree.
178
+ * @param wiring - which optional surfaces are composed.
179
+ * @returns the capability names that are genuinely implemented HERE.
145
180
  */
146
- export declare function implementedCapabilities(): string[];
181
+ export declare function implementedCapabilities(wiring?: {
182
+ readonly subscriptions?: boolean;
183
+ }): string[];
147
184
  /**
148
185
  * Whether a client's REQUIRED capability is one this server supports (P26-03).
149
186
  *
150
187
  * @param required - capability names the client declared as required.
151
188
  * @returns the unsupported names, empty when all are supported.
152
189
  */
153
- export declare function unsupportedRequiredCapabilities(required: readonly string[]): string[];
190
+ export declare function unsupportedRequiredCapabilities(required: readonly string[], wiring?: {
191
+ readonly subscriptions?: boolean;
192
+ }): string[];
154
193
  /**
155
194
  * The caching fields every cacheable result must carry (P26-14).
156
195
  *
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/mcp/protocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../src/mcp/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,uCAAuC;AACvC,eAAO,MAAM,gBAAgB,eAAe,CAAA;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,MAAM,EAAuB,CAAA;AAEhF,6CAA6C;AAC7C,eAAO,MAAM,SAAS;;;;;CAKZ,CAAA;AAEV;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW;IACtB,yBAAyB;;;;;;IAMzB,oBAAoB;;;;IAIpB;;;;;;;;OAQG;;CAEK,CAAA;AAEV,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;IACtB,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;CAC9E;AAED,qDAAqD;AACrD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;IACvB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC1C;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;IACxE,QAAQ,CAAC,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;CAC/D;AAED,wEAAwE;AACxE,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAkB,IAAI,eAAc;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;gBAE3B,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAK1E;AAED,0EAA0E;AAC1E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGzD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,eAAe,CA+CjG;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAWlE;AAED,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAC9B;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,kBAAkB,EAmB5D,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,GAAE;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,SAAS,kBAAkB,EAAE,CAMnH;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,GAAE;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,MAAM,EAAE,CAEnG;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,MAAM,GAAE;IAAE,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GAChD,MAAM,EAAE,CAMV;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,IAAI;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAE/F;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,UAAU,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC9D,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAOjE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,EAC1B,KAAK,EAAE,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACnF,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWzB;AAED,+CAA+C;AAC/C,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA6BhF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAiBpE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAK5D"}