Guides MCP server design, implementation, and integration with patterns for multi-server orchestration, security, and performance. Use when building or reviewing an MCP server.
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-skills-library:mcp-2025-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill covers current best practices for Model Context Protocol (MCP) server design, implementation, and integration as of late 2025.
This skill covers current best practices for Model Context Protocol (MCP) server design, implementation, and integration as of late 2025.
┌─────────────────────────────────────────────────────────────┐
│ Claude Code (Host) │
├─────────────────────────────────────────────────────────────┤
│ MCP Client Layer │
│ ├── Server Discovery & Connection │
│ ├── Tool Registration & Invocation │
│ ├── Resource Management │
│ └── Prompt Templates │
├─────────────────────────────────────────────────────────────┤
│ MCP Servers (Multiple) │
│ ├── github-mcp (repositories, issues, PRs) │
│ ├── notion-mcp (pages, databases, blocks) │
│ ├── linear-mcp (issues, projects, cycles) │
│ ├── custom-mcp (your domain-specific tools) │
│ └── ... │
└─────────────────────────────────────────────────────────────┘
// Good: Focused server for one domain
const server = new MCPServer({
name: "github-mcp",
version: "1.0.0"
});
// Tools all relate to GitHub
server.addTool("create_issue", createIssueHandler);
server.addTool("list_pulls", listPullsHandler);
server.addTool("merge_pr", mergePRHandler);
// Define resources before tools
server.addResource({
uri: "github://repos/{owner}/{repo}",
name: "Repository",
description: "GitHub repository data and metadata",
mimeType: "application/json"
});
// Tools operate on resources
server.addTool({
name: "get_repo",
description: "Fetch repository details",
inputSchema: {
type: "object",
properties: {
owner: { type: "string" },
repo: { type: "string" }
},
required: ["owner", "repo"]
}
});
// Organize tools by domain/action
const tools = {
// Issues domain
"issues_create": createIssue,
"issues_update": updateIssue,
"issues_list": listIssues,
"issues_close": closeIssue,
// PRs domain
"pulls_create": createPR,
"pulls_merge": mergePR,
"pulls_review": reviewPR,
// Repos domain
"repos_list": listRepos,
"repos_create": createRepo
};
// Good: Clear action + noun
"create_issue"
"list_repositories"
"merge_pull_request"
"search_code"
// Bad: Vague or ambiguous
"do_github"
"handle_request"
"process_data"
server.addTool({
name: "create_issue",
description: "Create a new GitHub issue with title, body, labels, and assignees",
inputSchema: {
type: "object",
properties: {
owner: {
type: "string",
description: "Repository owner (user or org)"
},
repo: {
type: "string",
description: "Repository name"
},
title: {
type: "string",
description: "Issue title",
minLength: 1,
maxLength: 256
},
body: {
type: "string",
description: "Issue body (Markdown supported)"
},
labels: {
type: "array",
items: { type: "string" },
description: "Labels to apply"
},
assignees: {
type: "array",
items: { type: "string" },
description: "GitHub usernames to assign"
}
},
required: ["owner", "repo", "title"]
}
});
// Return structured, predictable data
async function createIssue(params) {
const issue = await github.issues.create({...});
return {
success: true,
issue: {
number: issue.number,
url: issue.html_url,
title: issue.title,
state: issue.state,
created_at: issue.created_at
},
// Include actionable next steps
suggested_actions: [
`Add labels: /api/issues/${issue.number}/labels`,
`Assign team: /api/issues/${issue.number}/assignees`
]
};
}
// Store credentials in environment, not code
const server = new MCPServer({
name: "secure-server"
});
// Validate env vars at startup
const requiredEnv = ["API_KEY", "API_SECRET"];
for (const key of requiredEnv) {
if (!process.env[key]) {
throw new Error(`Missing required env var: ${key}`);
}
}
// Never log or expose credentials
server.addTool("secure_action", async (params) => {
// Use env vars directly, never pass as params
const client = new APIClient({
key: process.env.API_KEY // Not from params
});
});
import { z } from "zod";
const CreateIssueSchema = z.object({
owner: z.string().regex(/^[a-zA-Z0-9-]+$/),
repo: z.string().regex(/^[a-zA-Z0-9._-]+$/),
title: z.string().min(1).max(256),
body: z.string().max(65536).optional()
});
server.addTool("create_issue", async (params) => {
// Validate before processing
const validated = CreateIssueSchema.parse(params);
// Safe to use validated data
return await github.issues.create(validated);
});
import { RateLimiter } from "rate-limiter";
const limiter = new RateLimiter({
tokensPerInterval: 100,
interval: "minute"
});
server.addTool("api_call", async (params) => {
// Check rate limit before proceeding
if (!await limiter.tryRemoveTokens(1)) {
return {
success: false,
error: "Rate limit exceeded. Try again in a minute.",
retry_after: 60
};
}
return await performAPICall(params);
});
server.addTool("sensitive_action", async (params, context) => {
// Log all sensitive operations
await auditLog({
action: "sensitive_action",
params: sanitize(params), // Remove secrets
user: context.user,
timestamp: new Date().toISOString(),
result: "pending"
});
try {
const result = await performAction(params);
await auditLog.update({ result: "success" });
return result;
} catch (error) {
await auditLog.update({ result: "error", error: error.message });
throw error;
}
});
// In Claude Code settings, compose servers:
{
"mcpServers": {
"github": {
"command": "mcp-github",
"env": { "GITHUB_TOKEN": "..." }
},
"linear": {
"command": "mcp-linear",
"env": { "LINEAR_API_KEY": "..." }
},
"notion": {
"command": "mcp-notion",
"env": { "NOTION_TOKEN": "..." }
}
}
}
When handling complex tasks, coordinate across servers:
1. Get issue from GitHub (github-mcp)
2. Create linked Linear ticket (linear-mcp)
3. Update project doc in Notion (notion-mcp)
4. Comment back on GitHub with links (github-mcp)
Claude orchestrates automatically based on task.
// Each server should expose health endpoint
server.addTool("health_check", async () => {
const checks = await Promise.all([
checkAPIConnection(),
checkDatabaseConnection(),
checkCacheConnection()
]);
return {
status: checks.every(c => c.ok) ? "healthy" : "degraded",
checks: checks,
timestamp: new Date().toISOString()
};
});
// Reuse connections across requests
const pool = new ConnectionPool({
max: 10,
idleTimeout: 30000
});
server.addTool("db_query", async (params) => {
const conn = await pool.acquire();
try {
return await conn.query(params.sql);
} finally {
pool.release(conn);
}
});
import { LRUCache } from "lru-cache";
const cache = new LRUCache({
max: 1000,
ttl: 1000 * 60 * 5 // 5 minutes
});
server.addTool("get_user", async (params) => {
const cacheKey = `user:${params.id}`;
// Check cache first
const cached = cache.get(cacheKey);
if (cached) return cached;
// Fetch and cache
const user = await fetchUser(params.id);
cache.set(cacheKey, user);
return user;
});
// Support batch operations to reduce round trips
server.addTool("batch_create_issues", async (params) => {
const { issues } = params;
// Process in parallel with concurrency limit
const results = await pMap(
issues,
issue => createIssue(issue),
{ concurrency: 5 }
);
return {
created: results.filter(r => r.success).length,
failed: results.filter(r => !r.success).length,
results
};
});
class MCPError extends Error {
constructor(code, message, details = {}) {
super(message);
this.code = code;
this.details = details;
}
toResponse() {
return {
success: false,
error: {
code: this.code,
message: this.message,
details: this.details,
recoverable: this.isRecoverable(),
suggested_action: this.getSuggestedAction()
}
};
}
}
// Usage
throw new MCPError(
"RATE_LIMITED",
"GitHub API rate limit exceeded",
{
limit: 5000,
remaining: 0,
reset_at: "2025-12-19T12:00:00Z"
}
);
server.addTool("enriched_search", async (params) => {
const results = await primarySearch(params);
// Try to enrich, but don't fail if enrichment fails
try {
return await enrichResults(results);
} catch (enrichError) {
console.warn("Enrichment failed, returning basic results", enrichError);
return {
...results,
enrichment_status: "failed",
enrichment_error: enrichError.message
};
}
});
import { describe, it, expect, vi } from "vitest";
describe("create_issue tool", () => {
it("creates issue with valid params", async () => {
const mockGithub = vi.fn().mockResolvedValue({
number: 123,
html_url: "https://github.com/..."
});
const result = await createIssueTool({
owner: "test",
repo: "test-repo",
title: "Test issue"
}, { github: mockGithub });
expect(result.success).toBe(true);
expect(result.issue.number).toBe(123);
});
it("validates required params", async () => {
await expect(createIssueTool({ owner: "test" }))
.rejects.toThrow("Missing required: repo, title");
});
});
describe("MCP Server Integration", () => {
let server;
let client;
beforeAll(async () => {
server = await startMCPServer();
client = await connectMCPClient(server.url);
});
it("lists available tools", async () => {
const tools = await client.listTools();
expect(tools).toContain("create_issue");
expect(tools).toContain("list_repositories");
});
it("executes tool and returns result", async () => {
const result = await client.callTool("health_check", {});
expect(result.status).toBe("healthy");
});
});
mcp__<server>__<action> formatMCP servers extend Claude's capabilities with real-world integrations. Design them with clear responsibility, robust error handling, and security in mind.
npx claudepluginhub frankxai/claude-skills-library --plugin claude-skills-libraryDesigns and implements Model Context Protocol servers with resources, tools, prompts, and security best practices. Use when architecting an MCP server or integrating a service as an MCP endpoint.
Provides patterns for building secure MCP servers with OAuth auth, tool composition, elicitation, sampling, interactive UIs, and debugging. Use for MCP server development and integrations.
Provides patterns, architecture diagrams, and decision trees for building, testing, and deploying Model Context Protocol (MCP) servers in Python and TypeScript with tools, resources, prompts, and transports like stdio, SSE, streamable HTTP.