guniweb-sap-mcp 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +61 -61
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
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 ui from'pino';import {minimatch}from'minimatch';import {z as z$1}from'zod/v4';import {lookup}from'dns/promises';import {connect}from'tls';import Vt,{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 {createMcpExpressApp}from'@modelcontextprotocol/sdk/server/express.js';import {SSEServerTransport}from'@modelcontextprotocol/sdk/server/sse.js';import {StreamableHTTPServerTransport}from'@modelcontextprotocol/sdk/server/streamableHttp.js';var wn=16;function ke(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 vn(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 Ns(t,e)}function Ns(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 C=Number.parseInt(process.env.SAP_WEBHOOK_MAX_BODY_BYTES,10);Number.isFinite(C)&&C>0&&(e.webhookMaxBodyBytes=C);}let i=t.indexOf("--transport");if(i!==-1&&i<t.length-1){let C=t[i+1];(C==="stdio"||C==="http"||C==="sse")&&(e.transport=C);}let o=t.indexOf("--port");o!==-1&&o<t.length-1&&(e.port=Number.parseInt(t[o+1],10));let a=t.indexOf("--api-key");a!==-1&&a<t.length-1&&(e.apiKey=t[a+1]);let c=["sndpor","sndprt","sndprn","rcvpor","rcvprt","rcvprn"];for(let C of c){let X=t.indexOf(`--${C}`);X!==-1&&X<t.length-1&&(e[C]=t[X+1]);}let l=t.indexOf("--catalog-v2-url");l!==-1&&l<t.length-1&&(e.catalogV2Url=t[l+1]),!e.catalogV2Url&&process.env.ODATA_V2_CATALOG_PATH&&(e.catalogV2Url=process.env.ODATA_V2_CATALOG_PATH);let d=t.indexOf("--catalog-v4-url");d!==-1&&d<t.length-1&&(e.catalogV4Url=t[d+1]);let u=t.includes("--allow-write")||process.env.SAP_MCP_ALLOW_WRITE==="true";e.readOnly=!u,e.allowWriteExplicit=u;let m=t.indexOf("--tool-timeout"),p=m!==-1&&m<t.length-1?t[m+1]:process.env.SAP_MCP_TOOL_TIMEOUT;if(p!==void 0){let C=Number.parseInt(p,10);if(!Number.isFinite(C)||C<0)throw new Error(`--tool-timeout muss eine Zahl in Sekunden sein (0 schaltet das Budget ab), war: "${p}"`);e.toolTimeoutSeconds=C;}let f=t.indexOf("--cache-dir"),g=f!==-1&&f<t.length-1?t[f+1]:process.env.SAP_CACHE_DIR;g&&g.trim().length>0&&(e.cacheDir=g.trim());let h=t.indexOf("--concurrency"),S=h!==-1&&h<t.length-1?t[h+1]:process.env.SAP_CONCURRENCY;if(S!==void 0){let C=Number.parseInt(S,10);if(!Number.isFinite(C)||C<1)throw new Error(`--concurrency muss eine Zahl >= 1 sein, war: "${S}"`);e.concurrency=C;}let T=t.indexOf("--destinations"),y=T!==-1&&T<t.length-1?t[T+1]:process.env.SAP_MCP_DESTINATIONS;y&&y.trim().length>0&&(e.destinationsFile=y.trim());let b=t.indexOf("--admin-token"),D=b!==-1&&b<t.length-1?t[b+1]:process.env.SAP_MCP_ADMIN_TOKEN;if(D&&D.trim().length>0){let C=D.trim();if(C.length<wn)throw new Error(`--admin-token / SAP_MCP_ADMIN_TOKEN muss mindestens ${wn} Zeichen lang sein (z. B. openssl rand -base64 32).`);e.adminToken=C;}(t.includes("--demo")||process.env.SAP_MCP_DEMO==="true")&&(e.demo=true);let ce=t.indexOf("--help"),ze=t.indexOf("-h");(ce!==-1||ze!==-1)&&(e.help=true);let kt=t.indexOf("--tiers");if(kt!==-1&&kt<t.length-1){let C=t[kt+1].split(",").map(X=>X.trim()).filter(X=>X.length>0);e.tiers=C.length>0?C:void 0;}let Ot=t.indexOf("--destination-name");Ot!==-1&&Ot<t.length-1&&(e.destinationName=t[Ot+1]),!e.destinationName&&process.env.SAP_DESTINATION_NAME&&(e.destinationName=process.env.SAP_DESTINATION_NAME);let It=t.indexOf("--destination-service-url");It!==-1&&It<t.length-1&&(e.destinationServiceUrl=t[It+1]),!e.destinationServiceUrl&&process.env.SAP_DESTINATION_SERVICE_URL&&(e.destinationServiceUrl=process.env.SAP_DESTINATION_SERVICE_URL);let Dt=t.indexOf("--issuer");Dt!==-1&&Dt<t.length-1&&(e.issuer=t[Dt+1]),!e.issuer&&process.env.SAP_ISSUER&&(e.issuer=process.env.SAP_ISSUER);let Nt=t.indexOf("--name-id-format");Nt!==-1&&Nt<t.length-1&&(e.nameIdFormat=t[Nt+1]),!e.nameIdFormat&&process.env.SAP_NAME_ID_FORMAT&&(e.nameIdFormat=process.env.SAP_NAME_ID_FORMAT);let xt=t.indexOf("--private-key"),ks=xt!==-1&&xt<t.length-1?t[xt+1]:void 0;e.privateKey=ke(ks,"SAP_PRIVATE_KEY","SAP_PRIVATE_KEY_FILE");let Mt=t.indexOf("--certificate"),Os=Mt!==-1&&Mt<t.length-1?t[Mt+1]:void 0;e.certificate=ke(Os,"SAP_CERTIFICATE","SAP_CERTIFICATE_FILE");let Ut=t.indexOf("--clock-skew");Ut!==-1&&Ut<t.length-1&&(e.clockSkew=Number.parseInt(t[Ut+1],10)),e.clockSkew===void 0&&process.env.SAP_CLOCK_SKEW&&(e.clockSkew=Number.parseInt(process.env.SAP_CLOCK_SKEW,10));let $t=t.indexOf("--assertion-lifetime");return $t!==-1&&$t<t.length-1&&(e.assertionLifetime=Number.parseInt(t[$t+1],10)),e.assertionLifetime===void 0&&process.env.SAP_ASSERTION_LIFETIME&&(e.assertionLifetime=Number.parseInt(process.env.SAP_ASSERTION_LIFETIME,10)),e}function En(t){return !!(t.sndprn&&t.rcvprn)}function J(t,e){return !e||e.length===0?true:e.some(n=>minimatch(t,n))}var Cn=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 Lt(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=ke(void 0,"SAP_CLIENT_CERT","SAP_CLIENT_CERT_PATH",t),s=ke(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}:{}},c=Cn.safeParse(a);if(!c.success){let l=c.error.issues.map(d=>` - ${d.path.join(".")||"root"}: ${d.message}`).join(`
|
|
3
3
|
`);throw new Error(`Invalid HTTP options:
|
|
4
|
-
${l}`)}return c.data}function ze(n){let e={name:n.name,url:n.baseUrl,sapClient:n.sapClient};switch(n.authType){case "basic":return {...e,auth:{type:"basic",username:n.username,password:n.password}};case "user-basic":throw new Error(`Destination "${n.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:n.clientId,clientSecret:n.clientSecret,tokenServiceUrl:n.tokenServiceUrl}};case "apikey":return {...e,headers:{APIKey:n.apiKey},auth:{type:"apikey",apiKey:n.apiKey}};case "ias":return {...e,auth:{type:"ias",clientId:n.clientId,clientSecret:n.clientSecret,tenantUrl:n.tenantUrl}};case "xsuaa":return {...e,auth:{type:"xsuaa",clientId:n.clientId,clientSecret:n.clientSecret,xsuaaUrl:n.xsuaaUrl}};case "btp-principal":return {...e,auth:{type:"btp-principal",destinationName:n.destinationName,destinationServiceUrl:n.destinationServiceUrl,clientId:n.clientId,clientSecret:n.clientSecret,tokenServiceUrl:n.tokenServiceUrl}};case "saml-bearer":return {...e,auth:{type:"saml-bearer",tokenServiceUrl:n.tokenServiceUrl,clientId:n.clientId,issuer:n.issuer,nameIdFormat:n.nameIdFormat,privateKey:n.privateKey,certificate:n.certificate,clockSkew:n.clockSkew,assertionLifetime:n.assertionLifetime}}}}var ce=new AsyncLocalStorage;function En(){return randomBytes(6).toString("hex")}function de(){return ce.getStore()}function Cn(n,e){return ce.run(n,e)}function Ye(){let n=ce.getStore();if(n)return n.sapCalls.count+=1,n.correlationId}function Pn(n=Date.now()){let e=ce.getStore();if(e?.deadline)return Math.max(0,e.deadline-n)}function Xe(){return ce.getStore()?.signal?.aborted??false}var $=class extends Error{timedOut;constructor(e,t){super(e?`Zeitbudget f\xFCr ${t??"den Tool-Aufruf"} \xFCberschritten \u2014 abgebrochen.`:`Client hat die Verbindung verworfen \u2014 ${t??"der Tool-Aufruf"} wurde abgebrochen.`),this.name="ToolCallAbortedError",this.timedOut=e;}};function Je(){let n=ce.getStore();if(!n?.signal?.aborted)return;let e=n.signal.reason?.name==="TimeoutError";throw new $(e,n.toolName)}function Rn(n,e){if(!e||e<=0)return {signal:n,deadline:void 0};let t=AbortSignal.timeout(e),r=Date.now()+e;return n?{signal:AbortSignal.any([n,t]),deadline:r}:{signal:t,deadline:r}}function le(n){if(n==null)return {code:"UNKNOWN",message:"Unknown SAP error",details:[],rawResponse:n};if(typeof n=="string")return {code:"UNKNOWN",message:n,details:[],rawResponse:n};let t=n.error;if(!t)return {code:"UNKNOWN",message:"Unknown SAP error",details:[],rawResponse:n};let r=t.message,s;r&&typeof r=="object"&&"value"in r?s=r.value??"Unknown error":typeof r=="string"?s=r:s="Unknown error";let a=(t.innererror?.errordetails??[]).map(c=>({code:c.code??"",message:c.message??"",severity:c.severity??"error",target:c.target}));return {code:t.code??"UNKNOWN",message:s,target:t.target,details:a,rawResponse:n}}function Lt(n){if(typeof n!="string")return false;let e=n.trimStart().slice(0,200).toLowerCase();return e.startsWith("<!doctype")||e.startsWith("<html")||e.includes("<title")}function Bt(n){let e=/<title[^>]*>([^<]{1,200})<\/title>/i.exec(n)?.[1]?.trim();return e||n.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 Ms(n){return /\/IW[A-Z]+\/[A-Z_]+\/\d+/i.exec(n)?.[0]}var Us=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 $s(n){let e=n?.code;if(typeof e=="string")return e;let t=n?.cause;if(typeof t?.code=="string")return t.code}function Ls(n){let t=(n?.cause?.cert??n?.cert)?.issuer?.CN;return typeof t=="string"?t:void 0}function ue(n,e){if(n instanceof $)return {code:n.timedOut?"TOOL_TIMEOUT":"ABORTED",message:n.message};let t=$s(n);if(t&&Us.has(t)){let i=Ls(n);return {code:"TLS_CHAIN_INCOMPLETE",message:i?`Die Zertifikatskette ist nicht vollst\xE4ndig verifizierbar (${t}). Aussteller: ${i}.`:`Die Zertifikatskette ist nicht vollst\xE4ndig verifizierbar (${t}).`,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(t==="ENOTFOUND"||t==="EAI_AGAIN")return {code:"DNS_FAILED",message:`Der SAP-Host ist nicht aufl\xF6sbar (${t}).`,guidance:"SAP_BASE_URL pr\xFCfen. Im Container zus\xE4tzlich die DNS-Konfiguration."};if(t==="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(t==="ECONNABORTED"||t==="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(n)&&n.response)return Bs(n.response.status,n.response.data,e);let r=n;if(typeof r?.message=="string"&&r.message.length>0)return Lt(r.message)?{code:"HTML_RESPONSE",message:`SAP hat eine HTML-Seite statt Daten geliefert: ${Bt(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(n instanceof Error)return Lt(n.message)?{code:"HTML_RESPONSE",message:`SAP hat eine HTML-Seite statt Daten geliefert: ${Bt(n.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:n.message};if(typeof n=="string")return {code:"ERROR",message:n};let s=n===void 0?void 0:JSON.stringify(n);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 Bs(n,e,t){let r=typeof e=="string"?e:JSON.stringify(e??""),s=Ms(r);if(s?.toUpperCase().includes("/IWFND/MED/170")){let o=t?.serviceUrl?`Service ${t.serviceUrl}`:"Der Service";return {code:"SERVICE_NOT_REGISTERED",httpStatus:n,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(n===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(n===403)return {code:"FORBIDDEN",httpStatus:403,...s?{sapCode:s}:{},message:Fs(e)??"Zugriff verweigert.",guidance:"Berechtigungen in PFCG pr\xFCfen, Trace \xFCber SU53. Bei OData-Services zus\xE4tzlich das Objekt S_SERVICE."};if(n===404)return {code:"NOT_FOUND",httpStatus:404,...s?{sapCode:s}:{},message:t?.serviceUrl?`Unter ${t.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(Lt(e))return {code:"HTML_RESPONSE",httpStatus:n,message:`SAP hat eine HTML-Fehlerseite geliefert (HTTP ${n}): ${Bt(e)}`,guidance:"Der Pfad wird von SAP nicht als OData-Service beantwortet. Bei Katalogpfaden sap_discover_services statt sap_query verwenden."};let i=le(e);return {code:i.code&&i.code!=="UNKNOWN"?i.code:`HTTP_${n}`,httpStatus:n,...s?{sapCode:s}:{},message:i.message&&i.message!=="Unknown error"&&i.message!=="Unknown SAP error"?i.message:`SAP antwortete mit HTTP ${n} ohne verwertbare Fehlermeldung.`,...i.details.length>0?{guidance:i.details.map(o=>`${o.code}: ${o.message}`).join(" | ")}:{}}}function Fs(n){let e=le(n);if(e.message&&!(e.message==="Unknown error"||e.message==="Unknown SAP error"))return e.message}var Vs=5e3;function _n(n){let e=n?.issuer?.CN??n?.issuer?.O;return Array.isArray(e)?e.join(", ")||void 0:typeof e=="string"?e:void 0}function _(n,e,t,r,s){return {stage:n,status:t,detail:r,durationMs:Date.now()-e,...s?.guidance?{guidance:s.guidance}:{},...s?.info?{info:s.info}:{}}}function Ws(n){try{let e=new URL(n),t=e.protocol==="https:";return {host:e.hostname,port:e.port?Number(e.port):t?443:80,isTls:t}}catch{return}}async function qs(n,e){let t=Date.now();try{let{address:r,family:s}=await e(n);return _("dns",t,"ok",`${n} l\xF6st auf ${r} auf.`,{info:{address:r,family:s}})}catch(r){let s=ue(r);return _("dns",t,"failed",`${n} ist nicht aufl\xF6sbar.`,{guidance:s.guidance??"SAP_BASE_URL pr\xFCfen. Im Container zus\xE4tzlich die DNS-Konfiguration des Docker-Netzes."})}}async function Ks(n,e,t,r,s){let i=Date.now(),o=a=>new Promise((c,l)=>{let d=s({host:n,port:e,rejectUnauthorized:a,servername:n},()=>{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(t),c=_n(a);return _("tls",i,"ok",t?`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:t,...c?{issuer:c}:{},...a?.subject?.CN?{subject:String(a.subject.CN)}:{},...a?.valid_to?{validTo:a.valid_to}:{}}})}catch(a){let c=ue(a);if(c.code==="TLS_CHAIN_INCOMPLETE"){let l;try{let d=await o(!1);l=_n(d);}catch{}return _("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 _("tls",i,"failed",c.message,{guidance:c.guidance})}}async function Gs(n,e){let t=Date.now();try{let r=await n.request({method:"get",url:"/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/",headers:{Accept:"application/json"},params:{$format:"json"}});return [_("auth",t,"ok","Anmeldung am Gateway erfolgreich."),_("client",t,"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}}),_("catalog",t,"ok",`Servicekatalog erreichbar (HTTP ${r.status}).`)]}catch(r){let s=ue(r);return s.httpStatus===401&&!e?[_("auth",t,"ok","Zugangsdaten wurden gesendet."),_("client",t,"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."}),_("catalog",t,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor.")]:s.httpStatus===401?[_("auth",t,"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.`}),_("client",t,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor."),_("catalog",t,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor.")]:[_("auth",t,"ok","Anmeldung am Gateway erfolgreich."),_("client",t,"ok",e?`Mandant ${e} angenommen.`:"Kein Mandant konfiguriert."),_("catalog",t,"failed",s.message,{guidance:s.guidance??"Katalogdienst in /IWFND/MAINT_SERVICE pr\xFCfen. Der Server probiert drei bekannte Katalogpfade durch."})]}}async function Ze(n,e){let t=Date.now(),r=e.budgetMs??Vs,s=e.deps?.lookup??lookup,i=e.deps?.connectTls??connect,o=[],a=Ws(e.baseUrl);if(!a)return o.push(_("dns",t,"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=>_(c,t,"skipped","Nicht gepr\xFCft \u2014 die Basis-URL ist unbrauchbar."))),Qe(o,t);if(o.push(await qs(a.host,s)),o[0].status==="failed")return o.push(...["tls","auth","client","catalog"].map(c=>_(c,t,"skipped","Nicht gepr\xFCft \u2014 der Name war nicht aufl\xF6sbar."))),Qe(o,t);if(a.isTls){let c=Math.max(500,r-(Date.now()-t));if(o.push(await Ks(a.host,a.port,e.tlsVerify!==false,c,i)),o[o.length-1].status==="failed")return o.push(...["auth","client","catalog"].map(l=>_(l,t,"skipped","Nicht gepr\xFCft \u2014 die TLS-Stufe scheiterte zuvor."))),Qe(o,t)}else o.push(_("tls",t,"skipped","Nicht gepr\xFCft \u2014 SAP_BASE_URL verwendet http, nicht https."));return o.push(...await Gs(n,e.sapClient)),Qe(o,t)}function Qe(n,e){let t=n.find(r=>r.status==="failed");return {status:t?"failed":"connected",...t?{failedStage:t.stage}:{},stages:n,durationMs:Date.now()-e,timestamp:new Date().toISOString()}}var pe=class{constructor(e,t,r,s=60){this.tokenUrl=e;this.clientId=t;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 Ht.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:t,expires_in:r}=e.data;return this.cachedToken=t,this.expiresAt=Date.now()+(r-this.bufferSeconds)*1e3,t}};function Ft(n){return createHash("sha256").update(n).digest("hex")}var ke=class{constructor(e=100){this.maxSize=e;}cache=new Map;get(e){let t=this.cache.get(e);return t?Date.now()>=t.expiresAt?(this.cache.delete(e),null):(this.cache.delete(e),this.cache.set(e,t),t.token):null}set(e,t,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:t,expiresAt:r});}},et=class{clientTokenManager;cache;config;constructor(e){this.config=e,this.clientTokenManager=new pe(e.tokenServiceUrl,e.clientId,e.clientSecret),this.cache=new ke;}async getToken(e){if(!e)return this.clientTokenManager.getToken();let t=Ft(e),r=this.cache.get(t);return r||this.exchangeToken(e,t)}async exchangeToken(e,t){let r=await this.clientTokenManager.getToken(),s=`${this.config.destinationServiceUrl}/destination-configuration/v1/destinations/${this.config.destinationName}`,o=(await Ht.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(t,a.value,d),a.value}};var Oe=class{constructor(e,t){this.config=e;this.discoveryClient=t;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),t=await Ht.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}=t.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,t,r){this.jwks||(this.jwks=createRemoteJWKSet(new URL(t)));let{payload:s}=await jwtVerify(e,this.jwks,{issuer:r});return s}};var Z=class{constructor(e=36e5){this.ttlMs=e;}cache=new Map;async discover(e){let t=e.replace(/\/+$/,""),r=this.cache.get(t);if(r&&Date.now()<r.expiresAt)return r.document;let s=`${t}/.well-known/openid-configuration`,o=(await Ht.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(t,{document:o,expiresAt:Date.now()+this.ttlMs}),o}clearCache(){this.cache.clear();}};var ii={email:"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",sapUsername:"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"};function kn(n,e){if(!n.includes("-----BEGIN")||!n.includes("-----END"))throw new Error(`Invalid PEM format for ${e}. Expected PEM-encoded content starting with -----BEGIN`)}function oi(n){return `<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="${n.assertionId}" IssueInstant="${n.authnInstant}" Version="2.0"><saml:Issuer>${n.issuer}</saml:Issuer><saml:Subject><saml:NameID Format="${n.nameIdFormat}">${n.nameId}</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData NotOnOrAfter="${n.notOnOrAfter}" Recipient="${n.recipient}"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="${n.notBefore}" NotOnOrAfter="${n.notOnOrAfter}"><saml:AudienceRestriction><saml:Audience>${n.audience}</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="${n.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(n,e,t){let r=new SignedXml({privateKey:e,publicCert:t});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(n,{location:{reference:"//*[local-name(.)='Issuer']",action:"after"}}),r.getSignedXml()}var tt=class{cache;config;constructor(e){kn(e.privateKey,"private key"),kn(e.certificate,"certificate"),this.config={...e,clockSkew:e.clockSkew??120,assertionLifetime:e.assertionLifetime??300},this.cache=new ke;}async getToken(e){if(!e)throw new Error("saml-bearer auth requires a user token for principal propagation");let t=Ft(e),r=this.cache.get(t);return r||this.exchangeToken(e,t)}resolveNameId(e,t){return t==="email"?e.email??e.sub??"":e.sub??""}async exchangeToken(e,t){let r=decodeJwt(e),s=Date.now(),i=this.resolveNameId({sub:r.sub,email:r.email},this.config.nameIdFormat),o=ii[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=oi({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 Ht.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:f,expires_in:g}=p.data,h=typeof g=="number"&&g>0?g:3600,S=Date.now()+(h-60)*1e3;return this.cache.set(t,f,S),f}};function On(n){if(n.tlsVerify===false||n.clientCert!==void 0)return new Agent({rejectUnauthorized:n.tlsVerify!==false,...n.clientCert?{cert:n.clientCert}:{},...n.clientKey?{key:n.clientKey}:{},...n.clientKeyPassphrase?{passphrase:n.clientKeyPassphrase}:{}})}var li=3e4,q=di({name:"sap-http-client",transport:{target:"pino/file",options:{destination:2}}});function ui(n){return n.startsWith("http://")||n.startsWith("https://")}function pi(n){let e=n.replace(/\/+$/,"");try{let t=new URL(e),r=t.pathname.replace(/\/+$/,"");return {origin:`${t.protocol}//${t.host}`,path:r}}catch{return {origin:e,path:""}}}var me=class{constructor(e,t={}){this.destination=e;switch(this.httpsAgent=On(t),this.requestTimeoutMs=t.requestTimeoutMs??li,e.auth.type){case "oauth2":this.tokenManager=new pe(e.auth.tokenServiceUrl,e.auth.clientId,e.auth.clientSecret);break;case "ias":this.tokenManager=new Oe({issuerUrl:e.auth.tenantUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new Z);break;case "xsuaa":this.tokenManager=new Oe({issuerUrl:e.auth.xsuaaUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new Z);break;case "btp-principal":this.tokenManager=new et({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 tt({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){Je();let t=e.method!=="get",r=de(),s=Ye(),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(t){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,httpsAgent:this.httpsAgent,timeout:this.effectiveTimeoutMs(),signal:r?.signal,beforeRedirect:p=>{this.httpsAgent&&(p.agent=this.httpsAgent);let f=p.headers??{};for(let[g,h]of Object.entries(d))f[g]||(f[g]=h);}},m={...u.headers};m.Authorization&&(m.Authorization=`${m.Authorization.substring(0,15)}***`),q.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 Ht(u);if(q.info({...s?{correlationId:s}:{},status:p.status,contentType:p.headers["content-type"]},"SAP HTTP response OK"),typeof p.data=="string"&&p.data.trimStart().startsWith("<!DOCTYPE"))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 f=e.expectedStatuses?.includes(p.response.status)??false;(f?q.info.bind(q):q.error.bind(q))({...s?{correlationId:s}:{},status:p.response.status,statusText:p.response.statusText,contentType:p.response.headers?.["content-type"],url:u.url,...f?{expected:true}:{},dataPreview:typeof p.response.data=="string"?p.response.data.substring(0,200):typeof p.response.data},f?"SAP HTTP response (expected)":"SAP HTTP response ERROR");}else p instanceof Error&&q.error({message:p.message,name:p.name},"SAP HTTP connection error");if(t&&this.isCsrf403(p)){Je();try{let f=await this.fetchCsrfToken({...c,...a},e.url),g={...u.headers};f.token&&(g["x-csrf-token"]=f.token,f.cookies&&(g.Cookie=f.cookies)),Ye();let h=await Ht({...u,headers:g});return {data:h.data,headers:h.headers,status:h.status}}catch(f){throw this.toSapError(f)}}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(ui(e))return e;let{origin:t,path:r}=pi(this.destination.url);return r&&(e===r||e.startsWith(`${r}/`))?`${t}${e}`:`${this.destination.url}${e}`}async fetchCsrfToken(e,t){let r=this.destination.url,s=[];if(t){let i=t.replace(/\/[^/]*$/,"/");i!=="/"&&i!==t&&s.push(this.resolveUrl(i));}s.push(`${r}/`,r);for(let i of s){Je(),Ye();try{let o={...e,"x-csrf-token":"fetch"},a={};this.destination.sapClient&&(a["sap-client"]=this.destination.sapClient);let c=await Ht.get(i,{headers:o,params:Object.keys(a).length>0?a:void 0,validateStatus:()=>!0,timeout:this.effectiveTimeoutMs(),signal:de()?.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(q.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=Pn();return e===void 0?this.requestTimeoutMs:Math.max(1,Math.min(this.requestTimeoutMs,e))}toSapError(e){if(e instanceof $)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 t=de()?.signal?.reason?.name==="TimeoutError";return {code:t?"TOOL_TIMEOUT":"ABORTED",message:t?"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?le(e.response.data):e instanceof Error?{code:"CONNECTION_ERROR",message:e.message,details:[],rawResponse:void 0}:le(e)}};function In(n,e){e||n.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 Dn(n,e,t,r){try{let s=new me(ze(e),t),i=await Ze(s,{baseUrl:e.baseUrl,sapClient:e.sapClient,tlsVerify:t.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"?n.warn(a,`Selbsttest ${o.stage}: ${o.detail}`):n.info(a,`Selbsttest ${o.stage}: ${o.detail}`);}i.status==="connected"?n.info({durationMs:i.durationMs,sapClient:e.sapClient??null},"Selbsttest bestanden: Namensaufl\xF6sung, TLS, Anmeldung, Mandant und Katalog sind in Ordnung"):n.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){n.warn({err:s instanceof Error?s.message:String(s)},"Selbsttest konnte nicht ausgef\xFChrt werden. Der Server l\xE4uft weiter.");}}function ee(n){return `/Date(${Date.parse(n.length===10?`${n}T00:00:00Z`:n)})/`}var A=(n,e,t,r=true)=>({name:n,type:"Edm.String",maxLength:e,...t?{label:t}:{},nullable:r}),De=(n,e,t=15,r=3)=>({name:n,type:"Edm.Decimal",precision:t,scale:r,...e?{label:e}:{}}),te=(n,e)=>({name:n,type:"Edm.DateTime",precision:7,...e?{label:e}:{}}),Nn=(n,e)=>({name:n,type:"Edm.Boolean",...e?{label:e}:{}}),Wt="/sap/opu/odata/sap",mi={technicalName:"API_BUSINESS_PARTNER",title:"Business Partner (A2X)",description:"Business partners, customers and suppliers with their addresses.",path:`${Wt}/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"),te("CreationDate","Created On"),Nn("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),te("ValidityStartDate","Valid From"),te("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:[]},fi={technicalName:"API_SALES_ORDER_SRV",title:"Sales Order (A2X)",description:"Sales orders with items \u2014 read, create (deep insert), change, release.",path:`${Wt}/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"),te("CreationDate","Created On"),te("LastChangeDateTime","Last Changed"),te("RequestedDeliveryDate","Requested Delivery Date"),De("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"),De("RequestedQuantity","Order Quantity",15,3),A("RequestedQuantityUnit",3,"Unit"),De("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"}]},gi={technicalName:"API_PRODUCT_SRV",title:"Product Master (A2X)",description:"Products (materials) with descriptions.",path:`${Wt}/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"),De("GrossWeight","Gross Weight",13,3),De("NetWeight","Net Weight",13,3),A("WeightUnit",3,"Weight Unit"),A("CreatedByUser",12,"Created By"),te("CreationDate","Created On"),Nn("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:[]},nt=[mi,fi,gi],jt="MCP_DEMO",hi=[["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"]],yi=[["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]],Si=[["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 Vt(n,e=3){return n.toFixed(e)}function qt(){let n=[],e=[];for(let[c,l,d,u,m,p,f,g,h,S,T,y]of hi){let v=l==="1",D=v?`${d} ${u}`:d;n.push({BusinessPartner:c,Customer:S==="C"||S==="B"?c:"",Supplier:S==="S"||S==="B"?c:"",BusinessPartnerCategory:l,BusinessPartnerFullName:D,BusinessPartnerName:D,OrganizationBPName1:v?"":d,FirstName:v?d:"",LastName:v?u:"",BusinessPartnerGrouping:v?"BP02":"BP01",SearchTerm1:(v?u:d.split(" ")[0]).toUpperCase().slice(0,20),Industry:T,CreatedByUser:jt,CreationDate:ee(y),IsMarkedForArchiving:false}),e.push({BusinessPartner:c,AddressID:String(2e4+n.length),ValidityStartDate:ee(y),ValidityEndDate:ee("9999-12-31"),StreetName:m,HouseNumber:p,PostalCode:f,CityName:g,Country:"DE",Region:h,Language:"DE"});}let t=[],r=[],s=new Map,i=new Map;yi.forEach(([c,l,d,u,m,p,f,g,h],S)=>{s.set(c,h),i.set(c,g),t.push({Product:c,ProductType:l,ProductGroup:d,BaseUnit:u,GrossWeight:m,NetWeight:p,WeightUnit:"KG",CreatedByUser:jt,CreationDate:ee(`2023-11-${String(6+S).padStart(2,"0")}`),IsMarkedForDeletion:false}),r.push({Product:c,Language:"DE",ProductDescription:f}),r.push({Product:c,Language:"EN",ProductDescription:g});});let o=[],a=[];for(let[c,l,d,u,m,p,f,g]of Si){let h=0;g.forEach(([S,T],y)=>{let v=T*(s.get(S)??0);h+=v,a.push({SalesOrder:c,SalesOrderItem:String((y+1)*10),Material:S,SalesOrderItemText:i.get(S)??S,SalesOrderItemCategory:"TAN",RequestedQuantity:Vt(T),RequestedQuantityUnit:"ST",NetAmount:Vt(v),TransactionCurrency:"EUR",ProductionPlant:"1010"});}),o.push({SalesOrder:c,SalesOrderType:"OR",SalesOrganization:"1010",DistributionChannel:"10",OrganizationDivision:"00",SoldToParty:l,PurchaseOrderByCustomer:d,CreatedByUser:jt,CreationDate:ee(u),LastChangeDateTime:ee(`${u}T09:30:00Z`),RequestedDeliveryDate:ee(m),TotalNetAmount:Vt(h),TransactionCurrency:"EUR",OverallSDProcessStatus:p,OverallDeliveryStatus:f});}return {A_BusinessPartner:n,A_BusinessPartnerAddress:e,A_SalesOrder:o,A_SalesOrderItem:a,A_Product:t,A_ProductDescription:r}}function Ti(n){return n.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function bi(n,e){return `assoc_${n.set}_${e}`}function xn(n){let e=n.namespace,t=[],r=[],s=[];for(let a of n.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="${Ti(d.label)}"`),a.keys.includes(d.name)&&u.push('sap:creatable="true" sap:updatable="false"'),` <Property ${u.join(" ")}/>`}).join(`
|
|
5
|
-
`),l=a.navigations.map(d=>{let u=
|
|
4
|
+
${l}`)}return c.data}function Ye(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 de=new AsyncLocalStorage;function Pn(){return randomBytes(6).toString("hex")}function le(){return de.getStore()}function Rn(t,e){return de.run(t,e)}function Xe(){let t=de.getStore();if(t)return t.sapCalls.count+=1,t.correlationId}function _n(t=Date.now()){let e=de.getStore();if(e?.deadline)return Math.max(0,e.deadline-t)}function Je(){return de.getStore()?.signal?.aborted??false}var $=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=de.getStore();if(!t?.signal?.aborted)return;let e=t.signal.reason?.name==="TimeoutError";throw new $(e,t.toolName)}function kn(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 ue(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 Bt(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 Ft(t){let e=Z(t).toLowerCase();return /anmeldung fehlgeschlagen|anmeldefehler|logon (error|failed)|login failed|unauthori[sz]ed/.test(e)}var Ht="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 Z(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 $s(t){return /\/IW[A-Z]+\/[A-Z_]+\/\d+/i.exec(t)?.[0]}var Ls=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 Bs(t){let e=t?.code;if(typeof e=="string")return e;let n=t?.cause;if(typeof n?.code=="string")return n.code}function Fs(t){let n=(t?.cause?.cert??t?.cert)?.issuer?.CN;return typeof n=="string"?n:void 0}function pe(t,e){if(t instanceof $)return {code:t.timedOut?"TOOL_TIMEOUT":"ABORTED",message:t.message};let n=Bs(t);if(n&&Ls.has(n)){let i=Fs(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 Hs(t.response.status,t.response.data,e);let r=t;if(typeof r?.message=="string"&&r.message.length>0)return Bt(r.message)?Ft(r.message)?{code:"AUTH_FAILED",message:`Anmeldung abgelehnt (SAP-Anmeldeseite: ${Z(r.message)}).`,guidance:Ht}:{code:"HTML_RESPONSE",message:`SAP hat eine HTML-Seite statt Daten geliefert: ${Z(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 Bt(t.message)?Ft(t.message)?{code:"AUTH_FAILED",message:`Anmeldung abgelehnt (SAP-Anmeldeseite: ${Z(t.message)}).`,guidance:Ht}:{code:"HTML_RESPONSE",message:`SAP hat eine HTML-Seite statt Daten geliefert: ${Z(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 Hs(t,e,n){let r=typeof e=="string"?e:JSON.stringify(e??""),s=$s(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(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:js(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(Bt(e))return Ft(e)?{code:"AUTH_FAILED",httpStatus:t,message:`Anmeldung abgelehnt (SAP-Anmeldeseite, HTTP ${t}: ${Z(e)}).`,guidance:Ht}:{code:"HTML_RESPONSE",httpStatus:t,message:`SAP hat eine HTML-Fehlerseite geliefert (HTTP ${t}): ${Z(e)}`,guidance:"Der Pfad wird von SAP nicht als OData-Service beantwortet. Bei Katalogpfaden sap_discover_services statt sap_query verwenden."};let i=ue(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 js(t){let e=ue(t);if(e.message&&!(e.message==="Unknown error"||e.message==="Unknown SAP error"))return e.message}var qs=5e3;function On(t){let e=t?.issuer?.CN??t?.issuer?.O;return Array.isArray(e)?e.join(", ")||void 0:typeof e=="string"?e:void 0}function _(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 Ks(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 Gs(t,e){let n=Date.now();try{let{address:r,family:s}=await e(t);return _("dns",n,"ok",`${t} l\xF6st auf ${r} auf.`,{info:{address:r,family:s}})}catch(r){let s=pe(r);return _("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 zs(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=On(a);return _("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=pe(a);if(c.code==="TLS_CHAIN_INCOMPLETE"){let l;try{let d=await o(!1);l=On(d);}catch{}return _("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 _("tls",i,"failed",c.message,{guidance:c.guidance})}}async function Ys(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 [_("auth",n,"ok","Anmeldung am Gateway erfolgreich."),_("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}}),_("catalog",n,"ok",`Servicekatalog erreichbar (HTTP ${r.status}).`)]}catch(r){let s=pe(r),i=s.httpStatus===401||s.code==="AUTH_FAILED";return i&&!e?[_("auth",n,"ok","Zugangsdaten wurden gesendet."),_("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."}),_("catalog",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor.")]:i?[_("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.`}),_("client",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor."),_("catalog",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor.")]:[_("auth",n,"ok","Anmeldung am Gateway erfolgreich."),_("client",n,"ok",e?`Mandant ${e} angenommen.`:"Kein Mandant konfiguriert."),_("catalog",n,"failed",s.message,{guidance:s.guidance??"Katalogdienst in /IWFND/MAINT_SERVICE pr\xFCfen. Der Server probiert drei bekannte Katalogpfade durch."})]}}async function et(t,e){let n=Date.now(),r=e.budgetMs??qs,s=e.deps?.lookup??lookup,i=e.deps?.connectTls??connect,o=[],a=Ks(e.baseUrl);if(!a)return o.push(_("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=>_(c,n,"skipped","Nicht gepr\xFCft \u2014 die Basis-URL ist unbrauchbar."))),Ze(o,n);if(o.push(await Gs(a.host,s)),o[0].status==="failed")return o.push(...["tls","auth","client","catalog"].map(c=>_(c,n,"skipped","Nicht gepr\xFCft \u2014 der Name war nicht aufl\xF6sbar."))),Ze(o,n);if(a.isTls){let c=Math.max(500,r-(Date.now()-n));if(o.push(await zs(a.host,a.port,e.tlsVerify!==false,c,i)),o[o.length-1].status==="failed")return o.push(...["auth","client","catalog"].map(l=>_(l,n,"skipped","Nicht gepr\xFCft \u2014 die TLS-Stufe scheiterte zuvor."))),Ze(o,n)}else o.push(_("tls",n,"skipped","Nicht gepr\xFCft \u2014 SAP_BASE_URL verwendet http, nicht https."));return o.push(...await Ys(t,e.sapClient)),Ze(o,n)}function Ze(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 me=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 Vt.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 jt(t){return createHash("sha256").update(t).digest("hex")}var Oe=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});}},tt=class{clientTokenManager;cache;config;constructor(e){this.config=e,this.clientTokenManager=new me(e.tokenServiceUrl,e.clientId,e.clientSecret),this.cache=new Oe;}async getToken(e){if(!e)return this.clientTokenManager.getToken();let n=jt(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 Vt.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 Ie=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 Vt.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 ee=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 Vt.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 ai={email:"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",sapUsername:"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"};function In(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 ci(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 di(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 nt=class{cache;config;constructor(e){In(e.privateKey,"private key"),In(e.certificate,"certificate"),this.config={...e,clockSkew:e.clockSkew??120,assertionLifetime:e.assertionLifetime??300},this.cache=new Oe;}async getToken(e){if(!e)throw new Error("saml-bearer auth requires a user token for principal propagation");let n=jt(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=ai[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=ci({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=di(d,this.config.privateKey,this.config.certificate),m=Buffer.from(u).toString("base64"),p=await Vt.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:f,expires_in:g}=p.data,h=typeof g=="number"&&g>0?g:3600,S=Date.now()+(h-60)*1e3;return this.cache.set(n,f,S),f}};function Dn(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 pi=3e4,q=ui({name:"sap-http-client",transport:{target:"pino/file",options:{destination:2}}});function mi(t){return t.startsWith("http://")||t.startsWith("https://")}function fi(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 fe=class{constructor(e,n={}){this.destination=e;switch(this.httpsAgent=Dn(n),this.requestTimeoutMs=n.requestTimeoutMs??pi,e.auth.type){case "oauth2":this.tokenManager=new me(e.auth.tokenServiceUrl,e.auth.clientId,e.auth.clientSecret);break;case "ias":this.tokenManager=new Ie({issuerUrl:e.auth.tenantUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new ee);break;case "xsuaa":this.tokenManager=new Ie({issuerUrl:e.auth.xsuaaUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new ee);break;case "btp-principal":this.tokenManager=new tt({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 nt({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=le(),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,httpsAgent:this.httpsAgent,timeout:this.effectiveTimeoutMs(),signal:r?.signal,beforeRedirect:p=>{this.httpsAgent&&(p.agent=this.httpsAgent);let f=p.headers??{};for(let[g,h]of Object.entries(d))f[g]||(f[g]=h);}},m={...u.headers};m.Authorization&&(m.Authorization=`${m.Authorization.substring(0,15)}***`),q.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 Vt(u);if(q.info({...s?{correlationId:s}:{},status:p.status,contentType:p.headers["content-type"]},"SAP HTTP response OK"),typeof p.data=="string"&&p.data.trimStart().startsWith("<!DOCTYPE"))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 f=e.expectedStatuses?.includes(p.response.status)??false;(f?q.info.bind(q):q.error.bind(q))({...s?{correlationId:s}:{},status:p.response.status,statusText:p.response.statusText,contentType:p.response.headers?.["content-type"],url:u.url,...f?{expected:true}:{},dataPreview:typeof p.response.data=="string"?p.response.data.substring(0,200):typeof p.response.data},f?"SAP HTTP response (expected)":"SAP HTTP response ERROR");}else p instanceof Error&&q.error({message:p.message,name:p.name},"SAP HTTP connection error");if(n&&this.isCsrf403(p)){Qe();try{let f=await this.fetchCsrfToken({...c,...a},e.url),g={...u.headers};f.token&&(g["x-csrf-token"]=f.token,f.cookies&&(g.Cookie=f.cookies)),Xe();let h=await Vt({...u,headers:g});return {data:h.data,headers:h.headers,status:h.status}}catch(f){throw this.toSapError(f)}}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(mi(e))return e;let{origin:n,path:r}=fi(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 Vt.get(i,{headers:o,params:Object.keys(a).length>0?a:void 0,validateStatus:()=>!0,timeout:this.effectiveTimeoutMs(),signal:le()?.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(q.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=_n();return e===void 0?this.requestTimeoutMs:Math.max(1,Math.min(this.requestTimeoutMs,e))}toSapError(e){if(e instanceof $)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=le()?.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?ue(e.response.data):e instanceof Error?{code:"CONNECTION_ERROR",message:e.message,details:[],rawResponse:void 0}:ue(e)}};function Nn(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 xn(t,e,n,r){try{let s=new fe(Ye(e),n),i=await et(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 te(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}),Ne=(t,e,n=15,r=3)=>({name:t,type:"Edm.Decimal",precision:n,scale:r,...e?{label:e}:{}}),ne=(t,e)=>({name:t,type:"Edm.DateTime",precision:7,...e?{label:e}:{}}),Mn=(t,e)=>({name:t,type:"Edm.Boolean",...e?{label:e}:{}}),Kt="/sap/opu/odata/sap",gi={technicalName:"API_BUSINESS_PARTNER",title:"Business Partner (A2X)",description:"Business partners, customers and suppliers with their addresses.",path:`${Kt}/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"),ne("CreationDate","Created On"),Mn("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),ne("ValidityStartDate","Valid From"),ne("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:[]},hi={technicalName:"API_SALES_ORDER_SRV",title:"Sales Order (A2X)",description:"Sales orders with items \u2014 read, create (deep insert), change, release.",path:`${Kt}/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"),ne("CreationDate","Created On"),ne("LastChangeDateTime","Last Changed"),ne("RequestedDeliveryDate","Requested Delivery Date"),Ne("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"),Ne("RequestedQuantity","Order Quantity",15,3),w("RequestedQuantityUnit",3,"Unit"),Ne("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"}]},yi={technicalName:"API_PRODUCT_SRV",title:"Product Master (A2X)",description:"Products (materials) with descriptions.",path:`${Kt}/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"),Ne("GrossWeight","Gross Weight",13,3),Ne("NetWeight","Net Weight",13,3),w("WeightUnit",3,"Weight Unit"),w("CreatedByUser",12,"Created By"),ne("CreationDate","Created On"),Mn("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:[]},rt=[gi,hi,yi],Wt="MCP_DEMO",Si=[["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"]],Ti=[["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]],Ai=[["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 qt(t,e=3){return t.toFixed(e)}function Gt(){let t=[],e=[];for(let[c,l,d,u,m,p,f,g,h,S,T,y]of Si){let b=l==="1",D=b?`${d} ${u}`:d;t.push({BusinessPartner:c,Customer:S==="C"||S==="B"?c:"",Supplier:S==="S"||S==="B"?c:"",BusinessPartnerCategory:l,BusinessPartnerFullName:D,BusinessPartnerName:D,OrganizationBPName1:b?"":d,FirstName:b?d:"",LastName:b?u:"",BusinessPartnerGrouping:b?"BP02":"BP01",SearchTerm1:(b?u:d.split(" ")[0]).toUpperCase().slice(0,20),Industry:T,CreatedByUser:Wt,CreationDate:te(y),IsMarkedForArchiving:false}),e.push({BusinessPartner:c,AddressID:String(2e4+t.length),ValidityStartDate:te(y),ValidityEndDate:te("9999-12-31"),StreetName:m,HouseNumber:p,PostalCode:f,CityName:g,Country:"DE",Region:h,Language:"DE"});}let n=[],r=[],s=new Map,i=new Map;Ti.forEach(([c,l,d,u,m,p,f,g,h],S)=>{s.set(c,h),i.set(c,g),n.push({Product:c,ProductType:l,ProductGroup:d,BaseUnit:u,GrossWeight:m,NetWeight:p,WeightUnit:"KG",CreatedByUser:Wt,CreationDate:te(`2023-11-${String(6+S).padStart(2,"0")}`),IsMarkedForDeletion:false}),r.push({Product:c,Language:"DE",ProductDescription:f}),r.push({Product:c,Language:"EN",ProductDescription:g});});let o=[],a=[];for(let[c,l,d,u,m,p,f,g]of Ai){let h=0;g.forEach(([S,T],y)=>{let b=T*(s.get(S)??0);h+=b,a.push({SalesOrder:c,SalesOrderItem:String((y+1)*10),Material:S,SalesOrderItemText:i.get(S)??S,SalesOrderItemCategory:"TAN",RequestedQuantity:qt(T),RequestedQuantityUnit:"ST",NetAmount:qt(b),TransactionCurrency:"EUR",ProductionPlant:"1010"});}),o.push({SalesOrder:c,SalesOrderType:"OR",SalesOrganization:"1010",DistributionChannel:"10",OrganizationDivision:"00",SoldToParty:l,PurchaseOrderByCustomer:d,CreatedByUser:Wt,CreationDate:te(u),LastChangeDateTime:te(`${u}T09:30:00Z`),RequestedDeliveryDate:te(m),TotalNetAmount:qt(h),TransactionCurrency:"EUR",OverallSDProcessStatus:p,OverallDeliveryStatus:f});}return {A_BusinessPartner:t,A_BusinessPartnerAddress:e,A_SalesOrder:o,A_SalesOrderItem:a,A_Product:n,A_ProductDescription:r}}function bi(t){return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function wi(t,e){return `assoc_${t.set}_${e}`}function Un(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="${bi(d.label)}"`),a.keys.includes(d.name)&&u.push('sap:creatable="true" sap:updatable="false"'),` <Property ${u.join(" ")}/>`}).join(`
|
|
5
|
+
`),l=a.navigations.map(d=>{let u=wi(a,d.name),m=`FromRole_${u}`,p=`ToRole_${u}`;return r.push(` <Association Name="${u}" sap:content-version="1">
|
|
6
6
|
<End Type="${e}.${a.set}Type" Multiplicity="1" Role="${m}"/>
|
|
7
7
|
<End Type="${e}.${d.target}Type" Multiplicity="${d.multiplicity}" Role="${p}"/>
|
|
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
9
|
<End EntitySet="${a.set}" Role="${m}"/>
|
|
10
10
|
<End EntitySet="${d.target}" Role="${p}"/>
|
|
11
11
|
</AssociationSet>`),` <NavigationProperty Name="${d.name}" Relationship="${e}.${u}" FromRole="${m}" ToRole="${p}"/>`}).join(`
|
|
12
|
-
`);
|
|
12
|
+
`);n.push(` <EntityType Name="${a.set}Type" sap:content-version="1">
|
|
13
13
|
<Key>
|
|
14
14
|
${a.keys.map(d=>` <PropertyRef Name="${d}"/>`).join(`
|
|
15
15
|
`)}
|
|
16
16
|
</Key>
|
|
17
17
|
${c}${l?`
|
|
18
18
|
${l}`:""}
|
|
19
|
-
</EntityType>`);}let i=
|
|
20
|
-
`),o=
|
|
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 c=[`Name="${a.name}"`];a.returnType&&c.push(`ReturnType="${a.returnType}"`),a.entitySet&&c.push(`EntitySet="${a.entitySet}"`),c.push(`m:HttpMethod="${a.httpMethod}"`);let l=a.parameters.map(d=>` <Parameter Name="${d.name}" Type="${d.type}" Mode="In" Nullable="${d.nullable?"true":"false"}"/>`).join(`
|
|
21
21
|
`);return ` <FunctionImport ${c.join(" ")}>
|
|
22
22
|
${l}
|
|
23
23
|
</FunctionImport>`}).join(`
|
|
@@ -25,7 +25,7 @@ ${l}
|
|
|
25
25
|
<edmx:Edmx Version="1.0" xmlns:edmx="http://schemas.microsoft.com/ado/2007/06/edmx" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:sap="http://www.sap.com/Protocols/SAPData">
|
|
26
26
|
<edmx:DataServices m:DataServiceVersion="2.0">
|
|
27
27
|
<Schema Namespace="${e}" xml:lang="en" sap:schema-version="1" xmlns="http://schemas.microsoft.com/ado/2008/09/edm">
|
|
28
|
-
${
|
|
28
|
+
${n.join(`
|
|
29
29
|
`)}
|
|
30
30
|
${r.join(`
|
|
31
31
|
`)}
|
|
@@ -38,31 +38,31 @@ ${o}`:""}
|
|
|
38
38
|
</Schema>
|
|
39
39
|
</edmx:DataServices>
|
|
40
40
|
</edmx:Edmx>
|
|
41
|
-
`}var I=class extends Error{constructor(e){super(e),this.name="ODataSyntaxError";}},Mn=new Set(["and","or","not","eq","ne","gt","ge","lt","le"]);function vi(n){let e=[],t=0;for(;t<n.length;){let r=n[t];if(/\s/.test(r)){t++;continue}if(r==="("){e.push({kind:"lparen"}),t++;continue}if(r===")"){e.push({kind:"rparen"}),t++;continue}if(r===","){e.push({kind:"comma"}),t++;continue}if(r==="'"){let{value:s,end:i}=Un(n,t);e.push({kind:"string",value:s}),t=i;continue}if(/[0-9]/.test(r)||r==="-"&&/[0-9]/.test(n[t+1]??"")){let s=/^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?[mMdDfFlL]?/.exec(n.slice(t));if(!s)throw new I(`Ung\xFCltige Zahl an Position ${t}.`);e.push({kind:"number",value:Number(s[0].replace(/[mMdDfFlL]$/,""))}),t+=s[0].length;continue}if(/[A-Za-z_]/.test(r)){let i=/^[A-Za-z_][A-Za-z0-9_./]*/.exec(n.slice(t))[0];if(t+=i.length,n[t]==="'"){let{value:o,end:a}=Un(n,t);t=a;let c=i.toLowerCase();if(c==="datetime"||c==="datetimeoffset"){let l=Ln(o);if(Number.isNaN(l))throw new I(`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 I(`Unbekanntes Literal-Pr\xE4fix '${i}'.`);continue}e.push({kind:"ident",value:i});continue}throw new I(`Unerwartetes Zeichen '${r}' an Position ${t}.`)}return e}function Un(n,e){let t=e+1,r="";for(;t<n.length;){let s=n[t];if(s==="'"){if(n[t+1]==="'"){r+="'",t+=2;continue}return {value:r,end:t+1}}r+=s,t++;}throw new I("Zeichenkette ohne schlie\xDFendes Apostroph.")}function Ln(n){let e=/[zZ]|[+-]\d{2}:\d{2}$/.test(n)?n:`${n}Z`;return Date.parse(e)}var Ai=new Set(["substringof","startswith","endswith","tolower","toupper"]),Kt=class{constructor(e){this.tokens=e;}pos=0;parse(){if(this.tokens.length===0)throw new I("Leerer $filter-Ausdruck.");let e=this.parseOr();if(this.pos<this.tokens.length)throw new I(`Unerwartetes Token nach dem Ausdruck: ${$n(this.peek())}.`);return e}peek(){return this.tokens[this.pos]}isKeyword(e){let t=this.peek();return t?.kind==="ident"&&t.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(),t=this.peek();if(t?.kind==="ident"){let r=t.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(!Mn.has(r))throw new I(`Unbekannter Operator '${t.value}'.`)}return e}parsePrimary(){let e=this.peek();if(!e)throw new I("Ausdruck endet unerwartet.");if(e.kind==="lparen"){this.pos++;let t=this.parseOr();if(this.peek()?.kind!=="rparen")throw new I("Schlie\xDFende Klammer fehlt.");return this.pos++,t}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 t=e.value.toLowerCase();if(t==="true"||t==="false")return this.pos++,{type:"literal",value:t==="true"};if(t==="null")return this.pos++,{type:"literal",value:null};if(Mn.has(t))throw new I(`Operand erwartet, aber '${e.value}' gefunden.`);if(this.pos++,this.peek()?.kind==="lparen"){if(!Ai.has(t))throw new I(`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 I(`Schlie\xDFende Klammer nach ${e.value}(...) fehlt.`);return this.pos++,{type:"call",fn:t,args:r}}return {type:"field",path:e.value}}throw new I(`Unerwartetes Token ${$n(e)}.`)}};function $n(n){if(!n)return "(Ende)";switch(n.kind){case "lparen":return "'('";case "rparen":return "')'";case "comma":return "','";default:return `'${String(n.value)}'`}}var wi=/^\/Date\((-?\d+)(?:[+-]\d{4})?\)\/$/;function Ei(n,e){let t=n;for(let r of e.split("/")){if(t===null||typeof t!="object")return null;t=t[r];}return t===void 0?null:t}function j(n,e){switch(n.type){case "and":return fe(j(n.left,e))&&fe(j(n.right,e));case "or":return fe(j(n.left,e))||fe(j(n.right,e));case "not":return !fe(j(n.operand,e));case "literal":return n.datetime?{datetime:n.value}:n.value;case "field":return Ei(e,n.path);case "call":{let t=n.args.map(s=>j(s,e)),r=s=>s==null?"":String(s);switch(n.fn){case "substringof":return r(t[1]).includes(r(t[0]));case "startswith":return r(t[0]).startsWith(r(t[1]));case "endswith":return r(t[0]).endsWith(r(t[1]));case "tolower":return r(t[0]).toLowerCase();case "toupper":return r(t[0]).toUpperCase();default:throw new I(`Unbekannte Funktion '${n.fn}'.`)}}case "compare":{let[t,r]=Ci(j(n.left,e),j(n.right,e));switch(n.op){case "eq":return t===r;case "ne":return t!==r;case "gt":return t!==null&&r!==null&&t>r;case "ge":return t!==null&&r!==null&&t>=r;case "lt":return t!==null&&r!==null&&t<r;case "le":return t!==null&&r!==null&&t<=r}}}}function fe(n){return n===true}function Ci(n,e){let t=i=>typeof i=="object"&&i!==null&&"datetime"in i;if(t(n)||t(e)){let i=o=>{if(t(o))return o.datetime;if(typeof o=="number")return o;if(typeof o=="string"){let a=wi.exec(o);if(a)return Number(a[1]);let c=Ln(o);return Number.isNaN(c)?null:c}return null};return [i(n),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 n=="number"||typeof e=="number")&&!(n===null||e===null)){let i=r(n),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(n),s(e)]}function Bn(n){let e=new Kt(vi(n)).parse();return t=>fe(j(e,t))}var ge=class extends Error{constructor(e){super(e),this.name="ODataUrlError";}};function rt(n){let e=n.trim();if(e.startsWith("'"))return Pi(e);let t=/^(guid|datetime|datetimeoffset|time|X|binary)'(.*)'$/is.exec(e);return t?t[2].replace(/''/g,"'"):/^-?\d+(\.\d+)?[mMdDfFlL]?$/.test(e)?Number(e.replace(/[mMdDfFlL]$/,"")):e==="true"?true:e==="false"?false:e}function Pi(n){if(!n.endsWith("'")||n.length<2)throw new ge(`Zeichenkette ohne schlie\xDFendes Apostroph: ${n}`);return n.slice(1,-1).replace(/''/g,"'")}function Ri(n){let e=Gt(n);return ki(e,",").map(r=>{let s=Oi(r,"=");return s===-1?{value:rt(r)}:{name:r.slice(0,s).trim(),value:rt(r.slice(s+1))}})}function zt(n){let e=n.replace(/^\/+|\/+$/g,""),t=e.indexOf("("),r=e.indexOf("/"),s,i,o;if(t!==-1&&(r===-1||t<r)){s=e.slice(0,t);let l=_i(e,t);i=Ri(e.slice(t+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=>Gt(l)).filter(l=>l.length>0),c={entitySet:Gt(s),segments:a};return i&&(c.key=i),c}function _i(n,e){let t=false;for(let r=e+1;r<n.length;r++){let s=n[r];if(s==="'"){t=!t;continue}if(!t&&s===")")return r}throw new ge(`Schlie\xDFende Klammer fehlt in: ${n}`)}function ki(n,e){let t=[],r="",s=false;for(let i=0;i<n.length;i++){let o=n[i];if(o==="'"){s=!s,r+=o;continue}if(!s&&o===e){t.push(r),r="";continue}r+=o;}return t.push(r),t.filter(i=>i.trim().length>0)}function Oi(n,e){let t=false;for(let r=0;r<n.length;r++)if(n[r]==="'")t=!t;else if(!t&&n[r]===e)return r;return -1}function Gt(n){try{return decodeURIComponent(n)}catch{return n}}var st="DEMO",Jt="demo",Qt="100",Ne="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2",Ii="/sap/opu/odata4/",b=class extends Error{constructor(t,r,s,i={}){super(s);this.status=t;this.code=r;this.headers=i;}},it=class{constructor(e={}){this.options=e;for(let t of nt)for(let r of t.entityTypes)this.types.set(r.set,{type:r,service:t});}data=qt();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=qt(),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,t){let r=e.method.toUpperCase(),s=new URL(e.url,t.base),i=s.pathname.replace(/\/+$/,"")||"/",o=s.searchParams;t.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"&&!t.inBatch&&e.headers["x-csrf-token"]!==this.csrfToken)throw new b(403,"HTTP/403","CSRF token validation failed",{"x-csrf-token":"Required"});let p;if(i===Ne||i.startsWith(`${Ne}/`))p=this.handleCatalog(u,i.slice(Ne.length),o,t);else {if(i.startsWith(Ii))throw new b(404,"HTTP/404",`Resource not found for segment '${i}'`);{let f=nt.find(g=>i===g.path||i.startsWith(`${g.path}/`));if(!f)throw new b(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${i}'`);p=this.handleService(f,u,i.slice(f.path.length),o,e,t);}}return p.headers={...p.headers,...c},l&&(p.body=""),p}checkAuth(e,t){let r=e.authorization??"",s=`Basic ${Buffer.from(`${this.options.username??st}:${this.options.password??Jt}`).toString("base64")}`,i=a=>new b(401,"HTTP/401",a,{"www-authenticate":'Basic realm="SAP NetWeaver Application Server [DEMO/100]"'});if(r!==s)throw i("Anmeldung fehlgeschlagen");let o=t.get("sap-client");if(o!==(this.options.sapClient??Qt))throw i(o?`Mandant ${o} existiert nicht`:"Anmeldung fehlgeschlagen")}handleCatalog(e,t,r,s){if(e!=="GET")throw new b(405,"HTTP/405","Method not allowed on catalog");let i=t.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=zt(i);if(o.entitySet!=="ServiceCollection")throw new b(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${i}'`);let a=[...nt].map(u=>({__metadata:{id:`${s.base}${Ne}/ServiceCollection('${u.technicalName}_0001')`,uri:`${s.base}${Ne}/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 b(404,"/IWFND/CM_MGW/002",`Service '${u}' not found`);return this.json(200,{d:m})}let c=jn(r),{rows:l,count:d}=Yt(a,c);return this.json(200,{d:{results:l,...c.inlinecount==="allpages"?{__count:String(d)}:{}}})}handleService(e,t,r,s,i,o){let a=r.replace(/^\/+/,"");if(a===""){if(t!=="GET")throw new b(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,xn(e));if(a==="$batch"){if(t!=="POST")throw new b(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(t!==c.httpMethod&&!(t==="MERGE"&&c.httpMethod==="POST"))throw new b(405,"HTTP/405",`Function import ${c.name} requires ${c.httpMethod}`);return this.handleFunctionImport(e,c.name,s,o)}return this.handleEntity(e,t,a,s,i,o)}handleFunctionImport(e,t,r,s){let i=e.functionImports.find(a=>a.name===t),o={};for(let a of i.parameters){let c=r.get(a.name);if(c===null){if(!a.nullable)throw new b(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Parameter '${a.name}' is missing`);continue}o[a.name]=rt(c);}if(t==="ReleaseSalesOrder"){let a=String(o.SalesOrder??""),c=this.rows("A_SalesOrder").find(l=>l.SalesOrder===a);if(!c)throw new b(404,"SD/001",`Sales order ${a} does not exist`);if(c.OverallSDProcessStatus==="C")throw new b(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(t==="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 b(501,"HTTP/501",`Function import ${t} not implemented`)}handleEntity(e,t,r,s,i,o){let a=i.body,c=i.headers["if-match"],l;try{l=zt(r);}catch(h){throw h instanceof ge?new b(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR",h.message):h}let d=e.entityTypes.find(h=>h.set===l.entitySet);if(!d)throw new b(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${l.entitySet}'`);let u=jn(s),m=this.rows(d.set),p;if(l.key&&(p=this.find(d,l.key),!p))throw new b(404,"/IWBEP/CM_MGW_RT/020",`Resource not found for the segment '${d.set}'`);let f=[...l.segments],g=false;if(f[f.length-1]==="$count"&&(g=true,f.pop()),f.length>0){if(!p)throw new b(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR","Navigation requires a key");if(f.length>1)throw new b(501,"HTTP/501","Only one navigation level is supported");let h=d.navigations.find(y=>y.name===f[0]);if(!h)throw new b(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${f[0]}'`);let S=this.typeOf(h.target),T=this.related(p,h);if(h.multiplicity==="1"){if(p=T[0],!p)throw new b(404,"/IWBEP/CM_MGW_RT/020",`No ${h.target} for this ${d.set}`);d=S,m=[];}else m=T,p=void 0,d=S;}switch(t){case "GET":{if(p){let T=this.json(200,{d:this.present(e,d,p,o,u)});return T.headers.etag=this.etag(d,p),T}if(g){let T=Yt(m,{...u,top:void 0,skip:void 0,raw:u.raw});return {status:200,headers:{"content-type":"text/plain"},body:String(T.count)}}let{rows:h,count:S}=Yt(m,u);return this.json(200,{d:{results:h.map(T=>this.present(e,d,T,o,u)),...u.inlinecount==="allpages"?{__count:String(S)}:{}}})}case "POST":{if(p||l.key)throw new b(405,"HTTP/405","POST on a single entity is not allowed (use MERGE/PATCH)");if(d.creatable===false)throw new b(405,"HTTP/405",`${d.set} is not creatable`);let h=this.parseBody(a),S=this.create(d,h,o),T=this.json(201,{d:this.present(e,d,S,o,{expand:this.deepExpands(d,h)})});return T.headers.location=this.uri(o,e,d,S),T.headers.etag=this.etag(d,S),T}case "MERGE":case "PATCH":case "PUT":{if(!p)throw new b(405,"HTTP/405",`${t} requires a single entity`);if(d.updatable===false)throw new b(405,"HTTP/405",`${d.set} is not updatable`);this.checkIfMatch(d,p,c);let h=this.parseBody(a);return this.update(d,p,h,t==="PUT"),{status:204,headers:{etag:this.etag(d,p),dataserviceversion:"2.0"},body:""}}case "DELETE":{if(!p)throw new b(405,"HTTP/405","DELETE requires a single entity");if(d.deletable===false)throw new b(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 b(405,"HTTP/405",`Method ${t} not allowed`)}}parseBody(e){if(!e||e.trim()==="")throw new b(400,"/IWCOR/CX_DS_BODY_MISSING","Request body is empty");let t;try{t=JSON.parse(e);}catch{throw new b(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR","Request body is not valid JSON")}if(t===null||typeof t!="object"||Array.isArray(t))throw new b(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR","Request body must be a JSON object");let r=t;return "d"in r&&typeof r.d=="object"&&r.d!==null&&Object.keys(r).length===1?r.d:r}create(e,t,r,s={}){let i={},o=[];for(let[a,c]of Object.entries(t)){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 b(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 b(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,t,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 b(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Property '${o}' is invalid`);if(e.keys.includes(o)&&this.coerce(o,c.type,a)!==t[o])throw new b(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)&&(t[o.name]=this.emptyValue(o.type));if(Object.assign(t,i),(e.set==="A_SalesOrder"||e.set==="A_SalesOrderItem")&&(t.LastChangeDateTime=`/Date(${Date.now()})/`,e.set==="A_SalesOrderItem")){let o=this.rows("A_SalesOrder").find(a=>a.SalesOrder===t.SalesOrder);o&&this.recalculate(this.typeOf("A_SalesOrder"),o);}this.bump(e.set,t);}remove(e,t){let r=this.rows(e.set),s=r.indexOf(t);s!==-1&&r.splice(s,1),this.versions.delete(this.versionKey(e.set,t));for(let i of e.navigations){if(i.multiplicity!=="*")continue;let o=this.typeOf(i.target);for(let a of this.related(t,i))this.remove(o,a);}}checkIfMatch(e,t,r){if(r===void 0||r.trim()==="*")return;let s=this.etag(e,t);if(!r.split(",").map(o=>o.trim()).map(o=>o.startsWith("W/")?o:`W/${o}`).includes(s))throw new b(412,"/IWBEP/CM_MGW_RT/021","The entity has been changed by another user; reload and retry (ETag mismatch)")}handleBatch(e,t,r){let s=t.headers["content-type"]??"",i=/boundary=("?)([^";]+)\1/i.exec(s)?.[2];if(!i||!t.body)throw new b(400,"/IWCOR/CX_DS_BATCH","multipart/mixed body with boundary expected");let o=`batchresponse_${randomUUID()}`,a=[];for(let c of Vn(t.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 I=class extends Error{constructor(e){super(e),this.name="ODataSyntaxError";}},$n=new Set(["and","or","not","eq","ne","gt","ge","lt","le"]);function vi(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}=Ln(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 I(`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}=Ln(t,n);n=a;let c=i.toLowerCase();if(c==="datetime"||c==="datetimeoffset"){let l=Fn(o);if(Number.isNaN(l))throw new I(`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 I(`Unbekanntes Literal-Pr\xE4fix '${i}'.`);continue}e.push({kind:"ident",value:i});continue}throw new I(`Unerwartetes Zeichen '${r}' an Position ${n}.`)}return e}function Ln(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 I("Zeichenkette ohne schlie\xDFendes Apostroph.")}function Fn(t){let e=/[zZ]|[+-]\d{2}:\d{2}$/.test(t)?t:`${t}Z`;return Date.parse(e)}var Ei=new Set(["substringof","startswith","endswith","tolower","toupper"]),zt=class{constructor(e){this.tokens=e;}pos=0;parse(){if(this.tokens.length===0)throw new I("Leerer $filter-Ausdruck.");let e=this.parseOr();if(this.pos<this.tokens.length)throw new I(`Unerwartetes Token nach dem Ausdruck: ${Bn(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(!$n.has(r))throw new I(`Unbekannter Operator '${n.value}'.`)}return e}parsePrimary(){let e=this.peek();if(!e)throw new I("Ausdruck endet unerwartet.");if(e.kind==="lparen"){this.pos++;let n=this.parseOr();if(this.peek()?.kind!=="rparen")throw new I("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($n.has(n))throw new I(`Operand erwartet, aber '${e.value}' gefunden.`);if(this.pos++,this.peek()?.kind==="lparen"){if(!Ei.has(n))throw new I(`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 I(`Schlie\xDFende Klammer nach ${e.value}(...) fehlt.`);return this.pos++,{type:"call",fn:n,args:r}}return {type:"field",path:e.value}}throw new I(`Unerwartetes Token ${Bn(e)}.`)}};function Bn(t){if(!t)return "(Ende)";switch(t.kind){case "lparen":return "'('";case "rparen":return "')'";case "comma":return "','";default:return `'${String(t.value)}'`}}var Ci=/^\/Date\((-?\d+)(?:[+-]\d{4})?\)\/$/;function Pi(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 j(t,e){switch(t.type){case "and":return ge(j(t.left,e))&&ge(j(t.right,e));case "or":return ge(j(t.left,e))||ge(j(t.right,e));case "not":return !ge(j(t.operand,e));case "literal":return t.datetime?{datetime:t.value}:t.value;case "field":return Pi(e,t.path);case "call":{let n=t.args.map(s=>j(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 I(`Unbekannte Funktion '${t.fn}'.`)}}case "compare":{let[n,r]=Ri(j(t.left,e),j(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 ge(t){return t===true}function Ri(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=Ci.exec(o);if(a)return Number(a[1]);let c=Fn(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 Hn(t){let e=new zt(vi(t)).parse();return n=>ge(j(e,n))}var he=class extends Error{constructor(e){super(e),this.name="ODataUrlError";}};function st(t){let e=t.trim();if(e.startsWith("'"))return _i(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 _i(t){if(!t.endsWith("'")||t.length<2)throw new he(`Zeichenkette ohne schlie\xDFendes Apostroph: ${t}`);return t.slice(1,-1).replace(/''/g,"'")}function ki(t){let e=Yt(t);return Ii(e,",").map(r=>{let s=Di(r,"=");return s===-1?{value:st(r)}:{name:r.slice(0,s).trim(),value:st(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=Oi(e,n);i=ki(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=>Yt(l)).filter(l=>l.length>0),c={entitySet:Yt(s),segments:a};return i&&(c.key=i),c}function Oi(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 he(`Schlie\xDFende Klammer fehlt in: ${t}`)}function Ii(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 Di(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 Yt(t){try{return decodeURIComponent(t)}catch{return t}}var it="DEMO",Zt="demo",en="100",xe="/sap/opu/odata/IWFND/CATALOGSERVICE;v=2",Ni="/sap/opu/odata4/",A=class extends Error{constructor(n,r,s,i={}){super(s);this.status=n;this.code=r;this.headers=i;}},ot=class{constructor(e={}){this.options=e;for(let n of rt)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 A(403,"HTTP/403","CSRF token validation failed",{"x-csrf-token":"Required"});let p;if(i===xe||i.startsWith(`${xe}/`))p=this.handleCatalog(u,i.slice(xe.length),o,n);else {if(i.startsWith(Ni))throw new A(404,"HTTP/404",`Resource not found for segment '${i}'`);{let f=rt.find(g=>i===g.path||i.startsWith(`${g.path}/`));if(!f)throw new A(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${i}'`);p=this.handleService(f,u,i.slice(f.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??it}:${this.options.password??Zt}`).toString("base64")}`,i=a=>new A(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??en))throw i(o?`Mandant ${o} existiert nicht`:"Anmeldung fehlgeschlagen")}handleCatalog(e,n,r,s){if(e!=="GET")throw new A(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 A(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${i}'`);let a=[...rt].map(u=>({__metadata:{id:`${s.base}${xe}/ServiceCollection('${u.technicalName}_0001')`,uri:`${s.base}${xe}/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 A(404,"/IWFND/CM_MGW/002",`Service '${u}' not found`);return this.json(200,{d:m})}let c=Wn(r),{rows:l,count:d}=Jt(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 A(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,Un(e));if(a==="$batch"){if(n!=="POST")throw new A(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 A(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 A(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Parameter '${a.name}' is missing`);continue}o[a.name]=st(c);}if(n==="ReleaseSalesOrder"){let a=String(o.SalesOrder??""),c=this.rows("A_SalesOrder").find(l=>l.SalesOrder===a);if(!c)throw new A(404,"SD/001",`Sales order ${a} does not exist`);if(c.OverallSDProcessStatus==="C")throw new A(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 A(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 he?new A(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR",h.message):h}let d=e.entityTypes.find(h=>h.set===l.entitySet);if(!d)throw new A(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${l.entitySet}'`);let u=Wn(s),m=this.rows(d.set),p;if(l.key&&(p=this.find(d,l.key),!p))throw new A(404,"/IWBEP/CM_MGW_RT/020",`Resource not found for the segment '${d.set}'`);let f=[...l.segments],g=false;if(f[f.length-1]==="$count"&&(g=true,f.pop()),f.length>0){if(!p)throw new A(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR","Navigation requires a key");if(f.length>1)throw new A(501,"HTTP/501","Only one navigation level is supported");let h=d.navigations.find(y=>y.name===f[0]);if(!h)throw new A(404,"/IWFND/CM_MGW/002",`Resource not found for the segment '${f[0]}'`);let S=this.typeOf(h.target),T=this.related(p,h);if(h.multiplicity==="1"){if(p=T[0],!p)throw new A(404,"/IWBEP/CM_MGW_RT/020",`No ${h.target} for this ${d.set}`);d=S,m=[];}else m=T,p=void 0,d=S;}switch(n){case "GET":{if(p){let T=this.json(200,{d:this.present(e,d,p,o,u)});return T.headers.etag=this.etag(d,p),T}if(g){let T=Jt(m,{...u,top:void 0,skip:void 0,raw:u.raw});return {status:200,headers:{"content-type":"text/plain"},body:String(T.count)}}let{rows:h,count:S}=Jt(m,u);return this.json(200,{d:{results:h.map(T=>this.present(e,d,T,o,u)),...u.inlinecount==="allpages"?{__count:String(S)}:{}}})}case "POST":{if(p||l.key)throw new A(405,"HTTP/405","POST on a single entity is not allowed (use MERGE/PATCH)");if(d.creatable===false)throw new A(405,"HTTP/405",`${d.set} is not creatable`);let h=this.parseBody(a),S=this.create(d,h,o),T=this.json(201,{d:this.present(e,d,S,o,{expand:this.deepExpands(d,h)})});return T.headers.location=this.uri(o,e,d,S),T.headers.etag=this.etag(d,S),T}case "MERGE":case "PATCH":case "PUT":{if(!p)throw new A(405,"HTTP/405",`${n} requires a single entity`);if(d.updatable===false)throw new A(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 A(405,"HTTP/405","DELETE requires a single entity");if(d.deletable===false)throw new A(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 A(405,"HTTP/405",`Method ${n} not allowed`)}}parseBody(e){if(!e||e.trim()==="")throw new A(400,"/IWCOR/CX_DS_BODY_MISSING","Request body is empty");let n;try{n=JSON.parse(e);}catch{throw new A(400,"/IWCOR/CX_DS_JSON_SYNTAX_ERROR","Request body is not valid JSON")}if(n===null||typeof n!="object"||Array.isArray(n))throw new A(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 A(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 A(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 A(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 A(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 A(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 A(400,"/IWCOR/CX_DS_BATCH","multipart/mixed body with boundary expected");let o=`batchresponse_${randomUUID()}`,a=[];for(let c of qn(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
|
|
42
42
|
\r
|
|
43
|
-
`),m=u===-1?"":c.slice(u+4),p=this.snapshot(),f=[],g;for(let h of
|
|
44
|
-
${
|
|
45
|
-
${
|
|
43
|
+
`),m=u===-1?"":c.slice(u+4),p=this.snapshot(),f=[],g;for(let h of qn(m,d)){let S=this.runBatchPart(e,h,r);if(S.status>=400){g=S;break}f.push(S);}if(g)this.restore(p),a.push(`--${o}\r
|
|
44
|
+
${Qt(g)}`);else {let h=`changesetresponse_${randomUUID()}`,S=f.map(T=>`--${h}\r
|
|
45
|
+
${Qt(T)}`);a.push(`--${o}\r
|
|
46
46
|
Content-Type: multipart/mixed; boundary=${h}\r
|
|
47
47
|
\r
|
|
48
48
|
${S.join(`\r
|
|
49
49
|
`)}\r
|
|
50
50
|
--${h}--`);}continue}a.push(`--${o}\r
|
|
51
|
-
${
|
|
51
|
+
${Qt(this.runBatchPart(e,c,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,
|
|
54
|
-
`),
|
|
53
|
+
`}}runBatchPart(e,n,r){let s=Mi(n);if(!s)return this.errorResponse(new A(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)}},c=i.select&&i.select.length>0?new Set(i.select.map(d=>d.split("/")[0])):void 0;if(c){for(let d of c)if(!n.properties.some(u=>u.name===d)&&!n.navigations.some(u=>u.name===d))throw new A(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Property '${d}' is invalid for $select`)}for(let d of n.properties)c&&!c.has(d.name)||(a[d.name]=r[d.name]??null);let l=new Set((i.expand??[]).map(d=>d.split("/")[0]));for(let d of l)if(!n.navigations.some(u=>u.name===d))throw new A(400,"/IWBEP/CX_MGW_TECH_EXCEPTION",`Navigation property '${d}' is invalid for $expand`);for(let d of n.navigations)if(!(c&&!c.has(d.name)&&!l.has(d.name)))if(l.has(d.name)){let u=this.typeOf(d.target),m=this.types.get(d.target).service,p=this.related(r,d).map(f=>this.present(m,u,f,s,{}));a[d.name]=d.multiplicity==="*"?{results:p}:p[0]??null;}else a[d.name]={__deferred:{uri:`${o}/${d.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 I&&(e=new A(400,"/IWCOR/CX_DS_EXPR_SYNTAX_ERROR",`Invalid $filter: ${e.message}`)),e instanceof A?{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 A(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 A(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 A(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(d=>d.SalesOrder===r.SalesOrder).reduce((d,u)=>Math.max(d,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 A(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 c=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=(c.length>0?Math.max(...c):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(c=>String(c[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 A(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 A(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 A(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 A(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=it),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 Wn(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 A(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 A(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 Jt(t,e){let n=t;if(e.filter&&(n=n.filter(Hn(e.filter))),e.orderby){let o=e.orderby.split(",").map(a=>{let[c,l]=a.trim().split(/\s+/);if(!c)throw new A(400,"/IWCOR/CX_DS_URI_SYNTAX_ERROR","Empty $orderby clause");return {field:c,desc:(l??"asc").toLowerCase()==="desc"}});n=[...n].sort((a,c)=>{for(let{field:l,desc:d}of o){let u=xi(a[l],c[l]);if(u!==0)return d?-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 xi(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 qn(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 Mi(t){let e=t.replace(/\r\n/g,`
|
|
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
56
|
`),i=s===-1?r:r.slice(0,s),o=s===-1?void 0:r.slice(s+2).trim()||void 0,a={};for(let c of i.split(`
|
|
57
|
-
`)){let l=c.indexOf(":");l!==-1&&(a[c.slice(0,l).trim().toLowerCase()]=c.slice(l+1).trim());}return {method:
|
|
58
|
-
`)}function
|
|
57
|
+
`)){let l=c.indexOf(":");l!==-1&&(a[c.slice(0,l).trim().toLowerCase()]=c.slice(l+1).trim());}return {method:n[1].toUpperCase(),url:n[2],headers:a,body:o}}var Ui={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 Qt(t){let e=["Content-Type: application/http","Content-Transfer-Encoding: binary","",`HTTP/1.1 ${t.status} ${Ui[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 Kn(t){return {name:"default",baseUrl:t,authType:"basic",username:it,password:Zt,sapClient:en}}async function Gn(t={}){let e=new ot,n=createServer((i,o)=>{let a=[];i.on("data",c=>a.push(c)),i.on("end",()=>{let c={};for(let[d,u]of Object.entries(i.headers))c[d.toLowerCase()]=Array.isArray(u)?u.join(", "):u;let l=e.handle({method:i.method??"GET",url:i.url??"/",headers:c,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 K=z$2.object({name:z$2.string().default("default"),baseUrl:z$2.url(),sapClient:z$2.string().optional()}),Li=K.extend({authType:z$2.literal("basic"),username:z$2.string().min(1),password:z$2.string().min(1)}),Bi=K.extend({authType:z$2.literal("user-basic")}),Fi=K.extend({authType:z$2.literal("oauth2"),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1),tokenServiceUrl:z$2.url()}),Hi=K.extend({authType:z$2.literal("apikey"),apiKey:z$2.string().min(1)}),ji=K.extend({authType:z$2.literal("ias"),tenantUrl:z$2.url(),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1)}),Vi=K.extend({authType:z$2.literal("xsuaa"),xsuaaUrl:z$2.url(),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1)}),Wi=K.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()}),qi=K.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)}),ye=z$2.discriminatedUnion("authType",[Li,Bi,Fi,Hi,ji,Vi,Wi,qi]);function Me(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=ye.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
|
|
62
|
+
Set the required SAP_* environment variables.`)}return r.data}var tn=/^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/,Ki=z$2.enum(["core","odata","idoc","rfc"]),Gi=z$2.object({readOnly:z$2.boolean().optional(),tiers:z$2.array(Ki).min(1).optional()}).strict(),zi=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:Gi.optional(),createdAt:z$2.string().optional(),revoked:z$2.boolean().optional()}).strict(),Yi=z$2.object({version:z$2.literal(1),destinations:z$2.record(z$2.string(),z$2.unknown()),tokens:z$2.array(zi).default([])}).strict(),zn=/\$\{([A-Z_][A-Z0-9_]*)\}/g;function nn(t,e){let n=[],r=s=>{if(typeof s=="string"){let i=[...s.matchAll(zn)].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(zn,(a,c)=>e[c]??"")}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 Xi=ye.options.map(t=>t.shape.authType.value);function Yn(t,e){return e.map(n=>{let r=n.path.join("."),s=r==="authType"?`fehlt oder ist unbekannt (erlaubt: ${Xi.join(", ")})`:n.message;return `${t}${r?`${r}: `:""}${s}`})}function at(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=Yi.safeParse(t);if(!n.success)return {ok:false,errors:Yn("",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,c]of i){if(!tn.test(a)){r.push(`Destination "${a}": unzul\xE4ssiger Name \u2014 erlaubt sind Buchstaben, Ziffern, "_", "." und "-" (max. 64 Zeichen, kein Leerzeichen).`);continue}if(c===null||typeof c!="object"||Array.isArray(c)){r.push(`Destination "${a}": muss ein Objekt sein.`);continue}let l=nn(c,e);if(l.missing.length>0){r.push(`Destination "${a}": Umgebungsvariable(n) nicht gesetzt: ${l.missing.join(", ")}.`);continue}let d={...l.value,name:a},u=ye.safeParse(d);if(!u.success){r.push(...Yn(`Destination "${a}": `,u.error.issues));continue}s.set(a,u.data);}let o=n.data.tokens;return o.forEach((a,c)=>{Object.hasOwn(n.data.destinations,a.destination)||r.push(`tokens[${c}]${a.label?` ("${a.label}")`:""}: Destination "${a.destination}" existiert nicht.`);}),r.length>0?{ok:false,errors:r}:{ok:true,value:{destinations:s,tokens:o}}}function Ue(t,e){let n=createHash("sha256").update(t).digest(),r=createHash("sha256").update(e).digest();return timingSafeEqual(n,r)}var to="gsm",Jn=10,ct=6e4,no=1e4;function Qn(t){return `sha256:${createHash("sha256").update(t,"utf8").digest("hex")}`}function Zn(t){let e=randomBytes(32).toString("base64url"),n=`${to}_${t}_${e}`;return {token:n,hash:Qn(n)}}var Se=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=ro(e),r=n??"",s=this.apiKey!==void 0&&Ue(r,this.apiKey),i=Buffer.from(Qn(r),"utf8"),o,a=false;for(let c of this.tokens()){let l=Buffer.from(c.hash,"utf8");l.length===i.length&&timingSafeEqual(l,i)&&(c.revoked?a=true:o||(o=c));}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>ct){this.failures.delete(e);return}if(n.count>=Jn)return Math.max(1,Math.ceil((ct-r)/1e3))}recordFailure(e){let n=this.now();if(this.failures.size>=no)for(let[s,i]of this.failures)n-i.windowStart>ct&&this.failures.delete(s);let r=this.failures.get(e);if(!r||n-r.windowStart>ct){this.failures.set(e,{count:1,windowStart:n});return}r.count+=1,r.count===Jn&&(r.windowStart=n);}};function ro(t){return typeof t!="string"?void 0:/^Bearer\s+(.+)$/i.exec(t.trim())?.[1]?.trim()||void 0}var er=["core","odata","idoc","rfc"],P=class extends Error{constructor(n,r=400){super(n);this.status=r;this.name="AdminError";}},Te=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=tr(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(!tn.test(e))throw new P(`"${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 P(`Destination "${e}" already exists. Use --replace to overwrite it.`,409);let o;if(r.fromEnv){let l;try{let{name:d,...u}=Me(this.env);l=u;}catch(d){throw new P(d instanceof Error?d.message:String(d),400)}o=nr(l);}else if(o=nr(n),Object.keys(o).length===0)throw new P("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:c}=this.validate(a,!!r.allowMissingEnv,400);return this.write(a),{replaced:i,warnings:c}}removeDestination(e,n={}){let r=this.read({mustExist:true});if(!r.destinations[e])throw new P(`Destination "${e}" not found in ${this.filePath}.`,404);let s=tr(r,e);if(s.length>0&&!n.force)throw new P(`Destination "${e}" still has ${s.length} token(s) (${s.map(c=>typeof c.label=="string"?c.label:re(c.hash)).join(", ")}). Revoke them first, or pass --force to remove them together.`,409);if(Object.keys(r.destinations).length===1)throw new P(`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(c=>c.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:re(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 P(`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),d=l.filter(u=>!er.includes(u));if(d.length>0||l.length===0)throw new P(`Unknown tier(s): ${d.join(", ")||"(empty)"}. Allowed: ${er.join(", ")}.`,400);s.tiers=l;}if(n.label!==void 0&&n.label.length>200)throw new P("Label must not exceed 200 characters.",400);let{token:i,hash:o}=Zn(e),a={hash:o,destination:e,...n.label?{label:n.label}:{},...Object.keys(s).length>0?{policy:s}:{},createdAt:this.now().toISOString()},c={...r,tokens:[...r.tokens,a]};return this.validate(c,true,400),this.write(c),{token:i,hash:o,entry:a}}revokeToken(e){let n=this.read({mustExist:true}),r=n.tokens.filter(d=>d.label===e),s=e.startsWith("sha256:")?e.slice(7):e,i=/^[0-9a-f]{8,64}$/i.test(s)?n.tokens.filter(d=>typeof d.hash=="string"&&d.hash.slice(7).startsWith(s.toLowerCase())):[],o=r.length>0?r:i;if(o.length===0)throw new P(`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 P(`"${e}" is ambiguous (${o.length} tokens). Use a longer hash prefix: ${o.map(d=>re(d.hash)).join(", ")}.`,409);let a=o[0],c=typeof a.label=="string"?a.label:void 0;if(a.revoked===true)return {hash:re(a.hash),label:c,alreadyRevoked:true};let l={...n,tokens:n.tokens.map(d=>d===a?{...d,revoked:true}:d)};return this.validate(l,true,400),this.write(l),{hash:re(a.hash),label:c,alreadyRevoked:false}}read({mustExist:e}){if(!existsSync(this.filePath)){if(e)throw new P(`${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 P(`${this.filePath}: not valid JSON (${s instanceof Error?s.message:String(s)}).`,422)}if(n===null||typeof n!="object"||Array.isArray(n))throw new P(`${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=nn(e.destinations,this.env),i=[],o=this.env;if(s.missing.length>0&&n){o={...this.env};for(let c of s.missing)o[c]="placeholder";i.push(`Environment variable(s) not set here: ${s.missing.join(", ")}. The server needs them at load time.`);}let a=at(e,o);if(!a.ok)throw new P(`${r===400?"Refusing to write an invalid file":`${this.filePath} is invalid`}:
|
|
63
63
|
${a.errors.map(c=>` - ${c}`).join(`
|
|
64
|
-
`)}`,r);return {warnings:i}}write(e){let
|
|
65
|
-
`;try{writeFileSync(r,s,{mode:384}),renameSync(r,this.filePath);}catch(i){try{unlinkSync(r);}catch{}throw i}}};function
|
|
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 tr(t,e){return t.tokens.filter(n=>n.destination===e)}function re(t){return typeof t=="string"?t.replace(/^sha256:/,"").slice(0,12):"?"}function nr(t){return Object.fromEntries(Object.entries(t).filter(([,e])=>e!==void 0))}var rr=new Set(["destinations","tokens"]),dt=`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
|
-
`,L=class extends Error{constructor(
|
|
90
|
+
`,L=class extends Error{constructor(n,r=1){super(n);this.exitCode=r;}};async function sr(t,e){let n=go(t);try{if(n.flags.help)return e.stdout(dt),0;let[r,s,...i]=n.positional;if(r==="destinations")switch(s){case "list":return ho(n,e);case "add":return So(i[0],n,e);case "remove":return To(i[0],n,e);default:throw new L(`Unknown command "destinations ${s??""}".
|
|
91
91
|
|
|
92
|
-
${
|
|
92
|
+
${dt}`,2)}if(r==="tokens")switch(s){case "list":return Ao(n,e);case "issue":return bo(i[0],n,e);case "revoke":return wo(i[0],n,e);default:throw new L(`Unknown command "tokens ${s??""}".
|
|
93
93
|
|
|
94
|
-
${
|
|
94
|
+
${dt}`,2)}throw new L(dt,2)}catch(r){return r instanceof L?(e.stderr(`${r.message}
|
|
95
95
|
`),r.exitCode):r instanceof P?(e.stderr(`${r.message}
|
|
96
96
|
`),1):(e.stderr(`Error: ${r instanceof Error?r.message:String(r)}
|
|
97
|
-
`),1)}}var
|
|
98
|
-
`),0}var
|
|
99
|
-
`);return
|
|
100
|
-
`),0}function
|
|
101
|
-
`),0}function
|
|
102
|
-
`),0}function
|
|
103
|
-
`),
|
|
104
|
-
`)),0}function
|
|
105
|
-
`),0):(
|
|
106
|
-
`),0)}function
|
|
107
|
-
`)}var
|
|
97
|
+
`),1)}}var mo=new Set(["help","h","from-env","replace","force","read-only","allow-missing-env"]),fo=new Set(["set"]);function go(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(mo.has(o)){n[o]=true;continue}let a=i===-1?t[++r]:s.slice(i+1);if(a===void 0)throw new L(`Missing value for --${o}.`,2);if(fo.has(o)){let c=n[o]??[];c.push(a),n[o]=c;}else n[o]=a;}return {positional:e,flags:n}}function lt(t,e){let n=t.flags[e];return typeof n=="string"?n:void 0}function Ae(t,e){let n=lt(t,"destinations")??e.env.SAP_MCP_DESTINATIONS;if(!n||n.trim().length===0)throw new L("No destinations file given. Pass --destinations <path> or set SAP_MCP_DESTINATIONS.",2);return new Te({filePath:resolve(n.trim()),env:e.env,now:e.now})}function ho(t,e){let n=Ae(t,e).listDestinations();return e.stdout(`${ir(["NAME","BASE URL","AUTH","CLIENT","TOKENS"],n.map(r=>[r.name,r.baseUrl,r.authType,r.sapClient??"-",String(r.tokens)]))}
|
|
98
|
+
`),0}var yo={"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 So(t,e,n){if(!t)throw new L("Usage: destinations add <name> ...",2);let r=Ae(e,n),s={};if(!e.flags["from-env"]){for(let[a,c]of Object.entries(yo)){let l=lt(e,a);l!==void 0&&(s[c]=l);}for(let a of e.flags.set??[]){let c=a.indexOf("=");if(c<=0)throw new L(`--set expects field=value, got "${a}".`,2);s[a.slice(0,c)]=a.slice(c+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
|
+
`);return n.stderr(`Destination "${t}" ${i?"replaced":"added"} in ${r.filePath}.
|
|
100
|
+
`),0}function To(t,e,n){if(!t)throw new L("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 Ao(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(`${ir(["LABEL","DESTINATION","HASH","POLICY","STATUS","CREATED"],n)}
|
|
102
|
+
`),0}function bo(t,e,n){if(!t)throw new L("Usage: tokens issue <destination> [--label ...]",2);let r=Ae(e,n),s=lt(e,"tiers"),i=lt(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 ${re(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 wo(t,e,n){if(!t)throw new L("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
|
+
`),0):(n.stderr(`Token ${r}${s?` (${s})`:""} revoked. A running server rejects it from the next request on.
|
|
106
|
+
`),0)}function ir(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 se="default",ko=250,or=1e3,be=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={...Me(e),name:se};return new t({source:"env",config:{destinations:new Map([[se,n]]),tokens:[]},env:e})}static async fromFile(e,n={}){let r=n.env??process.env,s=resolve(e),i=r.SAP_BASE_URL?{...Me(r),name:se}: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([[se,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
108
|
${a.errors.map(c=>` - ${c}`).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(re);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,t)=>{(e.mtimeMs!==t.mtimeMs||e.size!==t.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();},Ro),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 t={version:this.currentVersion,names:this.names(),tokenCount:this.current.tokens.length};for(let r of this.changeListeners)r(t);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 t;try{t=JSON.parse(e);}catch(s){return {ok:false,errors:[`${this.filePath}: kein g\xFCltiges JSON (${s instanceof Error?s.message:String(s)})`]}}let r=ot(t,this.env);return r.ok?(this.envDefault&&(r.value.destinations.has(re)?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(re,this.envDefault)),{ok:true,value:r.value,raw:e}):r}};var ve=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(t=>t.docnum===e)}list(){return [...this.buffer]}get count(){return this.buffer.length}};var ko=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",parseTagValue:false,processEntities:false,htmlEntities:false,isArray:n=>n==="IDOC"||n.startsWith("E1")||n.startsWith("E2")}),Ue=new Set(["@_SEGMENT","@_BEGIN"]);function tn(n,e){return !!(n.startsWith("E1")||n.startsWith("E2")||typeof e=="object"&&e!==null&&"@_SEGMENT"in e)}function ir(n){let e=[];for(let[t,r]of Object.entries(n)){if(Ue.has(t)||!tn(t,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(!Ue.has(c))if(tn(c,l)){let d=Array.isArray(l)?l:[l];for(let u of d){if(typeof u!="object"||u===null)continue;let m={},p=ir(u);for(let[f,g]of Object.entries(u))Ue.has(f)||!tn(f,g)&&typeof g!="object"&&(m[f]=String(g));a.push({name:c,fields:m,...p.length>0?{children:p}:{}});}}else typeof l!="object"&&(o[c]=String(l));e.push({name:t,fields:o,...a.length>0?{children:a}:{}});}}return e}function or(n){let e=ko.parse(n),t=Object.keys(e).find(u=>u!=="?xml");if(!t)throw new Error("Invalid IDoc XML: no root element found");let r=t,i=e[t].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))!Ue.has(m)&&typeof p!="object"&&(c[m]=String(p));}let l={};for(let[u,m]of Object.entries(o))u==="EDI_DC40"||Ue.has(u)||(l[u]=m);let d=ir(l);return {idocType:r,controlRecord:c,segments:d}}var Oo=10*1024*1024,lt=class extends Error{};function Io(n,e){return new Promise((t,r)=>{let s=[],i=0,o=false;n.on("data",a=>{if(!o){if(i+=a.length,i>e){o=true,s.length=0,r(new lt(`Request body exceeds ${e} bytes`));return}s.push(a);}}),n.on("end",()=>{o||t(Buffer.concat(s).toString("utf-8"));}),n.on("error",a=>{o||r(a);});})}function Do(n,e){if(e.length===0||typeof n!="string")return false;if(n.startsWith("Bearer "))return Me(n.slice(7),e);if(n.startsWith("Basic ")){let t=Buffer.from(n.slice(6),"base64").toString("utf-8"),r=t.indexOf(":");return r===-1?false:Me(t.slice(r+1),e)}return false}function No(n){if(typeof n!="string")return true;let e=(n.split(";")[0]??"").trim().toLowerCase();return e.endsWith("xml")||e==="application/x-sap.idoc"}function ut(n,e={}){let t=e.maxBodyBytes??Oo;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&&!Do(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(!No(r.headers["content-type"])){s.writeHead(415,{"Content-Type":"text/plain"}),s.end("Unsupported Media Type: expected XML"),r.destroy();return}Io(r,t).then(i=>{let{idocType:o,controlRecord:a,segments:c}=or(i),l={docnum:a.DOCNUM||`UNKNOWN_${Date.now()}`,idocType:o,mesType:a.MESTYP||"",receivedAt:new Date,rawXml:i,parsed:{controlRecord:a,segments:c}};n.add(l),s.writeHead(200,{"Content-Type":"text/plain"}),s.end("OK");}).catch(i=>{if(i instanceof lt){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 ar(n,e,t={}){let r=ut(e,t);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:n;s({port:l,close:()=>new Promise((d,u)=>{o.close(m=>m?u(m):d());})});};t.host?o.listen(n,t.host,a):o.listen(n,a),o.on("error",i);})}var dr=1;function pt(n){if(!n.directory)return [];let e=join(n.directory,n.fileName),t;try{t=readFileSync(e,"utf-8");}catch{return []}try{let r=JSON.parse(t);if(r?.version!==dr||!Array.isArray(r.entries))return n.onWarn?.(`Cache-Datei ${e} hat ein unbekanntes Format und wird verworfen.`),[];let s=Date.now()-n.ttlMs;return r.entries.filter(i=>i&&typeof i.key=="string"&&typeof i.storedAt=="number"&&i.storedAt>s)}catch(r){return n.onWarn?.(`Cache-Datei ${e} ist unlesbar und wird verworfen. Der Cache wird aus SAP neu aufgebaut.`,r),[]}}function mt(n,e){if(!e.directory)return;let t=join(e.directory,e.fileName),r=`${t}.tmp`;try{mkdirSync(dirname(t),{recursive:!0}),writeFileSync(r,JSON.stringify({version:dr,entries:n}),"utf-8"),renameSync(r,t);}catch(s){e.onWarn?.(`Cache konnte nicht nach ${t} geschrieben werden. Der Betrieb l\xE4uft weiter, nach einem Neustart ist der Cache aber leer.`,s);try{unlinkSync(r);}catch{}}}var Ho=30,jo="catalog-cache.json",lr="catalog",G=class{cache=null;ttlMs;key;persistence;constructor(e={}){let t=typeof e=="number"?{ttlMinutes:e}:e;this.ttlMs=(t.ttlMinutes??Ho)*60*1e3;let r=(t.baseUrl??"").replace(/\/+$/,"").toLowerCase();if(this.key=r?`${r}|${lr}`:lr,t.persistDir){this.persistence={directory:t.persistDir,fileName:jo,ttlMs:this.ttlMs,...t.onWarn?{onWarn:t.onWarn}:{}};let s=pt(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&&mt(this.cache?[{key:this.key,value:this.cache.entries,storedAt:this.cache.timestamp}]:[],this.persistence);}};var nn=0;function ur(n){let e=n.docnum??`${Date.now().toString().padStart(13,"0")}${String(nn++).padStart(3,"0")}`;nn>=1e3&&(nn=0);let t=n.mesType??n.idocType.replace(/\d+$/,"");return {TABNAM:"EDI_DC40",DOCNUM:e,DIRECT:"2",IDOCTYP:n.idocType,MESTYP:t,SNDPOR:n.sndpor,SNDPRT:n.sndprt??"LS",SNDPRN:n.sndprn,RCVPOR:n.rcvpor,RCVPRT:n.rcvprt??"LS",RCVPRN:n.rcvprn,...n.extra}}function Vo(n,e,t){return e.required&&t===""?{segmentName:n,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:t,violation:"Required field is empty"}:t===""?null:(e.dataType==="CHAR"||e.dataType==="NUMC")&&t.length>e.length?{segmentName:n,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:t,violation:`Field value exceeds max length ${e.length} (actual: ${t.length})`}:e.dataType==="NUMC"&&!/^\d*$/.test(t)?{segmentName:n,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:t,violation:"NUMC field must contain only digits"}:e.dataType==="DATS"&&!/^\d{8}$/.test(t)?{segmentName:n,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:t,violation:"DATS field must be in YYYYMMDD format"}:e.dataType==="TIMS"&&!/^\d{6}$/.test(t)?{segmentName:n,fieldName:e.name,expectedType:e.dataType,expectedLength:e.length,actualValue:t,violation:"TIMS field must be in HHMMSS format"}:null}function Wo(n,e){return n.filter(t=>t.name===e).length}function pr(n,e,t){let r=new Set;for(let s of n){if(r.has(s.name))continue;r.add(s.name);let i=e.get(s.name);if(!i)continue;let o=Wo(n,s.name);o>i.maxOccurrence&&t.push({segmentName:s.name,fieldName:"",expectedType:"",expectedLength:i.maxOccurrence,actualValue:String(o),violation:`Segment count ${o} exceeds maxOccurrence ${i.maxOccurrence}`});}for(let s of n){let i=e.get(s.name);if(!i){t.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=Vo(s.name,o,a);c&&t.push(c);}s.children&&s.children.length>0&&pr(s.children,e,t);}}function mr(n,e){if(!e)return [];let t=new Map;for(let s of e.segments)t.set(s.name,s);let r=[];return pr(n,t,r),r}var Ko=new XMLBuilder({ignoreAttributes:false,attributeNamePrefix:"@_",format:true,suppressEmptyNode:false});function fr(n){let e={"@_SEGMENT":"1"};for(let[t,r]of Object.entries(n.fields))e[t]=r;if(n.children)for(let t of n.children){let r=fr(t),s=e[t.name];s?Array.isArray(s)?s.push(r):e[t.name]=[s,r]:e[t.name]=r;}return e}function gr(n,e,t){let r={};for(let i of t){let o=fr(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"},[n]:{IDOC:{"@_BEGIN":"1",EDI_DC40:{"@_SEGMENT":"1",...e},...r}}};return Ko.build(s)}var ft=class{constructor(e,t,r){this.httpClient=e;this.sapClient=t;this.partnerDefaults=r;}async send(e){let t={...this.partnerDefaults,...e.partnerOverrides},r=ur({idocType:e.idocType,sndpor:t.sndpor??"",sndprn:t.sndprn??"",rcvpor:t.rcvpor??"",rcvprn:t.rcvprn??"",sndprt:t.sndprt,rcvprt:t.rcvprt,mesType:t.mesType,docnum:t.docnum,extra:t.extra});if(e.metadata){let u=mr(e.segments,e.metadata);if(u.length>0)return {success:false,docnum:r.DOCNUM,message:"Validation failed",violations:u}}let s=gr(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 gt=30,Go=5e3;function zo(n,e,t,r){let s=n?._meta?.progressToken,i=n?.sendNotification;if(s===void 0||typeof i!="function"||r.afterMs<=0)return ()=>{};let a=setInterval(()=>{let c=Date.now()-t;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 Yo="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 Xo(n){return {...n,content:[...n.content,{type:"text",text:`\u26A0\uFE0F ${Yo}`}]}}function Jo(n,e,t,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:n,correlationId:e,elapsedMs:t,sapCalls:r},null,2)}],isError:true}}function Qo(n,e){return {content:[{type:"text",text:JSON.stringify({aborted:true,reason:"Der Client hat die Verbindung verworfen; die Arbeit wurde eingestellt.",tool:n,correlationId:e})}],isError:true}}function hr(n,e,t={}){let{toolTimeoutMs:r,logger:s,tlsVerifyDisabled:i}=t,o=t.progressAfterMs??Go;return async(a,c)=>{let l=En(),{signal:d,deadline:u}=Rn(c?.signal,r),m={correlationId:l,toolName:n,signal:d,deadline:u,sapCalls:{count:0}},p=Date.now();return Cn(m,async()=>{let f=zo(c,m,p,{afterMs:o,...r?{budgetMs:r}:{}}),g="ok",h,S=(async()=>e(a,c))();S.catch(()=>{});try{h=await Promise.race([S,Zo(d,n)]),h?.isError&&(g="error");}catch(y){if(y instanceof $)g=y.timedOut?"timeout":"aborted",h=y.timedOut?Jo(n,l,Date.now()-p,m.sapCalls.count,r??0):Qo(n,l);else {g="error";let v=y instanceof Error?y.message:String(y);h={content:[{type:"text",text:JSON.stringify({error:v,tool:n,correlationId:l})}],isError:true};}}g==="ok"&&d?.aborted&&(g=d.reason?.name==="TimeoutError"?"timeout":"aborted"),f();let T=i?Xo(h):h;return s?.info({correlationId:l,tool:n,outcome:g,durationMs:Date.now()-p,sapCalls:m.sapCalls.count,resultBytes:ea(h),...r?{budgetMs:r}:{}},"MCP tool call"),T})}}function Zo(n,e){return n?new Promise((t,r)=>{let s=()=>{let i=n.reason?.name==="TimeoutError";r(new $(i,e));};if(n.aborted){s();return}n.addEventListener("abort",s,{once:true});}):new Promise(()=>{})}function ea(n){if(!n?.content)return 0;let e=0;for(let t of n.content)typeof t?.text=="string"&&(e+=Buffer.byteLength(t.text,"utf-8"));return e}var ta=1440*60*1e3,na="metadata-cache.json",k=class{cache=new Map;ttlMs;baseUrl;persistence;constructor(e={}){if(this.ttlMs=e.ttlMs??ta,this.baseUrl=ra(e.baseUrl),e.persistDir){this.persistence={directory:e.persistDir,fileName:na,ttlMs:this.ttlMs,...e.onWarn?{onWarn:e.onWarn}:{}};for(let t of pt(this.persistence))this.cache.set(t.key,{value:t.value,storedAt:t.storedAt});}}get size(){return this.cache.size}get(e){let t=this.key(e),r=this.cache.get(t);if(r){if(Date.now()-r.storedAt>this.ttlMs){this.cache.delete(t);return}return r.value}}set(e,t){this.cache.set(this.key(e),{value:t,storedAt:Date.now()}),this.persist();}has(e){return this.get(e)!==void 0}clear(){this.cache.clear(),this.persist();}key(e){let t=e.replace(/\/+$/,"");return this.baseUrl?`${this.baseUrl}|${t}`:t}persist(){this.persistence&&mt([...this.cache.entries()].map(([e,t])=>({key:e,value:t.value,storedAt:t.storedAt})),this.persistence);}};function ra(n){return n?n.replace(/\/+$/,"").toLowerCase():""}var yr="0.0.0-unknown",Sr="guniweb-sap-mcp";function ca(n){if(typeof n!="string"||n.length===0)return yr;let e=resolve(n);for(;;){try{let r=readFileSync(join(e,"package.json"),"utf-8"),s=JSON.parse(r);if(s.name===Sr&&typeof s.version=="string")return s.version}catch{}let t=dirname(e);if(t===e)return yr;e=t;}}var Tr=ca(import.meta.dirname),br=Sr;function vr(n){n.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:t,question:r,allowWrite:s})=>{let i=s===true?"":`
|
|
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(se);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:or},(e,n)=>{(e.mtimeMs!==n.mtimeMs||e.size!==n.size)&&this.scheduleReload();}),this.startupRecheck=setTimeout(()=>{this.startupRecheck=void 0,this.scheduleReload();},or+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();},ko),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=at(n,this.env);return r.ok?(this.envDefault&&(r.value.destinations.has(se)?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(se,this.envDefault)),{ok:true,value:r.value,raw:e}):r}};var we=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 Io=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",parseTagValue:false,processEntities:false,htmlEntities:false,isArray:t=>t==="IDOC"||t.startsWith("E1")||t.startsWith("E2")}),$e=new Set(["@_SEGMENT","@_BEGIN"]);function rn(t,e){return !!(t.startsWith("E1")||t.startsWith("E2")||typeof e=="object"&&e!==null&&"@_SEGMENT"in e)}function ar(t){let e=[];for(let[n,r]of Object.entries(t)){if($e.has(n)||!rn(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(!$e.has(c))if(rn(c,l)){let d=Array.isArray(l)?l:[l];for(let u of d){if(typeof u!="object"||u===null)continue;let m={},p=ar(u);for(let[f,g]of Object.entries(u))$e.has(f)||!rn(f,g)&&typeof g!="object"&&(m[f]=String(g));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 cr(t){let e=Io.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))!$e.has(m)&&typeof p!="object"&&(c[m]=String(p));}let l={};for(let[u,m]of Object.entries(o))u==="EDI_DC40"||$e.has(u)||(l[u]=m);let d=ar(l);return {idocType:r,controlRecord:c,segments:d}}var Do=10*1024*1024,ut=class extends Error{};function No(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 ut(`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 xo(t,e){if(e.length===0||typeof t!="string")return false;if(t.startsWith("Bearer "))return Ue(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:Ue(n.slice(r+1),e)}return false}function Mo(t){if(typeof t!="string")return true;let e=(t.split(";")[0]??"").trim().toLowerCase();return e.endsWith("xml")||e==="application/x-sap.idoc"}function pt(t,e={}){let n=e.maxBodyBytes??Do;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&&!xo(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(!Mo(r.headers["content-type"])){s.writeHead(415,{"Content-Type":"text/plain"}),s.end("Unsupported Media Type: expected XML"),r.destroy();return}No(r,n).then(i=>{let{idocType:o,controlRecord:a,segments:c}=cr(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 ut){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 dr(t,e,n={}){let r=pt(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 ur=1;function mt(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!==ur||!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 ft(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:ur,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 Vo=30,Wo="catalog-cache.json",pr="catalog",G=class{cache=null;ttlMs;key;persistence;constructor(e={}){let n=typeof e=="number"?{ttlMinutes:e}:e;this.ttlMs=(n.ttlMinutes??Vo)*60*1e3;let r=(n.baseUrl??"").replace(/\/+$/,"").toLowerCase();if(this.key=r?`${r}|${pr}`:pr,n.persistDir){this.persistence={directory:n.persistDir,fileName:Wo,ttlMs:this.ttlMs,...n.onWarn?{onWarn:n.onWarn}:{}};let s=mt(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&&ft(this.cache?[{key:this.key,value:this.cache.entries,storedAt:this.cache.timestamp}]:[],this.persistence);}};var sn=0;function mr(t){let e=t.docnum??`${Date.now().toString().padStart(13,"0")}${String(sn++).padStart(3,"0")}`;sn>=1e3&&(sn=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 qo(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 Ko(t,e){return t.filter(n=>n.name===e).length}function fr(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=Ko(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=qo(s.name,o,a);c&&n.push(c);}s.children&&s.children.length>0&&fr(s.children,e,n);}}function gr(t,e){if(!e)return [];let n=new Map;for(let s of e.segments)n.set(s.name,s);let r=[];return fr(t,n,r),r}var zo=new XMLBuilder({ignoreAttributes:false,attributeNamePrefix:"@_",format:true,suppressEmptyNode:false});function hr(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=hr(n),s=e[n.name];s?Array.isArray(s)?s.push(r):e[n.name]=[s,r]:e[n.name]=r;}return e}function yr(t,e,n){let r={};for(let i of n){let o=hr(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 zo.build(s)}var gt=class{constructor(e,n,r){this.httpClient=e;this.sapClient=n;this.partnerDefaults=r;}async send(e){let n={...this.partnerDefaults,...e.partnerOverrides},r=mr({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=gr(e.segments,e.metadata);if(u.length>0)return {success:false,docnum:r.DOCNUM,message:"Validation failed",violations:u}}let s=yr(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 ht=30,Yo=5e3;function Xo(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 Jo="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 Qo(t){return {...t,content:[...t.content,{type:"text",text:`\u26A0\uFE0F ${Jo}`}]}}function Zo(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 ea(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 Sr(t,e,n={}){let{toolTimeoutMs:r,logger:s,tlsVerifyDisabled:i}=n,o=n.progressAfterMs??Yo;return async(a,c)=>{let l=Pn(),{signal:d,deadline:u}=kn(c?.signal,r),m={correlationId:l,toolName:t,signal:d,deadline:u,sapCalls:{count:0}},p=Date.now();return Rn(m,async()=>{let f=Xo(c,m,p,{afterMs:o,...r?{budgetMs:r}:{}}),g="ok",h,S=(async()=>e(a,c))();S.catch(()=>{});try{h=await Promise.race([S,ta(d,t)]),h?.isError&&(g="error");}catch(y){if(y instanceof $)g=y.timedOut?"timeout":"aborted",h=y.timedOut?Zo(t,l,Date.now()-p,m.sapCalls.count,r??0):ea(t,l);else {g="error";let b=y instanceof Error?y.message:String(y);h={content:[{type:"text",text:JSON.stringify({error:b,tool:t,correlationId:l})}],isError:true};}}g==="ok"&&d?.aborted&&(g=d.reason?.name==="TimeoutError"?"timeout":"aborted"),f();let T=i?Qo(h):h;return s?.info({correlationId:l,tool:t,outcome:g,durationMs:Date.now()-p,sapCalls:m.sapCalls.count,resultBytes:na(h),...r?{budgetMs:r}:{}},"MCP tool call"),T})}}function ta(t,e){return t?new Promise((n,r)=>{let s=()=>{let i=t.reason?.name==="TimeoutError";r(new $(i,e));};if(t.aborted){s();return}t.addEventListener("abort",s,{once:true});}):new Promise(()=>{})}function na(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 ra=1440*60*1e3,sa="metadata-cache.json",k=class{cache=new Map;ttlMs;baseUrl;persistence;constructor(e={}){if(this.ttlMs=e.ttlMs??ra,this.baseUrl=ia(e.baseUrl),e.persistDir){this.persistence={directory:e.persistDir,fileName:sa,ttlMs:this.ttlMs,...e.onWarn?{onWarn:e.onWarn}:{}};for(let n of mt(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&&ft([...this.cache.entries()].map(([e,n])=>({key:e,value:n.value,storedAt:n.storedAt})),this.persistence);}};function ia(t){return t?t.replace(/\/+$/,"").toLowerCase():""}var Tr="0.0.0-unknown",Ar="guniweb-sap-mcp";function la(t){if(typeof t!="string"||t.length===0)return Tr;let e=resolve(t);for(;;){try{let r=readFileSync(join(e,"package.json"),"utf-8"),s=JSON.parse(r);if(s.name===Ar&&typeof s.version=="string")return s.version}catch{}let n=dirname(e);if(n===e)return Tr;e=n;}}var br=la(import.meta.dirname),wr=Ar;function vr(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.
|
|
@@ -115,19 +115,19 @@ If the user's question implies a write operation (create, update, delete, modify
|
|
|
115
115
|
|
|
116
116
|
**Question:** ${r}
|
|
117
117
|
**Service:** ${e}
|
|
118
|
-
**Entity Set:** ${
|
|
118
|
+
**Entity Set:** ${n}
|
|
119
119
|
|
|
120
120
|
## Step 1: Read Entity Schema
|
|
121
121
|
|
|
122
122
|
First, read the entity schema resource to understand available properties and types:
|
|
123
|
-
\`sap://schema/${encodeURIComponent(e)}/${encodeURIComponent(
|
|
123
|
+
\`sap://schema/${encodeURIComponent(e)}/${encodeURIComponent(n)}\`
|
|
124
124
|
|
|
125
125
|
## Step 2: Map to Structured Filters
|
|
126
126
|
|
|
127
127
|
Map the natural language question to structured filters using the property names from the schema.
|
|
128
128
|
Use the \`sap_nl_query\` tool with these parameters:
|
|
129
129
|
- \`serviceUrl\`: "${e}"
|
|
130
|
-
- \`entitySet\`: "${
|
|
130
|
+
- \`entitySet\`: "${n}"
|
|
131
131
|
- \`filters\`: Array of { field, operator, value } objects
|
|
132
132
|
|
|
133
133
|
## Step 3: OData Filter Syntax Reference
|
|
@@ -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 Er(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
|
|
@@ -175,7 +175,7 @@ ${i}`}}]}});}function Ar(n){n.registerPrompt("explore-sales-orders",{title:"Expl
|
|
|
175
175
|
4. Use sap_query to fetch a sample of sales orders with key fields like SalesOrder, SalesOrganization, SoldToParty
|
|
176
176
|
5. Summarize the data model and show me the results
|
|
177
177
|
|
|
178
|
-
Focus on the most important fields and relationships.`}}]})),
|
|
178
|
+
Focus on the most important fields and relationships.`}}]})),t.registerPrompt("check-material-availability",{title:"Check Material Availability",description:"Look up material master data and check availability in SAP",argsSchema:{materialNumber:z$2.string().optional().describe("SAP Material Number (leave empty to search)")}},async({materialNumber:e})=>({messages:[{role:"user",content:{type:"text",text:`I want to check material availability in SAP. Please help me:
|
|
179
179
|
|
|
180
180
|
1. Find the material master service (common URL: /sap/opu/odata/sap/API_PRODUCT_SRV)
|
|
181
181
|
2. Use sap_list_services to discover entity sets related to materials
|
|
@@ -183,7 +183,7 @@ Focus on the most important fields and relationships.`}}]})),n.registerPrompt("c
|
|
|
183
183
|
4. ${e?`Look up material number: ${e}`:"Browse available materials to find the right one"}
|
|
184
184
|
5. Show material details including description, type, unit of measure, and availability info
|
|
185
185
|
|
|
186
|
-
Use sap_query with appropriate filters to find the material data.`}}]})),
|
|
186
|
+
Use sap_query with appropriate filters to find the material data.`}}]})),t.registerPrompt("manage-business-partners",{title:"Manage Business Partners",description:"Create, read, update Business Partners in SAP",argsSchema:{serviceUrl:z$2.string().optional().describe("OData service URL (leave empty to discover first)"),action:z$2.enum(["list","create","search"]).optional().describe("What to do with Business Partners")}},async({serviceUrl:e,action:n})=>{let r=e?`Use the Business Partner service at: ${e}`:"Find the Business Partner service (common URL: /sap/opu/odata/sap/API_BUSINESS_PARTNER)",s;switch(n){case "create":s=`Create a new Business Partner:
|
|
187
187
|
4. Use sap_get_metadata to understand required fields for A_BusinessPartner
|
|
188
188
|
5. Use sap_create to create a new Business Partner with the required data
|
|
189
189
|
6. Verify the creation by reading back the created entity`;break;case "search":s=`Search for Business Partners:
|
|
@@ -195,20 +195,20 @@ Use sap_query with appropriate filters to find the material data.`}}]})),n.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 Tt=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 ua(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 c={status:
|
|
200
|
+
`),a;o!==-1&&(a=i.substring(o+2).trim());let c={status:n,entitySet:""};if(a&&a.length>0)if(r)try{c.result=JSON.parse(a);}catch{}else c.error=a;return c}function Cr(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 c=o[1],l=i.indexOf(`
|
|
201
201
|
|
|
202
|
-
`);if(l!==-1){let d=i.substring(l+2),u=
|
|
203
|
-
`);return
|
|
204
|
-
`)}function
|
|
205
|
-
${
|
|
206
|
-
${
|
|
202
|
+
`);if(l!==-1){let d=i.substring(l+2),u=Cr(d,c);n.push(...u);}continue}let a=ua(i);a&&n.push(a);}return n}function Pr(t,e){let n=e.match(/boundary=([^\s;]+)/);if(!n)return [];let r=n[1],s=t.replace(/\r\n/g,`
|
|
203
|
+
`);return Cr(s,r)}var pa=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function Rr(t){return typeof t=="number"?String(t):typeof t=="string"&&pa.test(t)?`guid'${t}'`:`'${String(t)}'`}function ve(t,e){let n=Object.entries(e);if(n.length===1){let s=n[0];return `${t}(${Rr(s[1])})`}let r=n.map(([s,i])=>`${s}=${Rr(i)}`);return `${t}(${r.join(",")})`}function on(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 ma(t){return t.key?ve(t.entitySet,t.key):t.entitySet}function kr(t){let e=ma(t),n=t.method.toUpperCase(),r=n==="PATCH",s=r?"POST":n,i=[];if(i.push("Content-Type: application/http"),i.push("Content-Transfer-Encoding: binary"),i.push(""),i.push(`${s} ${e} HTTP/1.1`),n==="GET"?i.push("Accept: application/json"):(i.push("Content-Type: application/json"),i.push("Accept: application/json"),r&&i.push("X-HTTP-Method: MERGE"),t.data&&"__etag"in t.data&&i.push(`If-Match: ${t.data.__etag}`)),i.push(""),t.data&&(n==="POST"||n==="PATCH"||n==="DELETE")){let{__etag:o,...a}=t.data;Object.keys(a).length>0&&i.push(JSON.stringify(a));}return i.join(`\r
|
|
204
|
+
`)}function Or(t,e,n){let r=`batch_${randomUUID()}`,s=[];for(let a of e)s.push(`--${r}\r
|
|
205
|
+
${kr(a)}`);if(n)for(let a of n){let c=`changeset_${randomUUID()}`,l=[];for(let u of a.operations)l.push(`--${c}\r
|
|
206
|
+
${kr(u)}`);l.push(`--${c}--`);let d=l.join(`\r
|
|
207
207
|
`);s.push(`--${r}\r
|
|
208
208
|
Content-Type: multipart/mixed; boundary=${c}\r
|
|
209
209
|
\r
|
|
210
210
|
${d}`);}s.push(`--${r}--`);let i=s.join(`\r
|
|
211
|
-
`),o=`multipart/mixed; boundary=${r}`;return {body:i,contentType:o}}var ma=new Set(["EntityType","Property","NavigationProperty","Association","EntitySet","AssociationSet","PropertyRef","End","FunctionImport","Parameter"]),fa=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",removeNSPrefix:true,processEntities:false,htmlEntities:false,isArray:n=>ma.has(n)});function sn(n){return n===void 0?true:String(n).toLowerCase()!=="false"}function Tt(n,e){let r=fa.parse(n).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 g of o)l.set(g["@_Name"],g);let d=i.map(g=>{let h=ga(g.Property??[]),S=(g.Key?.PropertyRef??[]).map(v=>v["@_Name"]),T=h.filter(v=>S.includes(v.name)),y=ha(g.NavigationProperty??[],l);return {name:g["@_Name"],keys:T,properties:h,navigationProperties:y}}),u=c.map(g=>({name:g["@_Name"],entityTypeName:on(g["@_EntityType"]),creatable:sn(g["@_creatable"]),updatable:sn(g["@_updatable"]),deletable:sn(g["@_deletable"])})),m=a?.FunctionImport??[],p=ya(Array.isArray(m)?m:[m]),f={version:"v2",serviceUrl:e,entityTypes:d,entitySets:u};return p.length>0&&(f.functionImports=p),f}function ga(n){return n.map(e=>{let t={name:e["@_Name"],type:e["@_Type"],nullable:e["@_Nullable"]!=="false"};return e["@_MaxLength"]!==void 0&&(t.maxLength=Number.parseInt(e["@_MaxLength"],10)),e["@_Precision"]!==void 0&&(t.precision=Number.parseInt(e["@_Precision"],10)),e["@_Scale"]!==void 0&&(t.scale=Number.parseInt(e["@_Scale"],10)),t})}function ha(n,e){return n.map(t=>{let r=on(t["@_Relationship"]),s=e.get(r),i="Unknown",o=false;if(s){let a=s.End.find(c=>c["@_Role"]===t["@_ToRole"]);a&&(i=on(a["@_Type"]),o=a["@_Multiplicity"]==="*");}return {name:t["@_Name"],targetEntityType:i,isCollection:o}})}function ya(n){return n.filter(e=>e["@_Name"]).map(e=>{let t=(e.Parameter??[]).map(s=>({name:s["@_Name"],type:s["@_Type"],...s["@_Nullable"]==="true"?{nullable:true}:{}})),r={name:e["@_Name"],httpMethod:e["@_HttpMethod"]??"POST",parameters:t};return e["@_ReturnType"]&&(r.returnType=e["@_ReturnType"]),r})}function on(n){let e=n.lastIndexOf(".");return e>=0?n.substring(e+1):n}var Sa=/^\/Date\((-?\d+)([+-]\d{4})?\)\/$/;function $e(n){if(!n||typeof n!="object")return {results:[]};let e=n;if("d"in e&&e.d&&typeof e.d=="object"){let t=e.d;if("results"in t&&Array.isArray(t.results)){let s={results:t.results};return t.__count!==void 0&&(s.count=Number(t.__count)),typeof t.__next=="string"&&(s.nextLink=t.__next),s}return {results:[{...t}]}}return "results"in e&&Array.isArray(e.results)?{results:e.results}:{results:[e]}}function kr(n){let e=Sa.exec(n);if(!e)return n;let t=Number(e[1]);return new Date(t).toISOString()}function se(n){if(n==null)return n;if(Array.isArray(n))return n.map(e=>se(e));if(typeof n=="object"){let e=n,t={};for(let[r,s]of Object.entries(e))r!=="__metadata"&&(s&&typeof s=="object"&&!Array.isArray(s)&&"__deferred"in s||(typeof s=="string"?t[r]=kr(s):t[r]=se(s)));return t}return typeof n=="string"?kr(n):n}var Or=10,Le=class{constructor(e,t,r){this.httpClient=e;this.serviceUrl=t;this.etagCache=r;}async read(e,t,r){let s=rn(r??{});if(t){let i=`${this.serviceUrl}/${Ae(e,t)}`,o=await this.httpClient.request({method:"get",url:i,headers:{Accept:"application/json"},params:s});o.headers.etag&&this.etagCache.set(e,t,o.headers.etag);let a=$e(o.data);return {...a,results:a.results.map(c=>se(c))}}return this.fetchWithPagination(`${this.serviceUrl}/${e}`,s,r?.maxPages??Or)}async query(e,t){let r=rn(t,true),s=t.maxPages??Or;return this.fetchWithPagination(`${this.serviceUrl}/${e}`,r,s)}async create(e,t){let r=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/${e}`,data:t,headers:{Accept:"application/json","Content-Type":"application/json"}}),s=$e(r.data);return s.results.length>0?se(s.results[0]):{}}async update(e,t,r){let s=`${this.serviceUrl}/${Ae(e,t)}`,i=this.etagCache.get(e,t)??"*",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,t,o.headers.etag);}async delete(e,t){let r=`${this.serviceUrl}/${Ae(e,t)}`,s=this.etagCache.get(e,t)??"*";await this.httpClient.request({method:"delete",url:r,headers:{Accept:"application/json","If-Match":s}}),this.etagCache.update(e,t,void 0);}async functionImport(e,t,r){let s=(r??"POST").toLowerCase(),i={$format:"json"};if(t)for(let[l,d]of Object.entries(t))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=$e(o.data);if(a.results.length>0)return se(a.results[0]);let c=o.data;return c?.d&&typeof c.d=="object"?c.d:{}}async batch(e,t){let{body:r,contentType:s}=_r(this.serviceUrl,e,t),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=Er(i.data,o),c=[...e,...(t??[]).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 Tt(e.data,this.serviceUrl)}async fetchWithPagination(e,t,r){let s=[],i,o=e,a=t,c=0,l=false;for(;o&&c<r;){if(c>0&&Xe()){l=true;break}let m=(await this.httpClient.request({method:"get",url:o,headers:{Accept:"application/json"},params:a})).data,p=$e(m),f=p.results.map(h=>se(h));s.push(...f),c===0&&p.count!==void 0&&(i=p.count),c++;let g=m?.d;g&&typeof g.__next=="string"?(o=g.__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 Ir(n){let t=(typeof n=="string"?JSON.parse(n):n).responses;return !t||!Array.isArray(t)?[]:t.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 Dr(n){return typeof n=="string"?`'${n}'`:String(n)}function z(n,e){let t=Object.entries(e);if(t.length===1){let[,s]=t[0];return `${n}(${Dr(s)})`}let r=t.map(([s,i])=>`${s}=${Dr(i)}`).join(",");return `${n}(${r})`}function bt(n,e){let t={};return n.filter&&(t.$filter=n.filter),n.select?.length&&(t.$select=n.select.join(",")),n.expand?.length&&(t.$expand=n.expand.join(",")),n.orderby&&(t.$orderby=n.orderby),n.top!==void 0&&(t.$top=String(n.top)),n.skip!==void 0&&(t.$skip=String(n.skip)),e&&(t.$count="true"),t}function Nr(n,e){let t=[];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),t.push(a);}return {body:JSON.stringify({requests:t}),contentType:"application/json"}}function xr(n){let e=n.split(".");return e[e.length-1]}function Ta(n,e){return {name:n,type:e.$Type??"Edm.String",nullable:e.$Nullable??false,maxLength:e.$MaxLength,precision:e.$Precision,scale:e.$Scale}}function Mr(n,e){let t=JSON.parse(n),r=[],s=[],i=[];for(let[a,c]of Object.entries(t)){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,f=m.$Parameter??[],g=p&&f.length>0?f[0].$Name:void 0,h=(p?f.slice(1):f).map(T=>({name:T.$Name,type:T.$Type??"Edm.String",...T.$Nullable===true?{nullable:true}:{}})),S=m.$ReturnType?m.$ReturnType.$Type:void 0;i.push({name:d,isBound:p,...g?{bindingParameter:g}:{},parameters:h,...S?{returnType:S}:{}});}else if(m.$Kind==="EntityType"){let p=[],f=[];for(let[S,T]of Object.entries(m)){if(S.startsWith("$"))continue;let y=T;y.$Kind==="NavigationProperty"?f.push({name:S,targetEntityType:xr(y.$Type),isCollection:y.$Collection??false}):p.push(Ta(S,y));}let h=(m.$Key??[]).map(S=>{let T=p.find(y=>y.name===S);return T||{name:S,type:"Edm.String",nullable:false}});r.push({name:d,keys:h,properties:p,navigationProperties:f});}else if(m.$Kind==="EntityContainer")for(let[p,f]of Object.entries(m)){if(p.startsWith("$"))continue;let g=f;g.$Collection&&g.$Type&&s.push({name:p,entityTypeName:xr(g.$Type),creatable:an(g,"InsertRestrictions","Insertable"),updatable:an(g,"UpdateRestrictions","Updatable"),deletable:an(g,"DeleteRestrictions","Deletable")});}}}let o={version:"v4",serviceUrl:e,entityTypes:r,entitySets:s};return i.length>0&&(o.actions=i),o}function an(n,e,t){for(let[r,s]of Object.entries(n)){if(!r.startsWith("@")||!r.endsWith(e))continue;let i=s?.[t];if(typeof i=="boolean")return i}return true}var ba=10,vt=class{constructor(e,t,r){this.httpClient=e;this.serviceUrl=t;this.etagCache=r;}async read(e,t,r){if(t){let l=`${this.serviceUrl}/${z(e,t)}`,d=r?bt(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,t,p),{results:[m]}}let s=r?bt(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,t){let r=t.maxPages??ba,s=bt(t,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(Xe()){u=true;break}let p=this.resolveNextLink(l),g=(await this.httpClient.request({method:"get",url:p})).data,h=g.value??[];a.push(...h),l=g["@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,t){return (await this.httpClient.request({method:"post",url:`${this.serviceUrl}/${e}`,data:t,headers:{"Content-Type":"application/json"}})).data}async update(e,t,r){let s=`${this.serviceUrl}/${z(e,t)}`,i=this.etagCache.get(e,t)??"*",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,t,a);}async delete(e,t){let r=`${this.serviceUrl}/${z(e,t)}`,s=this.etagCache.get(e,t)??"*";await this.httpClient.request({method:"delete",url:r,headers:{"If-Match":s}});}async batch(e){let{body:t,contentType:r}=Nr(this.serviceUrl,e),s=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/$batch`,data:t,headers:{"Content-Type":r,Accept:"application/json"}}),i=Ir(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,t,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:t??{},headers:{"Content-Type":"application/json"}})).data??{}}async callFunction(e,t,r,s){let i=this.buildFunctionParams(t),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(([t,r])=>typeof r=="string"?`${t}='${r}'`:`${t}=${String(r)}`).join(",")}async getMetadata(){let e=await this.httpClient.request({method:"get",url:`${this.serviceUrl}/$metadata`,headers:{Accept:"application/json"}}),t=typeof e.data=="string"?e.data:JSON.stringify(e.data);return Mr(t,this.serviceUrl)}resolveNextLink(e){return e.startsWith("http://")||e.startsWith("https://")?e:`${this.serviceUrl}/${e}`}};function cn(n,e){let t=n.trim();if(t.startsWith("{"))try{if("$Version"in JSON.parse(t))return "v4"}catch{}if(t.startsWith("<?xml")||t.startsWith("<edmx:Edmx")||t.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,t,r){this.httpClient=e;this.serviceUrl=t;this.metadataCache=r??new k;}protocolClient=null;version=null;initPromise=null;etagCache=new St;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 t="",r=false;try{let s=await this.httpClient.request({method:"get",url:`${this.serviceUrl}/$metadata`,headers:{Accept:"application/xml, application/json"}});t=typeof s.data=="string"?s.data:JSON.stringify(s.data),this.version=cn(t,this.serviceUrl),r=!0;}catch{this.version=cn("",this.serviceUrl);}if(this.protocolClient=this.createProtocolClient(this.version),r&&this.version==="v2"&&t)try{let s=Tt(t,this.serviceUrl);this.metadataCache.set(this.serviceUrl,s);}catch{}}createProtocolClient(e){return e==="v4"?new vt(this.httpClient,this.serviceUrl,this.etagCache):new Le(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,t,r){return (await this.getClient()).read(e,t,r)}async query(e,t){return (await this.getClient()).query(e,t)}async create(e,t){return (await this.getClient()).create(e,t)}async update(e,t,r){return (await this.getClient()).update(e,t,r)}async delete(e,t){return (await this.getClient()).delete(e,t)}async batch(e,t){let r=await this.getClient();return this.version==="v4"?r.batch(e):r.batch(e,t)}async functionImport(e,t,r){let s=await this.getClient();if(s instanceof Le)return s.functionImport(e,t,r?.httpMethod);let i=s;return r?.isFunction?i.callFunction(e,t,r?.entitySet,r?.key):i.callAction(e,t,r?.entitySet,r?.key)}async getMetadata(){let e=this.metadataCache.get(this.serviceUrl);if(e)return e;let t=await this.getClient();if(e=this.metadataCache.get(this.serviceUrl),e)return e;let r=await t.getMetadata();return this.metadataCache.set(this.serviceUrl,r),r}getVersion(){return this.version}};function Ur(n,e,t){let r=t??new k,s=new ResourceTemplate("sap://schema/{serviceUrl}/{entitySet}",{list:void 0});n.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)),f=m.properties.map(h=>({name:h.name,type:h.type,isKey:p.has(h.name)})),g=Aa(m.properties,d.version);return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({entitySet:c,version:d.version,properties:f,sampleFilter:g},null,2)}]}});}function Aa(n,e){let t=n.find(r=>r.type==="Edm.String");return t?e==="v2"?`${t.name} eq 'example' or substringof('example',${t.name})`:`${t.name} eq 'example' or contains(${t.name},'example')`:"PropertyName eq 'value'"}function $r(n,e){let t=new ResourceTemplate("idoc://received/{docnum}",{list:void 0});n.registerResource("idoc-received",t,{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 Lr(n,e,t){let r=t??new k,s=new ResourceTemplate("sap://metadata/{serviceUrl}",{list:void 0});n.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 dn={"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 Ca={$TODAY:()=>ln(new Date),$YESTERDAY:()=>{let n=new Date;return n.setDate(n.getDate()-1),ln(n)},$MONTH_START:()=>{let n=new Date;return `${n.getFullYear()}-${un(n.getMonth()+1)}-01`},$YEAR_START:()=>`${new Date().getFullYear()}-01-01`,$MONTH_AGO:()=>{let n=new Date;return n.setMonth(n.getMonth()-1),ln(n)}};function ln(n){return `${n.getFullYear()}-${un(n.getMonth()+1)}-${un(n.getDate())}`}function un(n){return n.toString().padStart(2,"0")}function Pa(n){if(typeof n!="string")return n;let e=Ca[n];return e?e():n}function Br(n){return {...n,queryTemplates:n.queryTemplates.map(e=>({...e,filters:e.filters.map(t=>({...t,value:Pa(t.value)}))}))}}function Fr(n){let e=new ResourceTemplate("sap://routing/{domain}",{list:void 0});n.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(t,r)=>{let s=r.domain,i=dn[s];if(!i)return {contents:[{uri:t.toString(),mimeType:"application/json",text:JSON.stringify({error:`Unknown domain '${s}'`,availableDomains:Object.keys(dn)},null,2)}]};let o=Br(i);return {contents:[{uri:t.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 pn={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"],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"]},Hr=new Set(["sap_create","sap_update","sap_delete","sap_function","sap_batch","sap_idoc_send","sap_rfc_call","sap_bapi_call","sap_bapi_commit"]),jr=["core","odata"],Vr=new Map;for(let[n,e]of Object.entries(pn))for(let t of e)Vr.set(t,n);var At=class{handles=new Map;activeTiers;options;constructor(e){this.options=e,this.activeTiers=new Set(e.activeTiers);}registerHandle(e,t){this.handles.set(e,t);}applyInitialVisibility(){for(let[e,t]of this.handles)this.shouldToolBeVisible(e)||t.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 t=pn[e],r=[];for(let s of t){if(this.options.readOnly&&Hr.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 pn[e]}isReadOnlyMode(){return this.options.readOnly}shouldToolBeVisible(e){let t=Vr.get(e);return t?!(!this.activeTiers.has(t)||this.options.allowedTiers&&!this.options.allowedTiers.includes(t)||t==="idoc"&&!this.options.hasIdocConfig||t==="rfc"&&!this.options.hasRfcConfig||this.options.readOnly&&Hr.has(e)):true}};var Wr="/sap/opu/odata/sap",_a=/^[A-Z0-9_/]+$/i,ie=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 ${Wr}/API_BUSINESS_PARTNER oder der technische Name (API_BUSINESS_PARTNER), den sap_discover_services als technicalName liefert.`;}};function H(n){let e=n.trim();if(e.length===0)throw new ie(n);if(e.startsWith("/"))return e.replace(/\/+$/,"")||"/";if(e.startsWith("http://")||e.startsWith("https://"))try{return new URL(e).pathname.replace(/\/+$/,"")}catch{throw new ie(n)}if(_a.test(e))return `${Wr}/${e.replace(/^\/+|\/+$/g,"")}`;throw new ie(n)}var Be=class extends Error{guidance;extra;constructor(e,t,r={}){super(e),this.name="ToolInputError",this.guidance=t,this.extra=r;}};function N(n,e){if(n instanceof ie)return mn({code:"INVALID_INPUT",error:n.message,guidance:n.guidance,...e?.hint?{hint:e.hint}:{}});if(n instanceof Be)return mn({code:"INVALID_INPUT",error:n.message,...n.guidance?{guidance:n.guidance}:{},...n.extra,...e?.hint?{hint:e.hint}:{}});let t=ue(n,{serviceUrl:e?.serviceUrl});return mn({code:t.code,error:t.message,...t.httpStatus!==void 0?{httpStatus:t.httpStatus}:{},...t.sapCode?{sapCode:t.sapCode}:{},...t.guidance?{guidance:t.guidance}:{},...e?.hint?{hint:e.hint}:{}})}function mn(n){let e=de()?.correlationId;return {content:[{type:"text",text:JSON.stringify({...n,...e?{correlationId:e}:{}},null,2)}],isError:true}}function qr(n,e,t){let r=t??new k,s=new Map;function i(a){let c=H(a),l=s.get(c);return l||(l=new M(e,c,r),s.set(c,l)),l}function o(a,c){return N(a,{serviceUrl:c})}n.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 Kr(n,e,t=5){if(n.length===0)return [];let r=Math.max(1,Math.min(Math.floor(t)||1,n.length)),s=new Array(n.length),i=0;async function o(){for(;;){let a=i++;if(a>=n.length)return;s[a]=await e(n[a],a);}}return await Promise.all(Array.from({length:r},()=>o())),s}var ka=[".integrationsuite.","integrationsuite-trial.",".it-cpi.",".hci.",".prod.apimanagement.","-api.s4hana.ondemand.com"];function Gr(n){let e=n.toLowerCase();return ka.some(t=>e.includes(t))}var V=class extends Error{constructor(t,r,s,i){super(t);this.httpStatus=r;this.sapGuidance=s;this.isBtpDetected=i;this.name="CatalogError";}},Oa=new Set(["ECONNREFUSED","ETIMEDOUT","ENOTFOUND"]);function Ia(n,e=false){switch(n){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 Da(n){switch(n){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 zr(n){return typeof n=="object"&&n!==null&&"response"in n&&typeof n.response=="object"&&n.response!==null&&typeof n.response.status=="number"}function Na(n){return typeof n=="object"&&n!==null&&"code"in n&&typeof n.code=="string"&&Oa.has(n.code)}function Fe(n,e,t=false){let r=Gr(e);if(zr(n)){let i=n.response.status,o=Ia(i,t),a="message"in n&&typeof n.message=="string"?n.message:`HTTP ${i}`;return new V(a,i,o,r)}if(Na(n)){let i=Da(n.code),o="message"in n&&typeof n.message=="string"?n.message:n.code;return new V(o,void 0,i,r)}let s=n instanceof Error?n.message:String(n);return new V(s,void 0,void 0,r)}function Yr(n){return zr(n)&&n.response.status===404?"V4 catalog not available. Publish service group via /IWFND/V4_ADMIN.":n instanceof Error?n.message:String(n)}var xa=["/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/iwfnd/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/IWFND/CATALOGSERVICE/ServiceCollection"];function Ma(n){if(typeof n=="object"&&n!==null&&"response"in n)return n.response?.status}async function Xr(n,e,t){let r;for(let s of xa)try{return await n.request({method:"get",url:s,params:{$format:"json",$top:"1"},expectedStatuses:[404]}),t?.onMatch?.(s),s}catch(i){if(i instanceof $)throw i;if(Ma(i)===404){r=i;continue}throw Fe(i,e,false)}throw Fe(r??new Error("All V2 catalog paths returned 404"),e,false)}function Jr(n){try{return new URL(n).pathname}catch{return n}}function $a(n){let e=n.replace(/'/g,"''");return `substringof('${e}',TechnicalServiceName) or substringof('${e}',Title)`}var wt=class n{constructor(e,t,r={}){this.httpClient=e;this.cache=t;this.options=r;}static DEFAULT_V4_URL="/sap/opu/odata4/iwfnd/config/default/iwfnd/catalog/0002/ServiceGroups";resolvedV2Path;async discoverServices(e){let t=this.cache.get();if(t)return {services:t,warnings:[]};if(e){let l=await this.tryFilteredFetch(e);if(l)return l}let r=[],[s,i]=await Kr(["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 $)throw s.error;r.push(`V4 catalog fetch failed: ${Yr(s.error)}`);}if(!i?.ok){let l=i?.error;throw l instanceof V?l:Fe(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($a(e)),warnings:[],filtered:!0}}catch(t){if(t instanceof $)throw t;t?.response?.status;return void 0}}async fetchV2Catalog(e){let t=this.options.catalogV2Url??this.resolvedV2Path;return t||(t=await Xr(this.httpClient,this.options.baseUrl??"",{onMatch:this.options.onDiscoveryMatch}),this.resolvedV2Path=t),((await this.httpClient.request({method:"get",url:t,params:{$format:"json",...e?{$filter:e}:{}},...e?{expectedStatuses:[400,501]}:{}})).data?.d?.results??[]).map(o=>({technicalName:o.TechnicalServiceName,title:o.Title,serviceUrl:Jr(o.ServiceUrl),description:o.Description,version:"v2",serviceVersion:o.TechnicalServiceVersion}))}async fetchV4Catalog(){let e=this.options.catalogV4Url??n.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:Jr(c.ServiceUrl),version:"v4",serviceVersion:c.ServiceVersion});}return i}mergeAndDeduplicate(e,t){let r=new Map;for(let s of e)r.set(s.technicalName,s);for(let s of t)r.has(s.technicalName)||r.set(s.technicalName,s);return [...r.values()]}};var La=[[/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"]],Ba=[[/^[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 Fa(n){for(let[e,t]of La)if(e.test(n.technicalName))return {...n,category:t,categoryConfidence:"high"};for(let[e,t]of Ba)if(e.test(n.technicalName))return {...n,category:t,categoryConfidence:"high"};return {...n,category:"unknown",categoryConfidence:"low"}}function Qr(n){return n.map(Fa)}function we(n,e){let t=Number.isFinite(n)&&n>0?Math.floor(n):50,r=Number.isFinite(e)&&e>0?Math.floor(e):0;return {top:Math.min(t,1e3),skip:r}}function Ee(n){let{returned:e,totalCount:t,paging:r,hasMoreHint:s}=n,i=s??(t!==void 0?r.skip+e<t:e>=r.top),o={returned:e,hasMore:i,top:r.top,skip:r.skip};return t!==void 0&&(o.totalCount=t),i&&(o.nextSkip=r.skip+e),o}function Zr(n,e,t,r){let s=r??new G,i=new wt(e,s,t);n.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.
|
|
211
|
+
`),o=`multipart/mixed; boundary=${r}`;return {body:i,contentType:o}}var ga=new Set(["EntityType","Property","NavigationProperty","Association","EntitySet","AssociationSet","PropertyRef","End","FunctionImport","Parameter"]),ha=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",removeNSPrefix:true,processEntities:false,htmlEntities:false,isArray:t=>ga.has(t)});function an(t){return t===void 0?true:String(t).toLowerCase()!=="false"}function At(t,e){let r=ha.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 g of o)l.set(g["@_Name"],g);let d=i.map(g=>{let h=ya(g.Property??[]),S=(g.Key?.PropertyRef??[]).map(b=>b["@_Name"]),T=h.filter(b=>S.includes(b.name)),y=Sa(g.NavigationProperty??[],l);return {name:g["@_Name"],keys:T,properties:h,navigationProperties:y}}),u=c.map(g=>({name:g["@_Name"],entityTypeName:cn(g["@_EntityType"]),creatable:an(g["@_creatable"]),updatable:an(g["@_updatable"]),deletable:an(g["@_deletable"])})),m=a?.FunctionImport??[],p=Ta(Array.isArray(m)?m:[m]),f={version:"v2",serviceUrl:e,entityTypes:d,entitySets:u};return p.length>0&&(f.functionImports=p),f}function ya(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 Sa(t,e){return t.map(n=>{let r=cn(n["@_Relationship"]),s=e.get(r),i="Unknown",o=false;if(s){let a=s.End.find(c=>c["@_Role"]===n["@_ToRole"]);a&&(i=cn(a["@_Type"]),o=a["@_Multiplicity"]==="*");}return {name:n["@_Name"],targetEntityType:i,isCollection:o}})}function Ta(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 cn(t){let e=t.lastIndexOf(".");return e>=0?t.substring(e+1):t}var Aa=/^\/Date\((-?\d+)([+-]\d{4})?\)\/$/;function Le(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 Ir(t){let e=Aa.exec(t);if(!e)return t;let n=Number(e[1]);return new Date(n).toISOString()}function ie(t){if(t==null)return t;if(Array.isArray(t))return t.map(e=>ie(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]=Ir(s):n[r]=ie(s)));return n}return typeof t=="string"?Ir(t):t}var Dr=10,Be=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.etagCache=r;}async read(e,n,r){let s=on(r??{});if(n){let i=`${this.serviceUrl}/${ve(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=Le(o.data);return {...a,results:a.results.map(c=>ie(c))}}return this.fetchWithPagination(`${this.serviceUrl}/${e}`,s,r?.maxPages??Dr)}async query(e,n){let r=on(n,true),s=n.maxPages??Dr;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=Le(r.data);return s.results.length>0?ie(s.results[0]):{}}async update(e,n,r){let s=`${this.serviceUrl}/${ve(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}/${ve(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=Le(o.data);if(a.results.length>0)return ie(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}=Or(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=Pr(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&&Je()){l=true;break}let m=(await this.httpClient.request({method:"get",url:o,headers:{Accept:"application/json"},params:a})).data,p=Le(m),f=p.results.map(h=>ie(h));s.push(...f),c===0&&p.count!==void 0&&(i=p.count),c++;let g=m?.d;g&&typeof g.__next=="string"?(o=g.__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 Nr(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 xr(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}(${xr(s)})`}let r=n.map(([s,i])=>`${s}=${xr(i)}`).join(",");return `${t}(${r})`}function bt(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 Mr(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 Ur(t){let e=t.split(".");return e[e.length-1]}function ba(t,e){return {name:t,type:e.$Type??"Edm.String",nullable:e.$Nullable??false,maxLength:e.$MaxLength,precision:e.$Precision,scale:e.$Scale}}function $r(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,f=m.$Parameter??[],g=p&&f.length>0?f[0].$Name:void 0,h=(p?f.slice(1):f).map(T=>({name:T.$Name,type:T.$Type??"Edm.String",...T.$Nullable===true?{nullable:true}:{}})),S=m.$ReturnType?m.$ReturnType.$Type:void 0;i.push({name:d,isBound:p,...g?{bindingParameter:g}:{},parameters:h,...S?{returnType:S}:{}});}else if(m.$Kind==="EntityType"){let p=[],f=[];for(let[S,T]of Object.entries(m)){if(S.startsWith("$"))continue;let y=T;y.$Kind==="NavigationProperty"?f.push({name:S,targetEntityType:Ur(y.$Type),isCollection:y.$Collection??false}):p.push(ba(S,y));}let h=(m.$Key??[]).map(S=>{let T=p.find(y=>y.name===S);return T||{name:S,type:"Edm.String",nullable:false}});r.push({name:d,keys:h,properties:p,navigationProperties:f});}else if(m.$Kind==="EntityContainer")for(let[p,f]of Object.entries(m)){if(p.startsWith("$"))continue;let g=f;g.$Collection&&g.$Type&&s.push({name:p,entityTypeName:Ur(g.$Type),creatable:dn(g,"InsertRestrictions","Insertable"),updatable:dn(g,"UpdateRestrictions","Updatable"),deletable:dn(g,"DeleteRestrictions","Deletable")});}}}let o={version:"v4",serviceUrl:e,entityTypes:r,entitySets:s};return i.length>0&&(o.actions=i),o}function dn(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 wa=10,wt=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)}`,d=r?bt(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?bt(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??wa,s=bt(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(Je()){u=true;break}let p=this.resolveNextLink(l),g=(await this.httpClient.request({method:"get",url:p})).data,h=g.value??[];a.push(...h),l=g["@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}/${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}=Mr(this.serviceUrl,e),s=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/$batch`,data:n,headers:{"Content-Type":r,Accept:"application/json"}}),i=Nr(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 $r(n,this.serviceUrl)}resolveNextLink(e){return e.startsWith("http://")||e.startsWith("https://")?e:`${this.serviceUrl}/${e}`}};function ln(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 k;}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=ln(n,this.serviceUrl),r=!0;}catch{this.version=ln("",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 wt(this.httpClient,this.serviceUrl,this.etagCache):new Be(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 Be)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 Lr(t,e,n){let r=n??new k,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)),f=m.properties.map(h=>({name:h.name,type:h.type,isKey:p.has(h.name)})),g=Ea(m.properties,d.version);return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({entitySet:c,version:d.version,properties:f,sampleFilter:g},null,2)}]}});}function Ea(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 Br(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 Fr(t,e,n){let r=n??new k,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 un={"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 Ra={$TODAY:()=>pn(new Date),$YESTERDAY:()=>{let t=new Date;return t.setDate(t.getDate()-1),pn(t)},$MONTH_START:()=>{let t=new Date;return `${t.getFullYear()}-${mn(t.getMonth()+1)}-01`},$YEAR_START:()=>`${new Date().getFullYear()}-01-01`,$MONTH_AGO:()=>{let t=new Date;return t.setMonth(t.getMonth()-1),pn(t)}};function pn(t){return `${t.getFullYear()}-${mn(t.getMonth()+1)}-${mn(t.getDate())}`}function mn(t){return t.toString().padStart(2,"0")}function _a(t){if(typeof t!="string")return t;let e=Ra[t];return e?e():t}function Hr(t){return {...t,queryTemplates:t.queryTemplates.map(e=>({...e,filters:e.filters.map(n=>({...n,value:_a(n.value)}))}))}}function jr(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=un[s];if(!i)return {contents:[{uri:n.toString(),mimeType:"application/json",text:JSON.stringify({error:`Unknown domain '${s}'`,availableDomains:Object.keys(un)},null,2)}]};let o=Hr(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 fn={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"],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"]},Vr=new Set(["sap_create","sap_update","sap_delete","sap_function","sap_batch","sap_idoc_send","sap_rfc_call","sap_bapi_call","sap_bapi_commit"]),Wr=["core","odata"],qr=new Map;for(let[t,e]of Object.entries(fn))for(let n of e)qr.set(n,t);var vt=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=fn[e],r=[];for(let s of n){if(this.options.readOnly&&Vr.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 fn[e]}isReadOnlyMode(){return this.options.readOnly}shouldToolBeVisible(e){let n=qr.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&&Vr.has(e)):true}};var Kr="/sap/opu/odata/sap",Oa=/^[A-Z0-9_/]+$/i,oe=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 ${Kr}/API_BUSINESS_PARTNER oder der technische Name (API_BUSINESS_PARTNER), den sap_discover_services als technicalName liefert.`;}};function H(t){let e=t.trim();if(e.length===0)throw new oe(t);if(e.startsWith("/"))return e.replace(/\/+$/,"")||"/";if(e.startsWith("http://")||e.startsWith("https://"))try{return new URL(e).pathname.replace(/\/+$/,"")}catch{throw new oe(t)}if(Oa.test(e))return `${Kr}/${e.replace(/^\/+|\/+$/g,"")}`;throw new oe(t)}var Fe=class extends Error{guidance;extra;constructor(e,n,r={}){super(e),this.name="ToolInputError",this.guidance=n,this.extra=r;}};function N(t,e){if(t instanceof oe)return gn({code:"INVALID_INPUT",error:t.message,guidance:t.guidance,...e?.hint?{hint:e.hint}:{}});if(t instanceof Fe)return gn({code:"INVALID_INPUT",error:t.message,...t.guidance?{guidance:t.guidance}:{},...t.extra,...e?.hint?{hint:e.hint}:{}});let n=pe(t,{serviceUrl:e?.serviceUrl});return gn({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 gn(t){let e=le()?.correlationId;return {content:[{type:"text",text:JSON.stringify({...t,...e?{correlationId:e}:{}},null,2)}],isError:true}}function Gr(t,e,n){let r=n??new k,s=new Map;function i(a){let c=H(a),l=s.get(c);return l||(l=new M(e,c,r),s.set(c,l)),l}function o(a,c){return N(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 zr(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 Ia=[".integrationsuite.","integrationsuite-trial.",".it-cpi.",".hci.",".prod.apimanagement.","-api.s4hana.ondemand.com"];function Yr(t){let e=t.toLowerCase();return Ia.some(n=>e.includes(n))}var V=class extends Error{constructor(n,r,s,i){super(n);this.httpStatus=r;this.sapGuidance=s;this.isBtpDetected=i;this.name="CatalogError";}},Da=new Set(["ECONNREFUSED","ETIMEDOUT","ENOTFOUND"]);function Na(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 xa(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 Xr(t){return typeof t=="object"&&t!==null&&"response"in t&&typeof t.response=="object"&&t.response!==null&&typeof t.response.status=="number"}function Ma(t){return typeof t=="object"&&t!==null&&"code"in t&&typeof t.code=="string"&&Da.has(t.code)}function He(t,e,n=false){let r=Yr(e);if(Xr(t)){let i=t.response.status,o=Na(i,n),a="message"in t&&typeof t.message=="string"?t.message:`HTTP ${i}`;return new V(a,i,o,r)}if(Ma(t)){let i=xa(t.code),o="message"in t&&typeof t.message=="string"?t.message:t.code;return new V(o,void 0,i,r)}let s=t instanceof Error?t.message:String(t);return new V(s,void 0,void 0,r)}function Jr(t){return Xr(t)&&t.response.status===404?"V4 catalog not available. Publish service group via /IWFND/V4_ADMIN.":t instanceof Error?t.message:String(t)}var Ua=["/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/iwfnd/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/IWFND/CATALOGSERVICE/ServiceCollection"];function $a(t){if(typeof t=="object"&&t!==null&&"response"in t)return t.response?.status}async function Qr(t,e,n){let r;for(let s of Ua)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 $)throw i;if($a(i)===404){r=i;continue}throw He(i,e,false)}throw He(r??new Error("All V2 catalog paths returned 404"),e,false)}function Zr(t){try{return new URL(t).pathname}catch{return t}}function Ba(t){let e=t.replace(/'/g,"''");return `substringof('${e}',TechnicalServiceName) or substringof('${e}',Title)`}var Et=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;async discoverServices(e){let n=this.cache.get();if(n)return {services:n,warnings:[]};if(e){let l=await this.tryFilteredFetch(e);if(l)return l}let r=[],[s,i]=await zr(["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 $)throw s.error;r.push(`V4 catalog fetch failed: ${Jr(s.error)}`);}if(!i?.ok){let l=i?.error;throw l instanceof V?l:He(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(Ba(e)),warnings:[],filtered:!0}}catch(n){if(n instanceof $)throw n;n?.response?.status;return void 0}}async fetchV2Catalog(e){let n=this.options.catalogV2Url??this.resolvedV2Path;return n||(n=await Qr(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:Zr(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:Zr(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 Fa=[[/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"]],Ha=[[/^[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 ja(t){for(let[e,n]of Fa)if(e.test(t.technicalName))return {...t,category:n,categoryConfidence:"high"};for(let[e,n]of Ha)if(e.test(t.technicalName))return {...t,category:n,categoryConfidence:"high"};return {...t,category:"unknown",categoryConfidence:"low"}}function es(t){return t.map(ja)}function Ee(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 Ce(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 ts(t,e,n,r){let s=r??new G,i=new Et(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
212
|
|
|
213
213
|
**When to use:**
|
|
214
214
|
- You need to find which SAP services are available on this system
|
|
@@ -217,7 +217,7 @@ ${d}`);}s.push(`--${r}--`);let i=s.join(`\r
|
|
|
217
217
|
|
|
218
218
|
**When NOT to use:**
|
|
219
219
|
- 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:c,offset:l})=>{try{let d=await i.discoverServices(o),u=
|
|
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:c,offset:l})=>{try{let d=await i.discoverServices(o),u=es(d.services);if(a&&(u=u.filter(T=>T.category===a)),o&&!d.filtered){let T=o.toLowerCase();u=u.filter(y=>y.technicalName.toLowerCase().includes(T)||y.title.toLowerCase().includes(T)||(y.description?.toLowerCase().includes(T)??!1));}let m=u.length,p={};for(let T of u)p[T.category]=(p[T.category]??0)+1;let f=Ee(c,l),g=u.slice(f.skip,f.skip+f.top),h=Ce({returned:g.length,totalCount:m,paging:f}),S={services:g.map(T=>({technicalName:T.technicalName,title:T.title,category:T.category,serviceUrl:T.serviceUrl,version:T.version})),...h,summary:{total:m,returned:g.length,offset:f.skip,byCategory:p},...d.warnings.length>0?{warnings:d.warnings}:{},...o?{filterApplied:d.filtered?"server":"client"}:{}};return {content:[{type:"text",text:JSON.stringify(S,null,2)}]}}catch(d){if(d instanceof V){let u={error:d.message};return d.sapGuidance&&(u.guidance=d.sapGuidance),d.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 N(d)}});}function ns(t,e,n){let r=new k;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
221
|
|
|
222
222
|
**When to use:**
|
|
223
223
|
- You need the raw list of OData services at a base URL
|
|
@@ -225,7 +225,7 @@ ${d}`);}s.push(`--${r}--`);let i=s.join(`\r
|
|
|
225
225
|
|
|
226
226
|
**When NOT to use:**
|
|
227
227
|
- 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=H(s),l=(await e.request({method:"get",url:a,headers:{Accept:"application/json"},params:{$format:"json"}})).data,d=[],u=l.d;u&&"EntitySets"in u&&Array.isArray(u.EntitySets)?d=u.EntitySets:"value"in l&&Array.isArray(l.value)&&(d=l.value.map(h=>h.name)),d=d.filter(h=>J(h,
|
|
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=H(s),l=(await e.request({method:"get",url:a,headers:{Accept:"application/json"},params:{$format:"json"}})).data,d=[],u=l.d;u&&"EntitySets"in u&&Array.isArray(u.EntitySets)?d=u.EntitySets:"value"in l&&Array.isArray(l.value)&&(d=l.value.map(h=>h.name)),d=d.filter(h=>J(h,n));let m=Ee(i,o),p=d.length,f=d.slice(m.skip,m.skip+m.top),g=Ce({returned:f.length,totalCount:p,paging:m});return {content:[{type:"text",text:JSON.stringify({serviceUrl:a,entitySets:f,count:p,...g},null,2)}]}}catch(a){return N(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
229
|
|
|
230
230
|
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
231
|
|
|
@@ -236,14 +236,14 @@ Each entity set carries what SAP allows on it \u2014 \`creatable\`, \`updatable\
|
|
|
236
236
|
- Use 'search' to filter large services by name pattern
|
|
237
237
|
|
|
238
238
|
**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 c=await new M(e,H(s),r).getMetadata(),l;if(i){let d=c.entityTypes.find(u=>u.name===i);if(!d){let u=i.toLowerCase();d=c.entityTypes.find(m=>m.name.toLowerCase()===u);}if(!d){let u=i.toLowerCase(),m=c.entityTypes.filter(p=>p.name.toLowerCase().includes(u)).map(p=>p.name).slice(0,20);return N(new
|
|
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 c=await new M(e,H(s),r).getMetadata(),l;if(i){let d=c.entityTypes.find(u=>u.name===i);if(!d){let u=i.toLowerCase();d=c.entityTypes.find(m=>m.name.toLowerCase()===u);}if(!d){let u=i.toLowerCase(),m=c.entityTypes.filter(p=>p.name.toLowerCase().includes(u)).map(p=>p.name).slice(0,20);return N(new Fe(`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.",m.length>0?{closestMatches:m}:{}),{serviceUrl:s})}l={version:c.version,entityType:d};}else {let d=c.entitySets.filter(f=>J(f.name,n)),u=c.entityTypes;if(o){let f=o.toLowerCase();d=d.filter(g=>g.name.toLowerCase().includes(f)),u=u.filter(g=>g.name.toLowerCase().includes(f));}let m={creatable:d.filter(f=>f.creatable).map(f=>f.name),updatable:d.filter(f=>f.updatable).length,deletable:d.filter(f=>f.deletable).length};u.length>50&&!o?l={version:c.version,entitySets:d,capabilities:m,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:c.version,entityTypes:u.map(f=>({name:f.name,keys:f.keys.map(g=>g.name),propertyCount:f.properties.length,navigationProperties:f.navigationProperties.map(g=>g.name)})),entitySets:d,capabilities:m};}return {content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(a){return N(a,{serviceUrl:s})}});}function rs(t,e,n){let r=n??new k,s=new Map;function i(a){let c=H(a),l=s.get(c);return l||(l=new M(e,c,r),s.set(c,l)),l}function o(a,c){return N(a,{serviceUrl:c})}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. 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.",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:a,functionName:c,parameters:l,httpMethod:d,entitySet:u,key:m,isFunction:p})=>{try{let g=await i(a).functionImport(c,l,{httpMethod:d,entitySet:u,key:m,isFunction:p??!1});return {content:[{type:"text",text:JSON.stringify(g,null,2)}]}}catch(f){return o(f)}});}function ss(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
240
|
|
|
241
241
|
**When to use:**
|
|
242
242
|
- Any SAP tool fails and you need to know which layer is at fault
|
|
243
243
|
- After changing SAP_BASE_URL, credentials, SAP_CLIENT or the CA bundle
|
|
244
244
|
|
|
245
245
|
**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
|
|
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 et(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 N(r,{hint:"Die Verbindungsdiagnose konnte nicht ausgef\xFChrt werden. Das ist ein Fehler des Servers, kein Befund \xFCber das SAP-System."})}});}function Wa(t){return {name:t.FieldName,dataType:t.DataType,length:t.Length,decimals:t.Decimals??0,required:t.Required}}function qa(t){return {name:t.SegmentName,parentSegment:t.ParentSegment??"",minOccurrence:t.MinOccurrence,maxOccurrence:t.MaxOccurrence,fields:t.Fields?.results?.map(Wa)??[]}}function Ka(t){return {idocType:t.IdocType,mesType:t.MesType,segments:t.Segments?.results?.map(qa)??[]}}async function hn(t,e,n){try{if(n){let o=(await t.request({method:"get",url:`${e}/IdocTypes('${n}')?$expand=Segments($expand=Fields)`})).data;return Ka(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 is={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 os(t,e,n){try{let i=(await t.request({method:"get",url:`${e}/IdocStatus('${n}')`})).data.d,o=i.Status,a=is[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 yn={name:z$2.string().describe("Segment name, e.g. E1MARAM"),fields:z$2.record(z$2.string(),z$2.string()).describe("Segment field values")},Ga=z$2.object({...yn}),za=z$2.object({...yn,children:z$2.array(Ga).optional().describe("Nested child segments (level 3)")}),Ya=z$2.object({...yn,children:z$2.array(za).optional().describe("Nested child segments")});function as(t,e,n,r){function s(i,o){return N(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(Ya).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:c,sndpor:l,sndprn:d,rcvpor:u,rcvprn:m})=>{try{let p;if(a){let g=await hn(e,a,i);Array.isArray(g)||(p=g);}let f=await n.send({idocType:i,segments:o,metadata:p,partnerOverrides:{mesType:c,sndpor:l,sndprn:d,rcvpor:u,rcvprn:m}});return {content:[{type:"text",text:JSON.stringify(f,null,2)}]}}catch(p){return s(p)}}),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 os(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 hn(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 Xa={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 ds(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} (${Xa[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 c=e.enableTier(a);return "error"in c?{content:[{type:"text",text:JSON.stringify({status:"error",message:c.error})}],isError:true}:{content:[{type:"text",text:JSON.stringify({status:"success",tier_enabled:a,enabled_tools:c.enabled,active_tiers:c.activeTiers,note:"Tools enabled. If your client did not refresh the tool list automatically, you may need to reconnect."})}]}});}function ls(t,e){return t.length===0?"":t.map(n=>Ja(n,e)).join(" and ")}function Ja(t,e){let{field:n,operator:r,value:s}=t;switch(r){case "contains":return Qa(n,String(s),e);case "startswith":return `startswith(${n},'${Ct(String(s))}')`;case "endswith":return `endswith(${n},'${Ct(String(s))}')`;default:return Za(n,r,s,e)}}function Qa(t,e,n){let r=Ct(e);return n==="v2"?`substringof('${r}',${t})`:`contains(${t},'${r}')`}function Za(t,e,n,r){let s=ec(n,r);return `${t} ${e} ${s}`}function ec(t,e){return typeof t=="number"||typeof t=="boolean"?String(t):tc(t)?nc(t,e):`'${Ct(t)}'`}function tc(t){return /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?/.test(t)}function nc(t,e){return e==="v2"?`datetime'${t.includes("T")?t:`${t}T00:00:00`}'`:t}function Ct(t){return t.replace(/'/g,"''")}function us(t,e,n,r){let s=r??new k,i=new Map;function o(c){let l=H(c),d=i.get(l);return d||(d=new M(e,l,s),i.set(l,d)),d}function a(c,l){return N(c,{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.
|
|
247
247
|
|
|
248
248
|
**When to use:**
|
|
249
249
|
- You have a natural language question about SAP data
|
|
@@ -253,7 +253,7 @@ Each entity set carries what SAP allows on it \u2014 \`creatable\`, \`updatable\
|
|
|
253
253
|
**When NOT to use:**
|
|
254
254
|
- You need to create, update, or delete data (use sap_create/sap_update/sap_delete)
|
|
255
255
|
- You already have an exact OData $filter string (use sap_query instead)
|
|
256
|
-
- 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:c,entitySet:l,filters:d,select:u,orderby:m,top:p,skip:f})=>{if(!J(l,
|
|
256
|
+
- 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:c,entitySet:l,filters:d,select:u,orderby:m,top:p,skip:f})=>{if(!J(l,n))return a(new Error(`Entity set '${l}' is not exposed. Configure --expose to include it.`));try{let g=o(c),S=(await g.getMetadata()).version,T=ls(d,S),y=await g.query(l,{filter:T||void 0,select:u,orderby:m,top:p,skip:f});return {content:[{type:"text",text:JSON.stringify({version:S,generatedFilter:T||"(none)",results:y.results,count:y.count,nextLink:y.nextLink},null,2)}]}}catch(g){return a(g)}});}function ps(t,e,n,r){let s=r??new k,i=new Map;function o(l){let d=H(l),u=i.get(d);return u||(u=new M(e,d,s),i.set(d,u)),u}function a(l,d){return N(l,{serviceUrl:d})}function c(l){return J(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.
|
|
257
257
|
|
|
258
258
|
**When to use:**
|
|
259
259
|
- You have a specific entity key and want one record
|
|
@@ -261,7 +261,7 @@ Each entity set carries what SAP allows on it \u2014 \`creatable\`, \`updatable\
|
|
|
261
261
|
|
|
262
262
|
**When NOT to use:**
|
|
263
263
|
- You want to list/filter multiple entities (use sap_query)
|
|
264
|
-
- 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:d,key:u,select:m,expand:p})=>{let f=c(d);if(f)return f;try{let g=o(l),h={select:m,expand:p},S=await g.read(d,u,h);return {content:[{type:"text",text:JSON.stringify(S,null,2)}]}}catch(g){return a(g,l)}}),
|
|
264
|
+
- 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:d,key:u,select:m,expand:p})=>{let f=c(d);if(f)return f;try{let g=o(l),h={select:m,expand:p},S=await g.read(d,u,h);return {content:[{type:"text",text:JSON.stringify(S,null,2)}]}}catch(g){return a(g,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.
|
|
265
265
|
|
|
266
266
|
**When to use:**
|
|
267
267
|
- You have an exact OData $filter string ready
|
|
@@ -271,7 +271,7 @@ Each entity set carries what SAP allows on it \u2014 \`creatable\`, \`updatable\
|
|
|
271
271
|
**When NOT to use:**
|
|
272
272
|
- You have a natural language question (use sap_nl_query instead)
|
|
273
273
|
- You don't know which service to query (use sap://routing/{domain} first, then sap_discover_services)
|
|
274
|
-
- 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:d,filter:u,select:m,expand:p,orderby:f,top:g,skip:h,maxPages:S})=>{let T=c(d);if(T)return T;try{let y=o(l),
|
|
274
|
+
- 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:d,filter:u,select:m,expand:p,orderby:f,top:g,skip:h,maxPages:S})=>{let T=c(d);if(T)return T;try{let y=o(l),b=Ee(g,h),D=await y.query(d,{filter:u,select:m,expand:p,orderby:f,top:b.top,skip:b.skip,maxPages:S}),ce=Ce({returned:D.results.length,totalCount:D.count,paging:b,hasMoreHint:D.nextLink?!0:void 0});return {content:[{type:"text",text:JSON.stringify({...D,...ce},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:d,data:u})=>{let m;try{m=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 p=c(d);if(p)return p;try{let g=await o(l).create(d,m);return {content:[{type:"text",text:JSON.stringify({created:g},null,2)}]}}catch(f){return a(f,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"}'`),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:d,key:u,data:m})=>{let p,f;try{p=JSON.parse(u),f=JSON.parse(m);}catch{return a(new Error("Invalid JSON in key or data parameter"))}let g=c(d);if(g)return g;try{return await o(l).update(d,p,f),{content:[{type:"text",text:JSON.stringify({updated:!0,entitySet:d,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"}'`)}),annotations:{readOnlyHint:false,destructiveHint:true,idempotentHint:true,openWorldHint:true}},async({serviceUrl:l,entitySet:d,key:u})=>{let m;try{m=JSON.parse(u);}catch{return a(new Error("Invalid JSON in key parameter"))}let p=c(d);if(p)return p;try{return await o(l).delete(d,m),{content:[{type:"text",text:JSON.stringify({deleted:!0,entitySet:d,key:u})}]}}catch(f){return a(f,l)}});}function We(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=>rc(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 rc(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:W(t.MESSAGE_V1),messageV2:W(t.MESSAGE_V2),messageV3:W(t.MESSAGE_V3),messageV4:W(t.MESSAGE_V4),parameter:W(t.PARAMETER),row:sc(t.ROW),field:W(t.FIELD),system:W(t.SYSTEM),logNo:W(t.LOG_NO),logMsgNo:W(t.LOG_MSG_NO)}}function W(t){if(t==null)return;let e=String(t);return e===""?void 0:e}function sc(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 ms(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 Pt=null;async function fs(t){if(Pt)return true;try{return Pt=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 gs(){if(!Pt)throw new Error("node-rfc not loaded \u2014 call tryLoadRfc() first");return Pt}var hs=6e4,oc=5e3,ac=2,cc=10,Re=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";}},Rt=class{pool;sessions=new Map;logger;closed=false;constructor(e,n){let s=gs().Pool;this.pool=new s({connectionParameters:{...e},poolOptions:{low:ac,high:cc}}),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 Re(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 Re(e);clearTimeout(r.idleTimer);try{let s=n?{WAIT:"X"}:{},i=await r.client.call("BAPI_TRANSACTION_COMMIT",s),{hasErrors:o}=We(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()+oc;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),hs)};return this.sessions.set(n,r),r}refreshIdle(e){clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>this.onIdle(e.id),hs);}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 qe(t){if(t instanceof Re)return {content:[{type:"text",text:JSON.stringify({code:"SESSION_NOT_FOUND",message:t.message,sessionId:t.sessionId})}],isError:true};let e=ms(t);return {content:[{type:"text",text:JSON.stringify(e)}],isError:true}}var dc={I:"IMPORT",E:"EXPORT",C:"CHANGING",T:"TABLES",X:"EXCEPTION"};function ys(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.
|
|
275
275
|
|
|
276
276
|
When to use:
|
|
277
277
|
- Custom Z_* function modules (no BAPI commit needed)
|
|
@@ -281,7 +281,7 @@ When NOT to use:
|
|
|
281
281
|
- For BAPIs that need transactional commit -> use sap_bapi_call + sap_bapi_commit instead
|
|
282
282
|
- 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.
|
|
283
283
|
|
|
284
|
-
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
|
|
284
|
+
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 qe(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).
|
|
285
285
|
|
|
286
286
|
When to use:
|
|
287
287
|
- Before calling an unfamiliar RFC/BAPI to understand its parameters
|
|
@@ -290,7 +290,7 @@ When to use:
|
|
|
290
290
|
When NOT to use:
|
|
291
291
|
- To search for function modules by name pattern -> use sap_rfc_search_functions
|
|
292
292
|
|
|
293
|
-
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 m=String(u.PARAMCLASS??"").trim(),p=
|
|
293
|
+
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 m=String(u.PARAMCLASS??"").trim(),p=dc[m]??"IMPORT",f=String(u.TABNAME??"").trim(),g=String(u.FIELDNAME??"").trim(),h=f&&g?`${f}-${g}`:f||g||void 0,S=String(u.DECIMALS??"").trim(),T=S===""?void 0:Number.parseInt(S,10),y=Number.isNaN(T)?void 0:T,b=String(u.INTLENGTH??"0").trim(),D=Number.parseInt(b===""?"0":b,10)||0,ce=String(u.DEFAULT??"").trim(),ze=String(u.PARAMTEXT??"").trim();return {name:String(u.PARAMETER??"").trim(),direction:p,type:String(u.EXID??"").trim(),length:D,decimals:y,optional:String(u.OPTIONAL??"").trim()==="X",default:ce===""?void 0:ce,text:ze===""?void 0:ze,ddicReference:h}}),d={name:i,remoteEnabled:String(a.REMOTE_CALL??"")==="X",updateTask:String(a.UPDATE_TASK??"")==="X",parameters:l};return {content:[{type:"text",text:JSON.stringify(d,null,2)}]}}catch(i){return qe(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.
|
|
294
294
|
|
|
295
295
|
When to use:
|
|
296
296
|
- BAPI_USER_CREATE1, BAPI_SALESORDER_CREATEFROMDAT2, BAPI_*_CHANGE, etc.
|
|
@@ -303,7 +303,7 @@ Critical behavior:
|
|
|
303
303
|
- The output contains a parsed \`return\` array (BAPIRET2 messages, camelCase) plus \`hasErrors\`, \`errorCount\`, \`warningCount\`.
|
|
304
304
|
- 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).
|
|
305
305
|
- hasErrors=true is NOT isError=true at the MCP transport level (the call itself succeeded).
|
|
306
|
-
- 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),c=
|
|
306
|
+
- 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),c=We(o),{RETURN:l,E_RETURN:d,...u}=o,m={},p={};for(let[f,g]of Object.entries(u))Array.isArray(g)?p[f]=g:m[f]=g;return {content:[{type:"text",text:JSON.stringify({sessionId:a,return:c.messages,hasErrors:c.hasErrors,errorCount:c.errorCount,warningCount:c.warningCount,exports:m,tables:p},null,2)}]}}catch(o){return qe(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.
|
|
307
307
|
|
|
308
308
|
When to use:
|
|
309
309
|
- After one or more sap_bapi_call invocations that returned hasErrors=false
|
|
@@ -312,7 +312,7 @@ When NOT to use:
|
|
|
312
312
|
- 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)
|
|
313
313
|
- For non-BAPI RFC calls (sap_rfc_call cannot participate)
|
|
314
314
|
|
|
315
|
-
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),c=
|
|
315
|
+
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),c=We(i);return {content:[{type:"text",text:JSON.stringify({sessionId:a,return:c.messages,committed:o},null,2)}]}}catch(i){return qe(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.
|
|
316
316
|
|
|
317
317
|
When to use:
|
|
318
318
|
- Discover BAPIs/FMs by partial name (e.g. "BAPI_USER_*", "Z_*")
|
|
@@ -323,7 +323,7 @@ When NOT to use:
|
|
|
323
323
|
Wildcards (SAP-native, NOT glob):
|
|
324
324
|
- \`*\` matches any sequence of characters
|
|
325
325
|
- \`+\` matches a single character
|
|
326
|
-
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,f={functions:(u?c.slice(0,d):c).map(g=>{let h=String(g.TEXT??"").trim();return {name:String(g.FUNCNAME??"").trim(),groupName:String(g.GROUPNAME??"").trim(),shortText:h===""?void 0:h}}),total:l,...u?{truncated:!0}:{}};return {content:[{type:"text",text:JSON.stringify(f,null,2)}]}}catch(o){return We(o)}});}function qe(n,e){let t=new McpServer({name:br,version:Tr},{capabilities:{logging:{},resources:{},prompts:{},tools:{listChanged:true}}}),r={readOnly:e?.readOnly??false,activeTiers:e?.tiers??jr,...e?.allowedTiers?{allowedTiers:e.allowedTiers}:{},hasIdocConfig:e?.hasIdocConfig??false,hasRfcConfig:e?.hasRfcConfig??false},s=new At(r),i=t.registerTool.bind(t),o=e?.toolTimeoutMs??gt*1e3;t.registerTool=((h,...S)=>{let T=S.length-1,y=S[T];typeof y=="function"&&(S[T]=hr(h,y,{toolTimeoutMs:o,logger:e?.logger,tlsVerifyDisabled:e?.httpOptions?.tlsVerify===false}));let v=i(h,...S);return s.registerHandle(h,v),v});let a={baseUrl:n.baseUrl,...e?.cacheDir?{persistDir:e.cacheDir}:{},...e?.logger?{onWarn:(h,S)=>e.logger?.warn({err:S},h)}:{}},c=e?.caches?.catalog??new G(a),l=e?.caches?.metadata??new k(a),d=ze(n),u=new me(d,e?.httpOptions);e?.userContext&&(u.userContext=e.userContext);let m=e?.exposePatterns,p=e?.sapClient??n.sapClient;Ar(t),vr(t),ns(t,u,{baseUrl:n.baseUrl,sapClient:p,tlsVerify:e?.httpOptions?.tlsVerify}),es(t,u,m),Zr(t,u,{catalogV2Url:e?.catalogV2Url,catalogV4Url:e?.catalogV4Url,baseUrl:n.baseUrl,onDiscoveryMatch:e?.onDiscoveryMatch,...e?.concurrency?{concurrency:e.concurrency}:{}},c),ls(t,u,m,l),ts(t,u,l),qr(t,u,l),ds(t,u,m,l);let f=e?.idocStore??new ve,g=new ft(u,p,e?.partnerDefaults);return is(t,u,g,f),$r(t,f),e?.rfcAvailable&&e.rfcPool&&e.logger&&gs(t,e.rfcPool,e.logger),as(t,s),Lr(t,u,l),Ur(t,u,l),Fr(t),s.applyInitialVisibility(),{server:t,registry:s,httpClient:u}}var dc=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((n,e)=>{let t=[n.snc_qop,n.snc_myname,n.snc_partnername,n.snc_lib].filter(r=>r!==void 0).length;if(t>0&&t<4){let r=[];n.snc_qop||r.push("SAP_SNC_QOP"),n.snc_myname||r.push("SAP_SNC_MYNAME"),n.snc_partnername||r.push("SAP_SNC_PARTNERNAME"),n.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 hn(n=process.env){let e=n.SAP_USERNAME,t=n.SAP_PASSWORD;if(!e||!t)return null;let r=!!(n.SAP_RFC_ASHOST&&n.SAP_RFC_SYSNR),s=!!(n.SAP_RFC_MSHOST&&n.SAP_RFC_GROUP);if(!r&&!s)return null;let i={user:e,passwd:t,client:n.SAP_CLIENT,lang:n.SAP_RFC_LANG??"EN"};r&&(i.ashost=n.SAP_RFC_ASHOST,i.sysnr=n.SAP_RFC_SYSNR),s&&(i.mshost=n.SAP_RFC_MSHOST,i.msserv=n.SAP_RFC_MSSERV,i.group=n.SAP_RFC_GROUP);let o=dc.safeParse({snc_qop:n.SAP_SNC_QOP,snc_myname:n.SAP_SNC_MYNAME,snc_partnername:n.SAP_SNC_PARTNERNAME,snc_lib:n.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 hs(n=process.env){try{return hn(n)!==null}catch{return false}}var Rt=class{constructor(e={}){this.options=e;this.discovery=e.discoveryClient??new Z;}discovery;jwks;jwksIssuer;get verifies(){return !!this.options.issuerUrl}async verify(e){let t=e.authorization??e.Authorization;if(typeof t!="string"||!t.startsWith("Bearer "))return {kind:"none"};let r=t.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 mc(r)?{kind:"rejected",reason:s instanceof Error?s.message:"Signaturpr\xFCfung fehlgeschlagen"}:{kind:"none"}}}withoutVerification(e){try{let t=decodeJwt(e);return typeof t.sub!="string"?{kind:"none"}:{kind:"user",context:{token:e,sub:t.sub,email:typeof t.email=="string"?t.email:void 0,name:typeof t.name=="string"?t.name:void 0,verified:!1}}}catch{return {kind:"none"}}}async getKeySet(e){if(this.jwks&&this.jwksIssuer===e)return this.jwks;let t=await this.discovery.discover(e);return this.jwks=createRemoteJWKSet(new URL(t.jwks_uri)),this.jwksIssuer=e,this.jwks}};function mc(n){let e=n.split(".");return e.length===3&&e.every(t=>t.length>0)}var fc="x-sap-username",gc="x-sap-password",hc="x-sap-authorization";function yn(n,e){let t=n[e]??n[e.toLowerCase()],r=Array.isArray(t)?t[0]:t;return typeof r=="string"&&r.length>0?r:void 0}function yc(n){let e=yn(n,fc),t=yn(n,gc);if(e&&t)return {username:e,password:t};let r=yn(n,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 Sn(n,e){let t=yc(e);if(n.authType==="user-basic"){if(!t)return {kind:"missing",hint:`Destination "${n.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}=n;return {kind:"ok",config:{name:r,baseUrl:s,authType:"basic",...i!==void 0?{sapClient:i}:{},username:t.username,password:t.password},username:t.username}}return t?{kind:"rejected",reason:`Destination "${n.name}" (authType ${n.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:n}}var Sc=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 oe(n,e,t){n.setHeader("Cache-Control","no-store"),n.status(e).json(t);}function ys(n){return n.ip??n.socket?.remoteAddress??"unknown"}function Ss(n,e){let t=n?.[e];return t==="true"||t==="1"||t===""}function Ts(n){n.use("/admin",(e,t)=>{oe(t,404,{error:"Admin API not enabled",hint:"Start the server with --admin-token <secret> (or SAP_MCP_ADMIN_TOKEN) and --destinations <file>."});});}function bs(n,e){let{destinations:t,logger:r}=e,s=new ye({apiKey:e.adminToken,tokens:()=>[]});n.use("/admin",(c,l,d)=>{let u=c,m=l,p=ys(u),f=s.authenticate(u.headers.authorization,p);if(f.kind==="rate-limited"){r?.warn({clientAddress:p,path:u.originalUrl??u.path,retryAfterSeconds:f.retryAfterSeconds},"Admin-API: zu viele fehlgeschlagene Anmeldeversuche von dieser Adresse"),m.setHeader("Retry-After",String(f.retryAfterSeconds)),oe(m,429,{error:"Too Many Requests",retryAfterSeconds:f.retryAfterSeconds});return}if(f.kind!=="api-key"){r?.warn({clientAddress:p,path:u.originalUrl??u.path},"Admin-API: Anfrage abgewiesen"),oe(m,401,{error:"Unauthorized"});return}d();});let i=()=>{if(t.source!=="file"||!t.filePath)throw new P("Admin API requires a destinations file: start the server with --destinations <file>.",409);return new Se({filePath:t.filePath,env:e.env})},o=(c,l)=>(d,u)=>{let m=d,p=u,f=ys(m);(async()=>{try{let{status:g,body:h,changed:S}=l(m);if(S){let T=await t.reload();h.reloaded=T.ok,T.ok||(h.reloadErrors=T.errors),r?.info({action:c,clientAddress:f,target:m.params?.name??m.params?.ref??m.body?.destination,reloaded:T.ok},"Admin-API: \xC4nderung ausgef\xFChrt");}oe(p,g,h);}catch(g){if(g instanceof P){oe(p,g.status,{error:g.message});return}r?.warn({action:c,clientAddress:f,err:g instanceof Error?g.message:String(g)},"Admin-API: unerwarteter Fehler"),oe(p,500,{error:"Internal Server Error"});}})();},a=c=>{let l=c.body;if(l===null||typeof l!="object"||Array.isArray(l))throw new P("Request body must be a JSON object (Content-Type: application/json).",400);return l};n.get("/admin/destinations",o("destinations.list",()=>({status:200,body:{destinations:i().listDestinations()},changed:!1}))),n.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:Ss(c.query,"allowMissingEnv")});return {status:u?200:201,body:{name:l,replaced:u,warnings:m},changed:!0}})),n.delete("/admin/destinations/:name",o("destinations.delete",c=>{let l=c.params?.name??"",{removedTokens:d}=i().removeDestination(l,{force:Ss(c.query,"force")});return {status:200,body:{name:l,removedTokens:d},changed:!0}})),n.get("/admin/tokens",o("tokens.list",()=>({status:200,body:{tokens:i().listTokens()},changed:!1}))),n.post("/admin/tokens",o("tokens.issue",c=>{let l=Sc.safeParse(a(c));if(!l.success)throw new P(`Invalid body: ${l.error.issues.map(g=>`${g.path.join(".")||"(root)"}: ${g.message}`).join("; ")}`,400);let{destination:d,...u}=l.data,{token:m,hash:p,entry:f}=i().issueToken(d,u);return {status:201,body:{token:m,hash:p.replace(/^sha256:/,"").slice(0,12),destination:f.destination,label:f.label,policy:f.policy,createdAt:f.createdAt},changed:!0}})),n.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}})),n.use("/admin",(c,l)=>{oe(l,404,{error:"Not Found"});});}function vs(n,e){return e?.kind==="token"?n.destinations?.get(e.token.destination):n.destinations?n.destinations.defaultDestination():n.config}function As(n,e){if(e?.kind!=="token"||!e.token.policy)return n;let t=e.token.policy;return {...n,readOnly:n.readOnly===true||t.readOnly===true,...t.tiers?{allowedTiers:[...t.tiers]}:{}}}function ws(n,e){n.writeHead(503,{"Content-Type":"application/json"});let t=e?.names().length??0;n.end(JSON.stringify({error:"Keine Destination bestimmbar",hint:t===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 Tn=class n{constructor(e){this.persistDir=e;}entries=new Map;static MAX_USER_SCOPES=100;for(e,t){let r=`${e.name}\0${e.baseUrl}\0${t??""}`,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);t!==void 0&&this.evictOldestUserScopes(e);let i={baseUrl:e.baseUrl,...this.persistDir&&t===void 0?{persistDir:this.persistDir}:{}};s={catalog:new G(i),metadata:new k(i)},this.entries.set(r,s);}return s}evictOldestUserScopes(e){let t=`${e.name}\0${e.baseUrl}\0`,r=[...this.entries.keys()].filter(s=>s.startsWith(t)&&s!==t);for(;r.length>=n.MAX_USER_SCOPES;)this.entries.delete(r.shift());}};function Es(n,e){if(e.kind==="missing"){n.writeHead(401,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:"SAP login required",hint:e.hint}));return}n.writeHead(400,{"Content-Type":"application/json"}),n.end(JSON.stringify({error:e.reason}));}function Cs(n,e){let t=n.logger;return !e||!t||typeof t.child!="function"?n:{...n,logger:t.child({sapUser:e})}}async function Ps(n){let e=new Rt({...n.jwtIssuer?{issuerUrl:n.jwtIssuer}:{},...n.jwtAudience?{audience:n.jwtAudience}:{}}),t=createMcpExpressApp({host:"0.0.0.0"}),r=new ye({apiKey:n.apiKey,tokens:()=>n.destinations?.tokens()??[]}),s=n.serverOptions?.logger;t.use((a,c,l)=>{let d=a,u=c,m=l;if(d.path==="/healthz"){m();return}if(d.path==="/admin"||d.path.startsWith("/admin/")){m();return}if(n.webhookSecret&&d.path==="/idoc"){m();return}let p=d.ip??d.socket?.remoteAddress??"unknown",f=r.authenticate(d.headers.authorization,p);if(f.kind==="rate-limited"){s?.warn({clientAddress:p,path:d.path,retryAfterSeconds:f.retryAfterSeconds},"MCP-Anfrage abgewiesen: zu viele fehlgeschlagene Anmeldeversuche von dieser Adresse"),u.setHeader("Retry-After",String(f.retryAfterSeconds)),u.status(429).json({error:"Too Many Requests",retryAfterSeconds:f.retryAfterSeconds});return}if(f.kind==="rejected"){s?.warn({clientAddress:p,path:d.path,reason:f.reason},"MCP-Anfrage abgewiesen"),u.status(401).json({error:"Unauthorized"});return}d.sapAuth=f,m();}),t.get("/healthz",(a,c)=>{c.status(200).json({status:"ok"});}),n.adminToken&&n.destinations?bs(t,{adminToken:n.adminToken,destinations:n.destinations,env:n.env??process.env,logger:s}):Ts(t),n.transport==="http"?wc(t,n,e):Ec(t,n,e);let i=ut(n.idocStore,{secret:n.webhookSecret,maxBodyBytes:n.webhookMaxBodyBytes});t.post("/idoc",(a,c)=>{i(a,c);}),t.use((a,c,l,d)=>{let u=a,m=l;if(u?.type==="entity.parse.failed"&&!m.headersSent){m.status(400).json({error:`Invalid JSON body: ${u.message??"parse error"}`});return}d(a);});let o=createServer(t);return new Promise(a=>{o.listen(n.port,()=>{let c=o.address(),l=typeof c=="object"&&c!==null?c.port:n.port;a({port:l,close:()=>new Promise((d,u)=>{o.close(m=>m?u(m):d());})});});})}function wc(n,e,t){let r=new Tn(e.serverOptions?.cacheDir),s=e.serverOptions?.logger;n.all("/mcp",(i,o)=>{let a=i,c=o,l=vs(e,a.sapAuth);if(!l){ws(c,e.destinations);return}let d=Sn(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"),Es(c,d);return}let u=d.config,m=d.username,p=new StreamableHTTPServerTransport({sessionIdGenerator:void 0}),f=g=>{let{server:h}=qe(u,{...As(Cs(e.serverOptions,m),a.sapAuth),sapClient:u.sapClient,userContext:g,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"));});};t.verify(a.headers).then(g=>{if(g.kind==="rejected"){c.writeHead(401,{"Content-Type":"application/json"}),c.end(JSON.stringify({error:"Unauthorized",reason:g.reason,hint:"Das mitgeschickte Bearer-Token hat die Signaturpr\xFCfung gegen SAP_JWT_ISSUER nicht bestanden."}));return}let h=g.kind==="user"?g.context:null;f(h);});});}function Ec(n,e,t){let r=new Map;n.get("/sse",(s,i)=>{let o=s,a=i,c=vs(e,o.sapAuth);if(!c){ws(a,e.destinations);return}let l=Sn(c,o.headers??{});if(l.kind!=="ok"){Es(a,l);return}let d=l.config,u=new SSEServerTransport("/message",a),{server:m,httpClient:p}=qe(d,{...As(Cs(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"));});}),n.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);t.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 _c=`Usage: guniweb-sap-mcp [options]
|
|
326
|
+
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,f={functions:(u?c.slice(0,d):c).map(g=>{let h=String(g.TEXT??"").trim();return {name:String(g.FUNCNAME??"").trim(),groupName:String(g.GROUPNAME??"").trim(),shortText:h===""?void 0:h}}),total:l,...u?{truncated:!0}:{}};return {content:[{type:"text",text:JSON.stringify(f,null,2)}]}}catch(o){return qe(o)}});}function Ke(t,e){let n=new McpServer({name:wr,version:br},{capabilities:{logging:{},resources:{},prompts:{},tools:{listChanged:true}}}),r={readOnly:e?.readOnly??false,activeTiers:e?.tiers??Wr,...e?.allowedTiers?{allowedTiers:e.allowedTiers}:{},hasIdocConfig:e?.hasIdocConfig??false,hasRfcConfig:e?.hasRfcConfig??false},s=new vt(r),i=n.registerTool.bind(n),o=e?.toolTimeoutMs??ht*1e3;n.registerTool=((h,...S)=>{let T=S.length-1,y=S[T];typeof y=="function"&&(S[T]=Sr(h,y,{toolTimeoutMs:o,logger:e?.logger,tlsVerifyDisabled:e?.httpOptions?.tlsVerify===false}));let b=i(h,...S);return s.registerHandle(h,b),b});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 G(a),l=e?.caches?.metadata??new k(a),d=Ye(t),u=new fe(d,e?.httpOptions);e?.userContext&&(u.userContext=e.userContext);let m=e?.exposePatterns,p=e?.sapClient??t.sapClient;Er(n),vr(n),ss(n,u,{baseUrl:t.baseUrl,sapClient:p,tlsVerify:e?.httpOptions?.tlsVerify}),ns(n,u,m),ts(n,u,{catalogV2Url:e?.catalogV2Url,catalogV4Url:e?.catalogV4Url,baseUrl:t.baseUrl,onDiscoveryMatch:e?.onDiscoveryMatch,...e?.concurrency?{concurrency:e.concurrency}:{}},c),ps(n,u,m,l),rs(n,u,l),Gr(n,u,l),us(n,u,m,l);let f=e?.idocStore??new we,g=new gt(u,p,e?.partnerDefaults);return as(n,u,g,f),Br(n,f),e?.rfcAvailable&&e.rfcPool&&e.logger&&ys(n,e.rfcPool,e.logger),ds(n,s),Fr(n,u,l),Lr(n,u,l),jr(n),s.applyInitialVisibility(),{server:n,registry:s,httpClient:u}}var uc=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 Sn(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=uc.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 Ss(t=process.env){try{return Sn(t)!==null}catch{return false}}var _t=class{constructor(e={}){this.options=e;this.discovery=e.discoveryClient??new ee;}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 gc(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 gc(t){let e=t.split(".");return e.length===3&&e.every(n=>n.length>0)}var hc="x-sap-username",yc="x-sap-password",Sc="x-sap-authorization";function Tn(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 Tc(t){let e=Tn(t,hc),n=Tn(t,yc);if(e&&n)return {username:e,password:n};let r=Tn(t,Sc),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 An(t,e){let n=Tc(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 Ac=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 ae(t,e,n){t.setHeader("Cache-Control","no-store"),t.status(e).json(n);}function Ts(t){return t.ip??t.socket?.remoteAddress??"unknown"}function As(t,e){let n=t?.[e];return n==="true"||n==="1"||n===""}function bs(t){t.use("/admin",(e,n)=>{ae(n,404,{error:"Admin API not enabled",hint:"Start the server with --admin-token <secret> (or SAP_MCP_ADMIN_TOKEN) and --destinations <file>."});});}function ws(t,e){let{destinations:n,logger:r}=e,s=new Se({apiKey:e.adminToken,tokens:()=>[]});t.use("/admin",(c,l,d)=>{let u=c,m=l,p=Ts(u),f=s.authenticate(u.headers.authorization,p);if(f.kind==="rate-limited"){r?.warn({clientAddress:p,path:u.originalUrl??u.path,retryAfterSeconds:f.retryAfterSeconds},"Admin-API: zu viele fehlgeschlagene Anmeldeversuche von dieser Adresse"),m.setHeader("Retry-After",String(f.retryAfterSeconds)),ae(m,429,{error:"Too Many Requests",retryAfterSeconds:f.retryAfterSeconds});return}if(f.kind!=="api-key"){r?.warn({clientAddress:p,path:u.originalUrl??u.path},"Admin-API: Anfrage abgewiesen"),ae(m,401,{error:"Unauthorized"});return}d();});let i=()=>{if(n.source!=="file"||!n.filePath)throw new P("Admin API requires a destinations file: start the server with --destinations <file>.",409);return new Te({filePath:n.filePath,env:e.env})},o=(c,l)=>(d,u)=>{let m=d,p=u,f=Ts(m);(async()=>{try{let{status:g,body:h,changed:S}=l(m);if(S){let T=await n.reload();h.reloaded=T.ok,T.ok||(h.reloadErrors=T.errors),r?.info({action:c,clientAddress:f,target:m.params?.name??m.params?.ref??m.body?.destination,reloaded:T.ok},"Admin-API: \xC4nderung ausgef\xFChrt");}ae(p,g,h);}catch(g){if(g instanceof P){ae(p,g.status,{error:g.message});return}r?.warn({action:c,clientAddress:f,err:g instanceof Error?g.message:String(g)},"Admin-API: unerwarteter Fehler"),ae(p,500,{error:"Internal Server Error"});}})();},a=c=>{let l=c.body;if(l===null||typeof l!="object"||Array.isArray(l))throw new P("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:As(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:As(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=Ac.safeParse(a(c));if(!l.success)throw new P(`Invalid body: ${l.error.issues.map(g=>`${g.path.join(".")||"(root)"}: ${g.message}`).join("; ")}`,400);let{destination:d,...u}=l.data,{token:m,hash:p,entry:f}=i().issueToken(d,u);return {status:201,body:{token:m,hash:p.replace(/^sha256:/,"").slice(0,12),destination:f.destination,label:f.label,policy:f.policy,createdAt:f.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)=>{ae(l,404,{error:"Not Found"});});}function vs(t,e){return e?.kind==="token"?t.destinations?.get(e.token.destination):t.destinations?t.destinations.defaultDestination():t.config}function Es(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 Cs(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 bn=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 G(i),metadata:new k(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 Ps(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 Rs(t,e){let n=t.logger;return !e||!n||typeof n.child!="function"?t:{...t,logger:n.child({sapUser:e})}}async function _s(t){let e=new _t({...t.jwtIssuer?{issuerUrl:t.jwtIssuer}:{},...t.jwtAudience?{audience:t.jwtAudience}:{}}),n=createMcpExpressApp({host:"0.0.0.0"}),r=new Se({apiKey:t.apiKey,tokens:()=>t.destinations?.tokens()??[]}),s=t.serverOptions?.logger;n.use((a,c,l)=>{let d=a,u=c,m=l;if(d.path==="/healthz"){m();return}if(d.path==="/admin"||d.path.startsWith("/admin/")){m();return}if(t.webhookSecret&&d.path==="/idoc"){m();return}let p=d.ip??d.socket?.remoteAddress??"unknown",f=r.authenticate(d.headers.authorization,p);if(f.kind==="rate-limited"){s?.warn({clientAddress:p,path:d.path,retryAfterSeconds:f.retryAfterSeconds},"MCP-Anfrage abgewiesen: zu viele fehlgeschlagene Anmeldeversuche von dieser Adresse"),u.setHeader("Retry-After",String(f.retryAfterSeconds)),u.status(429).json({error:"Too Many Requests",retryAfterSeconds:f.retryAfterSeconds});return}if(f.kind==="rejected"){s?.warn({clientAddress:p,path:d.path,reason:f.reason},"MCP-Anfrage abgewiesen"),u.status(401).json({error:"Unauthorized"});return}d.sapAuth=f,m();}),n.get("/healthz",(a,c)=>{c.status(200).json({status:"ok"});}),t.adminToken&&t.destinations?ws(n,{adminToken:t.adminToken,destinations:t.destinations,env:t.env??process.env,logger:s}):bs(n),t.transport==="http"?Cc(n,t,e):Pc(n,t,e);let i=pt(t.idocStore,{secret:t.webhookSecret,maxBodyBytes:t.webhookMaxBodyBytes});n.post("/idoc",(a,c)=>{i(a,c);}),n.use((a,c,l,d)=>{let u=a,m=l;if(u?.type==="entity.parse.failed"&&!m.headersSent){m.status(400).json({error:`Invalid JSON body: ${u.message??"parse error"}`});return}d(a);});let o=createServer(n);return new Promise(a=>{o.listen(t.port,()=>{let c=o.address(),l=typeof c=="object"&&c!==null?c.port:t.port;a({port:l,close:()=>new Promise((d,u)=>{o.close(m=>m?u(m):d());})});});})}function Cc(t,e,n){let r=new bn(e.serverOptions?.cacheDir),s=e.serverOptions?.logger;t.all("/mcp",(i,o)=>{let a=i,c=o,l=vs(e,a.sapAuth);if(!l){Cs(c,e.destinations);return}let d=An(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"),Ps(c,d);return}let u=d.config,m=d.username,p=new StreamableHTTPServerTransport({sessionIdGenerator:void 0}),f=g=>{let{server:h}=Ke(u,{...Es(Rs(e.serverOptions,m),a.sapAuth),sapClient:u.sapClient,userContext:g,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(g=>{if(g.kind==="rejected"){c.writeHead(401,{"Content-Type":"application/json"}),c.end(JSON.stringify({error:"Unauthorized",reason:g.reason,hint:"Das mitgeschickte Bearer-Token hat die Signaturpr\xFCfung gegen SAP_JWT_ISSUER nicht bestanden."}));return}let h=g.kind==="user"?g.context:null;f(h);});});}function Pc(t,e,n){let r=new Map;t.get("/sse",(s,i)=>{let o=s,a=i,c=vs(e,o.sapAuth);if(!c){Cs(a,e.destinations);return}let l=An(c,o.headers??{});if(l.kind!=="ok"){Ps(a,l);return}let d=l.config,u=new SSEServerTransport("/message",a),{server:m,httpClient:p}=Ke(d,{...Es(Rs(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 Oc=`Usage: guniweb-sap-mcp [options]
|
|
327
327
|
guniweb-sap-mcp destinations list|add|remove ... (manage destinations.json)
|
|
328
328
|
guniweb-sap-mcp tokens issue|revoke|list ... (see: guniweb-sap-mcp tokens --help)
|
|
329
329
|
|
|
@@ -386,5 +386,5 @@ Configuration via environment variables: SAP_BASE_URL, SAP_AUTH_TYPE
|
|
|
386
386
|
|
|
387
387
|
Documentation, releases, issues: https://github.com/guniweb/guniweb-sap-mcp
|
|
388
388
|
Support: support@guniweb.de \u2014 https://guniweb.de/sap-mcp
|
|
389
|
-
`;async function
|
|
389
|
+
`;async function Ic(){let t=process.argv.slice(2);if(t[0]!==void 0&&rr.has(t[0])){let y=await sr(t,{env:process.env,stdout:b=>process.stdout.write(b),stderr:b=>process.stderr.write(b)});process.exit(y);}let e=vn();e.help&&(process.stdout.write(Oc),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(b=>process.env[b]);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 Gn();}let i=[],o=s?be.fromEnv(Dc(s.baseUrl)):e.destinationsFile?await be.fromFile(e.destinationsFile,{env:process.env,onWarn:y=>i.push(y),allowMissing:r}):be.fromEnv(process.env),a=o.defaultDestination(),c=o.tokens().length;if(!a&&(n==="stdio"||c===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=Lt(process.env),d=ui({transport:{target:"pino/file",options:{destination:2}}});l.tlsVerify===false&&d.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&&d.info({hasCert:true,hasPassphrase:!!l.clientKeyPassphrase},"X.509 client certificate enabled for OData transport"),d.info(a?{baseUrl:a.baseUrl,authType:a.authType}:{destinations:o.names(),tokens:c},"SAP MCP server starting"),s&&d.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)d.warn(y);r&&!a&&c===0&&d.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"?(d.info({file:o.filePath,destinations:o.names(),defaultDestination:a?.name??null,tokens:c},"Destinations aus Datei geladen"),o.onChange(y=>{d.info({version:y.version,destinations:y.names,tokens:y.tokenCount},"Destinations neu geladen");}),o.onError(y=>{d.warn({errors:y},"Destinations-Datei ung\xFCltig \u2014 die letzte g\xFCltige Konfiguration bleibt in Kraft");})):d.info({destinations:o.names()},'Destination "default" aus SAP_*-Umgebung (keine destinations.json konfiguriert)'),e.allowWriteExplicit?d.warn("Schreibwerkzeuge sind aktiv (--allow-write): sap_create, sap_update, sap_delete, sap_function und sap_idoc_send k\xF6nnen Daten im SAP-System ver\xE4ndern."):d.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())Nn(o.names().length>1?d.child({destination:y}):d,o.get(y)?.sapClient);e.transport&&e.transport!=="stdio"&&(process.env.SAP_JWT_ISSUER?d.info({issuer:process.env.SAP_JWT_ISSUER},"Eingehende Benutzer-Token werden gegen die JWKS des Issuers gepr\xFCft"):d.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&&d.info({exposePatterns:e.expose},"Entity set filtering active");let u=await fs(d),m;if(u){let y=Sn(process.env);if(y){y.snc_lib&&!existsSync(y.snc_lib)&&d.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."),m=new Rt(y,d);let b=!!y.snc_qop;d.info({ashost:y.ashost,mshost:y.mshost,snc:b,...b?{qop:y.snc_qop,myname:y.snc_myname}:{}},b?"RFC pool ready (SNC-secured)":"RFC pool ready");}else d.info("node-rfc loaded but no SAP_RFC_* config \u2014 RFC tier requires sap_enable_tools rfc + env vars to activate");}let p=new we,f={exposePatterns:e.expose,idocStore:p,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=>{d.info({path:y,attemptedPaths:3},"V2 catalog auto-discovery: matched path");}},g=e.toolTimeoutSeconds??ht;g===0?d.warn("Zeitbudget je Tool-Aufruf abgeschaltet (--tool-timeout 0): ein Aufruf kann unbegrenzt laufen und die Client-Verbindung bis zu deren eigenem Timeout blockieren."):d.info({toolTimeoutSeconds:g},"Zeitbudget je Tool-Aufruf aktiv"),e.cacheDir?d.info({cacheDir:e.cacheDir},"Cache-Ablage aktiv (\xFCberlebt Neustarts)"):d.info("Kein Cache-Verzeichnis konfiguriert (--cache-dir / SAP_CACHE_DIR): Katalog und Metadaten werden nach jedem Neustart erneut aus SAP geholt.");let h={...f,toolTimeoutMs:g*1e3,cacheDir:e.cacheDir,readOnly:e.readOnly,tiers:e.tiers,hasIdocConfig:En(e),hasRfcConfig:Ss(process.env),rfcAvailable:u,rfcPool:m,logger:d},S;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}=Ke(a,h),b=new StdioServerTransport;if(await y.connect(b),d.info("SAP MCP server connected on stdio transport"),e.webhookPort){e.webhookSecret||d.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 D=await dr(e.webhookPort,p,{host:e.webhookHost,secret:e.webhookSecret,maxBodyBytes:e.webhookMaxBodyBytes});d.info({port:D.port,auth:e.webhookSecret?"secret":"open"},"IDoc webhook server listening");}}else {let y=e.port??8808,b=!!e.apiKey||c>0;!e.webhookSecret&&!b&&d.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(),S=await _s({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:p,webhookSecret:e.webhookSecret,webhookMaxBodyBytes:e.webhookMaxBodyBytes}),d.info({transport:n,port:S.port,auth:[...e.apiKey?["api-key"]:[],...c>0?[`tokens(${c})`]:[]].join("+")||"open",adminApi:e.adminToken?"/admin (admin-token)":"off"},"SAP MCP server listening on HTTP"),e.adminToken&&d.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 b=o.get(y);if(!b)continue;let D=o.names().length>1?d.child({destination:y}):d;if(b.authType==="user-basic"){D.info({baseUrl:b.baseUrl,sapClient:b.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}xn(D,b,l);}let T=async y=>{d.info({signal:y},"Shutting down...");try{o.close(),m&&await m.closeAll(),S&&await S.close(),s&&await s.close();}catch(b){d.error({err:b},"Shutdown error (continuing)");}process.exit(0);};process.on("SIGTERM",()=>T("SIGTERM")),process.on("SIGINT",()=>T("SIGINT"));}function Dc(t){let e=Kn(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}}Ic().catch(t=>{process.stderr.write(`Fatal error: ${t instanceof Error?t.message:String(t)}
|
|
390
390
|
`),process.exit(1);});
|