@qase/mcp-server 1.1.8 → 2.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 (284) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +75 -542
  3. package/build/auth/client-context.d.ts +12 -0
  4. package/build/auth/client-context.d.ts.map +1 -0
  5. package/build/auth/client-context.js +12 -0
  6. package/build/auth/client-context.js.map +1 -0
  7. package/build/auth/jwks-verifier.d.ts +15 -0
  8. package/build/auth/jwks-verifier.d.ts.map +1 -0
  9. package/build/auth/jwks-verifier.js +31 -0
  10. package/build/auth/jwks-verifier.js.map +1 -0
  11. package/build/auth/mcp-guard.d.ts +15 -0
  12. package/build/auth/mcp-guard.d.ts.map +1 -0
  13. package/build/auth/mcp-guard.js +44 -0
  14. package/build/auth/mcp-guard.js.map +1 -0
  15. package/build/auth/oauth-config.d.ts +40 -0
  16. package/build/auth/oauth-config.d.ts.map +1 -0
  17. package/build/auth/oauth-config.js +34 -0
  18. package/build/auth/oauth-config.js.map +1 -0
  19. package/build/auth/proxy-provider.d.ts +31 -0
  20. package/build/auth/proxy-provider.d.ts.map +1 -0
  21. package/build/auth/proxy-provider.js +87 -0
  22. package/build/auth/proxy-provider.js.map +1 -0
  23. package/build/auth/token-type.d.ts +7 -0
  24. package/build/auth/token-type.d.ts.map +1 -0
  25. package/build/auth/token-type.js +10 -0
  26. package/build/auth/token-type.js.map +1 -0
  27. package/build/cache/circuit-breaker.d.ts +33 -0
  28. package/build/cache/circuit-breaker.d.ts.map +1 -0
  29. package/build/cache/circuit-breaker.js +68 -0
  30. package/build/cache/circuit-breaker.js.map +1 -0
  31. package/build/cache/index.d.ts +24 -0
  32. package/build/cache/index.d.ts.map +1 -0
  33. package/build/cache/index.js +86 -0
  34. package/build/cache/index.js.map +1 -0
  35. package/build/cache/keys.d.ts +25 -0
  36. package/build/cache/keys.d.ts.map +1 -0
  37. package/build/cache/keys.js +36 -0
  38. package/build/cache/keys.js.map +1 -0
  39. package/build/cache/layered.d.ts +31 -0
  40. package/build/cache/layered.d.ts.map +1 -0
  41. package/build/cache/layered.js +88 -0
  42. package/build/cache/layered.js.map +1 -0
  43. package/build/cache/memory.d.ts +32 -0
  44. package/build/cache/memory.d.ts.map +1 -0
  45. package/build/cache/memory.js +108 -0
  46. package/build/cache/memory.js.map +1 -0
  47. package/build/cache/metrics.d.ts +31 -0
  48. package/build/cache/metrics.d.ts.map +1 -0
  49. package/build/cache/metrics.js +95 -0
  50. package/build/cache/metrics.js.map +1 -0
  51. package/build/cache/prom-format.d.ts +7 -0
  52. package/build/cache/prom-format.d.ts.map +1 -0
  53. package/build/cache/prom-format.js +16 -0
  54. package/build/cache/prom-format.js.map +1 -0
  55. package/build/cache/redis-bus.d.ts +29 -0
  56. package/build/cache/redis-bus.d.ts.map +1 -0
  57. package/build/cache/redis-bus.js +50 -0
  58. package/build/cache/redis-bus.js.map +1 -0
  59. package/build/cache/redis.d.ts +32 -0
  60. package/build/cache/redis.d.ts.map +1 -0
  61. package/build/cache/redis.js +89 -0
  62. package/build/cache/redis.js.map +1 -0
  63. package/build/cache/types.d.ts +38 -0
  64. package/build/cache/types.d.ts.map +1 -0
  65. package/build/cache/types.js +2 -0
  66. package/build/cache/types.js.map +1 -0
  67. package/build/client/index.d.ts +10 -3
  68. package/build/client/index.d.ts.map +1 -1
  69. package/build/client/index.js +75 -50
  70. package/build/client/index.js.map +1 -1
  71. package/build/http/agent.d.ts +16 -0
  72. package/build/http/agent.d.ts.map +1 -0
  73. package/build/http/agent.js +16 -0
  74. package/build/http/agent.js.map +1 -0
  75. package/build/http/dedupe.d.ts +14 -0
  76. package/build/http/dedupe.d.ts.map +1 -0
  77. package/build/http/dedupe.js +69 -0
  78. package/build/http/dedupe.js.map +1 -0
  79. package/build/http/index.d.ts +6 -0
  80. package/build/http/index.d.ts.map +1 -0
  81. package/build/http/index.js +4 -0
  82. package/build/http/index.js.map +1 -0
  83. package/build/http/retry.d.ts +18 -0
  84. package/build/http/retry.d.ts.map +1 -0
  85. package/build/http/retry.js +52 -0
  86. package/build/http/retry.js.map +1 -0
  87. package/build/index.d.ts +1 -18
  88. package/build/index.d.ts.map +1 -1
  89. package/build/index.js +95 -63
  90. package/build/index.js.map +1 -1
  91. package/build/operations-v2/composites/ci-report.d.ts +2 -0
  92. package/build/operations-v2/composites/ci-report.d.ts.map +1 -0
  93. package/build/operations-v2/composites/ci-report.js +99 -0
  94. package/build/operations-v2/composites/ci-report.js.map +1 -0
  95. package/build/operations-v2/composites/regression-run.d.ts +2 -0
  96. package/build/operations-v2/composites/regression-run.d.ts.map +1 -0
  97. package/build/operations-v2/composites/regression-run.js +81 -0
  98. package/build/operations-v2/composites/regression-run.js.map +1 -0
  99. package/build/operations-v2/composites/triage-defect.d.ts +2 -0
  100. package/build/operations-v2/composites/triage-defect.d.ts.map +1 -0
  101. package/build/operations-v2/composites/triage-defect.js +67 -0
  102. package/build/operations-v2/composites/triage-defect.js.map +1 -0
  103. package/build/operations-v2/escape/api.d.ts +2 -0
  104. package/build/operations-v2/escape/api.d.ts.map +1 -0
  105. package/build/operations-v2/escape/api.js +34 -0
  106. package/build/operations-v2/escape/api.js.map +1 -0
  107. package/build/operations-v2/index.d.ts +19 -0
  108. package/build/operations-v2/index.d.ts.map +1 -0
  109. package/build/operations-v2/index.js +25 -0
  110. package/build/operations-v2/index.js.map +1 -0
  111. package/build/operations-v2/meta/discover.d.ts +9 -0
  112. package/build/operations-v2/meta/discover.d.ts.map +1 -0
  113. package/build/operations-v2/meta/discover.js +76 -0
  114. package/build/operations-v2/meta/discover.js.map +1 -0
  115. package/build/{operations/search.d.ts → operations-v2/qql/index.d.ts} +1 -1
  116. package/build/operations-v2/qql/index.d.ts.map +1 -0
  117. package/build/{operations/search.js → operations-v2/qql/index.js} +34 -9
  118. package/build/operations-v2/qql/index.js.map +1 -0
  119. package/build/operations-v2/read/get.d.ts +2 -0
  120. package/build/operations-v2/read/get.d.ts.map +1 -0
  121. package/build/operations-v2/read/get.js +91 -0
  122. package/build/operations-v2/read/get.js.map +1 -0
  123. package/build/operations-v2/read/project-context.d.ts +2 -0
  124. package/build/operations-v2/read/project-context.d.ts.map +1 -0
  125. package/build/operations-v2/read/project-context.js +102 -0
  126. package/build/operations-v2/read/project-context.js.map +1 -0
  127. package/build/operations-v2/write/attachments.d.ts +2 -0
  128. package/build/operations-v2/write/attachments.d.ts.map +1 -0
  129. package/build/operations-v2/write/attachments.js +58 -0
  130. package/build/operations-v2/write/attachments.js.map +1 -0
  131. package/build/operations-v2/write/cases.d.ts +2 -0
  132. package/build/operations-v2/write/cases.d.ts.map +1 -0
  133. package/build/operations-v2/write/cases.js +121 -0
  134. package/build/operations-v2/write/cases.js.map +1 -0
  135. package/build/operations-v2/write/defects.d.ts +2 -0
  136. package/build/operations-v2/write/defects.d.ts.map +1 -0
  137. package/build/operations-v2/write/defects.js +81 -0
  138. package/build/operations-v2/write/defects.js.map +1 -0
  139. package/build/operations-v2/write/environments.d.ts +2 -0
  140. package/build/operations-v2/write/environments.d.ts.map +1 -0
  141. package/build/operations-v2/write/environments.js +56 -0
  142. package/build/operations-v2/write/environments.js.map +1 -0
  143. package/build/operations-v2/write/milestones.d.ts +2 -0
  144. package/build/operations-v2/write/milestones.d.ts.map +1 -0
  145. package/build/operations-v2/write/milestones.js +56 -0
  146. package/build/operations-v2/write/milestones.js.map +1 -0
  147. package/build/operations-v2/write/plans.d.ts +2 -0
  148. package/build/operations-v2/write/plans.d.ts.map +1 -0
  149. package/build/operations-v2/write/plans.js +55 -0
  150. package/build/operations-v2/write/plans.js.map +1 -0
  151. package/build/operations-v2/write/results.d.ts +2 -0
  152. package/build/operations-v2/write/results.d.ts.map +1 -0
  153. package/build/operations-v2/write/results.js +72 -0
  154. package/build/operations-v2/write/results.js.map +1 -0
  155. package/build/operations-v2/write/runs.d.ts +2 -0
  156. package/build/operations-v2/write/runs.d.ts.map +1 -0
  157. package/build/operations-v2/write/runs.js +84 -0
  158. package/build/operations-v2/write/runs.js.map +1 -0
  159. package/build/operations-v2/write/shared-steps.d.ts +2 -0
  160. package/build/operations-v2/write/shared-steps.d.ts.map +1 -0
  161. package/build/operations-v2/write/shared-steps.js +60 -0
  162. package/build/operations-v2/write/shared-steps.js.map +1 -0
  163. package/build/operations-v2/write/suites.d.ts +2 -0
  164. package/build/operations-v2/write/suites.d.ts.map +1 -0
  165. package/build/operations-v2/write/suites.js +63 -0
  166. package/build/operations-v2/write/suites.js.map +1 -0
  167. package/build/prompts/index.d.ts +31 -0
  168. package/build/prompts/index.d.ts.map +1 -0
  169. package/build/prompts/index.js +229 -0
  170. package/build/prompts/index.js.map +1 -0
  171. package/build/transports/sse.d.ts.map +1 -1
  172. package/build/transports/sse.js +6 -0
  173. package/build/transports/sse.js.map +1 -1
  174. package/build/transports/streamableHttp.d.ts +6 -1
  175. package/build/transports/streamableHttp.d.ts.map +1 -1
  176. package/build/transports/streamableHttp.js +128 -16
  177. package/build/transports/streamableHttp.js.map +1 -1
  178. package/build/utils/auth-context.d.ts +8 -0
  179. package/build/utils/auth-context.d.ts.map +1 -1
  180. package/build/utils/auth-context.js +18 -0
  181. package/build/utils/auth-context.js.map +1 -1
  182. package/build/utils/case-enums.d.ts +5 -3
  183. package/build/utils/case-enums.d.ts.map +1 -1
  184. package/build/utils/case-enums.js +77 -59
  185. package/build/utils/case-enums.js.map +1 -1
  186. package/build/utils/output-schemas.d.ts +16 -0
  187. package/build/utils/output-schemas.d.ts.map +1 -0
  188. package/build/utils/output-schemas.js +82 -0
  189. package/build/utils/output-schemas.js.map +1 -0
  190. package/build/utils/registry.d.ts +42 -9
  191. package/build/utils/registry.d.ts.map +1 -1
  192. package/build/utils/registry.js +64 -11
  193. package/build/utils/registry.js.map +1 -1
  194. package/build/utils/response-shape.d.ts +23 -0
  195. package/build/utils/response-shape.d.ts.map +1 -0
  196. package/build/utils/response-shape.js +73 -0
  197. package/build/utils/response-shape.js.map +1 -0
  198. package/build/utils/rich-response.d.ts +58 -0
  199. package/build/utils/rich-response.d.ts.map +1 -0
  200. package/build/utils/rich-response.js +93 -0
  201. package/build/utils/rich-response.js.map +1 -0
  202. package/build/utils/server-context.d.ts +65 -0
  203. package/build/utils/server-context.d.ts.map +1 -0
  204. package/build/utils/server-context.js +67 -0
  205. package/build/utils/server-context.js.map +1 -0
  206. package/build/version.d.ts +1 -1
  207. package/build/version.js +1 -1
  208. package/docs/connect.md +81 -0
  209. package/docs/migration.md +351 -0
  210. package/docs/self-run.md +270 -0
  211. package/docs/tools.md +109 -0
  212. package/docs/troubleshooting.md +144 -0
  213. package/package.json +11 -2
  214. package/server.json +8 -2
  215. package/build/operations/attachments.d.ts +0 -8
  216. package/build/operations/attachments.d.ts.map +0 -1
  217. package/build/operations/attachments.js +0 -146
  218. package/build/operations/attachments.js.map +0 -1
  219. package/build/operations/authors.d.ts +0 -8
  220. package/build/operations/authors.d.ts.map +0 -1
  221. package/build/operations/authors.js +0 -70
  222. package/build/operations/authors.js.map +0 -1
  223. package/build/operations/cases.d.ts +0 -8
  224. package/build/operations/cases.d.ts.map +0 -1
  225. package/build/operations/cases.js +0 -370
  226. package/build/operations/cases.js.map +0 -1
  227. package/build/operations/configurations.d.ts +0 -10
  228. package/build/operations/configurations.d.ts.map +0 -1
  229. package/build/operations/configurations.js +0 -125
  230. package/build/operations/configurations.js.map +0 -1
  231. package/build/operations/custom-fields.d.ts +0 -8
  232. package/build/operations/custom-fields.d.ts.map +0 -1
  233. package/build/operations/custom-fields.js +0 -172
  234. package/build/operations/custom-fields.js.map +0 -1
  235. package/build/operations/defects.d.ts +0 -8
  236. package/build/operations/defects.d.ts.map +0 -1
  237. package/build/operations/defects.js +0 -256
  238. package/build/operations/defects.js.map +0 -1
  239. package/build/operations/environments.d.ts +0 -8
  240. package/build/operations/environments.d.ts.map +0 -1
  241. package/build/operations/environments.js +0 -154
  242. package/build/operations/environments.js.map +0 -1
  243. package/build/operations/milestones.d.ts +0 -8
  244. package/build/operations/milestones.d.ts.map +0 -1
  245. package/build/operations/milestones.js +0 -155
  246. package/build/operations/milestones.js.map +0 -1
  247. package/build/operations/plans.d.ts +0 -8
  248. package/build/operations/plans.d.ts.map +0 -1
  249. package/build/operations/plans.js +0 -158
  250. package/build/operations/plans.js.map +0 -1
  251. package/build/operations/projects.d.ts +0 -8
  252. package/build/operations/projects.d.ts.map +0 -1
  253. package/build/operations/projects.js +0 -192
  254. package/build/operations/projects.js.map +0 -1
  255. package/build/operations/results.d.ts +0 -8
  256. package/build/operations/results.d.ts.map +0 -1
  257. package/build/operations/results.js +0 -214
  258. package/build/operations/results.js.map +0 -1
  259. package/build/operations/runs.d.ts +0 -8
  260. package/build/operations/runs.d.ts.map +0 -1
  261. package/build/operations/runs.js +0 -224
  262. package/build/operations/runs.js.map +0 -1
  263. package/build/operations/search.d.ts.map +0 -1
  264. package/build/operations/search.js.map +0 -1
  265. package/build/operations/shared-parameters.d.ts +0 -10
  266. package/build/operations/shared-parameters.d.ts.map +0 -1
  267. package/build/operations/shared-parameters.js +0 -93
  268. package/build/operations/shared-parameters.js.map +0 -1
  269. package/build/operations/shared-steps.d.ts +0 -8
  270. package/build/operations/shared-steps.d.ts.map +0 -1
  271. package/build/operations/shared-steps.js +0 -160
  272. package/build/operations/shared-steps.js.map +0 -1
  273. package/build/operations/suites.d.ts +0 -8
  274. package/build/operations/suites.d.ts.map +0 -1
  275. package/build/operations/suites.js +0 -169
  276. package/build/operations/suites.js.map +0 -1
  277. package/build/operations/system-fields.d.ts +0 -10
  278. package/build/operations/system-fields.d.ts.map +0 -1
  279. package/build/operations/system-fields.js +0 -47
  280. package/build/operations/system-fields.js.map +0 -1
  281. package/build/operations/users.d.ts +0 -10
  282. package/build/operations/users.d.ts.map +0 -1
  283. package/build/operations/users.js +0 -78
  284. package/build/operations/users.js.map +0 -1
@@ -0,0 +1,12 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ /**
3
+ * Per-request storage for the OAuth `/authorize` request's `redirect_uri`.
4
+ *
5
+ * The proxy is transparent: it echoes the incoming redirect_uri back through
6
+ * `getClient` so the MCP SDK's local redirect_uri check passes, then forwards
7
+ * the request to auth.qase.io, which is the authoritative validator of a
8
+ * client's registered redirect URIs. Empty/undefined when no redirect_uri is
9
+ * present on the request.
10
+ */
11
+ export const authorizeRedirectUriStorage = new AsyncLocalStorage();
12
+ //# sourceMappingURL=client-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-context.js","sourceRoot":"","sources":["../../src/auth/client-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,iBAAiB,EAAsB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type JWTVerifyGetKey } from 'jose';
2
+ import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
3
+ import type { OAuthConfig } from './oauth-config.js';
4
+ export interface JwksVerifier {
5
+ verifyJwt(token: string): Promise<AuthInfo>;
6
+ }
7
+ /**
8
+ * Build a JWKS-backed access-token verifier. The remote key set is cached for
9
+ * one hour. `keyResolver` may be injected in tests (e.g. createLocalJWKSet).
10
+ *
11
+ * Signature, `exp`, `nbf`, `iss`, and `aud` are all validated by `jwtVerify`;
12
+ * it throws on any mismatch.
13
+ */
14
+ export declare function createJwksVerifier(config: OAuthConfig, keyResolver?: JWTVerifyGetKey): JwksVerifier;
15
+ //# sourceMappingURL=jwks-verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwks-verifier.d.ts","sourceRoot":"","sources":["../../src/auth/jwks-verifier.ts"],"names":[],"mappings":"AACA,OAAO,EAAiC,KAAK,eAAe,EAAE,MAAM,MAAM,CAAC;AAC3E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gDAAgD,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,WAAW,EACnB,WAAW,CAAC,EAAE,eAAe,GAC5B,YAAY,CAyBd"}
@@ -0,0 +1,31 @@
1
+ // src/auth/jwks-verifier.ts
2
+ import { createRemoteJWKSet, jwtVerify } from 'jose';
3
+ /**
4
+ * Build a JWKS-backed access-token verifier. The remote key set is cached for
5
+ * one hour. `keyResolver` may be injected in tests (e.g. createLocalJWKSet).
6
+ *
7
+ * Signature, `exp`, `nbf`, `iss`, and `aud` are all validated by `jwtVerify`;
8
+ * it throws on any mismatch.
9
+ */
10
+ export function createJwksVerifier(config, keyResolver) {
11
+ const resolve = keyResolver ?? createRemoteJWKSet(new URL(config.jwksUrl), { cacheMaxAge: 3600_000 });
12
+ return {
13
+ async verifyJwt(token) {
14
+ const { payload } = await jwtVerify(token, resolve, {
15
+ issuer: config.issuer,
16
+ audience: config.audience,
17
+ algorithms: config.jwtAlgorithms,
18
+ });
19
+ const scopes = typeof payload.scope === 'string' ? payload.scope.split(' ').filter(Boolean) : [];
20
+ return {
21
+ token,
22
+ clientId: payload.client_id ?? payload.azp ?? '',
23
+ scopes,
24
+ expiresAt: payload.exp,
25
+ resource: new URL(config.audience[0]),
26
+ extra: { sub: payload.sub },
27
+ };
28
+ },
29
+ };
30
+ }
31
+ //# sourceMappingURL=jwks-verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwks-verifier.js","sourceRoot":"","sources":["../../src/auth/jwks-verifier.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAwB,MAAM,MAAM,CAAC;AAQ3E;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAmB,EACnB,WAA6B;IAE7B,MAAM,OAAO,GACX,WAAW,IAAI,kBAAkB,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC;IAExF,OAAO;QACL,KAAK,CAAC,SAAS,CAAC,KAAa;YAC3B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE;gBAClD,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,UAAU,EAAE,MAAM,CAAC,aAAa;aACjC,CAAC,CAAC;YAEH,MAAM,MAAM,GACV,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAEpF,OAAO;gBACL,KAAK;gBACL,QAAQ,EAAG,OAAO,CAAC,SAAoB,IAAK,OAAO,CAAC,GAAc,IAAI,EAAE;gBACxE,MAAM;gBACN,SAAS,EAAE,OAAO,CAAC,GAAG;gBACtB,QAAQ,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACrC,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;aAC5B,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { RequestHandler } from 'express';
2
+ import type { JwksVerifier } from './jwks-verifier.js';
3
+ import type { OAuthConfig } from './oauth-config.js';
4
+ /**
5
+ * Dual-mode auth guard for the `/mcp` endpoint.
6
+ *
7
+ * - No bearer token → 401 + WWW-Authenticate (triggers OAuth discovery).
8
+ * - JWT (3 segments) → validated against JWKS; 401 on failure.
9
+ * - Opaque (legacy) token → passed through unchanged (no JWKS validation).
10
+ *
11
+ * The token itself is forwarded by the existing transport handler, which reads
12
+ * the Authorization header and stores it in requestTokenStorage.
13
+ */
14
+ export declare function createMcpGuard(verifier: JwksVerifier, config: OAuthConfig): RequestHandler;
15
+ //# sourceMappingURL=mcp-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-guard.d.ts","sourceRoot":"","sources":["../../src/auth/mcp-guard.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,GAAG,cAAc,CAqC1F"}
@@ -0,0 +1,44 @@
1
+ import { isJwt } from './token-type.js';
2
+ /**
3
+ * Dual-mode auth guard for the `/mcp` endpoint.
4
+ *
5
+ * - No bearer token → 401 + WWW-Authenticate (triggers OAuth discovery).
6
+ * - JWT (3 segments) → validated against JWKS; 401 on failure.
7
+ * - Opaque (legacy) token → passed through unchanged (no JWKS validation).
8
+ *
9
+ * The token itself is forwarded by the existing transport handler, which reads
10
+ * the Authorization header and stores it in requestTokenStorage.
11
+ */
12
+ export function createMcpGuard(verifier, config) {
13
+ // RFC 9728 path-aware discovery: the metadata URL must carry the resource's
14
+ // path so clients that use the full endpoint URL as the resource identifier
15
+ // (e.g. VS Code) resolve the matching document. Origin comes from publicUrl
16
+ // (the reachable server), the path segment from resourceUrl (`/mcp`).
17
+ const origin = config.publicUrl.replace(/\/$/, '');
18
+ const resourcePath = new URL(config.resourceUrl).pathname;
19
+ const resourceMetadataUrl = `${origin}/.well-known/oauth-protected-resource${resourcePath === '/' ? '' : resourcePath}`;
20
+ const challenge = `Bearer resource_metadata="${resourceMetadataUrl}"`;
21
+ const unauthorized = (res, description) => {
22
+ res.set('WWW-Authenticate', challenge);
23
+ res.status(401).json({ error: 'invalid_token', error_description: description });
24
+ };
25
+ return async (req, res, next) => {
26
+ const authHeader = req.headers['authorization'] || '';
27
+ const token = authHeader.startsWith('Bearer ') ? authHeader.slice(7).trim() : '';
28
+ if (!token) {
29
+ unauthorized(res, 'Missing bearer token');
30
+ return;
31
+ }
32
+ if (isJwt(token)) {
33
+ try {
34
+ await verifier.verifyJwt(token);
35
+ }
36
+ catch {
37
+ unauthorized(res, 'JWT validation failed');
38
+ return;
39
+ }
40
+ }
41
+ next();
42
+ };
43
+ }
44
+ //# sourceMappingURL=mcp-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-guard.js","sourceRoot":"","sources":["../../src/auth/mcp-guard.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAIxC;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,QAAsB,EAAE,MAAmB;IACxE,4EAA4E;IAC5E,4EAA4E;IAC5E,4EAA4E;IAC5E,sEAAsE;IACtE,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACnD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC;IAC1D,MAAM,mBAAmB,GAAG,GAAG,MAAM,wCACnC,YAAY,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAC9B,EAAE,CAAC;IACH,MAAM,SAAS,GAAG,6BAA6B,mBAAmB,GAAG,CAAC;IAEtE,MAAM,YAAY,GAAG,CAAC,GAAa,EAAE,WAAmB,EAAQ,EAAE;QAChE,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;QACvC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,WAAW,EAAE,CAAC,CAAC;IACnF,CAAC,CAAC;IAEF,OAAO,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAiB,EAAE;QAC9E,MAAM,UAAU,GAAI,GAAG,CAAC,OAAO,CAAC,eAAe,CAAY,IAAI,EAAE,CAAC;QAClE,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAEjF,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,YAAY,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC;YAC1C,OAAO;QACT,CAAC;QAED,IAAI,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACjB,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,YAAY,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAC;gBAC3C,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,40 @@
1
+ export interface OAuthConfig {
2
+ enabled: boolean;
3
+ authorizationUrl: string;
4
+ tokenUrl: string;
5
+ registrationUrl: string;
6
+ revocationUrl: string;
7
+ jwksUrl: string;
8
+ issuer: string;
9
+ /**
10
+ * Accepted JWT `aud` values. Multiple are supported because different MCP
11
+ * clients derive the RFC 8707 resource indicator differently (e.g. Claude
12
+ * sends `https://mcp.qase.io`, Codex sends the endpoint `https://mcp.qase.io/mcp`),
13
+ * and the AS mints the token audience from that resource. A token is accepted
14
+ * if its `aud` matches any entry. Configured via comma-separated QASE_OAUTH_AUDIENCE.
15
+ */
16
+ audience: string[];
17
+ jwtAlgorithms: string[];
18
+ /**
19
+ * The RFC 9728 resource identifier for the protected `/mcp` endpoint, e.g.
20
+ * `https://mcp.qase.io/mcp`. Modern clients (VS Code) use the FULL endpoint
21
+ * URL as the resource identifier and do path-aware metadata discovery, so
22
+ * this carries the `/mcp` path. Drives the path-aware protected-resource
23
+ * metadata document + the WWW-Authenticate challenge path.
24
+ */
25
+ resourceUrl: string;
26
+ /**
27
+ * The public ORIGIN the server is reached at, e.g. `https://mcp.qase.io`
28
+ * (no path). This is the OAuth proxy base — `/authorize`, `/token`,
29
+ * `/register` and the AS metadata are mounted here. Kept decoupled from
30
+ * `resourceUrl` so the `/mcp` path on the resource identifier never leaks
31
+ * into the proxy endpoint paths.
32
+ */
33
+ publicUrl: string;
34
+ }
35
+ /**
36
+ * Read OAuth configuration from the environment, falling back to Qase
37
+ * production defaults. OAuth is enabled unless explicitly disabled.
38
+ */
39
+ export declare function getOAuthConfig(): OAuthConfig;
40
+ //# sourceMappingURL=oauth-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-config.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,WAAW,CA4B5C"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Read OAuth configuration from the environment, falling back to Qase
3
+ * production defaults. OAuth is enabled unless explicitly disabled.
4
+ */
5
+ export function getOAuthConfig() {
6
+ const env = process.env;
7
+ const resourceUrl = env.QASE_OAUTH_RESOURCE_URL ?? 'https://mcp.qase.io/mcp';
8
+ return {
9
+ enabled: env.QASE_OAUTH_ENABLED !== 'false',
10
+ authorizationUrl: env.QASE_OAUTH_AUTHORIZATION_URL ?? 'https://auth.qase.io/oauth/authorize',
11
+ tokenUrl: env.QASE_OAUTH_TOKEN_URL ?? 'https://auth.qase.io/oauth/token',
12
+ registrationUrl: env.QASE_OAUTH_REGISTRATION_URL ?? 'https://auth.qase.io/oauth/register',
13
+ revocationUrl: env.QASE_OAUTH_REVOCATION_URL ?? '',
14
+ jwksUrl: env.QASE_OAUTH_JWKS_URL ?? 'https://auth.qase.io/oauth/jwks.json',
15
+ issuer: env.QASE_OAUTH_ISSUER ?? 'https://auth.qase.io',
16
+ // The AS canonicalizes the token audience to the origin WITH a trailing
17
+ // slash (`https://mcp.qase.io/`), regardless of which resource variant the
18
+ // client sent, so that value MUST be accepted. The `/mcp` form is kept for
19
+ // defense in depth. Override with comma-separated QASE_OAUTH_AUDIENCE.
20
+ audience: (env.QASE_OAUTH_AUDIENCE ?? 'https://mcp.qase.io/,https://mcp.qase.io/mcp')
21
+ .split(',')
22
+ .map((a) => a.trim())
23
+ .filter(Boolean),
24
+ jwtAlgorithms: (env.QASE_OAUTH_JWT_ALGORITHMS ?? 'RS256')
25
+ .split(',')
26
+ .map((a) => a.trim())
27
+ .filter(Boolean),
28
+ resourceUrl,
29
+ // Decoupled from resourceUrl: default to its ORIGIN so the proxy base never
30
+ // inherits the `/mcp` path. Override independently with QASE_OAUTH_PUBLIC_URL.
31
+ publicUrl: env.QASE_OAUTH_PUBLIC_URL ?? new URL(resourceUrl).origin,
32
+ };
33
+ }
34
+ //# sourceMappingURL=oauth-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-config.js","sourceRoot":"","sources":["../../src/auth/oauth-config.ts"],"names":[],"mappings":"AAmCA;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACxB,MAAM,WAAW,GAAG,GAAG,CAAC,uBAAuB,IAAI,yBAAyB,CAAC;IAC7E,OAAO;QACL,OAAO,EAAE,GAAG,CAAC,kBAAkB,KAAK,OAAO;QAC3C,gBAAgB,EAAE,GAAG,CAAC,4BAA4B,IAAI,sCAAsC;QAC5F,QAAQ,EAAE,GAAG,CAAC,oBAAoB,IAAI,kCAAkC;QACxE,eAAe,EAAE,GAAG,CAAC,2BAA2B,IAAI,qCAAqC;QACzF,aAAa,EAAE,GAAG,CAAC,yBAAyB,IAAI,EAAE;QAClD,OAAO,EAAE,GAAG,CAAC,mBAAmB,IAAI,sCAAsC;QAC1E,MAAM,EAAE,GAAG,CAAC,iBAAiB,IAAI,sBAAsB;QACvD,wEAAwE;QACxE,2EAA2E;QAC3E,2EAA2E;QAC3E,uEAAuE;QACvE,QAAQ,EAAE,CAAC,GAAG,CAAC,mBAAmB,IAAI,8CAA8C,CAAC;aAClF,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC;QAClB,aAAa,EAAE,CAAC,GAAG,CAAC,yBAAyB,IAAI,OAAO,CAAC;aACtD,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;aACpB,MAAM,CAAC,OAAO,CAAC;QAClB,WAAW;QACX,4EAA4E;QAC5E,+EAA+E;QAC/E,SAAS,EAAE,GAAG,CAAC,qBAAqB,IAAI,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM;KACpE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { ProxyOAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js';
2
+ import type { FetchLike } from '@modelcontextprotocol/sdk/shared/transport.js';
3
+ import type { OAuthConfig } from './oauth-config.js';
4
+ import type { JwksVerifier } from './jwks-verifier.js';
5
+ /**
6
+ * Wraps fetch to adapt both the outgoing DCR request and the incoming registration
7
+ * response so they are compatible with the Qase AS and the SDK schema.
8
+ *
9
+ * Request side: the Qase AS only supports token_endpoint_auth_method "none" and
10
+ * "client_secret_basic". Claude registers with "client_secret_post", which the AS
11
+ * rejects (400 invalid_client_metadata). Rewrite it to "none" (public + PKCE) — the
12
+ * recommended model for browser MCP clients, and the proxy never uses HTTP Basic.
13
+ *
14
+ * Response side: the Qase AS returns `client_secret: null` for public clients, but the
15
+ * SDK's OAuthClientInformationFullSchema expects a string or an omitted field. Strip the
16
+ * null field (RFC 7591: client_secret is optional / omitted for public clients).
17
+ *
18
+ * Only the registration endpoint is touched; all other requests pass through unchanged.
19
+ */
20
+ export declare function createRegistrationSanitizingFetch(registrationUrl: string, underlyingFetch?: FetchLike): FetchLike;
21
+ /**
22
+ * Build a ProxyOAuthServerProvider that transparently forwards /authorize,
23
+ * /token, /register, and /revoke to auth.qase.io. Access tokens are verified
24
+ * locally with the JWKS verifier (defence-in-depth).
25
+ *
26
+ * Dynamic client registration is proxied upstream, so auth.qase.io is the
27
+ * source of truth for clients. `getClient` echoes a permissive record so the
28
+ * SDK's redirect-uri validation in /authorize passes for proxied clients.
29
+ */
30
+ export declare function createProxyProvider(config: OAuthConfig, verifier: JwksVerifier): ProxyOAuthServerProvider;
31
+ //# sourceMappingURL=proxy-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-provider.d.ts","sourceRoot":"","sources":["../../src/auth/proxy-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,MAAM,kEAAkE,CAAC;AAE5G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iCAAiC,CAC/C,eAAe,EAAE,MAAM,EACvB,eAAe,GAAE,SAA4B,GAC5C,SAAS,CAwCX;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,YAAY,GACrB,wBAAwB,CAoB1B"}
@@ -0,0 +1,87 @@
1
+ // src/auth/proxy-provider.ts
2
+ import { ProxyOAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js';
3
+ import { authorizeRedirectUriStorage } from './client-context.js';
4
+ /**
5
+ * Wraps fetch to adapt both the outgoing DCR request and the incoming registration
6
+ * response so they are compatible with the Qase AS and the SDK schema.
7
+ *
8
+ * Request side: the Qase AS only supports token_endpoint_auth_method "none" and
9
+ * "client_secret_basic". Claude registers with "client_secret_post", which the AS
10
+ * rejects (400 invalid_client_metadata). Rewrite it to "none" (public + PKCE) — the
11
+ * recommended model for browser MCP clients, and the proxy never uses HTTP Basic.
12
+ *
13
+ * Response side: the Qase AS returns `client_secret: null` for public clients, but the
14
+ * SDK's OAuthClientInformationFullSchema expects a string or an omitted field. Strip the
15
+ * null field (RFC 7591: client_secret is optional / omitted for public clients).
16
+ *
17
+ * Only the registration endpoint is touched; all other requests pass through unchanged.
18
+ */
19
+ export function createRegistrationSanitizingFetch(registrationUrl, underlyingFetch = globalThis.fetch) {
20
+ return async (url, init) => {
21
+ // Request side: rewrite unsupported token_endpoint_auth_method before forwarding.
22
+ let effectiveInit = init;
23
+ if (String(url) === registrationUrl && init?.body != null) {
24
+ try {
25
+ const reqBody = JSON.parse(String(init.body));
26
+ if (reqBody.token_endpoint_auth_method === 'client_secret_post') {
27
+ reqBody.token_endpoint_auth_method = 'none';
28
+ effectiveInit = { ...init, body: JSON.stringify(reqBody) };
29
+ }
30
+ }
31
+ catch {
32
+ // Non-JSON body — forward unchanged.
33
+ }
34
+ }
35
+ const res = await underlyingFetch(url, effectiveInit);
36
+ if (String(url) !== registrationUrl || !res.ok) {
37
+ return res;
38
+ }
39
+ // Response side: strip null client_secret so the SDK schema parses correctly.
40
+ const data = await res
41
+ .clone()
42
+ .json()
43
+ .catch(() => null);
44
+ if (data &&
45
+ typeof data === 'object' &&
46
+ data.client_secret === null) {
47
+ delete data.client_secret;
48
+ return new Response(JSON.stringify(data), {
49
+ status: res.status,
50
+ statusText: res.statusText,
51
+ headers: { 'content-type': 'application/json' },
52
+ });
53
+ }
54
+ return res;
55
+ };
56
+ }
57
+ /**
58
+ * Build a ProxyOAuthServerProvider that transparently forwards /authorize,
59
+ * /token, /register, and /revoke to auth.qase.io. Access tokens are verified
60
+ * locally with the JWKS verifier (defence-in-depth).
61
+ *
62
+ * Dynamic client registration is proxied upstream, so auth.qase.io is the
63
+ * source of truth for clients. `getClient` echoes a permissive record so the
64
+ * SDK's redirect-uri validation in /authorize passes for proxied clients.
65
+ */
66
+ export function createProxyProvider(config, verifier) {
67
+ return new ProxyOAuthServerProvider({
68
+ endpoints: {
69
+ authorizationUrl: config.authorizationUrl,
70
+ tokenUrl: config.tokenUrl,
71
+ registrationUrl: config.registrationUrl,
72
+ revocationUrl: config.revocationUrl,
73
+ },
74
+ fetch: createRegistrationSanitizingFetch(config.registrationUrl),
75
+ verifyAccessToken: (token) => verifier.verifyJwt(token),
76
+ getClient: async (clientId) => {
77
+ // Transparent proxy: echo the request's redirect_uri so the SDK's local
78
+ // /authorize check passes. auth.qase.io is the authoritative validator.
79
+ const redirectUri = authorizeRedirectUriStorage.getStore();
80
+ return {
81
+ client_id: clientId,
82
+ redirect_uris: redirectUri ? [redirectUri] : [],
83
+ };
84
+ },
85
+ });
86
+ }
87
+ //# sourceMappingURL=proxy-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-provider.js","sourceRoot":"","sources":["../../src/auth/proxy-provider.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,kEAAkE,CAAC;AAK5G,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iCAAiC,CAC/C,eAAuB,EACvB,kBAA6B,UAAU,CAAC,KAAK;IAE7C,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACzB,kFAAkF;QAClF,IAAI,aAAa,GAAG,IAAI,CAAC;QACzB,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,eAAe,IAAI,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC;YAC1D,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAA4B,CAAC;gBACzE,IAAI,OAAO,CAAC,0BAA0B,KAAK,oBAAoB,EAAE,CAAC;oBAChE,OAAO,CAAC,0BAA0B,GAAG,MAAM,CAAC;oBAC5C,aAAa,GAAG,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC7D,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,qCAAqC;YACvC,CAAC;QACH,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QACtD,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,eAAe,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YAC/C,OAAO,GAAG,CAAC;QACb,CAAC;QAED,8EAA8E;QAC9E,MAAM,IAAI,GAAG,MAAM,GAAG;aACnB,KAAK,EAAE;aACP,IAAI,EAAE;aACN,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACrB,IACE,IAAI;YACJ,OAAO,IAAI,KAAK,QAAQ;YACvB,IAAgC,CAAC,aAAa,KAAK,IAAI,EACxD,CAAC;YACD,OAAQ,IAAgC,CAAC,aAAa,CAAC;YACvD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;gBACxC,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;aAChD,CAAC,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAmB,EACnB,QAAsB;IAEtB,OAAO,IAAI,wBAAwB,CAAC;QAClC,SAAS,EAAE;YACT,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;YACzC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,eAAe,EAAE,MAAM,CAAC,eAAe;YACvC,aAAa,EAAE,MAAM,CAAC,aAAa;SACpC;QACD,KAAK,EAAE,iCAAiC,CAAC,MAAM,CAAC,eAAe,CAAC;QAChE,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC;QACvD,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAuC,EAAE;YACjE,wEAAwE;YACxE,wEAAwE;YACxE,MAAM,WAAW,GAAG,2BAA2B,CAAC,QAAQ,EAAE,CAAC;YAC3D,OAAO;gBACL,SAAS,EAAE,QAAQ;gBACnB,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE;aAChD,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A JWT has exactly three non-empty dot-separated segments
3
+ * (header.payload.signature). Anything else is treated as an opaque
4
+ * legacy api-token.
5
+ */
6
+ export declare function isJwt(token: string): boolean;
7
+ //# sourceMappingURL=token-type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-type.d.ts","sourceRoot":"","sources":["../../src/auth/token-type.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAG5C"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A JWT has exactly three non-empty dot-separated segments
3
+ * (header.payload.signature). Anything else is treated as an opaque
4
+ * legacy api-token.
5
+ */
6
+ export function isJwt(token) {
7
+ const parts = token.split('.');
8
+ return parts.length === 3 && parts.every((p) => p.length > 0);
9
+ }
10
+ //# sourceMappingURL=token-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-type.js","sourceRoot":"","sources":["../../src/auth/token-type.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,KAAK,CAAC,KAAa;IACjC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,33 @@
1
+ export type CircuitState = 'closed' | 'open' | 'half_open';
2
+ export interface CircuitBreakerOptions {
3
+ name: string;
4
+ /** Consecutive failures that flip the state from closed to open. */
5
+ failureThreshold: number;
6
+ /** Time in open state before transitioning to half-open. */
7
+ openDurationMs: number;
8
+ /** Optional callback invoked on every state transition. */
9
+ onStateChange?: (state: CircuitState) => void;
10
+ }
11
+ export declare class CircuitOpenError extends Error {
12
+ constructor(name: string);
13
+ }
14
+ /**
15
+ * Three-state circuit breaker: closed → open → half_open → closed/open.
16
+ *
17
+ * - closed: calls pass through; consecutive failures accumulate
18
+ * - open: calls short-circuit with CircuitOpenError until openDurationMs passes
19
+ * - half_open: the next call probes the upstream; success closes, failure reopens
20
+ */
21
+ export declare class CircuitBreaker {
22
+ private _state;
23
+ private consecutiveFailures;
24
+ private openedAt;
25
+ private readonly opts;
26
+ constructor(opts: CircuitBreakerOptions);
27
+ get state(): CircuitState;
28
+ exec<T>(fn: () => Promise<T>): Promise<T>;
29
+ private onSuccess;
30
+ private onFailure;
31
+ private transition;
32
+ }
33
+ //# sourceMappingURL=circuit-breaker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/cache/circuit-breaker.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,gBAAgB,EAAE,MAAM,CAAC;IACzB,4DAA4D;IAC5D,cAAc,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CAC/C;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,IAAI,EAAE,MAAM;CAKzB;AAED;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAwB;gBAEjC,IAAI,EAAE,qBAAqB;IAIvC,IAAI,KAAK,IAAI,YAAY,CAKxB;IAEK,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAe/C,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,SAAS;IAajB,OAAO,CAAC,UAAU;CAKnB"}
@@ -0,0 +1,68 @@
1
+ export class CircuitOpenError extends Error {
2
+ constructor(name) {
3
+ super(`CircuitBreaker[${name}] is open`);
4
+ this.name = 'CircuitOpenError';
5
+ Object.setPrototypeOf(this, CircuitOpenError.prototype);
6
+ }
7
+ }
8
+ /**
9
+ * Three-state circuit breaker: closed → open → half_open → closed/open.
10
+ *
11
+ * - closed: calls pass through; consecutive failures accumulate
12
+ * - open: calls short-circuit with CircuitOpenError until openDurationMs passes
13
+ * - half_open: the next call probes the upstream; success closes, failure reopens
14
+ */
15
+ export class CircuitBreaker {
16
+ _state = 'closed';
17
+ consecutiveFailures = 0;
18
+ openedAt = 0;
19
+ opts;
20
+ constructor(opts) {
21
+ this.opts = opts;
22
+ }
23
+ get state() {
24
+ if (this._state === 'open' && Date.now() - this.openedAt >= this.opts.openDurationMs) {
25
+ this.transition('half_open');
26
+ }
27
+ return this._state;
28
+ }
29
+ async exec(fn) {
30
+ const s = this.state;
31
+ if (s === 'open') {
32
+ throw new CircuitOpenError(this.opts.name);
33
+ }
34
+ try {
35
+ const result = await fn();
36
+ this.onSuccess();
37
+ return result;
38
+ }
39
+ catch (err) {
40
+ this.onFailure();
41
+ throw err;
42
+ }
43
+ }
44
+ onSuccess() {
45
+ this.consecutiveFailures = 0;
46
+ if (this._state === 'half_open')
47
+ this.transition('closed');
48
+ }
49
+ onFailure() {
50
+ if (this._state === 'half_open') {
51
+ this.openedAt = Date.now();
52
+ this.transition('open');
53
+ return;
54
+ }
55
+ this.consecutiveFailures += 1;
56
+ if (this.consecutiveFailures >= this.opts.failureThreshold) {
57
+ this.openedAt = Date.now();
58
+ this.transition('open');
59
+ }
60
+ }
61
+ transition(next) {
62
+ if (this._state === next)
63
+ return;
64
+ this._state = next;
65
+ this.opts.onStateChange?.(next);
66
+ }
67
+ }
68
+ //# sourceMappingURL=circuit-breaker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../src/cache/circuit-breaker.ts"],"names":[],"mappings":"AAYA,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACzC,YAAY,IAAY;QACtB,KAAK,CAAC,kBAAkB,IAAI,WAAW,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC1D,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,cAAc;IACjB,MAAM,GAAiB,QAAQ,CAAC;IAChC,mBAAmB,GAAG,CAAC,CAAC;IACxB,QAAQ,GAAG,CAAC,CAAC;IACJ,IAAI,CAAwB;IAE7C,YAAY,IAA2B;QACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,IAAI,KAAK;QACP,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;YACrF,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,IAAI,CAAI,EAAoB;QAChC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QACrB,IAAI,CAAC,KAAK,MAAM,EAAE,CAAC;YACjB,MAAM,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC;YAC1B,IAAI,CAAC,SAAS,EAAE,CAAC;YACjB,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,SAAS,EAAE,CAAC;YACjB,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAEO,SAAS;QACf,IAAI,CAAC,mBAAmB,GAAG,CAAC,CAAC;QAC7B,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW;YAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC7D,CAAC;IAEO,SAAS;QACf,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YAChC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;YACxB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,mBAAmB,IAAI,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,mBAAmB,IAAI,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC3D,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAEO,UAAU,CAAC,IAAkB;QACnC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI;YAAE,OAAO;QACjC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;CACF"}
@@ -0,0 +1,24 @@
1
+ import { CacheBackend } from './types.js';
2
+ export { hashToken, buildCacheKey } from './keys.js';
3
+ export type { CacheBackend, InvalidationBus } from './types.js';
4
+ export { InMemoryCache } from './memory.js';
5
+ export { RedisCache } from './redis.js';
6
+ export { RedisInvalidationBus } from './redis-bus.js';
7
+ export { LayeredCache } from './layered.js';
8
+ export { getMetrics, resetMetricsForTest } from './metrics.js';
9
+ /**
10
+ * Build the process-wide cache backend.
11
+ *
12
+ * If QASE_MCP_REDIS_URL is set, compose LayeredCache(L1 in-memory, L2 Redis,
13
+ * pub/sub bus). Otherwise return plain InMemoryCache. The ioredis module is
14
+ * dynamically imported only when the URL is present — stdio users never load it.
15
+ */
16
+ export declare function buildCache(): Promise<CacheBackend>;
17
+ /**
18
+ * Lazy async singleton. First call triggers buildCache(); concurrent callers
19
+ * share the same promise so we build exactly once.
20
+ */
21
+ export declare function getCache(): Promise<CacheBackend>;
22
+ /** @internal */
23
+ export declare function resetCacheForTest(): Promise<void>;
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAM1C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACrD,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAM/D;;;;;;GAMG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CA4CxD;AAKD;;;GAGG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,YAAY,CAAC,CAUtD;AAED,gBAAgB;AAChB,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMvD"}
@@ -0,0 +1,86 @@
1
+ import { InMemoryCache } from './memory.js';
2
+ import { RedisCache } from './redis.js';
3
+ import { RedisInvalidationBus } from './redis-bus.js';
4
+ import { LayeredCache } from './layered.js';
5
+ export { hashToken, buildCacheKey } from './keys.js';
6
+ export { InMemoryCache } from './memory.js';
7
+ export { RedisCache } from './redis.js';
8
+ export { RedisInvalidationBus } from './redis-bus.js';
9
+ export { LayeredCache } from './layered.js';
10
+ export { getMetrics, resetMetricsForTest } from './metrics.js';
11
+ const DEFAULT_MAX_ENTRIES = 1000;
12
+ const DEFAULT_MAX_PER_TENANT = 50;
13
+ const INVALIDATION_CHANNEL = 'qase-mcp:invalidations';
14
+ /**
15
+ * Build the process-wide cache backend.
16
+ *
17
+ * If QASE_MCP_REDIS_URL is set, compose LayeredCache(L1 in-memory, L2 Redis,
18
+ * pub/sub bus). Otherwise return plain InMemoryCache. The ioredis module is
19
+ * dynamically imported only when the URL is present — stdio users never load it.
20
+ */
21
+ export async function buildCache() {
22
+ const l1 = new InMemoryCache({
23
+ maxEntries: DEFAULT_MAX_ENTRIES,
24
+ maxPerTenant: DEFAULT_MAX_PER_TENANT,
25
+ });
26
+ const redisUrl = process.env.QASE_MCP_REDIS_URL;
27
+ if (!redisUrl)
28
+ return l1;
29
+ let Redis;
30
+ try {
31
+ const mod = await import('ioredis');
32
+ Redis = mod.default ?? mod.Redis;
33
+ }
34
+ catch (err) {
35
+ console.error('[cache] QASE_MCP_REDIS_URL is set but the optional `ioredis` dependency is not installed. ' +
36
+ 'Falling back to in-memory cache only.', err);
37
+ return l1;
38
+ }
39
+ // Fail fast when Redis is unreachable so cache ops degrade to L1 instead of
40
+ // hanging the request. Without enableOfflineQueue:false + timeouts, ioredis
41
+ // queues commands until (re)connect, which blocks getCache()/get()/set() and
42
+ // times out the whole tool call when Redis is misconfigured or down.
43
+ const commandOpts = {
44
+ maxRetriesPerRequest: 1,
45
+ enableReadyCheck: true,
46
+ enableOfflineQueue: false,
47
+ connectTimeout: 3000,
48
+ commandTimeout: 2000,
49
+ };
50
+ // The subscriber keeps its offline queue so the (re)subscribe survives
51
+ // reconnects and cross-instance invalidation resumes once Redis is back.
52
+ const subOpts = { maxRetriesPerRequest: 1, enableReadyCheck: true, connectTimeout: 3000 };
53
+ const l2Client = new Redis(redisUrl, commandOpts);
54
+ const pubClient = new Redis(redisUrl, commandOpts);
55
+ const subClient = new Redis(redisUrl, subOpts);
56
+ const l2 = new RedisCache(l2Client);
57
+ const bus = new RedisInvalidationBus(pubClient, subClient, INVALIDATION_CHANNEL);
58
+ return new LayeredCache(l1, l2, bus);
59
+ }
60
+ let instance = null;
61
+ let pending = null;
62
+ /**
63
+ * Lazy async singleton. First call triggers buildCache(); concurrent callers
64
+ * share the same promise so we build exactly once.
65
+ */
66
+ export async function getCache() {
67
+ if (instance)
68
+ return instance;
69
+ if (!pending) {
70
+ pending = buildCache().then((c) => {
71
+ instance = c;
72
+ pending = null;
73
+ return c;
74
+ });
75
+ }
76
+ return pending;
77
+ }
78
+ /** @internal */
79
+ export async function resetCacheForTest() {
80
+ if (instance) {
81
+ await instance.close();
82
+ instance = null;
83
+ }
84
+ pending = null;
85
+ }
86
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cache/index.ts"],"names":[],"mappings":"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"}