Guides · February 9, 2023
Build Your First MCP Server (a Storefront Data Example)
Building an MCP server means wrapping your data behind typed tools an AI client can call safely. This tutorial builds one from scratch using storefront-style data — themes, categories, and search — as the worked example.
By Polo Themes
To build an MCP server, you define a small set of typed tools and resources that expose your data or actions to an AI client, then run them behind a transport the client can connect to — stdio for a local desktop client, or streamable HTTP for a remote one. This guide builds a working example end to end: a server that exposes a storefront-style product catalog (themes, categories, search) as MCP tools, so an AI assistant like Claude Desktop can answer questions like "which Shopify themes do you have for a course platform" by calling real functions instead of guessing from training data.
If you have never touched MCP before, treat this as your first working reference implementation. If you already have a server running, skip ahead to the sections on resources versus tools, error handling, and the security checklist — that is where most home-grown servers go wrong.
What MCP Actually Is
The Model Context Protocol is an open, JSON-RPC-based standard for connecting AI applications to external systems. Before MCP, every AI product that wanted to read your database, call your API, or search your docs had to write a bespoke integration — one for ChatGPT plugins, a different one for a LangChain agent, another for a Claude-specific tool schema. MCP standardizes that boundary. You write one server; any MCP-compatible client (Claude Desktop, Claude Code, an IDE assistant, a custom agent runtime) can talk to it the same way.
An MCP server exposes three kinds of things to a client, and picking the right one for each piece of functionality is the first real design decision you will make:
- Tools — functions the model can call with arguments and get a result back. Use these for anything action-shaped or query-shaped: "search themes by category," "get pricing for a theme," "place an order." The model decides when to call a tool based on the conversation.
- Resources — addressable, read-only data the client can fetch directly, similar to a file or a URL. Use these for reference material the model (or the human) might want to browse without a specific question in mind — a style guide, a changelog, a full catalog export.
- Prompts — reusable, parameterized prompt templates the server exposes so a client can offer them as slash-commands or quick actions. Less commonly used than tools, but useful for encoding a workflow you want repeated consistently.
Most first servers are tools-only, and that is a reasonable place to stop. Resources and prompts are worth adding once you know which parts of your data genuinely benefit from being browsed rather than queried.
The Example: A Storefront Catalog Server
Rather than a toy "add two numbers" example, this tutorial builds something closer to what you would actually ship: a server that sits in front of a small product catalog — the kind of data a Shopify or headless storefront already has — and exposes it through three tools: list categories, search products by keyword or category, and get full details for one product by its handle. This mirrors a real use case: letting an AI assistant answer product questions grounded in your actual inventory instead of hallucinating specs.
Start a new Node project with TypeScript, and add the official MCP SDK for TypeScript as your one real dependency, plus zod for input validation — the SDK uses zod schemas to both validate tool arguments and generate the JSON schema the client sees. Set your project to output CommonJS or ESM consistently; the SDK supports both, but pick one and do not mix module systems inside the same server, which is a common source of confusing runtime errors early on.
Step 1: Model your data source
Before writing any MCP-specific code, write the plain functions your tools will wrap. This separation matters: your MCP layer should be a thin adapter over functions you could unit test and call from anywhere, not a place where business logic lives. For the catalog example, write three ordinary async functions — one that returns the list of categories, one that filters products by a search term and optional category, and one that looks up a single product by its handle and returns full details. Back these with whatever you actually have: a JSON file for a prototype, a database call, or a fetch against your storefront's existing product API. The MCP wrapper does not care.
Step 2: Register tools with clear names and strict schemas
Create the server object from the SDK, giving it a name and version string — these show up in client logs and debugging tools, so make them descriptive rather than generic. Then register each tool with four things: a unique snake_case name, a one-to-two sentence description written for the model (not for a human developer — the model reads this description to decide when to call the tool), a zod input schema describing every argument precisely, and a handler function that calls your underlying data function and returns the result.
Two details matter more than they look. First, tool descriptions should state constraints explicitly — if search results cap at twenty items, say so in the description, not just in a code comment, because the model only ever sees the description. Second, keep argument schemas narrow: an optional category filter should be an enum of your actual known categories rather than a free string, because a constrained schema both improves the model's accuracy and closes off a class of injection-style misuse where a client passes unexpected values through to a downstream query.
Step 3: Return structured, bounded results
A tool handler must return content in the shape the protocol expects — typically a text block, though the spec also supports images and, for compatible clients, structured JSON content alongside the text. Resist the temptation to dump your entire raw database row into the response. Trim to the fields relevant to the tool's stated purpose, cap list lengths, and format prices and other display values the way you would want a human to read them, since the model will often relay your text back to the user close to verbatim. If a query returns nothing, return a clear "no results" message rather than an empty array with no explanation — this measurably improves how gracefully the model recovers in conversation.
Step 4: Handle errors as tool results, not exceptions
MCP has a specific convention here that trips up almost everyone building their first server: expected, recoverable errors — a not-found product handle, an invalid category, a malformed date range — should be returned as a normal tool result with an error flag set, not thrown as a JavaScript exception. Throwing should be reserved for genuinely unexpected failures (a database connection drop, a bug). The distinction matters because the client surfaces flagged tool errors to the model as part of the conversation, letting it try a different approach or ask a clarifying question, whereas an unhandled exception typically just breaks the exchange with a generic transport-level failure the model cannot reason about.
Step 5: Wire up a transport
For a first local server meant to run inside Claude Desktop or Claude Code, use the stdio transport — the SDK provides a ready-made stdio server transport class; you construct it and connect your server instance to it in a couple of lines. This is the right choice for anything running on the same machine as the client, since there is no networking, authentication, or CORS to think about. If you later need remote access — a server other people's clients connect to over the internet — move to the streamable HTTP transport, which is the current recommended remote transport in the spec (the older HTTP+SSE transport is deprecated). Remote transports bring a genuinely different set of concerns: you now need authentication (the spec leans on OAuth 2.1), rate limiting, and CORS configuration, so do not reach for a remote transport until a local one has proven the tool design actually works.
Testing the Server Before You Trust It
Do not connect an unfinished server straight to a chat client and try to debug it through conversation — you will spend more time guessing what the model saw than fixing your code. Use the official MCP Inspector instead: it is a small web-based tool you run alongside your server that lists every registered tool, lets you call each one directly with hand-entered arguments, and shows you the raw JSON-RPC request and response. Confirm each tool's schema renders the way you expect, that valid inputs return sensible output, and that invalid inputs return a flagged error result rather than crashing the process. Only after a tool behaves correctly in the Inspector should you point a real client at it.
Once the Inspector checks out, register the server with an actual client. For Claude Desktop and Claude Code, this means adding an entry to the client's MCP server configuration that points at the command used to launch your server (for a stdio server, typically the node command and your entry-point file). Restart the client, confirm the server appears as connected, and then run a handful of natural-language prompts that should trigger each tool — "what categories of themes do you have," "find me something for a course platform," "tell me more about the wosa theme" — and watch whether the model calls the right tool with sensible arguments. If it is calling the wrong tool, or not calling one at all, the fix is almost always to sharpen the tool description rather than the underlying code.
Security and Trust Boundaries
An MCP server is a new kind of trust boundary, and it deserves the same scrutiny as any other externally-callable API, arguably more, because the caller is a model that reasons probabilistically rather than a deterministic client following your exact instructions.
- Never let a tool execute arbitrary shell commands, raw SQL, or unsandboxed file writes based on model-supplied arguments — treat every argument as untrusted input, exactly as you would a public HTTP endpoint.
- Keep read tools and write tools clearly separated, and put a real confirmation step (either in your client's UI or in the tool design itself) in front of anything that mutates data, sends an email, or spends money.
- Scope credentials narrowly. A server that only needs to read a product catalog should hold read-only credentials, not the same database user your admin dashboard uses.
- If you expose the server remotely, put real authentication in front of it — do not rely on obscurity of the URL. The streamable HTTP transport is designed to carry standard bearer-token or OAuth flows; use them.
- Log tool calls and their arguments somewhere you can audit later. When something goes wrong with an AI-driven flow, the call log is usually the fastest way to see what actually happened versus what you assumed happened.
Where This Fits in a Modern Storefront Stack
MCP servers are becoming a natural companion to headless and Next.js-based commerce builds, sitting alongside your existing storefront API rather than replacing it. A catalog MCP server like the one above lets an internal support agent, a merchandising assistant, or a customer-facing chat experience query real product data through a controlled, auditable interface instead of scraping your site or working from a stale export. The same pattern extends past product data: an order-lookup tool, a shipping-status tool, or a content-search tool over your blog and documentation are all natural next servers once the first one is working.
This is squarely part of where commerce tooling is heading, and it is a direction we are actively building toward at Polo Themes as we expand from design assets into more code-adjacent, AI-native territory. Today, what we ship is Figma UI kits and Shopify OS 2.0 themes — real, purchasable products, not roadmap items. An MCP starter kit, a component registry, or agent-ready commerce templates are directions we are exploring for the future, not something available to buy right now, and we want to be upfront about that distinction rather than blur it. If you are building headless or AI-integrated storefronts today, the pattern in this tutorial — thin, well-scoped MCP tools over real data, tested before they are trusted — will serve you regardless of which commerce platform or design system sits underneath.
Common Mistakes When Building Your First Server
- One giant tool instead of several narrow ones. A single "query" tool that accepts a free-text query and tries to do everything is harder for the model to use correctly than three or four narrow tools with clear names and constrained arguments.
- Descriptions written for developers, not models. The tool description is the only thing the model sees before deciding to call it — write it as documentation aimed at the caller, including edge cases and limits.
- Unbounded responses. Returning your full database table because the filter matched everything will blow past context limits and degrade the conversation. Always cap and paginate.
- Throwing on expected failures. As covered above, use the protocol's error-content convention for anything recoverable; reserve real exceptions for genuine bugs.
- Skipping the Inspector. Debugging tool schemas through a live chat conversation is slow and ambiguous. The Inspector gives you a direct, deterministic view of what your server actually returns.
Frequently Asked Questions
Do I need to know the full MCP spec to build a server?
No. The official SDKs (TypeScript, Python, and others) handle the JSON-RPC framing, capability negotiation, and transport details for you. You mainly need to understand the tools-versus-resources-versus-prompts distinction and how to register handlers correctly — the low-level wire protocol is not something you will touch directly for a typical server.
Should my first server use stdio or HTTP?
Start with stdio if the server will run locally alongside a desktop client like Claude Desktop or Claude Code — it is simpler, has no networking surface, and is the fastest path to a working prototype. Move to the streamable HTTP transport only once you actually need remote clients to connect, since that introduces authentication and CORS concerns you do not need to solve on day one.
Can an MCP server replace my existing REST or GraphQL API?
Not really, and it should not try to. Treat the MCP server as a thin, model-facing adapter in front of the API and data functions you already have, with tool schemas and descriptions tuned for how a language model reasons about arguments and results. Your existing API keeps serving your web and mobile clients unchanged.
Is MCP specific to Claude, or does it work with other AI clients?
MCP is an open protocol, and while Anthropic originated it and Claude Desktop and Claude Code are prominent clients, a growing set of other AI applications and agent frameworks implement it as well. A server built against the standard SDK should work with any compliant client, which is the entire point of the protocol.