MCP Server Tool Design and Naming Best Practices: A Complete Guide
Use snake_case tool names prefixed with the service name, implement strict Pydantic or Zod validation, and provide dual JSON/Markdown response formats with proper pagination metadata.
The Anthropic Skills repository provides authoritative guidelines for building Model Context Protocol (MCP) servers that are discoverable, composable, and safe for AI agents. Following the conventions established in skills/mcp-builder/reference/mcp_best_practices.md ensures your tools integrate seamlessly with Claude and other MCP clients.
Server Naming Conventions
Consistent server naming prevents collisions and clarifies the service domain.
Python Servers
Use the pattern {service}_mcp (lowercase with underscores) as defined in skills/mcp-builder/reference/python_mcp_server.md.
- Examples:
slack_mcp,github_mcp,jira_mcp
from mcp.server.fastmcp import FastMCP
# Correct naming pattern
mcp = FastMCP("slack_mcp")
Node.js/TypeScript Servers
Use the pattern {service}-mcp-server (lowercase with hyphens) as specified in skills/mcp-builder/reference/node_mcp_server.md.
- Examples:
slack-mcp-server,github-mcp-server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({
name: "github-mcp-server",
version: "1.0.0"
});
Tool Naming Best Practices
Clear tool names reduce ambiguity for AI agents deciding which function to invoke.
Use Snake Case with Service Prefixes
All tool names must use snake_case (search_users, create_project) and include the service prefix to avoid namespace collisions.
- Correct:
slack_send_message,github_create_issue - Incorrect:
send_message,create_issue
Start with Action Verbs
Begin tool names with standard action verbs that indicate the operation type:
- get / list – Retrieve single or multiple items
- create – Instantiate new resources
- update – Modify existing resources
- delete – Remove resources
Tool Design Fundamentals
Robust tool design requires strict validation, comprehensive metadata, and safety annotations.
Strict Input Validation
Use Pydantic (Python) or Zod (TypeScript) with .strict() configuration to prevent extra fields and validate types automatically.
from pydantic import BaseModel, Field, ConfigDict
class SearchInput(BaseModel):
model_config = ConfigDict(str_strip_whitespace=True, extra="forbid")
query: str = Field(..., min_length=2, max_length=200)
limit: int = Field(default=20, ge=1, le=100)
import { z } from "zod";
const IssueSearchSchema = z.object({
query: z.string().min(2).max(200),
limit: z.number().int().min(1).max(100).default(20),
offset: z.number().int().min(0).default(0),
response_format: z.enum(["markdown", "json"]).default("markdown"),
}).strict();
Tool Annotations
Provide annotations in skills/mcp-builder/reference/mcp_best_practices.md to guide agent behavior:
- readOnlyHint:
trueif the tool only reads data - destructiveHint:
trueif the tool deletes or irreversibly modifies data - idempotentHint:
trueif repeated calls with the same inputs produce the same results - openWorldHint:
trueif the tool interacts with external systems beyond the local environment
@mcp.tool(
name="slack_search_messages",
annotations={
"readOnlyHint": True,
"destructiveHint": False,
"idempotentHint": True,
"openWorldHint": True,
},
)
async def slack_search_messages(params: SearchInput) -> str:
"""Search Slack messages matching query. Returns JSON or Markdown."""
...
Response Handling and Pagination
Dual-format responses and consistent pagination metadata improve usability across different client types.
Dual Response Formats
Support both JSON (machine-readable) and Markdown (human-readable) outputs. Default to Markdown for general agent usage unless the client specifically requests JSON.
if params.response_format == SearchInput.ResponseFormat.JSON:
return json.dumps({
"total": total,
"count": len(messages),
"offset": params.offset,
"messages": messages,
"has_more": total > (params.offset + len(messages)),
}, indent=2)
# Markdown formatting for human readability
lines = [f"# Search Results", ""]
for msg in messages:
lines.append(f"**{msg['user']}**: {msg['text']}")
return "\n".join(lines)
Pagination Metadata
For list operations, return standard pagination fields as defined in skills/mcp-builder/reference/mcp_best_practices.md:
- has_more: Boolean indicating additional results available
- next_offset: Integer for the next page starting position
- total_count: Total number of matching items
- Default limit: 20-50 items per request
Transport and Error Handling
Proper transport selection and sanitized error messages ensure reliable operation.
Transport Selection
Choose transport based on deployment context:
- stdio: Use for local CLI tools and single-user environments
- streamable HTTP: Use for remote services requiring multiple concurrent clients
- Avoid SSE: Server-Sent Events transport is deprecated in favor of streamable HTTP
// stdio transport for local usage
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
await server.connect(new StdioServerTransport());
Error Handling Best Practices
Map API errors to user-friendly messages without exposing internal stack traces or sensitive tokens:
function handleApiError(error: unknown): string {
if (error instanceof AxiosError) {
if (error.response) {
switch (error.response.status) {
case 404: return "Error: Resource not found.";
case 403: return "Error: Permission denied.";
case 429: return "Error: Rate limit exceeded.";
default: return `Error: API request failed with status ${error.response.status}`;
}
}
}
return `Error: ${error instanceof Error ? error.message : String(error)}`;
}
Code Quality and Testing
Maintainable MCP servers require modular architecture and comprehensive validation.
Code Reusability
Extract common functionality into shared modules:
- API clients: Reusable HTTP clients with authentication
- Pagination helpers: Standardized offset/limit handling
- Formatters: Shared JSON and Markdown rendering utilities
- Error handlers: Centralized exception mapping
Testing Requirements
As specified in skills/mcp-builder/reference/mcp_best_practices.md, validate your server with:
- Functional tests: Verify individual tool logic
- Integration tests: Test against live or mocked APIs
- Security tests: Ensure no token leakage in errors
- Build verification: Confirm
npm run build(Node) orpython -m <module>(Python) executes without errors
Summary
- Name servers using
{service}_mcp(Python) or{service}-mcp-server(Node) patterns - Name tools with snake_case, service prefixes, and action verbs (get, list, create, update, delete)
- Validate inputs strictly using Pydantic (Python) or Zod (Node) with
.strict()configuration - Annotate tools with
readOnlyHint,destructiveHint,idempotentHint, andopenWorldHint - Support dual formats (JSON and Markdown) with standard pagination metadata (
has_more,next_offset,total_count) - Choose transport based on context: stdio for local tools, streamable HTTP for remote services
- Handle errors gracefully without exposing stack traces or sensitive credentials
Frequently Asked Questions
What is the correct naming convention for MCP server packages?
Python servers should use the pattern {service}_mcp with lowercase and underscores (e.g., slack_mcp, github_mcp), while Node.js/TypeScript servers should use {service}-mcp-server with lowercase and hyphens (e.g., slack-mcp-server). These patterns are defined in skills/mcp-builder/reference/mcp_best_practices.md and prevent naming collisions across the MCP ecosystem.
How should I handle input validation in MCP tools?
Use strict schema validation with Pydantic for Python or Zod for TypeScript, ensuring you configure .strict() or extra="forbid" to reject unexpected fields. Define clear constraints using Field() or .min()/.max() validators, and include descriptive metadata so the LLM understands parameter purposes. This approach is documented in skills/mcp-builder/reference/python_mcp_server.md and skills/mcp-builder/reference/node_mcp_server.md.
What transport protocol should I choose for my MCP server?
Select stdio transport for local CLI tools and single-user environments where the server runs as a subprocess, or streamable HTTP for remote services that must support multiple concurrent clients over the network. Avoid using Server-Sent Events (SSE) as it is deprecated in favor of streamable HTTP. The transport selection guidance appears in skills/mcp-builder/reference/mcp_best_practices.md.
Why should MCP tools support both JSON and Markdown response formats?
Supporting dual formats accommodates different consumption patterns: JSON provides structured data for programmatic processing and agent tool chaining, while Markdown offers human-readable output for direct user display. Default to Markdown unless the client explicitly requests JSON via a parameter. This pattern is implemented in the reference examples within skills/mcp-builder/reference/python_mcp_server.md and ensures maximum compatibility across diverse MCP clients.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →