Skip to content

MCP Best Practices

Published:
8 min read

Building high-quality Model Context Protocol (MCP) tools requires attention to detail across many dimensions. After developing several MCP tools, I’ve compiled this comprehensive guide to best practices that ensure your tools are reliable, user-friendly, and maintainable.

My MCP Tools

Here are the MCP tools I’ve built following these practices:

I. General Tool Configuration & Behavior

Sensible Defaults

All environment variables must have sensible defaults for easy out-of-the-box usage. Users should be able to get started without extensive configuration.

Dynamic Versioning

The tool’s version is emitted in its description. This version must be read dynamically (e.g., from package.json) and not hardcoded. This ensures version consistency and eliminates manual update errors.

Tool & Parameter Descriptions

These details should be verifiable by hovering over the tool in clients like Cursor or using the MCP inspector.

Parameter Parsing

Parameter parsing should be lenient (e.g., accept path if project_path is formally defined). Generally, advertise stricter schemas but be more lenient in execution to accommodate variations from agents.

Error Handling

Output Control

There should be no output to stdio during normal tool operation, as this can disrupt MCP clients. File-based logging is the designated method for operational output.

The info Command

At least one tool must offer an info sub command. This command shall list:

II. Logging (Pino)

Default File Logger

Pino is used for logging with a default file logger in the system’s log directory (e.g., ~/Library/Logs/). The log file path is configurable via the [ProjectName]_LOG_FILE environment variable.

Log Path Resilience

Configuration

III. Code, Dependencies & Build

Code Quality Standards

Build Configuration

IV. Testing

Test Framework

Tests must use vitest for consistency and modern testing capabilities.

Test Coverage

NPM Scripts

V. Native Binary Rules (If Applicable)

Platform Compatibility

Native Code Quality

Integration Requirements

Communication Protocol

Distribution

Consider options for distributing as a single, statically linked binary if feasible and beneficial for simpler installation by end-users who might use the CLI directly.

VI. Rules to Check Before a Release (scripts/prepare-release.js)

There is a scripts/prepare-release.js that runs an extensive test suite. The script runs these checks sequentially and stops at the first failure.

Git & Version Control

  1. Check current branch (warns if not on main or designated release branch)
  2. Check for uncommitted changes
  3. Check if synced with origin/main (or designated release branch)
  4. Version availability check (ensures version isn’t already published)
  5. Version consistency between package.json and package-lock.json
  6. Changelog Check: Check for a changelog entry corresponding to the current version

Code Quality & Security

  1. Dependency installation check
  2. Outdated dependencies check (warning only)
  3. Security audit (fails on critical/high vulnerabilities)
  4. TypeScript compilation
  5. TypeScript tests
  6. TypeScript declaration files generation
  7. Delete any build folders and reset package caches before building
  8. If native binary exists: Swift analyze
  9. If native binary exists: Swift formatting (SwiftFormat)
  10. If native binary exists: Swift linting (SwiftLint)
  11. If native binary exists: Swift tests
  12. No build warnings

Binary & CLI Validation (If Applicable)

  1. If native binary exists: Swift CLI command tests (help, version, and other key functionalities)
  2. If native binary exists: Swift CLI error handling tests (invalid commands, missing args, invalid window index, etc.)
  3. If native binary exists: Swift CLI JSON output validation
  4. If native binary exists: Binary exists and is executable
  5. If native binary exists: Binary contains both architectures (arm64 + x86_64, verifiable via lipo -info)
  6. If native binary exists: Binary responds correctly to --help

Package Validation

  1. Required fields in package.json
  2. Package size check (warns if >2MB, configurable threshold)
  3. Executable permission check in postinstall (if a CLI is present)
  4. Critical files included (e.g., dist/index.js, native binary name, README.md, LICENSE)
  5. MCP server smoke test (JSON-RPC request/response)
  6. Full integration tests

Beta Release Strategy

Releases are first done with a beta tag to the npm registry so they can be tested via the npx [packageName]@beta install method.

Conclusion

Following these best practices ensures that your MCP tools are professional, reliable, and user-friendly. They represent lessons learned from real-world development and deployment of production MCP tools. As the MCP ecosystem evolves, these practices will continue to be refined and expanded.

New posts, shipping stories, and nerdy links straight to your inbox.

2× per month, pure signal, zero fluff.


Edit on GitHub