sap-wm-mcp 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ Connect AI agents — Claude, Copilot, or any MCP-compatible client — directly
13
13
  >
14
14
  > This project ships a custom RAP OData V4 service that exposes classic WM operations as a proper API — and wraps it in an MCP server so AI agents can drive it. Large portions of the SAP install base are still on classic WM. This fills the gap.
15
15
 
16
- The **npm package** ships 21 tools covering core operations, analytics, shift management, anomaly detection, audit history, and proactive replenishment. This repository contains the 9 open-sourced tool implementations — the ABAP RAP service source and additional tool source are available separately (see [ABAP Service Installation](#abap-service-installation)).
16
+ The **npm package** ships 22 tools covering core operations, analytics, shift management, anomaly detection, audit history, proactive replenishment, and interim zone reconciliation. This repository contains the 9 open-sourced tool implementations — the ABAP RAP service source and additional tool source are available separately (see [ABAP Service Installation](#abap-service-installation)).
17
17
 
18
18
  ---
19
19
 
@@ -374,7 +374,7 @@ For write operations:
374
374
 
375
375
  ## Tools Reference
376
376
 
377
- The npm package ships **21 tools** across five capability areas. The 9 tools below are open-sourced in this repository. Analytics, shift management, anomaly detection, and operations tools are available in the published package.
377
+ The npm package ships **22 tools** across five capability areas. The 9 tools below are open-sourced in this repository. Analytics, shift management, anomaly detection, and operations tools are available in the published package.
378
378
 
379
379
  ---
380
380
 
@@ -536,6 +536,7 @@ The following tools are available in the published npm package and fully functio
536
536
  | `get_inventory_anomalies` | Bins stuck in mid-inventory state — empty bins with locks, open count docs, orphaned lock codes |
537
537
  | `get_transfer_order_history` | Full TO history — creator, executor (resolved from LTAP.QNAME), and item detail; filterable by date range, status, movement type, material, `createdBy`, or `executedBy` |
538
538
  | `get_replenishment_needs` | Find forward-pick bins at or below a stock threshold — `defaultReplenishQty` param (default 50) used as fallback when no bin max qty is configured; flags bins with an open replenishment TO to avoid duplicate moves |
539
+ | `get_interim_zone_anomalies` | Detect positive stock stranded in interim/staging zones (types 999, 998, 902) — surfaces same-day, overnight, and multi-day strandings with likely cause per zone; use `minDaysStranded` to filter noise during active shifts |
539
540
 
540
541
  ---
541
542
 
@@ -677,7 +678,8 @@ sap-wm-mcp/
677
678
  │ ├── confirmTransferOrderSU.js ← confirm_transfer_order_su
678
679
  │ ├── cancelTransferOrder.js ← cancel_transfer_order
679
680
  │ ├── transferOrderHistory.js ← get_transfer_order_history
680
- └── replenishmentNeeds.js ← get_replenishment_needs
681
+ ├── replenishmentNeeds.js ← get_replenishment_needs
682
+ │ └── interimZoneAnomalies.js ← get_interim_zone_anomalies
681
683
  ├── .env.example
682
684
  └── package.json
683
685
  ```
package/index.js CHANGED
@@ -28,8 +28,9 @@ import { getInventoryAnomalies } from './tools/inventoryAnomalies.js';
28
28
  import { getTransferOrderHistory } from './tools/transferOrderHistory.js';
29
29
  import { cancelTransferOrder } from './tools/cancelTransferOrder.js';
30
30
  import { getReplenishmentNeeds } from './tools/replenishmentNeeds.js';
31
+ import { getInterimZoneAnomalies } from './tools/interimZoneAnomalies.js';
31
32
 
32
- const server = new McpServer({ name: 'sap-wm-mcp', version: '0.2.6' });
33
+ const server = new McpServer({ name: 'sap-wm-mcp', version: '0.2.8' });
33
34
 
34
35
  // Tool 1 — get_bin_status
35
36
  server.tool(
@@ -457,6 +458,27 @@ server.tool(
457
458
  }
458
459
  );
459
460
 
461
+ // Tool 22 — get_interim_zone_anomalies
462
+ server.tool(
463
+ 'get_interim_zone_anomalies',
464
+ 'Detect positive stock stranded in interim/staging zones (types 999, 998, 902) where stock should only pass through briefly. Surfaces same-day, overnight, and multi-day strandings with likely cause per zone type. Run when stock appears to be lost or unaccounted for, or as part of end-of-shift reconciliation.',
465
+ {
466
+ warehouse: z.string().describe('Warehouse number e.g. 102'),
467
+ interimTypes: z.array(z.string()).optional().default(['999', '998', '902']).describe('Interim storage types to check — default [999, 998, 902]'),
468
+ minDaysStranded: z.number().optional().default(0).describe('Only return stock stranded for at least this many days. Default 0 = include same-day strandings.'),
469
+ material: z.string().optional().describe('Narrow to a specific material e.g. TG0001'),
470
+ top: z.number().optional().default(100).describe('Max quants to scan (scans top * 3 internally to account for client-side filtering)')
471
+ },
472
+ async (params) => {
473
+ try {
474
+ const result = await getInterimZoneAnomalies(params);
475
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
476
+ } catch (err) {
477
+ return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
478
+ }
479
+ }
480
+ );
481
+
460
482
  const transport = new StdioServerTransport();
461
483
  await server.connect(transport);
462
484
  console.error('SAP WM MCP Server running (stdio)...');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sap-wm-mcp",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "MCP server for SAP Classic Warehouse Management — connects AI agents to S/4HANA WM via a custom RAP OData V4 service. For systems where EWM is not active.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -0,0 +1,82 @@
1
+ import { s4hGet } from '../lib/s4hClient.js';
2
+ import { esc } from '../lib/sanitize.js';
3
+
4
+ const BASE = `/sap/opu/odata4/iwbep/all/srvd/sap/zsd_wmmcpservice/0001/WMWarehouseStock`;
5
+
6
+ const INTERIM_CAUSE = {
7
+ '999': 'SU/GI interim — GI posted before TO confirmation; confirm open TO or create reversal',
8
+ '998': 'GR interim — goods receipt without putaway TO; check open TRs and create putaway',
9
+ '902': 'GR staging (WE-ZONE) — putaway TO not yet created or confirmed; check GR monitor',
10
+ };
11
+
12
+ export async function getInterimZoneAnomalies({
13
+ warehouse,
14
+ interimTypes = ['999', '998', '902'],
15
+ minDaysStranded = 0,
16
+ material,
17
+ top = 100
18
+ }) {
19
+ const typeFilter = interimTypes.map(t => `StorageType eq '${esc(t)}'`).join(' or ');
20
+ const filters = [
21
+ `WarehouseNumber eq '${esc(warehouse)}'`,
22
+ `(${typeFilter})`
23
+ ];
24
+ if (material) filters.push(`Material eq '${esc(material)}'`);
25
+
26
+ const path = `${BASE}?$filter=${encodeURIComponent(filters.join(' and '))}&$top=${top * 3}`;
27
+ const data = await s4hGet(path);
28
+ const rows = data.value ?? [];
29
+
30
+ const today = new Date();
31
+
32
+ const stranded = rows
33
+ .filter(r => parseFloat(r.TotalStock ?? 0) > 0)
34
+ .map(r => {
35
+ const daysSinceMove = r.LastMovementDate
36
+ ? Math.floor((today - new Date(r.LastMovementDate)) / 86400000)
37
+ : null;
38
+ const ageFlag = daysSinceMove === null ? 'unknown'
39
+ : daysSinceMove === 0 ? 'same-day'
40
+ : daysSinceMove === 1 ? 'overnight'
41
+ : 'multi-day';
42
+ return {
43
+ storageType: r.StorageType,
44
+ bin: r.StorageBin,
45
+ material: r.Material?.trimStart?.() ?? r.Material,
46
+ plant: r.Plant,
47
+ qty: parseFloat(r.TotalStock ?? 0),
48
+ uom: r.UnitOfMeasure,
49
+ lastMove: r.LastMovementDate ?? 'unknown',
50
+ daysSinceMove,
51
+ ageFlag,
52
+ likelyCause: INTERIM_CAUSE[r.StorageType] ?? 'Positive stock in interim zone — investigate movement chain'
53
+ };
54
+ })
55
+ .filter(r => (r.daysSinceMove ?? 0) >= minDaysStranded)
56
+ .sort((a, b) => (b.daysSinceMove ?? 0) - (a.daysSinceMove ?? 0));
57
+
58
+ const byType = {};
59
+ for (const r of stranded) {
60
+ if (!byType[r.storageType]) byType[r.storageType] = { count: 0, totalQty: 0 };
61
+ byType[r.storageType].count++;
62
+ byType[r.storageType].totalQty += r.qty;
63
+ }
64
+
65
+ return {
66
+ count: stranded.length,
67
+ truncated: rows.length === top * 3,
68
+ warehouse,
69
+ filters: {
70
+ interimTypes,
71
+ minDaysStranded,
72
+ material: material ?? 'all'
73
+ },
74
+ summary: {
75
+ multiDay: stranded.filter(r => r.ageFlag === 'multi-day').length,
76
+ overnight: stranded.filter(r => r.ageFlag === 'overnight').length,
77
+ sameDay: stranded.filter(r => r.ageFlag === 'same-day').length,
78
+ },
79
+ byStorageType: byType,
80
+ stranded
81
+ };
82
+ }