File size: 4,772 Bytes
924a755
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""AutoSource warehouse MCP server (plan §7.1).

The ONLY component that touches the SQLite DB. Exposes exactly the five
contracted tools. `cost_floor` and internal thresholds never leave this process
via supplier-facing tools (a separate _floor lookup exists for the in-process
vendor validator, which is part of the simulation's ground truth, not an agent).

Run standalone (stdio transport):  python -m mcp_server.server
"""
from __future__ import annotations

import sqlite3
import sys
import uuid
from datetime import datetime, timezone
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from mcp.server.fastmcp import FastMCP  # noqa: E402

from core.config import db_path  # noqa: E402

mcp = FastMCP("autosource-warehouse")


def _conn() -> sqlite3.Connection:
    conn = sqlite3.connect(db_path())
    conn.row_factory = sqlite3.Row
    return conn


@mcp.tool()
def get_stock(sku: str) -> dict:
    """Current stock position for one SKU."""
    with _conn() as c:
        row = c.execute(
            "SELECT sku, qty_on_hand, reorder_point, status FROM inventory WHERE sku=?",
            (sku,),
        ).fetchone()
    if row is None:
        return {"error": f"unknown sku {sku!r}"}
    return dict(row)


@mcp.tool()
def list_low_stock() -> list[dict]:
    """All items below their reorder point (status='low')."""
    with _conn() as c:
        rows = c.execute(
            """SELECT sku, name, qty_on_hand, reorder_point, reorder_qty, list_price
               FROM inventory WHERE status='low'"""
        ).fetchall()
    return [dict(r) for r in rows]


@mcp.tool()
def get_suppliers_for(sku: str) -> list[dict]:
    """Vendors stocking a SKU, with their vendor memory. cost_floor is NEVER returned."""
    with _conn() as c:
        rows = c.execute(
            """SELECT s.vendor_id, s.name, s.persona, ss.base_price,
                      s.lead_time_days, s.reliability,
                      vm.last_price AS mem_last_price, vm.rounds AS mem_rounds,
                      vm.settled AS mem_settled, vm.reliability_obs AS mem_reliability_obs
               FROM supplier_skus ss
               JOIN suppliers s ON s.vendor_id = ss.vendor_id
               LEFT JOIN vendor_memory vm ON vm.vendor_id = ss.vendor_id AND vm.sku = ss.sku
               WHERE ss.sku = ? AND ss.in_stock = 1
               ORDER BY ss.base_price ASC""",
            (sku,),
        ).fetchall()
    return [dict(r) for r in rows]


@mcp.tool()
def log_purchase_order(sku: str, vendor_id: str, qty: int, unit_price: float,
                       approved_by: str) -> dict:
    """Write an approved PO and mark the item on-order. Only called after human approval."""
    po_id = f"PO-{datetime.now(timezone.utc).strftime('%Y%m%d')}-{uuid.uuid4().hex[:8].upper()}"
    total = round(qty * unit_price, 2)
    with _conn() as c:
        c.execute(
            "INSERT INTO purchase_orders VALUES (?,?,?,?,?,?,?,?,?)",
            (po_id, sku, vendor_id, qty, unit_price, total, "created",
             datetime.now(timezone.utc).isoformat(), approved_by),
        )
        c.execute("UPDATE inventory SET status='on_order' WHERE sku=?", (sku,))
    return {"po_id": po_id, "status": "created", "total": total}


@mcp.tool()
def update_vendor_memory(vendor_id: str, sku: str, last_price: float, rounds: int,
                         settled: bool, reliability_obs: float) -> dict:
    """Persist negotiation outcome so future runs learn vendor behavior."""
    with _conn() as c:
        c.execute(
            """INSERT INTO vendor_memory (vendor_id, sku, last_price, rounds, settled,
                                          reliability_obs, updated_at)
               VALUES (?,?,?,?,?,?,?)
               ON CONFLICT(vendor_id, sku) DO UPDATE SET
                 last_price=excluded.last_price, rounds=excluded.rounds,
                 settled=excluded.settled, reliability_obs=excluded.reliability_obs,
                 updated_at=excluded.updated_at""",
            (vendor_id, sku, last_price, rounds, int(settled), reliability_obs,
             datetime.now(timezone.utc).isoformat()),
        )
    return {"ok": True}


def get_cost_floor(vendor_id: str) -> float:
    """Ground-truth floor for the deterministic vendor validator (plan §9).

    NOT an MCP tool — never exposed to any agent. Imported only by the
    negotiation engine's validator, which plays the vendor's own 'books'.
    """
    with _conn() as c:
        row = c.execute(
            "SELECT cost_floor FROM suppliers WHERE vendor_id=?", (vendor_id,)
        ).fetchone()
    if row is None:
        raise KeyError(f"unknown vendor {vendor_id!r}")
    return float(row["cost_floor"])


if __name__ == "__main__":
    mcp.run()  # stdio transport