@frontmcp/adapters 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +4 -1
- package/package.json +7 -5
- package/src/openapi/README.md +753 -0
- package/src/openapi/__tests__/fixtures.d.ts +58 -0
- package/src/openapi/__tests__/fixtures.js +286 -0
- package/src/openapi/__tests__/fixtures.js.map +1 -0
- package/src/openapi/openapi.adapter.d.ts +6 -1
- package/src/openapi/openapi.adapter.js +73 -22
- package/src/openapi/openapi.adapter.js.map +1 -1
- package/src/openapi/openapi.security.d.ts +56 -0
- package/src/openapi/openapi.security.js +174 -0
- package/src/openapi/openapi.security.js.map +1 -0
- package/src/openapi/openapi.tool.d.ts +10 -3
- package/src/openapi/openapi.tool.js +50 -78
- package/src/openapi/openapi.tool.js.map +1 -1
- package/src/openapi/openapi.types.d.ts +75 -5
- package/src/openapi/openapi.types.js.map +1 -1
- package/src/openapi/openapi.utils.d.ts +35 -0
- package/src/openapi/openapi.utils.js +126 -0
- package/src/openapi/openapi.utils.js.map +1 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildRequest = buildRequest;
|
|
4
|
+
exports.applyAdditionalHeaders = applyAdditionalHeaders;
|
|
5
|
+
exports.parseResponse = parseResponse;
|
|
6
|
+
/**
|
|
7
|
+
* Build HTTP request from OpenAPI tool and input parameters
|
|
8
|
+
*
|
|
9
|
+
* @param tool - OpenAPI tool definition with mapper
|
|
10
|
+
* @param input - User input parameters
|
|
11
|
+
* @param security - Resolved security (headers, query params, etc.)
|
|
12
|
+
* @param baseUrl - API base URL
|
|
13
|
+
* @returns Request configuration ready for fetch
|
|
14
|
+
*/
|
|
15
|
+
function buildRequest(tool, input, security, baseUrl) {
|
|
16
|
+
const apiBaseUrl = tool.metadata.servers?.[0]?.url || baseUrl;
|
|
17
|
+
let path = tool.metadata.path;
|
|
18
|
+
const queryParams = new URLSearchParams();
|
|
19
|
+
const headers = new Headers({
|
|
20
|
+
accept: 'application/json',
|
|
21
|
+
...security.headers,
|
|
22
|
+
});
|
|
23
|
+
let body;
|
|
24
|
+
// Process each mapper entry
|
|
25
|
+
for (const mapper of tool.mapper) {
|
|
26
|
+
// Skip security parameters (already handled by SecurityResolver)
|
|
27
|
+
if (mapper.security)
|
|
28
|
+
continue;
|
|
29
|
+
const value = input[mapper.inputKey];
|
|
30
|
+
// Check required parameters
|
|
31
|
+
if (value === undefined || value === null) {
|
|
32
|
+
if (mapper.required) {
|
|
33
|
+
throw new Error(`Required parameter '${mapper.inputKey}' is missing for operation ${tool.name}`);
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// Apply parameter to correct location
|
|
38
|
+
switch (mapper.type) {
|
|
39
|
+
case 'path':
|
|
40
|
+
path = path.replace(`{${mapper.key}}`, encodeURIComponent(String(value)));
|
|
41
|
+
break;
|
|
42
|
+
case 'query':
|
|
43
|
+
queryParams.set(mapper.key, String(value));
|
|
44
|
+
break;
|
|
45
|
+
case 'header':
|
|
46
|
+
headers.set(mapper.key, String(value));
|
|
47
|
+
break;
|
|
48
|
+
case 'cookie':
|
|
49
|
+
// Simple cookie header merge; you may want a more robust cookie encoder.
|
|
50
|
+
{
|
|
51
|
+
const existing = headers.get('cookie') ?? headers.get('Cookie');
|
|
52
|
+
const cookiePair = `${mapper.key}=${encodeURIComponent(String(value))}`;
|
|
53
|
+
const combined = existing ? `${existing}; ${cookiePair}` : cookiePair;
|
|
54
|
+
headers.set('Cookie', combined);
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
case 'body':
|
|
58
|
+
if (!body)
|
|
59
|
+
body = {};
|
|
60
|
+
body[mapper.key] = value;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Add query parameters from security (e.g., API keys in query string)
|
|
65
|
+
Object.entries(security.query).forEach(([key, value]) => {
|
|
66
|
+
queryParams.set(key, value);
|
|
67
|
+
});
|
|
68
|
+
// Add cookies from a security context
|
|
69
|
+
if (security.cookies && Object.keys(security.cookies).length > 0) {
|
|
70
|
+
const existing = headers.get('cookie') ?? headers.get('Cookie');
|
|
71
|
+
const securityCookieString = Object.entries(security.cookies)
|
|
72
|
+
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
|
|
73
|
+
.join('; ');
|
|
74
|
+
const combined = existing ? `${existing}; ${securityCookieString}` : securityCookieString;
|
|
75
|
+
headers.set('Cookie', combined);
|
|
76
|
+
}
|
|
77
|
+
// Ensure all path parameters are resolved
|
|
78
|
+
if (path.includes('{')) {
|
|
79
|
+
throw new Error(`Failed to resolve all path parameters in ${path} for operation ${tool.name}`);
|
|
80
|
+
}
|
|
81
|
+
// Build final URL
|
|
82
|
+
const queryString = queryParams.toString();
|
|
83
|
+
const url = `${apiBaseUrl}${path}${queryString ? `?${queryString}` : ''}`;
|
|
84
|
+
return { url, headers, body };
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Apply custom headers to request
|
|
88
|
+
*
|
|
89
|
+
* @param headers - Current headers
|
|
90
|
+
* @param additionalHeaders - Additional static headers to add
|
|
91
|
+
*/
|
|
92
|
+
function applyAdditionalHeaders(headers, additionalHeaders) {
|
|
93
|
+
if (!additionalHeaders)
|
|
94
|
+
return;
|
|
95
|
+
Object.entries(additionalHeaders).forEach(([key, value]) => {
|
|
96
|
+
headers.set(key, value);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse API response based on content type
|
|
101
|
+
*
|
|
102
|
+
* @param response - Fetch response
|
|
103
|
+
* @returns Parsed response data
|
|
104
|
+
*/
|
|
105
|
+
async function parseResponse(response) {
|
|
106
|
+
const contentType = response.headers.get('content-type');
|
|
107
|
+
const text = await response.text();
|
|
108
|
+
// Check for error responses
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}\n${text}`);
|
|
111
|
+
}
|
|
112
|
+
// Parse JSON responses
|
|
113
|
+
if (contentType?.includes('application/json')) {
|
|
114
|
+
try {
|
|
115
|
+
return { data: JSON.parse(text) };
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
// Invalid JSON, return as text
|
|
119
|
+
console.warn('Failed to parse JSON response:', error);
|
|
120
|
+
return { data: text };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Return text for non-JSON responses
|
|
124
|
+
return { data: text };
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=openapi.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.utils.js","sourceRoot":"","sources":["../../../src/openapi/openapi.utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpOpenAPITool, SecurityResolver } from 'mcp-from-openapi';\n\n/**\n * Request configuration for building HTTP requests\n */\nexport interface RequestConfig {\n url: string;\n headers: Headers;\n body?: Record<string, unknown>;\n}\n\n/**\n * Build HTTP request from OpenAPI tool and input parameters\n *\n * @param tool - OpenAPI tool definition with mapper\n * @param input - User input parameters\n * @param security - Resolved security (headers, query params, etc.)\n * @param baseUrl - API base URL\n * @returns Request configuration ready for fetch\n */\nexport function buildRequest(\n tool: McpOpenAPITool,\n input: Record<string, unknown>,\n security: Awaited<ReturnType<SecurityResolver['resolve']>>,\n baseUrl: string,\n): RequestConfig {\n const apiBaseUrl = tool.metadata.servers?.[0]?.url || baseUrl;\n let path = tool.metadata.path;\n const queryParams = new URLSearchParams();\n const headers = new Headers({\n accept: 'application/json',\n ...security.headers,\n });\n let body: Record<string, unknown> | undefined;\n\n // Process each mapper entry\n for (const mapper of tool.mapper) {\n // Skip security parameters (already handled by SecurityResolver)\n if (mapper.security) continue;\n\n const value = input[mapper.inputKey];\n\n // Check required parameters\n if (value === undefined || value === null) {\n if (mapper.required) {\n throw new Error(`Required parameter '${mapper.inputKey}' is missing for operation ${tool.name}`);\n }\n continue;\n }\n\n // Apply parameter to correct location\n switch (mapper.type) {\n case 'path':\n path = path.replace(`{${mapper.key}}`, encodeURIComponent(String(value)));\n break;\n\n case 'query':\n queryParams.set(mapper.key, String(value));\n break;\n\n case 'header':\n headers.set(mapper.key, String(value));\n break;\n case 'cookie':\n // Simple cookie header merge; you may want a more robust cookie encoder.\n {\n const existing = headers.get('cookie') ?? headers.get('Cookie');\n const cookiePair = `${mapper.key}=${encodeURIComponent(String(value))}`;\n const combined = existing ? `${existing}; ${cookiePair}` : cookiePair;\n headers.set('Cookie', combined);\n }\n break;\n\n case 'body':\n if (!body) body = {};\n body[mapper.key] = value;\n break;\n }\n }\n\n // Add query parameters from security (e.g., API keys in query string)\n Object.entries(security.query).forEach(([key, value]) => {\n queryParams.set(key, value);\n });\n\n // Add cookies from a security context\n if (security.cookies && Object.keys(security.cookies).length > 0) {\n const existing = headers.get('cookie') ?? headers.get('Cookie');\n const securityCookieString = Object.entries(security.cookies)\n .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)\n .join('; ');\n const combined = existing ? `${existing}; ${securityCookieString}` : securityCookieString;\n headers.set('Cookie', combined);\n }\n\n // Ensure all path parameters are resolved\n if (path.includes('{')) {\n throw new Error(`Failed to resolve all path parameters in ${path} for operation ${tool.name}`);\n }\n\n // Build final URL\n const queryString = queryParams.toString();\n const url = `${apiBaseUrl}${path}${queryString ? `?${queryString}` : ''}`;\n\n return { url, headers, body };\n}\n\n/**\n * Apply custom headers to request\n *\n * @param headers - Current headers\n * @param additionalHeaders - Additional static headers to add\n */\nexport function applyAdditionalHeaders(headers: Headers, additionalHeaders?: Record<string, string>): void {\n if (!additionalHeaders) return;\n\n Object.entries(additionalHeaders).forEach(([key, value]) => {\n headers.set(key, value);\n });\n}\n\n/**\n * Parse API response based on content type\n *\n * @param response - Fetch response\n * @returns Parsed response data\n */\nexport async function parseResponse(response: Response): Promise<{ data: unknown }> {\n const contentType = response.headers.get('content-type');\n const text = await response.text();\n\n // Check for error responses\n if (!response.ok) {\n throw new Error(`API request failed: ${response.status} ${response.statusText}\\n${text}`);\n }\n\n // Parse JSON responses\n if (contentType?.includes('application/json')) {\n try {\n return { data: JSON.parse(text) };\n } catch (error) {\n // Invalid JSON, return as text\n console.warn('Failed to parse JSON response:', error);\n return { data: text };\n }\n }\n\n // Return text for non-JSON responses\n return { data: text };\n}\n"]}
|