inventory-ops-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@inventory-ops-mcpInvestigate oversell for SKU-STALE-CANCEL-003 and release stale reservation."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
inventory-ops-mcp
A remotely hosted, TypeScript MCP server that lets a commerce operations person investigate and resolve inventory oversells — without needing an engineer to dig through the database on their behalf.
Built for the AI-First Commerce Operations Challenge: the MCP server is the product, not a wrapper bolted onto one. An AI client (Claude Desktop, Claude Code, or any MCP-compatible agent) connects to it remotely and uses its tools to diagnose, and where it's safe to, fix the problem itself.
Hosted MCP URL: https://inventory-mcp-xd0j.onrender.com/mcp
The problem this solves
An oversell happens when a SKU shows more stock reserved across open orders than it actually has:
available_stock = total_stock - active_reservationsWhen available_stock goes negative, the numbers the app shows and the
numbers the warehouse can actually fulfill have drifted apart — a
trust-eroding place for a commerce business to be in, and today it usually
means an engineer manually cross-referencing orders, reservations, and
payment state to figure out why.
There are two distinct root causes, and they require different responses:
Cause | What happened | Safe to auto-fix? |
Stale reservation | An order was cancelled or expired, but the stock it was holding was never released back. | Yes — releasing it is a safe, mechanical correction. |
Genuine conflict | Two or more legitimate orders (open or paid) together exceed available stock. | No — needs a human judgment call (who gets fulfilled, who gets delayed). |
The whole design of this MCP server exists to tell these two cases apart and act accordingly — fix what's safe to fix automatically, and flag everything else instead of guessing.
Related MCP server: fix-mcp
Why Claude for testing this MCP
I tested the deployed server using Claude (both connected as a live MCP connector and via Claude Desktop/Code) rather than a custom test harness:
The thing under test is tool-calling behavior, not just the API. The brief's evaluation emphasis is on whether an AI consumer can use the MCP effectively — tool descriptions, input schemas, and safety boundaries only matter if a real model reads them and acts correctly. A scripted client would validate the HTTP contract but not whether an LLM picks the right tool for an ambiguous ops question.
Visible reasoning. Testing through a chat interface makes the model's tool selection and argument choices visible turn by turn — which is exactly what surfaces a misleading description or an unsafe assumption before a real ops person hits it.
No extra client to build. MCP support is native to Claude, which kept the 3-4 hour budget focused on the server itself rather than tooling to test it.
It found a real bug. Testing conversationally is what surfaced the
analyze_inventory_issue/detect_stale_reservationsdefect documented below — a scripted happy-path test likely wouldn't have exercised that path the same way.
How it fits together
Ops person --(plain English)--> AI client (Claude Desktop / Claude Code)
|
| MCP over Streamable HTTP
v
inventory-ops-mcp (this server)
+----------------------------------+
| Tools (src/tools/inventory) |
| - get_stock_and_reservations |
| - release_stale_reservation |
| - analyze_inventory_issue |
| - detect_stale_reservations |
+-----------------+------------------+
| pg.Pool (shared)
v
Postgres (Neon, synthetic data)The AI never touches the database directly — it can only act through the tools below, each with an explicit, code-enforced boundary on what it's allowed to do.
Tools reference
These are documented here for review, without being invoked while writing this file — see "Test flow" below for how to actually exercise them.
get_stock_and_reservations(sku: string)
Read-only. Returns total stock, every active reservation for the SKU
(joined with each reservation's order status), computed available stock,
and an isOversold flag. Always the entry point for an investigation — the
order status on each reservation is what determines what can legitimately
happen next.
release_stale_reservation(reservationId: number)
State-changing, tightly bounded. Releases exactly one reservation by
ID. Only permitted when that reservation's order is cancelled or
expired — enforced in code, not left to the model's judgment. A
reservation on an open, paid, or fulfilled order is always refused,
regardless of what's requested. Confirmed by direct testing (see
Verification).
analyze_inventory_issue(sku: string)
Intended to give a higher-level diagnosis than the raw stock/reservation dump. Currently broken — see Known Issues.
detect_stale_reservations(sku: string)
Intended to specifically flag reservations that should have been released. Currently broken — see Known Issues, same root cause as above.
There is no tool that scans/lists across all SKUs at once (see Exclusions) — every tool operates on one SKU or one reservation at a time.
Project structure
db/
migrations/
001_init.sql tables, enums, indexes - add 002_*.sql etc. as the
schema grows; applied automatically, in order,
every time the process starts
src/
config.ts env config (DATABASE_URL, PORT)
db/
pool.ts single shared pg.Pool for the whole process,
Neon-ready (auto-detects sslmode=require)
migrate.ts applies db/migrations/*.sql not yet recorded
migrateCli.ts `npm run db:migrate` - run migrations standalone
seed.ts `npm run db:seed` - loads synthetic demo data
queries/
inventory.ts stock/reservation SQL
audit.ts audit_log writes
tools/
inventory/
getStockAndReservations.ts
releaseStaleReservation.ts
analyzeInventoryIssue.ts
detectStaleReservations.ts
index.ts registers all inventory tools
index.ts top-level tool registry - add new domain
categories here later (e.g. tools/orders/)
server/
createMcpServer.ts builds one McpServer instance per request
httpServer.ts Express + Streamable HTTP transport
index.ts entrypoint - runs migrations, then servesTools are grouped by domain category (tools/inventory/) rather than
dumped in one file. Adding a second category later means creating
tools/orders/, registering its tools in its own index.ts, and adding
one line to src/tools/index.ts — nothing existing needs to change.
Architecture notes
Stateless HTTP, always reachable. The MCP endpoint uses the SDK's
Streamable HTTP transport in stateless mode — no session map, no
initialize-then-reuse handshake required. Every request is handled
independently: a fresh, short-lived McpServer + transport pair is
created per request and torn down once the response closes. A client can
call the endpoint immediately with no setup step, and it works cleanly
with multiple server instances behind a load balancer, since nothing about
handling one request depends on another having happened first.
One shared database connection pool. Statelessness at the protocol
layer is deliberately not extended to the database layer. src/db/pool.ts
creates exactly one pg.Pool for the entire process, imported by every
query module. Every request borrows a connection from this pool rather
than opening its own — a new pool per request would exhaust Postgres
connections almost immediately under any real load.
Safety is enforced in code, not prompted for. release_stale_reservation
does not rely on the tool description alone to keep the AI from releasing
the wrong reservation — the order-status check happens in the handler
itself before any write occurs. Whatever the AI requests, a reservation on
a paid or open order is refused every time.
Migrations are idempotent and run on boot. src/db/migrate.ts tracks
applied files in a schema_migrations table and only runs what's new.
Pointing DATABASE_URL at a brand-new Neon database and starting the
server is enough to build the entire schema — no manual psql step.
Setup
The hosted URL above is sufficient to review this submission — no local setup is required. To run it locally instead:
Create a free Neon Postgres database (or use any Postgres instance). Copy
.env.exampleto.envand paste your connection string intoDATABASE_URL.Install dependencies and start the server:
npm install npm run devOn boot this applies
db/migrations/001_init.sqlautomatically — safe to restart repeatedly, since already-applied migrations are skipped.Load the synthetic demo data:
npm run db:seedThis truncates and reloads the inventory tables, so it's safe to repeat whenever you want a clean, known state.
Point an MCP client at
http://localhost:3000/mcp(or the hosted URL above). For Claude Code:claude mcp add --transport http inventory-ops https://inventory-mcp-xd0j.onrender.com/mcpNo further setup is required — the connection is stateless, so there's no session to initialize separately.
Test flow (no local setup required)
Connect any MCP client to the hosted URL above, then try the following. These use the actual seed data currently loaded on the hosted database.
1. Confirmed-healthy read
"Check stock for SKU-BULK-1."
Sanity check that the read path works and returns believable numbers. Bulk
SKUs (SKU-BULK-1 through SKU-BULK-100) are randomly generated on each
reseed, so exact numbers will vary — use this path for realistic-scale
exploration, not exact assertions.
2. Deterministic, unresolvable oversell (needs a human)
"Is SKU-OVERSELL-003 oversold? If so, try to fix it."
SKU-OVERSELL-003 (4K Monitor, stock 8) has one paid order (5) and one
open order (6) — 11 reserved against 8 in stock, oversold by 3. Neither
order is cancelled/expired, so release_stale_reservation should be
refused for both, and the correct outcome is "this needs a human
decision," not a forced fix. The same pattern holds for
SKU-OVERSELL-001, -002, -004, and -005.
3. Deterministic, auto-fixable oversell
"Is SKU-MIXED-001 oversold? If it's safe to fix automatically, do it."
SKU-MIXED-001 (Smartphone, stock 20) has one legitimate open order (8)
plus two stale reservations — one on a cancelled order (10), one on an
expired order (7) — 25 reserved against 20 in stock, oversold by 5.
Releasing both stale reservations should bring it back to a healthy state
(8 reserved, 12 available). This is the best single test of the full
diagnose → resolve loop, including whether the model completes both
releases rather than stopping after one.
4. Safety boundary, tested directly
"Release the reservation for order ORD-OVR-1."
ORD-OVR-1 is a paid order. This should be refused with a clear
explanation, regardless of how the request is phrased — this is the
guardrail that matters most for the "safety and operational
considerations" the brief asks about.
5. Not-found handling
"Check stock for SKU-DOES-NOT-EXIST."
Should return a clean "no product found," not an error or a fabricated answer.
6. Known-bug reproduction (documented, not hidden)
"Analyze SKU-BULK-1 for inventory issues."
This currently reproduces the analyze_inventory_issue bug described
below — included here deliberately, since a submission that only shows
happy paths is less convincing than one that shows what was actually
found under test.
The seed data (for reference)
The hosted database currently contains three data sets. Referencing it here so a reviewer can understand why specific SKUs behave the way they do, without needing DB access:
100 randomized bulk SKUs (
SKU-BULK-1..100) with random stock and 600 randomly-distributed orders across all five order statuses — a realistic, noisy dataset for scale/exploration testing. Not deterministic across reseeds.5 deterministic oversell SKUs (
SKU-OVERSELL-001..005) — each has exactly one paid + one open order that together exceed stock, with no cancelled/expired order involved. Permanently unresolvable byrelease_stale_reservation— the primary test of the refusal path.1 deterministic mixed SKU (
SKU-MIXED-001) — one legitimate open order plus two stale reservations (cancelled + expired) — the primary test of the resolve path.
INSERT INTO products (sku, name, total_stock)
SELECT 'SKU-BULK-' || i, 'Bulk Product ' || i, (random() * 80 + 20)::int
FROM generate_series(1, 100) i;
INSERT INTO orders (order_id, sku, quantity, status)
SELECT 'ORD-BULK-' || i, 'SKU-BULK-' || ((i % 100) + 1),
(random() * 6 + 1)::int,
(ARRAY['open','paid','cancelled','expired','fulfilled'])[floor(random()*5)+1]::order_status
FROM generate_series(1, 600) i;
INSERT INTO reservations (order_id, sku, quantity, status)
SELECT o.order_id, o.sku, o.quantity, 'active'::reservation_status
FROM orders o
WHERE o.status IN ('open'::order_status, 'paid'::order_status) AND random() > 0.2;
INSERT INTO reservations (order_id, sku, quantity, status)
SELECT o.order_id, o.sku, o.quantity, 'active'::reservation_status
FROM orders o
WHERE o.status IN ('cancelled'::order_status, 'expired'::order_status) AND random() > 0.5;
INSERT INTO products (sku, name, total_stock) VALUES
('SKU-OVERSELL-001', 'Gaming Mouse', 10),
('SKU-OVERSELL-002', 'Gaming Headset', 15),
('SKU-OVERSELL-003', '4K Monitor', 8),
('SKU-OVERSELL-004', 'SSD Drive', 12),
('SKU-OVERSELL-005', 'Graphics Card', 5);
INSERT INTO orders (order_id, sku, quantity, status) VALUES
('ORD-OVR-1', 'SKU-OVERSELL-001', 6, 'paid'::order_status),
('ORD-OVR-2', 'SKU-OVERSELL-001', 7, 'open'::order_status),
('ORD-OVR-3', 'SKU-OVERSELL-002', 10, 'paid'::order_status),
('ORD-OVR-4', 'SKU-OVERSELL-002', 9, 'open'::order_status),
('ORD-OVR-5', 'SKU-OVERSELL-003', 5, 'paid'::order_status),
('ORD-OVR-6', 'SKU-OVERSELL-003', 6, 'open'::order_status),
('ORD-OVR-7', 'SKU-OVERSELL-004', 8, 'paid'::order_status),
('ORD-OVR-8', 'SKU-OVERSELL-004', 7, 'open'::order_status),
('ORD-OVR-9', 'SKU-OVERSELL-005', 3, 'paid'::order_status),
('ORD-OVR-10','SKU-OVERSELL-005', 4, 'open'::order_status);
INSERT INTO reservations (order_id, sku, quantity, status)
SELECT order_id, sku, quantity, 'active'::reservation_status
FROM orders WHERE order_id LIKE 'ORD-OVR-%';
INSERT INTO products (sku, name, total_stock) VALUES ('SKU-MIXED-001', 'Smartphone', 20);
INSERT INTO orders (order_id, sku, quantity, status) VALUES
('ORD-MIX-1', 'SKU-MIXED-001', 8, 'open'::order_status),
('ORD-MIX-2', 'SKU-MIXED-001', 10, 'cancelled'::order_status),
('ORD-MIX-3', 'SKU-MIXED-001', 7, 'expired'::order_status);
INSERT INTO reservations (order_id, sku, quantity, status) VALUES
('ORD-MIX-1', 'SKU-MIXED-001', 8, 'active'::reservation_status),
('ORD-MIX-2', 'SKU-MIXED-001', 10, 'active'::reservation_status), -- stale
('ORD-MIX-3', 'SKU-MIXED-001', 7, 'active'::reservation_status); -- staleProduct decisions
Scope: one workflow, fully handled. Oversell detection and resolution, not a general commerce backend. Chosen because it's a small, self-contained problem that still requires real cross-referencing (stock vs. reservations vs. order state) rather than a single-table lookup.
The resolution boundary is enforced in code, not in the prompt.
release_stale_reservationchecks order status directly in the handler before any write — refusing paid/open orders isn't something the model is trusted to remember, it's structurally impossible for it to bypass.Genuine conflicts are surfaced, not forced. Where a fix would require a business judgment call (which order gets fulfilled), the correct behavior is flagging that clearly rather than picking for the human.
Assumptions
One reservation per order (no partial reservations or multi-line orders).
"Active reservation" and order status are tracked independently (
reservations.status,orders.status) rather than derived from one another — this is what allows a reservation to become stale in the first place, and is what makes the stale-reservation bug representable in the schema at all.Synthetic data only — no real customers, inventory numbers, or credentials anywhere in this project.
Exclusions (explicit)
No bulk/fleet-wide scan tool. Every tool operates on one SKU or reservation at a time; there's no
list_oversold_skusyet. Discovered as a real gap during testing — a natural next addition, deliberately left out to keep this pass small.No physical stock-count reconciliation — a related but separate workflow, not built here.
Authentication, a frontend, and CI/CD — excluded per the assignment brief.
Multi-SKU orders — one line item per order, to keep the schema simple; a real commerce system would need an
order_itemstable.
Verification
Beyond the type checker (npx tsc --noEmit), the core logic was run
end-to-end against a live Postgres instance, and the deployed server was
exercised directly:
Migrations applied cleanly on a fresh database and were correctly skipped on re-run.
The diagnosis math was checked against every deterministic scenario (
SKU-OVERSELL-001..005,SKU-MIXED-001) — computed available stock and theisOversoldflag matched what each scenario was designed to produce.release_stale_reservationwas exercised directly: both stale reservations (cancelled-order and expired-order cases) released successfully and brought their SKU back to a healthy state, while the same call against a reservation on a paid order was correctly refused with a clear message.Re-running a release against an already-released reservation was checked and handled cleanly (reported as already done, not an error).
The HTTP layer was tested with raw
curlrequests carrying nomcp-session-idheader at all —initialize,tools/list, and a realtools/callagainst an oversold SKU all succeeded, confirming the server is genuinely stateless rather than just tolerant of a missing session.
Known issues / remaining risk
analyze_inventory_issueanddetect_stale_reservationsboth throw a raw Postgres error —invalid input value for enum order_status: "failed"— on every SKU tested. This points to a query referencing an order status value ("failed") that isn't part of this database'sorder_statusenum (open, paid, cancelled, expired, fulfilled). Root cause is a schema/query mismatch, not a data problem. Left unresolved in this submission and disclosed here rather than hidden; the core diagnose/resolve loop (get_stock_and_reservations+release_stale_reservation) is unaffected and fully verified.No bulk-scan tool, as noted above — "show me everything currently oversold" isn't answerable in one call today.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseCqualityCmaintenanceMCP server that enables deployment and management of MCP servers through a simple configuration-based interface.Last updated1
- AlicenseBqualityBmaintenanceMCP server for AI-assisted trading operations, enabling agents to diagnose and resolve FIX, OMS, and venue incidents through controlled tools and human approval.Last updated38MIT
- Alicense-qualityCmaintenanceMCP server that provides tools for automating AWS Pricing Calculator estimate generation.Last updatedMIT
- Flicense-qualityCmaintenanceMCP server for infrastructure discovery and remote management, enabling SSH command execution, file transfer, log tailing, and machine/service inventory with a companion web dashboard.Last updated1
Related MCP Connectors
A paid remote MCP for hosted MCP server, built to return verdicts, receipts, usage logs, and audit-r
Remote MCP for A2A failure replay MCP, structured receipts, audit logs, and reviewer-ready evidence.
Hosted Amazon Seller and Vendor MCP server for Claude, ChatGPT, Cursor, Codex, Gemini, Copilot.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/MohdSaquib114/inventory-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server