Build Your Own MCP Server for Claude: Tools, Resources, Prompts

Source topic: MCP (Model Context Protocol), flagged in the "AI Engineering Roadmap 2026" as a 2026-specific skill.
Stack: mcp Python SDK (or TypeScript), Claude Desktop / Claude Code.
What MCP actually is (in one paragraph)
MCP is the plug standard between LLMs and external systems. Before MCP, every tool you wanted Claude to use needed a custom integration per LLM host. With MCP, you write a server once (exposing tools, resources, prompts) and any MCP-aware client (Claude Desktop, Claude Code, Cursor, etc.) can connect to it. Think "USB-C for LLMs."
Three things an MCP server can expose:
- Tools: functions the LLM can call (
query_database,send_email) - Resources: readable content (files, API responses) the LLM can pull
- Prompts: pre-baked prompt templates the user can invoke
Why this is a genuine resume skill
Every serious LLM deployment is moving to MCP. Writing your own MCP server shows you understand:
- Tool schemas (JSON Schema)
- Stateful session handling between LLM and server
- The protocol contracts that make agents interoperable
- How to expose internal company systems to LLMs safely
Build: a "Company Wiki" MCP server
Scenario: expose a local markdown wiki to Claude so it can search, read, and summarize pages.
1. Install
pip install mcp
# or for TypeScript:
# npm install @modelcontextprotocol/sdk
2. Full server: wiki_server.py
import os, re
from pathlib import Path
from mcp.server.fastmcp import FastMCP
WIKI_ROOT = Path(os.environ.get("WIKI_ROOT", "./wiki"))
mcp = FastMCP("company-wiki")
# ---------- TOOLS ----------
@mcp.tool()
def search_wiki(query: str, limit: int = 10) -> list[dict]:
"""Full-text search across all wiki pages. Returns hits with page title, path, and matched snippet."""
hits = []
pattern = re.compile(re.escape(query), re.IGNORECASE)
for md in WIKI_ROOT.rglob("*.md"):
text = md.read_text(encoding="utf-8", errors="ignore")
if match := pattern.search(text):
start = max(0, match.start() - 80)
end = min(len(text), match.end() + 80)
hits.append({
"path": str(md.relative_to(WIKI_ROOT)),
"title": md.stem.replace("-", " ").title(),
"snippet": text[start:end].replace("\n", " "),
})
if len(hits) >= limit:
break
return hits
@mcp.tool()
def read_page(path: str) -> str:
"""Read a single wiki page by its relative path."""
full = WIKI_ROOT / path
if not full.exists() or WIKI_ROOT not in full.resolve().parents and full.resolve() != (WIKI_ROOT / path).resolve():
return f"Page not found or outside wiki: {path}"
return full.read_text(encoding="utf-8", errors="ignore")
@mcp.tool()
def list_pages(folder: str = "") -> list[str]:
"""List all wiki pages, optionally under a subfolder."""
base = WIKI_ROOT / folder if folder else WIKI_ROOT
return sorted(str(p.relative_to(WIKI_ROOT)) for p in base.rglob("*.md"))
# ---------- RESOURCES ----------
@mcp.resource("wiki://{path}")
def page_resource(path: str) -> str:
"""Expose individual pages as readable resources Claude can pull without invoking a tool."""
return read_page(path)
# ---------- PROMPTS ----------
@mcp.prompt()
def onboarding_summary() -> str:
"""Prompt template: summarize the onboarding-tagged pages for a new hire."""
return (
"Search the wiki for pages tagged 'onboarding'. For each, pull the page and "
"produce a concise summary grouped by theme (accounts, tooling, team norms, code review)."
)
if __name__ == "__main__":
mcp.run(transport="stdio")
3. Register with Claude Desktop
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"company-wiki": {
"command": "python",
"args": ["/absolute/path/to/wiki_server.py"],
"env": {
"WIKI_ROOT": "/absolute/path/to/your/wiki"
}
}
}
}
Restart Claude Desktop. You should see a plug icon with company-wiki connected. Claude can now call search_wiki, read_page, and list_pages.
4. Register with Claude Code (CLI)
claude mcp add company-wiki \
--command python \
--args /absolute/path/to/wiki_server.py \
--env WIKI_ROOT=/absolute/path/to/your/wiki
Or edit .claude/settings.json:
{
"mcpServers": {
"company-wiki": {
"command": "python",
"args": ["./wiki_server.py"],
"env": {"WIKI_ROOT": "./wiki"}
}
}
}
Level up: remote HTTP MCP server
For team use, run MCP over HTTP (SSE transport) so several people share one server.
# http_server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("company-wiki", host="0.0.0.0", port=8765)
# (same @mcp.tool definitions as above)
if __name__ == "__main__":
mcp.run(transport="sse") # Server-Sent Events
Client config:
{
"mcpServers": {
"company-wiki": {
"transport": "sse",
"url": "http://wiki-mcp.internal:8765/sse"
}
}
}
Add auth with a reverse proxy (nginx plus a token). MCP itself doesn't enforce it.
Level up #2: tools that write, not just read
The wiki example is read-only. For a more impressive demo, add a create_page tool:
@mcp.tool()
def create_page(path: str, title: str, content: str) -> str:
"""Create a new wiki page. Requires human approval — Claude Desktop will prompt."""
full = WIKI_ROOT / path
if full.exists():
return f"Page already exists: {path}"
full.parent.mkdir(parents=True, exist_ok=True)
full.write_text(f"# {title}\n\n{content}\n", encoding="utf-8")
return f"Created: {path}"
Claude will show the user the proposed write and ask for approval before executing. That's the MCP security model: the client gates destructive actions.
Debugging checklist
- Server won't start in Claude Desktop: check logs at
~/Library/Logs/Claude/mcp-server-company-wiki.log(macOS) or%APPDATA%\Claude\logs\(Windows). - Tool doesn't appear: confirm the
@mcp.tool()decorator is applied and the function has a docstring (Claude uses it as the description). - Type errors: MCP inspects Python type hints to generate JSON Schema. Use
list[dict], not a barelist. - Stdio hang: don't print to stdout inside tool functions. stdout is the transport. Use
loggingto stderr instead.
What makes this different from a REST API
| MCP server | REST API |
|---|---|
| LLM discovers tools at connection time | You hardcode endpoints |
| Schemas are strongly typed (JSON Schema) | OpenAPI optional |
| Built-in tool-use / human-approval loop | You roll your own |
| Stateful session (can keep context) | Usually stateless |
| Standard across all MCP-aware clients | Per-client integration |
Resume angle
"Shipped an MCP server exposing internal knowledge-base search as first-class tools for Claude: search, read, and create pages over stdio and SSE transports. Demonstrated the agent-to-system integration pattern that replaces bespoke LLM tool wiring."