guniweb-sap-mcp 0.5.0 → 0.5.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/dist/index.js +70 -67
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {readFileSync,existsSync,watchFile,watch,unwatchFile,mkdirSync,writeFileSync,renameSync,unlinkSync}from'fs';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import
|
|
2
|
+
import {readFileSync,existsSync,watchFile,watch,unwatchFile,mkdirSync,writeFileSync,renameSync,unlinkSync}from'fs';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import $i from'pino';import {minimatch}from'minimatch';import {z as z$1}from'zod/v4';import {lookup}from'dns/promises';import {connect}from'tls';import Jt,{isAxiosError}from'axios';import {AsyncLocalStorage}from'async_hooks';import {randomUUID,randomBytes,timingSafeEqual,createHash}from'crypto';import {jwtVerify,decodeJwt,createRemoteJWKSet}from'jose';import {SignedXml}from'xml-crypto';import {Agent}from'https';import {createServer}from'http';import {resolve,join,dirname}from'path';import {z as z$2}from'zod';import {readFile}from'fs/promises';import {XMLParser,XMLBuilder}from'fast-xml-parser';import {McpServer,ResourceTemplate}from'@modelcontextprotocol/sdk/server/mcp.js';import {SSEServerTransport}from'@modelcontextprotocol/sdk/server/sse.js';import {StreamableHTTPServerTransport}from'@modelcontextprotocol/sdk/server/streamableHttp.js';import js from'express';var Nn=16;function Ie(t,e,n,r=process.env){if(t)return t;let s=r[e];if(s)return s;let i=r[n];if(i)return readFileSync(i,"utf-8")}function xn(t=process.argv){let e={},n=t.indexOf("--expose");if(n!==-1&&n<t.length-1){let r=t[n+1].split(",").map(s=>s.trim()).filter(s=>s.length>0);e.expose=r.length>0?r:void 0;}return ei(t,e)}function ei(t,e){let n=t.indexOf("--webhook-port");n!==-1&&n<t.length-1&&(e.webhookPort=Number.parseInt(t[n+1],10));let r=t.indexOf("--webhook-secret");r!==-1&&r<t.length-1&&(e.webhookSecret=t[r+1]),!e.webhookSecret&&process.env.SAP_WEBHOOK_SECRET&&(e.webhookSecret=process.env.SAP_WEBHOOK_SECRET),e.webhookSecret!==void 0&&e.webhookSecret.trim().length===0&&(e.webhookSecret=void 0);let s=t.indexOf("--webhook-host");if(s!==-1&&s<t.length-1&&(e.webhookHost=t[s+1]),!e.webhookHost&&process.env.SAP_WEBHOOK_HOST&&(e.webhookHost=process.env.SAP_WEBHOOK_HOST),process.env.SAP_WEBHOOK_MAX_BODY_BYTES){let v=Number.parseInt(process.env.SAP_WEBHOOK_MAX_BODY_BYTES,10);Number.isFinite(v)&&v>0&&(e.webhookMaxBodyBytes=v);}let i=t.indexOf("--max-request-bytes");if(i!==-1&&i<t.length-1){let v=Number.parseInt(t[i+1],10);Number.isFinite(v)&&v>0&&(e.maxRequestBytes=v);}if(e.maxRequestBytes===void 0&&process.env.SAP_MCP_MAX_REQUEST_BYTES){let v=Number.parseInt(process.env.SAP_MCP_MAX_REQUEST_BYTES,10);Number.isFinite(v)&&v>0&&(e.maxRequestBytes=v);}let o=t.indexOf("--transport");if(o!==-1&&o<t.length-1){let v=t[o+1];(v==="stdio"||v==="http"||v==="sse")&&(e.transport=v);}if(e.transport===void 0){let v=process.env.SAP_MCP_TRANSPORT?.trim();(v==="stdio"||v==="http"||v==="sse")&&(e.transport=v);}let a=t.indexOf("--port");if(a!==-1&&a<t.length-1&&(e.port=Number.parseInt(t[a+1],10)),e.port===void 0&&process.env.SAP_MCP_PORT){let v=Number.parseInt(process.env.SAP_MCP_PORT,10);Number.isFinite(v)&&v>0&&v<=65535&&(e.port=v);}let d=t.indexOf("--api-key");d!==-1&&d<t.length-1&&(e.apiKey=t[d+1]);let l=["sndpor","sndprt","sndprn","rcvpor","rcvprt","rcvprn"];for(let v of l){let ee=t.indexOf(`--${v}`);ee!==-1&&ee<t.length-1&&(e[v]=t[ee+1]);}let c=t.indexOf("--catalog-v2-url");c!==-1&&c<t.length-1&&(e.catalogV2Url=t[c+1]),!e.catalogV2Url&&process.env.ODATA_V2_CATALOG_PATH&&(e.catalogV2Url=process.env.ODATA_V2_CATALOG_PATH);let u=t.indexOf("--catalog-v4-url");u!==-1&&u<t.length-1&&(e.catalogV4Url=t[u+1]);let p=t.includes("--allow-write")||process.env.SAP_MCP_ALLOW_WRITE==="true";e.readOnly=!p,e.allowWriteExplicit=p;let m=t.indexOf("--tool-timeout"),g=m!==-1&&m<t.length-1?t[m+1]:process.env.SAP_MCP_TOOL_TIMEOUT;if(g!==void 0){let v=Number.parseInt(g,10);if(!Number.isFinite(v)||v<0)throw new Error(`--tool-timeout muss eine Zahl in Sekunden sein (0 schaltet das Budget ab), war: "${g}"`);e.toolTimeoutSeconds=v;}let f=t.indexOf("--cache-dir"),h=f!==-1&&f<t.length-1?t[f+1]:process.env.SAP_CACHE_DIR;h&&h.trim().length>0&&(e.cacheDir=h.trim());let b=t.indexOf("--concurrency"),S=b!==-1&&b<t.length-1?t[b+1]:process.env.SAP_CONCURRENCY;if(S!==void 0){let v=Number.parseInt(S,10);if(!Number.isFinite(v)||v<1)throw new Error(`--concurrency muss eine Zahl >= 1 sein, war: "${S}"`);e.concurrency=v;}let y=t.indexOf("--destinations"),w=y!==-1&&y<t.length-1?t[y+1]:process.env.SAP_MCP_DESTINATIONS;w&&w.trim().length>0&&(e.destinationsFile=w.trim());let N=t.indexOf("--admin-token"),G=N!==-1&&N<t.length-1?t[N+1]:process.env.SAP_MCP_ADMIN_TOKEN;if(G&&G.trim().length>0){let v=G.trim();if(v.length<Nn)throw new Error(`--admin-token / SAP_MCP_ADMIN_TOKEN muss mindestens ${Nn} Zeichen lang sein (z. B. openssl rand -base64 32).`);e.adminToken=v;}(t.includes("--demo")||process.env.SAP_MCP_DEMO==="true")&&(e.demo=true);let Xe=t.indexOf("--help"),Ys=t.indexOf("-h");(Xe!==-1||Ys!==-1)&&(e.help=true);let Mt=t.indexOf("--tiers");if(Mt!==-1&&Mt<t.length-1){let v=t[Mt+1].split(",").map(ee=>ee.trim()).filter(ee=>ee.length>0);e.tiers=v.length>0?v:void 0;}let Ut=t.indexOf("--destination-name");Ut!==-1&&Ut<t.length-1&&(e.destinationName=t[Ut+1]),!e.destinationName&&process.env.SAP_DESTINATION_NAME&&(e.destinationName=process.env.SAP_DESTINATION_NAME);let $t=t.indexOf("--destination-service-url");$t!==-1&&$t<t.length-1&&(e.destinationServiceUrl=t[$t+1]),!e.destinationServiceUrl&&process.env.SAP_DESTINATION_SERVICE_URL&&(e.destinationServiceUrl=process.env.SAP_DESTINATION_SERVICE_URL);let Bt=t.indexOf("--issuer");Bt!==-1&&Bt<t.length-1&&(e.issuer=t[Bt+1]),!e.issuer&&process.env.SAP_ISSUER&&(e.issuer=process.env.SAP_ISSUER);let Lt=t.indexOf("--name-id-format");Lt!==-1&&Lt<t.length-1&&(e.nameIdFormat=t[Lt+1]),!e.nameIdFormat&&process.env.SAP_NAME_ID_FORMAT&&(e.nameIdFormat=process.env.SAP_NAME_ID_FORMAT);let Ft=t.indexOf("--private-key"),Js=Ft!==-1&&Ft<t.length-1?t[Ft+1]:void 0;e.privateKey=Ie(Js,"SAP_PRIVATE_KEY","SAP_PRIVATE_KEY_FILE");let Ht=t.indexOf("--certificate"),Xs=Ht!==-1&&Ht<t.length-1?t[Ht+1]:void 0;e.certificate=Ie(Xs,"SAP_CERTIFICATE","SAP_CERTIFICATE_FILE");let jt=t.indexOf("--clock-skew");jt!==-1&&jt<t.length-1&&(e.clockSkew=Number.parseInt(t[jt+1],10)),e.clockSkew===void 0&&process.env.SAP_CLOCK_SKEW&&(e.clockSkew=Number.parseInt(process.env.SAP_CLOCK_SKEW,10));let Vt=t.indexOf("--assertion-lifetime");return Vt!==-1&&Vt<t.length-1&&(e.assertionLifetime=Number.parseInt(t[Vt+1],10)),e.assertionLifetime===void 0&&process.env.SAP_ASSERTION_LIFETIME&&(e.assertionLifetime=Number.parseInt(process.env.SAP_ASSERTION_LIFETIME,10)),e}function Wt(t){return !!(t.sndprn&&t.rcvprn)}function V(t,e){return !e||e.length===0?true:e.some(n=>minimatch(t,n))}var Mn=z$1.object({tlsVerify:z$1.boolean().default(true),clientCert:z$1.string().optional(),clientKey:z$1.string().optional(),clientKeyPassphrase:z$1.string().optional(),requestTimeoutMs:z$1.number().int().positive().default(3e4),caCertPath:z$1.string().optional()}).superRefine((t,e)=>{let n=t.clientCert!==void 0,r=t.clientKey!==void 0;n!==r&&e.addIssue({code:"custom",message:n?"SAP_CLIENT_CERT/PATH set without matching SAP_CLIENT_KEY/PATH":"SAP_CLIENT_KEY/PATH set without matching SAP_CLIENT_CERT/PATH",path:["clientKey"]});});function qt(t=process.env){let e=t.SAP_TLS_VERIFY!=="false",n=t.SAP_CA_CERT?.trim();n&&!t.NODE_EXTRA_CA_CERTS&&(process.env.NODE_EXTRA_CA_CERTS=n);let r=Ie(void 0,"SAP_CLIENT_CERT","SAP_CLIENT_CERT_PATH",t),s=Ie(void 0,"SAP_CLIENT_KEY","SAP_CLIENT_KEY_PATH",t),i=t.SAP_CLIENT_KEY_PASSPHRASE,o=t.SAP_HTTP_TIMEOUT_MS!==void 0?Number.parseInt(t.SAP_HTTP_TIMEOUT_MS,10):void 0,a={tlsVerify:e,...n?{caCertPath:n}:{},...r!==void 0?{clientCert:r}:{},...s!==void 0?{clientKey:s}:{},...i!==void 0?{clientKeyPassphrase:i}:{},...o!==void 0?{requestTimeoutMs:o}:{}},d=Mn.safeParse(a);if(!d.success){let l=d.error.issues.map(c=>` - ${c.path.join(".")||"root"}: ${c.message}`).join(`
|
|
3
3
|
`);throw new Error(`Invalid HTTP options:
|
|
4
|
-
${l}`)}return c.data}function Je(t){let e={name:t.name,url:t.baseUrl,sapClient:t.sapClient};switch(t.authType){case "basic":return {...e,auth:{type:"basic",username:t.username,password:t.password}};case "user-basic":throw new Error(`Destination "${t.name}" verlangt eine pers\xF6nliche SAP-Anmeldung (authType user-basic): SAP-Benutzer und -Passwort kommen je Anfrage \xFCber X-SAP-Username/X-SAP-Password \u2014 ohne sie gibt es keinen technischen Fallback.`);case "oauth2":return {...e,auth:{type:"oauth2",clientId:t.clientId,clientSecret:t.clientSecret,tokenServiceUrl:t.tokenServiceUrl}};case "apikey":return {...e,headers:{APIKey:t.apiKey},auth:{type:"apikey",apiKey:t.apiKey}};case "ias":return {...e,auth:{type:"ias",clientId:t.clientId,clientSecret:t.clientSecret,tenantUrl:t.tenantUrl}};case "xsuaa":return {...e,auth:{type:"xsuaa",clientId:t.clientId,clientSecret:t.clientSecret,xsuaaUrl:t.xsuaaUrl}};case "btp-principal":return {...e,auth:{type:"btp-principal",destinationName:t.destinationName,destinationServiceUrl:t.destinationServiceUrl,clientId:t.clientId,clientSecret:t.clientSecret,tokenServiceUrl:t.tokenServiceUrl}};case "saml-bearer":return {...e,auth:{type:"saml-bearer",tokenServiceUrl:t.tokenServiceUrl,clientId:t.clientId,issuer:t.issuer,nameIdFormat:t.nameIdFormat,privateKey:t.privateKey,certificate:t.certificate,clockSkew:t.clockSkew,assertionLifetime:t.assertionLifetime}}}}var pe=new AsyncLocalStorage;function _n(){return randomBytes(6).toString("hex")}function me(){return pe.getStore()}function kn(t,e){return pe.run(t,e)}function Xe(){let t=pe.getStore();if(t)return t.sapCalls.count+=1,t.correlationId}function On(t=Date.now()){let e=pe.getStore();if(e?.deadline)return Math.max(0,e.deadline-t)}function Ze(){return pe.getStore()?.signal?.aborted??false}var B=class extends Error{timedOut;constructor(e,n){super(e?`Zeitbudget f\xFCr ${n??"den Tool-Aufruf"} \xFCberschritten \u2014 abgebrochen.`:`Client hat die Verbindung verworfen \u2014 ${n??"der Tool-Aufruf"} wurde abgebrochen.`),this.name="ToolCallAbortedError",this.timedOut=e;}};function Qe(){let t=pe.getStore();if(!t?.signal?.aborted)return;let e=t.signal.reason?.name==="TimeoutError";throw new B(e,t.toolName)}function In(t,e){if(!e||e<=0)return {signal:t,deadline:void 0};let n=AbortSignal.timeout(e),r=Date.now()+e;return t?{signal:AbortSignal.any([t,n]),deadline:r}:{signal:n,deadline:r}}function fe(t){if(t==null)return {code:"UNKNOWN",message:"Unknown SAP error",details:[],rawResponse:t};if(typeof t=="string")return {code:"UNKNOWN",message:t,details:[],rawResponse:t};let n=t.error;if(!n)return {code:"UNKNOWN",message:"Unknown SAP error",details:[],rawResponse:t};let r=n.message,s;r&&typeof r=="object"&&"value"in r?s=r.value??"Unknown error":typeof r=="string"?s=r:s="Unknown error";let a=(n.innererror?.errordetails??[]).map(c=>({code:c.code??"",message:c.message??"",severity:c.severity??"error",target:c.target}));return {code:n.code??"UNKNOWN",message:s,target:n.target,details:a,rawResponse:t}}function Ft(t){if(typeof t!="string")return false;let e=t.trimStart().slice(0,200).toLowerCase();return e.startsWith("<!doctype")||e.startsWith("<html")||e.includes("<title")}function Ht(t){let e=re(t).toLowerCase();return /anmeldung fehlgeschlagen|anmeldefehler|logon (error|failed)|login failed|unauthori[sz]ed/.test(e)}var jt="Benutzer und Passwort pr\xFCfen. Ist sap-client gesetzt? Ohne Mandant lehnt das Gateway die Anmeldung ebenfalls ab. Bei pers\xF6nlicher Anmeldung (user-basic): SAP-Benutzer/-Passwort im n8n-Credential pr\xFCfen \u2014 und nicht mehrfach probieren, SAP sperrt die Kennung nach wenigen Fehlversuchen.";function Gs(t){return /no authorization for operation\s+\d+\s+on object/i.test(t)}var Ys="SAP-KBA 3421507 nennt daf\xFCr zwei Ursachen, und keine ist die Berechtigung: (1) der Objektschl\xFCssel ist falsch formatiert \u2014 er geht zehnstellig mit f\xFChrenden Nullen hinaus, z. B. 0000012345 statt 12345; (2) der BusinessObjectTypeName passt nicht zum Objekt (Kundenauftrag: BUS2032). Dritte M\xF6glichkeit: Kopfzeile gegen Abfrageparameter verwechselt \u2014 beim schreibenden POST sind es Kopfzeilen, beim lesenden Aufruf Abfrageparameter. Erst danach lohnt der Blick in PFCG/SU53.";function re(t){let e=/<title[^>]*>([^<]{1,200})<\/title>/i.exec(t)?.[1]?.trim();return e||t.replace(/<script[\s\S]*?<\/script>/gi," ").replace(/<style[\s\S]*?<\/style>/gi," ").replace(/<[^>]+>/g," ").replace(/\s+/g," ").trim().slice(0,200)||"HTML-Seite ohne verwertbaren Text"}function Js(t){return /\/IW[A-Z]+\/[A-Z_]+\/\d+/i.exec(t)?.[0]}var Xs=new Set(["UNABLE_TO_GET_ISSUER_CERT_LOCALLY","UNABLE_TO_VERIFY_LEAF_SIGNATURE","SELF_SIGNED_CERT_IN_CHAIN","DEPTH_ZERO_SELF_SIGNED_CERT","CERT_UNTRUSTED"]);function Zs(t){let e=t?.code;if(typeof e=="string")return e;let n=t?.cause;if(typeof n?.code=="string")return n.code}function Qs(t){let n=(t?.cause?.cert??t?.cert)?.issuer?.CN;return typeof n=="string"?n:void 0}function ge(t,e){if(t instanceof B)return {code:t.timedOut?"TOOL_TIMEOUT":"ABORTED",message:t.message};let n=Zs(t);if(n&&Xs.has(n)){let i=Qs(t);return {code:"TLS_CHAIN_INCOMPLETE",message:i?`Die Zertifikatskette ist nicht vollst\xE4ndig verifizierbar (${n}). Aussteller: ${i}.`:`Die Zertifikatskette ist nicht vollst\xE4ndig verifizierbar (${n}).`,guidance:"Root-CA \xFCber SAP_CA_CERT=<pfad> einbinden (intern NODE_EXTRA_CA_CERTS). Welches Zertifikat fehlt, zeigt `test-connection` (Stufe TLS) oder `openssl s_client -connect <host>:<port> -showcerts`. SAP_TLS_VERIFY=false ist kein Ersatz \u2014 es schaltet die Pr\xFCfung nur ab."}}if(n==="ENOTFOUND"||n==="EAI_AGAIN")return {code:"DNS_FAILED",message:`Der SAP-Host ist nicht aufl\xF6sbar (${n}).`,guidance:"SAP_BASE_URL pr\xFCfen. Im Container zus\xE4tzlich die DNS-Konfiguration."};if(n==="ECONNREFUSED")return {code:"CONNECTION_REFUSED",message:"Der SAP-Host nimmt keine Verbindung auf dem angegebenen Port an.",guidance:"Port in SAP_BASE_URL pr\xFCfen (HTTPS-Port des ICM, oft 44300 oder 8443)."};if(n==="ECONNABORTED"||n==="ETIMEDOUT")return {code:"TIMEOUT",message:"Das SAP-System hat nicht rechtzeitig geantwortet.",guidance:"Anfrage eingrenzen (top/skip, filter) oder SAP_HTTP_TIMEOUT_MS erh\xF6hen \u2014 aber unterhalb der kleinsten Client-Grenze bleiben."};if(isAxiosError(t)&&t.response)return ei(t.response.status,t.response.data,e);let r=t;if(typeof r?.message=="string"&&r.message.length>0)return Ft(r.message)?Ht(r.message)?{code:"AUTH_FAILED",message:`Anmeldung abgelehnt (SAP-Anmeldeseite: ${re(r.message)}).`,guidance:jt}:{code:"HTML_RESPONSE",message:`SAP hat eine HTML-Seite statt Daten geliefert: ${re(r.message)}`,guidance:"Meist eine abgelehnte Anmeldung oder ein ung\xFCltiger Pfad. Ist sap-client gesetzt? Ist der Service in /IWFND/MAINT_SERVICE aktiviert?"}:{code:typeof r.code=="string"?r.code:"ERROR",message:r.message};if(t instanceof Error)return Ft(t.message)?Ht(t.message)?{code:"AUTH_FAILED",message:`Anmeldung abgelehnt (SAP-Anmeldeseite: ${re(t.message)}).`,guidance:jt}:{code:"HTML_RESPONSE",message:`SAP hat eine HTML-Seite statt Daten geliefert: ${re(t.message)}`,guidance:"Meist eine abgelehnte Anmeldung oder ein ung\xFCltiger Pfad. Ist sap-client gesetzt? Ist der Service in /IWFND/MAINT_SERVICE aktiviert?"}:{code:"ERROR",message:t.message};if(typeof t=="string")return {code:"ERROR",message:t};let s=t===void 0?void 0:JSON.stringify(t);return {code:"ERROR",message:s?`Unerwarteter Fehler ohne Meldung: ${s}`:"Der Aufruf schlug fehl, ohne einen Fehler zu hinterlassen. Das Serverlog zeigt unter derselben Korrelations-ID, welche SAP-Aufrufe beteiligt waren."}}function ei(t,e,n){let r=typeof e=="string"?e:JSON.stringify(e??""),s=Js(r);if(s?.toUpperCase().includes("/IWFND/MED/170")){let o=n?.serviceUrl?`Service ${n.serviceUrl}`:"Der Service";return {code:"SERVICE_NOT_REGISTERED",httpStatus:t,sapCode:s,message:`${o} ist im Gateway nicht registriert.`,guidance:"Aktivierung in Transaktion /IWFND/MAINT_SERVICE n\xF6tig. Das ist kein Berechtigungsproblem \u2014 PFCG und SU53 f\xFChren hier nicht weiter."}}if(Gs(r))return {code:"MEDIA_OBJECT_MISMATCH",httpStatus:t,...s?{sapCode:s}:{},message:Dn(e)??"SAP meldet fehlende Berechtigung f\xFCr das Objekt \u2014 meist ist es aber der Objektschl\xFCssel oder der Objekttyp.",guidance:Ys};if(t===401)return {code:"AUTH_FAILED",httpStatus:401,...s?{sapCode:s}:{},message:"Anmeldung abgelehnt.",guidance:'Ist sap-client gesetzt? Ohne Mandant antwortet das Gateway mit 401 \u201EAnmeldung fehlgeschlagen" statt mit einer Mandantenmeldung. Sonst Benutzer und Passwort pr\xFCfen.'};if(t===403)return {code:"FORBIDDEN",httpStatus:403,...s?{sapCode:s}:{},message:Dn(e)??"Zugriff verweigert.",guidance:"Berechtigungen in PFCG pr\xFCfen, Trace \xFCber SU53. Bei OData-Services zus\xE4tzlich das Objekt S_SERVICE."};if(t===404)return {code:"NOT_FOUND",httpStatus:404,...s?{sapCode:s}:{},message:n?.serviceUrl?`Unter ${n.serviceUrl} liegt nichts.`:"Der angeforderte Pfad existiert auf diesem System nicht.",guidance:"Pfad \xFCber sap_discover_services pr\xFCfen. Ein Service kann existieren, aber nicht aktiviert sein (/IWFND/MAINT_SERVICE)."};if(Ft(e))return Ht(e)?{code:"AUTH_FAILED",httpStatus:t,message:`Anmeldung abgelehnt (SAP-Anmeldeseite, HTTP ${t}: ${re(e)}).`,guidance:jt}:{code:"HTML_RESPONSE",httpStatus:t,message:`SAP hat eine HTML-Fehlerseite geliefert (HTTP ${t}): ${re(e)}`,guidance:"Der Pfad wird von SAP nicht als OData-Service beantwortet. Bei Katalogpfaden sap_discover_services statt sap_query verwenden."};let i=fe(e);return {code:i.code&&i.code!=="UNKNOWN"?i.code:`HTTP_${t}`,httpStatus:t,...s?{sapCode:s}:{},message:i.message&&i.message!=="Unknown error"&&i.message!=="Unknown SAP error"?i.message:`SAP antwortete mit HTTP ${t} ohne verwertbare Fehlermeldung.`,...i.details.length>0?{guidance:i.details.map(o=>`${o.code}: ${o.message}`).join(" | ")}:{}}}function Dn(t){let e=fe(t);if(e.message&&!(e.message==="Unknown error"||e.message==="Unknown SAP error"))return e.message}var ri=5e3;function Nn(t){let e=t?.issuer?.CN??t?.issuer?.O;return Array.isArray(e)?e.join(", ")||void 0:typeof e=="string"?e:void 0}function k(t,e,n,r,s){return {stage:t,status:n,detail:r,durationMs:Date.now()-e,...s?.guidance?{guidance:s.guidance}:{},...s?.info?{info:s.info}:{}}}function si(t){try{let e=new URL(t),n=e.protocol==="https:";return {host:e.hostname,port:e.port?Number(e.port):n?443:80,isTls:n}}catch{return}}async function ii(t,e){let n=Date.now();try{let{address:r,family:s}=await e(t);return k("dns",n,"ok",`${t} l\xF6st auf ${r} auf.`,{info:{address:r,family:s}})}catch(r){let s=ge(r);return k("dns",n,"failed",`${t} ist nicht aufl\xF6sbar.`,{guidance:s.guidance??"SAP_BASE_URL pr\xFCfen. Im Container zus\xE4tzlich die DNS-Konfiguration des Docker-Netzes."})}}async function oi(t,e,n,r,s){let i=Date.now(),o=a=>new Promise((c,l)=>{let d=s({host:t,port:e,rejectUnauthorized:a,servername:t},()=>{let u=d.getPeerCertificate(true);d.destroy(),c(u);});d.setTimeout(r,()=>{d.destroy(),l(Object.assign(new Error(`TLS-Handshake nach ${r} ms ohne Antwort`),{code:"ETIMEDOUT"}));}),d.on("error",u=>{d.destroy(),l(u);});});try{let a=await o(n),c=Nn(a);return k("tls",i,"ok",n?`TLS-Verbindung steht, Zertifikatskette ist verifizierbar.${c?` Aussteller: ${c}.`:""}`:`TLS-Verbindung steht. Die Zertifikatspr\xFCfung ist \xFCber SAP_TLS_VERIFY=false abgeschaltet \u2014 die Kette wurde NICHT verifiziert.${c?` Aussteller: ${c}.`:""}`,{info:{verified:n,...c?{issuer:c}:{},...a?.subject?.CN?{subject:String(a.subject.CN)}:{},...a?.valid_to?{validTo:a.valid_to}:{}}})}catch(a){let c=ge(a);if(c.code==="TLS_CHAIN_INCOMPLETE"){let l;try{let d=await o(!1);l=Nn(d);}catch{}return k("tls",i,"failed",l?`Die Zertifikatskette ist nicht vollst\xE4ndig verifizierbar. Aussteller des Serverzertifikats: ${l}.`:"Die Zertifikatskette ist nicht vollst\xE4ndig verifizierbar.",{guidance:c.guidance,...l?{info:{issuer:l}}:{}})}return k("tls",i,"failed",c.message,{guidance:c.guidance})}}async function ai(t,e){let n=Date.now();try{let r=await t.request({method:"get",url:"/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/",headers:{Accept:"application/json"},params:{$format:"json"}});return [k("auth",n,"ok","Anmeldung am Gateway erfolgreich."),k("client",n,"ok",e?`Mandant ${e} wird an jede Anfrage geh\xE4ngt und vom Gateway angenommen.`:"Kein SAP_CLIENT konfiguriert \u2014 das System hat die Anfrage dennoch angenommen. Auf mandantenabh\xE4ngigen Systemen sollte er gesetzt sein.",{info:{sapClient:e??null}}),k("catalog",n,"ok",`Servicekatalog erreichbar (HTTP ${r.status}).`)]}catch(r){let s=ge(r),i=s.httpStatus===401||s.code==="AUTH_FAILED";return i&&!e?[k("auth",n,"ok","Zugangsdaten wurden gesendet."),k("client",n,"failed",'Das Gateway lehnt die Anmeldung ab, und SAP_CLIENT ist nicht gesetzt. Gemessen antwortet das Gateway ohne sap-client mit 401 \u201EAnmeldung fehlgeschlagen" statt mit einer Mandantenmeldung.',{guidance:"SAP_CLIENT setzen (z. B. 100) und erneut pr\xFCfen. Bleibt der 401 bestehen, liegt es an Benutzer oder Passwort."}),k("catalog",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor.")]:i?[k("auth",n,"failed","Anmeldung abgelehnt.",{guidance:`Benutzer und Passwort pr\xFCfen. Mandant ${e} wird gesendet \u2014 bei Zweifeln in SU01 pr\xFCfen, ob der Benutzer in diesem Mandanten existiert und nicht gesperrt ist.`}),k("client",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor."),k("catalog",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor.")]:[k("auth",n,"ok","Anmeldung am Gateway erfolgreich."),k("client",n,"ok",e?`Mandant ${e} angenommen.`:"Kein Mandant konfiguriert."),k("catalog",n,"failed",s.message,{guidance:s.guidance??"Katalogdienst in /IWFND/MAINT_SERVICE pr\xFCfen. Der Server probiert drei bekannte Katalogpfade durch."})]}}async function tt(t,e){let n=Date.now(),r=e.budgetMs??ri,s=e.deps?.lookup??lookup,i=e.deps?.connectTls??connect,o=[],a=si(e.baseUrl);if(!a)return o.push(k("dns",n,"failed",`SAP_BASE_URL ist keine g\xFCltige URL: "${e.baseUrl}"`,{guidance:"Erwartet wird etwa https://host.example.com:44300 \u2014 mit Schema und ohne Pfad."}),...["tls","auth","client","catalog"].map(c=>k(c,n,"skipped","Nicht gepr\xFCft \u2014 die Basis-URL ist unbrauchbar."))),et(o,n);if(o.push(await ii(a.host,s)),o[0].status==="failed")return o.push(...["tls","auth","client","catalog"].map(c=>k(c,n,"skipped","Nicht gepr\xFCft \u2014 der Name war nicht aufl\xF6sbar."))),et(o,n);if(a.isTls){let c=Math.max(500,r-(Date.now()-n));if(o.push(await oi(a.host,a.port,e.tlsVerify!==false,c,i)),o[o.length-1].status==="failed")return o.push(...["auth","client","catalog"].map(l=>k(l,n,"skipped","Nicht gepr\xFCft \u2014 die TLS-Stufe scheiterte zuvor."))),et(o,n)}else o.push(k("tls",n,"skipped","Nicht gepr\xFCft \u2014 SAP_BASE_URL verwendet http, nicht https."));return o.push(...await ai(t,e.sapClient)),et(o,n)}function et(t,e){let n=t.find(r=>r.status==="failed");return {status:n?"failed":"connected",...n?{failedStage:n.stage}:{},stages:t,durationMs:Date.now()-e,timestamp:new Date().toISOString()}}var he=class{constructor(e,n,r,s=60){this.tokenUrl=e;this.clientId=n;this.clientSecret=r;this.bufferSeconds=s;}cachedToken=null;expiresAt=0;pendingFetch=null;async getToken(e){if(this.cachedToken&&Date.now()<this.expiresAt)return this.cachedToken;if(this.pendingFetch)return this.pendingFetch;this.pendingFetch=this.fetchToken();try{return await this.pendingFetch}finally{this.pendingFetch=null;}}async fetchToken(){let e=await Wt.post(this.tokenUrl,new URLSearchParams({grant_type:"client_credentials"}),{auth:{username:this.clientId,password:this.clientSecret},headers:{"Content-Type":"application/x-www-form-urlencoded"}}),{access_token:n,expires_in:r}=e.data;return this.cachedToken=n,this.expiresAt=Date.now()+(r-this.bufferSeconds)*1e3,n}};function Vt(t){return createHash("sha256").update(t).digest("hex")}var De=class{constructor(e=100){this.maxSize=e;}cache=new Map;get(e){let n=this.cache.get(e);return n?Date.now()>=n.expiresAt?(this.cache.delete(e),null):(this.cache.delete(e),this.cache.set(e,n),n.token):null}set(e,n,r){if(this.cache.has(e))this.cache.delete(e);else if(this.cache.size>=this.maxSize){let s=this.cache.keys().next().value;s!==void 0&&this.cache.delete(s);}this.cache.set(e,{token:n,expiresAt:r});}},nt=class{clientTokenManager;cache;config;constructor(e){this.config=e,this.clientTokenManager=new he(e.tokenServiceUrl,e.clientId,e.clientSecret),this.cache=new De;}async getToken(e){if(!e)return this.clientTokenManager.getToken();let n=Vt(e),r=this.cache.get(n);return r||this.exchangeToken(e,n)}async exchangeToken(e,n){let r=await this.clientTokenManager.getToken(),s=`${this.config.destinationServiceUrl}/destination-configuration/v1/destinations/${this.config.destinationName}`,o=(await Wt.get(s,{headers:{Authorization:`Bearer ${r}`,"X-user-token":e}})).data.authTokens;if(!o?.length)throw new Error(`Destination '${this.config.destinationName}': no authTokens in response`);let a=o[0];if(a.error)throw new Error(`Destination '${this.config.destinationName}' token exchange failed: ${a.error}`);let c=parseInt(a.expires_in,10),l=Number.isFinite(c)&&c>0?c:3600,d=Date.now()+(l-60)*1e3;return this.cache.set(n,a.value,d),a.value}};var Ne=class{constructor(e,n){this.config=e;this.discoveryClient=n;this.bufferSeconds=e.bufferSeconds??60;}cachedToken=null;expiresAt=0;pendingFetch=null;jwks=null;bufferSeconds;async getToken(e){if(this.cachedToken&&Date.now()<this.expiresAt)return this.cachedToken;if(this.pendingFetch)return this.pendingFetch;this.pendingFetch=this.fetchAndValidateToken();try{return await this.pendingFetch}finally{this.pendingFetch=null;}}async fetchAndValidateToken(){let e=await this.discoveryClient.discover(this.config.issuerUrl),n=await Wt.post(e.token_endpoint,new URLSearchParams({grant_type:"client_credentials"}),{auth:{username:this.config.clientId,password:this.config.clientSecret},headers:{"Content-Type":"application/x-www-form-urlencoded"}}),{access_token:r,expires_in:s}=n.data;return await this.validateJwt(r,e.jwks_uri,e.issuer),this.cachedToken=r,this.expiresAt=Date.now()+(s-this.bufferSeconds)*1e3,r}async validateJwt(e,n,r){this.jwks||(this.jwks=createRemoteJWKSet(new URL(n)));let{payload:s}=await jwtVerify(e,this.jwks,{issuer:r});return s}};var se=class{constructor(e=36e5){this.ttlMs=e;}cache=new Map;async discover(e){let n=e.replace(/\/+$/,""),r=this.cache.get(n);if(r&&Date.now()<r.expiresAt)return r.document;let s=`${n}/.well-known/openid-configuration`,o=(await Wt.get(s,{timeout:1e4,headers:{Accept:"application/json"}})).data;if(!o.token_endpoint||!o.jwks_uri)throw new Error(`OIDC discovery document at ${s} is missing required fields (token_endpoint, jwks_uri)`);return this.cache.set(n,{document:o,expiresAt:Date.now()+this.ttlMs}),o}clearCache(){this.cache.clear();}};var bi={email:"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",sapUsername:"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"};function xn(t,e){if(!t.includes("-----BEGIN")||!t.includes("-----END"))throw new Error(`Invalid PEM format for ${e}. Expected PEM-encoded content starting with -----BEGIN`)}function Ti(t){return `<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="${t.assertionId}" IssueInstant="${t.authnInstant}" Version="2.0"><saml:Issuer>${t.issuer}</saml:Issuer><saml:Subject><saml:NameID Format="${t.nameIdFormat}">${t.nameId}</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="${t.notOnOrAfter}" Recipient="${t.recipient}"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="${t.notBefore}" NotOnOrAfter="${t.notOnOrAfter}"><saml:AudienceRestriction><saml:Audience>${t.audience}</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="${t.authnInstant}"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement></saml:Assertion>`}function Ai(t,e,n){let r=new SignedXml({privateKey:e,publicCert:n});return r.signatureAlgorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",r.canonicalizationAlgorithm="http://www.w3.org/2001/10/xml-exc-c14n#",r.addReference({xpath:"//*[local-name(.)='Assertion']",digestAlgorithm:"http://www.w3.org/2001/04/xmlenc#sha256",transforms:["http://www.w3.org/2000/09/xmldsig#enveloped-signature","http://www.w3.org/2001/10/xml-exc-c14n#"]}),r.computeSignature(t,{location:{reference:"//*[local-name(.)='Issuer']",action:"after"}}),r.getSignedXml()}var rt=class{cache;config;constructor(e){xn(e.privateKey,"private key"),xn(e.certificate,"certificate"),this.config={...e,clockSkew:e.clockSkew??120,assertionLifetime:e.assertionLifetime??300},this.cache=new De;}async getToken(e){if(!e)throw new Error("saml-bearer auth requires a user token for principal propagation");let n=Vt(e),r=this.cache.get(n);return r||this.exchangeToken(e,n)}resolveNameId(e,n){return n==="email"?e.email??e.sub??"":e.sub??""}async exchangeToken(e,n){let r=decodeJwt(e),s=Date.now(),i=this.resolveNameId({sub:r.sub,email:r.email},this.config.nameIdFormat),o=bi[this.config.nameIdFormat],a=new Date(s-this.config.clockSkew*1e3).toISOString(),c=new Date(s+this.config.assertionLifetime*1e3).toISOString(),l=new Date(s).toISOString(),d=Ti({assertionId:`_${randomUUID()}`,issuer:this.config.issuer,nameId:i,nameIdFormat:o,recipient:this.config.tokenServiceUrl,audience:this.config.clientId,notBefore:a,notOnOrAfter:c,authnInstant:l}),u=Ai(d,this.config.privateKey,this.config.certificate),m=Buffer.from(u).toString("base64"),p=await Wt.post(this.config.tokenServiceUrl,new URLSearchParams({grant_type:"urn:ietf:params:oauth:grant-type:saml2-bearer",assertion:m,client_id:this.config.clientId}).toString(),{headers:{"Content-Type":"application/x-www-form-urlencoded"}}),{access_token:g,expires_in:f}=p.data,h=typeof f=="number"&&f>0?f:3600,S=Date.now()+(h-60)*1e3;return this.cache.set(n,g,S),g}};function Mn(t){if(t.tlsVerify===false||t.clientCert!==void 0)return new Agent({rejectUnauthorized:t.tlsVerify!==false,...t.clientCert?{cert:t.clientCert}:{},...t.clientKey?{key:t.clientKey}:{},...t.clientKeyPassphrase?{passphrase:t.clientKeyPassphrase}:{}})}var Ei=3e4,Y=vi({name:"sap-http-client",transport:{target:"pino/file",options:{destination:2}}});function Ci(t,e){return typeof t=="string"?t.trimStart().startsWith("<!DOCTYPE"):Buffer.isBuffer(t)?String(e?.["content-type"]??"").includes("text/html")?true:t.subarray(0,32).toString("latin1").trimStart().startsWith("<!DOCTYPE"):false}function Ri(t){return t.startsWith("http://")||t.startsWith("https://")}function Pi(t){let e=t.replace(/\/+$/,"");try{let n=new URL(e),r=n.pathname.replace(/\/+$/,"");return {origin:`${n.protocol}//${n.host}`,path:r}}catch{return {origin:e,path:""}}}var ye=class{constructor(e,n={}){this.destination=e;switch(this.httpsAgent=Mn(n),this.requestTimeoutMs=n.requestTimeoutMs??Ei,e.auth.type){case "oauth2":this.tokenManager=new he(e.auth.tokenServiceUrl,e.auth.clientId,e.auth.clientSecret);break;case "ias":this.tokenManager=new Ne({issuerUrl:e.auth.tenantUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new se);break;case "xsuaa":this.tokenManager=new Ne({issuerUrl:e.auth.xsuaaUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new se);break;case "btp-principal":this.tokenManager=new nt({destinationName:e.auth.destinationName,destinationServiceUrl:e.auth.destinationServiceUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret,tokenServiceUrl:e.auth.tokenServiceUrl});break;case "saml-bearer":this.tokenManager=new rt({tokenServiceUrl:e.auth.tokenServiceUrl,clientId:e.auth.clientId,issuer:e.auth.issuer,nameIdFormat:e.auth.nameIdFormat,privateKey:e.auth.privateKey,certificate:e.auth.certificate,clockSkew:e.auth.clockSkew,assertionLifetime:e.auth.assertionLifetime});break;default:this.tokenManager=null;}}tokenManager;userContext=null;httpsAgent;requestTimeoutMs;async request(e){Qe();let n=e.method!=="get",r=me(),s=Xe(),i={...e.params};this.destination.sapClient&&(i["sap-client"]=this.destination.sapClient);let o=e.userContext??this.userContext??void 0,a=await this.buildAuthHeaders(o),c=this.destination.headers??{},l={};if(n){let p=await this.fetchCsrfToken({...c,...a},e.url);p.token&&(l={"x-csrf-token":p.token},p.cookies&&(l.Cookie=p.cookies));}let d={...c,...a,...l,...e.headers},u={method:e.method,url:this.resolveUrl(e.url),data:e.data,headers:d,params:Object.keys(i).length>0?i:void 0,...e.responseType?{responseType:e.responseType}:{},httpsAgent:this.httpsAgent,timeout:this.effectiveTimeoutMs(),signal:r?.signal,beforeRedirect:p=>{this.httpsAgent&&(p.agent=this.httpsAgent);let g=p.headers??{};for(let[f,h]of Object.entries(d))g[f]||(g[f]=h);}},m={...u.headers};m.Authorization&&(m.Authorization=`${m.Authorization.substring(0,15)}***`),Y.info({...s?{correlationId:s,tool:r?.toolName}:{},method:u.method,url:u.url,headers:m,params:u.params,hasAuth:!!u.headers.Authorization||!!u.headers.APIKey},"SAP HTTP request outgoing");try{let p=await Wt(u);if(Y.info({...s?{correlationId:s}:{},status:p.status,contentType:p.headers["content-type"]},"SAP HTTP response OK"),Ci(p.data,p.headers))throw new Error("SAP returned an HTML page instead of JSON. This usually means authentication failed or the URL path is invalid.");return {data:p.data,headers:p.headers,status:p.status}}catch(p){if(isAxiosError(p)&&p.response){let g=e.expectedStatuses?.includes(p.response.status)??false;(g?Y.info.bind(Y):Y.error.bind(Y))({...s?{correlationId:s}:{},status:p.response.status,statusText:p.response.statusText,contentType:p.response.headers?.["content-type"],url:u.url,...g?{expected:true}:{},dataPreview:typeof p.response.data=="string"?p.response.data.substring(0,200):typeof p.response.data},g?"SAP HTTP response (expected)":"SAP HTTP response ERROR");}else p instanceof Error&&Y.error({message:p.message,name:p.name},"SAP HTTP connection error");if(n&&this.isCsrf403(p)){Qe();try{let g=await this.fetchCsrfToken({...c,...a},e.url),f={...u.headers};g.token&&(f["x-csrf-token"]=g.token,g.cookies&&(f.Cookie=g.cookies)),Xe();let h=await Wt({...u,headers:f});return {data:h.data,headers:h.headers,status:h.status}}catch(g){throw this.toSapError(g)}}throw this.toSapError(p)}}async buildAuthHeaders(e){switch(this.destination.auth.type){case "basic":return {Authorization:`Basic ${Buffer.from(`${this.destination.auth.username}:${this.destination.auth.password}`).toString("base64")}`};case "saml-bearer":case "btp-principal":case "oauth2":case "ias":case "xsuaa":return {Authorization:`Bearer ${await this.tokenManager.getToken(e?.token)}`};case "apikey":return {APIKey:this.destination.auth.apiKey};default:return {}}}resolveUrl(e){if(Ri(e))return e;let{origin:n,path:r}=Pi(this.destination.url);return r&&(e===r||e.startsWith(`${r}/`))?`${n}${e}`:`${this.destination.url}${e}`}async fetchCsrfToken(e,n){let r=this.destination.url,s=[];if(n){let i=n.replace(/\/[^/]*$/,"/");i!=="/"&&i!==n&&s.push(this.resolveUrl(i));}s.push(`${r}/`,r);for(let i of s){Qe(),Xe();try{let o={...e,"x-csrf-token":"fetch"},a={};this.destination.sapClient&&(a["sap-client"]=this.destination.sapClient);let c=await Wt.get(i,{headers:o,params:Object.keys(a).length>0?a:void 0,validateStatus:()=>!0,timeout:this.effectiveTimeoutMs(),signal:me()?.signal,httpsAgent:this.httpsAgent,beforeRedirect:d=>{this.httpsAgent&&(d.agent=this.httpsAgent);let u=d.headers??{};for(let[m,p]of Object.entries(o))u[m]||(u[m]=p);}}),l=c.headers["x-csrf-token"];if(Y.debug({url:i,status:c.status,hasToken:!!l,tokenValue:l?.substring(0,8)},"CSRF token fetch result"),l&&l.toLowerCase()!=="required"){let u=(c.headers["set-cookie"]??[]).map(m=>m.split(";")[0]).join("; ");return {token:l,cookies:u}}}catch{}}return {token:"",cookies:""}}isCsrf403(e){return isAxiosError(e)&&e.response?.status===403}effectiveTimeoutMs(){let e=On();return e===void 0?this.requestTimeoutMs:Math.max(1,Math.min(this.requestTimeoutMs,e))}toSapError(e){if(e instanceof B)return {code:e.timedOut?"TOOL_TIMEOUT":"ABORTED",message:e.message,details:[],rawResponse:void 0};if(isAxiosError(e)&&(e.code==="ERR_CANCELED"||e.name==="CanceledError")){let n=me()?.signal?.reason?.name==="TimeoutError";return {code:n?"TOOL_TIMEOUT":"ABORTED",message:n?"Zeitbudget des Tool-Aufrufs \xFCberschritten \u2014 laufender SAP-Aufruf abgebrochen.":"Client hat die Verbindung verworfen \u2014 laufender SAP-Aufruf abgebrochen.",details:[],rawResponse:void 0}}return isAxiosError(e)&&(e.code==="ECONNABORTED"||e.code==="ETIMEDOUT")?{code:"TIMEOUT",message:`SAP request exceeded ${this.requestTimeoutMs} ms (SAP_HTTP_TIMEOUT_MS): ${e.message}`,details:[],rawResponse:void 0}:isAxiosError(e)&&e.response?fe(e.response.data):e instanceof Error?{code:"CONNECTION_ERROR",message:e.message,details:[],rawResponse:void 0}:fe(e)}};function Un(t,e){e||t.warn({sapClient:null},'SAP_CLIENT ist nicht gesetzt. Auf mandantenabh\xE4ngigen Systemen antwortet das Gateway ohne sap-client mit 401 \u201EAnmeldung fehlgeschlagen" \u2014 was wie ein falsches Passwort aussieht, aber keines ist. Bei Anmeldeproblemen zuerst SAP_CLIENT setzen (z. B. 100).');}async function $n(t,e,n,r){try{let s=new ye(Je(e),n),i=await tt(s,{baseUrl:e.baseUrl,sapClient:e.sapClient,tlsVerify:n.tlsVerify,...r?{deps:r}:{}});for(let o of i.stages){let a={stage:o.stage,status:o.status,durationMs:o.durationMs,...o.info??{},...o.guidance?{guidance:o.guidance}:{}};o.status==="failed"?t.warn(a,`Selbsttest ${o.stage}: ${o.detail}`):t.info(a,`Selbsttest ${o.stage}: ${o.detail}`);}i.status==="connected"?t.info({durationMs:i.durationMs,sapClient:e.sapClient??null},"Selbsttest bestanden: Namensaufl\xF6sung, TLS, Anmeldung, Mandant und Katalog sind in Ordnung"):t.warn({failedStage:i.failedStage,durationMs:i.durationMs},`Selbsttest gescheitert in Stufe \u201E${i.failedStage}". Der Server l\xE4uft weiter \u2014 Tool-Aufrufe werden bis zur Behebung fehlschlagen. Einzelheiten liefert das Tool test-connection.`);}catch(s){t.warn({err:s instanceof Error?s.message:String(s)},"Selbsttest konnte nicht ausgef\xFChrt werden. Der Server l\xE4uft weiter.");}}function ie(t){return `/Date(${Date.parse(t.length===10?`${t}T00:00:00Z`:t)})/`}var w=(t,e,n,r=true)=>({name:t,type:"Edm.String",maxLength:e,...n?{label:n}:{},nullable:r}),Me=(t,e,n=15,r=3)=>({name:t,type:"Edm.Decimal",precision:n,scale:r,...e?{label:e}:{}}),oe=(t,e)=>({name:t,type:"Edm.DateTime",precision:7,...e?{label:e}:{}}),Bn=(t,e)=>({name:t,type:"Edm.Boolean",...e?{label:e}:{}}),zt="/sap/opu/odata/sap",_i={technicalName:"API_BUSINESS_PARTNER",title:"Business Partner (A2X)",description:"Business partners, customers and suppliers with their addresses.",path:`${zt}/API_BUSINESS_PARTNER`,namespace:"API_BUSINESS_PARTNER",entityTypes:[{set:"A_BusinessPartner",keys:["BusinessPartner"],autoKey:{prefix:"10",width:7},properties:[w("BusinessPartner",10,"Business Partner",false),w("Customer",10,"Customer"),w("Supplier",10,"Supplier"),w("BusinessPartnerCategory",1,"BP Category"),w("BusinessPartnerFullName",81,"Full Name"),w("BusinessPartnerName",81,"Name"),w("OrganizationBPName1",40,"Organization Name 1"),w("FirstName",40,"First Name"),w("LastName",40,"Last Name"),w("BusinessPartnerGrouping",4,"Grouping"),w("SearchTerm1",20,"Search Term"),w("Industry",4,"Industry"),w("CreatedByUser",12,"Created By"),oe("CreationDate","Created On"),Bn("IsMarkedForArchiving","Archiving Flag")],navigations:[{name:"to_BusinessPartnerAddress",target:"A_BusinessPartnerAddress",multiplicity:"*",join:[["BusinessPartner","BusinessPartner"]]}]},{set:"A_BusinessPartnerAddress",keys:["BusinessPartner","AddressID"],autoKey:{prefix:"2",width:5},properties:[w("BusinessPartner",10,"Business Partner",false),w("AddressID",10,"Address ID",false),oe("ValidityStartDate","Valid From"),oe("ValidityEndDate","Valid To"),w("StreetName",60,"Street"),w("HouseNumber",10,"House Number"),w("PostalCode",10,"Postal Code"),w("CityName",40,"City"),w("Country",3,"Country"),w("Region",3,"Region"),w("Language",2,"Language")],navigations:[{name:"to_BusinessPartner",target:"A_BusinessPartner",multiplicity:"1",join:[["BusinessPartner","BusinessPartner"]]}]}],functionImports:[]},ki={technicalName:"API_SALES_ORDER_SRV",title:"Sales Order (A2X)",description:"Sales orders with items \u2014 read, create (deep insert), change, release.",path:`${zt}/API_SALES_ORDER_SRV`,namespace:"API_SALES_ORDER_SRV",entityTypes:[{set:"A_SalesOrder",keys:["SalesOrder"],autoKey:{prefix:"45",width:7},properties:[w("SalesOrder",10,"Sales Order",false),w("SalesOrderType",4,"Order Type"),w("SalesOrganization",4,"Sales Organization"),w("DistributionChannel",2,"Distribution Channel"),w("OrganizationDivision",2,"Division"),w("SoldToParty",10,"Sold-to Party"),w("PurchaseOrderByCustomer",35,"Customer Reference"),w("CreatedByUser",12,"Created By"),oe("CreationDate","Created On"),oe("LastChangeDateTime","Last Changed"),oe("RequestedDeliveryDate","Requested Delivery Date"),Me("TotalNetAmount","Net Value",16,3),w("TransactionCurrency",5,"Currency"),w("OverallSDProcessStatus",1,"Overall Status"),w("OverallDeliveryStatus",1,"Delivery Status")],navigations:[{name:"to_Item",target:"A_SalesOrderItem",multiplicity:"*",join:[["SalesOrder","SalesOrder"]]}]},{set:"A_SalesOrderItem",keys:["SalesOrder","SalesOrderItem"],properties:[w("SalesOrder",10,"Sales Order",false),w("SalesOrderItem",6,"Item",false),w("Material",40,"Material"),w("SalesOrderItemText",40,"Item Description"),w("SalesOrderItemCategory",4,"Item Category"),Me("RequestedQuantity","Order Quantity",15,3),w("RequestedQuantityUnit",3,"Unit"),Me("NetAmount","Net Value",16,3),w("TransactionCurrency",5,"Currency"),w("ProductionPlant",4,"Plant")],navigations:[{name:"to_SalesOrder",target:"A_SalesOrder",multiplicity:"1",join:[["SalesOrder","SalesOrder"]]}]}],functionImports:[{name:"ReleaseSalesOrder",httpMethod:"POST",parameters:[{name:"SalesOrder",type:"Edm.String"}],returnType:"API_SALES_ORDER_SRV.A_SalesOrderType",entitySet:"A_SalesOrder"},{name:"GetOpenSalesOrderCount",httpMethod:"GET",parameters:[{name:"SoldToParty",type:"Edm.String",nullable:true}],returnType:"Edm.Int32"}]},Oi={technicalName:"API_PRODUCT_SRV",title:"Product Master (A2X)",description:"Products (materials) with descriptions.",path:`${zt}/API_PRODUCT_SRV`,namespace:"API_PRODUCT_SRV",entityTypes:[{set:"A_Product",keys:["Product"],autoKey:{prefix:"DEMO-",width:8},properties:[w("Product",40,"Product",false),w("ProductType",4,"Product Type"),w("ProductGroup",9,"Product Group"),w("BaseUnit",3,"Base Unit"),Me("GrossWeight","Gross Weight",13,3),Me("NetWeight","Net Weight",13,3),w("WeightUnit",3,"Weight Unit"),w("CreatedByUser",12,"Created By"),oe("CreationDate","Created On"),Bn("IsMarkedForDeletion","Deletion Flag")],navigations:[{name:"to_Description",target:"A_ProductDescription",multiplicity:"*",join:[["Product","Product"]]}]},{set:"A_ProductDescription",keys:["Product","Language"],properties:[w("Product",40,"Product",false),w("Language",2,"Language",false),w("ProductDescription",40,"Description")],navigations:[]}],functionImports:[]},st=[_i,ki,Oi],qt="MCP_DEMO",Ii=[["1000001","2","Nordlicht Maschinenbau GmbH","","Hafenstra\xDFe","12","20457","Hamburg","HH","C","MACH","2024-03-04"],["1000002","2","Weberei Hartmann & S\xF6hne KG","","M\xFChlweg","3","91522","Ansbach","BY","C","TEXT","2024-03-11"],["1000003","2","Fr\xE4nkische Pr\xE4zisionstechnik AG","","Industriestra\xDFe","48","90411","N\xFCrnberg","BY","C","MACH","2024-04-02"],["1000004","2","Altm\xFChl Kunststoffwerk GmbH","","Am Kanal","7","91710","Gunzenhausen","BY","C","CHEM","2024-04-19"],["1000005","2","Seenland Logistik GmbH & Co. KG","","Frankenstra\xDFe","101","91781","Wei\xDFenburg","BY","C","TRAN","2024-05-06"],["1000006","2","Brauerei Hopfenkranz OHG","","Brauhausgasse","2","91154","Roth","BY","C","FOOD","2024-05-21"],["1000007","2","Rothsee Elektronik GmbH","","Gewerbering","15","91126","Schwabach","BY","C","ELEC","2024-06-03"],["1000008","2","Hesselberg Metallbau e.K.","","Bahnhofstra\xDFe","33","91717","Wassertr\xFCdingen","BY","C","MACH","2024-06-17"],["1000009","2","Bavaria Sensorik AG","","Technologiepark","9","91058","Erlangen","BY","C","ELEC","2024-07-01"],["1000010","2","Kastner Werkzeugbau GmbH","","Schmiedeweg","21","90762","F\xFCrth","BY","C","MACH","2024-07-15"],["1000011","2","M\xF6belwerkstatt Lindenhof GmbH","","Lindenallee","5","97070","W\xFCrzburg","BY","C","WOOD","2024-08-05"],["1000012","2","Steinbach Automotive GmbH","","Werkstra\xDFe","60","93055","Regensburg","BY","C","AUTO","2024-08-19"],["1000013","2","Tauber Verpackungen GmbH","","Ringstra\xDFe","18","91541","Rothenburg ob der Tauber","BY","C","PACK","2024-09-02"],["1000014","1","Anna","Berger","Rosenweg","4","96047","Bamberg","BY","C","","2024-09-16"],["1000015","1","Markus","Vogel","Am Stadtpark","11","89073","Ulm","BW","C","","2024-10-07"],["1000016","1","Sabine","Kr\xFCger","Elbchaussee","250","22605","Hamburg","HH","C","","2024-10-21"],["1000017","2","Ruhrstahl Handels GmbH","","Kruppstra\xDFe","77","45143","Essen","NW","S","META","2024-02-05"],["1000018","2","Elbe Chemie AG","","Chemiepark","1","01067","Dresden","SN","S","CHEM","2024-02-12"],["1000019","2","NordPack Verpackungssysteme GmbH","","Hansaring","40","28195","Bremen","HB","S","PACK","2024-02-19"],["1000020","2","Donau Kunststoffe GmbH","","Donaustra\xDFe","88","94032","Passau","BY","B","CHEM","2024-02-26"]],Di=[["PUMP-100","FERT","PUMPS","ST","42.500","39.800","Kreiselpumpe 100 l/min","Centrifugal pump 100 l/min",1890],["MOTOR-15KW","FERT","DRIVES","ST","118.000","112.500","Drehstrommotor 15 kW","Three-phase motor 15 kW",2450],["SENSOR-T200","HAWA","SENSOR","ST","0.180","0.150","Temperatursensor PT100 -50..200 \xB0C","Temperature sensor PT100 -50..200 \xB0C",64.5],["VALVE-DN50","FERT","VALVES","ST","6.400","6.100","Absperrventil DN50 PN16","Shut-off valve DN50 PN16",312],["HOUSING-AL3","HALB","PARTS","ST","2.900","2.700","Aluminiumgeh\xE4use Gr\xF6\xDFe 3","Aluminium housing size 3",148],["PLC-B200","HAWA","CONTROL","ST","1.100","0.950","Steuerung SPS B200","PLC controller B200",985],["CABLE-5M","HAWA","CABLE","ST","0.600","0.550","Anschlusskabel 5 m geschirmt","Connection cable 5 m shielded",27.9],["FILTER-F40","HAWA","FILTER","ST","0.900","0.800","Filtereinsatz F40","Filter cartridge F40",45],["SEAL-KIT-3","HAWA","PARTS","ST","0.250","0.200","Dichtungssatz Baugr\xF6\xDFe 3","Seal kit size 3",38],["BRACKET-XL","HALB","PARTS","ST","3.300","3.100","Wandhalterung XL verzinkt","Wall bracket XL galvanised",72]],Ni=[["4500001","1000001","PO-2026-0117","2026-01-08","2026-01-29","C","C",[["PUMP-100",2],["SEAL-KIT-3",4]]],["4500002","1000003","BEST-88123","2026-01-12","2026-02-05","C","C",[["MOTOR-15KW",1],["CABLE-5M",6],["PLC-B200",1]]],["4500003","1000007","RE-2026-014","2026-01-19","2026-02-10","C","C",[["SENSOR-T200",25],["CABLE-5M",25]]],["4500004","1000004","AK-4471","2026-02-02","2026-02-27","C","C",[["VALVE-DN50",8],["SEAL-KIT-3",8]]],["4500005","1000009","BS-2026-0301","2026-02-16","2026-03-13","C","B",[["SENSOR-T200",120],["HOUSING-AL3",40],["BRACKET-XL",40]]],["4500006","1000012","SA-77201","2026-03-03","2026-03-31","B","B",[["MOTOR-15KW",3],["PLC-B200",3],["CABLE-5M",12]]],["4500007","1000010","KW-2026-19","2026-03-10","2026-04-07","B","A",[["PUMP-100",1],["FILTER-F40",10]]],["4500008","1000005","SL-0209","2026-03-24","2026-04-14","B","A",[["BRACKET-XL",30],["HOUSING-AL3",12]]],["4500009","1000002","WH-2026-04","2026-04-06","2026-05-04","B","A",[["MOTOR-15KW",2],["SENSOR-T200",10]]],["4500010","1000014","","2026-04-14","2026-04-28","C","C",[["FILTER-F40",2],["SEAL-KIT-3",1]]],["4500011","1000011","ML-3350","2026-05-05","2026-06-02","A","A",[["VALVE-DN50",4],["PUMP-100",1],["CABLE-5M",4]]],["4500012","1000013","TV-2026-0888","2026-05-18","2026-06-15","A","A",[["PLC-B200",2],["SENSOR-T200",40],["CABLE-5M",40],["BRACKET-XL",8]]],["4500013","1000015","","2026-06-01","2026-06-22","A","A",[["FILTER-F40",5]]],["4500014","1000006","HK-2026-11","2026-06-15","2026-07-13","A","A",[["PUMP-100",3],["VALVE-DN50",6],["SEAL-KIT-3",6]]],["4500015","1000008","HM-0521","2026-07-01","2026-07-29","A","A",[["MOTOR-15KW",1],["HOUSING-AL3",20],["PLC-B200",1]]]];function Kt(t,e=3){return t.toFixed(e)}function Gt(){let t=[],e=[];for(let[c,l,d,u,m,p,g,f,h,S,b,y]of Ii){let A=l==="1",N=A?`${d} ${u}`:d;t.push({BusinessPartner:c,Customer:S==="C"||S==="B"?c:"",Supplier:S==="S"||S==="B"?c:"",BusinessPartnerCategory:l,BusinessPartnerFullName:N,BusinessPartnerName:N,OrganizationBPName1:A?"":d,FirstName:A?d:"",LastName:A?u:"",BusinessPartnerGrouping:A?"BP02":"BP01",SearchTerm1:(A?u:d.split(" ")[0]).toUpperCase().slice(0,20),Industry:b,CreatedByUser:qt,CreationDate:ie(y),IsMarkedForArchiving:false}),e.push({BusinessPartner:c,AddressID:String(2e4+t.length),ValidityStartDate:ie(y),ValidityEndDate:ie("9999-12-31"),StreetName:m,HouseNumber:p,PostalCode:g,CityName:f,Country:"DE",Region:h,Language:"DE"});}let n=[],r=[],s=new Map,i=new Map;Di.forEach(([c,l,d,u,m,p,g,f,h],S)=>{s.set(c,h),i.set(c,f),n.push({Product:c,ProductType:l,ProductGroup:d,BaseUnit:u,GrossWeight:m,NetWeight:p,WeightUnit:"KG",CreatedByUser:qt,CreationDate:ie(`2023-11-${String(6+S).padStart(2,"0")}`),IsMarkedForDeletion:false}),r.push({Product:c,Language:"DE",ProductDescription:g}),r.push({Product:c,Language:"EN",ProductDescription:f});});let o=[],a=[];for(let[c,l,d,u,m,p,g,f]of Ni){let h=0;f.forEach(([S,b],y)=>{let A=b*(s.get(S)??0);h+=A,a.push({SalesOrder:c,SalesOrderItem:String((y+1)*10),Material:S,SalesOrderItemText:i.get(S)??S,SalesOrderItemCategory:"TAN",RequestedQuantity:Kt(b),RequestedQuantityUnit:"ST",NetAmount:Kt(A),TransactionCurrency:"EUR",ProductionPlant:"1010"});}),o.push({SalesOrder:c,SalesOrderType:"OR",SalesOrganization:"1010",DistributionChannel:"10",OrganizationDivision:"00",SoldToParty:l,PurchaseOrderByCustomer:d,CreatedByUser:qt,CreationDate:ie(u),LastChangeDateTime:ie(`${u}T09:30:00Z`),RequestedDeliveryDate:ie(m),TotalNetAmount:Kt(h),TransactionCurrency:"EUR",OverallSDProcessStatus:p,OverallDeliveryStatus:g});}return {A_BusinessPartner:t,A_BusinessPartnerAddress:e,A_SalesOrder:o,A_SalesOrderItem:a,A_Product:n,A_ProductDescription:r}}function xi(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function Mi(t,e){return `assoc_${t.set}_${e}`}function Ln(t){let e=t.namespace,n=[],r=[],s=[];for(let a of t.entityTypes){let c=a.properties.map(d=>{let u=[`Name="${d.name}"`,`Type="${d.type}"`,`Nullable="${d.nullable===false||a.keys.includes(d.name)?"false":"true"}"`];return d.maxLength!==void 0&&u.push(`MaxLength="${d.maxLength}"`),d.precision!==void 0&&u.push(`Precision="${d.precision}"`),d.scale!==void 0&&u.push(`Scale="${d.scale}"`),d.label&&u.push(`sap:label="${xi(d.label)}"`),a.keys.includes(d.name)&&u.push('sap:creatable="true" sap:updatable="false"'),` <Property ${u.join(" ")}/>`}).join(`
|
|
5
|
-
`),l=a.navigations.map(
|
|
6
|
-
<End Type="${e}.${a.set}Type" Multiplicity="1" Role="${
|
|
7
|
-
<End Type="${e}.${
|
|
4
|
+
${l}`)}return d.data}function Ze(t){let e={name:t.name,url:t.baseUrl,sapClient:t.sapClient};switch(t.authType){case "basic":return {...e,auth:{type:"basic",username:t.username,password:t.password}};case "user-basic":throw new Error(`Destination "${t.name}" verlangt eine pers\xF6nliche SAP-Anmeldung (authType user-basic): SAP-Benutzer und -Passwort kommen je Anfrage \xFCber X-SAP-Username/X-SAP-Password \u2014 ohne sie gibt es keinen technischen Fallback.`);case "oauth2":return {...e,auth:{type:"oauth2",clientId:t.clientId,clientSecret:t.clientSecret,tokenServiceUrl:t.tokenServiceUrl}};case "apikey":return {...e,headers:{APIKey:t.apiKey},auth:{type:"apikey",apiKey:t.apiKey}};case "ias":return {...e,auth:{type:"ias",clientId:t.clientId,clientSecret:t.clientSecret,tenantUrl:t.tenantUrl}};case "xsuaa":return {...e,auth:{type:"xsuaa",clientId:t.clientId,clientSecret:t.clientSecret,xsuaaUrl:t.xsuaaUrl}};case "btp-principal":return {...e,auth:{type:"btp-principal",destinationName:t.destinationName,destinationServiceUrl:t.destinationServiceUrl,clientId:t.clientId,clientSecret:t.clientSecret,tokenServiceUrl:t.tokenServiceUrl}};case "saml-bearer":return {...e,auth:{type:"saml-bearer",tokenServiceUrl:t.tokenServiceUrl,clientId:t.clientId,issuer:t.issuer,nameIdFormat:t.nameIdFormat,privateKey:t.privateKey,certificate:t.certificate,clockSkew:t.clockSkew,assertionLifetime:t.assertionLifetime}}}}var ue=new AsyncLocalStorage;function Un(){return randomBytes(6).toString("hex")}function pe(){return ue.getStore()}function $n(t,e){return ue.run(t,e)}function Qe(){let t=ue.getStore();if(t)return t.sapCalls.count+=1,t.correlationId}function Bn(t=Date.now()){let e=ue.getStore();if(e?.deadline)return Math.max(0,e.deadline-t)}function et(){return ue.getStore()?.signal?.aborted??false}var B=class extends Error{timedOut;constructor(e,n){super(e?`Zeitbudget f\xFCr ${n??"den Tool-Aufruf"} \xFCberschritten \u2014 abgebrochen.`:`Client hat die Verbindung verworfen \u2014 ${n??"der Tool-Aufruf"} wurde abgebrochen.`),this.name="ToolCallAbortedError",this.timedOut=e;}};function tt(){let t=ue.getStore();if(!t?.signal?.aborted)return;let e=t.signal.reason?.name==="TimeoutError";throw new B(e,t.toolName)}function Ln(t,e){if(!e||e<=0)return {signal:t,deadline:void 0};let n=AbortSignal.timeout(e),r=Date.now()+e;return t?{signal:AbortSignal.any([t,n]),deadline:r}:{signal:n,deadline:r}}function me(t){if(t==null)return {code:"UNKNOWN",message:"Unknown SAP error",details:[],rawResponse:t};if(typeof t=="string")return {code:"UNKNOWN",message:t,details:[],rawResponse:t};let n=t.error;if(!n)return {code:"UNKNOWN",message:"Unknown SAP error",details:[],rawResponse:t};let r=n.message,s;r&&typeof r=="object"&&"value"in r?s=r.value??"Unknown error":typeof r=="string"?s=r:s="Unknown error";let a=(n.innererror?.errordetails??[]).map(d=>({code:d.code??"",message:d.message??"",severity:d.severity??"error",target:d.target}));return {code:n.code??"UNKNOWN",message:s,target:n.target,details:a,rawResponse:t}}function Kt(t){if(typeof t!="string")return false;let e=t.trimStart().slice(0,200).toLowerCase();return e.startsWith("<!doctype")||e.startsWith("<html")||e.includes("<title")}function zt(t){let e=ne(t).toLowerCase();return /anmeldung fehlgeschlagen|anmeldefehler|logon (error|failed)|login failed|unauthori[sz]ed/.test(e)}var Gt="Benutzer und Passwort pr\xFCfen. Ist sap-client gesetzt? Ohne Mandant lehnt das Gateway die Anmeldung ebenfalls ab. Bei pers\xF6nlicher Anmeldung (user-basic): SAP-Benutzer/-Passwort im n8n-Credential pr\xFCfen \u2014 und nicht mehrfach probieren, SAP sperrt die Kennung nach wenigen Fehlversuchen.";function si(t){return /no authorization for operation\s+\d+\s+on object/i.test(t)}var ii="SAP-KBA 3421507 nennt daf\xFCr zwei Ursachen, und keine ist die Berechtigung: (1) der Objektschl\xFCssel ist falsch formatiert \u2014 er geht zehnstellig mit f\xFChrenden Nullen hinaus, z. B. 0000012345 statt 12345; (2) der BusinessObjectTypeName passt nicht zum Objekt (Kundenauftrag: BUS2032). Dritte M\xF6glichkeit: Kopfzeile gegen Abfrageparameter verwechselt \u2014 beim schreibenden POST sind es Kopfzeilen, beim lesenden Aufruf Abfrageparameter. Erst danach lohnt der Blick in PFCG/SU53.";function ne(t){let e=/<title[^>]*>([^<]{1,200})<\/title>/i.exec(t)?.[1]?.trim();return e||t.replace(/<script[\s\S]*?<\/script>/gi," ").replace(/<style[\s\S]*?<\/style>/gi," ").replace(/<[^>]+>/g," ").replace(/\s+/g," ").trim().slice(0,200)||"HTML-Seite ohne verwertbaren Text"}function oi(t){return /\/IW[A-Z]+\/[A-Z_]+\/\d+/i.exec(t)?.[0]}var ai=new Set(["UNABLE_TO_GET_ISSUER_CERT_LOCALLY","UNABLE_TO_VERIFY_LEAF_SIGNATURE","SELF_SIGNED_CERT_IN_CHAIN","DEPTH_ZERO_SELF_SIGNED_CERT","CERT_UNTRUSTED"]);function ci(t){let e=t?.code;if(typeof e=="string")return e;let n=t?.cause;if(typeof n?.code=="string")return n.code}function di(t){let n=(t?.cause?.cert??t?.cert)?.issuer?.CN;return typeof n=="string"?n:void 0}function fe(t,e){if(t instanceof B)return {code:t.timedOut?"TOOL_TIMEOUT":"ABORTED",message:t.message};let n=ci(t);if(n&&ai.has(n)){let i=di(t);return {code:"TLS_CHAIN_INCOMPLETE",message:i?`Die Zertifikatskette ist nicht vollst\xE4ndig verifizierbar (${n}). Aussteller: ${i}.`:`Die Zertifikatskette ist nicht vollst\xE4ndig verifizierbar (${n}).`,guidance:"Root-CA \xFCber SAP_CA_CERT=<pfad> einbinden (intern NODE_EXTRA_CA_CERTS). Welches Zertifikat fehlt, zeigt `test-connection` (Stufe TLS) oder `openssl s_client -connect <host>:<port> -showcerts`. SAP_TLS_VERIFY=false ist kein Ersatz \u2014 es schaltet die Pr\xFCfung nur ab."}}if(n==="ENOTFOUND"||n==="EAI_AGAIN")return {code:"DNS_FAILED",message:`Der SAP-Host ist nicht aufl\xF6sbar (${n}).`,guidance:"SAP_BASE_URL pr\xFCfen. Im Container zus\xE4tzlich die DNS-Konfiguration."};if(n==="ECONNREFUSED")return {code:"CONNECTION_REFUSED",message:"Der SAP-Host nimmt keine Verbindung auf dem angegebenen Port an.",guidance:"Port in SAP_BASE_URL pr\xFCfen (HTTPS-Port des ICM, oft 44300 oder 8443)."};if(n==="ECONNABORTED"||n==="ETIMEDOUT")return {code:"TIMEOUT",message:"Das SAP-System hat nicht rechtzeitig geantwortet.",guidance:"Anfrage eingrenzen (top/skip, filter) oder SAP_HTTP_TIMEOUT_MS erh\xF6hen \u2014 aber unterhalb der kleinsten Client-Grenze bleiben."};if(isAxiosError(t)&&t.response)return li(t.response.status,t.response.data,e);let r=t;if(typeof r?.message=="string"&&r.message.length>0)return Kt(r.message)?zt(r.message)?{code:"AUTH_FAILED",message:`Anmeldung abgelehnt (SAP-Anmeldeseite: ${ne(r.message)}).`,guidance:Gt}:{code:"HTML_RESPONSE",message:`SAP hat eine HTML-Seite statt Daten geliefert: ${ne(r.message)}`,guidance:"Meist eine abgelehnte Anmeldung oder ein ung\xFCltiger Pfad. Ist sap-client gesetzt? Ist der Service in /IWFND/MAINT_SERVICE aktiviert?"}:{code:typeof r.code=="string"?r.code:"ERROR",message:r.message};if(t instanceof Error)return Kt(t.message)?zt(t.message)?{code:"AUTH_FAILED",message:`Anmeldung abgelehnt (SAP-Anmeldeseite: ${ne(t.message)}).`,guidance:Gt}:{code:"HTML_RESPONSE",message:`SAP hat eine HTML-Seite statt Daten geliefert: ${ne(t.message)}`,guidance:"Meist eine abgelehnte Anmeldung oder ein ung\xFCltiger Pfad. Ist sap-client gesetzt? Ist der Service in /IWFND/MAINT_SERVICE aktiviert?"}:{code:"ERROR",message:t.message};if(typeof t=="string")return {code:"ERROR",message:t};let s=t===void 0?void 0:JSON.stringify(t);return {code:"ERROR",message:s?`Unerwarteter Fehler ohne Meldung: ${s}`:"Der Aufruf schlug fehl, ohne einen Fehler zu hinterlassen. Das Serverlog zeigt unter derselben Korrelations-ID, welche SAP-Aufrufe beteiligt waren."}}function li(t,e,n){let r=typeof e=="string"?e:JSON.stringify(e??""),s=oi(r);if(s?.toUpperCase().includes("/IWFND/MED/170")){let o=n?.serviceUrl?`Service ${n.serviceUrl}`:"Der Service";return {code:"SERVICE_NOT_REGISTERED",httpStatus:t,sapCode:s,message:`${o} ist im Gateway nicht registriert.`,guidance:"Aktivierung in Transaktion /IWFND/MAINT_SERVICE n\xF6tig. Das ist kein Berechtigungsproblem \u2014 PFCG und SU53 f\xFChren hier nicht weiter."}}if(si(r))return {code:"MEDIA_OBJECT_MISMATCH",httpStatus:t,...s?{sapCode:s}:{},message:Fn(e)??"SAP meldet fehlende Berechtigung f\xFCr das Objekt \u2014 meist ist es aber der Objektschl\xFCssel oder der Objekttyp.",guidance:ii};if(t===401)return {code:"AUTH_FAILED",httpStatus:401,...s?{sapCode:s}:{},message:"Anmeldung abgelehnt.",guidance:'Ist sap-client gesetzt? Ohne Mandant antwortet das Gateway mit 401 \u201EAnmeldung fehlgeschlagen" statt mit einer Mandantenmeldung. Sonst Benutzer und Passwort pr\xFCfen.'};if(t===403)return {code:"FORBIDDEN",httpStatus:403,...s?{sapCode:s}:{},message:Fn(e)??"Zugriff verweigert.",guidance:"Berechtigungen in PFCG pr\xFCfen, Trace \xFCber SU53. Bei OData-Services zus\xE4tzlich das Objekt S_SERVICE."};if(t===404)return {code:"NOT_FOUND",httpStatus:404,...s?{sapCode:s}:{},message:n?.serviceUrl?`Unter ${n.serviceUrl} liegt nichts.`:"Der angeforderte Pfad existiert auf diesem System nicht.",guidance:"Pfad \xFCber sap_discover_services pr\xFCfen. Ein Service kann existieren, aber nicht aktiviert sein (/IWFND/MAINT_SERVICE)."};if(Kt(e))return zt(e)?{code:"AUTH_FAILED",httpStatus:t,message:`Anmeldung abgelehnt (SAP-Anmeldeseite, HTTP ${t}: ${ne(e)}).`,guidance:Gt}:{code:"HTML_RESPONSE",httpStatus:t,message:`SAP hat eine HTML-Fehlerseite geliefert (HTTP ${t}): ${ne(e)}`,guidance:"Der Pfad wird von SAP nicht als OData-Service beantwortet. Bei Katalogpfaden sap_discover_services statt sap_query verwenden."};let i=me(e);return {code:i.code&&i.code!=="UNKNOWN"?i.code:`HTTP_${t}`,httpStatus:t,...s?{sapCode:s}:{},message:i.message&&i.message!=="Unknown error"&&i.message!=="Unknown SAP error"?i.message:`SAP antwortete mit HTTP ${t} ohne verwertbare Fehlermeldung.`,...i.details.length>0?{guidance:i.details.map(o=>`${o.code}: ${o.message}`).join(" | ")}:{}}}function Fn(t){let e=me(t);if(e.message&&!(e.message==="Unknown error"||e.message==="Unknown SAP error"))return e.message}var mi=5e3;function Hn(t){let e=t?.issuer?.CN??t?.issuer?.O;return Array.isArray(e)?e.join(", ")||void 0:typeof e=="string"?e:void 0}function k(t,e,n,r,s){return {stage:t,status:n,detail:r,durationMs:Date.now()-e,...s?.guidance?{guidance:s.guidance}:{},...s?.info?{info:s.info}:{}}}function fi(t){try{let e=new URL(t),n=e.protocol==="https:";return {host:e.hostname,port:e.port?Number(e.port):n?443:80,isTls:n}}catch{return}}async function gi(t,e){let n=Date.now();try{let{address:r,family:s}=await e(t);return k("dns",n,"ok",`${t} l\xF6st auf ${r} auf.`,{info:{address:r,family:s}})}catch(r){let s=fe(r);return k("dns",n,"failed",`${t} ist nicht aufl\xF6sbar.`,{guidance:s.guidance??"SAP_BASE_URL pr\xFCfen. Im Container zus\xE4tzlich die DNS-Konfiguration des Docker-Netzes."})}}async function hi(t,e,n,r,s){let i=Date.now(),o=a=>new Promise((d,l)=>{let c=s({host:t,port:e,rejectUnauthorized:a,servername:t},()=>{let u=c.getPeerCertificate(true);c.destroy(),d(u);});c.setTimeout(r,()=>{c.destroy(),l(Object.assign(new Error(`TLS-Handshake nach ${r} ms ohne Antwort`),{code:"ETIMEDOUT"}));}),c.on("error",u=>{c.destroy(),l(u);});});try{let a=await o(n),d=Hn(a);return k("tls",i,"ok",n?`TLS-Verbindung steht, Zertifikatskette ist verifizierbar.${d?` Aussteller: ${d}.`:""}`:`TLS-Verbindung steht. Die Zertifikatspr\xFCfung ist \xFCber SAP_TLS_VERIFY=false abgeschaltet \u2014 die Kette wurde NICHT verifiziert.${d?` Aussteller: ${d}.`:""}`,{info:{verified:n,...d?{issuer:d}:{},...a?.subject?.CN?{subject:String(a.subject.CN)}:{},...a?.valid_to?{validTo:a.valid_to}:{}}})}catch(a){let d=fe(a);if(d.code==="TLS_CHAIN_INCOMPLETE"){let l;try{let c=await o(!1);l=Hn(c);}catch{}return k("tls",i,"failed",l?`Die Zertifikatskette ist nicht vollst\xE4ndig verifizierbar. Aussteller des Serverzertifikats: ${l}.`:"Die Zertifikatskette ist nicht vollst\xE4ndig verifizierbar.",{guidance:d.guidance,...l?{info:{issuer:l}}:{}})}return k("tls",i,"failed",d.message,{guidance:d.guidance})}}async function yi(t,e){let n=Date.now();try{let r=await t.request({method:"get",url:"/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/",headers:{Accept:"application/json"},params:{$format:"json"}});return [k("auth",n,"ok","Anmeldung am Gateway erfolgreich."),k("client",n,"ok",e?`Mandant ${e} wird an jede Anfrage geh\xE4ngt und vom Gateway angenommen.`:"Kein SAP_CLIENT konfiguriert \u2014 das System hat die Anfrage dennoch angenommen. Auf mandantenabh\xE4ngigen Systemen sollte er gesetzt sein.",{info:{sapClient:e??null}}),k("catalog",n,"ok",`Servicekatalog erreichbar (HTTP ${r.status}).`)]}catch(r){let s=fe(r),i=s.httpStatus===401||s.code==="AUTH_FAILED";return i&&!e?[k("auth",n,"ok","Zugangsdaten wurden gesendet."),k("client",n,"failed",'Das Gateway lehnt die Anmeldung ab, und SAP_CLIENT ist nicht gesetzt. Gemessen antwortet das Gateway ohne sap-client mit 401 \u201EAnmeldung fehlgeschlagen" statt mit einer Mandantenmeldung.',{guidance:"SAP_CLIENT setzen (z. B. 100) und erneut pr\xFCfen. Bleibt der 401 bestehen, liegt es an Benutzer oder Passwort."}),k("catalog",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor.")]:i?[k("auth",n,"failed","Anmeldung abgelehnt.",{guidance:`Benutzer und Passwort pr\xFCfen. Mandant ${e} wird gesendet \u2014 bei Zweifeln in SU01 pr\xFCfen, ob der Benutzer in diesem Mandanten existiert und nicht gesperrt ist.`}),k("client",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor."),k("catalog",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor.")]:[k("auth",n,"ok","Anmeldung am Gateway erfolgreich."),k("client",n,"ok",e?`Mandant ${e} angenommen.`:"Kein Mandant konfiguriert."),k("catalog",n,"failed",s.message,{guidance:s.guidance??"Katalogdienst in /IWFND/MAINT_SERVICE pr\xFCfen. Der Server probiert drei bekannte Katalogpfade durch."})]}}async function rt(t,e){let n=Date.now(),r=e.budgetMs??mi,s=e.deps?.lookup??lookup,i=e.deps?.connectTls??connect,o=[],a=fi(e.baseUrl);if(!a)return o.push(k("dns",n,"failed",`SAP_BASE_URL ist keine g\xFCltige URL: "${e.baseUrl}"`,{guidance:"Erwartet wird etwa https://host.example.com:44300 \u2014 mit Schema und ohne Pfad."}),...["tls","auth","client","catalog"].map(d=>k(d,n,"skipped","Nicht gepr\xFCft \u2014 die Basis-URL ist unbrauchbar."))),nt(o,n);if(o.push(await gi(a.host,s)),o[0].status==="failed")return o.push(...["tls","auth","client","catalog"].map(d=>k(d,n,"skipped","Nicht gepr\xFCft \u2014 der Name war nicht aufl\xF6sbar."))),nt(o,n);if(a.isTls){let d=Math.max(500,r-(Date.now()-n));if(o.push(await hi(a.host,a.port,e.tlsVerify!==false,d,i)),o[o.length-1].status==="failed")return o.push(...["auth","client","catalog"].map(l=>k(l,n,"skipped","Nicht gepr\xFCft \u2014 die TLS-Stufe scheiterte zuvor."))),nt(o,n)}else o.push(k("tls",n,"skipped","Nicht gepr\xFCft \u2014 SAP_BASE_URL verwendet http, nicht https."));return o.push(...await yi(t,e.sapClient)),nt(o,n)}function nt(t,e){let n=t.find(r=>r.status==="failed");return {status:n?"failed":"connected",...n?{failedStage:n.stage}:{},stages:t,durationMs:Date.now()-e,timestamp:new Date().toISOString()}}var ge=class{constructor(e,n,r,s=60){this.tokenUrl=e;this.clientId=n;this.clientSecret=r;this.bufferSeconds=s;}cachedToken=null;expiresAt=0;pendingFetch=null;async getToken(e){if(this.cachedToken&&Date.now()<this.expiresAt)return this.cachedToken;if(this.pendingFetch)return this.pendingFetch;this.pendingFetch=this.fetchToken();try{return await this.pendingFetch}finally{this.pendingFetch=null;}}async fetchToken(){let e=await Jt.post(this.tokenUrl,new URLSearchParams({grant_type:"client_credentials"}),{auth:{username:this.clientId,password:this.clientSecret},headers:{"Content-Type":"application/x-www-form-urlencoded"}}),{access_token:n,expires_in:r}=e.data;return this.cachedToken=n,this.expiresAt=Date.now()+(r-this.bufferSeconds)*1e3,n}};function Yt(t){return createHash("sha256").update(t).digest("hex")}var De=class{constructor(e=100){this.maxSize=e;}cache=new Map;get(e){let n=this.cache.get(e);return n?Date.now()>=n.expiresAt?(this.cache.delete(e),null):(this.cache.delete(e),this.cache.set(e,n),n.token):null}set(e,n,r){if(this.cache.has(e))this.cache.delete(e);else if(this.cache.size>=this.maxSize){let s=this.cache.keys().next().value;s!==void 0&&this.cache.delete(s);}this.cache.set(e,{token:n,expiresAt:r});}},st=class{clientTokenManager;cache;config;constructor(e){this.config=e,this.clientTokenManager=new ge(e.tokenServiceUrl,e.clientId,e.clientSecret),this.cache=new De;}async getToken(e){if(!e)return this.clientTokenManager.getToken();let n=Yt(e),r=this.cache.get(n);return r||this.exchangeToken(e,n)}async exchangeToken(e,n){let r=await this.clientTokenManager.getToken(),s=`${this.config.destinationServiceUrl}/destination-configuration/v1/destinations/${this.config.destinationName}`,o=(await Jt.get(s,{headers:{Authorization:`Bearer ${r}`,"X-user-token":e}})).data.authTokens;if(!o?.length)throw new Error(`Destination '${this.config.destinationName}': no authTokens in response`);let a=o[0];if(a.error)throw new Error(`Destination '${this.config.destinationName}' token exchange failed: ${a.error}`);let d=parseInt(a.expires_in,10),l=Number.isFinite(d)&&d>0?d:3600,c=Date.now()+(l-60)*1e3;return this.cache.set(n,a.value,c),a.value}};var Ne=class{constructor(e,n){this.config=e;this.discoveryClient=n;this.bufferSeconds=e.bufferSeconds??60;}cachedToken=null;expiresAt=0;pendingFetch=null;jwks=null;bufferSeconds;async getToken(e){if(this.cachedToken&&Date.now()<this.expiresAt)return this.cachedToken;if(this.pendingFetch)return this.pendingFetch;this.pendingFetch=this.fetchAndValidateToken();try{return await this.pendingFetch}finally{this.pendingFetch=null;}}async fetchAndValidateToken(){let e=await this.discoveryClient.discover(this.config.issuerUrl),n=await Jt.post(e.token_endpoint,new URLSearchParams({grant_type:"client_credentials"}),{auth:{username:this.config.clientId,password:this.config.clientSecret},headers:{"Content-Type":"application/x-www-form-urlencoded"}}),{access_token:r,expires_in:s}=n.data;return await this.validateJwt(r,e.jwks_uri,e.issuer),this.cachedToken=r,this.expiresAt=Date.now()+(s-this.bufferSeconds)*1e3,r}async validateJwt(e,n,r){this.jwks||(this.jwks=createRemoteJWKSet(new URL(n)));let{payload:s}=await jwtVerify(e,this.jwks,{issuer:r});return s}};var re=class{constructor(e=36e5){this.ttlMs=e;}cache=new Map;async discover(e){let n=e.replace(/\/+$/,""),r=this.cache.get(n);if(r&&Date.now()<r.expiresAt)return r.document;let s=`${n}/.well-known/openid-configuration`,o=(await Jt.get(s,{timeout:1e4,headers:{Accept:"application/json"}})).data;if(!o.token_endpoint||!o.jwks_uri)throw new Error(`OIDC discovery document at ${s} is missing required fields (token_endpoint, jwks_uri)`);return this.cache.set(n,{document:o,expiresAt:Date.now()+this.ttlMs}),o}clearCache(){this.cache.clear();}};var ki={email:"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",sapUsername:"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"};function jn(t,e){if(!t.includes("-----BEGIN")||!t.includes("-----END"))throw new Error(`Invalid PEM format for ${e}. Expected PEM-encoded content starting with -----BEGIN`)}function Oi(t){return `<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="${t.assertionId}" IssueInstant="${t.authnInstant}" Version="2.0"><saml:Issuer>${t.issuer}</saml:Issuer><saml:Subject><saml:NameID Format="${t.nameIdFormat}">${t.nameId}</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="${t.notOnOrAfter}" Recipient="${t.recipient}"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="${t.notBefore}" NotOnOrAfter="${t.notOnOrAfter}"><saml:AudienceRestriction><saml:Audience>${t.audience}</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="${t.authnInstant}"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement></saml:Assertion>`}function Ii(t,e,n){let r=new SignedXml({privateKey:e,publicCert:n});return r.signatureAlgorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",r.canonicalizationAlgorithm="http://www.w3.org/2001/10/xml-exc-c14n#",r.addReference({xpath:"//*[local-name(.)='Assertion']",digestAlgorithm:"http://www.w3.org/2001/04/xmlenc#sha256",transforms:["http://www.w3.org/2000/09/xmldsig#enveloped-signature","http://www.w3.org/2001/10/xml-exc-c14n#"]}),r.computeSignature(t,{location:{reference:"//*[local-name(.)='Issuer']",action:"after"}}),r.getSignedXml()}var it=class{cache;config;constructor(e){jn(e.privateKey,"private key"),jn(e.certificate,"certificate"),this.config={...e,clockSkew:e.clockSkew??120,assertionLifetime:e.assertionLifetime??300},this.cache=new De;}async getToken(e){if(!e)throw new Error("saml-bearer auth requires a user token for principal propagation");let n=Yt(e),r=this.cache.get(n);return r||this.exchangeToken(e,n)}resolveNameId(e,n){return n==="email"?e.email??e.sub??"":e.sub??""}async exchangeToken(e,n){let r=decodeJwt(e),s=Date.now(),i=this.resolveNameId({sub:r.sub,email:r.email},this.config.nameIdFormat),o=ki[this.config.nameIdFormat],a=new Date(s-this.config.clockSkew*1e3).toISOString(),d=new Date(s+this.config.assertionLifetime*1e3).toISOString(),l=new Date(s).toISOString(),c=Oi({assertionId:`_${randomUUID()}`,issuer:this.config.issuer,nameId:i,nameIdFormat:o,recipient:this.config.tokenServiceUrl,audience:this.config.clientId,notBefore:a,notOnOrAfter:d,authnInstant:l}),u=Ii(c,this.config.privateKey,this.config.certificate),p=Buffer.from(u).toString("base64"),m=await Jt.post(this.config.tokenServiceUrl,new URLSearchParams({grant_type:"urn:ietf:params:oauth:grant-type:saml2-bearer",assertion:p,client_id:this.config.clientId}).toString(),{headers:{"Content-Type":"application/x-www-form-urlencoded"}}),{access_token:g,expires_in:f}=m.data,h=typeof f=="number"&&f>0?f:3600,b=Date.now()+(h-60)*1e3;return this.cache.set(n,g,b),g}};var Di="[geschwaerzt]",Ni=new Set(["accept","accept-encoding","content-type","content-length","if-match","if-none-match","x-http-method","x-requested-with"]);function Vn(t){let e="'(?:[^']|'')*'",n=new RegExp(`\\b(guid|datetimeoffset|datetime|time|binary|X)${e}|${e}|\\b\\d+(?:\\.\\d+)?[dfmDFM]?\\b`,"gi");return t.replace(n,(r,s)=>s?`${s}'<${s}>'`:r.startsWith("'")?"'<str>'":"<num>")}function xi(t){return t.replace(/\(([^)]*)\)/g,(e,n)=>`(${Vn(n)})`)}function Mi(t){if(t===null||typeof t!="object")return t;let e={};for(let[n,r]of Object.entries(t))e[n]=Ni.has(n.toLowerCase())?r:Di;return e}var ot={paths:["headers","url",'params["$filter"]','params["$search"]'],censor:(t,e)=>{let n=e[e.length-1];return n==="headers"?Mi(t):typeof t!="string"?t:n==="url"?xi(t):Vn(t)}};function Wn(t){if(t.tlsVerify===false||t.clientCert!==void 0)return new Agent({rejectUnauthorized:t.tlsVerify!==false,...t.clientCert?{cert:t.clientCert}:{},...t.clientKey?{key:t.clientKey}:{},...t.clientKeyPassphrase?{passphrase:t.clientKeyPassphrase}:{}})}var Bi=3e4,Y=$i({name:"sap-http-client",redact:ot,transport:{target:"pino/file",options:{destination:2}}});function Li(t,e){return typeof t=="string"?t.trimStart().startsWith("<!DOCTYPE"):Buffer.isBuffer(t)?String(e?.["content-type"]??"").includes("text/html")?true:t.subarray(0,32).toString("latin1").trimStart().startsWith("<!DOCTYPE"):false}function Fi(t){return t.startsWith("http://")||t.startsWith("https://")}function Hi(t){let e=t.replace(/\/+$/,"");try{let n=new URL(e),r=n.pathname.replace(/\/+$/,"");return {origin:`${n.protocol}//${n.host}`,path:r}}catch{return {origin:e,path:""}}}var he=class{constructor(e,n={}){this.destination=e;switch(this.httpsAgent=Wn(n),this.requestTimeoutMs=n.requestTimeoutMs??Bi,e.auth.type){case "oauth2":this.tokenManager=new ge(e.auth.tokenServiceUrl,e.auth.clientId,e.auth.clientSecret);break;case "ias":this.tokenManager=new Ne({issuerUrl:e.auth.tenantUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new re);break;case "xsuaa":this.tokenManager=new Ne({issuerUrl:e.auth.xsuaaUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new re);break;case "btp-principal":this.tokenManager=new st({destinationName:e.auth.destinationName,destinationServiceUrl:e.auth.destinationServiceUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret,tokenServiceUrl:e.auth.tokenServiceUrl});break;case "saml-bearer":this.tokenManager=new it({tokenServiceUrl:e.auth.tokenServiceUrl,clientId:e.auth.clientId,issuer:e.auth.issuer,nameIdFormat:e.auth.nameIdFormat,privateKey:e.auth.privateKey,certificate:e.auth.certificate,clockSkew:e.auth.clockSkew,assertionLifetime:e.auth.assertionLifetime});break;default:this.tokenManager=null;}}tokenManager;userContext=null;httpsAgent;requestTimeoutMs;async request(e){tt();let n=e.method!=="get",r=pe(),s=Qe(),i={...e.params};this.destination.sapClient&&(i["sap-client"]=this.destination.sapClient);let o=e.userContext??this.userContext??void 0,a=await this.buildAuthHeaders(o),d=this.destination.headers??{},l={};if(n){let p=await this.fetchCsrfToken({...d,...a},e.url);p.token&&(l={"x-csrf-token":p.token},p.cookies&&(l.Cookie=p.cookies));}let c={...d,...a,...l,...e.headers},u={method:e.method,url:this.resolveUrl(e.url),data:e.data,headers:c,params:Object.keys(i).length>0?i:void 0,...e.responseType?{responseType:e.responseType}:{},httpsAgent:this.httpsAgent,timeout:this.effectiveTimeoutMs(),signal:r?.signal,beforeRedirect:p=>{this.httpsAgent&&(p.agent=this.httpsAgent);let m=p.headers??{};for(let[g,f]of Object.entries(c))m[g]||(m[g]=f);}};Y.info({...s?{correlationId:s,tool:r?.toolName}:{},method:u.method,url:u.url,headers:u.headers,params:u.params,hasAuth:!!u.headers.Authorization||!!u.headers.APIKey},"SAP HTTP request outgoing");try{let p=await Jt(u);if(Y.info({...s?{correlationId:s}:{},status:p.status,contentType:p.headers["content-type"]},"SAP HTTP response OK"),Li(p.data,p.headers))throw new Error("SAP returned an HTML page instead of JSON. This usually means authentication failed or the URL path is invalid.");return {data:p.data,headers:p.headers,status:p.status}}catch(p){if(isAxiosError(p)&&p.response){let m=e.expectedStatuses?.includes(p.response.status)??false;(m?Y.info.bind(Y):Y.error.bind(Y))({...s?{correlationId:s}:{},status:p.response.status,statusText:p.response.statusText,contentType:p.response.headers?.["content-type"],url:u.url,...m?{expected:true}:{},dataType:typeof p.response.data,dataLength:typeof p.response.data=="string"?p.response.data.length:void 0},m?"SAP HTTP response (expected)":"SAP HTTP response ERROR");}else p instanceof Error&&Y.error({message:p.message,name:p.name},"SAP HTTP connection error");if(n&&this.isCsrf403(p)){tt();try{let m=await this.fetchCsrfToken({...d,...a},e.url),g={...u.headers};m.token&&(g["x-csrf-token"]=m.token,m.cookies&&(g.Cookie=m.cookies)),Qe();let f=await Jt({...u,headers:g});return {data:f.data,headers:f.headers,status:f.status}}catch(m){throw this.toSapError(m)}}throw this.toSapError(p)}}async buildAuthHeaders(e){switch(this.destination.auth.type){case "basic":return {Authorization:`Basic ${Buffer.from(`${this.destination.auth.username}:${this.destination.auth.password}`).toString("base64")}`};case "saml-bearer":case "btp-principal":case "oauth2":case "ias":case "xsuaa":return {Authorization:`Bearer ${await this.tokenManager.getToken(e?.token)}`};case "apikey":return {APIKey:this.destination.auth.apiKey};default:return {}}}resolveUrl(e){if(Fi(e))return e;let{origin:n,path:r}=Hi(this.destination.url);return r&&(e===r||e.startsWith(`${r}/`))?`${n}${e}`:`${this.destination.url}${e}`}async fetchCsrfToken(e,n){let r=this.destination.url,s=[];if(n){let i=n.replace(/\/[^/]*$/,"/");i!=="/"&&i!==n&&s.push(this.resolveUrl(i));}s.push(`${r}/`,r);for(let i of s){tt(),Qe();try{let o={...e,"x-csrf-token":"fetch"},a={};this.destination.sapClient&&(a["sap-client"]=this.destination.sapClient);let d=await Jt.get(i,{headers:o,params:Object.keys(a).length>0?a:void 0,validateStatus:()=>!0,timeout:this.effectiveTimeoutMs(),signal:pe()?.signal,httpsAgent:this.httpsAgent,beforeRedirect:c=>{this.httpsAgent&&(c.agent=this.httpsAgent);let u=c.headers??{};for(let[p,m]of Object.entries(o))u[p]||(u[p]=m);}}),l=d.headers["x-csrf-token"];if(Y.debug({url:i,status:d.status,hasToken:!!l},"CSRF token fetch result"),l&&l.toLowerCase()!=="required"){let u=(d.headers["set-cookie"]??[]).map(p=>p.split(";")[0]).join("; ");return {token:l,cookies:u}}}catch{}}return {token:"",cookies:""}}isCsrf403(e){return isAxiosError(e)&&e.response?.status===403}effectiveTimeoutMs(){let e=Bn();return e===void 0?this.requestTimeoutMs:Math.max(1,Math.min(this.requestTimeoutMs,e))}toSapError(e){if(e instanceof B)return {code:e.timedOut?"TOOL_TIMEOUT":"ABORTED",message:e.message,details:[],rawResponse:void 0};if(isAxiosError(e)&&(e.code==="ERR_CANCELED"||e.name==="CanceledError")){let n=pe()?.signal?.reason?.name==="TimeoutError";return {code:n?"TOOL_TIMEOUT":"ABORTED",message:n?"Zeitbudget des Tool-Aufrufs \xFCberschritten \u2014 laufender SAP-Aufruf abgebrochen.":"Client hat die Verbindung verworfen \u2014 laufender SAP-Aufruf abgebrochen.",details:[],rawResponse:void 0}}return isAxiosError(e)&&(e.code==="ECONNABORTED"||e.code==="ETIMEDOUT")?{code:"TIMEOUT",message:`SAP request exceeded ${this.requestTimeoutMs} ms (SAP_HTTP_TIMEOUT_MS): ${e.message}`,details:[],rawResponse:void 0}:isAxiosError(e)&&e.response?me(e.response.data):e instanceof Error?{code:"CONNECTION_ERROR",message:e.message,details:[],rawResponse:void 0}:me(e)}};function qn(t,e){e||t.warn({sapClient:null},'SAP_CLIENT ist nicht gesetzt. Auf mandantenabh\xE4ngigen Systemen antwortet das Gateway ohne sap-client mit 401 \u201EAnmeldung fehlgeschlagen" \u2014 was wie ein falsches Passwort aussieht, aber keines ist. Bei Anmeldeproblemen zuerst SAP_CLIENT setzen (z. B. 100).');}async function Kn(t,e,n,r){try{let s=new he(Ze(e),n),i=await rt(s,{baseUrl:e.baseUrl,sapClient:e.sapClient,tlsVerify:n.tlsVerify,...r?{deps:r}:{}});for(let o of i.stages){let a={stage:o.stage,status:o.status,durationMs:o.durationMs,...o.info??{},...o.guidance?{guidance:o.guidance}:{}};o.status==="failed"?t.warn(a,`Selbsttest ${o.stage}: ${o.detail}`):t.info(a,`Selbsttest ${o.stage}: ${o.detail}`);}i.status==="connected"?t.info({durationMs:i.durationMs,sapClient:e.sapClient??null},"Selbsttest bestanden: Namensaufl\xF6sung, TLS, Anmeldung, Mandant und Katalog sind in Ordnung"):t.warn({failedStage:i.failedStage,durationMs:i.durationMs},`Selbsttest gescheitert in Stufe \u201E${i.failedStage}". Der Server l\xE4uft weiter \u2014 Tool-Aufrufe werden bis zur Behebung fehlschlagen. Einzelheiten liefert das Tool test-connection.`);}catch(s){t.warn({err:s instanceof Error?s.message:String(s)},"Selbsttest konnte nicht ausgef\xFChrt werden. Der Server l\xE4uft weiter.");}}function se(t){return `/Date(${Date.parse(t.length===10?`${t}T00:00:00Z`:t)})/`}var A=(t,e,n,r=true)=>({name:t,type:"Edm.String",maxLength:e,...n?{label:n}:{},nullable:r}),Me=(t,e,n=15,r=3)=>({name:t,type:"Edm.Decimal",precision:n,scale:r,...e?{label:e}:{}}),ie=(t,e)=>({name:t,type:"Edm.DateTime",precision:7,...e?{label:e}:{}}),zn=(t,e)=>({name:t,type:"Edm.Boolean",...e?{label:e}:{}}),Qt="/sap/opu/odata/sap",ji={technicalName:"API_BUSINESS_PARTNER",title:"Business Partner (A2X)",description:"Business partners, customers and suppliers with their addresses.",path:`${Qt}/API_BUSINESS_PARTNER`,namespace:"API_BUSINESS_PARTNER",entityTypes:[{set:"A_BusinessPartner",keys:["BusinessPartner"],autoKey:{prefix:"10",width:7},properties:[A("BusinessPartner",10,"Business Partner",false),A("Customer",10,"Customer"),A("Supplier",10,"Supplier"),A("BusinessPartnerCategory",1,"BP Category"),A("BusinessPartnerFullName",81,"Full Name"),A("BusinessPartnerName",81,"Name"),A("OrganizationBPName1",40,"Organization Name 1"),A("FirstName",40,"First Name"),A("LastName",40,"Last Name"),A("BusinessPartnerGrouping",4,"Grouping"),A("SearchTerm1",20,"Search Term"),A("Industry",4,"Industry"),A("CreatedByUser",12,"Created By"),ie("CreationDate","Created On"),zn("IsMarkedForArchiving","Archiving Flag")],navigations:[{name:"to_BusinessPartnerAddress",target:"A_BusinessPartnerAddress",multiplicity:"*",join:[["BusinessPartner","BusinessPartner"]]}]},{set:"A_BusinessPartnerAddress",keys:["BusinessPartner","AddressID"],autoKey:{prefix:"2",width:5},properties:[A("BusinessPartner",10,"Business Partner",false),A("AddressID",10,"Address ID",false),ie("ValidityStartDate","Valid From"),ie("ValidityEndDate","Valid To"),A("StreetName",60,"Street"),A("HouseNumber",10,"House Number"),A("PostalCode",10,"Postal Code"),A("CityName",40,"City"),A("Country",3,"Country"),A("Region",3,"Region"),A("Language",2,"Language")],navigations:[{name:"to_BusinessPartner",target:"A_BusinessPartner",multiplicity:"1",join:[["BusinessPartner","BusinessPartner"]]}]}],functionImports:[]},Vi={technicalName:"API_SALES_ORDER_SRV",title:"Sales Order (A2X)",description:"Sales orders with items \u2014 read, create (deep insert), change, release.",path:`${Qt}/API_SALES_ORDER_SRV`,namespace:"API_SALES_ORDER_SRV",entityTypes:[{set:"A_SalesOrder",keys:["SalesOrder"],autoKey:{prefix:"45",width:7},properties:[A("SalesOrder",10,"Sales Order",false),A("SalesOrderType",4,"Order Type"),A("SalesOrganization",4,"Sales Organization"),A("DistributionChannel",2,"Distribution Channel"),A("OrganizationDivision",2,"Division"),A("SoldToParty",10,"Sold-to Party"),A("PurchaseOrderByCustomer",35,"Customer Reference"),A("CreatedByUser",12,"Created By"),ie("CreationDate","Created On"),ie("LastChangeDateTime","Last Changed"),ie("RequestedDeliveryDate","Requested Delivery Date"),Me("TotalNetAmount","Net Value",16,3),A("TransactionCurrency",5,"Currency"),A("OverallSDProcessStatus",1,"Overall Status"),A("OverallDeliveryStatus",1,"Delivery Status")],navigations:[{name:"to_Item",target:"A_SalesOrderItem",multiplicity:"*",join:[["SalesOrder","SalesOrder"]]}]},{set:"A_SalesOrderItem",keys:["SalesOrder","SalesOrderItem"],properties:[A("SalesOrder",10,"Sales Order",false),A("SalesOrderItem",6,"Item",false),A("Material",40,"Material"),A("SalesOrderItemText",40,"Item Description"),A("SalesOrderItemCategory",4,"Item Category"),Me("RequestedQuantity","Order Quantity",15,3),A("RequestedQuantityUnit",3,"Unit"),Me("NetAmount","Net Value",16,3),A("TransactionCurrency",5,"Currency"),A("ProductionPlant",4,"Plant")],navigations:[{name:"to_SalesOrder",target:"A_SalesOrder",multiplicity:"1",join:[["SalesOrder","SalesOrder"]]}]}],functionImports:[{name:"ReleaseSalesOrder",httpMethod:"POST",parameters:[{name:"SalesOrder",type:"Edm.String"}],returnType:"API_SALES_ORDER_SRV.A_SalesOrderType",entitySet:"A_SalesOrder"},{name:"GetOpenSalesOrderCount",httpMethod:"GET",parameters:[{name:"SoldToParty",type:"Edm.String",nullable:true}],returnType:"Edm.Int32"}]},Wi={technicalName:"API_PRODUCT_SRV",title:"Product Master (A2X)",description:"Products (materials) with descriptions.",path:`${Qt}/API_PRODUCT_SRV`,namespace:"API_PRODUCT_SRV",entityTypes:[{set:"A_Product",keys:["Product"],autoKey:{prefix:"DEMO-",width:8},properties:[A("Product",40,"Product",false),A("ProductType",4,"Product Type"),A("ProductGroup",9,"Product Group"),A("BaseUnit",3,"Base Unit"),Me("GrossWeight","Gross Weight",13,3),Me("NetWeight","Net Weight",13,3),A("WeightUnit",3,"Weight Unit"),A("CreatedByUser",12,"Created By"),ie("CreationDate","Created On"),zn("IsMarkedForDeletion","Deletion Flag")],navigations:[{name:"to_Description",target:"A_ProductDescription",multiplicity:"*",join:[["Product","Product"]]}]},{set:"A_ProductDescription",keys:["Product","Language"],properties:[A("Product",40,"Product",false),A("Language",2,"Language",false),A("ProductDescription",40,"Description")],navigations:[]}],functionImports:[]},at=[ji,Vi,Wi],Xt="MCP_DEMO",qi=[["1000001","2","Nordlicht Maschinenbau GmbH","","Hafenstra\xDFe","12","20457","Hamburg","HH","C","MACH","2024-03-04"],["1000002","2","Weberei Hartmann & S\xF6hne KG","","M\xFChlweg","3","91522","Ansbach","BY","C","TEXT","2024-03-11"],["1000003","2","Fr\xE4nkische Pr\xE4zisionstechnik AG","","Industriestra\xDFe","48","90411","N\xFCrnberg","BY","C","MACH","2024-04-02"],["1000004","2","Altm\xFChl Kunststoffwerk GmbH","","Am Kanal","7","91710","Gunzenhausen","BY","C","CHEM","2024-04-19"],["1000005","2","Seenland Logistik GmbH & Co. KG","","Frankenstra\xDFe","101","91781","Wei\xDFenburg","BY","C","TRAN","2024-05-06"],["1000006","2","Brauerei Hopfenkranz OHG","","Brauhausgasse","2","91154","Roth","BY","C","FOOD","2024-05-21"],["1000007","2","Rothsee Elektronik GmbH","","Gewerbering","15","91126","Schwabach","BY","C","ELEC","2024-06-03"],["1000008","2","Hesselberg Metallbau e.K.","","Bahnhofstra\xDFe","33","91717","Wassertr\xFCdingen","BY","C","MACH","2024-06-17"],["1000009","2","Bavaria Sensorik AG","","Technologiepark","9","91058","Erlangen","BY","C","ELEC","2024-07-01"],["1000010","2","Kastner Werkzeugbau GmbH","","Schmiedeweg","21","90762","F\xFCrth","BY","C","MACH","2024-07-15"],["1000011","2","M\xF6belwerkstatt Lindenhof GmbH","","Lindenallee","5","97070","W\xFCrzburg","BY","C","WOOD","2024-08-05"],["1000012","2","Steinbach Automotive GmbH","","Werkstra\xDFe","60","93055","Regensburg","BY","C","AUTO","2024-08-19"],["1000013","2","Tauber Verpackungen GmbH","","Ringstra\xDFe","18","91541","Rothenburg ob der Tauber","BY","C","PACK","2024-09-02"],["1000014","1","Anna","Berger","Rosenweg","4","96047","Bamberg","BY","C","","2024-09-16"],["1000015","1","Markus","Vogel","Am Stadtpark","11","89073","Ulm","BW","C","","2024-10-07"],["1000016","1","Sabine","Kr\xFCger","Elbchaussee","250","22605","Hamburg","HH","C","","2024-10-21"],["1000017","2","Ruhrstahl Handels GmbH","","Kruppstra\xDFe","77","45143","Essen","NW","S","META","2024-02-05"],["1000018","2","Elbe Chemie AG","","Chemiepark","1","01067","Dresden","SN","S","CHEM","2024-02-12"],["1000019","2","NordPack Verpackungssysteme GmbH","","Hansaring","40","28195","Bremen","HB","S","PACK","2024-02-19"],["1000020","2","Donau Kunststoffe GmbH","","Donaustra\xDFe","88","94032","Passau","BY","B","CHEM","2024-02-26"]],Ki=[["PUMP-100","FERT","PUMPS","ST","42.500","39.800","Kreiselpumpe 100 l/min","Centrifugal pump 100 l/min",1890],["MOTOR-15KW","FERT","DRIVES","ST","118.000","112.500","Drehstrommotor 15 kW","Three-phase motor 15 kW",2450],["SENSOR-T200","HAWA","SENSOR","ST","0.180","0.150","Temperatursensor PT100 -50..200 \xB0C","Temperature sensor PT100 -50..200 \xB0C",64.5],["VALVE-DN50","FERT","VALVES","ST","6.400","6.100","Absperrventil DN50 PN16","Shut-off valve DN50 PN16",312],["HOUSING-AL3","HALB","PARTS","ST","2.900","2.700","Aluminiumgeh\xE4use Gr\xF6\xDFe 3","Aluminium housing size 3",148],["PLC-B200","HAWA","CONTROL","ST","1.100","0.950","Steuerung SPS B200","PLC controller B200",985],["CABLE-5M","HAWA","CABLE","ST","0.600","0.550","Anschlusskabel 5 m geschirmt","Connection cable 5 m shielded",27.9],["FILTER-F40","HAWA","FILTER","ST","0.900","0.800","Filtereinsatz F40","Filter cartridge F40",45],["SEAL-KIT-3","HAWA","PARTS","ST","0.250","0.200","Dichtungssatz Baugr\xF6\xDFe 3","Seal kit size 3",38],["BRACKET-XL","HALB","PARTS","ST","3.300","3.100","Wandhalterung XL verzinkt","Wall bracket XL galvanised",72]],zi=[["4500001","1000001","PO-2026-0117","2026-01-08","2026-01-29","C","C",[["PUMP-100",2],["SEAL-KIT-3",4]]],["4500002","1000003","BEST-88123","2026-01-12","2026-02-05","C","C",[["MOTOR-15KW",1],["CABLE-5M",6],["PLC-B200",1]]],["4500003","1000007","RE-2026-014","2026-01-19","2026-02-10","C","C",[["SENSOR-T200",25],["CABLE-5M",25]]],["4500004","1000004","AK-4471","2026-02-02","2026-02-27","C","C",[["VALVE-DN50",8],["SEAL-KIT-3",8]]],["4500005","1000009","BS-2026-0301","2026-02-16","2026-03-13","C","B",[["SENSOR-T200",120],["HOUSING-AL3",40],["BRACKET-XL",40]]],["4500006","1000012","SA-77201","2026-03-03","2026-03-31","B","B",[["MOTOR-15KW",3],["PLC-B200",3],["CABLE-5M",12]]],["4500007","1000010","KW-2026-19","2026-03-10","2026-04-07","B","A",[["PUMP-100",1],["FILTER-F40",10]]],["4500008","1000005","SL-0209","2026-03-24","2026-04-14","B","A",[["BRACKET-XL",30],["HOUSING-AL3",12]]],["4500009","1000002","WH-2026-04","2026-04-06","2026-05-04","B","A",[["MOTOR-15KW",2],["SENSOR-T200",10]]],["4500010","1000014","","2026-04-14","2026-04-28","C","C",[["FILTER-F40",2],["SEAL-KIT-3",1]]],["4500011","1000011","ML-3350","2026-05-05","2026-06-02","A","A",[["VALVE-DN50",4],["PUMP-100",1],["CABLE-5M",4]]],["4500012","1000013","TV-2026-0888","2026-05-18","2026-06-15","A","A",[["PLC-B200",2],["SENSOR-T200",40],["CABLE-5M",40],["BRACKET-XL",8]]],["4500013","1000015","","2026-06-01","2026-06-22","A","A",[["FILTER-F40",5]]],["4500014","1000006","HK-2026-11","2026-06-15","2026-07-13","A","A",[["PUMP-100",3],["VALVE-DN50",6],["SEAL-KIT-3",6]]],["4500015","1000008","HM-0521","2026-07-01","2026-07-29","A","A",[["MOTOR-15KW",1],["HOUSING-AL3",20],["PLC-B200",1]]]];function Zt(t,e=3){return t.toFixed(e)}function en(){let t=[],e=[];for(let[d,l,c,u,p,m,g,f,h,b,S,y]of qi){let w=l==="1",N=w?`${c} ${u}`:c;t.push({BusinessPartner:d,Customer:b==="C"||b==="B"?d:"",Supplier:b==="S"||b==="B"?d:"",BusinessPartnerCategory:l,BusinessPartnerFullName:N,BusinessPartnerName:N,OrganizationBPName1:w?"":c,FirstName:w?c:"",LastName:w?u:"",BusinessPartnerGrouping:w?"BP02":"BP01",SearchTerm1:(w?u:c.split(" ")[0]).toUpperCase().slice(0,20),Industry:S,CreatedByUser:Xt,CreationDate:se(y),IsMarkedForArchiving:false}),e.push({BusinessPartner:d,AddressID:String(2e4+t.length),ValidityStartDate:se(y),ValidityEndDate:se("9999-12-31"),StreetName:p,HouseNumber:m,PostalCode:g,CityName:f,Country:"DE",Region:h,Language:"DE"});}let n=[],r=[],s=new Map,i=new Map;Ki.forEach(([d,l,c,u,p,m,g,f,h],b)=>{s.set(d,h),i.set(d,f),n.push({Product:d,ProductType:l,ProductGroup:c,BaseUnit:u,GrossWeight:p,NetWeight:m,WeightUnit:"KG",CreatedByUser:Xt,CreationDate:se(`2023-11-${String(6+b).padStart(2,"0")}`),IsMarkedForDeletion:false}),r.push({Product:d,Language:"DE",ProductDescription:g}),r.push({Product:d,Language:"EN",ProductDescription:f});});let o=[],a=[];for(let[d,l,c,u,p,m,g,f]of zi){let h=0;f.forEach(([b,S],y)=>{let w=S*(s.get(b)??0);h+=w,a.push({SalesOrder:d,SalesOrderItem:String((y+1)*10),Material:b,SalesOrderItemText:i.get(b)??b,SalesOrderItemCategory:"TAN",RequestedQuantity:Zt(S),RequestedQuantityUnit:"ST",NetAmount:Zt(w),TransactionCurrency:"EUR",ProductionPlant:"1010"});}),o.push({SalesOrder:d,SalesOrderType:"OR",SalesOrganization:"1010",DistributionChannel:"10",OrganizationDivision:"00",SoldToParty:l,PurchaseOrderByCustomer:c,CreatedByUser:Xt,CreationDate:se(u),LastChangeDateTime:se(`${u}T09:30:00Z`),RequestedDeliveryDate:se(p),TotalNetAmount:Zt(h),TransactionCurrency:"EUR",OverallSDProcessStatus:m,OverallDeliveryStatus:g});}return {A_BusinessPartner:t,A_BusinessPartnerAddress:e,A_SalesOrder:o,A_SalesOrderItem:a,A_Product:n,A_ProductDescription:r}}function Gi(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function Yi(t,e){return `assoc_${t.set}_${e}`}function Gn(t){let e=t.namespace,n=[],r=[],s=[];for(let a of t.entityTypes){let d=a.properties.map(c=>{let u=[`Name="${c.name}"`,`Type="${c.type}"`,`Nullable="${c.nullable===false||a.keys.includes(c.name)?"false":"true"}"`];return c.maxLength!==void 0&&u.push(`MaxLength="${c.maxLength}"`),c.precision!==void 0&&u.push(`Precision="${c.precision}"`),c.scale!==void 0&&u.push(`Scale="${c.scale}"`),c.label&&u.push(`sap:label="${Gi(c.label)}"`),a.keys.includes(c.name)&&u.push('sap:creatable="true" sap:updatable="false"'),` <Property ${u.join(" ")}/>`}).join(`
|
|
5
|
+
`),l=a.navigations.map(c=>{let u=Yi(a,c.name),p=`FromRole_${u}`,m=`ToRole_${u}`;return r.push(` <Association Name="${u}" sap:content-version="1">
|
|
6
|
+
<End Type="${e}.${a.set}Type" Multiplicity="1" Role="${p}"/>
|
|
7
|
+
<End Type="${e}.${c.target}Type" Multiplicity="${c.multiplicity}" Role="${m}"/>
|
|
8
8
|
</Association>`),s.push(` <AssociationSet Name="${u}" Association="${e}.${u}" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:content-version="1">
|
|
9
|
-
<End EntitySet="${a.set}" Role="${
|
|
10
|
-
<End EntitySet="${
|
|
11
|
-
</AssociationSet>`),` <NavigationProperty Name="${
|
|
9
|
+
<End EntitySet="${a.set}" Role="${p}"/>
|
|
10
|
+
<End EntitySet="${c.target}" Role="${m}"/>
|
|
11
|
+
</AssociationSet>`),` <NavigationProperty Name="${c.name}" Relationship="${e}.${u}" FromRole="${p}" ToRole="${m}"/>`}).join(`
|
|
12
12
|
`);n.push(` <EntityType Name="${a.set}Type" sap:content-version="1">
|
|
13
13
|
<Key>
|
|
14
|
-
${a.keys.map(
|
|
14
|
+
${a.keys.map(c=>` <PropertyRef Name="${c}"/>`).join(`
|
|
15
15
|
`)}
|
|
16
16
|
</Key>
|
|
17
|
-
${
|
|
17
|
+
${d}${l?`
|
|
18
18
|
${l}`:""}
|
|
19
19
|
</EntityType>`);}let i=t.entityTypes.map(a=>` <EntitySet Name="${a.set}" EntityType="${e}.${a.set}Type" sap:creatable="${a.creatable!==false}" sap:updatable="${a.updatable!==false}" sap:deletable="${a.deletable!==false}" sap:searchable="true" sap:content-version="1"/>`).join(`
|
|
20
|
-
`),o=t.functionImports.map(a=>{let
|
|
21
|
-
`);return ` <FunctionImport ${
|
|
20
|
+
`),o=t.functionImports.map(a=>{let d=[`Name="${a.name}"`];a.returnType&&d.push(`ReturnType="${a.returnType}"`),a.entitySet&&d.push(`EntitySet="${a.entitySet}"`),d.push(`m:HttpMethod="${a.httpMethod}"`);let l=a.parameters.map(c=>` <Parameter Name="${c.name}" Type="${c.type}" Mode="In" Nullable="${c.nullable?"true":"false"}"/>`).join(`
|
|
21
|
+
`);return ` <FunctionImport ${d.join(" ")}>
|
|
22
22
|
${l}
|
|
23
23
|
</FunctionImport>`}).join(`
|
|
24
24
|
`);return `<?xml version="1.0" encoding="utf-8"?>
|
|
@@ -38,31 +38,31 @@ ${o}`:""}
|
|
|
38
38
|
</Schema>
|
|
39
39
|
</edmx:DataServices>
|
|
40
40
|
</edmx:Edmx>
|
|
41
|
-
`}var D=class extends Error{constructor(e){super(e),this.name="ODataSyntaxError";}},Fn=new Set(["and","or","not","eq","ne","gt","ge","lt","le"]);function Ui(t){let e=[],n=0;for(;n<t.length;){let r=t[n];if(/\s/.test(r)){n++;continue}if(r==="("){e.push({kind:"lparen"}),n++;continue}if(r===")"){e.push({kind:"rparen"}),n++;continue}if(r===","){e.push({kind:"comma"}),n++;continue}if(r==="'"){let{value:s,end:i}=Hn(t,n);e.push({kind:"string",value:s}),n=i;continue}if(/[0-9]/.test(r)||r==="-"&&/[0-9]/.test(t[n+1]??"")){let s=/^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?[mMdDfFlL]?/.exec(t.slice(n));if(!s)throw new D(`Ung\xFCltige Zahl an Position ${n}.`);e.push({kind:"number",value:Number(s[0].replace(/[mMdDfFlL]$/,""))}),n+=s[0].length;continue}if(/[A-Za-z_]/.test(r)){let i=/^[A-Za-z_][A-Za-z0-9_./]*/.exec(t.slice(n))[0];if(n+=i.length,t[n]==="'"){let{value:o,end:a}=Hn(t,n);n=a;let c=i.toLowerCase();if(c==="datetime"||c==="datetimeoffset"){let l=Vn(o);if(Number.isNaN(l))throw new D(`Ung\xFCltiges datetime-Literal '${o}'.`);e.push({kind:"datetime",value:l});}else if(c==="guid"||c==="x"||c==="binary"||c==="time")e.push({kind:"string",value:o});else throw new D(`Unbekanntes Literal-Pr\xE4fix '${i}'.`);continue}e.push({kind:"ident",value:i});continue}throw new D(`Unerwartetes Zeichen '${r}' an Position ${n}.`)}return e}function Hn(t,e){let n=e+1,r="";for(;n<t.length;){let s=t[n];if(s==="'"){if(t[n+1]==="'"){r+="'",n+=2;continue}return {value:r,end:n+1}}r+=s,n++;}throw new D("Zeichenkette ohne schlie\xDFendes Apostroph.")}function Vn(t){let e=/[zZ]|[+-]\d{2}:\d{2}$/.test(t)?t:`${t}Z`;return Date.parse(e)}var $i=new Set(["substringof","startswith","endswith","tolower","toupper"]),Yt=class{constructor(e){this.tokens=e;}pos=0;parse(){if(this.tokens.length===0)throw new D("Leerer $filter-Ausdruck.");let e=this.parseOr();if(this.pos<this.tokens.length)throw new D(`Unerwartetes Token nach dem Ausdruck: ${jn(this.peek())}.`);return e}peek(){return this.tokens[this.pos]}isKeyword(e){let n=this.peek();return n?.kind==="ident"&&n.value.toLowerCase()===e}parseOr(){let e=this.parseAnd();for(;this.isKeyword("or");)this.pos++,e={type:"or",left:e,right:this.parseAnd()};return e}parseAnd(){let e=this.parseNot();for(;this.isKeyword("and");)this.pos++,e={type:"and",left:e,right:this.parseNot()};return e}parseNot(){return this.isKeyword("not")?(this.pos++,{type:"not",operand:this.parseNot()}):this.parseComparison()}parseComparison(){let e=this.parsePrimary(),n=this.peek();if(n?.kind==="ident"){let r=n.value.toLowerCase();if(r==="eq"||r==="ne"||r==="gt"||r==="ge"||r==="lt"||r==="le"){this.pos++;let s=this.parsePrimary();return {type:"compare",op:r,left:e,right:s}}if(!Fn.has(r))throw new D(`Unbekannter Operator '${n.value}'.`)}return e}parsePrimary(){let e=this.peek();if(!e)throw new D("Ausdruck endet unerwartet.");if(e.kind==="lparen"){this.pos++;let n=this.parseOr();if(this.peek()?.kind!=="rparen")throw new D("Schlie\xDFende Klammer fehlt.");return this.pos++,n}if(e.kind==="string")return this.pos++,{type:"literal",value:e.value};if(e.kind==="number")return this.pos++,{type:"literal",value:e.value};if(e.kind==="datetime")return this.pos++,{type:"literal",value:e.value,datetime:true};if(e.kind==="ident"){let n=e.value.toLowerCase();if(n==="true"||n==="false")return this.pos++,{type:"literal",value:n==="true"};if(n==="null")return this.pos++,{type:"literal",value:null};if(Fn.has(n))throw new D(`Operand erwartet, aber '${e.value}' gefunden.`);if(this.pos++,this.peek()?.kind==="lparen"){if(!$i.has(n))throw new D(`Unbekannte Funktion '${e.value}'.`);this.pos++;let r=[];if(this.peek()?.kind!=="rparen")for(r.push(this.parseOr());this.peek()?.kind==="comma";)this.pos++,r.push(this.parseOr());if(this.peek()?.kind!=="rparen")throw new D(`Schlie\xDFende Klammer nach ${e.value}(...) fehlt.`);return this.pos++,{type:"call",fn:n,args:r}}return {type:"field",path:e.value}}throw new D(`Unerwartetes Token ${jn(e)}.`)}};function jn(t){if(!t)return "(Ende)";switch(t.kind){case "lparen":return "'('";case "rparen":return "')'";case "comma":return "','";default:return `'${String(t.value)}'`}}var Bi=/^\/Date\((-?\d+)(?:[+-]\d{4})?\)\/$/;function Li(t,e){let n=t;for(let r of e.split("/")){if(n===null||typeof n!="object")return null;n=n[r];}return n===void 0?null:n}function W(t,e){switch(t.type){case "and":return Se(W(t.left,e))&&Se(W(t.right,e));case "or":return Se(W(t.left,e))||Se(W(t.right,e));case "not":return !Se(W(t.operand,e));case "literal":return t.datetime?{datetime:t.value}:t.value;case "field":return Li(e,t.path);case "call":{let n=t.args.map(s=>W(s,e)),r=s=>s==null?"":String(s);switch(t.fn){case "substringof":return r(n[1]).includes(r(n[0]));case "startswith":return r(n[0]).startsWith(r(n[1]));case "endswith":return r(n[0]).endsWith(r(n[1]));case "tolower":return r(n[0]).toLowerCase();case "toupper":return r(n[0]).toUpperCase();default:throw new D(`Unbekannte Funktion '${t.fn}'.`)}}case "compare":{let[n,r]=Fi(W(t.left,e),W(t.right,e));switch(t.op){case "eq":return n===r;case "ne":return n!==r;case "gt":return n!==null&&r!==null&&n>r;case "ge":return n!==null&&r!==null&&n>=r;case "lt":return n!==null&&r!==null&&n<r;case "le":return n!==null&&r!==null&&n<=r}}}}function Se(t){return t===true}function Fi(t,e){let n=i=>typeof i=="object"&&i!==null&&"datetime"in i;if(n(t)||n(e)){let i=o=>{if(n(o))return o.datetime;if(typeof o=="number")return o;if(typeof o=="string"){let a=Bi.exec(o);if(a)return Number(a[1]);let c=Vn(o);return Number.isNaN(c)?null:c}return null};return [i(t),i(e)]}let r=i=>{if(typeof i=="number")return i;if(typeof i=="string"&&i.trim()!==""&&!Number.isNaN(Number(i)))return Number(i)};if((typeof t=="number"||typeof e=="number")&&!(t===null||e===null)){let i=r(t),o=r(e);if(i!==void 0&&o!==void 0)return [i,o]}let s=i=>i===void 0?null:typeof i=="object"&&i!==null?String(i):i;return [s(t),s(e)]}function Wn(t){let e=new Yt(Ui(t)).parse();return n=>Se(W(e,n))}var be=class extends Error{constructor(e){super(e),this.name="ODataUrlError";}};function it(t){let e=t.trim();if(e.startsWith("'"))return Hi(e);let n=/^(guid|datetime|datetimeoffset|time|X|binary)'(.*)'$/is.exec(e);return n?n[2].replace(/''/g,"'"):/^-?\d+(\.\d+)?[mMdDfFlL]?$/.test(e)?Number(e.replace(/[mMdDfFlL]$/,"")):e==="true"?true:e==="false"?false:e}function Hi(t){if(!t.endsWith("'")||t.length<2)throw new be(`Zeichenkette ohne schlie\xDFendes Apostroph: ${t}`);return t.slice(1,-1).replace(/''/g,"'")}function ji(t){let e=Jt(t);return Wi(e,",").map(r=>{let s=qi(r,"=");return s===-1?{value:it(r)}:{name:r.slice(0,s).trim(),value:it(r.slice(s+1))}})}function Xt(t){let e=t.replace(/^\/+|\/+$/g,""),n=e.indexOf("("),r=e.indexOf("/"),s,i,o;if(n!==-1&&(r===-1||n<r)){s=e.slice(0,n);let l=Vi(e,n);i=ji(e.slice(n+1,l)),o=e.slice(l+1);}else r!==-1?(s=e.slice(0,r),o=e.slice(r)):(s=e,o="");let a=o.split("/").map(l=>Jt(l)).filter(l=>l.length>0),c={entitySet:Jt(s),segments:a};return i&&(c.key=i),c}function Vi(t,e){let n=false;for(let r=e+1;r<t.length;r++){let s=t[r];if(s==="'"){n=!n;continue}if(!n&&s===")")return r}throw new be(`Schlie\xDFende Klammer fehlt in: ${t}`)}function Wi(t,e){let n=[],r="",s=false;for(let i=0;i<t.length;i++){let o=t[i];if(o==="'"){s=!s,r+=o;continue}if(!s&&o===e){n.push(r),r="";continue}r+=o;}return n.push(r),n.filter(i=>i.trim().length>0)}function qi(t,e){let n=false;for(let r=0;r<t.length;r++)if(t[r]==="'")n=!n;else if(!n&&t[r]===e)return r;return -1}function Jt(t){try{return decodeURIComponent(t)}catch{return t}}var ot="DEMO",en="demo",tn="100",Ue="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2",Ki="/sap/opu/odata4/",T=class extends Error{constructor(n,r,s,i={}){super(s);this.status=n;this.code=r;this.headers=i;}},at=class{constructor(e={}){this.options=e;for(let n of st)for(let r of n.entityTypes)this.types.set(r.set,{type:r,service:n});}data=Gt();versions=new Map;csrfToken=randomBytes(16).toString("base64url");cookie=`SAP_SESSIONID_DEMO_100=${randomBytes(12).toString("base64url")}`;types=new Map;nextNumber=new Map;reset(){this.data=Gt(),this.versions.clear(),this.nextNumber.clear();}rows(e){return this.data[e]??[]}handle(e){let r={base:this.options.baseUrl??`http://${e.headers.host??"127.0.0.1"}`,inBatch:false};try{return this.dispatch(e,r)}catch(s){return this.errorResponse(s)}}dispatch(e,n){let r=e.method.toUpperCase(),s=new URL(e.url,n.base),i=s.pathname.replace(/\/+$/,"")||"/",o=s.searchParams;n.inBatch||this.checkAuth(e.headers,o);let c=(e.headers["x-csrf-token"]??"").toLowerCase()==="fetch"?{"x-csrf-token":this.csrfToken,"set-cookie":`${this.cookie}; path=/`}:{},l=r==="HEAD",d=l?"GET":r,u=d==="POST"&&(e.headers["x-http-method"]??"").toUpperCase()==="MERGE"?"MERGE":d;if(u!=="GET"&&!n.inBatch&&e.headers["x-csrf-token"]!==this.csrfToken)throw new T(403,"HTTP/403","CSRF token validation failed",{"x-csrf-token":"Required"});let p;if(i===Ue||i.startsWith(`${Ue}/`))p=this.handleCatalog(u,i.slice(Ue.length),o,n);else {if(i.startsWith(Ki))throw new T(404,"HTTP/404",`Resource not found for segment '${i}'`);{let g=st.find(f=>i===f.path||i.startsWith(`${f.path}/`));if(!g)throw new T(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${i}'`);p=this.handleService(g,u,i.slice(g.path.length),o,e,n);}}return p.headers={...p.headers,...c},l&&(p.body=""),p}checkAuth(e,n){let r=e.authorization??"",s=`Basic ${Buffer.from(`${this.options.username??ot}:${this.options.password??en}`).toString("base64")}`,i=a=>new T(401,"HTTP/401",a,{"www-authenticate":'Basic realm="SAP NetWeaver Application Server [DEMO/100]"'});if(r!==s)throw i("Anmeldung fehlgeschlagen");let o=n.get("sap-client");if(o!==(this.options.sapClient??tn))throw i(o?`Mandant ${o} existiert nicht`:"Anmeldung fehlgeschlagen")}handleCatalog(e,n,r,s){if(e!=="GET")throw new T(405,"HTTP/405","Method not allowed on catalog");let i=n.replace(/^\/+/,"");if(i==="")return this.json(200,{d:{EntitySets:["ServiceCollection"]}});if(i==="$metadata")return this.xml(200,'<?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"><edmx:DataServices m:DataServiceVersion="2.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><Schema Namespace="CATALOGSERVICE" xmlns="http://schemas.microsoft.com/ado/2008/09/edm"><EntityType Name="Service"><Key><PropertyRef Name="ID"/></Key><Property Name="ID" Type="Edm.String" Nullable="false"/><Property Name="TechnicalServiceName" Type="Edm.String"/><Property Name="Title" Type="Edm.String"/><Property Name="Description" Type="Edm.String"/><Property Name="ServiceUrl" Type="Edm.String"/><Property Name="TechnicalServiceVersion" Type="Edm.String"/></EntityType><EntityContainer Name="CATALOGSERVICE_Entities" m:IsDefaultEntityContainer="true"><EntitySet Name="ServiceCollection" EntityType="CATALOGSERVICE.Service"/></EntityContainer></Schema></edmx:DataServices></edmx:Edmx>');let o=Xt(i);if(o.entitySet!=="ServiceCollection")throw new T(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${i}'`);let a=[...st].map(u=>({__metadata:{id:`${s.base}${Ue}/ServiceCollection('${u.technicalName}_0001')`,uri:`${s.base}${Ue}/ServiceCollection('${u.technicalName}_0001')`,type:"CATALOGSERVICE.Service"},ID:`${u.technicalName}_0001`,TechnicalServiceName:u.technicalName,Title:u.title,Description:u.description,ServiceUrl:`${s.base}${u.path}`,TechnicalServiceVersion:"0001",MetadataUrl:`${s.base}${u.path}/$metadata`}));if(o.key){let u=String(o.key[0]?.value??""),m=a.find(p=>p.ID===u);if(!m)throw new T(404,"/IWFND/CM_MGW/002",`Service '${u}' not found`);return this.json(200,{d:m})}let c=zn(r),{rows:l,count:d}=Zt(a,c);return this.json(200,{d:{results:l,...c.inlinecount==="allpages"?{__count:String(d)}:{}}})}handleService(e,n,r,s,i,o){let a=r.replace(/^\/+/,"");if(a===""){if(n!=="GET")throw new T(405,"HTTP/405","Method not allowed");return this.json(200,{d:{EntitySets:e.entityTypes.map(l=>l.set)}})}if(a==="$metadata")return this.xml(200,Ln(e));if(a==="$batch"){if(n!=="POST")throw new T(405,"HTTP/405","Method not allowed on $batch");return this.handleBatch(e,i,o)}let c=e.functionImports.find(l=>l.name===a);if(c){if(n!==c.httpMethod&&!(n==="MERGE"&&c.httpMethod==="POST"))throw new T(405,"HTTP/405",`Function import ${c.name} requires ${c.httpMethod}`);return this.handleFunctionImport(e,c.name,s,o)}return this.handleEntity(e,n,a,s,i,o)}handleFunctionImport(e,n,r,s){let i=e.functionImports.find(a=>a.name===n),o={};for(let a of i.parameters){let c=r.get(a.name);if(c===null){if(!a.nullable)throw new T(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Parameter '${a.name}' is missing`);continue}o[a.name]=it(c);}if(n==="ReleaseSalesOrder"){let a=String(o.SalesOrder??""),c=this.rows("A_SalesOrder").find(l=>l.SalesOrder===a);if(!c)throw new T(404,"SD/001",`Sales order ${a} does not exist`);if(c.OverallSDProcessStatus==="C")throw new T(400,"SD/002",`Sales order ${a} is already completed and cannot be released`);return c.OverallSDProcessStatus="B",c.LastChangeDateTime=`/Date(${Date.now()})/`,this.bump("A_SalesOrder",c),this.json(200,{d:this.present(e,this.typeOf("A_SalesOrder"),c,s,{})})}if(n==="GetOpenSalesOrderCount"){let a=o.SoldToParty===void 0?void 0:String(o.SoldToParty),c=this.rows("A_SalesOrder").filter(l=>l.OverallSDProcessStatus!=="C"&&(a===void 0||l.SoldToParty===a)).length;return this.json(200,{d:{GetOpenSalesOrderCount:c}})}throw new T(501,"HTTP/501",`Function import ${n} not implemented`)}handleEntity(e,n,r,s,i,o){let a=i.body,c=i.headers["if-match"],l;try{l=Xt(r);}catch(h){throw h instanceof be?new T(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR",h.message):h}let d=e.entityTypes.find(h=>h.set===l.entitySet);if(!d)throw new T(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${l.entitySet}'`);let u=zn(s),m=this.rows(d.set),p;if(l.key&&(p=this.find(d,l.key),!p))throw new T(404,"/IWBEP/CM_MGW_RT/020",`Resource not found for the segment '${d.set}'`);let g=[...l.segments],f=false;if(g[g.length-1]==="$count"&&(f=true,g.pop()),g.length>0){if(!p)throw new T(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR","Navigation requires a key");if(g.length>1)throw new T(501,"HTTP/501","Only one navigation level is supported");let h=d.navigations.find(y=>y.name===g[0]);if(!h)throw new T(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${g[0]}'`);let S=this.typeOf(h.target),b=this.related(p,h);if(h.multiplicity==="1"){if(p=b[0],!p)throw new T(404,"/IWBEP/CM_MGW_RT/020",`No ${h.target} for this ${d.set}`);d=S,m=[];}else m=b,p=void 0,d=S;}switch(n){case "GET":{if(p){let b=this.json(200,{d:this.present(e,d,p,o,u)});return b.headers.etag=this.etag(d,p),b}if(f){let b=Zt(m,{...u,top:void 0,skip:void 0,raw:u.raw});return {status:200,headers:{"content-type":"text/plain"},body:String(b.count)}}let{rows:h,count:S}=Zt(m,u);return this.json(200,{d:{results:h.map(b=>this.present(e,d,b,o,u)),...u.inlinecount==="allpages"?{__count:String(S)}:{}}})}case "POST":{if(p||l.key)throw new T(405,"HTTP/405","POST on a single entity is not allowed (use MERGE/PATCH)");if(d.creatable===false)throw new T(405,"HTTP/405",`${d.set} is not creatable`);let h=this.parseBody(a),S=this.create(d,h,o),b=this.json(201,{d:this.present(e,d,S,o,{expand:this.deepExpands(d,h)})});return b.headers.location=this.uri(o,e,d,S),b.headers.etag=this.etag(d,S),b}case "MERGE":case "PATCH":case "PUT":{if(!p)throw new T(405,"HTTP/405",`${n} requires a single entity`);if(d.updatable===false)throw new T(405,"HTTP/405",`${d.set} is not updatable`);this.checkIfMatch(d,p,c);let h=this.parseBody(a);return this.update(d,p,h,n==="PUT"),{status:204,headers:{etag:this.etag(d,p),dataserviceversion:"2.0"},body:""}}case "DELETE":{if(!p)throw new T(405,"HTTP/405","DELETE requires a single entity");if(d.deletable===false)throw new T(405,"HTTP/405",`${d.set} is not deletable`);return this.checkIfMatch(d,p,c),this.remove(d,p),{status:204,headers:{dataserviceversion:"2.0"},body:""}}default:throw new T(405,"HTTP/405",`Method ${n} not allowed`)}}parseBody(e){if(!e||e.trim()==="")throw new T(400,"/IWCOR/CX_DS_BODY_MISSING","Request body is empty");let n;try{n=JSON.parse(e);}catch{throw new T(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR","Request body is not valid JSON")}if(n===null||typeof n!="object"||Array.isArray(n))throw new T(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR","Request body must be a JSON object");let r=n;return "d"in r&&typeof r.d=="object"&&r.d!==null&&Object.keys(r).length===1?r.d:r}create(e,n,r,s={}){let i={},o=[];for(let[a,c]of Object.entries(n)){if(a==="__metadata")continue;let l=e.navigations.find(u=>u.name===a);if(l){if(l.multiplicity!=="*")continue;let u=Array.isArray(c)?c:c&&typeof c=="object"&&Array.isArray(c.results)?c.results:[];o.push({nav:l,children:u});continue}let d=e.properties.find(u=>u.name===a);if(!d)throw new T(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Property '${a}' is invalid`);i[a]=this.coerce(d.name,d.type,c);}Object.assign(i,s);for(let a of e.keys)(i[a]===void 0||i[a]==="")&&(i[a]=this.nextKey(e,a,i));if(this.find(e,e.keys.map(a=>({name:a,value:i[a]}))))throw new T(400,"/IWBEP/CX_MGW_BUSI_EXCEPTION",`${e.set} with this key already exists`);for(let a of e.properties)i[a.name]===void 0&&(i[a.name]=this.emptyValue(a.type));this.defaults(e,i),this.rows(e.set).push(i),this.bump(e.set,i);for(let{nav:a,children:c}of o){let l=this.typeOf(a.target),d={};for(let[u,m]of a.join)d[m]=i[u];for(let u of c)this.create(l,u,r,d);}return this.recalculate(e,i),i}update(e,n,r,s){let i={};for(let[o,a]of Object.entries(r)){if(o==="__metadata"||e.navigations.some(l=>l.name===o))continue;let c=e.properties.find(l=>l.name===o);if(!c)throw new T(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Property '${o}' is invalid`);if(e.keys.includes(o)&&this.coerce(o,c.type,a)!==n[o])throw new T(400,"/IWBEP/CX_MGW_BUSI_EXCEPTION",`Key property '${o}' cannot be changed`);i[o]=this.coerce(c.name,c.type,a);}if(s)for(let o of e.properties)!e.keys.includes(o.name)&&!(o.name in i)&&(n[o.name]=this.emptyValue(o.type));if(Object.assign(n,i),(e.set==="A_SalesOrder"||e.set==="A_SalesOrderItem")&&(n.LastChangeDateTime=`/Date(${Date.now()})/`,e.set==="A_SalesOrderItem")){let o=this.rows("A_SalesOrder").find(a=>a.SalesOrder===n.SalesOrder);o&&this.recalculate(this.typeOf("A_SalesOrder"),o);}this.bump(e.set,n);}remove(e,n){let r=this.rows(e.set),s=r.indexOf(n);s!==-1&&r.splice(s,1),this.versions.delete(this.versionKey(e.set,n));for(let i of e.navigations){if(i.multiplicity!=="*")continue;let o=this.typeOf(i.target);for(let a of this.related(n,i))this.remove(o,a);}}checkIfMatch(e,n,r){if(r===void 0||r.trim()==="*")return;let s=this.etag(e,n);if(!r.split(",").map(o=>o.trim()).map(o=>o.startsWith("W/")?o:`W/${o}`).includes(s))throw new T(412,"/IWBEP/CM_MGW_RT/021","The entity has been changed by another user; reload and retry (ETag mismatch)")}handleBatch(e,n,r){let s=n.headers["content-type"]??"",i=/boundary=("?)([^";]+)\1/i.exec(s)?.[2];if(!i||!n.body)throw new T(400,"/IWCOR/CX_DS_BATCH","multipart/mixed body with boundary expected");let o=`batchresponse_${randomUUID()}`,a=[];for(let c of Gn(n.body,i)){let l=/content-type:\s*multipart\/mixed;\s*boundary=("?)([^";\r\n]+)\1/i.exec(c);if(l){let d=l[2],u=c.indexOf(`\r
|
|
41
|
+
`}var D=class extends Error{constructor(e){super(e),this.name="ODataSyntaxError";}},Yn=new Set(["and","or","not","eq","ne","gt","ge","lt","le"]);function Ji(t){let e=[],n=0;for(;n<t.length;){let r=t[n];if(/\s/.test(r)){n++;continue}if(r==="("){e.push({kind:"lparen"}),n++;continue}if(r===")"){e.push({kind:"rparen"}),n++;continue}if(r===","){e.push({kind:"comma"}),n++;continue}if(r==="'"){let{value:s,end:i}=Jn(t,n);e.push({kind:"string",value:s}),n=i;continue}if(/[0-9]/.test(r)||r==="-"&&/[0-9]/.test(t[n+1]??"")){let s=/^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?[mMdDfFlL]?/.exec(t.slice(n));if(!s)throw new D(`Ung\xFCltige Zahl an Position ${n}.`);e.push({kind:"number",value:Number(s[0].replace(/[mMdDfFlL]$/,""))}),n+=s[0].length;continue}if(/[A-Za-z_]/.test(r)){let i=/^[A-Za-z_][A-Za-z0-9_./]*/.exec(t.slice(n))[0];if(n+=i.length,t[n]==="'"){let{value:o,end:a}=Jn(t,n);n=a;let d=i.toLowerCase();if(d==="datetime"||d==="datetimeoffset"){let l=Zn(o);if(Number.isNaN(l))throw new D(`Ung\xFCltiges datetime-Literal '${o}'.`);e.push({kind:"datetime",value:l});}else if(d==="guid"||d==="x"||d==="binary"||d==="time")e.push({kind:"string",value:o});else throw new D(`Unbekanntes Literal-Pr\xE4fix '${i}'.`);continue}e.push({kind:"ident",value:i});continue}throw new D(`Unerwartetes Zeichen '${r}' an Position ${n}.`)}return e}function Jn(t,e){let n=e+1,r="";for(;n<t.length;){let s=t[n];if(s==="'"){if(t[n+1]==="'"){r+="'",n+=2;continue}return {value:r,end:n+1}}r+=s,n++;}throw new D("Zeichenkette ohne schlie\xDFendes Apostroph.")}function Zn(t){let e=/[zZ]|[+-]\d{2}:\d{2}$/.test(t)?t:`${t}Z`;return Date.parse(e)}var Xi=new Set(["substringof","startswith","endswith","tolower","toupper"]),tn=class{constructor(e){this.tokens=e;}pos=0;parse(){if(this.tokens.length===0)throw new D("Leerer $filter-Ausdruck.");let e=this.parseOr();if(this.pos<this.tokens.length)throw new D(`Unerwartetes Token nach dem Ausdruck: ${Xn(this.peek())}.`);return e}peek(){return this.tokens[this.pos]}isKeyword(e){let n=this.peek();return n?.kind==="ident"&&n.value.toLowerCase()===e}parseOr(){let e=this.parseAnd();for(;this.isKeyword("or");)this.pos++,e={type:"or",left:e,right:this.parseAnd()};return e}parseAnd(){let e=this.parseNot();for(;this.isKeyword("and");)this.pos++,e={type:"and",left:e,right:this.parseNot()};return e}parseNot(){return this.isKeyword("not")?(this.pos++,{type:"not",operand:this.parseNot()}):this.parseComparison()}parseComparison(){let e=this.parsePrimary(),n=this.peek();if(n?.kind==="ident"){let r=n.value.toLowerCase();if(r==="eq"||r==="ne"||r==="gt"||r==="ge"||r==="lt"||r==="le"){this.pos++;let s=this.parsePrimary();return {type:"compare",op:r,left:e,right:s}}if(!Yn.has(r))throw new D(`Unbekannter Operator '${n.value}'.`)}return e}parsePrimary(){let e=this.peek();if(!e)throw new D("Ausdruck endet unerwartet.");if(e.kind==="lparen"){this.pos++;let n=this.parseOr();if(this.peek()?.kind!=="rparen")throw new D("Schlie\xDFende Klammer fehlt.");return this.pos++,n}if(e.kind==="string")return this.pos++,{type:"literal",value:e.value};if(e.kind==="number")return this.pos++,{type:"literal",value:e.value};if(e.kind==="datetime")return this.pos++,{type:"literal",value:e.value,datetime:true};if(e.kind==="ident"){let n=e.value.toLowerCase();if(n==="true"||n==="false")return this.pos++,{type:"literal",value:n==="true"};if(n==="null")return this.pos++,{type:"literal",value:null};if(Yn.has(n))throw new D(`Operand erwartet, aber '${e.value}' gefunden.`);if(this.pos++,this.peek()?.kind==="lparen"){if(!Xi.has(n))throw new D(`Unbekannte Funktion '${e.value}'.`);this.pos++;let r=[];if(this.peek()?.kind!=="rparen")for(r.push(this.parseOr());this.peek()?.kind==="comma";)this.pos++,r.push(this.parseOr());if(this.peek()?.kind!=="rparen")throw new D(`Schlie\xDFende Klammer nach ${e.value}(...) fehlt.`);return this.pos++,{type:"call",fn:n,args:r}}return {type:"field",path:e.value}}throw new D(`Unerwartetes Token ${Xn(e)}.`)}};function Xn(t){if(!t)return "(Ende)";switch(t.kind){case "lparen":return "'('";case "rparen":return "')'";case "comma":return "','";default:return `'${String(t.value)}'`}}var Zi=/^\/Date\((-?\d+)(?:[+-]\d{4})?\)\/$/;function Qi(t,e){let n=t;for(let r of e.split("/")){if(n===null||typeof n!="object")return null;n=n[r];}return n===void 0?null:n}function W(t,e){switch(t.type){case "and":return ye(W(t.left,e))&&ye(W(t.right,e));case "or":return ye(W(t.left,e))||ye(W(t.right,e));case "not":return !ye(W(t.operand,e));case "literal":return t.datetime?{datetime:t.value}:t.value;case "field":return Qi(e,t.path);case "call":{let n=t.args.map(s=>W(s,e)),r=s=>s==null?"":String(s);switch(t.fn){case "substringof":return r(n[1]).includes(r(n[0]));case "startswith":return r(n[0]).startsWith(r(n[1]));case "endswith":return r(n[0]).endsWith(r(n[1]));case "tolower":return r(n[0]).toLowerCase();case "toupper":return r(n[0]).toUpperCase();default:throw new D(`Unbekannte Funktion '${t.fn}'.`)}}case "compare":{let[n,r]=eo(W(t.left,e),W(t.right,e));switch(t.op){case "eq":return n===r;case "ne":return n!==r;case "gt":return n!==null&&r!==null&&n>r;case "ge":return n!==null&&r!==null&&n>=r;case "lt":return n!==null&&r!==null&&n<r;case "le":return n!==null&&r!==null&&n<=r}}}}function ye(t){return t===true}function eo(t,e){let n=i=>typeof i=="object"&&i!==null&&"datetime"in i;if(n(t)||n(e)){let i=o=>{if(n(o))return o.datetime;if(typeof o=="number")return o;if(typeof o=="string"){let a=Zi.exec(o);if(a)return Number(a[1]);let d=Zn(o);return Number.isNaN(d)?null:d}return null};return [i(t),i(e)]}let r=i=>{if(typeof i=="number")return i;if(typeof i=="string"&&i.trim()!==""&&!Number.isNaN(Number(i)))return Number(i)};if((typeof t=="number"||typeof e=="number")&&!(t===null||e===null)){let i=r(t),o=r(e);if(i!==void 0&&o!==void 0)return [i,o]}let s=i=>i===void 0?null:typeof i=="object"&&i!==null?String(i):i;return [s(t),s(e)]}function Qn(t){let e=new tn(Ji(t)).parse();return n=>ye(W(e,n))}var Se=class extends Error{constructor(e){super(e),this.name="ODataUrlError";}};function ct(t){let e=t.trim();if(e.startsWith("'"))return to(e);let n=/^(guid|datetime|datetimeoffset|time|X|binary)'(.*)'$/is.exec(e);return n?n[2].replace(/''/g,"'"):/^-?\d+(\.\d+)?[mMdDfFlL]?$/.test(e)?Number(e.replace(/[mMdDfFlL]$/,"")):e==="true"?true:e==="false"?false:e}function to(t){if(!t.endsWith("'")||t.length<2)throw new Se(`Zeichenkette ohne schlie\xDFendes Apostroph: ${t}`);return t.slice(1,-1).replace(/''/g,"'")}function no(t){let e=nn(t);return so(e,",").map(r=>{let s=io(r,"=");return s===-1?{value:ct(r)}:{name:r.slice(0,s).trim(),value:ct(r.slice(s+1))}})}function rn(t){let e=t.replace(/^\/+|\/+$/g,""),n=e.indexOf("("),r=e.indexOf("/"),s,i,o;if(n!==-1&&(r===-1||n<r)){s=e.slice(0,n);let l=ro(e,n);i=no(e.slice(n+1,l)),o=e.slice(l+1);}else r!==-1?(s=e.slice(0,r),o=e.slice(r)):(s=e,o="");let a=o.split("/").map(l=>nn(l)).filter(l=>l.length>0),d={entitySet:nn(s),segments:a};return i&&(d.key=i),d}function ro(t,e){let n=false;for(let r=e+1;r<t.length;r++){let s=t[r];if(s==="'"){n=!n;continue}if(!n&&s===")")return r}throw new Se(`Schlie\xDFende Klammer fehlt in: ${t}`)}function so(t,e){let n=[],r="",s=false;for(let i=0;i<t.length;i++){let o=t[i];if(o==="'"){s=!s,r+=o;continue}if(!s&&o===e){n.push(r),r="";continue}r+=o;}return n.push(r),n.filter(i=>i.trim().length>0)}function io(t,e){let n=false;for(let r=0;r<t.length;r++)if(t[r]==="'")n=!n;else if(!n&&t[r]===e)return r;return -1}function nn(t){try{return decodeURIComponent(t)}catch{return t}}var dt="DEMO",an="demo",cn="100",Ue="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2",oo="/sap/opu/odata4/",T=class extends Error{constructor(n,r,s,i={}){super(s);this.status=n;this.code=r;this.headers=i;}},lt=class{constructor(e={}){this.options=e;for(let n of at)for(let r of n.entityTypes)this.types.set(r.set,{type:r,service:n});}data=en();versions=new Map;csrfToken=randomBytes(16).toString("base64url");cookie=`SAP_SESSIONID_DEMO_100=${randomBytes(12).toString("base64url")}`;types=new Map;nextNumber=new Map;reset(){this.data=en(),this.versions.clear(),this.nextNumber.clear();}rows(e){return this.data[e]??[]}handle(e){let r={base:this.options.baseUrl??`http://${e.headers.host??"127.0.0.1"}`,inBatch:false};try{return this.dispatch(e,r)}catch(s){return this.errorResponse(s)}}dispatch(e,n){let r=e.method.toUpperCase(),s=new URL(e.url,n.base),i=s.pathname.replace(/\/+$/,"")||"/",o=s.searchParams;n.inBatch||this.checkAuth(e.headers,o);let d=(e.headers["x-csrf-token"]??"").toLowerCase()==="fetch"?{"x-csrf-token":this.csrfToken,"set-cookie":`${this.cookie}; path=/`}:{},l=r==="HEAD",c=l?"GET":r,u=c==="POST"&&(e.headers["x-http-method"]??"").toUpperCase()==="MERGE"?"MERGE":c;if(u!=="GET"&&!n.inBatch&&e.headers["x-csrf-token"]!==this.csrfToken)throw new T(403,"HTTP/403","CSRF token validation failed",{"x-csrf-token":"Required"});let m;if(i===Ue||i.startsWith(`${Ue}/`))m=this.handleCatalog(u,i.slice(Ue.length),o,n);else {if(i.startsWith(oo))throw new T(404,"HTTP/404",`Resource not found for segment '${i}'`);{let g=at.find(f=>i===f.path||i.startsWith(`${f.path}/`));if(!g)throw new T(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${i}'`);m=this.handleService(g,u,i.slice(g.path.length),o,e,n);}}return m.headers={...m.headers,...d},l&&(m.body=""),m}checkAuth(e,n){let r=e.authorization??"",s=`Basic ${Buffer.from(`${this.options.username??dt}:${this.options.password??an}`).toString("base64")}`,i=a=>new T(401,"HTTP/401",a,{"www-authenticate":'Basic realm="SAP NetWeaver Application Server [DEMO/100]"'});if(r!==s)throw i("Anmeldung fehlgeschlagen");let o=n.get("sap-client");if(o!==(this.options.sapClient??cn))throw i(o?`Mandant ${o} existiert nicht`:"Anmeldung fehlgeschlagen")}handleCatalog(e,n,r,s){if(e!=="GET")throw new T(405,"HTTP/405","Method not allowed on catalog");let i=n.replace(/^\/+/,"");if(i==="")return this.json(200,{d:{EntitySets:["ServiceCollection"]}});if(i==="$metadata")return this.xml(200,'<?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx"><edmx:DataServices m:DataServiceVersion="2.0" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"><Schema Namespace="CATALOGSERVICE" xmlns="http://schemas.microsoft.com/ado/2008/09/edm"><EntityType Name="Service"><Key><PropertyRef Name="ID"/></Key><Property Name="ID" Type="Edm.String" Nullable="false"/><Property Name="TechnicalServiceName" Type="Edm.String"/><Property Name="Title" Type="Edm.String"/><Property Name="Description" Type="Edm.String"/><Property Name="ServiceUrl" Type="Edm.String"/><Property Name="TechnicalServiceVersion" Type="Edm.String"/></EntityType><EntityContainer Name="CATALOGSERVICE_Entities" m:IsDefaultEntityContainer="true"><EntitySet Name="ServiceCollection" EntityType="CATALOGSERVICE.Service"/></EntityContainer></Schema></edmx:DataServices></edmx:Edmx>');let o=rn(i);if(o.entitySet!=="ServiceCollection")throw new T(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${i}'`);let a=[...at].map(u=>({__metadata:{id:`${s.base}${Ue}/ServiceCollection('${u.technicalName}_0001')`,uri:`${s.base}${Ue}/ServiceCollection('${u.technicalName}_0001')`,type:"CATALOGSERVICE.Service"},ID:`${u.technicalName}_0001`,TechnicalServiceName:u.technicalName,Title:u.title,Description:u.description,ServiceUrl:`${s.base}${u.path}`,TechnicalServiceVersion:"0001",MetadataUrl:`${s.base}${u.path}/$metadata`}));if(o.key){let u=String(o.key[0]?.value??""),p=a.find(m=>m.ID===u);if(!p)throw new T(404,"/IWFND/CM_MGW/002",`Service '${u}' not found`);return this.json(200,{d:p})}let d=nr(r),{rows:l,count:c}=sn(a,d);return this.json(200,{d:{results:l,...d.inlinecount==="allpages"?{__count:String(c)}:{}}})}handleService(e,n,r,s,i,o){let a=r.replace(/^\/+/,"");if(a===""){if(n!=="GET")throw new T(405,"HTTP/405","Method not allowed");return this.json(200,{d:{EntitySets:e.entityTypes.map(l=>l.set)}})}if(a==="$metadata")return this.xml(200,Gn(e));if(a==="$batch"){if(n!=="POST")throw new T(405,"HTTP/405","Method not allowed on $batch");return this.handleBatch(e,i,o)}let d=e.functionImports.find(l=>l.name===a);if(d){if(n!==d.httpMethod&&!(n==="MERGE"&&d.httpMethod==="POST"))throw new T(405,"HTTP/405",`Function import ${d.name} requires ${d.httpMethod}`);return this.handleFunctionImport(e,d.name,s,o)}return this.handleEntity(e,n,a,s,i,o)}handleFunctionImport(e,n,r,s){let i=e.functionImports.find(a=>a.name===n),o={};for(let a of i.parameters){let d=r.get(a.name);if(d===null){if(!a.nullable)throw new T(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Parameter '${a.name}' is missing`);continue}o[a.name]=ct(d);}if(n==="ReleaseSalesOrder"){let a=String(o.SalesOrder??""),d=this.rows("A_SalesOrder").find(l=>l.SalesOrder===a);if(!d)throw new T(404,"SD/001",`Sales order ${a} does not exist`);if(d.OverallSDProcessStatus==="C")throw new T(400,"SD/002",`Sales order ${a} is already completed and cannot be released`);return d.OverallSDProcessStatus="B",d.LastChangeDateTime=`/Date(${Date.now()})/`,this.bump("A_SalesOrder",d),this.json(200,{d:this.present(e,this.typeOf("A_SalesOrder"),d,s,{})})}if(n==="GetOpenSalesOrderCount"){let a=o.SoldToParty===void 0?void 0:String(o.SoldToParty),d=this.rows("A_SalesOrder").filter(l=>l.OverallSDProcessStatus!=="C"&&(a===void 0||l.SoldToParty===a)).length;return this.json(200,{d:{GetOpenSalesOrderCount:d}})}throw new T(501,"HTTP/501",`Function import ${n} not implemented`)}handleEntity(e,n,r,s,i,o){let a=i.body,d=i.headers["if-match"],l;try{l=rn(r);}catch(h){throw h instanceof Se?new T(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR",h.message):h}let c=e.entityTypes.find(h=>h.set===l.entitySet);if(!c)throw new T(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${l.entitySet}'`);let u=nr(s),p=this.rows(c.set),m;if(l.key&&(m=this.find(c,l.key),!m))throw new T(404,"/IWBEP/CM_MGW_RT/020",`Resource not found for the segment '${c.set}'`);let g=[...l.segments],f=false;if(g[g.length-1]==="$count"&&(f=true,g.pop()),g.length>0){if(!m)throw new T(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR","Navigation requires a key");if(g.length>1)throw new T(501,"HTTP/501","Only one navigation level is supported");let h=c.navigations.find(y=>y.name===g[0]);if(!h)throw new T(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${g[0]}'`);let b=this.typeOf(h.target),S=this.related(m,h);if(h.multiplicity==="1"){if(m=S[0],!m)throw new T(404,"/IWBEP/CM_MGW_RT/020",`No ${h.target} for this ${c.set}`);c=b,p=[];}else p=S,m=void 0,c=b;}switch(n){case "GET":{if(m){let S=this.json(200,{d:this.present(e,c,m,o,u)});return S.headers.etag=this.etag(c,m),S}if(f){let S=sn(p,{...u,top:void 0,skip:void 0,raw:u.raw});return {status:200,headers:{"content-type":"text/plain"},body:String(S.count)}}let{rows:h,count:b}=sn(p,u);return this.json(200,{d:{results:h.map(S=>this.present(e,c,S,o,u)),...u.inlinecount==="allpages"?{__count:String(b)}:{}}})}case "POST":{if(m||l.key)throw new T(405,"HTTP/405","POST on a single entity is not allowed (use MERGE/PATCH)");if(c.creatable===false)throw new T(405,"HTTP/405",`${c.set} is not creatable`);let h=this.parseBody(a),b=this.create(c,h,o),S=this.json(201,{d:this.present(e,c,b,o,{expand:this.deepExpands(c,h)})});return S.headers.location=this.uri(o,e,c,b),S.headers.etag=this.etag(c,b),S}case "MERGE":case "PATCH":case "PUT":{if(!m)throw new T(405,"HTTP/405",`${n} requires a single entity`);if(c.updatable===false)throw new T(405,"HTTP/405",`${c.set} is not updatable`);this.checkIfMatch(c,m,d);let h=this.parseBody(a);return this.update(c,m,h,n==="PUT"),{status:204,headers:{etag:this.etag(c,m),dataserviceversion:"2.0"},body:""}}case "DELETE":{if(!m)throw new T(405,"HTTP/405","DELETE requires a single entity");if(c.deletable===false)throw new T(405,"HTTP/405",`${c.set} is not deletable`);return this.checkIfMatch(c,m,d),this.remove(c,m),{status:204,headers:{dataserviceversion:"2.0"},body:""}}default:throw new T(405,"HTTP/405",`Method ${n} not allowed`)}}parseBody(e){if(!e||e.trim()==="")throw new T(400,"/IWCOR/CX_DS_BODY_MISSING","Request body is empty");let n;try{n=JSON.parse(e);}catch{throw new T(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR","Request body is not valid JSON")}if(n===null||typeof n!="object"||Array.isArray(n))throw new T(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR","Request body must be a JSON object");let r=n;return "d"in r&&typeof r.d=="object"&&r.d!==null&&Object.keys(r).length===1?r.d:r}create(e,n,r,s={}){let i={},o=[];for(let[a,d]of Object.entries(n)){if(a==="__metadata")continue;let l=e.navigations.find(u=>u.name===a);if(l){if(l.multiplicity!=="*")continue;let u=Array.isArray(d)?d:d&&typeof d=="object"&&Array.isArray(d.results)?d.results:[];o.push({nav:l,children:u});continue}let c=e.properties.find(u=>u.name===a);if(!c)throw new T(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Property '${a}' is invalid`);i[a]=this.coerce(c.name,c.type,d);}Object.assign(i,s);for(let a of e.keys)(i[a]===void 0||i[a]==="")&&(i[a]=this.nextKey(e,a,i));if(this.find(e,e.keys.map(a=>({name:a,value:i[a]}))))throw new T(400,"/IWBEP/CX_MGW_BUSI_EXCEPTION",`${e.set} with this key already exists`);for(let a of e.properties)i[a.name]===void 0&&(i[a.name]=this.emptyValue(a.type));this.defaults(e,i),this.rows(e.set).push(i),this.bump(e.set,i);for(let{nav:a,children:d}of o){let l=this.typeOf(a.target),c={};for(let[u,p]of a.join)c[p]=i[u];for(let u of d)this.create(l,u,r,c);}return this.recalculate(e,i),i}update(e,n,r,s){let i={};for(let[o,a]of Object.entries(r)){if(o==="__metadata"||e.navigations.some(l=>l.name===o))continue;let d=e.properties.find(l=>l.name===o);if(!d)throw new T(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Property '${o}' is invalid`);if(e.keys.includes(o)&&this.coerce(o,d.type,a)!==n[o])throw new T(400,"/IWBEP/CX_MGW_BUSI_EXCEPTION",`Key property '${o}' cannot be changed`);i[o]=this.coerce(d.name,d.type,a);}if(s)for(let o of e.properties)!e.keys.includes(o.name)&&!(o.name in i)&&(n[o.name]=this.emptyValue(o.type));if(Object.assign(n,i),(e.set==="A_SalesOrder"||e.set==="A_SalesOrderItem")&&(n.LastChangeDateTime=`/Date(${Date.now()})/`,e.set==="A_SalesOrderItem")){let o=this.rows("A_SalesOrder").find(a=>a.SalesOrder===n.SalesOrder);o&&this.recalculate(this.typeOf("A_SalesOrder"),o);}this.bump(e.set,n);}remove(e,n){let r=this.rows(e.set),s=r.indexOf(n);s!==-1&&r.splice(s,1),this.versions.delete(this.versionKey(e.set,n));for(let i of e.navigations){if(i.multiplicity!=="*")continue;let o=this.typeOf(i.target);for(let a of this.related(n,i))this.remove(o,a);}}checkIfMatch(e,n,r){if(r===void 0||r.trim()==="*")return;let s=this.etag(e,n);if(!r.split(",").map(o=>o.trim()).map(o=>o.startsWith("W/")?o:`W/${o}`).includes(s))throw new T(412,"/IWBEP/CM_MGW_RT/021","The entity has been changed by another user; reload and retry (ETag mismatch)")}handleBatch(e,n,r){let s=n.headers["content-type"]??"",i=/boundary=("?)([^";]+)\1/i.exec(s)?.[2];if(!i||!n.body)throw new T(400,"/IWCOR/CX_DS_BATCH","multipart/mixed body with boundary expected");let o=`batchresponse_${randomUUID()}`,a=[];for(let d of rr(n.body,i)){let l=/content-type:\s*multipart\/mixed;\s*boundary=("?)([^";\r\n]+)\1/i.exec(d);if(l){let c=l[2],u=d.indexOf(`\r
|
|
42
42
|
\r
|
|
43
|
-
`),
|
|
44
|
-
${
|
|
45
|
-
${
|
|
43
|
+
`),p=u===-1?"":d.slice(u+4),m=this.snapshot(),g=[],f;for(let h of rr(p,c)){let b=this.runBatchPart(e,h,r);if(b.status>=400){f=b;break}g.push(b);}if(f)this.restore(m),a.push(`--${o}\r
|
|
44
|
+
${on(f)}`);else {let h=`changesetresponse_${randomUUID()}`,b=g.map(S=>`--${h}\r
|
|
45
|
+
${on(S)}`);a.push(`--${o}\r
|
|
46
46
|
Content-Type: multipart/mixed; boundary=${h}\r
|
|
47
47
|
\r
|
|
48
|
-
${
|
|
48
|
+
${b.join(`\r
|
|
49
49
|
`)}\r
|
|
50
50
|
--${h}--`);}continue}a.push(`--${o}\r
|
|
51
|
-
${
|
|
51
|
+
${on(this.runBatchPart(e,d,r))}`);}return a.push(`--${o}--`),{status:202,headers:{"content-type":`multipart/mixed; boundary=${o}`,dataserviceversion:"2.0"},body:`${a.join(`\r
|
|
52
52
|
`)}\r
|
|
53
|
-
`}}runBatchPart(e,n,r){let s=
|
|
53
|
+
`}}runBatchPart(e,n,r){let s=co(n);if(!s)return this.errorResponse(new T(400,"/IWCOR/CX_DS_BATCH","Malformed batch part"));let i=/^https?:\/\//.test(s.url)||s.url.startsWith("/")?s.url:`${e.path}/${s.url}`;try{return this.dispatch({method:s.method,url:i,headers:s.headers,body:s.body},{...r,inBatch:!0})}catch(o){return this.errorResponse(o)}}snapshot(){return {data:Object.fromEntries(Object.entries(this.data).map(([e,n])=>[e,n.map(r=>({...r}))])),versions:new Map(this.versions)}}restore(e){this.data=e.data,this.versions=e.versions;}present(e,n,r,s,i){let o=this.uri(s,e,n,r),a={__metadata:{id:o,uri:o,type:`${e.namespace}.${n.set}Type`,etag:this.etag(n,r)}},d=i.select&&i.select.length>0?new Set(i.select.map(c=>c.split("/")[0])):void 0;if(d){for(let c of d)if(!n.properties.some(u=>u.name===c)&&!n.navigations.some(u=>u.name===c))throw new T(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Property '${c}' is invalid for $select`)}for(let c of n.properties)d&&!d.has(c.name)||(a[c.name]=r[c.name]??null);let l=new Set((i.expand??[]).map(c=>c.split("/")[0]));for(let c of l)if(!n.navigations.some(u=>u.name===c))throw new T(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Navigation property '${c}' is invalid for $expand`);for(let c of n.navigations)if(!(d&&!d.has(c.name)&&!l.has(c.name)))if(l.has(c.name)){let u=this.typeOf(c.target),p=this.types.get(c.target).service,m=this.related(r,c).map(g=>this.present(p,u,g,s,{}));a[c.name]=c.multiplicity==="*"?{results:m}:m[0]??null;}else a[c.name]={__deferred:{uri:`${o}/${c.name}`}};return a}uri(e,n,r,s){let i=r.keys.length===1?`'${encodeURIComponent(String(s[r.keys[0]]))}'`:r.keys.map(o=>`${o}='${encodeURIComponent(String(s[o]))}'`).join(",");return `${e.base}${n.path}/${r.set}(${i})`}json(e,n){return {status:e,headers:{"content-type":"application/json; charset=utf-8",dataserviceversion:"2.0"},body:JSON.stringify(n)}}xml(e,n){return {status:e,headers:{"content-type":"application/xml",dataserviceversion:"2.0"},body:n}}errorResponse(e){return e instanceof D&&(e=new T(400,"/IWCOR/CX_DS_EXPR_SYNTAX_ERROR",`Invalid $filter: ${e.message}`)),e instanceof T?{status:e.status,headers:{"content-type":"application/json; charset=utf-8",dataserviceversion:"2.0",...e.headers},body:JSON.stringify({error:{code:e.code,message:{lang:"en",value:e.message},innererror:{application:{component_id:"DEMO",service_namespace:"/SAP/",service_id:"MOCK",service_version:"0001"},errordetails:[]}}})}:{status:500,headers:{"content-type":"application/json; charset=utf-8"},body:JSON.stringify({error:{code:"HTTP/500",message:{lang:"en",value:e instanceof Error?e.message:String(e)}}})}}typeOf(e){let n=this.types.get(e);if(!n)throw new T(404,"/IWFND/CM_MGW/002",`Entity set ${e} unknown`);return n.type}find(e,n){let r={};if(n.length===1&&n[0].name===void 0)r[e.keys[0]]=String(n[0].value);else for(let s of n){if(!s.name||!e.keys.includes(s.name))throw new T(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR",`Invalid key '${s.name??s.value}' for ${e.set}`);r[s.name]=String(s.value);}if(Object.keys(r).length!==e.keys.length)throw new T(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR",`Key of ${e.set} needs ${e.keys.join(", ")}`);return this.rows(e.set).find(s=>e.keys.every(i=>String(s[i])===r[i]))}related(e,n){return this.rows(n.target).filter(r=>n.join.every(([s,i])=>r[i]===e[s]))}nextKey(e,n,r){if(e.set==="A_SalesOrderItem"&&n==="SalesOrderItem"){let l=this.rows(e.set).filter(c=>c.SalesOrder===r.SalesOrder).reduce((c,u)=>Math.max(c,Number(u.SalesOrderItem)||0),0);return String(l+10)}if(e.set==="A_ProductDescription"&&n==="Language")return "EN";let s=e.autoKey;if(!s)throw new T(400,"/IWBEP/CX_MGW_BUSI_EXCEPTION",`Key '${n}' of ${e.set} is required`);let i=this.nextNumber.get(e.set);if(i===void 0){let d=this.rows(e.set).map(l=>String(l[n])).filter(l=>l.startsWith(s.prefix)).map(l=>Number(l.slice(s.prefix.length))).filter(l=>Number.isFinite(l));i=(d.length>0?Math.max(...d):0)+1;}this.nextNumber.set(e.set,i+1);let o=String(i).padStart(Math.max(1,s.width-s.prefix.length),"0"),a=`${s.prefix}${o}`;return this.rows(e.set).some(d=>String(d[n])===a)?this.nextKey(e,n,r):a}coerce(e,n,r){if(r==null)return null;switch(n){case "Edm.Boolean":if(typeof r=="boolean")return r;if(r==="true"||r==="X")return true;if(r==="false"||r==="")return false;throw new T(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR",`Property '${e}' expects a boolean`);case "Edm.Decimal":{let s=Number(r);if(typeof r=="boolean"||Number.isNaN(s))throw new T(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR",`Property '${e}' expects a decimal number`);return s.toFixed(3)}case "Edm.Int32":{let s=Number(r);if(!Number.isInteger(s))throw new T(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR",`Property '${e}' expects an integer`);return s}case "Edm.DateTime":{if(typeof r=="string"&&/^\/Date\(-?\d+/.test(r))return r;let s=typeof r=="number"?r:Date.parse(String(r));if(Number.isNaN(s))throw new T(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR",`Property '${e}' expects a date (ISO 8601 or /Date(ms)/)`);return `/Date(${s})/`}default:return String(r)}}emptyValue(e){switch(e){case "Edm.Boolean":return false;case "Edm.Decimal":return "0.000";case "Edm.Int32":return 0;case "Edm.DateTime":return null;default:return ""}}defaults(e,n){let r=`/Date(${Date.now()})/`;if(e.properties.some(s=>s.name==="CreationDate")&&!n.CreationDate&&(n.CreationDate=r),e.properties.some(s=>s.name==="CreatedByUser")&&!n.CreatedByUser&&(n.CreatedByUser=dt),e.set==="A_SalesOrder"&&(n.SalesOrderType||="OR",n.SalesOrganization||="1010",n.DistributionChannel||="10",n.OrganizationDivision||="00",n.TransactionCurrency||="EUR",n.OverallSDProcessStatus||="A",n.OverallDeliveryStatus||="A",n.LastChangeDateTime||=r),e.set==="A_SalesOrderItem"&&(n.SalesOrderItemCategory||="TAN",n.RequestedQuantityUnit||="ST",n.TransactionCurrency||="EUR",n.ProductionPlant||="1010",!n.SalesOrderItemText)){let s=this.rows("A_ProductDescription").find(i=>i.Product===n.Material&&i.Language==="EN");n.SalesOrderItemText=s?.ProductDescription??n.Material??"";}if(e.set==="A_BusinessPartner"){n.BusinessPartnerCategory||="2";let s=String(n.BusinessPartnerName||n.OrganizationBPName1||`${n.FirstName??""} ${n.LastName??""}`.trim());n.BusinessPartnerName||=s,n.BusinessPartnerFullName||=s,n.BusinessPartnerGrouping||=n.BusinessPartnerCategory==="1"?"BP02":"BP01";}e.set==="A_BusinessPartnerAddress"&&(n.Country||="DE",n.Language||="DE",n.ValidityStartDate||=r,n.ValidityEndDate||="/Date(253402214400000)/"),e.set==="A_Product"&&(n.ProductType||="HAWA",n.BaseUnit||="ST",n.WeightUnit||="KG");}recalculate(e,n){if(e.set!=="A_SalesOrder")return;let s=this.rows("A_SalesOrderItem").filter(i=>i.SalesOrder===n.SalesOrder).reduce((i,o)=>i+Number(o.NetAmount??0),0);n.TotalNetAmount=s.toFixed(3);}versionKey(e,n){let r=this.typeOf(e);return `${e}|${r.keys.map(s=>String(n[s])).join("|")}`}bump(e,n){let r=this.versionKey(e,n);this.versions.set(r,(this.versions.get(r)??1)+1);}etag(e,n){return `W/"${this.versions.get(this.versionKey(e.set,n))??1}"`}deepExpands(e,n){return e.navigations.filter(r=>r.name in n).map(r=>r.name)}};function nr(t){let e=s=>{let i=t.get(s);if(i===null)return;let o=Number(i);if(!Number.isInteger(o)||o<0)throw new T(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR",`Invalid value '${i}' for ${s}`);return o},n=s=>{let i=t.get(s);if(!(i===null||i.trim()===""))return i.split(",").map(o=>o.trim()).filter(o=>o.length>0)},r=t.get("$inlinecount")??void 0;if(r!==void 0&&r!=="allpages"&&r!=="none")throw new T(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR",`Invalid value '${r}' for $inlinecount`);return {filter:t.get("$filter")??void 0,orderby:t.get("$orderby")??void 0,top:e("$top"),skip:e("$skip"),select:n("$select"),expand:n("$expand"),inlinecount:r,raw:t}}function sn(t,e){let n=t;if(e.filter&&(n=n.filter(Qn(e.filter))),e.orderby){let o=e.orderby.split(",").map(a=>{let[d,l]=a.trim().split(/\s+/);if(!d)throw new T(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR","Empty $orderby clause");return {field:d,desc:(l??"asc").toLowerCase()==="desc"}});n=[...n].sort((a,d)=>{for(let{field:l,desc:c}of o){let u=ao(a[l],d[l]);if(u!==0)return c?-u:u}return 0});}let r=n.length,s=e.skip??0,i=e.top;return n=n.slice(s,i===void 0?void 0:s+i),{rows:n,count:r}}function ao(t,e){if(t===e)return 0;if(t==null)return -1;if(e==null)return 1;let n=Number(t),r=Number(e),s=typeof t=="string"?/^\/Date\((-?\d+)/.exec(t)?.[1]:void 0,i=typeof e=="string"?/^\/Date\((-?\d+)/.exec(e)?.[1]:void 0;return s!==void 0&&i!==void 0?Number(s)-Number(i):!Number.isNaN(n)&&!Number.isNaN(r)&&String(t).trim()!==""&&String(e).trim()!==""?n-r:String(t)<String(e)?-1:1}function rr(t,e){let n=[],r=`--${e}`,s=t.split(r);for(let i=1;i<s.length;i++){let o=s[i];if(o.startsWith("--"))break;o=o.replace(/^\r?\n/,"").replace(/\r?\n$/,""),o.trim().length>0&&n.push(o);}return n}function co(t){let e=t.replace(/\r\n/g,`
|
|
54
54
|
`),n=/^(GET|POST|PUT|PATCH|DELETE|MERGE|HEAD)\s+(\S+)\s+HTTP\/1\.\d\s*$/im.exec(e);if(!n)return;let r=e.slice(n.index+n[0].length).replace(/^\n/,""),s=r.indexOf(`
|
|
55
55
|
|
|
56
|
-
`),i=s===-1?r:r.slice(0,s),o=s===-1?void 0:r.slice(s+2).trim()||void 0,a={};for(let
|
|
57
|
-
`)){let l=
|
|
58
|
-
`)}function
|
|
56
|
+
`),i=s===-1?r:r.slice(0,s),o=s===-1?void 0:r.slice(s+2).trim()||void 0,a={};for(let d of i.split(`
|
|
57
|
+
`)){let l=d.indexOf(":");l!==-1&&(a[d.slice(0,l).trim().toLowerCase()]=d.slice(l+1).trim());}return {method:n[1].toUpperCase(),url:n[2],headers:a,body:o}}var lo={200:"OK",201:"Created",202:"Accepted",204:"No Content",400:"Bad Request",401:"Unauthorized",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",412:"Precondition Failed",500:"Internal Server Error",501:"Not Implemented"};function on(t){let e=["Content-Type: application/http","Content-Transfer-Encoding: binary","",`HTTP/1.1 ${t.status} ${lo[t.status]??""}`.trimEnd()];for(let[n,r]of Object.entries(t.headers))e.push(`${n}: ${r}`);return e.push(`Content-Length: ${Buffer.byteLength(t.body,"utf8")}`),e.push(""),e.push(t.body),e.join(`\r
|
|
58
|
+
`)}function sr(t){return {name:"default",baseUrl:t,authType:"basic",username:dt,password:an,sapClient:cn}}async function ir(t={}){let e=new lt,n=createServer((i,o)=>{let a=[];i.on("data",d=>a.push(d)),i.on("end",()=>{let d={};for(let[c,u]of Object.entries(i.headers))d[c.toLowerCase()]=Array.isArray(u)?u.join(", "):u;let l=e.handle({method:i.method??"GET",url:i.url??"/",headers:d,body:a.length>0?Buffer.concat(a).toString("utf8"):void 0});o.writeHead(l.status,l.headers),o.end(l.body);});});await new Promise((i,o)=>{n.once("error",o),n.listen(t.port??0,"127.0.0.1",()=>i());});let r=n.address(),s=typeof r=="object"&&r!==null?r.port:t.port??0;return {baseUrl:`http://127.0.0.1:${s}`,port:s,mock:e,close:()=>new Promise((i,o)=>{n.close(a=>a?o(a):i());})}}var J=z$2.object({name:z$2.string().default("default"),baseUrl:z$2.url(),sapClient:z$2.string().optional()}),po=J.extend({authType:z$2.literal("basic"),username:z$2.string().min(1),password:z$2.string().min(1)}),mo=J.extend({authType:z$2.literal("user-basic")}),fo=J.extend({authType:z$2.literal("oauth2"),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1),tokenServiceUrl:z$2.url()}),go=J.extend({authType:z$2.literal("apikey"),apiKey:z$2.string().min(1)}),ho=J.extend({authType:z$2.literal("ias"),tenantUrl:z$2.url(),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1)}),yo=J.extend({authType:z$2.literal("xsuaa"),xsuaaUrl:z$2.url(),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1)}),So=J.extend({authType:z$2.literal("btp-principal"),destinationName:z$2.string().min(1),destinationServiceUrl:z$2.url(),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1),tokenServiceUrl:z$2.url()}),bo=J.extend({authType:z$2.literal("saml-bearer"),tokenServiceUrl:z$2.url(),clientId:z$2.string().min(1),issuer:z$2.string().min(1),nameIdFormat:z$2.enum(["email","sapUsername"]).default("email"),privateKey:z$2.string().min(1),certificate:z$2.string().min(1),clockSkew:z$2.coerce.number().int().nonnegative().default(120),assertionLifetime:z$2.coerce.number().int().positive().default(300)}),be=z$2.discriminatedUnion("authType",[po,mo,fo,go,ho,yo,So,bo]);function $e(t=process.env){let e={name:t.SAP_CONNECTION_NAME??"default",baseUrl:t.SAP_BASE_URL,authType:t.SAP_AUTH_TYPE?.toLowerCase(),sapClient:t.SAP_CLIENT,username:t.SAP_USERNAME,password:t.SAP_PASSWORD,clientId:t.SAP_CLIENT_ID,clientSecret:t.SAP_CLIENT_SECRET,tokenServiceUrl:t.SAP_TOKEN_SERVICE_URL,apiKey:t.SAP_API_KEY,tenantUrl:t.SAP_TENANT_URL,xsuaaUrl:t.SAP_XSUAA_URL,destinationName:t.SAP_DESTINATION_NAME,destinationServiceUrl:t.SAP_DESTINATION_SERVICE_URL,issuer:t.SAP_ISSUER,nameIdFormat:t.SAP_NAME_ID_FORMAT,privateKey:t.SAP_PRIVATE_KEY,certificate:t.SAP_CERTIFICATE,clockSkew:t.SAP_CLOCK_SKEW,assertionLifetime:t.SAP_ASSERTION_LIFETIME},n=Object.fromEntries(Object.entries(e).filter(([s,i])=>i!==void 0)),r=be.safeParse(n);if(!r.success){let s=r.error.issues.map(i=>` - ${i.path.join(".")||"root"}: ${i.message}`).join(`
|
|
59
59
|
`);throw new Error(`Invalid SAP connection configuration:
|
|
60
60
|
${s}
|
|
61
61
|
|
|
62
|
-
Set the required SAP_* environment variables.`)}return r.data}var
|
|
63
|
-
${a.errors.map(
|
|
62
|
+
Set the required SAP_* environment variables.`)}return r.data}var dn=/^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/,To=z$2.enum(["core","odata","idoc","rfc"]),wo=z$2.object({readOnly:z$2.boolean().optional(),tiers:z$2.array(To).min(1).optional()}).strict(),Ao=z$2.object({hash:z$2.string().regex(/^sha256:[0-9a-f]{64}$/,"muss die Form sha256:<64 hex> haben"),destination:z$2.string().min(1),label:z$2.string().max(200).optional(),policy:wo.optional(),createdAt:z$2.string().optional(),revoked:z$2.boolean().optional()}).strict(),vo=z$2.object({version:z$2.literal(1),destinations:z$2.record(z$2.string(),z$2.unknown()),tokens:z$2.array(Ao).default([])}).strict(),or=/\$\{([A-Z_][A-Z0-9_]*)\}/g;function ln(t,e){let n=[],r=s=>{if(typeof s=="string"){let i=[...s.matchAll(or)].map(a=>a[1]);if(i.length===0)return s;let o=i.filter(a=>e[a]===void 0);if(o.length>0){for(let a of o)n.includes(a)||n.push(a);return s}return s.replace(or,(a,d)=>e[d]??"")}return Array.isArray(s)?s.map(r):s!==null&&typeof s=="object"?Object.fromEntries(Object.entries(s).map(([i,o])=>[i,r(o)])):s};return {value:r(t),missing:n}}var Eo=be.options.map(t=>t.shape.authType.value);function ar(t,e){return e.map(n=>{let r=n.path.join("."),s=r==="authType"?`fehlt oder ist unbekannt (erlaubt: ${Eo.join(", ")})`:n.message;return `${t}${r?`${r}: `:""}${s}`})}function ut(t,e){if(t===null||typeof t!="object"||Array.isArray(t))return {ok:false,errors:['Die Datei muss ein JSON-Objekt mit "version" und "destinations" sein.']};let n=vo.safeParse(t);if(!n.success)return {ok:false,errors:ar("",n.error.issues)};let r=[],s=new Map,i=Object.entries(n.data.destinations);i.length===0&&r.push('"destinations" enth\xE4lt keinen Eintrag.');for(let[a,d]of i){if(!dn.test(a)){r.push(`Destination "${a}": unzul\xE4ssiger Name \u2014 erlaubt sind Buchstaben, Ziffern, "_", "." und "-" (max. 64 Zeichen, kein Leerzeichen).`);continue}if(d===null||typeof d!="object"||Array.isArray(d)){r.push(`Destination "${a}": muss ein Objekt sein.`);continue}let l=ln(d,e);if(l.missing.length>0){r.push(`Destination "${a}": Umgebungsvariable(n) nicht gesetzt: ${l.missing.join(", ")}.`);continue}let c={...l.value,name:a},u=be.safeParse(c);if(!u.success){r.push(...ar(`Destination "${a}": `,u.error.issues));continue}s.set(a,u.data);}let o=n.data.tokens;return o.forEach((a,d)=>{Object.hasOwn(n.data.destinations,a.destination)||r.push(`tokens[${d}]${a.label?` ("${a.label}")`:""}: Destination "${a.destination}" existiert nicht.`);}),r.length>0?{ok:false,errors:r}:{ok:true,value:{destinations:s,tokens:o}}}function Be(t,e){let n=createHash("sha256").update(t).digest(),r=createHash("sha256").update(e).digest();return timingSafeEqual(n,r)}var ko="gsm",dr=10,pt=6e4,Oo=1e4;function lr(t){return `sha256:${createHash("sha256").update(t,"utf8").digest("hex")}`}function ur(t){let e=randomBytes(32).toString("base64url"),n=`${ko}_${t}_${e}`;return {token:n,hash:lr(n)}}var Te=class{apiKey;tokens;now;failures=new Map;constructor(e){this.apiKey=e.apiKey,this.tokens=e.tokens,this.now=e.now??Date.now;}enforced(){return this.apiKey!==void 0||this.tokens().length>0}authenticate(e,n){if(!this.enforced())return {kind:"open"};let r=this.blockedFor(n);if(r!==void 0)return {kind:"rate-limited",retryAfterSeconds:r};let s=this.check(e);return s.kind==="rejected"?this.recordFailure(n):this.failures.delete(n),s}check(e){let n=Io(e),r=n??"",s=this.apiKey!==void 0&&Be(r,this.apiKey),i=Buffer.from(lr(r),"utf8"),o,a=false;for(let d of this.tokens()){let l=Buffer.from(d.hash,"utf8");l.length===i.length&&timingSafeEqual(l,i)&&(d.revoked?a=true:o||(o=d));}return o?{kind:"token",token:o}:s?{kind:"api-key"}:n===void 0?{kind:"rejected",reason:"Kein Bearer-Token im Authorization-Header."}:a?{kind:"rejected",reason:"Das Token wurde widerrufen."}:{kind:"rejected",reason:"Unbekanntes Token oder falscher API-Key."}}blockedFor(e){let n=this.failures.get(e);if(!n)return;let r=this.now()-n.windowStart;if(r>pt){this.failures.delete(e);return}if(n.count>=dr)return Math.max(1,Math.ceil((pt-r)/1e3))}recordFailure(e){let n=this.now();if(this.failures.size>=Oo)for(let[s,i]of this.failures)n-i.windowStart>pt&&this.failures.delete(s);let r=this.failures.get(e);if(!r||n-r.windowStart>pt){this.failures.set(e,{count:1,windowStart:n});return}r.count+=1,r.count===dr&&(r.windowStart=n);}};function Io(t){return typeof t!="string"?void 0:/^Bearer\s+(.+)$/i.exec(t.trim())?.[1]?.trim()||void 0}var pr=["core","odata","idoc","rfc"],C=class extends Error{constructor(n,r=400){super(n);this.status=r;this.name="AdminError";}},we=class{filePath;env;now;constructor(e){this.filePath=e.filePath,this.env=e.env,this.now=e.now??(()=>new Date);}listDestinations(){let e=this.read({mustExist:false});return Object.keys(e.destinations).length===0&&e.tokens.length===0?[]:(this.validate(e,true,422),Object.entries(e.destinations).map(([n,r])=>{let s=mr(e,n);return {name:n,baseUrl:String(r.baseUrl??""),authType:String(r.authType??""),sapClient:r.sapClient===void 0?void 0:String(r.sapClient),tokens:s.length,activeTokens:s.filter(i=>i.revoked!==true).length}}))}addDestination(e,n,r={}){if(!dn.test(e))throw new C(`"${e}" is not a valid destination name (letters, digits, "_", ".", "-", max 64 characters).`,400);let s=this.read({mustExist:false}),i=s.destinations[e]!==void 0;if(i&&!r.replace)throw new C(`Destination "${e}" already exists. Use --replace to overwrite it.`,409);let o;if(r.fromEnv){let l;try{let{name:c,...u}=$e(this.env);l=u;}catch(c){throw new C(c instanceof Error?c.message:String(c),400)}o=fr(l);}else if(o=fr(n),Object.keys(o).length===0)throw new C("Nothing to add: pass --from-env or --base-url/--auth-type and the auth fields.",400);let a={...s,destinations:{...s.destinations,[e]:o}},{warnings:d}=this.validate(a,!!r.allowMissingEnv,400);return this.write(a),{replaced:i,warnings:d}}removeDestination(e,n={}){let r=this.read({mustExist:true});if(!r.destinations[e])throw new C(`Destination "${e}" not found in ${this.filePath}.`,404);let s=mr(r,e);if(s.length>0&&!n.force)throw new C(`Destination "${e}" still has ${s.length} token(s) (${s.map(d=>typeof d.label=="string"?d.label:oe(d.hash)).join(", ")}). Revoke them first, or pass --force to remove them together.`,409);if(Object.keys(r.destinations).length===1)throw new C(`Destination "${e}" is the last one in ${this.filePath}; the server needs at least one. Add a replacement first, or delete the file.`,409);let{[e]:i,...o}=r.destinations,a={...r,destinations:o,tokens:r.tokens.filter(d=>d.destination!==e)};return this.validate(a,true,400),this.write(a),{removedTokens:s.length}}listTokens(){let e=this.read({mustExist:false});return Object.keys(e.destinations).length===0&&e.tokens.length===0?[]:(this.validate(e,true,422),e.tokens.map(n=>({label:typeof n.label=="string"?n.label:void 0,destination:String(n.destination),hash:oe(n.hash),policy:n.policy,revoked:n.revoked===true,createdAt:typeof n.createdAt=="string"?n.createdAt:void 0})))}issueToken(e,n={}){let r=this.read({mustExist:true});if(!r.destinations[e])throw new C(`Destination "${e}" not found in ${this.filePath}. Known: ${Object.keys(r.destinations).join(", ")||"(none)"}.`,404);let s={};if(n.readOnly&&(s.readOnly=true),n.tiers!==void 0){let l=n.tiers.map(u=>u.trim()).filter(u=>u.length>0),c=l.filter(u=>!pr.includes(u));if(c.length>0||l.length===0)throw new C(`Unknown tier(s): ${c.join(", ")||"(empty)"}. Allowed: ${pr.join(", ")}.`,400);s.tiers=l;}if(n.label!==void 0&&n.label.length>200)throw new C("Label must not exceed 200 characters.",400);let{token:i,hash:o}=ur(e),a={hash:o,destination:e,...n.label?{label:n.label}:{},...Object.keys(s).length>0?{policy:s}:{},createdAt:this.now().toISOString()},d={...r,tokens:[...r.tokens,a]};return this.validate(d,true,400),this.write(d),{token:i,hash:o,entry:a}}revokeToken(e){let n=this.read({mustExist:true}),r=n.tokens.filter(c=>c.label===e),s=e.startsWith("sha256:")?e.slice(7):e,i=/^[0-9a-f]{8,64}$/i.test(s)?n.tokens.filter(c=>typeof c.hash=="string"&&c.hash.slice(7).startsWith(s.toLowerCase())):[],o=r.length>0?r:i;if(o.length===0)throw new C(`No token matches "${e}". Use the label or at least 8 hex characters of the hash (see: tokens list).`,404);if(o.length>1)throw new C(`"${e}" is ambiguous (${o.length} tokens). Use a longer hash prefix: ${o.map(c=>oe(c.hash)).join(", ")}.`,409);let a=o[0],d=typeof a.label=="string"?a.label:void 0;if(a.revoked===true)return {hash:oe(a.hash),label:d,alreadyRevoked:true};let l={...n,tokens:n.tokens.map(c=>c===a?{...c,revoked:true}:c)};return this.validate(l,true,400),this.write(l),{hash:oe(a.hash),label:d,alreadyRevoked:false}}read({mustExist:e}){if(!existsSync(this.filePath)){if(e)throw new C(`${this.filePath}: file not found.`,404);return {version:1,destinations:{},tokens:[]}}let n;try{n=JSON.parse(readFileSync(this.filePath,"utf8"));}catch(s){throw new C(`${this.filePath}: not valid JSON (${s instanceof Error?s.message:String(s)}).`,422)}if(n===null||typeof n!="object"||Array.isArray(n))throw new C(`${this.filePath}: must be a JSON object with "version" and "destinations".`,422);let r=n;return {version:1,destinations:r.destinations??{},tokens:r.tokens??[],...r.version!==void 0&&r.version!==1?{version:r.version}:{}}}validate(e,n,r){let s=ln(e.destinations,this.env),i=[],o=this.env;if(s.missing.length>0&&n){o={...this.env};for(let d of s.missing)o[d]="placeholder";i.push(`Environment variable(s) not set here: ${s.missing.join(", ")}. The server needs them at load time.`);}let a=ut(e,o);if(!a.ok)throw new C(`${r===400?"Refusing to write an invalid file":`${this.filePath} is invalid`}:
|
|
63
|
+
${a.errors.map(d=>` - ${d}`).join(`
|
|
64
64
|
`)}`,r);return {warnings:i}}write(e){let n=dirname(this.filePath);mkdirSync(n,{recursive:true});let r=`${this.filePath}.${process.pid}.tmp`,s=`${JSON.stringify(e,null,2)}
|
|
65
|
-
`;try{writeFileSync(r,s,{mode:384}),renameSync(r,this.filePath);}catch(i){try{unlinkSync(r);}catch{}throw i}}};function
|
|
65
|
+
`;try{writeFileSync(r,s,{mode:384}),renameSync(r,this.filePath);}catch(i){try{unlinkSync(r);}catch{}throw i}}};function mr(t,e){return t.tokens.filter(n=>n.destination===e)}function oe(t){return typeof t=="string"?t.replace(/^sha256:/,"").slice(0,12):"?"}function fr(t){return Object.fromEntries(Object.entries(t).filter(([,e])=>e!==void 0))}var gr=new Set(["destinations","tokens"]),mt=`Usage:
|
|
66
66
|
guniweb-sap-mcp destinations list
|
|
67
67
|
guniweb-sap-mcp destinations add <name> (--from-env | --base-url <url> --auth-type <type> [fields])
|
|
68
68
|
[--sap-client <c>] [--set <field>=<value>]... [--replace]
|
|
@@ -87,26 +87,26 @@ Auth types: basic, user-basic (personal SAP login per request \u2014 the caller
|
|
|
87
87
|
|
|
88
88
|
"tokens issue" prints the plain token ONCE on stdout; only its SHA-256 hash is stored.
|
|
89
89
|
Put the token into an n8n Bearer Auth credential on the MCP Client node.
|
|
90
|
-
`,F=class extends Error{constructor(n,r=1){super(n);this.exitCode=r;}};async function
|
|
90
|
+
`,F=class extends Error{constructor(n,r=1){super(n);this.exitCode=r;}};async function hr(t,e){let n=jo(t);try{if(n.flags.help)return e.stdout(mt),0;let[r,s,...i]=n.positional;if(r==="destinations")switch(s){case "list":return Vo(n,e);case "add":return qo(i[0],n,e);case "remove":return Ko(i[0],n,e);default:throw new F(`Unknown command "destinations ${s??""}".
|
|
91
91
|
|
|
92
|
-
${
|
|
92
|
+
${mt}`,2)}if(r==="tokens")switch(s){case "list":return zo(n,e);case "issue":return Go(i[0],n,e);case "revoke":return Yo(i[0],n,e);default:throw new F(`Unknown command "tokens ${s??""}".
|
|
93
93
|
|
|
94
|
-
${
|
|
95
|
-
`),r.exitCode):r instanceof
|
|
94
|
+
${mt}`,2)}throw new F(mt,2)}catch(r){return r instanceof F?(e.stderr(`${r.message}
|
|
95
|
+
`),r.exitCode):r instanceof C?(e.stderr(`${r.message}
|
|
96
96
|
`),1):(e.stderr(`Error: ${r instanceof Error?r.message:String(r)}
|
|
97
|
-
`),1)}}var
|
|
98
|
-
`),0}var
|
|
97
|
+
`),1)}}var Fo=new Set(["help","h","from-env","replace","force","read-only","allow-missing-env"]),Ho=new Set(["set"]);function jo(t){let e=[],n={};for(let r=0;r<t.length;r++){let s=t[r];if(s==="-h"){n.help=true;continue}if(!s.startsWith("--")){e.push(s);continue}let i=s.indexOf("="),o=i===-1?s.slice(2):s.slice(2,i);if(Fo.has(o)){n[o]=true;continue}let a=i===-1?t[++r]:s.slice(i+1);if(a===void 0)throw new F(`Missing value for --${o}.`,2);if(Ho.has(o)){let d=n[o]??[];d.push(a),n[o]=d;}else n[o]=a;}return {positional:e,flags:n}}function ft(t,e){let n=t.flags[e];return typeof n=="string"?n:void 0}function Ae(t,e){let n=ft(t,"destinations")??e.env.SAP_MCP_DESTINATIONS;if(!n||n.trim().length===0)throw new F("No destinations file given. Pass --destinations <path> or set SAP_MCP_DESTINATIONS.",2);return new we({filePath:resolve(n.trim()),env:e.env,now:e.now})}function Vo(t,e){let n=Ae(t,e).listDestinations();return e.stdout(`${yr(["NAME","BASE URL","AUTH","CLIENT","TOKENS"],n.map(r=>[r.name,r.baseUrl,r.authType,r.sapClient??"-",String(r.tokens)]))}
|
|
98
|
+
`),0}var Wo={"base-url":"baseUrl","auth-type":"authType","sap-client":"sapClient",username:"username",password:"password","api-key":"apiKey","client-id":"clientId","client-secret":"clientSecret","token-service-url":"tokenServiceUrl"};function qo(t,e,n){if(!t)throw new F("Usage: destinations add <name> ...",2);let r=Ae(e,n),s={};if(!e.flags["from-env"]){for(let[a,d]of Object.entries(Wo)){let l=ft(e,a);l!==void 0&&(s[d]=l);}for(let a of e.flags.set??[]){let d=a.indexOf("=");if(d<=0)throw new F(`--set expects field=value, got "${a}".`,2);s[a.slice(0,d)]=a.slice(d+1);}}let{replaced:i,warnings:o}=r.addDestination(t,s,{replace:!!e.flags.replace,allowMissingEnv:!!e.flags["allow-missing-env"],fromEnv:!!e.flags["from-env"]});for(let a of o)n.stderr(`Warning: ${a}
|
|
99
99
|
`);return n.stderr(`Destination "${t}" ${i?"replaced":"added"} in ${r.filePath}.
|
|
100
|
-
`),0}function
|
|
101
|
-
`),0}function
|
|
102
|
-
`),0}function
|
|
103
|
-
`),n.stderr([`Token issued for destination "${t}"${i?` (${i})`:""} \u2014 hash ${
|
|
104
|
-
`)),0}function
|
|
100
|
+
`),0}function Ko(t,e,n){if(!t)throw new F("Usage: destinations remove <name> [--force]",2);let{removedTokens:r}=Ae(e,n).removeDestination(t,{force:!!e.flags.force});return n.stderr(`Destination "${t}" removed${r>0?` together with ${r} token(s)`:""}.
|
|
101
|
+
`),0}function zo(t,e){let n=Ae(t,e).listTokens().map(r=>{let s=[];return r.policy?.readOnly&&s.push("read-only"),r.policy?.tiers&&s.push(`tiers=${r.policy.tiers.join(",")}`),[r.label??"-",r.destination,r.hash,s.length>0?s.join(" "):"full",r.revoked?"revoked":"active",r.createdAt??"-"]});return e.stdout(`${yr(["LABEL","DESTINATION","HASH","POLICY","STATUS","CREATED"],n)}
|
|
102
|
+
`),0}function Go(t,e,n){if(!t)throw new F("Usage: tokens issue <destination> [--label ...]",2);let r=Ae(e,n),s=ft(e,"tiers"),i=ft(e,"label"),{token:o,hash:a}=r.issueToken(t,{label:i,readOnly:!!e.flags["read-only"],tiers:s===void 0?void 0:s.split(",")});return n.stdout(`${o}
|
|
103
|
+
`),n.stderr([`Token issued for destination "${t}"${i?` (${i})`:""} \u2014 hash ${oe(a)}\u2026 stored in ${r.filePath}.`,"The plain token above is shown ONCE and cannot be recovered; put it into an n8n Bearer Auth credential","(Authorization: Bearer <token>) on the MCP Client node. Revoke with: tokens revoke <label|hash-prefix>.",""].join(`
|
|
104
|
+
`)),0}function Yo(t,e,n){if(!t)throw new F("Usage: tokens revoke <label | hash-prefix>",2);let{hash:r,label:s,alreadyRevoked:i}=Ae(e,n).revokeToken(t);return i?(n.stderr(`Token ${r} was already revoked.
|
|
105
105
|
`),0):(n.stderr(`Token ${r}${s?` (${s})`:""} revoked. A running server rejects it from the next request on.
|
|
106
|
-
`),0)}function
|
|
107
|
-
`)}var
|
|
108
|
-
${a.errors.map(
|
|
109
|
-
`)}`)}return o.current=a.value,o.currentRaw=a.raw,o}get version(){return this.currentVersion}get(e){return this.current.destinations.get(e)}names(){return [...this.current.destinations.keys()]}defaultDestination(){let e=this.current.destinations.get(ce);if(e)return e;if(this.current.destinations.size===1)return this.current.destinations.values().next().value}tokens(){return this.current.tokens}onChange(e){return this.changeListeners.add(e),()=>this.changeListeners.delete(e)}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}reload(){return this.source==="env"?Promise.resolve({ok:true,changed:false}):this.reloadInFlight?(this.reloadQueued=true,this.reloadInFlight):(this.reloadInFlight=this.doReload().finally(()=>{this.reloadInFlight=void 0,this.reloadQueued&&(this.reloadQueued=false,this.reload());}),this.reloadInFlight)}watch(){if(!(this.source==="env"||!this.filePath)&&(this.sighupHandler||(this.sighupHandler=()=>{this.reload();},process.on("SIGHUP",this.sighupHandler)),this.observeFile&&!this.polling&&(this.polling=true,watchFile(this.filePath,{persistent:false,interval:dr},(e,n)=>{(e.mtimeMs!==n.mtimeMs||e.size!==n.size)&&this.scheduleReload();}),this.startupRecheck=setTimeout(()=>{this.startupRecheck=void 0,this.scheduleReload();},dr+500),this.startupRecheck.unref()),this.observeFile&&!this.watcher))try{this.watcher=watch(dirname(this.filePath),{persistent:!1},()=>{this.scheduleReload();}),this.watcher.on("error",e=>{this.warn(`Dateibeobachtung f\xFCr ${this.filePath} ausgefallen (${e.message}); SIGHUP l\xE4dt weiterhin neu.`),this.watcher=void 0;});}catch(e){this.warn(`Dateibeobachtung f\xFCr ${this.filePath} nicht m\xF6glich (${e instanceof Error?e.message:String(e)}); SIGHUP l\xE4dt weiterhin neu.`);}}close(){this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=void 0),this.startupRecheck&&(clearTimeout(this.startupRecheck),this.startupRecheck=void 0),this.watcher&&(this.watcher.close(),this.watcher=void 0),this.polling&&this.filePath&&(unwatchFile(this.filePath),this.polling=false),this.sighupHandler&&(process.off("SIGHUP",this.sighupHandler),this.sighupHandler=void 0),this.changeListeners.clear(),this.errorListeners.clear();}scheduleReload(){this.debounceTimer&&clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>{this.debounceTimer=void 0,this.reload();},jo),this.debounceTimer.unref();}async doReload(){let e=await this.readAndParse();if(!e.ok){if(e.missing&&this.currentRaw===void 0)return {ok:true,changed:false};for(let r of this.errorListeners)r(e.errors);return {ok:false,errors:e.errors}}if(e.raw===this.currentRaw)return {ok:true,changed:false};this.current=e.value,this.currentRaw=e.raw,this.currentVersion+=1;let n={version:this.currentVersion,names:this.names(),tokenCount:this.current.tokens.length};for(let r of this.changeListeners)r(n);return {ok:true,changed:true}}async readAndParse(){if(!this.filePath)return {ok:false,errors:["Kein Dateipfad konfiguriert."]};let e;try{e=await readFile(this.filePath,"utf8");}catch(s){let i=s.code,o=i==="ENOENT"?"Datei nicht gefunden":s.message;return {ok:false,errors:[`${this.filePath}: ${o}`],missing:i==="ENOENT"}}let n;try{n=JSON.parse(e);}catch(s){return {ok:false,errors:[`${this.filePath}: kein g\xFCltiges JSON (${s instanceof Error?s.message:String(s)})`]}}let r=ct(n,this.env);return r.ok?(this.envDefault&&(r.value.destinations.has(ce)?this.warnedFileWins||(this.warnedFileWins=true,this.warn(`Sowohl ${this.filePath} als auch die SAP_*-Umgebung definieren die Destination "default" \u2014 die Datei gilt, die Umgebung wird ignoriert.`)):r.value.destinations.set(ce,this.envDefault)),{ok:true,value:r.value,raw:e}):r}};var Ce=class{buffer=[];maxSize;constructor(e=100){this.maxSize=e;}add(e){this.buffer.length>=this.maxSize&&this.buffer.shift(),this.buffer.push(e);}get(e){return this.buffer.find(n=>n.docnum===e)}list(){return [...this.buffer]}get count(){return this.buffer.length}};var Wo=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",parseTagValue:false,processEntities:false,htmlEntities:false,isArray:t=>t==="IDOC"||t.startsWith("E1")||t.startsWith("E2")}),Le=new Set(["@_SEGMENT","@_BEGIN"]);function sn(t,e){return !!(t.startsWith("E1")||t.startsWith("E2")||typeof e=="object"&&e!==null&&"@_SEGMENT"in e)}function lr(t){let e=[];for(let[n,r]of Object.entries(t)){if(Le.has(n)||!sn(n,r))continue;let s=Array.isArray(r)?r:[r];for(let i of s){if(typeof i!="object"||i===null)continue;let o={},a=[];for(let[c,l]of Object.entries(i))if(!Le.has(c))if(sn(c,l)){let d=Array.isArray(l)?l:[l];for(let u of d){if(typeof u!="object"||u===null)continue;let m={},p=lr(u);for(let[g,f]of Object.entries(u))Le.has(g)||!sn(g,f)&&typeof f!="object"&&(m[g]=String(f));a.push({name:c,fields:m,...p.length>0?{children:p}:{}});}}else typeof l!="object"&&(o[c]=String(l));e.push({name:n,fields:o,...a.length>0?{children:a}:{}});}}return e}function ur(t){let e=Wo.parse(t),n=Object.keys(e).find(u=>u!=="?xml");if(!n)throw new Error("Invalid IDoc XML: no root element found");let r=n,i=e[n].IDOC;if(!i||!Array.isArray(i)||i.length===0)throw new Error("Invalid IDoc XML: no IDOC element found");let o=i[0],a=o.EDI_DC40,c={};if(a){let u=Array.isArray(a)?a[0]:a;for(let[m,p]of Object.entries(u))!Le.has(m)&&typeof p!="object"&&(c[m]=String(p));}let l={};for(let[u,m]of Object.entries(o))u==="EDI_DC40"||Le.has(u)||(l[u]=m);let d=lr(l);return {idocType:r,controlRecord:c,segments:d}}var qo=10*1024*1024,pt=class extends Error{};function Ko(t,e){return new Promise((n,r)=>{let s=[],i=0,o=false;t.on("data",a=>{if(!o){if(i+=a.length,i>e){o=true,s.length=0,r(new pt(`Request body exceeds ${e} bytes`));return}s.push(a);}}),t.on("end",()=>{o||n(Buffer.concat(s).toString("utf-8"));}),t.on("error",a=>{o||r(a);});})}function zo(t,e){if(e.length===0||typeof t!="string")return false;if(t.startsWith("Bearer "))return Be(t.slice(7),e);if(t.startsWith("Basic ")){let n=Buffer.from(t.slice(6),"base64").toString("utf-8"),r=n.indexOf(":");return r===-1?false:Be(n.slice(r+1),e)}return false}function Go(t){if(typeof t!="string")return true;let e=(t.split(";")[0]??"").trim().toLowerCase();return e.endsWith("xml")||e==="application/x-sap.idoc"}function mt(t,e={}){let n=e.maxBodyBytes??qo;return (r,s)=>{if(r.method!=="POST"){s.writeHead(405,{"Content-Type":"text/plain"}),s.end("Method Not Allowed");return}if(e.secret!==void 0&&!zo(r.headers.authorization,e.secret)){s.writeHead(401,{"Content-Type":"text/plain","WWW-Authenticate":'Basic realm="idoc-webhook"'}),s.end("Unauthorized"),r.destroy();return}if(!Go(r.headers["content-type"])){s.writeHead(415,{"Content-Type":"text/plain"}),s.end("Unsupported Media Type: expected XML"),r.destroy();return}Ko(r,n).then(i=>{let{idocType:o,controlRecord:a,segments:c}=ur(i),l={docnum:a.DOCNUM||`UNKNOWN_${Date.now()}`,idocType:o,mesType:a.MESTYP||"",receivedAt:new Date,rawXml:i,parsed:{controlRecord:a,segments:c}};t.add(l),s.writeHead(200,{"Content-Type":"text/plain"}),s.end("OK");}).catch(i=>{if(i instanceof pt){s.writeHead(413,{"Content-Type":"text/plain"}),s.end("Payload Too Large"),r.destroy();return}s.writeHead(400,{"Content-Type":"text/plain"}),s.end(`Parse error: ${i instanceof Error?i.message:String(i)}`);});}}function pr(t,e,n={}){let r=mt(e,n);return new Promise((s,i)=>{let o=createServer((c,l)=>{c.method==="POST"&&c.url==="/idoc"?r(c,l):(l.writeHead(404,{"Content-Type":"text/plain"}),l.end("Not Found"));}),a=()=>{let c=o.address(),l=typeof c=="object"&&c!==null?c.port:t;s({port:l,close:()=>new Promise((d,u)=>{o.close(m=>m?u(m):d());})});};n.host?o.listen(t,n.host,a):o.listen(t,a),o.on("error",i);})}var fr=1;function ft(t){if(!t.directory)return [];let e=join(t.directory,t.fileName),n;try{n=readFileSync(e,"utf-8");}catch{return []}try{let r=JSON.parse(n);if(r?.version!==fr||!Array.isArray(r.entries))return t.onWarn?.(`Cache-Datei ${e} hat ein unbekanntes Format und wird verworfen.`),[];let s=Date.now()-t.ttlMs;return r.entries.filter(i=>i&&typeof i.key=="string"&&typeof i.storedAt=="number"&&i.storedAt>s)}catch(r){return t.onWarn?.(`Cache-Datei ${e} ist unlesbar und wird verworfen. Der Cache wird aus SAP neu aufgebaut.`,r),[]}}function gt(t,e){if(!e.directory)return;let n=join(e.directory,e.fileName),r=`${n}.tmp`;try{mkdirSync(dirname(n),{recursive:!0}),writeFileSync(r,JSON.stringify({version:fr,entries:t}),"utf-8"),renameSync(r,n);}catch(s){e.onWarn?.(`Cache konnte nicht nach ${n} geschrieben werden. Der Betrieb l\xE4uft weiter, nach einem Neustart ist der Cache aber leer.`,s);try{unlinkSync(r);}catch{}}}var na=30,ra="catalog-cache.json",gr="catalog",X=class{cache=null;ttlMs;key;persistence;constructor(e={}){let n=typeof e=="number"?{ttlMinutes:e}:e;this.ttlMs=(n.ttlMinutes??na)*60*1e3;let r=(n.baseUrl??"").replace(/\/+$/,"").toLowerCase();if(this.key=r?`${r}|${gr}`:gr,n.persistDir){this.persistence={directory:n.persistDir,fileName:ra,ttlMs:this.ttlMs,...n.onWarn?{onWarn:n.onWarn}:{}};let s=ft(this.persistence).find(i=>i.key===this.key);s&&(this.cache={entries:s.value,timestamp:s.storedAt});}}get(){return this.cache?Date.now()-this.cache.timestamp>this.ttlMs?(this.cache=null,null):this.cache.entries:null}set(e){this.cache={entries:e,timestamp:Date.now()},this.persist();}clear(){this.cache=null,this.persist();}persist(){this.persistence&>(this.cache?[{key:this.key,value:this.cache.entries,storedAt:this.cache.timestamp}]:[],this.persistence);}};var on=0;function hr(t){let e=t.docnum??`${Date.now().toString().padStart(13,"0")}${String(on++).padStart(3,"0")}`;on>=1e3&&(on=0);let n=t.mesType??t.idocType.replace(/\d+$/,"");return {TABNAM:"EDI_DC40",DOCNUM:e,DIRECT:"2",IDOCTYP:t.idocType,MESTYP:n,SNDPOR:t.sndpor,SNDPRT:t.sndprt??"LS",SNDPRN:t.sndprn,RCVPOR:t.rcvpor,RCVPRT:t.rcvprt??"LS",RCVPRN:t.rcvprn,...t.extra}}function sa(t,e,n){return e.required&&n===""?{segmentName:t,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:n,violation:"Required field is empty"}:n===""?null:(e.dataType==="CHAR"||e.dataType==="NUMC")&&n.length>e.length?{segmentName:t,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:n,violation:`Field value exceeds max length ${e.length} (actual: ${n.length})`}:e.dataType==="NUMC"&&!/^\d*$/.test(n)?{segmentName:t,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:n,violation:"NUMC field must contain only digits"}:e.dataType==="DATS"&&!/^\d{8}$/.test(n)?{segmentName:t,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:n,violation:"DATS field must be in YYYYMMDD format"}:e.dataType==="TIMS"&&!/^\d{6}$/.test(n)?{segmentName:t,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:n,violation:"TIMS field must be in HHMMSS format"}:null}function ia(t,e){return t.filter(n=>n.name===e).length}function yr(t,e,n){let r=new Set;for(let s of t){if(r.has(s.name))continue;r.add(s.name);let i=e.get(s.name);if(!i)continue;let o=ia(t,s.name);o>i.maxOccurrence&&n.push({segmentName:s.name,fieldName:"",expectedType:"",expectedLength:i.maxOccurrence,actualValue:String(o),violation:`Segment count ${o} exceeds maxOccurrence ${i.maxOccurrence}`});}for(let s of t){let i=e.get(s.name);if(!i){n.push({segmentName:s.name,fieldName:"",expectedType:"",expectedLength:0,actualValue:"",violation:`Unknown segment '${s.name}' not found in metadata`});continue}for(let o of i.fields){let a=s.fields[o.name]??"",c=sa(s.name,o,a);c&&n.push(c);}s.children&&s.children.length>0&&yr(s.children,e,n);}}function Sr(t,e){if(!e)return [];let n=new Map;for(let s of e.segments)n.set(s.name,s);let r=[];return yr(t,n,r),r}var aa=new XMLBuilder({ignoreAttributes:false,attributeNamePrefix:"@_",format:true,suppressEmptyNode:false});function br(t){let e={"@_SEGMENT":"1"};for(let[n,r]of Object.entries(t.fields))e[n]=r;if(t.children)for(let n of t.children){let r=br(n),s=e[n.name];s?Array.isArray(s)?s.push(r):e[n.name]=[s,r]:e[n.name]=r;}return e}function Tr(t,e,n){let r={};for(let i of n){let o=br(i),a=r[i.name];a?Array.isArray(a)?a.push(o):r[i.name]=[a,o]:r[i.name]=o;}let s={"?xml":{"@_version":"1.0","@_encoding":"UTF-8"},[t]:{IDOC:{"@_BEGIN":"1",EDI_DC40:{"@_SEGMENT":"1",...e},...r}}};return aa.build(s)}var ht=class{constructor(e,n,r){this.httpClient=e;this.sapClient=n;this.partnerDefaults=r;}async send(e){let n={...this.partnerDefaults,...e.partnerOverrides},r=hr({idocType:e.idocType,sndpor:n.sndpor??"",sndprn:n.sndprn??"",rcvpor:n.rcvpor??"",rcvprn:n.rcvprn??"",sndprt:n.sndprt,rcvprt:n.rcvprt,mesType:n.mesType,docnum:n.docnum,extra:n.extra});if(e.metadata){let u=Sr(e.segments,e.metadata);if(u.length>0)return {success:false,docnum:r.DOCNUM,message:"Validation failed",violations:u}}let s=Tr(e.idocType,r,e.segments),i="/sap/bc/idoc_xml";this.sapClient&&(i+=`?sap-client=${this.sapClient}`);let o=await this.httpClient.request({method:"post",url:i,data:s,headers:{"Content-Type":"text/xml"}}),a=typeof o.data=="string"?o.data:String(o.data);if(a.includes("<title>IDoc-XML-inbound ok</title>"))return {success:true,docnum:r.DOCNUM,message:"IDoc sent successfully"};let d=a.match(/<title>(.*?)<\/title>/)?.[1]??"IDoc send failed";return {success:false,docnum:r.DOCNUM,message:d}}};var yt=30,ca=5e3;function da(t,e,n,r){let s=t?._meta?.progressToken,i=t?.sendNotification;if(s===void 0||typeof i!="function"||r.afterMs<=0)return ()=>{};let a=setInterval(()=>{let c=Date.now()-n;i({method:"notifications/progress",params:{progressToken:s,progress:Math.round(c/1e3),...r.budgetMs?{total:Math.round(r.budgetMs/1e3)}:{},message:`${e.toolName}: ${Math.round(c/1e3)} s, ${e.sapCalls.count} SAP-Aufruf(e)`}}).catch(()=>{});},r.afterMs);return a.unref?.(),()=>clearInterval(a)}var la="SAP_TLS_VERIFY=false: Die Zertifikatspr\xFCfung ist abgeschaltet \u2014 die Verbindung zu SAP ist nicht gegen einen Angreifer in der Leitung gesch\xFCtzt. Richtiger Weg: Root-CA \xFCber SAP_CA_CERT (bzw. NODE_EXTRA_CA_CERTS) einbinden und SAP_TLS_VERIFY wieder entfernen.";function ua(t){return {...t,content:[...t.content,{type:"text",text:`\u26A0\uFE0F ${la}`}]}}function pa(t,e,n,r,s){return {content:[{type:"text",text:JSON.stringify({truncated:true,reason:`Zeitbudget von ${Math.round(s/1e3)} s \xFCberschritten \u2014 der Aufruf wurde abgebrochen, damit die Verbindung nicht h\xE4ngen bleibt.`,hint:"Grenzen Sie die Anfrage ein (top/skip, filter, search) oder erh\xF6hen Sie --tool-timeout. Der Wert sollte unter der kleinsten Client-Grenze bleiben (Cloudflare 100 s, n8n-MCP-Transport 300 s).",tool:t,correlationId:e,elapsedMs:n,sapCalls:r},null,2)}],isError:true}}function ma(t,e){return {content:[{type:"text",text:JSON.stringify({aborted:true,reason:"Der Client hat die Verbindung verworfen; die Arbeit wurde eingestellt.",tool:t,correlationId:e})}],isError:true}}function Ar(t,e,n={}){let{toolTimeoutMs:r,logger:s,tlsVerifyDisabled:i}=n,o=n.progressAfterMs??ca;return async(a,c)=>{let l=_n(),{signal:d,deadline:u}=In(c?.signal,r),m={correlationId:l,toolName:t,signal:d,deadline:u,sapCalls:{count:0}},p=Date.now();return kn(m,async()=>{let g=da(c,m,p,{afterMs:o,...r?{budgetMs:r}:{}}),f="ok",h,S=(async()=>e(a,c))();S.catch(()=>{});try{h=await Promise.race([S,fa(d,t)]),h?.isError&&(f="error");}catch(y){if(y instanceof B)f=y.timedOut?"timeout":"aborted",h=y.timedOut?pa(t,l,Date.now()-p,m.sapCalls.count,r??0):ma(t,l);else {f="error";let A=y instanceof Error?y.message:String(y);h={content:[{type:"text",text:JSON.stringify({error:A,tool:t,correlationId:l})}],isError:true};}}f==="ok"&&d?.aborted&&(f=d.reason?.name==="TimeoutError"?"timeout":"aborted"),g();let b=i?ua(h):h;return s?.info({correlationId:l,tool:t,outcome:f,durationMs:Date.now()-p,sapCalls:m.sapCalls.count,resultBytes:ga(h),...r?{budgetMs:r}:{}},"MCP tool call"),b})}}function fa(t,e){return t?new Promise((n,r)=>{let s=()=>{let i=t.reason?.name==="TimeoutError";r(new B(i,e));};if(t.aborted){s();return}t.addEventListener("abort",s,{once:true});}):new Promise(()=>{})}function ga(t){if(!t?.content)return 0;let e=0;for(let n of t.content)typeof n?.text=="string"&&(e+=Buffer.byteLength(n.text,"utf-8"));return e}var ha=1440*60*1e3,ya="metadata-cache.json",O=class{cache=new Map;ttlMs;baseUrl;persistence;constructor(e={}){if(this.ttlMs=e.ttlMs??ha,this.baseUrl=Sa(e.baseUrl),e.persistDir){this.persistence={directory:e.persistDir,fileName:ya,ttlMs:this.ttlMs,...e.onWarn?{onWarn:e.onWarn}:{}};for(let n of ft(this.persistence))this.cache.set(n.key,{value:n.value,storedAt:n.storedAt});}}get size(){return this.cache.size}get(e){let n=this.key(e),r=this.cache.get(n);if(r){if(Date.now()-r.storedAt>this.ttlMs){this.cache.delete(n);return}return r.value}}set(e,n){this.cache.set(this.key(e),{value:n,storedAt:Date.now()}),this.persist();}has(e){return this.get(e)!==void 0}clear(){this.cache.clear(),this.persist();}key(e){let n=e.replace(/\/+$/,"");return this.baseUrl?`${this.baseUrl}|${n}`:n}persist(){this.persistence&>([...this.cache.entries()].map(([e,n])=>({key:e,value:n.value,storedAt:n.storedAt})),this.persistence);}};function Sa(t){return t?t.replace(/\/+$/,"").toLowerCase():""}var wr="0.0.0-unknown",vr="guniweb-sap-mcp";function va(t){if(typeof t!="string"||t.length===0)return wr;let e=resolve(t);for(;;){try{let r=readFileSync(join(e,"package.json"),"utf-8"),s=JSON.parse(r);if(s.name===vr&&typeof s.version=="string")return s.version}catch{}let n=dirname(e);if(n===e)return wr;e=n;}}var Er=va(import.meta.dirname),Cr=vr;function Rr(t){t.registerPrompt("nl-to-odata-query",{title:"Natural Language to OData Query",description:"Convert a natural language question into a correct OData query against an SAP service. Reads entity schema for context, applies V2/V4 syntax rules, and enforces read-only by default.",argsSchema:{serviceUrl:z$2.string().describe("OData service URL, e.g., /sap/opu/odata/sap/API_BUSINESS_PARTNER"),entitySet:z$2.string().describe("Entity set to query, e.g., A_BusinessPartner"),question:z$2.string().describe('Natural language question, e.g., "show me all customers in Germany"'),allowWrite:z$2.boolean().optional().describe("Allow write operations (create/update/delete). Default: false (read-only)")}},async({serviceUrl:e,entitySet:n,question:r,allowWrite:s})=>{let i=s===true?"":`
|
|
106
|
+
`),0)}function yr(t,e){let n=[t,...e],r=t.map((s,i)=>Math.max(...n.map(o=>(o[i]??"").length)));return n.map(s=>s.map((i,o)=>(i??"").padEnd(r[o])).join(" ").trimEnd()).join(`
|
|
107
|
+
`)}var ae="default",na=250,Sr=1e3,ve=class t{source;filePath;current;currentRaw;currentVersion=1;env;envDefault;warn;observeFile;warnedFileWins=false;watcher;polling=false;startupRecheck;sighupHandler;debounceTimer;reloadInFlight;reloadQueued=false;changeListeners=new Set;errorListeners=new Set;constructor(e){this.source=e.source,this.filePath=e.filePath,this.current=e.config,this.currentRaw=e.raw,this.env=e.env,this.envDefault=e.envDefault,this.warn=e.onWarn??(()=>{}),this.observeFile=e.watchFile??true;}static fromEnv(e=process.env){let n={...$e(e),name:ae};return new t({source:"env",config:{destinations:new Map([[ae,n]]),tokens:[]},env:e})}static async fromFile(e,n={}){let r=n.env??process.env,s=resolve(e),i=r.SAP_BASE_URL?{...$e(r),name:ae}:void 0,o=new t({source:"file",filePath:s,config:{destinations:new Map,tokens:[]},env:r,envDefault:i,onWarn:n.onWarn,watchFile:n.watchFile}),a=await o.readAndParse();if(!a.ok){if(a.missing&&i)return o.warn(`${s} existiert (noch) nicht \u2014 der Server startet mit der SAP_*-Umgebung als Destination "default" und \xFCbernimmt die Datei, sobald sie angelegt wird.`),o.current={destinations:new Map([[ae,i]]),tokens:[]},o;if(a.missing&&n.allowMissing)return o.warn(`${s} existiert (noch) nicht und es ist keine SAP_*-Umgebung gesetzt \u2014 der Server startet ohne Destination und \xFCbernimmt die Datei, sobald sie angelegt wird (z. B. \xFCber die Admin-API).`),o;throw new Error(`Ung\xFCltige Destinations-Datei ${s}:
|
|
108
|
+
${a.errors.map(d=>` - ${d}`).join(`
|
|
109
|
+
`)}`)}return o.current=a.value,o.currentRaw=a.raw,o}get version(){return this.currentVersion}get(e){return this.current.destinations.get(e)}names(){return [...this.current.destinations.keys()]}defaultDestination(){let e=this.current.destinations.get(ae);if(e)return e;if(this.current.destinations.size===1)return this.current.destinations.values().next().value}tokens(){return this.current.tokens}onChange(e){return this.changeListeners.add(e),()=>this.changeListeners.delete(e)}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}reload(){return this.source==="env"?Promise.resolve({ok:true,changed:false}):this.reloadInFlight?(this.reloadQueued=true,this.reloadInFlight):(this.reloadInFlight=this.doReload().finally(()=>{this.reloadInFlight=void 0,this.reloadQueued&&(this.reloadQueued=false,this.reload());}),this.reloadInFlight)}watch(){if(!(this.source==="env"||!this.filePath)&&(this.sighupHandler||(this.sighupHandler=()=>{this.reload();},process.on("SIGHUP",this.sighupHandler)),this.observeFile&&!this.polling&&(this.polling=true,watchFile(this.filePath,{persistent:false,interval:Sr},(e,n)=>{(e.mtimeMs!==n.mtimeMs||e.size!==n.size)&&this.scheduleReload();}),this.startupRecheck=setTimeout(()=>{this.startupRecheck=void 0,this.scheduleReload();},Sr+500),this.startupRecheck.unref()),this.observeFile&&!this.watcher))try{this.watcher=watch(dirname(this.filePath),{persistent:!1},()=>{this.scheduleReload();}),this.watcher.on("error",e=>{this.warn(`Dateibeobachtung f\xFCr ${this.filePath} ausgefallen (${e.message}); SIGHUP l\xE4dt weiterhin neu.`),this.watcher=void 0;});}catch(e){this.warn(`Dateibeobachtung f\xFCr ${this.filePath} nicht m\xF6glich (${e instanceof Error?e.message:String(e)}); SIGHUP l\xE4dt weiterhin neu.`);}}close(){this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=void 0),this.startupRecheck&&(clearTimeout(this.startupRecheck),this.startupRecheck=void 0),this.watcher&&(this.watcher.close(),this.watcher=void 0),this.polling&&this.filePath&&(unwatchFile(this.filePath),this.polling=false),this.sighupHandler&&(process.off("SIGHUP",this.sighupHandler),this.sighupHandler=void 0),this.changeListeners.clear(),this.errorListeners.clear();}scheduleReload(){this.debounceTimer&&clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>{this.debounceTimer=void 0,this.reload();},na),this.debounceTimer.unref();}async doReload(){let e=await this.readAndParse();if(!e.ok){if(e.missing&&this.currentRaw===void 0)return {ok:true,changed:false};for(let r of this.errorListeners)r(e.errors);return {ok:false,errors:e.errors}}if(e.raw===this.currentRaw)return {ok:true,changed:false};this.current=e.value,this.currentRaw=e.raw,this.currentVersion+=1;let n={version:this.currentVersion,names:this.names(),tokenCount:this.current.tokens.length};for(let r of this.changeListeners)r(n);return {ok:true,changed:true}}async readAndParse(){if(!this.filePath)return {ok:false,errors:["Kein Dateipfad konfiguriert."]};let e;try{e=await readFile(this.filePath,"utf8");}catch(s){let i=s.code,o=i==="ENOENT"?"Datei nicht gefunden":s.message;return {ok:false,errors:[`${this.filePath}: ${o}`],missing:i==="ENOENT"}}let n;try{n=JSON.parse(e);}catch(s){return {ok:false,errors:[`${this.filePath}: kein g\xFCltiges JSON (${s instanceof Error?s.message:String(s)})`]}}let r=ut(n,this.env);return r.ok?(this.envDefault&&(r.value.destinations.has(ae)?this.warnedFileWins||(this.warnedFileWins=true,this.warn(`Sowohl ${this.filePath} als auch die SAP_*-Umgebung definieren die Destination "default" \u2014 die Datei gilt, die Umgebung wird ignoriert.`)):r.value.destinations.set(ae,this.envDefault)),{ok:true,value:r.value,raw:e}):r}};var Ee=class{buffer=[];maxSize;constructor(e=100){this.maxSize=e;}add(e){this.buffer.length>=this.maxSize&&this.buffer.shift(),this.buffer.push(e);}get(e){return this.buffer.find(n=>n.docnum===e)}list(){return [...this.buffer]}get count(){return this.buffer.length}};var sa=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",parseTagValue:false,processEntities:false,htmlEntities:false,isArray:t=>t==="IDOC"||t.startsWith("E1")||t.startsWith("E2")}),Le=new Set(["@_SEGMENT","@_BEGIN"]);function un(t,e){return !!(t.startsWith("E1")||t.startsWith("E2")||typeof e=="object"&&e!==null&&"@_SEGMENT"in e)}function br(t){let e=[];for(let[n,r]of Object.entries(t)){if(Le.has(n)||!un(n,r))continue;let s=Array.isArray(r)?r:[r];for(let i of s){if(typeof i!="object"||i===null)continue;let o={},a=[];for(let[d,l]of Object.entries(i))if(!Le.has(d))if(un(d,l)){let c=Array.isArray(l)?l:[l];for(let u of c){if(typeof u!="object"||u===null)continue;let p={},m=br(u);for(let[g,f]of Object.entries(u))Le.has(g)||!un(g,f)&&typeof f!="object"&&(p[g]=String(f));a.push({name:d,fields:p,...m.length>0?{children:m}:{}});}}else typeof l!="object"&&(o[d]=String(l));e.push({name:n,fields:o,...a.length>0?{children:a}:{}});}}return e}function Tr(t){let e=sa.parse(t),n=Object.keys(e).find(u=>u!=="?xml");if(!n)throw new Error("Invalid IDoc XML: no root element found");let r=n,i=e[n].IDOC;if(!i||!Array.isArray(i)||i.length===0)throw new Error("Invalid IDoc XML: no IDOC element found");let o=i[0],a=o.EDI_DC40,d={};if(a){let u=Array.isArray(a)?a[0]:a;for(let[p,m]of Object.entries(u))!Le.has(p)&&typeof m!="object"&&(d[p]=String(m));}let l={};for(let[u,p]of Object.entries(o))u==="EDI_DC40"||Le.has(u)||(l[u]=p);let c=br(l);return {idocType:r,controlRecord:d,segments:c}}var ia=10*1024*1024,gt=class extends Error{};function oa(t,e){return new Promise((n,r)=>{let s=[],i=0,o=false;t.on("data",a=>{if(!o){if(i+=a.length,i>e){o=true,s.length=0,r(new gt(`Request body exceeds ${e} bytes`));return}s.push(a);}}),t.on("end",()=>{o||n(Buffer.concat(s).toString("utf-8"));}),t.on("error",a=>{o||r(a);});})}function aa(t,e){if(e.length===0||typeof t!="string")return false;if(t.startsWith("Bearer "))return Be(t.slice(7),e);if(t.startsWith("Basic ")){let n=Buffer.from(t.slice(6),"base64").toString("utf-8"),r=n.indexOf(":");return r===-1?false:Be(n.slice(r+1),e)}return false}function ca(t){if(typeof t!="string")return true;let e=(t.split(";")[0]??"").trim().toLowerCase();return e.endsWith("xml")||e==="application/x-sap.idoc"}function ht(t,e={}){let n=e.maxBodyBytes??ia;return (r,s)=>{if(r.method!=="POST"){s.writeHead(405,{"Content-Type":"text/plain"}),s.end("Method Not Allowed");return}if(e.secret!==void 0&&!aa(r.headers.authorization,e.secret)){s.writeHead(401,{"Content-Type":"text/plain","WWW-Authenticate":'Basic realm="idoc-webhook"'}),s.end("Unauthorized"),r.destroy();return}if(!ca(r.headers["content-type"])){s.writeHead(415,{"Content-Type":"text/plain"}),s.end("Unsupported Media Type: expected XML"),r.destroy();return}oa(r,n).then(i=>{let{idocType:o,controlRecord:a,segments:d}=Tr(i),l={docnum:a.DOCNUM||`UNKNOWN_${Date.now()}`,idocType:o,mesType:a.MESTYP||"",receivedAt:new Date,rawXml:i,parsed:{controlRecord:a,segments:d}};t.add(l),s.writeHead(200,{"Content-Type":"text/plain"}),s.end("OK");}).catch(i=>{if(i instanceof gt){s.writeHead(413,{"Content-Type":"text/plain"}),s.end("Payload Too Large"),r.destroy();return}s.writeHead(400,{"Content-Type":"text/plain"}),s.end(`Parse error: ${i instanceof Error?i.message:String(i)}`);});}}function wr(t,e,n={}){let r=ht(e,n);return new Promise((s,i)=>{let o=createServer((d,l)=>{d.method==="POST"&&d.url==="/idoc"?r(d,l):(l.writeHead(404,{"Content-Type":"text/plain"}),l.end("Not Found"));}),a=()=>{let d=o.address(),l=typeof d=="object"&&d!==null?d.port:t;s({port:l,close:()=>new Promise((c,u)=>{o.close(p=>p?u(p):c());})});};n.host?o.listen(t,n.host,a):o.listen(t,a),o.on("error",i);})}var vr=1;function yt(t){if(!t.directory)return [];let e=join(t.directory,t.fileName),n;try{n=readFileSync(e,"utf-8");}catch{return []}try{let r=JSON.parse(n);if(r?.version!==vr||!Array.isArray(r.entries))return t.onWarn?.(`Cache-Datei ${e} hat ein unbekanntes Format und wird verworfen.`),[];let s=Date.now()-t.ttlMs;return r.entries.filter(i=>i&&typeof i.key=="string"&&typeof i.storedAt=="number"&&i.storedAt>s)}catch(r){return t.onWarn?.(`Cache-Datei ${e} ist unlesbar und wird verworfen. Der Cache wird aus SAP neu aufgebaut.`,r),[]}}function St(t,e){if(!e.directory)return;let n=join(e.directory,e.fileName),r=`${n}.tmp`;try{mkdirSync(dirname(n),{recursive:!0}),writeFileSync(r,JSON.stringify({version:vr,entries:t}),"utf-8"),renameSync(r,n);}catch(s){e.onWarn?.(`Cache konnte nicht nach ${n} geschrieben werden. Der Betrieb l\xE4uft weiter, nach einem Neustart ist der Cache aber leer.`,s);try{unlinkSync(r);}catch{}}}var ha=30,ya="catalog-cache.json",Er="catalog",X=class{cache=null;ttlMs;key;persistence;constructor(e={}){let n=typeof e=="number"?{ttlMinutes:e}:e;this.ttlMs=(n.ttlMinutes??ha)*60*1e3;let r=(n.baseUrl??"").replace(/\/+$/,"").toLowerCase();if(this.key=r?`${r}|${Er}`:Er,n.persistDir){this.persistence={directory:n.persistDir,fileName:ya,ttlMs:this.ttlMs,...n.onWarn?{onWarn:n.onWarn}:{}};let s=yt(this.persistence).find(i=>i.key===this.key);s&&(this.cache={entries:s.value,timestamp:s.storedAt});}}get(){return this.cache?Date.now()-this.cache.timestamp>this.ttlMs?(this.cache=null,null):this.cache.entries:null}set(e){this.cache={entries:e,timestamp:Date.now()},this.persist();}clear(){this.cache=null,this.persist();}persist(){this.persistence&&St(this.cache?[{key:this.key,value:this.cache.entries,storedAt:this.cache.timestamp}]:[],this.persistence);}};var pn=0;function Rr(t){let e=t.docnum??`${Date.now().toString().padStart(13,"0")}${String(pn++).padStart(3,"0")}`;pn>=1e3&&(pn=0);let n=t.mesType??t.idocType.replace(/\d+$/,"");return {TABNAM:"EDI_DC40",DOCNUM:e,DIRECT:"2",IDOCTYP:t.idocType,MESTYP:n,SNDPOR:t.sndpor,SNDPRT:t.sndprt??"LS",SNDPRN:t.sndprn,RCVPOR:t.rcvpor,RCVPRT:t.rcvprt??"LS",RCVPRN:t.rcvprn,...t.extra}}function Sa(t,e,n){return e.required&&n===""?{segmentName:t,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:n,violation:"Required field is empty"}:n===""?null:(e.dataType==="CHAR"||e.dataType==="NUMC")&&n.length>e.length?{segmentName:t,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:n,violation:`Field value exceeds max length ${e.length} (actual: ${n.length})`}:e.dataType==="NUMC"&&!/^\d*$/.test(n)?{segmentName:t,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:n,violation:"NUMC field must contain only digits"}:e.dataType==="DATS"&&!/^\d{8}$/.test(n)?{segmentName:t,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:n,violation:"DATS field must be in YYYYMMDD format"}:e.dataType==="TIMS"&&!/^\d{6}$/.test(n)?{segmentName:t,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:n,violation:"TIMS field must be in HHMMSS format"}:null}function ba(t,e){return t.filter(n=>n.name===e).length}function Cr(t,e,n){let r=new Set;for(let s of t){if(r.has(s.name))continue;r.add(s.name);let i=e.get(s.name);if(!i)continue;let o=ba(t,s.name);o>i.maxOccurrence&&n.push({segmentName:s.name,fieldName:"",expectedType:"",expectedLength:i.maxOccurrence,actualValue:String(o),violation:`Segment count ${o} exceeds maxOccurrence ${i.maxOccurrence}`});}for(let s of t){let i=e.get(s.name);if(!i){n.push({segmentName:s.name,fieldName:"",expectedType:"",expectedLength:0,actualValue:"",violation:`Unknown segment '${s.name}' not found in metadata`});continue}for(let o of i.fields){let a=s.fields[o.name]??"",d=Sa(s.name,o,a);d&&n.push(d);}s.children&&s.children.length>0&&Cr(s.children,e,n);}}function Pr(t,e){if(!e)return [];let n=new Map;for(let s of e.segments)n.set(s.name,s);let r=[];return Cr(t,n,r),r}var wa=new XMLBuilder({ignoreAttributes:false,attributeNamePrefix:"@_",format:true,suppressEmptyNode:false});function _r(t){let e={"@_SEGMENT":"1"};for(let[n,r]of Object.entries(t.fields))e[n]=r;if(t.children)for(let n of t.children){let r=_r(n),s=e[n.name];s?Array.isArray(s)?s.push(r):e[n.name]=[s,r]:e[n.name]=r;}return e}function kr(t,e,n){let r={};for(let i of n){let o=_r(i),a=r[i.name];a?Array.isArray(a)?a.push(o):r[i.name]=[a,o]:r[i.name]=o;}let s={"?xml":{"@_version":"1.0","@_encoding":"UTF-8"},[t]:{IDOC:{"@_BEGIN":"1",EDI_DC40:{"@_SEGMENT":"1",...e},...r}}};return wa.build(s)}var bt=class{constructor(e,n,r){this.httpClient=e;this.sapClient=n;this.partnerDefaults=r;}async send(e){let n={...this.partnerDefaults,...e.partnerOverrides},r=Rr({idocType:e.idocType,sndpor:n.sndpor??"",sndprn:n.sndprn??"",rcvpor:n.rcvpor??"",rcvprn:n.rcvprn??"",sndprt:n.sndprt,rcvprt:n.rcvprt,mesType:n.mesType,docnum:n.docnum,extra:n.extra});if(e.metadata){let u=Pr(e.segments,e.metadata);if(u.length>0)return {success:false,docnum:r.DOCNUM,message:"Validation failed",violations:u}}let s=kr(e.idocType,r,e.segments),i="/sap/bc/idoc_xml";this.sapClient&&(i+=`?sap-client=${this.sapClient}`);let o=await this.httpClient.request({method:"post",url:i,data:s,headers:{"Content-Type":"text/xml"}}),a=typeof o.data=="string"?o.data:String(o.data);if(a.includes("<title>IDoc-XML-inbound ok</title>"))return {success:true,docnum:r.DOCNUM,message:"IDoc sent successfully"};let c=a.match(/<title>(.*?)<\/title>/)?.[1]??"IDoc send failed";return {success:false,docnum:r.DOCNUM,message:c}}};var Tt=30,Aa=5e3;function va(t,e,n,r){let s=t?._meta?.progressToken,i=t?.sendNotification;if(s===void 0||typeof i!="function"||r.afterMs<=0)return ()=>{};let a=setInterval(()=>{let d=Date.now()-n;i({method:"notifications/progress",params:{progressToken:s,progress:Math.round(d/1e3),...r.budgetMs?{total:Math.round(r.budgetMs/1e3)}:{},message:`${e.toolName}: ${Math.round(d/1e3)} s, ${e.sapCalls.count} SAP-Aufruf(e)`}}).catch(()=>{});},r.afterMs);return a.unref?.(),()=>clearInterval(a)}var Ea="SAP_TLS_VERIFY=false: Die Zertifikatspr\xFCfung ist abgeschaltet \u2014 die Verbindung zu SAP ist nicht gegen einen Angreifer in der Leitung gesch\xFCtzt. Richtiger Weg: Root-CA \xFCber SAP_CA_CERT (bzw. NODE_EXTRA_CA_CERTS) einbinden und SAP_TLS_VERIFY wieder entfernen.";function Ra(t){return {...t,content:[...t.content,{type:"text",text:`\u26A0\uFE0F ${Ea}`}]}}function Ca(t,e,n,r,s){return {content:[{type:"text",text:JSON.stringify({truncated:true,reason:`Zeitbudget von ${Math.round(s/1e3)} s \xFCberschritten \u2014 der Aufruf wurde abgebrochen, damit die Verbindung nicht h\xE4ngen bleibt.`,hint:"Grenzen Sie die Anfrage ein (top/skip, filter, search) oder erh\xF6hen Sie --tool-timeout. Der Wert sollte unter der kleinsten Client-Grenze bleiben (Cloudflare 100 s, n8n-MCP-Transport 300 s).",tool:t,correlationId:e,elapsedMs:n,sapCalls:r},null,2)}],isError:true}}function Pa(t,e){return {content:[{type:"text",text:JSON.stringify({aborted:true,reason:"Der Client hat die Verbindung verworfen; die Arbeit wurde eingestellt.",tool:t,correlationId:e})}],isError:true}}function Or(t,e,n={}){let{toolTimeoutMs:r,logger:s,tlsVerifyDisabled:i}=n,o=n.progressAfterMs??Aa;return async(a,d)=>{let l=Un(),{signal:c,deadline:u}=Ln(d?.signal,r),p={correlationId:l,toolName:t,signal:c,deadline:u,sapCalls:{count:0}},m=Date.now();return $n(p,async()=>{let g=va(d,p,m,{afterMs:o,...r?{budgetMs:r}:{}}),f="ok",h,b=(async()=>e(a,d))();b.catch(()=>{});try{h=await Promise.race([b,_a(c,t)]),h?.isError&&(f="error");}catch(y){if(y instanceof B)f=y.timedOut?"timeout":"aborted",h=y.timedOut?Ca(t,l,Date.now()-m,p.sapCalls.count,r??0):Pa(t,l);else {f="error";let w=y instanceof Error?y.message:String(y);h={content:[{type:"text",text:JSON.stringify({error:w,tool:t,correlationId:l})}],isError:true};}}f==="ok"&&c?.aborted&&(f=c.reason?.name==="TimeoutError"?"timeout":"aborted"),g();let S=i?Ra(h):h;return s?.info({correlationId:l,tool:t,outcome:f,durationMs:Date.now()-m,sapCalls:p.sapCalls.count,resultBytes:ka(h),...r?{budgetMs:r}:{}},"MCP tool call"),S})}}function _a(t,e){return t?new Promise((n,r)=>{let s=()=>{let i=t.reason?.name==="TimeoutError";r(new B(i,e));};if(t.aborted){s();return}t.addEventListener("abort",s,{once:true});}):new Promise(()=>{})}function ka(t){if(!t?.content)return 0;let e=0;for(let n of t.content)typeof n?.text=="string"&&(e+=Buffer.byteLength(n.text,"utf-8"));return e}var Oa=1440*60*1e3,Ia="metadata-cache.json",O=class{cache=new Map;ttlMs;baseUrl;persistence;constructor(e={}){if(this.ttlMs=e.ttlMs??Oa,this.baseUrl=Da(e.baseUrl),e.persistDir){this.persistence={directory:e.persistDir,fileName:Ia,ttlMs:this.ttlMs,...e.onWarn?{onWarn:e.onWarn}:{}};for(let n of yt(this.persistence))this.cache.set(n.key,{value:n.value,storedAt:n.storedAt});}}get size(){return this.cache.size}get(e){let n=this.key(e),r=this.cache.get(n);if(r){if(Date.now()-r.storedAt>this.ttlMs){this.cache.delete(n);return}return r.value}}set(e,n){this.cache.set(this.key(e),{value:n,storedAt:Date.now()}),this.persist();}has(e){return this.get(e)!==void 0}clear(){this.cache.clear(),this.persist();}key(e){let n=e.replace(/\/+$/,"");return this.baseUrl?`${this.baseUrl}|${n}`:n}persist(){this.persistence&&St([...this.cache.entries()].map(([e,n])=>({key:e,value:n.value,storedAt:n.storedAt})),this.persistence);}};function Da(t){return t?t.replace(/\/+$/,"").toLowerCase():""}var Ir="0.0.0-unknown",Dr="guniweb-sap-mcp";function $a(t){if(typeof t!="string"||t.length===0)return Ir;let e=resolve(t);for(;;){try{let r=readFileSync(join(e,"package.json"),"utf-8"),s=JSON.parse(r);if(s.name===Dr&&typeof s.version=="string")return s.version}catch{}let n=dirname(e);if(n===e)return Ir;e=n;}}var Nr=$a(import.meta.dirname),xr=Dr;function Mr(t){t.registerPrompt("nl-to-odata-query",{title:"Natural Language to OData Query",description:"Convert a natural language question into a correct OData query against an SAP service. Reads entity schema for context, applies V2/V4 syntax rules, and enforces read-only by default.",argsSchema:{serviceUrl:z$2.string().describe("OData service URL, e.g., /sap/opu/odata/sap/API_BUSINESS_PARTNER"),entitySet:z$2.string().describe("Entity set to query, e.g., A_BusinessPartner"),question:z$2.string().describe('Natural language question, e.g., "show me all customers in Germany"'),allowWrite:z$2.boolean().optional().describe("Allow write operations (create/update/delete). Default: false (read-only)")}},async({serviceUrl:e,entitySet:n,question:r,allowWrite:s})=>{let i=s===true?"":`
|
|
110
110
|
|
|
111
111
|
IMPORTANT \u2014 READ-ONLY MODE:
|
|
112
112
|
You MUST only generate read queries (sap_query or sap_nl_query). Do NOT use sap_create, sap_update, or sap_delete.
|
|
@@ -167,7 +167,7 @@ Use the \`sap_nl_query\` tool with these parameters:
|
|
|
167
167
|
- "orders over 1000 EUR" \u2192 \`{ field: "TotalAmount", operator: "gt", value: 1000 }\`
|
|
168
168
|
- "products containing 'bolt'" \u2192 \`{ field: "ProductName", operator: "contains", value: "bolt" }\`
|
|
169
169
|
- "invoices since January 2024" \u2192 \`{ field: "InvoiceDate", operator: "ge", value: "2024-01-01" }\`
|
|
170
|
-
${i}`}}]}});}function
|
|
170
|
+
${i}`}}]}});}function Ur(t){t.registerPrompt("explore-sales-orders",{title:"Explore Sales Orders",description:"Discover and query SAP Sales Order data step by step",argsSchema:{serviceUrl:z$2.string().optional().describe("OData service URL (leave empty to discover first)")}},async({serviceUrl:e})=>({messages:[{role:"user",content:{type:"text",text:`I want to explore SAP Sales Order data. Please help me step by step:
|
|
171
171
|
|
|
172
172
|
1. ${e?`Start by getting metadata for the service at: ${e}`:"Start by listing available services. A common Sales Order service URL is /sap/opu/odata/sap/API_SALES_ORDER_SRV"}
|
|
173
173
|
2. Use sap_list_services to discover available entity sets
|
|
@@ -195,20 +195,21 @@ Use sap_query with appropriate filters to find the material data.`}}]})),t.regis
|
|
|
195
195
|
1. ${r}
|
|
196
196
|
2. Use sap_list_services to discover entity sets
|
|
197
197
|
3. Use sap_get_metadata to understand the Business Partner data model
|
|
198
|
-
${s}`}}]}});}var
|
|
198
|
+
${s}`}}]}});}var vt=class{cache=new Map;buildKey(e,n){let s=Object.entries(n).sort(([i],[o])=>i.localeCompare(o)).map(([i,o])=>`${i}=${o}`).join(",");return `${e}|${s}`}set(e,n,r){this.cache.set(this.buildKey(e,n),r);}get(e,n){return this.cache.get(this.buildKey(e,n))}update(e,n,r){r?this.set(e,n,r):this.cache.delete(this.buildKey(e,n));}};function Ba(t){let e=t.match(/HTTP\/1\.\d\s+(\d+)\s+/);if(!e)return null;let n=Number.parseInt(e[1],10),r=n>=200&&n<300,s=t.indexOf(e[0]),i=t.substring(s),o=i.indexOf(`
|
|
199
199
|
|
|
200
|
-
`),a;o!==-1&&(a=i.substring(o+2).trim());let
|
|
200
|
+
`),a;o!==-1&&(a=i.substring(o+2).trim());let d={status:n,entitySet:""};if(a&&a.length>0)if(r)try{d.result=JSON.parse(a);}catch{}else d.error=a;return d}function $r(t,e){let n=[],r=t.split(`--${e}`);for(let s of r){let i=s.trim();if(!i||i==="--")continue;let o=i.match(/Content-Type:\s*multipart\/mixed;\s*boundary=([^\s\r\n]+)/i);if(o){let d=o[1],l=i.indexOf(`
|
|
201
201
|
|
|
202
|
-
`);if(l!==-1){let
|
|
203
|
-
`);return
|
|
204
|
-
`)}function
|
|
205
|
-
${
|
|
206
|
-
${
|
|
207
|
-
`);
|
|
208
|
-
Content-Type: multipart/mixed; boundary=${
|
|
202
|
+
`);if(l!==-1){let c=i.substring(l+2);n.push($r(c,d).flat());}continue}let a=Ba(i);a&&n.push(a);}return n}function Br(t,e){let n=e.match(/boundary=([^\s;]+)/);if(!n)return [];let r=n[1],s=t.replace(/\r\n/g,`
|
|
203
|
+
`);return $r(s,r)}var La=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,Fa=/^(guid|datetime|datetimeoffset|time|binary|X)'[^']*'$/i,Ha=/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/;function Et(t,e){if(t==null)return "null";if(e){let r=ja(t,e);if(r!==void 0)return r}if(typeof t=="boolean"||typeof t=="number"||typeof t=="bigint")return String(t);if(t instanceof Date)return `datetime'${Fr(t.toISOString())}'`;let n=String(t);return Fa.test(n)?n:La.test(n)?`guid'${n}'`:Lr(n)}function ja(t,e){let n=t instanceof Date?t.toISOString():String(t);switch(e){case "Edm.String":return Lr(n);case "Edm.Guid":return `guid'${Fe(n,"guid")}'`;case "Edm.Boolean":{let r=n.toLowerCase();return r==="true"||r==="false"?r:void 0}case "Edm.DateTime":return `datetime'${Fr(Fe(n,"datetime"))}'`;case "Edm.DateTimeOffset":return `datetimeoffset'${Fe(n,"datetimeoffset")}'`;case "Edm.Time":return `time'${Fe(n,"time")}'`;case "Edm.Binary":return `binary'${Fe(n,"binary")}'`;case "Edm.Decimal":return He(n,"M");case "Edm.Int64":return He(n,"L");case "Edm.Double":return He(n,"d");case "Edm.Single":return He(n,"f");case "Edm.Byte":case "Edm.SByte":case "Edm.Int16":case "Edm.Int32":return He(n,"");default:return}}function Lr(t){return `'${t.replace(/'/g,"''")}'`}function Fe(t,e){let n=new RegExp(`^${e}'(.*)'$`,"is").exec(t);return n?n[1]:t}function He(t,e){let n=t.replace(/[mMlLdDfF]$/,"");return Ha.test(n)?`${n}${e}`:void 0}function Fr(t){let e=/^\/Date\((-?\d+)(?:[+-]\d{4})?\)\/$/.exec(t),n=e?new Date(Number(e[1])).toISOString():t;return /^\d{4}-\d{2}-\d{2}$/.test(n)&&(n=`${n}T00:00:00`),n.replace(/(?:Z|[+-]00:?00)$/i,"").replace(/\.0+$/,"")}function mn(t,e){let n=t?.entitySets.find(i=>i.name===e),r=n&&t?.entityTypes.find(i=>i.name===n.entityTypeName);if(!r)return;let s={};for(let i of [...r.properties,...r.keys])s[i.name]=i.type;return s}function Hr(t,e){let n=t?.functionImports?.find(r=>r.name===e);if(n)return Object.fromEntries(n.parameters.map(r=>[r.name,r.type]))}function jr(t,e){return Et(t,e)}function Re(t,e,n){let r=Object.entries(e);if(r.length===1){let[i,o]=r[0];return `${t}(${jr(o,n?.[i])})`}let s=r.map(([i,o])=>`${i}=${jr(o,n?.[i])}`);return `${t}(${s.join(",")})`}function fn(t,e){let n={$format:"json"};return t.filter&&(n.$filter=t.filter),t.select&&t.select.length>0&&(n.$select=t.select.join(",")),t.expand&&t.expand.length>0&&(n.$expand=t.expand.join(",")),t.orderby&&(n.$orderby=t.orderby),t.top!==void 0&&(n.$top=String(t.top)),t.skip!==void 0&&(n.$skip=String(t.skip)),e&&(n.$inlinecount="allpages"),n}function Va(t,e){let n=[],r=new Map;for(let s of t){if(s.method==="get"){n.push({kind:"single",operations:[s]});continue}if(!s.changesetId){n.push({kind:"changeset",operations:[s]});continue}let i=r.get(s.changesetId);i||(i={kind:"changeset",operations:[]},r.set(s.changesetId,i),n.push(i)),i.operations.push(s);}for(let s of e??[])n.push({kind:"changeset",operations:s.operations});return n}function Wr(t,e){let n=t.key?Re(t.entitySet,t.key,e?.(t.entitySet)):t.entitySet,r=t.method==="patch"?"MERGE":t.method.toUpperCase(),{__etag:s,...i}=t.data??{},o=t.data!==void 0&&(r==="POST"||r==="MERGE")?JSON.stringify(i):void 0,a=["Content-Type: application/http","Content-Transfer-Encoding: binary","",`${r} ${n} HTTP/1.1`,"Accept: application/json"];return (r==="MERGE"||r==="DELETE")&&a.push(`If-Match: ${typeof s=="string"?s:"*"}`),o!==void 0&&a.push("Content-Type: application/json"),a.push("",o??""),a.join(`\r
|
|
204
|
+
`)}function qr(t,e,n,r){let s=`batch_${randomUUID()}`,i=Va(e,n),o=[];for(let a of i){if(a.kind==="single"){o.push(`--${s}\r
|
|
205
|
+
${Wr(a.operations[0],r)}`);continue}let d=`changeset_${randomUUID()}`,l=[...a.operations.map(c=>`--${d}\r
|
|
206
|
+
${Wr(c,r)}`),`--${d}--`].join(`\r
|
|
207
|
+
`);o.push(`--${s}\r
|
|
208
|
+
Content-Type: multipart/mixed; boundary=${d}\r
|
|
209
209
|
\r
|
|
210
|
-
${
|
|
211
|
-
`),o=`multipart/mixed; boundary=${r}`;return {body:i,contentType:o}}var _a=new Set(["EntityType","Property","NavigationProperty","Association","EntitySet","AssociationSet","PropertyRef","End","FunctionImport","Parameter"]),ka=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",removeNSPrefix:true,processEntities:false,htmlEntities:false,isArray:t=>_a.has(t)});function cn(t){return t===void 0?true:String(t).toLowerCase()!=="false"}function At(t,e){let r=ka.parse(t).Edmx.DataServices.Schema,s=Array.isArray(r)?r[0]:r,i=s.EntityType??[],o=s.Association??[],a=s.EntityContainer,c=a?.EntitySet??[],l=new Map;for(let f of o)l.set(f["@_Name"],f);let d=i.map(f=>{let h=Oa(f.Property??[]),S=(f.Key?.PropertyRef??[]).map(A=>A["@_Name"]),b=h.filter(A=>S.includes(A.name)),y=Ia(f.NavigationProperty??[],l);return {name:f["@_Name"],keys:b,properties:h,navigationProperties:y}}),u=c.map(f=>({name:f["@_Name"],entityTypeName:dn(f["@_EntityType"]),creatable:cn(f["@_creatable"]),updatable:cn(f["@_updatable"]),deletable:cn(f["@_deletable"])})),m=a?.FunctionImport??[],p=Da(Array.isArray(m)?m:[m]),g={version:"v2",serviceUrl:e,entityTypes:d,entitySets:u};return p.length>0&&(g.functionImports=p),g}function Oa(t){return t.map(e=>{let n={name:e["@_Name"],type:e["@_Type"],nullable:e["@_Nullable"]!=="false"};return e["@_MaxLength"]!==void 0&&(n.maxLength=Number.parseInt(e["@_MaxLength"],10)),e["@_Precision"]!==void 0&&(n.precision=Number.parseInt(e["@_Precision"],10)),e["@_Scale"]!==void 0&&(n.scale=Number.parseInt(e["@_Scale"],10)),n})}function Ia(t,e){return t.map(n=>{let r=dn(n["@_Relationship"]),s=e.get(r),i="Unknown",o=false;if(s){let a=s.End.find(c=>c["@_Role"]===n["@_ToRole"]);a&&(i=dn(a["@_Type"]),o=a["@_Multiplicity"]==="*");}return {name:n["@_Name"],targetEntityType:i,isCollection:o}})}function Da(t){return t.filter(e=>e["@_Name"]).map(e=>{let n=(e.Parameter??[]).map(s=>({name:s["@_Name"],type:s["@_Type"],...s["@_Nullable"]==="true"?{nullable:true}:{}})),r={name:e["@_Name"],httpMethod:e["@_HttpMethod"]??"POST",parameters:n};return e["@_ReturnType"]&&(r.returnType=e["@_ReturnType"]),r})}function dn(t){let e=t.lastIndexOf(".");return e>=0?t.substring(e+1):t}var Na=/^\/Date\((-?\d+)([+-]\d{4})?\)\/$/;function de(t){if(!t||typeof t!="object")return {results:[]};let e=t;if("d"in e&&e.d&&typeof e.d=="object"){let n=e.d;if("results"in n&&Array.isArray(n.results)){let s={results:n.results};return n.__count!==void 0&&(s.count=Number(n.__count)),typeof n.__next=="string"&&(s.nextLink=n.__next),s}return {results:[{...n}]}}return "results"in e&&Array.isArray(e.results)?{results:e.results}:{results:[e]}}function xr(t){let e=Na.exec(t);if(!e)return t;let n=Number(e[1]);return new Date(n).toISOString()}function q(t){if(t==null)return t;if(Array.isArray(t))return t.map(e=>q(e));if(typeof t=="object"){let e=t,n={};for(let[r,s]of Object.entries(e))r!=="__metadata"&&(s&&typeof s=="object"&&!Array.isArray(s)&&"__deferred"in s||(typeof s=="string"?n[r]=xr(s):n[r]=q(s)));return n}return typeof t=="string"?xr(t):t}var Mr=10,Fe=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.etagCache=r;}async read(e,n,r){let s=an(r??{});if(n){let i=`${this.serviceUrl}/${Z(e,n)}`,o=await this.httpClient.request({method:"get",url:i,headers:{Accept:"application/json"},params:s});o.headers.etag&&this.etagCache.set(e,n,o.headers.etag);let a=de(o.data);return {...a,results:a.results.map(c=>q(c))}}return this.fetchWithPagination(`${this.serviceUrl}/${e}`,s,r?.maxPages??Mr)}async query(e,n){let r=an(n,true),s=n.maxPages??Mr;return this.fetchWithPagination(`${this.serviceUrl}/${e}`,r,s)}async create(e,n){let r=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/${e}`,data:n,headers:{Accept:"application/json","Content-Type":"application/json"}}),s=de(r.data);return s.results.length>0?q(s.results[0]):{}}async update(e,n,r){let s=`${this.serviceUrl}/${Z(e,n)}`,i=this.etagCache.get(e,n)??"*",o=await this.httpClient.request({method:"post",url:s,data:r,headers:{Accept:"application/json","Content-Type":"application/json","X-HTTP-Method":"MERGE","If-Match":i}});o.headers.etag&&this.etagCache.set(e,n,o.headers.etag);}async delete(e,n){let r=`${this.serviceUrl}/${Z(e,n)}`,s=this.etagCache.get(e,n)??"*";await this.httpClient.request({method:"delete",url:r,headers:{Accept:"application/json","If-Match":s}}),this.etagCache.update(e,n,void 0);}async functionImport(e,n,r){let s=(r??"POST").toLowerCase(),i={$format:"json"};if(n)for(let[l,d]of Object.entries(n))typeof d=="string"?i[l]=`'${d}'`:i[l]=String(d);let o=await this.httpClient.request({method:s,url:`${this.serviceUrl}/${e}`,headers:{Accept:"application/json"},params:i}),a=de(o.data);if(a.results.length>0)return q(a.results[0]);let c=o.data;return c?.d&&typeof c.d=="object"?c.d:{}}async batch(e,n){let{body:r,contentType:s}=Nr(this.serviceUrl,e,n),i=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/$batch`,data:r,headers:{"Content-Type":s,Accept:"multipart/mixed"}}),o=i.headers["content-type"]??s,a=kr(i.data,o),c=[...e,...(n??[]).flatMap(l=>l.operations)];for(let l=0;l<a.length&&l<c.length;l++)a[l].entitySet=c[l].entitySet;return {results:a}}async getMetadata(){let e=await this.httpClient.request({method:"get",url:`${this.serviceUrl}/$metadata`,headers:{Accept:"application/xml"}});return At(e.data,this.serviceUrl)}async fetchWithPagination(e,n,r){let s=[],i,o=e,a=n,c=0,l=false;for(;o&&c<r;){if(c>0&&Ze()){l=true;break}let m=(await this.httpClient.request({method:"get",url:o,headers:{Accept:"application/json"},params:a})).data,p=de(m),g=p.results.map(h=>q(h));s.push(...g),c===0&&p.count!==void 0&&(i=p.count),c++;let f=m?.d;f&&typeof f.__next=="string"?(o=f.__next,a=void 0):o=void 0;}let d={results:s};return i!==void 0&&(d.count=i),o&&(d.nextLink=o),l&&(d.truncated=true,d.truncationReason=`Nach ${c} Seite(n) abgebrochen: Zeitbudget des Tool-Aufrufs ersch\xF6pft oder Client hat die Verbindung verworfen. Mit skip=${s.length} weiterlesen.`),d}};function Ur(t){let n=(typeof t=="string"?JSON.parse(t):t).responses;return !n||!Array.isArray(n)?[]:n.map(r=>{let s=r.status,i=s>=200&&s<300,o={status:s,entitySet:""};return r.body!==void 0&&r.body!==null&&(i?o.result=r.body:o.error=JSON.stringify(r.body)),o})}function $r(t){return typeof t=="string"?`'${t}'`:String(t)}function Q(t,e){let n=Object.entries(e);if(n.length===1){let[,s]=n[0];return `${t}(${$r(s)})`}let r=n.map(([s,i])=>`${s}=${$r(i)}`).join(",");return `${t}(${r})`}function wt(t,e){let n={};return t.filter&&(n.$filter=t.filter),t.select?.length&&(n.$select=t.select.join(",")),t.expand?.length&&(n.$expand=t.expand.join(",")),t.orderby&&(n.$orderby=t.orderby),t.top!==void 0&&(n.$top=String(t.top)),t.skip!==void 0&&(n.$skip=String(t.skip)),e&&(n.$count="true"),n}function Br(t,e){let n=[];for(let s=0;s<e.length;s++){let i=e[s],o=i.key?Q(i.entitySet,i.key):i.entitySet,a={id:String(s+1),method:i.method.toUpperCase(),url:o};i.changesetId&&(a.atomicityGroup=i.changesetId),i.data&&(i.method==="post"||i.method==="patch"||i.method==="delete")&&(a.headers={"Content-Type":"application/json"},a.body=i.data),n.push(a);}return {body:JSON.stringify({requests:n}),contentType:"application/json"}}function Lr(t){let e=t.split(".");return e[e.length-1]}function xa(t,e){return {name:t,type:e.$Type??"Edm.String",nullable:e.$Nullable??false,maxLength:e.$MaxLength,precision:e.$Precision,scale:e.$Scale}}function Fr(t,e){let n=JSON.parse(t),r=[],s=[],i=[];for(let[a,c]of Object.entries(n)){if(a.startsWith("$"))continue;let l=c;for(let[d,u]of Object.entries(l)){let m=u;if(m.$Kind==="Action"||m.$Kind==="Function"){let p=m.$IsBound??false,g=m.$Parameter??[],f=p&&g.length>0?g[0].$Name:void 0,h=(p?g.slice(1):g).map(b=>({name:b.$Name,type:b.$Type??"Edm.String",...b.$Nullable===true?{nullable:true}:{}})),S=m.$ReturnType?m.$ReturnType.$Type:void 0;i.push({name:d,isBound:p,kind:m.$Kind,...f?{bindingParameter:f}:{},parameters:h,...S?{returnType:S}:{}});}else if(m.$Kind==="EntityType"){let p=[],g=[];for(let[S,b]of Object.entries(m)){if(S.startsWith("$"))continue;let y=b;y.$Kind==="NavigationProperty"?g.push({name:S,targetEntityType:Lr(y.$Type),isCollection:y.$Collection??false}):p.push(xa(S,y));}let h=(m.$Key??[]).map(S=>{let b=p.find(y=>y.name===S);return b||{name:S,type:"Edm.String",nullable:false}});r.push({name:d,keys:h,properties:p,navigationProperties:g});}else if(m.$Kind==="EntityContainer")for(let[p,g]of Object.entries(m)){if(p.startsWith("$"))continue;let f=g;f.$Collection&&f.$Type&&s.push({name:p,entityTypeName:Lr(f.$Type),creatable:ln(f,"InsertRestrictions","Insertable"),updatable:ln(f,"UpdateRestrictions","Updatable"),deletable:ln(f,"DeleteRestrictions","Deletable")});}}}let o={version:"v4",serviceUrl:e,entityTypes:r,entitySets:s};return i.length>0&&(o.actions=i),o}function ln(t,e,n){for(let[r,s]of Object.entries(t)){if(!r.startsWith("@")||!r.endsWith(e))continue;let i=s?.[n];if(typeof i=="boolean")return i}return true}var Ma=10,vt=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.etagCache=r;}async read(e,n,r){if(n){let l=`${this.serviceUrl}/${Q(e,n)}`,d=r?wt(r):void 0,u=await this.httpClient.request({method:"get",url:l,params:d}),m=u.data,p=u.headers.etag??u.headers.ETag;return p&&this.etagCache.set(e,n,p),{results:[m]}}let s=r?wt(r):void 0,o=(await this.httpClient.request({method:"get",url:`${this.serviceUrl}/${e}`,params:s})).data,a=o.value??[],c=o["@odata.count"];return {results:a,count:c}}async query(e,n){let r=n.maxPages??Ma,s=wt(n,true),o=(await this.httpClient.request({method:"get",url:`${this.serviceUrl}/${e}`,params:s})).data,a=[...o.value??[]],c=o["@odata.count"],l=o["@odata.nextLink"],d=1,u=false;for(;l&&d<r;){if(Ze()){u=true;break}let p=this.resolveNextLink(l),f=(await this.httpClient.request({method:"get",url:p})).data,h=f.value??[];a.push(...h),l=f["@odata.nextLink"],d++;}let m={results:a,count:c};return l&&(m.nextLink=l),u&&(m.truncated=true,m.truncationReason=`Nach ${d} Seite(n) abgebrochen: Zeitbudget des Tool-Aufrufs ersch\xF6pft oder Client hat die Verbindung verworfen. Mit skip=${a.length} weiterlesen.`),m}async create(e,n){return (await this.httpClient.request({method:"post",url:`${this.serviceUrl}/${e}`,data:n,headers:{"Content-Type":"application/json"}})).data}async update(e,n,r){let s=`${this.serviceUrl}/${Q(e,n)}`,i=this.etagCache.get(e,n)??"*",o=await this.httpClient.request({method:"patch",url:s,data:r,headers:{"Content-Type":"application/json","If-Match":i}}),a=o.headers.etag??o.headers.ETag;a&&this.etagCache.update(e,n,a);}async delete(e,n){let r=`${this.serviceUrl}/${Q(e,n)}`,s=this.etagCache.get(e,n)??"*";await this.httpClient.request({method:"delete",url:r,headers:{"If-Match":s}});}async batch(e){let{body:n,contentType:r}=Br(this.serviceUrl,e),s=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/$batch`,data:n,headers:{"Content-Type":r,Accept:"application/json"}}),i=Ur(s.data);for(let o=0;o<i.length&&o<e.length;o++)i[o].entitySet=e[o].entitySet;return {results:i}}async callAction(e,n,r,s){let i;return r&&s?i=`${this.serviceUrl}/${Q(r,s)}/${e}`:i=`${this.serviceUrl}/${e}`,(await this.httpClient.request({method:"post",url:i,data:n??{},headers:{"Content-Type":"application/json"}})).data??{}}async callFunction(e,n,r,s){let i=this.buildFunctionParams(n),o;return r&&s?o=`${this.serviceUrl}/${Q(r,s)}/${e}(${i})`:o=`${this.serviceUrl}/${e}(${i})`,(await this.httpClient.request({method:"get",url:o})).data??{}}buildFunctionParams(e){return !e||Object.keys(e).length===0?"":Object.entries(e).map(([n,r])=>typeof r=="string"?`${n}='${r}'`:`${n}=${String(r)}`).join(",")}async getMetadata(){let e=await this.httpClient.request({method:"get",url:`${this.serviceUrl}/$metadata`,headers:{Accept:"application/json"}}),n=typeof e.data=="string"?e.data:JSON.stringify(e.data);return Fr(n,this.serviceUrl)}resolveNextLink(e){return e.startsWith("http://")||e.startsWith("https://")?e:`${this.serviceUrl}/${e}`}};function un(t,e){let n=t.trim();if(n.startsWith("{"))try{if("$Version"in JSON.parse(n))return "v4"}catch{}if(n.startsWith("<?xml")||n.startsWith("<edmx:Edmx")||n.startsWith("<Edmx"))return "v2";if(e){if(e.includes("/odata4/"))return "v4";if(e.includes("/odata/"))return "v2"}return "v2"}var M=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.metadataCache=r??new O;}protocolClient=null;version=null;initPromise=null;etagCache=new Tt;metadataCache;async ensureInitialized(){if(!this.protocolClient)return this.initPromise?this.initPromise:(this.initPromise=this.initialize(),this.initPromise)}async initialize(){let e=this.metadataCache.get(this.serviceUrl);if(e){this.version=e.version,this.protocolClient=this.createProtocolClient(e.version);return}let n="",r=false;try{let s=await this.httpClient.request({method:"get",url:`${this.serviceUrl}/$metadata`,headers:{Accept:"application/xml, application/json"}});n=typeof s.data=="string"?s.data:JSON.stringify(s.data),this.version=un(n,this.serviceUrl),r=!0;}catch{this.version=un("",this.serviceUrl);}if(this.protocolClient=this.createProtocolClient(this.version),r&&this.version==="v2"&&n)try{let s=At(n,this.serviceUrl);this.metadataCache.set(this.serviceUrl,s);}catch{}}createProtocolClient(e){return e==="v4"?new vt(this.httpClient,this.serviceUrl,this.etagCache):new Fe(this.httpClient,this.serviceUrl,this.etagCache)}async getClient(){if(await this.ensureInitialized(),!this.protocolClient)throw new Error("ODataClient initialization failed: protocol client not created");return this.protocolClient}async read(e,n,r){return (await this.getClient()).read(e,n,r)}async query(e,n){return (await this.getClient()).query(e,n)}async create(e,n){return (await this.getClient()).create(e,n)}async update(e,n,r){return (await this.getClient()).update(e,n,r)}async delete(e,n){return (await this.getClient()).delete(e,n)}async batch(e,n){let r=await this.getClient();return this.version==="v4"?r.batch(e):r.batch(e,n)}async functionImport(e,n,r){let s=await this.getClient();if(s instanceof Fe)return s.functionImport(e,n,r?.httpMethod);let i=s;return r?.isFunction?i.callFunction(e,n,r?.entitySet,r?.key):i.callAction(e,n,r?.entitySet,r?.key)}async getMetadata(){let e=this.metadataCache.get(this.serviceUrl);if(e)return e;let n=await this.getClient();if(e=this.metadataCache.get(this.serviceUrl),e)return e;let r=await n.getMetadata();return this.metadataCache.set(this.serviceUrl,r),r}getVersion(){return this.version}};function Hr(t,e,n){let r=n??new O,s=new ResourceTemplate("sap://schema/{serviceUrl}/{entitySet}",{list:void 0});t.registerResource("sap-entity-schema",s,{title:"SAP Entity Schema for Query Building",description:"Property names, types, and keys for an entity set \u2014 optimized context for building OData queries. Includes a sample filter in the correct V2/V4 syntax.",mimeType:"application/json"},async(i,o)=>{let a=decodeURIComponent(o.serviceUrl),c=decodeURIComponent(o.entitySet),d=await new M(e,a,r).getMetadata(),u=d.entitySets.find(h=>h.name===c);if(!u)return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({error:`Entity set '${c}' not found in service ${a}`,availableEntitySets:d.entitySets.map(h=>h.name)},null,2)}]};let m=d.entityTypes.find(h=>h.name===u.entityTypeName);if(!m)return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({error:`Entity type '${u.entityTypeName}' not found in metadata`},null,2)}]};let p=new Set(m.keys.map(h=>h.name)),g=m.properties.map(h=>({name:h.name,type:h.type,isKey:p.has(h.name)})),f=$a(m.properties,d.version);return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({entitySet:c,version:d.version,properties:g,sampleFilter:f},null,2)}]}});}function $a(t,e){let n=t.find(r=>r.type==="Edm.String");return n?e==="v2"?`${n.name} eq 'example' or substringof('example',${n.name})`:`${n.name} eq 'example' or contains(${n.name},'example')`:"PropertyName eq 'value'"}function jr(t,e){let n=new ResourceTemplate("idoc://received/{docnum}",{list:void 0});t.registerResource("idoc-received",n,{title:"Received IDoc",description:"Received IDoc data from webhook",mimeType:"application/json"},async(r,s)=>{let i=s.docnum,o=e.get(i);return o?{contents:[{uri:r.toString(),mimeType:"application/json",text:JSON.stringify({docnum:o.docnum,idocType:o.idocType,mesType:o.mesType,receivedAt:o.receivedAt.toISOString(),parsed:o.parsed},null,2)}]}:{contents:[{uri:r.toString(),mimeType:"application/json",text:JSON.stringify({error:`IDoc with docnum '${i}' not found`},null,2)}]}});}function Vr(t,e,n){let r=n??new O,s=new ResourceTemplate("sap://metadata/{serviceUrl}",{list:void 0});t.registerResource("sap-metadata",s,{title:"SAP Service Metadata",description:"Entity types, properties, keys, and navigation properties for an SAP OData service",mimeType:"application/json"},async(i,o)=>{let a=decodeURIComponent(o.serviceUrl),l=await new M(e,a,r).getMetadata();return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({version:l.version,serviceUrl:l.serviceUrl,entityTypes:l.entityTypes,entitySets:l.entitySets},null,2)}]}});}var pn={"business-partner":{domain:"business-partner",description:"Customer, supplier, and business partner master data",services:[{name:"API_BUSINESS_PARTNER",path:"/sap/opu/odata/sap/API_BUSINESS_PARTNER",version:"v2",keyEntitySets:["A_BusinessPartner","A_BusinessPartnerAddress","A_Customer","A_Supplier"]}],queryTemplates:[{name:"find-customer-by-name",description:"Find customers by name (substring search)",entitySet:"A_Customer",filters:[{field:"CustomerName",operator:"contains",value:""}],select:["Customer","CustomerName","CustomerFullName","Country"],top:20},{name:"find-supplier-by-country",description:"Find suppliers in a specific country",entitySet:"A_Supplier",filters:[{field:"Country",operator:"eq",value:""}],select:["Supplier","SupplierName","Country","Region"],top:50}],suggestedTool:"sap_nl_query",commonQueries:["Find customers by name","List suppliers in a country","Get business partner addresses"]},sales:{domain:"sales",description:"Sales orders, billing documents, and revenue tracking",services:[{name:"API_SALES_ORDER_SRV",path:"/sap/opu/odata/sap/API_SALES_ORDER_SRV",version:"v2",keyEntitySets:["A_SalesOrder","A_SalesOrderItem"]},{name:"API_BILLING_DOCUMENT_SRV",path:"/sap/opu/odata/sap/API_BILLING_DOCUMENT_SRV",version:"v2",keyEntitySets:["A_BillingDocument"]}],queryTemplates:[{name:"recent-sales-orders",description:"Sales orders created this month",entitySet:"A_SalesOrder",filters:[{field:"CreationDate",operator:"ge",value:"$MONTH_START"}],select:["SalesOrder","SalesOrderType","SoldToParty","TotalNetAmount","CreationDate"],orderby:"CreationDate desc",top:50},{name:"overdue-invoices",description:"Billing documents past due date",entitySet:"A_BillingDocument",filters:[{field:"BillingDocumentDate",operator:"lt",value:"$TODAY"}],select:["BillingDocument","BillingDocumentType","SoldToParty","TotalNetAmount","BillingDocumentDate"],orderby:"BillingDocumentDate asc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Recent sales orders","Overdue invoices","Sales by customer"]},finance:{domain:"finance",description:"Journal entries, accounting documents, and financial postings",services:[{name:"API_JOURNALENTRY_SRV",path:"/sap/opu/odata/sap/API_JOURNALENTRY_SRV",version:"v2",keyEntitySets:["A_JournalEntry"]},{name:"API_OPLACCTG_SRV",path:"/sap/opu/odata/sap/API_OPLACCTG_SRV",version:"v2",keyEntitySets:["A_OperationalAcctgDocItem"]}],queryTemplates:[{name:"recent-journal-entries",description:"Journal entries posted this month",entitySet:"A_JournalEntry",filters:[{field:"DocumentDate",operator:"ge",value:"$MONTH_START"}],select:["AccountingDocument","CompanyCode","DocumentDate","PostingDate","DocumentType"],orderby:"DocumentDate desc",top:50},{name:"open-items-by-account",description:"Open accounting items for a G/L account",entitySet:"A_OperationalAcctgDocItem",filters:[{field:"GLAccount",operator:"eq",value:""}],select:["AccountingDocument","CompanyCode","GLAccount","AmountInCompanyCodeCurrency","PostingDate"],top:100}],suggestedTool:"sap_nl_query",commonQueries:["Recent journal entries","Open items by account","Accounting documents this month"]},procurement:{domain:"procurement",description:"Purchase orders, purchase requisitions, and supplier management",services:[{name:"API_PURCHASEORDER_PROCESS_SRV",path:"/sap/opu/odata/sap/API_PURCHASEORDER_PROCESS_SRV",version:"v2",keyEntitySets:["A_PurchaseOrder","A_PurchaseOrderItem"]},{name:"API_PURCHASEREQ_PROCESS_SRV",path:"/sap/opu/odata/sap/API_PURCHASEREQ_PROCESS_SRV",version:"v2",keyEntitySets:["A_PurchaseRequisition"]}],queryTemplates:[{name:"open-purchase-orders",description:"Purchase orders not yet fully delivered",entitySet:"A_PurchaseOrder",filters:[],select:["PurchaseOrder","PurchaseOrderType","Supplier","PurchasingOrganization","CreationDate"],orderby:"CreationDate desc",top:50},{name:"recent-purchase-requisitions",description:"Purchase requisitions created this month",entitySet:"A_PurchaseRequisition",filters:[{field:"CreationDate",operator:"ge",value:"$MONTH_START"}],select:["PurchaseRequisition","PurchaseRequisitionType","CreationDate"],orderby:"CreationDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Open purchase orders","Recent purchase requisitions","Purchase orders by supplier"]},material:{domain:"material",description:"Material master data and material document movements",services:[{name:"API_PRODUCT_SRV",path:"/sap/opu/odata/sap/API_PRODUCT_SRV",version:"v2",keyEntitySets:["A_Product"]},{name:"API_MATERIAL_DOCUMENT_SRV",path:"/sap/opu/odata/sap/API_MATERIAL_DOCUMENT_SRV",version:"v2",keyEntitySets:["A_MaterialDocumentHeader"]}],queryTemplates:[{name:"find-material-by-description",description:"Search materials by description text",entitySet:"A_Product",filters:[{field:"ProductDescription",operator:"contains",value:""}],select:["Product","ProductType","ProductDescription","BaseUnit"],top:20},{name:"recent-material-movements",description:"Material documents created this month",entitySet:"A_MaterialDocumentHeader",filters:[{field:"DocumentDate",operator:"ge",value:"$MONTH_START"}],select:["MaterialDocument","MaterialDocumentYear","DocumentDate","PostingDate"],orderby:"DocumentDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Find material by description","Recent material movements","Material stock levels"]},logistics:{domain:"logistics",description:"Inbound and outbound deliveries, shipping, and transportation",services:[{name:"API_INBOUND_DELIVERY_SRV",path:"/sap/opu/odata/sap/API_INBOUND_DELIVERY_SRV",version:"v2",keyEntitySets:["A_InbDeliveryHeader"]},{name:"API_OUTBOUND_DELIVERY_SRV",path:"/sap/opu/odata/sap/API_OUTBOUND_DELIVERY_SRV",version:"v2",keyEntitySets:["A_OutbDeliveryHeader"]}],queryTemplates:[{name:"pending-inbound-deliveries",description:"Inbound deliveries not yet received",entitySet:"A_InbDeliveryHeader",filters:[],select:["DeliveryDocument","Supplier","DeliveryDate","ReceivingPlant"],orderby:"DeliveryDate asc",top:50},{name:"recent-outbound-deliveries",description:"Outbound deliveries created this month",entitySet:"A_OutbDeliveryHeader",filters:[{field:"CreationDate",operator:"ge",value:"$MONTH_START"}],select:["DeliveryDocument","SoldToParty","CreationDate","DeliveryDate"],orderby:"CreationDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Pending inbound deliveries","Recent outbound deliveries","Deliveries by ship-to party"]},hr:{domain:"hr",description:"Employee master data and organizational assignments",services:[{name:"API_BUSINESS_PARTNER",path:"/sap/opu/odata/sap/API_BUSINESS_PARTNER",version:"v2",keyEntitySets:["A_BusinessPartner"]}],queryTemplates:[{name:"find-employee-by-name",description:"Search employees by name (BP category 2 = Person)",entitySet:"A_BusinessPartner",filters:[{field:"BusinessPartnerCategory",operator:"eq",value:"2"},{field:"BusinessPartnerFullName",operator:"contains",value:""}],select:["BusinessPartner","BusinessPartnerFullName","BusinessPartnerCategory","OrganizationBPName1"],top:20},{name:"employees-by-department",description:"List employees assigned to a department/organization",entitySet:"A_BusinessPartner",filters:[{field:"BusinessPartnerCategory",operator:"eq",value:"2"},{field:"OrganizationBPName1",operator:"contains",value:""}],select:["BusinessPartner","BusinessPartnerFullName","OrganizationBPName1"],top:50}],suggestedTool:"sap_nl_query",commonQueries:["Find employee by name","Employees by department"]},production:{domain:"production",description:"Production orders, planned orders, and manufacturing execution",services:[{name:"API_PLANNED_ORDER",path:"/sap/opu/odata/sap/API_PLANNED_ORDER",version:"v2",keyEntitySets:["PlannedOrder"]},{name:"API_PRODUCTION_ORDER",path:"/sap/opu/odata/sap/API_PRODUCTION_ORDER",version:"v2",keyEntitySets:["A_ProductionOrder"]}],queryTemplates:[{name:"open-production-orders",description:"Production orders not yet completed",entitySet:"A_ProductionOrder",filters:[],select:["ManufacturingOrder","ManufacturingOrderType","Material","ProductionPlant","MfgOrderPlannedStartDate"],orderby:"MfgOrderPlannedStartDate asc",top:50},{name:"planned-orders-this-month",description:"Planned orders created this month",entitySet:"PlannedOrder",filters:[{field:"PlannedOrderCreationDate",operator:"ge",value:"$MONTH_START"}],select:["PlannedOrder","Material","ProductionPlant","PlannedOrderCreationDate"],orderby:"PlannedOrderCreationDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Open production orders","Planned orders this month","Production orders by material"]},quality:{domain:"quality",description:"Quality inspections, inspection lots, and quality management",services:[{name:"API_QUALITYINSPECTION",path:"/sap/opu/odata/sap/API_QUALITYINSPECTION",version:"v2",keyEntitySets:["QualityInspection"]}],queryTemplates:[{name:"open-inspections",description:"Quality inspections not yet completed",entitySet:"QualityInspection",filters:[],select:["InspectionLot","Material","Plant","InspectionLotStartDate"],orderby:"InspectionLotStartDate asc",top:50},{name:"inspections-this-month",description:"Quality inspections started this month",entitySet:"QualityInspection",filters:[{field:"InspectionLotStartDate",operator:"ge",value:"$MONTH_START"}],select:["InspectionLot","Material","Plant","InspectionLotStartDate"],orderby:"InspectionLotStartDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Open quality inspections","Inspections this month"]},"plant-maintenance":{domain:"plant-maintenance",description:"Maintenance notifications, orders, and equipment management",services:[{name:"API_MAINTNOTIFICATION",path:"/sap/opu/odata/sap/API_MAINTNOTIFICATION",version:"v2",keyEntitySets:["MaintenanceNotification"]},{name:"API_MAINTENANCEORDER",path:"/sap/opu/odata/sap/API_MAINTENANCEORDER",version:"v2",keyEntitySets:["MaintenanceOrder"]}],queryTemplates:[{name:"open-maintenance-notifications",description:"Maintenance notifications not yet completed",entitySet:"MaintenanceNotification",filters:[],select:["MaintenanceNotification","NotificationType","FunctionalLocation","ReportedByUser","CreationDate"],orderby:"CreationDate desc",top:50},{name:"maintenance-orders-this-month",description:"Maintenance orders created this month",entitySet:"MaintenanceOrder",filters:[{field:"CreationDate",operator:"ge",value:"$MONTH_START"}],select:["MaintenanceOrder","MaintenanceOrderType","FunctionalLocation","CreationDate"],orderby:"CreationDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Open maintenance notifications","Maintenance orders this month","Notifications by functional location"]},project:{domain:"project",description:"Project systems, WBS elements, and project planning",services:[{name:"API_PROJECT",path:"/sap/opu/odata/sap/API_PROJECT",version:"v2",keyEntitySets:["A_Project","A_ProjectElement"]}],queryTemplates:[{name:"active-projects",description:"List active projects",entitySet:"A_Project",filters:[],select:["Project","ProjectDescription","ResponsiblePerson","PlannedStartDate"],orderby:"PlannedStartDate desc",top:50},{name:"project-elements-by-project",description:"WBS elements for a specific project",entitySet:"A_ProjectElement",filters:[{field:"Project",operator:"eq",value:""}],select:["ProjectElement","ProjectElementDescription","PlannedStartDate","PlannedEndDate"],top:100}],suggestedTool:"sap_nl_query",commonQueries:["Active projects","WBS elements for a project"]},warehouse:{domain:"warehouse",description:"Warehouse resources, tasks, and storage management",services:[{name:"API_WAREHOUSE_RESOURCE",path:"/sap/opu/odata/sap/API_WAREHOUSE_RESOURCE",version:"v2",keyEntitySets:["WarehouseResource","WarehouseTask"]}],queryTemplates:[{name:"open-warehouse-tasks",description:"Warehouse tasks not yet completed",entitySet:"WarehouseTask",filters:[],select:["WarehouseTask","WarehouseTaskType","Warehouse","WarehouseStorageBin"],top:50},{name:"warehouse-resources-by-plant",description:"Warehouse resources for a specific plant",entitySet:"WarehouseResource",filters:[{field:"Plant",operator:"eq",value:""}],select:["WarehouseResource","Warehouse","Plant"],top:50}],suggestedTool:"sap_nl_query",commonQueries:["Open warehouse tasks","Warehouse resources by plant"]},analytics:{domain:"analytics",description:"Financial analytics, expense tracking, and revenue analysis",services:[{name:"API_OPLACCTG_SRV",path:"/sap/opu/odata/sap/API_OPLACCTG_SRV",version:"v2",keyEntitySets:["A_OperationalAcctgDocItem"]}],queryTemplates:[{name:"expenses-this-year",description:"Expense postings since start of current year",entitySet:"A_OperationalAcctgDocItem",filters:[{field:"PostingDate",operator:"ge",value:"$YEAR_START"}],select:["AccountingDocument","CompanyCode","GLAccount","AmountInCompanyCodeCurrency","PostingDate"],orderby:"PostingDate desc",top:100},{name:"monthly-revenue",description:"Revenue postings this month",entitySet:"A_OperationalAcctgDocItem",filters:[{field:"PostingDate",operator:"ge",value:"$MONTH_START"}],select:["AccountingDocument","CompanyCode","GLAccount","AmountInCompanyCodeCurrency","PostingDate"],orderby:"PostingDate desc",top:100}],suggestedTool:"sap_nl_query",commonQueries:["Expenses this year","Monthly revenue","Postings by G/L account"]},"master-data":{domain:"master-data",description:"Cross-domain master data: business partners, materials, cost centers",services:[{name:"API_BUSINESS_PARTNER",path:"/sap/opu/odata/sap/API_BUSINESS_PARTNER",version:"v2",keyEntitySets:["A_BusinessPartner"]},{name:"API_PRODUCT_SRV",path:"/sap/opu/odata/sap/API_PRODUCT_SRV",version:"v2",keyEntitySets:["A_Product"]},{name:"API_COSTCENTER_SRV",path:"/sap/opu/odata/sap/API_COSTCENTER_SRV",version:"v2",keyEntitySets:["A_CostCenter"]}],queryTemplates:[{name:"search-master-data-by-name",description:"Search business partners, materials, or cost centers by name",entitySet:"A_BusinessPartner",filters:[{field:"BusinessPartnerFullName",operator:"contains",value:""}],select:["BusinessPartner","BusinessPartnerFullName","BusinessPartnerCategory"],top:20},{name:"cost-centers-by-company",description:"List cost centers for a company code",entitySet:"A_CostCenter",filters:[{field:"CompanyCode",operator:"eq",value:""}],select:["CostCenter","CostCenterDescription","CompanyCode","ValidityStartDate"],top:100}],suggestedTool:"sap_nl_query",commonQueries:["Search master data by name","Cost centers by company code","Materials by type"]},"cross-application":{domain:"cross-application",description:"Attachments, change documents, and cross-module services",services:[{name:"API_ATTACHMENT_SRV",path:"/sap/opu/odata/sap/API_ATTACHMENT_SRV",version:"v2",keyEntitySets:["A_DocumentInfoRecord"]},{name:"API_BUSINESS_OBJECT_PAGE",path:"/sap/opu/odata/sap/API_BUSINESS_OBJECT_PAGE",version:"v2",keyEntitySets:["A_BusinessObjectPage"]}],queryTemplates:[{name:"attachments-for-object",description:"Find attachments linked to a business object",entitySet:"A_DocumentInfoRecord",filters:[{field:"LinkedSAPObjectKey",operator:"eq",value:""}],select:["DocumentInfoRecordDocNumber","DocumentInfoRecordDocType","LinkedSAPObjectKey"],top:50},{name:"recent-change-documents",description:"Change documents from the last month",entitySet:"A_BusinessObjectPage",filters:[{field:"LastChangeDate",operator:"ge",value:"$MONTH_AGO"}],select:["BusinessObjectType","LastChangeDate","LastChangedByUser"],orderby:"LastChangeDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Attachments for a business object","Recent change documents"]},unknown:{domain:"unknown",description:"Unknown or unclassified domain. Use service discovery to find available services.",services:[],queryTemplates:[{name:"discover-services",description:"No specific domain matched. Use sap_discover_services to explore available SAP services on this system.",entitySet:"",filters:[]},{name:"browse-domains",description:"Browse available routing domains with sap://routing/{domain} to find the right service for your query.",entitySet:"",filters:[]}],suggestedTool:"sap_discover_services",commonQueries:["Use sap_discover_services to find available services","Check sap://routing/{domain} with a specific domain"]}};var Fa={$TODAY:()=>mn(new Date),$YESTERDAY:()=>{let t=new Date;return t.setDate(t.getDate()-1),mn(t)},$MONTH_START:()=>{let t=new Date;return `${t.getFullYear()}-${fn(t.getMonth()+1)}-01`},$YEAR_START:()=>`${new Date().getFullYear()}-01-01`,$MONTH_AGO:()=>{let t=new Date;return t.setMonth(t.getMonth()-1),mn(t)}};function mn(t){return `${t.getFullYear()}-${fn(t.getMonth()+1)}-${fn(t.getDate())}`}function fn(t){return t.toString().padStart(2,"0")}function Ha(t){if(typeof t!="string")return t;let e=Fa[t];return e?e():t}function Wr(t){return {...t,queryTemplates:t.queryTemplates.map(e=>({...e,filters:e.filters.map(n=>({...n,value:Ha(n.value)}))}))}}function qr(t){let e=new ResourceTemplate("sap://routing/{domain}",{list:void 0});t.registerResource("sap-routing",e,{title:"SAP Domain Routing",description:"Service mappings and query templates for a business domain. Returns the relevant SAP services, entity sets, and pre-built query examples for a domain like business-partner, sales, or finance. Use this before building OData queries to know which service and entity set to target.",mimeType:"application/json"},async(n,r)=>{let s=r.domain,i=pn[s];if(!i)return {contents:[{uri:n.toString(),mimeType:"application/json",text:JSON.stringify({error:`Unknown domain '${s}'`,availableDomains:Object.keys(pn)},null,2)}]};let o=Wr(i);return {contents:[{uri:n.toString(),mimeType:"application/json",text:JSON.stringify({...o,note:"Service paths are typical defaults. Use sap_discover_services to verify actual paths on your system."},null,2)}]}});}var gn={core:["test-connection","sap_list_services","sap_get_metadata","sap_discover_services","sap_nl_query"],odata:["sap_read","sap_query","sap_create","sap_update","sap_delete","sap_function","sap_batch","sap_media_upload","sap_media_download"],idoc:["sap_idoc_send","sap_idoc_status","sap_idoc_discover","sap_idoc_list_received"],rfc:["sap_rfc_call","sap_rfc_metadata","sap_bapi_call","sap_bapi_commit","sap_rfc_search_functions"]},Kr=new Set(["sap_create","sap_update","sap_delete","sap_batch","sap_media_upload","sap_idoc_send","sap_rfc_call","sap_bapi_call","sap_bapi_commit"]),zr=["core","odata"],Gr=new Map;for(let[t,e]of Object.entries(gn))for(let n of e)Gr.set(n,t);var Et=class{handles=new Map;activeTiers;options;constructor(e){this.options=e,this.activeTiers=new Set(e.activeTiers);}registerHandle(e,n){this.handles.set(e,n);}applyInitialVisibility(){for(let[e,n]of this.handles)this.shouldToolBeVisible(e)||n.disable();}enableTier(e){if(this.options.allowedTiers&&!this.options.allowedTiers.includes(e))return {error:`Cannot enable ${e} tier: not permitted by the token policy of this connection (allowed: ${this.options.allowedTiers.join(", ")})`};if(e==="idoc"&&!this.options.hasIdocConfig)return {error:"Cannot enable IDoc tier: no IDoc partner configuration provided (--sndprn, --rcvprn required)"};if(e==="rfc"&&!this.options.hasRfcConfig)return {error:"Cannot enable RFC tier: no RFC connection configuration provided (SAP_RFC_ASHOST + SAP_RFC_SYSNR, or SAP_RFC_MSHOST + SAP_RFC_GROUP required)"};this.activeTiers.add(e);let n=gn[e],r=[];for(let s of n){if(this.options.readOnly&&Kr.has(s))continue;let i=this.handles.get(s);i&&(i.enable(),r.push(s));}return {enabled:r,activeTiers:this.getActiveTiers()}}getActiveTiers(){return Array.from(this.activeTiers)}getAllowedTiers(){return this.options.allowedTiers?[...this.options.allowedTiers]:void 0}getToolsByTier(e){return gn[e]}isReadOnlyMode(){return this.options.readOnly}shouldToolBeVisible(e){let n=Gr.get(e);return n?!(!this.activeTiers.has(n)||this.options.allowedTiers&&!this.options.allowedTiers.includes(n)||n==="idoc"&&!this.options.hasIdocConfig||n==="rfc"&&!this.options.hasRfcConfig||this.options.readOnly&&Kr.has(e)):true}};var Yr="/sap/opu/odata/sap",Va=/^[A-Z0-9_/]+$/i,le=class extends Error{guidance;constructor(e){super(`serviceUrl ist weder ein Pfad noch ein technischer Servicename: "${e}"`),this.name="InvalidServiceUrlError",this.guidance=`Erwartet wird ein Pfad wie ${Yr}/API_BUSINESS_PARTNER oder der technische Name (API_BUSINESS_PARTNER), den sap_discover_services als technicalName liefert.`;}};function L(t){let e=t.trim();if(e.length===0)throw new le(t);if(e.startsWith("/"))return e.replace(/\/+$/,"")||"/";if(e.startsWith("http://")||e.startsWith("https://"))try{return new URL(e).pathname.replace(/\/+$/,"")}catch{throw new le(t)}if(Va.test(e))return `${Yr}/${e.replace(/^\/+|\/+$/g,"")}`;throw new le(t)}var He=class extends Error{guidance;extra;constructor(e,n,r={}){super(e),this.name="ToolInputError",this.guidance=n,this.extra=r;}};function _(t,e){if(t instanceof le)return hn({code:"INVALID_INPUT",error:t.message,guidance:t.guidance,...e?.hint?{hint:e.hint}:{}});if(t instanceof He)return hn({code:"INVALID_INPUT",error:t.message,...t.guidance?{guidance:t.guidance}:{},...t.extra,...e?.hint?{hint:e.hint}:{}});let n=ge(t,{serviceUrl:e?.serviceUrl});return hn({code:n.code,error:n.message,...n.httpStatus!==void 0?{httpStatus:n.httpStatus}:{},...n.sapCode?{sapCode:n.sapCode}:{},...n.guidance?{guidance:n.guidance}:{},...e?.hint?{hint:e.hint}:{}})}function hn(t){let e=me()?.correlationId;return {content:[{type:"text",text:JSON.stringify({...t,...e?{correlationId:e}:{}},null,2)}],isError:true}}function Jr(t,e,n){let r=n??new O,s=new Map;function i(a){let c=L(a),l=s.get(c);return l||(l=new M(e,c,r),s.set(c,l)),l}function o(a,c){return _(a,{serviceUrl:c})}t.registerTool("sap_batch",{title:"Execute SAP Batch Request",description:"Execute multiple OData operations in a single batch request. Supports V2 (multipart/mixed) and V4 (JSON) batch formats automatically. Operations can be grouped into changesets for transactional behavior (all succeed or all fail). Returns per-operation status -- individual failures are always reported, never hidden behind the batch HTTP 200.",inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),operations:z$2.array(z$2.object({method:z$2.enum(["get","post","patch","delete"]).describe("HTTP method"),entitySet:z$2.string().describe("Entity set name"),key:z$2.record(z$2.string(),z$2.unknown()).optional().describe("Entity key for read/update/delete"),data:z$2.record(z$2.string(),z$2.unknown()).optional().describe("Request body for create/update"),changesetId:z$2.string().optional().describe("Changeset group ID for transactional grouping")})).describe("Array of operations to execute in batch")}),annotations:{readOnlyHint:false,destructiveHint:true,idempotentHint:false,openWorldHint:true}},async({serviceUrl:a,operations:c})=>{try{let l=i(a),d=c,u=await l.batch(d);return {content:[{type:"text",text:JSON.stringify(u,null,2)}]}}catch(l){return o(l)}});}async function Xr(t,e,n=5){if(t.length===0)return [];let r=Math.max(1,Math.min(Math.floor(n)||1,t.length)),s=new Array(t.length),i=0;async function o(){for(;;){let a=i++;if(a>=t.length)return;s[a]=await e(t[a],a);}}return await Promise.all(Array.from({length:r},()=>o())),s}var Wa=[".integrationsuite.","integrationsuite-trial.",".it-cpi.",".hci.",".prod.apimanagement.","-api.s4hana.ondemand.com"];function Zr(t){let e=t.toLowerCase();return Wa.some(n=>e.includes(n))}var K=class extends Error{constructor(n,r,s,i){super(n);this.httpStatus=r;this.sapGuidance=s;this.isBtpDetected=i;this.name="CatalogError";}},qa=new Set(["ECONNREFUSED","ETIMEDOUT","ENOTFOUND"]);function Ka(t,e=false){switch(t){case 403:return "Catalog access denied. Check transaction PFCG for role assignment and SU53 for authorization trace. Required authorization object: S_SERVICE with SRV_NAME = IWFND_CATALOGSERVICE.";case 404:return e?"V4 catalog not available. Publish service group via /IWFND/V4_ADMIN.":"Catalog service not found. Activate it via transaction /IWFND/MAINT_SERVICE.";case 500:return "SAP system error. Check transaction ST22 for ABAP dumps and SM21 for system log.";default:return "Could not connect to SAP system. Verify the hostname, port, and network connectivity."}}function za(t){switch(t){case "ECONNREFUSED":return "Connection refused. Verify that the SAP system is running and the port is correct.";case "ETIMEDOUT":return "Connection timeout. Check network connectivity and firewall rules.";case "ENOTFOUND":return "Hostname not found. Verify the SAP system hostname in your configuration.";default:return "Could not connect to SAP system. Verify the hostname, port, and network connectivity."}}function Qr(t){return typeof t=="object"&&t!==null&&"response"in t&&typeof t.response=="object"&&t.response!==null&&typeof t.response.status=="number"}function Ga(t){return typeof t=="object"&&t!==null&&"code"in t&&typeof t.code=="string"&&qa.has(t.code)}function je(t,e,n=false){let r=Zr(e);if(Qr(t)){let i=t.response.status,o=Ka(i,n),a="message"in t&&typeof t.message=="string"?t.message:`HTTP ${i}`;return new K(a,i,o,r)}if(Ga(t)){let i=za(t.code),o="message"in t&&typeof t.message=="string"?t.message:t.code;return new K(o,void 0,i,r)}let s=t instanceof Error?t.message:String(t);return new K(s,void 0,void 0,r)}function es(t){return Qr(t)&&t.response.status===404?"V4 catalog not available. Publish service group via /IWFND/V4_ADMIN.":t instanceof Error?t.message:String(t)}var Ya=["/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/iwfnd/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/IWFND/CATALOGSERVICE/ServiceCollection"];function Ja(t){if(typeof t=="object"&&t!==null&&"response"in t)return t.response?.status}async function ts(t,e,n){let r;for(let s of Ya)try{return await t.request({method:"get",url:s,params:{$format:"json",$top:"1"},expectedStatuses:[404]}),n?.onMatch?.(s),s}catch(i){if(i instanceof B)throw i;if(Ja(i)===404){r=i;continue}throw je(i,e,false)}throw je(r??new Error("All V2 catalog paths returned 404"),e,false)}function ns(t){try{return new URL(t).pathname}catch{return t}}function Za(t){let e=t.toLowerCase().replace(/'/g,"''");return [`substringof('${e}',tolower(TechnicalServiceName))`,`substringof('${e}',tolower(Title))`,`substringof('${e}',tolower(Description))`].join(" or ")}var Ct=class t{constructor(e,n,r={}){this.httpClient=e;this.cache=n;this.options=r;}static DEFAULT_V4_URL="/sap/opu/odata4/iwfnd/config/default/iwfnd/catalog/0002/ServiceGroups";resolvedV2Path;serverSideFilterUnsupported=false;async discoverServices(e){let n=this.cache.get();if(n)return {services:n,warnings:[]};if(e&&!this.serverSideFilterUnsupported){let l=await this.tryFilteredFetch(e);if(l)return l}let r=[],[s,i]=await Xr(["v4","v2"],async l=>l==="v4"?await this.fetchV4Catalog().then(d=>({ok:true,services:d}),d=>({ok:false,error:d})):await this.fetchV2Catalog().then(d=>({ok:true,services:d}),d=>({ok:false,error:d})),this.options.concurrency??5),o=[];if(s?.ok)o=s.services;else if(s){if(s.error instanceof B)throw s.error;r.push(`V4 catalog fetch failed: ${es(s.error)}`);}if(!i?.ok){let l=i?.error;throw l instanceof K?l:je(l,this.options.baseUrl??"",false)}let a=i.services,c=this.mergeAndDeduplicate(o,a);return this.cache.set(c),{services:c,warnings:r}}async tryFilteredFetch(e){try{return {services:await this.fetchV2Catalog(Za(e)),warnings:[],filtered:!0}}catch(n){if(n instanceof B)throw n;let r=n?.response?.status;(r===400||r===501)&&(this.serverSideFilterUnsupported=true);return}}async fetchV2Catalog(e){let n=this.options.catalogV2Url??this.resolvedV2Path;return n||(n=await ts(this.httpClient,this.options.baseUrl??"",{onMatch:this.options.onDiscoveryMatch}),this.resolvedV2Path=n),((await this.httpClient.request({method:"get",url:n,params:{$format:"json",...e?{$filter:e}:{}},...e?{expectedStatuses:[400,501]}:{}})).data?.d?.results??[]).map(o=>({technicalName:o.TechnicalServiceName,title:o.Title,serviceUrl:ns(o.ServiceUrl),description:o.Description,version:"v2",serviceVersion:o.TechnicalServiceVersion}))}async fetchV4Catalog(){let e=this.options.catalogV4Url??t.DEFAULT_V4_URL,s=(await this.httpClient.request({method:"get",url:e,params:{$expand:"DefaultSystem($expand=Services)"},headers:{Accept:"application/json"},expectedStatuses:[404]})).data?.value??[],i=[];for(let o of s){let a=o.DefaultSystem?.Services??[];for(let c of a)i.push({technicalName:c.ServiceId??c.ServiceName??"",title:c.ServiceTitle??c.Title??"",serviceUrl:ns(c.ServiceUrl),version:"v4",serviceVersion:c.ServiceVersion});}return i}mergeAndDeduplicate(e,n){let r=new Map;for(let s of e)r.set(s.technicalName,s);for(let s of n)r.has(s.technicalName)||r.set(s.technicalName,s);return [...r.values()]}};var Qa=[[/BUSINESS_?PARTNER|BUPA/i,"business-partner"],[/SALES_ORDER|BILLING|CREDIT_MEMO|DEBIT_MEMO|SALES_CONTRACT|SALES_QUOT/i,"sales"],[/PURCHASEORDER|PURCHASE_ORDER|PURCHASE_REQ|SUPPLIER|PURG/i,"procurement"],[/JOURNALENTRY|JOURNAL_ENTRY|OPLACCTG|COST_CENTER|PROFIT_CENTER|GL_ACCOUNT|FINANCIAL/i,"finance"],[/MATERIAL_DOC|MATERIAL_STOCK|MATERIAL_SRV|PRODUCT\b/i,"material"],[/OUTBOUND_DELIV|INBOUND_DELIV|SHIPPING|TRANSPORTATION/i,"logistics"],[/PRODUCTION_ORDER|PLANNED_ORDER|ROUTING|BOM\b/i,"production"],[/EMPLOYEE|WORKFORCE|PERSONNEL|PAYROLL/i,"hr"],[/INSPECTION|QUALITY/i,"quality"],[/MAINTENANCE|EQUIPMENT|FUNCTIONAL_LOC/i,"plant-maintenance"],[/PROJECT|WBS\b/i,"project"],[/WAREHOUSE|STORAGE_BIN/i,"warehouse"],[/ANALYTIC|CUBE/i,"analytics"]],ec=[[/^[ZY]?MM[_]/i,"procurement"],[/^[ZY]?SD[_]/i,"sales"],[/^[ZY]?FI[_]|^[ZY]?CO[_]/i,"finance"],[/^[ZY]?PP[_]/i,"production"],[/^[ZY]?HR[_]|^[ZY]?PA[_]|^[ZY]?PT[_]/i,"hr"],[/^[ZY]?WM[_]/i,"warehouse"],[/^[ZY]?PM[_]/i,"plant-maintenance"],[/^[ZY]?LE[_]/i,"logistics"]];function tc(t){for(let[e,n]of Qa)if(e.test(t.technicalName))return {...t,category:n,categoryConfidence:"high"};for(let[e,n]of ec)if(e.test(t.technicalName))return {...t,category:n,categoryConfidence:"high"};return {...t,category:"unknown",categoryConfidence:"low"}}function rs(t){return t.map(tc)}function Re(t,e){let n=Number.isFinite(t)&&t>0?Math.floor(t):50,r=Number.isFinite(e)&&e>0?Math.floor(e):0;return {top:Math.min(n,1e3),skip:r}}function Pe(t){let{returned:e,totalCount:n,paging:r,hasMoreHint:s}=t,i=s??(n!==void 0?r.skip+e<n:e>=r.top),o={returned:e,hasMore:i,top:r.top,skip:r.skip};return n!==void 0&&(o.totalCount=n),i&&(o.nextSkip=r.skip+e),o}function ss(t,e,n,r){let s=r??new X,i=new Ct(e,s,n);t.registerTool("sap_discover_services",{title:"Discover SAP OData Services",description:`Discover all available OData services on the connected SAP system. Returns categorized services with search, category filtering, and pagination. Use this as the first step to explore what APIs are available before using sap_list_services or sap_get_metadata.
|
|
210
|
+
${l}`);}return o.push(`--${s}--`),{body:`${o.join(`\r
|
|
211
|
+
`)}\r
|
|
212
|
+
`,contentType:`multipart/mixed; boundary=${s}`,parts:i}}var qa=new Set(["EntityType","Property","NavigationProperty","Association","EntitySet","AssociationSet","PropertyRef","End","FunctionImport","Parameter"]),Ka=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",removeNSPrefix:true,processEntities:false,htmlEntities:false,isArray:t=>qa.has(t)});function gn(t){return t===void 0?true:String(t).toLowerCase()!=="false"}function Rt(t,e){let r=Ka.parse(t).Edmx.DataServices.Schema,s=Array.isArray(r)?r[0]:r,i=s.EntityType??[],o=s.Association??[],a=s.EntityContainer,d=a?.EntitySet??[],l=new Map;for(let f of o)l.set(f["@_Name"],f);let c=i.map(f=>{let h=za(f.Property??[]),b=(f.Key?.PropertyRef??[]).map(w=>w["@_Name"]),S=h.filter(w=>b.includes(w.name)),y=Ga(f.NavigationProperty??[],l);return {name:f["@_Name"],keys:S,properties:h,navigationProperties:y}}),u=d.map(f=>({name:f["@_Name"],entityTypeName:hn(f["@_EntityType"]),creatable:gn(f["@_creatable"]),updatable:gn(f["@_updatable"]),deletable:gn(f["@_deletable"])})),p=a?.FunctionImport??[],m=Ya(Array.isArray(p)?p:[p]),g={version:"v2",serviceUrl:e,entityTypes:c,entitySets:u};return m.length>0&&(g.functionImports=m),g}function za(t){return t.map(e=>{let n={name:e["@_Name"],type:e["@_Type"],nullable:e["@_Nullable"]!=="false"};return e["@_MaxLength"]!==void 0&&(n.maxLength=Number.parseInt(e["@_MaxLength"],10)),e["@_Precision"]!==void 0&&(n.precision=Number.parseInt(e["@_Precision"],10)),e["@_Scale"]!==void 0&&(n.scale=Number.parseInt(e["@_Scale"],10)),n})}function Ga(t,e){return t.map(n=>{let r=hn(n["@_Relationship"]),s=e.get(r),i="Unknown",o=false;if(s){let a=s.End.find(d=>d["@_Role"]===n["@_ToRole"]);a&&(i=hn(a["@_Type"]),o=a["@_Multiplicity"]==="*");}return {name:n["@_Name"],targetEntityType:i,isCollection:o}})}function Ya(t){return t.filter(e=>e["@_Name"]).map(e=>{let n=(e.Parameter??[]).map(s=>({name:s["@_Name"],type:s["@_Type"],...s["@_Nullable"]==="true"?{nullable:true}:{}})),r={name:e["@_Name"],httpMethod:e["@_HttpMethod"]??"POST",parameters:n};return e["@_ReturnType"]&&(r.returnType=e["@_ReturnType"]),r})}function hn(t){let e=t.lastIndexOf(".");return e>=0?t.substring(e+1):t}var Ja=/^\/Date\((-?\d+)([+-]\d{4})?\)\/$/;function ce(t){if(!t||typeof t!="object")return {results:[]};let e=t;if("d"in e&&e.d&&typeof e.d=="object"){let n=e.d;if("results"in n&&Array.isArray(n.results)){let s={results:n.results};return n.__count!==void 0&&(s.count=Number(n.__count)),typeof n.__next=="string"&&(s.nextLink=n.__next),s}return {results:[{...n}]}}return "results"in e&&Array.isArray(e.results)?{results:e.results}:{results:[e]}}function Kr(t){let e=Ja.exec(t);if(!e)return t;let n=Number(e[1]);return new Date(n).toISOString()}function q(t){if(t==null)return t;if(Array.isArray(t))return t.map(e=>q(e));if(typeof t=="object"){let e=t,n={};for(let[r,s]of Object.entries(e))r!=="__metadata"&&(s&&typeof s=="object"&&!Array.isArray(s)&&"__deferred"in s||(typeof s=="string"?n[r]=Kr(s):n[r]=q(s)));return n}return typeof t=="string"?Kr(t):t}var zr=10,je=class{constructor(e,n,r,s){this.httpClient=e;this.serviceUrl=n;this.etagCache=r;this.metadataProvider=s;}async metadataForLiterals(){if(this.metadataProvider)try{return await this.metadataProvider()}catch{return}}async keyUrl(e,n){let r=mn(await this.metadataForLiterals(),e);return `${this.serviceUrl}/${Re(e,n,r)}`}async read(e,n,r){let s=fn(r??{});if(n){let i=await this.keyUrl(e,n),o=await this.httpClient.request({method:"get",url:i,headers:{Accept:"application/json"},params:s});o.headers.etag&&this.etagCache.set(e,n,o.headers.etag);let a=ce(o.data);return {...a,results:a.results.map(d=>q(d))}}return this.fetchWithPagination(`${this.serviceUrl}/${e}`,s,r?.maxPages??zr)}async query(e,n){let r=fn(n,true),s=n.maxPages??zr;return this.fetchWithPagination(`${this.serviceUrl}/${e}`,r,s)}async create(e,n){let r=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/${e}`,data:n,headers:{Accept:"application/json","Content-Type":"application/json"}}),s=ce(r.data);return s.results.length>0?q(s.results[0]):{}}async update(e,n,r){let s=await this.keyUrl(e,n),i=this.etagCache.get(e,n)??"*",o=await this.httpClient.request({method:"post",url:s,data:r,headers:{Accept:"application/json","Content-Type":"application/json","X-HTTP-Method":"MERGE","If-Match":i}});o.headers.etag&&this.etagCache.set(e,n,o.headers.etag);}async delete(e,n){let r=await this.keyUrl(e,n),s=this.etagCache.get(e,n)??"*";await this.httpClient.request({method:"delete",url:r,headers:{Accept:"application/json","If-Match":s}}),this.etagCache.update(e,n,void 0);}async functionImport(e,n,r){let s=await this.metadataForLiterals(),i=s?.functionImports?.find(p=>p.name===e),o=Hr(s,e),a=(r??i?.httpMethod??"POST").toLowerCase(),d={$format:"json"};if(n)for(let[p,m]of Object.entries(n))d[p]=Et(m,o?.[p]);let l=await this.httpClient.request({method:a,url:`${this.serviceUrl}/${e}`,headers:{Accept:"application/json"},params:d}),c=ce(l.data);if(c.results.length>0)return q(c.results[0]);let u=l.data;return u?.d&&typeof u.d=="object"?u.d:{}}async batch(e,n){let r=await this.metadataForLiterals(),{body:s,contentType:i,parts:o}=qr(this.serviceUrl,e,n,m=>mn(r,m)),a=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/$batch`,data:s,headers:{"Content-Type":i,Accept:"multipart/mixed"}}),d=a.headers["content-type"]??i,l=Br(a.data,d),c=new Map;return o.forEach((m,g)=>{let f=l[g];m.operations.forEach((h,b)=>{let S=Array.isArray(f)?f[b]:f;c.set(h,S?{...S,entitySet:h.entitySet}:{status:0,entitySet:h.entitySet,error:"SAP returned no response for this operation"});});}),{results:[...e,...(n??[]).flatMap(m=>m.operations)].map(m=>c.get(m)).filter(m=>m!==void 0)}}async getMetadata(){let e=await this.httpClient.request({method:"get",url:`${this.serviceUrl}/$metadata`,headers:{Accept:"application/xml"}});return Rt(e.data,this.serviceUrl)}async fetchWithPagination(e,n,r){let s=[],i,o=e,a=n,d=0,l=false;for(;o&&d<r;){if(d>0&&et()){l=true;break}let p=(await this.httpClient.request({method:"get",url:o,headers:{Accept:"application/json"},params:a})).data,m=ce(p),g=m.results.map(h=>q(h));s.push(...g),d===0&&m.count!==void 0&&(i=m.count),d++;let f=p?.d;f&&typeof f.__next=="string"?(o=f.__next,a=void 0):o=void 0;}let c={results:s};return i!==void 0&&(c.count=i),o&&(c.nextLink=o),l&&(c.truncated=true,c.truncationReason=`Nach ${d} Seite(n) abgebrochen: Zeitbudget des Tool-Aufrufs ersch\xF6pft oder Client hat die Verbindung verworfen. Mit skip=${s.length} weiterlesen.`),c}};function Gr(t){let n=(typeof t=="string"?JSON.parse(t):t).responses;return !n||!Array.isArray(n)?[]:n.map(r=>{let s=r.status,i=s>=200&&s<300,o={status:s,entitySet:""};return r.body!==void 0&&r.body!==null&&(i?o.result=r.body:o.error=JSON.stringify(r.body)),o})}function Yr(t){return typeof t=="string"?`'${t}'`:String(t)}function Z(t,e){let n=Object.entries(e);if(n.length===1){let[,s]=n[0];return `${t}(${Yr(s)})`}let r=n.map(([s,i])=>`${s}=${Yr(i)}`).join(",");return `${t}(${r})`}function Ct(t,e){let n={};return t.filter&&(n.$filter=t.filter),t.select?.length&&(n.$select=t.select.join(",")),t.expand?.length&&(n.$expand=t.expand.join(",")),t.orderby&&(n.$orderby=t.orderby),t.top!==void 0&&(n.$top=String(t.top)),t.skip!==void 0&&(n.$skip=String(t.skip)),e&&(n.$count="true"),n}function Jr(t,e){let n=[];for(let s=0;s<e.length;s++){let i=e[s],o=i.key?Z(i.entitySet,i.key):i.entitySet,a={id:String(s+1),method:i.method.toUpperCase(),url:o};i.changesetId&&(a.atomicityGroup=i.changesetId),i.data&&(i.method==="post"||i.method==="patch"||i.method==="delete")&&(a.headers={"Content-Type":"application/json"},a.body=i.data),n.push(a);}return {body:JSON.stringify({requests:n}),contentType:"application/json"}}function Xr(t){let e=t.split(".");return e[e.length-1]}function Xa(t,e){return {name:t,type:e.$Type??"Edm.String",nullable:e.$Nullable??false,maxLength:e.$MaxLength,precision:e.$Precision,scale:e.$Scale}}function Zr(t,e){let n=JSON.parse(t),r=[],s=[],i=[];for(let[a,d]of Object.entries(n)){if(a.startsWith("$"))continue;let l=d;for(let[c,u]of Object.entries(l)){let p=u;if(p.$Kind==="Action"||p.$Kind==="Function"){let m=p.$IsBound??false,g=p.$Parameter??[],f=m&&g.length>0?g[0].$Name:void 0,h=(m?g.slice(1):g).map(S=>({name:S.$Name,type:S.$Type??"Edm.String",...S.$Nullable===true?{nullable:true}:{}})),b=p.$ReturnType?p.$ReturnType.$Type:void 0;i.push({name:c,isBound:m,kind:p.$Kind,...f?{bindingParameter:f}:{},parameters:h,...b?{returnType:b}:{}});}else if(p.$Kind==="EntityType"){let m=[],g=[];for(let[b,S]of Object.entries(p)){if(b.startsWith("$"))continue;let y=S;y.$Kind==="NavigationProperty"?g.push({name:b,targetEntityType:Xr(y.$Type),isCollection:y.$Collection??false}):m.push(Xa(b,y));}let h=(p.$Key??[]).map(b=>{let S=m.find(y=>y.name===b);return S||{name:b,type:"Edm.String",nullable:false}});r.push({name:c,keys:h,properties:m,navigationProperties:g});}else if(p.$Kind==="EntityContainer")for(let[m,g]of Object.entries(p)){if(m.startsWith("$"))continue;let f=g;f.$Collection&&f.$Type&&s.push({name:m,entityTypeName:Xr(f.$Type),creatable:yn(f,"InsertRestrictions","Insertable"),updatable:yn(f,"UpdateRestrictions","Updatable"),deletable:yn(f,"DeleteRestrictions","Deletable")});}}}let o={version:"v4",serviceUrl:e,entityTypes:r,entitySets:s};return i.length>0&&(o.actions=i),o}function yn(t,e,n){for(let[r,s]of Object.entries(t)){if(!r.startsWith("@")||!r.endsWith(e))continue;let i=s?.[n];if(typeof i=="boolean")return i}return true}var Za=10,Pt=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.etagCache=r;}async read(e,n,r){if(n){let l=`${this.serviceUrl}/${Z(e,n)}`,c=r?Ct(r):void 0,u=await this.httpClient.request({method:"get",url:l,params:c}),p=u.data,m=u.headers.etag??u.headers.ETag;return m&&this.etagCache.set(e,n,m),{results:[p]}}let s=r?Ct(r):void 0,o=(await this.httpClient.request({method:"get",url:`${this.serviceUrl}/${e}`,params:s})).data,a=o.value??[],d=o["@odata.count"];return {results:a,count:d}}async query(e,n){let r=n.maxPages??Za,s=Ct(n,true),o=(await this.httpClient.request({method:"get",url:`${this.serviceUrl}/${e}`,params:s})).data,a=[...o.value??[]],d=o["@odata.count"],l=o["@odata.nextLink"],c=1,u=false;for(;l&&c<r;){if(et()){u=true;break}let m=this.resolveNextLink(l),f=(await this.httpClient.request({method:"get",url:m})).data,h=f.value??[];a.push(...h),l=f["@odata.nextLink"],c++;}let p={results:a,count:d};return l&&(p.nextLink=l),u&&(p.truncated=true,p.truncationReason=`Nach ${c} Seite(n) abgebrochen: Zeitbudget des Tool-Aufrufs ersch\xF6pft oder Client hat die Verbindung verworfen. Mit skip=${a.length} weiterlesen.`),p}async create(e,n){return (await this.httpClient.request({method:"post",url:`${this.serviceUrl}/${e}`,data:n,headers:{"Content-Type":"application/json"}})).data}async update(e,n,r){let s=`${this.serviceUrl}/${Z(e,n)}`,i=this.etagCache.get(e,n)??"*",o=await this.httpClient.request({method:"patch",url:s,data:r,headers:{"Content-Type":"application/json","If-Match":i}}),a=o.headers.etag??o.headers.ETag;a&&this.etagCache.update(e,n,a);}async delete(e,n){let r=`${this.serviceUrl}/${Z(e,n)}`,s=this.etagCache.get(e,n)??"*";await this.httpClient.request({method:"delete",url:r,headers:{"If-Match":s}});}async batch(e){let{body:n,contentType:r}=Jr(this.serviceUrl,e),s=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/$batch`,data:n,headers:{"Content-Type":r,Accept:"application/json"}}),i=Gr(s.data);for(let o=0;o<i.length&&o<e.length;o++)i[o].entitySet=e[o].entitySet;return {results:i}}async callAction(e,n,r,s){let i;return r&&s?i=`${this.serviceUrl}/${Z(r,s)}/${e}`:i=`${this.serviceUrl}/${e}`,(await this.httpClient.request({method:"post",url:i,data:n??{},headers:{"Content-Type":"application/json"}})).data??{}}async callFunction(e,n,r,s){let i=this.buildFunctionParams(n),o;return r&&s?o=`${this.serviceUrl}/${Z(r,s)}/${e}(${i})`:o=`${this.serviceUrl}/${e}(${i})`,(await this.httpClient.request({method:"get",url:o})).data??{}}buildFunctionParams(e){return !e||Object.keys(e).length===0?"":Object.entries(e).map(([n,r])=>typeof r=="string"?`${n}='${r}'`:`${n}=${String(r)}`).join(",")}async getMetadata(){let e=await this.httpClient.request({method:"get",url:`${this.serviceUrl}/$metadata`,headers:{Accept:"application/json"}}),n=typeof e.data=="string"?e.data:JSON.stringify(e.data);return Zr(n,this.serviceUrl)}resolveNextLink(e){return e.startsWith("http://")||e.startsWith("https://")?e:`${this.serviceUrl}/${e}`}};function Sn(t,e){let n=t.trim();if(n.startsWith("{"))try{if("$Version"in JSON.parse(n))return "v4"}catch{}if(n.startsWith("<?xml")||n.startsWith("<edmx:Edmx")||n.startsWith("<Edmx"))return "v2";if(e){if(e.includes("/odata4/"))return "v4";if(e.includes("/odata/"))return "v2"}return "v2"}var M=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.metadataCache=r??new O;}protocolClient=null;version=null;initPromise=null;etagCache=new vt;metadataCache;metadataUnavailable=false;async ensureInitialized(){if(!this.protocolClient)return this.initPromise?this.initPromise:(this.initPromise=this.initialize(),this.initPromise)}async initialize(){let e=this.metadataCache.get(this.serviceUrl);if(e){this.version=e.version,this.protocolClient=this.createProtocolClient(e.version);return}let n="",r=false;try{let s=await this.httpClient.request({method:"get",url:`${this.serviceUrl}/$metadata`,headers:{Accept:"application/xml, application/json"}});n=typeof s.data=="string"?s.data:JSON.stringify(s.data),this.version=Sn(n,this.serviceUrl),r=!0;}catch{this.version=Sn("",this.serviceUrl),this.metadataUnavailable=true;}if(this.protocolClient=this.createProtocolClient(this.version),r&&this.version==="v2"&&n)try{let s=Rt(n,this.serviceUrl);this.metadataCache.set(this.serviceUrl,s);}catch{this.metadataUnavailable=true;}}async metadataForLiterals(){if(!this.metadataUnavailable)try{return await this.getMetadata()}catch{this.metadataUnavailable=true;return}}createProtocolClient(e){return e==="v4"?new Pt(this.httpClient,this.serviceUrl,this.etagCache):new je(this.httpClient,this.serviceUrl,this.etagCache,()=>this.metadataForLiterals())}async getClient(){if(await this.ensureInitialized(),!this.protocolClient)throw new Error("ODataClient initialization failed: protocol client not created");return this.protocolClient}async read(e,n,r){return (await this.getClient()).read(e,n,r)}async query(e,n){return (await this.getClient()).query(e,n)}async create(e,n){return (await this.getClient()).create(e,n)}async update(e,n,r){return (await this.getClient()).update(e,n,r)}async delete(e,n){return (await this.getClient()).delete(e,n)}async batch(e,n){let r=await this.getClient();return this.version==="v4"?r.batch(e):r.batch(e,n)}async functionImport(e,n,r){let s=await this.getClient();if(s instanceof je)return s.functionImport(e,n,r?.httpMethod);let i=s;return r?.isFunction?i.callFunction(e,n,r?.entitySet,r?.key):i.callAction(e,n,r?.entitySet,r?.key)}async getMetadata(){let e=this.metadataCache.get(this.serviceUrl);if(e)return e;let n=await this.getClient();if(e=this.metadataCache.get(this.serviceUrl),e)return e;let r=await n.getMetadata();return this.metadataCache.set(this.serviceUrl,r),r}getVersion(){return this.version}};function Qr(t,e,n){let r=n??new O,s=new ResourceTemplate("sap://schema/{serviceUrl}/{entitySet}",{list:void 0});t.registerResource("sap-entity-schema",s,{title:"SAP Entity Schema for Query Building",description:"Property names, types, and keys for an entity set \u2014 optimized context for building OData queries. Includes a sample filter in the correct V2/V4 syntax.",mimeType:"application/json"},async(i,o)=>{let a=decodeURIComponent(o.serviceUrl),d=decodeURIComponent(o.entitySet),c=await new M(e,a,r).getMetadata(),u=c.entitySets.find(h=>h.name===d);if(!u)return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({error:`Entity set '${d}' not found in service ${a}`,availableEntitySets:c.entitySets.map(h=>h.name)},null,2)}]};let p=c.entityTypes.find(h=>h.name===u.entityTypeName);if(!p)return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({error:`Entity type '${u.entityTypeName}' not found in metadata`},null,2)}]};let m=new Set(p.keys.map(h=>h.name)),g=p.properties.map(h=>({name:h.name,type:h.type,isKey:m.has(h.name)})),f=ec(p.properties,c.version);return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({entitySet:d,version:c.version,properties:g,sampleFilter:f},null,2)}]}});}function ec(t,e){let n=t.find(r=>r.type==="Edm.String");return n?e==="v2"?`${n.name} eq 'example' or substringof('example',${n.name})`:`${n.name} eq 'example' or contains(${n.name},'example')`:"PropertyName eq 'value'"}function es(t,e){let n=new ResourceTemplate("idoc://received/{docnum}",{list:void 0});t.registerResource("idoc-received",n,{title:"Received IDoc",description:"Received IDoc data from webhook",mimeType:"application/json"},async(r,s)=>{let i=s.docnum,o=e.get(i);return o?{contents:[{uri:r.toString(),mimeType:"application/json",text:JSON.stringify({docnum:o.docnum,idocType:o.idocType,mesType:o.mesType,receivedAt:o.receivedAt.toISOString(),parsed:o.parsed},null,2)}]}:{contents:[{uri:r.toString(),mimeType:"application/json",text:JSON.stringify({error:`IDoc with docnum '${i}' not found`},null,2)}]}});}function ts(t,e,n){let r=n??new O,s=new ResourceTemplate("sap://metadata/{serviceUrl}",{list:void 0});t.registerResource("sap-metadata",s,{title:"SAP Service Metadata",description:"Entity types, properties, keys, and navigation properties for an SAP OData service",mimeType:"application/json"},async(i,o)=>{let a=decodeURIComponent(o.serviceUrl),l=await new M(e,a,r).getMetadata();return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({version:l.version,serviceUrl:l.serviceUrl,entityTypes:l.entityTypes,entitySets:l.entitySets},null,2)}]}});}var bn={"business-partner":{domain:"business-partner",description:"Customer, supplier, and business partner master data",services:[{name:"API_BUSINESS_PARTNER",path:"/sap/opu/odata/sap/API_BUSINESS_PARTNER",version:"v2",keyEntitySets:["A_BusinessPartner","A_BusinessPartnerAddress","A_Customer","A_Supplier"]}],queryTemplates:[{name:"find-customer-by-name",description:"Find customers by name (substring search)",entitySet:"A_Customer",filters:[{field:"CustomerName",operator:"contains",value:""}],select:["Customer","CustomerName","CustomerFullName","Country"],top:20},{name:"find-supplier-by-country",description:"Find suppliers in a specific country",entitySet:"A_Supplier",filters:[{field:"Country",operator:"eq",value:""}],select:["Supplier","SupplierName","Country","Region"],top:50}],suggestedTool:"sap_nl_query",commonQueries:["Find customers by name","List suppliers in a country","Get business partner addresses"]},sales:{domain:"sales",description:"Sales orders, billing documents, and revenue tracking",services:[{name:"API_SALES_ORDER_SRV",path:"/sap/opu/odata/sap/API_SALES_ORDER_SRV",version:"v2",keyEntitySets:["A_SalesOrder","A_SalesOrderItem"]},{name:"API_BILLING_DOCUMENT_SRV",path:"/sap/opu/odata/sap/API_BILLING_DOCUMENT_SRV",version:"v2",keyEntitySets:["A_BillingDocument"]}],queryTemplates:[{name:"recent-sales-orders",description:"Sales orders created this month",entitySet:"A_SalesOrder",filters:[{field:"CreationDate",operator:"ge",value:"$MONTH_START"}],select:["SalesOrder","SalesOrderType","SoldToParty","TotalNetAmount","CreationDate"],orderby:"CreationDate desc",top:50},{name:"overdue-invoices",description:"Billing documents past due date",entitySet:"A_BillingDocument",filters:[{field:"BillingDocumentDate",operator:"lt",value:"$TODAY"}],select:["BillingDocument","BillingDocumentType","SoldToParty","TotalNetAmount","BillingDocumentDate"],orderby:"BillingDocumentDate asc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Recent sales orders","Overdue invoices","Sales by customer"]},finance:{domain:"finance",description:"Journal entries, accounting documents, and financial postings",services:[{name:"API_JOURNALENTRY_SRV",path:"/sap/opu/odata/sap/API_JOURNALENTRY_SRV",version:"v2",keyEntitySets:["A_JournalEntry"]},{name:"API_OPLACCTG_SRV",path:"/sap/opu/odata/sap/API_OPLACCTG_SRV",version:"v2",keyEntitySets:["A_OperationalAcctgDocItem"]}],queryTemplates:[{name:"recent-journal-entries",description:"Journal entries posted this month",entitySet:"A_JournalEntry",filters:[{field:"DocumentDate",operator:"ge",value:"$MONTH_START"}],select:["AccountingDocument","CompanyCode","DocumentDate","PostingDate","DocumentType"],orderby:"DocumentDate desc",top:50},{name:"open-items-by-account",description:"Open accounting items for a G/L account",entitySet:"A_OperationalAcctgDocItem",filters:[{field:"GLAccount",operator:"eq",value:""}],select:["AccountingDocument","CompanyCode","GLAccount","AmountInCompanyCodeCurrency","PostingDate"],top:100}],suggestedTool:"sap_nl_query",commonQueries:["Recent journal entries","Open items by account","Accounting documents this month"]},procurement:{domain:"procurement",description:"Purchase orders, purchase requisitions, and supplier management",services:[{name:"API_PURCHASEORDER_PROCESS_SRV",path:"/sap/opu/odata/sap/API_PURCHASEORDER_PROCESS_SRV",version:"v2",keyEntitySets:["A_PurchaseOrder","A_PurchaseOrderItem"]},{name:"API_PURCHASEREQ_PROCESS_SRV",path:"/sap/opu/odata/sap/API_PURCHASEREQ_PROCESS_SRV",version:"v2",keyEntitySets:["A_PurchaseRequisition"]}],queryTemplates:[{name:"open-purchase-orders",description:"Purchase orders not yet fully delivered",entitySet:"A_PurchaseOrder",filters:[],select:["PurchaseOrder","PurchaseOrderType","Supplier","PurchasingOrganization","CreationDate"],orderby:"CreationDate desc",top:50},{name:"recent-purchase-requisitions",description:"Purchase requisitions created this month",entitySet:"A_PurchaseRequisition",filters:[{field:"CreationDate",operator:"ge",value:"$MONTH_START"}],select:["PurchaseRequisition","PurchaseRequisitionType","CreationDate"],orderby:"CreationDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Open purchase orders","Recent purchase requisitions","Purchase orders by supplier"]},material:{domain:"material",description:"Material master data and material document movements",services:[{name:"API_PRODUCT_SRV",path:"/sap/opu/odata/sap/API_PRODUCT_SRV",version:"v2",keyEntitySets:["A_Product"]},{name:"API_MATERIAL_DOCUMENT_SRV",path:"/sap/opu/odata/sap/API_MATERIAL_DOCUMENT_SRV",version:"v2",keyEntitySets:["A_MaterialDocumentHeader"]}],queryTemplates:[{name:"find-material-by-description",description:"Search materials by description text",entitySet:"A_Product",filters:[{field:"ProductDescription",operator:"contains",value:""}],select:["Product","ProductType","ProductDescription","BaseUnit"],top:20},{name:"recent-material-movements",description:"Material documents created this month",entitySet:"A_MaterialDocumentHeader",filters:[{field:"DocumentDate",operator:"ge",value:"$MONTH_START"}],select:["MaterialDocument","MaterialDocumentYear","DocumentDate","PostingDate"],orderby:"DocumentDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Find material by description","Recent material movements","Material stock levels"]},logistics:{domain:"logistics",description:"Inbound and outbound deliveries, shipping, and transportation",services:[{name:"API_INBOUND_DELIVERY_SRV",path:"/sap/opu/odata/sap/API_INBOUND_DELIVERY_SRV",version:"v2",keyEntitySets:["A_InbDeliveryHeader"]},{name:"API_OUTBOUND_DELIVERY_SRV",path:"/sap/opu/odata/sap/API_OUTBOUND_DELIVERY_SRV",version:"v2",keyEntitySets:["A_OutbDeliveryHeader"]}],queryTemplates:[{name:"pending-inbound-deliveries",description:"Inbound deliveries not yet received",entitySet:"A_InbDeliveryHeader",filters:[],select:["DeliveryDocument","Supplier","DeliveryDate","ReceivingPlant"],orderby:"DeliveryDate asc",top:50},{name:"recent-outbound-deliveries",description:"Outbound deliveries created this month",entitySet:"A_OutbDeliveryHeader",filters:[{field:"CreationDate",operator:"ge",value:"$MONTH_START"}],select:["DeliveryDocument","SoldToParty","CreationDate","DeliveryDate"],orderby:"CreationDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Pending inbound deliveries","Recent outbound deliveries","Deliveries by ship-to party"]},hr:{domain:"hr",description:"Employee master data and organizational assignments",services:[{name:"API_BUSINESS_PARTNER",path:"/sap/opu/odata/sap/API_BUSINESS_PARTNER",version:"v2",keyEntitySets:["A_BusinessPartner"]}],queryTemplates:[{name:"find-employee-by-name",description:"Search employees by name (BP category 2 = Person)",entitySet:"A_BusinessPartner",filters:[{field:"BusinessPartnerCategory",operator:"eq",value:"2"},{field:"BusinessPartnerFullName",operator:"contains",value:""}],select:["BusinessPartner","BusinessPartnerFullName","BusinessPartnerCategory","OrganizationBPName1"],top:20},{name:"employees-by-department",description:"List employees assigned to a department/organization",entitySet:"A_BusinessPartner",filters:[{field:"BusinessPartnerCategory",operator:"eq",value:"2"},{field:"OrganizationBPName1",operator:"contains",value:""}],select:["BusinessPartner","BusinessPartnerFullName","OrganizationBPName1"],top:50}],suggestedTool:"sap_nl_query",commonQueries:["Find employee by name","Employees by department"]},production:{domain:"production",description:"Production orders, planned orders, and manufacturing execution",services:[{name:"API_PLANNED_ORDER",path:"/sap/opu/odata/sap/API_PLANNED_ORDER",version:"v2",keyEntitySets:["PlannedOrder"]},{name:"API_PRODUCTION_ORDER",path:"/sap/opu/odata/sap/API_PRODUCTION_ORDER",version:"v2",keyEntitySets:["A_ProductionOrder"]}],queryTemplates:[{name:"open-production-orders",description:"Production orders not yet completed",entitySet:"A_ProductionOrder",filters:[],select:["ManufacturingOrder","ManufacturingOrderType","Material","ProductionPlant","MfgOrderPlannedStartDate"],orderby:"MfgOrderPlannedStartDate asc",top:50},{name:"planned-orders-this-month",description:"Planned orders created this month",entitySet:"PlannedOrder",filters:[{field:"PlannedOrderCreationDate",operator:"ge",value:"$MONTH_START"}],select:["PlannedOrder","Material","ProductionPlant","PlannedOrderCreationDate"],orderby:"PlannedOrderCreationDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Open production orders","Planned orders this month","Production orders by material"]},quality:{domain:"quality",description:"Quality inspections, inspection lots, and quality management",services:[{name:"API_QUALITYINSPECTION",path:"/sap/opu/odata/sap/API_QUALITYINSPECTION",version:"v2",keyEntitySets:["QualityInspection"]}],queryTemplates:[{name:"open-inspections",description:"Quality inspections not yet completed",entitySet:"QualityInspection",filters:[],select:["InspectionLot","Material","Plant","InspectionLotStartDate"],orderby:"InspectionLotStartDate asc",top:50},{name:"inspections-this-month",description:"Quality inspections started this month",entitySet:"QualityInspection",filters:[{field:"InspectionLotStartDate",operator:"ge",value:"$MONTH_START"}],select:["InspectionLot","Material","Plant","InspectionLotStartDate"],orderby:"InspectionLotStartDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Open quality inspections","Inspections this month"]},"plant-maintenance":{domain:"plant-maintenance",description:"Maintenance notifications, orders, and equipment management",services:[{name:"API_MAINTNOTIFICATION",path:"/sap/opu/odata/sap/API_MAINTNOTIFICATION",version:"v2",keyEntitySets:["MaintenanceNotification"]},{name:"API_MAINTENANCEORDER",path:"/sap/opu/odata/sap/API_MAINTENANCEORDER",version:"v2",keyEntitySets:["MaintenanceOrder"]}],queryTemplates:[{name:"open-maintenance-notifications",description:"Maintenance notifications not yet completed",entitySet:"MaintenanceNotification",filters:[],select:["MaintenanceNotification","NotificationType","FunctionalLocation","ReportedByUser","CreationDate"],orderby:"CreationDate desc",top:50},{name:"maintenance-orders-this-month",description:"Maintenance orders created this month",entitySet:"MaintenanceOrder",filters:[{field:"CreationDate",operator:"ge",value:"$MONTH_START"}],select:["MaintenanceOrder","MaintenanceOrderType","FunctionalLocation","CreationDate"],orderby:"CreationDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Open maintenance notifications","Maintenance orders this month","Notifications by functional location"]},project:{domain:"project",description:"Project systems, WBS elements, and project planning",services:[{name:"API_PROJECT",path:"/sap/opu/odata/sap/API_PROJECT",version:"v2",keyEntitySets:["A_Project","A_ProjectElement"]}],queryTemplates:[{name:"active-projects",description:"List active projects",entitySet:"A_Project",filters:[],select:["Project","ProjectDescription","ResponsiblePerson","PlannedStartDate"],orderby:"PlannedStartDate desc",top:50},{name:"project-elements-by-project",description:"WBS elements for a specific project",entitySet:"A_ProjectElement",filters:[{field:"Project",operator:"eq",value:""}],select:["ProjectElement","ProjectElementDescription","PlannedStartDate","PlannedEndDate"],top:100}],suggestedTool:"sap_nl_query",commonQueries:["Active projects","WBS elements for a project"]},warehouse:{domain:"warehouse",description:"Warehouse resources, tasks, and storage management",services:[{name:"API_WAREHOUSE_RESOURCE",path:"/sap/opu/odata/sap/API_WAREHOUSE_RESOURCE",version:"v2",keyEntitySets:["WarehouseResource","WarehouseTask"]}],queryTemplates:[{name:"open-warehouse-tasks",description:"Warehouse tasks not yet completed",entitySet:"WarehouseTask",filters:[],select:["WarehouseTask","WarehouseTaskType","Warehouse","WarehouseStorageBin"],top:50},{name:"warehouse-resources-by-plant",description:"Warehouse resources for a specific plant",entitySet:"WarehouseResource",filters:[{field:"Plant",operator:"eq",value:""}],select:["WarehouseResource","Warehouse","Plant"],top:50}],suggestedTool:"sap_nl_query",commonQueries:["Open warehouse tasks","Warehouse resources by plant"]},analytics:{domain:"analytics",description:"Financial analytics, expense tracking, and revenue analysis",services:[{name:"API_OPLACCTG_SRV",path:"/sap/opu/odata/sap/API_OPLACCTG_SRV",version:"v2",keyEntitySets:["A_OperationalAcctgDocItem"]}],queryTemplates:[{name:"expenses-this-year",description:"Expense postings since start of current year",entitySet:"A_OperationalAcctgDocItem",filters:[{field:"PostingDate",operator:"ge",value:"$YEAR_START"}],select:["AccountingDocument","CompanyCode","GLAccount","AmountInCompanyCodeCurrency","PostingDate"],orderby:"PostingDate desc",top:100},{name:"monthly-revenue",description:"Revenue postings this month",entitySet:"A_OperationalAcctgDocItem",filters:[{field:"PostingDate",operator:"ge",value:"$MONTH_START"}],select:["AccountingDocument","CompanyCode","GLAccount","AmountInCompanyCodeCurrency","PostingDate"],orderby:"PostingDate desc",top:100}],suggestedTool:"sap_nl_query",commonQueries:["Expenses this year","Monthly revenue","Postings by G/L account"]},"master-data":{domain:"master-data",description:"Cross-domain master data: business partners, materials, cost centers",services:[{name:"API_BUSINESS_PARTNER",path:"/sap/opu/odata/sap/API_BUSINESS_PARTNER",version:"v2",keyEntitySets:["A_BusinessPartner"]},{name:"API_PRODUCT_SRV",path:"/sap/opu/odata/sap/API_PRODUCT_SRV",version:"v2",keyEntitySets:["A_Product"]},{name:"API_COSTCENTER_SRV",path:"/sap/opu/odata/sap/API_COSTCENTER_SRV",version:"v2",keyEntitySets:["A_CostCenter"]}],queryTemplates:[{name:"search-master-data-by-name",description:"Search business partners, materials, or cost centers by name",entitySet:"A_BusinessPartner",filters:[{field:"BusinessPartnerFullName",operator:"contains",value:""}],select:["BusinessPartner","BusinessPartnerFullName","BusinessPartnerCategory"],top:20},{name:"cost-centers-by-company",description:"List cost centers for a company code",entitySet:"A_CostCenter",filters:[{field:"CompanyCode",operator:"eq",value:""}],select:["CostCenter","CostCenterDescription","CompanyCode","ValidityStartDate"],top:100}],suggestedTool:"sap_nl_query",commonQueries:["Search master data by name","Cost centers by company code","Materials by type"]},"cross-application":{domain:"cross-application",description:"Attachments, change documents, and cross-module services",services:[{name:"API_ATTACHMENT_SRV",path:"/sap/opu/odata/sap/API_ATTACHMENT_SRV",version:"v2",keyEntitySets:["A_DocumentInfoRecord"]},{name:"API_BUSINESS_OBJECT_PAGE",path:"/sap/opu/odata/sap/API_BUSINESS_OBJECT_PAGE",version:"v2",keyEntitySets:["A_BusinessObjectPage"]}],queryTemplates:[{name:"attachments-for-object",description:"Find attachments linked to a business object",entitySet:"A_DocumentInfoRecord",filters:[{field:"LinkedSAPObjectKey",operator:"eq",value:""}],select:["DocumentInfoRecordDocNumber","DocumentInfoRecordDocType","LinkedSAPObjectKey"],top:50},{name:"recent-change-documents",description:"Change documents from the last month",entitySet:"A_BusinessObjectPage",filters:[{field:"LastChangeDate",operator:"ge",value:"$MONTH_AGO"}],select:["BusinessObjectType","LastChangeDate","LastChangedByUser"],orderby:"LastChangeDate desc",top:50}],suggestedTool:"sap_nl_query",commonQueries:["Attachments for a business object","Recent change documents"]},unknown:{domain:"unknown",description:"Unknown or unclassified domain. Use service discovery to find available services.",services:[],queryTemplates:[{name:"discover-services",description:"No specific domain matched. Use sap_discover_services to explore available SAP services on this system.",entitySet:"",filters:[]},{name:"browse-domains",description:"Browse available routing domains with sap://routing/{domain} to find the right service for your query.",entitySet:"",filters:[]}],suggestedTool:"sap_discover_services",commonQueries:["Use sap_discover_services to find available services","Check sap://routing/{domain} with a specific domain"]}};var rc={$TODAY:()=>Tn(new Date),$YESTERDAY:()=>{let t=new Date;return t.setDate(t.getDate()-1),Tn(t)},$MONTH_START:()=>{let t=new Date;return `${t.getFullYear()}-${wn(t.getMonth()+1)}-01`},$YEAR_START:()=>`${new Date().getFullYear()}-01-01`,$MONTH_AGO:()=>{let t=new Date;return t.setMonth(t.getMonth()-1),Tn(t)}};function Tn(t){return `${t.getFullYear()}-${wn(t.getMonth()+1)}-${wn(t.getDate())}`}function wn(t){return t.toString().padStart(2,"0")}function sc(t){if(typeof t!="string")return t;let e=rc[t];return e?e():t}function ns(t){return {...t,queryTemplates:t.queryTemplates.map(e=>({...e,filters:e.filters.map(n=>({...n,value:sc(n.value)}))}))}}function rs(t){let e=new ResourceTemplate("sap://routing/{domain}",{list:void 0});t.registerResource("sap-routing",e,{title:"SAP Domain Routing",description:"Service mappings and query templates for a business domain. Returns the relevant SAP services, entity sets, and pre-built query examples for a domain like business-partner, sales, or finance. Use this before building OData queries to know which service and entity set to target.",mimeType:"application/json"},async(n,r)=>{let s=r.domain,i=bn[s];if(!i)return {contents:[{uri:n.toString(),mimeType:"application/json",text:JSON.stringify({error:`Unknown domain '${s}'`,availableDomains:Object.keys(bn)},null,2)}]};let o=ns(i);return {contents:[{uri:n.toString(),mimeType:"application/json",text:JSON.stringify({...o,note:"Service paths are typical defaults. Use sap_discover_services to verify actual paths on your system."},null,2)}]}});}var _t={core:["test-connection","sap_list_services","sap_get_metadata","sap_discover_services","sap_nl_query"],odata:["sap_read","sap_query","sap_create","sap_update","sap_delete","sap_function","sap_batch","sap_media_upload","sap_media_download"],idoc:["sap_idoc_send","sap_idoc_status","sap_idoc_discover","sap_idoc_list_received"],rfc:["sap_rfc_call","sap_rfc_metadata","sap_bapi_call","sap_bapi_commit","sap_rfc_search_functions"]},An=new Set(["sap_create","sap_update","sap_delete","sap_batch","sap_media_upload","sap_idoc_send","sap_rfc_call","sap_bapi_call","sap_bapi_commit"]),vn=["core","odata"],ss=new Map;for(let[t,e]of Object.entries(_t))for(let n of e)ss.set(n,t);var kt=class{handles=new Map;activeTiers;options;constructor(e){this.options=e,this.activeTiers=new Set(e.activeTiers);}registerHandle(e,n){this.handles.set(e,n);}applyInitialVisibility(){for(let[e,n]of this.handles)this.shouldToolBeVisible(e)||n.disable();}enableTier(e){if(this.options.allowedTiers&&!this.options.allowedTiers.includes(e))return {error:`Cannot enable ${e} tier: not permitted by the token policy of this connection (allowed: ${this.options.allowedTiers.join(", ")})`};if(e==="idoc"&&!this.options.hasIdocConfig)return {error:"Cannot enable IDoc tier: no IDoc partner configuration provided (--sndprn, --rcvprn required)"};if(e==="rfc"&&!this.options.hasRfcConfig)return {error:"Cannot enable RFC tier: no RFC connection configuration provided (SAP_RFC_ASHOST + SAP_RFC_SYSNR, or SAP_RFC_MSHOST + SAP_RFC_GROUP required)"};this.activeTiers.add(e);let n=_t[e],r=[];for(let s of n){if(this.options.readOnly&&An.has(s))continue;let i=this.handles.get(s);i&&(i.enable(),r.push(s));}return {enabled:r,activeTiers:this.getActiveTiers()}}getActiveTiers(){return Array.from(this.activeTiers)}getAllowedTiers(){return this.options.allowedTiers?[...this.options.allowedTiers]:void 0}getToolsByTier(e){return _t[e]}isReadOnlyMode(){return this.options.readOnly}shouldToolBeVisible(e){let n=ss.get(e);return n?!(!this.activeTiers.has(n)||this.options.allowedTiers&&!this.options.allowedTiers.includes(n)||n==="idoc"&&!this.options.hasIdocConfig||n==="rfc"&&!this.options.hasRfcConfig||this.options.readOnly&&An.has(e)):true}};function is(t){let e=t.tiers??vn,n=[];for(let r of e)if(!(r==="idoc"&&!t.hasIdocConfig)&&!(r==="rfc"&&!t.hasRfcConfig))for(let s of _t[r])An.has(s)&&n.push(s);return n}var os="/sap/opu/odata/sap",oc=/^[A-Z0-9_/]+$/i,de=class extends Error{guidance;constructor(e){super(`serviceUrl ist weder ein Pfad noch ein technischer Servicename: "${e}"`),this.name="InvalidServiceUrlError",this.guidance=`Erwartet wird ein Pfad wie ${os}/API_BUSINESS_PARTNER oder der technische Name (API_BUSINESS_PARTNER), den sap_discover_services als technicalName liefert.`;}};function L(t){let e=t.trim();if(e.length===0)throw new de(t);if(e.startsWith("/"))return e.replace(/\/+$/,"")||"/";if(e.startsWith("http://")||e.startsWith("https://"))try{return new URL(e).pathname.replace(/\/+$/,"")}catch{throw new de(t)}if(oc.test(e))return `${os}/${e.replace(/^\/+|\/+$/g,"")}`;throw new de(t)}var Ve=class extends Error{guidance;extra;constructor(e,n,r={}){super(e),this.name="ToolInputError",this.guidance=n,this.extra=r;}};function _(t,e){if(t instanceof de)return En({code:"INVALID_INPUT",error:t.message,guidance:t.guidance,...e?.hint?{hint:e.hint}:{}});if(t instanceof Ve)return En({code:"INVALID_INPUT",error:t.message,...t.guidance?{guidance:t.guidance}:{},...t.extra,...e?.hint?{hint:e.hint}:{}});let n=fe(t,{serviceUrl:e?.serviceUrl});return En({code:n.code,error:n.message,...n.httpStatus!==void 0?{httpStatus:n.httpStatus}:{},...n.sapCode?{sapCode:n.sapCode}:{},...n.guidance?{guidance:n.guidance}:{},...e?.hint?{hint:e.hint}:{}})}function En(t){let e=pe()?.correlationId;return {content:[{type:"text",text:JSON.stringify({...t,...e?{correlationId:e}:{}},null,2)}],isError:true}}function as(t,e,n){let r=n??new O,s=new Map;function i(a){let d=L(a),l=s.get(d);return l||(l=new M(e,d,r),s.set(d,l)),l}function o(a,d){return _(a,{serviceUrl:d})}t.registerTool("sap_batch",{title:"Execute SAP Batch Request",description:"Execute multiple OData operations in a single batch request. Supports V2 (multipart/mixed) and V4 (JSON) batch formats automatically. Write operations sharing a changesetId run as one transaction (all succeed or all fail); in V2 a write without changesetId gets a changeset of its own. Returns per-operation status in the order of the operations -- individual failures are always reported, never hidden behind the batch HTTP 200.",inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),operations:z$2.array(z$2.object({method:z$2.enum(["get","post","patch","delete"]).describe("HTTP method"),entitySet:z$2.string().describe("Entity set name"),key:z$2.record(z$2.string(),z$2.unknown()).optional().describe("Entity key for read/update/delete"),data:z$2.record(z$2.string(),z$2.unknown()).optional().describe("Request body for create/update"),changesetId:z$2.string().optional().describe("Changeset group ID for transactional grouping")})).describe("Array of operations to execute in batch")}),annotations:{readOnlyHint:false,destructiveHint:true,idempotentHint:false,openWorldHint:true}},async({serviceUrl:a,operations:d})=>{try{let l=i(a),c=d,u=await l.batch(c);return {content:[{type:"text",text:JSON.stringify(u,null,2)}]}}catch(l){return o(l)}});}async function cs(t,e,n=5){if(t.length===0)return [];let r=Math.max(1,Math.min(Math.floor(n)||1,t.length)),s=new Array(t.length),i=0;async function o(){for(;;){let a=i++;if(a>=t.length)return;s[a]=await e(t[a],a);}}return await Promise.all(Array.from({length:r},()=>o())),s}var ac=[".integrationsuite.","integrationsuite-trial.",".it-cpi.",".hci.",".prod.apimanagement.","-api.s4hana.ondemand.com"];function ds(t){let e=t.toLowerCase();return ac.some(n=>e.includes(n))}var K=class extends Error{constructor(n,r,s,i){super(n);this.httpStatus=r;this.sapGuidance=s;this.isBtpDetected=i;this.name="CatalogError";}},cc=new Set(["ECONNREFUSED","ETIMEDOUT","ENOTFOUND"]);function dc(t,e=false){switch(t){case 403:return "Catalog access denied. Check transaction PFCG for role assignment and SU53 for authorization trace. Required authorization object: S_SERVICE with SRV_NAME = IWFND_CATALOGSERVICE.";case 404:return e?"V4 catalog not available. Publish service group via /IWFND/V4_ADMIN.":"Catalog service not found. Activate it via transaction /IWFND/MAINT_SERVICE.";case 500:return "SAP system error. Check transaction ST22 for ABAP dumps and SM21 for system log.";default:return "Could not connect to SAP system. Verify the hostname, port, and network connectivity."}}function lc(t){switch(t){case "ECONNREFUSED":return "Connection refused. Verify that the SAP system is running and the port is correct.";case "ETIMEDOUT":return "Connection timeout. Check network connectivity and firewall rules.";case "ENOTFOUND":return "Hostname not found. Verify the SAP system hostname in your configuration.";default:return "Could not connect to SAP system. Verify the hostname, port, and network connectivity."}}function ls(t){return typeof t=="object"&&t!==null&&"response"in t&&typeof t.response=="object"&&t.response!==null&&typeof t.response.status=="number"}function uc(t){return typeof t=="object"&&t!==null&&"code"in t&&typeof t.code=="string"&&cc.has(t.code)}function We(t,e,n=false){let r=ds(e);if(ls(t)){let i=t.response.status,o=dc(i,n),a="message"in t&&typeof t.message=="string"?t.message:`HTTP ${i}`;return new K(a,i,o,r)}if(uc(t)){let i=lc(t.code),o="message"in t&&typeof t.message=="string"?t.message:t.code;return new K(o,void 0,i,r)}let s=t instanceof Error?t.message:String(t);return new K(s,void 0,void 0,r)}function us(t){return ls(t)&&t.response.status===404?"V4 catalog not available. Publish service group via /IWFND/V4_ADMIN.":t instanceof Error?t.message:String(t)}var pc=["/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/iwfnd/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/IWFND/CATALOGSERVICE/ServiceCollection"];function mc(t){if(typeof t=="object"&&t!==null&&"response"in t)return t.response?.status}async function ps(t,e,n){let r;for(let s of pc)try{return await t.request({method:"get",url:s,params:{$format:"json",$top:"1"},expectedStatuses:[404]}),n?.onMatch?.(s),s}catch(i){if(i instanceof B)throw i;if(mc(i)===404){r=i;continue}throw We(i,e,false)}throw We(r??new Error("All V2 catalog paths returned 404"),e,false)}function ms(t){try{return new URL(t).pathname}catch{return t}}function gc(t){let e=t.toLowerCase().replace(/'/g,"''");return [`substringof('${e}',tolower(TechnicalServiceName))`,`substringof('${e}',tolower(Title))`,`substringof('${e}',tolower(Description))`].join(" or ")}var Ot=class t{constructor(e,n,r={}){this.httpClient=e;this.cache=n;this.options=r;}static DEFAULT_V4_URL="/sap/opu/odata4/iwfnd/config/default/iwfnd/catalog/0002/ServiceGroups";resolvedV2Path;serverSideFilterUnsupported=false;async discoverServices(e){let n=this.cache.get();if(n)return {services:n,warnings:[]};if(e&&!this.serverSideFilterUnsupported){let l=await this.tryFilteredFetch(e);if(l)return l}let r=[],[s,i]=await cs(["v4","v2"],async l=>l==="v4"?await this.fetchV4Catalog().then(c=>({ok:true,services:c}),c=>({ok:false,error:c})):await this.fetchV2Catalog().then(c=>({ok:true,services:c}),c=>({ok:false,error:c})),this.options.concurrency??5),o=[];if(s?.ok)o=s.services;else if(s){if(s.error instanceof B)throw s.error;r.push(`V4 catalog fetch failed: ${us(s.error)}`);}if(!i?.ok){let l=i?.error;throw l instanceof K?l:We(l,this.options.baseUrl??"",false)}let a=i.services,d=this.mergeAndDeduplicate(o,a);return this.cache.set(d),{services:d,warnings:r}}async tryFilteredFetch(e){try{return {services:await this.fetchV2Catalog(gc(e)),warnings:[],filtered:!0}}catch(n){if(n instanceof B)throw n;let r=n?.response?.status;(r===400||r===501)&&(this.serverSideFilterUnsupported=true);return}}async fetchV2Catalog(e){let n=this.options.catalogV2Url??this.resolvedV2Path;return n||(n=await ps(this.httpClient,this.options.baseUrl??"",{onMatch:this.options.onDiscoveryMatch}),this.resolvedV2Path=n),((await this.httpClient.request({method:"get",url:n,params:{$format:"json",...e?{$filter:e}:{}},...e?{expectedStatuses:[400,501]}:{}})).data?.d?.results??[]).map(o=>({technicalName:o.TechnicalServiceName,title:o.Title,serviceUrl:ms(o.ServiceUrl),description:o.Description,version:"v2",serviceVersion:o.TechnicalServiceVersion}))}async fetchV4Catalog(){let e=this.options.catalogV4Url??t.DEFAULT_V4_URL,s=(await this.httpClient.request({method:"get",url:e,params:{$expand:"DefaultSystem($expand=Services)"},headers:{Accept:"application/json"},expectedStatuses:[404]})).data?.value??[],i=[];for(let o of s){let a=o.DefaultSystem?.Services??[];for(let d of a)i.push({technicalName:d.ServiceId??d.ServiceName??"",title:d.ServiceTitle??d.Title??"",serviceUrl:ms(d.ServiceUrl),version:"v4",serviceVersion:d.ServiceVersion});}return i}mergeAndDeduplicate(e,n){let r=new Map;for(let s of e)r.set(s.technicalName,s);for(let s of n)r.has(s.technicalName)||r.set(s.technicalName,s);return [...r.values()]}};var hc=[[/BUSINESS_?PARTNER|BUPA/i,"business-partner"],[/SALES_ORDER|BILLING|CREDIT_MEMO|DEBIT_MEMO|SALES_CONTRACT|SALES_QUOT/i,"sales"],[/PURCHASEORDER|PURCHASE_ORDER|PURCHASE_REQ|SUPPLIER|PURG/i,"procurement"],[/JOURNALENTRY|JOURNAL_ENTRY|OPLACCTG|COST_CENTER|PROFIT_CENTER|GL_ACCOUNT|FINANCIAL/i,"finance"],[/MATERIAL_DOC|MATERIAL_STOCK|MATERIAL_SRV|PRODUCT\b/i,"material"],[/OUTBOUND_DELIV|INBOUND_DELIV|SHIPPING|TRANSPORTATION/i,"logistics"],[/PRODUCTION_ORDER|PLANNED_ORDER|ROUTING|BOM\b/i,"production"],[/EMPLOYEE|WORKFORCE|PERSONNEL|PAYROLL/i,"hr"],[/INSPECTION|QUALITY/i,"quality"],[/MAINTENANCE|EQUIPMENT|FUNCTIONAL_LOC/i,"plant-maintenance"],[/PROJECT|WBS\b/i,"project"],[/WAREHOUSE|STORAGE_BIN/i,"warehouse"],[/ANALYTIC|CUBE/i,"analytics"]],yc=[[/^[ZY]?MM[_]/i,"procurement"],[/^[ZY]?SD[_]/i,"sales"],[/^[ZY]?FI[_]|^[ZY]?CO[_]/i,"finance"],[/^[ZY]?PP[_]/i,"production"],[/^[ZY]?HR[_]|^[ZY]?PA[_]|^[ZY]?PT[_]/i,"hr"],[/^[ZY]?WM[_]/i,"warehouse"],[/^[ZY]?PM[_]/i,"plant-maintenance"],[/^[ZY]?LE[_]/i,"logistics"]];function Sc(t){for(let[e,n]of hc)if(e.test(t.technicalName))return {...t,category:n,categoryConfidence:"high"};for(let[e,n]of yc)if(e.test(t.technicalName))return {...t,category:n,categoryConfidence:"high"};return {...t,category:"unknown",categoryConfidence:"low"}}function fs(t){return t.map(Sc)}function Ce(t,e){let n=Number.isFinite(t)&&t>0?Math.floor(t):50,r=Number.isFinite(e)&&e>0?Math.floor(e):0;return {top:Math.min(n,1e3),skip:r}}function Pe(t){let{returned:e,totalCount:n,paging:r,hasMoreHint:s}=t,i=s??(n!==void 0?r.skip+e<n:e>=r.top),o={returned:e,hasMore:i,top:r.top,skip:r.skip};return n!==void 0&&(o.totalCount=n),i&&(o.nextSkip=r.skip+e),o}function gs(t,e,n,r){let s=r??new X,i=new Ot(e,s,n);t.registerTool("sap_discover_services",{title:"Discover SAP OData Services",description:`Discover all available OData services on the connected SAP system. Returns categorized services with search, category filtering, and pagination. Use this as the first step to explore what APIs are available before using sap_list_services or sap_get_metadata.
|
|
212
213
|
|
|
213
214
|
**When to use:**
|
|
214
215
|
- You need to find which SAP services are available on this system
|
|
@@ -217,7 +218,7 @@ ${d}`);}s.push(`--${r}--`);let i=s.join(`\r
|
|
|
217
218
|
|
|
218
219
|
**When NOT to use:**
|
|
219
220
|
- You already know the service URL (proceed to sap_query or sap_nl_query)
|
|
220
|
-
- You need entity-level metadata (use sap_get_metadata after discovering the service)`,inputSchema:z$2.object({search:z$2.string().optional().describe("Free-text search across service name, title, and description (case-insensitive)"),category:z$2.enum(["business-partner","sales","finance","procurement","hr","logistics","production","quality","plant-maintenance","project","material","warehouse","analytics","master-data","cross-application","unknown"]).optional().describe("Filter by business domain category"),limit:z$2.number().optional().default(50).describe(`Maximum number of services to return (default ${50}, hard cap ${1e3})`),offset:z$2.number().optional().default(0).describe("Number of services to skip for pagination (default 0)")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({search:o,category:a,limit:
|
|
221
|
+
- You need entity-level metadata (use sap_get_metadata after discovering the service)`,inputSchema:z$2.object({search:z$2.string().optional().describe("Free-text search across service name, title, and description (case-insensitive)"),category:z$2.enum(["business-partner","sales","finance","procurement","hr","logistics","production","quality","plant-maintenance","project","material","warehouse","analytics","master-data","cross-application","unknown"]).optional().describe("Filter by business domain category"),limit:z$2.number().optional().default(50).describe(`Maximum number of services to return (default ${50}, hard cap ${1e3})`),offset:z$2.number().optional().default(0).describe("Number of services to skip for pagination (default 0)")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({search:o,category:a,limit:d,offset:l})=>{try{let c=await i.discoverServices(o),u=fs(c.services);if(a&&(u=u.filter(S=>S.category===a)),o&&!c.filtered){let S=o.toLowerCase();u=u.filter(y=>y.technicalName.toLowerCase().includes(S)||y.title.toLowerCase().includes(S)||(y.description?.toLowerCase().includes(S)??!1));}let p=u.length,m={};for(let S of u)m[S.category]=(m[S.category]??0)+1;let g=Ce(d,l),f=u.slice(g.skip,g.skip+g.top),h=Pe({returned:f.length,totalCount:p,paging:g}),b={services:f.map(S=>({technicalName:S.technicalName,title:S.title,category:S.category,serviceUrl:S.serviceUrl,version:S.version})),...h,summary:{total:p,returned:f.length,offset:g.skip,byCategory:m},...c.warnings.length>0?{warnings:c.warnings}:{},...o?{filterApplied:c.filtered?"server":"client"}:{}};return {content:[{type:"text",text:JSON.stringify(b,null,2)}]}}catch(c){if(c instanceof K){let u={error:c.message};return c.sapGuidance&&(u.guidance=c.sapGuidance),c.isBtpDetected&&(u.btpWarning="BTP Integration Suite detected. Catalog discovery requires direct S/4HANA access. The Integration Suite proxies individual OData services but does not expose the system catalog. Use sap_list_services with a known service URL instead."),{content:[{type:"text",text:JSON.stringify(u,null,2)}],isError:true}}return _(c)}});}function hs(t,e,n){let r=new O;t.registerTool("sap_list_services",{title:"List SAP Entity Sets",description:`Discover available entity sets in a specific SAP OData service. Requires a full OData service path \u2014 NOT a base URL. Common SAP OData services: /sap/opu/odata/sap/API_BUSINESS_PARTNER, /sap/opu/odata/sap/API_MATERIAL_DOCUMENT_SRV, /sap/opu/odata/sap/API_SALES_ORDER_SRV. Use test-connection first to verify SAP connectivity.
|
|
221
222
|
|
|
222
223
|
**When to use:**
|
|
223
224
|
- You need the raw list of OData services at a base URL
|
|
@@ -225,7 +226,7 @@ ${d}`);}s.push(`--${r}--`);let i=s.join(`\r
|
|
|
225
226
|
|
|
226
227
|
**When NOT to use:**
|
|
227
228
|
- Use sap_discover_services instead for categorized, searchable results
|
|
228
|
-
- You already know the service URL`,inputSchema:z$2.object({serviceUrl:z$2.string().describe('Full OData service path starting with /sap/opu/odata/sap/. Example: /sap/opu/odata/sap/API_BUSINESS_PARTNER. Do NOT pass "/" or a base URL.'),top:z$2.number().optional().describe(`Maximum number of entity sets to return (default ${50}, hard cap ${1e3}).`),skip:z$2.number().optional().describe("Number of entity sets to skip for paging")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:s,top:i,skip:o})=>{try{let a=L(s),l=(await e.request({method:"get",url:a,headers:{Accept:"application/json"},params:{$format:"json"}})).data,
|
|
229
|
+
- You already know the service URL`,inputSchema:z$2.object({serviceUrl:z$2.string().describe('Full OData service path starting with /sap/opu/odata/sap/. Example: /sap/opu/odata/sap/API_BUSINESS_PARTNER. Do NOT pass "/" or a base URL.'),top:z$2.number().optional().describe(`Maximum number of entity sets to return (default ${50}, hard cap ${1e3}).`),skip:z$2.number().optional().describe("Number of entity sets to skip for paging")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:s,top:i,skip:o})=>{try{let a=L(s),l=(await e.request({method:"get",url:a,headers:{Accept:"application/json"},params:{$format:"json"}})).data,c=[],u=l.d;u&&"EntitySets"in u&&Array.isArray(u.EntitySets)?c=u.EntitySets:"value"in l&&Array.isArray(l.value)&&(c=l.value.map(h=>h.name)),c=c.filter(h=>V(h,n));let p=Ce(i,o),m=c.length,g=c.slice(p.skip,p.skip+p.top),f=Pe({returned:g.length,totalCount:m,paging:p});return {content:[{type:"text",text:JSON.stringify({serviceUrl:a,entitySets:g,count:m,...f},null,2)}]}}catch(a){return _(a,{serviceUrl:s})}}),t.registerTool("sap_get_metadata",{title:"Get SAP Service Metadata",description:`Inspect entity types, properties, keys, and navigation properties for an SAP OData service. Second step: after listing services, use this to understand the data model before executing operations.
|
|
229
230
|
|
|
230
231
|
Each entity set carries what SAP allows on it \u2014 \`creatable\`, \`updatable\`, \`deletable\` \u2014 plus a \`capabilities\` summary listing the entity sets you can create in. Use it to answer questions like 'which service can create sales orders?' without reading raw $metadata XML.
|
|
231
232
|
|
|
@@ -236,22 +237,24 @@ Each entity set carries what SAP allows on it \u2014 \`creatable\`, \`updatable\
|
|
|
236
237
|
- Use 'search' to filter large services by name pattern
|
|
237
238
|
|
|
238
239
|
**When NOT to use:**
|
|
239
|
-
- You don't know which service to query (use sap_discover_services first)`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entityType:z$2.string().optional().describe("Filter to a specific entity type name for full property details"),search:z$2.string().optional().describe("Filter entity types/sets by name pattern (case-insensitive). Use this for large services to narrow results.")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:s,entityType:i,search:o})=>{try{let
|
|
240
|
+
- You don't know which service to query (use sap_discover_services first)`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entityType:z$2.string().optional().describe("Filter to a specific entity type name for full property details"),search:z$2.string().optional().describe("Filter entity types/sets by name pattern (case-insensitive). Use this for large services to narrow results.")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:s,entityType:i,search:o})=>{try{let d=await new M(e,L(s),r).getMetadata(),l;if(i){let c=d.entityTypes.find(u=>u.name===i);if(!c){let u=i.toLowerCase();c=d.entityTypes.find(p=>p.name.toLowerCase()===u);}if(!c){let u=i.toLowerCase(),p=d.entityTypes.filter(m=>m.name.toLowerCase().includes(u)).map(m=>m.name).slice(0,20);return _(new Ve(`Entity type '${i}' existiert in diesem Service nicht.`,"Mit dem Parameter 'search' nach passenden Typen suchen, oder entityType weglassen, um alle Entity Sets zu sehen.",p.length>0?{closestMatches:p}:{}),{serviceUrl:s})}l={version:d.version,entityType:c};}else {let c=d.entitySets.filter(g=>V(g.name,n)),u=d.entityTypes;if(o){let g=o.toLowerCase();c=c.filter(f=>f.name.toLowerCase().includes(g)),u=u.filter(f=>f.name.toLowerCase().includes(g));}let p={creatable:c.filter(g=>g.creatable).map(g=>g.name),updatable:c.filter(g=>g.updatable).length,deletable:c.filter(g=>g.deletable).length};u.length>50&&!o?l={version:d.version,entitySets:c,capabilities:p,entityTypeCount:u.length,hint:`Service has ${u.length} entity types. Use the 'search' parameter (e.g. search='BusinessPartner') or 'entityType' parameter to get details for specific types. Entity sets listed above are queryable.`}:l={version:d.version,entityTypes:u.map(g=>({name:g.name,keys:g.keys.map(f=>f.name),propertyCount:g.properties.length,navigationProperties:g.navigationProperties.map(f=>f.name)})),entitySets:c,capabilities:p};}return {content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(a){return _(a,{serviceUrl:s})}});}function Ss(t,e){if(!t)return "unknown";let n=ys(e).toLowerCase(),r=t.functionImports?.find(i=>i.name.toLowerCase()===n);if(r)return r.httpMethod==="GET"?"read":"write";let s=t.actions?.find(i=>ys(i.name).toLowerCase()===n);return s?.kind==="Function"?"read":s?.kind==="Action"?"write":"unknown"}function ys(t){let e=t.lastIndexOf(".");return e===-1?t:t.slice(e+1)}function bs(t,e,n,r=false){let s=n??new O,i=new Map;function o(l){let c=L(l),u=i.get(c);return u||(u=new M(e,c,s),i.set(c,u)),u}function a(l,c){return _(l,{serviceUrl:c})}async function d(l,c){if(!r)return null;let u;try{u=await o(l).getMetadata();}catch{}let p=Ss(u,c);if(p==="read")return null;let m=p==="write"?`"${c}" schreibt laut den Metadaten des Dienstes (POST).`:`"${c}" ist in den Metadaten des Dienstes nicht als lesend nachweisbar.`;return _(new Error(`${m} Dieser Zugang ist lesend. Lesende Funktionsimporte (V2 HttpMethod=GET, V4 Function) sind erlaubt; f\xFCr schreibende braucht es einen Zugang ohne Read-only-Policy bzw. einen Server mit --allow-write.`),{serviceUrl:l})}t.registerTool("sap_function",{title:"Call SAP Function Import or Action",description:`Invoke an OData V2 Function Import or V4 Action/Function. For V2: calls function imports with parameters; values are typed from $metadata (Edm.Guid \u2192 guid'\u2026', Edm.Boolean \u2192 true/false), so pass plain values. For V4: supports bound actions (on a specific entity) and unbound actions/functions. Set isFunction=true for V4 Functions (GET), leave false for V4 Actions (POST). For bound operations, provide entitySet and key.
|
|
241
|
+
|
|
242
|
+
Fiori draft services (V2, e.g. purchase requisitions): activate a draft with the <EntitySet>Activation function import and start editing an active document with <EntitySet>Edit, passing the full key as parameters, e.g. { PurchaseRequisition: "", DraftUUID: "<uuid>", IsActiveEntity: false }. An active document has DraftUUID 00000000-0000-0000-0000-000000000000 and IsActiveEntity true. Draft items have a DraftUUID of their own: read their keys via the draft header's navigation (e.g. to_PurchaseReqnItem) before sap_update.`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),functionName:z$2.string().describe("Function import name (V2) or action/function name (V4). For V4 bound operations, include the full namespace e.g. com.sap.namespace.ActionName"),parameters:z$2.record(z$2.string(),z$2.unknown()).optional().describe("Function/action parameters as key-value pairs"),httpMethod:z$2.enum(["GET","POST"]).optional().describe("HTTP method override. V2: from metadata or default POST. V4: Actions=POST, Functions=GET"),entitySet:z$2.string().optional().describe("Entity set for V4 bound actions/functions"),key:z$2.record(z$2.string(),z$2.unknown()).optional().describe('Entity key for V4 bound actions/functions, e.g. { BusinessPartner: "1000001" }'),isFunction:z$2.boolean().optional().describe("Set true for V4 Functions (GET). Default false = Action (POST). Ignored for V2.")}),annotations:{readOnlyHint:false,destructiveHint:false,idempotentHint:false,openWorldHint:true}},async({serviceUrl:l,functionName:c,parameters:u,httpMethod:p,entitySet:m,key:g,isFunction:f})=>{try{let h=await d(l,c);if(h)return h;let S=await o(l).functionImport(c,u,{httpMethod:p,entitySet:m,key:g,isFunction:f??!1});return {content:[{type:"text",text:JSON.stringify(S,null,2)}]}}catch(h){return a(h)}});}function Ts(t,e,n){t.registerTool("test-connection",{title:"Test SAP Connection",description:`Diagnose the connection to the configured SAP system stage by stage: DNS \u2192 TLS (including certificate chain) \u2192 authentication \u2192 client (Mandant) \u2192 service catalog. Reports the FIRST failing stage with a concrete next step, so a 401 no longer leaves you guessing between certificate, user and client.
|
|
240
243
|
|
|
241
244
|
**When to use:**
|
|
242
245
|
- Any SAP tool fails and you need to know which layer is at fault
|
|
243
246
|
- After changing SAP_BASE_URL, credentials, SAP_CLIENT or the CA bundle
|
|
244
247
|
|
|
245
248
|
**When NOT to use:**
|
|
246
|
-
- You already know the connection works (proceed to sap_discover_services)`,inputSchema:z$2.object({}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async()=>{try{let r=await
|
|
249
|
+
- You already know the connection works (proceed to sap_discover_services)`,inputSchema:z$2.object({}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async()=>{try{let r=await rt(e,{baseUrl:n?.baseUrl??"",sapClient:n?.sapClient,tlsVerify:n?.tlsVerify});return {content:[{type:"text",text:JSON.stringify({...r,...r.status==="connected"?{hint:"N\xE4chster Schritt: sap_discover_services zeigt die verf\xFCgbaren OData-Services dieses Systems."}:{}},null,2)}],...r.status==="failed"?{isError:!0}:{}}}catch(r){return _(r,{hint:"Die Verbindungsdiagnose konnte nicht ausgef\xFChrt werden. Das ist ein Fehler des Servers, kein Befund \xFCber das SAP-System."})}});}function Tc(t){return {name:t.FieldName,dataType:t.DataType,length:t.Length,decimals:t.Decimals??0,required:t.Required}}function wc(t){return {name:t.SegmentName,parentSegment:t.ParentSegment??"",minOccurrence:t.MinOccurrence,maxOccurrence:t.MaxOccurrence,fields:t.Fields?.results?.map(Tc)??[]}}function Ac(t){return {idocType:t.IdocType,mesType:t.MesType,segments:t.Segments?.results?.map(wc)??[]}}async function Rn(t,e,n){try{if(n){let o=(await t.request({method:"get",url:`${e}/IdocTypes('${n}')?$expand=Segments($expand=Fields)`})).data;return Ac(o.d)}return (await t.request({method:"get",url:`${e}/IdocTypes`})).data.d.results.map(i=>({idocType:i.IdocType,mesType:i.MesType}))}catch(r){let s=r instanceof Error?r.message:String(r);throw new Error(`Failed to fetch IDoc metadata: ${s}`)}}var ws={50:{text:"IDoc added",category:"pending"},51:{text:"Application document not posted",category:"error"},52:{text:"Application document not fully posted",category:"error"},53:{text:"Application document posted",category:"success"},56:{text:"IDoc with errors added",category:"error"},62:{text:"IDoc passed to application",category:"pending"},64:{text:"IDoc ready to be transferred to application",category:"pending"},66:{text:"IDoc is waiting for predecessor IDoc (serialization)",category:"pending"},68:{text:"Error - no further processing",category:"error"},69:{text:"IDoc was edited",category:"pending"},73:{text:"IDoc passed to BTP Integration Suite",category:"pending"},1:{text:"IDoc generated",category:"pending"},3:{text:"IDoc data passed to port OK",category:"success"},5:{text:"IDoc translation error",category:"error"},12:{text:"Dispatch OK",category:"success"},18:{text:"Triggering EDI subsystem OK",category:"success"},30:{text:"IDoc ready to be dispatched",category:"pending"}};async function As(t,e,n){try{let i=(await t.request({method:"get",url:`${e}/IdocStatus('${n}')`})).data.d,o=i.Status,a=ws[o];return {docnum:i.Docnum,status:o,statusText:a?.text??"Unknown status",category:a?.category??"pending",timestamp:i.Timestamp}}catch(r){let s=r instanceof Error?r.message:String(r);throw new Error(`Failed to query IDoc status: ${s}`)}}var Cn={name:z$2.string().describe("Segment name, e.g. E1MARAM"),fields:z$2.record(z$2.string(),z$2.string()).describe("Segment field values")},vc=z$2.object({...Cn}),Ec=z$2.object({...Cn,children:z$2.array(vc).optional().describe("Nested child segments (level 3)")}),Rc=z$2.object({...Cn,children:z$2.array(Ec).optional().describe("Nested child segments")});function vs(t,e,n,r){function s(i,o){return _(i,{serviceUrl:o})}t.registerTool("sap_idoc_send",{title:"Send IDoc to SAP",description:"Send an IDoc to SAP via HTTP/XML. Auto-constructs EDI_DC40 control record. Validates against segment metadata before sending -- blocks on validation failure.",inputSchema:z$2.object({idocType:z$2.string().describe("IDoc type name, e.g. MATMAS05, ORDERS05"),segments:z$2.array(Rc).describe("IDoc data segments"),metadataServiceUrl:z$2.string().optional().describe("OData service URL for IDoc metadata validation. If provided, validates before sending."),mesType:z$2.string().optional().describe("Override auto-derived message type (MESTYP)"),sndpor:z$2.string().optional().describe("Override sender port"),sndprn:z$2.string().optional().describe("Override sender partner number"),rcvpor:z$2.string().optional().describe("Override receiver port"),rcvprn:z$2.string().optional().describe("Override receiver partner number")}),annotations:{readOnlyHint:false,destructiveHint:false,idempotentHint:false,openWorldHint:true}},async({idocType:i,segments:o,metadataServiceUrl:a,mesType:d,sndpor:l,sndprn:c,rcvpor:u,rcvprn:p})=>{try{let m;if(a){let f=await Rn(e,a,i);Array.isArray(f)||(m=f);}let g=await n.send({idocType:i,segments:o,metadata:m,partnerOverrides:{mesType:d,sndpor:l,sndprn:c,rcvpor:u,rcvprn:p}});return {content:[{type:"text",text:JSON.stringify(g,null,2)}]}}catch(m){return s(m)}}),t.registerTool("sap_idoc_status",{title:"Query IDoc Status",description:"Query IDoc processing status in SAP. Returns status code with human-readable description (50=received, 53=processed, 51=error).",inputSchema:z$2.object({docnum:z$2.string().describe("IDoc document number"),statusServiceUrl:z$2.string().describe("OData service URL for IDoc status query")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({docnum:i,statusServiceUrl:o})=>{try{let a=await As(e,o,i);return {content:[{type:"text",text:JSON.stringify(a,null,2)}]}}catch(a){return s(a)}}),t.registerTool("sap_idoc_discover",{title:"Discover IDoc Types",description:"Discover available IDoc types and segment structures from SAP. Returns IDoc type definitions with segment hierarchy and field metadata.",inputSchema:z$2.object({metadataServiceUrl:z$2.string().describe("OData service URL for IDoc metadata"),idocType:z$2.string().optional().describe("Specific IDoc type to get details for. Omit to list all types.")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({metadataServiceUrl:i,idocType:o})=>{try{let a=await Rn(e,i,o);return {content:[{type:"text",text:JSON.stringify(a,null,2)}]}}catch(a){return s(a)}}),t.registerTool("sap_idoc_list_received",{title:"List Received IDocs",description:"List IDocs received via webhook. Returns recently received IDocs from the in-memory store.",inputSchema:z$2.object({}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async()=>{try{let i=r.list().map(o=>({docnum:o.docnum,idocType:o.idocType,mesType:o.mesType,receivedAt:o.receivedAt.toISOString()}));return {content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(i){return s(i)}});}var Cc=/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/,Pc=/[\u0000-\u001f\u007f]/;function Pn(t){let e={};if(!t)return e;for(let[n,r]of Object.entries(t)){if(r==null)continue;let s=typeof r=="string"?r.trim():String(r);if(s.length!==0){if(!Cc.test(n))throw new Error(`"${n}" ist kein zul\xE4ssiger Kopfzeilenname. Erlaubt sind Buchstaben, Ziffern und -_.~ ohne Leerzeichen.`);if(Pc.test(s))throw new Error(`Der Wert der Kopfzeile "${n}" enth\xE4lt einen Zeilenumbruch oder ein Steuerzeichen. Das w\xE4re eine Kopfzeilen-Einschleusung und wird nicht gesendet.`);e[n]=s;}}return e}var _c=[[/ä/g,"ae"],[/ö/g,"oe"],[/ü/g,"ue"],[/Ä/g,"Ae"],[/Ö/g,"Oe"],[/Ü/g,"Ue"],[/ß/g,"ss"]],kc=[[/[\u2010-\u2015]/g,"-"],[/[\u2018\u2019\u201a\u201b]/g,"'"],[/[\u201c\u201d\u201e\u201f]/g,'"'],[/…/g,"..."],[/[\u00a0\u2007\u202f\u2009]/g," "]],Oc=/[\u0300-\u036f]/g,Ic=/[^\u0020-\u007e]/g;function Es(t){let e=t.trim();if(e.length===0)throw new Error("Der Dateiname ist leer. SAP braucht ihn als Slug-Kopfzeile.");let n=e;for(let[r,s]of _c)n=n.replace(r,s);for(let[r,s]of kc)n=n.replace(r,s);if(n=n.normalize("NFKD").replace(Oc,""),n=n.replace(Ic,"").trim(),n.length===0)throw new Error(`Aus dem Dateinamen "${e}" bleibt nichts \xFCbrig, was durch eine HTTP-Kopfzeile passt. Gib einen Namen mit lateinischen Zeichen an.`);return {slug:n,changed:n!==e}}async function Rs(t,e,n){if(n.content.byteLength===0)throw new Error("Der Rumpf ist leer. Ein Anhang ohne Bytes legt in SAP ein leeres Dokument an, das sich nicht mehr \xE4ndern l\xE4sst (der Dienst kennt kein PATCH).");let{slug:r,changed:s}=Es(n.fileName),i=n.contentType.trim();if(i.length===0)throw new Error("Es fehlt der Content-Type der Datei, z. B. application/pdf.");let o={...Pn(n.headers),...Pn({Slug:r,"Content-Type":i,Accept:"application/json"})},a=await t.request({method:"post",url:`${e}/${n.entitySet}`,data:n.content,headers:o}),d=ce(a.data),l=d.results.length>0?q(d.results[0]):{},c={status:a.status,slug:r,bytesSent:n.content.byteLength,entity:l};s&&(c.slugAdjustedFrom=n.fileName);let u=a.headers?.location;return u&&(c.location=u),c}async function Cs(t,e,n){let r=Re(n.entitySet,n.key),s=`${e}/${r}${n.withoutValuePath?"":"/$value"}`,i=await t.request({method:"get",url:s,responseType:"arraybuffer"}),o={status:i.status,content:Dc(i.data),contentType:String(i.headers?.["content-type"]??"application/octet-stream")},a=Nc(i.headers?.["content-disposition"]);return a&&(o.fileName=a),o}function Dc(t){return Buffer.isBuffer(t)?t:t instanceof ArrayBuffer?Buffer.from(t):ArrayBuffer.isView(t)?Buffer.from(t.buffer,t.byteOffset,t.byteLength):typeof t=="string"?Buffer.from(t,"binary"):Buffer.alloc(0)}function Nc(t){if(typeof t!="string")return;let e=/filename\*=UTF-8''([^;]+)/i.exec(t);if(e?.[1])try{return decodeURIComponent(e[1].trim())}catch{}return /filename="?([^";]+)"?/i.exec(t)?.[1]?.trim()}function Ps(t,e,n){function r(s){return V(s,n)?null:_(new Error(`Entity set '${s}' is not exposed. Configure --expose to include it.`))}t.registerTool("sap_media_upload",{title:"Upload to a SAP Media Entity",description:`L\xE4dt eine Datei als Rohbytes in eine SAP-Media-Entity, z. B. einen Anhang in AttachmentContentSet (API_CV_ATTACHMENT_SRV). Der Dateiname geht als Slug-Kopfzeile hinaus, die Bytes in den Rumpf, die Antwort kommt als JSON.
|
|
247
250
|
|
|
248
251
|
**Kopfzeilen:** objektbezogene Angaben wie BusinessObjectTypeName (Kundenauftrag: BUS2032), LinkedSAPObjectKey (zehnstellig mit f\xFChrenden Nullen) und DocumentInfoRecordDocType geh\xF6ren in \`headers\`. Leere Werte werden nicht gesendet.
|
|
249
252
|
|
|
250
253
|
**Achtung:** Der Anhangdienst kennt kein PATCH \u2014 ein zweiter Upload ersetzt nichts, er h\xE4ngt ein zweites Dokument an dasselbe Objekt. Vorher nachsehen, ob schon eines da ist.
|
|
251
254
|
|
|
252
|
-
**Wann nicht:** f\xFCr gew\xF6hnliche JSON-Entit\xE4ten sap_create verwenden.`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name, e.g., AttachmentContentSet"),contentBase64:z$2.string().describe("Der Dateiinhalt base64-kodiert. Ein data:-Pr\xE4fix wird abgeschnitten."),fileName:z$2.string().describe("Dateiname, geht als Slug-Kopfzeile hinaus"),contentType:z$2.string().describe("Content-Type der Datei, z. B. application/pdf"),headers:z$2.record(z$2.string(),z$2.unknown()).optional().describe('Weitere Kopfzeilen, z. B. { "BusinessObjectTypeName": "BUS2032", "LinkedSAPObjectKey": "0000012345", "DocumentInfoRecordDocType": "PDF" }. Leere Werte werden nicht gesendet.')}),annotations:{readOnlyHint:false,destructiveHint:false,idempotentHint:false,openWorldHint:true}},async({serviceUrl:s,entitySet:i,contentBase64:o,fileName:a,contentType:
|
|
255
|
+
**Wann nicht:** f\xFCr gew\xF6hnliche JSON-Entit\xE4ten sap_create verwenden.`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name, e.g., AttachmentContentSet"),contentBase64:z$2.string().describe("Der Dateiinhalt base64-kodiert. Ein data:-Pr\xE4fix wird abgeschnitten."),fileName:z$2.string().describe("Dateiname, geht als Slug-Kopfzeile hinaus"),contentType:z$2.string().describe("Content-Type der Datei, z. B. application/pdf"),headers:z$2.record(z$2.string(),z$2.unknown()).optional().describe('Weitere Kopfzeilen, z. B. { "BusinessObjectTypeName": "BUS2032", "LinkedSAPObjectKey": "0000012345", "DocumentInfoRecordDocType": "PDF" }. Leere Werte werden nicht gesendet.')}),annotations:{readOnlyHint:false,destructiveHint:false,idempotentHint:false,openWorldHint:true}},async({serviceUrl:s,entitySet:i,contentBase64:o,fileName:a,contentType:d,headers:l})=>{let c=r(i);if(c)return c;let u;try{u=Mc(o);}catch(p){return _(p,{serviceUrl:s})}try{let p=L(s),m=await Rs(e,p,{entitySet:i,content:u,fileName:a,contentType:d,...l?{headers:l}:{}});return {content:[{type:"text",text:JSON.stringify({uploaded:m},null,2)}]}}catch(p){return _(p,{serviceUrl:s})}}),t.registerTool("sap_media_download",{title:"Download from a SAP Media Entity",description:`Holt die Rohbytes einer SAP-Media-Entity \xFCber den $value-Pfad, z. B. den Inhalt eines Anhangs. Die Bytes kommen als resource/blob zur\xFCck, nicht als base64-Zeichenkette im JSON; dazu Dateiname, Content-Type und Gr\xF6\xDFe als Text.
|
|
253
256
|
|
|
254
|
-
**Wann nicht:** f\xFCr die Metadaten eines Anhangs (Name, Gr\xF6\xDFe, Anleger) reicht sap_read auf dieselbe Entit\xE4t.`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name, e.g., AttachmentContentSet"),key:z$2.record(z$2.string(),z$2.unknown()).describe('Entity key as object, e.g., { DocumentInfoRecordDocNumber: "10000042" }'),withoutValuePath:z$2.boolean().optional().describe("Ohne /$value anfragen. Nur n\xF6tig, wenn der Dienst die Bytes direkt unter dem Entit\xE4tspfad liefert.")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:s,entitySet:i,key:o,withoutValuePath:a})=>{let
|
|
257
|
+
**Wann nicht:** f\xFCr die Metadaten eines Anhangs (Name, Gr\xF6\xDFe, Anleger) reicht sap_read auf dieselbe Entit\xE4t.`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name, e.g., AttachmentContentSet"),key:z$2.record(z$2.string(),z$2.unknown()).describe('Entity key as object, e.g., { DocumentInfoRecordDocNumber: "10000042" }'),withoutValuePath:z$2.boolean().optional().describe("Ohne /$value anfragen. Nur n\xF6tig, wenn der Dienst die Bytes direkt unter dem Entit\xE4tspfad liefert.")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:s,entitySet:i,key:o,withoutValuePath:a})=>{let d=r(i);if(d)return d;try{let l=L(s),c=await Cs(e,l,{entitySet:i,key:o,...a?{withoutValuePath:a}:{}}),u=`sap://${i}/${encodeURIComponent(c.fileName??"content")}`;return {content:[{type:"text",text:JSON.stringify({status:c.status,...c.fileName?{fileName:c.fileName}:{},contentType:c.contentType,bytes:c.content.byteLength},null,2)},{type:"resource",resource:{uri:u,mimeType:c.contentType,blob:c.content.toString("base64")}}]}}catch(l){return _(l,{serviceUrl:s})}});}var xc=/^[A-Za-z0-9+/\r\n=_-]+$/;function Mc(t){let e=t.replace(/^data:[^;,]*;base64,/,"").trim();if(e.length===0)throw new Error("contentBase64 ist leer.");if(!xc.test(e))throw new Error("contentBase64 ist keine base64-Kodierung. Erwartet wird der Dateiinhalt base64-kodiert (im n8n-Knoten \xFCbernimmt das das Bin\xE4rfeld), nicht der Klartext oder ein Dateipfad.");let n=Buffer.from(e,"base64");if(n.byteLength===0)throw new Error("contentBase64 ergibt keine Bytes.");return n}var Uc={core:"Core tools (connection, discovery, metadata, NL query)",odata:"OData CRUD tools (read, query, create, update, delete, function, batch)",idoc:"IDoc tools (send, status, discover, list received)",rfc:"RFC/BAPI tools (rfc_call, rfc_metadata, bapi_call, bapi_commit, rfc_search_functions). Requires SAP_RFC_ASHOST/SYSNR or load-balanced equivalent."};function ks(t,e){if(e.isReadOnlyMode())return;let n=e.getActiveTiers(),r=["core","odata","idoc","rfc"],s=e.getAllowedTiers(),i=r.filter(a=>!n.includes(a)&&(!s||s.includes(a))),o=i.length>0?`Available tiers to activate: ${i.map(a=>`${a} (${Uc[a]})`).join(", ")}.`:"All tiers are already active.";t.registerTool("sap_enable_tools",{title:"Enable Additional SAP Tools",description:`Activate additional tool tiers for this session. Currently active: ${n.join(", ")}. ${o}`,inputSchema:z$2.object({tier:z$2.enum(["core","odata","idoc","rfc"]).describe('Tier to activate. Use "idoc" for IDoc tools, "rfc" for RFC/BAPI tools.')}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:false}},async({tier:a})=>{let d=e.enableTier(a);return "error"in d?{content:[{type:"text",text:JSON.stringify({status:"error",message:d.error})}],isError:true}:{content:[{type:"text",text:JSON.stringify({status:"success",tier_enabled:a,enabled_tools:d.enabled,active_tiers:d.activeTiers,note:"Tools enabled. If your client did not refresh the tool list automatically, you may need to reconnect."})}]}});}function Os(t,e){return t.length===0?"":t.map(n=>$c(n,e)).join(" and ")}function $c(t,e){let{field:n,operator:r,value:s}=t;switch(r){case "contains":return Bc(n,String(s),e);case "startswith":return `startswith(${n},'${It(String(s))}')`;case "endswith":return `endswith(${n},'${It(String(s))}')`;default:return Lc(n,r,s,e)}}function Bc(t,e,n){let r=It(e);return n==="v2"?`substringof('${r}',${t})`:`contains(${t},'${r}')`}function Lc(t,e,n,r){let s=Fc(n,r);return `${t} ${e} ${s}`}function Fc(t,e){return typeof t=="number"||typeof t=="boolean"?String(t):Hc(t)?jc(t,e):`'${It(t)}'`}function Hc(t){return /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?/.test(t)}function jc(t,e){return e==="v2"?`datetime'${t.includes("T")?t:`${t}T00:00:00`}'`:t}function It(t){return t.replace(/'/g,"''")}function Is(t,e,n,r){let s=r??new O,i=new Map;function o(d){let l=L(d),c=i.get(l);return c||(c=new M(e,l,s),i.set(l,c)),c}function a(d,l){return _(d,{serviceUrl:l})}t.registerTool("sap_nl_query",{title:"Execute NL-Generated OData Query",description:`Execute a structured OData query generated from natural language. Accepts structured filters (field/operator/value) and converts them to the correct OData V2 or V4 filter syntax automatically. Read-only: only performs queries, never creates, updates, or deletes data.
|
|
255
258
|
|
|
256
259
|
**When to use:**
|
|
257
260
|
- You have a natural language question about SAP data
|
|
@@ -261,7 +264,7 @@ Each entity set carries what SAP allows on it \u2014 \`creatable\`, \`updatable\
|
|
|
261
264
|
**When NOT to use:**
|
|
262
265
|
- You need to create, update, or delete data (use sap_create/sap_update/sap_delete)
|
|
263
266
|
- You already have an exact OData $filter string (use sap_query instead)
|
|
264
|
-
- You don't know which service to query (use sap://routing/{domain} first)`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set to query"),filters:z$2.array(z$2.object({field:z$2.string().describe("Property name from entity schema"),operator:z$2.enum(["eq","ne","gt","ge","lt","le","contains","startswith","endswith"]).describe("Filter operator"),value:z$2.union([z$2.string(),z$2.number(),z$2.boolean()]).describe("Filter value")})).describe("Structured filters from NL interpretation"),select:z$2.array(z$2.string()).optional().describe("Properties to return"),orderby:z$2.string().optional().describe("Sort expression"),top:z$2.number().optional().describe("Maximum number of results"),skip:z$2.number().optional().describe("Number of results to skip")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:
|
|
267
|
+
- You don't know which service to query (use sap://routing/{domain} first)`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set to query"),filters:z$2.array(z$2.object({field:z$2.string().describe("Property name from entity schema"),operator:z$2.enum(["eq","ne","gt","ge","lt","le","contains","startswith","endswith"]).describe("Filter operator"),value:z$2.union([z$2.string(),z$2.number(),z$2.boolean()]).describe("Filter value")})).describe("Structured filters from NL interpretation"),select:z$2.array(z$2.string()).optional().describe("Properties to return"),orderby:z$2.string().optional().describe("Sort expression"),top:z$2.number().optional().describe("Maximum number of results"),skip:z$2.number().optional().describe("Number of results to skip")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:d,entitySet:l,filters:c,select:u,orderby:p,top:m,skip:g})=>{if(!V(l,n))return a(new Error(`Entity set '${l}' is not exposed. Configure --expose to include it.`));try{let f=o(d),b=(await f.getMetadata()).version,S=Os(c,b),y=await f.query(l,{filter:S||void 0,select:u,orderby:p,top:m,skip:g});return {content:[{type:"text",text:JSON.stringify({version:b,generatedFilter:S||"(none)",results:y.results,count:y.count,nextLink:y.nextLink},null,2)}]}}catch(f){return a(f)}});}function Ds(t,e,n,r){let s=r??new O,i=new Map;function o(l){let c=L(l),u=i.get(c);return u||(u=new M(e,c,s),i.set(c,u)),u}function a(l,c){return _(l,{serviceUrl:c})}function d(l){return V(l,n)?null:a(new Error(`Entity set '${l}' is not exposed. Configure --expose to include it.`))}t.registerTool("sap_read",{title:"Read SAP Entity",description:`Read a single entity or entity collection from SAP. Use key parameter for single entity, omit for collection. Supports $select and $expand.
|
|
265
268
|
|
|
266
269
|
**When to use:**
|
|
267
270
|
- You have a specific entity key and want one record
|
|
@@ -269,7 +272,7 @@ Each entity set carries what SAP allows on it \u2014 \`creatable\`, \`updatable\
|
|
|
269
272
|
|
|
270
273
|
**When NOT to use:**
|
|
271
274
|
- You want to list/filter multiple entities (use sap_query)
|
|
272
|
-
- You don't know the entity key (query first, then read)`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name, e.g., A_BusinessPartner"),key:z$2.record(z$2.string(),z$2.unknown()).optional().describe('Entity key as object, e.g., { BusinessPartner: "1000001" }'),select:z$2.array(z$2.string()).optional().describe("Properties to return"),expand:z$2.array(z$2.string()).optional().describe("Navigation properties to expand")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:l,entitySet:
|
|
275
|
+
- You don't know the entity key (query first, then read)`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name, e.g., A_BusinessPartner"),key:z$2.record(z$2.string(),z$2.unknown()).optional().describe('Entity key as object, e.g., { BusinessPartner: "1000001" }. Draft entities: { PurchaseRequisition: "10000411", DraftUUID: "<uuid>", IsActiveEntity: true }'),select:z$2.array(z$2.string()).optional().describe("Properties to return"),expand:z$2.array(z$2.string()).optional().describe("Navigation properties to expand")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:l,entitySet:c,key:u,select:p,expand:m})=>{let g=d(c);if(g)return g;try{let f=o(l),h={select:p,expand:m},b=await f.read(c,u,h);return {content:[{type:"text",text:JSON.stringify(b,null,2)}]}}catch(f){return a(f,l)}}),t.registerTool("sap_query",{title:"Query SAP Entities",description:`Query SAP entities with filter, sort, pagination. Returns matching entities with count. Per D-03: each query option is a separate typed parameter.
|
|
273
276
|
|
|
274
277
|
**When to use:**
|
|
275
278
|
- You have an exact OData $filter string ready
|
|
@@ -279,7 +282,7 @@ Each entity set carries what SAP allows on it \u2014 \`creatable\`, \`updatable\
|
|
|
279
282
|
**When NOT to use:**
|
|
280
283
|
- You have a natural language question (use sap_nl_query instead)
|
|
281
284
|
- You don't know which service to query (use sap://routing/{domain} first, then sap_discover_services)
|
|
282
|
-
- You need to create, update, or delete data (use sap_create/sap_update/sap_delete)`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name"),filter:z$2.string().optional().describe(`OData $filter expression, e.g., "Country eq 'DE'"`),select:z$2.array(z$2.string()).optional().describe("Properties to return"),expand:z$2.array(z$2.string()).optional().describe("Navigation properties to expand"),orderby:z$2.string().optional().describe('Sort expression, e.g., "CreationDate desc"'),top:z$2.number().optional().describe(`Maximum number of results (default ${50}, hard cap ${1e3}). Omitting it does NOT mean "all" \u2014 use skip plus the nextSkip value from the response to page through.`),skip:z$2.number().optional().describe("Number of results to skip"),maxPages:z$2.number().optional().describe("Maximum pagination pages to follow (default: 10)")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:l,entitySet:
|
|
285
|
+
- You need to create, update, or delete data (use sap_create/sap_update/sap_delete)`,inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name"),filter:z$2.string().optional().describe(`OData $filter expression, e.g., "Country eq 'DE'"`),select:z$2.array(z$2.string()).optional().describe("Properties to return"),expand:z$2.array(z$2.string()).optional().describe("Navigation properties to expand"),orderby:z$2.string().optional().describe('Sort expression, e.g., "CreationDate desc"'),top:z$2.number().optional().describe(`Maximum number of results (default ${50}, hard cap ${1e3}). Omitting it does NOT mean "all" \u2014 use skip plus the nextSkip value from the response to page through.`),skip:z$2.number().optional().describe("Number of results to skip"),maxPages:z$2.number().optional().describe("Maximum pagination pages to follow (default: 10)")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:l,entitySet:c,filter:u,select:p,expand:m,orderby:g,top:f,skip:h,maxPages:b})=>{let S=d(c);if(S)return S;try{let y=o(l),w=Ce(f,h),N=await y.query(c,{filter:u,select:p,expand:m,orderby:g,top:w.top,skip:w.skip,maxPages:b}),G=Pe({returned:N.results.length,totalCount:N.count,paging:w,hasMoreHint:N.nextLink?!0:void 0});return {content:[{type:"text",text:JSON.stringify({...N,...G},null,2)}]}}catch(y){return a(y,l)}}),t.registerTool("sap_create",{title:"Create SAP Entity",description:"Create a new entity in SAP. Pass entity data as a JSON string in the data parameter. Use sap_get_metadata with entityType first to discover required fields.",inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name"),data:z$2.string().describe(`Entity data as JSON string, e.g. '{"BusinessPartnerCategory": "2", "BusinessPartnerName": "Acme Corp", "Country": "DE"}'`)}),annotations:{readOnlyHint:false,destructiveHint:false,idempotentHint:false,openWorldHint:true}},async({serviceUrl:l,entitySet:c,data:u})=>{let p;try{p=JSON.parse(u);}catch{return {content:[{type:"text",text:JSON.stringify({error:"Invalid JSON in data parameter. Must be a valid JSON string.",example:'{"BusinessPartnerCategory": "2", "BusinessPartnerName": "Acme Corp"}'})}],isError:true}}let m=d(c);if(m)return m;try{let f=await o(l).create(c,p);return {content:[{type:"text",text:JSON.stringify({created:f},null,2)}]}}catch(g){return a(g,l)}}),t.registerTool("sap_update",{title:"Update SAP Entity",description:"Update an existing SAP entity. Provide the entity key and the fields to update as JSON strings. ETag handling is automatic.",inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name"),key:z$2.string().describe(`Entity key as JSON string, e.g. '{"BusinessPartner": "1000001"}'. Draft entities include DraftUUID and IsActiveEntity (boolean).`),data:z$2.string().describe(`Fields to update as JSON string, e.g. '{"BusinessPartnerName": "New Name"}'`)}),annotations:{readOnlyHint:false,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:l,entitySet:c,key:u,data:p})=>{let m,g;try{m=JSON.parse(u),g=JSON.parse(p);}catch{return a(new Error("Invalid JSON in key or data parameter"))}let f=d(c);if(f)return f;try{return await o(l).update(c,m,g),{content:[{type:"text",text:JSON.stringify({updated:!0,entitySet:c,key:u})}]}}catch(h){return a(h,l)}}),t.registerTool("sap_delete",{title:"Delete SAP Entity",description:"Delete an entity from SAP. Provide the entity key. ETag handling is automatic.",inputSchema:z$2.object({serviceUrl:z$2.string().describe("OData service URL"),entitySet:z$2.string().describe("Entity set name"),key:z$2.string().describe(`Entity key as JSON string, e.g. '{"BusinessPartner": "1000001"}'. Draft entities include DraftUUID and IsActiveEntity (boolean).`)}),annotations:{readOnlyHint:false,destructiveHint:true,idempotentHint:true,openWorldHint:true}},async({serviceUrl:l,entitySet:c,key:u})=>{let p;try{p=JSON.parse(u);}catch{return a(new Error("Invalid JSON in key parameter"))}let m=d(c);if(m)return m;try{return await o(l).delete(c,p),{content:[{type:"text",text:JSON.stringify({deleted:!0,entitySet:c,key:u})}]}}catch(g){return a(g,l)}});}function ze(t){let e=t.RETURN??t.E_RETURN??null;if(e==null)return {messages:[],hasErrors:false,errorCount:0,warningCount:0};let r=(Array.isArray(e)?e:[e]).filter(a=>a!==null&&typeof a=="object").map(a=>Vc(a)),s=r.filter(a=>a.type==="E"||a.type==="A").length,i=r.filter(a=>a.type==="W").length,o=s>0;return {messages:r,hasErrors:o,errorCount:s,warningCount:i}}function Vc(t){let e=String(t.TYPE??"I");return {type:e==="S"||e==="I"||e==="W"||e==="E"||e==="A"?e:"I",id:String(t.ID??""),number:String(t.NUMBER??""),message:String(t.MESSAGE??""),messageV1:z(t.MESSAGE_V1),messageV2:z(t.MESSAGE_V2),messageV3:z(t.MESSAGE_V3),messageV4:z(t.MESSAGE_V4),parameter:z(t.PARAMETER),row:Wc(t.ROW),field:z(t.FIELD),system:z(t.SYSTEM),logNo:z(t.LOG_NO),logMsgNo:z(t.LOG_MSG_NO)}}function z(t){if(t==null)return;let e=String(t);return e===""?void 0:e}function Wc(t){if(t==null)return;let e=String(t);if(e==="")return;let n=Number.parseInt(e,10);return Number.isNaN(n)?void 0:n}function Ns(t){if(!(t instanceof Error))return {code:"UNKNOWN_RFC",message:typeof t=="string"?t:String(t),details:[],rawResponse:t};let e=t;switch(e.code){case "RFC_COMMUNICATION_FAILURE":case "RFC_LOGON_FAILURE":case "RFC_TIMEOUT":case "RFC_INVALID_HANDLE":case "RFC_CLOSED":case "RFC_AUTHORIZATION_FAILURE":case "RFC_AUTHENTICATION_FAILURE":case "RFC_CRYPTOLIB_FAILURE":case "RFC_IO_FAILURE":return {code:e.code,message:e.message,details:[],rawResponse:t};case "RFC_ABAP_MESSAGE":{let n={code:`${e.msgClass??"UNKNOWN"}-${e.msgNumber??"000"}`,message:e.message,severity:e.msgType==="E"||e.msgType==="A"||e.msgType==="X"?"error":e.msgType==="W"?"warning":"info"};return {code:`ABAP_MESSAGE:${e.msgClass??"UNKNOWN"}:${e.msgNumber??"000"}`,message:e.message,details:[n],rawResponse:t}}case "RFC_ABAP_EXCEPTION":return {code:`ABAP_CLASSIC:${e.key??"UNKNOWN"}`,message:e.message,details:[],rawResponse:t};case "RFC_ABAP_CLASS_EXCEPTION":return {code:`ABAP_CLASS:${e.classKey??"UNKNOWN"}`,message:e.message,details:[],rawResponse:t};case "RFC_EXTERNAL_FAILURE":case "RFC_ABAP_RUNTIME_FAILURE":return {code:"RFC_EXTERNAL_RUNTIME",message:e.message,details:[],rawResponse:t};default:return {code:"UNKNOWN_RFC",message:e.message,details:[],rawResponse:t}}}var Dt=null;async function xs(t){if(Dt)return true;try{return Dt=await import('node-rfc'),!0}catch(e){let n=e instanceof Error?e.message:String(e);return t.warn({err:n},"node-rfc not available \u2014 RFC/BAPI tools disabled"),t.warn("Install SAP NW RFC SDK 7.50+ from SAP Support Portal (https://support.sap.com/connectors)"),t.warn("Then run `npm install node-rfc` to enable RFC/BAPI tools"),false}}function Ms(){if(!Dt)throw new Error("node-rfc not loaded \u2014 call tryLoadRfc() first");return Dt}var Us=6e4,Kc=5e3,zc=2,Gc=10,ke=class extends Error{constructor(n){super(`Session ${n} expired or not found. Re-run sap_bapi_call to start a new session.`);this.sessionId=n;this.name="SessionNotFoundError";}},Nt=class{pool;sessions=new Map;logger;closed=false;constructor(e,n){let s=Ms().Pool;this.pool=new s({connectionParameters:{...e},poolOptions:{low:zc,high:Gc}}),this.logger=n;}async callOnce(e,n){if(this.closed)throw new Error("RfcConnectionPool closed");let r=await this.pool.acquire();try{return await r.call(e,n)}finally{try{await this.pool.release(r);}catch(s){this.logger.warn({err:s},"rfc-pool: release after callOnce failed");}}}async callBapi(e,n,r){if(this.closed)throw new Error("RfcConnectionPool closed");let s;if(r){let o=this.sessions.get(r);if(!o)throw new ke(r);this.refreshIdle(o),s=o;}else {let o=await this.pool.acquire();s=this.createSession(o);}return {result:await s.client.call(e,n),sessionId:s.id}}async commit(e,n){let r=this.sessions.get(e);if(!r)throw new ke(e);clearTimeout(r.idleTimer);try{let s=n?{WAIT:"X"}:{},i=await r.client.call("BAPI_TRANSACTION_COMMIT",s),{hasErrors:o}=ze(i);return {result:i,committed:!o,sessionId:e}}finally{this.sessions.delete(e);try{await this.pool.release(r.client);}catch(s){this.logger.warn({err:s},"rfc-pool: release after commit failed");}}}async closeAll(){if(this.closed)return;this.closed=true;let e=Date.now()+Kc;for(;this.sessions.size>0&&Date.now()<e;)await new Promise(n=>setTimeout(n,100));for(let n of this.sessions.values()){clearTimeout(n.idleTimer);try{await this.pool.release(n.client);}catch(r){this.logger.warn({err:r},"rfc-pool: force-release on closeAll failed");}}this.sessions.clear(),await this.pool.closeAll();}sessionCount(){return this.sessions.size}createSession(e){let n=randomUUID(),r={id:n,client:e,createdAt:new Date,idleTimer:setTimeout(()=>this.onIdle(n),Us)};return this.sessions.set(n,r),r}refreshIdle(e){clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>this.onIdle(e.id),Us);}async onIdle(e){let n=this.sessions.get(e);if(n){this.sessions.delete(e);try{await this.pool.release(n.client);}catch(r){this.logger.warn({err:r,sessionId:e},"rfc-pool: release on idle-timeout failed");}}}};function Ge(t){if(t instanceof ke)return {content:[{type:"text",text:JSON.stringify({code:"SESSION_NOT_FOUND",message:t.message,sessionId:t.sessionId})}],isError:true};let e=Ns(t);return {content:[{type:"text",text:JSON.stringify(e)}],isError:true}}var Yc={I:"IMPORT",E:"EXPORT",C:"CHANGING",T:"TABLES",X:"EXCEPTION"};function $s(t,e,n){t.registerTool("sap_rfc_call",{title:"Call SAP RFC Function Module",description:`Invoke any RFC-enabled function module on the SAP backend.
|
|
283
286
|
|
|
284
287
|
When to use:
|
|
285
288
|
- Custom Z_* function modules (no BAPI commit needed)
|
|
@@ -289,7 +292,7 @@ When NOT to use:
|
|
|
289
292
|
- For BAPIs that need transactional commit -> use sap_bapi_call + sap_bapi_commit instead
|
|
290
293
|
- sap_rfc_call is STATELESS: it CANNOT participate in BAPI commit transactions. If you need BAPI_TRANSACTION_COMMIT after this call, the function module must be invoked via sap_bapi_call.
|
|
291
294
|
|
|
292
|
-
The function module must be flagged Remote-Enabled in SE37. Returns the raw output parameters from node-rfc (UPPERCASE keys). SAP date/time/decimal types come as strings (e.g. '20260427', '143052', '1234.56') \u2014 see README for type-mapping notes.`,inputSchema:z$2.object({functionModule:z$2.string().min(1).describe("Function module name, e.g. RFC_PING, RFC_READ_TABLE, Z_CUSTOM_FM"),parameters:z$2.record(z$2.string(),z$2.unknown()).optional().describe('IMPORT/CHANGING/TABLES parameters as ABAP-uppercase keys, e.g. { QUERY_TABLE: "T001", DELIMITER: "|" }')}),annotations:{readOnlyHint:false,destructiveHint:false,idempotentHint:false,openWorldHint:true}},async({functionModule:r,parameters:s})=>{try{let i=await e.callOnce(r.toUpperCase(),s??{});return {content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(i){return
|
|
295
|
+
The function module must be flagged Remote-Enabled in SE37. Returns the raw output parameters from node-rfc (UPPERCASE keys). SAP date/time/decimal types come as strings (e.g. '20260427', '143052', '1234.56') \u2014 see README for type-mapping notes.`,inputSchema:z$2.object({functionModule:z$2.string().min(1).describe("Function module name, e.g. RFC_PING, RFC_READ_TABLE, Z_CUSTOM_FM"),parameters:z$2.record(z$2.string(),z$2.unknown()).optional().describe('IMPORT/CHANGING/TABLES parameters as ABAP-uppercase keys, e.g. { QUERY_TABLE: "T001", DELIMITER: "|" }')}),annotations:{readOnlyHint:false,destructiveHint:false,idempotentHint:false,openWorldHint:true}},async({functionModule:r,parameters:s})=>{try{let i=await e.callOnce(r.toUpperCase(),s??{});return {content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(i){return Ge(i)}}),t.registerTool("sap_rfc_metadata",{title:"Get SAP RFC Function Module Interface",description:`Retrieve the IMPORT/EXPORT/CHANGING/TABLES/EXCEPTIONS interface of an RFC-enabled function module (via RFC_GET_FUNCTION_INTERFACE).
|
|
293
296
|
|
|
294
297
|
When to use:
|
|
295
298
|
- Before calling an unfamiliar RFC/BAPI to understand its parameters
|
|
@@ -298,7 +301,7 @@ When to use:
|
|
|
298
301
|
When NOT to use:
|
|
299
302
|
- To search for function modules by name pattern -> use sap_rfc_search_functions
|
|
300
303
|
|
|
301
|
-
Returns a structured FunctionInterface object: name, remoteEnabled, updateTask, parameters[].`,inputSchema:z$2.object({functionModule:z$2.string().min(1).describe("Function module name, e.g. BAPI_USER_GET_DETAIL"),language:z$2.string().length(1).optional().describe("Single-character language key for parameter texts (default: server config)")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({functionModule:r,language:s})=>{try{let i=r.toUpperCase(),o={FUNCNAME:i};s&&(o.LANGUAGE=s);let a=await e.callOnce("RFC_GET_FUNCTION_INTERFACE",o),l=(a.PARAMS??[]).map(u=>{let
|
|
304
|
+
Returns a structured FunctionInterface object: name, remoteEnabled, updateTask, parameters[].`,inputSchema:z$2.object({functionModule:z$2.string().min(1).describe("Function module name, e.g. BAPI_USER_GET_DETAIL"),language:z$2.string().length(1).optional().describe("Single-character language key for parameter texts (default: server config)")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({functionModule:r,language:s})=>{try{let i=r.toUpperCase(),o={FUNCNAME:i};s&&(o.LANGUAGE=s);let a=await e.callOnce("RFC_GET_FUNCTION_INTERFACE",o),l=(a.PARAMS??[]).map(u=>{let p=String(u.PARAMCLASS??"").trim(),m=Yc[p]??"IMPORT",g=String(u.TABNAME??"").trim(),f=String(u.FIELDNAME??"").trim(),h=g&&f?`${g}-${f}`:g||f||void 0,b=String(u.DECIMALS??"").trim(),S=b===""?void 0:Number.parseInt(b,10),y=Number.isNaN(S)?void 0:S,w=String(u.INTLENGTH??"0").trim(),N=Number.parseInt(w===""?"0":w,10)||0,G=String(u.DEFAULT??"").trim(),Xe=String(u.PARAMTEXT??"").trim();return {name:String(u.PARAMETER??"").trim(),direction:m,type:String(u.EXID??"").trim(),length:N,decimals:y,optional:String(u.OPTIONAL??"").trim()==="X",default:G===""?void 0:G,text:Xe===""?void 0:Xe,ddicReference:h}}),c={name:i,remoteEnabled:String(a.REMOTE_CALL??"")==="X",updateTask:String(a.UPDATE_TASK??"")==="X",parameters:l};return {content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(i){return Ge(i)}}),t.registerTool("sap_bapi_call",{title:"Call SAP BAPI (stateful)",description:`Invoke a BAPI (Business API) on a stateful connection. Returns a sessionId for subsequent BAPI calls and the eventual sap_bapi_commit.
|
|
302
305
|
|
|
303
306
|
When to use:
|
|
304
307
|
- BAPI_USER_CREATE1, BAPI_SALESORDER_CREATEFROMDAT2, BAPI_*_CHANGE, etc.
|
|
@@ -311,7 +314,7 @@ Critical behavior:
|
|
|
311
314
|
- The output contains a parsed \`return\` array (BAPIRET2 messages, camelCase) plus \`hasErrors\`, \`errorCount\`, \`warningCount\`.
|
|
312
315
|
- If hasErrors=true, do NOT call sap_bapi_commit. The session has not yet been committed; either call sap_bapi_call again with corrected params (sessionId reused) or let the session idle out (auto-rollback after 60s).
|
|
313
316
|
- hasErrors=true is NOT isError=true at the MCP transport level (the call itself succeeded).
|
|
314
|
-
- If sessionId is omitted, a fresh stateful connection is opened. Pass it back to chain BAPIs on the same connection before BAPI_TRANSACTION_COMMIT.`,inputSchema:z$2.object({bapi:z$2.string().min(1).describe("BAPI function module name, e.g. BAPI_USER_GET_DETAIL"),parameters:z$2.record(z$2.string(),z$2.unknown()).optional().describe("BAPI input parameters with ABAP-uppercase keys"),sessionId:z$2.string().uuid().optional().describe("Continue an existing session from a previous sap_bapi_call. Omit on first call.")}),annotations:{readOnlyHint:false,destructiveHint:false,idempotentHint:false,openWorldHint:true}},async({bapi:r,parameters:s,sessionId:i})=>{try{let{result:o,sessionId:a}=await e.callBapi(r.toUpperCase(),s??{},i),
|
|
317
|
+
- If sessionId is omitted, a fresh stateful connection is opened. Pass it back to chain BAPIs on the same connection before BAPI_TRANSACTION_COMMIT.`,inputSchema:z$2.object({bapi:z$2.string().min(1).describe("BAPI function module name, e.g. BAPI_USER_GET_DETAIL"),parameters:z$2.record(z$2.string(),z$2.unknown()).optional().describe("BAPI input parameters with ABAP-uppercase keys"),sessionId:z$2.string().uuid().optional().describe("Continue an existing session from a previous sap_bapi_call. Omit on first call.")}),annotations:{readOnlyHint:false,destructiveHint:false,idempotentHint:false,openWorldHint:true}},async({bapi:r,parameters:s,sessionId:i})=>{try{let{result:o,sessionId:a}=await e.callBapi(r.toUpperCase(),s??{},i),d=ze(o),{RETURN:l,E_RETURN:c,...u}=o,p={},m={};for(let[g,f]of Object.entries(u))Array.isArray(f)?m[g]=f:p[g]=f;return {content:[{type:"text",text:JSON.stringify({sessionId:a,return:d.messages,hasErrors:d.hasErrors,errorCount:d.errorCount,warningCount:d.warningCount,exports:p,tables:m},null,2)}]}}catch(o){return Ge(o)}}),t.registerTool("sap_bapi_commit",{title:"Commit SAP BAPI Transaction",description:`Call BAPI_TRANSACTION_COMMIT on the SAME stateful connection used by a prior sap_bapi_call. Releases the session afterwards.
|
|
315
318
|
|
|
316
319
|
When to use:
|
|
317
320
|
- After one or more sap_bapi_call invocations that returned hasErrors=false
|
|
@@ -320,7 +323,7 @@ When NOT to use:
|
|
|
320
323
|
- If the prior sap_bapi_call returned hasErrors=true (commit would persist a faulty state \u2014 implicit rollback by letting the session idle is safer)
|
|
321
324
|
- For non-BAPI RFC calls (sap_rfc_call cannot participate)
|
|
322
325
|
|
|
323
|
-
Returns committed=true when BAPI_TRANSACTION_COMMIT returns no E/A messages.`,inputSchema:z$2.object({sessionId:z$2.string().uuid().describe("Session id from a prior sap_bapi_call"),wait:z$2.boolean().optional().default(true).describe("When true (default), waits for ABAP UPDATE-Task to complete (WAIT=X). When false, fire-and-forget (risky \u2014 failures only visible in SM58/SM37).")}),annotations:{readOnlyHint:false,destructiveHint:true,idempotentHint:false,openWorldHint:true}},async({sessionId:r,wait:s})=>{try{let{result:i,committed:o,sessionId:a}=await e.commit(r,s??!0),
|
|
326
|
+
Returns committed=true when BAPI_TRANSACTION_COMMIT returns no E/A messages.`,inputSchema:z$2.object({sessionId:z$2.string().uuid().describe("Session id from a prior sap_bapi_call"),wait:z$2.boolean().optional().default(true).describe("When true (default), waits for ABAP UPDATE-Task to complete (WAIT=X). When false, fire-and-forget (risky \u2014 failures only visible in SM58/SM37).")}),annotations:{readOnlyHint:false,destructiveHint:true,idempotentHint:false,openWorldHint:true}},async({sessionId:r,wait:s})=>{try{let{result:i,committed:o,sessionId:a}=await e.commit(r,s??!0),d=ze(i);return {content:[{type:"text",text:JSON.stringify({sessionId:a,return:d.messages,committed:o},null,2)}]}}catch(i){return Ge(i)}}),t.registerTool("sap_rfc_search_functions",{title:"Search SAP RFC Function Modules",description:`Search for function modules by name pattern via RFC_FUNCTION_SEARCH.
|
|
324
327
|
|
|
325
328
|
When to use:
|
|
326
329
|
- Discover BAPIs/FMs by partial name (e.g. "BAPI_USER_*", "Z_*")
|
|
@@ -331,7 +334,7 @@ When NOT to use:
|
|
|
331
334
|
Wildcards (SAP-native, NOT glob):
|
|
332
335
|
- \`*\` matches any sequence of characters
|
|
333
336
|
- \`+\` matches a single character
|
|
334
|
-
Patterns are case-folded to uppercase. Default limit is 100; set higher to widen, but SAP returns unpaginated results \u2014 large patterns may stress the backend.`,inputSchema:z$2.object({pattern:z$2.string().min(1).describe('Name pattern with SAP wildcards (* and +). Examples: "BAPI_USER_*", "Z_*", "+MATERIAL+"'),groupName:z$2.string().optional().describe('Optional function group filter (RS38L_AREA, e.g. "SU05")'),limit:z$2.number().int().positive().optional().default(100).describe("Max number of hits to return (default 100). Excess is truncated.")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({pattern:r,groupName:s,limit:i})=>{try{let o={FUNCNAME:r.toUpperCase()};s&&(o.GROUPNAME=s.toUpperCase());let c=(await e.callOnce("RFC_FUNCTION_SEARCH",o)).FUNCTIONS??[],l=c.length,d=i??100,u=l>d,g={functions:(u?c.slice(0,d):c).map(f=>{let h=String(f.TEXT??"").trim();return {name:String(f.FUNCNAME??"").trim(),groupName:String(f.GROUPNAME??"").trim(),shortText:h===""?void 0:h}}),total:l,...u?{truncated:!0}:{}};return {content:[{type:"text",text:JSON.stringify(g,null,2)}]}}catch(o){return Ke(o)}});}function ze(t,e){let n=new McpServer({name:Cr,version:Er},{capabilities:{logging:{},resources:{},prompts:{},tools:{listChanged:true}}}),r={readOnly:e?.readOnly??false,activeTiers:e?.tiers??zr,...e?.allowedTiers?{allowedTiers:e.allowedTiers}:{},hasIdocConfig:e?.hasIdocConfig??false,hasRfcConfig:e?.hasRfcConfig??false},s=new Et(r),i=n.registerTool.bind(n),o=e?.toolTimeoutMs??yt*1e3;n.registerTool=((h,...S)=>{let b=S.length-1,y=S[b];typeof y=="function"&&(S[b]=Ar(h,y,{toolTimeoutMs:o,logger:e?.logger,tlsVerifyDisabled:e?.httpOptions?.tlsVerify===false}));let A=i(h,...S);return s.registerHandle(h,A),A});let a={baseUrl:t.baseUrl,...e?.cacheDir?{persistDir:e.cacheDir}:{},...e?.logger?{onWarn:(h,S)=>e.logger?.warn({err:S},h)}:{}},c=e?.caches?.catalog??new X(a),l=e?.caches?.metadata??new O(a),d=Je(t),u=new ye(d,e?.httpOptions);e?.userContext&&(u.userContext=e.userContext);let m=e?.exposePatterns,p=e?.sapClient??t.sapClient;Pr(n),Rr(n),ds(n,u,{baseUrl:t.baseUrl,sapClient:p,tlsVerify:e?.httpOptions?.tlsVerify}),is(n,u,m),ss(n,u,{catalogV2Url:e?.catalogV2Url,catalogV4Url:e?.catalogV4Url,baseUrl:t.baseUrl,onDiscoveryMatch:e?.onDiscoveryMatch,...e?.concurrency?{concurrency:e.concurrency}:{}},c),As(n,u,m,l),cs(n,u,l,e?.readOnly??false),hs(n,u,m),Jr(n,u,l),Ts(n,u,m,l);let g=e?.idocStore??new Ce,f=new ht(u,p,e?.partnerDefaults);return ps(n,u,f,g),jr(n,g),e?.rfcAvailable&&e.rfcPool&&e.logger&&Rs(n,e.rfcPool,e.logger),Ss(n,s),Vr(n,u,l),Hr(n,u,l),qr(n),s.applyInitialVisibility(),{server:n,registry:s,httpClient:u}}var xc=z$1.object({snc_qop:z$1.enum(["1","2","3","8","9"]).optional(),snc_myname:z$1.string().min(1).optional(),snc_partnername:z$1.string().min(1).optional(),snc_lib:z$1.string().min(1).optional()}).superRefine((t,e)=>{let n=[t.snc_qop,t.snc_myname,t.snc_partnername,t.snc_lib].filter(r=>r!==void 0).length;if(n>0&&n<4){let r=[];t.snc_qop||r.push("SAP_SNC_QOP"),t.snc_myname||r.push("SAP_SNC_MYNAME"),t.snc_partnername||r.push("SAP_SNC_PARTNERNAME"),t.snc_lib||r.push("SAP_SNC_LIB"),e.addIssue({code:"custom",message:`SNC config incomplete: missing ${r.join(", ")} (D-12 all-or-none)`,path:["snc_qop"]});}});function Tn(t=process.env){let e=t.SAP_USERNAME,n=t.SAP_PASSWORD;if(!e||!n)return null;let r=!!(t.SAP_RFC_ASHOST&&t.SAP_RFC_SYSNR),s=!!(t.SAP_RFC_MSHOST&&t.SAP_RFC_GROUP);if(!r&&!s)return null;let i={user:e,passwd:n,client:t.SAP_CLIENT,lang:t.SAP_RFC_LANG??"EN"};r&&(i.ashost=t.SAP_RFC_ASHOST,i.sysnr=t.SAP_RFC_SYSNR),s&&(i.mshost=t.SAP_RFC_MSHOST,i.msserv=t.SAP_RFC_MSSERV,i.group=t.SAP_RFC_GROUP);let o=xc.safeParse({snc_qop:t.SAP_SNC_QOP,snc_myname:t.SAP_SNC_MYNAME,snc_partnername:t.SAP_SNC_PARTNERNAME,snc_lib:t.SAP_SNC_LIB});if(!o.success){let a=o.error.issues.map(c=>c.message).join("; ");throw new Error(`Invalid SNC configuration: ${a}`)}return o.data.snc_qop&&Object.assign(i,o.data),i}function Ps(t=process.env){try{return Tn(t)!==null}catch{return false}}var kt=class{constructor(e={}){this.options=e;this.discovery=e.discoveryClient??new se;}discovery;jwks;jwksIssuer;get verifies(){return !!this.options.issuerUrl}async verify(e){let n=e.authorization??e.Authorization;if(typeof n!="string"||!n.startsWith("Bearer "))return {kind:"none"};let r=n.slice(7).trim();if(!r)return {kind:"none"};if(!this.options.issuerUrl)return this.withoutVerification(r);try{let s=await this.getKeySet(this.options.issuerUrl),{payload:i}=await jwtVerify(r,s,{issuer:void 0,...this.options.audience?{audience:this.options.audience}:{}});return typeof i.sub!="string"?{kind:"rejected",reason:"Token ohne sub-Claim"}:{kind:"user",context:{token:r,sub:i.sub,email:typeof i.email=="string"?i.email:void 0,name:typeof i.name=="string"?i.name:void 0,verified:!0}}}catch(s){return Bc(r)?{kind:"rejected",reason:s instanceof Error?s.message:"Signaturpr\xFCfung fehlgeschlagen"}:{kind:"none"}}}withoutVerification(e){try{let n=decodeJwt(e);return typeof n.sub!="string"?{kind:"none"}:{kind:"user",context:{token:e,sub:n.sub,email:typeof n.email=="string"?n.email:void 0,name:typeof n.name=="string"?n.name:void 0,verified:!1}}}catch{return {kind:"none"}}}async getKeySet(e){if(this.jwks&&this.jwksIssuer===e)return this.jwks;let n=await this.discovery.discover(e);return this.jwks=createRemoteJWKSet(new URL(n.jwks_uri)),this.jwksIssuer=e,this.jwks}};function Bc(t){let e=t.split(".");return e.length===3&&e.every(n=>n.length>0)}var Lc="x-sap-username",Fc="x-sap-password",Hc="x-sap-authorization";function An(t,e){let n=t[e]??t[e.toLowerCase()],r=Array.isArray(n)?n[0]:n;return typeof r=="string"&&r.length>0?r:void 0}function jc(t){let e=An(t,Lc),n=An(t,Fc);if(e&&n)return {username:e,password:n};let r=An(t,Hc),s=r?/^Basic\s+(.+)$/i.exec(r.trim()):null;if(s?.[1]){let i=Buffer.from(s[1].trim(),"base64").toString("utf8"),o=i.indexOf(":");if(o>0&&o<i.length-1)return {username:i.slice(0,o),password:i.slice(o+1)}}}function wn(t,e){let n=jc(e);if(t.authType==="user-basic"){if(!n)return {kind:"missing",hint:`Destination "${t.name}" verlangt eine pers\xF6nliche SAP-Anmeldung: sende X-SAP-Username und X-SAP-Password (oder X-SAP-Authorization: Basic <base64 user:pass>) mit \u2014 im n8n-Node \u201EGuniWeb SAP" sind das die Felder SAP-Benutzer/SAP-Passwort des Credentials.`};let{name:r,baseUrl:s,sapClient:i}=t;return {kind:"ok",config:{name:r,baseUrl:s,authType:"basic",...i!==void 0?{sapClient:i}:{},username:n.username,password:n.password},username:n.username}}return n?{kind:"rejected",reason:`Destination "${t.name}" (authType ${t.authType}) nimmt keine pers\xF6nliche SAP-Anmeldung an \u2014 die Anfrage w\xFCrde unter dem konfigurierten technischen Zugang laufen. Lass X-SAP-Username/X-SAP-Password weg oder stelle die Destination auf authType user-basic um.`}:{kind:"ok",config:t}}var Vc=z$2.object({destination:z$2.string().min(1),label:z$2.string().max(200).optional(),readOnly:z$2.boolean().optional(),tiers:z$2.array(z$2.string()).optional()}).strict();function ue(t,e,n){t.setHeader("Cache-Control","no-store"),t.status(e).json(n);}function _s(t){return t.ip??t.socket?.remoteAddress??"unknown"}function ks(t,e){let n=t?.[e];return n==="true"||n==="1"||n===""}function Os(t){t.use("/admin",(e,n)=>{ue(n,404,{error:"Admin API not enabled",hint:"Start the server with --admin-token <secret> (or SAP_MCP_ADMIN_TOKEN) and --destinations <file>."});});}function Is(t,e){let{destinations:n,logger:r}=e,s=new Ae({apiKey:e.adminToken,tokens:()=>[]});t.use("/admin",(c,l,d)=>{let u=c,m=l,p=_s(u),g=s.authenticate(u.headers.authorization,p);if(g.kind==="rate-limited"){r?.warn({clientAddress:p,path:u.originalUrl??u.path,retryAfterSeconds:g.retryAfterSeconds},"Admin-API: zu viele fehlgeschlagene Anmeldeversuche von dieser Adresse"),m.setHeader("Retry-After",String(g.retryAfterSeconds)),ue(m,429,{error:"Too Many Requests",retryAfterSeconds:g.retryAfterSeconds});return}if(g.kind!=="api-key"){r?.warn({clientAddress:p,path:u.originalUrl??u.path},"Admin-API: Anfrage abgewiesen"),ue(m,401,{error:"Unauthorized"});return}d();});let i=()=>{if(n.source!=="file"||!n.filePath)throw new R("Admin API requires a destinations file: start the server with --destinations <file>.",409);return new we({filePath:n.filePath,env:e.env})},o=(c,l)=>(d,u)=>{let m=d,p=u,g=_s(m);(async()=>{try{let{status:f,body:h,changed:S}=l(m);if(S){let b=await n.reload();h.reloaded=b.ok,b.ok||(h.reloadErrors=b.errors),r?.info({action:c,clientAddress:g,target:m.params?.name??m.params?.ref??m.body?.destination,reloaded:b.ok},"Admin-API: \xC4nderung ausgef\xFChrt");}ue(p,f,h);}catch(f){if(f instanceof R){ue(p,f.status,{error:f.message});return}r?.warn({action:c,clientAddress:g,err:f instanceof Error?f.message:String(f)},"Admin-API: unerwarteter Fehler"),ue(p,500,{error:"Internal Server Error"});}})();},a=c=>{let l=c.body;if(l===null||typeof l!="object"||Array.isArray(l))throw new R("Request body must be a JSON object (Content-Type: application/json).",400);return l};t.get("/admin/destinations",o("destinations.list",()=>({status:200,body:{destinations:i().listDestinations()},changed:!1}))),t.put("/admin/destinations/:name",o("destinations.put",c=>{let l=c.params?.name??"",d=a(c),{replaced:u,warnings:m}=i().addDestination(l,d,{replace:!0,allowMissingEnv:ks(c.query,"allowMissingEnv")});return {status:u?200:201,body:{name:l,replaced:u,warnings:m},changed:!0}})),t.delete("/admin/destinations/:name",o("destinations.delete",c=>{let l=c.params?.name??"",{removedTokens:d}=i().removeDestination(l,{force:ks(c.query,"force")});return {status:200,body:{name:l,removedTokens:d},changed:!0}})),t.get("/admin/tokens",o("tokens.list",()=>({status:200,body:{tokens:i().listTokens()},changed:!1}))),t.post("/admin/tokens",o("tokens.issue",c=>{let l=Vc.safeParse(a(c));if(!l.success)throw new R(`Invalid body: ${l.error.issues.map(f=>`${f.path.join(".")||"(root)"}: ${f.message}`).join("; ")}`,400);let{destination:d,...u}=l.data,{token:m,hash:p,entry:g}=i().issueToken(d,u);return {status:201,body:{token:m,hash:p.replace(/^sha256:/,"").slice(0,12),destination:g.destination,label:g.label,policy:g.policy,createdAt:g.createdAt},changed:!0}})),t.delete("/admin/tokens/:ref",o("tokens.revoke",c=>{let l=c.params?.ref??"",d=i().revokeToken(l);return {status:200,body:{...d,revoked:!0},changed:!d.alreadyRevoked}})),t.use("/admin",(c,l)=>{ue(l,404,{error:"Not Found"});});}var zc=16*1024*1024;function Gc(t){let e=Ds();return e.use(Ds.json({limit:t})),e}function Ns(t,e){return e?.kind==="token"?t.destinations?.get(e.token.destination):t.destinations?t.destinations.defaultDestination():t.config}function xs(t,e){if(e?.kind!=="token"||!e.token.policy)return t;let n=e.token.policy;return {...t,readOnly:t.readOnly===true||n.readOnly===true,...n.tiers?{allowedTiers:[...n.tiers]}:{}}}function Ms(t,e){t.writeHead(503,{"Content-Type":"application/json"});let n=e?.names().length??0;t.end(JSON.stringify({error:"Keine Destination bestimmbar",hint:n===0?'Es ist noch keine Destination konfiguriert. Lege eine an \u2014 per Admin-API (PUT /admin/destinations/<name>) oder CLI (guniweb-sap-mcp destinations add) \u2014 und ein Token daf\xFCr, oder nenne sie "default".':'Es sind mehrere Destinations konfiguriert, aber keine hei\xDFt "default". Benenne eine Destination "default", lege Tokens an oder reduziere die Datei auf ein Ziel.'}));}var vn=class t{constructor(e){this.persistDir=e;}entries=new Map;static MAX_USER_SCOPES=100;for(e,n){let r=`${e.name}\0${e.baseUrl}\0${n??""}`,s=this.entries.get(r);if(!s){for(let o of this.entries.keys())o.startsWith(`${e.name}\0`)&&!o.startsWith(`${e.name}\0${e.baseUrl}\0`)&&this.entries.delete(o);n!==void 0&&this.evictOldestUserScopes(e);let i={baseUrl:e.baseUrl,...this.persistDir&&n===void 0?{persistDir:this.persistDir}:{}};s={catalog:new X(i),metadata:new O(i)},this.entries.set(r,s);}return s}evictOldestUserScopes(e){let n=`${e.name}\0${e.baseUrl}\0`,r=[...this.entries.keys()].filter(s=>s.startsWith(n)&&s!==n);for(;r.length>=t.MAX_USER_SCOPES;)this.entries.delete(r.shift());}};function Us(t,e){if(e.kind==="missing"){t.writeHead(401,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"SAP login required",hint:e.hint}));return}t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:e.reason}));}function $s(t,e){let n=t.logger;return !e||!n||typeof n.child!="function"?t:{...t,logger:n.child({sapUser:e})}}async function Bs(t){let e=new kt({...t.jwtIssuer?{issuerUrl:t.jwtIssuer}:{},...t.jwtAudience?{audience:t.jwtAudience}:{}}),n=t.maxRequestBytes??zc,r=Gc(n),s=new Ae({apiKey:t.apiKey,tokens:()=>t.destinations?.tokens()??[]}),i=t.serverOptions?.logger;r.use((c,l,d)=>{let u=c,m=l,p=d;if(u.path==="/healthz"){p();return}if(u.path==="/admin"||u.path.startsWith("/admin/")){p();return}if(t.webhookSecret&&u.path==="/idoc"){p();return}let g=u.ip??u.socket?.remoteAddress??"unknown",f=s.authenticate(u.headers.authorization,g);if(f.kind==="rate-limited"){i?.warn({clientAddress:g,path:u.path,retryAfterSeconds:f.retryAfterSeconds},"MCP-Anfrage abgewiesen: zu viele fehlgeschlagene Anmeldeversuche von dieser Adresse"),m.setHeader("Retry-After",String(f.retryAfterSeconds)),m.status(429).json({error:"Too Many Requests",retryAfterSeconds:f.retryAfterSeconds});return}if(f.kind==="rejected"){i?.warn({clientAddress:g,path:u.path,reason:f.reason},"MCP-Anfrage abgewiesen"),m.status(401).json({error:"Unauthorized"});return}u.sapAuth=f,p();}),r.get("/healthz",(c,l)=>{l.status(200).json({status:"ok"});}),t.adminToken&&t.destinations?Is(r,{adminToken:t.adminToken,destinations:t.destinations,env:t.env??process.env,logger:i}):Os(r),t.transport==="http"?Yc(r,t,e):Jc(r,t,e);let o=mt(t.idocStore,{secret:t.webhookSecret,maxBodyBytes:t.webhookMaxBodyBytes});r.post("/idoc",(c,l)=>{o(c,l);}),r.use((c,l,d,u)=>{let m=c,p=d;if(m?.type==="entity.parse.failed"&&!p.headersSent){p.status(400).json({error:`Invalid JSON body: ${m.message??"parse error"}`});return}if(m?.type==="entity.too.large"&&!p.headersSent){let g=(n/1024/1024).toFixed(1);p.status(413).json({error:`Anfrage zu gro\xDF: die Grenze liegt bei ${n} Bytes (${g} MiB). Bei einem base64-kodierten Anhang sind das rund ${(n*3/4/1024/1024).toFixed(1)} MiB Datei. Mit --max-request-bytes anheben.`});return}u(c);});let a=createServer(r);return new Promise(c=>{a.listen(t.port,()=>{let l=a.address(),d=typeof l=="object"&&l!==null?l.port:t.port;c({port:d,close:()=>new Promise((u,m)=>{a.close(p=>p?m(p):u());})});});})}function Yc(t,e,n){let r=new vn(e.serverOptions?.cacheDir),s=e.serverOptions?.logger;t.all("/mcp",(i,o)=>{let a=i,c=o,l=Ns(e,a.sapAuth);if(!l){Ms(c,e.destinations);return}let d=wn(l,a.headers);if(d.kind!=="ok"){s?.warn({destination:l.name,clientAddress:a.socket?.remoteAddress,reason:d.kind},d.kind==="missing"?"MCP-Anfrage ohne pers\xF6nliche SAP-Anmeldung an eine user-basic-Destination abgewiesen":"MCP-Anfrage mit pers\xF6nlicher SAP-Anmeldung an eine Destination mit technischem Benutzer abgewiesen"),Us(c,d);return}let u=d.config,m=d.username,p=new StreamableHTTPServerTransport({sessionIdGenerator:void 0}),g=f=>{let{server:h}=ze(u,{...xs($s(e.serverOptions,m),a.sapAuth),sapClient:u.sapClient,userContext:f,caches:r.for(u,m)});h.connect(p).then(()=>p.handleRequest(a,c,a.body)).catch(()=>{c.headersSent||(c.writeHead(500),c.end("Internal Server Error"));});};n.verify(a.headers).then(f=>{if(f.kind==="rejected"){c.writeHead(401,{"Content-Type":"application/json"}),c.end(JSON.stringify({error:"Unauthorized",reason:f.reason,hint:"Das mitgeschickte Bearer-Token hat die Signaturpr\xFCfung gegen SAP_JWT_ISSUER nicht bestanden."}));return}let h=f.kind==="user"?f.context:null;g(h);});});}function Jc(t,e,n){let r=new Map;t.get("/sse",(s,i)=>{let o=s,a=i,c=Ns(e,o.sapAuth);if(!c){Ms(a,e.destinations);return}let l=wn(c,o.headers??{});if(l.kind!=="ok"){Us(a,l);return}let d=l.config,u=new SSEServerTransport("/message",a),{server:m,httpClient:p}=ze(d,{...xs($s(e.serverOptions,l.username),o.sapAuth),sapClient:d.sapClient});m.connect(u).then(()=>u.start()).then(()=>{r.set(u.sessionId,{transport:u,server:m,httpClient:p}),u.onclose=()=>{r.delete(u.sessionId);};}).catch(()=>{a.headersSent||(a.writeHead(500),a.end("Internal Server Error"));});}),t.post("/message",(s,i)=>{let o=s,a=i,l=new URL(o.url??"",`http://${o.headers.host??"localhost"}`).searchParams.get("sessionId")??o.query?.sessionId;if(!l||!r.has(l)){a.writeHead(400,{"Content-Type":"application/json"}),a.end(JSON.stringify({error:"Invalid or missing session ID"}));return}let d=r.get(l);n.verify(o.headers).then(u=>{if(u.kind==="rejected"){a.writeHead(401,{"Content-Type":"application/json"}),a.end(JSON.stringify({error:"Unauthorized",reason:u.reason}));return}d.httpClient.userContext=u.kind==="user"?u.context:null,d.transport.handlePostMessage(o,a,o.body).catch(()=>{a.headersSent||(a.writeHead(500),a.end("Internal Server Error"));});});});}var ed=`Usage: guniweb-sap-mcp [options]
|
|
337
|
+
Patterns are case-folded to uppercase. Default limit is 100; set higher to widen, but SAP returns unpaginated results \u2014 large patterns may stress the backend.`,inputSchema:z$2.object({pattern:z$2.string().min(1).describe('Name pattern with SAP wildcards (* and +). Examples: "BAPI_USER_*", "Z_*", "+MATERIAL+"'),groupName:z$2.string().optional().describe('Optional function group filter (RS38L_AREA, e.g. "SU05")'),limit:z$2.number().int().positive().optional().default(100).describe("Max number of hits to return (default 100). Excess is truncated.")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({pattern:r,groupName:s,limit:i})=>{try{let o={FUNCNAME:r.toUpperCase()};s&&(o.GROUPNAME=s.toUpperCase());let d=(await e.callOnce("RFC_FUNCTION_SEARCH",o)).FUNCTIONS??[],l=d.length,c=i??100,u=l>c,g={functions:(u?d.slice(0,c):d).map(f=>{let h=String(f.TEXT??"").trim();return {name:String(f.FUNCNAME??"").trim(),groupName:String(f.GROUPNAME??"").trim(),shortText:h===""?void 0:h}}),total:l,...u?{truncated:!0}:{}};return {content:[{type:"text",text:JSON.stringify(g,null,2)}]}}catch(o){return Ge(o)}});}function Ye(t,e){let n=new McpServer({name:xr,version:Nr},{capabilities:{logging:{},resources:{},prompts:{},tools:{listChanged:true}}}),r={readOnly:e?.readOnly??false,activeTiers:e?.tiers??vn,...e?.allowedTiers?{allowedTiers:e.allowedTiers}:{},hasIdocConfig:e?.hasIdocConfig??false,hasRfcConfig:e?.hasRfcConfig??false},s=new kt(r),i=n.registerTool.bind(n),o=e?.toolTimeoutMs??Tt*1e3;n.registerTool=((h,...b)=>{let S=b.length-1,y=b[S];typeof y=="function"&&(b[S]=Or(h,y,{toolTimeoutMs:o,logger:e?.logger,tlsVerifyDisabled:e?.httpOptions?.tlsVerify===false}));let w=i(h,...b);return s.registerHandle(h,w),w});let a={baseUrl:t.baseUrl,...e?.cacheDir?{persistDir:e.cacheDir}:{},...e?.logger?{onWarn:(h,b)=>e.logger?.warn({err:b},h)}:{}},d=e?.caches?.catalog??new X(a),l=e?.caches?.metadata??new O(a),c=Ze(t),u=new he(c,e?.httpOptions);e?.userContext&&(u.userContext=e.userContext);let p=e?.exposePatterns,m=e?.sapClient??t.sapClient;Ur(n),Mr(n),Ts(n,u,{baseUrl:t.baseUrl,sapClient:m,tlsVerify:e?.httpOptions?.tlsVerify}),hs(n,u,p),gs(n,u,{catalogV2Url:e?.catalogV2Url,catalogV4Url:e?.catalogV4Url,baseUrl:t.baseUrl,onDiscoveryMatch:e?.onDiscoveryMatch,...e?.concurrency?{concurrency:e.concurrency}:{}},d),Ds(n,u,p,l),bs(n,u,l,e?.readOnly??false),Ps(n,u,p),as(n,u,l),Is(n,u,p,l);let g=e?.idocStore??new Ee,f=new bt(u,m,e?.partnerDefaults);return vs(n,u,f,g),es(n,g),e?.rfcAvailable&&e.rfcPool&&e.logger&&$s(n,e.rfcPool,e.logger),ks(n,s),ts(n,u,l),Qr(n,u,l),rs(n),s.applyInitialVisibility(),{server:n,registry:s,httpClient:u}}var Xc=z$1.object({snc_qop:z$1.enum(["1","2","3","8","9"]).optional(),snc_myname:z$1.string().min(1).optional(),snc_partnername:z$1.string().min(1).optional(),snc_lib:z$1.string().min(1).optional()}).superRefine((t,e)=>{let n=[t.snc_qop,t.snc_myname,t.snc_partnername,t.snc_lib].filter(r=>r!==void 0).length;if(n>0&&n<4){let r=[];t.snc_qop||r.push("SAP_SNC_QOP"),t.snc_myname||r.push("SAP_SNC_MYNAME"),t.snc_partnername||r.push("SAP_SNC_PARTNERNAME"),t.snc_lib||r.push("SAP_SNC_LIB"),e.addIssue({code:"custom",message:`SNC config incomplete: missing ${r.join(", ")} (D-12 all-or-none)`,path:["snc_qop"]});}});function _n(t=process.env){let e=t.SAP_USERNAME,n=t.SAP_PASSWORD;if(!e||!n)return null;let r=!!(t.SAP_RFC_ASHOST&&t.SAP_RFC_SYSNR),s=!!(t.SAP_RFC_MSHOST&&t.SAP_RFC_GROUP);if(!r&&!s)return null;let i={user:e,passwd:n,client:t.SAP_CLIENT,lang:t.SAP_RFC_LANG??"EN"};r&&(i.ashost=t.SAP_RFC_ASHOST,i.sysnr=t.SAP_RFC_SYSNR),s&&(i.mshost=t.SAP_RFC_MSHOST,i.msserv=t.SAP_RFC_MSSERV,i.group=t.SAP_RFC_GROUP);let o=Xc.safeParse({snc_qop:t.SAP_SNC_QOP,snc_myname:t.SAP_SNC_MYNAME,snc_partnername:t.SAP_SNC_PARTNERNAME,snc_lib:t.SAP_SNC_LIB});if(!o.success){let a=o.error.issues.map(d=>d.message).join("; ");throw new Error(`Invalid SNC configuration: ${a}`)}return o.data.snc_qop&&Object.assign(i,o.data),i}function kn(t=process.env){try{return _n(t)!==null}catch{return false}}var xt=class{constructor(e={}){this.options=e;this.discovery=e.discoveryClient??new re;}discovery;jwks;jwksIssuer;get verifies(){return !!this.options.issuerUrl}async verify(e){let n=e.authorization??e.Authorization;if(typeof n!="string"||!n.startsWith("Bearer "))return {kind:"none"};let r=n.slice(7).trim();if(!r)return {kind:"none"};if(!this.options.issuerUrl)return this.withoutVerification(r);try{let s=await this.getKeySet(this.options.issuerUrl),{payload:i}=await jwtVerify(r,s,{issuer:void 0,...this.options.audience?{audience:this.options.audience}:{}});return typeof i.sub!="string"?{kind:"rejected",reason:"Token ohne sub-Claim"}:{kind:"user",context:{token:r,sub:i.sub,email:typeof i.email=="string"?i.email:void 0,name:typeof i.name=="string"?i.name:void 0,verified:!0}}}catch(s){return td(r)?{kind:"rejected",reason:s instanceof Error?s.message:"Signaturpr\xFCfung fehlgeschlagen"}:{kind:"none"}}}withoutVerification(e){try{let n=decodeJwt(e);return typeof n.sub!="string"?{kind:"none"}:{kind:"user",context:{token:e,sub:n.sub,email:typeof n.email=="string"?n.email:void 0,name:typeof n.name=="string"?n.name:void 0,verified:!1}}}catch{return {kind:"none"}}}async getKeySet(e){if(this.jwks&&this.jwksIssuer===e)return this.jwks;let n=await this.discovery.discover(e);return this.jwks=createRemoteJWKSet(new URL(n.jwks_uri)),this.jwksIssuer=e,this.jwks}};function td(t){let e=t.split(".");return e.length===3&&e.every(n=>n.length>0)}var nd="x-sap-username",rd="x-sap-password",sd="x-sap-authorization";function On(t,e){let n=t[e]??t[e.toLowerCase()],r=Array.isArray(n)?n[0]:n;return typeof r=="string"&&r.length>0?r:void 0}function id(t){let e=On(t,nd),n=On(t,rd);if(e&&n)return {username:e,password:n};let r=On(t,sd),s=r?/^Basic\s+(.+)$/i.exec(r.trim()):null;if(s?.[1]){let i=Buffer.from(s[1].trim(),"base64").toString("utf8"),o=i.indexOf(":");if(o>0&&o<i.length-1)return {username:i.slice(0,o),password:i.slice(o+1)}}}function In(t,e){let n=id(e);if(t.authType==="user-basic"){if(!n)return {kind:"missing",hint:`Destination "${t.name}" verlangt eine pers\xF6nliche SAP-Anmeldung: sende X-SAP-Username und X-SAP-Password (oder X-SAP-Authorization: Basic <base64 user:pass>) mit \u2014 im n8n-Node \u201EGuniWeb SAP" sind das die Felder SAP-Benutzer/SAP-Passwort des Credentials.`};let{name:r,baseUrl:s,sapClient:i}=t;return {kind:"ok",config:{name:r,baseUrl:s,authType:"basic",...i!==void 0?{sapClient:i}:{},username:n.username,password:n.password},username:n.username}}return n?{kind:"rejected",reason:`Destination "${t.name}" (authType ${t.authType}) nimmt keine pers\xF6nliche SAP-Anmeldung an \u2014 die Anfrage w\xFCrde unter dem konfigurierten technischen Zugang laufen. Lass X-SAP-Username/X-SAP-Password weg oder stelle die Destination auf authType user-basic um.`}:{kind:"ok",config:t}}var od=z$2.object({destination:z$2.string().min(1),label:z$2.string().max(200).optional(),readOnly:z$2.boolean().optional(),tiers:z$2.array(z$2.string()).optional()}).strict();function le(t,e,n){t.setHeader("Cache-Control","no-store"),t.status(e).json(n);}function Bs(t){return t.ip??t.socket?.remoteAddress??"unknown"}function Ls(t,e){let n=t?.[e];return n==="true"||n==="1"||n===""}function Fs(t){t.use("/admin",(e,n)=>{le(n,404,{error:"Admin API not enabled",hint:"Start the server with --admin-token <secret> (or SAP_MCP_ADMIN_TOKEN) and --destinations <file>."});});}function Hs(t,e){let{destinations:n,logger:r}=e,s=new Te({apiKey:e.adminToken,tokens:()=>[]});t.use("/admin",(d,l,c)=>{let u=d,p=l,m=Bs(u),g=s.authenticate(u.headers.authorization,m);if(g.kind==="rate-limited"){r?.warn({clientAddress:m,path:u.originalUrl??u.path,retryAfterSeconds:g.retryAfterSeconds},"Admin-API: zu viele fehlgeschlagene Anmeldeversuche von dieser Adresse"),p.setHeader("Retry-After",String(g.retryAfterSeconds)),le(p,429,{error:"Too Many Requests",retryAfterSeconds:g.retryAfterSeconds});return}if(g.kind!=="api-key"){r?.warn({clientAddress:m,path:u.originalUrl??u.path},"Admin-API: Anfrage abgewiesen"),le(p,401,{error:"Unauthorized"});return}c();});let i=()=>{if(n.source!=="file"||!n.filePath)throw new C("Admin API requires a destinations file: start the server with --destinations <file>.",409);return new we({filePath:n.filePath,env:e.env})},o=(d,l)=>(c,u)=>{let p=c,m=u,g=Bs(p);(async()=>{try{let{status:f,body:h,changed:b}=l(p);if(b){let S=await n.reload();h.reloaded=S.ok,S.ok||(h.reloadErrors=S.errors),r?.info({action:d,clientAddress:g,target:p.params?.name??p.params?.ref??p.body?.destination,reloaded:S.ok},"Admin-API: \xC4nderung ausgef\xFChrt");}le(m,f,h);}catch(f){if(f instanceof C){le(m,f.status,{error:f.message});return}r?.warn({action:d,clientAddress:g,err:f instanceof Error?f.message:String(f)},"Admin-API: unerwarteter Fehler"),le(m,500,{error:"Internal Server Error"});}})();},a=d=>{let l=d.body;if(l===null||typeof l!="object"||Array.isArray(l))throw new C("Request body must be a JSON object (Content-Type: application/json).",400);return l};t.get("/admin/destinations",o("destinations.list",()=>({status:200,body:{destinations:i().listDestinations()},changed:!1}))),t.put("/admin/destinations/:name",o("destinations.put",d=>{let l=d.params?.name??"",c=a(d),{replaced:u,warnings:p}=i().addDestination(l,c,{replace:!0,allowMissingEnv:Ls(d.query,"allowMissingEnv")});return {status:u?200:201,body:{name:l,replaced:u,warnings:p},changed:!0}})),t.delete("/admin/destinations/:name",o("destinations.delete",d=>{let l=d.params?.name??"",{removedTokens:c}=i().removeDestination(l,{force:Ls(d.query,"force")});return {status:200,body:{name:l,removedTokens:c},changed:!0}})),t.get("/admin/tokens",o("tokens.list",()=>({status:200,body:{tokens:i().listTokens()},changed:!1}))),t.post("/admin/tokens",o("tokens.issue",d=>{let l=od.safeParse(a(d));if(!l.success)throw new C(`Invalid body: ${l.error.issues.map(f=>`${f.path.join(".")||"(root)"}: ${f.message}`).join("; ")}`,400);let{destination:c,...u}=l.data,{token:p,hash:m,entry:g}=i().issueToken(c,u);return {status:201,body:{token:p,hash:m.replace(/^sha256:/,"").slice(0,12),destination:g.destination,label:g.label,policy:g.policy,createdAt:g.createdAt},changed:!0}})),t.delete("/admin/tokens/:ref",o("tokens.revoke",d=>{let l=d.params?.ref??"",c=i().revokeToken(l);return {status:200,body:{...c,revoked:!0},changed:!c.alreadyRevoked}})),t.use("/admin",(d,l)=>{le(l,404,{error:"Not Found"});});}var ld=16*1024*1024;function ud(t){let e=js();return e.use(js.json({limit:t})),e}function Vs(t,e){return e?.kind==="token"?t.destinations?.get(e.token.destination):t.destinations?t.destinations.defaultDestination():t.config}function Ws(t,e){if(e?.kind!=="token"||!e.token.policy)return t;let n=e.token.policy;return {...t,readOnly:t.readOnly===true||n.readOnly===true,...n.tiers?{allowedTiers:[...n.tiers]}:{}}}function qs(t,e){t.writeHead(503,{"Content-Type":"application/json"});let n=e?.names().length??0;t.end(JSON.stringify({error:"Keine Destination bestimmbar",hint:n===0?'Es ist noch keine Destination konfiguriert. Lege eine an \u2014 per Admin-API (PUT /admin/destinations/<name>) oder CLI (guniweb-sap-mcp destinations add) \u2014 und ein Token daf\xFCr, oder nenne sie "default".':'Es sind mehrere Destinations konfiguriert, aber keine hei\xDFt "default". Benenne eine Destination "default", lege Tokens an oder reduziere die Datei auf ein Ziel.'}));}var Dn=class t{constructor(e){this.persistDir=e;}entries=new Map;static MAX_USER_SCOPES=100;for(e,n){let r=`${e.name}\0${e.baseUrl}\0${n??""}`,s=this.entries.get(r);if(!s){for(let o of this.entries.keys())o.startsWith(`${e.name}\0`)&&!o.startsWith(`${e.name}\0${e.baseUrl}\0`)&&this.entries.delete(o);n!==void 0&&this.evictOldestUserScopes(e);let i={baseUrl:e.baseUrl,...this.persistDir&&n===void 0?{persistDir:this.persistDir}:{}};s={catalog:new X(i),metadata:new O(i)},this.entries.set(r,s);}return s}evictOldestUserScopes(e){let n=`${e.name}\0${e.baseUrl}\0`,r=[...this.entries.keys()].filter(s=>s.startsWith(n)&&s!==n);for(;r.length>=t.MAX_USER_SCOPES;)this.entries.delete(r.shift());}};function Ks(t,e){if(e.kind==="missing"){t.writeHead(401,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:"SAP login required",hint:e.hint}));return}t.writeHead(400,{"Content-Type":"application/json"}),t.end(JSON.stringify({error:e.reason}));}function zs(t,e){let n=t.logger;return !e||!n||typeof n.child!="function"?t:{...t,logger:n.child({sapUser:e})}}async function Gs(t){let e=new xt({...t.jwtIssuer?{issuerUrl:t.jwtIssuer}:{},...t.jwtAudience?{audience:t.jwtAudience}:{}}),n=t.maxRequestBytes??ld,r=ud(n),s=new Te({apiKey:t.apiKey,tokens:()=>t.destinations?.tokens()??[]}),i=t.serverOptions?.logger;r.use((d,l,c)=>{let u=d,p=l,m=c;if(u.path==="/healthz"){m();return}if(u.path==="/admin"||u.path.startsWith("/admin/")){m();return}if(t.webhookSecret&&u.path==="/idoc"){m();return}let g=u.ip??u.socket?.remoteAddress??"unknown",f=s.authenticate(u.headers.authorization,g);if(f.kind==="rate-limited"){i?.warn({clientAddress:g,path:u.path,retryAfterSeconds:f.retryAfterSeconds},"MCP-Anfrage abgewiesen: zu viele fehlgeschlagene Anmeldeversuche von dieser Adresse"),p.setHeader("Retry-After",String(f.retryAfterSeconds)),p.status(429).json({error:"Too Many Requests",retryAfterSeconds:f.retryAfterSeconds});return}if(f.kind==="rejected"){i?.warn({clientAddress:g,path:u.path,reason:f.reason},"MCP-Anfrage abgewiesen"),p.status(401).json({error:"Unauthorized"});return}u.sapAuth=f,m();}),r.get("/healthz",(d,l)=>{l.status(200).json({status:"ok"});}),t.adminToken&&t.destinations?Hs(r,{adminToken:t.adminToken,destinations:t.destinations,env:t.env??process.env,logger:i}):Fs(r),t.transport==="http"?pd(r,t,e):md(r,t,e);let o=ht(t.idocStore,{secret:t.webhookSecret,maxBodyBytes:t.webhookMaxBodyBytes});r.post("/idoc",(d,l)=>{o(d,l);}),r.use((d,l,c,u)=>{let p=d,m=c;if(p?.type==="entity.parse.failed"&&!m.headersSent){m.status(400).json({error:`Invalid JSON body: ${p.message??"parse error"}`});return}if(p?.type==="entity.too.large"&&!m.headersSent){let g=(n/1024/1024).toFixed(1);m.status(413).json({error:`Anfrage zu gro\xDF: die Grenze liegt bei ${n} Bytes (${g} MiB). Bei einem base64-kodierten Anhang sind das rund ${(n*3/4/1024/1024).toFixed(1)} MiB Datei. Mit --max-request-bytes anheben.`});return}u(d);});let a=createServer(r);return new Promise(d=>{a.listen(t.port,()=>{let l=a.address(),c=typeof l=="object"&&l!==null?l.port:t.port;d({port:c,close:()=>new Promise((u,p)=>{a.close(m=>m?p(m):u());})});});})}function pd(t,e,n){let r=new Dn(e.serverOptions?.cacheDir),s=e.serverOptions?.logger;t.all("/mcp",(i,o)=>{let a=i,d=o,l=Vs(e,a.sapAuth);if(!l){qs(d,e.destinations);return}let c=In(l,a.headers);if(c.kind!=="ok"){s?.warn({destination:l.name,clientAddress:a.socket?.remoteAddress,reason:c.kind},c.kind==="missing"?"MCP-Anfrage ohne pers\xF6nliche SAP-Anmeldung an eine user-basic-Destination abgewiesen":"MCP-Anfrage mit pers\xF6nlicher SAP-Anmeldung an eine Destination mit technischem Benutzer abgewiesen"),Ks(d,c);return}let u=c.config,p=c.username,m=new StreamableHTTPServerTransport({sessionIdGenerator:void 0}),g=f=>{let{server:h}=Ye(u,{...Ws(zs(e.serverOptions,p),a.sapAuth),sapClient:u.sapClient,userContext:f,caches:r.for(u,p)});h.connect(m).then(()=>m.handleRequest(a,d,a.body)).catch(()=>{d.headersSent||(d.writeHead(500),d.end("Internal Server Error"));});};n.verify(a.headers).then(f=>{if(f.kind==="rejected"){d.writeHead(401,{"Content-Type":"application/json"}),d.end(JSON.stringify({error:"Unauthorized",reason:f.reason,hint:"Das mitgeschickte Bearer-Token hat die Signaturpr\xFCfung gegen SAP_JWT_ISSUER nicht bestanden."}));return}let h=f.kind==="user"?f.context:null;g(h);});});}function md(t,e,n){let r=new Map;t.get("/sse",(s,i)=>{let o=s,a=i,d=Vs(e,o.sapAuth);if(!d){qs(a,e.destinations);return}let l=In(d,o.headers??{});if(l.kind!=="ok"){Ks(a,l);return}let c=l.config,u=new SSEServerTransport("/message",a),{server:p,httpClient:m}=Ye(c,{...Ws(zs(e.serverOptions,l.username),o.sapAuth),sapClient:c.sapClient});p.connect(u).then(()=>u.start()).then(()=>{r.set(u.sessionId,{transport:u,server:p,httpClient:m}),u.onclose=()=>{r.delete(u.sessionId);};}).catch(()=>{a.headersSent||(a.writeHead(500),a.end("Internal Server Error"));});}),t.post("/message",(s,i)=>{let o=s,a=i,l=new URL(o.url??"",`http://${o.headers.host??"localhost"}`).searchParams.get("sessionId")??o.query?.sessionId;if(!l||!r.has(l)){a.writeHead(400,{"Content-Type":"application/json"}),a.end(JSON.stringify({error:"Invalid or missing session ID"}));return}let c=r.get(l);n.verify(o.headers).then(u=>{if(u.kind==="rejected"){a.writeHead(401,{"Content-Type":"application/json"}),a.end(JSON.stringify({error:"Unauthorized",reason:u.reason}));return}c.httpClient.userContext=u.kind==="user"?u.context:null,c.transport.handlePostMessage(o,a,o.body).catch(()=>{a.headersSent||(a.writeHead(500),a.end("Internal Server Error"));});});});}var yd=`Usage: guniweb-sap-mcp [options]
|
|
335
338
|
guniweb-sap-mcp destinations list|add|remove ... (manage destinations.json)
|
|
336
339
|
guniweb-sap-mcp tokens issue|revoke|list ... (see: guniweb-sap-mcp tokens --help)
|
|
337
340
|
|
|
@@ -398,5 +401,5 @@ Configuration via environment variables: SAP_BASE_URL, SAP_AUTH_TYPE
|
|
|
398
401
|
|
|
399
402
|
Documentation, releases, issues: https://github.com/guniweb/guniweb-sap-mcp
|
|
400
403
|
Support: support@guniweb.de \u2014 https://guniweb.de/sap-mcp
|
|
401
|
-
`;async function
|
|
404
|
+
`;async function Sd(){let t=process.argv.slice(2);if(t[0]!==void 0&&gr.has(t[0])){let y=await hr(t,{env:process.env,stdout:w=>process.stdout.write(w),stderr:w=>process.stderr.write(w)});process.exit(y);}let e=xn();e.help&&(process.stdout.write(yd),process.exit(0));let n=e.transport??"stdio",r=!!e.adminToken;if(r&&!e.destinationsFile)throw new Error("--admin-token / SAP_MCP_ADMIN_TOKEN braucht --destinations <path> (bzw. SAP_MCP_DESTINATIONS): die Admin-API verwaltet diese Datei.");if(r&&n==="stdio")throw new Error("--admin-token / SAP_MCP_ADMIN_TOKEN wirkt nur im HTTP-Betrieb (--transport http|sse): im stdio-Betrieb gibt es keinen /admin-Endpunkt.");let s;if(e.demo){let y=["SAP_BASE_URL","SAP_AUTH_TYPE"].filter(w=>process.env[w]);if(e.destinationsFile||y.length>0)throw new Error(`--demo und eine echte SAP-Konfiguration schlie\xDFen sich aus (gefunden: ${[...e.destinationsFile?["--destinations/SAP_MCP_DESTINATIONS"]:[],...y].join(", ")}). F\xFCr die Demo alle SAP_*-Variablen und --destinations weglassen.`);s=await ir();}let i=[],o=s?ve.fromEnv(bd(s.baseUrl)):e.destinationsFile?await ve.fromFile(e.destinationsFile,{env:process.env,onWarn:y=>i.push(y),allowMissing:r}):ve.fromEnv(process.env),a=o.defaultDestination(),d=o.tokens().length;if(!a&&(n==="stdio"||d===0)&&!r)throw new Error(`${o.filePath} definiert ${o.names().length} Destinations (${o.names().join(", ")}), aber keine hei\xDFt "default"${n==="stdio"?' \u2014 im stdio-Betrieb gibt es genau eine Verbindung, also muss eine Destination "default" hei\xDFen oder die Datei genau ein Ziel enthalten.':' und es sind keine Tokens konfiguriert, die eine Anfrage einem Ziel zuordnen k\xF6nnten. Benenne eine Destination "default" oder lege Tokens an.'}`);let l=qt(process.env),c=$i({redact:ot,transport:{target:"pino/file",options:{destination:2}}});l.tlsVerify===false&&c.warn({tlsVerify:false},"SAP_TLS_VERIFY=false: TLS certificate validation disabled. ONLY use for development/test against self-signed certs. Production deployments MUST use SAP_TLS_VERIFY=true (or unset)."),l.clientCert&&c.info({hasCert:true,hasPassphrase:!!l.clientKeyPassphrase},"X.509 client certificate enabled for OData transport"),c.info(a?{baseUrl:a.baseUrl,authType:a.authType}:{destinations:o.names(),tokens:d},"SAP MCP server starting"),s&&c.warn({demo:true,mockSapUrl:s.baseUrl},`DEMO-MODUS: eingebautes Mock-SAP auf ${s.baseUrl} \u2014 Beispieldaten im Speicher (Gesch\xE4ftspartner, Kundenauftr\xE4ge, Produkte), keine echten SAP-Daten, keine Verbindung nach au\xDFen. \xC4nderungen gehen mit dem Prozess verloren.`);for(let y of i)c.warn(y);if(r&&!a&&d===0&&c.warn({file:o.filePath,destinations:o.names()},'Keine Standard-Destination und keine Tokens: /mcp antwortet 503, bis \xFCber die Admin-API (/admin) eine Destination "default" oder ein Token angelegt ist.'),o.source==="file"?(c.info({file:o.filePath,destinations:o.names(),defaultDestination:a?.name??null,tokens:d},"Destinations aus Datei geladen"),o.onChange(y=>{c.info({version:y.version,destinations:y.names,tokens:y.tokenCount},"Destinations neu geladen");}),o.onError(y=>{c.warn({errors:y},"Destinations-Datei ung\xFCltig \u2014 die letzte g\xFCltige Konfiguration bleibt in Kraft");})):c.info({destinations:o.names()},'Destination "default" aus SAP_*-Umgebung (keine destinations.json konfiguriert)'),e.allowWriteExplicit){let y=is({tiers:e.tiers,hasIdocConfig:Wt(e),hasRfcConfig:kn(process.env)});c.warn({writeTools:y},`Schreibwerkzeuge sind aktiv (--allow-write): ${y.join(", ")} k\xF6nnen Daten im SAP-System ver\xE4ndern. sap_function ist nicht darunter: dort entscheiden die Metadaten des Dienstes je Aufruf, ob er lesend ist.`);}else c.info("Nur-Lese-Betrieb (Standard seit 1.0). Schreibwerkzeuge sind nicht registriert; mit --allow-write bzw. SAP_MCP_ALLOW_WRITE=true aktivieren.");for(let y of o.names())qn(o.names().length>1?c.child({destination:y}):c,o.get(y)?.sapClient);e.transport&&e.transport!=="stdio"&&(process.env.SAP_JWT_ISSUER?c.info({issuer:process.env.SAP_JWT_ISSUER},"Eingehende Benutzer-Token werden gegen die JWKS des Issuers gepr\xFCft"):c.warn("SAP_JWT_ISSUER ist nicht gesetzt: eingehende Bearer-Token werden nur dekodiert, nicht auf ihre Signatur gepr\xFCft. F\xFCr Principal Propagation an SAP ist das vertretbar (SAP pr\xFCft selbst); eine lokale Entscheidung darf sich auf die Identit\xE4t aber nicht st\xFCtzen.")),e.expose&&c.info({exposePatterns:e.expose},"Entity set filtering active");let u=await xs(c),p;if(u){let y=_n(process.env);if(y){y.snc_lib&&!existsSync(y.snc_lib)&&c.warn({path:y.snc_lib},"SAP_SNC_LIB path does not exist on this host: SNC will fail at first RFC connect. Verify SAP NW RFC SDK + Cryptographic Library installation."),p=new Nt(y,c);let w=!!y.snc_qop;c.info({ashost:y.ashost,mshost:y.mshost,snc:w,...w?{qop:y.snc_qop,myname:y.snc_myname}:{}},w?"RFC pool ready (SNC-secured)":"RFC pool ready");}else c.info("node-rfc loaded but no SAP_RFC_* config \u2014 RFC tier requires sap_enable_tools rfc + env vars to activate");}let m=new Ee,g={exposePatterns:e.expose,idocStore:m,sapClient:a?.sapClient,partnerDefaults:{sndpor:e.sndpor,sndprt:e.sndprt,sndprn:e.sndprn,rcvpor:e.rcvpor,rcvprt:e.rcvprt,rcvprn:e.rcvprn},catalogV2Url:e.catalogV2Url,catalogV4Url:e.catalogV4Url,concurrency:e.concurrency,httpOptions:l,onDiscoveryMatch:y=>{c.info({path:y,attemptedPaths:3},"V2 catalog auto-discovery: matched path");}},f=e.toolTimeoutSeconds??Tt;f===0?c.warn("Zeitbudget je Tool-Aufruf abgeschaltet (--tool-timeout 0): ein Aufruf kann unbegrenzt laufen und die Client-Verbindung bis zu deren eigenem Timeout blockieren."):c.info({toolTimeoutSeconds:f},"Zeitbudget je Tool-Aufruf aktiv"),e.cacheDir?c.info({cacheDir:e.cacheDir},"Cache-Ablage aktiv (\xFCberlebt Neustarts)"):c.info("Kein Cache-Verzeichnis konfiguriert (--cache-dir / SAP_CACHE_DIR): Katalog und Metadaten werden nach jedem Neustart erneut aus SAP geholt.");let h={...g,toolTimeoutMs:f*1e3,cacheDir:e.cacheDir,readOnly:e.readOnly,tiers:e.tiers,hasIdocConfig:Wt(e),hasRfcConfig:kn(process.env),rfcAvailable:u,rfcPool:p,logger:c},b;if(n==="stdio"){if(!a)throw new Error("stdio-Betrieb ohne Standard-Destination.");if(a.authType==="user-basic")throw new Error("authType user-basic (pers\xF6nliche SAP-Anmeldung je Anfrage) gibt es nur im HTTP-Betrieb: stdio hat keinen Header, \xFCber den SAP-Benutzer und -Passwort mitkommen k\xF6nnten. Starte mit --transport http oder verwende authType basic.");let{server:y}=Ye(a,h),w=new StdioServerTransport;if(await y.connect(w),c.info("SAP MCP server connected on stdio transport"),e.webhookPort){e.webhookSecret||c.warn({port:e.webhookPort},"IDoc webhook has NO authentication: anyone reaching this port can inject IDocs. Set --webhook-secret (or SAP_WEBHOOK_SECRET) for production.");let N=await wr(e.webhookPort,m,{host:e.webhookHost,secret:e.webhookSecret,maxBodyBytes:e.webhookMaxBodyBytes});c.info({port:N.port,auth:e.webhookSecret?"secret":"open"},"IDoc webhook server listening");}}else {let y=e.port??8808,w=!!e.apiKey||d>0;!e.webhookSecret&&!w&&c.warn({port:y},"IDoc webhook (/idoc) and MCP endpoint have NO authentication. Set --api-key (or tokens in destinations.json) and --webhook-secret (or SAP_WEBHOOK_SECRET) for production."),o.watch(),b=await Gs({transport:n,port:y,apiKey:e.apiKey,adminToken:e.adminToken,env:process.env,destinations:o,serverOptions:h,jwtIssuer:process.env.SAP_JWT_ISSUER,jwtAudience:process.env.SAP_JWT_AUDIENCE,idocStore:m,webhookSecret:e.webhookSecret,webhookMaxBodyBytes:e.webhookMaxBodyBytes,maxRequestBytes:e.maxRequestBytes}),c.info({transport:n,port:b.port,auth:[...e.apiKey?["api-key"]:[],...d>0?[`tokens(${d})`]:[]].join("+")||"open",adminApi:e.adminToken?"/admin (admin-token)":"off"},"SAP MCP server listening on HTTP"),e.adminToken&&c.info({file:o.filePath},"Admin-API aktiv unter /admin: Destinations und Tokens per HTTP verwaltbar (nur mit dem Admin-Secret; n8n-Tokens gelten dort nicht).");}for(let y of o.names()){let w=o.get(y);if(!w)continue;let N=o.names().length>1?c.child({destination:y}):c;if(w.authType==="user-basic"){N.info({baseUrl:w.baseUrl,sapClient:w.sapClient??null},"Pers\xF6nliche SAP-Anmeldung je Anfrage (authType user-basic): Selbsttest ohne Benutzer nicht m\xF6glich \u2014 jede Anfrage bringt X-SAP-Username/X-SAP-Password mit, SAP pr\xFCft Passwort und Berechtigungen.");continue}Kn(N,w,l);}let S=async y=>{c.info({signal:y},"Shutting down...");try{o.close(),p&&await p.closeAll(),b&&await b.close(),s&&await s.close();}catch(w){c.error({err:w},"Shutdown error (continuing)");}process.exit(0);};process.on("SIGTERM",()=>S("SIGTERM")),process.on("SIGINT",()=>S("SIGINT"));}function bd(t){let e=sr(t),n=e.authType==="basic"?e:void 0;return {...process.env,SAP_BASE_URL:e.baseUrl,SAP_AUTH_TYPE:e.authType,SAP_USERNAME:n?.username,SAP_PASSWORD:n?.password,SAP_CLIENT:e.sapClient,SAP_TLS_VERIFY:void 0,SAP_CLIENT_CERT:void 0,SAP_CLIENT_KEY:void 0}}Sd().catch(t=>{process.stderr.write(`Fatal error: ${t instanceof Error?t.message:String(t)}
|
|
402
405
|
`),process.exit(1);});
|