1kalin/afrexai-mcp-engineering/SKILL.md
# MCP Engineering — Complete Model Context Protocol System Build, integrate, secure, and scale MCP servers and clients. From first server to production multi-tool architecture. ## When to Use - Building an MCP server (any language) - Integrating MCP tools into an AI agent - Debugging MCP connection/auth issues - Designing multi-server architectures - Securing MCP endpoints for production - Evaluating which MCP servers to use --- ## Phase 1: MCP Fundamentals ### What MCP Is Model Context Pr
npx skillsauth add openclaw/skills 1kalin/afrexai-mcp-engineeringInstall 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.
Build, integrate, secure, and scale MCP servers and clients. From first server to production multi-tool architecture.
Model Context Protocol = standardized way for AI agents to call external tools. Think of it as "USB for AI" — one protocol, any tool.
Agent (Client) ←→ MCP Transport ←→ MCP Server ←→ External Service
(stdio/HTTP) (your code) (API, DB, file system)
| Concept | What It Does | Example |
|---------|-------------|---------|
| Server | Exposes tools, resources, prompts | A server wrapping the GitHub API |
| Client | Discovers and calls server capabilities | OpenClaw, Claude Desktop, Cursor |
| Tool | A callable function with typed params | create_issue(title, body, labels) |
| Resource | Read-only data the agent can access | file://workspace/config.json |
| Prompt | Reusable prompt templates | summarize_pr(pr_url) |
| Transport | How client↔server communicate | stdio (local) or HTTP+SSE (remote) |
| Factor | stdio | HTTP/SSE | Streamable HTTP | |--------|-------|----------|-----------------| | Setup complexity | Low | Medium | Medium | | Multi-client | No | Yes | Yes | | Remote access | No | Yes | Yes | | Streaming | Via stdio | SSE | Native | | Auth needed | No (local) | Yes | Yes | | Best for | Local dev, single agent | Production, shared | Modern production |
Rule: Start with stdio for development. Move to HTTP for production or multi-agent.
server_name: "[service]-mcp"
description: "[What this server does in one sentence]"
transport: stdio | http
tools:
- name: "[verb_noun]"
description: "[What it does — be specific for LLM tool selection]"
params:
- name: "[param]"
type: "string | number | boolean | object | array"
required: true | false
description: "[What this param controls]"
returns: "[What the tool returns]"
error_cases:
- "[When/how it fails]"
resources:
- uri: "[protocol://path]"
description: "[What data this exposes]"
external_dependencies:
- "[API/service this wraps]"
auth_required: true | false
auth_method: "api_key | oauth2 | none"
// server.ts — minimal MCP server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-service",
version: "1.0.0",
});
// Define a tool
server.tool(
"get_item", // tool name (verb_noun)
"Fetch an item by ID", // description (LLM reads this)
{ id: z.string().describe("Item ID") }, // params with descriptions
async ({ id }) => {
try {
const result = await fetchItem(id);
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
}
);
// Define a resource
server.resource(
"config",
"config://app",
async (uri) => ({
contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(config) }],
})
);
// Start
const transport = new StdioServerTransport();
await server.connect(transport);
# server.py — minimal MCP server
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import json
server = Server("my-service")
@server.list_tools()
async def list_tools():
return [
Tool(
name="get_item",
description="Fetch an item by ID",
inputSchema={
"type": "object",
"properties": {
"id": {"type": "string", "description": "Item ID"}
},
"required": ["id"]
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "get_item":
result = await fetch_item(arguments["id"])
return [TextContent(type="text", text=json.dumps(result, indent=2))]
raise ValueError(f"Unknown tool: {name}")
async def main():
async with stdio_server() as (read, write):
await server.run(read, write, server.create_initialization_options())
if __name__ == "__main__":
import asyncio
asyncio.run(main())
create_issue, search_docs, update_config — never issue or doStuffsearch_issues + get_issue + create_issue beats manage_issuescreate_or_update > create (prevents duplicates from retries)import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
const app = express();
app.use(express.json());
const server = new McpServer({ name: "my-service", version: "1.0.0" });
// ... register tools ...
app.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport("/mcp", res);
await server.connect(transport);
await transport.handleRequest(req, res);
});
app.listen(3001, () => console.log("MCP server on :3001"));
// Middleware
function authMiddleware(req, res, next) {
const key = req.headers["x-api-key"] || req.headers.authorization?.replace("Bearer ", "");
if (!key || !validKeys.has(key)) {
return res.status(401).json({ error: "Invalid API key" });
}
req.userId = keyToUser.get(key);
next();
}
# MCP OAuth flow
1. Client requests tool → server returns 401 with auth URL
2. User completes OAuth in browser → gets access token
3. Client stores token, includes in subsequent requests
4. Server validates token, calls external API on user's behalf
/health)# In openclaw config — stdio server
mcpServers:
my-service:
command: "node"
args: ["path/to/server.js"]
env:
API_KEY: "{{env.MY_SERVICE_API_KEY}}"
# HTTP server
mcpServers:
my-service:
url: "https://mcp.myservice.com/mcp"
headers:
Authorization: "Bearer {{env.MY_SERVICE_TOKEN}}"
{
"mcpServers": {
"my-service": {
"command": "node",
"args": ["/path/to/server.js"],
"env": { "API_KEY": "your-key" }
}
}
}
When multiple MCP servers are connected, the agent sees ALL tools. Help the agent pick correctly:
github_search vs jira_search)Agent
├── github-mcp (code: create_pr, search_code, list_issues)
├── slack-mcp (comms: send_message, search_messages)
├── postgres-mcp (data: query, list_tables)
└── internal-mcp (business: get_customer, update_pipeline)
Principle: One server per domain. Don't build a mega-server.
/ E2E \ Agent actually uses the tool
/ Integration \ Tool calls real API (sandbox)
/ Unit \ Business logic without MCP layer
// Test the tool handler directly, no MCP transport
describe("get_item", () => {
it("returns item when found", async () => {
mockDb.findById.mockResolvedValue({ id: "123", name: "Test" });
const result = await getItemHandler({ id: "123" });
expect(result.content[0].text).toContain("Test");
});
it("returns error for missing item", async () => {
mockDb.findById.mockResolvedValue(null);
const result = await getItemHandler({ id: "missing" });
expect(result.isError).toBe(true);
});
it("handles API timeout gracefully", async () => {
mockDb.findById.mockRejectedValue(new Error("timeout"));
const result = await getItemHandler({ id: "123" });
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("try again");
});
});
# Use the MCP Inspector to manually test
npx @modelcontextprotocol/inspector node server.js
# Or use mcporter for CLI testing
mcporter call my-service.get_item id=123
mcporter list my-service --schema # verify tool schemas
Wrap an existing REST/GraphQL API as MCP tools.
External API → MCP Server → Agent
Key decisions:
Database → MCP Server → Agent
Safety rules:
File System → MCP Server → Agent
Safety rules:
../ traversal.Some tools need to orchestrate multiple steps:
server.tool("deploy_service", "Build, test, and deploy a service", {
service: z.string(),
environment: z.enum(["staging", "production"]),
}, async ({ service, environment }) => {
// Step 1: Build
const buildResult = await build(service);
if (!buildResult.success) return error(`Build failed: ${buildResult.error}`);
// Step 2: Test
const testResult = await runTests(service);
if (!testResult.success) return error(`Tests failed: ${testResult.summary}`);
// Step 3: Deploy (only if build + tests pass)
if (environment === "production") {
// Extra safety: require confirmation resource
return {
content: [{
type: "text",
text: `Ready to deploy ${service} to production. Tests: ${testResult.passed}/${testResult.total} passed. Call confirm_deploy to proceed.`
}]
};
}
const deployResult = await deploy(service, environment);
return success(`Deployed ${service} to ${environment}: ${deployResult.url}`);
});
Combine multiple data sources into unified tools:
GitHub + Jira + PagerDuty → DevOps MCP Server → Agent
One get_service_status tool that queries all three and returns a unified view.
| Threat | Risk | Mitigation | |--------|------|------------| | Prompt injection via tool output | Agent executes malicious instructions in API response | Sanitize output, strip HTML/scripts | | Excessive permissions | Tool has write access it shouldn't | Principle of least privilege per tool | | Data exfiltration | Agent sends sensitive data to wrong tool | Tool allowlists, audit logging | | Denial of service | Agent calls tool in infinite loop | Rate limiting, circuit breakers | | Credential leakage | API keys in tool responses | Strip sensitive fields from output | | SSRF | Agent provides URL that hits internal network | URL allowlisting, no private IPs |
❌ server.tool("execute_sql", ..., async ({ query }) => db.raw(query))
❌ server.tool("run_command", ..., async ({ cmd }) => exec(cmd))
❌ server.tool("fetch_url", ..., async ({ url }) => fetch(url)) // SSRF
❌ server.tool("write_file", ..., async ({ path, content }) => fs.writeFile(path, content))
✅ Parameterized queries with allowlisted tables
✅ Predefined commands with argument validation
✅ URL allowlist + no private IP ranges
✅ Write to specific directory + filename validation
| Symptom | Likely Cause | Fix |
|---------|-------------|-----|
| Tool not appearing in agent | Schema error / server not connected | Check mcporter list or client logs |
| "Connection refused" | Server not running or wrong port | Verify process, check port |
| Tool times out | External API slow or hanging | Add timeout, check API health |
| "Invalid params" | Schema mismatch between client/server | Verify schema with --schema flag |
| Agent picks wrong tool | Ambiguous descriptions | Rewrite descriptions, add "Use this when..." |
| Agent calls tool in loop | Tool returning confusing error | Return clearer error with "do NOT retry" |
| Large response crashes | No output truncation | Add pagination or character limit |
| Auth errors intermittent | Token expiry | Implement token refresh |
node server.js — does it start without errors?mcporter list my-server --schema — are all tools registered?mcporter call my-server.tool_name param=value — does it return expected output?npx @modelcontextprotocol/inspector for interactive debuggingserver.tool("my_tool", description, schema, async (params) => {
const requestId = crypto.randomUUID().slice(0, 8);
console.error(`[${requestId}] my_tool called:`, JSON.stringify(params));
const start = Date.now();
try {
const result = await doWork(params);
console.error(`[${requestId}] my_tool success: ${Date.now() - start}ms`);
return success(result);
} catch (error) {
console.error(`[${requestId}] my_tool error: ${error.message} (${Date.now() - start}ms)`);
return errorResponse(error.message);
}
});
Note: Use console.error for logs in stdio transport (stdout is reserved for MCP protocol).
Score 0-5 per dimension:
| Dimension | What to Check | |-----------|--------------| | Maintained | Last commit < 3 months? Issues addressed? Version > 1.0? | | Secure | No raw SQL/exec? Auth implemented? Input validated? | | Well-typed | Full JSON Schema for all tools? Descriptions useful? | | Tested | Has tests? CI passing? | | Documented | Setup instructions? Tool descriptions? Examples? | | Lightweight | Minimal dependencies? Fast startup? |
Score < 15/30: Build your own. Score 15-24: Use with caution. Score 25+: Good to use.
| Category | Use Case | Examples | |----------|----------|---------| | Code | GitHub, GitLab, code search | github-mcp, gitlab-mcp | | Data | PostgreSQL, SQLite, Snowflake | postgres-mcp, sqlite-mcp | | Comms | Slack, Discord, email | slack-mcp, gmail-mcp | | Docs | Notion, Confluence, Google Docs | notion-mcp, gdocs-mcp | | DevOps | AWS, GCP, Kubernetes, Terraform | aws-mcp, k8s-mcp | | Search | Brave, Google, vector stores | brave-search, rag-mcp | | Files | Local FS, S3, Google Drive | filesystem-mcp, s3-mcp | | CRM | HubSpot, Salesforce | hubspot-mcp, sfdc-mcp |
Agent ──┬── github-mcp
├── slack-mcp
├── postgres-mcp
└── custom-mcp
Best for: Most use cases. Simple, effective.
Agent ── MCP Gateway ──┬── server-1
├── server-2
└── server-3
Gateway handles: auth, rate limiting, logging, routing. Best for: Enterprise, multi-tenant, compliance requirements.
Orchestrator Agent
├── Code Agent (github-mcp, gitlab-mcp)
├── Data Agent (postgres-mcp, analytics-mcp)
└── Comms Agent (slack-mcp, email-mcp)
Best for: Complex workflows, specialized agents.
| Total Tools | Recommendation | |-------------|---------------| | 1-10 | Great. Agent handles well. | | 10-20 | Good. Ensure distinct descriptions. | | 20-30 | Caution. Group by server, review descriptions. | | 30-50 | Risk. Consider agent-per-domain pattern. | | 50+ | Dangerous. Agent WILL pick wrong tools. Split or use gateway. |
my-mcp-server/
├── src/
│ ├── server.ts # MCP server entry
│ ├── tools/ # Tool handlers
│ │ ├── search.ts
│ │ └── create.ts
│ ├── auth.ts # Auth middleware
│ └── config.ts # Configuration
├── tests/
│ ├── tools.test.ts
│ └── integration.test.ts
├── package.json
├── tsconfig.json
├── README.md # Setup + tool docs
└── LICENSE
# [Service] MCP Server
[One sentence: what this enables]
## Quick Start
[3 steps max to get running]
## Tools
| Tool | Description | Params |
|------|-------------|--------|
[Table of all tools]
## Configuration
[Env vars, auth setup]
## Examples
[2-3 real usage examples with agent conversation]
# package.json
{
"name": "@myorg/service-mcp",
"version": "1.0.0",
"bin": { "service-mcp": "./dist/server.js" },
"files": ["dist"],
"keywords": ["mcp", "model-context-protocol", "ai-tools"]
}
npm publish
| Dimension | Weight | What to Score | |-----------|--------|--------------| | Tool design | 20% | Names, descriptions, granularity, params | | Security | 20% | Auth, input validation, output sanitization, least privilege | | Reliability | 15% | Error handling, timeouts, circuit breakers | | Testing | 15% | Unit + integration coverage, edge cases | | Documentation | 10% | Setup, tool docs, examples | | Performance | 10% | Response time, output size, caching | | Maintainability | 10% | Code structure, types, logging |
Score 0-40: Not production ready. 40-70: Usable with caveats. 70-90: Solid. 90+: Excellent.
| Mistake | Fix | |---------|-----| | God-tool that does everything | Split into focused tools | | Vague tool descriptions | Write descriptions as if explaining to a new hire | | No error handling | Every external call wrapped in try/catch | | Returning raw API responses | Shape output for agent consumption | | No rate limiting | Add per-tool and per-client limits | | Ignoring output size | Paginate or truncate responses | | Hardcoded credentials | Use env vars or secret manager | | No logging | Can't debug what you can't see | | Testing only happy path | Test errors, timeouts, edge cases | | Building before checking | Search for existing MCP server first |
tools
Use when the user wants to connect to, test, or use the McDonalds service at mcp.mcd.cn, including checking authentication, probing MCP endpoints, listing tools, or calling McDonalds MCP tools through a reusable local CLI.
development
Web scraping platform — Twitter/X data, Vinted marketplace, and general web scraping API
development
SlowMist AI Agent Security Review — comprehensive security framework for skills, repositories, URLs, on-chain addresses, and products (Claude Code version)
data-ai
去除中文文本中的 AI 写作痕迹,使其读起来自然。基于维基百科 AI 写作特征指南,检测 24 种 AI 模式。触发词:humanizer-cn、去除 AI 痕迹、去除 AI 写作痕迹、中文文本人性化。