mcp-standards
MCP Standards Skill
1. Overview
What is MCP in Claude Code?
Model Context Protocol (MCP) is the standard way to extend Claude Code with custom tools and integrations. MCP servers provide:
- Tool Integration: Connect to external APIs, databases, and services
- Context Providers: Supply relevant information to Claude during conversations
- Action Handlers: Execute operations in external systems
- Data Sources: Access project-specific or organization-specific data
Why Standardization Matters
Standardized MCP servers ensure:
- Predictable Behavior: Developers know what to expect from MCP tools
- Easier Debugging: Consistent patterns make issues easier to identify
- Better Discoverability: Standard naming helps Claude and users find tools
- Maintainability: Common patterns reduce maintenance burden
- Team Consistency: Multiple developers follow same conventions
MCP in the Plugin Ecosystem
MCP servers are plugin components alongside agents, commands, and skills:
plugin/
├── agents/ # Specialized Claude instances
├── commands/ # CLI commands
├── skills/ # Knowledge documents
└── mcp-servers/ # MCP tool providers ← We're here
Key Difference: While agents use built-in tools, MCP servers provide NEW tools that extend Claude's capabilities.
2. MCP Server Structure
Standard Directory Layout
mcp-servers/
├── server-name/
│ ├── index.ts # Server entry point
│ ├── package.json # Dependencies and metadata
│ ├── tsconfig.json # TypeScript configuration
│ ├── README.md # Server documentation
│ ├── tools/ # Tool implementations
│ │ ├── read-tool.ts
│ │ ├── write-tool.ts
│ │ └── index.ts # Tool exports
│ ├── lib/ # Shared utilities
│ │ ├── client.ts # API client
│ │ ├── validation.ts # Input validation
│ │ └── errors.ts # Error handling
│ └── tests/ # Test files
│ ├── read-tool.test.ts
│ └── write-tool.test.ts
Entry Point Pattern (index.ts)
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// Import tools
import { fetchTool, createTool, updateTool } from "./tools/index.js";
const server = new Server(
{
name: "mcp-plugin-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Register tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [fetchTool.definition, createTool.definition, updateTool.definition],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case fetchTool.name:
return fetchTool.handler(args);
case createTool.name:
return createTool.handler(args);
case updateTool.name:
return updateTool.handler(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
Tool Module Pattern
// tools/fetch-tool.ts
import { z } from "zod";
const inputSchema = z.object({
id: z.string().describe("The resource ID to fetch"),
includeMetadata: z.boolean().optional().describe("Include metadata in response"),
});
export const fetchTool = {
name: "mcp__plugin__fetch_resource",
definition: {
name: "mcp__plugin__fetch_resource",
description: "Fetch a resource by ID from the external service",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "The resource ID to fetch",
},
includeMetadata: {
type: "boolean",
description: "Include metadata in response",
},
},
required: ["id"],
},
},
handler: async (args: unknown) => {
const validated = inputSchema.parse(args);
try {
const result = await fetchResourceById(validated.id);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
throw new Error(`Failed to fetch resource: ${error.message}`);
}
},
};
3. Tool Naming Conventions
Standard Pattern
mcp__<plugin-name>__<tool-name>
Components:
mcp__- Universal prefix indicating MCP tool<plugin-name>- Plugin identifier (matches plugin.json id)<tool-name>- Descriptive snake_case tool name
Real-World Examples
// Frontend Plugin
"mcp__frontend__figma_fetch" // Fetch Figma designs
"mcp__frontend__figma_export_assets" // Export Figma assets
"mcp__frontend__lighthouse_audit" // Run Lighthouse audit
// Code Analysis Plugin
"mcp__code-analysis__claudemem_search" // Search codebase
"mcp__code-analysis__claudemem_enrich" // Enrich file context
// Bun Backend Plugin
"mcp__bun__apidog_sync" // Sync with Apidog
"mcp__bun__apidog_validate" // Validate API spec
// SEO Plugin
"mcp__seo__analyze_page" // Analyze page SEO
"mcp__seo__check_schema" // Validate schema markup
Tool Name Guidelines
DO:
- Use snake_case for tool names
- Use action verbs (fetch, create, update, analyze)
- Be specific about what the tool does
- Keep names under 50 characters
DON'T:
- Use camelCase or PascalCase
- Use generic names like "do_thing"
- Include version numbers in names
- Use abbreviations unless widely known
Verb Conventions
| Verb | Use Case | Example |
|---|---|---|
fetch |
Retrieve single resource | fetch_user |
list |
Retrieve multiple resources | list_projects |
search |
Query with filters | search_files |
create |
Create new resource | create_issue |
update |
Modify existing resource | update_config |
delete |
Remove resource | delete_cache |
validate |
Check data validity | validate_schema |
analyze |
Perform analysis | analyze_performance |
sync |
Synchronize data | sync_database |
export |
Export data | export_report |
4. Transport Configuration
stdio Transport (Most Common)
Standard for local development and command-line usage:
{
"mcpServers": {
"frontend-tools": {
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/mcp-servers/frontend-tools/index.js"],
"transport": "stdio"
}
}
}
When to Use:
- Local plugin development
- Command-line integrations
- Single-user scenarios
- No network requirements
Advantages:
- Simple setup
- No port conflicts
- Secure (local only)
- Low latency
HTTP Transport
For remote services or multi-user scenarios:
{
"mcpServers": {
"shared-service": {
"url": "http://localhost:3000/mcp",
"transport": "http",
"headers": {
"Authorization": "Bearer ${API_TOKEN}"
}
}
}
}
When to Use:
- Remote API services
- Shared team resources
- Cloud-hosted tools
- Microservice architecture
Advantages:
- Network accessible
- Scalable
- Can use load balancing
- Standard HTTP tooling
WebSocket Transport
For real-time bidirectional communication:
{
"mcpServers": {
"realtime-service": {
"url": "ws://localhost:8080/mcp",
"transport": "websocket"
}
}
}
When to Use:
- Real-time updates
- Streaming responses
- Bidirectional communication
- Live collaboration tools
Environment Variable Interpolation
All transports support environment variable substitution:
{
"mcpServers": {
"apidog-sync": {
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/mcp-servers/apidog/index.js"],
"env": {
"APIDOG_API_TOKEN": "${APIDOG_API_TOKEN}",
"APIDOG_PROJECT_ID": "${APIDOG_PROJECT_ID}"
}
}
}
}
Pattern: ${VARIABLE_NAME} is replaced at runtime.
5. Tool Categories
Read-Only Tools
Purpose: Retrieve information without side effects.
Characteristics:
- Safe to call multiple times
- No state changes
- Fast response times
- Cacheable results
Examples:
// Fetch single resource
mcp__plugin__fetch_config
mcp__plugin__get_status
// List multiple resources
mcp__plugin__list_projects
mcp__plugin__list_files
// Search/query
mcp__plugin__search_code
mcp__plugin__query_database
Write Tools
Purpose: Create, update, or delete resources.
Characteristics:
- Modify state
- Require validation
- Need error handling
- Should be idempotent when possible
Examples:
// Create
mcp__plugin__create_file
mcp__plugin__create_issue
// Update
mcp__plugin__update_config
mcp__plugin__update_document
// Delete
mcp__plugin__delete_cache
mcp__plugin__remove_entry
Analysis Tools
Purpose: Process data and provide insights.
Characteristics:
- Compute-intensive
- Return structured results
- May have longer timeouts
- Often cacheable
Examples:
mcp__plugin__analyze_performance
mcp__plugin__audit_security
mcp__plugin__validate_schema
mcp__plugin__check_quality
Integration Tools
Purpose: Connect to external services.
Characteristics:
- Bridge systems
- Handle authentication
- Manage rate limits
- Deal with network errors
Examples:
mcp__plugin__sync_database
mcp__plugin__import_data
mcp__plugin__export_report
mcp__plugin__webhook_notify
6. Performance Standards
Response Time Targets
| Tool Type | Target | Max Acceptable |
|---|---|---|
| Simple fetch | <50ms | 200ms |
| List operation | <100ms | 500ms |
| Search | <200ms | 1000ms |
| Analysis | <500ms | 3000ms |
| Write operation | <300ms | 2000ms |
| Sync operation | <1000ms | 5000ms |
Timeout Configuration
// Set timeouts for different operations
const TIMEOUT_CONFIG = {
fetch: 5000, // 5 seconds
search: 10000, // 10 seconds
analyze: 30000, // 30 seconds
sync: 60000, // 1 minute
};
async function callWithTimeout<T>(
operation: Promise<T>,
timeoutMs: number
): Promise<T> {
const timeout = new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error("Operation timed out")), timeoutMs)
);
return Promise.race([operation, timeout]);
}
Rate Limiting
class RateLimiter {
private requests: number = 0;
private resetTime: number = Date.now() + 60000;
async checkLimit(limit: number = 100) {
if (Date.now() > this.resetTime) {
this.requests = 0;
this.resetTime = Date.now() + 60000;
}
if (this.requests >= limit) {
throw new Error("Rate limit exceeded. Try again later.");
}
this.requests++;
}
}
Error Handling Patterns
class MCPError extends Error {
constructor(
message: string,
public code: string,
public details?: unknown
) {
super(message);
this.name = "MCPError";
}
}
// Usage in tools
try {
const result = await externalAPI.fetch(id);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
} catch (error) {
if (error.code === "NOT_FOUND") {
throw new MCPError("Resource not found", "NOT_FOUND", { id });
}
if (error.code === "RATE_LIMITED") {
throw new MCPError("Rate limit exceeded", "RATE_LIMITED");
}
throw new MCPError("Unexpected error", "INTERNAL_ERROR", { error });
}
7. Security Patterns
Input Validation
Always validate inputs using a schema validation library:
import { z } from "zod";
// Define strict schemas
const fetchInputSchema = z.object({
id: z.string().uuid("Invalid UUID format"),
includePrivate: z.boolean().default(false),
});
// Validate in handler
export const handler = async (args: unknown) => {
const validated = fetchInputSchema.parse(args);
// Now 'validated' is type-safe
};
Output Sanitization
Never return raw data that might contain sensitive information:
function sanitizeOutput(data: any): any {
// Remove sensitive fields
const { password, apiKey, secret, ...safe } = data;
// Sanitize nested objects
if (typeof safe === "object" && safe !== null) {
for (const key in safe) {
if (key.toLowerCase().includes("token") ||
key.toLowerCase().includes("key") ||
key.toLowerCase().includes("password")) {
safe[key] = "[REDACTED]";
}
}
}
return safe;
}
Permission Checks
Implement permission checks before executing operations:
async function checkPermission(userId: string, action: string): Promise<void> {
const permissions = await getPermissions(userId);
if (!permissions.includes(action)) {
throw new MCPError(
`User ${userId} lacks permission: ${action}`,
"PERMISSION_DENIED"
);
}
}
// Use in tool handler
export const handler = async (args: unknown) => {
await checkPermission(args.userId, "resource:write");
// Proceed with operation
};
Sensitive Data Handling
Environment variables for secrets:
// NEVER hardcode secrets
const API_KEY = process.env.EXTERNAL_API_KEY;
if (!API_KEY) {
throw new Error("EXTERNAL_API_KEY environment variable is required");
}
// Use in requests
const response = await fetch(url, {
headers: {
Authorization: `Bearer ${API_KEY}`,
},
});
Audit logging for sensitive operations:
async function auditLog(
action: string,
userId: string,
details: object
): Promise<void> {
const timestamp = new Date().toISOString();
console.log(JSON.stringify({ timestamp, action, userId, details }));
}
// Log before sensitive operations
await auditLog("delete_resource", userId, { resourceId });
await deleteResource(resourceId);
8. Configuration Patterns
Plugin Manifest (plugin.json)
{
"id": "example-plugin",
"name": "Example Plugin",
"version": "1.0.0",
"mcpServers": {
"example-tools": {
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/mcp-servers/example-tools/index.js"],
"env": {
"EXAMPLE_API_TOKEN": "${EXAMPLE_API_TOKEN}",
"EXAMPLE_BASE_URL": "${EXAMPLE_BASE_URL}"
}
}
}
}
Project Settings (.claude/settings.json)
Override plugin defaults per project:
{
"enabledPlugins": {
"example-plugin@marketplace": true
},
"mcpServers": {
"example-tools": {
"env": {
"EXAMPLE_BASE_URL": "https://custom-api.example.com"
}
}
}
}
Environment Variables
Project .env file:
# API credentials
EXAMPLE_API_TOKEN=your-token-here
APIDOG_API_TOKEN=your-apidog-token
# Configuration
EXAMPLE_BASE_URL=https://api.example.com
CHROME_EXECUTABLE_PATH=/usr/bin/chromium
Access in MCP server:
const config = {
apiToken: process.env.EXAMPLE_API_TOKEN,
baseUrl: process.env.EXAMPLE_BASE_URL || "https://api.example.com",
};
9. Best Practices
DO
- Use Standard Naming: Follow
mcp__plugin__action_resourcepattern - Validate All Inputs: Use Zod or similar schema validation
- Handle Errors Gracefully: Return clear error messages
- Document Tools: Provide detailed descriptions and examples
- Set Appropriate Timeouts: Don't let operations hang indefinitely
- Test Thoroughly: Unit tests for all tools
- Log Operations: Help with debugging and auditing
- Use Environment Variables: Never hardcode secrets
- Implement Rate Limiting: Protect external APIs
- Version Your Servers: Track breaking changes
DON'T
- Don't Return Sensitive Data: Sanitize outputs
- Don't Ignore Errors: Handle and report all failures
- Don't Use Hardcoded Credentials: Always use environment variables
- Don't Skip Validation: Trust no input
- Don't Create Side Effects in Read Tools: Keep reads idempotent
- Don't Use Blocking Operations: Use async/await properly
- Don't Expose Internal Implementation: Abstract away complexity
- Don't Forget Error Context: Include relevant details
- Don't Mix Concerns: One tool, one purpose
- Don't Skip Documentation: Future you will thank you
Testing MCP Servers
Unit test example:
import { describe, it, expect, beforeEach } from "bun:test";
import { fetchTool } from "./fetch-tool.js";
describe("fetchTool", () => {
it("should fetch resource by ID", async () => {
const result = await fetchTool.handler({ id: "test-123" });
expect(result.content[0].text).toContain("test-123");
});
it("should throw on invalid ID", async () => {
await expect(
fetchTool.handler({ id: "invalid" })
).rejects.toThrow("Invalid UUID format");
});
});
Documentation Requirements
Every MCP server needs:
- README.md: Overview, installation, usage
- Tool descriptions: In tool definitions
- Input schemas: With descriptions for each field
- Error documentation: Possible error codes and meanings
- Examples: Sample requests and responses
10. Examples
Example 1: Simple Read-Only MCP Server
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
const server = new Server(
{ name: "simple-reader", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Tool definition
const fetchConfigTool = {
name: "mcp__simple__fetch_config",
definition: {
name: "mcp__simple__fetch_config",
description: "Fetch configuration by key",
inputSchema: {
type: "object",
properties: {
key: { type: "string", description: "Configuration key" },
},
required: ["key"],
},
},
handler: async (args: unknown) => {
const { key } = z.object({ key: z.string() }).parse(args);
const configs = {
apiUrl: "https://api.example.com",
timeout: "5000",
retries: "3",
};
const value = configs[key];
if (!value) {
throw new Error(`Configuration key not found: ${key}`);
}
return {
content: [
{ type: "text", text: `${key}=${value}` },
],
};
},
};
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [fetchConfigTool.definition],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === fetchConfigTool.name) {
return fetchConfigTool.handler(request.params.arguments);
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
Example 2: Full CRUD MCP Server
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// In-memory store (replace with real database)
const store = new Map<string, any>();
// Create tool
const createTool = {
name: "mcp__crud__create_item",
definition: {
name: "mcp__crud__create_item",
description: "Create a new item",
inputSchema: {
type: "object",
properties: {
name: { type: "string" },
value: { type: "string" },
},
required: ["name", "value"],
},
},
handler: async (args: unknown) => {
const { name, value } = z.object({
name: z.string(),
value: z.string(),
}).parse(args);
if (store.has(name)) {
throw new Error(`Item already exists: ${name}`);
}
const item = { name, value, createdAt: new Date().toISOString() };
store.set(name, item);
return {
content: [{ type: "text", text: JSON.stringify(item) }],
};
},
};
// Read tool
const readTool = {
name: "mcp__crud__read_item",
definition: {
name: "mcp__crud__read_item",
description: "Read an item by name",
inputSchema: {
type: "object",
properties: {
name: { type: "string" },
},
required: ["name"],
},
},
handler: async (args: unknown) => {
const { name } = z.object({ name: z.string() }).parse(args);
const item = store.get(name);
if (!item) {
throw new Error(`Item not found: ${name}`);
}
return {
content: [{ type: "text", text: JSON.stringify(item) }],
};
},
};
// Update tool
const updateTool = {
name: "mcp__crud__update_item",
definition: {
name: "mcp__crud__update_item",
description: "Update an existing item",
inputSchema: {
type: "object",
properties: {
name: { type: "string" },
value: { type: "string" },
},
required: ["name", "value"],
},
},
handler: async (args: unknown) => {
const { name, value } = z.object({
name: z.string(),
value: z.string(),
}).parse(args);
const existing = store.get(name);
if (!existing) {
throw new Error(`Item not found: ${name}`);
}
const updated = {
...existing,
value,
updatedAt: new Date().toISOString(),
};
store.set(name, updated);
return {
content: [{ type: "text", text: JSON.stringify(updated) }],
};
},
};
// Delete tool
const deleteTool = {
name: "mcp__crud__delete_item",
definition: {
name: "mcp__crud__delete_item",
description: "Delete an item by name",
inputSchema: {
type: "object",
properties: {
name: { type: "string" },
},
required: ["name"],
},
},
handler: async (args: unknown) => {
const { name } = z.object({ name: z.string() }).parse(args);
if (!store.has(name)) {
throw new Error(`Item not found: ${name}`);
}
store.delete(name);
return {
content: [{ type: "text", text: `Deleted: ${name}` }],
};
},
};
const server = new Server(
{ name: "crud-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
createTool.definition,
readTool.definition,
updateTool.definition,
deleteTool.definition,
],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tools = { createTool, readTool, updateTool, deleteTool };
const tool = Object.values(tools).find((t) => t.name === name);
if (!tool) {
throw new Error(`Unknown tool: ${name}`);
}
return tool.handler(args);
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
Example 3: External API Integration MCP Server
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Configuration from environment
const API_TOKEN = process.env.EXTERNAL_API_TOKEN;
const BASE_URL = process.env.EXTERNAL_BASE_URL || "https://api.example.com";
if (!API_TOKEN) {
throw new Error("EXTERNAL_API_TOKEN environment variable is required");
}
// API client
class ExternalAPIClient {
async fetch(endpoint: string, options = {}) {
const response = await fetch(`${BASE_URL}${endpoint}`, {
...options,
headers: {
Authorization: `Bearer ${API_TOKEN}`,
"Content-Type": "application/json",
...options.headers,
},
});
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return response.json();
}
}
const client = new ExternalAPIClient();
// Tool: Fetch user
const fetchUserTool = {
name: "mcp__external__fetch_user",
definition: {
name: "mcp__external__fetch_user",
description: "Fetch user data from external API",
inputSchema: {
type: "object",
properties: {
userId: { type: "string", description: "User ID" },
},
required: ["userId"],
},
},
handler: async (args: unknown) => {
const { userId } = z.object({ userId: z.string() }).parse(args);
try {
const user = await client.fetch(`/users/${userId}`);
return {
content: [{ type: "text", text: JSON.stringify(user, null, 2) }],
};
} catch (error) {
throw new Error(`Failed to fetch user: ${error.message}`);
}
},
};
const server = new Server(
{ name: "external-api-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [fetchUserTool.definition],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === fetchUserTool.name) {
return fetchUserTool.handler(request.params.arguments);
}
throw new Error(`Unknown tool: ${request.params.name}`);
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
Summary
This skill provides comprehensive MCP server standards for Claude Code plugins:
- Structure: Consistent directory layout and entry point patterns
- Naming:
mcp__plugin__action_resourceconvention - Transports: stdio (local), HTTP (remote), WebSocket (realtime)
- Categories: Read, Write, Analysis, Integration tools
- Performance: Response time targets, timeouts, rate limiting
- Security: Input validation, output sanitization, permission checks
- Configuration: plugin.json, settings.json, environment variables
- Best Practices: Comprehensive DO/DON'T list
- Examples: Three complete, production-ready MCP servers
Follow these standards to create maintainable, secure, and performant MCP servers for your Claude Code plugins.