@kya-os/mcp-i
Version:
COMING SOON:Production-ready MCP Identity with automatic registration, key rotation, and optimized performance
340 lines • 14.4 kB
JavaScript
/**
* Transport abstraction for HTTP requests
* Supports both axios and native fetch with runtime detection
*/
/**
* Native fetch-based transport (preferred for Edge runtimes)
*/
export class FetchTransport {
async post(url, data, options) {
const controller = new AbortController();
const timeout = options?.timeout || 30000;
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...options?.headers
},
body: JSON.stringify(data),
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
let errorMessage = '';
let errorData = null;
try {
// Try to parse JSON error response
errorData = await response.json();
// Handle CLI endpoint error format with suggestions
if (errorData.suggestion) {
errorMessage = `${errorData.error || errorData.message || 'Request failed'}. ${errorData.suggestion}`;
}
else {
errorMessage = errorData.message || errorData.error || '';
}
}
catch {
// Fallback to text if not JSON
errorMessage = await response.text();
}
// Handle specific error cases
if (response.status === 429) {
throw new Error('Rate limit exceeded. Please wait a few minutes and try again.');
}
else if (response.status === 403) {
throw new Error('Access forbidden. Please check your API credentials.');
}
else if (response.status === 404) {
throw new Error('API endpoint not found. The service might be unavailable.');
}
else if (response.status === 500 || response.status === 502 || response.status === 503) {
throw new Error('Server error during registration. This might be a temporary issue. Please try again in a few moments.');
}
else if (response.status === 400) {
throw new Error(`Bad request: ${errorMessage || 'Invalid registration data'}`);
}
// Generic error with status code
throw new Error(`HTTP ${response.status}: ${errorMessage || 'Request failed'}`);
}
const responseData = await response.json();
return {
data: responseData,
status: response.status,
headers: Object.fromEntries(response.headers.entries())
};
}
catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timeout. The server is taking too long to respond. Please try again.');
}
throw error;
}
}
async get(url, options) {
const controller = new AbortController();
const timeout = options?.timeout || 30000;
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
method: 'GET',
headers: options?.headers,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
let errorMessage = '';
let errorData = null;
try {
// Try to parse JSON error response
errorData = await response.json();
// Handle CLI endpoint error format with suggestions
if (errorData.suggestion) {
errorMessage = `${errorData.error || errorData.message || 'Request failed'}. ${errorData.suggestion}`;
}
else {
errorMessage = errorData.message || errorData.error || '';
}
}
catch {
// Fallback to text if not JSON
errorMessage = await response.text();
}
// Handle specific error cases
if (response.status === 429) {
throw new Error('Rate limit exceeded. Please wait a few minutes and try again.');
}
else if (response.status === 403) {
throw new Error('Access forbidden. Please check your API credentials.');
}
else if (response.status === 404) {
throw new Error('API endpoint not found. The service might be unavailable.');
}
else if (response.status === 500 || response.status === 502 || response.status === 503) {
throw new Error('Server error during registration. This might be a temporary issue. Please try again in a few moments.');
}
else if (response.status === 400) {
throw new Error(`Bad request: ${errorMessage || 'Invalid registration data'}`);
}
// Generic error with status code
throw new Error(`HTTP ${response.status}: ${errorMessage || 'Request failed'}`);
}
const responseData = await response.json();
return {
data: responseData,
status: response.status,
headers: Object.fromEntries(response.headers.entries())
};
}
catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('Request timeout. The server is taking too long to respond. Please try again.');
}
throw error;
}
}
}
/**
* Axios-based transport (for Node.js environments)
*/
export class AxiosTransport {
axiosInstance;
async post(url, data, options) {
const axios = await this.getAxios();
try {
const response = await axios.post(url, data, {
timeout: options?.timeout || 30000,
headers: options?.headers
});
return {
data: response.data,
status: response.status,
headers: response.headers
};
}
catch (error) {
// Handle specific Axios error cases
if (error.response) {
const status = error.response.status;
let errorMessage = '';
// Handle CLI endpoint error format with suggestions
if (error.response.data?.suggestion) {
errorMessage = `${error.response.data.error || error.response.data.message || 'Request failed'}. ${error.response.data.suggestion}`;
}
else {
errorMessage = error.response.data?.message || error.response.data?.error || '';
}
if (status === 429) {
throw new Error('Rate limit exceeded. Please wait a few minutes and try again.');
}
else if (status === 403) {
throw new Error('Access forbidden. Please check your API credentials.');
}
else if (status === 404) {
throw new Error('API endpoint not found. The service might be unavailable.');
}
else if (status === 500 || status === 502 || status === 503) {
throw new Error('Server error during registration. This might be a temporary issue. Please try again in a few moments.');
}
else if (status === 400) {
throw new Error(`Bad request: ${errorMessage || 'Invalid registration data'}`);
}
// Generic error with status code
throw new Error(`HTTP ${status}: ${errorMessage || 'Request failed'}`);
}
else if (error.request) {
// Request was made but no response received
throw new Error('No response from server. Please check your internet connection.');
}
else {
// Something else happened
throw new Error(error.message || 'Request failed');
}
}
}
async get(url, options) {
const axios = await this.getAxios();
try {
const response = await axios.get(url, {
timeout: options?.timeout || 30000,
headers: options?.headers
});
return {
data: response.data,
status: response.status,
headers: response.headers
};
}
catch (error) {
// Handle specific Axios error cases
if (error.response) {
const status = error.response.status;
let errorMessage = '';
// Handle CLI endpoint error format with suggestions
if (error.response.data?.suggestion) {
errorMessage = `${error.response.data.error || error.response.data.message || 'Request failed'}. ${error.response.data.suggestion}`;
}
else {
errorMessage = error.response.data?.message || error.response.data?.error || '';
}
if (status === 429) {
throw new Error('Rate limit exceeded. Please wait a few minutes and try again.');
}
else if (status === 403) {
throw new Error('Access forbidden. Please check your API credentials.');
}
else if (status === 404) {
throw new Error('API endpoint not found. The service might be unavailable.');
}
else if (status === 500 || status === 502 || status === 503) {
throw new Error('Server error. This might be a temporary issue. Please try again in a few moments.');
}
else if (status === 400) {
throw new Error(`Bad request: ${errorMessage || 'Invalid request data'}`);
}
// Generic error with status code
throw new Error(`HTTP ${status}: ${errorMessage || 'Request failed'}`);
}
else if (error.request) {
// Request was made but no response received
throw new Error('No response from server. Please check your internet connection.');
}
else {
// Something else happened
throw new Error(error.message || 'Request failed');
}
}
}
async getAxios() {
if (!this.axiosInstance) {
try {
// Try to require axios - if it fails, we'll throw a helpful error
// @ts-ignore - dynamic require for optional dependency
const axios = require('axios');
this.axiosInstance = axios.default || axios;
}
catch (error) {
throw new Error('axios is not installed. Please install it with: npm install axios\n' +
'Or use the fetch transport instead by setting transport: "fetch" in options');
}
}
return this.axiosInstance;
}
}
/**
* Runtime detection for optimal transport selection
*/
export class RuntimeDetector {
static isEdgeRuntime() {
return !!(globalThis.EdgeRuntime ||
process.env.NEXT_RUNTIME === 'edge' ||
process.env.VERCEL_EDGE ||
globalThis.Deno ||
globalThis.Bun);
}
static isNodeRuntime() {
return !!(process.versions?.node &&
!this.isEdgeRuntime() &&
!this.isNextJs());
}
static isNextJs() {
return !!(process.env.NEXT_RUNTIME ||
globalThis.__NEXT_DATA__ ||
process.env.NEXT_PUBLIC_VERCEL_ENV);
}
static hasFetch() {
return typeof globalThis.fetch === 'function';
}
static isLambda() {
return !!(process.env.AWS_LAMBDA_FUNCTION_NAME ||
process.env.LAMBDA_TASK_ROOT ||
process.env._HANDLER);
}
}
/**
* Transport factory with runtime detection
*/
export class TransportFactory {
static create(options) {
const transportType = options?.transport || 'auto';
if (transportType === 'fetch') {
if (!RuntimeDetector.hasFetch()) {
throw new Error('Fetch is not available in this runtime');
}
return new FetchTransport();
}
if (transportType === 'axios') {
return new AxiosTransport();
}
// Auto-detect based on runtime
if (transportType === 'auto') {
// Prefer fetch for edge runtimes
if (RuntimeDetector.isEdgeRuntime() && RuntimeDetector.hasFetch()) {
return new FetchTransport();
}
// Use fetch for Next.js to avoid bundling issues
if (RuntimeDetector.isNextJs() && RuntimeDetector.hasFetch()) {
return new FetchTransport();
}
// Use fetch if available and we're in a Lambda (lighter weight)
if (RuntimeDetector.isLambda() && RuntimeDetector.hasFetch()) {
return new FetchTransport();
}
// Default to axios for Node.js
if (RuntimeDetector.isNodeRuntime()) {
return new AxiosTransport();
}
// Fallback to fetch if available
if (RuntimeDetector.hasFetch()) {
return new FetchTransport();
}
// Last resort: axios
return new AxiosTransport();
}
throw new Error(`Unknown transport type: ${transportType}`);
}
}
//# sourceMappingURL=transport.js.map