Skip to main content
Glama
AI-Product-Allen-Yu

Korral StoreLink MCP Server

README.md
# Korral StoreLink MCP Server

A standalone [Model Context Protocol](https://modelcontextprotocol.io) server, written in
TypeScript with the official [`@modelcontextprotocol/sdk`](https://www.npmjs.com/package/@modelcontextprotocol/sdk),
that exposes the **StoreLink** backend of the Korral grocery chain to an AI agent.

It ships **two collapsed, semantic tools** designed for a stock-out / replenishment workflow:

| Tool | Purpose |
| --- | --- |
| `get_store_inventory_and_sales(store_id, sku)` | One call returns on-hand inventory **and** trailing-24h POS sales, plus a pre-computed `demand_gap` and `stockout_risk`. |
| `create_replenishment_order(store_id, sku, quantity)` | Fires a mock `POST` to raise a replenishment (restock) order and returns the confirmation. |

The transport is **Stdio** — the server reads JSON-RPC from stdin and writes to stdout, which is
how MCP hosts (Claude Desktop, IDEs, agent runtimes) launch local servers.

## Quick start

```bash
npm install
npm test        # vitest — unit tests for the tool logic
npm run build   # esbuild — bundles a single production file to dist/index.js
npm start       # runs dist/index.js over stdio
```

During development you can run the TypeScript source directly without building:

```bash
npm run dev     # tsx src/index.ts
```

### Wiring it into an MCP host

```json
{
  "mcpServers": {
    "korral-storelink": {
      "command": "node",
      "args": ["E:/temp/duvo-mcp-assignment/dist/index.js"]
    }
  }
}
```

## Mock data

The mock backend (`src/mockData.ts`) tracks one SKU — **8847291, Madeta butter 250g** — across two
stores, deliberately seeded to show contrasting demand gaps:

| Store | On-hand | POS (last 24h) | Demand gap | Risk |
| ----- | ------- | -------------- | ---------- | -------- |
| 47    | 2       | 10             | **8**      | high     |
| 102   | 12      | 14             | **2**      | moderate |

`demand_gap = units_sold_last_24h - on_hand`. Store 47 sold far more than it has on the shelf — a
clear restock candidate; Store 102 is keeping pace.

## Architectural choices & tradeoffs

### 1. Collapsed, semantic tools over a 1:1 API mirror

In the real StoreLink API, inventory and POS sales live behind **two separate endpoints**. A naïve
MCP server would expose them as two tools, forcing the agent to make two round-trips, hold both
payloads in context, and do the subtraction itself.

Instead, `get_store_inventory_and_sales` **collapses both endpoints into one payload** and
**pre-computes `demand_gap` and `stockout_risk`**. The benefits for an agent:

- **Fewer tokens / less context churn** — one tool result instead of two, with only the fields that
  matter for the decision.
- **Fewer round-trips** — one tool call to assess a store/SKU instead of two-then-reason.
- **Less room for error** — the server, not the model, does the arithmetic and risk bucketing.

**Tradeoff:** the tool is opinionated and less general-purpose than raw endpoints. A different
consumer that wanted *only* inventory now over-fetches POS data, and the `stockout_risk` thresholds
(`gap >= 5` → high, `>= 1` → moderate) are baked into the server rather than chosen by the caller.
For an agent-first tool surface this is the right trade — we optimize for the agent's context window
and decision quality, not for maximal API flexibility.

### 2. Action tool returns a confirmation, not a raw HTTP echo

`create_replenishment_order` models a side-effecting `POST`. It returns a small, structured
confirmation (`order_id`, `status: "submitted"`, echoed inputs) rather than a verbose HTTP response,
so the agent gets just enough to confirm success and report back.

### 3. Schema validation with Zod

Tool inputs are validated with Zod schemas registered through the SDK. Each field carries a
`.describe()` string that the SDK surfaces in the JSON Schema sent to the model — so the agent learns
what `store_id`, `sku`, and `quantity` mean. `quantity` is constrained to a **positive integer** both
in the schema and defensively in the handler.

### 4. Pure functions + thin server wiring (testability)

The tool logic (`getStoreInventoryAndSales`, `createReplenishmentOrder`) is implemented as **pure,
exported functions**; `buildServer()` only wires them to the MCP transport. This keeps the
Vitest suite fast and transport-free — it tests behavior directly, with no stdio mocking.

### 5. esbuild → single bundled file

`npm run build` (see `build.mjs`) bundles everything — including the SDK and Zod — into one
self-contained, minified `dist/index.js` with a `#!/usr/bin/env node` shebang. An MCP host can run it
with a single `node dist/index.js` command, no `node_modules` required at the deploy target. Node
built-ins are left external since the runtime provides them.

**Tradeoff:** bundling dependencies makes the output larger (~330 KB) and pins them at build time, but
removes any runtime install step — the right call for a server meant to be dropped into a host config.

## Project layout

```
.
├── build.mjs            # esbuild bundle script -> dist/index.js
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── src
│   ├── index.ts         # MCP server: schemas, tools, stdio wiring
│   ├── index.test.ts    # Vitest unit tests
│   └── mockData.ts      # Mock StoreLink inventory + POS data
└── dist
    └── index.js         # Bundled production server (generated)
```

TDQS

A3.9/5.0

Scored across 2 tools

Disambiguation5/5

The two tools have clearly distinct purposes: one retrieves inventory and sales data, the other creates a replenishment order. There is no overlap or ambiguity.

Naming Consistency5/5

Both tools follow a consistent verb_noun pattern in snake_case: get_store_inventory_and_sales and create_replenishment_order.

Tool Count3/5

Only 2 tools is minimal, but acceptable for a narrowly scoped replenishment server. However, typical integrations might benefit from a few more tools.

Completeness2/5

The server covers the core check-and-create cycle but lacks essential operations like listing, updating, or canceling replenishment orders, which agents may need.

Maintenance

ActivityInactive
ResponsivenessNo issues