Electronic Markets TR — MCP Server (Rust)
A lightweight Model Context Protocol server that searches Turkish electronics e-commerce sites in parallel and returns normalized product, price (TRY) and stock data for AI agents.
Purpose
Give LLM agents one tool to compare prices and stock across Turkish electronics markets, without loading pages or scraping HTML themselves. The server returns raw page-one search hits; ranking and relevance judgment stay with the agent.
Supported markets
Architecture
src/
domain/ Product, Price/Money (kurus-backed integer), StockStatus, Market, SearchQuery
parsing.rs Turkish price/stock text parsing (pure functions, no I/O)
http.rs Shared reqwest client: pooling, timeouts, bounded retry
scrapers/ One independent module per market (fetch + site-specific parser)
search.rs Concurrent orchestration: all 5 markets in parallel, per-market timeout,
partial results on failure
pipeline.rs Stock filter + URL-canonicalization dedup (pure functions)
mcp_service.rs rmcp tool server + shared search execution
main.rs stdio entry point
Design notes:
- Money:
kurus(1/100 TRY) stored asi64; no floating point anywhere near prices. A missing price isPrice::Unavailable, distinct from zero. - Concurrency: all markets are queried concurrently (bounded by the static market count). A 20 s per-market timeout turns a slow site into a per-market error without affecting other markets.
- Errors: typed (
HttpError,ScrapeError); market failures are reported in the response payload (marketErrors) instead of failing the whole search.
Setup
Requires Rust 1.85+ (edition 2024).
cargo build --release
Configuration
Environment variables:
RUST_LOG— log filter (default: no logs on stdout; logs go to stderr, e.g.RUST_LOG=warn)
HTTP behavior (timeouts, retries, pool size) is configured in
src/http.rs::HttpClientConfig with sensible defaults: 5 s connect timeout, 15 s request
timeout, 1 retry for transient failures (5xx/timeout/network) only.
MCP usage
Register in an MCP client (e.g. Claude Desktop):
{
"mcpServers": {
"electronic-markets-tr": {
"command": "/absolute/path/to/mcp-electronicmarkets-tr/target/release/mcp-electronicmarkets-tr"
}
}
}
Tool: search_products
Response (JSON):
{
"query": "esp32",
"onlyInStock": true,
"resultCount": 5,
"products": [
{
"market": "robotistan",
"name": "ESP32-CAM ...",
"price": { "available": { "kurus": 92320 } },
"stockStatus": "in_stock",
"url": "https://www.robotistan.com/..."
}
],
"marketErrors": []
}
Development
cargo check # fast type check
cargo test # unit + fixture + MCP protocol tests (offline)
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --check
Testing
- Unit tests (
src/): Turkish price parsing, stock text parsing, URL building, filtering, deduplication, query validation. - Fixture tests (
tests/scraper_fixtures.rs,tests/robocombo_fixture.rs): real HTML/JSON captured from the live sites; parser output verified against live-observed counts (e.g. Ceriyanesp32-> 10 products, 5 in stock / 5 out of stock). Includes malformed-HTML and missing-field safety tests. - MCP protocol tests (
tests/mcp_protocol.rs): full rmcp handshake over an in-memory duplex transport — tool registration, input schema shape, invalid-parameter rejection.
Live smoke testing
Hits the real sites; ignored by default so cargo test stays offline:
cargo test --test live_smoke -- --ignored --nocapture
Verifies all five markets with three queries: arduino uno, esp32, 220 ohm resistor.
Each market is reported independently; one failing market does not hide others' results.
Performance comparison (concurrent vs sequential search) can be run with:
cargo run --release --example perf_search
Measured on a residential connection (2026-09-10): concurrent 2.4 s vs sequential
7.4 s for esp32 (~3.1x speedup); results vary with per-site latency. Working set
during a search: ~14 MB.
Release build
cargo build --release
# binary: target/release/mcp-electronicmarkets-tr
Adding a new scraper
- Add a variant to
Market(src/domain/market.rs) and include it inMarket::ALL. - Create
src/scrapers/<market>.rswithpub async fn search(http: &HttpClient, query: &SearchQuery)plus a purepub fn parse_search_page(&str) -> Vec<Product>. - Wire the variant in
search.rs::scrape_market. - Capture a real fixture (e.g.
curl -A "Mozilla/..." <search-url> > tests/fixtures/<market>_search.html) and add a fixture test. - Verify with live smoke tests before shipping.