← All posts

Building an MCP Server From Scratch: What I Learned

Notes from building job-search-mcp, a small MCP server, and what the protocol actually asks of you once you're the one exposing tools instead of consuming them.

mcpagentsai-engineering

Why build a server instead of just using one

As a daily Claude Code user, I'd already used MCP servers built by other people. As a client, the protocol is invisible, which is the point. To actually understand it, I needed to be the one exposing tools, not just calling them. That's what job-search-mcp started as: a small, focused MCP server that exposes job-search related tools to MCP-compatible clients.

What MCP actually standardizes

The useful mental model I landed on: MCP is not "an API for LLMs," it's a protocol for describing capabilities in a way any compliant client can discover and call, without custom integration code per tool. Three things the server needs to get right:

  • Tool definitions: name, description and a schema for arguments, written for a model to read, not just a human. The description is doing real work: it's the only thing the model sees when deciding whether and how to call your tool.
  • Transport: how the client and server actually talk (stdio for local tools, HTTP-based transport for remote ones). Getting this wrong looks like a silent hang, not a clear error, which cost me more debugging time than I expected.
  • Structured responses: returning results in a shape the model can reliably parse and reason about, not just human-readable text.

The part that surprised me: description quality is the interface

Coming from front-end work, I'm used to thinking about interface design as pixels and interaction states. With MCP, the "interface" for a tool is almost entirely its name and description string. A vague description leads to a model either never calling the tool or calling it with the wrong arguments, and there's no user watching a screen to notice the mismatch. Writing tool descriptions ended up feeling closer to writing a good function signature and docstring than writing UI copy, but with higher stakes, since the only consumer is a model deciding autonomously whether to use it.

Error handling looks different

In a typical REST API, a 400 with a clear message is enough: a developer reads it and fixes their request. In an MCP tool, the "developer" reading your error might be the model itself, mid-agentic-loop, trying to decide whether to retry, try different arguments, or give up. That pushed me toward returning errors that are explicit about what went wrong and what a valid call looks like, rather than terse status codes.

What's still rough

job-search-mcp is a personal project, not a polished product. It's genuinely useful for understanding the protocol, but I wouldn't call it production-hardened. The things I'd still want to improve: better input validation at the schema level (catch bad calls before they hit application code), and more thoughtful pagination for tools that can return large result sets.

Where the code lives

The repo is public: github.com/edefice/job-search-mcp. If you're evaluating whether I understand MCP beyond the buzzword, that's the place to check, not this post.