Skip to content
iJava Cafe logo

iJava Developers

MCP Server

Let any AI assistant read the iJava menu, place a pickup order, and check on it.

POSThttps://ijavacafe.com/api/mcp

Overview

The iJava MCP server lets an AI assistant order real food from iJava Cafe in San Jose. It can read the cafes and the live menu, build a pickup order, hand the customer a secure payment link, and check the order afterwards.

It speaks the Model Context Protocol, so Claude, Cursor and any other MCP client can use it with no SDK and no glue code.

Endpointhttps://ijavacafe.com/api/mcp
TransportStreamable HTTP, stateless. One JSON-RPC 2.0 message per POST, answered with JSON.
Protocol versions2025-06-18, 2025-03-26, 2024-11-05
AuthenticationNone. See Authentication and limits.
FulfilmentPickup only, at Delmas (Downtown San Jose) and Willow Glen.
PaymentThe customer pays on a Stripe page. The server never charges a card by itself.

What is available

PieceStatus
MCP tools: list_locations, search_menu, create_order, get_order_statusLive
Customer accounts, favourites, schedule_order, cancel_orderComing
Saved cards and spending rules ("Max $25", automatic orders)Coming
REST API, API keys, webhooks, sandboxComing

Quickstart

Add the server to your client, then ask it something like "What cold drinks does iJava Willow Glen have?"

claude mcp add --transport http ijava https://ijavacafe.com/api/mcp

No key and no sign-up. Every tool works the moment the client connects.

How ordering works

  1. Pick a cafe. Call list_locations. Use a cafe where online_pickup_open is true.
  2. Find the items. Call search_menu to get each item_id, its sizes, and its required options.
  3. Confirm with the customer. Read back the items and the price before you order.
  4. Create the order. Call create_order. You get an order_number and a payment_url.
  5. Hand over the link. The customer pays on Stripe's page. The order goes to the kitchen the moment they pay.
  6. Check on it. Call get_order_status with the order number and the customer's email.
Note.This is the Ask every time permission level: the agent prepares the order and the customer approves it by paying. Saved cards and rules-based or automatic ordering need customer accounts, which are coming.

When a cafe is not taking online pickup orders, create_order refuses with a link where the customer can order instead. Pass that link on.

Authentication and limits

The server needs no key. The tools only read the public menu, or create an order the customer still has to pay for. Customer details are never returned, except to someone who already has both the order number and the email.

Rate limits

LimitPer
120 requests a minutecaller
5 create_order calls a minutecaller
20 get_order_status calls a minutecaller
30 create_order calls a minuteall callers together

Past a limit the server answers HTTP 429 with JSON-RPC error -32000. Wait a minute and try again. Request bodies over 64 KB get HTTP 413.

list_locations

Read-only

List iJava cafes

iJava cafe locations with address, phone, weekly hours, and whether online pickup orders are open right now.

Arguments

None. Send an empty object, {}.

Example

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_locations",
    "arguments": {}
  }
}
Result (the text of content[0], parsed)
[
  {
    "location": "delmas",
    "name": "iJava Cafe — Delmas",
    "address": "387 Delmas Ave, San Jose, CA 95126",
    "phone": "(408) 753-9732",
    "hours": {
      "sun": "06:30-14:00",
      "mon": "06:30-14:00",
      "…": "…"
    },
    "online_pickup_open": false,
    "order_elsewhere": "https://order.toasttab.com/online/ijava-cafe-delmas-387-delmas-avenue"
  },
  {
    "location": "almaden",
    "name": "iJava Cafe — Willow Glen",
    "address": "2306 Almaden Rd #150, San Jose, CA 95125",
    "phone": "(408) 979-0251",
    "hours": {
      "sun": "08:00-14:00",
      "mon": "08:00-14:00",
      "…": "…"
    },
    "online_pickup_open": true,
    "next_pickup": "ASAP"
  }
]

Hours are Pacific time, 24-hour clock. When online_pickup_open is true the result has next_pickup (ASAP, or the next opening such as Saturday at 8:00 AM). When it is false the result has order_elsewhere, a link the customer can order from instead.

Try itCalls the live server

search_menu

Read-only

Search the menu

Search one cafe's orderable menu. Returns item_id, price, sizes and modifiers, which create_order needs. Prices are in USD before tax.

Arguments

location
"delmas" | "almaden"required

delmas = Downtown San Jose, almaden = Willow Glen

query
string

Words to match in the item name, description or section. Omit to list the whole menu. (max 80 chars)

Example

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "search_menu",
    "arguments": {
      "location": "almaden",
      "query": "cold brew"
    }
  }
}
Result (the text of content[0], parsed)
{
  "location": "almaden",
  "count": 1,
  "items": [
    {
      "item_id": "cold-brew",
      "name": "Cold Brew",
      "section": "Coffee",
      "price": "$7.00",
      "modifiers": [
        {
          "name": "Milk",
          "choose": "exactly 1 (required)",
          "options": [
            "Regular milk",
            "Oat milk (+$0.75)",
            "Black (no milk)"
          ]
        },
        {
          "name": "Extras",
          "choose": "any (optional)",
          "options": [
            "Vanilla (+$0.75)",
            "Extra shot (+$0.75)"
          ]
        }
      ]
    }
  ]
}

Every word in query must match the item name, description, section or category. Omit query to get the whole orderable menu for that cafe (about 120 items).

Sized items return sizes, and you must send one of them as size in create_order. A modifier marked required must be sent. Send option names without the (+$0.75) suffix. Items the cafe has run out of today carry "sold_out": true and will be refused.

Try itCalls the live server

create_order

Creates an order

Create a pickup order

Prices a pickup order and returns a secure Stripe payment link. Nothing is charged until the customer opens the link and pays; give the link to the customer. The kitchen gets the order once payment goes through. Confirm the items and total with the customer before calling this.

Arguments

location
"delmas" | "almaden"required

delmas = Downtown San Jose, almaden = Willow Glen

customer
objectrequired

The person picking up

customer.name
stringrequired

Name on the pickup ticket (max 120 chars)

customer.email
stringrequired

Gets the receipt; needed for get_order_status (email, max 320 chars)

customer.phone
stringrequired

The cafe calls this number if there is a problem (max 40 chars)

lines
object[]required

One entry per menu item. At most 40 items in all (1–40 items)

lines[].item_id
stringrequired

From search_menu

lines[].quantity
integerrequired

How many of this item (1–20)

lines[].size
string

Required when the item has sizes; one of its sizes

lines[].modifiers
object[]

Option names exactly as search_menu lists them, without the price suffix

lines[].modifiers[].name
stringrequired

—

lines[].modifiers[].options
string[]

—

lines[].special_instructions
string

Note for this item (max 280 chars)

special_instructions
string

Note for the whole order (max 500 chars)

Example

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "create_order",
    "arguments": {
      "location": "almaden",
      "customer": {
        "name": "Jordan Lee",
        "email": "jordan@example.com",
        "phone": "408-555-0100"
      },
      "lines": [
        {
          "item_id": "cold-brew",
          "quantity": 2,
          "modifiers": [
            {
              "name": "Milk",
              "options": [
                "Oat milk"
              ]
            }
          ]
        }
      ],
      "special_instructions": "Light ice"
    }
  }
}
Result (the text of content[0], parsed)
{
  "order_number": "IJ-261002-4214BB",
  "payment_url": "https://checkout.stripe.com/c/pay/cs_live_…",
  "payment_link_expires_in_minutes": 30,
  "status": "pending_payment",
  "next_step": "Send payment_url to the customer. The order goes to the kitchen when they pay."
}
Important.Confirm the items and the price with the customer before you call this tool. The customer pays on Stripe's page, so nothing is charged until they do, and an unpaid order never reaches the kitchen.

Prices, tax and the pickup time are set by the server. The payment page shows the final total with tax. The link stays valid for 30 minutes.

No “Try it” for this tool: a test call would create a real order and a real payment link. Use the example above from your own client.

get_order_status

Read-only

Check an order

Status, pickup time, items and total for an order. Needs the order number and the email it was placed with.

Arguments

order_number
stringrequired

e.g. IJ-261002-A1B2C3, returned by create_order (max 40 chars)

email
stringrequired

The email the order was placed with (email)

Example

Request
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_order_status",
    "arguments": {
      "order_number": "IJ-261002-4214BB",
      "email": "jordan@example.com"
    }
  }
}
Result (the text of content[0], parsed)
{
  "order_number": "IJ-261002-4214BB",
  "status": "paid",
  "location": "almaden",
  "cafe": "iJava Cafe — Willow Glen",
  "address": "2306 Almaden Rd #150, San Jose, CA 95125",
  "pickup": "ASAP",
  "items": [
    {
      "name": "Cold Brew",
      "quantity": 2,
      "price": "$15.50"
    }
  ],
  "subtotal": "$15.50",
  "total_paid": "$16.95"
}
statusMeaning
pending_paymentLink created, the customer has not paid yet.
paidPaid. The order is with the cafe. refunded appears if staff refunded any of it.
payment_failedCheckout could not start or did not complete.
canceledStaff canceled the order.

A wrong email and an unknown order number get the same answer, No order matches that number and email.

Try itCalls the live server

Errors

There are two kinds. A tool error means the tool ran and said no: bad arguments, a sold-out item, a closed cafe. It comes back as a normal result with isError: true and a sentence your model can read and act on.

Tool error
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Complete Milk for Cold Brew."
      }
    ],
    "isError": true
  }
}

A protocol error means the request itself was wrong. It comes back as a JSON-RPC error.

CodeHTTPMeaning
-32700400The body is not valid JSON.
-32600400 / 413Not a single JSON-RPC 2.0 message, or the body is over 64 KB.
-32601200Unknown method. The server supports initialize, ping, tools/list and tools/call.
-32602200Unknown tool name.
-32000429Rate limited.

Raw protocol

You only need this to call the server without an MCP client. Every request is a POST to the endpoint with one JSON-RPC message. There are no sessions: you can skip initialize and call tools/call directly.

List the tools
curl -s https://ijavacafe.com/api/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Call a tool
curl -s https://ijavacafe.com/api/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_locations","arguments":{}}}'

The result of tools/call is { content: [{ type: "text", text }] }, where text is JSON. tools/list returns each tool's full JSON Schema as inputSchema. Notifications get HTTP 202 with no body. GET and DELETE get 405, because there is no event stream and no session to end. CORS is open, so a browser page can call the server directly.

Changelog

DateVersionChange
2026-10-021.0.0First release: list_locations, search_menu, create_order, get_order_status.