Skip to content

Alembic

Field Value
Owner Olger Avila (IT)
Status live
Runs on Windows Server, "Jetty box" (192.168.1.6, office LAN)
Connectivity Public HTTPS via Caddy reverse proxy, NOT via Cloudflare Tunnel
Hostname mcp.alquemis.com
Repository Alquemis-Automation/Alembic (private) — moved from Olger's personal account, renamed from "MCP-Internal"

What it is

A read-only MCP server giving Claude (and other authorized MCP clients) access to two Alquemis data sources:

  • Oracle INTG4PDB — company catalog, global config, the Compras app's purchase requests/POs, and item/BOM master data (schema BIO).
  • SAP Business One — mostly via a local Oracle cache (BIO.SB1_* tables, refreshed by ETL at 3am; up to ~24h stale). Live SAP B1 reads are also possible for verification: get_supplier_sap fetches straight from SAP B1, with the HTTPS call made by Oracle (the BIO.PK_SAPB1 package), not by the MCP process. The standalone Python Service Layer client (sap_client.py) exists but remains unused by any active tool.

Built with FastMCP (mcp[cli]), Python 3.13. Publishes thirteen read-only JSON-RPC tools; nothing creates, modifies, or cancels data in either system.

Athanor built its own SAP B1 connector/MCP independently in the same window this one was being finished (2026-07-09) — see connectors/sap-b1/README.md for that history. Olger's is the canonical implementation going forward; Athanor's was retired to avoid duplicate work.

Tools published

Tool Function
list_companias(estado='A', etl='S', tipo=None) List group companies (initials, alias, name, tax ID)
get_compania(compania) Detail for one company, by initials or alias
get_config(config) Value of a non-sensitive global config entry (sensitive keys are blocked — see security notes)
get_open_purchase_orders(compania, proveedor=None, grupo=None, moneda=None) Open POs for a supplier, optionally filtered by supplier group / currency
get_purchase_order_lines(compania, doc_entry) Lines of a PO by DocEntry
get_purchase_order_full(compania, doc_entry) Combined SAP + INT_SOLCOMP view, including inconsistencies
list_supplier_groups(compania) Supplier groups of a company, with supplier counts
get_supplier_sap(compania, proveedor) Live SAP B1 lookup of a supplier (bypasses the cache); returns SAP vs. local snapshot + differences, for verifying suspected drift
list_items_produccion(compania, prefijo=None, estado=None) Producible items (those with a BOM) of a producing company
get_item(compania, codigo) Item detail by exact code
get_bom(compania, codigo) Bill of materials (direct components) of a producible item
find_products_using(compania, componente, estado=None) Reverse BOM: products that use a given component (MP/ME/CF/MO)
get_item_purchase_history(compania, item) Effective purchase history of an item by supplier, from posted A/P invoices (SB1_INVOICES type CFC, cancelled excluded; not open POs/quotes) — invoice count, last purchase, and last/min/max/avg unit price, grouped by currency

How it's reached

Endpoint: https://mcp.alquemis.com/<TOKEN>/, MCP streamable-HTTP transport.

Claude ──HTTPS──► mcp.alquemis.com/<TOKEN>/  (443, TLS)
                        │  201.202.107.128 → NAT → 192.168.1.6
                        ▼
                     Caddy
                        │  1. terminates TLS (Let's Encrypt)
                        │  2. validates the token in the URL path
                        │  3. rewrites Host → localhost:8000
                        ▼
              127.0.0.1:8000  (FastMCP / Uvicorn, loopback-only)
                        │  4. validates Host (anti DNS-rebinding)
                        │  5. dispatches the requested tool
                        ▼
              Oracle (192.168.1.5:1521) or, optionally, SAP B1 Service Layer

Security notes: - The Python MCP process only listens on loopback (127.0.0.1:8000) — never directly reachable from the network. Caddy is the only component with public ports (80/443). - The token is embedded in the URL path (handle_path /<TOKEN>/*); any request without it gets a 404, not a 401 — the service's existence isn't revealed to unauthenticated requests. - FastMCP rejects requests with an unexpected Host header (anti DNS-rebinding), which is why Caddy must rewrite Host to localhost:8000 before proxying. - Oracle access uses a dedicated read-only user (BIOMCP) with grants limited to select/ execute on specific objects — even a compromised MCP process can't write to Oracle. - get_config enforces a denylist: it refuses to return sensitive keys — SAP Service-Layer credentials (SL_*), per-company CompanyDB, electronic-invoice keys (CLAVE_API_FE_*), the Oracle wallet (WALLET_*), and any token/user entries — raising an error instead of the value. A stopgap until those secrets are moved out of BIO.SB1_GENERAL (Oracle Wallet, under evaluation by Olger).

Configuration

.env file on the server (not versioned) holds Oracle/SAP connection parameters. SAP B1 Service Layer credentials are read dynamically from the Oracle table BIO.SB1_GENERAL, not hardcoded anywhere in the app — and those credential rows are shielded from the get_config tool by the denylist (see security notes).

Dependencies

  • Oracle INTG4PDB at 192.168.1.5:1521 (Easy Connect: 192.168.1.5:1521/INTG4PDB.Bioland.local)
  • SAP B1 Service Layer at sap-bioland-sl.skyinone.net:50000 (/b1s/v2/) — client exists, not currently used by any active tool
  • Caddy + Let's Encrypt (auto-renewed TLS for mcp.alquemis.com)
  • Windows services via NSSM (caddy, mcp-sapb1)
  • Consumer: Mycelium's procurement agent (Hyphae / "Compras Agent")

Known limitations

  • Read-only. No creates, updates, or cancellations of POs, business partners, or items.
  • Cache staleness. Most queries hit the local Oracle cache (ETL at 3am) — up to 24h behind SAP B1, not real-time. get_supplier_sap is the exception (live SAP read via BIO.PK_SAPB1).
  • Purchasing + production scope. Covers companies, config, purchase orders, suppliers, items, and BOMs — no sales orders, invoices, inventory movements, etc.
  • Single shared token. Auth is one bearer token in the URL, not per-user identity — no rotation automation.