mcp4openapi 0.3.0 → 0.3.1

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 (188) hide show
  1. package/dist/src/argument-normalizer.d.ts +5 -0
  2. package/dist/src/argument-normalizer.d.ts.map +1 -0
  3. package/dist/src/argument-normalizer.js +61 -0
  4. package/dist/src/argument-normalizer.js.map +1 -0
  5. package/dist/src/auth/oauth-provider.d.ts.map +1 -1
  6. package/dist/src/auth/oauth-provider.js +5 -2
  7. package/dist/src/auth/oauth-provider.js.map +1 -1
  8. package/dist/src/cli-config.d.ts +9 -0
  9. package/dist/src/cli-config.d.ts.map +1 -0
  10. package/dist/src/cli-config.js +111 -0
  11. package/dist/src/cli-config.js.map +1 -0
  12. package/dist/src/composite-executor.d.ts +77 -0
  13. package/dist/src/composite-executor.d.ts.map +1 -0
  14. package/dist/src/composite-executor.js +193 -0
  15. package/dist/src/composite-executor.js.map +1 -0
  16. package/dist/src/constants.d.ts +85 -0
  17. package/dist/src/constants.d.ts.map +1 -0
  18. package/dist/src/constants.js +85 -0
  19. package/dist/src/constants.js.map +1 -0
  20. package/dist/src/core/cli-config.d.ts.map +1 -1
  21. package/dist/src/core/cli-config.js +1 -0
  22. package/dist/src/core/cli-config.js.map +1 -1
  23. package/dist/src/core/index.d.ts.map +1 -1
  24. package/dist/src/core/index.js +1 -0
  25. package/dist/src/core/index.js.map +1 -1
  26. package/dist/src/dag-executor.d.ts +49 -0
  27. package/dist/src/dag-executor.d.ts.map +1 -0
  28. package/dist/src/dag-executor.js +138 -0
  29. package/dist/src/dag-executor.js.map +1 -0
  30. package/dist/src/errors.d.ts +59 -0
  31. package/dist/src/errors.d.ts.map +1 -0
  32. package/dist/src/errors.js +119 -0
  33. package/dist/src/errors.js.map +1 -0
  34. package/dist/src/filtering.d.ts +19 -0
  35. package/dist/src/filtering.d.ts.map +1 -0
  36. package/dist/src/filtering.js +292 -0
  37. package/dist/src/filtering.js.map +1 -0
  38. package/dist/src/generated-schemas.d.ts +45 -0
  39. package/dist/src/generated-schemas.d.ts.map +1 -1
  40. package/dist/src/generated-schemas.js +3 -0
  41. package/dist/src/generated-schemas.js.map +1 -1
  42. package/dist/src/http-client-factory.d.ts +62 -0
  43. package/dist/src/http-client-factory.d.ts.map +1 -0
  44. package/dist/src/http-client-factory.js +133 -0
  45. package/dist/src/http-client-factory.js.map +1 -0
  46. package/dist/src/http-transport-config.d.ts +6 -0
  47. package/dist/src/http-transport-config.d.ts.map +1 -0
  48. package/dist/src/http-transport-config.js +47 -0
  49. package/dist/src/http-transport-config.js.map +1 -0
  50. package/dist/src/http-transport.d.ts +316 -0
  51. package/dist/src/http-transport.d.ts.map +1 -0
  52. package/dist/src/http-transport.js +2412 -0
  53. package/dist/src/http-transport.js.map +1 -0
  54. package/dist/src/index.js +0 -0
  55. package/dist/src/interceptors.d.ts +116 -0
  56. package/dist/src/interceptors.d.ts.map +1 -0
  57. package/dist/src/interceptors.js +392 -0
  58. package/dist/src/interceptors.js.map +1 -0
  59. package/dist/src/jsonrpc-validator.d.ts +27 -0
  60. package/dist/src/jsonrpc-validator.d.ts.map +1 -0
  61. package/dist/src/jsonrpc-validator.js +58 -0
  62. package/dist/src/jsonrpc-validator.js.map +1 -0
  63. package/dist/src/logger.d.ts +59 -0
  64. package/dist/src/logger.d.ts.map +1 -0
  65. package/dist/src/logger.js +177 -0
  66. package/dist/src/logger.js.map +1 -0
  67. package/dist/src/mcp-server-manager.d.ts +20 -0
  68. package/dist/src/mcp-server-manager.d.ts.map +1 -0
  69. package/dist/src/mcp-server-manager.js +38 -0
  70. package/dist/src/mcp-server-manager.js.map +1 -0
  71. package/dist/src/mcp-server.d.ts +203 -0
  72. package/dist/src/mcp-server.d.ts.map +1 -0
  73. package/dist/src/mcp-server.js +1369 -0
  74. package/dist/src/mcp-server.js.map +1 -0
  75. package/dist/src/metrics.d.ts +97 -0
  76. package/dist/src/metrics.d.ts.map +1 -0
  77. package/dist/src/metrics.js +273 -0
  78. package/dist/src/metrics.js.map +1 -0
  79. package/dist/src/naming-warnings.d.ts +23 -0
  80. package/dist/src/naming-warnings.d.ts.map +1 -0
  81. package/dist/src/naming-warnings.js +83 -0
  82. package/dist/src/naming-warnings.js.map +1 -0
  83. package/dist/src/naming.d.ts +58 -0
  84. package/dist/src/naming.d.ts.map +1 -0
  85. package/dist/src/naming.js +510 -0
  86. package/dist/src/naming.js.map +1 -0
  87. package/dist/src/oauth-provider.d.ts +131 -0
  88. package/dist/src/oauth-provider.d.ts.map +1 -0
  89. package/dist/src/oauth-provider.js +836 -0
  90. package/dist/src/oauth-provider.js.map +1 -0
  91. package/dist/src/openapi/openapi-parser.d.ts.map +1 -1
  92. package/dist/src/openapi/openapi-parser.js +22 -0
  93. package/dist/src/openapi/openapi-parser.js.map +1 -1
  94. package/dist/src/openapi-parser.d.ts +70 -0
  95. package/dist/src/openapi-parser.d.ts.map +1 -0
  96. package/dist/src/openapi-parser.js +436 -0
  97. package/dist/src/openapi-parser.js.map +1 -0
  98. package/dist/src/profile/profile-loader.d.ts.map +1 -1
  99. package/dist/src/profile/profile-loader.js +8 -1
  100. package/dist/src/profile/profile-loader.js.map +1 -1
  101. package/dist/src/profile/profile-registry.d.ts +2 -1
  102. package/dist/src/profile/profile-registry.d.ts.map +1 -1
  103. package/dist/src/profile/profile-registry.js +18 -1
  104. package/dist/src/profile/profile-registry.js.map +1 -1
  105. package/dist/src/profile/profile-resolver.d.ts +16 -0
  106. package/dist/src/profile/profile-resolver.d.ts.map +1 -1
  107. package/dist/src/profile/profile-resolver.js +120 -0
  108. package/dist/src/profile/profile-resolver.js.map +1 -1
  109. package/dist/src/profile-loader.d.ts +78 -0
  110. package/dist/src/profile-loader.d.ts.map +1 -0
  111. package/dist/src/profile-loader.js +483 -0
  112. package/dist/src/profile-loader.js.map +1 -0
  113. package/dist/src/profile-registry.d.ts +18 -0
  114. package/dist/src/profile-registry.d.ts.map +1 -0
  115. package/dist/src/profile-registry.js +26 -0
  116. package/dist/src/profile-registry.js.map +1 -0
  117. package/dist/src/profile-resolver.d.ts +19 -0
  118. package/dist/src/profile-resolver.d.ts.map +1 -0
  119. package/dist/src/profile-resolver.js +167 -0
  120. package/dist/src/profile-resolver.js.map +1 -0
  121. package/dist/src/proxy-executor.d.ts +86 -0
  122. package/dist/src/proxy-executor.d.ts.map +1 -0
  123. package/dist/src/proxy-executor.js +497 -0
  124. package/dist/src/proxy-executor.js.map +1 -0
  125. package/dist/src/schema-validator.d.ts +30 -0
  126. package/dist/src/schema-validator.d.ts.map +1 -0
  127. package/dist/src/schema-validator.js +128 -0
  128. package/dist/src/schema-validator.js.map +1 -0
  129. package/dist/src/startup-profile.d.ts +17 -0
  130. package/dist/src/startup-profile.d.ts.map +1 -0
  131. package/dist/src/startup-profile.js +30 -0
  132. package/dist/src/startup-profile.js.map +1 -0
  133. package/dist/src/startup-validation.d.ts +11 -0
  134. package/dist/src/startup-validation.d.ts.map +1 -0
  135. package/dist/src/startup-validation.js +21 -0
  136. package/dist/src/startup-validation.js.map +1 -0
  137. package/dist/src/tool-filter.d.ts +65 -0
  138. package/dist/src/tool-filter.d.ts.map +1 -0
  139. package/dist/src/tool-filter.js +471 -0
  140. package/dist/src/tool-filter.js.map +1 -0
  141. package/dist/src/tool-generator.d.ts +67 -0
  142. package/dist/src/tool-generator.d.ts.map +1 -0
  143. package/dist/src/tool-generator.js +182 -0
  144. package/dist/src/tool-generator.js.map +1 -0
  145. package/dist/src/tooling/composite-executor.d.ts.map +1 -1
  146. package/dist/src/tooling/composite-executor.js +7 -2
  147. package/dist/src/tooling/composite-executor.js.map +1 -1
  148. package/dist/src/tooling/proxy-executor.d.ts.map +1 -1
  149. package/dist/src/tooling/proxy-executor.js +4 -0
  150. package/dist/src/tooling/proxy-executor.js.map +1 -1
  151. package/dist/src/tooling/tool-generator.d.ts.map +1 -1
  152. package/dist/src/tooling/tool-generator.js +36 -3
  153. package/dist/src/tooling/tool-generator.js.map +1 -1
  154. package/dist/src/transport/http-transport-config.d.ts.map +1 -1
  155. package/dist/src/transport/http-transport-config.js +1 -0
  156. package/dist/src/transport/http-transport-config.js.map +1 -1
  157. package/dist/src/transport/http-transport.d.ts +5 -0
  158. package/dist/src/transport/http-transport.d.ts.map +1 -1
  159. package/dist/src/transport/http-transport.js +63 -1
  160. package/dist/src/transport/http-transport.js.map +1 -1
  161. package/dist/src/transport/profile-index.d.ts +84 -0
  162. package/dist/src/transport/profile-index.d.ts.map +1 -0
  163. package/dist/src/transport/profile-index.js +405 -0
  164. package/dist/src/transport/profile-index.js.map +1 -0
  165. package/dist/src/types/http-transport.d.ts +1 -0
  166. package/dist/src/types/http-transport.d.ts.map +1 -1
  167. package/dist/src/types/openapi.d.ts +3 -0
  168. package/dist/src/types/openapi.d.ts.map +1 -1
  169. package/dist/src/types/profile.d.ts +3 -0
  170. package/dist/src/types/profile.d.ts.map +1 -1
  171. package/dist/src/validation/validation-utils.d.ts.map +1 -1
  172. package/dist/src/validation/validation-utils.js +1 -0
  173. package/dist/src/validation/validation-utils.js.map +1 -1
  174. package/dist/src/validation-utils.d.ts +49 -0
  175. package/dist/src/validation-utils.d.ts.map +1 -0
  176. package/dist/src/validation-utils.js +138 -0
  177. package/dist/src/validation-utils.js.map +1 -0
  178. package/html/profile-index.html +386 -0
  179. package/package.json +2 -1
  180. package/profile-schema.json +14 -0
  181. package/profiles/gitlab/developer-profile-oauth.json +1 -1
  182. package/profiles/gitlab/developer-profile.json +1508 -0
  183. package/profiles/gitlab/developer-profile.test.json +3432 -0
  184. package/profiles/n8n/profile-optimized.json +1 -1
  185. package/profiles/n8n/profile.json +1 -1
  186. package/profiles/n8n-nodes/profile-nodes.json +1 -1
  187. package/profiles/semgrep/profile.json +1 -1
  188. package/profiles/youtrack/profile.json +1 -1
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Validation utilities for common data types
3
+ *
4
+ * Why: Provides reusable validation functions for email, URI, and other formats
5
+ * Centralizes validation logic and ensures consistency across the application
6
+ */
7
+ import escapeHtml from 'escape-html';
8
+ /** Property names that must never be used as dynamic object keys */
9
+ const FORBIDDEN_PROPERTY_NAMES = new Set([
10
+ '__proto__',
11
+ 'constructor',
12
+ 'prototype',
13
+ '__defineGetter__',
14
+ '__defineSetter__',
15
+ '__lookupGetter__',
16
+ '__lookupSetter__',
17
+ 'hasOwnProperty',
18
+ 'isPrototypeOf',
19
+ 'propertyIsEnumerable',
20
+ 'toLocaleString',
21
+ 'toString',
22
+ 'valueOf',
23
+ ]);
24
+ /**
25
+ * Validates that a property name is safe to use as dynamic object key.
26
+ * Prevents prototype pollution attacks.
27
+ */
28
+ export function isSafePropertyName(name) {
29
+ return !FORBIDDEN_PROPERTY_NAMES.has(name);
30
+ }
31
+ /**
32
+ * Escape special regex characters in a string.
33
+ * Prevents ReDoS attacks when using dynamic strings in RegExp.
34
+ */
35
+ export function escapeRegExp(str) {
36
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
37
+ }
38
+ /**
39
+ * Redact specific header from headers object (case-insensitive)
40
+ */
41
+ export function redactHeader(headers, headerName) {
42
+ if (!headers || typeof headers !== 'object')
43
+ return {};
44
+ const redacted = { ...headers };
45
+ for (const key of Object.keys(redacted)) {
46
+ if (key.toLowerCase() === headerName.toLowerCase()) {
47
+ redacted[key] = '[REDACTED]';
48
+ }
49
+ }
50
+ return redacted;
51
+ }
52
+ /**
53
+ * Redact query parameter from URL string
54
+ */
55
+ export function redactQueryParam(url, paramName) {
56
+ if (!url)
57
+ return '';
58
+ // Enforce safe paramName (alphanumeric, underscore, dash) length <= 64
59
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(paramName)) {
60
+ return url; // Unsafe param name; return original unmodified
61
+ }
62
+ try {
63
+ const urlObj = new URL(url);
64
+ if (urlObj.searchParams.has(paramName)) {
65
+ urlObj.searchParams.set(paramName, '[REDACTED]');
66
+ }
67
+ return urlObj.toString();
68
+ }
69
+ catch {
70
+ // Fallback: manual parsing without dynamic RegExp to avoid ReDoS concerns
71
+ // Split on '?' then process query string key-value pairs
72
+ const qIndex = url.indexOf('?');
73
+ if (qIndex === -1)
74
+ return url;
75
+ const base = url.substring(0, qIndex);
76
+ const query = url.substring(qIndex + 1);
77
+ const parts = query.split('&');
78
+ const redactedParts = parts.map(part => {
79
+ const eqIndex = part.indexOf('=');
80
+ if (eqIndex === -1)
81
+ return part; // skip malformed segment
82
+ const key = part.substring(0, eqIndex);
83
+ if (key === paramName) {
84
+ // Encode [REDACTED] for consistency with URLSearchParams behavior
85
+ return key + '=%5BREDACTED%5D';
86
+ }
87
+ return part;
88
+ });
89
+ return base + '?' + redactedParts.join('&');
90
+ }
91
+ }
92
+ /**
93
+ * Redact parameter from params object
94
+ */
95
+ export function redactParam(params, paramName) {
96
+ if (!params || typeof params !== 'object')
97
+ return {};
98
+ const redacted = { ...params };
99
+ if (paramName in redacted) {
100
+ redacted[paramName] = '[REDACTED]';
101
+ }
102
+ return redacted;
103
+ }
104
+ /**
105
+ * Validates if a string is a valid email address
106
+ */
107
+ export function isEmail(value) {
108
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
109
+ }
110
+ /**
111
+ * Validates if a string is a valid URI
112
+ */
113
+ export function isUri(value) {
114
+ try {
115
+ new URL(value);
116
+ return true;
117
+ }
118
+ catch {
119
+ return false;
120
+ }
121
+ }
122
+ /**
123
+ * Escape HTML special characters to prevent XSS attacks
124
+ *
125
+ * Why: User-provided strings in error messages must be sanitized
126
+ * before being returned in JSON responses that might be rendered as HTML.
127
+ *
128
+ * Uses escape-html library for reliable HTML entity escaping.
129
+ *
130
+ * @param str - String to escape (can be undefined or null)
131
+ * @returns Escaped string safe for HTML rendering, empty string if input is falsy
132
+ */
133
+ export function escapeHtmlSafe(str) {
134
+ if (!str)
135
+ return '';
136
+ return escapeHtml(String(str));
137
+ }
138
+ //# sourceMappingURL=validation-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation-utils.js","sourceRoot":"","sources":["../../src/validation-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,UAAU,MAAM,aAAa,CAAC;AAErC,oEAAoE;AACpE,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC;IACvC,WAAW;IACX,aAAa;IACb,WAAW;IACX,kBAAkB;IAClB,kBAAkB;IAClB,kBAAkB;IAClB,kBAAkB;IAClB,gBAAgB;IAChB,eAAe;IACf,sBAAsB;IACtB,gBAAgB;IAChB,UAAU;IACV,SAAS;CACV,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,CAAC,wBAAwB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAgB,EAChB,UAAkB;IAElB,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IAEvD,MAAM,QAAQ,GAAG,EAAE,GAAI,OAAmC,EAAE,CAAC;IAE7D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,UAAU,CAAC,WAAW,EAAE,EAAE,CAAC;YACnD,QAAQ,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAAuB,EACvB,SAAiB;IAEjB,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,uEAAuE;IACvE,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAC7C,OAAO,GAAG,CAAC,CAAC,gDAAgD;IAC9D,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACvC,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,yDAAyD;QACzD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,MAAM,KAAK,CAAC,CAAC;YAAE,OAAO,GAAG,CAAC;QAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YACrC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAClC,IAAI,OAAO,KAAK,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC,CAAC,yBAAyB;YAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YACvC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,kEAAkE;gBAClE,OAAO,GAAG,GAAG,iBAAiB,CAAC;YACjC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,GAAG,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CACzB,MAAe,EACf,SAAiB;IAEjB,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IAErD,MAAM,QAAQ,GAAG,EAAE,GAAI,MAAkC,EAAE,CAAC;IAC5D,IAAI,SAAS,IAAI,QAAQ,EAAE,CAAC;QAC1B,QAAQ,CAAC,SAAS,CAAC,GAAG,YAAY,CAAC;IACrC,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,OAAO,4BAA4B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,KAAK,CAAC,KAAa;IACjC,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,GAA8B;IAC3D,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,OAAO,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,386 @@
1
+ <!DOCTYPE html>
2
+ <html lang="{{lang}}">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{title}}</title>
7
+ <style nonce="{{nonce}}">
8
+ :root {
9
+ color-scheme: light;
10
+ --bg: #f6f2ea;
11
+ --bg-accent: #efe7da;
12
+ --ink: #1f1f1f;
13
+ --muted: #615d56;
14
+ --card: #ffffff;
15
+ --accent: #b15a2b;
16
+ --accent-2: #2b5aa3;
17
+ --border: #e1d8cc;
18
+ --shadow: 0 12px 40px rgba(0, 0, 0, 0.08);
19
+ }
20
+ * { box-sizing: border-box; }
21
+ body {
22
+ margin: 0;
23
+ font-family: "Space Grotesk", "Avenir Next", "Segoe UI", sans-serif;
24
+ color: var(--ink);
25
+ background: radial-gradient(1200px 600px at 10% -20%, #fbe5d6 0%, transparent 60%),
26
+ radial-gradient(800px 600px at 90% 0%, #dbe9ff 0%, transparent 55%),
27
+ var(--bg);
28
+ }
29
+ header {
30
+ padding: 48px 40px 32px;
31
+ }
32
+ h1 {
33
+ font-size: 36px;
34
+ margin: 0 0 8px;
35
+ letter-spacing: -0.02em;
36
+ }
37
+ p {
38
+ margin: 0;
39
+ color: var(--muted);
40
+ line-height: 1.6;
41
+ }
42
+ main {
43
+ display: grid;
44
+ grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);
45
+ gap: 24px;
46
+ padding: 0 40px 48px;
47
+ }
48
+ .panel {
49
+ background: var(--card);
50
+ border-radius: 16px;
51
+ border: 1px solid var(--border);
52
+ box-shadow: var(--shadow);
53
+ padding: 24px;
54
+ }
55
+ .profiles {
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: 12px;
59
+ }
60
+ .profile-item {
61
+ padding: 12px 14px;
62
+ border-radius: 12px;
63
+ border: 1px solid transparent;
64
+ background: var(--bg-accent);
65
+ cursor: pointer;
66
+ transition: border-color 0.2s ease, transform 0.2s ease;
67
+ }
68
+ .profile-item:hover {
69
+ border-color: var(--accent);
70
+ transform: translateY(-1px);
71
+ }
72
+ .profile-item.active {
73
+ border-color: var(--accent);
74
+ background: #fff5ec;
75
+ }
76
+ .profile-title {
77
+ font-weight: 600;
78
+ margin-bottom: 4px;
79
+ }
80
+ .profile-subtitle {
81
+ font-size: 13px;
82
+ color: var(--muted);
83
+ overflow: hidden;
84
+ text-overflow: ellipsis;
85
+ display: -webkit-box;
86
+ -webkit-line-clamp: 2;
87
+ -webkit-box-orient: vertical;
88
+ }
89
+ .detail-grid {
90
+ display: grid;
91
+ gap: 18px;
92
+ }
93
+ .tab-row {
94
+ display: flex;
95
+ flex-wrap: wrap;
96
+ gap: 8px;
97
+ margin-bottom: 8px;
98
+ }
99
+ .tab {
100
+ border: 1px solid var(--border);
101
+ background: #fff;
102
+ padding: 6px 12px;
103
+ border-radius: 999px;
104
+ cursor: pointer;
105
+ font-size: 13px;
106
+ color: var(--muted);
107
+ }
108
+ .tab.active {
109
+ color: var(--accent);
110
+ border-color: var(--accent);
111
+ background: #fff5ec;
112
+ font-weight: 600;
113
+ }
114
+ .detail-card {
115
+ border: 1px solid var(--border);
116
+ border-radius: 12px;
117
+ padding: 16px;
118
+ background: #fff;
119
+ }
120
+ .detail-card h3 {
121
+ margin: 0 0 8px;
122
+ font-size: 16px;
123
+ }
124
+ .tag-list {
125
+ display: flex;
126
+ flex-wrap: wrap;
127
+ gap: 8px;
128
+ margin-top: 8px;
129
+ }
130
+ .tag {
131
+ background: #f1efe9;
132
+ color: #2d2b28;
133
+ border-radius: 999px;
134
+ padding: 4px 10px;
135
+ font-size: 12px;
136
+ border: 1px solid var(--border);
137
+ }
138
+ code, pre {
139
+ font-family: "IBM Plex Mono", "SFMono-Regular", Consolas, monospace;
140
+ }
141
+ pre {
142
+ background: #0f172a;
143
+ color: #e2e8f0;
144
+ padding: 14px;
145
+ border-radius: 12px;
146
+ overflow-x: auto;
147
+ line-height: 1.6;
148
+ }
149
+ .snippets {
150
+ display: grid;
151
+ gap: 16px;
152
+ }
153
+ .snippet-header {
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: space-between;
157
+ gap: 12px;
158
+ }
159
+ .snippet-meta {
160
+ display: flex;
161
+ align-items: center;
162
+ gap: 8px;
163
+ }
164
+ .format-pill {
165
+ border: 1px solid var(--border);
166
+ border-radius: 999px;
167
+ padding: 2px 8px;
168
+ font-size: 11px;
169
+ color: var(--muted);
170
+ background: #f7f4ef;
171
+ text-transform: uppercase;
172
+ letter-spacing: 0.05em;
173
+ }
174
+ .copy-btn {
175
+ border: 1px solid var(--border);
176
+ background: #fff;
177
+ color: var(--accent-2);
178
+ border-radius: 999px;
179
+ padding: 6px 12px;
180
+ cursor: pointer;
181
+ font-size: 12px;
182
+ }
183
+ .notice {
184
+ background: #fff8f1;
185
+ border: 1px solid #f6d5bd;
186
+ padding: 12px;
187
+ border-radius: 12px;
188
+ color: #7a3f1e;
189
+ }
190
+ @media (max-width: 960px) {
191
+ main {
192
+ grid-template-columns: 1fr;
193
+ padding: 0 20px 40px;
194
+ }
195
+ header {
196
+ padding: 36px 20px 24px;
197
+ }
198
+ }
199
+ </style>
200
+ </head>
201
+ <body>
202
+ <header>
203
+ <h1>{{title}}</h1>
204
+ <p>{{subtitle}}</p>
205
+ </header>
206
+ <main>
207
+ <section class="panel">
208
+ <div class="profiles" id="profile-list"></div>
209
+ </section>
210
+ <section class="panel">
211
+ <div class="detail-grid" id="profile-detail">
212
+ <noscript>
213
+ <div class="notice">{{noscript}}</div>
214
+ </noscript>
215
+ </div>
216
+ </section>
217
+ </main>
218
+
219
+ <script type="application/json" id="profile-data" nonce="{{nonce}}">{{profile_data}}</script>
220
+ <script type="application/json" id="i18n-data" nonce="{{nonce}}">{{i18n_data}}</script>
221
+
222
+ <script nonce="{{nonce}}">
223
+ const profileData = JSON.parse(document.getElementById('profile-data').textContent || '[]');
224
+ const i18n = JSON.parse(document.getElementById('i18n-data').textContent || '{}');
225
+
226
+ const listEl = document.getElementById('profile-list');
227
+ const detailEl = document.getElementById('profile-detail');
228
+
229
+ function escapeHtml(value) {
230
+ return String(value)
231
+ .replace(/&/g, '&amp;')
232
+ .replace(/</g, '&lt;')
233
+ .replace(/>/g, '&gt;')
234
+ .replace(/"/g, '&quot;')
235
+ .replace(/'/g, '&#039;');
236
+ }
237
+
238
+ function renderTags(items) {
239
+ if (!items || items.length === 0) {
240
+ return `<div class="profile-subtitle">${escapeHtml(i18n.noEnvVars || '')}</div>`;
241
+ }
242
+ const tags = items.map(item => `<span class="tag">${escapeHtml(item)}</span>`).join('');
243
+ return `<div class="tag-list">${tags}</div>`;
244
+ }
245
+
246
+ function buildSnippet(template, profile) {
247
+ return template
248
+ .replace(/__PROFILE_ID__/g, profile.profileId)
249
+ .replace(/__PROFILE_NAME__/g, profile.profileName)
250
+ .replace(/__PROFILE_URL__/g, profile.mcpUrl)
251
+ .replace(/__PROFILE_SSE_URL__/g, profile.sseUrl);
252
+ }
253
+
254
+ function renderSnippets(profile, activeAuth) {
255
+ const blocks = [];
256
+ const order = ['vscode', 'cursor', 'jetbrains', 'claude'];
257
+ const snippets = profile.snippets || {};
258
+ for (const entry of snippets) {
259
+ if (!entry || !entry.key) continue;
260
+ if (activeAuth && entry.authKey && entry.authKey !== activeAuth) continue;
261
+ if (!order.some(prefix => entry.key.startsWith(prefix))) continue;
262
+ const title = entry.label || entry.key;
263
+ const format = entry.format || '';
264
+ const snippet = buildSnippet(entry.content, profile);
265
+ const encodedSnippet = encodeURIComponent(snippet);
266
+ blocks.push(`
267
+ <div class="detail-card">
268
+ <div class="snippet-header">
269
+ <h3>${escapeHtml(title)}</h3>
270
+ <div class="snippet-meta">
271
+ ${format ? `<span class="format-pill">${escapeHtml(format)}</span>` : ''}
272
+ <button class="copy-btn" data-copy="${encodedSnippet}">${escapeHtml(i18n.copy || '')}</button>
273
+ </div>
274
+ </div>
275
+ <pre><code>${escapeHtml(snippet)}</code></pre>
276
+ </div>
277
+ `);
278
+ }
279
+ return blocks.join('');
280
+ }
281
+
282
+ function buildProfileTitle(profile) {
283
+ const parts = [profile.profileId, profile.profileName, ...(profile.profileAliases || [])];
284
+ const seen = new Set();
285
+ const unique = [];
286
+ for (const part of parts) {
287
+ if (!part) continue;
288
+ const key = String(part);
289
+ if (seen.has(key)) continue;
290
+ seen.add(key);
291
+ unique.push(key);
292
+ }
293
+ if (unique.length === 0) return '';
294
+ if (unique.length === 1) return unique[0];
295
+ return `${unique[0]} (${unique.slice(1).join(', ')})`;
296
+ }
297
+
298
+ function renderDetail(profile) {
299
+ const title = buildProfileTitle(profile);
300
+ const description = profile.description || i18n.noDescription || '';
301
+ const authTabs = profile.authTabs || [];
302
+ const activeAuth = authTabs.length > 0 ? authTabs[0].key : null;
303
+ detailEl.innerHTML = `
304
+ <div class="detail-card">
305
+ <h3>${escapeHtml(i18n.profileLabel || '')}</h3>
306
+ <div class="profile-title">${escapeHtml(title)}</div>
307
+ <div class="profile-subtitle">${escapeHtml(description)}</div>
308
+ </div>
309
+ <div class="detail-card">
310
+ <h3>${escapeHtml(i18n.endpointLabel || '')}</h3>
311
+ <p><strong>MCP</strong>: ${escapeHtml(profile.mcpUrl)}</p>
312
+ <p><strong>SSE</strong>: ${escapeHtml(profile.sseUrl)}</p>
313
+ </div>
314
+ <div class="detail-card">
315
+ <div class="tab-row" id="auth-tabs">
316
+ ${authTabs.map(tab => `
317
+ <button class="tab${tab.key === activeAuth ? ' active' : ''}" data-auth="${escapeHtml(tab.key)}">
318
+ ${escapeHtml(tab.label)}
319
+ </button>
320
+ `).join('')}
321
+ </div>
322
+ <div class="snippets" id="snippet-list">
323
+ ${renderSnippets(profile, activeAuth)}
324
+ </div>
325
+ </div>
326
+ `;
327
+
328
+ const tabRow = detailEl.querySelector('#auth-tabs');
329
+ const snippetList = detailEl.querySelector('#snippet-list');
330
+ if (tabRow && snippetList) {
331
+ tabRow.addEventListener('click', event => {
332
+ const target = event.target;
333
+ if (!(target instanceof HTMLElement)) return;
334
+ const authKey = target.getAttribute('data-auth');
335
+ if (!authKey) return;
336
+ tabRow.querySelectorAll('.tab').forEach(tab => {
337
+ tab.classList.toggle('active', tab.getAttribute('data-auth') === authKey);
338
+ });
339
+ snippetList.innerHTML = renderSnippets(profile, authKey);
340
+ });
341
+ }
342
+ }
343
+
344
+ function renderList(activeId) {
345
+ listEl.innerHTML = '';
346
+ profileData.forEach(profile => {
347
+ const title = buildProfileTitle(profile);
348
+ const item = document.createElement('div');
349
+ item.className = 'profile-item' + (profile.profileId === activeId ? ' active' : '');
350
+ item.innerHTML = `
351
+ <div class="profile-title">${escapeHtml(title)}</div>
352
+ <div class="profile-subtitle">${escapeHtml(profile.description || i18n.noDescription || '')}</div>
353
+ `;
354
+ item.addEventListener('click', () => {
355
+ renderList(profile.profileId);
356
+ renderDetail(profile);
357
+ });
358
+ listEl.appendChild(item);
359
+ });
360
+ }
361
+
362
+ if (profileData.length > 0) {
363
+ renderList(profileData[0].profileId);
364
+ renderDetail(profileData[0]);
365
+ } else {
366
+ detailEl.innerHTML = `<div class="notice">${escapeHtml(i18n.noProfiles || '')}</div>`;
367
+ }
368
+
369
+ document.addEventListener('click', event => {
370
+ const target = event.target;
371
+ if (!(target instanceof HTMLElement)) return;
372
+ if (!target.matches('[data-copy]')) return;
373
+ const text = target.getAttribute('data-copy');
374
+ if (!text) return;
375
+ const decoded = decodeURIComponent(text);
376
+ navigator.clipboard?.writeText(decoded).then(() => {
377
+ target.textContent = i18n.copied || '';
378
+ setTimeout(() => { target.textContent = i18n.copy || ''; }, 1200);
379
+ }).catch(() => {
380
+ target.textContent = i18n.copyFailed || '';
381
+ setTimeout(() => { target.textContent = i18n.copy || ''; }, 1200);
382
+ });
383
+ });
384
+ </script>
385
+ </body>
386
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp4openapi",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Universal MCP server that generates tools from any OpenAPI specification",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "files": [
15
15
  "dist",
16
+ "html",
16
17
  "profiles",
17
18
  "profile-schema.json",
18
19
  "README.md",
@@ -248,6 +248,20 @@
248
248
  },
249
249
  "minItems": 1
250
250
  },
251
+ "minLength": {
252
+ "type": "integer",
253
+ "description": "Minimum length for string parameters",
254
+ "minimum": 0
255
+ },
256
+ "maxLength": {
257
+ "type": "integer",
258
+ "description": "Maximum length for string parameters",
259
+ "minimum": 0
260
+ },
261
+ "pattern": {
262
+ "type": "string",
263
+ "description": "Regex pattern for string parameters"
264
+ },
251
265
  "items": {
252
266
  "type": "object",
253
267
  "description": "Item type for array parameters",
@@ -4,7 +4,7 @@
4
4
  "profile_id": "gitlab",
5
5
  "profile_aliases": ["gitlab-default", "gitlab-developer"],
6
6
  "openapi_spec_path": "./openapi.yaml",
7
- "description": "Developer-focused tools for common GitLab workflows",
7
+ "description": "Comprehensive operation-style tool set (manage_merge_requests, manage_issues, manage_projects, ...).",
8
8
 
9
9
  "parameter_aliases": {
10
10
  "id": ["project_id", "group_id", "resource_id"],