Building Custom MCP Servers
End-to-end MCP server: tools, resources, prompts, error handling, and client registration.
Why
MCP launched in November 2024; by December 2025 it had 97M+ monthly SDK downloads, and by September 2026 the npm SDK alone reached ~204M monthly downloads (5x growth in 9 months) with 10,000+ published servers. The existing intro covers one tool.
Real servers expose several tools, read-only resources, reusable prompt templates, and must handle errors without crashing the client. Getting all four right is what makes a server production-grade instead of a demo that works once and silently fails on edge cases.
When
Build a full server when an AI client needs durable access to your systems (a database, a ticket tracker, a deploy API) across many sessions, and when a single tool is not enough. Skip it for one-off scripts a prompt with curl can cover.
How
Example
Common Problems
- ⚠Throwing in a tool handler crashes the client session. Always return { isError: true, content } instead.
- ⚠"Error: transport closed" / server not listed by client → verify the stdio/HTTP transport matches on both sides; check `npx @modelcontextprotocol/inspector` output.
- ⚠Tool call returns `{"error": "Invalid params"}` → validate the input schema with zod; common cause is missing required field or wrong type.
- ⚠Error: Cannot find module '@modelcontextprotocol/sdk' → run `npm install @modelcontextprotocol/sdk zod`; the SDK requires Node.js 18+.
Best Practices
- ✓Design the capability surface (tools/resources/prompts) on paper before writing any handler.
- ✓Return typed errors via isError instead of throwing; the client stays alive and can retry.
- ✓Pin the SDK version and run the MCP Inspector in CI to catch protocol regressions.
- ✓Keep resource payloads under ~4KB; paginate or summarize larger data.
- ✓Write tool descriptions with a usage example so the AI reliably picks the right tool.
FAQ
What's the difference between MCP tools and resources?
Tools are actions the AI can call (query_db, insert_ticket) with side effects; resources are read-only data the AI can browse (schema://db/tables). Use tools for mutations and queries, resources for context the AI should read before acting.
How do I add reusable prompt templates to an MCP server?
Implement the prompts capability: define a named prompt with arguments (e.g., 'investigate-incident' with incident_id) and a handler returning the rendered prompt pre-filled with resource data. The client can then offer it as a slash-command-like template.
How do I test an MCP server before deploying?
Run the MCP Inspector: `npx @modelcontextprotocol/inspector node dist/server.js`. It lets you call tools, read resources, and inspect protocol messages interactively. Always test with the inspector before registering with a real client.
How do I keep resource payloads from blowing the context window?
Cap each resource read to ~4KB and paginate or summarize larger data. Return JSON for structured data so the AI can parse it. Huge unbounded payloads waste context and degrade the AI's reasoning on subsequent turns.
How do I handle a tool call with missing parameters?
Validate against your inputSchema and return { isError: true, content: [{ type: 'text', text: 'title is required' }] }. Never throw — throwing crashes the client session. Strict schemas with required fields prevent the AI from calling tools without mandatory params.