guniweb-sap-mcp 0.2.1 → 0.2.2

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.
Files changed (3) hide show
  1. package/README.md +66 -8
  2. package/dist/index.js +56 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -36,7 +36,7 @@ graph LR
36
36
  ### Tool Visibility (v1.5)
37
37
  - **3-Tier Tool Registry** -- Core / OData / IDoc tiers with on-demand activation
38
38
  - **`sap_enable_tools`** -- LLMs activate additional tool tiers at runtime
39
- - **`--read-only` Mode** -- Hide all write/destructive tools
39
+ - **Read-only by default (since 0.2.2)** -- write tools require `--allow-write`
40
40
  - **IDoc Auto-Disable** -- IDoc tools hidden when no IDoc config present
41
41
  - **MCP Annotations** -- readOnlyHint, destructiveHint, idempotentHint on all 17 tools
42
42
 
@@ -67,7 +67,7 @@ graph TB
67
67
  - **Three Transport Modes** -- stdio, Streamable HTTP, SSE
68
68
  - **API Key Protection** -- Secure HTTP transport with Bearer token
69
69
  - **Docker Ready** -- Docker Compose for n8n + SAP MCP sidecar
70
- - **918 Tests** -- Unit, integration, E2E with CI/CD via GitHub Actions
70
+ - **1143 Tests** -- Unit, integration, E2E with CI/CD via GitHub Actions
71
71
 
72
72
  ## Quick Start
73
73
 
@@ -84,7 +84,9 @@ export SAP_BASE_URL=https://your-sap-system.example.com
84
84
  export SAP_AUTH_TYPE=basic
85
85
  export SAP_USERNAME=your-user
86
86
  export SAP_PASSWORD=your-password
87
- export SAP_CLIENT=100
87
+ export SAP_CLIENT=100 # do not omit: without it a client-dependent Gateway
88
+ # answers 401 "Anmeldung fehlgeschlagen", which looks
89
+ # like a wrong password but is not one
88
90
  ```
89
91
 
90
92
  ### 3. Run
@@ -98,9 +100,65 @@ guniweb-sap-mcp --transport http --port 8808
98
100
 
99
101
  # With tool tier control
100
102
  guniweb-sap-mcp --transport http --port 8808 --tiers core,odata
101
- guniweb-sap-mcp --transport http --port 8808 --read-only
103
+ guniweb-sap-mcp --transport http --port 8808 --allow-write # writes are OFF by default
104
+
105
+ # Production: authenticate the MCP endpoint AND the inbound IDoc webhook
106
+ guniweb-sap-mcp --transport http --port 8808 --api-key <mcp-key> --webhook-secret <idoc-secret>
107
+ ```
108
+
109
+ #### Production hardening
110
+
111
+ | Variable / Flag | Default | Description |
112
+ |-------------------------------|----------------|-------------------------------------------------------------------------------------------------|
113
+ | `--tool-timeout` / `SAP_MCP_TOOL_TIMEOUT` | `30` (seconds) | Time budget for a **whole tool call**, however many SAP requests it makes. On expiry the client gets a valid MCP response with `truncated: true` and a plain-language reason — never a hanging connection. Paginated reads return the pages fetched so far. `0` disables the budget (logged as a warning at startup) |
114
+ | `SAP_HTTP_TIMEOUT_MS` | `30000` | Timeout for a **single** outbound SAP request (ms). Hanging gateways fail fast with a structured `TIMEOUT` error instead of stalling the n8n agent. Capped by whatever is left of the tool budget above |
115
+ | `--cache-dir` / `SAP_CACHE_DIR` | _(unset = in-memory only)_ | Persist the catalog and metadata caches to disk so a restart does not refetch them. Without it every restart re-reads the full catalog — on a system with 1222 services that is the cold start that breaks client timeouts. n8n evicts its own client entry on a transport error, so restarts are not rare |
116
+ | `SAP_CA_CERT` | _(unset)_ | Path to the root CA that signs your SAP certificate — the documented way past a corporate PKI. Mirrored into `NODE_EXTRA_CA_CERTS`. Prefer this over `SAP_TLS_VERIFY=false`, which disables the check rather than solving it |
117
+ | `--concurrency` / `SAP_CONCURRENCY` | `5` | Upper bound on simultaneous SAP requests where several are unavoidable |
118
+ | `--allow-write` / `SAP_MCP_ALLOW_WRITE` | _(unset = read-only)_ | **Since 0.2.2 the server is read-only by default.** `sap_create`, `sap_update`, `sap_delete`, `sap_function` and `sap_idoc_send` are not registered at all until you set this — a tool an agent cannot see is a tool it cannot call by accident. `--read-only` is still accepted and now simply describes the default |
119
+ | `SAP_JWT_ISSUER` | _(unset = no verification)_ | Issuer whose JWKS is used to verify incoming user tokens. Without it tokens are only decoded, and the resulting identity is marked `verified: false` — safe to forward to SAP (SAP verifies itself), but not safe to base a local decision on. With it, a token failing verification is rejected with `401` rather than silently downgraded to the technical user |
120
+ | `SAP_WEBHOOK_SECRET` / `--webhook-secret` | _(unset = open + startup warning)_ | Shared secret for the inbound IDoc webhook. Accepts `Authorization: Bearer <secret>` or HTTP Basic auth whose **password** equals the secret (matches SAP SM59 HTTP destinations) |
121
+ | `SAP_WEBHOOK_HOST` / `--webhook-host` | all interfaces | Bind address of the standalone IDoc webhook (stdio mode). Set `127.0.0.1` behind a reverse proxy |
122
+ | `SAP_WEBHOOK_MAX_BODY_BYTES` | `10485760` (10 MB) | Reject larger IDoc payloads with `413` before they are buffered in memory |
123
+
124
+ In HTTP transport mode the `/idoc` route is mounted on the MCP port: with `--webhook-secret` it authenticates independently of `--api-key` (so SAP's Basic auth works while n8n uses the Bearer key); without it, it falls under `--api-key` if set — and is **open** if neither is configured (the server logs a warning at startup).
125
+
126
+ Both timeouts must stay **below the smallest client limit** in your setup. Measured against a production S/4HANA behind Cloudflare: Cloudflare aborts at 100 s (`524`), n8n's MCP client transport at 300 s. A budget above the smallest limit never takes effect, because the client gives up first.
127
+
128
+ #### Tracing a slow tool call
129
+
130
+ Every tool call logs one line on completion, carrying a correlation id that also appears on each SAP request it triggered:
131
+
132
+ ```json
133
+ {"correlationId":"a3f19c8b2d04","tool":"sap_discover_services","outcome":"ok","durationMs":842,"sapCalls":1,"resultBytes":18422}
102
134
  ```
103
135
 
136
+ `grep a3f19c8b2d04` then shows the full story of that one call — including how many SAP requests it produced. `outcome` is one of `ok`, `error`, `timeout` (budget expired) or `aborted` (client disconnected).
137
+
138
+ **`level: 50` means something is actually wrong.** Expected non-2xx responses — the `404`s from the catalog path probing and from the V4 catalog attempt on systems that have none — are logged at `info` with `"expected": true`, not at `error`. Measured on a production system: over 96 hours of runtime that false alarm was the *only* error-level entry in the log. Alerting on `level: 50` is therefore meaningful; before, it produced nothing but noise.
139
+
140
+ #### When something breaks
141
+
142
+ `test-connection` walks the whole chain and reports the **first** failing stage — DNS, TLS (including the certificate chain), authentication, client (Mandant), catalog. Stages after the failure are reported as `skipped`, never as passed. The same five stages are logged once at startup, so the container log shows the state of the connection without calling anything.
143
+
144
+ Every error response carries a classification, the SAP error code where one exists, a plain-language message and the next action — no tool answers with `"Unknown error"`. Three cases that used to cost a separate diagnosis each:
145
+
146
+ | What SAP returns | What the server tells you |
147
+ |---|---|
148
+ | `403` with `/IWFND/MED/170` | The service is not registered — activate it in `/IWFND/MAINT_SERVICE`. **Not** an authorization problem, despite the 403 |
149
+ | `401` with an HTML logon page | Is `sap-client` set? Without it a client-dependent Gateway rejects the logon in a way that looks like a wrong password |
150
+ | `UNABLE_TO_GET_ISSUER_CERT_LOCALLY` | The certificate chain is incomplete; supply the root CA via `SAP_CA_CERT`. The message names the issuing CA |
151
+
152
+ If `SAP_TLS_VERIFY=false` is in effect, **every tool result** carries a warning about it — not just the startup log. A disabled check that nobody sees after the first day tends to stay for years; on the production system that prompted this work it was set, and nobody noticed.
153
+
154
+ #### Streamable HTTP is stateless
155
+
156
+ `--transport http` serves MCP in **stateless** mode: every request is self-contained and the server neither issues nor expects an `mcp-session-id` header. There is no server-side session to resume, so a client may reconnect at any time without losing state.
157
+
158
+ Clients that *require* a session id — they read `mcp-session-id` from the initialize response and send it back on subsequent requests — will fail against this server. The n8n MCP Client, Claude Desktop, and the MCP Inspector all work without it.
159
+
160
+ The legacy `--transport sse` mode is different: it keeps a per-connection session and expects a `?sessionId=` query parameter on `/messages`. Prefer `http` for new setups.
161
+
104
162
  ### 4. Use with n8n
105
163
 
106
164
  1. Add an **AI Agent** node with an **MCP Client Tool** sub-node
@@ -165,16 +223,16 @@ In addition to the variables documented in [Quick Start](#quick-start):
165
223
  ### Core Tier (always active)
166
224
  | Tool | Description |
167
225
  |------|-------------|
168
- | `test-connection` | Test connectivity to SAP (pings catalog service) |
169
- | `sap_list_services` | List entity sets in an OData service |
226
+ | `test-connection` | Stage-by-stage connection diagnosis (DNS → TLS → auth → client → catalog); names the first failing stage |
227
+ | `sap_list_services` | List entity sets in an OData service (paged, default 50) |
170
228
  | `sap_discover_services` | Discover all services with category filtering and search |
171
- | `sap_get_metadata` | Inspect entity types, properties, keys (with search filter) |
229
+ | `sap_get_metadata` | Inspect entity types, properties, keys, and what SAP allows per entity set (creatable/updatable/deletable) |
172
230
 
173
231
  ### OData Tier (active by default)
174
232
  | Tool | Description |
175
233
  |------|-------------|
176
234
  | `sap_read` | Read a single entity or collection |
177
- | `sap_query` | Query with OData $filter, $select, $expand, pagination |
235
+ | `sap_query` | Query with OData $filter, $select, $expand, paging (default `top` 50) |
178
236
  | `sap_nl_query` | Natural language query with structured filters |
179
237
  | `sap_create` | Create entities (supports deep insert + draft services) |
180
238
  | `sap_update` | Update entities (automatic ETag handling) |
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import {existsSync,readFileSync}from'fs';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import In from'pino';import {minimatch}from'minimatch';import {z as z$1}from'zod/v4';import {z as z$2}from'zod';import {createServer}from'http';import {XMLParser,XMLBuilder}from'fast-xml-parser';import {McpServer,ResourceTemplate}from'@modelcontextprotocol/sdk/server/mcp.js';import Ie,{isAxiosError}from'axios';import {randomUUID,createHash}from'crypto';import {decodeJwt,createRemoteJWKSet,jwtVerify}from'jose';import {SignedXml}from'xml-crypto';import {Agent}from'https';import {createMcpExpressApp}from'@modelcontextprotocol/sdk/server/express.js';import {SSEServerTransport}from'@modelcontextprotocol/sdk/server/sse.js';import {StreamableHTTPServerTransport}from'@modelcontextprotocol/sdk/server/streamableHttp.js';function G(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 Ve(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 en(t,e)}function en(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("--transport");if(r!==-1&&r<t.length-1){let E=t[r+1];(E==="stdio"||E==="http"||E==="sse")&&(e.transport=E);}let s=t.indexOf("--port");s!==-1&&s<t.length-1&&(e.port=Number.parseInt(t[s+1],10));let i=t.indexOf("--api-key");i!==-1&&i<t.length-1&&(e.apiKey=t[i+1]);let o=["sndpor","sndprt","sndprn","rcvpor","rcvprt","rcvprn"];for(let E of o){let D=t.indexOf(`--${E}`);D!==-1&&D<t.length-1&&(e[E]=t[D+1]);}let a=t.indexOf("--catalog-v2-url");a!==-1&&a<t.length-1&&(e.catalogV2Url=t[a+1]),!e.catalogV2Url&&process.env.ODATA_V2_CATALOG_PATH&&(e.catalogV2Url=process.env.ODATA_V2_CATALOG_PATH);let c=t.indexOf("--catalog-v4-url");c!==-1&&c<t.length-1&&(e.catalogV4Url=t[c+1]),t.indexOf("--read-only")!==-1&&(e.readOnly=true),process.env.SAP_MCP_READ_ONLY==="true"&&(e.readOnly=true);let d=t.indexOf("--help"),l=t.indexOf("-h");(d!==-1||l!==-1)&&(e.help=true);let u=t.indexOf("--tiers");if(u!==-1&&u<t.length-1){let E=t[u+1].split(",").map(D=>D.trim()).filter(D=>D.length>0);e.tiers=E.length>0?E:void 0;}let m=t.indexOf("--destination-name");m!==-1&&m<t.length-1&&(e.destinationName=t[m+1]),!e.destinationName&&process.env.SAP_DESTINATION_NAME&&(e.destinationName=process.env.SAP_DESTINATION_NAME);let g=t.indexOf("--destination-service-url");g!==-1&&g<t.length-1&&(e.destinationServiceUrl=t[g+1]),!e.destinationServiceUrl&&process.env.SAP_DESTINATION_SERVICE_URL&&(e.destinationServiceUrl=process.env.SAP_DESTINATION_SERVICE_URL);let f=t.indexOf("--issuer");f!==-1&&f<t.length-1&&(e.issuer=t[f+1]),!e.issuer&&process.env.SAP_ISSUER&&(e.issuer=process.env.SAP_ISSUER);let h=t.indexOf("--name-id-format");h!==-1&&h<t.length-1&&(e.nameIdFormat=t[h+1]),!e.nameIdFormat&&process.env.SAP_NAME_ID_FORMAT&&(e.nameIdFormat=process.env.SAP_NAME_ID_FORMAT);let R=t.indexOf("--private-key"),S=R!==-1&&R<t.length-1?t[R+1]:void 0;e.privateKey=G(S,"SAP_PRIVATE_KEY","SAP_PRIVATE_KEY_FILE");let T=t.indexOf("--certificate"),O=T!==-1&&T<t.length-1?t[T+1]:void 0;e.certificate=G(O,"SAP_CERTIFICATE","SAP_CERTIFICATE_FILE");let W=t.indexOf("--clock-skew");W!==-1&&W<t.length-1&&(e.clockSkew=Number.parseInt(t[W+1],10)),e.clockSkew===void 0&&process.env.SAP_CLOCK_SKEW&&(e.clockSkew=Number.parseInt(process.env.SAP_CLOCK_SKEW,10));let j=t.indexOf("--assertion-lifetime");return j!==-1&&j<t.length-1&&(e.assertionLifetime=Number.parseInt(t[j+1],10)),e.assertionLifetime===void 0&&process.env.SAP_ASSERTION_LIFETIME&&(e.assertionLifetime=Number.parseInt(process.env.SAP_ASSERTION_LIFETIME,10)),e}function Be(t){return !!(t.sndprn&&t.rcvprn)}function M(t,e){return !e||e.length===0?true:e.some(n=>minimatch(t,n))}var qe=z$1.object({tlsVerify:z$1.boolean().default(true),clientCert:z$1.string().optional(),clientKey:z$1.string().optional(),clientKeyPassphrase: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 Ce(t=process.env){let e=t.SAP_TLS_VERIFY!=="false",n=G(void 0,"SAP_CLIENT_CERT","SAP_CLIENT_CERT_PATH",t),r=G(void 0,"SAP_CLIENT_KEY","SAP_CLIENT_KEY_PATH",t),s=t.SAP_CLIENT_KEY_PASSPHRASE,i={tlsVerify:e,...n!==void 0?{clientCert:n}:{},...r!==void 0?{clientKey:r}:{},...s!==void 0?{clientKeyPassphrase:s}:{}},o=qe.safeParse(i);if(!o.success){let a=o.error.issues.map(c=>` - ${c.path.join(".")||"root"}: ${c.message}`).join(`
2
+ import {readFileSync,existsSync,mkdirSync,writeFileSync,renameSync,unlinkSync}from'fs';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import Hr from'pino';import {minimatch}from'minimatch';import {z as z$1}from'zod/v4';import {z as z$2}from'zod';import {lookup}from'dns/promises';import {connect}from'tls';import at,{isAxiosError}from'axios';import {AsyncLocalStorage}from'async_hooks';import {randomUUID,createHash,timingSafeEqual,randomBytes}from'crypto';import {jwtVerify,decodeJwt,createRemoteJWKSet}from'jose';import {SignedXml}from'xml-crypto';import {Agent}from'https';import {createServer}from'http';import {XMLParser,XMLBuilder}from'fast-xml-parser';import {McpServer,ResourceTemplate}from'@modelcontextprotocol/sdk/server/mcp.js';import {resolve,join,dirname}from'path';import {createMcpExpressApp}from'@modelcontextprotocol/sdk/server/express.js';import {SSEServerTransport}from'@modelcontextprotocol/sdk/server/sse.js';import {StreamableHTTPServerTransport}from'@modelcontextprotocol/sdk/server/streamableHttp.js';function ce(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 _t(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 tr(t,e)}function tr(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 _=Number.parseInt(process.env.SAP_WEBHOOK_MAX_BODY_BYTES,10);Number.isFinite(_)&&_>0&&(e.webhookMaxBodyBytes=_);}let i=t.indexOf("--transport");if(i!==-1&&i<t.length-1){let _=t[i+1];(_==="stdio"||_==="http"||_==="sse")&&(e.transport=_);}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 _ of c){let j=t.indexOf(`--${_}`);j!==-1&&j<t.length-1&&(e[_]=t[j+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 p=t.indexOf("--catalog-v4-url");p!==-1&&p<t.length-1&&(e.catalogV4Url=t[p+1]);let d=t.includes("--allow-write")||process.env.SAP_MCP_ALLOW_WRITE==="true";e.readOnly=!d,e.allowWriteExplicit=d;let m=t.indexOf("--tool-timeout"),u=m!==-1&&m<t.length-1?t[m+1]:process.env.SAP_MCP_TOOL_TIMEOUT;if(u!==void 0){let _=Number.parseInt(u,10);if(!Number.isFinite(_)||_<0)throw new Error(`--tool-timeout muss eine Zahl in Sekunden sein (0 schaltet das Budget ab), war: "${u}"`);e.toolTimeoutSeconds=_;}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"),T=h!==-1&&h<t.length-1?t[h+1]:process.env.SAP_CONCURRENCY;if(T!==void 0){let _=Number.parseInt(T,10);if(!Number.isFinite(_)||_<1)throw new Error(`--concurrency muss eine Zahl >= 1 sein, war: "${T}"`);e.concurrency=_;}let y=t.indexOf("--help"),v=t.indexOf("-h");(y!==-1||v!==-1)&&(e.help=true);let P=t.indexOf("--tiers");if(P!==-1&&P<t.length-1){let _=t[P+1].split(",").map(j=>j.trim()).filter(j=>j.length>0);e.tiers=_.length>0?_:void 0;}let D=t.indexOf("--destination-name");D!==-1&&D<t.length-1&&(e.destinationName=t[D+1]),!e.destinationName&&process.env.SAP_DESTINATION_NAME&&(e.destinationName=process.env.SAP_DESTINATION_NAME);let L=t.indexOf("--destination-service-url");L!==-1&&L<t.length-1&&(e.destinationServiceUrl=t[L+1]),!e.destinationServiceUrl&&process.env.SAP_DESTINATION_SERVICE_URL&&(e.destinationServiceUrl=process.env.SAP_DESTINATION_SERVICE_URL);let Y=t.indexOf("--issuer");Y!==-1&&Y<t.length-1&&(e.issuer=t[Y+1]),!e.issuer&&process.env.SAP_ISSUER&&(e.issuer=process.env.SAP_ISSUER);let Xe=t.indexOf("--name-id-format");Xe!==-1&&Xe<t.length-1&&(e.nameIdFormat=t[Xe+1]),!e.nameIdFormat&&process.env.SAP_NAME_ID_FORMAT&&(e.nameIdFormat=process.env.SAP_NAME_ID_FORMAT);let Qe=t.indexOf("--private-key"),Xn=Qe!==-1&&Qe<t.length-1?t[Qe+1]:void 0;e.privateKey=ce(Xn,"SAP_PRIVATE_KEY","SAP_PRIVATE_KEY_FILE");let Ze=t.indexOf("--certificate"),Qn=Ze!==-1&&Ze<t.length-1?t[Ze+1]:void 0;e.certificate=ce(Qn,"SAP_CERTIFICATE","SAP_CERTIFICATE_FILE");let et=t.indexOf("--clock-skew");et!==-1&&et<t.length-1&&(e.clockSkew=Number.parseInt(t[et+1],10)),e.clockSkew===void 0&&process.env.SAP_CLOCK_SKEW&&(e.clockSkew=Number.parseInt(process.env.SAP_CLOCK_SKEW,10));let tt=t.indexOf("--assertion-lifetime");return tt!==-1&&tt<t.length-1&&(e.assertionLifetime=Number.parseInt(t[tt+1],10)),e.assertionLifetime===void 0&&process.env.SAP_ASSERTION_LIFETIME&&(e.assertionLifetime=Number.parseInt(process.env.SAP_ASSERTION_LIFETIME,10)),e}function Ct(t){return !!(t.sndprn&&t.rcvprn)}function V(t,e){return !e||e.length===0?true:e.some(n=>minimatch(t,n))}var Et=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 nt(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=ce(void 0,"SAP_CLIENT_CERT","SAP_CLIENT_CERT_PATH",t),s=ce(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=Et.safeParse(a);if(!c.success){let l=c.error.issues.map(p=>` - ${p.path.join(".")||"root"}: ${p.message}`).join(`
3
3
  `);throw new Error(`Invalid HTTP options:
4
- ${a}`)}return o.data}var U=z$2.object({name:z$2.string().default("default"),baseUrl:z$2.url(),sapClient:z$2.string().optional()}),tn=U.extend({authType:z$2.literal("basic"),username:z$2.string().min(1),password:z$2.string().min(1)}),nn=U.extend({authType:z$2.literal("oauth2"),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1),tokenServiceUrl:z$2.url()}),rn=U.extend({authType:z$2.literal("apikey"),apiKey:z$2.string().min(1)}),sn=U.extend({authType:z$2.literal("ias"),tenantUrl:z$2.url(),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1)}),on=U.extend({authType:z$2.literal("xsuaa"),xsuaaUrl:z$2.url(),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1)}),an=U.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()}),cn=U.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)}),Ae=z$2.discriminatedUnion("authType",[tn,nn,rn,sn,on,an,cn]);function We(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=Ae.safeParse(n);if(!r.success){let s=r.error.issues.map(i=>` - ${i.path.join(".")||"root"}: ${i.message}`).join(`
4
+ ${l}`)}return c.data}var W=z$2.object({name:z$2.string().default("default"),baseUrl:z$2.url(),sapClient:z$2.string().optional()}),nr=W.extend({authType:z$2.literal("basic"),username:z$2.string().min(1),password:z$2.string().min(1)}),rr=W.extend({authType:z$2.literal("oauth2"),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1),tokenServiceUrl:z$2.url()}),sr=W.extend({authType:z$2.literal("apikey"),apiKey:z$2.string().min(1)}),ir=W.extend({authType:z$2.literal("ias"),tenantUrl:z$2.url(),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1)}),or=W.extend({authType:z$2.literal("xsuaa"),xsuaaUrl:z$2.url(),clientId:z$2.string().min(1),clientSecret:z$2.string().min(1)}),ar=W.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()}),cr=W.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)}),rt=z$2.discriminatedUnion("authType",[nr,rr,sr,ir,or,ar,cr]);function Rt(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=rt.safeParse(n);if(!r.success){let s=r.error.issues.map(i=>` - ${i.path.join(".")||"root"}: ${i.message}`).join(`
5
5
  `);throw new Error(`Invalid SAP connection configuration:
6
6
  ${s}
7
7
 
8
- Set the required SAP_* environment variables.`)}return r.data}var F=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 ln=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",parseTagValue:false,isArray:t=>t==="IDOC"||t.startsWith("E1")||t.startsWith("E2")}),Y=new Set(["@_SEGMENT","@_BEGIN"]);function Pe(t,e){return !!(t.startsWith("E1")||t.startsWith("E2")||typeof e=="object"&&e!==null&&"@_SEGMENT"in e)}function Ge(t){let e=[];for(let[n,r]of Object.entries(t)){if(Y.has(n)||!Pe(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,p]of Object.entries(i))if(!Y.has(c))if(Pe(c,p)){let d=Array.isArray(p)?p:[p];for(let l of d){if(typeof l!="object"||l===null)continue;let u={},m=Ge(l);for(let[g,f]of Object.entries(l))Y.has(g)||!Pe(g,f)&&typeof f!="object"&&(u[g]=String(f));a.push({name:c,fields:u,...m.length>0?{children:m}:{}});}}else typeof p!="object"&&(o[c]=String(p));e.push({name:n,fields:o,...a.length>0?{children:a}:{}});}}return e}function Ke(t){let e=ln.parse(t),n=Object.keys(e).find(l=>l!=="?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 l=Array.isArray(a)?a[0]:a;for(let[u,m]of Object.entries(l))!Y.has(u)&&typeof m!="object"&&(c[u]=String(m));}let p={};for(let[l,u]of Object.entries(o))l==="EDI_DC40"||Y.has(l)||(p[l]=u);let d=Ge(p);return {idocType:r,controlRecord:c,segments:d}}function dn(t){return new Promise((e,n)=>{let r="";t.on("data",s=>{r+=s.toString();}),t.on("end",()=>e(r)),t.on("error",n);})}function ae(t){return (e,n)=>{if(e.method!=="POST"){n.writeHead(405,{"Content-Type":"text/plain"}),n.end("Method Not Allowed");return}dn(e).then(r=>{let{idocType:s,controlRecord:i,segments:o}=Ke(r),a={docnum:i.DOCNUM||`UNKNOWN_${Date.now()}`,idocType:s,mesType:i.MESTYP||"",receivedAt:new Date,rawXml:r,parsed:{controlRecord:i,segments:o}};t.add(a),n.writeHead(200,{"Content-Type":"text/plain"}),n.end("OK");}).catch(r=>{n.writeHead(400,{"Content-Type":"text/plain"}),n.end(`Parse error: ${r instanceof Error?r.message:String(r)}`);});}}function Ye(t,e){let n=ae(e);return new Promise((r,s)=>{let i=createServer((o,a)=>{o.method==="POST"&&o.url==="/idoc"?n(o,a):(a.writeHead(404,{"Content-Type":"text/plain"}),a.end("Not Found"));});i.listen(t,()=>{let o=i.address(),a=typeof o=="object"&&o!==null?o.port:t;r({port:a,close:()=>new Promise((c,p)=>{i.close(d=>d?p(d):c());})});}),i.on("error",s);})}function Je(t){let e={name:t.name,url:t.baseUrl,sapClient:t.sapClient};switch(t.authType){case "basic":return {...e,auth:{type:"basic",username:t.username,password:t.password}};case "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 $=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 Ie.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 be(t){return createHash("sha256").update(t).digest("hex")}var J=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});}},ce=class{clientTokenManager;cache;config;constructor(e){this.config=e,this.clientTokenManager=new $(e.tokenServiceUrl,e.clientId,e.clientSecret),this.cache=new J;}async getToken(e){if(!e)return this.clientTokenManager.getToken();let n=be(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 Ie.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),p=Number.isFinite(c)&&c>0?c:3600,d=Date.now()+(p-60)*1e3;return this.cache.set(n,a.value,d),a.value}};var z=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 Ie.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 Q=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 Ie.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 An={email:"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",sapUsername:"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"};function ze(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 Pn(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 bn(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 pe=class{cache;config;constructor(e){ze(e.privateKey,"private key"),ze(e.certificate,"certificate"),this.config={...e,clockSkew:e.clockSkew??120,assertionLifetime:e.assertionLifetime??300},this.cache=new J;}async getToken(e){if(!e)throw new Error("saml-bearer auth requires a user token for principal propagation");let n=be(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=An[this.config.nameIdFormat],a=new Date(s-this.config.clockSkew*1e3).toISOString(),c=new Date(s+this.config.assertionLifetime*1e3).toISOString(),p=new Date(s).toISOString(),d=Pn({assertionId:`_${randomUUID()}`,issuer:this.config.issuer,nameId:i,nameIdFormat:o,recipient:this.config.tokenServiceUrl,audience:this.config.clientId,notBefore:a,notOnOrAfter:c,authnInstant:p}),l=bn(d,this.config.privateKey,this.config.certificate),u=Buffer.from(l).toString("base64"),m=await Ie.post(this.config.tokenServiceUrl,new URLSearchParams({grant_type:"urn:ietf:params:oauth:grant-type:saml2-bearer",assertion:u,client_id:this.config.clientId}).toString(),{headers:{"Content-Type":"application/x-www-form-urlencoded"}}),{access_token:g,expires_in:f}=m.data,h=typeof f=="number"&&f>0?f:3600,R=Date.now()+(h-60)*1e3;return this.cache.set(n,g,R),g}};function Ee(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 Qe(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 X=In({name:"sap-http-client",transport:{target:"pino/file",options:{destination:2}}}),le=class{constructor(e,n={}){this.destination=e;switch(this.httpsAgent=Qe(n),e.auth.type){case "oauth2":this.tokenManager=new $(e.auth.tokenServiceUrl,e.auth.clientId,e.auth.clientSecret);break;case "ias":this.tokenManager=new z({issuerUrl:e.auth.tenantUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new Q);break;case "xsuaa":this.tokenManager=new z({issuerUrl:e.auth.xsuaaUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new Q);break;case "btp-principal":this.tokenManager=new ce({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 pe({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;async request(e){let n=e.method!=="get",r={...e.params};this.destination.sapClient&&(r["sap-client"]=this.destination.sapClient);let s=e.userContext??this.userContext??void 0,i=await this.buildAuthHeaders(s),o=this.destination.headers??{},a={};if(n){let l=await this.fetchCsrfToken({...o,...i},e.url);l.token&&(a={"x-csrf-token":l.token},l.cookies&&(a.Cookie=l.cookies));}let c={...o,...i,...a,...e.headers},p={method:e.method,url:`${this.destination.url}${e.url}`,data:e.data,headers:c,params:Object.keys(r).length>0?r:void 0,httpsAgent:this.httpsAgent,beforeRedirect:l=>{this.httpsAgent&&(l.agent=this.httpsAgent);let u=l.headers??{};for(let[m,g]of Object.entries(c))u[m]||(u[m]=g);}},d={...p.headers};d.Authorization&&(d.Authorization=`${d.Authorization.substring(0,15)}***`),X.info({method:p.method,url:p.url,headers:d,params:p.params,hasAuth:!!p.headers.Authorization||!!p.headers.APIKey},"SAP HTTP request outgoing");try{let l=await Ie(p);if(X.info({status:l.status,contentType:l.headers["content-type"]},"SAP HTTP response OK"),typeof l.data=="string"&&l.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:l.data,headers:l.headers,status:l.status}}catch(l){if(isAxiosError(l)&&l.response?X.error({status:l.response.status,statusText:l.response.statusText,contentType:l.response.headers?.["content-type"],url:p.url,dataPreview:typeof l.response.data=="string"?l.response.data.substring(0,200):typeof l.response.data},"SAP HTTP response ERROR"):l instanceof Error&&X.error({message:l.message,name:l.name},"SAP HTTP connection error"),n&&this.isCsrf403(l))try{let u=await this.fetchCsrfToken({...o,...i},e.url),m={...p.headers};u.token&&(m["x-csrf-token"]=u.token,u.cookies&&(m.Cookie=u.cookies));let g=await Ie({...p,headers:m});return {data:g.data,headers:g.headers,status:g.status}}catch(u){throw this.toSapError(u)}throw this.toSapError(l)}}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 {}}}async fetchCsrfToken(e,n){let r=this.destination.url,s=[];if(n){let i=n.replace(/\/[^/]*$/,"/");i!=="/"&&i!==n&&s.push(`${r}${i}`);}s.push(`${r}/`,r);for(let i of s)try{let o={...e,"x-csrf-token":"fetch"},a={};this.destination.sapClient&&(a["sap-client"]=this.destination.sapClient);let c=await Ie.get(i,{headers:o,params:Object.keys(a).length>0?a:void 0,validateStatus:()=>!0,httpsAgent:this.httpsAgent,beforeRedirect:d=>{this.httpsAgent&&(d.agent=this.httpsAgent);let l=d.headers??{};for(let[u,m]of Object.entries(o))l[u]||(l[u]=m);}}),p=c.headers["x-csrf-token"];if(X.debug({url:i,status:c.status,hasToken:!!p,tokenValue:p?.substring(0,8)},"CSRF token fetch result"),p&&p.toLowerCase()!=="required"){let l=(c.headers["set-cookie"]??[]).map(u=>u.split(";")[0]).join("; ");return {token:p,cookies:l}}}catch{}return {token:"",cookies:""}}isCsrf403(e){return isAxiosError(e)&&e.response?.status===403}toSapError(e){return isAxiosError(e)&&e.response?Ee(e.response.data):e instanceof Error?{code:"CONNECTION_ERROR",message:e.message,details:[],rawResponse:void 0}:Ee(e)}};var Oe=0;function Xe(t){let e=t.docnum??`${Date.now().toString().padStart(13,"0")}${String(Oe++).padStart(3,"0")}`;Oe>=1e3&&(Oe=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 wn(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 On(t,e){return t.filter(n=>n.name===e).length}function Ze(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=On(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=wn(s.name,o,a);c&&n.push(c);}s.children&&s.children.length>0&&Ze(s.children,e,n);}}function et(t,e){if(!e)return [];let n=new Map;for(let s of e.segments)n.set(s.name,s);let r=[];return Ze(t,n,r),r}var xn=new XMLBuilder({ignoreAttributes:false,attributeNamePrefix:"@_",format:true,suppressEmptyNode:false});function tt(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=tt(n),s=e[n.name];s?Array.isArray(s)?s.push(r):e[n.name]=[s,r]:e[n.name]=r;}return e}function nt(t,e,n){let r={};for(let i of n){let o=tt(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 xn.build(s)}var de=class{constructor(e,n,r){this.httpClient=e;this.sapClient=n;this.partnerDefaults=r;}async send(e){let n={...this.partnerDefaults,...e.partnerOverrides},r=Xe({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 l=et(e.segments,e.metadata);if(l.length>0)return {success:false,docnum:r.DOCNUM,message:"Validation failed",violations:l}}let s=nt(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}}};function rt(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?"":`
8
+ Set the required SAP_* environment variables.`)}return r.data}function Ee(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 "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 J=new AsyncLocalStorage;function wt(){return randomBytes(6).toString("hex")}function X(){return J.getStore()}function Pt(t,e){return J.run(t,e)}function Re(){let t=J.getStore();if(t)return t.sapCalls.count+=1,t.correlationId}function It(t=Date.now()){let e=J.getStore();if(e?.deadline)return Math.max(0,e.deadline-t)}function we(){return J.getStore()?.signal?.aborted??false}var k=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 Pe(){let t=J.getStore();if(!t?.signal?.aborted)return;let e=t.signal.reason?.name==="TimeoutError";throw new k(e,t.toolName)}function Ot(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 Q(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 st(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 it(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 ur(t){return /\/IW[A-Z]+\/[A-Z_]+\/\d+/i.exec(t)?.[0]}var mr=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 gr(t){let e=t?.code;if(typeof e=="string")return e;let n=t?.cause;if(typeof n?.code=="string")return n.code}function fr(t){let n=(t?.cause?.cert??t?.cert)?.issuer?.CN;return typeof n=="string"?n:void 0}function Z(t,e){if(t instanceof k)return {code:t.timedOut?"TOOL_TIMEOUT":"ABORTED",message:t.message};let n=gr(t);if(n&&mr.has(n)){let i=fr(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 hr(t.response.status,t.response.data,e);let r=t;if(typeof r?.message=="string"&&r.message.length>0)return st(r.message)?{code:"HTML_RESPONSE",message:`SAP hat eine HTML-Seite statt Daten geliefert: ${it(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 st(t.message)?{code:"HTML_RESPONSE",message:`SAP hat eine HTML-Seite statt Daten geliefert: ${it(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 hr(t,e,n){let r=typeof e=="string"?e:JSON.stringify(e??""),s=ur(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:yr(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(st(e))return {code:"HTML_RESPONSE",httpStatus:t,message:`SAP hat eine HTML-Fehlerseite geliefert (HTTP ${t}): ${it(e)}`,guidance:"Der Pfad wird von SAP nicht als OData-Service beantwortet. Bei Katalogpfaden sap_discover_services statt sap_query verwenden."};let i=Q(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 yr(t){let e=Q(t);if(e.message&&!(e.message==="Unknown error"||e.message==="Unknown SAP error"))return e.message}var br=5e3;function kt(t){let e=t?.issuer?.CN??t?.issuer?.O;return Array.isArray(e)?e.join(", ")||void 0:typeof e=="string"?e:void 0}function C(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 vr(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 Ar(t,e){let n=Date.now();try{let{address:r,family:s}=await e(t);return C("dns",n,"ok",`${t} l\xF6st auf ${r} auf.`,{info:{address:r,family:s}})}catch(r){let s=Z(r);return C("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 _r(t,e,n,r,s){let i=Date.now(),o=a=>new Promise((c,l)=>{let p=s({host:t,port:e,rejectUnauthorized:a,servername:t},()=>{let d=p.getPeerCertificate(true);p.destroy(),c(d);});p.setTimeout(r,()=>{p.destroy(),l(Object.assign(new Error(`TLS-Handshake nach ${r} ms ohne Antwort`),{code:"ETIMEDOUT"}));}),p.on("error",d=>{p.destroy(),l(d);});});try{let a=await o(n),c=kt(a);return C("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=Z(a);if(c.code==="TLS_CHAIN_INCOMPLETE"){let l;try{let p=await o(!1);l=kt(p);}catch{}return C("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 C("tls",i,"failed",c.message,{guidance:c.guidance})}}async function Cr(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 [C("auth",n,"ok","Anmeldung am Gateway erfolgreich."),C("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}}),C("catalog",n,"ok",`Servicekatalog erreichbar (HTTP ${r.status}).`)]}catch(r){let s=Z(r);return s.httpStatus===401&&!e?[C("auth",n,"ok","Zugangsdaten wurden gesendet."),C("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."}),C("catalog",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor.")]:s.httpStatus===401?[C("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.`}),C("client",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor."),C("catalog",n,"skipped","Nicht gepr\xFCft \u2014 die Anmeldung scheiterte zuvor.")]:[C("auth",n,"ok","Anmeldung am Gateway erfolgreich."),C("client",n,"ok",e?`Mandant ${e} angenommen.`:"Kein Mandant konfiguriert."),C("catalog",n,"failed",s.message,{guidance:s.guidance??"Katalogdienst in /IWFND/MAINT_SERVICE pr\xFCfen. Der Server probiert drei bekannte Katalogpfade durch."})]}}async function Oe(t,e){let n=Date.now(),r=e.budgetMs??br,s=e.deps?.lookup??lookup,i=e.deps?.connectTls??connect,o=[],a=vr(e.baseUrl);if(!a)return o.push(C("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(c,n,"skipped","Nicht gepr\xFCft \u2014 die Basis-URL ist unbrauchbar."))),Ie(o,n);if(o.push(await Ar(a.host,s)),o[0].status==="failed")return o.push(...["tls","auth","client","catalog"].map(c=>C(c,n,"skipped","Nicht gepr\xFCft \u2014 der Name war nicht aufl\xF6sbar."))),Ie(o,n);if(a.isTls){let c=Math.max(500,r-(Date.now()-n));if(o.push(await _r(a.host,a.port,e.tlsVerify!==false,c,i)),o[o.length-1].status==="failed")return o.push(...["auth","client","catalog"].map(l=>C(l,n,"skipped","Nicht gepr\xFCft \u2014 die TLS-Stufe scheiterte zuvor."))),Ie(o,n)}else o.push(C("tls",n,"skipped","Nicht gepr\xFCft \u2014 SAP_BASE_URL verwendet http, nicht https."));return o.push(...await Cr(t,e.sapClient)),Ie(o,n)}function Ie(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 ee=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 at.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 ot(t){return createHash("sha256").update(t).digest("hex")}var le=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});}},ke=class{clientTokenManager;cache;config;constructor(e){this.config=e,this.clientTokenManager=new ee(e.tokenServiceUrl,e.clientId,e.clientSecret),this.cache=new le;}async getToken(e){if(!e)return this.clientTokenManager.getToken();let n=ot(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 at.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,p=Date.now()+(l-60)*1e3;return this.cache.set(n,a.value,p),a.value}};var de=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 at.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 G=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 at.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 Ur={email:"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",sapUsername:"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"};function Nt(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 Lr(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 $r(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 Ne=class{cache;config;constructor(e){Nt(e.privateKey,"private key"),Nt(e.certificate,"certificate"),this.config={...e,clockSkew:e.clockSkew??120,assertionLifetime:e.assertionLifetime??300},this.cache=new le;}async getToken(e){if(!e)throw new Error("saml-bearer auth requires a user token for principal propagation");let n=ot(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=Ur[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(),p=Lr({assertionId:`_${randomUUID()}`,issuer:this.config.issuer,nameId:i,nameIdFormat:o,recipient:this.config.tokenServiceUrl,audience:this.config.clientId,notBefore:a,notOnOrAfter:c,authnInstant:l}),d=$r(p,this.config.privateKey,this.config.certificate),m=Buffer.from(d).toString("base64"),u=await at.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}=u.data,h=typeof g=="number"&&g>0?g:3600,T=Date.now()+(h-60)*1e3;return this.cache.set(n,f,T),f}};function xt(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 Br=3e4,$=Hr({name:"sap-http-client",transport:{target:"pino/file",options:{destination:2}}});function jr(t){return t.startsWith("http://")||t.startsWith("https://")}function Vr(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 te=class{constructor(e,n={}){this.destination=e;switch(this.httpsAgent=xt(n),this.requestTimeoutMs=n.requestTimeoutMs??Br,e.auth.type){case "oauth2":this.tokenManager=new ee(e.auth.tokenServiceUrl,e.auth.clientId,e.auth.clientSecret);break;case "ias":this.tokenManager=new de({issuerUrl:e.auth.tenantUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new G);break;case "xsuaa":this.tokenManager=new de({issuerUrl:e.auth.xsuaaUrl,clientId:e.auth.clientId,clientSecret:e.auth.clientSecret},new G);break;case "btp-principal":this.tokenManager=new ke({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 Ne({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){Pe();let n=e.method!=="get",r=X(),s=Re(),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 u=await this.fetchCsrfToken({...c,...a},e.url);u.token&&(l={"x-csrf-token":u.token},u.cookies&&(l.Cookie=u.cookies));}let p={...c,...a,...l,...e.headers},d={method:e.method,url:this.resolveUrl(e.url),data:e.data,headers:p,params:Object.keys(i).length>0?i:void 0,httpsAgent:this.httpsAgent,timeout:this.effectiveTimeoutMs(),signal:r?.signal,beforeRedirect:u=>{this.httpsAgent&&(u.agent=this.httpsAgent);let f=u.headers??{};for(let[g,h]of Object.entries(p))f[g]||(f[g]=h);}},m={...d.headers};m.Authorization&&(m.Authorization=`${m.Authorization.substring(0,15)}***`),$.info({...s?{correlationId:s,tool:r?.toolName}:{},method:d.method,url:d.url,headers:m,params:d.params,hasAuth:!!d.headers.Authorization||!!d.headers.APIKey},"SAP HTTP request outgoing");try{let u=await at(d);if($.info({...s?{correlationId:s}:{},status:u.status,contentType:u.headers["content-type"]},"SAP HTTP response OK"),typeof u.data=="string"&&u.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:u.data,headers:u.headers,status:u.status}}catch(u){if(isAxiosError(u)&&u.response){let f=e.expectedStatuses?.includes(u.response.status)??false;(f?$.info.bind($):$.error.bind($))({...s?{correlationId:s}:{},status:u.response.status,statusText:u.response.statusText,contentType:u.response.headers?.["content-type"],url:d.url,...f?{expected:true}:{},dataPreview:typeof u.response.data=="string"?u.response.data.substring(0,200):typeof u.response.data},f?"SAP HTTP response (expected)":"SAP HTTP response ERROR");}else u instanceof Error&&$.error({message:u.message,name:u.name},"SAP HTTP connection error");if(n&&this.isCsrf403(u)){Pe();try{let f=await this.fetchCsrfToken({...c,...a},e.url),g={...d.headers};f.token&&(g["x-csrf-token"]=f.token,f.cookies&&(g.Cookie=f.cookies)),Re();let h=await at({...d,headers:g});return {data:h.data,headers:h.headers,status:h.status}}catch(f){throw this.toSapError(f)}}throw this.toSapError(u)}}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(jr(e))return e;let{origin:n,path:r}=Vr(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){Pe(),Re();try{let o={...e,"x-csrf-token":"fetch"},a={};this.destination.sapClient&&(a["sap-client"]=this.destination.sapClient);let c=await at.get(i,{headers:o,params:Object.keys(a).length>0?a:void 0,validateStatus:()=>!0,timeout:this.effectiveTimeoutMs(),signal:X()?.signal,httpsAgent:this.httpsAgent,beforeRedirect:p=>{this.httpsAgent&&(p.agent=this.httpsAgent);let d=p.headers??{};for(let[m,u]of Object.entries(o))d[m]||(d[m]=u);}}),l=c.headers["x-csrf-token"];if($.debug({url:i,status:c.status,hasToken:!!l,tokenValue:l?.substring(0,8)},"CSRF token fetch result"),l&&l.toLowerCase()!=="required"){let d=(c.headers["set-cookie"]??[]).map(m=>m.split(";")[0]).join("; ");return {token:l,cookies:d}}}catch{}}return {token:"",cookies:""}}isCsrf403(e){return isAxiosError(e)&&e.response?.status===403}effectiveTimeoutMs(){let e=It();return e===void 0?this.requestTimeoutMs:Math.max(1,Math.min(this.requestTimeoutMs,e))}toSapError(e){if(e instanceof k)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=X()?.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?Q(e.response.data):e instanceof Error?{code:"CONNECTION_ERROR",message:e.message,details:[],rawResponse:void 0}:Q(e)}};function Dt(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 Mt(t,e,n,r){try{let s=new te(Ee(e),n),i=await Oe(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.");}}var ne=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}};function ue(t,e){let n=createHash("sha256").update(t).digest(),r=createHash("sha256").update(e).digest();return timingSafeEqual(n,r)}var Gr=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",parseTagValue:false,processEntities:false,htmlEntities:false,isArray:t=>t==="IDOC"||t.startsWith("E1")||t.startsWith("E2")}),me=new Set(["@_SEGMENT","@_BEGIN"]);function ct(t,e){return !!(t.startsWith("E1")||t.startsWith("E2")||typeof e=="object"&&e!==null&&"@_SEGMENT"in e)}function Lt(t){let e=[];for(let[n,r]of Object.entries(t)){if(me.has(n)||!ct(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(!me.has(c))if(ct(c,l)){let p=Array.isArray(l)?l:[l];for(let d of p){if(typeof d!="object"||d===null)continue;let m={},u=Lt(d);for(let[f,g]of Object.entries(d))me.has(f)||!ct(f,g)&&typeof g!="object"&&(m[f]=String(g));a.push({name:c,fields:m,...u.length>0?{children:u}:{}});}}else typeof l!="object"&&(o[c]=String(l));e.push({name:n,fields:o,...a.length>0?{children:a}:{}});}}return e}function $t(t){let e=Gr.parse(t),n=Object.keys(e).find(d=>d!=="?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 d=Array.isArray(a)?a[0]:a;for(let[m,u]of Object.entries(d))!me.has(m)&&typeof u!="object"&&(c[m]=String(u));}let l={};for(let[d,m]of Object.entries(o))d==="EDI_DC40"||me.has(d)||(l[d]=m);let p=Lt(l);return {idocType:r,controlRecord:c,segments:p}}var zr=10*1024*1024,xe=class extends Error{};function Kr(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 xe(`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 Yr(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 Jr(t){if(typeof t!="string")return true;let e=(t.split(";")[0]??"").trim().toLowerCase();return e.endsWith("xml")||e==="application/x-sap.idoc"}function De(t,e={}){let n=e.maxBodyBytes??zr;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&&!Yr(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(!Jr(r.headers["content-type"])){s.writeHead(415,{"Content-Type":"text/plain"}),s.end("Unsupported Media Type: expected XML"),r.destroy();return}Kr(r,n).then(i=>{let{idocType:o,controlRecord:a,segments:c}=$t(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 xe){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 Ft(t,e,n={}){let r=De(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((p,d)=>{o.close(m=>m?d(m):p());})});};n.host?o.listen(t,n.host,a):o.listen(t,a),o.on("error",i);})}var Bt=1;function Me(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!==Bt||!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 Ue(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:Bt,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 ss=30,is="catalog-cache.json",jt="catalog",F=class{cache=null;ttlMs;key;persistence;constructor(e={}){let n=typeof e=="number"?{ttlMinutes:e}:e;this.ttlMs=(n.ttlMinutes??ss)*60*1e3;let r=(n.baseUrl??"").replace(/\/+$/,"").toLowerCase();if(this.key=r?`${r}|${jt}`:jt,n.persistDir){this.persistence={directory:n.persistDir,fileName:is,ttlMs:this.ttlMs,...n.onWarn?{onWarn:n.onWarn}:{}};let s=Me(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&&Ue(this.cache?[{key:this.key,value:this.cache.entries,storedAt:this.cache.timestamp}]:[],this.persistence);}};var lt=0;function Vt(t){let e=t.docnum??`${Date.now().toString().padStart(13,"0")}${String(lt++).padStart(3,"0")}`;lt>=1e3&&(lt=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 os(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 as(t,e){return t.filter(n=>n.name===e).length}function qt(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=as(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=os(s.name,o,a);c&&n.push(c);}s.children&&s.children.length>0&&qt(s.children,e,n);}}function Wt(t,e){if(!e)return [];let n=new Map;for(let s of e.segments)n.set(s.name,s);let r=[];return qt(t,n,r),r}var ls=new XMLBuilder({ignoreAttributes:false,attributeNamePrefix:"@_",format:true,suppressEmptyNode:false});function Gt(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=Gt(n),s=e[n.name];s?Array.isArray(s)?s.push(r):e[n.name]=[s,r]:e[n.name]=r;}return e}function zt(t,e,n){let r={};for(let i of n){let o=Gt(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 ls.build(s)}var Le=class{constructor(e,n,r){this.httpClient=e;this.sapClient=n;this.partnerDefaults=r;}async send(e){let n={...this.partnerDefaults,...e.partnerOverrides},r=Vt({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 d=Wt(e.segments,e.metadata);if(d.length>0)return {success:false,docnum:r.DOCNUM,message:"Validation failed",violations:d}}let s=zt(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 p=a.match(/<title>(.*?)<\/title>/)?.[1]??"IDoc send failed";return {success:false,docnum:r.DOCNUM,message:p}}};var $e=30,ds=5e3;function ps(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 us="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 ms(t){return {...t,content:[...t.content,{type:"text",text:`\u26A0\uFE0F ${us}`}]}}function gs(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 fs(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 Kt(t,e,n={}){let{toolTimeoutMs:r,logger:s,tlsVerifyDisabled:i}=n,o=n.progressAfterMs??ds;return async(a,c)=>{let l=wt(),{signal:p,deadline:d}=Ot(c?.signal,r),m={correlationId:l,toolName:t,signal:p,deadline:d,sapCalls:{count:0}},u=Date.now();return Pt(m,async()=>{let f=ps(c,m,u,{afterMs:o,...r?{budgetMs:r}:{}}),g="ok",h,T=(async()=>e(a,c))();T.catch(()=>{});try{h=await Promise.race([T,hs(p,t)]),h?.isError&&(g="error");}catch(v){if(v instanceof k)g=v.timedOut?"timeout":"aborted",h=v.timedOut?gs(t,l,Date.now()-u,m.sapCalls.count,r??0):fs(t,l);else {g="error";let P=v instanceof Error?v.message:String(v);h={content:[{type:"text",text:JSON.stringify({error:P,tool:t,correlationId:l})}],isError:true};}}g==="ok"&&p?.aborted&&(g=p.reason?.name==="TimeoutError"?"timeout":"aborted"),f();let y=i?ms(h):h;return s?.info({correlationId:l,tool:t,outcome:g,durationMs:Date.now()-u,sapCalls:m.sapCalls.count,resultBytes:ys(h),...r?{budgetMs:r}:{}},"MCP tool call"),y})}}function hs(t,e){return t?new Promise((n,r)=>{let s=()=>{let i=t.reason?.name==="TimeoutError";r(new k(i,e));};if(t.aborted){s();return}t.addEventListener("abort",s,{once:true});}):new Promise(()=>{})}function ys(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 Ss=1440*60*1e3,Ts="metadata-cache.json",E=class{cache=new Map;ttlMs;baseUrl;persistence;constructor(e={}){if(this.ttlMs=e.ttlMs??Ss,this.baseUrl=bs(e.baseUrl),e.persistDir){this.persistence={directory:e.persistDir,fileName:Ts,ttlMs:this.ttlMs,...e.onWarn?{onWarn:e.onWarn}:{}};for(let n of Me(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&&Ue([...this.cache.entries()].map(([e,n])=>({key:e,value:n.value,storedAt:n.storedAt})),this.persistence);}};function bs(t){return t?t.replace(/\/+$/,"").toLowerCase():""}var Yt="0.0.0-unknown",Jt="guniweb-sap-mcp";function Es(t){if(typeof t!="string"||t.length===0)return Yt;let e=resolve(t);for(;;){try{let r=readFileSync(join(e,"package.json"),"utf-8"),s=JSON.parse(r);if(s.name===Jt&&typeof s.version=="string")return s.version}catch{}let n=dirname(e);if(n===e)return Yt;e=n;}}var Xt=Es(import.meta.dirname),Qt=Jt;function Zt(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?"":`
9
9
 
10
10
  IMPORTANT \u2014 READ-ONLY MODE:
11
11
  You MUST only generate read queries (sap_query or sap_nl_query). Do NOT use sap_create, sap_update, or sap_delete.
@@ -66,7 +66,7 @@ Use the \`sap_nl_query\` tool with these parameters:
66
66
  - "orders over 1000 EUR" \u2192 \`{ field: "TotalAmount", operator: "gt", value: 1000 }\`
67
67
  - "products containing 'bolt'" \u2192 \`{ field: "ProductName", operator: "contains", value: "bolt" }\`
68
68
  - "invoices since January 2024" \u2192 \`{ field: "InvoiceDate", operator: "ge", value: "2024-01-01" }\`
69
- ${i}`}}]}});}function st(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:
69
+ ${i}`}}]}});}function en(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:
70
70
 
71
71
  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"}
72
72
  2. Use sap_list_services to discover available entity sets
@@ -94,20 +94,20 @@ Use sap_query with appropriate filters to find the material data.`}}]})),t.regis
94
94
  1. ${r}
95
95
  2. Use sap_list_services to discover entity sets
96
96
  3. Use sap_get_metadata to understand the Business Partner data model
97
- ${s}`}}]}});}var A=class{cache=new Map;get(e){return this.cache.get(this.normalizeUrl(e))}set(e,n){this.cache.set(this.normalizeUrl(e),n);}has(e){return this.cache.has(this.normalizeUrl(e))}clear(){this.cache.clear();}normalizeUrl(e){return e.replace(/\/+$/,"")}};var ge=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 kn(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(`
97
+ ${s}`}}]}});}var Be=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 Rs(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(`
98
98
 
99
- `),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 it(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],p=i.indexOf(`
99
+ `),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 tn(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(`
100
100
 
101
- `);if(p!==-1){let d=i.substring(p+2),l=it(d,c);n.push(...l);}continue}let a=kn(i);a&&n.push(a);}return n}function ot(t,e){let n=e.match(/boundary=([^\s;]+)/);if(!n)return [];let r=n[1],s=t.replace(/\r\n/g,`
102
- `);return it(s,r)}var Dn=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function at(t){return typeof t=="number"?String(t):typeof t=="string"&&Dn.test(t)?`guid'${t}'`:`'${String(t)}'`}function H(t,e){let n=Object.entries(e);if(n.length===1){let s=n[0];return `${t}(${at(s[1])})`}let r=n.map(([s,i])=>`${s}=${at(i)}`);return `${t}(${r.join(",")})`}function Ne(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 Mn(t){return t.key?H(t.entitySet,t.key):t.entitySet}function pt(t){let e=Mn(t),n=t.method.toUpperCase(),r=[];if(r.push("Content-Type: application/http"),r.push("Content-Transfer-Encoding: binary"),r.push(""),r.push(`${n} ${e} HTTP/1.1`),n==="GET"?r.push("Accept: application/json"):(r.push("Content-Type: application/json"),r.push("Accept: application/json"),t.data&&"__etag"in t.data&&r.push(`If-Match: ${t.data.__etag}`)),r.push(""),t.data&&(n==="POST"||n==="PATCH"||n==="DELETE")){let{__etag:s,...i}=t.data;Object.keys(i).length>0&&r.push(JSON.stringify(i));}return r.join(`\r
103
- `)}function lt(t,e,n){let r=`batch_${randomUUID()}`,s=[];for(let a of e)s.push(`--${r}\r
104
- ${pt(a)}`);if(n)for(let a of n){let c=`changeset_${randomUUID()}`,p=[];for(let l of a.operations)p.push(`--${c}\r
105
- ${pt(l)}`);p.push(`--${c}--`);let d=p.join(`\r
101
+ `);if(l!==-1){let p=i.substring(l+2),d=tn(p,c);n.push(...d);}continue}let a=Rs(i);a&&n.push(a);}return n}function nn(t,e){let n=e.match(/boundary=([^\s;]+)/);if(!n)return [];let r=n[1],s=t.replace(/\r\n/g,`
102
+ `);return tn(s,r)}var ws=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function rn(t){return typeof t=="number"?String(t):typeof t=="string"&&ws.test(t)?`guid'${t}'`:`'${String(t)}'`}function re(t,e){let n=Object.entries(e);if(n.length===1){let s=n[0];return `${t}(${rn(s[1])})`}let r=n.map(([s,i])=>`${s}=${rn(i)}`);return `${t}(${r.join(",")})`}function dt(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 Ps(t){return t.key?re(t.entitySet,t.key):t.entitySet}function on(t){let e=Ps(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
103
+ `)}function an(t,e,n){let r=`batch_${randomUUID()}`,s=[];for(let a of e)s.push(`--${r}\r
104
+ ${on(a)}`);if(n)for(let a of n){let c=`changeset_${randomUUID()}`,l=[];for(let d of a.operations)l.push(`--${c}\r
105
+ ${on(d)}`);l.push(`--${c}--`);let p=l.join(`\r
106
106
  `);s.push(`--${r}\r
107
107
  Content-Type: multipart/mixed; boundary=${c}\r
108
108
  \r
109
- ${d}`);}s.push(`--${r}--`);let i=s.join(`\r
110
- `),o=`multipart/mixed; boundary=${r}`;return {body:i,contentType:o}}var Ln=new Set(["EntityType","Property","NavigationProperty","Association","EntitySet","AssociationSet","PropertyRef","End","FunctionImport","Parameter"]),jn=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",removeNSPrefix:true,isArray:t=>Ln.has(t)});function fe(t,e){let r=jn.parse(t).Edmx.DataServices.Schema,s=Array.isArray(r)?r[0]:r,i=s.EntityType??[],o=s.Association??[],a=s.EntityContainer,c=a?.EntitySet??[],p=new Map;for(let f of o)p.set(f["@_Name"],f);let d=i.map(f=>{let h=Fn(f.Property??[]),R=(f.Key?.PropertyRef??[]).map(O=>O["@_Name"]),S=h.filter(O=>R.includes(O.name)),T=$n(f.NavigationProperty??[],p);return {name:f["@_Name"],keys:S,properties:h,navigationProperties:T}}),l=c.map(f=>({name:f["@_Name"],entityTypeName:xe(f["@_EntityType"])})),u=a?.FunctionImport??[],m=Hn(Array.isArray(u)?u:[u]),g={version:"v2",serviceUrl:e,entityTypes:d,entitySets:l};return m.length>0&&(g.functionImports=m),g}function Fn(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 $n(t,e){return t.map(n=>{let r=xe(n["@_Relationship"]),s=e.get(r),i="Unknown",o=false;if(s){let a=s.End.find(c=>c["@_Role"]===n["@_ToRole"]);a&&(i=xe(a["@_Type"]),o=a["@_Multiplicity"]==="*");}return {name:n["@_Name"],targetEntityType:i,isCollection:o}})}function Hn(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 xe(t){let e=t.lastIndexOf(".");return e>=0?t.substring(e+1):t}var Vn=/^\/Date\((-?\d+)([+-]\d{4})?\)\/$/;function Z(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 dt(t){let e=Vn.exec(t);if(!e)return t;let n=Number(e[1]);return new Date(n).toISOString()}function L(t){if(t==null)return t;if(Array.isArray(t))return t.map(e=>L(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]=dt(s):n[r]=L(s)));return n}return typeof t=="string"?dt(t):t}var ut=10,ee=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.etagCache=r;}async read(e,n,r){let s=Ne(r??{});if(n){let i=`${this.serviceUrl}/${H(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=Z(o.data);return {...a,results:a.results.map(c=>L(c))}}return this.fetchWithPagination(`${this.serviceUrl}/${e}`,s,r?.maxPages??ut)}async query(e,n){let r=Ne(n,true),s=n.maxPages??ut;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=Z(r.data);return s.results.length>0?L(s.results[0]):{}}async update(e,n,r){let s=`${this.serviceUrl}/${H(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}/${H(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[p,d]of Object.entries(n))typeof d=="string"?i[p]=`'${d}'`:i[p]=String(d);let o=await this.httpClient.request({method:s,url:`${this.serviceUrl}/${e}`,headers:{Accept:"application/json"},params:i}),a=Z(o.data);if(a.results.length>0)return L(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}=lt(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=ot(i.data,o),c=[...e,...(n??[]).flatMap(p=>p.operations)];for(let p=0;p<a.length&&p<c.length;p++)a[p].entitySet=c[p].entitySet;return {results:a}}async getMetadata(){let e=await this.httpClient.request({method:"get",url:`${this.serviceUrl}/$metadata`,headers:{Accept:"application/xml"}});return fe(e.data,this.serviceUrl)}async fetchWithPagination(e,n,r){let s=[],i,o=e,a=n,c=0;for(;o&&c<r;){let l=(await this.httpClient.request({method:"get",url:o,headers:{Accept:"application/json"},params:a})).data,u=Z(l),m=u.results.map(f=>L(f));s.push(...m),c===0&&u.count!==void 0&&(i=u.count),c++;let g=l?.d;g&&typeof g.__next=="string"?(o=g.__next,a=void 0):o=void 0;}let p={results:s};return i!==void 0&&(p.count=i),o&&(p.nextLink=o),p}};function mt(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 gt(t){return typeof t=="string"?`'${t}'`:String(t)}function k(t,e){let n=Object.entries(e);if(n.length===1){let[,s]=n[0];return `${t}(${gt(s)})`}let r=n.map(([s,i])=>`${s}=${gt(i)}`).join(",");return `${t}(${r})`}function he(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 ft(t,e){let n=[];for(let s=0;s<e.length;s++){let i=e[s],o=i.key?k(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 ht(t){let e=t.split(".");return e[e.length-1]}function Bn(t,e){return {name:t,type:e.$Type??"Edm.String",nullable:e.$Nullable??false,maxLength:e.$MaxLength,precision:e.$Precision,scale:e.$Scale}}function yt(t,e){let n=JSON.parse(t),r=[],s=[],i=[];for(let[a,c]of Object.entries(n)){if(a.startsWith("$"))continue;let p=c;for(let[d,l]of Object.entries(p)){let u=l;if(u.$Kind==="Action"||u.$Kind==="Function"){let m=u.$IsBound??false,g=u.$Parameter??[],f=m&&g.length>0?g[0].$Name:void 0,h=(m?g.slice(1):g).map(S=>({name:S.$Name,type:S.$Type??"Edm.String",...S.$Nullable===true?{nullable:true}:{}})),R=u.$ReturnType?u.$ReturnType.$Type:void 0;i.push({name:d,isBound:m,...f?{bindingParameter:f}:{},parameters:h,...R?{returnType:R}:{}});}else if(u.$Kind==="EntityType"){let m=[],g=[];for(let[R,S]of Object.entries(u)){if(R.startsWith("$"))continue;let T=S;T.$Kind==="NavigationProperty"?g.push({name:R,targetEntityType:ht(T.$Type),isCollection:T.$Collection??false}):m.push(Bn(R,T));}let h=(u.$Key??[]).map(R=>{let S=m.find(T=>T.name===R);return S||{name:R,type:"Edm.String",nullable:false}});r.push({name:d,keys:h,properties:m,navigationProperties:g});}else if(u.$Kind==="EntityContainer")for(let[m,g]of Object.entries(u)){if(m.startsWith("$"))continue;let f=g;f.$Collection&&f.$Type&&s.push({name:m,entityTypeName:ht(f.$Type)});}}}let o={version:"v4",serviceUrl:e,entityTypes:r,entitySets:s};return i.length>0&&(o.actions=i),o}var qn=10,ye=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.etagCache=r;}async read(e,n,r){if(n){let p=`${this.serviceUrl}/${k(e,n)}`,d=r?he(r):void 0,l=await this.httpClient.request({method:"get",url:p,params:d}),u=l.data,m=l.headers.etag??l.headers.ETag;return m&&this.etagCache.set(e,n,m),{results:[u]}}let s=r?he(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??qn,s=he(n,true),o=(await this.httpClient.request({method:"get",url:`${this.serviceUrl}/${e}`,params:s})).data,a=[...o.value??[]],c=o["@odata.count"],p=o["@odata.nextLink"],d=1;for(;p&&d<r;){let u=this.resolveNextLink(p),g=(await this.httpClient.request({method:"get",url:u})).data,f=g.value??[];a.push(...f),p=g["@odata.nextLink"],d++;}let l={results:a,count:c};return p&&(l.nextLink=p),l}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}/${k(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}/${k(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}=ft(this.serviceUrl,e),s=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/$batch`,data:n,headers:{"Content-Type":r,Accept:"application/json"}}),i=mt(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}/${k(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}/${k(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 yt(n,this.serviceUrl)}resolveNextLink(e){return e.startsWith("http://")||e.startsWith("https://")?e:`${this.serviceUrl}/${e}`}};function ke(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 P=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.metadataCache=r??new A;}protocolClient=null;version=null;initPromise=null;etagCache=new ge;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=ke(n,this.serviceUrl),r=!0;}catch{this.version=ke("",this.serviceUrl);}if(this.protocolClient=this.createProtocolClient(this.version),r&&this.version==="v2"&&n)try{let s=fe(n,this.serviceUrl);this.metadataCache.set(this.serviceUrl,s);}catch{}}createProtocolClient(e){return e==="v4"?new ye(this.httpClient,this.serviceUrl,this.etagCache):new ee(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 ee)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 St(t,e,n){let r=n??new A,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 P(e,a,r).getMetadata(),l=d.entitySets.find(h=>h.name===c);if(!l)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 u=d.entityTypes.find(h=>h.name===l.entityTypeName);if(!u)return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({error:`Entity type '${l.entityTypeName}' not found in metadata`},null,2)}]};let m=new Set(u.keys.map(h=>h.name)),g=u.properties.map(h=>({name:h.name,type:h.type,isKey:m.has(h.name)})),f=Gn(u.properties,d.version);return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({entitySet:c,version:d.version,properties:g,sampleFilter:f},null,2)}]}});}function Gn(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 vt(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 Rt(t,e,n){let r=n??new A,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),p=await new P(e,a,r).getMetadata();return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({version:p.version,serviceUrl:p.serviceUrl,entityTypes:p.entityTypes,entitySets:p.entitySets},null,2)}]}});}var De={"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 Jn={$TODAY:()=>Me(new Date),$YESTERDAY:()=>{let t=new Date;return t.setDate(t.getDate()-1),Me(t)},$MONTH_START:()=>{let t=new Date;return `${t.getFullYear()}-${Ue(t.getMonth()+1)}-01`},$YEAR_START:()=>`${new Date().getFullYear()}-01-01`,$MONTH_AGO:()=>{let t=new Date;return t.setMonth(t.getMonth()-1),Me(t)}};function Me(t){return `${t.getFullYear()}-${Ue(t.getMonth()+1)}-${Ue(t.getDate())}`}function Ue(t){return t.toString().padStart(2,"0")}function zn(t){if(typeof t!="string")return t;let e=Jn[t];return e?e():t}function Tt(t){return {...t,queryTemplates:t.queryTemplates.map(e=>({...e,filters:e.filters.map(n=>({...n,value:zn(n.value)}))}))}}function _t(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=De[s];if(!i)return {contents:[{uri:n.toString(),mimeType:"application/json",text:JSON.stringify({error:`Unknown domain '${s}'`,availableDomains:Object.keys(De)},null,2)}]};let o=Tt(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 Le={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"]},Ct=new Set(["sap_create","sap_update","sap_delete","sap_function","sap_batch","sap_idoc_send","sap_rfc_call","sap_bapi_call","sap_bapi_commit"]),At=["core","odata"],Pt=new Map;for(let[t,e]of Object.entries(Le))for(let n of e)Pt.set(n,t);var Se=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(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=Le[e],r=[];for(let s of n){if(this.options.readOnly&&Ct.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)}getToolsByTier(e){return Le[e]}isReadOnlyMode(){return this.options.readOnly}shouldToolBeVisible(e){let n=Pt.get(e);return n?!(!this.activeTiers.has(n)||n==="idoc"&&!this.options.hasIdocConfig||n==="rfc"&&!this.options.hasRfcConfig||this.options.readOnly&&Ct.has(e)):true}};function bt(t,e,n){let r=n??new A,s=new Map;function i(a){let c=s.get(a);return c||(c=new P(e,a,r),s.set(a,c)),c}function o(a){let c=a instanceof Error?a.message:a&&typeof a=="object"&&"message"in a?String(a.message):"Unknown error";return {content:[{type:"text",text:JSON.stringify({error:c})}],isError:true}}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 p=i(a),d=c,l=await p.batch(d);return {content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(p){return o(p)}});}var V=class{cache=null;ttlMs;constructor(e=30){this.ttlMs=e*60*1e3;}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()};}clear(){this.cache=null;}};var Xn=[".integrationsuite.","integrationsuite-trial.",".it-cpi.",".hci.",".prod.apimanagement.","-api.s4hana.ondemand.com"];function Et(t){let e=t.toLowerCase();return Xn.some(n=>e.includes(n))}var N=class extends Error{constructor(n,r,s,i){super(n);this.httpStatus=r;this.sapGuidance=s;this.isBtpDetected=i;this.name="CatalogError";}},Zn=new Set(["ECONNREFUSED","ETIMEDOUT","ENOTFOUND"]);function er(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 tr(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 It(t){return typeof t=="object"&&t!==null&&"response"in t&&typeof t.response=="object"&&t.response!==null&&typeof t.response.status=="number"}function nr(t){return typeof t=="object"&&t!==null&&"code"in t&&typeof t.code=="string"&&Zn.has(t.code)}function te(t,e,n=false){let r=Et(e);if(It(t)){let i=t.response.status,o=er(i,n),a="message"in t&&typeof t.message=="string"?t.message:`HTTP ${i}`;return new N(a,i,o,r)}if(nr(t)){let i=tr(t.code),o="message"in t&&typeof t.message=="string"?t.message:t.code;return new N(o,void 0,i,r)}let s=t instanceof Error?t.message:String(t);return new N(s,void 0,void 0,r)}function wt(t){return It(t)&&t.response.status===404?"V4 catalog not available. Publish service group via /IWFND/V4_ADMIN.":t instanceof Error?t.message:String(t)}var rr=["/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/iwfnd/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/IWFND/CATALOGSERVICE/ServiceCollection"];function sr(t){if(typeof t=="object"&&t!==null&&"response"in t)return t.response?.status}async function Ot(t,e,n){let r;for(let s of rr)try{return await t.request({method:"get",url:s,params:{$format:"json"}}),n?.onMatch?.(s),s}catch(i){if(sr(i)===404){r=i;continue}throw te(i,e,false)}throw te(r??new Error("All V2 catalog paths returned 404"),e,false)}function Nt(t){try{return new URL(t).pathname}catch{return t}}var ve=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(){let e=this.cache.get();if(e)return {services:e,warnings:[]};let n=[],r=[];try{r=await this.fetchV4Catalog();}catch(o){let a=wt(o);n.push(`V4 catalog fetch failed: ${a}`);}let s;try{s=await this.fetchV2Catalog();}catch(o){throw o instanceof N?o:te(o,this.options.baseUrl??"",false)}let i=this.mergeAndDeduplicate(r,s);return this.cache.set(i),{services:i,warnings:n}}async fetchV2Catalog(){let e=this.options.catalogV2Url??this.resolvedV2Path;return e||(e=await Ot(this.httpClient,this.options.baseUrl??"",{onMatch:this.options.onDiscoveryMatch}),this.resolvedV2Path=e),((await this.httpClient.request({method:"get",url:e,params:{$format:"json"}})).data?.d?.results??[]).map(i=>({technicalName:i.TechnicalServiceName,title:i.Title,serviceUrl:Nt(i.ServiceUrl),description:i.Description,version:"v2",serviceVersion:i.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"}})).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:Nt(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 ir=[[/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"]],or=[[/^[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 ar(t){for(let[e,n]of ir)if(e.test(t.technicalName))return {...t,category:n,categoryConfidence:"high"};for(let[e,n]of or)if(e.test(t.technicalName))return {...t,category:n,categoryConfidence:"high"};return {...t,category:"unknown",categoryConfidence:"low"}}function xt(t){return t.map(ar)}function kt(t,e,n,r){let s=r??new V,i=new ve(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.
109
+ ${p}`);}s.push(`--${r}--`);let i=s.join(`\r
110
+ `),o=`multipart/mixed; boundary=${r}`;return {body:i,contentType:o}}var Os=new Set(["EntityType","Property","NavigationProperty","Association","EntitySet","AssociationSet","PropertyRef","End","FunctionImport","Parameter"]),ks=new XMLParser({ignoreAttributes:false,attributeNamePrefix:"@_",removeNSPrefix:true,processEntities:false,htmlEntities:false,isArray:t=>Os.has(t)});function pt(t){return t===void 0?true:String(t).toLowerCase()!=="false"}function je(t,e){let r=ks.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 p=i.map(g=>{let h=Ns(g.Property??[]),T=(g.Key?.PropertyRef??[]).map(P=>P["@_Name"]),y=h.filter(P=>T.includes(P.name)),v=xs(g.NavigationProperty??[],l);return {name:g["@_Name"],keys:y,properties:h,navigationProperties:v}}),d=c.map(g=>({name:g["@_Name"],entityTypeName:ut(g["@_EntityType"]),creatable:pt(g["@_creatable"]),updatable:pt(g["@_updatable"]),deletable:pt(g["@_deletable"])})),m=a?.FunctionImport??[],u=Ds(Array.isArray(m)?m:[m]),f={version:"v2",serviceUrl:e,entityTypes:p,entitySets:d};return u.length>0&&(f.functionImports=u),f}function Ns(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 xs(t,e){return t.map(n=>{let r=ut(n["@_Relationship"]),s=e.get(r),i="Unknown",o=false;if(s){let a=s.End.find(c=>c["@_Role"]===n["@_ToRole"]);a&&(i=ut(a["@_Type"]),o=a["@_Multiplicity"]==="*");}return {name:n["@_Name"],targetEntityType:i,isCollection:o}})}function Ds(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 ut(t){let e=t.lastIndexOf(".");return e>=0?t.substring(e+1):t}var Ms=/^\/Date\((-?\d+)([+-]\d{4})?\)\/$/;function ge(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 cn(t){let e=Ms.exec(t);if(!e)return t;let n=Number(e[1]);return new Date(n).toISOString()}function z(t){if(t==null)return t;if(Array.isArray(t))return t.map(e=>z(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]=cn(s):n[r]=z(s)));return n}return typeof t=="string"?cn(t):t}var ln=10,fe=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.etagCache=r;}async read(e,n,r){let s=dt(r??{});if(n){let i=`${this.serviceUrl}/${re(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=ge(o.data);return {...a,results:a.results.map(c=>z(c))}}return this.fetchWithPagination(`${this.serviceUrl}/${e}`,s,r?.maxPages??ln)}async query(e,n){let r=dt(n,true),s=n.maxPages??ln;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=ge(r.data);return s.results.length>0?z(s.results[0]):{}}async update(e,n,r){let s=`${this.serviceUrl}/${re(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}/${re(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,p]of Object.entries(n))typeof p=="string"?i[l]=`'${p}'`:i[l]=String(p);let o=await this.httpClient.request({method:s,url:`${this.serviceUrl}/${e}`,headers:{Accept:"application/json"},params:i}),a=ge(o.data);if(a.results.length>0)return z(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}=an(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=nn(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 je(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&&we()){l=true;break}let m=(await this.httpClient.request({method:"get",url:o,headers:{Accept:"application/json"},params:a})).data,u=ge(m),f=u.results.map(h=>z(h));s.push(...f),c===0&&u.count!==void 0&&(i=u.count),c++;let g=m?.d;g&&typeof g.__next=="string"?(o=g.__next,a=void 0):o=void 0;}let p={results:s};return i!==void 0&&(p.count=i),o&&(p.nextLink=o),l&&(p.truncated=true,p.truncationReason=`Nach ${c} Seite(n) abgebrochen: Zeitbudget des Tool-Aufrufs ersch\xF6pft oder Client hat die Verbindung verworfen. Mit skip=${s.length} weiterlesen.`),p}};function dn(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 pn(t){return typeof t=="string"?`'${t}'`:String(t)}function H(t,e){let n=Object.entries(e);if(n.length===1){let[,s]=n[0];return `${t}(${pn(s)})`}let r=n.map(([s,i])=>`${s}=${pn(i)}`).join(",");return `${t}(${r})`}function Ve(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 un(t,e){let n=[];for(let s=0;s<e.length;s++){let i=e[s],o=i.key?H(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 mn(t){let e=t.split(".");return e[e.length-1]}function Us(t,e){return {name:t,type:e.$Type??"Edm.String",nullable:e.$Nullable??false,maxLength:e.$MaxLength,precision:e.$Precision,scale:e.$Scale}}function gn(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[p,d]of Object.entries(l)){let m=d;if(m.$Kind==="Action"||m.$Kind==="Function"){let u=m.$IsBound??false,f=m.$Parameter??[],g=u&&f.length>0?f[0].$Name:void 0,h=(u?f.slice(1):f).map(y=>({name:y.$Name,type:y.$Type??"Edm.String",...y.$Nullable===true?{nullable:true}:{}})),T=m.$ReturnType?m.$ReturnType.$Type:void 0;i.push({name:p,isBound:u,...g?{bindingParameter:g}:{},parameters:h,...T?{returnType:T}:{}});}else if(m.$Kind==="EntityType"){let u=[],f=[];for(let[T,y]of Object.entries(m)){if(T.startsWith("$"))continue;let v=y;v.$Kind==="NavigationProperty"?f.push({name:T,targetEntityType:mn(v.$Type),isCollection:v.$Collection??false}):u.push(Us(T,v));}let h=(m.$Key??[]).map(T=>{let y=u.find(v=>v.name===T);return y||{name:T,type:"Edm.String",nullable:false}});r.push({name:p,keys:h,properties:u,navigationProperties:f});}else if(m.$Kind==="EntityContainer")for(let[u,f]of Object.entries(m)){if(u.startsWith("$"))continue;let g=f;g.$Collection&&g.$Type&&s.push({name:u,entityTypeName:mn(g.$Type),creatable:mt(g,"InsertRestrictions","Insertable"),updatable:mt(g,"UpdateRestrictions","Updatable"),deletable:mt(g,"DeleteRestrictions","Deletable")});}}}let o={version:"v4",serviceUrl:e,entityTypes:r,entitySets:s};return i.length>0&&(o.actions=i),o}function mt(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 Ls=10,qe=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}/${H(e,n)}`,p=r?Ve(r):void 0,d=await this.httpClient.request({method:"get",url:l,params:p}),m=d.data,u=d.headers.etag??d.headers.ETag;return u&&this.etagCache.set(e,n,u),{results:[m]}}let s=r?Ve(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??Ls,s=Ve(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"],p=1,d=false;for(;l&&p<r;){if(we()){d=true;break}let u=this.resolveNextLink(l),g=(await this.httpClient.request({method:"get",url:u})).data,h=g.value??[];a.push(...h),l=g["@odata.nextLink"],p++;}let m={results:a,count:c};return l&&(m.nextLink=l),d&&(m.truncated=true,m.truncationReason=`Nach ${p} 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}/${H(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}/${H(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}=un(this.serviceUrl,e),s=await this.httpClient.request({method:"post",url:`${this.serviceUrl}/$batch`,data:n,headers:{"Content-Type":r,Accept:"application/json"}}),i=dn(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}/${H(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}/${H(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 gn(n,this.serviceUrl)}resolveNextLink(e){return e.startsWith("http://")||e.startsWith("https://")?e:`${this.serviceUrl}/${e}`}};function gt(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 I=class{constructor(e,n,r){this.httpClient=e;this.serviceUrl=n;this.metadataCache=r??new E;}protocolClient=null;version=null;initPromise=null;etagCache=new Be;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=gt(n,this.serviceUrl),r=!0;}catch{this.version=gt("",this.serviceUrl);}if(this.protocolClient=this.createProtocolClient(this.version),r&&this.version==="v2"&&n)try{let s=je(n,this.serviceUrl);this.metadataCache.set(this.serviceUrl,s);}catch{}}createProtocolClient(e){return e==="v4"?new qe(this.httpClient,this.serviceUrl,this.etagCache):new fe(this.httpClient,this.serviceUrl,this.etagCache)}async getClient(){if(await this.ensureInitialized(),!this.protocolClient)throw new Error("ODataClient initialization failed: protocol client not created");return this.protocolClient}async read(e,n,r){return (await this.getClient()).read(e,n,r)}async query(e,n){return (await this.getClient()).query(e,n)}async create(e,n){return (await this.getClient()).create(e,n)}async update(e,n,r){return (await this.getClient()).update(e,n,r)}async delete(e,n){return (await this.getClient()).delete(e,n)}async batch(e,n){let r=await this.getClient();return this.version==="v4"?r.batch(e):r.batch(e,n)}async functionImport(e,n,r){let s=await this.getClient();if(s instanceof fe)return s.functionImport(e,n,r?.httpMethod);let i=s;return r?.isFunction?i.callFunction(e,n,r?.entitySet,r?.key):i.callAction(e,n,r?.entitySet,r?.key)}async getMetadata(){let e=this.metadataCache.get(this.serviceUrl);if(e)return e;let n=await this.getClient();if(e=this.metadataCache.get(this.serviceUrl),e)return e;let r=await n.getMetadata();return this.metadataCache.set(this.serviceUrl,r),r}getVersion(){return this.version}};function fn(t,e,n){let r=n??new E,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),p=await new I(e,a,r).getMetadata(),d=p.entitySets.find(h=>h.name===c);if(!d)return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({error:`Entity set '${c}' not found in service ${a}`,availableEntitySets:p.entitySets.map(h=>h.name)},null,2)}]};let m=p.entityTypes.find(h=>h.name===d.entityTypeName);if(!m)return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({error:`Entity type '${d.entityTypeName}' not found in metadata`},null,2)}]};let u=new Set(m.keys.map(h=>h.name)),f=m.properties.map(h=>({name:h.name,type:h.type,isKey:u.has(h.name)})),g=Fs(m.properties,p.version);return {contents:[{uri:i.toString(),mimeType:"application/json",text:JSON.stringify({entitySet:c,version:p.version,properties:f,sampleFilter:g},null,2)}]}});}function Fs(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 hn(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 yn(t,e,n){let r=n??new E,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 I(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 ft={"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 js={$TODAY:()=>ht(new Date),$YESTERDAY:()=>{let t=new Date;return t.setDate(t.getDate()-1),ht(t)},$MONTH_START:()=>{let t=new Date;return `${t.getFullYear()}-${yt(t.getMonth()+1)}-01`},$YEAR_START:()=>`${new Date().getFullYear()}-01-01`,$MONTH_AGO:()=>{let t=new Date;return t.setMonth(t.getMonth()-1),ht(t)}};function ht(t){return `${t.getFullYear()}-${yt(t.getMonth()+1)}-${yt(t.getDate())}`}function yt(t){return t.toString().padStart(2,"0")}function Vs(t){if(typeof t!="string")return t;let e=js[t];return e?e():t}function Sn(t){return {...t,queryTemplates:t.queryTemplates.map(e=>({...e,filters:e.filters.map(n=>({...n,value:Vs(n.value)}))}))}}function Tn(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=ft[s];if(!i)return {contents:[{uri:n.toString(),mimeType:"application/json",text:JSON.stringify({error:`Unknown domain '${s}'`,availableDomains:Object.keys(ft)},null,2)}]};let o=Sn(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 St={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"]},bn=new Set(["sap_create","sap_update","sap_delete","sap_function","sap_batch","sap_idoc_send","sap_rfc_call","sap_bapi_call","sap_bapi_commit"]),vn=["core","odata"],An=new Map;for(let[t,e]of Object.entries(St))for(let n of e)An.set(n,t);var We=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(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=St[e],r=[];for(let s of n){if(this.options.readOnly&&bn.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)}getToolsByTier(e){return St[e]}isReadOnlyMode(){return this.options.readOnly}shouldToolBeVisible(e){let n=An.get(e);return n?!(!this.activeTiers.has(n)||n==="idoc"&&!this.options.hasIdocConfig||n==="rfc"&&!this.options.hasRfcConfig||this.options.readOnly&&bn.has(e)):true}};var _n="/sap/opu/odata/sap",Ws=/^[A-Z0-9_/]+$/i,K=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 ${_n}/API_BUSINESS_PARTNER oder der technische Name (API_BUSINESS_PARTNER), den sap_discover_services als technicalName liefert.`;}};function he(t){let e=t.trim();if(e.length===0)throw new K(t);if(e.startsWith("/"))return e.replace(/\/+$/,"")||"/";if(e.startsWith("http://")||e.startsWith("https://"))try{return new URL(e).pathname.replace(/\/+$/,"")}catch{throw new K(t)}if(Ws.test(e))return `${_n}/${e.replace(/^\/+|\/+$/g,"")}`;throw new K(t)}var ye=class extends Error{guidance;extra;constructor(e,n,r={}){super(e),this.name="ToolInputError",this.guidance=n,this.extra=r;}};function w(t,e){if(t instanceof K)return Tt({code:"INVALID_INPUT",error:t.message,guidance:t.guidance,...e?.hint?{hint:e.hint}:{}});if(t instanceof ye)return Tt({code:"INVALID_INPUT",error:t.message,...t.guidance?{guidance:t.guidance}:{},...t.extra,...e?.hint?{hint:e.hint}:{}});let n=Z(t,{serviceUrl:e?.serviceUrl});return Tt({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 Tt(t){let e=X()?.correlationId;return {content:[{type:"text",text:JSON.stringify({...t,...e?{correlationId:e}:{}},null,2)}],isError:true}}function Cn(t,e,n){let r=n??new E,s=new Map;function i(a){let c=s.get(a);return c||(c=new I(e,a,r),s.set(a,c)),c}function o(a,c){return w(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),p=c,d=await l.batch(p);return {content:[{type:"text",text:JSON.stringify(d,null,2)}]}}catch(l){return o(l)}});}async function En(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 Gs=[".integrationsuite.","integrationsuite-trial.",".it-cpi.",".hci.",".prod.apimanagement.","-api.s4hana.ondemand.com"];function Rn(t){let e=t.toLowerCase();return Gs.some(n=>e.includes(n))}var M=class extends Error{constructor(n,r,s,i){super(n);this.httpStatus=r;this.sapGuidance=s;this.isBtpDetected=i;this.name="CatalogError";}},zs=new Set(["ECONNREFUSED","ETIMEDOUT","ENOTFOUND"]);function Ks(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 Ys(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 wn(t){return typeof t=="object"&&t!==null&&"response"in t&&typeof t.response=="object"&&t.response!==null&&typeof t.response.status=="number"}function Js(t){return typeof t=="object"&&t!==null&&"code"in t&&typeof t.code=="string"&&zs.has(t.code)}function Se(t,e,n=false){let r=Rn(e);if(wn(t)){let i=t.response.status,o=Ks(i,n),a="message"in t&&typeof t.message=="string"?t.message:`HTTP ${i}`;return new M(a,i,o,r)}if(Js(t)){let i=Ys(t.code),o="message"in t&&typeof t.message=="string"?t.message:t.code;return new M(o,void 0,i,r)}let s=t instanceof Error?t.message:String(t);return new M(s,void 0,void 0,r)}function Pn(t){return wn(t)&&t.response.status===404?"V4 catalog not available. Publish service group via /IWFND/V4_ADMIN.":t instanceof Error?t.message:String(t)}var Xs=["/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/iwfnd/CATALOGSERVICE;v=2/ServiceCollection","/sap/opu/odata/IWFND/CATALOGSERVICE/ServiceCollection"];function Qs(t){if(typeof t=="object"&&t!==null&&"response"in t)return t.response?.status}async function In(t,e,n){let r;for(let s of Xs)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 k)throw i;if(Qs(i)===404){r=i;continue}throw Se(i,e,false)}throw Se(r??new Error("All V2 catalog paths returned 404"),e,false)}function On(t){try{return new URL(t).pathname}catch{return t}}function ei(t){let e=t.replace(/'/g,"''");return `substringof('${e}',TechnicalServiceName) or substringof('${e}',Title)`}var Ge=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 En(["v4","v2"],async l=>l==="v4"?await this.fetchV4Catalog().then(p=>({ok:true,services:p}),p=>({ok:false,error:p})):await this.fetchV2Catalog().then(p=>({ok:true,services:p}),p=>({ok:false,error:p})),this.options.concurrency??5),o=[];if(s?.ok)o=s.services;else if(s){if(s.error instanceof k)throw s.error;r.push(`V4 catalog fetch failed: ${Pn(s.error)}`);}if(!i?.ok){let l=i?.error;throw l instanceof M?l:Se(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(ei(e)),warnings:[],filtered:!0}}catch(n){if(n instanceof k)throw n;n?.response?.status;return void 0}}async fetchV2Catalog(e){let n=this.options.catalogV2Url??this.resolvedV2Path;return n||(n=await In(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:On(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:On(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 ti=[[/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"]],ni=[[/^[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 ri(t){for(let[e,n]of ti)if(e.test(t.technicalName))return {...t,category:n,categoryConfidence:"high"};for(let[e,n]of ni)if(e.test(t.technicalName))return {...t,category:n,categoryConfidence:"high"};return {...t,category:"unknown",categoryConfidence:"low"}}function kn(t){return t.map(ri)}function se(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 ie(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 Nn(t,e,n,r){let s=r??new F,i=new Ge(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.
111
111
 
112
112
  **When to use:**
113
113
  - You need to find which SAP services are available on this system
@@ -116,7 +116,7 @@ ${d}`);}s.push(`--${r}--`);let i=s.join(`\r
116
116
 
117
117
  **When NOT to use:**
118
118
  - You already know the service URL (proceed to sap_query or sap_nl_query)
119
- - 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)"),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:p})=>{try{let d=c??50,l=p??0,u=await i.discoverServices(),m=xt(u.services);if(a&&(m=m.filter(S=>S.category===a)),o){let S=o.toLowerCase();m=m.filter(T=>T.technicalName.toLowerCase().includes(S)||T.title.toLowerCase().includes(S)||(T.description?.toLowerCase().includes(S)??!1));}let g=m.length,f={};for(let S of m)f[S.category]=(f[S.category]??0)+1;let h=m.slice(l,l+d),R={services:h.map(S=>({technicalName:S.technicalName,title:S.title,category:S.category,serviceUrl:S.serviceUrl,version:S.version})),summary:{total:g,returned:h.length,offset:l,byCategory:f},...u.warnings.length>0?{warnings:u.warnings}:{}};return {content:[{type:"text",text:JSON.stringify(R,null,2)}]}}catch(d){if(d instanceof N){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}}let l=d instanceof Error?d.message:"Unknown error";return {content:[{type:"text",text:JSON.stringify({error:l})}],isError:true}}});}function Dt(t,e,n){let r=new A;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.
119
+ - 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 p=await i.discoverServices(o),d=kn(p.services);if(a&&(d=d.filter(y=>y.category===a)),o&&!p.filtered){let y=o.toLowerCase();d=d.filter(v=>v.technicalName.toLowerCase().includes(y)||v.title.toLowerCase().includes(y)||(v.description?.toLowerCase().includes(y)??!1));}let m=d.length,u={};for(let y of d)u[y.category]=(u[y.category]??0)+1;let f=se(c,l),g=d.slice(f.skip,f.skip+f.top),h=ie({returned:g.length,totalCount:m,paging:f}),T={services:g.map(y=>({technicalName:y.technicalName,title:y.title,category:y.category,serviceUrl:y.serviceUrl,version:y.version})),...h,summary:{total:m,returned:g.length,offset:f.skip,byCategory:u},...p.warnings.length>0?{warnings:p.warnings}:{},...o?{filterApplied:p.filtered?"server":"client"}:{}};return {content:[{type:"text",text:JSON.stringify(T,null,2)}]}}catch(p){if(p instanceof M){let d={error:p.message};return p.sapGuidance&&(d.guidance=p.sapGuidance),p.isBtpDetected&&(d.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(d,null,2)}],isError:true}}return w(p)}});}function xn(t,e,n){let r=new E;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.
120
120
 
121
121
  **When to use:**
122
122
  - You need the raw list of OData services at a base URL
@@ -124,15 +124,25 @@ ${d}`);}s.push(`--${r}--`);let i=s.join(`\r
124
124
 
125
125
  **When NOT to use:**
126
126
  - Use sap_discover_services instead for categorized, searchable results
127
- - 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.')}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({serviceUrl:s})=>{try{let o=(await e.request({method:"get",url:s,headers:{Accept:"application/json"},params:{$format:"json"}})).data,a=[],c=o.d;return c&&"EntitySets"in c&&Array.isArray(c.EntitySets)?a=c.EntitySets:"value"in o&&Array.isArray(o.value)&&(a=o.value.map(p=>p.name)),a=a.filter(p=>M(p,n)),{content:[{type:"text",text:JSON.stringify({serviceUrl:s,entitySets:a,count:a.length},null,2)}]}}catch(i){let o=i instanceof Error?i.message:i&&typeof i=="object"&&"message"in i?String(i.message):"Unknown error";return {content:[{type:"text",text:JSON.stringify({error:o})}],isError:true}}}),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.
127
+ - 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=he(s),l=(await e.request({method:"get",url:a,headers:{Accept:"application/json"},params:{$format:"json"}})).data,p=[],d=l.d;d&&"EntitySets"in d&&Array.isArray(d.EntitySets)?p=d.EntitySets:"value"in l&&Array.isArray(l.value)&&(p=l.value.map(h=>h.name)),p=p.filter(h=>V(h,n));let m=se(i,o),u=p.length,f=p.slice(m.skip,m.skip+m.top),g=ie({returned:f.length,totalCount:u,paging:m});return {content:[{type:"text",text:JSON.stringify({serviceUrl:a,entitySets:f,count:u,...g},null,2)}]}}catch(a){return w(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.
128
+
129
+ 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.
128
130
 
129
131
  **When to use:**
130
132
  - You need to discover entity sets in a service (omit entityType)
131
133
  - You need full property details for a specific entity type (provide entityType)
134
+ - You need to know whether an entity set accepts writes
132
135
  - Use 'search' to filter large services by name pattern
133
136
 
134
137
  **When NOT to use:**
135
- - 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 P(e,s,r).getMetadata(),p;if(i){let d=c.entityTypes.find(l=>l.name===i);if(!d){let l=i.toLowerCase();d=c.entityTypes.find(u=>u.name.toLowerCase()===l);}if(!d){let l=i.toLowerCase(),u=c.entityTypes.filter(m=>m.name.toLowerCase().includes(l)).map(m=>m.name).slice(0,20);return {content:[{type:"text",text:JSON.stringify({error:`Entity type '${i}' not found`,hint:"Use search parameter to find matching types, or omit entityType to see all entity sets.",closestMatches:u.length>0?u:void 0})}],isError:!0}}p={version:c.version,entityType:d};}else {let d=c.entitySets.filter(m=>M(m.name,n)),l=c.entityTypes;if(o){let m=o.toLowerCase();d=d.filter(g=>g.name.toLowerCase().includes(m)),l=l.filter(g=>g.name.toLowerCase().includes(m));}l.length>50&&!o?p={version:c.version,entitySets:d,entityTypeCount:l.length,hint:`Service has ${l.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.`}:p={version:c.version,entityTypes:l.map(m=>({name:m.name,keys:m.keys.map(g=>g.name),propertyCount:m.properties.length,navigationProperties:m.navigationProperties.map(g=>g.name)})),entitySets:d};}return {content:[{type:"text",text:JSON.stringify(p,null,2)}]}}catch(a){let c=a instanceof Error?a.message:"Unknown error";return {content:[{type:"text",text:JSON.stringify({error:c})}],isError:true}}});}function Mt(t,e,n){let r=n??new A,s=new Map;function i(a){let c=s.get(a);return c||(c=new P(e,a,r),s.set(a,c)),c}function o(a){let c=a instanceof Error?a.message:a&&typeof a=="object"&&"message"in a?String(a.message):"Unknown error";return {content:[{type:"text",text:JSON.stringify({error:c})}],isError:true}}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:p,httpMethod:d,entitySet:l,key:u,isFunction:m})=>{try{let f=await i(a).functionImport(c,p,{httpMethod:d,entitySet:l,key:u,isFunction:m??!1});return {content:[{type:"text",text:JSON.stringify(f,null,2)}]}}catch(g){return o(g)}});}function Ut(t,e){t.registerTool("test-connection",{title:"Test SAP Connection",description:"Test connectivity to the configured SAP system by sending a simple request",inputSchema:z$2.object({}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async()=>{try{return await e.request({method:"get",url:"/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/",headers:{Accept:"application/json"}}),{content:[{type:"text",text:JSON.stringify({status:"connected",timestamp:new Date().toISOString(),hint:"Use sap_list_services with a specific OData service path like /sap/opu/odata/sap/API_BUSINESS_PARTNER to discover entity sets."})}]}}catch(n){let r=n&&typeof n=="object"&&"message"in n?n.message:"Unknown error";return {content:[{type:"text",text:JSON.stringify({status:"error",message:r,timestamp:new Date().toISOString()})}],isError:true}}});}function pr(t){return {name:t.FieldName,dataType:t.DataType,length:t.Length,decimals:t.Decimals??0,required:t.Required}}function lr(t){return {name:t.SegmentName,parentSegment:t.ParentSegment??"",minOccurrence:t.MinOccurrence,maxOccurrence:t.MaxOccurrence,fields:t.Fields?.results?.map(pr)??[]}}function dr(t){return {idocType:t.IdocType,mesType:t.MesType,segments:t.Segments?.results?.map(lr)??[]}}async function je(t,e,n){try{if(n){let o=(await t.request({method:"get",url:`${e}/IdocTypes('${n}')?$expand=Segments($expand=Fields)`})).data;return dr(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 Lt={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 jt(t,e,n){try{let i=(await t.request({method:"get",url:`${e}/IdocStatus('${n}')`})).data.d,o=i.Status,a=Lt[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 Fe={name:z$2.string().describe("Segment name, e.g. E1MARAM"),fields:z$2.record(z$2.string(),z$2.string()).describe("Segment field values")},ur=z$2.object({...Fe}),mr=z$2.object({...Fe,children:z$2.array(ur).optional().describe("Nested child segments (level 3)")}),gr=z$2.object({...Fe,children:z$2.array(mr).optional().describe("Nested child segments")});function Ft(t,e,n,r){function s(i){let o=i instanceof Error?i.message:i&&typeof i=="object"&&"message"in i?String(i.message):"Unknown error";return {content:[{type:"text",text:JSON.stringify({error:o})}],isError:true}}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(gr).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:p,sndprn:d,rcvpor:l,rcvprn:u})=>{try{let m;if(a){let f=await je(e,a,i);Array.isArray(f)||(m=f);}let g=await n.send({idocType:i,segments:o,metadata:m,partnerOverrides:{mesType:c,sndpor:p,sndprn:d,rcvpor:l,rcvprn:u}});return {content:[{type:"text",text:JSON.stringify(g,null,2)}]}}catch(m){return s(m)}}),t.registerTool("sap_idoc_status",{title:"Query IDoc Status",description:"Query IDoc processing status in SAP. Returns status code with human-readable description (50=received, 53=processed, 51=error).",inputSchema:z$2.object({docnum:z$2.string().describe("IDoc document number"),statusServiceUrl:z$2.string().describe("OData service URL for IDoc status query")}),annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true}},async({docnum:i,statusServiceUrl:o})=>{try{let a=await jt(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 je(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 fr={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 Ht(t,e){if(e.isReadOnlyMode())return;let n=e.getActiveTiers(),s=["core","odata","idoc","rfc"].filter(o=>!n.includes(o)),i=s.length>0?`Available tiers to activate: ${s.map(o=>`${o} (${fr[o]})`).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(", ")}. ${i}`,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:o})=>{let a=e.enableTier(o);return "error"in a?{content:[{type:"text",text:JSON.stringify({status:"error",message:a.error})}],isError:true}:{content:[{type:"text",text:JSON.stringify({status:"success",tier_enabled:o,enabled_tools:a.enabled,active_tiers:a.activeTiers,note:"Tools enabled. If your client did not refresh the tool list automatically, you may need to reconnect."})}]}});}function Vt(t,e){return t.length===0?"":t.map(n=>hr(n,e)).join(" and ")}function hr(t,e){let{field:n,operator:r,value:s}=t;switch(r){case "contains":return yr(n,String(s),e);case "startswith":return `startswith(${n},'${Re(String(s))}')`;case "endswith":return `endswith(${n},'${Re(String(s))}')`;default:return Sr(n,r,s,e)}}function yr(t,e,n){let r=Re(e);return n==="v2"?`substringof('${r}',${t})`:`contains(${t},'${r}')`}function Sr(t,e,n,r){let s=vr(n,r);return `${t} ${e} ${s}`}function vr(t,e){return typeof t=="number"||typeof t=="boolean"?String(t):Rr(t)?Tr(t,e):`'${Re(t)}'`}function Rr(t){return /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?/.test(t)}function Tr(t,e){return e==="v2"?`datetime'${t.includes("T")?t:`${t}T00:00:00`}'`:t}function Re(t){return t.replace(/'/g,"''")}function Bt(t,e,n,r){let s=r??new A,i=new Map;function o(c){let p=i.get(c);return p||(p=new P(e,c,s),i.set(c,p)),p}function a(c){let p=c instanceof Error?c.message:c&&typeof c=="object"&&"message"in c?String(c.message):"Unknown error";return {content:[{type:"text",text:JSON.stringify({error:p})}],isError:true}}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.
138
+ - 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 I(e,he(s),r).getMetadata(),l;if(i){let p=c.entityTypes.find(d=>d.name===i);if(!p){let d=i.toLowerCase();p=c.entityTypes.find(m=>m.name.toLowerCase()===d);}if(!p){let d=i.toLowerCase(),m=c.entityTypes.filter(u=>u.name.toLowerCase().includes(d)).map(u=>u.name).slice(0,20);return w(new ye(`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:p};}else {let p=c.entitySets.filter(f=>V(f.name,n)),d=c.entityTypes;if(o){let f=o.toLowerCase();p=p.filter(g=>g.name.toLowerCase().includes(f)),d=d.filter(g=>g.name.toLowerCase().includes(f));}let m={creatable:p.filter(f=>f.creatable).map(f=>f.name),updatable:p.filter(f=>f.updatable).length,deletable:p.filter(f=>f.deletable).length};d.length>50&&!o?l={version:c.version,entitySets:p,capabilities:m,entityTypeCount:d.length,hint:`Service has ${d.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:d.map(f=>({name:f.name,keys:f.keys.map(g=>g.name),propertyCount:f.properties.length,navigationProperties:f.navigationProperties.map(g=>g.name)})),entitySets:p,capabilities:m};}return {content:[{type:"text",text:JSON.stringify(l,null,2)}]}}catch(a){return w(a,{serviceUrl:s})}});}function Dn(t,e,n){let r=n??new E,s=new Map;function i(a){let c=s.get(a);return c||(c=new I(e,a,r),s.set(a,c)),c}function o(a,c){return w(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:p,entitySet:d,key:m,isFunction:u})=>{try{let g=await i(a).functionImport(c,l,{httpMethod:p,entitySet:d,key:m,isFunction:u??!1});return {content:[{type:"text",text:JSON.stringify(g,null,2)}]}}catch(f){return o(f)}});}function Mn(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.
139
+
140
+ **When to use:**
141
+ - Any SAP tool fails and you need to know which layer is at fault
142
+ - After changing SAP_BASE_URL, credentials, SAP_CLIENT or the CA bundle
143
+
144
+ **When NOT to use:**
145
+ - 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 Oe(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 w(r,{hint:"Die Verbindungsdiagnose konnte nicht ausgef\xFChrt werden. Das ist ein Fehler des Servers, kein Befund \xFCber das SAP-System."})}});}function ii(t){return {name:t.FieldName,dataType:t.DataType,length:t.Length,decimals:t.Decimals??0,required:t.Required}}function oi(t){return {name:t.SegmentName,parentSegment:t.ParentSegment??"",minOccurrence:t.MinOccurrence,maxOccurrence:t.MaxOccurrence,fields:t.Fields?.results?.map(ii)??[]}}function ai(t){return {idocType:t.IdocType,mesType:t.MesType,segments:t.Segments?.results?.map(oi)??[]}}async function bt(t,e,n){try{if(n){let o=(await t.request({method:"get",url:`${e}/IdocTypes('${n}')?$expand=Segments($expand=Fields)`})).data;return ai(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 Un={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 Ln(t,e,n){try{let i=(await t.request({method:"get",url:`${e}/IdocStatus('${n}')`})).data.d,o=i.Status,a=Un[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 vt={name:z$2.string().describe("Segment name, e.g. E1MARAM"),fields:z$2.record(z$2.string(),z$2.string()).describe("Segment field values")},ci=z$2.object({...vt}),li=z$2.object({...vt,children:z$2.array(ci).optional().describe("Nested child segments (level 3)")}),di=z$2.object({...vt,children:z$2.array(li).optional().describe("Nested child segments")});function $n(t,e,n,r){function s(i,o){return w(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(di).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:p,rcvpor:d,rcvprn:m})=>{try{let u;if(a){let g=await bt(e,a,i);Array.isArray(g)||(u=g);}let f=await n.send({idocType:i,segments:o,metadata:u,partnerOverrides:{mesType:c,sndpor:l,sndprn:p,rcvpor:d,rcvprn:m}});return {content:[{type:"text",text:JSON.stringify(f,null,2)}]}}catch(u){return s(u)}}),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 Ln(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 bt(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 pi={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 Hn(t,e){if(e.isReadOnlyMode())return;let n=e.getActiveTiers(),s=["core","odata","idoc","rfc"].filter(o=>!n.includes(o)),i=s.length>0?`Available tiers to activate: ${s.map(o=>`${o} (${pi[o]})`).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(", ")}. ${i}`,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:o})=>{let a=e.enableTier(o);return "error"in a?{content:[{type:"text",text:JSON.stringify({status:"error",message:a.error})}],isError:true}:{content:[{type:"text",text:JSON.stringify({status:"success",tier_enabled:o,enabled_tools:a.enabled,active_tiers:a.activeTiers,note:"Tools enabled. If your client did not refresh the tool list automatically, you may need to reconnect."})}]}});}function Bn(t,e){return t.length===0?"":t.map(n=>ui(n,e)).join(" and ")}function ui(t,e){let{field:n,operator:r,value:s}=t;switch(r){case "contains":return mi(n,String(s),e);case "startswith":return `startswith(${n},'${ze(String(s))}')`;case "endswith":return `endswith(${n},'${ze(String(s))}')`;default:return gi(n,r,s,e)}}function mi(t,e,n){let r=ze(e);return n==="v2"?`substringof('${r}',${t})`:`contains(${t},'${r}')`}function gi(t,e,n,r){let s=fi(n,r);return `${t} ${e} ${s}`}function fi(t,e){return typeof t=="number"||typeof t=="boolean"?String(t):hi(t)?yi(t,e):`'${ze(t)}'`}function hi(t){return /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2})?)?/.test(t)}function yi(t,e){return e==="v2"?`datetime'${t.includes("T")?t:`${t}T00:00:00`}'`:t}function ze(t){return t.replace(/'/g,"''")}function jn(t,e,n,r){let s=r??new E,i=new Map;function o(c){let l=i.get(c);return l||(l=new I(e,c,s),i.set(c,l)),l}function a(c,l){return w(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.
136
146
 
137
147
  **When to use:**
138
148
  - You have a natural language question about SAP data
@@ -142,7 +152,7 @@ ${d}`);}s.push(`--${r}--`);let i=s.join(`\r
142
152
  **When NOT to use:**
143
153
  - You need to create, update, or delete data (use sap_create/sap_update/sap_delete)
144
154
  - You already have an exact OData $filter string (use sap_query instead)
145
- - 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:p,filters:d,select:l,orderby:u,top:m,skip:g})=>{if(!M(p,n))return a(new Error(`Entity set '${p}' is not exposed. Configure --expose to include it.`));try{let f=o(c),R=(await f.getMetadata()).version,S=Vt(d,R),T=await f.query(p,{filter:S||void 0,select:l,orderby:u,top:m,skip:g});return {content:[{type:"text",text:JSON.stringify({version:R,generatedFilter:S||"(none)",results:T.results,count:T.count,nextLink:T.nextLink},null,2)}]}}catch(f){return a(f)}});}function qt(t,e,n,r){let s=r??new A,i=new Map;function o(p){let d=i.get(p);return d||(d=new P(e,p,s),i.set(p,d)),d}function a(p){let d=p instanceof Error?p.message:p&&typeof p=="object"&&"message"in p?String(p.message):"Unknown error";return {content:[{type:"text",text:JSON.stringify({error:d})}],isError:true}}function c(p){return M(p,n)?null:a(new Error(`Entity set '${p}' 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.
155
+ - 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:p,select:d,orderby:m,top:u,skip:f})=>{if(!V(l,n))return a(new Error(`Entity set '${l}' is not exposed. Configure --expose to include it.`));try{let g=o(c),T=(await g.getMetadata()).version,y=Bn(p,T),v=await g.query(l,{filter:y||void 0,select:d,orderby:m,top:u,skip:f});return {content:[{type:"text",text:JSON.stringify({version:T,generatedFilter:y||"(none)",results:v.results,count:v.count,nextLink:v.nextLink},null,2)}]}}catch(g){return a(g)}});}function Vn(t,e,n,r){let s=r??new E,i=new Map;function o(l){let p=he(l),d=i.get(p);return d||(d=new I(e,p,s),i.set(p,d)),d}function a(l,p){return w(l,{serviceUrl:p})}function c(l){return V(l,n)?null:a(new Error(`Entity set '${l}' is not exposed. Configure --expose to include it.`))}t.registerTool("sap_read",{title:"Read SAP Entity",description:`Read a single entity or entity collection from SAP. Use key parameter for single entity, omit for collection. Supports $select and $expand.
146
156
 
147
157
  **When to use:**
148
158
  - You have a specific entity key and want one record
@@ -150,7 +160,7 @@ ${d}`);}s.push(`--${r}--`);let i=s.join(`\r
150
160
 
151
161
  **When NOT to use:**
152
162
  - You want to list/filter multiple entities (use sap_query)
153
- - 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:p,entitySet:d,key:l,select:u,expand:m})=>{let g=c(d);if(g)return g;try{let f=o(p),h={select:u,expand:m},R=await f.read(d,l,h);return {content:[{type:"text",text:JSON.stringify(R,null,2)}]}}catch(f){return a(f)}}),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.
163
+ - 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:p,key:d,select:m,expand:u})=>{let f=c(p);if(f)return f;try{let g=o(l),h={select:m,expand:u},T=await g.read(p,d,h);return {content:[{type:"text",text:JSON.stringify(T,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.
154
164
 
155
165
  **When to use:**
156
166
  - You have an exact OData $filter string ready
@@ -160,7 +170,7 @@ ${d}`);}s.push(`--${r}--`);let i=s.join(`\r
160
170
  **When NOT to use:**
161
171
  - You have a natural language question (use sap_nl_query instead)
162
172
  - You don't know which service to query (use sap://routing/{domain} first, then sap_discover_services)
163
- - 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 per page"),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:p,entitySet:d,filter:l,select:u,expand:m,orderby:g,top:f,skip:h,maxPages:R})=>{let S=c(d);if(S)return S;try{let O=await o(p).query(d,{filter:l,select:u,expand:m,orderby:g,top:f,skip:h,maxPages:R});return {content:[{type:"text",text:JSON.stringify(O,null,2)}]}}catch(T){return a(T)}}),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:p,entitySet:d,data:l})=>{let u;try{u=JSON.parse(l);}catch{return {content:[{type:"text",text:JSON.stringify({error:"Invalid JSON in data parameter. Must be a valid JSON string.",example:'{"BusinessPartnerCategory": "2", "BusinessPartnerName": "Acme Corp"}'})}],isError:true}}let m=c(d);if(m)return m;try{let f=await o(p).create(d,u);return {content:[{type:"text",text:JSON.stringify({created:f},null,2)}]}}catch(g){return a(g)}}),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:p,entitySet:d,key:l,data:u})=>{let m,g;try{m=JSON.parse(l),g=JSON.parse(u);}catch{return a(new Error("Invalid JSON in key or data parameter"))}let f=c(d);if(f)return f;try{return await o(p).update(d,m,g),{content:[{type:"text",text:JSON.stringify({updated:!0,entitySet:d,key:l})}]}}catch(h){return a(h)}}),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:p,entitySet:d,key:l})=>{let u;try{u=JSON.parse(l);}catch{return a(new Error("Invalid JSON in key parameter"))}let m=c(d);if(m)return m;try{return await o(p).delete(d,u),{content:[{type:"text",text:JSON.stringify({deleted:!0,entitySet:d,key:l})}]}}catch(g){return a(g)}});}function re(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=>_r(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 _r(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:x(t.MESSAGE_V1),messageV2:x(t.MESSAGE_V2),messageV3:x(t.MESSAGE_V3),messageV4:x(t.MESSAGE_V4),parameter:x(t.PARAMETER),row:Cr(t.ROW),field:x(t.FIELD),system:x(t.SYSTEM),logNo:x(t.LOG_NO),logMsgNo:x(t.LOG_MSG_NO)}}function x(t){if(t==null)return;let e=String(t);return e===""?void 0:e}function Cr(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 Wt(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 Te=null;async function Gt(t){if(Te)return true;try{return Te=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 Kt(){if(!Te)throw new Error("node-rfc not loaded \u2014 call tryLoadRfc() first");return Te}var Yt=6e4,Pr=5e3,br=2,Er=10,q=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";}},_e=class{pool;sessions=new Map;logger;closed=false;constructor(e,n){let s=Kt().Pool;this.pool=new s({connectionParameters:{...e},poolOptions:{low:br,high:Er}}),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 q(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 q(e);clearTimeout(r.idleTimer);try{let s=n?{WAIT:"X"}:{},i=await r.client.call("BAPI_TRANSACTION_COMMIT",s),{hasErrors:o}=re(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()+Pr;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),Yt)};return this.sessions.set(n,r),r}refreshIdle(e){clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>this.onIdle(e.id),Yt);}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 se(t){if(t instanceof q)return {content:[{type:"text",text:JSON.stringify({code:"SESSION_NOT_FOUND",message:t.message,sessionId:t.sessionId})}],isError:true};let e=Wt(t);return {content:[{type:"text",text:JSON.stringify(e)}],isError:true}}var Ir={I:"IMPORT",E:"EXPORT",C:"CHANGING",T:"TABLES",X:"EXCEPTION"};function Jt(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.
173
+ - 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:p,filter:d,select:m,expand:u,orderby:f,top:g,skip:h,maxPages:T})=>{let y=c(p);if(y)return y;try{let v=o(l),P=se(g,h),D=await v.query(p,{filter:d,select:m,expand:u,orderby:f,top:P.top,skip:P.skip,maxPages:T}),L=ie({returned:D.results.length,totalCount:D.count,paging:P,hasMoreHint:D.nextLink?!0:void 0});return {content:[{type:"text",text:JSON.stringify({...D,...L},null,2)}]}}catch(v){return a(v,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:p,data:d})=>{let m;try{m=JSON.parse(d);}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 u=c(p);if(u)return u;try{let g=await o(l).create(p,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:p,key:d,data:m})=>{let u,f;try{u=JSON.parse(d),f=JSON.parse(m);}catch{return a(new Error("Invalid JSON in key or data parameter"))}let g=c(p);if(g)return g;try{return await o(l).update(p,u,f),{content:[{type:"text",text:JSON.stringify({updated:!0,entitySet:p,key:d})}]}}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:p,key:d})=>{let m;try{m=JSON.parse(d);}catch{return a(new Error("Invalid JSON in key parameter"))}let u=c(p);if(u)return u;try{return await o(l).delete(p,m),{content:[{type:"text",text:JSON.stringify({deleted:!0,entitySet:p,key:d})}]}}catch(f){return a(f,l)}});}function ve(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=>Si(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 Si(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:U(t.MESSAGE_V1),messageV2:U(t.MESSAGE_V2),messageV3:U(t.MESSAGE_V3),messageV4:U(t.MESSAGE_V4),parameter:U(t.PARAMETER),row:Ti(t.ROW),field:U(t.FIELD),system:U(t.SYSTEM),logNo:U(t.LOG_NO),logMsgNo:U(t.LOG_MSG_NO)}}function U(t){if(t==null)return;let e=String(t);return e===""?void 0:e}function Ti(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 qn(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 Ke=null;async function Wn(t){if(Ke)return true;try{return Ke=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 Gn(){if(!Ke)throw new Error("node-rfc not loaded \u2014 call tryLoadRfc() first");return Ke}var zn=6e4,vi=5e3,Ai=2,_i=10,ae=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";}},Ye=class{pool;sessions=new Map;logger;closed=false;constructor(e,n){let s=Gn().Pool;this.pool=new s({connectionParameters:{...e},poolOptions:{low:Ai,high:_i}}),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 ae(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 ae(e);clearTimeout(r.idleTimer);try{let s=n?{WAIT:"X"}:{},i=await r.client.call("BAPI_TRANSACTION_COMMIT",s),{hasErrors:o}=ve(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()+vi;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),zn)};return this.sessions.set(n,r),r}refreshIdle(e){clearTimeout(e.idleTimer),e.idleTimer=setTimeout(()=>this.onIdle(e.id),zn);}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 Ae(t){if(t instanceof ae)return {content:[{type:"text",text:JSON.stringify({code:"SESSION_NOT_FOUND",message:t.message,sessionId:t.sessionId})}],isError:true};let e=qn(t);return {content:[{type:"text",text:JSON.stringify(e)}],isError:true}}var Ci={I:"IMPORT",E:"EXPORT",C:"CHANGING",T:"TABLES",X:"EXCEPTION"};function Kn(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.
164
174
 
165
175
  When to use:
166
176
  - Custom Z_* function modules (no BAPI commit needed)
@@ -170,7 +180,7 @@ When NOT to use:
170
180
  - For BAPIs that need transactional commit -> use sap_bapi_call + sap_bapi_commit instead
171
181
  - 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.
172
182
 
173
- 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 se(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).
183
+ 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 Ae(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).
174
184
 
175
185
  When to use:
176
186
  - Before calling an unfamiliar RFC/BAPI to understand its parameters
@@ -179,7 +189,7 @@ When to use:
179
189
  When NOT to use:
180
190
  - To search for function modules by name pattern -> use sap_rfc_search_functions
181
191
 
182
- 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),p=(a.PARAMS??[]).map(l=>{let u=String(l.PARAMCLASS??"").trim(),m=Ir[u]??"IMPORT",g=String(l.TABNAME??"").trim(),f=String(l.FIELDNAME??"").trim(),h=g&&f?`${g}-${f}`:g||f||void 0,R=String(l.DECIMALS??"").trim(),S=R===""?void 0:Number.parseInt(R,10),T=Number.isNaN(S)?void 0:S,O=String(l.INTLENGTH??"0").trim(),W=Number.parseInt(O===""?"0":O,10)||0,j=String(l.DEFAULT??"").trim(),E=String(l.PARAMTEXT??"").trim();return {name:String(l.PARAMETER??"").trim(),direction:m,type:String(l.EXID??"").trim(),length:W,decimals:T,optional:String(l.OPTIONAL??"").trim()==="X",default:j===""?void 0:j,text:E===""?void 0:E,ddicReference:h}}),d={name:i,remoteEnabled:String(a.REMOTE_CALL??"")==="X",updateTask:String(a.UPDATE_TASK??"")==="X",parameters:p};return {content:[{type:"text",text:JSON.stringify(d,null,2)}]}}catch(i){return se(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.
192
+ 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(d=>{let m=String(d.PARAMCLASS??"").trim(),u=Ci[m]??"IMPORT",f=String(d.TABNAME??"").trim(),g=String(d.FIELDNAME??"").trim(),h=f&&g?`${f}-${g}`:f||g||void 0,T=String(d.DECIMALS??"").trim(),y=T===""?void 0:Number.parseInt(T,10),v=Number.isNaN(y)?void 0:y,P=String(d.INTLENGTH??"0").trim(),D=Number.parseInt(P===""?"0":P,10)||0,L=String(d.DEFAULT??"").trim(),Y=String(d.PARAMTEXT??"").trim();return {name:String(d.PARAMETER??"").trim(),direction:u,type:String(d.EXID??"").trim(),length:D,decimals:v,optional:String(d.OPTIONAL??"").trim()==="X",default:L===""?void 0:L,text:Y===""?void 0:Y,ddicReference:h}}),p={name:i,remoteEnabled:String(a.REMOTE_CALL??"")==="X",updateTask:String(a.UPDATE_TASK??"")==="X",parameters:l};return {content:[{type:"text",text:JSON.stringify(p,null,2)}]}}catch(i){return Ae(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.
183
193
 
184
194
  When to use:
185
195
  - BAPI_USER_CREATE1, BAPI_SALESORDER_CREATEFROMDAT2, BAPI_*_CHANGE, etc.
@@ -192,7 +202,7 @@ Critical behavior:
192
202
  - The output contains a parsed \`return\` array (BAPIRET2 messages, camelCase) plus \`hasErrors\`, \`errorCount\`, \`warningCount\`.
193
203
  - 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).
194
204
  - hasErrors=true is NOT isError=true at the MCP transport level (the call itself succeeded).
195
- - 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=re(o),{RETURN:p,E_RETURN:d,...l}=o,u={},m={};for(let[g,f]of Object.entries(l))Array.isArray(f)?m[g]=f:u[g]=f;return {content:[{type:"text",text:JSON.stringify({sessionId:a,return:c.messages,hasErrors:c.hasErrors,errorCount:c.errorCount,warningCount:c.warningCount,exports:u,tables:m},null,2)}]}}catch(o){return se(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.
205
+ - 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=ve(o),{RETURN:l,E_RETURN:p,...d}=o,m={},u={};for(let[f,g]of Object.entries(d))Array.isArray(g)?u[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:u},null,2)}]}}catch(o){return Ae(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.
196
206
 
197
207
  When to use:
198
208
  - After one or more sap_bapi_call invocations that returned hasErrors=false
@@ -201,7 +211,7 @@ When NOT to use:
201
211
  - 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)
202
212
  - For non-BAPI RFC calls (sap_rfc_call cannot participate)
203
213
 
204
- 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=re(i);return {content:[{type:"text",text:JSON.stringify({sessionId:a,return:c.messages,committed:o},null,2)}]}}catch(i){return se(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.
214
+ 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=ve(i);return {content:[{type:"text",text:JSON.stringify({sessionId:a,return:c.messages,committed:o},null,2)}]}}catch(i){return Ae(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.
205
215
 
206
216
  When to use:
207
217
  - Discover BAPIs/FMs by partial name (e.g. "BAPI_USER_*", "Z_*")
@@ -212,26 +222,45 @@ When NOT to use:
212
222
  Wildcards (SAP-native, NOT glob):
213
223
  - \`*\` matches any sequence of characters
214
224
  - \`+\` matches a single character
215
- 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??[],p=c.length,d=i??100,l=p>d,g={functions:(l?c.slice(0,d):c).map(f=>{let h=String(f.TEXT??"").trim();return {name:String(f.FUNCNAME??"").trim(),groupName:String(f.GROUPNAME??"").trim(),shortText:h===""?void 0:h}}),total:p,...l?{truncated:!0}:{}};return {content:[{type:"text",text:JSON.stringify(g,null,2)}]}}catch(o){return se(o)}});}function ie(t,e){let n=new McpServer({name:"guniweb-sap-mcp",version:"1.1.0"},{capabilities:{logging:{},resources:{},prompts:{},tools:{listChanged:true}}}),r={readOnly:e?.readOnly??false,activeTiers:e?.tiers??At,hasIdocConfig:e?.hasIdocConfig??false,hasRfcConfig:e?.hasRfcConfig??false},s=new Se(r),i=n.registerTool.bind(n);n.registerTool=((l,...u)=>{let m=i(l,...u);return s.registerHandle(l,m),m});let o=Je(t),a=new le(o,e?.httpOptions);e?.userContext&&(a.userContext=e.userContext);let c=e?.exposePatterns;st(n),rt(n),Ut(n,a),Dt(n,a,c),kt(n,a,{catalogV2Url:e?.catalogV2Url,catalogV4Url:e?.catalogV4Url,baseUrl:t.baseUrl,onDiscoveryMatch:e?.onDiscoveryMatch},e?.caches?.catalog),qt(n,a,c,e?.caches?.metadata),Mt(n,a,e?.caches?.metadata),bt(n,a,e?.caches?.metadata),Bt(n,a,c,e?.caches?.metadata);let p=e?.idocStore??new F,d=new de(a,e?.sapClient,e?.partnerDefaults);return Ft(n,a,d,p),vt(n,p),e?.rfcAvailable&&e.rfcPool&&e.logger&&Jt(n,e.rfcPool,e.logger),Ht(n,s),Rt(n,a,e?.caches?.metadata),St(n,a,e?.caches?.metadata),_t(n),s.applyInitialVisibility(),{server:n,registry:s,httpClient:a}}var Or=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 $e(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=Or.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 zt(t=process.env){try{return $e(t)!==null}catch{return false}}function He(t){let e=t.authorization??t.Authorization;if(typeof e!="string"||!e.startsWith("Bearer "))return null;let n=e.slice(7);if(!n)return null;try{let r=decodeJwt(n);return typeof r.sub!="string"?null:{token:n,sub:r.sub,email:typeof r.email=="string"?r.email:void 0,name:typeof r.name=="string"?r.name:void 0}}catch{return null}}async function Qt(t){let e=createMcpExpressApp({host:"0.0.0.0"});if(t.apiKey){let s=`Bearer ${t.apiKey}`;e.use((i,o,a)=>{let c=i,p=o,d=a;if(c.path==="/healthz"){d();return}if(c.headers.authorization!==s){p.status(401).json({error:"Unauthorized"});return}d();});}e.get("/healthz",(s,i)=>{i.status(200).json({status:"ok"});}),t.transport==="http"?Ur(e,t):Lr(e,t);let n=ae(t.idocStore);e.post("/idoc",(s,i)=>{n(s,i);});let r=createServer(e);return new Promise(s=>{r.listen(t.port,()=>{let i=r.address(),o=typeof i=="object"&&i!==null?i.port:t.port;s({port:o,close:()=>new Promise((a,c)=>{r.close(p=>p?c(p):a());})});});})}function Ur(t,e){let n=new V,r=new A;t.all("/mcp",(s,i)=>{let o=s,a=i,c=new StreamableHTTPServerTransport({sessionIdGenerator:void 0}),p=He(o.headers),{server:d}=ie(e.config,{...e.serverOptions,userContext:p,caches:{catalog:n,metadata:r}});d.connect(c).then(()=>c.handleRequest(o,a,o.body)).catch(()=>{a.headersSent||(a.writeHead(500),a.end("Internal Server Error"));});});}function Lr(t,e){let n=new Map;t.get("/sse",(r,s)=>{let i=s,o=new SSEServerTransport("/message",i),{server:a,httpClient:c}=ie(e.config,e.serverOptions);a.connect(o).then(()=>o.start()).then(()=>{n.set(o.sessionId,{transport:o,server:a,httpClient:c}),o.onclose=()=>{n.delete(o.sessionId);};}).catch(()=>{i.headersSent||(i.writeHead(500),i.end("Internal Server Error"));});}),t.post("/message",(r,s)=>{let i=r,o=s,c=new URL(i.url??"",`http://${i.headers.host??"localhost"}`).searchParams.get("sessionId")??i.query?.sessionId;if(!c||!n.has(c)){o.writeHead(400,{"Content-Type":"application/json"}),o.end(JSON.stringify({error:"Invalid or missing session ID"}));return}let p=n.get(c),d=He(i.headers);p.httpClient.userContext=d,p.transport.handlePostMessage(i,o,i.body).catch(()=>{o.headersSent||(o.writeHead(500),o.end("Internal Server Error"));});});}var Hr=`Usage: guniweb-sap-mcp [options]
225
+ 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,p=i??100,d=l>p,f={functions:(d?c.slice(0,p):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,...d?{truncated:!0}:{}};return {content:[{type:"text",text:JSON.stringify(f,null,2)}]}}catch(o){return Ae(o)}});}function _e(t,e){let n=new McpServer({name:Qt,version:Xt},{capabilities:{logging:{},resources:{},prompts:{},tools:{listChanged:true}}}),r={readOnly:e?.readOnly??false,activeTiers:e?.tiers??vn,hasIdocConfig:e?.hasIdocConfig??false,hasRfcConfig:e?.hasRfcConfig??false},s=new We(r),i=n.registerTool.bind(n),o=e?.toolTimeoutMs??$e*1e3;n.registerTool=((g,...h)=>{let T=h.length-1,y=h[T];typeof y=="function"&&(h[T]=Kt(g,y,{toolTimeoutMs:o,logger:e?.logger,tlsVerifyDisabled:e?.httpOptions?.tlsVerify===false}));let v=i(g,...h);return s.registerHandle(g,v),v});let a={baseUrl:t.baseUrl,...e?.cacheDir?{persistDir:e.cacheDir}:{},...e?.logger?{onWarn:(g,h)=>e.logger?.warn({err:h},g)}:{}},c=e?.caches?.catalog??new F(a),l=e?.caches?.metadata??new E(a),p=Ee(t),d=new te(p,e?.httpOptions);e?.userContext&&(d.userContext=e.userContext);let m=e?.exposePatterns;en(n),Zt(n),Mn(n,d,{baseUrl:t.baseUrl,sapClient:e?.sapClient,tlsVerify:e?.httpOptions?.tlsVerify}),xn(n,d,m),Nn(n,d,{catalogV2Url:e?.catalogV2Url,catalogV4Url:e?.catalogV4Url,baseUrl:t.baseUrl,onDiscoveryMatch:e?.onDiscoveryMatch,...e?.concurrency?{concurrency:e.concurrency}:{}},c),Vn(n,d,m,l),Dn(n,d,l),Cn(n,d,l),jn(n,d,m,l);let u=e?.idocStore??new ne,f=new Le(d,e?.sapClient,e?.partnerDefaults);return $n(n,d,f,u),hn(n,u),e?.rfcAvailable&&e.rfcPool&&e.logger&&Kn(n,e.rfcPool,e.logger),Hn(n,s),yn(n,d,l),fn(n,d,l),Tn(n),s.applyInitialVisibility(),{server:n,registry:s,httpClient:d}}var Ri=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 At(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=Ri.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 Yn(t=process.env){try{return At(t)!==null}catch{return false}}var Je=class{constructor(e={}){this.options=e;this.discovery=e.discoveryClient??new G;}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 Oi(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 Oi(t){let e=t.split(".");return e.length===3&&e.every(n=>n.length>0)}async function Jn(t){let e=new Je({...t.jwtIssuer?{issuerUrl:t.jwtIssuer}:{},...t.jwtAudience?{audience:t.jwtAudience}:{}}),n=createMcpExpressApp({host:"0.0.0.0"});if(t.apiKey){let i=`Bearer ${t.apiKey}`;n.use((o,a,c)=>{let l=o,p=a,d=c;if(l.path==="/healthz"){d();return}if(t.webhookSecret&&l.path==="/idoc"){d();return}let m=l.headers.authorization;if(!ue(typeof m=="string"?m:"",i)){p.status(401).json({error:"Unauthorized"});return}d();});}n.get("/healthz",(i,o)=>{o.status(200).json({status:"ok"});}),t.transport==="http"?Mi(n,t,e):Ui(n,t,e);let r=De(t.idocStore,{secret:t.webhookSecret,maxBodyBytes:t.webhookMaxBodyBytes});n.post("/idoc",(i,o)=>{r(i,o);});let s=createServer(n);return new Promise(i=>{s.listen(t.port,()=>{let o=s.address(),a=typeof o=="object"&&o!==null?o.port:t.port;i({port:a,close:()=>new Promise((c,l)=>{s.close(p=>p?l(p):c());})});});})}function Mi(t,e,n){let r={baseUrl:e.config.baseUrl,...e.serverOptions?.cacheDir?{persistDir:e.serverOptions.cacheDir}:{}},s=new F(r),i=new E(r);t.all("/mcp",(o,a)=>{let c=o,l=a,p=new StreamableHTTPServerTransport({sessionIdGenerator:void 0});n.verify(c.headers).then(m=>{if(m.kind==="rejected"){l.writeHead(401,{"Content-Type":"application/json"}),l.end(JSON.stringify({error:"Unauthorized",reason:m.reason,hint:"Das mitgeschickte Bearer-Token hat die Signaturpr\xFCfung gegen SAP_JWT_ISSUER nicht bestanden."}));return}let u=m.kind==="user"?m.context:null;d(u);});function d(m){let{server:u}=_e(e.config,{...e.serverOptions,userContext:m,caches:{catalog:s,metadata:i}});u.connect(p).then(()=>p.handleRequest(c,l,c.body)).catch(()=>{l.headersSent||(l.writeHead(500),l.end("Internal Server Error"));});}});}function Ui(t,e,n){let r=new Map;t.get("/sse",(s,i)=>{let o=i,a=new SSEServerTransport("/message",o),{server:c,httpClient:l}=_e(e.config,e.serverOptions);c.connect(a).then(()=>a.start()).then(()=>{r.set(a.sessionId,{transport:a,server:c,httpClient:l}),a.onclose=()=>{r.delete(a.sessionId);};}).catch(()=>{o.headersSent||(o.writeHead(500),o.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 p=r.get(l);n.verify(o.headers).then(d=>{if(d.kind==="rejected"){a.writeHead(401,{"Content-Type":"application/json"}),a.end(JSON.stringify({error:"Unauthorized",reason:d.reason}));return}p.httpClient.userContext=d.kind==="user"?d.context:null,p.transport.handlePostMessage(o,a,o.body).catch(()=>{a.headersSent||(a.writeHead(500),a.end("Internal Server Error"));});});});}var Hi=`Usage: guniweb-sap-mcp [options]
216
226
 
217
227
  SAP S/4HANA MCP Server -- OData V2/V4 and IDoc integration.
218
228
 
219
229
  Options:
220
230
  --transport <stdio|http|sse> Transport mode (default: stdio)
221
231
  --port <number> HTTP/SSE port (default: 8808)
222
- --read-only Disable write tools (sap_create, sap_update, sap_delete, ...)
232
+ --allow-write Enable write tools (sap_create, sap_update, sap_delete,
233
+ sap_function, sap_idoc_send). Since 1.0 the server is
234
+ READ-ONLY by default: first contact with a production
235
+ ERP should not be able to change anything.
236
+ --read-only Accepted and ignored \u2014 read-only is the default now
237
+ --tool-timeout <seconds> Time budget per tool call (default: 30, 0 disables).
238
+ Keep it below the smallest client limit \u2014
239
+ Cloudflare aborts at 100 s, n8n's MCP transport at 300 s.
240
+ --cache-dir <path> Persist catalog + metadata caches across restarts.
241
+ Without it every restart refetches both from SAP.
242
+ --concurrency <n> Max simultaneous SAP requests where several are
243
+ unavoidable (default: 5)
223
244
  --tiers <core,odata,idoc> Active tool tiers (default: core,odata)
224
245
  --expose <pattern1,pattern2> Filter exposed entity sets (glob patterns)
225
246
  --webhook-port <number> IDoc webhook listen port
247
+ --webhook-secret <secret> Shared secret for inbound IDoc webhook auth
248
+ (accepts Bearer <secret> or Basic auth password)
249
+ --webhook-host <host> IDoc webhook bind address (default: all interfaces)
226
250
  --catalog-v2-url <url> Override OData V2 catalog URL
227
251
  --catalog-v4-url <url> Override OData V4 catalog URL
228
252
  --api-key <key> Inbound API key for HTTP transport
229
253
  --help, -h Show this help and exit
230
254
 
231
255
  Configuration via environment variables: SAP_BASE_URL, SAP_AUTH_TYPE,
232
- SAP_USERNAME, SAP_PASSWORD, SAP_API_KEY, etc.
256
+ SAP_CLIENT (appended to every request \u2014 without it a client-dependent
257
+ Gateway answers 401 "Anmeldung fehlgeschlagen"),
258
+ SAP_USERNAME, SAP_PASSWORD, SAP_API_KEY, SAP_TLS_VERIFY,
259
+ SAP_HTTP_TIMEOUT_MS (single request timeout, default 30000),
260
+ SAP_MCP_TOOL_TIMEOUT (tool call budget in seconds, same as --tool-timeout),
261
+ SAP_WEBHOOK_SECRET, SAP_WEBHOOK_HOST, SAP_WEBHOOK_MAX_BODY_BYTES, etc.
233
262
  See README for the full list.
234
263
 
235
264
  Repository: https://github.com/jhaertf/guniweb-sap-mcp
236
- `;async function Vr(){let t=Ve();t.help&&(process.stdout.write(Hr),process.exit(0));let e=We(),n=Ce(process.env),r=In({transport:{target:"pino/file",options:{destination:2}}});n.tlsVerify===false&&r.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)."),n.clientCert&&r.info({hasCert:true,hasPassphrase:!!n.clientKeyPassphrase},"X.509 client certificate enabled for OData transport"),r.info({baseUrl:e.baseUrl,authType:e.authType},"SAP MCP server starting"),t.expose&&r.info({exposePatterns:t.expose},"Entity set filtering active");let s=await Gt(r),i;if(s){let u=$e(process.env);if(u){u.snc_lib&&!existsSync(u.snc_lib)&&r.warn({path:u.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."),i=new _e(u,r);let m=!!u.snc_qop;r.info({ashost:u.ashost,mshost:u.mshost,snc:m,...m?{qop:u.snc_qop,myname:u.snc_myname}:{}},m?"RFC pool ready (SNC-secured)":"RFC pool ready");}else r.info("node-rfc loaded but no SAP_RFC_* config \u2014 RFC tier requires sap_enable_tools rfc + env vars to activate");}let o=new F,a={exposePatterns:t.expose,idocStore:o,sapClient:e.sapClient,partnerDefaults:{sndpor:t.sndpor,sndprt:t.sndprt,sndprn:t.sndprn,rcvpor:t.rcvpor,rcvprt:t.rcvprt,rcvprn:t.rcvprn},catalogV2Url:t.catalogV2Url,catalogV4Url:t.catalogV4Url,httpOptions:n,onDiscoveryMatch:u=>{r.info({path:u,attemptedPaths:3},"V2 catalog auto-discovery: matched path");}},c=t.transport??"stdio",p={...a,readOnly:t.readOnly,tiers:t.tiers,hasIdocConfig:Be(t),hasRfcConfig:zt(process.env),rfcAvailable:s,rfcPool:i,logger:r},d;if(c==="stdio"){let{server:u}=ie(e,p),m=new StdioServerTransport;if(await u.connect(m),r.info("SAP MCP server connected on stdio transport"),t.webhookPort){let g=await Ye(t.webhookPort,o);r.info({port:g.port},"IDoc webhook server listening");}}else {let u=t.port??8808;d=await Qt({transport:c,port:u,apiKey:t.apiKey,config:e,serverOptions:p,idocStore:o}),r.info({transport:c,port:d.port,auth:t.apiKey?"api-key":"open"},"SAP MCP server listening on HTTP");}let l=async u=>{r.info({signal:u},"Shutting down...");try{i&&await i.closeAll(),d&&await d.close();}catch(m){r.error({err:m},"Shutdown error (continuing)");}process.exit(0);};process.on("SIGTERM",()=>l("SIGTERM")),process.on("SIGINT",()=>l("SIGINT"));}Vr().catch(t=>{process.stderr.write(`Fatal error: ${t instanceof Error?t.message:String(t)}
265
+ `;async function Bi(){let t=_t();t.help&&(process.stdout.write(Hi),process.exit(0));let e=Rt(),n=nt(process.env),r=Hr({transport:{target:"pino/file",options:{destination:2}}});n.tlsVerify===false&&r.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)."),n.clientCert&&r.info({hasCert:true,hasPassphrase:!!n.clientKeyPassphrase},"X.509 client certificate enabled for OData transport"),r.info({baseUrl:e.baseUrl,authType:e.authType},"SAP MCP server starting"),t.allowWriteExplicit?r.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."):r.info("Nur-Lese-Betrieb (Standard seit 1.0). Schreibwerkzeuge sind nicht registriert; mit --allow-write bzw. SAP_MCP_ALLOW_WRITE=true aktivieren."),Dt(r,e.sapClient),t.transport&&t.transport!=="stdio"&&(process.env.SAP_JWT_ISSUER?r.info({issuer:process.env.SAP_JWT_ISSUER},"Eingehende Benutzer-Token werden gegen die JWKS des Issuers gepr\xFCft"):r.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.")),t.expose&&r.info({exposePatterns:t.expose},"Entity set filtering active");let s=await Wn(r),i;if(s){let u=At(process.env);if(u){u.snc_lib&&!existsSync(u.snc_lib)&&r.warn({path:u.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."),i=new Ye(u,r);let f=!!u.snc_qop;r.info({ashost:u.ashost,mshost:u.mshost,snc:f,...f?{qop:u.snc_qop,myname:u.snc_myname}:{}},f?"RFC pool ready (SNC-secured)":"RFC pool ready");}else r.info("node-rfc loaded but no SAP_RFC_* config \u2014 RFC tier requires sap_enable_tools rfc + env vars to activate");}let o=new ne,a={exposePatterns:t.expose,idocStore:o,sapClient:e.sapClient,partnerDefaults:{sndpor:t.sndpor,sndprt:t.sndprt,sndprn:t.sndprn,rcvpor:t.rcvpor,rcvprt:t.rcvprt,rcvprn:t.rcvprn},catalogV2Url:t.catalogV2Url,catalogV4Url:t.catalogV4Url,concurrency:t.concurrency,httpOptions:n,onDiscoveryMatch:u=>{r.info({path:u,attemptedPaths:3},"V2 catalog auto-discovery: matched path");}},c=t.transport??"stdio",l=t.toolTimeoutSeconds??$e;l===0?r.warn("Zeitbudget je Tool-Aufruf abgeschaltet (--tool-timeout 0): ein Aufruf kann unbegrenzt laufen und die Client-Verbindung bis zu deren eigenem Timeout blockieren."):r.info({toolTimeoutSeconds:l},"Zeitbudget je Tool-Aufruf aktiv"),t.cacheDir?r.info({cacheDir:t.cacheDir},"Cache-Ablage aktiv (\xFCberlebt Neustarts)"):r.info("Kein Cache-Verzeichnis konfiguriert (--cache-dir / SAP_CACHE_DIR): Katalog und Metadaten werden nach jedem Neustart erneut aus SAP geholt.");let p={...a,toolTimeoutMs:l*1e3,cacheDir:t.cacheDir,readOnly:t.readOnly,tiers:t.tiers,hasIdocConfig:Ct(t),hasRfcConfig:Yn(process.env),rfcAvailable:s,rfcPool:i,logger:r},d;if(c==="stdio"){let{server:u}=_e(e,p),f=new StdioServerTransport;if(await u.connect(f),r.info("SAP MCP server connected on stdio transport"),t.webhookPort){t.webhookSecret||r.warn({port:t.webhookPort},"IDoc webhook has NO authentication: anyone reaching this port can inject IDocs. Set --webhook-secret (or SAP_WEBHOOK_SECRET) for production.");let g=await Ft(t.webhookPort,o,{host:t.webhookHost,secret:t.webhookSecret,maxBodyBytes:t.webhookMaxBodyBytes});r.info({port:g.port,auth:t.webhookSecret?"secret":"open"},"IDoc webhook server listening");}}else {let u=t.port??8808;!t.webhookSecret&&!t.apiKey&&r.warn({port:u},"IDoc webhook (/idoc) and MCP endpoint have NO authentication. Set --api-key and --webhook-secret (or SAP_WEBHOOK_SECRET) for production."),d=await Jn({transport:c,port:u,apiKey:t.apiKey,config:e,serverOptions:p,jwtIssuer:process.env.SAP_JWT_ISSUER,jwtAudience:process.env.SAP_JWT_AUDIENCE,idocStore:o,webhookSecret:t.webhookSecret,webhookMaxBodyBytes:t.webhookMaxBodyBytes}),r.info({transport:c,port:d.port,auth:t.apiKey?"api-key":"open"},"SAP MCP server listening on HTTP");}Mt(r,e,n);let m=async u=>{r.info({signal:u},"Shutting down...");try{i&&await i.closeAll(),d&&await d.close();}catch(f){r.error({err:f},"Shutdown error (continuing)");}process.exit(0);};process.on("SIGTERM",()=>m("SIGTERM")),process.on("SIGINT",()=>m("SIGINT"));}Bi().catch(t=>{process.stderr.write(`Fatal error: ${t instanceof Error?t.message:String(t)}
237
266
  `),process.exit(1);});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guniweb-sap-mcp",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "GuniWeb SAP S/4HANA & ECC MCP Server -- OData V2/V4, IDoc, and RFC/BAPI for n8n",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",