Skip to main content
Glama
simplifier-client.ts24.1 kB
import {config} from '../config.js'; import {login} from "./basicauth.js"; import {trackingHeader} from "./matomo-tracking.js"; import {appendFile} from 'fs/promises'; import { BusinessObjectTestRequest, BusinessObjectTestResponse, ConnectorTestRequest, ConnectorTestResponse, CreateLoginMethodRequest, GenericApiResponse, RFCWizardCreateCallsPayload, RFCWizardDetailsResponse, RFCWizardSearchOptions, SAPSystem, SAPSystemListResponse, SimplifierApiResponse, SimplifierBusinessObjectDetails, SimplifierBusinessObjectFunction, SimplifierConnectorCallDetails, SimplifierConnectorCallsResponse, SimplifierConnectorCallUpdate, SimplifierConnectorDetails, SimplifierConnectorListResponse, SimplifierConnectorUpdate, SimplifierDataType, SimplifierDataTypesResponse, SimplifierDataTypeUpdate, SimplifierInstance, SimplifierInstanceSettings, SimplifierLogEntryDetails, SimplifierLoginMethodDetailsRaw, SimplifierLoginMethodsResponse, SimplifierLogListOptions, SimplifierLogListResponse, SimplifierLogPagesResponse, SimplifierOAuth2ClientsResponse, UnwrappedSimplifierApiResponse, UpdateLoginMethodRequest, } from './types.js'; /** * Client for interacting with Simplifier Low Code Platform REST API * * This client will need to be enhanced with SimplifierToken. * The SimplifierToken acts as a session key that needs to be: * - Obtained daily by the user * - Configured in environment variables * - Included in API requests as authentication header */ export class SimplifierClient { private baseUrl: string; private simplifierToken?: string | undefined; constructor() { this.baseUrl = config.simplifierBaseUrl; } getBaseUrl(): string { return this.baseUrl; } /** * Log HTTP request details to file if HTTP_REQUEST_LOG_FILE is configured * Sanitizes sensitive information like SimplifierToken */ private async logRequest(url: string, options: RequestInit): Promise<void> { if (!config.httpRequestLogFile) { return; } try { const timestamp = new Date().toISOString(); const method = options.method || 'GET'; // Sanitize headers to hide SimplifierToken const sanitizedHeaders: Record<string, string> = {}; if (options.headers) { const headers = options.headers as Record<string, string>; for (const [key, value] of Object.entries(headers)) { if (key.toLowerCase() === 'simplifiertoken') { sanitizedHeaders[key] = '***REDACTED***'; } else { sanitizedHeaders[key] = value; } } } // Format body (truncate if too large) let bodyStr = ''; if (options.body) { const body = typeof options.body === 'string' ? options.body : JSON.stringify(options.body); //bodyStr = body.length > 10000 ? body.substring(0, 10000) + '...[truncated]' : body; bodyStr = body; } const logEntry = { timestamp, method, url, headers: sanitizedHeaders, body: bodyStr }; await appendFile(config.httpRequestLogFile, JSON.stringify(logEntry) + '\n'); } catch (error) { // Silently fail - logging should not break requests console.error('Failed to log HTTP request:', error); } } private async getSimplifierToken(): Promise<string> { if (!this.simplifierToken) { if (config.simplifierToken) { this.simplifierToken = config.simplifierToken; } else if (config.credentialsFile) { this.simplifierToken = await login(); } } return this.simplifierToken!; } /** * Private method to execute HTTP request with common setup * Returns raw Response object for different processing approaches */ private async executeRequest(urlPath: string, options: RequestInit = {}): Promise<Response> { const url = `${this.baseUrl}${urlPath}`; const simplifierToken = await this.getSimplifierToken(); const data = { ...options, headers: { 'Content-Type': 'application/json', 'SimplifierToken': simplifierToken, ...options.headers, }, } // Log the request if logging is enabled await this.logRequest(url, data); const response: Response = await fetch(url, data); if (!response.ok) { const body = await response.text(); throw new Error(`HTTP ${response.status}: ${response.statusText}\n${body}`); } return response; } async executeRequestWithHandler<T>( urlPath: string, options: RequestInit, handle: (response: Response) => T ): Promise<T> { try { const response = await this.executeRequest(urlPath, options); return handle(response) } catch (error) { if (error instanceof Error) { throw new Error(`Failed request ${options.method || "GET"} ${this.baseUrl}${urlPath}: ${error.message}`); } throw error; } } /** For handling APIs that return JSON in the common Simplifier API format: * ``` * { success: true, result: T } | {success: false, message?: string, error?: string } * ``` */ async makeRequest<T>( urlPath: string, options: RequestInit = {} ): Promise<T> { return this.executeRequestWithHandler(urlPath, options, async (response: Response) => { const oResponse = (await response.json()) as SimplifierApiResponse<T>; if (oResponse.success === false) { throw new Error(`Received error: ${oResponse.error || ""}${oResponse.message || ""}`); } return oResponse.result as T; }) } /** For handling APIs that return JSON, but don't wrap successful calls with `{success: true, result: ... }`. * Errors are still expected to be of the form `{success: false, message?: string, error?: string }` */ async makeUnwrappedRequest<T extends object>( urlPath: string, options: RequestInit = {} ): Promise<T> { return this.executeRequestWithHandler(urlPath, options, async (response: Response) => { const oResponse = (await response.json()) as UnwrappedSimplifierApiResponse<T>; if ('success' in oResponse && oResponse.success === false) { throw new Error(`Received error: ${oResponse.error || ""}${oResponse.message || ""}`); } return (oResponse) as T; }) } /** For handling APIs that return plaintext results and errors */ async makePlaintextRequest( urlPath: string, options: RequestInit = {} ): Promise<string> { return this.executeRequestWithHandler(urlPath, options, async (response: Response) => { return await response.text(); }) } async ping(): Promise<boolean> { return this.makeUnwrappedRequest<{msg: string}>(`/client/2.0/ping`).then(response => response.msg === "pong"); } async getServerBusinessObjects(trackingKey: string): Promise<SimplifierBusinessObjectDetails[]> { return this.makeRequest("/UserInterface/api/businessobjects/server", { method: "GET", headers: trackingHeader(trackingKey) }); } async getServerBusinessObjectDetails(objectName: string, trackingKey: string): Promise<SimplifierBusinessObjectDetails> { return this.makeRequest(`/UserInterface/api/businessobjects/server/${objectName}`, { method: "GET", headers: trackingHeader(trackingKey) }) } async deleteServerBusinessObject(objectName: string, trackingKey: string): Promise<string> { const oResult = await this.makeUnwrappedRequest<GenericApiResponse>(`/UserInterface/api/businessobjects/server/${objectName}`, { method: "DELETE", headers: trackingHeader(trackingKey) }) return oResult.message; } async getServerBusinessObjectFunction(objectName: string, functionName: string, trackingKey?: string): Promise<SimplifierBusinessObjectFunction> { return this.makeRequest(`/UserInterface/api/businessobjects/server/${objectName}/functions/${functionName}?completions=false&dataTypes=true`, { method: "GET", headers: trackingHeader(trackingKey) }) } async getServerBusinessObjectFunctions(objectName: string, trackingKey: string): Promise<SimplifierBusinessObjectFunction[]> { return this.makeRequest(`/UserInterface/api/businessobjects/server/${objectName}/functions`, { method: "GET", headers: trackingHeader(trackingKey) }) } async createServerBusinessObjectFunction(objectName: string, functionData: SimplifierBusinessObjectFunction): Promise<string> { await this.makeRequest(`/UserInterface/api/businessobjects/server/${objectName}/functions`, { method: "POST", body: JSON.stringify(functionData) }); return `Successfully created function '${functionData.name}' in Business Object '${objectName}'`; } async updateServerBusinessObjectFunction(objectName: string, functionName: string, functionData: SimplifierBusinessObjectFunction): Promise<string> { await this.makeRequest(`/UserInterface/api/businessobjects/server/${objectName}/functions/${functionName}`, { method: "PUT", body: JSON.stringify(functionData) }); return `Successfully updated function '${functionName}' in Business Object '${objectName}'`; } async testServerBusinessObjectFunction(objectName: string, functionName: string, testRequest: BusinessObjectTestRequest, trackingKey: string): Promise<BusinessObjectTestResponse> { return await this.makeUnwrappedRequest(`/UserInterface/api/businessobjecttest/${objectName}/methods/${functionName}`, { method: "POST", body: JSON.stringify(testRequest), headers: trackingHeader(trackingKey) }); } async deleteServerBusinessObjectFunction(objectName: string, functionName: string, trackingKey: string): Promise<string> { const oResult = await this.makeUnwrappedRequest<GenericApiResponse>(`/UserInterface/api/businessobjects/server/${objectName}/functions/${functionName}`, { method: "DELETE", headers: trackingHeader(trackingKey) }) return oResult.message; } async createServerBusinessObject(oData: SimplifierBusinessObjectDetails): Promise<string> { return this.makeRequest(`/UserInterface/api/businessobjects/server`, { method: "POST", body: JSON.stringify(oData) }) .then(() => `Successfully created Business Object '${oData.name}'`) } async updateServerBusinessObject(oData: SimplifierBusinessObjectDetails): Promise<string> { return this.makeRequest(`/UserInterface/api/businessobjects/server/${oData.name}`, { method: "PUT", body: JSON.stringify(oData) }) .then(() => `Successfully updated Business Object '${oData.name}'`); } async createConnector(oData: SimplifierConnectorUpdate): Promise<string> { await this.makeRequest(`/UserInterface/api/connectors`, { method: "POST", body: JSON.stringify(oData) }); return `Successfully created Connector '${oData.name}'`; } async updateConnector(oData: SimplifierConnectorUpdate): Promise<string> { await this.makeRequest(`/UserInterface/api/connectors/${oData.name}`, { method: "PUT", body: JSON.stringify(oData) }); return `Successfully updated Connector '${oData.name}'`; } async createConnectorCall(connectorName: string, oData: SimplifierConnectorCallUpdate): Promise<string> { await this.makeRequest(`/UserInterface/api/connectors/${connectorName}/calls`, { method: "POST", body: JSON.stringify(oData) }); return `Successfully created Connector call '${connectorName}.${oData.name}'`; } async updateConnectorCall(connectorName: string, oData: SimplifierConnectorCallUpdate): Promise<string> { await this.makeRequest(`/UserInterface/api/connectors/${connectorName}/calls/${oData.name}`, { method: "PUT", body: JSON.stringify(oData) }); return `Successfully updated Connector call '${connectorName}.${oData.name}'`; } async getSoapConnectorWSDL(connectorName: string, endpoint: string): Promise<string> { const response = await this.executeRequest(`/UserInterface/api/connectors/${connectorName}/state/${endpoint}`, { method: "POST", body: '{"action": "download"}', }); return response.json(); } async searchPossibleRFCConnectorCalls(connectorName: string, filter: RFCWizardSearchOptions, trackingKey: string): Promise<string[]> { const result = await this.makeUnwrappedRequest<{names: string[]}>(`/UserInterface/api/connectorCallWizard/${connectorName}/operations/search`, { method: "POST", body: JSON.stringify(filter), headers: trackingHeader(trackingKey), }); return result.names; } async viewRFCFunctions(connectorName: string, functionNames: string[], trackingKey: string): Promise<void> { await this.makeUnwrappedRequest(`/UserInterface/api/connectorCallWizard/${connectorName}/operations/view`, { method: "POST", body: JSON.stringify({filter: functionNames.join(", ")}), headers: trackingHeader(trackingKey), }); } async rfcWizardGetCallDetails(connectorName: string, functionNames: string[]): Promise<RFCWizardDetailsResponse> { return this.makeUnwrappedRequest<RFCWizardDetailsResponse>(`/UserInterface/api/connectorCallWizard/${connectorName}/suggestions/detailed`, { method: "POST", body: JSON.stringify({callsRfc: functionNames}), }); } async rfcWizardCreateCalls(connectorName: string, calls: RFCWizardCreateCallsPayload): Promise<string> { await this.makePlaintextRequest(`/UserInterface/api/connectorCallWizard/${connectorName}`, { method: "POST", body: JSON.stringify(calls), }); return `Successfully created ${calls.callsRfc.length} calls: ${calls.callsRfc}.`; } async getDataTypes(trackingKey: string): Promise<SimplifierDataTypesResponse> { return this.makeUnwrappedRequest("/UserInterface/api/datatypes?cacheIndex=true", { method: "GET", headers: trackingHeader(trackingKey) }); } async getSingleDataType(name: string, nameSpace: string | undefined, trackingKey: string): Promise<SimplifierDataType> { const fullDataType = `${nameSpace ? nameSpace + '/' : ''}${name}` return this.makeUnwrappedRequest(`/UserInterface/api/datatypes/${fullDataType}`, { method: "GET", headers: trackingHeader(trackingKey) }); } /** * Get detailed information about a specific datatype by its full identifier (not the hash id). * * @param datatypeId - The fully qualified datatype identifier, which is namespace/datatypename. * For root namespace (no namespace), use just the datatype name without slash. * Examples: * - "bo/SF_User/getUser_groups_Struct" (business object datatype with namespace) * - "_ITIZ_B_BUS2038_DATA" (datatype in root namespace) * @param trackingKey - The MCP tool or resource name for tracking purposes * @returns Detailed datatype information including fields, category, and metadata */ async getDataTypeByName(datatypeId: string, trackingKey?: string): Promise<SimplifierDataType> { return this.makeUnwrappedRequest(`/UserInterface/api/datatypes/${datatypeId}?woAutoGen=false&detailLevel=detailed`, { method: "GET", headers: trackingHeader(trackingKey) }); } async createDataType(datatypeDesc: SimplifierDataTypeUpdate): Promise<string> { const fullDataType = `${datatypeDesc.nameSpace ? datatypeDesc.nameSpace + '/' : ''}${datatypeDesc.name}` return this.makePlaintextRequest(`/UserInterface/api/datatypes`, { method: "POST", body: JSON.stringify(datatypeDesc) }) .then((id) => `Successfully created data type ${fullDataType} with id ${id}`); } async updateDataType(datatypeDesc: SimplifierDataTypeUpdate): Promise<string> { const fullDataType = `${datatypeDesc.nameSpace ? datatypeDesc.nameSpace + '/' : ''}${datatypeDesc.name}` return this.makePlaintextRequest(`/UserInterface/api/datatypes/${fullDataType}`, { method: "PUT", body: JSON.stringify(datatypeDesc) }) .then((id) => `Successfully updated data type ${fullDataType} with id ${id}`) } async deleteDataType(name: string, nameSpace: string | undefined, trackingKey: string): Promise<string> { const fullDataType = `${nameSpace ? nameSpace + '/' : ''}${name}` return this.makePlaintextRequest(`/UserInterface/api/datatypes/${fullDataType}`, { method: "DELETE", headers: trackingHeader(trackingKey) }); } // ======================================== // Connector API Methods // ======================================== async listConnectors(trackingKey: string): Promise<SimplifierConnectorListResponse> { return this.makeUnwrappedRequest(`/UserInterface/api/connectors`, { method: "GET", headers: trackingHeader(trackingKey) }); } async getConnector(name: string, trackingKey: string, withEndpointConfigurations: boolean = true): Promise<SimplifierConnectorDetails> { const params = withEndpointConfigurations ? '' : '?withEndpointConfigurations=false'; return this.makeUnwrappedRequest(`/UserInterface/api/connectors/${name}${params}`, { method: "GET", headers: trackingHeader(trackingKey) }); } async listConnectorCalls(connectorName: string, trackingKey: string): Promise<SimplifierConnectorCallsResponse> { return this.makeUnwrappedRequest(`/UserInterface/api/connectors/${connectorName}/calls`, { method: "GET", headers: trackingHeader(trackingKey) }); } async getConnectorCall(connectorName: string, callName: string, trackingKey?: string): Promise<SimplifierConnectorCallDetails> { const response = await this.makeUnwrappedRequest<SimplifierConnectorCallDetails>(`/UserInterface/api/connectors/${connectorName}/calls/${callName}`, { method: "GET", headers: trackingHeader(trackingKey) }); // Normalize category values from API - 'any' should be 'base' if (response.connectorCallParameters) { response.connectorCallParameters = response.connectorCallParameters.map(param => ({ ...param, dataType: { ...param.dataType, category: param.dataType.category === 'any' ? 'base' : param.dataType.category } })); } return response; } async testConnectorCall(connectorName: string, callName: string, testRequest: ConnectorTestRequest, trackingKey: string): Promise<ConnectorTestResponse> { return await this.makeUnwrappedRequest(`/UserInterface/api/connectortest/${connectorName}/calls/${callName}`, { method: "POST", body: JSON.stringify(testRequest), headers: trackingHeader(trackingKey) }); } async deleteConnector(connectorName: string, trackingKey: string): Promise<string> { const oResult = await this.makeUnwrappedRequest<GenericApiResponse>(`/UserInterface/api/connectors/${connectorName}`, { method: "DELETE", headers: trackingHeader(trackingKey) }) return oResult.message; } async deleteConnectorCall(connectorName: string, callName: string, trackingKey: string): Promise<string> { const oResult = await this.makeUnwrappedRequest<GenericApiResponse>(`/UserInterface/api/connectors/${connectorName}/calls/${callName}`, { method: "DELETE", headers: trackingHeader(trackingKey) }) return oResult.message; } // ======================================== // LoginMethod API Methods // ======================================== async listLoginMethods(trackingKey: string): Promise<SimplifierLoginMethodsResponse> { return this.makeUnwrappedRequest(`/UserInterface/api/login-methods`, { method: "GET", headers: trackingHeader(trackingKey) }); } async listOAuth2Clients(trackingKey: string): Promise<SimplifierOAuth2ClientsResponse> { return this.makeUnwrappedRequest(`/UserInterface/api/AuthSettings?mechanism=OAuth2`, { method: "GET", headers: trackingHeader(trackingKey) }); } async getLoginMethodDetails(name: string, trackingKey: string): Promise<SimplifierLoginMethodDetailsRaw> { return this.makeUnwrappedRequest(`/UserInterface/api/login-methods/${name}`, { method: "GET", headers: trackingHeader(trackingKey) }); } async createLoginMethod(request: CreateLoginMethodRequest): Promise<string> { await this.makeUnwrappedRequest(`/UserInterface/api/login-methods`, { method: "POST", body: JSON.stringify(request) }); return `Successfully created Login Method '${request.name}'`; } async updateLoginMethod(name: string, request: UpdateLoginMethodRequest): Promise<string> { await this.makeUnwrappedRequest(`/UserInterface/api/login-methods/${name}`, { method: "PUT", body: JSON.stringify(request) }); return `Successfully updated Login Method '${name}'`; } // SAP system API methods async getSapSystem(systemName: string, trackingKey: string): Promise<SAPSystem> { return this.makeUnwrappedRequest(`/UserInterface/api/sapSystem/${systemName}`, { method: "GET", headers: trackingHeader(trackingKey), }) } async listSapSystems(trackingKey: string): Promise<SAPSystemListResponse> { return this.makeUnwrappedRequest(`/UserInterface/api/sapSystem`, { method: "GET", headers: trackingHeader(trackingKey), }) } async createSapSystem(sapSystemRequest: SAPSystem): Promise<string> { await this.makeUnwrappedRequest(`/UserInterface/api/sapSystem`, { method: "POST", body: JSON.stringify(sapSystemRequest), }) return `Successfully created SAP system '${sapSystemRequest.name}'`; } async updateSapSystem(sapSystemRequest: SAPSystem): Promise<string> { await this.makeUnwrappedRequest(`/UserInterface/api/sapSystem/${sapSystemRequest.name}`, { method: "PUT", body: JSON.stringify(sapSystemRequest), }) return `Successfully updated SAP system '${sapSystemRequest.name}'`; } async deleteSapSystem(systemName: string, trackingKey: string): Promise<string> { const oResult = await this.makeUnwrappedRequest<GenericApiResponse>(`/UserInterface/api/sapSystem/${systemName}`, { method: "DELETE", headers: trackingHeader(trackingKey), }) return oResult.message; } // Logging API methods async listLogEntriesPaginated(pageNo: number, pageSize: number, trackingKey: string, options?: SimplifierLogListOptions): Promise<SimplifierLogListResponse> { const params = this.optionsToQueryParams(options) const queryString = params.toString(); const url = queryString ? `/UserInterface/api/logging/list/page/${pageNo}/pagesize/${pageSize}?${queryString}` : `/UserInterface/api/logging/list/page/${pageNo}/pagesize/${pageSize}`; return await this.makeUnwrappedRequest(url, { method: "GET", headers: trackingHeader(trackingKey) }); } async getLogPages(pageSize: number = 50, options?: SimplifierLogListOptions): Promise<SimplifierLogPagesResponse> { const params = this.optionsToQueryParams(options) params.append('pagesize', pageSize.toString()) return await this.makeUnwrappedRequest(`/UserInterface/api/logging/pages?${params}`); } async getLogEntry(id: string, trackingKey: string): Promise<SimplifierLogEntryDetails> { return await this.makeUnwrappedRequest(`/UserInterface/api/logging/entry/${id}`, { method: "GET", headers: trackingHeader(trackingKey) }); } optionsToQueryParams(options?: SimplifierLogListOptions): URLSearchParams { const params = new URLSearchParams(); if (options?.logLevel !== undefined) params.append('logLevel', options.logLevel.toString()); if (options?.since) params.append('since', options.since); if (options?.from) params.append('from', options.from); if (options?.until) params.append('until', options.until); return params; } // Instance settings async getInstanceSettings(trackingKey: string): Promise<SimplifierInstance[]> { const oInstanceSettings: SimplifierInstanceSettings = await this.makeUnwrappedRequest(`/UserInterface/api/InstanceSettings`, { method: "GET", headers: trackingHeader(trackingKey) }); return oInstanceSettings.instanceSettings; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/SimplifierIO/simplifier-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server