An assistant can find the right racket name and still recommend the wrong product. The 2025 and 2026 editions are different records. βUnder 150β needs a currency. A price on a manufacturer's page does not prove that a shop can deliver it.
We are building PadelTrue, a padel racket comparison site. Those small distinctions became the design problem for our agent interface: how can a model search our catalogue without quietly losing the qualifications that make an answer useful?
My view is that sites with useful structured information should give agents a way to query it. For a product catalogue, that means exact identifiers, explicit constraints and sources that travel with the answer.
This is a look at our working implementation. PadelTrue may earn commissions from eligible store links on its product pages; commissions do not determine these search results or calculated ratings.
One catalogue, several ways to use it
The catalogue contained 191 rackets on 2 October 2026. It feeds the human website, a read-only HTTP API, an MCP endpoint, and a downloadable dataset.
Published specifications + source URLs
|
evidence gate
|
one catalogue snapshot
|
+--------+---------+------------+
| | | |
web pages JSON API MCP tools dataset export
The gate is deliberately concrete. A specification with a value but no source URL holds the record back. So does a missing sourced price or fewer than three usable inputs to the rating model. A source URL alone cannot establish that a claim is true; checking the quoted evidence is a separate job.
This also creates a coverage tradeoff: some real rackets are absent because our record is incomplete. An empty search result describes our catalogue, not the entire market.
Tools should follow decisions people actually make
There are three buying-research operations:
| Operation | Tool | What comes back |
|---|---|---|
| Find candidates | search_rackets |
Exact models, applied filters, ordering rule, observed prices and missing rating inputs |
| Inspect one candidate | get_racket |
Specifications with source URLs and source wording, plus the model's page |
| Compare two identified models | compare_rackets |
Differences, missing-data cautions and a human-readable comparison URL |
Two further tools, search and fetch, expose the same records as retrievable documents. That makes five tools in total.
The distinction matters. The document search is literal keyword retrieval. It should receive nox at10 18k 2026, not a whole conversation about budget, shipping and an aching wrist. The structured search accepts supported filters; it does not pretend to answer every constraint a user might mention.
Run a real query
Save this as racket-example.mjs and run node racket-example.mjs with Node.js 22 or later. It needs no package installation or API key.
The example pins the 2025-06-18 protocol revision supported by this deployment. It targets our stateless JSON responses; it is not a general MCP client with SSE support. The transport specification describes the request headers and initialization flow.
// Node.js 22+. This small client targets PadelTrue's JSON responses,
// not every possible MCP transport. No package install or API key.
const endpoint = 'https://padeltrue.com/mcp';
let id = 0;
let protocol;
async function rpc(method, params = {}, notify = false) {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json, text/event-stream',
...(protocol ? { 'MCP-Protocol-Version': protocol } : {}),
},
body: JSON.stringify({ jsonrpc: '2.0', ...(!notify && { id: ++id }), method, params }),
signal: AbortSignal.timeout(15000),
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
if (response.status === 202) return;
const message = await response.json();
if (message.error) throw new Error(JSON.stringify(message.error));
return message.result;
}
async function call(name, args) {
const result = await rpc('tools/call', { name, arguments: args });
if (result.isError) throw new Error(JSON.stringify(result.structuredContent.error));
return result.structuredContent;
}
const init = await rpc('initialize', {
protocolVersion: '2025-06-18', capabilities: {},
clientInfo: { name: 'racket-example', version: '1.0.0' },
});
protocol = init.protocolVersion;
if (protocol !== '2025-06-18') throw new Error(`Unreviewed protocol: ${protocol}`);
await rpc('notifications/initialized', {}, true);
const shortlist = await call('search_rackets', {
query: 'nox at10 18k', year: 2026, shape: 'teardrop', limit: 3,
});
console.table(shortlist.results.map(({ name, year, page }) => ({ name, year, page })));
const candidate = shortlist.results[0];
if (!candidate) throw new Error('No match in this catalogue. Do not substitute another year.');
const { racket } = await call('get_racket', { slug: candidate.slug });
console.log(JSON.stringify({
name: racket.name, page: racket.page,
weight: racket.specifications.weight,
missingRatingInputs: racket.missingRatingInputs,
}, null, 2));
The search uses the exact year and shape. It returns the standard 2026 teardrop model in this snapshot. The detail response includes a weight object like this:
{
"value": "360 to 375 g",
"publishedAt": "https://noxsport.com/products/pala-at10-genius-18k-alum-2026-by-agustin-tapia",
"wordingOnThePage": "PESO: 360 - 375g",
"sourceType": "manufacturer"
}
Here, publishedAt is a source URL, despite the date-like name. Observed prices have a separate checkedOn date. This response also flags balance as a missing rating input in our record. Keeping those meanings explicit is more useful than sending a bare number and hoping the client reconstructs its context.
The returned page is the exact PadelTrue model page. A reader can inspect the evidence and available buying links there. The model can use that URL in its response, but a tool result cannot guarantee that every assistant will display it.
Test the questions the service cannot answer
We checked the deployed endpoint on 2 October, including these cases:
| Input | Observed behavior |
|---|---|
maxPrice: 150, without currency
|
currency_required error |
Unsupported shipsTo filter |
unknown_filter error |
| Invented model identifier |
not_found, with no substitute model |
| An exact year with no matching record | An empty result list |
| Same supported search over HTTP and MCP | Matching structured data |
These are useful failures. Silently dropping the shipping constraint or swapping in a different edition would produce a more confident-looking answer and a worse buying decision.
The successful answers also carry limits. A recorded price is dated evidence, not a live stock or delivery promise. Ratings are calculated from published specifications using our published formula, not court tests. Missing rating inputs use a neutral midpoint, and the response lists the gaps so a client can explain that uncertainty. A request without a playing priority defaults to name order, which is browsing rather than a best-match recommendation.
Keep the human experience connected
Our Racket Lab explores a related human question: when changing rackets, what do you want to keep, and what do you want to change?
βThe racket grams are only half the storyβ is the idea behind it. Listed mass is one input; balance, setup and the player's experience matter too. The Lab's personal estimate is an unvalidated heuristic, not measured swingweight or a medical assessment. That estimate is separate from the catalogue tools shown here.
The two interfaces should complement each other: an agent narrows the records and exposes the evidence; the person still gets a visual place to compare choices and decide what to try.
A practical starting point for another site
Pick one useful question your site already answers. Give it stable identifiers and a small response schema. Preserve the facts that qualify an answer, such as currency, edition, source and observation date. Then test what happens when those facts are missing.
You can inspect our agent documentation and HTTP examples, or get the open dataset and its GitHub snapshots under CC BY 4.0. That data licence does not include product photographs. We also publish /llms.txt as a plain-text map and /llms-full.txt with expanded content. These are entry points a client can choose to read, not a guarantee of discovery or citations.
We have listed the service in MCP directories, including Smithery. That is distribution, not proof of adoption. Our 16-question Claude API citation check on 1 October returned no PadelTrue citations. That small result is specific to that test, not a verdict on every assistant. Passing an integration test and being discovered by a buyer are separate milestones.
I would start with this small, testable contract before adding a conversational interface. Which constraint does your own product API currently drop instead of admitting that it cannot answer?
Disclosure: AI agents drafted this article and ran the code examples against the deployed PadelTrue service. It describes our own project; it is not an independent review. The examples reflect the deployment tested on 2 October 2026.
Top comments (0)