.claude/skills/mcp-builder/SKILL.md
Guide developers in creating Model Context Protocol (MCP) servers. Use for building MCP tools that enable LLMs to interact with external services. Covers TypeScript (primary) and Python FastMCP (secondary), tool annotations, Zod/Pydantic validation, and evaluation question creation.
npx skillsauth add oimiragieo/agent-studio mcp-builderInstall 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.
"The quality of an MCP server is measured by how well it enables LLMs to accomplish real-world tasks."
This skill guides you through creating Model Context Protocol (MCP) servers — tools that enable LLMs to interact with external services. MCP servers expose capabilities as typed tools that agents can discover and invoke.
Before writing any code, understand the target API or service thoroughly.
Study MCP protocol documentation
https://modelcontextprotocol.io/sitemap.xml for current docsAnalyze the target service
Design tool coverage
get_user, create_issue, list_reposChoose transport
TypeScript (recommended) — high-quality SDK, strong type safety, better IDE support:
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',
});
Python (FastMCP) — simpler syntax, good for Python-native services:
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel
mcp = FastMCP("my-service")
mcp-server/
├── src/
│ ├── index.ts # Server entry point
│ ├── client.ts # API client + auth
│ ├── tools/
│ │ ├── read.ts # Read-only tools
│ │ └── write.ts # Mutating tools
│ └── utils/
│ ├── pagination.ts
│ └── errors.ts
├── package.json
├── tsconfig.json
└── README.md
Every tool MUST declare intent annotations:
server.tool(
'get_repository',
'Fetch a GitHub repository by owner and name',
{
owner: z.string().describe('Repository owner (username or org)'),
repo: z.string().describe('Repository name'),
},
{
// Annotations
readOnlyHint: true, // Does not modify external state
destructiveHint: false, // Not destructive
idempotentHint: true, // Safe to call multiple times
openWorldHint: true, // Makes external network calls
},
async ({ owner, repo }) => {
// implementation
}
);
Annotation reference:
| Annotation | Type | Meaning |
| ----------------- | ------- | ----------------------------------------------- |
| readOnlyHint | boolean | Does not modify external state |
| destructiveHint | boolean | May destroy data irreversibly |
| idempotentHint | boolean | Multiple identical calls have same effect |
| openWorldHint | boolean | Interacts with external systems (network, disk) |
// Good: explicit, descriptive schemas
const schema = {
query: z.string().min(1).max(500).describe('Search query'),
limit: z.number().int().min(1).max(100).default(20).describe('Max results'),
cursor: z.string().optional().describe('Pagination cursor from previous call'),
};
from pydantic import BaseModel, Field
from typing import Optional
class SearchParams(BaseModel):
query: str = Field(..., min_length=1, max_length=500, description="Search query")
limit: int = Field(20, ge=1, le=100, description="Max results")
cursor: Optional[str] = Field(None, description="Pagination cursor")
Include both text (for human-readable output) and structured data (for agent parsing):
return {
content: [
{
type: 'text',
text: `Found ${results.length} results for "${query}"`,
},
{
type: 'text',
text: JSON.stringify(results, null, 2),
},
],
};
Always build these utilities before implementing tools:
try {
const result = await apiClient.getResource(id);
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
} catch (error) {
if (error instanceof NotFoundError) {
return {
content: [{ type: 'text', text: `Resource ${id} not found` }],
isError: true,
};
}
throw error; // Re-throw unexpected errors
}
describe() annotationsany typesTypeScript:
npm run build # Must succeed with 0 errors
npx tsc --noEmit # Type-check without output
Python:
python -m py_compile src/server.py # Syntax check
mypy src/ # Type check
# TypeScript
npx @modelcontextprotocol/inspector node dist/index.js
# Python
npx @modelcontextprotocol/inspector python src/server.py
Use MCP Inspector to:
Design 10 independent evaluation questions per server. These test whether the MCP server enables real-world tasks.
Evaluation question requirements:
Output format:
<evaluations>
<question id="1">
<task>Get the description of the 'anthropics/claude-code' repository</task>
<expected_tools>get_repository</expected_tools>
<answer>Claude Code is Anthropic's official CLI for Claude</answer>
</question>
<question id="2">
<task>List the open issues labeled 'bug' in 'anthropics/claude-code', limit to 5</task>
<expected_tools>list_issues</expected_tools>
<answer>Returns up to 5 open issues with 'bug' label</answer>
</question>
<!-- ... 8 more questions -->
</evaluations>
Before starting MCP server development:
cat .claude/context/memory/learnings.md | grep -i "mcp\|model context"
cat .claude/context/memory/issues.md | grep -i "mcp\|model context"
After completing the server, record findings:
.claude/context/memory/learnings.md.claude/context/memory/issues.md.claude/context/memory/decisions.mdThe modelcontextprotocol/servers repository provides production-ready reference implementations. Use these as starting templates before building custom servers.
# Install and run directly
npx -y @modelcontextprotocol/server-postgres postgresql://localhost/mydb
Claude Desktop config:
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"]
}
}
}
Tools provided: query (read-only SQL), list_tables, describe_table
Key implementation patterns from the reference server:
// Read-only query tool pattern
server.tool(
'query',
'Run a read-only SQL query',
{ sql: z.string().describe('SQL SELECT statement') },
{ readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
async ({ sql }) => {
// Enforce read-only by setting transaction to read-only
const result = await pool.query('BEGIN READ ONLY; ' + sql + '; COMMIT');
return {
content: [{ type: 'text', text: JSON.stringify(result.rows, null, 2) }],
};
}
);
# Install and run
npx -y @modelcontextprotocol/server-sqlite /path/to/database.db
Claude Desktop config:
{
"mcpServers": {
"sqlite": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sqlite", "/path/to/database.db"]
}
}
}
Tools provided: read_query, write_query, create_table, list_tables, describe_table, insert_row, delete_rows
Schema introspection pattern:
server.tool(
'describe_table',
'Get the schema for a table',
{ table_name: z.string().describe('Name of the table') },
{ readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
async ({ table_name }) => {
const rows = db.prepare(`PRAGMA table_info(?)`).all(table_name);
return {
content: [{ type: 'text', text: JSON.stringify(rows, null, 2) }],
};
}
);
npx -y @modelcontextprotocol/server-filesystem /allowed/path
Tools provided: read_file, write_file, list_directory, create_directory, move_file, search_files, get_file_info
Path safety pattern (copy this for any filesystem tool):
function validatePath(requestedPath: string, allowedDir: string): string {
const resolved = path.resolve(requestedPath);
if (!resolved.startsWith(path.resolve(allowedDir))) {
throw new Error(`Path ${requestedPath} is outside allowed directory`);
}
return resolved;
}
npx -y @modelcontextprotocol/server-github
# Requires: GITHUB_PERSONAL_ACCESS_TOKEN env var
Tools provided: create_repository, get_file_contents, push_files, create_issue, create_pull_request, search_repositories, search_code, fork_repository, list_commits
| Scenario | Use Official Server | Build Custom | | --------------------------------- | ------------------- | ------------ | | Standard PostgreSQL/SQLite access | YES | No | | GitHub repo operations | YES | No | | Custom API integration | No | YES | | Business-specific logic | No | YES | | Combining multiple APIs | No | YES | | Existing service with SDK | No | YES |
| Pitfall | Description | Fix |
| ----------------------------- | --------------------------------- | ----------------------------------- |
| Missing annotations | Tools without readOnlyHint etc. | Always declare all 4 annotations |
| Overly broad tools | do_everything(action, params) | One tool per distinct operation |
| Missing input descriptions | query: z.string() | Always add .describe() |
| Leaking auth tokens in errors | Logging API key in error message | Sanitize error messages |
| No pagination | Returning all results at once | Add cursor/limit to list operations |
| Blocking event loop | Synchronous I/O in Node.js | Always use async/await |
After building an MCP server, auto-detect which AI coding agents are installed on the user's system and generate the correct configuration for each. This eliminates the manual "copy this JSON into your settings" step.
Supported agents and their config locations:
| Agent | Transport | Config Path (Windows) | Config Path (macOS/Linux) |
| --------------- | --------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| Claude Code | stdio | ~/.claude.json | ~/.claude.json |
| Cursor | HTTP | %APPDATA%\Cursor\mcp_settings.json | ~/Library/Application Support/Cursor/mcp_settings.json |
| Windsurf | HTTP | %APPDATA%\Windsurf\mcp_config.json | ~/Library/Application Support/Windsurf/mcp_config.json |
| VS Code + Cline | stdio | %APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json | ~/.config/Code/User/globalStorage/... |
| IntelliJ IDEA | HTTP | %APPDATA%\JetBrains\IntelliJIdea2024.3\mcp.xml | ~/Library/Application Support/JetBrains/... |
Detection protocol:
Config generation template (Claude Code stdio example):
{
"mcpServers": {
"<server-name>": {
"command": "node",
"args": ["<path-to-server>/dist/index.js"],
"env": {}
}
}
}
This step should be part of Phase 4 (Testing & Deployment) — after the server is built and tested, offer to install it into all detected agents.
https://modelcontextprotocol.io/https://github.com/modelcontextprotocol/typescript-sdkhttps://github.com/modelcontextprotocol/python-sdkhttps://github.com/jlowin/fastmcpnpx @modelcontextprotocol/inspectortypescript-expert — TypeScript type system, SDK patternsapi-development-expert — REST API design, authentication patternstdd — Test-driven development for MCP tool testingverification-before-completion — Pre-completion quality gatestools
Comprehensive biosignal processing toolkit for analyzing physiological data including ECG, EEG, EDA, RSP, PPG, EMG, and EOG signals. Use this skill when processing cardiovascular signals, brain activity, electrodermal responses, respiratory patterns, muscle activity, or eye movements. Applicable for heart rate variability analysis, event-related potentials, complexity measures, autonomic nervous system assessment, psychophysiology research, and multi-modal physiological signal integration.
tools
Comprehensive toolkit for creating, analyzing, and visualizing complex networks and graphs in Python. Use when working with network/graph data structures, analyzing relationships between entities, computing graph algorithms (shortest paths, centrality, clustering), detecting communities, generating synthetic networks, or visualizing network topologies. Applicable to social networks, biological networks, transportation systems, citation networks, and any domain involving pairwise relationships.
data-ai
Molecular featurization for ML (100+ featurizers). ECFP, MACCS, descriptors, pretrained models (ChemBERTa), convert SMILES to features, for QSAR and molecular ML.
development
Run Python code in the cloud with serverless containers, GPUs, and autoscaling. Use when deploying ML models, running batch processing jobs, scheduling compute-intensive tasks, or serving APIs that require GPU acceleration or dynamic scaling.