Most web tools have the same distribution problem: users have to find your site, visit it, and learn your UI. But increasingly, people don't start tasks on websites — they start them in a chat with an AI assistant.
The Model Context Protocol (MCP) lets you flip this. Instead of hoping users find your site, you expose your tool as something Claude (or any MCP client) can call directly inside a conversation. The user asks "is this VIN safe to buy?", the assistant calls your API, and your product does the work — without the user ever opening a browser tab.
I run CheckMyVIN.net, a free VIN decoder that pulls vehicle specs and open safety recalls from the official NHTSA database. This post covers how I added an MCP server to it, what design decisions mattered, and what I'd do differently.
What MCP actually is (30-second version)
MCP is an open protocol (originally from Anthropic, now adopted more widely) that standardizes how AI assistants discover and call external tools. Your server declares a set of tools with JSON Schema inputs; the client (Claude Desktop, Claude.ai, increasingly others) presents them to the model; the model decides when to call them mid-conversation.
The key insight for web app builders: a remote MCP server is just an HTTP endpoint. If your app already has an API, you're 80% of the way there.
Why bother
Three reasons convinced me:
- New distribution channel, zero competition. Every VIN decoder competes for the same Google keywords. Almost none of them are callable from an AI assistant. When someone asks Claude to check a VIN, there's currently a very short list of tools that can answer.
- It's cheap to build. My MCP server is a thin wrapper over existing logic. The decode pipeline (NHTSA VPIC API → recalls API → maintenance DB → AI summary) already existed for the website; the MCP layer reuses all of it.
- AI traffic is real and growing. I already allow GPTBot, ClaudeBot, and PerplexityBot in robots.txt and serve an llms.txt. MCP is the active version of that strategy — instead of waiting to be crawled, you're directly usable.
Designing the tools
The biggest design decision isn't code — it's tool granularity. My first instinct was to mirror my internal API: separate tools for decode, recalls, maintenance specs, and summary. That's wrong for MCP.
LLMs perform better with fewer, more complete tools. Four chained calls means four chances for the model to mis-sequence them, plus a slower conversation. I ended up with exactly two:
-
decode_vin— takes a VIN, returns the full report: decoded specs, open recalls, maintenance specs, and a link to the shareable report page. -
check_recalls— takes a VIN, returns only open safety recalls. Exists because "does this car have recalls?" is the single most common question, and the full report is overkill for it.
Rule of thumb: design tools around user questions, not around your API routes.
Tool descriptions are prompts
The model decides whether to call your tool based entirely on the name and description. Treat these as prompt engineering, not documentation.
My first description was "Decodes a VIN number." Too vague — the model didn't reliably know what it would get back. The version that works:
{
"name": "decode_vin",
"description": "Decode a 17-character VIN and return a complete vehicle report: make, model, year, engine, transmission, plant, open NHTSA safety recalls, and maintenance specs (oil type, tire size, fluids). Use when the user provides a VIN or asks about a specific vehicle's specs, recalls, or whether a used car is safe to buy. Returns a shareable report URL.",
"inputSchema": {
"type": "object",
"properties": {
"vin": {
"type": "string",
"description": "17-character Vehicle Identification Number, e.g. 5YJ3E1EA7KF317000"
}
},
"required": ["vin"]
}
}
Three things that mattered:
- List what's returned. The model uses this to decide if the tool answers the user's question.
- Say when to use it. "Use when the user asks whether a used car is safe to buy" triggers calls that "Decodes a VIN" never would.
- Give an input example. VINs are weird strings; one example eliminated most malformed calls.
Return structured text, plus a link back
MCP tool results are content blocks. I return a structured, human-readable text block — the model summarizes it naturally — and always include the report URL at the end:
Vehicle: 2019 Tesla Model 3 Long Range
Powertrain: Electric (battery: 75 kWh)
⚠ Open recalls: 1
- 21V-035: Touchscreen failure may affect defrost...
Maintenance: tire size 235/45R18, no engine oil (EV)
Full printable report: https://checkmyvin.net/report/5YJ3E1EA7KF317000
That last line is the bridge from the AI conversation back to your product. The assistant almost always passes the link to the user, and the user clicks when they want the printable version. Every tool result should contain a reason to visit your site.
One domain-specific detail: for EVs, the result omits oil specs and includes battery info instead. Most VIN tools happily tell you what oil your Tesla needs. Don't be that tool — and more generally, when you don't have real data, say so explicitly in the result ("exact tire size varies by trim — check the door jamb sticker") rather than letting the model fill the gap with a guess. Hallucination prevention starts in your tool output.
Edge runtime notes
The whole site runs on Cloudflare Workers, and the MCP endpoint is just another route (/api/mcp). A few things that came up:
- Streamable HTTP transport is what you want for remote MCP servers — it's plain HTTP request/response, fits the Workers model perfectly. No long-lived processes needed.
- Reuse your cache. VIN lookups are cached in Workers KV with a 7-day TTL for the website; MCP calls hit the same cache. A repeat VIN costs zero upstream API calls regardless of which surface asked.
- Stateless is fine. MCP supports sessions, but for a tool like this every call is independent. Don't add session state you don't need.
- Validate hard at the boundary. I check the VIN format (17 chars, valid check digit) before any upstream call, and return a clear error message the model can relay ("That VIN appears invalid — VINs are 17 characters and exclude I, O, Q").
Connecting it
For Claude Desktop users, the config is a few lines via the mcp-remote bridge:
{
"mcpServers": {
"checkmyvin": {
"command": "npx",
"args": ["mcp-remote", "https://checkmyvin.net/api/mcp"]
}
}
}
Claude.ai supports adding remote MCP servers (custom connectors) directly by URL. Either way, after connecting, "check recalls on VIN 5YJ3..." just works inside the conversation.
Results and takeaways
It's early, but a few things are already clear:
- The MCP server took roughly a day to build on top of an existing API. The leverage-to-effort ratio is excellent.
- MCP directories (mcp.so, PulseMCP, Smithery, Glama) are a discovery channel with very little competition right now. Listing there took an afternoon.
- The conversations that reach the tool are higher-intent than search traffic. Someone asking an AI "should I buy this specific car?" is exactly the user the report page was built for.
If your web app does something useful with a clear input → output shape, an MCP server is probably the cheapest distribution experiment you can run this year.
Try it: the server is live at https://checkmyvin.net/api/mcp, and the web version is at checkmyvin.net. Questions about the implementation welcome in the comments.


Top comments (0)