Installs into .claude/skills of the current project.
Are you the author of Mcp Server?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/loopyluci-mcp-server)
---
name: mcp-server
description: "Build MCP servers for AI agent tool integration."
tags: ["mcp", "ai-agents", "protocol"]
---
# MCP Server Construction
## When to Use This Skill
Building an MCP (Model Context Protocol) server for AI agent integration. Covers: tool registration with JSON Schema parameters, resource URI templates, prompt templates, multi-transport support (stdio, WebSocket, HTTP+SSE), and Axum-based HTTP routing.
## Core Components
An MCP server has three elements:
1. **Tools** — callable actions with typed JSON schemas (e.g., `play_track`, `search`)
2. **Resources** — URI-addressable data with RFC 6576 templates (e.g., `sovereign://track/{trackId}`)
3. **Prompts** — reusable prompt templates with named arguments for common agent workflows
## Tool Registration Pattern
### Python (recommended for build environments, quick prototypes)
Minimal JSON-RPC 2.0 server over stdin/stdout — no external dependencies beyond Python 3.10+:
```python
TOOLS = [{"name": "build", "description": "Configure and compile.", "inputSchema": {...}}]
def handle_request(request):
if request["method"] == "tools/call":
tool = request["params"]["name"]
args = request["params"].get("arguments", {})
result = TOOL_DISPATCH[tool](args)
return {"jsonrpc": "2.0", "id": request["id"],
"result": {"content": [{"type": "text", "text": json.dumps(result)}]}}
for line in sys.stdin:
resp = handle_request(json.loads(line.strip()))
if resp: sys.stdout.write(json.dumps(resp) + "\n"); sys.stdout.flush()
```
### FastMCP (Python library — recommended for most Python MCP servers)
The `mcp` package provides `FastMCP`, a high-level framework that eliminates JSON-RPC boilerplate. Tools, resources, and prompts are registered via decorators on async functions. The server handles JSON-RPC framing, transport negotiation, and capability advertisement automatically.
**Install:** `pip install 'mcp[cli]'`
#### Construction
```python
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(
"My Service",
instructions="Description shown to the agent during capability discovery.",
host="127.0.0.1", # Used by SSE transport — NOT passed to run_sse_async()
port=8090, # Same: set here, not in the run call
)
```
Key parameter notes:
- `description` is **NOT** a parameter — use `instructions` instead
- `host` and `port` are constructor kwargs, NOT arguments to `run_sse_async()`
- `host` defaults to `127.0.0.1`, `port` defaults to `8000`
#### Tool Registration
```python
@mcp.tool()
async def my_tool(param1: str, param2: int = 0) -> dict:
"""Description visible to the agent for tool selection. Keep under 200 chars."""
result = await do_something(param1, param2)
return {"status": "ok", "data": result}
```
- Parameters are auto-converted to JSON Schema from Python type hints
- Return value is serialized as JSON text content
- Functions can be sync or async
- Tool names become `mcp_{server_name}_{tool_name}` in Hermes (hyphens → underscores)
#### Resource Registration
```python
@mcp.resource("config://system")
def config_resource() -> str:
"""Description shown during resource discovery."""
import json
return json.dumps(config.to_dict(), indent=2)
```
- URI scheme and path are arbitrary but should follow `scheme://path` convention
- Resources can be sync or async
- Return value becomes the resource's text content
#### Prompt Registration
```python
@mcp.prompt()
def troubleshoot_connection() -> str:
"""Template for common agent workflows. Name becomes the prompt ID."""
return """Diagnose bot connection issues:
1. Check bot_status()
2. Run health_check()
3. Review gateway logs with gateway_log_tail()
"""
```
- Prompts are templates returned as text strings
- Can accept named arguments by adding function parameters
- Agents use prompt templates by name for recurring tasks
#### Running the Server
```python
# SSE transport (HTTP) — for remote/network-accessible servers
await mcp.run_sse_async() # host/port come from constructor
# Stdio transport — for subprocess mode (Hermes spawns it)
await mcp.run_stdio_async()
# Streamable HTTP — newer transport, check SDK version
await mcp.run_streamable_http_async()
```
- **SSE** — clients connect via `http://host:port/sse`, exchange messages over `POST /messages/`
- **Stdio** — JSON-RPC over stdin/stdout; Hermes spawns the process
- Pick SSE when you want the server to run independently; pick stdio when Hermes should manage the lifecycle
#### Discovering Registered Tools
```python
import asyncio
tools = asyncio.run(mcp.list_tools())
for t in tools:
print(f"{t.name}: {t.description}")
```
Returns a list of all registered `Tool` objects with their full schemas.
#### Example: multi-category control server pattern
```python
def register_tools(mcp):
@mcp.tool()
async def bot_status() -> dict: ...
@mcp.tool()
async def service_list() -> list[dict]: ...
def register_telemetry(mcp):
@mcp.tool()
async def telemetry_snapshot() -> dict: ...
register_telemetry(mcp)
mcp = FastMCP("Control Server", host="127.0.0.1", port=8090)
register_tools(mcp)
await mcp.run_sse_async()
```
#### Hermes Agent Configuration for FastMCP (SSE) Servers
Add to `~/.hermes/config.yaml` under the top-level `mcp_servers` key:
```yaml
mcp_servers:
my-service:
url: "http://127.0.0.1:8090/sse" # the /sse path is required
timeout: 180
connect_timeout: 60
```
Use `hermes config set` to write it:
```bash
hermes config set mcp_servers.my-service.url "http://127.0.0.1:8090/sse"
```
The `mcp_servers` section is NOT under `mcp.servers` — it's a top-level key. After adding, restart Hermes for the tools to appear. Tool names are prefixed as `mcp_my_service_tool_name`.
### Rust (recommended for production, high-throughput)
Register all tools at init time in `McpToolRegistry::register_builtin_tools()`. Each tool has `name`, `description`, and `parameters` (JSON Schema). Dispatch in `call_tool()` via match on tool name.
Key points:
- Tool descriptions must be under 200 chars — LLMs use them for routing
- Parameter schemas must be valid JSON Schema or clients reject them
- Tool parameter names must be camelCase for LLM tokenizer compatibility
- Each handler must be idempotent where possible — MCP clients retry on timeout
## Resource Template Pattern
Use URI templates (RFC 6576), not custom patterns. Dynamic templates support flexible addressing with query parameters.
## Transport Layer
- **stdio**: JSON-RPC over stdin/stdout. Best for agent subprocess (Hermes Agent).
- **WebSocket**: Persistent connection. Best for remote agents. Add ping/pong every 30s.
- **HTTP+SSE**: Long-lived streaming. Best for real-time resource updates.
## HTTP Route Pattern (Axum)
```
GET / → list all tools
POST /tools/:name → execute a tool
GET /resources → list resource templates
GET /resources/:uri → read a specific resource
GET /prompts → list prompt templates
GET /health → health check
```
## Reference Files
| File | Covers |
|---|---|
| `references/media-streaming-mcp.md` | Patterns for building MCP servers with 25+ tools in media/streaming applications — tool categorization, resource URI templates for media entities, web frontend integration with Zustand stores, agent workflow patterns. Created from the SovereignStream project. |
| `references/build-environment-mcp.md` | Python MCP server for C/C++ build environments — 8 tools (build, test, lint, analyze, file_list, file_read, syntax_check, status) with Docker integration. Stdio transport, JSON-RPC 2.0, subprocess-based tool handlers. |
| `references/network-tools-mcp.md` | Network security MCP server (adblocker + firewall) with 10 tools across Rust, Python, TypeScript, Swift, and Kotlin clients. Hardware capabilities discovery, event subscription model, WebSocket transport. Created from the Sentinel project. |
| `references/fastmcp-patterns.md` | FastMCP Python library patterns — constructor/quirk API, tool/resource/prompt registration, SSE vs stdio transport, Hermes `mcp_servers` configuration for HTTP/SSE servers, testing instructions, and known issues. Created from a 50-tool Telegram bot control server. |
| `references/cron-watcher-pattern.md` | Cron-based autonomous watcher pattern — silent-until-action background monitors that run on Hermes cron schedules, with no_agent=True watchdog mode and origin delivery. |
| `references/mcp-auto-config.md` | MCP server auto-load configuration for Hermes Agent — configuring mcp_servers in config.yaml, YAML list format pitfalls, startup discovery sequence, and transport options. |
| `references/windows-deployment.md` | Windows-specific MCP server deployment — orphan process cleanup, port contention, signal handlers, background watchdog pattern, and Hermes MCP config cleanup. Created from a Telegram bot control server on Windows. |
| `references/json-extraction-pattern.md` | Multi-line JSON parser for extracting pretty-printed JSON from mixed stdout (log lines + JSON output) — brace-depth tracking technique. |
## Auto-Discovery Manifest (Hermes Agent Integration)
To make an MCP server auto-discoverable by Hermes Agent (and other MCP-aware
clients), provide a manifest file that describes tools, resources, prompts,
and transport options.
### Manifest location conventions
| Agent | Location |
|---|---|
| Hermes Agent | `~/.hermes/mcp/<service-name>.json` |
| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| .hermes-profile/ | `./hermes-profile/<name>-mcp.yaml` |
### Manifest structure
```json
{
"name": "sovereign-stream",
"description": "Self-hosted music & video streaming with full MCP agent interface",
"version": "1.0.0",
"transport": {
"type": "websocket",
"url": "ws://localhost:3001/mcp",
"fallback": {
"type": "stdio",
"command": "sovereign-mcp",
"args": ["--transport", "stdio"]
}
},
"auto_discovery": {
"enabled": true,
"scan_ports": [3001, 3002, 3003],
"health_endpoint": "http://localhost:3001/health",
"timeout_seconds": 5
},
"capabilities": {
"tools": { "enabled": true, "count": 25 },
"resources": { "enabled": true,
"templates": [
"sovereign://tracks/{id}",
"sovereign://albums/{id}",
"sovereign://playlists/{id}"
]
},
"prompts": {
"enabled": true,
"templates": [
{"name": "play-music", "description": "Play music by voice or text",
"arguments": [{"name": "query", "type": "string"}]}
]
}
},
"integration": {
"hermes_agent": {
"auto_register": true,
"system_prompt_hint": "You have access to [service name]...",
"tools": [
{"name": "play_track", "description": "Play a specific track by ID"},
{"name": "search", "description": "Search the media library"}
]
}
}
}
```
### Auto-connect YAML (Hermes profile)
```yaml
# ~/.hermes/profiles/<name>/mcp/sovereign-stream.yaml
name: sovereign-stream
auto_connect:
enabled: true
on_startup: true
health_check:
url: "http://localhost:3001/health"
interval_seconds: 15
on_failure: reconnect
max_reconnect_attempts: -1 # infinite
transport:
ws:
url: "ws://localhost:3001/mcp"
heartbeat_interval_seconds: 30
mcp:
protocol_version: "2025-03-26"
capabilities: { tools: true, resources: true, prompts: true }
# Tools that require user confirmation
confirmations:
- "upload_music"
- "delete_playlist"
- "import_playlist"
hooks:
on_connect:
- command: "sovereign-mcp"
args: ["list-resources", "sovereign://library/status"]
```
## Pitfalls
- Tool parameter schemas must be valid JSON Schema — validate before registering
- Resource URI templates must use RFC 6576 syntax, not custom glob patterns
- Stdio transport blocks the main thread — use `tokio::spawn` for async handlers
- Rate limit MCP tool calls per session to prevent agent loops from overwhelming the server
- Tool argument count should stay under 10 for LLM reliability
- **FastMCP: `description` is rejected** — use `instructions` kwarg instead
- **FastMCP: host/port in constructor** — `run_sse_async()` does NOT accept them; set them when creating FastMCP()
- **FastMCP: `/sse` path is required** — Hermes `mcp_servers.url` must end in `/sse` for SSE transport
- **Hermes `mcp_servers` is top-level** — NOT under `mcp.servers`; wrong nesting means the server won't be discovered
- **Windows signal handlers** — `loop.add_signal_handler()` raises `NotImplementedError` on Windows; catch and skip gracefully
## Verification
1. `list_tools()` returns all registered tools with valid schemas
2. `call_tool()` with invalid arguments returns structured error (not panic)
3. `read_resource()` with unknown URI returns error response, not crash
4. WebSocket connection survives 5 min idle (ping/pong keepalive)
5. Stdio transport handles concurrent JSON-RPC messages without deadlock
6. **FastMCP SSE:** `curl -s -o /dev/null -w "%{http_code}" http://host:port/` returns 200
7. **Hermes integration:** After configuring `mcp_servers`, restart Hermes and verify tools appear with `mcp_` prefix