@frontmcp/adapters 1.1.2 → 1.2.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 (64) hide show
  1. package/esm/index.mjs +396 -385
  2. package/esm/openapi/index.mjs +396 -385
  3. package/esm/package.json +18 -6
  4. package/esm/skills/index.mjs +2117 -0
  5. package/index.js +407 -389
  6. package/openapi/index.d.ts +11 -1
  7. package/openapi/index.d.ts.map +1 -1
  8. package/openapi/index.js +407 -389
  9. package/openapi/openapi.adapter.d.ts +2 -2
  10. package/openapi/openapi.adapter.d.ts.map +1 -1
  11. package/openapi/openapi.tool.d.ts.map +1 -1
  12. package/openapi/openapi.types.d.ts +3 -3
  13. package/openapi/openapi.types.d.ts.map +1 -1
  14. package/openapi/openapi.utils.d.ts.map +1 -1
  15. package/package.json +18 -6
  16. package/skills/audit/audit-chain.d.ts +81 -0
  17. package/skills/audit/audit-chain.d.ts.map +1 -0
  18. package/skills/audit/audit-config.d.ts +87 -0
  19. package/skills/audit/audit-config.d.ts.map +1 -0
  20. package/skills/audit/audit-record.types.d.ts +114 -0
  21. package/skills/audit/audit-record.types.d.ts.map +1 -0
  22. package/skills/audit/audit-signer.d.ts +72 -0
  23. package/skills/audit/audit-signer.d.ts.map +1 -0
  24. package/skills/audit/audit-store.d.ts +84 -0
  25. package/skills/audit/audit-store.d.ts.map +1 -0
  26. package/skills/audit/audit-writer.d.ts +175 -0
  27. package/skills/audit/audit-writer.d.ts.map +1 -0
  28. package/skills/audit/index.d.ts +7 -0
  29. package/skills/audit/index.d.ts.map +1 -0
  30. package/skills/bundle/bundle-diff.d.ts +30 -0
  31. package/skills/bundle/bundle-diff.d.ts.map +1 -0
  32. package/skills/bundle/bundle.schema.d.ts +101 -0
  33. package/skills/bundle/bundle.schema.d.ts.map +1 -0
  34. package/skills/bundle/bundle.store.d.ts +143 -0
  35. package/skills/bundle/bundle.store.d.ts.map +1 -0
  36. package/skills/bundle/bundle.types.d.ts +143 -0
  37. package/skills/bundle/bundle.types.d.ts.map +1 -0
  38. package/skills/bundle/overlay-parser.d.ts +42 -0
  39. package/skills/bundle/overlay-parser.d.ts.map +1 -0
  40. package/skills/dependency/skill-dag.d.ts +39 -0
  41. package/skills/dependency/skill-dag.d.ts.map +1 -0
  42. package/skills/index.d.ts +17 -0
  43. package/skills/index.d.ts.map +1 -0
  44. package/skills/index.js +2192 -0
  45. package/skills/security/bundle-signature.d.ts +44 -0
  46. package/skills/security/bundle-signature.d.ts.map +1 -0
  47. package/skills/security/jwt-verifier.d.ts +38 -0
  48. package/skills/security/jwt-verifier.d.ts.map +1 -0
  49. package/skills/security/webhook-replay-guard.d.ts +52 -0
  50. package/skills/security/webhook-replay-guard.d.ts.map +1 -0
  51. package/skills/source-options.d.ts +55 -0
  52. package/skills/source-options.d.ts.map +1 -0
  53. package/skills/sources/filesystem-skills.source.d.ts +127 -0
  54. package/skills/sources/filesystem-skills.source.d.ts.map +1 -0
  55. package/skills/sources/index.d.ts +10 -0
  56. package/skills/sources/index.d.ts.map +1 -0
  57. package/skills/sources/npm.source.d.ts +30 -0
  58. package/skills/sources/npm.source.d.ts.map +1 -0
  59. package/skills/sources/saas-pull.source.d.ts +47 -0
  60. package/skills/sources/saas-pull.source.d.ts.map +1 -0
  61. package/skills/sources/skill-bundle-source.interface.d.ts +36 -0
  62. package/skills/sources/skill-bundle-source.interface.d.ts.map +1 -0
  63. package/skills/sources/static.source.d.ts +30 -0
  64. package/skills/sources/static.source.d.ts.map +1 -0
package/index.js CHANGED
@@ -32,19 +32,371 @@ __export(index_exports, {
32
32
  OpenAPIFetchError: () => OpenAPIFetchError,
33
33
  OpenApiSpecPoller: () => OpenApiSpecPoller,
34
34
  OpenapiAdapter: () => OpenapiAdapter,
35
+ applyAdditionalHeaders: () => applyAdditionalHeaders,
36
+ buildRequest: () => buildRequest,
37
+ createSecurityContextFromAuth: () => createSecurityContextFromAuth,
38
+ extractSecuritySchemes: () => extractSecuritySchemes,
35
39
  forceJwtSecurity: () => forceJwtSecurity,
36
- removeSecurityFromOperations: () => removeSecurityFromOperations
40
+ parseResponse: () => parseResponse,
41
+ removeSecurityFromOperations: () => removeSecurityFromOperations,
42
+ resolveToolSecurity: () => resolveToolSecurity,
43
+ validateSecurityConfiguration: () => validateSecurityConfiguration
37
44
  });
38
45
  module.exports = __toCommonJS(index_exports);
39
46
 
40
47
  // libs/adapters/src/openapi/openapi.adapter.ts
41
- var import_sdk4 = require("@frontmcp/sdk");
42
48
  var import_mcp_from_openapi2 = require("mcp-from-openapi");
49
+ var import_sdk4 = require("@frontmcp/sdk");
50
+
51
+ // libs/adapters/src/openapi/openapi-spec-poller.ts
52
+ var import_utils = require("@frontmcp/utils");
53
+
54
+ // libs/adapters/src/openapi/openapi.errors.ts
55
+ var import_sdk = require("@frontmcp/sdk");
56
+ var OpenAPIFetchError = class extends import_sdk.InternalMcpError {
57
+ constructor(url, status, statusText) {
58
+ super(`OpenAPI spec fetch failed from "${url}": ${status} ${statusText}`, "OPENAPI_FETCH_FAILED");
59
+ }
60
+ };
61
+
62
+ // libs/adapters/src/openapi/openapi-spec-poller.ts
63
+ var DEFAULT_RETRY = {
64
+ maxRetries: 3,
65
+ initialDelayMs: 1e3,
66
+ maxDelayMs: 1e4,
67
+ backoffMultiplier: 2
68
+ };
69
+ var OpenApiSpecPoller = class {
70
+ url;
71
+ intervalMs;
72
+ fetchTimeoutMs;
73
+ changeDetection;
74
+ retry;
75
+ unhealthyThreshold;
76
+ headers;
77
+ callbacks;
78
+ intervalTimer = null;
79
+ lastHash = null;
80
+ lastEtag = null;
81
+ lastModified = null;
82
+ consecutiveFailures = 0;
83
+ isPolling = false;
84
+ wasUnhealthy = false;
85
+ health = "unknown";
86
+ constructor(url, options = {}, callbacks = {}) {
87
+ this.url = url;
88
+ this.intervalMs = options.intervalMs ?? 6e4;
89
+ this.fetchTimeoutMs = options.fetchTimeoutMs ?? 1e4;
90
+ this.changeDetection = options.changeDetection ?? "auto";
91
+ this.retry = { ...DEFAULT_RETRY, ...options.retry };
92
+ this.unhealthyThreshold = options.unhealthyThreshold ?? 3;
93
+ this.headers = options.headers ?? {};
94
+ this.callbacks = callbacks;
95
+ }
96
+ /**
97
+ * Start polling for changes.
98
+ */
99
+ start() {
100
+ if (this.intervalTimer) return;
101
+ this.poll().catch(() => {
102
+ });
103
+ this.intervalTimer = setInterval(() => {
104
+ this.poll().catch(() => {
105
+ });
106
+ }, this.intervalMs);
107
+ }
108
+ /**
109
+ * Stop polling.
110
+ */
111
+ stop() {
112
+ if (this.intervalTimer) {
113
+ clearInterval(this.intervalTimer);
114
+ this.intervalTimer = null;
115
+ }
116
+ }
117
+ /**
118
+ * Get current stats.
119
+ */
120
+ getStats() {
121
+ return {
122
+ hash: this.lastHash,
123
+ consecutiveFailures: this.consecutiveFailures,
124
+ health: this.health,
125
+ isRunning: this.intervalTimer !== null
126
+ };
127
+ }
128
+ /**
129
+ * Perform a single poll cycle.
130
+ */
131
+ async poll() {
132
+ if (this.isPolling) return;
133
+ this.isPolling = true;
134
+ try {
135
+ await this.fetchWithRetry();
136
+ } finally {
137
+ this.isPolling = false;
138
+ }
139
+ }
140
+ async fetchWithRetry() {
141
+ const { maxRetries, initialDelayMs, maxDelayMs, backoffMultiplier } = this.retry;
142
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
143
+ try {
144
+ await this.doFetch();
145
+ if (this.consecutiveFailures > 0) {
146
+ this.consecutiveFailures = 0;
147
+ if (this.wasUnhealthy) {
148
+ this.wasUnhealthy = false;
149
+ this.health = "healthy";
150
+ this.callbacks.onRecovered?.();
151
+ }
152
+ }
153
+ this.health = "healthy";
154
+ return;
155
+ } catch (error) {
156
+ if (attempt === maxRetries) {
157
+ this.consecutiveFailures++;
158
+ this.callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
159
+ if (this.consecutiveFailures >= this.unhealthyThreshold && !this.wasUnhealthy) {
160
+ this.wasUnhealthy = true;
161
+ this.health = "unhealthy";
162
+ this.callbacks.onUnhealthy?.(this.consecutiveFailures);
163
+ }
164
+ return;
165
+ }
166
+ const delay = Math.min(initialDelayMs * Math.pow(backoffMultiplier, attempt), maxDelayMs);
167
+ await new Promise((r) => setTimeout(r, delay));
168
+ }
169
+ }
170
+ }
171
+ async doFetch() {
172
+ const headers = { ...this.headers };
173
+ if (this.changeDetection !== "content-hash") {
174
+ if (this.lastEtag) {
175
+ headers["If-None-Match"] = this.lastEtag;
176
+ }
177
+ if (this.lastModified) {
178
+ headers["If-Modified-Since"] = this.lastModified;
179
+ }
180
+ }
181
+ const controller = new AbortController();
182
+ const timeout = setTimeout(() => controller.abort(), this.fetchTimeoutMs);
183
+ try {
184
+ const response = await fetch(this.url, {
185
+ headers,
186
+ signal: controller.signal
187
+ });
188
+ if (response.status === 304) {
189
+ this.callbacks.onUnchanged?.();
190
+ return;
191
+ }
192
+ if (!response.ok) {
193
+ throw new OpenAPIFetchError(this.url, response.status, response.statusText);
194
+ }
195
+ const etag = response.headers.get("etag");
196
+ const lastModified = response.headers.get("last-modified");
197
+ if (etag) this.lastEtag = etag;
198
+ if (lastModified) this.lastModified = lastModified;
199
+ const body = await response.text();
200
+ const hash = (0, import_utils.sha256Hex)(body);
201
+ if (this.lastHash && this.lastHash === hash) {
202
+ this.callbacks.onUnchanged?.();
203
+ return;
204
+ }
205
+ this.lastHash = hash;
206
+ this.callbacks.onChanged?.(body, hash);
207
+ } finally {
208
+ clearTimeout(timeout);
209
+ }
210
+ }
211
+ /**
212
+ * Dispose the poller and release resources.
213
+ */
214
+ dispose() {
215
+ this.stop();
216
+ }
217
+ };
218
+
219
+ // libs/adapters/src/openapi/openapi.security.ts
220
+ var import_mcp_from_openapi = require("mcp-from-openapi");
221
+ async function createSecurityContextFromAuth(tool2, ctx, options) {
222
+ if (options.securityResolver) {
223
+ return await options.securityResolver(tool2, ctx);
224
+ }
225
+ if (options.authProviderMapper) {
226
+ const context = (0, import_mcp_from_openapi.createSecurityContext)({});
227
+ const securitySchemes = /* @__PURE__ */ new Set();
228
+ for (const mapper of tool2.mapper) {
229
+ if (mapper.security?.scheme) {
230
+ securitySchemes.add(mapper.security.scheme);
231
+ }
232
+ }
233
+ for (const scheme of securitySchemes) {
234
+ const authExtractor = options.authProviderMapper[scheme];
235
+ if (authExtractor) {
236
+ try {
237
+ const token = authExtractor(ctx);
238
+ if (token !== void 0 && token !== null && typeof token !== "string") {
239
+ throw new Error(
240
+ `authProviderMapper['${scheme}'] must return a string or undefined, but returned: ${typeof token}`
241
+ );
242
+ }
243
+ if (token === "") {
244
+ throw new Error(
245
+ `authProviderMapper['${scheme}'] returned empty string. Return undefined/null if no token is available, or provide a valid token.`
246
+ );
247
+ }
248
+ if (token) {
249
+ const schemeMapper = tool2.mapper.find((m) => m.security?.scheme === scheme);
250
+ const schemeType = schemeMapper?.security?.type?.toLowerCase();
251
+ const httpScheme = schemeMapper?.security?.httpScheme?.toLowerCase();
252
+ if (schemeType === "apikey") {
253
+ if (!context.apiKey) {
254
+ context.apiKey = token;
255
+ }
256
+ } else if (schemeType === "http" && httpScheme === "basic") {
257
+ if (!context.basic) {
258
+ context.basic = token;
259
+ }
260
+ } else if (schemeType === "oauth2") {
261
+ if (!context.oauth2Token) {
262
+ context.oauth2Token = token;
263
+ }
264
+ } else {
265
+ if (!context.jwt) {
266
+ context.jwt = token;
267
+ }
268
+ }
269
+ }
270
+ } catch (err) {
271
+ if (err instanceof Error && err.message.includes("authProviderMapper")) {
272
+ throw err;
273
+ }
274
+ const errorMessage = err instanceof Error ? err.message : String(err);
275
+ throw new Error(`authProviderMapper['${scheme}'] threw an error: ${errorMessage}`);
276
+ }
277
+ }
278
+ }
279
+ const hasAnyAuth = context.jwt || context.apiKey || context.basic || context.oauth2Token;
280
+ const authToken = ctx.authInfo?.token;
281
+ if (!hasAnyAuth && authToken) {
282
+ if (typeof authToken !== "string") {
283
+ throw new Error(`authInfo.token must be a string, but got: ${typeof authToken}`);
284
+ }
285
+ context.jwt = authToken;
286
+ }
287
+ return context;
288
+ }
289
+ if (options.staticAuth) {
290
+ return (0, import_mcp_from_openapi.createSecurityContext)(options.staticAuth);
291
+ }
292
+ return (0, import_mcp_from_openapi.createSecurityContext)({
293
+ jwt: ctx.authInfo?.token
294
+ });
295
+ }
296
+ function extractSecuritySchemes(tools) {
297
+ const schemes = /* @__PURE__ */ new Set();
298
+ for (const tool2 of tools) {
299
+ for (const mapper of tool2.mapper) {
300
+ if (mapper.security?.scheme) {
301
+ schemes.add(mapper.security.scheme);
302
+ }
303
+ }
304
+ }
305
+ return schemes;
306
+ }
307
+ function validateSecurityConfiguration(tools, options) {
308
+ const result = {
309
+ valid: true,
310
+ missingMappings: [],
311
+ warnings: [],
312
+ securityRiskScore: "low"
313
+ };
314
+ const securitySchemes = extractSecuritySchemes(tools);
315
+ const includeSecurityInInput = options.generateOptions?.includeSecurityInInput ?? false;
316
+ if (includeSecurityInInput) {
317
+ result.securityRiskScore = "high";
318
+ result.warnings.push(
319
+ "SECURITY WARNING: includeSecurityInInput is enabled. Users will provide authentication directly in tool inputs. This increases security risk as credentials may be logged or exposed."
320
+ );
321
+ return result;
322
+ }
323
+ if (options.securityResolver) {
324
+ result.securityRiskScore = "low";
325
+ result.warnings.push(
326
+ "INFO: Using custom securityResolver. Ensure your resolver properly validates and secures credentials from context."
327
+ );
328
+ return result;
329
+ }
330
+ if (options.staticAuth && Object.keys(options.staticAuth).length > 0) {
331
+ result.securityRiskScore = "medium";
332
+ result.warnings.push(
333
+ "SECURITY INFO: Using staticAuth with hardcoded credentials. Ensure credentials are stored securely (environment variables, secrets manager)."
334
+ );
335
+ return result;
336
+ }
337
+ const schemesInInput = new Set(options.securitySchemesInInput || []);
338
+ if (options.authProviderMapper || schemesInInput.size > 0) {
339
+ result.securityRiskScore = schemesInInput.size > 0 ? "medium" : "low";
340
+ if (schemesInInput.size > 0) {
341
+ result.warnings.push(
342
+ `INFO: Per-scheme security control enabled. Schemes in input: ${Array.from(schemesInInput).join(", ")}`
343
+ );
344
+ }
345
+ for (const scheme of securitySchemes) {
346
+ if (schemesInInput.has(scheme)) {
347
+ continue;
348
+ }
349
+ if (!options.authProviderMapper?.[scheme]) {
350
+ result.valid = false;
351
+ result.missingMappings.push(scheme);
352
+ }
353
+ }
354
+ if (!result.valid) {
355
+ result.warnings.push(
356
+ `ERROR: Missing auth provider mappings for security schemes: ${result.missingMappings.join(", ")}`
357
+ );
358
+ }
359
+ return result;
360
+ }
361
+ if (securitySchemes.size > 0) {
362
+ result.securityRiskScore = "medium";
363
+ result.warnings.push(
364
+ `INFO: No auth configuration provided. Using default ctx.authInfo.token for all security schemes: ${Array.from(
365
+ securitySchemes
366
+ ).join(", ")}`
367
+ );
368
+ result.warnings.push(
369
+ "RECOMMENDATION: For multiple auth providers, use authProviderMapper or securityResolver to map each security scheme to the correct auth provider."
370
+ );
371
+ }
372
+ return result;
373
+ }
374
+ async function resolveToolSecurity(tool2, ctx, options) {
375
+ const securityResolver = new import_mcp_from_openapi.SecurityResolver();
376
+ const securityContext = await createSecurityContextFromAuth(tool2, ctx, options);
377
+ const hasAuth = securityContext.jwt || securityContext.apiKey || securityContext.basic || securityContext.oauth2Token || securityContext.apiKeys && Object.keys(securityContext.apiKeys).length > 0 || securityContext.customHeaders && Object.keys(securityContext.customHeaders).length > 0;
378
+ const requiresSecurity = tool2.mapper.some((m) => m.security && m.required === true);
379
+ if (requiresSecurity && !hasAuth) {
380
+ const requiredSchemes = tool2.mapper.filter((m) => m.security && m.required === true).map((m) => m.security?.scheme ?? "unknown");
381
+ const uniqueSchemes = [...new Set(requiredSchemes)];
382
+ const schemesStr = uniqueSchemes.join(", ") || "unknown";
383
+ const firstScheme = uniqueSchemes[0] || "BearerAuth";
384
+ throw new Error(
385
+ `Authentication required for tool '${tool2.name}' but no auth configuration found.
386
+ Required security schemes: ${schemesStr}
387
+ Solutions:
388
+ 1. Add authProviderMapper: { '${firstScheme}': (ctx) => ctx.authInfo.user?.token }
389
+ 2. Add securityResolver: (tool, ctx) => ({ jwt: ctx.authInfo.token })
390
+ 3. Add staticAuth: { jwt: process.env.API_TOKEN }
391
+ 4. Set generateOptions.includeSecurityInInput: true (not recommended for production)`
392
+ );
393
+ }
394
+ return await securityResolver.resolve(tool2.mapper, securityContext);
395
+ }
43
396
 
44
397
  // libs/adapters/src/openapi/openapi.tool.ts
45
- var import_zod_from_json_schema = require("zod-from-json-schema");
46
398
  var import_lazy_zod2 = require("@frontmcp/lazy-zod");
47
- var import_sdk2 = require("@frontmcp/sdk");
399
+ var import_sdk3 = require("@frontmcp/sdk");
48
400
 
49
401
  // libs/adapters/src/openapi/openapi.frontmcp-schema.ts
50
402
  var import_lazy_zod = require("@frontmcp/lazy-zod");
@@ -247,226 +599,48 @@ function extractValidCodeCall(data, warnings) {
247
599
  warnings.push(`Invalid 'codecall.enabledInCodeCall': expected boolean`);
248
600
  }
249
601
  if (typeof obj["visibleInListTools"] === "boolean") {
250
- result.visibleInListTools = obj["visibleInListTools"];
251
- } else if (obj["visibleInListTools"] !== void 0) {
252
- warnings.push(`Invalid 'codecall.visibleInListTools': expected boolean`);
253
- }
254
- for (const key of Object.keys(obj)) {
255
- if (!KNOWN_CODECALL_FIELDS.has(key)) {
256
- warnings.push(`Unknown field 'codecall.${key}' (will be ignored)`);
257
- }
258
- }
259
- return Object.keys(result).length > 0 ? result : void 0;
260
- }
261
- function extractValidTags(data) {
262
- if (!Array.isArray(data)) return void 0;
263
- const validTags = data.filter((item) => typeof item === "string");
264
- return validTags.length > 0 ? validTags : void 0;
265
- }
266
- function extractValidExamples(data, warnings) {
267
- if (!Array.isArray(data)) return void 0;
268
- const validExamples = [];
269
- for (let i = 0; i < data.length; i++) {
270
- const item = data[i];
271
- const result = FrontMcpExampleSchema.safeParse(item);
272
- if (result.success) {
273
- validExamples.push(result.data);
274
- } else {
275
- warnings.push(
276
- `Invalid example at index ${i}: ${formatZodIssues(result.error.issues, `examples[${i}]`).join(", ")}`
277
- );
278
- }
279
- }
280
- return validExamples.length > 0 ? validExamples : void 0;
281
- }
282
- function formatZodIssues(issues, prefix) {
283
- return issues.map((issue) => {
284
- const path = issue.path.length > 0 ? `${prefix}.${issue.path.join(".")}` : prefix;
285
- return `Invalid '${path}': ${issue.message}`;
286
- });
287
- }
288
-
289
- // libs/adapters/src/openapi/openapi.security.ts
290
- var import_mcp_from_openapi = require("mcp-from-openapi");
291
- async function createSecurityContextFromAuth(tool2, ctx, options) {
292
- if (options.securityResolver) {
293
- return await options.securityResolver(tool2, ctx);
294
- }
295
- if (options.authProviderMapper) {
296
- const context = (0, import_mcp_from_openapi.createSecurityContext)({});
297
- const securitySchemes = /* @__PURE__ */ new Set();
298
- for (const mapper of tool2.mapper) {
299
- if (mapper.security?.scheme) {
300
- securitySchemes.add(mapper.security.scheme);
301
- }
302
- }
303
- for (const scheme of securitySchemes) {
304
- const authExtractor = options.authProviderMapper[scheme];
305
- if (authExtractor) {
306
- try {
307
- const token = authExtractor(ctx);
308
- if (token !== void 0 && token !== null && typeof token !== "string") {
309
- throw new Error(
310
- `authProviderMapper['${scheme}'] must return a string or undefined, but returned: ${typeof token}`
311
- );
312
- }
313
- if (token === "") {
314
- throw new Error(
315
- `authProviderMapper['${scheme}'] returned empty string. Return undefined/null if no token is available, or provide a valid token.`
316
- );
317
- }
318
- if (token) {
319
- const schemeMapper = tool2.mapper.find((m) => m.security?.scheme === scheme);
320
- const schemeType = schemeMapper?.security?.type?.toLowerCase();
321
- const httpScheme = schemeMapper?.security?.httpScheme?.toLowerCase();
322
- if (schemeType === "apikey") {
323
- if (!context.apiKey) {
324
- context.apiKey = token;
325
- }
326
- } else if (schemeType === "http" && httpScheme === "basic") {
327
- if (!context.basic) {
328
- context.basic = token;
329
- }
330
- } else if (schemeType === "oauth2") {
331
- if (!context.oauth2Token) {
332
- context.oauth2Token = token;
333
- }
334
- } else {
335
- if (!context.jwt) {
336
- context.jwt = token;
337
- }
338
- }
339
- }
340
- } catch (err) {
341
- if (err instanceof Error && err.message.includes("authProviderMapper")) {
342
- throw err;
343
- }
344
- const errorMessage = err instanceof Error ? err.message : String(err);
345
- throw new Error(`authProviderMapper['${scheme}'] threw an error: ${errorMessage}`);
346
- }
347
- }
348
- }
349
- const hasAnyAuth = context.jwt || context.apiKey || context.basic || context.oauth2Token;
350
- const authToken = ctx.authInfo?.token;
351
- if (!hasAnyAuth && authToken) {
352
- if (typeof authToken !== "string") {
353
- throw new Error(`authInfo.token must be a string, but got: ${typeof authToken}`);
354
- }
355
- context.jwt = authToken;
356
- }
357
- return context;
358
- }
359
- if (options.staticAuth) {
360
- return (0, import_mcp_from_openapi.createSecurityContext)(options.staticAuth);
361
- }
362
- return (0, import_mcp_from_openapi.createSecurityContext)({
363
- jwt: ctx.authInfo?.token
364
- });
365
- }
366
- function extractSecuritySchemes(tools) {
367
- const schemes = /* @__PURE__ */ new Set();
368
- for (const tool2 of tools) {
369
- for (const mapper of tool2.mapper) {
370
- if (mapper.security?.scheme) {
371
- schemes.add(mapper.security.scheme);
372
- }
373
- }
374
- }
375
- return schemes;
376
- }
377
- function validateSecurityConfiguration(tools, options) {
378
- const result = {
379
- valid: true,
380
- missingMappings: [],
381
- warnings: [],
382
- securityRiskScore: "low"
383
- };
384
- const securitySchemes = extractSecuritySchemes(tools);
385
- const includeSecurityInInput = options.generateOptions?.includeSecurityInInput ?? false;
386
- if (includeSecurityInInput) {
387
- result.securityRiskScore = "high";
388
- result.warnings.push(
389
- "SECURITY WARNING: includeSecurityInInput is enabled. Users will provide authentication directly in tool inputs. This increases security risk as credentials may be logged or exposed."
390
- );
391
- return result;
392
- }
393
- if (options.securityResolver) {
394
- result.securityRiskScore = "low";
395
- result.warnings.push(
396
- "INFO: Using custom securityResolver. Ensure your resolver properly validates and secures credentials from context."
397
- );
398
- return result;
399
- }
400
- if (options.staticAuth && Object.keys(options.staticAuth).length > 0) {
401
- result.securityRiskScore = "medium";
402
- result.warnings.push(
403
- "SECURITY INFO: Using staticAuth with hardcoded credentials. Ensure credentials are stored securely (environment variables, secrets manager)."
404
- );
405
- return result;
602
+ result.visibleInListTools = obj["visibleInListTools"];
603
+ } else if (obj["visibleInListTools"] !== void 0) {
604
+ warnings.push(`Invalid 'codecall.visibleInListTools': expected boolean`);
406
605
  }
407
- const schemesInInput = new Set(options.securitySchemesInInput || []);
408
- if (options.authProviderMapper || schemesInInput.size > 0) {
409
- result.securityRiskScore = schemesInInput.size > 0 ? "medium" : "low";
410
- if (schemesInInput.size > 0) {
411
- result.warnings.push(
412
- `INFO: Per-scheme security control enabled. Schemes in input: ${Array.from(schemesInInput).join(", ")}`
413
- );
414
- }
415
- for (const scheme of securitySchemes) {
416
- if (schemesInInput.has(scheme)) {
417
- continue;
418
- }
419
- if (!options.authProviderMapper?.[scheme]) {
420
- result.valid = false;
421
- result.missingMappings.push(scheme);
422
- }
606
+ for (const key of Object.keys(obj)) {
607
+ if (!KNOWN_CODECALL_FIELDS.has(key)) {
608
+ warnings.push(`Unknown field 'codecall.${key}' (will be ignored)`);
423
609
  }
424
- if (!result.valid) {
425
- result.warnings.push(
426
- `ERROR: Missing auth provider mappings for security schemes: ${result.missingMappings.join(", ")}`
610
+ }
611
+ return Object.keys(result).length > 0 ? result : void 0;
612
+ }
613
+ function extractValidTags(data) {
614
+ if (!Array.isArray(data)) return void 0;
615
+ const validTags = data.filter((item) => typeof item === "string");
616
+ return validTags.length > 0 ? validTags : void 0;
617
+ }
618
+ function extractValidExamples(data, warnings) {
619
+ if (!Array.isArray(data)) return void 0;
620
+ const validExamples = [];
621
+ for (let i = 0; i < data.length; i++) {
622
+ const item = data[i];
623
+ const result = FrontMcpExampleSchema.safeParse(item);
624
+ if (result.success) {
625
+ validExamples.push(result.data);
626
+ } else {
627
+ warnings.push(
628
+ `Invalid example at index ${i}: ${formatZodIssues(result.error.issues, `examples[${i}]`).join(", ")}`
427
629
  );
428
630
  }
429
- return result;
430
- }
431
- if (securitySchemes.size > 0) {
432
- result.securityRiskScore = "medium";
433
- result.warnings.push(
434
- `INFO: No auth configuration provided. Using default ctx.authInfo.token for all security schemes: ${Array.from(
435
- securitySchemes
436
- ).join(", ")}`
437
- );
438
- result.warnings.push(
439
- "RECOMMENDATION: For multiple auth providers, use authProviderMapper or securityResolver to map each security scheme to the correct auth provider."
440
- );
441
631
  }
442
- return result;
632
+ return validExamples.length > 0 ? validExamples : void 0;
443
633
  }
444
- async function resolveToolSecurity(tool2, ctx, options) {
445
- const securityResolver = new import_mcp_from_openapi.SecurityResolver();
446
- const securityContext = await createSecurityContextFromAuth(tool2, ctx, options);
447
- const hasAuth = securityContext.jwt || securityContext.apiKey || securityContext.basic || securityContext.oauth2Token || securityContext.apiKeys && Object.keys(securityContext.apiKeys).length > 0 || securityContext.customHeaders && Object.keys(securityContext.customHeaders).length > 0;
448
- const requiresSecurity = tool2.mapper.some((m) => m.security && m.required === true);
449
- if (requiresSecurity && !hasAuth) {
450
- const requiredSchemes = tool2.mapper.filter((m) => m.security && m.required === true).map((m) => m.security?.scheme ?? "unknown");
451
- const uniqueSchemes = [...new Set(requiredSchemes)];
452
- const schemesStr = uniqueSchemes.join(", ") || "unknown";
453
- const firstScheme = uniqueSchemes[0] || "BearerAuth";
454
- throw new Error(
455
- `Authentication required for tool '${tool2.name}' but no auth configuration found.
456
- Required security schemes: ${schemesStr}
457
- Solutions:
458
- 1. Add authProviderMapper: { '${firstScheme}': (ctx) => ctx.authInfo.user?.token }
459
- 2. Add securityResolver: (tool, ctx) => ({ jwt: ctx.authInfo.token })
460
- 3. Add staticAuth: { jwt: process.env.API_TOKEN }
461
- 4. Set generateOptions.includeSecurityInInput: true (not recommended for production)`
462
- );
463
- }
464
- return await securityResolver.resolve(tool2.mapper, securityContext);
634
+ function formatZodIssues(issues, prefix) {
635
+ return issues.map((issue) => {
636
+ const path = issue.path.length > 0 ? `${prefix}.${issue.path.join(".")}` : prefix;
637
+ return `Invalid '${path}': ${issue.message}`;
638
+ });
465
639
  }
466
640
 
467
641
  // libs/adapters/src/openapi/openapi.utils.ts
468
- var import_utils = require("@frontmcp/utils");
469
- var import_sdk = require("@frontmcp/sdk");
642
+ var import_sdk2 = require("@frontmcp/sdk");
643
+ var import_utils2 = require("@frontmcp/utils");
470
644
  function coerceToString(value, paramName, location) {
471
645
  if (value === null || value === void 0) {
472
646
  return "";
@@ -493,8 +667,8 @@ function appendCookie(headers, name, value) {
493
667
  }
494
668
  function buildRequest(tool2, input, security, baseUrl) {
495
669
  const rawBaseUrl = tool2.metadata.servers?.[0]?.url || baseUrl;
496
- (0, import_utils.validateBaseUrl)(rawBaseUrl);
497
- const apiBaseUrl = rawBaseUrl.replace(/\/+$/, "");
670
+ (0, import_utils2.validateBaseUrl)(rawBaseUrl);
671
+ const apiBaseUrl = (0, import_utils2.trimTrailing)(rawBaseUrl, "/");
498
672
  let path = tool2.metadata.path;
499
673
  const queryParams = new URLSearchParams();
500
674
  const headers = new Headers({
@@ -523,7 +697,7 @@ function buildRequest(tool2, input, security, baseUrl) {
523
697
  case "header": {
524
698
  const headerValue = coerceToString(value, mapper.key, "header");
525
699
  if (/[\r\n\x00\f\v]/.test(headerValue)) {
526
- throw new import_sdk.PublicMcpError(
700
+ throw new import_sdk2.PublicMcpError(
527
701
  `Invalid header value for '${mapper.key}': contains control characters (possible header injection attack)`,
528
702
  "INVALID_HEADER_VALUE",
529
703
  400
@@ -688,7 +862,7 @@ function createOpenApiTool(openapiTool, options, logger) {
688
862
  if (toolTransform.ui) {
689
863
  toolMetadata["ui"] = toolTransform.ui;
690
864
  }
691
- return (0, import_sdk2.tool)(toolMetadata)(async (input, toolCtx) => {
865
+ return (0, import_sdk3.tool)(toolMetadata)(async (input, toolCtx) => {
692
866
  const ctx = toolCtx.context;
693
867
  const transformContext = {
694
868
  ctx,
@@ -877,7 +1051,12 @@ function getZodSchemaFromJsonSchema(jsonSchema, toolName, logger) {
877
1051
  return { schema: import_lazy_zod2.z.looseObject({}), conversionFailed: true, error: "No valid JSON schema" };
878
1052
  }
879
1053
  try {
880
- const zodSchema = (0, import_zod_from_json_schema.convertJsonSchemaToZod)(jsonSchema);
1054
+ const zodSchema = import_lazy_zod2.z.fromJSONSchema(
1055
+ jsonSchema,
1056
+ {
1057
+ defaultTarget: "draft-2020-12"
1058
+ }
1059
+ );
881
1060
  if (typeof zodSchema?.parse !== "function") {
882
1061
  throw new Error("Conversion did not produce a valid Zod schema.");
883
1062
  }
@@ -891,174 +1070,6 @@ function getZodSchemaFromJsonSchema(jsonSchema, toolName, logger) {
891
1070
  }
892
1071
  }
893
1072
 
894
- // libs/adapters/src/openapi/openapi-spec-poller.ts
895
- var import_utils2 = require("@frontmcp/utils");
896
-
897
- // libs/adapters/src/openapi/openapi.errors.ts
898
- var import_sdk3 = require("@frontmcp/sdk");
899
- var OpenAPIFetchError = class extends import_sdk3.InternalMcpError {
900
- constructor(url, status, statusText) {
901
- super(`OpenAPI spec fetch failed from "${url}": ${status} ${statusText}`, "OPENAPI_FETCH_FAILED");
902
- }
903
- };
904
-
905
- // libs/adapters/src/openapi/openapi-spec-poller.ts
906
- var DEFAULT_RETRY = {
907
- maxRetries: 3,
908
- initialDelayMs: 1e3,
909
- maxDelayMs: 1e4,
910
- backoffMultiplier: 2
911
- };
912
- var OpenApiSpecPoller = class {
913
- url;
914
- intervalMs;
915
- fetchTimeoutMs;
916
- changeDetection;
917
- retry;
918
- unhealthyThreshold;
919
- headers;
920
- callbacks;
921
- intervalTimer = null;
922
- lastHash = null;
923
- lastEtag = null;
924
- lastModified = null;
925
- consecutiveFailures = 0;
926
- isPolling = false;
927
- wasUnhealthy = false;
928
- health = "unknown";
929
- constructor(url, options = {}, callbacks = {}) {
930
- this.url = url;
931
- this.intervalMs = options.intervalMs ?? 6e4;
932
- this.fetchTimeoutMs = options.fetchTimeoutMs ?? 1e4;
933
- this.changeDetection = options.changeDetection ?? "auto";
934
- this.retry = { ...DEFAULT_RETRY, ...options.retry };
935
- this.unhealthyThreshold = options.unhealthyThreshold ?? 3;
936
- this.headers = options.headers ?? {};
937
- this.callbacks = callbacks;
938
- }
939
- /**
940
- * Start polling for changes.
941
- */
942
- start() {
943
- if (this.intervalTimer) return;
944
- this.poll().catch(() => {
945
- });
946
- this.intervalTimer = setInterval(() => {
947
- this.poll().catch(() => {
948
- });
949
- }, this.intervalMs);
950
- }
951
- /**
952
- * Stop polling.
953
- */
954
- stop() {
955
- if (this.intervalTimer) {
956
- clearInterval(this.intervalTimer);
957
- this.intervalTimer = null;
958
- }
959
- }
960
- /**
961
- * Get current stats.
962
- */
963
- getStats() {
964
- return {
965
- hash: this.lastHash,
966
- consecutiveFailures: this.consecutiveFailures,
967
- health: this.health,
968
- isRunning: this.intervalTimer !== null
969
- };
970
- }
971
- /**
972
- * Perform a single poll cycle.
973
- */
974
- async poll() {
975
- if (this.isPolling) return;
976
- this.isPolling = true;
977
- try {
978
- await this.fetchWithRetry();
979
- } finally {
980
- this.isPolling = false;
981
- }
982
- }
983
- async fetchWithRetry() {
984
- const { maxRetries, initialDelayMs, maxDelayMs, backoffMultiplier } = this.retry;
985
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
986
- try {
987
- await this.doFetch();
988
- if (this.consecutiveFailures > 0) {
989
- this.consecutiveFailures = 0;
990
- if (this.wasUnhealthy) {
991
- this.wasUnhealthy = false;
992
- this.health = "healthy";
993
- this.callbacks.onRecovered?.();
994
- }
995
- }
996
- this.health = "healthy";
997
- return;
998
- } catch (error) {
999
- if (attempt === maxRetries) {
1000
- this.consecutiveFailures++;
1001
- this.callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
1002
- if (this.consecutiveFailures >= this.unhealthyThreshold && !this.wasUnhealthy) {
1003
- this.wasUnhealthy = true;
1004
- this.health = "unhealthy";
1005
- this.callbacks.onUnhealthy?.(this.consecutiveFailures);
1006
- }
1007
- return;
1008
- }
1009
- const delay = Math.min(initialDelayMs * Math.pow(backoffMultiplier, attempt), maxDelayMs);
1010
- await new Promise((r) => setTimeout(r, delay));
1011
- }
1012
- }
1013
- }
1014
- async doFetch() {
1015
- const headers = { ...this.headers };
1016
- if (this.changeDetection !== "content-hash") {
1017
- if (this.lastEtag) {
1018
- headers["If-None-Match"] = this.lastEtag;
1019
- }
1020
- if (this.lastModified) {
1021
- headers["If-Modified-Since"] = this.lastModified;
1022
- }
1023
- }
1024
- const controller = new AbortController();
1025
- const timeout = setTimeout(() => controller.abort(), this.fetchTimeoutMs);
1026
- try {
1027
- const response = await fetch(this.url, {
1028
- headers,
1029
- signal: controller.signal
1030
- });
1031
- if (response.status === 304) {
1032
- this.callbacks.onUnchanged?.();
1033
- return;
1034
- }
1035
- if (!response.ok) {
1036
- throw new OpenAPIFetchError(this.url, response.status, response.statusText);
1037
- }
1038
- const etag = response.headers.get("etag");
1039
- const lastModified = response.headers.get("last-modified");
1040
- if (etag) this.lastEtag = etag;
1041
- if (lastModified) this.lastModified = lastModified;
1042
- const body = await response.text();
1043
- const hash = (0, import_utils2.sha256Hex)(body);
1044
- if (this.lastHash && this.lastHash === hash) {
1045
- this.callbacks.onUnchanged?.();
1046
- return;
1047
- }
1048
- this.lastHash = hash;
1049
- this.callbacks.onChanged?.(body, hash);
1050
- } finally {
1051
- clearTimeout(timeout);
1052
- }
1053
- }
1054
- /**
1055
- * Dispose the poller and release resources.
1056
- */
1057
- dispose() {
1058
- this.stop();
1059
- }
1060
- };
1061
-
1062
1073
  // libs/adapters/src/openapi/openapi.adapter.ts
1063
1074
  var RESERVED_KEYS2 = ["__proto__", "constructor", "prototype"];
1064
1075
  function createConsoleLogger(prefix) {
@@ -1902,6 +1913,13 @@ function removeSecurityFromOperations(spec, operations) {
1902
1913
  OpenAPIFetchError,
1903
1914
  OpenApiSpecPoller,
1904
1915
  OpenapiAdapter,
1916
+ applyAdditionalHeaders,
1917
+ buildRequest,
1918
+ createSecurityContextFromAuth,
1919
+ extractSecuritySchemes,
1905
1920
  forceJwtSecurity,
1906
- removeSecurityFromOperations
1921
+ parseResponse,
1922
+ removeSecurityFromOperations,
1923
+ resolveToolSecurity,
1924
+ validateSecurityConfiguration
1907
1925
  });