plugins/dev/skills/mcp-standards/SKILL.md
MCP server standardization patterns for Claude Code plugins. Use when implementing MCP servers, designing tool interfaces, configuring MCP transports, or standardizing MCP naming conventions. Trigger keywords - "MCP", "MCP server", "MCP tools", "MCP transport", "tool naming", "MCP configuration".
npx skillsauth add madappgang/claude-code mcp-standardsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Model Context Protocol (MCP) is the standard way to extend Claude Code with custom tools and integrations. MCP servers provide:
Standardized MCP servers ensure:
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.
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
#!/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);
// 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}`);
}
},
};
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// 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
DO:
DON'T:
| 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 |
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:
Advantages:
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:
Advantages:
For real-time bidirectional communication:
{
"mcpServers": {
"realtime-service": {
"url": "ws://localhost:8080/mcp",
"transport": "websocket"
}
}
}
When to Use:
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.
Purpose: Retrieve information without side effects.
Characteristics:
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
Purpose: Create, update, or delete resources.
Characteristics:
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
Purpose: Process data and provide insights.
Characteristics:
Examples:
mcp__plugin__analyze_performance
mcp__plugin__audit_security
mcp__plugin__validate_schema
mcp__plugin__check_quality
Purpose: Connect to external services.
Characteristics:
Examples:
mcp__plugin__sync_database
mcp__plugin__import_data
mcp__plugin__export_report
mcp__plugin__webhook_notify
| 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 |
// 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]);
}
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++;
}
}
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 });
}
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
};
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;
}
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
};
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);
{
"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}"
}
}
}
}
Override plugin defaults per project:
{
"enabledPlugins": {
"example-plugin@marketplace": true
},
"mcpServers": {
"example-tools": {
"env": {
"EXAMPLE_BASE_URL": "https://custom-api.example.com"
}
}
}
}
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",
};
mcp__plugin__action_resource patternUnit 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");
});
});
Every MCP server needs:
#!/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);
#!/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);
#!/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);
This skill provides comprehensive MCP server standards for Claude Code plugins:
mcp__plugin__action_resource conventionFollow these standards to create maintainable, secure, and performant MCP servers for your Claude Code plugins.
testing
A test skill for validation testing. Use when testing skill parsing and validation logic.
tools
--- name: bad-skill description: This skill has invalid YAML in frontmatter allowed-tools: [invalid, array, syntax prerequisites: not-an-array --- # Bad Skill This skill has malformed frontmatter that should fail parsing. The YAML has: - Unclosed array bracket - Wrong type for prerequisites (should be array, not string)
tools
Plugin release process for MAG Claude Plugins marketplace. Covers version bumping, marketplace.json updates, git tagging, and common mistakes. Use when releasing new plugin versions or troubleshooting update issues.
testing
Fetch trending programming models from OpenRouter rankings. Use when selecting models for multi-model review, updating model recommendations, or researching current AI coding trends. Provides model IDs, context windows, pricing, and usage statistics from the most recent week.