Skip to main content
Glama
Ricoledan

korral-storelink-mcp

by Ricoledan

korral-storelink-mcp

An MCP server that lets a Duvo agent do a Korral category buyer's detective work — checking on-hand vs. POS, judging whether a store will be empty by afternoon, raising replenishment orders — against Korral's homegrown StoreLink API. Built on mcp-onprem-starter: stdio transport, a validated config, a closed error taxonomy, and a write gate.

StoreLink is not available to us yet, so the upstream is stubbed (src/mock/server.ts). The tool surface below — not the mock — is the actual deliverable: it's what an agent sees on every turn and what a human reviews in an audit log, and it is designed to work unchanged once a real UPSTREAM_BASE_URL is set.

Tool surface

5 tools, covering 8 StoreLink endpoints. The governing constraint is context: every tool definition costs tokens on every turn, so each tool below earns its place against a specific buyer question — see docs/decisions/ for the full reasoning and what was rejected.

The core design choice: rather than mirroring each REST endpoint 1:1, related endpoints are folded into workflow-shaped tools that return an answer plus its evidence, instead of raw payloads the agent would have to assemble and do arithmetic over itself. That arithmetic matters — a naive "on-hand ÷ recent sales rate" projection run in-context produces a different stockout time on every call. Here it's computed once, deterministically, in tested code (src/lib/depletion.ts), and returned as a reproducible number the agent relays rather than derives.

Tool

Type

Buyer question it answers

korral_list_authorized_stores

read

Which stores can I see, what time is it there, when do they close?

korral_check_stock_risk

read

Will this SKU run out today, and can a delivery beat it?

korral_list_recent_sales

read

That projection looks wrong — what actually sold?

korral_get_order_status

read

Did the order I raised go through / ship?

korral_raise_replenishment_order

write

Raise it — dry-run preview by default

Naming convention

korral_ + verb_noun, snake_case.

  • korral_, not storelink_. StoreLink is the implementation Korral happens to run today — the thing most likely to be replaced. Korral is the durable business domain, and the one a buyer reading an audit log recognizes.

  • Verb first, and every read starts list_/check_/get_ while the sole write starts raise_ — a reviewer scanning a log spots the one tool that mutates state from its first word alone. raise_ because it's the buyers' own word for the action, not create_.

  • No abbreviations except sku, which is genuine domain vocabulary.

Return shape

Every tool returns the same envelope (src/tools/shared.ts):

// success
{ "ok": true, "data": { /* tool-specific */ } }
// failure
{ "ok": false, "error": { "code": "STORE_NOT_AUTHORIZED", "message": "...", "remediation": "..." } }

Two shape conventions apply throughout, deliberately:

  • Lists are bounded and self-describing. Anything list-shaped (korral_list_recent_sales) returns { items, truncated, count_returned, count_total_available } rather than an unbounded array — the agent knows to narrow its query instead of assuming it saw everything.

  • Projections are never a bare number. korral_check_stock_risk returns a stockout window (earliest/latest), a confidence enum computed by explicit rules in code, data_quality_flags, and a plain-English caveat string — not a single confident timestamp. See ADR-0004.

1. korral_list_authorized_stores

  • In: none.

  • Out: per store — store_id, name, timezone, local_time_now, opening_hours_today, closes_in_minutes; plus count.

  • Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: true

  • "Empty by afternoon" is meaningless without local time and closing time. This is also how the agent discovers its scope — the response is exactly the deployment's keyring (see below), so it's honest whether that's one store or several.

2. korral_check_stock_risk — the headline tool

  • In: sku (required), store_id (see scoping rules below), lookback_days (default 14, max 28).

  • Out: identity (product_name, category, supplier_name) · state (on_hand_units, on_hand_as_of) · velocity (units_per_hour_trading as [low, high], basis, observation_days) · projection (projected_stockout_window, projected_stockout_confidence, will_stockout_before_close_today: true|false|unknown, units_short_by_close) · replenishment (supplier_lead_time_days, earliest_replenishment_arrival, arrival_beats_stockout) · evidence (per-day units sold, ≤28 rows) · data_quality_flags · caveat.

  • Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: true

  • Collapses what would otherwise be 4 separate calls (inventory, POS, SKU, supplier) into one deterministic answer. The evidence block is what lets a buyer check the projection without a second tool call — the whole reason this is one workflow tool instead of four thin ones.

3. korral_list_recent_sales — the escape hatch

  • In: sku (required), store_id, since (default 24h ago, max 7 days back), limit (default 100, max 500).

  • Out: items: { timestamp, units, transaction_id }[], count_returned, count_total_available, window_start, window_end, truncated.

  • Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: true

  • The one question check_stock_risk structurally can't answer: transaction shape. Sixty units as one catering order vs. sixty separate baskets calls for opposite decisions, and any aggregate destroys that distinction. Also the audit path when a buyer disputes a number.

4. korral_get_order_status

  • In: order_id (required), store_id.

  • Out: status, submitted_at, expected_arrival, sku, product_name, quantity_units.

  • Annotations: readOnlyHint: true · destructiveHint: false · idempotentHint: true · openWorldHint: true

  • Covers the cross-session follow-up ("did yesterday's order ship?") — also the state check that the write tool's own WRITE_OUTCOME_UNKNOWN remediation tells a caller to perform after an ambiguous write.

5. korral_raise_replenishment_order — the only write

  • In: sku (required), quantity_units (required), reason (required — the buyer's justification, lands in the audit log), store_id, confirm (default false), idempotency_key (optional, auto-derived).

  • Annotations: readOnlyHint: false · destructiveHint: false · idempotentHint: false · openWorldHint: true

    • destructiveHint: false is deliberate, not an oversight: a replenishment order is additive — it creates a record, it doesn't overwrite or delete one. The hint means "may irreversibly destroy data," not "has real-world consequences."

    • idempotentHint: false is the honest default: called twice, it creates two orders. Our idempotency key is an in-process cache that doesn't survive a restart, so claiming otherwise would be a lie the annotation tells the agent. Revisit only if StoreLink itself starts deduplicating.

  • Dry-run by default (confirm: false). The preview is built so a human can judge "should this order exist, at this size?" with no further tool call: the exact request payload, the resolved product_name (nobody can vet a bare SKU code), the justification snapshot (on-hand, velocity, stockout window, lead time, and days_of_cover_after_delivery — the single best sanity check, since 45 days of cover on a chilled product exposes an order-of-magnitude mistake that the raw quantity alone hides), a duplicate-open-order warning, and a quantity-sanity flag.

  • confirm: true is the agent asserting intent — not proof a human approved it. Unless Duvo's host application puts a real approval step in front of the confirmed call, this handshake is decorative. Said here plainly rather than left implicit. Two independent backstops hold regardless of what the agent asserts: ALLOW_WRITES must be set at the deployment level (defaults to false), and MAX_ORDER_UNITS is a hard server-side ceiling no confirm can bypass.

Deliberately not exposed

Cut

Why

get_sku, get_supplier

Metadata, not an answer — a lead time only matters against a projected stockout. Folded into check_stock_risk; standalone versions would just cost a round trip for a field the agent already has.

get_inventory (bare on-hand)

Cut for safety, not economy. A bare on-hand number is the most misleading fact in this domain — "300 units" reads as reassuring while omitting the sell rate that makes it meaningful. On-hand is always returned with velocity, never alone.

list_stores (all ~180)

Would advertise stores this deployment holds no key for, inviting a doomed cross-store loop. Replaced by korral_list_authorized_stores, which is honest about scope at any size.

A generic storelink_request(method, path, body) passthrough

Defeats the write gate, the closed error union, and response bounding in one move, and hands an LLM arbitrary URL construction against an API whose URLs already misrepresent the real permission scope (see below).

cancel_replenishment_order

Not in StoreLink's documented surface — we don't invent capability. Would be a genuinely destructive write needing its own gate design if added later.

Store scoping: store_id and the keyring

StoreLink's paths are shaped /v1/stores/{store_id}/... as if any of ~180 stores were reachable, but each X-Korral-Store-Key is scoped to exactly one. The URL shape misrepresents the real permission surface — this server does not repeat that misrepresentation to the agent.

The server holds a keyring (store_id → key, see STORELINK_KEYRING below). store_id is a real tool parameter, but it is validated against the keyring before any HTTP call leaves the process:

  • Keyring has one store → store_id is optional; omitting it resolves to that store.

  • Keyring has severalstore_id is required. There's no safe implicit default among multiple authorized stores — that's exactly where a silent-wrong-store bug would live.

  • An unrecognized store_idSTORE_NOT_AUTHORIZED, always a local rejection. A store ID this deployment holds no key for is never forwarded upstream, so a lax StoreLink endpoint can't be exploited to leak another store's data.

Full reasoning, including the rejected alternatives (forwarding store_id unchecked; one deployment per store) and what to do if Korral later issues chain-wide keys, is in ADR-0003.

Both of Korral IT's two hard failure cases are tested end to end, not just asserted: a key rotating on StoreLink's side while this server is still running the old one (AUTH_KEY_INVALID, never silently retried), and the agent asking for a real store this deployment simply isn't authorized for (STORE_NOT_AUTHORIZED, rejected locally before any request reaches StoreLink). See tests/key-rotation.test.ts and ADR-0009.

Related MCP server: korral-mcp

Seeing it work: a real scenario

docs/scenarios/butter-restock.md is the full request/response transcript for a real buyer scenario, run against the actual server and the bundled StoreLink stub — not hand-written:

SKU 8847291 (Madeta butter 250g) is running empty at stores 47 and 102. Check on-hand vs. last 24h of POS for both, and raise a replenishment order for any store where the gap exceeds 6 units.

It shows korral_check_stock_risk and korral_list_recent_sales called for both stores, the gap computed from their real responses, and korral_raise_replenishment_order called (dry-run preview, then executed) only for the store whose gap actually breaches the threshold — the other store's real numbers don't cross it, and no order is raised for it. Regenerate with npm run scenario after any tool or mock change; see ADR-0007 for why this is a generated artifact rather than prose.

Quick start

npm install
npm run dev

Boots the server over stdio against the bundled StoreLink stub — no configuration needed. Point an MCP client at it and call any of the 5 tools above.

To verify the whole deployable chain — build, container boot, a real protocol round trip, and a rollback drill:

just verify

Testing

npm run test         # run the full suite once
npm run test:watch   # re-run on file changes, for local development
npm run ci            # typecheck + test + build — what CI runs, and what must pass before every commit

69 tests across 8 files, all real: nothing here mocks the MCP protocol or fakes a tool's response. Every test either calls pure functions directly or spawns the actual server (src/index.ts) as a real stdio subprocess and drives it with a real @modelcontextprotocol/sdk Client — the same shape of connection a Duvo agent runtime uses. Server-spawning tests each set whatever env they need internally (ALLOW_WRITES, a specific STORELINK_KEYRING, LOG_LEVEL) — there's nothing to export by hand before running npm run test. The bundled mock upstream binds an OS-assigned ephemeral port per instance (startMockUpstream(port = 0)), so test files that each start their own server never collide even when vitest runs them concurrently.

File

What it proves

config.test.ts

loadConfig — defaults, structural-vs-capability validation, _FILE secret reading, STORELINK_KEYRING JSON parsing and its error messages

keyring.test.ts

buildKeyring — the store-scope resolution rules from ADR-0003: optional store_id with one authorized store, required with several, STORE_NOT_AUTHORIZED on an unrecognized one, all before any HTTP call is even constructed

depletion.test.ts

The stockout projection algorithm (ADR-0004) in isolation — pure functions, no server: normal projections, refusing to guess on thin data, every data_quality_flag, the replenishment-arrival comparison, and the evidence shape

http-client.test.ts

The generic HttpClient — the one-guarded-retry-on-401 behavior, HTTP-200-with-envelope-error detection, and why post() timeouts surface WRITE_OUTCOME_UNKNOWN while get() timeouts surface UPSTREAM_TIMEOUT

server.test.ts

The full stdio round trip — exact tool list and annotations, korral_list_authorized_stores against the bundled mock, store-scope enforcement, and the write tool refusing by default (ALLOW_WRITES unset)

write-gate.test.ts

korral_raise_replenishment_order with ALLOW_WRITES=true in its own subprocess (kept separate from server.test.ts so the two ALLOW_WRITES states never interfere) — the dry-run preview's full shape, that a preview never mutates state, the MAX_ORDER_UNITS ceiling, actual execution, and the duplicate-order warning

observability.test.ts

The Step 3 guarantees (ADR-0008) against real captured stderr, not just tool responses — a shared call_id across a tool call's whole upstream fan-out, error_code/error_message on tool.err, and an audit line for every call, not only writes

key-rotation.test.ts

The Step 4 failure stories (ADR-0009) — a key rotating on StoreLink's side mid-session fails as AUTH_KEY_INVALID and is never silently retried past one guarded attempt or replaced with a fallback; a real, valid store outside this deployment's keyring is rejected locally as STORE_NOT_AUTHORIZED, never forwarded upstream

Running a single file or a single test:

npx vitest run tests/depletion.test.ts               # one file
npx vitest run tests/depletion.test.ts -t "refuses"   # one test/describe block by name match

Two things to know before extending the suite:

  • Server-spawning tests (server.test.ts, write-gate.test.ts, observability.test.ts, key-rotation.test.ts) launch tsx src/index.ts as a real child process rather than importing buildServer directly, specifically so they exercise the actual main() boot path — config loading, the mock-upstream fallback, keyring construction — not just the tool-registration logic. If you're adding a test that only needs to check a tool handler's behavior without touching process boundaries, prefer unit-testing the underlying pure function (as depletion.test.ts and keyring.test.ts do) over spawning another subprocess — it's faster and the failure is easier to localize.

  • key-rotation.test.ts is the one file that runs the mock upstream in-process (via startMockUpstream() imported directly) rather than letting a spawned server start its own — that's what lets it mutate the mock's accepted key mid-test via __rotateMockStoreKey() while a separately-spawned real server subprocess is still connected, simulating a rotation racing a live session. See the file's header comment and ADR-0009 for why.

End-to-end, beyond the test suite: just verify (cold container build, non-root boot, save/load rollback drill) and just smoke (dependency budget, npm pack/install/connect against the packaged binary) — see DEPLOYMENT.md — and npm run scenario, which regenerates docs/scenarios/butter-restock.md from a real run, covered above.

Configuration

See .env.example for the full reference. StoreLink-specific settings:

Variable

Purpose

STORELINK_KEYRING / STORELINK_KEYRING_FILE

JSON object mapping store_idX-Korral-Store-Key. The _FILE form is preferred in production — see DEPLOYMENT.md.

MAX_ORDER_UNITS

Hard ceiling on a single replenishment order, enforced independently of confirm.

DEFAULT_LOOKBACK_DAYS

Default POS lookback window for korral_check_stock_risk (max 28).

Shipping this into Korral's environment

DEPLOYMENT.md covers the runnable artifact (a multi-stage Dockerfile, verified end to end by just verify — cold build, non-root boot, save/load rollback drill) and the concrete GCP placement: this server and the Duvo agent runtime it's spawned from run on the same GCE VM or GKE node, inside Korral's tenancy, pulling from a private Artifact Registry — no architecture change from the stdio model described above, since the runtime already has exactly one egress destination (StoreLink) and nothing else. See "Korral GCP deployment" in DEPLOYMENT.md for the placement diagram and the digest-pinned redeploy procedure for frequent post-launch updates, and ADR-0010 for why this didn't require a different deployment shape.

Observability

docs/observability.md is written for the two people who actually read these logs: an FDE debugging a failure at 11pm with log access and nothing else, and a category buyer reading the audit log the next morning to see what the agent did on their behalf. Every tool call gets a call_id correlating it with every upstream request it triggers; tool.err logs the real error code and message, not just timing; and the audit stream covers every call — read or write — not only executed writes. See ADR-0008 for why.

Design decisions

Every non-obvious choice above — why workflow-shaped tools over a thin per-endpoint mirror, why the keyring instead of trusting store_id at face value, how the stockout projection expresses uncertainty, why confirm isn't treated as human approval — is recorded as an ADR in docs/decisions/, each with what was rejected and a "revisit when" trigger. Two are explicitly falsifiable during the pilot: if korral_list_recent_sales gets called after nearly every risk check, check_stock_risk's evidence is under-serving and should be enriched rather than answered with more tools; if buyers never chase order status, korral_get_order_status should be cut.

What's included (template layer)

Path

What

src/index.ts

Entry point; buildServer(deps) exported separately from main() for testing.

src/config.ts

Zod-validated config, including the StoreLink keyring.

src/lib/keyring.ts

Store-scope resolution and validation — see "Store scoping" above.

src/lib/depletion.ts

The stockout-projection algorithm behind korral_check_stock_risk.

src/errors.ts

Closed error-code taxonomy, extended with STORE_NOT_AUTHORIZED and AUTH_KEY_INVALID.

src/http/client.ts, src/http/storelink.ts

Generic REST client, plus the X-Korral-Store-Key auth strategy and per-store path resolution.

src/tools/

The 5 tools above, one module each.

src/mock/server.ts

The StoreLink stub — stores, SKUs, suppliers, and POS histories shaped to exercise each data-quality flag.

docs/decisions/

ADR log — the "why" behind everything in this README.

DEPLOYMENT.md

Distribution paths, keyring provisioning, and the weekly key-rotation runbook.

License

MIT.

Available Tools

5 tools
korral_check_stock_riskCheck stock riskA
Read-onlyIdempotent

Assess whether a SKU at a store will run out before closing today, and whether a replenishment order would arrive in time. Combines current on-hand, recent sales velocity, and supplier lead time into one deterministic projection with an explicit confidence level and any data-quality caveats — the evidence used is included so the projection can be checked without another call. Use korral_list_recent_sales if you need the individual transactions behind the velocity figure (e.g. to check whether a single bulk sale skewed it).

ParametersJSON Schema
NameRequiredDescriptionDefault
skuYesSKU code to check.
store_idNoWhich store to check. Optional only if this deployment is authorized for exactly one store, in which case it defaults to that store. Required otherwise — call korral_list_authorized_stores to see the options.
lookback_daysNoDays of POS history to use. Hard cap 28.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, openWorld, idempotent, and non-destructive. The description adds valuable behavioral context: it states the tool 'combines current on-hand, recent sales velocity, and supplier lead time into one deterministic projection with an explicit confidence level' and notes that 'the evidence used is included so the projection can be checked without another call.' No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences. The first sentence front-loads the purpose, the second adds transparency and an explicit alternative. No filler or redundancy — every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite no output schema, the description tells the agent what to expect: a deterministic projection, confidence level, data-quality caveats, and included evidence. It also orients the agent relative to sibling tools and explains the need for store authorization. The combination of annotations and description is sufficient for safe invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, including details for store_id (optional only if one store is authorized) and lookback_days (default, max, exclusive minimum). The tool description itself does not add parameter meaning beyond the schema; it only mentions 'SKU at a store' in passing. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a very specific purpose: 'Assess whether a SKU at a store will run out before closing today, and whether a replenishment order would arrive in time.' This clearly identifies the verb (assess) and resource (stock risk for a SKU/store), and distinguishes it from siblings like korral_list_recent_sales by explaining what evidence it includes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly names korral_list_recent_sales as an alternative when the user needs individual transactions, and the store_id parameter in the schema points to korral_list_authorized_stores. This provides clear when-to-use and when-not-to-use guidance, exceeding the minimum.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

korral_get_order_statusGet replenishment order statusA
Read-onlyIdempotent

Look up the status of a replenishment order previously raised with korral_raise_replenishment_order, by its order_id. Use this to follow up on an order from an earlier session, or to check upstream state after a write call returned WRITE_OUTCOME_UNKNOWN before deciding whether to resubmit.

ParametersJSON Schema
NameRequiredDescriptionDefault
order_idYesOrder ID returned by korral_raise_replenishment_order.
store_idNoWhich store the order belongs to. Optional if this deployment is authorized for exactly one store; required otherwise.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the tool's safety profile is clear. The description adds valuable behavioral context, explaining that it can be used to check upstream state after a write call returned WRITE_OUTCOME_UNKNOWN, which helps the agent decide whether to resubmit. This goes beyond the annotations by framing the tool's role in a workflow, though it does not describe return format or other edge cases.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences long, front-loaded with the core purpose in the first sentence and usage guidance in the second. Every clause earns its place with relevant information, and there is no redundancy or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple lookup tool with rich annotations and complete schema coverage, the description is highly complete. It explains the tool's role in the order lifecycle, when to use it, and how it relates to the write operation. The absence of an output schema is not a gap because annotations and usage guidance provide sufficient context for the agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema covers 100% of parameters with descriptions: order_id is 'Order ID returned by korral_raise_replenishment_order' and store_id specifies 'Which store the order belongs to... Optional if this deployment is authorized for exactly one store; required otherwise.' The description does not add additional parameter semantics beyond what the schema already provides, so it meets the baseline for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Look up the status of a replenishment order previously raised with korral_raise_replenishment_order, by its order_id.' It uses a specific verb ('look up'), identifies the resource (replenishment order status), and mentions the key parameter (order_id). It also distinguishes from siblings by focusing on order status lookup versus listing stores, checking stock risk, listing sales, or raising orders.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit usage context: 'Use this to follow up on an order from an earlier session, or to check upstream state after a write call returned WRITE_OUTCOME_UNKNOWN before deciding whether to resubmit.' This tells the agent when to use it, though it does not explicitly state when not to use it or name alternatives. Since the sibling tools serve different purposes, the context is sufficient.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

korral_list_authorized_storesList authorized storesA
Read-onlyIdempotent

List the Korral stores this deployment is authorized to query, each with local time and closing time. Call this first to learn which store_id(s) are available — every other tool rejects a store_id not in this list. If exactly one store is authorized, store_id is optional on every other tool and defaults to it.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, openWorldHint=true, idempotentHint=true, and destructiveHint=false. The description adds context beyond these: it specifies the output includes local time and closing time, and warns that sibling tools will reject store_ids not returned here, implying this list is authoritative for valid IDs. No contradictions. Minor gap: it doesn't specify the exact response structure, but for a no-param read-only list this is acceptable.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, no wasted words. The purpose, usage guidance, and edge-case default are all front-loaded and clearly structured. Each sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter, read-only list tool with strong annotations and no output schema, the description is complete: it states what is listed, what fields are included, the tool's role as a prerequisite, and the single-store default behavior. It covers both the output and the logical relationship with sibling tools, leaving no critical gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has zero parameters, so the baseline is 4. The description adds value by explaining that the tool's output contains store_id values which are used as parameters for other tools, and clarifies the optionality/default behavior. This bridges the output to subsequent parameter usage, which is helpful even though this tool itself has no parameters.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('List') and resource ('Korral stores this deployment is authorized to query'), with specific detail about returned data (local time, closing time). It distinguishes this tool from siblings as the discovery mechanism for valid store_id values, which none of the listed sibling tools provide.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs to call this tool first, explains that every other tool rejects invalid store_id values, and describes the default behavior when exactly one store is authorized. This gives unambiguous when-to-use guidance relative to all sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

korral_list_recent_salesList recent salesA
Read-onlyIdempotent

List individual POS transactions for a SKU at a store, most recent first. Use this to see whether a sales velocity figure from korral_check_stock_risk was driven by many small baskets or a single large one (e.g. a catering order), or as the audit trail when a projection needs to be double-checked against what actually sold.

ParametersJSON Schema
NameRequiredDescriptionDefault
skuYesSKU code to look up.
limitNoMax transactions to return. Hard cap 500.
sinceNoISO 8601 timestamp; only transactions at or after this time are returned. Defaults to 24 hours ago. Capped at 7 days back.
store_idNoWhich store. Optional if this deployment is authorized for exactly one store; required otherwise.

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish safe read-only/idempotent behavior. The description adds concrete behavioral details: returns individual transaction lines (not aggregates), sorted most recent first, and serves as an audit trail. This goes beyond the schema's parameter descriptions. Minor gap: no mention of return fields or pagination, but schema covers limit.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the primary action, and the second sentence provides valuable selection context without redundancy. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a medium-complexity read-only tool with well-documented schema and clear annotations, the description provides purpose, granularity, ordering, and use-case guidance. The only missing piece is what fields appear in each transaction, but no output schema exists and the description's clarity compensates. It's sufficiently complete for an agent to select and invoke correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline applies. The description reinforces that sku and store_id define the target, and context implies limit/since control the window, but it doesn't add new parameter-level meaning beyond the schema. It does tie 'sales velocity' to the sku parameter, which adds a hint, but not substantial.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('List') with a clear resource ('individual POS transactions') and scope ('for a SKU at a store, most recent first'). It also differentiates from sibling tools by referencing korral_check_stock_risk and framing this as the transactional detail behind that aggregate view.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use: to break down sales velocity into basket sizes or as an audit trail against projections. Names the related sibling tool (korral_check_stock_risk) and gives a concrete decision scenario, providing clear guidance for tool selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

korral_raise_replenishment_orderRaise a replenishment orderA

Raise a replenishment order for a SKU at a store. Defaults to a dry run: the response is a preview containing the exact order plus the on-hand, sales velocity, and stockout projection that justify it, a duplicate-order warning, and a quantity-sanity check — enough for a human to approve or reject without another tool call. Set confirm=true to execute, which also requires the deployment to have ALLOW_WRITES enabled. confirm=true reflects this agent's own judgment, not a recorded human approval — treat it accordingly.

ParametersJSON Schema
NameRequiredDescriptionDefault
skuYesSKU code to order.
reasonYesWhy this order is being raised — required so the audit log carries intent, not just the payload.
confirmNoSet true to execute. Default false returns a dry-run preview only.
store_idNoWhich store. Optional if this deployment is authorized for exactly one store; required otherwise.
quantity_unitsYesUnits to order.
idempotency_keyNoOptional caller-supplied key to avoid double-submission on retry. Auto-generated if omitted.

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations offer only high-level hints (readOnly=false, idempotent=false). The description adds substantial behavioral detail: dry-run default with preview contents, confirm gating execution, ALLOW_WRITES requirement, and the critical caveat that confirm=true reflects agent judgment not human approval. No contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the core action, then tightly packed with essential workflow and safety information. Every clause earns its place; no repetition or fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no output schema, the description covers the return value preview elements (order, on-hand, sales velocity, stockout projection, warnings, sanity check), execution prerequisites, and a safety caveat. For a tool with this complexity, the description is fully self-sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. The description enriches parameter understanding by explaining confirm's dual dry-run/execute behavior, store_id optionality based on authorization, and idempotency_key's purpose—none of which are in the schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource: 'Raise a replenishment order for a SKU at a store.' It clearly distinguishes this write tool from sibling read-only tools like list_authorized_stores and check_stock_risk.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides clear context: dry run by default for human approval, confirm=true to execute, and ALLOW_WRITES requirement. It doesn't explicitly state when not to use it or name alternatives, but the write vs read contrast is implicit and the workflow is well described.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedkorral_check_stock_risk
    • First observedkorral_get_order_status
    • First observedkorral_list_authorized_stores
    • First observedkorral_list_recent_sales
    • First observedkorral_raise_replenishment_order

TDQS

A4.6/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clear, unique purpose: store discovery, risk assessment, sales audit, order creation, and order status lookup. The descriptions explicitly cross-reference when to use related tools (e.g., check_stock_risk vs list_recent_sales), eliminating ambiguity.

Naming Consistency5/5

All tool names follow a uniform korral_<verb>_<object> pattern using snake_case. Verbs are consistent (list, check, get, raise) and objects are descriptive, making the toolset easily predictable.

Tool Count5/5

With 5 tools, the set is well-scoped for a replenishment workflow. Each tool covers an essential step (discovery, analysis, detail, write, follow-up) without redundancy or bloat.

Completeness4/5

The core lifecycle of assessing stock risk and raising/checking replenishment orders is covered, including a dry-run preview and audit trail. Minor gaps exist, such as no list of all orders and no cancel/update order operation, but these may be out of scope for the intended purpose.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server that provides pre-checkout basket tools and a local API/viewer, enabling agents to research products and manage a shopping cart through natural language.
    1
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    MCP server that lets a Duvo agent talk to Korral's StoreLink API, enabling category buyers to offload daily stock checking, replenishment ordering, and order tracking tasks.
    -
  • F
    license
    A
    quality
    B
    maintenance
    MCP server for managing store replenishment, including listing stores, raising orders, checking order status, and viewing SKU reports with observability and key rotation handling.
    4
    -
  • F
    license
    A
    quality
    C
    maintenance
    MCP server exposing the StoreLink grocery stocking API as tools for category buyer workflows, including inventory checks and replenishment orders.
    5
    -