MCP ABAP ADT
by mario-andreschak
Verified
- src
- lib
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import axios, { AxiosError, AxiosInstance } from 'axios';
import { Agent } from 'https';
import { AxiosResponse } from 'axios';
import { getConfig, SapConfig } from '../index'; // getConfig needs to be exported from index.ts
export { McpError, ErrorCode, AxiosResponse };
export function return_response(response: AxiosResponse) {
return {
isError: false,
content: [{
type: 'text',
text: response.data
}]
};
}
export function return_error(error: any) {
return {
isError: true,
content: [{
type: 'text',
text: `Error: ${error instanceof AxiosError ? String(error.response?.data)
: error instanceof Error ? error.message
: String(error)}`
}]
};
}
let axiosInstance: AxiosInstance | null = null;
export function createAxiosInstance() {
if (!axiosInstance) {
axiosInstance = axios.create({
httpsAgent: new Agent({
rejectUnauthorized: false // Allow self-signed certificates
})
});
}
return axiosInstance;
}
// Cleanup function for tests
export function cleanup() {
if (axiosInstance) {
// Clear any interceptors
const reqInterceptor = axiosInstance.interceptors.request.use((config) => config);
const resInterceptor = axiosInstance.interceptors.response.use((response) => response);
axiosInstance.interceptors.request.eject(reqInterceptor);
axiosInstance.interceptors.response.eject(resInterceptor);
}
axiosInstance = null;
config = undefined;
csrfToken = null;
cookies = null;
}
let config: SapConfig | undefined;
let csrfToken: string | null = null;
let cookies: string | null = null; // Variable to store cookies
export async function getBaseUrl() {
if (!config) {
config = getConfig();
}
const { url } = config;
try {
const urlObj = new URL(url);
const baseUrl = Buffer.from(`${urlObj.origin}`);
return baseUrl;
} catch (error) {
const errorMessage = `Invalid URL in configuration: ${error instanceof Error ? error.message : error}`;
throw new Error(errorMessage);
}
}
export async function getAuthHeaders() {
if (!config) {
config = getConfig();
}
const { username, password, client } = config;
const auth = Buffer.from(`${username}:${password}`).toString('base64'); // Create Basic Auth string
return {
'Authorization': `Basic ${auth}`, // Basic Authentication header
'X-SAP-Client': client // SAP client header
};
}
async function fetchCsrfToken(url: string): Promise<string> {
try {
const response = await createAxiosInstance()({
method: 'GET',
url,
headers: {
...(await getAuthHeaders()),
'x-csrf-token': 'fetch'
}
});
const token = response.headers['x-csrf-token'];
if (!token) {
throw new Error('No CSRF token in response headers');
}
// Extract and store cookies
if (response.headers['set-cookie']) {
cookies = response.headers['set-cookie'].join('; ');
}
return token;
} catch (error) {
// Even if the request fails, try to get token from error response
if (error instanceof AxiosError && error.response?.headers['x-csrf-token']) {
const token = error.response.headers['x-csrf-token'];
if (token) {
// Extract and store cookies from the error response as well
if (error.response.headers['set-cookie']) {
cookies = error.response.headers['set-cookie'].join('; ');
}
return token;
}
}
// If we couldn't get token from error response either, throw the original error
throw new Error(`Failed to fetch CSRF token: ${error instanceof Error ? error.message : String(error)}`);
}
}
export async function makeAdtRequest(url: string, method: string, timeout: number, data?: any, params?: any) {
// For POST/PUT requests, ensure we have a CSRF token
if ((method === 'POST' || method === 'PUT') && !csrfToken) {
try {
csrfToken = await fetchCsrfToken(url);
} catch (error) {
throw new Error('CSRF token is required for POST/PUT requests but could not be fetched');
}
}
const requestHeaders = {
...(await getAuthHeaders())
};
// Add CSRF token for POST/PUT requests
if ((method === 'POST' || method === 'PUT') && csrfToken) {
requestHeaders['x-csrf-token'] = csrfToken;
}
// Add cookies if available
if (cookies) {
requestHeaders['Cookie'] = cookies;
}
const config: any = {
method,
url,
headers: requestHeaders,
timeout,
params: params
};
// Include data in the request configuration if provided
if (data) {
config.data = data;
}
try {
const response = await createAxiosInstance()(config);
return response;
} catch (error) {
// If we get a 403 with "CSRF token validation failed", try to fetch a new token and retry
if (error instanceof AxiosError && error.response?.status === 403 &&
error.response.data?.includes('CSRF')) {
csrfToken = await fetchCsrfToken(url);
config.headers['x-csrf-token'] = csrfToken;
return await createAxiosInstance()(config);
}
throw error;
}
}