@frontmcp/adapters 0.5.1 → 0.6.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.
@@ -0,0 +1,358 @@
1
+ "use strict";
2
+ /**
3
+ * Zod schemas for x-frontmcp OpenAPI extension validation.
4
+ *
5
+ * The x-frontmcp extension allows embedding FrontMCP-specific configuration
6
+ * directly in OpenAPI specs. This module provides versioned schema validation
7
+ * to ensure only valid data is used and invalid fields are warned about.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.FrontMcpExampleSchema = exports.FrontMcpCodeCallSchema = exports.FrontMcpCacheSchema = exports.FrontMcpAnnotationsSchema = exports.SUPPORTED_VERSIONS = exports.FRONTMCP_SCHEMA_VERSION = void 0;
11
+ exports.validateFrontMcpExtension = validateFrontMcpExtension;
12
+ const zod_1 = require("zod");
13
+ /**
14
+ * Current schema version for x-frontmcp extension.
15
+ * Increment when making breaking changes to the schema.
16
+ */
17
+ exports.FRONTMCP_SCHEMA_VERSION = '1.0';
18
+ /**
19
+ * Supported schema versions.
20
+ */
21
+ exports.SUPPORTED_VERSIONS = ['1.0'];
22
+ // ============================================================================
23
+ // Annotations Schema (v1.0)
24
+ // ============================================================================
25
+ /**
26
+ * Tool annotations schema - hints about tool behavior for AI clients.
27
+ */
28
+ exports.FrontMcpAnnotationsSchema = zod_1.z.object({
29
+ /** Human-readable title for the tool */
30
+ title: zod_1.z.string().optional(),
31
+ /** If true, the tool does not modify its environment */
32
+ readOnlyHint: zod_1.z.boolean().optional(),
33
+ /** If true, the tool may perform destructive updates */
34
+ destructiveHint: zod_1.z.boolean().optional(),
35
+ /** If true, calling repeatedly with same args has no additional effect */
36
+ idempotentHint: zod_1.z.boolean().optional(),
37
+ /** If true, tool may interact with external entities */
38
+ openWorldHint: zod_1.z.boolean().optional(),
39
+ });
40
+ // ============================================================================
41
+ // Cache Schema (v1.0)
42
+ // ============================================================================
43
+ /**
44
+ * Cache configuration schema.
45
+ */
46
+ exports.FrontMcpCacheSchema = zod_1.z.object({
47
+ /** Time-to-live in seconds for cached responses */
48
+ ttl: zod_1.z.number().int().positive().optional(),
49
+ /** If true, cache window slides on each access */
50
+ slideWindow: zod_1.z.boolean().optional(),
51
+ });
52
+ // ============================================================================
53
+ // CodeCall Schema (v1.0)
54
+ // ============================================================================
55
+ /**
56
+ * CodeCall plugin configuration schema.
57
+ */
58
+ exports.FrontMcpCodeCallSchema = zod_1.z.object({
59
+ /** Whether this tool can be used via CodeCall */
60
+ enabledInCodeCall: zod_1.z.boolean().optional(),
61
+ /** If true, stays visible in list_tools when CodeCall is active */
62
+ visibleInListTools: zod_1.z.boolean().optional(),
63
+ });
64
+ // ============================================================================
65
+ // Example Schema (v1.0)
66
+ // ============================================================================
67
+ /**
68
+ * Tool usage example schema.
69
+ */
70
+ exports.FrontMcpExampleSchema = zod_1.z.object({
71
+ /** Description of the example */
72
+ description: zod_1.z.string(),
73
+ /** Example input values */
74
+ input: zod_1.z.record(zod_1.z.string(), zod_1.z.any()),
75
+ /** Expected output (optional) */
76
+ output: zod_1.z.any().optional(),
77
+ });
78
+ // ============================================================================
79
+ // Known Fields for Validation
80
+ // ============================================================================
81
+ /** Known field names for validation */
82
+ const KNOWN_EXTENSION_FIELDS = new Set([
83
+ 'version',
84
+ 'annotations',
85
+ 'cache',
86
+ 'codecall',
87
+ 'tags',
88
+ 'hideFromDiscovery',
89
+ 'examples',
90
+ ]);
91
+ const KNOWN_ANNOTATION_FIELDS = new Set([
92
+ 'title',
93
+ 'readOnlyHint',
94
+ 'destructiveHint',
95
+ 'idempotentHint',
96
+ 'openWorldHint',
97
+ ]);
98
+ const KNOWN_CACHE_FIELDS = new Set(['ttl', 'slideWindow']);
99
+ const KNOWN_CODECALL_FIELDS = new Set(['enabledInCodeCall', 'visibleInListTools']);
100
+ // ============================================================================
101
+ // Schema Validation Functions
102
+ // ============================================================================
103
+ /**
104
+ * Validate and parse x-frontmcp extension data.
105
+ *
106
+ * This function:
107
+ * 1. Detects the schema version (defaults to current)
108
+ * 2. Validates each field against the appropriate schema
109
+ * 3. Returns only valid fields, ignoring invalid ones
110
+ * 4. Collects warnings for invalid/unknown fields
111
+ *
112
+ * @param rawData - Raw x-frontmcp data from OpenAPI spec
113
+ * @param toolName - Tool name for error context
114
+ * @param logger - Logger for warnings
115
+ * @returns Validation result with valid data and warnings
116
+ */
117
+ function validateFrontMcpExtension(rawData, toolName, logger) {
118
+ const warnings = [];
119
+ // Handle null/undefined
120
+ if (rawData === null || rawData === undefined) {
121
+ return { success: true, data: null, warnings: [] };
122
+ }
123
+ // Must be an object
124
+ if (typeof rawData !== 'object' || Array.isArray(rawData)) {
125
+ warnings.push(`x-frontmcp must be an object, got ${Array.isArray(rawData) ? 'array' : typeof rawData}`);
126
+ logger.warn(`[${toolName}] Invalid x-frontmcp extension: ${warnings[0]}`);
127
+ return { success: false, data: null, warnings };
128
+ }
129
+ const data = rawData;
130
+ // Detect version
131
+ const version = typeof data['version'] === 'string' ? data['version'] : exports.FRONTMCP_SCHEMA_VERSION;
132
+ // Check if version is supported
133
+ if (!exports.SUPPORTED_VERSIONS.includes(version)) {
134
+ warnings.push(`Unsupported x-frontmcp version '${version}'. Supported versions: ${exports.SUPPORTED_VERSIONS.join(', ')}`);
135
+ logger.warn(`[${toolName}] ${warnings[0]}`);
136
+ return { success: false, data: null, warnings };
137
+ }
138
+ // Extract valid fields and collect warnings
139
+ const validData = extractValidFields(data, version, warnings);
140
+ if (warnings.length > 0) {
141
+ logger.warn(`[${toolName}] x-frontmcp extension has invalid fields that were ignored:`);
142
+ warnings.forEach((w) => logger.warn(` - ${w}`));
143
+ }
144
+ return {
145
+ success: true,
146
+ data: validData,
147
+ warnings,
148
+ };
149
+ }
150
+ /**
151
+ * Extract valid fields from x-frontmcp data, collecting warnings for invalid fields.
152
+ */
153
+ function extractValidFields(data, version, warnings) {
154
+ const result = {
155
+ version: version,
156
+ };
157
+ // Warn about unknown top-level fields
158
+ for (const key of Object.keys(data)) {
159
+ if (!KNOWN_EXTENSION_FIELDS.has(key)) {
160
+ warnings.push(`Unknown field '${key}' in x-frontmcp (will be ignored)`);
161
+ }
162
+ }
163
+ // Validate annotations
164
+ if (data['annotations'] !== undefined) {
165
+ const annotationsResult = exports.FrontMcpAnnotationsSchema.safeParse(data['annotations']);
166
+ if (annotationsResult.success) {
167
+ result.annotations = annotationsResult.data;
168
+ }
169
+ else {
170
+ const issues = formatZodIssues(annotationsResult.error.issues, 'annotations');
171
+ warnings.push(...issues);
172
+ // Try to extract valid annotation fields
173
+ result.annotations = extractValidAnnotations(data['annotations'], warnings);
174
+ }
175
+ }
176
+ // Validate cache
177
+ if (data['cache'] !== undefined) {
178
+ const cacheResult = exports.FrontMcpCacheSchema.safeParse(data['cache']);
179
+ if (cacheResult.success) {
180
+ result.cache = cacheResult.data;
181
+ }
182
+ else {
183
+ const issues = formatZodIssues(cacheResult.error.issues, 'cache');
184
+ warnings.push(...issues);
185
+ // Try to extract valid cache fields
186
+ result.cache = extractValidCache(data['cache'], warnings);
187
+ }
188
+ }
189
+ // Validate codecall
190
+ if (data['codecall'] !== undefined) {
191
+ const codecallResult = exports.FrontMcpCodeCallSchema.safeParse(data['codecall']);
192
+ if (codecallResult.success) {
193
+ result.codecall = codecallResult.data;
194
+ }
195
+ else {
196
+ const issues = formatZodIssues(codecallResult.error.issues, 'codecall');
197
+ warnings.push(...issues);
198
+ // Try to extract valid codecall fields
199
+ result.codecall = extractValidCodeCall(data['codecall'], warnings);
200
+ }
201
+ }
202
+ // Validate tags
203
+ if (data['tags'] !== undefined) {
204
+ const tagsSchema = zod_1.z.array(zod_1.z.string());
205
+ const tagsResult = tagsSchema.safeParse(data['tags']);
206
+ if (tagsResult.success) {
207
+ result.tags = tagsResult.data;
208
+ }
209
+ else {
210
+ warnings.push(`Invalid 'tags': expected array of strings`);
211
+ // Try to extract valid tags
212
+ result.tags = extractValidTags(data['tags']);
213
+ }
214
+ }
215
+ // Validate hideFromDiscovery
216
+ if (data['hideFromDiscovery'] !== undefined) {
217
+ if (typeof data['hideFromDiscovery'] === 'boolean') {
218
+ result.hideFromDiscovery = data['hideFromDiscovery'];
219
+ }
220
+ else {
221
+ warnings.push(`Invalid 'hideFromDiscovery': expected boolean, got ${typeof data['hideFromDiscovery']}`);
222
+ }
223
+ }
224
+ // Validate examples
225
+ if (data['examples'] !== undefined) {
226
+ const examplesSchema = zod_1.z.array(exports.FrontMcpExampleSchema);
227
+ const examplesResult = examplesSchema.safeParse(data['examples']);
228
+ if (examplesResult.success) {
229
+ result.examples = examplesResult.data;
230
+ }
231
+ else {
232
+ warnings.push(`Invalid 'examples': some examples have invalid format`);
233
+ // Try to extract valid examples
234
+ result.examples = extractValidExamples(data['examples'], warnings);
235
+ }
236
+ }
237
+ return result;
238
+ }
239
+ /**
240
+ * Extract valid annotation fields.
241
+ */
242
+ function extractValidAnnotations(data, warnings) {
243
+ if (typeof data !== 'object' || data === null)
244
+ return undefined;
245
+ const obj = data;
246
+ const result = {};
247
+ for (const field of KNOWN_ANNOTATION_FIELDS) {
248
+ if (obj[field] !== undefined) {
249
+ if (field === 'title' && typeof obj[field] === 'string') {
250
+ result.title = obj[field];
251
+ }
252
+ else if (field !== 'title' && typeof obj[field] === 'boolean') {
253
+ result[field] = obj[field];
254
+ }
255
+ }
256
+ }
257
+ // Warn about unknown annotation fields
258
+ for (const key of Object.keys(obj)) {
259
+ if (!KNOWN_ANNOTATION_FIELDS.has(key)) {
260
+ warnings.push(`Unknown field 'annotations.${key}' (will be ignored)`);
261
+ }
262
+ }
263
+ return Object.keys(result).length > 0 ? result : undefined;
264
+ }
265
+ /**
266
+ * Extract valid cache fields.
267
+ */
268
+ function extractValidCache(data, warnings) {
269
+ if (typeof data !== 'object' || data === null)
270
+ return undefined;
271
+ const obj = data;
272
+ const result = {};
273
+ if (typeof obj['ttl'] === 'number' && Number.isInteger(obj['ttl']) && obj['ttl'] > 0) {
274
+ result.ttl = obj['ttl'];
275
+ }
276
+ else if (obj['ttl'] !== undefined) {
277
+ warnings.push(`Invalid 'cache.ttl': expected positive integer`);
278
+ }
279
+ if (typeof obj['slideWindow'] === 'boolean') {
280
+ result.slideWindow = obj['slideWindow'];
281
+ }
282
+ else if (obj['slideWindow'] !== undefined) {
283
+ warnings.push(`Invalid 'cache.slideWindow': expected boolean`);
284
+ }
285
+ // Warn about unknown cache fields
286
+ for (const key of Object.keys(obj)) {
287
+ if (!KNOWN_CACHE_FIELDS.has(key)) {
288
+ warnings.push(`Unknown field 'cache.${key}' (will be ignored)`);
289
+ }
290
+ }
291
+ return Object.keys(result).length > 0 ? result : undefined;
292
+ }
293
+ /**
294
+ * Extract valid codecall fields.
295
+ */
296
+ function extractValidCodeCall(data, warnings) {
297
+ if (typeof data !== 'object' || data === null)
298
+ return undefined;
299
+ const obj = data;
300
+ const result = {};
301
+ if (typeof obj['enabledInCodeCall'] === 'boolean') {
302
+ result.enabledInCodeCall = obj['enabledInCodeCall'];
303
+ }
304
+ else if (obj['enabledInCodeCall'] !== undefined) {
305
+ warnings.push(`Invalid 'codecall.enabledInCodeCall': expected boolean`);
306
+ }
307
+ if (typeof obj['visibleInListTools'] === 'boolean') {
308
+ result.visibleInListTools = obj['visibleInListTools'];
309
+ }
310
+ else if (obj['visibleInListTools'] !== undefined) {
311
+ warnings.push(`Invalid 'codecall.visibleInListTools': expected boolean`);
312
+ }
313
+ // Warn about unknown codecall fields
314
+ for (const key of Object.keys(obj)) {
315
+ if (!KNOWN_CODECALL_FIELDS.has(key)) {
316
+ warnings.push(`Unknown field 'codecall.${key}' (will be ignored)`);
317
+ }
318
+ }
319
+ return Object.keys(result).length > 0 ? result : undefined;
320
+ }
321
+ /**
322
+ * Extract valid tags from array.
323
+ */
324
+ function extractValidTags(data) {
325
+ if (!Array.isArray(data))
326
+ return undefined;
327
+ const validTags = data.filter((item) => typeof item === 'string');
328
+ return validTags.length > 0 ? validTags : undefined;
329
+ }
330
+ /**
331
+ * Extract valid examples from array.
332
+ */
333
+ function extractValidExamples(data, warnings) {
334
+ if (!Array.isArray(data))
335
+ return undefined;
336
+ const validExamples = [];
337
+ for (let i = 0; i < data.length; i++) {
338
+ const item = data[i];
339
+ const result = exports.FrontMcpExampleSchema.safeParse(item);
340
+ if (result.success) {
341
+ validExamples.push(result.data);
342
+ }
343
+ else {
344
+ warnings.push(`Invalid example at index ${i}: ${formatZodIssues(result.error.issues, `examples[${i}]`).join(', ')}`);
345
+ }
346
+ }
347
+ return validExamples.length > 0 ? validExamples : undefined;
348
+ }
349
+ /**
350
+ * Format Zod validation issues into readable messages.
351
+ */
352
+ function formatZodIssues(issues, prefix) {
353
+ return issues.map((issue) => {
354
+ const path = issue.path.length > 0 ? `${prefix}.${issue.path.join('.')}` : prefix;
355
+ return `Invalid '${path}': ${issue.message}`;
356
+ });
357
+ }
358
+ //# sourceMappingURL=openapi.frontmcp-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openapi.frontmcp-schema.js","sourceRoot":"","sources":["../../../src/openapi/openapi.frontmcp-schema.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Zod schemas for x-frontmcp OpenAPI extension validation.\n *\n * The x-frontmcp extension allows embedding FrontMCP-specific configuration\n * directly in OpenAPI specs. This module provides versioned schema validation\n * to ensure only valid data is used and invalid fields are warned about.\n */\n\nimport { z } from 'zod';\nimport type { FrontMcpLogger } from '@frontmcp/sdk';\n\n/**\n * Current schema version for x-frontmcp extension.\n * Increment when making breaking changes to the schema.\n */\nexport const FRONTMCP_SCHEMA_VERSION = '1.0' as const;\n\n/**\n * Supported schema versions.\n */\nexport const SUPPORTED_VERSIONS = ['1.0'] as const;\nexport type FrontMcpSchemaVersion = (typeof SUPPORTED_VERSIONS)[number];\n\n// ============================================================================\n// Annotations Schema (v1.0)\n// ============================================================================\n\n/**\n * Tool annotations schema - hints about tool behavior for AI clients.\n */\nexport const FrontMcpAnnotationsSchema = z.object({\n /** Human-readable title for the tool */\n title: z.string().optional(),\n /** If true, the tool does not modify its environment */\n readOnlyHint: z.boolean().optional(),\n /** If true, the tool may perform destructive updates */\n destructiveHint: z.boolean().optional(),\n /** If true, calling repeatedly with same args has no additional effect */\n idempotentHint: z.boolean().optional(),\n /** If true, tool may interact with external entities */\n openWorldHint: z.boolean().optional(),\n});\n\nexport type FrontMcpAnnotations = z.infer<typeof FrontMcpAnnotationsSchema>;\n\n// ============================================================================\n// Cache Schema (v1.0)\n// ============================================================================\n\n/**\n * Cache configuration schema.\n */\nexport const FrontMcpCacheSchema = z.object({\n /** Time-to-live in seconds for cached responses */\n ttl: z.number().int().positive().optional(),\n /** If true, cache window slides on each access */\n slideWindow: z.boolean().optional(),\n});\n\nexport type FrontMcpCache = z.infer<typeof FrontMcpCacheSchema>;\n\n// ============================================================================\n// CodeCall Schema (v1.0)\n// ============================================================================\n\n/**\n * CodeCall plugin configuration schema.\n */\nexport const FrontMcpCodeCallSchema = z.object({\n /** Whether this tool can be used via CodeCall */\n enabledInCodeCall: z.boolean().optional(),\n /** If true, stays visible in list_tools when CodeCall is active */\n visibleInListTools: z.boolean().optional(),\n});\n\nexport type FrontMcpCodeCall = z.infer<typeof FrontMcpCodeCallSchema>;\n\n// ============================================================================\n// Example Schema (v1.0)\n// ============================================================================\n\n/**\n * Tool usage example schema.\n */\nexport const FrontMcpExampleSchema = z.object({\n /** Description of the example */\n description: z.string(),\n /** Example input values */\n input: z.record(z.string(), z.any()),\n /** Expected output (optional) */\n output: z.any().optional(),\n});\n\nexport type FrontMcpExample = z.infer<typeof FrontMcpExampleSchema>;\n\n// ============================================================================\n// Known Fields for Validation\n// ============================================================================\n\n/** Known field names for validation */\nconst KNOWN_EXTENSION_FIELDS = new Set([\n 'version',\n 'annotations',\n 'cache',\n 'codecall',\n 'tags',\n 'hideFromDiscovery',\n 'examples',\n]);\n\nconst KNOWN_ANNOTATION_FIELDS = new Set([\n 'title',\n 'readOnlyHint',\n 'destructiveHint',\n 'idempotentHint',\n 'openWorldHint',\n]);\n\nconst KNOWN_CACHE_FIELDS = new Set(['ttl', 'slideWindow']);\nconst KNOWN_CODECALL_FIELDS = new Set(['enabledInCodeCall', 'visibleInListTools']);\n\n// ============================================================================\n// Validated Extension Type (after parsing)\n// ============================================================================\n\n/**\n * Validated x-frontmcp extension data.\n * This is the output after schema validation.\n */\nexport interface ValidatedFrontMcpExtension {\n version: FrontMcpSchemaVersion;\n annotations?: FrontMcpAnnotations;\n cache?: FrontMcpCache;\n codecall?: FrontMcpCodeCall;\n tags?: string[];\n hideFromDiscovery?: boolean;\n examples?: FrontMcpExample[];\n}\n\n// ============================================================================\n// Validation Result\n// ============================================================================\n\nexport interface FrontMcpValidationResult {\n /** Whether validation succeeded (may still have warnings) */\n success: boolean;\n /** Validated data (only valid fields) */\n data: ValidatedFrontMcpExtension | null;\n /** Validation warnings (invalid fields that were ignored) */\n warnings: string[];\n}\n\n// ============================================================================\n// Schema Validation Functions\n// ============================================================================\n\n/**\n * Validate and parse x-frontmcp extension data.\n *\n * This function:\n * 1. Detects the schema version (defaults to current)\n * 2. Validates each field against the appropriate schema\n * 3. Returns only valid fields, ignoring invalid ones\n * 4. Collects warnings for invalid/unknown fields\n *\n * @param rawData - Raw x-frontmcp data from OpenAPI spec\n * @param toolName - Tool name for error context\n * @param logger - Logger for warnings\n * @returns Validation result with valid data and warnings\n */\nexport function validateFrontMcpExtension(\n rawData: unknown,\n toolName: string,\n logger: FrontMcpLogger,\n): FrontMcpValidationResult {\n const warnings: string[] = [];\n\n // Handle null/undefined\n if (rawData === null || rawData === undefined) {\n return { success: true, data: null, warnings: [] };\n }\n\n // Must be an object\n if (typeof rawData !== 'object' || Array.isArray(rawData)) {\n warnings.push(`x-frontmcp must be an object, got ${Array.isArray(rawData) ? 'array' : typeof rawData}`);\n logger.warn(`[${toolName}] Invalid x-frontmcp extension: ${warnings[0]}`);\n return { success: false, data: null, warnings };\n }\n\n const data = rawData as Record<string, unknown>;\n\n // Detect version\n const version = typeof data['version'] === 'string' ? data['version'] : FRONTMCP_SCHEMA_VERSION;\n\n // Check if version is supported\n if (!SUPPORTED_VERSIONS.includes(version as FrontMcpSchemaVersion)) {\n warnings.push(`Unsupported x-frontmcp version '${version}'. Supported versions: ${SUPPORTED_VERSIONS.join(', ')}`);\n logger.warn(`[${toolName}] ${warnings[0]}`);\n return { success: false, data: null, warnings };\n }\n\n // Extract valid fields and collect warnings\n const validData = extractValidFields(data, version, warnings);\n\n if (warnings.length > 0) {\n logger.warn(`[${toolName}] x-frontmcp extension has invalid fields that were ignored:`);\n warnings.forEach((w) => logger.warn(` - ${w}`));\n }\n\n return {\n success: true,\n data: validData,\n warnings,\n };\n}\n\n/**\n * Extract valid fields from x-frontmcp data, collecting warnings for invalid fields.\n */\nfunction extractValidFields(\n data: Record<string, unknown>,\n version: string,\n warnings: string[],\n): ValidatedFrontMcpExtension {\n const result: ValidatedFrontMcpExtension = {\n version: version as FrontMcpSchemaVersion,\n };\n\n // Warn about unknown top-level fields\n for (const key of Object.keys(data)) {\n if (!KNOWN_EXTENSION_FIELDS.has(key)) {\n warnings.push(`Unknown field '${key}' in x-frontmcp (will be ignored)`);\n }\n }\n\n // Validate annotations\n if (data['annotations'] !== undefined) {\n const annotationsResult = FrontMcpAnnotationsSchema.safeParse(data['annotations']);\n if (annotationsResult.success) {\n result.annotations = annotationsResult.data;\n } else {\n const issues = formatZodIssues(annotationsResult.error.issues, 'annotations');\n warnings.push(...issues);\n // Try to extract valid annotation fields\n result.annotations = extractValidAnnotations(data['annotations'], warnings);\n }\n }\n\n // Validate cache\n if (data['cache'] !== undefined) {\n const cacheResult = FrontMcpCacheSchema.safeParse(data['cache']);\n if (cacheResult.success) {\n result.cache = cacheResult.data;\n } else {\n const issues = formatZodIssues(cacheResult.error.issues, 'cache');\n warnings.push(...issues);\n // Try to extract valid cache fields\n result.cache = extractValidCache(data['cache'], warnings);\n }\n }\n\n // Validate codecall\n if (data['codecall'] !== undefined) {\n const codecallResult = FrontMcpCodeCallSchema.safeParse(data['codecall']);\n if (codecallResult.success) {\n result.codecall = codecallResult.data;\n } else {\n const issues = formatZodIssues(codecallResult.error.issues, 'codecall');\n warnings.push(...issues);\n // Try to extract valid codecall fields\n result.codecall = extractValidCodeCall(data['codecall'], warnings);\n }\n }\n\n // Validate tags\n if (data['tags'] !== undefined) {\n const tagsSchema = z.array(z.string());\n const tagsResult = tagsSchema.safeParse(data['tags']);\n if (tagsResult.success) {\n result.tags = tagsResult.data;\n } else {\n warnings.push(`Invalid 'tags': expected array of strings`);\n // Try to extract valid tags\n result.tags = extractValidTags(data['tags']);\n }\n }\n\n // Validate hideFromDiscovery\n if (data['hideFromDiscovery'] !== undefined) {\n if (typeof data['hideFromDiscovery'] === 'boolean') {\n result.hideFromDiscovery = data['hideFromDiscovery'];\n } else {\n warnings.push(`Invalid 'hideFromDiscovery': expected boolean, got ${typeof data['hideFromDiscovery']}`);\n }\n }\n\n // Validate examples\n if (data['examples'] !== undefined) {\n const examplesSchema = z.array(FrontMcpExampleSchema);\n const examplesResult = examplesSchema.safeParse(data['examples']);\n if (examplesResult.success) {\n result.examples = examplesResult.data;\n } else {\n warnings.push(`Invalid 'examples': some examples have invalid format`);\n // Try to extract valid examples\n result.examples = extractValidExamples(data['examples'], warnings);\n }\n }\n\n return result;\n}\n\n/**\n * Extract valid annotation fields.\n */\nfunction extractValidAnnotations(data: unknown, warnings: string[]): FrontMcpAnnotations | undefined {\n if (typeof data !== 'object' || data === null) return undefined;\n\n const obj = data as Record<string, unknown>;\n const result: FrontMcpAnnotations = {};\n\n for (const field of KNOWN_ANNOTATION_FIELDS) {\n if (obj[field] !== undefined) {\n if (field === 'title' && typeof obj[field] === 'string') {\n result.title = obj[field] as string;\n } else if (field !== 'title' && typeof obj[field] === 'boolean') {\n (result as Record<string, unknown>)[field] = obj[field];\n }\n }\n }\n\n // Warn about unknown annotation fields\n for (const key of Object.keys(obj)) {\n if (!KNOWN_ANNOTATION_FIELDS.has(key)) {\n warnings.push(`Unknown field 'annotations.${key}' (will be ignored)`);\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n}\n\n/**\n * Extract valid cache fields.\n */\nfunction extractValidCache(data: unknown, warnings: string[]): FrontMcpCache | undefined {\n if (typeof data !== 'object' || data === null) return undefined;\n\n const obj = data as Record<string, unknown>;\n const result: FrontMcpCache = {};\n\n if (typeof obj['ttl'] === 'number' && Number.isInteger(obj['ttl']) && obj['ttl'] > 0) {\n result.ttl = obj['ttl'];\n } else if (obj['ttl'] !== undefined) {\n warnings.push(`Invalid 'cache.ttl': expected positive integer`);\n }\n\n if (typeof obj['slideWindow'] === 'boolean') {\n result.slideWindow = obj['slideWindow'];\n } else if (obj['slideWindow'] !== undefined) {\n warnings.push(`Invalid 'cache.slideWindow': expected boolean`);\n }\n\n // Warn about unknown cache fields\n for (const key of Object.keys(obj)) {\n if (!KNOWN_CACHE_FIELDS.has(key)) {\n warnings.push(`Unknown field 'cache.${key}' (will be ignored)`);\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n}\n\n/**\n * Extract valid codecall fields.\n */\nfunction extractValidCodeCall(data: unknown, warnings: string[]): FrontMcpCodeCall | undefined {\n if (typeof data !== 'object' || data === null) return undefined;\n\n const obj = data as Record<string, unknown>;\n const result: FrontMcpCodeCall = {};\n\n if (typeof obj['enabledInCodeCall'] === 'boolean') {\n result.enabledInCodeCall = obj['enabledInCodeCall'];\n } else if (obj['enabledInCodeCall'] !== undefined) {\n warnings.push(`Invalid 'codecall.enabledInCodeCall': expected boolean`);\n }\n\n if (typeof obj['visibleInListTools'] === 'boolean') {\n result.visibleInListTools = obj['visibleInListTools'];\n } else if (obj['visibleInListTools'] !== undefined) {\n warnings.push(`Invalid 'codecall.visibleInListTools': expected boolean`);\n }\n\n // Warn about unknown codecall fields\n for (const key of Object.keys(obj)) {\n if (!KNOWN_CODECALL_FIELDS.has(key)) {\n warnings.push(`Unknown field 'codecall.${key}' (will be ignored)`);\n }\n }\n\n return Object.keys(result).length > 0 ? result : undefined;\n}\n\n/**\n * Extract valid tags from array.\n */\nfunction extractValidTags(data: unknown): string[] | undefined {\n if (!Array.isArray(data)) return undefined;\n\n const validTags = data.filter((item): item is string => typeof item === 'string');\n return validTags.length > 0 ? validTags : undefined;\n}\n\n/**\n * Extract valid examples from array.\n */\nfunction extractValidExamples(data: unknown, warnings: string[]): FrontMcpExample[] | undefined {\n if (!Array.isArray(data)) return undefined;\n\n const validExamples: FrontMcpExample[] = [];\n\n for (let i = 0; i < data.length; i++) {\n const item = data[i];\n const result = FrontMcpExampleSchema.safeParse(item);\n if (result.success) {\n validExamples.push(result.data);\n } else {\n warnings.push(\n `Invalid example at index ${i}: ${formatZodIssues(result.error.issues, `examples[${i}]`).join(', ')}`,\n );\n }\n }\n\n return validExamples.length > 0 ? validExamples : undefined;\n}\n\n/** Zod issue shape for formatting */\ninterface ZodIssue {\n path: PropertyKey[];\n message: string;\n}\n\n/**\n * Format Zod validation issues into readable messages.\n */\nfunction formatZodIssues(issues: ZodIssue[], prefix: string): string[] {\n return issues.map((issue) => {\n const path = issue.path.length > 0 ? `${prefix}.${issue.path.join('.')}` : prefix;\n return `Invalid '${path}': ${issue.message}`;\n });\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { type McpOpenAPITool, type SecurityContext } from 'mcp-from-openapi';
2
- import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
2
+ import type { FrontMcpContext } from '@frontmcp/sdk';
3
3
  import type { OpenApiAdapterOptions } from './openapi.types';
4
4
  /**
5
5
  * Security scheme information extracted from OpenAPI spec
@@ -21,14 +21,14 @@ export interface SecurityValidationResult {
21
21
  securityRiskScore: 'low' | 'medium' | 'high';
22
22
  }
23
23
  /**
24
- * Resolve security context from FrontMCP auth info with support for multiple auth providers
24
+ * Resolve security context from FrontMCP context with support for multiple auth providers
25
25
  *
26
26
  * @param tool - OpenAPI tool to resolve security for
27
- * @param authInfo - FrontMCP authentication info
27
+ * @param ctx - FrontMCP request context with authInfo, sessionId, traceId, etc.
28
28
  * @param options - Adapter options with auth configuration
29
29
  * @returns Security context for resolver
30
30
  */
31
- export declare function createSecurityContextFromAuth(tool: McpOpenAPITool, authInfo: AuthInfo, options: Pick<OpenApiAdapterOptions, 'securityResolver' | 'authProviderMapper' | 'staticAuth'>): Promise<SecurityContext>;
31
+ export declare function createSecurityContextFromAuth(tool: McpOpenAPITool, ctx: FrontMcpContext, options: Pick<OpenApiAdapterOptions, 'securityResolver' | 'authProviderMapper' | 'staticAuth'>): Promise<SecurityContext>;
32
32
  /**
33
33
  * Extract all security schemes used by a set of tools
34
34
  *
@@ -43,14 +43,14 @@ export declare function extractSecuritySchemes(tools: McpOpenAPITool[]): Set<str
43
43
  * @param options - Adapter options
44
44
  * @returns Validation result with errors and warnings
45
45
  */
46
- export declare function validateSecurityConfiguration(tools: McpOpenAPITool[], options: Pick<OpenApiAdapterOptions, 'securityResolver' | 'authProviderMapper' | 'staticAuth' | 'generateOptions'>): SecurityValidationResult;
46
+ export declare function validateSecurityConfiguration(tools: McpOpenAPITool[], options: Pick<OpenApiAdapterOptions, 'securityResolver' | 'authProviderMapper' | 'staticAuth' | 'generateOptions' | 'securitySchemesInInput'>): SecurityValidationResult;
47
47
  /**
48
48
  * Resolve security for an OpenAPI tool with validation
49
49
  *
50
50
  * @param tool - OpenAPI tool with mapper
51
- * @param authInfo - FrontMCP authentication info
51
+ * @param ctx - FrontMCP request context with authInfo, sessionId, traceId, etc.
52
52
  * @param options - Adapter options with auth configuration
53
53
  * @returns Resolved security (headers, query params, etc.)
54
54
  * @throws Error if security cannot be resolved
55
55
  */
56
- export declare function resolveToolSecurity(tool: McpOpenAPITool, authInfo: AuthInfo, options: Pick<OpenApiAdapterOptions, 'securityResolver' | 'authProviderMapper' | 'staticAuth'>): Promise<import("mcp-from-openapi").ResolvedSecurity>;
56
+ export declare function resolveToolSecurity(tool: McpOpenAPITool, ctx: FrontMcpContext, options: Pick<OpenApiAdapterOptions, 'securityResolver' | 'authProviderMapper' | 'staticAuth'>): Promise<import("mcp-from-openapi").ResolvedSecurity>;
@@ -6,17 +6,17 @@ exports.validateSecurityConfiguration = validateSecurityConfiguration;
6
6
  exports.resolveToolSecurity = resolveToolSecurity;
7
7
  const mcp_from_openapi_1 = require("mcp-from-openapi");
8
8
  /**
9
- * Resolve security context from FrontMCP auth info with support for multiple auth providers
9
+ * Resolve security context from FrontMCP context with support for multiple auth providers
10
10
  *
11
11
  * @param tool - OpenAPI tool to resolve security for
12
- * @param authInfo - FrontMCP authentication info
12
+ * @param ctx - FrontMCP request context with authInfo, sessionId, traceId, etc.
13
13
  * @param options - Adapter options with auth configuration
14
14
  * @returns Security context for resolver
15
15
  */
16
- async function createSecurityContextFromAuth(tool, authInfo, options) {
16
+ async function createSecurityContextFromAuth(tool, ctx, options) {
17
17
  // 1. Use custom security resolver if provided (highest priority)
18
18
  if (options.securityResolver) {
19
- return await options.securityResolver(tool, authInfo);
19
+ return await options.securityResolver(tool, ctx);
20
20
  }
21
21
  // 2. Use auth provider mapper if provided
22
22
  if (options.authProviderMapper) {
@@ -29,21 +29,74 @@ async function createSecurityContextFromAuth(tool, authInfo, options) {
29
29
  }
30
30
  }
31
31
  // Map each security scheme to its auth provider
32
+ // Process all schemes - first matching token for each auth type (jwt, apiKey, basic, oauth2Token)
32
33
  for (const scheme of securitySchemes) {
33
34
  const authExtractor = options.authProviderMapper[scheme];
34
35
  if (authExtractor) {
35
- const token = authExtractor(authInfo);
36
- if (token) {
37
- // Store in jwt field for http bearer auth
38
- // For other auth types, you can extend this logic
39
- context.jwt = token;
40
- break; // Use first matching provider
36
+ try {
37
+ const token = authExtractor(ctx);
38
+ // Validate return type - must be string or undefined/null
39
+ if (token !== undefined && token !== null && typeof token !== 'string') {
40
+ throw new Error(`authProviderMapper['${scheme}'] must return a string or undefined, ` + `but returned: ${typeof token}`);
41
+ }
42
+ // Reject empty string tokens explicitly - indicates misconfiguration
43
+ if (token === '') {
44
+ throw new Error(`authProviderMapper['${scheme}'] returned empty string. ` +
45
+ `Return undefined/null if no token is available, or provide a valid token.`);
46
+ }
47
+ if (token) {
48
+ // Route token to correct context field based on scheme type
49
+ // Look up the scheme info from the mapper to determine type
50
+ const schemeMapper = tool.mapper.find((m) => m.security?.scheme === scheme);
51
+ const schemeType = schemeMapper?.security?.type?.toLowerCase();
52
+ const httpScheme = schemeMapper?.security?.httpScheme?.toLowerCase();
53
+ // Route based on security scheme type (first token for each type wins)
54
+ if (schemeType === 'apikey') {
55
+ if (!context.apiKey) {
56
+ context.apiKey = token;
57
+ }
58
+ }
59
+ else if (schemeType === 'http' && httpScheme === 'basic') {
60
+ if (!context.basic) {
61
+ context.basic = token;
62
+ }
63
+ }
64
+ else if (schemeType === 'oauth2') {
65
+ if (!context.oauth2Token) {
66
+ context.oauth2Token = token;
67
+ }
68
+ }
69
+ else {
70
+ // Default to jwt for http bearer and unknown types
71
+ if (!context.jwt) {
72
+ context.jwt = token;
73
+ }
74
+ }
75
+ // Continue checking other schemes - don't break
76
+ // This allows validation to see all configured providers
77
+ }
78
+ }
79
+ catch (err) {
80
+ // Re-throw validation errors as-is
81
+ if (err instanceof Error && err.message.includes('authProviderMapper')) {
82
+ throw err;
83
+ }
84
+ // Wrap other errors with context
85
+ const errorMessage = err instanceof Error ? err.message : String(err);
86
+ throw new Error(`authProviderMapper['${scheme}'] threw an error: ${errorMessage}`);
41
87
  }
42
88
  }
43
89
  }
44
- // If no provider matched but we have a default token, use it
45
- if (!context.jwt && authInfo.token) {
46
- context.jwt = authInfo.token;
90
+ // If no auth was set from providers, fall back to ctx.authInfo.token
91
+ // Only fall back if ALL auth fields are empty (not just jwt)
92
+ const hasAnyAuth = context.jwt || context.apiKey || context.basic || context.oauth2Token;
93
+ const authToken = ctx.authInfo?.token;
94
+ if (!hasAnyAuth && authToken) {
95
+ // Validate type before assignment to prevent non-string values
96
+ if (typeof authToken !== 'string') {
97
+ throw new Error(`authInfo.token must be a string, but got: ${typeof authToken}`);
98
+ }
99
+ context.jwt = authToken;
47
100
  }
48
101
  return context;
49
102
  }
@@ -53,7 +106,7 @@ async function createSecurityContextFromAuth(tool, authInfo, options) {
53
106
  }
54
107
  // 4. Default: use main JWT token from auth context
55
108
  return (0, mcp_from_openapi_1.createSecurityContext)({
56
- jwt: authInfo?.token,
109
+ jwt: ctx.authInfo?.token,
57
110
  });
58
111
  }
59
112
  /**
@@ -110,12 +163,23 @@ function validateSecurityConfiguration(tools, options) {
110
163
  // If static auth is provided, assume it covers all schemes
111
164
  return result;
112
165
  }
166
+ // Get schemes that will be provided via input (don't need mapping)
167
+ const schemesInInput = new Set(options.securitySchemesInInput || []);
113
168
  // Check authProviderMapper (low risk - context-based auth)
114
- if (options.authProviderMapper) {
115
- result.securityRiskScore = 'low';
116
- // Validate that all schemes have mappings
169
+ if (options.authProviderMapper || schemesInInput.size > 0) {
170
+ result.securityRiskScore = schemesInInput.size > 0 ? 'medium' : 'low';
171
+ // Log info about per-scheme control
172
+ if (schemesInInput.size > 0) {
173
+ result.warnings.push(`INFO: Per-scheme security control enabled. Schemes in input: ${Array.from(schemesInInput).join(', ')}`);
174
+ }
175
+ // Validate that all schemes have mappings (except those in input)
117
176
  for (const scheme of securitySchemes) {
118
- if (!options.authProviderMapper[scheme]) {
177
+ // Skip schemes that will be provided via input
178
+ if (schemesInInput.has(scheme)) {
179
+ continue;
180
+ }
181
+ // Check if there's a mapping for this scheme
182
+ if (!options.authProviderMapper?.[scheme]) {
119
183
  result.valid = false;
120
184
  result.missingMappings.push(scheme);
121
185
  }
@@ -138,14 +202,14 @@ function validateSecurityConfiguration(tools, options) {
138
202
  * Resolve security for an OpenAPI tool with validation
139
203
  *
140
204
  * @param tool - OpenAPI tool with mapper
141
- * @param authInfo - FrontMCP authentication info
205
+ * @param ctx - FrontMCP request context with authInfo, sessionId, traceId, etc.
142
206
  * @param options - Adapter options with auth configuration
143
207
  * @returns Resolved security (headers, query params, etc.)
144
208
  * @throws Error if security cannot be resolved
145
209
  */
146
- async function resolveToolSecurity(tool, authInfo, options) {
210
+ async function resolveToolSecurity(tool, ctx, options) {
147
211
  const securityResolver = new mcp_from_openapi_1.SecurityResolver();
148
- const securityContext = await createSecurityContextFromAuth(tool, authInfo, options);
212
+ const securityContext = await createSecurityContextFromAuth(tool, ctx, options);
149
213
  // Validate that we have auth for this tool
150
214
  const hasAuth = securityContext.jwt ||
151
215
  securityContext.apiKey ||
@@ -154,18 +218,22 @@ async function resolveToolSecurity(tool, authInfo, options) {
154
218
  (securityContext.apiKeys && Object.keys(securityContext.apiKeys).length > 0) ||
155
219
  (securityContext.customHeaders && Object.keys(securityContext.customHeaders).length > 0);
156
220
  // Check if this tool requires security
157
- const requiresSecurity = tool.mapper.some((m) => m.security && m.required);
221
+ // A tool requires security ONLY if a mapper has security with required=true
222
+ // Optional security schemes (required=false or undefined) should not block requests
223
+ const requiresSecurity = tool.mapper.some((m) => m.security && m.required === true);
158
224
  if (requiresSecurity && !hasAuth) {
159
- // Extract security scheme names
160
- const schemes = tool.mapper
161
- .filter((m) => m.security && m.required)
162
- .map((m) => m.security?.scheme ?? 'unknown')
163
- .join(', ');
225
+ // Extract required security scheme names for error message
226
+ const requiredSchemes = tool.mapper
227
+ .filter((m) => m.security && m.required === true)
228
+ .map((m) => m.security?.scheme ?? 'unknown');
229
+ const uniqueSchemes = [...new Set(requiredSchemes)];
230
+ const schemesStr = uniqueSchemes.join(', ') || 'unknown';
231
+ const firstScheme = uniqueSchemes[0] || 'BearerAuth';
164
232
  throw new Error(`Authentication required for tool '${tool.name}' but no auth configuration found.\n` +
165
- `Required security schemes: ${schemes}\n` +
233
+ `Required security schemes: ${schemesStr}\n` +
166
234
  `Solutions:\n` +
167
- ` 1. Add authProviderMapper: { '${schemes.split(',')[0].trim()}': (authInfo) => authInfo.user?.token }\n` +
168
- ` 2. Add securityResolver: (tool, authInfo) => ({ jwt: authInfo.token })\n` +
235
+ ` 1. Add authProviderMapper: { '${firstScheme}': (ctx) => ctx.authInfo.user?.token }\n` +
236
+ ` 2. Add securityResolver: (tool, ctx) => ({ jwt: ctx.authInfo.token })\n` +
169
237
  ` 3. Add staticAuth: { jwt: process.env.API_TOKEN }\n` +
170
238
  ` 4. Set generateOptions.includeSecurityInInput: true (not recommended for production)`);
171
239
  }