Skip to main content
Early access — new tools and guides added regularly
🟣 Power User Workflows — Guide 12 of 17
View track
>_ claude codeAdvanced40 min

Building MCP Servers from Scratch

Build custom MCP (Model Context Protocol) servers that extend Claude Code with new tools, data sources, and integrations — from protocol basics to deployment.

What you will build
A production-ready MCP server with custom tools, resource access, and error handling
Prerequisites
Getting Started with Claude Codemcp-servers-connect-everything

Understanding the Model Context Protocol

The Model Context Protocol (MCP) is a standard that lets AI assistants like Claude Code connect to external tools and data sources. Without MCP, Claude Code is limited to what it can do in the terminal — read files, run commands, and write code. With MCP, Claude Code can query databases, call APIs, interact with cloud services, manage infrastructure, and access any system you build a connector for. An MCP server is a program that exposes tools and resources to Claude Code. A tool is an action Claude Code can perform (like query_database or create_ticket). A resource is data Claude Code can read (like the contents of a database table or a remote configuration file). The protocol uses JSON-RPC over stdio — the server reads JSON messages from stdin and writes JSON responses to stdout. Ask Claude Code: Create a new Node.js project with TypeScript for an MCP server. Install the MCP SDK: npm install @modelcontextprotocol/sdk. Create the server entry point at src/index.ts. Initialize the MCP server with a name and version. Register a simple tool called hello that takes a name parameter and returns a greeting. This is the minimal MCP server — one tool, one response. Test it by configuring Claude Code to use your server. Ask Claude Code: Show me how to add this MCP server to my Claude Code configuration at ~/.claude/settings.json under the mcpServers key. The configuration specifies the command to start the server (npx ts-node src/index.ts) and any environment variables it needs. Once configured, start a new Claude Code session and ask it to use the hello tool. You should see Claude Code call your tool and display the response.

Building tools with input validation

Tools are the primary way MCP servers extend Claude Code's capabilities. Each tool has a name, a description (which Claude Code reads to decide when to use the tool), an input schema (JSON Schema defining the parameters), and a handler function that executes the tool and returns results. Ask Claude Code: Add three tools to the MCP server. First, a database_query tool that takes a SQL query string, executes it against a SQLite database, and returns the results as a formatted table. Second, a file_search tool that takes a search pattern and a directory path, searches for files matching the pattern, and returns the file paths with sizes and modification dates. Third, a http_request tool that takes a URL, method, headers, and body, makes the HTTP request, and returns the response status, headers, and body. Each tool should have a detailed description that tells Claude Code when to use it. For example, the database_query tool description should say: Execute a SQL query against the project database. Use this for reading data, running reports, or checking database state. Returns results as a formatted table. The input schema validates parameters before the handler runs. Ask Claude Code: Define JSON Schema for each tool's inputs. The database_query tool requires a query string (maximum 1000 characters, must start with SELECT for safety — no writes through this tool). The file_search tool requires a pattern string and an optional directory string (default to the current working directory). The http_request tool requires a url string and an optional method enum (GET, POST, PUT, DELETE, default GET). Add error handling to every tool handler. Ask Claude Code: Wrap each handler in a try-catch block. If the tool fails, return a structured error with: the error type (validation_error, execution_error, timeout_error), a human-readable message, and suggested fixes. Claude Code reads these error messages and can often fix the issue and retry. Common error: MCP tool descriptions are critically important. If the description is vague, Claude Code will not know when to use the tool. If it is inaccurate, Claude Code will use the tool incorrectly. Write descriptions as if you are explaining the tool to a competent colleague.

Resource providers and dynamic data

Resources expose data that Claude Code can read — like a database schema, a configuration file, or a live dashboard. Unlike tools (which perform actions), resources provide context. Ask Claude Code: Add resource providers to the MCP server. Create a resource that exposes the database schema. When Claude Code requests the resource at database://schema, the server queries the SQLite database for all table names, their columns, types, and relationships, and returns a formatted schema description. This lets Claude Code understand your database structure without you explaining it. Create a dynamic resource provider. Ask Claude Code: Add a resource that exposes application configuration. The resource URI is config://app. The server reads a YAML or JSON configuration file and returns its contents. When the configuration file changes, the resource returns the updated content. Add a resource for environment information: env://status returns the current Node.js version, installed packages, disk space, memory usage, and uptime. Resources can also be templates — URIs with parameters. Ask Claude Code: Create a resource template database://table/{tableName} that returns the first 100 rows of the specified table as formatted JSON. When Claude Code requests database://table/users, it gets the user data. When it requests database://table/orders, it gets order data. The template pattern makes the server flexible without defining a separate resource for every table. Add resource subscriptions. Ask Claude Code: Implement a resource that watches a log file and provides updates. The resource at logs://app returns the last 100 lines of the application log. When new lines are written, the server can notify Claude Code that the resource has changed. This is useful for monitoring — Claude Code can watch logs and alert you to errors in real-time. Common error: resources should be read-only. If you need to modify data, use a tool instead. Resources are for providing context, tools are for taking action. Mixing these concerns creates confusion about when Claude Code will read versus write.

Authentication and security

MCP servers often need access to sensitive systems — databases, APIs, cloud infrastructure. Security is not optional. Ask Claude Code: Implement authentication for the MCP server. Add support for environment variable configuration — API keys, database credentials, and service tokens should be read from environment variables, never hardcoded. Create a configuration validation function that runs at startup and exits with a clear error if any required variable is missing. Add input sanitisation. Ask Claude Code: Create a sanitisation layer for all tool inputs. The database_query tool must prevent SQL injection — use parameterised queries and reject inputs containing dangerous keywords like DROP, DELETE, UPDATE, INSERT, ALTER, or TRUNCATE (this server is read-only by design). The http_request tool must validate URLs against an allowlist of domains — prevent the server from being used to make requests to internal networks or localhost (SSRF attacks). The file_search tool must restrict search to allowed directories — prevent path traversal attacks (../../etc/passwd). Implement rate limiting. Ask Claude Code: Add a rate limiter to the MCP server. Track tool invocations per minute. If Claude Code calls a tool more than 30 times per minute, start returning rate limit errors. This prevents runaway loops where Claude Code repeatedly calls a failing tool. Log the rate limit events for debugging. Add audit logging. Ask Claude Code: Log every tool invocation and resource access with: the timestamp, tool or resource name, input parameters (redacted for sensitive fields), execution duration, and whether it succeeded or failed. Write logs to a file in a structured JSON format for easy searching. Never log credentials, API keys, or sensitive query parameters — replace them with [REDACTED] in the log output. Common error: MCP servers run with your local permissions. A tool that executes shell commands gives Claude Code full access to everything you can access. Be deliberate about which capabilities you expose and add explicit safeguards for destructive operations.

Testing and debugging MCP servers

MCP servers are harder to debug than typical applications because they communicate over stdio and are invoked by Claude Code rather than by you directly. Ask Claude Code: Create a test harness at src/test/harness.ts that simulates Claude Code's MCP client. The harness sends JSON-RPC messages to the server's stdin and reads responses from stdout. Write test functions: callTool(name, params) that sends a tool invocation and returns the result, getResource(uri) that requests a resource and returns its content, and listTools() that requests the server's tool manifest. Create comprehensive tests. Ask Claude Code: Write tests for every tool and resource. For the database_query tool: test a valid SELECT query, test a query with no results, test an invalid query (SQL syntax error), test a forbidden query (DROP TABLE), and test a query that takes too long (add a timeout). For the file_search tool: test a pattern that matches files, test a pattern with no matches, test a restricted directory path, and test with special characters in the pattern. Run tests with: npx ts-node src/test/harness.ts. Each test should print PASS or FAIL with details. Add a debug mode. Ask Claude Code: When the environment variable MCP_DEBUG=true is set, the server should write detailed debug logs to stderr (not stdout — stdout is reserved for the protocol). Log every incoming message, outgoing response, tool execution time, and internal state changes. This debug output appears in Claude Code's MCP logs, helping diagnose issues during development. Create a manual testing workflow. Ask Claude Code: Write a TESTING.md file with step-by-step instructions for testing the server. Start the server in debug mode, use the test harness to send sample requests, verify the responses, then connect to Claude Code and test with natural language requests. Include a troubleshooting section for common issues: server crashes silently (check stderr), Claude Code does not see the tools (check the server configuration path), and tools return unexpected results (check the input schema and description). Common error: stdout is exclusively for the JSON-RPC protocol. Any console.log statement in your tool handlers will break the protocol by injecting non-JSON text into the message stream. Use console.error for debug output or write to a log file.

Deployment and distribution

Once your MCP server works locally, you may want to share it with your team or the community. Ask Claude Code: Prepare the MCP server for distribution. Create a proper package.json with a name (scoped to your organisation: @yourorg/mcp-server-name), version, description, the bin field pointing to the compiled entry point, and a build script that compiles TypeScript to JavaScript. The bin field is important — it allows the server to be run as npx @yourorg/mcp-server-name after publishing to npm. Add a clean README with: what the server does, which tools and resources it provides, installation instructions, configuration (required environment variables), and example usage with Claude Code. Ask Claude Code: Create installation instructions that work for both local development (clone and build) and npm installation (npx). Include the exact Claude Code configuration needed in settings.json with example environment variables. Add a Docker deployment option. Ask Claude Code: Create a Dockerfile that builds the MCP server as a Docker image. This is useful for team distribution where you want a consistent environment. The Claude Code configuration can use the command docker run instead of npx ts-node, pointing to the Docker image. Publish to npm. Ask Claude Code: Walk me through publishing an MCP server to npm. Create an .npmrc file, configure the build to produce a clean dist/ directory, ensure the bin field points to the compiled JavaScript (not TypeScript), add a shebang line (#!/usr/bin/env node) to the entry point, and run npm publish. After publishing, anyone can install it with: npx @yourorg/mcp-server-name. Add automatic updates. Ask Claude Code: Create a version check that runs on server startup. Compare the installed version to the latest version on npm. If a newer version is available, log a message to stderr: A newer version of @yourorg/mcp-server-name is available (1.2.0 > 1.1.0). Update with: npm update -g @yourorg/mcp-server-name. Test the complete distribution cycle: publish, install on a clean machine, configure Claude Code, and verify all tools and resources work. This is the real test — your development environment may have dependencies or configurations that the clean install lacks.

Related Lesson

Extending AI Tools

This guide is hands-on and practical. The full curriculum covers the conceptual foundations in depth with structured lessons and quizzes.

Go to lesson