@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/esm/index.mjs CHANGED
@@ -10,11 +10,356 @@ var __decorateClass = (decorators, target, key, kind) => {
10
10
  };
11
11
 
12
12
  // libs/adapters/src/openapi/openapi.adapter.ts
13
- import { Adapter, DynamicAdapter } from "@frontmcp/sdk";
14
13
  import { OpenAPIToolGenerator } from "mcp-from-openapi";
14
+ import { Adapter, DynamicAdapter } from "@frontmcp/sdk";
15
+
16
+ // libs/adapters/src/openapi/openapi-spec-poller.ts
17
+ import { sha256Hex } from "@frontmcp/utils";
18
+
19
+ // libs/adapters/src/openapi/openapi.errors.ts
20
+ import { InternalMcpError } from "@frontmcp/sdk";
21
+ var OpenAPIFetchError = class extends InternalMcpError {
22
+ constructor(url, status, statusText) {
23
+ super(`OpenAPI spec fetch failed from "${url}": ${status} ${statusText}`, "OPENAPI_FETCH_FAILED");
24
+ }
25
+ };
26
+
27
+ // libs/adapters/src/openapi/openapi-spec-poller.ts
28
+ var DEFAULT_RETRY = {
29
+ maxRetries: 3,
30
+ initialDelayMs: 1e3,
31
+ maxDelayMs: 1e4,
32
+ backoffMultiplier: 2
33
+ };
34
+ var OpenApiSpecPoller = class {
35
+ url;
36
+ intervalMs;
37
+ fetchTimeoutMs;
38
+ changeDetection;
39
+ retry;
40
+ unhealthyThreshold;
41
+ headers;
42
+ callbacks;
43
+ intervalTimer = null;
44
+ lastHash = null;
45
+ lastEtag = null;
46
+ lastModified = null;
47
+ consecutiveFailures = 0;
48
+ isPolling = false;
49
+ wasUnhealthy = false;
50
+ health = "unknown";
51
+ constructor(url, options = {}, callbacks = {}) {
52
+ this.url = url;
53
+ this.intervalMs = options.intervalMs ?? 6e4;
54
+ this.fetchTimeoutMs = options.fetchTimeoutMs ?? 1e4;
55
+ this.changeDetection = options.changeDetection ?? "auto";
56
+ this.retry = { ...DEFAULT_RETRY, ...options.retry };
57
+ this.unhealthyThreshold = options.unhealthyThreshold ?? 3;
58
+ this.headers = options.headers ?? {};
59
+ this.callbacks = callbacks;
60
+ }
61
+ /**
62
+ * Start polling for changes.
63
+ */
64
+ start() {
65
+ if (this.intervalTimer) return;
66
+ this.poll().catch(() => {
67
+ });
68
+ this.intervalTimer = setInterval(() => {
69
+ this.poll().catch(() => {
70
+ });
71
+ }, this.intervalMs);
72
+ }
73
+ /**
74
+ * Stop polling.
75
+ */
76
+ stop() {
77
+ if (this.intervalTimer) {
78
+ clearInterval(this.intervalTimer);
79
+ this.intervalTimer = null;
80
+ }
81
+ }
82
+ /**
83
+ * Get current stats.
84
+ */
85
+ getStats() {
86
+ return {
87
+ hash: this.lastHash,
88
+ consecutiveFailures: this.consecutiveFailures,
89
+ health: this.health,
90
+ isRunning: this.intervalTimer !== null
91
+ };
92
+ }
93
+ /**
94
+ * Perform a single poll cycle.
95
+ */
96
+ async poll() {
97
+ if (this.isPolling) return;
98
+ this.isPolling = true;
99
+ try {
100
+ await this.fetchWithRetry();
101
+ } finally {
102
+ this.isPolling = false;
103
+ }
104
+ }
105
+ async fetchWithRetry() {
106
+ const { maxRetries, initialDelayMs, maxDelayMs, backoffMultiplier } = this.retry;
107
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
108
+ try {
109
+ await this.doFetch();
110
+ if (this.consecutiveFailures > 0) {
111
+ this.consecutiveFailures = 0;
112
+ if (this.wasUnhealthy) {
113
+ this.wasUnhealthy = false;
114
+ this.health = "healthy";
115
+ this.callbacks.onRecovered?.();
116
+ }
117
+ }
118
+ this.health = "healthy";
119
+ return;
120
+ } catch (error) {
121
+ if (attempt === maxRetries) {
122
+ this.consecutiveFailures++;
123
+ this.callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
124
+ if (this.consecutiveFailures >= this.unhealthyThreshold && !this.wasUnhealthy) {
125
+ this.wasUnhealthy = true;
126
+ this.health = "unhealthy";
127
+ this.callbacks.onUnhealthy?.(this.consecutiveFailures);
128
+ }
129
+ return;
130
+ }
131
+ const delay = Math.min(initialDelayMs * Math.pow(backoffMultiplier, attempt), maxDelayMs);
132
+ await new Promise((r) => setTimeout(r, delay));
133
+ }
134
+ }
135
+ }
136
+ async doFetch() {
137
+ const headers = { ...this.headers };
138
+ if (this.changeDetection !== "content-hash") {
139
+ if (this.lastEtag) {
140
+ headers["If-None-Match"] = this.lastEtag;
141
+ }
142
+ if (this.lastModified) {
143
+ headers["If-Modified-Since"] = this.lastModified;
144
+ }
145
+ }
146
+ const controller = new AbortController();
147
+ const timeout = setTimeout(() => controller.abort(), this.fetchTimeoutMs);
148
+ try {
149
+ const response = await fetch(this.url, {
150
+ headers,
151
+ signal: controller.signal
152
+ });
153
+ if (response.status === 304) {
154
+ this.callbacks.onUnchanged?.();
155
+ return;
156
+ }
157
+ if (!response.ok) {
158
+ throw new OpenAPIFetchError(this.url, response.status, response.statusText);
159
+ }
160
+ const etag = response.headers.get("etag");
161
+ const lastModified = response.headers.get("last-modified");
162
+ if (etag) this.lastEtag = etag;
163
+ if (lastModified) this.lastModified = lastModified;
164
+ const body = await response.text();
165
+ const hash = sha256Hex(body);
166
+ if (this.lastHash && this.lastHash === hash) {
167
+ this.callbacks.onUnchanged?.();
168
+ return;
169
+ }
170
+ this.lastHash = hash;
171
+ this.callbacks.onChanged?.(body, hash);
172
+ } finally {
173
+ clearTimeout(timeout);
174
+ }
175
+ }
176
+ /**
177
+ * Dispose the poller and release resources.
178
+ */
179
+ dispose() {
180
+ this.stop();
181
+ }
182
+ };
183
+
184
+ // libs/adapters/src/openapi/openapi.security.ts
185
+ import { SecurityResolver, createSecurityContext } from "mcp-from-openapi";
186
+ async function createSecurityContextFromAuth(tool2, ctx, options) {
187
+ if (options.securityResolver) {
188
+ return await options.securityResolver(tool2, ctx);
189
+ }
190
+ if (options.authProviderMapper) {
191
+ const context = createSecurityContext({});
192
+ const securitySchemes = /* @__PURE__ */ new Set();
193
+ for (const mapper of tool2.mapper) {
194
+ if (mapper.security?.scheme) {
195
+ securitySchemes.add(mapper.security.scheme);
196
+ }
197
+ }
198
+ for (const scheme of securitySchemes) {
199
+ const authExtractor = options.authProviderMapper[scheme];
200
+ if (authExtractor) {
201
+ try {
202
+ const token = authExtractor(ctx);
203
+ if (token !== void 0 && token !== null && typeof token !== "string") {
204
+ throw new Error(
205
+ `authProviderMapper['${scheme}'] must return a string or undefined, but returned: ${typeof token}`
206
+ );
207
+ }
208
+ if (token === "") {
209
+ throw new Error(
210
+ `authProviderMapper['${scheme}'] returned empty string. Return undefined/null if no token is available, or provide a valid token.`
211
+ );
212
+ }
213
+ if (token) {
214
+ const schemeMapper = tool2.mapper.find((m) => m.security?.scheme === scheme);
215
+ const schemeType = schemeMapper?.security?.type?.toLowerCase();
216
+ const httpScheme = schemeMapper?.security?.httpScheme?.toLowerCase();
217
+ if (schemeType === "apikey") {
218
+ if (!context.apiKey) {
219
+ context.apiKey = token;
220
+ }
221
+ } else if (schemeType === "http" && httpScheme === "basic") {
222
+ if (!context.basic) {
223
+ context.basic = token;
224
+ }
225
+ } else if (schemeType === "oauth2") {
226
+ if (!context.oauth2Token) {
227
+ context.oauth2Token = token;
228
+ }
229
+ } else {
230
+ if (!context.jwt) {
231
+ context.jwt = token;
232
+ }
233
+ }
234
+ }
235
+ } catch (err) {
236
+ if (err instanceof Error && err.message.includes("authProviderMapper")) {
237
+ throw err;
238
+ }
239
+ const errorMessage = err instanceof Error ? err.message : String(err);
240
+ throw new Error(`authProviderMapper['${scheme}'] threw an error: ${errorMessage}`);
241
+ }
242
+ }
243
+ }
244
+ const hasAnyAuth = context.jwt || context.apiKey || context.basic || context.oauth2Token;
245
+ const authToken = ctx.authInfo?.token;
246
+ if (!hasAnyAuth && authToken) {
247
+ if (typeof authToken !== "string") {
248
+ throw new Error(`authInfo.token must be a string, but got: ${typeof authToken}`);
249
+ }
250
+ context.jwt = authToken;
251
+ }
252
+ return context;
253
+ }
254
+ if (options.staticAuth) {
255
+ return createSecurityContext(options.staticAuth);
256
+ }
257
+ return createSecurityContext({
258
+ jwt: ctx.authInfo?.token
259
+ });
260
+ }
261
+ function extractSecuritySchemes(tools) {
262
+ const schemes = /* @__PURE__ */ new Set();
263
+ for (const tool2 of tools) {
264
+ for (const mapper of tool2.mapper) {
265
+ if (mapper.security?.scheme) {
266
+ schemes.add(mapper.security.scheme);
267
+ }
268
+ }
269
+ }
270
+ return schemes;
271
+ }
272
+ function validateSecurityConfiguration(tools, options) {
273
+ const result = {
274
+ valid: true,
275
+ missingMappings: [],
276
+ warnings: [],
277
+ securityRiskScore: "low"
278
+ };
279
+ const securitySchemes = extractSecuritySchemes(tools);
280
+ const includeSecurityInInput = options.generateOptions?.includeSecurityInInput ?? false;
281
+ if (includeSecurityInInput) {
282
+ result.securityRiskScore = "high";
283
+ result.warnings.push(
284
+ "SECURITY WARNING: includeSecurityInInput is enabled. Users will provide authentication directly in tool inputs. This increases security risk as credentials may be logged or exposed."
285
+ );
286
+ return result;
287
+ }
288
+ if (options.securityResolver) {
289
+ result.securityRiskScore = "low";
290
+ result.warnings.push(
291
+ "INFO: Using custom securityResolver. Ensure your resolver properly validates and secures credentials from context."
292
+ );
293
+ return result;
294
+ }
295
+ if (options.staticAuth && Object.keys(options.staticAuth).length > 0) {
296
+ result.securityRiskScore = "medium";
297
+ result.warnings.push(
298
+ "SECURITY INFO: Using staticAuth with hardcoded credentials. Ensure credentials are stored securely (environment variables, secrets manager)."
299
+ );
300
+ return result;
301
+ }
302
+ const schemesInInput = new Set(options.securitySchemesInInput || []);
303
+ if (options.authProviderMapper || schemesInInput.size > 0) {
304
+ result.securityRiskScore = schemesInInput.size > 0 ? "medium" : "low";
305
+ if (schemesInInput.size > 0) {
306
+ result.warnings.push(
307
+ `INFO: Per-scheme security control enabled. Schemes in input: ${Array.from(schemesInInput).join(", ")}`
308
+ );
309
+ }
310
+ for (const scheme of securitySchemes) {
311
+ if (schemesInInput.has(scheme)) {
312
+ continue;
313
+ }
314
+ if (!options.authProviderMapper?.[scheme]) {
315
+ result.valid = false;
316
+ result.missingMappings.push(scheme);
317
+ }
318
+ }
319
+ if (!result.valid) {
320
+ result.warnings.push(
321
+ `ERROR: Missing auth provider mappings for security schemes: ${result.missingMappings.join(", ")}`
322
+ );
323
+ }
324
+ return result;
325
+ }
326
+ if (securitySchemes.size > 0) {
327
+ result.securityRiskScore = "medium";
328
+ result.warnings.push(
329
+ `INFO: No auth configuration provided. Using default ctx.authInfo.token for all security schemes: ${Array.from(
330
+ securitySchemes
331
+ ).join(", ")}`
332
+ );
333
+ result.warnings.push(
334
+ "RECOMMENDATION: For multiple auth providers, use authProviderMapper or securityResolver to map each security scheme to the correct auth provider."
335
+ );
336
+ }
337
+ return result;
338
+ }
339
+ async function resolveToolSecurity(tool2, ctx, options) {
340
+ const securityResolver = new SecurityResolver();
341
+ const securityContext = await createSecurityContextFromAuth(tool2, ctx, options);
342
+ 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;
343
+ const requiresSecurity = tool2.mapper.some((m) => m.security && m.required === true);
344
+ if (requiresSecurity && !hasAuth) {
345
+ const requiredSchemes = tool2.mapper.filter((m) => m.security && m.required === true).map((m) => m.security?.scheme ?? "unknown");
346
+ const uniqueSchemes = [...new Set(requiredSchemes)];
347
+ const schemesStr = uniqueSchemes.join(", ") || "unknown";
348
+ const firstScheme = uniqueSchemes[0] || "BearerAuth";
349
+ throw new Error(
350
+ `Authentication required for tool '${tool2.name}' but no auth configuration found.
351
+ Required security schemes: ${schemesStr}
352
+ Solutions:
353
+ 1. Add authProviderMapper: { '${firstScheme}': (ctx) => ctx.authInfo.user?.token }
354
+ 2. Add securityResolver: (tool, ctx) => ({ jwt: ctx.authInfo.token })
355
+ 3. Add staticAuth: { jwt: process.env.API_TOKEN }
356
+ 4. Set generateOptions.includeSecurityInInput: true (not recommended for production)`
357
+ );
358
+ }
359
+ return await securityResolver.resolve(tool2.mapper, securityContext);
360
+ }
15
361
 
16
362
  // libs/adapters/src/openapi/openapi.tool.ts
17
- import { convertJsonSchemaToZod } from "zod-from-json-schema";
18
363
  import { z as z2 } from "@frontmcp/lazy-zod";
19
364
  import { tool } from "@frontmcp/sdk";
20
365
 
@@ -218,227 +563,49 @@ function extractValidCodeCall(data, warnings) {
218
563
  } else if (obj["enabledInCodeCall"] !== void 0) {
219
564
  warnings.push(`Invalid 'codecall.enabledInCodeCall': expected boolean`);
220
565
  }
221
- if (typeof obj["visibleInListTools"] === "boolean") {
222
- result.visibleInListTools = obj["visibleInListTools"];
223
- } else if (obj["visibleInListTools"] !== void 0) {
224
- warnings.push(`Invalid 'codecall.visibleInListTools': expected boolean`);
225
- }
226
- for (const key of Object.keys(obj)) {
227
- if (!KNOWN_CODECALL_FIELDS.has(key)) {
228
- warnings.push(`Unknown field 'codecall.${key}' (will be ignored)`);
229
- }
230
- }
231
- return Object.keys(result).length > 0 ? result : void 0;
232
- }
233
- function extractValidTags(data) {
234
- if (!Array.isArray(data)) return void 0;
235
- const validTags = data.filter((item) => typeof item === "string");
236
- return validTags.length > 0 ? validTags : void 0;
237
- }
238
- function extractValidExamples(data, warnings) {
239
- if (!Array.isArray(data)) return void 0;
240
- const validExamples = [];
241
- for (let i = 0; i < data.length; i++) {
242
- const item = data[i];
243
- const result = FrontMcpExampleSchema.safeParse(item);
244
- if (result.success) {
245
- validExamples.push(result.data);
246
- } else {
247
- warnings.push(
248
- `Invalid example at index ${i}: ${formatZodIssues(result.error.issues, `examples[${i}]`).join(", ")}`
249
- );
250
- }
251
- }
252
- return validExamples.length > 0 ? validExamples : void 0;
253
- }
254
- function formatZodIssues(issues, prefix) {
255
- return issues.map((issue) => {
256
- const path = issue.path.length > 0 ? `${prefix}.${issue.path.join(".")}` : prefix;
257
- return `Invalid '${path}': ${issue.message}`;
258
- });
259
- }
260
-
261
- // libs/adapters/src/openapi/openapi.security.ts
262
- import { SecurityResolver, createSecurityContext } from "mcp-from-openapi";
263
- async function createSecurityContextFromAuth(tool2, ctx, options) {
264
- if (options.securityResolver) {
265
- return await options.securityResolver(tool2, ctx);
266
- }
267
- if (options.authProviderMapper) {
268
- const context = createSecurityContext({});
269
- const securitySchemes = /* @__PURE__ */ new Set();
270
- for (const mapper of tool2.mapper) {
271
- if (mapper.security?.scheme) {
272
- securitySchemes.add(mapper.security.scheme);
273
- }
274
- }
275
- for (const scheme of securitySchemes) {
276
- const authExtractor = options.authProviderMapper[scheme];
277
- if (authExtractor) {
278
- try {
279
- const token = authExtractor(ctx);
280
- if (token !== void 0 && token !== null && typeof token !== "string") {
281
- throw new Error(
282
- `authProviderMapper['${scheme}'] must return a string or undefined, but returned: ${typeof token}`
283
- );
284
- }
285
- if (token === "") {
286
- throw new Error(
287
- `authProviderMapper['${scheme}'] returned empty string. Return undefined/null if no token is available, or provide a valid token.`
288
- );
289
- }
290
- if (token) {
291
- const schemeMapper = tool2.mapper.find((m) => m.security?.scheme === scheme);
292
- const schemeType = schemeMapper?.security?.type?.toLowerCase();
293
- const httpScheme = schemeMapper?.security?.httpScheme?.toLowerCase();
294
- if (schemeType === "apikey") {
295
- if (!context.apiKey) {
296
- context.apiKey = token;
297
- }
298
- } else if (schemeType === "http" && httpScheme === "basic") {
299
- if (!context.basic) {
300
- context.basic = token;
301
- }
302
- } else if (schemeType === "oauth2") {
303
- if (!context.oauth2Token) {
304
- context.oauth2Token = token;
305
- }
306
- } else {
307
- if (!context.jwt) {
308
- context.jwt = token;
309
- }
310
- }
311
- }
312
- } catch (err) {
313
- if (err instanceof Error && err.message.includes("authProviderMapper")) {
314
- throw err;
315
- }
316
- const errorMessage = err instanceof Error ? err.message : String(err);
317
- throw new Error(`authProviderMapper['${scheme}'] threw an error: ${errorMessage}`);
318
- }
319
- }
320
- }
321
- const hasAnyAuth = context.jwt || context.apiKey || context.basic || context.oauth2Token;
322
- const authToken = ctx.authInfo?.token;
323
- if (!hasAnyAuth && authToken) {
324
- if (typeof authToken !== "string") {
325
- throw new Error(`authInfo.token must be a string, but got: ${typeof authToken}`);
326
- }
327
- context.jwt = authToken;
328
- }
329
- return context;
330
- }
331
- if (options.staticAuth) {
332
- return createSecurityContext(options.staticAuth);
333
- }
334
- return createSecurityContext({
335
- jwt: ctx.authInfo?.token
336
- });
337
- }
338
- function extractSecuritySchemes(tools) {
339
- const schemes = /* @__PURE__ */ new Set();
340
- for (const tool2 of tools) {
341
- for (const mapper of tool2.mapper) {
342
- if (mapper.security?.scheme) {
343
- schemes.add(mapper.security.scheme);
344
- }
345
- }
346
- }
347
- return schemes;
348
- }
349
- function validateSecurityConfiguration(tools, options) {
350
- const result = {
351
- valid: true,
352
- missingMappings: [],
353
- warnings: [],
354
- securityRiskScore: "low"
355
- };
356
- const securitySchemes = extractSecuritySchemes(tools);
357
- const includeSecurityInInput = options.generateOptions?.includeSecurityInInput ?? false;
358
- if (includeSecurityInInput) {
359
- result.securityRiskScore = "high";
360
- result.warnings.push(
361
- "SECURITY WARNING: includeSecurityInInput is enabled. Users will provide authentication directly in tool inputs. This increases security risk as credentials may be logged or exposed."
362
- );
363
- return result;
364
- }
365
- if (options.securityResolver) {
366
- result.securityRiskScore = "low";
367
- result.warnings.push(
368
- "INFO: Using custom securityResolver. Ensure your resolver properly validates and secures credentials from context."
369
- );
370
- return result;
371
- }
372
- if (options.staticAuth && Object.keys(options.staticAuth).length > 0) {
373
- result.securityRiskScore = "medium";
374
- result.warnings.push(
375
- "SECURITY INFO: Using staticAuth with hardcoded credentials. Ensure credentials are stored securely (environment variables, secrets manager)."
376
- );
377
- return result;
378
- }
379
- const schemesInInput = new Set(options.securitySchemesInInput || []);
380
- if (options.authProviderMapper || schemesInInput.size > 0) {
381
- result.securityRiskScore = schemesInInput.size > 0 ? "medium" : "low";
382
- if (schemesInInput.size > 0) {
383
- result.warnings.push(
384
- `INFO: Per-scheme security control enabled. Schemes in input: ${Array.from(schemesInInput).join(", ")}`
385
- );
386
- }
387
- for (const scheme of securitySchemes) {
388
- if (schemesInInput.has(scheme)) {
389
- continue;
390
- }
391
- if (!options.authProviderMapper?.[scheme]) {
392
- result.valid = false;
393
- result.missingMappings.push(scheme);
394
- }
566
+ if (typeof obj["visibleInListTools"] === "boolean") {
567
+ result.visibleInListTools = obj["visibleInListTools"];
568
+ } else if (obj["visibleInListTools"] !== void 0) {
569
+ warnings.push(`Invalid 'codecall.visibleInListTools': expected boolean`);
570
+ }
571
+ for (const key of Object.keys(obj)) {
572
+ if (!KNOWN_CODECALL_FIELDS.has(key)) {
573
+ warnings.push(`Unknown field 'codecall.${key}' (will be ignored)`);
395
574
  }
396
- if (!result.valid) {
397
- result.warnings.push(
398
- `ERROR: Missing auth provider mappings for security schemes: ${result.missingMappings.join(", ")}`
575
+ }
576
+ return Object.keys(result).length > 0 ? result : void 0;
577
+ }
578
+ function extractValidTags(data) {
579
+ if (!Array.isArray(data)) return void 0;
580
+ const validTags = data.filter((item) => typeof item === "string");
581
+ return validTags.length > 0 ? validTags : void 0;
582
+ }
583
+ function extractValidExamples(data, warnings) {
584
+ if (!Array.isArray(data)) return void 0;
585
+ const validExamples = [];
586
+ for (let i = 0; i < data.length; i++) {
587
+ const item = data[i];
588
+ const result = FrontMcpExampleSchema.safeParse(item);
589
+ if (result.success) {
590
+ validExamples.push(result.data);
591
+ } else {
592
+ warnings.push(
593
+ `Invalid example at index ${i}: ${formatZodIssues(result.error.issues, `examples[${i}]`).join(", ")}`
399
594
  );
400
595
  }
401
- return result;
402
- }
403
- if (securitySchemes.size > 0) {
404
- result.securityRiskScore = "medium";
405
- result.warnings.push(
406
- `INFO: No auth configuration provided. Using default ctx.authInfo.token for all security schemes: ${Array.from(
407
- securitySchemes
408
- ).join(", ")}`
409
- );
410
- result.warnings.push(
411
- "RECOMMENDATION: For multiple auth providers, use authProviderMapper or securityResolver to map each security scheme to the correct auth provider."
412
- );
413
596
  }
414
- return result;
597
+ return validExamples.length > 0 ? validExamples : void 0;
415
598
  }
416
- async function resolveToolSecurity(tool2, ctx, options) {
417
- const securityResolver = new SecurityResolver();
418
- const securityContext = await createSecurityContextFromAuth(tool2, ctx, options);
419
- 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;
420
- const requiresSecurity = tool2.mapper.some((m) => m.security && m.required === true);
421
- if (requiresSecurity && !hasAuth) {
422
- const requiredSchemes = tool2.mapper.filter((m) => m.security && m.required === true).map((m) => m.security?.scheme ?? "unknown");
423
- const uniqueSchemes = [...new Set(requiredSchemes)];
424
- const schemesStr = uniqueSchemes.join(", ") || "unknown";
425
- const firstScheme = uniqueSchemes[0] || "BearerAuth";
426
- throw new Error(
427
- `Authentication required for tool '${tool2.name}' but no auth configuration found.
428
- Required security schemes: ${schemesStr}
429
- Solutions:
430
- 1. Add authProviderMapper: { '${firstScheme}': (ctx) => ctx.authInfo.user?.token }
431
- 2. Add securityResolver: (tool, ctx) => ({ jwt: ctx.authInfo.token })
432
- 3. Add staticAuth: { jwt: process.env.API_TOKEN }
433
- 4. Set generateOptions.includeSecurityInInput: true (not recommended for production)`
434
- );
435
- }
436
- return await securityResolver.resolve(tool2.mapper, securityContext);
599
+ function formatZodIssues(issues, prefix) {
600
+ return issues.map((issue) => {
601
+ const path = issue.path.length > 0 ? `${prefix}.${issue.path.join(".")}` : prefix;
602
+ return `Invalid '${path}': ${issue.message}`;
603
+ });
437
604
  }
438
605
 
439
606
  // libs/adapters/src/openapi/openapi.utils.ts
440
- import { validateBaseUrl } from "@frontmcp/utils";
441
607
  import { PublicMcpError } from "@frontmcp/sdk";
608
+ import { trimTrailing, validateBaseUrl } from "@frontmcp/utils";
442
609
  function coerceToString(value, paramName, location) {
443
610
  if (value === null || value === void 0) {
444
611
  return "";
@@ -466,7 +633,7 @@ function appendCookie(headers, name, value) {
466
633
  function buildRequest(tool2, input, security, baseUrl) {
467
634
  const rawBaseUrl = tool2.metadata.servers?.[0]?.url || baseUrl;
468
635
  validateBaseUrl(rawBaseUrl);
469
- const apiBaseUrl = rawBaseUrl.replace(/\/+$/, "");
636
+ const apiBaseUrl = trimTrailing(rawBaseUrl, "/");
470
637
  let path = tool2.metadata.path;
471
638
  const queryParams = new URLSearchParams();
472
639
  const headers = new Headers({
@@ -849,7 +1016,12 @@ function getZodSchemaFromJsonSchema(jsonSchema, toolName, logger) {
849
1016
  return { schema: z2.looseObject({}), conversionFailed: true, error: "No valid JSON schema" };
850
1017
  }
851
1018
  try {
852
- const zodSchema = convertJsonSchemaToZod(jsonSchema);
1019
+ const zodSchema = z2.fromJSONSchema(
1020
+ jsonSchema,
1021
+ {
1022
+ defaultTarget: "draft-2020-12"
1023
+ }
1024
+ );
853
1025
  if (typeof zodSchema?.parse !== "function") {
854
1026
  throw new Error("Conversion did not produce a valid Zod schema.");
855
1027
  }
@@ -863,174 +1035,6 @@ function getZodSchemaFromJsonSchema(jsonSchema, toolName, logger) {
863
1035
  }
864
1036
  }
865
1037
 
866
- // libs/adapters/src/openapi/openapi-spec-poller.ts
867
- import { sha256Hex } from "@frontmcp/utils";
868
-
869
- // libs/adapters/src/openapi/openapi.errors.ts
870
- import { InternalMcpError } from "@frontmcp/sdk";
871
- var OpenAPIFetchError = class extends InternalMcpError {
872
- constructor(url, status, statusText) {
873
- super(`OpenAPI spec fetch failed from "${url}": ${status} ${statusText}`, "OPENAPI_FETCH_FAILED");
874
- }
875
- };
876
-
877
- // libs/adapters/src/openapi/openapi-spec-poller.ts
878
- var DEFAULT_RETRY = {
879
- maxRetries: 3,
880
- initialDelayMs: 1e3,
881
- maxDelayMs: 1e4,
882
- backoffMultiplier: 2
883
- };
884
- var OpenApiSpecPoller = class {
885
- url;
886
- intervalMs;
887
- fetchTimeoutMs;
888
- changeDetection;
889
- retry;
890
- unhealthyThreshold;
891
- headers;
892
- callbacks;
893
- intervalTimer = null;
894
- lastHash = null;
895
- lastEtag = null;
896
- lastModified = null;
897
- consecutiveFailures = 0;
898
- isPolling = false;
899
- wasUnhealthy = false;
900
- health = "unknown";
901
- constructor(url, options = {}, callbacks = {}) {
902
- this.url = url;
903
- this.intervalMs = options.intervalMs ?? 6e4;
904
- this.fetchTimeoutMs = options.fetchTimeoutMs ?? 1e4;
905
- this.changeDetection = options.changeDetection ?? "auto";
906
- this.retry = { ...DEFAULT_RETRY, ...options.retry };
907
- this.unhealthyThreshold = options.unhealthyThreshold ?? 3;
908
- this.headers = options.headers ?? {};
909
- this.callbacks = callbacks;
910
- }
911
- /**
912
- * Start polling for changes.
913
- */
914
- start() {
915
- if (this.intervalTimer) return;
916
- this.poll().catch(() => {
917
- });
918
- this.intervalTimer = setInterval(() => {
919
- this.poll().catch(() => {
920
- });
921
- }, this.intervalMs);
922
- }
923
- /**
924
- * Stop polling.
925
- */
926
- stop() {
927
- if (this.intervalTimer) {
928
- clearInterval(this.intervalTimer);
929
- this.intervalTimer = null;
930
- }
931
- }
932
- /**
933
- * Get current stats.
934
- */
935
- getStats() {
936
- return {
937
- hash: this.lastHash,
938
- consecutiveFailures: this.consecutiveFailures,
939
- health: this.health,
940
- isRunning: this.intervalTimer !== null
941
- };
942
- }
943
- /**
944
- * Perform a single poll cycle.
945
- */
946
- async poll() {
947
- if (this.isPolling) return;
948
- this.isPolling = true;
949
- try {
950
- await this.fetchWithRetry();
951
- } finally {
952
- this.isPolling = false;
953
- }
954
- }
955
- async fetchWithRetry() {
956
- const { maxRetries, initialDelayMs, maxDelayMs, backoffMultiplier } = this.retry;
957
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
958
- try {
959
- await this.doFetch();
960
- if (this.consecutiveFailures > 0) {
961
- this.consecutiveFailures = 0;
962
- if (this.wasUnhealthy) {
963
- this.wasUnhealthy = false;
964
- this.health = "healthy";
965
- this.callbacks.onRecovered?.();
966
- }
967
- }
968
- this.health = "healthy";
969
- return;
970
- } catch (error) {
971
- if (attempt === maxRetries) {
972
- this.consecutiveFailures++;
973
- this.callbacks.onError?.(error instanceof Error ? error : new Error(String(error)));
974
- if (this.consecutiveFailures >= this.unhealthyThreshold && !this.wasUnhealthy) {
975
- this.wasUnhealthy = true;
976
- this.health = "unhealthy";
977
- this.callbacks.onUnhealthy?.(this.consecutiveFailures);
978
- }
979
- return;
980
- }
981
- const delay = Math.min(initialDelayMs * Math.pow(backoffMultiplier, attempt), maxDelayMs);
982
- await new Promise((r) => setTimeout(r, delay));
983
- }
984
- }
985
- }
986
- async doFetch() {
987
- const headers = { ...this.headers };
988
- if (this.changeDetection !== "content-hash") {
989
- if (this.lastEtag) {
990
- headers["If-None-Match"] = this.lastEtag;
991
- }
992
- if (this.lastModified) {
993
- headers["If-Modified-Since"] = this.lastModified;
994
- }
995
- }
996
- const controller = new AbortController();
997
- const timeout = setTimeout(() => controller.abort(), this.fetchTimeoutMs);
998
- try {
999
- const response = await fetch(this.url, {
1000
- headers,
1001
- signal: controller.signal
1002
- });
1003
- if (response.status === 304) {
1004
- this.callbacks.onUnchanged?.();
1005
- return;
1006
- }
1007
- if (!response.ok) {
1008
- throw new OpenAPIFetchError(this.url, response.status, response.statusText);
1009
- }
1010
- const etag = response.headers.get("etag");
1011
- const lastModified = response.headers.get("last-modified");
1012
- if (etag) this.lastEtag = etag;
1013
- if (lastModified) this.lastModified = lastModified;
1014
- const body = await response.text();
1015
- const hash = sha256Hex(body);
1016
- if (this.lastHash && this.lastHash === hash) {
1017
- this.callbacks.onUnchanged?.();
1018
- return;
1019
- }
1020
- this.lastHash = hash;
1021
- this.callbacks.onChanged?.(body, hash);
1022
- } finally {
1023
- clearTimeout(timeout);
1024
- }
1025
- }
1026
- /**
1027
- * Dispose the poller and release resources.
1028
- */
1029
- dispose() {
1030
- this.stop();
1031
- }
1032
- };
1033
-
1034
1038
  // libs/adapters/src/openapi/openapi.adapter.ts
1035
1039
  var RESERVED_KEYS2 = ["__proto__", "constructor", "prototype"];
1036
1040
  function createConsoleLogger(prefix) {
@@ -1873,6 +1877,13 @@ export {
1873
1877
  OpenAPIFetchError,
1874
1878
  OpenApiSpecPoller,
1875
1879
  OpenapiAdapter,
1880
+ applyAdditionalHeaders,
1881
+ buildRequest,
1882
+ createSecurityContextFromAuth,
1883
+ extractSecuritySchemes,
1876
1884
  forceJwtSecurity,
1877
- removeSecurityFromOperations
1885
+ parseResponse,
1886
+ removeSecurityFromOperations,
1887
+ resolveToolSecurity,
1888
+ validateSecurityConfiguration
1878
1889
  };