How our MCP server answers a price question
One request from Claude to a distributor's stock list and back: what each stop does, what it refuses to do, and four real answers to try.
Our Connect demo server is live at mcp.askmojood.com. Add it to Claude as a connector, ask for the price of a box of bolts, and Claude answers with a quote from the distributor’s own list.
This post follows one of those requests from Claude to the stock list and back. Every server we build for a client has the same shape: a front door that lets in only what it should, a server that checks each request, and tools that answer only from the client’s own data.
Follow one request
Step through it, or pick a stop. Each step shows the real code or data at that point.
One get_quote call, end to end
01
You ask, Claude picks a tool
Claude has read our four tool descriptions. For a price it calls get_quote and passes your words exactly as you typed them.
The model never sees the stock list, only tool answers
{ "method": "tools/call", "params": { "name": "get_quote", "arguments": { "text": "200 pcs M10x40 galv bolts with nuts and washers" } } }02
It arrives at Cloudflare
The request goes over HTTPS to Cloudflare. Our server has no public address for it to reach directly.
No open web port on our server
POST https://mcp.askmojood.com/mcp content-type: application/json03
Down the tunnel
Our server keeps one outbound tunnel open to Cloudflare. The request comes down it to a port only the server itself can reach.
Firewall: SSH in, nothing else
mcp.askmojood.com → tunnel "mojood-api" → 127.0.0.1:800104
The MCP server checks it and runs the tool
It checks the host name, and a browser request must come from claude.ai or chatgpt.com. Each tool is marked read-only, so the AI app knows nothing will change.
4 tools, all read-only
READ_ONLY = ToolAnnotations( read_only_hint=True, destructive_hint=False, idempotent_hint=True, open_world_hint=False)05
Shorthand becomes an exact part
The matcher reads the message the way a counter salesman would. What it fills in is listed as assumed; what's missing is asked for, never guessed.
A missing size gets a question
"200 pcs M10x40 galv bolts" → DIN 933 M10×40 HDG · 200 pcs assumed: DIN 933 (standard not stated) assumed: grade 8.8 (the only one in stock)06
Priced from the distributor's own list
Each exact part is looked up in the stock list and priced per piece. Customers see In stock, Low or Out of stock, never the count.
Prices come only from the list (sample prices)
DEMO-933-M10-040-88-HDG 27.80 PKR In stock DEMO-934-M10-8-HDG 10.406 PKR In stock DEMO-125-M10-HDG 2.923 PKR In stock07
The answer goes back the same way
The result returns as JSON and Claude writes its reply from it. The server's instructions tell it to quote these prices exactly and never from its own knowledge.
Real output, captured 21 October 2026
{ "subtotal": "8225.80", "ready": true, "note": "SAMPLE prices from a fictional distributor. Before tax. Prices come from the distributor's stock list, not from the model." }
Try it on four real messages
Contractors don’t write DIN 933 M10×40 8.8 HDG. They write “200 pcs M10x40 galv”. Here is what the server returned for four messages, unedited.
Try an order
Customer writes
200 pcs M10x40 galv bolts with nuts and washers
get_quote returns
| Part | Qty | Price each | Line |
|---|---|---|---|
DIN 933 M10×40 HDGPriced
| 200 | 27.80 | 5,560.00 |
DIN 934 M10 HDGPriced
| 200 | 10.406 | 2,081.20 |
DIN 125 M10 HDGPriced
| 200 | 2.923 | 584.60 |
| Ready to confirm · subtotal before tax | PKR 8,225.80 | ||
Raw JSON from the server
{
"distributor": "Demo Fasteners",
"sample_data": true,
"currency": "PKR",
"lines": [
{
"requested": "200 pcs M10x40 galv bolts",
"part": "DIN 933 M10×40 HDG",
"quantity": 200,
"status": "matched",
"sku": "DEMO-933-M10-040-88-HDG",
"unit_price": "27.80",
"line_total": "5560.00",
"ask_for": [],
"assumed": [
"DIN 933: standard not stated (fully threaded; DIN 931 is part-threaded)",
"grade 8.8: not stated, the only one in stock"
]
},
{
"requested": "nuts",
"part": "DIN 934 M10 HDG",
"quantity": 200,
"status": "matched",
"sku": "DEMO-934-M10-8-HDG",
"unit_price": "10.406",
"line_total": "2081.20",
"ask_for": [],
"assumed": [
"size M10, finish HDG: from the line it goes with",
"DIN 934: standard not stated",
"quantity 200: same as the line it goes with",
"nut class 8: not stated, the only one in stock"
]
},
{
"requested": "washers",
"part": "DIN 125 M10 HDG",
"quantity": 200,
"status": "matched",
"sku": "DEMO-125-M10-HDG",
"unit_price": "2.923",
"line_total": "584.60",
"ask_for": [],
"assumed": [
"size M10, finish HDG: from the line it goes with",
"DIN 125: standard not stated",
"quantity 200: same as the line it goes with"
]
}
],
"subtotal": "8225.80",
"ready": true,
"note": "SAMPLE prices from a fictional distributor. Before tax. Prices come from the distributor's stock list, not from the model."
}Customer writes
M16 bolts 100 pcs
get_quote returns
| Part | Qty | Price each | Line |
|---|---|---|---|
DIN 933 M16Asks: length
| 100 | ||
| Not ready: the agent asks first | — | ||
Raw JSON from the server
{
"distributor": "Demo Fasteners",
"sample_data": true,
"currency": null,
"lines": [
{
"requested": "M16 bolts 100 pcs",
"part": "DIN 933 M16",
"quantity": 100,
"status": "ask",
"sku": null,
"unit_price": null,
"line_total": null,
"ask_for": [
"length"
],
"assumed": [
"DIN 933: standard not stated (fully threaded; DIN 931 is part-threaded)"
]
}
],
"subtotal": "0",
"ready": false,
"note": "SAMPLE prices from a fictional distributor. Before tax. Prices come from the distributor's stock list, not from the model."
}Customer writes
M12 ke 500 nut chahiye galv
get_quote returns
| Part | Qty | Price each | Line |
|---|---|---|---|
DIN 934 M12 HDGPriced
| 500 | 14.04 | 7,020.00 |
| Ready to confirm · subtotal before tax | PKR 7,020.00 | ||
Raw JSON from the server
{
"distributor": "Demo Fasteners",
"sample_data": true,
"currency": "PKR",
"lines": [
{
"requested": "M12 ke 500 nut chahiye galv",
"part": "DIN 934 M12 HDG",
"quantity": 500,
"status": "matched",
"sku": "DEMO-934-M12-8-HDG",
"unit_price": "14.04",
"line_total": "7020.00",
"ask_for": [],
"assumed": [
"DIN 934: standard not stated",
"nut class 8: not stated, the only one in stock"
]
}
],
"subtotal": "7020.00",
"ready": true,
"note": "SAMPLE prices from a fictional distributor. Before tax. Prices come from the distributor's stock list, not from the model."
}Customer writes
50 M20x80 A4 bolts
get_quote returns
| Part | Qty | Price each | Line |
|---|---|---|---|
DIN 933 M20×80Not stocked
| 50 | ||
| Not ready: the agent asks first | — | ||
Raw JSON from the server
{
"distributor": "Demo Fasteners",
"sample_data": true,
"currency": null,
"lines": [
{
"requested": "50 M20x80 A4 bolts",
"part": "DIN 933 M20×80",
"quantity": 50,
"status": "not_stocked",
"sku": null,
"unit_price": null,
"line_total": null,
"ask_for": [],
"assumed": [
"DIN 933: standard not stated (fully threaded; DIN 931 is part-threaded)"
]
}
],
"subtotal": "0",
"ready": false,
"note": "SAMPLE prices from a fictional distributor. Before tax. Prices come from the distributor's stock list, not from the model."
}get_quote on mcp.askmojood.com, captured 21 October 2026. Demo Fasteners is a fictional distributor and its prices are samples.Two of the four don’t get a price, and that’s on purpose. A missing length gets a question. A part that isn’t on the list gets “not stocked”. Neither gets a guess.
Three decisions that keep it safe
No way in from outside. The server has no open web port. Requests reach it through a tunnel the server opened itself, and the firewall lets in SSH only.
Read-only, and it says so. Every tool carries MCP’s read-only annotation, so Claude and ChatGPT can show the user that nothing will change. Directory reviewers check for this.
Prices from the list, never from the model. The server’s instructions tell the model so in plain words:
Prices and availability come only from these tools, never from your own knowledge; quote them exactly.
What changes for your product
For a client, the shape stays and the parts become yours:
- Your API instead of the matcher and stock list. Each tool calls your product as the signed-in user, through your existing login, so people see only what their role allows.
- Write tools, kept apart. Creating an invoice or assigning a ticket are separate tools from reading, and they run only after the user says yes in the chat.
- Code and hosting in your accounts. The server runs on your cloud, and you own it from day one.
We start every client with a one-page tool design: the tools, what each reads or changes, and how users sign in. It’s free.
Try it yourself
In Claude, open Settings, then Connectors. Add a custom connector, paste https://mcp.askmojood.com/mcp and ask: Quote 300 M12×50 10.9 black bolts with nuts. No sign-in, sample data.
Hammad