Install
$ agentstack add skill-jignesh-ponamwar-skills-mcp-mcp-server-builder ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ● Filesystem access Used
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
MCP Server Builder Skill
MCP Architecture
An MCP server exposes three primitives to AI clients:
| Primitive | Purpose | When to Use | |-----------|---------|-------------| | Tools | Callable functions the agent invokes (tools/call) | Actions, API calls, computations | | Resources | Read-only data the agent fetches (resources/read) | Files, database records, configuration | | Prompts | Reusable prompt templates (prompts/get) | Guided workflows, structured interactions |
Option A: Python with FastMCP (Recommended)
pip install fastmcp
Minimal Server
# server.py
from fastmcp import FastMCP
mcp = FastMCP("My Tools Server", version="1.0.0")
@mcp.tool(description="Add two numbers together")
def add(a: float, b: float) -> float:
"""Add two numbers and return the result."""
return a + b
@mcp.tool(description="Fetch the current weather for a city")
async def get_weather(city: str, unit: str = "celsius") -> dict:
"""Get current weather. unit: 'celsius' or 'fahrenheit'"""
import httpx
async with httpx.AsyncClient() as client:
resp = await client.get(f"https://wttr.in/{city}?format=j1")
resp.raise_for_status()
data = resp.json()
return {
"city": city,
"temperature": data["current_condition"][0]["temp_C" if unit == "celsius" else "temp_F"],
"condition": data["current_condition"][0]["weatherDesc"][0]["value"],
}
if __name__ == "__main__":
mcp.run() # stdio mode (default)
Run Modes
# stdio (for Claude Code, Cursor - recommended for local tools)
python server.py
# SSE (for browser clients and remote access)
MCP_TRANSPORT=sse python server.py # http://localhost:8000/sse
# Streamable HTTP (modern, bidirectional)
MCP_TRANSPORT=streamable-http python server.py
Resources
@mcp.resource("config://app-settings")
def get_app_settings() -> str:
"""Return current application settings."""
import json
return json.dumps({
"version": "1.0.0",
"features": ["auth", "analytics"],
"max_users": 1000,
})
@mcp.resource("file://{path}") # template URI with parameter
def read_file(path: str) -> str:
"""Read a file by path."""
import pathlib
# IMPORTANT: validate path to prevent traversal
base = pathlib.Path("./allowed_dir").resolve()
target = (base / path).resolve()
if base not in target.parents and base != target:
raise ValueError("Path outside allowed directory")
return target.read_text()
Prompts
@mcp.prompt(description="Generate a code review checklist for a PR")
def code_review_prompt(language: str, pr_description: str) -> str:
return f"""Review the following {language} pull request:
{pr_description}
Check for:
1. Correctness - does it do what it claims?
2. Security - any vulnerabilities (injection, auth issues, path traversal)?
3. Performance - any N+1 queries, unnecessary allocations?
4. Error handling - all edge cases covered?
5. Tests - adequate coverage?
Use severity tags: [CRITICAL] [HIGH] [MEDIUM] [LOW]"""
Option B: TypeScript MCP SDK
npm install @modelcontextprotocol/sdk
// server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
const server = new Server(
{ name: 'my-tools-server', version: '1.0.0' },
{ capabilities: { tools: {}, resources: {} } }
)
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'calculate',
description: 'Perform arithmetic calculations',
inputSchema: {
type: 'object',
properties: {
expression: { type: 'string', description: 'Math expression, e.g. "2 + 2"' },
},
required: ['expression'],
},
},
],
}))
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === 'calculate') {
const { expression } = request.params.arguments as { expression: string }
// Safe eval - use a proper math library in production
const result = Function(`'use strict'; return (${expression})`)()
return { content: [{ type: 'text', text: String(result) }] }
}
throw new Error(`Unknown tool: ${request.params.name}`)
})
// Start
const transport = new StdioServerTransport()
await server.connect(transport)
Tool Design Best Practices
Write Descriptions for LLMs, Not Humans
The description is what the agent reads to decide when to call the tool:
# ❌ Too vague
@mcp.tool(description="Database lookup")
def db_lookup(query: str) -> list: ...
# ✅ Tells the agent exactly when and how to use it
@mcp.tool(description="""Search the product catalog by keyword, category, or SKU.
Returns a list of matching products with id, name, price, and stock_count.
Use when the user asks about a product, wants to find items, or asks about availability.
Returns empty list if no products match. Max 20 results.""")
def search_products(query: str, category: str = "", limit: int = 20) -> list[dict]: ...
Return Structured Data
# ❌ String concatenation - hard for agent to parse
return f"User {user.name} has {len(orders)} orders totalling ${total}"
# ✅ Structured dict - agent can reason about individual fields
return {
"user": {"id": user.id, "name": user.name},
"order_count": len(orders),
"total_spent": total,
"currency": "USD",
}
Handle Errors Explicitly
@mcp.tool(description="Look up a user by email address")
async def get_user(email: str) -> dict:
user = await db.find_user_by_email(email)
if not user:
# Return structured error - don't raise (let agent handle it)
return {"found": False, "error": f"No user found with email: {email}"}
return {"found": True, "user": {"id": user.id, "name": user.name, "email": user.email}}
MCP Client Configuration
{
"mcpServers": {
"my-tools": {
"command": "python",
"args": ["server.py"],
"cwd": "/path/to/server",
"env": {
"DATABASE_URL": "postgresql://...",
"API_KEY": "sk-..."
}
}
}
}
For SSE transport:
{
"mcpServers": {
"my-tools": {
"transport": "sse",
"url": "https://my-mcp-server.com/sse"
}
}
}
Testing and Debugging
# Test with MCP Inspector
npx @modelcontextprotocol/inspector python server.py
# Or for SSE:
npx @modelcontextprotocol/inspector sse http://localhost:8000/sse
The Inspector provides a web UI to list tools, call them, and view responses without needing an AI client.
Security Checklist
- [ ] Path validation - never accept arbitrary file paths; resolve and check against a whitelist
- [ ] Input validation - validate all inputs, especially those used in SQL queries or shell commands
- [ ] No secrets in tool descriptions - descriptions are sent to the LLM; don't include credentials
- [ ] Rate limiting - protect expensive operations (API calls, DB queries) from being called in tight loops
- [ ] Authentication - for remote (SSE/HTTP) servers, require an API key or OAuth token
- [ ] Least privilege - tools should only have access to what they need
Common Mistakes
- Descriptions too short - agents use descriptions to decide when to call tools; make them specific and complete
- Raising exceptions on not-found - return a structured
{"found": false}response so the agent can handle it gracefully - Synchronous blocking I/O in async handlers - use
httpx(notrequests),asyncpg(notpsycopg2) - Not sanitizing file paths - always resolve and validate against a base directory
- Returning unstructured strings - structured dicts make it easy for agents to extract relevant fields
- No logging - add structured logging to diagnose issues when the agent calls your tools
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Jignesh-Ponamwar
- Source: Jignesh-Ponamwar/skills-mcp
- License: Apache-2.0
- Homepage: https://skills-mcp-jignesh.vercel.app/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.