Skip to main content
Glama
nhannt95

Production MCP Server

by nhannt95

Production MCP Server

πŸ‡»πŸ‡³ HΖ°α»›ng dαΊ«n tiαΊΏng Việt: HUONG_DAN.md Β· ThΓͺm tool mα»›i: THEM_TOOL.md Β· ChαΊ‘y nhiều client (HTTP/SSE): CHAY_NHIEU_CLIENT.md

A production-ready, modular Model Context Protocol (MCP) server built with FastMCP. It ships 40+ tools across 8 categories and is designed to scale to 300+ tools without modifying any existing code β€” you just drop a new file into tools/<category>/ and it registers itself on startup.

Highlights

  • Clean architecture & SOLID β€” tools stay thin; logic lives in services/; cross-cutting concerns live in core/.

  • Dynamic auto-loading β€” the loader scans tools/** and calls each module's register(mcp). No manual imports, ever.

  • Standard tool template β€” every tool has a description, input validation, error handling, logging, type hints and a docstring.

  • Uniform responses β€” every tool returns { success, data, error }.

  • Security by default β€” filesystem sandbox, SSRF guard on HTTP, secret redaction, write-guarded SQL.

  • Async everywhere β€” httpx, aiofiles, and threads for blocking libs (sqlite3, openpyxl).

  • Typed config via .env (pydantic-settings) and structured logging.

  • Local AI β€” AI tools call your local model over an OpenAI-compatible API (vLLM), with an offline heuristic fallback so they work with zero config.


Related MCP server: MCP Custom Tools Server

Project structure

mcp-server/
β”œβ”€β”€ main.py                # Entrypoint: configures logging, runs the transport
β”œβ”€β”€ server.py              # Builds the FastMCP instance + runs the loader
β”œβ”€β”€ config.py              # Typed settings from .env (pydantic-settings)
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ pytest.ini
β”œβ”€β”€ .env.example
β”‚
β”œβ”€β”€ core/                  # Reusable infrastructure
β”‚   β”œβ”€β”€ logger.py          # Logging (stderr-safe for stdio), JSON option
β”‚   β”œβ”€β”€ exceptions.py      # Domain exception hierarchy
β”‚   β”œβ”€β”€ decorators.py      # @tool_handler: logging + timing + error mapping
β”‚   β”œβ”€β”€ response.py        # make_response() / make_error() envelopes
β”‚   β”œβ”€β”€ utils.py           # validate_path/validate_url/safe_read/safe_write
β”‚   └── loader.py          # Dynamic tool discovery & registration
β”‚
β”œβ”€β”€ services/              # Stateful / IO logic (reused by tools)
β”‚   β”œβ”€β”€ http_service.py    # Shared httpx client + SSRF policy
β”‚   β”œβ”€β”€ database_service.py# Pluggable DB drivers (SQLite now; PG/MySQL later)
β”‚   β”œβ”€β”€ ai_service.py      # OpenAI-compatible (vLLM) chat client
β”‚   └── text_heuristics.py # Offline fallback for AI tools
β”‚
β”œβ”€β”€ models/                # Pydantic models (typed tool outputs)
β”‚   └── common.py
β”‚
β”œβ”€β”€ tools/                 # One folder per category, one file per tool
β”‚   β”œβ”€β”€ system/            # get_system_info, get_os, get_hostname, get_cpu,
β”‚   β”‚                      #   get_memory, get_disk, get_env, current_time
β”‚   β”œβ”€β”€ filesystem/        # read/write/append/delete/move/copy/rename files,
β”‚   β”‚                      #   create/delete/list directory, search, file_info
β”‚   β”œβ”€β”€ api/               # http_get, http_post, http_put, http_delete
β”‚   β”œβ”€β”€ network/           # dns_lookup (extend freely)
β”‚   β”œβ”€β”€ database/          # execute_query, list_tables, describe_table
β”‚   β”œβ”€β”€ utilities/         # base64, uuid, md5, sha256, json_format, yaml<->json
β”‚   β”œβ”€β”€ ai/                # count_tokens, summarize_text, extract_keywords
β”‚   └── office/            # csv_reader/writer, excel_reader/writer
β”‚
β”œβ”€β”€ tests/                 # pytest suite (loader, core, ~2 tools/category)
└── examples/              # Example client + Claude/VSCode/Cursor/Windsurf configs

Quick start

# 1. Create and activate a virtual environment (Python 3.12+; 3.11 also works)
python -m venv .venv
.\.venv\Scripts\activate          # Windows PowerShell
# source .venv/bin/activate         # macOS / Linux

# 2. Install dependencies
pip install -r requirements.txt

# 3. Configure
copy .env.example .env             # Windows  (cp on macOS/Linux)
#   edit .env β€” at minimum set MCP_ALLOWED_ROOT to your working folder

# 4. Run the server (stdio transport by default)
python main.py

# 5. (optional) Run the tests
pytest

To run over HTTP instead of stdio, set MCP_TRANSPORT=http (and MCP_HOST / MCP_PORT) in .env, then python main.py.


Trying it out with the example client

The in-memory client needs no running server β€” it imports the server object directly:

python examples/mcp_client.py           # in-memory (fastest)
python examples/mcp_client.py --stdio   # spawns `python main.py` over stdio

Connecting from an IDE / desktop client

Ready-to-edit configs live in examples/. Replace the absolute paths with your own (point command at your venv's Python and args at main.py).

Client

File to edit

Example

Claude Desktop

%APPDATA%\Claude\claude_desktop_config.json (Win) / ~/Library/Application Support/Claude/… (mac)

examples/claude_desktop_config.json

VSCode

.vscode/mcp.json

examples/vscode_mcp.json

Cursor

.cursor/mcp.json or ~/.cursor/mcp.json

examples/cursor_mcp.json

Windsurf

~/.codeium/windsurf/mcp_config.json

examples/windsurf_mcp.json

After editing, restart the client. The server's tools appear in the client's tool list.


Configuration reference (.env)

All variables are prefixed MCP_. See .env.example for the full list. Key ones:

Variable

Default

Purpose

MCP_TRANSPORT

stdio

stdio | http | sse

MCP_LOG_LEVEL

INFO

Logging verbosity

MCP_ALLOWED_ROOT

current working dir

Filesystem sandbox root β€” tools cannot escape it

MCP_HTTP_ALLOW_PRIVATE_HOSTS

false

Keep false to block SSRF to private/loopback IPs

MCP_DATABASE_URL

sqlite:///./data/app.db

DB connection (SQLite today)

MCP_DB_ALLOW_WRITE

true

Allow write/DDL SQL statements

MCP_OPENAI_BASE_URL

(unset)

vLLM OpenAI-compatible URL, e.g. http://localhost:8001/v1

MCP_OPENAI_MODEL

(unset)

Model name served by vLLM


Tool categories (40+ tools)

Every tool returns the same envelope:

{ "success": true, "data": { ... }, "error": null }

...and on failure:

{ "success": false, "data": null, "error": { "code": "validation_error", "message": "...", "details": {} } }
  • System β€” get_system_info, get_os, get_hostname, get_cpu, get_memory, get_disk, get_env, current_time

  • File System (sandboxed) β€” read_file, write_file, append_file, delete_file, move_file, copy_file, rename_file, create_directory, delete_directory, list_directory, search_files, file_info

  • API (SSRF-guarded, httpx) β€” http_get, http_post, http_put, http_delete

  • Network β€” dns_lookup

  • Database (SQLite; PG/MySQL-ready) β€” execute_query, list_tables, describe_table

  • Utilities β€” base64_encode, base64_decode, uuid, md5, sha256, json_format, yaml_to_json, json_to_yaml

  • AI (local vLLM or heuristic fallback) β€” count_tokens, summarize_text, extract_keywords

  • Office β€” csv_reader, csv_writer, excel_reader, excel_writer


Using the local AI (vLLM) tools

The AI tools call a local model through an OpenAI-compatible API. Start vLLM:

python -m vllm.entrypoints.openai.api_server \
    --model Qwen/Qwen2.5-7B-Instruct \
    --port 8001

Then set in .env:

MCP_OPENAI_BASE_URL=http://localhost:8001/v1
MCP_OPENAI_API_KEY=EMPTY
MCP_OPENAI_MODEL=Qwen/Qwen2.5-7B-Instruct
  • With these set, summarize_text and extract_keywords use the LLM ("backend": "llm").

  • Without them, they fall back to a fast local heuristic ("backend": "heuristic") β€” so the tools work out of the box with no model running.

  • count_tokens always runs locally via tiktoken.


How the dynamic loader works

On startup, core/loader.py walks the tools package recursively, imports every non-private module, and calls its register(mcp) function. A broken tool is logged and skipped β€” it never takes down the server. That single convention is what makes the project scale to 300+ tools with zero edits to existing files.


Add a new tool in under a minute

See ADD_A_TOOL.md for the copy-paste template. In short:

  1. Create tools/<category>/my_tool.py.

  2. Paste the template, rename the function, write your logic.

  3. Restart the server β€” done. It auto-registers.

# tools/utilities/reverse_text.py
from __future__ import annotations
from typing import TYPE_CHECKING
from core.decorators import tool_handler

if TYPE_CHECKING:
    from fastmcp import FastMCP

def register(mcp: "FastMCP") -> None:
    @mcp.tool()
    @tool_handler
    async def reverse_text(text: str) -> dict:
        """Reverse a string."""
        return {"reversed": text[::-1]}

No imports to update, no registry to edit. That's the whole workflow.


Extending the database to PostgreSQL / MySQL

The database layer is built for this. In services/database_service.py:

  1. Implement a class satisfying the DatabaseDriver protocol (execute, list_tables, describe_table).

  2. Register it in the _DRIVERS dict by URL scheme.

  3. Point MCP_DATABASE_URL at the new backend.

The three database tools do not change β€” they depend on the abstract driver.


Docker

The image defaults to the HTTP transport (stdio can't work in a container) and exposes port 8000.

# Compose (recommended) β€” build, run, persist data in a volume
docker compose up --build

# Or plain Docker
docker build -t production-mcp-server:latest .
docker run -d --name mcp-server -p 8000:8000 -v mcp-data:/data \
  production-mcp-server:latest

The server is then reachable at http://localhost:8000/mcp. It runs as a non-root user, and SQLite + the filesystem-tool workspace live under /data (mounted as a volume). To wire in a local vLLM model, uncomment the vllm service in docker-compose.yml and set the MCP_OPENAI_* vars.


Testing

pytest            # runs the full suite
pytest -v         # verbose

The suite covers the dynamic loader, core helpers (response envelope, path/URL validation, the @tool_handler decorator), and 1–2 representative tools per category exercised end-to-end.


Security notes

  • Filesystem tools resolve every path and reject anything outside MCP_ALLOWED_ROOT (blocks ../ traversal).

  • HTTP tools reject non-http(s) schemes and, by default, any host resolving to a private/loopback/link-local address (SSRF guard). Toggle with MCP_HTTP_ALLOW_PRIVATE_HOSTS.

  • get_env redacts values whose names look secret (*key*, *token*, *password*, …).

  • SQL write/DDL statements are blocked unless MCP_DB_ALLOW_WRITE=true; use parameterised queries (?).

  • Logs go to stderr so they never corrupt the stdio JSON-RPC stream.


License

MIT (or your organisation's preferred license).

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

–Maintainers
–Response time
–Release cycle
–Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    A comprehensive MCP server with 30+ custom tools organized into categories: date/time operations, file management, system information, text processing, and web operations. Enables async communication with robust error handling and flexible CLI integration.
    Last updated
    31
    1
    MIT
  • F
    license
    B
    quality
    C
    maintenance
    An MCP server framework featuring dynamic tool loading and a modular one-tool-per-file architecture for rapid development. It supports both Stdio and HTTP transport modes, offering automated test generation and centralized configuration management.
    Last updated
    11
  • A
    license
    -
    quality
    C
    maintenance
    A production-oriented MCP server for coding agents that enables multi-project management through secure file operations, Git integration, and safe command execution. It supports project discovery across multiple root directories and provides robust audit logging with both STDIO and HTTP transport options.
    Last updated
    27
    2
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nhannt95/mcp-server-new'

If you have feedback or need assistance with the MCP directory API, please join our Discord server