# Order handoff and catalog feed kit Version: November 7, 2026. Companion article: https://jwatte.com/blog/big-brands-mcp-agentic-commerce-small-business/ Use this kit to let an assistant answer from public business facts and prepare a link to your existing order or booking page. The customer reviews the choices and enters personal and payment details on that page. Opening the link must not place an order, reserve inventory, send a message or create a booking. All examples below are original, fictional teaching examples. Example Cafe, its address, prices, availability and times describe no real business. The example.com addresses are placeholders, not working checkout integrations. Replace every example fact before publishing. A price such as 4.50 is an illustrative published item price, not a vendor quote or a suggested selling price. ## 1. Assign the source of each fact Owner: approve public facts, pickup rules, price basis and staff fallback. Web person: keep the customer page and feed consistent. Developer: enforce input limits, freshness checks and the handoff boundary. A feed is a structured list that software can read. JSON is the text format used here. A stable item ID identifies the same choice even when its display name changes. An availability snapshot says what was known at its checked time; it does not hold stock. Fill these decisions before building: | Decision | Your answer | |---|---| | Menu or catalog source of truth | [system and responsible role] | | Published price basis | [currency, portion, tax treatment, mandatory fees] | | Availability source and maximum age | [source; age appropriate to this business] | | Allergen statement source | [approved ingredient record and staff reviewer] | | Hours and holiday-hours owner | [role and review schedule] | | Existing order or booking destination | [exact HTTPS origin and path] | | Customer fallback when facts are uncertain | [public page or published phone] | | Staff approval needed before fulfillment | [when and how customer sees status] | Done when: the owner can trace every displayed fact to an approved source and knows who corrects it. ## 2. Public menu and catalog feed This original JSON example carries both a menu item and a retail item. Prices use decimal strings to avoid changing the published decimal representation. `checked_at` and `expires_at` apply to each availability observation. The short expiry is an example operating choice, not an industry rule. An empty allergen list must never mean safe for an allergy. Use an explicit status. An allergen question that the published statement does not answer goes to staff. The mug uses `not_applicable`; that state is not appropriate for an edible item with missing information. ```json { "feed_version": "1.0", "business_id": "example-cafe", "published_at": "2026-11-07T00:00:00Z", "source_url": "https://example.com/menu/", "currency": "USD", "items": [ { "id": "latte-12oz", "kind": "menu_item", "name": "12 oz dairy latte", "description": "Espresso with dairy milk; 12 oz serving.", "price": { "amount": "4.50", "currency": "USD", "unit": "one 12 oz drink", "tax_included": false, "published_note": "Sales tax is calculated at checkout. No pickup fee." }, "allergens": { "status": "published", "contains": ["milk"], "published_note": "Contains milk. Shared preparation area; ask staff about cross-contact.", "source_url": "https://example.com/menu/latte-12oz/" }, "availability": [ { "store_id": "downtown", "status": "in_stock", "checked_at": "2026-11-07T00:00:00Z", "expires_at": "2026-11-07T00:05:00Z" } ], "source_url": "https://example.com/menu/latte-12oz/" }, { "id": "cafe-mug", "kind": "product", "name": "Example Cafe ceramic mug", "description": "One 12 oz ceramic mug, sold empty.", "price": { "amount": "12.00", "currency": "USD", "unit": "one mug", "tax_included": false, "published_note": "Sales tax is calculated at checkout. No pickup fee." }, "allergens": { "status": "not_applicable", "contains": [], "published_note": "Non-food item." }, "availability": [ { "store_id": "downtown", "status": "out_of_stock", "checked_at": "2026-11-07T00:00:00Z", "expires_at": "2026-11-07T00:05:00Z" } ], "source_url": "https://example.com/shop/cafe-mug/" } ] } ``` Implementation contract: - Allow only the business's documented item IDs, currencies and availability states: `in_stock`, `out_of_stock` or `unknown`. - After `expires_at`, return `unknown` with the last checked time. Do not repeat an old stock claim as current. - If the source fails, return an unavailable-data result and the ordinary customer page. Failure is different from sold out. - Preserve the published portion, tax and fee explanation alongside the amount. Recheck the final total at checkout. - Publish only owner-approved ingredient statements. Never infer allergen safety from a product name, photograph or absence of a warning. - Do not put customer names, purchases, account records, access codes or supplier-only costs into this feed. Done when: a reviewer compares the public page and feed and finds matching items, amounts, conditions and availability labels. ## 3. Public location and hours feed This original JSON example separates normal hours from a dated holiday exception. The address is a fictional business address. `timezone` tells the implementation which local date and time govern opening hours. A special schedule overrides the regular schedule for its date. ```json { "feed_version": "1.0", "checked_at": "2026-11-07T00:00:00Z", "locations": [ { "id": "downtown", "name": "Example Cafe Downtown", "source_url": "https://example.com/locations/downtown/", "address": { "street_address": "100 Example Street", "address_locality": "Example City", "address_region": "ID", "postal_code": "00000", "address_country": "US" }, "timezone": "America/Boise", "regular_hours": [ { "days": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], "opens": "07:00", "closes": "17:00" } ], "regular_closed_days": ["Sunday"], "holiday_hours": [ { "date": "2026-11-26", "status": "open", "opens": "08:00", "closes": "12:00", "label": "Example holiday schedule" } ], "fulfillment_methods": ["pickup"], "order_page": "https://example.com/order/" } ] } ``` For a closed holiday use `status: "closed"` and omit `opens` and `closes` from this custom feed. Document overnight hours separately if your business has them. Missing hours mean unknown; an empty record must not become an open-all-day claim. Done when: staff confirm the regular week, special dates and local time zone, including any location-specific differences. ## 4. Handoff link specification A query parameter is a named choice after the question mark in a URL. This kit allows only choices from a controlled list. It excludes personal details and free-form messages, even when a customer already typed them into chat. URLs can appear in browser history, logs and shared links. The developer must connect these proposed parameters to the existing site's actual form. A checkout that ignores them needs an adapter or an ordinary link with instructions. Adding a parameter to a URL does not make the destination support it. | Field | Accepted value | Purpose | |---|---|---| | `store_id` | Known public location ID | Select location | | `item_id` | Known published item ID | Select one item | | `quantity` | Integer 1 to 10 | Example business-defined limit | | `fulfillment` | `pickup` | Supported fulfillment method | Original illustrative order link: ```text https://example.com/order/?store_id=downtown&item_id=latte-12oz&quantity=2&fulfillment=pickup ``` The server constructs the origin and path from its own fixed configuration. Never accept a destination URL, redirect address, callback, arbitrary parameter name, price, discount or account token from the assistant. Reject unknown fields instead of forwarding them. Validate the store-item combination as well as the individual IDs. The landing page displays a draft selection and retrieves current prices and availability. The customer chooses whether to continue, signs in only if the existing service requires an account, and enters personal details there. The payment provider or brand checkout handles payment. The page must not create server-side cart state or other customer commitments just because someone fetched the link. If a cart needs to be created, make that a deliberate customer action on the page. For a service business, use the same design with a separate fixed booking destination and public `service_id`, `location_id` and `slot_id` choices. A slot ID is a published appointment option, not a customer identifier or access token. Omit it when current availability is unknown. Example: ```text https://example.com/book/?service_id=standard-consultation&location_id=downtown&slot_id=public-slot-a ``` That link expresses a preference. The booking page must recheck the slot before it confirms anything. Keep names, phone numbers, email addresses, street addresses, symptoms, access instructions and payment details out of both kinds of link. Done when: opening a valid link preselects only supported choices, creates no commitment, and leaves the ordinary unfilled form usable. ## 5. Matching Schema.org descriptions Structured data labels the facts on a customer page so software can understand their meaning. JSON-LD is a JSON format for those labels. Schema.org supplies the vocabulary. Its [Menu](https://schema.org/Menu), [MenuItem](https://schema.org/MenuItem), [Product](https://schema.org/Product), [LocalBusiness](https://schema.org/LocalBusiness) and [Offer](https://schema.org/Offer) documentation defines the types and properties used here. This original JSON-LD example matches the fictional feed above at its example checked time. It is plain JSON here so it can be parsed without HTML. Your web person embeds the relevant entities in the actual pages using the site's normal structured-data method. Keep each entity's facts visible on that page. The graph is a set of related records; `@id` gives each one a stable identity. ```json { "@context": "https://schema.org", "@graph": [ { "@type": "LocalBusiness", "@id": "https://example.com/locations/downtown/#business", "name": "Example Cafe Downtown", "url": "https://example.com/locations/downtown/", "address": { "@type": "PostalAddress", "streetAddress": "100 Example Street", "addressLocality": "Example City", "addressRegion": "ID", "postalCode": "00000", "addressCountry": "US" }, "openingHoursSpecification": [ { "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], "opens": "07:00", "closes": "17:00" } ], "specialOpeningHoursSpecification": [ { "@type": "OpeningHoursSpecification", "validFrom": "2026-11-26", "validThrough": "2026-11-26", "opens": "08:00", "closes": "12:00" } ] }, { "@type": "Menu", "@id": "https://example.com/menu/#menu", "name": "Example Cafe menu", "url": "https://example.com/menu/", "hasMenuItem": [ { "@type": "MenuItem", "@id": "https://example.com/menu/latte-12oz/#item", "name": "12 oz dairy latte", "description": "Espresso with dairy milk; 12 oz serving. Contains milk. Shared preparation area; ask staff about cross-contact.", "offers": { "@type": "Offer", "price": "4.50", "priceCurrency": "USD", "availability": "https://schema.org/InStock", "url": "https://example.com/menu/latte-12oz/", "description": "One 12 oz drink. Sales tax is calculated at checkout. No pickup fee.", "seller": {"@id": "https://example.com/locations/downtown/#business"} } } ] }, { "@type": "Product", "@id": "https://example.com/shop/cafe-mug/#product", "sku": "cafe-mug", "name": "Example Cafe ceramic mug", "description": "One 12 oz ceramic mug, sold empty.", "url": "https://example.com/shop/cafe-mug/", "offers": { "@type": "Offer", "price": "12.00", "priceCurrency": "USD", "availability": "https://schema.org/OutOfStock", "url": "https://example.com/shop/cafe-mug/", "description": "One mug. Sales tax is calculated at checkout. No pickup fee.", "seller": {"@id": "https://example.com/locations/downtown/#business"} } } ] } ``` Schema.org [special opening hours](https://schema.org/LocalBusiness) override normal hours; [OpeningHoursSpecification](https://schema.org/OpeningHoursSpecification) defines the time and date fields. Use a more specific business subtype when appropriate. This generic example uses `LocalBusiness` to show the shared location fields. Generate feed and structured data from the same approved record. When an availability observation expires, omit the structured-data availability claim until it can be refreshed; do not convert `unknown` into `InStock`. The custom feed's `checked_at` is not a Schema.org Offer field and is intentionally absent from the Offer object. Keep the checked time visible in the page's availability explanation. No dietary certification is asserted. `contains: ["milk"]` in the custom feed becomes the exact approved statement in the description. Do not manufacture a `suitableForDiet` value to stand in for an incomplete allergen record. Done when: the JSON parses, its fields match the visible page, and the selected types fit what that page actually describes. Syntax validity alone does not establish search eligibility or assistant discovery. ## 6. Original read-only MCP tool definitions Model Context Protocol, or MCP, gives compatible assistants a standard way to request named capabilities. A tool is one such named capability. An input schema describes the accepted fields. These definitions are a design for your developer, not a running server. The MCP [tools specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools) defines tool descriptions and schemas. Its [annotation definitions](https://modelcontextprotocol.io/specification/2025-11-25/schema#toolannotations) explain read-only, destructive, repeatability and open-world hints. Hints describe behavior; the implementation must enforce the boundary. The original JSON below is a possible `tools/list` result for a server reading only the business's maintained public snapshots. `openWorldHint: false` assumes these handlers do not contact outside services or arbitrary URLs. If a later implementation makes an external live lookup, review that annotation and its privacy notice. `build_order_link` calculates a URL string only. It does not open a browser, fill an existing page, create a cart or store a draft. ```json { "tools": [ { "name": "get_menu", "description": "Return the public menu or catalog for one known store, including published prices, allergen statements and availability checked times. Does not create an order or hold inventory.", "inputSchema": { "type": "object", "properties": {"store_id": {"type": "string", "enum": ["downtown"]}}, "required": ["store_id"], "additionalProperties": false }, "annotations": {"readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false} }, { "name": "find_store", "description": "Return public store addresses, normal hours, dated exceptions and order-page links for a published city. Does not accept a customer's precise location or access account records.", "inputSchema": { "type": "object", "properties": {"city": {"type": "string", "enum": ["Example City"]}}, "required": ["city"], "additionalProperties": false }, "annotations": {"readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false} }, { "name": "check_item_availability", "description": "Read a public item availability snapshot for a known store. Return its checked time and unknown when expired or unavailable. Does not reserve stock or promise fulfillment.", "inputSchema": { "type": "object", "properties": { "store_id": {"type": "string", "enum": ["downtown"]}, "item_id": {"type": "string", "enum": ["latte-12oz", "cafe-mug"]} }, "required": ["store_id", "item_id"], "additionalProperties": false }, "annotations": {"readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false} }, { "name": "build_order_link", "description": "Construct a fixed-destination order-page URL containing only approved public choices. Returns a URL and a review notice. Does not visit it, create a cart, store a draft, send a message, reserve stock or take payment.", "inputSchema": { "type": "object", "properties": { "store_id": {"type": "string", "enum": ["downtown"]}, "item_id": {"type": "string", "enum": ["latte-12oz", "cafe-mug"]}, "quantity": {"type": "integer", "minimum": 1, "maximum": 10}, "fulfillment": {"type": "string", "enum": ["pickup"]} }, "required": ["store_id", "item_id", "quantity", "fulfillment"], "additionalProperties": false }, "annotations": {"readOnlyHint": true, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false} } ] } ``` These exact enums intentionally limit the specimen to its own dataset. A larger catalog can use bounded string IDs plus server-side membership checks. The handler must still reject unknown IDs and extra fields even if the calling assistant claims to have validated them. Read-only answers can change as stock changes; the repeatability hint does not freeze the answer. Original example of the business data returned by `build_order_link`, inside your server's normal MCP response: ```json { "url": "https://example.com/order/?store_id=downtown&item_id=latte-12oz&quantity=2&fulfillment=pickup", "status": "choices_prepared", "notice": "No order has been placed. Review current prices and availability on the order page before continuing." } ``` Done when: the handler's actual behavior matches every description and annotation, and nobody can obtain private records through these public tools. ## 7. Acceptance tests for your implementation These are tests for the reader's implementation on fixtures and an isolated test destination they control. No real order, booking, email or payment test is authorized by this kit. | Test | Expected result | |---|---| | Parse every published feed | Valid JSON; documented fields and correct types | | Compare feed, visible page and structured data | Names, location IDs, prices, tax basis and stock status agree | | Request a known item and store | Correct published fact and source URL | | Request an unknown ID or extra field | Clear validation error; no guessed match or forwarded parameter | | Pass quantity zero, 11, a decimal or text | Validation error for this example's 1 to 10 integer rule | | Pass a name, email, phone, free text or destination URL | Rejected; no personal data enters a handoff URL | | Request a valid item at an unsupported store | Rejected even if both IDs exist elsewhere | | Ask after availability expiry | Unknown with last checked time and customer fallback | | Lose access to the stock source | Data-unavailable result, never a sold-out claim | | Open the link repeatedly or preview it | No order, inventory hold, mail, payment or saved customer draft | | Submit malicious markup in a source description | Treated as data; never executed or followed as an instruction | | Ask an unsupported allergen question | Published statement plus staff referral; no inferred safety claim | | Check regular day, Sunday and the holiday exception | Each matches the approved local schedule | | Change stock or price between chat and checkout | Checkout rechecks and displays the changed facts before commitment | | Inspect tool response, analytics and application logs | No unnecessary customer data or account tokens | | Deny cookies or use only a keyboard | Ordinary order or booking path remains understandable and usable | Record: test date [ ], build reviewed [ ], tester [ ], passed cases [ ], failed cases [ ], unresolved limits [ ], owner accepting release [ ]. ## 8. Validation and sources The kit's five JSON blocks were parsed with `JSON.parse`. A local consistency check compared item prices, currency, stock states, location address, regular hours, holiday hours, tool IDs and the handoff parameters across these examples. All examples are original. No third-party implementation code was copied or run. No server, checkout, booking page, search rich result or live MCP session was tested. The acceptance list above remains work for your implementation. Primary documentation read November 7, 2026, UTC: - [Schema.org Menu](https://schema.org/Menu), 19:00:04: menu items and menu sections. - [Schema.org MenuItem](https://schema.org/MenuItem), 19:00:05: offers and dietary descriptions. - [Schema.org Product](https://schema.org/Product), 19:00:06: product identifiers and offers. - [Schema.org LocalBusiness](https://schema.org/LocalBusiness), 19:00:07: addresses, hours and dated overrides. - [Schema.org Offer](https://schema.org/Offer), 19:00:09: prices, currency and availability. - [Schema.org OpeningHoursSpecification](https://schema.org/OpeningHoursSpecification), 19:00:10: opening times and valid dates. - [MCP tools specification, version 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25/server/tools), 19:00:11: tool definitions and trust limits. - [MCP ToolAnnotations, version 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25/schema#toolannotations), 19:01:02: behavior hints. The field names in the custom feeds, expiry policy, quantity ceiling and handoff rules are this kit's design choices. They are not a universal commerce protocol or promises about any assistant's behavior.