@mcp-z/client 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/AGENTS.md +159 -0
  2. package/LICENSE +21 -0
  3. package/README.md +90 -0
  4. package/dist/cjs/auth/capability-discovery.d.cts +25 -0
  5. package/dist/cjs/auth/capability-discovery.d.ts +25 -0
  6. package/dist/cjs/auth/capability-discovery.js +280 -0
  7. package/dist/cjs/auth/capability-discovery.js.map +1 -0
  8. package/dist/cjs/auth/index.d.cts +9 -0
  9. package/dist/cjs/auth/index.d.ts +9 -0
  10. package/dist/cjs/auth/index.js +28 -0
  11. package/dist/cjs/auth/index.js.map +1 -0
  12. package/dist/cjs/auth/interactive-oauth-flow.d.cts +58 -0
  13. package/dist/cjs/auth/interactive-oauth-flow.d.ts +58 -0
  14. package/dist/cjs/auth/interactive-oauth-flow.js +537 -0
  15. package/dist/cjs/auth/interactive-oauth-flow.js.map +1 -0
  16. package/dist/cjs/auth/oauth-callback-listener.d.cts +56 -0
  17. package/dist/cjs/auth/oauth-callback-listener.d.ts +56 -0
  18. package/dist/cjs/auth/oauth-callback-listener.js +333 -0
  19. package/dist/cjs/auth/oauth-callback-listener.js.map +1 -0
  20. package/dist/cjs/auth/pkce.d.cts +17 -0
  21. package/dist/cjs/auth/pkce.d.ts +17 -0
  22. package/dist/cjs/auth/pkce.js +192 -0
  23. package/dist/cjs/auth/pkce.js.map +1 -0
  24. package/dist/cjs/auth/rfc9728-discovery.d.cts +34 -0
  25. package/dist/cjs/auth/rfc9728-discovery.d.ts +34 -0
  26. package/dist/cjs/auth/rfc9728-discovery.js +436 -0
  27. package/dist/cjs/auth/rfc9728-discovery.js.map +1 -0
  28. package/dist/cjs/auth/types.d.cts +137 -0
  29. package/dist/cjs/auth/types.d.ts +137 -0
  30. package/dist/cjs/auth/types.js +9 -0
  31. package/dist/cjs/auth/types.js.map +1 -0
  32. package/dist/cjs/client-helpers.d.cts +55 -0
  33. package/dist/cjs/client-helpers.d.ts +55 -0
  34. package/dist/cjs/client-helpers.js +128 -0
  35. package/dist/cjs/client-helpers.js.map +1 -0
  36. package/dist/cjs/config/server-loader.d.cts +27 -0
  37. package/dist/cjs/config/server-loader.d.ts +27 -0
  38. package/dist/cjs/config/server-loader.js +111 -0
  39. package/dist/cjs/config/server-loader.js.map +1 -0
  40. package/dist/cjs/config/validate-config.d.cts +15 -0
  41. package/dist/cjs/config/validate-config.d.ts +15 -0
  42. package/dist/cjs/config/validate-config.js +128 -0
  43. package/dist/cjs/config/validate-config.js.map +1 -0
  44. package/dist/cjs/connection/connect-client.d.cts +59 -0
  45. package/dist/cjs/connection/connect-client.d.ts +59 -0
  46. package/dist/cjs/connection/connect-client.js +536 -0
  47. package/dist/cjs/connection/connect-client.js.map +1 -0
  48. package/dist/cjs/connection/existing-process-transport.d.cts +40 -0
  49. package/dist/cjs/connection/existing-process-transport.d.ts +40 -0
  50. package/dist/cjs/connection/existing-process-transport.js +274 -0
  51. package/dist/cjs/connection/existing-process-transport.js.map +1 -0
  52. package/dist/cjs/connection/types.d.cts +61 -0
  53. package/dist/cjs/connection/types.d.ts +61 -0
  54. package/dist/cjs/connection/types.js +53 -0
  55. package/dist/cjs/connection/types.js.map +1 -0
  56. package/dist/cjs/connection/wait-for-http-ready.d.cts +15 -0
  57. package/dist/cjs/connection/wait-for-http-ready.d.ts +15 -0
  58. package/dist/cjs/connection/wait-for-http-ready.js +232 -0
  59. package/dist/cjs/connection/wait-for-http-ready.js.map +1 -0
  60. package/dist/cjs/dcr/dcr-authenticator.d.cts +73 -0
  61. package/dist/cjs/dcr/dcr-authenticator.d.ts +73 -0
  62. package/dist/cjs/dcr/dcr-authenticator.js +655 -0
  63. package/dist/cjs/dcr/dcr-authenticator.js.map +1 -0
  64. package/dist/cjs/dcr/dynamic-client-registrar.d.cts +28 -0
  65. package/dist/cjs/dcr/dynamic-client-registrar.d.ts +28 -0
  66. package/dist/cjs/dcr/dynamic-client-registrar.js +245 -0
  67. package/dist/cjs/dcr/dynamic-client-registrar.js.map +1 -0
  68. package/dist/cjs/dcr/index.d.cts +8 -0
  69. package/dist/cjs/dcr/index.d.ts +8 -0
  70. package/dist/cjs/dcr/index.js +24 -0
  71. package/dist/cjs/dcr/index.js.map +1 -0
  72. package/dist/cjs/index.d.cts +21 -0
  73. package/dist/cjs/index.d.ts +21 -0
  74. package/dist/cjs/index.js +94 -0
  75. package/dist/cjs/index.js.map +1 -0
  76. package/dist/cjs/monkey-patches.d.cts +6 -0
  77. package/dist/cjs/monkey-patches.d.ts +6 -0
  78. package/dist/cjs/monkey-patches.js +236 -0
  79. package/dist/cjs/monkey-patches.js.map +1 -0
  80. package/dist/cjs/package.json +1 -0
  81. package/dist/cjs/response-wrappers.d.cts +41 -0
  82. package/dist/cjs/response-wrappers.d.ts +41 -0
  83. package/dist/cjs/response-wrappers.js +443 -0
  84. package/dist/cjs/response-wrappers.js.map +1 -0
  85. package/dist/cjs/search/index.d.cts +6 -0
  86. package/dist/cjs/search/index.d.ts +6 -0
  87. package/dist/cjs/search/index.js +25 -0
  88. package/dist/cjs/search/index.js.map +1 -0
  89. package/dist/cjs/search/search.d.cts +22 -0
  90. package/dist/cjs/search/search.d.ts +22 -0
  91. package/dist/cjs/search/search.js +630 -0
  92. package/dist/cjs/search/search.js.map +1 -0
  93. package/dist/cjs/search/types.d.cts +122 -0
  94. package/dist/cjs/search/types.d.ts +122 -0
  95. package/dist/cjs/search/types.js +10 -0
  96. package/dist/cjs/search/types.js.map +1 -0
  97. package/dist/cjs/spawn/spawn-server.d.cts +83 -0
  98. package/dist/cjs/spawn/spawn-server.d.ts +83 -0
  99. package/dist/cjs/spawn/spawn-server.js +410 -0
  100. package/dist/cjs/spawn/spawn-server.js.map +1 -0
  101. package/dist/cjs/spawn/spawn-servers.d.cts +151 -0
  102. package/dist/cjs/spawn/spawn-servers.d.ts +151 -0
  103. package/dist/cjs/spawn/spawn-servers.js +911 -0
  104. package/dist/cjs/spawn/spawn-servers.js.map +1 -0
  105. package/dist/cjs/types.d.cts +11 -0
  106. package/dist/cjs/types.d.ts +11 -0
  107. package/dist/cjs/types.js +10 -0
  108. package/dist/cjs/types.js.map +1 -0
  109. package/dist/cjs/utils/logger.d.cts +24 -0
  110. package/dist/cjs/utils/logger.d.ts +24 -0
  111. package/dist/cjs/utils/logger.js +80 -0
  112. package/dist/cjs/utils/logger.js.map +1 -0
  113. package/dist/cjs/utils/path-utils.d.cts +45 -0
  114. package/dist/cjs/utils/path-utils.d.ts +45 -0
  115. package/dist/cjs/utils/path-utils.js +158 -0
  116. package/dist/cjs/utils/path-utils.js.map +1 -0
  117. package/dist/cjs/utils/sanitizer.d.cts +30 -0
  118. package/dist/cjs/utils/sanitizer.d.ts +30 -0
  119. package/dist/cjs/utils/sanitizer.js +124 -0
  120. package/dist/cjs/utils/sanitizer.js.map +1 -0
  121. package/dist/esm/auth/capability-discovery.d.ts +25 -0
  122. package/dist/esm/auth/capability-discovery.js +110 -0
  123. package/dist/esm/auth/capability-discovery.js.map +1 -0
  124. package/dist/esm/auth/index.d.ts +9 -0
  125. package/dist/esm/auth/index.js +6 -0
  126. package/dist/esm/auth/index.js.map +1 -0
  127. package/dist/esm/auth/interactive-oauth-flow.d.ts +58 -0
  128. package/dist/esm/auth/interactive-oauth-flow.js +217 -0
  129. package/dist/esm/auth/interactive-oauth-flow.js.map +1 -0
  130. package/dist/esm/auth/oauth-callback-listener.d.ts +56 -0
  131. package/dist/esm/auth/oauth-callback-listener.js +166 -0
  132. package/dist/esm/auth/oauth-callback-listener.js.map +1 -0
  133. package/dist/esm/auth/pkce.d.ts +17 -0
  134. package/dist/esm/auth/pkce.js +41 -0
  135. package/dist/esm/auth/pkce.js.map +1 -0
  136. package/dist/esm/auth/rfc9728-discovery.d.ts +34 -0
  137. package/dist/esm/auth/rfc9728-discovery.js +157 -0
  138. package/dist/esm/auth/rfc9728-discovery.js.map +1 -0
  139. package/dist/esm/auth/types.d.ts +137 -0
  140. package/dist/esm/auth/types.js +7 -0
  141. package/dist/esm/auth/types.js.map +1 -0
  142. package/dist/esm/client-helpers.d.ts +55 -0
  143. package/dist/esm/client-helpers.js +81 -0
  144. package/dist/esm/client-helpers.js.map +1 -0
  145. package/dist/esm/config/server-loader.d.ts +27 -0
  146. package/dist/esm/config/server-loader.js +49 -0
  147. package/dist/esm/config/server-loader.js.map +1 -0
  148. package/dist/esm/config/validate-config.d.ts +15 -0
  149. package/dist/esm/config/validate-config.js +76 -0
  150. package/dist/esm/config/validate-config.js.map +1 -0
  151. package/dist/esm/connection/connect-client.d.ts +59 -0
  152. package/dist/esm/connection/connect-client.js +272 -0
  153. package/dist/esm/connection/connect-client.js.map +1 -0
  154. package/dist/esm/connection/existing-process-transport.d.ts +40 -0
  155. package/dist/esm/connection/existing-process-transport.js +103 -0
  156. package/dist/esm/connection/existing-process-transport.js.map +1 -0
  157. package/dist/esm/connection/types.d.ts +61 -0
  158. package/dist/esm/connection/types.js +34 -0
  159. package/dist/esm/connection/types.js.map +1 -0
  160. package/dist/esm/connection/wait-for-http-ready.d.ts +15 -0
  161. package/dist/esm/connection/wait-for-http-ready.js +43 -0
  162. package/dist/esm/connection/wait-for-http-ready.js.map +1 -0
  163. package/dist/esm/dcr/dcr-authenticator.d.ts +73 -0
  164. package/dist/esm/dcr/dcr-authenticator.js +235 -0
  165. package/dist/esm/dcr/dcr-authenticator.js.map +1 -0
  166. package/dist/esm/dcr/dynamic-client-registrar.d.ts +28 -0
  167. package/dist/esm/dcr/dynamic-client-registrar.js +66 -0
  168. package/dist/esm/dcr/dynamic-client-registrar.js.map +1 -0
  169. package/dist/esm/dcr/index.d.ts +8 -0
  170. package/dist/esm/dcr/index.js +5 -0
  171. package/dist/esm/dcr/index.js.map +1 -0
  172. package/dist/esm/index.d.ts +21 -0
  173. package/dist/esm/index.js +22 -0
  174. package/dist/esm/index.js.map +1 -0
  175. package/dist/esm/monkey-patches.d.ts +6 -0
  176. package/dist/esm/monkey-patches.js +32 -0
  177. package/dist/esm/monkey-patches.js.map +1 -0
  178. package/dist/esm/package.json +1 -0
  179. package/dist/esm/response-wrappers.d.ts +41 -0
  180. package/dist/esm/response-wrappers.js +201 -0
  181. package/dist/esm/response-wrappers.js.map +1 -0
  182. package/dist/esm/search/index.d.ts +6 -0
  183. package/dist/esm/search/index.js +3 -0
  184. package/dist/esm/search/index.js.map +1 -0
  185. package/dist/esm/search/search.d.ts +22 -0
  186. package/dist/esm/search/search.js +236 -0
  187. package/dist/esm/search/search.js.map +1 -0
  188. package/dist/esm/search/types.d.ts +122 -0
  189. package/dist/esm/search/types.js +8 -0
  190. package/dist/esm/search/types.js.map +1 -0
  191. package/dist/esm/spawn/spawn-server.d.ts +83 -0
  192. package/dist/esm/spawn/spawn-server.js +145 -0
  193. package/dist/esm/spawn/spawn-server.js.map +1 -0
  194. package/dist/esm/spawn/spawn-servers.d.ts +151 -0
  195. package/dist/esm/spawn/spawn-servers.js +406 -0
  196. package/dist/esm/spawn/spawn-servers.js.map +1 -0
  197. package/dist/esm/types.d.ts +11 -0
  198. package/dist/esm/types.js +9 -0
  199. package/dist/esm/types.js.map +1 -0
  200. package/dist/esm/utils/logger.d.ts +24 -0
  201. package/dist/esm/utils/logger.js +59 -0
  202. package/dist/esm/utils/logger.js.map +1 -0
  203. package/dist/esm/utils/path-utils.d.ts +45 -0
  204. package/dist/esm/utils/path-utils.js +89 -0
  205. package/dist/esm/utils/path-utils.js.map +1 -0
  206. package/dist/esm/utils/sanitizer.d.ts +30 -0
  207. package/dist/esm/utils/sanitizer.js +43 -0
  208. package/dist/esm/utils/sanitizer.js.map +1 -0
  209. package/package.json +92 -0
  210. package/schemas/servers.d.ts +90 -0
  211. package/schemas/servers.schema.json +104 -0
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Minimal log sanitization for spawn operations
3
+ * Redacts credentials from environment variables and command arguments
4
+ */ "use strict";
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ function _export(target, all) {
9
+ for(var name in all)Object.defineProperty(target, name, {
10
+ enumerable: true,
11
+ get: Object.getOwnPropertyDescriptor(all, name).get
12
+ });
13
+ }
14
+ _export(exports, {
15
+ get sanitizeForLogging () {
16
+ return sanitizeForLogging;
17
+ },
18
+ get sanitizeForLoggingFormatter () {
19
+ return sanitizeForLoggingFormatter;
20
+ }
21
+ });
22
+ function _define_property(obj, key, value) {
23
+ if (key in obj) {
24
+ Object.defineProperty(obj, key, {
25
+ value: value,
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true
29
+ });
30
+ } else {
31
+ obj[key] = value;
32
+ }
33
+ return obj;
34
+ }
35
+ function _object_spread(target) {
36
+ for(var i = 1; i < arguments.length; i++){
37
+ var source = arguments[i] != null ? arguments[i] : {};
38
+ var ownKeys = Object.keys(source);
39
+ if (typeof Object.getOwnPropertySymbols === "function") {
40
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
41
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
42
+ }));
43
+ }
44
+ ownKeys.forEach(function(key) {
45
+ _define_property(target, key, source[key]);
46
+ });
47
+ }
48
+ return target;
49
+ }
50
+ function ownKeys(object, enumerableOnly) {
51
+ var keys = Object.keys(object);
52
+ if (Object.getOwnPropertySymbols) {
53
+ var symbols = Object.getOwnPropertySymbols(object);
54
+ if (enumerableOnly) {
55
+ symbols = symbols.filter(function(sym) {
56
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
57
+ });
58
+ }
59
+ keys.push.apply(keys, symbols);
60
+ }
61
+ return keys;
62
+ }
63
+ function _object_spread_props(target, source) {
64
+ source = source != null ? source : {};
65
+ if (Object.getOwnPropertyDescriptors) {
66
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
67
+ } else {
68
+ ownKeys(Object(source)).forEach(function(key) {
69
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
70
+ });
71
+ }
72
+ return target;
73
+ }
74
+ function _type_of(obj) {
75
+ "@swc/helpers - typeof";
76
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
77
+ }
78
+ function sanitizeForLogging(message, obj) {
79
+ // Redact common credential patterns in message
80
+ var cleanMessage = message.replace(/key[=:]\S+/gi, 'key=[REDACTED]').replace(/secret[=:]\S+/gi, 'secret=[REDACTED]').replace(/token[=:]\S+/gi, 'token=[REDACTED]').replace(/password[=:]\S+/gi, 'password=[REDACTED]').replace(/auth[=:]\S+/gi, 'auth=[REDACTED]');
81
+ // Deep clone and redact sensitive env var keys
82
+ var cleanMeta = JSON.parse(JSON.stringify(obj));
83
+ // Redact sensitive environment variable values
84
+ if (cleanMeta.env && _type_of(cleanMeta.env) === 'object') {
85
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
86
+ try {
87
+ for(var _iterator = Object.keys(cleanMeta.env)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
88
+ var envKey = _step.value;
89
+ if (/key|secret|token|password|auth|credential/i.test(envKey)) {
90
+ cleanMeta.env[envKey] = '[REDACTED]';
91
+ }
92
+ }
93
+ } catch (err) {
94
+ _didIteratorError = true;
95
+ _iteratorError = err;
96
+ } finally{
97
+ try {
98
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
99
+ _iterator.return();
100
+ }
101
+ } finally{
102
+ if (_didIteratorError) {
103
+ throw _iteratorError;
104
+ }
105
+ }
106
+ }
107
+ }
108
+ return {
109
+ message: cleanMessage,
110
+ meta: cleanMeta
111
+ };
112
+ }
113
+ function sanitizeForLoggingFormatter() {
114
+ return {
115
+ log: function(obj) {
116
+ var message = obj.msg || obj.message || '';
117
+ var _sanitizeForLogging = sanitizeForLogging(message, obj), clean = _sanitizeForLogging.message, meta = _sanitizeForLogging.meta;
118
+ return _object_spread_props(_object_spread({}, meta), {
119
+ msg: clean
120
+ });
121
+ }
122
+ };
123
+ }
124
+ /* CJS INTEROP */ if (exports.__esModule && exports.default) { try { Object.defineProperty(exports.default, '__esModule', { value: true }); for (var key in exports) { exports.default[key] = exports[key]; } } catch (_) {}; module.exports = exports.default; }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/utils/sanitizer.ts"],"sourcesContent":["/**\n * Minimal log sanitization for spawn operations\n * Redacts credentials from environment variables and command arguments\n */\n\nimport type { SpawnMetadata } from '../connection/types.ts';\n\n/**\n * Sanitize log messages and metadata to prevent credential leakage\n *\n * Redacts common credential patterns:\n * - key=value, secret=value, token=value, password=value\n * - Environment variables with sensitive names\n *\n * @param message - Log message to sanitize\n * @param obj - Metadata object to sanitize\n * @returns Sanitized message and metadata\n */\nexport function sanitizeForLogging(message: string, obj: SpawnMetadata): { message: string; meta: SpawnMetadata } {\n // Redact common credential patterns in message\n const cleanMessage = message\n .replace(/key[=:]\\S+/gi, 'key=[REDACTED]')\n .replace(/secret[=:]\\S+/gi, 'secret=[REDACTED]')\n .replace(/token[=:]\\S+/gi, 'token=[REDACTED]')\n .replace(/password[=:]\\S+/gi, 'password=[REDACTED]')\n .replace(/auth[=:]\\S+/gi, 'auth=[REDACTED]');\n\n // Deep clone and redact sensitive env var keys\n const cleanMeta = JSON.parse(JSON.stringify(obj));\n\n // Redact sensitive environment variable values\n if (cleanMeta.env && typeof cleanMeta.env === 'object') {\n for (const envKey of Object.keys(cleanMeta.env)) {\n if (/key|secret|token|password|auth|credential/i.test(envKey)) {\n cleanMeta.env[envKey] = '[REDACTED]';\n }\n }\n }\n\n return { message: cleanMessage, meta: cleanMeta };\n}\n\nexport function sanitizeForLoggingFormatter() {\n return {\n log: (obj: SpawnMetadata) => {\n const message = (obj.msg || obj.message || '') as string;\n const { message: clean, meta } = sanitizeForLogging(message, obj);\n return { ...meta, msg: clean };\n },\n };\n}\n"],"names":["sanitizeForLogging","sanitizeForLoggingFormatter","message","obj","cleanMessage","replace","cleanMeta","JSON","parse","stringify","env","Object","keys","envKey","test","meta","log","msg","clean"],"mappings":"AAAA;;;CAGC;;;;;;;;;;;QAeeA;eAAAA;;QAwBAC;eAAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAxBT,SAASD,mBAAmBE,OAAe,EAAEC,GAAkB;IACpE,+CAA+C;IAC/C,IAAMC,eAAeF,QAClBG,OAAO,CAAC,gBAAgB,kBACxBA,OAAO,CAAC,mBAAmB,qBAC3BA,OAAO,CAAC,kBAAkB,oBAC1BA,OAAO,CAAC,qBAAqB,uBAC7BA,OAAO,CAAC,iBAAiB;IAE5B,+CAA+C;IAC/C,IAAMC,YAAYC,KAAKC,KAAK,CAACD,KAAKE,SAAS,CAACN;IAE5C,+CAA+C;IAC/C,IAAIG,UAAUI,GAAG,IAAI,SAAOJ,UAAUI,GAAG,MAAK,UAAU;YACjD,kCAAA,2BAAA;;YAAL,QAAK,YAAgBC,OAAOC,IAAI,CAACN,UAAUI,GAAG,sBAAzC,SAAA,6BAAA,QAAA,yBAAA,iCAA4C;gBAA5C,IAAMG,SAAN;gBACH,IAAI,6CAA6CC,IAAI,CAACD,SAAS;oBAC7DP,UAAUI,GAAG,CAACG,OAAO,GAAG;gBAC1B;YACF;;YAJK;YAAA;;;qBAAA,6BAAA;oBAAA;;;oBAAA;0BAAA;;;;IAKP;IAEA,OAAO;QAAEX,SAASE;QAAcW,MAAMT;IAAU;AAClD;AAEO,SAASL;IACd,OAAO;QACLe,KAAK,SAACb;YACJ,IAAMD,UAAWC,IAAIc,GAAG,IAAId,IAAID,OAAO,IAAI;YAC3C,IAAiCF,sBAAAA,mBAAmBE,SAASC,MAArDD,AAASgB,QAAgBlB,oBAAzBE,SAAgBa,OAASf,oBAATe;YACxB,OAAO,wCAAKA;gBAAME,KAAKC;;QACzB;IACF;AACF"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * OAuth Server Capability Discovery
3
+ * Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata
4
+ */
5
+ import type { AuthCapabilities } from './types.js';
6
+ /**
7
+ * Probe OAuth server capabilities using RFC 9728 → RFC 8414 discovery chain
8
+ * Returns capabilities including DCR support detection
9
+ *
10
+ * Discovery Strategy:
11
+ * 1. Try RFC 9728 Protected Resource Metadata (supports cross-domain OAuth)
12
+ * 2. If found, use first authorization_server to discover RFC 8414 Authorization Server Metadata
13
+ * 3. Fall back to direct RFC 8414 discovery at resource origin
14
+ *
15
+ * @param baseUrl - Base URL of the protected resource (e.g., https://ai.todoist.net/mcp)
16
+ * @returns AuthCapabilities object with discovered endpoints and features
17
+ *
18
+ * @example
19
+ * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com
20
+ * const caps = await probeAuthCapabilities('https://ai.todoist.net/mcp');
21
+ * if (caps.supportsDcr) {
22
+ * console.log('Registration endpoint:', caps.registrationEndpoint);
23
+ * }
24
+ */
25
+ export declare function probeAuthCapabilities(baseUrl: string): Promise<AuthCapabilities>;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * OAuth Server Capability Discovery
3
+ * Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata
4
+ */ import { discoverAuthorizationServerMetadata, discoverProtectedResourceMetadata } from './rfc9728-discovery.js';
5
+ /**
6
+ * Extract origin (protocol + host) from a URL
7
+ * @param url - Full URL that may include a path
8
+ * @returns Origin (e.g., "https://example.com") or original string if invalid URL
9
+ *
10
+ * @example
11
+ * getOrigin('https://example.com/mcp') // → 'https://example.com'
12
+ * getOrigin('http://localhost:9999/api/v1/mcp') // → 'http://localhost:9999'
13
+ */ function getOrigin(url) {
14
+ try {
15
+ return new URL(url).origin;
16
+ } catch {
17
+ // Invalid URL - return as-is for graceful degradation
18
+ return url;
19
+ }
20
+ }
21
+ /**
22
+ * Probe OAuth server capabilities using RFC 9728 → RFC 8414 discovery chain
23
+ * Returns capabilities including DCR support detection
24
+ *
25
+ * Discovery Strategy:
26
+ * 1. Try RFC 9728 Protected Resource Metadata (supports cross-domain OAuth)
27
+ * 2. If found, use first authorization_server to discover RFC 8414 Authorization Server Metadata
28
+ * 3. Fall back to direct RFC 8414 discovery at resource origin
29
+ *
30
+ * @param baseUrl - Base URL of the protected resource (e.g., https://ai.todoist.net/mcp)
31
+ * @returns AuthCapabilities object with discovered endpoints and features
32
+ *
33
+ * @example
34
+ * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com
35
+ * const caps = await probeAuthCapabilities('https://ai.todoist.net/mcp');
36
+ * if (caps.supportsDcr) {
37
+ * console.log('Registration endpoint:', caps.registrationEndpoint);
38
+ * }
39
+ */ export async function probeAuthCapabilities(baseUrl) {
40
+ try {
41
+ // Strategy 1: Try RFC 9728 Protected Resource Metadata discovery
42
+ // This handles cross-domain OAuth (e.g., Todoist: ai.todoist.net/mcp → todoist.com)
43
+ const resourceMetadata = await discoverProtectedResourceMetadata(baseUrl);
44
+ if (resourceMetadata && resourceMetadata.authorization_servers.length > 0) {
45
+ // Found protected resource metadata with authorization servers
46
+ // Discover the authorization server's metadata (RFC 8414)
47
+ const authServerUrl = resourceMetadata.authorization_servers[0];
48
+ if (!authServerUrl) {
49
+ // Array has length > 0 but first element is undefined/null - skip this path
50
+ return {
51
+ supportsDcr: false
52
+ };
53
+ }
54
+ const authServerMetadata = await discoverAuthorizationServerMetadata(authServerUrl);
55
+ if (authServerMetadata) {
56
+ // Successfully discovered full OAuth metadata via RFC 9728 → RFC 8414 chain
57
+ const supportsDcr = !!authServerMetadata.registration_endpoint;
58
+ const capabilities = {
59
+ supportsDcr
60
+ };
61
+ if (authServerMetadata.registration_endpoint) {
62
+ capabilities.registrationEndpoint = authServerMetadata.registration_endpoint;
63
+ }
64
+ if (authServerMetadata.authorization_endpoint) {
65
+ capabilities.authorizationEndpoint = authServerMetadata.authorization_endpoint;
66
+ }
67
+ if (authServerMetadata.token_endpoint) capabilities.tokenEndpoint = authServerMetadata.token_endpoint;
68
+ if (authServerMetadata.introspection_endpoint) {
69
+ capabilities.introspectionEndpoint = authServerMetadata.introspection_endpoint;
70
+ }
71
+ // Prefer resource scopes over auth server scopes
72
+ const scopes = resourceMetadata.scopes_supported || authServerMetadata.scopes_supported;
73
+ if (scopes) capabilities.scopes = scopes;
74
+ return capabilities;
75
+ }
76
+ }
77
+ // Strategy 2: Fall back to direct RFC 8414 discovery at resource origin
78
+ // This handles same-domain OAuth (traditional setup)
79
+ const origin = getOrigin(baseUrl);
80
+ const authServerMetadata = await discoverAuthorizationServerMetadata(origin);
81
+ if (authServerMetadata) {
82
+ const supportsDcr = !!authServerMetadata.registration_endpoint;
83
+ const capabilities = {
84
+ supportsDcr
85
+ };
86
+ if (authServerMetadata.registration_endpoint) {
87
+ capabilities.registrationEndpoint = authServerMetadata.registration_endpoint;
88
+ }
89
+ if (authServerMetadata.authorization_endpoint) {
90
+ capabilities.authorizationEndpoint = authServerMetadata.authorization_endpoint;
91
+ }
92
+ if (authServerMetadata.token_endpoint) capabilities.tokenEndpoint = authServerMetadata.token_endpoint;
93
+ if (authServerMetadata.introspection_endpoint) {
94
+ capabilities.introspectionEndpoint = authServerMetadata.introspection_endpoint;
95
+ }
96
+ if (authServerMetadata.scopes_supported) capabilities.scopes = authServerMetadata.scopes_supported;
97
+ return capabilities;
98
+ }
99
+ // No OAuth metadata found
100
+ return {
101
+ supportsDcr: false
102
+ };
103
+ } catch (_error) {
104
+ // Network error, invalid JSON, or other fetch failure
105
+ // Gracefully degrade - assume no DCR support
106
+ return {
107
+ supportsDcr: false
108
+ };
109
+ }
110
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/auth/capability-discovery.ts"],"sourcesContent":["/**\n * OAuth Server Capability Discovery\n * Probes RFC 9728 (Protected Resource) and RFC 8414 (Authorization Server) metadata\n */\n\nimport { discoverAuthorizationServerMetadata, discoverProtectedResourceMetadata } from './rfc9728-discovery.ts';\nimport type { AuthCapabilities } from './types.ts';\n\n/**\n * Extract origin (protocol + host) from a URL\n * @param url - Full URL that may include a path\n * @returns Origin (e.g., \"https://example.com\") or original string if invalid URL\n *\n * @example\n * getOrigin('https://example.com/mcp') // → 'https://example.com'\n * getOrigin('http://localhost:9999/api/v1/mcp') // → 'http://localhost:9999'\n */\nfunction getOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n // Invalid URL - return as-is for graceful degradation\n return url;\n }\n}\n\n/**\n * Probe OAuth server capabilities using RFC 9728 → RFC 8414 discovery chain\n * Returns capabilities including DCR support detection\n *\n * Discovery Strategy:\n * 1. Try RFC 9728 Protected Resource Metadata (supports cross-domain OAuth)\n * 2. If found, use first authorization_server to discover RFC 8414 Authorization Server Metadata\n * 3. Fall back to direct RFC 8414 discovery at resource origin\n *\n * @param baseUrl - Base URL of the protected resource (e.g., https://ai.todoist.net/mcp)\n * @returns AuthCapabilities object with discovered endpoints and features\n *\n * @example\n * // Todoist case: MCP at ai.todoist.net/mcp, OAuth at todoist.com\n * const caps = await probeAuthCapabilities('https://ai.todoist.net/mcp');\n * if (caps.supportsDcr) {\n * console.log('Registration endpoint:', caps.registrationEndpoint);\n * }\n */\nexport async function probeAuthCapabilities(baseUrl: string): Promise<AuthCapabilities> {\n try {\n // Strategy 1: Try RFC 9728 Protected Resource Metadata discovery\n // This handles cross-domain OAuth (e.g., Todoist: ai.todoist.net/mcp → todoist.com)\n const resourceMetadata = await discoverProtectedResourceMetadata(baseUrl);\n\n if (resourceMetadata && resourceMetadata.authorization_servers.length > 0) {\n // Found protected resource metadata with authorization servers\n // Discover the authorization server's metadata (RFC 8414)\n const authServerUrl = resourceMetadata.authorization_servers[0];\n if (!authServerUrl) {\n // Array has length > 0 but first element is undefined/null - skip this path\n return { supportsDcr: false };\n }\n const authServerMetadata = await discoverAuthorizationServerMetadata(authServerUrl);\n\n if (authServerMetadata) {\n // Successfully discovered full OAuth metadata via RFC 9728 → RFC 8414 chain\n const supportsDcr = !!authServerMetadata.registration_endpoint;\n const capabilities: AuthCapabilities = { supportsDcr };\n\n if (authServerMetadata.registration_endpoint) {\n capabilities.registrationEndpoint = authServerMetadata.registration_endpoint;\n }\n if (authServerMetadata.authorization_endpoint) {\n capabilities.authorizationEndpoint = authServerMetadata.authorization_endpoint;\n }\n if (authServerMetadata.token_endpoint) capabilities.tokenEndpoint = authServerMetadata.token_endpoint;\n if (authServerMetadata.introspection_endpoint) {\n capabilities.introspectionEndpoint = authServerMetadata.introspection_endpoint;\n }\n\n // Prefer resource scopes over auth server scopes\n const scopes = resourceMetadata.scopes_supported || authServerMetadata.scopes_supported;\n if (scopes) capabilities.scopes = scopes;\n\n return capabilities;\n }\n }\n\n // Strategy 2: Fall back to direct RFC 8414 discovery at resource origin\n // This handles same-domain OAuth (traditional setup)\n const origin = getOrigin(baseUrl);\n const authServerMetadata = await discoverAuthorizationServerMetadata(origin);\n\n if (authServerMetadata) {\n const supportsDcr = !!authServerMetadata.registration_endpoint;\n const capabilities: AuthCapabilities = { supportsDcr };\n\n if (authServerMetadata.registration_endpoint) {\n capabilities.registrationEndpoint = authServerMetadata.registration_endpoint;\n }\n if (authServerMetadata.authorization_endpoint) {\n capabilities.authorizationEndpoint = authServerMetadata.authorization_endpoint;\n }\n if (authServerMetadata.token_endpoint) capabilities.tokenEndpoint = authServerMetadata.token_endpoint;\n if (authServerMetadata.introspection_endpoint) {\n capabilities.introspectionEndpoint = authServerMetadata.introspection_endpoint;\n }\n if (authServerMetadata.scopes_supported) capabilities.scopes = authServerMetadata.scopes_supported;\n\n return capabilities;\n }\n\n // No OAuth metadata found\n return { supportsDcr: false };\n } catch (_error) {\n // Network error, invalid JSON, or other fetch failure\n // Gracefully degrade - assume no DCR support\n return { supportsDcr: false };\n }\n}\n"],"names":["discoverAuthorizationServerMetadata","discoverProtectedResourceMetadata","getOrigin","url","URL","origin","probeAuthCapabilities","baseUrl","resourceMetadata","authorization_servers","length","authServerUrl","supportsDcr","authServerMetadata","registration_endpoint","capabilities","registrationEndpoint","authorization_endpoint","authorizationEndpoint","token_endpoint","tokenEndpoint","introspection_endpoint","introspectionEndpoint","scopes","scopes_supported","_error"],"mappings":"AAAA;;;CAGC,GAED,SAASA,mCAAmC,EAAEC,iCAAiC,QAAQ,yBAAyB;AAGhH;;;;;;;;CAQC,GACD,SAASC,UAAUC,GAAW;IAC5B,IAAI;QACF,OAAO,IAAIC,IAAID,KAAKE,MAAM;IAC5B,EAAE,OAAM;QACN,sDAAsD;QACtD,OAAOF;IACT;AACF;AAEA;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,eAAeG,sBAAsBC,OAAe;IACzD,IAAI;QACF,iEAAiE;QACjE,oFAAoF;QACpF,MAAMC,mBAAmB,MAAMP,kCAAkCM;QAEjE,IAAIC,oBAAoBA,iBAAiBC,qBAAqB,CAACC,MAAM,GAAG,GAAG;YACzE,+DAA+D;YAC/D,0DAA0D;YAC1D,MAAMC,gBAAgBH,iBAAiBC,qBAAqB,CAAC,EAAE;YAC/D,IAAI,CAACE,eAAe;gBAClB,4EAA4E;gBAC5E,OAAO;oBAAEC,aAAa;gBAAM;YAC9B;YACA,MAAMC,qBAAqB,MAAMb,oCAAoCW;YAErE,IAAIE,oBAAoB;gBACtB,4EAA4E;gBAC5E,MAAMD,cAAc,CAAC,CAACC,mBAAmBC,qBAAqB;gBAC9D,MAAMC,eAAiC;oBAAEH;gBAAY;gBAErD,IAAIC,mBAAmBC,qBAAqB,EAAE;oBAC5CC,aAAaC,oBAAoB,GAAGH,mBAAmBC,qBAAqB;gBAC9E;gBACA,IAAID,mBAAmBI,sBAAsB,EAAE;oBAC7CF,aAAaG,qBAAqB,GAAGL,mBAAmBI,sBAAsB;gBAChF;gBACA,IAAIJ,mBAAmBM,cAAc,EAAEJ,aAAaK,aAAa,GAAGP,mBAAmBM,cAAc;gBACrG,IAAIN,mBAAmBQ,sBAAsB,EAAE;oBAC7CN,aAAaO,qBAAqB,GAAGT,mBAAmBQ,sBAAsB;gBAChF;gBAEA,iDAAiD;gBACjD,MAAME,SAASf,iBAAiBgB,gBAAgB,IAAIX,mBAAmBW,gBAAgB;gBACvF,IAAID,QAAQR,aAAaQ,MAAM,GAAGA;gBAElC,OAAOR;YACT;QACF;QAEA,wEAAwE;QACxE,qDAAqD;QACrD,MAAMV,SAASH,UAAUK;QACzB,MAAMM,qBAAqB,MAAMb,oCAAoCK;QAErE,IAAIQ,oBAAoB;YACtB,MAAMD,cAAc,CAAC,CAACC,mBAAmBC,qBAAqB;YAC9D,MAAMC,eAAiC;gBAAEH;YAAY;YAErD,IAAIC,mBAAmBC,qBAAqB,EAAE;gBAC5CC,aAAaC,oBAAoB,GAAGH,mBAAmBC,qBAAqB;YAC9E;YACA,IAAID,mBAAmBI,sBAAsB,EAAE;gBAC7CF,aAAaG,qBAAqB,GAAGL,mBAAmBI,sBAAsB;YAChF;YACA,IAAIJ,mBAAmBM,cAAc,EAAEJ,aAAaK,aAAa,GAAGP,mBAAmBM,cAAc;YACrG,IAAIN,mBAAmBQ,sBAAsB,EAAE;gBAC7CN,aAAaO,qBAAqB,GAAGT,mBAAmBQ,sBAAsB;YAChF;YACA,IAAIR,mBAAmBW,gBAAgB,EAAET,aAAaQ,MAAM,GAAGV,mBAAmBW,gBAAgB;YAElG,OAAOT;QACT;QAEA,0BAA0B;QAC1B,OAAO;YAAEH,aAAa;QAAM;IAC9B,EAAE,OAAOa,QAAQ;QACf,sDAAsD;QACtD,6CAA6C;QAC7C,OAAO;YAAEb,aAAa;QAAM;IAC9B;AACF"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Authentication Module
3
+ * Exports public API for OAuth authentication only (DCR moved to ../dcr/)
4
+ */
5
+ export { probeAuthCapabilities } from './capability-discovery.js';
6
+ export { InteractiveOAuthFlow } from './interactive-oauth-flow.js';
7
+ export type { OAuthCallbackListenerOptions } from './oauth-callback-listener.js';
8
+ export { OAuthCallbackListener } from './oauth-callback-listener.js';
9
+ export type { AuthCapabilities, CallbackResult, OAuthFlowOptions, TokenSet } from './types.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Authentication Module
3
+ * Exports public API for OAuth authentication only (DCR moved to ../dcr/)
4
+ */ export { probeAuthCapabilities } from './capability-discovery.js';
5
+ export { InteractiveOAuthFlow } from './interactive-oauth-flow.js';
6
+ export { OAuthCallbackListener } from './oauth-callback-listener.js';
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/auth/index.ts"],"sourcesContent":["/**\n * Authentication Module\n * Exports public API for OAuth authentication only (DCR moved to ../dcr/)\n */\n\nexport { probeAuthCapabilities } from './capability-discovery.ts';\nexport { InteractiveOAuthFlow } from './interactive-oauth-flow.ts';\nexport type { OAuthCallbackListenerOptions } from './oauth-callback-listener.ts';\nexport { OAuthCallbackListener } from './oauth-callback-listener.ts';\nexport type { AuthCapabilities, CallbackResult, OAuthFlowOptions, TokenSet } from './types.ts';\n"],"names":["probeAuthCapabilities","InteractiveOAuthFlow","OAuthCallbackListener"],"mappings":"AAAA;;;CAGC,GAED,SAASA,qBAAqB,QAAQ,4BAA4B;AAClE,SAASC,oBAAoB,QAAQ,8BAA8B;AAEnE,SAASC,qBAAqB,QAAQ,+BAA+B"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * OAuth Authorization Flow Handler
3
+ * Manages browser-based OAuth flows and token exchange with PKCE support
4
+ */
5
+ import type { OAuthFlowOptions, TokenSet } from './types.js';
6
+ /**
7
+ * InteractiveOAuthFlow manages the complete OAuth authorization code flow
8
+ */
9
+ export declare class InteractiveOAuthFlow {
10
+ /**
11
+ * Perform OAuth authorization code flow
12
+ *
13
+ * @param authorizationEndpoint - OAuth authorization endpoint URL
14
+ * @param tokenEndpoint - OAuth token endpoint URL
15
+ * @param clientId - OAuth client ID
16
+ * @param clientSecret - OAuth client secret
17
+ * @param options - Flow options (port is required - use get-port to find available port)
18
+ * @returns Token set with access and refresh tokens
19
+ *
20
+ * @throws Error if flow fails or times out
21
+ *
22
+ * @example
23
+ * import getPort from 'get-port';
24
+ *
25
+ * const flow = new InteractiveOAuthFlow();
26
+ * const port = await getPort();
27
+ * const tokens = await flow.performAuthFlow(
28
+ * 'https://example.com/oauth/authorize',
29
+ * 'https://example.com/oauth/token',
30
+ * 'client-id',
31
+ * 'client-secret',
32
+ * { port, scopes: ['read', 'write'] }
33
+ * );
34
+ */
35
+ performAuthFlow(authorizationEndpoint: string, tokenEndpoint: string, clientId: string, clientSecret: string, options: OAuthFlowOptions): Promise<TokenSet>;
36
+ /**
37
+ * Exchange authorization code for access and refresh tokens
38
+ * @param codeVerifier - Optional PKCE code verifier (RFC 7636)
39
+ */
40
+ private exchangeCodeForTokens;
41
+ /**
42
+ * Refresh access token using refresh token
43
+ *
44
+ * @param tokenEndpoint - OAuth token endpoint URL
45
+ * @param refreshToken - Refresh token from previous token set
46
+ * @param clientId - OAuth client ID
47
+ * @param clientSecret - OAuth client secret
48
+ * @returns New token set with refreshed access token
49
+ *
50
+ * @throws Error if refresh fails
51
+ */
52
+ refreshTokens(tokenEndpoint: string, refreshToken: string, clientId: string, clientSecret: string): Promise<TokenSet>;
53
+ /**
54
+ * Open browser to authorization URL
55
+ * Uses platform-specific command to open default browser
56
+ */
57
+ private openBrowser;
58
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * OAuth Authorization Flow Handler
3
+ * Manages browser-based OAuth flows and token exchange with PKCE support
4
+ */ import * as child_process from 'node:child_process';
5
+ import { logger as defaultLogger } from '../utils/logger.js';
6
+ import { OAuthCallbackListener } from './oauth-callback-listener.js';
7
+ import { generatePkce } from './pkce.js';
8
+ /**
9
+ * InteractiveOAuthFlow manages the complete OAuth authorization code flow
10
+ */ export class InteractiveOAuthFlow {
11
+ /**
12
+ * Perform OAuth authorization code flow
13
+ *
14
+ * @param authorizationEndpoint - OAuth authorization endpoint URL
15
+ * @param tokenEndpoint - OAuth token endpoint URL
16
+ * @param clientId - OAuth client ID
17
+ * @param clientSecret - OAuth client secret
18
+ * @param options - Flow options (port is required - use get-port to find available port)
19
+ * @returns Token set with access and refresh tokens
20
+ *
21
+ * @throws Error if flow fails or times out
22
+ *
23
+ * @example
24
+ * import getPort from 'get-port';
25
+ *
26
+ * const flow = new InteractiveOAuthFlow();
27
+ * const port = await getPort();
28
+ * const tokens = await flow.performAuthFlow(
29
+ * 'https://example.com/oauth/authorize',
30
+ * 'https://example.com/oauth/token',
31
+ * 'client-id',
32
+ * 'client-secret',
33
+ * { port, scopes: ['read', 'write'] }
34
+ * );
35
+ */ async performAuthFlow(authorizationEndpoint, tokenEndpoint, clientId, clientSecret, options) {
36
+ var _options_logger;
37
+ const logger = (_options_logger = options.logger) !== null && _options_logger !== void 0 ? _options_logger : defaultLogger;
38
+ const callbackListener = new OAuthCallbackListener({
39
+ port: options.port,
40
+ logger
41
+ });
42
+ // Generate PKCE parameters if requested (RFC 7636)
43
+ let pkce;
44
+ if (options.pkce) {
45
+ logger.debug('🔐 Generating PKCE parameters...');
46
+ pkce = await generatePkce();
47
+ }
48
+ try {
49
+ // Start callback server
50
+ await callbackListener.start();
51
+ // Build redirect URI
52
+ const redirectUri = options.redirectUri || `http://localhost:${options.port}/callback`;
53
+ // Build authorization URL
54
+ const authUrl = new URL(authorizationEndpoint);
55
+ authUrl.searchParams.set('client_id', clientId);
56
+ authUrl.searchParams.set('redirect_uri', redirectUri);
57
+ authUrl.searchParams.set('response_type', 'code');
58
+ if (options.scopes && options.scopes.length > 0) {
59
+ authUrl.searchParams.set('scope', options.scopes.join(' '));
60
+ }
61
+ // Add resource parameter if specified (RFC 8707)
62
+ if (options.resource) {
63
+ authUrl.searchParams.set('resource', options.resource);
64
+ }
65
+ // Add PKCE parameters if generated (RFC 7636)
66
+ if (pkce) {
67
+ authUrl.searchParams.set('code_challenge', pkce.codeChallenge);
68
+ authUrl.searchParams.set('code_challenge_method', pkce.codeChallengeMethod);
69
+ }
70
+ // Open browser or print URL for headless mode
71
+ if (options.headless) {
72
+ logger.info('🔗 Please visit this URL to authorize:');
73
+ logger.info(authUrl.toString());
74
+ logger.info('Waiting for callback...');
75
+ } else {
76
+ logger.debug('🌐 Opening browser for OAuth authorization...');
77
+ // Try to open browser (requires 'open' package or native command)
78
+ await this.openBrowser(authUrl.toString());
79
+ }
80
+ // Wait for callback with timeout
81
+ const timeout = options.timeout || (options.headless ? 60000 : 300000);
82
+ const result = await callbackListener.waitForCallback(timeout);
83
+ // Exchange authorization code for tokens (with PKCE verifier if used)
84
+ const tokens = await this.exchangeCodeForTokens(tokenEndpoint, result.code, clientId, clientSecret, redirectUri, pkce === null || pkce === void 0 ? void 0 : pkce.codeVerifier);
85
+ return tokens;
86
+ } catch (error) {
87
+ logger.error('❌ OAuth flow failed:', error instanceof Error ? error.message : String(error));
88
+ throw error;
89
+ } finally{
90
+ // Always close callback server
91
+ await callbackListener.stop();
92
+ }
93
+ }
94
+ /**
95
+ * Exchange authorization code for access and refresh tokens
96
+ * @param codeVerifier - Optional PKCE code verifier (RFC 7636)
97
+ */ async exchangeCodeForTokens(tokenEndpoint, code, clientId, clientSecret, redirectUri, codeVerifier) {
98
+ const params = new URLSearchParams({
99
+ grant_type: 'authorization_code',
100
+ code,
101
+ redirect_uri: redirectUri,
102
+ client_id: clientId,
103
+ client_secret: clientSecret
104
+ });
105
+ // Add PKCE code verifier if provided (RFC 7636)
106
+ if (codeVerifier) {
107
+ params.set('code_verifier', codeVerifier);
108
+ }
109
+ const response = await fetch(tokenEndpoint, {
110
+ method: 'POST',
111
+ headers: {
112
+ 'Content-Type': 'application/x-www-form-urlencoded',
113
+ Accept: 'application/json',
114
+ Connection: 'close'
115
+ },
116
+ body: params
117
+ });
118
+ if (!response.ok) {
119
+ const errorText = await response.text();
120
+ throw new Error(`Token exchange failed (${response.status}): ${errorText}`);
121
+ }
122
+ const data = await response.json();
123
+ if (!data.access_token) {
124
+ throw new Error('Token response missing access_token');
125
+ }
126
+ const tokenSet = {
127
+ accessToken: data.access_token,
128
+ refreshToken: data.refresh_token || '',
129
+ expiresAt: Date.now() + data.expires_in * 1000,
130
+ clientId,
131
+ clientSecret
132
+ };
133
+ if (data.scope) {
134
+ tokenSet.scopes = data.scope.split(' ');
135
+ }
136
+ return tokenSet;
137
+ }
138
+ /**
139
+ * Refresh access token using refresh token
140
+ *
141
+ * @param tokenEndpoint - OAuth token endpoint URL
142
+ * @param refreshToken - Refresh token from previous token set
143
+ * @param clientId - OAuth client ID
144
+ * @param clientSecret - OAuth client secret
145
+ * @returns New token set with refreshed access token
146
+ *
147
+ * @throws Error if refresh fails
148
+ */ async refreshTokens(tokenEndpoint, refreshToken, clientId, clientSecret) {
149
+ const response = await fetch(tokenEndpoint, {
150
+ method: 'POST',
151
+ headers: {
152
+ 'Content-Type': 'application/x-www-form-urlencoded',
153
+ Accept: 'application/json',
154
+ Connection: 'close'
155
+ },
156
+ body: new URLSearchParams({
157
+ grant_type: 'refresh_token',
158
+ refresh_token: refreshToken,
159
+ client_id: clientId,
160
+ client_secret: clientSecret
161
+ })
162
+ });
163
+ if (!response.ok) {
164
+ const errorText = await response.text();
165
+ throw new Error(`Token refresh failed (${response.status}): ${errorText}`);
166
+ }
167
+ const data = await response.json();
168
+ if (!data.access_token) {
169
+ throw new Error('Token refresh response missing access_token');
170
+ }
171
+ const tokenSet = {
172
+ accessToken: data.access_token,
173
+ refreshToken: data.refresh_token || refreshToken,
174
+ expiresAt: Date.now() + data.expires_in * 1000,
175
+ clientId,
176
+ clientSecret
177
+ };
178
+ if (data.scope) {
179
+ tokenSet.scopes = data.scope.split(' ');
180
+ }
181
+ return tokenSet;
182
+ }
183
+ /**
184
+ * Open browser to authorization URL
185
+ * Uses platform-specific command to open default browser
186
+ */ async openBrowser(url) {
187
+ // Determine platform-specific command
188
+ const platform = process.platform;
189
+ let command;
190
+ let args;
191
+ if (platform === 'darwin') {
192
+ command = 'open';
193
+ args = [
194
+ url
195
+ ];
196
+ } else if (platform === 'win32') {
197
+ command = 'cmd';
198
+ args = [
199
+ '/c',
200
+ 'start',
201
+ url
202
+ ];
203
+ } else {
204
+ // Linux and others
205
+ command = 'xdg-open';
206
+ args = [
207
+ url
208
+ ];
209
+ }
210
+ // Spawn browser process
211
+ const child = child_process.spawn(command, args, {
212
+ detached: true,
213
+ stdio: 'ignore'
214
+ });
215
+ child.unref();
216
+ }
217
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/kevin/Dev/Projects/ai/mcp-z/libs/client/src/auth/interactive-oauth-flow.ts"],"sourcesContent":["/**\n * OAuth Authorization Flow Handler\n * Manages browser-based OAuth flows and token exchange with PKCE support\n */\n\nimport * as child_process from 'node:child_process';\nimport { logger as defaultLogger } from '../utils/logger.ts';\nimport { OAuthCallbackListener } from './oauth-callback-listener.ts';\nimport { generatePkce } from './pkce.ts';\nimport type { OAuthFlowOptions, PkceParams, TokenSet } from './types.ts';\n\n/**\n * OAuth token response from token endpoint\n */\ninterface TokenResponse {\n access_token: string;\n refresh_token?: string;\n expires_in: number;\n scope?: string;\n token_type?: string;\n}\n\n/**\n * InteractiveOAuthFlow manages the complete OAuth authorization code flow\n */\nexport class InteractiveOAuthFlow {\n /**\n * Perform OAuth authorization code flow\n *\n * @param authorizationEndpoint - OAuth authorization endpoint URL\n * @param tokenEndpoint - OAuth token endpoint URL\n * @param clientId - OAuth client ID\n * @param clientSecret - OAuth client secret\n * @param options - Flow options (port is required - use get-port to find available port)\n * @returns Token set with access and refresh tokens\n *\n * @throws Error if flow fails or times out\n *\n * @example\n * import getPort from 'get-port';\n *\n * const flow = new InteractiveOAuthFlow();\n * const port = await getPort();\n * const tokens = await flow.performAuthFlow(\n * 'https://example.com/oauth/authorize',\n * 'https://example.com/oauth/token',\n * 'client-id',\n * 'client-secret',\n * { port, scopes: ['read', 'write'] }\n * );\n */\n async performAuthFlow(authorizationEndpoint: string, tokenEndpoint: string, clientId: string, clientSecret: string, options: OAuthFlowOptions): Promise<TokenSet> {\n const logger = options.logger ?? defaultLogger;\n const callbackListener = new OAuthCallbackListener({ port: options.port, logger });\n\n // Generate PKCE parameters if requested (RFC 7636)\n let pkce: PkceParams | undefined;\n if (options.pkce) {\n logger.debug('🔐 Generating PKCE parameters...');\n pkce = await generatePkce();\n }\n\n try {\n // Start callback server\n await callbackListener.start();\n\n // Build redirect URI\n const redirectUri = options.redirectUri || `http://localhost:${options.port}/callback`;\n\n // Build authorization URL\n const authUrl = new URL(authorizationEndpoint);\n authUrl.searchParams.set('client_id', clientId);\n authUrl.searchParams.set('redirect_uri', redirectUri);\n authUrl.searchParams.set('response_type', 'code');\n\n if (options.scopes && options.scopes.length > 0) {\n authUrl.searchParams.set('scope', options.scopes.join(' '));\n }\n\n // Add resource parameter if specified (RFC 8707)\n if (options.resource) {\n authUrl.searchParams.set('resource', options.resource);\n }\n\n // Add PKCE parameters if generated (RFC 7636)\n if (pkce) {\n authUrl.searchParams.set('code_challenge', pkce.codeChallenge);\n authUrl.searchParams.set('code_challenge_method', pkce.codeChallengeMethod);\n }\n\n // Open browser or print URL for headless mode\n if (options.headless) {\n logger.info('🔗 Please visit this URL to authorize:');\n logger.info(authUrl.toString());\n logger.info('Waiting for callback...');\n } else {\n logger.debug('🌐 Opening browser for OAuth authorization...');\n // Try to open browser (requires 'open' package or native command)\n await this.openBrowser(authUrl.toString());\n }\n\n // Wait for callback with timeout\n const timeout = options.timeout || (options.headless ? 60000 : 300000);\n const result = await callbackListener.waitForCallback(timeout);\n\n // Exchange authorization code for tokens (with PKCE verifier if used)\n const tokens = await this.exchangeCodeForTokens(tokenEndpoint, result.code, clientId, clientSecret, redirectUri, pkce?.codeVerifier);\n\n return tokens;\n } catch (error) {\n logger.error('❌ OAuth flow failed:', error instanceof Error ? error.message : String(error));\n throw error;\n } finally {\n // Always close callback server\n await callbackListener.stop();\n }\n }\n\n /**\n * Exchange authorization code for access and refresh tokens\n * @param codeVerifier - Optional PKCE code verifier (RFC 7636)\n */\n private async exchangeCodeForTokens(tokenEndpoint: string, code: string, clientId: string, clientSecret: string, redirectUri: string, codeVerifier?: string): Promise<TokenSet> {\n const params = new URLSearchParams({\n grant_type: 'authorization_code',\n code,\n redirect_uri: redirectUri,\n client_id: clientId,\n client_secret: clientSecret,\n });\n\n // Add PKCE code verifier if provided (RFC 7636)\n if (codeVerifier) {\n params.set('code_verifier', codeVerifier);\n }\n\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n Connection: 'close',\n },\n body: params,\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token exchange failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as TokenResponse;\n\n if (!data.access_token) {\n throw new Error('Token response missing access_token');\n }\n\n const tokenSet: TokenSet = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token || '',\n expiresAt: Date.now() + data.expires_in * 1000,\n clientId,\n clientSecret,\n };\n\n if (data.scope) {\n tokenSet.scopes = data.scope.split(' ');\n }\n\n return tokenSet;\n }\n\n /**\n * Refresh access token using refresh token\n *\n * @param tokenEndpoint - OAuth token endpoint URL\n * @param refreshToken - Refresh token from previous token set\n * @param clientId - OAuth client ID\n * @param clientSecret - OAuth client secret\n * @returns New token set with refreshed access token\n *\n * @throws Error if refresh fails\n */\n async refreshTokens(tokenEndpoint: string, refreshToken: string, clientId: string, clientSecret: string): Promise<TokenSet> {\n const response = await fetch(tokenEndpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n Connection: 'close',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: clientId,\n client_secret: clientSecret,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Token refresh failed (${response.status}): ${errorText}`);\n }\n\n const data = (await response.json()) as TokenResponse;\n\n if (!data.access_token) {\n throw new Error('Token refresh response missing access_token');\n }\n\n const tokenSet: TokenSet = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token || refreshToken, // Reuse old refresh token if not provided\n expiresAt: Date.now() + data.expires_in * 1000,\n clientId,\n clientSecret,\n };\n\n if (data.scope) {\n tokenSet.scopes = data.scope.split(' ');\n }\n\n return tokenSet;\n }\n\n /**\n * Open browser to authorization URL\n * Uses platform-specific command to open default browser\n */\n private async openBrowser(url: string): Promise<void> {\n // Determine platform-specific command\n const platform = process.platform;\n let command: string;\n let args: string[];\n\n if (platform === 'darwin') {\n command = 'open';\n args = [url];\n } else if (platform === 'win32') {\n command = 'cmd';\n args = ['/c', 'start', url];\n } else {\n // Linux and others\n command = 'xdg-open';\n args = [url];\n }\n\n // Spawn browser process\n const child = child_process.spawn(command, args, {\n detached: true,\n stdio: 'ignore',\n });\n\n child.unref();\n }\n}\n"],"names":["child_process","logger","defaultLogger","OAuthCallbackListener","generatePkce","InteractiveOAuthFlow","performAuthFlow","authorizationEndpoint","tokenEndpoint","clientId","clientSecret","options","callbackListener","port","pkce","debug","start","redirectUri","authUrl","URL","searchParams","set","scopes","length","join","resource","codeChallenge","codeChallengeMethod","headless","info","toString","openBrowser","timeout","result","waitForCallback","tokens","exchangeCodeForTokens","code","codeVerifier","error","Error","message","String","stop","params","URLSearchParams","grant_type","redirect_uri","client_id","client_secret","response","fetch","method","headers","Accept","Connection","body","ok","errorText","text","status","data","json","access_token","tokenSet","accessToken","refreshToken","refresh_token","expiresAt","Date","now","expires_in","scope","split","refreshTokens","url","platform","process","command","args","child","spawn","detached","stdio","unref"],"mappings":"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"}