home / skills / doanchienthangdev / omgkit / mcp-development

mcp-development skill

/plugin/skills/tools/mcp-development

This skill helps you design and expose MCP servers quickly with tools, resources, and prompts to extend Claude with custom capabilities.

npx playbooks add skill doanchienthangdev/omgkit --skill mcp-development

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
6.7 KB
---
name: Developing MCP Servers
description: Creates Model Context Protocol servers with tools, resources, and prompts for AI assistant integration. Use when extending Claude with custom capabilities, building AI tool integrations, or exposing APIs to AI assistants.
category: tools
triggers:
  - mcp server
  - model context protocol
  - claude tools
  - ai tools
  - mcp development
  - fastmcp
  - tool creation
---

# Developing MCP Servers

## Quick Start

```python
from fastmcp import FastMCP

mcp = FastMCP("my-service")

@mcp.tool()
def get_weather(city: str) -> str:
    """Get current weather for a city.

    Args:
        city: Name of the city to get weather for
    """
    return f"Weather in {city}: 72F, Sunny"

@mcp.resource("config://settings")
def get_settings() -> str:
    """Expose application settings as a resource."""
    return json.dumps({"theme": "dark", "language": "en"})

@mcp.prompt()
def analyze_code(code: str, language: str = "python") -> str:
    """Generate a prompt for code analysis."""
    return f"Analyze this {language} code:\n```{language}\n{code}\n```"

if __name__ == "__main__":
    mcp.run()
```

## Features

| Feature | Description | Guide |
|---------|-------------|-------|
| Tool Definition | Create callable tools with typed parameters | Use @mcp.tool() decorator with docstrings |
| Resource Exposure | Expose data resources for AI to read | Use @mcp.resource() with URI patterns |
| Prompt Templates | Define reusable prompt templates | Use @mcp.prompt() for consistent prompts |
| Dynamic Resources | Create parameterized resource URIs | Use URI templates like "user://{id}/profile" |
| Progress Reporting | Report progress for long operations | Use ctx.report_progress() in async tools |
| Streaming Results | Stream results for large outputs | Use AsyncGenerator return type |
| Lifecycle Hooks | Manage server startup and shutdown | Use lifespan context manager |
| Middleware | Add logging, rate limiting, auth | Implement middleware functions |
| Error Handling | Return structured error responses | Use try/except with error codes |
| Testing | Test tools and resources in isolation | Use MCPTestClient for unit tests |

## Common Patterns

### TypeScript MCP Server

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new Server(
  { name: "my-server", version: "1.0.0" },
  { capabilities: { tools: {}, resources: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [{
    name: "search",
    description: "Search the database",
    inputSchema: {
      type: "object",
      properties: { query: { type: "string" } },
      required: ["query"],
    },
  }],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  if (name === "search") {
    const results = await searchDatabase(args.query);
    return { content: [{ type: "text", text: JSON.stringify(results) }] };
  }
  throw new Error(`Unknown tool: ${name}`);
});

const transport = new StdioServerTransport();
await server.connect(transport);
```

### Database Integration Server

```python
from fastmcp import FastMCP
import asyncpg

mcp = FastMCP("database-server")
pool: asyncpg.Pool = None

@mcp.tool()
async def query(sql: str, params: list = None) -> dict:
    """Execute read-only SQL query."""
    if not sql.strip().upper().startswith("SELECT"):
        raise ValueError("Only SELECT queries allowed")

    async with pool.acquire() as conn:
        rows = await conn.fetch(sql, *(params or []))
        return {
            "columns": list(rows[0].keys()) if rows else [],
            "rows": [dict(row) for row in rows],
            "count": len(rows),
        }

@mcp.resource("schema://tables")
async def list_tables() -> str:
    """List all database tables."""
    async with pool.acquire() as conn:
        tables = await conn.fetch(
            "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"
        )
        return json.dumps([t["table_name"] for t in tables])
```

### Secure File System Server

```python
from pathlib import Path

mcp = FastMCP("filesystem-server")
WORKSPACE = Path(os.getenv("WORKSPACE", ".")).resolve()

def validate_path(path: str) -> Path:
    """Ensure path is within workspace."""
    full_path = (WORKSPACE / path).resolve()
    if not str(full_path).startswith(str(WORKSPACE)):
        raise ValueError("Path outside workspace")
    return full_path

@mcp.tool()
def read_file(path: str) -> str:
    """Read file contents safely."""
    file_path = validate_path(path)
    return file_path.read_text()

@mcp.tool()
def list_directory(path: str = ".") -> list[dict]:
    """List directory contents."""
    dir_path = validate_path(path)
    return [{"name": f.name, "type": "dir" if f.is_dir() else "file"} for f in dir_path.iterdir()]
```

### Testing MCP Servers

```python
import pytest
from fastmcp.testing import MCPTestClient

@pytest.fixture
def client():
    return MCPTestClient(mcp)

class TestMCPServer:
    async def test_tool_execution(self, client):
        result = await client.call_tool("get_weather", {"city": "Seattle"})
        assert result.success
        assert "Seattle" in result.content

    async def test_resource_access(self, client):
        result = await client.read_resource("config://settings")
        assert result.success
        data = json.loads(result.content)
        assert "theme" in data
```

## Best Practices

| Do | Avoid |
|----|-------|
| Document tools thoroughly with clear docstrings | Vague or missing tool descriptions |
| Validate all inputs with type hints | Trusting user input without validation |
| Return structured error responses | Exposing internal error details |
| Use async for I/O-bound operations | Blocking the event loop with sync I/O |
| Implement pagination for large results | Returning unbounded data sets |
| Add rate limiting for resource-intensive tools | Allowing unlimited API calls |
| Test tools with MCPTestClient | Skipping unit tests for tools |
| Follow MCP specification strictly | Deviating from protocol standards |
| Sanitize paths and SQL queries | Allowing path traversal or SQL injection |
| Log tool calls for debugging | Missing audit trail for operations |

## Related Skills

- **python** - Primary language for FastMCP
- **typescript** - MCP SDK for TypeScript
- **api-architecture** - API design patterns

## References

- [MCP Specification](https://spec.modelcontextprotocol.io/)
- [FastMCP Documentation](https://github.com/jlowin/fastmcp)
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
- [Claude MCP Guide](https://docs.anthropic.com/en/docs/build-with-claude/mcp)

Overview

This skill creates Model Context Protocol (MCP) servers with tools, resources, prompt templates, and integrations for AI assistant workflows. It provides a lightweight framework and examples for exposing callable tools, parameterized resources, streaming results, and lifecycle hooks. Use it to extend assistants like Claude with custom capabilities and safe external APIs.

How this skill works

The skill defines an MCP server that advertises tools and resources over the MCP protocol and handles incoming requests via request handlers or decorated functions. Tools are declared with typed parameters and docstrings, resources use URI patterns (including templates) for read access, and prompts are defined as reusable templates. It supports async I/O, progress reporting, streaming outputs, middleware, structured error responses, and testing with an MCP test client.

When to use it

  • Extending an AI assistant with domain-specific tools (search, DB queries, file ops).
  • Exposing internal APIs and datasets to assistants via secure resource URIs.
  • Building integrations that require streaming or long-running operations with progress updates.
  • Creating sandboxed endpoints for assistant-driven workflows (file browsing, read-only SQL).
  • Testing and validating tool behavior in isolation with an MCPTestClient.

Best practices

  • Document every tool and resource with clear docstrings and input schemas.
  • Validate and sanitize all inputs (SQL, filesystem paths, URIs) to prevent injection or traversal attacks.
  • Prefer async for I/O-bound tools and use streaming or pagination for large results.
  • Return structured error responses with codes; avoid leaking internal stack traces.
  • Add middleware for auth, rate limiting, and logging to maintain security and observability.
  • Write unit tests using MCPTestClient and follow the MCP specification strictly.

Example use cases

  • Database query server that exposes read-only SQL tools and a schema resource for assistants to inspect tables.
  • Secure filesystem server that lets an assistant list files and read workspace files with path validation.
  • Weather or external API tools that fetch live data and stream large responses when needed.
  • Code analysis prompt templates and tools that generate consistent prompts for assistant-driven code reviews.
  • Plugin-style service that advertises tools and resources via the TypeScript MCP SDK and connects over stdio or other transports.

FAQ

How do I expose parameterized resources?

Define resources with URI templates like "user://{id}/profile" and implement the handler to accept path params and return the resource content.

How should I handle long-running operations?

Use async tools, report progress via a context API, and stream results using async generators to keep the assistant informed and responsive.

Can I restrict tool capabilities?

Yes. Implement middleware for authentication, rate limiting, and input validation. For sensitive operations, enforce allowlists and read-only modes.