Building a Production MCP Server from Scratch
2026-08-20 · 4 min read
Why I Built an MCP Server
When I started building Career-Agents, I needed a way for LLM clients — Claude Desktop, Cursor, Windsurf — to invoke backend career-intelligence tools reliably. The Model Context Protocol (MCP) was the right abstraction: a standard interface for exposing tool definitions to LLM clients with typed inputs, typed outputs, and no hallucinated tool names.
This post covers what I learned building a production MCP server from scratch.
What is MCP?
MCP is an open protocol that defines how an LLM client (the "host") discovers and calls tools exposed by a server. The server publishes a list of tool definitions — each with a name, description, and JSON Schema for inputs and outputs — and the client calls them during inference.
The key insight: the tool schema is the contract. Get it right and your agent is reliable. Get it wrong and the LLM hallucinates arguments, calls the wrong tool, or misinterprets results.
Tool Schema Design
Each of my 10 tools follows this pattern:
{
name: "assess_career",
description: "Analyze a candidate's background and return career readiness scores across technical, communication, and portfolio dimensions.",
inputSchema: {
type: "object",
properties: {
resume_text: { type: "string", description: "Raw resume text" },
target_role: { type: "string", description: "Target job title, e.g. 'Senior AI Engineer'" },
},
required: ["resume_text", "target_role"],
},
}
Lessons learned on schema design:
- Be specific in descriptions — "Analyze a candidate's background" is better than "Process resume". The LLM uses the description to decide when to call the tool.
- Use
requiredstrictly — optional fields often cause the LLM to omit them even when they'd improve results. - Return structured JSON — not prose. Prose outputs are hard for the orchestrator to pass to the next tool.
The 10 Tools
| Tool | Function |
|---|---|
assess_career |
Career readiness scoring |
optimize_resume |
ATS keyword and bullet optimization |
generate_interview_questions |
Role-specific interview preparation |
analyze_job_fit |
Job description vs candidate match scoring |
recommend_career_path |
Next role and skill gap recommendations |
audit_github_portfolio |
Repository and profile analysis |
optimize_linkedin |
Headline and summary optimization |
search_companies |
Company intelligence lookup |
recommend_skills |
Skill gap analysis and learning path |
voice_lab |
Spoken voice agent interface (27 languages) |
Error Handling
The biggest reliability issue I hit was query-matching quirks in search and recommendation tools. When the LLM passes a slightly malformed query, the tool would return an empty result set — and the agent would silently fail.
Fix: validate and normalize all inputs before hitting the backend, and return structured error objects (not thrown exceptions) so the agent can retry with a corrected query:
if (!input.resume_text || input.resume_text.trim().length < 50) {
return {
error: "resume_text_too_short",
message: "Resume text must be at least 50 characters.",
suggestion: "Ask the user to provide more resume detail.",
};
}
Client Integration
The MCP server works with Claude Desktop, Cursor, and Windsurf out of the box:
// claude_desktop_config.json
{
"mcpServers": {
"career-agents": {
"command": "npx",
"args": ["-y", "career-agents", "mcp"]
}
}
}
Cursor / Windsurf: Add the same config to .cursor/mcp.json or .windsurf/mcp.json.
Publishing to NPM
The full Career-Agents platform — including the MCP server — is published as a global CLI:
npm install -g career-agents
career-agents mcp # starts the MCP server
career-agents assess # runs the career assessment agent
Publishing as a global CLI means any developer can add Career-Agents tools to their LLM client in seconds, without cloning the repo.
What I'd Do Differently
- Version tool schemas explicitly — when I changed an input field name, existing Claude Desktop configs broke silently.
- Add a health-check tool — a
pingtool that returns server version and available tools makes debugging much easier. - Log every invocation — structured logs of tool name, input hash, latency, and result type are invaluable in production.
Try It
npm install -g career-agents
career-agents doctor # check your environment
career-agents assess # run the career assessment